@hasna/knowledge 0.2.26 → 0.2.28

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 (61) hide show
  1. package/README.md +61 -0
  2. package/bin/open-knowledge-mcp.js +85 -9
  3. package/bin/open-knowledge.js +86 -86
  4. package/dist/agent.d.ts +35 -0
  5. package/dist/artifact-store.d.ts +63 -0
  6. package/dist/auth.d.ts +35 -0
  7. package/dist/embeddings.d.ts +77 -0
  8. package/dist/index.d.ts +20 -0
  9. package/dist/index.js +5709 -0
  10. package/dist/knowledge-db.d.ts +27 -0
  11. package/dist/manifest-ingest.d.ts +35 -0
  12. package/dist/outbox-consume.d.ts +25 -0
  13. package/dist/provenance.d.ts +50 -0
  14. package/dist/providers.d.ts +89 -0
  15. package/dist/reindex.d.ts +37 -0
  16. package/dist/remote-client.d.ts +108 -0
  17. package/dist/retrieval.d.ts +71 -0
  18. package/dist/safety.d.ts +70 -0
  19. package/dist/sdk.d.ts +72 -0
  20. package/dist/search.d.ts +65 -0
  21. package/dist/service.d.ts +117 -0
  22. package/dist/source-ingest.d.ts +18 -0
  23. package/dist/source-ref.d.ts +30 -0
  24. package/dist/source-resolver.d.ts +92 -0
  25. package/dist/storage-contract.d.ts +106 -0
  26. package/dist/web-search.d.ts +40 -0
  27. package/dist/wiki-compiler.d.ts +67 -0
  28. package/dist/wiki-layout.d.ts +23 -0
  29. package/dist/workspace.d.ts +111 -0
  30. package/docs/architecture/ai-native-knowledge-base.md +24 -0
  31. package/docs/architecture/hosted-wrapper-responsibilities.md +8 -0
  32. package/docs/canonical-secrets-bootstrap-2026-06-08.md +127 -0
  33. package/package.json +15 -7
  34. package/src/agent.ts +0 -367
  35. package/src/artifact-store.ts +0 -184
  36. package/src/auth.ts +0 -123
  37. package/src/cli.ts +0 -1181
  38. package/src/embeddings.ts +0 -516
  39. package/src/knowledge-db.ts +0 -354
  40. package/src/manifest-ingest.ts +0 -515
  41. package/src/mcp-http.js +0 -110
  42. package/src/mcp.js +0 -1503
  43. package/src/outbox-consume.ts +0 -463
  44. package/src/provenance.ts +0 -93
  45. package/src/providers.ts +0 -308
  46. package/src/reindex.ts +0 -260
  47. package/src/remote-client.ts +0 -268
  48. package/src/retrieval.ts +0 -326
  49. package/src/safety.ts +0 -265
  50. package/src/schema.js +0 -25
  51. package/src/search.ts +0 -510
  52. package/src/service.ts +0 -432
  53. package/src/source-ingest.ts +0 -268
  54. package/src/source-ref.ts +0 -104
  55. package/src/source-resolver.ts +0 -436
  56. package/src/storage-contract.ts +0 -293
  57. package/src/store.ts +0 -113
  58. package/src/web-search.ts +0 -330
  59. package/src/wiki-compiler.ts +0 -711
  60. package/src/wiki-layout.ts +0 -251
  61. package/src/workspace.ts +0 -213
