monoco-toolkit 0.3.9__py3-none-any.whl → 0.3.11__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/config.py +38 -4
- monoco/core/git.py +23 -0
- monoco/core/hooks/builtin/git_cleanup.py +1 -1
- 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/injection.py +63 -29
- monoco/core/integrations.py +2 -2
- monoco/core/loader.py +633 -0
- monoco/core/output.py +5 -5
- monoco/core/registry.py +34 -19
- monoco/core/resource/__init__.py +5 -0
- monoco/core/resource/finder.py +98 -0
- monoco/core/resource/manager.py +91 -0
- monoco/core/resource/models.py +35 -0
- monoco/core/skill_framework.py +292 -0
- monoco/core/skills.py +524 -385
- monoco/core/sync.py +73 -1
- monoco/core/workflow_converter.py +420 -0
- monoco/daemon/app.py +77 -1
- monoco/daemon/commands.py +10 -0
- monoco/daemon/mailroom_service.py +196 -0
- monoco/daemon/models.py +1 -0
- monoco/daemon/scheduler.py +236 -0
- monoco/daemon/services.py +185 -0
- monoco/daemon/triggers.py +55 -0
- monoco/features/agent/__init__.py +2 -2
- monoco/features/agent/adapter.py +41 -0
- monoco/features/agent/apoptosis.py +44 -0
- monoco/features/agent/cli.py +101 -144
- monoco/features/agent/config.py +35 -21
- monoco/features/agent/defaults.py +6 -49
- monoco/features/agent/engines.py +32 -6
- monoco/features/agent/manager.py +47 -6
- monoco/features/agent/models.py +2 -2
- monoco/features/agent/resources/atoms/atom-code-dev.yaml +61 -0
- monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +73 -0
- monoco/features/agent/resources/atoms/atom-knowledge.yaml +55 -0
- monoco/features/agent/resources/atoms/atom-review.yaml +60 -0
- monoco/{core/resources/en → features/agent/resources/en/skills/monoco_atom_core}/SKILL.md +3 -1
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +94 -0
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +93 -0
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +85 -0
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +114 -0
- monoco/features/agent/resources/workflows/workflow-dev.yaml +83 -0
- monoco/features/agent/resources/workflows/workflow-issue-create.yaml +72 -0
- monoco/features/agent/resources/workflows/workflow-review.yaml +94 -0
- monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +49 -0
- monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +46 -0
- monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +46 -0
- monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +47 -0
- monoco/{core/resources/zh → features/agent/resources/zh/skills/monoco_atom_core}/SKILL.md +3 -1
- monoco/features/agent/resources/{skills/flow_engineer → zh/skills/monoco_workflow_agent_engineer}/SKILL.md +2 -2
- monoco/features/agent/resources/{skills/flow_manager → zh/skills/monoco_workflow_agent_manager}/SKILL.md +2 -2
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +259 -0
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +137 -0
- monoco/features/agent/session.py +59 -11
- monoco/features/agent/worker.py +38 -2
- 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/__init__.py +0 -0
- monoco/features/glossary/adapter.py +42 -0
- monoco/features/glossary/config.py +5 -0
- monoco/features/glossary/resources/en/AGENTS.md +29 -0
- monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +35 -0
- monoco/features/glossary/resources/zh/AGENTS.md +29 -0
- monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +35 -0
- 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/{SKILL.md → skills/monoco_atom_i18n/SKILL.md} +3 -1
- monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +105 -0
- monoco/features/i18n/resources/zh/{SKILL.md → skills/monoco_atom_i18n/SKILL.md} +3 -1
- monoco/features/i18n/resources/{skills/i18n_scan_workflow → zh/skills/monoco_workflow_i18n_scan}/SKILL.md +2 -2
- monoco/features/issue/adapter.py +19 -6
- monoco/features/issue/commands.py +281 -7
- monoco/features/issue/core.py +272 -19
- monoco/features/issue/engine/machine.py +118 -5
- monoco/features/issue/linter.py +60 -5
- monoco/features/issue/models.py +3 -2
- monoco/features/issue/resources/en/AGENTS.md +109 -0
- monoco/features/issue/resources/en/{SKILL.md → skills/monoco_atom_issue/SKILL.md} +3 -1
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +167 -0
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +224 -0
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +159 -0
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +203 -0
- 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/{SKILL.md → skills/monoco_atom_issue_lifecycle/SKILL.md} +3 -1
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +167 -0
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +224 -0
- monoco/features/issue/resources/{skills/issue_lifecycle_workflow → zh/skills/monoco_workflow_issue_management}/SKILL.md +2 -2
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +203 -0
- 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_atom_memo/SKILL.md +77 -0
- monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +140 -0
- monoco/features/memo/resources/zh/{SKILL.md → skills/monoco_atom_memo/SKILL.md} +3 -1
- monoco/features/memo/resources/{skills/note_processing_workflow → zh/skills/monoco_workflow_note_processing}/SKILL.md +2 -2
- monoco/features/spike/adapter.py +18 -5
- monoco/features/spike/resources/en/{SKILL.md → skills/monoco_atom_spike/SKILL.md} +3 -1
- monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +121 -0
- monoco/features/spike/resources/zh/{SKILL.md → skills/monoco_atom_spike/SKILL.md} +3 -1
- monoco/features/spike/resources/{skills/research_workflow → zh/skills/monoco_workflow_research}/SKILL.md +2 -2
- monoco/main.py +38 -1
- monoco_toolkit-0.3.11.dist-info/METADATA +130 -0
- monoco_toolkit-0.3.11.dist-info/RECORD +181 -0
- monoco/features/agent/reliability.py +0 -106
- monoco/features/agent/resources/skills/flow_reviewer/SKILL.md +0 -114
- monoco_toolkit-0.3.9.dist-info/METADATA +0 -127
- monoco_toolkit-0.3.9.dist-info/RECORD +0 -115
- /monoco/{core → features/agent}/resources/en/AGENTS.md +0 -0
- /monoco/{core → features/agent}/resources/zh/AGENTS.md +0 -0
- {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.11.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.11.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.3.9.dist-info → monoco_toolkit-0.3.11.dist-info}/licenses/LICENSE +0 -0
monoco/features/issue/adapter.py
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Dict
|
|
3
|
-
from monoco.core.
|
|
3
|
+
from monoco.core.loader import FeatureModule, FeatureMetadata
|
|
4
|
+
from monoco.core.feature import IntegrationData
|
|
4
5
|
from monoco.features.issue import core
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
class IssueFeature(
|
|
8
|
-
|
|
9
|
-
def name(self) -> str:
|
|
10
|
-
return "issue"
|
|
8
|
+
class IssueFeature(FeatureModule):
|
|
9
|
+
"""Issue management feature module with unified lifecycle support."""
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
@property
|
|
12
|
+
def metadata(self) -> FeatureMetadata:
|
|
13
|
+
return FeatureMetadata(
|
|
14
|
+
name="issue",
|
|
15
|
+
version="1.0.0",
|
|
16
|
+
description="Issue management system for Monoco",
|
|
17
|
+
dependencies=["core"],
|
|
18
|
+
priority=10, # High priority - load early
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def _on_mount(self, context: "FeatureContext") -> None: # type: ignore
|
|
22
|
+
"""Initialize issue feature with workspace context."""
|
|
23
|
+
root = context.root
|
|
24
|
+
config = context.config
|
|
13
25
|
issues_path = root / config.get("paths", {}).get("issues", "Issues")
|
|
14
26
|
core.init(issues_path)
|
|
15
27
|
|
|
16
28
|
def integrate(self, root: Path, config: Dict) -> IntegrationData:
|
|
29
|
+
"""Provide integration data for agent environment."""
|
|
17
30
|
# Determine language from config, default to 'en'
|
|
18
31
|
lang = config.get("i18n", {}).get("source_lang", "en")
|
|
19
32
|
|
|
@@ -43,6 +43,9 @@ def create(
|
|
|
43
43
|
related: List[str] = typer.Option(
|
|
44
44
|
[], "--related", "-r", help="Related Issue ID(s)"
|
|
45
45
|
),
|
|
46
|
+
from_memo: List[str] = typer.Option(
|
|
47
|
+
[], "--from-memo", "-m", help="Memo ID(s) to link to this issue"
|
|
48
|
+
),
|
|
46
49
|
force: bool = typer.Option(False, "--force", help="Bypass branch context checks"),
|
|
47
50
|
subdir: Optional[str] = typer.Option(
|
|
48
51
|
None,
|
|
@@ -118,20 +121,46 @@ def create(
|
|
|
118
121
|
criticality=criticality_level,
|
|
119
122
|
)
|
|
120
123
|
|
|
124
|
+
# Link memos to the newly created issue
|
|
125
|
+
linked_memos = []
|
|
126
|
+
missing_memos = []
|
|
127
|
+
if from_memo:
|
|
128
|
+
from monoco.features.memo.core import load_memos, update_memo
|
|
129
|
+
|
|
130
|
+
existing_memos = {m.uid: m for m in load_memos(issues_root)}
|
|
131
|
+
|
|
132
|
+
for memo_id in from_memo:
|
|
133
|
+
if memo_id in existing_memos:
|
|
134
|
+
# Only update if not already linked to this issue (idempotency)
|
|
135
|
+
memo = existing_memos[memo_id]
|
|
136
|
+
if memo.ref != issue.id:
|
|
137
|
+
update_memo(issues_root, memo_id, {"status": "tracked", "ref": issue.id})
|
|
138
|
+
linked_memos.append(memo_id)
|
|
139
|
+
else:
|
|
140
|
+
missing_memos.append(memo_id)
|
|
141
|
+
|
|
121
142
|
try:
|
|
122
143
|
rel_path = path.relative_to(Path.cwd())
|
|
123
144
|
except ValueError:
|
|
124
145
|
rel_path = path
|
|
125
146
|
|
|
126
147
|
if OutputManager.is_agent_mode():
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
result = {"issue": issue, "path": str(rel_path), "status": "created"}
|
|
149
|
+
if linked_memos:
|
|
150
|
+
result["linked_memos"] = linked_memos
|
|
151
|
+
if missing_memos:
|
|
152
|
+
result["missing_memos"] = missing_memos
|
|
153
|
+
OutputManager.print(result)
|
|
130
154
|
else:
|
|
131
155
|
console.print(
|
|
132
156
|
f"[green]✔ Created {issue.id} in status {issue.status}.[/green]"
|
|
133
157
|
)
|
|
134
158
|
console.print(f"Path: {rel_path}")
|
|
159
|
+
|
|
160
|
+
if linked_memos:
|
|
161
|
+
console.print(f"[green]✔ Linked {len(linked_memos)} memo(s): {', '.join(linked_memos)}[/green]")
|
|
162
|
+
if missing_memos:
|
|
163
|
+
console.print(f"[yellow]⚠ Memo(s) not found: {', '.join(missing_memos)}[/yellow]")
|
|
135
164
|
|
|
136
165
|
# Prompt for Language
|
|
137
166
|
source_lang = config.i18n.source_lang or "en"
|
|
@@ -162,6 +191,7 @@ def update(
|
|
|
162
191
|
title: Optional[str] = typer.Option(None, "--title", "-t", help="New title"),
|
|
163
192
|
status: Optional[str] = typer.Option(None, "--status", help="New status"),
|
|
164
193
|
stage: Optional[str] = typer.Option(None, "--stage", help="New stage"),
|
|
194
|
+
solution: Optional[str] = typer.Option(None, "--solution", "-s", help="Solution type (implemented, cancelled, wontfix, duplicate)"),
|
|
165
195
|
parent: Optional[str] = typer.Option(
|
|
166
196
|
None, "--parent", "-p", help="Parent Issue ID"
|
|
167
197
|
),
|
|
@@ -188,6 +218,7 @@ def update(
|
|
|
188
218
|
issue_id,
|
|
189
219
|
status=status,
|
|
190
220
|
stage=stage,
|
|
221
|
+
solution=solution,
|
|
191
222
|
title=title,
|
|
192
223
|
parent=parent,
|
|
193
224
|
sprint=sprint,
|
|
@@ -398,7 +429,7 @@ def move_close(
|
|
|
398
429
|
None, "--solution", "-s", help="Solution type"
|
|
399
430
|
),
|
|
400
431
|
prune: bool = typer.Option(
|
|
401
|
-
|
|
432
|
+
True, "--prune/--no-prune", help="Delete branch/worktree after close (default: True)"
|
|
402
433
|
),
|
|
403
434
|
force: bool = typer.Option(False, "--force", help="Force delete branch/worktree"),
|
|
404
435
|
force_prune: bool = typer.Option(
|
|
@@ -453,6 +484,32 @@ def move_close(
|
|
|
453
484
|
force = True
|
|
454
485
|
|
|
455
486
|
try:
|
|
487
|
+
# 0. Perform Smart Atomic Merge (FEAT-0154)
|
|
488
|
+
merged_files = []
|
|
489
|
+
try:
|
|
490
|
+
merged_files = core.merge_issue_changes(issues_root, issue_id, project_root)
|
|
491
|
+
if merged_files:
|
|
492
|
+
if not OutputManager.is_agent_mode():
|
|
493
|
+
console.print(
|
|
494
|
+
f"[green]✔ Smart Merge:[/green] Synced {len(merged_files)} files from feature branch."
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Auto-commit merged files if not no_commit
|
|
498
|
+
if not no_commit:
|
|
499
|
+
commit_msg = f"feat: atomic merge changes from {issue_id}"
|
|
500
|
+
try:
|
|
501
|
+
git.git_commit(project_root, commit_msg)
|
|
502
|
+
if not OutputManager.is_agent_mode():
|
|
503
|
+
console.print(f"[green]✔ Committed merged changes.[/green]")
|
|
504
|
+
except Exception as e:
|
|
505
|
+
# If commit fails (e.g. nothing to commit?), just warn
|
|
506
|
+
if not OutputManager.is_agent_mode():
|
|
507
|
+
console.print(f"[yellow]⚠ Commit skipped: {e}[/yellow]")
|
|
508
|
+
|
|
509
|
+
except Exception as e:
|
|
510
|
+
OutputManager.error(f"Merge Error: {e}")
|
|
511
|
+
raise typer.Exit(code=1)
|
|
512
|
+
|
|
456
513
|
issue = core.update_issue(
|
|
457
514
|
issues_root,
|
|
458
515
|
issue_id,
|
|
@@ -464,10 +521,37 @@ def move_close(
|
|
|
464
521
|
|
|
465
522
|
pruned_resources = []
|
|
466
523
|
if prune:
|
|
524
|
+
# Get isolation info for confirmation prompt
|
|
525
|
+
isolation_info = None
|
|
526
|
+
if issue.isolation:
|
|
527
|
+
isolation_type = issue.isolation.type.value if issue.isolation.type else None
|
|
528
|
+
isolation_ref = issue.isolation.ref
|
|
529
|
+
isolation_info = (isolation_type, isolation_ref)
|
|
530
|
+
|
|
531
|
+
# Interactive confirmation before pruning (non-agent mode only)
|
|
532
|
+
if not OutputManager.is_agent_mode() and isolation_info and not force:
|
|
533
|
+
iso_type, iso_ref = isolation_info
|
|
534
|
+
if iso_ref:
|
|
535
|
+
console.print(f"\n[bold yellow]⚠️ Resource Cleanup Confirmation[/bold yellow]")
|
|
536
|
+
console.print(f"Issue [cyan]{issue_id}[/cyan] will be closed with the following action:")
|
|
537
|
+
console.print(f" • Delete {iso_type}: [bold]{iso_ref}[/bold]")
|
|
538
|
+
console.print(f"\n[dim]This operation will permanently remove the {iso_type}. "
|
|
539
|
+
f"Ensure all changes have been merged to main.[/dim]")
|
|
540
|
+
confirm = typer.confirm(
|
|
541
|
+
f"\nProceed with closing {issue_id} and deleting {iso_type}?",
|
|
542
|
+
default=True,
|
|
543
|
+
)
|
|
544
|
+
if not confirm:
|
|
545
|
+
console.print(f"[yellow]Close operation cancelled.[/yellow]")
|
|
546
|
+
console.print(f"[dim]Tip: Use --no-prune to close without deleting {iso_type}.[/dim]")
|
|
547
|
+
raise typer.Abort()
|
|
548
|
+
|
|
467
549
|
try:
|
|
468
550
|
pruned_resources = core.prune_issue_resources(
|
|
469
551
|
issues_root, issue_id, force, project_root
|
|
470
552
|
)
|
|
553
|
+
if pruned_resources and not OutputManager.is_agent_mode():
|
|
554
|
+
console.print(f"[green]✔ Cleaned up:[/green] {', '.join(pruned_resources)}")
|
|
471
555
|
except Exception as e:
|
|
472
556
|
OutputManager.error(f"Prune Error: {e}")
|
|
473
557
|
raise typer.Exit(code=1)
|
|
@@ -727,6 +811,9 @@ def list_cmd(
|
|
|
727
811
|
workspace: bool = typer.Option(
|
|
728
812
|
False, "--workspace", "-w", help="Include issues from workspace members"
|
|
729
813
|
),
|
|
814
|
+
all: bool = typer.Option(
|
|
815
|
+
False, "--all", "-a", help="Include archived issues in the list"
|
|
816
|
+
),
|
|
730
817
|
json: AgentOutput = False,
|
|
731
818
|
):
|
|
732
819
|
"""List issues in a table format with filtering."""
|
|
@@ -734,15 +821,15 @@ def list_cmd(
|
|
|
734
821
|
issues_root = _resolve_issues_root(config, root)
|
|
735
822
|
|
|
736
823
|
# Validation
|
|
737
|
-
if status and status.lower() not in ["open", "closed", "backlog", "all"]:
|
|
824
|
+
if status and status.lower() not in ["open", "closed", "backlog", "archived", "all"]:
|
|
738
825
|
OutputManager.error(
|
|
739
|
-
f"Invalid status: {status}. Use open, closed, backlog or all."
|
|
826
|
+
f"Invalid status: {status}. Use open, closed, backlog, archived or all."
|
|
740
827
|
)
|
|
741
828
|
raise typer.Exit(code=1)
|
|
742
829
|
|
|
743
830
|
target_status = status.lower() if status else "open"
|
|
744
831
|
|
|
745
|
-
issues = core.list_issues(issues_root, recursive_workspace=workspace)
|
|
832
|
+
issues = core.list_issues(issues_root, recursive_workspace=workspace, include_archived=all)
|
|
746
833
|
filtered = []
|
|
747
834
|
|
|
748
835
|
for i in issues:
|
|
@@ -1521,6 +1608,193 @@ def show(
|
|
|
1521
1608
|
OutputManager.print(result)
|
|
1522
1609
|
|
|
1523
1610
|
|
|
1611
|
+
@app.command("check-critical")
|
|
1612
|
+
def check_critical(
|
|
1613
|
+
fail_on_warning: bool = typer.Option(
|
|
1614
|
+
False,
|
|
1615
|
+
"--fail-on-warning",
|
|
1616
|
+
help="Exit with error code if high priority issues are found",
|
|
1617
|
+
),
|
|
1618
|
+
root: Optional[str] = typer.Option(
|
|
1619
|
+
None, "--root", help="Override issues root directory"
|
|
1620
|
+
),
|
|
1621
|
+
json: AgentOutput = False,
|
|
1622
|
+
):
|
|
1623
|
+
"""
|
|
1624
|
+
Check for incomplete critical/high priority issues.
|
|
1625
|
+
|
|
1626
|
+
Returns:
|
|
1627
|
+
0: No critical/high issues found
|
|
1628
|
+
1: High priority issues found (warning)
|
|
1629
|
+
2: Critical issues found (blocking)
|
|
1630
|
+
"""
|
|
1631
|
+
from .criticality import CriticalityLevel
|
|
1632
|
+
|
|
1633
|
+
config = get_config()
|
|
1634
|
+
issues_root = _resolve_issues_root(config, root)
|
|
1635
|
+
|
|
1636
|
+
issues = core.list_issues(issues_root)
|
|
1637
|
+
|
|
1638
|
+
critical_issues = []
|
|
1639
|
+
high_issues = []
|
|
1640
|
+
|
|
1641
|
+
for issue in issues:
|
|
1642
|
+
if issue.status == "closed":
|
|
1643
|
+
continue
|
|
1644
|
+
|
|
1645
|
+
criticality = issue.criticality
|
|
1646
|
+
if not criticality:
|
|
1647
|
+
continue
|
|
1648
|
+
|
|
1649
|
+
if criticality == CriticalityLevel.CRITICAL:
|
|
1650
|
+
critical_issues.append(issue)
|
|
1651
|
+
elif criticality == CriticalityLevel.HIGH:
|
|
1652
|
+
high_issues.append(issue)
|
|
1653
|
+
|
|
1654
|
+
result = {
|
|
1655
|
+
"critical_count": len(critical_issues),
|
|
1656
|
+
"high_count": len(high_issues),
|
|
1657
|
+
"critical_issues": [
|
|
1658
|
+
{"id": i.id, "title": i.title, "stage": i.stage}
|
|
1659
|
+
for i in critical_issues
|
|
1660
|
+
],
|
|
1661
|
+
"high_issues": [
|
|
1662
|
+
{"id": i.id, "title": i.title, "stage": i.stage}
|
|
1663
|
+
for i in high_issues
|
|
1664
|
+
],
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
if OutputManager.is_agent_mode() or json:
|
|
1668
|
+
OutputManager.print(result)
|
|
1669
|
+
else:
|
|
1670
|
+
if critical_issues:
|
|
1671
|
+
console.print("[red]❌ Critical issues (blocking):[/red]")
|
|
1672
|
+
for issue in critical_issues:
|
|
1673
|
+
console.print(f" [red]• {issue.id}:[/red] {issue.title} ({issue.stage})")
|
|
1674
|
+
|
|
1675
|
+
if high_issues:
|
|
1676
|
+
console.print("[yellow]⚠️ High priority issues (warning):[/yellow]")
|
|
1677
|
+
for issue in high_issues:
|
|
1678
|
+
console.print(f" [yellow]• {issue.id}:[/yellow] {issue.title} ({issue.stage})")
|
|
1679
|
+
|
|
1680
|
+
if not critical_issues and not high_issues:
|
|
1681
|
+
console.print("[green]✓ No incomplete critical or high priority issues.[/green]")
|
|
1682
|
+
|
|
1683
|
+
# Determine exit code
|
|
1684
|
+
if critical_issues:
|
|
1685
|
+
raise typer.Exit(code=2)
|
|
1686
|
+
elif high_issues and fail_on_warning:
|
|
1687
|
+
raise typer.Exit(code=1)
|
|
1688
|
+
elif high_issues:
|
|
1689
|
+
# Warning only, don't fail
|
|
1690
|
+
pass
|
|
1691
|
+
|
|
1692
|
+
raise typer.Exit(code=0)
|
|
1693
|
+
|
|
1694
|
+
|
|
1695
|
+
@app.command("sync-isolation")
|
|
1696
|
+
def sync_isolation(
|
|
1697
|
+
issue_id: str = typer.Argument(..., help="Issue ID to sync isolation for"),
|
|
1698
|
+
branch: Optional[str] = typer.Option(
|
|
1699
|
+
None, "--branch", help="Current branch name (auto-detected if not provided)"
|
|
1700
|
+
),
|
|
1701
|
+
root: Optional[str] = typer.Option(
|
|
1702
|
+
None, "--root", help="Override issues root directory"
|
|
1703
|
+
),
|
|
1704
|
+
json: AgentOutput = False,
|
|
1705
|
+
):
|
|
1706
|
+
"""
|
|
1707
|
+
Sync issue isolation configuration with current branch.
|
|
1708
|
+
|
|
1709
|
+
Updates the isolation.ref field to match the current branch context.
|
|
1710
|
+
This is typically called by the post-checkout hook.
|
|
1711
|
+
"""
|
|
1712
|
+
import subprocess
|
|
1713
|
+
from .core import update_issue_field
|
|
1714
|
+
|
|
1715
|
+
config = get_config()
|
|
1716
|
+
issues_root = _resolve_issues_root(config, root)
|
|
1717
|
+
project_root = Path(config.paths.root).resolve()
|
|
1718
|
+
|
|
1719
|
+
# Auto-detect branch if not provided
|
|
1720
|
+
if not branch:
|
|
1721
|
+
try:
|
|
1722
|
+
result = subprocess.run(
|
|
1723
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
1724
|
+
cwd=project_root,
|
|
1725
|
+
capture_output=True,
|
|
1726
|
+
text=True,
|
|
1727
|
+
check=True,
|
|
1728
|
+
)
|
|
1729
|
+
branch = result.stdout.strip()
|
|
1730
|
+
except Exception:
|
|
1731
|
+
OutputManager.error("Could not detect current branch. Use --branch.")
|
|
1732
|
+
raise typer.Exit(code=1)
|
|
1733
|
+
|
|
1734
|
+
# Find the issue
|
|
1735
|
+
issue_path = core.find_issue_path(issues_root, issue_id)
|
|
1736
|
+
if not issue_path:
|
|
1737
|
+
OutputManager.error(f"Issue {issue_id} not found.")
|
|
1738
|
+
raise typer.Exit(code=1)
|
|
1739
|
+
|
|
1740
|
+
# Parse current issue
|
|
1741
|
+
issue = core.parse_issue(issue_path)
|
|
1742
|
+
if not issue:
|
|
1743
|
+
OutputManager.error(f"Could not parse issue {issue_id}.")
|
|
1744
|
+
raise typer.Exit(code=1)
|
|
1745
|
+
|
|
1746
|
+
# Check if isolation needs updating
|
|
1747
|
+
current_isolation = issue.isolation
|
|
1748
|
+
if current_isolation:
|
|
1749
|
+
current_ref = current_isolation.ref or ""
|
|
1750
|
+
current_isolation_dict = {
|
|
1751
|
+
"type": current_isolation.type,
|
|
1752
|
+
"ref": current_isolation.ref,
|
|
1753
|
+
}
|
|
1754
|
+
if current_isolation.path:
|
|
1755
|
+
current_isolation_dict["path"] = current_isolation.path
|
|
1756
|
+
if current_isolation.created_at:
|
|
1757
|
+
current_isolation_dict["created_at"] = current_isolation.created_at.isoformat()
|
|
1758
|
+
else:
|
|
1759
|
+
current_ref = ""
|
|
1760
|
+
current_isolation_dict = {}
|
|
1761
|
+
|
|
1762
|
+
# Expected ref format: branch:branch-name
|
|
1763
|
+
expected_ref = f"branch:{branch}"
|
|
1764
|
+
|
|
1765
|
+
if current_ref == expected_ref:
|
|
1766
|
+
if OutputManager.is_agent_mode() or json:
|
|
1767
|
+
OutputManager.print({"updated": False, "message": "Isolation already up to date"})
|
|
1768
|
+
else:
|
|
1769
|
+
console.print(f"[dim]Isolation already up to date for {issue_id}[/dim]")
|
|
1770
|
+
raise typer.Exit(code=0)
|
|
1771
|
+
|
|
1772
|
+
# Update the isolation field
|
|
1773
|
+
new_isolation = {**current_isolation_dict, "ref": expected_ref, "type": "branch"}
|
|
1774
|
+
|
|
1775
|
+
try:
|
|
1776
|
+
update_issue_field(
|
|
1777
|
+
issue_path,
|
|
1778
|
+
"isolation",
|
|
1779
|
+
new_isolation,
|
|
1780
|
+
)
|
|
1781
|
+
|
|
1782
|
+
result = {
|
|
1783
|
+
"updated": True,
|
|
1784
|
+
"issue_id": issue_id,
|
|
1785
|
+
"isolation": new_isolation,
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
if OutputManager.is_agent_mode() or json:
|
|
1789
|
+
OutputManager.print(result)
|
|
1790
|
+
else:
|
|
1791
|
+
console.print(f"[green]✓ Updated isolation for {issue_id}:[/green] {expected_ref}")
|
|
1792
|
+
|
|
1793
|
+
except Exception as e:
|
|
1794
|
+
OutputManager.error(f"Failed to update isolation: {e}")
|
|
1795
|
+
raise typer.Exit(code=1)
|
|
1796
|
+
|
|
1797
|
+
|
|
1524
1798
|
def _validate_branch_context(
|
|
1525
1799
|
project_root: Path,
|
|
1526
1800
|
allowed: Optional[List[str]] = None,
|