cowork-flow 0.0.41 → 0.0.43

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 (69) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/commands/install-zcode-plugin.js +11 -0
  4. package/template/.claude/agents/cowork-check.md +13 -2
  5. package/template/.claude/agents/cowork-implement.md +15 -4
  6. package/template/.codex/agents/cowork-check.toml +12 -7
  7. package/template/.codex/agents/cowork-implement.toml +13 -6
  8. package/template/.cowork-flow/.version +1 -1
  9. package/template/.cowork-flow/scripts/application/batch_execution.py +21 -0
  10. package/template/.cowork-flow/scripts/application/task_context.py +18 -8
  11. package/template/.cowork-flow/scripts/commands/task_navigation.py +1 -1
  12. package/template/.cowork-flow/scripts/common/core/quality_sources.py +140 -0
  13. package/template/.cowork-flow/scripts/common/gates/anti_rationalization.py +1 -1
  14. package/template/.cowork-flow/scripts/common/gates/gates.py +55 -2
  15. package/template/.cowork-flow/scripts/common/gates/quality_review.py +332 -0
  16. package/template/.cowork-flow/scripts/common/gates/tdd_evidence.py +231 -71
  17. package/template/.cowork-flow/scripts/common/gates/test_intent.py +6 -30
  18. package/template/.cowork-flow/scripts/common/gates/validate_coding_standards.py +13 -5
  19. package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
  20. package/template/.cowork-flow/spec/protocols/review.md +20 -1
  21. package/template/.cowork-flow/spec/protocols/tdd.md +3 -3
  22. package/template/.cowork-flow/spec/references/definition-of-done.md +1 -1
  23. package/template/.cowork-flow/spec/runtime/host-assets.json +0 -2
  24. package/template/.cowork-flow/spec/runtime/skill-registry.json +25 -2
  25. package/template/.cowork-flow/workflow.md +4 -3
  26. package/template/.opencode/agents/cowork-check.md +6 -0
  27. package/template/.opencode/agents/cowork-implement.md +15 -3
  28. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  29. package/template/.zcode/scaffold/.cowork-flow/workflow.md +4 -3
  30. package/template/skills/batch-mode/SKILL.md +72 -59
  31. package/template/skills/tdd/SKILL.md +44 -0
  32. package/template/skills/writing-plans/SKILL.md +44 -17
  33. package/template/.zcode/scaffold/.cowork-flow/spec/backend/database-guidelines.md +0 -23
  34. package/template/.zcode/scaffold/.cowork-flow/spec/backend/directory-structure.md +0 -27
  35. package/template/.zcode/scaffold/.cowork-flow/spec/backend/encoding-guidelines.md +0 -27
  36. package/template/.zcode/scaffold/.cowork-flow/spec/backend/error-handling.md +0 -20
  37. package/template/.zcode/scaffold/.cowork-flow/spec/backend/index.md +0 -40
  38. package/template/.zcode/scaffold/.cowork-flow/spec/backend/logging-guidelines.md +0 -20
  39. package/template/.zcode/scaffold/.cowork-flow/spec/backend/quality-guidelines.md +0 -25
  40. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/capabilities.md +0 -34
  41. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/decision-anchor.md +0 -74
  42. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/index.md +0 -11
  43. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/party-mode-v2-board.md +0 -68
  44. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/subagent-dispatch.md +0 -83
  45. package/template/.zcode/scaffold/.cowork-flow/spec/contracts/workflow-state-templates.md +0 -72
  46. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/component-guidelines.md +0 -19
  47. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/directory-structure.md +0 -18
  48. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/hook-guidelines.md +0 -17
  49. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/index.md +0 -39
  50. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/quality-guidelines.md +0 -25
  51. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/state-management.md +0 -18
  52. package/template/.zcode/scaffold/.cowork-flow/spec/frontend/type-safety.md +0 -18
  53. package/template/.zcode/scaffold/.cowork-flow/spec/game/asset-pipeline.md +0 -53
  54. package/template/.zcode/scaffold/.cowork-flow/spec/game/engine-guidelines.md +0 -43
  55. package/template/.zcode/scaffold/.cowork-flow/spec/game/index.md +0 -23
  56. package/template/.zcode/scaffold/.cowork-flow/spec/game/multiplayer-guidelines.md +0 -44
  57. package/template/.zcode/scaffold/.cowork-flow/spec/game/performance-guidelines.md +0 -58
  58. package/template/.zcode/scaffold/.cowork-flow/spec/guides/code-reuse-thinking-guide.md +0 -26
  59. package/template/.zcode/scaffold/.cowork-flow/spec/guides/cross-layer-thinking-guide.md +0 -21
  60. package/template/.zcode/scaffold/.cowork-flow/spec/guides/index.md +0 -31
  61. package/template/.zcode/scaffold/.cowork-flow/spec/guides/pre-implementation-checklist.md +0 -15
  62. package/template/.zcode/scaffold/.cowork-flow/spec/index.md +0 -15
  63. package/template/.zcode/scaffold/.cowork-flow/spec/runtime/contract-registry.json +0 -68
  64. package/template/.zcode/scaffold/.cowork-flow/spec/runtime/index.md +0 -10
  65. package/template/.zcode/scaffold/.cowork-flow/spec/runtime/rules.json +0 -176
  66. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/adapter.schema.json +0 -128
  67. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/index.md +0 -10
  68. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/party-mode-v2-actions.schema.json +0 -90
  69. package/template/.zcode/scaffold/.cowork-flow/spec/schemas/rules.schema.json +0 -89
