@hraness/kb 0.17.1
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/DISCLOSURE +43 -0
- package/LICENSE +21 -0
- package/README.md +545 -0
- package/dist/agent-context.js +35 -0
- package/dist/agent-guide-audit.js +18 -0
- package/dist/attachments.js +19 -0
- package/dist/authoring.js +33 -0
- package/dist/benchmark.js +19 -0
- package/dist/capture.js +49 -0
- package/dist/cli.js +3646 -0
- package/dist/clip/acquire.js +57 -0
- package/dist/clip/args.js +21 -0
- package/dist/clip/bounded-byte-buffer.js +9 -0
- package/dist/clip/browser-profiles.js +17 -0
- package/dist/clip/bundle-reader.js +11 -0
- package/dist/clip/cli.js +27 -0
- package/dist/clip/cookies.js +21 -0
- package/dist/clip/defuddle-worker.js +84 -0
- package/dist/clip/doctor.js +32 -0
- package/dist/clip/jobs.js +784 -0
- package/dist/clip/network-proxy.js +9 -0
- package/dist/clip/network.js +28 -0
- package/dist/clip/persist.js +30 -0
- package/dist/clip/refresh.js +11 -0
- package/dist/clip/terminal.js +11 -0
- package/dist/evaluation-builder.js +12238 -0
- package/dist/evaluation-kb.js +26 -0
- package/dist/evaluation.js +35 -0
- package/dist/git.js +39 -0
- package/dist/graph.js +39 -0
- package/dist/index-01jj6rbv.js +1103 -0
- package/dist/index-06c9ctr6.js +738 -0
- package/dist/index-0kavxzqj.js +2480 -0
- package/dist/index-1gwbassd.js +743 -0
- package/dist/index-1n418kb9.js +827 -0
- package/dist/index-1vrd1rmn.js +56 -0
- package/dist/index-1xxnjn0d.js +171 -0
- package/dist/index-3rm7cz6h.js +373 -0
- package/dist/index-3v2z4f0q.js +328 -0
- package/dist/index-48pz4jpc.js +392 -0
- package/dist/index-4j3tt0c3.js +234 -0
- package/dist/index-5n05se68.js +307 -0
- package/dist/index-5vwpzb5a.js +764 -0
- package/dist/index-6g2pv9d2.js +370 -0
- package/dist/index-7fzc32gf.js +712 -0
- package/dist/index-84x0vjjp.js +432 -0
- package/dist/index-adx6khj5.js +488 -0
- package/dist/index-b88v3vtm.js +805 -0
- package/dist/index-cv6fh7z5.js +462 -0
- package/dist/index-cxfrakt7.js +1067 -0
- package/dist/index-d13v9ckt.js +150 -0
- package/dist/index-dycjbhw2.js +1943 -0
- package/dist/index-dyqwejk5.js +531 -0
- package/dist/index-e5fbsywq.js +658 -0
- package/dist/index-f984hw45.js +927 -0
- package/dist/index-gh719d91.js +63 -0
- package/dist/index-hgve9rh2.js +2046 -0
- package/dist/index-hya40gb2.js +474 -0
- package/dist/index-j4zgmzjr.js +152 -0
- package/dist/index-jsmvyyvf.js +1166 -0
- package/dist/index-kh0pwvdd.js +1974 -0
- package/dist/index-mqx4nd6v.js +119 -0
- package/dist/index-mxxxytys.js +1254 -0
- package/dist/index-n5dd7r0v.js +711 -0
- package/dist/index-npg9z1a4.js +431 -0
- package/dist/index-pj501bh1.js +140 -0
- package/dist/index-qry4vhxk.js +1041 -0
- package/dist/index-s2gw5aw9.js +199 -0
- package/dist/index-vvdj6kca.js +70 -0
- package/dist/index-w2zc0vwa.js +571 -0
- package/dist/index-x3fthpsc.js +585 -0
- package/dist/index-zr53sf63.js +44 -0
- package/dist/index-zxdy5pby.js +2728 -0
- package/dist/index-zzhgcwyt.js +966 -0
- package/dist/index.js +453 -0
- package/dist/navigation.js +13 -0
- package/dist/pdf.js +85 -0
- package/dist/percolate.js +24 -0
- package/dist/portfolio.js +77 -0
- package/dist/query.js +29 -0
- package/dist/repository-memory.js +60 -0
- package/dist/sdk.js +36 -0
- package/dist/search-rules.js +34 -0
- package/dist/search.js +22 -0
- package/dist/semantic.js +39 -0
- package/dist/source-inbox.js +17 -0
- package/dist/untrusted-content.js +32 -0
- package/dist/url-intelligence.js +74 -0
- package/dist/workflow.js +23 -0
- package/dist/workflows/decision-context.js +20 -0
- package/dist/workflows/explain-change.js +8 -0
- package/dist/workflows/index.js +28 -0
- package/dist/workflows/plan-radar.js +10 -0
- package/package.json +409 -0
- package/skills/kb/AGENTS.md +19 -0
- package/skills/kb/SKILL.md +82 -0
- package/skills/kb/agents/openai.yaml +4 -0
- package/skills/kb/references/pdf-review.md +51 -0
- package/skills/kb/references/percolate.md +144 -0
- package/skills/kb/references/plan-structure.md +105 -0
- package/skills/kb/references/plan.md +115 -0
- package/skills/kb/references/query.md +231 -0
- package/skills/kb/references/refresh.md +133 -0
- package/skills/kb/references/save-pdf.md +125 -0
- package/skills/kb/references/save-url.md +178 -0
- package/skills/kb/references/url-authentication.md +75 -0
- package/skills/kb/references/url-platforms.md +25 -0
- package/src/agent-context.ts +1241 -0
- package/src/agent-guide-audit.ts +750 -0
- package/src/attachments.ts +723 -0
- package/src/authoring.ts +1725 -0
- package/src/benchmark.ts +362 -0
- package/src/capture.ts +68 -0
- package/src/cli.ts +3660 -0
- package/src/clip/acquire.ts +1273 -0
- package/src/clip/archive-today.ts +402 -0
- package/src/clip/args.ts +423 -0
- package/src/clip/assets.ts +262 -0
- package/src/clip/bounded-byte-buffer.ts +75 -0
- package/src/clip/browser-profiles.ts +372 -0
- package/src/clip/bundle-reader.ts +608 -0
- package/src/clip/capture.ts +1054 -0
- package/src/clip/cli.ts +181 -0
- package/src/clip/cookies.ts +512 -0
- package/src/clip/doctor.ts +1110 -0
- package/src/clip/extract.ts +868 -0
- package/src/clip/jobs.ts +959 -0
- package/src/clip/lib.ts +861 -0
- package/src/clip/media.ts +1474 -0
- package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
- package/src/clip/metadata-search-tool/Cargo.toml +18 -0
- package/src/clip/metadata-search-tool/runner.ts +685 -0
- package/src/clip/metadata-search-tool/src/main.rs +389 -0
- package/src/clip/metadata-search.ts +687 -0
- package/src/clip/network-proxy.ts +627 -0
- package/src/clip/network.ts +950 -0
- package/src/clip/package-root.ts +54 -0
- package/src/clip/persist.ts +1472 -0
- package/src/clip/platforms.ts +1824 -0
- package/src/clip/refresh.ts +197 -0
- package/src/clip/structured.ts +432 -0
- package/src/clip/terminal.ts +197 -0
- package/src/clip/url-intelligence.ts +581 -0
- package/src/clip/url-metadata-backfill.ts +616 -0
- package/src/clip/url-metadata-cli.ts +251 -0
- package/src/clip/url-metadata.ts +1397 -0
- package/src/evaluation-analysis-v2.ts +1440 -0
- package/src/evaluation-builder.ts +2539 -0
- package/src/evaluation-corpus-authoring.ts +2052 -0
- package/src/evaluation-evidence.ts +1147 -0
- package/src/evaluation-execution-v2.ts +426 -0
- package/src/evaluation-implementation.ts +131 -0
- package/src/evaluation-kb-closure.ts +1430 -0
- package/src/evaluation-kb-runner-v2.ts +735 -0
- package/src/evaluation-kb-v2.ts +2567 -0
- package/src/evaluation-kb.ts +1018 -0
- package/src/evaluation-measurement-v2.ts +1462 -0
- package/src/evaluation-packing-v2.ts +452 -0
- package/src/evaluation-redaction.ts +28 -0
- package/src/evaluation-v2.ts +3857 -0
- package/src/evaluation.ts +1220 -0
- package/src/git.ts +1276 -0
- package/src/graph.ts +1487 -0
- package/src/index.ts +20 -0
- package/src/init.ts +123 -0
- package/src/navigation.ts +277 -0
- package/src/note-lock.ts +529 -0
- package/src/pdf/args.ts +202 -0
- package/src/pdf/capture.ts +298 -0
- package/src/pdf/cli.ts +159 -0
- package/src/pdf/extract.ts +303 -0
- package/src/pdf/layout.ts +618 -0
- package/src/pdf/markdown.ts +154 -0
- package/src/pdf/model.ts +234 -0
- package/src/pdf/ocr.ts +190 -0
- package/src/pdf/persist.ts +314 -0
- package/src/pdf/source.ts +135 -0
- package/src/pdf/tools.ts +146 -0
- package/src/pdf.ts +92 -0
- package/src/percolate.ts +877 -0
- package/src/portfolio-audit.ts +574 -0
- package/src/portfolio-identity.ts +162 -0
- package/src/portfolio-registry.ts +473 -0
- package/src/portfolio.ts +765 -0
- package/src/query.ts +646 -0
- package/src/repository-memory.ts +1150 -0
- package/src/sdk.ts +1452 -0
- package/src/search-rules.ts +712 -0
- package/src/search.ts +710 -0
- package/src/semantic-runtime.ts +1139 -0
- package/src/semantic.ts +2480 -0
- package/src/source-inbox.ts +232 -0
- package/src/untrusted-content.ts +401 -0
- package/src/url-intelligence.ts +104 -0
- package/src/vault.ts +636 -0
- package/src/workflow.ts +653 -0
- package/src/workflows/decision-context.ts +81 -0
- package/src/workflows/explain-change.ts +73 -0
- package/src/workflows/index.ts +3 -0
- package/src/workflows/plan-radar.ts +101 -0
package/src/portfolio.ts
ADDED
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
indexGitHistory,
|
|
5
|
+
type GitHistoryDependencies,
|
|
6
|
+
} from "./git.js";
|
|
7
|
+
import type { QueryRow } from "./query.js";
|
|
8
|
+
import { validateQueryOptions } from "./query.js";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_SEARCH_RESULTS,
|
|
11
|
+
MAX_SEARCH_CANDIDATES,
|
|
12
|
+
MAX_SEARCH_RESULTS,
|
|
13
|
+
openKnowledgeBase,
|
|
14
|
+
validateKnowledgeBaseSearchHistory,
|
|
15
|
+
type KnowledgeBaseDependencies,
|
|
16
|
+
type KnowledgeBaseGraphOptions,
|
|
17
|
+
type KnowledgeBaseHistoryOptions,
|
|
18
|
+
type KnowledgeBaseReadResult,
|
|
19
|
+
type KnowledgeBaseSearchDiagnostic,
|
|
20
|
+
type KnowledgeBaseSearchEvidence,
|
|
21
|
+
type KnowledgeBaseSearchHit,
|
|
22
|
+
type KnowledgeBaseSearchMode,
|
|
23
|
+
type KnowledgeBaseSearchOptions,
|
|
24
|
+
type KnowledgeBaseSearchRuleApplication,
|
|
25
|
+
type KnowledgeBaseSession,
|
|
26
|
+
type OpenKnowledgeBaseOptions,
|
|
27
|
+
} from "./sdk.js";
|
|
28
|
+
import { validateSearchQuery } from "./search.js";
|
|
29
|
+
import {
|
|
30
|
+
expandSearchRequest,
|
|
31
|
+
parseSearchRules,
|
|
32
|
+
} from "./search-rules.js";
|
|
33
|
+
import {
|
|
34
|
+
parseQualifiedDocumentUri,
|
|
35
|
+
portfolioDocumentIdentity,
|
|
36
|
+
type PortfolioDocumentIdentity,
|
|
37
|
+
type QualifiedDocumentUri,
|
|
38
|
+
type StablePortfolioDocumentIdentity,
|
|
39
|
+
type VaultKey,
|
|
40
|
+
} from "./portfolio-identity.js";
|
|
41
|
+
import {
|
|
42
|
+
loadPortfolioRegistry,
|
|
43
|
+
snapshotPortfolioRegistry,
|
|
44
|
+
resolvePortfolioVault,
|
|
45
|
+
selectAuthorizedVaults,
|
|
46
|
+
validateResolvedPortfolioVaults,
|
|
47
|
+
type PortfolioPathDependencies,
|
|
48
|
+
type PortfolioRegistryFileDependencies,
|
|
49
|
+
type PortfolioRegistryV1,
|
|
50
|
+
type PortfolioVaultEntry,
|
|
51
|
+
type ResolvedPortfolioVault,
|
|
52
|
+
} from "./portfolio-registry.js";
|
|
53
|
+
|
|
54
|
+
export const MAX_PORTFOLIO_SEARCH_CONCURRENCY = 8;
|
|
55
|
+
export const MAX_PORTFOLIO_REVISION_BYTES = 64 * 1_024;
|
|
56
|
+
|
|
57
|
+
export type PortfolioFailurePolicy = "partial" | "required";
|
|
58
|
+
|
|
59
|
+
export type PortfolioKnowledgeBaseOptions = Omit<
|
|
60
|
+
OpenKnowledgeBaseOptions,
|
|
61
|
+
"database" | "repository" | "root"
|
|
62
|
+
>;
|
|
63
|
+
|
|
64
|
+
export type OpenKnowledgePortfolioOptions = {
|
|
65
|
+
readonly registryPath: string;
|
|
66
|
+
/**
|
|
67
|
+
* An already loaded registry snapshot. Callers that derive authorization from
|
|
68
|
+
* registry contents must pass that same snapshot to prevent a reload race.
|
|
69
|
+
*/
|
|
70
|
+
readonly registry?: PortfolioRegistryV1;
|
|
71
|
+
readonly workspaceRoot: string;
|
|
72
|
+
/** Registry discovery never grants access; every usable vault must be named here. */
|
|
73
|
+
readonly authorizedVaults: readonly VaultKey[];
|
|
74
|
+
readonly failurePolicy?: PortfolioFailurePolicy;
|
|
75
|
+
readonly searchConcurrency?: number;
|
|
76
|
+
readonly knowledgeBase?: PortfolioKnowledgeBaseOptions;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type PortfolioDependencies = PortfolioRegistryFileDependencies & PortfolioPathDependencies & {
|
|
80
|
+
readonly loadPortfolioRegistry?: typeof loadPortfolioRegistry;
|
|
81
|
+
readonly resolvePortfolioVault?: typeof resolvePortfolioVault;
|
|
82
|
+
readonly openKnowledgeBase?: typeof openKnowledgeBase;
|
|
83
|
+
readonly knowledgeBase?: KnowledgeBaseDependencies;
|
|
84
|
+
readonly indexGitHistory?: typeof indexGitHistory;
|
|
85
|
+
readonly git?: GitHistoryDependencies;
|
|
86
|
+
readonly sha256?: (content: string) => string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type PortfolioVaultDescriptor = {
|
|
90
|
+
readonly owner: string;
|
|
91
|
+
readonly id: string;
|
|
92
|
+
readonly key: VaultKey;
|
|
93
|
+
readonly role: PortfolioVaultEntry["role"];
|
|
94
|
+
readonly visibility: PortfolioVaultEntry["visibility"];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type PortfolioRepositoryProvenance = {
|
|
98
|
+
readonly id: VaultKey;
|
|
99
|
+
readonly defaultRef?: string;
|
|
100
|
+
readonly head: string | null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export type PortfolioContentRevision =
|
|
104
|
+
| { readonly complete: true; readonly sha256: string }
|
|
105
|
+
| { readonly complete: false; readonly sha256: null };
|
|
106
|
+
|
|
107
|
+
export type PortfolioOpenDiagnostic = {
|
|
108
|
+
readonly vault: PortfolioVaultDescriptor;
|
|
109
|
+
readonly lane: "git" | "open" | "resolve";
|
|
110
|
+
readonly status: "unavailable";
|
|
111
|
+
readonly message: string;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type PortfolioSearchOptions = Omit<
|
|
115
|
+
KnowledgeBaseSearchOptions,
|
|
116
|
+
"candidateLimit" | "graph" | "limit"
|
|
117
|
+
> & {
|
|
118
|
+
readonly limit?: number;
|
|
119
|
+
readonly candidateLimit?: number;
|
|
120
|
+
/** Graph seeds are vault-local; unqualified cross-vault graph requests are rejected. */
|
|
121
|
+
readonly graph?: false;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type PortfolioSearchHit = {
|
|
125
|
+
readonly identity: PortfolioDocumentIdentity;
|
|
126
|
+
readonly vault: PortfolioVaultDescriptor;
|
|
127
|
+
readonly repository: PortfolioRepositoryProvenance;
|
|
128
|
+
readonly id: string;
|
|
129
|
+
readonly path: string;
|
|
130
|
+
readonly title: string;
|
|
131
|
+
readonly rank: number;
|
|
132
|
+
/** Reciprocal local-rank score used only for deterministic federation. */
|
|
133
|
+
readonly score: number;
|
|
134
|
+
readonly exactIdentity: boolean;
|
|
135
|
+
readonly localRank: number;
|
|
136
|
+
readonly revision: PortfolioContentRevision;
|
|
137
|
+
readonly snippet: string;
|
|
138
|
+
readonly line?: number;
|
|
139
|
+
readonly tags: readonly string[];
|
|
140
|
+
readonly metadata: KnowledgeBaseSearchHit["metadata"];
|
|
141
|
+
readonly evidence: readonly KnowledgeBaseSearchEvidence[];
|
|
142
|
+
readonly local: KnowledgeBaseSearchHit;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export type PortfolioVaultSearchDiagnostic = {
|
|
146
|
+
readonly vault: PortfolioVaultDescriptor;
|
|
147
|
+
readonly status: "partial" | "ready" | "unavailable";
|
|
148
|
+
readonly results: number;
|
|
149
|
+
readonly message?: string;
|
|
150
|
+
readonly lanes?: readonly KnowledgeBaseSearchDiagnostic[];
|
|
151
|
+
readonly rules?: KnowledgeBaseSearchRuleApplication;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export type PortfolioSearchResult = {
|
|
155
|
+
readonly query: string;
|
|
156
|
+
readonly mode: KnowledgeBaseSearchMode;
|
|
157
|
+
readonly results: readonly PortfolioSearchHit[];
|
|
158
|
+
readonly partial: boolean;
|
|
159
|
+
readonly diagnostics: {
|
|
160
|
+
readonly selectedVaults: number;
|
|
161
|
+
readonly availableVaults: number;
|
|
162
|
+
readonly notes: number;
|
|
163
|
+
readonly open: readonly PortfolioOpenDiagnostic[];
|
|
164
|
+
readonly vaults: readonly PortfolioVaultSearchDiagnostic[];
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export type PortfolioReadResult = KnowledgeBaseReadResult & {
|
|
169
|
+
readonly identity: StablePortfolioDocumentIdentity;
|
|
170
|
+
readonly vault: PortfolioVaultDescriptor;
|
|
171
|
+
readonly repository: PortfolioRepositoryProvenance;
|
|
172
|
+
readonly revision: PortfolioContentRevision;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type KnowledgePortfolioSession = {
|
|
176
|
+
readonly selectedVaultCount: number;
|
|
177
|
+
readonly availableVaultCount: number;
|
|
178
|
+
readonly noteCount: number;
|
|
179
|
+
readonly openDiagnostics: readonly PortfolioOpenDiagnostic[];
|
|
180
|
+
readonly search: (options: PortfolioSearchOptions) => Promise<PortfolioSearchResult>;
|
|
181
|
+
readonly read: (
|
|
182
|
+
uri: QualifiedDocumentUri | string,
|
|
183
|
+
options?: { readonly maxBytes?: number },
|
|
184
|
+
) => PortfolioReadResult;
|
|
185
|
+
readonly close: () => Promise<void>;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export class PortfolioOpenError extends Error {
|
|
189
|
+
readonly diagnostics: readonly PortfolioOpenDiagnostic[];
|
|
190
|
+
|
|
191
|
+
constructor(message: string, diagnostics: readonly PortfolioOpenDiagnostic[], options?: ErrorOptions) {
|
|
192
|
+
super(message, options);
|
|
193
|
+
this.name = "PortfolioOpenError";
|
|
194
|
+
this.diagnostics = Object.freeze([...diagnostics]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export class PortfolioSearchError extends Error {
|
|
199
|
+
readonly vault: PortfolioVaultDescriptor;
|
|
200
|
+
|
|
201
|
+
constructor(vault: PortfolioVaultDescriptor, message: string, options?: ErrorOptions) {
|
|
202
|
+
super(message, options);
|
|
203
|
+
this.name = "PortfolioSearchError";
|
|
204
|
+
this.vault = vault;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
type RepositoryProbe = {
|
|
209
|
+
readonly provenance: PortfolioRepositoryProvenance;
|
|
210
|
+
readonly diagnostic?: PortfolioOpenDiagnostic;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
type OpenedVault = {
|
|
214
|
+
readonly resolved: ResolvedPortfolioVault;
|
|
215
|
+
readonly descriptor: PortfolioVaultDescriptor;
|
|
216
|
+
readonly repository: PortfolioRepositoryProvenance;
|
|
217
|
+
readonly session: KnowledgeBaseSession;
|
|
218
|
+
readonly rowsById: ReadonlyMap<string, QueryRow>;
|
|
219
|
+
readonly idsByDocumentId: ReadonlyMap<string, readonly string[]>;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
type LocalSearchSuccess = {
|
|
223
|
+
readonly kind: "success";
|
|
224
|
+
readonly opened: OpenedVault;
|
|
225
|
+
readonly result: Awaited<ReturnType<KnowledgeBaseSession["search"]>>;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
type LocalSearchFailure = {
|
|
229
|
+
readonly kind: "failure";
|
|
230
|
+
readonly opened: OpenedVault;
|
|
231
|
+
readonly error: unknown;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
type LocalSearchOutcome = LocalSearchFailure | LocalSearchSuccess;
|
|
235
|
+
|
|
236
|
+
function checkedInteger(
|
|
237
|
+
value: number | undefined,
|
|
238
|
+
fallback: number,
|
|
239
|
+
maximum: number,
|
|
240
|
+
label: string,
|
|
241
|
+
): number {
|
|
242
|
+
const checked = value ?? fallback;
|
|
243
|
+
if (!Number.isSafeInteger(checked) || checked < 1 || checked > maximum) {
|
|
244
|
+
throw new RangeError(`${label} must be an integer from 1 through ${maximum}.`);
|
|
245
|
+
}
|
|
246
|
+
return checked;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function errorMessage(error: unknown): string {
|
|
250
|
+
return error instanceof Error ? error.message : String(error);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function descriptor(entry: PortfolioVaultEntry): PortfolioVaultDescriptor {
|
|
254
|
+
return Object.freeze({
|
|
255
|
+
owner: entry.owner,
|
|
256
|
+
id: entry.id,
|
|
257
|
+
key: entry.key,
|
|
258
|
+
role: entry.role,
|
|
259
|
+
visibility: entry.visibility,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function defaultSha256(content: string): string {
|
|
264
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function contentRevision(
|
|
268
|
+
session: KnowledgeBaseSession,
|
|
269
|
+
noteId: string,
|
|
270
|
+
sha256: (content: string) => string,
|
|
271
|
+
): PortfolioContentRevision {
|
|
272
|
+
try {
|
|
273
|
+
const read = session.read(noteId, { maxBytes: MAX_PORTFOLIO_REVISION_BYTES });
|
|
274
|
+
return read.truncated
|
|
275
|
+
? Object.freeze({ complete: false, sha256: null })
|
|
276
|
+
: Object.freeze({ complete: true, sha256: sha256(read.content) });
|
|
277
|
+
} catch {
|
|
278
|
+
return Object.freeze({ complete: false, sha256: null });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function indexedRows(session: KnowledgeBaseSession): {
|
|
283
|
+
readonly rowsById: ReadonlyMap<string, QueryRow>;
|
|
284
|
+
readonly idsByDocumentId: ReadonlyMap<string, readonly string[]>;
|
|
285
|
+
} {
|
|
286
|
+
const rows = session.list();
|
|
287
|
+
const rowsById = new Map(rows.map((row) => [row.id, row]));
|
|
288
|
+
const mutableIds = new Map<string, string[]>();
|
|
289
|
+
for (const row of rows) {
|
|
290
|
+
const identity = portfolioDocumentIdentity({ owner: "index", id: "index" }, row.path, row.metadata);
|
|
291
|
+
if (identity.kind !== "stable") continue;
|
|
292
|
+
const ids = mutableIds.get(identity.documentId) ?? [];
|
|
293
|
+
ids.push(row.id);
|
|
294
|
+
mutableIds.set(identity.documentId, ids);
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
rowsById,
|
|
298
|
+
idsByDocumentId: new Map([...mutableIds].map(([documentId, ids]) => [
|
|
299
|
+
documentId,
|
|
300
|
+
Object.freeze(ids.toSorted()),
|
|
301
|
+
])),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function repositoryProbe(
|
|
306
|
+
resolved: ResolvedPortfolioVault,
|
|
307
|
+
dependencies: PortfolioDependencies,
|
|
308
|
+
): Promise<RepositoryProbe> {
|
|
309
|
+
const vault = descriptor(resolved.entry);
|
|
310
|
+
const unavailable = (message: string): RepositoryProbe => ({
|
|
311
|
+
provenance: Object.freeze({
|
|
312
|
+
id: resolved.entry.repository,
|
|
313
|
+
...(resolved.entry.defaultRef === undefined ? {} : { defaultRef: resolved.entry.defaultRef }),
|
|
314
|
+
head: null,
|
|
315
|
+
}),
|
|
316
|
+
diagnostic: Object.freeze({ vault, lane: "git", status: "unavailable", message }),
|
|
317
|
+
});
|
|
318
|
+
try {
|
|
319
|
+
const indexed = await (dependencies.indexGitHistory ?? indexGitHistory)(
|
|
320
|
+
{
|
|
321
|
+
repository: resolved.repositoryRoot,
|
|
322
|
+
root: resolved.root,
|
|
323
|
+
notes: [],
|
|
324
|
+
maxCommits: 1,
|
|
325
|
+
},
|
|
326
|
+
dependencies.git,
|
|
327
|
+
);
|
|
328
|
+
if (indexed.status === "unavailable") return unavailable(indexed.reason);
|
|
329
|
+
return {
|
|
330
|
+
provenance: Object.freeze({
|
|
331
|
+
id: resolved.entry.repository,
|
|
332
|
+
...(resolved.entry.defaultRef === undefined ? {} : { defaultRef: resolved.entry.defaultRef }),
|
|
333
|
+
head: indexed.head,
|
|
334
|
+
}),
|
|
335
|
+
};
|
|
336
|
+
} catch (error) {
|
|
337
|
+
return unavailable(errorMessage(error));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function closeOpened(vaults: readonly OpenedVault[]): Promise<void> {
|
|
342
|
+
await Promise.all(vaults.map(({ session }) => session.close().catch(() => undefined)));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function mapConcurrent<T, R>(
|
|
346
|
+
values: readonly T[],
|
|
347
|
+
concurrency: number,
|
|
348
|
+
operation: (value: T) => Promise<R>,
|
|
349
|
+
): Promise<readonly R[]> {
|
|
350
|
+
const results = new Array<R>(values.length);
|
|
351
|
+
let cursor = 0;
|
|
352
|
+
const worker = async (): Promise<void> => {
|
|
353
|
+
for (;;) {
|
|
354
|
+
const index = cursor;
|
|
355
|
+
cursor += 1;
|
|
356
|
+
if (index >= values.length) return;
|
|
357
|
+
const value = values[index];
|
|
358
|
+
if (value !== undefined) results[index] = await operation(value);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
await Promise.all(Array.from(
|
|
362
|
+
{ length: Math.min(concurrency, values.length) },
|
|
363
|
+
() => worker(),
|
|
364
|
+
));
|
|
365
|
+
return results;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function maybeQualifiedIdentity(query: string): StablePortfolioDocumentIdentity | null {
|
|
369
|
+
try {
|
|
370
|
+
return parseQualifiedDocumentUri(query);
|
|
371
|
+
} catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function identitySortKey(identity: PortfolioDocumentIdentity): string {
|
|
377
|
+
return identity.kind === "stable" ? identity.uri : identity.path;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Open selected local vault sessions. No unselected registry entry is resolved, scanned, or reported. */
|
|
381
|
+
export async function openKnowledgePortfolio(
|
|
382
|
+
options: OpenKnowledgePortfolioOptions,
|
|
383
|
+
dependencies: PortfolioDependencies = {},
|
|
384
|
+
): Promise<KnowledgePortfolioSession> {
|
|
385
|
+
const failurePolicy = options.failurePolicy ?? "partial";
|
|
386
|
+
if (failurePolicy !== "partial" && failurePolicy !== "required") {
|
|
387
|
+
throw new TypeError('Portfolio failurePolicy must be "partial" or "required".');
|
|
388
|
+
}
|
|
389
|
+
const searchConcurrency = checkedInteger(
|
|
390
|
+
options.searchConcurrency,
|
|
391
|
+
4,
|
|
392
|
+
MAX_PORTFOLIO_SEARCH_CONCURRENCY,
|
|
393
|
+
"Portfolio search concurrency",
|
|
394
|
+
);
|
|
395
|
+
const configuredSearchRules = options.knowledgeBase?.searchRules === undefined
|
|
396
|
+
? null
|
|
397
|
+
: parseSearchRules(options.knowledgeBase.searchRules);
|
|
398
|
+
const registry = snapshotPortfolioRegistry(options.registry ?? await (
|
|
399
|
+
dependencies.loadPortfolioRegistry ?? loadPortfolioRegistry
|
|
400
|
+
)(options.registryPath, dependencies));
|
|
401
|
+
const selected = selectAuthorizedVaults(registry, options.authorizedVaults);
|
|
402
|
+
const diagnostics: PortfolioOpenDiagnostic[] = [];
|
|
403
|
+
const resolved: ResolvedPortfolioVault[] = [];
|
|
404
|
+
for (const entry of selected) {
|
|
405
|
+
try {
|
|
406
|
+
resolved.push(await (dependencies.resolvePortfolioVault ?? resolvePortfolioVault)(
|
|
407
|
+
entry,
|
|
408
|
+
options.workspaceRoot,
|
|
409
|
+
dependencies,
|
|
410
|
+
));
|
|
411
|
+
} catch (error) {
|
|
412
|
+
diagnostics.push(Object.freeze({
|
|
413
|
+
vault: descriptor(entry),
|
|
414
|
+
lane: "resolve",
|
|
415
|
+
status: "unavailable",
|
|
416
|
+
message: errorMessage(error),
|
|
417
|
+
}));
|
|
418
|
+
if (failurePolicy === "required") {
|
|
419
|
+
throw new PortfolioOpenError("A required portfolio vault could not be resolved.", diagnostics, {
|
|
420
|
+
cause: error,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
validateResolvedPortfolioVaults(resolved);
|
|
426
|
+
|
|
427
|
+
const opened: OpenedVault[] = [];
|
|
428
|
+
for (const vault of resolved) {
|
|
429
|
+
const vaultDescriptor = descriptor(vault.entry);
|
|
430
|
+
const probe = await repositoryProbe(vault, dependencies);
|
|
431
|
+
if (probe.diagnostic !== undefined) diagnostics.push(probe.diagnostic);
|
|
432
|
+
let session: KnowledgeBaseSession | undefined;
|
|
433
|
+
try {
|
|
434
|
+
const knowledgeBase = options.knowledgeBase ?? {};
|
|
435
|
+
session = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)(
|
|
436
|
+
{
|
|
437
|
+
...knowledgeBase,
|
|
438
|
+
...(configuredSearchRules === null ? {} : { searchRules: configuredSearchRules }),
|
|
439
|
+
vaultId: vaultDescriptor.key,
|
|
440
|
+
root: vault.root,
|
|
441
|
+
repository: vault.repositoryRoot,
|
|
442
|
+
scan: {
|
|
443
|
+
...(knowledgeBase.scan ?? {}),
|
|
444
|
+
catalogMode: "authored",
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
dependencies.knowledgeBase,
|
|
448
|
+
);
|
|
449
|
+
const indexed = indexedRows(session);
|
|
450
|
+
opened.push({
|
|
451
|
+
resolved: vault,
|
|
452
|
+
descriptor: vaultDescriptor,
|
|
453
|
+
repository: probe.provenance,
|
|
454
|
+
session,
|
|
455
|
+
...indexed,
|
|
456
|
+
});
|
|
457
|
+
} catch (error) {
|
|
458
|
+
await session?.close().catch(() => undefined);
|
|
459
|
+
diagnostics.push(Object.freeze({
|
|
460
|
+
vault: vaultDescriptor,
|
|
461
|
+
lane: "open",
|
|
462
|
+
status: "unavailable",
|
|
463
|
+
message: errorMessage(error),
|
|
464
|
+
}));
|
|
465
|
+
if (failurePolicy === "required") {
|
|
466
|
+
await closeOpened(opened);
|
|
467
|
+
throw new PortfolioOpenError("A required portfolio vault could not be opened.", diagnostics, {
|
|
468
|
+
cause: error,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const openedByKey = new Map(opened.map((vault) => [vault.descriptor.key, vault]));
|
|
475
|
+
const hash = dependencies.sha256 ?? defaultSha256;
|
|
476
|
+
let closed = false;
|
|
477
|
+
let closePromise: Promise<void> | undefined;
|
|
478
|
+
const assertOpen = (): void => {
|
|
479
|
+
if (closed) throw new Error("Knowledge-portfolio session is closed.");
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const search = async (searchOptions: PortfolioSearchOptions): Promise<PortfolioSearchResult> => {
|
|
483
|
+
assertOpen();
|
|
484
|
+
const { query } = validateSearchQuery(searchOptions.query);
|
|
485
|
+
if (searchOptions.graph !== undefined && searchOptions.graph !== false) {
|
|
486
|
+
throw new TypeError("Portfolio graph requests must be false; graph seeds require a qualified vault route.");
|
|
487
|
+
}
|
|
488
|
+
const limit = checkedInteger(
|
|
489
|
+
searchOptions.limit,
|
|
490
|
+
DEFAULT_SEARCH_RESULTS,
|
|
491
|
+
MAX_SEARCH_RESULTS,
|
|
492
|
+
"Portfolio search limit",
|
|
493
|
+
);
|
|
494
|
+
const candidateLimit = checkedInteger(
|
|
495
|
+
searchOptions.candidateLimit,
|
|
496
|
+
Math.max(40, limit * 4),
|
|
497
|
+
MAX_SEARCH_CANDIDATES,
|
|
498
|
+
"Portfolio candidate limit",
|
|
499
|
+
);
|
|
500
|
+
if (candidateLimit < limit) {
|
|
501
|
+
throw new RangeError("Portfolio candidate limit must be at least the result limit.");
|
|
502
|
+
}
|
|
503
|
+
const validationRequest: PortfolioSearchOptions = configuredSearchRules === null
|
|
504
|
+
? searchOptions
|
|
505
|
+
: {
|
|
506
|
+
...searchOptions,
|
|
507
|
+
...expandSearchRequest({
|
|
508
|
+
query: searchOptions.query,
|
|
509
|
+
...(searchOptions.mode === undefined ? {} : { mode: searchOptions.mode }),
|
|
510
|
+
...(searchOptions.filters === undefined ? {} : { filters: searchOptions.filters }),
|
|
511
|
+
...(searchOptions.tags === undefined ? {} : { tags: searchOptions.tags }),
|
|
512
|
+
...(searchOptions.repositoryScopes === undefined
|
|
513
|
+
? {}
|
|
514
|
+
: { repositoryScopes: searchOptions.repositoryScopes }),
|
|
515
|
+
}, configuredSearchRules).request,
|
|
516
|
+
};
|
|
517
|
+
validateSearchQuery(validationRequest.query);
|
|
518
|
+
validateQueryOptions({
|
|
519
|
+
...(validationRequest.filters === undefined ? {} : { filters: validationRequest.filters }),
|
|
520
|
+
...(validationRequest.tags === undefined ? {} : { tags: validationRequest.tags }),
|
|
521
|
+
...(validationRequest.repositoryScopes === undefined
|
|
522
|
+
? {}
|
|
523
|
+
: { repositoryScopes: validationRequest.repositoryScopes }),
|
|
524
|
+
});
|
|
525
|
+
const validatedMode = validationRequest.mode ?? "hybrid";
|
|
526
|
+
if (
|
|
527
|
+
validatedMode !== "exact"
|
|
528
|
+
&& validatedMode !== "hybrid"
|
|
529
|
+
&& validatedMode !== "keyword"
|
|
530
|
+
&& validatedMode !== "semantic"
|
|
531
|
+
) {
|
|
532
|
+
throw new TypeError("Portfolio search mode must be exact, hybrid, keyword, or semantic.");
|
|
533
|
+
}
|
|
534
|
+
const validatedOrdering = validationRequest.ordering ?? "relevance";
|
|
535
|
+
if (validatedOrdering !== "relevance" && validatedOrdering !== "priority-then-relevance") {
|
|
536
|
+
throw new TypeError('Portfolio search ordering must be "relevance" or "priority-then-relevance".');
|
|
537
|
+
}
|
|
538
|
+
if (
|
|
539
|
+
validationRequest.minScore !== undefined
|
|
540
|
+
&& (
|
|
541
|
+
!Number.isFinite(validationRequest.minScore)
|
|
542
|
+
|| validationRequest.minScore < 0
|
|
543
|
+
|| validationRequest.minScore > 1
|
|
544
|
+
)
|
|
545
|
+
) {
|
|
546
|
+
throw new RangeError("Portfolio search minimum score must be a number from 0 through 1.");
|
|
547
|
+
}
|
|
548
|
+
if (validatedMode === "exact" && validationRequest.minScore !== undefined) {
|
|
549
|
+
throw new Error("Portfolio search minimum score applies only to hybrid, keyword, or semantic mode.");
|
|
550
|
+
}
|
|
551
|
+
validateKnowledgeBaseSearchHistory(validationRequest.history);
|
|
552
|
+
const routed = maybeQualifiedIdentity(query);
|
|
553
|
+
const searchedVaults = routed === null
|
|
554
|
+
? opened
|
|
555
|
+
: opened.filter(({ descriptor: selectedVault }) => selectedVault.key === routed.vault.key);
|
|
556
|
+
const routedLocalIds = routed === null || searchedVaults[0] === undefined
|
|
557
|
+
? undefined
|
|
558
|
+
: searchedVaults[0].idsByDocumentId.get(routed.documentId);
|
|
559
|
+
if (routedLocalIds !== undefined && routedLocalIds.length > 1) {
|
|
560
|
+
throw new Error("Qualified document identity is ambiguous in its selected vault.");
|
|
561
|
+
}
|
|
562
|
+
const localOptions: Omit<KnowledgeBaseSearchOptions, "query"> = {
|
|
563
|
+
...(searchOptions.mode === undefined ? {} : { mode: searchOptions.mode }),
|
|
564
|
+
...(searchOptions.ordering === undefined ? {} : { ordering: searchOptions.ordering }),
|
|
565
|
+
...(searchOptions.filters === undefined ? {} : { filters: searchOptions.filters }),
|
|
566
|
+
...(searchOptions.tags === undefined ? {} : { tags: searchOptions.tags }),
|
|
567
|
+
...(searchOptions.repositoryScopes === undefined
|
|
568
|
+
? {}
|
|
569
|
+
: { repositoryScopes: searchOptions.repositoryScopes }),
|
|
570
|
+
limit,
|
|
571
|
+
candidateLimit,
|
|
572
|
+
...(searchOptions.minScore === undefined ? {} : { minScore: searchOptions.minScore }),
|
|
573
|
+
graph: false,
|
|
574
|
+
...(searchOptions.history === undefined ? {} : { history: searchOptions.history }),
|
|
575
|
+
};
|
|
576
|
+
const outcomes = await mapConcurrent(searchedVaults, searchConcurrency, async (vault): Promise<LocalSearchOutcome> => {
|
|
577
|
+
try {
|
|
578
|
+
const routedLocalId = routed === null ? undefined : routedLocalIds?.[0];
|
|
579
|
+
const result = await vault.session.search({
|
|
580
|
+
...localOptions,
|
|
581
|
+
query: routedLocalId ?? routed?.documentId ?? query,
|
|
582
|
+
...(routed === null
|
|
583
|
+
? {}
|
|
584
|
+
: {
|
|
585
|
+
mode: "exact",
|
|
586
|
+
filters: Object.freeze([
|
|
587
|
+
...(localOptions.filters ?? []),
|
|
588
|
+
{ kind: "equals" as const, path: "document_id", value: routed.documentId },
|
|
589
|
+
]),
|
|
590
|
+
}),
|
|
591
|
+
});
|
|
592
|
+
return { kind: "success", opened: vault, result };
|
|
593
|
+
} catch (error) {
|
|
594
|
+
return { kind: "failure", opened: vault, error };
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
const vaultDiagnostics: PortfolioVaultSearchDiagnostic[] = diagnostics
|
|
598
|
+
.filter(({ lane }) => lane !== "git")
|
|
599
|
+
.map((diagnostic) => Object.freeze({
|
|
600
|
+
vault: diagnostic.vault,
|
|
601
|
+
status: "unavailable" as const,
|
|
602
|
+
results: 0,
|
|
603
|
+
message: diagnostic.message,
|
|
604
|
+
}));
|
|
605
|
+
const candidates: Array<{
|
|
606
|
+
readonly opened: OpenedVault;
|
|
607
|
+
readonly hit: KnowledgeBaseSearchHit;
|
|
608
|
+
readonly identity: PortfolioDocumentIdentity;
|
|
609
|
+
readonly exactIdentity: boolean;
|
|
610
|
+
readonly localRank: number;
|
|
611
|
+
readonly score: number;
|
|
612
|
+
}> = [];
|
|
613
|
+
for (const outcome of outcomes) {
|
|
614
|
+
if (outcome.kind === "failure") {
|
|
615
|
+
if (failurePolicy === "required") {
|
|
616
|
+
throw new PortfolioSearchError(
|
|
617
|
+
outcome.opened.descriptor,
|
|
618
|
+
"A required portfolio vault search failed.",
|
|
619
|
+
{ cause: outcome.error },
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
vaultDiagnostics.push(Object.freeze({
|
|
623
|
+
vault: outcome.opened.descriptor,
|
|
624
|
+
status: "unavailable",
|
|
625
|
+
results: 0,
|
|
626
|
+
message: errorMessage(outcome.error),
|
|
627
|
+
}));
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
vaultDiagnostics.push(Object.freeze({
|
|
631
|
+
vault: outcome.opened.descriptor,
|
|
632
|
+
status: outcome.result.partial ? "partial" : "ready",
|
|
633
|
+
results: outcome.result.results.length,
|
|
634
|
+
lanes: outcome.result.diagnostics.lanes,
|
|
635
|
+
...(outcome.result.rules === undefined ? {} : { rules: outcome.result.rules }),
|
|
636
|
+
}));
|
|
637
|
+
for (const hit of outcome.result.results) {
|
|
638
|
+
const identity = portfolioDocumentIdentity(
|
|
639
|
+
outcome.opened.descriptor,
|
|
640
|
+
hit.path,
|
|
641
|
+
hit.metadata,
|
|
642
|
+
);
|
|
643
|
+
if (
|
|
644
|
+
routed !== null
|
|
645
|
+
&& (identity.kind !== "stable" || identity.documentId !== routed.documentId)
|
|
646
|
+
) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
const localRank = hit.rank;
|
|
650
|
+
candidates.push({
|
|
651
|
+
opened: outcome.opened,
|
|
652
|
+
hit,
|
|
653
|
+
identity,
|
|
654
|
+
exactIdentity: routed !== null || hit.identity,
|
|
655
|
+
localRank,
|
|
656
|
+
score: 1 / (60 + localRank),
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const ordered = candidates.toSorted((left, right) =>
|
|
661
|
+
Number(right.exactIdentity) - Number(left.exactIdentity)
|
|
662
|
+
|| right.score - left.score
|
|
663
|
+
|| left.opened.descriptor.key.localeCompare(right.opened.descriptor.key)
|
|
664
|
+
|| identitySortKey(left.identity).localeCompare(identitySortKey(right.identity))
|
|
665
|
+
|| left.hit.path.localeCompare(right.hit.path));
|
|
666
|
+
const results = ordered.slice(0, limit).map((candidate, index): PortfolioSearchHit => Object.freeze({
|
|
667
|
+
identity: candidate.identity,
|
|
668
|
+
vault: candidate.opened.descriptor,
|
|
669
|
+
repository: candidate.opened.repository,
|
|
670
|
+
id: candidate.hit.id,
|
|
671
|
+
path: candidate.hit.path,
|
|
672
|
+
title: candidate.hit.title,
|
|
673
|
+
rank: index + 1,
|
|
674
|
+
score: candidate.score,
|
|
675
|
+
exactIdentity: candidate.exactIdentity,
|
|
676
|
+
localRank: candidate.localRank,
|
|
677
|
+
revision: contentRevision(candidate.opened.session, candidate.hit.id, hash),
|
|
678
|
+
snippet: candidate.hit.snippet,
|
|
679
|
+
...(candidate.hit.line === undefined ? {} : { line: candidate.hit.line }),
|
|
680
|
+
tags: candidate.hit.tags,
|
|
681
|
+
metadata: candidate.hit.metadata,
|
|
682
|
+
evidence: candidate.hit.evidence,
|
|
683
|
+
local: candidate.hit,
|
|
684
|
+
}));
|
|
685
|
+
const partial = diagnostics.length > 0
|
|
686
|
+
|| vaultDiagnostics.some(({ status }) => status !== "ready");
|
|
687
|
+
const successfulModes = new Set(outcomes.flatMap((outcome) =>
|
|
688
|
+
outcome.kind === "success" ? [outcome.result.mode] : []));
|
|
689
|
+
if (successfulModes.size > 1) {
|
|
690
|
+
throw new Error("Selected vaults produced inconsistent effective search modes.");
|
|
691
|
+
}
|
|
692
|
+
const effectiveMode = routed === null
|
|
693
|
+
? successfulModes.values().next().value ?? validatedMode
|
|
694
|
+
: "exact";
|
|
695
|
+
return Object.freeze({
|
|
696
|
+
query,
|
|
697
|
+
mode: effectiveMode,
|
|
698
|
+
results: Object.freeze(results),
|
|
699
|
+
partial,
|
|
700
|
+
diagnostics: Object.freeze({
|
|
701
|
+
selectedVaults: selected.length,
|
|
702
|
+
availableVaults: opened.length,
|
|
703
|
+
notes: opened.reduce((count, vault) => count + vault.session.noteCount, 0),
|
|
704
|
+
open: Object.freeze([...diagnostics]),
|
|
705
|
+
vaults: Object.freeze(vaultDiagnostics),
|
|
706
|
+
}),
|
|
707
|
+
});
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const read = (
|
|
711
|
+
value: QualifiedDocumentUri | string,
|
|
712
|
+
readOptions: { readonly maxBytes?: number } = {},
|
|
713
|
+
): PortfolioReadResult => {
|
|
714
|
+
assertOpen();
|
|
715
|
+
const identity = parseQualifiedDocumentUri(value);
|
|
716
|
+
const vault = openedByKey.get(identity.vault.key);
|
|
717
|
+
if (vault === undefined) throw new Error("Qualified document is not available in this portfolio session.");
|
|
718
|
+
const ids = vault.idsByDocumentId.get(identity.documentId);
|
|
719
|
+
if (ids === undefined || ids.length === 0) {
|
|
720
|
+
throw new Error("Qualified document is not available in this portfolio session.");
|
|
721
|
+
}
|
|
722
|
+
if (ids.length > 1) {
|
|
723
|
+
throw new Error("Qualified document identity is ambiguous within its vault.");
|
|
724
|
+
}
|
|
725
|
+
const noteId = ids[0];
|
|
726
|
+
if (noteId === undefined || !vault.rowsById.has(noteId)) {
|
|
727
|
+
throw new Error("Qualified document is not available in this portfolio session.");
|
|
728
|
+
}
|
|
729
|
+
const result = vault.session.read(noteId, readOptions);
|
|
730
|
+
return Object.freeze({
|
|
731
|
+
...result,
|
|
732
|
+
identity,
|
|
733
|
+
vault: vault.descriptor,
|
|
734
|
+
repository: vault.repository,
|
|
735
|
+
revision: result.truncated
|
|
736
|
+
? Object.freeze({ complete: false, sha256: null })
|
|
737
|
+
: Object.freeze({ complete: true, sha256: hash(result.content) }),
|
|
738
|
+
});
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
return Object.freeze({
|
|
742
|
+
selectedVaultCount: selected.length,
|
|
743
|
+
availableVaultCount: opened.length,
|
|
744
|
+
noteCount: opened.reduce((count, vault) => count + vault.session.noteCount, 0),
|
|
745
|
+
openDiagnostics: Object.freeze([...diagnostics]),
|
|
746
|
+
search,
|
|
747
|
+
read,
|
|
748
|
+
close: () => {
|
|
749
|
+
if (closePromise !== undefined) return closePromise;
|
|
750
|
+
closed = true;
|
|
751
|
+
closePromise = Promise.allSettled(opened.map(({ session }) => session.close())).then((settled) => {
|
|
752
|
+
const errors = settled
|
|
753
|
+
.filter((result): result is PromiseRejectedResult => result.status === "rejected")
|
|
754
|
+
.map(({ reason }) => reason as unknown);
|
|
755
|
+
if (errors.length > 0) throw new AggregateError(errors, "One or more portfolio vault sessions failed to close.");
|
|
756
|
+
});
|
|
757
|
+
return closePromise;
|
|
758
|
+
},
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export type { KnowledgeBaseGraphOptions, KnowledgeBaseHistoryOptions };
|
|
763
|
+
export * from "./portfolio-audit.js";
|
|
764
|
+
export * from "./portfolio-identity.js";
|
|
765
|
+
export * from "./portfolio-registry.js";
|