@gmickel/gno 1.22.0 → 1.23.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 (42) hide show
  1. package/README.md +16 -1
  2. package/assets/skill/SKILL.md +15 -0
  3. package/package.json +1 -1
  4. package/spec/cli.md +36 -20
  5. package/spec/evals-agentic.md +35 -0
  6. package/spec/evals.md +6 -0
  7. package/spec/mcp.md +18 -0
  8. package/spec/output-schemas/query-diagnose-v1.schema.json +123 -0
  9. package/spec/output-schemas/query-diagnose.schema.json +89 -2
  10. package/src/app/context-runtime-types.ts +3 -0
  11. package/src/app/context-runtime.ts +1 -0
  12. package/src/app/context-surface.ts +4 -2
  13. package/src/cli/commands/ask.ts +31 -20
  14. package/src/cli/commands/context-build.ts +17 -7
  15. package/src/cli/commands/query.ts +58 -37
  16. package/src/cli/commands/search.ts +29 -19
  17. package/src/cli/commands/vsearch.ts +31 -22
  18. package/src/cli/options.ts +39 -0
  19. package/src/cli/program.ts +48 -0
  20. package/src/config/defaults.ts +10 -1
  21. package/src/config/types.ts +71 -0
  22. package/src/core/project-affinity-surface.ts +114 -0
  23. package/src/core/project-affinity.ts +330 -0
  24. package/src/core/validation.ts +20 -1
  25. package/src/mcp/tools/ask.ts +10 -1
  26. package/src/mcp/tools/context.ts +18 -0
  27. package/src/mcp/tools/index.ts +13 -2
  28. package/src/mcp/tools/query.ts +12 -0
  29. package/src/mcp/tools/search.ts +7 -0
  30. package/src/mcp/tools/vsearch.ts +7 -0
  31. package/src/pipeline/diagnose.ts +48 -3
  32. package/src/pipeline/explain.ts +54 -13
  33. package/src/pipeline/hybrid.ts +100 -59
  34. package/src/pipeline/project-affinity.ts +162 -0
  35. package/src/pipeline/search.ts +76 -10
  36. package/src/pipeline/types.ts +9 -0
  37. package/src/pipeline/vsearch.ts +117 -91
  38. package/src/sdk/client.ts +80 -20
  39. package/src/sdk/index.ts +2 -0
  40. package/src/sdk/types.ts +20 -7
  41. package/src/serve/context-capsule.ts +18 -1
  42. package/src/serve/routes/api.ts +69 -0
package/README.md CHANGED
@@ -17,6 +17,17 @@
17
17
 
18
18
  GNO is a local knowledge engine for notes, code, PDFs, Office docs, meeting transcripts, and reference material. It gives you fast keyword search, semantic retrieval, grounded answers with citations, wiki-style linking, and a real workspace UI, while keeping the whole stack local by default.
19
19
 
20
+ CLI retrieval also uses the current repository/workspace as a transparent soft
21
+ ranking signal. A trusted local cwd or repeatable `--project-root` can add at
22
+ most `+0.03` to matching collection results; `--no-project-affinity` disables
23
+ it, and explicit roots replace cwd inference. It never overrides collection,
24
+ tag, date, exclude, or egress filters. SDK, REST, and MCP `projectHints` are
25
+ opaque, untrusted, limited to 16, and intentionally have zero ranking effect:
26
+ those surfaces never probe caller or server filesystem paths. Trusted local
27
+ diagnose output uses closed `schemaVersion: "1.1"` redacted affinity metadata;
28
+ absent, disabled, and remote/untrusted diagnose requests preserve exact legacy
29
+ v1.0 bytes and omit `affinity`.
30
+
20
31
  Use it when:
21
32
 
22
33
  - your notes live in more than one folder
@@ -94,12 +105,16 @@ gno daemon --detach # headless continuous indexing (background; --status / --st
94
105
 
95
106
  <!-- public-truth:current-version -->
96
107
 
97
- > Current release: **v1.22.0** — see [CHANGELOG.md](./CHANGELOG.md)
108
+ > Current release: **v1.23.0** — see [CHANGELOG.md](./CHANGELOG.md)
98
109
 
