@hasna/knowledge 0.2.11 → 0.2.12
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 +18 -2
- package/bin/open-knowledge-mcp.js +218 -10
- package/bin/open-knowledge.js +44 -31
- package/docs/architecture/ai-native-knowledge-base.md +12 -0
- package/package.json +1 -1
- package/src/cli.ts +31 -4
- package/src/knowledge-db.ts +2 -0
- package/src/mcp.js +12 -0
- package/src/service.ts +26 -2
- package/src/storage-contract.ts +265 -0
- package/src/wiki-layout.ts +22 -6
package/README.md
CHANGED
|
@@ -62,6 +62,9 @@ open-knowledge export --format jsonl
|
|
|
62
62
|
# Show resolved workspace paths
|
|
63
63
|
open-knowledge paths --scope project --json
|
|
64
64
|
|
|
65
|
+
# Inspect local/S3 artifact storage and source ownership
|
|
66
|
+
open-knowledge storage status --scope project --json
|
|
67
|
+
|
|
65
68
|
# Initialize the project SQLite catalog
|
|
66
69
|
open-knowledge db init --scope project
|
|
67
70
|
|
|
@@ -168,6 +171,18 @@ open-knowledge paths [--scope global|project|local] [--json]
|
|
|
168
171
|
Show the resolved Hasna app workspace, JSON compatibility store, SQLite path,
|
|
169
172
|
artifact directories, and config.
|
|
170
173
|
|
|
174
|
+
### storage
|
|
175
|
+
```bash
|
|
176
|
+
open-knowledge storage status [--scope project] [--json]
|
|
177
|
+
open-knowledge storage validate [--scope project] [--json]
|
|
178
|
+
```
|
|
179
|
+
Show the storage contract for local or S3-backed generated artifacts. Local mode
|
|
180
|
+
uses `.hasna/apps/knowledge` for config, SQLite, indexes, wiki artifacts, logs,
|
|
181
|
+
runs, and exports. S3 mode stores generated artifacts under the configured
|
|
182
|
+
knowledge bucket/prefix while `open-files` remains the source of truth for raw
|
|
183
|
+
source bytes. The command also reports artifact classes, allowed source ref
|
|
184
|
+
schemes, and warnings for non-scalable or unsafe config.
|
|
185
|
+
|
|
171
186
|
### db
|
|
172
187
|
```bash
|
|
173
188
|
open-knowledge db init [--scope project]
|
|
@@ -277,8 +292,9 @@ open-knowledge-mcp
|
|
|
277
292
|
The MCP server exposes item tools (`ok_add`, `ok_list`, `ok_get`, `ok_update`,
|
|
278
293
|
`ok_delete`, `ok_archive`, `ok_restore`, `ok_upsert`, `ok_untag`,
|
|
279
294
|
`ok_bulk_delete`, `ok_prune`, `ok_dedupe`, `ok_stats`, `ok_export`,
|
|
280
|
-
`ok_import`, `ok_batch`), workspace inspection (`ok_paths
|
|
281
|
-
parsing/resolution
|
|
295
|
+
`ok_import`, `ok_batch`), workspace/storage inspection (`ok_paths`,
|
|
296
|
+
`ok_storage_status`), and source-ref parsing/resolution
|
|
297
|
+
(`ok_parse_source_ref`, `ok_resolve_source`).
|
|
282
298
|
|
|
283
299
|
## Source And Artifact Boundary
|
|
284
300
|
|
|
@@ -13660,7 +13660,7 @@ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync
|
|
|
13660
13660
|
// package.json
|
|
13661
13661
|
var package_default = {
|
|
13662
13662
|
name: "@hasna/knowledge",
|
|
13663
|
-
version: "0.2.
|
|
13663
|
+
version: "0.2.12",
|
|
13664
13664
|
description: "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
|
|
13665
13665
|
type: "module",
|
|
13666
13666
|
bin: {
|
|
@@ -14391,7 +14391,8 @@ function getKnowledgeDbStats(path) {
|
|
|
14391
14391
|
run_events: count(db, "run_events"),
|
|
14392
14392
|
redaction_findings: count(db, "redaction_findings"),
|
|
14393
14393
|
audit_events: count(db, "audit_events"),
|
|
14394
|
-
approval_gates: count(db, "approval_gates")
|
|
14394
|
+
approval_gates: count(db, "approval_gates"),
|
|
14395
|
+
storage_objects: count(db, "storage_objects")
|
|
14395
14396
|
};
|
|
14396
14397
|
} finally {
|
|
14397
14398
|
db.close();
|
|
@@ -15922,6 +15923,177 @@ function providerStatus(config2, env = process.env) {
|
|
|
15922
15923
|
};
|
|
15923
15924
|
}
|
|
15924
15925
|
|
|
15926
|
+
// src/storage-contract.ts
|
|
15927
|
+
import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
|
|
15928
|
+
var GENERATED_ARTIFACTS = [
|
|
15929
|
+
{
|
|
15930
|
+
kind: "schema",
|
|
15931
|
+
prefix: "schemas/",
|
|
15932
|
+
description: "Machine-readable agent schemas and source rules."
|
|
15933
|
+
},
|
|
15934
|
+
{
|
|
15935
|
+
kind: "index",
|
|
15936
|
+
prefix: "indexes/",
|
|
15937
|
+
description: "Small orientation indexes and future shard manifests."
|
|
15938
|
+
},
|
|
15939
|
+
{
|
|
15940
|
+
kind: "log",
|
|
15941
|
+
prefix: "logs/",
|
|
15942
|
+
description: "Append-only JSONL run and wiki-maintenance log partitions."
|
|
15943
|
+
},
|
|
15944
|
+
{
|
|
15945
|
+
kind: "run",
|
|
15946
|
+
prefix: "runs/",
|
|
15947
|
+
description: "Prompt/tool/cost ledgers and generated output records."
|
|
15948
|
+
},
|
|
15949
|
+
{
|
|
15950
|
+
kind: "wiki_page",
|
|
15951
|
+
prefix: "wiki/",
|
|
15952
|
+
description: "Generated cited Markdown pages, not raw source files."
|
|
15953
|
+
},
|
|
15954
|
+
{
|
|
15955
|
+
kind: "export",
|
|
15956
|
+
prefix: "exports/",
|
|
15957
|
+
description: "Portable exports and snapshots of derived knowledge state."
|
|
15958
|
+
}
|
|
15959
|
+
];
|
|
15960
|
+
function hashArtifactBody(body) {
|
|
15961
|
+
const bytes = typeof body === "string" ? Buffer.from(body) : Buffer.from(body);
|
|
15962
|
+
return {
|
|
15963
|
+
hash: `sha256:${createHash5("sha256").update(bytes).digest("hex")}`,
|
|
15964
|
+
size_bytes: bytes.byteLength
|
|
15965
|
+
};
|
|
15966
|
+
}
|
|
15967
|
+
function artifactKindForKey(key) {
|
|
15968
|
+
const match = GENERATED_ARTIFACTS.find((entry) => key.startsWith(entry.prefix));
|
|
15969
|
+
return match?.kind ?? "artifact";
|
|
15970
|
+
}
|
|
15971
|
+
function resolveStorageContract(config2, workspace, scope = "global") {
|
|
15972
|
+
const validation = validateStorageConfig(config2, workspace);
|
|
15973
|
+
const s3 = config2.storage.s3 ?? null;
|
|
15974
|
+
const prefix = s3?.prefix?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
15975
|
+
const s3UriPrefix = s3 ? `s3://${s3.bucket}/${prefix ? `${prefix}/` : ""}` : "";
|
|
15976
|
+
return {
|
|
15977
|
+
scope,
|
|
15978
|
+
mode: config2.mode,
|
|
15979
|
+
storage_type: config2.storage.type,
|
|
15980
|
+
workspace_home: workspace.home,
|
|
15981
|
+
local_layout: {
|
|
15982
|
+
app_path: HASNA_KNOWLEDGE_APP_PATH,
|
|
15983
|
+
config_path: workspace.configPath,
|
|
15984
|
+
json_store_path: workspace.jsonStorePath,
|
|
15985
|
+
knowledge_db_path: workspace.knowledgeDbPath,
|
|
15986
|
+
directories: {
|
|
15987
|
+
artifacts: workspace.artifactsDir,
|
|
15988
|
+
cache: workspace.cacheDir,
|
|
15989
|
+
exports: workspace.exportsDir,
|
|
15990
|
+
indexes: workspace.indexesDir,
|
|
15991
|
+
logs: workspace.logsDir,
|
|
15992
|
+
runs: workspace.runsDir,
|
|
15993
|
+
schemas: workspace.schemasDir,
|
|
15994
|
+
wiki: workspace.wikiDir
|
|
15995
|
+
}
|
|
15996
|
+
},
|
|
15997
|
+
artifact_store: {
|
|
15998
|
+
type: config2.storage.type,
|
|
15999
|
+
artifacts_root: config2.storage.artifacts_root,
|
|
16000
|
+
uri_prefix: config2.storage.type === "s3" ? s3UriPrefix : `file://${workspace.artifactsDir}/`,
|
|
16001
|
+
s3: s3 ? {
|
|
16002
|
+
bucket: s3.bucket,
|
|
16003
|
+
prefix,
|
|
16004
|
+
region: s3.region ?? null,
|
|
16005
|
+
profile: s3.profile ?? null,
|
|
16006
|
+
server_side_encryption: s3.server_side_encryption ?? null,
|
|
16007
|
+
kms_key_configured: Boolean(s3.kms_key_id)
|
|
16008
|
+
} : null
|
|
16009
|
+
},
|
|
16010
|
+
source_ownership: {
|
|
16011
|
+
owner: "open-files",
|
|
16012
|
+
preferred_ref: config2.sources.preferred_ref,
|
|
16013
|
+
allowed_schemes: config2.sources.allowed_schemes,
|
|
16014
|
+
raw_source_bytes_stored_in_open_knowledge: false,
|
|
16015
|
+
stores: [
|
|
16016
|
+
"source refs",
|
|
16017
|
+
"source revisions and hashes",
|
|
16018
|
+
"citation spans",
|
|
16019
|
+
"redacted extracted chunks",
|
|
16020
|
+
"embeddings",
|
|
16021
|
+
"generated wiki artifacts",
|
|
16022
|
+
"indexes",
|
|
16023
|
+
"run ledgers"
|
|
16024
|
+
],
|
|
16025
|
+
does_not_store: [
|
|
16026
|
+
"raw open-files bytes",
|
|
16027
|
+
"S3 object credentials",
|
|
16028
|
+
"connector secrets",
|
|
16029
|
+
"hosted tenant ownership state"
|
|
16030
|
+
]
|
|
16031
|
+
},
|
|
16032
|
+
generated_artifacts: GENERATED_ARTIFACTS,
|
|
16033
|
+
scalability: {
|
|
16034
|
+
catalog: "knowledge.db tracks sources, revisions, chunks, citations, indexes, runs, and storage_objects.",
|
|
16035
|
+
indexes: "Indexes are cataloged DB rows plus sharded artifacts, not one giant index.md.",
|
|
16036
|
+
logs: "Logs use dated JSONL partitions under logs/yyyy/mm/dd.jsonl.",
|
|
16037
|
+
markdown: "Markdown pages are the readable wiki layer over DB/object-store state."
|
|
16038
|
+
},
|
|
16039
|
+
warnings: validation.warnings
|
|
16040
|
+
};
|
|
16041
|
+
}
|
|
16042
|
+
function validateStorageConfig(config2, workspace) {
|
|
16043
|
+
const errors3 = [];
|
|
16044
|
+
const warnings = [];
|
|
16045
|
+
if (!workspace.home.endsWith(HASNA_KNOWLEDGE_APP_PATH)) {
|
|
16046
|
+
warnings.push(`Workspace home does not end with ${HASNA_KNOWLEDGE_APP_PATH}: ${workspace.home}`);
|
|
16047
|
+
}
|
|
16048
|
+
if (config2.storage.type === "s3") {
|
|
16049
|
+
if (!config2.storage.s3?.bucket)
|
|
16050
|
+
errors3.push("storage.s3.bucket is required when storage.type is s3.");
|
|
16051
|
+
if (!config2.storage.s3?.prefix)
|
|
16052
|
+
warnings.push("storage.s3.prefix is empty; generated knowledge artifacts will be written at the bucket root.");
|
|
16053
|
+
if (config2.mode === "local")
|
|
16054
|
+
warnings.push("storage.type is s3 while mode is local; this is valid for BYO S3, but hosted wrappers should set mode to hosted.");
|
|
16055
|
+
}
|
|
16056
|
+
if (config2.storage.type === "local" && config2.storage.s3) {
|
|
16057
|
+
warnings.push("storage.s3 is configured but ignored while storage.type is local.");
|
|
16058
|
+
}
|
|
16059
|
+
if (config2.sources.preferred_ref !== "open-files") {
|
|
16060
|
+
warnings.push("sources.preferred_ref should stay open-files for durable company knowledge.");
|
|
16061
|
+
}
|
|
16062
|
+
if (!config2.sources.allowed_schemes.includes("open-files")) {
|
|
16063
|
+
errors3.push("sources.allowed_schemes must include open-files.");
|
|
16064
|
+
}
|
|
16065
|
+
return {
|
|
16066
|
+
ok: errors3.length === 0,
|
|
16067
|
+
errors: errors3,
|
|
16068
|
+
warnings
|
|
16069
|
+
};
|
|
16070
|
+
}
|
|
16071
|
+
function recordStorageObjects(db, objects, now = new Date) {
|
|
16072
|
+
const timestamp = now.toISOString();
|
|
16073
|
+
const statement = db.prepare(`
|
|
16074
|
+
INSERT INTO storage_objects (
|
|
16075
|
+
id, artifact_uri, kind, content_type, hash, size_bytes, metadata_json, created_at, updated_at
|
|
16076
|
+
)
|
|
16077
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
16078
|
+
ON CONFLICT(artifact_uri) DO UPDATE SET
|
|
16079
|
+
kind = excluded.kind,
|
|
16080
|
+
content_type = excluded.content_type,
|
|
16081
|
+
hash = excluded.hash,
|
|
16082
|
+
size_bytes = excluded.size_bytes,
|
|
16083
|
+
metadata_json = excluded.metadata_json,
|
|
16084
|
+
updated_at = excluded.updated_at
|
|
16085
|
+
`);
|
|
16086
|
+
const insert = db.transaction((entries) => {
|
|
16087
|
+
for (const entry of entries) {
|
|
16088
|
+
statement.run(randomUUID4(), entry.uri, entry.kind, entry.content_type ?? null, entry.hash ?? null, entry.size_bytes ?? null, JSON.stringify({
|
|
16089
|
+
key: entry.key,
|
|
16090
|
+
...entry.metadata ?? {}
|
|
16091
|
+
}), timestamp, timestamp);
|
|
16092
|
+
}
|
|
16093
|
+
});
|
|
16094
|
+
insert(objects);
|
|
16095
|
+
}
|
|
16096
|
+
|
|
15925
16097
|
// src/wiki-layout.ts
|
|
15926
16098
|
function todayParts(now) {
|
|
15927
16099
|
const year = String(now.getUTCFullYear());
|
|
@@ -15996,19 +16168,29 @@ async function initializeWikiLayout(store, now = new Date) {
|
|
|
15996
16168
|
root_index_key: rootIndexKey,
|
|
15997
16169
|
wiki_readme_key: wikiReadmeKey
|
|
15998
16170
|
};
|
|
15999
|
-
const
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
`, content_type: "application/x-ndjson" }
|
|
16171
|
+
const entries = [
|
|
16172
|
+
{ key: schemaKey, body: agentSchemaTemplate(), content_type: "text/markdown" },
|
|
16173
|
+
{ key: rootIndexKey, body: rootIndexTemplate(), content_type: "text/markdown" },
|
|
16174
|
+
{ key: wikiReadmeKey, body: wikiReadmeTemplate(), content_type: "text/markdown" },
|
|
16175
|
+
{ key: logKey, body: `${JSON.stringify(event)}
|
|
16176
|
+
`, content_type: "application/x-ndjson" }
|
|
16005
16177
|
];
|
|
16006
|
-
await Promise.all(
|
|
16178
|
+
const artifacts = await Promise.all(entries.map(async (entry) => {
|
|
16179
|
+
const result = await store.put(entry);
|
|
16180
|
+
return {
|
|
16181
|
+
key: result.key,
|
|
16182
|
+
uri: result.uri,
|
|
16183
|
+
kind: artifactKindForKey(entry.key),
|
|
16184
|
+
content_type: entry.content_type,
|
|
16185
|
+
...hashArtifactBody(entry.body)
|
|
16186
|
+
};
|
|
16187
|
+
}));
|
|
16007
16188
|
return {
|
|
16008
16189
|
schema_key: schemaKey,
|
|
16009
16190
|
root_index_key: rootIndexKey,
|
|
16010
16191
|
wiki_readme_key: wikiReadmeKey,
|
|
16011
16192
|
log_key: logKey,
|
|
16193
|
+
artifacts,
|
|
16012
16194
|
written: [schemaKey, rootIndexKey, wikiReadmeKey, logKey]
|
|
16013
16195
|
};
|
|
16014
16196
|
}
|
|
@@ -16048,6 +16230,12 @@ class KnowledgeService {
|
|
|
16048
16230
|
artifactStore() {
|
|
16049
16231
|
return createArtifactStore(this.config(), this.ensureWorkspace());
|
|
16050
16232
|
}
|
|
16233
|
+
storageContract() {
|
|
16234
|
+
return resolveStorageContract(this.config(), this.ensureWorkspace(), this.scope);
|
|
16235
|
+
}
|
|
16236
|
+
validateStorage() {
|
|
16237
|
+
return validateStorageConfig(this.config(), this.ensureWorkspace());
|
|
16238
|
+
}
|
|
16051
16239
|
paths() {
|
|
16052
16240
|
const workspace = this.ensureWorkspace();
|
|
16053
16241
|
return {
|
|
@@ -16076,7 +16264,16 @@ class KnowledgeService {
|
|
|
16076
16264
|
return getKnowledgeDbStats(workspace.knowledgeDbPath);
|
|
16077
16265
|
}
|
|
16078
16266
|
async initWiki() {
|
|
16079
|
-
|
|
16267
|
+
const workspace = this.ensureWorkspace();
|
|
16268
|
+
migrateKnowledgeDb(workspace.knowledgeDbPath);
|
|
16269
|
+
const result = await initializeWikiLayout(this.artifactStore());
|
|
16270
|
+
const db = openKnowledgeDb(workspace.knowledgeDbPath);
|
|
16271
|
+
try {
|
|
16272
|
+
recordStorageObjects(db, result.artifacts);
|
|
16273
|
+
} finally {
|
|
16274
|
+
db.close();
|
|
16275
|
+
}
|
|
16276
|
+
return result;
|
|
16080
16277
|
}
|
|
16081
16278
|
async ingestManifest(input) {
|
|
16082
16279
|
const workspace = this.ensureWorkspace();
|
|
@@ -16187,6 +16384,17 @@ function buildServer() {
|
|
|
16187
16384
|
}, async ({ scope }) => {
|
|
16188
16385
|
return jsonText(createKnowledgeService({ scope }).paths());
|
|
16189
16386
|
});
|
|
16387
|
+
registerTool(server, "ok_storage_status", "Knowledge storage status", "Inspect local/S3 artifact storage, source ownership, and scalability contract", {
|
|
16388
|
+
scope: scopeField
|
|
16389
|
+
}, async ({ scope }) => {
|
|
16390
|
+
const service = createKnowledgeService({ scope });
|
|
16391
|
+
const validation = service.validateStorage();
|
|
16392
|
+
return jsonText({
|
|
16393
|
+
ok: validation.ok,
|
|
16394
|
+
...service.storageContract(),
|
|
16395
|
+
validation
|
|
16396
|
+
});
|
|
16397
|
+
});
|
|
16190
16398
|
registerTool(server, "ok_parse_source_ref", "Parse source reference", "Parse and validate an open-files, S3, file, or web source ref", {
|
|
16191
16399
|
uri: exports_external.string().describe("Source reference URI")
|
|
16192
16400
|
}, async ({ uri }) => {
|