@tiic-tech/openworkflow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/adapters/codex/src/doctorCodexAdapter.d.ts +6 -0
  4. package/dist/adapters/codex/src/doctorCodexAdapter.js +124 -0
  5. package/dist/adapters/codex/src/doctorCodexAdapter.js.map +1 -0
  6. package/dist/adapters/codex/src/generateAgents.d.ts +2 -0
  7. package/dist/adapters/codex/src/generateAgents.js +40 -0
  8. package/dist/adapters/codex/src/generateAgents.js.map +1 -0
  9. package/dist/adapters/codex/src/generateCodexAdapter.d.ts +9 -0
  10. package/dist/adapters/codex/src/generateCodexAdapter.js +59 -0
  11. package/dist/adapters/codex/src/generateCodexAdapter.js.map +1 -0
  12. package/dist/adapters/codex/src/generateCommands.d.ts +6 -0
  13. package/dist/adapters/codex/src/generateCommands.js +205 -0
  14. package/dist/adapters/codex/src/generateCommands.js.map +1 -0
  15. package/dist/adapters/codex/src/generateSkills.d.ts +7 -0
  16. package/dist/adapters/codex/src/generateSkills.js +60 -0
  17. package/dist/adapters/codex/src/generateSkills.js.map +1 -0
  18. package/dist/adapters/codex/src/generatedFiles.d.ts +4 -0
  19. package/dist/adapters/codex/src/generatedFiles.js +67 -0
  20. package/dist/adapters/codex/src/generatedFiles.js.map +1 -0
  21. package/dist/adapters/codex/src/manifest.d.ts +4 -0
  22. package/dist/adapters/codex/src/manifest.js +40 -0
  23. package/dist/adapters/codex/src/manifest.js.map +1 -0
  24. package/dist/adapters/codex/src/templates.d.ts +7 -0
  25. package/dist/adapters/codex/src/templates.js +6 -0
  26. package/dist/adapters/codex/src/templates.js.map +1 -0
  27. package/dist/cli/src/args.d.ts +8 -0
  28. package/dist/cli/src/args.js +34 -0
  29. package/dist/cli/src/args.js.map +1 -0
  30. package/dist/cli/src/commands/doctor.d.ts +1 -0
  31. package/dist/cli/src/commands/doctor.js +26 -0
  32. package/dist/cli/src/commands/doctor.js.map +1 -0
  33. package/dist/cli/src/commands/init.d.ts +1 -0
  34. package/dist/cli/src/commands/init.js +52 -0
  35. package/dist/cli/src/commands/init.js.map +1 -0
  36. package/dist/cli/src/commands/shared.d.ts +4 -0
  37. package/dist/cli/src/commands/shared.js +19 -0
  38. package/dist/cli/src/commands/shared.js.map +1 -0
  39. package/dist/cli/src/commands/sync.d.ts +1 -0
  40. package/dist/cli/src/commands/sync.js +27 -0
  41. package/dist/cli/src/commands/sync.js.map +1 -0
  42. package/dist/cli/src/commands/validate.d.ts +1 -0
  43. package/dist/cli/src/commands/validate.js +17 -0
  44. package/dist/cli/src/commands/validate.js.map +1 -0
  45. package/dist/cli/src/dev/validateRepositoryContractsCli.d.ts +2 -0
  46. package/dist/cli/src/dev/validateRepositoryContractsCli.js +37 -0
  47. package/dist/cli/src/dev/validateRepositoryContractsCli.js.map +1 -0
  48. package/dist/cli/src/dev/verifyRuntimeSurface.d.ts +2 -0
  49. package/dist/cli/src/dev/verifyRuntimeSurface.js +344 -0
  50. package/dist/cli/src/dev/verifyRuntimeSurface.js.map +1 -0
  51. package/dist/cli/src/dev/verifyWorkflowE2E.d.ts +2 -0
  52. package/dist/cli/src/dev/verifyWorkflowE2E.js +366 -0
  53. package/dist/cli/src/dev/verifyWorkflowE2E.js.map +1 -0
  54. package/dist/cli/src/index.d.ts +2 -0
  55. package/dist/cli/src/index.js +51 -0
  56. package/dist/cli/src/index.js.map +1 -0
  57. package/dist/core/src/artifacts/registry.d.ts +53 -0
  58. package/dist/core/src/artifacts/registry.js +483 -0
  59. package/dist/core/src/artifacts/registry.js.map +1 -0
  60. package/dist/core/src/commands/registry.d.ts +36 -0
  61. package/dist/core/src/commands/registry.js +539 -0
  62. package/dist/core/src/commands/registry.js.map +1 -0
  63. package/dist/core/src/contracts/index.d.ts +23 -0
  64. package/dist/core/src/contracts/index.js +16 -0
  65. package/dist/core/src/contracts/index.js.map +1 -0
  66. package/dist/core/src/contracts/yaml.d.ts +2 -0
  67. package/dist/core/src/contracts/yaml.js +12 -0
  68. package/dist/core/src/contracts/yaml.js.map +1 -0
  69. package/dist/core/src/contracts.d.ts +23 -0
  70. package/dist/core/src/contracts.js +15 -0
  71. package/dist/core/src/contracts.js.map +1 -0
  72. package/dist/core/src/fs/index.d.ts +4 -0
  73. package/dist/core/src/fs/index.js +28 -0
  74. package/dist/core/src/fs/index.js.map +1 -0
  75. package/dist/core/src/fs.d.ts +4 -0
  76. package/dist/core/src/fs.js +28 -0
  77. package/dist/core/src/fs.js.map +1 -0
  78. package/dist/core/src/initOpenWorkflow.d.ts +7 -0
  79. package/dist/core/src/initOpenWorkflow.js +220 -0
  80. package/dist/core/src/initOpenWorkflow.js.map +1 -0
  81. package/dist/core/src/validateOpenWorkflow.d.ts +5 -0
  82. package/dist/core/src/validateOpenWorkflow.js +145 -0
  83. package/dist/core/src/validateOpenWorkflow.js.map +1 -0
  84. package/dist/core/src/validators/validateOpenWorkflow.d.ts +5 -0
  85. package/dist/core/src/validators/validateOpenWorkflow.js +551 -0
  86. package/dist/core/src/validators/validateOpenWorkflow.js.map +1 -0
  87. package/dist/core/src/validators/validateRepositoryContracts.d.ts +2 -0
  88. package/dist/core/src/validators/validateRepositoryContracts.js +827 -0
  89. package/dist/core/src/validators/validateRepositoryContracts.js.map +1 -0
  90. package/dist/core/src/workflow/initOpenWorkflow.d.ts +7 -0
  91. package/dist/core/src/workflow/initOpenWorkflow.js +182 -0
  92. package/dist/core/src/workflow/initOpenWorkflow.js.map +1 -0
  93. package/dist/core/src/yaml.d.ts +2 -0
  94. package/dist/core/src/yaml.js +12 -0
  95. package/dist/core/src/yaml.js.map +1 -0
  96. package/package.json +55 -0
  97. package/references/artifact-authoring-templates.md +78 -0
  98. package/references/audit-first-discovery-loop.md +85 -0
  99. package/references/contract-graph.md +129 -0
  100. package/references/discovery-artifact-contracts.md +155 -0
  101. package/references/engineering-skill-reference-research.md +204 -0
  102. package/references/npm-cli-architecture.md +63 -0
  103. package/references/runtime-command-surface.md +169 -0
  104. package/schemas/artifact-contracts.schema.json +130 -0
  105. package/schemas/change.schema.json +71 -0
  106. package/schemas/contract-graph.schema.json +80 -0
  107. package/schemas/decision-record.schema.json +92 -0
  108. package/schemas/disclosure-levels.schema.json +66 -0
  109. package/schemas/openworkflow-contract.schema.json +88 -0
  110. package/schemas/product-design.schema.json +356 -0
  111. package/schemas/prototype-evidence.schema.json +325 -0
  112. package/schemas/prototype.schema.json +149 -0
  113. package/schemas/validation-target.schema.json +127 -0
  114. package/schemas/validation.schema.json +123 -0
  115. package/schemas/vision-session.schema.json +78 -0
  116. package/schemas/work-items.schema.json +87 -0
  117. package/schemas/workflow-index.schema.json +70 -0
  118. package/skills/build-prototype/SKILL.md +87 -0
  119. package/skills/build-prototype/agents/openai.yaml +4 -0
  120. package/skills/build-prototype/references/prototype-protocol.md +56 -0
  121. package/skills/build-prototype/scripts/init_prototype.py +260 -0
  122. package/skills/build-team/SKILL.md +292 -0
  123. package/skills/build-team/agents/openai.yaml +4 -0
  124. package/skills/build-team/references/runtime-schema.md +275 -0
  125. package/skills/build-team/references/team-protocol.md +244 -0
  126. package/skills/build-team/scripts/init_team_runtime.py +431 -0
  127. package/skills/build-validation/SKILL.md +81 -0
  128. package/skills/build-validation/agents/openai.yaml +4 -0
  129. package/skills/build-validation/references/validation-protocol.md +51 -0
  130. package/skills/build-validation/scripts/init_validation.py +194 -0
  131. package/skills/build-workflow/SKILL.md +65 -0
  132. package/skills/build-workflow/agents/openai.yaml +4 -0
  133. package/skills/build-workflow/references/workflow-layout.md +57 -0
  134. package/skills/build-workflow/scripts/init_workflow.py +423 -0
  135. package/skills/run-team/SKILL.md +93 -0
  136. package/skills/run-team/agents/openai.yaml +4 -0
  137. package/skills/run-team/references/delegation-and-agent-lifecycle.md +78 -0
  138. package/skills/run-team/references/run-loop.md +73 -0
  139. package/skills/run-team/references/runtime-audit.md +56 -0
  140. package/skills/run-team/references/scope-selection.md +64 -0
  141. package/skills/run-team/scripts/audit_team_runtime.py +173 -0
  142. package/skills/run-team/scripts/init_next_scope.py +304 -0
  143. package/templates/README.md +5 -0
  144. package/templates/codex/README.md +4 -0
  145. package/templates/openworkflow/README.md +4 -0
