cowork-flow 0.0.26 → 0.0.28

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 (55) hide show
  1. package/README.md +16 -8
  2. package/package.json +1 -1
  3. package/src/lib/copy-template.js +2 -2
  4. package/template/.agents/skills/before-dev/SKILL.md +1 -0
  5. package/template/.agents/skills/check/SKILL.md +1 -0
  6. package/template/.agents/skills/party-mode-v2/SKILL.md +75 -0
  7. package/template/.agents/skills/tdd/SKILL.md +61 -0
  8. package/template/.agents/skills/writing-plans/SKILL.md +1 -0
  9. package/template/.claude/agents/cowork-implement.md +153 -0
  10. package/template/.claude/hooks/inject-workflow-state.py +17 -8
  11. package/template/.claude/skills/before-dev/SKILL.md +1 -0
  12. package/template/.claude/skills/check/SKILL.md +1 -0
  13. package/template/.claude/skills/party-mode-v2/SKILL.md +75 -0
  14. package/template/.claude/skills/tdd/SKILL.md +61 -0
  15. package/template/.claude/skills/writing-plans/SKILL.md +1 -0
  16. package/template/.codex/agents/cowork-check.toml +2 -0
  17. package/template/.codex/agents/cowork-implement.toml +2 -0
  18. package/template/.codex/hooks/inject-workflow-state.py +17 -8
  19. package/template/.cowork-flow/.version +1 -1
  20. package/template/.cowork-flow/config.yaml +16 -0
  21. package/template/.cowork-flow/scripts/common/archive_utils.py +1 -1
  22. package/template/.cowork-flow/scripts/common/coding_standards.py +209 -0
  23. package/template/.cowork-flow/scripts/common/config.py +93 -0
  24. package/template/.cowork-flow/scripts/common/gates.py +66 -0
  25. package/template/.cowork-flow/scripts/common/git_snapshot.py +83 -0
  26. package/template/.cowork-flow/scripts/common/state_machine.py +57 -0
  27. package/template/.cowork-flow/scripts/common/tdd_evidence.py +301 -0
  28. package/template/.cowork-flow/scripts/common/test_intent.py +265 -0
  29. package/template/.cowork-flow/scripts/common/validate_coding_standards.py +181 -0
  30. package/template/.cowork-flow/scripts/common/validate_implementation.py +243 -0
  31. package/template/.cowork-flow/scripts/common/validate_rules.py +465 -0
  32. package/template/.cowork-flow/scripts/doctor.py +18 -36
  33. package/template/.cowork-flow/scripts/party_mode_v2.py +1162 -0
  34. package/template/.cowork-flow/scripts/run.py +3 -0
  35. package/template/.cowork-flow/scripts/task.py +190 -0
  36. package/template/.cowork-flow/spec/contracts/index.md +12 -0
  37. package/template/.cowork-flow/spec/contracts/party-mode-v2-board.md +68 -0
  38. package/template/.cowork-flow/spec/{subagent-dispatch.md → contracts/subagent-dispatch.md} +2 -0
  39. package/template/.cowork-flow/spec/{workflow-state-templates.md → contracts/workflow-state-templates.md} +1 -1
  40. package/template/.cowork-flow/spec/index.md +15 -0
  41. package/template/.cowork-flow/spec/{registry.json → runtime/contract-registry.json} +17 -4
  42. package/template/.cowork-flow/spec/runtime/index.md +10 -0
  43. package/template/.cowork-flow/spec/runtime/rules.json +173 -0
  44. package/template/.cowork-flow/spec/schemas/index.md +10 -0
  45. package/template/.cowork-flow/spec/schemas/party-mode-v2-actions.schema.json +90 -0
  46. package/template/.cowork-flow/spec/schemas/rules.schema.json +80 -0
  47. package/template/.cowork-flow/workflow.md +13 -7
  48. package/template/.opencode/agents/cowork-implement.md +153 -0
  49. package/template/.opencode/commands/party-mode-v2.md +26 -0
  50. package/template/.opencode/plugins/cowork-flow.js +18 -7
  51. package/template/AGENTS.md +2 -6
  52. package/template/.cowork-flow/scripts/common/entry_classifier.py +0 -122
  53. /package/template/.cowork-flow/spec/{capabilities.md → contracts/capabilities.md} +0 -0
  54. /package/template/.cowork-flow/spec/{entry-contract.md → contracts/entry-contract.md} +0 -0
  55. /package/template/.cowork-flow/spec/{adapter.schema.json → schemas/adapter.schema.json} +0 -0
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Coding standards helper.
4
+
5
+ Summarizes relevant standards and validates changed files for lifecycle gates.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ from .coding_standards import validate_changed_files
16
+ from .git_snapshot import collect_changed_files, collect_changed_paths
17
+
18
+
19
+ def get_coding_standards_summary(
20
+ repo_root: Path,
21
+ task_dir: Path,
22
+ ) -> str:
23
+ """
24
+ Get coding standards summary based on changed files.
25
+
26
+ Args:
27
+ repo_root: Repository root directory
28
+ task_dir: Task directory
29
+
30
+ Returns:
31
+ Summary string of relevant coding standards
32
+ """
33
+ modified_files = collect_changed_paths(repo_root)
34
+ if not modified_files:
35
+ return ""
36
+
37
+ # Classify files
38
+ backend_files = [f for f in modified_files if _is_backend_file(f)]
39
+ frontend_files = [f for f in modified_files if _is_frontend_file(f)]
40
+
41
+ summaries = []
42
+
43
+ # Get backend standards summary
44
+ if backend_files:
45
+ backend_summary = _get_standards_summary(repo_root, "backend")
46
+ if backend_summary:
47
+ summaries.append(f"=== Backend Coding Standards ===\n{backend_summary}")
48
+
49
+ # Get frontend standards summary
50
+ if frontend_files:
51
+ frontend_summary = _get_standards_summary(repo_root, "frontend")
52
+ if frontend_summary:
53
+ summaries.append(f"=== Frontend Coding Standards ===\n{frontend_summary}")
54
+
55
+ return "\n\n".join(summaries)
56
+
57
+
58
+ def validate_coding_standards(
59
+ repo_root: Path,
60
+ task_dir: Path | None = None,
61
+ ) -> list[dict]:
62
+ """Validate coding standards for changed files."""
63
+ return validate_changed_files(repo_root, collect_changed_files(repo_root))
64
+
65
+
66
+ def _is_backend_file(file_path: str) -> bool:
67
+ """Check if file is a backend file"""
68
+ backend_patterns = [
69
+ r"\.py$",
70
+ r"\.java$",
71
+ r"\.go$",
72
+ r"\.rs$",
73
+ r"\.rb$",
74
+ ]
75
+ return any(re.search(p, file_path) for p in backend_patterns)
76
+
77
+
78
+ def _is_frontend_file(file_path: str) -> bool:
79
+ """Check if file is a frontend file"""
80
+ frontend_patterns = [
81
+ r"\.tsx$",
82
+ r"\.jsx$",
83
+ r"\.vue$",
84
+ r"\.svelte$",
85
+ ]
86
+ return any(re.search(p, file_path) for p in frontend_patterns)
87
+
88
+
89
+ def _get_standards_summary(repo_root: Path, category: str) -> str:
90
+ """
91
+ Get summary of coding standards from markdown files.
92
+
93
+ Args:
94
+ repo_root: Repository root directory
95
+ category: 'backend' or 'frontend'
96
+
97
+ Returns:
98
+ Summary string
99
+ """
100
+ spec_dir = repo_root / ".cowork-flow" / "spec" / category
101
+ if not spec_dir.exists():
102
+ return ""
103
+
104
+ summaries = []
105
+
106
+ for md_file in sorted(spec_dir.glob("*.md")):
107
+ if md_file.name == "index.md":
108
+ continue
109
+
110
+ content = md_file.read_text(encoding="utf-8")
111
+ rules = _extract_key_rules(content)
112
+
113
+ if rules:
114
+ summaries.append(f"File: {md_file.name}")
115
+ for rule in rules[:5]: # Limit to top 5 rules per file
116
+ summaries.append(f" - {rule}")
117
+ summaries.append("")
118
+
119
+ return "\n".join(summaries)
120
+
121
+
122
+ def _extract_key_rules(content: str) -> list[str]:
123
+ """Extract key rules from markdown content"""
124
+ rules = []
125
+
126
+ # Extract forbidden patterns (Must not, Should not, Do not, etc.)
127
+ # Also match Chinese patterns
128
+ forbidden_patterns = [
129
+ r"[-*]\s*(Must not|Should not|Do not|Never|Avoid)\s+(.+)",
130
+ r"[-*]\s*(禁止|不得|不能|不应|避免|不要|不可)\s*(.+)",
131
+ r"[-*]\s*(.+不得|.+不能|.+不应|.+避免|.+不要)\s*(.+)",
132
+ ]
133
+
134
+ for line in content.split("\n"):
135
+ line = line.strip()
136
+ if not line or line.startswith("#"):
137
+ continue
138
+
139
+ for pattern in forbidden_patterns:
140
+ match = re.search(pattern, line, re.IGNORECASE)
141
+ if match:
142
+ # Extract the rule text
143
+ rule_text = line.lstrip("-* ").strip()
144
+ if len(rule_text) > 5: # Skip very short rules
145
+ rules.append(rule_text[:80]) # Limit length
146
+ break # Only match one pattern per line
147
+
148
+ return rules[:10] # Return top 10 rules
149
+
150
+
151
+ def main():
152
+ """Main entry point"""
153
+ import argparse
154
+
155
+ parser = argparse.ArgumentParser(description="Get coding standards summary")
156
+ parser.add_argument("--task-dir", required=True, help="Task directory path")
157
+ parser.add_argument("--repo-root", default=".", help="Repository root path")
158
+ parser.add_argument("--validate", action="store_true", help="Fail on coding standards violations")
159
+
160
+ args = parser.parse_args()
161
+
162
+ repo_root = Path(args.repo_root).resolve()
163
+ task_dir = Path(args.task_dir).resolve()
164
+
165
+ if args.validate:
166
+ violations = validate_coding_standards(repo_root, task_dir)
167
+ if violations:
168
+ for violation in violations:
169
+ print(json.dumps(violation, ensure_ascii=False))
170
+ sys.exit(1)
171
+ sys.exit(0)
172
+
173
+ summary = get_coding_standards_summary(repo_root, task_dir)
174
+ if summary:
175
+ print(summary)
176
+ else:
177
+ print("No relevant coding standards found for changed files.")
178
+
179
+
180
+ if __name__ == "__main__":
181
+ main()
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Implementation validation script.
4
+
5
+ Validates code changes against forbidden action rules after task implementation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ try:
17
+ from .validate_rules import (
18
+ config_violation,
19
+ load_rule_index,
20
+ rule_violation,
21
+ runtime_rules_path,
22
+ )
23
+ except ImportError: # pragma: no cover - direct script execution
24
+ from validate_rules import ( # type: ignore[no-redef]
25
+ config_violation,
26
+ load_rule_index,
27
+ rule_violation,
28
+ runtime_rules_path,
29
+ )
30
+
31
+
32
+ def validate_implementation(
33
+ repo_root: Path,
34
+ task_dir: Path,
35
+ allow_spec_file_modifications: bool = False,
36
+ ) -> list[dict]:
37
+ """
38
+ Validate code changes against implementation rules.
39
+
40
+ Args:
41
+ repo_root: Repository root directory
42
+ task_dir: Task directory
43
+
44
+ Returns:
45
+ List of violations
46
+ """
47
+ violations = []
48
+
49
+ modified_files = _get_modified_files(repo_root)
50
+ if not modified_files:
51
+ return violations
52
+
53
+ rule_index, config_violations = load_rule_index(repo_root)
54
+ if config_violations:
55
+ return config_violations
56
+
57
+ diff_output = _get_git_diff(repo_root, modified_files)
58
+
59
+ # Check forbidden action rules
60
+ violations.extend(
61
+ _check_spec_file_modifications(
62
+ repo_root,
63
+ modified_files,
64
+ rule_index,
65
+ allow_spec_file_modifications=allow_spec_file_modifications,
66
+ )
67
+ )
68
+ violations.extend(_check_premature_abstraction(repo_root, diff_output, rule_index))
69
+ violations.extend(_check_unrequested_features(repo_root, diff_output, task_dir, rule_index))
70
+
71
+ return violations
72
+
73
+
74
+ def _missing_rule(repo_root: Path, rule_id: str) -> dict:
75
+ return config_violation(
76
+ "RULES-CONFIG-004",
77
+ f"Runtime workflow rule metadata is missing: {rule_id}",
78
+ runtime_rules_path(repo_root),
79
+ f"Add {rule_id} to .cowork-flow/spec/runtime/rules.json.",
80
+ )
81
+
82
+
83
+ def _get_modified_files(repo_root: Path) -> list[str]:
84
+ """Get git-tracked modified files under repo_root."""
85
+ try:
86
+ result = subprocess.run(
87
+ ["git", "diff", "--name-only", "--", "."],
88
+ cwd=repo_root,
89
+ capture_output=True,
90
+ text=True,
91
+ encoding="utf-8",
92
+ errors="replace",
93
+ )
94
+ if result.returncode == 0:
95
+ return [file_path for file_path in result.stdout.strip().split("\n") if file_path]
96
+ return []
97
+ except Exception:
98
+ return []
99
+
100
+
101
+ def _get_git_diff(repo_root: Path, files: list[str]) -> str:
102
+ """Get git diff output"""
103
+ if not files:
104
+ return ""
105
+
106
+ try:
107
+ diff_result = subprocess.run(
108
+ ["git", "diff", "--", *files],
109
+ cwd=repo_root,
110
+ capture_output=True,
111
+ text=True,
112
+ encoding="utf-8",
113
+ errors="replace",
114
+ )
115
+ return diff_result.stdout
116
+ except Exception:
117
+ return ""
118
+
119
+
120
+ def _check_spec_file_modifications(
121
+ repo_root: Path,
122
+ modified_files: list[str],
123
+ rule_index: dict[str, dict],
124
+ *,
125
+ allow_spec_file_modifications: bool,
126
+ ) -> list[dict]:
127
+ """R-AG-002: Check if spec files were modified"""
128
+ violations = []
129
+
130
+ if allow_spec_file_modifications:
131
+ return violations
132
+
133
+ # Spec file path patterns
134
+ spec_patterns = [
135
+ r"(^|/)\.cowork-flow/spec/",
136
+ r"(^|/)\.cowork-flow/workflow\.md$",
137
+ r"(^|/)AGENTS\.md$",
138
+ r"(^|/)CLAUDE\.md$",
139
+ ]
140
+
141
+ for file_path in modified_files:
142
+ normalized_path = file_path.replace("\\", "/")
143
+ if any(re.search(pattern, normalized_path) for pattern in spec_patterns):
144
+ rule = rule_index.get("R-AG-002")
145
+ violations.append(
146
+ rule_violation(rule, file_path) if rule else _missing_rule(repo_root, "R-AG-002")
147
+ )
148
+
149
+ return violations
150
+
151
+
152
+ def _check_premature_abstraction(
153
+ repo_root: Path,
154
+ diff_output: str,
155
+ rule_index: dict[str, dict],
156
+ ) -> list[dict]:
157
+ """R-AG-006: Check if premature abstraction was used"""
158
+ violations = []
159
+
160
+ # Detection patterns for abstraction
161
+ abstraction_patterns = [
162
+ r"def\s+base_", # base_ prefixed functions
163
+ r"class\s+Abstract", # Abstract prefixed classes
164
+ r"class\s+Base", # Base prefixed classes
165
+ r"class\s+Generic", # Generic prefixed classes
166
+ r"def\s+create_factory", # Factory pattern
167
+ r"def\s+get_instance", # Singleton pattern
168
+ ]
169
+
170
+ for pattern in abstraction_patterns:
171
+ matches = re.findall(pattern, diff_output)
172
+ if matches:
173
+ rule = rule_index.get("R-AG-006")
174
+ violations.append(
175
+ rule_violation(rule, "", detail=f"detected pattern: {pattern}")
176
+ if rule
177
+ else _missing_rule(repo_root, "R-AG-006")
178
+ )
179
+
180
+ return violations
181
+
182
+
183
+ def _check_unrequested_features(
184
+ repo_root: Path,
185
+ diff_output: str,
186
+ task_dir: Path,
187
+ rule_index: dict[str, dict],
188
+ ) -> list[dict]:
189
+ """R-AG-005: Check if unrequested features were introduced"""
190
+ violations = []
191
+
192
+ # Read PRD
193
+ prd_path = task_dir / "prd.md"
194
+ if not prd_path.exists():
195
+ return violations
196
+
197
+ prd_content = prd_path.read_text(encoding="utf-8")
198
+
199
+ # Check for obvious extra features
200
+ # This is a simple heuristic check, actual implementation may need more complex NLP analysis
201
+ feature_indicators = [
202
+ r"Added.*feature",
203
+ r"New.*functionality",
204
+ r"Implemented.*module",
205
+ ]
206
+
207
+ for pattern in feature_indicators:
208
+ if re.search(pattern, diff_output) and not re.search(pattern, prd_content):
209
+ rule = rule_index.get("R-AG-005")
210
+ violations.append(
211
+ rule_violation(rule, "", detail=f"detected pattern: {pattern}")
212
+ if rule
213
+ else _missing_rule(repo_root, "R-AG-005")
214
+ )
215
+
216
+ return violations
217
+
218
+
219
+ def main():
220
+ """Main entry point"""
221
+ import argparse
222
+
223
+ parser = argparse.ArgumentParser(description="Validate implementation against spec")
224
+ parser.add_argument("--task-dir", required=True, help="Task directory path")
225
+ parser.add_argument("--repo-root", default=".", help="Repository root path")
226
+
227
+ args = parser.parse_args()
228
+
229
+ repo_root = Path(args.repo_root).resolve()
230
+ task_dir = Path(args.task_dir).resolve()
231
+
232
+ violations = validate_implementation(repo_root, task_dir)
233
+
234
+ if violations:
235
+ for v in violations:
236
+ print(json.dumps(v, ensure_ascii=False))
237
+ sys.exit(1)
238
+
239
+ sys.exit(0)
240
+
241
+
242
+ if __name__ == "__main__":
243
+ main()