@hasna/knowledge 0.2.21 → 0.2.22

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.
@@ -207,6 +207,14 @@ The database catalog tracks every schema, index shard, log partition, wiki page,
207
207
  source citation, and generated artifact. Markdown remains the readable layer;
208
208
  SQLite/Postgres and object storage carry the scalable catalog.
209
209
 
210
+ The first compile/write loop is local and approval-gated. `wiki compile`
211
+ generates cited pages from derived source chunks, creates concept backlinks,
212
+ updates index rows, records storage objects, and appends dated JSONL logs.
213
+ `wiki file-answer` writes answer notes only with `--approve-write`; otherwise it
214
+ returns the dry-run proposal. `wiki lint` checks missing/stale citations,
215
+ duplicates, orphan pages, unresolved source refs, contradiction markers, and
216
+ new-article candidates.
217
+
210
218
  ## Search Model
211
219
 
212
220
  Search is hybrid:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/knowledge",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -209,7 +209,8 @@ Commands:
209
209
  remote contracts|status Inspect hosted client contracts/readiness
210
210
  storage status|validate Inspect local/S3 artifact storage contract
211
211
  db init|stats Initialize or inspect local knowledge.db
212
- wiki init Initialize scalable wiki/schema/index/log artifacts
212
+ wiki init|compile|file-answer|lint
213
+ Initialize, compile, file, or lint wiki artifacts
213
214
  source resolve <source-ref> Resolve read-only source content and citation evidence
214
215
  ingest manifest <file|s3://> Ingest an open-files manifest into knowledge.db
215
216
  ingest source <source-ref> Ingest a read-only source ref into knowledge.db
