stravinsky 0.2.38__py3-none-any.whl → 0.2.52__py3-none-any.whl

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.

Potentially problematic release.


This version of stravinsky might be problematic. Click here for more details.

@@ -4,7 +4,7 @@ Preemptive Context Compaction Hook.
4
4
  Proactively compresses context BEFORE hitting limits by:
5
5
  - Tracking estimated token usage
6
6
  - Triggering compaction at 70% capacity (not waiting for errors)
7
- - Using DCP -> Truncate -> Summarize pipeline
7
+ - Using DCP -> Truncate -> Summarize pipeline with gemini-3-flash
8
8
  - Registered as pre_model_invoke hook
9
9
  """
10
10
 
@@ -14,6 +14,9 @@ from typing import Any, Dict, Optional
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
17
+ # Flag to prevent recursive summarization calls
18
+ _in_summarization = False
19
+
17
20
  # Token estimation constants
18
21
  CHARS_PER_TOKEN = 4 # Rough estimate for English text
19
22
  MAX_CONTEXT_TOKENS = 200000 # Claude's context window
@@ -112,6 +115,60 @@ CRITICAL_WARNING = """
112
115
  > 3. Reference TASK_STATE.md in new session for continuity
113
116
  """
114
117
 
118
+ SUMMARIZATION_PROMPT = """Summarize the following context concisely while preserving:
119
+ 1. Key technical decisions and their rationale
120
+ 2. Important code patterns and file paths mentioned
121
+ 3. Current task state and pending items
122
+ 4. Any errors or warnings that need attention
123
+
124
+ Keep the summary under 2000 characters. Use bullet points for clarity.
125
+
126
+ CONTEXT TO SUMMARIZE:
127
+ {content}"""
128
+
129
+
130
+ async def summarize_with_gemini(token_store: Any, content: str) -> str:
131
+ """
132
+ Use gemini-3-flash to summarize context for compaction.
133
+
134
+ Args:
135
+ token_store: Token store for Gemini authentication
136
+ content: The content to summarize
137
+
138
+ Returns:
139
+ Summarized content or original if summarization fails
140
+ """
141
+ global _in_summarization
142
+
143
+ if not token_store:
144
+ logger.warning("[PreemptiveCompaction] No token_store available, skipping summarization")
145
+ return content
146
+
147
+ try:
148
+ # Import here to avoid circular imports
149
+ from mcp_bridge.tools.model_invoke import invoke_gemini
150
+
151
+ _in_summarization = True
152
+
153
+ prompt = SUMMARIZATION_PROMPT.format(content=content[:50000]) # Limit input size
154
+
155
+ summary = await invoke_gemini(
156
+ token_store=token_store,
157
+ prompt=prompt,
158
+ model="gemini-3-flash",
159
+ max_tokens=2000,
160
+ temperature=0.3,
161
+ )
162
+
163
+ logger.info(f"[PreemptiveCompaction] Summarized {len(content)} chars -> {len(summary)} chars")
164
+ return summary
165
+
166
+ except Exception as e:
167
+ logger.error(f"[PreemptiveCompaction] Summarization failed: {e}")
168
+ return content # Fall back to original content
169
+ finally:
170
+ _in_summarization = False
171
+
115
172
 
116
173
  async def preemptive_compaction_hook(params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
117
174
  """
