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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +192 -100
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +190 -14
- htmlgraph/analytics_index.py +135 -51
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +45 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/orchestration/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {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
|