@karmaniverous/jeeves-watcher 0.18.8 → 0.18.10

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createConfigQueryHandler as createConfigQueryHandler$1, createStatusHandler, createConfigApplyHandler, DEFAULT_PORTS, getBindAddress } from '@karmaniverous/jeeves';
3
- import { a as normalizeSlashes, n as normalizeError, r as retry, G as GitignoreFilter, e as executeReindex, V as VALID_REINDEX_SCOPES, m as mergeInferenceRules, i as isPathWatched, g as getWatchRootBases, C as CONFIG_WATCH_VALID_SCOPES, s as sleep, l as loadConfig } from './index.js';
4
- export { b as startFromConfig } from './index.js';
3
+ import { d as normalizeSlashes, n as normalizeError, e as execFileAsync, r as retry, b as gitAddViaStdin, c as normalizePathCase, f as findRootForPath, G as GitignoreFilter, a as executeReindex, V as VALID_REINDEX_SCOPES, m as mergeInferenceRules, i as isPathWatched, g as getWatchRootBases, C as CONFIG_WATCH_VALID_SCOPES, s as sleep, l as loadConfig } from './index.js';
4
+ export { h as startFromConfig } from './index.js';
5
5
  import { mkdirSync, existsSync, readFileSync, writeFileSync, rmSync, renameSync, readdirSync, statSync } from 'node:fs';
6
6
  import { join, dirname, resolve, extname, basename, relative, sep, isAbsolute } from 'node:path';
7
7
  import Database from 'better-sqlite3';
@@ -10,11 +10,9 @@ import Ajv from 'ajv';
10
10
  import addFormats from 'ajv-formats';
11
11
  import picomatch from 'picomatch';
12
12
  import { deserialize, serialize } from 'node:v8';
13
- import { normalizeWatchPaths, extractWatchPathStrings, vcsAuthorConfigSchema, inferenceRuleSchema, jeevesWatcherConfigSchema } from '@karmaniverous/jeeves-watcher-core';
14
- import { execFile } from 'node:child_process';
15
- import { promisify } from 'node:util';
13
+ import { normalizeWatchPaths, vcsRetentionConfigSchema, extractWatchPathStrings, vcsAuthorConfigSchema, inferenceRuleSchema, jeevesWatcherConfigSchema, getEndpoint } from '@karmaniverous/jeeves-watcher-core';
16
14
  import { homedir } from 'node:os';
17
- import { access, rm, readFile, writeFile, appendFile, mkdir, stat } from 'node:fs/promises';
15
+ import { access, rm, stat, readFile, writeFile, appendFile, mkdir } from 'node:fs/promises';
18
16
  import { pathToFileURL, fileURLToPath } from 'node:url';
19
17
  import { packageDirectorySync } from 'package-directory';
20
18
  import { JsonMap } from '@karmaniverous/jsonmap';
