cowork-flow 0.0.2 → 0.0.4
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/LICENSE +21 -0
- package/README.md +23 -11
- package/package.json +2 -2
- package/src/lib/copy-template.js +56 -2
- package/template/.agent/skills/record-session/SKILL.md +8 -8
- package/template/.agent/skills/start/SKILL.md +75 -38
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/run +162 -0
- package/template/.cowork-flow/run.cmd +191 -0
- package/template/.cowork-flow/scripts/add_session.py +2 -2
- package/template/.cowork-flow/scripts/change.py +5 -11
- package/template/.cowork-flow/scripts/common/developer.py +1 -1
- package/template/.cowork-flow/scripts/common/git_context.py +142 -5
- package/template/.cowork-flow/scripts/get_context.py +2 -2
- package/template/.cowork-flow/scripts/init_developer.py +1 -1
- package/template/.cowork-flow/scripts/resume.py +21 -0
- package/template/.cowork-flow/scripts/task.py +116 -42
- package/template/.cowork-flow/spec/guides/pre-implementation-checklist.md +1 -1
- package/template/.cowork-flow/workflow.md +43 -19
- package/template/.cowork-flow/workspace/index.md +3 -3
- package/template/.superpowers/brainstorming/SKILL.md +5 -2
- package/template/.superpowers/brainstorming/spec-document-reviewer-prompt.md +1 -1
- package/template/.superpowers/requesting-code-review/SKILL.md +1 -1
- package/template/.superpowers/subagent-driven-development/SKILL.md +1 -1
- package/template/.superpowers/writing-plans/SKILL.md +2 -2
- package/template/AGENTS.md +13 -3
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal EnableExtensions
|
|
3
|
+
|
|
4
|
+
set "MIN_PYTHON_LABEL=Python 3.8+"
|
|
5
|
+
set "VERSION_CHECK=import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)"
|
|
6
|
+
set "WORKFLOW_DIR=%~dp0"
|
|
7
|
+
set "SCRIPTS_DIR=%WORKFLOW_DIR%scripts"
|
|
8
|
+
set "SELECTED_PYTHON="
|
|
9
|
+
set "SELECTED_PYTHON_ARG="
|
|
10
|
+
set "REST_ARGS="
|
|
11
|
+
|
|
12
|
+
if "%~1"=="" goto usage_error
|
|
13
|
+
|
|
14
|
+
set "COMMAND_NAME=%~1"
|
|
15
|
+
if not "%~2"=="" (
|
|
16
|
+
for /F "tokens=1,* delims= " %%A in ("%*") do set "REST_ARGS=%%B"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if /I "%COMMAND_NAME%"=="-h" goto usage
|
|
20
|
+
if /I "%COMMAND_NAME%"=="--help" goto usage
|
|
21
|
+
if /I "%COMMAND_NAME%"=="help" goto usage
|
|
22
|
+
if /I "%COMMAND_NAME%"=="python" goto run_python_passthrough
|
|
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
|
+
|
|
35
|
+
if exist "%SCRIPTS_DIR%\%COMMAND_NAME%.py" (
|
|
36
|
+
call :run_script "%SCRIPTS_DIR%\%COMMAND_NAME%.py"
|
|
37
|
+
exit /b %ERRORLEVEL%
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
echo 错误:未知 cowork-flow 命令:%COMMAND_NAME% 1>&2
|
|
41
|
+
call :print_usage 1>&2
|
|
42
|
+
exit /b 2
|
|
43
|
+
|
|
44
|
+
:usage
|
|
45
|
+
call :print_usage
|
|
46
|
+
exit /b 0
|
|
47
|
+
|
|
48
|
+
:usage_error
|
|
49
|
+
call :print_usage 1>&2
|
|
50
|
+
exit /b 2
|
|
51
|
+
|
|
52
|
+
:run_python_passthrough
|
|
53
|
+
call :exec_python %REST_ARGS%
|
|
54
|
+
exit /b %ERRORLEVEL%
|
|
55
|
+
|
|
56
|
+
:run_resume
|
|
57
|
+
call :run_script "%SCRIPTS_DIR%\resume.py"
|
|
58
|
+
exit /b %ERRORLEVEL%
|
|
59
|
+
|
|
60
|
+
:run_task
|
|
61
|
+
call :run_script "%SCRIPTS_DIR%\task.py"
|
|
62
|
+
exit /b %ERRORLEVEL%
|
|
63
|
+
|
|
64
|
+
:run_change
|
|
65
|
+
call :run_script "%SCRIPTS_DIR%\change.py"
|
|
66
|
+
exit /b %ERRORLEVEL%
|
|
67
|
+
|
|
68
|
+
:run_get_context
|
|
69
|
+
call :run_script "%SCRIPTS_DIR%\get_context.py"
|
|
70
|
+
exit /b %ERRORLEVEL%
|
|
71
|
+
|
|
72
|
+
:run_get_developer
|
|
73
|
+
call :run_script "%SCRIPTS_DIR%\get_developer.py"
|
|
74
|
+
exit /b %ERRORLEVEL%
|
|
75
|
+
|
|
76
|
+
:run_init_developer
|
|
77
|
+
call :run_script "%SCRIPTS_DIR%\init_developer.py"
|
|
78
|
+
exit /b %ERRORLEVEL%
|
|
79
|
+
|
|
80
|
+
:run_add_session
|
|
81
|
+
call :run_script "%SCRIPTS_DIR%\add_session.py"
|
|
82
|
+
exit /b %ERRORLEVEL%
|
|
83
|
+
|
|
84
|
+
:print_usage
|
|
85
|
+
echo 用法:
|
|
86
|
+
echo .\.cowork-flow\run.cmd ^<command^> [args...]
|
|
87
|
+
echo .\.cowork-flow\run.cmd python [python-args...]
|
|
88
|
+
echo.
|
|
89
|
+
echo 常用 command:
|
|
90
|
+
echo resume
|
|
91
|
+
echo task
|
|
92
|
+
echo change
|
|
93
|
+
echo get-context
|
|
94
|
+
echo get-developer
|
|
95
|
+
echo init-developer
|
|
96
|
+
echo add-session
|
|
97
|
+
echo.
|
|
98
|
+
echo 解释器选择顺序:
|
|
99
|
+
echo COWORK_FLOW_PYTHON -^> PYTHON -^> python3 -^> python -^> py -3
|
|
100
|
+
exit /b 0
|
|
101
|
+
|
|
102
|
+
:candidate_is_valid
|
|
103
|
+
set "CANDIDATE_CMD=%~1"
|
|
104
|
+
set "CANDIDATE_ARG=%~2"
|
|
105
|
+
|
|
106
|
+
where "%CANDIDATE_CMD%" >nul 2>nul
|
|
107
|
+
if errorlevel 1 (
|
|
108
|
+
if not exist "%CANDIDATE_CMD%" exit /b 1
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if not "%CANDIDATE_ARG%"=="" (
|
|
112
|
+
"%CANDIDATE_CMD%" "%CANDIDATE_ARG%" -c "%VERSION_CHECK%" >nul 2>nul
|
|
113
|
+
) else (
|
|
114
|
+
"%CANDIDATE_CMD%" -c "%VERSION_CHECK%" >nul 2>nul
|
|
115
|
+
)
|
|
116
|
+
exit /b %ERRORLEVEL%
|
|
117
|
+
|
|
118
|
+
:select_explicit_python
|
|
119
|
+
set "ENV_NAME=%~1"
|
|
120
|
+
set "ENV_VALUE=%~2"
|
|
121
|
+
|
|
122
|
+
call :candidate_is_valid "%ENV_VALUE%" ""
|
|
123
|
+
if not errorlevel 1 (
|
|
124
|
+
set "SELECTED_PYTHON=%ENV_VALUE%"
|
|
125
|
+
set "SELECTED_PYTHON_ARG="
|
|
126
|
+
exit /b 0
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
echo 错误:%ENV_NAME% 指向的解释器不可用或版本低于 %MIN_PYTHON_LABEL%:%ENV_VALUE% 1>&2
|
|
130
|
+
echo 请设置 %ENV_NAME% 为可执行的 Python 3.8+ 解释器路径。 1>&2
|
|
131
|
+
exit /b 127
|
|
132
|
+
|
|
133
|
+
:select_python
|
|
134
|
+
set "SELECTED_PYTHON="
|
|
135
|
+
set "SELECTED_PYTHON_ARG="
|
|
136
|
+
|
|
137
|
+
if not "%COWORK_FLOW_PYTHON%"=="" (
|
|
138
|
+
call :select_explicit_python "COWORK_FLOW_PYTHON" "%COWORK_FLOW_PYTHON%"
|
|
139
|
+
exit /b %ERRORLEVEL%
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if not "%PYTHON%"=="" (
|
|
143
|
+
call :select_explicit_python "PYTHON" "%PYTHON%"
|
|
144
|
+
exit /b %ERRORLEVEL%
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
call :candidate_is_valid "python3" ""
|
|
148
|
+
if not errorlevel 1 (
|
|
149
|
+
set "SELECTED_PYTHON=python3"
|
|
150
|
+
exit /b 0
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
call :candidate_is_valid "python" ""
|
|
154
|
+
if not errorlevel 1 (
|
|
155
|
+
set "SELECTED_PYTHON=python"
|
|
156
|
+
exit /b 0
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
call :candidate_is_valid "py" "-3"
|
|
160
|
+
if not errorlevel 1 (
|
|
161
|
+
set "SELECTED_PYTHON=py"
|
|
162
|
+
set "SELECTED_PYTHON_ARG=-3"
|
|
163
|
+
exit /b 0
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
echo 错误:需要 %MIN_PYTHON_LABEL%,但未找到可用解释器。 1>&2
|
|
167
|
+
echo 已尝试:COWORK_FLOW_PYTHON、PYTHON、python3、python、py -3。 1>&2
|
|
168
|
+
echo 可设置 COWORK_FLOW_PYTHON=C:\Path\To\python.exe 后重试。 1>&2
|
|
169
|
+
exit /b 127
|
|
170
|
+
|
|
171
|
+
:exec_python
|
|
172
|
+
call :select_python
|
|
173
|
+
if errorlevel 1 exit /b %ERRORLEVEL%
|
|
174
|
+
|
|
175
|
+
if not "%SELECTED_PYTHON_ARG%"=="" (
|
|
176
|
+
"%SELECTED_PYTHON%" "%SELECTED_PYTHON_ARG%" %*
|
|
177
|
+
) else (
|
|
178
|
+
"%SELECTED_PYTHON%" %*
|
|
179
|
+
)
|
|
180
|
+
exit /b %ERRORLEVEL%
|
|
181
|
+
|
|
182
|
+
:run_script
|
|
183
|
+
set "SCRIPT_PATH=%~1"
|
|
184
|
+
|
|
185
|
+
if not exist "%SCRIPT_PATH%" (
|
|
186
|
+
echo 错误:找不到脚本:%SCRIPT_PATH% 1>&2
|
|
187
|
+
exit /b 2
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
call :exec_python "%SCRIPT_PATH%" %REST_ARGS%
|
|
191
|
+
exit /b %ERRORLEVEL%
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
Add a new session to journal file and update index.md.
|
|
5
5
|
|
|
6
6
|
Usage:
|
|
7
|
-
|
|
8
|
-
echo "content" |
|
|
7
|
+
./.cowork-flow/run add-session --title "Title" --commit "hash" --summary "Summary"
|
|
8
|
+
echo "content" | ./.cowork-flow/run add-session --title "Title" --commit "hash"
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
@@ -92,11 +92,8 @@ def _has_text(path: Path) -> bool:
|
|
|
92
92
|
return path.is_file() and bool(path.read_text(encoding="utf-8").strip())
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
if not specs_dir.is_dir():
|
|
98
|
-
return []
|
|
99
|
-
return sorted(path for path in specs_dir.rglob("spec.md") if path.is_file())
|
|
95
|
+
def _spec_file(change_dir: Path) -> Path:
|
|
96
|
+
return change_dir / "spec.md"
|
|
100
97
|
|
|
101
98
|
|
|
102
99
|
def _resolve_link(repo_root: Path, base_dir: str, value: object) -> Path | None:
|
|
@@ -151,10 +148,8 @@ def validate_change(repo_root: Path, slug: str, quiet: bool = False) -> bool:
|
|
|
151
148
|
errors.append("proposal.md is missing or empty")
|
|
152
149
|
|
|
153
150
|
documentation_only = metadata.get("documentation_only") is True
|
|
154
|
-
if not documentation_only:
|
|
155
|
-
|
|
156
|
-
if not specs or not any(_has_text(spec) for spec in specs):
|
|
157
|
-
errors.append("specs must include at least one non-empty behavior spec")
|
|
151
|
+
if not documentation_only and not _has_text(_spec_file(change_dir)):
|
|
152
|
+
errors.append("spec.md is missing or empty")
|
|
158
153
|
|
|
159
154
|
if level == "L2" and not _has_text(change_dir / "design.md"):
|
|
160
155
|
errors.append("design.md is required for L2 changes")
|
|
@@ -189,13 +184,12 @@ def create_change(args: argparse.Namespace) -> int:
|
|
|
189
184
|
return 1
|
|
190
185
|
|
|
191
186
|
change_dir.mkdir(parents=True)
|
|
192
|
-
(change_dir / "specs").mkdir()
|
|
193
187
|
(change_dir / "proposal.md").write_text(
|
|
194
188
|
f"# {slug}\n\nDescribe the proposed behavior change.\n",
|
|
195
189
|
encoding="utf-8",
|
|
196
190
|
)
|
|
197
191
|
(change_dir / "design.md").write_text("", encoding="utf-8")
|
|
198
|
-
(change_dir
|
|
192
|
+
_spec_file(change_dir).write_text("", encoding="utf-8")
|
|
199
193
|
_write_metadata(
|
|
200
194
|
change_dir,
|
|
201
195
|
{
|
|
@@ -174,7 +174,7 @@ def ensure_developer(repo_root: Path | None = None) -> None:
|
|
|
174
174
|
|
|
175
175
|
if not check_developer(repo_root):
|
|
176
176
|
print("Error: developer identity has not been initialized.", file=sys.stderr)
|
|
177
|
-
print(f"Run:
|
|
177
|
+
print(f"Run: ./{DIR_WORKFLOW}/run init-developer <your-name>", file=sys.stderr)
|
|
178
178
|
sys.exit(1)
|
|
179
179
|
|
|
180
180
|
|
|
@@ -31,6 +31,8 @@ from .paths import (
|
|
|
31
31
|
get_tasks_dir,
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
+
CONTEXT_JSONL_FILES = ("implement.jsonl", "check.jsonl", "debug.jsonl")
|
|
35
|
+
|
|
34
36
|
# =============================================================================
|
|
35
37
|
# Helper Functions
|
|
36
38
|
# =============================================================================
|
|
@@ -191,6 +193,93 @@ def _get_current_task_snapshot(repo_root: Path) -> CurrentTaskSnapshot:
|
|
|
191
193
|
)
|
|
192
194
|
|
|
193
195
|
|
|
196
|
+
def _jsonl_file_references(jsonl_path: Path) -> list[str]:
|
|
197
|
+
"""读取 JSONL 中声明的文件引用,忽略损坏行。"""
|
|
198
|
+
references: list[str] = []
|
|
199
|
+
if not jsonl_path.is_file():
|
|
200
|
+
return references
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
lines = jsonl_path.read_text(encoding="utf-8").splitlines()
|
|
204
|
+
except (OSError, IOError):
|
|
205
|
+
return references
|
|
206
|
+
|
|
207
|
+
for line in lines:
|
|
208
|
+
if not line.strip():
|
|
209
|
+
continue
|
|
210
|
+
try:
|
|
211
|
+
data = json.loads(line)
|
|
212
|
+
except json.JSONDecodeError:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
file_path = data.get("file")
|
|
216
|
+
if isinstance(file_path, str) and file_path.strip():
|
|
217
|
+
references.append(file_path.strip())
|
|
218
|
+
|
|
219
|
+
return references
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _task_plan_references(repo_root: Path, snapshot: CurrentTaskSnapshot) -> list[str]:
|
|
223
|
+
"""从当前任务上下文中提取 plan 文件引用。"""
|
|
224
|
+
if not snapshot.path:
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
task_dir = repo_root / snapshot.path
|
|
228
|
+
plan_refs: list[str] = []
|
|
229
|
+
seen: set[str] = set()
|
|
230
|
+
|
|
231
|
+
data = snapshot.data or {}
|
|
232
|
+
metadata_candidates = [
|
|
233
|
+
data.get("plan"),
|
|
234
|
+
data.get("planFile"),
|
|
235
|
+
data.get("meta", {}).get("plan") if isinstance(data.get("meta"), dict) else None,
|
|
236
|
+
data.get("meta", {}).get("planFile") if isinstance(data.get("meta"), dict) else None,
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
for candidate in metadata_candidates:
|
|
240
|
+
if isinstance(candidate, str) and candidate.startswith(f"{DIR_WORKFLOW}/plans/"):
|
|
241
|
+
seen.add(candidate)
|
|
242
|
+
plan_refs.append(candidate)
|
|
243
|
+
|
|
244
|
+
for jsonl_name in CONTEXT_JSONL_FILES:
|
|
245
|
+
for reference in _jsonl_file_references(task_dir / jsonl_name):
|
|
246
|
+
if not reference.startswith(f"{DIR_WORKFLOW}/plans/"):
|
|
247
|
+
continue
|
|
248
|
+
if reference in seen:
|
|
249
|
+
continue
|
|
250
|
+
seen.add(reference)
|
|
251
|
+
plan_refs.append(reference)
|
|
252
|
+
|
|
253
|
+
return plan_refs
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _build_resume_checklist(
|
|
257
|
+
repo_root: Path,
|
|
258
|
+
snapshot: CurrentTaskSnapshot,
|
|
259
|
+
) -> dict[str, list[str]]:
|
|
260
|
+
"""构建最小恢复清单,只返回路径和命令,不展开文件内容。"""
|
|
261
|
+
commands = [f"./{DIR_WORKFLOW}/run resume"]
|
|
262
|
+
read_files: list[str] = []
|
|
263
|
+
notes: list[str] = []
|
|
264
|
+
|
|
265
|
+
if not snapshot.path:
|
|
266
|
+
notes.append("No current task. Use `.agent/skills/start` to classify the next file-changing request.")
|
|
267
|
+
notes.append("Do not bulk-read `.cowork-flow/spec/` or workspace journals; read details only after a task is selected.")
|
|
268
|
+
return {"commands": commands, "readFiles": read_files, "notes": notes}
|
|
269
|
+
|
|
270
|
+
current_task = snapshot.path
|
|
271
|
+
commands.append(f"./{DIR_WORKFLOW}/run task list-context {current_task}")
|
|
272
|
+
|
|
273
|
+
if snapshot.has_prd:
|
|
274
|
+
read_files.append(f"{current_task}/prd.md")
|
|
275
|
+
|
|
276
|
+
read_files.extend(_task_plan_references(repo_root, snapshot))
|
|
277
|
+
notes.append("Read current plan status only when continuing implementation.")
|
|
278
|
+
notes.append("Do not bulk-read `.cowork-flow/spec/` or workspace journals; follow JSONL references on demand.")
|
|
279
|
+
|
|
280
|
+
return {"commands": commands, "readFiles": read_files, "notes": notes}
|
|
281
|
+
|
|
282
|
+
|
|
194
283
|
def _current_task_json(snapshot: CurrentTaskSnapshot) -> dict | None:
|
|
195
284
|
"""Build the record-mode currentTask JSON object."""
|
|
196
285
|
current_task = snapshot.path
|
|
@@ -205,6 +294,45 @@ def _current_task_json(snapshot: CurrentTaskSnapshot) -> dict | None:
|
|
|
205
294
|
}
|
|
206
295
|
|
|
207
296
|
|
|
297
|
+
def _append_resume_checklist(
|
|
298
|
+
lines: list[str],
|
|
299
|
+
repo_root: Path,
|
|
300
|
+
snapshot: CurrentTaskSnapshot,
|
|
301
|
+
) -> None:
|
|
302
|
+
"""向文本上下文追加最小恢复清单。"""
|
|
303
|
+
lines.append("## RESUME CHECKLIST")
|
|
304
|
+
checklist = _build_resume_checklist(repo_root, snapshot)
|
|
305
|
+
commands = checklist["commands"]
|
|
306
|
+
read_files = checklist["readFiles"]
|
|
307
|
+
|
|
308
|
+
lines.append(f"- Recovery entrypoint (rerun only if context is stale): {commands[0]}")
|
|
309
|
+
|
|
310
|
+
if not snapshot.path:
|
|
311
|
+
lines.append("- No current task. Run `.agent/skills/start` before editing files.")
|
|
312
|
+
lines.append("- Do not bulk-read .cowork-flow/spec/ or workspace journals; choose a task first.")
|
|
313
|
+
lines.append("")
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
current_task = snapshot.path
|
|
317
|
+
prd_path = f"{current_task}/prd.md"
|
|
318
|
+
if prd_path in read_files:
|
|
319
|
+
lines.append(f"- Read current task PRD: {prd_path}")
|
|
320
|
+
else:
|
|
321
|
+
lines.append(f"- Current task PRD missing: {prd_path}")
|
|
322
|
+
|
|
323
|
+
lines.append(f"- List task context before reading details: {commands[1]}")
|
|
324
|
+
|
|
325
|
+
plan_files = [path for path in read_files if path.startswith(f"{DIR_WORKFLOW}/plans/")]
|
|
326
|
+
if plan_files:
|
|
327
|
+
for plan_file in plan_files:
|
|
328
|
+
lines.append(f"- Read current plan status: {plan_file}")
|
|
329
|
+
else:
|
|
330
|
+
lines.append("- No plan reference found in task context; do not search all plans unless needed.")
|
|
331
|
+
|
|
332
|
+
lines.append("- Do not bulk-read .cowork-flow/spec/ or workspace journals; follow JSONL references on demand.")
|
|
333
|
+
lines.append("")
|
|
334
|
+
|
|
335
|
+
|
|
208
336
|
def _append_current_task(
|
|
209
337
|
lines: list[str],
|
|
210
338
|
snapshot: CurrentTaskSnapshot,
|
|
@@ -297,6 +425,7 @@ def get_context_json(repo_root: Path | None = None) -> dict:
|
|
|
297
425
|
tasks_dir = get_tasks_dir(repo_root)
|
|
298
426
|
journal_file = get_active_journal_file(repo_root)
|
|
299
427
|
git_snapshot = _get_git_snapshot(repo_root)
|
|
428
|
+
current_task_snapshot = _get_current_task_snapshot(repo_root)
|
|
300
429
|
|
|
301
430
|
journal_lines = 0
|
|
302
431
|
journal_relative = ""
|
|
@@ -331,6 +460,7 @@ def get_context_json(repo_root: Path | None = None) -> dict:
|
|
|
331
460
|
"lines": journal_lines,
|
|
332
461
|
"nearLimit": journal_lines > 1800,
|
|
333
462
|
},
|
|
463
|
+
"resumeChecklist": _build_resume_checklist(repo_root, current_task_snapshot),
|
|
334
464
|
}
|
|
335
465
|
|
|
336
466
|
|
|
@@ -373,7 +503,7 @@ def get_context_text(repo_root: Path | None = None) -> str:
|
|
|
373
503
|
lines.append("## DEVELOPER")
|
|
374
504
|
if not developer:
|
|
375
505
|
lines.append(
|
|
376
|
-
f"ERROR: Not initialized. Run:
|
|
506
|
+
f"ERROR: Not initialized. Run: ./{DIR_WORKFLOW}/run init-developer <name>"
|
|
377
507
|
)
|
|
378
508
|
return "\n".join(lines)
|
|
379
509
|
|
|
@@ -383,13 +513,15 @@ def get_context_text(repo_root: Path | None = None) -> str:
|
|
|
383
513
|
git_snapshot = _get_git_snapshot(repo_root)
|
|
384
514
|
_append_git_status(lines, git_snapshot, repo_root)
|
|
385
515
|
_append_recent_commits(lines, git_snapshot)
|
|
516
|
+
current_task_snapshot = _get_current_task_snapshot(repo_root)
|
|
386
517
|
_append_current_task(
|
|
387
518
|
lines,
|
|
388
|
-
|
|
519
|
+
current_task_snapshot,
|
|
389
520
|
include_created=True,
|
|
390
521
|
include_description=True,
|
|
391
522
|
include_prd_hint=True,
|
|
392
523
|
)
|
|
524
|
+
_append_resume_checklist(lines, repo_root, current_task_snapshot)
|
|
393
525
|
|
|
394
526
|
# Active tasks
|
|
395
527
|
lines.append("## ACTIVE TASKS")
|
|
@@ -503,11 +635,14 @@ def get_context_record_json(repo_root: Path | None = None) -> dict:
|
|
|
503
635
|
"meta": data.get("meta", {}),
|
|
504
636
|
})
|
|
505
637
|
|
|
638
|
+
current_task_snapshot = _get_current_task_snapshot(repo_root)
|
|
639
|
+
|
|
506
640
|
return {
|
|
507
641
|
"developer": developer or "",
|
|
508
642
|
"git": _git_json(git_snapshot),
|
|
509
643
|
"myTasks": my_tasks,
|
|
510
|
-
"currentTask": _current_task_json(
|
|
644
|
+
"currentTask": _current_task_json(current_task_snapshot),
|
|
645
|
+
"resumeChecklist": _build_resume_checklist(repo_root, current_task_snapshot),
|
|
511
646
|
}
|
|
512
647
|
|
|
513
648
|
|
|
@@ -535,7 +670,7 @@ def get_context_text_record(repo_root: Path | None = None) -> str:
|
|
|
535
670
|
developer = get_developer(repo_root)
|
|
536
671
|
if not developer:
|
|
537
672
|
lines.append(
|
|
538
|
-
f"ERROR: Not initialized. Run:
|
|
673
|
+
f"ERROR: Not initialized. Run: ./{DIR_WORKFLOW}/run init-developer <name>"
|
|
539
674
|
)
|
|
540
675
|
return "\n".join(lines)
|
|
541
676
|
|
|
@@ -570,7 +705,9 @@ def get_context_text_record(repo_root: Path | None = None) -> str:
|
|
|
570
705
|
git_snapshot = _get_git_snapshot(repo_root)
|
|
571
706
|
_append_git_status(lines, git_snapshot, repo_root)
|
|
572
707
|
_append_recent_commits(lines, git_snapshot)
|
|
573
|
-
|
|
708
|
+
current_task_snapshot = _get_current_task_snapshot(repo_root)
|
|
709
|
+
_append_current_task(lines, current_task_snapshot)
|
|
710
|
+
_append_resume_checklist(lines, repo_root, current_task_snapshot)
|
|
574
711
|
|
|
575
712
|
lines.append("========================================")
|
|
576
713
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Get Session Context for AI Agent.
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
./.cowork-flow/run get-context Output context in text format
|
|
7
|
+
./.cowork-flow/run get-context --json Output context in JSON format
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""恢复 cowork-flow 会话的最小入口。"""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from common.git_context import get_context_text
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> int:
|
|
10
|
+
print("========================================")
|
|
11
|
+
print("COWORK-FLOW RESUME")
|
|
12
|
+
print("========================================")
|
|
13
|
+
print("Use this after new sessions, long-task resumes, or context compression.")
|
|
14
|
+
print("Read RESUME CHECKLIST first; load details on demand.")
|
|
15
|
+
print("")
|
|
16
|
+
print(get_context_text())
|
|
17
|
+
return 0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
raise SystemExit(main())
|