@gmickel/gno 1.46.0 → 2.0.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 (230) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +46 -0
  3. package/assets/skill/SKILL.md +7 -6
  4. package/assets/spa-production.json.gz +0 -0
  5. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
  6. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
  7. package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
  8. package/browser-extension/dist/manifest.json +1 -1
  9. package/browser-extension/dist/preview.html +1 -1
  10. package/browser-extension/dist/service-worker.js +32 -33
  11. package/bunfig.toml +2 -0
  12. package/package.json +40 -26
  13. package/spec/cli.md +21 -4
  14. package/spec/db/schema.sql +146 -1
  15. package/spec/mcp.md +26 -0
  16. package/src/app/context-runtime-types.ts +3 -0
  17. package/src/app/context-runtime.ts +2 -0
  18. package/src/cli/commands/ask.ts +6 -1
  19. package/src/cli/commands/daemon.ts +21 -8
  20. package/src/cli/commands/embed.ts +77 -41
  21. package/src/cli/detach.ts +3 -2
  22. package/src/config/types.ts +3 -3
  23. package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
  24. package/src/converters/adapters/officeparser/adapter.ts +1 -2
  25. package/src/converters/versions.ts +6 -8
  26. package/src/core/context-evidence.ts +8 -4
  27. package/src/core/job-manager.ts +95 -13
  28. package/src/core/network-boundary-inventory.ts +10 -0
  29. package/src/core/shutdown-budget.ts +45 -0
  30. package/src/embed/backlog.ts +107 -4
  31. package/src/embed/batch.ts +42 -2
  32. package/src/embed/fingerprint.ts +16 -0
  33. package/src/embed/retry.ts +113 -5
  34. package/src/embed/variant-backlog.ts +105 -0
  35. package/src/embed/variant-plan.ts +62 -0
  36. package/src/embed/variant-retry.ts +113 -0
  37. package/src/ingestion/graph-reconciliation.ts +327 -0
  38. package/src/ingestion/sync.ts +9 -272
  39. package/src/llm/http-inference.ts +6 -0
  40. package/src/llm/httpEmbedding.ts +37 -6
  41. package/src/llm/httpGeneration.ts +18 -3
  42. package/src/llm/httpRerank.ts +23 -5
  43. package/src/llm/inference-cancellation.ts +168 -0
  44. package/src/llm/inference-scope.ts +202 -0
  45. package/src/llm/lazy-ports.ts +115 -0
  46. package/src/llm/native-worker/client.ts +541 -0
  47. package/src/llm/native-worker/dispatcher.ts +228 -0
  48. package/src/llm/native-worker/embedding-identity.ts +33 -0
  49. package/src/llm/native-worker/entry.ts +173 -0
  50. package/src/llm/native-worker/errors.ts +32 -0
  51. package/src/llm/native-worker/evaluation.ts +16 -0
  52. package/src/llm/native-worker/owned-exit.ts +108 -0
  53. package/src/llm/native-worker/owner.ts +141 -0
  54. package/src/llm/native-worker/ports.ts +317 -0
  55. package/src/llm/native-worker/protocol.ts +442 -0
  56. package/src/llm/native-worker/runtime-config.ts +92 -0
  57. package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
  58. package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
  59. package/src/llm/nodeLlamaCpp/generation.ts +34 -5
  60. package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
  61. package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
  62. package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
  63. package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
  64. package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
  65. package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
  66. package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
  67. package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
  68. package/src/llm/types.ts +35 -5
  69. package/src/mcp/context.ts +27 -0
  70. package/src/mcp/http-transport.ts +12 -10
  71. package/src/mcp/server.ts +3 -0
  72. package/src/mcp/tool-profile.ts +30 -8
  73. package/src/mcp/tools/context.ts +8 -11
  74. package/src/mcp/tools/embed.ts +1 -1
  75. package/src/mcp/tools/index-cmd.ts +1 -1
  76. package/src/mcp/tools/index.ts +10 -8
  77. package/src/mcp/tools/query.ts +14 -30
  78. package/src/mcp/tools/vsearch.ts +1 -1
  79. package/src/pipeline/answer.ts +23 -3
  80. package/src/pipeline/claim-verifier.ts +6 -0
  81. package/src/pipeline/expansion.ts +43 -40
  82. package/src/pipeline/explain.ts +6 -2
  83. package/src/pipeline/filters.ts +63 -0
  84. package/src/pipeline/fusion.ts +29 -9
  85. package/src/pipeline/graph-retrieval.ts +29 -9
  86. package/src/pipeline/hybrid.ts +198 -55
  87. package/src/pipeline/hydration.ts +161 -0
  88. package/src/pipeline/owner-fusion.ts +87 -0
  89. package/src/pipeline/rerank.ts +35 -11
  90. package/src/pipeline/search.ts +13 -2
  91. package/src/pipeline/types.ts +5 -3
  92. package/src/pipeline/vsearch.ts +87 -7
  93. package/src/sdk/client.ts +47 -3
  94. package/src/sdk/embed.ts +63 -39
  95. package/src/serve/background-runtime.ts +1 -1
  96. package/src/serve/context.ts +41 -56
  97. package/src/serve/embed-scheduler.ts +58 -35
  98. package/src/serve/public/components/IndexingProgress.tsx +46 -60
  99. package/src/serve/public/globals.built.css +1 -1
  100. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  101. package/src/serve/resident-admission.ts +36 -36
  102. package/src/serve/resident-background-work.ts +20 -2
  103. package/src/serve/resident-request.ts +11 -5
  104. package/src/serve/resident-runtime.ts +97 -61
  105. package/src/serve/resident-shutdown.ts +153 -0
  106. package/src/serve/routes/api.ts +3 -1
  107. package/src/serve/server.ts +47 -26
  108. package/src/store/migrations/028-vector-variants.ts +54 -0
  109. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  110. package/src/store/migrations/index.ts +4 -0
  111. package/src/store/sqlite/adapter.ts +251 -183
  112. package/src/store/sqlite/eligibility.ts +174 -0
  113. package/src/store/sqlite/graph-edge-application.ts +66 -0
  114. package/src/store/sqlite/graph-reference-state.ts +194 -0
  115. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  116. package/src/store/types.ts +80 -12
  117. package/src/store/vector/eligibility.ts +36 -0
  118. package/src/store/vector/freshness.ts +33 -6
  119. package/src/store/vector/lazy.ts +81 -0
  120. package/src/store/vector/sqlite-vec.ts +106 -54
  121. package/src/store/vector/stats.ts +14 -3
  122. package/src/store/vector/types.ts +35 -2
  123. package/src/store/vector/variant-search.ts +192 -0
  124. package/src/store/vector/variants.ts +451 -0
  125. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  126. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  127. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  128. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  129. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  130. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  131. package/vendor/converters/markitdown-ts/package.json +77 -0
  132. package/vendor/converters/officeparser/LICENSE +21 -0
  133. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  134. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  135. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  136. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  137. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  138. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  139. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  140. package/vendor/converters/officeparser/dist/cli.js +381 -0
  141. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  142. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  143. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  144. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  145. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  146. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  147. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  148. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  149. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  150. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  151. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  152. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  153. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  154. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  155. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  156. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  157. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  158. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  159. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  160. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  161. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  162. package/vendor/converters/officeparser/dist/index.js +72 -0
  163. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  164. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  165. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  166. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  167. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  168. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  169. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  170. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  171. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  172. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  173. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  174. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  175. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  176. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  177. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  178. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  179. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  180. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  181. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  182. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  183. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  184. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  185. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  186. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  187. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  188. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  189. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  190. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  191. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  192. package/vendor/converters/officeparser/dist/types.js +107 -0
  193. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  194. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  195. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  196. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  197. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  198. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  199. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  200. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  201. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  202. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  203. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  204. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  205. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  206. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  207. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  208. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  209. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  210. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  211. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  212. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  213. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  214. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  215. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  216. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  217. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  218. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  219. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  220. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  221. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  222. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  223. package/vendor/converters/officeparser/package.json +147 -0
  224. package/vendor/converters/upstream-manifest.json +124 -0
  225. package/vendor/dependency-fixes/README.md +77 -0
  226. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  227. package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip +0 -0
  228. package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip.sha256 +0 -1
  229. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  230. /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