package/README.md CHANGED
@@ -68,7 +68,7 @@ Skills 维护在 `template/skills/` 唯一源码,`init` 时按平台分发到
68
68
  | `codex` / `opencode` | `.agents/skills/` |
69
69
  | `claude-code` | `.claude/skills/` |
70
70
 
71
- 分发动作:`batch-mode`、`brainstorming`、`break-loop`、`cowork-flow`、`doubt-review`、`game-design`、`party-mode`、`writing-plans`
71
+ 分发动作:`batch-mode`、`brainstorming`、`break-loop`、`cowork-flow`、`doubt-review`、`game-design`、`party-mode`、`tdd`、`writing-plans`
72
72
 
73
73
  ## CLI 命令
74
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.41",
3
+ "version": "0.0.43",
4
4
  "description": "CLI for installing and maintaining cowork-flow collaboration templates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -72,6 +72,7 @@ export async function runInstallZCodePlugin(args = []) {
72
72
  const { dryRun, force } = parseArgs(args);
73
73
  const pluginSrc = join(templateRoot, ".zcode");
74
74
  const skillsSrc = join(templateRoot, "skills");
75
+ const specSrc = join(templateRoot, ".cowork-flow", "spec");
75
76
  const { version } = await readPackageInfo();
76
77
 
77
78
  if (!(await pathExists(pluginSrc))) {
@@ -80,6 +81,9 @@ export async function runInstallZCodePlugin(args = []) {
80
81
  if (!(await pathExists(skillsSrc))) {
81
82
  throw new Error(`Skills source missing at ${skillsSrc}. Reinstall cowork-flow.`);
82
83
  }
84
+ if (!(await pathExists(specSrc))) {
85
+ throw new Error(`Spec source missing at ${specSrc}. Reinstall cowork-flow.`);
86
+ }
83
87
 
84
88
  const cacheRoot = await getZCodeCacheDir();
85
89
  const destDir = join(cacheRoot, version);
@@ -94,6 +98,7 @@ export async function runInstallZCodePlugin(args = []) {
94
98
  console.log(`[dry-run] Would install ZCode plugin:`);
95
99
  console.log(` Plugin: ${pluginSrc} -> ${destDir}`);
96
100
  console.log(` Skills: ${skillsSrc} -> ${join(destDir, 'skills')}`);
101
+ console.log(` Spec: ${specSrc} -> ${join(destDir, 'scaffold', '.cowork-flow', 'spec')}`);
97
102
  return;
98
103
  }
99
104
 
@@ -110,6 +115,12 @@ export async function runInstallZCodePlugin(args = []) {
110
115
  const scaffoldVersionPath = join(destDir, "scaffold", ".cowork-flow", ".version");
111
116
  await writeFile(scaffoldVersionPath, `${version}\n`, "utf8");
112
117
 
118
+ // Materialize scaffold specs from the main template instead of maintaining
119
+ // a second static copy under template/.zcode/scaffold/.
120
+ const scaffoldSpecDest = join(destDir, "scaffold", ".cowork-flow", "spec");
121
+ await rm(scaffoldSpecDest, { recursive: true, force: true });
122
+ await cp(specSrc, scaffoldSpecDest, { recursive: true, force: true });
123
+
113
124
  // Sync canonical scripts from main template (single source of truth).
114
125
  // The .zcode/hooks/runtime/scripts/ copy is stale; overwrite with the
115
126
  // authoritative version from template/.cowork-flow/scripts/.
@@ -38,8 +38,11 @@ Load context before checking:
38
38
  2. Read `<task>/decision-anchor.md`.
39
39
  3. Read the plan file linked from this task (check `<task>/task.json` `relatedFiles` for a plan path, or search `.cowork-flow/plans/` for a plan file referencing this task directory). Use the plan's Verify commands and Expected results as the checklist for each step.
40
40
  4. Read `<task>/check.jsonl`.
41
- 5. Read `git diff`.
42
- 6. Include `test_intent_review` in the review output for meaningful behavior
41
+ 5. Read each JSONL `file` entry from `<task>/check.jsonl`.
42
+ 6. Read `<task>/quality-review.jsonl` if present and verify machine warning,
43
+ checklist, and Definition of Done evidence.
44
+ 7. Read `git diff`.
45
+ 8. Include `test_intent_review` in the review output for meaningful behavior
43
46
  breaks coverage and shallow tests rejection.
44
47
 
45
48
  Authoritative internal protocols:
@@ -59,3 +62,11 @@ Rules:
59
62
  - Report findings, changed files, and exact verification commands.
60
63
  - Include test intent findings that map tests back to decision-anchor acceptance or
61
64
  regression behavior.
65
+ - Run the deterministic coding gate before accepting quality:
66
+ `./.cowork-flow/run python .cowork-flow/scripts/common/gates/validate_coding_standards.py --validate --repo-root .`.
67
+ This gate enforces machine-decidable UTF-8/IO checks; backend/frontend
68
+ natural-language markdown remains checklist context, not dynamic hard validators.
69
+ - Treat machine warning output as review evidence: fix real issues or require
70
+ acknowledged warning records in `<task>/quality-review.jsonl`.
71
+ - Verify `<task>/quality-review.jsonl` covers Definition of Done, checklist
72
+ sources, affected files, and exact verification commands before acceptance.
@@ -36,19 +36,22 @@ the task.
36
36
  Load context before editing:
37
37
  1. Read the task directory from the bound runtime context.
38
38
  2. Read `<task>/decision-anchor.md`.
39
- 3. Read .
39
+ 3. Read `<task>/info.md` if present.
40
40
  4. Read `<task>/implement.jsonl`.
41
41
  5. Read each JSONL `file` entry.
42
42
  6. Read the plan file linked from this task (check `<task>/task.json` `relatedFiles` for a plan path, or search `.cowork-flow/plans/` for a plan file referencing this task directory). Follow the plan steps in order — each step has Files, Action, Verify, and Expected fields.
43
- 7. For behavior-change tasks, create or verify `<task>/tdd.jsonl` before
44
- implementation or record a documented exemption.
43
+ 7. Read quality source entries from context; backend/frontend natural-language
44
+ specs are review checklists, not dynamic hard validators.
45
+ 8. For behavior-change tasks, write the failing test before implementation and record any red/green evidence in `<task>/check.jsonl`, or record a documented exemption there.
46
+ 9. Before reporting completion, write `<task>/quality-review.jsonl` with
47
+ checklist, machine warning, and Definition of Done evidence.
45
48
 
46
49
  Authoritative internal protocols:
47
50
  - `.cowork-flow/spec/protocols/tdd.md`
48
51
  - `.cowork-flow/spec/protocols/decision-review.md`
49
52
  - `.cowork-flow/spec/protocols/spec-maintenance.md`
50
53
 
51
- Apply their output contracts exactly. Implementation reports and `tdd.jsonl`
54
+ Apply their output contracts exactly. Implementation reports and TDD entries in `check.jsonl`
52
55
  must preserve `acceptanceId`, `redCommand`, `redExitCode`, `greenCommand`,
53
56
  `greenExitCode`, and `specUpdates`.
54
57
 
@@ -62,3 +65,11 @@ Rules:
62
65
  include redCommand, redExitCode, greenCommand, greenExitCode, and
63
66
  decision-anchor acceptance ID mapping.
64
67
  - Report changed files and exact verification commands.
68
+ - Before editing source files, run the deterministic coding gate:
69
+ `./.cowork-flow/run python .cowork-flow/scripts/common/gates/validate_coding_standards.py --validate --repo-root .`.
70
+ This gate enforces machine-decidable UTF-8/IO checks; backend/frontend
71
+ natural-language markdown remains checklist context, not dynamic hard validators.
72
+ - Fix machine warning findings or record acknowledged warning evidence in
73
+ `<task>/quality-review.jsonl`.
74
+ - Record Definition of Done and quality checklist evidence in
75
+ `<task>/quality-review.jsonl`.
@@ -29,8 +29,11 @@ Load context before checking:
29
29
  2. Read `<task>/decision-anchor.md`.
30
30
  3. Read the plan file linked from this task (check `<task>/task.json` `relatedFiles` for a plan path, or search `.cowork-flow/plans/` for a plan file referencing this task directory). Use the plan's Verify commands and Expected results as the checklist for each step.
31
31
  4. Read `<task>/check.jsonl`.
32
- 5. Read `git diff`.
33
- 6. Include `test_intent_review` in the review output for meaningful behavior breaks coverage and shallow tests rejection.
32
+ 5. Read each JSONL `file` entry from `<task>/check.jsonl`.
33
+ 6. Read `<task>/quality-review.jsonl` if present and verify machine warning,
34
+ checklist, and Definition of Done evidence.
35
+ 7. Read `git diff`.
36
+ 8. Include `test_intent_review` in the review output for meaningful behavior breaks coverage and shallow tests rejection.
34
37
 
35
38
  Authoritative internal protocols:
36
39
  - `.cowork-flow/spec/protocols/review.md`
@@ -47,13 +50,15 @@ Rules:
47
50
  - MUST NOT commit, archive, or mutate cowork-flow task state.
48
51
  - Report findings, changed files, and exact verification commands.
49
52
  - Include test intent findings that map tests back to decision-anchor acceptance criteria or regression behavior.
50
- - BEFORE declaring decision-anchor acceptance passed, run the spec-driven coding gate and
53
+ - BEFORE declaring decision-anchor acceptance passed, run the deterministic coding gate and
51
54
  treat a nonzero exit as a hard blocker:
52
55
  ./.cowork-flow/run python .cowork-flow/scripts/common/gates/validate_coding_standards.py --validate --repo-root .
53
- This naturally enforces every rule the user added to
54
- .cowork-flow/spec/backend/*.md and spec/frontend/*.md — they are not just
55
- documentation but active validators. On violation, either fix the code or
56
- explicitly discard the rule from the spec; do not skip the gate.
56
+ This gate enforces machine-decidable UTF-8/IO checks. Backend/frontend
57
+ natural-language markdown remains checklist context, not dynamic hard validators.
58
+ - Treat machine warning output as review evidence: fix real issues or require
59
+ an acknowledged warning record in `<task>/quality-review.jsonl`.
60
+ - Verify `<task>/quality-review.jsonl` covers Definition of Done, checklist
61
+ sources, affected files, and exact verification commands before acceptance.
57
62
  """
58
63
 
59
64
  [features]
@@ -31,14 +31,18 @@ Load context before editing:
31
31
  4. Read `<task>/implement.jsonl`.
32
32
  5. Read each JSONL `file` entry.
33
33
  6. Read the plan file linked from this task (check `<task>/task.json` `relatedFiles` for a plan path, or search `.cowork-flow/plans/` for a plan file referencing this task directory). Follow the plan steps in order — each step has Files, Action, Verify, and Expected fields.
34
- 7. For behavior-change tasks, create or verify `<task>/tdd.jsonl` before implementation or record a documented exemption.
34
+ 7. Read quality source entries from context; backend/frontend natural-language
35
+ specs are review checklists, not dynamic hard validators.
36
+ 8. For behavior-change tasks, write the failing test before implementation and record any red/green evidence in `<task>/check.jsonl`, or record a documented exemption there.
37
+ 9. Before reporting completion, write `<task>/quality-review.jsonl` with
38
+ checklist, machine warning, and Definition of Done evidence.
35
39
 
36
40
  Authoritative internal protocols:
37
41
  - `.cowork-flow/spec/protocols/tdd.md`
38
42
  - `.cowork-flow/spec/protocols/decision-review.md`
39
43
  - `.cowork-flow/spec/protocols/spec-maintenance.md`
40
44
 
41
- Apply their output contracts exactly. Implementation reports and `tdd.jsonl`
45
+ Apply their output contracts exactly. Implementation reports and TDD entries in `check.jsonl`
42
46
  must preserve `acceptanceId`, `redCommand`, `redExitCode`, `greenCommand`,
43
47
  `greenExitCode`, and `specUpdates`.
44
48
 
@@ -49,12 +53,15 @@ Rules:
49
53
  - Keep edits inside requested scope.
50
54
  - Do not treat shallow tests as TDD evidence; red-green-refactor proof must include redCommand, redExitCode, greenCommand, greenExitCode, and decision-anchor acceptance ID mapping.
51
55
  - Report changed files and exact verification commands.
52
- - BEFORE editing any source file, run the spec-driven coding gate and treat a
56
+ - BEFORE editing any source file, run the deterministic coding gate and treat a
53
57
  nonzero exit as a hard blocker:
54
58
  ./.cowork-flow/run python .cowork-flow/scripts/common/gates/validate_coding_standards.py --validate --repo-root .
55
- Parse every *.md under .cowork-flow/spec/backend and spec/frontend; rules
56
- declared there activate validators dynamically. On violation, fix the code
57
- (do not disable the rule) and re-run until exit code 0.
59
+ This gate enforces machine-decidable UTF-8/IO checks. Backend/frontend
60
+ natural-language markdown remains checklist context, not dynamic hard validators.
61
+ - Run or inspect machine warning output, fix real issues, and record any
62
+ acknowledged warning in `<task>/quality-review.jsonl`.
63
+ - Record Definition of Done coverage and quality checklist evidence in
64
+ `<task>/quality-review.jsonl`.
58
65
  """
59
66
 
60
67
  [features]
@@ -1 +1 @@
1
- 0.0.41
1
+ 0.0.43
@@ -25,6 +25,7 @@ BATCH_STEPS = (
25
25
  "init_check_context",
26
26
  "await_check_result",
27
27
  "complete_task",
28
+ "archive_task",
28
29
  "commit_task",
29
30
  )
30
31
  STEP_PHASES = {
@@ -33,12 +34,14 @@ STEP_PHASES = {
33
34
  "review_task": "review",
34
35
  "await_check_result": "check",
35
36
  "complete_task": "complete",
37
+ "archive_task": "archive",
36
38
  "commit_task": "commit",
37
39
  }
38
40
  LIFECYCLE_STATUSES = {
39
41
  "start_task": "in_progress",
40
42
  "review_task": "review",
41
43
  "complete_task": "completed",
44
+ "archive_task": "completed",
42
45
  }
43
46
  RUNTIME_ROLES = {
44
47
  "init_implement_context": "implement",
@@ -322,6 +325,9 @@ class BatchExecutionService:
322
325
  f"commit could not be verified: {commit_id}",
323
326
  )
324
327
  return
328
+ if action_type == "archive_task":
329
+ self._verify_archive_result(task_name, result)
330
+ return
325
331
  raise BatchExecutionError(
326
332
  "BATCH-ACTION-UNKNOWN-001",
327
333
  f"unsupported Batch action: {action_type}",
@@ -364,6 +370,21 @@ class BatchExecutionService:
364
370
  f"expected {expected_status}",
365
371
  )
366
372
 
373
+ def _verify_archive_result(
374
+ self,
375
+ task_name: str,
376
+ result: dict,
377
+ ) -> None:
378
+ destination = self._optional_text(result, "archive_destination")
379
+ if destination is None:
380
+ return
381
+ archive_path = self.repo_root / destination
382
+ if not archive_path.exists():
383
+ raise BatchExecutionError(
384
+ "BATCH-ARCHIVE-VERIFY-001",
385
+ f"archive destination does not exist: {destination}",
386
+ )
387
+
367
388
  def _verify_runtime_initialized(
368
389
  self,
369
390
  state: dict,
@@ -9,6 +9,10 @@ from dataclasses import dataclass
9
9
  from pathlib import Path
10
10
 
11
11
  from common.core.files import read_text_utf8
12
+ from common.core.quality_sources import (
13
+ quality_review_artifact_entry,
14
+ quality_source_entries,
15
+ )
12
16
  from common.core.skill_registry import SkillRegistryError, load_skill_registry
13
17
  from common.core.paths import (
14
18
  DIR_AGENTS,
@@ -457,13 +461,15 @@ def get_check_context(repo_root: Path, dev_type: str) -> list[dict]:
457
461
  "reason": "Workflow review, completion, and archive routing",
458
462
  },
459
463
  ]
460
- entries.extend(
461
- {
462
- "file": spec_file,
463
- "reason": f"Verify {Path(spec_file).name} compliance",
464
- }
465
- for spec_file in discover_spec_files(repo_root, dev_type)
466
- )
464
+ if dev_type == "spec":
465
+ entries.extend(
466
+ {
467
+ "file": spec_file,
468
+ "reason": f"Verify {Path(spec_file).name} compliance",
469
+ }
470
+ for spec_file in discover_spec_files(repo_root, dev_type)
471
+ )
472
+ entries.extend(quality_source_entries(repo_root, dev_type))
467
473
  return entries
468
474
 
469
475
 
@@ -519,8 +525,12 @@ class TaskContextService:
519
525
  "task directory does not exist",
520
526
  )
521
527
 
528
+ implement_entries = self._implement_entries(dev_type)
529
+ implement_entries.append(
530
+ quality_review_artifact_entry(task_dir, self.repo_root)
531
+ )
522
532
  entries_by_file = {
523
- "implement.jsonl": self._implement_entries(dev_type),
533
+ "implement.jsonl": implement_entries,
524
534
  "check.jsonl": get_check_context(self.repo_root, dev_type),
525
535
  "debug.jsonl": get_debug_context(dev_type, self.repo_root),
526
536
  }
@@ -96,7 +96,7 @@ def _print_blockers(blockers: list[str]) -> None:
96
96
 
97
97
  def _implementation(task_path: str) -> None:
98
98
  print("Next action: execute implementation plan")
99
- print(f"TDD reminder: for behavior changes, write a failing test and record red evidence in {task_path}/tdd.jsonl before modifying code.")
99
+ print(f"TDD reminder: for behavior changes, write the failing test first and record any red/green evidence in {task_path}/check.jsonl.")
100
100
  print(
101
101
  f"Command: ./.cowork-flow/run subagent init --role implement "
102
102
  f"--agent-type cowork-implement --execution-task-dir {task_path} "
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env python3
2
+ """Resolve precise quality context sources for review/check stages."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ from pathlib import Path
8
+
9
+
10
+ QUALITY_REVIEW_FILE = "quality-review.jsonl"
11
+ SPEC_ROOT = ".cowork-flow/spec"
12
+ REFERENCE_DOD = f"{SPEC_ROOT}/references/definition-of-done.md"
13
+ REFERENCE_TESTING = f"{SPEC_ROOT}/references/testing-checklist.md"
14
+ REFERENCE_SECURITY = f"{SPEC_ROOT}/references/security-checklist.md"
15
+
16
+ _MARKDOWN_LINK_RE = re.compile(r"\[[^\]]+\]\(([^)#]+)(?:#[^)]+)?\)")
17
+ _SECURITY_HINT_RE = re.compile(
18
+ r"(?i)(auth|credential|jwt|oauth|password|permission|secret|security|session|token|\.env)"
19
+ )
20
+
21
+
22
+ def quality_source_entries(
23
+ repo_root: Path,
24
+ dev_type: str,
25
+ *,
26
+ paths: tuple[str, ...] = (),
27
+ ) -> list[dict]:
28
+ """Return narrow quality/spec sources a checker should read."""
29
+ entries: list[dict] = []
30
+ seen: set[str] = set()
31
+ for source in quality_source_paths(repo_root, dev_type, paths=paths):
32
+ if source in seen:
33
+ continue
34
+ seen.add(source)
35
+ entries.append(
36
+ {
37
+ "file": source,
38
+ "reason": _quality_source_reason(source),
39
+ }
40
+ )
41
+ return entries
42
+
43
+
44
+ def quality_source_paths(
45
+ repo_root: Path,
46
+ dev_type: str,
47
+ *,
48
+ paths: tuple[str, ...] = (),
49
+ ) -> list[str]:
50
+ """Return existing repo-relative quality source paths."""
51
+ root = Path(repo_root)
52
+ sources: list[str] = []
53
+ for category in _categories_for(dev_type):
54
+ index = f"{SPEC_ROOT}/{category}/index.md"
55
+ sources.extend(_existing_paths(root, (index,)))
56
+ sources.extend(_linked_markdown_sources(root, index, category))
57
+
58
+ sources.extend(_existing_paths(root, (REFERENCE_DOD, REFERENCE_TESTING)))
59
+ if _security_relevant(paths):
60
+ sources.extend(_existing_paths(root, (REFERENCE_SECURITY,)))
61
+ return sources
62
+
63
+
64
+ def quality_review_artifact_entry(task_dir: Path, repo_root: Path) -> dict:
65
+ """Return the task-local quality-review artifact context entry."""
66
+ task_path = _repo_relative_task_path(task_dir, repo_root)
67
+ return {
68
+ "file": f"{task_path}/{QUALITY_REVIEW_FILE}",
69
+ "reason": "Task-local quality review evidence required before complete",
70
+ }
71
+
72
+
73
+ def _categories_for(dev_type: str) -> tuple[str, ...]:
74
+ if dev_type == "backend":
75
+ return ("backend",)
76
+ if dev_type == "frontend":
77
+ return ("frontend",)
78
+ if dev_type == "fullstack":
79
+ return ("backend", "frontend")
80
+ return ()
81
+
82
+
83
+ def _linked_markdown_sources(
84
+ repo_root: Path,
85
+ index_path: str,
86
+ category: str,
87
+ ) -> list[str]:
88
+ index = repo_root / index_path
89
+ if not index.is_file():
90
+ return []
91
+ try:
92
+ text = index.read_text(encoding="utf-8")
93
+ except (OSError, UnicodeDecodeError):
94
+ return []
95
+
96
+ sources: list[str] = []
97
+ for match in _MARKDOWN_LINK_RE.finditer(text):
98
+ target = match.group(1).strip()
99
+ if "://" in target or target.startswith("#"):
100
+ continue
101
+ normalized = _normalize_markdown_target(category, target)
102
+ if normalized is not None:
103
+ sources.extend(_existing_paths(repo_root, (normalized,)))
104
+ return sources
105
+
106
+
107
+ def _normalize_markdown_target(category: str, target: str) -> str | None:
108
+ without_anchor = target.split("#", 1)[0].strip()
109
+ if not without_anchor.endswith(".md"):
110
+ return None
111
+ if without_anchor.startswith("./"):
112
+ without_anchor = without_anchor[2:]
113
+ if without_anchor.startswith("../") or without_anchor.startswith("/"):
114
+ return None
115
+ return f"{SPEC_ROOT}/{category}/{without_anchor}"
116
+
117
+
118
+ def _existing_paths(repo_root: Path, paths: tuple[str, ...]) -> list[str]:
119
+ return [path for path in paths if (repo_root / path).is_file()]
120
+
121
+
122
+ def _security_relevant(paths: tuple[str, ...]) -> bool:
123
+ return any(_SECURITY_HINT_RE.search(path) is not None for path in paths)
124
+
125
+
126
+ def _quality_source_reason(source: str) -> str:
127
+ if source == REFERENCE_DOD:
128
+ return "Verify Definition of Done evidence"
129
+ if source == REFERENCE_TESTING:
130
+ return "Verify test depth and anti-shallow-test coverage"
131
+ if source == REFERENCE_SECURITY:
132
+ return "Verify security-sensitive changes"
133
+ return f"Verify {Path(source).name} quality rules"
134
+
135
+
136
+ def _repo_relative_task_path(task_dir: Path, repo_root: Path) -> str:
137
+ try:
138
+ return Path(task_dir).resolve().relative_to(Path(repo_root).resolve()).as_posix()
139
+ except ValueError:
140
+ return Path(task_dir).as_posix()
@@ -43,7 +43,7 @@ def validate_exemption_rationalization(reason: str) -> list[dict]:
43
43
  f"'{pattern}'. Replace with a concrete behavior test or "
44
44
  f"specific verification command."
45
45
  ),
46
- "file": "<task>/tdd.jsonl",
46
+ "file": "<task>/check.jsonl",
47
47
  "fix_hint": (
48
48
  "Remove rationalization from exemption reason. "
49
49
  "Either write a failing behavior test for this AC "
@@ -25,6 +25,7 @@ STAGE_GATES = {
25
25
  "test_intent",
26
26
  "runtime_rules",
27
27
  "coding_standards",
28
+ "quality_machine_checks",
28
29
  "complexity",
29
30
  ),
30
31
  "task_complete": (
@@ -33,12 +34,14 @@ STAGE_GATES = {
33
34
  "test_intent",
34
35
  "runtime_rules",
35
36
  "coding_standards",
37
+ "quality_machine_checks",
38
+ "quality_review",
36
39
  "complexity",
37
40
  ),
38
41
  }
39
42
 
40
43
 
41
- def _validator_bindings() -> tuple[ValidatorBinding, ...]:
44
+ def _core_validator_bindings() -> tuple[ValidatorBinding, ...]:
42
45
  return (
43
46
  ValidatorBinding(
44
47
  key="runtime_rules",
@@ -73,6 +76,30 @@ def _validator_bindings() -> tuple[ValidatorBinding, ...]:
73
76
  function="validate_coding_standards",
74
77
  positional=("repo_root", "task_dir"),
75
78
  ),
79
+ )
80
+
81
+
82
+ def _quality_validator_bindings() -> tuple[ValidatorBinding, ...]:
83
+ return (
84
+ ValidatorBinding(
85
+ key="quality_machine_checks",
86
+ module="validate_coding_standards",
87
+ function="validate_machine_checks",
88
+ positional=("repo_root", "task_dir"),
89
+ ),
90
+ ValidatorBinding(
91
+ key="quality_review",
92
+ module="quality_review",
93
+ function="validate_quality_review",
94
+ positional=("repo_root", "task_dir"),
95
+ ),
96
+ )
97
+
98
+
99
+ def _validator_bindings() -> tuple[ValidatorBinding, ...]:
100
+ return (
101
+ *_core_validator_bindings(),
102
+ *_quality_validator_bindings(),
76
103
  ValidatorBinding(
77
104
  key="complexity",
78
105
  module="validate_coding_standards",
@@ -82,7 +109,7 @@ def _validator_bindings() -> tuple[ValidatorBinding, ...]:
82
109
  )
83
110
 
84
111
 
85
- def _gate_definitions() -> tuple[GateDefinition, ...]:
112
+ def _core_gate_definitions() -> tuple[GateDefinition, ...]:
86
113
  return (
87
114
  GateDefinition(
88
115
  id="runtime_rules",
@@ -103,6 +130,7 @@ def _gate_definitions() -> tuple[GateDefinition, ...]:
103
130
  validator_key="tdd_evidence",
104
131
  required=True,
105
132
  block_message="TDD evidence gate blocked lifecycle transition",
133
+ warning_message="TDD evidence advisories",
106
134
  ),
107
135
  GateDefinition(
108
136
  id="test_intent",
@@ -117,6 +145,31 @@ def _gate_definitions() -> tuple[GateDefinition, ...]:
117
145
  required=True,
118
146
  block_message="Coding standards gate blocked lifecycle transition",
119
147
  ),
148
+ )
149
+
150
+
151
+ def _quality_gate_definitions() -> tuple[GateDefinition, ...]:
152
+ return (
153
+ GateDefinition(
154
+ id="quality_machine_checks",
155
+ validator_key="quality_machine_checks",
156
+ required=False,
157
+ block_message="",
158
+ warning_message="Quality machine-check warnings",
159
+ ),
160
+ GateDefinition(
161
+ id="quality_review",
162
+ validator_key="quality_review",
163
+ required=True,
164
+ block_message="Quality review gate blocked lifecycle transition",
165
+ ),
166
+ )
167
+
168
+
169
+ def _gate_definitions() -> tuple[GateDefinition, ...]:
170
+ return (
171
+ *_core_gate_definitions(),
172
+ *_quality_gate_definitions(),
120
173
  GateDefinition(
121
174
  id="complexity",
122
175
  validator_key="complexity",