cowork-flow 0.0.2 → 0.0.3
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 +1 -1
- package/package.json +1 -1
- package/src/lib/copy-template.js +51 -1
- package/template/.agent/skills/start/SKILL.md +51 -18
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/scripts/common/git_context.py +140 -3
- package/template/.cowork-flow/scripts/resume.py +21 -0
- package/template/.cowork-flow/scripts/task.py +74 -0
- package/template/.cowork-flow/spec/guides/pre-implementation-checklist.md +1 -1
- package/template/.cowork-flow/workflow.md +25 -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 +11 -3
package/README.md
CHANGED
|
@@ -128,7 +128,7 @@ cowork-flow sync .
|
|
|
128
128
|
cowork-flow sync . --dry-run
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
`sync`
|
|
131
|
+
`sync` 默认刷新 `.agent/skills/`、`.cowork-flow/scripts/` 和 `AGENTS.md` 中的 `<!-- COWORK-FLOW:START --> ... <!-- COWORK-FLOW:END -->` 托管块,保留 `AGENTS.md` 托管块之外的项目自定义内容。`.cowork-flow/config.yaml`、`.cowork-flow/workflow.md`、`.cowork-flow/spec/`、任务、计划、变更和 workspace 记录默认受保护。只有明确传入 `--force` 时才整文件覆盖保护文件。
|
|
132
132
|
|
|
133
133
|
## 常用入口
|
|
134
134
|
|
package/package.json
CHANGED
package/src/lib/copy-template.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { constants } from 'node:fs';
|
|
2
|
-
import { access, copyFile, mkdir, readdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { access, copyFile, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, relative } from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { templateRoot } from './paths.js';
|
|
@@ -113,6 +113,9 @@ const SAFE_SYNC_FILES = new Set([
|
|
|
113
113
|
'.cowork-flow/.version'
|
|
114
114
|
]);
|
|
115
115
|
|
|
116
|
+
const COWORK_FLOW_START = '<!-- COWORK-FLOW:START -->';
|
|
117
|
+
const COWORK_FLOW_END = '<!-- COWORK-FLOW:END -->';
|
|
118
|
+
|
|
116
119
|
function isProtectedSyncFile(relativePath) {
|
|
117
120
|
return PROTECTED_SYNC_FILES.has(relativePath)
|
|
118
121
|
|| PROTECTED_SYNC_PREFIXES.some((prefix) => relativePath.startsWith(prefix));
|
|
@@ -124,6 +127,48 @@ function isSafeSyncFile(relativePath) {
|
|
|
124
127
|
|| relativePath.endsWith('/.gitkeep');
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
function replaceManagedBlock(targetContent, templateContent) {
|
|
131
|
+
const targetStart = targetContent.indexOf(COWORK_FLOW_START);
|
|
132
|
+
const targetEnd = targetContent.indexOf(COWORK_FLOW_END, targetStart + COWORK_FLOW_START.length);
|
|
133
|
+
const templateStart = templateContent.indexOf(COWORK_FLOW_START);
|
|
134
|
+
const templateEnd = templateContent.indexOf(COWORK_FLOW_END, templateStart + COWORK_FLOW_START.length);
|
|
135
|
+
|
|
136
|
+
if (targetStart === -1 || targetEnd === -1 || templateStart === -1 || templateEnd === -1) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const targetBlockEnd = targetEnd + COWORK_FLOW_END.length;
|
|
141
|
+
const templateBlockEnd = templateEnd + COWORK_FLOW_END.length;
|
|
142
|
+
return [
|
|
143
|
+
targetContent.slice(0, targetStart),
|
|
144
|
+
templateContent.slice(templateStart, templateBlockEnd),
|
|
145
|
+
targetContent.slice(targetBlockEnd)
|
|
146
|
+
].join('');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function buildAgentsSyncAction({ source, destination, exists, options }) {
|
|
150
|
+
if (!exists || options.force) {
|
|
151
|
+
return {
|
|
152
|
+
action: exists ? 'update' : 'create',
|
|
153
|
+
source,
|
|
154
|
+
destination,
|
|
155
|
+
relativePath: 'AGENTS.md'
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const [templateContent, targetContent] = await Promise.all([
|
|
160
|
+
readFile(source, 'utf8'),
|
|
161
|
+
readFile(destination, 'utf8')
|
|
162
|
+
]);
|
|
163
|
+
const content = replaceManagedBlock(targetContent, templateContent);
|
|
164
|
+
|
|
165
|
+
if (content === null) {
|
|
166
|
+
return { action: 'protected', source, destination, relativePath: 'AGENTS.md' };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { action: 'update', source, destination, relativePath: 'AGENTS.md', content };
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
export async function buildSyncPlan(targetDir, options = {}) {
|
|
128
173
|
if (!await pathExists(join(targetDir, '.cowork-flow'))) {
|
|
129
174
|
throw new Error(`Target is not initialized: ${targetDir}`);
|
|
@@ -140,6 +185,11 @@ export async function buildSyncPlan(targetDir, options = {}) {
|
|
|
140
185
|
const source = join(templateRoot, file);
|
|
141
186
|
const destination = join(targetDir, file);
|
|
142
187
|
const exists = await pathExists(destination);
|
|
188
|
+
if (file === 'AGENTS.md') {
|
|
189
|
+
actions.push(await buildAgentsSyncAction({ source, destination, exists, options }));
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
143
193
|
const protectedFile = isProtectedSyncFile(file) && !options.force && exists;
|
|
144
194
|
const safeFile = isSafeSyncFile(file);
|
|
145
195
|
|
|
@@ -7,6 +7,14 @@ description: Use when beginning a development session in a project that uses the
|
|
|
7
7
|
|
|
8
8
|
Initialize your AI development session and begin working on tasks.
|
|
9
9
|
|
|
10
|
+
<HARD-GATE>
|
|
11
|
+
Any request that changes repository files MUST go through this skill's task workflow.
|
|
12
|
+
Questions may be answered directly, but file edits are at least `L0` and require a task,
|
|
13
|
+
PRD, context initialization, task activation, verification, and session recording.
|
|
14
|
+
Do not use a direct-edit shortcut unless the user explicitly tells you not to use
|
|
15
|
+
cowork-flow for this task.
|
|
16
|
+
</HARD-GATE>
|
|
17
|
+
|
|
10
18
|
---
|
|
11
19
|
|
|
12
20
|
## Operation Types
|
|
@@ -42,10 +50,11 @@ cat AGENTS.md
|
|
|
42
50
|
### Step 2: Get Current Context
|
|
43
51
|
|
|
44
52
|
```bash
|
|
45
|
-
python3 ./.cowork-flow/scripts/
|
|
53
|
+
python3 ./.cowork-flow/scripts/resume.py
|
|
46
54
|
```
|
|
47
55
|
|
|
48
|
-
This shows: developer identity, git status, current task (if any), active tasks
|
|
56
|
+
This shows: developer identity, git status, current task (if any), active tasks, and
|
|
57
|
+
the minimal `RESUME CHECKLIST` for loading task details on demand.
|
|
49
58
|
|
|
50
59
|
### Step 3: Read Guidelines Index
|
|
51
60
|
|
|
@@ -71,13 +80,19 @@ When user describes a task, classify it:
|
|
|
71
80
|
|
|
72
81
|
| Type | Criteria | Workflow |
|
|
73
82
|
|------|----------|----------|
|
|
74
|
-
| **Question** | User asks about code, architecture, or how something works | Answer directly |
|
|
75
|
-
| **
|
|
76
|
-
| **
|
|
77
|
-
| **
|
|
83
|
+
| **Question** | User asks about code, architecture, or how something works, and no repository files need to change | Answer directly after session context is loaded |
|
|
84
|
+
| **L0 Change** | Docs, tests, comments, config, small refactors, or any file change without external behavior change | Task Workflow, no Behavior Change Gate |
|
|
85
|
+
| **L1 Change** | Bounded behavior change in one module or one interface | Behavior Change Gate → Task Workflow |
|
|
86
|
+
| **L2 Change** | Cross-layer, architectural, migration, permission, compatibility, release, or security-impacting change | Behavior Change Gate + `design.md` → Task Workflow |
|
|
87
|
+
| **Unclear / Complex** | Vague goal, multiple possible interpretations, or architectural decisions | **Brainstorm → classify L0/L1/L2 → gated workflow** |
|
|
78
88
|
|
|
79
89
|
### Decision Rule
|
|
80
90
|
|
|
91
|
+
> **Any file change must enter L0 / L1 / L2 workflow.**
|
|
92
|
+
>
|
|
93
|
+
> A typo, comment, one-line fix, or "quick" change is still a repository change.
|
|
94
|
+
> Do not treat small size as permission to skip PRD, context, verification, or session recording.
|
|
95
|
+
>
|
|
81
96
|
> **If in doubt, use Brainstorm + Task Workflow.**
|
|
82
97
|
>
|
|
83
98
|
> Task Workflow ensures code-specs are injected to the right context, resulting in higher quality code.
|
|
@@ -114,24 +129,24 @@ Before Phase 2, always complete this task handoff:
|
|
|
114
129
|
|
|
115
130
|
---
|
|
116
131
|
|
|
117
|
-
## Question
|
|
132
|
+
## Question Only
|
|
118
133
|
|
|
119
|
-
For questions
|
|
134
|
+
For questions that do not modify repository files, work directly:
|
|
120
135
|
|
|
121
|
-
1. Answer
|
|
122
|
-
2. If
|
|
136
|
+
1. Answer the question.
|
|
137
|
+
2. If the answer reveals that files should change, stop and re-enter Task Classification.
|
|
138
|
+
3. Do not modify files from this section.
|
|
123
139
|
|
|
124
140
|
---
|
|
125
141
|
|
|
126
|
-
## Simple Task
|
|
142
|
+
## L0 / Simple Task
|
|
127
143
|
|
|
128
|
-
For simple, well-defined tasks:
|
|
144
|
+
For simple, well-defined tasks that modify files without external behavior change:
|
|
129
145
|
|
|
130
|
-
1.
|
|
131
|
-
2.
|
|
132
|
-
3.
|
|
133
|
-
4. **If it is `
|
|
134
|
-
5. **If it is `L0`, execute ALL steps below without stopping. Do NOT ask for additional confirmation between steps.**
|
|
146
|
+
1. Clarify only if the goal, scope, or verification method is unclear.
|
|
147
|
+
2. Determine whether the task is `L0`, `L1`, or `L2`.
|
|
148
|
+
3. **If it is `L1/L2`, complete the Behavior Change Gate first. After that, execute Path B Step 2 and Step 3, complete the task handoff, then continue with Phase 2.**
|
|
149
|
+
4. **If it is `L0`, execute ALL steps below without stopping. Do NOT ask for additional confirmation between steps. Do NOT switch to ad hoc direct editing.**
|
|
135
150
|
- Create task directory (Phase 1 Path B, Step 2)
|
|
136
151
|
- Write PRD (Step 3)
|
|
137
152
|
- Research codebase (Phase 2, Step 5)
|
|
@@ -339,7 +354,7 @@ Run a quality pass against check context:
|
|
|
339
354
|
|
|
340
355
|
## Continuing Existing Task
|
|
341
356
|
|
|
342
|
-
If `
|
|
357
|
+
If `resume.py` shows a current task:
|
|
343
358
|
|
|
344
359
|
1. Read the task's `prd.md` to understand the goal
|
|
345
360
|
2. Check `task.json` for current status
|
|
@@ -349,6 +364,23 @@ If yes, resume from the appropriate step (usually Step 7 or 8).
|
|
|
349
364
|
|
|
350
365
|
---
|
|
351
366
|
|
|
367
|
+
## Resume / Context Compression
|
|
368
|
+
|
|
369
|
+
When a conversation has many rounds, a task runs for a long time, or context was
|
|
370
|
+
compressed, resume with the smallest useful context:
|
|
371
|
+
|
|
372
|
+
1. Run `python3 ./.cowork-flow/scripts/resume.py`.
|
|
373
|
+
2. Follow the `RESUME CHECKLIST` section.
|
|
374
|
+
3. Read the current task `prd.md`.
|
|
375
|
+
4. Run `python3 ./.cowork-flow/scripts/task.py list-context <task-dir>` and read only the jsonl references needed for the current phase.
|
|
376
|
+
5. If a plan is listed, read its current execution status before continuing implementation.
|
|
377
|
+
|
|
378
|
+
Do not bulk-read `.cowork-flow/spec/`, all plans, all tasks, or workspace journals
|
|
379
|
+
just because context was compressed. Use the checklist as the minimal recovery
|
|
380
|
+
anchor, then load details on demand.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
352
384
|
## Skills Reference
|
|
353
385
|
|
|
354
386
|
### User Skills `[USER]`
|
|
@@ -363,6 +395,7 @@ If yes, resume from the appropriate step (usually Step 7 or 8).
|
|
|
363
395
|
|
|
364
396
|
| Script | Purpose |
|
|
365
397
|
|--------|---------|
|
|
398
|
+
| `python3 ./.cowork-flow/scripts/resume.py` | Resume session with minimal context checklist |
|
|
366
399
|
| `python3 ./.cowork-flow/scripts/get_context.py` | Get session context |
|
|
367
400
|
| `python3 ./.cowork-flow/scripts/task.py create` | Create task directory |
|
|
368
401
|
| `python3 ./.cowork-flow/scripts/task.py init-context` | Initialize jsonl files |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.3
|
|
@@ -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"python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/resume.py"]
|
|
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"python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/task.py 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
|
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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())
|
|
@@ -382,7 +382,10 @@ def _resolve_task_dir(target_dir: str, repo_root: Path) -> Path:
|
|
|
382
382
|
def get_implement_base() -> list[dict]:
|
|
383
383
|
"""Get base implement context entries."""
|
|
384
384
|
return [
|
|
385
|
+
{"file": "AGENTS.md", "reason": "Project collaboration rules and workflow gates"},
|
|
385
386
|
{"file": f"{DIR_WORKFLOW}/workflow.md", "reason": "Project workflow and conventions"},
|
|
387
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/guides/index.md", "reason": "Pre-implementation thinking guides"},
|
|
388
|
+
{"file": f"{DIR_WORKFLOW}/{DIR_SPEC}/guides/pre-implementation-checklist.md", "reason": "Mandatory pre-coding checklist"},
|
|
386
389
|
]
|
|
387
390
|
|
|
388
391
|
|
|
@@ -408,6 +411,7 @@ def get_check_context(dev_type: str, repo_root: Path) -> list[dict]:
|
|
|
408
411
|
"""Get check context entries."""
|
|
409
412
|
entries = [
|
|
410
413
|
{"file": _skill_path("finish-work"), "reason": "Finish work checklist"},
|
|
414
|
+
{"file": _skill_path("record-session"), "reason": "Session recording and state sync"},
|
|
411
415
|
]
|
|
412
416
|
|
|
413
417
|
if dev_type in ("backend", "frontend", "fullstack"):
|
|
@@ -420,6 +424,7 @@ def get_debug_context(dev_type: str, repo_root: Path) -> list[dict]:
|
|
|
420
424
|
"""Get debug context entries."""
|
|
421
425
|
entries: list[dict] = [
|
|
422
426
|
{"file": _skill_path("break-loop"), "reason": "Deep bug analysis workflow"},
|
|
427
|
+
{"file": _skill_path("update-spec"), "reason": "Capture implementation lessons and contracts"},
|
|
423
428
|
]
|
|
424
429
|
|
|
425
430
|
if dev_type in ("backend", "frontend", "fullstack"):
|
|
@@ -434,6 +439,53 @@ def _write_jsonl(path: Path, entries: list[dict]) -> None:
|
|
|
434
439
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
435
440
|
|
|
436
441
|
|
|
442
|
+
def _read_text_if_present(path: Path) -> str:
|
|
443
|
+
"""读取文本文件并返回去空白后的内容;不存在或读取失败时返回空字符串。"""
|
|
444
|
+
if not path.is_file():
|
|
445
|
+
return ""
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
return path.read_text(encoding="utf-8").strip()
|
|
449
|
+
except (OSError, IOError):
|
|
450
|
+
return ""
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _task_start_blockers(task_dir: Path) -> list[str]:
|
|
454
|
+
"""返回启动任务前必须处理的准备阻塞项。"""
|
|
455
|
+
blockers: list[str] = []
|
|
456
|
+
|
|
457
|
+
task_json = task_dir / FILE_TASK_JSON
|
|
458
|
+
if not task_json.is_file():
|
|
459
|
+
blockers.append("task.json is missing")
|
|
460
|
+
|
|
461
|
+
prd_file = task_dir / "prd.md"
|
|
462
|
+
if not _read_text_if_present(prd_file):
|
|
463
|
+
blockers.append("prd.md is missing or empty")
|
|
464
|
+
|
|
465
|
+
for jsonl_name in CONTEXT_JSONL_FILES:
|
|
466
|
+
jsonl_file = task_dir / jsonl_name
|
|
467
|
+
if not _read_text_if_present(jsonl_file):
|
|
468
|
+
blockers.append(f"{jsonl_name} is missing or empty")
|
|
469
|
+
|
|
470
|
+
return blockers
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _task_context_validation_issues(task_dir: Path, repo_root: Path) -> list[str]:
|
|
474
|
+
"""返回任务上下文文件的校验问题。"""
|
|
475
|
+
issues: list[str] = []
|
|
476
|
+
|
|
477
|
+
for jsonl_name in CONTEXT_JSONL_FILES:
|
|
478
|
+
jsonl_file = task_dir / jsonl_name
|
|
479
|
+
if not _read_text_if_present(jsonl_file):
|
|
480
|
+
continue
|
|
481
|
+
|
|
482
|
+
error_count = _validate_jsonl(jsonl_file, repo_root)
|
|
483
|
+
if error_count > 0:
|
|
484
|
+
issues.append(f"{jsonl_name} has {error_count} validation error(s)")
|
|
485
|
+
|
|
486
|
+
return issues
|
|
487
|
+
|
|
488
|
+
|
|
437
489
|
def _iter_jsonl_lines(path: Path):
|
|
438
490
|
"""Yield line number and content from a JSONL file."""
|
|
439
491
|
with path.open("r", encoding="utf-8") as f:
|
|
@@ -856,6 +908,28 @@ def cmd_start(args: argparse.Namespace) -> int:
|
|
|
856
908
|
print(f"Hint: Use task name (e.g., 'my-task') or full path (e.g., '{DIR_WORKFLOW}/tasks/01-31-my-task')")
|
|
857
909
|
return 1
|
|
858
910
|
|
|
911
|
+
blockers = _task_start_blockers(full_path)
|
|
912
|
+
if blockers:
|
|
913
|
+
print(colored("Error: Task is not ready to start yet", Colors.RED), file=sys.stderr)
|
|
914
|
+
for blocker in blockers:
|
|
915
|
+
print(f" - {blocker}", file=sys.stderr)
|
|
916
|
+
print(
|
|
917
|
+
"Hint: write prd.md, run python3 ./.cowork-flow/scripts/task.py init-context <dir> <dev_type>, then retry",
|
|
918
|
+
file=sys.stderr,
|
|
919
|
+
)
|
|
920
|
+
return 1
|
|
921
|
+
|
|
922
|
+
validation_issues = _task_context_validation_issues(full_path, repo_root)
|
|
923
|
+
if validation_issues:
|
|
924
|
+
print(colored("Error: Task context validation failed", Colors.RED), file=sys.stderr)
|
|
925
|
+
for issue in validation_issues:
|
|
926
|
+
print(f" - {issue}", file=sys.stderr)
|
|
927
|
+
print(
|
|
928
|
+
"Hint: run python3 ./.cowork-flow/scripts/task.py validate <dir> and fix the reported issues",
|
|
929
|
+
file=sys.stderr,
|
|
930
|
+
)
|
|
931
|
+
return 1
|
|
932
|
+
|
|
859
933
|
# Convert to relative path for storage
|
|
860
934
|
try:
|
|
861
935
|
task_dir = str(full_path.relative_to(repo_root))
|
|
@@ -81,7 +81,7 @@ python3 ./.cowork-flow/scripts/init_developer.py <developer-name>
|
|
|
81
81
|
每次开始工作先执行:
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
python3 ./.cowork-flow/scripts/
|
|
84
|
+
python3 ./.cowork-flow/scripts/resume.py
|
|
85
85
|
python3 ./.cowork-flow/scripts/task.py list
|
|
86
86
|
git status
|
|
87
87
|
git log --oneline -10
|
|
@@ -387,7 +387,29 @@ python3 ./.cowork-flow/scripts/change.py archive <slug>
|
|
|
387
387
|
|
|
388
388
|
---
|
|
389
389
|
|
|
390
|
-
## 11.
|
|
390
|
+
## 11. 恢复与上下文压缩
|
|
391
|
+
|
|
392
|
+
多轮对话、长任务、中断恢复或上下文压缩后,必须先走最小恢复层,再按需读取细节。
|
|
393
|
+
|
|
394
|
+
| 层级 | 读取内容 | 使用时机 |
|
|
395
|
+
|------|----------|----------|
|
|
396
|
+
| 最小恢复层 | `resume.py` 输出中的 `RESUME CHECKLIST` | 每次恢复、压缩后、重新接手任务 |
|
|
397
|
+
| 任务执行层 | 当前任务 `prd.md`、当前 plan 状态、`task.py list-context <task-dir>` | 准备继续实现或检查前 |
|
|
398
|
+
| 细节规范层 | jsonl 指向的具体 spec、代码模式或计划段落 | 当前修改确实需要该细节时 |
|
|
399
|
+
|
|
400
|
+
恢复规则:
|
|
401
|
+
|
|
402
|
+
- 先执行 `python3 ./.cowork-flow/scripts/resume.py`。
|
|
403
|
+
- 按 `RESUME CHECKLIST` 读取当前 PRD、当前 plan 和 `list-context` 输出。
|
|
404
|
+
- 不要全量重读 `.cowork-flow/spec/`、所有 plan、所有 task 或 workspace journal。
|
|
405
|
+
- 如果 plan 有 `Current Execution Status`,先读该段以确定下一步。
|
|
406
|
+
- 如果 jsonl 指向的规范过多,先根据当前阶段选择最小相关集合。
|
|
407
|
+
|
|
408
|
+
恢复目标是重建“现在该做什么”和“当前门禁是否满足”,不是把全部历史重新塞进上下文。
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## 12. 禁止事项
|
|
391
413
|
|
|
392
414
|
- 不跳过上下文读取直接编码。
|
|
393
415
|
- 不在未验证时声称完成。
|
|
@@ -398,7 +420,7 @@ python3 ./.cowork-flow/scripts/change.py archive <slug>
|
|
|
398
420
|
|
|
399
421
|
---
|
|
400
422
|
|
|
401
|
-
##
|
|
423
|
+
## 13. 完成定义
|
|
402
424
|
|
|
403
425
|
一个任务只有在以下条件同时满足时,才算完成:
|
|
404
426
|
|
|
@@ -26,7 +26,7 @@ You MUST create a task for each of these items and complete them in order:
|
|
|
26
26
|
3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
|
|
27
27
|
4. **Propose 2-3 approaches** — with trade-offs and your recommendation
|
|
28
28
|
5. **Present design** — in sections scaled to their complexity, get user approval after each section
|
|
29
|
-
6. **Write
|
|
29
|
+
6. **Write change artifacts** — save proposal/spec/design in `.cowork-flow/changes/<slug>/` and commit
|
|
30
30
|
7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below)
|
|
31
31
|
8. **User reviews written spec** — ask user to review the spec file before proceeding
|
|
32
32
|
9. **Transition to implementation** — invoke writing-plans skill to create implementation plan
|
|
@@ -108,7 +108,10 @@ digraph brainstorming {
|
|
|
108
108
|
|
|
109
109
|
**Documentation:**
|
|
110
110
|
|
|
111
|
-
- Write the validated
|
|
111
|
+
- Write the validated change artifacts under `.cowork-flow/changes/<slug>/`
|
|
112
|
+
- `proposal.md` explains why the behavior should change
|
|
113
|
+
- `specs/.../spec.md` defines external behavior and acceptance criteria
|
|
114
|
+
- `design.md` records important design trade-offs for L2 work
|
|
112
115
|
- (User preferences for spec location override this default)
|
|
113
116
|
- Use elements-of-style:writing-clearly-and-concisely skill if available
|
|
114
117
|
- Commit the design document to git
|
|
@@ -4,7 +4,7 @@ Use this template when dispatching a spec document reviewer subagent.
|
|
|
4
4
|
|
|
5
5
|
**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning.
|
|
6
6
|
|
|
7
|
-
**Dispatch after:** Spec document is written
|
|
7
|
+
**Dispatch after:** Spec or design document is written under `.cowork-flow/changes/<slug>/`
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
Task tool (general-purpose):
|
|
@@ -57,7 +57,7 @@ HEAD_SHA=$(git rev-parse HEAD)
|
|
|
57
57
|
|
|
58
58
|
[Dispatch code reviewer subagent]
|
|
59
59
|
DESCRIPTION: Added verifyIndex() and repairIndex() with 4 issue types
|
|
60
|
-
PLAN_OR_REQUIREMENTS: Task 2 from
|
|
60
|
+
PLAN_OR_REQUIREMENTS: Task 2 from .cowork-flow/plans/deployment-plan.md
|
|
61
61
|
BASE_SHA: a7981ec
|
|
62
62
|
HEAD_SHA: 3df7661
|
|
63
63
|
|
|
@@ -130,7 +130,7 @@ Implementer subagents report one of four statuses. Handle each appropriately:
|
|
|
130
130
|
```
|
|
131
131
|
You: I'm using Subagent-Driven Development to execute this plan.
|
|
132
132
|
|
|
133
|
-
[Read plan file once:
|
|
133
|
+
[Read plan file once: .cowork-flow/plans/feature-plan.md]
|
|
134
134
|
[Extract all 5 tasks with full text and context]
|
|
135
135
|
[Create TodoWrite with all tasks]
|
|
136
136
|
|
|
@@ -15,7 +15,7 @@ Assume they are a skilled developer, but know almost nothing about our toolset o
|
|
|
15
15
|
|
|
16
16
|
**Context:** If working in an isolated worktree, it should have been created via the `superpowers:using-git-worktrees` skill at execution time.
|
|
17
17
|
|
|
18
|
-
**Save plans to:**
|
|
18
|
+
**Save plans to:** `.cowork-flow/plans/YYYY-MM-DD-<feature-name>.md`
|
|
19
19
|
- (User preferences for plan location override this default)
|
|
20
20
|
|
|
21
21
|
## Scope Check
|
|
@@ -135,7 +135,7 @@ If you find issues, fix them inline. No need to re-review — just fix and move
|
|
|
135
135
|
|
|
136
136
|
After saving the plan, offer execution choice:
|
|
137
137
|
|
|
138
|
-
**"Plan complete and saved to
|
|
138
|
+
**"Plan complete and saved to `.cowork-flow/plans/<filename>.md`. Two execution options:**
|
|
139
139
|
|
|
140
140
|
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
|
|
141
141
|
|
package/template/AGENTS.md
CHANGED
|
@@ -95,14 +95,22 @@
|
|
|
95
95
|
- 如果跟丢上下文,先停下来重新整理。
|
|
96
96
|
|
|
97
97
|
<!-- COWORK-FLOW:START -->
|
|
98
|
-
# cowork-flow
|
|
98
|
+
# cowork-flow 强门禁
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
若与项目自定义说明冲突,以项目说明和用户明确指令为准。
|
|
101
|
+
|
|
102
|
+
1. 每次新会话、重新接手任务、或上下文明显丢失时,先执行 `python3 ./.cowork-flow/scripts/resume.py`,再进入 `.agent/skills/start`。
|
|
103
|
+
2. 执行 `start` 时,先读 `AGENTS.md`、`.cowork-flow/workflow.md` 和 `resume.py` 的输出,不要直接编码。
|
|
104
|
+
3. 任何会修改仓库文件的请求,都必须先分级为 `L0` / `L1` / `L2`,再进入对应流程;不要把“很小”当成可以绕过流程的理由。
|
|
105
|
+
4. `L1` / `L2` 行为变更必须先有 `change -> spec -> plan`;`L2` 还必须有 `design.md`,然后才能进入实现。
|
|
106
|
+
5. 实现前必须把任务上下文写入 `.cowork-flow/tasks/<task>/implement.jsonl`、`check.jsonl`、`debug.jsonl`,并在 `start` 之后再改代码。
|
|
107
|
+
6. 收尾前必须跑验证、同步 plan / task / change 状态,完成 `finish-work` 检查,再按项目策略记录 session。
|
|
108
|
+
7. 多轮对话、长任务恢复或上下文压缩后,先运行 `python3 ./.cowork-flow/scripts/resume.py` 并查看 `RESUME CHECKLIST`;只读取清单列出的 PRD、当前 plan 和 jsonl 引用,不要全量重读 `.cowork-flow/spec/` 或历史 journal。
|
|
101
109
|
|
|
102
110
|
Use the `.agent/skills/start` skill when starting a new session to:
|
|
103
111
|
- Initialize your developer identity
|
|
104
112
|
- Understand current project context
|
|
105
|
-
-
|
|
113
|
+
- Enforce the task workflow gates before any implementation
|
|
106
114
|
|
|
107
115
|
Use `@/.cowork-flow/` to learn:
|
|
108
116
|
- Development workflow (`workflow.md`)
|