@shrkcrft/knowledge 0.1.0-alpha.2 → 0.1.0-alpha.20
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/dist/format/action-hints-formatter.d.ts.map +1 -1
- package/dist/format/action-hints-formatter.js +24 -4
- package/dist/format/knowledge-formatter.d.ts +12 -0
- package/dist/format/knowledge-formatter.d.ts.map +1 -1
- package/dist/format/knowledge-formatter.js +31 -0
- package/dist/index/relevance-score.d.ts.map +1 -1
- package/dist/index/relevance-score.js +42 -6
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-hints-formatter.d.ts","sourceRoot":"","sources":["../../src/format/action-hints-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAEnB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;
|
|
1
|
+
{"version":3,"file":"action-hints-formatter.d.ts","sourceRoot":"","sources":["../../src/format/action-hints-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAEnB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAoCD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,eAAe,EAAE,GAClC,sBAAsB,CA0CxB;AAED,MAAM,WAAW,wBAAwB;IACvC,sEAAsE;IACtE,KAAK,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC;IACrB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAgBD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,sBAAsB,EAC7B,OAAO,GAAE,wBAA6B,GACrC,MAAM,CA2DR;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAGR;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CAE3E"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { priorityWeight } from "../model/knowledge-priority.js";
|
|
2
2
|
function dedupePush(out, items, key) {
|
|
3
|
-
|
|
3
|
+
// Defensive: an entry may author an object-array hint (commands / mcpTools)
|
|
4
|
+
// as a scalar by mistake. Iterating a non-array here would walk string chars
|
|
5
|
+
// and produce garbage keys, so anything that isn't an array is ignored.
|
|
6
|
+
if (!Array.isArray(items))
|
|
4
7
|
return;
|
|
5
8
|
const seen = new Set(out.map(key));
|
|
6
9
|
for (const item of items) {
|
|
@@ -11,8 +14,24 @@ function dedupePush(out, items, key) {
|
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Coerce an authored hint value into a string array. A common authoring typo
|
|
19
|
+
* is to write a single-value field as a scalar (`preferredFlow: 'x'`) instead
|
|
20
|
+
* of an array (`preferredFlow: ['x']`). A scalar string has `.length` but no
|
|
21
|
+
* `.map`, which previously crashed the formatter (`preferredFlow.map is not a
|
|
22
|
+
* function`) and took down the whole `shrk context` entrypoint. Normalizing
|
|
23
|
+
* here preserves the authored content while guaranteeing the all-array contract
|
|
24
|
+
* of `IAggregatedActionHints`.
|
|
25
|
+
*/
|
|
26
|
+
function toHintStrings(value) {
|
|
27
|
+
if (Array.isArray(value))
|
|
28
|
+
return value.filter((v) => typeof v === 'string');
|
|
29
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
30
|
+
return [value];
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
14
33
|
function dedupeStrings(out, items) {
|
|
15
|
-
dedupePush(out, items, (s) => s);
|
|
34
|
+
dedupePush(out, toHintStrings(items), (s) => s);
|
|
16
35
|
}
|
|
17
36
|
/**
|
|
18
37
|
* Combine actionHints from a list of relevant entries into a single bundle.
|
|
@@ -41,8 +60,9 @@ export function aggregateActionHints(entries) {
|
|
|
41
60
|
continue;
|
|
42
61
|
dedupePush(out.commands, h.commands, (c) => c.command);
|
|
43
62
|
dedupePush(out.mcpTools, h.mcpTools, (m) => m.tool);
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
const flow = toHintStrings(h.preferredFlow);
|
|
64
|
+
if (!out.preferredFlow.length && flow.length) {
|
|
65
|
+
out.preferredFlow = flow;
|
|
46
66
|
out.preferredFlowSourceId = entry.id;
|
|
47
67
|
}
|
|
48
68
|
dedupeStrings(out.forbiddenActions, h.forbiddenActions);
|
|
@@ -7,5 +7,17 @@ export interface FormatEntryOptions {
|
|
|
7
7
|
maxContentChars?: number;
|
|
8
8
|
}
|
|
9
9
|
export declare function formatEntryCompact(entry: IKnowledgeEntry): string;
|
|
10
|
+
/**
|
|
11
|
+
* Project an entry to a plain JSON-serialisable object by reading each declared
|
|
12
|
+
* `IKnowledgeEntry` field by DIRECT property access.
|
|
13
|
+
*
|
|
14
|
+
* Spreading (`{ ...entry }`) copies only own-enumerable properties, so a
|
|
15
|
+
* pack-contributed entry whose fields are getters / non-enumerable /
|
|
16
|
+
* prototype-backed would serialise to `{ id, source }` only — the JSON looked
|
|
17
|
+
* "empty" while the text form (which reads fields directly) was complete.
|
|
18
|
+
* Direct access matches the text path and is robust to the entry's property
|
|
19
|
+
* descriptors. Undefined optionals drop out of `JSON.stringify` naturally.
|
|
20
|
+
*/
|
|
21
|
+
export declare function projectKnowledgeEntryForJson(entry: IKnowledgeEntry): Record<string, unknown>;
|
|
10
22
|
export declare function formatEntryFull(entry: IKnowledgeEntry, options?: FormatEntryOptions): string;
|
|
11
23
|
//# sourceMappingURL=knowledge-formatter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge-formatter.d.ts","sourceRoot":"","sources":["../../src/format/knowledge-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAGnE,MAAM,WAAW,kBAAkB;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAKjE;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CA4CR"}
|
|
1
|
+
{"version":3,"file":"knowledge-formatter.d.ts","sourceRoot":"","sources":["../../src/format/knowledge-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAGnE,MAAM,WAAW,kBAAkB;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAKjE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmB5F;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CA4CR"}
|
|
@@ -5,6 +5,37 @@ export function formatEntryCompact(entry) {
|
|
|
5
5
|
const appliesWhen = entry.appliesWhen.length ? ` appliesWhen=[${entry.appliesWhen.join(', ')}]` : '';
|
|
6
6
|
return `${entry.id} (${entry.type}, ${entry.priority}) — ${entry.title}${tags}${scope}${appliesWhen}`;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Project an entry to a plain JSON-serialisable object by reading each declared
|
|
10
|
+
* `IKnowledgeEntry` field by DIRECT property access.
|
|
11
|
+
*
|
|
12
|
+
* Spreading (`{ ...entry }`) copies only own-enumerable properties, so a
|
|
13
|
+
* pack-contributed entry whose fields are getters / non-enumerable /
|
|
14
|
+
* prototype-backed would serialise to `{ id, source }` only — the JSON looked
|
|
15
|
+
* "empty" while the text form (which reads fields directly) was complete.
|
|
16
|
+
* Direct access matches the text path and is robust to the entry's property
|
|
17
|
+
* descriptors. Undefined optionals drop out of `JSON.stringify` naturally.
|
|
18
|
+
*/
|
|
19
|
+
export function projectKnowledgeEntryForJson(entry) {
|
|
20
|
+
return {
|
|
21
|
+
id: entry.id,
|
|
22
|
+
title: entry.title,
|
|
23
|
+
type: entry.type,
|
|
24
|
+
priority: entry.priority,
|
|
25
|
+
scope: entry.scope,
|
|
26
|
+
tags: entry.tags,
|
|
27
|
+
appliesWhen: entry.appliesWhen,
|
|
28
|
+
content: entry.content,
|
|
29
|
+
summary: entry.summary,
|
|
30
|
+
examples: entry.examples,
|
|
31
|
+
related: entry.related,
|
|
32
|
+
source: entry.source,
|
|
33
|
+
metadata: entry.metadata,
|
|
34
|
+
actionHints: entry.actionHints,
|
|
35
|
+
references: entry.references,
|
|
36
|
+
anchors: entry.anchors,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
8
39
|
export function formatEntryFull(entry, options = {}) {
|
|
9
40
|
const { includeExamples = true, includeContent = true, maxContentChars } = options;
|
|
10
41
|
const lines = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relevance-score.d.ts","sourceRoot":"","sources":["../../src/index/relevance-score.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAGjF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,qBAAqB,EAAE,CAAC;CAClC;AAYD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"relevance-score.d.ts","sourceRoot":"","sources":["../../src/index/relevance-score.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAGjF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,qBAAqB,EAAE,CAAC;CAClC;AAYD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,WAAW,CAmHtF"}
|
|
@@ -11,8 +11,18 @@ const FIELD_WEIGHTS = {
|
|
|
11
11
|
export function scoreEntry(entry, query) {
|
|
12
12
|
let score = 0;
|
|
13
13
|
const reasons = [];
|
|
14
|
-
// Priority baseline always contributes
|
|
15
|
-
|
|
14
|
+
// Priority baseline always contributes so that — once an entry has *any*
|
|
15
|
+
// match reason — a foundational critical rule (e.g. architecture.layer-order)
|
|
16
|
+
// outranks a non-critical entry that merely shares a keyword. The old
|
|
17
|
+
// `/ 10` shrank Critical (weight 100) to a baseline of 10, an order of
|
|
18
|
+
// magnitude below a single lexical hit (id 80, title 50, appliesWhen 40), so
|
|
19
|
+
// critical rules were reliably buried. Use the full priority weight: Critical
|
|
20
|
+
// 100, High 70, Medium 40, Low 10 — on par with a strong lexical hit, while
|
|
21
|
+
// a *strong* multi-field lexical match (which sums well past 100) still wins.
|
|
22
|
+
// This does not surface no-reason entries: the index drops score>0 results
|
|
23
|
+
// with empty reasons, so an irrelevant critical rule never leaks in on
|
|
24
|
+
// priority alone.
|
|
25
|
+
score += priorityWeight(entry.priority);
|
|
16
26
|
// Type filter is a hard match — only score the rest if type matches when types specified.
|
|
17
27
|
const queryText = (query.query ?? '').trim().toLowerCase();
|
|
18
28
|
const queryWords = queryText
|
|
@@ -25,13 +35,39 @@ export function scoreEntry(entry, query) {
|
|
|
25
35
|
score += FIELD_WEIGHTS.id;
|
|
26
36
|
reasons.push({ field: 'id', match: queryText });
|
|
27
37
|
}
|
|
28
|
-
|
|
38
|
+
// Title / summary: an exact full-phrase hit earns the full weight; otherwise
|
|
39
|
+
// credit by the SHARE of query words present. Matching per-word — not only
|
|
40
|
+
// the whole query string — lets a semantically relevant title (e.g.
|
|
41
|
+
// "Catalog i18n overlay" for "localize product catalog translations
|
|
42
|
+
// currency") outscore an entry that merely shares one incidental tag. The
|
|
43
|
+
// old full-phrase-only check earned title/summary zero on every multi-word
|
|
44
|
+
// query, so a single off-topic tag hit could win.
|
|
45
|
+
const wordCount = Math.max(1, queryWords.length);
|
|
46
|
+
const titleLc = entry.title.toLowerCase();
|
|
47
|
+
if (titleLc.includes(queryText)) {
|
|
29
48
|
score += FIELD_WEIGHTS.title;
|
|
30
49
|
reasons.push({ field: 'title', match: queryText });
|
|
31
50
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
else {
|
|
52
|
+
const hits = queryWords.filter((w) => titleLc.includes(w));
|
|
53
|
+
if (hits.length > 0) {
|
|
54
|
+
score += FIELD_WEIGHTS.title * (hits.length / wordCount);
|
|
55
|
+
reasons.push({ field: 'title', match: hits.join(' ') });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const summaryLc = entry.summary?.toLowerCase() ?? '';
|
|
59
|
+
if (summaryLc.length > 0) {
|
|
60
|
+
if (summaryLc.includes(queryText)) {
|
|
61
|
+
score += FIELD_WEIGHTS.summary;
|
|
62
|
+
reasons.push({ field: 'summary', match: queryText });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const hits = queryWords.filter((w) => summaryLc.includes(w));
|
|
66
|
+
if (hits.length > 0) {
|
|
67
|
+
score += FIELD_WEIGHTS.summary * (hits.length / wordCount);
|
|
68
|
+
reasons.push({ field: 'summary', match: hits.join(' ') });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
35
71
|
}
|
|
36
72
|
if (entry.content.toLowerCase().includes(queryText)) {
|
|
37
73
|
score += FIELD_WEIGHTS.content;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shrkcrft/knowledge",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.20",
|
|
4
4
|
"description": "SharkCraft structured knowledge model: typed entries, index, search, loaders (TS + markdown), validation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SharkCraft contributors",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
|
-
"types": "./dist/index.d.
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@shrkcrft/core": "^0.1.0-alpha.
|
|
47
|
+
"@shrkcrft/core": "^0.1.0-alpha.20"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|