@gmickel/gno 1.9.0 → 1.10.1

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 (37) hide show
  1. package/assets/skill/SKILL.md +28 -16
  2. package/assets/skill/cli-reference.md +30 -0
  3. package/assets/skill/examples.md +20 -0
  4. package/assets/skill/mcp-reference.md +7 -1
  5. package/package.json +1 -1
  6. package/src/cli/commands/graph.ts +89 -2
  7. package/src/cli/commands/links.ts +237 -54
  8. package/src/cli/commands/query.ts +212 -0
  9. package/src/cli/commands/ref-parser.ts +8 -103
  10. package/src/cli/options.ts +4 -0
  11. package/src/cli/program.ts +176 -9
  12. package/src/config/content-types.ts +14 -0
  13. package/src/core/file-lock.ts +38 -3
  14. package/src/core/graph-query.ts +137 -0
  15. package/src/core/graph-resolver.ts +117 -0
  16. package/src/core/ref-parser.ts +145 -0
  17. package/src/ingestion/frontmatter.ts +95 -2
  18. package/src/ingestion/sync.ts +281 -1
  19. package/src/mcp/tools/get.ts +1 -1
  20. package/src/mcp/tools/index.ts +89 -1
  21. package/src/mcp/tools/links.ts +83 -1
  22. package/src/mcp/tools/multi-get.ts +1 -1
  23. package/src/mcp/tools/query.ts +207 -0
  24. package/src/pipeline/diagnose.ts +302 -0
  25. package/src/pipeline/filters.ts +119 -0
  26. package/src/pipeline/hybrid.ts +90 -17
  27. package/src/pipeline/types.ts +32 -0
  28. package/src/publish/export-service.ts +1 -1
  29. package/src/sdk/documents.ts +2 -2
  30. package/src/serve/routes/api.ts +194 -0
  31. package/src/serve/routes/graph.ts +173 -1
  32. package/src/serve/server.ts +24 -1
  33. package/src/store/migrations/010-typed-edges.ts +67 -0
  34. package/src/store/migrations/011-doc-edge-traversal-indexes.ts +30 -0
  35. package/src/store/migrations/index.ts +4 -0
  36. package/src/store/sqlite/adapter.ts +826 -102
  37. package/src/store/types.ts +141 -0
@@ -35,20 +35,20 @@ gno search "your query" # BM25 keyword search
35
35
 
36
36
  ## Command Overview
37
37
 
38
- | Category | Commands | Description |
39
- | ------------ | ---------------------------------------------------------------- | --------------------------------------------------------- |
40
- | **Search** | `search`, `vsearch`, `query`, `ask` | Find documents by keywords, meaning, or get AI answers |
41
- | **Links** | `links`, `backlinks`, `similar`, `graph` | Navigate document relationships and visualize connections |
42
- | **Retrieve** | `get`, `multi-get`, `ls` | Fetch document content by URI or ID |
43
- | **Index** | `init`, `collection add/list/remove`, `index`, `update`, `embed` | Set up and maintain document index |
44
- | **Tags** | `tags`, `tags add`, `tags rm` | Organize and filter documents |
45
- | **Context** | `context add/list/rm/check` | Add hints to improve search relevance |
46
- | **Models** | `models list/use/pull/clear/path` | Manage local AI models |
47
- | **Serve** | `serve` | Web UI for browsing and searching |
48
- | **Publish** | `publish export` | Export gno.sh publish artifacts |
49
- | **MCP** | `mcp`, `mcp install/uninstall/status` | AI assistant integration |
50
- | **Skill** | `skill install/uninstall/show/paths` | Install skill for AI agents |
51
- | **Admin** | `status`, `doctor`, `cleanup`, `reset`, `vec`, `completion` | Maintenance and diagnostics |
38
+ | Category | Commands | Description |
39
+ | ------------ | ---------------------------------------------------------------- | ------------------------------------------------------ |
40
+ | **Search** | `search`, `vsearch`, `query`, `ask` | Find documents by keywords, meaning, or get AI answers |
41
+ | **Links** | `links`, `backlinks`, `similar`, `graph`, `graph query` | Navigate document relationships and typed connections |
42
+ | **Retrieve** | `get`, `multi-get`, `ls` | Fetch document content by URI or ID |
43
+ | **Index** | `init`, `collection add/list/remove`, `index`, `update`, `embed` | Set up and maintain document index |
44
+ | **Tags** | `tags`, `tags add`, `tags rm` | Organize and filter documents |
45
+ | **Context** | `context add/list/rm/check` | Add hints to improve search relevance |
46
+ | **Models** | `models list/use/pull/clear/path` | Manage local AI models |
47
+ | **Serve** | `serve` | Web UI for browsing and searching |
48
+ | **Publish** | `publish export` | Export gno.sh publish artifacts |
49
+ | **MCP** | `mcp`, `mcp install/uninstall/status` | AI assistant integration |
50
+ | **Skill** | `skill install/uninstall/show/paths` | Install skill for AI agents |
51
+ | **Admin** | `status`, `doctor`, `cleanup`, `reset`, `vec`, `completion` | Maintenance and diagnostics |
52
52
 