@@ -119,11 +176,18 @@ async def preemptive_compaction_hook(params: Dict[str, Any]) -> Optional[Dict[st
119
176
 
120
177
  Uses a multi-tier strategy:
121
178
  - Below 70%: No action
122
- - 70-85%: Apply DCP truncation with notice
123
- - Above 85%: Apply aggressive truncation with critical warning
179
+ - 70-85%: Apply DCP truncation + gemini-3-flash summarization
180
+ - Above 85%: Apply aggressive truncation + gemini-3-flash summarization
124
181
  """
182
+ global _in_summarization
183
+
184
+ # Prevent recursive calls (when this hook triggers summarization via gemini)
185
+ if _in_summarization:
186
+ return None
187
+
125
188
  prompt = params.get("prompt", "")
126
189
  prompt_length = len(prompt)
190
+ token_store = params.get("token_store") # May be None if not provided
127
191
 
128
192
  # Skip if already optimized recently
129
193
  if "[PREEMPTIVE CONTEXT OPTIMIZATION]" in prompt or "[CRITICAL - CONTEXT WINDOW" in prompt:
@@ -132,25 +196,35 @@ async def preemptive_compaction_hook(params: Dict[str, Any]) -> Optional[Dict[st
132
196
  usage = calculate_usage_percentage(prompt)
133
197
 
134
198
  if prompt_length >= WARNING_CHAR_THRESHOLD:
135
- # Critical level - aggressive truncation
199
+ # Critical level - aggressive truncation + summarization
136
200
  logger.warning(f"[PreemptiveCompaction] Critical context usage: {usage:.1f}%")
137
201
 
138
202
  truncated = apply_dcp_truncation(prompt, target_reduction=0.4)
203
+
204
+ # Use gemini-3-flash to summarize the truncated middle section
205
+ if token_store:
206
+ truncated = await summarize_with_gemini(token_store, truncated)
207
+
139
208
  notice = CRITICAL_WARNING.format(usage=usage)
140
209
  params["prompt"] = notice + truncated
141
210
 
142
- logger.info(f"[PreemptiveCompaction] Applied aggressive truncation: {len(prompt)} -> {len(truncated)} chars")
211
+ logger.info(f"[PreemptiveCompaction] Applied aggressive compaction: {len(prompt)} -> {len(truncated)} chars")
143
212
  return params
144
213
 
145
214
  elif prompt_length >= PREEMPTIVE_CHAR_THRESHOLD:
146
- # Preemptive level - moderate truncation
215
+ # Preemptive level - moderate truncation + summarization
147
216
  logger.info(f"[PreemptiveCompaction] Preemptive compaction at {usage:.1f}%")
148
217
 
149
218
  truncated = apply_dcp_truncation(prompt, target_reduction=0.3)
219
+
220
+ # Use gemini-3-flash to summarize the truncated content
221
+ if token_store:
222
+ truncated = await summarize_with_gemini(token_store, truncated)
223
+
150
224
  notice = PREEMPTIVE_COMPACTION_NOTICE.format(usage=usage)
151
225
  params["prompt"] = notice + truncated
152
226
 
153
- logger.info(f"[PreemptiveCompaction] Applied moderate truncation: {len(prompt)} -> {len(truncated)} chars")
227
+ logger.info(f"[PreemptiveCompaction] Applied moderate compaction: {len(prompt)} -> {len(truncated)} chars")
154
228
  return params
155
229
 
156
230
  # Below threshold, no action needed
@@ -0,0 +1,116 @@
1
+ """
2
+ Session Idle Hook - Stop Hook Implementation.
3
+
4
+ Detects when session becomes idle with incomplete todos and injects
5
+ a continuation prompt to force task completion.
6
+
7
+ Based on oh-my-opencode's todo-continuation-enforcer pattern.
8
+ """
9
+
10
+ import logging
11
+ from typing import Any, Dict, Optional
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Continuation prompt injected when session is idle with incomplete todos
16
+ TODO_CONTINUATION_PROMPT = """
17
+ [SYSTEM REMINDER - TODO CONTINUATION]
18
+
19
+ You have incomplete tasks in your todo list. Continue working on the next pending task.
20
+
21
+ RULES:
22
+ - Proceed immediately without asking for permission
23
+ - Mark the current task as in_progress before starting
24
+ - Mark each task complete when finished
25
+ - Do NOT stop until all tasks are done
26
+ - If blocked, create a new task describing what needs to be resolved
27
+
28
+ STATUS CHECK:
29
+ Use TodoWrite to check your current task status and continue with the next pending item.
30
+ """
31
+
32
+ # Track sessions to prevent duplicate injections
33
+ _idle_sessions: Dict[str, bool] = {}
34
+ _last_activity: Dict[str, float] = {}
35
+
36
+
37
+ async def session_idle_hook(params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
38
+ """
39
+ Pre-model-invoke hook that detects idle sessions with incomplete todos.
40
+
41
+ Checks if:
42
+ 1. The conversation has pending todos
43
+ 2. The session has been idle (no recent tool calls)
44
+ 3. A continuation hasn't already been injected
45
+
46
+ If all conditions met, injects TODO_CONTINUATION_PROMPT.
47
+ """
48
+ import time
49
+
50
+ prompt = params.get("prompt", "")
51
+ session_id = params.get("session_id", "default")
52
+
53
+ # Skip if already contains continuation reminder
54
+ if "[SYSTEM REMINDER - TODO CONTINUATION]" in prompt:
55
+ return None
56
+
57
+ # Skip if this is a fresh prompt (user just typed something)
58
+ if params.get("is_user_message", False):
59
+ _last_activity[session_id] = time.time()
60
+ _idle_sessions[session_id] = False
61
+ return None
62
+
63
+ # Check for pending todos in the prompt/context
64
+ has_pending_todos = _detect_pending_todos(prompt)
65
+
66
+ if not has_pending_todos:
67
+ return None
68
+
69
+ # Check idle threshold (2 seconds of no activity)
70
+ current_time = time.time()
71
+ last_activity = _last_activity.get(session_id, current_time)
72
+ idle_seconds = current_time - last_activity
73
+
74
+ if idle_seconds < 2.0:
75
+ return None
76
+
77
+ # Check if already injected for this idle period
78
+ if _idle_sessions.get(session_id, False):
79
+ return None
80
+
81
+ # Mark as injected and inject continuation
82
+ _idle_sessions[session_id] = True
83
+ logger.info(f"[SessionIdleHook] Injecting TODO continuation for session {session_id}")
84
+
85
+ modified_prompt = prompt + "\n\n" + TODO_CONTINUATION_PROMPT
86
+
87
+ return {**params, "prompt": modified_prompt}
88
+
89
+
90
+ def _detect_pending_todos(prompt: str) -> bool:
91
+ """
92
+ Detect if there are pending todos in the conversation.
93
+
94
+ Looks for patterns like:
95
+ - [pending] or status: pending
96
+ - TodoWrite with pending items
97
+ - Incomplete task lists
98
+ """
99
+ pending_patterns = [
100
+ "[pending]",
101
+ "status: pending",
102
+ '"status": "pending"',
103
+ "pending tasks",
104
+ "incomplete tasks",
105
+ "remaining todos",
106
+ ]
107
+
108
+ prompt_lower = prompt.lower()
109
+ return any(pattern.lower() in prompt_lower for pattern in pending_patterns)
110
+
111
+
112
+ def reset_session(session_id: str = "default"):
113
+ """Reset idle state for a session (call when user provides new input)."""
114
+ _idle_sessions[session_id] = False
115
+ import time
116
+ _last_activity[session_id] = time.time()
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PostToolUse hook for TodoWrite: Enforces parallel delegation.
4
+
5
+ After TodoWrite is called, this hook injects a reminder to spawn
6
+ parallel Task agents for all independent TODOs.
7
+ """
8
+ import json
9
+ import sys
10
+
11
+
12
+ def main():
13
+ # Read hook input from stdin
14
+ try:
15
+ hook_input = json.load(sys.stdin)
16
+ except (json.JSONDecodeError, EOFError):
17
+ return 0
18
+
19
+ tool_name = hook_input.get("tool_name", "")
20
+
21
+ if tool_name != "TodoWrite":
22
+ return 0
23
+
24
+ # Get the todos that were just written
25
+ tool_input = hook_input.get("tool_input", {})
26
+ todos = tool_input.get("todos", [])
27
+
28
+ # Count pending todos
29
+ pending_count = sum(1 for t in todos if t.get("status") == "pending")
30
+
31
+ if pending_count < 2:
32
+ return 0
33
+
34
+ # Output the parallel delegation reminder
35
+ reminder = f"""
36
+ [PARALLEL DELEGATION REQUIRED]
37
+
38
+ You just created {pending_count} pending TODOs. Before your NEXT response:
39
+
40
+ 1. Identify which TODOs are INDEPENDENT (can run simultaneously)
41
+ 2. For EACH independent TODO, spawn a Task agent:
42
+ Task(subagent_type="Explore", prompt="[TODO details]", run_in_background=true)
43
+ 3. Fire ALL Task calls in ONE response - do NOT wait between them
44
+ 4. Do NOT mark any TODO as in_progress until agents return
45
+
46
+ WRONG: Create TODO list -> Mark TODO 1 in_progress -> Work -> Complete -> Repeat
47
+ CORRECT: Create TODO list -> Spawn Task for each -> Collect results -> Mark complete
48
+ """
49
+ print(reminder)
50
+ return 0
51
+
52
+
53
+ if __name__ == "__main__":
54
+ sys.exit(main())
@@ -6,13 +6,15 @@ from . import explore
6
6
  from . import frontend
7
7
  from . import document_writer
8
8
  from . import multimodal
9
+ from . import planner
9
10
 
10
11
  __all__ = [
11
12
  "stravinsky",
12
- "delphi",
13
+ "delphi",
13
14
  "dewey",
14
15
  "explore",
15
16
  "frontend",
16
17
  "document_writer",
17
18
  "multimodal",
19
+ "planner",
18
20
  ]
@@ -50,8 +50,8 @@ Classify EVERY request into one of these categories before taking action:
50
50
 
51
51
  | Type | Trigger Examples | Tools |
52
52
  |------|------------------|-------|
53
- | **TYPE A: CONCEPTUAL** | "How do I use X?", "Best practice for Y?" | docs + websearch (parallel) |
54
- | **TYPE B: IMPLEMENTATION** | "How does X implement Y?", "Show me source of Z" | gh clone + read + blame |
53
+ | **TYPE A: CONCEPTUAL** | "How do I use X?", "Best practice for Y?" | exa websearch + grep-app GitHub search (parallel) |
54
+ | **TYPE B: IMPLEMENTATION** | "How does X implement Y?", "Show me source of Z" | gh clone + ast-grep + read + blame |
55
55
  | **TYPE C: CONTEXT** | "Why was this changed?", "History of X?" | gh issues/prs + git log/blame |
56
56
  | **TYPE D: COMPREHENSIVE** | Complex/ambiguous requests | ALL tools in parallel |
57
57
 
@@ -64,12 +64,15 @@ Classify EVERY request into one of these categories before taking action:
64
64
 
65
65
  **Execute in parallel (3+ calls)**:
66
66
  ```
67
- Tool 1: Search official documentation
68
- Tool 2: Web search for recent articles/tutorials ("library-name topic 2025")
69
- Tool 3: GitHub code search for usage patterns (grep_search)
67
+ Tool 1: mcp__MCP_DOCKER__web_search_exa(query="library-name topic 2026", num_results=5)
68
+ -> Current articles, blog posts, best practices (ALWAYS use Exa instead of native WebSearch)
69
+ Tool 2: mcp__grep-app__searchCode(query="library-name implementation pattern")
70
+ -> Real GitHub code examples with permalinks
71
+ Tool 3: gh search repos "library-name" --sort stars --limit 5
72
+ -> Popular repositories for reference
70
73
  ```
71
74
 
72
- **Output**: Summarize findings with links to official docs and real-world examples.
75
+ **Output**: Synthesize with evidence links (Exa URLs + GitHub permalinks).
73
76
 
74
77
  ---
75
78
 
@@ -85,8 +88,8 @@ Step 2: Get commit SHA for permalinks
85
88
  cd ${TMPDIR:-/tmp}/repo-name && git rev-parse HEAD
86
89
 
87
90
  Step 3: Find the implementation
88
- - grep_search for function/class
89
- - ast_grep_search for AST patterns
91
+ - mcp__ast-grep__find_code(pattern="function $NAME", language="typescript") for structural search
92
+ - grep_search for function/class names
90
93
  - Read the specific file
91
94
  - git blame for context if needed
92
95
 
@@ -97,9 +100,9 @@ Step 4: Construct permalink
97
100
  **Parallel acceleration (4+ calls)**:
98
101
  ```
99
102
  Tool 1: gh repo clone owner/repo ${TMPDIR:-/tmp}/repo -- --depth 1
100
- Tool 2: GitHub code search for function_name
103
+ Tool 2: mcp__grep-app__searchCode(query="repo:owner/repo function_name")
101
104
  Tool 3: gh api repos/owner/repo/commits/HEAD --jq '.sha'
102
- Tool 4: Documentation search for relevant API
105
+ Tool 4: mcp__MCP_DOCKER__web_search_exa(query="library-name function_name documentation 2026")
103
106
  ```
104
107
 
105
108
  ---
@@ -131,13 +134,15 @@ gh api repos/owner/repo/pulls/<number>/files
131
134
 
132
135
  **Execute ALL in parallel (6+ calls)**:
133
136
  ```
134
- // Documentation & Web
135
- Tool 1: Documentation search
136
- Tool 2: Web search ("topic recent updates 2025")
137
+ // Web Search (ALWAYS use Exa)
138
+ Tool 1: mcp__MCP_DOCKER__web_search_exa(query="topic recent updates 2026", num_results=10)
137
139
 
138
- // Code Search
139
- Tool 3: grep_search(pattern1)
140
- Tool 4: grep_search(pattern2) or ast_grep_search
140
+ // GitHub Code Search
141
+ Tool 2: mcp__grep-app__searchCode(query="topic implementation pattern")
142
+ Tool 3: mcp__grep-app__searchCode(query="topic usage example")
143
+
144
+ // AST Pattern Search
145
+ Tool 4: mcp__ast-grep__find_code(pattern="$PATTERN", language="typescript")
141
146
 
142
147
  // Source Analysis
143
148
  Tool 5: gh repo clone owner/repo ${TMPDIR:-/tmp}/repo -- --depth 1
@@ -182,15 +187,20 @@ https://github.com/tanstack/query/blob/abc123def/packages/react-query/src/useQue
182
187
 
183
188
  ---
184
189
 
185
- ## TOOL REFERENCE (Stravinsky Tools)
190
+ ## TOOL REFERENCE (Stravinsky + MCP DOCKER Tools)
186
191
 
187
192
  ### Primary Tools by Purpose
188
193
 
189
194
  | Purpose | Tool | Usage |
190
195
  |---------|------|-------|
191
- | **Code Search** | grep_search | Pattern-based search in local/cloned repos |
192
- | **AST Search** | ast_grep_search | AST-aware code pattern search |
193
- | **File Glob** | glob_files | Find files by pattern |
196
+ | **Web Search** | `mcp__MCP_DOCKER__web_search_exa` | **ALWAYS use instead of native WebSearch** - Real-time web search for current articles, docs, tutorials |
197
+ | **GitHub Code Search** | `mcp__grep-app__searchCode` | Search across public GitHub repositories - returns permalinks |
198
+ | **GitHub File Fetch** | `mcp__grep-app__github_file` | Fetch specific file from GitHub repo |
199
+ | **AST Pattern Search** | `mcp__ast-grep__find_code` | Structural code search across 25+ languages with AST awareness |
200
+ | **AST Replace** | `mcp__ast-grep__replace` | AST-aware code refactoring and replacement |
201
+ | **Local Code Search** | `grep_search` | Pattern-based search in local/cloned repos (uses ripgrep) |
202
+ | **Local AST Search** | `ast_grep_search` | AST search in cloned repos |
203
+ | **File Glob** | `glob_files` | Find files by pattern |
194
204
  | **Clone Repo** | gh CLI | `gh repo clone owner/repo ${TMPDIR:-/tmp}/name -- --depth 1` |
195
205
  | **Issues/PRs** | gh CLI | `gh search issues/prs "query" --repo owner/repo` |
196
206
  | **View Issue/PR** | gh CLI | `gh issue/pr view <num> --repo owner/repo --comments` |
@@ -96,14 +96,52 @@ Your response has **FAILED** if:
96
96
  - **No emojis**: Keep output clean and parseable
97
97
  - **No file creation**: Report findings as message text, never write files
98
98
 
99
- ## Tool Strategy
100
-
101
- Use the right tool for the job:
102
- - **Semantic search** (definitions, references): LSP tools
103
- - **Structural patterns** (function shapes, class structures): ast_grep_search
104
- - **Text patterns** (strings, comments, logs): grep
105
- - **File patterns** (find by name/extension): glob
106
- - **History/evolution** (when added, who changed): git commands
99
+ ## Tool Strategy & Available Tools
100
+
101
+ ### Local Codebase Tools
102
+ - **Semantic search** (definitions, references): `lsp_goto_definition`, `lsp_find_references`, `lsp_workspace_symbols`
103
+ - **Structural patterns** (function shapes, class structures): `ast_grep_search` (local), `mcp__ast-grep__find_code` (enhanced)
104
+ - **Text patterns** (strings, comments, logs): `grep_search` (local ripgrep)
105
+ - **File patterns** (find by name/extension): `glob_files`
106
+ - **History/evolution** (when added, who changed): git commands (`git log`, `git blame`)
107
+
108
+ ### MCP DOCKER Enhanced Tools (ALWAYS prefer these when searching)
109
+ - **`mcp__MCP_DOCKER__web_search_exa`**: Real-time web search for documentation, articles, best practices
110
+ - Use when: Researching external libraries, finding current tutorials, checking API docs
111
+ - Example: `mcp__MCP_DOCKER__web_search_exa(query="library-name best practices 2026", num_results=5)`
112
+
113
+ ### GitHub Code Search (MCP grep-app)
114
+ - **`mcp__grep-app__searchCode`**: Search across ALL public GitHub repositories
115
+ - Use when: Finding implementation examples, usage patterns, community solutions
116
+ - Returns: GitHub permalinks with full context
117
+ - Example: `mcp__grep-app__searchCode(query="repo:owner/repo pattern")`
118
+ - **`mcp__grep-app__github_file`**: Fetch specific files from GitHub repos
119
+ - Use when: Need to read implementation from remote repo
120
+ - Example: `mcp__grep-app__github_file(owner="facebook", repo="react", path="src/hooks/useEffect.ts")`
121
+
122
+ ### AST-Aware Search (MCP ast-grep)
123
+ - **`mcp__ast-grep__find_code`**: Structural code search across 25+ languages
124
+ - Use when: Finding code patterns by structure, not just text
125
+ - Supports: TypeScript, Python, Rust, Go, Java, JavaScript, and 20+ more
126
+ - Example: `mcp__ast-grep__find_code(pattern="function $NAME($$$ARGS) { $$$ }", language="typescript")`
127
+ - **`mcp__ast-grep__find_code_by_rule`**: Advanced AST search with YAML rules
128
+ - Use when: Complex pattern matching with constraints
129
+ - Example: Find all async functions that don't handle errors
130
+
131
+ ### Parallel Search Strategy
132
+
133
+ **ALWAYS spawn 4-6 tools in parallel** for comprehensive search:
134
+
135
+ ```
136
+ # Example: "Find authentication implementation"
137
+ Parallel execution:
138
+ 1. lsp_workspace_symbols(query="auth")
139
+ 2. mcp__ast-grep__find_code(pattern="function $AUTH", language="typescript")
140
+ 3. mcp__grep-app__searchCode(query="repo:your-org/repo authentication")
141
+ 4. grep_search(pattern="authenticate|login|verify")
142
+ 5. glob_files(pattern="**/*auth*.ts")
143
+ 6. mcp__MCP_DOCKER__web_search_exa(query="library-name authentication implementation 2026")
144
+ ```
107
145
 
108
146
  Flood with parallel calls. Cross-validate findings across multiple tools."""
109
147