@hasna/hook-knowledge-context 0.1.5 → 0.1.6

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 CHANGED
@@ -34,7 +34,9 @@ text from that pack and emits Codewith-native
34
34
  `hookSpecificOutput.additionalContext` with the same event name. Citation-only
35
35
  packs are expanded into progressive context lines with bounded previews and a
36
36
  single `knowledge get --id <item_id> --json` hint so agents can decide which
37
- full items are worth opening.
37
+ full items are worth opening. Items that mark themselves as
38
+ historical/reference-only or not suitable for auto-loading are filtered out by
39
+ default, because they are not useful ambient context for normal agent work.
38
40
 
39
41
  Example injected context:
40
42
 
@@ -55,7 +57,8 @@ bad stdin, or empty packs return `{"continue":true}` without context.
55
57
  export HOOKS_KNOWLEDGE_CONTEXT_DISABLE=1 # Kill switch
56
58
  export HOOKS_KNOWLEDGE_COMMAND=knowledge # CLI command/path
57
59
  export HOOKS_KNOWLEDGE_TIMEOUT_MS=5000 # Per-call timeout
58
- export HOOKS_KNOWLEDGE_MAX_ITEMS=3 # Context pack item budget
60
+ export HOOKS_KNOWLEDGE_MAX_ITEMS=3 # Rendered item budget
61
+ export HOOKS_KNOWLEDGE_CANDIDATE_ITEMS=12 # Internal candidate budget before filtering
59
62
  export HOOKS_KNOWLEDGE_MAX_TOKENS=1200 # Context pack token budget
60
63
  export HOOKS_KNOWLEDGE_REQUIRE_HIGH_SIGNAL=1
61
64
  export HOOKS_KNOWLEDGE_MIN_PROMPT_CHARS=6
