@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.
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/adapters/codex/src/doctorCodexAdapter.d.ts +6 -0
- package/dist/adapters/codex/src/doctorCodexAdapter.js +124 -0
- package/dist/adapters/codex/src/doctorCodexAdapter.js.map +1 -0
- package/dist/adapters/codex/src/generateAgents.d.ts +2 -0
- package/dist/adapters/codex/src/generateAgents.js +40 -0
- package/dist/adapters/codex/src/generateAgents.js.map +1 -0
- package/dist/adapters/codex/src/generateCodexAdapter.d.ts +9 -0
- package/dist/adapters/codex/src/generateCodexAdapter.js +59 -0
- package/dist/adapters/codex/src/generateCodexAdapter.js.map +1 -0
- package/dist/adapters/codex/src/generateCommands.d.ts +6 -0
- package/dist/adapters/codex/src/generateCommands.js +205 -0
- package/dist/adapters/codex/src/generateCommands.js.map +1 -0
- package/dist/adapters/codex/src/generateSkills.d.ts +7 -0
- package/dist/adapters/codex/src/generateSkills.js +60 -0
- package/dist/adapters/codex/src/generateSkills.js.map +1 -0
- package/dist/adapters/codex/src/generatedFiles.d.ts +4 -0
- package/dist/adapters/codex/src/generatedFiles.js +67 -0
- package/dist/adapters/codex/src/generatedFiles.js.map +1 -0
- package/dist/adapters/codex/src/manifest.d.ts +4 -0
- package/dist/adapters/codex/src/manifest.js +40 -0
- package/dist/adapters/codex/src/manifest.js.map +1 -0
- package/dist/adapters/codex/src/templates.d.ts +7 -0
- package/dist/adapters/codex/src/templates.js +6 -0
- package/dist/adapters/codex/src/templates.js.map +1 -0
- package/dist/cli/src/args.d.ts +8 -0
- package/dist/cli/src/args.js +34 -0
- package/dist/cli/src/args.js.map +1 -0
- package/dist/cli/src/commands/doctor.d.ts +1 -0
- package/dist/cli/src/commands/doctor.js +26 -0
- package/dist/cli/src/commands/doctor.js.map +1 -0
- package/dist/cli/src/commands/init.d.ts +1 -0
- package/dist/cli/src/commands/init.js +52 -0
- package/dist/cli/src/commands/init.js.map +1 -0
- package/dist/cli/src/commands/shared.d.ts +4 -0
- package/dist/cli/src/commands/shared.js +19 -0
- package/dist/cli/src/commands/shared.js.map +1 -0
- package/dist/cli/src/commands/sync.d.ts +1 -0
- package/dist/cli/src/commands/sync.js +27 -0
- package/dist/cli/src/commands/sync.js.map +1 -0
- package/dist/cli/src/commands/validate.d.ts +1 -0
- package/dist/cli/src/commands/validate.js +17 -0
- package/dist/cli/src/commands/validate.js.map +1 -0
- package/dist/cli/src/dev/validateRepositoryContractsCli.d.ts +2 -0
- package/dist/cli/src/dev/validateRepositoryContractsCli.js +37 -0
- package/dist/cli/src/dev/validateRepositoryContractsCli.js.map +1 -0
- package/dist/cli/src/dev/verifyRuntimeSurface.d.ts +2 -0
- package/dist/cli/src/dev/verifyRuntimeSurface.js +344 -0
- package/dist/cli/src/dev/verifyRuntimeSurface.js.map +1 -0
- package/dist/cli/src/dev/verifyWorkflowE2E.d.ts +2 -0
- package/dist/cli/src/dev/verifyWorkflowE2E.js +366 -0
- package/dist/cli/src/dev/verifyWorkflowE2E.js.map +1 -0
- package/dist/cli/src/index.d.ts +2 -0
- package/dist/cli/src/index.js +51 -0
- package/dist/cli/src/index.js.map +1 -0
- package/dist/core/src/artifacts/registry.d.ts +53 -0
- package/dist/core/src/artifacts/registry.js +483 -0
- package/dist/core/src/artifacts/registry.js.map +1 -0
- package/dist/core/src/commands/registry.d.ts +36 -0
- package/dist/core/src/commands/registry.js +539 -0
- package/dist/core/src/commands/registry.js.map +1 -0
- package/dist/core/src/contracts/index.d.ts +23 -0
- package/dist/core/src/contracts/index.js +16 -0
- package/dist/core/src/contracts/index.js.map +1 -0
- package/dist/core/src/contracts/yaml.d.ts +2 -0
- package/dist/core/src/contracts/yaml.js +12 -0
- package/dist/core/src/contracts/yaml.js.map +1 -0
- package/dist/core/src/contracts.d.ts +23 -0
- package/dist/core/src/contracts.js +15 -0
- package/dist/core/src/contracts.js.map +1 -0
- package/dist/core/src/fs/index.d.ts +4 -0
- package/dist/core/src/fs/index.js +28 -0
- package/dist/core/src/fs/index.js.map +1 -0
- package/dist/core/src/fs.d.ts +4 -0
- package/dist/core/src/fs.js +28 -0
- package/dist/core/src/fs.js.map +1 -0
- package/dist/core/src/initOpenWorkflow.d.ts +7 -0
- package/dist/core/src/initOpenWorkflow.js +220 -0
- package/dist/core/src/initOpenWorkflow.js.map +1 -0
- package/dist/core/src/validateOpenWorkflow.d.ts +5 -0
- package/dist/core/src/validateOpenWorkflow.js +145 -0
- package/dist/core/src/validateOpenWorkflow.js.map +1 -0
- package/dist/core/src/validators/validateOpenWorkflow.d.ts +5 -0
- package/dist/core/src/validators/validateOpenWorkflow.js +551 -0
- package/dist/core/src/validators/validateOpenWorkflow.js.map +1 -0
- package/dist/core/src/validators/validateRepositoryContracts.d.ts +2 -0
- package/dist/core/src/validators/validateRepositoryContracts.js +827 -0
- package/dist/core/src/validators/validateRepositoryContracts.js.map +1 -0
- package/dist/core/src/workflow/initOpenWorkflow.d.ts +7 -0
- package/dist/core/src/workflow/initOpenWorkflow.js +182 -0
- package/dist/core/src/workflow/initOpenWorkflow.js.map +1 -0
- package/dist/core/src/yaml.d.ts +2 -0
- package/dist/core/src/yaml.js +12 -0
- package/dist/core/src/yaml.js.map +1 -0
- package/package.json +55 -0
- package/references/artifact-authoring-templates.md +78 -0
- package/references/audit-first-discovery-loop.md +85 -0
- package/references/contract-graph.md +129 -0
- package/references/discovery-artifact-contracts.md +155 -0
- package/references/engineering-skill-reference-research.md +204 -0
- package/references/npm-cli-architecture.md +63 -0
- package/references/runtime-command-surface.md +169 -0
- package/schemas/artifact-contracts.schema.json +130 -0
- package/schemas/change.schema.json +71 -0
- package/schemas/contract-graph.schema.json +80 -0
- package/schemas/decision-record.schema.json +92 -0
- package/schemas/disclosure-levels.schema.json +66 -0
- package/schemas/openworkflow-contract.schema.json +88 -0
- package/schemas/product-design.schema.json +356 -0
- package/schemas/prototype-evidence.schema.json +325 -0
- package/schemas/prototype.schema.json +149 -0
- package/schemas/validation-target.schema.json +127 -0
- package/schemas/validation.schema.json +123 -0
- package/schemas/vision-session.schema.json +78 -0
- package/schemas/work-items.schema.json +87 -0
- package/schemas/workflow-index.schema.json +70 -0
- package/skills/build-prototype/SKILL.md +87 -0
- package/skills/build-prototype/agents/openai.yaml +4 -0
- package/skills/build-prototype/references/prototype-protocol.md +56 -0
- package/skills/build-prototype/scripts/init_prototype.py +260 -0
- package/skills/build-team/SKILL.md +292 -0
- package/skills/build-team/agents/openai.yaml +4 -0
- package/skills/build-team/references/runtime-schema.md +275 -0
- package/skills/build-team/references/team-protocol.md +244 -0
- package/skills/build-team/scripts/init_team_runtime.py +431 -0
- package/skills/build-validation/SKILL.md +81 -0
- package/skills/build-validation/agents/openai.yaml +4 -0
- package/skills/build-validation/references/validation-protocol.md +51 -0
- package/skills/build-validation/scripts/init_validation.py +194 -0
- package/skills/build-workflow/SKILL.md +65 -0
- package/skills/build-workflow/agents/openai.yaml +4 -0
- package/skills/build-workflow/references/workflow-layout.md +57 -0
- package/skills/build-workflow/scripts/init_workflow.py +423 -0
- package/skills/run-team/SKILL.md +93 -0
- package/skills/run-team/agents/openai.yaml +4 -0
- package/skills/run-team/references/delegation-and-agent-lifecycle.md +78 -0
- package/skills/run-team/references/run-loop.md +73 -0
- package/skills/run-team/references/runtime-audit.md +56 -0
- package/skills/run-team/references/scope-selection.md +64 -0
- package/skills/run-team/scripts/audit_team_runtime.py +173 -0
- package/skills/run-team/scripts/init_next_scope.py +304 -0
- package/templates/README.md +5 -0
- package/templates/codex/README.md +4 -0
- 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())
|