@gmickel/gno 1.8.0 → 1.10.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 (62) hide show
  1. package/README.md +6 -2
  2. package/assets/skill/SKILL.md +41 -16
  3. package/assets/skill/cli-reference.md +39 -0
  4. package/assets/skill/examples.md +41 -0
  5. package/assets/skill/mcp-reference.md +13 -1
  6. package/package.json +1 -1
  7. package/src/cli/commands/capture.ts +9 -6
  8. package/src/cli/commands/graph.ts +89 -2
  9. package/src/cli/commands/index-cmd.ts +17 -6
  10. package/src/cli/commands/links.ts +237 -54
  11. package/src/cli/commands/query.ts +212 -0
  12. package/src/cli/commands/ref-parser.ts +8 -103
  13. package/src/cli/commands/shared.ts +17 -1
  14. package/src/cli/commands/update.ts +17 -6
  15. package/src/cli/options.ts +4 -0
  16. package/src/cli/program.ts +176 -9
  17. package/src/config/content-types.ts +154 -0
  18. package/src/config/defaults.ts +1 -0
  19. package/src/config/index.ts +14 -0
  20. package/src/config/loader.ts +11 -2
  21. package/src/config/types.ts +37 -1
  22. package/src/core/config-mutation.ts +14 -2
  23. package/src/core/graph-query.ts +137 -0
  24. package/src/core/graph-resolver.ts +117 -0
  25. package/src/core/note-presets.ts +61 -5
  26. package/src/core/ref-parser.ts +145 -0
  27. package/src/ingestion/frontmatter.ts +170 -2
  28. package/src/ingestion/index.ts +2 -0
  29. package/src/ingestion/sync-options.ts +29 -0
  30. package/src/ingestion/sync.ts +385 -17
  31. package/src/ingestion/types.ts +14 -0
  32. package/src/mcp/tools/add-collection.ts +8 -5
  33. package/src/mcp/tools/capture.ts +5 -2
  34. package/src/mcp/tools/get.ts +1 -1
  35. package/src/mcp/tools/index-cmd.ts +13 -7
  36. package/src/mcp/tools/index.ts +97 -10
  37. package/src/mcp/tools/links.ts +83 -1
  38. package/src/mcp/tools/multi-get.ts +1 -1
  39. package/src/mcp/tools/query.ts +207 -0
  40. package/src/mcp/tools/sync.ts +12 -6
  41. package/src/mcp/tools/workspace-write.ts +16 -10
  42. package/src/pipeline/diagnose.ts +302 -0
  43. package/src/pipeline/filters.ts +119 -0
  44. package/src/pipeline/hybrid.ts +92 -17
  45. package/src/pipeline/search.ts +2 -0
  46. package/src/pipeline/types.ts +34 -0
  47. package/src/pipeline/vsearch.ts +4 -0
  48. package/src/publish/export-service.ts +1 -1
  49. package/src/sdk/client.ts +51 -24
  50. package/src/sdk/documents.ts +2 -2
  51. package/src/serve/background-runtime.ts +18 -4
  52. package/src/serve/config-sync.ts +9 -2
  53. package/src/serve/routes/api.ts +244 -24
  54. package/src/serve/routes/graph.ts +173 -1
  55. package/src/serve/server.ts +24 -1
  56. package/src/serve/watch-service.ts +12 -2
  57. package/src/store/migrations/009-content-type-rule-fingerprint.ts +36 -0
  58. package/src/store/migrations/010-typed-edges.ts +67 -0
  59. package/src/store/migrations/011-doc-edge-traversal-indexes.ts +30 -0
  60. package/src/store/migrations/index.ts +16 -1
  61. package/src/store/sqlite/adapter.ts +853 -114
  62. package/src/store/types.ts +147 -0
package/README.md CHANGED
@@ -97,7 +97,11 @@ gno daemon --detach # headless continuous indexing (background; --status / --st
97
97
 
98
98
  - **Second-brain capture**: `gno capture`, REST `/api/capture`, SDK
99
99
  `client.capture()`, MCP `gno_capture`, and Web UI Quick Capture write
