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.
Files changed (61) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.en.md +46 -22
  3. package/README.md +10 -7
  4. package/docs/architecture.en.md +95 -0
  5. package/docs/architecture.md +7 -3
  6. package/docs/direction.en.md +71 -0
  7. package/docs/direction.md +6 -1
  8. package/docs/hosts.en.md +299 -0
  9. package/docs/hosts.md +5 -1
  10. package/docs/index.en.md +30 -0
  11. package/docs/index.md +2 -0
  12. package/docs/release.en.md +108 -0
  13. package/docs/release.md +2 -0
  14. package/package.json +2 -2
  15. package/presets/claude-code/.claude-plugin/plugin.json +1 -1
  16. package/presets/claude-code/skills/cowork-flow-bootstrap/SKILL.md +8 -2
  17. package/presets/codex/.codex-plugin/plugin.json +1 -1
  18. package/presets/codex/skills/cowork-flow-bootstrap/SKILL.md +8 -2
  19. package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
  20. package/presets/kimi-code/skills/cowork-flow-bootstrap/SKILL.md +8 -2
  21. package/presets/opencode/cowork-flow/skills/cowork-flow-bootstrap/SKILL.md +8 -2
  22. package/presets/qoder/.qoder-plugin/plugin.json +1 -1
  23. package/presets/qoder/skills/cowork-flow-bootstrap/SKILL.md +8 -2
  24. package/presets/zcode/.zcode-plugin/plugin.json +1 -1
  25. package/presets/zcode/skills/cowork-flow-bootstrap/SKILL.md +8 -2
  26. package/src/cli.js +1 -1
  27. package/src/commands/init.js +200 -16
  28. package/src/commands/registry.js +10 -7
  29. package/src/commands/source-refresh.js +5 -1
  30. package/src/commands/sync.js +4 -1
  31. package/src/lib/copy-template.js +23 -5
  32. package/src/lib/host-assets.js +119 -4
  33. package/src/lib/language.js +162 -0
  34. package/template/.cowork-flow/.version +1 -1
  35. package/template/.cowork-flow/config.yaml +8 -0
  36. package/template/.cowork-flow/scripts/adapters/cli/task_create_command.py +5 -3
  37. package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +71 -2
  38. package/template/.cowork-flow/scripts/infra/config.py +85 -0
  39. package/template/.cowork-flow/scripts/services/decision_anchor_schema.py +104 -0
  40. package/template/.cowork-flow/scripts/services/fact_view.py +21 -15
  41. package/template/.cowork-flow/scripts/services/task_context.py +13 -7
  42. package/template/.cowork-flow/scripts/services/task_creation.py +72 -30
  43. package/template/.cowork-flow/scripts/services/task_gate_profiles.py +7 -24
  44. package/template/.cowork-flow/spec/contracts/context-injection.md +5 -4
  45. package/template/.cowork-flow/spec/contracts/decision-anchor.md +21 -14
  46. package/template/.cowork-flow/spec/contracts/plan-binding.md +2 -1
  47. package/template/.cowork-flow/spec/index.md +26 -26
  48. package/template/.cowork-flow/spec/references/definition-of-done.md +26 -26
  49. package/template/.cowork-flow/spec/references/index.md +16 -16
  50. package/template/.cowork-flow/spec/references/orchestration-patterns.md +71 -71
  51. package/template/.cowork-flow/spec/references/security-checklist.md +37 -37
  52. package/template/.cowork-flow/spec/references/testing-checklist.md +32 -32
  53. package/template/.cowork-flow/spec/runtime/host-assets.json +23 -4
  54. package/template/.cowork-flow/spec/runtime/index.md +10 -12
  55. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +31 -0
  56. package/template/.cowork-flow/spec/schemas/index.md +5 -5
  57. package/template/.cowork-flow/templates/plan-template.md +91 -0
  58. package/template/locales/en-US/.cowork-flow/spec/contracts/decision-anchor.md +160 -0
  59. package/template/locales/en-US/.cowork-flow/templates/plan-template.md +91 -0
  60. package/template/skills/runtime-health/scripts/doctor.py +110 -0
  61. package/template/skills/task-planning/SKILL.md +19 -92
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+ """Shared decision-anchor heading schema.
3
+
4
+ The runtime recognizes two heading sets for the same sections: the historical
5
+ Chinese set and the English aliases introduced for English-language task
6
+ artifacts. Recognition is language-independent so existing tasks and mixed
7
+ language projects keep working; the configured project language only decides
8
+ which set a newly generated anchor uses.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+
15
+ GOAL = "goal"
16
+ ACCEPTANCE = "acceptance"
17
+ REJECTED = "rejected"
18
+ ASSUMPTIONS = "assumptions"
19
+ SCOPE = "scope"
20
+ VERIFICATION = "verification"
21
+
22
+ REQUIRED_SECTIONS = (GOAL, ACCEPTANCE)
23
+
24
+ DEFAULT_LANGUAGE = "zh-CN"
25
+
26
+ LANGUAGE_HEADINGS: dict[str, dict[str, str]] = {
27
+ "zh-CN": {
28
+ GOAL: "目标",
29
+ ACCEPTANCE: "验收标准",
30
+ REJECTED: "被拒方案",
31
+ ASSUMPTIONS: "关键假设",
32
+ SCOPE: "范围边界",
33
+ VERIFICATION: "验证命令",
34
+ },
35
+ "en-US": {
36
+ GOAL: "Goal",
37
+ ACCEPTANCE: "Acceptance Criteria",
38
+ REJECTED: "Rejected Options",
39
+ ASSUMPTIONS: "Key Assumptions",
40
+ SCOPE: "Scope Boundary",
41
+ VERIFICATION: "Verification Commands",
42
+ },
43
+ }
44
+
45
+ SECTION_ALIASES: dict[str, str] = {
46
+ heading: section
47
+ for headings in LANGUAGE_HEADINGS.values()
48
+ for section, heading in headings.items()
49
+ }
50
+
51
+ HEADING_RE = re.compile(r"^##\s*(.+?)\s*$")
52
+ ACCEPTANCE_RE = re.compile(
53
+ r"^\s*-\s*(?:\[([ xX])\]?\s*)?(AC-[A-Za-z0-9-]+)\s*[::]\s*(.+?)\s*$"
54
+ )
55
+ LIST_ITEM_RE = re.compile(r"^\s*[-*]\s")
56
+
57
+
58
+ def canonical_section(heading: str) -> str | None:
59
+ """Canonical section key for a recognized heading, or None."""
60
+ return SECTION_ALIASES.get(heading)
61
+
62
+
63
+ def headings_for(language: str) -> dict[str, str]:
64
+ """Heading set for a language, falling back to the default language."""
65
+ return LANGUAGE_HEADINGS.get(language, LANGUAGE_HEADINGS[DEFAULT_LANGUAGE])
66
+
67
+
68
+ def heading_for(section: str, language: str) -> str:
69
+ """One heading in the requested language, falling back to the default."""
70
+ return headings_for(language)[section]
71
+
72
+
73
+ def section_headings(section: str) -> tuple[str, ...]:
74
+ """Every recognized heading for one section, default language first."""
75
+ return tuple(
76
+ LANGUAGE_HEADINGS[language][section] for language in LANGUAGE_HEADINGS
77
+ )
78
+
79
+
80
+ def present_sections(text: str) -> set[str]:
81
+ """Canonical keys of the recognized `##` sections present in the text."""
82
+ present: set[str] = set()
83
+ for raw in text.splitlines():
84
+ heading = HEADING_RE.match(raw.strip())
85
+ if not heading:
86
+ continue
87
+ section = canonical_section(heading.group(1))
88
+ if section is not None:
89
+ present.add(section)
90
+ return present
91
+
92
+
93
+ def acceptance_section_lines(text: str) -> list[str]:
94
+ """Raw lines under the acceptance heading, up to the next heading."""
95
+ lines: list[str] = []
96
+ inside = False
97
+ for raw in text.splitlines():
98
+ heading = HEADING_RE.match(raw.strip())
99
+ if heading:
100
+ inside = canonical_section(heading.group(1)) == ACCEPTANCE
101
+ continue
102
+ if inside:
103
+ lines.append(raw)
104
+ return lines
@@ -17,23 +17,30 @@ from datetime import datetime, timezone
17
17
  from pathlib import Path
18
18
  from typing import Any
19
19
 
20
+ from services.decision_anchor_schema import (
21
+ ACCEPTANCE,
22
+ ACCEPTANCE_RE,
23
+ GOAL,
24
+ HEADING_RE,
25
+ REJECTED,
26
+ SCOPE,
27
+ VERIFICATION,
28
+ canonical_section,
29
+ )
30
+
20
31
  GOAL_TRUNCATE = 300
21
32
  VERIFY_COMMAND_TRUNCATE = 120
22
33
  VERIFY_COMMAND_MAX = 10
23
34
  SCOPE_BOUNDARY_TRUNCATE = 160
24
- ANCHOR_SECTIONS = ("目标", "验收标准", "被拒方案", "验证命令", "范围边界")
25
- ACCEPTANCE_RE = re.compile(
26
- r"^\s*-\s*(?:\[([ xX])\]?\s*)?(AC-[A-Za-z0-9-]+)\s*[::]\s*(.+?)\s*$"
27
- )
28
35
  REJECTED_RE = re.compile(r"^\s*-\s*\*\*(.+?)\*\*")
29
- _HEADING_RE = re.compile(r"^##\s*(.+?)\s*$")
30
36
 
31
37
 
32
38
  def parse_decision_anchor(text: str) -> dict[str, Any]:
33
39
  """Extract goal / acceptance criteria / rejected option names / declared
