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
|
@@ -2,6 +2,7 @@ import typer
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Optional, List
|
|
4
4
|
from datetime import datetime
|
|
5
|
+
import logging
|
|
5
6
|
from rich.console import Console
|
|
6
7
|
from rich.tree import Tree
|
|
7
8
|
from rich.panel import Panel
|
|
@@ -24,6 +25,7 @@ from . import domain_commands
|
|
|
24
25
|
|
|
25
26
|
app.add_typer(domain_commands.app, name="domain")
|
|
26
27
|
console = Console()
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
@app.command("create")
|
|
@@ -43,6 +45,9 @@ def create(
|
|
|
43
45
|
related: List[str] = typer.Option(
|
|
44
46
|
[], "--related", "-r", help="Related Issue ID(s)"
|
|
45
47
|
),
|
|
48
|
+
from_memo: List[str] = typer.Option(
|
|
49
|
+
[], "--from-memo", "-m", help="Memo ID(s) to link to this issue"
|
|
50
|
+
),
|
|
46
51
|
force: bool = typer.Option(False, "--force", help="Bypass branch context checks"),
|
|
47
52
|
subdir: Optional[str] = typer.Option(
|
|
48
53
|
None,
|
|
@@ -118,20 +123,46 @@ def create(
|
|
|
118
123
|
criticality=criticality_level,
|
|
119
124
|
)
|
|
120
125
|
|
|
126
|
+
# Link memos to the newly created issue
|
|
127
|
+
linked_memos = []
|
|
128
|
+
missing_memos = []
|
|
129
|
+
if from_memo:
|
|
130
|
+
from monoco.features.memo.core import load_memos, update_memo
|
|
131
|
+
|
|
132
|
+
existing_memos = {m.uid: m for m in load_memos(issues_root)}
|
|
133
|
+
|
|
134
|
+
for memo_id in from_memo:
|
|
135
|
+
if memo_id in existing_memos:
|
|
136
|
+
# Only update if not already linked to this issue (idempotency)
|
|
137
|
+
memo = existing_memos[memo_id]
|
|
138
|
+
if memo.ref != issue.id:
|
|
139
|
+
update_memo(issues_root, memo_id, {"status": "tracked", "ref": issue.id})
|
|
140
|
+
linked_memos.append(memo_id)
|
|
141
|
+
else:
|
|
142
|
+
missing_memos.append(memo_id)
|
|
143
|
+
|
|
121
144
|
try:
|
|
122
145
|
rel_path = path.relative_to(Path.cwd())
|
|
123
146
|
except ValueError:
|
|
124
147
|
rel_path = path
|
|
125
148
|
|
|
126
149
|
if OutputManager.is_agent_mode():
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
150
|
+
result = {"issue": issue, "path": str(rel_path), "status": "created"}
|
|
151
|
+
if linked_memos:
|
|
152
|
+
result["linked_memos"] = linked_memos
|
|
153
|
+
if missing_memos:
|
|
154
|
+
result["missing_memos"] = missing_memos
|
|
155
|
+
OutputManager.print(result)
|
|
130
156
|
else:
|
|
131
157
|
console.print(
|
|
132
158
|
f"[green]✔ Created {issue.id} in status {issue.status}.[/green]"
|
|
133
159
|
)
|
|
134
160
|
console.print(f"Path: {rel_path}")
|
|
161
|
+
|
|
162
|
+
if linked_memos:
|
|
163
|
+
console.print(f"[green]✔ Linked {len(linked_memos)} memo(s): {', '.join(linked_memos)}[/green]")
|
|
164
|
+
if missing_memos:
|
|
165
|
+
console.print(f"[yellow]⚠ Memo(s) not found: {', '.join(missing_memos)}[/yellow]")
|
|
135
166
|
|
|
136
167
|
# Prompt for Language
|
|
137
168
|
source_lang = config.i18n.source_lang or "en"
|
|
@@ -162,6 +193,7 @@ def update(
|
|
|
162
193
|
title: Optional[str] = typer.Option(None, "--title", "-t", help="New title"),
|
|
163
194
|
status: Optional[str] = typer.Option(None, "--status", help="New status"),
|
|
164
195
|
stage: Optional[str] = typer.Option(None, "--stage", help="New stage"),
|
|
196
|
+
solution: Optional[str] = typer.Option(None, "--solution", "-s", help="Solution type (implemented, cancelled, wontfix, duplicate)"),
|
|
165
197
|
parent: Optional[str] = typer.Option(
|
|
166
198
|
None, "--parent", "-p", help="Parent Issue ID"
|
|
167
199
|
),
|
|
@@ -188,6 +220,7 @@ def update(
|
|
|
188
220
|
issue_id,
|
|
189
221
|
status=status,
|
|
190
222
|
stage=stage,
|
|
223
|
+
solution=solution,
|
|
191
224
|
title=title,
|
|
192
225
|
parent=parent,
|
|
193
226
|
sprint=sprint,
|
|
@@ -360,6 +393,13 @@ def submit(
|
|
|
360
393
|
)
|
|
361
394
|
|
|
362
395
|
try:
|
|
396
|
+
# FEAT-0163: Automatically sync files before submission to ensure manifest completeness
|
|
397
|
+
try:
|
|
398
|
+
core.sync_issue_files(issues_root, issue_id, project_root)
|
|
399
|
+
except Exception as se:
|
|
400
|
+
# Just log warning, don't fail submit if sync fails (defensive)
|
|
401
|
+
logger.warning(f"Auto-sync failed during submit for {issue_id}: {se}")
|
|
402
|
+
|
|
363
403
|
# Implicitly ensure status is Open
|
|
364
404
|
issue = core.update_issue(
|
|
365
405
|
issues_root,
|
|
@@ -398,7 +438,7 @@ def move_close(
|
|
|
398
438
|
None, "--solution", "-s", help="Solution type"
|
|
399
439
|
),
|
|
400
440
|
prune: bool = typer.Option(
|
|
401
|
-
|
|
441
|
+
True, "--prune/--no-prune", help="Delete branch/worktree after close (default: True)"
|
|
402
442
|
),
|
|
403
443
|
force: bool = typer.Option(False, "--force", help="Force delete branch/worktree"),
|
|
404
444
|
force_prune: bool = typer.Option(
|
|
@@ -414,7 +454,11 @@ def move_close(
|
|
|
414
454
|
),
|
|
415
455
|
json: AgentOutput = False,
|
|
416
456
|
):
|
|
417
|
-
"""Close issue.
|
|
457
|
+
"""Close issue with atomic transaction guarantee.
|
|
458
|
+
|
|
459
|
+
If any step fails, all changes made during the close operation will be
|
|
460
|
+
automatically rolled back to ensure the mainline remains clean.
|
|
461
|
+
"""
|
|
418
462
|
config = get_config()
|
|
419
463
|
issues_root = _resolve_issues_root(config, root)
|
|
420
464
|
project_root = _resolve_project_root(config)
|
|
@@ -441,43 +485,141 @@ def move_close(
|
|
|
441
485
|
|
|
442
486
|
# Handle force-prune logic
|
|
443
487
|
if force_prune:
|
|
444
|
-
#
|
|
445
|
-
|
|
488
|
+
# FEAT-0125: force-prune requires interactive confirmation to avoid accidental data loss
|
|
489
|
+
# unless in agent mode (which is handled by typer.confirm's default)
|
|
490
|
+
if not OutputManager.is_agent_mode():
|
|
446
491
|
confirm = typer.confirm(
|
|
447
|
-
"
|
|
448
|
-
default=False
|
|
492
|
+
"⚠ FORCE PRUNE will permanently delete the feature branch without checking its merge status. Continue?",
|
|
493
|
+
default=False
|
|
449
494
|
)
|
|
450
495
|
if not confirm:
|
|
451
|
-
|
|
496
|
+
console.print("[yellow]Aborted.[/yellow]")
|
|
497
|
+
raise typer.Exit(code=1)
|
|
498
|
+
|
|
452
499
|
prune = True
|
|
453
500
|
force = True
|
|
454
501
|
|
|
502
|
+
# ATOMIC TRANSACTION: Capture initial state for potential rollback
|
|
503
|
+
initial_head = None
|
|
504
|
+
transaction_commits = []
|
|
505
|
+
|
|
506
|
+
def rollback_transaction():
|
|
507
|
+
"""Rollback all changes made during the transaction."""
|
|
508
|
+
if initial_head and transaction_commits:
|
|
509
|
+
try:
|
|
510
|
+
git.git_reset_hard(project_root, initial_head)
|
|
511
|
+
if not OutputManager.is_agent_mode():
|
|
512
|
+
console.print(f"[yellow]↩ Rolled back to {initial_head[:7]}[/yellow]")
|
|
513
|
+
except Exception as rollback_error:
|
|
514
|
+
if not OutputManager.is_agent_mode():
|
|
515
|
+
console.print(f"[red]⚠ Rollback failed: {rollback_error}[/red]")
|
|
516
|
+
console.print(f"[red] Manual recovery may be required. Run: git reset --hard {initial_head[:7]}[/red]")
|
|
517
|
+
|
|
455
518
|
try:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
project_root
|
|
519
|
+
# Capture initial HEAD before any modifications
|
|
520
|
+
initial_head = git.get_current_head(project_root)
|
|
521
|
+
|
|
522
|
+
# 0. Find issue across branches (FIX-0006)
|
|
523
|
+
# This will raise RuntimeError if issue found in multiple branches
|
|
524
|
+
found_path, source_branch = core.find_issue_path_across_branches(
|
|
525
|
+
issues_root, issue_id, project_root
|
|
463
526
|
)
|
|
527
|
+
if not found_path:
|
|
528
|
+
OutputManager.error(f"Issue {issue_id} not found in any branch.")
|
|
529
|
+
raise typer.Exit(code=1)
|
|
530
|
+
|
|
531
|
+
# If issue was found in a different branch, notify user
|
|
532
|
+
if source_branch and source_branch != git.get_current_branch(project_root):
|
|
533
|
+
if not OutputManager.is_agent_mode():
|
|
534
|
+
console.print(
|
|
535
|
+
f"[green]✔ Found {issue_id} in branch '{source_branch}', synced to working tree.[/green]"
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
# 1. Perform Smart Atomic Merge (FEAT-0154)
|
|
539
|
+
merged_files = []
|
|
540
|
+
try:
|
|
541
|
+
merged_files = core.merge_issue_changes(issues_root, issue_id, project_root)
|
|
542
|
+
if merged_files:
|
|
543
|
+
if not OutputManager.is_agent_mode():
|
|
544
|
+
console.print(
|
|
545
|
+
f"[green]✔ Smart Merge:[/green] Synced {len(merged_files)} files from feature branch."
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# Auto-commit merged files if not no_commit
|
|
549
|
+
if not no_commit:
|
|
550
|
+
commit_msg = f"feat: atomic merge changes from {issue_id}"
|
|
551
|
+
try:
|
|
552
|
+
commit_hash = git.git_commit(project_root, commit_msg)
|
|
553
|
+
transaction_commits.append(commit_hash)
|
|
554
|
+
if not OutputManager.is_agent_mode():
|
|
555
|
+
console.print(f"[green]✔ Committed merged changes.[/green]")
|
|
556
|
+
except Exception as e:
|
|
557
|
+
# If commit fails (e.g. nothing to commit?), just warn
|
|
558
|
+
if not OutputManager.is_agent_mode():
|
|
559
|
+
console.print(f"[yellow]⚠ Commit skipped: {e}[/yellow]")
|
|
560
|
+
|
|
561
|
+
except Exception as e:
|
|
562
|
+
OutputManager.error(f"Merge Error: {e}")
|
|
563
|
+
rollback_transaction()
|
|
564
|
+
raise typer.Exit(code=1)
|
|
464
565
|
|
|
566
|
+
# 2. Update issue status to closed
|
|
567
|
+
try:
|
|
568
|
+
issue = core.update_issue(
|
|
569
|
+
issues_root,
|
|
570
|
+
issue_id,
|
|
571
|
+
status="closed",
|
|
572
|
+
solution=solution,
|
|
573
|
+
no_commit=no_commit,
|
|
574
|
+
project_root=project_root,
|
|
575
|
+
)
|
|
576
|
+
# Track the auto-commit from update_issue if it occurred
|
|
577
|
+
if hasattr(issue, 'commit_result') and issue.commit_result:
|
|
578
|
+
transaction_commits.append(issue.commit_result)
|
|
579
|
+
except Exception as e:
|
|
580
|
+
OutputManager.error(f"Update Error: {e}")
|
|
581
|
+
rollback_transaction()
|
|
582
|
+
raise typer.Exit(code=1)
|
|
583
|
+
|
|
584
|
+
# 3. Prune issue resources (branch/worktree)
|
|
465
585
|
pruned_resources = []
|
|
466
586
|
if prune:
|
|
587
|
+
# Get isolation info for confirmation prompt
|
|
588
|
+
isolation_info = None
|
|
589
|
+
if issue.isolation:
|
|
590
|
+
isolation_type = issue.isolation.type if issue.isolation.type else None
|
|
591
|
+
isolation_ref = issue.isolation.ref
|
|
592
|
+
isolation_info = (isolation_type, isolation_ref)
|
|
593
|
+
|
|
594
|
+
# Auto-prune without confirmation (FEAT-0082 Update)
|
|
595
|
+
if not OutputManager.is_agent_mode() and isolation_info:
|
|
596
|
+
iso_type, iso_ref = isolation_info
|
|
597
|
+
if iso_ref:
|
|
598
|
+
console.print(f"[dim]Cleaning up {iso_type}: {iso_ref}...[/dim]")
|
|
599
|
+
|
|
467
600
|
try:
|
|
468
601
|
pruned_resources = core.prune_issue_resources(
|
|
469
602
|
issues_root, issue_id, force, project_root
|
|
470
603
|
)
|
|
604
|
+
if pruned_resources and not OutputManager.is_agent_mode():
|
|
605
|
+
console.print(f"[green]✔ Cleaned up:[/green] {', '.join(pruned_resources)}")
|
|
471
606
|
except Exception as e:
|
|
472
607
|
OutputManager.error(f"Prune Error: {e}")
|
|
608
|
+
rollback_transaction()
|
|
473
609
|
raise typer.Exit(code=1)
|
|
474
610
|
|
|
611
|
+
# Success: Clear transaction state as all operations completed
|
|
475
612
|
OutputManager.print(
|
|
476
613
|
{"issue": issue, "status": "closed", "pruned": pruned_resources}
|
|
477
614
|
)
|
|
478
615
|
|
|
616
|
+
except typer.Abort:
|
|
617
|
+
# User cancelled, rollback already handled
|
|
618
|
+
raise
|
|
479
619
|
except Exception as e:
|
|
620
|
+
# Catch-all for unexpected errors
|
|
480
621
|
OutputManager.error(str(e))
|
|
622
|
+
rollback_transaction()
|
|
481
623
|
raise typer.Exit(code=1)
|
|
482
624
|
|
|
483
625
|
|
|
@@ -727,6 +869,9 @@ def list_cmd(
|
|
|
727
869
|
workspace: bool = typer.Option(
|
|
728
870
|
False, "--workspace", "-w", help="Include issues from workspace members"
|
|
729
871
|
),
|
|
872
|
+
all: bool = typer.Option(
|
|
873
|
+
False, "--all", "-a", help="Include archived issues in the list"
|
|
874
|
+
),
|
|
730
875
|
json: AgentOutput = False,
|
|
731
876
|
):
|
|
732
877
|
"""List issues in a table format with filtering."""
|
|
@@ -734,15 +879,15 @@ def list_cmd(
|
|
|
734
879
|
issues_root = _resolve_issues_root(config, root)
|
|
735
880
|
|
|
736
881
|
# Validation
|
|
737
|
-
if status and status.lower() not in ["open", "closed", "backlog", "all"]:
|
|
882
|
+
if status and status.lower() not in ["open", "closed", "backlog", "archived", "all"]:
|
|
738
883
|
OutputManager.error(
|
|
739
|
-
f"Invalid status: {status}. Use open, closed, backlog or all."
|
|
884
|
+
f"Invalid status: {status}. Use open, closed, backlog, archived or all."
|
|
740
885
|
)
|
|
741
886
|
raise typer.Exit(code=1)
|
|
742
887
|
|
|
743
888
|
target_status = status.lower() if status else "open"
|
|
744
889
|
|
|
745
|
-
issues = core.list_issues(issues_root, recursive_workspace=workspace)
|
|
890
|
+
issues = core.list_issues(issues_root, recursive_workspace=workspace, include_archived=all)
|
|
746
891
|
filtered = []
|
|
747
892
|
|
|
748
893
|
for i in issues:
|
|
@@ -1521,6 +1666,193 @@ def show(
|
|
|
1521
1666
|
OutputManager.print(result)
|
|
1522
1667
|
|
|
1523
1668
|
|
|
1669
|
+
@app.command("check-critical")
|
|
1670
|
+
def check_critical(
|
|
1671
|
+
fail_on_warning: bool = typer.Option(
|
|
1672
|
+
False,
|
|
1673
|
+
"--fail-on-warning",
|
|
1674
|
+
help="Exit with error code if high priority issues are found",
|
|
1675
|
+
),
|
|
1676
|
+
root: Optional[str] = typer.Option(
|
|
1677
|
+
None, "--root", help="Override issues root directory"
|
|
1678
|
+
),
|
|
1679
|
+
json: AgentOutput = False,
|
|
1680
|
+
):
|
|
1681
|
+
"""
|
|
1682
|
+
Check for incomplete critical/high priority issues.
|
|
1683
|
+
|
|
1684
|
+
Returns:
|
|
1685
|
+
0: No critical/high issues found
|
|
1686
|
+
1: High priority issues found (warning)
|
|
1687
|
+
2: Critical issues found (blocking)
|
|
1688
|
+
"""
|
|
1689
|
+
from .criticality import CriticalityLevel
|
|
1690
|
+
|
|
1691
|
+
config = get_config()
|
|
1692
|
+
issues_root = _resolve_issues_root(config, root)
|
|
1693
|
+
|
|
1694
|
+
issues = core.list_issues(issues_root)
|
|
1695
|
+
|
|
1696
|
+
critical_issues = []
|
|
1697
|
+
high_issues = []
|
|
1698
|
+
|
|
1699
|
+
for issue in issues:
|
|
1700
|
+
if issue.status == "closed":
|
|
1701
|
+
continue
|
|
1702
|
+
|
|
1703
|
+
criticality = issue.criticality
|
|
1704
|
+
if not criticality:
|
|
1705
|
+
continue
|
|
1706
|
+
|
|
1707
|
+
if criticality == CriticalityLevel.CRITICAL:
|
|
1708
|
+
critical_issues.append(issue)
|
|
1709
|
+
elif criticality == CriticalityLevel.HIGH:
|
|
1710
|
+
high_issues.append(issue)
|
|
1711
|
+
|
|
1712
|
+
result = {
|
|
1713
|
+
"critical_count": len(critical_issues),
|
|
1714
|
+
"high_count": len(high_issues),
|
|
1715
|
+
"critical_issues": [
|
|
1716
|
+
{"id": i.id, "title": i.title, "stage": i.stage}
|
|
1717
|
+
for i in critical_issues
|
|
1718
|
+
],
|
|
1719
|
+
"high_issues": [
|
|
1720
|
+
{"id": i.id, "title": i.title, "stage": i.stage}
|
|
1721
|
+
for i in high_issues
|
|
1722
|
+
],
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
if OutputManager.is_agent_mode() or json:
|
|
1726
|
+
OutputManager.print(result)
|
|
1727
|
+
else:
|
|
1728
|
+
if critical_issues:
|
|
1729
|
+
console.print("[red]❌ Critical issues (blocking):[/red]")
|
|
1730
|
+
for issue in critical_issues:
|
|
1731
|
+
console.print(f" [red]• {issue.id}:[/red] {issue.title} ({issue.stage})")
|
|
1732
|
+
|
|
1733
|
+
if high_issues:
|
|
1734
|
+
console.print("[yellow]⚠️ High priority issues (warning):[/yellow]")
|
|
1735
|
+
for issue in high_issues:
|
|
1736
|
+
console.print(f" [yellow]• {issue.id}:[/yellow] {issue.title} ({issue.stage})")
|
|
1737
|
+
|
|
1738
|
+
if not critical_issues and not high_issues:
|
|
1739
|
+
console.print("[green]✓ No incomplete critical or high priority issues.[/green]")
|
|
1740
|
+
|
|
1741
|
+
# Determine exit code
|
|
1742
|
+
if critical_issues:
|
|
1743
|
+
raise typer.Exit(code=2)
|
|
1744
|
+
elif high_issues and fail_on_warning:
|
|
1745
|
+
raise typer.Exit(code=1)
|
|
1746
|
+
elif high_issues:
|
|
1747
|
+
# Warning only, don't fail
|
|
1748
|
+
pass
|
|
1749
|
+
|
|
1750
|
+
raise typer.Exit(code=0)
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
@app.command("sync-isolation")
|
|
1754
|
+
def sync_isolation(
|
|
1755
|
+
issue_id: str = typer.Argument(..., help="Issue ID to sync isolation for"),
|
|
1756
|
+
branch: Optional[str] = typer.Option(
|
|
1757
|
+
None, "--branch", help="Current branch name (auto-detected if not provided)"
|
|
1758
|
+
),
|
|
1759
|
+
root: Optional[str] = typer.Option(
|
|
1760
|
+
None, "--root", help="Override issues root directory"
|
|
1761
|
+
),
|
|
1762
|
+
json: AgentOutput = False,
|
|
1763
|
+
):
|
|
1764
|
+
"""
|
|
1765
|
+
Sync issue isolation configuration with current branch.
|
|
1766
|
+
|
|
1767
|
+
Updates the isolation.ref field to match the current branch context.
|
|
1768
|
+
This is typically called by the post-checkout hook.
|
|
1769
|
+
"""
|
|
1770
|
+
import subprocess
|
|
1771
|
+
from .core import update_issue_field
|
|
1772
|
+
|
|
1773
|
+
config = get_config()
|
|
1774
|
+
issues_root = _resolve_issues_root(config, root)
|
|
1775
|
+
project_root = Path(config.paths.root).resolve()
|
|
1776
|
+
|
|
1777
|
+
# Auto-detect branch if not provided
|
|
1778
|
+
if not branch:
|
|
1779
|
+
try:
|
|
1780
|
+
result = subprocess.run(
|
|
1781
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
1782
|
+
cwd=project_root,
|
|
1783
|
+
capture_output=True,
|
|
1784
|
+
text=True,
|
|
1785
|
+
check=True,
|
|
1786
|
+
)
|
|
1787
|
+
branch = result.stdout.strip()
|
|
1788
|
+
except Exception:
|
|
1789
|
+
OutputManager.error("Could not detect current branch. Use --branch.")
|
|
1790
|
+
raise typer.Exit(code=1)
|
|
1791
|
+
|
|
1792
|
+
# Find the issue
|
|
1793
|
+
issue_path = core.find_issue_path(issues_root, issue_id)
|
|
1794
|
+
if not issue_path:
|
|
1795
|
+
OutputManager.error(f"Issue {issue_id} not found.")
|
|
1796
|
+
raise typer.Exit(code=1)
|
|
1797
|
+
|
|
1798
|
+
# Parse current issue
|
|
1799
|
+
issue = core.parse_issue(issue_path)
|
|
1800
|
+
if not issue:
|
|
1801
|
+
OutputManager.error(f"Could not parse issue {issue_id}.")
|
|
1802
|
+
raise typer.Exit(code=1)
|
|
1803
|
+
|
|
1804
|
+
# Check if isolation needs updating
|
|
1805
|
+
current_isolation = issue.isolation
|
|
1806
|
+
if current_isolation:
|
|
1807
|
+
current_ref = current_isolation.ref or ""
|
|
1808
|
+
current_isolation_dict = {
|
|
1809
|
+
"type": current_isolation.type,
|
|
1810
|
+
"ref": current_isolation.ref,
|
|
1811
|
+
}
|
|
1812
|
+
if current_isolation.path:
|
|
1813
|
+
current_isolation_dict["path"] = current_isolation.path
|
|
1814
|
+
if current_isolation.created_at:
|
|
1815
|
+
current_isolation_dict["created_at"] = current_isolation.created_at.isoformat()
|
|
1816
|
+
else:
|
|
1817
|
+
current_ref = ""
|
|
1818
|
+
current_isolation_dict = {}
|
|
1819
|
+
|
|
1820
|
+
# Expected ref format: branch:branch-name
|
|
1821
|
+
expected_ref = f"branch:{branch}"
|
|
1822
|
+
|
|
1823
|
+
if current_ref == expected_ref:
|
|
1824
|
+
if OutputManager.is_agent_mode() or json:
|
|
1825
|
+
OutputManager.print({"updated": False, "message": "Isolation already up to date"})
|
|
1826
|
+
else:
|
|
1827
|
+
console.print(f"[dim]Isolation already up to date for {issue_id}[/dim]")
|
|
1828
|
+
raise typer.Exit(code=0)
|
|
1829
|
+
|
|
1830
|
+
# Update the isolation field
|
|
1831
|
+
new_isolation = {**current_isolation_dict, "ref": expected_ref, "type": "branch"}
|
|
1832
|
+
|
|
1833
|
+
try:
|
|
1834
|
+
update_issue_field(
|
|
1835
|
+
issue_path,
|
|
1836
|
+
"isolation",
|
|
1837
|
+
new_isolation,
|
|
1838
|
+
)
|
|
1839
|
+
|
|
1840
|
+
result = {
|
|
1841
|
+
"updated": True,
|
|
1842
|
+
"issue_id": issue_id,
|
|
1843
|
+
"isolation": new_isolation,
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
if OutputManager.is_agent_mode() or json:
|
|
1847
|
+
OutputManager.print(result)
|
|
1848
|
+
else:
|
|
1849
|
+
console.print(f"[green]✓ Updated isolation for {issue_id}:[/green] {expected_ref}")
|
|
1850
|
+
|
|
1851
|
+
except Exception as e:
|
|
1852
|
+
OutputManager.error(f"Failed to update isolation: {e}")
|
|
1853
|
+
raise typer.Exit(code=1)
|
|
1854
|
+
|
|
1855
|
+
|
|
1524
1856
|
def _validate_branch_context(
|
|
1525
1857
|
project_root: Path,
|
|
1526
1858
|
allowed: Optional[List[str]] = None,
|