@hasna/hooks 0.3.2 → 0.3.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/bin/index.js +1 -1
- package/dist/index.js +1 -1
- package/hooks/hook-knowledge-context/README.md +5 -2
- package/hooks/hook-knowledge-context/package.json +1 -1
- package/hooks/hook-knowledge-context/src/hook.test.ts +21 -2
- package/hooks/hook-knowledge-context/src/hook.ts +52 -6
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -2089,7 +2089,7 @@ var init_registry = __esm(() => {
|
|
|
2089
2089
|
name: "knowledge-context",
|
|
2090
2090
|
displayName: "Knowledge Context",
|
|
2091
2091
|
description: "Injects deterministic Knowledge context packs into Codewith SessionStart, UserPromptSubmit, and SubagentStart",
|
|
2092
|
-
version: "0.1.
|
|
2092
|
+
version: "0.1.3",
|
|
2093
2093
|
category: "Context Management",
|
|
2094
2094
|
event: "SessionStart",
|
|
2095
2095
|
events: ["SessionStart", "UserPromptSubmit", "SubagentStart"],
|
package/dist/index.js
CHANGED
|
@@ -1010,7 +1010,7 @@ var HOOKS = [
|
|
|
1010
1010
|
name: "knowledge-context",
|
|
1011
1011
|
displayName: "Knowledge Context",
|
|
1012
1012
|
description: "Injects deterministic Knowledge context packs into Codewith SessionStart, UserPromptSubmit, and SubagentStart",
|
|
1013
|
-
version: "0.1.
|
|
1013
|
+
version: "0.1.3",
|
|
1014
1014
|
category: "Context Management",
|
|
1015
1015
|
event: "SessionStart",
|
|
1016
1016
|
events: ["SessionStart", "UserPromptSubmit", "SubagentStart"],
|
|
@@ -29,7 +29,10 @@ The hook intentionally does not pass `--semantic`, does not call web search,
|
|
|
29
29
|
does not call ask/build/generate flows, and does not crawl raw stores directly.
|
|
30
30
|
When Knowledge returns a nonempty context pack, the hook redacts credential-like
|
|
31
31
|
text from that pack and emits Codewith-native
|
|
32
|
-
`hookSpecificOutput.additionalContext` with the same event name.
|
|
32
|
+
`hookSpecificOutput.additionalContext` with the same event name. Citation-only
|
|
33
|
+
packs are expanded into progressive context lines with a bounded preview and a
|
|
34
|
+
`knowledge get --id ... --json` read hint so agents can decide which full items
|
|
35
|
+
are worth opening.
|
|
33
36
|
|
|
34
37
|
All failures are fail-open: missing CLI, timeout, nonzero exit, malformed JSON,
|
|
35
38
|
bad stdin, or empty packs return `{"continue":true}` without context.
|
|
@@ -40,7 +43,7 @@ bad stdin, or empty packs return `{"continue":true}` without context.
|
|
|
40
43
|
export HOOKS_KNOWLEDGE_CONTEXT_DISABLE=1 # Kill switch
|
|
41
44
|
export HOOKS_KNOWLEDGE_COMMAND=knowledge # CLI command/path
|
|
42
45
|
export HOOKS_KNOWLEDGE_TIMEOUT_MS=5000 # Per-call timeout
|
|
43
|
-
export HOOKS_KNOWLEDGE_MAX_ITEMS=
|
|
46
|
+
export HOOKS_KNOWLEDGE_MAX_ITEMS=3 # Context pack item budget
|
|
44
47
|
export HOOKS_KNOWLEDGE_MAX_TOKENS=1200 # Context pack token budget
|
|
45
48
|
export HOOKS_KNOWLEDGE_MAX_QUERY_CHARS=1200 # Redacted query bound
|
|
46
49
|
export HOOKS_KNOWLEDGE_MAX_OUTPUT_CHARS=8000
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
const baseConfig: KnowledgeContextConfig = {
|
|
15
15
|
command: "knowledge",
|
|
16
16
|
timeoutMs: 5000,
|
|
17
|
-
maxItems:
|
|
17
|
+
maxItems: 3,
|
|
18
18
|
maxTokens: 1200,
|
|
19
19
|
maxQueryChars: 1200,
|
|
20
20
|
maxOutputChars: 8000,
|
|
@@ -23,6 +23,7 @@ const baseConfig: KnowledgeContextConfig = {
|
|
|
23
23
|
describe("hook-knowledge-context", () => {
|
|
24
24
|
test("defaults Knowledge CLI timeout to 5000ms and keeps env override", () => {
|
|
25
25
|
expect(getConfig({}).timeoutMs).toBe(5000);
|
|
26
|
+
expect(getConfig({}).maxItems).toBe(3);
|
|
26
27
|
expect(getConfig({ HOOKS_KNOWLEDGE_TIMEOUT_MS: "2345" }).timeoutMs).toBe(2345);
|
|
27
28
|
});
|
|
28
29
|
|
|
@@ -36,7 +37,7 @@ describe("hook-knowledge-context", () => {
|
|
|
36
37
|
"--from",
|
|
37
38
|
"search",
|
|
38
39
|
"--max-items",
|
|
39
|
-
"
|
|
40
|
+
"3",
|
|
40
41
|
"--max-tokens",
|
|
41
42
|
"1200",
|
|
42
43
|
"--json",
|
|
@@ -170,4 +171,22 @@ describe("hook-knowledge-context", () => {
|
|
|
170
171
|
expect(extractContextText({ pack: { context: "direct context" } })).toBe("direct context");
|
|
171
172
|
expect(extractContextText([{ title: "One", text: "Body", uri: "knowledge://item/one" }])).toContain("Body");
|
|
172
173
|
});
|
|
174
|
+
|
|
175
|
+
test("formats citation previews as useful progressive blurbs with compact open commands", () => {
|
|
176
|
+
const text = extractContextText({
|
|
177
|
+
citations: [
|
|
178
|
+
{
|
|
179
|
+
id: "cite_123",
|
|
180
|
+
source_ref: "knowledge://item/k_example",
|
|
181
|
+
quote_preview: "A short preview that tells the agent why this Knowledge item might matter.",
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(text).toContain("- k_example (cite_123): A short preview");
|
|
187
|
+
expect(text).toContain("cite_123");
|
|
188
|
+
expect(text).toContain("A short preview");
|
|
189
|
+
expect(text).toContain("open: knowledge get --id k_example --json");
|
|
190
|
+
expect(text).not.toContain("source: knowledge://item/k_example");
|
|
191
|
+
});
|
|
173
192
|
});
|
|
@@ -56,7 +56,7 @@ export type KnowledgeExecutor = (
|
|
|
56
56
|
) => Promise<KnowledgeExecResult>;
|
|
57
57
|
|
|
58
58
|
const DEFAULT_TIMEOUT_MS = 5000;
|
|
59
|
-
const DEFAULT_MAX_ITEMS =
|
|
59
|
+
const DEFAULT_MAX_ITEMS = 3;
|
|
60
60
|
const DEFAULT_MAX_TOKENS = 1200;
|
|
61
61
|
const DEFAULT_MAX_QUERY_CHARS = 1200;
|
|
62
62
|
const DEFAULT_MAX_OUTPUT_CHARS = 8000;
|
|
@@ -227,6 +227,18 @@ function firstString(obj: Record<string, unknown>, keys: string[]): string | nul
|
|
|
227
227
|
return null;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
function firstSourceString(obj: Record<string, unknown>): string | null {
|
|
231
|
+
const direct = firstString(obj, ["source_ref", "source_uri", "source", "uri", "ref", "url", "citation"]);
|
|
232
|
+
if (direct) return direct;
|
|
233
|
+
|
|
234
|
+
const source = obj.source;
|
|
235
|
+
if (source && typeof source === "object") {
|
|
236
|
+
return firstString(source as Record<string, unknown>, ["uri", "ref", "url", "id"]);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
230
242
|
function firstArray(obj: Record<string, unknown>, keys: string[]): unknown[] | null {
|
|
231
243
|
for (const key of keys) {
|
|
232
244
|
const value = obj[key];
|
|
@@ -241,6 +253,15 @@ export function truncate(text: string, max: number): string {
|
|
|
241
253
|
return `${text.slice(0, max - 3)}...`;
|
|
242
254
|
}
|
|
243
255
|
|
|
256
|
+
function compactText(text: string): string {
|
|
257
|
+
return text.replace(/\s+/g, " ").trim();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function knowledgeItemId(source: string | null): string | null {
|
|
261
|
+
const match = source?.match(/^knowledge:\/\/item\/([^/?#\s]+)$/);
|
|
262
|
+
return match?.[1] ?? null;
|
|
263
|
+
}
|
|
264
|
+
|
|
244
265
|
function formatPackItems(items: unknown[]): string | null {
|
|
245
266
|
const lines: string[] = [];
|
|
246
267
|
|
|
@@ -252,12 +273,37 @@ function formatPackItems(items: unknown[]): string | null {
|
|
|
252
273
|
if (!item || typeof item !== "object") continue;
|
|
253
274
|
|
|
254
275
|
const obj = item as Record<string, unknown>;
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
const
|
|
276
|
+
const citationId = firstString(obj, ["id", "citation_id", "citationId"]);
|
|
277
|
+
const source = firstSourceString(obj);
|
|
278
|
+
const itemId = knowledgeItemId(source);
|
|
279
|
+
const title =
|
|
280
|
+
firstString(obj, ["title", "name"]) ||
|
|
281
|
+
itemId ||
|
|
282
|
+
citationId ||
|
|
283
|
+
`item ${index + 1}`;
|
|
284
|
+
const body = firstString(obj, [
|
|
285
|
+
"summary",
|
|
286
|
+
"text",
|
|
287
|
+
"content",
|
|
288
|
+
"snippet",
|
|
289
|
+
"preview",
|
|
290
|
+
"quote_preview",
|
|
291
|
+
"quotePreview",
|
|
292
|
+
"excerpt",
|
|
293
|
+
"body",
|
|
294
|
+
"description",
|
|
295
|
+
]);
|
|
258
296
|
if (!body && !source) continue;
|
|
259
|
-
|
|
260
|
-
|
|
297
|
+
|
|
298
|
+
const suffixParts = [
|
|
299
|
+
citationId && citationId !== title ? citationId : null,
|
|
300
|
+
source && !itemId ? `source: ${truncate(source, 180)}` : null,
|
|
301
|
+
].filter(Boolean);
|
|
302
|
+
const suffix = suffixParts.length > 0 ? ` (${suffixParts.join("; ")})` : "";
|
|
303
|
+
lines.push(`- ${truncate(title, 160)}${suffix}${body ? `: ${truncate(compactText(body), 520)}` : ""}`);
|
|
304
|
+
if (itemId) {
|
|
305
|
+
lines.push(` open: knowledge get --id ${itemId} --json`);
|
|
306
|
+
}
|
|
261
307
|
}
|
|
262
308
|
|
|
263
309
|
return lines.length > 0 ? lines.join("\n") : null;
|
package/package.json
CHANGED