@@ -0,0 +1,56 @@
1
+ # Runtime Audit
2
+
3
+ Use this reference before planning or executing.
4
+
5
+ ## Required Checks
6
+
7
+ Run:
8
+
9
+ ```bash
10
+ python3 .codex/skills/run-team/scripts/audit_team_runtime.py --root . --format markdown
11
+ git status --short
12
+ git branch --show-current
13
+ git log --oneline -5
14
+ ```
15
+
16
+ Inspect:
17
+
18
+ - `AGENT.md`
19
+ - `.codex/agents/README.md`
20
+ - `.codex/agents/orchestrator.md`
21
+ - `.codex/runtime/STATE_MACHINE.md`
22
+ - `.codex/runtime/RUNTIME_INDEX.yaml`
23
+ - `.codex/runtime/scopes/<scope_id>/SCOPE.yaml`
24
+ - `.codex/runtime/scopes/<scope_id>/MILESTONES.yaml`
25
+ - `.codex/runtime/scopes/<scope_id>/IMPLEMENT_INDEX.yaml`
26
+ - `.codex/runtime/scopes/<scope_id>/IMPLEMENT_ISSUE_INDEX.yaml`
27
+ - `.codex/runtime/scopes/<scope_id>/AGENT_ROSTER.yaml`
28
+ - active milestone `IMPLEMENT_TASKS.yaml`
29
+ - active milestone `IMPLEMENT_ISSUES.yaml`
30
+
31
+ ## What To Detect
32
+
33
+ - dirty unrelated files
34
+ - active scope and milestone
35
+ - completed or frozen scope
36
+ - missing roster
37
+ - stale `agent_id: null` in active tasks
38
+ - unresolved issues
39
+ - blocked tasks
40
+ - missing prompt/review/archive directories
41
+ - mismatch between runtime state and source files
42
+ - completed milestones without QA report or checkpoint
43
+
44
+ ## Archive Use
45
+
46
+ Read `archive/` to understand previous decisions or frozen snapshots. Do not revive archived work without creating a new task or scope that explains why.
47
+
48
+ ## Reporting
49
+
50
+ Summarize:
51
+
52
+ - current scope/milestone
53
+ - run mode recommendation
54
+ - blockers
55
+ - next likely action
56
+ - files that must be read next
@@ -0,0 +1,64 @@
1
+ # Scope Selection
2
+
3
+ Use this reference when `/ow:team CONTENT` may need a new scope.
4
+
5
+ ## Continue Current Scope When
6
+
7
+ - active scope status is active or blocked but still matches `CONTENT`
8
+ - active milestone has remaining planned, prompted, in-progress, fix-required, or QA-ready tasks
9
+ - unresolved issues must be fixed before moving forward
10
+ - user asks to continue the same phase
11
+
12
+ ## Create New Scope When
13
+
14
+ - MVP or current phase is completed and user asks for the next phase
15
+ - `CONTENT` introduces a distinct delivery target or product stage
16
+ - current scope was frozen and should not be modified
17
+ - work is large enough to need its own milestone plan
18
+ - acceptance criteria differ materially from the current scope
19
+
20
+ ## Scope Id Guidance
21
+
22
+ Use short uppercase ids:
23
+
24
+ - `V1` for first post-MVP iteration
25
+ - `POST_MVP` for broad next-stage work
26
+ - `LAUNCH` for release and production readiness
27
+ - `CONTENT` for content expansion
28
+ - `PERF` for performance work
29
+ - `SECURITY` for security hardening
30
+ - `MIGRATION` for structural migration
31
+
32
+ ## Milestone Requirements
33
+
34
+ Every milestone needs:
35
+
36
+ - `milestone_id`
37
+ - `title`
38
+ - `status`
39
+ - `scope`
40
+ - `target`
41
+ - `dependencies`
42
+ - `required_specs`
43
+ - `expected_artifacts`
44
+ - `estimated_atom_tasks`
45
+ - `task_file`
46
+ - `issue_file`
47
+ - `qa_gate`
48
+ - `acceptance`
49
+
50
+ Keep contracts before broad implementation. For product work, prefer:
51
+
52
+ ```txt
53
+ requirements/contracts -> architecture/data model -> implementation -> QA/hardening
54
+ ```
55
+
56
+ ## One-Question Rule
57
+
58
+ Ask one question only if needed:
59
+
60
+ ```txt
61
+ I infer the next scope is <scope_id>: <goal>. What is the delivery target, first milestone done criteria, and any required or forbidden roles/tools for this run?
62
+ ```
63
+
64
+ If the user answer is incomplete, proceed with explicit assumptions.
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ """Audit a repo-local Agent Team runtime before /ow:team execution."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ import subprocess
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+
14
+ def run_git(root: Path, args: list[str]) -> str:
15
+ try:
16
+ result = subprocess.run(
17
+ ["git", *args],
18
+ cwd=root,
19
+ check=True,
20
+ text=True,
21
+ stdout=subprocess.PIPE,
22
+ stderr=subprocess.DEVNULL,
23
+ )
24
+ except (OSError, subprocess.CalledProcessError):
25
+ return ""
26
+ return result.stdout.strip()
27
+
28
+
29
+ def read_text(path: Path) -> str:
30
+ try:
31
+ return path.read_text(encoding="utf-8")
32
+ except OSError:
33
+ return ""
34
+
35
+
36
+ def first_match(text: str, pattern: str) -> str | None:
37
+ match = re.search(pattern, text, flags=re.MULTILINE)
38
+ return match.group(1).strip() if match else None
39
+
40
+
41
+ def normalize_optional_scalar(value: str | None) -> str | None:
42
+ if value is None:
43
+ return None
44
+ cleaned = value.strip().strip("\"'")
45
+ if cleaned.lower() in {"", "null", "none", "~"}:
46
+ return None
47
+ return cleaned
48
+
49
+
50
+ def count_values(text: str, key: str) -> dict[str, int]:
51
+ counts: dict[str, int] = {}
52
+ for value in re.findall(rf"^\s*{re.escape(key)}:\s*([A-Za-z0-9_-]+)\s*$", text, flags=re.MULTILINE):
53
+ counts[value] = counts.get(value, 0) + 1
54
+ return counts
55
+
56
+
57
+ def list_files(path: Path, pattern: str) -> list[str]:
58
+ if not path.exists():
59
+ return []
60
+ return [str(p) for p in sorted(path.glob(pattern))]
61
+
62
+
63
+ def audit(root: Path) -> dict[str, Any]:
64
+ runtime = root / ".codex" / "runtime"
65
+ runtime_index = runtime / "RUNTIME_INDEX.yaml"
66
+ runtime_text = read_text(runtime_index)
67
+ active_scope = normalize_optional_scalar(first_match(runtime_text, r"^active_scope:\s*(.+?)\s*$"))
68
+ scope_root = runtime / "scopes" / active_scope if active_scope else None
69
+ implement_index = scope_root / "IMPLEMENT_INDEX.yaml" if scope_root else None
70
+ implement_text = read_text(implement_index) if implement_index else ""
71
+ active_milestone = first_match(implement_text, r"^active_milestone:\s*([A-Za-z0-9_-]+)\s*$")
72
+
73
+ scope_dirs = [p.name for p in sorted((runtime / "scopes").iterdir()) if p.is_dir()] if (runtime / "scopes").exists() else []
74
+ milestone_root = scope_root / "milestones" if scope_root else None
75
+ milestone_dirs = [p.name for p in sorted(milestone_root.iterdir()) if p.is_dir()] if milestone_root and milestone_root.exists() else []
76
+
77
+ task_file = milestone_root / active_milestone / "IMPLEMENT_TASKS.yaml" if milestone_root and active_milestone else None
78
+ issue_file = milestone_root / active_milestone / "IMPLEMENT_ISSUES.yaml" if milestone_root and active_milestone else None
79
+ task_text = read_text(task_file) if task_file else ""
80
+ issue_text = read_text(issue_file) if issue_file else ""
81
+
82
+ archive_dirs_missing: list[str] = []
83
+ for path in [runtime, scope_root, *(milestone_root / m for m in milestone_dirs)] if scope_root and milestone_root else [runtime]:
84
+ if path and path.exists() and not (path / "archive").is_dir():
85
+ archive_dirs_missing.append(str(path / "archive"))
86
+
87
+ roster = scope_root / "AGENT_ROSTER.yaml" if scope_root else None
88
+ roster_text = read_text(roster) if roster else ""
89
+ active_agent_ids = re.findall(r"^\s*agent_id:\s*(?!null$)([A-Za-z0-9_.:-]+)\s*$", roster_text, flags=re.MULTILINE)
90
+
91
+ return {
92
+ "root": str(root),
93
+ "git": {
94
+ "branch": run_git(root, ["branch", "--show-current"]),
95
+ "head": run_git(root, ["log", "--oneline", "-1"]),
96
+ "status_short": run_git(root, ["status", "--short"]).splitlines(),
97
+ },
98
+ "runtime": {
99
+ "exists": runtime.exists(),
100
+ "active_scope": active_scope,
101
+ "scopes": scope_dirs,
102
+ "active_milestone": active_milestone,
103
+ "milestones": milestone_dirs,
104
+ "runtime_index": str(runtime_index),
105
+ "scope_root": str(scope_root) if scope_root else None,
106
+ "agent_roster": str(roster) if roster else None,
107
+ "active_agent_ids": active_agent_ids,
108
+ "task_file": str(task_file) if task_file else None,
109
+ "task_status_counts": count_values(task_text, "status"),
110
+ "task_agent_null_count": len(re.findall(r"^\s*agent_id:\s*null\s*$", task_text, flags=re.MULTILINE)),
111
+ "issue_file": str(issue_file) if issue_file else None,
112
+ "issue_status_counts": count_values(issue_text, "status"),
113
+ "archive_dirs_missing": archive_dirs_missing,
114
+ },
115
+ "source_files": {
116
+ "agent_guide": (root / "AGENT.md").exists(),
117
+ "agent_protocol": (root / ".codex" / "agents" / "README.md").exists(),
118
+ "orchestrator": (root / ".codex" / "agents" / "orchestrator.md").exists(),
119
+ "design_spec": (root / "DESIGN_SPEC").is_dir(),
120
+ "launch_checklist": (root / "LAUNCH_CHECKLIST.md").exists(),
121
+ },
122
+ }
123
+
124
+
125
+ def to_markdown(data: dict[str, Any]) -> str:
126
+ git = data["git"]
127
+ runtime = data["runtime"]
128
+ lines = [
129
+ "# Agent Team Runtime Audit",
130
+ "",
131
+ f"- Root: `{data['root']}`",
132
+ f"- Branch: `{git['branch'] or 'unknown'}`",
133
+ f"- HEAD: `{git['head'] or 'unknown'}`",
134
+ f"- Dirty entries: {len(git['status_short'])}",
135
+ f"- Runtime exists: {runtime['exists']}",
136
+ f"- Active scope: `{runtime['active_scope'] or 'none'}`",
137
+ f"- Active milestone: `{runtime['active_milestone'] or 'none'}`",
138
+ f"- Scopes: {', '.join(runtime['scopes']) or 'none'}",
139
+ f"- Milestones: {', '.join(runtime['milestones']) or 'none'}",
140
+ f"- Task statuses: `{runtime['task_status_counts']}`",
141
+ f"- Active task `agent_id: null` count: {runtime['task_agent_null_count']}",
142
+ f"- Issue statuses: `{runtime['issue_status_counts']}`",
143
+ f"- Roster ids tracked: {len(runtime['active_agent_ids'])}",
144
+ f"- Missing archive dirs: {len(runtime['archive_dirs_missing'])}",
145
+ "",
146
+ ]
147
+ if git["status_short"]:
148
+ lines.append("## Dirty Worktree")
149
+ lines.extend(f"- `{entry}`" for entry in git["status_short"])
150
+ lines.append("")
151
+ if runtime["archive_dirs_missing"]:
152
+ lines.append("## Missing Archive Dirs")
153
+ lines.extend(f"- `{entry}`" for entry in runtime["archive_dirs_missing"])
154
+ lines.append("")
155
+ return "\n".join(lines)
156
+
157
+
158
+ def main() -> int:
159
+ parser = argparse.ArgumentParser(description="Audit Agent Team runtime state.")
160
+ parser.add_argument("--root", default=".", help="Repository root.")
161
+ parser.add_argument("--format", choices=["json", "markdown"], default="json")
162
+ args = parser.parse_args()
163
+
164
+ data = audit(Path(args.root).expanduser().resolve())
165
+ if args.format == "markdown":
166
+ print(to_markdown(data))
167
+ else:
168
+ print(json.dumps(data, indent=2, sort_keys=True))
169
+ return 0
170
+
171
+
172
+ if __name__ == "__main__":
173
+ raise SystemExit(main())
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env python3
2
+ """Initialize a next Agent Team scope for /ow:team execution."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ import subprocess
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class Milestone:
16
+ milestone_id: str
17
+ title: str
18
+
19
+
20
+ def q(value: str | None) -> str:
21
+ return "null" if value is None else json.dumps(value)
22
+
23
+
24
+ def slugify(value: str) -> str:
25
+ return re.sub(r"[^A-Za-z0-9]+", "-", value.strip().lower()).strip("-") or "milestone"
26
+
27
+
28
+ def git_ref(root: Path) -> str | None:
29
+ try:
30
+ result = subprocess.run(
31
+ ["git", "rev-parse", "--short", "HEAD"],
32
+ cwd=root,
33
+ check=True,
34
+ text=True,
35
+ stdout=subprocess.PIPE,
36
+ stderr=subprocess.DEVNULL,
37
+ )
38
+ except (OSError, subprocess.CalledProcessError):
39
+ return None
40
+ return result.stdout.strip() or None
41
+
42
+
43
+ def parse_milestone(raw: str) -> Milestone:
44
+ for sep in (":", "=", "|"):
45
+ if sep in raw:
46
+ left, right = raw.split(sep, 1)
47
+ return Milestone(left.strip().upper(), right.strip())
48
+ match = re.match(r"^(M\d+)\s+(.+)$", raw.strip(), flags=re.IGNORECASE)
49
+ if match:
50
+ return Milestone(match.group(1).upper(), match.group(2).strip())
51
+ raise ValueError(f"milestone must look like M01:Title, got {raw!r}")
52
+
53
+
54
+ def write_file(path: Path, content: str, force: bool) -> None:
55
+ if path.exists() and not force:
56
+ print(f"SKIP {path}")
57
+ return
58
+ path.parent.mkdir(parents=True, exist_ok=True)
59
+ path.write_text(content, encoding="utf-8")
60
+ print(f"WRITE {path}")
61
+
62
+
63
+ def touch(path: Path) -> None:
64
+ path.parent.mkdir(parents=True, exist_ok=True)
65
+ if not path.exists():
66
+ path.write_text("", encoding="utf-8")
67
+ print(f"WRITE {path}")
68
+
69
+
70
+ def yaml_list(items: list[str], indent: int) -> str:
71
+ prefix = " " * indent
72
+ if not items:
73
+ return f"{prefix}[]\n"
74
+ return "".join(f"{prefix}- {q(item)}\n" for item in items)
75
+
76
+
77
+ def scope_yaml(scope_id: str, title: str, sources: list[str], app_roots: list[str], ref: str | None) -> str:
78
+ return (
79
+ f"scope_id: {scope_id}\n"
80
+ f"title: {q(title)}\n"
81
+ "status: active\n"
82
+ "source_artifacts:\n"
83
+ f"{yaml_list(sources, 2)}"
84
+ f"base_git_ref: {q(ref)}\n"
85
+ "runtime_protocol:\n"
86
+ " agent_team_protocol: .codex/agents/README.md\n"
87
+ " orchestrator_role: .codex/agents/orchestrator.md\n"
88
+ f" agent_roster: .codex/runtime/scopes/{scope_id}/AGENT_ROSTER.yaml\n"
89
+ "boundary:\n"
90
+ " application_roots:\n"
91
+ f"{yaml_list(app_roots, 4)}"
92
+ " protected_roots:\n"
93
+ " - .git/\n"
94
+ " - .codex/runtime/\n"
95
+ )
96
+
97
+
98
+ def milestones_yaml(scope_id: str, sources: list[str], milestones: list[Milestone]) -> str:
99
+ lines = [f"scope_id: {scope_id}", "source_artifacts:"]
100
+ lines.extend(f" - {q(source)}" for source in sources)
101
+ lines.append("milestones:")
102
+ for index, milestone in enumerate(milestones):
103
+ status = "active" if index == 0 else "planned"
104
+ lines.extend(
105
+ [
106
+ f" - milestone_id: {milestone.milestone_id}",
107
+ f" title: {q(milestone.title)}",
108
+ f" status: {status}",
109
+ f" scope: {q('Deliver ' + milestone.title + '.')}",
110
+ " target: Complete expected artifacts and pass QA gate.",
111
+ " dependencies: []",
112
+ " required_specs:",
113
+ ]
114
+ )
115
+ lines.extend(f" - {q(source)}" for source in sources)
116
+ lines.extend(
117
+ [
118
+ " expected_artifacts: []",
119
+ " estimated_atom_tasks: 3-8",
120
+ f" task_file: .codex/runtime/scopes/{scope_id}/milestones/{milestone.milestone_id}/IMPLEMENT_TASKS.yaml",
121
+ f" issue_file: .codex/runtime/scopes/{scope_id}/milestones/{milestone.milestone_id}/IMPLEMENT_ISSUES.yaml",
122
+ " qa_gate:",
123
+ " - required checks pass or skipped checks are documented",
124
+ " acceptance:",
125
+ " - runtime state matches implementation reality",
126
+ ]
127
+ )
128
+ return "\n".join(lines) + "\n"
129
+
130
+
131
+ def implement_index(scope_id: str, milestones: list[Milestone]) -> str:
132
+ active = milestones[0].milestone_id
133
+ lines = [f"scope_id: {scope_id}", f"active_milestone: {active}", "milestones:"]
134
+ for index, milestone in enumerate(milestones):
135
+ status = "active" if index == 0 else "planned"
136
+ lines.extend(
137
+ [
138
+ f" - milestone_id: {milestone.milestone_id}",
139
+ f" status: {status}",
140
+ f" title: {q(milestone.title)}",
141
+ f" task_file: .codex/runtime/scopes/{scope_id}/milestones/{milestone.milestone_id}/IMPLEMENT_TASKS.yaml",
142
+ f" issue_file: .codex/runtime/scopes/{scope_id}/milestones/{milestone.milestone_id}/IMPLEMENT_ISSUES.yaml",
143
+ " qa_report: null",
144
+ f" branch: feat/{milestone.milestone_id.lower()}-{slugify(milestone.title)}",
145
+ " last_checkpoint: null",
146
+ ]
147
+ )
148
+ return "\n".join(lines) + "\n"
149
+
150
+
151
+ def agent_roster(scope_id: str, app_roots: list[str]) -> str:
152
+ impl_owns = ["frontend/src/app/", "frontend/src/components/", "frontend/src/systems/", "frontend/tests/"]
153
+ if not any(root.startswith("frontend") for root in app_roots):
154
+ impl_owns = app_roots or ["src/", "tests/"]
155
+ lines = [
156
+ f"scope_id: {scope_id}",
157
+ "roster_version: 1",
158
+ "updated_at: null",
159
+ "lifecycle_status_values:",
160
+ " - available",
161
+ " - active",
162
+ " - idle",
163
+ " - blocked",
164
+ " - closed",
165
+ " - archived",
166
+ " - legacy_untracked",
167
+ "session_policies:",
168
+ " persistent:",
169
+ " purpose: Keep domain agents mounted across related atom tasks and issue-fix loops.",
170
+ " reuse_rule: Resume the existing matching agent_id before spawning a replacement.",
171
+ " event:",
172
+ " purpose: Run async or one-off review, security, QA, and git drafting work.",
173
+ " reuse_rule: Close after handoff unless the Orchestrator records a reason to keep it idle.",
174
+ "persistent_agents:",
175
+ " - agent_name: tech-prompt-agent",
176
+ " agent_id: null",
177
+ " lifecycle_status: available",
178
+ " session_policy: persistent",
179
+ " owns:",
180
+ f" - .codex/runtime/scopes/{scope_id}/milestones/*/prompts/",
181
+ " current_task: null",
182
+ " last_completed_task: null",
183
+ " active_milestone: null",
184
+ " notes: Spawn once for prompt planning, then resume for related prompts.",
185
+ " - agent_name: implementation-agent",
186
+ " agent_id: null",
187
+ " lifecycle_status: available",
188
+ " session_policy: persistent",
189
+ " owns:",
190
+ ]
191
+ lines.extend(f" - {path}" for path in impl_owns)
192
+ lines.extend(
193
+ [
194
+ " current_task: null",
195
+ " last_completed_task: null",
196
+ " active_milestone: null",
197
+ " notes: Rename to the repo domain agent before assignment, such as frontend-agent, backend-agent, or content-schema-agent.",
198
+ "event_agents:",
199
+ " - agent_name: code-review-agent",
200
+ " agent_id: null",
201
+ " lifecycle_status: available",
202
+ " session_policy: event",
203
+ " trigger: artifact_ready",
204
+ " closes_after_handoff: true",
205
+ " - agent_name: tdd-qa-agent",
206
+ " agent_id: null",
207
+ " lifecycle_status: available",
208
+ " session_policy: event",
209
+ " trigger: qa_gate",
210
+ " closes_after_handoff: true",
211
+ " - agent_name: security-review-agent",
212
+ " agent_id: null",
213
+ " lifecycle_status: available",
214
+ " session_policy: event",
215
+ " trigger: security_sensitive_change",
216
+ " closes_after_handoff: true",
217
+ " - agent_name: git-release-agent",
218
+ " agent_id: null",
219
+ " lifecycle_status: available",
220
+ " session_policy: event",
221
+ " trigger: checkpoint_or_release",
222
+ " closes_after_handoff: true",
223
+ "legacy_tracking:",
224
+ " task_agent_ids_before_roster: legacy_untracked",
225
+ " note: Do not invent ids for historical null task agent_id values.",
226
+ ]
227
+ )
228
+ return "\n".join(lines) + "\n"
229
+
230
+
231
+ def update_runtime_index(runtime_index: Path, scope_id: str, title: str, source: str, ref: str | None, activate: bool) -> None:
232
+ block = (
233
+ f" - scope_id: {scope_id}\n"
234
+ f" title: {q(title)}\n"
235
+ " status: active\n"
236
+ f" source: {q(source)}\n"
237
+ f" path: .codex/runtime/scopes/{scope_id}/\n"
238
+ f" base_git_ref: {q(ref)}\n"
239
+ )
240
+ if not runtime_index.exists():
241
+ active = scope_id if activate else "null"
242
+ write_file(runtime_index, f"active_scope: {active}\nscopes:\n{block}", False)
243
+ return
244
+ text = runtime_index.read_text(encoding="utf-8")
245
+ if activate:
246
+ text = re.sub(r"^active_scope:\s*.*$", f"active_scope: {scope_id}", text, flags=re.MULTILINE)
247
+ if not re.search(rf"^\s*-\s*scope_id:\s*{re.escape(scope_id)}\s*$", text, flags=re.MULTILINE):
248
+ if re.search(r"^scopes:\s*\[\]\s*$", text, flags=re.MULTILINE):
249
+ text = re.sub(r"^scopes:\s*\[\]\s*$", f"scopes:\n{block.rstrip()}", text, flags=re.MULTILINE)
250
+ else:
251
+ if not text.endswith("\n"):
252
+ text += "\n"
253
+ text += block
254
+ runtime_index.write_text(text, encoding="utf-8")
255
+ print(f"WRITE {runtime_index}")
256
+
257
+
258
+ def main() -> int:
259
+ parser = argparse.ArgumentParser(description="Initialize a next Agent Team scope.")
260
+ parser.add_argument("--root", default=".", help="Repository root.")
261
+ parser.add_argument("--scope-id", required=True, help="New scope id, for example V1 or POST_MVP.")
262
+ parser.add_argument("--scope-title", required=True, help="Human-readable scope title.")
263
+ parser.add_argument("--source-artifact", action="append", default=[], help="Source-of-truth artifact. Repeatable.")
264
+ parser.add_argument("--application-root", action="append", default=[], help="Application root. Repeatable.")
265
+ parser.add_argument("--milestone", action="append", required=True, help="Milestone as M01:Title. Repeatable.")
266
+ parser.add_argument("--activate", action="store_true", help="Set active_scope to this scope in RUNTIME_INDEX.yaml.")
267
+ parser.add_argument("--force", action="store_true", help="Overwrite scope-local files if they exist.")
268
+ args = parser.parse_args()
269
+
270
+ root = Path(args.root).expanduser().resolve()
271
+ scope_id = args.scope_id.upper()
272
+ sources = args.source_artifact or ["AGENT.md"]
273
+ app_roots = args.application_root or ["frontend/"]
274
+ milestones = [parse_milestone(raw) for raw in args.milestone]
275
+ ref = git_ref(root)
276
+
277
+ runtime = root / ".codex" / "runtime"
278
+ scope_root = runtime / "scopes" / scope_id
279
+ for directory in [runtime, runtime / "archive", runtime / "scopes", scope_root, scope_root / "archive", scope_root / "milestones"]:
280
+ directory.mkdir(parents=True, exist_ok=True)
281
+ touch(runtime / "archive" / ".gitkeep")
282
+ touch(scope_root / "archive" / ".gitkeep")
283
+
284
+ update_runtime_index(runtime / "RUNTIME_INDEX.yaml", scope_id, args.scope_title, sources[0], ref, args.activate)
285
+ write_file(scope_root / "SCOPE.yaml", scope_yaml(scope_id, args.scope_title, sources, app_roots, ref), args.force)
286
+ write_file(scope_root / "MILESTONES.yaml", milestones_yaml(scope_id, sources, milestones), args.force)
287
+ write_file(scope_root / "IMPLEMENT_INDEX.yaml", implement_index(scope_id, milestones), args.force)
288
+ write_file(scope_root / "IMPLEMENT_ISSUE_INDEX.yaml", f"scope_id: {scope_id}\nissues: []\n", args.force)
289
+ write_file(scope_root / "AGENT_ROSTER.yaml", agent_roster(scope_id, app_roots), args.force)
290
+
291
+ for milestone in milestones:
292
+ milestone_root = scope_root / "milestones" / milestone.milestone_id
293
+ for directory in [milestone_root, milestone_root / "prompts", milestone_root / "reviews", milestone_root / "archive"]:
294
+ directory.mkdir(parents=True, exist_ok=True)
295
+ touch(milestone_root / "prompts" / ".gitkeep")
296
+ touch(milestone_root / "reviews" / ".gitkeep")
297
+ touch(milestone_root / "archive" / ".gitkeep")
298
+ write_file(milestone_root / "IMPLEMENT_TASKS.yaml", "tasks: []\n", args.force)
299
+ write_file(milestone_root / "IMPLEMENT_ISSUES.yaml", "issues: []\n", args.force)
300
+ return 0
301
+
302
+
303
+ if __name__ == "__main__":
304
+ raise SystemExit(main())
@@ -0,0 +1,5 @@
1
+ # Templates
2
+
3
+ OpenWorkflow keeps runtime template strings in TypeScript during M06 so package
4
+ loading stays unchanged. This directory defines the future external template
5
+ boundary for M07 and later.
@@ -0,0 +1,4 @@
1
+ # Codex Templates
2
+
3
+ Future home for externalized Codex adapter templates. M06 keeps the active
4
+ template registry in `packages/adapters/codex/src/`.
@@ -0,0 +1,4 @@
1
+ # OpenWorkflow Templates
2
+
3
+ Future home for platform-independent `.openworkflow/` contract templates.
4
+ M06 only establishes the directory boundary.