monoco-toolkit 0.3.10__py3-none-any.whl → 0.3.12__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/__main__.py +8 -0
- monoco/core/artifacts/__init__.py +16 -0
- monoco/core/artifacts/manager.py +575 -0
- monoco/core/artifacts/models.py +161 -0
- monoco/core/automation/__init__.py +51 -0
- monoco/core/automation/config.py +338 -0
- monoco/core/automation/field_watcher.py +296 -0
- monoco/core/automation/handlers.py +723 -0
- monoco/core/config.py +31 -4
- monoco/core/executor/__init__.py +38 -0
- monoco/core/executor/agent_action.py +254 -0
- monoco/core/executor/git_action.py +303 -0
- monoco/core/executor/im_action.py +309 -0
- monoco/core/executor/pytest_action.py +218 -0
- monoco/core/git.py +38 -0
- monoco/core/hooks/context.py +74 -13
- monoco/core/ingestion/__init__.py +20 -0
- monoco/core/ingestion/discovery.py +248 -0
- monoco/core/ingestion/watcher.py +343 -0
- monoco/core/ingestion/worker.py +436 -0
- monoco/core/loader.py +633 -0
- monoco/core/registry.py +34 -25
- monoco/core/router/__init__.py +55 -0
- monoco/core/router/action.py +341 -0
- monoco/core/router/router.py +392 -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 +171 -0
- monoco/core/scheduler/local.py +377 -0
- monoco/core/skills.py +119 -80
- monoco/core/watcher/__init__.py +57 -0
- monoco/core/watcher/base.py +365 -0
- monoco/core/watcher/dropzone.py +152 -0
- monoco/core/watcher/issue.py +303 -0
- monoco/core/watcher/memo.py +200 -0
- monoco/core/watcher/task.py +238 -0
- monoco/daemon/app.py +77 -1
- monoco/daemon/commands.py +10 -0
- monoco/daemon/events.py +34 -0
- monoco/daemon/mailroom_service.py +196 -0
- monoco/daemon/models.py +1 -0
- monoco/daemon/scheduler.py +207 -0
- monoco/daemon/services.py +27 -58
- monoco/daemon/triggers.py +55 -0
- monoco/features/agent/__init__.py +25 -7
- monoco/features/agent/adapter.py +17 -7
- monoco/features/agent/cli.py +91 -57
- monoco/features/agent/engines.py +31 -170
- monoco/{core/resources/en/skills/monoco_core → features/agent/resources/en/skills/monoco_atom_core}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_engineer → monoco_workflow_agent_engineer}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_manager → monoco_workflow_agent_manager}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_planner → monoco_workflow_agent_planner}/SKILL.md +2 -2
- monoco/features/agent/resources/en/skills/{flow_reviewer → monoco_workflow_agent_reviewer}/SKILL.md +2 -2
- monoco/features/agent/resources/{roles/role-engineer.yaml → zh/roles/monoco_role_engineer.yaml} +3 -3
- monoco/features/agent/resources/{roles/role-manager.yaml → zh/roles/monoco_role_manager.yaml} +8 -8
- monoco/features/agent/resources/{roles/role-planner.yaml → zh/roles/monoco_role_planner.yaml} +8 -8
- monoco/features/agent/resources/{roles/role-reviewer.yaml → zh/roles/monoco_role_reviewer.yaml} +8 -8
- monoco/{core/resources/zh/skills/monoco_core → features/agent/resources/zh/skills/monoco_atom_core}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_engineer → monoco_workflow_agent_engineer}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_manager → monoco_workflow_agent_manager}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_planner → monoco_workflow_agent_planner}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/{flow_reviewer → monoco_workflow_agent_reviewer}/SKILL.md +2 -2
- monoco/features/agent/worker.py +1 -1
- monoco/features/artifact/__init__.py +0 -0
- monoco/features/artifact/adapter.py +33 -0
- monoco/features/artifact/resources/zh/AGENTS.md +14 -0
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +278 -0
- monoco/features/glossary/adapter.py +18 -7
- monoco/features/glossary/resources/en/skills/{monoco_glossary → monoco_atom_glossary}/SKILL.md +2 -2
- monoco/features/glossary/resources/zh/skills/{monoco_glossary → monoco_atom_glossary}/SKILL.md +2 -2
- monoco/features/hooks/__init__.py +11 -0
- monoco/features/hooks/adapter.py +67 -0
- monoco/features/hooks/commands.py +309 -0
- monoco/features/hooks/core.py +441 -0
- monoco/features/hooks/resources/ADDING_HOOKS.md +234 -0
- monoco/features/i18n/adapter.py +18 -5
- monoco/features/i18n/core.py +482 -17
- monoco/features/i18n/resources/en/skills/{monoco_i18n → monoco_atom_i18n}/SKILL.md +2 -2
- monoco/features/i18n/resources/en/skills/{i18n_scan_workflow → monoco_workflow_i18n_scan}/SKILL.md +2 -2
- monoco/features/i18n/resources/zh/skills/{monoco_i18n → monoco_atom_i18n}/SKILL.md +2 -2
- monoco/features/i18n/resources/zh/skills/{i18n_scan_workflow → monoco_workflow_i18n_scan}/SKILL.md +2 -2
- monoco/features/issue/adapter.py +19 -6
- monoco/features/issue/commands.py +352 -20
- monoco/features/issue/core.py +475 -16
- monoco/features/issue/engine/machine.py +114 -4
- monoco/features/issue/linter.py +60 -5
- monoco/features/issue/models.py +2 -2
- monoco/features/issue/resources/en/AGENTS.md +109 -0
- monoco/features/issue/resources/en/skills/{monoco_issue → monoco_atom_issue}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_create_workflow → monoco_workflow_issue_creation}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_develop_workflow → monoco_workflow_issue_development}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_lifecycle_workflow → monoco_workflow_issue_management}/SKILL.md +2 -2
- monoco/features/issue/resources/en/skills/{issue_refine_workflow → monoco_workflow_issue_refinement}/SKILL.md +2 -2
- monoco/features/issue/resources/hooks/post-checkout.sh +39 -0
- monoco/features/issue/resources/hooks/pre-commit.sh +41 -0
- monoco/features/issue/resources/hooks/pre-push.sh +35 -0
- monoco/features/issue/resources/zh/AGENTS.md +109 -0
- monoco/features/issue/resources/zh/skills/{monoco_issue → monoco_atom_issue_lifecycle}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_create_workflow → monoco_workflow_issue_creation}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_develop_workflow → monoco_workflow_issue_development}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_lifecycle_workflow → monoco_workflow_issue_management}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/{issue_refine_workflow → monoco_workflow_issue_refinement}/SKILL.md +2 -2
- monoco/features/issue/validator.py +101 -1
- monoco/features/memo/adapter.py +21 -8
- monoco/features/memo/cli.py +103 -10
- monoco/features/memo/core.py +178 -92
- monoco/features/memo/models.py +53 -0
- monoco/features/memo/resources/en/skills/{monoco_memo → monoco_atom_memo}/SKILL.md +2 -2
- monoco/features/memo/resources/en/skills/{note_processing_workflow → monoco_workflow_note_processing}/SKILL.md +2 -2
- monoco/features/memo/resources/zh/skills/{monoco_memo → monoco_atom_memo}/SKILL.md +2 -2
- monoco/features/memo/resources/zh/skills/{note_processing_workflow → monoco_workflow_note_processing}/SKILL.md +2 -2
- monoco/features/spike/adapter.py +18 -5
- monoco/features/spike/commands.py +5 -3
- monoco/features/spike/resources/en/skills/{monoco_spike → monoco_atom_spike}/SKILL.md +2 -2
- monoco/features/spike/resources/en/skills/{research_workflow → monoco_workflow_research}/SKILL.md +2 -2
- monoco/features/spike/resources/zh/skills/{monoco_spike → monoco_atom_spike}/SKILL.md +2 -2
- monoco/features/spike/resources/zh/skills/{research_workflow → monoco_workflow_research}/SKILL.md +2 -2
- monoco/main.py +38 -1
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/METADATA +7 -1
- monoco_toolkit-0.3.12.dist-info/RECORD +202 -0
- monoco/features/agent/apoptosis.py +0 -44
- monoco/features/agent/manager.py +0 -91
- monoco/features/agent/session.py +0 -121
- monoco_toolkit-0.3.10.dist-info/RECORD +0 -156
- /monoco/{core → features/agent}/resources/en/AGENTS.md +0 -0
- /monoco/{core → features/agent}/resources/zh/AGENTS.md +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.10.dist-info → monoco_toolkit-0.3.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -79,6 +79,113 @@ class StateMachine:
|
|
|
79
79
|
allowed.append(t)
|
|
80
80
|
return allowed
|
|
81
81
|
|
|
82
|
+
def get_available_solutions(self, from_status: str, from_stage: Optional[str]) -> List[str]:
|
|
83
|
+
"""Get all valid solutions for transitions from the current state."""
|
|
84
|
+
solutions = set()
|
|
85
|
+
for t in self.transitions:
|
|
86
|
+
# Skip non-transitions (agent actions with same status/stage)
|
|
87
|
+
if t.from_status is None and t.from_stage is None:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
if t.from_status and t.from_status != from_status:
|
|
91
|
+
continue
|
|
92
|
+
if t.from_stage and t.from_stage != from_stage:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
if t.required_solution:
|
|
96
|
+
solutions.add(t.required_solution)
|
|
97
|
+
return sorted(list(solutions))
|
|
98
|
+
|
|
99
|
+
def get_valid_transitions_from_state(
|
|
100
|
+
self, from_status: str, from_stage: Optional[str]
|
|
101
|
+
) -> List[TransitionConfig]:
|
|
102
|
+
"""Get all valid transitions from a given state."""
|
|
103
|
+
valid = []
|
|
104
|
+
for t in self.transitions:
|
|
105
|
+
# Skip non-transitions (agent actions with same status/stage)
|
|
106
|
+
if t.from_status is None and t.from_stage is None:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
if t.from_status and t.from_status != from_status:
|
|
110
|
+
continue
|
|
111
|
+
if t.from_stage and t.from_stage != from_stage:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
valid.append(t)
|
|
115
|
+
return valid
|
|
116
|
+
|
|
117
|
+
def _format_state(self, status: str, stage: Optional[str]) -> str:
|
|
118
|
+
"""Format a state for display in error messages."""
|
|
119
|
+
# Handle Enum values
|
|
120
|
+
if hasattr(status, 'value'):
|
|
121
|
+
status = status.value
|
|
122
|
+
if stage and hasattr(stage, 'value'):
|
|
123
|
+
stage = stage.value
|
|
124
|
+
|
|
125
|
+
if stage:
|
|
126
|
+
return f"{status}({stage})"
|
|
127
|
+
return status
|
|
128
|
+
|
|
129
|
+
def _build_transition_not_found_error(
|
|
130
|
+
self,
|
|
131
|
+
from_status: str,
|
|
132
|
+
from_stage: Optional[str],
|
|
133
|
+
to_status: str,
|
|
134
|
+
to_stage: Optional[str],
|
|
135
|
+
) -> str:
|
|
136
|
+
"""Build a descriptive error message when no transition is found."""
|
|
137
|
+
current_state = self._format_state(from_status, from_stage)
|
|
138
|
+
target_state = self._format_state(to_status, to_stage)
|
|
139
|
+
|
|
140
|
+
error_msg = (
|
|
141
|
+
f"Lifecycle Policy: Transition from '{current_state}' "
|
|
142
|
+
f"to '{target_state}' is not defined."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Add available transitions hint
|
|
146
|
+
valid_transitions = self.get_valid_transitions_from_state(from_status, from_stage)
|
|
147
|
+
if valid_transitions:
|
|
148
|
+
error_msg += " Available transitions from this state:"
|
|
149
|
+
for t in valid_transitions:
|
|
150
|
+
target = self._format_state(t.to_status, t.to_stage)
|
|
151
|
+
if t.required_solution:
|
|
152
|
+
error_msg += f"\n - {t.name}: '{current_state}' -> '{target}' (requires --solution {t.required_solution})"
|
|
153
|
+
else:
|
|
154
|
+
error_msg += f"\n - {t.name}: '{current_state}' -> '{target}'"
|
|
155
|
+
else:
|
|
156
|
+
error_msg += " No transitions are available from this state."
|
|
157
|
+
|
|
158
|
+
return error_msg
|
|
159
|
+
|
|
160
|
+
def _build_invalid_solution_error(
|
|
161
|
+
self,
|
|
162
|
+
transition: TransitionConfig,
|
|
163
|
+
provided_solution: Optional[str],
|
|
164
|
+
from_status: str,
|
|
165
|
+
from_stage: Optional[str],
|
|
166
|
+
) -> str:
|
|
167
|
+
"""Build a descriptive error message when solution is invalid or missing."""
|
|
168
|
+
current_state = self._format_state(from_status, from_stage)
|
|
169
|
+
target_state = self._format_state(transition.to_status, transition.to_stage)
|
|
170
|
+
|
|
171
|
+
if provided_solution:
|
|
172
|
+
error_msg = (
|
|
173
|
+
f"Lifecycle Policy: Transition '{transition.label}' from '{current_state}' "
|
|
174
|
+
f"to '{target_state}' does not accept solution '{provided_solution}'."
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
error_msg = (
|
|
178
|
+
f"Lifecycle Policy: Transition '{transition.label}' from '{current_state}' "
|
|
179
|
+
f"to '{target_state}' requires a solution."
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Get valid solutions for this transition
|
|
183
|
+
valid_solutions = self.get_available_solutions(from_status, from_stage)
|
|
184
|
+
if valid_solutions:
|
|
185
|
+
error_msg += f" Valid solutions are: {', '.join(valid_solutions)}."
|
|
186
|
+
|
|
187
|
+
return error_msg
|
|
188
|
+
|
|
82
189
|
def find_transition(
|
|
83
190
|
self,
|
|
84
191
|
from_status: str,
|
|
@@ -134,7 +241,7 @@ class StateMachine:
|
|
|
134
241
|
to_stage: Optional[str],
|
|
135
242
|
solution: Optional[str] = None,
|
|
136
243
|
meta: Optional[IssueMetadata] = None,
|
|
137
|
-
) ->
|
|
244
|
+
) -> TransitionConfig:
|
|
138
245
|
"""
|
|
139
246
|
Validate if a transition is allowed. Raises ValueError if not.
|
|
140
247
|
If meta is provided, also validates criticality-based policies.
|
|
@@ -149,13 +256,16 @@ class StateMachine:
|
|
|
149
256
|
|
|
150
257
|
if not transition:
|
|
151
258
|
raise ValueError(
|
|
152
|
-
|
|
153
|
-
|
|
259
|
+
self._build_transition_not_found_error(
|
|
260
|
+
from_status, from_stage, to_status, to_stage
|
|
261
|
+
)
|
|
154
262
|
)
|
|
155
263
|
|
|
156
264
|
if transition.required_solution and solution != transition.required_solution:
|
|
157
265
|
raise ValueError(
|
|
158
|
-
|
|
266
|
+
self._build_invalid_solution_error(
|
|
267
|
+
transition, solution, from_status, from_stage
|
|
268
|
+
)
|
|
159
269
|
)
|
|
160
270
|
|
|
161
271
|
# Criticality-based policy checks
|
monoco/features/issue/linter.py
CHANGED
|
@@ -29,7 +29,7 @@ def check_integrity(issues_root: Path, recursive: bool = False) -> List[Diagnost
|
|
|
29
29
|
|
|
30
30
|
# 1. Collection Phase (Build Index)
|
|
31
31
|
# Helper to collect issues from a project
|
|
32
|
-
def collect_project_issues(project_issues_root: Path, project_name: str = "local"):
|
|
32
|
+
def collect_project_issues(project_issues_root: Path, project_name: str = "local", include_archived: bool = False):
|
|
33
33
|
project_issues = []
|
|
34
34
|
project_diagnostics = []
|
|
35
35
|
for subdir in ["Epics", "Features", "Chores", "Fixes", "Domains"]:
|
|
@@ -128,10 +128,30 @@ def check_integrity(issues_root: Path, recursive: bool = False) -> List[Diagnost
|
|
|
128
128
|
else:
|
|
129
129
|
# Standard Issues (Epics/Features/etc)
|
|
130
130
|
files = []
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
# Standard status directories
|
|
132
|
+
status_dirs = ["open", "closed", "backlog"]
|
|
133
|
+
# Include archived if requested (for full validation)
|
|
134
|
+
if include_archived:
|
|
135
|
+
status_dirs.append("archived")
|
|
136
|
+
|
|
137
|
+
for item in d.iterdir():
|
|
138
|
+
if item.is_dir():
|
|
139
|
+
status = item.name.lower()
|
|
140
|
+
if status in status_dirs:
|
|
141
|
+
files.extend(item.rglob("*.md"))
|
|
142
|
+
elif status != "archived": # archived is handled separately if include_archived
|
|
143
|
+
# Report Illegal Directory immediately
|
|
144
|
+
project_diagnostics.append(
|
|
145
|
+
Diagnostic(
|
|
146
|
+
range=Range(
|
|
147
|
+
start=Position(line=0, character=0),
|
|
148
|
+
end=Position(line=0, character=0),
|
|
149
|
+
),
|
|
150
|
+
message=f"Illegal Directory: Issues should be in 'open/', 'closed/', or 'backlog/' directories, not '{status}/' directory.",
|
|
151
|
+
severity=DiagnosticSeverity.Error,
|
|
152
|
+
source="System",
|
|
153
|
+
)
|
|
154
|
+
)
|
|
135
155
|
|
|
136
156
|
for f in files:
|
|
137
157
|
try:
|
|
@@ -283,12 +303,45 @@ def check_integrity(issues_root: Path, recursive: bool = False) -> List[Diagnost
|
|
|
283
303
|
|
|
284
304
|
# 2. Validation Phase
|
|
285
305
|
valid_domains = set()
|
|
306
|
+
# Build list of actual IssueMetadata objects for domain governance checks
|
|
307
|
+
all_issue_metas = []
|
|
308
|
+
|
|
286
309
|
# Now validate
|
|
287
310
|
for path, meta, project_name in all_issues:
|
|
288
311
|
if meta == "DOMAIN":
|
|
289
312
|
valid_domains.add(
|
|
290
313
|
project_name
|
|
291
314
|
) # Record the domain name (which was stored in project_name slot)
|
|
315
|
+
else:
|
|
316
|
+
all_issue_metas.append(meta)
|
|
317
|
+
|
|
318
|
+
# FEAT-0136: Project-Level Domain Governance Check
|
|
319
|
+
# Calculate scale metrics
|
|
320
|
+
num_issues = len(all_issue_metas)
|
|
321
|
+
num_epics = len([i for i in all_issue_metas if i.type == "epic"])
|
|
322
|
+
is_large_scale = num_issues > 128 or num_epics > 32
|
|
323
|
+
|
|
324
|
+
# Check Domain Coverage for large-scale projects
|
|
325
|
+
if is_large_scale and num_epics > 0:
|
|
326
|
+
epics = [i for i in all_issue_metas if i.type == "epic"]
|
|
327
|
+
untracked_epics = [e for e in epics if not e.domains]
|
|
328
|
+
untracked_ratio = len(untracked_epics) / len(epics)
|
|
329
|
+
|
|
330
|
+
# Rule: Untracked Epics / Total Epics <= 25%
|
|
331
|
+
if untracked_ratio > 0.25:
|
|
332
|
+
# Report this as a project-level diagnostic (attached to first epic or general)
|
|
333
|
+
diagnostics.append(
|
|
334
|
+
Diagnostic(
|
|
335
|
+
range=Range(
|
|
336
|
+
start=Position(line=0, character=0),
|
|
337
|
+
end=Position(line=0, character=0),
|
|
338
|
+
),
|
|
339
|
+
message=f"Domain Governance: Coverage is too low for a project of this scale ({len(untracked_epics)}/{len(epics)} Epics untracked). "
|
|
340
|
+
f"At least 75% of Epics must have domains assigned.",
|
|
341
|
+
severity=DiagnosticSeverity.Error,
|
|
342
|
+
source="DomainGovernance",
|
|
343
|
+
)
|
|
344
|
+
)
|
|
292
345
|
|
|
293
346
|
for path, meta, project_name in all_issues:
|
|
294
347
|
if meta == "DOMAIN":
|
|
@@ -325,6 +378,7 @@ def check_integrity(issues_root: Path, recursive: bool = False) -> List[Diagnost
|
|
|
325
378
|
|
|
326
379
|
# A. Run Core Validator
|
|
327
380
|
# Pass valid_domains kwarg (Validator needs update to accept it)
|
|
381
|
+
# FEAT-0136: Also pass all_issue_metas for domain governance checks
|
|
328
382
|
file_diagnostics = validator.validate(
|
|
329
383
|
meta,
|
|
330
384
|
content,
|
|
@@ -332,6 +386,7 @@ def check_integrity(issues_root: Path, recursive: bool = False) -> List[Diagnost
|
|
|
332
386
|
current_project=project_name,
|
|
333
387
|
workspace_root=workspace_root_name,
|
|
334
388
|
valid_domains=valid_domains,
|
|
389
|
+
all_issues=all_issue_metas,
|
|
335
390
|
)
|
|
336
391
|
|
|
337
392
|
# Add context to diagnostics (Path)
|
monoco/features/issue/models.py
CHANGED
|
@@ -74,6 +74,7 @@ class IssueStatus(str, Enum):
|
|
|
74
74
|
OPEN = "open"
|
|
75
75
|
CLOSED = "closed"
|
|
76
76
|
BACKLOG = "backlog"
|
|
77
|
+
ARCHIVED = "archived"
|
|
77
78
|
|
|
78
79
|
|
|
79
80
|
class IssueStage(str, Enum):
|
|
@@ -223,8 +224,7 @@ class IssueMetadata(BaseModel):
|
|
|
223
224
|
# Stage normalization
|
|
224
225
|
if "stage" in v and isinstance(v["stage"], str):
|
|
225
226
|
v["stage"] = v["stage"].lower()
|
|
226
|
-
|
|
227
|
-
v["stage"] = "draft"
|
|
227
|
+
|
|
228
228
|
try:
|
|
229
229
|
v["stage"] = IssueStage(v["stage"])
|
|
230
230
|
except ValueError:
|
|
@@ -20,3 +20,112 @@ System for managing tasks using `monoco issue`.
|
|
|
20
20
|
- 🛑 **NO** direct coding on `main`/`master` (Linter will fail).
|
|
21
21
|
- **Prune Timing**: ONLY prune environment (branch/worktree) during `monoco issue close --prune`. NEVER prune at `submit` stage.
|
|
22
22
|
- Must update `files` field after coding (via `sync-files` or manual).
|
|
23
|
+
|
|
24
|
+
## Git Merge Strategy
|
|
25
|
+
|
|
26
|
+
### Core Principles
|
|
27
|
+
|
|
28
|
+
To ensure safe merging of Feature branches into the mainline and prevent "stale state pollution", the following merge strategy must be followed:
|
|
29
|
+
|
|
30
|
+
#### 1. No Manual Merge
|
|
31
|
+
|
|
32
|
+
- **🛑 STRICTLY FORBIDDEN**: Agents must NOT manually execute `git merge` to merge Feature branches
|
|
33
|
+
- **🛑 STRICTLY FORBIDDEN**: Using `git pull origin main` followed by direct commits
|
|
34
|
+
- **✅ ONLY AUTHORITATIVE PATH**: Must use `monoco issue close` for closing the loop
|
|
35
|
+
|
|
36
|
+
#### 2. Safe Merge Flow
|
|
37
|
+
|
|
38
|
+
The correct Issue closing workflow is as follows:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 1. Ensure you're on main/master branch and code is merged
|
|
42
|
+
$ git checkout main
|
|
43
|
+
$ git pull origin main
|
|
44
|
+
|
|
45
|
+
# 2. Confirm Feature branch changes are merged to mainline
|
|
46
|
+
# (via PR/MR or other code review process)
|
|
47
|
+
|
|
48
|
+
# 3. Use monoco issue close to close Issue (prune by default)
|
|
49
|
+
$ monoco issue close FEAT-XXXX --solution implemented
|
|
50
|
+
|
|
51
|
+
# 4. To keep branch, use --no-prune
|
|
52
|
+
$ monoco issue close FEAT-XXXX --solution implemented --no-prune
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### 3. Conflict Resolution Principles
|
|
56
|
+
|
|
57
|
+
When Feature branch conflicts with mainline:
|
|
58
|
+
|
|
59
|
+
1. **Auto-merge Stop**: If `touched files` (Issue `files` field) conflict with mainline, automation tools **MUST IMMEDIATELY STOP** merging and throw a clear error.
|
|
60
|
+
|
|
61
|
+
2. **Manual Cherry-Pick Mode**:
|
|
62
|
+
- Error message will instruct Agent to switch to manual Cherry-Pick mode
|
|
63
|
+
- **Core Principle**: Only pick valid changes belonging to this Feature, STRICTLY FORBIDDEN from overwriting updates to unrelated Issues on mainline
|
|
64
|
+
- Use `git cherry-pick <commit>` to apply valid commits one by one
|
|
65
|
+
|
|
66
|
+
3. **Fallback Strategy**:
|
|
67
|
+
```bash
|
|
68
|
+
# 1. Create temporary branch for conflict resolution
|
|
69
|
+
$ git checkout main
|
|
70
|
+
$ git checkout -b temp/FEAT-XXXX-resolve
|
|
71
|
+
|
|
72
|
+
# 2. Cherry-pick valid commits one by one
|
|
73
|
+
$ git cherry-pick <commit-hash-1>
|
|
74
|
+
$ git cherry-pick <commit-hash-2>
|
|
75
|
+
|
|
76
|
+
# 3. If conflicts occur, only keep changes from this Feature
|
|
77
|
+
# Discard any modifications that would overwrite other Issue updates on mainline
|
|
78
|
+
|
|
79
|
+
# 4. Merge temporary branch when done
|
|
80
|
+
$ git checkout main
|
|
81
|
+
$ git merge temp/FEAT-XXXX-resolve
|
|
82
|
+
|
|
83
|
+
# 5. Close Issue
|
|
84
|
+
$ monoco issue close FEAT-XXXX --solution implemented
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### 4. Smart Atomic Merge Based on files Field
|
|
88
|
+
|
|
89
|
+
The Issue's `files` field records the Actual Impact Scope of the Feature branch:
|
|
90
|
+
|
|
91
|
+
- **Generation**: `monoco issue sync-files` uses `git diff --name-only base...target` logic
|
|
92
|
+
- **Purpose**: Serves as a merge whitelist, only merging files in the list, filtering out implicit overwrites caused by "stale baseline"
|
|
93
|
+
- **Limitation**: Cannot defend against explicit accidental modifications (e.g., inadvertently formatting other Issue files)
|
|
94
|
+
|
|
95
|
+
**Future Enhancement**: Implement selective merge logic based on `files` list:
|
|
96
|
+
```bash
|
|
97
|
+
# Selective merge (planned)
|
|
98
|
+
$ git checkout main
|
|
99
|
+
$ git checkout feature/FEAT-XXXX -- <files...>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### 5. Cleanup Strategy
|
|
103
|
+
|
|
104
|
+
- **Default Cleanup**: `monoco issue close` executes `--prune` by default, deleting Feature branch/worktree
|
|
105
|
+
- **Keep Branch**: To preserve branch, explicitly use `--no-prune`
|
|
106
|
+
- **Force Cleanup**: Use `--force` to force delete unmerged branches (use with caution)
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Default branch cleanup
|
|
110
|
+
$ monoco issue close FEAT-XXXX --solution implemented
|
|
111
|
+
# ✔ Cleaned up: branch:feat/feat-XXXX-xxx
|
|
112
|
+
|
|
113
|
+
# Keep branch
|
|
114
|
+
$ monoco issue close FEAT-XXXX --solution implemented --no-prune
|
|
115
|
+
|
|
116
|
+
# Force cleanup (caution)
|
|
117
|
+
$ monoco issue close FEAT-XXXX --solution implemented --force
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Summary
|
|
121
|
+
|
|
122
|
+
| Operation | Command | Description |
|
|
123
|
+
|-----------|---------|-------------|
|
|
124
|
+
| Create Issue | `monoco issue create feature -t "Title"` | Create Issue before development |
|
|
125
|
+
| Start Development | `monoco issue start FEAT-XXXX --branch` | Create Feature branch |
|
|
126
|
+
| Sync Files | `monoco issue sync-files` | Update files field |
|
|
127
|
+
| Submit Review | `monoco issue submit FEAT-XXXX` | Enter Review stage |
|
|
128
|
+
| Close Issue | `monoco issue close FEAT-XXXX --solution implemented` | Only merge path |
|
|
129
|
+
| Keep Branch | `monoco issue close ... --no-prune` | Close without deleting branch |
|
|
130
|
+
|
|
131
|
+
> ⚠️ **WARNING**: Any manual merge operation bypassing `monoco issue close` may cause mainline state pollution and violate workflow compliance requirements.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: monoco_workflow_issue_management
|
|
3
3
|
description: Issue Lifecycle Workflow (Flow Skill). Defines the complete Issue management process from creation to closure, ensuring task tracking and process compliance.
|
|
4
|
-
type:
|
|
4
|
+
type: workflow
|
|
5
5
|
domain: issue
|
|
6
6
|
version: 1.0.0
|
|
7
7
|
---
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Issue Feature Post-Checkout Hook
|
|
3
|
+
# Automatically syncs issue status when switching branches
|
|
4
|
+
|
|
5
|
+
echo "[Monoco] Syncing issue status after branch checkout..."
|
|
6
|
+
|
|
7
|
+
# Get the previous and current HEAD
|
|
8
|
+
PREVIOUS_HEAD="$1"
|
|
9
|
+
NEW_HEAD="$2"
|
|
10
|
+
BRANCH_SWITCH="$3" # 1 if branch switch, 0 if file checkout
|
|
11
|
+
|
|
12
|
+
# Only sync on actual branch switches, not file checkouts
|
|
13
|
+
if [ "$BRANCH_SWITCH" != "1" ]; then
|
|
14
|
+
echo "[Monoco] File checkout detected, skipping issue sync."
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Get current branch name
|
|
19
|
+
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")
|
|
20
|
+
|
|
21
|
+
echo "[Monoco] Switched to branch: $CURRENT_BRANCH"
|
|
22
|
+
|
|
23
|
+
# Try to extract issue ID from branch name
|
|
24
|
+
# Common patterns: FEAT-123, feat/FEAT-123, feature/FEAT-123, fix/FEAT-123
|
|
25
|
+
ISSUE_ID=$(echo "$CURRENT_BRANCH" | grep -oE '[A-Z]+-[0-9]+' | head -1)
|
|
26
|
+
|
|
27
|
+
if [ -n "$ISSUE_ID" ]; then
|
|
28
|
+
echo "[Monoco] Detected issue ID from branch: $ISSUE_ID"
|
|
29
|
+
|
|
30
|
+
# Check if issue exists and update its isolation ref if needed
|
|
31
|
+
$MONOCO_CMD issue sync-isolation "$ISSUE_ID" --branch "$CURRENT_BRANCH" 2>/dev/null || true
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Run general sync to ensure files field is up to date
|
|
35
|
+
echo "[Monoco] Running issue file sync..."
|
|
36
|
+
$MONOCO_CMD issue sync-files 2>/dev/null || true
|
|
37
|
+
|
|
38
|
+
echo "[Monoco] Issue sync complete."
|
|
39
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Issue Feature Pre-Commit Hook
|
|
3
|
+
# Runs monoco issue lint on staged Issue files
|
|
4
|
+
|
|
5
|
+
echo "[Monoco] Checking Issue integrity..."
|
|
6
|
+
|
|
7
|
+
# Get the list of staged Issue files
|
|
8
|
+
STAGED_ISSUES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^Issues/.*\.md$' || true)
|
|
9
|
+
|
|
10
|
+
if [ -z "$STAGED_ISSUES" ]; then
|
|
11
|
+
echo "[Monoco] No Issue files staged. Skipping lint."
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Run lint on staged Issue files
|
|
16
|
+
echo "[Monoco] Running lint on staged Issue files..."
|
|
17
|
+
|
|
18
|
+
# Build file list for monoco command
|
|
19
|
+
FILE_ARGS=""
|
|
20
|
+
for file in $STAGED_ISSUES; do
|
|
21
|
+
FILE_ARGS="$FILE_ARGS $file"
|
|
22
|
+
done
|
|
23
|
+
|
|
24
|
+
# Execute lint on each file
|
|
25
|
+
LINT_EXIT=0
|
|
26
|
+
for file in $STAGED_ISSUES; do
|
|
27
|
+
$MONOCO_CMD issue lint "$file"
|
|
28
|
+
if [ $? -ne 0 ]; then
|
|
29
|
+
LINT_EXIT=1
|
|
30
|
+
fi
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
if [ $LINT_EXIT -ne 0 ]; then
|
|
34
|
+
echo ""
|
|
35
|
+
echo "[Monoco] Issue lint failed. Please fix the errors above."
|
|
36
|
+
echo "[Monoco] You can run 'monoco issue lint --fix' to attempt automatic fixes."
|
|
37
|
+
exit $LINT_EXIT
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "[Monoco] Issue lint passed."
|
|
41
|
+
exit 0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Issue Feature Pre-Push Hook
|
|
3
|
+
# Checks for incomplete critical issues before pushing
|
|
4
|
+
|
|
5
|
+
echo "[Monoco] Checking critical issues before push..."
|
|
6
|
+
|
|
7
|
+
# Get the list of commits being pushed
|
|
8
|
+
# $1 = remote name, $2 = remote url (if available)
|
|
9
|
+
REMOTE="$1"
|
|
10
|
+
URL="$2"
|
|
11
|
+
|
|
12
|
+
# Check for high/critical issues that are not closed
|
|
13
|
+
# This uses monoco issue query to find open issues with high/critical criticality
|
|
14
|
+
echo "[Monoco] Scanning for incomplete critical issues..."
|
|
15
|
+
|
|
16
|
+
# Run the check using monoco command
|
|
17
|
+
$MONOCO_CMD issue check-critical --fail-on-warning
|
|
18
|
+
CHECK_EXIT=$?
|
|
19
|
+
|
|
20
|
+
if [ $CHECK_EXIT -eq 2 ]; then
|
|
21
|
+
echo ""
|
|
22
|
+
echo "[Monoco] ❌ Critical issues found! Push blocked."
|
|
23
|
+
echo "[Monoco] Please close or resolve the critical issues above before pushing."
|
|
24
|
+
exit 1
|
|
25
|
+
elif [ $CHECK_EXIT -eq 1 ]; then
|
|
26
|
+
echo ""
|
|
27
|
+
echo "[Monoco] ⚠️ High priority issues found."
|
|
28
|
+
echo "[Monoco] Use --force-push to bypass this warning (not recommended)."
|
|
29
|
+
# For now, we allow the push but warn
|
|
30
|
+
# To block, change the exit code to 1
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
echo "[Monoco] ✓ No blocking critical issues found."
|
|
35
|
+
exit 0
|
|
@@ -20,3 +20,112 @@
|
|
|
20
20
|
- 🛑 **禁止**直接在 `main`/`master` 分支修改代码 (Linter 会报错)。
|
|
21
21
|
- **清理时机**: 环境清理仅应在 `close` 时执行。**禁止**在 `submit` 阶段清理环境。
|
|
22
22
|
- 修改代码后**必须**更新 `files` 字段(通过 `sync-files` 或手动)。
|
|
23
|
+
|
|
24
|
+
## Git 合并策略 (Merge Strategy)
|
|
25
|
+
|
|
26
|
+
### 核心原则
|
|
27
|
+
|
|
28
|
+
为确保 Feature 分支安全合并到主线,避免"旧状态污染主线"问题,必须遵循以下合并策略:
|
|
29
|
+
|
|
30
|
+
#### 1. 禁止手动 Merge
|
|
31
|
+
|
|
32
|
+
- **🛑 严禁** Agent 手动执行 `git merge` 合并 Feature 分支
|
|
33
|
+
- **🛑 严禁** 使用 `git pull origin main` 后直接提交
|
|
34
|
+
- **✅ 唯一权威途径**: 必须使用 `monoco issue close` 进行闭环
|
|
35
|
+
|
|
36
|
+
#### 2. 安全合并流程 (Safe Merge Flow)
|
|
37
|
+
|
|
38
|
+
正确的 Issue 关闭流程如下:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 1. 确保当前在 main/master 分支,且代码已合并
|
|
42
|
+
$ git checkout main
|
|
43
|
+
$ git pull origin main
|
|
44
|
+
|
|
45
|
+
# 2. 确认 Feature 分支的变更已合并到主线
|
|
46
|
+
# (通过 PR/MR 或其他代码审查流程)
|
|
47
|
+
|
|
48
|
+
# 3. 使用 monoco issue close 关闭 Issue (默认执行 prune)
|
|
49
|
+
$ monoco issue close FEAT-XXXX --solution implemented
|
|
50
|
+
|
|
51
|
+
# 4. 如需保留分支,使用 --no-prune
|
|
52
|
+
$ monoco issue close FEAT-XXXX --solution implemented --no-prune
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### 3. 冲突处理原则
|
|
56
|
+
|
|
57
|
+
当 Feature 分支与主线产生冲突时:
|
|
58
|
+
|
|
59
|
+
1. **自动合并停止**: 如果 `touched files` (Issue `files` 字段) 与主线产生冲突,自动化工具**必须立即停止合并**,并抛出明确错误。
|
|
60
|
+
|
|
61
|
+
2. **手动 Cherry-Pick 模式**:
|
|
62
|
+
- 错误信息会指示 Agent 转入手动 Cherry-Pick 模式
|
|
63
|
+
- **核心原则**: 仅挑选属于本 Feature 的有效变更,严禁覆盖主线上无关 Issue 的更新
|
|
64
|
+
- 使用 `git cherry-pick <commit>` 逐个应用有效提交
|
|
65
|
+
|
|
66
|
+
3. **Fallback 策略**:
|
|
67
|
+
```bash
|
|
68
|
+
# 1. 创建临时分支用于解决冲突
|
|
69
|
+
$ git checkout main
|
|
70
|
+
$ git checkout -b temp/FEAT-XXXX-resolve
|
|
71
|
+
|
|
72
|
+
# 2. 逐个 Cherry-Pick 有效提交
|
|
73
|
+
$ git cherry-pick <commit-hash-1>
|
|
74
|
+
$ git cherry-pick <commit-hash-2>
|
|
75
|
+
|
|
76
|
+
# 3. 如有冲突,仅保留本 Feature 的变更
|
|
77
|
+
# 放弃任何会覆盖主线上其他 Issue 更新的修改
|
|
78
|
+
|
|
79
|
+
# 4. 完成后合并临时分支
|
|
80
|
+
$ git checkout main
|
|
81
|
+
$ git merge temp/FEAT-XXXX-resolve
|
|
82
|
+
|
|
83
|
+
# 5. 关闭 Issue
|
|
84
|
+
$ monoco issue close FEAT-XXXX --solution implemented
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### 4. 基于 files 字段的智能合并 (Smart Atomic Merge)
|
|
88
|
+
|
|
89
|
+
Issue 的 `files` 字段记录了 Feature 分支的真实影响范围 (Actual Impact Scope):
|
|
90
|
+
|
|
91
|
+
- **生成方式**: `monoco issue sync-files` 使用 `git diff --name-only base...target` 逻辑
|
|
92
|
+
- **作用**: 作为合并白名单,仅合并列表中的文件,过滤因"旧版本基线"导致的隐性覆盖
|
|
93
|
+
- **限制**: 无法防御显式的误操作修改(如无意中格式化其他 Issue 文件)
|
|
94
|
+
|
|
95
|
+
**未来增强**: 基于 `files` 列表实现选择性合并逻辑:
|
|
96
|
+
```bash
|
|
97
|
+
# 选择性合并(规划中)
|
|
98
|
+
$ git checkout main
|
|
99
|
+
$ git checkout feature/FEAT-XXXX -- <files...>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### 5. 清理策略
|
|
103
|
+
|
|
104
|
+
- **默认清理**: `monoco issue close` 默认执行 `--prune`,删除 Feature 分支/Worktree
|
|
105
|
+
- **保留分支**: 如需保留分支,显式使用 `--no-prune`
|
|
106
|
+
- **强制清理**: 使用 `--force` 强制删除未完全合并的分支(谨慎使用)
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# 默认清理分支
|
|
110
|
+
$ monoco issue close FEAT-XXXX --solution implemented
|
|
111
|
+
# ✔ Cleaned up: branch:feat/feat-XXXX-xxx
|
|
112
|
+
|
|
113
|
+
# 保留分支
|
|
114
|
+
$ monoco issue close FEAT-XXXX --solution implemented --no-prune
|
|
115
|
+
|
|
116
|
+
# 强制清理(谨慎)
|
|
117
|
+
$ monoco issue close FEAT-XXXX --solution implemented --force
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 总结
|
|
121
|
+
|
|
122
|
+
| 操作 | 命令 | 说明 |
|
|
123
|
+
|------|------|------|
|
|
124
|
+
| 创建 Issue | `monoco issue create feature -t "标题"` | 先创建 Issue 再开发 |
|
|
125
|
+
| 启动开发 | `monoco issue start FEAT-XXXX --branch` | 创建 Feature 分支 |
|
|
126
|
+
| 同步文件 | `monoco issue sync-files` | 更新 files 字段 |
|
|
127
|
+
| 提交评审 | `monoco issue submit FEAT-XXXX` | 进入 Review 阶段 |
|
|
128
|
+
| 关闭 Issue | `monoco issue close FEAT-XXXX --solution implemented` | 唯一合并途径 |
|
|
129
|
+
| 保留分支 | `monoco issue close ... --no-prune` | 关闭但不删除分支 |
|
|
130
|
+
|
|
131
|
+
> ⚠️ **警告**: 任何绕过 `monoco issue close` 的手动合并操作都可能导致主线状态污染,违反工作流合规要求。
|