@gmickel/gno 2.13.0 → 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.
package/README.md CHANGED
@@ -139,7 +139,7 @@ See the [guide](docs/COMPILED-CONTEXT.md).
139
139
 
140
140
  <!-- public-truth:current-version -->
141
141
 
142
- > Current source version: **v2.13.0**. See [CHANGELOG.md](./CHANGELOG.md).
142
+ > Current source version: **v2.14.0**. See [CHANGELOG.md](./CHANGELOG.md).
143
143
 
144
144
  <!-- /public-truth -->
145
145
 
@@ -84,7 +84,7 @@ Recipe rules:
84
84
  | ------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
85
85
  | **Search** | `search`, `vsearch`, `query`, `ask` | Find documents by keywords, meaning, or get AI answers |
86
86
  | **Links** | `links`, `backlinks`, `similar`, `graph`, `graph query` | Navigate document relationships and typed connections |
87
- | **Retrieve** | `get`, `multi-get`, `ls` | Fetch document content by URI or ID |
87
+ | **Retrieve** | `get`, `multi-get`, `ls`, `ls --filter` | Fetch document content by URI or ID; list documents by typed metadata |
88
88
  | **Index** | `setup`, `profile check/show/diff/apply`, `init`, `collection add/list/remove`, `index`, `update`, `embed` | Reproduce profile intent, prove retrieval, then maintain the index |
89
89
  | **Tags** | `tags`, `tags add`, `tags rm` | Organize and filter documents |
90
90
  | **Context** | `context add/list/rm/check/build/verify/watch/watches/reverify/unwatch` | Configure guidance or compile, verify, and watch saved evidence Capsules |
@@ -154,7 +154,9 @@ machines are slower.
154
154
  exact identifier/quote/error you already have verbatim → `gno search` (a
155
155
  question, or a name or topic taken from it, is not exact text). No good results: run balanced
156
156
  `gno query`, then rephrase, then `--thorough`. An expected document still
157
- missing: `gno query diagnose` before falling back to grep.
157
+ missing: `gno query diagnose` before falling back to grep. When the user
158
+ explicitly asks for a semantic, vector or meaning-only search (their documents
159
+ use different wording), use `gno vsearch` instead.
158
160
 
159
161
  ## Common Flags (search/vsearch/query/ask)
160
162
 
@@ -345,7 +347,10 @@ confirmation and invalidates active sessions, streams, and queued work.
345
347
 
346
348
  Bearer authentication, MCP write enablement, and collection policy are
347
349
  independent gates. `EGRESS_DENIED` is not permission to retry through another
348
- surface. Audit output is local and content-free via
350
+ surface. The one exception is reason `COLLECTION_LIMIT_EXCEEDED`: the call
351
+ covered more than 64 collections (128 for lineage), so retry it scoped to the
352
+ collections the task needs (`collection`/`collections`, `-c`). It is not an
353
+ outage; never restart, raise a limit or relax policy for it. Audit output is local and content-free via
349
354
  `gno egress-audit list|show|status`; deletion/purge must be explicit. A local
350
355
  policy change cannot retract data already uploaded—tell the user to remove it
351
356
  at the remote service. For gno.sh, supported private links can be revoked or
@@ -371,6 +376,29 @@ never from code spans, code blocks, or non-Markdown files.
371
376
  Report `truncation.snapshotTruncated` as "totals cover the bounded snapshot,
372
377
  not the whole index".
373
378
 
