@hasna/hooks 0.3.1 → 0.3.3
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 +2 -2
- package/dist/index.js +2 -2
- 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 +23 -1
- package/hooks/hook-knowledge-context/src/hook.ts +50 -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.2",
|
|
2093
2093
|
category: "Context Management",
|
|
2094
2094
|
event: "SessionStart",
|
|
2095
2095
|
events: ["SessionStart", "UserPromptSubmit", "SubagentStart"],
|
|
@@ -4126,7 +4126,7 @@ function codewithMatcher(matcher) {
|
|
|
4126
4126
|
function codewithTimeout(name) {
|
|
4127
4127
|
switch (name) {
|
|
4128
4128
|
case "knowledge-context":
|
|
4129
|
-
return
|
|
4129
|
+
return 6;
|
|
4130
4130
|
case "session-start":
|
|
4131
4131
|
return 8;
|
|
4132
4132
|
case "pre-bash":
|
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.2",
|
|
1014
1014
|
category: "Context Management",
|
|
1015
1015
|
event: "SessionStart",
|
|
1016
1016
|
events: ["SessionStart", "UserPromptSubmit", "SubagentStart"],
|
|
@@ -1452,7 +1452,7 @@ function codewithMatcher(matcher) {
|
|
|
1452
1452
|
function codewithTimeout(name) {
|
|
1453
1453
|
switch (name) {
|
|
1454
1454
|
case "knowledge-context":
|
|
1455
|
-
return
|
|
1455
|
+
return 6;
|
|
1456
1456
|
case "session-start":
|
|
1457
1457
|
return 8;
|
|
1458
1458
|
case "pre-bash":
|
|
@@ -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.
|
|
@@ -39,7 +42,7 @@ bad stdin, or empty packs return `{"continue":true}` without context.
|
|
|
39
42
|
```bash
|
|
40
43
|
export HOOKS_KNOWLEDGE_CONTEXT_DISABLE=1 # Kill switch
|
|
41
44
|
export HOOKS_KNOWLEDGE_COMMAND=knowledge # CLI command/path
|
|
42
|
-
export HOOKS_KNOWLEDGE_TIMEOUT_MS=
|
|
45
|
+
export HOOKS_KNOWLEDGE_TIMEOUT_MS=5000 # Per-call timeout
|
|
43
46
|
export HOOKS_KNOWLEDGE_MAX_ITEMS=6 # 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
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
const baseConfig: KnowledgeContextConfig = {
|
|
15
15
|
command: "knowledge",
|
|
16
|
-
timeoutMs:
|
|
16
|
+
timeoutMs: 5000,
|
|
17
17
|
maxItems: 6,
|
|
18
18
|
maxTokens: 1200,
|
|
19
19
|
maxQueryChars: 1200,
|
|
@@ -21,6 +21,11 @@ const baseConfig: KnowledgeContextConfig = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
describe("hook-knowledge-context", () => {
|
|
24
|
+
test("defaults Knowledge CLI timeout to 5000ms and keeps env override", () => {
|
|
25
|
+
expect(getConfig({}).timeoutMs).toBe(5000);
|
|
26
|
+
expect(getConfig({ HOOKS_KNOWLEDGE_TIMEOUT_MS: "2345" }).timeoutMs).toBe(2345);
|
|
27
|
+
});
|
|
28
|
+
|
|
24
29
|
test("builds deterministic knowledge context pack args without semantic, web, or generation flags", () => {
|
|
25
30
|
const args = buildKnowledgeArgs("repo hook context", baseConfig);
|
|
26
31
|
|
|
@@ -165,4 +170,21 @@ describe("hook-knowledge-context", () => {
|
|
|
165
170
|
expect(extractContextText({ pack: { context: "direct context" } })).toBe("direct context");
|
|
166
171
|
expect(extractContextText([{ title: "One", text: "Body", uri: "knowledge://item/one" }])).toContain("Body");
|
|
167
172
|
});
|
|
173
|
+
|
|
174
|
+
test("formats citation previews as useful progressive blurbs with read commands", () => {
|
|
175
|
+
const text = extractContextText({
|
|
176
|
+
citations: [
|
|
177
|
+
{
|
|
178
|
+
id: "cite_123",
|
|
179
|
+
source_ref: "knowledge://item/k_example",
|
|
180
|
+
quote_preview: "A short preview that tells the agent why this Knowledge item might matter.",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(text).toContain("Knowledge item k_example");
|
|
186
|
+
expect(text).toContain("cite_123");
|
|
187
|
+
expect(text).toContain("A short preview");
|
|
188
|
+
expect(text).toContain("read: knowledge get --id k_example --json");
|
|
189
|
+
});
|
|
168
190
|
});
|
|
@@ -55,7 +55,7 @@ export type KnowledgeExecutor = (
|
|
|
55
55
|
config: KnowledgeContextConfig
|
|
56
56
|
) => Promise<KnowledgeExecResult>;
|
|
57
57
|
|
|
58
|
-
const DEFAULT_TIMEOUT_MS =
|
|
58
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
59
59
|
const DEFAULT_MAX_ITEMS = 6;
|
|
60
60
|
const DEFAULT_MAX_TOKENS = 1200;
|
|
61
61
|
const DEFAULT_MAX_QUERY_CHARS = 1200;
|
|
@@ -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,35 @@ 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 ? `Knowledge item ${itemId}` : citationId || `item ${index + 1}`);
|
|
282
|
+
const body = firstString(obj, [
|
|
283
|
+
"summary",
|
|
284
|
+
"text",
|
|
285
|
+
"content",
|
|
286
|
+
"snippet",
|
|
287
|
+
"preview",
|
|
288
|
+
"quote_preview",
|
|
289
|
+
"quotePreview",
|
|
290
|
+
"excerpt",
|
|
291
|
+
"body",
|
|
292
|
+
"description",
|
|
293
|
+
]);
|
|
258
294
|
if (!body && !source) continue;
|
|
259
|
-
|
|
260
|
-
|
|
295
|
+
|
|
296
|
+
const suffixParts = [
|
|
297
|
+
citationId && citationId !== title ? citationId : null,
|
|
298
|
+
source ? `source: ${truncate(source, 180)}` : null,
|
|
299
|
+
].filter(Boolean);
|
|
300
|
+
const suffix = suffixParts.length > 0 ? ` (${suffixParts.join("; ")})` : "";
|
|
301
|
+
lines.push(`- ${truncate(title, 160)}${suffix}${body ? `: ${truncate(compactText(body), 520)}` : ""}`);
|
|
302
|
+
if (itemId) {
|
|
303
|
+
lines.push(` read: knowledge get --id ${itemId} --json`);
|
|
304
|
+
}
|
|
261
305
|
}
|
|
262
306
|
|
|
263
307
|
return lines.length > 0 ? lines.join("\n") : null;
|
package/package.json
CHANGED