@karmaniverous/jeeves-meta 0.3.0 → 0.3.1
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/dist/cli.js +265 -174
- package/dist/index.d.ts +76 -2
- package/dist/index.js +228 -81
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -420,6 +420,80 @@ async function discoverMetas(config, watcher) {
|
|
|
420
420
|
return metaPaths;
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
/**
|
|
424
|
+
* File-system lock for preventing concurrent synthesis on the same meta.
|
|
425
|
+
*
|
|
426
|
+
* Lock file: .meta/.lock containing PID + timestamp.
|
|
427
|
+
* Stale timeout: 30 minutes.
|
|
428
|
+
*
|
|
429
|
+
* @module lock
|
|
430
|
+
*/
|
|
431
|
+
const LOCK_FILE = '.lock';
|
|
432
|
+
const STALE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
433
|
+
/**
|
|
434
|
+
* Attempt to acquire a lock on a .meta directory.
|
|
435
|
+
*
|
|
436
|
+
* @param metaPath - Absolute path to the .meta directory.
|
|
437
|
+
* @returns True if lock was acquired, false if already locked (non-stale).
|
|
438
|
+
*/
|
|
439
|
+
function acquireLock(metaPath) {
|
|
440
|
+
const lockPath = join(metaPath, LOCK_FILE);
|
|
441
|
+
if (existsSync(lockPath)) {
|
|
442
|
+
try {
|
|
443
|
+
const raw = readFileSync(lockPath, 'utf8');
|
|
444
|
+
const data = JSON.parse(raw);
|
|
445
|
+
const lockAge = Date.now() - new Date(data.startedAt).getTime();
|
|
446
|
+
if (lockAge < STALE_TIMEOUT_MS) {
|
|
447
|
+
return false; // Lock is active
|
|
448
|
+
}
|
|
449
|
+
// Stale lock — fall through to overwrite
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
// Corrupt lock file — overwrite
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const lock = {
|
|
456
|
+
pid: process.pid,
|
|
457
|
+
startedAt: new Date().toISOString(),
|
|
458
|
+
};
|
|
459
|
+
writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Release a lock on a .meta directory.
|
|
464
|
+
*
|
|
465
|
+
* @param metaPath - Absolute path to the .meta directory.
|
|
466
|
+
*/
|
|
467
|
+
function releaseLock(metaPath) {
|
|
468
|
+
const lockPath = join(metaPath, LOCK_FILE);
|
|
469
|
+
try {
|
|
470
|
+
unlinkSync(lockPath);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
// Already removed or never existed
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Check if a .meta directory is currently locked (non-stale).
|
|
478
|
+
*
|
|
479
|
+
* @param metaPath - Absolute path to the .meta directory.
|
|
480
|
+
* @returns True if locked and not stale.
|
|
481
|
+
*/
|
|
482
|
+
function isLocked(metaPath) {
|
|
483
|
+
const lockPath = join(metaPath, LOCK_FILE);
|
|
484
|
+
if (!existsSync(lockPath))
|
|
485
|
+
return false;
|
|
486
|
+
try {
|
|
487
|
+
const raw = readFileSync(lockPath, 'utf8');
|
|
488
|
+
const data = JSON.parse(raw);
|
|
489
|
+
const lockAge = Date.now() - new Date(data.startedAt).getTime();
|
|
490
|
+
return lockAge < STALE_TIMEOUT_MS;
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
return false; // Corrupt lock = not locked
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
423
497
|
/**
|
|
424
498
|
* Build the ownership tree from discovered .meta/ paths.
|
|
425
499
|
*
|
|
@@ -497,6 +571,138 @@ function findNode(tree, targetPath) {
|
|
|
497
571
|
return Array.from(tree.nodes.values()).find((n) => n.metaPath === targetPath || n.ownerPath === targetPath);
|
|
498
572
|
}
|
|
499
573
|
|
|
574
|
+
/**
|
|
575
|
+
* Unified meta listing: scan, dedup, enrich.
|
|
576
|
+
*
|
|
577
|
+
* Single source of truth for all consumers that need a list of metas
|
|
578
|
+
* with enriched metadata. Replaces duplicated scan+dedup logic in
|
|
579
|
+
* plugin tools, CLI, and prompt injection.
|
|
580
|
+
*
|
|
581
|
+
* @module discovery/listMetas
|
|
582
|
+
*/
|
|
583
|
+
/**
|
|
584
|
+
* Discover, deduplicate, and enrich all metas.
|
|
585
|
+
*
|
|
586
|
+
* This is the single consolidated function that replaces all duplicated
|
|
587
|
+
* scan+dedup+enrich logic across the codebase. All enrichment comes from
|
|
588
|
+
* reading meta.json on disk (the canonical source).
|
|
589
|
+
*
|
|
590
|
+
* @param config - Validated synthesis config.
|
|
591
|
+
* @param watcher - Watcher HTTP client for discovery.
|
|
592
|
+
* @returns Enriched meta list with summary statistics and ownership tree.
|
|
593
|
+
*/
|
|
594
|
+
async function listMetas(config, watcher) {
|
|
595
|
+
// Step 1: Discover deduplicated meta paths via watcher scan
|
|
596
|
+
const metaPaths = await discoverMetas(config, watcher);
|
|
597
|
+
// Step 2: Build ownership tree
|
|
598
|
+
const tree = buildOwnershipTree(metaPaths);
|
|
599
|
+
// Step 3: Read and enrich each meta from disk
|
|
600
|
+
const entries = [];
|
|
601
|
+
let staleCount = 0;
|
|
602
|
+
let errorCount = 0;
|
|
603
|
+
let lockedCount = 0;
|
|
604
|
+
let neverSynthesizedCount = 0;
|
|
605
|
+
let totalArchTokens = 0;
|
|
606
|
+
let totalBuilderTokens = 0;
|
|
607
|
+
let totalCriticTokens = 0;
|
|
608
|
+
let lastSynthPath = null;
|
|
609
|
+
let lastSynthAt = null;
|
|
610
|
+
let stalestPath = null;
|
|
611
|
+
let stalestEffective = -1;
|
|
612
|
+
for (const node of tree.nodes.values()) {
|
|
613
|
+
let meta;
|
|
614
|
+
try {
|
|
615
|
+
meta = JSON.parse(readFileSync(join(node.metaPath, 'meta.json'), 'utf8'));
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
// Skip unreadable metas
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const depth = meta._depth ?? node.treeDepth;
|
|
622
|
+
const emphasis = meta._emphasis ?? 1;
|
|
623
|
+
const hasError = Boolean(meta._error);
|
|
624
|
+
const locked = isLocked(normalizePath$1(node.metaPath));
|
|
625
|
+
const neverSynth = !meta._generatedAt;
|
|
626
|
+
// Compute staleness
|
|
627
|
+
let stalenessSeconds;
|
|
628
|
+
if (neverSynth) {
|
|
629
|
+
stalenessSeconds = Infinity;
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
const genAt = new Date(meta._generatedAt).getTime();
|
|
633
|
+
stalenessSeconds = Math.max(0, Math.floor((Date.now() - genAt) / 1000));
|
|
634
|
+
}
|
|
635
|
+
// Tokens
|
|
636
|
+
const archTokens = meta._architectTokens ?? 0;
|
|
637
|
+
const buildTokens = meta._builderTokens ?? 0;
|
|
638
|
+
const critTokens = meta._criticTokens ?? 0;
|
|
639
|
+
// Accumulate summary stats
|
|
640
|
+
if (stalenessSeconds > 0)
|
|
641
|
+
staleCount++;
|
|
642
|
+
if (hasError)
|
|
643
|
+
errorCount++;
|
|
644
|
+
if (locked)
|
|
645
|
+
lockedCount++;
|
|
646
|
+
if (neverSynth)
|
|
647
|
+
neverSynthesizedCount++;
|
|
648
|
+
totalArchTokens += archTokens;
|
|
649
|
+
totalBuilderTokens += buildTokens;
|
|
650
|
+
totalCriticTokens += critTokens;
|
|
651
|
+
// Track last synthesized
|
|
652
|
+
if (meta._generatedAt) {
|
|
653
|
+
if (!lastSynthAt || meta._generatedAt > lastSynthAt) {
|
|
654
|
+
lastSynthAt = meta._generatedAt;
|
|
655
|
+
lastSynthPath = node.metaPath;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// Track stalest (effective staleness for scheduling)
|
|
659
|
+
const depthFactor = Math.pow(1 + config.depthWeight, depth);
|
|
660
|
+
const effectiveStaleness = (stalenessSeconds === Infinity
|
|
661
|
+
? Number.MAX_SAFE_INTEGER
|
|
662
|
+
: stalenessSeconds) *
|
|
663
|
+
depthFactor *
|
|
664
|
+
emphasis;
|
|
665
|
+
if (effectiveStaleness > stalestEffective) {
|
|
666
|
+
stalestEffective = effectiveStaleness;
|
|
667
|
+
stalestPath = node.metaPath;
|
|
668
|
+
}
|
|
669
|
+
entries.push({
|
|
670
|
+
path: node.metaPath,
|
|
671
|
+
depth,
|
|
672
|
+
emphasis,
|
|
673
|
+
stalenessSeconds,
|
|
674
|
+
lastSynthesized: meta._generatedAt ?? null,
|
|
675
|
+
hasError,
|
|
676
|
+
locked,
|
|
677
|
+
architectTokens: archTokens > 0 ? archTokens : null,
|
|
678
|
+
builderTokens: buildTokens > 0 ? buildTokens : null,
|
|
679
|
+
criticTokens: critTokens > 0 ? critTokens : null,
|
|
680
|
+
children: node.children.length,
|
|
681
|
+
node,
|
|
682
|
+
meta,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
summary: {
|
|
687
|
+
total: entries.length,
|
|
688
|
+
stale: staleCount,
|
|
689
|
+
errors: errorCount,
|
|
690
|
+
locked: lockedCount,
|
|
691
|
+
neverSynthesized: neverSynthesizedCount,
|
|
692
|
+
tokens: {
|
|
693
|
+
architect: totalArchTokens,
|
|
694
|
+
builder: totalBuilderTokens,
|
|
695
|
+
critic: totalCriticTokens,
|
|
696
|
+
},
|
|
697
|
+
stalestPath,
|
|
698
|
+
lastSynthesizedPath: lastSynthPath,
|
|
699
|
+
lastSynthesizedAt: lastSynthAt,
|
|
700
|
+
},
|
|
701
|
+
entries,
|
|
702
|
+
tree,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
500
706
|
/**
|
|
501
707
|
* Compute the file scope owned by a meta node.
|
|
502
708
|
*
|
|
@@ -690,80 +896,6 @@ class GatewayExecutor {
|
|
|
690
896
|
}
|
|
691
897
|
}
|
|
692
898
|
|
|
693
|
-
/**
|
|
694
|
-
* File-system lock for preventing concurrent synthesis on the same meta.
|
|
695
|
-
*
|
|
696
|
-
* Lock file: .meta/.lock containing PID + timestamp.
|
|
697
|
-
* Stale timeout: 30 minutes.
|
|
698
|
-
*
|
|
699
|
-
* @module lock
|
|
700
|
-
*/
|
|
701
|
-
const LOCK_FILE = '.lock';
|
|
702
|
-
const STALE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
703
|
-
/**
|
|
704
|
-
* Attempt to acquire a lock on a .meta directory.
|
|
705
|
-
*
|
|
706
|
-
* @param metaPath - Absolute path to the .meta directory.
|
|
707
|
-
* @returns True if lock was acquired, false if already locked (non-stale).
|
|
708
|
-
*/
|
|
709
|
-
function acquireLock(metaPath) {
|
|
710
|
-
const lockPath = join(metaPath, LOCK_FILE);
|
|
711
|
-
if (existsSync(lockPath)) {
|
|
712
|
-
try {
|
|
713
|
-
const raw = readFileSync(lockPath, 'utf8');
|
|
714
|
-
const data = JSON.parse(raw);
|
|
715
|
-
const lockAge = Date.now() - new Date(data.startedAt).getTime();
|
|
716
|
-
if (lockAge < STALE_TIMEOUT_MS) {
|
|
717
|
-
return false; // Lock is active
|
|
718
|
-
}
|
|
719
|
-
// Stale lock — fall through to overwrite
|
|
720
|
-
}
|
|
721
|
-
catch {
|
|
722
|
-
// Corrupt lock file — overwrite
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
const lock = {
|
|
726
|
-
pid: process.pid,
|
|
727
|
-
startedAt: new Date().toISOString(),
|
|
728
|
-
};
|
|
729
|
-
writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
730
|
-
return true;
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* Release a lock on a .meta directory.
|
|
734
|
-
*
|
|
735
|
-
* @param metaPath - Absolute path to the .meta directory.
|
|
736
|
-
*/
|
|
737
|
-
function releaseLock(metaPath) {
|
|
738
|
-
const lockPath = join(metaPath, LOCK_FILE);
|
|
739
|
-
try {
|
|
740
|
-
unlinkSync(lockPath);
|
|
741
|
-
}
|
|
742
|
-
catch {
|
|
743
|
-
// Already removed or never existed
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
/**
|
|
747
|
-
* Check if a .meta directory is currently locked (non-stale).
|
|
748
|
-
*
|
|
749
|
-
* @param metaPath - Absolute path to the .meta directory.
|
|
750
|
-
* @returns True if locked and not stale.
|
|
751
|
-
*/
|
|
752
|
-
function isLocked(metaPath) {
|
|
753
|
-
const lockPath = join(metaPath, LOCK_FILE);
|
|
754
|
-
if (!existsSync(lockPath))
|
|
755
|
-
return false;
|
|
756
|
-
try {
|
|
757
|
-
const raw = readFileSync(lockPath, 'utf8');
|
|
758
|
-
const data = JSON.parse(raw);
|
|
759
|
-
const lockAge = Date.now() - new Date(data.startedAt).getTime();
|
|
760
|
-
return lockAge < STALE_TIMEOUT_MS;
|
|
761
|
-
}
|
|
762
|
-
catch {
|
|
763
|
-
return false; // Corrupt lock = not locked
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
899
|
/**
|
|
768
900
|
* Build the SynthContext for a synthesis cycle.
|
|
769
901
|
*
|
|
@@ -1557,16 +1689,31 @@ class HttpWatcherClient {
|
|
|
1557
1689
|
throw new Error('Retry exhausted');
|
|
1558
1690
|
}
|
|
1559
1691
|
async scan(params) {
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1692
|
+
// Build Qdrant filter: merge explicit filter with pathPrefix/modifiedAfter
|
|
1693
|
+
const mustClauses = [];
|
|
1694
|
+
// Carry over any existing 'must' clauses from the provided filter
|
|
1695
|
+
if (params.filter) {
|
|
1696
|
+
const existing = params.filter.must;
|
|
1697
|
+
if (Array.isArray(existing)) {
|
|
1698
|
+
mustClauses.push(...existing);
|
|
1699
|
+
}
|
|
1563
1700
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1701
|
+
// Translate pathPrefix into a Qdrant text match on file_path
|
|
1702
|
+
if (params.pathPrefix !== undefined) {
|
|
1703
|
+
mustClauses.push({
|
|
1704
|
+
key: 'file_path',
|
|
1705
|
+
match: { text: params.pathPrefix },
|
|
1706
|
+
});
|
|
1566
1707
|
}
|
|
1708
|
+
// Translate modifiedAfter into a Qdrant range filter on modified_at
|
|
1567
1709
|
if (params.modifiedAfter !== undefined) {
|
|
1568
|
-
|
|
1710
|
+
mustClauses.push({
|
|
1711
|
+
key: 'modified_at',
|
|
1712
|
+
range: { gt: params.modifiedAfter },
|
|
1713
|
+
});
|
|
1569
1714
|
}
|
|
1715
|
+
const filter = { must: mustClauses };
|
|
1716
|
+
const body = { filter };
|
|
1570
1717
|
if (params.fields !== undefined) {
|
|
1571
1718
|
body.fields = params.fields;
|
|
1572
1719
|
}
|
|
@@ -1630,6 +1777,7 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
1630
1777
|
isLocked: isLocked,
|
|
1631
1778
|
isStale: isStale,
|
|
1632
1779
|
listArchiveFiles: listArchiveFiles,
|
|
1780
|
+
listMetas: listMetas,
|
|
1633
1781
|
loadSynthConfig: loadSynthConfig,
|
|
1634
1782
|
mergeAndWrite: mergeAndWrite,
|
|
1635
1783
|
metaJsonSchema: metaJsonSchema,
|
|
@@ -1700,85 +1848,36 @@ function output(data) {
|
|
|
1700
1848
|
}
|
|
1701
1849
|
async function runStatus(config) {
|
|
1702
1850
|
const watcher = new HttpWatcherClient({ baseUrl: config.watcherUrl });
|
|
1703
|
-
const
|
|
1704
|
-
|
|
1705
|
-
let stale = 0;
|
|
1706
|
-
let errors = 0;
|
|
1707
|
-
let locked = 0;
|
|
1708
|
-
let neverSynth = 0;
|
|
1709
|
-
let archTokens = 0;
|
|
1710
|
-
let buildTokens = 0;
|
|
1711
|
-
let critTokens = 0;
|
|
1712
|
-
for (const node of tree.nodes.values()) {
|
|
1713
|
-
let meta;
|
|
1714
|
-
try {
|
|
1715
|
-
meta = readMeta(node.metaPath);
|
|
1716
|
-
}
|
|
1717
|
-
catch {
|
|
1718
|
-
continue;
|
|
1719
|
-
}
|
|
1720
|
-
const s = actualStaleness(meta);
|
|
1721
|
-
if (s > 0)
|
|
1722
|
-
stale++;
|
|
1723
|
-
if (meta._error)
|
|
1724
|
-
errors++;
|
|
1725
|
-
if (isLocked(normalizePath$1(node.metaPath)))
|
|
1726
|
-
locked++;
|
|
1727
|
-
if (!meta._generatedAt)
|
|
1728
|
-
neverSynth++;
|
|
1729
|
-
if (meta._architectTokens)
|
|
1730
|
-
archTokens += meta._architectTokens;
|
|
1731
|
-
if (meta._builderTokens)
|
|
1732
|
-
buildTokens += meta._builderTokens;
|
|
1733
|
-
if (meta._criticTokens)
|
|
1734
|
-
critTokens += meta._criticTokens;
|
|
1735
|
-
}
|
|
1736
|
-
output({
|
|
1737
|
-
total: tree.nodes.size,
|
|
1738
|
-
stale,
|
|
1739
|
-
errors,
|
|
1740
|
-
locked,
|
|
1741
|
-
neverSynthesized: neverSynth,
|
|
1742
|
-
tokens: { architect: archTokens, builder: buildTokens, critic: critTokens },
|
|
1743
|
-
});
|
|
1851
|
+
const result = await listMetas(config, watcher);
|
|
1852
|
+
output(result.summary);
|
|
1744
1853
|
}
|
|
1745
1854
|
async function runList(config) {
|
|
1746
1855
|
const prefix = getArg('--prefix');
|
|
1747
1856
|
const filter = getArg('--filter');
|
|
1748
1857
|
const watcher = new HttpWatcherClient({ baseUrl: config.watcherUrl });
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
continue;
|
|
1773
|
-
rows.push({
|
|
1774
|
-
path: node.metaPath,
|
|
1775
|
-
depth: meta._depth ?? node.treeDepth,
|
|
1776
|
-
staleness: s === Infinity ? 'never' : String(Math.round(s)) + 's',
|
|
1777
|
-
hasError,
|
|
1778
|
-
locked: isLockedNow,
|
|
1779
|
-
children: node.children.length,
|
|
1780
|
-
});
|
|
1781
|
-
}
|
|
1858
|
+
const result = await listMetas(config, watcher);
|
|
1859
|
+
let entries = result.entries;
|
|
1860
|
+
if (prefix) {
|
|
1861
|
+
entries = entries.filter((e) => e.path.includes(prefix));
|
|
1862
|
+
}
|
|
1863
|
+
if (filter === 'hasError')
|
|
1864
|
+
entries = entries.filter((e) => e.hasError);
|
|
1865
|
+
if (filter === 'stale')
|
|
1866
|
+
entries = entries.filter((e) => e.stalenessSeconds > 0);
|
|
1867
|
+
if (filter === 'locked')
|
|
1868
|
+
entries = entries.filter((e) => e.locked);
|
|
1869
|
+
if (filter === 'never')
|
|
1870
|
+
entries = entries.filter((e) => e.stalenessSeconds === Infinity);
|
|
1871
|
+
const rows = entries.map((e) => ({
|
|
1872
|
+
path: e.path,
|
|
1873
|
+
depth: e.depth,
|
|
1874
|
+
staleness: e.stalenessSeconds === Infinity
|
|
1875
|
+
? 'never'
|
|
1876
|
+
: String(Math.round(e.stalenessSeconds)) + 's',
|
|
1877
|
+
hasError: e.hasError,
|
|
1878
|
+
locked: e.locked,
|
|
1879
|
+
children: e.children,
|
|
1880
|
+
}));
|
|
1782
1881
|
output({ total: rows.length, items: rows });
|
|
1783
1882
|
}
|
|
1784
1883
|
async function runDetail(config) {
|
|
@@ -1789,10 +1888,9 @@ async function runDetail(config) {
|
|
|
1789
1888
|
}
|
|
1790
1889
|
const archiveArg = getArg('--archive');
|
|
1791
1890
|
const watcher = new HttpWatcherClient({ baseUrl: config.watcherUrl });
|
|
1792
|
-
const
|
|
1793
|
-
const tree = buildOwnershipTree(metaPaths);
|
|
1891
|
+
const metaResult = await listMetas(config, watcher);
|
|
1794
1892
|
const normalized = normalizePath$1(targetPath);
|
|
1795
|
-
const node = findNode(tree, normalized);
|
|
1893
|
+
const node = findNode(metaResult.tree, normalized);
|
|
1796
1894
|
if (!node) {
|
|
1797
1895
|
console.error('Meta not found: ' + targetPath);
|
|
1798
1896
|
process.exit(1);
|
|
@@ -1813,33 +1911,26 @@ async function runDetail(config) {
|
|
|
1813
1911
|
}
|
|
1814
1912
|
async function runPreview(config) {
|
|
1815
1913
|
const targetPath = getArg('--path');
|
|
1816
|
-
const { filterInScope, paginatedScan, readLatestArchive, computeStructureHash,
|
|
1914
|
+
const { filterInScope, paginatedScan, readLatestArchive, computeStructureHash, } = await Promise.resolve().then(function () { return index; });
|
|
1817
1915
|
const watcher = new HttpWatcherClient({ baseUrl: config.watcherUrl });
|
|
1818
|
-
const
|
|
1819
|
-
const tree = buildOwnershipTree(metaPaths);
|
|
1916
|
+
const metaResult = await listMetas(config, watcher);
|
|
1820
1917
|
let targetNode;
|
|
1821
1918
|
if (targetPath) {
|
|
1822
1919
|
const normalized = normalizePath$1(targetPath);
|
|
1823
|
-
targetNode = findNode(tree, normalized);
|
|
1920
|
+
targetNode = findNode(metaResult.tree, normalized);
|
|
1824
1921
|
if (!targetNode) {
|
|
1825
1922
|
console.error('Meta not found: ' + targetPath);
|
|
1826
1923
|
process.exit(1);
|
|
1827
1924
|
}
|
|
1828
1925
|
}
|
|
1829
1926
|
else {
|
|
1830
|
-
const candidates =
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
continue;
|
|
1838
|
-
}
|
|
1839
|
-
const s = actualStaleness(meta);
|
|
1840
|
-
if (s > 0)
|
|
1841
|
-
candidates.push({ node, meta, actualStaleness: s });
|
|
1842
|
-
}
|
|
1927
|
+
const candidates = metaResult.entries
|
|
1928
|
+
.filter((e) => e.stalenessSeconds > 0)
|
|
1929
|
+
.map((e) => ({
|
|
1930
|
+
node: e.node,
|
|
1931
|
+
meta: e.meta,
|
|
1932
|
+
actualStaleness: e.stalenessSeconds,
|
|
1933
|
+
}));
|
|
1843
1934
|
const weighted = computeEffectiveStaleness(candidates, config.depthWeight);
|
|
1844
1935
|
const winner = selectCandidate(weighted);
|
|
1845
1936
|
if (!winner) {
|
package/dist/index.d.ts
CHANGED
|
@@ -389,6 +389,80 @@ interface OwnershipTree {
|
|
|
389
389
|
roots: MetaNode[];
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Unified meta listing: scan, dedup, enrich.
|
|
394
|
+
*
|
|
395
|
+
* Single source of truth for all consumers that need a list of metas
|
|
396
|
+
* with enriched metadata. Replaces duplicated scan+dedup logic in
|
|
397
|
+
* plugin tools, CLI, and prompt injection.
|
|
398
|
+
*
|
|
399
|
+
* @module discovery/listMetas
|
|
400
|
+
*/
|
|
401
|
+
|
|
402
|
+
/** Enriched meta entry returned by listMetas(). */
|
|
403
|
+
interface MetaEntry {
|
|
404
|
+
/** Normalized .meta/ directory path. */
|
|
405
|
+
path: string;
|
|
406
|
+
/** Tree depth (0 = leaf, higher = more abstract). */
|
|
407
|
+
depth: number;
|
|
408
|
+
/** Scheduling emphasis multiplier. */
|
|
409
|
+
emphasis: number;
|
|
410
|
+
/** Seconds since last synthesis, or Infinity if never synthesized. */
|
|
411
|
+
stalenessSeconds: number;
|
|
412
|
+
/** ISO timestamp of last synthesis, or null. */
|
|
413
|
+
lastSynthesized: string | null;
|
|
414
|
+
/** Whether the last synthesis had an error. */
|
|
415
|
+
hasError: boolean;
|
|
416
|
+
/** Whether this meta is currently locked. */
|
|
417
|
+
locked: boolean;
|
|
418
|
+
/** Cumulative architect tokens, or null if never run. */
|
|
419
|
+
architectTokens: number | null;
|
|
420
|
+
/** Cumulative builder tokens, or null if never run. */
|
|
421
|
+
builderTokens: number | null;
|
|
422
|
+
/** Cumulative critic tokens, or null if never run. */
|
|
423
|
+
criticTokens: number | null;
|
|
424
|
+
/** Number of direct children in the ownership tree. */
|
|
425
|
+
children: number;
|
|
426
|
+
/** The underlying MetaNode from the ownership tree. */
|
|
427
|
+
node: MetaNode;
|
|
428
|
+
/** The parsed meta.json content. */
|
|
429
|
+
meta: MetaJson;
|
|
430
|
+
}
|
|
431
|
+
/** Summary statistics computed from the meta list. */
|
|
432
|
+
interface MetaListSummary {
|
|
433
|
+
total: number;
|
|
434
|
+
stale: number;
|
|
435
|
+
errors: number;
|
|
436
|
+
locked: number;
|
|
437
|
+
neverSynthesized: number;
|
|
438
|
+
tokens: {
|
|
439
|
+
architect: number;
|
|
440
|
+
builder: number;
|
|
441
|
+
critic: number;
|
|
442
|
+
};
|
|
443
|
+
stalestPath: string | null;
|
|
444
|
+
lastSynthesizedPath: string | null;
|
|
445
|
+
lastSynthesizedAt: string | null;
|
|
446
|
+
}
|
|
447
|
+
/** Full result from listMetas(). */
|
|
448
|
+
interface MetaListResult {
|
|
449
|
+
summary: MetaListSummary;
|
|
450
|
+
entries: MetaEntry[];
|
|
451
|
+
tree: OwnershipTree;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Discover, deduplicate, and enrich all metas.
|
|
455
|
+
*
|
|
456
|
+
* This is the single consolidated function that replaces all duplicated
|
|
457
|
+
* scan+dedup+enrich logic across the codebase. All enrichment comes from
|
|
458
|
+
* reading meta.json on disk (the canonical source).
|
|
459
|
+
*
|
|
460
|
+
* @param config - Validated synthesis config.
|
|
461
|
+
* @param watcher - Watcher HTTP client for discovery.
|
|
462
|
+
* @returns Enriched meta list with summary statistics and ownership tree.
|
|
463
|
+
*/
|
|
464
|
+
declare function listMetas(config: SynthConfig, watcher: WatcherClient): Promise<MetaListResult>;
|
|
465
|
+
|
|
392
466
|
/**
|
|
393
467
|
* Build the ownership tree from discovered .meta/ paths.
|
|
394
468
|
*
|
|
@@ -895,5 +969,5 @@ declare class HttpWatcherClient implements WatcherClient {
|
|
|
895
969
|
unregisterRules(source: string): Promise<void>;
|
|
896
970
|
}
|
|
897
971
|
|
|
898
|
-
export { GatewayExecutor, HttpWatcherClient, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildMetaFilter, buildOwnershipTree, computeEffectiveStaleness, computeEma, computeStructureHash, createSnapshot, discoverMetas, filterInScope, findNode, getScopePrefix, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, loadSynthConfig, mergeAndWrite, metaJsonSchema, normalizePath, orchestrate, paginatedScan, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, releaseLock, resolveConfigPath, selectCandidate, synthConfigSchema, synthErrorSchema, toSynthError };
|
|
899
|
-
export type { BuilderOutput, GatewayExecutorOptions, HttpWatcherClientOptions, InferenceRuleSpec, MergeOptions, MetaJson, MetaNode, OrchestrateResult, OwnershipTree, ScanFile, ScanParams, ScanResponse, StalenessCandidate, SynthConfig, SynthContext, SynthError, SynthExecutor, SynthSpawnOptions, SynthSpawnResult, WatcherClient };
|
|
972
|
+
export { GatewayExecutor, HttpWatcherClient, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildMetaFilter, buildOwnershipTree, computeEffectiveStaleness, computeEma, computeStructureHash, createSnapshot, discoverMetas, filterInScope, findNode, getScopePrefix, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, listMetas, loadSynthConfig, mergeAndWrite, metaJsonSchema, normalizePath, orchestrate, paginatedScan, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, releaseLock, resolveConfigPath, selectCandidate, synthConfigSchema, synthErrorSchema, toSynthError };
|
|
973
|
+
export type { BuilderOutput, GatewayExecutorOptions, HttpWatcherClientOptions, InferenceRuleSpec, MergeOptions, MetaEntry, MetaJson, MetaListResult, MetaListSummary, MetaNode, OrchestrateResult, OwnershipTree, ScanFile, ScanParams, ScanResponse, StalenessCandidate, SynthConfig, SynthContext, SynthError, SynthExecutor, SynthSpawnOptions, SynthSpawnResult, WatcherClient };
|
package/dist/index.js
CHANGED
|
@@ -405,6 +405,80 @@ async function discoverMetas(config, watcher) {
|
|
|
405
405
|
return metaPaths;
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
/**
|
|
409
|
+
* File-system lock for preventing concurrent synthesis on the same meta.
|
|
410
|
+
*
|
|
411
|
+
* Lock file: .meta/.lock containing PID + timestamp.
|
|
412
|
+
* Stale timeout: 30 minutes.
|
|
413
|
+
*
|
|
414
|
+
* @module lock
|
|
415
|
+
*/
|
|
416
|
+
const LOCK_FILE = '.lock';
|
|
417
|
+
const STALE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
418
|
+
/**
|
|
419
|
+
* Attempt to acquire a lock on a .meta directory.
|
|
420
|
+
*
|
|
421
|
+
* @param metaPath - Absolute path to the .meta directory.
|
|
422
|
+
* @returns True if lock was acquired, false if already locked (non-stale).
|
|
423
|
+
*/
|
|
424
|
+
function acquireLock(metaPath) {
|
|
425
|
+
const lockPath = join(metaPath, LOCK_FILE);
|
|
426
|
+
if (existsSync(lockPath)) {
|
|
427
|
+
try {
|
|
428
|
+
const raw = readFileSync(lockPath, 'utf8');
|
|
429
|
+
const data = JSON.parse(raw);
|
|
430
|
+
const lockAge = Date.now() - new Date(data.startedAt).getTime();
|
|
431
|
+
if (lockAge < STALE_TIMEOUT_MS) {
|
|
432
|
+
return false; // Lock is active
|
|
433
|
+
}
|
|
434
|
+
// Stale lock — fall through to overwrite
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// Corrupt lock file — overwrite
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const lock = {
|
|
441
|
+
pid: process.pid,
|
|
442
|
+
startedAt: new Date().toISOString(),
|
|
443
|
+
};
|
|
444
|
+
writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Release a lock on a .meta directory.
|
|
449
|
+
*
|
|
450
|
+
* @param metaPath - Absolute path to the .meta directory.
|
|
451
|
+
*/
|
|
452
|
+
function releaseLock(metaPath) {
|
|
453
|
+
const lockPath = join(metaPath, LOCK_FILE);
|
|
454
|
+
try {
|
|
455
|
+
unlinkSync(lockPath);
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
// Already removed or never existed
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Check if a .meta directory is currently locked (non-stale).
|
|
463
|
+
*
|
|
464
|
+
* @param metaPath - Absolute path to the .meta directory.
|
|
465
|
+
* @returns True if locked and not stale.
|
|
466
|
+
*/
|
|
467
|
+
function isLocked(metaPath) {
|
|
468
|
+
const lockPath = join(metaPath, LOCK_FILE);
|
|
469
|
+
if (!existsSync(lockPath))
|
|
470
|
+
return false;
|
|
471
|
+
try {
|
|
472
|
+
const raw = readFileSync(lockPath, 'utf8');
|
|
473
|
+
const data = JSON.parse(raw);
|
|
474
|
+
const lockAge = Date.now() - new Date(data.startedAt).getTime();
|
|
475
|
+
return lockAge < STALE_TIMEOUT_MS;
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
return false; // Corrupt lock = not locked
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
408
482
|
/**
|
|
409
483
|
* Build the ownership tree from discovered .meta/ paths.
|
|
410
484
|
*
|
|
@@ -482,6 +556,138 @@ function findNode(tree, targetPath) {
|
|
|
482
556
|
return Array.from(tree.nodes.values()).find((n) => n.metaPath === targetPath || n.ownerPath === targetPath);
|
|
483
557
|
}
|
|
484
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Unified meta listing: scan, dedup, enrich.
|
|
561
|
+
*
|
|
562
|
+
* Single source of truth for all consumers that need a list of metas
|
|
563
|
+
* with enriched metadata. Replaces duplicated scan+dedup logic in
|
|
564
|
+
* plugin tools, CLI, and prompt injection.
|
|
565
|
+
*
|
|
566
|
+
* @module discovery/listMetas
|
|
567
|
+
*/
|
|
568
|
+
/**
|
|
569
|
+
* Discover, deduplicate, and enrich all metas.
|
|
570
|
+
*
|
|
571
|
+
* This is the single consolidated function that replaces all duplicated
|
|
572
|
+
* scan+dedup+enrich logic across the codebase. All enrichment comes from
|
|
573
|
+
* reading meta.json on disk (the canonical source).
|
|
574
|
+
*
|
|
575
|
+
* @param config - Validated synthesis config.
|
|
576
|
+
* @param watcher - Watcher HTTP client for discovery.
|
|
577
|
+
* @returns Enriched meta list with summary statistics and ownership tree.
|
|
578
|
+
*/
|
|
579
|
+
async function listMetas(config, watcher) {
|
|
580
|
+
// Step 1: Discover deduplicated meta paths via watcher scan
|
|
581
|
+
const metaPaths = await discoverMetas(config, watcher);
|
|
582
|
+
// Step 2: Build ownership tree
|
|
583
|
+
const tree = buildOwnershipTree(metaPaths);
|
|
584
|
+
// Step 3: Read and enrich each meta from disk
|
|
585
|
+
const entries = [];
|
|
586
|
+
let staleCount = 0;
|
|
587
|
+
let errorCount = 0;
|
|
588
|
+
let lockedCount = 0;
|
|
589
|
+
let neverSynthesizedCount = 0;
|
|
590
|
+
let totalArchTokens = 0;
|
|
591
|
+
let totalBuilderTokens = 0;
|
|
592
|
+
let totalCriticTokens = 0;
|
|
593
|
+
let lastSynthPath = null;
|
|
594
|
+
let lastSynthAt = null;
|
|
595
|
+
let stalestPath = null;
|
|
596
|
+
let stalestEffective = -1;
|
|
597
|
+
for (const node of tree.nodes.values()) {
|
|
598
|
+
let meta;
|
|
599
|
+
try {
|
|
600
|
+
meta = JSON.parse(readFileSync(join(node.metaPath, 'meta.json'), 'utf8'));
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
// Skip unreadable metas
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const depth = meta._depth ?? node.treeDepth;
|
|
607
|
+
const emphasis = meta._emphasis ?? 1;
|
|
608
|
+
const hasError = Boolean(meta._error);
|
|
609
|
+
const locked = isLocked(normalizePath$1(node.metaPath));
|
|
610
|
+
const neverSynth = !meta._generatedAt;
|
|
611
|
+
// Compute staleness
|
|
612
|
+
let stalenessSeconds;
|
|
613
|
+
if (neverSynth) {
|
|
614
|
+
stalenessSeconds = Infinity;
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
const genAt = new Date(meta._generatedAt).getTime();
|
|
618
|
+
stalenessSeconds = Math.max(0, Math.floor((Date.now() - genAt) / 1000));
|
|
619
|
+
}
|
|
620
|
+
// Tokens
|
|
621
|
+
const archTokens = meta._architectTokens ?? 0;
|
|
622
|
+
const buildTokens = meta._builderTokens ?? 0;
|
|
623
|
+
const critTokens = meta._criticTokens ?? 0;
|
|
624
|
+
// Accumulate summary stats
|
|
625
|
+
if (stalenessSeconds > 0)
|
|
626
|
+
staleCount++;
|
|
627
|
+
if (hasError)
|
|
628
|
+
errorCount++;
|
|
629
|
+
if (locked)
|
|
630
|
+
lockedCount++;
|
|
631
|
+
if (neverSynth)
|
|
632
|
+
neverSynthesizedCount++;
|
|
633
|
+
totalArchTokens += archTokens;
|
|
634
|
+
totalBuilderTokens += buildTokens;
|
|
635
|
+
totalCriticTokens += critTokens;
|
|
636
|
+
// Track last synthesized
|
|
637
|
+
if (meta._generatedAt) {
|
|
638
|
+
if (!lastSynthAt || meta._generatedAt > lastSynthAt) {
|
|
639
|
+
lastSynthAt = meta._generatedAt;
|
|
640
|
+
lastSynthPath = node.metaPath;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// Track stalest (effective staleness for scheduling)
|
|
644
|
+
const depthFactor = Math.pow(1 + config.depthWeight, depth);
|
|
645
|
+
const effectiveStaleness = (stalenessSeconds === Infinity
|
|
646
|
+
? Number.MAX_SAFE_INTEGER
|
|
647
|
+
: stalenessSeconds) *
|
|
648
|
+
depthFactor *
|
|
649
|
+
emphasis;
|
|
650
|
+
if (effectiveStaleness > stalestEffective) {
|
|
651
|
+
stalestEffective = effectiveStaleness;
|
|
652
|
+
stalestPath = node.metaPath;
|
|
653
|
+
}
|
|
654
|
+
entries.push({
|
|
655
|
+
path: node.metaPath,
|
|
656
|
+
depth,
|
|
657
|
+
emphasis,
|
|
658
|
+
stalenessSeconds,
|
|
659
|
+
lastSynthesized: meta._generatedAt ?? null,
|
|
660
|
+
hasError,
|
|
661
|
+
locked,
|
|
662
|
+
architectTokens: archTokens > 0 ? archTokens : null,
|
|
663
|
+
builderTokens: buildTokens > 0 ? buildTokens : null,
|
|
664
|
+
criticTokens: critTokens > 0 ? critTokens : null,
|
|
665
|
+
children: node.children.length,
|
|
666
|
+
node,
|
|
667
|
+
meta,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
summary: {
|
|
672
|
+
total: entries.length,
|
|
673
|
+
stale: staleCount,
|
|
674
|
+
errors: errorCount,
|
|
675
|
+
locked: lockedCount,
|
|
676
|
+
neverSynthesized: neverSynthesizedCount,
|
|
677
|
+
tokens: {
|
|
678
|
+
architect: totalArchTokens,
|
|
679
|
+
builder: totalBuilderTokens,
|
|
680
|
+
critic: totalCriticTokens,
|
|
681
|
+
},
|
|
682
|
+
stalestPath,
|
|
683
|
+
lastSynthesizedPath: lastSynthPath,
|
|
684
|
+
lastSynthesizedAt: lastSynthAt,
|
|
685
|
+
},
|
|
686
|
+
entries,
|
|
687
|
+
tree,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
485
691
|
/**
|
|
486
692
|
* Compute the file scope owned by a meta node.
|
|
487
693
|
*
|
|
@@ -675,80 +881,6 @@ class GatewayExecutor {
|
|
|
675
881
|
}
|
|
676
882
|
}
|
|
677
883
|
|
|
678
|
-
/**
|
|
679
|
-
* File-system lock for preventing concurrent synthesis on the same meta.
|
|
680
|
-
*
|
|
681
|
-
* Lock file: .meta/.lock containing PID + timestamp.
|
|
682
|
-
* Stale timeout: 30 minutes.
|
|
683
|
-
*
|
|
684
|
-
* @module lock
|
|
685
|
-
*/
|
|
686
|
-
const LOCK_FILE = '.lock';
|
|
687
|
-
const STALE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
688
|
-
/**
|
|
689
|
-
* Attempt to acquire a lock on a .meta directory.
|
|
690
|
-
*
|
|
691
|
-
* @param metaPath - Absolute path to the .meta directory.
|
|
692
|
-
* @returns True if lock was acquired, false if already locked (non-stale).
|
|
693
|
-
*/
|
|
694
|
-
function acquireLock(metaPath) {
|
|
695
|
-
const lockPath = join(metaPath, LOCK_FILE);
|
|
696
|
-
if (existsSync(lockPath)) {
|
|
697
|
-
try {
|
|
698
|
-
const raw = readFileSync(lockPath, 'utf8');
|
|
699
|
-
const data = JSON.parse(raw);
|
|
700
|
-
const lockAge = Date.now() - new Date(data.startedAt).getTime();
|
|
701
|
-
if (lockAge < STALE_TIMEOUT_MS) {
|
|
702
|
-
return false; // Lock is active
|
|
703
|
-
}
|
|
704
|
-
// Stale lock — fall through to overwrite
|
|
705
|
-
}
|
|
706
|
-
catch {
|
|
707
|
-
// Corrupt lock file — overwrite
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
const lock = {
|
|
711
|
-
pid: process.pid,
|
|
712
|
-
startedAt: new Date().toISOString(),
|
|
713
|
-
};
|
|
714
|
-
writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
715
|
-
return true;
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* Release a lock on a .meta directory.
|
|
719
|
-
*
|
|
720
|
-
* @param metaPath - Absolute path to the .meta directory.
|
|
721
|
-
*/
|
|
722
|
-
function releaseLock(metaPath) {
|
|
723
|
-
const lockPath = join(metaPath, LOCK_FILE);
|
|
724
|
-
try {
|
|
725
|
-
unlinkSync(lockPath);
|
|
726
|
-
}
|
|
727
|
-
catch {
|
|
728
|
-
// Already removed or never existed
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* Check if a .meta directory is currently locked (non-stale).
|
|
733
|
-
*
|
|
734
|
-
* @param metaPath - Absolute path to the .meta directory.
|
|
735
|
-
* @returns True if locked and not stale.
|
|
736
|
-
*/
|
|
737
|
-
function isLocked(metaPath) {
|
|
738
|
-
const lockPath = join(metaPath, LOCK_FILE);
|
|
739
|
-
if (!existsSync(lockPath))
|
|
740
|
-
return false;
|
|
741
|
-
try {
|
|
742
|
-
const raw = readFileSync(lockPath, 'utf8');
|
|
743
|
-
const data = JSON.parse(raw);
|
|
744
|
-
const lockAge = Date.now() - new Date(data.startedAt).getTime();
|
|
745
|
-
return lockAge < STALE_TIMEOUT_MS;
|
|
746
|
-
}
|
|
747
|
-
catch {
|
|
748
|
-
return false; // Corrupt lock = not locked
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
884
|
/**
|
|
753
885
|
* Build the SynthContext for a synthesis cycle.
|
|
754
886
|
*
|
|
@@ -1542,16 +1674,31 @@ class HttpWatcherClient {
|
|
|
1542
1674
|
throw new Error('Retry exhausted');
|
|
1543
1675
|
}
|
|
1544
1676
|
async scan(params) {
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1677
|
+
// Build Qdrant filter: merge explicit filter with pathPrefix/modifiedAfter
|
|
1678
|
+
const mustClauses = [];
|
|
1679
|
+
// Carry over any existing 'must' clauses from the provided filter
|
|
1680
|
+
if (params.filter) {
|
|
1681
|
+
const existing = params.filter.must;
|
|
1682
|
+
if (Array.isArray(existing)) {
|
|
1683
|
+
mustClauses.push(...existing);
|
|
1684
|
+
}
|
|
1548
1685
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1686
|
+
// Translate pathPrefix into a Qdrant text match on file_path
|
|
1687
|
+
if (params.pathPrefix !== undefined) {
|
|
1688
|
+
mustClauses.push({
|
|
1689
|
+
key: 'file_path',
|
|
1690
|
+
match: { text: params.pathPrefix },
|
|
1691
|
+
});
|
|
1551
1692
|
}
|
|
1693
|
+
// Translate modifiedAfter into a Qdrant range filter on modified_at
|
|
1552
1694
|
if (params.modifiedAfter !== undefined) {
|
|
1553
|
-
|
|
1695
|
+
mustClauses.push({
|
|
1696
|
+
key: 'modified_at',
|
|
1697
|
+
range: { gt: params.modifiedAfter },
|
|
1698
|
+
});
|
|
1554
1699
|
}
|
|
1700
|
+
const filter = { must: mustClauses };
|
|
1701
|
+
const body = { filter };
|
|
1555
1702
|
if (params.fields !== undefined) {
|
|
1556
1703
|
body.fields = params.fields;
|
|
1557
1704
|
}
|
|
@@ -1584,4 +1731,4 @@ class HttpWatcherClient {
|
|
|
1584
1731
|
}
|
|
1585
1732
|
}
|
|
1586
1733
|
|
|
1587
|
-
export { GatewayExecutor, HttpWatcherClient, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildMetaFilter, buildOwnershipTree, computeEffectiveStaleness, computeEma, computeStructureHash, createSnapshot, discoverMetas, filterInScope, findNode, getScopePrefix, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, loadSynthConfig, mergeAndWrite, metaJsonSchema, normalizePath$1 as normalizePath, orchestrate, paginatedScan, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, releaseLock, resolveConfigPath, selectCandidate, synthConfigSchema, synthErrorSchema, toSynthError };
|
|
1734
|
+
export { GatewayExecutor, HttpWatcherClient, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildMetaFilter, buildOwnershipTree, computeEffectiveStaleness, computeEma, computeStructureHash, createSnapshot, discoverMetas, filterInScope, findNode, getScopePrefix, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, listMetas, loadSynthConfig, mergeAndWrite, metaJsonSchema, normalizePath$1 as normalizePath, orchestrate, paginatedScan, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, releaseLock, resolveConfigPath, selectCandidate, synthConfigSchema, synthErrorSchema, toSynthError };
|