open-codev-workflow 0.1.0__py3-none-any.whl

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 (47) hide show
  1. codev_workflow/__init__.py +5 -0
  2. codev_workflow/__main__.py +4 -0
  3. codev_workflow/bundle/.agents/skills/build-change/SKILL.md +96 -0
  4. codev_workflow/bundle/.agents/skills/build-change/agents/openai.yaml +4 -0
  5. codev_workflow/bundle/.agents/skills/build-change/assets/implementation-plan.template.md +51 -0
  6. codev_workflow/bundle/.agents/skills/define-product/SKILL.md +79 -0
  7. codev_workflow/bundle/.agents/skills/define-product/agents/openai.yaml +4 -0
  8. codev_workflow/bundle/.agents/skills/define-product/assets/brief.template.md +50 -0
  9. codev_workflow/bundle/.agents/skills/design-solution/SKILL.md +75 -0
  10. codev_workflow/bundle/.agents/skills/design-solution/agents/openai.yaml +4 -0
  11. codev_workflow/bundle/.agents/skills/design-solution/assets/decision.template.md +26 -0
  12. codev_workflow/bundle/.agents/skills/design-solution/assets/design.template.md +76 -0
  13. codev_workflow/bundle/.agents/skills/launch-product/SKILL.md +66 -0
  14. codev_workflow/bundle/.agents/skills/launch-product/agents/openai.yaml +4 -0
  15. codev_workflow/bundle/.agents/skills/launch-product/assets/launch-plan.template.md +48 -0
  16. codev_workflow/bundle/.agents/skills/plan-delivery/SKILL.md +140 -0
  17. codev_workflow/bundle/.agents/skills/plan-delivery/agents/openai.yaml +4 -0
  18. codev_workflow/bundle/.agents/skills/plan-delivery/assets/delivery-plan.template.md +41 -0
  19. codev_workflow/bundle/.agents/skills/review-change/SKILL.md +48 -0
  20. codev_workflow/bundle/.agents/skills/review-change/agents/openai.yaml +4 -0
  21. codev_workflow/bundle/.agents/skills/specify-project/SKILL.md +205 -0
  22. codev_workflow/bundle/.agents/skills/specify-project/agents/openai.yaml +4 -0
  23. codev_workflow/bundle/.agents/skills/specify-project/assets/specification.template.md +151 -0
  24. codev_workflow/bundle/.agents/skills/specify-project/references/interview-coverage.md +303 -0
  25. codev_workflow/bundle/.agents/skills/specify-project/scripts/validate_specification.py +143 -0
  26. codev_workflow/bundle/.opencode/agents/builder.md +54 -0
  27. codev_workflow/bundle/.opencode/agents/orchestrator.md +72 -0
  28. codev_workflow/bundle/.opencode/agents/reviewer.md +35 -0
  29. codev_workflow/bundle/AGENTS.md +23 -0
  30. codev_workflow/bundle/docs/AI-WORKFLOW-PROMPTS.md +318 -0
  31. codev_workflow/bundle/docs/WORKFLOW-COOKBOOK.md +419 -0
  32. codev_workflow/bundle/docs/WORKFLOW-HUMAN.md +212 -0
  33. codev_workflow/bundle/docs/for-ai/WORKFLOW-AGENTS.md +171 -0
  34. codev_workflow/bundle/docs/handbooks/IDEA-TO-PRODUCTION-HANDBOOK.md +1190 -0
  35. codev_workflow/bundle/docs/handbooks/LANGUAGE-AGNOSTIC-PROJECT-HANDBOOK.md +745 -0
  36. codev_workflow/bundle/docs/handbooks/PYTHON-PROJECT-HANDBOOK.md +960 -0
  37. codev_workflow/bundle/evals/development-workflow/scenarios.json +132 -0
  38. codev_workflow/bundle/scripts/evaluate-development-workflow.py +352 -0
  39. codev_workflow/bundle/scripts/validate-development-workflow.py +213 -0
  40. codev_workflow/cli.py +140 -0
  41. codev_workflow/installer.py +891 -0
  42. open_codev_workflow-0.1.0.dist-info/METADATA +150 -0
  43. open_codev_workflow-0.1.0.dist-info/RECORD +47 -0
  44. open_codev_workflow-0.1.0.dist-info/WHEEL +5 -0
  45. open_codev_workflow-0.1.0.dist-info/entry_points.txt +2 -0
  46. open_codev_workflow-0.1.0.dist-info/licenses/LICENSE +28 -0
  47. open_codev_workflow-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python3