34
40
  verification commands / scope boundary from a decision-anchor.md body.
35
- Section format is frozen by spec/contracts/decision-anchor.md; parsing is
36
- line-level and degrades to empty values when sections are missing."""
41
+ Section headings may be the Chinese set or the English aliases (see
42
+ spec/contracts/decision-anchor.md); parsing is line-level and degrades to
43
+ empty values when sections are missing."""
37
44
  goal_lines: list[str] = []
38
45
  acceptance: list[dict[str, str]] = []
39
46
  rejected: list[str] = []
@@ -41,15 +48,14 @@ def parse_decision_anchor(text: str) -> dict[str, Any]:
41
48
  scope_boundary = ""
42
49
  section: str | None = None
43
50
  for raw in text.splitlines():
44
- heading = _HEADING_RE.match(raw.strip())
51
+ heading = HEADING_RE.match(raw.strip())
45
52
  if heading:
46
- name = heading.group(1)
47
- section = name if name in ANCHOR_SECTIONS else None
53
+ section = canonical_section(heading.group(1))
48
54
  continue
49
- if section == "目标":
55
+ if section == GOAL:
50
56
  if raw.strip():
51
57
  goal_lines.append(raw.strip())
52
- elif section == "验收标准":
58
+ elif section == ACCEPTANCE:
53
59
  match = ACCEPTANCE_RE.match(raw)