379
+ A collection can opt into three report-only link rules with an `audit` block in
380
+ its config: `linkCollections` (other collections it may link to; its own is
381
+ always allowed), `externalUrls: report`, and `paths` (globs every document path
382
+ must match). Their findings are `warning`s: `links.collection-policy`,
383
+ `links.external-url` and `links.path-layout`. Report them as structure and
384
+ publishing hygiene, not access control: `links.external-url` only reports a
385
+ URL written in a note; it sends and blocks nothing, so the remedy is editing
386
+ the note or setting `externalUrls: ignore`, never a collection policy change.
387
+ Show which URLs were found with
388
+ `gno audit links --json | jq -r '.findings[] | select(.ruleId == "links.external-url") | "\(.subject) \(.location) \(.evidence[0].summary)"'`.
389
+ Never add an `audit` block on your own initiative. When the user asks for a
390
+ rule, there is no CLI flag: always write out the exact `audit` block for that
391
+ collection's entry in the config file, then run the check. For "published may
392
+ link only to reference":
393
+
394
+ ```bash
395
+ gno doctor # prints "Config loaded: <path>"
396
+ # In <path>, under the `published` collection entry, add:
397
+ # audit:
398
+ # linkCollections: [reference]
399
+ gno audit links -c published --json
400
+ ```
401
+
374
402
  Treat exit `4` as a complete report with findings. Exit `5` or report status
375
403
  `partial`/`changed_during_audit` means evidence is unavailable, inconclusive,
376
404
  cancelled, truncated, or repeatedly changed—never healthy. Preserve stable
@@ -753,7 +781,10 @@ fields must be indexed from nested YAML `gno.metadata`; arbitrary frontmatter
753
781
  keys are not automatically custom fields. Run `gno metadata --collection <c>`
754
782
  (MCP `gno_list_metadata`) first to see which keys, types and values exist.
755
783
  CLI retrieval accepts `--filter` JSON; MCP/SDK/REST accept the same `filter`
756
- object:
784
+ object. To enumerate documents by metadata with no search term (every
785
+ `status: draft` note), use `gno ls [<collection>] --filter '<json>'`, not a
786
+ search with a placeholder query; `--json` returns `{documents, meta}`, so read
787
+ `.documents[]` and `meta.total`:
757
788
 
758
789
  ```json
759
790
  {
@@ -510,9 +510,14 @@ gno multi-get <pattern> [--max-bytes <n>] [--line-numbers]
510
510
  List documents.
511
511
 
512
512
  ```bash
513
- gno ls [<scope>] [--json|--files|--md]
513
+ gno ls [<scope>] [--filter <json>] [-n <num>] [--offset <num>] [--json|--files|--md]
514
514
  ```
515
515
 
516
+ - `<scope>`: collection name or `gno://collection/prefix`
517
+ - `--filter <json>`: list only documents whose typed metadata (`gno.metadata`) matches; same predicate syntax and semantics as `gno search --filter`. Applies before pagination
518
+ - `--json` returns `{documents: [{docid, uri, title, source}], meta: {total, returned, offset}}`; `meta.total` counts all matches
519
+ - With `--filter`, `meta.warnings` (`METADATA_COVERAGE_INCOMPLETE`) appears when documents with invalid or pending metadata were skipped; other formats print it on stderr
520
+
516
521
  ## Context Management
517
522
 
518
523
  ### gno context add
@@ -653,6 +658,9 @@ gno egress-audit purge
653
658
  Denied egress is terminal for that route; do not retry through another network
654
659
  surface. Relaxing policy requires the current revision; tightening to
655
660
  `local_only` does not. Audit receipts are local and content-free.
661
+ `COLLECTION_LIMIT_EXCEEDED` is the one denial to retry: the request covered more
662
+ than 64 collections (128 for lineage), so name only the collections it needs
663
+ (`-c <name>`).
656
664
  Deletion/purge must remain explicit.
657
665
 
658
666
  ## Note Linking
@@ -849,6 +857,36 @@ gno models path [--json]
849
857
 
850
858
  ## Maintenance
851
859
 
