@hasna/knowledge 0.2.3 → 0.2.5
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/README.md +97 -4
- package/bin/open-knowledge-mcp.js +1067 -1569
- package/bin/open-knowledge.js +257 -4
- package/docs/architecture/ai-native-knowledge-base.md +191 -0
- package/docs/architecture/hybrid-semantic-search.md +135 -0
- package/package.json +12 -7
- package/src/artifact-store.ts +184 -0
- package/src/cli.ts +662 -0
- package/src/knowledge-db.ts +247 -0
- package/src/manifest-ingest.ts +423 -0
- package/src/mcp.js +533 -0
- package/src/schema.js +25 -0
- package/src/source-ref.ts +92 -0
- package/src/store.ts +16 -6
- package/src/wiki-layout.ts +104 -0
- package/src/workspace.ts +123 -0
package/src/store.ts
CHANGED
|
@@ -3,17 +3,19 @@
|
|
|
3
3
|
* Copyright 2026 Hasna Inc.
|
|
4
4
|
* Licensed under the Apache License, Version 2.0
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import { dirname } from 'node:path';
|
|
8
|
-
import { homedir } from 'node:os';
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, renameSync, unlinkSync } from 'node:fs';
|
|
9
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { ensureParentDir, globalKnowledgeHome, legacyGlobalStorePath, workspaceForHome } from './workspace';
|
|
10
9
|
|
|
11
10
|
export interface KnowledgeItem {
|
|
12
11
|
id: string;
|
|
12
|
+
short_id?: string | null;
|
|
13
13
|
title: string;
|
|
14
14
|
content: string;
|
|
15
15
|
url: string | null;
|
|
16
16
|
tags: string[];
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
archived?: boolean;
|
|
17
19
|
created_at: string;
|
|
18
20
|
updated_at: string;
|
|
19
21
|
}
|
|
@@ -23,13 +25,17 @@ export interface Store {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function defaultStorePath(): string {
|
|
26
|
-
return
|
|
28
|
+
return workspaceForHome(globalKnowledgeHome()).jsonStorePath;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export function ensureStore(path: string): void {
|
|
30
32
|
if (!existsSync(path)) {
|
|
31
|
-
|
|
32
|
-
|
|
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
|
+
}
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -101,3 +107,7 @@ export function withLock<T>(path: string, fn: () => T): T {
|
|
|
101
107
|
export function makeId(): string {
|
|
102
108
|
return `k_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
103
109
|
}
|
|
110
|
+
|
|
111
|
+
export function makeShortId(id: string): string {
|
|
112
|
+
return id.replace(/^k_/, '').slice(0, 12);
|
|
113
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { ArtifactStore } from './artifact-store';
|
|
2
|
+
|
|
3
|
+
export interface WikiLayoutInitResult {
|
|
4
|
+
schema_key: string;
|
|
5
|
+
root_index_key: string;
|
|
6
|
+
wiki_readme_key: string;
|
|
7
|
+
log_key: string;
|
|
8
|
+
written: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function todayParts(now: Date): { year: string; month: string; day: string } {
|
|
12
|
+
const year = String(now.getUTCFullYear());
|
|
13
|
+
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
|
14
|
+
const day = String(now.getUTCDate()).padStart(2, '0');
|
|
15
|
+
return { year, month, day };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function agentSchemaTemplate(): string {
|
|
19
|
+
return `# Knowledge Agent Schema v1
|
|
20
|
+
|
|
21
|
+
## Source Rules
|
|
22
|
+
|
|
23
|
+
- Treat open-files source references as the preferred source of truth.
|
|
24
|
+
- Do not copy raw source files into open-knowledge.
|
|
25
|
+
- Cite every durable fact with a source URI, revision/hash when available, and optional span.
|
|
26
|
+
- Mark uncertainty explicitly when sources disagree or are incomplete.
|
|
27
|
+
|
|
28
|
+
## Wiki Rules
|
|
29
|
+
|
|
30
|
+
- Write generated knowledge as Markdown pages under wiki/.
|
|
31
|
+
- Keep root indexes small; use topic, team, project, and machine-readable shards for scale.
|
|
32
|
+
- Preserve backlinks between related pages and decisions.
|
|
33
|
+
- Prefer updating existing pages over creating near-duplicates.
|
|
34
|
+
|
|
35
|
+
## Query Rules
|
|
36
|
+
|
|
37
|
+
- Search wiki pages first, then source chunks, then deeper read-only source refs.
|
|
38
|
+
- Use web search only when requested or when current external context is required.
|
|
39
|
+
- File useful answers back into the wiki only after approval or approved auto-write mode.
|
|
40
|
+
|
|
41
|
+
## Lint Rules
|
|
42
|
+
|
|
43
|
+
- Flag stale pages, missing citations, contradictions, orphan pages, duplicate pages, and unresolved source refs.
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function rootIndexTemplate(): string {
|
|
48
|
+
return `# Knowledge Index
|
|
49
|
+
|
|
50
|
+
This is a compact orientation index for agents. It is not the full search index.
|
|
51
|
+
|
|
52
|
+
## Shards
|
|
53
|
+
|
|
54
|
+
- wiki/
|
|
55
|
+
- indexes/
|
|
56
|
+
- schemas/
|
|
57
|
+
- logs/
|
|
58
|
+
|
|
59
|
+
## Source Ownership
|
|
60
|
+
|
|
61
|
+
Raw source files are resolved through open-files. This app stores source refs,
|
|
62
|
+
citations, chunks, generated wiki artifacts, indexes, and run records.
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function wikiReadmeTemplate(): string {
|
|
67
|
+
return `# Wiki
|
|
68
|
+
|
|
69
|
+
Generated durable knowledge pages live here.
|
|
70
|
+
|
|
71
|
+
Pages should be concise, cited, and organized for both humans and agents.
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function initializeWikiLayout(store: ArtifactStore, now = new Date()): Promise<WikiLayoutInitResult> {
|
|
76
|
+
const { year, month, day } = todayParts(now);
|
|
77
|
+
const schemaKey = 'schemas/v1.md';
|
|
78
|
+
const rootIndexKey = 'indexes/root.md';
|
|
79
|
+
const wikiReadmeKey = 'wiki/README.md';
|
|
80
|
+
const logKey = `logs/${year}/${month}/${day}.jsonl`;
|
|
81
|
+
const event = {
|
|
82
|
+
ts: now.toISOString(),
|
|
83
|
+
event: 'wiki_layout_initialized',
|
|
84
|
+
schema_key: schemaKey,
|
|
85
|
+
root_index_key: rootIndexKey,
|
|
86
|
+
wiki_readme_key: wikiReadmeKey,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const writes = [
|
|
90
|
+
store.put({ key: schemaKey, body: agentSchemaTemplate(), content_type: 'text/markdown' }),
|
|
91
|
+
store.put({ key: rootIndexKey, body: rootIndexTemplate(), content_type: 'text/markdown' }),
|
|
92
|
+
store.put({ key: wikiReadmeKey, body: wikiReadmeTemplate(), content_type: 'text/markdown' }),
|
|
93
|
+
store.put({ key: logKey, body: `${JSON.stringify(event)}\n`, content_type: 'application/x-ndjson' }),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
await Promise.all(writes);
|
|
97
|
+
return {
|
|
98
|
+
schema_key: schemaKey,
|
|
99
|
+
root_index_key: rootIndexKey,
|
|
100
|
+
wiki_readme_key: wikiReadmeKey,
|
|
101
|
+
log_key: logKey,
|
|
102
|
+
written: [schemaKey, rootIndexKey, wikiReadmeKey, logKey],
|
|
103
|
+
};
|
|
104
|
+
}
|
package/src/workspace.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const HASNA_KNOWLEDGE_APP_PATH = join('.hasna', 'apps', 'knowledge');
|
|
6
|
+
|
|
7
|
+
export interface KnowledgeWorkspace {
|
|
8
|
+
home: string;
|
|
9
|
+
configPath: string;
|
|
10
|
+
jsonStorePath: string;
|
|
11
|
+
knowledgeDbPath: string;
|
|
12
|
+
artifactsDir: string;
|
|
13
|
+
cacheDir: string;
|
|
14
|
+
exportsDir: string;
|
|
15
|
+
indexesDir: string;
|
|
16
|
+
logsDir: string;
|
|
17
|
+
runsDir: string;
|
|
18
|
+
schemasDir: string;
|
|
19
|
+
wikiDir: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface KnowledgeConfig {
|
|
23
|
+
version: 1;
|
|
24
|
+
mode: 'local' | 'hosted';
|
|
25
|
+
storage: {
|
|
26
|
+
type: 'local' | 's3';
|
|
27
|
+
artifacts_root: string;
|
|
28
|
+
s3?: {
|
|
29
|
+
bucket: string;
|
|
30
|
+
prefix?: string;
|
|
31
|
+
region?: string;
|
|
32
|
+
profile?: string;
|
|
33
|
+
max_attempts?: number;
|
|
34
|
+
server_side_encryption?: 'AES256' | 'aws:kms';
|
|
35
|
+
kms_key_id?: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
sources: {
|
|
39
|
+
preferred_ref: 'open-files';
|
|
40
|
+
allowed_schemes: string[];
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function legacyGlobalStorePath(): string {
|
|
45
|
+
return join(homedir(), '.open-knowledge', 'db.json');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function globalKnowledgeHome(): string {
|
|
49
|
+
return join(homedir(), '.hasna', 'apps', 'knowledge');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function projectKnowledgeHome(cwd = process.cwd()): string {
|
|
53
|
+
return resolve(cwd, HASNA_KNOWLEDGE_APP_PATH);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function workspaceForHome(home: string): KnowledgeWorkspace {
|
|
57
|
+
return {
|
|
58
|
+
home,
|
|
59
|
+
configPath: join(home, 'config.json'),
|
|
60
|
+
jsonStorePath: join(home, 'db.json'),
|
|
61
|
+
knowledgeDbPath: join(home, 'knowledge.db'),
|
|
62
|
+
artifactsDir: join(home, 'artifacts'),
|
|
63
|
+
cacheDir: join(home, 'cache'),
|
|
64
|
+
exportsDir: join(home, 'exports'),
|
|
65
|
+
indexesDir: join(home, 'indexes'),
|
|
66
|
+
logsDir: join(home, 'logs'),
|
|
67
|
+
runsDir: join(home, 'runs'),
|
|
68
|
+
schemasDir: join(home, 'schemas'),
|
|
69
|
+
wikiDir: join(home, 'wiki'),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function defaultKnowledgeConfig(): KnowledgeConfig {
|
|
74
|
+
return {
|
|
75
|
+
version: 1,
|
|
76
|
+
mode: 'local',
|
|
77
|
+
storage: {
|
|
78
|
+
type: 'local',
|
|
79
|
+
artifacts_root: 'artifacts',
|
|
80
|
+
},
|
|
81
|
+
sources: {
|
|
82
|
+
preferred_ref: 'open-files',
|
|
83
|
+
allowed_schemes: ['open-files', 's3', 'file', 'https', 'http'],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function ensureKnowledgeWorkspace(home: string): KnowledgeWorkspace {
|
|
89
|
+
const workspace = workspaceForHome(home);
|
|
90
|
+
mkdirSync(workspace.home, { recursive: true });
|
|
91
|
+
for (const dir of [
|
|
92
|
+
workspace.artifactsDir,
|
|
93
|
+
workspace.cacheDir,
|
|
94
|
+
workspace.exportsDir,
|
|
95
|
+
workspace.indexesDir,
|
|
96
|
+
workspace.logsDir,
|
|
97
|
+
workspace.runsDir,
|
|
98
|
+
workspace.schemasDir,
|
|
99
|
+
workspace.wikiDir,
|
|
100
|
+
]) {
|
|
101
|
+
mkdirSync(dir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
if (!existsSync(workspace.configPath)) {
|
|
104
|
+
writeFileSync(workspace.configPath, `${JSON.stringify(defaultKnowledgeConfig(), null, 2)}\n`);
|
|
105
|
+
}
|
|
106
|
+
return workspace;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function resolveScopedWorkspace(scope: string | undefined, cwd = process.cwd()): KnowledgeWorkspace {
|
|
110
|
+
if (scope === 'project' || scope === 'local') {
|
|
111
|
+
return workspaceForHome(projectKnowledgeHome(cwd));
|
|
112
|
+
}
|
|
113
|
+
return workspaceForHome(globalKnowledgeHome());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function ensureParentDir(path: string): void {
|
|
117
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function readKnowledgeConfig(path: string): KnowledgeConfig {
|
|
121
|
+
const raw = readFileSync(path, 'utf8');
|
|
122
|
+
return JSON.parse(raw) as KnowledgeConfig;
|
|
123
|
+
}
|