@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
@@ -43,6 +43,8 @@ export interface SearchResult {
43
43
  score: number;
44
44
  uri: string;
45
45
  title?: string;
46
+ contentType?: string;
47
+ categories?: string[];
46
48
  /** Best source line for editor/agent anchors (1-indexed) */
47
49
  line?: number;
48
50
  snippet: string;
@@ -102,6 +104,8 @@ export interface SearchMeta {
102
104
  lines: ExplainLine[];
103
105
  results: ExplainResult[];
104
106
  };
107
+ /** Internal diagnose trace, only populated when diagnoseTrace is enabled */
108
+ trace?: QueryDiagnoseTrace;
105
109
  }
106
110
 
107
111
  /** Complete search results wrapper */
@@ -180,6 +184,8 @@ export type HybridSearchOptions = SearchOptions & {
180
184
  noGraph?: boolean;
181
185
  /** Language hint for prompt selection (does NOT filter retrieval, only affects expansion prompts) */
182
186
  queryLanguageHint?: string;
187
+ /** Internal: capture per-stage candidates for query diagnose */
188
+ diagnoseTrace?: boolean;
183
189
  };
184
190
 
185
191
  /** Options for ask command */
@@ -256,6 +262,34 @@ export interface FusionCandidate {
256
262
  sources: FusionSource[];
257
263
  }
258
264
 
265
+ export type QueryDiagnoseStageId =
266
+ | "bm25"
267
+ | "vector"
268
+ | "fusion"
269
+ | "graph"
270
+ | "rerank";
271
+
272
+ export type QueryDiagnoseStageStatus = "active" | "skipped";
273
+
274
+ export interface QueryDiagnoseTraceCandidate {
275
+ mirrorHash: string;
276
+ seq: number;
277
+ rank: number;
278
+ score: number;
279
+ }
280
+
281
+ export interface QueryDiagnoseTraceStage {
282
+ id: QueryDiagnoseStageId;
283
+ status: QueryDiagnoseStageStatus;
284
+ reason?: string;
285
+ sourceCount: number;
286
+ candidates: QueryDiagnoseTraceCandidate[];
287
+ }
288
+
289
+ export interface QueryDiagnoseTrace {
290
+ stages: QueryDiagnoseTraceStage[];
291
+ }
292
+
259
293
  // ─────────────────────────────────────────────────────────────────────────────
260
294
  // Rerank & Blending Types
261
295
  // ─────────────────────────────────────────────────────────────────────────────
