@hraness/wordcell 0.23.0 → 0.24.1
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 +29 -16
- package/dist/authoring.js +2 -1
- package/dist/cli.js +10 -9
- package/dist/clip/jobs.js +5 -1
- package/dist/evaluation-builder.js +5 -4
- package/dist/evaluation-kb.js +5 -4
- package/dist/graph-authority.js +2 -1
- package/dist/graph-percolation.js +3 -2
- package/dist/{index-1er88ckw.js → index-3gc2yk4k.js} +10 -5
- package/dist/{index-mnq2wy34.js → index-59bcxqee.js} +1 -1
- package/dist/{index-p70t8j13.js → index-7drercgk.js} +1 -1
- package/dist/{index-tcaq1c7f.js → index-e26mdwxz.js} +1 -1
- package/dist/{index-r4qs6vq4.js → index-hz2wyggk.js} +1 -1
- package/dist/{index-vtwbapjr.js → index-jqkkb4fr.js} +72 -12
- package/dist/{index-tvd2sqrx.js → index-mcpppwbq.js} +439 -4
- package/dist/index-qkesh4c6.js +7 -0
- package/dist/{index-b0khj0h5.js → index-qt2zwza8.js} +6 -1
- package/dist/{index-0eacgvpv.js → index-t0fh8a1q.js} +7 -3
- package/dist/index.js +6 -5
- package/dist/portfolio.js +5 -4
- package/dist/sdk.js +4 -3
- package/dist/{support-c8hy7qfa.js → support-07jpt1z4.js} +9 -8
- package/dist/url-intelligence.js +2 -1
- package/dist/workflows/decision-context.js +5 -4
- package/dist/workflows/index.js +5 -4
- package/package.json +12 -8
- package/skills/wordcell/SKILL.md +1 -1
- package/skills/wordcell/references/publish.md +2 -1
- package/skills/wordcell/references/query.md +17 -3
- package/src/authoring-platform.ts +3 -0
- package/src/cli-help.ts +1 -0
- package/src/cli-program.ts +41 -3
- package/src/clip/jobs.ts +2 -1
- package/src/clip/metadata-search-tool/Cargo.lock +2 -0
- package/src/clip/metadata-search-tool/Cargo.toml +2 -0
- package/src/clip/metadata-search-tool/runner.ts +4 -1
- package/src/clip/metadata-search-tool/src/duckduckgo.rs +534 -0
- package/src/clip/metadata-search-tool/src/main.rs +31 -18
- package/src/clip/metadata-search.ts +4 -2
- package/src/clip/url-metadata.ts +2 -1
- package/src/directory-sync.ts +9 -0
- package/src/graph-authority.ts +5 -2
- package/src/mcp-tools.ts +17 -1
- package/src/sdk.ts +149 -1
- package/src/selected-passage.ts +409 -0
package/src/graph-authority.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { syncsDirectories } from "./directory-sync.js";
|
|
2
3
|
import { constants } from "node:fs";
|
|
3
4
|
import { mkdir, mkdtemp, realpath, rename, rm } from "node:fs/promises";
|
|
4
5
|
import { join, resolve } from "node:path";
|
|
@@ -197,8 +198,10 @@ export async function rebuildGraph(rootInput: string, options: GraphRebuildOptio
|
|
|
197
198
|
const handle = await fs.open(stagedPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
198
199
|
try { await handle.sync(); } finally { await handle.close(); }
|
|
199
200
|
await rename(stagedPath, path);
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
if (syncsDirectories()) {
|
|
202
|
+
const directory = await fs.open(cache, constants.O_RDONLY);
|
|
203
|
+
try { await directory.sync(); } finally { await directory.close(); }
|
|
204
|
+
}
|
|
202
205
|
return result;
|
|
203
206
|
} finally {
|
|
204
207
|
try {
|
package/src/mcp-tools.ts
CHANGED
|
@@ -229,6 +229,15 @@ export function optionalText(arguments_: Arguments, key: string, maxBytes: numbe
|
|
|
229
229
|
return has(arguments_, key) ? checkedText(arguments_[key], key, maxBytes) : undefined;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
function optionalBoolean(arguments_: Arguments, key: string): boolean | undefined {
|
|
233
|
+
if (!has(arguments_, key)) return undefined;
|
|
234
|
+
const value = arguments_[key];
|
|
235
|
+
if (typeof value !== "boolean") {
|
|
236
|
+
throw new ToolArgumentError(`Invalid argument "${key}": expected a boolean.`);
|
|
237
|
+
}
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
|
|
232
241
|
export function optionalInteger(
|
|
233
242
|
arguments_: Arguments,
|
|
234
243
|
key: string,
|
|
@@ -631,6 +640,11 @@ export function readTools(root: string, sessions: VaultSessions): readonly ToolE
|
|
|
631
640
|
properties: {
|
|
632
641
|
query: { type: "string", minLength: 1, maxLength: MAX_QUERY_TEXT_UTF8_BYTES },
|
|
633
642
|
mode: { type: "string", enum: SEARCH_MODES, default: "hybrid" },
|
|
643
|
+
selectedPassage: {
|
|
644
|
+
type: "boolean",
|
|
645
|
+
default: false,
|
|
646
|
+
description: "Include a local source excerpt with line references and section headings. Ranking is unchanged.",
|
|
647
|
+
},
|
|
634
648
|
limit: { type: "integer", minimum: 1, maximum: MAX_SEARCH_RESULTS, default: DEFAULT_SEARCH_RESULTS },
|
|
635
649
|
...filterProperties,
|
|
636
650
|
},
|
|
@@ -638,10 +652,11 @@ export function readTools(root: string, sessions: VaultSessions): readonly ToolE
|
|
|
638
652
|
annotations: { title: "Search notes", ...READ_ONLY },
|
|
639
653
|
},
|
|
640
654
|
handler: async (arguments_) => {
|
|
641
|
-
allowOnly(arguments_, ["query", "mode", "limit", "tags", "where", "has", "scope"]);
|
|
655
|
+
allowOnly(arguments_, ["query", "mode", "limit", "tags", "where", "has", "scope", "selectedPassage"]);
|
|
642
656
|
const query = requiredText(arguments_, "query", MAX_QUERY_TEXT_UTF8_BYTES);
|
|
643
657
|
const mode = optionalChoice(arguments_, "mode", SEARCH_MODES) ?? "hybrid";
|
|
644
658
|
const limit = optionalInteger(arguments_, "limit", 1, MAX_SEARCH_RESULTS) ?? DEFAULT_SEARCH_RESULTS;
|
|
659
|
+
const selectedPassage = optionalBoolean(arguments_, "selectedPassage");
|
|
645
660
|
const filters = metadataFilters(arguments_);
|
|
646
661
|
const tags = optionalTextList(arguments_, "tags", MAX_QUERY_TAGS, MAX_QUERY_TEXT_UTF8_BYTES);
|
|
647
662
|
const scopes = optionalTextList(arguments_, "scope", MAX_REPOSITORY_SCOPES, MAX_QUERY_TEXT_UTF8_BYTES);
|
|
@@ -650,6 +665,7 @@ export function readTools(root: string, sessions: VaultSessions): readonly ToolE
|
|
|
650
665
|
query,
|
|
651
666
|
mode,
|
|
652
667
|
limit,
|
|
668
|
+
...(selectedPassage === undefined ? {} : { selectedPassage }),
|
|
653
669
|
...(filters.length === 0 ? {} : { filters }),
|
|
654
670
|
...(tags === undefined ? {} : { tags }),
|
|
655
671
|
...(scopes === undefined ? {} : { repositoryScopes: scopes }),
|
package/src/sdk.ts
CHANGED
|
@@ -52,6 +52,15 @@ import {
|
|
|
52
52
|
type SearchRuleRequest,
|
|
53
53
|
type SearchRulesV1,
|
|
54
54
|
} from "./search-rules.js";
|
|
55
|
+
import {
|
|
56
|
+
createSelectedPassageBudget,
|
|
57
|
+
SELECTED_PASSAGE_MAX_BYTES,
|
|
58
|
+
SELECTED_PASSAGE_MAX_HEADING_BYTES,
|
|
59
|
+
SELECTED_PASSAGE_NONE_REASONS,
|
|
60
|
+
SELECTED_PASSAGE_UNAVAILABLE_REASONS,
|
|
61
|
+
selectSearchPassage,
|
|
62
|
+
type SelectedPassageResult,
|
|
63
|
+
} from "./selected-passage.js";
|
|
55
64
|
import {
|
|
56
65
|
applyRerank,
|
|
57
66
|
MAX_RERANK_CANDIDATES,
|
|
@@ -82,6 +91,14 @@ import {
|
|
|
82
91
|
type UntrustedStructuredContent,
|
|
83
92
|
} from "./untrusted-content.js";
|
|
84
93
|
|
|
94
|
+
export type {
|
|
95
|
+
SelectedPassage,
|
|
96
|
+
SelectedPassageHeading,
|
|
97
|
+
SelectedPassageNone,
|
|
98
|
+
SelectedPassageResult,
|
|
99
|
+
SelectedPassageUnavailable,
|
|
100
|
+
} from "./selected-passage.js";
|
|
101
|
+
|
|
85
102
|
export const MAX_SEARCH_RESULTS = 100;
|
|
86
103
|
export const MAX_SEARCH_CANDIDATES = 500;
|
|
87
104
|
export const DEFAULT_SEARCH_RESULTS = 10;
|
|
@@ -131,6 +148,8 @@ export type KnowledgeBaseSearchOptions = {
|
|
|
131
148
|
readonly history?: false | "auto" | "required" | KnowledgeBaseHistoryOptions;
|
|
132
149
|
/** Opt-in rerank engine over a bounded result window. */
|
|
133
150
|
readonly rerank?: KnowledgeBaseSearchRerankOptions;
|
|
151
|
+
/** Add one local query-selected source passage after ranking; never changes reranker input. */
|
|
152
|
+
readonly selectedPassage?: boolean;
|
|
134
153
|
};
|
|
135
154
|
|
|
136
155
|
export type KnowledgeBaseExactEvidence = {
|
|
@@ -179,6 +198,8 @@ export type KnowledgeBaseSearchHit = {
|
|
|
179
198
|
readonly identity: boolean;
|
|
180
199
|
readonly line?: number;
|
|
181
200
|
readonly snippet: string;
|
|
201
|
+
/** Present only when requested; legacy snippet, line, and retrieval evidence retain their meanings. */
|
|
202
|
+
readonly selectedPassage?: SelectedPassageResult;
|
|
182
203
|
readonly tags: readonly string[];
|
|
183
204
|
readonly metadata: MetadataObject;
|
|
184
205
|
readonly evidence: readonly KnowledgeBaseSearchEvidence[];
|
|
@@ -729,6 +750,10 @@ export async function openKnowledgeBase(
|
|
|
729
750
|
searchRules,
|
|
730
751
|
);
|
|
731
752
|
const effectiveOptions = expansion.request as KnowledgeBaseSearchOptions;
|
|
753
|
+
if (effectiveOptions.selectedPassage !== undefined
|
|
754
|
+
&& typeof effectiveOptions.selectedPassage !== "boolean") {
|
|
755
|
+
throw new TypeError("Search selectedPassage must be a boolean.");
|
|
756
|
+
}
|
|
732
757
|
const { query: effectiveQuery } = validateSearchQuery(effectiveOptions.query);
|
|
733
758
|
const mode = checkedSearchMode(effectiveOptions.mode);
|
|
734
759
|
const ordering = checkedSearchOrdering(effectiveOptions.ordering);
|
|
@@ -1029,6 +1054,17 @@ export async function openKnowledgeBase(
|
|
|
1029
1054
|
hit.rank === index + 1 ? hit : { ...hit, rank: index + 1 }));
|
|
1030
1055
|
}
|
|
1031
1056
|
}
|
|
1057
|
+
if (effectiveOptions.selectedPassage === true) {
|
|
1058
|
+
const budget = createSelectedPassageBudget();
|
|
1059
|
+
results = Object.freeze(results.map((hit) => {
|
|
1060
|
+
const note = notesById.get(hit.id);
|
|
1061
|
+
if (note === undefined) throw new Error("Selected search hit has no snapshot note.");
|
|
1062
|
+
return Object.freeze({
|
|
1063
|
+
...hit,
|
|
1064
|
+
selectedPassage: selectSearchPassage({ query: effectiveQuery, text: note.content, budget }),
|
|
1065
|
+
});
|
|
1066
|
+
}));
|
|
1067
|
+
}
|
|
1032
1068
|
let graph: GraphContext | null = null;
|
|
1033
1069
|
if (graphOptions !== null) {
|
|
1034
1070
|
try {
|
|
@@ -1273,7 +1309,16 @@ export function packSearchContext(
|
|
|
1273
1309
|
if (!append(String(searchEvidenceRank(item)))) return writer.result();
|
|
1274
1310
|
}
|
|
1275
1311
|
if (!append("\n\n")) return writer.result();
|
|
1276
|
-
|
|
1312
|
+
const selected = selectedPassageForContext(hit, `Knowledge-base search result ${hit.rank}`);
|
|
1313
|
+
if (selected?.status === "selected") {
|
|
1314
|
+
for (const heading of selected.headings) {
|
|
1315
|
+
if (!append(`Section (level ${heading.level}, lines ${heading.startLine}-${heading.endLine}${heading.truncated ? ", shortened" : ""}):\n`)) return writer.result();
|
|
1316
|
+
if (!append(heading.text)) return writer.result();
|
|
1317
|
+
if (!append("\n\n")) return writer.result();
|
|
1318
|
+
}
|
|
1319
|
+
if (!append(`Selected source (UTF-8 snapshot ${selected.sourceSha256}; bytes ${selected.startByte}-${selected.endByte}; lines ${selected.startLine}-${selected.endLine}${selected.clippedStart || selected.clippedEnd ? "; excerpt clipped" : ""}):\n`)) return writer.result();
|
|
1320
|
+
if (!append(selected.text)) return writer.result();
|
|
1321
|
+
} else if (!append(hit.snippet)) return writer.result();
|
|
1277
1322
|
}
|
|
1278
1323
|
|
|
1279
1324
|
const graph = result.graph;
|
|
@@ -1420,6 +1465,107 @@ function optionalDataNumber(
|
|
|
1420
1465
|
return dataNumber(record[key], `${label}.${key}`);
|
|
1421
1466
|
}
|
|
1422
1467
|
|
|
1468
|
+
function dataCount(value: unknown, label: string, minimum = 0): number {
|
|
1469
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum) {
|
|
1470
|
+
throw new TypeError(`${label} must be an integer of at least ${minimum}.`);
|
|
1471
|
+
}
|
|
1472
|
+
return value;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function dataSourceText(value: unknown, label: string, maximumBytes: number): string {
|
|
1476
|
+
const text = dataString(value, label);
|
|
1477
|
+
if (/\p{Surrogate}/u.test(text)) throw new TypeError(`${label} must be well-formed Unicode.`);
|
|
1478
|
+
if (Buffer.byteLength(text, "utf8") > maximumBytes) {
|
|
1479
|
+
throw new RangeError(`${label} exceeds ${maximumBytes} UTF-8 bytes.`);
|
|
1480
|
+
}
|
|
1481
|
+
return text;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function sourceSpan(
|
|
1485
|
+
record: InspectedDataRecord,
|
|
1486
|
+
label: string,
|
|
1487
|
+
text: string,
|
|
1488
|
+
): { startByte: number; endByte: number; startLine: number; endLine: number } {
|
|
1489
|
+
const startByte = dataCount(requiredData(record, "startByte", label), `${label}.startByte`);
|
|
1490
|
+
const endByte = dataCount(requiredData(record, "endByte", label), `${label}.endByte`);
|
|
1491
|
+
const startLine = dataCount(requiredData(record, "startLine", label), `${label}.startLine`, 1);
|
|
1492
|
+
const endLine = dataCount(requiredData(record, "endLine", label), `${label}.endLine`, startLine);
|
|
1493
|
+
if (endByte - startByte !== Buffer.byteLength(text, "utf8")) {
|
|
1494
|
+
throw new TypeError(`${label} byte span must match its text.`);
|
|
1495
|
+
}
|
|
1496
|
+
return { startByte, endByte, startLine, endLine };
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function selectedPassageData(value: unknown, label: string): SelectedPassageResult {
|
|
1500
|
+
const record = inspectedDataRecord(value, label);
|
|
1501
|
+
const status = requiredData(record, "status", label);
|
|
1502
|
+
if (status === "none" || status === "unavailable") {
|
|
1503
|
+
const reason = requiredData(record, "reason", label);
|
|
1504
|
+
const reasons: readonly string[] = status === "none"
|
|
1505
|
+
? SELECTED_PASSAGE_NONE_REASONS
|
|
1506
|
+
: SELECTED_PASSAGE_UNAVAILABLE_REASONS;
|
|
1507
|
+
if (typeof reason !== "string" || !reasons.includes(reason)) {
|
|
1508
|
+
throw new TypeError(`${label}.reason is not a known ${status} reason.`);
|
|
1509
|
+
}
|
|
1510
|
+
return Object.freeze({ status, reason }) as SelectedPassageResult;
|
|
1511
|
+
}
|
|
1512
|
+
if (status !== "selected") throw new TypeError(`${label}.status is not a known selected-passage status.`);
|
|
1513
|
+
const text = dataSourceText(requiredData(record, "text", label), `${label}.text`, SELECTED_PASSAGE_MAX_BYTES);
|
|
1514
|
+
if (text === "") throw new TypeError(`${label}.text must not be empty.`);
|
|
1515
|
+
const span = sourceSpan(record, label, text);
|
|
1516
|
+
const sourceSha256 = dataString(requiredData(record, "sourceSha256", label), `${label}.sourceSha256`);
|
|
1517
|
+
if (!/^[0-9a-f]{64}$/u.test(sourceSha256)) throw new TypeError(`${label}.sourceSha256 must be a SHA-256 digest.`);
|
|
1518
|
+
if (requiredData(record, "sourceEncoding", label) !== "utf8-snapshot") {
|
|
1519
|
+
throw new TypeError(`${label}.sourceEncoding must be utf8-snapshot.`);
|
|
1520
|
+
}
|
|
1521
|
+
let previousLevel = 0;
|
|
1522
|
+
let previousEnd = 0;
|
|
1523
|
+
const headings = inspectedDataArray(requiredData(record, "headings", label), `${label}.headings`, 6)
|
|
1524
|
+
.map((entry, index) => {
|
|
1525
|
+
const headingLabel = `${label}.headings[${index}]`;
|
|
1526
|
+
const heading = inspectedDataRecord(entry, headingLabel);
|
|
1527
|
+
const level = dataCount(requiredData(heading, "level", headingLabel), `${headingLabel}.level`, previousLevel + 1);
|
|
1528
|
+
if (level > 6) throw new TypeError(`${headingLabel}.level must be at most 6.`);
|
|
1529
|
+
const headingText = dataSourceText(
|
|
1530
|
+
requiredData(heading, "text", headingLabel),
|
|
1531
|
+
`${headingLabel}.text`,
|
|
1532
|
+
SELECTED_PASSAGE_MAX_HEADING_BYTES,
|
|
1533
|
+
);
|
|
1534
|
+
const headingSpan = sourceSpan(heading, headingLabel, headingText);
|
|
1535
|
+
if (headingSpan.startByte < previousEnd || headingSpan.endByte > span.startByte) {
|
|
1536
|
+
throw new TypeError(`${headingLabel} must precede the selected passage in source order.`);
|
|
1537
|
+
}
|
|
1538
|
+
previousLevel = level;
|
|
1539
|
+
previousEnd = headingSpan.endByte;
|
|
1540
|
+
return Object.freeze({
|
|
1541
|
+
level,
|
|
1542
|
+
text: headingText,
|
|
1543
|
+
...headingSpan,
|
|
1544
|
+
truncated: dataBoolean(requiredData(heading, "truncated", headingLabel), `${headingLabel}.truncated`),
|
|
1545
|
+
});
|
|
1546
|
+
});
|
|
1547
|
+
return Object.freeze({
|
|
1548
|
+
status,
|
|
1549
|
+
text,
|
|
1550
|
+
...span,
|
|
1551
|
+
sourceSha256,
|
|
1552
|
+
sourceEncoding: "utf8-snapshot",
|
|
1553
|
+
clippedStart: dataBoolean(requiredData(record, "clippedStart", label), `${label}.clippedStart`),
|
|
1554
|
+
clippedEnd: dataBoolean(requiredData(record, "clippedEnd", label), `${label}.clippedEnd`),
|
|
1555
|
+
headings: Object.freeze(headings),
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
/** Read an optional hit passage as untrusted data without invoking accessors. */
|
|
1560
|
+
function selectedPassageForContext(hit: object, label: string): SelectedPassageResult | undefined {
|
|
1561
|
+
const descriptor = Object.getOwnPropertyDescriptor(hit, "selectedPassage");
|
|
1562
|
+
if (descriptor === undefined) return undefined;
|
|
1563
|
+
if (!("value" in descriptor)) throw new TypeError(`${label} contains an accessor property.`);
|
|
1564
|
+
return descriptor.value === undefined
|
|
1565
|
+
? undefined
|
|
1566
|
+
: selectedPassageData(descriptor.value, `${label}.selectedPassage`);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1423
1569
|
function searchEvidenceSummary(value: unknown, label: string): Readonly<Record<string, unknown>> {
|
|
1424
1570
|
const evidence = inspectedDataRecord(value, label);
|
|
1425
1571
|
return Object.freeze({
|
|
@@ -1438,6 +1584,7 @@ function searchHitContextRecord(value: unknown, index: number): Readonly<Record<
|
|
|
1438
1584
|
).map((entry, evidenceIndex) =>
|
|
1439
1585
|
searchEvidenceSummary(entry, `${label}.evidence[${evidenceIndex}]`));
|
|
1440
1586
|
const line = optionalDataNumber(hit, "line", label);
|
|
1587
|
+
const selectedPassage = selectedPassageForContext(hit, label);
|
|
1441
1588
|
return Object.freeze({
|
|
1442
1589
|
kind: "search-result",
|
|
1443
1590
|
id: dataString(requiredData(hit, "id", label), `${label}.id`),
|
|
@@ -1449,6 +1596,7 @@ function searchHitContextRecord(value: unknown, index: number): Readonly<Record<
|
|
|
1449
1596
|
...(line === undefined ? {} : { line }),
|
|
1450
1597
|
evidence: Object.freeze(evidence),
|
|
1451
1598
|
snippet: dataString(requiredData(hit, "snippet", label), `${label}.snippet`),
|
|
1599
|
+
...(selectedPassage === undefined ? {} : { selectedPassage }),
|
|
1452
1600
|
});
|
|
1453
1601
|
}
|
|
1454
1602
|
|