monoco-toolkit 0.2.5__py3-none-any.whl → 0.2.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.
Files changed (39) hide show
  1. monoco/core/agent/adapters.py +24 -1
  2. monoco/core/config.py +77 -17
  3. monoco/core/integrations.py +8 -0
  4. monoco/core/lsp.py +7 -0
  5. monoco/core/output.py +8 -1
  6. monoco/core/resources/zh/SKILL.md +6 -7
  7. monoco/core/setup.py +8 -0
  8. monoco/features/i18n/resources/zh/SKILL.md +5 -5
  9. monoco/features/issue/commands.py +135 -55
  10. monoco/features/issue/core.py +157 -122
  11. monoco/features/issue/domain/__init__.py +0 -0
  12. monoco/features/issue/domain/lifecycle.py +126 -0
  13. monoco/features/issue/domain/models.py +170 -0
  14. monoco/features/issue/domain/parser.py +223 -0
  15. monoco/features/issue/domain/workspace.py +104 -0
  16. monoco/features/issue/engine/__init__.py +22 -0
  17. monoco/features/issue/engine/config.py +172 -0
  18. monoco/features/issue/engine/machine.py +185 -0
  19. monoco/features/issue/engine/models.py +18 -0
  20. monoco/features/issue/linter.py +32 -11
  21. monoco/features/issue/lsp/__init__.py +3 -0
  22. monoco/features/issue/lsp/definition.py +72 -0
  23. monoco/features/issue/models.py +26 -9
  24. monoco/features/issue/resources/zh/SKILL.md +8 -9
  25. monoco/features/issue/validator.py +181 -65
  26. monoco/features/spike/core.py +5 -22
  27. monoco/features/spike/resources/zh/SKILL.md +2 -2
  28. monoco/main.py +2 -26
  29. monoco_toolkit-0.2.7.dist-info/METADATA +129 -0
  30. {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/RECORD +33 -27
  31. monoco/features/agent/commands.py +0 -166
  32. monoco/features/agent/doctor.py +0 -30
  33. monoco/features/pty/core.py +0 -185
  34. monoco/features/pty/router.py +0 -138
  35. monoco/features/pty/server.py +0 -56
  36. monoco_toolkit-0.2.5.dist-info/METADATA +0 -93
  37. {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/WHEEL +0 -0
  38. {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/entry_points.txt +0 -0
  39. {monoco_toolkit-0.2.5.dist-info → monoco_toolkit-0.2.7.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,9 @@ from pathlib import Path
6
6
  from monoco.core.lsp import Diagnostic, DiagnosticSeverity, Range, Position
7
7
  from monoco.core.config import get_config
8
8
  from monoco.features.i18n.core import detect_language
9
- from .models import IssueMetadata, IssueStatus, IssueStage, IssueType
9
+ from .models import IssueMetadata, IssueType
10
+ from .domain.parser import MarkdownParser
11
+ from .domain.models import ContentBlock
10
12
 
11
13
  class IssueValidator:
12
14
  """
@@ -20,14 +22,41 @@ class IssueValidator:
20
22
  def validate(self, meta: IssueMetadata, content: str, all_issue_ids: Set[str] = set()) -> List[Diagnostic]:
21
23
  diagnostics = []
22
24
 
25
+ # Parse Content into Blocks (Domain Layer)
26
+ # Handle case where content might be just body (from update_issue) or full file
27
+ if content.startswith("---"):
28
+ try:
29
+ issue_domain = MarkdownParser.parse(content)
30
+ blocks = issue_domain.body.blocks
31
+ has_frontmatter = True
32
+ except Exception:
33
+ # Fallback if parser fails (e.g. invalid YAML)
34
+ # We continue with empty blocks or try partial parsing?
35
+ # For now, let's try to parse blocks ignoring FM
36
+ lines = content.splitlines()
37
+ # Find end of FM
38
+ start_line = 0
39
+ if lines[0].strip() == "---":
40
+ for i in range(1, len(lines)):
41
+ if lines[i].strip() == "---":
42
+ start_line = i + 1
43
+ break
44
+ blocks = MarkdownParser._parse_blocks(lines[start_line:], start_line_offset=start_line)
45
+ has_frontmatter = True
46
+ else:
47
+ # Assume content is just body
48
+ lines = content.splitlines()
49
+ blocks = MarkdownParser._parse_blocks(lines, start_line_offset=0)
50
+ has_frontmatter = False
51
+
23
52
  # 1. State Matrix Validation
24
53
  diagnostics.extend(self._validate_state_matrix(meta, content))
25
54
 
26
- # 2. Content Completeness (Checkbox check)
27
- diagnostics.extend(self._validate_content_completeness(meta, content))
55
+ # 2. State Requirements (Strict Verification)
56
+ diagnostics.extend(self._validate_state_requirements(meta, blocks))
28
57
 
29
- # 3. Structure Consistency (Headings)
30
- diagnostics.extend(self._validate_structure(meta, content))
58
+ # 3. Structure Consistency (Headings) - Using Blocks
59
+ diagnostics.extend(self._validate_structure_blocks(meta, blocks))
31
60
 
32
61
  # 4. Lifecycle/Integrity (Solution, etc.)
33
62
  diagnostics.extend(self._validate_integrity(meta, content))
@@ -38,8 +67,8 @@ class IssueValidator:
38
67
  # 6. Time Consistency
39
68
  diagnostics.extend(self._validate_time_consistency(meta, content))
40
69
 
41
- # 7. Checkbox Syntax
42
- diagnostics.extend(self._validate_checkbox_logic(content))
70
+ # 7. Checkbox Syntax - Using Blocks
71
+ diagnostics.extend(self._validate_checkbox_logic_blocks(blocks))
43
72
 
44
73
  # 8. Language Consistency
45
74
  diagnostics.extend(self._validate_language_consistency(meta, content))
@@ -97,56 +126,102 @@ class IssueValidator:
97
126
  diagnostics = []
98
127
 
99
128
  # Check based on parsed metadata (now that auto-correction is disabled)
100
- if meta.status == IssueStatus.CLOSED and meta.stage != IssueStage.DONE:
129
+ if meta.status == "closed" and meta.stage != "done":
101
130
  line = self._get_field_line(content, "status")
102
131
  diagnostics.append(self._create_diagnostic(
103
- f"State Mismatch: Closed issues must be in 'Done' stage (found: {meta.stage.value if meta.stage else 'None'})",
132
+ f"State Mismatch: Closed issues must be in 'Done' stage (found: {meta.stage if meta.stage else 'None'})",
104
133
  DiagnosticSeverity.Error,
105
134
  line=line
106
135
  ))
107
136
 
108
- if meta.status == IssueStatus.BACKLOG and meta.stage != IssueStage.FREEZED:
137
+ if meta.status == "backlog" and meta.stage != "freezed":
109
138
  line = self._get_field_line(content, "status")
110
139
  diagnostics.append(self._create_diagnostic(
111
- f"State Mismatch: Backlog issues must be in 'Freezed' stage (found: {meta.stage.value if meta.stage else 'None'})",
140
+ f"State Mismatch: Backlog issues must be in 'Freezed' stage (found: {meta.stage if meta.stage else 'None'})",
112
141
  DiagnosticSeverity.Error,
113
142
  line=line
114
143
  ))
115
144
 
116
145
  return diagnostics
117
146
 
118
- def _validate_content_completeness(self, meta: IssueMetadata, content: str) -> List[Diagnostic]:
147
+ def _validate_state_requirements(self, meta: IssueMetadata, blocks: List[ContentBlock]) -> List[Diagnostic]:
119
148
  diagnostics = []
120
- # Checkbox regex: - [ ] or - [x] or - [-] or - [/]
121
- checkboxes = re.findall(r"-\s*\[([ x\-/])\]", content)
122
149
 
123
- if len(checkboxes) < 2:
124
- diagnostics.append(self._create_diagnostic(
125
- "Content Incomplete: Ticket must contain at least 2 checkboxes (AC & Tasks).",
126
- DiagnosticSeverity.Warning
127
- ))
128
-
129
- if meta.stage in [IssueStage.REVIEW, IssueStage.DONE]:
130
- # No empty checkboxes allowed
131
- if ' ' in checkboxes:
132
- # Find the first occurrence line
133
- lines = content.split('\n')
134
- first_line = 0
135
- for i, line in enumerate(lines):
136
- if re.search(r"-\s*\[ \]", line):
137
- first_line = i
138
- break
139
-
140
- diagnostics.append(self._create_diagnostic(
141
- f"Incomplete Tasks: Issue in {meta.stage} cannot have unchecked boxes.",
142
- DiagnosticSeverity.Error,
143
- line=first_line
144
- ))
150
+ # 1. Map Blocks to Sections
151
+ sections = {"tasks": [], "ac": [], "review": []}
152
+ current_section = None
153
+
154
+ for block in blocks:
155
+ if block.type == "heading":
156
+ title = block.content.strip().lower()
157
+ if "technical tasks" in title:
158
+ current_section = "tasks"
159
+ elif "acceptance criteria" in title:
160
+ current_section = "ac"
161
+ elif "review comments" in title:
162
+ current_section = "review"
163
+ else:
164
+ current_section = None
165
+ elif block.type == "task_item":
166
+ if current_section and current_section in sections:
167
+ sections[current_section].append(block)
168
+
169
+ # 2. Logic: DOING -> Must have defined tasks
170
+ if meta.stage in ["doing", "review", "done"]:
171
+ if not sections["tasks"]:
172
+ # We can't strictly point to a line if section missing, but we can point to top/bottom
173
+ # Or just a general error.
174
+ diagnostics.append(self._create_diagnostic(
175
+ "State Requirement (DOING+): Must define 'Technical Tasks' (at least 1 checkbox).",
176
+ DiagnosticSeverity.Warning
177
+ ))
178
+
179
+ # 3. Logic: REVIEW -> Tasks must be Completed ([x]) or Cancelled ([~], [+])
180
+ # No [ ] (ToDo) or [-]/[/] (Doing) allowed.
181
+ if meta.stage in ["review", "done"]:
182
+ for block in sections["tasks"]:
183
+ content = block.content.strip()
184
+ # Check for explicit illegal states
185
+ if re.search(r"-\s*\[\s+\]", content):
186
+ diagnostics.append(self._create_diagnostic(
187
+ f"State Requirement ({meta.stage.upper()}): Technical Tasks must be resolved. Found Todo [ ]: '{content}'",
188
+ DiagnosticSeverity.Error,
189
+ line=block.line_start
190
+ ))
191
+ elif re.search(r"-\s*\[[-\/]]", content):
192
+ diagnostics.append(self._create_diagnostic(
193
+ f"State Requirement ({meta.stage.upper()}): Technical Tasks must be finished (not Doing). Found Doing [-]: '{content}'",
194
+ DiagnosticSeverity.Error,
195
+ line=block.line_start
196
+ ))
197
+
198
+ # 4. Logic: DONE -> AC must be Verified ([x])
199
+ if meta.stage == "done":
200
+ for block in sections["ac"]:
201
+ content = block.content.strip()
202
+ if not re.search(r"-\s*\[[xX]\]", content):
203
+ diagnostics.append(self._create_diagnostic(
204
+ f"State Requirement (DONE): Acceptance Criteria must be passed ([x]). Found: '{content}'",
205
+ DiagnosticSeverity.Error,
206
+ line=block.line_start
207
+ ))
208
+
209
+ # 5. Logic: DONE -> Review Checkboxes (if any) must be Resolved ([x] or [~])
210
+ for block in sections["review"]:
211
+ content = block.content.strip()
212
+ # Must be [x], [X], [~], [+]
213
+ # Therefore [ ], [-], [/] are invalid blocking states
214
+ if re.search(r"-\s*\[[\s\-\/]\]", content):
215
+ diagnostics.append(self._create_diagnostic(
216
+ f"State Requirement (DONE): Actionable Review Comments must be resolved ([x] or [~]). Found: '{content}'",
217
+ DiagnosticSeverity.Error,
218
+ line=block.line_start
219
+ ))
220
+
145
221
  return diagnostics
146
222
 
147
- def _validate_structure(self, meta: IssueMetadata, content: str) -> List[Diagnostic]:
223
+ def _validate_structure_blocks(self, meta: IssueMetadata, blocks: List[ContentBlock]) -> List[Diagnostic]:
148
224
  diagnostics = []
149
- lines = content.split('\n')
150
225
 
151
226
  # 1. Heading check: ## {issue-id}: {issue-title}
152
227
  expected_header = f"## {meta.id}: {meta.title}"
@@ -156,19 +231,25 @@ class IssueValidator:
156
231
  review_header_found = False
157
232
  review_content_found = False
158
233
 
159
- for i, line in enumerate(lines):
160
- line_stripped = line.strip()
161
- if line_stripped == expected_header:
162
- header_found = True
163
-
164
- if line_stripped == "## Review Comments":
165
- review_header_found = True
166
- # Check near lines for content
167
- # This is a naive check (next line is not empty)
168
- if i + 1 < len(lines) and lines[i+1].strip():
169
- review_content_found = True
170
- elif i + 2 < len(lines) and lines[i+2].strip():
171
- review_content_found = True
234
+ review_header_index = -1
235
+
236
+ for i, block in enumerate(blocks):
237
+ if block.type == 'heading':
238
+ stripped = block.content.strip()
239
+ if stripped == expected_header:
240
+ header_found = True
241
+
242
+ if stripped == "## Review Comments":
243
+ review_header_found = True
244
+ review_header_index = i
245
+
246
+ # Check content after review header
247
+ if review_header_found:
248
+ # Check if there are blocks after review_header_index that are NOT empty
249
+ for j in range(review_header_index + 1, len(blocks)):
250
+ if blocks[j].type != 'empty':
251
+ review_content_found = True
252
+ break
172
253
 
173
254
  if not header_found:
174
255
  diagnostics.append(self._create_diagnostic(
@@ -176,7 +257,7 @@ class IssueValidator:
176
257
  DiagnosticSeverity.Warning
177
258
  ))
178
259
 
179
- if meta.stage in [IssueStage.REVIEW, IssueStage.DONE]:
260
+ if meta.stage in ["review", "done"]:
180
261
  if not review_header_found:
181
262
  diagnostics.append(self._create_diagnostic(
182
263
  "Review Requirement: Missing '## Review Comments' section.",
@@ -191,7 +272,7 @@ class IssueValidator:
191
272
 
192
273
  def _validate_integrity(self, meta: IssueMetadata, content: str) -> List[Diagnostic]:
193
274
  diagnostics = []
194
- if meta.status == IssueStatus.CLOSED and not meta.solution:
275
+ if meta.status == "closed" and not meta.solution:
195
276
  line = self._get_field_line(content, "status")
196
277
  diagnostics.append(self._create_diagnostic(
197
278
  f"Data Integrity: Closed issue {meta.id} missing 'solution' field.",
@@ -221,6 +302,42 @@ class IssueValidator:
221
302
  DiagnosticSeverity.Error,
222
303
  line=line
223
304
  ))
305
+
306
+ # Body Reference Check
307
+ # Regex for generic issue ID: (EPIC|FEAT|CHORE|FIX)-\d{4}
308
+ # We scan line by line to get line numbers
309
+ lines = content.split('\n')
310
+ # Skip frontmatter for body check to avoid double counting (handled above)
311
+ in_fm = False
312
+ fm_end = 0
313
+ for i, line in enumerate(lines):
314
+ if line.strip() == '---':
315
+ if not in_fm: in_fm = True
316
+ else:
317
+ fm_end = i
318
+ break
319
+
320
+ for i, line in enumerate(lines):
321
+ if i <= fm_end: continue # Skip frontmatter
322
+
323
+ # Find all matches
324
+ matches = re.finditer(r"\b((?:EPIC|FEAT|CHORE|FIX)-\d{4})\b", line)
325
+ for match in matches:
326
+ ref_id = match.group(1)
327
+ if ref_id != meta.id and ref_id not in all_ids:
328
+ # Check if it's a namespaced ID? The regex only catches local IDs.
329
+ # If users use MON::FEAT-0001, the regex might catch FEAT-0001.
330
+ # But all_ids contains full IDs (potentially namespaced).
331
+ # Simple logic: if ref_id isn't in all_ids, check if any id ENDS with ref_id
332
+
333
+ found_namespaced = any(known.endswith(f"::{ref_id}") for known in all_ids)
334
+
335
+ if not found_namespaced:
336
+ diagnostics.append(self._create_diagnostic(
337
+ f"Broken Reference: Issue '{ref_id}' not found.",
338
+ DiagnosticSeverity.Warning,
339
+ line=i
340
+ ))
224
341
  return diagnostics
225
342
 
226
343
  def _validate_time_consistency(self, meta: IssueMetadata, content: str) -> List[Diagnostic]:
@@ -249,21 +366,20 @@ class IssueValidator:
249
366
 
250
367
  return diagnostics
251
368
 
252
- def _validate_checkbox_logic(self, content: str) -> List[Diagnostic]:
369
+ def _validate_checkbox_logic_blocks(self, blocks: List[ContentBlock]) -> List[Diagnostic]:
253
370
  diagnostics = []
254
- lines = content.split('\n')
255
371
 
256
- for i, line in enumerate(lines):
257
- stripped = line.lstrip()
258
-
259
- # Syntax Check: - [?]
260
- if stripped.startswith("- ["):
261
- match = re.match(r"- \[([ x\-/])\]", stripped)
372
+ for block in blocks:
373
+ if block.type == 'task_item':
374
+ content = block.content.strip()
375
+ # Syntax Check: - [?]
376
+ # Added supported chars: /, ~, +
377
+ match = re.match(r"- \[([ x\-/~+])\]", content)
262
378
  if not match:
263
379
  # Check for Common errors
264
- if re.match(r"- \[.{2,}\]", stripped): # [xx] or [ ]
265
- diagnostics.append(self._create_diagnostic("Invalid Checkbox: Use single character [ ], [x], [-], [/]", DiagnosticSeverity.Error, i))
266
- elif re.match(r"- \[([^ x\-/])\]", stripped): # [v], [o]
267
- diagnostics.append(self._create_diagnostic("Invalid Checkbox Status: Use [ ], [x], [-], [/]", DiagnosticSeverity.Error, i))
380
+ if re.match(r"- \[.{2,}\]", content): # [xx] or [ ]
381
+ diagnostics.append(self._create_diagnostic("Invalid Checkbox: Use single character [ ], [x], [-], [/]", DiagnosticSeverity.Error, block.line_start))
382
+ elif re.match(r"- \[([^ x\-/~+])\]", content): # [v], [o]
383
+ diagnostics.append(self._create_diagnostic("Invalid Checkbox Status: Use [ ], [x], [/], [~]", DiagnosticSeverity.Error, block.line_start))
268
384
 
269
385
  return diagnostics
@@ -1,12 +1,12 @@
1
1
  import os
2
2
  import shutil
3
3
  import subprocess
4
- import yaml
4
+
5
5
  from pathlib import Path
6
6
  from typing import Dict, Optional, List, Any
7
7
  from rich.console import Console
8
8
 
9
- from monoco.core.config import get_config
9
+ from monoco.core.config import get_config, load_raw_config, save_raw_config, ConfigScope
10
10
 
11
11
  console = Console()
12
12
 
@@ -29,26 +29,10 @@ def run_git_command(cmd: List[str], cwd: Path) -> bool:
29
29
  console.print("[red]Error:[/red] git command not found.")
30
30
  return False
31
31
 
32
- def get_config_file_path(root: Path) -> Path:
33
- """Determine the config file to update."""
34
- # Standard: .monoco/project.yaml
35
- hidden = root / ".monoco" / "project.yaml"
36
-
37
- # Ensure parent exists
38
- hidden.parent.mkdir(exist_ok=True)
39
- return hidden
40
-
41
32
  def update_config_repos(root: Path, repo_name: str, repo_url: str, remove: bool = False):
42
33
  """Update the repos list in the config file."""
43
- config_path = get_config_file_path(root)
44
-
45
- data = {}
46
- if config_path.exists():
47
- try:
48
- with open(config_path, "r") as f:
49
- data = yaml.safe_load(f) or {}
50
- except Exception:
51
- data = {}
34
+ # Use core config utils
35
+ data = load_raw_config(ConfigScope.PROJECT, project_root=str(root))
52
36
 
53
37
  # Ensure structure exists
54
38
  if "project" not in data:
@@ -62,8 +46,7 @@ def update_config_repos(root: Path, repo_name: str, repo_url: str, remove: bool
62
46
  else:
63
47
  data["project"]["spike_repos"][repo_name] = repo_url
64
48
 
65
- with open(config_path, "w") as f:
66
- yaml.dump(data, f, sort_keys=False, default_flow_style=False)
49
+ save_raw_config(ConfigScope.PROJECT, data, project_root=str(root))
67
50
 
68
51
  def ensure_gitignore(root: Path, target_dir_name: str):
69
52
  """Ensure the target directory is in .gitignore."""
@@ -9,7 +9,7 @@ description: 管理用于研究和学习的外部参考仓库。提供对精选
9
9
 
10
10
  ## 概述
11
11
 
12
- Spike 功能允许你:
12
+ Spike 功能允许你:
13
13
 
14
14
  - **添加外部仓库**作为只读参考
15
15
  - **同步仓库内容**到本地 `.references/` 目录
@@ -50,7 +50,7 @@ monoco spike list
50
50
 
51
51
  ## 配置
52
52
 
53
- Spike 仓库在 `.monoco/config.yaml` 中配置:
53
+ Spike 仓库在 `.monoco/config.yaml` 中配置:
54
54
 
55
55
  ```yaml
56
56
  project:
monoco/main.py CHANGED
@@ -99,15 +99,7 @@ from monoco.core.sync import sync_command, uninstall_command
99
99
  app.command(name="sync")(sync_command)
100
100
  app.command(name="uninstall")(uninstall_command)
101
101
 
102
- @app.command(name="doctor")
103
- def doctor_cmd(
104
- force: bool = typer.Option(False, "--force", "-f", help="Force refresh of agent state")
105
- ):
106
- """
107
- Diagnose Agent Environment.
108
- """
109
- from monoco.features.agent.doctor import doctor
110
- doctor(force)
102
+
111
103
 
112
104
  @app.command()
113
105
  def info():
@@ -161,26 +153,10 @@ app.add_typer(config_cmd.app, name="config", help="Manage configuration")
161
153
  app.add_typer(project_cmd.app, name="project", help="Manage projects")
162
154
  app.add_typer(workspace_cmd.app, name="workspace", help="Manage workspace")
163
155
 
164
- from monoco.features.agent import commands as agent_cmd
165
- app.add_typer(agent_cmd.app, name="agent", help="Delegate tasks to Agent CLIs")
156
+
166
157
 
167
158
  from monoco.daemon.commands import serve
168
159
  app.command(name="serve")(serve)
169
160
 
170
- @app.command()
171
- def pty(
172
- host: str = "127.0.0.1",
173
- port: int = 3124,
174
- cwd: Optional[str] = None
175
- ):
176
- """
177
- Start the Monoco PTY Daemon (WebSocket).
178
- """
179
- from monoco.features.pty.server import run_pty_server
180
- from pathlib import Path
181
-
182
- path = Path(cwd) if cwd else None
183
- run_pty_server(host, port, path)
184
-
185
161
  if __name__ == "__main__":
186
162
  app()
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: monoco-toolkit
3
+ Version: 0.2.7
4
+ Summary: Agent Native Toolkit for Monoco - Task Management & Kanban for AI Agents
5
+ Project-URL: Homepage, https://monoco.io
6
+ Project-URL: Repository, https://github.com/IndenScale/Monoco
7
+ Project-URL: Documentation, https://monoco.io/docs
8
+ Project-URL: Issues, https://github.com/IndenScale/Monoco/issues
9
+ Author-email: Monoco Team <dev@monoco.io>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent-native,ai-agents,cli,kanban,monoco,task-management,workflow
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Office/Business :: Groupware
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: fastapi>=0.100.0
25
+ Requires-Dist: httpx>=0.28.1
26
+ Requires-Dist: prompt-toolkit>=3.0.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Requires-Dist: pyyaml>=6.0
29
+ Requires-Dist: rich>=13.0.0
30
+ Requires-Dist: sse-starlette>=1.6.0
31
+ Requires-Dist: typer[all]>=0.9.0
32
+ Requires-Dist: uvicorn[standard]>=0.20.0
33
+ Requires-Dist: watchdog>=6.0.0
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Monoco Toolkit
37
+
38
+ [![Version](https://img.shields.io/pypi/v/monoco-toolkit)](https://pypi.org/project/monoco-toolkit/)
39
+ [![License](https://img.shields.io/github/license/IndenScale/Monoco)](LICENSE)
40
+
41
+ > **The Operating System for Agentic Engineering.**
42
+ >
43
+ > Ground your AI Agents into deterministic workflows. Turn vague "chats" into structured, validatable, and shippable engineering units.
44
+
45
+ ---
46
+
47
+ ## ⚡️ Why Monoco?
48
+
49
+ In the era of LLMs, the bottleneck isn't **intelligence**—it's **control**.
50
+
51
+ Generating code is easy. Managing the lifecycle of thousands of agent-generated tasks, validating their outputs, and maintaining a coherent project state is hard. **Monoco** is the missing control plane that bridges the gap between raw AI velocity and strict engineering rigor.
52
+
53
+ Monoco handles the **"BizOps Logic"** of your development process, allowing you to orchestrate human and AI labor within a unified, version-controlled environment.
54
+
55
+ ## 🌟 Core Features
56
+
57
+ ### 1. Issue as Code (IaaC)
58
+
59
+ Treat your project management like your code.
60
+
61
+ - **Markdown Native**: All tasks (Epics, Features, Chores) are stored as structured Markdown files in your repository.
62
+ - **Git Backed**: Version control your roadmap. Review changes to requirements via Pull Requests.
63
+ - **Universal Context**: Provides a standardized, hallucination-free state representation for AI Agents.
64
+
65
+ ### 2. The Agent Cockpit (VS Code Extension)
66
+
67
+ Stop context switching. Manage your entire agentic workflow directly inside your editor.
68
+
69
+ - **Native Kanban Board**: Visualize and drag-and-drop tasks without leaving VS Code.
70
+ - **Hierarchical Tree View**: Drill down from high-level Epics to atomic Implementation Tasks.
71
+ - **Agent Integration**: Bind specific Agent Providers (Gemini, Claude, etc.) to specific tasks.
72
+
73
+ ### 3. Traceable Execution
74
+
75
+ - **Deterministic State Machine**: Every task follows a strict lifecycle (Proposed -> Approved -> Doing -> Review -> Done).
76
+ - **Audit Trails**: Agents log their actions and decisions directly into the task file.
77
+ - **Sanity Checks**: Built-in linters ensure your task definitions are complete and valid before execution.
78
+
79
+ ## 🚀 Quick Start
80
+
81
+ ### Installation
82
+
83
+ Monoco is available as a Python CLI tool.
84
+
85
+ ```bash
86
+ pip install monoco-toolkit
87
+ ```
88
+
89
+ ### Initialization
90
+
91
+ Turn any directory into a Monoco workspace.
92
+
93
+ ```bash
94
+ monoco init
95
+ ```
96
+
97
+ ### Workflow
98
+
99
+ 1. **Plan**: Create a new feature request.
100
+ ```bash
101
+ monoco issue create feature -t "Implement Dark Mode"
102
+ ```
103
+ 2. **Edit**: Refine the requirements in the generated markdown file.
104
+ 3. **Visualize**: Open the board in VS Code or via CLI.
105
+ ```bash
106
+ # Starts the local server
107
+ monoco serve
108
+ ```
109
+
110
+ ## 📦 Extension for VS Code
111
+
112
+ The **Monoco VS Code Extension** is the primary visual interface for the toolkit.
113
+
114
+ - **Install from Marketplace**: Search for `Monoco`.
115
+ - **Keybinding**: `Cmd+Shift+P` -> `Monoco: Open Kanban Board`.
116
+
117
+ ## 🛠️ Tech Stack & Architecture
118
+
119
+ - **Core**: Python (CLI & Logic Layer)
120
+ - **Extension**: TypeScript (VS Code Client & LSP)
121
+ - **Data**: Local Filesystem (Markdown/YAML)
122
+
123
+ ## 🤝 Contributing
124
+
125
+ Monoco is designed for the community. We welcome contributions to both the core CLI and the VS Code extension.
126
+
127
+ ## 📄 License
128
+
129
+ MIT © [IndenScale](https://github.com/IndenScale)