@gmickel/gno 2.12.2 → 2.14.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 (75) hide show
  1. package/README.md +1 -1
  2. package/assets/skill/SKILL.md +36 -5
  3. package/assets/skill/cli-reference.md +62 -11
  4. package/assets/skill/examples.md +30 -4
  5. package/assets/skill/mcp-reference.md +16 -0
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/{gno-browser-clipper-v2.12.2.zip → gno-browser-clipper-v2.14.0.zip} +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.14.0.zip.sha256 +1 -0
  9. package/browser-extension/dist/manifest.json +1 -1
  10. package/package.json +1 -1
  11. package/spec/cli.md +139 -16
  12. package/spec/mcp.md +111 -3
  13. package/spec/output-schemas/collection-list.schema.json +28 -0
  14. package/spec/output-schemas/convert.schema.json +97 -0
  15. package/spec/output-schemas/ls.schema.json +65 -0
  16. package/spec/output-schemas/mcp-job-status.schema.json +17 -1
  17. package/spec/output-schemas/mcp-sync-result.schema.json +9 -2
  18. package/spec/output-schemas/search-results.schema.json +7 -0
  19. package/src/cli/commands/index-cmd.ts +50 -12
  20. package/src/cli/commands/ls.ts +20 -3
  21. package/src/cli/commands/profile-apply.ts +2 -1
  22. package/src/cli/commands/query.ts +21 -8
  23. package/src/cli/commands/search.ts +11 -0
  24. package/src/cli/commands/vsearch.ts +17 -4
  25. package/src/cli/options.ts +19 -0
  26. package/src/cli/program.ts +90 -10
  27. package/src/config/loader.ts +11 -1
  28. package/src/config/types.ts +90 -1
  29. package/src/core/audit-collection-rules.ts +331 -0
  30. package/src/core/audit-links.ts +3 -1
  31. package/src/core/audit-workspace.ts +69 -5
  32. package/src/core/collection-egress-policy-service.ts +4 -1
  33. package/src/core/collection-scope.ts +105 -0
  34. package/src/core/egress-enforcement.ts +20 -4
  35. package/src/core/egress-policy.ts +4 -0
  36. package/src/core/egress-provenance.ts +33 -11
  37. package/src/core/errors.ts +5 -0
  38. package/src/core/job-manager.ts +238 -19
  39. package/src/core/project-profile-apply-state.ts +6 -15
  40. package/src/core/project-profile-apply.ts +32 -1
  41. package/src/core/reindex-paths.ts +167 -0
  42. package/src/core/retrieval-trace-request.ts +1 -0
  43. package/src/ingestion/convert-file.ts +181 -0
  44. package/src/llm/registry.ts +20 -0
  45. package/src/mcp/http-egress.ts +21 -2
  46. package/src/mcp/tools/collection-scope.ts +25 -0
  47. package/src/mcp/tools/index-cmd.ts +66 -48
  48. package/src/mcp/tools/index.ts +74 -10
  49. package/src/mcp/tools/job-status.ts +7 -0
  50. package/src/mcp/tools/query.ts +28 -7
  51. package/src/mcp/tools/search.ts +4 -0
  52. package/src/mcp/tools/sync.ts +68 -60
  53. package/src/mcp/tools/vsearch.ts +9 -3
  54. package/src/pipeline/filters.ts +48 -5
  55. package/src/pipeline/graph-retrieval.ts +1 -0
  56. package/src/pipeline/hybrid.ts +15 -2
  57. package/src/pipeline/hydration.ts +7 -2
  58. package/src/pipeline/owner-fusion.ts +1 -0
  59. package/src/pipeline/search.ts +2 -0
  60. package/src/pipeline/types.ts +7 -0
  61. package/src/pipeline/vsearch.ts +10 -5
  62. package/src/sdk/client.ts +45 -12
  63. package/src/sdk/convert.ts +27 -0
  64. package/src/sdk/documents.ts +15 -2
  65. package/src/sdk/index.ts +2 -0
  66. package/src/sdk/types.ts +4 -1
  67. package/src/serve/jobs.ts +4 -1
  68. package/src/serve/public/components/IndexingProgress.tsx +25 -3
  69. package/src/serve/routes/api.ts +164 -24
  70. package/src/store/sqlite/adapter.ts +97 -27
  71. package/src/store/sqlite/eligibility.ts +4 -0
  72. package/src/store/types.ts +7 -0
  73. package/src/store/vector/sqlite-vec.ts +67 -15
  74. package/src/store/vector/variant-search.ts +41 -4
  75. package/browser-extension/artifacts/gno-browser-clipper-v2.12.2.zip.sha256 +0 -1
