@gmickel/gno 1.26.0 → 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.
Files changed (54) hide show
  1. package/README.md +2 -1
  2. package/assets/skill/SKILL.md +8 -0
  3. package/browser-extension/artifacts/{gno-browser-clipper-v1.26.0.zip → gno-browser-clipper-v1.27.0.zip} +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.27.0.zip.sha256 +1 -0
  5. package/browser-extension/dist/manifest.json +1 -1
  6. package/package.json +1 -1
  7. package/spec/cli.md +15 -2
  8. package/spec/evals-agentic.md +17 -0
  9. package/spec/mcp.md +25 -6
  10. package/spec/output-schemas/ask.schema.json +3 -0
  11. package/spec/output-schemas/query-diagnose.schema.json +61 -4
  12. package/spec/output-schemas/search-results.schema.json +87 -1
  13. package/spec/output-schemas/status.schema.json +24 -0
  14. package/spec/project-profile.schema.json +3 -1
  15. package/src/app/context-runtime-contract.ts +4 -1
  16. package/src/app/context-runtime-types.ts +2 -0
  17. package/src/app/context-runtime.ts +26 -0
  18. package/src/app/verified-ask.ts +6 -1
  19. package/src/cli/commands/ask.ts +8 -1
  20. package/src/cli/commands/query.ts +6 -3
  21. package/src/cli/commands/search.ts +6 -1
  22. package/src/cli/commands/status.ts +43 -7
  23. package/src/cli/program.ts +2 -0
  24. package/src/config/content-types.ts +82 -0
  25. package/src/config/index.ts +8 -0
  26. package/src/config/project-profile.ts +8 -1
  27. package/src/config/types.ts +11 -2
  28. package/src/core/context-compiler.ts +38 -1
  29. package/src/core/retrieval-replay-candidate.ts +6 -1
  30. package/src/ingestion/sync-options.ts +6 -2
  31. package/src/ingestion/sync.ts +21 -29
  32. package/src/ingestion/types.ts +1 -1
  33. package/src/mcp/tools/ask.ts +1 -0
  34. package/src/mcp/tools/index.ts +4 -0
  35. package/src/mcp/tools/query.ts +4 -2
  36. package/src/mcp/tools/search.ts +3 -0
  37. package/src/mcp/tools/status.ts +4 -0
  38. package/src/pipeline/content-type-boost.ts +264 -0
  39. package/src/pipeline/diagnose.ts +46 -19
  40. package/src/pipeline/explain.ts +15 -2
  41. package/src/pipeline/hybrid.ts +170 -74
  42. package/src/pipeline/rerank.ts +45 -15
  43. package/src/pipeline/search.ts +29 -11
  44. package/src/pipeline/types.ts +13 -4
  45. package/src/pipeline/vsearch.ts +30 -10
  46. package/src/sdk/client.ts +19 -3
  47. package/src/sdk/index.ts +1 -0
  48. package/src/sdk/types.ts +21 -5
  49. package/src/serve/routes/api.ts +17 -3
  50. package/src/serve/status-model.ts +2 -0
  51. package/src/serve/status.ts +4 -0
  52. package/src/store/sqlite/adapter.ts +3 -0
  53. package/src/store/types.ts +3 -2
  54. package/browser-extension/artifacts/gno-browser-clipper-v1.26.0.zip.sha256 +0 -1
package/README.md CHANGED
@@ -149,7 +149,8 @@ gno daemon --detach # headless continuous indexing (background; --status / --st
149
149
  remote fetch, store listing, or Firefox parity is claimed.
150
150
  - **Schema-lite content types**: optional `contentTypes` rules map configured
151
151
  frontmatter `type` values or path prefixes to canonical `contentType` metadata
