@gmickel/gno 1.2.0 → 1.3.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 (43) hide show
  1. package/README.md +1 -1
  2. package/assets/skill/SKILL.md +3 -0
  3. package/assets/skill/cli-reference.md +5 -0
  4. package/assets/skill/examples.md +2 -0
  5. package/package.json +1 -1
  6. package/src/app/constants.ts +64 -8
  7. package/src/cli/commands/embed.ts +6 -2
  8. package/src/cli/commands/get.ts +15 -5
  9. package/src/cli/commands/index-cmd.ts +4 -0
  10. package/src/cli/commands/multi-get.ts +62 -1
  11. package/src/cli/commands/query.ts +8 -2
  12. package/src/cli/commands/search.ts +8 -2
  13. package/src/cli/commands/shared.ts +18 -1
  14. package/src/cli/commands/status.ts +4 -2
  15. package/src/cli/commands/update.ts +6 -1
  16. package/src/cli/commands/vsearch.ts +8 -2
  17. package/src/cli/format/search-results.ts +1 -1
  18. package/src/cli/program.ts +22 -1
  19. package/src/ingestion/chunker.ts +6 -0
  20. package/src/llm/cache.ts +162 -28
  21. package/src/llm/errors.ts +32 -0
  22. package/src/llm/lockfile.ts +49 -4
  23. package/src/llm/nodeLlamaCpp/embedding.ts +69 -3
  24. package/src/llm/nodeLlamaCpp/lifecycle.ts +60 -4
  25. package/src/mcp/resources/index.ts +13 -4
  26. package/src/mcp/server.ts +2 -0
  27. package/src/mcp/tools/get.ts +7 -2
  28. package/src/mcp/tools/multi-get.ts +2 -2
  29. package/src/mcp/tools/query.ts +2 -1
  30. package/src/mcp/tools/search.ts +2 -1
  31. package/src/mcp/tools/vsearch.ts +2 -1
  32. package/src/pipeline/explain.ts +12 -2
  33. package/src/pipeline/hybrid.ts +9 -1
  34. package/src/pipeline/search.ts +16 -7
  35. package/src/pipeline/types.ts +2 -0
  36. package/src/pipeline/vsearch.ts +29 -15
  37. package/src/publish/export-service.ts +27 -2
  38. package/src/sdk/client.ts +83 -28
  39. package/src/store/content-batch.ts +38 -0
  40. package/src/store/sqlite/adapter.ts +38 -2
  41. package/src/store/types.ts +8 -0
  42. package/src/store/vector/sqlite-vec.ts +10 -4
  43. package/src/store/vector/types.ts +2 -0
@@ -0,0 +1,38 @@
1
+ import type { StorePort, StoreResult } from "./types";
2
+
3
+ import { err, ok } from "./types";
4
+
5
+ /**
6
+ * Load content in batch when the store supports it.
7
+ * Falls back to sequential reads for lightweight test doubles.
8
+ */
9
+ export async function getContentBatch(
10
+ store: StorePort,
11
+ mirrorHashes: string[]
12
+ ): Promise<StoreResult<Map<string, string>>> {
13
+ const uniqueHashes = [...new Set(mirrorHashes)];
14
+ if (uniqueHashes.length === 0) {
15
+ return ok(new Map());
16
+ }
17
+
18
+ if (store.getContentBatch) {
19
+ return store.getContentBatch(uniqueHashes);
20
+ }
21
+
22
+ const contentByHash = new Map<string, string>();
23
+ for (const mirrorHash of uniqueHashes) {
24
+ const contentResult = await store.getContent(mirrorHash);
25
+ if (!contentResult.ok) {
26
+ return err(
27
+ "QUERY_FAILED",
28
+ contentResult.error.message,
29
+ contentResult.error.cause
30
+ );
31
+ }
32
+ if (contentResult.value !== null) {
33
+ contentByHash.set(mirrorHash, contentResult.value);
34
+ }
35
+ }
36
+
37
+ return ok(contentByHash);
38
+ }
@@ -42,7 +42,7 @@ import type {
42
42
  } from "../types";
43
43
  import type { SqliteDbProvider } from "./types";
44
44
 
45
- import { buildUri, deriveDocid } from "../../app/constants";
45
+ import { buildUri, deriveDocid, stripUriIndex } from "../../app/constants";
46
46
  import { normalizeWikiName, stripWikiMdExt } from "../../core/links";
47
47
  import { migrations, runMigrations } from "../migrations";
48
48
  import { err, ok } from "../types";