99
110
  <!-- /public-truth -->
100
111
 
101
112
  > Full release history: [CHANGELOG.md](./CHANGELOG.md)
102
113
 
114
+ - **Project-aware retrieval affinity**: trusted local CLI searches can use the
115
+ current workspace or explicit `--project-root` values as a transparent,
116
+ explainable `+0.03` soft ranking signal. Filters remain hard, and untrusted
117
+ SDK, REST, MCP, and Web hints never probe paths or affect ranking.
103
118
  - **Retrieval-proven activation**: `gno status`, `gno doctor`, REST, and the
104
119
  Web/Desktop dashboard now share a per-folder lexical retrieval proof. Local
105
120
  semantic readiness remains independent, and installed MCP targets can run an
@@ -108,8 +108,23 @@ Recipe rules:
108
108
  --json JSON output
109
109
  --files URI list output
110
110
  --line-numbers Include line numbers
111
+ --project-root <path> Trusted local root; repeatable and replaces cwd affinity
112
+ --no-project-affinity Disable trusted local project-aware ranking
111
113
  ```
112
114
 
115
+ CLI searches use the current repository/worktree as a soft signal by default.
116
+ A matching collection can receive at most `+0.03`; roots never stack, all
117
+ auxiliary signals share `±0.08`, and collection/tag/date/exclude/egress filters
118
+ stay hard. Use `--project-root` for explicit trusted roots or
119
+ `--no-project-affinity` to disable it.
120
+
121
+ Do not treat MCP/SDK/REST `projectHints` as paths. They are opaque, untrusted,
122
+ limited to 16, never trigger filesystem probing, and currently produce zero
123
+ affinity. Explain uses redacted aliases only. Diagnose preserves exact closed
124
+ v1.0 bytes and omits `affinity` for absent, disabled, and remote/untrusted
125
+ inputs; trusted local diagnose uses closed v1.1 redacted metadata, including an
126
+ explicit unmatched state. The Web UI does not infer a browser project root.
127
+
113
128
  ## Advanced: Structured Query Modes (query/ask only)
114
129
 
115
130
  Use `--query-mode` to combine multiple retrieval strategies in one query (repeatable):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.22.0",
3
+ "version": "1.23.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
@@ -749,22 +749,24 @@ gno search <query> [-n <num>] [--min-score <num>] [-c <collection>] [--since <da
749
749
 
750
750
  **Options:**
751
751
 
752
- | Option | Type | Default | Description |
753
- | ------------------ | ------- | ------------------------- | --------------------------------------------------------------------------------------------- |
754
- | `-n` | integer | 5 (20 for --json/--files) | Max results |
755
- | `--min-score` | number | 0 | Minimum score threshold |
756
- | `-c, --collection` | string | all | Filter to collection |
757
- | `--since` | string | none | Modified-at lower bound (ISO date/time or relative token) |
758
- | `--until` | string | none | Modified-at upper bound (ISO date/time or relative token) |
759
- | `--category` | string | none | Filter to docs with matching category/content type (comma-separated) |
760
- | `--author` | string | none | Filter to docs where author contains value (case-insensitive) |
761
- | `--intent` | string | none | Disambiguating context for ambiguous queries; steers snippets without being searched directly |
762
- | `--exclude` | string | none | Hard-prune docs containing any comma-separated term in title/path/body |
763
- | `--tags-all` | string | none | Filter to docs with ALL tags (comma-separated) |
764
- | `--tags-any` | string | none | Filter to docs with ANY tag (comma-separated) |
765
- | `--full` | boolean | false | Include full mirror content instead of snippet |
766
- | `--line-numbers` | boolean | false | Include line numbers in output |
767
- | `--lang` | string | auto | Language filter/hint (BCP-47) |
752
+ | Option | Type | Default | Description |
753
+ | ----------------------- | -------- | ------------------------- | --------------------------------------------------------------------------------------------- |
754
+ | `-n` | integer | 5 (20 for --json/--files) | Max results |
755
+ | `--min-score` | number | 0 | Minimum score threshold |
756
+ | `-c, --collection` | string | all | Filter to collection |
757
+ | `--since` | string | none | Modified-at lower bound (ISO date/time or relative token) |
758
+ | `--until` | string | none | Modified-at upper bound (ISO date/time or relative token) |
759
+ | `--category` | string | none | Filter to docs with matching category/content type (comma-separated) |
760
+ | `--author` | string | none | Filter to docs where author contains value (case-insensitive) |
761
+ | `--intent` | string | none | Disambiguating context for ambiguous queries; steers snippets without being searched directly |
762
+ | `--exclude` | string | none | Hard-prune docs containing any comma-separated term in title/path/body |
763
+ | `--tags-all` | string | none | Filter to docs with ALL tags (comma-separated) |
764
+ | `--tags-any` | string | none | Filter to docs with ANY tag (comma-separated) |
765
+ | `--project-root` | string[] | cwd | Trusted project root; repeatable, replaces default cwd/repository affinity |
766
+ | `--no-project-affinity` | boolean | false | Disable project-aware soft ranking; invalid with `--project-root` |
767
+ | `--full` | boolean | false | Include full mirror content instead of snippet |
768
+ | `--line-numbers` | boolean | false | Include line numbers in output |
769
+ | `--lang` | string | auto | Language filter/hint (BCP-47) |
768
770
 
769
771
  **Scoring:**
770
772
 
@@ -824,7 +826,8 @@ Vector semantic search over indexed documents.
824
826
  gno vsearch <query> [-n <num>] [--min-score <num>] [-c <collection>] [--since <date>] [--until <date>] [--category <values>] [--author <text>] [--intent <text>] [--exclude <values>] [--tags-all <tags>] [--tags-any <tags>] [--full] [--line-numbers] [--lang <bcp47>] [--json|--files|--csv|--md|--xml]
825
827
  ```