@@ -1,293 +0,0 @@
1
- import { createHash, randomUUID } from 'node:crypto';
2
- import type { Database } from 'bun:sqlite';
3
- import { DEFAULT_KNOWLEDGE_API_URL, normalizeKnowledgeApiOrigin } from './auth';
4
- import { REMOTE_KNOWLEDGE_CONTRACT_VERSION } from './remote-client';
5
- import type { KnowledgeConfig, KnowledgeWorkspace } from './workspace';
6
- import { HASNA_KNOWLEDGE_APP_PATH } from './workspace';
7
-
8
- export interface StorageArtifactClass {
9
- kind: string;
10
- prefix: string;
11
- description: string;
12
- }
13
-
14
- export interface StorageContract {
15
- scope: string;
16
- mode: KnowledgeConfig['mode'];
17
- storage_type: KnowledgeConfig['storage']['type'];
18
- workspace_home: string;
19
- local_layout: {
20
- app_path: string;
21
- config_path: string;
22
- json_store_path: string;
23
- knowledge_db_path: string;
24
- directories: Record<string, string>;
25
- };
26
- artifact_store: {
27
- type: KnowledgeConfig['storage']['type'];
28
- artifacts_root: string;
29
- uri_prefix: string;
30
- s3: {
31
- bucket: string;
32
- prefix: string;
33
- region: string | null;
34
- profile: string | null;
35
- server_side_encryption: string | null;
36
- kms_key_configured: boolean;
37
- } | null;
38
- };
39
- hosted: {
40
- enabled: boolean;
41
- api_url: string;
42
- api_url_env: 'KNOWLEDGE_API_URL';
43
- api_key_env: 'KNOWLEDGE_API_KEY';
44
- auth_storage: '~/.hasna/knowledge/auth.json';
45
- remote_contract_version: typeof REMOTE_KNOWLEDGE_CONTRACT_VERSION;
46
- requires_hosted_account_for_local_use: false;
47
- };
48
- source_ownership: {
49
- owner: 'open-files';
50
- preferred_ref: string;
51
- allowed_schemes: string[];
52
- raw_source_bytes_stored_in_open_knowledge: false;
53
- stores: string[];
54
- does_not_store: string[];
55
- };
56
- generated_artifacts: StorageArtifactClass[];
57
- scalability: {
58
- catalog: string;
59
- indexes: string;
60
- logs: string;
61
- markdown: string;
62
- };
63
- warnings: string[];
64
- }
65
-
66
- export interface StorageValidationResult {
67
- ok: boolean;
68
- errors: string[];
69
- warnings: string[];
70
- }
71
-
72
- export interface GeneratedStorageObject {
73
- uri: string;
74
- key: string;
75
- kind: string;
76
- content_type?: string;
77
- hash?: string;
78
- size_bytes?: number;
79
- metadata?: Record<string, unknown>;
80
- }
81
-
82
- const GENERATED_ARTIFACTS: StorageArtifactClass[] = [
83
- {
84
- kind: 'schema',
85
- prefix: 'schemas/',
86
- description: 'Machine-readable agent schemas and source rules.',
87
- },
88
- {
89
- kind: 'index',
90
- prefix: 'indexes/',
91
- description: 'Small orientation indexes and future shard manifests.',
92
- },
93
- {
94
- kind: 'log',
95
- prefix: 'logs/',
96
- description: 'Append-only JSONL run and wiki-maintenance log partitions.',
97
- },
98
- {
99
- kind: 'run',
100
- prefix: 'runs/',
101
- description: 'Prompt/tool/cost ledgers and generated output records.',
102
- },
103
- {
104
- kind: 'wiki_page',
105
- prefix: 'wiki/',
106
- description: 'Generated cited Markdown pages, not raw source files.',
107
- },
108
- {
109
- kind: 'export',
110
- prefix: 'exports/',
111
- description: 'Portable exports and snapshots of derived knowledge state.',
112
- },
113
- ];
114
-
115
- export function hashArtifactBody(body: string | Uint8Array): { hash: string; size_bytes: number } {
116
- const bytes = typeof body === 'string' ? Buffer.from(body) : Buffer.from(body);
117
- return {
118
- hash: `sha256:${createHash('sha256').update(bytes).digest('hex')}`,
119
- size_bytes: bytes.byteLength,
120
- };
121
- }
122
-
123
- export function artifactKindForKey(key: string): string {
124
- const match = GENERATED_ARTIFACTS.find((entry) => key.startsWith(entry.prefix));
125
- return match?.kind ?? 'artifact';
126
- }
127
-
128
- export function resolveStorageContract(
129
- config: KnowledgeConfig,
130
- workspace: KnowledgeWorkspace,
131
- scope = 'global',
132
- ): StorageContract {
133
- const validation = validateStorageConfig(config, workspace);
134
- const s3 = config.storage.s3 ?? null;
135
- const prefix = s3?.prefix?.replace(/^\/+|\/+$/g, '') ?? '';
136
- const s3UriPrefix = s3 ? `s3://${s3.bucket}/${prefix ? `${prefix}/` : ''}` : '';
137
-
138
- return {
139
- scope,
140
- mode: config.mode,
141
- storage_type: config.storage.type,
142
- workspace_home: workspace.home,
143
- local_layout: {
144
- app_path: HASNA_KNOWLEDGE_APP_PATH,
145
- config_path: workspace.configPath,
146
- json_store_path: workspace.jsonStorePath,
147
- knowledge_db_path: workspace.knowledgeDbPath,
148
- directories: {
149
- artifacts: workspace.artifactsDir,
150
- cache: workspace.cacheDir,
151
- exports: workspace.exportsDir,
152
- indexes: workspace.indexesDir,
153
- logs: workspace.logsDir,
154
- runs: workspace.runsDir,
155
- schemas: workspace.schemasDir,
156
- wiki: workspace.wikiDir,
157
- },
158
- },
159
- artifact_store: {
160
- type: config.storage.type,
161
- artifacts_root: config.storage.artifacts_root,
162
- uri_prefix: config.storage.type === 's3' ? s3UriPrefix : `file://${workspace.artifactsDir}/`,
163
- s3: s3
164
- ? {
165
- bucket: s3.bucket,
166
- prefix,
167
- region: s3.region ?? null,
168
- profile: s3.profile ?? null,
169
- server_side_encryption: s3.server_side_encryption ?? null,
170
- kms_key_configured: Boolean(s3.kms_key_id),
171
- }
172
- : null,
173
- },
174
- hosted: {
175
- enabled: config.mode === 'hosted',
176
- api_url: normalizeKnowledgeApiOrigin(config.hosted?.api_url ?? DEFAULT_KNOWLEDGE_API_URL),
177
- api_url_env: 'KNOWLEDGE_API_URL',
178
- api_key_env: 'KNOWLEDGE_API_KEY',
179
- auth_storage: '~/.hasna/knowledge/auth.json',
180
- remote_contract_version: REMOTE_KNOWLEDGE_CONTRACT_VERSION,
181
- requires_hosted_account_for_local_use: false,
182
- },
183
- source_ownership: {
184
- owner: 'open-files',
185
- preferred_ref: config.sources.preferred_ref,
186
- allowed_schemes: config.sources.allowed_schemes,
187
- raw_source_bytes_stored_in_open_knowledge: false,
188
- stores: [
189
- 'source refs',
190
- 'source revisions and hashes',
191
- 'citation spans',
192
- 'redacted extracted chunks',
193
- 'embeddings',
194
- 'generated wiki artifacts',
195
- 'indexes',
196
- 'run ledgers',
197
- ],
198
- does_not_store: [
199
- 'raw open-files bytes',
200
- 'S3 object credentials',
201
- 'connector secrets',
202
- 'hosted tenant ownership state',
203
- ],
204
- },
205
- generated_artifacts: GENERATED_ARTIFACTS,
206
- scalability: {
207
- catalog: 'knowledge.db tracks sources, revisions, chunks, citations, indexes, runs, and storage_objects.',
208
- indexes: 'Indexes are cataloged DB rows plus sharded artifacts, not one giant index.md.',
209
- logs: 'Logs use dated JSONL partitions under logs/yyyy/mm/dd.jsonl.',
210
- markdown: 'Markdown pages are the readable wiki layer over DB/object-store state.',
211
- },
212
- warnings: validation.warnings,
213
- };
214
- }
215
-
216
- export function validateStorageConfig(config: KnowledgeConfig, workspace: KnowledgeWorkspace): StorageValidationResult {
217
- const errors: string[] = [];
218
- const warnings: string[] = [];
219
-
220
- if (!workspace.home.endsWith(HASNA_KNOWLEDGE_APP_PATH)) {
221
- warnings.push(`Workspace home does not end with ${HASNA_KNOWLEDGE_APP_PATH}: ${workspace.home}`);
222
- }
223
-
224
- if (config.storage.type === 's3') {
225
- if (!config.storage.s3?.bucket) errors.push('storage.s3.bucket is required when storage.type is s3.');
226
- if (!config.storage.s3?.prefix) warnings.push('storage.s3.prefix is empty; generated knowledge artifacts will be written at the bucket root.');
227
- if (config.mode === 'local') warnings.push('storage.type is s3 while mode is local; this is valid for BYO S3, but hosted wrappers should set mode to hosted.');
228
- }
229
-
230
- if (config.storage.type === 'local' && config.storage.s3) {
231
- warnings.push('storage.s3 is configured but ignored while storage.type is local.');
232
- }
233
-
234
- if (config.sources.preferred_ref !== 'open-files') {
235
- warnings.push('sources.preferred_ref should stay open-files for durable company knowledge.');
236
- }
237
-
238
- if (!config.sources.allowed_schemes.includes('open-files')) {
239
- errors.push('sources.allowed_schemes must include open-files.');
240
- }
241
-
242
- if (config.mode === 'hosted' && config.hosted?.api_url) {
243
- try {
244
- normalizeKnowledgeApiOrigin(config.hosted.api_url);
245
- } catch {
246
- errors.push('hosted.api_url must be an http(s) URL when mode is hosted.');
247
- }
248
- }
249
-
250
- return {
251
- ok: errors.length === 0,
252
- errors,
253
- warnings,
254
- };
255
- }
256
-
257
- export function recordStorageObjects(db: Database, objects: GeneratedStorageObject[], now = new Date()): void {
258
- const timestamp = now.toISOString();
259
- const statement = db.prepare(`
260
- INSERT INTO storage_objects (
261
- id, artifact_uri, kind, content_type, hash, size_bytes, metadata_json, created_at, updated_at
262
- )
263
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
264
- ON CONFLICT(artifact_uri) DO UPDATE SET
265
- kind = excluded.kind,
266
- content_type = excluded.content_type,
267
- hash = excluded.hash,
268
- size_bytes = excluded.size_bytes,
269
- metadata_json = excluded.metadata_json,
270
- updated_at = excluded.updated_at
271
- `);
272
-
273
- const insert = db.transaction((entries: GeneratedStorageObject[]) => {
274
- for (const entry of entries) {
275
- statement.run(
276
- randomUUID(),
277
- entry.uri,
278
- entry.kind,
279
- entry.content_type ?? null,
280
- entry.hash ?? null,
281
- entry.size_bytes ?? null,
282
- JSON.stringify({
283
- key: entry.key,
284
- ...(entry.metadata ?? {}),
285
- }),
286
- timestamp,
287
- timestamp,
288
- );
289
- }
290
- });
291
-
292
- insert(objects);
293
- }
package/src/store.ts DELETED
@@ -1,113 +0,0 @@
1
- /**
2
- * @hasna/knowledge
3
- * Copyright 2026 Hasna Inc.
4
- * Licensed under the Apache License, Version 2.0
5
- */
6
- import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync } from 'node:fs';
7
- import { randomUUID } from 'node:crypto';
8
- import { ensureParentDir, globalKnowledgeHome, legacyGlobalStorePath, workspaceForHome } from './workspace';
9
-
10
- export interface KnowledgeItem {
11
- id: string;
12
- short_id?: string | null;
13
- title: string;
14
- content: string;
15
- url: string | null;
16
- tags: string[];
17
- metadata?: Record<string, unknown>;
18
- archived?: boolean;
19
- created_at: string;
20
- updated_at: string;
21
- }
22
-
23
- export interface Store {
24
- items: KnowledgeItem[];
25
- }
26
-
27
- export function defaultStorePath(): string {
28
- return workspaceForHome(globalKnowledgeHome()).jsonStorePath;
29
- }
30
-
31
- export function ensureStore(path: string): void {
32
- if (!existsSync(path)) {
33
- ensureParentDir(path);
34
- if (path === defaultStorePath() && existsSync(legacyGlobalStorePath())) {
35
- writeFileSync(path, readFileSync(legacyGlobalStorePath(), 'utf8'));
36
- } else {
37
- writeFileSync(path, JSON.stringify({ items: [] }, null, 2));
38
- }
39
- }
40
- }
41
-
42
- function lockPath(path: string): string {
43
- return `${path}.lock`;
44
- }
45
-
46
- function acquireLock(lockPath: string, ownerId: string): void {
47
- const maxWait = 5000;
48
- const interval = 50;
49
- const start = Date.now();
50
- while (Date.now() - start < maxWait) {
51
- try {
52
- if (!existsSync(lockPath)) {
53
- writeFileSync(lockPath, JSON.stringify({ owner: ownerId, ts: Date.now() }));
54
- return;
55
- }
56
- const lock = JSON.parse(readFileSync(lockPath, 'utf8')) as { owner: string; ts: number };
57
- if (Date.now() - lock.ts > 10000) {
58
- unlinkSync(lockPath);
59
- }
60
- } catch {
61
- // lock file disappeared, try again
62
- }
63
- const start2 = Date.now();
64
- while (Date.now() - start2 < interval) {}
65
- }
66
- throw new Error(`Could not acquire lock on ${lockPath} after ${maxWait}ms`);
67
- }
68
-
69
- function releaseLock(lockPath: string, ownerId: string): void {
70
- try {
71
- if (existsSync(lockPath)) {
72
- const lock = JSON.parse(readFileSync(lockPath, 'utf8')) as { owner: string; ts: number };
73
- if (lock.owner === ownerId) {
74
- unlinkSync(lockPath);
75
- }
76
- }
77
- } catch {}
78
- }
79
-
80
- export function loadStore(path: string): Store {
81
- ensureStore(path);
82
- const raw = readFileSync(path, 'utf8');
83
- const parsed = JSON.parse(raw) as Store;
84
- if (!parsed || !Array.isArray(parsed.items)) {
85
- return { items: [] };
86
- }
87
- return parsed;
88
- }
89
-
90
- export function saveStore(path: string, store: Store): void {
91
- const tmp = `${path}.tmp.${randomUUID()}`;
92
- writeFileSync(tmp, JSON.stringify(store, null, 2));
93
- renameSync(tmp, path);
94
- }
95
-
96
- export function withLock<T>(path: string, fn: () => T): T {
97
- const owner = randomUUID();
98
- const lpath = lockPath(path);
99
- acquireLock(lpath, owner);
100
- try {
101
- return fn();
102
- } finally {
103
- releaseLock(lpath, owner);
104
- }
105
- }
106
-
107
- export function makeId(): string {
108
- return `k_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
109
- }
110
-
111
- export function makeShortId(id: string): string {
112
- return id.replace(/^k_/, '').slice(0, 12);
113
- }