53
53
  ## Search Modes
54
54
 
@@ -137,8 +137,9 @@ When using GNO through MCP, prefer this retrieval order:
137
137
 
138
138
  1. Check `gno_status` first when freshness, missing vectors, or stale results are plausible.
139
139
  2. Use `gno_query` first for normal content questions. It returns snippets plus `uri`, `docid`, and often `line`; pass `graph: true` only when linked context is worth the extra latency.
140
- 3. Use graph/link expansion for relationship context: `gno_graph_neighbors` for nearby documents, `gno_graph_path` for "how are X and Y connected?", `gno_links`/`gno_backlinks` for one-document link expansion, and `gno_similar` for semantic neighbors. Prefer `explicit` graph edges over `inferred`, `ambiguous`, or `similarity` edges when confidence matters.
141
- 4. Use `gno_get` with `fromLine`/`lineCount` for targeted reads, or `gno_multi_get` to batch top refs.
140
+ 3. Use graph/link expansion for relationship context: `gno_graph_query` for typed relationship traversal, `gno_graph_neighbors` for nearby documents, `gno_graph_path` for "how are X and Y connected?", `gno_links`/`gno_backlinks` for one-document link expansion, and `gno_similar` for semantic neighbors. Prefer explicit or typed edges over inferred, ambiguous, or similarity edges when confidence matters.
141
+ 4. Use `gno_query_diagnose` when a known target document should have appeared but did not; it reports BM25/vector/fusion/graph/rerank stage presence and filter state.
142
+ 5. Use `gno_get` with `fromLine`/`lineCount` for targeted reads, or `gno_multi_get` to batch top refs.
142
143
 
143
144
  Use narrower tools when the request tells you to:
144
145
 
@@ -146,8 +147,10 @@ Use narrower tools when the request tells you to:
146
147
  - `gno_vsearch`: conceptual similarity when exact wording differs
147
148
  - `gno_status`: stale results, missing embeddings, vector unavailable
148
149
  - `gno_graph`: graph report/stats, hubs, isolates, unresolved links, edge confidence/audit, communities, unfamiliar corpus overview
150
+ - `gno_graph_query`: bounded typed-edge traversal from a known document
149
151
  - `gno_graph_neighbors`: relationship/corpus-navigation questions around a known document
150
152
  - `gno_graph_path`: "how are X and Y connected?" questions
153
+ - `gno_query_diagnose`: why a named target did or did not surface for a query
151
154
 
152
155
  For ambiguous terms, pass `intent` instead of bloating the query text. For typed retrieval, use `queryModes`: `term` for lexical anchors, `intent` for disambiguation, one `hyde` for a hypothetical answer/document.
153
156
 
@@ -160,6 +163,15 @@ gno links gno://notes/readme.md
160
163
  # Find documents linking TO a document (backlinks)
161
164
  gno backlinks gno://notes/api-design.md
162
165
 
166
+ # Traverse typed relationships
167
+ gno graph query gno://notes/people/alice.md --edge-type works_at --max-depth 2
168
+
169
+ # Query semantic edges on link commands
170
+ gno links gno://notes/people/alice.md --edge-type works_at
171
+
172
+ # Diagnose a missing expected result
173
+ gno query diagnose "Alice Acme" --target gno://notes/people/alice.md --json
174
+
163
175
  # Find semantically similar documents
164
176
  gno similar gno://notes/auth.md
165
177
 
@@ -317,6 +317,36 @@ gno backlinks <ref> [options]
317
317
  | `-n` | Max results (default: 20) |
318
318
  | `--json` | JSON output |
319
319
 
