@syndash/research-vault-mcp 1.1.4 → 1.1.6
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/CHANGELOG.md +21 -0
- package/LICENSE +30 -0
- package/README.md +28 -2
- package/dist/server.js +59 -16
- package/package.json +3 -2
- package/src/evidence_metadata.ts +63 -3
- package/src/vault.ts +16 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.1.6 — 2026-06-16
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Aligned npm package license metadata with the monorepo root `LICENSE`.
|
|
10
|
+
- Added `LICENSE` to the published package artifact so npm installs include the governing license text.
|
|
11
|
+
- Updated README package mechanics and license language to match the packaged artifact.
|
|
12
|
+
|
|
13
|
+
## 1.1.5 — 2026-06-08
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `vault_search` now separates read/index/analysis state with `readability_verdict`, `index_verdict`, and `analysis_verdict`.
|
|
18
|
+
- Readable search hits without `lastAnalyzedAt` now return `analysis_verdict: "NOT_ANALYZED"` without turning the overall search guidance into a scary `FLAG`.
|
|
19
|
+
- Exact-id search hits are marked with `matched_fields: ["id_exact"]`.
|
|
20
|
+
- Slash-heavy titles/categories are easier to find through punctuation-normalized matching, such as `Music/Rhythm` via `music rhythm`.
|
|
21
|
+
|
|
22
|
+
### Verified
|
|
23
|
+
|
|
24
|
+
- `bun test packages/research-vault-mcp/__tests__/vault_evidence_metadata.test.ts`
|
|
25
|
+
|
|
5
26
|
## 1.1.4 — 2026-05-10
|
|
6
27
|
|
|
7
28
|
### Changed
|
package/LICENSE
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# CC BY-NC-ND 4.0 — Attribution-NonCommercial-NoDerivatives
|
|
2
|
+
|
|
3
|
+
**Creative Commons License — Non-Commercial Use Only**
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
You MAY:
|
|
8
|
+
- Study this research privately
|
|
9
|
+
- Share it with attribution (keep this license intact)
|
|
10
|
+
- Build on it for personal/research purposes
|
|
11
|
+
|
|
12
|
+
You MAY NOT:
|
|
13
|
+
- **Commercialize** the content or any derivative works
|
|
14
|
+
- Create derivative works (remixes, modifications, extensions)
|
|
15
|
+
- Remove this license from any distribution
|
|
16
|
+
- Claim authorship of original work
|
|
17
|
+
|
|
18
|
+
## Full Legal Notice
|
|
19
|
+
|
|
20
|
+
This work may not be used for commercial purposes. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
21
|
+
|
|
22
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
23
|
+
|
|
24
|
+
2. The name "ProjectAlpha" and the author's name may not be used to endorse or promote products derived from this software without specific prior written permission.
|
|
25
|
+
|
|
26
|
+
3. This software is provided by the copyright holders "as is" and any express or implied warranties are disclaimed, including but not limited to implied warranties of merchantability and fitness for a particular purpose.
|
|
27
|
+
|
|
28
|
+
## Jurisdiction
|
|
29
|
+
|
|
30
|
+
This license is governed by the laws of the jurisdiction in which the original author resides.
|
package/README.md
CHANGED
|
@@ -95,6 +95,31 @@ Mutation tools are hidden and blocked in `readonly`. `MCP_PROFILE=full` enables
|
|
|
95
95
|
|
|
96
96
|
`vault_get` is bounded by default: it returns an excerpt unless the operator approves `include_content:true`, and even full-content requests are capped by `max_chars`. Search, status, and batch responses include `agent_guidance` plus evidence metadata for provenance, freshness, profile, and public-safety state.
|
|
97
97
|
|
|
98
|
+
## Search and read semantics
|
|
99
|
+
|
|
100
|
+
`vault_search` is an index/readability surface. It answers whether the query matched saved knowledge entries and whether those entries are readable. It does not treat missing analysis metadata as missing content.
|
|
101
|
+
|
|
102
|
+
Search matching covers:
|
|
103
|
+
|
|
104
|
+
- exact note IDs, marked with `matched_fields: ["id_exact"]`
|
|
105
|
+
- titles, note content, IDs, and categories
|
|
106
|
+
- punctuation-normalized text, so slash-heavy categories such as `software-engineering/game-design` can be found with `software engineering game design`
|
|
107
|
+
|
|
108
|
+
Search results separate the relevant states:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"readability_verdict": "PASS",
|
|
113
|
+
"index_verdict": "PASS",
|
|
114
|
+
"analysis_verdict": "NOT_ANALYZED",
|
|
115
|
+
"freshness_verdict": "PASS"
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
That means the note matched and is readable, but has no `lastAnalyzedAt` yet. Treat `NOT_ANALYZED` as an analysis caveat, not a broken read. Stale or invalid analysis timestamps still produce a `FLAG`.
|
|
120
|
+
|
|
121
|
+
`vault_get` is the authoritative exact-ID read path for follow-up evidence. Call `vault_search` first, then pass the exact returned `id` to `vault_get`.
|
|
122
|
+
|
|
98
123
|
## Tools exposed
|
|
99
124
|
|
|
100
125
|
Current MCP contract:
|
|
@@ -119,6 +144,7 @@ Published packages include:
|
|
|
119
144
|
- `dist/server.js`
|
|
120
145
|
- `src/**/*.ts` for source inspection
|
|
121
146
|
- `README.md`
|
|
147
|
+
- `LICENSE`
|
|
122
148
|
- `CHANGELOG.md`
|
|
123
149
|
- `package.json`
|
|
124
150
|
|
|
@@ -144,8 +170,8 @@ The package is intentionally Bun-native today because the server uses Bun APIs.
|
|
|
144
170
|
|
|
145
171
|
## License
|
|
146
172
|
|
|
147
|
-
|
|
173
|
+
Released under the same license as the monorepo root: [CC BY-NC-ND 4.0](./LICENSE). You may share unmodified copies with attribution for non-commercial use. For commercial licensing or research collaboration, open an issue in the source repository.
|
|
148
174
|
|
|
149
175
|
## Releases
|
|
150
176
|
|
|
151
|
-
See [CHANGELOG.md](./CHANGELOG.md). Current npm release: `1.1.
|
|
177
|
+
See [CHANGELOG.md](./CHANGELOG.md). Current npm release: `1.1.6`.
|
package/dist/server.js
CHANGED
|
@@ -270,6 +270,9 @@ function clamp(value, min, max) {
|
|
|
270
270
|
function lower(value) {
|
|
271
271
|
return (value ?? "").toLowerCase();
|
|
272
272
|
}
|
|
273
|
+
function normalized(value) {
|
|
274
|
+
return lower(value).replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
275
|
+
}
|
|
273
276
|
function queryTerms(query) {
|
|
274
277
|
return lower(query).split(/\s+/).map((term) => term.trim()).filter(Boolean);
|
|
275
278
|
}
|
|
@@ -278,12 +281,17 @@ function includesQuery(value, query) {
|
|
|
278
281
|
if (terms.length === 0)
|
|
279
282
|
return false;
|
|
280
283
|
const haystack = lower(value);
|
|
281
|
-
|
|
284
|
+
const normalizedHaystack = normalized(value);
|
|
285
|
+
return terms.some((term) => haystack.includes(term)) || normalized(queryTerms(query).join(" ")).split(/\s+/).some((term) => normalizedHaystack.includes(term));
|
|
286
|
+
}
|
|
287
|
+
function equalsQuery(value, query) {
|
|
288
|
+
return Boolean(query?.trim()) && lower(value).trim() === lower(query).trim();
|
|
282
289
|
}
|
|
283
290
|
function matchedFields(entry, query) {
|
|
284
291
|
if (!query?.trim())
|
|
285
292
|
return [];
|
|
286
293
|
const candidates = [
|
|
294
|
+
["id_exact", equalsQuery(entry.id, query) ? entry.id : undefined],
|
|
287
295
|
["title", entry.title],
|
|
288
296
|
["content", entry.content],
|
|
289
297
|
["id", entry.id],
|
|
@@ -297,6 +305,8 @@ function whyMatched(entry, query, fields) {
|
|
|
297
305
|
if (fields.length === 0)
|
|
298
306
|
return "Result is included after filters; no direct field match was detected.";
|
|
299
307
|
const labels = fields.map((field) => {
|
|
308
|
+
if (field === "id_exact")
|
|
309
|
+
return "exact id";
|
|
300
310
|
if (field === "title")
|
|
301
311
|
return "title";
|
|
302
312
|
if (field === "content")
|
|
@@ -311,21 +321,21 @@ function snippetFromContent(content, query, maxChars = 240) {
|
|
|
311
321
|
const limit = Math.max(0, Math.floor(maxChars));
|
|
312
322
|
if (limit === 0)
|
|
313
323
|
return "";
|
|
314
|
-
const
|
|
315
|
-
if (
|
|
316
|
-
return
|
|
324
|
+
const normalized2 = content.replace(/\s+/g, " ").trim();
|
|
325
|
+
if (normalized2.length <= limit)
|
|
326
|
+
return normalized2;
|
|
317
327
|
const terms = queryTerms(query);
|
|
318
|
-
const lowerContent = lower(
|
|
328
|
+
const lowerContent = lower(normalized2);
|
|
319
329
|
const hitIndex = terms.map((term) => lowerContent.indexOf(term)).filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
320
330
|
if (hitIndex === undefined)
|
|
321
|
-
return
|
|
331
|
+
return normalized2.slice(0, limit).trimEnd();
|
|
322
332
|
const halfWindow = Math.floor(limit / 2);
|
|
323
|
-
const start = Math.max(0, Math.min(hitIndex - halfWindow,
|
|
324
|
-
const end = Math.min(
|
|
333
|
+
const start = Math.max(0, Math.min(hitIndex - halfWindow, normalized2.length - limit));
|
|
334
|
+
const end = Math.min(normalized2.length, start + limit);
|
|
325
335
|
const prefix = start > 0 ? "..." : "";
|
|
326
|
-
const suffix = end <
|
|
336
|
+
const suffix = end < normalized2.length ? "..." : "";
|
|
327
337
|
const available = Math.max(0, limit - prefix.length - suffix.length);
|
|
328
|
-
return `${prefix}${
|
|
338
|
+
return `${prefix}${normalized2.slice(start, start + available).trim()}${suffix}`;
|
|
329
339
|
}
|
|
330
340
|
function staleVerdict(lastAnalyzedAt) {
|
|
331
341
|
if (!lastAnalyzedAt) {
|
|
@@ -341,14 +351,46 @@ function staleVerdict(lastAnalyzedAt) {
|
|
|
341
351
|
}
|
|
342
352
|
return { verdict: "PASS", reason: "Analysis is fresh enough for the read surface." };
|
|
343
353
|
}
|
|
354
|
+
function analysisVerdict(lastAnalyzedAt) {
|
|
355
|
+
if (!lastAnalyzedAt) {
|
|
356
|
+
return {
|
|
357
|
+
verdict: "NOT_ANALYZED",
|
|
358
|
+
freshness_verdict: "PASS",
|
|
359
|
+
reason: "No analysis timestamp was provided; content can still be readable."
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const freshness = staleVerdict(lastAnalyzedAt);
|
|
363
|
+
if (freshness.verdict === "PASS") {
|
|
364
|
+
return {
|
|
365
|
+
verdict: "PASS",
|
|
366
|
+
freshness_verdict: "PASS",
|
|
367
|
+
reason: freshness.reason
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (freshness.reason.includes("could not be parsed")) {
|
|
371
|
+
return {
|
|
372
|
+
verdict: "INVALID",
|
|
373
|
+
freshness_verdict: "FLAG",
|
|
374
|
+
reason: freshness.reason
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
verdict: "STALE",
|
|
379
|
+
freshness_verdict: "FLAG",
|
|
380
|
+
reason: freshness.reason
|
|
381
|
+
};
|
|
382
|
+
}
|
|
344
383
|
function itemFreshness(entry) {
|
|
345
384
|
const lastAnalyzedAt = entry.score?.lastAnalyzedAt ?? null;
|
|
346
|
-
const
|
|
385
|
+
const analysis = analysisVerdict(lastAnalyzedAt);
|
|
347
386
|
return {
|
|
348
387
|
last_analyzed_at: lastAnalyzedAt,
|
|
349
388
|
source_mtime: entry.modified || null,
|
|
350
|
-
|
|
351
|
-
|
|
389
|
+
readability_verdict: "PASS",
|
|
390
|
+
index_verdict: "PASS",
|
|
391
|
+
analysis_verdict: analysis.verdict,
|
|
392
|
+
freshness_verdict: analysis.freshness_verdict,
|
|
393
|
+
freshness_reason: analysis.reason
|
|
352
394
|
};
|
|
353
395
|
}
|
|
354
396
|
function queueFreshness(queueItems) {
|
|
@@ -672,9 +714,10 @@ var vaultTools = [
|
|
|
672
714
|
...freshness
|
|
673
715
|
};
|
|
674
716
|
});
|
|
675
|
-
const
|
|
676
|
-
const
|
|
677
|
-
|
|
717
|
+
const hasAnalysisRisk = results.some((result) => ["STALE", "INVALID"].includes(result.analysis_verdict));
|
|
718
|
+
const hasNotAnalyzed = results.some((result) => result.analysis_verdict === "NOT_ANALYZED");
|
|
719
|
+
const envelope = okEnvelope({ query, category, results, total: results.length }, hasAnalysisRisk ? flagGuidance("Search completed, but one or more results have stale or invalid analysis metadata.", "Use source_ref for readonly follow-up and refresh stale analysis metadata in the operator lane if needed.", "vault_get") : passGuidance(hasNotAnalyzed ? "Search completed; one or more readable results have not been analyzed yet." : "Search completed with provenance and freshness metadata.", hasNotAnalyzed ? "Use source_ref or vault_get for bounded follow-up evidence; treat NOT_ANALYZED as an analysis caveat, not missing content." : "Use source_ref or vault_get for bounded follow-up evidence.", "vault_get"), {
|
|
720
|
+
freshness: hasAnalysisRisk ? "Some search results have stale or invalid analysis timestamps." : hasNotAnalyzed ? "Some readable search results do not have analysis timestamps yet." : "Search result analysis metadata is fresh.",
|
|
678
721
|
provenance: "vault_search result source_ref values are vault:// references without local paths."
|
|
679
722
|
});
|
|
680
723
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syndash/research-vault-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "DASH Research Vault MCP server — local-first semantic search, note persistence, and public-safe knowledge-base tools for MCP-compatible agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"bugs": {
|
|
22
22
|
"url": "https://github.com/Fearvox/dash-research-vault/issues"
|
|
23
23
|
},
|
|
24
|
-
"license": "
|
|
24
|
+
"license": "CC-BY-NC-ND-4.0",
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"dist/**/*.js",
|
|
31
31
|
"bin/**/*.mjs",
|
|
32
32
|
"README.md",
|
|
33
|
+
"LICENSE",
|
|
33
34
|
"CHANGELOG.md",
|
|
34
35
|
"package.json"
|
|
35
36
|
],
|
package/src/evidence_metadata.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import type { DecayScore, VaultEntry } from './types.ts'
|
|
2
2
|
|
|
3
3
|
export type FreshnessVerdict = 'PASS' | 'FLAG'
|
|
4
|
+
export type ReadabilityVerdict = 'PASS' | 'MISSING'
|
|
5
|
+
export type IndexVerdict = 'PASS' | 'NO_MATCH'
|
|
6
|
+
export type AnalysisVerdict = 'PASS' | 'STALE' | 'NOT_ANALYZED' | 'INVALID'
|
|
4
7
|
|
|
5
8
|
export interface FreshnessShape {
|
|
6
9
|
verdict: FreshnessVerdict
|
|
7
10
|
reason: string
|
|
8
11
|
}
|
|
9
12
|
|
|
13
|
+
export interface AnalysisShape {
|
|
14
|
+
verdict: AnalysisVerdict
|
|
15
|
+
freshness_verdict: FreshnessVerdict
|
|
16
|
+
reason: string
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
const STALE_AFTER_DAYS = 7
|
|
11
20
|
const DAY_MS = 24 * 60 * 60 * 1000
|
|
12
21
|
|
|
@@ -18,6 +27,13 @@ function lower(value: string | undefined | null): string {
|
|
|
18
27
|
return (value ?? '').toLowerCase()
|
|
19
28
|
}
|
|
20
29
|
|
|
30
|
+
function normalized(value: string | undefined | null): string {
|
|
31
|
+
return lower(value)
|
|
32
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
33
|
+
.replace(/\s+/g, ' ')
|
|
34
|
+
.trim()
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
function queryTerms(query?: string): string[] {
|
|
22
38
|
return lower(query)
|
|
23
39
|
.split(/\s+/)
|
|
@@ -29,13 +45,20 @@ function includesQuery(value: string | undefined, query?: string): boolean {
|
|
|
29
45
|
const terms = queryTerms(query)
|
|
30
46
|
if (terms.length === 0) return false
|
|
31
47
|
const haystack = lower(value)
|
|
48
|
+
const normalizedHaystack = normalized(value)
|
|
32
49
|
return terms.some(term => haystack.includes(term))
|
|
50
|
+
|| normalized(queryTerms(query).join(' ')).split(/\s+/).some(term => normalizedHaystack.includes(term))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function equalsQuery(value: string | undefined, query?: string): boolean {
|
|
54
|
+
return Boolean(query?.trim()) && lower(value).trim() === lower(query).trim()
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
export function matchedFields(entry: VaultEntry & { content?: string }, query?: string): string[] {
|
|
36
58
|
if (!query?.trim()) return []
|
|
37
59
|
|
|
38
60
|
const candidates: Array<[string, string | undefined]> = [
|
|
61
|
+
['id_exact', equalsQuery(entry.id, query) ? entry.id : undefined],
|
|
39
62
|
['title', entry.title],
|
|
40
63
|
['content', entry.content],
|
|
41
64
|
['id', entry.id],
|
|
@@ -52,6 +75,7 @@ export function whyMatched(entry: VaultEntry & { content?: string }, query: stri
|
|
|
52
75
|
if (fields.length === 0) return 'Result is included after filters; no direct field match was detected.'
|
|
53
76
|
|
|
54
77
|
const labels = fields.map(field => {
|
|
78
|
+
if (field === 'id_exact') return 'exact id'
|
|
55
79
|
if (field === 'title') return 'title'
|
|
56
80
|
if (field === 'content') return 'note content'
|
|
57
81
|
if (field === 'category') return 'category'
|
|
@@ -104,15 +128,51 @@ export function staleVerdict(lastAnalyzedAt?: string | null): FreshnessShape {
|
|
|
104
128
|
return { verdict: 'PASS', reason: 'Analysis is fresh enough for the read surface.' }
|
|
105
129
|
}
|
|
106
130
|
|
|
131
|
+
export function analysisVerdict(lastAnalyzedAt?: string | null): AnalysisShape {
|
|
132
|
+
if (!lastAnalyzedAt) {
|
|
133
|
+
return {
|
|
134
|
+
verdict: 'NOT_ANALYZED',
|
|
135
|
+
freshness_verdict: 'PASS',
|
|
136
|
+
reason: 'No analysis timestamp was provided; content can still be readable.',
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const freshness = staleVerdict(lastAnalyzedAt)
|
|
141
|
+
if (freshness.verdict === 'PASS') {
|
|
142
|
+
return {
|
|
143
|
+
verdict: 'PASS',
|
|
144
|
+
freshness_verdict: 'PASS',
|
|
145
|
+
reason: freshness.reason,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (freshness.reason.includes('could not be parsed')) {
|
|
150
|
+
return {
|
|
151
|
+
verdict: 'INVALID',
|
|
152
|
+
freshness_verdict: 'FLAG',
|
|
153
|
+
reason: freshness.reason,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
verdict: 'STALE',
|
|
159
|
+
freshness_verdict: 'FLAG',
|
|
160
|
+
reason: freshness.reason,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
107
164
|
export function itemFreshness(entry: VaultEntry & { score?: DecayScore & { lastAnalyzedAt?: string } }) {
|
|
108
165
|
const lastAnalyzedAt = entry.score?.lastAnalyzedAt ?? null
|
|
109
|
-
const
|
|
166
|
+
const analysis = analysisVerdict(lastAnalyzedAt)
|
|
110
167
|
|
|
111
168
|
return {
|
|
112
169
|
last_analyzed_at: lastAnalyzedAt,
|
|
113
170
|
source_mtime: entry.modified || null,
|
|
114
|
-
|
|
115
|
-
|
|
171
|
+
readability_verdict: 'PASS' as ReadabilityVerdict,
|
|
172
|
+
index_verdict: 'PASS' as IndexVerdict,
|
|
173
|
+
analysis_verdict: analysis.verdict,
|
|
174
|
+
freshness_verdict: analysis.freshness_verdict,
|
|
175
|
+
freshness_reason: analysis.reason,
|
|
116
176
|
}
|
|
117
177
|
}
|
|
118
178
|
|
package/src/vault.ts
CHANGED
|
@@ -330,22 +330,31 @@ const vaultTools = [
|
|
|
330
330
|
}
|
|
331
331
|
})
|
|
332
332
|
|
|
333
|
-
const
|
|
333
|
+
const hasAnalysisRisk = results.some(result => ['STALE', 'INVALID'].includes(result.analysis_verdict))
|
|
334
|
+
const hasNotAnalyzed = results.some(result => result.analysis_verdict === 'NOT_ANALYZED')
|
|
334
335
|
const envelope = okEnvelope(
|
|
335
336
|
{ query, category, results, total: results.length },
|
|
336
|
-
|
|
337
|
+
hasAnalysisRisk
|
|
337
338
|
? flagGuidance(
|
|
338
|
-
'Search completed, but one or more results
|
|
339
|
-
'Use source_ref for readonly follow-up and refresh analysis metadata in the operator lane if needed.',
|
|
339
|
+
'Search completed, but one or more results have stale or invalid analysis metadata.',
|
|
340
|
+
'Use source_ref for readonly follow-up and refresh stale analysis metadata in the operator lane if needed.',
|
|
340
341
|
'vault_get',
|
|
341
342
|
)
|
|
342
343
|
: passGuidance(
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
hasNotAnalyzed
|
|
345
|
+
? 'Search completed; one or more readable results have not been analyzed yet.'
|
|
346
|
+
: 'Search completed with provenance and freshness metadata.',
|
|
347
|
+
hasNotAnalyzed
|
|
348
|
+
? 'Use source_ref or vault_get for bounded follow-up evidence; treat NOT_ANALYZED as an analysis caveat, not missing content.'
|
|
349
|
+
: 'Use source_ref or vault_get for bounded follow-up evidence.',
|
|
345
350
|
'vault_get',
|
|
346
351
|
),
|
|
347
352
|
{
|
|
348
|
-
freshness:
|
|
353
|
+
freshness: hasAnalysisRisk
|
|
354
|
+
? 'Some search results have stale or invalid analysis timestamps.'
|
|
355
|
+
: hasNotAnalyzed
|
|
356
|
+
? 'Some readable search results do not have analysis timestamps yet.'
|
|
357
|
+
: 'Search result analysis metadata is fresh.',
|
|
349
358
|
provenance: 'vault_search result source_ref values are vault:// references without local paths.',
|
|
350
359
|
},
|
|
351
360
|
)
|