@deployxai/dxc 0.1.2 → 0.1.4
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 +10 -127
- package/dist/chunks/{chunk-UO2FNP6N.js → chunk-GCGIIKQJ.js} +1 -1
- package/dist/chunks/chunk-JGUPNE36.js +2077 -0
- package/dist/chunks/{knowledge-4ZXXUSDQ.js → knowledge-6LOGJETT.js} +902 -22
- package/dist/chunks/{monitor-63LXCSZA.js → monitor-CYJPUX5V.js} +2 -2
- package/dist/index.js +575 -63
- package/package.json +3 -3
- package/skills/dxc-article-outline/references/outline-methods.md +20 -0
- package/skills/dxc-article-write/SKILL.md +34 -2
- package/skills/dxc-article-write/references/writing-methods.md +20 -0
- package/skills/dxc-content-brief/references/brief-method.md +18 -0
- package/skills/dxc-content-review/SKILL.md +2 -0
- package/skills/dxc-content-review/references/review-checklist.md +17 -0
- package/skills/dxc-content-workflow/SKILL.md +42 -8
- package/skills/dxc-content-workflow/references/onboarding-questions.md +67 -41
- package/skills/dxc-content-workflow/references/stage-contract.md +4 -1
- package/skills/dxc-knowledge/SKILL.md +30 -0
- package/skills/dxc-knowledge/agents/openai.yaml +6 -0
- package/skills/dxc-memory/SKILL.md +28 -0
- package/skills/dxc-memory/agents/openai.yaml +6 -0
- package/skills/dxc-profile/SKILL.md +32 -0
- package/skills/dxc-profile/agents/openai.yaml +6 -0
- package/skills/dxc-quote-curator/SKILL.md +35 -0
- package/skills/dxc-quote-curator/agents/openai.yaml +6 -0
- package/skills/dxc-research/references/research-method.md +21 -0
- package/skills/dxc-title-write/SKILL.md +6 -1
- package/skills/dxc-title-write/references/title-methods.md +15 -0
- package/skills/dxc-visual-plan/SKILL.md +24 -4
- package/skills/dxc-visual-plan/references/visual-methods.md +18 -0
- package/skills/dxc-wechat-publisher/SKILL.md +1 -0
- package/dist/chunks/chunk-ZPBFGBWP.js +0 -16422
- package/docs/00-project-context.md +0 -125
- package/docs/01-north-star-architecture.md +0 -234
- package/docs/02-mvp-technical-design.md +0 -553
- package/docs/03-domain-state-api.md +0 -600
- package/docs/04-security-and-operations.md +0 -413
- package/docs/05-delivery-plan.md +0 -407
- package/docs/README.md +0 -44
- package/docs/decisions/0001-initial-architecture.md +0 -57
- package/docs/decisions/0002-mongodb-environment-boundary.md +0 -42
- package/docs/decisions/0003-staged-production-topology.md +0 -33
- package/docs/decisions/0004-local-first-agent-research-runtime.md +0 -71
- package/docs/decisions/0005-official-skill-orchestration-and-local-content-memory.md +0 -97
- package/docs/decisions/0006-separate-wechat-user-login-from-account-authorization.md +0 -87
- package/docs/decisions/0007-explicit-personal-wechat-start.md +0 -67
- package/docs/decisions/0008-end-to-end-content-workflow-continuity.md +0 -115
- package/docs/decisions/0009-privileged-multitenant-draft-scheduling.md +0 -36
- package/docs/decisions/0009-versioned-cloud-template-catalog.md +0 -39
- package/docs/decisions/0010-cli-runtime-and-default-server.md +0 -31
- package/docs/decisions/0011-user-memory-and-explicit-history-library.md +0 -30
- package/docs/eight-stage-implementation-audit.md +0 -62
- package/docs/first-user-guide.md +0 -191
- package/docs/history/content-forge-prd-v0.2-summary.md +0 -81
- package/docs/local-development.md +0 -520
- package/docs/references/aliyun-oss-production-setup.md +0 -89
- package/docs/references/legacy-content-to-wechat-contract.md +0 -223
- package/docs/references/renderer-compatibility-report.md +0 -68
- package/docs/references/source-inventory.md +0 -179
- package/docs/references/wechat-renderer-platform-validation.md +0 -92
- package/docs/references/wechat-third-party-platform-setup.md +0 -159
- package/docs/references/wechat-website-login-setup.md +0 -137
- package/docs/references/wemd-template-attribution.md +0 -25
- package/docs/research-monitoring-design.md +0 -235
- package/docs/todo-preview-local-first.md +0 -31
- package/docs/workbuddy-first-user-runbook.md +0 -244
- package/docs//345/221/230/345/267/245BCDE/347/232/204skill/employee-b-research-analyst/SKILL.md +0 -230
- package/docs//345/221/230/345/267/245BCDE/347/232/204skill/employee-c-outline-architect/SKILL.md +0 -194
- package/docs//345/221/230/345/267/245BCDE/347/232/204skill/employee-d-content-writer/SKILL.md +0 -296
- package/docs//345/221/230/345/267/245BCDE/347/232/204skill/employee-e-visual-designer/SKILL.md +0 -268
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
LocalStateError,
|
|
10
10
|
contentKnowledgeImportResultSchema,
|
|
11
11
|
contentKnowledgeModelSchema,
|
|
12
|
+
contentKnowledgeQuoteInitResultSchema,
|
|
13
|
+
contentKnowledgeQuoteListResultSchema,
|
|
14
|
+
contentKnowledgeQuoteMutationResultSchema,
|
|
15
|
+
contentKnowledgeQuoteStatusResultSchema,
|
|
16
|
+
contentKnowledgeQuoteSyncResultSchema,
|
|
12
17
|
contentKnowledgeRemoveResultSchema,
|
|
13
18
|
contentKnowledgeSearchModeSchema,
|
|
14
19
|
contentKnowledgeSearchResultSchema,
|
|
@@ -19,13 +24,14 @@ import {
|
|
|
19
24
|
readPrivateFile,
|
|
20
25
|
writePrivateBuffer,
|
|
21
26
|
writePrivateJson
|
|
22
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-JGUPNE36.js";
|
|
23
28
|
|
|
24
29
|
// apps/cli/src/knowledge.ts
|
|
25
30
|
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
26
31
|
import { lstat, readdir, readFile, stat } from "node:fs/promises";
|
|
27
32
|
import path2 from "node:path";
|
|
28
33
|
import { DatabaseSync } from "node:sqlite";
|
|
34
|
+
import { z } from "zod";
|
|
29
35
|
|
|
30
36
|
// apps/cli/src/knowledge-embedding.ts
|
|
31
37
|
import { createHash } from "node:crypto";
|
|
@@ -326,9 +332,14 @@ var LocalBgeKnowledgeEmbedder = class {
|
|
|
326
332
|
|
|
327
333
|
// apps/cli/src/knowledge.ts
|
|
328
334
|
var DATABASE_DISPLAY_PATH = "~/.dxc/content-memory.sqlite";
|
|
335
|
+
var QUOTE_SOURCE_DISPLAY_PATH = "~/Documents/DxC/\u91D1\u53E5\u5E93.md";
|
|
336
|
+
var QUOTE_SOURCE_FILE_NAME = "\u91D1\u53E5\u5E93.md";
|
|
337
|
+
var LEGACY_QUOTE_SOURCE_FILE_NAME = "quotes.json";
|
|
329
338
|
var MAXIMUM_ARTICLE_BYTES = 5 * 1024 * 1024;
|
|
330
339
|
var MAXIMUM_CHUNK_CHARACTERS = 420;
|
|
331
340
|
var TARGET_CHUNK_CHARACTERS = 280;
|
|
341
|
+
var MINIMUM_CONTEXTUAL_CHUNK_CHARACTERS = 80;
|
|
342
|
+
var MINIMUM_INDEXABLE_ARTICLE_CHARACTERS = 20;
|
|
332
343
|
var MAXIMUM_RESULTS = 20;
|
|
333
344
|
var SEARCH_CANDIDATE_LIMIT = 80;
|
|
334
345
|
var RRF_CONSTANT = 60;
|
|
@@ -346,9 +357,71 @@ var KnowledgeCliError = class extends Error {
|
|
|
346
357
|
}
|
|
347
358
|
code;
|
|
348
359
|
};
|
|
360
|
+
var quoteMetadataValueSchema = z.string().trim().min(1).max(300).refine((value) => !/[\r\n]/u.test(value) && value !== "\uFF08\u65E0\uFF09", {
|
|
361
|
+
message: "quote metadata must stay on one line"
|
|
362
|
+
});
|
|
363
|
+
var quoteTagSchema = quoteMetadataValueSchema.max(80).refine((value) => !/[,,]/u.test(value), {
|
|
364
|
+
message: "quote tags must not contain separators"
|
|
365
|
+
});
|
|
366
|
+
var quoteSourceItemSchema = z.object({
|
|
367
|
+
attribution: quoteMetadataValueSchema.nullable().optional(),
|
|
368
|
+
origin: z.enum(["original", "third-party"]),
|
|
369
|
+
quoteId: z.uuid(),
|
|
370
|
+
source: quoteMetadataValueSchema.nullable().optional(),
|
|
371
|
+
status: z.enum(["active", "archived"]),
|
|
372
|
+
tags: z.array(quoteTagSchema).max(20).default([]),
|
|
373
|
+
text: z.string().trim().min(1).max(2e3),
|
|
374
|
+
verbatimUse: z.enum(["original", "needs-verification", "permitted", "do-not-use-verbatim"])
|
|
375
|
+
}).strict().superRefine((quote, context) => {
|
|
376
|
+
if (quote.origin === "third-party" && quote.attribution == null) {
|
|
377
|
+
context.addIssue({
|
|
378
|
+
code: "custom",
|
|
379
|
+
message: "Third-party quotes require attribution",
|
|
380
|
+
path: ["attribution"]
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (quote.origin === "original" && quote.verbatimUse !== "original") {
|
|
384
|
+
context.addIssue({
|
|
385
|
+
code: "custom",
|
|
386
|
+
message: "Original quotes must use the original verbatim-use value",
|
|
387
|
+
path: ["verbatimUse"]
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
var quoteSourceDocumentSchema = z.object({
|
|
392
|
+
quotes: z.array(quoteSourceItemSchema).max(5e3),
|
|
393
|
+
schemaVersion: z.literal("1")
|
|
394
|
+
}).strict().superRefine((document, context) => {
|
|
395
|
+
const ids = /* @__PURE__ */ new Set();
|
|
396
|
+
for (const [index, quote] of document.quotes.entries()) {
|
|
397
|
+
if (ids.has(quote.quoteId)) {
|
|
398
|
+
context.addIssue({
|
|
399
|
+
code: "custom",
|
|
400
|
+
message: "quoteId values must be unique",
|
|
401
|
+
path: ["quotes", index, "quoteId"]
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
ids.add(quote.quoteId);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
349
407
|
function errorCode(error) {
|
|
350
408
|
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
351
409
|
}
|
|
410
|
+
function skippedArticle(error, inputPath) {
|
|
411
|
+
if (!(error instanceof KnowledgeCliError)) {
|
|
412
|
+
return void 0;
|
|
413
|
+
}
|
|
414
|
+
const code = error.code;
|
|
415
|
+
if (code !== "DXC_KNOWLEDGE_ARTICLE_INVALID" && code !== "DXC_KNOWLEDGE_FILE_TOO_LARGE") {
|
|
416
|
+
return void 0;
|
|
417
|
+
}
|
|
418
|
+
const sourceLabel = path2.basename(inputPath).slice(0, 255).trim();
|
|
419
|
+
return {
|
|
420
|
+
code,
|
|
421
|
+
message: error.message,
|
|
422
|
+
sourceLabel: sourceLabel.length === 0 ? "(unknown)" : sourceLabel
|
|
423
|
+
};
|
|
424
|
+
}
|
|
352
425
|
function sha2562(value) {
|
|
353
426
|
return createHash2("sha256").update(value).digest("hex");
|
|
354
427
|
}
|
|
@@ -404,10 +477,29 @@ function splitOversizedText(value) {
|
|
|
404
477
|
flush();
|
|
405
478
|
return parts;
|
|
406
479
|
}
|
|
480
|
+
function characterCount(value) {
|
|
481
|
+
return [...value].length;
|
|
482
|
+
}
|
|
483
|
+
function combinedHeading(left, right) {
|
|
484
|
+
const headings = [left, right].filter((heading) => heading !== null);
|
|
485
|
+
if (headings.length === 0) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
return [...new Set(headings)].join(" / ").slice(0, 300);
|
|
489
|
+
}
|
|
490
|
+
function combineChunks(left, right) {
|
|
491
|
+
return {
|
|
492
|
+
body: `${left.body}
|
|
493
|
+
|
|
494
|
+
${right.body}`,
|
|
495
|
+
heading: combinedHeading(left.heading, right.heading)
|
|
496
|
+
};
|
|
497
|
+
}
|
|
407
498
|
function chunkHistoricalArticle(markdown) {
|
|
408
499
|
const value = removeFrontmatter(markdown);
|
|
409
500
|
const lines = value.split("\n");
|
|
410
501
|
const paragraphs = [];
|
|
502
|
+
let fencedCodeDelimiter = null;
|
|
411
503
|
let heading = null;
|
|
412
504
|
let paragraphLines = [];
|
|
413
505
|
const flushParagraph = () => {
|
|
@@ -421,8 +513,22 @@ function chunkHistoricalArticle(markdown) {
|
|
|
421
513
|
}
|
|
422
514
|
};
|
|
423
515
|
for (const line of lines) {
|
|
516
|
+
const fenceMarker = line.match(/^\s*(`{3,}|~{3,})/u)?.[1];
|
|
517
|
+
if (fenceMarker !== void 0) {
|
|
518
|
+
const delimiter = fenceMarker.startsWith("`") ? "`" : "~";
|
|
519
|
+
if (fencedCodeDelimiter === null) {
|
|
520
|
+
fencedCodeDelimiter = delimiter;
|
|
521
|
+
paragraphLines.push(line);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (fencedCodeDelimiter === delimiter) {
|
|
525
|
+
fencedCodeDelimiter = null;
|
|
526
|
+
paragraphLines.push(line);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
424
530
|
const headingMatch = line.match(/^\s{0,3}#{1,6}\s+(.+)$/u);
|
|
425
|
-
if (headingMatch !== null) {
|
|
531
|
+
if (fencedCodeDelimiter === null && headingMatch !== null) {
|
|
426
532
|
flushParagraph();
|
|
427
533
|
heading = plainMarkdown(headingMatch[1] ?? "").slice(0, 300).trim() || null;
|
|
428
534
|
continue;
|
|
@@ -452,7 +558,31 @@ ${paragraph.text}`;
|
|
|
452
558
|
});
|
|
453
559
|
}
|
|
454
560
|
}
|
|
455
|
-
|
|
561
|
+
const contextualized = [];
|
|
562
|
+
let pending;
|
|
563
|
+
for (const chunk of merged) {
|
|
564
|
+
if (pending === void 0) {
|
|
565
|
+
pending = chunk;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const combined = combineChunks(pending, chunk);
|
|
569
|
+
if (characterCount(pending.body) < MINIMUM_CONTEXTUAL_CHUNK_CHARACTERS && characterCount(combined.body) <= MAXIMUM_CHUNK_CHARACTERS) {
|
|
570
|
+
pending = combined;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
contextualized.push(pending);
|
|
574
|
+
pending = chunk;
|
|
575
|
+
}
|
|
576
|
+
if (pending !== void 0) {
|
|
577
|
+
const previous = contextualized.at(-1);
|
|
578
|
+
const combined = previous === void 0 ? void 0 : combineChunks(previous, pending);
|
|
579
|
+
if (previous !== void 0 && combined !== void 0 && characterCount(pending.body) < MINIMUM_CONTEXTUAL_CHUNK_CHARACTERS && characterCount(combined.body) <= MAXIMUM_CHUNK_CHARACTERS) {
|
|
580
|
+
contextualized[contextualized.length - 1] = combined;
|
|
581
|
+
} else {
|
|
582
|
+
contextualized.push(pending);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return contextualized.map((chunk, ordinal) => ({
|
|
456
586
|
...chunk,
|
|
457
587
|
ordinal,
|
|
458
588
|
sha256: sha2562(chunk.body)
|
|
@@ -496,10 +626,10 @@ async function prepareArticle(currentDirectory, inputPath) {
|
|
|
496
626
|
}
|
|
497
627
|
const sourceLabel = path2.basename(absolutePath).slice(0, 255);
|
|
498
628
|
const chunks = chunkHistoricalArticle(content);
|
|
499
|
-
if (chunks.length === 0) {
|
|
629
|
+
if (chunks.length === 0 || chunks.reduce((total, chunk) => total + characterCount(chunk.body), 0) < MINIMUM_INDEXABLE_ARTICLE_CHARACTERS) {
|
|
500
630
|
throw new KnowledgeCliError(
|
|
501
631
|
"DXC_KNOWLEDGE_ARTICLE_INVALID",
|
|
502
|
-
"Article has
|
|
632
|
+
"Article has too little indexable content"
|
|
503
633
|
);
|
|
504
634
|
}
|
|
505
635
|
return {
|
|
@@ -649,10 +779,54 @@ function chunkRow(value) {
|
|
|
649
779
|
title: stringColumn(row, "title")
|
|
650
780
|
};
|
|
651
781
|
}
|
|
782
|
+
function quoteRow(value) {
|
|
783
|
+
const row = sqlRow(value);
|
|
784
|
+
const nullableString = (name) => {
|
|
785
|
+
const value2 = row[name];
|
|
786
|
+
if (value2 === null) {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
return stringColumn(row, name);
|
|
790
|
+
};
|
|
791
|
+
const parseValue = (schema, value2) => {
|
|
792
|
+
const parsed = schema.safeParse(value2);
|
|
793
|
+
if (!parsed.success) {
|
|
794
|
+
throw new KnowledgeCliError(
|
|
795
|
+
"DXC_KNOWLEDGE_STATE_INVALID",
|
|
796
|
+
"Knowledge quote state is invalid"
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return parsed.data;
|
|
800
|
+
};
|
|
801
|
+
return {
|
|
802
|
+
attribution: nullableString("attribution"),
|
|
803
|
+
contentSha256: stringColumn(row, "content_sha256"),
|
|
804
|
+
embedding: vectorFromColumn(row["embedding"]),
|
|
805
|
+
id: stringColumn(row, "quote_id"),
|
|
806
|
+
origin: parseValue(z.enum(["original", "third-party"]), row["origin"]),
|
|
807
|
+
sourceLabel: nullableString("source_label"),
|
|
808
|
+
status: parseValue(z.enum(["active", "archived"]), row["status"]),
|
|
809
|
+
tags: (() => {
|
|
810
|
+
try {
|
|
811
|
+
return parseValue(z.array(z.string()), JSON.parse(stringColumn(row, "tags_json")));
|
|
812
|
+
} catch {
|
|
813
|
+
throw new KnowledgeCliError(
|
|
814
|
+
"DXC_KNOWLEDGE_STATE_INVALID",
|
|
815
|
+
"Knowledge quote state is invalid"
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
})(),
|
|
819
|
+
text: stringColumn(row, "body"),
|
|
820
|
+
verbatimUse: parseValue(
|
|
821
|
+
z.enum(["original", "needs-verification", "permitted", "do-not-use-verbatim"]),
|
|
822
|
+
row["verbatim_use"]
|
|
823
|
+
)
|
|
824
|
+
};
|
|
825
|
+
}
|
|
652
826
|
function databaseSchema(database) {
|
|
653
827
|
const versionRow = sqlRow(database.prepare("PRAGMA user_version").get());
|
|
654
828
|
const version = numberColumn(versionRow, "user_version");
|
|
655
|
-
if (version
|
|
829
|
+
if (version < 0 || version > Number(CONTENT_KNOWLEDGE_SCHEMA_VERSION)) {
|
|
656
830
|
throw new KnowledgeCliError(
|
|
657
831
|
"DXC_KNOWLEDGE_SCHEMA_UNSUPPORTED",
|
|
658
832
|
"Knowledge database version is unsupported"
|
|
@@ -723,8 +897,53 @@ function databaseSchema(database) {
|
|
|
723
897
|
tokenize = 'trigram'
|
|
724
898
|
);
|
|
725
899
|
|
|
726
|
-
PRAGMA user_version = 1;
|
|
727
900
|
`);
|
|
901
|
+
if (version < 2) {
|
|
902
|
+
database.exec(`
|
|
903
|
+
CREATE TABLE IF NOT EXISTS quote_sources (
|
|
904
|
+
source_key TEXT PRIMARY KEY,
|
|
905
|
+
content_sha256 TEXT NOT NULL,
|
|
906
|
+
synced_at TEXT NOT NULL
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
CREATE TABLE IF NOT EXISTS quotes (
|
|
910
|
+
quote_id TEXT PRIMARY KEY,
|
|
911
|
+
status TEXT NOT NULL CHECK (status IN ('active', 'archived')),
|
|
912
|
+
body TEXT NOT NULL,
|
|
913
|
+
tags_json TEXT NOT NULL,
|
|
914
|
+
source_label TEXT,
|
|
915
|
+
origin TEXT NOT NULL CHECK (origin IN ('original', 'third-party')),
|
|
916
|
+
attribution TEXT,
|
|
917
|
+
verbatim_use TEXT NOT NULL CHECK (verbatim_use IN ('original', 'needs-verification', 'permitted', 'do-not-use-verbatim')),
|
|
918
|
+
content_sha256 TEXT NOT NULL,
|
|
919
|
+
imported_at TEXT NOT NULL,
|
|
920
|
+
updated_at TEXT NOT NULL,
|
|
921
|
+
embedding BLOB,
|
|
922
|
+
embedding_model TEXT,
|
|
923
|
+
embedding_revision TEXT,
|
|
924
|
+
embedding_quantization TEXT,
|
|
925
|
+
embedding_dimensions INTEGER,
|
|
926
|
+
CHECK (
|
|
927
|
+
(embedding IS NULL AND embedding_model IS NULL AND embedding_revision IS NULL AND embedding_quantization IS NULL AND embedding_dimensions IS NULL)
|
|
928
|
+
OR
|
|
929
|
+
(embedding IS NOT NULL AND embedding_model IS NOT NULL AND embedding_revision IS NOT NULL AND embedding_quantization IS NOT NULL AND embedding_dimensions IS NOT NULL)
|
|
930
|
+
)
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
CREATE INDEX IF NOT EXISTS quotes_status_idx ON quotes(status);
|
|
934
|
+
|
|
935
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS quote_search USING fts5(
|
|
936
|
+
quote_id UNINDEXED,
|
|
937
|
+
body,
|
|
938
|
+
tags,
|
|
939
|
+
source_label,
|
|
940
|
+
attribution,
|
|
941
|
+
tokenize = 'trigram'
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
PRAGMA user_version = 2;
|
|
945
|
+
`);
|
|
946
|
+
}
|
|
728
947
|
}
|
|
729
948
|
async function openDatabase(target) {
|
|
730
949
|
try {
|
|
@@ -761,6 +980,310 @@ async function databaseExists(target) {
|
|
|
761
980
|
throw new KnowledgeCliError("DXC_KNOWLEDGE_IO_FAILED", "Unable to inspect knowledge database");
|
|
762
981
|
}
|
|
763
982
|
}
|
|
983
|
+
function quoteSourcePath(homeDirectory) {
|
|
984
|
+
return path2.join(homeDirectory, "Documents", "DxC", QUOTE_SOURCE_FILE_NAME);
|
|
985
|
+
}
|
|
986
|
+
function legacyQuoteSourcePath(homeDirectory) {
|
|
987
|
+
return path2.join(homeDirectory, ".dxc", LEGACY_QUOTE_SOURCE_FILE_NAME);
|
|
988
|
+
}
|
|
989
|
+
function quoteContentHash(quote) {
|
|
990
|
+
return sha2562(
|
|
991
|
+
JSON.stringify({
|
|
992
|
+
attribution: quote.attribution ?? null,
|
|
993
|
+
origin: quote.origin,
|
|
994
|
+
quoteId: quote.quoteId,
|
|
995
|
+
source: quote.source ?? null,
|
|
996
|
+
status: quote.status,
|
|
997
|
+
tags: [...quote.tags],
|
|
998
|
+
text: quote.text,
|
|
999
|
+
verbatimUse: quote.verbatimUse
|
|
1000
|
+
})
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
function preparedQuotes(document) {
|
|
1004
|
+
return document.quotes.map((quote) => ({ ...quote, contentSha256: quoteContentHash(quote) }));
|
|
1005
|
+
}
|
|
1006
|
+
function sourceQuote(quote) {
|
|
1007
|
+
return {
|
|
1008
|
+
attribution: quote.attribution,
|
|
1009
|
+
origin: quote.origin,
|
|
1010
|
+
quoteId: quote.quoteId,
|
|
1011
|
+
source: quote.source,
|
|
1012
|
+
status: quote.status,
|
|
1013
|
+
tags: quote.tags,
|
|
1014
|
+
text: quote.text,
|
|
1015
|
+
verbatimUse: quote.verbatimUse
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
async function quoteSourceExists(sourcePath) {
|
|
1019
|
+
try {
|
|
1020
|
+
const metadata = await lstat(sourcePath);
|
|
1021
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
1022
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote source file is invalid");
|
|
1023
|
+
}
|
|
1024
|
+
return true;
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
if (error instanceof KnowledgeCliError) {
|
|
1027
|
+
throw error;
|
|
1028
|
+
}
|
|
1029
|
+
if (errorCode(error) === "ENOENT") {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_IO_FAILED", "Unable to inspect quote source file");
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function quoteSourceTemplate(quotes) {
|
|
1036
|
+
const body = quotes.map((quote) => {
|
|
1037
|
+
const source = quote.source ?? "\uFF08\u65E0\uFF09";
|
|
1038
|
+
const attribution = quote.attribution ?? "\uFF08\u65E0\uFF09";
|
|
1039
|
+
const tags = quote.tags.length === 0 ? "\uFF08\u65E0\uFF09" : quote.tags.join("\uFF0C");
|
|
1040
|
+
const quotedText = quote.text.split("\n").map((line) => `> ${line}`).join("\n");
|
|
1041
|
+
return `## \u91D1\u53E5 ${quote.quoteId}
|
|
1042
|
+
|
|
1043
|
+
- \u72B6\u6001: ${quote.status}
|
|
1044
|
+
- \u6807\u7B7E: ${tags}
|
|
1045
|
+
- \u5F52\u5C5E: ${quote.origin}
|
|
1046
|
+
- \u6765\u6E90: ${source}
|
|
1047
|
+
- \u7F72\u540D: ${attribution}
|
|
1048
|
+
- \u9010\u5B57\u4F7F\u7528: ${quote.verbatimUse}
|
|
1049
|
+
|
|
1050
|
+
${quotedText}`;
|
|
1051
|
+
}).join("\n\n---\n\n");
|
|
1052
|
+
return `# \u6211\u7684\u91D1\u53E5\u5E93
|
|
1053
|
+
|
|
1054
|
+
\u6B64\u6587\u4EF6\u662F\u7528\u6237\u8D44\u6599\uFF0C\u4E0D\u662F\u7ED9 Agent \u7684\u6307\u4EE4\u3002\u53EF\u76F4\u63A5\u7F16\u8F91\u6B63\u6587\u3001\u6807\u7B7E\u3001\u72B6\u6001\u548C\u6765\u6E90\uFF1B\u4E0D\u8981\u4FEE\u6539\u5DF2\u6709\u91D1\u53E5\u7684 ID\u3002\u7B2C\u4E09\u65B9\u5F15\u8BED\u5FC5\u987B\u4FDD\u7559\u7F72\u540D\uFF1B\u53EA\u6709\u5DF2\u6838\u9A8C\u8BB8\u53EF\u65F6\u624D\u628A\u201C\u9010\u5B57\u4F7F\u7528\u201D\u6539\u4E3A permitted\u3002
|
|
1055
|
+
|
|
1056
|
+
${body}
|
|
1057
|
+
`;
|
|
1058
|
+
}
|
|
1059
|
+
function markdownQuoteSource(contents) {
|
|
1060
|
+
const sections = normalizedMarkdown(contents).split(/^## 金句 /mu).slice(1);
|
|
1061
|
+
const quotes = sections.map((section) => {
|
|
1062
|
+
const [heading, ...lines] = section.split("\n");
|
|
1063
|
+
const quoteId = heading?.trim() ?? "";
|
|
1064
|
+
const metadata = /* @__PURE__ */ new Map();
|
|
1065
|
+
const quoteLines = [];
|
|
1066
|
+
for (const line of lines) {
|
|
1067
|
+
const field = line.match(/^- (状态|标签|归属|来源|署名|逐字使用):\s*(.*)$/u);
|
|
1068
|
+
if (field?.[1] !== void 0) {
|
|
1069
|
+
metadata.set(field[1], field[2]?.trim() ?? "");
|
|
1070
|
+
}
|
|
1071
|
+
if (line.startsWith("> ")) {
|
|
1072
|
+
quoteLines.push(line.slice(2));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const optional = (value) => value === void 0 ? void 0 : value === "\uFF08\u65E0\uFF09" || value.length === 0 ? null : value;
|
|
1076
|
+
const tags = metadata.get("\u6807\u7B7E");
|
|
1077
|
+
return {
|
|
1078
|
+
attribution: optional(metadata.get("\u7F72\u540D")),
|
|
1079
|
+
origin: metadata.get("\u5F52\u5C5E"),
|
|
1080
|
+
quoteId,
|
|
1081
|
+
source: optional(metadata.get("\u6765\u6E90")),
|
|
1082
|
+
status: metadata.get("\u72B6\u6001"),
|
|
1083
|
+
tags: tags === void 0 || tags === "\uFF08\u65E0\uFF09" || tags.length === 0 ? [] : tags.split(/[,,]/u).map((tag) => tag.trim()).filter(Boolean),
|
|
1084
|
+
text: quoteLines.join("\n").trim(),
|
|
1085
|
+
verbatimUse: metadata.get("\u9010\u5B57\u4F7F\u7528")
|
|
1086
|
+
};
|
|
1087
|
+
});
|
|
1088
|
+
return { quotes, schemaVersion: "1" };
|
|
1089
|
+
}
|
|
1090
|
+
async function readQuoteSource(sourcePath, allowLegacyPermissions = false) {
|
|
1091
|
+
let contents;
|
|
1092
|
+
try {
|
|
1093
|
+
if (allowLegacyPermissions) {
|
|
1094
|
+
const metadata = await lstat(sourcePath);
|
|
1095
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAXIMUM_ARTICLE_BYTES) {
|
|
1096
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote source file is invalid");
|
|
1097
|
+
}
|
|
1098
|
+
contents = await readFile(sourcePath, "utf8");
|
|
1099
|
+
} else {
|
|
1100
|
+
contents = await readPrivateFile(sourcePath, MAXIMUM_ARTICLE_BYTES);
|
|
1101
|
+
}
|
|
1102
|
+
} catch {
|
|
1103
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_IO_FAILED", "Unable to read quote source file");
|
|
1104
|
+
}
|
|
1105
|
+
let parsed;
|
|
1106
|
+
try {
|
|
1107
|
+
parsed = sourcePath.endsWith(".json") ? JSON.parse(contents) : markdownQuoteSource(contents);
|
|
1108
|
+
} catch {
|
|
1109
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote source file is invalid");
|
|
1110
|
+
}
|
|
1111
|
+
const document = quoteSourceDocumentSchema.safeParse(parsed);
|
|
1112
|
+
if (!document.success) {
|
|
1113
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote source file is invalid");
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
contentSha256: sha2562(contents),
|
|
1117
|
+
quotes: preparedQuotes(document.data)
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
async function writeQuoteSource(sourcePath, quotes) {
|
|
1121
|
+
try {
|
|
1122
|
+
await writePrivateBuffer(sourcePath, Buffer.from(quoteSourceTemplate(quotes), "utf8"));
|
|
1123
|
+
} catch {
|
|
1124
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_IO_FAILED", "Unable to write quote source file");
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
function quoteRecord(quote) {
|
|
1128
|
+
return {
|
|
1129
|
+
attribution: quote.attribution ?? null,
|
|
1130
|
+
origin: quote.origin,
|
|
1131
|
+
quoteId: quote.quoteId,
|
|
1132
|
+
source: quote.source ?? null,
|
|
1133
|
+
status: quote.status,
|
|
1134
|
+
tags: [...quote.tags],
|
|
1135
|
+
text: quote.text,
|
|
1136
|
+
verbatimUse: quote.verbatimUse
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
function quoteSourceSummary(database, sourceInitialized) {
|
|
1140
|
+
const counts = sqlRow(
|
|
1141
|
+
database.prepare(
|
|
1142
|
+
`SELECT
|
|
1143
|
+
COALESCE(SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END), 0) AS active_count,
|
|
1144
|
+
COALESCE(SUM(CASE WHEN status = 'archived' THEN 1 ELSE 0 END), 0) AS archived_count
|
|
1145
|
+
FROM quotes`
|
|
1146
|
+
).get()
|
|
1147
|
+
);
|
|
1148
|
+
const source = database.prepare("SELECT content_sha256 FROM quote_sources WHERE source_key = 'default' LIMIT 1").get();
|
|
1149
|
+
return {
|
|
1150
|
+
activeCount: numberColumn(counts, "active_count"),
|
|
1151
|
+
archivedCount: numberColumn(counts, "archived_count"),
|
|
1152
|
+
sourceInitialized,
|
|
1153
|
+
sourcePath: QUOTE_SOURCE_DISPLAY_PATH,
|
|
1154
|
+
sourceSha256: source === void 0 ? null : stringColumn(sqlRow(source), "content_sha256")
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
function quoteEmbeddingText(quote) {
|
|
1158
|
+
return [quote.text, ...quote.tags, quote.source ?? "", quote.attribution ?? ""].filter((value) => value.length > 0).join("\n");
|
|
1159
|
+
}
|
|
1160
|
+
async function embedQuotes(embedder, quotes) {
|
|
1161
|
+
const activeQuotes = quotes.filter((quote) => quote.status === "active");
|
|
1162
|
+
if (activeQuotes.length === 0) {
|
|
1163
|
+
return /* @__PURE__ */ new Map();
|
|
1164
|
+
}
|
|
1165
|
+
let vectors;
|
|
1166
|
+
try {
|
|
1167
|
+
vectors = await embedder.embedDocuments(activeQuotes.map(quoteEmbeddingText));
|
|
1168
|
+
} catch {
|
|
1169
|
+
throw new KnowledgeCliError(
|
|
1170
|
+
"DXC_KNOWLEDGE_MODEL_FAILED",
|
|
1171
|
+
"Local semantic model could not index quotes"
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (vectors.length !== activeQuotes.length || vectors.some((vector) => vector.length !== CONTENT_KNOWLEDGE_EMBEDDING_DIMENSIONS)) {
|
|
1175
|
+
throw new KnowledgeCliError(
|
|
1176
|
+
"DXC_KNOWLEDGE_MODEL_FAILED",
|
|
1177
|
+
"Local semantic model returned invalid quote vectors"
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
return new Map(
|
|
1181
|
+
activeQuotes.map((quote, index) => [quote.quoteId, vectors[index]])
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
function syncQuotes(database, quotes, sourceSha256, vectors, timestamp, allowUnchanged) {
|
|
1185
|
+
const priorSource = database.prepare("SELECT content_sha256 FROM quote_sources WHERE source_key = 'default' LIMIT 1").get();
|
|
1186
|
+
if (allowUnchanged && priorSource !== void 0 && stringColumn(sqlRow(priorSource), "content_sha256") === sourceSha256) {
|
|
1187
|
+
return "unchanged";
|
|
1188
|
+
}
|
|
1189
|
+
database.exec("BEGIN IMMEDIATE");
|
|
1190
|
+
try {
|
|
1191
|
+
const existingRows = database.prepare("SELECT quote_id, content_sha256, imported_at FROM quotes").all().map((value) => sqlRow(value));
|
|
1192
|
+
const existing = new Map(
|
|
1193
|
+
existingRows.map((row) => [
|
|
1194
|
+
stringColumn(row, "quote_id"),
|
|
1195
|
+
{
|
|
1196
|
+
contentSha256: stringColumn(row, "content_sha256"),
|
|
1197
|
+
importedAt: stringColumn(row, "imported_at")
|
|
1198
|
+
}
|
|
1199
|
+
])
|
|
1200
|
+
);
|
|
1201
|
+
const sourceIds = new Set(quotes.map((quote) => quote.quoteId));
|
|
1202
|
+
database.prepare("DELETE FROM quote_search").run();
|
|
1203
|
+
const upsert = database.prepare(`
|
|
1204
|
+
INSERT INTO quotes (
|
|
1205
|
+
quote_id, status, body, tags_json, source_label, origin, attribution, verbatim_use,
|
|
1206
|
+
content_sha256, imported_at, updated_at, embedding, embedding_model, embedding_revision,
|
|
1207
|
+
embedding_quantization, embedding_dimensions
|
|
1208
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1209
|
+
ON CONFLICT(quote_id) DO UPDATE SET
|
|
1210
|
+
status = excluded.status, body = excluded.body, tags_json = excluded.tags_json,
|
|
1211
|
+
source_label = excluded.source_label, origin = excluded.origin, attribution = excluded.attribution,
|
|
1212
|
+
verbatim_use = excluded.verbatim_use, content_sha256 = excluded.content_sha256,
|
|
1213
|
+
updated_at = excluded.updated_at, embedding = excluded.embedding,
|
|
1214
|
+
embedding_model = excluded.embedding_model, embedding_revision = excluded.embedding_revision,
|
|
1215
|
+
embedding_quantization = excluded.embedding_quantization, embedding_dimensions = excluded.embedding_dimensions
|
|
1216
|
+
`);
|
|
1217
|
+
const insertSearch = database.prepare(`
|
|
1218
|
+
INSERT INTO quote_search (quote_id, body, tags, source_label, attribution) VALUES (?, ?, ?, ?, ?)
|
|
1219
|
+
`);
|
|
1220
|
+
for (const quote of quotes) {
|
|
1221
|
+
const vector = quote.status === "active" ? vectors?.get(quote.quoteId) : void 0;
|
|
1222
|
+
upsert.run(
|
|
1223
|
+
quote.quoteId,
|
|
1224
|
+
quote.status,
|
|
1225
|
+
quote.text,
|
|
1226
|
+
JSON.stringify(quote.tags),
|
|
1227
|
+
quote.source ?? null,
|
|
1228
|
+
quote.origin,
|
|
1229
|
+
quote.attribution ?? null,
|
|
1230
|
+
quote.verbatimUse,
|
|
1231
|
+
quote.contentSha256,
|
|
1232
|
+
existing.get(quote.quoteId)?.importedAt ?? timestamp,
|
|
1233
|
+
timestamp,
|
|
1234
|
+
vector === void 0 ? null : vectorBuffer(vector),
|
|
1235
|
+
vector === void 0 ? null : CONTENT_KNOWLEDGE_EMBEDDING_MODEL,
|
|
1236
|
+
vector === void 0 ? null : CONTENT_KNOWLEDGE_EMBEDDING_REVISION,
|
|
1237
|
+
vector === void 0 ? null : CONTENT_KNOWLEDGE_EMBEDDING_QUANTIZATION,
|
|
1238
|
+
vector === void 0 ? null : CONTENT_KNOWLEDGE_EMBEDDING_DIMENSIONS
|
|
1239
|
+
);
|
|
1240
|
+
if (quote.status === "active") {
|
|
1241
|
+
insertSearch.run(
|
|
1242
|
+
quote.quoteId,
|
|
1243
|
+
quote.text,
|
|
1244
|
+
quote.tags.join(" "),
|
|
1245
|
+
quote.source ?? "",
|
|
1246
|
+
quote.attribution ?? ""
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const archiveMissing = database.prepare(`
|
|
1251
|
+
UPDATE quotes
|
|
1252
|
+
SET status = 'archived', updated_at = ?, embedding = NULL, embedding_model = NULL,
|
|
1253
|
+
embedding_revision = NULL, embedding_quantization = NULL, embedding_dimensions = NULL
|
|
1254
|
+
WHERE status = 'active' AND quote_id NOT IN (${quotes.map(() => "?").join(",") || "''"})
|
|
1255
|
+
`);
|
|
1256
|
+
archiveMissing.run(timestamp, ...sourceIds);
|
|
1257
|
+
database.prepare(
|
|
1258
|
+
`INSERT INTO quote_sources (source_key, content_sha256, synced_at) VALUES ('default', ?, ?)
|
|
1259
|
+
ON CONFLICT(source_key) DO UPDATE SET content_sha256 = excluded.content_sha256, synced_at = excluded.synced_at`
|
|
1260
|
+
).run(sourceSha256, timestamp);
|
|
1261
|
+
database.exec("COMMIT");
|
|
1262
|
+
} catch {
|
|
1263
|
+
try {
|
|
1264
|
+
database.exec("ROLLBACK");
|
|
1265
|
+
} catch {
|
|
1266
|
+
}
|
|
1267
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_IO_FAILED", "Unable to synchronize quote index");
|
|
1268
|
+
}
|
|
1269
|
+
return priorSource === void 0 ? "created" : "updated";
|
|
1270
|
+
}
|
|
1271
|
+
function quoteSemanticIndexComplete(database) {
|
|
1272
|
+
const row = sqlRow(
|
|
1273
|
+
database.prepare(
|
|
1274
|
+
`SELECT COUNT(*) AS total, COALESCE(SUM(CASE
|
|
1275
|
+
WHEN embedding IS NOT NULL AND embedding_model = ? AND embedding_revision = ?
|
|
1276
|
+
AND embedding_quantization = ? AND embedding_dimensions = ? THEN 1 ELSE 0 END), 0) AS indexed
|
|
1277
|
+
FROM quotes WHERE status = 'active'`
|
|
1278
|
+
).get(
|
|
1279
|
+
CONTENT_KNOWLEDGE_EMBEDDING_MODEL,
|
|
1280
|
+
CONTENT_KNOWLEDGE_EMBEDDING_REVISION,
|
|
1281
|
+
CONTENT_KNOWLEDGE_EMBEDDING_QUANTIZATION,
|
|
1282
|
+
CONTENT_KNOWLEDGE_EMBEDDING_DIMENSIONS
|
|
1283
|
+
)
|
|
1284
|
+
);
|
|
1285
|
+
return numberColumn(row, "total") === numberColumn(row, "indexed");
|
|
1286
|
+
}
|
|
764
1287
|
function embeddingText(article, chunk) {
|
|
765
1288
|
return [article.title.slice(0, 120), chunk.heading?.slice(0, 120) ?? "", chunk.body].filter((value) => value.length > 0).join("\n");
|
|
766
1289
|
}
|
|
@@ -972,6 +1495,28 @@ function loadChunks(database) {
|
|
|
972
1495
|
CONTENT_KNOWLEDGE_EMBEDDING_DIMENSIONS
|
|
973
1496
|
).map(chunkRow);
|
|
974
1497
|
}
|
|
1498
|
+
function loadActiveQuotes(database) {
|
|
1499
|
+
return database.prepare(
|
|
1500
|
+
`
|
|
1501
|
+
SELECT
|
|
1502
|
+
quote_id, status, body, tags_json, source_label, origin, attribution, verbatim_use,
|
|
1503
|
+
content_sha256,
|
|
1504
|
+
CASE
|
|
1505
|
+
WHEN embedding_model = ? AND embedding_revision = ? AND embedding_quantization = ?
|
|
1506
|
+
AND embedding_dimensions = ?
|
|
1507
|
+
THEN embedding ELSE NULL
|
|
1508
|
+
END AS embedding
|
|
1509
|
+
FROM quotes
|
|
1510
|
+
WHERE status = 'active'
|
|
1511
|
+
ORDER BY imported_at ASC, quote_id ASC
|
|
1512
|
+
`
|
|
1513
|
+
).all(
|
|
1514
|
+
CONTENT_KNOWLEDGE_EMBEDDING_MODEL,
|
|
1515
|
+
CONTENT_KNOWLEDGE_EMBEDDING_REVISION,
|
|
1516
|
+
CONTENT_KNOWLEDGE_EMBEDDING_QUANTIZATION,
|
|
1517
|
+
CONTENT_KNOWLEDGE_EMBEDDING_DIMENSIONS
|
|
1518
|
+
).map(quoteRow);
|
|
1519
|
+
}
|
|
975
1520
|
function ftsExpression(query) {
|
|
976
1521
|
const terms = query.normalize("NFKC").split(/[\s,,。!?!?;;::、/\\|()[\]{}]+/u).map((term) => term.trim()).filter((term) => [...term].length >= 3).slice(0, 12);
|
|
977
1522
|
if (terms.length === 0) {
|
|
@@ -1001,6 +1546,24 @@ function lexicalRanking(database, query) {
|
|
|
1001
1546
|
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUERY_INVALID", "Knowledge query is invalid");
|
|
1002
1547
|
}
|
|
1003
1548
|
}
|
|
1549
|
+
function quoteLexicalRanking(database, query) {
|
|
1550
|
+
const expression = ftsExpression(query);
|
|
1551
|
+
if (expression === null) {
|
|
1552
|
+
return [];
|
|
1553
|
+
}
|
|
1554
|
+
try {
|
|
1555
|
+
return database.prepare(
|
|
1556
|
+
`
|
|
1557
|
+
SELECT quote_id FROM quote_search
|
|
1558
|
+
WHERE quote_search MATCH ?
|
|
1559
|
+
ORDER BY bm25(quote_search, 4.0, 1.5, 0.5, 0.5, 0.5)
|
|
1560
|
+
LIMIT ?
|
|
1561
|
+
`
|
|
1562
|
+
).all(expression, SEARCH_CANDIDATE_LIMIT).map((value, rank) => ({ id: stringColumn(sqlRow(value), "quote_id"), rank }));
|
|
1563
|
+
} catch {
|
|
1564
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUERY_INVALID", "Knowledge query is invalid");
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1004
1567
|
function dotProduct(left, right) {
|
|
1005
1568
|
let result = 0;
|
|
1006
1569
|
for (let index = 0; index < left.length; index += 1) {
|
|
@@ -1009,9 +1572,7 @@ function dotProduct(left, right) {
|
|
|
1009
1572
|
return result;
|
|
1010
1573
|
}
|
|
1011
1574
|
function semanticRanking(chunks, queryVector) {
|
|
1012
|
-
const candidates = chunks.filter(
|
|
1013
|
-
(chunk) => chunk.embedding !== null
|
|
1014
|
-
).map((chunk) => ({
|
|
1575
|
+
const candidates = chunks.filter((chunk) => chunk.embedding !== null).map((chunk) => ({
|
|
1015
1576
|
id: chunk.id,
|
|
1016
1577
|
similarity: dotProduct(queryVector, chunk.embedding)
|
|
1017
1578
|
})).sort((left, right) => right.similarity - left.similarity || left.id.localeCompare(right.id)).slice(0, SEARCH_CANDIDATE_LIMIT);
|
|
@@ -1065,6 +1626,7 @@ function fusedResults(chunks, mode, lexical, semantic, limit) {
|
|
|
1065
1626
|
rank: results.length + 1,
|
|
1066
1627
|
snippet: snippet(chunk.body),
|
|
1067
1628
|
sourceLabel: chunk.sourceLabel,
|
|
1629
|
+
sourceType: "history-article",
|
|
1068
1630
|
title: chunk.title
|
|
1069
1631
|
});
|
|
1070
1632
|
if (results.length >= limit) {
|
|
@@ -1073,10 +1635,66 @@ function fusedResults(chunks, mode, lexical, semantic, limit) {
|
|
|
1073
1635
|
}
|
|
1074
1636
|
return results;
|
|
1075
1637
|
}
|
|
1638
|
+
function fusedQuoteResults(quotes, mode, lexical, semantic, limit) {
|
|
1639
|
+
const scores = /* @__PURE__ */ new Map();
|
|
1640
|
+
const addRanking = (ranking, lane, weight) => {
|
|
1641
|
+
for (const candidate of ranking) {
|
|
1642
|
+
const current = scores.get(candidate.id) ?? { lanes: /* @__PURE__ */ new Set(), score: 0 };
|
|
1643
|
+
current.lanes.add(lane);
|
|
1644
|
+
current.score += weight / (RRF_CONSTANT + candidate.rank + 1);
|
|
1645
|
+
scores.set(candidate.id, current);
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
addRanking(semantic, "semantic", mode === "hybrid" ? 0.7 : mode === "semantic" ? 1 : 0);
|
|
1649
|
+
addRanking(lexical, "lexical", mode === "hybrid" ? 0.3 : mode === "lexical" ? 1 : 0);
|
|
1650
|
+
const byId = new Map(quotes.map((quote) => [quote.id, quote]));
|
|
1651
|
+
const results = [];
|
|
1652
|
+
for (const [quoteId, score] of [...scores.entries()].sort(
|
|
1653
|
+
(left, right) => right[1].score - left[1].score || left[0].localeCompare(right[0])
|
|
1654
|
+
)) {
|
|
1655
|
+
const quote = byId.get(quoteId);
|
|
1656
|
+
if (quote === void 0) {
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
results.push({
|
|
1660
|
+
attribution: quote.attribution,
|
|
1661
|
+
contentSha256: quote.contentSha256,
|
|
1662
|
+
lanes: [...score.lanes].sort(),
|
|
1663
|
+
origin: quote.origin,
|
|
1664
|
+
quoteId: quote.id,
|
|
1665
|
+
rank: results.length + 1,
|
|
1666
|
+
snippet: snippet(quote.text),
|
|
1667
|
+
sourceLabel: quote.sourceLabel,
|
|
1668
|
+
sourceType: "quote",
|
|
1669
|
+
status: quote.status,
|
|
1670
|
+
tags: [...quote.tags],
|
|
1671
|
+
verbatimUse: quote.verbatimUse
|
|
1672
|
+
});
|
|
1673
|
+
if (results.length >= limit) {
|
|
1674
|
+
break;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
return results;
|
|
1678
|
+
}
|
|
1679
|
+
function mergeSearchResults(history, quotes, limit) {
|
|
1680
|
+
const merged = [];
|
|
1681
|
+
for (let index = 0; merged.length < limit && (index < history.length || index < quotes.length); index += 1) {
|
|
1682
|
+
const historyItem = history[index];
|
|
1683
|
+
if (historyItem !== void 0 && merged.length < limit) {
|
|
1684
|
+
merged.push({ ...historyItem, rank: merged.length + 1 });
|
|
1685
|
+
}
|
|
1686
|
+
const quoteItem = quotes[index];
|
|
1687
|
+
if (quoteItem !== void 0 && merged.length < limit) {
|
|
1688
|
+
merged.push({ ...quoteItem, rank: merged.length + 1 });
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return merged;
|
|
1692
|
+
}
|
|
1076
1693
|
var KnowledgeCli = class {
|
|
1077
1694
|
#currentDirectory;
|
|
1078
1695
|
#databasePath;
|
|
1079
1696
|
#embedder;
|
|
1697
|
+
#homeDirectory;
|
|
1080
1698
|
#isModelCached;
|
|
1081
1699
|
#now;
|
|
1082
1700
|
#randomUUID;
|
|
@@ -1085,6 +1703,7 @@ var KnowledgeCli = class {
|
|
|
1085
1703
|
const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
|
|
1086
1704
|
const modelCacheDirectory = dependencies.modelCacheDirectory ?? path2.join(dependencies.homeDirectory, ".dxc", "models", "bge-small-zh-v1.5");
|
|
1087
1705
|
this.#currentDirectory = dependencies.currentDirectory;
|
|
1706
|
+
this.#homeDirectory = dependencies.homeDirectory;
|
|
1088
1707
|
this.#databasePath = dependencies.databasePath ?? path2.join(dependencies.homeDirectory, ".dxc", "content-memory.sqlite");
|
|
1089
1708
|
this.#embedder = dependencies.embedder ?? new LocalBgeKnowledgeEmbedder(modelCacheDirectory, now);
|
|
1090
1709
|
this.#isModelCached = dependencies.isModelCached ?? (() => isKnowledgeModelCached(modelCacheDirectory));
|
|
@@ -1099,16 +1718,23 @@ var KnowledgeCli = class {
|
|
|
1099
1718
|
"At least one article file is required"
|
|
1100
1719
|
);
|
|
1101
1720
|
}
|
|
1102
|
-
if (!options.lexicalOnly) {
|
|
1103
|
-
this.#writeStderr(
|
|
1104
|
-
"Preparing the local semantic index; first use downloads a quantized model of about 24 MB.\n"
|
|
1105
|
-
);
|
|
1106
|
-
}
|
|
1107
1721
|
const database = await openDatabase(this.#databasePath);
|
|
1108
1722
|
try {
|
|
1109
1723
|
const imported = [];
|
|
1724
|
+
const skipped = [];
|
|
1725
|
+
let semanticPreparationAnnounced = false;
|
|
1110
1726
|
for (const file of options.files) {
|
|
1111
|
-
|
|
1727
|
+
let article;
|
|
1728
|
+
try {
|
|
1729
|
+
article = await prepareArticle(this.#currentDirectory, file);
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
const skippedItem = skippedArticle(error, file);
|
|
1732
|
+
if (skippedItem !== void 0) {
|
|
1733
|
+
skipped.push(skippedItem);
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
throw error;
|
|
1737
|
+
}
|
|
1112
1738
|
const existing = findExistingArticle(database, article);
|
|
1113
1739
|
const unchanged = existing?.contentSha256 === article.contentSha256 && existing.chunkerVersion === CONTENT_KNOWLEDGE_CHUNKER_VERSION;
|
|
1114
1740
|
const semanticComplete = existing === void 0 ? false : semanticIndexComplete(database, existing.id);
|
|
@@ -1120,6 +1746,12 @@ var KnowledgeCli = class {
|
|
|
1120
1746
|
});
|
|
1121
1747
|
continue;
|
|
1122
1748
|
}
|
|
1749
|
+
if (!options.lexicalOnly && !semanticPreparationAnnounced) {
|
|
1750
|
+
this.#writeStderr(
|
|
1751
|
+
"Preparing the local semantic index; first use downloads a quantized model of about 24 MB.\n"
|
|
1752
|
+
);
|
|
1753
|
+
semanticPreparationAnnounced = true;
|
|
1754
|
+
}
|
|
1123
1755
|
const vectors = options.lexicalOnly ? void 0 : await embedArticle(this.#embedder, article);
|
|
1124
1756
|
const saved = saveArticle(
|
|
1125
1757
|
database,
|
|
@@ -1139,7 +1771,8 @@ var KnowledgeCli = class {
|
|
|
1139
1771
|
command: "knowledge.import",
|
|
1140
1772
|
data: {
|
|
1141
1773
|
imported,
|
|
1142
|
-
model: options.lexicalOnly ? null : this.#embedder.model
|
|
1774
|
+
model: options.lexicalOnly ? null : this.#embedder.model,
|
|
1775
|
+
skipped
|
|
1143
1776
|
},
|
|
1144
1777
|
ok: true
|
|
1145
1778
|
});
|
|
@@ -1153,6 +1786,236 @@ var KnowledgeCli = class {
|
|
|
1153
1786
|
lexicalOnly
|
|
1154
1787
|
});
|
|
1155
1788
|
}
|
|
1789
|
+
async initQuotes() {
|
|
1790
|
+
const sourcePath = quoteSourcePath(this.#homeDirectory);
|
|
1791
|
+
const exists = await quoteSourceExists(sourcePath);
|
|
1792
|
+
if (!exists) {
|
|
1793
|
+
const legacyPath = legacyQuoteSourcePath(this.#homeDirectory);
|
|
1794
|
+
const legacyExists = await quoteSourceExists(legacyPath);
|
|
1795
|
+
const quotes = legacyExists ? (await readQuoteSource(legacyPath, true)).quotes : [
|
|
1796
|
+
{
|
|
1797
|
+
quoteId: this.#randomUUID(),
|
|
1798
|
+
status: "active",
|
|
1799
|
+
text: "\u628A\u8FD9\u53E5\u66FF\u6362\u6210\u4F60\u81EA\u5DF1\u7684\u91D1\u53E5\uFF1B\u91D1\u53E5 ID \u4E00\u65E6\u521B\u5EFA\u5C31\u4E0D\u8981\u4FEE\u6539\u3002",
|
|
1800
|
+
tags: ["\u793A\u4F8B"],
|
|
1801
|
+
source: "\u4E2A\u4EBA\u539F\u521B",
|
|
1802
|
+
origin: "original",
|
|
1803
|
+
attribution: null,
|
|
1804
|
+
verbatimUse: "original"
|
|
1805
|
+
}
|
|
1806
|
+
];
|
|
1807
|
+
await writeQuoteSource(sourcePath, quotes);
|
|
1808
|
+
}
|
|
1809
|
+
const database = await openDatabase(this.#databasePath);
|
|
1810
|
+
try {
|
|
1811
|
+
return contentKnowledgeQuoteInitResultSchema.parse({
|
|
1812
|
+
command: "knowledge.quotes.init",
|
|
1813
|
+
data: { ...quoteSourceSummary(database, true), created: !exists },
|
|
1814
|
+
ok: true
|
|
1815
|
+
});
|
|
1816
|
+
} finally {
|
|
1817
|
+
database.close();
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
async listQuotes() {
|
|
1821
|
+
const sourcePath = quoteSourcePath(this.#homeDirectory);
|
|
1822
|
+
if (!await quoteSourceExists(sourcePath)) {
|
|
1823
|
+
throw new KnowledgeCliError(
|
|
1824
|
+
"DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
|
|
1825
|
+
"Quote source file is not initialized; run knowledge quotes init first"
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
const source = await readQuoteSource(sourcePath);
|
|
1829
|
+
return contentKnowledgeQuoteListResultSchema.parse({
|
|
1830
|
+
command: "knowledge.quotes.list",
|
|
1831
|
+
data: { quotes: source.quotes.map(quoteRecord) },
|
|
1832
|
+
ok: true
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
async addQuote(options) {
|
|
1836
|
+
const sourcePath = quoteSourcePath(this.#homeDirectory);
|
|
1837
|
+
if (!await quoteSourceExists(sourcePath)) {
|
|
1838
|
+
throw new KnowledgeCliError(
|
|
1839
|
+
"DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
|
|
1840
|
+
"Quote source file is not initialized; run knowledge quotes init first"
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
const source = await readQuoteSource(sourcePath);
|
|
1844
|
+
const quote = quoteSourceItemSchema.safeParse({
|
|
1845
|
+
attribution: options.attribution ?? null,
|
|
1846
|
+
origin: options.origin ?? "original",
|
|
1847
|
+
quoteId: this.#randomUUID(),
|
|
1848
|
+
source: options.source ?? null,
|
|
1849
|
+
status: "active",
|
|
1850
|
+
tags: options.tags ?? [],
|
|
1851
|
+
text: options.text,
|
|
1852
|
+
verbatimUse: options.verbatimUse ?? (options.origin === "third-party" ? "needs-verification" : "original")
|
|
1853
|
+
});
|
|
1854
|
+
if (!quote.success) {
|
|
1855
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote input is invalid");
|
|
1856
|
+
}
|
|
1857
|
+
await writeQuoteSource(sourcePath, [...source.quotes, quote.data]);
|
|
1858
|
+
return contentKnowledgeQuoteMutationResultSchema.parse({
|
|
1859
|
+
command: "knowledge.quotes.add",
|
|
1860
|
+
data: { pendingSync: true, quote: quoteRecord(quote.data) },
|
|
1861
|
+
ok: true
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
async updateQuote(quoteId, options) {
|
|
1865
|
+
if (!UUID_PATTERN.test(quoteId) || Object.keys(options).length === 0) {
|
|
1866
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote update is invalid");
|
|
1867
|
+
}
|
|
1868
|
+
const sourcePath = quoteSourcePath(this.#homeDirectory);
|
|
1869
|
+
if (!await quoteSourceExists(sourcePath)) {
|
|
1870
|
+
throw new KnowledgeCliError(
|
|
1871
|
+
"DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
|
|
1872
|
+
"Quote source file is not initialized; run knowledge quotes init first"
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
const source = await readQuoteSource(sourcePath);
|
|
1876
|
+
const index = source.quotes.findIndex((quote) => quote.quoteId === quoteId);
|
|
1877
|
+
if (index < 0) {
|
|
1878
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote ID was not found");
|
|
1879
|
+
}
|
|
1880
|
+
const existing = source.quotes[index];
|
|
1881
|
+
if (existing === void 0) {
|
|
1882
|
+
throw new KnowledgeCliError(
|
|
1883
|
+
"DXC_KNOWLEDGE_STATE_INVALID",
|
|
1884
|
+
"Knowledge quote state is invalid"
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
const updated = quoteSourceItemSchema.safeParse({
|
|
1888
|
+
...sourceQuote(existing),
|
|
1889
|
+
...options,
|
|
1890
|
+
attribution: options.attribution === void 0 ? existing.attribution ?? null : options.attribution,
|
|
1891
|
+
source: options.source === void 0 ? existing.source ?? null : options.source,
|
|
1892
|
+
tags: options.tags === void 0 ? existing.tags : options.tags,
|
|
1893
|
+
verbatimUse: options.verbatimUse ?? (options.origin === "third-party" && existing.origin !== "third-party" ? "needs-verification" : existing.verbatimUse)
|
|
1894
|
+
});
|
|
1895
|
+
if (!updated.success) {
|
|
1896
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote update is invalid");
|
|
1897
|
+
}
|
|
1898
|
+
const quotes = source.quotes.map(sourceQuote);
|
|
1899
|
+
quotes[index] = updated.data;
|
|
1900
|
+
await writeQuoteSource(sourcePath, quotes);
|
|
1901
|
+
return contentKnowledgeQuoteMutationResultSchema.parse({
|
|
1902
|
+
command: "knowledge.quotes.update",
|
|
1903
|
+
data: { pendingSync: true, quote: quoteRecord(updated.data) },
|
|
1904
|
+
ok: true
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
async archiveQuote(quoteId) {
|
|
1908
|
+
if (!UUID_PATTERN.test(quoteId)) {
|
|
1909
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote ID is invalid");
|
|
1910
|
+
}
|
|
1911
|
+
const sourcePath = quoteSourcePath(this.#homeDirectory);
|
|
1912
|
+
if (!await quoteSourceExists(sourcePath)) {
|
|
1913
|
+
throw new KnowledgeCliError(
|
|
1914
|
+
"DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
|
|
1915
|
+
"Quote source file is not initialized; run knowledge quotes init first"
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
const source = await readQuoteSource(sourcePath);
|
|
1919
|
+
const index = source.quotes.findIndex((quote) => quote.quoteId === quoteId);
|
|
1920
|
+
if (index < 0) {
|
|
1921
|
+
throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote ID was not found");
|
|
1922
|
+
}
|
|
1923
|
+
const existing = source.quotes[index];
|
|
1924
|
+
if (existing === void 0) {
|
|
1925
|
+
throw new KnowledgeCliError(
|
|
1926
|
+
"DXC_KNOWLEDGE_STATE_INVALID",
|
|
1927
|
+
"Knowledge quote state is invalid"
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
const archived = { ...sourceQuote(existing), status: "archived" };
|
|
1931
|
+
const quotes = source.quotes.map(sourceQuote);
|
|
1932
|
+
quotes[index] = archived;
|
|
1933
|
+
await writeQuoteSource(sourcePath, quotes);
|
|
1934
|
+
return contentKnowledgeQuoteMutationResultSchema.parse({
|
|
1935
|
+
command: "knowledge.quotes.archive",
|
|
1936
|
+
data: { pendingSync: true, quote: quoteRecord(archived) },
|
|
1937
|
+
ok: true
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
async quoteStatus() {
|
|
1941
|
+
const sourceInitialized = await quoteSourceExists(quoteSourcePath(this.#homeDirectory));
|
|
1942
|
+
if (!await databaseExists(this.#databasePath)) {
|
|
1943
|
+
return contentKnowledgeQuoteStatusResultSchema.parse({
|
|
1944
|
+
command: "knowledge.quotes.status",
|
|
1945
|
+
data: {
|
|
1946
|
+
activeCount: 0,
|
|
1947
|
+
archivedCount: 0,
|
|
1948
|
+
sourceInitialized,
|
|
1949
|
+
sourcePath: QUOTE_SOURCE_DISPLAY_PATH,
|
|
1950
|
+
sourceSha256: null
|
|
1951
|
+
},
|
|
1952
|
+
ok: true
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
const database = await openDatabase(this.#databasePath);
|
|
1956
|
+
try {
|
|
1957
|
+
return contentKnowledgeQuoteStatusResultSchema.parse({
|
|
1958
|
+
command: "knowledge.quotes.status",
|
|
1959
|
+
data: quoteSourceSummary(database, sourceInitialized),
|
|
1960
|
+
ok: true
|
|
1961
|
+
});
|
|
1962
|
+
} finally {
|
|
1963
|
+
database.close();
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
async syncQuotes(lexicalOnly) {
|
|
1967
|
+
const sourcePath = quoteSourcePath(this.#homeDirectory);
|
|
1968
|
+
if (!await quoteSourceExists(sourcePath)) {
|
|
1969
|
+
throw new KnowledgeCliError(
|
|
1970
|
+
"DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
|
|
1971
|
+
"Quote source file is not initialized; run knowledge quotes init first"
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
if (!lexicalOnly) {
|
|
1975
|
+
this.#writeStderr(
|
|
1976
|
+
"Preparing the local semantic quote index; first use downloads a quantized model of about 24 MB.\n"
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
const source = await readQuoteSource(sourcePath);
|
|
1980
|
+
const database = await openDatabase(this.#databasePath);
|
|
1981
|
+
try {
|
|
1982
|
+
const sourceRow = database.prepare("SELECT content_sha256 FROM quote_sources WHERE source_key = 'default' LIMIT 1").get();
|
|
1983
|
+
const unchanged = sourceRow !== void 0 && stringColumn(sqlRow(sourceRow), "content_sha256") === source.contentSha256 && (lexicalOnly || quoteSemanticIndexComplete(database));
|
|
1984
|
+
const currentQuotes = new Map(loadActiveQuotes(database).map((quote) => [quote.id, quote]));
|
|
1985
|
+
const quotesToEmbed = source.quotes.filter((quote) => {
|
|
1986
|
+
const current = currentQuotes.get(quote.quoteId);
|
|
1987
|
+
return quote.status === "active" && (current?.contentSha256 !== quote.contentSha256 || current.embedding === null);
|
|
1988
|
+
});
|
|
1989
|
+
const vectors = unchanged || lexicalOnly ? void 0 : await embedQuotes(this.#embedder, quotesToEmbed);
|
|
1990
|
+
if (vectors !== void 0) {
|
|
1991
|
+
for (const quote of source.quotes) {
|
|
1992
|
+
const current = currentQuotes.get(quote.quoteId);
|
|
1993
|
+
if (quote.status === "active" && !vectors.has(quote.quoteId) && current?.contentSha256 === quote.contentSha256 && current.embedding !== null) {
|
|
1994
|
+
vectors.set(quote.quoteId, current.embedding);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
const status = syncQuotes(
|
|
1999
|
+
database,
|
|
2000
|
+
source.quotes,
|
|
2001
|
+
source.contentSha256,
|
|
2002
|
+
vectors,
|
|
2003
|
+
this.#now().toISOString(),
|
|
2004
|
+
unchanged
|
|
2005
|
+
);
|
|
2006
|
+
return contentKnowledgeQuoteSyncResultSchema.parse({
|
|
2007
|
+
command: "knowledge.quotes.sync",
|
|
2008
|
+
data: {
|
|
2009
|
+
...quoteSourceSummary(database, true),
|
|
2010
|
+
model: lexicalOnly ? null : this.#embedder.model,
|
|
2011
|
+
status
|
|
2012
|
+
},
|
|
2013
|
+
ok: true
|
|
2014
|
+
});
|
|
2015
|
+
} finally {
|
|
2016
|
+
database.close();
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
1156
2019
|
async remove(articleId) {
|
|
1157
2020
|
if (!UUID_PATTERN.test(articleId)) {
|
|
1158
2021
|
throw new KnowledgeCliError(
|
|
@@ -1214,16 +2077,19 @@ var KnowledgeCli = class {
|
|
|
1214
2077
|
const database = await openDatabase(this.#databasePath);
|
|
1215
2078
|
try {
|
|
1216
2079
|
const chunks = loadChunks(database);
|
|
2080
|
+
const quotes = loadActiveQuotes(database);
|
|
1217
2081
|
const usesSemantic = mode.data !== "lexical";
|
|
1218
|
-
if (usesSemantic && chunks.some((chunk) => chunk.embedding === null)) {
|
|
2082
|
+
if (usesSemantic && (chunks.some((chunk) => chunk.embedding === null) || quotes.some((quote) => quote.embedding === null))) {
|
|
1219
2083
|
throw new KnowledgeCliError(
|
|
1220
2084
|
"DXC_KNOWLEDGE_SEMANTIC_NOT_READY",
|
|
1221
|
-
"All
|
|
2085
|
+
"All active knowledge items must have a semantic index before hybrid search"
|
|
1222
2086
|
);
|
|
1223
2087
|
}
|
|
1224
2088
|
const lexical = mode.data === "semantic" ? [] : lexicalRanking(database, query);
|
|
2089
|
+
const quoteLexical = mode.data === "semantic" ? [] : quoteLexicalRanking(database, query);
|
|
1225
2090
|
let semantic = [];
|
|
1226
|
-
|
|
2091
|
+
let quoteSemantic = [];
|
|
2092
|
+
if (usesSemantic && (chunks.length > 0 || quotes.length > 0)) {
|
|
1227
2093
|
let queryVector;
|
|
1228
2094
|
try {
|
|
1229
2095
|
queryVector = await this.#embedder.embedQuery(query);
|
|
@@ -1234,6 +2100,7 @@ var KnowledgeCli = class {
|
|
|
1234
2100
|
);
|
|
1235
2101
|
}
|
|
1236
2102
|
semantic = semanticRanking(chunks, queryVector);
|
|
2103
|
+
quoteSemantic = semanticRanking(quotes, queryVector);
|
|
1237
2104
|
}
|
|
1238
2105
|
return contentKnowledgeSearchResultSchema.parse({
|
|
1239
2106
|
command: "knowledge.search",
|
|
@@ -1241,7 +2108,11 @@ var KnowledgeCli = class {
|
|
|
1241
2108
|
mode: mode.data,
|
|
1242
2109
|
model: usesSemantic ? this.#embedder.model : null,
|
|
1243
2110
|
query,
|
|
1244
|
-
results:
|
|
2111
|
+
results: mergeSearchResults(
|
|
2112
|
+
fusedResults(chunks, mode.data, lexical, semantic, limitInput),
|
|
2113
|
+
fusedQuoteResults(quotes, mode.data, quoteLexical, quoteSemantic, limitInput),
|
|
2114
|
+
limitInput
|
|
2115
|
+
)
|
|
1245
2116
|
},
|
|
1246
2117
|
ok: true
|
|
1247
2118
|
});
|
|
@@ -1253,6 +2124,13 @@ var KnowledgeCli = class {
|
|
|
1253
2124
|
let articleCount = 0;
|
|
1254
2125
|
let chunkCount = 0;
|
|
1255
2126
|
let semanticIndexedChunkCount = 0;
|
|
2127
|
+
let quoteSummary = {
|
|
2128
|
+
activeCount: 0,
|
|
2129
|
+
archivedCount: 0,
|
|
2130
|
+
sourceInitialized: await quoteSourceExists(quoteSourcePath(this.#homeDirectory)),
|
|
2131
|
+
sourcePath: QUOTE_SOURCE_DISPLAY_PATH,
|
|
2132
|
+
sourceSha256: null
|
|
2133
|
+
};
|
|
1256
2134
|
if (await databaseExists(this.#databasePath)) {
|
|
1257
2135
|
const database = await openDatabase(this.#databasePath);
|
|
1258
2136
|
try {
|
|
@@ -1287,6 +2165,7 @@ var KnowledgeCli = class {
|
|
|
1287
2165
|
articleCount = numberColumn(articleRowValue, "count");
|
|
1288
2166
|
chunkCount = numberColumn(chunkRowValue, "count");
|
|
1289
2167
|
semanticIndexedChunkCount = numberColumn(chunkRowValue, "semantic_count");
|
|
2168
|
+
quoteSummary = quoteSourceSummary(database, quoteSummary.sourceInitialized);
|
|
1290
2169
|
} finally {
|
|
1291
2170
|
database.close();
|
|
1292
2171
|
}
|
|
@@ -1304,7 +2183,8 @@ var KnowledgeCli = class {
|
|
|
1304
2183
|
revision: CONTENT_KNOWLEDGE_EMBEDDING_REVISION
|
|
1305
2184
|
}),
|
|
1306
2185
|
modelCached: await this.#isModelCached(),
|
|
1307
|
-
semanticIndexedChunkCount
|
|
2186
|
+
semanticIndexedChunkCount,
|
|
2187
|
+
quotes: quoteSummary
|
|
1308
2188
|
},
|
|
1309
2189
|
ok: true
|
|
1310
2190
|
});
|