cowork-flow 0.0.37 → 0.0.39
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.
- package/README.md +27 -14
- package/package.json +1 -1
- package/src/commands/init.js +39 -28
- package/src/commands/sync.js +2 -2
- package/src/lib/asset-plan.js +127 -0
- package/src/lib/copy-template.js +101 -141
- package/src/lib/host-assets.js +231 -0
- package/src/lib/plan-applier.js +329 -0
- package/src/lib/platforms.js +15 -76
- package/src/lib/skill-registry.js +503 -0
- package/template/.claude/agents/cowork-check.md +10 -1
- package/template/.claude/agents/cowork-implement.md +11 -2
- package/template/.claude/hooks/inject-workflow-state.py +51 -254
- package/template/.codex/agents/cowork-check.toml +12 -3
- package/template/.codex/agents/cowork-implement.toml +11 -2
- package/template/.codex/agents/default.toml +3 -2
- package/template/.codex/agents/explorer.toml +3 -2
- package/template/.codex/agents/worker.toml +2 -1
- package/template/.codex/hooks/inject-workflow-state.py +46 -265
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/application/__init__.py +1 -0
- package/template/.cowork-flow/scripts/application/batch_execution.py +743 -0
- package/template/.cowork-flow/scripts/application/runtime_context_service.py +365 -0
- package/template/.cowork-flow/scripts/application/task_archive.py +220 -0
- package/template/.cowork-flow/scripts/application/task_context.py +765 -0
- package/template/.cowork-flow/scripts/application/task_creation.py +201 -0
- package/template/.cowork-flow/scripts/application/task_lifecycle.py +532 -0
- package/template/.cowork-flow/scripts/application/task_tree.py +179 -0
- package/template/.cowork-flow/scripts/commands/doctor.py +78 -57
- package/template/.cowork-flow/scripts/commands/party_mode_v2.py +671 -425
- package/template/.cowork-flow/scripts/commands/subagent.py +38 -26
- package/template/.cowork-flow/scripts/commands/task.py +535 -1953
- package/template/.cowork-flow/scripts/commands/task_archive_commands.py +195 -0
- package/template/.cowork-flow/scripts/commands/task_context_commands.py +240 -0
- package/template/.cowork-flow/scripts/commands/task_create_command.py +164 -0
- package/template/.cowork-flow/scripts/commands/task_navigation.py +407 -0
- package/template/.cowork-flow/scripts/commands/task_parser.py +364 -0
- package/template/.cowork-flow/scripts/commands/task_support.py +76 -0
- package/template/.cowork-flow/scripts/commands/task_tree_commands.py +160 -0
- package/template/.cowork-flow/scripts/common/core/execution_context.py +161 -85
- package/template/.cowork-flow/scripts/common/core/host_manifest.py +393 -0
- package/template/.cowork-flow/scripts/common/core/skill_registry.py +568 -0
- package/template/.cowork-flow/scripts/common/gates/coding_standards.py +58 -7
- package/template/.cowork-flow/scripts/common/gates/gates.py +323 -46
- package/template/.cowork-flow/scripts/common/gates/models.py +196 -0
- package/template/.cowork-flow/scripts/common/gates/registry.py +79 -0
- package/template/.cowork-flow/scripts/common/gates/tdd_evidence.py +13 -13
- package/template/.cowork-flow/scripts/common/gates/validate_coding_standards.py +104 -62
- package/template/.cowork-flow/scripts/common/gates/validate_implementation.py +39 -22
- package/template/.cowork-flow/scripts/common/gates/validate_rules.py +223 -40
- package/template/.cowork-flow/scripts/common/git/git_context.py +216 -182
- package/template/.cowork-flow/scripts/common/host/__init__.py +1 -0
- package/template/.cowork-flow/scripts/common/host/workflow_state_hook.py +303 -0
- package/template/.cowork-flow/scripts/common/storage/__init__.py +1 -0
- package/template/.cowork-flow/scripts/common/storage/operation_log.py +72 -0
- package/template/.cowork-flow/scripts/common/storage/state_store.py +279 -0
- package/template/.cowork-flow/scripts/common/storage/unit_of_work.py +214 -0
- package/template/.cowork-flow/scripts/common/task/active_task.py +93 -135
- package/template/.cowork-flow/scripts/common/task/batch_mode.py +33 -120
- package/template/.cowork-flow/scripts/common/task/decision_review.py +85 -0
- package/template/.cowork-flow/scripts/common/task/readiness.py +9 -15
- package/template/.cowork-flow/scripts/common/task/task_repository.py +156 -0
- package/template/.cowork-flow/scripts/run.py +24 -0
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +8 -2
- package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +2 -4
- package/template/.cowork-flow/spec/contracts/workflow-state-templates.md +1 -1
- package/template/.cowork-flow/spec/protocols/decision-review.md +28 -0
- package/template/.cowork-flow/spec/protocols/review.md +34 -0
- package/template/.cowork-flow/spec/protocols/spec-maintenance.md +16 -0
- package/template/.cowork-flow/spec/protocols/tdd.md +30 -0
- package/template/.cowork-flow/spec/references/definition-of-done.md +4 -4
- package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
- package/template/.cowork-flow/spec/runtime/contract-registry.json +25 -0
- package/template/.cowork-flow/spec/runtime/host-assets.json +214 -0
- package/template/.cowork-flow/spec/runtime/index.md +8 -0
- package/template/.cowork-flow/spec/runtime/rules.json +61 -10
- package/template/.cowork-flow/spec/runtime/skill-registry.json +397 -0
- package/template/.cowork-flow/spec/schemas/host-assets.schema.json +173 -0
- package/template/.cowork-flow/spec/schemas/rules.schema.json +10 -1
- package/template/.cowork-flow/spec/schemas/skill-registry.schema.json +202 -0
- package/template/.cowork-flow/workflow.md +18 -13
- package/template/.opencode/agents/cowork-check.md +11 -0
- package/template/.opencode/agents/cowork-implement.md +16 -1
- package/template/.zcode/.zcode-plugin/plugin.json +1 -1
- package/template/.zcode/hooks/inject-context.js +3 -85
- package/template/.zcode/hooks/inject-context.selfcheck.mjs +6 -6
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/decision-anchor.md +8 -2
- package/template/.zcode/scaffold/.cowork-flow/spec/contracts/subagent-dispatch.md +2 -4
- package/template/.zcode/scaffold/.cowork-flow/spec/runtime/rules.json +61 -10
- package/template/.zcode/scaffold/.cowork-flow/spec/schemas/rules.schema.json +10 -1
- package/template/.zcode/scaffold/.cowork-flow/workflow.md +18 -13
- package/template/.zcode/scaffold/AGENTS.md +18 -8
- package/template/AGENTS.md +1 -1
- package/template/skills/batch-mode/SKILL.md +140 -76
- package/template/skills/brainstorming/SKILL.md +2 -2
- package/template/skills/cowork-flow/SKILL.md +42 -0
- package/template/skills/party-mode/SHARED-BOUNDARIES.md +1 -1
- package/template/skills/party-mode/SKILL.md +48 -119
- package/template/.cowork-flow/scripts/common/gates/spec_validator.py +0 -53
- package/template/.cowork-flow/scripts/common/gates/validate_jsonl.py +0 -32
- package/template/skills/before-dev/SKILL.md +0 -41
- package/template/skills/check/SKILL.md +0 -53
- package/template/skills/check/simplification-guide.md +0 -47
- package/template/skills/continue/SKILL.md +0 -29
- package/template/skills/doubt-review/SKILL.md +0 -118
- package/template/skills/finish-work/SKILL.md +0 -32
- package/template/skills/party-mode-v2/SKILL.md +0 -75
- package/template/skills/start/SKILL.md +0 -78
- package/template/skills/tdd/SKILL.md +0 -74
- package/template/skills/update-spec/SKILL.md +0 -39
- package/template/skills/using-cowork-flow/SKILL.md +0 -80
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Argument parser and usage rendering for task commands."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from common.core.execution_context import (
|
|
10
|
+
build_internal_execution_context_parser,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _add_create_command(subparsers: argparse._SubParsersAction) -> None:
|
|
15
|
+
create = subparsers.add_parser("create", help="Create new task")
|
|
16
|
+
create.add_argument("title", help="Task title")
|
|
17
|
+
create.add_argument("--slug", "-s", help="Task slug")
|
|
18
|
+
create.add_argument("--assignee", "-a", help="Assignee developer")
|
|
19
|
+
create.add_argument(
|
|
20
|
+
"--priority",
|
|
21
|
+
"-p",
|
|
22
|
+
default="P2",
|
|
23
|
+
help="Priority (P0-P3)",
|
|
24
|
+
)
|
|
25
|
+
create.add_argument("--description", "-d", help="Task description")
|
|
26
|
+
create.add_argument(
|
|
27
|
+
"--parent",
|
|
28
|
+
help="Parent task directory (establishes subtask link)",
|
|
29
|
+
)
|
|
30
|
+
create.add_argument(
|
|
31
|
+
"--from-plan",
|
|
32
|
+
"-f",
|
|
33
|
+
help="Path to plan file (auto-generate decision-anchor skeleton)",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
USAGE_TEXT = """Task Management Script for cowork-flow workflow
|
|
38
|
+
|
|
39
|
+
Usage:
|
|
40
|
+
./.cowork-flow/run task create <title> Create new task directory
|
|
41
|
+
./.cowork-flow/run task create <title> --parent <dir> Create task as child of parent
|
|
42
|
+
./.cowork-flow/run task init-context <dir> <dev_type> Initialize jsonl files
|
|
43
|
+
./.cowork-flow/run task add-context <dir> <jsonl> <path> [reason] [--type TYPE]
|
|
44
|
+
Add entry to jsonl
|
|
45
|
+
./.cowork-flow/run task add-planned-file <dir> <jsonl> <path> [reason]
|
|
46
|
+
Add planned file entry
|
|
47
|
+
./.cowork-flow/run task validate <dir> Validate jsonl files
|
|
48
|
+
./.cowork-flow/run task list-context <dir> List jsonl entries
|
|
49
|
+
./.cowork-flow/run task start <dir> Set active session task
|
|
50
|
+
./.cowork-flow/run task review [dir] Mark task ready for check
|
|
51
|
+
./.cowork-flow/run task complete [dir] Mark task completed
|
|
52
|
+
./.cowork-flow/run task finish Clear active session task
|
|
53
|
+
./.cowork-flow/run task next [dir] [--json] [--intent I] Show next safe workflow action
|
|
54
|
+
./.cowork-flow/run task archive <task-name> Archive completed task and linked changes
|
|
55
|
+
./.cowork-flow/run task add-subtask <parent> <child> Link child task to parent
|
|
56
|
+
./.cowork-flow/run task remove-subtask <parent> <child> Unlink child from parent
|
|
57
|
+
./.cowork-flow/run task list [--mine] [--status <status>] List tasks
|
|
58
|
+
./.cowork-flow/run task list-archive [YYYY-MM] List archived tasks
|
|
59
|
+
|
|
60
|
+
Arguments:
|
|
61
|
+
dev_type: backend | frontend | fullstack | test | docs
|
|
62
|
+
|
|
63
|
+
List options:
|
|
64
|
+
--mine, -m Show only tasks assigned to current developer
|
|
65
|
+
--status, -s <s> Filter by status (planning, in_progress, review, completed)
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
./.cowork-flow/run task create "Add login feature" --slug add-login
|
|
69
|
+
./.cowork-flow/run task create "Child task" --slug child --parent .cowork-flow/tasks/01-21-parent
|
|
70
|
+
./.cowork-flow/run task init-context .cowork-flow/tasks/01-21-add-login backend
|
|
71
|
+
./.cowork-flow/run task next
|
|
72
|
+
./.cowork-flow/run task add-context <dir> implement .cowork-flow/spec/backend/auth.md "Auth guidelines"
|
|
73
|
+
./.cowork-flow/run task add-context <dir> implement src/new.py "Planned source" --type planned-file
|
|
74
|
+
./.cowork-flow/run task add-planned-file <dir> implement src/new.py "Planned source"
|
|
75
|
+
./.cowork-flow/run task start .cowork-flow/tasks/01-21-add-login
|
|
76
|
+
./.cowork-flow/run task review
|
|
77
|
+
./.cowork-flow/run task complete
|
|
78
|
+
./.cowork-flow/run task finish
|
|
79
|
+
./.cowork-flow/run task archive add-login
|
|
80
|
+
./.cowork-flow/run task add-subtask parent-task child-task
|
|
81
|
+
./.cowork-flow/run task remove-subtask parent-task child-task
|
|
82
|
+
./.cowork-flow/run task list
|
|
83
|
+
./.cowork-flow/run task list --mine
|
|
84
|
+
./.cowork-flow/run task list --mine --status in_progress
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _add_init_context_command(subparsers: argparse._SubParsersAction) -> None:
|
|
89
|
+
init_context = subparsers.add_parser(
|
|
90
|
+
"init-context",
|
|
91
|
+
help="Initialize context files",
|
|
92
|
+
)
|
|
93
|
+
init_context.add_argument("dir", help="Task directory")
|
|
94
|
+
init_context.add_argument(
|
|
95
|
+
"type",
|
|
96
|
+
help="Dev type: backend|frontend|fullstack|test|docs",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _add_add_context_command(subparsers: argparse._SubParsersAction) -> None:
|
|
101
|
+
add_context = subparsers.add_parser(
|
|
102
|
+
"add-context",
|
|
103
|
+
help="Add context entry",
|
|
104
|
+
)
|
|
105
|
+
add_context.add_argument("dir", help="Task directory")
|
|
106
|
+
add_context.add_argument(
|
|
107
|
+
"file",
|
|
108
|
+
help="JSONL file (implement|check|debug)",
|
|
109
|
+
)
|
|
110
|
+
add_context.add_argument("path", help="File path to add")
|
|
111
|
+
add_context.add_argument(
|
|
112
|
+
"reason",
|
|
113
|
+
nargs="?",
|
|
114
|
+
help="Reason for adding",
|
|
115
|
+
)
|
|
116
|
+
add_context.add_argument(
|
|
117
|
+
"--type",
|
|
118
|
+
dest="entry_type",
|
|
119
|
+
choices=("file", "directory", "planned-file"),
|
|
120
|
+
default=None,
|
|
121
|
+
help="Context entry type; planned-file may target a file not created yet",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _add_add_planned_file_command(subparsers: argparse._SubParsersAction) -> None:
|
|
126
|
+
add_planned_file = subparsers.add_parser(
|
|
127
|
+
"add-planned-file",
|
|
128
|
+
help="Add planned file context entry",
|
|
129
|
+
)
|
|
130
|
+
add_planned_file.add_argument("dir", help="Task directory")
|
|
131
|
+
add_planned_file.add_argument(
|
|
132
|
+
"file",
|
|
133
|
+
help="JSONL file (implement|check|debug)",
|
|
134
|
+
)
|
|
135
|
+
add_planned_file.add_argument("path", help="Planned file path to add")
|
|
136
|
+
add_planned_file.add_argument(
|
|
137
|
+
"reason",
|
|
138
|
+
nargs="?",
|
|
139
|
+
help="Reason for adding",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _add_context_validation_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
144
|
+
validate = subparsers.add_parser(
|
|
145
|
+
"validate",
|
|
146
|
+
help="Validate context files",
|
|
147
|
+
)
|
|
148
|
+
validate.add_argument("dir", help="Task directory")
|
|
149
|
+
|
|
150
|
+
list_context = subparsers.add_parser(
|
|
151
|
+
"list-context",
|
|
152
|
+
help="List context entries",
|
|
153
|
+
)
|
|
154
|
+
list_context.add_argument("dir", help="Task directory")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _add_context_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
158
|
+
_add_init_context_command(subparsers)
|
|
159
|
+
_add_add_context_command(subparsers)
|
|
160
|
+
_add_add_planned_file_command(subparsers)
|
|
161
|
+
_add_context_validation_commands(subparsers)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _add_start_and_batch_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
165
|
+
start = subparsers.add_parser(
|
|
166
|
+
"start",
|
|
167
|
+
help="Set active session task",
|
|
168
|
+
)
|
|
169
|
+
start.add_argument("dir", help="Task directory")
|
|
170
|
+
start.add_argument(
|
|
171
|
+
"--auto",
|
|
172
|
+
action="store_true",
|
|
173
|
+
help="Enable batch mode (requires --approved)",
|
|
174
|
+
)
|
|
175
|
+
start.add_argument(
|
|
176
|
+
"--approved",
|
|
177
|
+
action="store_true",
|
|
178
|
+
help="User has approved the plan",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
batch_resume = subparsers.add_parser(
|
|
182
|
+
"batch-resume",
|
|
183
|
+
help="Resume a paused Batch operation",
|
|
184
|
+
)
|
|
185
|
+
batch_resume.add_argument(
|
|
186
|
+
"operation_id",
|
|
187
|
+
help="Batch operation id",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
batch_result = subparsers.add_parser(
|
|
191
|
+
"batch-record-result",
|
|
192
|
+
help="Record one Host action result and advance Batch",
|
|
193
|
+
)
|
|
194
|
+
batch_result.add_argument(
|
|
195
|
+
"operation_id",
|
|
196
|
+
help="Batch operation id",
|
|
197
|
+
)
|
|
198
|
+
batch_result.add_argument(
|
|
199
|
+
"--file",
|
|
200
|
+
type=Path,
|
|
201
|
+
required=True,
|
|
202
|
+
help="UTF-8 JSON Host action result",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _add_optional_task_dir(parser: argparse.ArgumentParser) -> None:
|
|
207
|
+
parser.add_argument(
|
|
208
|
+
"dir",
|
|
209
|
+
nargs="?",
|
|
210
|
+
help="Task directory or name",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _add_review_command(subparsers: argparse._SubParsersAction) -> None:
|
|
215
|
+
review = subparsers.add_parser(
|
|
216
|
+
"review",
|
|
217
|
+
help="Mark task ready for check",
|
|
218
|
+
)
|
|
219
|
+
_add_optional_task_dir(review)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _add_complete_command(subparsers: argparse._SubParsersAction) -> None:
|
|
223
|
+
complete = subparsers.add_parser(
|
|
224
|
+
"complete",
|
|
225
|
+
help="Mark task completed",
|
|
226
|
+
)
|
|
227
|
+
_add_optional_task_dir(complete)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _add_next_command(subparsers: argparse._SubParsersAction) -> None:
|
|
231
|
+
next_parser = subparsers.add_parser(
|
|
232
|
+
"next",
|
|
233
|
+
help="Show next safe workflow action",
|
|
234
|
+
)
|
|
235
|
+
_add_optional_task_dir(next_parser)
|
|
236
|
+
next_parser.add_argument(
|
|
237
|
+
"--json",
|
|
238
|
+
action="store_true",
|
|
239
|
+
help="Render the stable machine-readable navigation contract",
|
|
240
|
+
)
|
|
241
|
+
next_parser.add_argument(
|
|
242
|
+
"--intent",
|
|
243
|
+
choices=(
|
|
244
|
+
"question",
|
|
245
|
+
"clarify",
|
|
246
|
+
"plan",
|
|
247
|
+
"implement",
|
|
248
|
+
"review",
|
|
249
|
+
"debug",
|
|
250
|
+
"discuss",
|
|
251
|
+
"batch",
|
|
252
|
+
),
|
|
253
|
+
help="Classified user intent for structured routing",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _add_lifecycle_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
258
|
+
subparsers.add_parser(
|
|
259
|
+
"current",
|
|
260
|
+
help="Show active session task",
|
|
261
|
+
)
|
|
262
|
+
_add_review_command(subparsers)
|
|
263
|
+
_add_complete_command(subparsers)
|
|
264
|
+
_add_next_command(subparsers)
|
|
265
|
+
subparsers.add_parser(
|
|
266
|
+
"finish",
|
|
267
|
+
help="Clear active session task",
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _add_archive_command(subparsers: argparse._SubParsersAction) -> None:
|
|
272
|
+
archive = subparsers.add_parser("archive", help="Archive task")
|
|
273
|
+
archive.add_argument("name", help="Task name")
|
|
274
|
+
archive.add_argument(
|
|
275
|
+
"--commit",
|
|
276
|
+
action="store_true",
|
|
277
|
+
help="Auto git commit after archive",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _add_list_command(subparsers: argparse._SubParsersAction) -> None:
|
|
282
|
+
list_parser = subparsers.add_parser("list", help="List tasks")
|
|
283
|
+
list_parser.add_argument(
|
|
284
|
+
"--mine",
|
|
285
|
+
"-m",
|
|
286
|
+
action="store_true",
|
|
287
|
+
help="My tasks only",
|
|
288
|
+
)
|
|
289
|
+
list_parser.add_argument(
|
|
290
|
+
"--status",
|
|
291
|
+
"-s",
|
|
292
|
+
help="Filter by status",
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _add_subtask_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
297
|
+
add_subtask = subparsers.add_parser(
|
|
298
|
+
"add-subtask",
|
|
299
|
+
help="Link child task to parent",
|
|
300
|
+
)
|
|
301
|
+
add_subtask.add_argument(
|
|
302
|
+
"parent_dir",
|
|
303
|
+
help="Parent task directory",
|
|
304
|
+
)
|
|
305
|
+
add_subtask.add_argument(
|
|
306
|
+
"child_dir",
|
|
307
|
+
help="Child task directory",
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
remove_subtask = subparsers.add_parser(
|
|
311
|
+
"remove-subtask",
|
|
312
|
+
help="Unlink child task from parent",
|
|
313
|
+
)
|
|
314
|
+
remove_subtask.add_argument(
|
|
315
|
+
"parent_dir",
|
|
316
|
+
help="Parent task directory",
|
|
317
|
+
)
|
|
318
|
+
remove_subtask.add_argument(
|
|
319
|
+
"child_dir",
|
|
320
|
+
help="Child task directory",
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _add_list_archive_command(subparsers: argparse._SubParsersAction) -> None:
|
|
325
|
+
list_archive = subparsers.add_parser(
|
|
326
|
+
"list-archive",
|
|
327
|
+
help="List archived tasks",
|
|
328
|
+
)
|
|
329
|
+
list_archive.add_argument(
|
|
330
|
+
"month",
|
|
331
|
+
nargs="?",
|
|
332
|
+
help="Month (YYYY-MM)",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _add_archive_and_listing_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
337
|
+
_add_archive_command(subparsers)
|
|
338
|
+
_add_list_command(subparsers)
|
|
339
|
+
_add_subtask_commands(subparsers)
|
|
340
|
+
_add_list_archive_command(subparsers)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
344
|
+
parser = argparse.ArgumentParser(
|
|
345
|
+
description="Task Management Script for cowork-flow workflow",
|
|
346
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
347
|
+
parents=[build_internal_execution_context_parser()],
|
|
348
|
+
)
|
|
349
|
+
subparsers = parser.add_subparsers(
|
|
350
|
+
dest="command",
|
|
351
|
+
help="Commands",
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
_add_create_command(subparsers)
|
|
355
|
+
_add_context_commands(subparsers)
|
|
356
|
+
_add_start_and_batch_commands(subparsers)
|
|
357
|
+
_add_lifecycle_commands(subparsers)
|
|
358
|
+
_add_archive_and_listing_commands(subparsers)
|
|
359
|
+
return parser
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def show_usage() -> None:
|
|
363
|
+
"""Show usage help."""
|
|
364
|
+
print(USAGE_TEXT)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Shared delivery helpers for task commands."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shlex
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from common.core.config import get_hooks
|
|
14
|
+
from common.task.task_repository import TaskRepository
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Colors:
|
|
18
|
+
RED = "\033[0;31m"
|
|
19
|
+
GREEN = "\033[0;32m"
|
|
20
|
+
YELLOW = "\033[1;33m"
|
|
21
|
+
BLUE = "\033[0;34m"
|
|
22
|
+
CYAN = "\033[0;36m"
|
|
23
|
+
NC = "\033[0m"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def colored(text: str, color: str) -> str:
|
|
27
|
+
return f"{color}{text}{Colors.NC}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def run_hooks(event: str, task_json_path: Path, repo_root: Path) -> None:
|
|
31
|
+
"""Run configured lifecycle hooks with UTF-8 subprocess decoding."""
|
|
32
|
+
commands = get_hooks(event, repo_root)
|
|
33
|
+
if not commands:
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
env = {**os.environ, "TASK_JSON_PATH": str(task_json_path)}
|
|
37
|
+
for command in commands:
|
|
38
|
+
try:
|
|
39
|
+
result = subprocess.run(
|
|
40
|
+
shlex.split(command),
|
|
41
|
+
cwd=repo_root,
|
|
42
|
+
env=env,
|
|
43
|
+
capture_output=True,
|
|
44
|
+
text=True,
|
|
45
|
+
encoding="utf-8",
|
|
46
|
+
errors="replace",
|
|
47
|
+
)
|
|
48
|
+
if result.returncode != 0:
|
|
49
|
+
print(
|
|
50
|
+
colored(
|
|
51
|
+
f"[WARN] Hook failed ({event}): {command}",
|
|
52
|
+
Colors.YELLOW,
|
|
53
|
+
),
|
|
54
|
+
file=sys.stderr,
|
|
55
|
+
)
|
|
56
|
+
if result.stderr.strip():
|
|
57
|
+
print(f" {result.stderr.strip()}", file=sys.stderr)
|
|
58
|
+
except Exception as error:
|
|
59
|
+
print(
|
|
60
|
+
colored(
|
|
61
|
+
f"[WARN] Hook error ({event}): {command} - {error}",
|
|
62
|
+
Colors.YELLOW,
|
|
63
|
+
),
|
|
64
|
+
file=sys.stderr,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def slugify(title: str) -> str:
|
|
69
|
+
result = title.lower()
|
|
70
|
+
result = re.sub(r"[^a-z0-9]", "-", result)
|
|
71
|
+
result = re.sub(r"-+", "-", result)
|
|
72
|
+
return result.strip("-")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def resolve_task_dir(target_dir: str | Path, repo_root: Path) -> Path:
|
|
76
|
+
return TaskRepository(repo_root).resolve(target_dir)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Delivery adapters for task tree commands."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from application.task_tree import TaskTreeError, TaskTreeService
|
|
9
|
+
from commands.task_support import Colors, colored, resolve_task_dir
|
|
10
|
+
from common.core.paths import (
|
|
11
|
+
DIR_TASKS,
|
|
12
|
+
DIR_WORKFLOW,
|
|
13
|
+
get_developer,
|
|
14
|
+
get_repo_root,
|
|
15
|
+
)
|
|
16
|
+
from common.task.active_task import get_active_task
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def cmd_add_subtask(args) -> int:
|
|
20
|
+
repo_root = get_repo_root()
|
|
21
|
+
parent_dir = resolve_task_dir(args.parent_dir, repo_root)
|
|
22
|
+
child_dir = resolve_task_dir(args.child_dir, repo_root)
|
|
23
|
+
try:
|
|
24
|
+
TaskTreeService(repo_root).link(parent_dir, child_dir)
|
|
25
|
+
except TaskTreeError as error:
|
|
26
|
+
if error.code == "TASK-TREE-PARENT-LOAD-001":
|
|
27
|
+
message = f"Parent task.json not found: {args.parent_dir}"
|
|
28
|
+
elif error.code == "TASK-TREE-CHILD-LOAD-001":
|
|
29
|
+
message = f"Child task.json not found: {args.child_dir}"
|
|
30
|
+
elif error.code == "TASK-TREE-PARENT-001":
|
|
31
|
+
existing_parent = error.detail.removeprefix(
|
|
32
|
+
"child already has parent "
|
|
33
|
+
)
|
|
34
|
+
message = f"Child task already has a parent: {existing_parent}"
|
|
35
|
+
else:
|
|
36
|
+
message = "Failed to update task.json"
|
|
37
|
+
print(colored(f"Error: {message}", Colors.RED), file=sys.stderr)
|
|
38
|
+
return 1
|
|
39
|
+
print(
|
|
40
|
+
colored(
|
|
41
|
+
f"Linked: {child_dir.name} -> {parent_dir.name}",
|
|
42
|
+
Colors.GREEN,
|
|
43
|
+
),
|
|
44
|
+
file=sys.stderr,
|
|
45
|
+
)
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cmd_remove_subtask(args) -> int:
|
|
50
|
+
repo_root = get_repo_root()
|
|
51
|
+
parent_dir = resolve_task_dir(args.parent_dir, repo_root)
|
|
52
|
+
child_dir = resolve_task_dir(args.child_dir, repo_root)
|
|
53
|
+
try:
|
|
54
|
+
TaskTreeService(repo_root).unlink(parent_dir, child_dir)
|
|
55
|
+
except TaskTreeError as error:
|
|
56
|
+
if error.code == "TASK-TREE-PARENT-LOAD-001":
|
|
57
|
+
message = f"Parent task.json not found: {args.parent_dir}"
|
|
58
|
+
elif error.code == "TASK-TREE-CHILD-LOAD-001":
|
|
59
|
+
message = f"Child task.json not found: {args.child_dir}"
|
|
60
|
+
else:
|
|
61
|
+
message = "Failed to update task.json"
|
|
62
|
+
print(colored(f"Error: {message}", Colors.RED), file=sys.stderr)
|
|
63
|
+
return 1
|
|
64
|
+
print(
|
|
65
|
+
colored(
|
|
66
|
+
f"Unlinked: {child_dir.name} from {parent_dir.name}",
|
|
67
|
+
Colors.GREEN,
|
|
68
|
+
),
|
|
69
|
+
file=sys.stderr,
|
|
70
|
+
)
|
|
71
|
+
return 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def cmd_list(args) -> int:
|
|
75
|
+
repo_root = get_repo_root()
|
|
76
|
+
active_task = get_active_task(repo_root).task_path
|
|
77
|
+
developer = get_developer(repo_root)
|
|
78
|
+
filter_mine = args.mine
|
|
79
|
+
filter_status = args.status
|
|
80
|
+
|
|
81
|
+
if filter_mine:
|
|
82
|
+
if not developer:
|
|
83
|
+
print(
|
|
84
|
+
colored(
|
|
85
|
+
"Error: No developer set. Run init_developer.py first",
|
|
86
|
+
Colors.RED,
|
|
87
|
+
),
|
|
88
|
+
file=sys.stderr,
|
|
89
|
+
)
|
|
90
|
+
return 1
|
|
91
|
+
print(colored(f"My tasks (assignee: {developer}):", Colors.BLUE))
|
|
92
|
+
else:
|
|
93
|
+
print(colored("All active tasks:", Colors.BLUE))
|
|
94
|
+
print()
|
|
95
|
+
|
|
96
|
+
service = TaskTreeService(repo_root)
|
|
97
|
+
all_tasks = service.active_nodes()
|
|
98
|
+
count = 0
|
|
99
|
+
|
|
100
|
+
def print_task(dir_name: str, indent: int = 0) -> None:
|
|
101
|
+
nonlocal count
|
|
102
|
+
info = all_tasks[dir_name]
|
|
103
|
+
if filter_mine and info.assignee != developer:
|
|
104
|
+
return
|
|
105
|
+
if filter_status and info.status != filter_status:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
|
|
109
|
+
marker = (
|
|
110
|
+
f" {colored('<- active', Colors.GREEN)}"
|
|
111
|
+
if relative_path == active_task
|
|
112
|
+
else ""
|
|
113
|
+
)
|
|
114
|
+
done, total = service.children_progress(info.children, all_tasks)
|
|
115
|
+
progress = f" [{done}/{total} done]" if info.children else ""
|
|
116
|
+
prefix = " " * indent + " - "
|
|
117
|
+
if filter_mine:
|
|
118
|
+
print(f"{prefix}{dir_name}/ ({info.status}){progress}{marker}")
|
|
119
|
+
else:
|
|
120
|
+
print(
|
|
121
|
+
f"{prefix}{dir_name}/ ({info.status}){progress} "
|
|
122
|
+
f"[{colored(info.assignee, Colors.CYAN)}]{marker}"
|
|
123
|
+
)
|
|
124
|
+
count += 1
|
|
125
|
+
for child_name in info.children:
|
|
126
|
+
if child_name in all_tasks:
|
|
127
|
+
print_task(child_name, indent + 1)
|
|
128
|
+
|
|
129
|
+
for dir_name in service.root_names(all_tasks):
|
|
130
|
+
print_task(dir_name)
|
|
131
|
+
|
|
132
|
+
if count == 0:
|
|
133
|
+
print(
|
|
134
|
+
" (no tasks assigned to you)"
|
|
135
|
+
if filter_mine
|
|
136
|
+
else " (no active tasks)"
|
|
137
|
+
)
|
|
138
|
+
print()
|
|
139
|
+
print(f"Total: {count} task(s)")
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def cmd_list_archive(args) -> int:
|
|
144
|
+
repo_root = get_repo_root()
|
|
145
|
+
month = args.month
|
|
146
|
+
archives = TaskTreeService(repo_root).archived_tasks(month)
|
|
147
|
+
|
|
148
|
+
print(colored("Archived tasks:", Colors.BLUE))
|
|
149
|
+
print()
|
|
150
|
+
if month:
|
|
151
|
+
if month in archives:
|
|
152
|
+
print(f"[{month}]")
|
|
153
|
+
for task_name in archives[month]:
|
|
154
|
+
print(f" - {task_name}/")
|
|
155
|
+
else:
|
|
156
|
+
print(f" No archives for {month}")
|
|
157
|
+
else:
|
|
158
|
+
for month_name, task_names in archives.items():
|
|
159
|
+
print(f"[{month_name}] - {len(task_names)} task(s)")
|
|
160
|
+
return 0
|