@hasna/knowledge 0.2.11 → 0.2.13
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 +24 -2
- package/bin/open-knowledge-mcp.js +370 -14
- package/bin/open-knowledge.js +57 -31
- package/docs/architecture/ai-native-knowledge-base.md +27 -0
- package/package.json +1 -1
- package/src/cli.ts +31 -4
- package/src/knowledge-db.ts +3 -0
- package/src/manifest-ingest.ts +19 -2
- package/src/mcp.js +12 -0
- package/src/provenance.ts +93 -0
- package/src/service.ts +28 -3
- package/src/source-resolver.ts +18 -0
- package/src/storage-contract.ts +265 -0
- package/src/wiki-layout.ts +113 -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
|
|
|
@@ -298,6 +314,12 @@ source ref. It does not copy raw files into the knowledge workspace; local file,
|
|
|
298
314
|
S3, web, and open-files inputs are converted into redacted chunks with offsets,
|
|
299
315
|
hashes, revision metadata, and FTS rows.
|
|
300
316
|
|
|
317
|
+
Chunks, resolver results, generated wiki pages, and index records carry
|
|
318
|
+
provenance metadata: source owner, source ref/URI, revision/hash, chunk offsets,
|
|
319
|
+
read-only status, citation requirements, and stale-source status. This keeps
|
|
320
|
+
future semantic search and wiki compile flows tied back to `open-files` instead
|
|
321
|
+
of detached Markdown.
|
|
322
|
+
|
|
301
323
|
AI provider configuration is local/BYOK by default. `open-knowledge` declares
|
|
302
324
|
AI SDK v6 provider support through `ai`, `@ai-sdk/openai`,
|
|
303
325
|
`@ai-sdk/anthropic`, and `@ai-sdk/deepseek`, but does not call providers until a
|
|
@@ -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.13",
|
|
13664
13664
|
description: "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
|
|
13665
13665
|
type: "module",
|
|
13666
13666
|
bin: {
|
|
@@ -14353,6 +14353,7 @@ function openKnowledgeDb(path) {
|
|
|
14353
14353
|
ensureParentDir(path);
|
|
14354
14354
|
const db = new Database(path);
|
|
14355
14355
|
db.exec("PRAGMA foreign_keys = ON;");
|
|
14356
|
+
db.exec("PRAGMA busy_timeout = 5000;");
|
|
14356
14357
|
return db;
|
|
14357
14358
|
}
|
|
14358
14359
|
function migrateKnowledgeDb(path) {
|
|
@@ -14391,7 +14392,8 @@ function getKnowledgeDbStats(path) {
|
|
|
14391
14392
|
run_events: count(db, "run_events"),
|
|
14392
14393
|
redaction_findings: count(db, "redaction_findings"),
|
|
14393
14394
|
audit_events: count(db, "audit_events"),
|
|
14394
|
-
approval_gates: count(db, "approval_gates")
|
|
14395
|
+
approval_gates: count(db, "approval_gates"),
|
|
14396
|
+
storage_objects: count(db, "storage_objects")
|
|
14395
14397
|
};
|
|
14396
14398
|
} finally {
|
|
14397
14399
|
db.close();
|
|
@@ -14898,6 +14900,50 @@ async function consumeOpenFilesOutbox(options) {
|
|
|
14898
14900
|
import { createHash as createHash3 } from "crypto";
|
|
14899
14901
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
14900
14902
|
import { basename as basename2 } from "path";
|
|
14903
|
+
|
|
14904
|
+
// src/provenance.ts
|
|
14905
|
+
function isStaleStatus(status) {
|
|
14906
|
+
return ["deleted", "stale", "invalidated", "reindex_required"].includes((status ?? "").toLowerCase());
|
|
14907
|
+
}
|
|
14908
|
+
function sourceProvenance(input) {
|
|
14909
|
+
const status = input.status ?? null;
|
|
14910
|
+
return {
|
|
14911
|
+
source_owner: "open-files",
|
|
14912
|
+
source_ref: input.source_ref ?? null,
|
|
14913
|
+
source_uri: input.source_uri ?? null,
|
|
14914
|
+
source_kind: input.source_kind ?? null,
|
|
14915
|
+
source_revision_id: input.source_revision_id ?? null,
|
|
14916
|
+
revision: input.revision ?? null,
|
|
14917
|
+
hash: input.hash ?? null,
|
|
14918
|
+
chunk_id: input.chunk_id ?? null,
|
|
14919
|
+
start_offset: input.start_offset ?? null,
|
|
14920
|
+
end_offset: input.end_offset ?? null,
|
|
14921
|
+
status,
|
|
14922
|
+
read_only: true,
|
|
14923
|
+
citation_required: true,
|
|
14924
|
+
resolver: input.resolver ?? null,
|
|
14925
|
+
stale: isStaleStatus(status)
|
|
14926
|
+
};
|
|
14927
|
+
}
|
|
14928
|
+
function generatedArtifactProvenance(input) {
|
|
14929
|
+
return {
|
|
14930
|
+
source_owner: "open-files",
|
|
14931
|
+
generated_from: input.generated_from,
|
|
14932
|
+
artifact_key: input.artifact_key,
|
|
14933
|
+
source_refs: input.source_refs ?? [],
|
|
14934
|
+
read_only_sources: true,
|
|
14935
|
+
citation_required: input.citation_required ?? true,
|
|
14936
|
+
raw_source_bytes_stored_in_open_knowledge: false
|
|
14937
|
+
};
|
|
14938
|
+
}
|
|
14939
|
+
function withProvenance(metadata, provenance) {
|
|
14940
|
+
return {
|
|
14941
|
+
...metadata,
|
|
14942
|
+
provenance
|
|
14943
|
+
};
|
|
14944
|
+
}
|
|
14945
|
+
|
|
14946
|
+
// src/manifest-ingest.ts
|
|
14901
14947
|
function stableId2(prefix, value) {
|
|
14902
14948
|
return `${prefix}_${createHash3("sha256").update(value).digest("hex").slice(0, 20)}`;
|
|
14903
14949
|
}
|
|
@@ -15186,15 +15232,31 @@ function insertChunks(db, sourceRevisionId, item, now, maxChars, overlapChars, s
|
|
|
15186
15232
|
const chunks = chunkText(redacted.text, maxChars, overlapChars);
|
|
15187
15233
|
for (const chunk of chunks) {
|
|
15188
15234
|
const chunkId = stableId2("chk", `${sourceRevisionId}\x00${chunk.ordinal}\x00${chunk.text}`);
|
|
15189
|
-
const
|
|
15235
|
+
const provenance = sourceProvenance({
|
|
15190
15236
|
source_ref: item.sourceRef,
|
|
15191
15237
|
source_uri: item.sourceUri,
|
|
15238
|
+
source_kind: item.kind,
|
|
15239
|
+
source_revision_id: sourceRevisionId,
|
|
15240
|
+
revision: item.revision,
|
|
15241
|
+
hash: item.hash,
|
|
15242
|
+
chunk_id: chunkId,
|
|
15243
|
+
start_offset: chunk.startOffset,
|
|
15244
|
+
end_offset: chunk.endOffset,
|
|
15245
|
+
status: item.status,
|
|
15246
|
+
resolver: "open-files-read-only"
|
|
15247
|
+
});
|
|
15248
|
+
const metadata = withProvenance({
|
|
15249
|
+
source_ref: item.sourceRef,
|
|
15250
|
+
source_uri: item.sourceUri,
|
|
15251
|
+
source_kind: item.kind,
|
|
15252
|
+
source_revision_id: sourceRevisionId,
|
|
15253
|
+
revision: item.revision,
|
|
15192
15254
|
hash: item.hash,
|
|
15193
15255
|
status: item.status,
|
|
15194
15256
|
path: asString2(item.raw.path) ?? null,
|
|
15195
15257
|
mime: asString2(item.raw.mime) ?? asString2(item.raw.content_type) ?? null,
|
|
15196
15258
|
size: asNumber(item.raw.size) ?? null
|
|
15197
|
-
};
|
|
15259
|
+
}, provenance);
|
|
15198
15260
|
db.run(`INSERT INTO chunks (id, source_revision_id, kind, ordinal, text, token_count, start_offset, end_offset, metadata_json, created_at)
|
|
15199
15261
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
15200
15262
|
chunkId,
|
|
@@ -15487,6 +15549,19 @@ async function resolveOpenFilesSource(options) {
|
|
|
15487
15549
|
end_offset: row.end_offset,
|
|
15488
15550
|
resolved_at: resolvedAt
|
|
15489
15551
|
};
|
|
15552
|
+
const provenance = sourceProvenance({
|
|
15553
|
+
source_ref: evidence.source_ref,
|
|
15554
|
+
source_uri: evidence.source_uri,
|
|
15555
|
+
source_kind: source.kind,
|
|
15556
|
+
source_revision_id: evidence.source_revision_id,
|
|
15557
|
+
revision: evidence.revision,
|
|
15558
|
+
hash: evidence.hash,
|
|
15559
|
+
chunk_id: row.id,
|
|
15560
|
+
start_offset: row.start_offset,
|
|
15561
|
+
end_offset: row.end_offset,
|
|
15562
|
+
status: metadataString(metadata, ["status"]),
|
|
15563
|
+
resolver: evidence.resolver
|
|
15564
|
+
});
|
|
15490
15565
|
return {
|
|
15491
15566
|
id: row.id,
|
|
15492
15567
|
kind: row.kind,
|
|
@@ -15496,7 +15571,8 @@ async function resolveOpenFilesSource(options) {
|
|
|
15496
15571
|
start_offset: row.start_offset,
|
|
15497
15572
|
end_offset: row.end_offset,
|
|
15498
15573
|
metadata,
|
|
15499
|
-
evidence
|
|
15574
|
+
evidence,
|
|
15575
|
+
provenance
|
|
15500
15576
|
};
|
|
15501
15577
|
});
|
|
15502
15578
|
const citations = chunks.map((chunk) => ({
|
|
@@ -15506,7 +15582,8 @@ async function resolveOpenFilesSource(options) {
|
|
|
15506
15582
|
quote: chunk.text.slice(0, 500),
|
|
15507
15583
|
start_offset: chunk.start_offset,
|
|
15508
15584
|
end_offset: chunk.end_offset,
|
|
15509
|
-
evidence: chunk.evidence
|
|
15585
|
+
evidence: chunk.evidence,
|
|
15586
|
+
provenance: chunk.provenance
|
|
15510
15587
|
}));
|
|
15511
15588
|
recordAuditEvent(db, {
|
|
15512
15589
|
event_type: "source_read",
|
|
@@ -15922,13 +15999,188 @@ function providerStatus(config2, env = process.env) {
|
|
|
15922
15999
|
};
|
|
15923
16000
|
}
|
|
15924
16001
|
|
|
16002
|
+
// src/storage-contract.ts
|
|
16003
|
+
import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
|
|
16004
|
+
var GENERATED_ARTIFACTS = [
|
|
16005
|
+
{
|
|
16006
|
+
kind: "schema",
|
|
16007
|
+
prefix: "schemas/",
|
|
16008
|
+
description: "Machine-readable agent schemas and source rules."
|
|
16009
|
+
},
|
|
16010
|
+
{
|
|
16011
|
+
kind: "index",
|
|
16012
|
+
prefix: "indexes/",
|
|
16013
|
+
description: "Small orientation indexes and future shard manifests."
|
|
16014
|
+
},
|
|
16015
|
+
{
|
|
16016
|
+
kind: "log",
|
|
16017
|
+
prefix: "logs/",
|
|
16018
|
+
description: "Append-only JSONL run and wiki-maintenance log partitions."
|
|
16019
|
+
},
|
|
16020
|
+
{
|
|
16021
|
+
kind: "run",
|
|
16022
|
+
prefix: "runs/",
|
|
16023
|
+
description: "Prompt/tool/cost ledgers and generated output records."
|
|
16024
|
+
},
|
|
16025
|
+
{
|
|
16026
|
+
kind: "wiki_page",
|
|
16027
|
+
prefix: "wiki/",
|
|
16028
|
+
description: "Generated cited Markdown pages, not raw source files."
|
|
16029
|
+
},
|
|
16030
|
+
{
|
|
16031
|
+
kind: "export",
|
|
16032
|
+
prefix: "exports/",
|
|
16033
|
+
description: "Portable exports and snapshots of derived knowledge state."
|
|
16034
|
+
}
|
|
16035
|
+
];
|
|
16036
|
+
function hashArtifactBody(body) {
|
|
16037
|
+
const bytes = typeof body === "string" ? Buffer.from(body) : Buffer.from(body);
|
|
16038
|
+
return {
|
|
16039
|
+
hash: `sha256:${createHash5("sha256").update(bytes).digest("hex")}`,
|
|
16040
|
+
size_bytes: bytes.byteLength
|
|
16041
|
+
};
|
|
16042
|
+
}
|
|
16043
|
+
function artifactKindForKey(key) {
|
|
16044
|
+
const match = GENERATED_ARTIFACTS.find((entry) => key.startsWith(entry.prefix));
|
|
16045
|
+
return match?.kind ?? "artifact";
|
|
16046
|
+
}
|
|
16047
|
+
function resolveStorageContract(config2, workspace, scope = "global") {
|
|
16048
|
+
const validation = validateStorageConfig(config2, workspace);
|
|
16049
|
+
const s3 = config2.storage.s3 ?? null;
|
|
16050
|
+
const prefix = s3?.prefix?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
16051
|
+
const s3UriPrefix = s3 ? `s3://${s3.bucket}/${prefix ? `${prefix}/` : ""}` : "";
|
|
16052
|
+
return {
|
|
16053
|
+
scope,
|
|
16054
|
+
mode: config2.mode,
|
|
16055
|
+
storage_type: config2.storage.type,
|
|
16056
|
+
workspace_home: workspace.home,
|
|
16057
|
+
local_layout: {
|
|
16058
|
+
app_path: HASNA_KNOWLEDGE_APP_PATH,
|
|
16059
|
+
config_path: workspace.configPath,
|
|
16060
|
+
json_store_path: workspace.jsonStorePath,
|
|
16061
|
+
knowledge_db_path: workspace.knowledgeDbPath,
|
|
16062
|
+
directories: {
|
|
16063
|
+
artifacts: workspace.artifactsDir,
|
|
16064
|
+
cache: workspace.cacheDir,
|
|
16065
|
+
exports: workspace.exportsDir,
|
|
16066
|
+
indexes: workspace.indexesDir,
|
|
16067
|
+
logs: workspace.logsDir,
|
|
16068
|
+
runs: workspace.runsDir,
|
|
16069
|
+
schemas: workspace.schemasDir,
|
|
16070
|
+
wiki: workspace.wikiDir
|
|
16071
|
+
}
|
|
16072
|
+
},
|
|
16073
|
+
artifact_store: {
|
|
16074
|
+
type: config2.storage.type,
|
|
16075
|
+
artifacts_root: config2.storage.artifacts_root,
|
|
16076
|
+
uri_prefix: config2.storage.type === "s3" ? s3UriPrefix : `file://${workspace.artifactsDir}/`,
|
|
16077
|
+
s3: s3 ? {
|
|
16078
|
+
bucket: s3.bucket,
|
|
16079
|
+
prefix,
|
|
16080
|
+
region: s3.region ?? null,
|
|
16081
|
+
profile: s3.profile ?? null,
|
|
16082
|
+
server_side_encryption: s3.server_side_encryption ?? null,
|
|
16083
|
+
kms_key_configured: Boolean(s3.kms_key_id)
|
|
16084
|
+
} : null
|
|
16085
|
+
},
|
|
16086
|
+
source_ownership: {
|
|
16087
|
+
owner: "open-files",
|
|
16088
|
+
preferred_ref: config2.sources.preferred_ref,
|
|
16089
|
+
allowed_schemes: config2.sources.allowed_schemes,
|
|
16090
|
+
raw_source_bytes_stored_in_open_knowledge: false,
|
|
16091
|
+
stores: [
|
|
16092
|
+
"source refs",
|
|
16093
|
+
"source revisions and hashes",
|
|
16094
|
+
"citation spans",
|
|
16095
|
+
"redacted extracted chunks",
|
|
16096
|
+
"embeddings",
|
|
16097
|
+
"generated wiki artifacts",
|
|
16098
|
+
"indexes",
|
|
16099
|
+
"run ledgers"
|
|
16100
|
+
],
|
|
16101
|
+
does_not_store: [
|
|
16102
|
+
"raw open-files bytes",
|
|
16103
|
+
"S3 object credentials",
|
|
16104
|
+
"connector secrets",
|
|
16105
|
+
"hosted tenant ownership state"
|
|
16106
|
+
]
|
|
16107
|
+
},
|
|
16108
|
+
generated_artifacts: GENERATED_ARTIFACTS,
|
|
16109
|
+
scalability: {
|
|
16110
|
+
catalog: "knowledge.db tracks sources, revisions, chunks, citations, indexes, runs, and storage_objects.",
|
|
16111
|
+
indexes: "Indexes are cataloged DB rows plus sharded artifacts, not one giant index.md.",
|
|
16112
|
+
logs: "Logs use dated JSONL partitions under logs/yyyy/mm/dd.jsonl.",
|
|
16113
|
+
markdown: "Markdown pages are the readable wiki layer over DB/object-store state."
|
|
16114
|
+
},
|
|
16115
|
+
warnings: validation.warnings
|
|
16116
|
+
};
|
|
16117
|
+
}
|
|
16118
|
+
function validateStorageConfig(config2, workspace) {
|
|
16119
|
+
const errors3 = [];
|
|
16120
|
+
const warnings = [];
|
|
16121
|
+
if (!workspace.home.endsWith(HASNA_KNOWLEDGE_APP_PATH)) {
|
|
16122
|
+
warnings.push(`Workspace home does not end with ${HASNA_KNOWLEDGE_APP_PATH}: ${workspace.home}`);
|
|
16123
|
+
}
|
|
16124
|
+
if (config2.storage.type === "s3") {
|
|
16125
|
+
if (!config2.storage.s3?.bucket)
|
|
16126
|
+
errors3.push("storage.s3.bucket is required when storage.type is s3.");
|
|
16127
|
+
if (!config2.storage.s3?.prefix)
|
|
16128
|
+
warnings.push("storage.s3.prefix is empty; generated knowledge artifacts will be written at the bucket root.");
|
|
16129
|
+
if (config2.mode === "local")
|
|
16130
|
+
warnings.push("storage.type is s3 while mode is local; this is valid for BYO S3, but hosted wrappers should set mode to hosted.");
|
|
16131
|
+
}
|
|
16132
|
+
if (config2.storage.type === "local" && config2.storage.s3) {
|
|
16133
|
+
warnings.push("storage.s3 is configured but ignored while storage.type is local.");
|
|
16134
|
+
}
|
|
16135
|
+
if (config2.sources.preferred_ref !== "open-files") {
|
|
16136
|
+
warnings.push("sources.preferred_ref should stay open-files for durable company knowledge.");
|
|
16137
|
+
}
|
|
16138
|
+
if (!config2.sources.allowed_schemes.includes("open-files")) {
|
|
16139
|
+
errors3.push("sources.allowed_schemes must include open-files.");
|
|
16140
|
+
}
|
|
16141
|
+
return {
|
|
16142
|
+
ok: errors3.length === 0,
|
|
16143
|
+
errors: errors3,
|
|
16144
|
+
warnings
|
|
16145
|
+
};
|
|
16146
|
+
}
|
|
16147
|
+
function recordStorageObjects(db, objects, now = new Date) {
|
|
16148
|
+
const timestamp = now.toISOString();
|
|
16149
|
+
const statement = db.prepare(`
|
|
16150
|
+
INSERT INTO storage_objects (
|
|
16151
|
+
id, artifact_uri, kind, content_type, hash, size_bytes, metadata_json, created_at, updated_at
|
|
16152
|
+
)
|
|
16153
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
16154
|
+
ON CONFLICT(artifact_uri) DO UPDATE SET
|
|
16155
|
+
kind = excluded.kind,
|
|
16156
|
+
content_type = excluded.content_type,
|
|
16157
|
+
hash = excluded.hash,
|
|
16158
|
+
size_bytes = excluded.size_bytes,
|
|
16159
|
+
metadata_json = excluded.metadata_json,
|
|
16160
|
+
updated_at = excluded.updated_at
|
|
16161
|
+
`);
|
|
16162
|
+
const insert = db.transaction((entries) => {
|
|
16163
|
+
for (const entry of entries) {
|
|
16164
|
+
statement.run(randomUUID4(), entry.uri, entry.kind, entry.content_type ?? null, entry.hash ?? null, entry.size_bytes ?? null, JSON.stringify({
|
|
16165
|
+
key: entry.key,
|
|
16166
|
+
...entry.metadata ?? {}
|
|
16167
|
+
}), timestamp, timestamp);
|
|
16168
|
+
}
|
|
16169
|
+
});
|
|
16170
|
+
insert(objects);
|
|
16171
|
+
}
|
|
16172
|
+
|
|
15925
16173
|
// src/wiki-layout.ts
|
|
16174
|
+
import { createHash as createHash6 } from "crypto";
|
|
15926
16175
|
function todayParts(now) {
|
|
15927
16176
|
const year = String(now.getUTCFullYear());
|
|
15928
16177
|
const month = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
15929
16178
|
const day = String(now.getUTCDate()).padStart(2, "0");
|
|
15930
16179
|
return { year, month, day };
|
|
15931
16180
|
}
|
|
16181
|
+
function stableId3(prefix, value) {
|
|
16182
|
+
return `${prefix}_${createHash6("sha256").update(value).digest("hex").slice(0, 20)}`;
|
|
16183
|
+
}
|
|
15932
16184
|
function agentSchemaTemplate() {
|
|
15933
16185
|
return `# Knowledge Agent Schema v1
|
|
15934
16186
|
|
|
@@ -15996,22 +16248,99 @@ async function initializeWikiLayout(store, now = new Date) {
|
|
|
15996
16248
|
root_index_key: rootIndexKey,
|
|
15997
16249
|
wiki_readme_key: wikiReadmeKey
|
|
15998
16250
|
};
|
|
15999
|
-
const
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
`, content_type: "application/x-ndjson" }
|
|
16251
|
+
const entries = [
|
|
16252
|
+
{ key: schemaKey, body: agentSchemaTemplate(), content_type: "text/markdown" },
|
|
16253
|
+
{ key: rootIndexKey, body: rootIndexTemplate(), content_type: "text/markdown" },
|
|
16254
|
+
{ key: wikiReadmeKey, body: wikiReadmeTemplate(), content_type: "text/markdown" },
|
|
16255
|
+
{ key: logKey, body: `${JSON.stringify(event)}
|
|
16256
|
+
`, content_type: "application/x-ndjson" }
|
|
16005
16257
|
];
|
|
16006
|
-
await Promise.all(
|
|
16258
|
+
const artifacts = await Promise.all(entries.map(async (entry) => {
|
|
16259
|
+
const result = await store.put(entry);
|
|
16260
|
+
return {
|
|
16261
|
+
key: result.key,
|
|
16262
|
+
uri: result.uri,
|
|
16263
|
+
kind: artifactKindForKey(entry.key),
|
|
16264
|
+
content_type: entry.content_type,
|
|
16265
|
+
metadata: {
|
|
16266
|
+
provenance: generatedArtifactProvenance({
|
|
16267
|
+
generated_from: "wiki_layout_init",
|
|
16268
|
+
artifact_key: entry.key,
|
|
16269
|
+
citation_required: entry.key.startsWith("wiki/") || entry.key.startsWith("indexes/")
|
|
16270
|
+
})
|
|
16271
|
+
},
|
|
16272
|
+
...hashArtifactBody(entry.body)
|
|
16273
|
+
};
|
|
16274
|
+
}));
|
|
16007
16275
|
return {
|
|
16008
16276
|
schema_key: schemaKey,
|
|
16009
16277
|
root_index_key: rootIndexKey,
|
|
16010
16278
|
wiki_readme_key: wikiReadmeKey,
|
|
16011
16279
|
log_key: logKey,
|
|
16280
|
+
artifacts,
|
|
16012
16281
|
written: [schemaKey, rootIndexKey, wikiReadmeKey, logKey]
|
|
16013
16282
|
};
|
|
16014
16283
|
}
|
|
16284
|
+
function provenanceFor(artifact) {
|
|
16285
|
+
const existing = artifact.metadata?.provenance;
|
|
16286
|
+
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
16287
|
+
return existing;
|
|
16288
|
+
}
|
|
16289
|
+
return generatedArtifactProvenance({
|
|
16290
|
+
generated_from: "wiki_layout_init",
|
|
16291
|
+
artifact_key: artifact.key
|
|
16292
|
+
});
|
|
16293
|
+
}
|
|
16294
|
+
function recordWikiLayoutCatalog(db, artifacts, now = new Date) {
|
|
16295
|
+
const timestamp = now.toISOString();
|
|
16296
|
+
const rootIndex = artifacts.find((artifact) => artifact.key.endsWith("indexes/root.md"));
|
|
16297
|
+
const wikiReadme = artifacts.find((artifact) => artifact.key.endsWith("wiki/README.md"));
|
|
16298
|
+
if (rootIndex) {
|
|
16299
|
+
db.run(`INSERT INTO knowledge_indexes (id, kind, name, artifact_uri, shard_key, metadata_json, created_at, updated_at)
|
|
16300
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
16301
|
+
ON CONFLICT(kind, name, shard_key) DO UPDATE SET
|
|
16302
|
+
artifact_uri = excluded.artifact_uri,
|
|
16303
|
+
metadata_json = excluded.metadata_json,
|
|
16304
|
+
updated_at = excluded.updated_at`, [
|
|
16305
|
+
stableId3("idx", "root:indexes/root.md"),
|
|
16306
|
+
"root",
|
|
16307
|
+
"root",
|
|
16308
|
+
rootIndex.uri,
|
|
16309
|
+
"root",
|
|
16310
|
+
JSON.stringify({
|
|
16311
|
+
artifact_key: rootIndex.key,
|
|
16312
|
+
content_hash: rootIndex.hash ?? null,
|
|
16313
|
+
provenance: provenanceFor(rootIndex)
|
|
16314
|
+
}),
|
|
16315
|
+
timestamp,
|
|
16316
|
+
timestamp
|
|
16317
|
+
]);
|
|
16318
|
+
}
|
|
16319
|
+
if (wikiReadme) {
|
|
16320
|
+
db.run(`INSERT INTO wiki_pages (id, path, title, artifact_uri, content_hash, status, metadata_json, created_at, updated_at)
|
|
16321
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
16322
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
16323
|
+
title = excluded.title,
|
|
16324
|
+
artifact_uri = excluded.artifact_uri,
|
|
16325
|
+
content_hash = excluded.content_hash,
|
|
16326
|
+
status = excluded.status,
|
|
16327
|
+
metadata_json = excluded.metadata_json,
|
|
16328
|
+
updated_at = excluded.updated_at`, [
|
|
16329
|
+
stableId3("wiki", "wiki/README.md"),
|
|
16330
|
+
"wiki/README.md",
|
|
16331
|
+
"Wiki",
|
|
16332
|
+
wikiReadme.uri,
|
|
16333
|
+
wikiReadme.hash ?? null,
|
|
16334
|
+
"active",
|
|
16335
|
+
JSON.stringify({
|
|
16336
|
+
artifact_key: wikiReadme.key,
|
|
16337
|
+
provenance: provenanceFor(wikiReadme)
|
|
16338
|
+
}),
|
|
16339
|
+
timestamp,
|
|
16340
|
+
timestamp
|
|
16341
|
+
]);
|
|
16342
|
+
}
|
|
16343
|
+
}
|
|
16015
16344
|
|
|
16016
16345
|
// src/service.ts
|
|
16017
16346
|
class KnowledgeService {
|
|
@@ -16048,6 +16377,12 @@ class KnowledgeService {
|
|
|
16048
16377
|
artifactStore() {
|
|
16049
16378
|
return createArtifactStore(this.config(), this.ensureWorkspace());
|
|
16050
16379
|
}
|
|
16380
|
+
storageContract() {
|
|
16381
|
+
return resolveStorageContract(this.config(), this.ensureWorkspace(), this.scope);
|
|
16382
|
+
}
|
|
16383
|
+
validateStorage() {
|
|
16384
|
+
return validateStorageConfig(this.config(), this.ensureWorkspace());
|
|
16385
|
+
}
|
|
16051
16386
|
paths() {
|
|
16052
16387
|
const workspace = this.ensureWorkspace();
|
|
16053
16388
|
return {
|
|
@@ -16076,7 +16411,17 @@ class KnowledgeService {
|
|
|
16076
16411
|
return getKnowledgeDbStats(workspace.knowledgeDbPath);
|
|
16077
16412
|
}
|
|
16078
16413
|
async initWiki() {
|
|
16079
|
-
|
|
16414
|
+
const workspace = this.ensureWorkspace();
|
|
16415
|
+
migrateKnowledgeDb(workspace.knowledgeDbPath);
|
|
16416
|
+
const result = await initializeWikiLayout(this.artifactStore());
|
|
16417
|
+
const db = openKnowledgeDb(workspace.knowledgeDbPath);
|
|
16418
|
+
try {
|
|
16419
|
+
recordStorageObjects(db, result.artifacts);
|
|
16420
|
+
recordWikiLayoutCatalog(db, result.artifacts);
|
|
16421
|
+
} finally {
|
|
16422
|
+
db.close();
|
|
16423
|
+
}
|
|
16424
|
+
return result;
|
|
16080
16425
|
}
|
|
16081
16426
|
async ingestManifest(input) {
|
|
16082
16427
|
const workspace = this.ensureWorkspace();
|
|
@@ -16187,6 +16532,17 @@ function buildServer() {
|
|
|
16187
16532
|
}, async ({ scope }) => {
|
|
16188
16533
|
return jsonText(createKnowledgeService({ scope }).paths());
|
|
16189
16534
|
});
|
|
16535
|
+
registerTool(server, "ok_storage_status", "Knowledge storage status", "Inspect local/S3 artifact storage, source ownership, and scalability contract", {
|
|
16536
|
+
scope: scopeField
|
|
16537
|
+
}, async ({ scope }) => {
|
|
16538
|
+
const service = createKnowledgeService({ scope });
|
|
16539
|
+
const validation = service.validateStorage();
|
|
16540
|
+
return jsonText({
|
|
16541
|
+
ok: validation.ok,
|
|
16542
|
+
...service.storageContract(),
|
|
16543
|
+
validation
|
|
16544
|
+
});
|
|
16545
|
+
});
|
|
16190
16546
|
registerTool(server, "ok_parse_source_ref", "Parse source reference", "Parse and validate an open-files, S3, file, or web source ref", {
|
|
16191
16547
|
uri: exports_external.string().describe("Source reference URI")
|
|
16192
16548
|
}, async ({ uri }) => {
|