cowork-flow 0.0.47 → 0.0.49

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 (90) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +102 -23
  3. package/package.json +8 -3
  4. package/src/cli.js +6 -0
  5. package/src/commands/init.js +0 -83
  6. package/src/commands/source-refresh.js +40 -0
  7. package/src/commands/sync.js +6 -0
  8. package/src/commands/update.js +59 -6
  9. package/src/lib/copy-template.js +200 -50
  10. package/src/lib/host-assets.js +367 -64
  11. package/src/lib/plan-applier.js +47 -0
  12. package/template/.claude/hooks/inject-workflow-state.py +7 -0
  13. package/template/.codex/hooks/inject-workflow-state.py +7 -0
  14. package/template/.codex/hooks.json +1 -1
  15. package/template/.cowork-flow/.version +1 -1
  16. package/template/.cowork-flow/config.yaml +0 -11
  17. package/template/.cowork-flow/scripts/adapters/cli/init_developer.py +0 -1
  18. package/template/.cowork-flow/scripts/adapters/cli/subagent.py +7 -4
  19. package/template/.cowork-flow/scripts/adapters/cli/task.py +8 -484
  20. package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +15 -134
  21. package/template/.cowork-flow/scripts/adapters/cli/task_context_commands.py +7 -1
  22. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +388 -0
  23. package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +163 -2
  24. package/template/.cowork-flow/scripts/adapters/git/git_context.py +16 -193
  25. package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +170 -84
  26. package/template/.cowork-flow/scripts/infra/config.py +0 -17
  27. package/template/.cowork-flow/scripts/infra/developer_profile.py +0 -95
  28. package/template/.cowork-flow/scripts/infra/paths.py +0 -79
  29. package/template/.cowork-flow/scripts/infra/skill_manifest.py +23 -3
  30. package/template/.cowork-flow/scripts/infra/storage/operation_log.py +40 -0
  31. package/template/.cowork-flow/scripts/infra/storage/state_store.py +356 -1
  32. package/template/.cowork-flow/scripts/kernel/workflow_route.py +15 -15
  33. package/template/.cowork-flow/scripts/run.py +0 -5
  34. package/template/.cowork-flow/scripts/services/context_discovery.py +156 -0
  35. package/template/.cowork-flow/scripts/services/context_jsonl.py +119 -0
  36. package/template/.cowork-flow/scripts/services/context_paths.py +186 -0
  37. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +186 -69
  38. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +128 -0
  39. package/template/.cowork-flow/scripts/services/plan_binding.py +146 -0
  40. package/template/.cowork-flow/scripts/services/readiness.py +3 -170
  41. package/template/.cowork-flow/scripts/services/task_archive.py +115 -24
  42. package/template/.cowork-flow/scripts/services/task_context.py +103 -365
  43. package/template/.cowork-flow/scripts/services/task_creation.py +40 -5
  44. package/template/.cowork-flow/scripts/services/task_lifecycle.py +155 -18
  45. package/template/.cowork-flow/scripts/services/task_routing.py +80 -67
  46. package/template/.cowork-flow/scripts/services/task_utils.py +0 -146
  47. package/template/.cowork-flow/scripts/services/workflow_runtime.py +1 -1
  48. package/template/.cowork-flow/spec/contracts/capabilities.md +23 -0
  49. package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
  50. package/template/.cowork-flow/spec/contracts/error-output-as-data.md +8 -0
  51. package/template/.cowork-flow/spec/contracts/index.md +1 -0
  52. package/template/.cowork-flow/spec/contracts/party-mode-v2-board.md +20 -0
  53. package/template/.cowork-flow/spec/contracts/plan-binding.md +50 -0
  54. package/template/.cowork-flow/spec/contracts/skill-owned-actions.md +31 -7
  55. package/template/.cowork-flow/spec/guides/index.md +3 -1
  56. package/template/.cowork-flow/spec/index.md +5 -4
  57. package/template/.cowork-flow/spec/references/definition-of-done.md +2 -3
  58. package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
  59. package/template/.cowork-flow/spec/runtime/contract-registry.json +14 -0
  60. package/template/.cowork-flow/spec/runtime/host-assets.json +73 -3
  61. package/template/.cowork-flow/spec/runtime/index.md +1 -1
  62. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +150 -8
  63. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  64. package/template/skills/batch-execution/SKILL.md +37 -3
  65. package/template/skills/batch-execution/scripts/batch_action.py +4 -0
  66. package/template/skills/batch-execution/scripts/batch_execution.py +120 -499
  67. package/template/skills/batch-execution/scripts/batch_recovery.py +94 -0
  68. package/template/skills/batch-execution/scripts/batch_state_machine.py +201 -0
  69. package/template/skills/batch-execution/scripts/batch_verification.py +326 -0
  70. package/template/skills/brainstorming/SKILL.md +8 -7
  71. package/template/skills/cowork-flow/SKILL.md +3 -0
  72. package/template/skills/party-mode/scripts/party_action_contract.py +113 -0
  73. package/template/skills/party-mode/scripts/party_actions.py +95 -0
  74. package/template/skills/party-mode/scripts/party_board_store.py +148 -0
  75. package/template/skills/party-mode/scripts/party_mode_v2.py +345 -317
  76. package/template/skills/party-mode/scripts/party_reports.py +177 -0
  77. package/template/skills/party-mode/scripts/party_state_machine.py +189 -0
  78. package/template/skills/runtime-health/SKILL.md +14 -2
  79. package/template/skills/runtime-health/scripts/doctor.py +278 -41
  80. package/template/skills/task-planning/SKILL.md +105 -61
  81. package/template/skills/task-review/SKILL.md +4 -4
  82. package/template/skills/task-review/scripts/review_check.py +277 -20
  83. package/template/skills/test-first/SKILL.md +18 -3
  84. package/template/skills/test-first/manifest.json +8 -4
  85. package/template/.cowork-flow/changes/archive/.gitkeep +0 -1
  86. package/template/.cowork-flow/scripts/adapters/cli/add_session.py +0 -465
  87. package/template/.cowork-flow/scripts/adapters/cli/change.py +0 -412
  88. package/template/.cowork-flow/workspace/index.md +0 -102
  89. package/template/.zcode/scaffold/AGENTS.md +0 -145
  90. package/template/.zcode/scaffold/CLAUDE.md +0 -5