package/spec/mcp.md CHANGED
@@ -230,6 +230,50 @@ All write tools acquire an OS-backed advisory lock at `.mcp-write.lock` under th
230
230
  If another process holds the lock, tools return `LOCKED`.
231
231
  For async jobs, the lock is held for the full job duration.
232
232
 
233
+ ### Reindex Queue
234
+
235
+ `gno_sync` and `gno_index` (and REST `POST /api/sync`) never reject a request
236
+ because another job in the same server is running:
237
+
238
+ - With no job running or waiting, the request starts at once (`status:
239
+ "started"`), taking the write lock as above; another process holding it
240
+ returns `LOCKED`.
241
+ - Otherwise it is queued (`status: "queued"`) and starts when the jobs ahead
242
+ of it finish, taking the write lock then. A queued job that cannot get the
243
+ lock fails with `LOCKED` in its job status.
244
+ - A request joins a waiting job of the same tool and collection and returns
245
+ its `jobId`, so any number of requests cause at most one more pass:
246
+ - a request with `paths` joins any such job (its files are added to the
247
+ pass, or covered by a whole-collection pass);
248
+ - a whole-collection request joins a job that only has path requests
249
+ (widening it and setting its options) or a whole-collection job with the
250
+ same `gitPull` / `runUpdateCmd`. Whole-collection requests with different
251
+ options stay separate passes, and a path-only pass never runs `gitPull`
252
+ or an update command.
253
+ - a path-only job whose merged files pass 256 becomes a whole-collection
254
+ pass with no `gitPull` or update command.
255
+ - A queued pass uses the configuration of the latest request that joined it;
256
+ `gno_index` resolves its embedding model when the pass starts, so a model
257
+ or preset change while it waits is picked up.
258
+ - At most 64 distinct scopes wait at once; a new scope beyond that returns
259
+ `QUEUE_FULL`. Retry after the running job finishes.
260
+ - The queue lives in the server process. Jobs still waiting when it stops are
261
+ failed at shutdown and unknown after a restart (`NOT_FOUND` from
262
+ `gno_job_status`); the index stays consistent.
263
+
264
+ ### Path-Level Reindex
265
+
266
+ `paths` on `gno_sync` / `gno_index` reindexes only the listed files of one
267
+ collection: each path is relative to the collection root or absolute inside
268
+ it, and only those files are re-read. A listed file that no longer exists is
269
+ marked inactive. `paths` requires `collection` and cannot be combined with
270
+ `gitPull` or `runUpdateCmd`; an empty list, more than 256 paths, a
271
+ directory, a path outside the root (including through a symlink) or a
272
+ non-string entry is rejected before anything is queued (`INVALID_INPUT` /
273
+ `INVALID_PATH`), and so is a path the collection's own `pattern`, `include`
274
+ or `exclude` rules leave out. To reindex a directory or more than 256 files,
275
+ reindex the whole collection.
276
+
233
277
  `gno_remember` is the one write tool whose adapter takes no lock of its own:
234
278
  the core memory service acquires the same `.mcp-write.lock` lease for every
235
279
  memory write, so an MCP remember and a CLI writer serialise on one lease. A
@@ -294,6 +338,13 @@ compatible collection policy. Policy denial returns HTTP 403 with stable
294
338
  `EGRESS_DENIED` data and a redacted evaluator audit. Write authorization remains
295
339
  an independent additional requirement.
296
340
 
