Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .tokeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
internal/ent/**
!internal/ent/schema/**
**/*.md
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ templates/
└── ...
docs/
skills/ - Skill directories (each has SKILL.md)
├── sp-writing-plans/SKILL.md - Plan writing methodology
├── sp-planning/SKILL.md - Full planning process (explore → design → write → validate)
├── sp-debugging/SKILL.md - Bug diagnosis + fix plans
├── sp-brainstorming/SKILL.md - Brainstorming framework
└── ...
Expand Down
7 changes: 2 additions & 5 deletions cmd/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,6 @@ Example:
if ag.Voice != "" {
fmt.Printf("Voice: %s\n", ag.Voice)
}
if ag.FlicknoteProject != "" {
fmt.Printf("Flicknote: %s\n", ag.FlicknoteProject)
}
return nil
},
}
Expand Down Expand Up @@ -244,10 +241,10 @@ Examples:
if !voice.IsValidVoice(value) {
return fmt.Errorf("unknown voice '%s' — run 'ttal voice list' to see available voices", value)
}
case "emoji", "description", "role", "flicknote_project":
case "emoji", "description", "role":
// valid fields
default:
return fmt.Errorf("unknown field '%s' (available: voice, emoji, description, role, flicknote_project)", field)
return fmt.Errorf("unknown field '%s' (available: voice, emoji, description, role)", field)
}

