cowork-flow 2.2.0 → 2.4.0
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/CHANGELOG.md +33 -1
- package/README.en.md +46 -22
- package/README.md +10 -7
- package/docs/architecture.en.md +95 -0
- package/docs/architecture.md +7 -3
- package/docs/direction.en.md +71 -0
- package/docs/direction.md +6 -1
- package/docs/hosts.en.md +299 -0
- package/docs/hosts.md +5 -1
- package/docs/index.en.md +30 -0
- package/docs/index.md +2 -0
- package/docs/release.en.md +108 -0
- package/docs/release.md +2 -0
- package/package.json +2 -2
- package/presets/claude-code/.claude-plugin/plugin.json +1 -1
- package/presets/claude-code/skills/cowork-flow-bootstrap/SKILL.md +8 -2
- package/presets/codex/.codex-plugin/plugin.json +1 -1
- package/presets/codex/skills/cowork-flow-bootstrap/SKILL.md +8 -2
- package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
- package/presets/kimi-code/skills/cowork-flow-bootstrap/SKILL.md +8 -2
- package/presets/opencode/cowork-flow/skills/cowork-flow-bootstrap/SKILL.md +8 -2
- package/presets/qoder/.qoder-plugin/plugin.json +1 -1
- package/presets/qoder/skills/cowork-flow-bootstrap/SKILL.md +8 -2
- package/presets/zcode/.zcode-plugin/plugin.json +1 -1
- package/presets/zcode/skills/cowork-flow-bootstrap/SKILL.md +8 -2
- package/src/cli.js +1 -1
- package/src/commands/init.js +200 -16
- package/src/commands/registry.js +10 -7
- package/src/commands/source-refresh.js +5 -1
- package/src/commands/sync.js +4 -1
- package/src/lib/copy-template.js +23 -5
- package/src/lib/host-assets.js +119 -4
- package/src/lib/language.js +162 -0
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/config.yaml +8 -0
- package/template/.cowork-flow/scripts/adapters/cli/task_create_command.py +5 -3
- package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +71 -2
- package/template/.cowork-flow/scripts/infra/config.py +85 -0
- package/template/.cowork-flow/scripts/services/decision_anchor_schema.py +104 -0
- package/template/.cowork-flow/scripts/services/fact_view.py +21 -15
- package/template/.cowork-flow/scripts/services/task_context.py +13 -7
- package/template/.cowork-flow/scripts/services/task_creation.py +72 -30
- package/template/.cowork-flow/scripts/services/task_gate_profiles.py +7 -24
- package/template/.cowork-flow/spec/contracts/context-injection.md +5 -4
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +21 -14
- package/template/.cowork-flow/spec/contracts/plan-binding.md +2 -1
- package/template/.cowork-flow/spec/index.md +26 -26
- package/template/.cowork-flow/spec/references/definition-of-done.md +26 -26
- package/template/.cowork-flow/spec/references/index.md +16 -16
- package/template/.cowork-flow/spec/references/orchestration-patterns.md +71 -71
- package/template/.cowork-flow/spec/references/security-checklist.md +37 -37
- package/template/.cowork-flow/spec/references/testing-checklist.md +32 -32
- package/template/.cowork-flow/spec/runtime/host-assets.json +23 -4
- package/template/.cowork-flow/spec/runtime/index.md +10 -12
- package/template/.cowork-flow/spec/schemas/host-assets.schema.json +31 -0
- package/template/.cowork-flow/spec/schemas/index.md +5 -5
- package/template/.cowork-flow/templates/plan-template.md +91 -0
- package/template/locales/en-US/.cowork-flow/spec/contracts/decision-anchor.md +160 -0
- package/template/locales/en-US/.cowork-flow/templates/plan-template.md +91 -0
- package/template/skills/runtime-health/scripts/doctor.py +110 -0
- package/template/skills/task-planning/SKILL.md +19 -92
|
@@ -56,6 +56,9 @@
|
|
|
56
56
|
"excludedPrefixes": {
|
|
57
57
|
"$ref": "#/$defs/stringList"
|
|
58
58
|
},
|
|
59
|
+
"localization": {
|
|
60
|
+
"$ref": "#/$defs/localization"
|
|
61
|
+
},
|
|
59
62
|
"syncPolicy": {
|
|
60
63
|
"$ref": "#/$defs/syncPolicy"
|
|
61
64
|
}
|
|
@@ -68,6 +71,31 @@
|
|
|
68
71
|
"minLength": 1
|
|
69
72
|
}
|
|
70
73
|
},
|
|
74
|
+
"localization": {
|
|
75
|
+
"description": "Per-language asset overrides. Paths are canonical project-relative paths; the base template is the default language.",
|
|
76
|
+
"type": "object",
|
|
77
|
+
"additionalProperties": false,
|
|
78
|
+
"required": [
|
|
79
|
+
"default",
|
|
80
|
+
"supported",
|
|
81
|
+
"assets"
|
|
82
|
+
],
|
|
83
|
+
"properties": {
|
|
84
|
+
"default": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"minLength": 1
|
|
87
|
+
},
|
|
88
|
+
"supported": {
|
|
89
|
+
"$ref": "#/$defs/stringList"
|
|
90
|
+
},
|
|
91
|
+
"assets": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"additionalProperties": {
|
|
94
|
+
"$ref": "#/$defs/stringList"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
71
99
|
"capabilities": {
|
|
72
100
|
"type": "object",
|
|
73
101
|
"minProperties": 1,
|
|
@@ -342,6 +370,9 @@
|
|
|
342
370
|
"protectedFiles": {
|
|
343
371
|
"$ref": "#/$defs/stringList"
|
|
344
372
|
},
|
|
373
|
+
"forceProtectedFiles": {
|
|
374
|
+
"$ref": "#/$defs/stringList"
|
|
375
|
+
},
|
|
345
376
|
"protectedPrefixes": {
|
|
346
377
|
"$ref": "#/$defs/stringList"
|
|
347
378
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Schema Specs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This directory holds JSON Schemas.
|
|
4
4
|
|
|
5
|
-
- `adapter.schema.json`:
|
|
6
|
-
- `host-assets.schema.json`: Host Asset Manifest schema
|
|
7
|
-
- `party-mode-v2-actions.schema.json`: Party Mode V2 action schema
|
|
5
|
+
- `adapter.schema.json`: host adapter declaration schema.
|
|
6
|
+
- `host-assets.schema.json`: Host Asset Manifest schema (structural constraints for `runtime/host-assets.json`).
|
|
7
|
+
- `party-mode-v2-actions.schema.json`: Party Mode V2 action schema.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Schemas describe data contracts only, not workflow guidance; workflow and behavior boundaries belong in `contracts/`, Skill command manifests, or `AGENTS.md`.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# <功能> 实施计划
|
|
2
|
+
|
|
3
|
+
> 执行规则:本计划用于防止实现漂移,实现者必须按任务顺序推进。
|
|
4
|
+
> 如果计划与当前代码事实冲突、需要计划外文件,或测试因未计划的原因失败,停止并报告,不要扩大范围。
|
|
5
|
+
>
|
|
6
|
+
> 正式固定代理工作:使用 `.cowork-flow/run subagent init` 创建 runtime context,通过当前 Host Adapter 传入 `cowork_runtime_context_id: <runtime_context_id>`,再派发 `cowork-implement` 或 `cowork-check`;验证完成后关闭 runtime context。
|
|
7
|
+
|
|
8
|
+
| 字段 | 内容 |
|
|
9
|
+
|----|------|
|
|
10
|
+
| **任务类型** | Tiny / Normal / High-risk:<理由> |
|
|
11
|
+
| **执行策略** | Serial / Parallel:<理由> |
|
|
12
|
+
| **成功标准** | AC-001 / AC-002 / AC-003 |
|
|
13
|
+
| **最终验证** | `<command>` → exit 0;逐条核对适用的 `.cowork-flow/spec/<dev_type>/` 规范与项目门禁 → 每条 `pass` / `not_applicable` |
|
|
14
|
+
| **主要风险** | Low / Medium / High:<一句话> |
|
|
15
|
+
|
|
16
|
+
**目标:** <一句话描述可观察的行为变化>
|
|
17
|
+
|
|
18
|
+
## 目标
|
|
19
|
+
|
|
20
|
+
<1-3 句话描述预期可观察行为。>
|
|
21
|
+
|
|
22
|
+
## 验收标准
|
|
23
|
+
|
|
24
|
+
- AC-001: <可验证行为>
|
|
25
|
+
- AC-002: <可验证行为>
|
|
26
|
+
|
|
27
|
+
## 全局约束
|
|
28
|
+
|
|
29
|
+
- <必须保留的架构边界、兼容要求、命令或输出字段>
|
|
30
|
+
- <明确禁止的范围扩张>
|
|
31
|
+
|
|
32
|
+
## 文件边界
|
|
33
|
+
|
|
34
|
+
- `path/a.py`:<职责;只改这个函数或区域>
|
|
35
|
+
- `tests/test_a.py`:<职责;只新增或修改这些测试>
|
|
36
|
+
|
|
37
|
+
## 任务
|
|
38
|
+
|
|
39
|
+
Tiny 任务可保留简化 brief,但仍须写明精确文件边界、完成条件和一条验证命令。
|
|
40
|
+
|
|
41
|
+
### 任务 N:<小目标>
|
|
42
|
+
|
|
43
|
+
**目的**
|
|
44
|
+
- <本任务完成后的可观察行为>
|
|
45
|
+
|
|
46
|
+
**代码事实来源**
|
|
47
|
+
- Read: `path/a.py::function_name`
|
|
48
|
+
- Read: `tests/test_a.py::test_existing_behavior`
|
|
49
|
+
|
|
50
|
+
**文件边界**
|
|
51
|
+
- Create / Modify / Test: `path/a.py`(只改 <函数/区域>)
|
|
52
|
+
- Test: `tests/test_a.py`(只改 <测试/区域>)
|
|
53
|
+
|
|
54
|
+
**关键符号**
|
|
55
|
+
- Change: `function_name()`、`ClassName.method()`、`<config-key>`
|
|
56
|
+
- Preserve: `existing_helper()`、输出字段 `<field>`
|
|
57
|
+
|
|
58
|
+
**实现说明**
|
|
59
|
+
```python
|
|
60
|
+
# 核心控制流草图;按现有代码调整名称。
|
|
61
|
+
# 不要把完整实现复制进计划。
|
|
62
|
+
...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**测试证明**
|
|
66
|
+
- `tests/test_a.py::test_x`:证明 <具体行为>
|
|
67
|
+
- 核心断言:<一句话描述断言,不只有测试名>
|
|
68
|
+
|
|
69
|
+
**验证命令**
|
|
70
|
+
```bash
|
|
71
|
+
<command>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
当改动文件受 `.cowork-flow/spec/<dev_type>/` 规则覆盖时,在这里加入决定命令或规范门禁核对行(`pass` / `not_applicable` 并附理由);这是强复核提醒,不是硬门禁。
|
|
75
|
+
|
|
76
|
+
**完成条件**
|
|
77
|
+
- <2-4 条可检查条件>
|
|
78
|
+
|
|
79
|
+
**禁止漂移**
|
|
80
|
+
- <2-5 条明确禁止的改动>
|
|
81
|
+
|
|
82
|
+
**偏差条件**
|
|
83
|
+
- 如果 <计划外文件/层> 变得必要,必须停止并报告,因为文件边界有误。
|
|
84
|
+
|
|
85
|
+
## 最终验证
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
<command>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
规范核对:逐条核对 `.cowork-flow/spec/<dev_type>/` 适用规范 → <pass / not_applicable + 理由>。
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Decision Anchor Contract
|
|
2
|
+
|
|
3
|
+
**Every task must have a `decision-anchor.md`: the single authoritative decision
|
|
4
|
+
source that prevents drift during execution.**
|
|
5
|
+
|
|
6
|
+
It merges and replaces the former `prd.md`, carrying both "what to do" and "why not
|
|
7
|
+
the alternatives", which compresses the number of task artifacts.
|
|
8
|
+
|
|
9
|
+
## Schema
|
|
10
|
+
|
|
11
|
+
These section names are machine-readable. The runtime parser accepts both the
|
|
12
|
+
Chinese and English heading sets, and new anchors use the language configured in
|
|
13
|
+
`.cowork-flow/config.yaml` (`zh-CN` is the default).
|
|
14
|
+
|
|
15
|
+
```markdown
|
|
16
|
+
# Decision Anchor
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
<one sentence>
|
|
20
|
+
|
|
21
|
+
## Acceptance Criteria
|
|
22
|
+
- [ ] AC-001: <verifiable>
|
|
23
|
+
- [ ] AC-002: <verifiable>
|
|
24
|
+
|
|
25
|
+
## Rejected Options
|
|
26
|
+
- **Option B**: <summary> — rejected because: <why>
|
|
27
|
+
|
|
28
|
+
## Key Assumptions
|
|
29
|
+
- <assumption 1>
|
|
30
|
+
|
|
31
|
+
## Scope Boundary
|
|
32
|
+
- In scope: <list>
|
|
33
|
+
- Out of scope: <list>
|
|
34
|
+
|
|
35
|
+
## Verification Commands
|
|
36
|
+
- `<command>` → <expected>
|
|
37
|
+
- Spec check: clause-by-clause check against applicable `.cowork-flow/spec/<dev_type>/` rules → pass / not_applicable (with reason)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Section requirements
|
|
41
|
+
|
|
42
|
+
Both heading columns below are accepted; the right column is the `en-US` output
|
|
43
|
+
and the left column the `zh-CN` output.
|
|
44
|
+
|
|
45
|
+
| Section | Required | Why |
|
|
46
|
+
|------|------|------|
|
|
47
|
+
| `## Goal` / `## 目标` | required | no goal, no acceptance baseline |
|
|
48
|
+
| `## Acceptance Criteria` / `## 验收标准` | required | `task_start` is blocked without acceptance criteria |
|
|
49
|
+
| `## Rejected Options` / `## 被拒方案` | optional | required for brainstorming tasks, so a rejected option is not reconsidered during execution |
|
|
50
|
+
| `## Key Assumptions` / `## 关键假设` | optional | filled in for complex tasks to surface implicit constraints; not read by the runtime parser |
|
|
51
|
+
| `## Scope Boundary` / `## 范围边界` | optional | prevents scope creep |
|
|
52
|
+
| `## Verification Commands` / `## 验证命令` | optional | filled in for complex tasks, as the basis for automated verification; when the changed files are covered by `.cowork-flow/spec/backend/` or `frontend/` rules, it must also carry the spec/gate check for the task's `dev_type` (deciding command, or clause-by-clause `pass` / `not_applicable` with reason) |
|
|
53
|
+
|
|
54
|
+
### Machine-readable line formats (for injection)
|
|
55
|
+
|
|
56
|
+
`## Verification Commands` and `## Scope Boundary` are parsed by stage-contract
|
|
57
|
+
injection and by the MCP fact view (`services/fact_view.parse_decision_anchor`).
|
|
58
|
+
Line-format conventions:
|
|
59
|
+
|
|
60
|
+
- `## Verification Commands`: one command per line (an optional `- ` prefix),
|
|
61
|
+
truncated to 120 characters, at most 10 lines; the implementing agent runs them
|
|
62
|
+
itself (self-declared, self-checked) and the reviewer verifies the execution
|
|
63
|
+
evidence. When the changed files are covered by `.cowork-flow/spec/<dev_type>/`
|
|
64
|
+
rules, the section must also carry the spec/gate check for the task's
|
|
65
|
+
`dev_type`: a deciding command, or a `Spec check:` line reviewed clause by
|
|
66
|
+
clause (`pass` / `not_applicable` + reason). This is a strong review reminder,
|
|
67
|
+
not a `spec-check` hard gate.
|
|
68
|
+
- `## Scope Boundary`: only the first non-empty line is taken, truncated to 160
|
|
69
|
+
characters.
|
|
70
|
+
|
|
71
|
+
## Scenarios
|
|
72
|
+
|
|
73
|
+
| Scenario | Source | What gets filled in |
|
|
74
|
+
|------|------|---------|
|
|
75
|
+
| Has brainstorming | extracted from the discussion outcome | every section |
|
|
76
|
+
| No brainstorming (requirements already clear) | extracted from the requirement description and the plan | the minimum: goal + acceptance criteria |
|
|
77
|
+
|
|
78
|
+
## Artifact compression
|
|
79
|
+
|
|
80
|
+
Before merging (6 files): `prd.md` + `implement.jsonl` + `check.jsonl` +
|
|
81
|
+
`debug.jsonl` + `task.json`
|
|
82
|
+
|
|
83
|
+
After merging (5 files): `decision-anchor.md` + `implement.jsonl` + `check.jsonl` +
|
|
84
|
+
`debug.jsonl` + `task.json`
|
|
85
|
+
|
|
86
|
+
**A net reduction of one file type. Related-file tracking is now owned entirely by
|
|
87
|
+
`implement.jsonl`.**
|
|
88
|
+
|
|
89
|
+
## Lifecycle check coupling
|
|
90
|
+
|
|
91
|
+
- The preflight for `task next <dir> --run` checks that `decision-anchor.md` is
|
|
92
|
+
non-empty and requires the `## Goal` / `## 目标` and `## Acceptance Criteria` /
|
|
93
|
+
`## 验收标准` sections to be present.
|
|
94
|
+
- **Gate profile `meta.gateProfile`**: a newly created task is written as
|
|
95
|
+
`"strict"`; an unmarked task is treated as `"legacy"` — existing tasks are not
|
|
96
|
+
retro-fitted with the stricter gate, and the profile is queryable through the
|
|
97
|
+
fact view. On top of the section check, a strict task additionally requires: at
|
|
98
|
+
least one parseable `- [ ] AC-xxx: ...` row in `## Acceptance Criteria` /
|
|
99
|
+
`## 验收标准`, no duplicate AC ids, no non-AC list rows in that section, and a
|
|
100
|
+
declared `dev_type` (otherwise `task specs` dev-type dispatch silently comes
|
|
101
|
+
back shorter).
|
|
102
|
+
- An acceptance row's checkbox state (`- [ ]` / `- [x]`) and the evidence records
|
|
103
|
+
(the task directory's `evidence.jsonl`) are the fact source of the completion
|
|
104
|
+
gate: completion is blocked when an unchecked AC exists (`LIFECYCLE-AC-001`), a
|
|
105
|
+
declared AC has no evidence (`LIFECYCLE-AC-002`), a strict task cannot read any
|
|
106
|
+
AC row (`LIFECYCLE-AC-003`), or the AC-evidence module is unavailable
|
|
107
|
+
(`LIFECYCLE-AC-004`). Schema and exemptions are in `evidence-completion.md`.
|
|
108
|
+
- **Review receipt change fingerprint** (a strict task requires its latest receipt
|
|
109
|
+
to match the current state at completion, `LIFECYCLE-REVIEW-003`). Input = the
|
|
110
|
+
git-visible change set (paths + content digests) + this anchor's goal and AC text
|
|
111
|
+
+ the content digest of the bound plan + `dev_type` + the **content digest** of
|
|
112
|
+
every referenced spec + the content digest of `implement.jsonl`. Both `spec/**`
|
|
113
|
+
and `tasks/**` are gitignored, which is why the last two must enter the
|
|
114
|
+
fingerprint explicitly — otherwise rewriting a referenced spec's prose, or
|
|
115
|
+
widening the declared scope, would leave a stale receipt valid.
|
|
116
|
+
- Fingerprint **exclusions** (mechanism): the change set excludes the whole
|
|
117
|
+
`.cowork-flow/tasks/**` tree, `.cowork-flow/.runtime/**`, and
|
|
118
|
+
`.cowork-flow/logs/**` — task-directory artifacts (`task.json`,
|
|
119
|
+
`evidence.jsonl`, `review.jsonl`, `check.jsonl`, `debug.jsonl`, and the anchor's
|
|
120
|
+
**path entry**) and runtime metadata are not counted, or a receipt would
|
|
121
|
+
invalidate itself the moment it was recorded. `implement.jsonl`'s path entry is
|
|
122
|
+
excluded the same way; its **content** is counted instead through the `scope`
|
|
123
|
+
component.
|
|
124
|
+
A **consequence** (expected cost, not a defect): **any byte change in
|
|
125
|
+
`implement.jsonl`** (appending or removing a scope entry, rewording a `reason`,
|
|
126
|
+
a domain-guide entry) **or a rewrite of a
|
|
127
|
+
referenced spec's prose invalidates an existing receipt**, requiring one more
|
|
128
|
+
review run against the final state. Appending evidence, ticking ACs, and runtime
|
|
129
|
+
metadata are not in that set.
|
|
130
|
+
- **Light profile `meta.taskType: "tiny"`**: applies to `strict` tasks only
|
|
131
|
+
(`legacy` + `tiny` stays a compatibility path). A light task **is still strict**
|
|
132
|
+
— what it waives are three things: the review receipt, AC evidence, and
|
|
133
|
+
`dev_type`. It does **not** waive the file-scope gate or "AC rows
|
|
134
|
+
are non-empty" (`LIFECYCLE-AC-003`/`004` still apply). Plan-file binding is not
|
|
135
|
+
among the three, but the pre-existing field-level exemption for tiny applies, so
|
|
136
|
+
a light task does not require `planFile` either. `tiny` **can only be declared at
|
|
137
|
+
creation time** (`task next --run --task-type tiny`) with no later edit path; the
|
|
138
|
+
light profile is visible in `task next --list` (a `[light]` text marker and
|
|
139
|
+
`lightProfile` in `--json`) and in doctor's task-hygiene output. The single
|
|
140
|
+
decision source is `task_gate_profiles.is_light_task` (`strict` + the field is
|
|
141
|
+
`tiny`); the start-preflight plan-file exemption still uses the field-level
|
|
142
|
+
`declares_light_profile` so existing pre-strict tiny tasks keep behaving as
|
|
143
|
+
before. The light profile **must not** be used for a task touching runtime /
|
|
144
|
+
gates / specs — that rule rests on visibility and review, not on a machine gate.
|
|
145
|
+
- Read by `cowork-implement`: see .cowork-flow/spec/contracts/subagent-dispatch.md
|
|
146
|
+
- Read by `cowork-check`: same as above
|
|
147
|
+
- `debug.jsonl` reference: optional; on deviation diagnosis it records the anchor
|
|
148
|
+
delta
|
|
149
|
+
|
|
150
|
+
## Formal-release behavior
|
|
151
|
+
|
|
152
|
+
The formal release does not auto-migrate a legacy `prd.md`. When a task is
|
|
153
|
+
started, only `decision-anchor.md` is recognized as the authoritative decision
|
|
154
|
+
source; if a task directory has only `prd.md` and no `decision-anchor.md`,
|
|
155
|
+
`task next <dir> --run` must fail-closed and report
|
|
156
|
+
`decision-anchor.md is missing or empty`.
|
|
157
|
+
|
|
158
|
+
An existing legacy task needs a human to convert the `prd.md` content into
|
|
159
|
+
`decision-anchor.md`, fill in `## Goal` / `## 目标` and
|
|
160
|
+
`## Acceptance Criteria` / `## 验收标准`, and only then start.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# <Feature> Implementation Plan
|
|
2
|
+
|
|
3
|
+
> Execution rule: this plan exists to prevent implementation drift. The implementer must work through tasks in order.
|
|
4
|
+
> If the plan conflicts with current code facts, a plan-external file becomes necessary, or a test fails for an unplanned reason, stop and report back instead of expanding scope.
|
|
5
|
+
>
|
|
6
|
+
> For formal fixed-agent work: create a runtime context with `.cowork-flow/run subagent init`, pass `cowork_runtime_context_id: <runtime_context_id>` through the active Host Adapter, then dispatch `cowork-implement` or `cowork-check`. Close the runtime context after verification.
|
|
7
|
+
|
|
8
|
+
| Field | Content |
|
|
9
|
+
|----|------|
|
|
10
|
+
| **Task Type** | Tiny / Normal / High-risk: <reason> |
|
|
11
|
+
| **Strategy** | Serial / Parallel: <why> |
|
|
12
|
+
| **Success Criteria** | AC-001 / AC-002 / AC-003 |
|
|
13
|
+
| **Final Verification** | `<command>` -> exit 0; clause-by-clause check against applicable `.cowork-flow/spec/<dev_type>/` rules and project gates -> `pass` / `not_applicable` per clause |
|
|
14
|
+
| **Primary Risk** | Low / Medium / High: <one sentence> |
|
|
15
|
+
|
|
16
|
+
**Goal:** <One sentence describing the observable behavior change>
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
|
|
20
|
+
<1-3 sentences describing the expected observable behavior.>
|
|
21
|
+
|
|
22
|
+
## Acceptance Criteria
|
|
23
|
+
|
|
24
|
+
- AC-001: <observable behavior>
|
|
25
|
+
- AC-002: <observable behavior>
|
|
26
|
+
|
|
27
|
+
## Global Constraints
|
|
28
|
+
|
|
29
|
+
- <Architecture boundary, compatibility requirement, command, or output field that must be preserved>
|
|
30
|
+
- <Explicitly prohibited scope expansion>
|
|
31
|
+
|
|
32
|
+
## File Boundaries
|
|
33
|
+
|
|
34
|
+
- `path/a.py`: <responsibility; only change this function or region>
|
|
35
|
+
- `tests/test_a.py`: <responsibility; only add or modify these tests>
|
|
36
|
+
|
|
37
|
+
## Tasks
|
|
38
|
+
|
|
39
|
+
Tiny plans may keep a shorter brief, but must still name exact file boundaries, completion conditions, and one verification command.
|
|
40
|
+
|
|
41
|
+
### Task N: <small goal>
|
|
42
|
+
|
|
43
|
+
**Purpose**
|
|
44
|
+
- <Observable behavior after this task is complete>
|
|
45
|
+
|
|
46
|
+
**Code Fact Sources**
|
|
47
|
+
- Read: `path/a.py::function_name`
|
|
48
|
+
- Read: `tests/test_a.py::test_existing_behavior`
|
|
49
|
+
|
|
50
|
+
**File Boundaries**
|
|
51
|
+
- Create / Modify / Test: `path/a.py` (only change <function/region>)
|
|
52
|
+
- Test: `tests/test_a.py` (only change <test/region>)
|
|
53
|
+
|
|
54
|
+
**Key Symbols**
|
|
55
|
+
- Change: `function_name()`, `ClassName.method()`, `<config-key>`
|
|
56
|
+
- Preserve: `existing_helper()`, output field `<field>`
|
|
57
|
+
|
|
58
|
+
**Implementation Notes**
|
|
59
|
+
```python
|
|
60
|
+
# Core control-flow sketch; adjust names to match existing code.
|
|
61
|
+
# Do not copy a complete implementation into the plan.
|
|
62
|
+
...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Test Proof**
|
|
66
|
+
- `tests/test_a.py::test_x`: proves <specific behavior>
|
|
67
|
+
- Core assertion: <one sentence describing the assertion, not only the test name>
|
|
68
|
+
|
|
69
|
+
**Verification Command**
|
|
70
|
+
```bash
|
|
71
|
+
<command>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
When changed files are covered by `.cowork-flow/spec/<dev_type>/` rules, add the deciding command or a spec/gate check line (`pass` / `not_applicable` with reason) here; it is a strong review reminder, not a hard gate.
|
|
75
|
+
|
|
76
|
+
**Completion Conditions**
|
|
77
|
+
- <2-4 checkable conditions>
|
|
78
|
+
|
|
79
|
+
**Prohibited Drift**
|
|
80
|
+
- <2-5 explicit prohibited changes>
|
|
81
|
+
|
|
82
|
+
**Deviation Conditions**
|
|
83
|
+
- If <plan-external file/layer> becomes necessary, must stop and report back because the file boundary is wrong.
|
|
84
|
+
|
|
85
|
+
## Final Verification
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
<command>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Spec check: clause-by-clause check against applicable `.cowork-flow/spec/<dev_type>/` rules -> <pass / not_applicable + reason>.
|
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import argparse
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
|
+
import re
|
|
9
10
|
import sys
|
|
10
11
|
from datetime import datetime, timezone
|
|
11
12
|
from pathlib import Path
|
|
@@ -31,6 +32,7 @@ from adapters.host.host_manifest import (
|
|
|
31
32
|
validate_host_assets,
|
|
32
33
|
)
|
|
33
34
|
from infra.paths import DIR_WORKFLOW, get_repo_root
|
|
35
|
+
from infra.config import UnsupportedLanguageError, get_language
|
|
34
36
|
from infra.skill_manifest import SkillManifestError, action_owners, load_skill_manifests
|
|
35
37
|
from infra.storage.operation_log import OperationLog
|
|
36
38
|
from infra.storage.state_store import DEFAULT_STALE_LOCK_SECONDS, StateStore
|
|
@@ -205,6 +207,101 @@ def check_distribution(repo_root: Path) -> list[str]:
|
|
|
205
207
|
return errors
|
|
206
208
|
|
|
207
209
|
|
|
210
|
+
_ANCHOR_LANGUAGE_CHECKS = {
|
|
211
|
+
"zh-CN": ("## 目标", "## 验收标准", "## 被拒方案", "## 验证命令"),
|
|
212
|
+
"en-US": ("## Goal", "## Acceptance Criteria", "## Rejected Options", "## Verification Commands"),
|
|
213
|
+
}
|
|
214
|
+
_PLAN_LANGUAGE_CHECKS = {
|
|
215
|
+
"zh-CN": re.compile(r"(?m)^\*\*目标(?::\*\*|\*\*:)"),
|
|
216
|
+
"en-US": re.compile(r"(?m)^\*\*Goal(?::\*\*|\*\*:)"),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _contract_block(text: str) -> str:
|
|
221
|
+
"""The schema example is the machine-readable heading source."""
|
|
222
|
+
match = re.search(r"```markdown\n(.*?)```", text, re.DOTALL)
|
|
223
|
+
return match.group(1) if match else text
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _asset_language(path: Path, kind: str) -> str | None:
|
|
227
|
+
"""Detected output language, `mixed`, or None when the asset is absent."""
|
|
228
|
+
try:
|
|
229
|
+
text = path.read_text(encoding="utf-8")
|
|
230
|
+
except (OSError, UnicodeDecodeError):
|
|
231
|
+
return None
|
|
232
|
+
if kind == "anchor":
|
|
233
|
+
block = _contract_block(text)
|
|
234
|
+
present = {
|
|
235
|
+
language
|
|
236
|
+
for language, markers in _ANCHOR_LANGUAGE_CHECKS.items()
|
|
237
|
+
if all(marker in block for marker in markers)
|
|
238
|
+
}
|
|
239
|
+
else:
|
|
240
|
+
present = {
|
|
241
|
+
language
|
|
242
|
+
for language, pattern in _PLAN_LANGUAGE_CHECKS.items()
|
|
243
|
+
if pattern.search(text)
|
|
244
|
+
}
|
|
245
|
+
if len(present) > 1:
|
|
246
|
+
return "mixed"
|
|
247
|
+
return next(iter(present), None)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def check_language(repo_root: Path) -> list[dict[str, str]]:
|
|
251
|
+
"""Report an invalid `language` value and localized-asset drift.
|
|
252
|
+
|
|
253
|
+
A missing config key or a missing asset means the project predates the
|
|
254
|
+
language option: it is not drift, so it stays silent until the first sync.
|
|
255
|
+
"""
|
|
256
|
+
config_file = repo_root / DIR_WORKFLOW / "config.yaml"
|
|
257
|
+
try:
|
|
258
|
+
config_text = config_file.read_text(encoding="utf-8")
|
|
259
|
+
except (OSError, UnicodeDecodeError):
|
|
260
|
+
return []
|
|
261
|
+
if not re.search(r"(?m)^language\s*:", config_text):
|
|
262
|
+
return []
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
language = get_language(repo_root, strict=True)
|
|
266
|
+
except UnsupportedLanguageError as error:
|
|
267
|
+
return [
|
|
268
|
+
_issue(
|
|
269
|
+
code="CONFIG-LANGUAGE-001",
|
|
270
|
+
severity="error",
|
|
271
|
+
path=config_file.relative_to(repo_root).as_posix(),
|
|
272
|
+
message=str(error),
|
|
273
|
+
command_hint="Edit .cowork-flow/config.yaml, then run cwf project sync",
|
|
274
|
+
contract="runtime-health:language",
|
|
275
|
+
)
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
issues: list[dict[str, str]] = []
|
|
279
|
+
assets = (
|
|
280
|
+
(".cowork-flow/spec/contracts/decision-anchor.md", "anchor"),
|
|
281
|
+
(".cowork-flow/templates/plan-template.md", "plan"),
|
|
282
|
+
)
|
|
283
|
+
for relative, kind in assets:
|
|
284
|
+
path = repo_root / relative
|
|
285
|
+
detected = _asset_language(path, kind)
|
|
286
|
+
if detected is None:
|
|
287
|
+
continue
|
|
288
|
+
if detected != language:
|
|
289
|
+
issues.append(
|
|
290
|
+
_issue(
|
|
291
|
+
code="LANGUAGE-DRIFT-001",
|
|
292
|
+
severity="warning",
|
|
293
|
+
path=relative,
|
|
294
|
+
message=(
|
|
295
|
+
f"installed asset language is {detected}, but "
|
|
296
|
+
f".cowork-flow/config.yaml selects {language}"
|
|
297
|
+
),
|
|
298
|
+
command_hint="cwf project sync",
|
|
299
|
+
contract="runtime-health:language",
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
return issues
|
|
303
|
+
|
|
304
|
+
|
|
208
305
|
def _task_path(repo_root: Path, task_dir: Path) -> str:
|
|
209
306
|
try:
|
|
210
307
|
return task_dir.resolve().relative_to(repo_root.resolve()).as_posix()
|
|
@@ -1673,6 +1770,7 @@ def _all_check_result(repo_root: Path) -> dict[str, object]:
|
|
|
1673
1770
|
host_issues = _host_issues(repo_root)
|
|
1674
1771
|
runtime_errors = check_runtime(repo_root)
|
|
1675
1772
|
distribution_errors = check_distribution(repo_root)
|
|
1773
|
+
language_issues = check_language(repo_root)
|
|
1676
1774
|
task_hygiene_issues = check_task_hygiene(repo_root)
|
|
1677
1775
|
state_recovery_issues = check_state_recovery(repo_root)
|
|
1678
1776
|
session_hygiene_issues = check_session_hygiene(repo_root)
|
|
@@ -1697,11 +1795,17 @@ def _all_check_result(repo_root: Path) -> dict[str, object]:
|
|
|
1697
1795
|
{"kind": "distribution", "message": error}
|
|
1698
1796
|
for error in distribution_errors
|
|
1699
1797
|
)
|
|
1798
|
+
errors.extend(
|
|
1799
|
+
{"kind": "language", **issue}
|
|
1800
|
+
for issue in language_issues
|
|
1801
|
+
if issue["severity"] == "error"
|
|
1802
|
+
)
|
|
1700
1803
|
return {
|
|
1701
1804
|
"ok": not errors,
|
|
1702
1805
|
"errors": errors,
|
|
1703
1806
|
"issues": {
|
|
1704
1807
|
"hostAdapters": host_issues,
|
|
1808
|
+
"language": language_issues,
|
|
1705
1809
|
"taskHygiene": task_hygiene_issues,
|
|
1706
1810
|
"stateRecovery": state_recovery_issues,
|
|
1707
1811
|
"sessionHygiene": session_hygiene_issues,
|
|
@@ -1725,6 +1829,12 @@ def _run_checks(repo_root: Path, *, structured: bool = False) -> int:
|
|
|
1725
1829
|
if structured:
|
|
1726
1830
|
print(json.dumps(result, ensure_ascii=False))
|
|
1727
1831
|
return 1 if errors else 0
|
|
1832
|
+
for issue in result["issues"]["language"]:
|
|
1833
|
+
if issue["severity"] == "error":
|
|
1834
|
+
continue
|
|
1835
|
+
print(f"Language ({issue['code']}): {issue['message']}")
|
|
1836
|
+
if issue.get("commandHint"):
|
|
1837
|
+
print(f" fix: {issue['commandHint']}")
|
|
1728
1838
|
_print_task_hygiene_issues(result["issues"]["taskHygiene"])
|
|
1729
1839
|
_print_state_recovery_issues(result["issues"]["stateRecovery"])
|
|
1730
1840
|
_print_session_hygiene_issues(result["issues"]["sessionHygiene"])
|