summaryrefslogtreecommitdiff
path: root/CLAUDE.md
diff options
context:
space:
mode:
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md63
1 files changed, 62 insertions, 1 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 6313f1d..90356e5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -79,6 +79,7 @@ Config defaults to `~/.claudomator/config.toml`. Data is stored in `~/.claudomat
| `internal/sandbox` | `Sandbox` interface (`HostSandbox`/`DockerSandbox`) + pre-tool-use guardrail hooks |
| `internal/role` | `RoleConfig`/`Tier`/`Rung` — per-role system prompt + provider/model escalation ladder |
| `internal/scheduler` | `Scheduler` — polls role-typed FAILED tasks and retries/escalates them per their role's ladder |
+| `internal/story` | `Epic`/`Story` — planning layer above the flat task tree; data model only (Phase 7a), no orchestration |
| `web` | Embedded static UI (`embed.go`) |
### Key Data Flows
@@ -196,6 +197,12 @@ events: id, task_id, seq, ts, kind, actor, payload_json
role_configs: id, role, version, status, config_json, created_at, activated_at,
retired_at, proposed_by (UNIQUE(role, version))
+
+epics: id, name, description, status, discovery_source, created_at, updated_at
+
+stories: id, epic_id, project_id, name, branch_name, status, discovery_source,
+ spec, acceptance_criteria_json, validation_json, deploy_config,
+ priority, root_task_id, created_at, updated_at
```
The `events` table is the per-task observability stream (`internal/event`); see
@@ -212,7 +219,12 @@ enforces "at most one active row per role" transactionally (retire-then-activate
in one transaction), the same way `UpdateTaskState` enforces the task state
machine in a transaction rather than in schema — see `internal/storage/roleconfig.go`.
-JSON blobs: `config_json` (AgentConfig on `tasks`; RoleConfig on `role_configs`), `retry_json`, `tags_json`, `depends_on_json`, `interactions_json`, `changestats_json`, `commits_json`.
+JSON blobs: `config_json` (AgentConfig on `tasks`; RoleConfig on `role_configs`), `retry_json`, `tags_json`, `depends_on_json`, `interactions_json`, `changestats_json`, `commits_json`, `acceptance_criteria_json`.
+
+`epics`/`stories` (`internal/story.Epic`/`internal/story.Story`) are the
+planning layer that sits above the flat task tree — see "Planning layer:
+epics & stories" below. This phase (7a) is pure data model + CRUD: no
+orchestration reads or writes these tables yet.
---
@@ -312,6 +324,45 @@ Two prerequisites for fan-out patterns (e.g. a Builder task with several role-ty
- **Auto-cascade-fail** (`internal/executor.Pool.cascadeFail`): the moment a task lands in a terminal failure state (`FAILED`/`TIMED_OUT`/`CANCELLED`/`BUDGET_EXCEEDED` — checked in `handleRunResult` plus the two direct-cancel paths in `execute()` for budget-gate and dependency-failure), the pool looks up `Store.ListDependents(taskID)` (a full-table scan over `depends_on_json` — no reverse index, same tradeoff already accepted elsewhere for JSON-blob columns) and cancels every direct dependent still waiting (`PENDING`/`QUEUED`), recording a `CANCELLED` execution whose `error_msg` references the upstream task and writing the ordinary `KindStateChange` event via `UpdateTaskState` (no new event kind). It recurses into each cancelled dependent's own dependents (visited-set guarded against a pathological dependency cycle — task creation does not itself reject cycles), so a multi-level chain cascades all the way down. `execute()`'s dependency-wait requeue loop re-checks the task's fresh DB state before dispatching so a task cascade-cancelled out from under it is never actually run.
- **Role-typed subtask spawning** (`agentchannel.SubtaskSpec.Role`, threaded through `storeChannel.SpawnSubtask` in `internal/executor/channel.go`): when a spawning agent sets `role` (available as an optional `spawn_subtask` tool parameter on both transports — `internal/agentloop/tools.go` for the native tool-use loop, `internal/executor/agentmcp.go` for the MCP transport), the child task gets `Agent.Role` set and `Agent.Type`/`Agent.Model` left empty, so Phase 5's role-based dispatch resolves them from the role's escalation ladder on the child's own first dispatch. Omitting `role` (every pre-Phase-6 caller) preserves the exact prior behavior (`Agent.Type: "claude"`, `Agent.Model` from the `model` argument).
+### Planning layer: epics & stories (data model only)
+
+`internal/story` defines `Epic` and `Story` — a planning layer above the flat
+task tree (`epics`/`stories` tables, see Storage Schema above). This is
+Phase 7a: **pure data model + CRUD**. Nothing creates, transitions, or reads
+these rows automatically yet — no watcher spawns Builder/Evaluator/
+Arbitration tasks from a story, no deploy-gating, no epic-proposal agent
+tooling. A later phase builds that orchestration on top of what's stored
+here.
+
+- **Epic** (`epics` table): a loosely-scoped initiative that decomposes into
+ Stories. `status` is `OPEN`/`CLOSED`, unvalidated pass-through (`UpdateEpic`
+ writes whatever it's given, same trust level as `UpdateProject`).
+- **Story** (`stories` table): a shippable slice of work realized by a tree
+ of `internal/task` Tasks rooted at `root_task_id`. `epic_id`/`project_id`/
+ `root_task_id` are loose references (plain strings, no FK enforcement) —
+ same tolerance the codebase already has for `tasks.project`. `status` is
+ one of `DISCOVERY|FRAMING|BACKLOG|PRIORITIZED|IN_PROGRESS|SHIPPABLE|
+ DEPLOYED|VALIDATING|REVIEW_READY|NEEDS_FIX|DONE|CANCELLED`, also
+ unvalidated pass-through in this phase (no `task.ValidTransition`-style
+ enforcement — there's no orchestrator yet to make transitions meaningful).
+- **`GET /api/stories/{id}/task-tree`** walks the task graph realizing a
+ story: starting at `root_task_id`, it follows both `parent_task_id`
+ children (`ListSubtasks`) and `depends_on_json` edges in either direction
+ (`ListDependents`), so a task that merely depends on something already in
+ the tree is discovered even if it isn't a literal `parent_task_id` child.
+ Returns a flat node list (`{story_id, root_task_id, nodes: [{id, name,
+ state, agent_type, role, parent_task_id, depends_on}]}`); clients
+ reconstruct the graph from each node's `parent_task_id`/`depends_on`. An
+ unset `root_task_id` returns an empty node list, not an error.
+- New `event.Kind` constants for the ceremony this layer will eventually
+ drive (`epic_proposed`, `discovery_proposed`, `framing_decided`,
+ `groomed`, `prioritized`, `eval_verdict`, `arbitration_decided`,
+ `retro_captured`, `human_accepted`) are defined in `internal/event` but
+ nothing emits them yet.
+- REST endpoints are not gated by `api_token`, consistent with
+ `/api/projects`/`/api/tasks` (only chatbot MCP, agent MCP, and WebSocket
+ require it).
+
---
@@ -411,6 +462,16 @@ In `executor.go`, `withFailureHistory` creates a copy of the task struct (`copy
| POST | `/api/roles/{role}/versions` | Create a new draft `role_configs` version |
| GET | `/api/roles/{role}/versions` | List all versions for a role |
| POST | `/api/roles/{role}/activate?version=N` | Activate a version (atomically retires the prior active one) |
+| GET | `/api/epics` | List epics; `?status=OPEN` |
+| POST | `/api/epics` | Create epic |
+| GET | `/api/epics/{id}` | Get epic |
+| PUT | `/api/epics/{id}` | Update epic (name/description/status; unvalidated pass-through) |
+| GET | `/api/epics/{id}/stories` | List stories whose `epic_id` matches `{id}` |
+| GET | `/api/stories` | List stories; `?status=X&epic_id=Y` |
+| POST | `/api/stories` | Create story |
+| GET | `/api/stories/{id}` | Get story |
+| PUT | `/api/stories/{id}` | Update story (all fields but id/created_at; unvalidated status pass-through) |
+| GET | `/api/stories/{id}/task-tree` | Walk the task graph realizing a story (parent_task_id + depends_on edges) |
---