@@ -675,9 +675,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
675
675
  ): Promise<StoreResult<DocumentRow | null>> {
676
676
  try {
677
677
  const db = this.ensureOpen();
678
+ const canonicalUri = stripUriIndex(uri);
678
679
  const row = db
679
680
  .query<DbDocumentRow, [string]>("SELECT * FROM documents WHERE uri = ?")
680
- .get(uri);
681
+ .get(canonicalUri);
681
682
 
682
683
  return ok(row ? mapDocumentRow(row) : null);
683
684
  } catch (cause) {
@@ -1048,6 +1049,41 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
1048
1049
  }
1049
1050
  }
1050
1051
 
1052
+ async getContentBatch(
1053
+ mirrorHashes: string[]
1054
+ ): Promise<StoreResult<Map<string, string>>> {
1055
+ try {
1056
+ const db = this.ensureOpen();
1057
+
1058
+ if (mirrorHashes.length === 0) {
1059
+ return ok(new Map());
1060
+ }
1061
+
1062
+ interface DbContentRow {
1063
+ mirror_hash: string;
1064
+ markdown: string;
1065
+ }
1066
+
1067
+ const placeholders = mirrorHashes.map(() => "?").join(", ");
1068
+ const rows = db
1069
+ .query<DbContentRow, string[]>(
1070
+ `SELECT mirror_hash, markdown FROM content
1071
+ WHERE mirror_hash IN (${placeholders})`
1072
+ )
1073
+ .all(...mirrorHashes);
1074
+
1075
+ return ok(
1076
+ new Map(rows.map((row) => [row.mirror_hash, row.markdown] as const))
1077
+ );
1078
+ } catch (cause) {
1079
+ return err(
1080
+ "QUERY_FAILED",
1081
+ cause instanceof Error ? cause.message : "Failed to get content batch",
1082
+ cause
1083
+ );
1084
+ }
1085
+ }
1086
+
1051
1087
  // ─────────────────────────────────────────────────────────────────────────
1052
1088
  // Chunks
1053
1089
  // ─────────────────────────────────────────────────────────────────────────
@@ -694,6 +694,14 @@ export interface StorePort {
694
694
  */
695
695
  getContent(mirrorHash: string): Promise<StoreResult<string | null>>;
696
696
 
697
+ /**
698
+ * Batch fetch markdown content for multiple mirror hashes.
699
+ * Returns a map of mirrorHash -> markdown for hashes that exist.
700
+ */
701
+ getContentBatch?(
702
+ mirrorHashes: string[]
703
+ ): Promise<StoreResult<Map<string, string>>>;
704
+
697
705
  // ─────────────────────────────────────────────────────────────────────────
698
706
  // Chunks
699
707
  // ─────────────────────────────────────────────────────────────────────────
@@ -66,6 +66,14 @@ export interface VectorIndexOptions {
66
66
  distanceMetric?: "cosine" | "l2";
67
67
  }
68
68
 
69
+ const SQLITE_VEC_GUIDANCE =
70
+ "Run `gno doctor` for sqlite-vec diagnostics. On macOS, verify Homebrew SQLite/sqlite-vec installation and see TROUBLESHOOTING.md.";
71
+
72
+ function formatUnavailableMessage(loadError?: string): string {
73
+ const reason = loadError ? ` Reason: ${loadError}` : "";
74
+ return `Vector search requires sqlite-vec. Embeddings are stored, but KNN search is disabled.${reason} ${SQLITE_VEC_GUIDANCE}`;
75
+ }
76
+
69
77
  /**
70
78
  * Create a VectorIndexPort for a specific model.
71
79
  * sqlite-vec is optional - storage works without it, search disabled.
@@ -147,6 +155,7 @@ export async function createVectorIndexPort(
147
155
  model,
148
156
  dimensions,
149
157
  loadError,
158
+ guidance: searchAvailable ? undefined : SQLITE_VEC_GUIDANCE,
150
159
  get vecDirty() {
151
160
  return vecDirty;
152
161
  },
@@ -235,10 +244,7 @@ export async function createVectorIndexPort(
235
244
  ): Promise<StoreResult<VectorSearchResult[]>> {
236
245
  if (!(searchAvailable && searchStmt)) {
237
246
  return Promise.resolve(
238
- err(
239
- "VEC_SEARCH_UNAVAILABLE",
240
- "Vector search requires sqlite-vec. Embeddings stored but KNN search disabled."
241
- )
247
+ err("VEC_SEARCH_UNAVAILABLE", formatUnavailableMessage(loadError))
242
248
  );
243
249
  }
244
250
 
@@ -60,6 +60,8 @@ export interface VectorIndexPort {
60
60
  readonly dimensions: number;
61
61
  /** Error message if sqlite-vec failed to load (for diagnostics) */
62
62
  readonly loadError?: string;
63
+ /** User-facing recovery guidance when search is unavailable */
64
+ readonly guidance?: string;
63
65
  /** True if vec0 inserts failed during this session (needs sync) */
64
66
  vecDirty: boolean;
65
67