if err := agentfs.SetField(teamPath, name, field, value); err != nil {
Expand Down
2 changes: 0 additions & 2 deletions docs/docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ All frontmatter fields are optional:
| `emoji` | Display emoji | `🦅`, `🐱` |
| `description` | Short role summary | `Task orchestration and planning` |
| `role` | Role key matching `[role]` in `roles.toml` | `manager`, `designer` |
| `flicknote_project` | Default flicknote project (injected as `$FLICKNOTE_PROJECT`) | `ttal.plans` |

Agent `.md` frontmatter is the single source of truth for agent identity and per-agent config.
Operational config (prompts, heartbeat) lives in `~/.config/ttal/roles.toml` per role.
Expand Down Expand Up @@ -84,7 +83,6 @@ Update frontmatter fields with `field:value` syntax:
```bash
ttal agent modify kestrel voice:af_heart
ttal agent modify kestrel emoji:🦅 description:'Worker lifecycle'
ttal agent modify kestrel flicknote_project:ttal.fixes
```

## One bot per agent
Expand Down
4 changes: 1 addition & 3 deletions docs/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ that contains a `CLAUDE.md` file is treated as an agent.
```

Agent configuration lives in two places:
- **CLAUDE.md frontmatter** — identity (role, emoji, voice, description, flicknote_project)
- **CLAUDE.md frontmatter** — identity (role, emoji, voice, description)
- **roles.toml** — operational config per role (prompts, heartbeat_interval)

Bot tokens follow the naming convention `{UPPER_NAME}_BOT_TOKEN` in `~/.config/ttal/.env`.
Expand Down Expand Up @@ -90,7 +90,6 @@ description: Task orchestrator — creates and routes work
emoji: 🐱
role: manager
voice: af_heart
flicknote_project: ttal.plans
---
```

Expand All @@ -100,7 +99,6 @@ flicknote_project: ttal.plans
| `emoji` | string | Display emoji |
| `role` | string | Role key that maps to `[role]` in roles.toml |
| `voice` | string | Kokoro TTS voice ID (e.g. `af_heart`) |
| `flicknote_project` | string | Default flicknote project (injected as `$FLICKNOTE_PROJECT`) |

## roles.toml fields

Expand Down
15 changes: 6 additions & 9 deletions internal/agentfs/agentfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,12 @@ func isAgentFile(e fs.DirEntry) (name string, ok bool) {

// AgentInfo holds agent metadata parsed from .md file frontmatter.
type AgentInfo struct {
Name string // directory name (lowercase)
Path string // absolute path to agent directory
Voice string // Kokoro TTS voice ID
Emoji string // display emoji
Description string // short role summary
Role string // e.g. designer, researcher — matches [prompts] key
FlicknoteProject string // default flicknote project for this agent
Name string // directory name (lowercase)
Path string // absolute path to agent directory
Voice string // Kokoro TTS voice ID
Emoji string // display emoji
Description string // short role summary
Role string // e.g. designer, researcher — matches [prompts] key
}

// Discover scans teamPath for agents via flat .md files (e.g., yuki.md).
Expand Down Expand Up @@ -79,7 +78,6 @@ func Discover(teamPath string) ([]AgentInfo, error) {
info.Emoji = fm["emoji"]
info.Description = fm["description"]
info.Role = fm["role"]
info.FlicknoteProject = fm["flicknote_project"]
} else {
log.Printf("agentfs: failed to parse frontmatter for %s: %v", name, err)
}
Expand Down Expand Up @@ -112,7 +110,6 @@ func Get(teamPath, name string) (*AgentInfo, error) {
info.Emoji = fm["emoji"]
info.Description = fm["description"]
info.Role = fm["role"]
info.FlicknoteProject = fm["flicknote_project"]
} else {
log.Printf("agentfs: failed to parse frontmatter for %s: %v", name, err)
}
Expand Down
1 change: 0 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@ type AgentConfig struct {
Runtime string `toml:"runtime" jsonschema:"-"`
Model string `toml:"model" jsonschema:"-"`
HeartbeatInterval string `toml:"heartbeat_interval" jsonschema:"-"`
FlicknoteProject string `toml:"flicknote_project" jsonschema:"-"`
}

// AgentBotToken returns the bot token for an agent using the naming convention.
Expand Down
12 changes: 6 additions & 6 deletions internal/config/config_render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ func TestRenderSkillPlaceholders(t *testing.T) {
},
{
name: "CC replaces skill placeholder at start",
input: "{{skill:sp-writing-plans}}\nWrite a plan for task {{task-id}}",
input: "{{skill:sp-planning}}\nWrite a plan for task {{task-id}}",
rt: runtime.ClaudeCode,
want: "Use sp-writing-plans skill\n\nWrite a plan for task abc123",
want: "Use sp-planning skill\n\nWrite a plan for task abc123",
},
{
name: "Codex replaces skill placeholder with dollar",
input: "{{skill:sp-writing-plans}}\nWrite a plan for task {{task-id}}",
input: "{{skill:sp-planning}}\nWrite a plan for task {{task-id}}",
rt: runtime.Codex,
want: "$sp-writing-plans\n\nWrite a plan for task abc123",
want: "$sp-planning\n\nWrite a plan for task abc123",
},
{
name: "multiple skill placeholders",
input: "{{skill:sp-writing-plans}}\n{{skill:flicknote-cli}}\nDo the thing",
input: "{{skill:sp-planning}}\n{{skill:flicknote}}\nDo the thing",
rt: runtime.Codex,
want: "$sp-writing-plans\n$flicknote-cli\n\nDo the thing",
want: "$sp-planning\n$flicknote\n\nDo the thing",
},
{
name: "no placeholders unchanged",
Expand Down
9 changes: 0 additions & 9 deletions internal/daemon/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"strings"
"sync"

"github.com/tta-lab/ttal-cli/internal/agentfs"
"github.com/tta-lab/ttal-cli/internal/claudeconfig"
"github.com/tta-lab/ttal-cli/internal/config"
"github.com/tta-lab/ttal-cli/internal/runtime"
Expand Down Expand Up @@ -73,14 +72,6 @@ func buildAgentEnv(agentName, teamName string, mcfg *config.DaemonConfig) []stri
if team, ok := mcfg.Teams[teamName]; ok && team.TaskRC != "" {
env = append(env, fmt.Sprintf("TASKRC=%s", team.TaskRC))
}
// Read flicknote_project from CLAUDE.md frontmatter
if team, ok := mcfg.Teams[teamName]; ok && team.TeamPath != "" {
info, err := agentfs.GetFromPath(filepath.Join(team.TeamPath, agentName))
if err == nil && info.FlicknoteProject != "" {
env = append(env, fmt.Sprintf("FLICKNOTE_PROJECT=%s", info.FlicknoteProject))
}
}

// Inject all secrets from .env
env = append(env, config.DotEnvParts()...)

Expand Down
11 changes: 2 additions & 9 deletions internal/daemon/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"strings"
"time"

"github.com/tta-lab/ttal-cli/internal/agentfs"
"github.com/tta-lab/ttal-cli/internal/breathe"
"github.com/tta-lab/ttal-cli/internal/config"
"github.com/tta-lab/ttal-cli/internal/env"
Expand Down Expand Up @@ -392,7 +391,7 @@ func handleBreathe(shellCfg *config.Config, req BreatheRequest) SendResponse {
log.Printf("[breathe] warning: failed to write status for %s/%s: %v", team, req.Agent, err)
}

// 8. Build restart command with full env (secrets, TASKRC, FLICKNOTE_PROJECT).
// 8. Build restart command with full env (secrets, TASKRC).
ccCmd := buildCCRestartCmd(newSessionID, am.model, req.Agent, trigger)
agentEnv := buildBreatheEnv(req.Agent, team, shellCfg)
fullCmd := shellCfg.BuildEnvShellCommand(agentEnv, ccCmd)
Expand All @@ -419,7 +418,7 @@ func handleBreathe(shellCfg *config.Config, req BreatheRequest) SendResponse {
}

// buildBreatheEnv returns the env var list for a breathe restart command.
// Mirrors buildAgentEnv: agent identity, TASKRC, FLICKNOTE_PROJECT, and .env secrets.
// Mirrors buildAgentEnv: agent identity, TASKRC, and .env secrets.
func buildBreatheEnv(agent, team string, cfg *config.Config) []string {
vars := []string{
fmt.Sprintf("TTAL_AGENT_NAME=%s", agent),
Expand All @@ -428,12 +427,6 @@ func buildBreatheEnv(agent, team string, cfg *config.Config) []string {
if taskRC := cfg.TaskRC(); taskRC != "" {
vars = append(vars, fmt.Sprintf("TASKRC=%s", taskRC))
}
agentInfo, err := agentfs.Get(cfg.TeamPath(), agent)
if err != nil {
log.Printf("[breathe] %s: could not read agent config, FLICKNOTE_PROJECT omitted: %v", agent, err)
} else if agentInfo.FlicknoteProject != "" {
vars = append(vars, fmt.Sprintf("FLICKNOTE_PROJECT=%s", agentInfo.FlicknoteProject))
}
vars = append(vars, config.DotEnvParts()...)
return vars
}
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func advanceTask(uuid string) tea.Cmd {
if err != nil {
return actionResultMsg{err: fmt.Errorf("advance %s: %s", short, strings.TrimSpace(string(out)))}
}
return actionResultMsg{message: fmt.Sprintf("Advanced %s", short), refresh: true}
return actionResultMsg{message: strings.TrimSpace(string(out)), refresh: true}
}
}

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion templates/basic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A minimal ttal setup with two agents: one manages tasks, one designs implementat

```
You create a task
→ manager routes it: ttal task design <uuid>
→ manager advances it: ttal task go <uuid>
→ designer writes an implementation plan
→ you approve
→ manager spawns a worker: ttal task go <uuid>
Expand Down
3 changes: 1 addition & 2 deletions templates/basic/designer.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ You are the design agent. You turn requirements into detailed implementation pla
4. Write a detailed implementation plan
5. Save the plan as markdown: `docs/plans/YYYY-MM-DD-<topic>.md`
6. Annotate the task: `task <uuid> annotate 'Plan: docs/plans/<filename>.md'`
7. Tag as planned: `task <uuid> modify -brainstorm -design +planned`
8. Wait for human approval before execution
7. Wait for human approval before execution

## Plan Quality

Expand Down
5 changes: 2 additions & 3 deletions templates/basic/manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ You are the task manager for this team. You organize work, route tasks to the ri
## Your Role

- Manage tasks via taskwarrior: create, prioritize, tag, schedule
- Route tasks to agents: `ttal task design` for planning, `ttal task go` for implementation
- Route tasks to agents: `ttal task go` advances tasks through the pipeline
- Maintain the daily focus list with `ttal today`
- Respond to human messages and status requests
- Monitor worker progress via `ttal worker list`
Expand All @@ -23,8 +23,7 @@ You are the task manager for this team. You organize work, route tasks to the ri
When a new task comes in:
1. Read it: `ttal task get <uuid>`
2. Decide what it needs:
- Needs a plan? → `ttal task design <uuid>`
- Ready to execute? → `ttal task go <uuid>` (only after human approval)
- Advance to next stage? → `ttal task go <uuid>` (only after human approval)
3. Track progress and report status

When the human asks "what's happening?":
Expand Down
3 changes: 1 addition & 2 deletions templates/basic/roles.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@ prompt = """Write an implementation plan for this task.
Read the task first: ttal task get {{task-id}}

Save plan to: docs/plans/YYYY-MM-DD-<topic>.md in the project repo.
When done: task {{task-id}} annotate 'Plan: docs/plans/<filename>.md'
Then tag-swap: task {{task-id}} modify -brainstorm -design +planned"""
When done: task {{task-id}} annotate 'Plan: docs/plans/<filename>.md'"""
4 changes: 2 additions & 2 deletions templates/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This directory contains skills, subagents, and commands that `ttal sync` deploys

```
docs/
├── skills/ # Methodology skills (sp-writing-plans, sp-tdd, etc.)
├── skills/ # Methodology skills (sp-planning, sp-tdd, etc.)
├── agents/ # Subagent definitions (pr-reviewers, task-creator, etc.)
└── commands/ # Slash commands (pr-review, triage, etc.)
```
Expand Down Expand Up @@ -38,7 +38,7 @@ Create a new directory in `docs/skills/` with a `SKILL.md` file. See existing sk
### Skills
| Skill | Purpose |
|-------|---------|
| sp-writing-plans | Write detailed implementation plans |
| sp-planning | Write detailed implementation plans |
| sp-executing-plans | Execute plans step by step |
| sp-research | Structured research methodology |
| sp-brainstorming | Collaborative idea exploration and design |
Expand Down
5 changes: 2 additions & 3 deletions templates/docs/skills/sp-debugging/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,8 @@ task <uuid> annotate 'Fix plan: flicknote <hex-id>'
1. **Save the plan** — inline annotation or flicknote (see above)
2. **Create a task** (if needed) via `ttal task add --project <alias> "description"`
3. **Annotate the task** with plan reference (inline or flicknote hex ID)
4. **Tag as planned:** `task <uuid> modify -bugfix +planned`
5. **Review:** Run at least 1 round of `/plan-review <flicknote-id>`. Revise if needed.
6. **Execute:** When the plan passes review, run `ttal task go <uuid>` to spawn a worker.
4. **Review:** Run at least 1 round of `/plan-review <flicknote-id>`. Revise if needed.
5. **Execute:** When the plan passes review, run `ttal task go <uuid>` to spawn a worker.

## Red Flags — STOP and Return to Phase 1

Expand Down
2 changes: 1 addition & 1 deletion templates/docs/skills/sp-executing-plans/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,5 @@ Don't guess your way through blockers — alert and wait.
## Integration

**Required workflow skills:**
- **sp-writing-plans** - Creates the plan this skill executes
- **sp-planning** - Creates the plan this skill executes
- **knowledge** - Vault conventions (frontmatter, folder structure)
Loading
Loading