860
+ ### gno audit
861
+
862
+ Read-only workspace integrity report. Exit 4 = complete with findings, 5 =
863
+ partial/inconclusive.
864
+
865
+ ```bash
866
+ gno audit [links|provenance|freshness|all] [-c <collection>] [--json]
867
+ gno audit links -c published --json
868
+ ```
869
+
870
+ Opt-in, report-only collection rules come from an `audit` block on a
871
+ collection in the config file (`gno doctor` shows its path); there are no CLI
872
+ flags for them:
873
+
874
+ ```yaml
875
+ collections:
876
+ - name: published
877
+ path: ~/notes/published
878
+ audit:
879
+ linkCollections: [reference] # other collections it may link to
880
+ externalUrls: report # report | ignore (default ignore)
881
+ paths: ["20??/**/*.md"] # globs every document path must match
882
+ ```
883
+
884
+ | Rule id | Reports |
885
+ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
886
+ | `links.collection-policy` | A link into a collection `linkCollections` does not list |
887
+ | `links.external-url` | A URL written in a document (outside frontmatter, code, comments), or `unchecked` when the text is unavailable. Not egress |
888
+ | `links.path-layout` | A document whose path matches none of the `paths` globs |
889
+
852
890
  ### gno peek
853
891
 
854
892
  Cheap counts, backlog, recent files, and serve liveness. Same `peek@1.0`
@@ -183,6 +183,17 @@ gno ls work
183
183
  gno ls --json
184
184
  ```
185
185
 
186
+ ### List by metadata
187
+
188
+ ```bash
189
+ # Every draft note in work (no search term needed)
190
+ gno ls work --filter '{"op":"eq","key":"status","value":"draft"}'
191
+
192
+ # URIs of every note whose project list contains atlas
193
+ gno ls work --filter '{"op":"in","key":"project","values":["atlas"]}' -n 1000 --json \
194
+ | jq -r '.documents[].uri'
195
+ ```
196
+
186
197
  ## Search & Retrieve Full Content
187
198
 
188
199
  ### Pipeline: Search then get
@@ -204,10 +215,10 @@ gno search "error handling" --json | jq -r '.results[].uri' | xargs gno multi-ge
204
215
 
205
216
  ```bash
206
217
  # Get URIs of matches
207
- gno search "api endpoint" --json | jq '.[] | .uri'
218
+ gno search "api endpoint" --json | jq '.results[] | .uri'
208
219
 
209
220
  # Get snippets
210
- gno search "config" --json | jq '.[] | {uri, snippet}'
221
+ gno search "config" --json | jq '.results[] | {uri, snippet}'
211
222
  ```
212
223
 
213
224
  ### Check index health
@@ -224,10 +235,10 @@ gno doctor --json
224
235
 
225
236
  ```bash
226
237
  # Export all document IDs
227
- gno ls --json | jq -r '.[].docid' > doc-ids.txt
238
+ gno ls --json -n 10000 | jq -r '.documents[].docid' > doc-ids.txt
228
239
 
229
240
  # Search and process results
230
- gno search "error" --json | jq -r '.[] | .uri' | while read uri; do
241
+ gno search "error" --json | jq -r '.results[] | .uri' | while read uri; do
231
242
  echo "Processing: $uri"
232
243
  gno get "$uri" > "output/$(basename $uri)"
233
244
  done
@@ -235,6 +246,21 @@ done
235
246
 
236
247
  ## Maintenance
237
248
 
249
+ ### Check a published collection's links
250
+
251
+ Let `published` link only to itself and `reference`: add this to the
252
+ `published` collection in the config file (`gno doctor` shows its path).
253
+
254
+ ```yaml
255
+ audit:
256
+ linkCollections: [reference]
257
+ ```
258
+
259
+ ```bash
260
+ gno audit links -c published --json \
261
+ | jq -r '.findings[] | select(.ruleId == "links.collection-policy") | "\(.subject) \(.location): \(.message)"'
262
+ ```
263
+
238
264
  ### Update index after changes
239
265
 
240
266
  ```bash
@@ -93,6 +93,14 @@ gno serve --status --json
93
93
  gno daemon --status --json
94
94
  ```
95
95
 
96
+ Each HTTP request is checked against the egress policy of the collections it
97
+ covers: the ones it names, or every configured collection when it names none.
98
+ One check covers at most 64 collections (128 for a policy lineage; 64 names for
99
+ `gno_egress_check`). A 403 whose reason is `COLLECTION_LIMIT_EXCEEDED` means the
100
+ call covered too many collections: retry with `collection` or `collections` set
101
+ to just the ones the task needs. It is not an outage, so do not restart the
102
+ server, raise a limit, change policy, or retry through another surface.
103
+
96
104
  ## Retrieval Order
