@karmaniverous/jeeves-watcher 0.18.9 → 0.18.11
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/README.md +4 -2
- package/config.schema.json +203 -180
- package/dist/cli/jeeves-watcher/{index-xP2XrV4W.js → index-ZbXuE9a7.js} +335 -182
- package/dist/cli/jeeves-watcher/index.js +48 -3
- package/dist/index.d.ts +56 -13
- package/dist/index.js +291 -93
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join, dirname, resolve, relative, extname, basename, isAbsolute, sep } from 'node:path';
|
|
2
2
|
import { createConfigQueryHandler as createConfigQueryHandler$1, createStatusHandler, createConfigApplyHandler, substituteEnvVars, DEFAULT_PORTS, getBindAddress, postJson, fetchJson, getPackageVersion } from '@karmaniverous/jeeves';
|
|
3
|
-
import { extractWatchPathStrings, inferenceRuleSchema, jeevesWatcherConfigSchema, normalizeWatchPaths, vcsRetentionConfigSchema, vcsAuthorConfigSchema, LOGGING_DEFAULTS, API_DEFAULTS, EMBEDDING_DEFAULTS, CONFIG_WATCH_DEFAULTS, WATCH_DEFAULTS, ROOT_DEFAULTS, INIT_CONFIG_TEMPLATE, DEFAULT_PORT as DEFAULT_PORT$1, PLUGIN_PACKAGE, SERVICE_PACKAGE, COMPONENT_NAME } from '@karmaniverous/jeeves-watcher-core';
|
|
3
|
+
import { extractWatchPathStrings, inferenceRuleSchema, jeevesWatcherConfigSchema, normalizeWatchPaths, vcsRetentionConfigSchema, vcsAuthorConfigSchema, getEndpoint, LOGGING_DEFAULTS, API_DEFAULTS, EMBEDDING_DEFAULTS, CONFIG_WATCH_DEFAULTS, WATCH_DEFAULTS, ROOT_DEFAULTS, INIT_CONFIG_TEMPLATE, DEFAULT_PORT as DEFAULT_PORT$1, PLUGIN_PACKAGE, SERVICE_PACKAGE, COMPONENT_NAME } from '@karmaniverous/jeeves-watcher-core';
|
|
4
4
|
export { apiConfigSchema, configWatchConfigSchema, embeddingConfigSchema, inferenceRuleSchema, jeevesWatcherConfigSchema, loggingConfigSchema, vectorStoreConfigSchema, watchConfigSchema } from '@karmaniverous/jeeves-watcher-core';
|
|
5
5
|
import Fastify from 'fastify';
|
|
6
6
|
import { readdir, stat, access, readFile, writeFile, mkdir, appendFile, rm } from 'node:fs/promises';
|
|
@@ -427,6 +427,39 @@ async function retry(fn, options) {
|
|
|
427
427
|
*/
|
|
428
428
|
/** Promisified execFile for git commands. */
|
|
429
429
|
const execFileAsync = promisify(execFile);
|
|
430
|
+
/** Standard timeout (ms) for most git operations. */
|
|
431
|
+
const GIT_TIMEOUT_STANDARD = 30_000;
|
|
432
|
+
/** Extended timeout (ms) for cherry-pick operations. */
|
|
433
|
+
const GIT_TIMEOUT_CHERRY_PICK = 120_000;
|
|
434
|
+
/** Timeout (ms) for push operations. */
|
|
435
|
+
const GIT_TIMEOUT_PUSH = 60_000;
|
|
436
|
+
/**
|
|
437
|
+
* Extract string fields from a child-process error.
|
|
438
|
+
* Node's execFile wraps failures in an Error with `stderr`, `stdout`, and
|
|
439
|
+
* `code` properties that aren't part of the Error type.
|
|
440
|
+
*/
|
|
441
|
+
function getExecErrorFields(error) {
|
|
442
|
+
if (!(error instanceof Error)) {
|
|
443
|
+
return { message: String(error), stderr: '', stdout: '' };
|
|
444
|
+
}
|
|
445
|
+
const rec = error;
|
|
446
|
+
const stderr = typeof rec['stderr'] === 'string' ? rec['stderr'] : '';
|
|
447
|
+
const stdout = typeof rec['stdout'] === 'string' ? rec['stdout'] : '';
|
|
448
|
+
return { message: error.message || '', stderr, stdout };
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Build an authenticated push URL by injecting a token into an HTTPS remote.
|
|
452
|
+
* Non-HTTPS URLs are returned as-is.
|
|
453
|
+
*
|
|
454
|
+
* @param remoteUrl - The remote repository URL.
|
|
455
|
+
* @param accessToken - Optional access token to inject.
|
|
456
|
+
* @returns The URL with the token injected if applicable.
|
|
457
|
+
*/
|
|
458
|
+
function buildAuthenticatedPushUrl(remoteUrl, accessToken) {
|
|
459
|
+
if (!accessToken)
|
|
460
|
+
return remoteUrl;
|
|
461
|
+
return remoteUrl.replace(/^https:\/\//, `https://${encodeURIComponent(accessToken)}@`);
|
|
462
|
+
}
|
|
430
463
|
/**
|
|
431
464
|
* Stage files via stdin to avoid ENAMETOOLONG on Windows.
|
|
432
465
|
*
|
|
@@ -438,22 +471,34 @@ const execFileAsync = promisify(execFile);
|
|
|
438
471
|
*
|
|
439
472
|
* @param files - Absolute paths of files to stage.
|
|
440
473
|
* @param cwd - Repository root (working directory for git).
|
|
474
|
+
* @param timeoutMs - Kill the child process after this many milliseconds. Default: 30000.
|
|
441
475
|
*/
|
|
442
|
-
function gitAddViaStdin(files, cwd) {
|
|
476
|
+
function gitAddViaStdin(files, cwd, timeoutMs = 30_000) {
|
|
443
477
|
return new Promise((resolve, reject) => {
|
|
478
|
+
let timedOut = false;
|
|
444
479
|
const child = execFile('git', ['add', '--pathspec-from-file=-', '--pathspec-file-nul'], { cwd }, (error) => {
|
|
480
|
+
clearTimeout(timer);
|
|
481
|
+
if (timedOut)
|
|
482
|
+
return;
|
|
445
483
|
if (error)
|
|
446
484
|
reject(error);
|
|
447
485
|
else
|
|
448
486
|
resolve();
|
|
449
487
|
});
|
|
488
|
+
const timer = setTimeout(() => {
|
|
489
|
+
timedOut = true;
|
|
490
|
+
child.kill();
|
|
491
|
+
reject(new Error(`git add timed out after ${String(timeoutMs)}ms`));
|
|
492
|
+
}, timeoutMs);
|
|
450
493
|
if (!child.stdin) {
|
|
494
|
+
clearTimeout(timer);
|
|
451
495
|
reject(new Error('Failed to open stdin for git add'));
|
|
452
496
|
return;
|
|
453
497
|
}
|
|
454
498
|
// Suppress EPIPE — expected if git exits before we finish writing
|
|
455
499
|
child.stdin.on('error', (err) => {
|
|
456
500
|
if (err.code !== 'EPIPE') {
|
|
501
|
+
clearTimeout(timer);
|
|
457
502
|
reject(err);
|
|
458
503
|
}
|
|
459
504
|
});
|
|
@@ -4320,8 +4365,8 @@ class CommitMessageBuilder {
|
|
|
4320
4365
|
*/
|
|
4321
4366
|
async getStagedDiff() {
|
|
4322
4367
|
try {
|
|
4323
|
-
const { stdout: stat } = await execFileAsync('git', ['diff', '--cached', '--stat'], { cwd: this.rootPath });
|
|
4324
|
-
const { stdout: patch } = await execFileAsync('git', ['diff', '--cached'], { cwd: this.rootPath });
|
|
4368
|
+
const { stdout: stat } = await execFileAsync('git', ['diff', '--cached', '--stat'], { cwd: this.rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4369
|
+
const { stdout: patch } = await execFileAsync('git', ['diff', '--cached'], { cwd: this.rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4325
4370
|
return `${stat}\n${patch}`;
|
|
4326
4371
|
}
|
|
4327
4372
|
catch {
|
|
@@ -4502,29 +4547,26 @@ function partMatches(part, value, min, max) {
|
|
|
4502
4547
|
// Number with step doesn't make sense for single values, treat as exact
|
|
4503
4548
|
return value === num;
|
|
4504
4549
|
}
|
|
4505
|
-
/**
|
|
4506
|
-
* Manages scheduled squash retention for a single VCS root.
|
|
4507
|
-
*
|
|
4508
|
-
* Runs on a cron schedule (checked every 60s). When triggered, squashes
|
|
4509
|
-
* commits older than the retention boundary (the tighter of maxAgeDays
|
|
4510
|
-
* and maxVersions) into a single "historical baseline" orphan commit,
|
|
4511
|
-
* then cherry-picks retained commits on top. Force-pushes to remote
|
|
4512
|
-
* if configured (D13).
|
|
4513
|
-
*/
|
|
4514
4550
|
class SquashManager {
|
|
4515
4551
|
rootPath;
|
|
4516
4552
|
retention;
|
|
4553
|
+
branch;
|
|
4554
|
+
pauseCommits;
|
|
4555
|
+
resumeCommits;
|
|
4517
4556
|
remoteUrl;
|
|
4518
4557
|
accessToken;
|
|
4519
4558
|
logger;
|
|
4520
4559
|
intervalTimer;
|
|
4521
4560
|
running = false;
|
|
4522
|
-
constructor(rootPath, retention, logger,
|
|
4561
|
+
constructor(rootPath, retention, logger, options = {}) {
|
|
4523
4562
|
this.rootPath = rootPath;
|
|
4524
4563
|
this.retention = retention;
|
|
4525
4564
|
this.logger = logger;
|
|
4526
|
-
this.
|
|
4527
|
-
this.
|
|
4565
|
+
this.branch = options.branch ?? 'master';
|
|
4566
|
+
this.pauseCommits = options.pauseCommits;
|
|
4567
|
+
this.resumeCommits = options.resumeCommits;
|
|
4568
|
+
this.remoteUrl = options.remoteUrl;
|
|
4569
|
+
this.accessToken = options.accessToken;
|
|
4528
4570
|
}
|
|
4529
4571
|
/**
|
|
4530
4572
|
* Start the cron-based squash scheduler.
|
|
@@ -4554,6 +4596,9 @@ class SquashManager {
|
|
|
4554
4596
|
}
|
|
4555
4597
|
/**
|
|
4556
4598
|
* Run the squash operation. Exposed publicly for testing.
|
|
4599
|
+
*
|
|
4600
|
+
* Coordinates with VcsManager by calling pause/resume callbacks
|
|
4601
|
+
* to drain and suspend the commit pipeline during the squash.
|
|
4557
4602
|
*/
|
|
4558
4603
|
async runSquash() {
|
|
4559
4604
|
// Check for index.lock before starting multi-step operation
|
|
@@ -4567,6 +4612,16 @@ class SquashManager {
|
|
|
4567
4612
|
catch {
|
|
4568
4613
|
// No lock — proceed
|
|
4569
4614
|
}
|
|
4615
|
+
// Bug 3: Pause the commit pipeline before squash
|
|
4616
|
+
try {
|
|
4617
|
+
await this.pauseCommits?.();
|
|
4618
|
+
}
|
|
4619
|
+
catch (error) {
|
|
4620
|
+
this.logger.error({ root: this.rootPath, err: normalizeError(error) }, 'Failed to pause commit pipeline before squash');
|
|
4621
|
+
// Resume in case pause partially executed (flush succeeded but flag not set)
|
|
4622
|
+
this.resumeCommits?.();
|
|
4623
|
+
return { squashed: false, error: normalizeError(error).message };
|
|
4624
|
+
}
|
|
4570
4625
|
try {
|
|
4571
4626
|
const commits = await this.getCommitLog();
|
|
4572
4627
|
if (commits.length <= 1) {
|
|
@@ -4591,13 +4646,17 @@ class SquashManager {
|
|
|
4591
4646
|
this.logger.error({ root: this.rootPath, err: normalizeError(error) }, 'Squash operation failed');
|
|
4592
4647
|
return { squashed: false, error: message };
|
|
4593
4648
|
}
|
|
4649
|
+
finally {
|
|
4650
|
+
// Bug 3: Always resume the commit pipeline
|
|
4651
|
+
this.resumeCommits?.();
|
|
4652
|
+
}
|
|
4594
4653
|
}
|
|
4595
4654
|
/**
|
|
4596
4655
|
* Get all commits in chronological order (oldest first).
|
|
4597
4656
|
*/
|
|
4598
4657
|
async getCommitLog() {
|
|
4599
4658
|
try {
|
|
4600
|
-
const { stdout } = await execFileAsync('git', ['log', '--format=%H %aI', '--reverse'], { cwd: this.rootPath });
|
|
4659
|
+
const { stdout } = await execFileAsync('git', ['log', '--format=%H %aI', '--reverse'], { cwd: this.rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4601
4660
|
if (!stdout.trim())
|
|
4602
4661
|
return [];
|
|
4603
4662
|
return stdout
|
|
@@ -4644,32 +4703,35 @@ class SquashManager {
|
|
|
4644
4703
|
}
|
|
4645
4704
|
/**
|
|
4646
4705
|
* Perform the squash: create orphan with baseline + cherry-pick retained commits.
|
|
4706
|
+
* Uses the configured branch name instead of dynamic detection (Bug 1).
|
|
4647
4707
|
*/
|
|
4648
4708
|
async performSquash(commits, boundaryIndex) {
|
|
4649
4709
|
// The commit just before boundary is the last one to squash
|
|
4650
4710
|
const baselineHash = commits[boundaryIndex - 1].hash;
|
|
4651
|
-
//
|
|
4652
|
-
const
|
|
4653
|
-
const currentBranch = branchOut.trim();
|
|
4711
|
+
// Bug 1: Use configured branch name, not dynamic detection
|
|
4712
|
+
const targetBranch = this.branch;
|
|
4654
4713
|
const orphanBranch = `__squash_orphan_${String(Date.now())}`;
|
|
4655
4714
|
try {
|
|
4656
4715
|
// 1. Create orphan branch with the tree state at the baseline commit
|
|
4657
4716
|
await execFileAsync('git', ['checkout', '--orphan', orphanBranch], {
|
|
4658
4717
|
cwd: this.rootPath,
|
|
4718
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4659
4719
|
});
|
|
4660
4720
|
// 2. Reset index to the baseline tree (the last squashed commit's tree)
|
|
4661
4721
|
await execFileAsync('git', ['reset', '--hard', baselineHash], {
|
|
4662
4722
|
cwd: this.rootPath,
|
|
4723
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4663
4724
|
});
|
|
4664
4725
|
// 3. Create the baseline commit with the same tree
|
|
4665
4726
|
// Use commit-tree for a clean orphan commit
|
|
4666
|
-
const { stdout: treeOut } = await execFileAsync('git', ['rev-parse', `${baselineHash}^{tree}`], { cwd: this.rootPath });
|
|
4727
|
+
const { stdout: treeOut } = await execFileAsync('git', ['rev-parse', `${baselineHash}^{tree}`], { cwd: this.rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4667
4728
|
const treeHash = treeOut.trim();
|
|
4668
|
-
const { stdout: commitOut } = await execFileAsync('git', ['commit-tree', treeHash, '-m', 'historical baseline'], { cwd: this.rootPath });
|
|
4729
|
+
const { stdout: commitOut } = await execFileAsync('git', ['commit-tree', treeHash, '-m', 'historical baseline'], { cwd: this.rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4669
4730
|
const baselineCommitHash = commitOut.trim();
|
|
4670
4731
|
// 4. Reset orphan branch to this new baseline commit
|
|
4671
4732
|
await execFileAsync('git', ['reset', '--hard', baselineCommitHash], {
|
|
4672
4733
|
cwd: this.rootPath,
|
|
4734
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4673
4735
|
});
|
|
4674
4736
|
// 5. Cherry-pick boundary..HEAD (the retained commits)
|
|
4675
4737
|
// Get the list of commits to cherry-pick (from boundary to the end)
|
|
@@ -4677,28 +4739,33 @@ class SquashManager {
|
|
|
4677
4739
|
if (commitsToCherry.length > 0) {
|
|
4678
4740
|
await execFileAsync('git', ['cherry-pick', ...commitsToCherry], {
|
|
4679
4741
|
cwd: this.rootPath,
|
|
4742
|
+
timeout: GIT_TIMEOUT_CHERRY_PICK,
|
|
4680
4743
|
});
|
|
4681
4744
|
}
|
|
4682
|
-
// 6. Force-update the
|
|
4683
|
-
const { stdout: newHead } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: this.rootPath });
|
|
4684
|
-
await execFileAsync('git', ['branch', '-f',
|
|
4685
|
-
// 7. Switch back to
|
|
4686
|
-
await execFileAsync('git', ['checkout',
|
|
4745
|
+
// 6. Force-update the configured branch to the orphan
|
|
4746
|
+
const { stdout: newHead } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: this.rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4747
|
+
await execFileAsync('git', ['branch', '-f', targetBranch, newHead.trim()], { cwd: this.rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4748
|
+
// 7. Switch back to configured branch
|
|
4749
|
+
await execFileAsync('git', ['checkout', targetBranch], {
|
|
4687
4750
|
cwd: this.rootPath,
|
|
4751
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4688
4752
|
});
|
|
4689
4753
|
// 8. Delete orphan branch
|
|
4690
4754
|
await execFileAsync('git', ['branch', '-D', orphanBranch], {
|
|
4691
4755
|
cwd: this.rootPath,
|
|
4756
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4692
4757
|
});
|
|
4693
4758
|
}
|
|
4694
4759
|
catch (error) {
|
|
4695
|
-
// Attempt cleanup: try to get back to
|
|
4760
|
+
// Attempt cleanup: try to get back to configured branch
|
|
4696
4761
|
try {
|
|
4697
|
-
await execFileAsync('git', ['checkout', '-f',
|
|
4762
|
+
await execFileAsync('git', ['checkout', '-f', targetBranch], {
|
|
4698
4763
|
cwd: this.rootPath,
|
|
4764
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4699
4765
|
});
|
|
4700
4766
|
await execFileAsync('git', ['branch', '-D', orphanBranch], {
|
|
4701
4767
|
cwd: this.rootPath,
|
|
4768
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4702
4769
|
});
|
|
4703
4770
|
}
|
|
4704
4771
|
catch {
|
|
@@ -4722,12 +4789,11 @@ class SquashManager {
|
|
|
4722
4789
|
if (!this.remoteUrl)
|
|
4723
4790
|
return;
|
|
4724
4791
|
try {
|
|
4725
|
-
const pushUrl = this.accessToken
|
|
4726
|
-
? this.remoteUrl.replace(/^https:\/\//, `https://${encodeURIComponent(this.accessToken)}@`)
|
|
4727
|
-
: this.remoteUrl;
|
|
4792
|
+
const pushUrl = buildAuthenticatedPushUrl(this.remoteUrl, this.accessToken);
|
|
4728
4793
|
await execFileAsync('git', ['push', '--force', pushUrl, 'HEAD'], {
|
|
4729
4794
|
cwd: this.rootPath,
|
|
4730
4795
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
4796
|
+
timeout: GIT_TIMEOUT_PUSH,
|
|
4731
4797
|
});
|
|
4732
4798
|
this.logger.info({ root: this.rootPath, remote: this.remoteUrl }, 'Squash force push succeeded');
|
|
4733
4799
|
}
|
|
@@ -4828,6 +4894,46 @@ async function configureRepoIdentity(rootPath, name, email) {
|
|
|
4828
4894
|
cwd: rootPath,
|
|
4829
4895
|
});
|
|
4830
4896
|
}
|
|
4897
|
+
/**
|
|
4898
|
+
* Detect and recover from orphan branch state on startup.
|
|
4899
|
+
*
|
|
4900
|
+
* If the repo is on an unexpected branch (e.g. a leftover squash orphan),
|
|
4901
|
+
* force-updates the expected branch to the current HEAD and checks it out.
|
|
4902
|
+
* Orphan branches are NOT deleted — they serve as a recovery safety net.
|
|
4903
|
+
*
|
|
4904
|
+
* @param rootPath - Directory of the git repository.
|
|
4905
|
+
* @param expectedBranch - The configured branch name (e.g. "master").
|
|
4906
|
+
* @param logger - Logger instance for warnings and info.
|
|
4907
|
+
*/
|
|
4908
|
+
async function detectAndRecoverOrphanBranch(rootPath, expectedBranch, logger) {
|
|
4909
|
+
const gitDir = join(rootPath, '.git');
|
|
4910
|
+
try {
|
|
4911
|
+
await access(gitDir);
|
|
4912
|
+
}
|
|
4913
|
+
catch {
|
|
4914
|
+
// Not a git repo — nothing to detect
|
|
4915
|
+
return;
|
|
4916
|
+
}
|
|
4917
|
+
const { stdout: branchOut } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4918
|
+
const currentBranch = branchOut.trim();
|
|
4919
|
+
if (currentBranch === expectedBranch)
|
|
4920
|
+
return;
|
|
4921
|
+
logger.warn({ root: rootPath, currentBranch, expectedBranch }, 'Repo is on unexpected branch — recovering to configured branch');
|
|
4922
|
+
// Get current HEAD
|
|
4923
|
+
const { stdout: headOut } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
4924
|
+
const headHash = headOut.trim();
|
|
4925
|
+
// Force-update the expected branch to current HEAD
|
|
4926
|
+
await execFileAsync('git', ['branch', '-f', expectedBranch, headHash], {
|
|
4927
|
+
cwd: rootPath,
|
|
4928
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4929
|
+
});
|
|
4930
|
+
// Checkout the expected branch
|
|
4931
|
+
await execFileAsync('git', ['checkout', expectedBranch], {
|
|
4932
|
+
cwd: rootPath,
|
|
4933
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
4934
|
+
});
|
|
4935
|
+
logger.info({ root: rootPath, recoveredFrom: currentBranch, expectedBranch, headHash }, 'Orphan branch recovery complete — now on configured branch');
|
|
4936
|
+
}
|
|
4831
4937
|
/**
|
|
4832
4938
|
* Ensure a .gitignore file exists at rootPath with all always-on entries.
|
|
4833
4939
|
* Creates the file if missing. Appends only entries not already present.
|
|
@@ -4914,17 +5020,36 @@ function buildGlobMatcher(globs) {
|
|
|
4914
5020
|
return isMatch(normalized);
|
|
4915
5021
|
};
|
|
4916
5022
|
}
|
|
5023
|
+
/**
|
|
5024
|
+
* Expand a bare directory path (no glob characters) to a recursive glob.
|
|
5025
|
+
* A path like `/opt/jeeves/content` becomes `/opt/jeeves/content/**`.
|
|
5026
|
+
* Paths that already contain glob characters are returned unchanged.
|
|
5027
|
+
*
|
|
5028
|
+
* @param glob - A glob pattern or bare directory path.
|
|
5029
|
+
* @returns The original glob, or the path suffixed with `/**` if bare.
|
|
5030
|
+
*/
|
|
5031
|
+
function expandBareDirectoryGlob(glob) {
|
|
5032
|
+
const normalized = normalizeSlashes(glob);
|
|
5033
|
+
if (/[*?{[\]]/.test(normalized))
|
|
5034
|
+
return glob;
|
|
5035
|
+
return glob.replace(/\/+$/, '') + '/**';
|
|
5036
|
+
}
|
|
4917
5037
|
/**
|
|
4918
5038
|
* Convert an array of glob patterns into chokidar-compatible directory roots
|
|
4919
5039
|
* and a filter function for post-hoc event filtering.
|
|
4920
5040
|
*
|
|
5041
|
+
* Bare directory paths (no glob characters, e.g. `/opt/jeeves/content`) are
|
|
5042
|
+
* automatically expanded to recursive globs (`/opt/jeeves/content/**`) so that
|
|
5043
|
+
* picomatch matches all files underneath them.
|
|
5044
|
+
*
|
|
4921
5045
|
* @param globs - Glob patterns from the watch config.
|
|
4922
5046
|
* @returns Object with `roots` (directories for chokidar) and `matches` (filter function).
|
|
4923
5047
|
*/
|
|
4924
5048
|
function resolveWatchPaths(globs) {
|
|
4925
|
-
const
|
|
5049
|
+
const expandedGlobs = globs.map(expandBareDirectoryGlob);
|
|
5050
|
+
const rawRoots = expandedGlobs.map(globRoot);
|
|
4926
5051
|
const roots = deduplicateRoots(rawRoots);
|
|
4927
|
-
const matches = buildGlobMatcher(
|
|
5052
|
+
const matches = buildGlobMatcher(expandedGlobs);
|
|
4928
5053
|
return { roots, matches };
|
|
4929
5054
|
}
|
|
4930
5055
|
/**
|
|
@@ -4977,13 +5102,11 @@ async function pushToRemote(rootPath, remoteUrl, accessToken, pushErrors, logger
|
|
|
4977
5102
|
if (!remoteUrl)
|
|
4978
5103
|
return undefined;
|
|
4979
5104
|
try {
|
|
4980
|
-
|
|
4981
|
-
const pushUrl = accessToken
|
|
4982
|
-
? remoteUrl.replace(/^https:\/\//, `https://${encodeURIComponent(accessToken)}@`)
|
|
4983
|
-
: remoteUrl;
|
|
5105
|
+
const pushUrl = buildAuthenticatedPushUrl(remoteUrl, accessToken);
|
|
4984
5106
|
await execFileAsync('git', ['push', pushUrl, 'HEAD'], {
|
|
4985
5107
|
cwd: rootPath,
|
|
4986
5108
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
5109
|
+
timeout: GIT_TIMEOUT_PUSH,
|
|
4987
5110
|
});
|
|
4988
5111
|
const timestamp = new Date().toISOString();
|
|
4989
5112
|
logger.info({ root: rootPath, remote: remoteUrl }, 'VCS push succeeded');
|
|
@@ -5008,7 +5131,7 @@ async function pushToRemote(rootPath, remoteUrl, accessToken, pushErrors, logger
|
|
|
5008
5131
|
* Per-root VCS manager for git-backed content versioning.
|
|
5009
5132
|
*
|
|
5010
5133
|
* Each VCS-enabled watch root gets its own VcsManager instance, which owns
|
|
5011
|
-
* a
|
|
5134
|
+
* a throttled commit pipeline: file changes are batched, staged, committed
|
|
5012
5135
|
* (with optional AI-generated messages), and optionally pushed to a remote.
|
|
5013
5136
|
*
|
|
5014
5137
|
* Concurrency: only one commit is in-flight at a time per root. Index.lock
|
|
@@ -5025,10 +5148,11 @@ class VcsManager {
|
|
|
5025
5148
|
pendingReversions = [];
|
|
5026
5149
|
_pushErrors = [];
|
|
5027
5150
|
squashManager;
|
|
5028
|
-
|
|
5151
|
+
throttleTimer;
|
|
5029
5152
|
commitInFlight = Promise.resolve();
|
|
5030
5153
|
consecutiveCommitFailures = 0;
|
|
5031
5154
|
started = false;
|
|
5155
|
+
paused = false;
|
|
5032
5156
|
isBaseline = true;
|
|
5033
5157
|
_lastPushTime = null;
|
|
5034
5158
|
constructor(rootPath, config, logger, commitMessageGenerator, remoteUrl, accessToken) {
|
|
@@ -5039,7 +5163,17 @@ class VcsManager {
|
|
|
5039
5163
|
this.accessToken = accessToken;
|
|
5040
5164
|
this.commitMessageBuilder = new CommitMessageBuilder(rootPath, logger, commitMessageGenerator);
|
|
5041
5165
|
if (config.retention) {
|
|
5042
|
-
this.squashManager = new SquashManager(rootPath, config.retention, logger,
|
|
5166
|
+
this.squashManager = new SquashManager(rootPath, config.retention, logger, {
|
|
5167
|
+
branch: config.branch,
|
|
5168
|
+
pauseCommits: () => {
|
|
5169
|
+
return this.pause();
|
|
5170
|
+
},
|
|
5171
|
+
resumeCommits: () => {
|
|
5172
|
+
this.resume();
|
|
5173
|
+
},
|
|
5174
|
+
remoteUrl,
|
|
5175
|
+
accessToken,
|
|
5176
|
+
});
|
|
5043
5177
|
}
|
|
5044
5178
|
}
|
|
5045
5179
|
get lastPushTime() {
|
|
@@ -5052,10 +5186,21 @@ class VcsManager {
|
|
|
5052
5186
|
* Begin accepting file changes. Sets the manager to started state and
|
|
5053
5187
|
* starts the squash manager if configured.
|
|
5054
5188
|
*
|
|
5189
|
+
* Performs startup orphan detection/recovery before starting the squash
|
|
5190
|
+
* manager, ensuring the repo is on the configured branch.
|
|
5191
|
+
*
|
|
5055
5192
|
* Baseline mode (commit prefix `"baseline:"`) remains active until
|
|
5056
5193
|
* {@link endBaseline} is called by the coordinator after the initial scan.
|
|
5057
5194
|
*/
|
|
5058
|
-
start() {
|
|
5195
|
+
async start() {
|
|
5196
|
+
// Bug 6: Detect and recover from orphan branches before normal operations
|
|
5197
|
+
const branch = this.config.branch;
|
|
5198
|
+
try {
|
|
5199
|
+
await detectAndRecoverOrphanBranch(this.rootPath, branch, this.logger);
|
|
5200
|
+
}
|
|
5201
|
+
catch (error) {
|
|
5202
|
+
this.logger.error({ root: this.rootPath, err: normalizeError(error) }, 'Orphan branch recovery failed — continuing with current state');
|
|
5203
|
+
}
|
|
5059
5204
|
this.started = true;
|
|
5060
5205
|
this.squashManager?.start();
|
|
5061
5206
|
this.logger.info({ root: this.rootPath }, 'VcsManager started');
|
|
@@ -5068,7 +5213,7 @@ class VcsManager {
|
|
|
5068
5213
|
this.isBaseline = false;
|
|
5069
5214
|
}
|
|
5070
5215
|
/**
|
|
5071
|
-
* Add a file to the pending set and
|
|
5216
|
+
* Add a file to the pending set and start the throttle timer.
|
|
5072
5217
|
* If the pending set exceeds maxBatchSize, flush immediately.
|
|
5073
5218
|
*
|
|
5074
5219
|
* @param filePath - Absolute path of the changed file.
|
|
@@ -5083,15 +5228,15 @@ class VcsManager {
|
|
|
5083
5228
|
}
|
|
5084
5229
|
this.pending.add(filePath);
|
|
5085
5230
|
if (this.pending.size >= this.config.maxBatchSize) {
|
|
5086
|
-
this.
|
|
5231
|
+
this.clearThrottle();
|
|
5087
5232
|
const batch = this.takeBatch(this.config.maxBatchSize);
|
|
5088
5233
|
this.commitInFlight = this.commitInFlight.then(() => this.commitBatch(batch));
|
|
5089
5234
|
if (this.pending.size > 0) {
|
|
5090
|
-
this.
|
|
5235
|
+
this.resetThrottle();
|
|
5091
5236
|
}
|
|
5092
5237
|
return;
|
|
5093
5238
|
}
|
|
5094
|
-
this.
|
|
5239
|
+
this.resetThrottle();
|
|
5095
5240
|
}
|
|
5096
5241
|
/**
|
|
5097
5242
|
* Record reversion metadata so the next commit uses a revert-prefixed message.
|
|
@@ -5112,11 +5257,33 @@ class VcsManager {
|
|
|
5112
5257
|
return;
|
|
5113
5258
|
this.fileChanged(filePath);
|
|
5114
5259
|
}
|
|
5260
|
+
/**
|
|
5261
|
+
* Pause the commit pipeline. Drains pending commits via flush(), then
|
|
5262
|
+
* sets a flag that makes commitBatch queue instead of execute.
|
|
5263
|
+
* Used by SquashManager to coordinate squash operations.
|
|
5264
|
+
*/
|
|
5265
|
+
async pause() {
|
|
5266
|
+
await this.flush();
|
|
5267
|
+
this.paused = true;
|
|
5268
|
+
this.logger.debug({ root: this.rootPath }, 'VcsManager paused');
|
|
5269
|
+
}
|
|
5270
|
+
/**
|
|
5271
|
+
* Resume the commit pipeline after a pause.
|
|
5272
|
+
* Re-enables normal commit operations and starts a throttle timer
|
|
5273
|
+
* if there are pending files that accumulated during the pause.
|
|
5274
|
+
*/
|
|
5275
|
+
resume() {
|
|
5276
|
+
this.paused = false;
|
|
5277
|
+
this.logger.debug({ root: this.rootPath }, 'VcsManager resumed');
|
|
5278
|
+
if (this.pending.size > 0) {
|
|
5279
|
+
this.resetThrottle();
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5115
5282
|
/**
|
|
5116
5283
|
* Flush all pending files immediately.
|
|
5117
5284
|
*/
|
|
5118
5285
|
async flush() {
|
|
5119
|
-
this.
|
|
5286
|
+
this.clearThrottle();
|
|
5120
5287
|
if (this.pending.size > 0) {
|
|
5121
5288
|
const batch = [...this.pending];
|
|
5122
5289
|
this.pending.clear();
|
|
@@ -5149,23 +5316,25 @@ class VcsManager {
|
|
|
5149
5316
|
return batch;
|
|
5150
5317
|
}
|
|
5151
5318
|
/**
|
|
5152
|
-
* Clear the
|
|
5319
|
+
* Clear the throttle timer.
|
|
5153
5320
|
*/
|
|
5154
|
-
|
|
5155
|
-
if (this.
|
|
5156
|
-
clearTimeout(this.
|
|
5157
|
-
this.
|
|
5321
|
+
clearThrottle() {
|
|
5322
|
+
if (this.throttleTimer !== undefined) {
|
|
5323
|
+
clearTimeout(this.throttleTimer);
|
|
5324
|
+
this.throttleTimer = undefined;
|
|
5158
5325
|
}
|
|
5159
5326
|
}
|
|
5160
5327
|
/**
|
|
5161
|
-
*
|
|
5328
|
+
* Start the throttle timer to fire flush() after commitThrottleMs.
|
|
5329
|
+
* If a timer is already running, do not reset it — this is throttle, not debounce.
|
|
5162
5330
|
*/
|
|
5163
|
-
|
|
5164
|
-
this.
|
|
5165
|
-
|
|
5166
|
-
|
|
5331
|
+
resetThrottle() {
|
|
5332
|
+
if (this.throttleTimer !== undefined)
|
|
5333
|
+
return;
|
|
5334
|
+
this.throttleTimer = setTimeout(() => {
|
|
5335
|
+
this.throttleTimer = undefined;
|
|
5167
5336
|
void this.flush();
|
|
5168
|
-
}, this.config.
|
|
5337
|
+
}, this.config.commitThrottleMs);
|
|
5169
5338
|
}
|
|
5170
5339
|
/**
|
|
5171
5340
|
* Check for and remove stale index.lock files before retrying.
|
|
@@ -5187,13 +5356,31 @@ class VcsManager {
|
|
|
5187
5356
|
}
|
|
5188
5357
|
}
|
|
5189
5358
|
}
|
|
5359
|
+
/**
|
|
5360
|
+
* Check whether a git error represents a "nothing to commit" state.
|
|
5361
|
+
* Git outputs this message to stdout (not stderr) on exit code 1.
|
|
5362
|
+
*/
|
|
5363
|
+
isNothingToCommitError(error) {
|
|
5364
|
+
const { message, stderr, stdout } = getExecErrorFields(error);
|
|
5365
|
+
return (message.includes('nothing to commit') ||
|
|
5366
|
+
stderr.includes('nothing to commit') ||
|
|
5367
|
+
stdout.includes('nothing to commit'));
|
|
5368
|
+
}
|
|
5190
5369
|
/**
|
|
5191
5370
|
* Commit a batch of files to the git repo with index.lock retry,
|
|
5192
|
-
* stale lock detection, circuit breaker, and re-queue cap.
|
|
5371
|
+
* stale lock detection, circuit breaker, pause support, and re-queue cap.
|
|
5193
5372
|
*/
|
|
5194
5373
|
async commitBatch(files) {
|
|
5195
5374
|
if (files.length === 0)
|
|
5196
5375
|
return;
|
|
5376
|
+
// Pause support: re-queue files without counting as failure
|
|
5377
|
+
if (this.paused) {
|
|
5378
|
+
for (const f of files) {
|
|
5379
|
+
this.pending.add(f);
|
|
5380
|
+
}
|
|
5381
|
+
this.logger.debug({ root: this.rootPath, fileCount: files.length }, 'VcsManager paused — files re-queued');
|
|
5382
|
+
return;
|
|
5383
|
+
}
|
|
5197
5384
|
// Circuit breaker: skip if too many consecutive failures
|
|
5198
5385
|
if (this.consecutiveCommitFailures >= this.config.maxConsecutiveFailures) {
|
|
5199
5386
|
this.logger.error({
|
|
@@ -5207,7 +5394,7 @@ class VcsManager {
|
|
|
5207
5394
|
// Check for stale lock before first attempt
|
|
5208
5395
|
await this.removeStaleLock();
|
|
5209
5396
|
await retry(async (attempt) => {
|
|
5210
|
-
await gitAddViaStdin(files, this.rootPath);
|
|
5397
|
+
await gitAddViaStdin(files, this.rootPath, GIT_TIMEOUT_STANDARD);
|
|
5211
5398
|
// Build message after staging so getStagedDiff can see the changes.
|
|
5212
5399
|
// Skip AI for baselines and retries — use template directly.
|
|
5213
5400
|
const message = this.isBaseline || attempt > 1
|
|
@@ -5216,8 +5403,9 @@ class VcsManager {
|
|
|
5216
5403
|
this.pendingReversions.length = 0;
|
|
5217
5404
|
await execFileAsync('git', ['commit', '-m', message], {
|
|
5218
5405
|
cwd: this.rootPath,
|
|
5406
|
+
timeout: GIT_TIMEOUT_STANDARD,
|
|
5219
5407
|
});
|
|
5220
|
-
const { stdout: hashOut } = await execFileAsync('git', ['rev-parse', '--short', 'HEAD'], { cwd: this.rootPath });
|
|
5408
|
+
const { stdout: hashOut } = await execFileAsync('git', ['rev-parse', '--short', 'HEAD'], { cwd: this.rootPath, timeout: GIT_TIMEOUT_STANDARD });
|
|
5221
5409
|
const hash = hashOut.trim();
|
|
5222
5410
|
this.logger.info({ root: this.rootPath, hash, fileCount: files.length }, 'VCS commit created');
|
|
5223
5411
|
const pushTime = await pushToRemote(this.rootPath, this.remoteUrl, this.accessToken, this._pushErrors, this.logger);
|
|
@@ -5235,6 +5423,11 @@ class VcsManager {
|
|
|
5235
5423
|
this.consecutiveCommitFailures = 0;
|
|
5236
5424
|
}
|
|
5237
5425
|
catch (error) {
|
|
5426
|
+
// Bug 5: "nothing to commit" is a no-op, not a failure
|
|
5427
|
+
if (this.isNothingToCommitError(error)) {
|
|
5428
|
+
this.logger.info({ root: this.rootPath, fileCount: files.length }, 'VCS commit skipped — nothing to commit');
|
|
5429
|
+
return;
|
|
5430
|
+
}
|
|
5238
5431
|
this.consecutiveCommitFailures++;
|
|
5239
5432
|
// Re-queue cap: only re-queue up to maxBatchSize files
|
|
5240
5433
|
const cap = this.config.maxBatchSize;
|
|
@@ -5248,6 +5441,8 @@ class VcsManager {
|
|
|
5248
5441
|
}
|
|
5249
5442
|
this.logger.warn({ root: this.rootPath, fileCount: toRequeue.length }, 'Re-queued files after commit failure');
|
|
5250
5443
|
this.logger.error({ root: this.rootPath, err: normalizeError(error) }, 'VCS commit failed');
|
|
5444
|
+
// Bug 2: Restart throttle timer so re-queued files get retried
|
|
5445
|
+
this.resetThrottle();
|
|
5251
5446
|
}
|
|
5252
5447
|
}
|
|
5253
5448
|
}
|
|
@@ -5308,11 +5503,14 @@ class VcsCoordinator {
|
|
|
5308
5503
|
}
|
|
5309
5504
|
/**
|
|
5310
5505
|
* Start all VcsManager instances.
|
|
5506
|
+
* Runs startup orphan detection/recovery for each root.
|
|
5311
5507
|
*/
|
|
5312
|
-
start() {
|
|
5508
|
+
async start() {
|
|
5509
|
+
const startPromises = [];
|
|
5313
5510
|
this.managers.forEach((manager) => {
|
|
5314
|
-
manager.start();
|
|
5511
|
+
startPromises.push(manager.start());
|
|
5315
5512
|
});
|
|
5513
|
+
await Promise.all(startPromises);
|
|
5316
5514
|
this.logger.info({ rootCount: this.managers.size }, 'VcsCoordinator started');
|
|
5317
5515
|
}
|
|
5318
5516
|
/**
|
|
@@ -5336,8 +5534,8 @@ class VcsCoordinator {
|
|
|
5336
5534
|
/**
|
|
5337
5535
|
* Signal that the initial filesystem scan is complete.
|
|
5338
5536
|
*
|
|
5339
|
-
* Flushes each manager's
|
|
5340
|
-
* any files still pending in the VCS
|
|
5537
|
+
* Flushes each manager's throttle buffer before ending baseline mode, ensuring
|
|
5538
|
+
* any files still pending in the VCS throttle are committed with a
|
|
5341
5539
|
* `"baseline:"` prefix rather than `"watcher:"`. AI generation is not enabled
|
|
5342
5540
|
* until all managers have completed their flush.
|
|
5343
5541
|
*/
|
|
@@ -5901,36 +6099,36 @@ function createApiServer(options) {
|
|
|
5901
6099
|
};
|
|
5902
6100
|
},
|
|
5903
6101
|
});
|
|
5904
|
-
app.get('
|
|
6102
|
+
app.get(getEndpoint('status').path, withCache(cacheTtlMs, async () => {
|
|
5905
6103
|
const result = await coreStatusHandler();
|
|
5906
6104
|
return result.body;
|
|
5907
6105
|
}));
|
|
5908
|
-
app.post('
|
|
6106
|
+
app.post(getEndpoint('metadata').path, createMetadataHandler({
|
|
5909
6107
|
processor,
|
|
5910
6108
|
getConfig,
|
|
5911
6109
|
logger,
|
|
5912
6110
|
configDir: dirname(configPath),
|
|
5913
6111
|
}));
|
|
5914
|
-
app.post('
|
|
6112
|
+
app.post(getEndpoint('render').path, withCache(cacheTtlMs, createRenderHandler({
|
|
5915
6113
|
processor,
|
|
5916
6114
|
getWatch: () => getConfig().watch,
|
|
5917
6115
|
logger,
|
|
5918
6116
|
})));
|
|
5919
|
-
app.get('
|
|
6117
|
+
app.get(getEndpoint('searchFacets').path, createFacetsHandler({
|
|
5920
6118
|
getConfig,
|
|
5921
6119
|
valuesManager,
|
|
5922
6120
|
configDir: dirname(configPath),
|
|
5923
6121
|
}));
|
|
5924
|
-
app.post('
|
|
6122
|
+
app.post(getEndpoint('scan').path, createScanHandler({
|
|
5925
6123
|
vectorStore,
|
|
5926
6124
|
logger,
|
|
5927
6125
|
}));
|
|
5928
|
-
app.post('
|
|
6126
|
+
app.post(getEndpoint('walk').path, createWalkHandler({
|
|
5929
6127
|
getWatchPaths: () => extractWatchPathStrings(getConfig().watch.paths),
|
|
5930
6128
|
getFileSystemWatcher,
|
|
5931
6129
|
logger,
|
|
5932
6130
|
}));
|
|
5933
|
-
app.post('
|
|
6131
|
+
app.post(getEndpoint('search').path, createSearchHandler({
|
|
5934
6132
|
embeddingProvider,
|
|
5935
6133
|
vectorStore,
|
|
5936
6134
|
logger,
|
|
@@ -5941,12 +6139,12 @@ function createApiServer(options) {
|
|
|
5941
6139
|
: undefined;
|
|
5942
6140
|
},
|
|
5943
6141
|
}));
|
|
5944
|
-
app.post('
|
|
6142
|
+
app.post(getEndpoint('rebuildMetadata').path, createRebuildMetadataHandler({
|
|
5945
6143
|
enrichmentStore: options.enrichmentStore,
|
|
5946
6144
|
vectorStore,
|
|
5947
6145
|
logger,
|
|
5948
6146
|
}));
|
|
5949
|
-
app.post('
|
|
6147
|
+
app.post(getEndpoint('reindex').path, createConfigReindexHandler({
|
|
5950
6148
|
getConfig,
|
|
5951
6149
|
processor,
|
|
5952
6150
|
logger,
|
|
@@ -5957,10 +6155,10 @@ function createApiServer(options) {
|
|
|
5957
6155
|
vectorStore,
|
|
5958
6156
|
queue,
|
|
5959
6157
|
}));
|
|
5960
|
-
app.get('
|
|
5961
|
-
app.get('
|
|
5962
|
-
app.post('
|
|
5963
|
-
app.get('
|
|
6158
|
+
app.get(getEndpoint('issues').path, withCache(cacheTtlMs, createIssuesHandler({ issuesManager })));
|
|
6159
|
+
app.get(getEndpoint('configSchema').path, withCache(cacheTtlMs, createConfigSchemaHandler()));
|
|
6160
|
+
app.post(getEndpoint('configMatch').path, createConfigMatchHandler({ getConfig, logger }));
|
|
6161
|
+
app.get(getEndpoint('config').path, withCache(cacheTtlMs, createConfigQueryHandler({
|
|
5964
6162
|
getConfig,
|
|
5965
6163
|
valuesManager,
|
|
5966
6164
|
issuesManager,
|
|
@@ -5970,7 +6168,7 @@ function createApiServer(options) {
|
|
|
5970
6168
|
? () => virtualRuleStore.getAll()
|
|
5971
6169
|
: undefined,
|
|
5972
6170
|
})));
|
|
5973
|
-
app.post('
|
|
6171
|
+
app.post(getEndpoint('configValidate').path, createConfigValidateHandler({
|
|
5974
6172
|
getConfig,
|
|
5975
6173
|
logger,
|
|
5976
6174
|
configDir: dirname(configPath),
|
|
@@ -5984,7 +6182,7 @@ function createApiServer(options) {
|
|
|
5984
6182
|
return Promise.resolve();
|
|
5985
6183
|
},
|
|
5986
6184
|
});
|
|
5987
|
-
app.post('
|
|
6185
|
+
app.post(getEndpoint('configApply').path, async (request, reply) => {
|
|
5988
6186
|
const { patch, replace } = request.body;
|
|
5989
6187
|
const result = await coreConfigApplyHandler({ patch, replace });
|
|
5990
6188
|
return reply.status(result.status).send(result.body);
|
|
@@ -6003,34 +6201,34 @@ function createApiServer(options) {
|
|
|
6003
6201
|
gitignoreFilter,
|
|
6004
6202
|
vectorStore,
|
|
6005
6203
|
});
|
|
6006
|
-
app.post('
|
|
6204
|
+
app.post(getEndpoint('rulesRegister').path, createRulesRegisterHandler({
|
|
6007
6205
|
virtualRuleStore,
|
|
6008
6206
|
logger,
|
|
6009
6207
|
onRulesChanged,
|
|
6010
6208
|
}));
|
|
6011
|
-
app.delete('
|
|
6209
|
+
app.delete(getEndpoint('rulesUnregister').path, createRulesUnregisterHandler({
|
|
6012
6210
|
virtualRuleStore,
|
|
6013
6211
|
logger,
|
|
6014
6212
|
onRulesChanged,
|
|
6015
6213
|
}));
|
|
6016
|
-
app.delete('
|
|
6214
|
+
app.delete(getEndpoint('rulesUnregisterBySource').path, createRulesUnregisterParamHandler({
|
|
6017
6215
|
virtualRuleStore,
|
|
6018
6216
|
logger,
|
|
6019
6217
|
onRulesChanged,
|
|
6020
6218
|
}));
|
|
6021
|
-
app.post('
|
|
6022
|
-
app.post('
|
|
6219
|
+
app.post(getEndpoint('pointsDelete').path, createPointsDeleteHandler({ vectorStore, logger }));
|
|
6220
|
+
app.post(getEndpoint('rulesReapply').path, createRulesReapplyHandler({ processor, vectorStore, logger }));
|
|
6023
6221
|
}
|
|
6024
6222
|
// VCS routes — only registered when a VcsCoordinator is provided
|
|
6025
6223
|
if (options.vcsCoordinator) {
|
|
6026
6224
|
const coordinator = options.vcsCoordinator;
|
|
6027
|
-
app.get('
|
|
6028
|
-
app.get('
|
|
6029
|
-
app.get('
|
|
6030
|
-
app.get('
|
|
6031
|
-
app.get('
|
|
6032
|
-
app.post('
|
|
6033
|
-
app.post('
|
|
6225
|
+
app.get(getEndpoint('vcsStatus').path, createVcsStatusHandler({ coordinator, logger }));
|
|
6226
|
+
app.get(getEndpoint('vcsHistory').path, createVcsHistoryHandler({ coordinator, logger }));
|
|
6227
|
+
app.get(getEndpoint('vcsShow').path, createVcsShowHandler({ coordinator, logger }));
|
|
6228
|
+
app.get(getEndpoint('vcsDiff').path, createVcsDiffHandler({ coordinator, logger }));
|
|
6229
|
+
app.get(getEndpoint('vcsCheckExclusion').path, createVcsCheckExclusionHandler({ coordinator, logger }));
|
|
6230
|
+
app.post(getEndpoint('vcsRevert').path, createVcsRevertHandler({ coordinator, logger }));
|
|
6231
|
+
app.post(getEndpoint('vcsExclude').path, createVcsExcludeHandler({ coordinator, logger }));
|
|
6034
6232
|
}
|
|
6035
6233
|
return app;
|
|
6036
6234
|
}
|
|
@@ -9129,7 +9327,7 @@ class JeevesWatcher {
|
|
|
9129
9327
|
this.watcher = watcher;
|
|
9130
9328
|
this.gitignoreFilter = gitignoreFilter;
|
|
9131
9329
|
this.server = await this.startApiServer();
|
|
9132
|
-
vcsCoordinator.start();
|
|
9330
|
+
await vcsCoordinator.start();
|
|
9133
9331
|
this.watcher.start();
|
|
9134
9332
|
this.startConfigWatch();
|
|
9135
9333
|
logger.info('jeeves-watcher started');
|