From eb6d1ee9bb4af320d33cb19eb49b2dd278836d7b Mon Sep 17 00:00:00 2001 From: Tony Casey Date: Sun, 15 Feb 2026 00:28:13 +0000 Subject: [PATCH 1/2] feat: add IIntentExtractor interface and config fields (GIT-97) - Add IIntentExtractor interface for keyword extraction from prompts - Extend IPromptSubmitConfig with extractIntent, intentTimeout, minWords, and memoryLimit fields - Update config defaults: enable promptSubmit by default, extractIntent=true, intentTimeout=3000ms, minWords=5, memoryLimit=20 - Update config tests for new defaults Co-Authored-By: Claude Opus 4.5 AI-Agent: Claude-Code/2.1.42 AI-Model: claude-opus-4-5-20251101 AI-Decision: add IIntentExtractor interface and config fields (GIT-97). - Add IIntentExtractor interface for keyword extraction from prompts AI-Confidence: medium AI-Tags: domain, hooks, utils, tests, unit AI-Lifecycle: project AI-Memory-Id: c64a5417 AI-Source: heuristic --- src/domain/interfaces/IHookConfig.ts | 10 ++++- src/domain/interfaces/IIntentExtractor.ts | 51 +++++++++++++++++++++++ src/hooks/utils/config.ts | 10 ++++- tests/unit/hooks/utils/config.test.ts | 6 ++- 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/domain/interfaces/IIntentExtractor.ts diff --git a/src/domain/interfaces/IHookConfig.ts b/src/domain/interfaces/IHookConfig.ts index 49c688b5..751862f5 100644 --- a/src/domain/interfaces/IHookConfig.ts +++ b/src/domain/interfaces/IHookConfig.ts @@ -27,8 +27,16 @@ export interface IPromptSubmitConfig { readonly enabled: boolean; /** Whether to record prompts as memories. Reserved — not yet wired to handler. */ readonly recordPrompts: boolean; - /** Whether to surface context memories per prompt. Reserved — not yet wired to handler. */ + /** Whether to surface context memories per prompt. */ readonly surfaceContext: boolean; + /** Enable LLM-based intent extraction for smarter memory retrieval. */ + readonly extractIntent: boolean; + /** Timeout in ms for intent extraction LLM call. Default: 3000. */ + readonly intentTimeout: number; + /** Minimum word count to trigger intent extraction. Default: 5. */ + readonly minWords: number; + /** Maximum memories to return. Default: 20. */ + readonly memoryLimit: number; } export interface IPostCommitConfig { diff --git a/src/domain/interfaces/IIntentExtractor.ts b/src/domain/interfaces/IIntentExtractor.ts new file mode 100644 index 00000000..e4d8d864 --- /dev/null +++ b/src/domain/interfaces/IIntentExtractor.ts @@ -0,0 +1,51 @@ +/** + * IIntentExtractor + * + * Interface for extracting searchable keywords from user prompts. + * Used by the prompt-submit hook to query memories with intent-based filtering. + */ + +/** + * Input to the intent extraction service. + */ +export interface IIntentExtractorInput { + /** The user's prompt text. */ + readonly prompt: string; +} + +/** + * Result of intent extraction. + */ +export interface IIntentExtractorResult { + /** + * Extracted keywords for memory search. + * null if extraction was skipped or no keywords found. + */ + readonly intent: string | null; + /** Whether extraction was skipped. */ + readonly skipped: boolean; + /** + * Reason for skipping. + * - 'too_short': Prompt has fewer words than minWords threshold. + * - 'confirmation': Prompt is a simple confirmation (yes/no/ok/etc). + * - 'no_llm': No LLM client available. + * - 'llm_skip': LLM returned SKIP (no extractable keywords). + * - 'timeout': LLM call timed out. + * - 'error': LLM call failed with error. + */ + readonly reason?: 'too_short' | 'confirmation' | 'no_llm' | 'llm_skip' | 'timeout' | 'error'; +} + +/** + * Service for extracting searchable keywords from user prompts. + */ +export interface IIntentExtractor { + /** + * Extract searchable keywords from a user prompt. + * Returns keywords suitable for memory search, or null if skipped. + * + * @param input - The prompt to extract intent from. + * @returns Extracted keywords or skip indicator. + */ + extract(input: IIntentExtractorInput): Promise; +} diff --git a/src/hooks/utils/config.ts b/src/hooks/utils/config.ts index 8096a201..85d13bad 100644 --- a/src/hooks/utils/config.ts +++ b/src/hooks/utils/config.ts @@ -42,7 +42,15 @@ const DEFAULTS: IHookConfig = { enabled: true, sessionStart: { enabled: true, memoryLimit: 20 }, sessionStop: { enabled: true, autoExtract: true, threshold: 3 }, - promptSubmit: { enabled: false, recordPrompts: false, surfaceContext: true }, + promptSubmit: { + enabled: true, + recordPrompts: false, + surfaceContext: true, + extractIntent: true, + intentTimeout: 3000, + minWords: 5, + memoryLimit: 20, + }, postCommit: { enabled: true }, commitMsg: { enabled: true, diff --git a/tests/unit/hooks/utils/config.test.ts b/tests/unit/hooks/utils/config.test.ts index c487565f..05b7f8de 100644 --- a/tests/unit/hooks/utils/config.test.ts +++ b/tests/unit/hooks/utils/config.test.ts @@ -74,9 +74,13 @@ describe('config', () => { assert.equal(config.hooks.sessionStop.enabled, true); assert.equal(config.hooks.sessionStop.autoExtract, true); assert.equal(config.hooks.sessionStop.threshold, 3); - assert.equal(config.hooks.promptSubmit.enabled, false); + assert.equal(config.hooks.promptSubmit.enabled, true); assert.equal(config.hooks.promptSubmit.recordPrompts, false); assert.equal(config.hooks.promptSubmit.surfaceContext, true); + assert.equal(config.hooks.promptSubmit.extractIntent, true); + assert.equal(config.hooks.promptSubmit.intentTimeout, 3000); + assert.equal(config.hooks.promptSubmit.minWords, 5); + assert.equal(config.hooks.promptSubmit.memoryLimit, 20); }); it('should read and merge config from .git-mem/.git-mem.yaml', () => { From 0021db408af829458f284a4343ab5c64b5079fac Mon Sep 17 00:00:00 2001 From: Tony Casey Date: Sun, 15 Feb 2026 01:03:26 +0000 Subject: [PATCH 2/2] refactor: use discriminated union for IIntentExtractorResult Address review feedback: - Change IIntentExtractorResult from interface to discriminated union for better type safety (skipped=true implies intent=null and reason defined) - Add IntentSkipReason type alias - Add hook timeout constraint to intentTimeout doc comment Co-Authored-By: Claude Opus 4.5 AI-Agent: Claude-Code/2.1.42 AI-Model: claude-opus-4-5-20251101 AI-Convention: use discriminated union for IIntentExtractorResult. Address review feedback: AI-Confidence: low AI-Tags: domain, typescript AI-Lifecycle: project AI-Memory-Id: e04f84fb AI-Source: heuristic --- src/domain/interfaces/IHookConfig.ts | 2 +- src/domain/interfaces/IIntentExtractor.ts | 59 +++++++++++++++-------- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/domain/interfaces/IHookConfig.ts b/src/domain/interfaces/IHookConfig.ts index 751862f5..13665072 100644 --- a/src/domain/interfaces/IHookConfig.ts +++ b/src/domain/interfaces/IHookConfig.ts @@ -31,7 +31,7 @@ export interface IPromptSubmitConfig { readonly surfaceContext: boolean; /** Enable LLM-based intent extraction for smarter memory retrieval. */ readonly extractIntent: boolean; - /** Timeout in ms for intent extraction LLM call. Default: 3000. */ + /** Timeout in ms for intent extraction LLM call. Default: 3000. Must be under hook timeout (10s). */ readonly intentTimeout: number; /** Minimum word count to trigger intent extraction. Default: 5. */ readonly minWords: number; diff --git a/src/domain/interfaces/IIntentExtractor.ts b/src/domain/interfaces/IIntentExtractor.ts index e4d8d864..c583ce5f 100644 --- a/src/domain/interfaces/IIntentExtractor.ts +++ b/src/domain/interfaces/IIntentExtractor.ts @@ -13,28 +13,49 @@ export interface IIntentExtractorInput { readonly prompt: string; } +/** + * Reason for skipping intent extraction. + */ +export type IntentSkipReason = + | 'too_short' + | 'confirmation' + | 'no_llm' + | 'llm_skip' + | 'timeout' + | 'error'; + /** * Result of intent extraction. + * + * This is a discriminated union keyed by `skipped`: + * - When `skipped` is `true`, `intent` is always `null` and `reason` is defined. + * - When `skipped` is `false`, `intent` may be a string, and `reason` is undefined. */ -export interface IIntentExtractorResult { - /** - * Extracted keywords for memory search. - * null if extraction was skipped or no keywords found. - */ - readonly intent: string | null; - /** Whether extraction was skipped. */ - readonly skipped: boolean; - /** - * Reason for skipping. - * - 'too_short': Prompt has fewer words than minWords threshold. - * - 'confirmation': Prompt is a simple confirmation (yes/no/ok/etc). - * - 'no_llm': No LLM client available. - * - 'llm_skip': LLM returned SKIP (no extractable keywords). - * - 'timeout': LLM call timed out. - * - 'error': LLM call failed with error. - */ - readonly reason?: 'too_short' | 'confirmation' | 'no_llm' | 'llm_skip' | 'timeout' | 'error'; -} +export type IIntentExtractorResult = + | { + /** Whether extraction was skipped. */ + readonly skipped: true; + /** Always null when extraction is skipped. */ + readonly intent: null; + /** + * Reason for skipping. + * - 'too_short': Prompt has fewer words than minWords threshold. + * - 'confirmation': Prompt is a simple confirmation (yes/no/ok/etc). + * - 'no_llm': No LLM client available. + * - 'llm_skip': LLM returned SKIP (no extractable keywords). + * - 'timeout': LLM call timed out. + * - 'error': LLM call failed with error. + */ + readonly reason: IntentSkipReason; + } + | { + /** Whether extraction was skipped. */ + readonly skipped: false; + /** Extracted keywords for memory search. */ + readonly intent: string; + /** Reason is not present when extraction succeeded. */ + readonly reason?: undefined; + }; /** * Service for extracting searchable keywords from user prompts.