341
+ The check covers the request's collection scope: the collections it names, or
342
+ every configured collection when it names none. One decision covers at most 64
343
+ collections and one policy lineage at most 128. A scope over either bound is
344
+ denied with reason `COLLECTION_LIMIT_EXCEEDED` through the same 403 response,
345
+ whose audit names no collection; it is never an internal error. A request that
346
+ names a few collections is evaluated normally however many are configured.
347
+
297
348
  The MCP tool and resource registry is paired with a callsite-level
298
349
  network-boundary inventory. A TypeScript AST scan covers shipped TS, TSX, JS,
299
350
  and JSX and recognizes direct, qualified, bracketed, imported, and aliased
@@ -367,7 +418,10 @@ Rules report `pass`, `fail`, `skip`, `unavailable`, or `inconclusive`; reports
367
418
  are `complete`, `partial`, `changed_during_audit`, or `failed`. A passing rule
368
419
  may carry `info` findings (`links.outside-index`: link targets that exist as
369
420
  files in the link workspace but are not indexed); only `warning` and `error`
370
- findings fail a rule. Link semantics match `gno audit` (spec/cli.md). Unavailable or
421
+ findings fail a rule. Link semantics match `gno audit` (spec/cli.md),
422
+ including the opt-in `links.collection-policy`, `links.external-url` and
423
+ `links.path-layout` rules read from collection `audit` config; they add no
424
+ tool inputs. Unavailable or
371
425
  changing evidence never appears healthy. Stable finding IDs are derived from
372
426
  rule, normalized subject/location, and evidence fingerprint. Responses are
373
427
  canonically ordered and bounded while retaining exact pre-truncation totals.
@@ -603,6 +657,19 @@ opaque remote hints. The server never stats, realpaths, discovers repositories,
603
657
  or infers its cwd from them; remote hints produce redacted zero-affinity
604
658
  metadata and no ranking change.
605
659
 
660
+ `gno_search`, `gno_vsearch` and `gno_query` also accept an optional
661
+ `collections` allowlist. The filter applies before every candidate limit (BM25,
662
+ vector, fusion, graph expansion, hydration and rerank), so unlisted collections
663
+ never take result slots. Names are trimmed, lowercased, de-duplicated and
664
+ sorted; responses echo them as `meta.collections`, and `meta.collection` keeps
665
+ its single-collection meaning. Passing both `collection` and `collections`, an
666
+ empty list, a non-string member, or more than 256 names after normalization is
667
+ a `VALIDATION` error; unconfigured names give `Collection not found: <name>` /
668
+ `Collections not found: <a>, <b>`. Over HTTP the egress check covers exactly
669
+ the listed collections, so the most restrictive listed collection decides.
670
+ When the listed collections resolve different models for a role, that role uses
671
+ the unscoped default. `gno_query_diagnose` does not accept `collections`.
672
+
606
673
  **Input Schema:**
607
674
 
608
675
  ```json
@@ -617,6 +684,11 @@ metadata and no ranking change.
617
684
  "type": "string",
618
685
  "description": "Optional collection name to filter results"
619
686
  },
687
+ "collections": {
688
+ "type": "array",
689
+ "items": { "type": "string" },
690
+ "description": "Optional allowlist: filter to any of these collection names (1-256); use instead of collection"
691
+ },
620
692
  "limit": {
621
693
  "type": "integer",
622
694
  "description": "Maximum number of results (1-100)",
@@ -738,6 +810,11 @@ Vector semantic search over indexed documents.
738
810
  "type": "string",
739
811
  "description": "Optional collection name to filter results"
740
812
  },
813
+ "collections": {
814
+ "type": "array",
815
+ "items": { "type": "string" },
816
+ "description": "Optional allowlist: filter to any of these collection names (1-256); use instead of collection"
817
+ },
741
818
  "limit": {
742
819
  "type": "integer",
743
820
  "description": "Maximum number of results (1-100)",
@@ -822,6 +899,11 @@ Hybrid search combining BM25 and vector retrieval with optional expansion and re
822
899
  "type": "string",
823
900
  "description": "Optional collection name to filter results"
824
901
  },
902
+ "collections": {
903
+ "type": "array",
904
+ "items": { "type": "string" },
905
+ "description": "Optional allowlist: filter to any of these collection names (1-256); use instead of collection"
906
+ },
825
907
  "limit": {
826
908
  "type": "integer",
827
909
  "description": "Maximum number of results (1-100)",
@@ -2719,6 +2801,12 @@ Reindex one or all collections (write-enabled).
2719
2801
  "type": "boolean",
2720
2802
  "description": "Run updateCmd before indexing (default: false for MCP)",
2721
2803
  "default": false
2804
+ },
2805
+ "paths": {
2806
+ "type": "array",
2807
+ "items": { "type": "string" },
2808
+ "maxItems": 256,
2809
+ "description": "Reindex only these files of the collection (see Path-Level Reindex); requires collection"
2722
2810
  }
2723
2811
  }
2724
2812
  }
@@ -2726,6 +2814,9 @@ Reindex one or all collections (write-enabled).
2726
2814
 
2727
2815
  **Output Schema:** `gno://schemas/mcp-sync-result@1.0`