@@ -40,6 +38,8 @@ import mammoth from 'mammoth';
40
38
  import { MarkdownTextSplitter, RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
41
39
  import { QdrantClient } from '@qdrant/js-client-rest';
42
40
  import 'cosmiconfig';
41
+ import 'node:child_process';
42
+ import 'node:util';
43
43
 
44
44
  /**
45
45
  * @module api/InitialScanTracker
@@ -928,77 +928,6 @@ class CommitMessageGenerator {
928
928
  }
929
929
  }
930
930
 
931
- /**
932
- * @module vcs/gitExec
933
- * Shared git execution utilities.
934
- */
935
- /** Promisified execFile for git commands. */
936
- const execFileAsync = promisify(execFile);
937
- /**
938
- * Stage files via stdin to avoid ENAMETOOLONG on Windows.
939
- *
940
- * Uses `git add --pathspec-from-file=- --pathspec-file-nul` so the file list is piped
941
- * through stdin (NUL-delimited) instead of passed as command-line
942
- * arguments. This sidesteps the Windows CreateProcessW 32 767-char
943
- * limit that triggers ENAMETOOLONG when batches contain many files
944
- * with long absolute paths.
945
- *
946
- * @param files - Absolute paths of files to stage.
947
- * @param cwd - Repository root (working directory for git).
948
- */
949
- function gitAddViaStdin(files, cwd) {
950
- return new Promise((resolve, reject) => {
951
- const child = execFile('git', ['add', '--pathspec-from-file=-', '--pathspec-file-nul'], { cwd }, (error) => {
952
- if (error)
953
- reject(error);
954
- else
955
- resolve();
956
- });
957
- if (!child.stdin) {
958
- reject(new Error('Failed to open stdin for git add'));
959
- return;
960
- }
961
- // Suppress EPIPE — expected if git exits before we finish writing
962
- child.stdin.on('error', (err) => {
963
- if (err.code !== 'EPIPE') {
964
- reject(err);
965
- }
966
- });
967
- child.stdin.end(files.join('\0'));
968
- });
969
- }
970
- /**
971
- * Normalize a path for case-insensitive comparison on Windows.
972
- * On Windows, lowercases the entire path; on other platforms, returns as-is.
973
- *
974
- * @param p - The path string to normalize.
975
- * @param platform - The platform to check against (default: `process.platform`).
976
- * @returns The normalized path.
977
- */
978
- function normalizePathCase(p, platform = process.platform) {
979
- return platform === 'win32' ? p.toLowerCase() : p;
980
- }
981
- /**
982
- * Find the longest-prefix-match root for a normalized path.
983
- * Roots must be sorted longest-first for correct nested matching.
984
- *
985
- * @param roots - Root paths sorted longest-first.
986
- * @param normalizedPath - Normalized absolute path (forward slashes).
987
- * @param platform - The platform to check against (default: `process.platform`).
988
- * @returns The matching root, or undefined.
989
- */
990
- function findRootForPath(roots, normalizedPath, platform = process.platform) {
991
- const comparePath = normalizePathCase(normalizedPath, platform);
992
- for (const root of roots) {
993
- const compareRoot = normalizePathCase(root, platform);
994
- if (comparePath === compareRoot ||
995
- comparePath.startsWith(compareRoot + '/')) {
996
- return root;
997
- }
998
- }
999
- return undefined;
1000
- }
1001
-
1002
931
  /**
1003
932
  * @module vcs/resolveCommitMessageApiKey
1004
933
  * Resolves the API key for AI commit message generation.
@@ -1460,7 +1389,7 @@ async function pushToRemote(rootPath, remoteUrl, accessToken, pushErrors, logger
1460
1389
  * Per-root VCS manager for git-backed content versioning.
1461
1390
  *
1462
1391
  * Each VCS-enabled watch root gets its own VcsManager instance, which owns
1463
- * a debounced commit pipeline: file changes are batched, staged, committed
1392
+ * a throttled commit pipeline: file changes are batched, staged, committed
1464
1393
  * (with optional AI-generated messages), and optionally pushed to a remote.
1465
1394
  *
1466
1395
  * Concurrency: only one commit is in-flight at a time per root. Index.lock
@@ -1477,8 +1406,9 @@ class VcsManager {
1477
1406
  pendingReversions = [];
1478
1407
  _pushErrors = [];
1479
1408
  squashManager;
1480
- debounceTimer;
1409
+ throttleTimer;
1481
1410
  commitInFlight = Promise.resolve();
1411
+ consecutiveCommitFailures = 0;
1482
1412
  started = false;
1483
1413
  isBaseline = true;
1484
1414
  _lastPushTime = null;
@@ -1519,7 +1449,7 @@ class VcsManager {
1519
1449
  this.isBaseline = false;
1520
1450
  }
1521
1451
  /**
1522
- * Add a file to the pending set and reset the debounce timer.
1452
+ * Add a file to the pending set and start the throttle timer.
1523
1453
  * If the pending set exceeds maxBatchSize, flush immediately.
1524
1454
  *
1525
1455
  * @param filePath - Absolute path of the changed file.
@@ -1527,17 +1457,22 @@ class VcsManager {
1527
1457
  fileChanged(filePath) {
1528
1458
  if (!this.started)
1529
1459
  return;
1460
+ // Reset circuit breaker on new file change so the system can recover
1461
+ if (this.consecutiveCommitFailures >= this.config.maxConsecutiveFailures) {
1462
+ this.logger.info({ root: this.rootPath }, 'VCS circuit breaker reset — new file change received, retrying commits');
1463
+ this.consecutiveCommitFailures = 0;
1464
+ }
1530
1465
  this.pending.add(filePath);
1531
1466
  if (this.pending.size >= this.config.maxBatchSize) {
1532
- this.clearDebounce();
1467
+ this.clearThrottle();
1533
1468
  const batch = this.takeBatch(this.config.maxBatchSize);
1534
1469
  this.commitInFlight = this.commitInFlight.then(() => this.commitBatch(batch));
1535
1470
  if (this.pending.size > 0) {
1536
- this.resetDebounce();
1471
+ this.resetThrottle();
1537
1472
  }
1538
1473
  return;
1539
1474
  }
1540
- this.resetDebounce();
1475
+ this.resetThrottle();
1541
1476
  }
1542
1477
  /**
1543
1478
  * Record reversion metadata so the next commit uses a revert-prefixed message.
@@ -1562,7 +1497,7 @@ class VcsManager {
1562
1497
  * Flush all pending files immediately.
1563
1498
  */
1564
1499
  async flush() {
1565
- this.clearDebounce();
1500
+ this.clearThrottle();
1566
1501
  if (this.pending.size > 0) {
1567
1502
  const batch = [...this.pending];
1568
1503
  this.pending.clear();
@@ -1595,31 +1530,65 @@ class VcsManager {
1595
1530
  return batch;
1596
1531
  }
1597
1532
  /**
1598
- * Clear the debounce timer.
1533
+ * Clear the throttle timer.
1599
1534
  */
1600
- clearDebounce() {
1601
- if (this.debounceTimer !== undefined) {
1602
- clearTimeout(this.debounceTimer);
1603
- this.debounceTimer = undefined;
1535
+ clearThrottle() {
1536
+ if (this.throttleTimer !== undefined) {
1537
+ clearTimeout(this.throttleTimer);
1538
+ this.throttleTimer = undefined;
1604
1539
  }
1605
1540
  }
1606
1541
  /**
1607
- * Reset the debounce timer to fire flush() after commitDebounceMs.
1542
+ * Start the throttle timer to fire flush() after commitThrottleMs.
1543
+ * If a timer is already running, do not reset it — this is throttle, not debounce.
1608
1544
  */
1609
- resetDebounce() {
1610
- this.clearDebounce();
1611
- this.debounceTimer = setTimeout(() => {
1612
- this.debounceTimer = undefined;
1545
+ resetThrottle() {
1546
+ if (this.throttleTimer !== undefined)
1547
+ return;
1548
+ this.throttleTimer = setTimeout(() => {
1549
+ this.throttleTimer = undefined;
1613
1550
  void this.flush();
1614
- }, this.config.commitDebounceMs);
1551
+ }, this.config.commitThrottleMs);
1615
1552
  }
1616
1553
  /**
1617
- * Commit a batch of files to the git repo with index.lock retry.
1554
+ * Check for and remove stale index.lock files before retrying.
1555
+ */
1556
+ async removeStaleLock() {
1557
+ const lockPath = join(this.rootPath, '.git', 'index.lock');
1558
+ try {
1559
+ const lockStat = await stat(lockPath);
1560
+ const ageMs = Date.now() - lockStat.mtimeMs;
1561
+ if (ageMs > this.config.staleLockThresholdMs) {
1562
+ await rm(lockPath, { force: true });
1563
+ this.logger.warn({ root: this.rootPath, ageMs }, 'Removed stale index.lock');
1564
+ }
1565
+ }
1566
+ catch (error) {
1567
+ const code = error.code;
1568
+ if (code !== 'ENOENT') {
1569
+ this.logger.warn({ root: this.rootPath, err: normalizeError(error) }, 'Unable to remove stale index.lock');
1570
+ }
1571
+ }
1572
+ }
1573
+ /**
1574
+ * Commit a batch of files to the git repo with index.lock retry,
1575
+ * stale lock detection, circuit breaker, and re-queue cap.
1618
1576
  */
1619
1577
  async commitBatch(files) {
1620
1578
  if (files.length === 0)
1621
1579
  return;
1580
+ // Circuit breaker: skip if too many consecutive failures
1581
+ if (this.consecutiveCommitFailures >= this.config.maxConsecutiveFailures) {
1582
+ this.logger.error({
1583
+ root: this.rootPath,
1584
+ consecutiveFailures: this.consecutiveCommitFailures,
1585
+ discardedFiles: files.length,
1586
+ }, 'VCS circuit breaker tripped — pending files discarded until next successful commit');
1587
+ return;
1588
+ }
1622
1589
  try {
1590
+ // Check for stale lock before first attempt
1591
+ await this.removeStaleLock();
1623
1592
  await retry(async (attempt) => {
1624
1593
  await gitAddViaStdin(files, this.rootPath);
1625
1594
  // Build message after staging so getStagedDiff can see the changes.
@@ -1645,12 +1614,22 @@ class VcsManager {
1645
1614
  this.logger.warn({ root: this.rootPath, attempt, delay: delayMs }, 'index.lock contention, retrying');
1646
1615
  },
1647
1616
  });
1617
+ // Success — reset circuit breaker
1618
+ this.consecutiveCommitFailures = 0;
1648
1619
  }
1649
1620
  catch (error) {
1650
- for (const f of files) {
1621
+ this.consecutiveCommitFailures++;
1622
+ // Re-queue cap: only re-queue up to maxBatchSize files
1623
+ const cap = this.config.maxBatchSize;
1624
+ const toRequeue = files.slice(0, cap);
1625
+ const overflow = files.length - toRequeue.length;
1626
+ for (const f of toRequeue) {
1651
1627
  this.pending.add(f);
1652
1628
  }
1653
- this.logger.warn({ root: this.rootPath, fileCount: files.length }, 'Re-queued files after commit failure');
1629
+ if (overflow > 0) {
1630
+ this.logger.warn({ root: this.rootPath, overflow, cap }, 'Re-queue cap exceeded — discarded overflow files');
1631
+ }
1632
+ this.logger.warn({ root: this.rootPath, fileCount: toRequeue.length }, 'Re-queued files after commit failure');
1654
1633
  this.logger.error({ root: this.rootPath, err: normalizeError(error) }, 'VCS commit failed');
1655
1634
  }
1656
1635
  }
@@ -1682,10 +1661,14 @@ class VcsCoordinator {
1682
1661
  // Deduplicate: skip if another glob already resolved to this root.
1683
1662
  if (this.managers.has(rootKey))
1684
1663
  continue;
1664
+ const mergedRetention = entry.vcs?.retention ??
1665
+ config.vcs.retention ??
1666
+ vcsRetentionConfigSchema.parse({});
1685
1667
  const mergedConfig = {
1686
1668
  ...config.vcs,
1687
1669
  ...entry.vcs,
1688
1670
  enabled: true,
1671
+ retention: mergedRetention,
1689
1672
  };
1690
1673
  // Create CommitMessageGenerator if AI commit messages are configured
1691
1674
  const cmConfig = mergedConfig.commitMessage;
@@ -1736,8 +1719,8 @@ class VcsCoordinator {
1736
1719
  /**
1737
1720
  * Signal that the initial filesystem scan is complete.
1738
1721
  *
1739
- * Flushes each manager's debounce buffer before ending baseline mode, ensuring
1740
- * any files still pending in the VCS debounce are committed with a
1722
+ * Flushes each manager's throttle buffer before ending baseline mode, ensuring
1723
+ * any files still pending in the VCS throttle are committed with a
1741
1724
  * `"baseline:"` prefix rather than `"watcher:"`. AI generation is not enabled
1742
1725
  * until all managers have completed their flush.
1743
1726
  */
@@ -3179,10 +3162,10 @@ function createWatcher(config, factories, queue, processor, logger, runtimeOptio
3179
3162
  * Tear down and rebuild the filesystem watcher with new config.
3180
3163
  * Falls back to the old watcher if the new one fails to start.
3181
3164
  */
3182
- async function rebuildWatcher(newConfig, factories, queue, processor, logger, runtimeOptions, oldState, initialScanTracker, contentHashCache) {
3165
+ async function rebuildWatcher(newConfig, factories, queue, processor, logger, runtimeOptions, oldState, initialScanTracker, contentHashCache, onVcsFileChange, onInitialScanComplete) {
3183
3166
  try {
3184
3167
  await oldState.watcher.stop();
3185
- const { watcher: newWatcher, gitignoreFilter: newGitignoreFilter } = createWatcher(newConfig, factories, queue, processor, logger, runtimeOptions, initialScanTracker, contentHashCache);
3168
+ const { watcher: newWatcher, gitignoreFilter: newGitignoreFilter } = createWatcher(newConfig, factories, queue, processor, logger, runtimeOptions, initialScanTracker, contentHashCache, onVcsFileChange, onInitialScanComplete);
3186
3169
  newWatcher.start();
3187
3170
  logger.info('Filesystem watcher rebuilt successfully');
3188
3171
  return { watcher: newWatcher, gitignoreFilter: newGitignoreFilter };
@@ -3377,7 +3360,7 @@ async function reloadConfig(state, deps) {
3377
3360
  // Rebuild filesystem watcher if watch config changed
3378
3361
  if (watchConfigChanged(oldConfig, newConfig)) {
3379
3362
  logger.info('Watch config changed, rebuilding filesystem watcher...');
3380
- const newState = await rebuildWatcher(newConfig, deps.factories, deps.queue, processor, logger, deps.runtimeOptions, { watcher: state.watcher, gitignoreFilter: state.gitignoreFilter }, deps.initialScanTracker, deps.contentHashCache);
3363
+ const newState = await rebuildWatcher(newConfig, deps.factories, deps.queue, processor, logger, deps.runtimeOptions, { watcher: state.watcher, gitignoreFilter: state.gitignoreFilter }, deps.initialScanTracker, deps.contentHashCache, deps.onVcsFileChange, deps.onInitialScanComplete);
3381
3364
  state.watcher = newState.watcher;
3382
3365
  state.gitignoreFilter = newState.gitignoreFilter;
3383
3366
  }
@@ -5431,36 +5414,36 @@ function createApiServer(options) {
5431
5414
  };
5432
5415
  },
5433
5416
  });
5434
- app.get('/status', withCache(cacheTtlMs, async () => {
5417
+ app.get(getEndpoint('status').path, withCache(cacheTtlMs, async () => {
5435
5418
  const result = await coreStatusHandler();
5436
5419
  return result.body;
5437
5420
  }));
5438
- app.post('/metadata', createMetadataHandler({
5421
+ app.post(getEndpoint('metadata').path, createMetadataHandler({
5439
5422
  processor,
5440
5423
  getConfig,
5441
5424
  logger,
5442
5425
  configDir: dirname(configPath),
5443
5426
  }));
5444
- app.post('/render', withCache(cacheTtlMs, createRenderHandler({
5427
+ app.post(getEndpoint('render').path, withCache(cacheTtlMs, createRenderHandler({
5445
5428
  processor,
5446
5429
  getWatch: () => getConfig().watch,
5447
5430
  logger,
5448
5431
  })));
5449
- app.get('/search/facets', createFacetsHandler({
5432
+ app.get(getEndpoint('searchFacets').path, createFacetsHandler({
5450
5433
  getConfig,
5451
5434
  valuesManager,
5452
5435
  configDir: dirname(configPath),
5453
5436
  }));
5454
- app.post('/scan', createScanHandler({
5437
+ app.post(getEndpoint('scan').path, createScanHandler({
5455
5438
  vectorStore,
5456
5439
  logger,
5457
5440
  }));
5458
- app.post('/walk', createWalkHandler({
5441
+ app.post(getEndpoint('walk').path, createWalkHandler({
5459
5442
  getWatchPaths: () => extractWatchPathStrings(getConfig().watch.paths),
5460
5443
  getFileSystemWatcher,
5461
5444
  logger,
5462
5445
  }));
5463
- app.post('/search', createSearchHandler({
5446
+ app.post(getEndpoint('search').path, createSearchHandler({
5464
5447
  embeddingProvider,
5465
5448
  vectorStore,
5466
5449
  logger,
@@ -5471,12 +5454,12 @@ function createApiServer(options) {
5471
5454
  : undefined;
5472
5455
  },
5473
5456
  }));
5474
- app.post('/rebuild-metadata', createRebuildMetadataHandler({
5457
+ app.post(getEndpoint('rebuildMetadata').path, createRebuildMetadataHandler({
5475
5458
  enrichmentStore: options.enrichmentStore,
5476
5459
  vectorStore,
5477
5460
  logger,
5478
5461
  }));
5479
- app.post('/reindex', createConfigReindexHandler({
5462
+ app.post(getEndpoint('reindex').path, createConfigReindexHandler({
5480
5463
  getConfig,
5481
5464
  processor,
5482
5465
  logger,
@@ -5487,10 +5470,10 @@ function createApiServer(options) {
5487
5470
  vectorStore,
5488
5471
  queue,
5489
5472
  }));
5490
- app.get('/issues', withCache(cacheTtlMs, createIssuesHandler({ issuesManager })));
5491
- app.get('/config/schema', withCache(cacheTtlMs, createConfigSchemaHandler()));
5492
- app.post('/config/match', createConfigMatchHandler({ getConfig, logger }));
5493
- app.get('/config', withCache(cacheTtlMs, createConfigQueryHandler({
5473
+ app.get(getEndpoint('issues').path, withCache(cacheTtlMs, createIssuesHandler({ issuesManager })));
5474
+ app.get(getEndpoint('configSchema').path, withCache(cacheTtlMs, createConfigSchemaHandler()));
5475
+ app.post(getEndpoint('configMatch').path, createConfigMatchHandler({ getConfig, logger }));
5476
+ app.get(getEndpoint('config').path, withCache(cacheTtlMs, createConfigQueryHandler({
5494
5477
  getConfig,
5495
5478
  valuesManager,
5496
5479
  issuesManager,
@@ -5500,7 +5483,7 @@ function createApiServer(options) {
5500
5483
  ? () => virtualRuleStore.getAll()
5501
5484
  : undefined,
5502
5485
  })));
5503
- app.post('/config/validate', createConfigValidateHandler({
5486
+ app.post(getEndpoint('configValidate').path, createConfigValidateHandler({
5504
5487
  getConfig,
5505
5488
  logger,
5506
5489
  configDir: dirname(configPath),
@@ -5514,7 +5497,7 @@ function createApiServer(options) {
5514
5497
  return Promise.resolve();
5515
5498
  },
5516
5499
  });
5517
- app.post('/config/apply', async (request, reply) => {
5500
+ app.post(getEndpoint('configApply').path, async (request, reply) => {
5518
5501
  const { patch, replace } = request.body;
5519
5502
  const result = await coreConfigApplyHandler({ patch, replace });
5520
5503
  return reply.status(result.status).send(result.body);
@@ -5533,34 +5516,34 @@ function createApiServer(options) {
5533
5516
  gitignoreFilter,
5534
5517
  vectorStore,
5535
5518
  });
5536
- app.post('/rules/register', createRulesRegisterHandler({
5519
+ app.post(getEndpoint('rulesRegister').path, createRulesRegisterHandler({
5537
5520
  virtualRuleStore,
5538
5521
  logger,
5539
5522
  onRulesChanged,
5540
5523
  }));
5541
- app.delete('/rules/unregister', createRulesUnregisterHandler({
5524
+ app.delete(getEndpoint('rulesUnregister').path, createRulesUnregisterHandler({
5542
5525
  virtualRuleStore,
5543
5526
  logger,
5544
5527
  onRulesChanged,
5545
5528
  }));
5546
- app.delete('/rules/unregister/:source', createRulesUnregisterParamHandler({
5529
+ app.delete(getEndpoint('rulesUnregisterBySource').path, createRulesUnregisterParamHandler({
5547
5530
  virtualRuleStore,
5548
5531
  logger,
5549
5532
  onRulesChanged,
5550
5533
  }));
5551
- app.post('/points/delete', createPointsDeleteHandler({ vectorStore, logger }));
5552
- app.post('/rules/reapply', createRulesReapplyHandler({ processor, vectorStore, logger }));
5534
+ app.post(getEndpoint('pointsDelete').path, createPointsDeleteHandler({ vectorStore, logger }));
5535
+ app.post(getEndpoint('rulesReapply').path, createRulesReapplyHandler({ processor, vectorStore, logger }));
5553
5536
  }
5554
5537
  // VCS routes — only registered when a VcsCoordinator is provided
5555
5538
  if (options.vcsCoordinator) {
5556
5539
  const coordinator = options.vcsCoordinator;
5557
- app.get('/vcs/status', createVcsStatusHandler({ coordinator, logger }));
5558
- app.get('/vcs/history', createVcsHistoryHandler({ coordinator, logger }));
5559
- app.get('/vcs/show', createVcsShowHandler({ coordinator, logger }));
5560
- app.get('/vcs/diff', createVcsDiffHandler({ coordinator, logger }));
5561
- app.get('/vcs/check-exclusion', createVcsCheckExclusionHandler({ coordinator, logger }));
5562
- app.post('/vcs/revert', createVcsRevertHandler({ coordinator, logger }));
5563
- app.post('/vcs/exclude', createVcsExcludeHandler({ coordinator, logger }));
5540
+ app.get(getEndpoint('vcsStatus').path, createVcsStatusHandler({ coordinator, logger }));
5541
+ app.get(getEndpoint('vcsHistory').path, createVcsHistoryHandler({ coordinator, logger }));
5542
+ app.get(getEndpoint('vcsShow').path, createVcsShowHandler({ coordinator, logger }));
5543
+ app.get(getEndpoint('vcsDiff').path, createVcsDiffHandler({ coordinator, logger }));
5544
+ app.get(getEndpoint('vcsCheckExclusion').path, createVcsCheckExclusionHandler({ coordinator, logger }));
5545
+ app.post(getEndpoint('vcsRevert').path, createVcsRevertHandler({ coordinator, logger }));
5546
+ app.post(getEndpoint('vcsExclude').path, createVcsExcludeHandler({ coordinator, logger }));
5564
5547
  }
5565
5548
  return app;
5566
5549
  }
@@ -7989,6 +7972,8 @@ class JeevesWatcher {
7989
7972
  contentHashCache: this.contentHashCache,
7990
7973
  valuesManager: this.valuesManager,
7991
7974
  issuesManager: this.issuesManager,
7975
+ onVcsFileChange: (filePath, event) => this.vcsCoordinator?.onFileChange(filePath, event),
7976
+ onInitialScanComplete: () => this.vcsCoordinator?.onInitialScanComplete(),
7992
7977
  });
7993
7978
  this.config = state.config;
7994
7979
  this.watcher = state.watcher;