152
- in JSON search/query results
152
+ in JSON search/query results and can apply one bounded, explainable
153
+ `searchBoost` without bypassing hard filters
153
154
  - **Publish to [gno.sh](https://gno.sh/publish)**: new `gno publish export` CLI and Web UI action produce a self-contained artifact you upload to the hosted reader — public, secret, invite-only, or locally encrypted before upload
154
155
  - **Retrieval Quality Upgrade**: stronger BM25 lexical handling, code-aware chunking, terminal result hyperlinks, and per-collection model overrides
155
156
  - **Code Embedding Benchmarks**: new benchmark workflow across canonical, real-GNO, and pinned OSS slices for comparing alternate embedding models
@@ -122,6 +122,7 @@ Recipe rules:
122
122
  --line-numbers Include line numbers
123
123
  --project-root <path> Trusted local root; repeatable and replaces cwd affinity
124
124
  --no-project-affinity Disable trusted local project-aware ranking
125
+ --explain Include retrieval scoring details
125
126
  ```
126
127
 
127
128
  CLI searches use explicit `--project-root`, the nearest valid compiled project
@@ -137,6 +138,13 @@ cannot change another repository's fallback. Explain/diagnose identify this
137
138
  trusted source as `project_profile`; contexts, content types, source metadata,
138
139
  and document fields never become project identity.
139
140
 
141
+ Configured `contentTypes[].searchBoost` is a separate local ranking signal.
142
+ `1` is neutral; `0.5..2` maps to a bounded `-0.05..+0.05` contribution, and
143
+ all auxiliary signals share `±0.08`. It cannot create candidates or bypass hard
144
+ filters. Use `gno query --explain`, `gno ask --explain`, or
145
+ `gno query diagnose` when the ranking effect matters; normal output omits the
146
+ boost receipt.
147
+
140
148
  Do not treat MCP/SDK/REST `projectHints` as paths. They are opaque, untrusted,
141
149
  limited to 16, never trigger filesystem probing, and currently produce zero
142
150
  affinity. Explain uses redacted aliases only. Diagnose preserves exact closed
@@ -0,0 +1 @@
1
+ 3da63d624c5d61f121c5082cd5db2b233e95be5c72beba14f4a3e73a14384e02 gno-browser-clipper-v1.27.0.zip
@@ -21,5 +21,5 @@
21
21
  "content_security_policy": {
22
22
  "extension_pages": "script-src 'self'; object-src 'none'; connect-src http://127.0.0.1:*"
23
23
  },
24
- "version": "1.26.0"
24
+ "version": "1.27.0"
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.26.0",
3
+ "version": "1.27.0",
4
4
  "description": "Local semantic search for your documents. Index Markdown, PDF, and Office files with hybrid BM25 + vector search.",
5
5
  "keywords": [
6
6
  "embeddings",
package/spec/cli.md CHANGED
@@ -436,7 +436,14 @@ Zod validates `id`, `prefixes`, `preset`, `graphHints`, `searchBoost`, and
436
436
  `temporal`, while `preset` remains a permissive string. Post-parse normalization
437
437
  warns and drops unknown preset references, dedupes exact duplicate prefixes,
438
438
  retains overlapping prefixes, and sorts rules longest-prefix-first. `searchBoost`
439
- is accepted but currently no-op. `graphHints` is active: ordered hints type
439
+ defaults to neutral `1`, accepts `0.5..2`, and maps one canonical configured
440
+ type to a bounded `-0.05..+0.05` ranking contribution. A frontmatter type ID
441
+ wins over longest-prefix matching; boosts never stack, cannot create
442
+ candidates, never widen retrieval or defer `minScore`, and share the final
443
+ `±0.08` auxiliary cap with project affinity. Hybrid applies the composed score
444
+ to normalized fusion before rerank blending; rerank order and lexical top-hit
445
+ protection remain authoritative.
446
+ `graphHints` is active: ordered hints type
440
447
  projected wiki/markdown edges and surface in graph traversal/diagnose metadata.
441
448
 
442
449
  ---
@@ -1203,7 +1210,7 @@ Human-friendly query with citations-first output and optional grounded answer.
1203
1210
  **Synopsis:**
1204
1211
 
1205
1212
  ```bash
1206
- gno ask <query> [-n <num>] [-c <collection>] [--lang <bcp47>] [--since <date>] [--until <date>] [--category <values>] [--author <text>] [--intent <text>] [--exclude <values>] [--query-mode <mode:text>]... [-C <num>] [--answer|--verify] [--no-answer] [--max-answer-tokens <n>] [--context-budget-tokens <n>] [--context-budget-bytes <n>] [--min-score <score>] [--graph] [--no-expand] [--no-rerank] [--show-sources] [--json|--md]
1213
+ gno ask <query> [-n <num>] [-c <collection>] [--lang <bcp47>] [--since <date>] [--until <date>] [--category <values>] [--author <text>] [--intent <text>] [--exclude <values>] [--query-mode <mode:text>]... [-C <num>] [--answer|--verify] [--no-answer] [--max-answer-tokens <n>] [--context-budget-tokens <n>] [--context-budget-bytes <n>] [--min-score <score>] [--graph] [--no-expand] [--no-rerank] [--explain] [--show-sources] [--json|--md]
1207
1214
  ```
1208
1215
 
1209
1216
  **Options:**
@@ -1228,6 +1235,7 @@ gno ask <query> [-n <num>] [-c <collection>] [--lang <bcp47>] [--since <date>] [
1228
1235
  | `-C, --candidate-limit` | integer | 20 | Max candidates passed to reranking |
1229
1236
  | `--no-expand` | boolean | false | Disable query expansion |
1230
1237
  | `--no-rerank` | boolean | false | Disable cross-encoder reranking |
1238
+ | `--explain` | boolean | false | Include retrieval scoring details; prints to stderr outside structured output |
1231
1239
  | `--show-sources` | boolean | false | Show all retrieved sources (not just cited) |
1232
1240
  | `--project-root` | string[] | cwd | Trusted project root; repeatable, replaces default cwd/repository affinity |
1233
1241
  | `--no-project-affinity` | boolean | false | Disable project-aware soft ranking; invalid with `--project-root` |
@@ -1238,6 +1246,11 @@ See [Output Schemas](./output-schemas/ask.schema.json)
1238
1246
  Notes:
1239
1247
 
1240
1248
  - `meta.answerContext` is optional explain payload for answer source selection.
1249
+ - `meta.explain` is present only with `--explain`. Its optional
1250
+ `contentTypeBoost` result component contains the raw/base score, configured
1251
+ factor, capped and combined contributions, final score, rule source, and full
1252
+ ranking-rules fingerprint. Verified Ask attaches this as a non-canonical
1253
+ sidecar; Capsule identity and bytes do not change.
1241
1254
  - `--verify` implies answer generation and cannot be combined with
1242
1255
  `--no-answer`. The JSON result adds the closed Capsule, freshness receipt,
1243
1256
  four-state per-claim verdicts (`supported`, `contradicted`, `insufficient`,
@@ -26,6 +26,7 @@ evals/agentic/
26
26
  project-affinity-outcome.ts
27
27
  project-affinity-promotion.ts
28
28
  project-affinity-runtime.ts
29
+ content-type-boost-promotion.ts
29
30
  verified-ask-outcome.ts
30
31
  verified-ask-promotion.ts
31
32
  demos/context-capsule.ts
@@ -103,6 +104,22 @@ receipts, and compares the committed artifact with a fresh deterministic
103
104
  production run. The controlled synthetic lane isolates the score seam and makes
104
105
  no general workload superiority claim.
105
106
 
107
+ ## Separate content-type boost promotion
108
+
109
+ The authoritative lane also writes
110
+ `content-type-boost-promotion.json` and `.md`. These 24 before/after receipts
111
+ project the fn-97 production retrieval rankings through the shipped
112
+ content-type ranking seam with no configured rules. Every ordered URI and
113
+ required-evidence receipt must remain byte-identical, with zero accuracy and
114
+ coverage loss. This is backward-compatibility evidence for existing configs,
115
+ not an active-rule quality claim.
116
+
117
+ Active positive/negative factors, keyword stuffing, deterministic ties,
118
+ configured-ID versus prefix conflicts, filter isolation, and project-affinity
119
+ composition are gated separately by the deterministic adversarial pipeline
120
+ suite. Egress policy is not yet an available retrieval capability, so the
121
+ artifact makes no egress-enforcement claim.
122
+
106
123
  The first fixture version contains 24 original synthetic tasks and 34 Markdown
107
124
  documents under the MIT license. It covers exact identifiers, ambiguity,
108
125
  multi-document comparisons, meeting decisions, temporal questions, typed
package/spec/mcp.md CHANGED
@@ -216,7 +216,7 @@ Required input:
216
216
  Optional fields are `collection`, `limit` (default 5), `minScore`, `lang`,
217
217
  `intent`, `candidateLimit`, `exclude`, `queryModes`, `tagsAll`, `tagsAny`,
218
218
  `since`, `until`, `categories`, `author`, `graph`, `noGraph`, `noRerank`,
219
- `maxAnswerTokens`, `contextBudgetTokens`, and `contextBudgetBytes`. Input
219
+ `explain`, `maxAnswerTokens`, `contextBudgetTokens`, and `contextBudgetBytes`. Input
220
220
  objects are closed.
221
221
  `projectHints` is an optional array of at most 16 non-empty caller hints. Hints
222
222
  are normalized and deduplicated as opaque values, never resolved against or
@@ -648,6 +648,11 @@ Hybrid search combining BM25 and vector retrieval with optional expansion and re
648
648
  "description": "Enable cross-encoder reranking",
649
649
  "default": true
650
650
  },
651
+ "explain": {
652
+ "type": "boolean",
653
+ "description": "Include deterministic stage and per-result scoring metadata",
654
+ "default": false
655
+ },
651
656
  "noGraph": {
652
657
  "type": "boolean",
653
658
  "description": "Compatibility no-op unless graph is also true",
@@ -692,6 +697,11 @@ Search result items include `contentType` when available and always include
692
697
  human-oriented; structured clients should read `structuredContent.results`.
693
698
  Structured result items also preserve optional `context` guidance in
694
699
  global-to-specific order without changing the result `uri` or `docid`.
700
+ When `explain: true`, `structuredContent.meta.explain` includes deterministic
701
+ stage lines and per-result score receipts. Non-neutral configured content-type
702
+ rules add `contentTypeBoost` with base/raw scores, factor, capped contribution,
703
+ shared auxiliary-cap composition, final score, rule source, and redacted rules
704
+ fingerprint. Normal output omits this sidecar.
695
705
 
696
706
  Compatibility / migration notes:
697
707
 
@@ -753,16 +763,17 @@ plus a required `target` reference.
753
763
  - `target`: URI, `#docid`, or `collection/path` for the document to diagnose.
754
764
  - `query`, filters, `queryModes`, `fast`/`thorough`, `graph`, and rerank/expand controls behave like `gno_query`.
755
765
 
756
- **Output Schema:** `gno://schemas/query-diagnose@1.0`
766
+ **Output Schema:** `gno://schemas/query-diagnose@1.2`
757
767
 
758
768
  Structured content includes `schemaVersion`, normalized `query`, `target`
759
769
  metadata/status (`not_found`, `inactive`, `no_indexed_content`,
760
770
  `filtered_out`, or `diagnosed`), `stages` for BM25/vector/fusion/graph/rerank,
761
771
  the selected target `chunk`, and retrieval `meta`.
762
- MCP inputs are remote and untrusted, so this tool preserves exact v1.0 bytes
763
- and omits `affinity`, even when `projectHints` are supplied. The shared current
764
- validation schema is `gno://schemas/query-diagnose@1.1`; its affinity-bearing
765
- v1.1 branch is reserved for trusted local CLI diagnose requests.
772
+ MCP inputs are remote and untrusted, so this tool omits `affinity`, even when
773
+ `projectHints` are supplied. Neutral configurations preserve exact v1.0 bytes;
774
+ an active configured content-type boost emits v1.2 with the same bounded score
775
+ receipt exposed by query explain. The v1.1 affinity-bearing branch remains
776
+ reserved for trusted local CLI diagnose requests.
766
777
 
767
778
  Use when an expected target is missing from `gno_query`, when filters may have
768
779
  excluded it, or when an agent needs evidence before raising `candidateLimit`,
@@ -979,11 +990,19 @@ counters; it never claims attachment to another process.
979
990
  "totalDocuments": 150,
980
991
  "totalChunks": 800,
981
992
  "embeddingBacklog": 0,
993
+ "contentTypeBoost": {
994
+ "rulesFingerprint": "<sha256>",
995
+ "rules": [{ "id": "decision", "searchBoost": 2 }]
996
+ },
982
997
  "healthy": true
983
998
  }