100
- provenance-rich notes from text, stdin, or files
100
+ provenance-rich notes from text, stdin, or files, including typed presets for
101
+ ideas, people, company/projects, and meetings
102
+ - **Schema-lite content types**: optional `contentTypes` rules map configured
103
+ frontmatter `type` values or path prefixes to canonical `contentType` metadata
104
+ in JSON search/query results
101
105
  - **Publish to [gno.sh](https://gno.sh/publish)**: new `gno publish export` CLI and Web UI action produce a self-contained artifact you upload to the hosted reader — public, secret, invite-only, or locally encrypted before upload
102
106
  - **Retrieval Quality Upgrade**: stronger BM25 lexical handling, code-aware chunking, terminal result hyperlinks, and per-collection model overrides
103
107
  - **Code Embedding Benchmarks**: new benchmark workflow across canonical, real-GNO, and pinned OSS slices for comparing alternate embedding models
@@ -546,7 +550,7 @@ Open `http://localhost:3000` to:
546
550
  - **Browse**: Cross-collection tree workspace with folder detail panes and per-tab browse context
547
551
  - **Edit**: Create, edit, and delete documents with live preview
548
552
  - **Create in place**: New notes in the current folder/collection with presets and command-palette flows
549
- - **Capture with provenance**: `gno capture` and Web UI Quick Capture write quick notes to an editable collection with structured `source:` metadata and a receipt that separates write, sync, and embed state
553
+ - **Capture with provenance**: `gno capture` and Web UI Quick Capture write quick notes to an editable collection with structured `source:` metadata, typed preset scaffolds, and a receipt that separates write, sync, and embed state
550
554
  - **Same capture contract everywhere**: CLI, MCP `gno_capture`, REST `/api/capture`, SDK `client.capture()`, and Web UI Quick Capture return the same provenance receipt shape
551
555
  - **Ask**: AI-powered Q&A with citations
552
556
  - **Manage Collections**: Add, remove, and re-index collections
@@ -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
 
@@ -214,8 +226,21 @@ Use `gno capture` for quick second-brain writes into an editable collection:
214
226
  ```bash
215
227
  gno capture "thought to remember"
216
228
  gno capture --file ./clip.md --source-url https://example.com --source-kind web --json
229
+ gno capture --preset person --title "Jane Doe" --folder people/
230
+ gno capture --preset meeting --title "Weekly sync" --folder meetings/
217
231
  ```
218
232
 
233
+ Preset IDs: `blank`, `project-note`, `research-note`, `decision-note`,
234
+ `prompt-pattern`, `source-summary`, `idea-original`, `person`,
235
+ `company-project`, `meeting`.
236
+
237
+ For second-brain pages, prefer the typed presets:
238
+
239
+ - `idea-original`: exact idea phrasing, context, related concepts, publish potential.
240
+ - `person`: current state, relationship, assessment, open threads, timeline.
241
+ - `company-project`: state, changes, decisions, people, timeline.
242
+ - `meeting`: synthesis/action analysis above `## Timeline`; raw notes below.
243
+
219
244
  The JSON receipt reports write, sync, and embed status separately. Generated
220
245
  captures land under `inbox/YYYY-MM-DD/capture-<body-hash>.md` unless `--path`,
221
246
  `--folder`, or `--title` overrides the path. Capture does not imply embedding
@@ -124,6 +124,15 @@ gno capture --file ./clip.md --source-url https://example.com --source-kind web
124
124
  gno capture "meeting note" --quiet
125
125
  ```
126
126
 
127
+ Preset IDs: `blank`, `project-note`, `research-note`, `decision-note`,
128
+ `prompt-pattern`, `source-summary`, `idea-original`, `person`,
129
+ `company-project`, `meeting`.
130
+
131
+ Second-brain presets keep current synthesis above `## Timeline` and dated
132
+ evidence below it. Use `idea-original` for exact idea wording, `person` for
133
+ relationship/current-state notes, `company-project` for organizations or active
134
+ workstreams, and `meeting` for analysis above transcript/raw notes/action items.
135
+
127
136
  Important behavior:
128
137
 
129
138
  - Inline content, `--stdin`, and `--file` are mutually exclusive.
@@ -308,6 +317,36 @@ gno backlinks <ref> [options]
308
317
  | `-n` | Max results (default: 20) |
309
318
  | `--json` | JSON output |
310
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
+
311
350
  ### gno similar
312
351
 
313
352
  Find semantically similar documents.
@@ -359,6 +359,47 @@ gno capture "thought to remember"
359
359
  gno capture --file ./clip.md --source-url https://example.com --source-kind web --json
360
360
  ```
361
361
 
362
+ ### Typed second-brain presets
363
+
364
+ ```bash
365
+ # Original idea: preserve exact phrasing and related concepts
366
+ gno capture --preset idea-original --title "Local-first inbox triage" --folder ideas/
367
+
368
+ # Person page: current synthesis plus relationship/open threads/timeline
369
+ gno capture --preset person --title "Jane Doe" --folder people/
370
+
371
+ # Company/project page: state, decisions, people, and timeline
372
+ gno capture --preset company-project --title "Acme renewal" --folder projects/
373
+
374
+ # Meeting page: analysis above raw notes, transcript, and action items
375
+ gno capture --preset meeting --title "Weekly sync" --folder meetings/
376
+ ```
377
+
378
+ For these presets, keep current synthesis above `## Timeline`; put dated evidence
379
+ and raw chronology below it. If `contentTypes` are configured in `index.yml`,
380
+ matching frontmatter `type` or path prefixes become `contentType` in JSON search
381
+ results.
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
+
362
403
  ### Web UI quick capture
363
404
 
364
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
@@ -76,6 +82,12 @@ frontmatter and returns the same provenance receipt shape as CLI, REST, and SDK
76
82
  capture, plus legacy MCP fields (`docid`, `absPath`, `overwritten`,
77
83
  `serverInstanceId`).
78
84
 
85
+ `presetId` accepts `blank`, `project-note`, `research-note`, `decision-note`,
86
+ `prompt-pattern`, `source-summary`, `idea-original`, `person`,
87
+ `company-project`, or `meeting`. The typed second-brain presets use flat
88
+ frontmatter (`type`, `category`, `tags`) and a synthesis/timeline page pattern;
89
+ provenance still comes from the capture `source` fields, not the preset.
90
+
79
91
  Use `collisionPolicy: "open_existing"` to return an existing note without
80
92
  rewriting, `create_with_suffix` to create the next available path, or legacy
81
93
  `overwrite: true` to replace the target path. Capture content must be text, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gmickel/gno",
3
- "version": "1.8.0",
3
+ "version": "1.10.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",
@@ -26,7 +26,7 @@ import {
26
26
  } from "../../core/capture";
27
27
  import { writeCapturePlanFile } from "../../core/capture-write";
28
28
  import { withWriteLock } from "../../core/file-lock";
29
- import { defaultSyncService } from "../../ingestion";
29
+ import { defaultSyncService, withContentTypeRules } from "../../ingestion";
30
30
  import { CliError } from "../errors";
31
31
  import { initStore } from "./shared";
32
32
 
@@ -140,7 +140,7 @@ export async function capture(
140
140
  throw new CliError("VALIDATION", storeInit.error);
141
141
  }
142
142
 
143
- const { store, collections } = storeInit;
143
+ const { store, collections, config } = storeInit;
144
144
  try {
145
145
  const collectionName = options.collection?.trim();
146
146
  const collection = collectionName
@@ -222,10 +222,13 @@ export async function capture(
222
222
  collection,
223
223
  store,
224
224
  [plan.relPath],
225
- {
226
- runUpdateCmd: false,
227
- gitPull: false,
228
- }
225
+ withContentTypeRules(
226
+ {
227
+ runUpdateCmd: false,
228
+ gitPull: false,
229
+ },
230
+ config
231
+ )
229
232
  );
230
233
  const syncResult = syncResults[0];
231
234
  const docResult = await store.getDocument(collection.name, plan.relPath);
@@ -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
+ }
@@ -5,7 +5,11 @@
5
5
  * @module src/cli/commands/indexCmd
6
6
  */
7
7
 
8
- import { defaultSyncService, type SyncResult } from "../../ingestion";
8
+ import {
9
+ defaultSyncService,
10
+ type SyncResult,
11
+ withContentTypeRules,
12
+ } from "../../ingestion";
9
13
  import { formatSyncResultLines, initStore } from "./shared";
10
14
 
11
15
  /**
@@ -55,14 +59,21 @@ export async function index(options: IndexOptions = {}): Promise<IndexResult> {
55
59
  return { success: false, error: initResult.error };
56
60
  }
57
61
 
58
- const { store, collections } = initResult;
62
+ const { store, collections, config } = initResult;
59
63
 
60
64
  try {
61
65
  // Run sync service (update phase)
62
- const syncResult = await defaultSyncService.syncAll(collections, store, {
63
- gitPull: options.gitPull,
64
- runUpdateCmd: true,
65
- });
66
+ const syncResult = await defaultSyncService.syncAll(
67
+ collections,
68
+ store,
69
+ withContentTypeRules(
70
+ {
71
+ gitPull: options.gitPull,
72
+ runUpdateCmd: true,
73
+ },
74
+ config
75
+ )
76
+ );
66
77
 
67
78
  // Embedding phase
68
79
  const embedSkipped = options.noEmbed ?? false;