monoco-toolkit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- monoco/core/__init__.py +0 -0
- monoco/core/config.py +113 -0
- monoco/core/git.py +184 -0
- monoco/core/output.py +97 -0
- monoco/core/setup.py +285 -0
- monoco/core/telemetry.py +86 -0
- monoco/core/workspace.py +40 -0
- monoco/daemon/__init__.py +0 -0
- monoco/daemon/app.py +378 -0
- monoco/daemon/commands.py +36 -0
- monoco/daemon/models.py +24 -0
- monoco/daemon/reproduce_stats.py +41 -0
- monoco/daemon/services.py +265 -0
- monoco/daemon/stats.py +124 -0
- monoco/features/__init__.py +0 -0
- monoco/features/config/commands.py +70 -0
- monoco/features/i18n/__init__.py +0 -0
- monoco/features/i18n/commands.py +121 -0
- monoco/features/i18n/core.py +178 -0
- monoco/features/issue/commands.py +710 -0
- monoco/features/issue/core.py +1174 -0
- monoco/features/issue/linter.py +172 -0
- monoco/features/issue/models.py +154 -0
- monoco/features/skills/__init__.py +1 -0
- monoco/features/skills/core.py +96 -0
- monoco/features/spike/commands.py +110 -0
- monoco/features/spike/core.py +154 -0
- monoco/main.py +73 -0
- monoco_toolkit-0.1.0.dist-info/METADATA +86 -0
- monoco_toolkit-0.1.0.dist-info/RECORD +33 -0
- monoco_toolkit-0.1.0.dist-info/WHEEL +4 -0
- monoco_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
- monoco_toolkit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import fnmatch
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Set, Dict, Any
|
|
5
|
+
|
|
6
|
+
DEFAULT_EXCLUDES = [".git", ".reference", "dist", "build", "node_modules", "__pycache__", ".agent", ".mono", ".venv", "venv", "ENV", "Issues"]
|
|
7
|
+
|
|
8
|
+
def load_gitignore_patterns(root: Path) -> List[str]:
|
|
9
|
+
"""Load patterns from .gitignore file."""
|
|
10
|
+
gitignore_path = root / ".gitignore"
|
|
11
|
+
if not gitignore_path.exists():
|
|
12
|
+
return []
|
|
13
|
+
|
|
14
|
+
patterns = []
|
|
15
|
+
try:
|
|
16
|
+
with open(gitignore_path, "r", encoding="utf-8") as f:
|
|
17
|
+
for line in f:
|
|
18
|
+
line = line.strip()
|
|
19
|
+
if line and not line.startswith("#"):
|
|
20
|
+
# Basic normalization for fnmatch
|
|
21
|
+
if line.startswith("/"):
|
|
22
|
+
line = line[1:]
|
|
23
|
+
patterns.append(line)
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
return patterns
|
|
27
|
+
|
|
28
|
+
def is_excluded(path: Path, root: Path, patterns: List[str]) -> bool:
|
|
29
|
+
"""Check if a path should be excluded based on patterns and defaults."""
|
|
30
|
+
rel_path = str(path.relative_to(root))
|
|
31
|
+
|
|
32
|
+
# 1. Check default excludes (exact match for any path component, case-insensitive)
|
|
33
|
+
for part in path.parts:
|
|
34
|
+
if part.lower() in [e.lower() for e in DEFAULT_EXCLUDES]:
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
# 2. Check gitignore patterns
|
|
38
|
+
for pattern in patterns:
|
|
39
|
+
# Check against relative path
|
|
40
|
+
if fnmatch.fnmatch(rel_path, pattern):
|
|
41
|
+
return True
|
|
42
|
+
# Check against filename
|
|
43
|
+
if fnmatch.fnmatch(path.name, pattern):
|
|
44
|
+
return True
|
|
45
|
+
# Check if the pattern matches a parent directory
|
|
46
|
+
# e.g. pattern "dist/" should match "dist/info.md"
|
|
47
|
+
if pattern.endswith("/"):
|
|
48
|
+
clean_pattern = pattern[:-1]
|
|
49
|
+
if rel_path.startswith(clean_pattern + "/") or rel_path == clean_pattern:
|
|
50
|
+
return True
|
|
51
|
+
elif "/" in pattern:
|
|
52
|
+
# If pattern has a slash, it might be a subpath match
|
|
53
|
+
if rel_path.startswith(pattern + "/"):
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
def discover_markdown_files(root: Path) -> List[Path]:
|
|
59
|
+
"""Recursively find markdown files while respecting exclusion rules."""
|
|
60
|
+
patterns = load_gitignore_patterns(root)
|
|
61
|
+
all_md_files = []
|
|
62
|
+
|
|
63
|
+
# We walk to ensure we can skip directories early if needed,
|
|
64
|
+
# but for now rglob + filter is simpler.
|
|
65
|
+
for p in root.rglob("*.md"):
|
|
66
|
+
if p.is_file() and not is_excluded(p, root, patterns):
|
|
67
|
+
all_md_files.append(p)
|
|
68
|
+
|
|
69
|
+
return sorted(all_md_files)
|
|
70
|
+
|
|
71
|
+
def is_translation_file(path: Path, target_langs: List[str]) -> bool:
|
|
72
|
+
"""Check if the given path is a translation file (target)."""
|
|
73
|
+
normalized_langs = [lang.lower() for lang in target_langs]
|
|
74
|
+
|
|
75
|
+
# Suffix check (case-insensitive)
|
|
76
|
+
stem_upper = path.stem.upper()
|
|
77
|
+
for lang in normalized_langs:
|
|
78
|
+
if stem_upper.endswith(f"_{lang.upper()}"):
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
# Subdir check (case-insensitive)
|
|
82
|
+
path_parts_lower = [p.lower() for p in path.parts]
|
|
83
|
+
for lang in normalized_langs:
|
|
84
|
+
if lang in path_parts_lower:
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
def get_target_translation_path(path: Path, root: Path, lang: str) -> Path:
|
|
90
|
+
"""Calculate the expected translation path for a specific language."""
|
|
91
|
+
lang = lang.lower()
|
|
92
|
+
|
|
93
|
+
# Parallel Directory Mode: docs/en/... -> docs/zh/...
|
|
94
|
+
# We assume 'en' is the source language for now.
|
|
95
|
+
path_parts = list(path.parts)
|
|
96
|
+
# Search for 'en' component to replace
|
|
97
|
+
# We iterate from root relative parts to be safe, but simple replacement of the first 'en'
|
|
98
|
+
# component (if not part of filename) is a good heuristic for docs structure.
|
|
99
|
+
for i, part in enumerate(path_parts):
|
|
100
|
+
if part.lower() == 'en':
|
|
101
|
+
path_parts[i] = lang
|
|
102
|
+
return Path(*path_parts)
|
|
103
|
+
|
|
104
|
+
# Suffix Mode: for root files
|
|
105
|
+
if path.parent == root:
|
|
106
|
+
return path.with_name(f"{path.stem}_{lang.upper()}{path.suffix}")
|
|
107
|
+
|
|
108
|
+
# Subdir Mode: for documentation directories (fallback)
|
|
109
|
+
return path.parent / lang / path.name
|
|
110
|
+
|
|
111
|
+
def check_translation_exists(path: Path, root: Path, target_langs: List[str]) -> List[str]:
|
|
112
|
+
"""
|
|
113
|
+
Verify which target languages have translations.
|
|
114
|
+
Returns a list of missing language codes.
|
|
115
|
+
"""
|
|
116
|
+
if is_translation_file(path, target_langs):
|
|
117
|
+
return [] # Already a translation, skip
|
|
118
|
+
|
|
119
|
+
missing = []
|
|
120
|
+
for lang in target_langs:
|
|
121
|
+
target = get_target_translation_path(path, root, lang)
|
|
122
|
+
if not target.exists():
|
|
123
|
+
missing.append(lang)
|
|
124
|
+
return missing
|
|
125
|
+
# ... (Existing code) ...
|
|
126
|
+
|
|
127
|
+
SKILL_CONTENT = """---
|
|
128
|
+
name: i18n-scan
|
|
129
|
+
description: Internationalization quality control skill.
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# i18n Maintenance Standard
|
|
133
|
+
|
|
134
|
+
i18n is a "first-class citizen" in Monoco.
|
|
135
|
+
|
|
136
|
+
## Core Standards
|
|
137
|
+
|
|
138
|
+
### 1. i18n Structure
|
|
139
|
+
- **Root Files**: Suffix pattern (e.g. `README_ZH.md`).
|
|
140
|
+
- **Docs Directories**: Subdirectory pattern (`docs/guide/zh/intro.md`).
|
|
141
|
+
|
|
142
|
+
### 2. Exclusion Rules
|
|
143
|
+
- `.gitignore` (respected automatically)
|
|
144
|
+
- `.references/`
|
|
145
|
+
- Build artifacts
|
|
146
|
+
|
|
147
|
+
## Automated Checklist
|
|
148
|
+
1. **Coverage Scan**: `monoco i18n scan` - Checks missing translations.
|
|
149
|
+
2. **Integrity Check**: Planned.
|
|
150
|
+
|
|
151
|
+
## Working with I18n
|
|
152
|
+
- Create English docs first.
|
|
153
|
+
- Create translations following the naming convention.
|
|
154
|
+
- Run `monoco i18n scan` to verify coverage.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
PROMPT_CONTENT = """### Documentation I18n
|
|
158
|
+
Manage internationalization.
|
|
159
|
+
- **Scan**: `monoco i18n scan` (Check for missing translations)
|
|
160
|
+
- **Structure**:
|
|
161
|
+
- Root files: `FILE_ZH.md`
|
|
162
|
+
- Subdirs: `folder/zh/file.md`"""
|
|
163
|
+
|
|
164
|
+
def init(root: Path):
|
|
165
|
+
"""Initialize I18n environment (No-op currently as it relies on config)."""
|
|
166
|
+
# In future, could generate i18n config section if missing.
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
def get_resources() -> Dict[str, Any]:
|
|
170
|
+
return {
|
|
171
|
+
"skills": {
|
|
172
|
+
"i18n": SKILL_CONTENT
|
|
173
|
+
},
|
|
174
|
+
"prompts": {
|
|
175
|
+
"i18n": PROMPT_CONTENT
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|