984
999
  }
985
1000
  ```
986
1001
 
1002
+ `contentTypeBoost` is a redacted ranking-status projection. It exposes only
1003
+ normalized IDs/factors plus the rules fingerprint; path prefixes are never
1004
+ returned.
1005
+
987
1006
  ---
988
1007
 
989
1008
  ### gno_capture
@@ -557,6 +557,9 @@
557
557
  }
558
558
  }
559
559
  }
560
+ },
561
+ "explain": {
562
+ "$ref": "gno://schemas/search-results@1.0#/properties/meta/properties/explain"
560
563
  }
561
564
  }
562
565
  }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "gno://schemas/query-diagnose@1.1",
3
+ "$id": "gno://schemas/query-diagnose@1.2",
4
4
  "title": "GNO Query Diagnose",
5
5
  "type": "object",
6
6
  "required": ["schemaVersion", "query", "target", "stages", "chunk", "meta"],
7
7
  "properties": {
8
- "schemaVersion": { "type": "string", "enum": ["1.0", "1.1"] },
8
+ "schemaVersion": { "type": "string", "enum": ["1.0", "1.1", "1.2"] },
9
9
  "query": { "type": "string" },
10
10
  "target": {
11
11
  "type": "object",
@@ -160,6 +160,51 @@
160
160
  },
161
161
  "additionalProperties": false
162
162
  },
163
+ "contentTypeBoost": {
164
+ "type": "object",
165
+ "required": [
166
+ "baseScore",
167
+ "cappedContribution",
168
+ "combinedAuxiliaryApplied",
169
+ "combinedAuxiliaryCap",
170
+ "combinedAuxiliaryRequested",
171
+ "configuredFactor",
172
+ "contentType",
173
+ "finalScore",
174
+ "rawContribution",
175
+ "rawScore",
176
+ "rawScoreKind",
177
+ "ruleSource",
178
+ "rulesFingerprint"
179
+ ],
180
+ "properties": {
181
+ "baseScore": { "type": "number", "minimum": 0, "maximum": 1 },
182
+ "cappedContribution": {
183
+ "type": "number",
184
+ "minimum": -0.05,
185
+ "maximum": 0.05
186
+ },
187
+ "combinedAuxiliaryApplied": {
188
+ "type": "number",
189
+ "minimum": -0.08,
190
+ "maximum": 0.08
191
+ },
192
+ "combinedAuxiliaryCap": { "type": "number", "const": 0.08 },
193
+ "combinedAuxiliaryRequested": { "type": "number" },
194
+ "configuredFactor": { "type": "number", "minimum": 0.5, "maximum": 2 },
195
+ "contentType": { "type": "string" },
196
+ "finalScore": { "type": "number", "minimum": 0, "maximum": 1 },
197
+ "rawContribution": { "type": "number" },
198
+ "rawScore": { "type": "number" },
199
+ "rawScoreKind": {
200
+ "type": "string",
201
+ "enum": ["bm25", "hybrid_blended", "normalized", "vector_distance"]
202
+ },
203
+ "ruleSource": { "type": "string", "enum": ["configured-id", "prefix"] },
204
+ "rulesFingerprint": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
205
+ },
206
+ "additionalProperties": false
207
+ },
163
208
  "chunk": {
164
209
  "type": "object",
165
210
  "required": ["seq", "startLine", "endLine", "language"],
@@ -199,7 +244,9 @@
199
244
  "properties": { "schemaVersion": { "const": "1.0" } },
200
245
  "required": ["schemaVersion"]
201
246
  },
202
- "then": { "properties": { "affinity": false } }
247
+ "then": {
248
+ "properties": { "affinity": false, "contentTypeBoost": false }
249
+ }
203
250
  },
204
251
  {
205
252
  "if": {
@@ -207,9 +254,19 @@
207
254
  "required": ["schemaVersion"]
208
255
  },
209
256
  "then": {
210
- "properties": { "affinity": {} },
257
+ "properties": { "affinity": {}, "contentTypeBoost": false },
211
258
  "required": ["affinity"]
212
259
  }
260
+ },
261
+ {
262
+ "if": {
263
+ "properties": { "schemaVersion": { "const": "1.2" } },
264
+ "required": ["schemaVersion"]
265
+ },
266
+ "then": {
267
+ "properties": { "contentTypeBoost": {} },
268
+ "required": ["contentTypeBoost"]
269
+ }
213
270
  }
214
271
  ],
215
272
  "additionalProperties": false
@@ -166,7 +166,78 @@
166
166
  "type": "string"
167
167
  }
168
168
  }
169
- }
169
+ },
170
+ "additionalProperties": false
171
+ },
172
+ "contentTypeBoost": {
173
+ "type": "object",
174
+ "required": [
175
+ "baseScore",
176
+ "cappedContribution",
177
+ "combinedAuxiliaryApplied",
178
+ "combinedAuxiliaryCap",
179
+ "combinedAuxiliaryRequested",
180
+ "configuredFactor",
181
+ "contentType",
182
+ "finalScore",
183
+ "rawContribution",
184
+ "rawScore",
185
+ "rawScoreKind",
186
+ "ruleSource",
187
+ "rulesFingerprint"
188
+ ],
189
+ "properties": {
190
+ "baseScore": { "type": "number", "minimum": 0, "maximum": 1 },
191
+ "cappedContribution": {
192
+ "type": "number",
193
+ "minimum": -0.05,
194
+ "maximum": 0.05
195
+ },
196
+ "combinedAuxiliaryApplied": {
197
+ "type": "number",
198
+ "minimum": -0.08,
199
+ "maximum": 0.08
200
+ },
201
+ "combinedAuxiliaryCap": { "type": "number", "const": 0.08 },
202
+ "combinedAuxiliaryRequested": { "type": "number" },
203
+ "configuredFactor": { "type": "number", "minimum": 0.5, "maximum": 2 },
204
+ "contentType": { "type": "string" },
205
+ "finalScore": { "type": "number", "minimum": 0, "maximum": 1 },
206
+ "rawContribution": { "type": "number" },
207
+ "rawScore": { "type": "number" },
208
+ "rawScoreKind": {
209
+ "type": "string",
210
+ "enum": ["bm25", "hybrid_blended", "normalized", "vector_distance"]
211
+ },
212
+ "ruleSource": { "type": "string", "enum": ["configured-id", "prefix"] },
213
+ "rulesFingerprint": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
214
+ },
215
+ "additionalProperties": false
216
+ },
217
+ "explainLine": {
218
+ "type": "object",
219
+ "required": ["stage", "message"],
220
+ "properties": {
221
+ "stage": { "type": "string" },
222
+ "message": { "type": "string" }
223
+ },
224
+ "additionalProperties": false
225
+ },
226
+ "explainResult": {
227
+ "type": "object",
228
+ "required": ["rank", "docid", "score"],
229
+ "properties": {
230
+ "rank": { "type": "integer", "minimum": 1 },
231
+ "docid": { "type": "string", "pattern": "^#[a-f0-9]{6,8}$" },
232
+ "score": { "type": "number" },
233
+ "fusionScore": { "type": "number" },
234
+ "bm25Score": { "type": "number" },
235
+ "vecScore": { "type": "number" },
236
+ "rerankScore": { "type": "number" },
237
+ "projectAffinity": { "type": "object" },
238
+ "contentTypeBoost": { "$ref": "#/$defs/contentTypeBoost" }
239
+ },
240
+ "additionalProperties": false
170
241
  }
171
242
  },
172
243
  "properties": {
@@ -331,6 +402,21 @@
331
402
  "type": "array",
332
403
  "items": { "type": "string" },
333
404
  "description": "Tags ANY filter applied (if any)"
405
+ },
406
+ "explain": {
407
+ "type": "object",
408
+ "required": ["lines", "results"],
409
+ "properties": {
410
+ "lines": {
411
+ "type": "array",
412
+ "items": { "$ref": "#/$defs/explainLine" }
413
+ },
414
+ "results": {
415
+ "type": "array",
416
+ "items": { "$ref": "#/$defs/explainResult" }
417
+ }
418
+ },
419
+ "additionalProperties": false
334
420
  }
335
421
  }
336
422
  }
@@ -15,6 +15,7 @@
15
15
  "recentErrors",
16
16
  "activePreset",
17
17
  "capabilities",
18
+ "contentTypeBoost",
18
19
  "activation",
19
20
  "onboarding",
20
21
  "health",
@@ -136,6 +137,29 @@
136
137
  }
137
138
  }
138
139
  },
140
+ "contentTypeBoost": {
141
+ "type": "object",
142
+ "additionalProperties": false,
143
+ "required": ["rulesFingerprint", "rules"],
144
+ "properties": {
145
+ "rulesFingerprint": {
146
+ "type": "string",
147
+ "pattern": "^[a-f0-9]{64}$"
148
+ },
149
+ "rules": {
150
+ "type": "array",
151
+ "items": {
152
+ "type": "object",
153
+ "additionalProperties": false,
154
+ "required": ["id", "searchBoost"],
155
+ "properties": {
156
+ "id": { "type": "string", "minLength": 1 },
157
+ "searchBoost": { "type": "number", "minimum": 0.5, "maximum": 2 }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ },
139
163
  "activation": {
140
164
  "type": "object",
141
165
  "additionalProperties": false,
@@ -270,7 +270,9 @@
270
270
  }
271
271
  },
272
272
  "searchBoost": {
273
- "type": "number"
273
+ "type": "number",
274
+ "minimum": 0.5,
275
+ "maximum": 2
274
276
  },
275
277
  "temporal": {
276
278
  "type": "boolean"
@@ -26,7 +26,10 @@ const fingerprint = (value: unknown): string =>
26
26
 
27
27
  export const contextRuntimeConfigFingerprint = (
28
28
  deps: Pick<ContextCapsuleRuntimeDeps, "config">
29
- ): string => fingerprint(deps.config);
29
+ ): string =>
30
+ fingerprint(
31
+ JSON.parse(JSON.stringify(deps.config)) as Record<string, unknown>
32
+ );
30
33
 
31
34
  const configuredContextFingerprint = (
32
35
  deps: ContextCapsuleRuntimeDeps
@@ -55,6 +55,8 @@ export interface ContextCapsuleRuntimeDeps {
55
55
  projectAffinity?: ProjectAffinityScoringInput;
56
56
  /** Optional non-canonical receipt session owned by the calling surface. */
57
57
  traceSession?: RetrievalTraceSession;
58
+ /** Internal non-canonical retrieval explanation request. */
59
+ explain?: boolean;
58
60
  }
59
61
 
60
62
  export type ContextRuntimeErrorCode =
@@ -4,6 +4,7 @@ import type {
4
4
  ContextCapsuleV1,
5
5
  ContextCapsuleVerification,
6
6
  } from "../core/context-capsule";
7
+ import type { SearchExplain } from "../pipeline/types";
7
8
  import type {
8
9
  ContextCapsuleBuildInput,
9
10
  ContextCapsuleRuntimeDeps,
@@ -36,6 +37,19 @@ export type {
36
37
  } from "./context-runtime-types";
37
38
  export { ContextRuntimeError } from "./context-runtime-types";
38
39
 
40
+ export const CONTEXT_CAPSULE_EXPLAIN_METADATA = Symbol(
41
+ "gno.contextCapsuleExplainMetadata"
42
+ );
43
+
44
+ export const getContextCapsuleExplain = (
45
+ capsule: ContextCapsuleV1
46
+ ): SearchExplain | undefined =>
47
+ (
48
+ capsule as ContextCapsuleV1 & {
49
+ [CONTEXT_CAPSULE_EXPLAIN_METADATA]?: SearchExplain;
50
+ }
51
+ )[CONTEXT_CAPSULE_EXPLAIN_METADATA];
52
+
39
53
  /** Build one strict Capsule through the shared compiler composition. */
40
54
  export const buildContextCapsule = async (
41
55
  input: ContextCapsuleBuildInput,
@@ -72,6 +86,7 @@ export const buildContextCapsule = async (
72
86
  limit: normalized.limit,
73
87
  candidateLimit: normalized.candidateLimit,
74
88
  temporalNow: now,
89
+ explain: deps.explain,
75
90
  limits: {
76
91
  requestedBytes: normalized.budgetBytes,
77
92
  requestedTokens: normalized.budgetTokens,
@@ -134,6 +149,17 @@ export const buildContextCapsule = async (
134
149
  traceResult.error.cause
135
150
  );
136
151
  }
152
+ if (plan.explain) {
153
+ Object.defineProperty(
154
+ plan.projection.value,
155
+ CONTEXT_CAPSULE_EXPLAIN_METADATA,
156
+ {
157
+ configurable: true,
158
+ enumerable: false,
159
+ value: plan.explain,
160
+ }
161
+ );
162
+ }
137
163
  return plan.projection.value;
138
164
  };
139
165
 
@@ -16,6 +16,7 @@ import { attachCitationTraceMetadata } from "../pipeline/trace-metadata";
16
16
  import { CITATION_TRACE_METADATA } from "../pipeline/types";
17
17
  import {
18
18
  buildContextCapsule,
19
+ getContextCapsuleExplain,
19
20
  verifyContextCapsuleRuntime,
20
21
  } from "./context-runtime";
21
22
  import { contextRuntimeConfigFingerprint } from "./context-runtime-contract";
@@ -199,7 +200,7 @@ export const buildVerifiedAsk = async (
199
200
  budgetBytes: options.contextBudgetBytes,
200
201
  depthPolicy: "balanced",
201
202
  },
202
- deps
203
+ { ...deps, explain: options.explain }
203
204
  );
204
205
  const freshness = await verifyContextCapsuleRuntime(capsule, deps);
205
206
  return synthesizeVerifiedAsk(query, options, capsule, freshness, deps);
@@ -258,6 +259,9 @@ export const synthesizeVerifiedAsk = async (
258
259
  citationEvidenceIds(verification, new Set(["supported", "contradicted"]))
259
260
  );
260
261
  await recordRetainedCitations(deps.traceSession, traceCitations);
262
+ const retrievalExplain = options.explain
263
+ ? getContextCapsuleExplain(capsule)
264
+ : undefined;
261
265
  return {
262
266
  query,
263
267
  mode: capsule.capabilities.semanticSearch ? "hybrid" : "bm25_only",
@@ -276,6 +280,7 @@ export const synthesizeVerifiedAsk = async (
276
280
  exclude: capsule.retrieval.request.exclude,
277
281
  answerGenerated: generated.ok,
278
282
  totalResults: capsule.evidence.length,
283
+ ...(retrievalExplain ? { explain: retrievalExplain } : {}),
279
284
  verificationRequested: true,
280
285
  abstained: verification.verification.abstained,
281
286
  },
@@ -48,7 +48,10 @@ export { formatAsk } from "./ask-format";
48
48
  // Types
49
49
  // ─────────────────────────────────────────────────────────────────────────────
50
50
 
51
- export type AskCommandOptions = Omit<AskOptions, "projectAffinity"> &
51
+ export type AskCommandOptions = Omit<
52
+ AskOptions,
53
+ "contentTypeRules" | "projectAffinity"
54
+ > &
52
55
  CliProjectAffinityRequest & {
53
56
  /** Override config path */
54
57
  configPath?: string;
@@ -318,6 +321,7 @@ export async function ask(
318
321
  noExpand: options.noExpand,
319
322
  noRerank: options.noRerank,
320
323
  candidateLimit: options.candidateLimit,
324
+ explain: options.explain,
321
325
  projectAffinity,
322
326
  traceSession,
323
327
  });
@@ -397,6 +401,9 @@ export async function ask(
397
401
  answerGenerated,
398
402
  totalResults: results.length,
399
403
  answerContext,
404
+ ...(options.explain && searchResult.value.meta.explain
405
+ ? { explain: searchResult.value.meta.explain }
406
+ : {}),
400
407
  },
401
408
  };
402
409
 
@@ -16,7 +16,7 @@ import type {
16
16
  import type { HybridSearchOptions, SearchResults } from "../../pipeline/types";
17
17
 
18
18
  import {
19
- fingerprintContentTypeRules,
19
+ fingerprintContentTypeMetadataRules,
20
20
  normalizeContentTypes,
21
21
  } from "../../config";
22
22
  import { resolveCliProjectAffinity } from "../../core/project-affinity-surface";
@@ -48,7 +48,10 @@ import { decorateSearchResultsForIndex, initStore } from "./shared";
48
48
  // Types
49
49
  // ─────────────────────────────────────────────────────────────────────────────
50
50
 
51
- export type QueryCommandOptions = Omit<HybridSearchOptions, "projectAffinity"> &
51
+ export type QueryCommandOptions = Omit<
52
+ HybridSearchOptions,
53
+ "contentTypeRules" | "projectAffinity"
54
+ > &
52
55
  CliProjectAffinityRequest & {
53
56
  /** Override config path */
54
57
  configPath?: string;
@@ -439,7 +442,7 @@ export async function queryDiagnose(
439
442
  projectAffinity,
440
443
  contentTypeRules,
441
444
  contentTypeRulesFingerprint:
442
- fingerprintContentTypeRules(contentTypeRules),
445
+ fingerprintContentTypeMetadataRules(contentTypeRules),
443
446
  });
444
447
 
445
448
  if (!result.ok) {