htmlgraph 0.24.2__py3-none-any.whl → 0.25.0__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.
Files changed (103) hide show
  1. htmlgraph/__init__.py +20 -1
  2. htmlgraph/agent_detection.py +26 -10
  3. htmlgraph/analytics/cross_session.py +4 -3
  4. htmlgraph/analytics/work_type.py +52 -16
  5. htmlgraph/analytics_index.py +51 -19
  6. htmlgraph/api/__init__.py +3 -0
  7. htmlgraph/api/main.py +2115 -0
  8. htmlgraph/api/static/htmx.min.js +1 -0
  9. htmlgraph/api/static/style-redesign.css +1344 -0
  10. htmlgraph/api/static/style.css +1079 -0
  11. htmlgraph/api/templates/dashboard-redesign.html +812 -0
  12. htmlgraph/api/templates/dashboard.html +783 -0
  13. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  14. htmlgraph/api/templates/partials/activity-feed.html +570 -0
  15. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  16. htmlgraph/api/templates/partials/agents.html +317 -0
  17. htmlgraph/api/templates/partials/event-traces.html +373 -0
  18. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  19. htmlgraph/api/templates/partials/features.html +509 -0
  20. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  21. htmlgraph/api/templates/partials/metrics.html +346 -0
  22. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  23. htmlgraph/api/templates/partials/orchestration.html +163 -0
  24. htmlgraph/api/templates/partials/spawners.html +375 -0
  25. htmlgraph/atomic_ops.py +560 -0
  26. htmlgraph/builders/base.py +55 -1
  27. htmlgraph/builders/bug.py +17 -2
  28. htmlgraph/builders/chore.py +17 -2
  29. htmlgraph/builders/epic.py +17 -2
  30. htmlgraph/builders/feature.py +25 -2
  31. htmlgraph/builders/phase.py +17 -2
  32. htmlgraph/builders/spike.py +27 -2
  33. htmlgraph/builders/track.py +14 -0
  34. htmlgraph/cigs/__init__.py +4 -0
  35. htmlgraph/cigs/reporter.py +818 -0
  36. htmlgraph/cli.py +1427 -401
  37. htmlgraph/cli_commands/__init__.py +1 -0
  38. htmlgraph/cli_commands/feature.py +195 -0
  39. htmlgraph/cli_framework.py +115 -0
  40. htmlgraph/collections/__init__.py +2 -0
  41. htmlgraph/collections/base.py +21 -0
  42. htmlgraph/collections/session.py +189 -0
  43. htmlgraph/collections/spike.py +7 -1
  44. htmlgraph/collections/task_delegation.py +236 -0
  45. htmlgraph/collections/traces.py +482 -0
  46. htmlgraph/config.py +113 -0
  47. htmlgraph/converter.py +41 -0
  48. htmlgraph/cost_analysis/__init__.py +5 -0
  49. htmlgraph/cost_analysis/analyzer.py +438 -0
  50. htmlgraph/dashboard.html +3315 -492
  51. htmlgraph-0.24.2.data/data/htmlgraph/dashboard.html → htmlgraph/dashboard.html.backup +2246 -248
  52. htmlgraph/dashboard.html.bak +7181 -0
  53. htmlgraph/dashboard.html.bak2 +7231 -0
  54. htmlgraph/dashboard.html.bak3 +7232 -0
  55. htmlgraph/db/__init__.py +38 -0
  56. htmlgraph/db/queries.py +790 -0
  57. htmlgraph/db/schema.py +1334 -0
  58. htmlgraph/deploy.py +26 -27
  59. htmlgraph/docs/API_REFERENCE.md +841 -0
  60. htmlgraph/docs/HTTP_API.md +750 -0
  61. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  62. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +710 -0
  63. htmlgraph/docs/README.md +533 -0
  64. htmlgraph/docs/version_check.py +3 -1
  65. htmlgraph/error_handler.py +544 -0
  66. htmlgraph/event_log.py +2 -0
  67. htmlgraph/hooks/__init__.py +8 -0
  68. htmlgraph/hooks/bootstrap.py +169 -0
  69. htmlgraph/hooks/context.py +271 -0
  70. htmlgraph/hooks/drift_handler.py +521 -0
  71. htmlgraph/hooks/event_tracker.py +405 -15
  72. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  73. htmlgraph/hooks/pretooluse.py +476 -6
  74. htmlgraph/hooks/prompt_analyzer.py +648 -0
  75. htmlgraph/hooks/session_handler.py +583 -0
  76. htmlgraph/hooks/state_manager.py +501 -0
  77. htmlgraph/hooks/subagent_stop.py +309 -0
  78. htmlgraph/hooks/task_enforcer.py +39 -0
  79. htmlgraph/models.py +111 -15
  80. htmlgraph/operations/fastapi_server.py +230 -0
  81. htmlgraph/orchestration/headless_spawner.py +22 -14
  82. htmlgraph/pydantic_models.py +476 -0
  83. htmlgraph/quality_gates.py +350 -0
  84. htmlgraph/repo_hash.py +511 -0
  85. htmlgraph/sdk.py +348 -10
  86. htmlgraph/server.py +194 -0
  87. htmlgraph/session_hooks.py +300 -0
  88. htmlgraph/session_manager.py +131 -1
  89. htmlgraph/session_registry.py +587 -0
  90. htmlgraph/session_state.py +436 -0
  91. htmlgraph/system_prompts.py +449 -0
  92. htmlgraph/templates/orchestration-view.html +350 -0
  93. htmlgraph/track_builder.py +19 -0
  94. htmlgraph/validation.py +115 -0
  95. htmlgraph-0.25.0.data/data/htmlgraph/dashboard.html +7417 -0
  96. {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/METADATA +91 -64
  97. {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/RECORD +103 -42
  98. {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/styles.css +0 -0
  99. {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  100. {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  101. {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  102. {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/WHEEL +0 -0
  103. {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,648 @@
1
+ """
2
+ Prompt Analysis Module for HtmlGraph Hooks.
3
+
4
+ Centralizes prompt classification and workflow guidance logic used across
5
+ multiple hooks (UserPromptSubmit, PreToolUse, etc.).
6
+
7
+ This module provides:
8
+ - Intent classification (implementation, testing, refactoring, etc.)
9
+ - CIGS (Computational Imperative Guidance System) violation detection
10
+ - Active work item tracking
11
+ - Workflow guidance generation
12
+ - UserQuery event creation for parent-child linking
13
+
14
+ The module is designed to be reusable across different hook implementations,
15
+ with graceful degradation if dependencies are unavailable.
16
+ """
17
+
18
+ import json
19
+ import logging
20
+ import os
21
+ import re
22
+ import uuid
23
+ from datetime import datetime, timezone
24
+ from pathlib import Path
25
+ from typing import Any, Optional
26
+
27
+ from htmlgraph.hooks.context import HookContext
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ # Patterns that indicate implementation intent
33
+ IMPLEMENTATION_PATTERNS = [
34
+ r"\b(implement|add|create|build|write|develop|make)\b.*\b(feature|function|method|class|component|endpoint|api)\b",
35
+ r"\b(fix|resolve|patch|repair)\b.*\b(bug|issue|error|problem)\b",
36
+ r"\b(refactor|rewrite|restructure|reorganize)\b",
37
+ r"\b(update|modify|change|edit)\b.*\b(code|file|function|class)\b",
38
+ r"\bcan you\b.*\b(add|implement|create|fix|change)\b",
39
+ r"\bplease\b.*\b(add|implement|create|fix|change)\b",
40
+ r"\bI need\b.*\b(feature|function|fix|change)\b",
41
+ r"\blet'?s\b.*\b(implement|add|create|build|fix)\b",
42
+ ]
43
+
44
+ # Patterns that indicate investigation/research
45
+ INVESTIGATION_PATTERNS = [
46
+ r"\b(investigate|research|explore|analyze|understand|find out|look into)\b",
47
+ r"\b(why|how come|what causes)\b.*\b(not working|broken|failing|error)\b",
48
+ r"\b(where|which|what)\b.*\b(file|code|function|class)\b.*\b(handle|process|do)\b",
49
+ r"\bcan you\b.*\b(find|search|look for|check)\b",
50
+ ]
51
+
52
+ # Patterns that indicate bug/issue
53
+ BUG_PATTERNS = [
54
+ r"\b(bug|issue|error|problem|broken|not working|fails|crash)\b",
55
+ r"\b(something'?s? wrong|doesn'?t work|isn'?t working)\b",
56
+ r"\bCI\b.*\b(fail|error|broken)\b",
57
+ r"\btest.*\b(fail|error|broken)\b",
58
+ ]
59
+
60
+ # Patterns for continuation
61
+ CONTINUATION_PATTERNS = [
62
+ r"^(continue|resume|proceed|go on|keep going|next)\b",
63
+ r"\b(where we left off|from before|last time)\b",
64
+ r"^(ok|okay|yes|sure|do it|go ahead)\b",
65
+ ]
66
+
67
+ # CIGS: Patterns for delegation-critical operations
68
+ EXPLORATION_KEYWORDS = [
69
+ "search",
70
+ "find",
71
+ "what files",
72
+ "which files",
73
+ "where is",
74
+ "locate",
75
+ "analyze",
76
+ "examine",
77
+ "inspect",
78
+ "review",
79
+ "check",
80
+ "look at",
81
+ "show me",
82
+ "list",
83
+ "grep",
84
+ "read",
85
+ "scan",
86
+ "explore",
87
+ ]
88
+
89
+ CODE_CHANGE_KEYWORDS = [
90
+ "implement",
91
+ "fix",
92
+ "update",
93
+ "refactor",
94
+ "change",
95
+ "modify",
96
+ "edit",
97
+ "write",
98
+ "create file",
99
+ "add code",
100
+ "remove code",
101
+ "replace",
102
+ "rewrite",
103
+ "patch",
104
+ "add",
105
+ ]
106
+
107
+ GIT_KEYWORDS = [
108
+ "commit",
109
+ "push",
110
+ "pull",
111
+ "merge",
112
+ "branch",
113
+ "checkout",
114
+ "git add",
115
+ "git commit",
116
+ "git push",
117
+ "git status",
118
+ "git diff",
119
+ "rebase",
120
+ "cherry-pick",
121
+ "stash",
122
+ ]
123
+
124
+
125
+ def classify_prompt(prompt: str) -> dict[str, Any]:
126
+ """
127
+ Classify the user's prompt intent.
128
+
129
+ Analyzes the prompt text to determine the primary intent using pattern matching.
130
+ Returns classification results with confidence scores.
131
+
132
+ Args:
133
+ prompt: User's prompt text
134
+
135
+ Returns:
136
+ dict with:
137
+ - is_implementation: bool - Implementation/coding request
138
+ - is_investigation: bool - Research/exploration request
139
+ - is_bug_report: bool - Bug/issue report
140
+ - is_continuation: bool - Continuation of previous work
141
+ - confidence: float - Overall confidence (0.0-1.0)
142
+ - matched_patterns: list - Patterns that matched
143
+
144
+ Example:
145
+ >>> result = classify_prompt("Can you implement a new feature?")
146
+ >>> result['is_implementation']
147
+ True
148
+ """
149
+ prompt_lower = prompt.lower().strip()
150
+
151
+ result: dict[str, Any] = {
152
+ "is_implementation": False,
153
+ "is_investigation": False,
154
+ "is_bug_report": False,
155
+ "is_continuation": False,
156
+ "confidence": 0.0,
157
+ "matched_patterns": [],
158
+ }
159
+
160
+ # Check for continuation first (short prompts like "ok", "continue")
161
+ for pattern in CONTINUATION_PATTERNS:
162
+ if re.search(pattern, prompt_lower):
163
+ result["is_continuation"] = True
164
+ result["confidence"] = 0.9
165
+ result["matched_patterns"].append(f"continuation: {pattern}")
166
+ return result
167
+
168
+ # Check for implementation patterns
169
+ for pattern in IMPLEMENTATION_PATTERNS:
170
+ if re.search(pattern, prompt_lower):
171
+ result["is_implementation"] = True
172
+ result["confidence"] = max(result["confidence"], 0.8)
173
+ result["matched_patterns"].append(f"implementation: {pattern}")
174
+
175
+ # Check for investigation patterns
176
+ for pattern in INVESTIGATION_PATTERNS:
177
+ if re.search(pattern, prompt_lower):
178
+ result["is_investigation"] = True
179
+ result["confidence"] = max(result["confidence"], 0.7)
180
+ result["matched_patterns"].append(f"investigation: {pattern}")
181
+
182
+ # Check for bug patterns
183
+ for pattern in BUG_PATTERNS:
184
+ if re.search(pattern, prompt_lower):
185
+ result["is_bug_report"] = True
186
+ result["confidence"] = max(result["confidence"], 0.75)
187
+ result["matched_patterns"].append(f"bug: {pattern}")
188
+
189
+ return result
190
+
191
+
192
+ def classify_cigs_intent(prompt: str) -> dict[str, Any]:
193
+ """
194
+ Classify prompt for CIGS delegation guidance.
195
+
196
+ Analyzes the prompt to detect patterns that indicate exploration, code changes,
197
+ or git operations. Used to generate pre-response delegation imperatives.
198
+
199
+ Args:
200
+ prompt: User's prompt text
201
+
202
+ Returns:
203
+ dict with:
204
+ - involves_exploration: bool - Exploration/search activity
205
+ - involves_code_changes: bool - Code modification activity
206
+ - involves_git: bool - Git operation activity
207
+ - intent_confidence: float - Overall confidence (0.0-1.0)
208
+
209
+ Example:
210
+ >>> result = classify_cigs_intent("Search for all error handling code")
211
+ >>> result['involves_exploration']
212
+ True
213
+ """
214
+ prompt_lower = prompt.lower().strip()
215
+
216
+ result: dict[str, Any] = {
217
+ "involves_exploration": False,
218
+ "involves_code_changes": False,
219
+ "involves_git": False,
220
+ "intent_confidence": 0.0,
221
+ }
222
+
223
+ # Check for exploration keywords
224
+ exploration_matches = sum(1 for kw in EXPLORATION_KEYWORDS if kw in prompt_lower)
225
+ if exploration_matches > 0:
226
+ result["involves_exploration"] = True
227
+ result["intent_confidence"] = min(1.0, exploration_matches * 0.3)
228
+
229
+ # Check for code change keywords
230
+ code_matches = sum(1 for kw in CODE_CHANGE_KEYWORDS if kw in prompt_lower)
231
+ if code_matches > 0:
232
+ result["involves_code_changes"] = True
233
+ result["intent_confidence"] = max(
234
+ result["intent_confidence"], min(1.0, code_matches * 0.35)
235
+ )
236
+
237
+ # Check for git keywords
238
+ git_matches = sum(1 for kw in GIT_KEYWORDS if kw in prompt_lower)
239
+ if git_matches > 0:
240
+ result["involves_git"] = True
241
+ result["intent_confidence"] = max(
242
+ result["intent_confidence"], min(1.0, git_matches * 0.4)
243
+ )
244
+
245
+ return result
246
+
247
+
248
+ def get_session_violation_count(context: HookContext) -> tuple[int, int]:
249
+ """
250
+ Get violation count for current session using CIGS ViolationTracker.
251
+
252
+ Queries the CIGS violation tracker to retrieve session violation metrics.
253
+ Gracefully degrades to (0, 0) if CIGS is unavailable.
254
+
255
+ Args:
256
+ context: HookContext with session and graph directory info
257
+
258
+ Returns:
259
+ Tuple of (violation_count, total_waste_tokens)
260
+ Returns (0, 0) if CIGS is unavailable
261
+
262
+ Example:
263
+ >>> violation_count, waste_tokens = get_session_violation_count(context)
264
+ >>> if violation_count > 0:
265
+ ... print(f"Violations this session: {violation_count}")
266
+ """
267
+ try:
268
+ from htmlgraph.cigs import ViolationTracker
269
+
270
+ tracker = ViolationTracker()
271
+ summary = tracker.get_session_violations()
272
+ return summary.total_violations, summary.total_waste_tokens
273
+ except Exception as e:
274
+ # Graceful degradation if CIGS not available
275
+ logger.debug(f"Could not get violation count: {e}")
276
+ return 0, 0
277
+
278
+
279
+ def get_active_work_item(context: HookContext) -> Optional[dict[str, Any]]:
280
+ """
281
+ Query HtmlGraph for active feature/spike.
282
+
283
+ Attempts to load the active work item from the session manager.
284
+ Returns None if no active work item or if SDK is unavailable.
285
+
286
+ Args:
287
+ context: HookContext with session and graph directory info
288
+
289
+ Returns:
290
+ dict with work item details (id, title, type) or None if not found
291
+
292
+ Example:
293
+ >>> active = get_active_work_item(context)
294
+ >>> if active and active['type'] == 'feature':
295
+ ... print(f"Active feature: {active['title']}")
296
+ """
297
+ try:
298
+ from htmlgraph import SDK
299
+
300
+ sdk = SDK()
301
+ work_item = sdk.get_active_work_item()
302
+ return work_item if isinstance(work_item, dict) else None
303
+ except Exception as e:
304
+ logger.debug(f"Could not get active work item: {e}")
305
+ return None
306
+
307
+
308
+ def generate_guidance(
309
+ classification: dict[str, Any], active_work: Optional[dict[str, Any]], prompt: str
310
+ ) -> Optional[str]:
311
+ """
312
+ Generate workflow guidance based on classification and context.
313
+
314
+ Produces orchestrator directives and workflow suggestions based on
315
+ the prompt classification and current active work item.
316
+
317
+ Args:
318
+ classification: Result from classify_prompt()
319
+ active_work: Result from get_active_work_item()
320
+ prompt: Original user prompt
321
+
322
+ Returns:
323
+ Guidance string to display to user, or None if no guidance needed
324
+
325
+ Example:
326
+ >>> classification = classify_prompt("Implement new API endpoint")
327
+ >>> guidance = generate_guidance(classification, None, prompt)
328
+ >>> if guidance:
329
+ ... print(guidance)
330
+ """
331
+
332
+ # If continuing and has active work, no guidance needed
333
+ if classification["is_continuation"] and active_work:
334
+ return None
335
+
336
+ # If has active work item, check if it matches intent
337
+ if active_work:
338
+ work_type = active_work.get("type", "")
339
+ work_id = active_work.get("id", "")
340
+ work_title = active_work.get("title", "")
341
+
342
+ # Implementation request with spike active - suggest creating feature
343
+ if classification["is_implementation"] and work_type == "spike":
344
+ return (
345
+ f"⚡ ORCHESTRATOR DIRECTIVE: Implementation requested during spike.\n\n"
346
+ f"Active work: {work_id} ({work_title}) - Type: spike\n\n"
347
+ f"Spikes are for investigation, NOT implementation.\n\n"
348
+ f"REQUIRED WORKFLOW:\n\n"
349
+ f"1. COMPLETE OR PAUSE the spike:\n"
350
+ f" sdk = SDK(agent='claude')\n"
351
+ f" sdk.spikes.complete('{work_id}') # or sdk.spikes.pause('{work_id}')\n\n"
352
+ f"2. CREATE A FEATURE for implementation:\n"
353
+ f" feature = sdk.features.create('Feature title').save()\n"
354
+ f" sdk.features.start(feature.id)\n\n"
355
+ f"3. DELEGATE TO SUBAGENT:\n"
356
+ f" from htmlgraph.tasks import Task\n"
357
+ f" Task(\n"
358
+ f" subagent_type='general-purpose',\n"
359
+ f" prompt='Implement: [details]'\n"
360
+ f" ).execute()\n\n"
361
+ f"Proceed with orchestration.\n"
362
+ )
363
+
364
+ # Implementation request with feature active - remind to delegate
365
+ if classification["is_implementation"] and work_type == "feature":
366
+ return (
367
+ f"⚡ ORCHESTRATOR DIRECTIVE: Implementation work detected.\n\n"
368
+ f"Active work: {work_id} ({work_title}) - Type: feature\n\n"
369
+ f"REQUIRED: DELEGATE TO SUBAGENT:\n\n"
370
+ f" from htmlgraph.tasks import Task\n"
371
+ f" Task(\n"
372
+ f" subagent_type='general-purpose',\n"
373
+ f" prompt='Implement: [specific implementation details for {work_title}]'\n"
374
+ f" ).execute()\n\n"
375
+ f"DO NOT EXECUTE CODE DIRECTLY IN THIS CONTEXT.\n"
376
+ f"Orchestrators coordinate, subagents implement.\n\n"
377
+ f"Proceed with orchestration.\n"
378
+ )
379
+
380
+ # Bug report with feature active - might want bug instead
381
+ if classification["is_bug_report"] and work_type == "feature":
382
+ return (
383
+ f"📋 WORKFLOW GUIDANCE:\n"
384
+ f"Active work: {work_id} ({work_title}) - Type: feature\n\n"
385
+ f"This looks like a bug report. Consider:\n"
386
+ f"1. If this bug is part of {work_title}, continue with current feature\n"
387
+ f"2. If this is a separate issue, create a bug:\n\n"
388
+ f" sdk = SDK(agent='claude')\n"
389
+ f" bug = sdk.bugs.create('Bug title').save()\n"
390
+ f" sdk.bugs.start(bug.id)\n"
391
+ )
392
+
393
+ # Has appropriate work item - no guidance needed
394
+ return None
395
+
396
+ # No active work item - provide guidance based on intent
397
+ if classification["is_implementation"]:
398
+ return (
399
+ "⚡ ORCHESTRATOR DIRECTIVE: This is implementation work.\n\n"
400
+ "REQUIRED WORKFLOW (execute in order):\n\n"
401
+ "1. CREATE A WORK ITEM:\n"
402
+ " sdk = SDK(agent='claude')\n"
403
+ " feature = sdk.features.create('Your feature title').save()\n"
404
+ " sdk.features.start(feature.id)\n\n"
405
+ "2. DELEGATE TO SUBAGENT:\n"
406
+ " from htmlgraph.tasks import Task\n"
407
+ " Task(\n"
408
+ " subagent_type='general-purpose',\n"
409
+ " prompt='Implement: [specific implementation details]'\n"
410
+ " ).execute()\n\n"
411
+ "3. DO NOT EXECUTE CODE DIRECTLY IN THIS CONTEXT\n"
412
+ " - Orchestrators coordinate, subagents implement\n"
413
+ " - This ensures proper work tracking and session management\n\n"
414
+ "Proceed with orchestration.\n"
415
+ )
416
+
417
+ if classification["is_bug_report"]:
418
+ return (
419
+ "📋 WORKFLOW GUIDANCE - BUG REPORT DETECTED:\n\n"
420
+ "Create a bug work item to track this:\n\n"
421
+ " sdk = SDK(agent='claude')\n"
422
+ " bug = sdk.bugs.create('Bug title').save()\n"
423
+ " sdk.bugs.start(bug.id)\n\n"
424
+ "Then investigate and fix the issue.\n"
425
+ )
426
+
427
+ if classification["is_investigation"]:
428
+ return (
429
+ "📋 WORKFLOW GUIDANCE - INVESTIGATION REQUEST DETECTED:\n\n"
430
+ "Create a spike for time-boxed investigation:\n\n"
431
+ " sdk = SDK(agent='claude')\n"
432
+ " spike = sdk.spikes.create('Investigation title').save()\n"
433
+ " sdk.spikes.start(spike.id)\n\n"
434
+ "Spikes help track research and exploration work.\n"
435
+ )
436
+
437
+ # Low confidence or unclear intent - provide gentle reminder
438
+ if classification["confidence"] < 0.5:
439
+ return (
440
+ "💡 REMINDER: Consider creating a work item if this is a task:\n"
441
+ "- Feature: sdk.features.create('Title').save()\n"
442
+ "- Bug: sdk.bugs.create('Title').save()\n"
443
+ "- Spike: sdk.spikes.create('Title').save()\n"
444
+ )
445
+
446
+ return None
447
+
448
+
449
+ def generate_cigs_guidance(
450
+ cigs_intent: dict, violation_count: int, waste_tokens: int
451
+ ) -> str:
452
+ """
453
+ Generate CIGS-specific guidance for detected violations.
454
+
455
+ Produces pre-response imperative guidance based on CIGS intent
456
+ classification and current session violation metrics.
457
+
458
+ Args:
459
+ cigs_intent: Result from classify_cigs_intent()
460
+ violation_count: Number of violations this session
461
+ waste_tokens: Total wasted tokens this session
462
+
463
+ Returns:
464
+ Imperative guidance string (empty if no guidance needed)
465
+
466
+ Example:
467
+ >>> cigs = classify_cigs_intent("Search for all error handling")
468
+ >>> guidance = generate_cigs_guidance(cigs, 0, 0)
469
+ >>> if guidance:
470
+ ... print(guidance)
471
+ """
472
+ imperatives = []
473
+
474
+ # Exploration guidance
475
+ if cigs_intent["involves_exploration"]:
476
+ imperatives.append(
477
+ "🔴 IMPERATIVE: This request involves exploration.\n"
478
+ "YOU MUST use spawn_gemini() for exploration (FREE cost).\n"
479
+ "DO NOT use Read/Grep/Glob directly - delegate to Explorer subagent."
480
+ )
481
+
482
+ # Code changes guidance
483
+ if cigs_intent["involves_code_changes"]:
484
+ imperatives.append(
485
+ "🔴 IMPERATIVE: This request involves code changes.\n"
486
+ "YOU MUST use spawn_codex() or Task() for implementation.\n"
487
+ "DO NOT use Edit/Write directly - delegate to Coder subagent."
488
+ )
489
+
490
+ # Git operations guidance
491
+ if cigs_intent["involves_git"]:
492
+ imperatives.append(
493
+ "🔴 IMPERATIVE: This request involves git operations.\n"
494
+ "YOU MUST use spawn_copilot() for git commands (60% cheaper).\n"
495
+ "DO NOT run git commands directly via Bash."
496
+ )
497
+
498
+ # Violation warning
499
+ if violation_count > 0:
500
+ warning_emoji = "⚠️" if violation_count < 3 else "🚨"
501
+ imperatives.append(
502
+ f"{warning_emoji} VIOLATION WARNING: You have {violation_count} delegation "
503
+ f"violations this session ({waste_tokens:,} tokens wasted).\n"
504
+ f"Circuit breaker triggers at 3 violations."
505
+ )
506
+
507
+ if not imperatives:
508
+ return ""
509
+
510
+ # Combine with header
511
+ guidance_parts = [
512
+ "═══════════════════════════════════════════════════════════",
513
+ "CIGS PRE-RESPONSE GUIDANCE (Computational Imperative Guidance System)",
514
+ "═══════════════════════════════════════════════════════════",
515
+ "",
516
+ ]
517
+ guidance_parts.extend(imperatives)
518
+ guidance_parts.append("")
519
+ guidance_parts.append("═══════════════════════════════════════════════════════════")
520
+
521
+ return "\n".join(guidance_parts)
522
+
523
+
524
+ def create_user_query_event(context: HookContext, prompt: str) -> Optional[str]:
525
+ """
526
+ Create UserQuery event in HtmlGraph database.
527
+
528
+ Records the user prompt as a UserQuery event that serves as the parent
529
+ for subsequent tool calls and delegations. The event ID is saved to a
530
+ session-scoped file for retrieval by PreToolUse hook.
531
+
532
+ Args:
533
+ context: HookContext with session and database access
534
+ prompt: User's prompt text
535
+
536
+ Returns:
537
+ UserQuery event_id if successful, None otherwise
538
+
539
+ Note:
540
+ Gracefully degrades if database is unavailable. Does not block
541
+ user interaction on failure.
542
+
543
+ Example:
544
+ >>> context = HookContext.from_input(hook_input)
545
+ >>> event_id = create_user_query_event(context, "Implement feature X")
546
+ >>> if event_id:
547
+ ... print(f"Created event: {event_id}")
548
+ """
549
+ try:
550
+ session_id = context.session_id
551
+ graph_dir = context.graph_dir
552
+
553
+ if not session_id or session_id == "unknown":
554
+ logger.debug("No valid session ID for UserQuery event")
555
+ return None
556
+
557
+ # Create UserQuery event in database
558
+ try:
559
+ from htmlgraph.db.schema import HtmlGraphDB
560
+ from htmlgraph.hooks.event_tracker import save_user_query_event
561
+
562
+ db = context.database
563
+
564
+ # Ensure session exists in database before creating event
565
+ # (sessions table has foreign key references, so we need to ensure it exists)
566
+ cursor = db.connection.cursor()
567
+ cursor.execute(
568
+ "SELECT COUNT(*) FROM sessions WHERE session_id = ?",
569
+ (session_id,),
570
+ )
571
+ session_exists = cursor.fetchone()[0] > 0
572
+
573
+ if not session_exists:
574
+ # Create session entry if it doesn't exist
575
+ cursor.execute(
576
+ """
577
+ INSERT INTO sessions (session_id, created_at, status)
578
+ VALUES (?, ?, 'active')
579
+ """,
580
+ (session_id, datetime.now(timezone.utc).isoformat()),
581
+ )
582
+ db.connection.commit()
583
+ logger.debug(f"Created session entry: {session_id}")
584
+
585
+ # Generate event ID
586
+ user_query_event_id = f"uq-{uuid.uuid4().hex[:8]}"
587
+
588
+ # Prepare event details
589
+ input_summary = prompt[:200]
590
+
591
+ # Insert UserQuery event into agent_events
592
+ success = db.insert_event(
593
+ event_id=user_query_event_id,
594
+ agent_id="user",
595
+ event_type="tool_call", # Valid event_type; find by tool_name='UserQuery'
596
+ session_id=session_id,
597
+ tool_name="UserQuery",
598
+ input_summary=input_summary,
599
+ context={
600
+ "prompt": prompt[:500],
601
+ "session": session_id,
602
+ },
603
+ )
604
+
605
+ if not success:
606
+ logger.warning("Failed to insert UserQuery event into database")
607
+ return None
608
+
609
+ # Save event ID to session-scoped file for subsequent tool calls
610
+ # This is used by PreToolUse hook to link Task delegations
611
+ try:
612
+ save_user_query_event(graph_dir, session_id, user_query_event_id)
613
+ logger.debug(f"Saved UserQuery event file: {user_query_event_id}")
614
+ except Exception as e:
615
+ # If saving to file fails, still continue (database insert succeeded)
616
+ logger.warning(f"Could not save UserQuery event file: {e}")
617
+
618
+ logger.info(f"Created UserQuery event: {user_query_event_id}")
619
+ return user_query_event_id
620
+
621
+ except Exception as e:
622
+ # Database tracking is optional - graceful degradation
623
+ logger.error(f"UserQuery event creation failed: {e}", exc_info=True)
624
+ return None
625
+
626
+ except Exception as e:
627
+ # Silent failure - don't block user interaction
628
+ logger.error(f"Unexpected error in create_user_query_event: {e}", exc_info=True)
629
+ return None
630
+
631
+
632
+ __all__ = [
633
+ "classify_prompt",
634
+ "classify_cigs_intent",
635
+ "get_session_violation_count",
636
+ "get_active_work_item",
637
+ "generate_guidance",
638
+ "generate_cigs_guidance",
639
+ "create_user_query_event",
640
+ # Pattern constants for testing/extension
641
+ "IMPLEMENTATION_PATTERNS",
642
+ "INVESTIGATION_PATTERNS",
643
+ "BUG_PATTERNS",
644
+ "CONTINUATION_PATTERNS",
645
+ "EXPLORATION_KEYWORDS",
646
+ "CODE_CHANGE_KEYWORDS",
647
+ "GIT_KEYWORDS",
648
+ ]