htmlgraph 0.9.3__py3-none-any.whl → 0.27.5__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 (331) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +173 -17
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +127 -0
  7. htmlgraph/agent_registry.py +45 -30
  8. htmlgraph/agents.py +160 -107
  9. htmlgraph/analytics/__init__.py +9 -2
  10. htmlgraph/analytics/cli.py +190 -51
  11. htmlgraph/analytics/cost_analyzer.py +391 -0
  12. htmlgraph/analytics/cost_monitor.py +664 -0
  13. htmlgraph/analytics/cost_reporter.py +675 -0
  14. htmlgraph/analytics/cross_session.py +617 -0
  15. htmlgraph/analytics/dependency.py +192 -100
  16. htmlgraph/analytics/pattern_learning.py +771 -0
  17. htmlgraph/analytics/session_graph.py +707 -0
  18. htmlgraph/analytics/strategic/__init__.py +80 -0
  19. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  20. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  21. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  22. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  23. htmlgraph/analytics/work_type.py +190 -14
  24. htmlgraph/analytics_index.py +135 -51
  25. htmlgraph/api/__init__.py +3 -0
  26. htmlgraph/api/cost_alerts_websocket.py +416 -0
  27. htmlgraph/api/main.py +2498 -0
  28. htmlgraph/api/static/htmx.min.js +1 -0
  29. htmlgraph/api/static/style-redesign.css +1344 -0
  30. htmlgraph/api/static/style.css +1079 -0
  31. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  32. htmlgraph/api/templates/dashboard.html +794 -0
  33. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  34. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  35. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  36. htmlgraph/api/templates/partials/agents.html +317 -0
  37. htmlgraph/api/templates/partials/event-traces.html +373 -0
  38. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  39. htmlgraph/api/templates/partials/features.html +578 -0
  40. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  41. htmlgraph/api/templates/partials/metrics.html +346 -0
  42. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  43. htmlgraph/api/templates/partials/orchestration.html +198 -0
  44. htmlgraph/api/templates/partials/spawners.html +375 -0
  45. htmlgraph/api/templates/partials/work-items.html +613 -0
  46. htmlgraph/api/websocket.py +538 -0
  47. htmlgraph/archive/__init__.py +24 -0
  48. htmlgraph/archive/bloom.py +234 -0
  49. htmlgraph/archive/fts.py +297 -0
  50. htmlgraph/archive/manager.py +583 -0
  51. htmlgraph/archive/search.py +244 -0
  52. htmlgraph/atomic_ops.py +560 -0
  53. htmlgraph/attribute_index.py +208 -0
  54. htmlgraph/bounded_paths.py +539 -0
  55. htmlgraph/builders/__init__.py +14 -0
  56. htmlgraph/builders/base.py +118 -29
  57. htmlgraph/builders/bug.py +150 -0
  58. htmlgraph/builders/chore.py +119 -0
  59. htmlgraph/builders/epic.py +150 -0
  60. htmlgraph/builders/feature.py +31 -6
  61. htmlgraph/builders/insight.py +195 -0
  62. htmlgraph/builders/metric.py +217 -0
  63. htmlgraph/builders/pattern.py +202 -0
  64. htmlgraph/builders/phase.py +162 -0
  65. htmlgraph/builders/spike.py +52 -19
  66. htmlgraph/builders/track.py +148 -72
  67. htmlgraph/cigs/__init__.py +81 -0
  68. htmlgraph/cigs/autonomy.py +385 -0
  69. htmlgraph/cigs/cost.py +475 -0
  70. htmlgraph/cigs/messages_basic.py +472 -0
  71. htmlgraph/cigs/messaging.py +365 -0
  72. htmlgraph/cigs/models.py +771 -0
  73. htmlgraph/cigs/pattern_storage.py +427 -0
  74. htmlgraph/cigs/patterns.py +503 -0
  75. htmlgraph/cigs/posttool_analyzer.py +234 -0
  76. htmlgraph/cigs/reporter.py +818 -0
  77. htmlgraph/cigs/tracker.py +317 -0
  78. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  79. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  80. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  81. htmlgraph/cli/__init__.py +42 -0
  82. htmlgraph/cli/__main__.py +6 -0
  83. htmlgraph/cli/analytics.py +1424 -0
  84. htmlgraph/cli/base.py +685 -0
  85. htmlgraph/cli/constants.py +206 -0
  86. htmlgraph/cli/core.py +954 -0
  87. htmlgraph/cli/main.py +147 -0
  88. htmlgraph/cli/models.py +475 -0
  89. htmlgraph/cli/templates/__init__.py +1 -0
  90. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  91. htmlgraph/cli/work/__init__.py +239 -0
  92. htmlgraph/cli/work/browse.py +115 -0
  93. htmlgraph/cli/work/features.py +568 -0
  94. htmlgraph/cli/work/orchestration.py +676 -0
  95. htmlgraph/cli/work/report.py +728 -0
  96. htmlgraph/cli/work/sessions.py +466 -0
  97. htmlgraph/cli/work/snapshot.py +559 -0
  98. htmlgraph/cli/work/tracks.py +486 -0
  99. htmlgraph/cli_commands/__init__.py +1 -0
  100. htmlgraph/cli_commands/feature.py +195 -0
  101. htmlgraph/cli_framework.py +115 -0
  102. htmlgraph/collections/__init__.py +18 -0
  103. htmlgraph/collections/base.py +415 -98
  104. htmlgraph/collections/bug.py +53 -0
  105. htmlgraph/collections/chore.py +53 -0
  106. htmlgraph/collections/epic.py +53 -0
  107. htmlgraph/collections/feature.py +12 -26
  108. htmlgraph/collections/insight.py +100 -0
  109. htmlgraph/collections/metric.py +92 -0
  110. htmlgraph/collections/pattern.py +97 -0
  111. htmlgraph/collections/phase.py +53 -0
  112. htmlgraph/collections/session.py +194 -0
  113. htmlgraph/collections/spike.py +56 -16
  114. htmlgraph/collections/task_delegation.py +241 -0
  115. htmlgraph/collections/todo.py +511 -0
  116. htmlgraph/collections/traces.py +487 -0
  117. htmlgraph/config/cost_models.json +56 -0
  118. htmlgraph/config.py +190 -0
  119. htmlgraph/context_analytics.py +344 -0
  120. htmlgraph/converter.py +216 -28
  121. htmlgraph/cost_analysis/__init__.py +5 -0
  122. htmlgraph/cost_analysis/analyzer.py +438 -0
  123. htmlgraph/dashboard.html +2406 -307
  124. htmlgraph/dashboard.html.backup +6592 -0
  125. htmlgraph/dashboard.html.bak +7181 -0
  126. htmlgraph/dashboard.html.bak2 +7231 -0
  127. htmlgraph/dashboard.html.bak3 +7232 -0
  128. htmlgraph/db/__init__.py +38 -0
  129. htmlgraph/db/queries.py +790 -0
  130. htmlgraph/db/schema.py +1788 -0
  131. htmlgraph/decorators.py +317 -0
  132. htmlgraph/dependency_models.py +19 -2
  133. htmlgraph/deploy.py +142 -125
  134. htmlgraph/deployment_models.py +474 -0
  135. htmlgraph/docs/API_REFERENCE.md +841 -0
  136. htmlgraph/docs/HTTP_API.md +750 -0
  137. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  138. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  139. htmlgraph/docs/README.md +532 -0
  140. htmlgraph/docs/__init__.py +77 -0
  141. htmlgraph/docs/docs_version.py +55 -0
  142. htmlgraph/docs/metadata.py +93 -0
  143. htmlgraph/docs/migrations.py +232 -0
  144. htmlgraph/docs/template_engine.py +143 -0
  145. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  146. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  147. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  148. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  149. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  150. htmlgraph/docs/version_check.py +163 -0
  151. htmlgraph/edge_index.py +182 -27
  152. htmlgraph/error_handler.py +544 -0
  153. htmlgraph/event_log.py +100 -52
  154. htmlgraph/event_migration.py +13 -4
  155. htmlgraph/exceptions.py +49 -0
  156. htmlgraph/file_watcher.py +101 -28
  157. htmlgraph/find_api.py +75 -63
  158. htmlgraph/git_events.py +145 -63
  159. htmlgraph/graph.py +1122 -106
  160. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  161. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  162. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  163. htmlgraph/hooks/__init__.py +45 -0
  164. htmlgraph/hooks/bootstrap.py +169 -0
  165. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  166. htmlgraph/hooks/concurrent_sessions.py +208 -0
  167. htmlgraph/hooks/context.py +350 -0
  168. htmlgraph/hooks/drift_handler.py +525 -0
  169. htmlgraph/hooks/event_tracker.py +1314 -0
  170. htmlgraph/hooks/git_commands.py +175 -0
  171. htmlgraph/hooks/hooks-config.example.json +12 -0
  172. htmlgraph/hooks/installer.py +343 -0
  173. htmlgraph/hooks/orchestrator.py +674 -0
  174. htmlgraph/hooks/orchestrator_reflector.py +223 -0
  175. htmlgraph/hooks/post-checkout.sh +28 -0
  176. htmlgraph/hooks/post-commit.sh +24 -0
  177. htmlgraph/hooks/post-merge.sh +26 -0
  178. htmlgraph/hooks/post_tool_use_failure.py +273 -0
  179. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  180. htmlgraph/hooks/posttooluse.py +408 -0
  181. htmlgraph/hooks/pre-commit.sh +94 -0
  182. htmlgraph/hooks/pre-push.sh +28 -0
  183. htmlgraph/hooks/pretooluse.py +819 -0
  184. htmlgraph/hooks/prompt_analyzer.py +637 -0
  185. htmlgraph/hooks/session_handler.py +668 -0
  186. htmlgraph/hooks/session_summary.py +395 -0
  187. htmlgraph/hooks/state_manager.py +504 -0
  188. htmlgraph/hooks/subagent_detection.py +202 -0
  189. htmlgraph/hooks/subagent_stop.py +369 -0
  190. htmlgraph/hooks/task_enforcer.py +255 -0
  191. htmlgraph/hooks/task_validator.py +177 -0
  192. htmlgraph/hooks/validator.py +628 -0
  193. htmlgraph/ids.py +41 -27
  194. htmlgraph/index.d.ts +286 -0
  195. htmlgraph/learning.py +767 -0
  196. htmlgraph/mcp_server.py +69 -23
  197. htmlgraph/models.py +1586 -87
  198. htmlgraph/operations/README.md +62 -0
  199. htmlgraph/operations/__init__.py +79 -0
  200. htmlgraph/operations/analytics.py +339 -0
  201. htmlgraph/operations/bootstrap.py +289 -0
  202. htmlgraph/operations/events.py +244 -0
  203. htmlgraph/operations/fastapi_server.py +231 -0
  204. htmlgraph/operations/hooks.py +350 -0
  205. htmlgraph/operations/initialization.py +597 -0
  206. htmlgraph/operations/initialization.py.backup +228 -0
  207. htmlgraph/operations/server.py +303 -0
  208. htmlgraph/orchestration/__init__.py +58 -0
  209. htmlgraph/orchestration/claude_launcher.py +179 -0
  210. htmlgraph/orchestration/command_builder.py +72 -0
  211. htmlgraph/orchestration/headless_spawner.py +281 -0
  212. htmlgraph/orchestration/live_events.py +377 -0
  213. htmlgraph/orchestration/model_selection.py +327 -0
  214. htmlgraph/orchestration/plugin_manager.py +140 -0
  215. htmlgraph/orchestration/prompts.py +137 -0
  216. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  217. htmlgraph/orchestration/spawners/__init__.py +16 -0
  218. htmlgraph/orchestration/spawners/base.py +194 -0
  219. htmlgraph/orchestration/spawners/claude.py +173 -0
  220. htmlgraph/orchestration/spawners/codex.py +435 -0
  221. htmlgraph/orchestration/spawners/copilot.py +294 -0
  222. htmlgraph/orchestration/spawners/gemini.py +471 -0
  223. htmlgraph/orchestration/subprocess_runner.py +36 -0
  224. htmlgraph/orchestration/task_coordination.py +343 -0
  225. htmlgraph/orchestration.md +563 -0
  226. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  227. htmlgraph/orchestrator.py +669 -0
  228. htmlgraph/orchestrator_config.py +357 -0
  229. htmlgraph/orchestrator_mode.py +328 -0
  230. htmlgraph/orchestrator_validator.py +133 -0
  231. htmlgraph/parallel.py +646 -0
  232. htmlgraph/parser.py +160 -35
  233. htmlgraph/path_query.py +608 -0
  234. htmlgraph/pattern_matcher.py +636 -0
  235. htmlgraph/planning.py +147 -52
  236. htmlgraph/pydantic_models.py +476 -0
  237. htmlgraph/quality_gates.py +350 -0
  238. htmlgraph/query_builder.py +109 -72
  239. htmlgraph/query_composer.py +509 -0
  240. htmlgraph/reflection.py +443 -0
  241. htmlgraph/refs.py +344 -0
  242. htmlgraph/repo_hash.py +512 -0
  243. htmlgraph/repositories/__init__.py +292 -0
  244. htmlgraph/repositories/analytics_repository.py +455 -0
  245. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  246. htmlgraph/repositories/feature_repository.py +581 -0
  247. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  248. htmlgraph/repositories/feature_repository_memory.py +607 -0
  249. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  250. htmlgraph/repositories/filter_service.py +620 -0
  251. htmlgraph/repositories/filter_service_standard.py +445 -0
  252. htmlgraph/repositories/shared_cache.py +621 -0
  253. htmlgraph/repositories/shared_cache_memory.py +395 -0
  254. htmlgraph/repositories/track_repository.py +552 -0
  255. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  256. htmlgraph/repositories/track_repository_memory.py +508 -0
  257. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  258. htmlgraph/routing.py +8 -19
  259. htmlgraph/scripts/deploy.py +1 -2
  260. htmlgraph/sdk/__init__.py +398 -0
  261. htmlgraph/sdk/__init__.pyi +14 -0
  262. htmlgraph/sdk/analytics/__init__.py +19 -0
  263. htmlgraph/sdk/analytics/engine.py +155 -0
  264. htmlgraph/sdk/analytics/helpers.py +178 -0
  265. htmlgraph/sdk/analytics/registry.py +109 -0
  266. htmlgraph/sdk/base.py +484 -0
  267. htmlgraph/sdk/constants.py +216 -0
  268. htmlgraph/sdk/core.pyi +308 -0
  269. htmlgraph/sdk/discovery.py +120 -0
  270. htmlgraph/sdk/help/__init__.py +12 -0
  271. htmlgraph/sdk/help/mixin.py +699 -0
  272. htmlgraph/sdk/mixins/__init__.py +15 -0
  273. htmlgraph/sdk/mixins/attribution.py +113 -0
  274. htmlgraph/sdk/mixins/mixin.py +410 -0
  275. htmlgraph/sdk/operations/__init__.py +12 -0
  276. htmlgraph/sdk/operations/mixin.py +427 -0
  277. htmlgraph/sdk/orchestration/__init__.py +17 -0
  278. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  279. htmlgraph/sdk/orchestration/spawner.py +204 -0
  280. htmlgraph/sdk/planning/__init__.py +19 -0
  281. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  282. htmlgraph/sdk/planning/mixin.py +211 -0
  283. htmlgraph/sdk/planning/parallel.py +186 -0
  284. htmlgraph/sdk/planning/queue.py +210 -0
  285. htmlgraph/sdk/planning/recommendations.py +87 -0
  286. htmlgraph/sdk/planning/smart_planning.py +319 -0
  287. htmlgraph/sdk/session/__init__.py +19 -0
  288. htmlgraph/sdk/session/continuity.py +57 -0
  289. htmlgraph/sdk/session/handoff.py +110 -0
  290. htmlgraph/sdk/session/info.py +309 -0
  291. htmlgraph/sdk/session/manager.py +103 -0
  292. htmlgraph/sdk/strategic/__init__.py +26 -0
  293. htmlgraph/sdk/strategic/mixin.py +563 -0
  294. htmlgraph/server.py +685 -180
  295. htmlgraph/services/__init__.py +10 -0
  296. htmlgraph/services/claiming.py +199 -0
  297. htmlgraph/session_hooks.py +300 -0
  298. htmlgraph/session_manager.py +1392 -175
  299. htmlgraph/session_registry.py +587 -0
  300. htmlgraph/session_state.py +436 -0
  301. htmlgraph/session_warning.py +201 -0
  302. htmlgraph/sessions/__init__.py +23 -0
  303. htmlgraph/sessions/handoff.py +756 -0
  304. htmlgraph/setup.py +34 -17
  305. htmlgraph/spike_index.py +143 -0
  306. htmlgraph/sync_docs.py +12 -15
  307. htmlgraph/system_prompts.py +450 -0
  308. htmlgraph/templates/AGENTS.md.template +366 -0
  309. htmlgraph/templates/CLAUDE.md.template +97 -0
  310. htmlgraph/templates/GEMINI.md.template +87 -0
  311. htmlgraph/templates/orchestration-view.html +350 -0
  312. htmlgraph/track_builder.py +146 -15
  313. htmlgraph/track_manager.py +69 -21
  314. htmlgraph/transcript.py +890 -0
  315. htmlgraph/transcript_analytics.py +699 -0
  316. htmlgraph/types.py +323 -0
  317. htmlgraph/validation.py +115 -0
  318. htmlgraph/watch.py +8 -5
  319. htmlgraph/work_type_utils.py +3 -2
  320. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
  321. htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
  322. htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
  323. htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
  324. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
  325. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  326. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  327. htmlgraph/cli.py +0 -2688
  328. htmlgraph/sdk.py +0 -709
  329. htmlgraph-0.9.3.dist-info/RECORD +0 -61
  330. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  331. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,350 @@