97
105
 
98
106
  `gno mcp --tool-profile core` (or `gateway.toolProfile: core` for the resident
@@ -253,6 +261,12 @@ before calling.
253
261
  `not_found`, so do not blindly resend an uncertain old write as new.
254
262
  - Errors arrive as tool text `CODE: message` with `structuredContent.error`.
255
263
 
264
+ ## Audits
265
+
266
+ `gno_audit` returns the same report as `gno audit`. Collection `audit` config
267
+ adds `links.collection-policy`, `links.external-url` and `links.path-layout`
268
+ findings with no new tool inputs; they report structure, not egress.
269
+
256
270
  ## Uninstall
257
271
 
258
272
  ```bash
@@ -0,0 +1 @@
1
+ aca4d00878cb85d4f57181425caf0bb2175ecec0a5bc4ea437e994b8fd42c9cc gno-browser-clipper-v2.14.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": "2.13.0"
24
+ "version": "2.14.0"
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "2.13.0",
3
+ "version": "2.14.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
@@ -191,7 +191,11 @@ through a resumable operation. It can initialize a missing user config. Omitted
191
191
  collection-scoped contexts, and content-type rules remain untouched; stale
192
192
  same-path collections are reported as skipped and retained. A stale same-name
193
193
  collection root is repaired in place without deleting its collection or index
194
- identity. Apply also updates a timestamp-free `projectProfileBindings` record
194
+ identity. A re-applied collection keeps every setting the profile does not
195
+ own (for example `egressPolicy`, `egressPolicyRevision`, `sourceAvailability`,
196
+ `workspaceRoot`, `memoryManaged`, `audit` and `updateCmd`). When a repaired root
197
+ falls outside an explicit `workspaceRoot`, apply fails with
198
+ `WORKSPACE_ROOT_CONFLICT` (exit 1) before any config write. Apply also updates a timestamp-free `projectProfileBindings` record
195
199
  in the local user config with the canonical absolute profile path, profile
196
200
  fingerprint, and projected collection. The public `profile_binding` resource
197
201
  receipt identifies only the collection and never exposes that local path.
@@ -893,6 +897,12 @@ List all configured collections.
893
897
  gno collection list [--json|--md]
894
898
  ```
895
899
 
900
+ Each JSON item is the configured collection plus `effectiveWorkspaceRoot` and
901
+ `workspaceSource`. A collection with an `audit` block lists it unchanged
902
+ (`linkCollections`, `externalUrls`, `paths`); see
903
+ [gno audit](#gno-audit). Schema:
904
+ [`collection-list@1.0`](./output-schemas/collection-list.schema.json).
905
+
896
906
  ---
897
907
 
898
908
  ### gno collection clear-embeddings
@@ -1046,6 +1056,32 @@ target missing from the listing stays unresolved; an unreadable folder or the
1046
1056
  file bound makes the listing incomplete, which the `links.outside-index` rule
1047
1057
  message states once.
1048
1058
 
1059
+ **Opt-in collection rules.** A collection's optional `audit` config object
1060
+ adds three report-only `links` rules. Each is emitted only when an audited
1061
+ collection (the `--collection` filter, else every collection) configures it,
1062
+ so without `audit` the rule list, findings, fingerprints and exit codes are
1063
+ unchanged. The config fingerprint includes a collection's `audit` object only
1064
+ when present on an audited collection.
1065
+
1066
+ | Rule id | Enabled by | Reports |
1067
+ | ------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------- |
1068
+ | `links.collection-policy` | `audit.linkCollections` | A link that resolves into a collection the list does not name |
1069
+ | `links.external-url` | `audit.externalUrls: report` | An external URL written in a document, or a document whose indexed text is unavailable (`unchecked`) |
1070
+ | `links.path-layout` | `audit.paths` | A document whose collection-relative path matches none of the globs |
1071
+
1072
+ Findings are `warning`s with location evidence: `collection-policy` (detail
1073
+ `sourceCollection`, `targetCollection`, `target`, `linkType`, `allowed`) at the
1074
+ link's line and column, `external-url` at the URL's line and column (or
1075
+ `external-url-unchecked` with no location), and `path-layout` (detail
1076
+ `paths`). Only links that resolve to exactly one document are policy findings.
1077
+ External URLs are `scheme://` URLs in the document's indexed text outside
1078
+ frontmatter, code and HTML comments; `gno://` and `file://` are local. Path
1079
+ globs use `pattern` syntax against `recordSourcePath ?? relPath`. A truncated
1080
+ snapshot makes all three `inconclusive`. Config validation: `linkCollections`
1081
+ members must name configured collections, `externalUrls` is `report` or
1082
+ `ignore`, and `paths` is 1-64 relative globs without `..` and with balanced
1083
+ `{}` / `[]`.
1084
+
1049
1085
  The wiki link parser reads Obsidian's table-escaped alias `[[Note\|Alias]]`
1050
1086
  as target `Note` with alias `Alias`. Markdown link text may contain balanced
1051
1087
  square brackets (`[see [1]](note.md)`); a destination containing a square
@@ -2564,7 +2600,7 @@ List documents in a collection or prefix.
2564
2600
  **Synopsis:**
2565
2601
 
2566
2602
  ```bash
2567
- gno ls [<scope>] [--json|--files|--md]
2603
+ gno ls [<scope>] [--filter <json>] [-n <num>] [--offset <num>] [--json|--files|--md]
2568
2604
  ```
2569
2605
 
2570
2606
  **Arguments:**
@@ -2572,23 +2608,49 @@ gno ls [<scope>] [--json|--files|--md]
2572
2608
  |-----|------|-------------|
2573
2609
  | `<scope>` | string | Collection name or `gno://collection/prefix` (default: all) |
2574
2610
 
2575
- **Output (JSON):**
2611
+ **Options:**
2612
+ | Flag | Type | Description |
2613
+ |------|------|-------------|
2614
+ | `--filter` | JSON | Typed metadata predicate; same schema and semantics as `gno search --filter` |
2615
+ | `-n, --limit` | integer | Max documents (default 20) |
2616
+ | `--offset` | integer | Skip the first N documents |
2617
+
2618
+ Active documents are listed in URI order. With `--filter`, only documents whose
2619
+ typed metadata satisfies the predicate are listed; documents with invalid or
2620
+ not-yet-extracted metadata never match, including under negated predicates.
2621
+ The filter and the scope both apply, before pagination: `limit`, `offset` and
2622
+ `meta.total` count matching documents only. Without `--filter`, output is
2623
+ unchanged.
2624
+
2625
+ **Output (JSON):** [`ls@1.0`](output-schemas/ls.schema.json)
2576
2626
 
2577
2627
  ```json
2578
- [
2579
- {
2580
- "docid": "#a1b2c3d4",
2581
- "uri": "gno://work/doc.md",
2582
- "title": "Document Title",
2583
- "source": { "relPath": "doc.md", "mime": "text/markdown", "ext": ".md" }
2628
+ {
2629
+ "documents": [
2630
+ {
2631
+ "docid": "#a1b2c3d4",
2632
+ "uri": "gno://work/doc.md",
2633
+ "title": "Document Title",
2634
+ "source": { "relPath": "doc.md", "mime": "text/markdown", "ext": ".md" }
2635
+ }
2636
+ ],
2637
+ "meta": {
2638
+ "total": 12,
2639
+ "returned": 1,
2640
+ "offset": 0,
2641
+ "warnings": [{ "code": "METADATA_COVERAGE_INCOMPLETE", "message": "..." }]
2584
2642
  }
2585
- ]
2643
+ }
2586
2644
  ```
2587
2645
 
2646
+ `meta.warnings` appears only with `--filter` when documents in scope were
2647
+ skipped because their metadata is invalid or pending re-ingestion. Terminal,
2648
+ `--files` and `--md` output print the warning on stderr.
2649
+
2588
2650
  **Exit Codes:**
2589
2651
 
2590
2652
  - 0: Success
2591
- - 1: Invalid scope
2653
+ - 1: Invalid scope or invalid `--filter` (`VALIDATION`, same message as `gno search --filter`)
2592
2654
  - 2: DB failure
2593
2655
 
2594
2656
  ---
package/spec/mcp.md CHANGED
@@ -338,6 +338,13 @@ compatible collection policy. Policy denial returns HTTP 403 with stable
338
338
  `EGRESS_DENIED` data and a redacted evaluator audit. Write authorization remains
339
339
  an independent additional requirement.
340
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
+
341
348
  The MCP tool and resource registry is paired with a callsite-level
342
349
  network-boundary inventory. A TypeScript AST scan covers shipped TS, TSX, JS,
343
350
  and JSX and recognizes direct, qualified, bracketed, imported, and aliased
@@ -411,7 +418,10 @@ Rules report `pass`, `fail`, `skip`, `unavailable`, or `inconclusive`; reports
411
418
  are `complete`, `partial`, `changed_during_audit`, or `failed`. A passing rule
412
419
  may carry `info` findings (`links.outside-index`: link targets that exist as
413
420
  files in the link workspace but are not indexed); only `warning` and `error`
414
- 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
415
425
  changing evidence never appears healthy. Stable finding IDs are derived from
416
426
  rule, normalized subject/location, and evidence fingerprint. Responses are
417
427
  canonically ordered and bounded while retaining exact pre-truncation totals.
@@ -3386,7 +3396,10 @@ Read tools:
3386
3396
  caller authentication/authorization, content class, and partial mode. It
3387
3397
  performs no action and returns the canonical decision/explanation contract.
3388
3398
  An explicit scope is a non-empty, duplicate-free list of at most 64 canonical
3389
- 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.
3390
3403
  - `gno_egress_audit_list`, `gno_egress_audit_show`, and
3391
3404
  `gno_egress_audit_status` expose only content-free local receipts.
3392
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,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
+ }
@@ -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",
@@ -2625,6 +2625,7 @@ function wireRetrievalCommands(program: Command): void {
2625
2625
  "Skip first N results",
2626
2626
  parsePositiveInt.bind(null, "offset")
2627
2627
  )
2628
+ .option("--filter <json>", "typed custom metadata predicate (JSON)")
2628
2629
  .option("--json", "JSON output")
2629
2630
  .option("--files", "File protocol output")
2630
2631
  .option("--md", "Markdown output")
@@ -2633,6 +2634,7 @@ function wireRetrievalCommands(program: Command): void {
2633
2634
  const format = getFormat(cmdOpts);
2634
2635
  assertFormatSupported(CMD.ls, format);
2635
2636
  const globals = getGlobals();
2637
+ const filter = parseCliMetadataFilter(cmdOpts.filter);
2636
2638
 
2637
2639
  const { ls, formatLs } = await import("./commands/ls");
2638
2640
  const result = await ls(scope, {
@@ -2640,6 +2642,7 @@ function wireRetrievalCommands(program: Command): void {
2640
2642
  indexName: globals.index,
2641
2643
  limit: cmdOpts.limit as number | undefined,
2642
2644
  offset: cmdOpts.offset as number | undefined,
2645
+ filter,
2643
2646
  json: format === "json",
2644
2647
  files: format === "files",
2645
2648
  md: format === "md",
@@ -2652,6 +2655,14 @@ function wireRetrievalCommands(program: Command): void {
2652
2655
  );
2653
2656
  }
2654
2657
 
2658
+ // JSON carries warnings in meta; other formats keep stdout clean.
2659
+ if (format !== "json") {
2660
+ for (const warning of result.data.meta.warnings ?? []) {
2661
+ process.stderr.write(
2662
+ `Warning: ${warning.code}: ${warning.message}\n`
2663
+ );
2664
+ }
2665
+ }
2655
2666
  const output = formatLs(result, {
2656
2667
  json: format === "json",
2657
2668
  files: format === "files",