2728
2816
 
2817
+ `status` is `started` or `queued` (see [Reindex Queue](#reindex-queue));
2818
+ `paths` echoes the resolved collection-relative paths of a path-level sync.
2819
+
2729
2820
  ---
2730
2821
 
2731
2822
  ### gno_embed
@@ -2818,6 +2909,12 @@ Full index: sync files + generate embeddings (write-enabled). Runs as background
2818
2909
  "type": "boolean",
2819
2910
  "description": "Run git pull before sync",
2820
2911
  "default": false
2912
+ },
2913
+ "paths": {
2914
+ "type": "array",
2915
+ "items": { "type": "string" },
2916
+ "maxItems": 256,
2917
+ "description": "Reindex only these files of the collection (see Path-Level Reindex); requires collection"
2821
2918
  }
2822
2919
  }
2823
2920
  }
@@ -2852,6 +2949,7 @@ Full index: sync files + generate embeddings (write-enabled). Runs as background
2852
2949
 
2853
2950
  - Requires `--enable-write` flag
2854
2951
  - Runs sync phase first, then embed phase
2952
+ - `status` is `started` or `queued` (see [Reindex Queue](#reindex-queue)); a path-level index adds `paths`
2855
2953
  - `runUpdateCmd` is always false for MCP (security)
2856
2954
  - Fails fast if embedding model not cached
2857
2955
  - Poll job status with `gno_job_status`
@@ -2902,6 +3000,11 @@ Get status of a background job.
2902
3000
 
2903
3001
  **Output Schema:** `gno://schemas/mcp-job-status@1.0`
2904
3002
 
3003
+ `status` is `queued` while a reindex job waits behind another job, then
3004
+ `running`, `completed` or `failed`. Reindex jobs also report `scope`: the
3005
+ collection (`null` for all) and the merged `paths` (`null` for the whole
3006
+ collection).
3007
+
2905
3008
  ---
2906
3009
 
2907
3010
  ### gno_list_jobs
@@ -3062,8 +3165,9 @@ Resource errors use standard MCP error responses.
3062
3165
  - `HAS_REFERENCES` — Collection referenced by contexts
3063
3166
  - `INVALID_PATH` — Path violates safety rules
3064
3167
  - `PATH_NOT_FOUND` — Path does not exist
3065
- - `JOB_CONFLICT` — Another job is already running
3168
+ - `JOB_CONFLICT` — Another job is already running (not returned by `gno_sync` / `gno_index`, which queue instead)
3066
3169
  - `LOCKED` — Another MCP process holds the write lock
3170
+ - `QUEUE_FULL` — Too many reindex scopes are already waiting; retry later
3067
3171
  - `CAPTURE_SYNC_FAILED` — `gno_capture` wrote the file but lexical sync failed
3068
3172
  - `WRITE_DISABLED` — Write tool dispatched while writes are disabled
3069
3173
  - `MEMORY_*` — Memory contract errors from `gno_recall` / `gno_remember`;
@@ -3106,6 +3210,7 @@ Clients should check schema version for compatibility.
3106
3210
  - No persistent state between tool calls
3107
3211
  - Each tool call is independent
3108
3212
  - Server handles concurrent tool calls sequentially; an accepted background job (`gno_sync`, `gno_embed`, `gno_index`) waits for the call in progress, then runs without holding that queue, so reads are not blocked while it runs
3213
+ - The retrieval tools (`gno_search`, `gno_vsearch`, `gno_query`, `gno_get`, `gno_multi_get`, `gno_status`, `gno_list_tags`, `gno_links`, `gno_backlinks`, `gno_similar`) read on a second connection, so they see only committed index data, never a sync batch still being written
3109
3214
 
3110
3215
  ---
3111
3216
 
@@ -3291,7 +3396,10 @@ Read tools:
3291
3396
  caller authentication/authorization, content class, and partial mode. It
3292
3397
  performs no action and returns the canonical decision/explanation contract.
3293
3398
  An explicit scope is a non-empty, duplicate-free list of at most 64 canonical
3294
- collection names; omitted scope means all configured collections.
3399
+ collection names; omitted scope means all configured collections. Omitted
3400
+ scope with more than 64 configured collections returns a denied decision
3401
+ with reason `COLLECTION_LIMIT_EXCEEDED`; with more than 128 it returns a
3402
+ `VALIDATION` error asking for named collections.
3295
3403
  - `gno_egress_audit_list`, `gno_egress_audit_show`, and
3296
3404
  `gno_egress_audit_status` expose only content-free local receipts.
3297
3405
 
@@ -57,6 +57,34 @@
57
57
  "type": "string",
58
58
  "enum": ["none", "detected", "configured", "disabled", "unavailable"],
59
59
  "description": "CLI list only: how the link workspace was set"
60
+ },
61
+ "audit": {
62
+ "type": "object",
63
+ "description": "Opt-in report-only link and path rules for gno audit links. Absent when not configured.",
64
+ "additionalProperties": false,
65
+ "properties": {
66
+ "linkCollections": {
67
+ "type": "array",
68
+ "description": "Other collections this one may link to; its own is always allowed",
69
+ "maxItems": 256,
70
+ "items": {
71
+ "type": "string",
72
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
73
+ }
74
+ },
75
+ "externalUrls": {
76
+ "type": "string",
77
+ "enum": ["report", "ignore"],
78
+ "description": "report lists external URLs written in documents"
79
+ },
80
+ "paths": {
81
+ "type": "array",
82
+ "description": "Relative globs; each document path must match at least one",
83
+ "minItems": 1,
84
+ "maxItems": 64,
85
+ "items": { "type": "string", "maxLength": 1024 }
86
+ }
87
+ }
60
88
  }
61
89
  }
