@gmickel/gno 1.25.1 → 1.27.0
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 +12 -5
- package/assets/skill/SKILL.md +37 -17
- package/browser-extension/artifacts/{gno-browser-clipper-v1.25.1.zip → gno-browser-clipper-v1.27.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.27.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +161 -6
- package/spec/db/schema.sql +1 -1
- package/spec/evals-agentic.md +17 -0
- package/spec/mcp.md +25 -6
- package/spec/output-schemas/ask.schema.json +3 -0
- package/spec/output-schemas/project-profile-apply.schema.json +209 -0
- package/spec/output-schemas/project-profile-command.schema.json +160 -0
- package/spec/output-schemas/query-diagnose.schema.json +68 -5
- package/spec/output-schemas/search-results.schema.json +87 -1
- package/spec/output-schemas/setup-profile-result.schema.json +87 -0
- package/spec/output-schemas/status.schema.json +24 -0
- package/spec/project-profile.schema.json +303 -0
- package/src/app/context-runtime-contract.ts +4 -1
- package/src/app/context-runtime-types.ts +2 -0
- package/src/app/context-runtime.ts +26 -0
- package/src/app/verified-ask.ts +6 -1
- package/src/cli/commands/ask.ts +8 -1
- package/src/cli/commands/collection/add.ts +39 -45
- package/src/cli/commands/collection/remove.ts +28 -28
- package/src/cli/commands/collection/rename.ts +55 -73
- package/src/cli/commands/context/add.ts +37 -26
- package/src/cli/commands/context/rm.ts +47 -20
- package/src/cli/commands/init.ts +55 -125
- package/src/cli/commands/models/use.ts +43 -38
- package/src/cli/commands/profile-apply.ts +334 -0
- package/src/cli/commands/profile.ts +409 -0
- package/src/cli/commands/query.ts +6 -3
- package/src/cli/commands/search.ts +6 -1
- package/src/cli/commands/setup-activation.ts +205 -54
- package/src/cli/commands/setup-profile.ts +223 -0
- package/src/cli/commands/setup.ts +3 -0
- package/src/cli/commands/status.ts +43 -7
- package/src/cli/program.ts +112 -9
- package/src/config/content-types.ts +82 -0
- package/src/config/index.ts +11 -0
- package/src/config/project-profile.ts +374 -0
- package/src/config/saver.ts +16 -7
- package/src/config/types.ts +53 -2
- package/src/core/config-mutation.ts +138 -76
- package/src/core/config-write-lock.ts +89 -0
- package/src/core/context-compiler.ts +38 -1
- package/src/core/context-identity.ts +16 -0
- package/src/core/context-resolver.ts +2 -12
- package/src/core/folder-setup-planning.ts +6 -21
- package/src/core/folder-setup.ts +30 -2
- package/src/core/path-rules.ts +53 -0
- package/src/core/project-affinity-surface.ts +102 -7
- package/src/core/project-profile-apply-state.ts +268 -0
- package/src/core/project-profile-apply-validation.ts +95 -0
- package/src/core/project-profile-apply.ts +408 -0
- package/src/core/project-profile-canonical.ts +71 -0
- package/src/core/project-profile-diff.ts +302 -0
- package/src/core/project-profile-discovery.ts +519 -0
- package/src/core/project-profile-file.ts +37 -0
- package/src/core/project-profile-parser.ts +98 -0
- package/src/core/project-profile.ts +490 -0
- package/src/core/retrieval-replay-candidate.ts +6 -1
- package/src/ingestion/sync-options.ts +6 -2
- package/src/ingestion/sync.ts +21 -29
- package/src/ingestion/types.ts +1 -1
- package/src/ingestion/walker.ts +85 -44
- package/src/llm/cache.ts +21 -0
- package/src/mcp/tools/ask.ts +1 -0
- package/src/mcp/tools/index.ts +4 -0
- package/src/mcp/tools/query.ts +4 -2
- package/src/mcp/tools/search.ts +3 -0
- package/src/mcp/tools/status.ts +4 -0
- package/src/pipeline/content-type-boost.ts +264 -0
- package/src/pipeline/diagnose.ts +46 -19
- package/src/pipeline/explain.ts +15 -2
- package/src/pipeline/hybrid.ts +170 -74
- package/src/pipeline/rerank.ts +45 -15
- package/src/pipeline/search.ts +29 -11
- package/src/pipeline/types.ts +13 -4
- package/src/pipeline/vsearch.ts +30 -10
- package/src/sdk/client.ts +19 -3
- package/src/sdk/index.ts +1 -0
- package/src/sdk/types.ts +21 -5
- package/src/serve/config-sync.ts +2 -2
- package/src/serve/resident-runtime.ts +1 -0
- package/src/serve/routes/api.ts +18 -3
- package/src/serve/status-model.ts +2 -0
- package/src/serve/status.ts +4 -0
- package/src/store/migrations/021-multi-context-identity.ts +37 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +86 -0
- package/src/store/types.ts +3 -2
- package/browser-extension/artifacts/gno-browser-clipper-v1.25.1.zip.sha256 +0 -1
package/src/pipeline/types.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module src/pipeline/types
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { NormalizedContentTypeRule } from "../config/content-types";
|
|
8
9
|
import type {
|
|
9
10
|
ContextCapsuleV1,
|
|
10
11
|
ContextCapsuleVerification,
|
|
@@ -13,6 +14,7 @@ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
|
|
|
13
14
|
import type { StoreResult } from "../store/types";
|
|
14
15
|
import type { ClaimVerificationResult } from "./claim-verification";
|
|
15
16
|
import type { SemanticVerificationCapability } from "./claim-verifier";
|
|
17
|
+
import type { ContentTypeBoostScoreMetadata } from "./content-type-boost";
|
|
16
18
|
import type {
|
|
17
19
|
ProjectAffinityScoreMetadata,
|
|
18
20
|
ProjectAffinityScoringInput,
|
|
@@ -132,10 +134,7 @@ export interface SearchMeta {
|
|
|
132
134
|
/** Explicit exclusion terms applied */
|
|
133
135
|
exclude?: string[];
|
|
134
136
|
/** Explain data (when --explain is used) */
|
|
135
|
-
explain?:
|
|
136
|
-
lines: ExplainLine[];
|
|
137
|
-
results: ExplainResult[];
|
|
138
|
-
};
|
|
137
|
+
explain?: SearchExplain;
|
|
139
138
|
/** Internal diagnose trace, only populated when diagnoseTrace is enabled */
|
|
140
139
|
trace?: QueryDiagnoseTrace;
|
|
141
140
|
}
|
|
@@ -172,6 +171,8 @@ export interface SearchOptions {
|
|
|
172
171
|
traceSession?: RetrievalTraceSession;
|
|
173
172
|
/** Trusted, already-resolved project affinity; never accepts raw roots. */
|
|
174
173
|
projectAffinity?: ProjectAffinityScoringInput;
|
|
174
|
+
/** Internal normalized rules used by bounded content-type scoring. */
|
|
175
|
+
contentTypeRules?: NormalizedContentTypeRule[];
|
|
175
176
|
/** Max results */
|
|
176
177
|
limit?: number;
|
|
177
178
|
/** Min score threshold (0-1) */
|
|
@@ -459,6 +460,8 @@ export interface AskMeta {
|
|
|
459
460
|
answerGenerated?: boolean;
|
|
460
461
|
totalResults?: number;
|
|
461
462
|
answerContext?: AnswerContextExplain;
|
|
463
|
+
/** Optional retrieval scoring explanation; absent from normal output. */
|
|
464
|
+
explain?: SearchExplain;
|
|
462
465
|
verificationRequested?: boolean;
|
|
463
466
|
abstained?: boolean;
|
|
464
467
|
}
|
|
@@ -545,4 +548,10 @@ export interface ExplainResult {
|
|
|
545
548
|
vecScore?: number;
|
|
546
549
|
rerankScore?: number;
|
|
547
550
|
projectAffinity?: ProjectAffinityScoreMetadata;
|
|
551
|
+
contentTypeBoost?: ContentTypeBoostScoreMetadata;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export interface SearchExplain {
|
|
555
|
+
lines: ExplainLine[];
|
|
556
|
+
results: ExplainResult[];
|
|
548
557
|
}
|
package/src/pipeline/vsearch.ts
CHANGED
|
@@ -11,13 +11,19 @@ import type { StorePort } from "../store/types";
|
|
|
11
11
|
import type { VectorIndexPort } from "../store/vector/types";
|
|
12
12
|
import type { SearchOptions, SearchResult, SearchResults } from "./types";
|
|
13
13
|
|
|
14
|
+
import { normalizeContentTypes } from "../config/content-types";
|
|
14
15
|
import { getContentBatch } from "../store/content-batch";
|
|
15
16
|
import { err, ok } from "../store/types";
|
|
16
17
|
import { createChunkLookup } from "./chunk-lookup";
|
|
18
|
+
import {
|
|
19
|
+
applyContentTypeBoost,
|
|
20
|
+
hasAuxiliaryRanking,
|
|
21
|
+
sortByFinalScoreStable,
|
|
22
|
+
} from "./content-type-boost";
|
|
17
23
|
import { formatQueryForEmbedding } from "./contextual";
|
|
18
24
|
import { matchesExcludedChunks, matchesExcludedText } from "./exclude";
|
|
19
25
|
import { selectBestChunkForSteering } from "./intent";
|
|
20
|
-
import {
|
|
26
|
+
import { hasProjectAffinity } from "./project-affinity";
|
|
21
27
|
import { detectQueryLanguage } from "./query-language";
|
|
22
28
|
import { attachSearchResultContexts } from "./result-context";
|
|
23
29
|
import {
|
|
@@ -83,9 +89,17 @@ export async function searchVectorWithEmbedding(
|
|
|
83
89
|
const { store, vectorIndex } = deps;
|
|
84
90
|
const limit = options.limit ?? 20;
|
|
85
91
|
const minScore = options.minScore ?? 0;
|
|
86
|
-
const
|
|
92
|
+
const contentTypeRules =
|
|
93
|
+
options.contentTypeRules ??
|
|
94
|
+
normalizeContentTypes(deps.config.contentTypes ?? []).rules;
|
|
95
|
+
const auxiliaryRankingActive = hasAuxiliaryRanking(
|
|
96
|
+
options.projectAffinity,
|
|
97
|
+
contentTypeRules
|
|
98
|
+
);
|
|
99
|
+
const projectAffinityActive = hasProjectAffinity(options.projectAffinity);
|
|
87
100
|
const recencySort = shouldSortByRecency(query);
|
|
88
|
-
const retrievalLimit =
|
|
101
|
+
const retrievalLimit =
|
|
102
|
+
recencySort || projectAffinityActive ? limit * 3 : limit;
|
|
89
103
|
const temporalRange = resolveTemporalRange(
|
|
90
104
|
query,
|
|
91
105
|
options.since,
|
|
@@ -106,7 +120,7 @@ export async function searchVectorWithEmbedding(
|
|
|
106
120
|
queryEmbedding,
|
|
107
121
|
retrievalLimit,
|
|
108
122
|
{
|
|
109
|
-
minScore:
|
|
123
|
+
minScore: projectAffinityActive ? undefined : minScore,
|
|
110
124
|
allowedMirrorHashes: options.retrievalScope?.allowedMirrorHashes,
|
|
111
125
|
}
|
|
112
126
|
);
|
|
@@ -165,7 +179,7 @@ export async function searchVectorWithEmbedding(
|
|
|
165
179
|
|
|
166
180
|
for (const vec of vecResults) {
|
|
167
181
|
const baseScore = normalizeVectorScore(vec.distance);
|
|
168
|
-
if (!
|
|
182
|
+
if (!projectAffinityActive && baseScore < minScore) {
|
|
169
183
|
continue;
|
|
170
184
|
}
|
|
171
185
|
|
|
@@ -196,7 +210,7 @@ export async function searchVectorWithEmbedding(
|
|
|
196
210
|
if (!matchingDocs || matchingDocs.length === 0) {
|
|
197
211
|
continue;
|
|
198
212
|
}
|
|
199
|
-
const docs =
|
|
213
|
+
const docs = auxiliaryRankingActive ? matchingDocs : [matchingDocs.at(-1)!];
|
|
200
214
|
for (const doc of docs) {
|
|
201
215
|
const collectionPath = collectionPaths.get(doc.collection);
|
|
202
216
|
const excluded =
|
|
@@ -218,7 +232,7 @@ export async function searchVectorWithEmbedding(
|
|
|
218
232
|
continue;
|
|
219
233
|
}
|
|
220
234
|
|
|
221
|
-
const scoredResult =
|
|
235
|
+
const scoredResult = applyContentTypeBoost(
|
|
222
236
|
{
|
|
223
237
|
docid: doc.docid,
|
|
224
238
|
score: baseScore,
|
|
@@ -254,7 +268,9 @@ export async function searchVectorWithEmbedding(
|
|
|
254
268
|
: undefined,
|
|
255
269
|
},
|
|
256
270
|
doc.collection,
|
|
271
|
+
contentTypeRules,
|
|
257
272
|
options.projectAffinity,
|
|
273
|
+
doc.contentTypeSource,
|
|
258
274
|
{ kind: "vector_distance", score: vec.distance }
|
|
259
275
|
);
|
|
260
276
|
if (scoredResult.score < minScore) continue;
|
|
@@ -317,7 +333,7 @@ export async function searchVectorWithEmbedding(
|
|
|
317
333
|
|
|
318
334
|
const collectionPath = collectionPaths.get(doc.collection);
|
|
319
335
|
|
|
320
|
-
const result =
|
|
336
|
+
const result = applyContentTypeBoost(
|
|
321
337
|
{
|
|
322
338
|
docid: doc.docid,
|
|
323
339
|
score,
|
|
@@ -353,7 +369,9 @@ export async function searchVectorWithEmbedding(
|
|
|
353
369
|
: undefined,
|
|
354
370
|
},
|
|
355
371
|
doc.collection,
|
|
372
|
+
contentTypeRules,
|
|
356
373
|
options.projectAffinity,
|
|
374
|
+
doc.contentTypeSource,
|
|
357
375
|
{ kind: "vector_distance", score: rawDistance }
|
|
358
376
|
);
|
|
359
377
|
results.push(
|
|
@@ -390,8 +408,8 @@ export async function searchVectorWithEmbedding(
|
|
|
390
408
|
}
|
|
391
409
|
return b.score - a.score;
|
|
392
410
|
});
|
|
393
|
-
} else if (
|
|
394
|
-
results
|
|
411
|
+
} else if (auxiliaryRankingActive) {
|
|
412
|
+
sortByFinalScoreStable(results);
|
|
395
413
|
}
|
|
396
414
|
|
|
397
415
|
const finalResults = results.slice(0, limit);
|
|
@@ -485,6 +503,7 @@ interface DocumentInfo {
|
|
|
485
503
|
relPath: string;
|
|
486
504
|
author: string | null;
|
|
487
505
|
contentType: string | null;
|
|
506
|
+
contentTypeSource: string | null;
|
|
488
507
|
categories: string[] | null;
|
|
489
508
|
sourceHash: string;
|
|
490
509
|
sourceMime: string;
|
|
@@ -625,6 +644,7 @@ async function buildDocumentMap(
|
|
|
625
644
|
relPath: doc.relPath,
|
|
626
645
|
author: doc.author ?? null,
|
|
627
646
|
contentType: doc.contentType ?? null,
|
|
647
|
+
contentTypeSource: doc.contentTypeSource ?? null,
|
|
628
648
|
categories: doc.categories ?? null,
|
|
629
649
|
sourceHash: doc.sourceHash,
|
|
630
650
|
sourceMime: doc.sourceMime,
|
package/src/sdk/client.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Config } from "../config/types";
|
|
|
11
11
|
import type { DownloadPolicy } from "../llm/policy";
|
|
12
12
|
import type { EmbeddingPort, GenerationPort, RerankPort } from "../llm/types";
|
|
13
13
|
import type { AskResult, SearchResults } from "../pipeline/types";
|
|
14
|
-
import type {
|
|
14
|
+
import type { StoreResult } from "../store/types";
|
|
15
15
|
import type { VectorIndexPort } from "../store/vector";
|
|
16
16
|
import type {
|
|
17
17
|
GnoAskOptions,
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
GnoGetOptions,
|
|
33
33
|
GnoIndexOptions,
|
|
34
34
|
GnoIndexResult,
|
|
35
|
+
GnoIndexStatus,
|
|
35
36
|
GnoListOptions,
|
|
36
37
|
GnoMoveNoteOptions,
|
|
37
38
|
GnoMultiGetOptions,
|
|
@@ -65,9 +66,11 @@ import {
|
|
|
65
66
|
} from "../app/index-name";
|
|
66
67
|
import { buildVerifiedAsk } from "../app/verified-ask";
|
|
67
68
|
import {
|
|
69
|
+
buildContentTypeBoostStatus,
|
|
68
70
|
ConfigSchema,
|
|
69
71
|
loadConfig,
|
|
70
72
|
normalizeConfigContentTypes,
|
|
73
|
+
normalizeContentTypes,
|
|
71
74
|
} from "../config";
|
|
72
75
|
import {
|
|
73
76
|
buildCaptureReceipt,
|
|
@@ -516,6 +519,9 @@ class GnoClientImpl implements GnoClient {
|
|
|
516
519
|
await searchBm25(this.store, query, {
|
|
517
520
|
...searchOptions,
|
|
518
521
|
projectAffinity,
|
|
522
|
+
contentTypeRules: normalizeContentTypes(
|
|
523
|
+
this.config.contentTypes ?? []
|
|
524
|
+
).rules,
|
|
519
525
|
traceSession: traceSession ?? undefined,
|
|
520
526
|
})
|
|
521
527
|
)
|
|
@@ -883,6 +889,7 @@ class GnoClientImpl implements GnoClient {
|
|
|
883
889
|
noExpand: options.noExpand,
|
|
884
890
|
noRerank: options.noRerank,
|
|
885
891
|
candidateLimit: options.candidateLimit,
|
|
892
|
+
explain: options.explain,
|
|
886
893
|
queryLanguageHint: options.queryLanguageHint,
|
|
887
894
|
projectAffinity,
|
|
888
895
|
traceSession: traceSession ?? undefined,
|
|
@@ -945,6 +952,9 @@ class GnoClientImpl implements GnoClient {
|
|
|
945
952
|
answerGenerated,
|
|
946
953
|
totalResults: searchResult.results.length,
|
|
947
954
|
answerContext,
|
|
955
|
+
...(options.explain && searchResult.meta.explain
|
|
956
|
+
? { explain: searchResult.meta.explain }
|
|
957
|
+
: {}),
|
|
948
958
|
},
|
|
949
959
|
};
|
|
950
960
|
if (answerRequested && traceSession) {
|
|
@@ -1169,13 +1179,19 @@ class GnoClientImpl implements GnoClient {
|
|
|
1169
1179
|
);
|
|
1170
1180
|
}
|
|
1171
1181
|
|
|
1172
|
-
async status(): Promise<
|
|
1182
|
+
async status(): Promise<GnoIndexStatus> {
|
|
1173
1183
|
this.assertOpen();
|
|
1174
|
-
|
|
1184
|
+
const status = unwrapStore(
|
|
1175
1185
|
await this.store.getStatus({
|
|
1176
1186
|
embedModel: resolveModelUri(this.config, "embed"),
|
|
1177
1187
|
})
|
|
1178
1188
|
);
|
|
1189
|
+
return {
|
|
1190
|
+
...status,
|
|
1191
|
+
contentTypeBoost: buildContentTypeBoostStatus(
|
|
1192
|
+
this.config.contentTypes ?? []
|
|
1193
|
+
),
|
|
1194
|
+
};
|
|
1179
1195
|
}
|
|
1180
1196
|
|
|
1181
1197
|
async listRetrievalTraces(
|
package/src/sdk/index.ts
CHANGED
package/src/sdk/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ContextCapsuleBuildInput,
|
|
9
9
|
ContextRuntimeErrorCode,
|
|
10
10
|
} from "../app/context-runtime";
|
|
11
|
+
import type { ContentTypeBoostStatus } from "../config/content-types";
|
|
11
12
|
import type { Config } from "../config/types";
|
|
12
13
|
import type { CaptureInput, CaptureReceipt } from "../core/capture";
|
|
13
14
|
import type {
|
|
@@ -102,15 +103,27 @@ export interface GnoProjectHintOptions {
|
|
|
102
103
|
projectHints?: string[];
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
export type GnoSearchOptions = Omit<
|
|
106
|
+
export type GnoSearchOptions = Omit<
|
|
107
|
+
SearchOptions,
|
|
108
|
+
"contentTypeRules" | "projectAffinity"
|
|
109
|
+
> &
|
|
106
110
|
GnoProjectHintOptions;
|
|
107
|
-
export type GnoQueryOptions = Omit<
|
|
111
|
+
export type GnoQueryOptions = Omit<
|
|
112
|
+
HybridSearchOptions,
|
|
113
|
+
"contentTypeRules" | "projectAffinity"
|
|
114
|
+
> &
|
|
108
115
|
GnoModelOverrides &
|
|
109
116
|
GnoProjectHintOptions;
|
|
110
|
-
export type GnoAskOptions = Omit<
|
|
117
|
+
export type GnoAskOptions = Omit<
|
|
118
|
+
AskOptions,
|
|
119
|
+
"contentTypeRules" | "projectAffinity"
|
|
120
|
+
> &
|
|
111
121
|
GnoModelOverrides &
|
|
112
122
|
GnoProjectHintOptions;
|
|
113
|
-
export type GnoVectorSearchOptions = Omit<
|
|
123
|
+
export type GnoVectorSearchOptions = Omit<
|
|
124
|
+
SearchOptions,
|
|
125
|
+
"contentTypeRules" | "projectAffinity"
|
|
126
|
+
> &
|
|
114
127
|
GnoProjectHintOptions & {
|
|
115
128
|
model?: string;
|
|
116
129
|
};
|
|
@@ -118,6 +131,9 @@ export type GnoVectorSearchOptions = Omit<SearchOptions, "projectAffinity"> &
|
|
|
118
131
|
export type GnoContextInput = Omit<ContextCapsuleBuildInput, "indexName"> &
|
|
119
132
|
GnoProjectHintOptions;
|
|
120
133
|
export type GnoContextResult = ContextCapsuleV1;
|
|
134
|
+
export type GnoIndexStatus = IndexStatus & {
|
|
135
|
+
contentTypeBoost: ContentTypeBoostStatus;
|
|
136
|
+
};
|
|
121
137
|
export type GnoContextVerificationResult = ContextCapsuleVerification;
|
|
122
138
|
export type GnoContextErrorCode =
|
|
123
139
|
| ContextRuntimeErrorCode
|
|
@@ -268,7 +284,7 @@ export interface GnoClient {
|
|
|
268
284
|
ref: string,
|
|
269
285
|
options?: KnowledgeImpactInput
|
|
270
286
|
): Promise<KnowledgeImpactResult>;
|
|
271
|
-
status(): Promise<
|
|
287
|
+
status(): Promise<GnoIndexStatus>;
|
|
272
288
|
listRetrievalTraces(
|
|
273
289
|
options?: RetrievalTraceListRequest
|
|
274
290
|
): Promise<RetrievalTraceListResult>;
|
package/src/serve/config-sync.ts
CHANGED
|
@@ -44,7 +44,7 @@ export async function applyConfigChange(
|
|
|
44
44
|
const result = await applyConfigChangeCore(
|
|
45
45
|
{
|
|
46
46
|
store,
|
|
47
|
-
configPath,
|
|
47
|
+
configPath: configPath ?? ctxHolder.actualConfigPath,
|
|
48
48
|
onConfigUpdated: (config) => {
|
|
49
49
|
ctxHolder.config = config;
|
|
50
50
|
ctxHolder.current = { ...ctxHolder.current, config };
|
|
@@ -72,7 +72,7 @@ export async function applyConfigChangeTyped<T>(
|
|
|
72
72
|
const result = await applyConfigChangeCore(
|
|
73
73
|
{
|
|
74
74
|
store,
|
|
75
|
-
configPath,
|
|
75
|
+
configPath: configPath ?? ctxHolder.actualConfigPath,
|
|
76
76
|
onConfigUpdated: (config) => {
|
|
77
77
|
ctxHolder.config = config;
|
|
78
78
|
ctxHolder.current = { ...ctxHolder.current, config };
|
package/src/serve/routes/api.ts
CHANGED
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
updateCollection,
|
|
46
46
|
} from "../../collection";
|
|
47
47
|
import {
|
|
48
|
-
|
|
48
|
+
fingerprintContentTypeMetadataRules,
|
|
49
49
|
normalizeContentTypes,
|
|
50
50
|
} from "../../config";
|
|
51
51
|
import { type PublicCaptureInput } from "../../core/capture";
|
|
@@ -168,6 +168,7 @@ import { handleChanges, handleDiff, handleImpact } from "./changes";
|
|
|
168
168
|
export interface ContextHolder {
|
|
169
169
|
current: ServerContext;
|
|
170
170
|
config: Config;
|
|
171
|
+
actualConfigPath?: string;
|
|
171
172
|
scheduler: EmbedScheduler | null;
|
|
172
173
|
eventBus: DocumentEventBus | null;
|
|
173
174
|
watchService: CollectionWatchService | null;
|
|
@@ -271,6 +272,7 @@ export interface QueryRequestBody {
|
|
|
271
272
|
noRerank?: boolean;
|
|
272
273
|
noGraph?: boolean;
|
|
273
274
|
graph?: boolean;
|
|
275
|
+
explain?: boolean;
|
|
274
276
|
/** Comma-separated tags - filter to docs having ALL (AND) */
|
|
275
277
|
tagsAll?: string;
|
|
276
278
|
/** Comma-separated tags - filter to docs having ANY (OR) */
|
|
@@ -305,6 +307,7 @@ export interface AskRequestBody {
|
|
|
305
307
|
noRerank?: boolean;
|
|
306
308
|
graph?: boolean;
|
|
307
309
|
noGraph?: boolean;
|
|
310
|
+
explain?: boolean;
|
|
308
311
|
/** Comma-separated tags - filter to docs having ALL (AND) */
|
|
309
312
|
tagsAll?: string;
|
|
310
313
|
/** Comma-separated tags - filter to docs having ANY (OR) */
|
|
@@ -334,6 +337,7 @@ const ASK_REQUEST_KEYS = new Set<keyof AskRequestBody>([
|
|
|
334
337
|
"noRerank",
|
|
335
338
|
"graph",
|
|
336
339
|
"noGraph",
|
|
340
|
+
"explain",
|
|
337
341
|
"tagsAll",
|
|
338
342
|
"tagsAny",
|
|
339
343
|
]);
|
|
@@ -3509,7 +3513,6 @@ export async function handleSearch(
|
|
|
3509
3513
|
if (body.author !== undefined && typeof body.author !== "string") {
|
|
3510
3514
|
return errorResponse("VALIDATION", "author must be a string");
|
|
3511
3515
|
}
|
|
3512
|
-
|
|
3513
3516
|
// Parse tag filters
|
|
3514
3517
|
let tagsAll: string[] | undefined;
|
|
3515
3518
|
let tagsAny: string[] | undefined;
|
|
@@ -3571,6 +3574,9 @@ export async function handleSearch(
|
|
|
3571
3574
|
categories,
|
|
3572
3575
|
author,
|
|
3573
3576
|
projectAffinity,
|
|
3577
|
+
contentTypeRules: context
|
|
3578
|
+
? normalizeContentTypes(context.config.contentTypes ?? []).rules
|
|
3579
|
+
: undefined,
|
|
3574
3580
|
};
|
|
3575
3581
|
|
|
3576
3582
|
const trace = context
|
|
@@ -3678,6 +3684,9 @@ export async function handleQuery(
|
|
|
3678
3684
|
if (body.author !== undefined && typeof body.author !== "string") {
|
|
3679
3685
|
return errorResponse("VALIDATION", "author must be a string");
|
|
3680
3686
|
}
|
|
3687
|
+
if (body.explain !== undefined && typeof body.explain !== "boolean") {
|
|
3688
|
+
return errorResponse("VALIDATION", "explain must be a boolean");
|
|
3689
|
+
}
|
|
3681
3690
|
|
|
3682
3691
|
const { queryModes, error: queryModesError } = parseQueryModesInput(
|
|
3683
3692
|
body.queryModes
|
|
@@ -3767,6 +3776,7 @@ export async function handleQuery(
|
|
|
3767
3776
|
categories,
|
|
3768
3777
|
author,
|
|
3769
3778
|
projectAffinity,
|
|
3779
|
+
explain: body.explain,
|
|
3770
3780
|
};
|
|
3771
3781
|
const trace = await startRestTrace(ctx, {
|
|
3772
3782
|
query: normalizedQuery,
|
|
@@ -3997,7 +4007,7 @@ export async function handleQueryDiagnose(
|
|
|
3997
4007
|
projectAffinity,
|
|
3998
4008
|
contentTypeRules,
|
|
3999
4009
|
contentTypeRulesFingerprint:
|
|
4000
|
-
|
|
4010
|
+
fingerprintContentTypeMetadataRules(contentTypeRules),
|
|
4001
4011
|
}
|
|
4002
4012
|
);
|
|
4003
4013
|
|
|
@@ -4046,6 +4056,7 @@ export async function handleAsk(
|
|
|
4046
4056
|
"noRerank",
|
|
4047
4057
|
"graph",
|
|
4048
4058
|
"noGraph",
|
|
4059
|
+
"explain",
|
|
4049
4060
|
] as const) {
|
|
4050
4061
|
if (body[field] !== undefined && typeof body[field] !== "boolean") {
|
|
4051
4062
|
return errorResponse("VALIDATION", `${field} must be a boolean`);
|
|
@@ -4229,6 +4240,7 @@ export async function handleAsk(
|
|
|
4229
4240
|
contextBudgetBytes: body.contextBudgetBytes,
|
|
4230
4241
|
maxAnswerTokens: body.maxAnswerTokens,
|
|
4231
4242
|
projectAffinity,
|
|
4243
|
+
explain: body.explain,
|
|
4232
4244
|
};
|
|
4233
4245
|
const trace = await startRestTrace(ctx, {
|
|
4234
4246
|
query: normalizedQuery,
|
|
@@ -4445,6 +4457,9 @@ export async function handleAsk(
|
|
|
4445
4457
|
answerGenerated,
|
|
4446
4458
|
totalResults: results.length,
|
|
4447
4459
|
answerContext,
|
|
4460
|
+
...(body.explain && searchResult.value.meta.explain
|
|
4461
|
+
? { explain: searchResult.value.meta.explain }
|
|
4462
|
+
: {}),
|
|
4448
4463
|
},
|
|
4449
4464
|
};
|
|
4450
4465
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ContentTypeBoostStatus } from "../config/content-types";
|
|
1
2
|
import type { ActivationStatus } from "../core/activation-status";
|
|
2
3
|
|
|
3
4
|
export type HealthCheckStatus = "ok" | "warn" | "error";
|
|
@@ -196,6 +197,7 @@ export interface AppStatusResponse {
|
|
|
196
197
|
hybrid: boolean;
|
|
197
198
|
answer: boolean;
|
|
198
199
|
};
|
|
200
|
+
contentTypeBoost: ContentTypeBoostStatus;
|
|
199
201
|
activation: ActivationStatus;
|
|
200
202
|
onboarding: OnboardingState;
|
|
201
203
|
health: HealthCenterState;
|
package/src/serve/status.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
} from "./status-model";
|
|
14
14
|
|
|
15
15
|
import { getModelsCachePath } from "../app/constants";
|
|
16
|
+
import { buildContentTypeBoostStatus } from "../config/content-types";
|
|
16
17
|
import {
|
|
17
18
|
type ActivationStatus,
|
|
18
19
|
buildActivationStatus,
|
|
@@ -740,6 +741,9 @@ export async function buildAppStatus(
|
|
|
740
741
|
name: preset.name,
|
|
741
742
|
},
|
|
742
743
|
capabilities: ctx.capabilities,
|
|
744
|
+
contentTypeBoost: buildContentTypeBoostStatus(
|
|
745
|
+
ctx.config.contentTypes ?? []
|
|
746
|
+
),
|
|
743
747
|
activation,
|
|
744
748
|
onboarding: buildOnboarding(
|
|
745
749
|
status,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration: allow multiple distinct context records at one logical scope.
|
|
3
|
+
*
|
|
4
|
+
* Context resolution already treats text as part of deterministic identity.
|
|
5
|
+
* The original two-column key accidentally limited each scope to one record.
|
|
6
|
+
*
|
|
7
|
+
* @module src/store/migrations/021-multi-context-identity
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Database } from "bun:sqlite";
|
|
11
|
+
|
|
12
|
+
import type { Migration } from "./runner";
|
|
13
|
+
|
|
14
|
+
export const migration: Migration = {
|
|
15
|
+
version: 21,
|
|
16
|
+
name: "multi_context_identity",
|
|
17
|
+
|
|
18
|
+
up(db: Database): void {
|
|
19
|
+
db.exec(`
|
|
20
|
+
CREATE TABLE contexts_v21 (
|
|
21
|
+
scope_type TEXT NOT NULL
|
|
22
|
+
CHECK (scope_type IN ('global', 'collection', 'prefix')),
|
|
23
|
+
scope_key TEXT NOT NULL,
|
|
24
|
+
text TEXT NOT NULL,
|
|
25
|
+
synced_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
26
|
+
PRIMARY KEY (scope_type, scope_key, text)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
INSERT INTO contexts_v21 (scope_type, scope_key, text, synced_at)
|
|
30
|
+
SELECT scope_type, scope_key, text, synced_at
|
|
31
|
+
FROM contexts;
|
|
32
|
+
|
|
33
|
+
DROP TABLE contexts;
|
|
34
|
+
ALTER TABLE contexts_v21 RENAME TO contexts;
|
|
35
|
+
`);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -34,6 +34,7 @@ import { migration as m017 } from "./017-document-change-retention-counters";
|
|
|
34
34
|
import { migration as m018 } from "./018-saved-capsule-registration-epoch";
|
|
35
35
|
import { migration as m019 } from "./019-saved-capsule-registration-generation";
|
|
36
36
|
import { migration as m020 } from "./020-browser-clipper-security";
|
|
37
|
+
import { migration as m021 } from "./021-multi-context-identity";
|
|
37
38
|
|
|
38
39
|
/** All migrations in order */
|
|
39
40
|
export const migrations = [
|
|
@@ -57,4 +58,5 @@ export const migrations = [
|
|
|
57
58
|
m018,
|
|
58
59
|
m019,
|
|
59
60
|
m020,
|
|
61
|
+
m021,
|
|
60
62
|
];
|
|
@@ -625,6 +625,57 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
625
625
|
}
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
+
async upsertCollections(
|
|
629
|
+
collections: Collection[]
|
|
630
|
+
): Promise<StoreResult<void>> {
|
|
631
|
+
try {
|
|
632
|
+
const db = this.ensureOpen();
|
|
633
|
+
const stmt = db.prepare(`
|
|
634
|
+
INSERT INTO collections (name, path, pattern, include, exclude, update_cmd, language_hint, synced_at)
|
|
635
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
|
|
636
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
637
|
+
path = excluded.path,
|
|
638
|
+
pattern = excluded.pattern,
|
|
639
|
+
include = excluded.include,
|
|
640
|
+
exclude = excluded.exclude,
|
|
641
|
+
update_cmd = excluded.update_cmd,
|
|
642
|
+
language_hint = excluded.language_hint,
|
|
643
|
+
synced_at = datetime('now')
|
|
644
|
+
WHERE collections.path IS NOT excluded.path
|
|
645
|
+
OR collections.pattern IS NOT excluded.pattern
|
|
646
|
+
OR collections.include IS NOT excluded.include
|
|
647
|
+
OR collections.exclude IS NOT excluded.exclude
|
|
648
|
+
OR collections.update_cmd IS NOT excluded.update_cmd
|
|
649
|
+
OR collections.language_hint IS NOT excluded.language_hint
|
|
650
|
+
`);
|
|
651
|
+
const transaction = db.transaction(() => {
|
|
652
|
+
for (const collection of collections) {
|
|
653
|
+
stmt.run(
|
|
654
|
+
collection.name,
|
|
655
|
+
collection.path,
|
|
656
|
+
collection.pattern,
|
|
657
|
+
collection.include.length > 0
|
|
658
|
+
? JSON.stringify(collection.include)
|
|
659
|
+
: null,
|
|
660
|
+
collection.exclude.length > 0
|
|
661
|
+
? JSON.stringify(collection.exclude)
|
|
662
|
+
: null,
|
|
663
|
+
collection.updateCmd ?? null,
|
|
664
|
+
collection.languageHint ?? null
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
transaction();
|
|
669
|
+
return ok(undefined);
|
|
670
|
+
} catch (cause) {
|
|
671
|
+
return err(
|
|
672
|
+
"QUERY_FAILED",
|
|
673
|
+
cause instanceof Error ? cause.message : "Failed to upsert collections",
|
|
674
|
+
cause
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
628
679
|
async syncContexts(contexts: Context[]): Promise<StoreResult<void>> {
|
|
629
680
|
try {
|
|
630
681
|
const db = this.ensureOpen();
|
|
@@ -636,6 +687,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
636
687
|
const stmt = db.prepare(`
|
|
637
688
|
INSERT INTO contexts (scope_type, scope_key, text, synced_at)
|
|
638
689
|
VALUES (?, ?, ?, datetime('now'))
|
|
690
|
+
ON CONFLICT(scope_type, scope_key, text) DO UPDATE SET
|
|
691
|
+
synced_at = excluded.synced_at
|
|
639
692
|
`);
|
|
640
693
|
|
|
641
694
|
for (const c of contexts) {
|
|
@@ -655,6 +708,36 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
655
708
|
}
|
|
656
709
|
}
|
|
657
710
|
|
|
711
|
+
async upsertContexts(contexts: Context[]): Promise<StoreResult<void>> {
|
|
712
|
+
try {
|
|
713
|
+
const db = this.ensureOpen();
|
|
714
|
+
const stmt = db.prepare(`
|
|
715
|
+
INSERT INTO contexts (scope_type, scope_key, text, synced_at)
|
|
716
|
+
VALUES (?, ?, ?, datetime('now'))
|
|
717
|
+
ON CONFLICT(scope_type, scope_key, text) DO NOTHING
|
|
718
|
+
`);
|
|
719
|
+
let inserted = 0;
|
|
720
|
+
const transaction = db.transaction(() => {
|
|
721
|
+
for (const context of contexts) {
|
|
722
|
+
inserted += stmt.run(
|
|
723
|
+
context.scopeType,
|
|
724
|
+
context.scopeKey,
|
|
725
|
+
context.text
|
|
726
|
+
).changes;
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
transaction();
|
|
730
|
+
if (inserted > 0) this.contextGeneration += 1;
|
|
731
|
+
return ok(undefined);
|
|
732
|
+
} catch (cause) {
|
|
733
|
+
return err(
|
|
734
|
+
"QUERY_FAILED",
|
|
735
|
+
cause instanceof Error ? cause.message : "Failed to upsert contexts",
|
|
736
|
+
cause
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
658
741
|
async getCollections(): Promise<StoreResult<CollectionRow[]>> {
|
|
659
742
|
try {
|
|
660
743
|
const db = this.ensureOpen();
|
|
@@ -2103,6 +2186,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
2103
2186
|
d.source_size,
|
|
2104
2187
|
d.source_hash,
|
|
2105
2188
|
d.content_type,
|
|
2189
|
+
d.content_type_source,
|
|
2106
2190
|
d.categories
|
|
2107
2191
|
FROM fts_matches fm
|
|
2108
2192
|
JOIN documents d ON d.id = fm.rowid AND d.active = 1
|
|
@@ -2129,6 +2213,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
2129
2213
|
source_size: number | null;
|
|
2130
2214
|
source_hash: string | null;
|
|
2131
2215
|
content_type: string | null;
|
|
2216
|
+
content_type_source: string | null;
|
|
2132
2217
|
categories: string | null;
|
|
2133
2218
|
}
|
|
2134
2219
|
|
|
@@ -2167,6 +2252,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
2167
2252
|
sourceSize: r.source_size ?? undefined,
|
|
2168
2253
|
sourceHash: r.source_hash ?? undefined,
|
|
2169
2254
|
contentType: r.content_type ?? undefined,
|
|
2255
|
+
contentTypeSource: r.content_type_source ?? undefined,
|
|
2170
2256
|
categories: parseCategoriesJson(r.categories) ?? undefined,
|
|
2171
2257
|
}))
|
|
2172
2258
|
);
|