@@ -303,7 +304,7 @@ function printCommandHelp(command: string): void {
303
304
  if (command === 'remote') { console.log('Usage: open-knowledge remote contracts|status [--scope local|global|project] [--json]'); return; }
304
305
  if (command === 'storage') { console.log('Usage: open-knowledge storage status|validate [--scope local|global|project] [--json]'); return; }
305
306
  if (command === 'db') { console.log('Usage: open-knowledge db init|stats [--scope local|global|project] [--json]'); return; }
306
- if (command === 'wiki') { console.log('Usage: open-knowledge wiki init [--scope local|global|project] [--json]'); return; }
307
+ if (command === 'wiki') { console.log('Usage: open-knowledge wiki init|compile|file-answer|lint [query|prompt] [--title <title>] [--content <answer>] [--approve-write] [--limit <n>] [--scope local|global|project] [--json]'); return; }
307
308
  if (command === 'source') { console.log('Usage: open-knowledge source resolve <source-ref> [--purpose knowledge_answer|knowledge_index] [--limit <n>] [--scope local|global|project] [--json]'); return; }
308
309
  if (command === 'ingest') { console.log('Usage: open-knowledge ingest manifest <file|s3://bucket/key> | source <source-ref> [--purpose knowledge_index] [--scope local|global|project] [--json]'); return; }
309
310
  if (command === 'reindex') { console.log('Usage: open-knowledge reindex status|enqueue|embeddings|outbox [file|s3://bucket/key] [--full] [--fake] [--scope local|global|project] [--json]'); return; }
@@ -512,10 +513,47 @@ async function run(argv: string[]): Promise<void> {
512
513
 
513
514
  if (command === 'wiki') {
514
515
  const action = positional[1] ?? 'init';
515
- if (action !== 'init') throw new Error("Invalid wiki action. Use 'init'.");
516
- const result = await service.initWiki();
517
- output({ ok: true, ...result, message: `Initialized wiki layout in ${service.workspace.home}` }, flags.json);
518
- return;
516
+ if (action === 'init') {
517
+ const result = await service.initWiki();
518
+ output({ ok: true, ...result, message: `Initialized wiki layout in ${service.workspace.home}` }, flags.json);
519
+ return;
520
+ }
521
+ if (action === 'compile') {
522
+ const args = positional.slice(2);
523
+ const sourceRefs = args.filter((arg) => /^(open-files|file|s3|https?):\/\//.test(arg));
524
+ const query = args.filter((arg) => !/^(open-files|file|s3|https?):\/\//.test(arg)).join(' ');
525
+ const result = await service.compileWiki({
526
+ title: flags.title,
527
+ query: query || flags.search,
528
+ sourceRefs: sourceRefs.length > 0 ? sourceRefs : undefined,
529
+ limit: flags.limit,
530
+ });
531
+ output({ ok: true, ...result, message: `Compiled wiki page ${result.path}` }, flags.json);
532
+ return;
533
+ }
534
+ if (action === 'file-answer' || action === 'answer') {
535
+ const prompt = positional.slice(2).join(' ');
536
+ if (!prompt) throw new Error('Usage: open-knowledge wiki file-answer <prompt> --content <answer> --approve-write');
537
+ if (!flags.content) throw new Error('Missing --content <answer> for wiki file-answer.');
538
+ const result = await service.fileAnswer({
539
+ prompt,
540
+ answer: flags.content,
541
+ approveWrite: flags.approveWrite,
542
+ limit: flags.limit,
543
+ semantic: flags.semantic,
544
+ modelRef: flags.model,
545
+ dimensions: flags.dimensions,
546
+ fake: flags.fake,
547
+ });
548
+ output({ ok: true, ...result }, flags.json);
549
+ return;
550
+ }
551
+ if (action === 'lint') {
552
+ const result = service.lintWiki();
553
+ output({ ok: result.ok, ...result, message: result.ok ? 'Wiki lint passed' : `Wiki lint found ${result.issue_count} issue(s)` }, flags.json);
554
+ return;
555
+ }
556
+ throw new Error("Invalid wiki action. Use 'init', 'compile', 'file-answer', or 'lint'.");
519
557
  }
520
558
 
521
559
  if (command === 'safety') {
package/src/service.ts CHANGED
@@ -26,6 +26,7 @@ import { retrieveKnowledgeContext, type RetrievalOptions } from './retrieval';
26
26
  import { hybridSearch, type HybridSearchOptions } from './search';
27
27
  import { resolveSafetyPolicy } from './safety';
28
28
  import { runProviderWebSearch, type WebSearchOptions } from './web-search';
29
+ import { compileWikiPage, fileAnswerToWiki, lintWiki, type WikiCompileOptions } from './wiki-compiler';
29
30
  import {
30
31
  recordStorageObjects,
31
32
  resolveStorageContract,
@@ -245,6 +246,49 @@ export class KnowledgeService {
245
246
  return result;
246
247
  }
247
248
 
249
+ async compileWiki(options: Omit<WikiCompileOptions, 'dbPath' | 'store'> = {}) {
250
+ const workspace = this.ensureWorkspace();
251
+ return compileWikiPage({
252
+ ...options,
253
+ dbPath: workspace.knowledgeDbPath,
254
+ store: this.artifactStore(),
255
+ });
256
+ }
257
+
258
+ async fileAnswer(options: {
259
+ prompt: string;
260
+ answer: string;
261
+ approveWrite?: boolean;
262
+ limit?: number;
263
+ semantic?: boolean;
264
+ modelRef?: string;
265
+ dimensions?: number;
266
+ fake?: boolean;
267
+ }) {
268
+ const workspace = this.ensureWorkspace();
269
+ const context = await this.retrieveContext({
270
+ query: options.prompt,
271
+ limit: options.limit,
272
+ semantic: options.semantic,
273
+ modelRef: options.modelRef,
274
+ dimensions: options.dimensions,
275
+ fake: options.fake,
276
+ });
277
+ return fileAnswerToWiki({
278
+ dbPath: workspace.knowledgeDbPath,
279
+ store: this.artifactStore(),
280
+ prompt: options.prompt,
281
+ answer: options.answer,
282
+ context,
283
+ approveWrite: options.approveWrite,
284
+ });
285
+ }
286
+
287
+ lintWiki() {
288
+ const workspace = this.ensureWorkspace();
289
+ return lintWiki({ dbPath: workspace.knowledgeDbPath });
290
+ }
291
+
248
292
  async ingestManifest(input: string) {
249
293
  const workspace = this.ensureWorkspace();
250
294
  return ingestOpenFilesManifest({