@@ -227,6 +227,8 @@ export async function searchVectorWithEmbedding(
227
227
  score,
228
228
  uri: doc.uri,
229
229
  title: doc.title ?? undefined,
230
+ contentType: doc.contentType ?? undefined,
231
+ categories: doc.categories ?? undefined,
230
232
  line: chunk.startLine,
231
233
  snippet: chunk.text,
232
234
  snippetLanguage: chunk.language ?? undefined,
@@ -281,6 +283,8 @@ export async function searchVectorWithEmbedding(
281
283
  score,
282
284
  uri: doc.uri,
283
285
  title: doc.title ?? undefined,
286
+ contentType: doc.contentType ?? undefined,
287
+ categories: doc.categories ?? undefined,
284
288
  line: chunk.startLine,
285
289
  snippet: fullContent ?? chunk.text,
286
290
  snippetLanguage: chunk.language ?? undefined,
@@ -7,7 +7,7 @@
7
7
  import type { Collection } from "../config/types";
8
8
  import type { DocumentRow, StorePort, TagRow } from "../store/types";
9
9
 
10
- import { parseRef } from "../cli/commands/ref-parser";
10
+ import { parseRef } from "../core/ref-parser";
11
11
  import { parseFrontmatter } from "../ingestion/frontmatter";
12
12
  import { getContentBatch } from "../store/content-batch";
13
13
  import {
package/src/sdk/client.ts CHANGED
@@ -44,7 +44,11 @@ import {
44
44
  getIndexDbPath,
45
45
  parseUri,
46
46
  } from "../app/constants";
47
- import { ConfigSchema, loadConfig } from "../config";
47
+ import {
48
+ ConfigSchema,
49
+ loadConfig,
50
+ normalizeConfigContentTypes,
51
+ } from "../config";
48
52
  import {
49
53
  buildCaptureReceipt,
50
54
  type CapturePlan,
@@ -70,7 +74,11 @@ import { resolveNotePreset } from "../core/note-presets";
70
74
  import { extractSections } from "../core/sections";
71
75
  import { normalizeStructuredQueryInput } from "../core/structured-query";
72
76
  import { parseAndValidateTagFilter } from "../core/tags";
73
- import { defaultSyncService, type SyncResult } from "../ingestion";
77
+ import {
78
+ defaultSyncService,
79
+ type SyncResult,
80
+ withContentTypeRules,
81
+ } from "../ingestion";
74
82
  import { updateFrontmatterTags } from "../ingestion/frontmatter";
75
83
  import { LlmAdapter } from "../llm/nodeLlamaCpp/adapter";
76
84
  import { resolveDownloadPolicy } from "../llm/policy";
@@ -141,7 +149,7 @@ async function resolveClientState(
141
149
  parsed.error.issues[0]?.message ?? "Invalid config"
142
150
  );
143
151
  }
144
- config = parsed.data;
152
+ config = normalizeConfigContentTypes(parsed.data).config;
145
153
  configPath = null;
146
154
  configSource = "inline";
147
155
  } else {
@@ -695,10 +703,17 @@ class GnoClientImpl implements GnoClient {
695
703
  async update(options: GnoUpdateOptions = {}): Promise<SyncResult> {
696
704
  this.assertOpen();
697
705
  const collections = this.getCollections(options.collection);
698
- return defaultSyncService.syncAll(collections, this.store, {
699
- gitPull: options.gitPull,
700
- runUpdateCmd: true,
701
- });
706
+ return defaultSyncService.syncAll(
707
+ collections,
708
+ this.store,
709
+ withContentTypeRules(
710
+ {
711
+ gitPull: options.gitPull,
712
+ runUpdateCmd: true,
713
+ },
714
+ this.config
715
+ )
716
+ );
702
717
  }
703
718
 
704
719
  async embed(options: GnoEmbedOptions = {}): Promise<GnoEmbedResult> {
@@ -808,10 +823,13 @@ class GnoClientImpl implements GnoClient {
808
823
  collection,
809
824
  this.store,
810
825
  [plan.relPath],
811
- {
812
- runUpdateCmd: false,
813
- gitPull: false,
814
- }
826
+ withContentTypeRules(
827
+ {
828
+ runUpdateCmd: false,
829
+ gitPull: false,
830
+ },
831
+ this.config
832
+ )
815
833
  );
816
834
  const syncResult = syncResults[0];
817
835
  if (!syncResult || syncResult.status === "error") {
@@ -903,10 +921,13 @@ class GnoClientImpl implements GnoClient {
903
921
  collection,
904
922
  this.store,
905
923
  [plan.relPath],
906
- {
907
- runUpdateCmd: false,
908
- gitPull: false,
909
- }
924
+ withContentTypeRules(
925
+ {
926
+ runUpdateCmd: false,
927
+ gitPull: false,
928
+ },
929
+ this.config
930
+ )
910
931
  );
911
932
  const syncResult = syncResults[0];
912
933
  const docResult = await this.store.getDocument(
@@ -987,9 +1008,11 @@ class GnoClientImpl implements GnoClient {
987
1008
  const currentPath = `${collection.path}/${storedDoc.relPath}`;
988
1009
  const nextPath = `${collection.path}/${plan.nextRelPath}`;
989
1010
  await renameFilePath(currentPath, nextPath);
990
- await defaultSyncService.syncCollection(collection, this.store, {
991
- runUpdateCmd: false,
992
- });
1011
+ await defaultSyncService.syncCollection(
1012
+ collection,
1013
+ this.store,
1014
+ withContentTypeRules({ runUpdateCmd: false }, this.config)
1015
+ );
993
1016
  const linksResult = await this.store.getLinksForDoc(storedDoc.id);
994
1017
  const backlinksResult = await this.store.getBacklinksForDoc(storedDoc.id);
995
1018
  if (!linksResult.ok || !backlinksResult.ok) {
@@ -1044,9 +1067,11 @@ class GnoClientImpl implements GnoClient {
1044
1067
  const nextPath = `${collection.path}/${plan.nextRelPath}`;
1045
1068
  await mkdir(dirname(nextPath), { recursive: true });
1046
1069
  await renameFilePath(currentPath, nextPath);
1047
- await defaultSyncService.syncCollection(collection, this.store, {
1048
- runUpdateCmd: false,
1049
- });
1070
+ await defaultSyncService.syncCollection(
1071
+ collection,
1072
+ this.store,
1073
+ withContentTypeRules({ runUpdateCmd: false }, this.config)
1074
+ );
1050
1075
  const linksResult = await this.store.getLinksForDoc(storedDoc.id);
1051
1076
  const backlinksResult = await this.store.getBacklinksForDoc(storedDoc.id);
1052
1077
  if (!linksResult.ok || !backlinksResult.ok) {
@@ -1113,9 +1138,11 @@ class GnoClientImpl implements GnoClient {
1113
1138
  const nextPath = `${collection.path}/${plan.nextRelPath}`;
1114
1139
  await mkdir(dirname(nextPath), { recursive: true });
1115
1140
  await copyFilePath(currentPath, nextPath);
1116
- await defaultSyncService.syncCollection(collection, this.store, {
1117
- runUpdateCmd: false,
1118
- });
1141
+ await defaultSyncService.syncCollection(
1142
+ collection,
1143
+ this.store,
1144
+ withContentTypeRules({ runUpdateCmd: false }, this.config)
1145
+ );
1119
1146
  const linksResult = await this.store.getLinksForDoc(storedDoc.id);
1120
1147
  const backlinksResult = await this.store.getBacklinksForDoc(storedDoc.id);
1121
1148
  if (!linksResult.ok || !backlinksResult.ok) {
@@ -13,8 +13,8 @@ import type {
13
13
  MultiGetResponse,
14
14
  SkippedDoc,
15
15
  } from "../cli/commands/multi-get";
16
- import type { ParsedRef } from "../cli/commands/ref-parser";
17
16
  import type { Config } from "../config/types";
17
+ import type { ParsedRef } from "../core/ref-parser";
18
18
  import type { DocumentRow, StorePort } from "../store/types";
19
19
  import type {
20
20
  GnoGetOptions,
@@ -22,8 +22,8 @@ import type {
22
22
  GnoMultiGetOptions,
23
23
  } from "./types";
24
24
 
25
- import { isGlobPattern, parseRef, splitRefs } from "../cli/commands/ref-parser";
26
25
  import { getDocumentCapabilities } from "../core/document-capabilities";
26
+ import { isGlobPattern, parseRef, splitRefs } from "../core/ref-parser";
27
27
  import { sdkError } from "./errors";
28
28
 
29
29
  const URI_PREFIX_PATTERN = /^gno:\/\/[^/]+\//;
@@ -11,11 +11,13 @@ import type {
11
11
  import { getIndexDbPath } from "../app/constants";
12
12
  import {
13
13
  ensureDirectories,
14
+ formatConfigWarnings,
14
15
  getConfigPaths,
15
16
  isInitialized,
16
17
  loadConfig,
17
18
  } from "../config";
18
19
  import { defaultSyncService } from "../ingestion";
20
+ import { withContentTypeRules } from "../ingestion";
19
21
  import { getActivePreset } from "../llm/registry";
20
22
  import { SqliteAdapter } from "../store/sqlite/adapter";
21
23
  import {
@@ -79,6 +81,7 @@ type BackgroundRuntimeDeps = {
79
81
  scheduler: EmbedScheduler | null;
80
82
  eventBus?: DocumentEventBus | null;
81
83
  callbacks?: CollectionWatchCallbacks;
84
+ syncOptions?: Parameters<typeof withContentTypeRules>[0];
82
85
  }) => CollectionWatchService;
83
86
  };
84
87
 
@@ -103,6 +106,9 @@ export async function startBackgroundRuntime(
103
106
  if (!configResult.ok) {
104
107
  return { success: false, error: configResult.error.message };
105
108
  }
109
+ for (const warning of formatConfigWarnings(configResult.warnings)) {
110
+ console.warn(warning);
111
+ }
106
112
  const config = configResult.value;
107
113
 
108
114
  if (options.requireCollections && config.collections.length === 0) {
@@ -171,6 +177,7 @@ export async function startBackgroundRuntime(
171
177
  scheduler,
172
178
  eventBus: options.eventBus ?? null,
173
179
  callbacks: options.watchCallbacks,
180
+ syncOptions: withContentTypeRules({}, config),
174
181
  });
175
182
  watchService.start();
176
183
  ctxHolder.watchService = watchService;
@@ -189,10 +196,17 @@ export async function startBackgroundRuntime(
189
196
  eventBus: options.eventBus ?? null,
190
197
  watchService,
191
198
  async syncAll(syncOptions = {}) {
192
- const syncResult = await syncAllService(config.collections, store, {
193
- gitPull: syncOptions.gitPull,
194
- runUpdateCmd: syncOptions.runUpdateCmd,
195
- });
199
+ const syncResult = await syncAllService(
200
+ config.collections,
201
+ store,
202
+ withContentTypeRules(
203
+ {
204
+ gitPull: syncOptions.gitPull,
205
+ runUpdateCmd: syncOptions.runUpdateCmd,
206
+ },
207
+ config
208
+ )
209
+ );
196
210
 
197
211
  let embedResult: EmbedResult | null = null;
198
212
  if (syncOptions.triggerEmbed !== false) {
@@ -14,6 +14,7 @@ import {
14
14
  type ApplyConfigResult,
15
15
  type MutationResult,
16
16
  } from "../core/config-mutation";
17
+ import { withContentTypeRules } from "../ingestion";
17
18
 
18
19
  /**
19
20
  * Apply a config mutation atomically with serialization.
@@ -53,7 +54,10 @@ export async function applyConfigChange(
53
54
  );
54
55
 
55
56
  if (result.ok) {
56
- ctxHolder.watchService?.updateCollections(ctxHolder.config.collections);
57
+ ctxHolder.watchService?.updateCollections(
58
+ ctxHolder.config.collections,
59
+ withContentTypeRules({}, ctxHolder.config)
60
+ );
57
61
  }
58
62
 
59
63
  return result;
@@ -78,7 +82,10 @@ export async function applyConfigChangeTyped<T>(
78
82
  );
79
83
 
80
84
  if (result.ok) {
81
- ctxHolder.watchService?.updateCollections(ctxHolder.config.collections);
85
+ ctxHolder.watchService?.updateCollections(
86
+ ctxHolder.config.collections,
87
+ withContentTypeRules({}, ctxHolder.config)
88
+ );
82
89
  }
83
90
 
84
91
  return result;