monoco-toolkit 0.3.11__py3-none-any.whl → 0.4.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.
- monoco/core/automation/__init__.py +40 -0
- monoco/core/automation/field_watcher.py +296 -0
- monoco/core/automation/handlers.py +805 -0
- monoco/core/config.py +29 -11
- monoco/core/daemon/__init__.py +5 -0
- monoco/core/daemon/pid.py +290 -0
- monoco/core/git.py +15 -0
- monoco/core/hooks/context.py +74 -13
- monoco/core/injection.py +86 -8
- monoco/core/integrations.py +0 -24
- monoco/core/router/__init__.py +17 -0
- monoco/core/router/action.py +202 -0
- monoco/core/scheduler/__init__.py +63 -0
- monoco/core/scheduler/base.py +152 -0
- monoco/core/scheduler/engines.py +175 -0
- monoco/core/scheduler/events.py +197 -0
- monoco/core/scheduler/local.py +377 -0
- monoco/core/setup.py +9 -0
- monoco/core/sync.py +199 -4
- monoco/core/watcher/__init__.py +63 -0
- monoco/core/watcher/base.py +382 -0
- monoco/core/watcher/dropzone.py +152 -0
- monoco/core/watcher/im.py +460 -0
- monoco/core/watcher/issue.py +303 -0
- monoco/core/watcher/memo.py +192 -0
- monoco/core/watcher/task.py +238 -0
- monoco/daemon/app.py +3 -60
- monoco/daemon/commands.py +459 -25
- monoco/daemon/events.py +34 -0
- monoco/daemon/scheduler.py +157 -201
- monoco/daemon/services.py +42 -243
- monoco/features/agent/__init__.py +25 -7
- monoco/features/agent/cli.py +91 -57
- monoco/features/agent/engines.py +31 -170
- monoco/features/agent/resources/en/AGENTS.md +14 -14
- monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/worker.py +1 -1
- monoco/features/hooks/__init__.py +61 -6
- monoco/features/hooks/commands.py +281 -271
- monoco/features/hooks/dispatchers/__init__.py +23 -0
- monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
- monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
- monoco/features/hooks/manager.py +357 -0
- monoco/features/hooks/models.py +262 -0
- monoco/features/hooks/parser.py +322 -0
- monoco/features/hooks/universal_interceptor.py +503 -0
- monoco/features/im/__init__.py +67 -0
- monoco/features/im/core.py +782 -0
- monoco/features/im/models.py +311 -0
- monoco/features/issue/commands.py +133 -60
- monoco/features/issue/core.py +385 -40
- monoco/features/issue/domain_commands.py +0 -19
- monoco/features/issue/resources/en/AGENTS.md +17 -122
- monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
- monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
- monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
- monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
- monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
- monoco/features/issue/resources/zh/AGENTS.md +18 -123
- monoco/features/memo/cli.py +15 -64
- monoco/features/memo/core.py +6 -34
- monoco/features/memo/models.py +24 -15
- monoco/features/memo/resources/en/AGENTS.md +31 -0
- monoco/features/memo/resources/zh/AGENTS.md +28 -5
- monoco/features/spike/commands.py +5 -3
- monoco/main.py +5 -3
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
- monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
- monoco/core/execution.py +0 -67
- monoco/features/agent/apoptosis.py +0 -44
- monoco/features/agent/manager.py +0 -127
- monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
- monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
- monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
- monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
- monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
- monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
- monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
- monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
- monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
- monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
- monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
- monoco/features/agent/session.py +0 -169
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
- monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/hooks/adapter.py +0 -67
- monoco/features/hooks/core.py +0 -441
- monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
- monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco_toolkit-0.3.11.dist-info/RECORD +0 -181
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,66 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Universal Hooks management feature for Monoco.
|
|
3
3
|
|
|
4
|
-
Provides
|
|
5
|
-
|
|
4
|
+
Provides a unified hooks system for Git, IDE, and Agent integration
|
|
5
|
+
with Front Matter metadata support.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .
|
|
9
|
-
|
|
8
|
+
from .models import (
|
|
9
|
+
HookType,
|
|
10
|
+
HookMetadata,
|
|
11
|
+
GitEvent,
|
|
12
|
+
AgentEvent,
|
|
13
|
+
IDEEvent,
|
|
14
|
+
ParsedHook,
|
|
15
|
+
HookGroup,
|
|
16
|
+
)
|
|
17
|
+
from .parser import HookParser, ParseError
|
|
18
|
+
from .manager import UniversalHookManager, ValidationResult, HookDispatcher
|
|
19
|
+
from .dispatchers import (
|
|
20
|
+
GitHookDispatcher,
|
|
21
|
+
AgentHookDispatcher,
|
|
22
|
+
ClaudeCodeDispatcher,
|
|
23
|
+
GeminiDispatcher,
|
|
24
|
+
create_agent_dispatchers,
|
|
25
|
+
get_dispatcher_for_provider,
|
|
26
|
+
)
|
|
27
|
+
from .universal_interceptor import (
|
|
28
|
+
UniversalInterceptor,
|
|
29
|
+
AgentAdapter,
|
|
30
|
+
ClaudeAdapter,
|
|
31
|
+
GeminiAdapter,
|
|
32
|
+
UnifiedDecision,
|
|
33
|
+
UnifiedHookInput,
|
|
34
|
+
)
|
|
10
35
|
|
|
11
|
-
__all__ = [
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Models
|
|
38
|
+
"HookType",
|
|
39
|
+
"HookMetadata",
|
|
40
|
+
"GitEvent",
|
|
41
|
+
"AgentEvent",
|
|
42
|
+
"IDEEvent",
|
|
43
|
+
"ParsedHook",
|
|
44
|
+
"HookGroup",
|
|
45
|
+
# Parser
|
|
46
|
+
"HookParser",
|
|
47
|
+
"ParseError",
|
|
48
|
+
# Manager
|
|
49
|
+
"UniversalHookManager",
|
|
50
|
+
"ValidationResult",
|
|
51
|
+
"HookDispatcher",
|
|
52
|
+
# Dispatchers
|
|
53
|
+
"GitHookDispatcher",
|
|
54
|
+
"AgentHookDispatcher",
|
|
55
|
+
"ClaudeCodeDispatcher",
|
|
56
|
+
"GeminiDispatcher",
|
|
57
|
+
"create_agent_dispatchers",
|
|
58
|
+
"get_dispatcher_for_provider",
|
|
59
|
+
# Universal Interceptor (ACL)
|
|
60
|
+
"UniversalInterceptor",
|
|
61
|
+
"AgentAdapter",
|
|
62
|
+
"ClaudeAdapter",
|
|
63
|
+
"GeminiAdapter",
|
|
64
|
+
"UnifiedDecision",
|
|
65
|
+
"UnifiedHookInput",
|
|
66
|
+
]
|
|
@@ -1,309 +1,319 @@
|
|
|
1
1
|
"""
|
|
2
|
-
CLI commands for
|
|
2
|
+
CLI commands for Universal Hooks management.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
- monoco hooks install
|
|
6
|
-
- monoco hooks uninstall
|
|
7
|
-
- monoco hooks status
|
|
4
|
+
This module provides commands for managing Universal Hooks (Git/IDE/Agent).
|
|
8
5
|
"""
|
|
9
6
|
|
|
10
|
-
import typer
|
|
11
7
|
from pathlib import Path
|
|
12
8
|
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import typer
|
|
13
11
|
from rich.console import Console
|
|
14
|
-
from rich.table import Table
|
|
15
|
-
from rich.panel import Panel
|
|
16
|
-
from rich.tree import Tree
|
|
17
12
|
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from .
|
|
13
|
+
from .manager import UniversalHookManager
|
|
14
|
+
from .models import HookType
|
|
15
|
+
from .dispatchers import GitHookDispatcher
|
|
21
16
|
|
|
22
|
-
app = typer.Typer(help="
|
|
17
|
+
app = typer.Typer(help="Universal Hooks management (Git/IDE/Agent).")
|
|
23
18
|
console = Console()
|
|
24
19
|
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
@app.command("scan")
|
|
22
|
+
def scan(
|
|
23
|
+
directory: Path = typer.Option(
|
|
24
|
+
".monoco/hooks",
|
|
25
|
+
"--dir",
|
|
26
|
+
"-d",
|
|
27
|
+
help="Directory to scan for hook scripts",
|
|
28
|
+
),
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Scan for hook scripts with Front Matter metadata."""
|
|
31
|
+
manager = UniversalHookManager()
|
|
32
|
+
dir_path = Path(directory)
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
enabled_hooks=config.hooks.hooks,
|
|
35
|
-
)
|
|
34
|
+
if not dir_path.exists():
|
|
35
|
+
console.print(f"[yellow]Directory not found: {dir_path}[/yellow]")
|
|
36
|
+
raise typer.Exit(1)
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
groups = manager.scan(dir_path)
|
|
38
39
|
|
|
40
|
+
if not groups:
|
|
41
|
+
console.print("[dim]No hooks found.[/dim]")
|
|
42
|
+
return
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
for key, group in groups.items():
|
|
45
|
+
console.print(f"\n[bold]{key}[/bold] ({len(group.hooks)} hooks)")
|
|
46
|
+
for hook in group.get_prioritized_hooks():
|
|
47
|
+
console.print(f" • {hook.script_path.name}")
|
|
48
|
+
console.print(f" Event: {hook.metadata.event}")
|
|
49
|
+
if hook.metadata.matcher:
|
|
50
|
+
console.print(f" Matchers: {', '.join(hook.metadata.matcher)}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command("validate")
|
|
54
|
+
def validate(
|
|
55
|
+
directory: Path = typer.Option(
|
|
56
|
+
".monoco/hooks",
|
|
57
|
+
"--dir",
|
|
58
|
+
"-d",
|
|
59
|
+
help="Directory to validate hook scripts",
|
|
51
60
|
),
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Validate hook metadata."""
|
|
63
|
+
manager = UniversalHookManager()
|
|
64
|
+
dir_path = Path(directory)
|
|
65
|
+
|
|
66
|
+
if not dir_path.exists():
|
|
67
|
+
console.print(f"[yellow]Directory not found: {dir_path}[/yellow]")
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
|
|
70
|
+
groups = manager.scan(dir_path)
|
|
71
|
+
errors_found = False
|
|
72
|
+
|
|
73
|
+
for key, group in groups.items():
|
|
74
|
+
for hook in group.hooks:
|
|
75
|
+
result = manager.validate(hook)
|
|
76
|
+
if not result.is_valid:
|
|
77
|
+
errors_found = True
|
|
78
|
+
console.print(f"\n[red]✗ {hook.script_path}[/red]")
|
|
79
|
+
for error in result.errors:
|
|
80
|
+
console.print(f" Error: {error}")
|
|
81
|
+
for warning in result.warnings:
|
|
82
|
+
console.print(f" Warning: {warning}")
|
|
83
|
+
elif result.warnings:
|
|
84
|
+
console.print(f"\n[yellow]⚠ {hook.script_path}[/yellow]")
|
|
85
|
+
for warning in result.warnings:
|
|
86
|
+
console.print(f" Warning: {warning}")
|
|
87
|
+
|
|
88
|
+
if not errors_found:
|
|
89
|
+
console.print("[green]✓ All hooks are valid[/green]")
|
|
90
|
+
else:
|
|
91
|
+
raise typer.Exit(1)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@app.command("run")
|
|
95
|
+
def run(
|
|
96
|
+
hook_type: str = typer.Argument(..., help="Hook type (git, ide, agent)"),
|
|
97
|
+
event: str = typer.Argument(..., help="Hook event (e.g., pre-commit, before-tool)"),
|
|
98
|
+
project_root: Path = typer.Option(
|
|
99
|
+
".",
|
|
100
|
+
"--project",
|
|
101
|
+
"-p",
|
|
102
|
+
help="Project root directory",
|
|
56
103
|
),
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
104
|
+
extra_args: Optional[list[str]] = typer.Argument(None),
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Run hooks for a specific type and event."""
|
|
107
|
+
try:
|
|
108
|
+
hook_type_enum = HookType(hook_type.lower())
|
|
109
|
+
except ValueError:
|
|
110
|
+
console.print(f"[red]Invalid hook type: {hook_type}[/red]")
|
|
111
|
+
console.print(f"Valid types: git, ide, agent")
|
|
112
|
+
raise typer.Exit(1)
|
|
113
|
+
|
|
114
|
+
manager = UniversalHookManager()
|
|
60
115
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
116
|
+
# Discover hooks from all active features
|
|
117
|
+
from monoco.core.registry import FeatureRegistry
|
|
118
|
+
active_features = FeatureRegistry.get_features()
|
|
64
119
|
|
|
65
|
-
|
|
66
|
-
OutputManager.error("Not a git repository. Cannot install hooks.")
|
|
67
|
-
raise typer.Exit(code=1)
|
|
120
|
+
all_hooks_list = []
|
|
68
121
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
from .
|
|
122
|
+
# 1. Builtin hooks
|
|
123
|
+
try:
|
|
124
|
+
from monoco.features import hooks as hooks_module
|
|
125
|
+
hooks_feature_dir = Path(hooks_module.__file__).parent
|
|
126
|
+
builtin_hooks_dir = hooks_feature_dir / "resources" / "hooks"
|
|
127
|
+
if builtin_hooks_dir.exists():
|
|
128
|
+
groups = manager.scan(builtin_hooks_dir)
|
|
129
|
+
for group in groups.values():
|
|
130
|
+
all_hooks_list.extend(group.hooks)
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
# 2. Feature hooks
|
|
135
|
+
for feature in active_features:
|
|
136
|
+
if feature.name == "hooks":
|
|
137
|
+
continue
|
|
72
138
|
try:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
139
|
+
import importlib
|
|
140
|
+
module_name = feature.__class__.__module__
|
|
141
|
+
module = importlib.import_module(module_name)
|
|
142
|
+
if hasattr(module, "__file__") and module.__file__:
|
|
143
|
+
feature_dir = Path(module.__file__).parent
|
|
144
|
+
hooks_resource_dir = feature_dir / "resources" / "hooks"
|
|
145
|
+
if hooks_resource_dir.exists():
|
|
146
|
+
groups = manager.scan(hooks_resource_dir)
|
|
147
|
+
for group in groups.values():
|
|
148
|
+
all_hooks_list.extend(group.hooks)
|
|
149
|
+
except Exception:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
# Find matching hooks
|
|
153
|
+
matching_hooks = [
|
|
154
|
+
h for h in all_hooks_list
|
|
155
|
+
if h.metadata.type == hook_type_enum and h.metadata.event == event
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
if not matching_hooks:
|
|
159
|
+
raise typer.Exit(0)
|
|
160
|
+
|
|
161
|
+
# Execute matching hooks
|
|
162
|
+
if hook_type_enum == HookType.AGENT:
|
|
163
|
+
# Use Universal Interceptor for agent hooks
|
|
164
|
+
from .universal_interceptor import UniversalInterceptor
|
|
165
|
+
|
|
166
|
+
interceptor = UniversalInterceptor()
|
|
167
|
+
exit_code = 0
|
|
168
|
+
|
|
169
|
+
for hook in matching_hooks:
|
|
170
|
+
result = interceptor.run(str(hook.script_path))
|
|
171
|
+
if result != 0:
|
|
172
|
+
exit_code = result
|
|
173
|
+
console.print(f"[red]Hook failed: {hook.script_path.name}[/red]")
|
|
174
|
+
|
|
175
|
+
raise typer.Exit(exit_code)
|
|
89
176
|
else:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
177
|
+
# Use dispatcher for Git/IDE hooks
|
|
178
|
+
dispatcher = manager.get_dispatcher(hook_type_enum)
|
|
179
|
+
if not dispatcher:
|
|
180
|
+
# Register appropriate dispatcher
|
|
181
|
+
if hook_type_enum == HookType.GIT:
|
|
182
|
+
dispatcher = GitHookDispatcher()
|
|
183
|
+
manager.register_dispatcher(hook_type_enum, dispatcher)
|
|
184
|
+
else:
|
|
185
|
+
console.print(f"[red]No dispatcher available for type: {hook_type}[/red]")
|
|
186
|
+
raise typer.Exit(1)
|
|
187
|
+
|
|
188
|
+
exit_code = 0
|
|
189
|
+
for hook in matching_hooks:
|
|
190
|
+
context = {
|
|
191
|
+
"event": event,
|
|
192
|
+
"git_root": str(project_root),
|
|
193
|
+
"args": extra_args or [],
|
|
194
|
+
}
|
|
195
|
+
if not dispatcher.execute(hook, context):
|
|
196
|
+
exit_code = 1
|
|
197
|
+
console.print(f"[red]Hook failed: {hook.script_path.name}[/red]")
|
|
198
|
+
|
|
199
|
+
raise typer.Exit(exit_code)
|
|
97
200
|
|
|
98
201
|
|
|
99
|
-
@app.command("
|
|
100
|
-
def
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
False,
|
|
107
|
-
"--json",
|
|
108
|
-
help="Output in JSON format (for agent mode)",
|
|
202
|
+
@app.command("install")
|
|
203
|
+
def install(
|
|
204
|
+
directory: Path = typer.Option(
|
|
205
|
+
".monoco/hooks",
|
|
206
|
+
"--dir",
|
|
207
|
+
"-d",
|
|
208
|
+
help="Directory containing hook scripts",
|
|
109
209
|
),
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"""
|
|
116
|
-
manager = _get_manager()
|
|
117
|
-
|
|
118
|
-
if not manager.is_git_repo():
|
|
119
|
-
OutputManager.error("Not a git repository.")
|
|
120
|
-
raise typer.Exit(code=1)
|
|
121
|
-
|
|
122
|
-
if hook_type:
|
|
123
|
-
# Uninstall specific hook type
|
|
124
|
-
hook_path = manager.hooks_dir / hook_type
|
|
125
|
-
if hook_path.exists():
|
|
126
|
-
try:
|
|
127
|
-
content = hook_path.read_text(encoding="utf-8")
|
|
128
|
-
if manager.MONOCO_MARKER in content:
|
|
129
|
-
hook_path.unlink()
|
|
130
|
-
results = {hook_type: True}
|
|
131
|
-
else:
|
|
132
|
-
results = {hook_type: False}
|
|
133
|
-
except Exception as e:
|
|
134
|
-
OutputManager.error(f"Failed to uninstall {hook_type}: {e}")
|
|
135
|
-
raise typer.Exit(code=1)
|
|
136
|
-
else:
|
|
137
|
-
results = {hook_type: False}
|
|
138
|
-
else:
|
|
139
|
-
results = manager.uninstall()
|
|
140
|
-
|
|
141
|
-
if OutputManager.is_agent_mode() or json:
|
|
142
|
-
OutputManager.print({
|
|
143
|
-
"status": "uninstalled",
|
|
144
|
-
"hooks": {k.value if hasattr(k, 'value') else k: v for k, v in results.items()}
|
|
145
|
-
})
|
|
146
|
-
else:
|
|
147
|
-
removed = [k.value if hasattr(k, 'value') else k for k, v in results.items() if v]
|
|
148
|
-
skipped = [k.value if hasattr(k, 'value') else k for k, v in results.items() if not v]
|
|
149
|
-
|
|
150
|
-
if removed:
|
|
151
|
-
console.print(f"[green]✓ Removed hooks:[/green] {', '.join(removed)}")
|
|
152
|
-
if skipped:
|
|
153
|
-
console.print(f"[dim]- Skipped (not managed by Monoco):[/dim] {', '.join(skipped)}")
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
@app.command("status")
|
|
157
|
-
def status(
|
|
158
|
-
json: bool = typer.Option(
|
|
159
|
-
False,
|
|
160
|
-
"--json",
|
|
161
|
-
help="Output in JSON format (for agent mode)",
|
|
210
|
+
project_root: Path = typer.Option(
|
|
211
|
+
".",
|
|
212
|
+
"--project",
|
|
213
|
+
"-p",
|
|
214
|
+
help="Project root directory",
|
|
162
215
|
),
|
|
163
|
-
):
|
|
164
|
-
"""
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Install Git hooks to .git/hooks/."""
|
|
218
|
+
manager = UniversalHookManager()
|
|
219
|
+
dispatcher = GitHookDispatcher()
|
|
220
|
+
manager.register_dispatcher(HookType.GIT, dispatcher)
|
|
221
|
+
|
|
222
|
+
dir_path = Path(directory)
|
|
223
|
+
if not dir_path.exists():
|
|
224
|
+
console.print(f"[yellow]Hooks directory not found: {dir_path}[/yellow]")
|
|
225
|
+
raise typer.Exit(1)
|
|
226
|
+
|
|
227
|
+
# Scan for git hooks
|
|
228
|
+
groups = manager.scan(dir_path)
|
|
229
|
+
|
|
230
|
+
if "git" not in groups:
|
|
231
|
+
console.print("[dim]No Git hooks found.[/dim]")
|
|
174
232
|
return
|
|
175
233
|
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
console.print(Panel("[bold blue]Git Hooks Status[/bold blue]", expand=False))
|
|
182
|
-
|
|
183
|
-
# Installed hooks table
|
|
184
|
-
table = Table(title="Installed Hooks")
|
|
185
|
-
table.add_column("Hook Type", style="cyan")
|
|
186
|
-
table.add_column("Status", style="green")
|
|
187
|
-
table.add_column("Managed By", style="dim")
|
|
234
|
+
# Install hooks
|
|
235
|
+
group = groups["git"]
|
|
236
|
+
installed = 0
|
|
237
|
+
failed = 0
|
|
188
238
|
|
|
189
|
-
for
|
|
190
|
-
if
|
|
191
|
-
|
|
192
|
-
|
|
239
|
+
for hook in group.hooks:
|
|
240
|
+
if dispatcher.install(hook, project_root):
|
|
241
|
+
installed += 1
|
|
242
|
+
console.print(f"[green]✓ Installed {hook.metadata.event}[/green]")
|
|
193
243
|
else:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
table.add_row(hook_type, status_str, managed)
|
|
197
|
-
|
|
198
|
-
console.print(table)
|
|
199
|
-
|
|
200
|
-
# Discovered hooks
|
|
201
|
-
if status_info["discovered"]:
|
|
202
|
-
console.print("\n[bold]Discovered Hook Scripts:[/bold]")
|
|
203
|
-
for hook_type, scripts in status_info["discovered"].items():
|
|
204
|
-
tree = Tree(f"[cyan]{hook_type}[/cyan]")
|
|
205
|
-
for script in scripts:
|
|
206
|
-
tree.add(f"[dim]{script['feature']}[/dim] (priority: {script['priority']})")
|
|
207
|
-
console.print(tree)
|
|
208
|
-
else:
|
|
209
|
-
console.print("\n[yellow]No hook scripts discovered from Features.[/yellow]")
|
|
210
|
-
|
|
211
|
-
# Configuration
|
|
212
|
-
config_tree = Tree("[bold]Configuration:[/bold]")
|
|
213
|
-
config_tree.add(f"Hooks enabled: {status_info['config']['enabled']}")
|
|
214
|
-
|
|
215
|
-
if status_info['config']['enabled_features']:
|
|
216
|
-
features_branch = config_tree.add("Feature-specific settings:")
|
|
217
|
-
for feature, enabled in status_info['config']['enabled_features'].items():
|
|
218
|
-
status = "enabled" if enabled else "disabled"
|
|
219
|
-
features_branch.add(f"{feature}: {status}")
|
|
244
|
+
failed += 1
|
|
245
|
+
console.print(f"[red]✗ Failed to install {hook.metadata.event}[/red]")
|
|
220
246
|
|
|
221
|
-
if
|
|
222
|
-
|
|
223
|
-
for hook, enabled in status_info['config']['enabled_hooks'].items():
|
|
224
|
-
status = "enabled" if enabled else "disabled"
|
|
225
|
-
hooks_branch.add(f"{hook}: {status}")
|
|
247
|
+
if failed > 0:
|
|
248
|
+
raise typer.Exit(1)
|
|
226
249
|
|
|
227
|
-
console.print(
|
|
250
|
+
console.print(f"\n[green]Installed {installed} hook(s)[/green]")
|
|
228
251
|
|
|
229
252
|
|
|
230
|
-
@app.command("
|
|
231
|
-
def
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
253
|
+
@app.command("uninstall")
|
|
254
|
+
def uninstall(
|
|
255
|
+
event: Optional[str] = typer.Option(
|
|
256
|
+
None,
|
|
257
|
+
"--event",
|
|
258
|
+
"-e",
|
|
259
|
+
help="Specific event to uninstall (uninstalls all if not specified)",
|
|
260
|
+
),
|
|
261
|
+
project_root: Path = typer.Option(
|
|
262
|
+
".",
|
|
263
|
+
"--project",
|
|
264
|
+
"-p",
|
|
265
|
+
help="Project root directory",
|
|
266
|
+
),
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Uninstall Git hooks from .git/hooks/."""
|
|
269
|
+
dispatcher = GitHookDispatcher()
|
|
236
270
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
271
|
+
if event:
|
|
272
|
+
if dispatcher.uninstall(event, project_root):
|
|
273
|
+
console.print(f"[green]✓ Uninstalled {event}[/green]")
|
|
274
|
+
else:
|
|
275
|
+
console.print(f"[red]✗ Failed to uninstall {event}[/red]")
|
|
276
|
+
raise typer.Exit(1)
|
|
277
|
+
else:
|
|
278
|
+
# Uninstall all Monoco hooks
|
|
279
|
+
installed = dispatcher.list_installed(project_root)
|
|
280
|
+
uninstalled = 0
|
|
281
|
+
failed = 0
|
|
282
|
+
|
|
283
|
+
for hook_info in installed:
|
|
284
|
+
if dispatcher.uninstall(hook_info["event"], project_root):
|
|
285
|
+
uninstalled += 1
|
|
286
|
+
console.print(f"[green]✓ Uninstalled {hook_info['event']}[/green]")
|
|
287
|
+
else:
|
|
288
|
+
failed += 1
|
|
289
|
+
console.print(f"[red]✗ Failed to uninstall {hook_info['event']}[/red]")
|
|
290
|
+
|
|
291
|
+
if failed > 0:
|
|
292
|
+
raise typer.Exit(1)
|
|
293
|
+
|
|
294
|
+
console.print(f"\n[green]Uninstalled {uninstalled} hook(s)[/green]")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@app.command("list")
|
|
298
|
+
def list_hooks(
|
|
299
|
+
project_root: Path = typer.Option(
|
|
300
|
+
".",
|
|
301
|
+
"--project",
|
|
302
|
+
"-p",
|
|
303
|
+
help="Project root directory",
|
|
304
|
+
),
|
|
305
|
+
) -> None:
|
|
306
|
+
"""List installed Git hooks."""
|
|
307
|
+
dispatcher = GitHookDispatcher()
|
|
308
|
+
installed = dispatcher.list_installed(project_root)
|
|
246
309
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
try:
|
|
251
|
-
import yaml
|
|
252
|
-
with open(config_path, 'r') as f:
|
|
253
|
-
data = yaml.safe_load(f) or {}
|
|
254
|
-
|
|
255
|
-
if 'hooks' not in data:
|
|
256
|
-
data['hooks'] = {}
|
|
257
|
-
if 'hooks' not in data['hooks']:
|
|
258
|
-
data['hooks']['hooks'] = {}
|
|
259
|
-
data['hooks']['hooks'][hook_type] = True
|
|
260
|
-
|
|
261
|
-
with open(config_path, 'w') as f:
|
|
262
|
-
yaml.dump(data, f, default_flow_style=False, allow_unicode=True)
|
|
263
|
-
|
|
264
|
-
console.print(f"[green]✓ Enabled {hook_type} hook in configuration[/green]")
|
|
265
|
-
console.print(f"[dim]Run 'monoco hooks install' to apply changes.[/dim]")
|
|
266
|
-
except Exception as e:
|
|
267
|
-
OutputManager.error(f"Failed to update configuration: {e}")
|
|
268
|
-
raise typer.Exit(code=1)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
@app.command("disable")
|
|
272
|
-
def disable(
|
|
273
|
-
hook_type: str = typer.Argument(..., help="Hook type to disable (pre-commit, pre-push, post-checkout)"),
|
|
274
|
-
):
|
|
275
|
-
"""
|
|
276
|
-
Disable a specific hook type in configuration.
|
|
277
|
-
|
|
278
|
-
This updates workspace.yaml to disable the hook for future installs.
|
|
279
|
-
"""
|
|
280
|
-
from .core import HookType
|
|
281
|
-
try:
|
|
282
|
-
HookType(hook_type)
|
|
283
|
-
except ValueError:
|
|
284
|
-
valid_types = [t.value for t in HookType]
|
|
285
|
-
OutputManager.error(f"Invalid hook type: {hook_type}. Valid types: {', '.join(valid_types)}")
|
|
286
|
-
raise typer.Exit(code=1)
|
|
310
|
+
if not installed:
|
|
311
|
+
console.print("[dim]No Monoco Git hooks installed.[/dim]")
|
|
312
|
+
return
|
|
287
313
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
data = yaml.safe_load(f) or {}
|
|
295
|
-
|
|
296
|
-
if 'hooks' not in data:
|
|
297
|
-
data['hooks'] = {}
|
|
298
|
-
if 'hooks' not in data['hooks']:
|
|
299
|
-
data['hooks']['hooks'] = {}
|
|
300
|
-
data['hooks']['hooks'][hook_type] = False
|
|
301
|
-
|
|
302
|
-
with open(config_path, 'w') as f:
|
|
303
|
-
yaml.dump(data, f, default_flow_style=False, allow_unicode=True)
|
|
304
|
-
|
|
305
|
-
console.print(f"[green]✓ Disabled {hook_type} hook in configuration[/green]")
|
|
306
|
-
console.print(f"[dim]Run 'monoco hooks uninstall {hook_type}' to remove existing hook.[/dim]")
|
|
307
|
-
except Exception as e:
|
|
308
|
-
OutputManager.error(f"Failed to update configuration: {e}")
|
|
309
|
-
raise typer.Exit(code=1)
|
|
314
|
+
console.print("[bold]Installed Git Hooks:[/bold]\n")
|
|
315
|
+
for hook in installed:
|
|
316
|
+
merged_status = " (merged)" if hook.get("is_merged") else ""
|
|
317
|
+
console.print(f" • {hook['event']}{merged_status}")
|
|
318
|
+
if hook.get("hook_id"):
|
|
319
|
+
console.print(f" ID: {hook['hook_id']}")
|