320
+ `gno links` and `gno backlinks` also accept `--edge-type <type>` or
321
+ `--relation <type>` to query semantic typed edges instead of positional
322
+ wiki/markdown links. Do not combine `--type` with `--edge-type`.
323
+
324
+ ### gno graph query
325
+
326
+ Bounded typed-edge traversal from a document.
327
+
328
+ ```bash
329
+ gno graph query <ref> [--edge-type <type>] [--max-depth <n>] [--direction out|in|both] [--json]
330
+ ```
331
+
332
+ Use this when relation meaning matters:
333
+
334
+ ```bash
335
+ gno graph query gno://notes/people/alice.md --edge-type works_at --max-depth 2 --json
336
+ ```
337
+
338
+ ### gno query diagnose
339
+
340
+ Explain why a target document did or did not surface for a query.
341
+
342
+ ```bash
343
+ gno query diagnose "<query>" --target <ref> [--fast|--thorough] [--json]
344
+ ```
345
+
346
+ The JSON output reports target status, filters, typed metadata, and per-stage
347
+ presence across BM25, vector, fusion, graph expansion, and rerank. Use `--fast`
348
+ for BM25-only diagnosis without vector/rerank startup.
349
+
320
350
  ### gno similar
321
351
 
322
352
  Find semantically similar documents.
@@ -380,6 +380,26 @@ and raw chronology below it. If `contentTypes` are configured in `index.yml`,
380
380
  matching frontmatter `type` or path prefixes become `contentType` in JSON search
381
381
  results.
382
382
 
383
+ ### Typed relationships and diagnostics
384
+
385
+ ```yaml
386
+ relations:
387
+ works_at: [gno://notes/companies/acme.md]
388
+ attended: [gno://notes/meetings/weekly-sync.md]
389
+ ```
390
+
391
+ ```bash
392
+ # Traverse semantic relationships
393
+ gno graph query gno://notes/people/alice.md --edge-type works_at --max-depth 2 --json
394
+
395
+ # Query the semantic edge layer from link commands
396
+ gno links gno://notes/people/alice.md --edge-type works_at
397
+ gno backlinks gno://notes/meetings/weekly-sync.md --relation attended
398
+
399
+ # Debug why a named target did not rank
400
+ gno query diagnose "Alice Acme" --target gno://notes/people/alice.md --fast --json
401
+ ```
402
+
383
403
  ### Web UI quick capture
384
404
 
385
405
  Press **N** in `gno serve`, write the note, and open **Source** only when you
@@ -58,16 +58,22 @@ gno mcp status
58
58
  For normal questions, start with `gno_query`, then read targeted snippets with
59
59
  `gno_get` or batch refs with `gno_multi_get`. Pass `graph: true` only when
60
60
  linked context is worth the extra latency. Check `gno_status` first when freshness or
61
- embeddings may be stale.
61
+ embeddings may be stale. Use `gno_query_diagnose` when a known target document
62
+ should have appeared but did not.
62
63
 
63
64
  Use graph tools for relationship context: `gno_graph` for corpus report/stats,
64
65
  community summaries,
66
+ `gno_graph_query` for bounded typed-edge traversal,
65
67
  `gno_graph_neighbors` for nearby incoming/outgoing graph context, and
66
68
  `gno_graph_path` for "how are X and Y connected?" questions. Use
67
69
  `gno_links`, `gno_backlinks`, and `gno_similar` for one-document expansion.
68
70
  Graph edges include confidence/audit metadata; prefer `explicit` edges when
69
71
  answers depend on link certainty.
70
72
 
73
+ Read-only MCP graph/query diagnostics include `gno_graph_query` and
74
+ `gno_query_diagnose`. In `gno_query_diagnose`, pass `fast: true` for a BM25-only
75
+ diagnosis that avoids embedding/rerank model initialization.
76
+
71
77
  ## Capture
72
78
 
73
79
  `gno_capture` is available only when MCP starts with `--enable-write` or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.9.0",
3
+ "version": "1.10.1",
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",
@@ -5,8 +5,15 @@
5
5
  * @module src/cli/commands/graph
6
6
  */
7
7
 
8
- import type { GetGraphOptions, GraphResult } from "../../store/types";
9
-
8
+ import type {
9
+ GetGraphOptions,
10
+ GraphQueryDirection,
11
+ GraphQueryResult,
12
+ GraphResult,
13
+ } from "../../store/types";
14
+
15
+ import { normalizeContentTypes } from "../../config";
16
+ import { diagnoseGraphQuery } from "../../core/graph-query";
10
17
  import { initStore } from "./shared";
11
18
 
12
19
  // ─────────────────────────────────────────────────────────────────────────────