package/dist/hook.d.ts CHANGED
@@ -31,6 +31,7 @@ export interface KnowledgeContextConfig {
31
31
  command: string;
32
32
  timeoutMs: number;
33
33
  maxItems: number;
34
+ candidateItems: number;
34
35
  maxTokens: number;
35
36
  minPromptChars: number;
36
37
  minSignalScore: number;
@@ -54,7 +55,11 @@ export declare function buildQuery(input: HookInput, config: KnowledgeContextCon
54
55
  export declare function buildKnowledgeArgs(query: string, config: KnowledgeContextConfig): string[];
55
56
  export declare function spawnKnowledgeContextPack(query: string, config: KnowledgeContextConfig): Promise<KnowledgeExecResult>;
56
57
  export declare function truncate(text: string, max: number): string;
57
- export declare function extractContextText(pack: unknown): string | null;
58
+ interface ExtractContextOptions {
59
+ maxItems?: number;
60
+ includeHistorical?: boolean;
61
+ }
62
+ export declare function extractContextText(pack: unknown, options?: ExtractContextOptions): string | null;
58
63
  export declare function formatAdditionalContext(event: KnowledgeContextEvent, context: string, config: KnowledgeContextConfig): string;
59
64
  export declare function buildHookOutput(input: HookInput | null, executor?: KnowledgeExecutor, env?: NodeJS.ProcessEnv): Promise<HookOutput>;
60
65
  export declare function run(): Promise<void>;
package/dist/hook.js CHANGED
@@ -41,10 +41,12 @@ function commandFromEnv(raw) {
41
41
  return "knowledge";
42
42
  }
43
43
  function getConfig(env = process.env) {
44
+ const maxItems = boundedNumber(env.HOOKS_KNOWLEDGE_MAX_ITEMS, DEFAULT_MAX_ITEMS, 1, 20);
44
45
  return {
45
46
  command: commandFromEnv(env.HOOKS_KNOWLEDGE_COMMAND),
46
47
  timeoutMs: boundedNumber(env.HOOKS_KNOWLEDGE_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, 100, 1e4),
47
- maxItems: boundedNumber(env.HOOKS_KNOWLEDGE_MAX_ITEMS, DEFAULT_MAX_ITEMS, 1, 20),
48
+ maxItems,
49
+ candidateItems: boundedNumber(env.HOOKS_KNOWLEDGE_CANDIDATE_ITEMS, Math.min(20, Math.max(maxItems, maxItems * 4)), maxItems, 20),
48
50
  maxTokens: boundedNumber(env.HOOKS_KNOWLEDGE_MAX_TOKENS, DEFAULT_MAX_TOKENS, 100, 8000),
49
51
  minPromptChars: boundedNumber(env.HOOKS_KNOWLEDGE_MIN_PROMPT_CHARS, DEFAULT_MIN_PROMPT_CHARS, 0, 4000),
50
52
  minSignalScore: boundedNumber(env.HOOKS_KNOWLEDGE_MIN_SIGNAL_SCORE, DEFAULT_MIN_SIGNAL_SCORE, 1, 10),
@@ -147,7 +149,7 @@ function buildKnowledgeArgs(query, config) {
147
149
  "--from",
148
150
  "search",
149
151
  "--max-items",
150
- String(config.maxItems),
152
+ String(config.candidateItems),
151
153
  "--max-tokens",
152
154
  String(config.maxTokens),
153
155
  "--json"
@@ -232,13 +234,24 @@ function knowledgeItemId(source) {
232
234
  const legacyPath = source?.match(/(?:^|\/)(k_[A-Za-z0-9]+_[A-Za-z0-9]+)(?:[?#].*)?$/);
233
235
  return legacyPath?.[1] ?? null;
234
236
  }
235
- function formatPackItems(items) {
237
+ var NON_AUTOLOADABLE_HISTORY_RE = /\b(historical\/reference only|historical reference only|should not be auto-?loaded|do not auto-?load|not an active startup instruction|brain orchestration startup files)\b/i;
238
+ var ARCHIVED_STARTUP_RE = /^Archived on \d{4}-\d{2}-\d{2}\b.*\b(startup context|startup files|CLAUDE\.md)\b/i;
239
+ function shouldSuppressKnowledgeItem(text, options) {
240
+ if (options.includeHistorical)
241
+ return false;
242
+ const compact = compactText(text);
243
+ return NON_AUTOLOADABLE_HISTORY_RE.test(compact) || ARCHIVED_STARTUP_RE.test(compact);
244
+ }
245
+ function formatPackItems(items, options = {}) {
236
246
  const lines = [];
237
247
  let hasKnowledgeItems = false;
248
+ const maxItems = options.maxItems ?? DEFAULT_MAX_ITEMS;
238
249
  for (const [index, item] of items.entries()) {
250
+ if (lines.length >= maxItems)
251
+ break;
239
252
  if (typeof item === "string" && item.trim()) {
240
253
  const text = cleanStringItem(item);
241
- if (text)
254
+ if (text && !shouldSuppressKnowledgeItem(text, options))
242
255
  lines.push(`- ${truncate(text, 900)}`);
243
256
  continue;
244
257
  }
@@ -263,6 +276,9 @@ function formatPackItems(items) {
263
276
  ]);
264
277
  if (!body && !source)
265
278
  continue;
279
+ const itemText = [title, body, source].filter(Boolean).join(" ");
280
+ if (shouldSuppressKnowledgeItem(itemText, options))
281
+ continue;
266
282
  if (itemId) {
267
283
  hasKnowledgeItems = true;
268
284
  const labelParts = [`item_id=${itemId}`];
@@ -291,12 +307,19 @@ function formatPackItems(items) {
291
307
  ].join(`
292
308
  `);
293
309
  }
294
- function extractContextText(pack) {
310
+ function shouldIncludeHistoricalItems(input) {
311
+ const prompt = `${stringField(input, "prompt")} ${stringField(input, "user_prompt")}`;
312
+ return /\b(archive|archived|historical|history|reference-only|reference only|old startup|startup files|CLAUDE\.md)\b/i.test(prompt);
313
+ }
314
+ function extractContextText(pack, options = {}) {
295
315
  if (typeof pack === "string") {
296
- return cleanLegacyEmptyBullet(pack.trim()) || null;
316
+ const text = cleanLegacyEmptyBullet(pack.trim());
317
+ if (!text || shouldSuppressKnowledgeItem(text, options))
318
+ return null;
319
+ return text;
297
320
  }
298
321
  if (Array.isArray(pack)) {
299
- return formatPackItems(pack);
322
+ return formatPackItems(pack, options);
300
323
  }
301
324
  if (!pack || typeof pack !== "object") {
302
325
  return null;
@@ -304,15 +327,15 @@ function extractContextText(pack) {
304
327
  const obj = pack;
305
328
  const direct = firstString(obj, ["additionalContext", "context", "markdown", "content", "text", "summary"]);
306
329
  if (direct)
307
- return direct;
330
+ return shouldSuppressKnowledgeItem(direct, options) ? null : direct;
308
331
  for (const key of ["pack", "contextPack", "context_pack", "data", "result"]) {
309
332
  const nested = obj[key];
310
- const text = extractContextText(nested);
333
+ const text = extractContextText(nested, options);
311
334
  if (text)
312
335
  return text;
313
336
  }
314
337
  const items = firstArray(obj, ["items", "results", "sources", "citations"]);
315
- return items ? formatPackItems(items) : null;
338
+ return items ? formatPackItems(items, options) : null;
316
339
  }
317
340
  function formatAdditionalContext(event, context, config) {
318
341
  const header = `[hook-knowledge-context] Knowledge matches (${event}; top ${config.maxItems}, deterministic search):`;
@@ -346,7 +369,10 @@ async function buildHookOutput(input, executor = spawnKnowledgeContextPack, env
346
369
  return { continue: true };
347
370
  }
348
371
  const parsed = JSON.parse(result.stdout);
349
- const context = extractContextText(parsed);
372
+ const context = extractContextText(parsed, {
373
+ maxItems: config.maxItems,
374
+ includeHistorical: shouldIncludeHistoricalItems(input)
375
+ });
350
376
  if (!context) {
351
377
  return { continue: true };
352
378
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/hook-knowledge-context",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Codewith hook that injects deterministic Knowledge context packs into lifecycle events",
5
5
  "type": "module",
6
6
  "main": "./dist/hook.js",