monoco-toolkit 0.1.1__py3-none-any.whl → 0.2.5__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/cli/__init__.py +0 -0
- monoco/cli/project.py +87 -0
- monoco/cli/workspace.py +46 -0
- monoco/core/agent/__init__.py +5 -0
- monoco/core/agent/action.py +144 -0
- monoco/core/agent/adapters.py +106 -0
- monoco/core/agent/protocol.py +31 -0
- monoco/core/agent/state.py +106 -0
- monoco/core/config.py +152 -17
- monoco/core/execution.py +62 -0
- monoco/core/feature.py +58 -0
- monoco/core/git.py +51 -2
- monoco/core/injection.py +196 -0
- monoco/core/integrations.py +234 -0
- monoco/core/lsp.py +61 -0
- monoco/core/output.py +13 -2
- 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 +88 -110
- monoco/core/skills.py +444 -0
- monoco/core/state.py +53 -0
- monoco/core/sync.py +224 -0
- monoco/core/telemetry.py +4 -1
- monoco/core/workspace.py +85 -20
- monoco/daemon/app.py +127 -58
- monoco/daemon/models.py +4 -0
- monoco/daemon/services.py +56 -155
- monoco/features/agent/commands.py +166 -0
- monoco/features/agent/doctor.py +30 -0
- monoco/features/config/commands.py +125 -44
- monoco/features/i18n/adapter.py +29 -0
- monoco/features/i18n/commands.py +89 -10
- monoco/features/i18n/core.py +113 -27
- 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 +183 -65
- monoco/features/issue/core.py +172 -77
- monoco/features/issue/linter.py +215 -116
- monoco/features/issue/migration.py +134 -0
- monoco/features/issue/models.py +23 -19
- monoco/features/issue/monitor.py +94 -0
- monoco/features/issue/resources/en/AGENTS.md +15 -0
- monoco/features/issue/resources/en/SKILL.md +87 -0
- monoco/features/issue/resources/zh/AGENTS.md +15 -0
- monoco/features/issue/resources/zh/SKILL.md +114 -0
- monoco/features/issue/validator.py +269 -0
- monoco/features/pty/core.py +185 -0
- monoco/features/pty/router.py +138 -0
- monoco/features/pty/server.py +56 -0
- monoco/features/spike/adapter.py +30 -0
- monoco/features/spike/commands.py +45 -24
- monoco/features/spike/core.py +4 -21
- 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 +115 -2
- {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.5.dist-info}/METADATA +2 -2
- monoco_toolkit-0.2.5.dist-info/RECORD +77 -0
- monoco_toolkit-0.1.1.dist-info/RECORD +0 -33
- {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.5.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.5.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,7 +8,7 @@ from rich.table import Table
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
10
|
from monoco.core.config import get_config
|
|
11
|
-
from monoco.core.output import print_output
|
|
11
|
+
from monoco.core.output import print_output, OutputManager, AgentOutput
|
|
12
12
|
from .models import IssueType, IssueStatus, IssueSolution, IssueStage, IsolationType, IssueMetadata
|
|
13
13
|
from . import core
|
|
14
14
|
|
|
@@ -23,13 +23,14 @@ 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')"),
|
|
30
30
|
sprint: Optional[str] = typer.Option(None, "--sprint", help="Sprint ID"),
|
|
31
31
|
tags: List[str] = typer.Option([], "--tag", help="Tags"),
|
|
32
32
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
33
|
+
json: AgentOutput = False,
|
|
33
34
|
):
|
|
34
35
|
"""Create a new issue."""
|
|
35
36
|
config = get_config()
|
|
@@ -39,7 +40,7 @@ def create(
|
|
|
39
40
|
if parent:
|
|
40
41
|
parent_path = core.find_issue_path(issues_root, parent)
|
|
41
42
|
if not parent_path:
|
|
42
|
-
|
|
43
|
+
OutputManager.error(f"Parent issue {parent} not found.")
|
|
43
44
|
raise typer.Exit(code=1)
|
|
44
45
|
|
|
45
46
|
try:
|
|
@@ -62,26 +63,75 @@ def create(
|
|
|
62
63
|
except ValueError:
|
|
63
64
|
rel_path = path
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
OutputManager.print({
|
|
67
|
+
"issue": issue,
|
|
68
|
+
"path": str(rel_path),
|
|
69
|
+
"status": "created"
|
|
70
|
+
})
|
|
71
|
+
|
|
67
72
|
except ValueError as e:
|
|
68
|
-
|
|
73
|
+
OutputManager.error(str(e))
|
|
69
74
|
raise typer.Exit(code=1)
|
|
70
75
|
|
|
76
|
+
@app.command("update")
|
|
77
|
+
def update(
|
|
78
|
+
issue_id: str = typer.Argument(..., help="Issue ID to update"),
|
|
79
|
+
title: Optional[str] = typer.Option(None, "--title", "-t", help="New title"),
|
|
80
|
+
status: Optional[IssueStatus] = typer.Option(None, "--status", help="New status"),
|
|
81
|
+
stage: Optional[IssueStage] = typer.Option(None, "--stage", help="New stage"),
|
|
82
|
+
parent: Optional[str] = typer.Option(None, "--parent", "-p", help="Parent Issue ID"),
|
|
83
|
+
sprint: Optional[str] = typer.Option(None, "--sprint", help="Sprint ID"),
|
|
84
|
+
dependencies: Optional[List[str]] = typer.Option(None, "--dependency", "-d", help="Issue dependency ID(s)"),
|
|
85
|
+
related: Optional[List[str]] = typer.Option(None, "--related", "-r", help="Related Issue ID(s)"),
|
|
86
|
+
tags: Optional[List[str]] = typer.Option(None, "--tag", help="Tags"),
|
|
87
|
+
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
88
|
+
json: AgentOutput = False,
|
|
89
|
+
):
|
|
90
|
+
"""Update an existing issue."""
|
|
91
|
+
config = get_config()
|
|
92
|
+
issues_root = _resolve_issues_root(config, root)
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
issue = core.update_issue(
|
|
96
|
+
issues_root,
|
|
97
|
+
issue_id,
|
|
98
|
+
status=status,
|
|
99
|
+
stage=stage,
|
|
100
|
+
title=title,
|
|
101
|
+
parent=parent,
|
|
102
|
+
sprint=sprint,
|
|
103
|
+
dependencies=dependencies,
|
|
104
|
+
related=related,
|
|
105
|
+
tags=tags
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
OutputManager.print({
|
|
109
|
+
"issue": issue,
|
|
110
|
+
"status": "updated"
|
|
111
|
+
})
|
|
112
|
+
except Exception as e:
|
|
113
|
+
OutputManager.error(str(e))
|
|
114
|
+
raise typer.Exit(code=1)
|
|
115
|
+
|
|
116
|
+
|
|
71
117
|
@app.command("open")
|
|
72
118
|
def move_open(
|
|
73
119
|
issue_id: str = typer.Argument(..., help="Issue ID to open"),
|
|
74
120
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
121
|
+
json: AgentOutput = False,
|
|
75
122
|
):
|
|
76
|
-
"""Move issue to open status and set stage to
|
|
123
|
+
"""Move issue to open status and set stage to Draft."""
|
|
77
124
|
config = get_config()
|
|
78
125
|
issues_root = _resolve_issues_root(config, root)
|
|
79
126
|
try:
|
|
80
127
|
# Pull operation: Force stage to TODO
|
|
81
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.
|
|
82
|
-
|
|
128
|
+
issue = core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.DRAFT)
|
|
129
|
+
OutputManager.print({
|
|
130
|
+
"issue": issue,
|
|
131
|
+
"status": "opened"
|
|
132
|
+
})
|
|
83
133
|
except Exception as e:
|
|
84
|
-
|
|
134
|
+
OutputManager.error(str(e))
|
|
85
135
|
raise typer.Exit(code=1)
|
|
86
136
|
|
|
87
137
|
@app.command("start")
|
|
@@ -90,6 +140,7 @@ def start(
|
|
|
90
140
|
branch: bool = typer.Option(False, "--branch", "-b", help="Start in a new git branch"),
|
|
91
141
|
worktree: bool = typer.Option(False, "--worktree", "-w", help="Start in a new git worktree"),
|
|
92
142
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
143
|
+
json: AgentOutput = False,
|
|
93
144
|
):
|
|
94
145
|
"""Start working on an issue (Stage -> Doing)."""
|
|
95
146
|
config = get_config()
|
|
@@ -97,32 +148,38 @@ def start(
|
|
|
97
148
|
project_root = _resolve_project_root(config)
|
|
98
149
|
|
|
99
150
|
if branch and worktree:
|
|
100
|
-
|
|
151
|
+
OutputManager.error("Cannot specify both --branch and --worktree.")
|
|
101
152
|
raise typer.Exit(code=1)
|
|
102
153
|
|
|
103
154
|
try:
|
|
104
155
|
# Implicitly ensure status is Open
|
|
105
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.DOING)
|
|
156
|
+
issue = core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.DOING)
|
|
106
157
|
|
|
158
|
+
isolation_info = None
|
|
159
|
+
|
|
107
160
|
if branch:
|
|
108
161
|
try:
|
|
109
|
-
|
|
110
|
-
|
|
162
|
+
issue = core.start_issue_isolation(issues_root, issue_id, IsolationType.BRANCH, project_root)
|
|
163
|
+
isolation_info = {"type": "branch", "ref": issue.isolation.ref}
|
|
111
164
|
except Exception as e:
|
|
112
|
-
|
|
165
|
+
OutputManager.error(f"Failed to create branch: {e}")
|
|
113
166
|
raise typer.Exit(code=1)
|
|
114
167
|
|
|
115
168
|
if worktree:
|
|
116
169
|
try:
|
|
117
|
-
|
|
118
|
-
|
|
170
|
+
issue = core.start_issue_isolation(issues_root, issue_id, IsolationType.WORKTREE, project_root)
|
|
171
|
+
isolation_info = {"type": "worktree", "path": issue.isolation.path, "ref": issue.isolation.ref}
|
|
119
172
|
except Exception as e:
|
|
120
|
-
|
|
173
|
+
OutputManager.error(f"Failed to create worktree: {e}")
|
|
121
174
|
raise typer.Exit(code=1)
|
|
122
175
|
|
|
123
|
-
|
|
176
|
+
OutputManager.print({
|
|
177
|
+
"issue": issue,
|
|
178
|
+
"status": "started",
|
|
179
|
+
"isolation": isolation_info
|
|
180
|
+
})
|
|
124
181
|
except Exception as e:
|
|
125
|
-
|
|
182
|
+
OutputManager.error(str(e))
|
|
126
183
|
raise typer.Exit(code=1)
|
|
127
184
|
|
|
128
185
|
@app.command("submit")
|
|
@@ -131,6 +188,7 @@ def submit(
|
|
|
131
188
|
prune: bool = typer.Option(False, "--prune", help="Delete branch/worktree after submit"),
|
|
132
189
|
force: bool = typer.Option(False, "--force", help="Force delete branch/worktree"),
|
|
133
190
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
191
|
+
json: AgentOutput = False,
|
|
134
192
|
):
|
|
135
193
|
"""Submit issue for review (Stage -> Review) and generate delivery report."""
|
|
136
194
|
config = get_config()
|
|
@@ -138,27 +196,33 @@ def submit(
|
|
|
138
196
|
project_root = _resolve_project_root(config)
|
|
139
197
|
try:
|
|
140
198
|
# Implicitly ensure status is Open
|
|
141
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.REVIEW)
|
|
142
|
-
console.print(f"[green]🚀[/green] Issue [bold]{issue_id}[/bold] submitted for review.")
|
|
199
|
+
issue = core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.REVIEW)
|
|
143
200
|
|
|
144
201
|
# Delivery Report Generation
|
|
202
|
+
report_status = "skipped"
|
|
145
203
|
try:
|
|
146
204
|
core.generate_delivery_report(issues_root, issue_id, project_root)
|
|
147
|
-
|
|
205
|
+
report_status = "generated"
|
|
148
206
|
except Exception as e:
|
|
149
|
-
|
|
207
|
+
report_status = f"failed: {e}"
|
|
150
208
|
|
|
209
|
+
pruned_resources = []
|
|
151
210
|
if prune:
|
|
152
211
|
try:
|
|
153
|
-
|
|
154
|
-
if deleted:
|
|
155
|
-
console.print(f"[dim]✔ Pruned resources: {', '.join(deleted)}[/dim]")
|
|
212
|
+
pruned_resources = core.prune_issue_resources(issues_root, issue_id, force, project_root)
|
|
156
213
|
except Exception as e:
|
|
157
|
-
|
|
214
|
+
OutputManager.error(f"Prune Error: {e}")
|
|
158
215
|
raise typer.Exit(code=1)
|
|
159
216
|
|
|
217
|
+
OutputManager.print({
|
|
218
|
+
"issue": issue,
|
|
219
|
+
"status": "submitted",
|
|
220
|
+
"report": report_status,
|
|
221
|
+
"pruned": pruned_resources
|
|
222
|
+
})
|
|
223
|
+
|
|
160
224
|
except Exception as e:
|
|
161
|
-
|
|
225
|
+
OutputManager.error(str(e))
|
|
162
226
|
raise typer.Exit(code=1)
|
|
163
227
|
|
|
164
228
|
@app.command("close")
|
|
@@ -168,86 +232,114 @@ def move_close(
|
|
|
168
232
|
prune: bool = typer.Option(False, "--prune", help="Delete branch/worktree after close"),
|
|
169
233
|
force: bool = typer.Option(False, "--force", help="Force delete branch/worktree"),
|
|
170
234
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
235
|
+
json: AgentOutput = False,
|
|
171
236
|
):
|
|
172
237
|
"""Close issue."""
|
|
173
238
|
config = get_config()
|
|
174
239
|
issues_root = _resolve_issues_root(config, root)
|
|
175
240
|
project_root = _resolve_project_root(config)
|
|
241
|
+
|
|
242
|
+
# Pre-flight check for interactive guidance (Requirement FEAT-0082 #6)
|
|
243
|
+
if solution is None:
|
|
244
|
+
valid_solutions = [e.value for e in IssueSolution]
|
|
245
|
+
OutputManager.error(f"Closing an issue requires a solution. Options: {', '.join(valid_solutions)}")
|
|
246
|
+
raise typer.Exit(code=1)
|
|
247
|
+
|
|
176
248
|
try:
|
|
177
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.CLOSED, solution=solution)
|
|
178
|
-
console.print(f"[dim]✔[/dim] Issue [bold]{issue_id}[/bold] closed.")
|
|
249
|
+
issue = core.update_issue(issues_root, issue_id, status=IssueStatus.CLOSED, solution=solution)
|
|
179
250
|
|
|
251
|
+
pruned_resources = []
|
|
180
252
|
if prune:
|
|
181
253
|
try:
|
|
182
|
-
|
|
183
|
-
if deleted:
|
|
184
|
-
console.print(f"[dim]✔ Pruned resources: {', '.join(deleted)}[/dim]")
|
|
254
|
+
pruned_resources = core.prune_issue_resources(issues_root, issue_id, force, project_root)
|
|
185
255
|
except Exception as e:
|
|
186
|
-
|
|
256
|
+
OutputManager.error(f"Prune Error: {e}")
|
|
187
257
|
raise typer.Exit(code=1)
|
|
188
258
|
|
|
259
|
+
OutputManager.print({
|
|
260
|
+
"issue": issue,
|
|
261
|
+
"status": "closed",
|
|
262
|
+
"pruned": pruned_resources
|
|
263
|
+
})
|
|
264
|
+
|
|
189
265
|
except Exception as e:
|
|
190
|
-
|
|
266
|
+
OutputManager.error(str(e))
|
|
191
267
|
raise typer.Exit(code=1)
|
|
192
268
|
|
|
193
269
|
@backlog_app.command("push")
|
|
194
270
|
def push(
|
|
195
271
|
issue_id: str = typer.Argument(..., help="Issue ID to push to backlog"),
|
|
196
272
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
273
|
+
json: AgentOutput = False,
|
|
197
274
|
):
|
|
198
275
|
"""Push issue to backlog."""
|
|
199
276
|
config = get_config()
|
|
200
277
|
issues_root = _resolve_issues_root(config, root)
|
|
201
278
|
try:
|
|
202
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.BACKLOG)
|
|
203
|
-
|
|
279
|
+
issue = core.update_issue(issues_root, issue_id, status=IssueStatus.BACKLOG)
|
|
280
|
+
OutputManager.print({
|
|
281
|
+
"issue": issue,
|
|
282
|
+
"status": "pushed_to_backlog"
|
|
283
|
+
})
|
|
204
284
|
except Exception as e:
|
|
205
|
-
|
|
285
|
+
OutputManager.error(str(e))
|
|
206
286
|
raise typer.Exit(code=1)
|
|
207
287
|
|
|
208
288
|
@backlog_app.command("pull")
|
|
209
289
|
def pull(
|
|
210
290
|
issue_id: str = typer.Argument(..., help="Issue ID to pull from backlog"),
|
|
211
291
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
292
|
+
json: AgentOutput = False,
|
|
212
293
|
):
|
|
213
|
-
"""Pull issue from backlog (Open &
|
|
294
|
+
"""Pull issue from backlog (Open & Draft)."""
|
|
214
295
|
config = get_config()
|
|
215
296
|
issues_root = _resolve_issues_root(config, root)
|
|
216
297
|
try:
|
|
217
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.
|
|
218
|
-
|
|
298
|
+
issue = core.update_issue(issues_root, issue_id, status=IssueStatus.OPEN, stage=IssueStage.DRAFT)
|
|
299
|
+
OutputManager.print({
|
|
300
|
+
"issue": issue,
|
|
301
|
+
"status": "pulled_from_backlog"
|
|
302
|
+
})
|
|
219
303
|
except Exception as e:
|
|
220
|
-
|
|
304
|
+
OutputManager.error(str(e))
|
|
221
305
|
raise typer.Exit(code=1)
|
|
222
306
|
|
|
223
307
|
@app.command("cancel")
|
|
224
308
|
def cancel(
|
|
225
309
|
issue_id: str = typer.Argument(..., help="Issue ID to cancel"),
|
|
226
310
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
311
|
+
json: AgentOutput = False,
|
|
227
312
|
):
|
|
228
313
|
"""Cancel issue."""
|
|
229
314
|
config = get_config()
|
|
230
315
|
issues_root = _resolve_issues_root(config, root)
|
|
231
316
|
try:
|
|
232
|
-
core.update_issue(issues_root, issue_id, status=IssueStatus.CLOSED, solution=IssueSolution.CANCELLED)
|
|
233
|
-
|
|
317
|
+
issue = core.update_issue(issues_root, issue_id, status=IssueStatus.CLOSED, solution=IssueSolution.CANCELLED)
|
|
318
|
+
OutputManager.print({
|
|
319
|
+
"issue": issue,
|
|
320
|
+
"status": "cancelled"
|
|
321
|
+
})
|
|
234
322
|
except Exception as e:
|
|
235
|
-
|
|
323
|
+
OutputManager.error(str(e))
|
|
236
324
|
raise typer.Exit(code=1)
|
|
237
325
|
|
|
238
326
|
@app.command("delete")
|
|
239
327
|
def delete(
|
|
240
328
|
issue_id: str = typer.Argument(..., help="Issue ID to delete"),
|
|
241
329
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
330
|
+
json: AgentOutput = False,
|
|
242
331
|
):
|
|
243
332
|
"""Physically remove an issue file."""
|
|
244
333
|
config = get_config()
|
|
245
334
|
issues_root = _resolve_issues_root(config, root)
|
|
246
335
|
try:
|
|
247
336
|
core.delete_issue_file(issues_root, issue_id)
|
|
248
|
-
|
|
337
|
+
OutputManager.print({
|
|
338
|
+
"id": issue_id,
|
|
339
|
+
"status": "deleted"
|
|
340
|
+
})
|
|
249
341
|
except Exception as e:
|
|
250
|
-
|
|
342
|
+
OutputManager.error(str(e))
|
|
251
343
|
raise typer.Exit(code=1)
|
|
252
344
|
|
|
253
345
|
@app.command("move")
|
|
@@ -256,6 +348,7 @@ def move(
|
|
|
256
348
|
target: str = typer.Option(..., "--to", help="Target project directory (e.g., ../OtherProject)"),
|
|
257
349
|
renumber: bool = typer.Option(False, "--renumber", help="Automatically renumber on ID conflict"),
|
|
258
350
|
root: Optional[str] = typer.Option(None, "--root", help="Override source issues root directory"),
|
|
351
|
+
json: AgentOutput = False,
|
|
259
352
|
):
|
|
260
353
|
"""Move an issue to another project."""
|
|
261
354
|
config = get_config()
|
|
@@ -270,7 +363,7 @@ def move(
|
|
|
270
363
|
elif target_path.name == "Issues" and target_path.exists():
|
|
271
364
|
target_issues_root = target_path
|
|
272
365
|
else:
|
|
273
|
-
|
|
366
|
+
OutputManager.error("Target path must be a project root with 'Issues' directory or an 'Issues' directory itself.")
|
|
274
367
|
raise typer.Exit(code=1)
|
|
275
368
|
|
|
276
369
|
try:
|
|
@@ -286,32 +379,37 @@ def move(
|
|
|
286
379
|
except ValueError:
|
|
287
380
|
rel_path = new_path
|
|
288
381
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
382
|
+
OutputManager.print({
|
|
383
|
+
"issue": updated_meta,
|
|
384
|
+
"new_path": str(rel_path),
|
|
385
|
+
"status": "moved",
|
|
386
|
+
"renumbered": updated_meta.id != issue_id
|
|
387
|
+
})
|
|
295
388
|
|
|
296
389
|
except FileNotFoundError as e:
|
|
297
|
-
|
|
390
|
+
OutputManager.error(str(e))
|
|
298
391
|
raise typer.Exit(code=1)
|
|
299
392
|
except ValueError as e:
|
|
300
|
-
|
|
393
|
+
OutputManager.error(str(e))
|
|
301
394
|
raise typer.Exit(code=1)
|
|
302
395
|
except Exception as e:
|
|
303
|
-
|
|
396
|
+
OutputManager.error(str(e))
|
|
304
397
|
raise typer.Exit(code=1)
|
|
305
398
|
|
|
306
399
|
@app.command("board")
|
|
307
400
|
def board(
|
|
308
401
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
402
|
+
json: AgentOutput = False,
|
|
309
403
|
):
|
|
310
404
|
"""Visualize issues in a Kanban board."""
|
|
311
405
|
config = get_config()
|
|
312
406
|
issues_root = _resolve_issues_root(config, root)
|
|
313
407
|
|
|
314
408
|
board_data = core.get_board_data(issues_root)
|
|
409
|
+
|
|
410
|
+
if OutputManager.is_agent_mode():
|
|
411
|
+
OutputManager.print(board_data)
|
|
412
|
+
return
|
|
315
413
|
|
|
316
414
|
from rich.columns import Columns
|
|
317
415
|
from rich.console import RenderableType
|
|
@@ -319,7 +417,7 @@ def board(
|
|
|
319
417
|
columns: List[RenderableType] = []
|
|
320
418
|
|
|
321
419
|
stage_titles = {
|
|
322
|
-
"
|
|
420
|
+
"draft": "[bold white]DRAFT[/bold white]",
|
|
323
421
|
"doing": "[bold yellow]DOING[/bold yellow]",
|
|
324
422
|
"review": "[bold cyan]REVIEW[/bold cyan]",
|
|
325
423
|
"done": "[bold green]DONE[/bold green]"
|
|
@@ -364,6 +462,7 @@ def list_cmd(
|
|
|
364
462
|
stage: Optional[IssueStage] = typer.Option(None, "--stage", help="Filter by stage"),
|
|
365
463
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
366
464
|
workspace: bool = typer.Option(False, "--workspace", "-w", help="Include issues from workspace members"),
|
|
465
|
+
json: AgentOutput = False,
|
|
367
466
|
):
|
|
368
467
|
"""List issues in a table format with filtering."""
|
|
369
468
|
config = get_config()
|
|
@@ -371,7 +470,7 @@ def list_cmd(
|
|
|
371
470
|
|
|
372
471
|
# Validation
|
|
373
472
|
if status and status.lower() not in ["open", "closed", "backlog", "all"]:
|
|
374
|
-
|
|
473
|
+
OutputManager.error(f"Invalid status: {status}. Use open, closed, backlog or all.")
|
|
375
474
|
raise typer.Exit(code=1)
|
|
376
475
|
|
|
377
476
|
target_status = status.lower() if status else "open"
|
|
@@ -395,12 +494,13 @@ def list_cmd(
|
|
|
395
494
|
|
|
396
495
|
filtered.append(i)
|
|
397
496
|
|
|
398
|
-
# Sort: Updated Descending
|
|
399
|
-
filtered.append(i)
|
|
400
|
-
|
|
401
497
|
# Sort: Updated Descending
|
|
402
498
|
filtered.sort(key=lambda x: x.updated_at, reverse=True)
|
|
403
499
|
|
|
500
|
+
if OutputManager.is_agent_mode():
|
|
501
|
+
OutputManager.print(filtered)
|
|
502
|
+
return
|
|
503
|
+
|
|
404
504
|
# Render
|
|
405
505
|
_render_issues_table(filtered, title=f"Issues ({len(filtered)})")
|
|
406
506
|
|
|
@@ -448,6 +548,7 @@ def _render_issues_table(issues: List[IssueMetadata], title: str = "Issues"):
|
|
|
448
548
|
def query_cmd(
|
|
449
549
|
query: str = typer.Argument(..., help="Search query (e.g. '+bug -ui' or 'login')"),
|
|
450
550
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
551
|
+
json: AgentOutput = False,
|
|
451
552
|
):
|
|
452
553
|
"""
|
|
453
554
|
Search issues using advanced syntax.
|
|
@@ -468,6 +569,10 @@ def query_cmd(
|
|
|
468
569
|
# For now, updated at descending is useful.
|
|
469
570
|
results.sort(key=lambda x: x.updated_at, reverse=True)
|
|
470
571
|
|
|
572
|
+
if OutputManager.is_agent_mode():
|
|
573
|
+
OutputManager.print(results)
|
|
574
|
+
return
|
|
575
|
+
|
|
471
576
|
_render_issues_table(results, title=f"Search Results for '{query}' ({len(results)})")
|
|
472
577
|
|
|
473
578
|
@app.command("scope")
|
|
@@ -477,6 +582,7 @@ def scope(
|
|
|
477
582
|
recursive: bool = typer.Option(False, "--recursive", "-r", help="Recursively scan subdirectories"),
|
|
478
583
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
479
584
|
workspace: bool = typer.Option(False, "--workspace", "-w", help="Include issues from workspace members"),
|
|
585
|
+
json: AgentOutput = False,
|
|
480
586
|
):
|
|
481
587
|
"""Show progress tree."""
|
|
482
588
|
config = get_config()
|
|
@@ -494,6 +600,10 @@ def scope(
|
|
|
494
600
|
|
|
495
601
|
issues = filtered_issues
|
|
496
602
|
|
|
603
|
+
if OutputManager.is_agent_mode():
|
|
604
|
+
OutputManager.print(issues)
|
|
605
|
+
return
|
|
606
|
+
|
|
497
607
|
tree = Tree(f"[bold blue]Monoco Issue Scope[/bold blue]")
|
|
498
608
|
epics = sorted([i for i in issues if i.type == IssueType.EPIC], key=lambda x: x.id)
|
|
499
609
|
stories = [i for i in issues if i.type == IssueType.FEATURE]
|
|
@@ -515,13 +625,21 @@ def scope(
|
|
|
515
625
|
@app.command("lint")
|
|
516
626
|
def lint(
|
|
517
627
|
recursive: bool = typer.Option(False, "--recursive", "-r", help="Recursively scan subdirectories"),
|
|
628
|
+
fix: bool = typer.Option(False, "--fix", help="Attempt to automatically fix issues (e.g. missing headings)"),
|
|
629
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format (table, json)"),
|
|
630
|
+
file: Optional[str] = typer.Option(None, "--file", help="Validate a single file instead of scanning the entire workspace"),
|
|
518
631
|
root: Optional[str] = typer.Option(None, "--root", help="Override issues root directory"),
|
|
632
|
+
json: AgentOutput = False,
|
|
519
633
|
):
|
|
520
634
|
"""Verify the integrity of the Issues directory (declarative check)."""
|
|
521
635
|
from . import linter
|
|
522
636
|
config = get_config()
|
|
523
637
|
issues_root = _resolve_issues_root(config, root)
|
|
524
|
-
|
|
638
|
+
|
|
639
|
+
if OutputManager.is_agent_mode():
|
|
640
|
+
format = "json"
|
|
641
|
+
|
|
642
|
+
linter.run_lint(issues_root, recursive=recursive, fix=fix, format=format, file_path=file)
|
|
525
643
|
|
|
526
644
|
def _resolve_issues_root(config, cli_root: Optional[str]) -> Path:
|
|
527
645
|
"""
|
|
@@ -547,7 +665,7 @@ def _resolve_issues_root(config, cli_root: Optional[str]) -> Path:
|
|
|
547
665
|
# We need to detect if we are in a Workspace Root with multiple projects
|
|
548
666
|
cwd = Path.cwd()
|
|
549
667
|
|
|
550
|
-
# If CWD is NOT a project root (no monoco
|
|
668
|
+
# If CWD is NOT a project root (no .monoco/), scan for subprojects
|
|
551
669
|
if not is_project_root(cwd):
|
|
552
670
|
subprojects = find_projects(cwd)
|
|
553
671
|
if len(subprojects) > 1:
|