62
90
  }
@@ -0,0 +1,97 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/convert@1.0",
4
+ "title": "GNO Convert Result",
5
+ "description": "Output of gno convert --json and SDK convertFile: the Markdown GNO would index for one file",
6
+ "type": "object",
7
+ "required": [
8
+ "schemaVersion",
9
+ "source",
10
+ "converter",
11
+ "title",
12
+ "languageHint",
13
+ "markdown",
14
+ "mirrorHash",
15
+ "warnings"
16
+ ],
17
+ "additionalProperties": false,
18
+ "properties": {
19
+ "schemaVersion": {
20
+ "const": "convert@1.0"
21
+ },
22
+ "source": {
23
+ "type": "object",
24
+ "required": ["path", "mime", "ext", "sizeBytes"],
25
+ "additionalProperties": false,
26
+ "properties": {
27
+ "path": {
28
+ "type": "string",
29
+ "minLength": 1,
30
+ "description": "Absolute path of the converted file"
31
+ },
32
+ "mime": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "description": "Detected MIME type"
36
+ },
37
+ "ext": {
38
+ "type": "string",
39
+ "description": "File extension, including the dot"
40
+ },
41
+ "sizeBytes": {
42
+ "type": "integer",
43
+ "minimum": 0
44
+ }
45
+ }
46
+ },
47
+ "converter": {
48
+ "type": "object",
49
+ "required": ["id", "version"],
50
+ "additionalProperties": false,
51
+ "properties": {
52
+ "id": {
53
+ "type": "string",
54
+ "minLength": 1
55
+ },
56
+ "version": {
57
+ "type": "string",
58
+ "minLength": 1
59
+ }
60
+ }
61
+ },
62
+ "title": {
63
+ "type": ["string", "null"],
64
+ "description": "Title from conversion, or null"
65
+ },
66
+ "languageHint": {
67
+ "type": ["string", "null"],
68
+ "description": "BCP-47 language hint from conversion, or null"
69
+ },
70
+ "markdown": {
71
+ "type": "string",
72
+ "description": "Canonical Markdown, identical to the mirror indexing stores"
73
+ },
74
+ "mirrorHash": {
75
+ "type": "string",
76
+ "pattern": "^[a-f0-9]{64}$",
77
+ "description": "SHA-256 hex of the canonical Markdown, as stored by indexing"
78
+ },
79
+ "warnings": {
80
+ "type": "array",
81
+ "items": {
82
+ "type": "object",
83
+ "required": ["code", "message"],
84
+ "additionalProperties": false,
85
+ "properties": {
86
+ "code": {
87
+ "type": "string",
88
+ "minLength": 1
89
+ },
90
+ "message": {
91
+ "type": "string"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/ls@1.0",
4
+ "title": "GNO Document Listing",
5
+ "description": "Output of gno ls --json and SDK list(): one page of active documents in URI order",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["documents", "meta"],
9
+ "properties": {
10
+ "documents": {
11
+ "type": "array",
12
+ "items": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["docid", "uri", "source"],
16
+ "properties": {
17
+ "docid": { "type": "string", "pattern": "^#[a-f0-9]+$" },
18
+ "uri": { "type": "string", "pattern": "^gno://" },
19
+ "title": { "type": "string" },
20
+ "source": {
21
+ "type": "object",
22
+ "additionalProperties": false,
23
+ "required": ["relPath", "mime", "ext"],
24
+ "properties": {
25
+ "relPath": { "type": "string" },
26
+ "mime": { "type": "string" },
27
+ "ext": { "type": "string" }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ },
33
+ "meta": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "required": ["total", "returned", "offset"],
37
+ "properties": {
38
+ "total": {
39
+ "type": "integer",
40
+ "minimum": 0,
41
+ "description": "Documents in scope that match the filter, before pagination"
42
+ },
43
+ "returned": { "type": "integer", "minimum": 0 },
44
+ "offset": { "type": "integer", "minimum": 0 },
45
+ "warnings": {
46
+ "type": "array",
47
+ "description": "Present only with a typed metadata filter when some documents could not be judged",
48
+ "minItems": 1,
49
+ "items": {
50
+ "type": "object",
51
+ "additionalProperties": false,
52
+ "required": ["code", "message"],
53
+ "properties": {
54
+ "code": {
55
+ "type": "string",
56
+ "enum": ["METADATA_COVERAGE_INCOMPLETE"]
57
+ },
58
+ "message": { "type": "string" }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
@@ -223,7 +223,7 @@
223
223
  "status": {
224
224
  "type": "string",
225
225
  "description": "Job status",
226
- "enum": ["running", "completed", "failed"]
226
+ "enum": ["queued", "running", "completed", "failed"]
227
227
  },
228
228
  "startedAt": {
229
229
  "type": "string",
@@ -247,6 +247,22 @@
247
247
  "type": "string",
248
248
  "description": "Error message if failed"
249
249
  },
250
+ "scope": {
251
+ "type": "object",
252
+ "description": "Reindex jobs: what the job covers after merging requests",
253
+ "required": ["collection", "paths"],
254
+ "properties": {
255
+ "collection": {
256
+ "type": ["string", "null"],
257
+ "description": "Collection name, or null for all collections"
258
+ },
259
+ "paths": {
260
+ "type": ["array", "null"],
261
+ "description": "Collection-relative files, or null for the whole collection",
262
+ "items": { "type": "string" }
263
+ }
264
+ }
265
+ },
250
266
  "serverInstanceId": {
251
267
  "type": "string",
252
268
  "description": "Server instance UUID",
@@ -19,8 +19,15 @@
19
19
  },
20
20
  "status": {
21
21
  "type": "string",
22
- "description": "Job status",
23
- "enum": ["started"]
22
+ "description": "started: the job is running; queued: it waits behind another write job (requests for the same scope share one queued job)",
23
+ "enum": ["started", "queued"]
24
+ },
25
+ "paths": {
26
+ "type": "array",
27
+ "description": "Collection-relative files this sync reindexes; absent for a whole-collection sync",
28
+ "items": {
29
+ "type": "string"
30
+ }
24
31
  },
25
32
  "options": {
26
33
  "type": "object",
@@ -454,6 +454,13 @@
454
454
  "type": "string",
455
455
  "description": "Collection filter if applied"
456
456
  },
457
+ "collections": {
458
+ "type": "array",
459
+ "items": { "type": "string" },
460
+ "minItems": 1,
461
+ "maxItems": 256,
462
+ "description": "Normalized collection allowlist if applied (sorted, lowercase)"
463
+ },
457
464
  "intent": {
458
465
  "type": "string",
459
466
  "description": "Disambiguating context used to steer retrieval without being searched directly"
@@ -7,6 +7,7 @@
7
7
  * @module src/cli/commands/indexCmd
8
8
  */
9
9
 
10
+ import { resolveReindexScope } from "../../core/reindex-paths";
10
11
  import {
11
12
  type CliWriteLeaseOptions,
12
13
  type WriteLeaseContention,
@@ -27,6 +28,7 @@ import {
27
28
  type SyncResult,
28
29
  withContentTypeRules,
29
30
  } from "../../ingestion";
31
+ import { CliError } from "../errors";
30
32
  import { type EmbedResult, embedStageOutcome } from "./embed";
31
33
  import {
32
34
  formatSyncResultLines,
@@ -44,6 +46,8 @@ export interface IndexOptions extends CliWriteLeaseOptions {
44
46
  indexName?: string;
45
47
  /** Scope to single collection */
46
48
  collection?: string;
49
+ /** Reindex only these files of `collection` */
50
+ paths?: string[];
47
51
  /** Run ingestion only, skip embedding */
48
52
  noEmbed?: boolean;
49
53
  /** Download models if missing */
@@ -186,6 +190,18 @@ export async function index(options: IndexOptions = {}): Promise<IndexResult> {
186
190
  }
187
191
 
188
192
  const { store, collections, config } = initResult;
193
+ let paths: string[] | null;
194
+ try {
195
+ paths = await resolveReindexScope(
196
+ options.collection ? (collections[0] ?? null) : null,
197
+ options.paths,
198
+ { gitPull: options.gitPull }
199
+ );
200
+ } catch (e) {
201
+ await store.close();
202
+ const message = e instanceof Error ? e.message : String(e);
203
+ throw new CliError("VALIDATION", message.replace(/^[A-Z_]+: /, ""));
204
+ }
189
205
  const db = store.getRawDb();
190
206
  const embedSkipped = options.noEmbed ?? false;
191
207
 
@@ -202,18 +218,40 @@ export async function index(options: IndexOptions = {}): Promise<IndexResult> {
202
218
  markIndexStageRunning(db, "lexical", { collection: options.collection });
203
219
  let syncResult: SyncResult;
204
220
  try {
205
- syncResult = await defaultSyncService.syncAll(
206
- collections,
207
- store,
208
- withContentTypeRules(
209
- {
210
- gitPull: options.gitPull,
211
- runUpdateCmd: true,
212
- onSlowConversion: reportSlowConversion,
213
- },
214
- config
215
- )
216
- );
221
+ const collection = collections[0];
222
+ if (paths && collection) {
223
+ const pathResult = await defaultSyncService.syncPaths(
224
+ collection,
225
+ store,
226
+ paths,
227
+ withContentTypeRules(
228
+ { onSlowConversion: reportSlowConversion },
229
+ config
230
+ )
231
+ );
232
+ syncResult = {
233
+ collections: [pathResult],
234
+ totalDurationMs: pathResult.durationMs,
235
+ totalFilesProcessed: pathResult.filesProcessed,
236
+ totalFilesAdded: pathResult.filesAdded,
237
+ totalFilesUpdated: pathResult.filesUpdated,
238
+ totalFilesErrored: pathResult.filesErrored,
239
+ totalFilesSkipped: pathResult.filesSkipped,
240
+ };
241
+ } else {
242
+ syncResult = await defaultSyncService.syncAll(
243
+ collections,
244
+ store,
245
+ withContentTypeRules(
246
+ {
247
+ gitPull: options.gitPull,
248
+ runUpdateCmd: true,
249
+ onSlowConversion: reportSlowConversion,
250
+ },
251
+ config
252
+ )
253
+ );
254
+ }
217
255
  } catch (e) {
218
256
  const message = e instanceof Error ? e.message : String(e);
219
257
  markIndexStageFinished(db, "lexical", "failed");
@@ -5,9 +5,14 @@
5
5
  * @module src/cli/commands/ls
6
6
  */
7
7
 
8
+ import type { MetadataPredicate } from "../../core/typed-metadata";
8
9
  import type { DocumentRow, StorePort, StoreResult } from "../../store/types";
9
10
 
10
11
  import { decorateUriForIndex } from "../../app/constants";
12
+ import {
13
+ filterDocumentsByMetadata,
14
+ metadataCoverageWarnings,
15
+ } from "../../pipeline/filters";
11
16
  import { initStore } from "./shared";
12
17
 
13
18
  // ─────────────────────────────────────────────────────────────────────────────
@@ -23,6 +28,8 @@ export interface LsCommandOptions {
23
28
  limit?: number;
24
29
  /** Skip first N results */
25
30
  offset?: number;
31
+ /** Typed metadata predicate (same semantics as search --filter) */
32
+ filter?: MetadataPredicate;
26
33
  /** JSON output */
27
34
  json?: boolean;
28
35
  /** File protocol output */
@@ -48,6 +55,8 @@ export interface LsResponse {
48
55
  total: number;
49
56
  returned: number;
50
57
  offset: number;
58
+ /** Only with a filter, when documents could not be judged. */
59
+ warnings?: { code: string; message: string }[];
51
60
  };
52
61
  }
53
62
 
@@ -128,9 +137,16 @@ export async function ls(
128
137
  return { success: false, error: docs.error.message };
129
138
  }
130
139
 
131
- // Filter active only, sort by URI
132
- const allActive = docs.value
133
- .filter((d) => d.active)
140
+ const active = docs.value.filter((d) => d.active);
141
+ const filtered = options.filter
142
+ ? filterDocumentsByMetadata(active, options.filter)
143
+ : undefined;
144
+ const warnings = filtered
145
+ ? metadataCoverageWarnings(filtered, "listing")
146
+ : undefined;
147
+
148
+ // Sort by URI
149
+ const allActive = (filtered?.matches ?? active)
134
150
  .map((d) => ({
135
151
  docid: d.docid,
136
152
  uri: d.uri,
@@ -160,6 +176,7 @@ export async function ls(
160
176
  total: allActive.length,
161
177
  returned: paged.length,
162
178
  offset,
179
+ ...(warnings ? { warnings } : {}),
163
180
  },
164
181
  },
165
182
  };
@@ -267,7 +267,8 @@ export async function runProjectProfileApplyCommand(
267
267
  ];
268
268
  const validationFailure =
269
269
  applied.error.code === "PROFILE_INVALID" ||
270
- applied.error.code === "RUNTIME_PATH_OVERLAP";
270
+ applied.error.code === "RUNTIME_PATH_OVERLAP" ||
271
+ applied.error.code === "WORKSPACE_ROOT_CONFLICT";
271
272
  return {
272
273
  result: failedApplyResult(
273
274
  validationFailure ? "invalid" : "failed",