@karmaniverous/jeeves-meta 0.11.0 → 0.11.2
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/jeeves-meta/index.js +42 -3
- package/dist/index.js +42 -3
- package/package.json +1 -1
|
@@ -834,6 +834,32 @@ async function listMetas(config, watcher) {
|
|
|
834
834
|
};
|
|
835
835
|
}
|
|
836
836
|
|
|
837
|
+
/**
|
|
838
|
+
* Escape special glob characters in a path so it can be used as a literal
|
|
839
|
+
* prefix in glob patterns.
|
|
840
|
+
*
|
|
841
|
+
* Glob metacharacters `* ? [ ] { } ( ) !` are escaped with a backslash so
|
|
842
|
+
* that paths containing parentheses (e.g. Slack channel IDs) or other
|
|
843
|
+
* special characters are matched literally by the watcher's walk endpoint.
|
|
844
|
+
*
|
|
845
|
+
* @module escapeGlob
|
|
846
|
+
*/
|
|
847
|
+
/**
|
|
848
|
+
* Escape glob metacharacters in a string using character-class wrapping.
|
|
849
|
+
*
|
|
850
|
+
* Backslash escaping (`\(`) does not work reliably on Windows where `\` is
|
|
851
|
+
* the path separator. Instead, each metacharacter is wrapped in a character
|
|
852
|
+
* class (e.g. `(` → `[(]`) which is universally supported by glob libraries.
|
|
853
|
+
*
|
|
854
|
+
* Square brackets themselves are escaped as `[[]` and `[]]`.
|
|
855
|
+
*
|
|
856
|
+
* @param s - Raw path string.
|
|
857
|
+
* @returns String with glob metacharacters wrapped in character classes.
|
|
858
|
+
*/
|
|
859
|
+
function escapeGlob(s) {
|
|
860
|
+
return s.replace(/[*?[\]{}()!]/g, (ch) => `[${ch}]`);
|
|
861
|
+
}
|
|
862
|
+
|
|
837
863
|
/**
|
|
838
864
|
* Filter file paths by modification time.
|
|
839
865
|
*
|
|
@@ -934,7 +960,7 @@ function filterInScope(node, files) {
|
|
|
934
960
|
*/
|
|
935
961
|
async function getScopeFiles(node, watcher, logger) {
|
|
936
962
|
const walkStart = Date.now();
|
|
937
|
-
const rawFiles = await watcher.walk([`${node.ownerPath}/**`]);
|
|
963
|
+
const rawFiles = await watcher.walk([`${escapeGlob(node.ownerPath)}/**`]);
|
|
938
964
|
const allFiles = rawFiles.map(normalizePath);
|
|
939
965
|
const scopeFiles = filterInScope(node, allFiles);
|
|
940
966
|
logger?.debug({
|
|
@@ -1746,7 +1772,7 @@ async function buildMinimalNode(metaPath, watcher) {
|
|
|
1746
1772
|
// We include only *direct* children (nearest descendants in the ownership tree)
|
|
1747
1773
|
// to match the ownership semantics used elsewhere.
|
|
1748
1774
|
const rawMetaJsonPaths = await watcher.walk([
|
|
1749
|
-
`${ownerPath}/**/.meta/meta.json`,
|
|
1775
|
+
`${escapeGlob(ownerPath)}/**/.meta/meta.json`,
|
|
1750
1776
|
]);
|
|
1751
1777
|
const candidateMetaPaths = [
|
|
1752
1778
|
...new Set(rawMetaJsonPaths.map((p) => normalizePath(dirname(p)))),
|
|
@@ -1882,7 +1908,7 @@ function discoverStalestPath(candidates, depthWeight) {
|
|
|
1882
1908
|
async function isStale(scopePrefix, meta, watcher) {
|
|
1883
1909
|
if (!meta._generatedAt)
|
|
1884
1910
|
return true; // Never synthesized = stale
|
|
1885
|
-
const files = await watcher.walk([`${scopePrefix}/**`]);
|
|
1911
|
+
const files = await watcher.walk([`${escapeGlob(scopePrefix)}/**`]);
|
|
1886
1912
|
return hasModifiedAfter(files, new Date(meta._generatedAt).getTime());
|
|
1887
1913
|
}
|
|
1888
1914
|
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
@@ -2159,6 +2185,14 @@ async function synthesizeNode(node, currentMeta, config, executor, watcher, onPr
|
|
|
2159
2185
|
Object.keys(ctx.childMetas).length > 0 ||
|
|
2160
2186
|
Object.keys(ctx.crossRefMetas).length > 0;
|
|
2161
2187
|
if (!hasScope && !currentMeta._content) {
|
|
2188
|
+
// Bump _generatedAt so this entity doesn't keep winning the staleness
|
|
2189
|
+
// race every cycle. It will be re-evaluated when files appear.
|
|
2190
|
+
// Uses lock-staging for atomic write consistency.
|
|
2191
|
+
currentMeta._generatedAt = new Date().toISOString();
|
|
2192
|
+
const lockPath = join(node.metaPath, '.lock');
|
|
2193
|
+
const metaJsonPath = join(node.metaPath, 'meta.json');
|
|
2194
|
+
await writeFile(lockPath, JSON.stringify(currentMeta, null, 2));
|
|
2195
|
+
await copyFile(lockPath, metaJsonPath);
|
|
2162
2196
|
logger?.debug({ path: node.ownerPath }, 'Skipping empty-scope entity');
|
|
2163
2197
|
return { synthesized: false };
|
|
2164
2198
|
}
|
|
@@ -11111,6 +11145,11 @@ async function startService(config, configPath) {
|
|
|
11111
11145
|
// orchestrate() always returns exactly one result
|
|
11112
11146
|
const result = results[0];
|
|
11113
11147
|
const durationMs = Date.now() - startMs;
|
|
11148
|
+
if (!result.synthesized) {
|
|
11149
|
+
// Entity was skipped (e.g. empty scope) — no progress to report.
|
|
11150
|
+
logger.debug({ path: ownerPath }, 'Synthesis skipped');
|
|
11151
|
+
return;
|
|
11152
|
+
}
|
|
11114
11153
|
// Update stats
|
|
11115
11154
|
stats.totalSyntheses++;
|
|
11116
11155
|
stats.lastCycleDurationMs = durationMs;
|
package/dist/index.js
CHANGED
|
@@ -826,6 +826,32 @@ async function listMetas(config, watcher) {
|
|
|
826
826
|
};
|
|
827
827
|
}
|
|
828
828
|
|
|
829
|
+
/**
|
|
830
|
+
* Escape special glob characters in a path so it can be used as a literal
|
|
831
|
+
* prefix in glob patterns.
|
|
832
|
+
*
|
|
833
|
+
* Glob metacharacters `* ? [ ] { } ( ) !` are escaped with a backslash so
|
|
834
|
+
* that paths containing parentheses (e.g. Slack channel IDs) or other
|
|
835
|
+
* special characters are matched literally by the watcher's walk endpoint.
|
|
836
|
+
*
|
|
837
|
+
* @module escapeGlob
|
|
838
|
+
*/
|
|
839
|
+
/**
|
|
840
|
+
* Escape glob metacharacters in a string using character-class wrapping.
|
|
841
|
+
*
|
|
842
|
+
* Backslash escaping (`\(`) does not work reliably on Windows where `\` is
|
|
843
|
+
* the path separator. Instead, each metacharacter is wrapped in a character
|
|
844
|
+
* class (e.g. `(` → `[(]`) which is universally supported by glob libraries.
|
|
845
|
+
*
|
|
846
|
+
* Square brackets themselves are escaped as `[[]` and `[]]`.
|
|
847
|
+
*
|
|
848
|
+
* @param s - Raw path string.
|
|
849
|
+
* @returns String with glob metacharacters wrapped in character classes.
|
|
850
|
+
*/
|
|
851
|
+
function escapeGlob(s) {
|
|
852
|
+
return s.replace(/[*?[\]{}()!]/g, (ch) => `[${ch}]`);
|
|
853
|
+
}
|
|
854
|
+
|
|
829
855
|
/**
|
|
830
856
|
* Filter file paths by modification time.
|
|
831
857
|
*
|
|
@@ -926,7 +952,7 @@ function filterInScope(node, files) {
|
|
|
926
952
|
*/
|
|
927
953
|
async function getScopeFiles(node, watcher, logger) {
|
|
928
954
|
const walkStart = Date.now();
|
|
929
|
-
const rawFiles = await watcher.walk([`${node.ownerPath}/**`]);
|
|
955
|
+
const rawFiles = await watcher.walk([`${escapeGlob(node.ownerPath)}/**`]);
|
|
930
956
|
const allFiles = rawFiles.map(normalizePath);
|
|
931
957
|
const scopeFiles = filterInScope(node, allFiles);
|
|
932
958
|
logger?.debug({
|
|
@@ -1738,7 +1764,7 @@ async function buildMinimalNode(metaPath, watcher) {
|
|
|
1738
1764
|
// We include only *direct* children (nearest descendants in the ownership tree)
|
|
1739
1765
|
// to match the ownership semantics used elsewhere.
|
|
1740
1766
|
const rawMetaJsonPaths = await watcher.walk([
|
|
1741
|
-
`${ownerPath}/**/.meta/meta.json`,
|
|
1767
|
+
`${escapeGlob(ownerPath)}/**/.meta/meta.json`,
|
|
1742
1768
|
]);
|
|
1743
1769
|
const candidateMetaPaths = [
|
|
1744
1770
|
...new Set(rawMetaJsonPaths.map((p) => normalizePath(dirname(p)))),
|
|
@@ -1874,7 +1900,7 @@ function discoverStalestPath(candidates, depthWeight) {
|
|
|
1874
1900
|
async function isStale(scopePrefix, meta, watcher) {
|
|
1875
1901
|
if (!meta._generatedAt)
|
|
1876
1902
|
return true; // Never synthesized = stale
|
|
1877
|
-
const files = await watcher.walk([`${scopePrefix}/**`]);
|
|
1903
|
+
const files = await watcher.walk([`${escapeGlob(scopePrefix)}/**`]);
|
|
1878
1904
|
return hasModifiedAfter(files, new Date(meta._generatedAt).getTime());
|
|
1879
1905
|
}
|
|
1880
1906
|
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
@@ -2151,6 +2177,14 @@ async function synthesizeNode(node, currentMeta, config, executor, watcher, onPr
|
|
|
2151
2177
|
Object.keys(ctx.childMetas).length > 0 ||
|
|
2152
2178
|
Object.keys(ctx.crossRefMetas).length > 0;
|
|
2153
2179
|
if (!hasScope && !currentMeta._content) {
|
|
2180
|
+
// Bump _generatedAt so this entity doesn't keep winning the staleness
|
|
2181
|
+
// race every cycle. It will be re-evaluated when files appear.
|
|
2182
|
+
// Uses lock-staging for atomic write consistency.
|
|
2183
|
+
currentMeta._generatedAt = new Date().toISOString();
|
|
2184
|
+
const lockPath = join(node.metaPath, '.lock');
|
|
2185
|
+
const metaJsonPath = join(node.metaPath, 'meta.json');
|
|
2186
|
+
await writeFile(lockPath, JSON.stringify(currentMeta, null, 2));
|
|
2187
|
+
await copyFile(lockPath, metaJsonPath);
|
|
2154
2188
|
logger?.debug({ path: node.ownerPath }, 'Skipping empty-scope entity');
|
|
2155
2189
|
return { synthesized: false };
|
|
2156
2190
|
}
|
|
@@ -11103,6 +11137,11 @@ async function startService(config, configPath) {
|
|
|
11103
11137
|
// orchestrate() always returns exactly one result
|
|
11104
11138
|
const result = results[0];
|
|
11105
11139
|
const durationMs = Date.now() - startMs;
|
|
11140
|
+
if (!result.synthesized) {
|
|
11141
|
+
// Entity was skipped (e.g. empty scope) — no progress to report.
|
|
11142
|
+
logger.debug({ path: ownerPath }, 'Synthesis skipped');
|
|
11143
|
+
return;
|
|
11144
|
+
}
|
|
11106
11145
|
// Update stats
|
|
11107
11146
|
stats.totalSyntheses++;
|
|
11108
11147
|
stats.lastCycleDurationMs = durationMs;
|