1
+ """
2
+ Quality Gates Module - Comprehensive Validation for SDK Operations
3
+
4
+ Provides Pydantic-based validators and quality checks for:
5
+ - Builder arguments (title, description, priority)
6
+ - SDK operations (feature creation, spike creation, task spawning)
7
+ - Work item metadata (required fields, completion criteria)
8
+ - Code quality markers (TODO/FIXME detection)
9
+
10
+ This module enforces minimum quality standards before allowing
11
+ SDK operations to proceed, preventing incomplete or invalid work items.
12
+ """
13
+
14
+ from typing import Any, Literal
15
+
16
+ from pydantic import BaseModel, Field, field_validator
17
+
18
+
19
+ class QualityGateBase(BaseModel):
20
+ """Base class for quality gate validation models."""
21
+
22
+ class Config:
23
+ extra = "forbid" # No extra fields allowed
24
+ str_strip_whitespace = True
25
+ validate_default = True
26
+
27
+
28
+ # =============================================================================
29
+ # Feature Creation Quality Gates
30
+ # =============================================================================
31
+
32
+
33
+ class FeatureQualityGate(QualityGateBase):
34
+ """Quality validation for feature creation operations.
35
+
36
+ Ensures:
37
+ - Title is present and non-empty
38
+ - Description (if provided) is meaningful
39
+ - Priority is valid (low, medium, high, critical)
40
+ - Agent assignment (if available)
41
+ """
42
+
43
+ title: str = Field(..., min_length=1, max_length=200)
44
+ description: str | None = Field(default=None, max_length=1000)
45
+ priority: Literal["low", "medium", "high", "critical"] = Field(default="medium")
46
+ status: Literal["todo", "in_progress", "blocked", "done"] = Field(default="todo")
47
+ agent_assigned: str | None = Field(default=None)
48
+
49
+ @field_validator("title")
50
+ @classmethod
51
+ def validate_title(cls, v: str) -> str:
52
+ """Ensure title is non-empty and meaningful."""
53
+ stripped = v.strip()
54
+ if not stripped:
55
+ raise ValueError("Feature title cannot be empty or whitespace only")
56
+ if len(stripped) < 3:
57
+ raise ValueError("Feature title must be at least 3 characters")
58
+ if stripped.lower().startswith(("todo", "fixme", "wip")):
59
+ raise ValueError(
60
+ f"Feature title cannot start with placeholder text: {stripped[:10]}"
61
+ )
62
+ return stripped
63
+
64
+ @field_validator("description")
65
+ @classmethod
66
+ def validate_description(cls, v: str | None) -> str | None:
67
+ """Ensure description is meaningful if provided."""
68
+ if v is not None:
69
+ stripped = v.strip()
70
+ if not stripped:
71
+ return None
72
+ if len(stripped) < 5:
73
+ raise ValueError(
74
+ "Description must be at least 5 characters if provided"
75
+ )
76
+ return stripped
77
+ return None
78
+
79
+ @field_validator("agent_assigned")
80
+ @classmethod
81
+ def validate_agent(cls, v: str | None) -> str | None:
82
+ """Ensure agent is assigned for tracking."""
83
+ if v is not None:
84
+ stripped = v.strip()
85
+ if not stripped:
86
+ return None
87
+ return stripped
88
+ return None
89
+
90
+
91
+ # =============================================================================
92
+ # Spike Creation Quality Gates
93
+ # =============================================================================
94
+
95
+
96
+ class SpikeQualityGate(QualityGateBase):
97
+ """Quality validation for spike creation operations.
98
+
99
+ Ensures:
100
+ - Title is present and non-empty
101
+ - Findings (if set) are non-empty
102
+ - Timebox is reasonable (1-40 hours)
103
+ - Spike type is valid
104
+ - Agent assignment for tracking
105
+ """
106
+
107
+ title: str = Field(..., min_length=1, max_length=200)
108
+ findings: str | None = Field(default=None, max_length=5000)
109
+ decision: str | None = Field(default=None, max_length=500)
110
+ timebox_hours: int = Field(default=4, ge=1, le=40)
111
+ spike_type: Literal["technical", "architectural", "risk", "research", "general"] = (
112
+ Field(default="general")
113
+ )
114
+ priority: Literal["low", "medium", "high", "critical"] = Field(default="medium")
115
+ agent_assigned: str | None = Field(default=None)
116
+
117
+ @field_validator("title")
118
+ @classmethod
119
+ def validate_title(cls, v: str) -> str:
120
+ """Ensure spike title is meaningful."""
121
+ stripped = v.strip()
122
+ if not stripped:
123
+ raise ValueError("Spike title cannot be empty or whitespace only")
124
+ if len(stripped) < 5:
125
+ raise ValueError("Spike title must be at least 5 characters")
126
+ return stripped
127
+
128
+ @field_validator("findings")
129
+ @classmethod
130
+ def validate_findings(cls, v: str | None) -> str | None:
131
+ """Ensure findings are non-empty if set."""
132
+ if v is not None:
133
+ stripped = v.strip()
134
+ if not stripped:
135
+ return None
136
+ if len(stripped) < 10:
137
+ raise ValueError("Findings must be at least 10 characters if provided")
138
+ return stripped
139
+ return None
140
+
141
+ @field_validator("decision")
142
+ @classmethod
143
+ def validate_decision(cls, v: str | None) -> str | None:
144
+ """Ensure decision is meaningful if set."""
145
+ if v is not None:
146
+ stripped = v.strip()
147
+ if not stripped:
148
+ return None
149
+ if len(stripped) < 5:
150
+ raise ValueError("Decision must be at least 5 characters if provided")
151
+ return stripped
152
+ return None
153
+
154
+ @field_validator("timebox_hours")
155
+ @classmethod
156
+ def validate_timebox(cls, v: int) -> int:
157
+ """Ensure timebox is reasonable."""
158
+ if v < 1:
159
+ raise ValueError("Timebox must be at least 1 hour")
160
+ if v > 40:
161
+ raise ValueError("Timebox should not exceed 40 hours")
162
+ return v
163
+
164
+
165
+ # =============================================================================
166
+ # Task/Subtask Quality Gates
167
+ # =============================================================================
168
+
169
+
170
+ class TaskQualityGate(QualityGateBase):
171
+ """Quality validation for task creation/spawning operations.
172
+
173
+ Ensures:
174
+ - Description is present and meaningful
175
+ - Task type is specified
176
+ - Priority is valid
177
+ - Agent type specified for spawning
178
+ """
179
+
180
+ description: str = Field(..., min_length=1, max_length=2000)
181
+ task_type: Literal["feature", "bug", "chore", "refactor", "test"] = Field(
182
+ default="feature"
183
+ )
184
+ priority: Literal["low", "medium", "high", "critical"] = Field(default="medium")
185
+ agent_type: str | None = Field(
186
+ default=None, description="Agent type for spawning (e.g., 'sonnet', 'claude')"
187
+ )
188
+ status: Literal["pending", "in_progress", "blocked", "completed"] = Field(
189
+ default="pending"
190
+ )
191
+
192
+ @field_validator("description")
193
+ @classmethod
194
+ def validate_description(cls, v: str) -> str:
195
+ """Ensure description is meaningful."""
196
+ stripped = v.strip()
197
+ if not stripped:
198
+ raise ValueError("Task description cannot be empty")
199
+ if len(stripped) < 10:
200
+ raise ValueError("Task description must be at least 10 characters")
201
+ if stripped.lower().startswith(("todo", "fixme", "wip")):
202
+ raise ValueError(
203
+ f"Task description cannot start with placeholder: {stripped[:15]}"
204
+ )
205
+ return stripped
206
+
207
+ @field_validator("agent_type")
208
+ @classmethod
209
+ def validate_agent_type(cls, v: str | None) -> str | None:
210
+ """Ensure agent type is valid if specified."""
211
+ if v is not None:
212
+ stripped = v.strip().lower()
213
+ valid_types = {"sonnet", "claude", "opus", "haiku", "gpt4", "gemini"}
214
+ if stripped not in valid_types:
215
+ raise ValueError(
216
+ f"Invalid agent type '{stripped}'. Must be one of: {', '.join(valid_types)}"
217
+ )
218
+ return stripped
219
+ return None
220
+
221
+
222
+ # =============================================================================
223
+ # Code Quality Markers Detection
224
+ # =============================================================================
225
+
226
+
227
+ class CodeQualityMarkers:
228
+ """Detect incomplete work markers in code."""
229
+
230
+ INCOMPLETE_PATTERNS = {
231
+ "TODO": r"(?:^|\s)TODO\b",
232
+ "FIXME": r"(?:^|\s)FIXME\b",
233
+ "WIP": r"(?:^|\s)WIP\b",
234
+ "XXX": r"(?:^|\s)XXX\b",
235
+ "HACK": r"(?:^|\s)HACK\b",
236
+ }
237
+
238
+ @staticmethod
239
+ def detect_markers(content: str) -> dict[str, list[tuple[int, str]]]:
240
+ """
241
+ Detect incomplete work markers in code content.
242
+
243
+ Args:
244
+ content: Code content to scan
245
+
246
+ Returns:
247
+ Dictionary mapping marker type to list of (line_no, line_content) tuples
248
+
249
+ Example:
250
+ >>> markers = CodeQualityMarkers.detect_markers(code)
251
+ >>> if markers["TODO"]:
252
+ ... print(f"Found {len(markers['TODO'])} TODO items")
253
+ """
254
+ import re
255
+
256
+ results: dict[str, list[tuple[int, str]]] = {
257
+ marker: [] for marker in CodeQualityMarkers.INCOMPLETE_PATTERNS
258
+ }
259
+
260
+ for line_no, line in enumerate(content.splitlines(), start=1):
261
+ # Skip comments that are documentation examples
262
+ if line.strip().startswith('"""') or line.strip().startswith("'''"):
263
+ continue
264
+
265
+ for marker, pattern in CodeQualityMarkers.INCOMPLETE_PATTERNS.items():
266
+ if re.search(pattern, line):
267
+ results[marker].append((line_no, line.strip()))
268
+
269
+ return results
270
+
271
+ @staticmethod
272
+ def has_incomplete_markers(content: str) -> bool:
273
+ """Check if content has any incomplete work markers."""
274
+ markers = CodeQualityMarkers.detect_markers(content)
275
+ return any(items for items in markers.values())
276
+
277
+
278
+ # =============================================================================
279
+ # Validation Utilities
280
+ # =============================================================================
281
+
282
+
283
+ def validate_feature_args(**kwargs: Any) -> FeatureQualityGate:
284
+ """
285
+ Validate feature creation arguments.
286
+
287
+ Raises:
288
+ ValueError: If validation fails
289
+
290
+ Example:
291
+ >>> gate = validate_feature_args(
292
+ ... title="Add user authentication",
293
+ ... priority="high",
294
+ ... agent_assigned="claude"
295
+ ... )
296
+ """
297
+ return FeatureQualityGate(**kwargs)
298
+
299
+
300
+ def validate_spike_args(**kwargs: Any) -> SpikeQualityGate:
301
+ """
302
+ Validate spike creation arguments.
303
+
304
+ Raises:
305
+ ValueError: If validation fails
306
+
307
+ Example:
308
+ >>> gate = validate_spike_args(
309
+ ... title="Research OAuth providers",
310
+ ... findings="OAuth2 is best fit",
311
+ ... timebox_hours=4
312
+ ... )
313
+ """
314
+ return SpikeQualityGate(**kwargs)
315
+
316
+
317
+ def validate_task_args(**kwargs: Any) -> TaskQualityGate:
318
+ """
319
+ Validate task creation/spawning arguments.
320
+
321
+ Raises:
322
+ ValueError: If validation fails
323
+
324
+ Example:
325
+ >>> gate = validate_task_args(
326
+ ... description="Implement user registration flow",
327
+ ... priority="high",
328
+ ... agent_type="sonnet"
329
+ ... )
330
+ """
331
+ return TaskQualityGate(**kwargs)
332
+
333
+
334
+ def validate_code_quality(file_path: str) -> bool:
335
+ """
336
+ Validate code file for quality issues.
337
+
338
+ Returns:
339
+ True if no incomplete markers found, False otherwise
340
+
341
+ Example:
342
+ >>> if validate_code_quality("src/mymodule.py"):
343
+ ... print("Code quality check passed")
344
+ """
345
+ try:
346
+ with open(file_path, encoding="utf-8") as f:
347
+ content = f.read()
348
+ return not CodeQualityMarkers.has_incomplete_markers(content)
349
+ except OSError:
350
+ return True # Skip if file can't be read