54
60
  if match:
55
61
  acceptance.append(
@@ -59,17 +65,17 @@ def parse_decision_anchor(text: str) -> dict[str, Any]:
59
65
  "checked": match.group(1) in ("x", "X"),
60
66
  }
61
67
  )
62
- elif section == "被拒方案":
68
+ elif section == REJECTED:
63
69
  match = REJECTED_RE.match(raw)
64
70
  if match:
65
71
  rejected.append(match.group(1).strip())
66
- elif section == "验证命令":
72
+ elif section == VERIFICATION:
67
73
  command = raw.strip()
68
74
  if command.startswith("- "):
69
75
  command = command[2:].strip()
70
76
  if command and len(verify_commands) < VERIFY_COMMAND_MAX:
71
77
  verify_commands.append(command[:VERIFY_COMMAND_TRUNCATE])
72
- elif section == "范围边界":
78
+ elif section == SCOPE:
73
79
  if raw.strip() and not scope_boundary:
74
80
  scope_boundary = raw.strip()[:SCOPE_BOUNDARY_TRUNCATE]
75
81
  return {
@@ -32,6 +32,11 @@ from services.context_paths import (
32
32
  normalize_context_path,
33
33
  prepare_context_entry,
34
34
  )
35
+ from services.decision_anchor_schema import (
36
+ REQUIRED_SECTIONS,
37
+ present_sections,
38
+ section_headings,
39
+ )
35
40
  from services.plan_binding import normalize_plan_file, plan_file_blockers
36
41
  from services.task_gate_profiles import (
37
42
  GATE_PROFILE_STRICT,
@@ -360,13 +365,14 @@ class TaskContextService:
360
365
 
361
366
  @staticmethod
362
367
  def _decision_anchor_section_blockers(anchor_text: str) -> list[str]:
363
- blockers: list[str] = []
364
- for section in ("## 目标", "## 验收标准"):
365
- if section not in anchor_text:
366
- blockers.append(
367
- f"decision-anchor.md missing required section: {section}"
368
- )
369
- return blockers
368
+ present = present_sections(anchor_text)
369
+ return [
370
+ "decision-anchor.md missing required section: "
371
+ + " (or ".join(f"## {heading}" for heading in section_headings(section))
372
+ + ")"
373
+ for section in REQUIRED_SECTIONS
374
+ if section not in present
375
+ ]
370
376
 
371
377
  @staticmethod
372
378
  def _is_tiny_task(task_data: dict) -> bool:
@@ -6,7 +6,14 @@ from __future__ import annotations
6
6
  from dataclasses import dataclass
7
7
  from datetime import datetime
8
8
  from pathlib import Path
9
+ import re
9
10
 
11
+ from infra.config import get_language
12
+ from services.decision_anchor_schema import (
13
+ ACCEPTANCE,
14
+ GOAL,
15
+ heading_for,
16
+ )
10
17
  from services.task_tree import TaskTreeError, TaskTreeService
11
18
  from services import task_graph
12
19
  from infra.paths import (
@@ -19,6 +26,20 @@ from services.task_gate_profiles import GATE_PROFILE_STRICT
19
26
  from services.task_repository import TaskRepository, TaskRepositoryError
20
27
 
21
28
 
29
+ _GOAL_LINE_RE = re.compile(r"^\*\*(?:Goal|目标):\*\*\s*(.+?)\s*$")
30
+ _GOAL_BOLD_COLON_RE = re.compile(r"^\*\*(?:Goal|目标)\*\*:\s*(.+?)\s*$")
31
+ _GOAL_TABLE_RE = re.compile(
32
+ r"^\|\s*\*\*(?:Goal|目标)\*\*\s*\|\s*(.+?)\s*\|\s*$"
33
+ )
34
+
35
+ _ANCHOR_PLACEHOLDERS = {
36
+ ("zh-CN", GOAL): "<!-- 用一句话描述可观察的目标。 -->",
37
+ ("zh-CN", ACCEPTANCE): "<!-- 至少一条,例如:- [ ] AC-001: ... -->",
38
+ ("en-US", GOAL): "<!-- One sentence describing the observable outcome. -->",
39
+ ("en-US", ACCEPTANCE): "<!-- At least one, e.g. - [ ] AC-001: ... -->",
40
+ }
41
+
42
+
22
43
  class TaskCreationError(RuntimeError):
23
44
  """Raised when task creation cannot be completed."""
24
45
 
@@ -66,6 +87,25 @@ class TaskCreationResult:
66
87
  linked_parent: str | None
67
88
  missing_parent: str | None
68
89
  generated_anchor: bool
90
+ generated_anchor_from_plan: bool
91
+
92
+
93
+ def _extract_plan_goal(plan_text: str) -> str | None:
94
+ """First machine-readable goal line, then the legacy table row."""
95
+ for raw in plan_text.splitlines():
96
+ line = raw.strip()
97
+ for pattern in (_GOAL_LINE_RE, _GOAL_BOLD_COLON_RE, _GOAL_TABLE_RE):
98
+ match = pattern.match(line)
99
+ if match:
100
+ return match.group(1).strip()
101
+ return None
102
+
103
+
104
+ def _anchor_placeholder(section: str, language: str) -> str:
105
+ return _ANCHOR_PLACEHOLDERS.get(
106
+ (language, section),
107
+ _ANCHOR_PLACEHOLDERS[("zh-CN", section)],
108
+ )
69
109
 
70
110
 
71
111
  def ensure_tasks_dir(repo_root: Path) -> Path:
@@ -122,7 +162,7 @@ class TaskCreationService:
122
162
  created_files.append(task_dir / FILE_TASK_JSON)
123
163
 
124
164
  linked_parent, missing_parent = self._link_parent(request, task_dir)
125
- generated_anchor = self._generate_anchor(
165
+ generated_anchor, generated_anchor_from_plan = self._generate_anchor(
126
166
  task_dir,
127
167
  bound_plan_path,
128
168
  )
@@ -136,6 +176,7 @@ class TaskCreationService:
136
176
  linked_parent=linked_parent,
137
177
  missing_parent=missing_parent,
138
178
  generated_anchor=generated_anchor,
179
+ generated_anchor_from_plan=generated_anchor_from_plan,
139
180
  )
140
181
 
141
182
  def _task_data(
@@ -316,41 +357,42 @@ class TaskCreationService:
316
357
  "plan file must live under .cowork-flow/plans",
317
358
  )
318
359
 
319
- @staticmethod
320
360
  def _generate_anchor(
361
+ self,
321
362
  task_dir: Path,
322
363
  plan_path: Path | None,
323
- ) -> bool:
324
- if plan_path is None:
325
- return False
326
- if not plan_path.is_file():
327
- return False
364
+ ) -> tuple[bool, bool]:
365
+ """Create a language-specific anchor scaffold.
328
366
 
329
- try:
330
- plan_text = plan_path.read_text(encoding="utf-8")
331
- except (OSError, UnicodeDecodeError) as error:
332
- raise TaskCreationError(
333
- "TASK-CREATE-PLAN-001",
334
- plan_path,
335
- "plan cannot be read as UTF-8",
336
- ) from error
337
-
338
- goal = next(
339
- (
340
- line.split(":", 1)[1].strip()
341
- for line in plan_text.splitlines()
342
- if line.startswith("**Goal:**")
343
- or line.startswith("**目标:**")
344
- ),
345
- None,
346
- )
367
+ A bound plan's goal line (or legacy table row) fills the goal section;
368
+ otherwise the scaffold carries a localized placeholder. An anchor that
369
+ already exists is never overwritten.
370
+ """
347
371
  anchor_file = task_dir / "decision-anchor.md"
348
- if not goal or anchor_file.exists():
349
- return False
372
+ if anchor_file.exists():
373
+ return False, False
374
+
375
+ goal: str | None = None
376
+ if plan_path is not None and plan_path.is_file():
377
+ try:
378
+ plan_text = plan_path.read_text(encoding="utf-8")
379
+ except (OSError, UnicodeDecodeError) as error:
380
+ raise TaskCreationError(
381
+ "TASK-CREATE-PLAN-001",
382
+ plan_path,
383
+ "plan cannot be read as UTF-8",
384
+ ) from error
385
+ goal = _extract_plan_goal(plan_text)
386
+
387
+ language = get_language(self.repo_root)
388
+ goal_heading = heading_for(GOAL, language)
389
+ acceptance_heading = heading_for(ACCEPTANCE, language)
390
+ goal_body = goal or _anchor_placeholder(GOAL, language)
391
+ acceptance_body = _anchor_placeholder(ACCEPTANCE, language)
350
392
  try:
351
393
  anchor_file.write_text(
352
- f"## 目标\n\n{goal}\n\n## 验收标准\n"
353
- "<!-- 至少一条,例如:- [ ] AC-001: ... -->\n",
394
+ f"## {goal_heading}\n\n{goal_body}\n\n"
395
+ f"## {acceptance_heading}\n\n{acceptance_body}\n",
354
396
  encoding="utf-8",
355
397
  )
356
398
  except OSError as error:
@@ -363,7 +405,7 @@ class TaskCreationService:
363
405
  anchor_file,
364
406
  "decision anchor could not be written",
365
407
  ) from error
366
- return True
408
+ return True, goal is not None
367
409
 
368
410
 
369
411
  def _validate_dev_type(dev_type: str | None, path: Path) -> None:
@@ -4,7 +4,7 @@ A task created today carries `meta.gateProfile = "strict"`; a task without the
4
4
  marker keeps the historical gate (legacy), so nothing that already exists is
5
5
  retroactively re-gated.
6
6
 
7
- The strict gate exists because the old one only checked that the `## 验收标准`
7
+ The strict gate exists because the old one only checked that the acceptance
8
8
  heading was present. An anchor with an empty acceptance section passed, the
9
9
  acceptance/evidence gates then found no AC rows to check, and the whole chain
10
10
  silently did nothing.
@@ -12,20 +12,17 @@ silently did nothing.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- import re
16
-
17
- from services.fact_view import ACCEPTANCE_RE
15
+ from services.decision_anchor_schema import (
16
+ ACCEPTANCE_RE,
17
+ LIST_ITEM_RE,
18
+ acceptance_section_lines,
19
+ )
18
20
 
19
21
  GATE_PROFILE_STRICT = "strict"
20
22
  GATE_PROFILE_LEGACY = "legacy"
21
23
 
22
24
  LIGHT_TASK_TYPE = "tiny"
23
25
 
24
- ACCEPTANCE_HEADING = "验收标准"
25
-
26
- _HEADING_RE = re.compile(r"^##\s*(.+?)\s*$")
27
- _LIST_ITEM_RE = re.compile(r"^\s*[-*]\s")
28
-
29
26
 
30
27
  def resolve_gate_profile(task_data: dict) -> str:
31
28
  """The gate a task is held to; an unmarked task is legacy."""
@@ -72,20 +69,6 @@ def is_light_task(task_data: dict) -> bool:
72
69
  return declares_light_profile(task_data)
73
70
 
74
71
 
75
- def acceptance_section_lines(anchor_text: str) -> list[str]:
76
- """Raw lines under `## 验收标准`, up to the next heading."""
77
- lines: list[str] = []
78
- inside = False
79
- for raw in anchor_text.splitlines():
80
- heading = _HEADING_RE.match(raw.strip())
81
- if heading:
82
- inside = heading.group(1) == ACCEPTANCE_HEADING
83
- continue
84
- if inside:
85
- lines.append(raw)
86
- return lines
87
-
88
-
89
72
  def acceptance_row_blockers(anchor_text: str) -> list[str]:
90
73
  """Why the acceptance section cannot start a strict task.
91
74
 
@@ -101,7 +84,7 @@ def acceptance_row_blockers(anchor_text: str) -> list[str]:
101
84
  if match:
102
85
  ids.append(match.group(2))
103
86
  continue
104
- if _LIST_ITEM_RE.match(raw):
87
+ if LIST_ITEM_RE.match(raw):
105
88
  malformed.append(raw.strip()[:80])
106
89
  blockers: list[str] = []
107
90
  if not ids:
@@ -289,7 +289,8 @@ Verify: npm run test:fast; python3 -m pytest tests/ -q
289
289
  - **Gates**: static preview text, identical on all hosts; the delegated
290
290
  variant replaces the self-declaration sentence with "scope is inherited
291
291
  from the parent task (read-only reference)".
292
- - **Verify**: `## 验证命令` lines from decision-anchor (first 10, 120 chars
292
+ - **Verify**: `## Verification Commands`/`## 验证命令` lines from
293
+ decision-anchor (first 10, 120 chars
293
294
  each) — the agent's own declared self-checks; omitted when none.
294
295
  - **Per-edit warning**: PostToolUse on an edit tool injects at most one
295
296
  line when the edited file is outside the task's declared scope —
@@ -326,9 +327,9 @@ Verify: npm run test:fast; python3 -m pytest tests/ -q
326
327
  budget/limits come from `.cowork-flow/spec/runtime/scope-rules.json`, the
327
328
  single source consumed at runtime; a missing or malformed file degrades to
328
329
  embedded defaults that are byte-identical with the shipped file (locked by
329
- `tests/test_scope_rules.py`). The file is template-managed
330
- (`syncPolicy.safeFiles`): `cwf project sync` refreshes it, so local edits to
331
- it do not survive a sync.
330
+ `tests/test_scope_rules.py`). The file is framework-managed: `cwf project
331
+ sync` refreshes it as part of the synced `spec/runtime/` tree, so local edits
332
+ to it do not survive a sync.
332
333
  - Absent for `no_task` / `planning` / `completed` states. Block content is
333
334
  byte-identical across host lines for the whole fixture matrix
334
335
  (`test/fixtures/stage-contract-matrix.json` drives the equality test:
@@ -8,8 +8,9 @@ the alternatives", which compresses the number of task artifacts.
8
8
 
9
9
  ## Schema
10
10
 
11
- These section names are literal: the runtime parser matches them exactly, so a
12
- translated heading would simply stop being read.
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).
13
14
 
14
15
  ```markdown
15
16
  # Decision Anchor
@@ -38,14 +39,17 @@ translated heading would simply stop being read.
38
39
 
39
40
  ## Section requirements
40
41
 
42
+ Both heading columns below are accepted; the left column is the `zh-CN` output
43
+ and the right column the `en-US` output.
44
+
41
45
  | Section | Required | Why |
42
46
  |------|------|------|
43
- | `## 目标` | required | no goal, no acceptance baseline |
44
- | `## 验收标准` | required | `task_start` is blocked without acceptance criteria |
45
- | `## 被拒方案` | optional | required for brainstorming tasks, so a rejected option is not reconsidered during execution |
46
- | `## 关键假设` | optional | filled in for complex tasks to surface implicit constraints; not read by the runtime parser |
47
- | `## 范围边界` | optional | prevents scope creep |
48
- | `## 验证命令` | 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) |
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) |
49
53
 
50
54
  ### Machine-readable line formats (for injection)
51
55
 
@@ -84,15 +88,17 @@ After merging (5 files): `decision-anchor.md` + `implement.jsonl` + `check.jsonl
84
88
  ## Lifecycle check coupling
85
89
 
86
90
  - The preflight for `task next <dir> --run` checks that `decision-anchor.md` is
87
- non-empty and requires the `## 目标` and `## 验收标准` sections to be present.
91
+ non-empty and requires the `## 目标` / `## Goal` and `## 验收标准` /
92
+ `## Acceptance Criteria` sections to be present.
88
93
  - **Gate profile `meta.gateProfile`**: a newly created task is written as
89
94
  `"strict"`; an unmarked task is treated as `"legacy"` — existing tasks are not
90
95
  retro-fitted with the stricter gate, and the profile is queryable through the
91
96
  fact view. On top of the section check, a strict task additionally requires: at
92
- least one parseable `- [ ] AC-xxx: ...` row in `## 验收标准`, no duplicate AC ids,
93
- no non-AC list rows in that section, and a declared `dev_type` (otherwise
94
- `task specs` dev-type dispatch silently comes back shorter).
95
- - A `## 验收标准` row's checkbox state (`- [ ]` / `- [x]`) and the evidence records
97
+ least one parseable `- [ ] AC-xxx: ...` row in `## 验收标准` /
98
+ `## Acceptance Criteria`, no duplicate AC ids, no non-AC list rows in that
99
+ section, and a declared `dev_type` (otherwise `task specs` dev-type dispatch
100
+ silently comes back shorter).
101
+ - An acceptance row's checkbox state (`- [ ]` / `- [x]`) and the evidence records
96
102
  (the task directory's `evidence.jsonl`) are the fact source of the completion
97
103
  gate: completion is blocked when an unchecked AC exists (`LIFECYCLE-AC-001`), a
98
104
  declared AC has no evidence (`LIFECYCLE-AC-002`), a strict task cannot read any
@@ -149,4 +155,5 @@ source; if a task directory has only `prd.md` and no `decision-anchor.md`,
149
155
  `decision-anchor.md is missing or empty`.
150
156
 
151
157
  An existing legacy task needs a human to convert the `prd.md` content into
152
- `decision-anchor.md`, fill in `## 目标` and `## 验收标准`, and only then start.
158
+ `decision-anchor.md`, fill in `## 目标` / `## Goal` and `## 验收标准` /
159
+ `## Acceptance Criteria`, and only then start.
@@ -48,7 +48,8 @@ into `in_progress`:
48
48
  - `task.json.meta.planFile` exists.
49
49
  - `planFile` is a repo-relative `.cowork-flow/plans/` path.
50
50
  - The plan file exists and is non-empty.
51
- - `decision-anchor.md` contains `## 目标` and `## 验收标准`.
51
+ - `decision-anchor.md` contains `## Goal`/`## 目标` and
52
+ `## Acceptance Criteria`/`## 验收标准`.
52
53
  - `implement.jsonl` passes the existing context validation.
53
54
 
54
55
  A Tiny task may have no planFile, but must still satisfy the existing anchor and
@@ -1,35 +1,35 @@
1
1
  # Cowork Flow Spec Index
2
2
 
3
- 本目录只存放项目规范、运行时合同和运行时配置 schema。按职责分层:
3
+ This directory holds project specs, runtime contracts, and runtime configuration schemas. Layers by responsibility:
4
4
 
5
- | 目录 | 作用 |
5
+ | Directory | Purpose |
6
6
  | --- | --- |
7
- | `contracts/` | 人读的 workflow、宿主适配器和子代理协议合同。 |
8
- | `runtime/` | runtime 读取的机器配置,包括 contract registry 和 host asset manifest。 |
9
- | `schemas/` | runtime 配置和宿主合同的 JSON Schema。 |
10
- | `backend/` | 后端实现规范;由 review skill 按需读取并逐条审查。 |
11
- | `frontend/` | 前端实现规范;由 review skill 按需读取并逐条审查。 |
12
- | `guides/` | brainstorming/task-planning 阶段的编码前思考、跨层设计和复用判断指引。 |
13
- | `references/` | 按需参考材料(DoD、测试清单、安全清单、协作模式);不自动加载。加载条件见 contract-registry.json 的 references 数组。 |
14
-
15
- 流程内核只负责任务状态、文件范围、runtime context、归档一致性等可机器判断的事实。
16
- 动作、阶段指导和专属运行时由各 Skill 的 `manifest.json` 声明;不维护集中式
17
- Skill registry。
18
- `backend/`、`frontend/` 中的自然语言规范不注册为 runtime gate;
19
- `task-review` skill 根据当前 diff 和任务范围读取并逐条审查。
20
- `guides/` 属于前置澄清与计划参考;选用后的结论应进入 `decision-anchor.md` 或任务计划。
7
+ | `contracts/` | Human-readable contracts for the workflow, host adapters, and subagent protocol. |
8
+ | `runtime/` | Machine configuration read by the runtime, including the contract registry and host asset manifest. |
9
+ | `schemas/` | JSON Schemas for runtime configuration and host contracts. |
10
+ | `backend/` | Backend implementation specs; the review skill reads them on demand and checks each clause. |
11
+ | `frontend/` | Frontend implementation specs; the review skill reads them on demand and checks each clause. |
12
+ | `guides/` | Pre-implementation thinking, cross-layer design, and reuse guidance for brainstorming/task-planning. |
13
+ | `references/` | On-demand reference material (DoD, testing checklist, security checklist, orchestration patterns); never auto-loaded. Load conditions are in the `references` array of contract-registry.json. |
14
+
15
+ Sync ownership: `contracts/`, `runtime/`, `schemas/`, and this file are framework-maintained and follow `cwf project sync`; `backend/`, `frontend/`, `guides/`, and `references/` are project-maintained, and `sync` never overwrites or deletes files there.
16
+
17
+ The workflow kernel only owns machine-checkable facts: task state, file scope, runtime context, and archive consistency.
18
+ Actions, stage guidance, and dedicated runtimes are declared by each Skill's `manifest.json`; there is no centralized Skill registry.
19
+ Natural-language specs under `backend/` and `frontend/` are not registered as runtime gates; the `task-review` skill reads them according to the current diff and task scope and checks each clause.
20
+ `guides/` is pre-implementation clarification and planning reference; selected conclusions belong in `decision-anchor.md` or the task plan.
21
21
 
22
22
  ## references/
23
23
 
24
- > 按需参考材料。**不在主会话中自动加载**——仅当 skill 入口或 agent 判断需要时读取。
24
+ > On-demand reference material. **Never auto-loaded in the main session** — read only when a skill entry or agent judges it necessary.
25
25
 
26
- reference 文件之作用是:
27
- 1. 为 skill 入口提供补充上下文(main skill 是入口,reference 是补充)
28
- 2. 减少每次会话需加载的内容(spec/backend/ 等窄域规范只有实际需要时才加载)
29
- 3. 在 planning、check 或 review 阶段提供快速对照清单
26
+ Reference files:
27
+ 1. Provide supplemental context for skill entries (the main skill is the entry point; references supplement it).
28
+ 2. Reduce what each session must load (narrow-domain specs such as `spec/backend/` are read only when needed).
29
+ 3. Provide quick checklists during planning, check, or review.
30
30
 
31
- 加载规则:
32
- - contract-registry 中 loadWhen 之一被匹配时读取
33
- - skill 正文中显式引用 reference ID 时读取
34
- - 用户在主会话请求特定检查时
35
- - **不**自动加载——这是与 contracts/ 系列的区别
31
+ Loading rules:
32
+ - Read when one of the `loadWhen` conditions in contract-registry matches.
33
+ - Read when a skill body explicitly references a reference ID.
34
+ - Read when the user requests a specific check in the main session.
35
+ - **Never auto-loaded** — that is the difference from `contracts/`.