@@ -16,6 +23,8 @@ import { initStore } from "./shared";
16
23
  export interface GraphOptions {
17
24
  /** Override config path */
18
25
  configPath?: string;
26
+ /** Override index name */
27
+ indexName?: string;
19
28
  /** Filter by collection */
20
29
  collection?: string;
21
30
  /** Max nodes (default 2000) */
@@ -44,12 +53,28 @@ export interface GraphOptions {
44
53
  maxDepth?: number;
45
54
  }
46
55
 
56
+ export interface GraphQueryCliOptions {
57
+ configPath?: string;
58
+ indexName?: string;
59
+ direction?: GraphQueryDirection;
60
+ edgeType?: string;
61
+ maxDepth?: number;
62
+ maxNodes?: number;
63
+ frontierLimit?: number;
64
+ visitedLimit?: number;
65
+ format?: "terminal" | "json";
66
+ }
67
+
47
68
  export type GraphCommandResult =
48
69
  | { success: true; data: GraphResult }
49
70
  | { success: true; data: GraphNeighborsCliResult }
50
71
  | { success: true; data: GraphPathCliResult }
51
72
  | { success: false; error: string; isValidation?: boolean };
52
73
 
74
+ export type GraphQueryCommandResult =
75
+ | { success: true; data: GraphQueryResult }
76
+ | { success: false; error: string; isValidation?: boolean };
77
+
53
78
  type GraphNode = GraphResult["nodes"][number];
54
79
  type GraphLink = GraphResult["links"][number];
55
80
 
@@ -94,6 +119,7 @@ export async function graph(
94
119
  ): Promise<GraphCommandResult> {
95
120
  const initResult = await initStore({
96
121
  configPath: options.configPath,
122
+ indexName: options.indexName,
97
123
  syncConfig: false,
98
124
  });
99
125
  if (!initResult.ok) {
@@ -185,6 +211,35 @@ export async function graph(
185
211
  }
186
212
  }
187
213
 
214
+ export async function graphQuery(
215
+ docRef: string,
216
+ options: GraphQueryCliOptions = {}
217
+ ): Promise<GraphQueryCommandResult> {
218
+ const initResult = await initStore({
219
+ configPath: options.configPath,
220
+ indexName: options.indexName,
221
+ syncConfig: false,
222
+ });
223
+ if (!initResult.ok) {
224
+ return { success: false, error: initResult.error };
225
+ }
226
+ const { store, config } = initResult;
227
+
228
+ try {
229
+ return await diagnoseGraphQuery(store, docRef, {
230
+ direction: options.direction,
231
+ edgeType: options.edgeType,
232
+ maxDepth: options.maxDepth,
233
+ maxNodes: options.maxNodes,
234
+ frontierLimit: options.frontierLimit,
235
+ visitedLimit: options.visitedLimit,
236
+ contentTypeRules: normalizeContentTypes(config.contentTypes ?? []).rules,
237
+ });
238
+ } finally {
239
+ await store.close();
240
+ }
241
+ }
242
+
188
243
  function resolveGraphNode(
189
244
  graphResult: GraphResult,
190
245
  ref: string
@@ -421,3 +476,35 @@ export function formatGraph(
421
476
  return JSON.stringify(data, null, 2);
422
477
  }
423
478
  }
479
+
480
+ export function formatGraphQuery(
481
+ result: GraphQueryCommandResult,
482
+ options: GraphQueryCliOptions
483
+ ): string {
484
+ if (!result.success) {
485
+ if (options.format === "json") {
486
+ return JSON.stringify({
487
+ error: { code: "GRAPH_QUERY_FAILED", message: result.error },
488
+ });
489
+ }
490
+ return `Error: ${result.error}`;
491
+ }
492
+
493
+ if (options.format === "json") {
494
+ return JSON.stringify(result.data, null, 2);
495
+ }
496
+
497
+ const lines = [
498
+ `Graph query from ${result.data.root.uri}`,
499
+ `Nodes: ${result.data.meta.returnedNodes}, edges: ${result.data.meta.returnedEdges}`,
500
+ ];
501
+ if (result.data.meta.truncated) {
502
+ lines.push(`Truncated: ${result.data.meta.warnings.join("; ")}`);
503
+ }
504
+ for (const edge of result.data.edges) {
505
+ lines.push(
506
+ `${edge.source} -> ${edge.target} (${edge.edgeType}, ${edge.confidence}, ${edge.edgeSource}, depth ${edge.depth})`
507
+ );
508
+ }
509
+ return lines.join("\n");
510
+ }