@@ -0,0 +1,113 @@
1
+ import type { EmbeddingPort } from "../llm/types";
2
+ import type { VectorOwnerInput } from "../store/vector/types";
3
+ import type { VectorVariantStore } from "../store/vector/variants";
4
+
5
+ import { err, ok } from "../store/types";
6
+ import { embedTextsWithRecovery } from "./batch";
7
+ import {
8
+ chunkRetryKey,
9
+ isUpsertLockContention,
10
+ upsertVectorsWithContentionRetry,
11
+ } from "./retry";
12
+
13
+ function isCurrent(
14
+ store: VectorVariantStore,
15
+ owner: VectorOwnerInput
16
+ ): boolean {
17
+ const current = store.current(owner.documentId, owner.seq);
18
+ return (
19
+ current?.mirrorHash === owner.mirrorHash &&
20
+ current.inputHash === owner.inputHash &&
21
+ current.formattedInput === owner.formattedInput
22
+ );
23
+ }
24
+
25
+ /** Exact inputs are inferred once; successful current owners checkpoint atomically. */
26
+ export async function embedVariantBatch(params: {
27
+ store: VectorVariantStore;
28
+ embedPort: EmbeddingPort;
29
+ owners: VectorOwnerInput[];
30
+ identityStillCurrent: () => boolean;
31
+ delays?: number[];
32
+ force?: boolean;
33
+ }): Promise<{
34
+ embedded: number;
35
+ errors: number;
36
+ contentionErrors: number;
37
+ retryOwners: VectorOwnerInput[];
38
+ }> {
39
+ const { store, embedPort, identityStillCurrent } = params;
40
+ const empty = {
41
+ embedded: 0,
42
+ errors: 0,
43
+ contentionErrors: 0,
44
+ retryOwners: [] as VectorOwnerInput[],
45
+ };
46
+ if (!identityStillCurrent()) return empty;
47
+ const owners = [
48
+ ...new Map(
49
+ params.owners.map((owner) => [chunkRetryKey(owner), owner])
50
+ ).values(),
51
+ ].filter((owner) => isCurrent(store, owner));
52
+ const inputs = new Map<string, string>();
53
+ const reusable = new Set<string>();
54
+ for (const owner of owners) {
55
+ if (!params.force && store.reusable(owner)) reusable.add(owner.inputHash);
56
+ else inputs.set(owner.inputHash, owner.formattedInput);
57
+ }
58
+ const keys = [...inputs.keys()];
59
+ const result = await embedTextsWithRecovery(embedPort, [...inputs.values()]);
60
+ if (!identityStillCurrent()) return empty;
61
+ const current = owners.filter((owner) => isCurrent(store, owner));
62
+ const vectors = new Map<string, Float32Array>();
63
+ if (result.ok) {
64
+ for (const [index, key] of keys.entries()) {
65
+ const vector = result.value.vectors[index];
66
+ if (vector) vectors.set(key, new Float32Array(vector));
67
+ }
68
+ }
69
+ const retryOwners = current.filter(
70
+ (owner) => !reusable.has(owner.inputHash) && !vectors.has(owner.inputHash)
71
+ );
72
+ const rows = current
73
+ .filter(
74
+ (owner) => reusable.has(owner.inputHash) || vectors.has(owner.inputHash)
75
+ )
76
+ .map((owner) => ({ owner, embedding: vectors.get(owner.inputHash) }));
77
+ if (!rows.length) return { ...empty, retryOwners };
78
+ let written = 0;
79
+ const persisted = await upsertVectorsWithContentionRetry(
80
+ {
81
+ upsertVectors: () => {
82
+ try {
83
+ // A contention wait may allow document or runtime mutations. Revalidate each attempt.
84
+ if (!identityStillCurrent()) return Promise.resolve(ok(undefined));
85
+ const valid = rows.filter(({ owner }) => isCurrent(store, owner));
86
+ store.write(valid);
87
+ written = valid.length;
88
+ return Promise.resolve(ok(undefined));
89
+ } catch (cause) {
90
+ return Promise.resolve(
91
+ err(
92
+ "QUERY_FAILED",
93
+ cause instanceof Error ? cause.message : String(cause),
94
+ cause
95
+ )
96
+ );
97
+ }
98
+ },
99
+ },
100
+ [],
101
+ params.delays
102
+ );
103
+ if (!persisted.ok)
104
+ return {
105
+ ...empty,
106
+ retryOwners,
107
+ errors: isUpsertLockContention(persisted.error) ? 0 : rows.length,
108
+ contentionErrors: isUpsertLockContention(persisted.error)
109
+ ? rows.length
110
+ : 0,
111
+ };
112
+ return { ...empty, embedded: written, retryOwners };
113
+ }
@@ -0,0 +1,327 @@
1
+ /** Incremental graph inventory closure; full projection remains the repair oracle. */
2
+ import type {
3
+ DocEdgeInput,
4
+ DocumentRow,
5
+ GraphReferenceDocument,
6
+ GraphReferenceInventory,
7
+ StorePort,
8
+ } from "../store/types";
9
+ import type { SyncOptions } from "./types";
10
+
11
+ import {
12
+ isRelationMap,
13
+ normalizeRelationEdgeType,
14
+ normalizeRelationTarget,
15
+ } from "../core/change-diff";
16
+ import {
17
+ normalizeMarkdownPath,
18
+ normalizeWikiName,
19
+ parseTargetParts,
20
+ } from "../core/links";
21
+ import { parseFrontmatter } from "./frontmatter";
22
+
23
+ const VERSION = 1;
24
+ const EDGE_TYPE = /^[a-z][a-z0-9_]*$/;
25
+ type ProjectionError = { relPath: string; code: string; message: string };
26
+
27
+ function identity(doc: DocumentRow): GraphReferenceDocument {
28
+ return {
29
+ documentId: doc.id,
30
+ collection: doc.collection,
31
+ relPath: doc.relPath,
32
+ docid: doc.docid,
33
+ uri: doc.uri,
34
+ title: doc.title,
35
+ mirrorHash: doc.mirrorHash,
36
+ sourceHash: doc.sourceHash,
37
+ contentType: doc.contentType ?? null,
38
+ };
39
+ }
40
+
41
+ /** Insert once in catalog order: preserves legacy Array.find ambiguity precedence. */
42
+ function relationResolver(docs: GraphReferenceDocument[]) {
43
+ const docids = new Map<string, GraphReferenceDocument>();
44
+ const uris = new Map<string, GraphReferenceDocument>();
45
+ const paths = new Map<string, GraphReferenceDocument>();
46
+ const wiki = new Map<string, GraphReferenceDocument>();
47
+ const localWiki = new Map<string, GraphReferenceDocument>();
48
+ const put = (
49
+ map: Map<string, GraphReferenceDocument>,
50
+ key: string,
51
+ doc: GraphReferenceDocument
52
+ ) => {
53
+ if (!map.has(key)) map.set(key, doc);
54
+ };
55
+ for (const doc of docs) {
56
+ put(docids, doc.docid, doc);
57
+ put(uris, doc.uri, doc);
58
+ put(paths, `${doc.collection}/${doc.relPath}`, doc);
59
+ const title = doc.title ?? doc.relPath.split("/").pop() ?? doc.relPath;
60
+ for (const value of [
61
+ title,
62
+ doc.relPath,
63
+ doc.relPath.replace(/\.[^/.]+$/, ""),
64
+ ]) {
65
+ const key = normalizeWikiName(value);
66
+ put(wiki, key, doc);
67
+ put(localWiki, `${doc.collection}\0${key}`, doc);
68
+ }
69
+ }
70
+ return (
71
+ source: GraphReferenceDocument,
72
+ raw: string
73
+ ): GraphReferenceDocument | undefined => {
74
+ const target = normalizeRelationTarget(raw);
75
+ if (!target) return;
76
+ if (target.startsWith("#")) return docids.get(target);
77
+ if (target.startsWith("gno://")) return uris.get(target);
78
+ const parts = parseTargetParts(target);
79
+ if (!parts.ref) return;
80
+ const key = normalizeWikiName(parts.ref);
81
+ if (parts.collection)
82
+ return (
83
+ paths.get(`${parts.collection}/${parts.ref}`) ??
84
+ localWiki.get(`${parts.collection}\0${key}`)
85
+ );
86
+ const relativePath = normalizeMarkdownPath(parts.ref, source.relPath);
87
+ return (
88
+ (relativePath
89
+ ? paths.get(`${source.collection}/${relativePath}`)
90
+ : undefined) ??
91
+ paths.get(parts.ref) ??
92
+ localWiki.get(`${source.collection}\0${key}`) ??
93
+ wiki.get(key)
94
+ );
95
+ };
96
+ }
97
+
98
+ export async function projectGraph(
99
+ store: StorePort,
100
+ options: SyncOptions,
101
+ requestedSources?: Set<number>,
102
+ forceFull = false
103
+ ): Promise<ProjectionError[]> {
104
+ const errors: ProjectionError[] = [];
105
+ try {
106
+ const graph = store.graphReferenceStore?.();
107
+ let fingerprint = "";
108
+ if (graph) {
109
+ const collections = await store.getCollections();
110
+ if (!collections.ok) throw new Error(collections.error.message);
111
+ fingerprint = new Bun.CryptoHasher("sha256")
112
+ .update(
113
+ JSON.stringify({
114
+ rules: options.contentTypeRules ?? [],
115
+ collections: collections.value
116
+ .map(({ syncedAt: _syncedAt, ...config }) => config)
117
+ .sort((a, b) => a.name.localeCompare(b.name)),
118
+ })
119
+ )
120
+ .digest("hex");
121
+ }
122
+ const state = graph?.state(VERSION, fingerprint);
123
+ if (!forceFull && state?.complete) return [];
124
+ const documents = await store.listDocuments();
125
+ if (!documents.ok) throw new Error(documents.error.message);
126
+ const activeDocs = documents.value.filter((doc) => doc.active);
127
+ const current = activeDocs.map(identity);
128
+ const currentById = new Map(current.map((doc) => [doc.documentId, doc]));
129
+ // Read before begin: a new version/config deliberately resets the old inventory.
130
+ const previous = graph?.readInventory() ?? [];
131
+ const previousById = new Map(
132
+ previous.map((row) => [row.document.documentId, row])
133
+ );
134
+ const full =
135
+ forceFull ||
136
+ !graph ||
137
+ !state ||
138
+ state.inProgress ||
139
+ state.version !== VERSION ||
140
+ state.configFingerprint !== fingerprint;
141
+ const resolve = relationResolver(current);
142
+ let selected: Set<number> | undefined;
143
+ if (!full && graph) {
144
+ selected = new Set(requestedSources);
145
+ const changed = new Map<number, GraphReferenceDocument[]>();
146
+ for (const doc of current) {
147
+ const old = previousById.get(doc.documentId)?.document;
148
+ if (!old || JSON.stringify(old) !== JSON.stringify(doc)) {
149
+ changed.set(doc.documentId, old ? [old, doc] : [doc]);
150
+ selected.add(doc.documentId);
151
+ }
152
+ }
153
+ for (const row of previous) {
154
+ if (!currentById.has(row.document.documentId)) {
155
+ changed.set(row.document.documentId, [row.document]);
156
+ selected.add(row.document.documentId);
157
+ }
158
+ }
159
+ // Input mutation without a changed identity can be a direct parsed-link edit.
160
+ // No durable per-source link journal exists; conservatively rebuild in that case.
161
+ if (changed.size === 0 && state.dirty) selected = undefined;
162
+ else {
163
+ for (const id of graph.incomingLinkSources(
164
+ [...changed.values()].flat()
165
+ ))
166
+ selected.add(id);
167
+ const resolveOld = relationResolver(
168
+ previous.map((row) => row.document)
169
+ );
170
+ for (const row of previous) {
171
+ const source = currentById.get(row.document.documentId);
172
+ if (!source || selected.has(source.documentId)) continue;
173
+ if (
174
+ row.references.some(
175
+ (ref) =>
176
+ resolveOld(row.document, ref.target)?.documentId !==
177
+ resolve(source, ref.target)?.documentId
178
+ )
179
+ )
180
+ selected.add(source.documentId);
181
+ }
182
+ }
183
+ }
184
+ // Persist interruption authority before starting the projection transaction.
185
+ const epoch = graph?.begin(VERSION, fingerprint);
186
+ if (state && epoch !== state.epoch + 1)
187
+ throw new Error("Graph inputs changed while computing affected sources");
188
+ const work = async () => {
189
+ const targets = selected
190
+ ? documents.value.filter((doc) => selected.has(doc.id))
191
+ : documents.value;
192
+ // Configured sources get their final sets below. Applying defaults first
193
+ // would delete/recreate an unchanged configured edge on every repair pass.
194
+ const defaults = targets.filter((doc) => {
195
+ const hint = options.contentTypeRules?.find(
196
+ (rule) => rule.id === doc.contentType
197
+ )?.graphHints?.[0];
198
+ return !doc.active || !hint || !EDGE_TYPE.test(hint);
199
+ });
200
+ const backfill = await store.backfillDocEdges(
201
+ defaults.map((doc) => doc.id)
202
+ );
203
+ if (!backfill.ok) throw new Error(backfill.error.message);
204
+ for (const [index, doc] of targets.entries()) {
205
+ if (index > 0 && index % 25 === 0) await Bun.sleep(0);
206
+ if (!doc.active) {
207
+ const cleared = await store.setDocEdges(
208
+ doc.id,
209
+ [],
210
+ "frontmatter-relation"
211
+ );
212
+ if (!cleared.ok)
213
+ errors.push({ relPath: doc.relPath, ...cleared.error });
214
+ continue;
215
+ }
216
+ const snapshot = currentById.get(doc.id)!;
217
+ const cached = selected ? previousById.get(doc.id) : undefined;
218
+ let references: GraphReferenceInventory["references"];
219
+ if (
220
+ cached &&
221
+ cached.document.mirrorHash === doc.mirrorHash &&
222
+ cached.document.sourceHash === doc.sourceHash
223
+ )
224
+ references = cached.references;
225
+ else {
226
+ references = [];
227
+ if (doc.mirrorHash) {
228
+ const content = await store.getContent(doc.mirrorHash);
229
+ if (!content.ok || content.value === null) {
230
+ errors.push({
231
+ relPath: doc.relPath,
232
+ code: "QUERY_FAILED",
233
+ message: content.ok
234
+ ? "Missing graph source content"
235
+ : content.error.message,
236
+ });
237
+ continue;
238
+ }
239
+ const relations = parseFrontmatter(content.value).metadata
240
+ .relations;
241
+ if (isRelationMap(relations)) {
242
+ for (const [rawType, refs] of Object.entries(relations)) {
243
+ const edgeType = normalizeRelationEdgeType(rawType);
244
+ if (EDGE_TYPE.test(edgeType)) {
245
+ for (const target of refs)
246
+ references.push({ edgeType, target });
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ const relationEdges: DocEdgeInput[] = [];
253
+ for (const ref of references) {
254
+ const target = resolve(snapshot, ref.target);
255
+ if (target)
256
+ relationEdges.push({
257
+ targetDocId: target.documentId,
258
+ edgeType: ref.edgeType,
259
+ confidence: "manual",
260
+ });
261
+ }
262
+ const relationsResult = await store.setDocEdges(
263
+ doc.id,
264
+ relationEdges,
265
+ "frontmatter-relation"
266
+ );
267
+ if (!relationsResult.ok)
268
+ errors.push({ relPath: doc.relPath, ...relationsResult.error });
269
+ graph?.writeInventory({ document: snapshot, references });
270
+ const hint = options.contentTypeRules?.find(
271
+ (rule) => rule.id === doc.contentType
272
+ )?.graphHints?.[0];
273
+ if (!hint || !EDGE_TYPE.test(hint)) continue;
274
+ const links = await store.getLinksForDoc(doc.id);
275
+ if (!links.ok) {
276
+ errors.push({ relPath: doc.relPath, ...links.error });
277
+ continue;
278
+ }
279
+ const relationIds = new Set(
280
+ relationEdges.map((edge) => edge.targetDocId)
281
+ );
282
+ const wikiEdges: DocEdgeInput[] = [];
283
+ const markdownEdges: DocEdgeInput[] = [];
284
+ for (const link of links.value) {
285
+ const ref =
286
+ link.linkType === "markdown"
287
+ ? `${doc.collection}/${link.targetRefNorm}`
288
+ : link.targetCollection
289
+ ? `${link.targetCollection}:${link.targetRef}`
290
+ : link.targetRefNorm;
291
+ const target = resolve(snapshot, ref);
292
+ if (!target || relationIds.has(target.documentId)) continue;
293
+ const edge: DocEdgeInput = {
294
+ targetDocId: target.documentId,
295
+ edgeType: hint,
296
+ confidence: "configured",
297
+ };
298
+ (link.linkType === "wiki" ? wikiEdges : markdownEdges).push(edge);
299
+ }
300
+ for (const [source, edges] of [
301
+ ["wikilink", wikiEdges],
302
+ ["markdown-link", markdownEdges],
303
+ ] as const) {
304
+ const result = await store.setDocEdges(doc.id, edges, source);
305
+ if (!result.ok)
306
+ errors.push({ relPath: doc.relPath, ...result.error });
307
+ }
308
+ }
309
+ if (errors.length) throw new Error("Graph projection incomplete");
310
+ if (graph && epoch !== undefined) graph.complete(epoch);
311
+ };
312
+ if (store.withTransaction) {
313
+ const result = await store.withTransaction(work);
314
+ if (!result.ok && errors.length === 0)
315
+ throw new Error(result.error.message);
316
+ } else await work();
317
+ } catch (cause) {
318
+ if (errors.length === 0)
319
+ errors.push({
320
+ relPath: "(typed edge projection)",
321
+ code: "QUERY_FAILED",
322
+ message:
323
+ cause instanceof Error ? cause.message : "Graph projection failed",
324
+ });
325
+ }
326
+ return errors;
327
+ }