2
+ """Validate the repository's human-AI development workflow without dependencies."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ EXPECTED_SKILLS = {
15
+ "specify-project": [
16
+ "assets/specification.template.md",
17
+ "references/interview-coverage.md",
18
+ "scripts/validate_specification.py",
19
+ ],
20
+ "define-product": ["assets/brief.template.md"],
21
+ "design-solution": [
22
+ "assets/design.template.md",
23
+ "assets/decision.template.md",
24
+ ],
25
+ "plan-delivery": ["assets/delivery-plan.template.md"],
26
+ "build-change": ["assets/implementation-plan.template.md"],
27
+ "review-change": [],
28
+ "launch-product": ["assets/launch-plan.template.md"],
29
+ }
30
+
31
+ EXPECTED_HANDBOOKS = [
32
+ "PYTHON-PROJECT-HANDBOOK.md",
33
+ "LANGUAGE-AGNOSTIC-PROJECT-HANDBOOK.md",
34
+ "IDEA-TO-PRODUCTION-HANDBOOK.md",
35
+ ]
36
+
37
+ EXPECTED_GUIDES = [
38
+ "AGENTS.md",
39
+ "docs/WORKFLOW-HUMAN.md",
40
+ "docs/for-ai/WORKFLOW-AGENTS.md",
41
+ "docs/WORKFLOW-COOKBOOK.md",
42
+ "docs/AI-WORKFLOW-PROMPTS.md",
43
+ ]
44
+
45
+ EVALUATION_SCRIPT = "scripts/evaluate-development-workflow.py"
46
+ EVALUATION_CATALOG = "evals/development-workflow/scenarios.json"
47
+
48
+
49
+ def parse_frontmatter(text: str, path: Path, errors: list[str]) -> dict[str, str]:
50
+ lines = text.splitlines()
51
+ if not lines or lines[0] != "---":
52
+ errors.append(f"{path}: missing opening YAML delimiter")
53
+ return {}
54
+ try:
55
+ end = lines.index("---", 1)
56
+ except ValueError:
57
+ errors.append(f"{path}: missing closing YAML delimiter")
58
+ return {}
59
+
60
+ result: dict[str, str] = {}
61
+ for line in lines[1:end]:
62
+ match = re.fullmatch(r"([a-zA-Z0-9_-]+):\s*(.+)", line)
63
+ if not match:
64
+ errors.append(f"{path}: unsupported frontmatter line: {line!r}")
65
+ continue
66
+ result[match.group(1)] = match.group(2).strip().strip('"')
67
+ return result
68
+
69
+
70
+ def validate_skill(root: Path, name: str, assets: list[str], errors: list[str]) -> None:
71
+ skill_dir = root / name
72
+ skill_file = skill_dir / "SKILL.md"
73
+ if not skill_file.is_file():
74
+ errors.append(f"{skill_file}: missing")
75
+ return
76
+
77
+ text = skill_file.read_text(encoding="utf-8")
78
+ metadata = parse_frontmatter(text, skill_file, errors)
79
+ if set(metadata) != {"name", "description"}:
80
+ errors.append(f"{skill_file}: frontmatter must contain only name and description")
81
+ if metadata.get("name") != name:
82
+ errors.append(f"{skill_file}: name must match directory {name!r}")
83
+ if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", name) or len(name) > 64:
84
+ errors.append(f"{skill_file}: name must be hyphen-case and at most 64 characters")
85
+ description = metadata.get("description", "")
86
+ if len(description) < 80:
87
+ errors.append(f"{skill_file}: description is too short to trigger reliably")
88
+ if len(description) > 1024 or "<" in description or ">" in description:
89
+ errors.append(f"{skill_file}: description violates skill metadata limits")
90
+ if len(text.splitlines()) > 500:
91
+ errors.append(f"{skill_file}: exceeds the 500-line skill budget")
92
+ if re.search(r"\b(TODO|TBD)\b", text, re.IGNORECASE):
93
+ errors.append(f"{skill_file}: contains an unfinished placeholder")
94
+
95
+ ui_file = skill_dir / "agents" / "openai.yaml"
96
+ if not ui_file.is_file():
97
+ errors.append(f"{ui_file}: missing")
98
+ else:
99
+ ui = ui_file.read_text(encoding="utf-8")
100
+ for field in ("display_name", "short_description", "default_prompt"):
101
+ if not re.search(rf"^\s*{field}:\s*\".+\"\s*$", ui, re.MULTILINE):
102
+ errors.append(f"{ui_file}: missing quoted {field}")
103
+ if f"${name}" not in ui:
104
+ errors.append(f"{ui_file}: default_prompt must mention ${name}")
105
+
106
+ for relative in assets:
107
+ asset = skill_dir / relative
108
+ if not asset.is_file() or asset.stat().st_size == 0:
109
+ errors.append(f"{asset}: missing or empty")
110
+
111
+
112
+ def validate_guides(repo: Path, errors: list[str]) -> None:
113
+ guides = [repo / relative for relative in EXPECTED_GUIDES]
114
+ for guide in guides:
115
+ if not guide.is_file():
116
+ errors.append(f"{guide}: missing")
117
+ continue
118
+ text = guide.read_text(encoding="utf-8")
119
+ for skill in EXPECTED_SKILLS:
120
+ if skill not in text:
121
+ errors.append(f"{guide}: does not reference {skill}")
122
+
123
+
124
+ def validate_handbooks(repo: Path, errors: list[str]) -> None:
125
+ handbook_root = repo / "docs" / "handbooks"
126
+ for name in EXPECTED_HANDBOOKS:
127
+ handbook = handbook_root / name
128
+ if not handbook.is_file():
129
+ errors.append(f"{handbook}: missing")
130
+ continue
131
+ text = handbook.read_text(encoding="utf-8")
132
+ if len(text.splitlines()) < 100:
133
+ errors.append(f"{handbook}: unexpectedly short")
134
+ if re.search(r"\b(TODO|TBD)\b", text, re.IGNORECASE):
135
+ errors.append(f"{handbook}: contains an unfinished placeholder")
136
+ for skill in EXPECTED_SKILLS:
137
+ if skill not in text:
138
+ errors.append(f"{handbook}: does not reference {skill}")
139
+
140
+
141
+ def validate_evaluations(repo: Path, errors: list[str]) -> int:
142
+ script = repo / EVALUATION_SCRIPT
143
+ catalog = repo / EVALUATION_CATALOG
144
+ if not script.is_file():
145
+ errors.append(f"{script}: missing")
146
+ return 0
147
+ if not catalog.is_file():
148
+ errors.append(f"{catalog}: missing")
149
+ return 0
150
+
151
+ for label, extra_args in (
152
+ ("catalog", []),
153
+ ("scorer self-test", ["--self-test"]),
154
+ ):
155
+ try:
156
+ completed = subprocess.run(
157
+ [sys.executable, str(script), "--repo", str(repo), *extra_args],
158
+ check=False,
159
+ capture_output=True,
160
+ text=True,
161
+ timeout=10,
162
+ )
163
+ except subprocess.TimeoutExpired:
164
+ errors.append(f"behavioral evaluation {label} timed out")
165
+ return 0
166
+ if completed.returncode != 0:
167
+ detail = (completed.stdout + completed.stderr).strip()
168
+ errors.append(f"behavioral evaluation {label} is invalid: {detail}")
169
+ return 0
170
+
171
+ try:
172
+ data = json.loads(catalog.read_text(encoding="utf-8"))
173
+ return len(data.get("scenarios", []))
174
+ except (OSError, ValueError, TypeError) as error:
175
+ errors.append(f"{catalog}: cannot count scenarios: {error}")
176
+ return 0
177
+
178
+
179
+ def main() -> int:
180
+ parser = argparse.ArgumentParser()
181
+ parser.add_argument(
182
+ "--repo",
183
+ type=Path,
184
+ default=Path(__file__).resolve().parents[1],
185
+ help="Repository root (defaults to the script's repository)",
186
+ )
187
+ args = parser.parse_args()
188
+ repo = args.repo.resolve()
189
+ skill_root = repo / ".agents" / "skills"
190
+ errors: list[str] = []
191
+
192
+ for skill, assets in EXPECTED_SKILLS.items():
193
+ validate_skill(skill_root, skill, assets, errors)
194
+ validate_guides(repo, errors)
195
+ validate_handbooks(repo, errors)
196
+ scenario_count = validate_evaluations(repo, errors)
197
+
198
+ if errors:
199
+ print("Workflow validation failed:")
200
+ for error in errors:
201
+ print(f"- {error}")
202
+ return 1
203
+
204
+ print(
205
+ "Workflow validation passed: "
206
+ f"{len(EXPECTED_SKILLS)} skills, {len(EXPECTED_GUIDES)} guides, and "
207
+ f"{len(EXPECTED_HANDBOOKS)} handbooks, plus {scenario_count} behavioral scenarios"
208
+ )
209
+ return 0
210
+
211
+
212
+ if __name__ == "__main__":
213
+ sys.exit(main())
codev_workflow/cli.py ADDED
@@ -0,0 +1,140 @@
1
+ """Command-line interface for CoDev."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import platform
7
+ import sys
8
+ from collections.abc import Sequence
9
+ from pathlib import Path
10
+
11
+ from codev_workflow import __version__
12
+ from codev_workflow.installer import (
13
+ CoDevError,
14
+ apply_plan,
15
+ check_project,
16
+ format_plan,
17
+ plan_init,
18
+ plan_remove,
19
+ plan_update,
20
+ )
21
+
22
+
23
+ def _target(value: str) -> Path:
24
+ return Path(value).expanduser()
25
+
26
+
27
+ def _parser() -> argparse.ArgumentParser:
28
+ parser = argparse.ArgumentParser(
29
+ prog="codev",
30
+ description="Install and maintain human-guided AI delivery workflows.",
31
+ )
32
+ parser.add_argument("--version", action="version", version=f"CoDev {__version__}")
33
+ commands = parser.add_subparsers(dest="command", required=True)
34
+
35
+ init = commands.add_parser("init", help="install CoDev into a repository")
36
+ init.add_argument("--target", type=_target, default=Path.cwd())
37
+ init.add_argument(
38
+ "--platform",
39
+ action="append",
40
+ choices=("all", "codex", "opencode"),
41
+ default=None,
42
+ help="target adapter; repeat to select several (default: all)",
43
+ )
44
+ init.add_argument("--dry-run", action="store_true", help="show the plan only")
45
+
46
+ check = commands.add_parser("check", help="verify an installed bundle")
47
+ check.add_argument("--target", type=_target, default=Path.cwd())
48
+
49
+ doctor = commands.add_parser("doctor", help="show environment and bundle health")
50
+ doctor.add_argument("--target", type=_target, default=Path.cwd())
51
+
52
+ diff = commands.add_parser("diff", help="preview update changes")
53
+ diff.add_argument("--target", type=_target, default=Path.cwd())
54
+
55
+ update = commands.add_parser("update", help="apply a conflict-free bundle update")
56
+ update.add_argument("--target", type=_target, default=Path.cwd())
57
+
58
+ remove = commands.add_parser(
59
+ "remove", help="remove an unchanged CoDev installation"
60
+ )
61
+ remove.add_argument("--target", type=_target, default=Path.cwd())
62
+ remove.add_argument("--dry-run", action="store_true", help="show the plan only")
63
+ return parser
64
+
65
+
66
+ def _print_check(target: Path) -> int:
67
+ result = check_project(target)
68
+ if result.ok:
69
+ print(
70
+ f"CoDev {result.version} is healthy: "
71
+ f"{result.managed_files} managed files, no drift."
72
+ )
73
+ return 0
74
+ print(f"CoDev check found {len(result.issues)} issue(s):")
75
+ for issue in result.issues:
76
+ print(f"- {issue}")
77
+ return 1
78
+
79
+
80
+ def main(argv: Sequence[str] | None = None) -> int:
81
+ args = _parser().parse_args(argv)
82
+ try:
83
+ if args.command == "init":
84
+ target = args.target.resolve()
85
+ platforms = args.platform or ["all"]
86
+ plan = plan_init(target, platforms)
87
+ print(format_plan(plan))
88
+ if plan.conflicts:
89
+ print(f"Installation stopped: {len(plan.conflicts)} conflict(s).")
90
+ return 2
91
+ if args.dry_run:
92
+ print("Dry run complete; no files were written.")
93
+ return 0
94
+ apply_plan(target, plan)
95
+ print(f"Installed CoDev {__version__} into {target}")
96
+ return 0
97
+
98
+ if args.command == "check":
99
+ return _print_check(args.target.resolve())
100
+
101
+ if args.command == "doctor":
102
+ print(f"CoDev: {__version__}")
103
+ print(f"Python: {platform.python_version()} ({platform.system()})")
104
+ print(f"Target: {args.target.resolve()}")
105
+ return _print_check(args.target.resolve())
106
+
107
+ if args.command in {"diff", "update"}:
108
+ target = args.target.resolve()
109
+ plan = plan_update(target)
110
+ print(format_plan(plan))
111
+ if plan.conflicts:
112
+ print(f"Update stopped: {len(plan.conflicts)} conflict(s).")
113
+ return 2
114
+ if args.command == "diff":
115
+ print("Preview complete; no files were written.")
116
+ return 0
117
+ apply_plan(target, plan)
118
+ print(f"Updated CoDev bundle to {__version__} in {target}")
119
+ return 0
120
+
121
+ if args.command == "remove":
122
+ target = args.target.resolve()
123
+ plan = plan_remove(target)
124
+ print(format_plan(plan))
125
+ if plan.conflicts:
126
+ print(f"Removal stopped: {len(plan.conflicts)} conflict(s).")
127
+ return 2
128
+ if args.dry_run:
129
+ print("Dry run complete; no files were removed.")
130
+ return 0
131
+ apply_plan(target, plan)
132
+ print(f"Removed CoDev from {target}")
133
+ return 0
134
+ except CoDevError as error:
135
+ print(f"codev: {error}", file=sys.stderr)
136
+ return 2
137
+ except OSError as error:
138
+ print(f"codev: filesystem error: {error}", file=sys.stderr)
139
+ return 2
140
+ return 2