cowork-flow 0.0.5 → 0.0.8
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 -1
- package/package.json +3 -2
- package/template/.agent/skills/start/SKILL.md +45 -390
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/agent-team/agents.yaml +78 -4
- package/template/.cowork-flow/run +10 -97
- package/template/.cowork-flow/run.cmd +12 -122
- package/template/.cowork-flow/scripts/agent_team.py +84 -8
- package/template/.cowork-flow/scripts/change.py +68 -15
- package/template/.cowork-flow/scripts/common/agent_team.py +250 -24
- package/template/.cowork-flow/scripts/common/archive_utils.py +72 -0
- package/template/.cowork-flow/scripts/common/task_utils.py +5 -18
- package/template/.cowork-flow/scripts/run.py +92 -0
- package/template/.cowork-flow/workflow.md +78 -21
- package/template/AGENTS.md +0 -14
|
@@ -3,30 +3,8 @@ set -u
|
|
|
3
3
|
|
|
4
4
|
MIN_PYTHON_LABEL="Python 3.8+"
|
|
5
5
|
VERSION_CHECK='import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)'
|
|
6
|
-
|
|
7
6
|
WORKFLOW_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd) || exit 1
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
print_usage() {
|
|
11
|
-
cat <<'EOF'
|
|
12
|
-
用法:
|
|
13
|
-
./.cowork-flow/run <command> [args...]
|
|
14
|
-
./.cowork-flow/run python [python-args...]
|
|
15
|
-
|
|
16
|
-
常用 command:
|
|
17
|
-
resume
|
|
18
|
-
task
|
|
19
|
-
change
|
|
20
|
-
get-context
|
|
21
|
-
get-developer
|
|
22
|
-
init-developer
|
|
23
|
-
add-session
|
|
24
|
-
agent-team
|
|
25
|
-
|
|
26
|
-
解释器选择顺序:
|
|
27
|
-
COWORK_FLOW_PYTHON -> PYTHON -> python3 -> python -> py -3
|
|
28
|
-
EOF
|
|
29
|
-
}
|
|
7
|
+
RUNNER_SCRIPT="$WORKFLOW_DIR/scripts/run.py"
|
|
30
8
|
|
|
31
9
|
candidate_is_valid() {
|
|
32
10
|
candidate_cmd=$1
|
|
@@ -51,8 +29,8 @@ select_explicit_python() {
|
|
|
51
29
|
return 0
|
|
52
30
|
fi
|
|
53
31
|
|
|
54
|
-
echo "
|
|
55
|
-
echo "
|
|
32
|
+
echo "Error: $env_name does not point to a usable ${MIN_PYTHON_LABEL} interpreter: $env_value" >&2
|
|
33
|
+
echo "Set $env_name to an executable Python 3.8+ interpreter path." >&2
|
|
56
34
|
exit 127
|
|
57
35
|
}
|
|
58
36
|
|
|
@@ -86,81 +64,16 @@ select_python() {
|
|
|
86
64
|
return 0
|
|
87
65
|
fi
|
|
88
66
|
|
|
89
|
-
echo "
|
|
90
|
-
echo "
|
|
91
|
-
echo "
|
|
67
|
+
echo "Error: ${MIN_PYTHON_LABEL} is required, but no usable interpreter was found." >&2
|
|
68
|
+
echo "Tried: COWORK_FLOW_PYTHON, PYTHON, python3, python, py -3." >&2
|
|
69
|
+
echo "Set COWORK_FLOW_PYTHON=/path/to/python3 and retry." >&2
|
|
92
70
|
exit 127
|
|
93
71
|
}
|
|
94
72
|
|
|
95
|
-
|
|
96
|
-
select_python
|
|
97
|
-
|
|
98
|
-
if [ -n "$SELECTED_PYTHON_ARG" ]; then
|
|
99
|
-
exec "$SELECTED_PYTHON" "$SELECTED_PYTHON_ARG" "$@"
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
exec "$SELECTED_PYTHON" "$@"
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
run_script() {
|
|
106
|
-
script_path=$1
|
|
107
|
-
shift
|
|
108
|
-
|
|
109
|
-
if [ ! -f "$script_path" ]; then
|
|
110
|
-
echo "错误:找不到脚本:$script_path" >&2
|
|
111
|
-
exit 2
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
exec_python "$script_path" "$@"
|
|
115
|
-
}
|
|
73
|
+
select_python
|
|
116
74
|
|
|
117
|
-
if [ "
|
|
118
|
-
|
|
119
|
-
exit 2
|
|
75
|
+
if [ -n "$SELECTED_PYTHON_ARG" ]; then
|
|
76
|
+
exec "$SELECTED_PYTHON" "$SELECTED_PYTHON_ARG" "$RUNNER_SCRIPT" "$@"
|
|
120
77
|
fi
|
|
121
78
|
|
|
122
|
-
|
|
123
|
-
shift
|
|
124
|
-
|
|
125
|
-
case "$command_name" in
|
|
126
|
-
-h|--help|help)
|
|
127
|
-
print_usage
|
|
128
|
-
exit 0
|
|
129
|
-
;;
|
|
130
|
-
python)
|
|
131
|
-
exec_python "$@"
|
|
132
|
-
;;
|
|
133
|
-
resume)
|
|
134
|
-
run_script "$SCRIPTS_DIR/resume.py" "$@"
|
|
135
|
-
;;
|
|
136
|
-
task)
|
|
137
|
-
run_script "$SCRIPTS_DIR/task.py" "$@"
|
|
138
|
-
;;
|
|
139
|
-
change)
|
|
140
|
-
run_script "$SCRIPTS_DIR/change.py" "$@"
|
|
141
|
-
;;
|
|
142
|
-
get-context|get_context)
|
|
143
|
-
run_script "$SCRIPTS_DIR/get_context.py" "$@"
|
|
144
|
-
;;
|
|
145
|
-
get-developer|get_developer)
|
|
146
|
-
run_script "$SCRIPTS_DIR/get_developer.py" "$@"
|
|
147
|
-
;;
|
|
148
|
-
init-developer|init_developer)
|
|
149
|
-
run_script "$SCRIPTS_DIR/init_developer.py" "$@"
|
|
150
|
-
;;
|
|
151
|
-
add-session|add_session)
|
|
152
|
-
run_script "$SCRIPTS_DIR/add_session.py" "$@"
|
|
153
|
-
;;
|
|
154
|
-
agent-team|agent_team)
|
|
155
|
-
run_script "$SCRIPTS_DIR/agent_team.py" "$@"
|
|
156
|
-
;;
|
|
157
|
-
*)
|
|
158
|
-
if [ -f "$SCRIPTS_DIR/$command_name.py" ]; then
|
|
159
|
-
run_script "$SCRIPTS_DIR/$command_name.py" "$@"
|
|
160
|
-
fi
|
|
161
|
-
|
|
162
|
-
echo "错误:未知 cowork-flow 命令:$command_name" >&2
|
|
163
|
-
print_usage >&2
|
|
164
|
-
exit 2
|
|
165
|
-
;;
|
|
166
|
-
esac
|
|
79
|
+
exec "$SELECTED_PYTHON" "$RUNNER_SCRIPT" "$@"
|
|
@@ -4,108 +4,20 @@ setlocal EnableExtensions
|
|
|
4
4
|
set "MIN_PYTHON_LABEL=Python 3.8+"
|
|
5
5
|
set "VERSION_CHECK=import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)"
|
|
6
6
|
set "WORKFLOW_DIR=%~dp0"
|
|
7
|
-
set "
|
|
7
|
+
set "RUNNER_SCRIPT=%WORKFLOW_DIR%scripts\run.py"
|
|
8
8
|
set "SELECTED_PYTHON="
|
|
9
9
|
set "SELECTED_PYTHON_ARG="
|
|
10
|
-
set "REST_ARGS="
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
set "COMMAND_NAME=%~1"
|
|
15
|
-
if not "%~2"=="" (
|
|
16
|
-
for /F "tokens=1,* delims= " %%A in ("%*") do set "REST_ARGS=%%B"
|
|
17
|
-
)
|
|
11
|
+
call :select_python
|
|
12
|
+
if errorlevel 1 exit /b %ERRORLEVEL%
|
|
18
13
|
|
|
19
|
-
if
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if /I "%COMMAND_NAME%"=="resume" goto run_resume
|
|
24
|
-
if /I "%COMMAND_NAME%"=="task" goto run_task
|
|
25
|
-
if /I "%COMMAND_NAME%"=="change" goto run_change
|
|
26
|
-
if /I "%COMMAND_NAME%"=="get-context" goto run_get_context
|
|
27
|
-
if /I "%COMMAND_NAME%"=="get_context" goto run_get_context
|
|
28
|
-
if /I "%COMMAND_NAME%"=="get-developer" goto run_get_developer
|
|
29
|
-
if /I "%COMMAND_NAME%"=="get_developer" goto run_get_developer
|
|
30
|
-
if /I "%COMMAND_NAME%"=="init-developer" goto run_init_developer
|
|
31
|
-
if /I "%COMMAND_NAME%"=="init_developer" goto run_init_developer
|
|
32
|
-
if /I "%COMMAND_NAME%"=="add-session" goto run_add_session
|
|
33
|
-
if /I "%COMMAND_NAME%"=="add_session" goto run_add_session
|
|
34
|
-
if /I "%COMMAND_NAME%"=="agent-team" goto run_agent_team
|
|
35
|
-
if /I "%COMMAND_NAME%"=="agent_team" goto run_agent_team
|
|
36
|
-
|
|
37
|
-
if exist "%SCRIPTS_DIR%\%COMMAND_NAME%.py" (
|
|
38
|
-
call :run_script "%SCRIPTS_DIR%\%COMMAND_NAME%.py"
|
|
39
|
-
exit /b %ERRORLEVEL%
|
|
14
|
+
if not "%SELECTED_PYTHON_ARG%"=="" (
|
|
15
|
+
"%SELECTED_PYTHON%" "%SELECTED_PYTHON_ARG%" "%RUNNER_SCRIPT%" %*
|
|
16
|
+
) else (
|
|
17
|
+
"%SELECTED_PYTHON%" "%RUNNER_SCRIPT%" %*
|
|
40
18
|
)
|
|
41
|
-
|
|
42
|
-
echo 错误:未知 cowork-flow 命令:%COMMAND_NAME% 1>&2
|
|
43
|
-
call :print_usage 1>&2
|
|
44
|
-
exit /b 2
|
|
45
|
-
|
|
46
|
-
:usage
|
|
47
|
-
call :print_usage
|
|
48
|
-
exit /b 0
|
|
49
|
-
|
|
50
|
-
:usage_error
|
|
51
|
-
call :print_usage 1>&2
|
|
52
|
-
exit /b 2
|
|
53
|
-
|
|
54
|
-
:run_python_passthrough
|
|
55
|
-
call :exec_python %REST_ARGS%
|
|
56
|
-
exit /b %ERRORLEVEL%
|
|
57
|
-
|
|
58
|
-
:run_resume
|
|
59
|
-
call :run_script "%SCRIPTS_DIR%\resume.py"
|
|
60
|
-
exit /b %ERRORLEVEL%
|
|
61
|
-
|
|
62
|
-
:run_task
|
|
63
|
-
call :run_script "%SCRIPTS_DIR%\task.py"
|
|
64
|
-
exit /b %ERRORLEVEL%
|
|
65
|
-
|
|
66
|
-
:run_change
|
|
67
|
-
call :run_script "%SCRIPTS_DIR%\change.py"
|
|
68
|
-
exit /b %ERRORLEVEL%
|
|
69
|
-
|
|
70
|
-
:run_get_context
|
|
71
|
-
call :run_script "%SCRIPTS_DIR%\get_context.py"
|
|
72
|
-
exit /b %ERRORLEVEL%
|
|
73
|
-
|
|
74
|
-
:run_get_developer
|
|
75
|
-
call :run_script "%SCRIPTS_DIR%\get_developer.py"
|
|
76
19
|
exit /b %ERRORLEVEL%
|
|
77
20
|
|
|
78
|
-
:run_init_developer
|
|
79
|
-
call :run_script "%SCRIPTS_DIR%\init_developer.py"
|
|
80
|
-
exit /b %ERRORLEVEL%
|
|
81
|
-
|
|
82
|
-
:run_add_session
|
|
83
|
-
call :run_script "%SCRIPTS_DIR%\add_session.py"
|
|
84
|
-
exit /b %ERRORLEVEL%
|
|
85
|
-
|
|
86
|
-
:run_agent_team
|
|
87
|
-
call :run_script "%SCRIPTS_DIR%\agent_team.py"
|
|
88
|
-
exit /b %ERRORLEVEL%
|
|
89
|
-
|
|
90
|
-
:print_usage
|
|
91
|
-
echo 用法:
|
|
92
|
-
echo .\.cowork-flow\run.cmd ^<command^> [args...]
|
|
93
|
-
echo .\.cowork-flow\run.cmd python [python-args...]
|
|
94
|
-
echo.
|
|
95
|
-
echo 常用 command:
|
|
96
|
-
echo resume
|
|
97
|
-
echo task
|
|
98
|
-
echo change
|
|
99
|
-
echo get-context
|
|
100
|
-
echo get-developer
|
|
101
|
-
echo init-developer
|
|
102
|
-
echo add-session
|
|
103
|
-
echo agent-team
|
|
104
|
-
echo.
|
|
105
|
-
echo 解释器选择顺序:
|
|
106
|
-
echo COWORK_FLOW_PYTHON -^> PYTHON -^> python3 -^> python -^> py -3
|
|
107
|
-
exit /b 0
|
|
108
|
-
|
|
109
21
|
:candidate_is_valid
|
|
110
22
|
set "CANDIDATE_CMD=%~1"
|
|
111
23
|
set "CANDIDATE_ARG=%~2"
|
|
@@ -133,8 +45,8 @@ if not errorlevel 1 (
|
|
|
133
45
|
exit /b 0
|
|
134
46
|
)
|
|
135
47
|
|
|
136
|
-
echo
|
|
137
|
-
echo
|
|
48
|
+
echo Error: %ENV_NAME% does not point to a usable %MIN_PYTHON_LABEL% interpreter: %ENV_VALUE% 1>&2
|
|
49
|
+
echo Set %ENV_NAME% to an executable Python 3.8+ interpreter path. 1>&2
|
|
138
50
|
exit /b 127
|
|
139
51
|
|
|
140
52
|
:select_python
|
|
@@ -170,29 +82,7 @@ if not errorlevel 1 (
|
|
|
170
82
|
exit /b 0
|
|
171
83
|
)
|
|
172
84
|
|
|
173
|
-
echo
|
|
174
|
-
echo
|
|
175
|
-
echo
|
|
85
|
+
echo Error: %MIN_PYTHON_LABEL% is required, but no usable interpreter was found. 1>&2
|
|
86
|
+
echo Tried: COWORK_FLOW_PYTHON, PYTHON, python3, python, py -3. 1>&2
|
|
87
|
+
echo Set COWORK_FLOW_PYTHON=C:\Path\To\python.exe and retry. 1>&2
|
|
176
88
|
exit /b 127
|
|
177
|
-
|
|
178
|
-
:exec_python
|
|
179
|
-
call :select_python
|
|
180
|
-
if errorlevel 1 exit /b %ERRORLEVEL%
|
|
181
|
-
|
|
182
|
-
if not "%SELECTED_PYTHON_ARG%"=="" (
|
|
183
|
-
"%SELECTED_PYTHON%" "%SELECTED_PYTHON_ARG%" %*
|
|
184
|
-
) else (
|
|
185
|
-
"%SELECTED_PYTHON%" %*
|
|
186
|
-
)
|
|
187
|
-
exit /b %ERRORLEVEL%
|
|
188
|
-
|
|
189
|
-
:run_script
|
|
190
|
-
set "SCRIPT_PATH=%~1"
|
|
191
|
-
|
|
192
|
-
if not exist "%SCRIPT_PATH%" (
|
|
193
|
-
echo 错误:找不到脚本:%SCRIPT_PATH% 1>&2
|
|
194
|
-
exit /b 2
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
call :exec_python "%SCRIPT_PATH%" %REST_ARGS%
|
|
198
|
-
exit /b %ERRORLEVEL%
|
|
@@ -15,6 +15,7 @@ from common.agent_team import (
|
|
|
15
15
|
build_initial_metrics,
|
|
16
16
|
build_initial_status,
|
|
17
17
|
parse_plan,
|
|
18
|
+
load_agent_registry,
|
|
18
19
|
render_assignment_prompt,
|
|
19
20
|
render_dispatch_plan,
|
|
20
21
|
write_json,
|
|
@@ -31,7 +32,7 @@ default_adapter: codex
|
|
|
31
32
|
|
|
32
33
|
agents:
|
|
33
34
|
implementer:
|
|
34
|
-
|
|
35
|
+
agent_type: worker
|
|
35
36
|
capabilities:
|
|
36
37
|
- implementation
|
|
37
38
|
- test-writing
|
|
@@ -45,9 +46,51 @@ agents:
|
|
|
45
46
|
- "test/**"
|
|
46
47
|
risk_limits:
|
|
47
48
|
max_parallel_write_conflicts: 0
|
|
49
|
+
prompt: |
|
|
50
|
+
Implement the smallest change that satisfies the approved plan and tests.
|
|
51
|
+
Write or update focused regression tests first, keep edits scoped, and report exact verification commands.
|
|
52
|
+
|
|
53
|
+
tester:
|
|
54
|
+
agent_type: worker
|
|
55
|
+
capabilities:
|
|
56
|
+
- test-writing
|
|
57
|
+
- verification
|
|
58
|
+
preferred_task_types:
|
|
59
|
+
- test
|
|
60
|
+
- verification
|
|
61
|
+
file_patterns:
|
|
62
|
+
- "tests/**"
|
|
63
|
+
- "test/**"
|
|
64
|
+
- "src/**"
|
|
65
|
+
- "template/**"
|
|
66
|
+
risk_limits:
|
|
67
|
+
max_parallel_write_conflicts: 0
|
|
68
|
+
prompt: |
|
|
69
|
+
Design tests that fail for the broken behavior and pass for the intended behavior.
|
|
70
|
+
Prefer clear behavior assertions over implementation details, and include the command that proves the result.
|
|
71
|
+
|
|
72
|
+
debugger:
|
|
73
|
+
agent_type: worker
|
|
74
|
+
capabilities:
|
|
75
|
+
- debugging
|
|
76
|
+
- implementation
|
|
77
|
+
- verification
|
|
78
|
+
preferred_task_types:
|
|
79
|
+
- bugfix
|
|
80
|
+
- code
|
|
81
|
+
file_patterns:
|
|
82
|
+
- "src/**"
|
|
83
|
+
- "template/**"
|
|
84
|
+
- "tests/**"
|
|
85
|
+
- "test/**"
|
|
86
|
+
risk_limits:
|
|
87
|
+
max_parallel_write_conflicts: 0
|
|
88
|
+
prompt: |
|
|
89
|
+
Find the root cause before changing code. Reproduce the failure, compare with nearby working patterns,
|
|
90
|
+
then make the smallest fix and verify the original symptom.
|
|
48
91
|
|
|
49
92
|
spec-reviewer:
|
|
50
|
-
|
|
93
|
+
agent_type: default
|
|
51
94
|
capabilities:
|
|
52
95
|
- spec-review
|
|
53
96
|
- acceptance-check
|
|
@@ -61,12 +104,16 @@ agents:
|
|
|
61
104
|
- "template/**"
|
|
62
105
|
risk_limits:
|
|
63
106
|
max_parallel_write_conflicts: 0
|
|
107
|
+
prompt: |
|
|
108
|
+
Review the proposal, spec, plan, and task PRD for contradictions, missing acceptance criteria,
|
|
109
|
+
unclear scope, and behavior that is not covered by verification.
|
|
64
110
|
|
|
65
111
|
quality-reviewer:
|
|
66
|
-
|
|
112
|
+
agent_type: default
|
|
67
113
|
capabilities:
|
|
68
114
|
- code-quality-review
|
|
69
115
|
- test-review
|
|
116
|
+
- verification
|
|
70
117
|
preferred_task_types:
|
|
71
118
|
- review
|
|
72
119
|
- verification
|
|
@@ -77,14 +124,18 @@ agents:
|
|
|
77
124
|
- "test/**"
|
|
78
125
|
risk_limits:
|
|
79
126
|
max_parallel_write_conflicts: 0
|
|
127
|
+
prompt: |
|
|
128
|
+
Review the diff for correctness, maintainability, focused scope, and meaningful tests.
|
|
129
|
+
Check that verification evidence matches the behavior being claimed.
|
|
80
130
|
|
|
81
131
|
docs-agent:
|
|
82
|
-
|
|
132
|
+
agent_type: worker
|
|
83
133
|
capabilities:
|
|
84
134
|
- documentation
|
|
85
135
|
- workflow-writing
|
|
86
136
|
preferred_task_types:
|
|
87
137
|
- docs
|
|
138
|
+
- documentation
|
|
88
139
|
file_patterns:
|
|
89
140
|
- "README.md"
|
|
90
141
|
- "AGENTS.md"
|
|
@@ -92,6 +143,30 @@ agents:
|
|
|
92
143
|
- ".agent/skills/**"
|
|
93
144
|
risk_limits:
|
|
94
145
|
max_parallel_write_conflicts: 0
|
|
146
|
+
prompt: |
|
|
147
|
+
Write concise project documentation that reflects actual commands, files, and workflow behavior.
|
|
148
|
+
Avoid aspirational text that is not backed by the implementation.
|
|
149
|
+
|
|
150
|
+
release-reviewer:
|
|
151
|
+
agent_type: default
|
|
152
|
+
capabilities:
|
|
153
|
+
- release-review
|
|
154
|
+
- acceptance-check
|
|
155
|
+
- verification
|
|
156
|
+
preferred_task_types:
|
|
157
|
+
- release
|
|
158
|
+
- review
|
|
159
|
+
file_patterns:
|
|
160
|
+
- "package.json"
|
|
161
|
+
- "package-lock.json"
|
|
162
|
+
- "scripts/**"
|
|
163
|
+
- "README.md"
|
|
164
|
+
- ".cowork-flow/changes/**"
|
|
165
|
+
risk_limits:
|
|
166
|
+
max_parallel_write_conflicts: 0
|
|
167
|
+
prompt: |
|
|
168
|
+
Review release-facing changes for versioning, packaging, documentation, and command safety.
|
|
169
|
+
Confirm that generated artifacts and publish steps are intentional.
|
|
95
170
|
""",
|
|
96
171
|
"adapters.yaml": """# Agent Team Adapters
|
|
97
172
|
|
|
@@ -309,7 +384,8 @@ def cmd_prepare(args: argparse.Namespace) -> int:
|
|
|
309
384
|
):
|
|
310
385
|
directory.mkdir(parents=True, exist_ok=True)
|
|
311
386
|
|
|
312
|
-
|
|
387
|
+
registry = load_agent_registry(_agent_team_config_dir(repo_root) / "agents.yaml")
|
|
388
|
+
dispatch_plan = build_dispatch_plan(tasks, registry)
|
|
313
389
|
(runtime_dir / "dispatch-plan.yaml").write_text(
|
|
314
390
|
render_dispatch_plan(dispatch_plan),
|
|
315
391
|
encoding="utf-8",
|
|
@@ -317,9 +393,9 @@ def cmd_prepare(args: argparse.Namespace) -> int:
|
|
|
317
393
|
write_json(runtime_dir / "status.json", build_initial_status(dispatch_plan))
|
|
318
394
|
write_json(runtime_dir / "metrics.json", build_initial_metrics(dispatch_plan))
|
|
319
395
|
write_json(
|
|
320
|
-
adapters_dir / "
|
|
396
|
+
adapters_dir / f"{dispatch_plan['adapter']}.json",
|
|
321
397
|
{
|
|
322
|
-
"adapter": "
|
|
398
|
+
"adapter": dispatch_plan["adapter"],
|
|
323
399
|
"mode": "coordinator-dispatched",
|
|
324
400
|
"assignmentCount": len(dispatch_plan["assignments"]),
|
|
325
401
|
},
|
|
@@ -371,7 +447,7 @@ def cmd_next(args: argparse.Namespace) -> int:
|
|
|
371
447
|
for assignment_id, assignment in ready:
|
|
372
448
|
print(
|
|
373
449
|
f"{assignment_id}\trole={assignment['role']}\t"
|
|
374
|
-
f"agent={assignment['recommended_agent']}\
|
|
450
|
+
f"agent={assignment['recommended_agent']}\tagent_type={assignment['agent_type']}"
|
|
375
451
|
)
|
|
376
452
|
return 0
|
|
377
453
|
|
|
@@ -6,12 +6,18 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import re
|
|
9
|
-
import shutil
|
|
10
9
|
import sys
|
|
11
10
|
from datetime import datetime
|
|
12
11
|
from pathlib import Path
|
|
13
12
|
|
|
14
|
-
from common.
|
|
13
|
+
from common.archive_utils import archive_directory_resumable
|
|
14
|
+
from common.paths import (
|
|
15
|
+
DIR_ARCHIVE,
|
|
16
|
+
DIR_CHANGES,
|
|
17
|
+
DIR_WORKFLOW,
|
|
18
|
+
generate_task_date_prefix,
|
|
19
|
+
get_repo_root,
|
|
20
|
+
)
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
SLUG_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
@@ -107,9 +113,52 @@ def _resolve_link(repo_root: Path, base_dir: str, value: object) -> Path | None:
|
|
|
107
113
|
return raw_path
|
|
108
114
|
|
|
109
115
|
direct = repo_root / raw_path
|
|
110
|
-
|
|
116
|
+
workflow_base = Path(DIR_WORKFLOW) / base_dir
|
|
117
|
+
if direct.exists() or raw_path.parts[:2] == workflow_base.parts:
|
|
111
118
|
return direct
|
|
112
|
-
return repo_root /
|
|
119
|
+
return repo_root / workflow_base / raw_path
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _archived_task_link(repo_root: Path, task_name: str) -> str | None:
|
|
123
|
+
archive_root = repo_root / DIR_WORKFLOW / "tasks" / DIR_ARCHIVE
|
|
124
|
+
if not archive_root.is_dir():
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
matches = [path for path in archive_root.glob(f"*/{task_name}") if path.is_dir()]
|
|
128
|
+
if len(matches) != 1:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
relative = matches[0].relative_to(archive_root)
|
|
132
|
+
return str(Path(DIR_ARCHIVE) / relative).replace("\\", "/")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _normalize_task_link(repo_root: Path, value: object) -> object:
|
|
136
|
+
if not isinstance(value, str) or not value.strip():
|
|
137
|
+
return value
|
|
138
|
+
|
|
139
|
+
target = _resolve_link(repo_root, "tasks", value)
|
|
140
|
+
tasks_root = repo_root / DIR_WORKFLOW / "tasks"
|
|
141
|
+
archive_root = tasks_root / DIR_ARCHIVE
|
|
142
|
+
if target is None:
|
|
143
|
+
return value
|
|
144
|
+
|
|
145
|
+
if not target.exists():
|
|
146
|
+
return _archived_task_link(repo_root, target.name) or value
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
relative = target.resolve().relative_to(archive_root.resolve())
|
|
150
|
+
except ValueError:
|
|
151
|
+
return value
|
|
152
|
+
|
|
153
|
+
return str(Path(DIR_ARCHIVE) / relative).replace("\\", "/")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _normalize_archive_metadata(repo_root: Path, change_dir: Path) -> None:
|
|
157
|
+
metadata = _read_metadata(change_dir)
|
|
158
|
+
normalized_task = _normalize_task_link(repo_root, metadata.get("task"))
|
|
159
|
+
if normalized_task != metadata.get("task"):
|
|
160
|
+
metadata["task"] = normalized_task
|
|
161
|
+
_write_metadata(change_dir, metadata)
|
|
113
162
|
|
|
114
163
|
|
|
115
164
|
def _validate_link(repo_root: Path, metadata: dict[str, object], field: str, base_dir: str) -> str | None:
|
|
@@ -178,14 +227,15 @@ def create_change(args: argparse.Namespace) -> int:
|
|
|
178
227
|
if not _validate_slug(slug):
|
|
179
228
|
return 2
|
|
180
229
|
|
|
181
|
-
|
|
230
|
+
dir_name = f"{generate_task_date_prefix()}-{slug}"
|
|
231
|
+
change_dir = _change_dir(repo_root, dir_name)
|
|
182
232
|
if change_dir.exists():
|
|
183
|
-
print(f"Error: change already exists: {
|
|
233
|
+
print(f"Error: change already exists: {dir_name}", file=sys.stderr)
|
|
184
234
|
return 1
|
|
185
235
|
|
|
186
236
|
change_dir.mkdir(parents=True)
|
|
187
237
|
(change_dir / "proposal.md").write_text(
|
|
188
|
-
f"# {
|
|
238
|
+
f"# {dir_name}\n\nDescribe the proposed behavior change.\n",
|
|
189
239
|
encoding="utf-8",
|
|
190
240
|
)
|
|
191
241
|
(change_dir / "design.md").write_text("", encoding="utf-8")
|
|
@@ -193,7 +243,7 @@ def create_change(args: argparse.Namespace) -> int:
|
|
|
193
243
|
_write_metadata(
|
|
194
244
|
change_dir,
|
|
195
245
|
{
|
|
196
|
-
"slug":
|
|
246
|
+
"slug": dir_name,
|
|
197
247
|
"status": "draft",
|
|
198
248
|
"level": args.level,
|
|
199
249
|
"created_at": _now_iso(),
|
|
@@ -203,7 +253,7 @@ def create_change(args: argparse.Namespace) -> int:
|
|
|
203
253
|
},
|
|
204
254
|
)
|
|
205
255
|
|
|
206
|
-
print(f"created {
|
|
256
|
+
print(f"created {dir_name}")
|
|
207
257
|
return 0
|
|
208
258
|
|
|
209
259
|
|
|
@@ -215,22 +265,25 @@ def validate_command(args: argparse.Namespace) -> int:
|
|
|
215
265
|
def archive_change(args: argparse.Namespace) -> int:
|
|
216
266
|
repo_root = get_repo_root()
|
|
217
267
|
slug = args.slug
|
|
268
|
+
source = _change_dir(repo_root, slug)
|
|
269
|
+
if source.is_dir():
|
|
270
|
+
_normalize_archive_metadata(repo_root, source)
|
|
271
|
+
|
|
218
272
|
if not validate_change(repo_root, slug, quiet=True):
|
|
219
273
|
return 1
|
|
220
274
|
|
|
221
|
-
source = _change_dir(repo_root, slug)
|
|
222
275
|
month = datetime.now().astimezone().strftime("%Y-%m")
|
|
223
276
|
destination = _archive_dir(repo_root) / month / slug
|
|
224
|
-
|
|
225
|
-
|
|
277
|
+
result = archive_directory_resumable(source, destination)
|
|
278
|
+
if not result.ok:
|
|
279
|
+
level = "Warning" if result.partial else "Error"
|
|
280
|
+
print(f"{level}: {result.message}", file=sys.stderr)
|
|
226
281
|
return 1
|
|
227
282
|
|
|
228
|
-
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
229
|
-
shutil.move(str(source), str(destination))
|
|
230
|
-
|
|
231
283
|
metadata = _read_metadata(destination)
|
|
232
284
|
metadata["status"] = "archived"
|
|
233
285
|
metadata["archived_at"] = _now_iso()
|
|
286
|
+
metadata["task"] = _normalize_task_link(repo_root, metadata.get("task"))
|
|
234
287
|
_write_metadata(destination, metadata)
|
|
235
288
|
|
|
236
289
|
print(f"archived {slug}")
|