monoco-toolkit 0.1.5__py3-none-any.whl → 0.1.7__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/config.py +60 -8
- monoco/core/feature.py +58 -0
- monoco/core/injection.py +196 -0
- monoco/core/integrations.py +181 -0
- monoco/core/registry.py +36 -0
- monoco/core/resources/en/AGENTS.md +8 -0
- monoco/core/resources/en/SKILL.md +66 -0
- monoco/core/resources/zh/AGENTS.md +8 -0
- monoco/core/resources/zh/SKILL.md +66 -0
- monoco/core/setup.py +40 -24
- monoco/core/skills.py +444 -0
- monoco/core/sync.py +224 -0
- monoco/core/workspace.py +2 -6
- monoco/daemon/services.py +1 -1
- monoco/features/config/commands.py +104 -44
- monoco/features/i18n/adapter.py +29 -0
- monoco/features/i18n/core.py +1 -11
- monoco/features/i18n/resources/en/AGENTS.md +8 -0
- monoco/features/i18n/resources/en/SKILL.md +94 -0
- monoco/features/i18n/resources/zh/AGENTS.md +8 -0
- monoco/features/i18n/resources/zh/SKILL.md +94 -0
- monoco/features/issue/adapter.py +34 -0
- monoco/features/issue/commands.py +8 -8
- monoco/features/issue/core.py +5 -16
- monoco/features/issue/migration.py +134 -0
- monoco/features/issue/models.py +5 -3
- monoco/features/issue/resources/en/AGENTS.md +9 -0
- monoco/features/issue/resources/en/SKILL.md +51 -0
- monoco/features/issue/resources/zh/AGENTS.md +9 -0
- monoco/features/issue/resources/zh/SKILL.md +85 -0
- monoco/features/spike/adapter.py +30 -0
- monoco/features/spike/core.py +3 -20
- monoco/features/spike/resources/en/AGENTS.md +7 -0
- monoco/features/spike/resources/en/SKILL.md +74 -0
- monoco/features/spike/resources/zh/AGENTS.md +7 -0
- monoco/features/spike/resources/zh/SKILL.md +74 -0
- monoco/main.py +4 -0
- {monoco_toolkit-0.1.5.dist-info → monoco_toolkit-0.1.7.dist-info}/METADATA +1 -1
- monoco_toolkit-0.1.7.dist-info/RECORD +62 -0
- monoco_toolkit-0.1.5.dist-info/RECORD +0 -36
- {monoco_toolkit-0.1.5.dist-info → monoco_toolkit-0.1.7.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.1.5.dist-info → monoco_toolkit-0.1.7.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.1.5.dist-info → monoco_toolkit-0.1.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monoco-i18n
|
|
3
|
+
description: 文档国际化质量控制。确保多语言文档保持同步。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 文档国际化
|
|
7
|
+
|
|
8
|
+
管理 Monoco 项目文档的国际化。
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
I18n 功能提供:
|
|
13
|
+
|
|
14
|
+
- **自动扫描**缺失的翻译
|
|
15
|
+
- **标准化结构**用于多语言文档
|
|
16
|
+
- **质量控制**以维护文档一致性
|
|
17
|
+
|
|
18
|
+
## 核心命令
|
|
19
|
+
|
|
20
|
+
### 扫描缺失的翻译
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
monoco i18n scan
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
扫描项目中的 markdown 文件并报告缺失的翻译。
|
|
27
|
+
|
|
28
|
+
**输出**:
|
|
29
|
+
|
|
30
|
+
- 列出没有对应翻译的源文件
|
|
31
|
+
- 显示缺少哪些目标语言
|
|
32
|
+
- 遵循 `.gitignore` 和默认排除规则
|
|
33
|
+
|
|
34
|
+
## 配置
|
|
35
|
+
|
|
36
|
+
I18n 设置在 `.monoco/config.yaml` 中配置:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
i18n:
|
|
40
|
+
source_lang: en # 源语言代码
|
|
41
|
+
target_langs: # 目标语言代码
|
|
42
|
+
- zh
|
|
43
|
+
- ja
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 文档结构
|
|
47
|
+
|
|
48
|
+
### 根文件(后缀模式)
|
|
49
|
+
|
|
50
|
+
对于项目根目录中的文件:
|
|
51
|
+
|
|
52
|
+
- 源文件: `README.md`
|
|
53
|
+
- 中文: `README_ZH.md`
|
|
54
|
+
- 日文: `README_JA.md`
|
|
55
|
+
|
|
56
|
+
### 子目录文件(目录模式)
|
|
57
|
+
|
|
58
|
+
对于 `docs/` 或其他目录中的文件:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
docs/
|
|
62
|
+
├── en/
|
|
63
|
+
│ ├── guide.md
|
|
64
|
+
│ └── api.md
|
|
65
|
+
├── zh/
|
|
66
|
+
│ ├── guide.md
|
|
67
|
+
│ └── api.md
|
|
68
|
+
└── ja/
|
|
69
|
+
├── guide.md
|
|
70
|
+
└── api.md
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 排除规则
|
|
74
|
+
|
|
75
|
+
以下内容会自动从 i18n 扫描中排除:
|
|
76
|
+
|
|
77
|
+
- `.gitignore` 模式(自动遵循)
|
|
78
|
+
- `.references/` 目录
|
|
79
|
+
- 构建产物(`dist/`, `build/`, `node_modules/`)
|
|
80
|
+
- `Issues/` 目录
|
|
81
|
+
|
|
82
|
+
## 最佳实践
|
|
83
|
+
|
|
84
|
+
1. **先创建英文版**: 首先用源语言编写文档
|
|
85
|
+
2. **遵循命名约定**: 使用适当的模式(后缀或目录)
|
|
86
|
+
3. **定期运行扫描**: 使用 `monoco i18n scan` 验证覆盖率
|
|
87
|
+
4. **提交所有语言**: 将翻译保存在版本控制中
|
|
88
|
+
|
|
89
|
+
## 工作流程
|
|
90
|
+
|
|
91
|
+
1. 用源语言(如英语)编写文档
|
|
92
|
+
2. 按照命名约定创建翻译文件
|
|
93
|
+
3. 运行 `monoco i18n scan` 验证所有翻译是否存在
|
|
94
|
+
4. 修复扫描报告的任何缺失翻译
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from monoco.core.feature import MonocoFeature, IntegrationData
|
|
4
|
+
from monoco.features.issue import core
|
|
5
|
+
|
|
6
|
+
class IssueFeature(MonocoFeature):
|
|
7
|
+
@property
|
|
8
|
+
def name(self) -> str:
|
|
9
|
+
return "issue"
|
|
10
|
+
|
|
11
|
+
def initialize(self, root: Path, config: Dict) -> None:
|
|
12
|
+
issues_path = root / config.get("paths", {}).get("issues", "Issues")
|
|
13
|
+
core.init(issues_path)
|
|
14
|
+
|
|
15
|
+
def integrate(self, root: Path, config: Dict) -> IntegrationData:
|
|
16
|
+
# Determine language from config, default to 'en'
|
|
17
|
+
lang = config.get("i18n", {}).get("source_lang", "en")
|
|
18
|
+
|
|
19
|
+
# Current file is in monoco/features/issue/adapter.py
|
|
20
|
+
# Resource path: monoco/features/issue/resources/{lang}/AGENTS.md
|
|
21
|
+
base_dir = Path(__file__).parent / "resources"
|
|
22
|
+
|
|
23
|
+
# Try specific language, fallback to 'en'
|
|
24
|
+
prompt_file = base_dir / lang / "AGENTS.md"
|
|
25
|
+
if not prompt_file.exists():
|
|
26
|
+
prompt_file = base_dir / "en" / "AGENTS.md"
|
|
27
|
+
|
|
28
|
+
content = ""
|
|
29
|
+
if prompt_file.exists():
|
|
30
|
+
content = prompt_file.read_text(encoding="utf-8").strip()
|
|
31
|
+
|
|
32
|
+
return IntegrationData(
|
|
33
|
+
system_prompts={"Issue Management": content}
|
|
34
|
+
)
|
|
@@ -23,7 +23,7 @@ def create(
|
|
|
23
23
|
title: str = typer.Option(..., "--title", "-t", help="Issue title"),
|
|
24
24
|
parent: Optional[str] = typer.Option(None, "--parent", "-p", help="Parent Issue ID"),
|
|
25
25
|
is_backlog: bool = typer.Option(False, "--backlog", help="Create as backlog item"),
|
|
26
|
-
stage: Optional[IssueStage] = typer.Option(None, "--stage", help="Issue stage (
|
|
26
|
+
stage: Optional[IssueStage] = typer.Option(None, "--stage", help="Issue stage (draft, doing, review)"),
|
|
27
27
|
dependencies: List[str] = typer.Option([], "--dependency", "-d", help="Issue dependency ID(s)"),
|
|
28
28
|
related: List[str] = typer.Option([], "--related", "-r", help="Related Issue ID(s)"),
|
|
29
29
|
subdir: Optional[str] = typer.Option(None, "--subdir", "-s", help="Subdirectory for organization (e.g. 'Backend/Auth')"),
|
|
@@ -73,13 +73,13 @@ def move_open(
|
|
|
73
73
|
issue_id: str = typer.Argument(..., help="Issue ID to open"),
|
|
74
74
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
75
75
|
):
|
|
76
|
-
"""Move issue to open status and set stage to
|
|
76
|
+
"""Move issue to open status and set stage to Draft."""
|
|
77
77
|
config = get_config()
|
|
78
78
|
issues_root = _resolve_issues_root(config, root)
|
|
79
79
|
try:
|
|
80
80
|
# Pull operation: Force stage to TODO
|
|
81
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.
|
|
82
|
-
console.print(f"[green]▶[/green] Issue [bold]{issue_id}[/bold] moved to open/
|
|
81
|
+
core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.DRAFT)
|
|
82
|
+
console.print(f"[green]▶[/green] Issue [bold]{issue_id}[/bold] moved to open/draft.")
|
|
83
83
|
except Exception as e:
|
|
84
84
|
console.print(f"[red]✘ Error:[/red] {str(e)}")
|
|
85
85
|
raise typer.Exit(code=1)
|
|
@@ -210,11 +210,11 @@ def pull(
|
|
|
210
210
|
issue_id: str = typer.Argument(..., help="Issue ID to pull from backlog"),
|
|
211
211
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
212
212
|
):
|
|
213
|
-
"""Pull issue from backlog (Open &
|
|
213
|
+
"""Pull issue from backlog (Open & Draft)."""
|
|
214
214
|
config = get_config()
|
|
215
215
|
issues_root = _resolve_issues_root(config, root)
|
|
216
216
|
try:
|
|
217
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.
|
|
217
|
+
core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.DRAFT)
|
|
218
218
|
console.print(f"[green]🔥[/green] Issue [bold]{issue_id}[/bold] pulled from backlog.")
|
|
219
219
|
except Exception as e:
|
|
220
220
|
console.print(f"[red]✘ Error:[/red] {str(e)}")
|
|
@@ -319,7 +319,7 @@ def board(
|
|
|
319
319
|
columns: List[RenderableType] = []
|
|
320
320
|
|
|
321
321
|
stage_titles = {
|
|
322
|
-
"
|
|
322
|
+
"draft": "[bold white]DRAFT[/bold white]",
|
|
323
323
|
"doing": "[bold yellow]DOING[/bold yellow]",
|
|
324
324
|
"review": "[bold cyan]REVIEW[/bold cyan]",
|
|
325
325
|
"done": "[bold green]DONE[/bold green]"
|
|
@@ -547,7 +547,7 @@ def _resolve_issues_root(config, cli_root: Optional[str]) -> Path:
|
|
|
547
547
|
# We need to detect if we are in a Workspace Root with multiple projects
|
|
548
548
|
cwd = Path.cwd()
|
|
549
549
|
|
|
550
|
-
# If CWD is NOT a project root (no monoco
|
|
550
|
+
# If CWD is NOT a project root (no .monoco/), scan for subprojects
|
|
551
551
|
if not is_project_root(cwd):
|
|
552
552
|
subprojects = find_projects(cwd)
|
|
553
553
|
if len(subprojects) > 1:
|
monoco/features/issue/core.py
CHANGED
|
@@ -583,15 +583,6 @@ description: Monoco Issue System 的官方技能定义。将 Issue 视为通用
|
|
|
583
583
|
5. **Modification**: `monoco issue start/submit/delete <id>`
|
|
584
584
|
"""
|
|
585
585
|
|
|
586
|
-
PROMPT_CONTENT = """
|
|
587
|
-
### Issue Management
|
|
588
|
-
System for managing tasks using `monoco issue`.
|
|
589
|
-
- **Create**: `monoco issue create <type> -t "Title"` (types: epic, feature, chore, fix)
|
|
590
|
-
- **Status**: `monoco issue open|close|backlog <id>`
|
|
591
|
-
- **Check**: `monoco issue lint` (Must run after manual edits)
|
|
592
|
-
- **Lifecycle**: `monoco issue start|submit|delete <id>`
|
|
593
|
-
- **Structure**: `Issues/{CapitalizedPluralType}/{lowercase_status}/` (e.g. `Issues/Features/open/`). Do not deviate.
|
|
594
|
-
"""
|
|
595
586
|
|
|
596
587
|
def init(issues_root: Path):
|
|
597
588
|
"""Initialize the Issues directory structure."""
|
|
@@ -612,9 +603,7 @@ def get_resources() -> Dict[str, Any]:
|
|
|
612
603
|
"skills": {
|
|
613
604
|
"issues-management": SKILL_CONTENT
|
|
614
605
|
},
|
|
615
|
-
"prompts": {
|
|
616
|
-
"issues-management": PROMPT_CONTENT
|
|
617
|
-
}
|
|
606
|
+
"prompts": {} # Handled by adapter via resource files
|
|
618
607
|
}
|
|
619
608
|
|
|
620
609
|
|
|
@@ -662,7 +651,7 @@ def get_board_data(issues_root: Path) -> Dict[str, List[IssueMetadata]]:
|
|
|
662
651
|
Get open issues grouped by their stage for Kanban view.
|
|
663
652
|
"""
|
|
664
653
|
board = {
|
|
665
|
-
IssueStage.
|
|
654
|
+
IssueStage.DRAFT.value: [],
|
|
666
655
|
IssueStage.DOING.value: [],
|
|
667
656
|
IssueStage.REVIEW.value: [],
|
|
668
657
|
IssueStage.DONE.value: []
|
|
@@ -1042,11 +1031,11 @@ def recalculate_parent(issues_root: Path, parent_id: str):
|
|
|
1042
1031
|
# FEAT-0003 Req: "If first child starts doing, auto-start Parent?"
|
|
1043
1032
|
# If parent is OPEN/TODO and child is DOING/REVIEW/DONE, set parent to DOING?
|
|
1044
1033
|
current_status = data.get("status", "open").lower()
|
|
1045
|
-
current_stage = data.get("stage", "
|
|
1034
|
+
current_stage = data.get("stage", "draft").lower()
|
|
1046
1035
|
|
|
1047
|
-
if current_status == "open" and current_stage == "
|
|
1036
|
+
if current_status == "open" and current_stage == "draft":
|
|
1048
1037
|
# Check if any child is active
|
|
1049
|
-
active_children = [c for c in children if c.status == IssueStatus.OPEN and c.stage != IssueStage.
|
|
1038
|
+
active_children = [c for c in children if c.status == IssueStatus.OPEN and c.stage != IssueStage.DRAFT]
|
|
1050
1039
|
closed_children = [c for c in children if c.status == IssueStatus.CLOSED]
|
|
1051
1040
|
|
|
1052
1041
|
if active_children or closed_children:
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import yaml
|
|
4
|
+
import hashlib
|
|
5
|
+
import secrets
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Dict, Any
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from .models import generate_uid
|
|
10
|
+
|
|
11
|
+
# Migration Mappings
|
|
12
|
+
DIR_MAP = {
|
|
13
|
+
"STORIES": "Features",
|
|
14
|
+
"Stories": "Features",
|
|
15
|
+
"TASKS": "Chores",
|
|
16
|
+
"Tasks": "Chores",
|
|
17
|
+
"BUGS": "Fixes",
|
|
18
|
+
"Bugs": "Fixes",
|
|
19
|
+
"EPICS": "Epics",
|
|
20
|
+
"Epics": "Epics",
|
|
21
|
+
"features": "Features",
|
|
22
|
+
"chores": "Chores",
|
|
23
|
+
"fixes": "Fixes",
|
|
24
|
+
"epics": "Epics"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
TYPE_MAP = {
|
|
28
|
+
"story": "feature",
|
|
29
|
+
"task": "chore",
|
|
30
|
+
"bug": "fix"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ID_PREFIX_MAP = {
|
|
34
|
+
"STORY": "FEAT",
|
|
35
|
+
"TASK": "CHORE",
|
|
36
|
+
"BUG": "FIX"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def migrate_issues_directory(issues_dir: Path):
|
|
40
|
+
"""
|
|
41
|
+
Core migration logic to upgrade an Issues directory to the latest Monoco standard.
|
|
42
|
+
"""
|
|
43
|
+
if not issues_dir.exists():
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
# 1. Rename Directories
|
|
47
|
+
for old_name, new_name in DIR_MAP.items():
|
|
48
|
+
old_path = issues_dir / old_name
|
|
49
|
+
if old_path.exists():
|
|
50
|
+
new_path = issues_dir / new_name
|
|
51
|
+
|
|
52
|
+
# Case sensitivity check for some filesystems
|
|
53
|
+
same_inode = False
|
|
54
|
+
try:
|
|
55
|
+
if new_path.exists() and os.path.samefile(old_path, new_path):
|
|
56
|
+
same_inode = True
|
|
57
|
+
except OSError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
if same_inode:
|
|
61
|
+
if old_path.name != new_path.name:
|
|
62
|
+
old_path.rename(new_path)
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if new_path.exists():
|
|
66
|
+
import shutil
|
|
67
|
+
for item in old_path.iterdir():
|
|
68
|
+
dest = new_path / item.name
|
|
69
|
+
if dest.exists() and item.is_dir():
|
|
70
|
+
for subitem in item.iterdir():
|
|
71
|
+
shutil.move(str(subitem), str(dest / subitem.name))
|
|
72
|
+
shutil.rmtree(item)
|
|
73
|
+
else:
|
|
74
|
+
shutil.move(str(item), str(dest))
|
|
75
|
+
shutil.rmtree(old_path)
|
|
76
|
+
else:
|
|
77
|
+
old_path.rename(new_path)
|
|
78
|
+
|
|
79
|
+
# 2. Rename Files and Update Content
|
|
80
|
+
for subdir_name in ["Features", "Chores", "Fixes", "Epics"]:
|
|
81
|
+
subdir = issues_dir / subdir_name
|
|
82
|
+
if not subdir.exists():
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
for file_path in subdir.rglob("*.md"):
|
|
86
|
+
content = file_path.read_text(encoding="utf-8")
|
|
87
|
+
new_content = content
|
|
88
|
+
|
|
89
|
+
# Replace Type in Frontmatter
|
|
90
|
+
for old_type, new_type in TYPE_MAP.items():
|
|
91
|
+
new_content = re.sub(rf"^type:\s*{old_type}", f"type: {new_type}", new_content, flags=re.IGNORECASE | re.MULTILINE)
|
|
92
|
+
|
|
93
|
+
# Replace ID Prefixes
|
|
94
|
+
for old_prefix, new_prefix in ID_PREFIX_MAP.items():
|
|
95
|
+
new_content = new_content.replace(f"[[{old_prefix}-", f"[[{new_prefix}-")
|
|
96
|
+
new_content = re.sub(rf"^id: {old_prefix}-", f"id: {new_prefix}-", new_content, flags=re.MULTILINE)
|
|
97
|
+
new_content = re.sub(rf"^parent: {old_prefix}-", f"parent: {new_prefix}-", new_content, flags=re.MULTILINE)
|
|
98
|
+
new_content = new_content.replace(f"{old_prefix}-", f"{new_prefix}-")
|
|
99
|
+
|
|
100
|
+
# Structural Updates (UID, Stage)
|
|
101
|
+
match = re.search(r"^---(.*?)---", new_content, re.DOTALL | re.MULTILINE)
|
|
102
|
+
if match:
|
|
103
|
+
yaml_str = match.group(1)
|
|
104
|
+
try:
|
|
105
|
+
data = yaml.safe_load(yaml_str) or {}
|
|
106
|
+
changed = False
|
|
107
|
+
|
|
108
|
+
if 'uid' not in data:
|
|
109
|
+
data['uid'] = generate_uid()
|
|
110
|
+
changed = True
|
|
111
|
+
|
|
112
|
+
if 'stage' in data and data['stage'] == 'todo':
|
|
113
|
+
data['stage'] = 'draft'
|
|
114
|
+
changed = True
|
|
115
|
+
|
|
116
|
+
if changed:
|
|
117
|
+
new_yaml = yaml.dump(data, sort_keys=False, allow_unicode=True)
|
|
118
|
+
new_content = new_content.replace(match.group(1), "\n" + new_yaml)
|
|
119
|
+
except yaml.YAMLError:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
if new_content != content:
|
|
123
|
+
file_path.write_text(new_content, encoding="utf-8")
|
|
124
|
+
|
|
125
|
+
# Rename File
|
|
126
|
+
filename = file_path.name
|
|
127
|
+
new_filename = filename
|
|
128
|
+
for old_prefix, new_prefix in ID_PREFIX_MAP.items():
|
|
129
|
+
if filename.startswith(f"{old_prefix}-"):
|
|
130
|
+
new_filename = filename.replace(f"{old_prefix}-", f"{new_prefix}-", 1)
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
if new_filename != filename:
|
|
134
|
+
file_path.rename(file_path.parent / new_filename)
|
monoco/features/issue/models.py
CHANGED
|
@@ -61,7 +61,7 @@ class IssueStatus(str, Enum):
|
|
|
61
61
|
BACKLOG = "backlog"
|
|
62
62
|
|
|
63
63
|
class IssueStage(str, Enum):
|
|
64
|
-
|
|
64
|
+
DRAFT = "draft"
|
|
65
65
|
DOING = "doing"
|
|
66
66
|
REVIEW = "review"
|
|
67
67
|
DONE = "done"
|
|
@@ -125,6 +125,8 @@ class IssueMetadata(BaseModel):
|
|
|
125
125
|
# Stage normalization
|
|
126
126
|
if "stage" in v and isinstance(v["stage"], str):
|
|
127
127
|
v["stage"] = v["stage"].lower()
|
|
128
|
+
if v["stage"] == "todo":
|
|
129
|
+
v["stage"] = "draft"
|
|
128
130
|
return v
|
|
129
131
|
|
|
130
132
|
@model_validator(mode='after')
|
|
@@ -132,7 +134,7 @@ class IssueMetadata(BaseModel):
|
|
|
132
134
|
# Logic Definition:
|
|
133
135
|
# status: backlog -> stage: null
|
|
134
136
|
# status: closed -> stage: done
|
|
135
|
-
# status: open -> stage:
|
|
137
|
+
# status: open -> stage: draft | doing | review (default draft)
|
|
136
138
|
|
|
137
139
|
if self.status == IssueStatus.BACKLOG:
|
|
138
140
|
self.stage = IssueStage.FREEZED
|
|
@@ -148,7 +150,7 @@ class IssueMetadata(BaseModel):
|
|
|
148
150
|
elif self.status == IssueStatus.OPEN:
|
|
149
151
|
# Ensure valid stage for open status
|
|
150
152
|
if self.stage is None or self.stage == IssueStage.DONE:
|
|
151
|
-
self.stage = IssueStage.
|
|
153
|
+
self.stage = IssueStage.DRAFT
|
|
152
154
|
|
|
153
155
|
return self
|
|
154
156
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Issue Management
|
|
2
|
+
|
|
3
|
+
System for managing tasks using `monoco issue`.
|
|
4
|
+
|
|
5
|
+
- **Create**: `monoco issue create <type> -t "Title"` (types: epic, feature, chore, fix)
|
|
6
|
+
- **Status**: `monoco issue open|close|backlog <id>`
|
|
7
|
+
- **Check**: `monoco issue lint` (Must run after manual edits)
|
|
8
|
+
- **Lifecycle**: `monoco issue start|submit|delete <id>`
|
|
9
|
+
- **Structure**: `Issues/{CapitalizedPluralType}/{lowercase_status}/` (e.g. `Issues/Features/open/`). Do not deviate.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monoco-issue
|
|
3
|
+
description: Official skill for Monoco Issue System. Treats Issues as Universal Atoms, managing the lifecycle of Epic/Feature/Chore/Fix.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Issue Management
|
|
7
|
+
|
|
8
|
+
Use this skill to create and manage **Issues** (Universal Atoms) in Monoco projects.
|
|
9
|
+
|
|
10
|
+
## Core Ontology
|
|
11
|
+
|
|
12
|
+
### 1. Strategy Layer
|
|
13
|
+
|
|
14
|
+
- **🏆 EPIC**: Grand goals, vision containers. Mindset: Architect.
|
|
15
|
+
|
|
16
|
+
### 2. Value Layer
|
|
17
|
+
|
|
18
|
+
- **✨ FEATURE**: Value increments from user perspective. Mindset: Product Owner.
|
|
19
|
+
- **Atomicity Principle**: Feature = Design + Dev + Test + Doc + i18n. They are one.
|
|
20
|
+
|
|
21
|
+
### 3. Execution Layer
|
|
22
|
+
|
|
23
|
+
- **🧹 CHORE**: Engineering maintenance, no direct user value. Mindset: Builder.
|
|
24
|
+
- **🐞 FIX**: Correcting deviations. Mindset: Debugger.
|
|
25
|
+
|
|
26
|
+
## Guidelines
|
|
27
|
+
|
|
28
|
+
### Directory Structure
|
|
29
|
+
|
|
30
|
+
`Issues/{CapitalizedPluralType}/{lowercase_status}/`
|
|
31
|
+
|
|
32
|
+
- `{TYPE}`: `Epics`, `Features`, `Chores`, `Fixes`
|
|
33
|
+
- `{STATUS}`: `open`, `backlog`, `closed`
|
|
34
|
+
|
|
35
|
+
### Path Transitions
|
|
36
|
+
|
|
37
|
+
Use `monoco issue`:
|
|
38
|
+
|
|
39
|
+
1. **Create**: `monoco issue create <type> --title "..."`
|
|
40
|
+
|
|
41
|
+
- Params: `--parent <id>`, `--dependency <id>`, `--related <id>`, `--sprint <id>`, `--tags <tag>`
|
|
42
|
+
|
|
43
|
+
2. **Transition**: `monoco issue open/close/backlog <id>`
|
|
44
|
+
|
|
45
|
+
3. **View**: `monoco issue scope`
|
|
46
|
+
|
|
47
|
+
4. **Validation**: `monoco issue lint`
|
|
48
|
+
|
|
49
|
+
5. **Modification**: `monoco issue start/submit/delete <id>`
|
|
50
|
+
|
|
51
|
+
6. **Commit**: `monoco issue commit` (Atomic commit for issue files)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Issue 管理
|
|
2
|
+
|
|
3
|
+
使用 `monoco issue` 管理任务的系统。
|
|
4
|
+
|
|
5
|
+
- **创建**: `monoco issue create <type> -t "标题"` (类型: epic, feature, chore, fix)
|
|
6
|
+
- **状态**: `monoco issue open|close|backlog <id>`
|
|
7
|
+
- **检查**: `monoco issue lint` (手动编辑后必须运行)
|
|
8
|
+
- **生命周期**: `monoco issue start|submit|delete <id>`
|
|
9
|
+
- **结构**: `Issues/{CapitalizedPluralType}/{lowercase_status}/` (例如 `Issues/Features/open/`)。不要偏离此结构。
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monoco-issue
|
|
3
|
+
description: Monoco Issue System 的官方技能定义。将 Issue 视为通用原子 (Universal Atom),管理 Epic/Feature/Chore/Fix 的生命周期。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 自我管理 (Monoco Issue System)
|
|
7
|
+
|
|
8
|
+
使用此技能在 Monoco 项目中创建和管理 **Issue** (通用原子)。该系统参考 Jira 表达体系,同时保持 "建设者 (Builder)" 和 "调试者 (Debugger)" 思维模式的隔离。
|
|
9
|
+
|
|
10
|
+
## 核心本体论 (Core Ontology)
|
|
11
|
+
|
|
12
|
+
Monoco 不仅仅复刻 Jira,而是基于 **"思维模式 (Mindset)"** 重新定义工作单元。
|
|
13
|
+
|
|
14
|
+
### 1. 战略层 (Strategy)
|
|
15
|
+
|
|
16
|
+
#### 🏆 EPIC (史诗)
|
|
17
|
+
|
|
18
|
+
- **Mindset**: _Architect_ (架构师)
|
|
19
|
+
- **定义**: 跨越多个周期的宏大目标。它不是单纯的"大任务",而是"愿景的容器"。
|
|
20
|
+
- **产出**: 定义了系统的边界和核心价值。
|
|
21
|
+
|
|
22
|
+
### 2. 价值层 (Value)
|
|
23
|
+
|
|
24
|
+
#### ✨ FEATURE (特性)
|
|
25
|
+
|
|
26
|
+
- **Mindset**: _Product Owner_ (产品负责人)
|
|
27
|
+
- **定义**: 用户视角的价值增量。必须是可独立交付 (Shippable) 的垂直切片。
|
|
28
|
+
- **Focus**: "Why" & "What" (用户想要什么?)。
|
|
29
|
+
- **Prefix**: `FEAT-`
|
|
30
|
+
|
|
31
|
+
### 3. 执行层 (Execution)
|
|
32
|
+
|
|
33
|
+
#### 🧹 CHORE (杂务)
|
|
34
|
+
|
|
35
|
+
- **Mindset**: _Builder_ (建设者)
|
|
36
|
+
- **定义**: **不产生**直接用户价值的工程性事务。
|
|
37
|
+
- **场景**: 架构升级、写构建脚本、修复 CI/CD 流水线。
|
|
38
|
+
- **Focus**: "How" (为了支撑系统运转,必须做什么)。
|
|
39
|
+
- **Prefix**: `CHORE-`
|
|
40
|
+
|
|
41
|
+
_(取代了 Task 概念)_
|
|
42
|
+
|
|
43
|
+
#### 🐞 FIX (修复)
|
|
44
|
+
|
|
45
|
+
- **Mindset**: _Debugger_ (调试者)
|
|
46
|
+
- **定义**: 预期与现实的偏差。它是负价值的修正。
|
|
47
|
+
- **Focus**: "Fix" (恢复原状)。
|
|
48
|
+
- **Prefix**: `FIX-`
|
|
49
|
+
|
|
50
|
+
_(取代了 Bug 概念)_
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
**关系链**:
|
|
55
|
+
|
|
56
|
+
- **主要**: `EPIC` (愿景) -> `FEATURE` (价值交付单元)
|
|
57
|
+
- **次要**: `CHORE` (工程维护/支撑) - 通常独立存在。
|
|
58
|
+
- **原子性原则**: Feature = Design + Dev + Test + Doc + i18n。它们是一体的。
|
|
59
|
+
|
|
60
|
+
## 准则 (Guidelines)
|
|
61
|
+
|
|
62
|
+
### 目录结构
|
|
63
|
+
|
|
64
|
+
`Issues/{CapitalizedPluralType}/{lowercase_status}/`
|
|
65
|
+
|
|
66
|
+
- `{TYPE}`: `Epics`, `Features`, `Chores`, `Fixes`
|
|
67
|
+
- `{STATUS}`: `open`, `backlog`, `closed`
|
|
68
|
+
|
|
69
|
+
### 路径流转
|
|
70
|
+
|
|
71
|
+
使用 `monoco issue`:
|
|
72
|
+
|
|
73
|
+
1. **Create**: `monoco issue create <type> --title "..."`
|
|
74
|
+
|
|
75
|
+
- Params: `--parent <id>`, `--dependency <id>`, `--related <id>`, `--sprint <id>`, `--tags <tag>`
|
|
76
|
+
|
|
77
|
+
2. **Transition**: `monoco issue open/close/backlog <id>`
|
|
78
|
+
|
|
79
|
+
3. **View**: `monoco issue scope`
|
|
80
|
+
|
|
81
|
+
4. **Validation**: `monoco issue lint`
|
|
82
|
+
|
|
83
|
+
5. **Modification**: `monoco issue start/submit/delete <id>`
|
|
84
|
+
|
|
85
|
+
6. **Commit**: `monoco issue commit` (Atomic commit for issue files)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from monoco.core.feature import MonocoFeature, IntegrationData
|
|
4
|
+
from monoco.features.spike import core
|
|
5
|
+
|
|
6
|
+
class SpikeFeature(MonocoFeature):
|
|
7
|
+
@property
|
|
8
|
+
def name(self) -> str:
|
|
9
|
+
return "spike"
|
|
10
|
+
|
|
11
|
+
def initialize(self, root: Path, config: Dict) -> None:
|
|
12
|
+
spikes_name = config.get("paths", {}).get("spikes", ".references")
|
|
13
|
+
core.init(root, spikes_name)
|
|
14
|
+
|
|
15
|
+
def integrate(self, root: Path, config: Dict) -> IntegrationData:
|
|
16
|
+
# Determine language from config, default to 'en'
|
|
17
|
+
lang = config.get("i18n", {}).get("source_lang", "en")
|
|
18
|
+
base_dir = Path(__file__).parent / "resources"
|
|
19
|
+
|
|
20
|
+
prompt_file = base_dir / lang / "AGENTS.md"
|
|
21
|
+
if not prompt_file.exists():
|
|
22
|
+
prompt_file = base_dir / "en" / "AGENTS.md"
|
|
23
|
+
|
|
24
|
+
content = ""
|
|
25
|
+
if prompt_file.exists():
|
|
26
|
+
content = prompt_file.read_text(encoding="utf-8").strip()
|
|
27
|
+
|
|
28
|
+
return IntegrationData(
|
|
29
|
+
system_prompts={"Spike (Research)": content}
|
|
30
|
+
)
|
monoco/features/spike/core.py
CHANGED
|
@@ -31,17 +31,10 @@ def run_git_command(cmd: List[str], cwd: Path) -> bool:
|
|
|
31
31
|
|
|
32
32
|
def get_config_file_path(root: Path) -> Path:
|
|
33
33
|
"""Determine the config file to update."""
|
|
34
|
-
#
|
|
34
|
+
# Standard: .monoco/config.yaml
|
|
35
35
|
hidden = root / ".monoco" / "config.yaml"
|
|
36
|
-
if hidden.exists():
|
|
37
|
-
return hidden
|
|
38
36
|
|
|
39
|
-
#
|
|
40
|
-
visible = root / "monoco.yaml"
|
|
41
|
-
if visible.exists():
|
|
42
|
-
return visible
|
|
43
|
-
|
|
44
|
-
# Default to .monoco/config.yaml for new files
|
|
37
|
+
# Ensure parent exists
|
|
45
38
|
hidden.parent.mkdir(exist_ok=True)
|
|
46
39
|
return hidden
|
|
47
40
|
|
|
@@ -131,24 +124,14 @@ This skill normalizes how we introduce external code repositories.
|
|
|
131
124
|
3. **Remove**: `monoco spike remove <name>`
|
|
132
125
|
"""
|
|
133
126
|
|
|
134
|
-
PROMPT_CONTENT = """### Spike (Research)
|
|
135
|
-
Manage external reference repositories.
|
|
136
|
-
- **Add Repo**: `monoco spike add <url>` (Available in `.reference/<name>` for reading)
|
|
137
|
-
- **Sync**: `monoco spike sync` (Run to download content)
|
|
138
|
-
- **Constraint**: Never edit files in `.reference/`. Treat them as read-only external knowledge."""
|
|
139
|
-
|
|
140
127
|
def init(root: Path, spikes_dir_name: str):
|
|
141
128
|
"""Initialize Spike environment."""
|
|
142
129
|
ensure_gitignore(root, spikes_dir_name)
|
|
143
130
|
(root / spikes_dir_name).mkdir(exist_ok=True)
|
|
144
131
|
|
|
145
|
-
def get_resources() -> Dict[str, Any]:
|
|
146
132
|
return {
|
|
147
133
|
"skills": {
|
|
148
134
|
"git-repo-spike": SKILL_CONTENT
|
|
149
135
|
},
|
|
150
|
-
"prompts": {
|
|
151
|
-
"spike": PROMPT_CONTENT
|
|
152
|
-
}
|
|
136
|
+
"prompts": {} # Handled by adapter via resource files
|
|
153
137
|
}
|
|
154
|
-
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
### Spike (Research)
|
|
2
|
+
|
|
3
|
+
Manage external reference repositories.
|
|
4
|
+
|
|
5
|
+
- **Add Repo**: `monoco spike add <url>` (Available in `.reference/<name>` for reading)
|
|
6
|
+
- **Sync**: `monoco spike sync` (Run to download content)
|
|
7
|
+
- **Constraint**: Never edit files in `.reference/`. Treat them as read-only external knowledge.
|