awf 1.0.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 (69) hide show
  1. awf/__init__.py +3 -0
  2. awf/__main__.py +6 -0
  3. awf/_atomic.py +37 -0
  4. awf/_env.py +90 -0
  5. awf/_log.py +24 -0
  6. awf/agent_stage.py +275 -0
  7. awf/api/__init__.py +128 -0
  8. awf/api/_background.py +158 -0
  9. awf/api/_errors.py +10 -0
  10. awf/api/_helpers.py +71 -0
  11. awf/api/_results.py +286 -0
  12. awf/api/_stack.py +130 -0
  13. awf/api/_templates.py +110 -0
  14. awf/api/context.py +420 -0
  15. awf/api/dashboard.py +909 -0
  16. awf/api/dashboard_server.py +76 -0
  17. awf/api/dispatch.py +182 -0
  18. awf/api/lifecycle.py +683 -0
  19. awf/api/model_check.py +246 -0
  20. awf/api/pipeline.py +764 -0
  21. awf/api/planning.py +95 -0
  22. awf/api/roles.py +448 -0
  23. awf/api/setup.py +453 -0
  24. awf/api/wait_event.py +186 -0
  25. awf/cli.py +232 -0
  26. awf/cmd_add_role.py +42 -0
  27. awf/cmd_analyze_roles.py +82 -0
  28. awf/cmd_approve.py +24 -0
  29. awf/cmd_baseline.py +40 -0
  30. awf/cmd_init.py +218 -0
  31. awf/cmd_report.py +56 -0
  32. awf/cmd_reset.py +78 -0
  33. awf/cmd_rollback.py +51 -0
  34. awf/cmd_start.py +70 -0
  35. awf/cmd_status.py +71 -0
  36. awf/commit_gate.py +272 -0
  37. awf/config.py +36 -0
  38. awf/git_utils.py +81 -0
  39. awf/opencode_agents.py +159 -0
  40. awf/orchestrator.py +189 -0
  41. awf/paths.py +73 -0
  42. awf/phase.py +170 -0
  43. awf/pipeline.py +153 -0
  44. awf/pipeline_engine.py +583 -0
  45. awf/pipeline_state.py +135 -0
  46. awf/plan_checkpoint.py +605 -0
  47. awf/plan_progress.py +172 -0
  48. awf/signal_watch.py +203 -0
  49. awf/signals.py +152 -0
  50. awf/supervisor.py +907 -0
  51. awf/templates/dashboard.html.j2 +420 -0
  52. awf/templates/roles/supervisor/_core.md +44 -0
  53. awf/templates/roles/supervisor/phase-brief.md +61 -0
  54. awf/templates/roles/supervisor/phase-form.md +29 -0
  55. awf/templates/roles/supervisor/phase-goal.md +25 -0
  56. awf/templates/roles/supervisor/phase-init.md +17 -0
  57. awf/templates/roles/supervisor/phase-normalize.md +35 -0
  58. awf/templates/roles/supervisor/phase-run.md +17 -0
  59. awf/templates/roles/supervisor/phase-verify.md +28 -0
  60. awf/templates/roles/supervisor.md +633 -0
  61. awf/todos.py +161 -0
  62. awf/transitions.py +54 -0
  63. awf/verify.py +202 -0
  64. awf/xdg.py +43 -0
  65. awf-1.0.0.dist-info/METADATA +189 -0
  66. awf-1.0.0.dist-info/RECORD +69 -0
  67. awf-1.0.0.dist-info/WHEEL +5 -0
  68. awf-1.0.0.dist-info/licenses/LICENSE +21 -0
  69. awf-1.0.0.dist-info/top_level.txt +1 -0
