@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.
- package/README.md +6 -2
- package/assets/skill/SKILL.md +41 -16
- package/assets/skill/cli-reference.md +39 -0
- package/assets/skill/examples.md +41 -0
- package/assets/skill/mcp-reference.md +13 -1
- package/package.json +1 -1
- package/src/cli/commands/capture.ts +9 -6
- package/src/cli/commands/graph.ts +89 -2
- package/src/cli/commands/index-cmd.ts +17 -6
- package/src/cli/commands/links.ts +237 -54
- package/src/cli/commands/query.ts +212 -0
- package/src/cli/commands/ref-parser.ts +8 -103
- package/src/cli/commands/shared.ts +17 -1
- package/src/cli/commands/update.ts +17 -6
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +176 -9
- package/src/config/content-types.ts +154 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/index.ts +14 -0
- package/src/config/loader.ts +11 -2
- package/src/config/types.ts +37 -1
- package/src/core/config-mutation.ts +14 -2
- package/src/core/graph-query.ts +137 -0
- package/src/core/graph-resolver.ts +117 -0
- package/src/core/note-presets.ts +61 -5
- package/src/core/ref-parser.ts +145 -0
- package/src/ingestion/frontmatter.ts +170 -2
- package/src/ingestion/index.ts +2 -0
- package/src/ingestion/sync-options.ts +29 -0
- package/src/ingestion/sync.ts +385 -17
- package/src/ingestion/types.ts +14 -0
- package/src/mcp/tools/add-collection.ts +8 -5
- package/src/mcp/tools/capture.ts +5 -2
- package/src/mcp/tools/get.ts +1 -1
- package/src/mcp/tools/index-cmd.ts +13 -7
- package/src/mcp/tools/index.ts +97 -10
- package/src/mcp/tools/links.ts +83 -1
- package/src/mcp/tools/multi-get.ts +1 -1
- package/src/mcp/tools/query.ts +207 -0
- package/src/mcp/tools/sync.ts +12 -6
- package/src/mcp/tools/workspace-write.ts +16 -10
- package/src/pipeline/diagnose.ts +302 -0
- package/src/pipeline/filters.ts +119 -0
- package/src/pipeline/hybrid.ts +92 -17
- package/src/pipeline/search.ts +2 -0
- package/src/pipeline/types.ts +34 -0
- package/src/pipeline/vsearch.ts +4 -0
- package/src/publish/export-service.ts +1 -1
- package/src/sdk/client.ts +51 -24
- package/src/sdk/documents.ts +2 -2
- package/src/serve/background-runtime.ts +18 -4
- package/src/serve/config-sync.ts +9 -2
- package/src/serve/routes/api.ts +244 -24
- package/src/serve/routes/graph.ts +173 -1
- package/src/serve/server.ts +24 -1
- package/src/serve/watch-service.ts +12 -2
- package/src/store/migrations/009-content-type-rule-fingerprint.ts +36 -0
- package/src/store/migrations/010-typed-edges.ts +67 -0
- package/src/store/migrations/011-doc-edge-traversal-indexes.ts +30 -0
- package/src/store/migrations/index.ts +16 -1
- package/src/store/sqlite/adapter.ts +853 -114
- 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
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -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`
|
|
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
|
|
141
|
-
4. Use `
|
|
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.
|
package/assets/skill/examples.md
CHANGED
|
@@ -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
|
@@ -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
|
-
|
|
227
|
-
|
|
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 {
|
|
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 {
|
|
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(
|
|
63
|
-
|
|
64
|
-
|
|
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;
|