@@ -29,12 +29,30 @@ from adapters.host.host_manifest import (
29
29
  )
30
30
  from infra.paths import get_repo_root
31
31
  from infra.skill_manifest import SkillManifestError, action_owners, load_skill_manifests
32
+ from infra.storage.operation_log import OperationLog
33
+ from infra.storage.state_store import DEFAULT_STALE_LOCK_SECONDS, StateStore
32
34
 
33
35
 
34
- SOURCE_CHECKOUT_BOOTSTRAP_FILES = (
35
- Path(".cowork-flow/scripts/adapters/cli/task_navigation.py"),
36
- Path(".cowork-flow/scripts/adapters/cli/task_parser.py"),
37
- )
36
+ def _issue(
37
+ *,
38
+ code: str,
39
+ severity: str,
40
+ path: str,
41
+ message: str,
42
+ command_hint: str = "",
43
+ contract: str,
44
+ **extra: str,
45
+ ) -> dict[str, str]:
46
+ issue = {
47
+ "code": code,
48
+ "severity": severity,
49
+ "path": path,
50
+ "message": message,
51
+ "commandHint": command_hint,
52
+ "contract": contract,
53
+ }
54
+ issue.update(extra)
55
+ return issue
38
56
 
39
57
 
40
58
  def _compare_file(
@@ -84,6 +102,40 @@ def _host_errors(repo_root: Path) -> list[str]:
84
102
  return validate_host_assets(distribution_root, platform_ids=platform_ids)
85
103
 
86
104
 
105
+ def _host_issue(error: str) -> dict[str, str]:
106
+ path = ""
107
+ if ":" in error:
108
+ path = error.rsplit(":", 1)[-1].strip()
109
+ lowered = error.lower()
110
+ if "missing command target" in lowered:
111
+ code = "HOST-ASSET-MISSING-COMMAND-TARGET"
112
+ elif "missing command config" in lowered:
113
+ code = "HOST-ASSET-MISSING-COMMAND-CONFIG"
114
+ elif "invalid command config" in lowered:
115
+ code = "HOST-ASSET-INVALID-COMMAND-CONFIG"
116
+ elif "illegal capability" in lowered:
117
+ code = "HOST-ASSET-ILLEGAL-CAPABILITY"
118
+ elif "capability mismatch" in lowered:
119
+ code = "HOST-ASSET-CAPABILITY-MISMATCH"
120
+ elif "adapter host mismatch" in lowered:
121
+ code = "HOST-ASSET-HOST-MISMATCH"
122
+ elif "invalid adapter yaml" in lowered:
123
+ code = "HOST-ASSET-INVALID-ADAPTER"
124
+ else:
125
+ code = "HOST-ASSET-VALIDATION-ERROR"
126
+ return _issue(
127
+ code=code,
128
+ severity="error",
129
+ path=path,
130
+ message=error,
131
+ contract="runtime-health:host-adapters",
132
+ )
133
+
134
+
135
+ def _host_issues(repo_root: Path) -> list[dict[str, str]]:
136
+ return [_host_issue(error) for error in _host_errors(repo_root)]
137
+
138
+
87
139
  def _distribution_files(root: Path) -> tuple[Path, ...]:
88
140
  files = [
89
141
  path
@@ -95,26 +147,31 @@ def _distribution_files(root: Path) -> tuple[Path, ...]:
95
147
  return tuple(sorted(files))
96
148
 
97
149
 
150
+ def _source_checkout_protected_path(relative: Path) -> bool:
151
+ value = relative.as_posix()
152
+ if value in {".cowork-flow/config.yaml", ".cowork-flow/.developer"}:
153
+ return True
154
+ return value.startswith(".cowork-flow/tasks/") or value.startswith(
155
+ ".cowork-flow/plans/"
156
+ ) or value.startswith(".cowork-flow/.runtime/")
157
+
158
+
159
+ def _source_checkout_live_files(template: Path) -> tuple[Path, ...]:
160
+ runtime_root = template / ".cowork-flow"
161
+ return tuple(
162
+ source
163
+ for source in _distribution_files(runtime_root)
164
+ if not _source_checkout_protected_path(source.relative_to(template))
165
+ )
166
+
167
+
98
168
  def check_distribution(repo_root: Path) -> list[str]:
99
169
  errors: list[str] = []
100
170
  template = repo_root / "template"
101
171
  if _distribution_root(repo_root) == repo_root:
102
172
  return errors
103
- runtime_root = template / ".cowork-flow" / "scripts"
104
- bootstrap = set(SOURCE_CHECKOUT_BOOTSTRAP_FILES)
105
- for relative in SOURCE_CHECKOUT_BOOTSTRAP_FILES:
106
- _compare_file(
107
- template / relative,
108
- repo_root / relative,
109
- errors,
110
- missing_left="missing template bootstrap asset",
111
- missing_right="missing source bootstrap asset",
112
- drift="source bootstrap drift",
113
- )
114
- for source in _distribution_files(runtime_root):
173
+ for source in _source_checkout_live_files(template):
115
174
  relative = source.relative_to(template)
116
- if relative in bootstrap:
117
- continue
118
175
  target = repo_root / relative
119
176
  if target.is_file():
120
177
  _compare_file(
@@ -123,11 +180,6 @@ def check_distribution(repo_root: Path) -> list[str]:
123
180
  errors,
124
181
  drift="local live runtime drift",
125
182
  )
126
- for relative in (Path(".cowork-flow/run"), Path(".cowork-flow/run.cmd")):
127
- source = template / relative
128
- target = repo_root / relative
129
- if source.is_file() and target.is_file():
130
- _compare_file(source, target, errors, drift="local live runtime drift")
131
183
  try:
132
184
  host_manifest = load_host_manifest(template)
133
185
  except HostManifestError as error:
@@ -186,13 +238,19 @@ def _task_hygiene_issue(
186
238
  message: str,
187
239
  hint: str,
188
240
  ) -> dict[str, str]:
189
- return {
190
- "kind": kind,
191
- "task": task,
192
- "status": status,
193
- "message": message,
194
- "hint": hint,
195
- }
241
+ code = f"TASK-HYGIENE-{kind.replace('_', '-').upper()}"
242
+ return _issue(
243
+ code=code,
244
+ severity="warning",
245
+ path=task,
246
+ message=message,
247
+ command_hint=hint,
248
+ contract="runtime-health:task-hygiene",
249
+ kind=kind,
250
+ task=task,
251
+ status=status,
252
+ hint=hint,
253
+ )
196
254
 
197
255
 
198
256
  def _missing_context_files(task_dir: Path) -> tuple[str, ...]:
@@ -257,6 +315,151 @@ def _print_task_hygiene_issues(issues: list[dict[str, str]]) -> None:
257
315
  print(f"Hint: {issue['hint']}", file=sys.stderr)
258
316
 
259
317
 
318
+ def _diagnostic_path(repo_root: Path, path: Path) -> str:
319
+ try:
320
+ return path.resolve().relative_to(repo_root.resolve()).as_posix()
321
+ except ValueError:
322
+ return str(path)
323
+
324
+
325
+ def _state_lock_code(status: str) -> str:
326
+ return f"STATE-RECOVERY-LOCK-{status.upper().replace('_', '-')}"
327
+
328
+
329
+ def _state_lock_command_hint(info) -> str:
330
+ if info.status != "recoverable":
331
+ return "Inspect the lock owner facts; do not delete unless the owner PID is missing and the lock is older than the stale threshold."
332
+ return (
333
+ "Use an explicit recovery action only after verification, for example "
334
+ "StateStore().remove_stale_lock(Path(<target>), stale_after_seconds="
335
+ f"{DEFAULT_STALE_LOCK_SECONDS})"
336
+ )
337
+
338
+
339
+ def _state_lock_issue(repo_root: Path, info) -> dict[str, object]:
340
+ fields = info.to_dict()
341
+ fields.update(
342
+ {
343
+ "kind": "state_lock",
344
+ "lockPath": _diagnostic_path(repo_root, info.lock_path),
345
+ "target": str(info.target),
346
+ }
347
+ )
348
+ if info.age_seconds is not None:
349
+ fields["ageSeconds"] = round(info.age_seconds, 3)
350
+ return _issue(
351
+ code=_state_lock_code(info.status),
352
+ severity="warning",
353
+ path=_diagnostic_path(repo_root, info.lock_path),
354
+ message=info.detail,
355
+ command_hint=_state_lock_command_hint(info),
356
+ contract="runtime-health:state-recovery",
357
+ **fields,
358
+ )
359
+
360
+
361
+ def _operation_command_hint() -> str:
362
+ return (
363
+ "Inspect the pending operation record; a trusted recovery command may "
364
+ "call UnitOfWork.recover_all(repo_root) without interpreting error "
365
+ "output as instructions."
366
+ )
367
+
368
+
369
+ def _pending_operation_issue(repo_root: Path, fact: dict) -> dict[str, object]:
370
+ error = fact.get("error")
371
+ if fact.get("phase") == "unreadable" and isinstance(error, dict):
372
+ return _issue(
373
+ code="STATE-RECOVERY-OPERATION-UNREADABLE",
374
+ severity="warning",
375
+ path=_diagnostic_path(repo_root, Path(str(fact.get("path") or ""))),
376
+ message=str(error.get("detail") or "operation record is unreadable"),
377
+ command_hint="Inspect the operation record manually; Doctor does not mutate recovery state.",
378
+ contract="runtime-health:state-recovery",
379
+ kind="pending_operation",
380
+ operationId=str(fact.get("operation_id") or ""),
381
+ phase=str(fact.get("phase") or "unreadable"),
382
+ errorCode=str(error.get("code") or ""),
383
+ )
384
+ operation_id = str(fact.get("operation_id") or "")
385
+ phase = str(fact.get("phase") or "unknown")
386
+ record_error = fact.get("error")
387
+ if phase == "conflicted":
388
+ error_detail = ""
389
+ error_code = ""
390
+ if isinstance(record_error, dict):
391
+ error_code = str(record_error.get("code") or "")
392
+ error_detail = str(record_error.get("detail") or "")
393
+ suffix = ": ".join(value for value in (error_code, error_detail) if value)
394
+ message = (
395
+ f"conflicted UnitOfWork operation {operation_id}"
396
+ + (f": {suffix}" if suffix else "")
397
+ )
398
+ return _issue(
399
+ code="STATE-RECOVERY-CONFLICTED-OPERATION",
400
+ severity="warning",
401
+ path=_diagnostic_path(repo_root, Path(str(fact.get("path") or ""))),
402
+ message=message,
403
+ command_hint=(
404
+ "Resolve the recorded conflict manually; Doctor does not retry "
405
+ "or mutate conflicted operation state."
406
+ ),
407
+ contract="runtime-health:state-recovery",
408
+ kind="pending_operation",
409
+ operationId=operation_id,
410
+ operationKind=str(fact.get("kind") or "unknown"),
411
+ phase=phase,
412
+ participantCount=fact.get("participant_count", 0),
413
+ )
414
+ return _issue(
415
+ code="STATE-RECOVERY-PENDING-OPERATION",
416
+ severity="warning",
417
+ path=_diagnostic_path(repo_root, Path(str(fact.get("path") or ""))),
418
+ message=f"pending UnitOfWork operation {operation_id} is in phase {phase}",
419
+ command_hint=_operation_command_hint(),
420
+ contract="runtime-health:state-recovery",
421
+ kind="pending_operation",
422
+ operationId=operation_id,
423
+ operationKind=str(fact.get("kind") or "unknown"),
424
+ phase=phase,
425
+ participantCount=fact.get("participant_count", 0),
426
+ )
427
+
428
+
429
+ def check_state_recovery(repo_root: Path) -> list[dict[str, object]]:
430
+ workflow = repo_root / ".cowork-flow"
431
+ if not workflow.is_dir():
432
+ return []
433
+ store = StateStore()
434
+ issues: list[dict[str, object]] = []
435
+ for lock_path in sorted(workflow.rglob("*.lock")):
436
+ if not lock_path.is_file():
437
+ continue
438
+ info = store.inspect_lock_path(
439
+ lock_path,
440
+ stale_after_seconds=DEFAULT_STALE_LOCK_SECONDS,
441
+ )
442
+ if info.status != "absent":
443
+ issues.append(_state_lock_issue(repo_root, info))
444
+ operation_log = OperationLog(repo_root, state_store=store)
445
+ for fact in operation_log.pending_facts():
446
+ issues.append(_pending_operation_issue(repo_root, fact))
447
+ return issues
448
+
449
+
450
+ def _print_state_recovery_issues(issues: list[dict[str, object]]) -> None:
451
+ for issue in issues:
452
+ print(
453
+ "WARNING: "
454
+ f"{issue['kind']}: {issue['path']}: "
455
+ f"{issue['message']}",
456
+ file=sys.stderr,
457
+ )
458
+ hint = issue.get("commandHint")
459
+ if hint:
460
+ print(f"Hint: {hint}", file=sys.stderr)
461
+
462
+
260
463
  def check_runtime(repo_root: Path) -> list[str]:
261
464
  errors: list[str] = []
262
465
  try:
@@ -286,22 +489,56 @@ def check_runtime(repo_root: Path) -> list[str]:
286
489
  return errors
287
490
 
288
491
 
289
- def _run_checks(repo_root: Path) -> int:
290
- errors = []
291
- errors.extend(_host_errors(repo_root))
292
- errors.extend(check_runtime(repo_root))
293
- errors.extend(check_distribution(repo_root))
294
- _print_task_hygiene_issues(check_task_hygiene(repo_root))
492
+ def _all_check_result(repo_root: Path) -> dict[str, object]:
493
+ host_issues = _host_issues(repo_root)
494
+ runtime_errors = check_runtime(repo_root)
495
+ distribution_errors = check_distribution(repo_root)
496
+ task_hygiene_issues = check_task_hygiene(repo_root)
497
+ state_recovery_issues = check_state_recovery(repo_root)
498
+ errors: list[dict[str, object]] = []
499
+ for issue in host_issues:
500
+ errors.append({"kind": "host_adapter", **issue})
501
+ errors.extend(
502
+ {"kind": "runtime", "message": error}
503
+ for error in runtime_errors
504
+ )
505
+ errors.extend(
506
+ {"kind": "distribution", "message": error}
507
+ for error in distribution_errors
508
+ )
509
+ return {
510
+ "ok": not errors,
511
+ "errors": errors,
512
+ "issues": {
513
+ "hostAdapters": host_issues,
514
+ "taskHygiene": task_hygiene_issues,
515
+ "stateRecovery": state_recovery_issues,
516
+ },
517
+ }
518
+
519
+
520
+ def _run_checks(repo_root: Path, *, structured: bool = False) -> int:
521
+ result = _all_check_result(repo_root)
522
+ errors = result["errors"]
523
+ if structured:
524
+ print(json.dumps(result, ensure_ascii=False))
525
+ return 1 if errors else 0
526
+ _print_task_hygiene_issues(result["issues"]["taskHygiene"])
527
+ _print_state_recovery_issues(result["issues"]["stateRecovery"])
295
528
  if errors:
296
529
  for error in errors:
297
- print(f"ERROR: {error}", file=sys.stderr)
530
+ print(f"ERROR: {error['message']}", file=sys.stderr)
298
531
  return 1
299
532
  print("runtime health checks passed")
300
533
  return 0
301
534
 
302
535
 
303
- def _run_host_checks(repo_root: Path) -> int:
304
- errors = _host_errors(repo_root)
536
+ def _run_host_checks(repo_root: Path, *, structured: bool = False) -> int:
537
+ issues = _host_issues(repo_root)
538
+ if structured:
539
+ print(json.dumps({"issues": issues}, ensure_ascii=False))
540
+ return 1 if issues else 0
541
+ errors = [issue["message"] for issue in issues]
305
542
  if errors:
306
543
  for error in errors:
307
544
  print(f"ERROR: {error}", file=sys.stderr)
@@ -346,9 +583,9 @@ def main() -> int:
346
583
  args = build_parser().parse_args()
347
584
  repo_root = get_repo_root()
348
585
  if args.all:
349
- return _run_checks(repo_root)
586
+ return _run_checks(repo_root, structured=bool(args.json))
350
587
  if args.host_adapters:
351
- return _run_host_checks(repo_root)
588
+ return _run_host_checks(repo_root, structured=bool(args.json))
352
589
  if args.subagent_safety:
353
590
  return _run_runtime_checks(repo_root)
354
591
  if args.task_hygiene:
@@ -13,108 +13,151 @@ Before writing a plan, confirm the request has an executable scope, acceptance c
13
13
 
14
14
  Read:
15
15
 
16
- - Active task decision-anchor.md.
17
- - Relevant change spec/design files.
16
+ - Active task `decision-anchor.md`.
17
+ - Relevant decision-anchor, design, guide, or spec files.
18
18
  - Relevant `.cowork-flow/spec/` indexes and target specs.
19
+ - `.cowork-flow/spec/guides/index.md` for non-trivial, ambiguous, cross-layer, or reusable-work planning.
19
20
  - Files that define the contracts being changed.
20
21
 
21
22
  ## Plan Shape
22
23
 
23
24
  Save plans to `.cowork-flow/plans/YYYY-MM-DD-<slug>.md` unless the user asks for another path.
24
25
 
25
- Start with:
26
+ ### Plan Style: Task Briefs, Not Design Docs
27
+
28
+ Plans exist to prevent implementation drift. They must guide implementation by pinning task order, file boundaries, key symbols, the core patch shape, test proof, completion conditions, and prohibited drift. They are not architecture essays and must not explain every possible option.
29
+
30
+ Do not write option-comparison tables unless the user explicitly asks. Do not copy complete implementations into the plan. The plan should expose the core patch shape: where to change, what behavior changes, what key branch or pseudocode should guide the edit, how tests prove it, and which boundaries must not be crossed.
31
+
32
+ Start with a concise brief:
26
33
 
27
34
  ```markdown
28
35
  # <Feature> Implementation Plan
29
36
 
37
+ > Execution rule: this plan exists to prevent implementation drift. The implementer must work through tasks in order.
38
+ > 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.
39
+ >
30
40
  > 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.
31
41
 
32
- | 项 | 内容 |
42
+ | Field | Content |
33
43
  |----|------|
34
- | **目标** | <一句话> |
35
- | **架构** | <2-3 句> |
36
- | **验证** | `<命令>` → exit 0;`<命令>` → exit 0 |
37
- | **策略** | 串行 / 并行(文件所有权:A 组 / B 组) |
38
- | **范围** | N 步 / N 文件 / N AC |
39
- | **风险** | 无显著风险 / 见尾部风险表 §高-N |
40
-
41
- ## 概览
42
-
43
- | Step | AC | 标题 | 状态 |
44
- |------|----|------|------|
45
- | 1.1 | AC-001 | <标题> | ⬜ |
46
- | 1.2 | AC-002 | <标题> | ⬜ |
47
- | 2.1 | — | <标题> | ⬜ |
44
+ | **Goal** | <One sentence describing the observable behavior change> |
45
+ | **Task Type** | Tiny / Normal / High-risk: <reason> |
46
+ | **Strategy** | Serial / Parallel: <why> |
47
+ | **Success Criteria** | AC-001 / AC-002 / AC-003 |
48
+ | **Final Verification** | `<command>` -> exit 0; `<command>` -> exit 0 |
49
+ | **Primary Risk** | Low / Medium / High: <one sentence> |
50
+
51
+ ## Goal
52
+
53
+ <1-3 sentences describing the expected observable behavior.>
54
+
55
+ ## Acceptance Criteria
56
+
57
+ - AC-001: <observable behavior>
58
+ - AC-002: <observable behavior>
59
+
60
+ ## Global Constraints
61
+
62
+ - <Architecture boundary, compatibility requirement, command, or output field that must be preserved>
63
+ - <Explicitly prohibited scope expansion>
64
+
65
+ ## File Boundaries
66
+
67
+ - `path/a.py`: <responsibility; only change this function or region>
68
+ - `tests/test_a.py`: <responsibility; only add or modify these tests>
69
+
70
+ ## Tasks
48
71
  ```
49
72
 
50
- Then list acceptance criteria, then steps.
73
+ Then list task briefs, followed by integrated verification and completion checks.
51
74
 
52
75
  ## Task Rules
53
76
 
77
+ - Classify the plan before writing tasks:
78
+ - **Tiny**: pure docs, names, comments, or no-behavior cleanup. Use file boundaries, completion conditions, and one verification command.
79
+ - **Normal**: ordinary behavior, test, or workflow guidance changes. Use the full task brief below.
80
+ - **High-risk**: workflow, runtime, template, spec, hook, state-machine, migration, or security-sensitive changes. Use the full task brief plus code-fact sources and explicit drift boundaries.
54
81
  - Each task names exact files to create, modify, or test.
55
- - Each step is small enough to execute and verify independently.
56
- - **Every step MUST follow the executable format below** so subagents never guess:
82
+ - Each task is small enough to execute and verify independently.
83
+ - Every Normal or High-risk task must use this brief format:
57
84
 
58
85
  ```markdown
59
- ### Step N.M [AC-XXX] ⬜: <一句话描述,中文>
86
+ ### Task N: <small goal>
60
87
 
61
- - **改动**: `path/a.py`, `path/b.py`(×2 = template + root 两份)
62
- - **做了什么**: <一句话具体变更>
63
- - **验证**: `<命令>` → exit 0(实现前 exit 1 → 实现后 exit 0 | AC-XXX)
64
- - **回滚**: <一句话或"仅增测试,无回滚成本">
65
- ```
88
+ **Purpose**
89
+ - <Observable behavior after this task is complete>
66
90
 
67
- For steps covering multiple AC:
91
+ **Code Fact Sources**
92
+ - Read: `path/a.py::function_name`
93
+ - Read: `tests/test_a.py::test_existing_behavior`
68
94
 
69
- ```markdown
70
- - **同时覆盖**: AC-002
71
- ```
95
+ **File Boundaries**
96
+ - Create / Modify / Test: `path/a.py` (only change <function/region>)
97
+ - Test: `tests/test_a.py` (only change <test/region>)
72
98
 
73
- For steps without an AC (e.g. integrated verification):
99
+ **Key Symbols**
100
+ - Change: `function_name()`, `ClassName.method()`, `<config-key>`
101
+ - Preserve: `existing_helper()`, output field `<field>`
74
102
 
75
- ```markdown
76
- ### Step N.M — ⬜: <一句话描述,中文>
103
+ **Implementation Notes**
104
+ ```python
105
+ # Core control-flow sketch; adjust names to match existing code.
106
+ # Do not copy a complete implementation into the plan.
107
+ ...
77
108
  ```
78
109
 
79
- In the overview table, fill AC column with `—` for no-AC steps.
110
+ **Test Proof**
111
+ - `tests/test_a.py::test_x`: proves <specific behavior>
112
+ - Core assertion: <one sentence describing the assertion, not only the test name>
80
113
 
81
- - For behavior changes, include concrete verification in Verify parentheses, such as `reproduces before change -> passes after change`. When setup and final verification differ, use two-line form:
82
- ```
83
- - **验证**: `red command` → exit 1 | `green command` → exit 0 | AC-XXX
84
- ```
85
- - Expected/断言 field is retired. Its responsibility moves into Verify parentheses — describe state changes that exit code alone cannot cover:
114
+ **Verification Command**
115
+ ```bash
116
+ <command>
86
117
  ```
87
- - **验证**: `pytest tests/test_archive.py -q` → exit 0(覆盖:归档成功的 JSONL 自路径规范化 + 失败路径的字节级回滚)
118
+
119
+ **Completion Conditions**
120
+ - <2-4 checkable conditions>
121
+
122
+ **Prohibited Drift**
123
+ - <2-5 explicit prohibited changes>
124
+
125
+ **Deviation Conditions**
126
+ - If <plan-external file/layer> becomes necessary, must stop and report back because the file boundary is wrong.
88
127
  ```
89
- - Rollback is always local — write recovery directly in the step. No cross-reference to risk table.
90
- - Include commands and expected results in every step.
91
- - Include a failing test before implementation when behavior can be tested.
92
- - Do not add shallow tests just to satisfy process. Avoid tests that only assert existence, mirror implementation details, count mocks without behavior, or snapshot empty structure.
93
- - For complex problems, test depth first: cover invariants, cross-layer contracts, state transitions, error boundaries, and real regression paths before narrow unit cases.
94
- - Map each behavior-changing test to a stable decision-anchor.md acceptance ID when useful; do not create `tdd.jsonl` or TDD evidence records in `check.jsonl`.
95
- - Avoid placeholders such as TODO, TBD, "handle edge cases", or "write tests".
128
+
129
+ - For Tiny tasks, keep the brief short but still name exact file boundaries, completion conditions, and verification commands.
130
+ - For behavior changes, include a failing test before implementation when behavior can be tested.
131
+ - Do not add shallow tests just to satisfy process. Avoid tests that only assert existence, mirror implementation details, count mock calls without behavior, or snapshot empty structure.
132
+ - For complex problems, test depth first: business invariants, cross-layer contracts, state transitions, error boundaries, and real regression paths before narrow unit cases.
133
+ - Map behavior-changing tests to stable `decision-anchor.md` acceptance IDs when useful; do not create `tdd.jsonl` or write TDD evidence records into `check.jsonl`.
134
+ - Avoid placeholders such as `TODO`, `TBD`, "handle edge cases", or "write tests".
96
135
  - Keep root/template parity explicit when both copies exist.
136
+ - When guide material changes the intended approach, capture the selected conclusion in `decision-anchor.md`, the task brief, or explicit prohibited drift; do not leave it as unstated background knowledge.
137
+ - If a task needs more than about 15 lines of implementation pseudocode, split it or narrow the scope. Plans should guide the edit, not replace the implementation.
97
138
 
98
139
  ### Anti-Rationalization - Planning Phase
99
140
 
100
141
  | Agent Rationalization | Rebuttal | Alternative |
101
142
  |---|---|---|
102
- | "This task is too small, let's merge it." | Task granularity is determined by acceptance criteria, not line count. Merge only when AC cannot be split; otherwise keep independent. | Split until each step has a distinct AC; keep them as independent steps even when short. |
103
- | "Write a rough plan first, refine during implementation." | Rough plan = no constraints during implementation. Implementation without constraints = scope creep or rework. | Write the plan to the full executable format (改动 / 做了什么 / 验证 / 回滚) before any code is touched. |
104
- | "Skip the plan and start directly since we have decision-anchor." | decision-anchor.md defines "what to do"; the plan defines "how to do it and in what order." Tasks without a plan incur doubled rework cost when dependency errors surface midway. | Use decision-anchor.md to derive the AC list, then produce plan steps that map each AC to a concrete file + verify command. |
105
- | "This task doesn't need a verify command." | No verify = no completion signal. Every task must have an executable verification command. | Add at least one automated check (typecheck, test, lint, build) as the Verify step so completion is observable. |
143
+ | "This task is too small, let's merge it." | Task granularity is determined by acceptance criteria, not line count. Merge only when AC cannot be split. | Split until each step has a distinct AC; keep steps independent even when short. |
144
+ | "Write a rough plan first, refine during implementation." | Rough plans do not constrain implementation. Unconstrained implementation invites scope creep and rework. | Use `decision-anchor.md` to derive acceptance criteria, then write task briefs with file boundaries, key symbols, implementation notes, test proof, completion conditions, and prohibited drift. |
145
+ | "Skip the plan since we already have decision-anchor." | `decision-anchor.md` defines what to do; the plan defines how to do it and in what order. Without a plan, dependency mistakes surface mid-implementation and increase rework. | Derive ACs from `decision-anchor.md`, then map each AC to concrete files and verification commands. |
146
+ | "This task does not need a verification command." | No verification means no completion signal. Every task needs an executable verification command. | Add at least one automated check, such as typecheck, test, lint, or build. |
147
+ | "The plan should explore every alternative." | Heavy option analysis slows routine implementation and hides the path to execute. | State the chosen path and drift boundaries. Add option analysis only when the user asks or the decision is genuinely architectural. |
106
148
 
107
149
  ## Plan Approval Gate
108
150
 
109
- A plan can be marked as approved only when all conditions hold:
110
- - Every task in implement.jsonl has clear acceptance criteria
111
- - Every task has implement steps and verify commands
112
- - Task dependencies are explicit (parallelizable vs. strictly sequential)
151
+ A plan can be marked approved only when all conditions hold:
152
+
153
+ - Every task in `implement.jsonl` has clear acceptance criteria.
154
+ - Every task has implementation steps and verification commands.
155
+ - Task dependencies are explicit, including whether work is serial or parallelizable.
113
156
 
114
157
  ## Post-Approval Options
115
158
 
116
- - **Step-by-task progression** (default path): user runs `task next <task-dir> --run` for the current safe action, completes the task, then runs `task next <next-task-dir> --run` for the next task.
117
- - **Batch mode**: triggered when the user explicitly says "auto" or "batch" (see skills/batch-execution/SKILL.md). Each task is still verified independently.
159
+ - **Step-by-task progression** (default path): the user runs `task next <task-dir> --run` for the current safe action, completes the task, then runs `task next <next-task-dir> --run` for the next task.
160
+ - **Batch mode**: triggered only when the user explicitly says "auto" or "batch"; see `skills/batch-execution/SKILL.md`. Each task still must be verified independently.
118
161
 
119
162
  > **Default is step-by-task progression.** Do not enter batch mode unless the user explicitly says "auto".
120
163
 
@@ -137,7 +180,8 @@ Execution strategy guide:
137
180
 
138
181
  Before handoff:
139
182
 
140
- 1. Confirm every decision-anchor.md acceptance criterion maps to a plan step.
183
+ 1. Confirm every `decision-anchor.md` acceptance criterion maps to a task.
141
184
  2. Search the plan for placeholders.
142
- 3. Check names, paths, command syntax, and expected outputs.
143
- 4. Record remaining risks or blocklers.
185
+ 3. Confirm every Normal or High-risk task has file boundaries, key symbols, implementation notes, test proof, completion conditions, and prohibited drift.
186
+ 4. Check names, paths, command syntax, and expected outputs.
187
+ 5. Confirm the plan says to stop and report back when code facts conflict with the plan, a plan-external file becomes necessary, or a test fails for an unplanned reason.
@@ -12,7 +12,7 @@ Use this Skill during the check phase, before the `task next --run` completion a
12
12
  - Review early enough that fixes are cheap; do not wait until archive/commit to discover blockers.
13
13
  - Review the current diff and exact task context, not broad conversation memory.
14
14
  - Separate machine-decidable lifecycle facts from human judgment: lifecycle blockers cover state/scope facts; review judgment explains spec, quality, and risk findings.
15
- - Treat backend/frontend/guides natural-language markdown as user-defined review requirements, not dynamic hard validators.
15
+ - Treat backend/frontend natural-language markdown as user-defined review requirements, not dynamic hard validators.
16
16
  - Prefer fresh verification from the current checkout over stale prior output.
17
17
  - Do not create task-local review artifact files. The review result and command output are the evidence.
18
18
  - Use advisory helper output only as review input; it must not become a second lifecycle gate.
@@ -28,16 +28,16 @@ Read only what is needed for the current task:
28
28
  2. The linked implementation plan, if present.
29
29
  3. The check context index (`<task>/check.jsonl`) and each referenced file, if present. Treat it as context only; do not write review conclusions or readiness evidence to it.
30
30
  4. Current `git diff` / `git status --short`.
31
- 5. Relevant `.cowork-flow/spec/backend/`, `.cowork-flow/spec/frontend/`, and `.cowork-flow/spec/guides/` files selected by changed paths and task scope.
31
+ 5. Relevant `.cowork-flow/spec/backend/` and `.cowork-flow/spec/frontend/` files selected by changed paths and task scope.
32
32
  6. Advisory facts from `review-check <task-dir> --json`, when useful.
33
- 7. Lifecycle blocker output, if any, from `task next`, `task review`, or `task complete`.
33
+ 7. Lifecycle blocker output, if any, from `task next --json`, `task next <dir> --validate`, or `task next <dir> --run --intent review`.
34
34
 
35
35
  ## Task Review Checklist
36
36
 
37
37
  - **Scope**: every changed file is planned or justified; no unrelated cleanup sneaks in.
38
38
  - **Behavior**: acceptance criteria are satisfied through observable behavior, not implementation-shaped assertions.
39
39
  - **Tests**: test intent is explicit; tests fail for meaningful regressions, reject shallow tests such as existence/mock/snapshot-only checks, and cover boundary/error paths when relevant.
40
- - **User specs**: every applicable backend/frontend/guides requirement is marked `pass`, `finding`, `not_applicable`, or `needs_user_judgment`.
40
+ - **User specs**: every applicable backend/frontend requirement is marked `pass`, `finding`, `not_applicable`, or `needs_user_judgment`.
41
41
  - **Specs**: project specs are updated when behavior/contracts changed, or the review states why no spec update is needed.
42
42
  - **Code quality**: naming, layering, error handling, state boundaries, security-sensitive paths, and complexity are reviewed against applicable user specs.
43
43
  - **Lifecycle blockers**: state/scope blockers are fixed before acceptance; review does not invent hard blockers for natural-language specs.