awf/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """awf — Agentic Workflow Framework Python core."""
2
+
3
+ __version__ = "0.4.0"
awf/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for ``python3 -m awf``."""
2
+ import sys
3
+
4
+ from .cli import main
5
+
6
+ sys.exit(main())
awf/_atomic.py ADDED
@@ -0,0 +1,37 @@
1
+ """Atomic file write helpers for awf-core.
2
+
3
+ Centralized so all modules use the same temp+rename pattern.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+
12
+ def atomic_write_text(path: Path, content: str, encoding: str = "utf-8") -> None:
13
+ """Write text file atomically via temp + rename.
14
+
15
+ Creates parent dir if missing. Uses tempfile in same dir to guarantee
16
+ same-filesystem rename (atomic on POSIX).
17
+
18
+ H5 fix: plan_progress.py was direct write_text — crash mid-write
19
+ corrupted plan.md (the only persistent progress artifact).
20
+ """
21
+ path.parent.mkdir(parents=True, exist_ok=True)
22
+ fd, tmp_path = tempfile.mkstemp(
23
+ prefix=f".{path.name}.",
24
+ suffix=".tmp",
25
+ dir=str(path.parent),
26
+ )
27
+ try:
28
+ with os.fdopen(fd, "w", encoding=encoding) as f:
29
+ f.write(content)
30
+ os.replace(tmp_path, path)
31
+ except Exception:
32
+ # Clean up temp file on failure
33
+ try:
34
+ os.unlink(tmp_path)
35
+ except OSError:
36
+ pass
37
+ raise
awf/_env.py ADDED
@@ -0,0 +1,90 @@
1
+ """BD-22/25: subprocess environment setup for opencode run.
2
+
3
+ Centralized so signal_watch + supervisor + agent_stage can use it without
4
+ circular imports through orchestrator.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import os
10
+ import sys
11
+
12
+
13
+ def _pdeathsig_preexec() -> None:
14
+ """DF6-8: Set PR_SET_PDEATHSIG so child dies when parent (orchestrator) dies.
15
+
16
+ Linux-only. On other platforms, no-op (best effort).
17
+ Prevents orphan worker subprocesses from continuing after orchestrator crash.
18
+ """
19
+ if sys.platform != "linux":
20
+ return
21
+ try:
22
+ import ctypes
23
+ import signal as _signal
24
+
25
+ libc = ctypes.CDLL("libc.so.6", use_errno=True)
26
+ PR_SET_PDEATHSIG = 1
27
+ libc.prctl(PR_SET_PDEATHSIG, _signal.SIGTERM)
28
+ except Exception:
29
+ pass # best effort — don't crash if libc/prctl unavailable
30
+
31
+
32
+ def awf_subprocess_env() -> dict[str, str]:
33
+ """BD-22/KAUD-5: env for opencode subprocess spawned by awf.
34
+
35
+ Sets ``OPENCODE_CONFIG_CONTENT`` to override permission rules so
36
+ the subprocess can run ``edit``/``bash``/``write`` without prompting.
37
+
38
+ KAUD-5: MERGES with user's existing opencode.json instead of replacing.
39
+ Reads user's config, adds our permission overrides on top, preserves
40
+ user's other settings (theme, providers, agents, etc.).
41
+
42
+ BD-25: strip ``OPENCODE_SERVER_*`` env vars so the subprocess does NOT
43
+ attach to a running ``opencode serve`` instance.
44
+ """
45
+ env = os.environ.copy()
46
+
47
+ # KAUD-5: Start from user's existing config, then merge our overrides
48
+ user_config: dict = {}
49
+ try:
50
+ from .xdg import opencode_config_file
51
+ config_path = opencode_config_file()
52
+ if config_path.is_file():
53
+ user_config = json.loads(config_path.read_text(encoding="utf-8"))
54
+ if not isinstance(user_config, dict):
55
+ user_config = {}
56
+ except (OSError, json.JSONDecodeError):
57
+ pass # If user config is unreadable, start from empty
58
+
59
+ # Merge: user config + our permission overrides (ours take priority)
60
+ merged = user_config.copy()
61
+ merged_permissions = merged.get("permission", {})
62
+ if not isinstance(merged_permissions, dict):
63
+ merged_permissions = {}
64
+ merged_permissions.update({
65
+ "edit": "allow",
66
+ "bash": "allow",
67
+ "write": "allow",
68
+ "webfetch": "allow",
69
+ })
70
+ merged["permission"] = merged_permissions
71
+
72
+ # P1 security: only serialize permission overrides to env — never API keys,
73
+ # providers, or other sensitive fields from opencode.json. Workers only
74
+ # need the permission overrides; everything else is loaded by opencode
75
+ # itself from the real config file.
76
+ config_json = json.dumps({"permission": merged_permissions})
77
+ env["OPENCODE_CONFIG_CONTENT"] = config_json
78
+
79
+ # BD-25: strip server env vars
80
+ strip_keys = (
81
+ "OPENCODE_SERVER",
82
+ "OPENCODE_SERVER_URL",
83
+ "OPENCODE_SERVER_TOKEN",
84
+ "OPENCODE_HOST",
85
+ "OPENCODE_HOST_TOKEN",
86
+ )
87
+ for key in list(env.keys()):
88
+ if key in strip_keys:
89
+ env.pop(key, None)
90
+ return env
awf/_log.py ADDED
@@ -0,0 +1,24 @@
1
+ """Lightweight file-based logger for awf.
2
+
3
+ Centralized so any module can log without circular imports.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import datetime as _dt
8
+ from pathlib import Path
9
+
10
+
11
+ def log(logs_dir: Path, message: str) -> None:
12
+ """Append a timestamped message to ``logs_dir/orchestrator.log``."""
13
+ if not logs_dir.exists():
14
+ try:
15
+ logs_dir.mkdir(parents=True, exist_ok=True)
16
+ except OSError:
17
+ return
18
+ log_file = logs_dir / "orchestrator.log"
19
+ ts = _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
20
+ try:
21
+ with open(log_file, "a", encoding="utf-8") as f:
22
+ f.write(f"[{ts}] {message}\n")
23
+ except OSError:
24
+ pass
awf/agent_stage.py ADDED
@@ -0,0 +1,275 @@
1
+ """Agent stage — spawn opencode run for each pipeline role.
2
+
3
+ Extracted from orchestrator.py (A6 refactor).
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import subprocess
8
+ from pathlib import Path
9
+
10
+ from . import paths
11
+ from ._atomic import atomic_write_text
12
+ from ._log import log as _log
13
+ from .pipeline import Stage
14
+ from .signals import clean_stage_signals, expected_signal_prefixes
15
+ from .supervisor import (
16
+ build_prompt,
17
+ get_agent_name,
18
+ get_role_model,
19
+ resolve_role_file,
20
+ )
21
+
22
+
23
+ def run_agent_stage(
24
+ stage: Stage,
25
+ todo_id: str,
26
+ project_dir: Path,
27
+ config: dict,
28
+ logs_dir: Path,
29
+ prev_handoffs: list[Path] | None = None,
30
+ hard_timeout: int | None = None,
31
+ ) -> None:
32
+ """Spawn opencode run for an agent stage.
33
+
34
+ KAUD-4: ``hard_timeout`` overrides default 3600s timeout. Pass through
35
+ from CLI --timeout or config. None = use BD20_HARD_TIMEOUT default.
36
+
37
+ BD-15: ``prev_handoffs`` is a list of handoff .md files from previous
38
+ pipeline stages (in pipeline order). Each is passed to the agent as
39
+ ``--file`` so the agent sees what previous roles did.
40
+ """
41
+ role = stage.role
42
+ kind = stage.kind
43
+ agent_name = get_agent_name(config, role)
44
+
45
+ print()
46
+ print("=" * 41)
47
+ print(f" AGENT STAGE: {role} ({kind})")
48
+ print(f" Task: {todo_id}")
49
+ print("=" * 41)
50
+ print()
51
+
52
+ role_file = resolve_role_file(role, project_dir)
53
+ inbox = paths.inbox(project_dir)
54
+ todo_file = inbox / f"{todo_id}.md"
55
+
56
+ prompt = build_prompt(kind, todo_id, project_dir=project_dir)
57
+
58
+ print(f"Running agent: {agent_name}")
59
+ print(f"Role file: {role_file}")
60
+ print(f"Task: {todo_file}")
61
+ print()
62
+
63
+ # Clean stale signals this kind could produce
64
+ prefixes = expected_signal_prefixes(kind)
65
+ outbox = paths.outbox(project_dir)
66
+ clean_stage_signals(outbox, todo_id, *prefixes)
67
+
68
+ # DF5-5: clean stale PROGRESS from previous stage so collect_handoff
69
+ # doesn't pick up the previous worker's notes as this stage's output.
70
+ # PROGRESS is per-stage running notes, not cumulative. Previous stage's
71
+ # PROGRESS is already captured in its handoff .md file (BD-19).
72
+ stale_progress = outbox / f"PROGRESS-{todo_id}.md"
73
+ if stale_progress.exists():
74
+ stale_progress.unlink()
75
+ _log(logs_dir, f"DF5-5: cleaned stale PROGRESS-{todo_id}.md from previous stage")
76
+
77
+ _log(logs_dir, f"Agent stage started: {role} ({kind}) for {todo_id}")
78
+
79
+ cmd = [
80
+ "opencode", "run", "--auto",
81
+ "--print-logs",
82
+ "--agent", agent_name,
83
+ "--title", f"awf-{role}-{todo_id}",
84
+ "--file", str(role_file),
85
+ "--file", str(todo_file),
86
+ ]
87
+ role_model = get_role_model(config, role)
88
+ if role_model:
89
+ cmd += ["--model", role_model]
90
+
91
+ if prev_handoffs:
92
+ for hf in prev_handoffs:
93
+ if hf.is_file():
94
+ cmd += ["--file", str(hf)]
95
+ print(f"Handoff in: {hf}")
96
+ _log(logs_dir, f"Forwarding handoff: {hf}")
97
+
98
+ cmd += ["--", prompt]
99
+
100
+ # BD-20: watch for expected signal files in outbox.
101
+ # P1: watch ALL expected prefixes, not just DONE/BLOCKED — otherwise
102
+ # REVIEW-APPROVED, TEST-PASSED etc. signals aren't detected until
103
+ # subprocess exits, causing unnecessary delays.
104
+ watch_paths: list[Path] = []
105
+ if todo_id:
106
+ for prefix in expected_signal_prefixes("execute"):
107
+ watch_paths.append(outbox / f"{prefix}-{todo_id}.ready")
108
+ watch_paths.append(outbox / f"{prefix}-{todo_id}.md.ready")
109
+
110
+ from ._env import awf_subprocess_env
111
+ from .signal_watch import run_subprocess_until_signal
112
+
113
+ result = run_subprocess_until_signal(
114
+ cmd, cwd=project_dir, watch_paths=watch_paths, logs_dir=logs_dir,
115
+ env=awf_subprocess_env(),
116
+ hard_timeout=hard_timeout,
117
+ )
118
+ _log(logs_dir, f"Agent stage finished: {role} ({kind}) for {todo_id} (exit={result.returncode})")
119
+ if result.returncode != 0:
120
+ raise RuntimeError(
121
+ f"Agent stage {role} ({kind}) subprocess exited with code {result.returncode}. "
122
+ f"Cmd: {' '.join(cmd)}"
123
+ )
124
+
125
+ collect_handoff(role, todo_id, project_dir, logs_dir)
126
+
127
+
128
+ def collect_handoff(
129
+ role: str,
130
+ todo_id: str,
131
+ project_dir: Path,
132
+ logs_dir: Path,
133
+ ) -> Path:
134
+ """BD-15/19: gather PROGRESS/DONE + git diff summary into handoff .md.
135
+
136
+ BD-19: filename is ``<role>-<todo_id>.md`` so retry on the same role
137
+ doesn't overwrite the previous attempt's handoff.
138
+ """
139
+ handoff_dir = paths.agentic_dir(project_dir) / "handoff"
140
+ handoff_dir.mkdir(parents=True, exist_ok=True)
141
+ outbox = paths.outbox(project_dir)
142
+
143
+ progress = outbox / f"PROGRESS-{todo_id}.md"
144
+ # H2 fix: support legacy short form DONE-{short_id}.md
145
+ from .signals import find_signal_file
146
+ done_path = find_signal_file(outbox, "DONE", todo_id, ".md") or (outbox / f"DONE-{todo_id}.md")
147
+
148
+ parts: list[str] = [
149
+ f"# Handoff from `{role}` (TODO {todo_id})",
150
+ "",
151
+ "**Generated:** by awf orchestrator (BD-15)",
152
+ f"**Stage role:** {role}",
153
+ f"**TODO:** {todo_id}",
154
+ "",
155
+ ]
156
+
157
+ has_output = False
158
+ done_has_body = False
159
+ if done_path.is_file():
160
+ body = done_path.read_text(encoding="utf-8").strip()
161
+ if body:
162
+ parts += ["## DONE summary (from worker)", "", body, ""]
163
+ has_output = True
164
+ done_has_body = True
165
+
166
+ if progress.is_file():
167
+ body = progress.read_text(encoding="utf-8").strip()
168
+ if body:
169
+ parts += ["## PROGRESS notes (from worker)", "", body, ""]
170
+ has_output = True
171
+ elif not done_has_body:
172
+ # Only warn about missing PROGRESS if DONE summary is also absent.
173
+ # DONE carries the same info — warning when DONE exists is noise.
174
+ parts += [
175
+ "## ⚠️ Worker did not leave progress notes",
176
+ "",
177
+ f"Role `{role}` did not write PROGRESS-{todo_id}.md.",
178
+ "The work may still be valid — inspect git diff and DONE report.",
179
+ "",
180
+ ]
181
+
182
+ if not has_output:
183
+ parts += [
184
+ "## ⚠️ NO OUTPUT FROM PREVIOUS STAGE",
185
+ "",
186
+ f"Role `{role}` did not write PROGRESS-{todo_id}.md or DONE-{todo_id}.md.",
187
+ "Likely causes: worker crashed, hit turn-budget, or signalled",
188
+ "BLOCKED without leaving notes. Treat prior stage work as",
189
+ "unverified — inspect `git diff` against baseline before",
190
+ "proceeding. If this is unexpected, escalate via BLOCKED signal.",
191
+ "",
192
+ ]
193
+
194
+ # Git diff summary vs baseline
195
+ context_dir = paths.context_dir(project_dir)
196
+ sha_file = context_dir / f"BASELINE-{todo_id}.sha"
197
+ if sha_file.is_file():
198
+ base_sha = sha_file.read_text(encoding="utf-8").strip().split("\n")[0]
199
+ if base_sha:
200
+ try:
201
+ diff = subprocess.run(
202
+ ["git", "diff", "--stat", base_sha],
203
+ cwd=str(project_dir),
204
+ capture_output=True,
205
+ text=True,
206
+ check=False,
207
+ timeout=10,
208
+ )
209
+ if diff is not None and getattr(diff, "stdout", "").strip():
210
+ parts += ["## Git diff summary (vs baseline)", "", "```", diff.stdout.strip(), "```", ""]
211
+ except (subprocess.TimeoutExpired, OSError) as e:
212
+ _log(logs_dir, f"handoff git-diff failed: {e}")
213
+
214
+ try:
215
+ last = subprocess.run(
216
+ ["git", "log", "-1", "--pretty=%h %s"],
217
+ cwd=str(project_dir),
218
+ capture_output=True,
219
+ text=True,
220
+ check=False,
221
+ timeout=5,
222
+ )
223
+ if last is not None and (getattr(last, "stdout", "") or "").strip():
224
+ parts += ["## Latest commit", "", f"`{last.stdout.strip()}`", ""]
225
+ except (subprocess.TimeoutExpired, OSError):
226
+ pass
227
+
228
+ parts += [
229
+ "## What the next role should know",
230
+ "",
231
+ "- Read this handoff first. The TODO file declares the goal; this file shows what's done.",
232
+ "- Pick up where this role left off. Do NOT redo work already done.",
233
+ "- Write your own handoff at `.agentic/handoff/<your-role>.md` when finished.",
234
+ "",
235
+ ]
236
+
237
+ out_file = handoff_dir / f"{role}-{todo_id}.md"
238
+ # QA-C: handoff write atomic (was direct write_text — inconsistent with
239
+ # rest of codebase which uses atomic_write_text for crash safety).
240
+ atomic_write_text(out_file, "\n".join(parts))
241
+ print(f"Handoff out: {out_file}")
242
+ _log(logs_dir, f"Handoff written: {out_file}")
243
+ return out_file
244
+
245
+
246
+ def resolve_prev_handoffs(
247
+ pipeline_stages: list[Stage],
248
+ current_stage_idx: int,
249
+ project_dir: Path,
250
+ todo_id: str = "",
251
+ ) -> list[Path]:
252
+ """BD-15/19: return handoff paths for all AGENT stages before current_stage_idx."""
253
+ handoff_dir = paths.agentic_dir(project_dir) / "handoff"
254
+ result: list[Path] = []
255
+ for i in range(current_stage_idx):
256
+ st = pipeline_stages[i]
257
+ if st.role == "supervisor":
258
+ continue
259
+ if todo_id:
260
+ result.append(handoff_dir / f"{st.role}-{todo_id}.md")
261
+ else:
262
+ # P2: sort by name (numeric ID) instead of mtime — deterministic
263
+ # even when rapid retry creates files in the same second.
264
+ import re
265
+ def _sort_key(p: Path) -> tuple[int, str]:
266
+ m = re.search(r"(\d+)", p.name)
267
+ return (int(m.group(1)) if m else 0, p.name)
268
+ candidates = sorted(
269
+ handoff_dir.glob(f"{st.role}- *.md".replace(" ", "")),
270
+ key=_sort_key,
271
+ reverse=True,
272
+ )
273
+ if candidates:
274
+ result.append(candidates[0])
275
+ return result
awf/api/__init__.py ADDED
@@ -0,0 +1,128 @@
1
+ """Public API for agentic-workflow.
2
+
3
+ All external callers (CLI, MCP plugin) MUST go through this module.
4
+ Business logic lives in submodules — this file re-exports the public
5
+ surface so callers can ``from awf import api; api.init_project(...)``.
6
+
7
+ Layout:
8
+ - ``_errors`` — AwfApiError
9
+ - ``_results`` — Result dataclasses (InitResult, StatusResult,
10
+ DispatchTodoResult, ApplyIncrementPlanResult, ...)
11
+ - ``_stack`` — detect_stack, derive_project_name
12
+ - ``_templates`` — _CONFIG_TEMPLATE, _ROLE_TEMPLATE, update_gitignore
13
+ - ``_helpers`` — require_agentic, require_git_repo, read helpers
14
+ - ``_background`` — start_in_background, check_pipeline_running, PipelineArgs
15
+ - ``lifecycle`` — init_project, get_status, get_report, reset_runtime,
16
+ list_orphans, remove_orphans
17
+ - ``pipeline`` — start_pipeline, continue_pipeline, create_baseline,
18
+ rollback, approve_commit
19
+ - ``roles`` — add_role, analyze_roles
20
+ - ``setup`` — apply_project_setup (project-setup form materialization)
21
+ - ``dispatch`` — dispatch_todo (atomic TODO + baseline + signal)
22
+ - ``context`` — load_supervisor_context (aggregate bootstrap payload),
23
+ _extract_stage_info, _compute_expected_action
24
+ - ``planning`` — apply_increment_plan (increment variant persistence)
25
+
26
+ Error convention: every function returns a Result dataclass (success)
27
+ or raises AwfApiError with a human-readable message. Callers wrap in
28
+ try/except.
29
+ """
30
+ from __future__ import annotations
31
+
32
+ # Public types
33
+ from ._errors import AwfApiError
34
+ from ._results import (
35
+ AddRoleResult,
36
+ AnalyzeRolesResult,
37
+ ApplyIncrementPlanResult,
38
+ ApplyProjectSetupResult,
39
+ ApproveResult,
40
+ BaselineResult,
41
+ DispatchTodoResult,
42
+ InitResult,
43
+ ReportResult,
44
+ ResetResult,
45
+ RollbackResult,
46
+ StartResult,
47
+ StatusResult,
48
+ SupervisorContextResult,
49
+ WaitEventResult,
50
+ )
51
+
52
+ # Public functions — organized by submodule for clarity
53
+ from ._stack import derive_project_name, detect_stack
54
+ from .context import load_supervisor_context
55
+ from .dispatch import dispatch_todo
56
+ from .lifecycle import (
57
+ get_report,
58
+ get_status,
59
+ init_project,
60
+ list_orphans,
61
+ remove_orphans,
62
+ reset_runtime,
63
+ )
64
+ from .model_check import check_model_config
65
+ from .pipeline import (
66
+ approve_commit,
67
+ continue_pipeline,
68
+ create_baseline,
69
+ kill_pipeline,
70
+ rollback,
71
+ start_pipeline,
72
+ )
73
+ from .planning import apply_increment_plan
74
+ from .roles import add_role, analyze_roles
75
+ from .setup import apply_project_setup
76
+ from .wait_event import wait_for_event
77
+
78
+ __all__ = [
79
+ # Exception
80
+ "AwfApiError",
81
+ # Result dataclasses
82
+ "InitResult",
83
+ "StatusResult",
84
+ "BaselineResult",
85
+ "RollbackResult",
86
+ "ApproveResult",
87
+ "ReportResult",
88
+ "ResetResult",
89
+ "AddRoleResult",
90
+ "StartResult",
91
+ "AnalyzeRolesResult",
92
+ "ApplyProjectSetupResult",
93
+ "DispatchTodoResult",
94
+ "SupervisorContextResult",
95
+ "ApplyIncrementPlanResult",
96
+ "WaitEventResult",
97
+ # Stack detection
98
+ "detect_stack",
99
+ "derive_project_name",
100
+ # Lifecycle
101
+ "init_project",
102
+ "get_status",
103
+ "get_report",
104
+ "reset_runtime",
105
+ "list_orphans",
106
+ "remove_orphans",
107
+ # Pipeline
108
+ "start_pipeline",
109
+ "continue_pipeline",
110
+ "kill_pipeline",
111
+ "create_baseline",
112
+ "rollback",
113
+ "approve_commit",
114
+ # Roles
115
+ "add_role",
116
+ "analyze_roles",
117
+ # Project-setup materialization
118
+ "apply_project_setup",
119
+ # TODO dispatch + supervisor context (dogfood-2 automation)
120
+ "dispatch_todo",
121
+ "load_supervisor_context",
122
+ # Increment planning (dogfood-7 — user picks decomposition variant)
123
+ "apply_increment_plan",
124
+ # Supervisor wake-up (DASH Phase 3 — no more polling)
125
+ "wait_for_event",
126
+ # Model configuration validation
127
+ "check_model_config",
128
+ ]