826
828
 
827
- **Options:** Same as `gno search` (including temporal/category/author and tag filters)
829
+ **Options:** Same as `gno search` (including temporal/category/author, tag, and
830
+ project-affinity controls).
828
831
 
829
832
  **Scoring:**
830
833
 
@@ -870,6 +873,9 @@ gno query diagnose <query> --target <doc> [-n <num>] [--min-score <num>] [-c <co
870
873
  | `--explain` | boolean | Print retrieval explanation to stderr |
871
874
  | `--target` | ref | Required for `query diagnose`; target document to diagnose |
872
875
 
876
+ `query diagnose` accepts the same `--project-root` and
877
+ `--no-project-affinity` controls as `query`.
878
+
873
879
  **Compatibility / Migration:**
874
880
 
875
881
  - Legacy query invocations remain valid (`gno query "<text>"`, `--fast`, `--thorough`, `--no-expand`, `--no-rerank`).
@@ -882,8 +888,12 @@ gno query diagnose <query> --target <doc> [-n <num>] [--min-score <num>] [-c <co
882
888
  **Diagnose Output:**
883
889
 
884
890
  `gno query diagnose` wraps the shared `diagnoseQueryTarget()` core and emits
885
- `query-diagnose.schema.json` for `--json`. The payload requires
886
- `schemaVersion: "1.0"`, resolves the target first, reports `target.status`
891
+ `query-diagnose.schema.json` for `--json`. No trusted affinity input preserves
892
+ the exact closed `schemaVersion: "1.0"` payload and omits `affinity`; the legacy
893
+ contract remains in `query-diagnose-v1.schema.json`. A resolved trusted CLI cwd
894
+ or explicit project root emits `schemaVersion: "1.1"` and requires closed,
895
+ redacted `affinity` metadata, including unmatched state. The payload resolves
896
+ the target first, reports `target.status`
887
897
  (`not_found|inactive|no_indexed_content|filtered_out|diagnosed`), and only runs
888
898
  stage tracing for `diagnosed` targets. Stages report
889
899
  `present`, `rank`, `score`, `survived`, `dropReason`, `status`, and
@@ -992,6 +1002,8 @@ gno ask <query> [-n <num>] [-c <collection>] [--lang <bcp47>] [--since <date>] [
992
1002
  | `--no-expand` | boolean | false | Disable query expansion |
993
1003
  | `--no-rerank` | boolean | false | Disable cross-encoder reranking |
994
1004
  | `--show-sources` | boolean | false | Show all retrieved sources (not just cited) |
1005
+ | `--project-root` | string[] | cwd | Trusted project root; repeatable, replaces default cwd/repository affinity |
1006
+ | `--no-project-affinity` | boolean | false | Disable project-aware soft ranking; invalid with `--project-root` |
995
1007
 
996
1008
  **Output (JSON):**
997
1009
  See [Output Schemas](./output-schemas/ask.schema.json)
@@ -1315,7 +1327,7 @@ written to stderr.
1315
1327
  **Synopsis:**
1316
1328
 
1317
1329
  ```bash
1318
- gno context build "<goal>" --budget <tokens> [--collection <name>] [--fast|--thorough] [--json|--md] [--output <file>]
1330
+ gno context build "<goal>" --budget <tokens> [--collection <name>] [--project-root <path>]... [--no-project-affinity] [--fast|--thorough] [--json|--md] [--output <file>]
1319
1331
  ```
1320
1332
 
1321
1333
  `--budget` is the global token ceiling. `--bytes` optionally sets a separate
@@ -1328,6 +1340,10 @@ repeatable. Tag filters are NFC-normalized, lowercased, deduplicated, and
1328
1340
  validated before retrieval. Result and candidate limits are global across
1329
1341
  repeated collections: the merged result pool is capped once, while candidate
1330
1342
  work is distributed deterministically in canonical collection order.
1343
+ Project affinity defaults to the trusted process cwd/repository. Repeatable
1344
+ `--project-root` values replace that default, are normalized/deduplicated, and
1345
+ are capped at 16. `--no-project-affinity` disables the soft signal and cannot
1346
+ be combined with explicit roots.
1331
1347
 
1332
1348
  JSON is the canonical V1 payload. Markdown is a readable projection of that
1333
1349
  same payload and hard-delimits each untrusted evidence passage. Passage,
@@ -22,6 +22,10 @@ evals/agentic/
22
22
  fixture-db.ts
23
23
  scoring.ts
24
24
  promotion.ts
25
+ project-affinity-contract.ts
26
+ project-affinity-outcome.ts
27
+ project-affinity-promotion.ts
28
+ project-affinity-runtime.ts
25
29
  verified-ask-outcome.ts
26
30
  verified-ask-promotion.ts
27
31
  demos/context-capsule.ts
@@ -68,6 +72,37 @@ evals/fixtures/agentic-retrieval/
68
72
  context-capsule.md
69
73
  ```
70
74
 
75
+ ## Separate project-affinity promotion
76
+
77
+ `project-affinity-cases.json` defines two controlled vector-distance pairs over
78
+ the existing `t456ef70` (`c015`/`c115`) and `t567f081`
79
+ (`c016`/`c116`) task/corpus/oracle identities. The separate closed
80
+ `project-affinity-promotion@1.0` artifact hash-binds those manifest identities;
81
+ it does not add tasks to the authoritative 24-task, 144-receipt matrix or
82
+ change `BenchmarkReport@1`.
83
+
84
+ The target collection starts `0.02` behind, then receives one trusted local
85
+ `+0.03` contribution. Promotion requires correct top-1 to strictly improve to
86
+ `2/2`, exact required evidence to remain retained, zero URI-rank/required
87
+ evidence-coverage loss across all 24 hard-collection tasks, and zero loss for
88
+ `t012ab3c`, `t123bc4d`, `te8f901a`, and `tf901a2b`. It also gates hard-filter
89
+ isolation, absent/disabled/unavailable/untrusted exact zero lanes, shared
90
+ auxiliary cap receipts, and structural store-call/candidate bounds. Structural
91
+ receipts record the complete closed StorePort method map, reject unexpected
92
+ methods, enforce per-method maxima, require candidate requests and returns to
93
+ stay within `3×` the output limit, and require returned candidates not to exceed
94
+ requested candidates. Latency is not a gate.
95
+
96
+ The committed artifacts are
97
+ `baseline/fixture-agent/project-affinity-promotion.json` and `.md`. They contain
98
+ only GNO evidence URIs, hashes, scores, raw filter/regression/zero/auxiliary/
99
+ structural receipts, implementation provenance, and redacted aliases—never
100
+ temporary roots, raw project hints, or absolute paths. An independent validator
101
+ rebinds fixture identities, recomputes summaries/gates/fingerprint from those
102
+ receipts, and compares the committed artifact with a fresh deterministic
103
+ production run. The controlled synthetic lane isolates the score seam and makes
104
+ no general workload superiority claim.
105
+
71
106
  The first fixture version contains 24 original synthetic tasks and 34 Markdown
72
107
  documents under the MIT license. It covers exact identifiers, ambiguity,
73
108
  multi-document comparisons, meeting decisions, temporal questions, typed
package/spec/evals.md CHANGED
@@ -27,6 +27,12 @@ Agent-level evidence sufficiency, exact citation coverage, stopping behavior,
27
27
  and Context Capsule promotion use the separate deterministic
28
28
  [Agentic Retrieval Evaluation Contract](evals-agentic.md).
29
29
 
30
+ That contract also owns a separate closed project-affinity promotion artifact.
31
+ It reuses the immutable 24-task fixture identities while leaving the main task
32
+ inventory and report schema unchanged. The gate records measured top-1,
33
+ evidence, multilingual, hard-filter, zero-lane, score-cap, and structural-call
34
+ outcomes; it does not use a wall-clock threshold.
35
+
30
36
  ## Dependencies
31
37
 
32
38
  ```json
package/spec/mcp.md CHANGED
@@ -218,6 +218,10 @@ Optional fields are `collection`, `limit` (default 5), `minScore`, `lang`,
218
218
  `since`, `until`, `categories`, `author`, `graph`, `noGraph`, `noRerank`,
219
219
  `maxAnswerTokens`, `contextBudgetTokens`, and `contextBudgetBytes`. Input
220
220
  objects are closed.
221
+ `projectHints` is an optional array of at most 16 non-empty caller hints. Hints
222
+ are normalized and deduplicated as opaque values, never resolved against or
223
+ reflected from the MCP server filesystem, and therefore have zero ranking
224
+ effect on the remote channel.
221
225
 
222
226
  `structuredContent` uses the
223
227
  [`ask`](./output-schemas/ask.schema.json) contract. Its `verification` object
@@ -273,6 +277,9 @@ validated before retrieval. `limit` and `candidateLimit` are global across all
273
277
  requested collections: result admission is capped after merging, and
274
278
  rerank/graph candidate work is distributed deterministically in canonical
275
279
  collection order.
280
+ `projectHints` is also accepted as an optional array of at most 16 opaque
281
+ caller hints. It follows the same remote zero-affinity, non-probing, and
282
+ non-reflection contract as all MCP retrieval tools.
276
283
 
277
284
  `structuredContent` is the complete canonical Context Capsule object for
278
285
  application clients. Model-visible text is always one deterministic
@@ -338,6 +345,13 @@ evidence reads. `structuredContent` is the canonical verification receipt.
338
345
 
339
346
  BM25 keyword search over indexed documents.
340
347
 
348
+ All retrieval input schemas (`gno_search`, `gno_vsearch`, `gno_query`, and
349
+ `gno_query_diagnose`) additionally accept optional `projectHints: string[]`
350
+ (maximum 16). Values are trimmed, NFC-normalized, deduplicated, and treated as
351
+ opaque remote hints. The server never stats, realpaths, discovers repositories,
352
+ or infers its cwd from them; remote hints produce redacted zero-affinity
353
+ metadata and no ranking change.
354
+
341
355
  **Input Schema:**
342
356
 
343
357
  ```json
@@ -745,6 +759,10 @@ Structured content includes `schemaVersion`, normalized `query`, `target`
745
759
  metadata/status (`not_found`, `inactive`, `no_indexed_content`,
746
760
  `filtered_out`, or `diagnosed`), `stages` for BM25/vector/fusion/graph/rerank,
747
761
  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.
748
766
 
749
767
  Use when an expected target is missing from `gno_query`, when filters may have
750
768
  excluded it, or when an agent needs evidence before raising `candidateLimit`,
@@ -0,0 +1,123 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/query-diagnose@1.0",
4
+ "title": "GNO Query Diagnose",
5
+ "type": "object",
6
+ "required": ["schemaVersion", "query", "target", "stages", "chunk", "meta"],
7
+ "properties": {
8
+ "schemaVersion": { "type": "string", "const": "1.0" },
9
+ "query": { "type": "string" },
10
+ "target": {
11
+ "type": "object",
12
+ "required": [
13
+ "ref",
14
+ "status",
15
+ "docid",
16
+ "uri",
17
+ "title",
18
+ "contentType",
19
+ "contentTypeSource",
20
+ "categories",
21
+ "graphHints",
22
+ "contentTypeRulesFingerprint",
23
+ "contentTypeFingerprintMatches",
24
+ "mirrorHash",
25
+ "chunkCount",
26
+ "filterReasons"
27
+ ],
28
+ "properties": {
29
+ "ref": { "type": "string" },
30
+ "status": {
31
+ "type": "string",
32
+ "enum": [
33
+ "not_found",
34
+ "inactive",
35
+ "no_indexed_content",
36
+ "filtered_out",
37
+ "diagnosed"
38
+ ]
39
+ },
40
+ "docid": { "type": ["string", "null"], "pattern": "^#[a-f0-9]{6,}$" },
41
+ "uri": { "type": ["string", "null"], "format": "uri" },
42
+ "title": { "type": ["string", "null"] },
43
+ "contentType": { "type": ["string", "null"] },
44
+ "contentTypeSource": { "type": ["string", "null"] },
45
+ "categories": { "type": "array", "items": { "type": "string" } },
46
+ "graphHints": { "type": "array", "items": { "type": "string" } },
47
+ "contentTypeRulesFingerprint": { "type": ["string", "null"] },
48
+ "contentTypeFingerprintMatches": { "type": ["boolean", "null"] },
49
+ "mirrorHash": { "type": ["string", "null"] },
50
+ "chunkCount": { "type": "integer", "minimum": 0 },
51
+ "filterReasons": { "type": "array", "items": { "type": "string" } }
52
+ },
53
+ "additionalProperties": false
54
+ },
55
+ "stages": {
56
+ "type": "array",
57
+ "items": {
58
+ "type": "object",
59
+ "required": [
60
+ "id",
61
+ "status",
62
+ "sourceCount",
63
+ "present",
64
+ "rank",
65
+ "score",
66
+ "survived",
67
+ "dropReason"
68
+ ],
69
+ "properties": {
70
+ "id": {
71
+ "type": "string",
72
+ "enum": ["bm25", "vector", "fusion", "graph", "rerank"]
73
+ },
74
+ "status": { "type": "string", "enum": ["active", "skipped"] },
75
+ "sourceCount": { "type": "integer", "minimum": 0 },
76
+ "present": { "type": "boolean" },
77
+ "rank": { "type": ["integer", "null"], "minimum": 1 },
78
+ "score": { "type": ["number", "null"] },
79
+ "survived": { "type": "boolean" },
80
+ "dropReason": {
81
+ "type": ["string", "null"],
82
+ "enum": ["not_in_candidate_set", "below_cutoff", "skipped", null]
83
+ },
84
+ "reason": { "type": "string" }
85
+ },
86
+ "additionalProperties": false
87
+ }
88
+ },
89
+ "chunk": {
90
+ "type": "object",
91
+ "required": ["seq", "startLine", "endLine", "language"],
92
+ "properties": {
93
+ "seq": { "type": ["integer", "null"], "minimum": 0 },
94
+ "startLine": { "type": ["integer", "null"], "minimum": 1 },
95
+ "endLine": { "type": ["integer", "null"], "minimum": 1 },
96
+ "language": { "type": ["string", "null"] }
97
+ },
98
+ "additionalProperties": false
99
+ },
100
+ "meta": {
101
+ "type": "object",
102
+ "required": ["mode", "vectorsUsed", "reranked", "totalResults"],
103
+ "properties": {
104
+ "mode": { "type": "string", "enum": ["bm25_only", "hybrid"] },
105
+ "vectorsUsed": { "type": "boolean" },
106
+ "reranked": { "type": "boolean" },
107
+ "totalResults": { "type": "integer", "minimum": 0 },
108
+ "queryModes": {
109
+ "type": "object",
110
+ "required": ["term", "intent", "hyde"],
111
+ "properties": {
112
+ "term": { "type": "integer", "minimum": 0 },
113
+ "intent": { "type": "integer", "minimum": 0 },
114
+ "hyde": { "type": "boolean" }
115
+ },
116
+ "additionalProperties": false
117
+ }
118
+ },
119
+ "additionalProperties": false
120
+ }
121
+ },
122
+ "additionalProperties": false
123
+ }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "gno://schemas/query-diagnose@1.0",
3
+ "$id": "gno://schemas/query-diagnose@1.1",
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", "const": "1.0" },
8
+ "schemaVersion": { "type": "string", "enum": ["1.0", "1.1"] },
9
9
  "query": { "type": "string" },
10
10
  "target": {
11
11
  "type": "object",
@@ -86,6 +86,74 @@
86
86
  "additionalProperties": false
87
87
  }
88
88
  },
89
+ "affinity": {
90
+ "type": "object",
91
+ "required": [
92
+ "affinityAdjustedScore",
93
+ "affinityApplied",
94
+ "affinityRequested",
95
+ "affinityWeight",
96
+ "baseScore",
97
+ "collectionAlias",
98
+ "combinedAuxiliaryApplied",
99
+ "combinedAuxiliaryCap",
100
+ "combinedAuxiliaryRequested",
101
+ "finalBlendedScore",
102
+ "finalScore",
103
+ "matched",
104
+ "rawScore",
105
+ "rawScoreKind",
106
+ "rootAlias",
107
+ "source"
108
+ ],
109
+ "properties": {
110
+ "affinityAdjustedScore": {
111
+ "type": "number",
112
+ "minimum": 0,
113
+ "maximum": 1
114
+ },
115
+ "affinityApplied": { "type": "number" },
116
+ "affinityRequested": { "type": "number" },
117
+ "affinityWeight": {
118
+ "type": "number",
119
+ "minimum": 0,
120
+ "maximum": 0.03
121
+ },
122
+ "baseScore": { "type": "number", "minimum": 0, "maximum": 1 },
123
+ "collectionAlias": {
124
+ "type": ["string", "null"],
125
+ "pattern": "^collection_[a-f0-9]{12}$"
126
+ },
127
+ "combinedAuxiliaryApplied": {
128
+ "type": "number",
129
+ "minimum": -0.08,
130
+ "maximum": 0.08
131
+ },
132
+ "combinedAuxiliaryCap": { "type": "number", "const": 0.08 },
133
+ "combinedAuxiliaryRequested": { "type": "number" },
134
+ "finalBlendedScore": {
135
+ "type": "number",
136
+ "minimum": 0,
137
+ "maximum": 1
138
+ },
139
+ "finalScore": { "type": "number", "minimum": 0, "maximum": 1 },
140
+ "matched": { "type": "boolean" },
141
+ "rawScore": { "type": "number" },
142
+ "rawScoreKind": {
143
+ "type": "string",
144
+ "enum": ["bm25", "hybrid_blended", "normalized", "vector_distance"]
145
+ },
146
+ "rootAlias": {
147
+ "type": ["string", "null"],
148
+ "pattern": "^root_[a-f0-9]{12}$"
149
+ },
150
+ "source": {
151
+ "type": ["string", "null"],
152
+ "enum": ["cli_cwd", "cli_explicit", "cli_worktree", null]
153
+ }
154
+ },
155
+ "additionalProperties": false
156
+ },
89
157
  "chunk": {
90
158
  "type": "object",
91
159
  "required": ["seq", "startLine", "endLine", "language"],
@@ -119,5 +187,24 @@
119
187
  "additionalProperties": false
120
188
  }
121
189
  },
190
+ "allOf": [
191
+ {
192
+ "if": {
193
+ "properties": { "schemaVersion": { "const": "1.0" } },
194
+ "required": ["schemaVersion"]
195
+ },
196
+ "then": { "properties": { "affinity": false } }
197
+ },
198
+ {
199
+ "if": {
200
+ "properties": { "schemaVersion": { "const": "1.1" } },
201
+ "required": ["schemaVersion"]
202
+ },
203
+ "then": {
204
+ "properties": { "affinity": {} },
205
+ "required": ["affinity"]
206
+ }
207
+ }
208
+ ],
122
209
  "additionalProperties": false
123
210
  }
@@ -4,6 +4,7 @@ import type { ContextEvidenceCompilerDeps } from "../core/context-evidence";
4
4
  import type { ContextVerifierDeps } from "../core/context-verifier";
5
5
  import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
6
6
  import type { EmbeddingPort, RerankPort } from "../llm/types";
7
+ import type { ProjectAffinityScoringInput } from "../pipeline/project-affinity";
7
8
  import type { QueryModeInput } from "../pipeline/types";
8
9
  import type { StorePort } from "../store/types";
9
10
  import type { VectorIndexPort } from "../store/vector";
@@ -50,6 +51,8 @@ export interface ContextCapsuleRuntimeDeps {
50
51
  countTokens?: (accountingJson: string) => number;
51
52
  tokenizerFingerprint?: string | null;
52
53
  resolveCurrentRanks?: ContextVerifierDeps["resolveCurrentRanks"];
54
+ /** Trusted, already-resolved project affinity; never accepts raw inputs. */
55
+ projectAffinity?: ProjectAffinityScoringInput;
53
56
  /** Optional non-canonical receipt session owned by the calling surface. */
54
57
  traceSession?: RetrievalTraceSession;
55
58
  }
@@ -96,6 +96,7 @@ export const buildContextCapsule = async (
96
96
  {
97
97
  ...request,
98
98
  noRerank: requestNoRerank,
99
+ projectAffinity: deps.projectAffinity,
99
100
  traceSession: deps.traceSession,
100
101
  }
101
102
  );
@@ -44,6 +44,7 @@ export const contextBuildSurfaceSchema = z
44
44
  safetyMarginTokens: nonnegativeInteger.optional(),
45
45
  safetyMarginBytes: nonnegativeInteger.optional(),
46
46
  depthPolicy: z.enum(["fast", "balanced", "thorough"]).optional(),
47
+ projectHints: z.array(z.string()).max(16).optional(),
47
48
  format: z.enum(["json", "md"]).optional(),
48
49
  })
49
50
  .strict();
@@ -60,6 +61,7 @@ export type ContextSurfaceFormat = "json" | "md";
60
61
  export interface ParsedContextBuildSurfaceInput {
61
62
  input: ContextCapsuleBuildInput;
62
63
  format: ContextSurfaceFormat;
64
+ projectHints?: string[];
63
65
  }
64
66
 
65
67
  export interface ParsedContextVerifySurfaceInput {
@@ -117,8 +119,8 @@ export const parseContextBuildSurfaceInput = (
117
119
  ): ParsedContextBuildSurfaceInput => {
118
120
  const parsed = contextBuildSurfaceSchema.safeParse(value);
119
121
  if (!parsed.success) throw invalidInput(parsed.error);
120
- const { format = "json", ...input } = parsed.data;
121
- return { input: { ...input, indexName }, format };
122
+ const { format = "json", projectHints, ...input } = parsed.data;
123
+ return { input: { ...input, indexName }, format, projectHints };
122
124
  };
123
125
 
124
126
  export const parseContextVerifySurfaceInput = (