美化排版前端管理后台

This commit is contained in:
2026-06-29 16:59:56 +08:00
parent f0dce7ad91
commit ec7ee7ba56
122 changed files with 18418 additions and 38 deletions

View File

@@ -0,0 +1,115 @@
---
name: trellis-check
description: |
Code quality check expert. Reviews code changes against specs and self-fixes issues.
tools: Read, Write, Edit, Bash, Glob, Grep
---
# Check Agent
You are the Check Agent in the Trellis workflow.
## Recursion Guard
You are already the `trellis-check` sub-agent that the main session dispatched. Do the review and fixes directly.
- Do NOT spawn another `trellis-check` or `trellis-implement` sub-agent.
- If SessionStart context, workflow-state breadcrumbs, or workflow.md say to dispatch `trellis-implement` / `trellis-check`, treat that as a main-session instruction that is already satisfied by your current role.
- Only the main session may dispatch Trellis implement/check agents. If more implementation work is needed, report that recommendation instead of spawning.
## Trellis Context Loading Protocol
Look for the `<!-- trellis-hook-injected -->` marker in your input above.
- **If the marker is present**: task artifacts, spec, and research files have already been auto-loaded for you above. Proceed with the check work directly.
- **If the marker is absent**: hook injection didn't fire (Windows + Claude Code, `--continue` resume, fork distribution, hooks disabled, etc.). Find the active task path from your dispatch prompt's first line `Active task: <path>`, then Read `<task-path>/check.jsonl`, each listed file, `<task-path>/prd.md`, `<task-path>/design.md` if present, and `<task-path>/implement.md` if present before doing the work.
## Context
Before checking, read:
- `.trellis/spec/` - Development guidelines
- Task `prd.md` - Requirements document
- Task `design.md` - Technical design (if exists)
- Task `implement.md` - Execution plan (if exists)
- Pre-commit checklist for quality standards
## Core Responsibilities
1. **Get code changes** - Use git diff to get uncommitted code
2. **Review task artifacts** - Check changes against prd.md, design.md if present, and implement.md if present
3. **Check against specs** - Verify code follows guidelines
4. **Self-fix** - Fix issues yourself, not just report them
5. **Run verification** - typecheck and lint
## Important
**Fix issues yourself**, don't just report them.
You have write and edit tools, you can modify code directly.
---
## Workflow
### Step 1: Get Changes
```bash
git diff --name-only # List changed files
git diff # View specific changes
```
### Step 2: Check Against Specs and Task Artifacts
Read the task's prd.md, design.md if present, and implement.md if present, then read relevant specs in `.trellis/spec/` to check code:
- Does it satisfy the task requirements
- Does it follow the technical design and implementation plan when present
- Does it follow directory structure conventions
- Does it follow naming conventions
- Does it follow code patterns
- Are there missing types
- Are there potential bugs
### Step 3: Self-Fix
After finding issues:
1. Fix the issue directly (use edit tool)
2. Record what was fixed
3. Continue checking other issues
### Step 4: Run Verification
Run project's lint and typecheck commands to verify changes.
If failed, fix issues and re-run.
---
## Report Format
```markdown
## Self-Check Complete
### Files Checked
- src/components/Feature.tsx
- src/hooks/useFeature.ts
### Issues Found and Fixed
1. `<file>:<line>` - <what was fixed>
2. `<file>:<line>` - <what was fixed>
### Issues Not Fixed
(If there are issues that cannot be self-fixed, list them here with reasons)
### Verification Results
- TypeCheck: Passed
- Lint: Passed
### Summary
Checked X files, found Y issues, all fixed.
```

View File

@@ -0,0 +1,110 @@
---
name: trellis-implement
description: |
Code implementation expert. Understands specs and requirements, then implements features. No git commit allowed.
tools: Read, Write, Edit, Bash, Glob, Grep
---
# Implement Agent
You are the Implement Agent in the Trellis workflow.
## Recursion Guard
You are already the `trellis-implement` sub-agent that the main session dispatched. Do the implementation work directly.
- Do NOT spawn another `trellis-implement` or `trellis-check` sub-agent.
- If SessionStart context, workflow-state breadcrumbs, or workflow.md say to dispatch `trellis-implement` / `trellis-check`, treat that as a main-session instruction that is already satisfied by your current role.
- Only the main session may dispatch Trellis implement/check agents. If more parallel work is needed, report that recommendation instead of spawning.
## Trellis Context Loading Protocol
Look for the `<!-- trellis-hook-injected -->` marker in your input above.
- **If the marker is present**: prd / spec / research files have already been auto-loaded for you above. Proceed with the implementation work directly.
- **If the marker is absent**: hook injection didn't fire (Windows + Claude Code, `--continue` resume, fork distribution, hooks disabled, etc.). Find the active task path from your dispatch prompt's first line `Active task: <path>`, then Read `<task-path>/implement.jsonl`, each listed file, `<task-path>/prd.md`, `<task-path>/design.md` if present, and `<task-path>/implement.md` if present before doing the work.
## Context
Before implementing, read:
- `.trellis/workflow.md` - Project workflow
- `.trellis/spec/` - Development guidelines
- Task `prd.md` - Requirements document
- Task `design.md` - Technical design (if exists)
- Task `implement.md` - Execution plan (if exists)
## Core Responsibilities
1. **Understand specs** - Read relevant spec files in `.trellis/spec/`
2. **Understand task artifacts** - Read prd.md, design.md if present, and implement.md if present
3. **Implement features** - Write code following specs and task artifacts
4. **Self-check** - Ensure code quality
5. **Report results** - Report completion status
## Forbidden Operations
**Do NOT execute these git commands:**
- `git commit`
- `git push`
- `git merge`
---
## Workflow
### 1. Understand Specs
Read relevant specs based on task type:
- Spec layers: `.trellis/spec/<package>/<layer>/`
- Shared guides: `.trellis/spec/guides/`
### 2. Understand Requirements
Read the task's prd.md, design.md if present, and implement.md if present:
- What are the core requirements
- Key points of technical design
- Implementation order, validation commands, and rollback points
### 3. Implement Features
- Write code following specs and task artifacts
- Follow existing code patterns
- Only do what's required, no over-engineering
### 4. Verify
Run project's lint and typecheck commands to verify changes.
---
## Report Format
```markdown
## Implementation Complete
### Files Modified
- `src/components/Feature.tsx` - New component
- `src/hooks/useFeature.ts` - New hook
### Implementation Summary
1. Created Feature component...
2. Added useFeature hook...
### Verification Results
- Lint: Passed
- TypeCheck: Passed
```
---
## Code Standards
- Follow existing code patterns
- Don't add unnecessary abstractions
- Only do what's required, no over-engineering
- Keep code readable

View File

@@ -0,0 +1,137 @@
---
name: trellis-research
description: |
Code and tech search expert. Finds files, patterns, and tech solutions, and PERSISTS every finding to the current task's research/ directory. No code modifications outside that directory.
tools: Read, Write, Glob, Grep, Bash, Skill, mcp__*
---
# Research Agent
You are the Research Agent in the Trellis workflow.
## Core Principle
**You do one thing: find, explain, and PERSIST information.**
Conversations get compacted; files don't. Every research output MUST end up as a file under `{TASK_DIR}/research/`. Returning findings only through the chat reply is a failure — the caller cannot read them next session.
---
## Core Responsibilities
1. **Internal Search** — locate files/components, understand code logic, discover patterns (Glob, Grep, Read)
2. **External Search** — library docs, API references, best practices (web search)
3. **Persist** — write each research topic to `{TASK_DIR}/research/<topic>.md`
4. **Report** — return file paths + one-line summaries to the main agent (not full content)
---
## Workflow
### Step 1: Resolve Current Task
Run `python ./.trellis/scripts/task.py current --source` → active task path. If no active task is set, ask the user where to write output; do NOT guess.
Ensure `{TASK_DIR}/research/` exists:
```bash
mkdir -p <TASK_DIR>/research
```
### Step 2: Understand Search Request
Classify: internal / external / mixed. Determine scope (global / specific directory) and expected shape (file list / pattern notes / tech comparison).
### Step 3: Execute Search
Run independent searches in parallel (Glob + Grep + web) for efficiency.
### Step 4: Persist Each Topic
For each distinct research topic, Write a markdown file at `{TASK_DIR}/research/<topic-slug>.md`. Use the File Format below.
### Step 5: Report to Main Agent
Reply with ONLY:
- List of files written (paths relative to repo root)
- One-line summary per file
- Any critical caveats that the main agent needs to know right now
Do NOT paste full research content into the reply. The files are the contract.
---
## Scope Limits (Strict)
### Write ALLOWED
- `{TASK_DIR}/research/*.md` — your own output
- Creating `{TASK_DIR}/research/` if it doesn't exist (via `mkdir -p`)
### Write FORBIDDEN
- Code files (`src/`, `lib/`, …)
- Spec files (`.trellis/spec/`) — main agent should use `update-spec` skill instead
- `.trellis/scripts/`, `.trellis/workflow.md`, platform config (`.claude/`, `.cursor/`, etc.)
- Other task directories
- Any git operation (commit / push / branch / merge)
If the user asks you to edit code, decline and suggest spawning `implement` instead.
---
## File Format
Each `{TASK_DIR}/research/<topic>.md` should follow:
```markdown
# Research: <topic>
- **Query**: <original query>
- **Scope**: <internal / external / mixed>
- **Date**: <YYYY-MM-DD>
## Findings
### Files Found
| File Path | Description |
|---|---|
| `src/services/xxx.ts` | Main implementation |
| `src/types/xxx.ts` | Type definitions |
### Code Patterns
<describe patterns, cite file:line>
### External References
- [Library X docs](url) — <why relevant, version constraints>
### Related Specs
- `.trellis/spec/xxx.md` — <description>
## Caveats / Not Found
<anything incomplete or uncertain>
```
---
## Guidelines
### DO
- Provide specific file paths and line numbers
- Quote actual code snippets
- Persist every topic to its own file
- Return file paths in your reply, not the full content
- Mark "not found" explicitly when searches come up empty
### DON'T
- Don't write code or modify files outside `{TASK_DIR}/research/`
- Don't guess uncertain info
- Don't paste full research text into the reply (files are the deliverable)
- Don't propose improvements or critique implementation (that's not your role)

View File

@@ -0,0 +1,56 @@
# Continue Current Task
Resume work on the current task — pick up at the right phase/step in `.trellis/workflow.md`.
---
## Step 1: Load Current Context
```bash
python ./.trellis/scripts/get_context.py
```
Confirms: current task, git state, recent commits.
## Step 2: Load the Phase Index
```bash
python ./.trellis/scripts/get_context.py --mode phase
```
Shows the Phase Index (Plan / Execute / Finish) with routing + skill mapping.
## Step 3: Decide Where You Are
`get_context.py` shows the active task's `status` field. Route by `status` + artifact presence. This command replaces the user needing to remember the Trellis flow; it does not itself approve implementation.
- `status=planning` + no `prd.md`**1.1** (load `trellis-brainstorm`)
- `status=planning` + `prd.md` only → decide whether the task is lightweight or complex. Lightweight can move to **1.4** review; complex returns to **1.1** to add `design.md` + `implement.md`.
- `status=planning` + complex artifacts complete + sub-agent jsonl not curated (only the seed `_example` row) → **1.3**
- `status=planning` + required artifacts complete + required jsonl curated or inline mode → **1.4** (ask for start review; only run `task.py start` after user confirms)
- `status=in_progress` + implementation not started → **2.1**
- `status=in_progress` + implementation done, not yet checked → **2.2**
- `status=in_progress` + check passed → **3.3** (spec update) → **3.4** (commit)
- `status=completed` (rare; usually archived immediately) → archive flow
Phase rules (full detail in `.trellis/workflow.md`):
1. Run steps **in order** within a phase — `[required]` steps must not be skipped
2. `[once]` steps are already done if the required output exists. `prd.md` alone can be enough only for lightweight tasks; complex tasks also need `design.md` and `implement.md`.
3. You may go back to an earlier phase if discoveries require it
## Step 4: Load the Specific Step
Once you know which step to resume at:
```bash
python ./.trellis/scripts/get_context.py --mode phase --step <X.X> --platform claude
```
Follow the loaded instructions. After each `[required]` step completes, move to the next.
---
## Reference
Full workflow and detailed phase steps live in `.trellis/workflow.md`. This command is only an entry point — the canonical guidance is there.

View File

@@ -0,0 +1,66 @@
# Finish Work
Wrap up the current session: archive the active task (and any other completed-but-unarchived tasks the user wants to clean up) and record the session journal. Code commits are NOT done here — those happen in workflow Phase 3.4 before you invoke this command.
## Step 1: Survey current state
```bash
python ./.trellis/scripts/get_context.py --mode record
```
This prints:
- **My active tasks** — review whether any besides the current one are actually done (code merged, AC met) and should be archived this round.
- **Git status** — quick visual on what's dirty.
- **Recent commits** — you'll need their hashes in Step 4 for `--commit`.
If `--mode record` surfaces other completed tasks not tied to the current session, surface them to the user with a one-shot confirmation: "These N tasks look done — archive them too in this round? [y/N]". Default is no; the current active task is always archived in Step 3 regardless.
## Step 2: Sanity check — classify dirty paths
Run:
```bash
git status --porcelain
```
Filter out paths under `.trellis/workspace/` and `.trellis/tasks/` — those are managed by `add_session.py` and `task.py archive` auto-commits and will appear dirty as part of this skill's own work.
For each remaining dirty path, decide whether it belongs to **the current task** or to **other parallel work** (e.g., another terminal window editing the same repo). Heuristics:
- Paths referenced in the current task's `prd.md` / `implement.jsonl` / `check.jsonl` → current task
- Paths in code areas matching the task's stated scope, or that you remember editing this session → current task
- Paths in unrelated areas you have no recollection of touching this session → other parallel work
Then route:
- **Any remaining path looks like current-task work** — bail out with:
> "Working tree has uncommitted code changes from this task: `<list>`. Return to workflow Phase 3.4 to commit them before running `/trellis:finish-work`."
Do NOT run `git commit` here. Do NOT prompt the user to commit. The user goes back to Phase 3.4 and the AI drives the batched commit there.
- **All remaining paths look unrelated** (other parallel-window work) — report them once and continue to Step 3:
> "FYI, dirty files outside this task's scope — leaving them for the other window: `<list>`."
- **Genuinely unsure** — ask the user once: "Are `<list>` this task's work I forgot to commit, or another window's? (commit / ignore)" — then route per their answer.
## Step 3: Archive task(s)
```bash
python ./.trellis/scripts/task.py archive <task-name>
```
At minimum: the current active task (if any). Plus any extra tasks the user confirmed in Step 1. Each archive produces a `chore(task): archive ...` commit via the script's auto-commit.
If there is no active task and the user did not confirm any cleanup archives, skip this step.
## Step 4: Record session journal
```bash
python ./.trellis/scripts/add_session.py \
--title "Session Title" \
--commit "hash1,hash2" \
--summary "Brief summary"
```
Use the work-commit hashes produced in Phase 3.4 (visible in Step 1's `Recent commits` list, or via `git log --oneline`) for `--commit`. Do not include the archive commit hashes from Step 3. This produces a `chore: record journal` commit.
Final git log order: `<work commits from 3.4>``chore(task): archive ...` (one or more) → `chore: record journal`.

View File

@@ -0,0 +1,771 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Multi-Platform Sub-Agent Context Injection Hook
Injects task-specific context when sub-agents (implement, check, research) are spawned.
Core Design Philosophy:
- Hook is responsible for injecting all context, subagent works autonomously with complete info
- Each agent has a dedicated jsonl file defining its context
- No resume needed, no segmentation, behavior controlled by code not prompt
Trigger: PreToolUse (before Task tool call)
Context Source: Trellis active task resolver points to task directory
- implement.jsonl - Implement agent dedicated context
- check.jsonl - Check agent dedicated context
- prd.md - Requirements document
- design.md - Technical design for complex tasks
- implement.md - Execution plan for complex tasks
- codex-review-output.txt - Code Review results
"""
from __future__ import annotations
# IMPORTANT: Suppress all warnings FIRST
import warnings
warnings.filterwarnings("ignore")
import json
import os
import sys
from pathlib import Path
from typing import Any
# IMPORTANT: Force stdout to use UTF-8 on Windows
# This fixes UnicodeEncodeError when outputting non-ASCII characters
if sys.platform.startswith("win"):
import io as _io
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
elif hasattr(sys.stdout, "detach"):
sys.stdout = _io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8", errors="replace") # type: ignore[union-attr]
# =============================================================================
# Path Constants (change here to rename directories)
# =============================================================================
DIR_WORKFLOW = ".trellis"
DIR_SPEC = "spec"
FILE_TASK_JSON = "task.json"
# =============================================================================
# Subagent Constants (change here to rename subagent types)
# =============================================================================
AGENT_IMPLEMENT = "trellis-implement"
AGENT_CHECK = "trellis-check"
AGENT_RESEARCH = "trellis-research"
# Agents that require a task directory
AGENTS_REQUIRE_TASK = (AGENT_IMPLEMENT, AGENT_CHECK)
# All supported agents
AGENTS_ALL = (AGENT_IMPLEMENT, AGENT_CHECK, AGENT_RESEARCH)
def find_repo_root(start_path: str) -> str | None:
"""
Find git repo root from start_path upwards
Returns:
Repo root path, or None if not found
"""
current = Path(start_path).resolve()
while current != current.parent:
if (current / ".git").exists():
return str(current)
current = current.parent
return None
def _detect_platform(input_data: dict) -> str | None:
if isinstance(input_data.get("cursor_version"), str):
return "cursor"
env_map = {
"CLAUDE_PROJECT_DIR": "claude",
"CURSOR_PROJECT_DIR": "cursor",
"CODEBUDDY_PROJECT_DIR": "codebuddy",
"FACTORY_PROJECT_DIR": "droid",
"GEMINI_PROJECT_DIR": "gemini",
"QODER_PROJECT_DIR": "qoder",
"KIRO_PROJECT_DIR": "kiro",
"COPILOT_PROJECT_DIR": "copilot",
}
for env_name, platform in env_map.items():
if os.environ.get(env_name):
return platform
script_parts = set(Path(sys.argv[0]).parts)
if ".claude" in script_parts:
return "claude"
if ".cursor" in script_parts:
return "cursor"
if ".gemini" in script_parts:
return "gemini"
if ".qoder" in script_parts:
return "qoder"
if ".codebuddy" in script_parts:
return "codebuddy"
if ".factory" in script_parts:
return "droid"
if ".kiro" in script_parts:
return "kiro"
return None
def get_current_task(repo_root: str, input_data: dict) -> str | None:
"""Resolve current task directory through the unified active task resolver."""
scripts_dir = Path(repo_root) / DIR_WORKFLOW / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
try:
from common.active_task import resolve_active_task # type: ignore[import-not-found]
except Exception:
return None
active = resolve_active_task(
Path(repo_root),
input_data,
platform=_detect_platform(input_data),
)
return active.task_path
def read_file_content(base_path: str, file_path: str) -> str | None:
"""Read file content, return None if file doesn't exist"""
full_path = os.path.join(base_path, file_path)
if os.path.exists(full_path) and os.path.isfile(full_path):
try:
with open(full_path, "r", encoding="utf-8") as f:
return f.read()
except Exception:
return None
return None
def read_directory_contents(
base_path: str, dir_path: str, max_files: int = 20
) -> list[tuple[str, str]]:
"""
Read all .md files in a directory
Args:
base_path: Base path (usually repo_root)
dir_path: Directory relative path
max_files: Max files to read (prevent huge directories)
Returns:
[(file_path, content), ...]
"""
full_path = os.path.join(base_path, dir_path)
if not os.path.exists(full_path) or not os.path.isdir(full_path):
return []
results = []
try:
# Only read .md files, sorted by filename
md_files = sorted(
[
f
for f in os.listdir(full_path)
if f.endswith(".md") and os.path.isfile(os.path.join(full_path, f))
]
)
for filename in md_files[:max_files]:
file_full_path = os.path.join(full_path, filename)
relative_path = os.path.join(dir_path, filename)
try:
with open(file_full_path, "r", encoding="utf-8") as f:
content = f.read()
results.append((relative_path, content))
except Exception:
continue
except Exception:
pass
return results
def read_jsonl_entries(base_path: str, jsonl_path: str) -> list[tuple[str, str]]:
"""
Read all file/directory contents referenced in jsonl file
Schema:
{"file": "path/to/file.md", "reason": "..."}
{"file": "path/to/dir/", "type": "directory", "reason": "..."}
{"_example": "..."} # seed row — skipped (no `file` field)
Rows without a ``file`` field (e.g. the self-describing seed line written
by ``task.py create`` before the agent has curated entries) are skipped
silently. If the resulting entry list is empty, a stderr warning is
emitted so the operator can debug missing context.
Returns:
[(path, content), ...]
"""
full_path = os.path.join(base_path, jsonl_path)
if not os.path.exists(full_path):
print(
f"[inject-subagent-context] WARN: {jsonl_path} not found — "
f"sub-agent will receive only task artifacts",
file=sys.stderr,
)
return []
results = []
saw_real_entry = False
try:
with open(full_path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
item = json.loads(line)
file_path = item.get("file") or item.get("path")
entry_type = item.get("type", "file")
if not file_path:
# Seed / comment row — skip silently
continue
saw_real_entry = True
if entry_type == "directory":
# Read all .md files in directory
dir_contents = read_directory_contents(base_path, file_path)
results.extend(dir_contents)
else:
# Read single file
content = read_file_content(base_path, file_path)
if content:
results.append((file_path, content))
except json.JSONDecodeError:
continue
except Exception:
pass
if not saw_real_entry:
print(
f"[inject-subagent-context] WARN: {jsonl_path} has no curated "
f"entries (only seed / empty) — sub-agent will receive only "
f"task artifacts. See workflow.md planning artifact guidance.",
file=sys.stderr,
)
return results
def get_agent_context(repo_root: str, task_dir: str, agent_type: str) -> str:
"""
Get context from {agent_type}.jsonl for the specified agent.
Only reads implement.jsonl or check.jsonl (the two JSONL files the task system creates).
"""
context_parts = []
agent_jsonl = f"{task_dir}/{agent_type}.jsonl"
for file_path, content in read_jsonl_entries(repo_root, agent_jsonl):
context_parts.append(f"=== {file_path} ===\n{content}")
return "\n\n".join(context_parts)
def get_implement_context(repo_root: str, task_dir: str) -> str:
"""
Complete context for Implement Agent
Read order:
1. All files in implement.jsonl (spec/research manifests)
2. prd.md (requirements)
3. design.md if present (technical design)
4. implement.md if present (execution plan)
"""
context_parts = []
# 1. Read implement.jsonl
base_context = get_agent_context(repo_root, task_dir, "implement")
if base_context:
context_parts.append(base_context)
# 2. Requirements document
prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
if prd_content:
context_parts.append(f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}")
# 3. Technical design for complex tasks
design_content = read_file_content(repo_root, f"{task_dir}/design.md")
if design_content:
context_parts.append(
f"=== {task_dir}/design.md (Technical Design) ===\n{design_content}"
)
# 4. Execution plan for complex tasks
implement_plan_content = read_file_content(repo_root, f"{task_dir}/implement.md")
if implement_plan_content:
context_parts.append(
f"=== {task_dir}/implement.md (Execution Plan) ===\n{implement_plan_content}"
)
return "\n\n".join(context_parts)
def get_check_context(repo_root: str, task_dir: str) -> str:
"""
Context for Check Agent: check.jsonl + task artifacts.
"""
context_parts = []
for file_path, content in read_jsonl_entries(repo_root, f"{task_dir}/check.jsonl"):
context_parts.append(f"=== {file_path} ===\n{content}")
prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
if prd_content:
context_parts.append(f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}")
design_content = read_file_content(repo_root, f"{task_dir}/design.md")
if design_content:
context_parts.append(
f"=== {task_dir}/design.md (Technical Design) ===\n{design_content}"
)
implement_plan_content = read_file_content(repo_root, f"{task_dir}/implement.md")
if implement_plan_content:
context_parts.append(
f"=== {task_dir}/implement.md (Execution Plan) ===\n{implement_plan_content}"
)
return "\n\n".join(context_parts)
def get_finish_context(repo_root: str, task_dir: str) -> str:
"""
Context for Finish phase: reuses check.jsonl + prd.md
(Finish is a final check, same context source.)
"""
return get_check_context(repo_root, task_dir)
def build_implement_prompt(original_prompt: str, context: str) -> str:
"""Build complete prompt for Implement"""
return f"""<!-- trellis-hook-injected -->
# Implement Agent Task
You are the Implement Agent in the Multi-Agent Pipeline.
## Your Context
All the information you need has been prepared for you:
{context}
---
## Your Task
{original_prompt}
---
## Workflow
1. **Understand specs** - All dev specs are injected above, understand them
2. **Understand task artifacts** - Read requirements, technical design if present, and execution plan if present
3. **Implement feature** - Implement following specs and task artifacts
4. **Self-check** - Ensure code quality against check specs
## Important Constraints
- Do NOT execute git commit, only code modifications
- Follow all dev specs injected above
- Report list of modified/created files when done"""
def build_check_prompt(original_prompt: str, context: str) -> str:
"""Build complete prompt for Check"""
return f"""<!-- trellis-hook-injected -->
# Check Agent Task
You are the Check Agent in the Multi-Agent Pipeline (code and cross-layer checker).
## Your Context
All check specs and dev specs you need:
{context}
---
## Your Task
{original_prompt}
---
## Workflow
1. **Get changes** - Run `git diff --name-only` and `git diff` to get code changes
2. **Check against specs** - Check item by item against specs above
3. **Self-fix** - Fix issues directly, don't just report
4. **Run verification** - Run project's lint and typecheck commands
## Important Constraints
- Fix issues yourself, don't just report
- Must execute complete checklist in check specs
- Pay special attention to impact radius analysis (L1-L5)"""
def build_finish_prompt(original_prompt: str, context: str) -> str:
"""Build complete prompt for Finish (final check before PR)"""
return f"""<!-- trellis-hook-injected -->
# Finish Agent Task
You are performing the final check before creating a PR.
## Your Context
Finish checklist and requirements:
{context}
---
## Your Task
{original_prompt}
---
## Workflow
1. **Review changes** - Run `git diff --name-only` to see all changed files
2. **Verify task artifacts** - Check requirements in prd.md and, when present, design.md / implement.md
3. **Spec sync** - Analyze whether changes introduce new patterns, contracts, or conventions
- If new pattern/convention found: read target spec file → update it → update index.md if needed
- If infra/cross-layer change: follow the 7-section mandatory template from update-spec.md
- If pure code fix with no new patterns: skip this step
4. **Run final checks** - Execute lint and typecheck
5. **Confirm ready** - Ensure code is ready for PR
## Important Constraints
- You MAY update spec files when gaps are detected (use update-spec.md as guide)
- MUST read the target spec file BEFORE editing (avoid duplicating existing content)
- Do NOT update specs for trivial changes (typos, formatting, obvious fixes)
- If critical CODE issues found, report them clearly (fix specs, not code)
- Verify all acceptance criteria in prd.md are met
- Verify design.md and implement.md constraints when those files are present"""
def get_research_context(repo_root: str, task_dir: str | None) -> str:
"""
Context for Research Agent — project structure overview for spec directories.
`task_dir` kept for signature parity with get_implement_context / get_check_context
so the dispatcher can call them uniformly.
"""
_ = task_dir
context_parts = []
# 1. Project structure overview (dynamically discover spec directories)
spec_path = f"{DIR_WORKFLOW}/{DIR_SPEC}"
spec_root = Path(repo_root) / DIR_WORKFLOW / DIR_SPEC
# Build spec tree dynamically
tree_lines = [f"{spec_path}/"]
if spec_root.is_dir():
pkg_dirs = sorted(d for d in spec_root.iterdir() if d.is_dir())
for i, pkg_dir in enumerate(pkg_dirs):
is_last = i == len(pkg_dirs) - 1
prefix = "└── " if is_last else "├── "
layers = sorted(d.name for d in pkg_dir.iterdir() if d.is_dir())
layer_info = f" ({', '.join(layers)})" if layers else ""
tree_lines.append(f"{prefix}{pkg_dir.name}/{layer_info}")
spec_tree = "\n".join(tree_lines)
project_structure = f"""## Project Spec Directory Structure
```
{spec_tree}
```
To get structured package info, run: `python ./{DIR_WORKFLOW}/scripts/get_context.py --mode packages`
## Search Tips
- Spec files: `{spec_path}/**/*.md`
- Code search: Use Glob and Grep tools
- Tech solutions: Use mcp__exa__web_search_exa or mcp__exa__get_code_context_exa"""
context_parts.append(project_structure)
return "\n\n".join(context_parts)
def build_research_prompt(original_prompt: str, context: str) -> str:
"""Build complete prompt for Research"""
return f"""# Research Agent Task
You are the Research Agent in the Multi-Agent Pipeline (search researcher).
## Core Principle
**You do one thing: find and explain information.**
You are a documenter, not a reviewer.
## Project Info
{context}
---
## Your Task
{original_prompt}
---
## Workflow
1. **Understand query** - Determine search type (internal/external) and scope
2. **Plan search** - List search steps for complex queries
3. **Execute search** - Execute multiple independent searches in parallel
4. **Organize results** - Output structured report
## Search Tools
| Tool | Purpose |
|------|---------|
| Glob | Search by filename pattern |
| Grep | Search by content |
| Read | Read file content |
| mcp__exa__web_search_exa | External web search |
| mcp__exa__get_code_context_exa | External code/doc search |
## Strict Boundaries
**Only allowed**: Describe what exists, where it is, how it works
**Forbidden** (unless explicitly asked):
- Suggest improvements
- Criticize implementation
- Recommend refactoring
- Modify any files
## Report Format
Provide structured search results including:
- List of files found (with paths)
- Code pattern analysis (if applicable)
- Related spec documents
- External references (if any)"""
def _string_value(value: Any) -> str:
if isinstance(value, str):
stripped = value.strip()
return stripped
return ""
def _extract_subagent_name(value: Any) -> str:
"""Extract a sub-agent name from common platform encodings.
Cursor's native Task args encode custom sub-agents as a protobuf oneof,
which can appear in hook JSON as either ``{"custom": {"name": "..."}}``
or ``{"type": {"case": "custom", "value": {"name": "..."}}}``.
"""
direct = _string_value(value)
if direct:
return direct
if not isinstance(value, dict):
return ""
for key in ("name", "subagent_type_name", "subagentTypeName"):
direct = _string_value(value.get(key))
if direct:
return direct
custom = value.get("custom")
if isinstance(custom, dict):
custom_name = _string_value(custom.get("name"))
if custom_name:
return custom_name
oneof = value.get("type")
if isinstance(oneof, dict):
case_name = _string_value(oneof.get("case"))
if case_name == "custom":
nested_value = oneof.get("value")
if isinstance(nested_value, dict):
custom_name = _string_value(nested_value.get("name"))
if custom_name:
return custom_name
if case_name:
return case_name
case_name = _string_value(value.get("case"))
if case_name == "custom":
nested_value = value.get("value")
if isinstance(nested_value, dict):
custom_name = _string_value(nested_value.get("name"))
if custom_name:
return custom_name
if case_name:
return case_name
for agent_name in AGENTS_ALL:
if agent_name in value:
return agent_name
return ""
def _extract_subagent_type(tool_input: dict) -> str:
for key in (
"subagent_type",
"subagentType",
"subagent_type_name",
"subagentTypeName",
"agent_type",
"agentType",
"name",
):
agent_name = _extract_subagent_name(tool_input.get(key))
if agent_name:
return agent_name
return ""
def _parse_hook_input(input_data: dict) -> tuple[str, str, dict]:
"""Parse hook input across different platform formats.
Returns (subagent_type, original_prompt, tool_input).
Handles:
- Claude Code / Qoder / CodeBuddy / Droid: tool_name=Task|Agent, tool_input.subagent_type
- Cursor: tool_name=Task|Subagent, tool_input.subagent_type
- Copilot CLI: toolName=task (camelCase key, lowercase value)
- Gemini CLI: tool_name IS the agent name (BeforeTool matcher already filtered)
- Kiro: agentSpawn hook, agent_name field at top level
"""
tool_input = input_data.get("tool_input", {})
# Standard format: Task/Agent tool with subagent_type
tool_name = input_data.get("tool_name", "") or input_data.get("toolName", "")
if tool_name.lower() in ("task", "agent", "subagent"):
return (
_extract_subagent_type(tool_input),
tool_input.get("prompt", ""),
tool_input,
)
# Kiro: agentSpawn hook passes agent_name at top level
agent_name = input_data.get("agent_name", "")
if agent_name:
return agent_name, tool_input.get("prompt", input_data.get("prompt", "")), tool_input
# Gemini CLI: BeforeTool where tool_name IS the agent name
# (matcher already ensured it's one of our agents)
if tool_name in AGENTS_ALL:
return tool_name, tool_input.get("prompt", ""), tool_input
# Copilot CLI: toolName field (camelCase), value might be the agent name
tool_name_camel = input_data.get("toolName", "")
if tool_name_camel in AGENTS_ALL:
return tool_name_camel, input_data.get("toolArgs", ""), tool_input
return "", "", tool_input
def main():
if os.environ.get("TRELLIS_HOOKS") == "0" or os.environ.get("TRELLIS_DISABLE_HOOKS") == "1":
sys.exit(0)
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError:
sys.exit(0)
subagent_type, original_prompt, tool_input = _parse_hook_input(input_data)
cwd = input_data.get("cwd", os.getcwd())
# Only handle subagent types we care about
if subagent_type not in AGENTS_ALL:
sys.exit(0)
# Find repo root
repo_root = find_repo_root(cwd)
if not repo_root:
sys.exit(0)
# Get current task directory (research doesn't require it)
task_dir = get_current_task(repo_root, input_data)
# implement/check need task directory
if subagent_type in AGENTS_REQUIRE_TASK:
if not task_dir:
sys.exit(0)
# Check if task directory exists
task_dir_full = os.path.join(repo_root, task_dir)
if not os.path.exists(task_dir_full):
sys.exit(0)
# Check for [finish] marker in prompt (check agent with finish context)
is_finish_phase = "[finish]" in original_prompt.lower()
# Get context and build prompt based on subagent type
if subagent_type == AGENT_IMPLEMENT:
assert task_dir is not None # validated above
context = get_implement_context(repo_root, task_dir)
new_prompt = build_implement_prompt(original_prompt, context)
elif subagent_type == AGENT_CHECK:
assert task_dir is not None # validated above
if is_finish_phase:
# Finish phase: use finish context (lighter, focused on final verification)
context = get_finish_context(repo_root, task_dir)
new_prompt = build_finish_prompt(original_prompt, context)
else:
# Regular check phase: use check context (full specs for self-fix loop)
context = get_check_context(repo_root, task_dir)
new_prompt = build_check_prompt(original_prompt, context)
elif subagent_type == AGENT_RESEARCH:
# Research can work without task directory
context = get_research_context(repo_root, task_dir)
new_prompt = build_research_prompt(original_prompt, context)
else:
sys.exit(0)
if not context:
sys.exit(0)
# Return updated input — use a multi-format output that covers all platforms.
# Most platforms ignore unrecognized fields, so we include multiple formats.
# The platform picks whichever fields it understands.
updated = {**tool_input, "prompt": new_prompt}
output = {
# Claude Code / Qoder / CodeBuddy / Droid format
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": updated,
},
# Cursor format
"permission": "allow",
"updated_input": updated,
# Gemini format
"updatedInput": updated,
}
print(json.dumps(output, ensure_ascii=False))
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,408 @@
#!/usr/bin/env python3
"""Trellis per-turn breadcrumb hook (UserPromptSubmit / BeforeAgent equivalent).
Runs on every user prompt. Resolves the active task through Trellis'
session-aware active task resolver and emits a short <workflow-state>
block reminding the main AI what task is active and its expected flow.
The emitted ``hookEventName`` field is platform-aware: most hosts expect
``UserPromptSubmit`` (Claude Code naming, also accepted by Cursor / Qoder /
CodeBuddy / Droid / Codex / Copilot wiring), but Gemini CLI 0.40.x renamed
its per-turn event to ``BeforeAgent`` and its schema validator rejects the
legacy name. ``_detect_platform`` picks the right value at runtime.
Breadcrumb text is pulled exclusively from workflow.md
[workflow-state:STATUS] tag blocks — workflow.md is the single source of
truth. There are no fallback dicts in this script: when workflow.md is
missing or a tag is absent, the breadcrumb degrades to a generic
"Refer to workflow.md for current step." line so users see (and fix)
the broken state instead of the hook silently masking it.
Shared across all hook-capable platforms (Claude, Cursor, Codex, Qoder,
CodeBuddy, Droid, Gemini, Copilot, Kiro). Kiro wires this via the CLI
custom agent's ``hooks.userPromptSubmit`` and the IDE ``.kiro.hook``
``promptSubmit`` event; its output branch emits a plain-text breadcrumb
(Kiro adds hook stdout directly to the conversation context). Written to
each platform's hooks directory via writeSharedHooks() at init time.
Silent exit 0 cases (no output):
- No .trellis/ directory found (not a Trellis project)
- task.json malformed or missing status
"""
from __future__ import annotations
import json
import os
import re
import sys
import queue
import threading
from pathlib import Path
# Force UTF-8 on stdin/stdout/stderr on Windows. Default codepage there is
# cp936 / cp1252 / etc. — non-ASCII content (Chinese task names, prd snippets)
# both in stdin (hook payload from host CLI) and stdout (our emitted blocks)
# raises UnicodeDecodeError / UnicodeEncodeError. Equivalent to `python -X utf8`
# but applied per-stream so we don't depend on host CLI's command wiring.
if sys.platform.startswith("win"):
import io as _io
for _stream_name in ("stdin", "stdout", "stderr"):
_stream = getattr(sys, _stream_name, None)
if _stream is None:
continue
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
except Exception:
pass
elif hasattr(_stream, "detach"):
try:
setattr(sys, _stream_name, _io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"))
except Exception:
pass
from typing import Optional
# Bootstrap notice for Codex while the session has no active task. Codex does not
# get the full SessionStart overview; this short reminder points the main session
# at the start skill once and leaves the per-turn state block compact.
CODEX_NO_TASK_BOOTSTRAP_NOTICE = """<trellis-bootstrap>
If you have not already loaded Trellis context this session, read the `trellis-start` skill once.
</trellis-bootstrap>"""
# ---------------------------------------------------------------------------
# CWD-robust Trellis root discovery (fixes hook-path-robustness for this hook)
# ---------------------------------------------------------------------------
def find_trellis_root(start: Path) -> Optional[Path]:
"""Walk up from start to find directory containing .trellis/.
Handles CWD drift: subdirectory launches, monorepo packages, etc.
Returns None if no .trellis/ found (silent no-op).
"""
cur = start.resolve()
while cur != cur.parent:
if (cur / ".trellis").is_dir():
return cur
cur = cur.parent
return None
# ---------------------------------------------------------------------------
# Active task discovery
# ---------------------------------------------------------------------------
def _detect_platform(input_data: dict) -> str | None:
if isinstance(input_data.get("cursor_version"), str):
return "cursor"
env_map = {
"CLAUDE_PROJECT_DIR": "claude",
"CURSOR_PROJECT_DIR": "cursor",
"CODEBUDDY_PROJECT_DIR": "codebuddy",
"FACTORY_PROJECT_DIR": "droid",
"GEMINI_PROJECT_DIR": "gemini",
"QODER_PROJECT_DIR": "qoder",
"KIRO_PROJECT_DIR": "kiro",
"COPILOT_PROJECT_DIR": "copilot",
"TRAE_PROJECT_DIR": "trae",
}
for env_name, platform in env_map.items():
if os.environ.get(env_name):
return platform
script_parts = set(Path(sys.argv[0]).parts)
if ".claude" in script_parts:
return "claude"
if ".cursor" in script_parts:
return "cursor"
if ".codex" in script_parts:
return "codex"
if ".gemini" in script_parts:
return "gemini"
if ".qoder" in script_parts:
return "qoder"
if ".codebuddy" in script_parts:
return "codebuddy"
if ".factory" in script_parts:
return "droid"
if ".kiro" in script_parts:
return "kiro"
if ".trae" in script_parts:
return "trae"
return None
def _resolve_active_task(root: Path, input_data: dict):
scripts_dir = root / ".trellis" / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
from common.active_task import resolve_active_task # type: ignore[import-not-found]
return resolve_active_task(root, input_data, platform=_detect_platform(input_data))
def get_active_task(root: Path, input_data: dict) -> Optional[tuple[str, str, str]]:
"""Return (task_id, status, source) from the current active task."""
active = _resolve_active_task(root, input_data)
if not active.task_path:
return None
task_dir = Path(active.task_path)
if not task_dir.is_absolute():
task_dir = root / task_dir
if active.stale:
return task_dir.name, f"stale_{active.source_type}", active.source
task_json = task_dir / "task.json"
if not task_json.is_file():
return None
try:
data = json.loads(task_json.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return None
task_id = data.get("id") or task_dir.name
status = data.get("status", "")
if not isinstance(status, str) or not status:
return None
return task_id, status, active.source
# ---------------------------------------------------------------------------
# Breadcrumb loading: parse workflow.md, fall back to hardcoded defaults
# ---------------------------------------------------------------------------
# Supports STATUS values with letters, digits, underscores, hyphens
# (so "in-review" / "blocked-by-team" work alongside "in_progress").
_TAG_RE = re.compile(
r"\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n(.*?)\n\s*\[/workflow-state:\1\]",
re.DOTALL,
)
def load_breadcrumbs(root: Path) -> dict[str, str]:
"""Parse workflow.md for [workflow-state:STATUS] blocks.
Returns {status: body_text}. workflow.md is the single source of
truth — there are no fallback dicts in this script. Missing tags
(or a missing/unreadable workflow.md) fall back to a generic line
in build_breadcrumb so users see the broken state and fix
workflow.md, rather than the hook silently masking the issue.
"""
workflow = root / ".trellis" / "workflow.md"
if not workflow.is_file():
return {}
try:
content = workflow.read_text(encoding="utf-8")
except OSError:
return {}
result: dict[str, str] = {}
for match in _TAG_RE.finditer(content):
status = match.group(1)
body = match.group(2).strip()
if body:
result[status] = body
return result
def _read_trellis_config(root: Path) -> dict:
"""Load .trellis/config.yaml via the bundled trellis_config helper.
The helper lives in .trellis/scripts/common; the hook lives outside the
scripts tree, so we extend sys.path before importing.
"""
scripts_dir = root / ".trellis" / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
try:
from common.trellis_config import read_trellis_config # type: ignore[import-not-found]
except Exception:
return {}
try:
return read_trellis_config(root)
except Exception:
return {}
def _codex_mode_banner(config: dict) -> str:
"""Emit a `<codex-mode>` banner for the additionalContext payload.
Reads `codex.dispatch_mode` from .trellis/config.yaml; defaults to
`inline` when missing or invalid because Codex sub-agents run with
`fork_turns="none"` isolation and can't inherit the parent session's
task context. The banner makes the active mode explicit to Codex AI
per turn, complementing the workflow-state body which is per-status.
Mode tells AI which dispatch protocol to follow; workflow-state tells
AI what step it's at.
"""
mode = "inline"
if isinstance(config, dict):
codex_cfg = config.get("codex")
if isinstance(codex_cfg, dict):
cfg_mode = codex_cfg.get("dispatch_mode")
if cfg_mode in ("inline", "sub-agent"):
mode = cfg_mode
if mode == "sub-agent":
meaning = (
"sub-agent: implement/check work defaults to Trellis sub-agents; "
"the main session still coordinates, clarifies, updates specs, commits, and finishes."
)
else:
meaning = (
"inline: the main session implements/checks directly; "
"do not dispatch implement/check sub-agents."
)
return f"<codex-mode>{meaning}</codex-mode>"
def resolve_breadcrumb_key(
status: str, platform: str | None, config: dict
) -> str:
"""Pick the breadcrumb tag key based on Codex dispatch_mode.
Codex defaults to ``inline`` because sub-agents run with ``fork_turns="none"``
isolation and can't inherit the parent session's task context. Users can
opt into ``codex.dispatch_mode: sub-agent`` in ``.trellis/config.yaml``
to use the parallel ``<status>-inline`` tag → ``<status>`` flip. Invalid
or missing values fall back to inline.
Non-codex platforms return the plain status unchanged.
"""
if platform == "codex":
mode = "inline"
if isinstance(config, dict):
codex_cfg = config.get("codex")
if isinstance(codex_cfg, dict):
cfg_mode = codex_cfg.get("dispatch_mode")
if cfg_mode in ("inline", "sub-agent"):
mode = cfg_mode
return f"{status}-inline" if mode == "inline" else status
return status
def build_breadcrumb(
task_id: Optional[str],
status: str,
templates: dict[str, str],
source: str | None = None,
breadcrumb_key: str | None = None,
) -> str:
"""Build the <workflow-state>...</workflow-state> block.
- Known status (tag present in workflow.md) → detailed template body
- Unknown status (no tag, or workflow.md missing) → generic
"Refer to workflow.md for current step." line
- `no_task` pseudo-status (task_id is None) → header omits task info
"""
lookup_key = breadcrumb_key or status
body = templates.get(lookup_key)
if body is None and lookup_key != status:
body = templates.get(status)
if body is None:
body = "Refer to workflow.md for current step."
header = f"Status: {status}" if task_id is None else f"Task: {task_id} ({status})"
return f"<workflow-state>\n{header}\n{body}\n</workflow-state>"
# ---------------------------------------------------------------------------
# Entry
# ---------------------------------------------------------------------------
def _load_hook_input() -> dict:
"""Read hook JSON without trusting host runners to close stdin.
Kiro IDE `runCommand` and similar hook runners can leave stdin open while
sending no payload. A plain `json.load(sys.stdin)` then blocks forever.
Normal hook runners write the complete JSON payload and close stdin, so the
short daemon read preserves that path while failing closed to `{}` for
non-piping hosts.
"""
result_queue: "queue.Queue[str | BaseException]" = queue.Queue(maxsize=1)
def _read() -> None:
try:
result_queue.put(sys.stdin.read())
except BaseException as exc:
result_queue.put(exc)
reader = threading.Thread(target=_read, daemon=True)
reader.start()
try:
raw = result_queue.get(timeout=0.2)
except queue.Empty:
return {}
if isinstance(raw, BaseException):
return {}
try:
data = json.loads(raw) if raw.strip() else {}
except (json.JSONDecodeError, ValueError):
return {}
return data if isinstance(data, dict) else {}
def main() -> int:
if os.environ.get("TRELLIS_HOOKS") == "0" or os.environ.get("TRELLIS_DISABLE_HOOKS") == "1":
return 0
data = _load_hook_input()
cwd_str = data.get("cwd") or os.getcwd()
cwd = Path(cwd_str)
root = find_trellis_root(cwd)
if root is None:
return 0 # not a Trellis project
templates = load_breadcrumbs(root)
platform = _detect_platform(data)
config = _read_trellis_config(root)
task = get_active_task(root, data)
if task is None:
# No active task — still emit a breadcrumb nudging AI toward
# trellis-brainstorm + task.py create when user describes real work.
no_task_key = resolve_breadcrumb_key("no_task", platform, config)
breadcrumb = build_breadcrumb(
None, "no_task", templates, breadcrumb_key=no_task_key
)
else:
task_id, status, source = task
status_key = resolve_breadcrumb_key(status, platform, config)
source_for_breadcrumb = None if platform == "codex" else source
breadcrumb = build_breadcrumb(
task_id, status, templates, source_for_breadcrumb, breadcrumb_key=status_key
)
if platform == "codex":
parts: list[str] = []
if task is None:
parts.append(CODEX_NO_TASK_BOOTSTRAP_NOTICE)
parts.append(_codex_mode_banner(config))
parts.append(breadcrumb)
breadcrumb = "\n\n".join(parts)
# Kiro (CLI userPromptSubmit / IDE promptSubmit) adds a hook's stdout
# directly to the conversation context — no JSON envelope. Emit the bare
# breadcrumb text. Conditionally isolated: all other platforms keep the
# hookSpecificOutput JSON path below unchanged.
if platform == "kiro":
print(breadcrumb)
return 0
# Gemini CLI 0.40.x rejects "UserPromptSubmit" — its per-turn event is
# named "BeforeAgent". Other platforms (Claude/Cursor/Qoder/CodeBuddy/
# Droid/Codex/Copilot) accept the original Claude-style name.
hook_event_name = (
"BeforeAgent" if platform == "gemini" else "UserPromptSubmit"
)
output = {
"hookSpecificOutput": {
"hookEventName": hook_event_name,
"additionalContext": breadcrumb,
}
}
print(json.dumps(output))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,844 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Session Start Hook - Inject structured context
"""
from __future__ import annotations
# IMPORTANT: Suppress all warnings FIRST
import warnings
warnings.filterwarnings("ignore")
import json
import os
import re
import shlex
import subprocess
import sys
from io import StringIO
from pathlib import Path
def _normalize_windows_shell_path(path_str: str) -> str:
"""Normalize Unix-style shell paths to real Windows paths.
On Windows, shells like Git Bash / MSYS2 / Cygwin may report paths like
`/d/Users/...` or `/cygdrive/d/Users/...`. `Path.resolve()` will misinterpret
these as `D:/d/Users...` on drive D: (or similar), breaking repo root
detection.
This function is intentionally conservative: it only rewrites patterns that
unambiguously represent a drive letter mount.
"""
if not isinstance(path_str, str) or not path_str:
return path_str
# Only relevant on Windows; keep other platforms untouched.
if not sys.platform.startswith("win"):
return path_str
p = path_str.strip()
# Already a Windows drive path (C:\... or C:/...)
if re.match(r"^[A-Za-z]:[\/]", p):
return p
# MSYS/Git-Bash style: /c/Users/... or /d/Work/...
m = re.match(r"^/([A-Za-z])/(.*)", p)
if m:
drive, rest = m.group(1).upper(), m.group(2)
rest = rest.replace('/', '\\')
return f"{drive}:\\{rest}"
# Cygwin style: /cygdrive/c/Users/...
m = re.match(r"^/cygdrive/([A-Za-z])/(.*)", p)
if m:
drive, rest = m.group(1).upper(), m.group(2)
rest = rest.replace('/', '\\')
return f"{drive}:\\{rest}"
# WSL mounted drive (sometimes leaked into env): /mnt/c/Users/...
m = re.match(r"^/mnt/([A-Za-z])/(.*)", p)
if m:
drive, rest = m.group(1).upper(), m.group(2)
rest = rest.replace('/', '\\')
return f"{drive}:\\{rest}"
return path_str
FIRST_REPLY_NOTICE = """<first-reply-notice>
First visible reply: say once in Chinese that Trellis SessionStart context is loaded, then answer directly.
This notice is one-shot: do not repeat it after the first assistant reply in the same session.
</first-reply-notice>"""
# Force UTF-8 on stdin/stdout/stderr on Windows. Default codepage there is
# cp936 / cp1252 / etc. — non-ASCII content (Chinese task names, prd snippets)
# both in stdin (hook payload from host CLI) and stdout (our emitted blocks)
# raises UnicodeDecodeError / UnicodeEncodeError. Equivalent to `python -X utf8`
# but applied per-stream so we don't depend on host CLI's command wiring.
if sys.platform.startswith("win"):
import io as _io
for _stream_name in ("stdin", "stdout", "stderr"):
_stream = getattr(sys, _stream_name, None)
if _stream is None:
continue
if hasattr(_stream, "reconfigure"):
try:
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
except Exception:
pass
elif hasattr(_stream, "detach"):
try:
setattr(sys, _stream_name, _io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"))
except Exception:
pass
def _has_curated_jsonl_entry(jsonl_path: Path) -> bool:
"""Return True iff jsonl has at least one row with a ``file`` field.
A freshly seeded jsonl only contains a ``{"_example": ...}`` row (no
``file`` key) — that is NOT "ready". Readiness requires at least one
curated entry. Matches the contract used by hook-inject and pull-based
sub-agent context loaders.
"""
try:
for line in jsonl_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
row = json.loads(line)
except json.JSONDecodeError:
continue
if isinstance(row, dict) and row.get("file"):
return True
except (OSError, UnicodeDecodeError):
return False
return False
def should_skip_injection() -> bool:
"""Check if any platform's non-interactive flag is set, or if Trellis
hooks are explicitly disabled via TRELLIS_HOOKS=0 / TRELLIS_DISABLE_HOOKS=1.
"""
if os.environ.get("TRELLIS_HOOKS") == "0":
return True
if os.environ.get("TRELLIS_DISABLE_HOOKS") == "1":
return True
non_interactive_vars = [
"CLAUDE_NON_INTERACTIVE",
"QODER_NON_INTERACTIVE",
"CODEBUDDY_NON_INTERACTIVE",
"FACTORY_NON_INTERACTIVE",
"CURSOR_NON_INTERACTIVE",
"GEMINI_NON_INTERACTIVE",
"KIRO_NON_INTERACTIVE",
"COPILOT_NON_INTERACTIVE",
"TRAE_NON_INTERACTIVE",
]
return any(os.environ.get(var) == "1" for var in non_interactive_vars)
def read_file(path: Path, fallback: str = "") -> str:
try:
return path.read_text(encoding="utf-8")
except (FileNotFoundError, PermissionError):
return fallback
def _repo_relative(repo_root: Path, path: Path) -> str:
try:
return path.relative_to(repo_root).as_posix()
except ValueError:
return str(path)
def _run_git(repo_root: Path, args: list[str]) -> str:
try:
result = subprocess.run(
["git", *args],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=3,
cwd=str(repo_root),
)
except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError):
return ""
if result.returncode != 0:
return ""
return result.stdout.strip()
def _format_git_state(repo_root: Path) -> str:
branch = _run_git(repo_root, ["branch", "--show-current"]) or "(detached)"
dirty_lines = [
line for line in _run_git(repo_root, ["status", "--porcelain"]).splitlines()
if line.strip()
]
dirty_text = "clean" if not dirty_lines else f"dirty {len(dirty_lines)} paths"
return f"Git: branch {branch}; {dirty_text}."
def _detect_platform(input_data: dict) -> str | None:
if isinstance(input_data.get("cursor_version"), str):
return "cursor"
env_map = {
"CLAUDE_PROJECT_DIR": "claude",
"CURSOR_PROJECT_DIR": "cursor",
"CODEBUDDY_PROJECT_DIR": "codebuddy",
"FACTORY_PROJECT_DIR": "droid",
"GEMINI_PROJECT_DIR": "gemini",
"QODER_PROJECT_DIR": "qoder",
"KIRO_PROJECT_DIR": "kiro",
"COPILOT_PROJECT_DIR": "copilot",
"TRAE_PROJECT_DIR": "trae",
}
for env_name, platform in env_map.items():
if os.environ.get(env_name):
return platform
script_parts = set(Path(sys.argv[0]).parts)
if ".claude" in script_parts:
return "claude"
if ".cursor" in script_parts:
return "cursor"
if ".codex" in script_parts:
return "codex"
if ".gemini" in script_parts:
return "gemini"
if ".qoder" in script_parts:
return "qoder"
if ".codebuddy" in script_parts:
return "codebuddy"
if ".factory" in script_parts:
return "droid"
if ".kiro" in script_parts:
return "kiro"
if ".trae" in script_parts:
return "trae"
return None
def _resolve_context_key(trellis_dir: Path, input_data: dict) -> str | None:
scripts_dir = trellis_dir / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
from common.active_task import resolve_context_key # type: ignore[import-not-found]
return resolve_context_key(input_data, platform=_detect_platform(input_data))
def _persist_context_key_for_bash(context_key: str | None) -> None:
"""Expose Trellis session identity to later Claude Code Bash commands.
Claude Code SessionStart hooks can append exports to CLAUDE_ENV_FILE; those
variables are then available to Bash tools in the same conversation. Without
this bridge, `task.py start` has hook stdin during SessionStart but no
session identity when the AI later runs it as a normal shell command.
"""
if not context_key:
return
env_file = os.environ.get("CLAUDE_ENV_FILE")
if not env_file:
return
try:
with open(env_file, "a", encoding="utf-8") as handle:
handle.write(f"export TRELLIS_CONTEXT_ID={shlex.quote(context_key)}\n")
except OSError:
pass
def _resolve_active_task(trellis_dir: Path, input_data: dict):
scripts_dir = trellis_dir / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
from common.active_task import resolve_active_task # type: ignore[import-not-found]
return resolve_active_task(
trellis_dir.parent,
input_data,
platform=_detect_platform(input_data),
)
def run_script(script_path: Path, context_key: str | None = None) -> str:
try:
if script_path.suffix == ".py":
# Add PYTHONIOENCODING to force UTF-8 in subprocess
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
if context_key:
env["TRELLIS_CONTEXT_ID"] = context_key
cmd = [sys.executable, "-W", "ignore", str(script_path)]
else:
env = os.environ.copy()
if context_key:
env["TRELLIS_CONTEXT_ID"] = context_key
cmd = [str(script_path)]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=5,
cwd=script_path.parent.parent.parent,
env=env,
)
return result.stdout if result.returncode == 0 else "No context available"
except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError):
return "No context available"
def _normalize_task_ref(task_ref: str) -> str:
normalized = task_ref.strip()
if not normalized:
return ""
path_obj = Path(normalized)
if path_obj.is_absolute():
return str(path_obj)
normalized = normalized.replace("\\", "/")
while normalized.startswith("./"):
normalized = normalized[2:]
if normalized.startswith("tasks/"):
return f".trellis/{normalized}"
return normalized
def _resolve_task_dir(trellis_dir: Path, task_ref: str) -> Path:
normalized = _normalize_task_ref(task_ref)
path_obj = Path(normalized)
if path_obj.is_absolute():
return path_obj
if normalized.startswith(".trellis/"):
return trellis_dir.parent / path_obj
return trellis_dir / "tasks" / path_obj
def _get_task_status(trellis_dir: Path, input_data: dict) -> str:
"""Return compact active-task status, artifact presence, and next action."""
active = _resolve_active_task(trellis_dir, input_data)
if not active.task_path:
return (
"Status: NO ACTIVE TASK\n"
"Next-Action: Classify the current turn before creating any Trellis task. "
"Simple conversation / small task asks only whether this turn should create a Trellis task. "
"Complex task asks whether task creation and planning are allowed."
)
task_ref = active.task_path
task_dir = _resolve_task_dir(trellis_dir, task_ref)
if active.stale or not task_dir.is_dir():
return (
f"Status: STALE POINTER\nTask: {task_ref}\n"
f"Next-Action: Run `python ./.trellis/scripts/task.py finish` to clear the stale pointer, "
"then ask the user what to work on next."
)
task_json_path = task_dir / "task.json"
task_data = {}
if task_json_path.is_file():
try:
task_data = json.loads(task_json_path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, PermissionError):
pass
task_title = task_data.get("title", task_ref)
task_status = task_data.get("status", "unknown")
artifact_names = ("prd.md", "design.md", "implement.md", "implement.jsonl", "check.jsonl")
present = [name for name in artifact_names if (task_dir / name).is_file()]
if (task_dir / "research").is_dir():
present.append("research/")
present_line = ", ".join(present) if present else "(none)"
if task_status == "completed":
return (
f"Status: COMPLETED\nTask: {task_title}\n"
f"Present: {present_line}\n"
"Next-Action: Run `/trellis:finish-work`. If the working tree is dirty, return to Phase 3.4 first."
)
has_prd = (task_dir / "prd.md").is_file()
has_design = (task_dir / "design.md").is_file()
has_implement_plan = (task_dir / "implement.md").is_file()
implement_jsonl = task_dir / "implement.jsonl"
check_jsonl = task_dir / "check.jsonl"
jsonl_ready = (
(not implement_jsonl.is_file() or _has_curated_jsonl_entry(implement_jsonl))
and (not check_jsonl.is_file() or _has_curated_jsonl_entry(check_jsonl))
)
if task_status == "planning" and not has_prd:
return (
f"Status: PLANNING\nTask: {task_title}\n"
f"Present: {present_line}\n"
"Next-Action: Load `trellis-brainstorm` and write `prd.md`. Stay in planning."
)
if task_status == "planning":
missing_complex = [
name for name, exists in (
("design.md", has_design),
("implement.md", has_implement_plan),
)
if not exists
]
next_bits: list[str] = []
if missing_complex:
next_bits.append(
"Lightweight task can request start review with PRD-only; "
f"complex task must add {', '.join(missing_complex)} before start"
)
else:
next_bits.append("Planning artifacts are present; ask for review before `task.py start`")
if not jsonl_ready:
next_bits.append("curate `implement.jsonl` and `check.jsonl` before sub-agent mode start")
return (
f"Status: PLANNING\nTask: {task_title}\n"
f"Present: {present_line}\n"
f"Next-Action: {'; '.join(next_bits)}. Do not enter implementation until the user confirms start."
)
return (
f"Status: {str(task_status).upper()}\nTask: {task_title}\n"
f"Present: {present_line}\n"
"Next-Action: Follow the matching per-turn workflow-state. "
"Implementation/check context order is jsonl entries -> `prd.md` -> `design.md if present` -> `implement.md if present`."
)
def _load_trellis_config(trellis_dir: Path, input_data: dict) -> tuple:
"""Load Trellis config for session-start decisions.
Returns:
(is_mono, packages_dict, spec_scope, task_pkg, default_pkg)
"""
scripts_dir = trellis_dir / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
try:
from common.config import get_default_package, get_packages, get_spec_scope, is_monorepo # type: ignore[import-not-found]
from common.paths import get_current_task # type: ignore[import-not-found]
repo_root = trellis_dir.parent
is_mono = is_monorepo(repo_root)
packages = get_packages(repo_root) or {}
scope = get_spec_scope(repo_root)
# Get active task's package
task_pkg = None
current = get_current_task(
repo_root,
input_data,
platform=_detect_platform(input_data),
)
if current:
task_json = repo_root / current / "task.json"
if task_json.is_file():
try:
data = json.loads(task_json.read_text(encoding="utf-8"))
if isinstance(data, dict):
tp = data.get("package")
if isinstance(tp, str) and tp:
task_pkg = tp
except (json.JSONDecodeError, OSError):
pass
default_pkg = get_default_package(repo_root)
return is_mono, packages, scope, task_pkg, default_pkg
except Exception:
return False, {}, None, None, None
def _check_legacy_spec(trellis_dir: Path, is_mono: bool, packages: dict) -> str | None:
"""Check for legacy spec directory structure in monorepo.
Returns warning message if legacy structure detected, None otherwise.
"""
if not is_mono or not packages:
return None
spec_dir = trellis_dir / "spec"
if not spec_dir.is_dir():
return None
# Check for legacy flat spec dirs (spec/backend/, spec/frontend/ with index.md)
has_legacy = False
for legacy_name in ("backend", "frontend"):
legacy_dir = spec_dir / legacy_name
if legacy_dir.is_dir() and (legacy_dir / "index.md").is_file():
has_legacy = True
break
if not has_legacy:
return None
# Check which packages are missing spec/<pkg>/ directory
missing = [
name for name in sorted(packages.keys())
if not (spec_dir / name).is_dir()
]
if not missing:
return None # All packages have spec dirs
if len(missing) == len(packages):
return (
f"[!] Legacy spec structure detected: found `spec/backend/` or `spec/frontend/` "
f"but no package-scoped `spec/<package>/` directories.\n"
f"Monorepo packages: {', '.join(sorted(packages.keys()))}\n"
f"Please reorganize: `spec/backend/` -> `spec/<package>/backend/`"
)
return (
f"[!] Partial spec migration detected: packages {', '.join(missing)} "
f"still missing `spec/<pkg>/` directory.\n"
f"Please complete migration for all packages."
)
def _resolve_spec_scope(
is_mono: bool,
packages: dict,
scope,
task_pkg: str | None,
default_pkg: str | None,
) -> set | None:
"""Resolve which packages should have their specs injected.
Returns:
Set of package names to include, or None for full scan.
"""
if not is_mono or not packages:
return None # Single-repo: full scan
if scope is None:
return None # No scope configured: full scan
if isinstance(scope, str) and scope == "active_task":
if task_pkg and task_pkg in packages:
return {task_pkg}
if default_pkg and default_pkg in packages:
return {default_pkg}
return None # Fallback to full scan
if isinstance(scope, list):
valid = set()
for entry in scope:
if entry in packages:
valid.add(entry)
else:
print(
f"Warning: spec_scope contains unknown package: {entry}, ignoring",
file=sys.stderr,
)
if valid:
# Warn if active task is out of scope
if task_pkg and task_pkg not in valid:
print(
f"Warning: active task package '{task_pkg}' is out of configured spec_scope",
file=sys.stderr,
)
return valid
# All entries invalid: fallback chain
print(
"Warning: all spec_scope entries invalid, falling back to task/default/full",
file=sys.stderr,
)
if task_pkg and task_pkg in packages:
return {task_pkg}
if default_pkg and default_pkg in packages:
return {default_pkg}
return None # Full scan
return None # Unknown scope type: full scan
def _collect_spec_index_paths(trellis_dir: Path, allowed_pkgs: set | None) -> list[str]:
paths: list[str] = []
guides_index = trellis_dir / "spec" / "guides" / "index.md"
if guides_index.is_file():
paths.append(".trellis/spec/guides/index.md")
spec_dir = trellis_dir / "spec"
if not spec_dir.is_dir():
return paths
for sub in sorted(spec_dir.iterdir()):
if not sub.is_dir() or sub.name.startswith(".") or sub.name == "guides":
continue
index_file = sub / "index.md"
if index_file.is_file():
paths.append(f".trellis/spec/{sub.name}/index.md")
continue
if allowed_pkgs is not None and sub.name not in allowed_pkgs:
continue
for nested in sorted(sub.iterdir()):
if not nested.is_dir():
continue
nested_index = nested / "index.md"
if nested_index.is_file():
paths.append(f".trellis/spec/{sub.name}/{nested.name}/index.md")
return paths
def _build_compact_current_state(
trellis_dir: Path,
input_data: dict,
spec_index_paths: list[str],
) -> str:
repo_root = trellis_dir.parent
lines: list[str] = []
try:
from common.paths import get_active_journal_file, get_developer, get_tasks_dir, count_lines # type: ignore[import-not-found]
from common.tasks import iter_active_tasks # type: ignore[import-not-found]
except Exception:
get_active_journal_file = None # type: ignore[assignment]
get_developer = None # type: ignore[assignment]
get_tasks_dir = None # type: ignore[assignment]
count_lines = None # type: ignore[assignment]
iter_active_tasks = None # type: ignore[assignment]
developer = get_developer(repo_root) if get_developer else None
lines.append(f"Developer: {developer or '(not initialized)'}")
lines.append(_format_git_state(repo_root))
active = _resolve_active_task(trellis_dir, input_data)
if active.task_path:
task_dir = _resolve_task_dir(trellis_dir, active.task_path)
status = "unknown"
task_json = task_dir / "task.json"
if task_json.is_file():
try:
data = json.loads(task_json.read_text(encoding="utf-8"))
if isinstance(data, dict):
status = str(data.get("status") or "unknown")
except (json.JSONDecodeError, OSError):
pass
lines.append(f"Current task: {_repo_relative(repo_root, task_dir)}; status={status}.")
else:
lines.append("Current task: none.")
if get_tasks_dir and iter_active_tasks:
try:
task_count = sum(1 for _ in iter_active_tasks(get_tasks_dir(repo_root)))
lines.append(
f"Active tasks: {task_count} total. Use `python ./.trellis/scripts/task.py list --mine` only if needed."
)
except Exception:
pass
if get_active_journal_file and count_lines:
journal = get_active_journal_file(repo_root)
if journal:
lines.append(
f"Journal: {_repo_relative(repo_root, journal)}, {count_lines(journal)} / 2000 lines."
)
if spec_index_paths:
lines.append(f"Spec indexes: {len(spec_index_paths)} available.")
return "\n".join(lines)
def _extract_range(content: str, start_header: str, end_header: str) -> str:
"""Extract lines starting at `## start_header` up to (but excluding) `## end_header`.
Both parameters are full header lines WITHOUT the `## ` prefix (e.g. "Phase Index").
Returns empty string if start header is not found.
End header missing → extracts to end of file.
"""
lines = content.splitlines()
start: int | None = None
end: int = len(lines)
start_match = f"## {start_header}"
end_match = f"## {end_header}"
for i, line in enumerate(lines):
stripped = line.strip()
if start is None and stripped == start_match:
start = i
continue
if start is not None and stripped == end_match:
end = i
break
if start is None:
return ""
return "\n".join(lines[start:end]).rstrip()
_BREADCRUMB_TAG_RE = re.compile(
r"\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n.*?\n\s*\[/workflow-state:\1\]",
re.DOTALL,
)
def _strip_breadcrumb_tag_blocks(content: str) -> str:
"""Remove `[workflow-state:STATUS]...[/workflow-state:STATUS]` blocks.
The tag blocks live inside `## Phase Index` (since v0.5.0-rc.0, when
they were colocated with their phase summaries) and are consumed by the
UserPromptSubmit hook (`inject-workflow-state.py`). The session-start
payload already covers the full step bodies, so re-inlining the
breadcrumbs here would just duplicate context.
"""
stripped = _BREADCRUMB_TAG_RE.sub("", content)
stripped = re.sub(r"<!--.*?-->", "", stripped, flags=re.DOTALL)
stripped = re.sub(r"^\[(?!/?workflow-state:)/?[^\]\n]+\]\s*\n?", "", stripped, flags=re.MULTILINE)
return re.sub(r"\n{3,}", "\n\n", stripped).strip()
def _build_workflow_overview(workflow_path: Path) -> str:
"""Inject only the compact Phase Index summary for SessionStart."""
content = read_file(workflow_path)
if not content:
return "No workflow.md found"
out_lines = [
"# Development Workflow - Session Summary",
"Full guide: .trellis/workflow.md. Step detail: `python ./.trellis/scripts/get_context.py --mode phase --step <X.Y>`.",
"",
]
phases = _extract_range(content, "Phase Index", "Phase 1: Plan")
if phases:
out_lines.append(_strip_breadcrumb_tag_blocks(phases).rstrip())
return "\n".join(out_lines).rstrip()
def main():
if should_skip_injection():
sys.exit(0)
try:
hook_input = json.loads(sys.stdin.read())
if not isinstance(hook_input, dict):
hook_input = {}
except (json.JSONDecodeError, ValueError):
hook_input = {}
# Try platform-specific env vars, hook cwd, fallback to cwd
project_dir_env_vars = [
"CLAUDE_PROJECT_DIR",
"QODER_PROJECT_DIR",
"CODEBUDDY_PROJECT_DIR",
"FACTORY_PROJECT_DIR",
"CURSOR_PROJECT_DIR",
"GEMINI_PROJECT_DIR",
"KIRO_PROJECT_DIR",
"COPILOT_PROJECT_DIR",
"TRAE_PROJECT_DIR",
]
project_dir = None
for var in project_dir_env_vars:
val = os.environ.get(var)
if val:
project_dir = Path(_normalize_windows_shell_path(val)).resolve()
break
if project_dir is None:
project_dir = Path(_normalize_windows_shell_path(hook_input.get("cwd", "."))).resolve()
trellis_dir = project_dir / ".trellis"
context_key = _resolve_context_key(trellis_dir, hook_input)
_persist_context_key_for_bash(context_key)
# Load config for scope filtering and legacy detection
is_mono, packages, scope_config, task_pkg, default_pkg = _load_trellis_config(
trellis_dir,
hook_input,
)
allowed_pkgs = _resolve_spec_scope(is_mono, packages, scope_config, task_pkg, default_pkg)
output = StringIO()
spec_index_paths = _collect_spec_index_paths(trellis_dir, allowed_pkgs)
output.write("""<session-context>
Trellis compact SessionStart context. Use it to orient the session; load details on demand.
</session-context>
""")
output.write(FIRST_REPLY_NOTICE)
output.write("\n\n")
# Legacy migration warning
legacy_warning = _check_legacy_spec(trellis_dir, is_mono, packages)
if legacy_warning:
output.write(f"<migration-warning>\n{legacy_warning}\n</migration-warning>\n\n")
output.write("<current-state>\n")
output.write(_build_compact_current_state(trellis_dir, hook_input, spec_index_paths))
output.write("\n</current-state>\n\n")
output.write("<trellis-workflow>\n")
output.write(_build_workflow_overview(trellis_dir / "workflow.md"))
output.write("\n</trellis-workflow>\n\n")
output.write("<guidelines>\n")
output.write(
"Task context order for implementation/check: jsonl entries -> `prd.md` -> "
"`design.md if present` -> `implement.md if present`. Missing optional artifacts "
"are skipped for lightweight tasks.\n\n"
)
if spec_index_paths:
output.write("## Available indexes (read on demand)\n")
for p in spec_index_paths:
output.write(f"- {p}\n")
output.write("\n")
output.write(
"Discover more via: "
"`python ./.trellis/scripts/get_context.py --mode packages`\n"
)
output.write("</guidelines>\n\n")
# Check task status and inject structured tag
task_status = _get_task_status(trellis_dir, hook_input)
output.write(f"<task-status>\n{task_status}\n</task-status>\n\n")
output.write("""<ready>
Context loaded. Follow <task-status>. Load workflow/spec/task details only when needed.
</ready>""")
context_text = output.getvalue()
# Kiro (CLI trellis agent agentSpawn) adds a hook's stdout directly to the
# conversation context — no JSON envelope. Emit the bare overview text.
# Conditionally isolated: all other platforms keep the JSON path below.
if _detect_platform(hook_input) == "kiro":
print(context_text, flush=True)
return
result = {
# Claude Code / Qoder / CodeBuddy / Droid / Gemini / Copilot format
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": context_text,
},
# Cursor sessionStart format (top-level snake_case per Cursor docs)
"additional_context": context_text,
}
# Output JSON - stdout is already configured for UTF-8
print(json.dumps(result, ensure_ascii=False), flush=True)
if __name__ == "__main__":
main()

324
.claude/hooks/statusline.py Normal file
View File

@@ -0,0 +1,324 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Trellis StatusLine — project-level status display for Claude Code.
Reads Claude Code session JSON from stdin + Trellis task data from filesystem.
Outputs 1-2 lines:
With active task: [P1] Task title (status) + info line
Without task: info line only
Info line: model · ctx% · branch · duration · developer · tasks · rate limits
When COLUMNS (injected by Claude Code v2.1.153+) is too narrow for the info
line, the rate-limit segments move to their own line via an explicit "\n".
"""
from __future__ import annotations
import json
import os
import re
import subprocess
import sys
import time
from datetime import datetime
from pathlib import Path
# Fix: Windows Python defaults to GBK encoding, which corrupts UTF-8
# characters like the middle dot (·). Wrap stdout/stderr with UTF-8.
if sys.platform == "win32":
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, "reconfigure", None)
if callable(reconfigure):
reconfigure(encoding="utf-8", errors="replace")
def _read_text(path: Path) -> str:
try:
return path.read_text(encoding="utf-8").strip()
except (FileNotFoundError, PermissionError, OSError):
return ""
def _read_json(path: Path) -> dict:
text = _read_text(path)
if not text:
return {}
try:
return json.loads(text)
except (json.JSONDecodeError, ValueError):
return {}
def _normalize_task_ref(task_ref: str) -> str:
normalized = task_ref.strip()
if not normalized:
return ""
path_obj = Path(normalized)
if path_obj.is_absolute():
return str(path_obj)
normalized = normalized.replace("\\", "/")
while normalized.startswith("./"):
normalized = normalized[2:]
if normalized.startswith("tasks/"):
return f".trellis/{normalized}"
return normalized
def _resolve_task_dir(trellis_dir: Path, task_ref: str) -> Path:
normalized = _normalize_task_ref(task_ref)
path_obj = Path(normalized)
if path_obj.is_absolute():
return path_obj
if normalized.startswith(".trellis/"):
return trellis_dir.parent / path_obj
return trellis_dir / "tasks" / path_obj
def _find_trellis_dir() -> Path | None:
"""Walk up from cwd to find .trellis/ directory."""
current = Path.cwd()
for parent in [current, *current.parents]:
candidate = parent / ".trellis"
if candidate.is_dir():
return candidate
return None
def _get_current_task(trellis_dir: Path) -> dict | None:
"""Load current task info through Trellis' active task resolver."""
return _get_current_task_for_input(trellis_dir, {})
def _get_current_task_for_input(trellis_dir: Path, cc_data: dict) -> dict | None:
"""Load current task info for the Claude Code session JSON."""
scripts_dir = trellis_dir / "scripts"
if str(scripts_dir) not in sys.path:
sys.path.insert(0, str(scripts_dir))
try:
from common.active_task import resolve_active_task # type: ignore[import-not-found]
except Exception:
return None
active = resolve_active_task(trellis_dir.parent, cc_data, platform="claude")
if not active.task_path:
return None
task_path = _resolve_task_dir(trellis_dir, active.task_path)
if active.stale:
return {
"title": task_path.name,
"status": "stale",
"priority": "P?",
"source": active.source,
}
task_data = _read_json(task_path / "task.json")
if not task_data:
return None
return {
"title": task_data.get("title") or task_data.get("name") or "unknown",
"status": task_data.get("status", "unknown"),
"priority": task_data.get("priority", "P2"),
"source": active.source,
}
def _count_active_tasks(trellis_dir: Path) -> int:
"""Count non-archived task directories with valid task.json."""
tasks_dir = trellis_dir / "tasks"
if not tasks_dir.is_dir():
return 0
count = 0
for d in tasks_dir.iterdir():
if d.is_dir() and d.name != "archive" and (d / "task.json").is_file():
count += 1
return count
def _get_developer(trellis_dir: Path) -> str:
content = _read_text(trellis_dir / ".developer")
if not content:
return "unknown"
for line in content.splitlines():
if line.startswith("name="):
return line[5:].strip()
return content.splitlines()[0].strip() or "unknown"
def _get_git_branch() -> str:
try:
result = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True, text=True, timeout=3,
)
return result.stdout.strip() if result.returncode == 0 else ""
except (FileNotFoundError, subprocess.TimeoutExpired):
return ""
def _format_ctx_size(size: int) -> str:
if size >= 1_000_000:
return f"{size // 1_000_000}M"
if size >= 1_000:
return f"{size // 1_000}K"
return str(size)
def _format_duration(ms: int) -> str:
secs = ms // 1000
hours, remainder = divmod(secs, 3600)
mins = remainder // 60
if hours > 0:
return f"{hours}h{mins}m"
return f"{mins}m"
def _format_remaining(secs: int) -> str:
if secs <= 0:
return ""
days, remainder = divmod(secs, 86400)
hours, remainder = divmod(remainder, 3600)
mins = remainder // 60
if days > 0:
return f"{days}d{hours}h"
if hours > 0:
return f"{hours}h{mins}m"
return f"{mins}m"
def _parse_resets_at(value: object) -> int:
"""`resets_at` is epoch seconds (int/float, possibly stringified) or an
ISO-8601 timestamp depending on Claude Code version. Return epoch
seconds, or 0 when absent/unparseable (countdown is then omitted)."""
if isinstance(value, (int, float)):
return int(value)
if isinstance(value, str) and value:
try:
return int(float(value))
except ValueError:
pass
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
return int(parsed.timestamp())
except ValueError:
pass
return 0
def _rate_limit_part(label: str, window: dict, now: int) -> str:
try:
pct = int(float(window.get("used_percentage"))) # pyright: ignore[reportArgumentType]
except (TypeError, ValueError):
return ""
part = f"{label} {pct}%"
remaining = _format_remaining(_parse_resets_at(window.get("resets_at")) - now)
if remaining:
part += f" \033[90m(reset {remaining})\033[0m"
return part
_ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
def _visible_len(s: str) -> int:
"""Length of s with ANSI escape sequences stripped."""
return len(_ANSI_RE.sub("", s))
def _terminal_width() -> int | None:
"""Terminal width from the COLUMNS env var, or None.
The statusline stdin JSON has no width field and stdout is a pipe, so
the COLUMNS env var (injected by Claude Code v2.1.153+) is the only
width signal. Absent or malformed values return None."""
try:
width = int(os.environ.get("COLUMNS", ""))
except ValueError:
return None
return width if width > 0 else None
def main() -> None:
# Read Claude Code session JSON from stdin
try:
cc_data = json.loads(sys.stdin.read())
except (json.JSONDecodeError, ValueError):
cc_data = {}
trellis_dir = _find_trellis_dir()
SEP = " \033[90m·\033[0m "
# --- Trellis data ---
task = _get_current_task_for_input(trellis_dir, cc_data) if trellis_dir else None
dev = _get_developer(trellis_dir) if trellis_dir else ""
task_count = _count_active_tasks(trellis_dir) if trellis_dir else 0
# --- CC session data ---
model = cc_data.get("model", {}).get("display_name", "?")
ctx_pct = int(cc_data.get("context_window", {}).get("used_percentage") or 0)
ctx_size = _format_ctx_size(cc_data.get("context_window", {}).get("context_window_size") or 0)
duration = _format_duration(cc_data.get("cost", {}).get("total_duration_ms") or 0)
branch = _get_git_branch()
# Avoid "Opus 4.6 (1M context) (1M)"
if re.search(r"\d+[KMG]\b", model, re.IGNORECASE):
model_label = model
else:
model_label = f"{model} ({ctx_size})"
# Context % with color
if ctx_pct >= 90:
ctx_color = "\033[31m"
elif ctx_pct >= 70:
ctx_color = "\033[33m"
else:
ctx_color = "\033[32m"
# Build info line: model · ctx · branch · duration · dev · tasks [· rate limits]
parts = [
model_label,
f"ctx {ctx_color}{ctx_pct}%\033[0m",
]
if branch:
parts.append(f"\033[35m{branch}\033[0m")
parts.append(duration)
if dev:
parts.append(f"\033[32m{dev}\033[0m")
if task_count:
parts.append(f"{task_count} task(s)")
now = int(time.time())
rate_limits = cc_data.get("rate_limits", {})
rate_parts: list[str] = []
for label, key in (("5h", "five_hour"), ("7d", "seven_day")):
part = _rate_limit_part(label, rate_limits.get(key) or {}, now)
if part:
rate_parts.append(part)
info_line = SEP.join(parts + rate_parts)
# Output: task line (only if active) + info line
if task:
source = str(task.get("source") or "")
source_tag = "session" if source.startswith("session:") else source
source_suffix = f" \033[90m[{source_tag}]\033[0m" if source_tag else ""
print(f"\033[36m[{task['priority']}]\033[0m {task['title']} \033[33m({task['status']})\033[0m{source_suffix}")
# Claude Code's status-bar height counts only "\n" characters, so a
# visually wrapped long line misaligns rows. When the host provides a
# terminal width and the info line would overflow, split the rate-limit
# segments onto their own line with an explicit "\n" instead.
width = _terminal_width()
if width is not None and rate_parts and _visible_len(info_line) > width:
print(SEP.join(parts))
print(SEP.join(rate_parts))
else:
print(info_line)
if __name__ == "__main__":
main()

77
.claude/settings.json Normal file
View File

@@ -0,0 +1,77 @@
{
"env": {
"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR": "1"
},
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/session-start.py",
"timeout": 30
}
]
},
{
"matcher": "clear",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/session-start.py",
"timeout": 30
}
]
},
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/session-start.py",
"timeout": 30
}
]
}
],
"PreToolUse": [
{
"matcher": "Task",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/inject-subagent-context.py",
"timeout": 30
}
]
},
{
"matcher": "Agent",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/inject-subagent-context.py",
"timeout": 30
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/inject-workflow-state.py",
"timeout": 15
}
]
}
]
},
"enabledPlugins": {},
"statusLine": {
"type": "command",
"command": "python .claude/hooks/statusline.py"
}
}

View File

@@ -0,0 +1,40 @@
---
name: trellis-before-dev
description: "Discovers and injects project-specific coding guidelines from .trellis/spec/ before implementation begins. Reads spec indexes, pre-development checklists, and shared thinking guides for the target package. Use when starting a new coding task, before writing any code, switching to a different package, or needing to refresh project conventions and standards."
---
Read the relevant development guidelines before starting your task.
Execute these steps:
1. **Read current task artifacts**:
- `prd.md` for requirements and acceptance criteria
- `design.md` if present for technical design
- `implement.md` if present for execution order and validation plan
2. **Discover packages and their spec layers**:
```bash
python ./.trellis/scripts/get_context.py --mode packages
```
3. **Identify which specs apply** to your task based on:
- Which package you're modifying (e.g., `cli/`, `docs-site/`)
- What type of work (backend, frontend, unit-test, docs, etc.)
- Any spec/research paths referenced by the task artifacts
4. **Read the spec index** for each relevant module:
```bash
cat .trellis/spec/<package>/<layer>/index.md
```
Follow the **"Pre-Development Checklist"** section in the index.
5. **Read the specific guideline files** listed in the Pre-Development Checklist that are relevant to your task. The index is NOT the goal — it points you to the actual guideline files (e.g., `error-handling.md`, `conventions.md`, `mock-strategies.md`). Read those files to understand the coding standards and patterns.
6. **Always read shared guides**:
```bash
cat .trellis/spec/guides/index.md
```
7. Understand the coding standards and patterns you need to follow, then proceed with your development plan.
This step is **mandatory** before writing any code.

View File

@@ -0,0 +1,173 @@
---
name: trellis-brainstorm
description: "Guides collaborative requirements discovery before implementation. Creates task directory, seeds PRD, asks high-value questions one at a time, researches technical choices, and converges on MVP scope. Use when requirements are unclear, there are multiple valid approaches, or the user describes a new feature or complex task."
---
# Trellis Brainstorm
## Non-Negotiable Interview Contract
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
Ask the questions one at a time.
## Non-Negotiable Evidence Rule
If a question can be answered by exploring the codebase, explore the codebase instead.
This is mandatory. Before asking the user a question, first check whether the answer is already available in code, tests, configs, docs, existing specs, or task history.
Do not ask the user to confirm facts that the repository can answer. Ask only for product intent, preference, scope, risk tolerance, or decisions that remain ambiguous after inspection.
---
Use this skill during Phase 1 planning to turn the user's request into clear requirements and planning artifacts.
## Preconditions
Use this skill only after task-creation consent has been given and the user is ready to enter Trellis planning.
If no task exists yet, create one:
```bash
TASK_DIR=$(python ./.trellis/scripts/task.py create "<short task title>" --slug <slug>)
```
Use a concise title from the user's request. Use a slug without a date prefix. `task.py create` adds the `MM-DD-` directory prefix automatically.
`task.py create` creates the default `prd.md`. Update that file with the current understanding before asking follow-up questions.
## Planning Flow
1. Capture the user's request and initial known facts in `prd.md`.
2. Inspect available evidence before asking questions:
- code, tests, fixtures, and configs
- README files, docs, existing specs, and domain notes
- related Trellis tasks, research files, and session history when present
3. Separate what you found into:
- confirmed facts
- product intent still needed from the user
- scope or risk decisions still needed from the user
- likely out-of-scope items
4. Ask the single highest-value remaining question.
5. Include your recommended answer with the question.
6. After each user answer, update `prd.md` before continuing.
7. For complex tasks, create or update `design.md` and `implement.md` before implementation starts.
8. Before final review or `task.py start`, run the PRD convergence pass below.
Do not invent a project-specific product/spec hierarchy. If the repository already has product, domain, or spec docs, use them. If it does not, proceed with the evidence that exists.
## Question Rules
Ask only one question per message.
Each question must include:
- the decision needed
- why the answer matters
- your recommended answer
- the trade-off if the user chooses differently
Do not ask process questions such as whether to search, inspect files, or continue brainstorming. Do the evidence work directly. Ask the user only when the remaining issue is a product decision, preference, scope boundary, or risk tolerance choice.
## Thinking Framework: First Principles Analysis
When requirements are vague, solutions feel over-engineered, or you're about to add complexity "because everyone does" — decompose to fundamental truths before reasoning upward.
### Step 1: Restate the Problem
Strip away implementation details to one sentence.
> Bad: "We need to add Redis caching to the user profile endpoint"
> Good: "User profile data takes too long to load"
### Step 2: List Fundamental Truths
What is absolutely true (not opinion or convention)?
| Category | Examples |
|----------|----------|
| **Physical constraints** | Network latency ≥ 0, disk I/O has limits |
| **Business rules** | "Users must see their own data" |
| **Technical invariants** | "Data must be consistent" |
| **User needs** | "The user wants X within Y seconds" |
### Step 3: Challenge Assumptions
For each component of the current plan:
- **Fact or convention?** "We always use REST" — why?
- **What if we removed this?** If nothing breaks, it's unnecessary.
- **Solving the actual problem or a symptom?** Trace the causal chain.
- **Who benefits from this complexity?** If "nobody", simplify.
### Step 4: Build Up from Truths
1. Start with the minimum viable mechanism satisfying all truths
2. Add complexity only when a specific truth demands it
3. Each addition must answer: "Which truth requires this?"
### Step 5: Validate
- Does the solution solve the original problem?
- What assumptions need verification?
- What's the simplest experiment to test this?
## Artifact Rules
`prd.md` records requirements and acceptance:
- goal and user value
- confirmed facts
- requirements
- acceptance criteria
- out of scope
- open questions that still block planning
`design.md` records technical design for complex tasks:
- architecture and boundaries
- data flow and contracts
- compatibility and migration notes
- important trade-offs
- operational or rollback considerations
`implement.md` records execution planning for complex tasks:
- ordered implementation checklist
- validation commands
- risky files or rollback points
- follow-up checks before `task.py start`
Lightweight tasks may have only `prd.md`. Complex tasks must have `prd.md`, `design.md`, and `implement.md` before `task.py start`.
`implement.md` is not a replacement for `implement.jsonl`. On sub-agent-dispatch workflows, `implement.jsonl` and `check.jsonl` must each contain at least one real spec/research entry before `task.py start`; the seed `_example` row does not count. Inline workflows skip this JSONL gate because Phase 2 loads context through `trellis-before-dev`.
## PRD Convergence Pass
Before declaring planning ready or running `task.py start`, rewrite `prd.md` once against the final structure described in the artifact rules above. This is not optional cleanup; it is the final planning gate.
The pass must be lossless:
- Collapse repeated facts into one authoritative section.
- Fold temporary brainstorm sections such as `What I already know`, `Assumptions`, and resolved `Open Questions` into Goal, Background, Requirements, Technical Notes, or Acceptance Criteria.
- Remove resolved open questions instead of leaving empty or already-answered sections.
- Merge parallel bug and requirement lists when they describe the same work; keep each defect's severity, evidence, and file:line anchors on the owning requirement.
- Preserve every file:line anchor, decision, constraint, requirement ID, and acceptance-criteria mapping.
- Keep only genuinely blocking open questions.
After the pass, read `prd.md` top to bottom and verify that no fact is repeated across sections unless the repetition adds new information.
## Quality Bar
Before declaring planning ready:
- `prd.md` contains testable acceptance criteria.
- `prd.md` has passed the PRD convergence pass: no unresolved temporary brainstorm sections, no duplicate facts across sections, and no lost anchors, decisions, or acceptance mappings.
- Repository-answerable questions have already been answered through inspection.
- Remaining open questions are genuinely about user intent or scope.
- Complex tasks have `design.md` and `implement.md`.
- Sub-agent-dispatch tasks have real curated entries in both `implement.jsonl` and `check.jsonl`; seed-only manifests are not ready.
- The user has reviewed the final planning artifacts or explicitly approved proceeding.
Do not start implementation until the user approves or asks for implementation.

View File

@@ -0,0 +1,188 @@
---
name: trellis-break-loop
description: "Deep bug analysis to break the fix-forget-repeat cycle. Analyzes root cause category, why fixes failed, prevention mechanisms, and captures knowledge into specs. Use after fixing a bug to prevent the same class of bugs."
---
# Break the Loop - Deep Bug Analysis
When debug is complete, use this for deep analysis to break the "fix bug -> forget -> repeat" cycle.
---
## Analysis Framework
Analyze the bug you just fixed from these 5 dimensions:
### 1. Root Cause Category
Which category does this bug belong to?
| Category | Characteristics | Example |
|----------|-----------------|---------|
| **A. Missing Spec** | No documentation on how to do it | New feature without checklist |
| **B. Cross-Layer Contract** | Interface between layers unclear | API returns different format than expected |
| **C. Change Propagation Failure** | Changed one place, missed others | Changed function signature, missed call sites |
| **D. Test Coverage Gap** | Unit test passes, integration fails | Works alone, breaks when combined |
| **E. Implicit Assumption** | Code relies on undocumented assumption | Timestamp seconds vs milliseconds |
### 2. Why Fixes Failed (if applicable)
If you tried multiple fixes before succeeding, analyze each failure:
- **Surface Fix**: Fixed symptom, not root cause
- **Incomplete Scope**: Found root cause, didn't cover all cases
- **Tool Limitation**: grep missed it, type check wasn't strict
- **Mental Model**: Kept looking in same layer, didn't think cross-layer
### 3. Prevention Mechanisms
What mechanisms would prevent this from happening again?
| Type | Description | Example |
|------|-------------|---------|
| **Documentation** | Write it down so people know | Update thinking guide |
| **Architecture** | Make the error impossible structurally | Type-safe wrappers |
| **Compile-time** | Strict type checking, no escape hatches | Signature change causes compile error |
| **Runtime** | Monitoring, alerts, scans | Detect orphan entities |
| **Test Coverage** | E2E tests, integration tests | Verify full flow |
| **Code Review** | Checklist, PR template | "Did you check X?" |
### 4. Systematic Expansion
What broader problems does this bug reveal?
- **Similar Issues**: Where else might this problem exist?
- **Design Flaw**: Is there a fundamental architecture issue?
- **Process Flaw**: Is there a development process improvement?
- **Knowledge Gap**: Is the team missing some understanding?
### 5. Knowledge Capture
Solidify insights into the system:
- [ ] Update `.trellis/spec/guides/` thinking guides
- [ ] Update relevant `.trellis/spec/` docs
- [ ] Create issue record (if applicable)
- [ ] Create feature ticket for root fix
- [ ] Update check guidelines if needed
---
## Output Format
Please output analysis in this format:
```markdown
## Bug Analysis: [Short Description]
### 1. Root Cause Category
- **Category**: [A/B/C/D/E] - [Category Name]
- **Specific Cause**: [Detailed description]
### 2. Why Fixes Failed (if applicable)
1. [First attempt]: [Why it failed]
2. [Second attempt]: [Why it failed]
...
### 3. Prevention Mechanisms
| Priority | Mechanism | Specific Action | Status |
|----------|-----------|-----------------|--------|
| P0 | ... | ... | TODO/DONE |
### 4. Systematic Expansion
- **Similar Issues**: [List places with similar problems]
- **Design Improvement**: [Architecture-level suggestions]
- **Process Improvement**: [Development process suggestions]
### 5. Knowledge Capture
- [ ] [Documents to update / tickets to create]
```
---
## Core Philosophy
> **The value of debugging is not in fixing the bug, but in making this class of bugs never happen again.**
Three levels of insight:
1. **Tactical**: How to fix THIS bug
2. **Strategic**: How to prevent THIS CLASS of bugs
3. **Philosophical**: How to expand thinking patterns
30 minutes of analysis saves 30 hours of future debugging.
## Thinking Framework: Bayesian Reasoning
When multiple root causes are plausible and evidence is incomplete, update your beliefs proportionally to new evidence rather than clinging to initial assumptions.
### Step 1: Establish Priors
Before investigating, state what you believe and why:
| Hypothesis | Prior | Reasoning |
|------------|-------|-----------|
| H1: [cause A] | 40% | Most common for this pattern |
| H2: [cause B] | 30% | Plausible given environment |
| H3: [other] | 30% | Catch-all |
Priors must sum to 100%. If you can't assign probabilities, investigate first.
### Step 2: Observe Evidence
Document what you found — be specific about reliability:
- What exactly did you observe?
- How reliable? (test output > log message > user report > hunch)
- Could multiple hypotheses explain this?
### Step 3: Update Beliefs
For each hypothesis, ask: **How likely is this evidence if this hypothesis were true?**
Direction of update matters more than calculation:
- Evidence strongly predicted by H1 → H1 probability increases
- Evidence contradicts H2 → H2 probability decreases
- Evidence equally likely under all → no update
### Step 4: Seek Discriminating Evidence
Don't gather more of the same. Find evidence that **differs strongly** between top hypotheses.
> If H1 and H3 are close: "What would I see if H1 is true but not if H3 is true?" Then check for that.
### Step 5: State Confidence
| Confidence | Action |
|------------|--------|
| 90%+ | Proceed with fix, monitor |
| 70-90% | Proceed, add fallback check |
| 50-70% | Test hypothesis before committing |
| <50% | Need more evidence, don't guess |
Never express binary certainty when evidence is incomplete. Use "most likely", "plausible but unlikely", "worth investigating".
### Common Fallacies
| Fallacy | Example | Correction |
|---------|---------|------------|
| **Base rate neglect** | "Test failed → code is broken" | How often do tests fail for other reasons? |
| **Confirmation bias** | "Must be a race condition, let me find race evidence" | Actively seek evidence AGAINST your top hypothesis |
| **Anchoring** | "Last time it was caching, probably caching again" | Establish priors from current context, not yesterday's bug |
---
## After Analysis: Immediate Actions
**IMPORTANT**: After completing the analysis above, you MUST immediately:
1. **Update spec/guides** - Don't just list TODOs, actually update the relevant files:
- If it's a cross-platform issue → update `cross-platform-thinking-guide.md`
- If it's a cross-layer issue → update `cross-layer-thinking-guide.md`
- If it's a code reuse issue → update `code-reuse-thinking-guide.md`
- If it's domain-specific → update `backend/*.md` or `frontend/*.md`
2. **Sync templates** - After updating `.trellis/spec/`, sync to `src/templates/markdown/spec/`
3. **Commit the spec updates** - This is the primary output, not just the analysis text
> **The analysis is worthless if it stays in chat. The value is in the updated specs.**

View File

@@ -0,0 +1,67 @@
---
name: trellis-channel
description: Use Trellis channel for live multi-agent collaboration, spawned workers, cross-agent review, progress inspection, forum channels, and channel log debugging.
---
# trellis-channel
`trellis channel` is the local multi-agent collaboration runtime. Reach for it when agents need to talk through a durable event log, when a worker should be spawned as a peer process, when an in-flight worker needs interrupt / debugging, or when feedback should be recorded on a durable `--type forum` channel.
Typical user signals: "和 codex/claude 讨论", "brainstorm with another agent", "spawn an implement/check worker", "let agent review", "open an issue board / changelog forum", "look at this thread", "channel is stuck / no output", "progress was truncated", "how do I write that channel command".
This skill is an index. Load only the reference file for the current job — do not preload all of them.
## First Commands
```bash
trellis --version
trellis channel --help
trellis channel list --all
trellis channel list --scope global --all
```
If the user names a channel or thread, inspect it before asking for background:
```bash
trellis channel forum <board> --scope global
trellis channel thread <board> <thread> --scope global
trellis channel context list <board> --scope global --thread <thread>
```
## Route By User Intent
| User intent | Read |
|---|---|
| "和 codex/claude 讨论一下", "brainstorm with another agent" | `references/workflows.md` |
| "派一个 implement/check agent", "让 agent review", "spawn a worker" | `references/workflows.md`, then `references/workers.md` |
| "开 issue 区 / topic 群 / changelog / board", "make a forum" | `references/forum.md` |
| "看看这个 thread / linked context", "inspect a thread" | `references/forum.md` |
| "channel 卡住了 / 没输出 / progress 被截断", "worker stalled" | `references/progress-debugging.md` |
| "具体命令怎么写", "what flags does X take" | `references/command-reference.md` |
## Core Rules
- New forum channels use `--type forum`. A `thread` is one item inside a forum channel.
- Use `--context-file` / `--context-raw` and `trellis channel context add/delete/list`. `--linked-context-*` is deprecated terminology.
- Use `--stdin` or `--text-file` for long messages. Do not put long mixed Chinese/English text in the positional shell argument.
- Pretty `messages` output is an operator dashboard and may truncate progress. Use `--raw` for audit.
- `--as` is the speaker or worker handle, depending on the command. Use explicit, stable names when multiple agents or sessions are involved.
- `--scope project` (default) operates on the current cwd's project bucket; `--scope global` operates on the shared `__global__` bucket. Pick scope deliberately — a global board is invisible from project listings unless `--scope global` is passed.
- For brainstorm, do multiple pressure-test rounds. One answer plus one confirmation is review, not brainstorm.
- **Dispatcher wait pattern**: use `--kind done` / `--kind turn_finished` (trellis-emitted system events), NOT a user `--tag` as the completion signal. CLI help lists `phase_done` / `question` as `--tag` examples but only `interrupt` is a reserved tag with hardcoded trellis behavior; the others are opaque user labels. Relying on a worker to run `send --tag <my_signal>` is unreliable — LLM workers commonly write the tag string into prose instead of running the actual CLI command. See `references/command-reference.md` "tag vs kind".
- Forum channels are event-sourced. Do not parse `events.jsonl` first; use `forum`, `thread`, `messages --thread`, and `context list`.
- `@mindfoldhq/trellis-core` owns reusable channel/thread state, event append, seq allocation, context/title projection, reducers, and task helpers. The CLI owns flags, terminal rendering, prompts, worker lifecycle, and process exits.
## Reference Files
- `references/workflows.md` — canonical collaboration patterns AF (peer brainstorm, spawned review, dispatch-and-wait, forum issue capture, interrupt-and-redirect, one-shot run).
- `references/forum.md` — forum channels, context, title, rename, changelog forums, thread filtering.
- `references/workers.md` — spawn, agent cards, context injection (`--file` / `--jsonl`), interrupts, kill semantics.
- `references/progress-debugging.md` — progress/raw inspection, stalled worker diagnosis, OOM guard, exit codes.
- `references/command-reference.md` — current CLI command reference (every subcommand, every flag, output conventions, scope/type model).
## Not For
- One static review where a markdown file and prompt are enough.
- Replacing normal tool calls with self-logging.
- Long-term memory retrieval. Use durable forum channels for actionable issues, and `trellis mem` (the `trellis-session-insight` skill) for session/history search.

View File

@@ -0,0 +1,480 @@
# Command Reference
Authoritative current command reference for `trellis channel` subcommands,
validated against the source in `packages/cli/src/commands/channel/`
(`index.ts` Commander wiring and each subcommand handler).
Every subcommand accepts `--scope <project|global>` unless noted; `project`
is the default and resolves against the current cwd's project bucket.
## Top-level
```
trellis channel <subcommand>
```
> Multi-agent collaboration runtime — spawn / coordinate / interrupt worker
> agents through a shared event log.
---
## Create / List
### `create <name>`
```bash
trellis channel create <name>
[--scope project|global] # default: project
[--type chat|forum] # default: chat
[--task <path>] # associated Trellis task dir
[--project <slug>]
[--labels a,b,c]
[--description <text>] # stable channel description
[--context-file <abs-path>] ... # repeatable
[--context-raw <text>] ... # repeatable
[--linked-context-file <abs-path>] # [deprecated alias]
[--linked-context-raw <text>] # [deprecated alias]
[--cwd <path>] # recorded in create event
[--by <agent>] # default: main
[--force] # overwrite existing channel
[--ephemeral] # hide from default list, prunable
```
Behavior:
- Appends a `create` event; immutable `type` (cannot mutate forum↔chat after).
- `--ephemeral` channels are hidden from `channel list` by default and are
the sweep target for `channel prune --ephemeral`.
- `--linked-context-*` are folded into `--context-*`; emit a deprecation
notice when used.
### `list`
```bash
trellis channel list
[--scope project|global]
[--json]
[--project <slug>] # substring match on task field
[--all] # include ephemeral (suffix '*')
[--all-projects] # scan every project bucket
```
Behavior:
- Default scope: current cwd's project. `--all-projects` scans every bucket.
- Pretty mode prints `NAME WORKERS EVENTS LAST KIND TYPE TASK`, sorted by
recency, with a footer noting hidden ephemeral count.
- `--json` switches to a JSON array.
---
## Chat Messages
### `send <name> [text]`
```bash
trellis channel send <name> [text]
--as <agent> # REQUIRED — author
[--scope project|global]
[--to <agents,csv>] # default: broadcast
[--stdin | --text-file <path>] # body from stdin or file
[--delivery-mode appendOnly|requireKnownWorker|requireRunningWorker]
```
Behavior:
- Body precedence: positional `[text]``--stdin``--text-file`.
- `--to` with one entry stores a string; multiple stores an array; omitted
means broadcast.
- `--delivery-mode` selects targeted-delivery validation:
- `appendOnly` (default-ish — just record),
- `requireKnownWorker` (the named target must have a `spawned` event),
- `requireRunningWorker` (the worker must currently be live).
- Prints the appended event as one JSON line on stdout.
> **Note:** `send` has **no** `--tag` and **no** `--kind` flag. See
> [`tag-vs-kind`](#tag-vs-kind--how-event-shape-is-actually-controlled) below.
### `messages <name>`
```bash
trellis channel messages <name>
[--scope project|global]
[--raw] # one JSON event per line
[--follow] # stream new events
[--last <N>] # last N matching events
[--since <seq>] # seq > N
[--kind <kind>] # one of CHANNEL_EVENT_KINDS
[--from <csv>] # author filter
[--to <target>] # routing target filter
[--thread <key>] # forum-only
[--action <thread-action>] # forum-only
[--no-progress] # hide progress events
```
Behavior:
- Auto-detects forum channels: with no filters it renders the thread board
instead of the event stream. `--thread` / `--action` are forum-only and
error against chat channels.
- `--kind` is validated against `CHANNEL_EVENT_KINDS` (single value, not
CSV — that's the `wait` side).
### `wait <name>`
```bash
trellis channel wait <name>
--as <agent> # REQUIRED — self for filter ctx
[--scope project|global]
[--timeout <Ns|Nm|Nh|Nms>] # parsed by parseDuration
[--from <a,b>] # author CSV
[--kind <k1,k2>] # CSV, OR semantics
[--thread <key>] # forum filter
[--action <thread-action>] # forum filter
[--to <target>] # default: own agent (broadcast + me)
[--include-progress] # also wake on progress events
[--all] # require every --from to match
```
Behavior:
- Streams matching events as JSON, one per line.
- Default `--to` filter is the caller's own agent (broadcast events still
match — broadcast + explicit-to-me).
- `--all` requires `--from` and blocks until every listed agent has produced
a matching event.
- **Timeout exits 124** and prints `timeout: still waiting on ...` to stderr
when `--all` was in play.
---
## tag-vs-kind — how event shape is actually controlled
There is **no `--tag` flag** anywhere in the v0.6.0 channel CLI; `--kind` is
not a legacy alias for any `--tag` flag.
Concrete model in the current source:
- `--kind` is the only event-type filter, and it is constrained to the
trellis-emitted whitelist (`CHANNEL_EVENT_KINDS` in
`packages/core/src/channel/internal/store/events.ts`):
- `create`, `join`, `leave`, `message`, `thread`, `context`, `channel`,
`spawned`, `killed`, `respawned`, `progress`, `done`, `error`,
`waiting`, `awake`, `undeliverable`, `interrupt_requested`,
`turn_started`, `turn_finished`, `interrupted`, `supervisor_warning`
- Passing anything else throws
`Invalid --kind '<x>'. Must be one of: …`.
- `--kind` lives on `wait` (CSV, OR semantics) and `messages` (single
value). `send` and `run` cannot emit a custom kind — every `send` writes
a `message` event.
- Mid-turn worker abort is **not** a tag. It is the dedicated
`channel interrupt` command, which appends an `interrupt_requested` /
`interrupted` pair and provider-level interrupts the worker.
Practical rule for dispatchers waiting on workers:
- Use `--kind done,turn_finished` for "worker finished a turn" — these are
system events that the supervisor fires automatically. Do not depend on
the worker LLM remembering to emit any custom signal.
- Use `trellis channel interrupt` (the command) only when you actually want
mid-turn abort behavior.
- Do **not** invent user-side tags as completion signals. There is no
`--tag` filter; a worker writing a custom string into its final message
is just text inside a `message` event and cannot be matched by `wait`.
Long bodies always go through stdin or a file:
```bash
trellis channel send T --as A --stdin < /tmp/message.md
trellis channel send T --as A --text-file /tmp/message.md
```
---
## Interrupt
### `interrupt <name> [text]`
```bash
trellis channel interrupt <name> [text]
--as <agent> # REQUIRED — caller
--to <agent> # REQUIRED — target worker
[--scope project|global]
[--stdin | --text-file <path>]
```
Behavior:
- Appends an `interrupt` event with `reason: "user"` and a replacement
instruction body; supervisor performs provider-level interrupt where
supported (Claude `/interrupt`, Codex turn cancel).
- Prints the appended event JSON on stdout.
---
## Workers
### `spawn <name>`
```bash
trellis channel spawn <name>
[--scope project|global]
[--agent <agent-name>] # loads .trellis/agents/<name>.md
[--provider claude|codex] # overrides agent file
[--as <worker-name>] # default: agent name
[--cwd <path>]
[--model <id>]
[--resume <id>] # session/thread id resume
[--timeout <Ns|Nm|Nh>] # auto-kill after duration
[--warn-before <Ns|Nm|Nh>] # supervisor_warning lead time
# default 5m, 0ms disables
[--file <path>] ... # glob, repeatable; inject content
[--jsonl <path>] ... # Trellis manifest, repeatable
[--by <agent>] # spawn-event author
# default: TRELLIS_CHANNEL_AS env or 'main'
[--inbox-policy explicitOnly|broadcastAndExplicit]
# default explicitOnly
[--idle-timeout <Ns|Nm|Nh>] # OOM-guard idle TTL
# default 5m, 0 disables
[--max-live-workers <n>] # spawn-time live-worker budget
# default 6, 0 disables
```
Behavior:
- Provider is validated against the adapter registry
(`packages/cli/src/commands/channel/adapters/`); current: `claude`,
`codex`.
- Worker stays inbox-idle until the first `send --to <worker>`.
- Records a `spawned` event with `pid`, `provider`, `agent`, `files`,
`manifests`.
- OOM-guard precedence: CLI flag → env var
(`TRELLIS_CHANNEL_WORKER_IDLE_TIMEOUT`,
`TRELLIS_CHANNEL_MAX_LIVE_WORKERS`) →
`.trellis/config.yaml#channel.worker_guard` → built-in defaults.
### `run [name]`
```bash
trellis channel run [name?]
[--agent <name>]
[--provider claude|codex]
[--as <worker-name>]
[--cwd <path>]
[--model <id>]
[--file <path>] ... # repeatable, glob
[--jsonl <path>] ... # repeatable
[--message <text> | --message-file <path> | --stdin]
[--timeout <Ns|Nm|Nh>] # default 5m
```
Behavior:
- One-shot. Auto-generates `run-<hex>` if `name` omitted.
- Creates an ephemeral channel (`createMode=run`), spawns a single worker,
sends the prompt, waits for `done`, prints the final assistant text to
stdout, then removes the channel on success. On failure the channel is
kept for inspection and exit code is 1.
> `run` has **no** `--tag` flag. Completion is detected via the `done`
> event the supervisor emits.
### `kill <name>`
```bash
trellis channel kill <name>
--as <agent> # REQUIRED — worker agent name
[--scope project|global]
[--force] # SIGKILL immediately
```
Behavior:
- Default path: SIGTERM → 8 s grace → SIGKILL escalation; the CLI writes a
`killed` event when SIGKILL was needed so the log stays truthful.
- Cleans `pid`, `worker-pid`, `config`, `spawnlock` sidecar files; keeps
`log`, `session-id`, `thread-id` for forensics / resume.
### `rm <name>`
```bash
trellis channel rm <name>
[--scope project|global]
```
Behavior:
- Kills any live workers, then deletes the entire channel directory.
- Prints `Removed channel '<name>'`.
### `prune`
```bash
trellis channel prune
[--scope project|global] # omitted: scan every project
[--all | --empty | --idle <Ns|Nm|Nh|Nd> | --ephemeral] # mutually exclusive
[--yes] # actually delete (default: dry-run)
[--dry-run] # default true; redundant with default
[--keep <names,csv>] # exclusion list
```
Behavior:
- Filter flags are mutually exclusive — error otherwise.
- Default is dry-run; `--yes` flips to real delete.
- Without `--scope`, scans **every** project bucket (intentional, repo-wide
cleanup); with `--scope project|global`, limited to that bucket.
- Live-worker channels are always skipped regardless of filter.
- Output: per-candidate line `name last-ts (reason)` plus a final summary.
---
## Forum Channels
### `post <name> <action>`
```bash
trellis channel post <name> <action>
--as <agent> # REQUIRED
[--scope project|global]
[--thread <key>] # required except action=opened
[--title <text>]
[--text <text> | --stdin | --text-file <path>]
[--description <text>] # stable thread description
[--status <status>]
[--labels a,b] # REPLACES thread labels
[--assignees a,b] # REPLACES assignees
[--summary <text>]
[--context-file <abs-path>] ...
[--context-raw <text>] ...
[--linked-context-file <abs-path>] # [deprecated alias]
[--linked-context-raw <text>] # [deprecated alias]
```
Behavior:
- `<action>` is free-form on the CLI surface; conventional values include
`opened`, `comment`, `status`, `labels`, `assignees`, `summary`,
`processed`.
- `action=rename` is rejected — use `thread rename` instead.
- `--labels` / `--assignees` are replace-semantics, not append.
- Output: appended event JSON on stdout.
### `forum <name>`
```bash
trellis channel forum <name>
[--scope project|global]
[--status <status>]
[--raw]
```
Behavior:
- Lists threads (reduced state). `--status` filters by current thread
status. `--raw` prints one JSON per thread.
### `thread <name> <thread>` / `thread rename`
```bash
trellis channel thread <name> <thread-key>
[--scope project|global]
[--raw]
trellis channel thread rename <name> <old-thread> <new-thread>
--as <agent> # REQUIRED
[--scope project|global]
```
Behavior:
- `thread <name> <key>` shows one thread's timeline:
header `<thread> [<status>] <title>`, then description / labels /
assignees / summary / timeline lines. `--raw` switches to raw events.
- `thread rename` is the only mutation; `post --action rename` is rejected.
---
## Context / Title
### `context add` / `context delete` / `context list`
```bash
trellis channel context add <name>
[--as <agent>] # default: main
[--scope project|global]
[--thread <key>] # thread-level instead of channel-level
[--file <abs-path>] ... # repeatable
[--raw <text>] ... # repeatable
# at least one of --file or --raw
trellis channel context delete <name>
[--as <agent>] # default: main
[--scope project|global]
[--thread <key>]
[--file <abs-path>] ...
[--raw <text>] ...
trellis channel context list <name>
[--scope project|global]
[--thread <key>]
[--raw] # one JSON entry per line
```
Behavior:
- `add` / `delete` append a `context` event and print the event JSON.
- `list` projects current context entries; pretty output is
`file <path>` / `raw <truncated text>` lines, `(no context)` when empty.
### `title set <name>` / `title clear <name>`
```bash
trellis channel title set <name>
--title <text> # REQUIRED
[--as <agent>] # default: main
[--scope project|global]
trellis channel title clear <name>
[--as <agent>] # default: main
[--scope project|global]
```
Behavior:
- Appends a `title` event projecting a stable display title onto the
channel. Output: event JSON.
---
## Hidden / Internal
| Command | Purpose |
|---|---|
| `channel __supervisor <channel> <worker> <config>` | Forked entry point invoked by `spawn`. Do not invoke directly. |
| `channel __parse-trace <adapter> <file>` | Dev helper — replays a recorded stream-json / wire trace through the matching adapter and prints the resulting channel events. Adapter is validated against the provider registry. |
---
## Event Model
`CHANNEL_EVENT_KINDS` (whitelist enforced by `parseChannelKind`):
`create`, `join`, `leave`, `message`, `thread`, `context`, `channel`,
`spawned`, `killed`, `respawned`, `progress`, `done`, `error`, `waiting`,
`awake`, `undeliverable`, `interrupt_requested`, `turn_started`,
`turn_finished`, `interrupted`, `supervisor_warning`.
`MEANINGFUL_EVENT_KINDS` (default-visible subset used by `wait` /
`messages` when no explicit `--kind` is given):
`create`, `join`, `leave`, `message`, `thread`, `context`, `channel`,
`spawned`, `killed`, `respawned`, `done`, `error`.
Non-meaningful kinds (e.g. `progress`, `waiting`, `awake`,
`supervisor_warning`, the `turn_*` / `interrupt*` set) still flow through
the store; opt in via `--kind` or `--include-progress`.
Forum channels are event-sourced; use the CLI reducers
(`forum`, `thread`, `context list`) for state projection.
---
## Output Conventions
- **Mutations** (`send`, `interrupt`, `post`, `context add/delete`,
`title set/clear`, `thread rename`) print the appended event as one JSON
line on **stdout**.
- **Streaming reads** (`wait`, `messages --follow`) print one JSON event
per line on stdout.
- **Pretty reads** (`list`, `messages`, `forum`, `thread`, `context list`)
print colored, padded tables / timelines.
- **`run`** prints only the final assistant text on stdout (so callers can
pipe); diagnostic notes go to stderr.
- **Errors** go through `chalk.red("Error:")` to stderr and `exit 1`.
- **`wait` timeout** specifically exits **124**.

View File

@@ -0,0 +1,233 @@
# Forum Channels
Forum channels are durable, topic-style channels. They are created with
`--type forum` at channel-creation time and are immutable after that. They are
not normal chat streams: the default read path is
**forum summary -> one thread timeline -> current context**.
## Forum vs Regular Channel
A channel's type is set with `--type` on `channel create` and never changes:
- `chat` (default) — flat message timeline. `channel messages` always renders
the event stream. Forum-only flags such as `--thread` and `--action` are
rejected here.
- `forum` — thread-oriented. `channel messages` without filters renders a
thread-board summary instead of raw events. The `post`, `forum`, `thread`,
and `thread rename` subcommands only apply to forum channels.
Both types share the same scope model (`--scope project` is the default;
`--scope global` puts the channel in the cross-project bucket).
## Create A Forum Channel
```bash
trellis channel create design-feedback \
--type forum \
--scope global \
--description "Cross-project design feedback board." \
--context-raw "One thread per design topic; close when resolved." \
--by main
```
Use `--scope project` for a board scoped to one repo, `--scope global` for a
cross-project board.
## Threads: Open, Comment, Status, Summary
Threads live inside a forum channel. Each thread is identified by a stable
`--thread <key>` (lowercase kebab-case is conventional). The first action on
a thread is `opened`; everything afterwards uses the same `--thread` key.
```bash
trellis channel post design-feedback opened \
--scope global \
--as main \
--thread login-empty-state \
--title "Empty state on the login screen" \
--description "Track design feedback for the new login empty state." \
--labels design,login \
--context-raw "Spotted during the 0.4 release review." \
--text-file /tmp/thread-open.md
trellis channel post design-feedback comment \
--scope global \
--as reviewer \
--thread login-empty-state \
--text-file /tmp/review.md
trellis channel post design-feedback status \
--scope global \
--as main \
--thread login-empty-state \
--status closed
trellis channel post design-feedback summary \
--scope global \
--as main \
--thread login-empty-state \
--summary "Adopted the option-B layout; ticket TRELLIS-123 owns the fix."
```
Key distinctions:
- `--description` is the **durable** thread description (the answer to "what
is this thread about?"). It is set on `opened` and edited by re-running
`post` with `--description`.
- `--text` / `--stdin` / `--text-file` is the **event body** — the comment or
payload attached to this specific timeline entry.
- `--labels` and `--assignees` are CSV and **replace** the current value; they
do not append.
- `--summary` is the rolling thread summary. Setting it on `status closed` is
the standard way to mark a thread resolved with context.
`--thread` is required for every action except `opened` (where it is also
required in practice — there is no anonymous thread).
## Read A Forum
```bash
trellis channel messages design-feedback --scope global
trellis channel forum design-feedback --scope global --status open
trellis channel thread design-feedback login-empty-state --scope global
trellis channel messages design-feedback --scope global --raw --thread login-empty-state
```
If a peer says "I commented on the forum", run `channel forum` first to see
which thread changed, then drill into that thread with `channel thread <name>
<thread>`. Do not jump straight to ad-hoc `events.jsonl` parsing.
## Context
Context entries are durable background that should always be in scope when
reading a channel or a thread. They are **not** timeline events; they are
projected separately and replayed for every reader.
Use the `context` subcommands. The legacy `--linked-context-file` /
`--linked-context-raw` flags on `create` and `post` are deprecated aliases
that fold into the canonical `--context-file` / `--context-raw`.
### Add Context
```bash
# Channel-level context (whole forum)
trellis channel context add design-feedback \
--scope global \
--raw "Upstream feedback board; please link tasks before opening threads."
# Thread-level context (one thread)
trellis channel context add design-feedback \
--scope global \
--thread login-empty-state \
--file "$PWD/.trellis/tasks/05-13-login-redesign/design.md"
```
- `--thread <key>` switches between channel-level and thread-level context.
- `--file` paths **must be absolute**; relative paths are rejected.
- `--raw` is plain text inline content.
- Both flags are repeatable; at least one is required for `add` / `delete`.
- `--as <agent>` records authorship; defaults to `main`.
### List Context
```bash
trellis channel context list design-feedback --scope global
trellis channel context list design-feedback --scope global --thread login-empty-state --raw
```
`--raw` on `list` emits one JSON entry per line (useful for piping); without
it you get a human-readable `file <path>` / `raw <truncated text>` listing.
An empty store prints `(no context)`.
### Delete Context
```bash
trellis channel context delete design-feedback \
--scope global \
--thread login-empty-state \
--raw "stale note"
```
You delete by **value**, not by id: pass the same `--file` or `--raw` value
that was added. Repeat the flag to delete multiple entries in one call.
### Reading Order
When reading a thread, work top-down:
1. Thread `description` (the durable "what is this about").
2. Context entries (channel-level + thread-level).
3. Timeline (`opened`, `comment`, `status`, `summary`).
If a context file is missing or unreadable, state that explicitly and
continue with the remaining data — do not fabricate the content.
## Title Projection
`title` projects a stable display title onto the channel without renaming the
storage address. The channel `name` you pass to every command stays the same.
```bash
trellis channel title set design-feedback \
--scope global \
--title "Design feedback board"
trellis channel title clear design-feedback --scope global
```
- `title set` requires `--title`.
- `--as <agent>` records authorship; defaults to `main`.
- This is a presentation-layer change. Tooling and scripts keep using the
original channel name.
## Thread Rename
`thread rename` is the correction path when a thread was opened with the
wrong key (typo, wrong slug convention, etc.). Threads do not support hard
deletion — rename is the supported corrective action.
```bash
trellis channel thread rename design-feedback old-key new-key \
--scope global \
--as main
```
- `--as <agent>` is **required**.
- `post <name> rename` is rejected — you must use `thread rename`.
## Deletion Discipline
Do not model single-comment deletion or hard thread deletion as normal
workflow. Forum threads are append-only collaboration history. To correct
state, use:
- `post ... status` to mark a thread closed / blocked / etc.
- `post ... summary` to record the resolution.
- `post ... --labels` to re-label (replaces the set).
- `thread rename` to correct a bad thread key.
## Internal Changelog Pattern
A common use of a global forum channel is an internal release / runtime
changelog. One thread per notable change keeps history searchable:
```bash
trellis channel create release-notes \
--type forum \
--scope global \
--description "Internal release and runtime changelog." \
--context-raw "One thread per notable change; close when shipped." \
--by main
trellis channel post release-notes opened \
--scope global \
--as main \
--thread release-2026-q1 \
--title "Channel threads and forum UX in 0.6" \
--description "Forum channel UX shipped in the 0.6 line." \
--labels channel,release \
--text-file /tmp/release-notes.md
```
Use stable, descriptive thread keys (e.g. `release-2026-q1`,
`runtime-event-schema-change`) so later readers can find them by name.

View File

@@ -0,0 +1,226 @@
# Progress And Debugging
Pretty output is for operators. Raw output is the audit log. Subcommands
(`forum`, `thread`, `messages`, `context`) are the audit *interface* — reach
for them before grepping `events.jsonl` by hand.
## Pretty vs `--raw`
`trellis channel messages <channel>` renders a compact, human-readable view:
timestamps, identities, kind, and a short body. It is meant for operators
scanning a channel, not for diagnostics.
Pretty output can and will truncate:
- long progress deltas (`text_delta`, partial tool args)
- tool names and command lines
- multi-line status fields and structured `detail` blobs
- forum thread titles past the column budget
When something looks "off" — a worker appears stuck, a progress line ends
mid-word, an action field shows `...` — switch to `--raw`. Raw mode emits
one JSON event per line exactly as it lives in `events.jsonl`, so nothing
is dropped.
```bash
# Pretty (operator view)
trellis channel messages <channel> --kind done --last 10
trellis channel messages <channel> --kind error --last 10
# Raw (diagnostic view) — one JSON per line
trellis channel messages <channel> --raw --kind progress --last 20
trellis channel messages <channel> --raw --last 50
```
Rule of thumb: never diagnose a worker from a truncated progress line.
### Rebuild Streaming Text
To reconstruct what a model actually streamed during a turn, concatenate
`detail.text_delta` from progress events:
```bash
trellis channel messages <channel> --raw --kind progress --last 80 \
| python -c 'import json,sys; [print((json.loads(l).get("detail") or {}).get("text_delta",""), end="") for l in sys.stdin if l.strip()]'
```
## Stalled Worker Diagnosis
Symptom: `trellis channel list` shows the worker as running, but no new
events appear in `messages` and `wait` keeps timing out.
Triage order:
1. **Locate the channel files.** Use `list --all --all-projects` if you are
not sure which bucket the channel lives in.
```bash
trellis channel list --all --all-projects
CHAN=~/.trellis/channels/<bucket>/<channel>
```
2. **Confirm the supervisor and worker PIDs are alive.**
```bash
cat "$CHAN/<worker>.pid" # supervisor PID
cat "$CHAN/<worker>.worker-pid" # actual CLI subprocess PID
ps -p "$(cat "$CHAN/<worker>.pid")"
ps -p "$(cat "$CHAN/<worker>.worker-pid")"
```
If the supervisor PID is gone but the channel still lists the worker,
you have a ghost entry — clean it with
`trellis channel kill <name> --as <worker> --force`.
3. **Tail the worker log.** This is the canonical place to see provider /
MCP / tool startup output that never makes it onto the channel.
```bash
tail -f "$CHAN/<worker>.log"
```
4. **Check the last raw events.** A worker that emitted `progress` but no
`message`/`done` is usually mid-stream or blocked on a tool call:
```bash
trellis channel messages <channel> --raw --last 50
```
Common "alive but silent" causes:
- Provider cold start before the first token (long, but eventually moves).
- A blocking MCP server during startup — visible in the worker log.
- Worker is waiting for a tool result whose subprocess hung.
- Prompt is huge / model is rate-limited; check provider-side errors in the
worker log.
## Progress Event Interpretation
A `progress` event represents an in-flight piece of work. Its shape varies
by `action` field, but the load-bearing fields are always under `detail`:
- `detail.text_delta` — incremental model output (concatenate across events
to rebuild the streamed reply).
- `detail.tool_name`, `detail.tool_input` — tool call about to run or
currently running.
- `detail.status` — short string used by long-running actions
(`starting`, `running`, `flushing`, `done`).
- `detail.action` — semantic label (e.g. `status` for thread heartbeats).
Progress events are **noisy** by design. `wait` ignores them unless you
pass `--include-progress`. When you do want to see them, prefer:
```bash
trellis channel messages <channel> --raw --kind progress --last 80
```
A stream that emits progress at a steady cadence but never closes with
`done`/`error`/`message` is the classic shape of a hung tool call —
inspect the worker log for the subprocess.
## Wait Semantics (Quick Reference)
`channel wait` watches `events.jsonl` from EOF and wakes on:
- `message`
- `done`
- `error`
- `killed`
- `progress` only with `--include-progress`
Useful filters:
```bash
trellis channel wait T --as main --from check --kind done --timeout 15m
trellis channel wait T --as main --from check,check-cx --kind done --all --timeout 15m
trellis channel wait T --as worker --tag interrupt --timeout 1h
trellis channel wait T --as main --thread release-note --action status --timeout 10m
```
Exit codes: `0` matched, `124` timeout, `1`/`2` errors. On `wait --all`
timeout, stderr names the workers still missing.
## Auditing `events.jsonl` — Use Subcommands, Not `grep`
Every channel persists its full history at `$CHAN/events.jsonl`. It is
tempting to `tail` / `grep` / `jq` this file directly during debugging.
Don't make it a habit, and **never** do it for forum channels.
Why subcommands first:
- `messages` already replays the file with filters (`--kind`, `--from`,
`--last`, `--tag`, `--thread`, `--action`) and gives you `--raw` for the
exact JSON. Anything you would write a one-liner for, `messages` already
does.
- `wait` consumes the same file with EOF semantics — re-implementing that
with `tail -f | jq` will drop events under load and misorder them under
rotation.
- `context` materializes a worker's inbox view, including cursor state.
Hand-rolled filters do not respect `<worker>.inbox-cursor`.
### Forum channels: never parse `events.jsonl` directly
Forum channels multiplex many logical threads onto a single `events.jsonl`.
Each event carries `thread`, `action`, and tag fields that the forum
subcommands know how to fold together. Parsing the file by hand will:
- Mix threads together and make a thread look incoherent.
- Miss thread lifecycle events (open / status / close) that change how
later events should be interpreted.
- Ignore worker inbox cursors, so you will "see" events a worker has
already consumed and assume they are pending.
Use the forum-aware views instead:
```bash
# List logical threads inside the forum channel
trellis channel forum list <channel>
# Inspect one thread end-to-end
trellis channel thread show <channel> <thread>
# Replay messages for a thread (supports --raw, --kind, --last)
trellis channel messages <channel> --thread <thread> --raw --last 100
# What a specific worker still has pending
trellis channel context <channel> --as <worker>
```
Direct reads of `events.jsonl` are reserved for the case where the CLI
itself is suspect — e.g. confirming an event was actually persisted, or
diffing against `<worker>.inbox-cursor` while debugging the supervisor.
## Common Failures
| Symptom | Cause | Fix |
|---|---|---|
| `trellis: command not found` | CLI not installed globally | `npm install -g @mindfoldhq/trellis` |
| `wait` exits immediately | wrong filter or identity collision | use distinct `--as`, inspect raw messages |
| zsh errors on message text | shell interpreted punctuation | use `--stdin` or `--text-file` |
| progress line is cut off | pretty output truncation | use `messages --raw --kind progress` |
| worker never speaks | provider startup / prompt / MCP delay | inspect `<worker>.log`, `ps`, raw events |
| channel not found in another cwd | project bucket mismatch | `cd` to project, use `--scope global`, or `list --all-projects` |
| ghost worker in list | supervisor died without cleanup | `trellis channel kill <name> --as <worker> --force` |
| forum thread looks scrambled | parsed `events.jsonl` directly | use `forum`, `thread`, `messages --thread` |
## Storage Layout
```text
~/.trellis/channels/
└── <bucket>/
└── <channel-name>/
├── events.jsonl
├── <channel>.lock
├── <worker>.log
├── <worker>.pid
├── <worker>.worker-pid
├── <worker>.config
├── <worker>.session-id
├── <worker>.thread-id
├── <worker>.inbox-cursor
└── <worker>.spawnlock
```
Agents normally use the CLI, not direct file reads. Direct file reads are
for debugging when CLI views are insufficient — and even then, never on a
forum channel's `events.jsonl`.

View File

@@ -0,0 +1,276 @@
# Workers And Agent Cards
Use workers when a peer agent should execute independently and report back
through the channel event log. A worker is a registered child process (claude
or codex) attached to a channel; the supervisor forwards inbox messages to it
and translates its output back into channel events.
## Spawn
```bash
trellis channel create impl-task --by dispatcher --cwd /path/to/repo
trellis channel spawn impl-task --provider codex --as codex-impl --timeout 30m
echo "Implement the schema for table X per .trellis/.../prd.md" \
| trellis channel send impl-task --as dispatcher --to codex-impl --stdin
trellis channel wait impl-task --as dispatcher --from codex-impl --kind done --timeout 30m
```
`spawn` forks a `channel __supervisor` worker that emits `spawned`, streams
`progress`, and should end with `done`, `error`, or `killed`. Workers stay
inbox-idle until a `send --to <worker>` (or a broadcast when
`--inbox-policy broadcastAndExplicit` is set) wakes them.
Key `spawn` flags:
- `--agent <name>` — load `.trellis/agents/<name>.md` (provider/model/as/system prompt defaults).
- `--provider <claude|codex>` — overrides the agent card; validated against the adapter registry.
- `--as <name>` — channel worker handle; defaults to the agent name.
- `--cwd <path>` — worker working directory (also the jail root for `--file`/`--jsonl`).
- `--model <id>` — model override.
- `--resume <id>` — resume an existing claude session / codex thread.
- `--timeout <duration>` — auto-kill after `30s` / `2m` / `1h`.
- `--warn-before <duration>` — supervisor_warning lead time (default `5m`; `0ms` disables).
- `--file <path>` (repeatable, glob-supported) — inject file content into the system prompt.
- `--jsonl <path>` (repeatable) — Trellis jsonl manifest (`{file, reason}` per line).
- `--by <agent>` — author of the `spawned` event (defaults to `$TRELLIS_CHANNEL_AS` or `main`).
- `--inbox-policy <explicitOnly|broadcastAndExplicit>` — default `explicitOnly`.
- `--idle-timeout <duration>` — OOM guard idle TTL (default `5m`; `0` disables).
- `--max-live-workers <n>` — spawn-time live-worker budget (default `6`; `0` disables).
The success event `spawned` records `pid`, `provider`, `agent`, the injected
`files`, and the resolved `manifests` so later spectators can audit context.
## Agent Cards
`--agent <name>` resolves to `.trellis/agents/<name>.md`. The card name must
match `[A-Za-z0-9._-]+`. The default Trellis install ships two cards:
- `.trellis/agents/check.md` — code-quality reviewer.
- `.trellis/agents/implement.md` — coding worker for implementation runs.
```yaml
---
name: check
description: Code quality check expert.
provider: claude
---
```
Frontmatter fields populate `spawn` defaults (provider, model, `as`); the
markdown body becomes the worker's system-prompt role. Cards do **not**
auto-attach task files — context must be injected explicitly per spawn (see
below).
Always inspect project cards before spawning a named agent:
```bash
ls .trellis/agents
sed -n '1,100p' .trellis/agents/check.md
```
## Context Injection
Two flags inject content into the worker's system prompt under a
`# CONTEXT FILES` block, assembled by `context-loader`:
- `--file <path>` — repeatable, glob-supported (`*`, `**`). Each match is
read and concatenated.
- `--jsonl <path>` — repeatable Trellis manifest where every line is
`{"file":"<path>","reason":"<why>"}`. The reason is preserved as a header
comment above each file's content.
Limits enforced by the loader:
- 1 MB hard cap per file (oversize → error).
- 200 KB per-file warning to stderr.
- 500 KB total assembled-context warning to stderr.
- Path-traversal jail: all resolved paths must stay under `--cwd`.
Example spawning a check agent against a task directory:
```bash
TASK=.trellis/tasks/05-13-example
trellis channel spawn cr-example --agent check --provider codex --as check-cx \
--file "$TASK/prd.md" \
--file "$TASK/design.md" \
--file "$TASK/implement.md" \
--jsonl "$TASK/check.jsonl" \
--cwd "$PWD" --timeout 30m
```
The `spawned` event records both the literal `files` array and any `manifests`
expanded from `--jsonl`, so the audit trail captures whatever the worker was
actually shown.
## Names And Routing
`--as` has two meanings:
- `send` / `wait` / `interrupt`: speaker identity (author of the resulting event).
- `spawn`: the worker handle that other agents address with `--to`.
Use explicit names when multiple workers or providers participate in one
channel:
```bash
trellis channel spawn cr-feature --agent check --as check-claude
trellis channel spawn cr-feature --agent check --provider codex --as check-cx
trellis channel wait cr-feature --as main \
--from check-claude,check-cx --kind done --all --timeout 15m
```
`--all` requires `--from` and blocks until every listed worker has produced a
matching event; timeout exits with code **124** and prints
`timeout: still waiting on ...` to stderr.
## Soft Interrupt — `interrupt`
`channel interrupt` is the cooperative redirect: it appends an `interrupt`
event (reason `"user"`) and, where the adapter supports it, issues a
provider-level turn interrupt with a replacement instruction. Use it when the
worker should drop its current turn and act on new input immediately, without
losing its session.
```bash
echo "Stop refactoring the parser — switch to fixing the failing test in src/foo.ts" \
| trellis channel interrupt impl-task --as dispatcher --to codex-impl --stdin
```
Flags:
- `--as <agent>` **(required)** — caller identity.
- `--to <agent>` **(required)** — target worker.
- `--scope <project|global>` — channel scope.
- `--stdin` / `--text-file <path>` / `[text]` — replacement instruction body.
The appended event has `kind: "interrupt"` — downstream `wait` / `messages`
filters can subscribe with `--kind interrupt` to react to redirections (e.g.
to log the rerouting, or to gate other workers behind a coordinator's
correction).
For low-priority hints that should wait for the worker's next turn, send a
plain tagged message instead:
```bash
echo "Check this when you reach the next turn." \
| trellis channel send impl-task --as dispatcher --to codex-impl \
--stdin --tag question
```
## Hard Interrupt — `kill` + `--resume`
Use `kill` when the worker must stop **now** (e.g. runaway loop, bad
instructions already in flight, or `interrupt` is not honored by the
adapter). The supervisor escalates SIGTERM → 8 s grace → SIGKILL; the CLI
writes a `killed` event when SIGKILL is needed so the event log stays
truthful.
```bash
trellis channel kill impl-task --as codex-impl
trellis channel spawn impl-task --as codex-impl --provider codex \
--resume "$(cat ~/.trellis/channels/<bucket>/impl-task/worker.session-id)"
echo "STOP — new instructions: ..." \
| trellis channel send impl-task --as dispatcher --to codex-impl --stdin
```
`kill` flags:
- `--as <agent>` **(required)** — names the worker (positional `<name>` is the channel).
- `--scope <project|global>`.
- `--force` — SIGKILL immediately (also kills the inner worker pid).
Side effects: cleans `pid`, `worker-pid`, `config`, `spawnlock` sidecar
files; keeps `log`, `session-id`, `thread-id` for forensics and resume.
When `interrupt` will not converge, kill + `--resume` is the guaranteed
redirection path.
## Worker OOM Guard
The OOM guard prevents orphaned/idle workers from accumulating and exhausting
host resources. It runs at every `spawn` and enforces two policies per
project bucket:
- **Idle TTL** — sweep workers whose last activity is older than the
configured threshold (default `5m`; `0` disables).
- **Live-worker budget** — refuse the new spawn if more than N workers are
already alive in the same project bucket (default `6`; `0` disables).
Precedence (highest first):
1. CLI flags: `--idle-timeout`, `--max-live-workers` on `spawn`.
2. Environment variables: `TRELLIS_CHANNEL_WORKER_IDLE_TIMEOUT`,
`TRELLIS_CHANNEL_MAX_LIVE_WORKERS`.
3. `.trellis/config.yaml` under `channel.worker_guard`.
4. Built-in defaults (`5m`, `6`).
Cleanup notices are written to stderr at spawn time so operators can see which
idle workers were swept and why a new spawn was rejected. The guard does not
touch ephemeral / `channel run` workers any differently — they are subject to
the same idle TTL and budget.
To audit current state, list workers via `channel list` (the `WORKERS`
column) and inspect per-channel `pid` / `worker-pid` sidecar files under
`~/.trellis/channels/<bucket>/<channel>/`.
## Worker Inbox APIs
The inbox is the channel surface workers wake on. Routing is controlled by
two knobs:
- **Inbox policy** (`spawn --inbox-policy`):
- `explicitOnly` (default) — worker only wakes on `send --to <worker>` or
`interrupt --to <worker>`.
- `broadcastAndExplicit` — also wakes on broadcasts (`send` with no `--to`).
- **Delivery mode** (`send --delivery-mode`):
- `appendOnly` — append the event regardless of worker state.
- `requireKnownWorker` — fail if no worker named in `--to` was ever spawned.
- `requireRunningWorker` — fail if the named worker is not currently alive.
Stricter delivery modes prevent silent message loss when callers expect a
running peer.
Inbox-relevant subcommands:
- `send <channel> [text]` — append a `message` event.
- `--as <agent>` **(required)** — author.
- `--to <agents>` — CSV; one → string, many → array; broadcast if omitted.
- `--stdin` / `--text-file <path>` / `[text]` — body source.
- `--delivery-mode <appendOnly|requireKnownWorker|requireRunningWorker>`.
- `interrupt <channel> [text]` — soft-interrupt redirect (see above).
- `wait <channel>` — block until matching events arrive.
- `--as <agent>` **(required)** — `self` for filter context.
- `--from <agents>` — CSV authors.
- `--kind <kind[,kind...]>` — CSV (OR semantics); supports `interrupt`,
`done`, `progress`, etc.
- `--to <target>` — defaults to own agent (broadcast + explicit-to-me).
- `--include-progress` — also wake on progress events.
- `--all` — require every `--from` agent to match (timeout → exit **124**).
- `--timeout <duration>``30s` / `2m` / `1h` / `1000ms`.
- `messages <channel>` — view / filter / follow the event stream.
- `--follow` to tail, `--kind` / `--from` / `--to` to filter, `--raw` for
JSON-per-line, `--no-progress` to hide progress noise.
A typical dispatcher loop:
```bash
# 1. Wake the worker.
echo "Run the failing test and report." \
| trellis channel send impl-task --as dispatcher --to codex-impl --stdin \
--delivery-mode requireRunningWorker
# 2. Block until it finishes.
trellis channel wait impl-task --as dispatcher \
--from codex-impl --kind done,error --timeout 30m
# 3. Read the final answer.
trellis channel messages impl-task --from codex-impl --last 1 --raw
```
All event-emitting subcommands (`send`, `interrupt`, `post`, `context add` /
`delete`, `title set` / `clear`, `thread rename`) print the appended event as
a single JSON line on stdout, making the inbox layer easy to script against.

View File

@@ -0,0 +1,128 @@
# Workflows
Use these patterns by intent. Prefer durable channels for multi-round work and
`channel run` for one-shot questions.
## Pattern A: Multi-round Brainstorm
Use when the user says "和 codex/claude 讨论一下", "brainstorm", or "拉一个 agent
进来一起看".
```bash
trellis channel create brainstorm-storage-layer --by main \
--task .trellis/tasks/05-XX-storage-adapter
trellis channel spawn brainstorm-storage-layer \
--agent architect --provider codex \
--file .trellis/tasks/05-XX-storage-adapter/prd.md \
--file .trellis/tasks/05-XX-storage-adapter/design.md \
--as cx-arch --timeout 30m
trellis channel send brainstorm-storage-layer \
--as main --to cx-arch --text-file /tmp/brainstorm-r1.md
trellis channel wait brainstorm-storage-layer \
--as main --kind done --from cx-arch --timeout 10m
```
Do not stop after one answer. Read the answer, identify vague areas, send a
new probe, and repeat until the result is executable.
Minimum round structure:
1. Direction split: should this live in an existing mechanism or a new one?
2. MVP boundary: v1, v2, and what would force v2 back into v1.
3. Data contract: events, schema, metadata, state source of truth, compatibility.
4. CLI / UX contract: command names, flags, errors, defaults, ambiguity.
5. Cross-layer risk and tests: shared helpers, drift points, release-blocking tests.
Optional rounds:
- Operations: logs, debugging, stuck workers, kill/restart, recovery.
- Migration/release: breaking status, manifest, changelog, docs-site.
- Opposition review: ask the peer agent to argue against the current plan.
Every probe should request concrete file paths, commands, schema, rejected
alternatives, and release-blocking issues. Reject hedging when a decision is
needed.
## Pattern B: Implement / Check Agent
Use when the user asks to dispatch implementation or review work.
```bash
TASK=.trellis/tasks/05-12-foo
trellis channel create cr-foo --task "$TASK" --by main
trellis channel spawn cr-foo \
--agent check \
--jsonl "$TASK/check.jsonl" \
--file "$TASK/prd.md" \
--file "$TASK/design.md" \
--file "$TASK/implement.md" \
--cwd "$PWD" --timeout 15m
trellis channel send cr-foo --as main --to check --text-file /tmp/cr-brief.md
trellis channel wait cr-foo --as main --kind done --from check --timeout 15m
trellis channel messages cr-foo --kind message --from check --tag final_answer
```
For implement work, use `--agent implement` and send an implementation brief.
For check work, include the exact diff scope, relevant specs, and validation
already run.
## Pattern C: Parallel Reviewers
Use one channel and distinct worker names.
```bash
trellis channel create cr-feature --by main --ephemeral
trellis channel spawn cr-feature --agent check \
--jsonl "$TASK/check.jsonl" --file "$TASK/prd.md" --file "$TASK/design.md" \
--timeout 15m
trellis channel spawn cr-feature --agent check --provider codex --as check-cx \
--jsonl "$TASK/check.jsonl" --file "$TASK/prd.md" --file "$TASK/design.md" \
--timeout 15m
trellis channel send cr-feature --as main --to check --text-file /tmp/cr-brief.md
trellis channel send cr-feature --as main --to check-cx --text-file /tmp/cr-brief.md
trellis channel wait cr-feature --as main --kind done --from check,check-cx --all --timeout 15m
```
`--all` means every listed worker must emit a matching event.
## Pattern D: One-shot Worker
```bash
trellis channel run --provider codex --message "say hi in 3 words" --timeout 1m
trellis channel run --agent plan --message-file /tmp/plan-question.md --timeout 10m
```
On success, `run` removes the ephemeral channel. On error/timeout/killed, it
keeps the channel and prints the path for inspection.
## Pattern E: Forum Channel
Use for issue forums, topic-style feedback, release todos, agent findings, and
internal changelogs. Read `forum.md` for the full model.
## Pattern F: Take Over Existing Thread
If the user gives a forum/thread name, restore context yourself:
```bash
trellis channel forum <board> --scope global
trellis channel thread <board> <thread> --scope global --raw
trellis channel context list <board> --scope global --thread <thread>
trellis channel messages <board> --scope global --raw --thread <thread>
```
Output a constraint summary, not a transcript dump:
- user-level problem
- context files that affect this repo
- current-version versus future-version requirements
- whether current code/design satisfies it
- next action or comment to append

View File

@@ -0,0 +1,98 @@
---
name: trellis-check
description: "Comprehensive quality verification: spec compliance, lint, type-check, tests, cross-layer data flow, code reuse, and consistency checks. Use when code is written and needs quality verification, before committing changes, or to catch context drift during long sessions."
---
# Code Quality Check
Comprehensive quality verification for recently written code. Combines spec compliance, cross-layer safety, and pre-commit checks.
---
## Step 1: Identify What Changed
```bash
git diff --name-only HEAD
git status
```
## Step 2: Read Task Artifacts and Applicable Specs
Read the current task artifacts in order:
- `prd.md`
- `design.md` if present
- `implement.md` if present
```bash
python ./.trellis/scripts/get_context.py --mode packages
```
For each changed package/layer, read the spec index and follow its **Quality Check** section:
```bash
cat .trellis/spec/<package>/<layer>/index.md
```
Read the specific guideline files referenced — the index is a pointer, not the goal.
## Step 3: Run Project Checks
Run the project's lint, type-check, and test commands. Fix any failures before proceeding.
## Step 4: Review Against Checklist
### Code Quality
- [ ] Linter passes?
- [ ] Type checker passes (if applicable)?
- [ ] Tests pass?
- [ ] No debug logging left in?
- [ ] No suppressed warnings or type-safety bypasses?
### Test Coverage
- [ ] New function → unit test added?
- [ ] Bug fix → regression test added?
- [ ] Changed behavior → existing tests updated?
### Spec Sync
- [ ] Does `.trellis/spec/` need updates? (new patterns, conventions, lessons learned)
> "If I fixed a bug or discovered something non-obvious, should I document it so future me won't hit the same issue?" → If YES, update the relevant spec doc.
## Step 5: Cross-Layer Dimensions (if applicable)
Skip this step if your change is confined to a single layer.
### A. Data Flow (changes touch 3+ layers)
- [ ] Read flow traces correctly: Storage → Service → API → UI
- [ ] Write flow traces correctly: UI → API → Service → Storage
- [ ] Types/schemas correctly passed between layers?
- [ ] Errors properly propagated to caller?
### B. Code Reuse (modifying constants, creating utilities)
- [ ] Searched for existing similar code before creating new?
```bash
grep -r "pattern" src/
```
- [ ] If 2+ places define same value → extracted to shared constant?
- [ ] After batch modification, all occurrences updated?
### C. Import/Dependency (creating new files)
- [ ] Correct import paths (relative vs absolute)?
- [ ] No circular dependencies?
### D. Same-Layer Consistency
- [ ] Other places using the same concept are consistent?
---
## Step 6: Report and Fix
Report violations found and fix them directly. Re-run project checks after fixes.

View File

@@ -0,0 +1,85 @@
---
name: trellis-meta
description: "Understand and customize the local Trellis architecture inside a user project. Use when modifying .trellis plus platform hooks, settings, agents, skills, commands, prompts, workflows, the channel runtime (trellis channel), bundled runtime agents under .trellis/agents/, selectable workflow templates, registry-backed spec refresh, cross-session memory (trellis mem) generated by trellis init, or AI-facing bundled skills (trellis-channel, trellis-session-insight, trellis-spec-bootstrap) and bundled-skill auto-dispatch flow."
---
# Trellis Meta
This skill is for local Trellis users who have already run `trellis init` in a project. After reading it, an AI should understand the Trellis architecture, operating model, and customization entry points inside that user project, then modify the generated `.trellis/` and platform directory files according to the user's request.
Trellis v0.6 adds three architectural surfaces on top of the pre-v0.6 workflow / persistence / platform model. First, a multi-agent collaboration runtime: `trellis channel` coordinates multiple AI worker processes through project-scoped JSONL event logs at `~/.trellis/channels/<project>/<channel>/events.jsonl`, with worker OOM guard, forum/thread channels, durable idempotency keys, and bundled `.trellis/agents/{check,implement}.md` runtime definitions. Second, cross-session memory: `trellis mem list | search | context | extract | projects` reads raw Claude Code, Codex, and Pi Agent JSONL already on disk, slices by `--phase brainstorm|implement|all`, and never uploads anything. Third, a dual-package npm release: `@mindfoldhq/trellis` (CLI) and `@mindfoldhq/trellis-core` (SDK with `/channel`, `/task`, `/mem`, `/testing` subpaths) ship in lockstep on one version. Treat these as first-class customization surfaces alongside the per-platform integration files.
The default operating scope is local files in the user project:
- `.trellis/`: workflow, config, tasks, spec, workspace, scripts, bundled runtime agents, and runtime state.
- Platform directories: `.claude/`, `.codex/`, `.cursor/`, `.opencode/`, `.kiro/`, `.gemini/`, `.qoder/`, `.codebuddy/`, `.github/`, `.factory/`, `.pi/`, `.reasonix/`, `.kilocode/`, `.agent/`, `.devin/`, and similar directories. Pi additionally exposes a native `trellis_subagent` tool with `single` / `parallel` / `chain` dispatch modes, throttled progress cards, and `isTrellisAgent()` validation on top of the file layout. Reasonix stores both workflow skills and subagent skills as `.reasonix/skills/<name>/SKILL.md`; subagent skills carry `runAs: subagent` frontmatter.
- Shared skill layer: `.agents/skills/`.
- User-owned channel store outside the project tree: `~/.trellis/channels/<project>/<channel>/events.jsonl`.
- Raw platform conversation logs queryable via `trellis mem`: `~/.claude/projects/`, `~/.codex/sessions/`, and `~/.pi/agent/sessions/` (OpenCode adapter degraded for the v0.6 line).
Do not assume the user has the Trellis source repository. Do not default to modifying the global npm install directory or `node_modules` — both `@mindfoldhq/trellis` and `@mindfoldhq/trellis-core` ship as published packages sharing one version and one git tag per release.
## How To Use
1. Read `references/local-architecture/overview.md` first to establish the local Trellis system model.
2. If the request involves a specific AI tool, read `references/platform-files/platform-map.md` and the relevant platform file notes.
3. If the request involves multi-agent dispatch or channel workers, read `references/local-architecture/multi-agent-channel.md` and the bundled `.trellis/agents/` files.
4. If the user wants to change behavior, read `references/customize-local/overview.md`, then open the specific customization topic.
5. Before editing, read the actual files in the user project and treat local content as authoritative.
## References
### Local Architecture
- `references/local-architecture/overview.md`: The layered local Trellis architecture (workflow / persistence / platform / channel runtime) and customization principles.
- `references/local-architecture/generated-files.md`: Files generated by `trellis init` and their customization boundaries, including `.trellis/agents/`.
- `references/local-architecture/workflow.md`: Phases, routing, workflow-state blocks, and selectable workflow templates (`native`, `tdd`, `channel-driven-subagent-dispatch`, marketplace) in `.trellis/workflow.md`.
- `references/local-architecture/task-system.md`: Task directories, active task, JSONL context, parent/child task trees, and task runtime.
- `references/local-architecture/spec-system.md`: How `.trellis/spec/` is organized, injected, and refreshed from a `registry.spec` source.
- `references/local-architecture/workspace-memory.md`: `.trellis/workspace/` journals plus `trellis mem` cross-session recall and the `@mindfoldhq/trellis-core/mem` SDK.
- `references/local-architecture/context-injection.md`: Hooks, sub-agent preludes, and channel-runtime worker inbox routing.
- `references/local-architecture/multi-agent-channel.md`: `trellis channel` subcommands, project-scoped event store, forum/thread channels, worker OOM guard, durable idempotency, and bundled `.trellis/agents/` runtime agents.
- `references/local-architecture/bundled-skills.md`: Auto-dispatched bundled skills (`trellis-meta`, `trellis-spec-bootstrap`, `trellis-session-insight`) and how `getBundledSkillTemplates()` ships them to every platform skill root.
### Platform Files
- `references/platform-files/overview.md`: How shared `.trellis/` files relate to platform directories and the four platform integration modes (hook-driven, agent prelude, main-session workflow, channel runtime).
- `references/platform-files/platform-map.md`: Platform directories and paths for skills, agents, hooks, and extensions across all 15 supported platforms including Reasonix and Pi's native `trellis_subagent` extension.
- `references/platform-files/hooks-and-settings.md`: How settings/config files, hooks, plugins, and extensions connect to Trellis; covers `channel.worker_guard.*` and `codex.dispatch_mode`.
- `references/platform-files/agents.md`: Per-platform `trellis-research` / `trellis-implement` / `trellis-check` sub-agent files plus bundled `.trellis/agents/{check,implement}.md` for the channel runtime.
- `references/platform-files/skills-and-commands.md`: Differences between skills, commands, prompts, and workflows, plus how to change them.
### Local Customization
- `references/customize-local/overview.md`: Choose the right local customization entry point for the user's request.
- `references/customize-local/change-workflow.md`: Change phases, routing, next actions, workflow-state, and the selected workflow template.
- `references/customize-local/change-task-lifecycle.md`: Change task creation, status, archive behavior, parent/child links, archive slug collision handling, and lifecycle hooks.
- `references/customize-local/change-context-loading.md`: Change how tasks, specs, journals, hook context, channel inbox messages, and `trellis mem` recall are loaded.
- `references/customize-local/change-hooks.md`: Change platform hooks, settings, task lifecycle hooks (`hooks.after_*`), and shell session bridges.
- `references/customize-local/change-agents.md`: Change research, implement, and check agent behavior across platform sub-agents, bundled channel runtime agents, and the Codex `dispatch_mode` toggle.
- `references/customize-local/change-skills-or-commands.md`: Add or modify local skills, commands, prompts, and workflows; covers upstream bundled-skill auto-dispatch.
- `references/customize-local/change-spec-structure.md`: Adjust the project spec structure under `.trellis/spec/`, including registry-backed sources.
- `references/customize-local/add-project-local-conventions.md`: Put team rules into project-local specs or local skills.
## Current Rules
- `.trellis/workflow.md` is the local workflow source of truth; its initial content was selected from a workflow template (built-in `native`, `tdd`, `channel-driven-subagent-dispatch`, or a marketplace template) at `trellis init` time and can be re-selected via `trellis workflow --template <id>`. Missing `.trellis/agents/<name>.md` files referenced by the active template trigger a non-blocking stderr warning pointing at `trellis update`.
- `.trellis/config.yaml` is the project-level Trellis configuration entry point. It hosts task lifecycle hooks (`hooks.after_create` / `after_start` / `after_finish` / `after_archive`), journal shape (`session_commit_message` / `max_journal_lines` / `session_auto_commit`), channel worker guard (`channel.worker_guard.idle_timeout` / `max_live_workers`), Codex dispatch mode (`codex.dispatch_mode: inline | sub-agent`), and the spec registry block (`registry.spec.source` + `registry.spec.template`).
- `.trellis/spec/` stores the user's project-specific coding conventions and design constraints. When `registry.spec` is set, files are refreshed by `trellis update`; local edits surface as "modified by user" conflicts in `.trellis/.template-hashes.json`.
- `.trellis/tasks/` stores task PRDs, design notes, implement plans, research files, and JSONL context. Tasks form parent/child trees: `task.py create --parent <slug>`, `task.py add-subtask <parent> <child>`, `task.py remove-subtask <parent> <child>`, and `task.py list-context <task>`. `task.py create` rejects a slug already present in `.trellis/tasks/archive/**`.
- `.trellis/workspace/` stores **deliberately written** developer journals. Raw cross-session dialogue is **not** stored here — it lives on disk under `~/.claude/projects/`, `~/.codex/sessions/`, and `~/.pi/agent/sessions/` and is recovered via `trellis mem search|extract|context`. The bundled `trellis-session-insight` skill teaches when to reach for `mem`.
- `.trellis/agents/{check,implement}.md` are bundled, platform-agnostic channel runtime agent definitions loaded by `trellis channel spawn --agent <name>`. Editable; `trellis update` backfills missing ones. Editing the per-platform `trellis-implement.md` / `trellis-check.md` does **not** change channel-runtime worker behavior.
- `~/.trellis/channels/<project>/<channel>/events.jsonl` is the channel runtime event log per project per channel. User-owned, file-locked sequence numbering, durable `idempotencyKey` support; never under `.trellis/`.
- Bundled multi-file skills (`trellis-meta`, `trellis-spec-bootstrap`, `trellis-session-insight`, `trellis-channel`) are auto-dispatched to every platform skill root by `getBundledSkillTemplates()` in `packages/cli/src/templates/common/index.ts`. Dropping a new directory under `packages/cli/src/templates/common/bundled-skills/` (upstream) ships it to every platform on the next `trellis update`.
- Platform settings/config files decide which hooks, agents, skills, commands, prompts, and workflows actually run. Reasonix has no settings file — behavior is encoded inside skill frontmatter.
- `.trellis/.template-hashes.json` and `.trellis/.runtime/` are management/runtime state files. Confirm necessity before editing them.
## Do Not
- Do not treat Trellis upstream source code as the default target for local customization.
- Do not modify the global npm install directory or `node_modules/@mindfoldhq/trellis` or `node_modules/@mindfoldhq/trellis-core` to implement project needs; both packages ship in lockstep.
- Do not overwrite user-modified local files with default templates; check `.trellis/.template-hashes.json` first and prefer `.new` sidecar files over destructive overwrites.
- Do not put team-private project rules into any public bundled skill (`trellis-meta`, `trellis-spec-bootstrap`, `trellis-session-insight`, `trellis-channel`); put project rules in `.trellis/spec/`, a project-local skill, the current task, or the workspace journal — `trellis update` will overwrite anything inside a bundled skill directory.
- Do not hand-edit `~/.trellis/channels/<project>/<channel>/events.jsonl`; sequence numbers are assigned under a file lock and replay-safe writes go through the `trellis channel` CLI or the `@mindfoldhq/trellis-core/channel` SDK.
- Do not edit `.claude/agents/trellis-implement.md` (or any other per-platform sub-agent file) when the goal is to change channel runtime worker behavior — edit `.trellis/agents/<name>.md` instead.
- Do not describe removed or never-shipped mechanisms as current Trellis behavior; cross-check against the local `.trellis/config.yaml` and the installed CLI's `trellis --help` before claiming a knob exists.

View File

@@ -0,0 +1,83 @@
# Add Project-Local Conventions
Often the user does not need to change Trellis mechanics; they need local AI to understand their team's conventions. In that case, prefer `.trellis/spec/` or a project-local skill instead of editing `trellis-meta`.
## Where To Put Things
| Content type | Location |
| --- | --- |
| Rules code must follow | `.trellis/spec/<layer>/` |
| Cross-layer thinking methods | `.trellis/spec/guides/` |
| AI capability for a project-specific flow | Platform-local skill |
| One-off task material | `.trellis/tasks/<task>/` |
| Session summary | `.trellis/workspace/<developer>/journal-N.md` |
## Create A Project-Local Skill
If the user wants AI to know "how this project customizes Trellis," create a local skill:
```text
.claude/skills/trellis-local/
└── SKILL.md
```
Example:
```md
---
name: trellis-local
description: "Project-local Trellis customizations for this repository. Use when changing this project's Trellis workflow, hooks, local agents, or team-specific conventions."
---
# Trellis Local
## Local Scope
This skill documents this repository's Trellis customizations only.
## Custom Workflow Rules
- ...
## Local Hook Changes
- ...
## Local Agent Changes
- ...
```
For multi-platform projects, place equivalent versions in other platform skill directories, or use `.agents/skills/` for platforms that support the shared layer.
## Write To `.trellis/spec/`
If the content is a coding convention, write it to spec. Examples:
```text
.trellis/spec/backend/error-handling.md
.trellis/spec/frontend/components.md
.trellis/spec/guides/cross-platform-thinking-guide.md
```
After writing it, update the corresponding `index.md` so AI can find the new rule from the entry point.
## Make The Current Task Use New Conventions
After writing a spec, add it to the current task context:
```bash
python ./.trellis/scripts/task.py add-context <task> implement ".trellis/spec/backend/error-handling.md" "Error handling conventions"
python ./.trellis/scripts/task.py add-context <task> check ".trellis/spec/backend/error-handling.md" "Review error handling"
```
## Do Not Store Project-Private Rules In `trellis-meta`
`trellis-meta` is a public skill for understanding Trellis architecture and local customization entry points. Put project-private content in:
- `.trellis/spec/`
- a project-local skill
- the current task
- workspace journal
This prevents future updates to Trellis's built-in `trellis-meta` from overwriting the team's own conventions.

View File

@@ -0,0 +1,56 @@
# Change Local Agents
When the user wants to change `trellis-research`, `trellis-implement`, or `trellis-check` behavior, edit platform agent files in the user project.
## Read These Files First
1. Target platform agent directory
2. `.trellis/workflow.md` Phase 2 / research routing
3. Current task `prd.md`
4. Current task `implement.jsonl` / `check.jsonl`
5. Relevant hook or agent prelude
## Common Paths
| Platform | Path |
| --- | --- |
| Claude Code | `.claude/agents/trellis-*.md` |
| Cursor | `.cursor/agents/trellis-*.md` |
| OpenCode | `.opencode/agents/trellis-*.md` |
| Codex | `.codex/agents/trellis-*.toml` |
| Kiro | `.kiro/agents/trellis-*.json` |
| Gemini CLI | `.gemini/agents/trellis-*.md` |
| Qoder | `.qoder/agents/trellis-*.md` |
| CodeBuddy | `.codebuddy/agents/trellis-*.md` |
| Factory Droid | `.factory/droids/trellis-*.md` |
| Pi Agent | `.pi/agents/trellis-*.md` |
| Reasonix | `.reasonix/skills/trellis-*/SKILL.md` (subagent frontmatter) |
| ZCode | `.zcode/cli/agents/trellis-*.md` |
Use the actual paths in the user project as authoritative.
## Common Needs
| Need | Which agent to edit |
| --- | --- |
| Research must write files, not only reply in chat | `trellis-research` |
| Certain local specs must be read before implementation | `trellis-implement` + `implement.jsonl` configuration rules |
| Specific commands must run during checking | `trellis-check` |
| Agent must not modify certain directories | The corresponding agent's write boundary instructions |
| Agent output format must be fixed | The corresponding agent's final/reporting instructions |
## Modification Principles
1. **Preserve role boundaries**: research investigates and persists; implement writes implementation; check reviews and fixes.
2. **Do not hard-code project specs into agents**: long-term specs belong in `.trellis/spec/`; agents are responsible for reading them.
3. **Make read order explicit**: active task -> PRD -> info -> JSONL -> spec/research.
4. **Make write boundaries explicit**: which directories may be written and which may not.
5. **Synchronize across platforms**: when the user configured multiple platforms, decide whether to change only the current platform or all platform agents.
## Agent Pull Platforms
If an agent file contains a prelude for "read task/context after startup," do not remove those steps when editing. Otherwise the agent will work only from chat context and bypass Trellis's core mechanism.
## Hook Push Platforms
If context is injected by a hook, the agent file should still retain responsibility boundaries. Do not remove PRD/spec requirements from the agent just because a hook injects context.

View File

@@ -0,0 +1,84 @@
# Change Local Context Loading
Context loading determines when AI reads workflow, task, spec, research, workspace, and git status. Read this page when the user says "AI does not know the current task," "the agent did not read specs," or "there is too much/too little context."
## Read These Files First
1. `.trellis/workflow.md`
2. `.trellis/scripts/get_context.py`
3. `.trellis/scripts/common/session_context.py`
4. `.trellis/scripts/common/task_context.py`
5. `.trellis/scripts/common/active_task.py`
6. Current platform hooks or agent files
7. The current task's `implement.jsonl` / `check.jsonl`
## Context Sources
| Source | Purpose |
| --- | --- |
| `.trellis/workflow.md` | Workflow and next-action hints. |
| `.trellis/tasks/<task>/prd.md` | Current task requirements. |
| `.trellis/tasks/<task>/design.md` | Complex task technical design. |
| `.trellis/tasks/<task>/implement.md` | Complex task execution plan. |
| `.trellis/tasks/<task>/implement.jsonl` | Spec/research to read before implementation. |
| `.trellis/tasks/<task>/check.jsonl` | Spec/research to read during checking. |
| `.trellis/spec/` | Project specs. |
| `.trellis/workspace/` | Session records. |
| git status | Current working tree changes. |
## Common Needs And Edit Points
| Need | Edit point |
| --- | --- |
| Inject more/less information in new sessions | `session_context.py` or the platform `session-start` hook. |
| Change hints on each user input | `[workflow-state:STATUS]` block in `.trellis/workflow.md`. The `inject-workflow-state` hook is parser-only and reads the block verbatim. |
| Agent did not read specs | Task JSONL, agent prelude, `inject-subagent-context` hook. |
| Active task is lost | `active_task.py` and platform session identity propagation. |
| Change JSONL validation rules | `task_context.py`. |
## JSONL Rules
`implement.jsonl` / `check.jsonl` are the key context loading interface:
```jsonl
{"file": ".trellis/spec/backend/index.md", "reason": "Backend conventions"}
{"file": ".trellis/tasks/04-28-x/research/api.md", "reason": "API research"}
```
Include only spec/research files. Do not put code files that will be modified into these manifests; agents read code files themselves during implementation.
## Change Session Context
If the user wants every new session to see more project state, edit:
- `.trellis/scripts/common/session_context.py`
- the corresponding platform `session-start` hook
Context cannot grow without bound. Prefer injecting indexes and paths so the AI can read detailed files on demand.
## Change Sub-Agent Context
First determine which mode the platform uses:
- hook push: edit the `inject-subagent-context` hook.
- agent pull: edit the read steps in the corresponding `trellis-implement` / `trellis-check` agent file.
In both modes, make sure the agent ultimately reads:
1. active task
2. the corresponding JSONL
3. spec/research referenced by the JSONL
4. `prd.md`
5. `design.md` if present
6. `implement.md` if present
## Troubleshooting Order
```bash
python ./.trellis/scripts/task.py current --source
python ./.trellis/scripts/task.py list-context <task>
python ./.trellis/scripts/task.py validate <task>
python ./.trellis/scripts/get_context.py --mode packages
```
Confirm the task and JSONL are correct before editing hooks/agents.

View File

@@ -0,0 +1,57 @@
# Change Local Hooks
Hooks are the automation layer that connects a platform to Trellis. When the user wants to change "when context is injected," "how shell commands inherit a session," or "which files are read before an agent starts," hooks are usually the edit point.
## Read These Files First
1. Target platform settings/config, such as `.claude/settings.json`, `.codex/hooks.json`, `.cursor/hooks.json`, `.trae/hooks.json`
2. Target platform hooks directory
3. `.trellis/scripts/common/active_task.py`
4. `.trellis/scripts/common/session_context.py`
5. `.trellis/workflow.md`
## Common Hook Types
| Hook | Purpose |
| --- | --- |
| session-start | Injects a Trellis overview when a session starts, clears, or compacts. |
| workflow-state | Injects a state hint on each user input. |
| sub-agent context | Injects PRD/spec/research before an agent starts. |
| shell session bridge | Lets `task.py` commands in shell see the same session identity. |
## Modification Steps
1. Find the hook registration in settings/config.
2. Confirm the registered script path exists.
3. Read the hook script and identify inputs, outputs, and called `.trellis/scripts/`.
4. Modify hook behavior.
5. If the hook depends on workflow content, synchronize `.trellis/workflow.md`.
## Example: Change New-Session Injection Content
First find the session-start hook:
```text
.claude/settings.json
.claude/hooks/session-start.py
```
If the hook ultimately calls `.trellis/scripts/get_context.py` or `session_context.py`, editing the local script is usually more robust than hard-coding content in the hook.
## Example: Agent Did Not Read JSONL
First confirm:
```bash
python ./.trellis/scripts/task.py current --source
python ./.trellis/scripts/task.py validate <task>
```
If the task and JSONL are correct, determine whether the platform uses hook push or agent pull. For hook push, edit `inject-subagent-context`; for agent pull, edit the agent file.
## Notes
- Settings handle registration, hook scripts handle behavior; inspect both together.
- Different platforms support different hook events. Do not directly copy another platform's settings.
- Hooks should read project-local `.trellis/`; they should not depend on Trellis upstream source paths.
- Hook failures should produce visible errors so AI does not silently lose context.

View File

@@ -0,0 +1,123 @@
# Change Local Skills, Commands, Prompts, And Workflows
When the user wants to change AI entry points, auto-trigger rules, or explicit command behavior, edit skills, commands, prompts, or workflows in local platform directories.
Before editing, classify the skill you are about to touch:
- **Bundled upstream skill** — `trellis-meta`, `trellis-spec-bootstrap`, `trellis-session-insight`, `trellis-channel`. Source of truth lives in the Trellis CLI repo under `packages/cli/src/templates/common/bundled-skills/<name>/`; auto-dispatched to every platform's skill root by `getBundledSkillTemplates()` on `trellis init` / `trellis update`. Local edits here are tracked by `.trellis/.template-hashes.json` and will be flagged on the next update.
- **Project-local skill** — anything else under `.{platform}/skills/`. Owned by the user; not refreshed by `trellis update`.
The remainder of this file uses "skill" for the local file; the override and conflict rules differ between the two cases.
## Read These Files First
1. `.trellis/workflow.md`
2. Target platform skill/command/prompt/workflow directory
3. Related agent or hook files
4. Whether project rules already exist in `.trellis/spec/`
5. `.trellis/.template-hashes.json` — confirms whether the skill you are about to edit is upstream-owned (entry present) or project-local (entry absent)
## Which Entry Type To Choose
| Goal | Recommendation |
| --- | --- |
| AI should automatically know a capability | Add or modify a skill. |
| User wants to trigger manually with a command | Add or modify a command/prompt/workflow. |
| Team project conventions | Prefer `.trellis/spec/` or a project-local skill — never a bundled skill directory. |
| Tweak a bundled skill (`trellis-meta` et al.) for the user's own project | Create a project-local sibling skill (different name) that overrides intent, or edit `.trellis/spec/`. Edits inside the bundled skill directory survive only until the next `trellis update` and will need a "keep" choice each time. |
| Contribute the change back upstream | Edit `packages/cli/src/templates/common/bundled-skills/<name>/` in the Trellis CLI repo, not the deployed copy. |
| Change Trellis flow semantics | Synchronize `.trellis/workflow.md`. |
## Modify A Skill
A skill is usually:
```text
<skill-name>/
├── SKILL.md
└── references/
```
`SKILL.md` should be short and responsible for triggering/routing. Put long content in `references/` so AI can read it on demand.
The frontmatter description should specify when to use the skill. Example:
```yaml
description: "Use when customizing this project's deployment workflow and release checklist."
```
Do not write vague descriptions such as "helpful project skill"; they can trigger incorrectly.
### Bundled vs. Project-Local
The same directory shape is used by two very different ownership models:
| Aspect | Bundled (`trellis-meta`, `trellis-spec-bootstrap`, `trellis-session-insight`, `trellis-channel`) | Project-local |
| --- | --- | --- |
| Source of truth | `packages/cli/src/templates/common/bundled-skills/<name>/` in Trellis CLI repo | Inside the user project itself |
| Dispatch | Auto-dispatched to every platform skill root by `getBundledSkillTemplates()` (`packages/cli/src/templates/common/index.ts`) on `trellis init` / `trellis update` | Created by the user (or another skill) and never moved |
| Hash tracking | Every file recorded in `.trellis/.template-hashes.json`; conflict prompt on update | Not tracked |
| Editing locally | Allowed but will be marked "modified by user" on next update | Free editing |
| The right way to customize | Add a *new* project-local skill with a *different* name that supplements (or supersedes) the bundled one | Edit the file directly |
If the goal is "make my project's AI behave differently when discussing release notes," the answer is almost always a project-local skill, not surgery on `trellis-meta/`.
## Modify A Command/Prompt/Workflow
Explicit entry points should state:
- How the user triggers it.
- Which `.trellis/` files to read.
- Which scripts to run.
- How to report after completion.
If a command only repeats workflow rules, prefer making it reference/read `.trellis/workflow.md` instead of maintaining a second copy of the flow.
## Common Paths
| Platform | Entry directories |
| --- | --- |
| Claude Code | `.claude/skills/`, `.claude/commands/` |
| Cursor | `.cursor/skills/`, `.cursor/commands/` |
| OpenCode | `.opencode/skills/`, `.opencode/commands/` |
| Codex | `.agents/skills/`, `.codex/skills/` |
| Gemini CLI | `.agents/skills/`, `.gemini/commands/` |
| Kiro | `.kiro/skills/` |
| Qoder | `.qoder/skills/`, `.qoder/commands/` |
| CodeBuddy | `.codebuddy/skills/`, `.codebuddy/commands/` |
| GitHub Copilot | `.github/skills/`, `.github/prompts/` |
| Factory Droid | `.factory/skills/`, `.factory/commands/` |
| Pi Agent | `.pi/skills/` |
| Reasonix | `.reasonix/skills/` (no separate commands dir; slash commands built into the platform) |
| ZCode | `.agents/skills/`, `.zcode/commands/` |
| Kilo / Antigravity / Devin | workflows + skills |
Every directory above is a deploy target for the four bundled skills. Each platform receives a full copy on `trellis init` and refresh on `trellis update`; nothing has to be wired by hand.
## Add A Project-Local Skill
If the user wants to document team-private customizations, create a project-local skill — never put project-private content into a bundled skill directory, since `trellis update` will overwrite it.
```text
.claude/skills/project-trellis-local/
└── SKILL.md
```
For multi-platform projects, add equivalent versions in each platform skill directory, or use `.agents/skills/` on platforms that support the shared layer (Codex, Gemini CLI).
Pick a name that does **not** collide with the bundled set:
- `trellis-meta`
- `trellis-spec-bootstrap`
- `trellis-session-insight`
- `trellis-channel`
A reused name causes `getBundledSkillTemplates()` to overwrite the project-local copy on the next update. A common convention is to prefix the project name: `acme-trellis-deploy`, `acme-trellis-onboarding`.
## Notes
- Do not mix every platform's syntax into one file.
- Do not change only one platform entry point while claiming all platforms are supported.
- Do not hide long-term engineering conventions inside a command; write them to `.trellis/spec/`.
- Do not hand-edit files inside `trellis-meta/`, `trellis-spec-bootstrap/`, `trellis-session-insight/`, or `trellis-channel/` under any `.{platform}/skills/` directory expecting the change to persist — they are bundled and refreshed by `trellis update`. Either contribute upstream or add a project-local skill that complements them.
- After `trellis update` reports a "modified by you" conflict on a bundled skill file, choose **keep** only if you accept maintaining the divergence by hand; otherwise accept the overwrite and re-apply the intent as a project-local skill.

View File

@@ -0,0 +1,83 @@
# Change Local Spec Structure
When the user wants to change the engineering conventions AI follows, add new spec layers, or adjust monorepo package mapping, edit `.trellis/spec/` and `.trellis/config.yaml`.
## Read These Files First
1. `.trellis/config.yaml`
2. `.trellis/spec/`
3. `.trellis/workflow.md` planning artifact guidance and Phase 3.3
4. Current task `implement.jsonl` / `check.jsonl`
## Common Needs
| Need | Edit location |
| --- | --- |
| Add backend/frontend/docs/test spec layer | `.trellis/spec/<layer>/` or `.trellis/spec/<package>/<layer>/` |
| Add shared thinking guides | `.trellis/spec/guides/` |
| Adjust monorepo packages | `packages` in `.trellis/config.yaml` |
| Change default package | `default_package` in `.trellis/config.yaml` |
| Control spec scanning scope | `spec_scope` in `.trellis/config.yaml` |
| Make a task read a new spec | Task `implement.jsonl` / `check.jsonl` |
## Add A Spec Layer
Single-repository example:
```text
.trellis/spec/security/
├── index.md
└── auth.md
```
Monorepo example:
```text
.trellis/spec/webapp/security/
├── index.md
└── auth.md
```
`index.md` should include:
- What code this layer applies to.
- Pre-Development Checklist.
- Quality Check.
- Links to specific guideline files.
## Update Context
Adding a spec does not mean every task automatically reads it. The current task must reference it in JSONL:
```bash
python ./.trellis/scripts/task.py add-context <task> implement ".trellis/spec/webapp/security/index.md" "Security conventions"
python ./.trellis/scripts/task.py add-context <task> check ".trellis/spec/webapp/security/index.md" "Security review rules"
```
## Change Monorepo Packages
Example `.trellis/config.yaml`:
```yaml
packages:
webapp:
path: apps/web
api:
path: apps/api
default_package: webapp
```
After editing, run:
```bash
python ./.trellis/scripts/get_context.py --mode packages
```
Use this output to confirm AI can see the correct packages and spec layers.
## Notes
- Specs are user project conventions and can be changed according to project needs.
- Do not put temporary task information into specs; put temporary information in the task.
- Do not put long-term conventions only in agents or commands; preserve them in specs.
- After changing spec structure, check whether existing task JSONL files still point to files that exist.

View File

@@ -0,0 +1,90 @@
# Change Local Task Lifecycle
Task lifecycle includes creation, start, context configuration, finish, archive, parent/child tasks, and lifecycle hooks. The default customization targets are `.trellis/tasks/`, `.trellis/config.yaml`, and `.trellis/scripts/`.
## Read These Files First
1. `.trellis/workflow.md`
2. `.trellis/config.yaml`
3. `.trellis/scripts/task.py`
4. `.trellis/scripts/common/task_store.py`
5. `.trellis/scripts/common/task_utils.py`
6. The current task's `.trellis/tasks/<task>/task.json`
## Common Needs And Edit Points
| Need | Edit point |
| --- | --- |
| Automatically sync an external system after task creation | `hooks.after_create` in `.trellis/config.yaml`. |
| Automatically update status after task start | `hooks.after_start` in `.trellis/config.yaml`. |
| Run a script after task finish | `hooks.after_finish` in `.trellis/config.yaml`. |
| Clean external resources after archive | `hooks.after_archive` in `.trellis/config.yaml`. |
| Change default task fields | `.trellis/scripts/common/task_store.py`. |
| Change task parsing/search | `.trellis/scripts/common/task_utils.py`. |
| Change active task behavior | `.trellis/scripts/common/active_task.py`. |
## lifecycle hooks
`.trellis/config.yaml` supports:
```yaml
hooks:
after_create:
- "python .trellis/scripts/hooks/my_sync.py create"
after_start:
- "python .trellis/scripts/hooks/my_sync.py start"
after_finish:
- "python .trellis/scripts/hooks/my_sync.py finish"
after_archive:
- "python .trellis/scripts/hooks/my_sync.py archive"
```
Hook commands receive the `TASK_JSON_PATH` environment variable, pointing to the current task's `task.json`. Hook failures should usually warn, but not block the main task operation.
## Change Task Fields
If the user wants to add project-local fields, prefer putting them under `meta` in `task.json` to avoid breaking existing scripts' assumptions about standard fields.
Example:
```json
"meta": {
"linearIssue": "ENG-123",
"risk": "high"
}
```
If standard fields really need to change, inspect every local script that reads `task.json`.
## Change Active Task
Active task is session-level state stored in `.trellis/.runtime/sessions/`. Do not fall back to a global `.current-task` model. If the user wants to change active task behavior, edit:
- `.trellis/scripts/common/active_task.py`
- platform hooks or shell session bridges
- active task descriptions in `.trellis/workflow.md`
### `task.py create` Sets the Active Pointer
`cmd_create` in `.trellis/scripts/common/task_store.py` calls `set_active_task` best-effort right after writing the new task directory. The behavior:
- When the calling shell carries session identity (`TRELLIS_CONTEXT_ID` env var, or any platform-specific session env that `resolve_context_key` recognizes — see `active_task.py:_ENV_SESSION_KEYS`), the per-session pointer at `.trellis/.runtime/sessions/<context_key>.json` is rewritten to point at the new task. The task's `status=planning` and `[workflow-state:planning]` fires on the very next `UserPromptSubmit`.
- When session identity is unavailable (raw CLI invocation outside an AI session, or a platform that doesn't propagate identity to shell), the task directory is still created and `status=planning` is still written, but the active pointer is left untouched. The user can attach the task later with `task.py start <dir>` once they're back in an AI session.
This makes `[workflow-state:planning]` the live breadcrumb during the brainstorm and JSONL curation work that follows `task.py create`. The pre-R7 behavior left the breadcrumb stuck on `no_task` until `task.py start`, so the planning block was effectively dead text.
If you fork `task.py` to add a new creation path (e.g. an external import that bypasses `cmd_create`), audit whether your path also calls `set_active_task`. Without that call, your created tasks will not surface as active. The full status writer table is in `.trellis/spec/cli/backend/workflow-state-contract.md`.
## Modification Steps
1. Confirm the current task with `python ./.trellis/scripts/task.py current --source`.
2. Read the current task's `task.json` and confirm status and fields.
3. For configuration needs, edit `.trellis/config.yaml` first.
4. For script behavior needs, then edit `.trellis/scripts/`.
5. If the AI flow changed, synchronize `.trellis/workflow.md`.
## Do Not
- Do not directly edit `.trellis/.runtime/sessions/` to "fix" business state.
- Do not hard-code project-private fields into scripts; prefer `meta`.
- Do not default to asking the user to fork Trellis CLI.

View File

@@ -0,0 +1,65 @@
# Change Local Workflow
When the user wants to change Trellis phases, next-action hints, whether to create tasks, whether to use sub-agents, or when to check/wrap up, edit `.trellis/workflow.md` first.
## Read These Files First
1. `.trellis/workflow.md`
2. Entry files for the current platform, such as skills/commands/prompts/workflows
3. The current task's `task.json` and `prd.md`
## Common Needs And Edit Points
| Need | Edit point |
| --- | --- |
| Change phase names or phase order | `Phase Index` and the corresponding Phase sections. |
| Change whether to create a task when there is no task | `[workflow-state:no_task]` state block. |
| Change the next step during planning | Phase 1 and `[workflow-state:planning]`. |
| Change whether an agent is required during in_progress | Phase 2 and `[workflow-state:in_progress]`. |
| Change wrap-up after completion | Phase 3 and `[workflow-state:completed]`. |
| Change which skill a user intent triggers | `Skill Routing` table. |
## Modification Steps
1. Find the relevant section in `.trellis/workflow.md`.
2. When changing rules, keep explicit trigger conditions and next actions.
3. If adding or renaming a skill/agent, synchronize the corresponding files in platform directories.
4. Workflow-state changes only need an edit to the `[workflow-state:STATUS]` block in `.trellis/workflow.md`. The hook is parser-only — it reads whatever you put in the block. Keep the opening and closing tags' STATUS strings identical (`[workflow-state:foo]…[/workflow-state:foo]`); mismatched STATUS pairs are silently dropped.
5. Make the AI reread `.trellis/workflow.md`; do not keep using rules from the old conversation.
## Example: Relax Task Creation Requirements
To change when task creation can be skipped, usually edit `[workflow-state:no_task]`:
```md
[workflow-state:no_task]
Task is not required when the answer is a one-reply explanation, no files are changed, and no research is needed.
[/workflow-state:no_task]
```
If the formal Phase 1 flow also needs to change, synchronize the Phase 1 section.
## Example: One Platform Does Not Use Sub-Agents
If the user wants only one platform to avoid sub-agents, first confirm whether that platform has a separate group in the workflow. Then change Phase 2 routing for that platform group instead of deleting all `trellis-implement` / `trellis-check` instructions across platforms.
## `/trellis:continue` Route Table
`/trellis:continue` resumes a task by deciding which phase step to load next. The decision combines `task.json.status` with the presence of artifacts inside the task directory. The mapping is fixed in the command itself; forks that add custom statuses must extend both the workflow.md tag block and this table.
| `status` | Artifact state | Resume at |
| --- | --- | --- |
| `planning` | `prd.md` missing | Phase 1.1 (load `trellis-brainstorm`) |
| `planning` | lightweight task with `prd.md` complete | ask for start review, then run `task.py start` |
| `planning` | complex task missing `design.md` or `implement.md` | complete missing planning artifacts |
| `planning` | complex task has `prd.md`, `design.md`, and `implement.md` | ask for start review, then run `task.py start` |
| `in_progress` | no implementation in conversation history | Phase 2.1 (`trellis-implement`) |
| `in_progress` | implementation done, no `trellis-check` run | Phase 2.2 (`trellis-check`) |
| `in_progress` | check passed | Phase 3.3 (spec update) → 3.4 (commit) |
| `completed` | task is still in active tree | Phase 3.5 (run `/trellis:finish-work` to archive) |
When you add a custom status (e.g. `in-review`), add a `[workflow-state:in-review]` block in `.trellis/workflow.md` for the per-turn breadcrumb AND extend this route table — usually by editing the `/trellis:continue` command file (`.{platform}/commands/trellis/continue.md` or equivalent) to add a row that decides where to resume from. Without the route entry, `/trellis:continue` will fall through to a default branch and the user will not land on the step you intended.
## Notes
`.trellis/workflow.md` is the local project workflow, not an immutable template. The user can adapt it to team habits. After editing it, platform entry files may still contain old descriptions, so inspect them too.

View File

@@ -0,0 +1,55 @@
# Local Customization Overview
This directory is for local AI working in a user project where Trellis was installed through npm and `trellis init` has already been run. The AI should modify generated `.trellis/` and platform directories inside the project, not Trellis CLI upstream source code.
## First Determine What The User Actually Wants To Change
| User wording | Read first |
| --- | --- |
| "Change the Trellis flow / phases / next prompt" | `change-workflow.md` |
| "Change task creation, status, archive, or hooks" | `change-task-lifecycle.md` |
| "AI did not read context / change injected content" | `change-context-loading.md` |
| "A platform hook is not behaving as expected" | `change-hooks.md` |
| "Change implement/check/research agent behavior" | `change-agents.md` |
| "Add a skill/command/workflow/prompt" | `change-skills-or-commands.md` |
| "Adjust the project spec structure" | `change-spec-structure.md` |
| "Add team conventions and local notes" | `add-project-local-conventions.md` |
## General Operation Order
1. **Confirm platform and directories**: inspect which directories exist, such as `.claude/`, `.codex/`, `.cursor/`, `.zcode/`.
2. **Confirm the current active task**: run `python ./.trellis/scripts/task.py current --source`.
3. **Read the local source of truth**: prefer `.trellis/workflow.md`, `.trellis/config.yaml`, and relevant platform files.
4. **Modify narrowly**: edit only files related to the user's request.
5. **Synchronize semantics**: if a shared flow changes, check whether platform entry points also need changes; if a platform entry changes, check whether `.trellis/workflow.md` still agrees.
## Local File Priority
| Layer | Files |
| --- | --- |
| Workflow | `.trellis/workflow.md` |
| Project configuration | `.trellis/config.yaml` |
| Task material | `.trellis/tasks/<task>/` |
| Project specs | `.trellis/spec/` |
| Runtime scripts | `.trellis/scripts/` |
| Platform integration | `.claude/`, `.codex/`, `.cursor/`, `.opencode/`, `.zcode/`, and similar directories |
| Shared skill | `.agents/skills/` |
## Things Not To Do By Default
- Do not edit the global npm install directory.
- Do not edit `node_modules/@mindfoldhq/trellis`.
- Do not assume the user has the Trellis GitHub repository.
- Do not overwrite local files already modified by the user with default templates.
- Do not put team project rules into public `trellis-meta`; project rules belong in `.trellis/spec/` or a local skill.
## When To Inspect Upstream Source
Switch to an upstream source-code perspective only when the user explicitly expresses one of these goals:
- "I want to open a PR to Trellis"
- "I want to change npm package publish contents"
- "I want to fork Trellis"
- "I want to modify the generation logic for `trellis init/update`"
Otherwise, default to modifying local Trellis files inside the user project.

View File

@@ -0,0 +1,146 @@
# Bundled Skills
"Bundled skills" are multi-file built-in skills shipped inside the Trellis CLI npm package. Unlike marketplace skills (which a user installs separately into their own `.claude/skills/` or other platform skill root), bundled skills are written automatically into every supported platform's skill root by `trellis init` and kept in sync by `trellis update`. They are part of Trellis itself, not third-party content.
A bundled skill is a directory under `packages/cli/src/templates/common/bundled-skills/<skill>/` that already contains its own `SKILL.md` (with YAML frontmatter) plus optional `references/`, assets, or other supporting files. Trellis copies the whole directory tree as-is into each platform's skill root, so references stay lazy-loadable instead of being flattened into one oversized `SKILL.md`.
## What Counts As Bundled (vs. Adjacent Concepts)
| Source path | Type | How it ships |
| --- | --- | --- |
| `templates/common/bundled-skills/<name>/` | Bundled skill (multi-file) | Whole directory copied to every platform skill root |
| `templates/common/skills/<name>.md` | Single-file workflow skill | Wrapped with frontmatter, written as `<root>/<name>/SKILL.md` |
| `templates/common/commands/<name>.md` | Slash command / prompt | Written to each platform's command directory (`.claude/commands/trellis/`, `.cursor/commands/trellis-*.md`, `.gemini/commands/trellis/*.toml`, etc.) |
| `templates/<platform>/skills/` | Platform-specific skill | Written only into that platform's directory (e.g. `.codex/skills/`) |
| User skills under `.claude/skills/<my-skill>/` etc. | Marketplace or user-authored | Not managed by Trellis at all |
The Trellis CLI never touches anything that is not produced by one of its own template loaders. Anything a user drops into a platform skill root by hand is left alone.
## Current Bundled Skills (v0.6.0)
The set is discovered at runtime by listing directories under `templates/common/bundled-skills/`:
| Skill | Purpose |
| --- | --- |
| `trellis-meta` | This skill. Explains the local Trellis architecture and customization entry points to an AI working inside a user project. |
| `trellis-session-insight` | Wraps the `trellis mem` CLI so an AI knows when and how to reach into past Claude Code / Codex / Pi Agent conversation logs. |
| `trellis-spec-bootstrap` | Platform-neutral workflow for creating or refreshing `.trellis/spec/` from the real codebase (with optional GitNexus / ABCoder integration). |
| `trellis-channel` | Capability skill teaching an AI when to reach for `trellis channel` for multi-agent collaboration, forum/thread persistent boards, and dispatcher-wait patterns. |
The list is discovered at runtime, so adding a new directory under `bundled-skills/` is the only step required to register a new skill (see "Adding a New Bundled Skill" below).
## Where Bundled Skills Land Per Platform
Each platform configurator calls `writeSkills(<root>, <workflowSkills>, resolveBundledSkills(ctx))` during `trellis init`. `resolveBundledSkills` reads every directory under `templates/common/bundled-skills/`, resolves placeholders, and returns a flat list of `{relativePath, content}` entries. `writeSkills` then mirrors them under the platform's skill root.
| Platform | Bundled skill root | Notes |
| --- | --- | --- |
| Claude Code | `.claude/skills/<skill>/` | `configureClaude` |
| Cursor | `.cursor/skills/<skill>/` | `configureCursor` |
| Codex | `.agents/skills/<skill>/` | `configureCodex` writes the shared `.agents/skills/` root, which Gemini CLI 0.40+ also reads |
| Gemini CLI | `.agents/skills/<skill>/` | Same shared root as Codex; the two configurators are required to produce byte-identical output |
| Kiro | `.kiro/skills/<skill>/` | `configureKiro` (skills-based platform — no commands) |
| Qoder | `.qoder/skills/<skill>/` | `configureQoder` |
| Codebuddy | `.codebuddy/skills/<skill>/` | `configureCodebuddy` |
| Copilot | `.github/skills/<skill>/` | `configureCopilot` |
| Droid | `.factory/skills/<skill>/` | `configureDroid` |
| Antigravity | `.agent/skills/<skill>/` | `configureAntigravity` |
| Devin | `.devin/skills/<skill>/` | `configureDevin` |
| Kilo | `.kilocode/skills/<skill>/` | `configureKilo` |
| OpenCode | (handled by `collectOpenCodeTemplates`) | Uses the same `resolveBundledSkills(ctx)` output |
| Pi, Reasonix | (their own collectors) | Same `resolveBundledSkills(ctx)` output |
Two paths exercise the same data:
1. `configureX(cwd)` writes files during `trellis init`.
2. `collectPlatformTemplates(platformId)` (in `configurators/index.ts`) returns a `Map<filePath, content>` that `trellis update` uses to detect drift and to populate `.trellis/.template-hashes.json`. Both must produce byte-identical output, so they both call `resolveBundledSkills(ctx)` and `collectSkillTemplates(root, …, resolveBundledSkills(ctx))`.
## Dispatch Wiring (Code Path)
The mechanism that auto-dispatches bundled skills to platform skill roots lives in two files:
1. `packages/cli/src/templates/common/index.ts`
- `listDirectories("bundled-skills")` enumerates the on-disk skills.
- `listBundledSkillFiles(skillDir)` walks each skill's directory recursively and returns `{relativePath, content}` for every file.
- `getBundledSkillTemplates()` returns the cached `CommonBundledSkill[]`.
2. `packages/cli/src/configurators/shared.ts`
- `resolveBundledSkills(ctx)` flattens that list into `ResolvedSkillFile[]` with `<skill>/<relativePath>` paths and resolved placeholders.
- `writeSkills(skillsRoot, workflowSkills, bundledSkills)` writes both workflow skills and bundled skill files under `skillsRoot`.
- `collectSkillTemplates(skillsRoot, workflowSkills, bundledSkills)` returns the same shape as a `Map<filePath, content>` for the update / hash pipeline.
Every platform configurator that supports skills imports both helpers (see `claude.ts`, `cursor.ts`, `codex.ts`, `gemini.ts`, `kiro.ts`, `qoder.ts`, `codebuddy.ts`, `copilot.ts`, `droid.ts`, `antigravity.ts`, `devin.ts`, `kilo.ts`). The `index.ts` `PLATFORM_FUNCTIONS` registry also calls `resolveBundledSkills(ctx)` inside each `collectTemplates` closure so `trellis update` tracking stays consistent.
## Adding a New Bundled Skill
The shape and dispatch wiring are already generic, so adding a skill requires only file changes plus distribution verification.
1. **Create the directory tree.**
```
packages/cli/src/templates/common/bundled-skills/<my-skill>/
SKILL.md # YAML frontmatter + body
references/ # optional
<topic>.md
assets/ # optional (anything readable as utf-8)
```
2. **Write a valid `SKILL.md` header.** The frontmatter must include at minimum:
```yaml
---
name: <my-skill>
description: "When the AI should reach for this skill. Triggering phrases go here."
---
```
The `description` is what each platform's auto-trigger mechanism matches against, so it should describe the user-intent triggers, not the skill's internals.
3. **Use placeholders where appropriate.** Bundled skill content runs through `resolvePlaceholders(file.content, ctx)`. Any `{{platform_name}}`, `{{python_cmd}}`, etc. token supported by `resolvePlaceholders` will be substituted per platform.
4. **No dispatch wiring is required.** `listDirectories("bundled-skills")` discovers the new directory automatically, so all platforms receive it on the next `trellis init` or `trellis update`.
5. **Verify the distribution path** before shipping. Skipping any of these steps has historically caused features to be documented as bundled while the published npm tarball was missing the files:
- Source files exist on the branch being tagged.
- `pnpm --filter @mindfoldhq/trellis build` copies the asset into `dist/templates/common/bundled-skills/<skill>/`.
- `npm pack --dry-run --json` includes the expected `dist/**` paths.
- In a fresh temp project, `trellis init` writes `.claude/skills/<skill>/SKILL.md`, `.agents/skills/<skill>/SKILL.md`, etc.
- `.trellis/.template-hashes.json` lists the generated files.
- `trellis update --dry-run` in that temp project reports "Already up to date!".
6. **Add a migration manifest entry** if the skill is added in a release that other projects will upgrade into. Without an explicit manifest entry the file will land via the standard "missing file" branch of `trellis update`, but a manifest makes the change visible in the changelog.
## Overriding a Bundled Skill Locally
There is no formal "project-local skill" mechanism (e.g. `.trellis/skills/`). Bundled skills are platform-rooted, so any override is platform-rooted too.
The supported pattern relies on the existing template-hash diff in `trellis update`:
1. Edit the local file directly. Example: `.claude/skills/trellis-meta/SKILL.md`.
2. The file's hash now diverges from the entry in `.trellis/.template-hashes.json`.
3. The next `trellis update` detects the user modification and leaves the file untouched (Trellis never overwrites user-modified files without an explicit `--force`).
Caveats:
- The override only applies to the one platform whose directory you edited. To override the same skill across, for example, Claude Code and Codex, you must edit both `.claude/skills/<name>/` and `.agents/skills/<name>/`.
- A future `trellis update --force` will overwrite local edits. Keep the override under version control so it can be reapplied if needed.
- Marketplace skills installed under the same platform skill root with a different folder name (e.g. `.claude/skills/my-custom-meta/`) are untouched by Trellis and are the cleaner option when the goal is to add behavior, not to mutate the bundled skill.
- Team-private conventions belong in `.trellis/spec/` or in a separate marketplace-style local skill, not in modifications to `trellis-meta` itself. See `customize-local/add-project-local-conventions.md`.
## Removing a Bundled Skill From a Project
There is no per-project opt-out flag for bundled skills. Two options:
1. **Delete the directory in each platform skill root.** `trellis update` will see the file missing, compare against `.template-hashes.json`, and treat the deletion the same as any other user modification — it will not silently re-create the directory unless `--force` is passed.
2. **Pin a Trellis version that did not ship the skill.** The bundled-skill set is determined at build time, so installing an older release of the CLI is the only way to permanently exclude a skill that the current release ships.
A third option — globally disabling all bundled skills — is not supported. The dispatch is unconditional in every configurator. Adding such a flag would require changing `PLATFORM_FUNCTIONS` in `configurators/index.ts` and every `configureX` function.
## Operating Rules
- Treat `templates/common/bundled-skills/` as the single source of truth for what bundled skills exist. Do not hand-maintain platform-by-platform skill lists.
- Do not add platform-specific logic inside a bundled `SKILL.md`. If a behavior is platform-specific, put it in `templates/<platform>/skills/` instead.
- Do not couple bundled skills to a specific CLI binary (e.g. `trellis mem`) without surfacing the dependency in the skill's description and references — users on older releases may not have the command.
- Do not store project-private content in a bundled skill. Bundled skills are public, shipped to every user; project rules belong in `.trellis/spec/` or a local skill.

View File

@@ -0,0 +1,68 @@
# Local Context Injection System
Trellis context injection aims to make AI read the right files at the right time instead of relying on model memory. In a user project, injection is implemented by `.trellis/` scripts together with platform hooks, agents, and skills.
## Injected Context Types
| Type | Source | Purpose |
| --- | --- | --- |
| session context | `.trellis/scripts/get_context.py` | Current developer, git status, active task, active tasks, journal, packages. |
| workflow context | `.trellis/workflow.md` | Current Trellis flow and next action. |
| spec context | `.trellis/spec/` + task JSONL | Specs that must be followed during implementation/checking. |
| task context | `.trellis/tasks/<task>/prd.md`, `design.md`, `implement.md`, `research/` | Current task requirements, design, execution plan, and research. |
| platform context | Platform hooks/settings/agents | Lets different AI tools read the files above through their own mechanisms. |
## session-start
Platforms with session-start support inject a Trellis overview when a session starts, clears, compacts, or receives a similar event. Injected content usually includes:
- workflow summary.
- current task status.
- active tasks.
- spec index paths.
- developer identity and git status.
If the user feels the AI does not know the current task in a new session, first check whether the platform's session-start hook or equivalent mechanism is installed and running.
## workflow-state
workflow-state is a lightweight hint injected around each user turn. Based on current task status, it selects a block from `.trellis/workflow.md`, such as `no_task`, `planning`, `in_progress`, or `completed`.
If the user wants to change "what the AI should do next in a given state," edit the corresponding state block in `.trellis/workflow.md` first.
## sub-agent context
Implement and check agents need task context. Trellis has two loading modes:
1. **hook push**: a platform hook injects jsonl-referenced files plus `prd.md`, `design.md` if present, and `implement.md` if present before the agent starts.
2. **agent pull**: the agent definition instructs the agent to read the active task, jsonl context, and task artifacts after startup.
In both modes, JSONL files in the task directory are the manifest for spec/research context. Task artifacts are read separately in this order: `prd.md` -> `design.md if present` -> `implement.md if present`.
## JSONL Reading Rules
`implement.jsonl` and `check.jsonl` contain one JSON object per line:
```jsonl
{"file": ".trellis/spec/backend/index.md", "reason": "Backend rules"}
```
Readers should skip seed rows without a `file` field. When configuring JSONL, the AI should include only spec/research files, not pre-register code files that will be modified.
## Active Task And Context Key
Active task state lives in `.trellis/.runtime/sessions/` and is isolated per session. Hooks try to resolve the context key from platform events, environment variables, transcript paths, or `TRELLIS_CONTEXT_ID`.
If shell commands cannot see the same context key, `task.py current --source` may report no active task. In that case, check whether the platform passes session identity into the shell instead of hand-writing a global current-task file.
## Local Customization Points
| Need | Edit location |
| --- | --- |
| Change session-start injected content | The platform's `session-start` hook or plugin file. |
| Change per-turn workflow-state rules | `[workflow-state:STATUS]` block in `.trellis/workflow.md`. The platform workflow-state hook parses these blocks verbatim and embeds no fallback text. |
| Change how sub-agents read context | Platform agent definitions, the `inject-subagent-context` hook, or agent preludes. |
| Change JSONL validation/display | `.trellis/scripts/common/task_context.py`. |
| Change active task resolution | `.trellis/scripts/common/active_task.py`. |
When modifying context injection, verify two things: new sessions can see the correct task, and sub-agents can see the correct task artifacts/spec/research.

View File

@@ -0,0 +1,80 @@
# Local Files Generated After Init
`trellis init` writes the Trellis runtime into the user project. Later, `trellis update` tries to update Trellis-managed template files, but it uses `.trellis/.template-hashes.json` to determine which files have already been modified by the user.
This page only describes files that are visible and editable inside the user project.
## `.trellis/`
```text
.trellis/
├── workflow.md
├── config.yaml
├── .developer
├── .version
├── .template-hashes.json
├── .runtime/
├── scripts/
├── spec/
├── tasks/
└── workspace/
```
| Path | Usually editable? | Notes |
| --- | --- | --- |
| `.trellis/workflow.md` | Yes | Local workflow documentation and AI routing rules. |
| `.trellis/config.yaml` | Yes | Project configuration, hooks, packages, journal line limits, and related settings. |
| `.trellis/spec/` | Yes | Project specs, intended to be updated regularly by users and AI. |
| `.trellis/tasks/` | Yes | Task material and research artifacts, maintained by the task workflow. |
| `.trellis/workspace/` | Yes | Session records, usually written by `add_session.py`. |
| `.trellis/scripts/` | Carefully | Local runtime. It can be customized, but only after understanding the call chain. |
| `.trellis/.runtime/` | No | Runtime state, usually written automatically by hooks/scripts. |
| `.trellis/.developer` | Carefully | Current developer identity. |
| `.trellis/.version` | No | Trellis version record used by update/migration logic. |
| `.trellis/.template-hashes.json` | No | Template hash record. Do not hand-write business rules here. |
## Platform Directories
Different platforms generate different directories. Common categories:
| Category | Example paths | Purpose |
| --- | --- | --- |
| hooks | `.claude/hooks/`, `.codex/hooks/`, `.cursor/hooks/` | Inject session context, workflow-state, and sub-agent context. |
| settings | `.claude/settings.json`, `.codex/hooks.json`, `.qoder/settings.json`, `.trae/hooks.json` | Tell the platform when to run hooks or plugins. |
| agents | `.claude/agents/`, `.codex/agents/`, `.kiro/agents/`, `.zcode/cli/agents/` | Define agents such as `trellis-research`, `trellis-implement`, and `trellis-check`. |
| skills | `.claude/skills/`, `.agents/skills/`, `.qoder/skills/` | Skills that auto-trigger or can be read by AI. |
| commands/prompts/workflows | `.cursor/commands/`, `.github/prompts/`, `.devin/workflows/`, `.zcode/commands/` | Explicit user-invoked command or workflow entry points. |
When modifying a platform directory, also confirm whether `.trellis/workflow.md` still describes the same flow.
## Meaning Of Template Hashes
`.trellis/.template-hashes.json` records the content hash from the last time Trellis wrote a template file. `trellis update` uses it to distinguish three cases:
| Case | Update behavior |
| --- | --- |
| File was not modified by the user | It can be updated automatically. |
| File was modified by the user | Prompt the user to overwrite, keep, or generate `.new`. |
| File is no longer a current template | It may be deleted, renamed, or preserved according to migration rules. |
When an AI customizes local Trellis files, it does not need to maintain hashes manually. It is normal for Trellis update to recognize the result as "modified by the user."
## Local Customization Boundaries
Editable by default:
- `.trellis/workflow.md`
- `.trellis/config.yaml`
- `.trellis/spec/**`
- `.trellis/scripts/**`
- Platform hooks, settings, agents, skills, commands, prompts, and workflows
Do not edit by default:
- Global npm install directory
- `node_modules/@mindfoldhq/trellis`
- Trellis GitHub repository source code
- Concrete state files under `.trellis/.runtime/**`
- Hash contents inside `.trellis/.template-hashes.json`
Switch to the Trellis CLI source-code perspective only when the user explicitly wants to contribute upstream.

View File

@@ -0,0 +1,69 @@
# Local Multi-Agent Channel Runtime
`trellis channel` is the local multi-agent collaboration runtime shipped with the Trellis CLI. It lets the main AI session spawn peer workers (Claude Code, Codex, or any agent definition under `.trellis/agents/`), exchange durable messages through an event log, and coordinate review or brainstorm loops without hand-stitching shell pipelines.
This reference covers how channels are wired into the user project so an AI customizing the project knows what to edit. For runtime usage (commands, forum/thread patterns, worker spawn flags), defer to the bundled `trellis-channel` capability skill.
## Local System Model
The channel runtime spans three local surfaces:
1. **Storage layer** in the user's home directory: durable event logs and worker state files.
2. **Agent definitions** inside the project at `.trellis/agents/`: platform-agnostic role cards consumed by `trellis channel spawn --agent <name>`.
3. **Project configuration** in `.trellis/config.yaml`: worker guard thresholds and other channel knobs.
## Core Paths
| Path | Purpose |
| --- | --- |
| `~/.trellis/channels/<project>/<channel>/events.jsonl` | Per-channel append-only event log. Sequence-locked, replay-safe. |
| `~/.trellis/channels/<project>/<channel>/<channel>.lock` | Channel-level write lock. |
| `~/.trellis/channels/<project>/<channel>/<worker>.spawnlock` | Per-worker spawn lock used by the OOM guard. |
| `~/.trellis/channels/<project>/<channel>/.seq` | Sequence sidecar for ordered event assignment. |
| `~/.trellis/channels/_global/<channel>/...` | Channels created with `--scope global`. The project bucket is replaced by a shared key. |
| `.trellis/agents/check.md` | Default Check Agent role definition consumed by `--agent check`. |
| `.trellis/agents/implement.md` | Default Implement Agent role definition consumed by `--agent implement`. |
| `.trellis/config.yaml` (`channel.*` block) | Worker guard thresholds and channel defaults. |
The project bucket name is derived from the absolute project path (slashes flattened, non-alphanumerics replaced with `-`), matching Claude Code's `~/.claude/projects/<sanitized-cwd>/` convention. Override with `TRELLIS_CHANNEL_ROOT` (root directory) or `TRELLIS_CHANNEL_PROJECT` (bucket name) for testing or sandboxing.
## When To Reach For The Channel Runtime
Channels are heavier than a single Bash call or a one-shot sub-agent dispatch. Use them only when at least one of these conditions holds:
- The work needs **two or more agents to converse** through more than one turn (cross-AI brainstorm, peer review, dispatcher + worker).
- A worker should run as a **peer process** that the main session can interrupt, watch progress on, or wait for asynchronously.
- The conversation must be **durable and inspectable** later (forum/thread channels, issue boards, decision trails).
- Multiple workers must **share an event log** so each can see what the others reported.
Prefer cheaper primitives when:
- A single-shot Bash command or single Agent tool call is enough -> do that directly.
- The user just needs a static review against a file -> read the file and reply inline.
- The need is "remember what we discussed last week" -> use `trellis mem` instead of a channel.
## Customization Points
| Need | Edit location |
| --- | --- |
| Change default channel worker idle timeout | `channel.worker_guard.idle_timeout` in `.trellis/config.yaml`. Accepts `5m`, `30s`, etc. Set `0` to disable idle cleanup. |
| Change live worker budget | `channel.worker_guard.max_live_workers` in `.trellis/config.yaml`. Set `0` to disable the spawn-time budget check. |
| Override worker guard per spawn | Pass `--idle-timeout` / `--max-live-workers` on `trellis channel spawn`, or set `TRELLIS_CHANNEL_WORKER_IDLE_TIMEOUT` / `TRELLIS_CHANNEL_MAX_LIVE_WORKERS` in the environment. |
| Change what the default Check or Implement worker does | Edit `.trellis/agents/check.md` or `.trellis/agents/implement.md`. These are platform-agnostic role cards; the channel runtime injects them when `--agent check|implement` is passed. |
| Add a new role card | Drop `<name>.md` into `.trellis/agents/`. `trellis channel spawn --agent <name>` will pick it up. |
| Relocate channel storage (CI sandbox, ephemeral runs) | Set `TRELLIS_CHANNEL_ROOT=/path/to/dir`. Channel events move with it; existing channels stay at the old root. |
| Switch storage scope | Pass `--scope project` (default) or `--scope global` on every channel subcommand. The bucket directory changes; nothing else does. |
Precedence for the worker guard is: CLI flag > environment variable > `.trellis/config.yaml` > built-in default. Built-in defaults are `idle_timeout: 5m` and `max_live_workers: 6`.
## Relationship To Other Local Layers
- **Workflow layer**: workflows that use channel dispatch (such as `channel-driven-subagent-dispatch`) instruct the main agent to call `trellis channel spawn --agent check` or `--agent implement` instead of a platform sub-agent. If `.trellis/agents/check.md` or `implement.md` is missing, `trellis workflow --template <id>` prints a non-blocking warning at install time. Restore them with `trellis update` if they are deleted by accident.
- **Task layer**: channel workers do not own task state. The supervising main session passes the active task path through the worker inbox; the worker resolves task artifacts from disk.
- **Spec layer**: workers read `.trellis/spec/` the same way the main session does. Channel runtime does not bypass spec context loading.
- **Platform integration layer**: channel runtime is platform-neutral. It does not depend on `.claude/`, `.codex/`, or any other platform directory. The adapters that normalize provider output (Claude `stream-json`, Codex `app-server`) live inside the Trellis CLI binary, not in the project.
- **Platform sub-agent files vs. channel workers**: editing `.claude/agents/trellis-implement.md` (and its peers in other platform `.X/agents/` directories) does NOT change channel-runtime worker behavior — channel workers load `.trellis/agents/<name>.md`. The platform-specific agent files are for direct sub-agent dispatch from the main AI session, not for channel-spawned workers. See `platform-files/agents.md` for the per-platform agent surface, and the `trellis-meta/SKILL.md` rule that codifies this split.
## Runtime Usage
For command syntax, forum/thread patterns, worker handles, progress inspection, and the `--kind done` / `--kind turn_finished` dispatcher wait pattern, load the bundled `trellis-channel` skill (auto-installed under each platform's skills directory after `trellis init` / `trellis update`). This reference only covers the local file layout and customization knobs; it does not duplicate command syntax that may change between releases.

View File

@@ -0,0 +1,51 @@
# Local Trellis Architecture Overview
`trellis-meta` is for user projects that have already run `trellis init`. The user's machine usually has only the npm-installed `trellis` command plus the Trellis files generated inside the project; it may not have the Trellis CLI source code.
Therefore, when an AI uses this skill, the default customization target is local files inside the user project:
- `.trellis/`: workflow, tasks, specs, memory, scripts, and runtime state.
- Platform directories: `.claude/`, `.codex/`, `.cursor/`, `.opencode/`, `.kiro/`, `.gemini/`, `.qoder/`, `.codebuddy/`, `.github/`, `.factory/`, `.pi/`, `.kilocode/`, `.agent/`, `.devin/`, `.reasonix/`, `.zcode/`, and similar directories.
- Shared skill layer: `.agents/skills/`.
Do not default to guiding the user to fork the Trellis CLI repository. Treat upstream source code as the operating target only when the user explicitly says they want to change Trellis upstream source, publish an npm package, or contribute a PR.
## Local System Model
Trellis provides three layers inside a user project:
1. **Workflow layer**: `.trellis/workflow.md` defines phases, routing, next actions, and prompt blocks.
2. **Persistence layer**: `.trellis/tasks/`, `.trellis/spec/`, and `.trellis/workspace/` store tasks, specs, and session memory.
3. **Platform integration layer**: hooks, settings, agents, skills, commands, prompts, and workflows in platform directories connect the Trellis workflow to different AI tools.
All three layers live inside the user project, so an AI can read and modify them directly.
## Core Paths
| Path | Purpose |
| --- | --- |
| `.trellis/workflow.md` | Workflow phases, skill routing, and workflow-state prompt blocks. |
| `.trellis/config.yaml` | Project configuration, task lifecycle hooks, monorepo package configuration, and journal configuration. |
| `.trellis/spec/` | The user's project-specific coding conventions and thinking guides. |
| `.trellis/tasks/` | Each task's PRD, technical notes, research files, and JSONL context. |
| `.trellis/workspace/` | Per-developer journals and cross-session memory. |
| `.trellis/scripts/` | Local Python runtime used by commands, hooks, and context injection. |
| `.trellis/.runtime/` | Session-level runtime state, such as the current task pointer. |
| `.trellis/.template-hashes.json` | Template hashes for Trellis-managed files, used by update to determine whether local files were modified by the user. |
## AI Customization Principles
1. **Find the local source of truth first**: Do not edit from memory. Read `.trellis/workflow.md`, `.trellis/config.yaml`, the relevant platform directory, and related task files first.
2. **Edit the user project, not the npm package cache**: Modify generated files inside the project, not `node_modules` or the global npm install directory.
3. **Keep platform files aligned with `.trellis/`**: If workflow routing changes, also check whether platform skills or commands still describe the same flow.
4. **Put project-specific rules in `.trellis/spec/` or a local skill**: Do not put team conventions into `trellis-meta`.
5. **Preserve user changes**: If a file was already modified locally, work from the current content instead of overwriting it with a default template.
## How To Use This Directory
- To understand which files exist after init, read `generated-files.md`.
- To change phases, routing, or next actions, read `workflow.md`.
- To change the task model, JSONL context, or active task behavior, read `task-system.md`.
- To change coding convention injection, read `spec-system.md`.
- To understand journals and cross-session memory, read `workspace-memory.md`.
- To change hooks or sub-agent context loading, read `context-injection.md`.

View File

@@ -0,0 +1,102 @@
# Local Spec System
`.trellis/spec/` is the user's project-specific engineering spec library. Trellis is not about making AI memorize conventions; it injects relevant specs or requires the AI to read them at the right time.
## Directory Model
A common single-repository structure:
```text
.trellis/spec/
├── backend/
│ ├── index.md
│ └── ...
├── frontend/
│ ├── index.md
│ └── ...
└── guides/
├── index.md
└── ...
```
A common monorepo structure:
```text
.trellis/spec/
├── cli/
│ ├── backend/
│ │ ├── index.md
│ │ └── ...
│ └── unit-test/
│ ├── index.md
│ └── ...
├── docs-site/
│ └── docs/
│ ├── index.md
│ └── ...
└── guides/
├── index.md
└── ...
```
`index.md` is the entry point for each layer. It should list the Pre-Development Checklist and Quality Check. Specific guidelines live in other Markdown files in the same directory.
## Package Configuration
`.trellis/config.yaml` can declare packages:
```yaml
packages:
cli:
path: packages/cli
docs-site:
path: docs-site
type: submodule
default_package: cli
```
The AI can run:
```bash
python ./.trellis/scripts/get_context.py --mode packages
```
This command lists packages and spec layers for the current project. Use this output as the reference when configuring context JSONL.
## How Specs Enter Tasks
Before a task enters implementation, planning may write relevant specs into `implement.jsonl` / `check.jsonl` when the task needs spec or research context beyond the task artifacts:
```jsonl
{"file": ".trellis/spec/cli/backend/index.md", "reason": "CLI backend conventions"}
{"file": ".trellis/spec/cli/unit-test/conventions.md", "reason": "Test expectations"}
```
Sub-agents or platform preludes read these JSONL files and load the referenced specs. On platforms without sub-agent support, the AI should read the relevant specs directly according to the workflow.
## What Specs Should Contain
Specs should contain executable engineering conventions for the project, not generic best practices:
- Where files should live.
- How error handling should be expressed.
- Input/output contracts for APIs, hooks, and commands.
- Patterns that are forbidden.
- Cases that require tests.
- Project-specific pitfalls and how to avoid them.
When the AI learns a new rule during implementation or debugging, it should update `.trellis/spec/` rather than only summarizing it in chat.
## Local Customization Points
| Need | Edit location |
| --- | --- |
| Add a new spec layer | `.trellis/spec/<package>/<layer>/index.md` and corresponding guideline files. |
| Change monorepo spec mapping | `packages` / `default_package` / `spec_scope` in `.trellis/config.yaml`. |
| Change which specs AI reads before implementation | The task's `implement.jsonl`. |
| Change which specs AI reads during checking | The task's `check.jsonl`. |
| Change when specs should be updated | Phase 3.3 in `.trellis/workflow.md` and the `trellis-update-spec` skill. |
## Boundaries
`.trellis/spec/` is the user's project specification, not a permanent copy of Trellis built-in templates. The AI should encourage the user to update it according to the actual project code instead of treating Trellis default templates as immutable documents.

View File

@@ -0,0 +1,130 @@
# Local Task System
The Trellis task system is stored entirely under `.trellis/tasks/` in the user project. Each task is a directory containing requirements, context, research, state, and relationship information.
## Task Directory Structure
```text
.trellis/tasks/
├── 04-28-example-task/
│ ├── task.json
│ ├── prd.md
│ ├── design.md
│ ├── implement.md
│ ├── implement.jsonl
│ ├── check.jsonl
│ └── research/
└── archive/
└── 2026-04/
```
| File | Purpose |
| --- | --- |
| `task.json` | Task metadata: status, assignee, priority, branch, parent/child tasks, and similar fields. |
| `prd.md` | Requirements, constraints, and acceptance criteria. Lightweight tasks may be PRD-only. |
| `design.md` | Technical design for complex tasks: boundaries, contracts, data flow, compatibility, tradeoffs. |
| `implement.md` | Execution plan for complex tasks: ordered checklist, validation commands, review gates, rollback points. |
| `implement.jsonl` | List of spec/research files the implement agent must read first. |
| `check.jsonl` | List of spec/research files the check agent must read first. |
| `research/` | Research artifacts. Complex findings should not live only in chat. |
## `task.json`
`task.json` records task status and metadata. Common fields:
| Field | Meaning |
| --- | --- |
| `id` / `name` / `title` | Task identity and title. |
| `status` | Status such as `planning`, `in_progress`, `review`, or `completed`. |
| `priority` | `P0`, `P1`, `P2`, `P3`. |
| `creator` / `assignee` | Creator and assignee. |
| `package` | Target package in a monorepo; may be empty. |
| `branch` / `base_branch` | Working branch and PR target branch. |
| `children` / `parent` | Parent/child task relationships. |
| `commit` / `pr_url` | Commit and PR information after completion. |
| `meta` | Extension fields. |
## Parent / Child Task Trees
Parent/child task relationships are for work structure. A parent task groups related deliverables under one source requirement set; it is not a dependency scheduler and does not replace the child task's own planning artifacts.
Use a parent task when a request has multiple independently verifiable deliverables. The parent owns:
- Source requirements and user-facing scope.
- The map of child tasks and their responsibility boundaries.
- Cross-child acceptance criteria and final integration review.
Use child tasks for deliverables that can move through planning, implementation, check, and archive independently. If one child depends on another, write that dependency in the child `prd.md` / `implement.md`; do not rely on tree position to imply ordering.
Create new children with:
```bash
python ./.trellis/scripts/task.py create "<child title>" --slug <child-slug> --parent <parent-dir>
```
Link or unlink existing tasks with:
```bash
python ./.trellis/scripts/task.py add-subtask <parent-dir> <child-dir>
python ./.trellis/scripts/task.py remove-subtask <parent-dir> <child-dir>
```
`children` on the parent is a historical list. When a child is archived, Trellis keeps that child name in the parent so progress like `[2/3 done]` remains meaningful after completed children move to `archive/`.
The AI should not treat phase numbers as task status. Task progress is mainly determined by `status`, artifact presence (`prd.md`, optional `design.md` / `implement.md`), whether JSONL context is configured for sub-agent mode, and the phase descriptions in `workflow.md`.
## Active Task
The user sees a "current task," but Trellis stores active task state per session.
```text
.trellis/.runtime/sessions/<context-key>.json
```
`task.py start` writes the task path into the runtime session file for the current session. `task.py current --source` shows the current task and where it came from. Different AI windows can point to different tasks without overwriting each other.
If the platform or shell environment has no stable session identity, `task.py start` may be unable to set the active task. The AI should read the error, inspect the platform hook/session environment, and not fall back to a shared global pointer.
## JSONL Context
`implement.jsonl` and `check.jsonl` are context manifests for sub-agents to read first. They do not replace `implement.md`; `implement.md` is the human-readable execution plan.
Format:
```jsonl
{"file": ".trellis/spec/cli/backend/index.md", "reason": "Backend conventions"}
{"file": ".trellis/tasks/04-28-example/research/api.md", "reason": "API research"}
```
Rules:
- Include spec and research files.
- Do not include code files that are about to be modified.
- Do not treat temporary conclusions in chat as the only context.
- Seed rows have no `file` field; they only prompt the AI to fill in real entries.
## Common Commands
```bash
python ./.trellis/scripts/task.py create "<title>" --slug <slug>
python ./.trellis/scripts/task.py start <task>
python ./.trellis/scripts/task.py current --source
python ./.trellis/scripts/task.py add-context <task> implement <file> <reason>
python ./.trellis/scripts/task.py validate <task>
python ./.trellis/scripts/task.py finish
python ./.trellis/scripts/task.py archive <task>
```
When modifying the task system, the AI should prefer script commands to maintain structure. Edit JSON/Markdown directly only when scripts do not cover the need.
## Local Customization Points
| Need | Edit location |
| --- | --- |
| Change the default task template | `.trellis/scripts/common/task_store.py` and task creation instructions. |
| Change status semantics | `.trellis/workflow.md`, workflow-state hook logic, and task usage conventions. |
| Add task lifecycle actions | `hooks.after_*` in `.trellis/config.yaml`. |
| Change context rules | Planning artifact guidance in `.trellis/workflow.md` and related platform agent/hook instructions. |
| Change archive policy | `.trellis/scripts/common/task_store.py` / `task_utils.py`. |
These are local files in the user project. Do not default to editing Trellis CLI source code unless the user wants to contribute upstream.

View File

@@ -0,0 +1,75 @@
# Local Workflow System
`.trellis/workflow.md` is the Trellis workflow source of truth inside the user project. An AI does not need Trellis source code to understand how the current project should move tasks forward; this file is enough.
## File Responsibilities
`.trellis/workflow.md` has three responsibilities:
1. **Explain workflow phases**: Plan, Execute, Finish.
2. **Define skill routing**: which skill or agent the AI should use when the user expresses a certain intent.
3. **Provide workflow-state prompt blocks**: hooks can inject the prompt block for the current state into the conversation.
## Current Phase Model
```text
Phase 1: Plan -> clarify what to build, produce prd.md and required research
Phase 2: Execute -> implement against the PRD and specs, then check
Phase 3: Finish -> final verification, preserve lessons, and wrap up
```
Each phase contains numbered steps, such as `1.3 Configure context`. These numbers are not runtime fields in `task.json`; they are workflow structure for AI and humans to read.
## Skill Routing
`workflow.md` separates routing by platform capability:
- Platforms with sub-agent support: dispatch `trellis-implement` by default for implementation and `trellis-check` for checking.
- Platforms without sub-agent support: the main session reads skills such as `trellis-before-dev`, then executes directly.
When changing local AI behavior, update the routing descriptions in `workflow.md` first, then check whether the corresponding platform skill, command, or agent files need to stay in sync.
## Workflow-State Prompt Blocks
The bottom of `workflow.md` can contain state blocks like this:
```text
[workflow-state:no_task]
...
[/workflow-state:no_task]
```
Hooks choose the right block based on current task status and inject it into the conversation. Common states include:
| State | Meaning |
| --- | --- |
| `no_task` | The current session has no active task. |
| `planning` | The task is still in requirements, research, or context configuration. |
| `in_progress` | The task has entered implementation and checking. |
| `completed` | The task is complete and waiting for wrap-up or archive. |
If the user wants to change policies such as "whether to create a task when there is no task," "when task creation may be skipped," or "whether sub-agents are required," edit these state blocks and the routing table above them.
## Local Modification Patterns
Common changes:
| Goal | Edit point |
| --- | --- |
| Add a phase | Update the Phase Index, phase body, routing, and state blocks. |
| Change task creation policy | Update the `no_task` state block and Phase 1 description. |
| Change the default implementation/check path | Update Phase 2 and skill routing. |
| Change the wrap-up flow | Update Phase 3 and `finish-work` related descriptions. Note the current split: Phase 3.4 = AI-driven code commits (batched, user-confirmed), Phase 3.5 = `/finish-work` (archive + record session). `/finish-work` refuses to run if the working tree is dirty. |
| Change platform differences | Update routing descriptions grouped by platform. |
After editing, make the AI reread `.trellis/workflow.md`; do not assume the flow from the old conversation is still valid.
## Relationship To Platform Files
`workflow.md` is the semantic center of the local workflow, but each platform can also have its own entry files:
- skills, such as `trellis-brainstorm` and `trellis-check`.
- commands/prompts/workflows, such as continue and finish-work.
- hooks, such as session-start or workflow-state injection.
If only `workflow.md` changes, platform entry files may still contain old language. When the user wants to change "what the AI actually does," also inspect the relevant platform directory.

View File

@@ -0,0 +1,71 @@
# Local Workspace Memory System
`.trellis/workspace/` stores cross-session memory. Its purpose is to let AI and humans understand what happened before across different windows and different days.
## Directory Structure
```text
.trellis/workspace/
├── index.md
└── <developer>/
├── index.md
├── journal-1.md
└── journal-2.md
```
| File | Purpose |
| --- | --- |
| `.trellis/.developer` | Current developer identity. |
| `.trellis/workspace/index.md` | Global workspace overview. |
| `.trellis/workspace/<developer>/index.md` | Session index for a developer. |
| `.trellis/workspace/<developer>/journal-N.md` | Session journal. |
## Developer Identity
Run this the first time:
```bash
python ./.trellis/scripts/init_developer.py <name>
```
This creates `.trellis/.developer` and the corresponding workspace directory. The AI should not change developer identity casually; if the identity is wrong, first confirm who is using the current project.
## Journal
`journal-N.md` records completed or partially completed work from each session. By default, each journal holds about 2000 lines; after that it rotates to the next file.
Common command for recording a session:
```bash
python ./.trellis/scripts/add_session.py \
--title "Session title" \
--summary "What changed" \
--commit "abc1234"
```
Planning or review work without a commit can also be recorded by using `--no-commit` or an empty commit value.
## Relationship Between Workspace Memory And Tasks
| System | What it stores |
| --- | --- |
| `.trellis/tasks/` | Requirements, design, research, and state for a specific task. |
| `.trellis/workspace/` | Work records across tasks and sessions. |
| `.trellis/spec/` | Engineering knowledge preserved as long-term conventions. |
If information is only useful for the current task, put it in the task directory.
If information describes what happened in the current session, put it in the workspace journal.
If information should be followed every time code is written in the future, put it in spec.
## Local Customization Points
| Need | Edit location |
| --- | --- |
| Change maximum journal lines | `max_journal_lines` in `.trellis/config.yaml`. |
| Change session auto-commit message | `session_commit_message` in `.trellis/config.yaml`. |
| Change session content format | `.trellis/scripts/add_session.py`. |
| Change how workspace is displayed in context | `.trellis/scripts/common/session_context.py`. |
## AI Usage Rules
The AI should not treat workspace as the only source of truth. When resuming a task, read the current task first, then use workspace for background. After a task is complete, record important process notes in workspace; if long-term rules emerged, update spec.

View File

@@ -0,0 +1,82 @@
# Agents
Trellis agent files define specialized roles. Common Trellis agents in a user project are:
- `trellis-research`
- `trellis-implement`
- `trellis-check`
File locations and formats differ by platform, but responsibility boundaries should stay consistent.
## Agent Responsibilities
| Agent | Responsibility |
| --- | --- |
| `trellis-research` | Investigate the question and write findings into the current task's `research/`. |
| `trellis-implement` | Implement against `prd.md`, optional `design.md` / `implement.md`, `implement.jsonl`, and related spec/research. |
| `trellis-check` | Review changes, fix discovered issues, and run necessary checks. |
Agent files should not become generic chat prompts. They should define input sources, write boundaries, whether code may be changed, and how results are reported.
## Common Paths
| Platform | Agent path |
| --- | --- |
| Claude Code | `.claude/agents/trellis-*.md` |
| Cursor | `.cursor/agents/trellis-*.md` |
| OpenCode | `.opencode/agents/trellis-*.md` |
| Codex | `.codex/agents/trellis-*.toml` |
| Kiro | `.kiro/agents/trellis-*.json` |
| Gemini CLI | `.gemini/agents/trellis-*.md` |
| Qoder | `.qoder/agents/trellis-*.md` |
| CodeBuddy | `.codebuddy/agents/trellis-*.md` |
| Factory Droid | `.factory/droids/trellis-*.md` |
| Pi Agent | `.pi/agents/trellis-*.md` |
| Reasonix | `.reasonix/skills/trellis-*/SKILL.md` (subagent frontmatter) |
| ZCode | `.zcode/cli/agents/trellis-*.md` |
GitHub Copilot agent/prompt support is provided by a combination of directories such as `.github/agents/`, `.github/prompts/`, and `.github/skills/`; inspect the files actually generated in the user project.
Main-session workflow platforms such as Kilo, Antigravity, and Devin may not have Trellis sub-agent files. They usually rely on workflows/skills to guide the main session.
## Two Context Loading Modes
### hook push
The platform hook injects task context before the agent starts. The agent file itself can focus more on responsibilities and boundaries.
Common on platforms that support agent hooks.
### agent pull
The agent file instructs the agent to read after startup:
- `python ./.trellis/scripts/task.py current --source`
- `implement.jsonl` or `check.jsonl`
- spec/research files referenced by JSONL
- current task `prd.md`
- `design.md` if present
- `implement.md` if present
This mode fits platforms whose hooks cannot reliably rewrite sub-agent prompts.
## Local Change Scenarios
| User need | Edit location |
| --- | --- |
| Implement agent must follow extra restrictions | The platform's `trellis-implement` agent file. |
| Check agent must run project-specific commands | `trellis-check` agent file, and `.trellis/spec/` if needed. |
| Research agent must output a fixed format | `trellis-research` agent file. |
| Agent cannot read task context | Agent prelude or `inject-subagent-context` hook. |
| Add a project-specific agent | Platform agent directory + related workflow/command/skill entry point. |
## Modification Principles
1. **Keep responsibilities single-purpose**. Do not mix research, implement, and check responsibilities into one agent.
2. **Specify the read order**. Agents must know to start from the active task, read jsonl/spec context, then read `prd.md`, `design.md` if present, and `implement.md` if present.
3. **Specify write boundaries**. Research usually only writes `research/`; implement can write code; check can fix issues.
4. **Keep semantics synchronized in multi-platform projects**. If the user configured Claude, Codex, and Cursor together, decide whether changes to one platform's agent also need to be applied to others.
## Do Not Default To Editing Upstream Templates
Local AI should default to modifying platform agent files inside the user project. Discuss upstream template source only when the user explicitly wants to contribute the change back to Trellis.

View File

@@ -0,0 +1,72 @@
# Hooks And Settings
Hooks/settings are the entry layer that connects a platform to Trellis. They decide which scripts, plugins, or extensions a platform runs for which events.
## Settings Responsibilities
settings/config files usually register:
- session-start hook: injects a Trellis overview when a new session starts or context resets.
- workflow-state hook: parses `[workflow-state:STATUS]` blocks from `.trellis/workflow.md` and emits the body matching the current task `status` on each user input. Parser-only; the script does not embed fallback content.
- sub-agent context hook: injects task context when implementation/check/research agents start.
- shell/session bridge: lets shell commands see the same Trellis session identity.
- platform plugin or extension entry points.
Common files:
| Platform | settings/config |
| --- | --- |
| Claude Code | `.claude/settings.json` |
| Cursor | `.cursor/hooks.json` |
| Codex | `.codex/hooks.json`, `.codex/config.toml` |
| OpenCode | `.opencode/package.json`, `.opencode/plugins/*` |
| Kiro | `.kiro/hooks/` + platform config |
| Gemini CLI | `.gemini/settings.json` |
| Qoder | `.qoder/settings.json` |
| CodeBuddy | `.codebuddy/settings.json` |
| GitHub Copilot | `.github/copilot/hooks.json` |
| Factory Droid | `.factory/settings.json` |
| Pi Agent | `.pi/settings.json`, `.pi/extensions/trellis/` |
| Trae IDE | `.trae/hooks.json` |
Reasonix and ZCode are pull-based platforms that do not use hooks or settings files; their agent files contain prelude instructions to read context after startup.
Whether these files exist in a project depends on which `trellis init --<platform>` flags the user ran.
## Hook Script Types
| Script | Purpose |
| --- | --- |
| `session-start.py` | Generates session-start context. |
| `inject-workflow-state.py` | Parses `[workflow-state:STATUS]` blocks in `.trellis/workflow.md` and emits the body matching the current task status. Falls back to `Refer to workflow.md for current step.` when no matching block exists. |
| `inject-subagent-context.py` | Injects PRD, JSONL context, and related spec/research into sub-agents. |
| `inject-shell-session-context.py` | Lets shell commands inherit Trellis session identity. |
Not every platform has every hook. Do not copy files from another platform just because a platform lacks a hook; first confirm whether that platform supports the corresponding event.
## Local Change Scenarios
| User need | Edit location |
| --- | --- |
| AI should see more/less context in a new session | Platform `session-start` hook. |
| Per-turn hint policy should change | `[workflow-state:STATUS]` block in `.trellis/workflow.md`. The hook parses workflow.md verbatim — no script edit required. |
| Sub-agent cannot read PRD/spec | `inject-subagent-context` hook or agent prelude. |
| `task.py current` in shell has no active task | Shell/session bridge hook or platform environment variable configuration. |
| Disable an automatic injection | The corresponding hook registration in settings/config. |
## Modification Principles
1. **Settings wire things up; hooks define behavior**. If only the hook changes, the platform may never call it. If only settings change, behavior may not change.
2. **Confirm platform event names first**. Different platforms use different names for SessionStart, UserPromptSubmit, AgentSpawn, shell execution, and similar events.
3. **Hooks read local `.trellis/`, not upstream source**. `.trellis/scripts/` and `.trellis/workflow.md` in the user project are the default targets.
4. **Errors must be visible**. Hook failures should tell the user what was not injected instead of silently leaving the AI without context.
## Troubleshooting Path
If the user says "AI did not read Trellis state":
1. Check whether the platform settings register the hook.
2. Check whether the hook file exists.
3. Manually run the `.trellis/scripts/get_context.py` or `task.py current --source` command that the hook depends on.
4. Check whether active task state exists in `.trellis/.runtime/sessions/`.
5. Check whether the platform shell passes session identity.

View File

@@ -0,0 +1,59 @@
# Platform Files Overview
Trellis connects the same local architecture to different AI tools. `.trellis/` stores the shared runtime; platform directories store adapter files that define how each AI tool enters Trellis.
When a local AI modifies Trellis, it should distinguish two file categories first:
- **Shared files**: `.trellis/workflow.md`, `.trellis/tasks/`, `.trellis/spec/`, `.trellis/scripts/`.
- **Platform files**: `.claude/`, `.codex/`, `.cursor/`, `.opencode/`, `.kiro/`, `.gemini/`, `.qoder/`, `.codebuddy/`, `.github/`, `.factory/`, `.pi/`, `.trae/`, `.kilocode/`, `.agent/`, `.devin/`, `.reasonix/`, `.zcode/`, and similar directories.
Platform files do not store business state. They let the corresponding AI tool read Trellis state, call Trellis scripts, and load Trellis skills/agents/hooks.
## Platform File Categories
| Category | Common paths | Purpose |
| --- | --- | --- |
| settings/config | `.claude/settings.json`, `.codex/hooks.json`, `.qoder/settings.json`, `.trae/hooks.json` | Register hooks, plugins, extensions, or platform behavior. |
| hooks/plugins/extensions | `.claude/hooks/`, `.opencode/plugins/`, `.pi/extensions/` | Inject context at session start, user input, agent startup, shell execution, and similar events. |
| agents | `.claude/agents/`, `.codex/agents/`, `.kiro/agents/` | Define `trellis-research`, `trellis-implement`, and `trellis-check`. |
| skills | `.claude/skills/`, `.agents/skills/`, `.qoder/skills/` | Capability descriptions that auto-trigger or can be read on demand. |
| commands/prompts/workflows | `.cursor/commands/`, `.github/prompts/`, `.devin/workflows/` | Entry points explicitly invoked by the user. |
## Three Platform Integration Modes
### 1. Hook / Extension Driven
These platforms can trigger scripts or plugins on specific events and actively inject Trellis context into AI.
Common capabilities:
- session-start injection of a `.trellis/` overview.
- workflow-state hints for each user turn.
- PRD/spec/research injection when sub-agents start.
- Shell commands inheriting session identity.
To change "when the AI knows what," inspect hooks/plugins/extensions and settings first.
### 2. Agent Prelude / Pull-Based
Some platforms cannot reliably let hooks rewrite sub-agent prompts, so the agent file itself instructs the agent to read the active task, PRD, and JSONL context after startup.
To change how sub-agents load context, inspect the agent files themselves.
### 3. Main-Session Workflow
Some platforms do not have Trellis sub-agent or hook capabilities. They rely on workflows/skills/commands to guide the main-session AI to read files, run scripts, and move tasks forward.
To change behavior, inspect platform workflows/skills/commands and `.trellis/workflow.md`.
## Local Modification Order
When the user asks to customize behavior for a platform, the AI should inspect files in this order:
1. Read `.trellis/workflow.md` to confirm the shared flow.
2. Read the target platform's settings/config to see which hooks/agents/skills/commands are registered.
3. Read the target platform's agents/skills/commands/hooks.
4. Modify the local file closest to the user's need.
5. If the change affects the shared flow, synchronize `.trellis/workflow.md` or `.trellis/spec/`.
Do not modify only platform files and forget the shared workflow. Do not modify only `.trellis/workflow.md` and forget that platform entry points may still contain old descriptions.

View File

@@ -0,0 +1,88 @@
# Platform File Map
This page lists common Trellis file locations in a user project by platform. Whether a platform directory exists in an actual project depends on which `trellis init --<platform>` commands the user ran.
## Matrix
| Platform | CLI flag | Main directory | Skill directory | Agent directory | Hooks/extensions |
| --- | --- | --- | --- | --- | --- |
| Claude Code | `--claude` | `.claude/` | `.claude/skills/` | `.claude/agents/` | `.claude/hooks/` + `.claude/settings.json` |
| Cursor | `--cursor` | `.cursor/` | `.cursor/skills/` | `.cursor/agents/` | `.cursor/hooks.json` + `.cursor/hooks/` |
| OpenCode | `--opencode` | `.opencode/` | `.opencode/skills/` | `.opencode/agents/` | `.opencode/plugins/` |
| Codex | `--codex` | `.codex/` | `.agents/skills/` | `.codex/agents/` | `.codex/hooks/` + `.codex/hooks.json` |
| Kilo | `--kilo` | `.kilocode/` | `.kilocode/skills/` | Usually none | `.kilocode/workflows/` |
| Kiro | `--kiro` | `.kiro/` | `.kiro/skills/` | `.kiro/agents/` | `.kiro/hooks/` |
| Gemini CLI | `--gemini` | `.gemini/` | `.agents/skills/` | `.gemini/agents/` | `.gemini/settings.json` + `.gemini/hooks/` |
| Antigravity | `--antigravity` | `.agent/` | `.agent/skills/` | Usually none | `.agent/workflows/` |
| Devin | `--devin` | `.devin/` | `.devin/skills/` | Usually none | `.devin/workflows/` |
| Qoder | `--qoder` | `.qoder/` | `.qoder/skills/` | `.qoder/agents/` | `.qoder/hooks/` + `.qoder/settings.json` |
| CodeBuddy | `--codebuddy` | `.codebuddy/` | `.codebuddy/skills/` | `.codebuddy/agents/` | `.codebuddy/hooks/` + `.codebuddy/settings.json` |
| GitHub Copilot | `--copilot` | `.github/` | `.github/skills/` | `.github/agents/` | `.github/copilot/hooks/` + prompts |
| Factory Droid | `--droid` | `.factory/` | `.factory/skills/` | `.factory/droids/` | `.factory/hooks/` + settings |
| Pi Agent | `--pi` | `.pi/` | `.pi/skills/` | `.pi/agents/` | `.pi/extensions/trellis/` (native `trellis_subagent` tool) + `.pi/settings.json` |
| Trae IDE | `--trae` | `.trae/` | `.trae/skills/` | `.trae/agents/` | `.trae/hooks/` + `.trae/hooks.json` |
| Reasonix | `--reasonix` | `.reasonix/` | `.reasonix/skills/` | None — sub-agents are skills with `runAs: subagent` frontmatter | None |
| ZCode | `--zcode` | `.zcode/` | `.agents/skills/` | `.zcode/cli/agents/` | pull-based prelude (no hooks) |
## Capability Groups
### Trellis Sub-Agent Support
These platforms usually have `trellis-research`, `trellis-implement`, and `trellis-check` files:
- Claude Code
- Cursor
- OpenCode
- Codex
- Kiro
- Gemini CLI
- Qoder
- CodeBuddy
- GitHub Copilot
- Factory Droid
- Pi Agent
- Trae IDE
- Reasonix (delivered as skills with `runAs: subagent` under `.reasonix/skills/`, not as a separate `agents/` directory)
- ZCode
When changing implementation/check/research behavior, look for the corresponding platform agent files first.
### Native Trellis Sub-Agent Tool
Some platforms expose a first-class tool that the host runtime understands. The model calls it like any other tool and the host renders progress cards, validates the agent name against `.<platform>/agents/`, and enforces dispatch modes.
- Pi Agent — `trellis_subagent` tool, defined in `.pi/extensions/trellis/index.ts`. Supports `single` / `parallel` / `chain` dispatch modes and emits live `trellis-subagent-progress` events.
When changing sub-agent dispatch behavior on these platforms, edit the extension file, **not** the agent markdown — the agent markdown defines responsibilities, but the host extension owns dispatch, validation, and progress rendering.
### Main-Session Workflow Platforms
These platforms rely more on workflows/skills to guide the main session:
- Kilo
- Antigravity
- Devin
When changing behavior, inspect workflows and skills first. Do not assume Trellis sub-agents exist.
### Shared `.agents/skills/`
Codex writes the shared `.agents/skills/` layer. Some tools that support agentskills.io can also read this directory. If the user wants multiple compatible tools to share one skill, consider `.agents/skills/` first, but do not assume every platform reads it.
## Decision Rules When Modifying Platform Files
1. User specified a platform: modify only that platform directory unless shared workflow/spec files must also change.
2. User says "all platforms should do this": synchronize equivalent entry points platform by platform; do not modify only one directory.
3. User only says "my AI": inspect the configuration directories that actually exist in the project and infer the current AI platform.
4. User wants project rules: prefer `.trellis/spec/` or a project-local skill.
5. User wants Trellis behavior: edit `.trellis/workflow.md` plus platform hooks/agents/skills/commands.
## When Paths Differ
Platform ecosystems change, and user projects may already be customized. If this table disagrees with local files, use the actual settings/config in the user project as authoritative:
- Check the hook that settings registers.
- Check the script that a command/prompt/workflow points to.
- Judge behavior by the read rules currently written in the agent file.
Do not delete a custom file just because it is not listed in this path table.

View File

@@ -0,0 +1,85 @@
# Skills, Commands, Prompts, And Workflows
Skills and commands are textual entry points for user interaction with Trellis. Different platforms use different names, but their core purpose is the same: tell the AI how to enter the Trellis flow when the user expresses a certain intent.
## Conceptual Differences
| Type | Trigger mode | Best for |
| --- | --- | --- |
| skill | AI auto-match or explicit user mention | Long-term capabilities, workflow rules, modification guides. |
| command | Explicit user invocation | Clear operation entry points such as continue and finish-work. |
| prompt | Explicit user invocation or platform selection | Similar to command, but in a platform prompt format. |
| workflow | Explicit user selection or platform auto-match | Guides the main session when no sub-agent/hook exists. |
Trellis workflow skills usually share one semantic set: brainstorm, before-dev, check, update-spec, break-loop. Multi-file built-in skills such as `trellis-meta` use layered references.
## Common Paths
| Platform | Common entries |
| --- | --- |
| Claude Code | `.claude/skills/`, `.claude/commands/` |
| Cursor | `.cursor/skills/`, `.cursor/commands/` |
| OpenCode | `.opencode/skills/`, `.opencode/commands/` |
| Codex | `.agents/skills/`, `.codex/skills/` |
| Kilo | `.kilocode/skills/`, `.kilocode/workflows/` |
| Kiro | `.kiro/skills/` |
| Gemini CLI | `.agents/skills/`, `.gemini/commands/` |
| Antigravity | `.agent/skills/`, `.agent/workflows/` |
| Devin | `.devin/skills/`, `.devin/workflows/` |
| Qoder | `.qoder/skills/`, `.qoder/commands/` |
| CodeBuddy | `.codebuddy/skills/`, `.codebuddy/commands/` |
| GitHub Copilot | `.github/skills/`, `.github/prompts/` |
| Factory Droid | `.factory/skills/`, `.factory/commands/` |
| Pi Agent | `.pi/skills/` |
| Reasonix | `.reasonix/skills/` |
| ZCode | `.agents/skills/`, `.zcode/commands/` |
In a user project, use the files actually generated by init as authoritative.
## Skill Structure
A common skill is a directory:
```text
trellis-meta/
├── SKILL.md
└── references/
```
`SKILL.md` should tell the AI:
- When to use this skill.
- Which reference to read first for the current task.
- What not to do.
References hold longer explanations so the entry file does not contain everything.
## Command/Prompt/Workflow Structure
Commands, prompts, and workflows are usually single files. Their content should include:
- When to use it.
- Which `.trellis/` files to read.
- Which scripts to run.
- How to report after completion.
They should not store task state; task state belongs in `.trellis/tasks/` and `.trellis/.runtime/`.
## Local Change Scenarios
| User need | Edit location |
| --- | --- |
| Change AI auto-trigger rules | The corresponding skill's frontmatter description. |
| Change user command behavior | The corresponding command/prompt/workflow file. |
| Add a project-local skill | Platform skill directory, or shared `.agents/skills/`. |
| Let multiple platforms share one capability | Write equivalent skills in each platform skill directory, or use the `.agents/skills/` shared layer on platforms that support it. |
| Change finish/continue entry points | Platform commands/prompts/workflows. |
## Modification Principles
1. **Keep entry files short; references carry long content**. This matters especially for multi-file skills like `trellis-meta`.
2. **Make trigger descriptions specific**. A description that is too broad can mis-trigger; one that is too narrow may not trigger.
3. **Keep the same semantics consistent across platforms**. File formats can differ, but behavior descriptions should match.
4. **Put project-specific capabilities in local skills**. Do not put team-private flows into public `trellis-meta`.
If the user only wants local AI to know one more project rule, usually create a project-local skill or update `.trellis/spec/` instead of changing a Trellis built-in workflow skill.

View File

@@ -0,0 +1,81 @@
---
name: trellis-session-insight
description: "Reach into past AI conversation history through the `trellis mem` CLI. Use whenever the user asks 'how did we solve X last time', 'have we discussed this before', 'what was the decision on X', 'remind me what we did in this task', '上次怎么解的', '之前讨论过吗', '想起一段对话', or when starting a brainstorm that overlaps prior work, debugging a familiar bug, continuing a task across sessions, or doing a finish-work review. Returns raw past dialogue; decide for the moment whether to update spec, append to task notes, quote inline in the answer, or just internalize."
---
# Trellis Session Insight
This skill teaches an AI **how to call `trellis mem`** — the project's cross-session memory feedstock — and **when reaching for it is the right move**.
It is intentionally a **capability skill, not a workflow**. There is no fixed output file, no required write-back step, no "always run after finish-work" rule. What to do with what `mem` returns is a judgement call made in the moment of the conversation. The skill exists so the AI knows the capability is there and can decide.
## What `trellis mem` is
A local CLI that indexes the user's past Claude Code, Codex, and Pi Agent conversation logs (the JSONL files each platform stores under `~/.claude/projects/`, `~/.codex/sessions/`, and `~/.pi/agent/sessions/`) and lets you list, search, slice by Trellis task boundaries, and dump cleaned dialogue from them. OpenCode logs are not yet indexable (provider adapter pending) — when an OpenCode session is the obvious target, surface that limitation rather than guessing.
Nothing in `mem` is uploaded. All reads are local.
## When to reach for it
The bar is "would a senior teammate ask 'didn't we already talk about this?'" — those are the moments. Some concrete patterns:
- **Brainstorm rerun risk.** Starting a new task that touches an area the user has been in before, and you want to check whether a decision was already made — before re-asking the user.
- **Familiar-bug debugging.** The current bug pattern feels like one the user reported / fixed before. Pulling the relevant past session can save a full debugging loop.
- **Cross-session continuation.** The user resumes work after a gap and says "where were we" / "继续上次的" without being specific.
- **Decision retrieval.** The user references "the decision we made about X" but the decision lives in an old brainstorm, not in any `prd.md` / `spec/`.
- **Finish-work retrospective.** When the user explicitly asks for a wrap-up of what was decided / what hurt / what surprised them in this task — not as a forced step on every finish-work.
- **Pattern-spotting across past work.** The user asks "do I keep making the same mistake on X" / "我每次都踩这个坑吗" — search across sessions answers that.
If none of these apply, don't call `mem`. It is a tool, not a ceremony.
## When NOT to reach for it
- The relevant context is already in the current turn, `prd.md`, `design.md`, recent `git log`, or the open files. `mem` is for stuff that has fallen out of immediate reach.
- The user is asking about a fact in the code, not a fact from a past conversation. `git log -p` / `grep` / reading the file directly is faster and more authoritative.
- You are in a sub-agent (`trellis-implement` / `trellis-check`) whose dispatch prompt already includes the curated `implement.jsonl` / `check.jsonl` context. Adding `mem` on top usually just clutters.
- The user has explicitly said "don't dig through history, just answer what I asked".
## What to do with what `mem` returns
Treat the output as **raw material**, not a deliverable. Once you have it, decide based on the live conversation:
- **Quote inline in your reply** if a specific past exchange answers the user's current question — and cite the session-id / phase so the user can verify.
- **Update `<task>/prd.md` or `<task>/design.md`** if `mem` surfaced a load-bearing decision that should have been written down but wasn't. Surface the proposed edit to the user first.
- **Append to a task-local notes file** (e.g. `<task>/notes.md` or extending an existing one) if the finding belongs to the current task's record but doesn't fit the PRD.
- **Update `.trellis/spec/`** if the finding is a project-wide convention or gotcha that would help future tasks. Run the `trellis-update-spec` skill for that — `session-insight` ends at the discovery.
- **Just absorb it** for the next few turns and answer better, without writing anything. This is often the right move for one-off recall.
Trellis does not prescribe a single destination. Forcing every recall into a fixed file makes the file grow into noise. Let the situation decide.
## How to call it
Full CLI reference is in `references/cli-quick-reference.md`. The 80% case is one of:
```bash
# Find sessions whose contents mention a keyword (project-scope is default;
# add --global to search every project on this machine).
trellis mem search "<keyword>"
# Dump dialogue from one session, optionally filtered by phase or keyword.
trellis mem extract <session-id> --phase brainstorm
trellis mem extract <session-id> --grep "<keyword>"
# Drill into a session: top-N hit turns + surrounding context.
trellis mem context <session-id> --turns 3 --around 2
# When you do not know the session id yet, start with list + filter.
trellis mem list --cwd <project-path>
trellis mem projects # → list active project cwds, then narrow
```
Phase slicing (`--phase brainstorm|implement|all`) cuts the session at `task.py create` and `task.py start` boundaries. For a finish-work review of the current task, `--phase brainstorm` recovers the planning discussion and `--phase implement` recovers the execution loop. Default is `all`.
## Triggering patterns
`references/triggering-patterns.md` lists more verbatim user phrasings (English + Chinese) that should make you think "reach for `mem`" — keep that handy when training instinct.
## Out of scope
- `mem` does not edit code or update files. Any write-back is your decision in the moment.
- `mem` is read-only on the platform JSONL stores. It does not push or sync to remote.
- This skill does not replace `trellis-update-spec` (which is the right tool for promoting a finding into project-wide guidance) or the platform-native task / spec workflow.

View File

@@ -0,0 +1,65 @@
# `trellis mem` CLI Reference
Full flag reference for the five subcommands. Pin this as the authoritative source — `trellis mem help` prints the same content at runtime, so anything here that drifts is a bug.
## Subcommands
| Command | Purpose |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `list` | List sessions. Default subcommand when none is given. |
| `search <keyword>` | Find sessions whose contents match a keyword. |
| `context <session-id>` | Drill into one session: top-N hit turns + surrounding context. Pair with `--grep` for keyword anchoring. |
| `extract <session-id>` | Dump cleaned dialogue. Combine with `--phase` / `--grep` to slice. |
| `projects` | List active project `cwd` values with session counts. Use this to discover which `--cwd` to pass to other subcommands. |
## Flags (apply where meaningful)
| Flag | Subcommands | Meaning |
| --------------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--platform claude\|codex\|opencode\|pi\|all` | all | Default `all`. OpenCode adapter is currently a stub on `0.6.0-beta.*` — see "Caveats" below. |
| `--since YYYY-MM-DD` | list / search | Inclusive lower date bound. |
| `--until YYYY-MM-DD` | list / search | Inclusive upper date bound. |
| `--global` | list / search | Include sessions from every project on this machine. Default is the current project `cwd`. |
| `--cwd <path>` | list / search | Force a specific project cwd instead of inferring from where you are. |
| `--limit N` | list / search | Cap output rows. Default `50`. |
| `--grep KW` | extract / context | Filter turns by keyword. Multi-token AND when whitespace-separated. |
| `--phase brainstorm\|implement\|all` | extract | Slice session by Trellis task boundaries. `brainstorm` = `[task.py create, task.py start)`. `implement` = turns outside brainstorm windows. Default `all`. |
| `--turns N` | context | Number of hit turns to return. Default `3`. |
| `--around N` | context | Surrounding turns to include per hit. Default `1`. |
| `--max-chars N` | context | Total character budget. Default `6000` (~1500 tokens). |
| `--include-children` | search / context | Merge OpenCode sub-agent sessions into their parent session. |
| `--json` | all | Emit machine-parseable JSON instead of human-readable output. |
## Common one-liners
```bash
# What past sessions discussed "deadlock" anywhere on this machine?
trellis mem search "deadlock" --global --limit 20
# Inside a specific session, surface the top 5 turns that mention "lock contention"
# plus 2 turns of surrounding context.
trellis mem context 5842592d --grep "lock contention" --turns 5 --around 2
# Recover the brainstorm window for a session — useful when continuing a task
# the user started a week ago.
trellis mem extract 5842592d --phase brainstorm
# List every project this machine has Trellis sessions for, with counts.
trellis mem projects
```
## Output shapes
- **Default human output** (no `--json`): wrapped to a terminal, with session ids highlighted and turn markers visible. Suitable to read inline but messy to paste into a markdown file.
- **`--json`**: stable schema, safe to parse and process. When piping `mem` output into a follow-up step (e.g. summarizing for a Lessons section), prefer `--json`.
## Caveats
- **OpenCode adapter is a stub on `0.6.0-beta.*`.** When `--platform` resolves to OpenCode (or `all` and OpenCode would be included), `mem` prints a one-line "reader unavailable" notice and continues with the other platforms. Don't promise OpenCode coverage in your reply until the adapter ships.
- **`--phase` slicing depends on `task.py create` / `task.py start` invocations appearing in the recorded bash calls of the session.** Sessions where the user ran `task.py` from a different terminal — outside the recorded AI loop — will not have phase boundaries. `--phase all` is the safe fallback.
- **`mem` indexes platform JSONL files directly.** If the user has cleared their Claude / Codex / Pi session storage, `mem` cannot recover what is no longer on disk.
- **`mem` is read-only.** No remote sync, no edits to platform JSONL. Any write you do based on `mem` findings is your own follow-up call into the editing tools available to you.
## When you need more than this reference
Run `trellis mem help` in the user's shell. The runtime help is authoritative and will be ahead of this reference during fast-moving beta releases.

View File

@@ -0,0 +1,93 @@
# Triggering Patterns
Verbatim user phrasings that should make an AI reach for `trellis mem`. Calibrate instinct against these — if a user message hits one of these patterns and you do not reach for `mem`, you probably missed an obvious recall.
Patterns are grouped by the *intent* behind the phrasing, not the surface words. The same intent shows up in different languages and registers.
## Past-solution recall
The user is asking "how did we (or I) solve this before". Past dialogue holds the answer; the codebase shows the result but not the reasoning.
- "How did we solve this last time?"
- "What did we end up doing about X?"
- "We dealt with this once already, didn't we?"
- "上次怎么解的?"
- "之前是怎么搞定 X 的?"
- "我记得以前修过类似的"
Reach: `trellis mem search "<symptom keyword>" --global --limit 10`, then `context` into the hit that looks closest.
## Decision retrieval
The user is referencing a decision that lives in old dialogue, not in any committed file. Look in brainstorm windows.
- "What was the decision on X?"
- "Did we decide to use Postgres or SQLite?"
- "The rationale for choosing X over Y was…?"
- "我们当时为啥选了 X 而不是 Y?"
- "关于 X 我们之前是怎么定的?"
- "之前讨论过 X 的方案吗?"
Reach: `trellis mem search "<decision keyword>"` to find the session, then `extract <id> --phase brainstorm` to recover the discussion.
## Cross-session continuation
The user resumed work after a gap and the context is implicit.
- "Where were we?"
- "Continue from last time."
- "Pick up where we left off."
- "继续上次的"
- "我们上次做到哪了"
- "接着昨天那个任务"
Reach: `trellis mem list --task <current-task-dir>` to find the most recent sessions tied to the active task, then `extract` the last one.
## Familiar-bug debugging
The current bug feels like one already seen. Past sessions probably hold the resolution path.
- "I feel like I've hit this before."
- "Doesn't this look like that bug from last month?"
- "Same kind of timeout I had in X."
- "这个错好像之前见过"
- "这个 bug 是不是上次那个?"
- "怎么又是这个 error?"
Reach: `trellis mem search "<error message fragment>" --global`. Anchor on a short, distinctive token from the actual error string.
## Self-pattern spotting
The user is asking whether they keep repeating the same kind of mistake or decision.
- "Do I always make this mistake?"
- "How often have I run into X?"
- "Is this a recurring thing for me?"
- "我每次都踩这个坑吗?"
- "我老犯这个错?"
- "这类问题之前出现过几次?"
Reach: `trellis mem search "<topic>" --global --limit 50` and scan the dates / projects in the listing. Optionally `extract` two or three for comparison.
## Finish-work retrospective (on demand)
The user explicitly wants to look back at this task — not as a forced step, only when they ask.
- "Summarize what we did in this task."
- "What were the key decisions / surprises?"
- "Write up the lessons from this round."
- "总结一下这次的经验"
- "记一下这次踩的坑"
- "复盘下这个任务"
Reach: identify the current task's session id (from `.trellis/.runtime/sessions/*.json` or `mem list --task <task-dir>`), then `extract <id> --phase brainstorm` and `--phase implement`. Present a summary — surface concrete file:line citations where possible. Whether to also write the summary somewhere (PRD, spec, notes file) is the user's call; offer, don't auto-write.
## Anti-patterns: do NOT reach for `mem` here
- "What does this function do?" → read the file.
- "Why is this test failing?" → read the test output and the file.
- "What's the right pattern for X in our codebase?" → grep / read spec files.
- "What's the latest npm version of Y?" → call `npm view`.
- "Fix this bug." → debug. Reach for `mem` only if you suspect prior context exists; otherwise it is noise.
The bar stays: would a senior teammate ask "didn't we already talk about this?" before answering? If yes, reach for `mem`. If no, don't.

View File

@@ -0,0 +1,41 @@
---
name: trellis-spec-bootstrap
description: "Bootstrap project-specific Trellis coding specs with a platform-neutral single-agent workflow. Use when creating or refreshing .trellis/spec guidelines, analyzing a codebase with GitNexus, ABCoder, or source inspection, decomposing package/layer spec work, and writing real codebase-backed spec docs without placeholder text."
---
# Trellis Spec Bootstrap
Use this skill to create or refresh `.trellis/spec/` guidelines from the real codebase. One capable agent owns the full loop: analyze the repository, choose the spec boundaries, write the docs, and verify the result. The workflow does not depend on a specific host, CLI, or agent brand.
## Workflow
1. Confirm Trellis is initialized and inspect the current `.trellis/spec/` tree.
2. Analyze the repository architecture with the best available tools: GitNexus, ABCoder, language tooling, and direct source reads.
3. Decompose the spec work by package and layer only when that reflects the actual codebase.
4. Fill or reshape the spec files with concrete patterns, file paths, examples, and anti-patterns from the project.
5. Verify that the final specs are internally consistent and contain no template placeholders.
## Reference Routing
| Need | Read |
|------|------|
| Repository architecture analysis | [references/repository-analysis.md](references/repository-analysis.md) |
| Spec work decomposition and task planning | [references/spec-task-planning.md](references/spec-task-planning.md) |
| Writing high-signal Trellis spec files | [references/spec-writing.md](references/spec-writing.md) |
| GitNexus and ABCoder MCP setup | [references/mcp-setup.md](references/mcp-setup.md) |
## Operating Rules
- Treat templates as starting points, not contracts. Delete, rename, split, or add spec files when the repository calls for it.
- Prefer source-backed rules over generic advice. Every important recommendation should point at a real file or repeated local pattern.
- Keep execution single-owner by default. Optional helper agents are an implementation detail, not a requirement or user-visible dependency.
- Do not write platform-specific instructions unless the target project already standardizes on that platform.
- Do not leave placeholder text, empty headings, or copied boilerplate in `.trellis/spec/`.
## Done Criteria
- `.trellis/spec/` describes the project as it exists now.
- Each relevant package or layer has practical coding guidance with real examples.
- Non-applicable template sections are removed.
- `index.md` files match the final spec file set.
- Any required setup or analysis assumptions are documented in the relevant spec or task notes.

View File

@@ -0,0 +1,90 @@
# MCP Setup
GitNexus and ABCoder are recommended when bootstrapping Trellis specs because they expose architecture and AST context to the agent. They are tool choices, not platform requirements. Configure them through whatever MCP mechanism your agent host provides.
## GitNexus
GitNexus builds a code knowledge graph from the repository. Use it for module boundaries, execution flows, dependency relationships, blast radius, and graph queries.
### Install and Index
```bash
# Run from the repository root.
npx gitnexus analyze
# Check index status.
npx gitnexus status
# Re-index after code changes when the analysis is stale.
npx gitnexus analyze
```
The index is written to `.gitnexus/`. Keep embeddings only if the project already uses them; otherwise a normal index is enough for spec bootstrapping.
### MCP Server Command
Use this server command in the host's MCP configuration:
```bash
npx -y gitnexus mcp
```
### Useful Tools
| Tool | Purpose |
|------|---------|
| `gitnexus_query` | Find execution flows and functional areas by concept |
| `gitnexus_context` | Inspect callers, callees, references, and process participation for a symbol |
| `gitnexus_impact` | Understand blast radius before changing a symbol |
| `gitnexus_detect_changes` | Check changed symbols and affected flows before finishing |
| `gitnexus_cypher` | Run direct graph queries |
| `gitnexus_list_repos` | List indexed repositories |
## ABCoder
ABCoder parses code into UniAST and gives precise package, file, and node-level structure. Use it for signatures, type shapes, implementations, dependencies, and reverse references.
### Install
```bash
go install github.com/cloudwego/abcoder@latest
abcoder --help
```
### Parse Repositories
```bash
abcoder parse /absolute/path/to/package \
--lang typescript \
--name package-name \
--output ~/abcoder-asts
```
For monorepos, parse each package with a stable `--name` so task notes can reference the same repository names.
### MCP Server Command
Use this server command in the host's MCP configuration:
```bash
abcoder mcp ~/abcoder-asts
```
### Useful Tools
| Tool | Layer | Purpose |
|------|-------|---------|
| `list_repos` | 1 | List parsed repositories |
| `get_repo_structure` | 2 | Inspect packages and files |
| `get_package_structure` | 3 | Inspect nodes within a package |
| `get_file_structure` | 3 | Inspect functions, classes, types, and signatures in a file |
| `get_ast_node` | 4 | Retrieve code, dependencies, references, and implementations |
## Verification
After configuration, verify from the agent host that both MCP servers are visible. Then run one simple query against each server before starting the spec writing pass.
```bash
ls .gitnexus/meta.json
ls ~/abcoder-asts/*.json
```

View File

@@ -0,0 +1,59 @@
# Repository Analysis
The goal is to discover the project's real architecture before writing rules. Do not start from generic spec templates and fill blanks. Start from the code, then let the spec structure follow.
## Analysis Order
1. Read the existing `.trellis/spec/` tree and note which files are templates, outdated, or already project-specific.
2. Inspect package manifests, build scripts, workspace config, and top-level documentation to identify packages and runtime layers.
3. Use GitNexus for execution flows, module clusters, dependency hubs, and impact-sensitive areas.
4. Use ABCoder or language-native tooling for exact signatures, types, class boundaries, and implementation examples.
5. Read representative source and test files directly before turning any finding into a spec rule.
## What To Capture
| Area | Questions |
|------|-----------|
| Package boundaries | What does each package own? What imports cross boundaries? |
| Runtime layers | Which code is CLI, backend, frontend, worker, shared library, test-only, or tooling? |
| Core abstractions | Which types, services, stores, commands, routes, or adapters define the system shape? |
| Data flow | Where does user input enter, how is it validated, and where does state persist? |
| Error handling | How are failures represented, logged, surfaced, and tested? |
| Configuration | Where do defaults, environment config, generated files, and templates live? |
| Tests | Which test styles are trusted examples for new work? |
## GitNexus Usage
Start broad, then inspect specific symbols:
```text
gitnexus_query({query: "CLI command execution flow"})
gitnexus_query({query: "template generation and migration"})
gitnexus_context({name: "SymbolName"})
gitnexus_cypher({query: "MATCH (n)-[r]->(m) RETURN n.name, type(r), m.name LIMIT 30"})
```
Use GitNexus results to find important files and flows. Do not quote graph output as the final authority until you have checked the relevant source files.
## ABCoder Usage
Use ABCoder when the spec needs exact code shapes:
```text
list_repos()
get_repo_structure({repo_name: "package-name"})
get_file_structure({repo_name: "package-name", file_path: "src/example.ts"})
get_ast_node({repo_name: "package-name", node_ids: [{mod_path: "...", pkg_path: "...", name: "SymbolName"}]})
```
ABCoder is most valuable for documenting constructor patterns, function signatures, type contracts, and reference chains.
## Analysis Notes
Keep short notes while analyzing. The notes should include:
- Package or layer name.
- Files that define the local pattern.
- Rules the spec should teach.
- Anti-patterns found in old code, comments, tests, or migration paths.
- Spec files that should be created, deleted, renamed, or merged.

View File

@@ -0,0 +1,61 @@
# Spec Task Planning
Use a single agent as the default execution model. The agent may create Trellis tasks for traceability, but the skill should not require a specific platform, CLI, or parallel worker model.
## Decomposition
Create spec work units around real ownership boundaries:
- One package when a package has its own conventions.
- One layer when the same package has distinct frontend, backend, CLI, worker, or shared-library rules.
- One cross-cutting guide when a pattern spans packages and is not owned by one layer.
Avoid artificial decomposition. A small library usually needs one focused spec pass, not several tasks.
## Task Shape
When a Trellis task is useful, write a concise PRD with these sections:
```markdown
# Fill <package-or-layer> Trellis Specs
## Goal
Write project-specific `.trellis/spec/` guidance for <scope>.
## Scope
- Spec directory:
- Source directories to inspect:
- Tests to inspect:
- Out of scope:
## Architecture Context
Summarize the concrete findings from repository analysis.
## Files To Create Or Update
- `.trellis/spec/.../index.md`
- `.trellis/spec/.../<topic>.md`
## Rules
- Adapt the spec file set to the real codebase.
- Use real source examples with file paths.
- Remove template-only sections that do not apply.
- Do not modify product source code unless the task explicitly asks for it.
## Acceptance Criteria
- [ ] Specs contain concrete examples and anti-patterns from the repository.
- [ ] No placeholder text remains.
- [ ] Index files match the final spec files.
- [ ] Claims are backed by source files, tests, or project docs.
```
## Optional Helper Agents
If the host supports subagents, helpers can inspect independent packages or run verification. They are optional. The main agent still owns integration and final quality.
Helper tasks must have clear ownership:
- Read-only research tasks may inspect any source needed for the assigned scope.
- Write tasks should own disjoint spec directories.
- Verification tasks should check placeholder removal, broken links, and consistency.
Do not encode helper-agent names, vendor-specific commands, or platform-specific routing in the skill. Put only the required work and acceptance criteria in the task.

View File

@@ -0,0 +1,70 @@
# Spec Writing
Trellis specs are coding guidance for future agents. They should explain how to work in this repository, not how a generic project might be organized.
## Write From Evidence
Each important rule should be backed by one of these:
- A source file that demonstrates the preferred pattern.
- A test file that shows expected behavior.
- A project document that defines the convention.
- A repeated pattern across multiple files.
Use short snippets only when they make the rule clearer. Prefer linking to the file path and naming the symbol or behavior.
## File Structure
Keep the spec tree aligned with the project:
- Keep `index.md` as the navigation file for the spec directory.
- Split topics when developers would look for them independently.
- Merge topics when separate files would repeat the same rule.
- Delete template files that do not apply.
- Add new files for important local patterns the template missed.
## Content Standards
Good spec sections include:
- When the rule applies.
- The local pattern to follow.
- The source or test files that prove the pattern.
- Common mistakes or anti-patterns.
- Verification commands or checks when they are specific and reliable.
Avoid:
- Placeholder prose.
- Generic framework advice.
- Tool instructions that only work in one agent host.
- Long copied code blocks.
- Rules based on a single accidental implementation detail.
## Example Shape
```markdown
## Command Handlers
Command handlers should keep argument parsing, validation, and side effects separate. The local pattern is:
- Parse CLI flags at the command boundary.
- Convert raw inputs into typed task options before invoking core logic.
- Keep filesystem writes in the command or service layer, not in template helpers.
Reference files:
- `packages/cli/src/commands/example.ts`
- `packages/cli/test/commands/example.test.ts`
Avoid passing raw `process.argv` or unvalidated config objects into shared helpers.
```
## Final Pass
Before finishing:
```bash
grep -R "To be filled\\|TODO: fill\\|placeholder" .trellis/spec
```
Also check links, index files, and whether any spec still describes a template rather than this repository.

View File

@@ -0,0 +1,356 @@
---
name: trellis-update-spec
description: "Captures executable contracts and coding conventions into .trellis/spec/ documents. Use when learning something valuable from debugging, implementing, or discussion that should be preserved for future sessions."
---
# Update Code-Spec - Capture Executable Contracts
When you learn something valuable (from debugging, implementing, or discussion), use this to update the relevant code-spec documents.
**Timing**: After completing a task, fixing a bug, or discovering a new pattern
---
## Code-Spec First Rule (CRITICAL)
In this project, "spec" for implementation work means **code-spec**:
- Executable contracts (not principle-only text)
- Concrete signatures, payload fields, env keys, and boundary behavior
- Testable validation/error behavior
If the change touches infra or cross-layer contracts, code-spec depth is mandatory.
### Mandatory Triggers
Apply code-spec depth when the change includes any of:
- New/changed command or API signature
- Cross-layer request/response contract change
- Database schema/migration change
- Infra integration (storage, queue, cache, secrets, env wiring)
### Mandatory Output (7 Sections)
For triggered tasks, include all sections below:
1. Scope / Trigger
2. Signatures (command/API/DB)
3. Contracts (request/response/env)
4. Validation & Error Matrix
5. Good/Base/Bad Cases
6. Tests Required (with assertion points)
7. Wrong vs Correct (at least one pair)
---
## When to Update Code-Specs
| Trigger | Example | Target Spec |
|---------|---------|-------------|
| **Implemented a feature** | Added a new integration or module | Relevant spec file |
| **Made a design decision** | Chose extensibility pattern over simplicity | Relevant spec + "Design Decisions" section |
| **Fixed a bug** | Found a subtle issue with error handling | Relevant spec (e.g., error-handling docs) |
| **Discovered a pattern** | Found a better way to structure code | Relevant spec file |
| **Hit a gotcha** | Learned that X must be done before Y | Relevant spec + "Common Mistakes" section |
| **Established a convention** | Team agreed on naming pattern | Quality guidelines |
| **New thinking trigger** | "Don't forget to check X before doing Y" | `guides/*.md` (as a checklist item) |
**Key Insight**: Code-spec updates are NOT just for problems. Every feature implementation contains design decisions and contracts that future AI/developers need to execute safely.
---
## Spec Structure Overview
```
.trellis/spec/
├── <layer>/ # Per-layer coding standards (e.g., backend/, frontend/, api/)
│ ├── index.md # Overview and links
│ └── *.md # Topic-specific guidelines
└── guides/ # Thinking checklists (NOT coding specs!)
├── index.md # Guide index
└── *.md # Topic-specific guides
```
### CRITICAL: Code-Spec vs Guide - Know the Difference
| Type | Location | Purpose | Content Style |
|------|----------|---------|---------------|
| **Code-Spec** | `<layer>/*.md` | Tell AI "how to implement safely" | Signatures, contracts, matrices, cases, test points |
| **Guide** | `guides/*.md` | Help AI "what to think about" | Checklists, questions, pointers to specs |
**Decision Rule**: Ask yourself:
- "This is **how to write** the code" → Put in a spec layer directory
- "This is **what to consider** before writing" → Put in `guides/`
**Example**:
| Learning | Wrong Location | Correct Location |
|----------|----------------|------------------|
| "Use API X not API Y for this task" | ❌ `guides/` (too specific for a thinking guide) | ✅ Relevant spec file (concrete convention) |
| "Remember to check X when doing Y" | ❌ Spec file (too abstract for a spec) | ✅ `guides/` (thinking checklist) |
**Guides should be short checklists that point to specs**, not duplicate the detailed rules.
---
## Update Process
### Step 1: Identify What You Learned
Answer these questions:
1. **What did you learn?** (Be specific)
2. **Why is it important?** (What problem does it prevent?)
3. **Where does it belong?** (Which spec file?)
### Step 2: Classify the Update Type
| Type | Description | Action |
|------|-------------|--------|
| **Design Decision** | Why we chose approach X over Y | Add to "Design Decisions" section |
| **Project Convention** | How we do X in this project | Add to relevant section with examples |
| **New Pattern** | A reusable approach discovered | Add to "Patterns" section |
| **Forbidden Pattern** | Something that causes problems | Add to "Anti-patterns" or "Don't" section |
| **Common Mistake** | Easy-to-make error | Add to "Common Mistakes" section |
| **Convention** | Agreed-upon standard | Add to relevant section |
| **Gotcha** | Non-obvious behavior | Add warning callout |
### Step 3: Read the Target Code-Spec
Before editing, read the current code-spec to:
- Understand existing structure
- Avoid duplicating content
- Find the right section for your update
```bash
cat .trellis/spec/<category>/<file>.md
```
### Step 4: Make the Update
Follow these principles:
1. **Be Specific**: Include concrete examples, not just abstract rules
2. **Explain Why**: State the problem this prevents
3. **Show Contracts**: Add signatures, payload fields, and error behavior
4. **Show Code**: Add code snippets for key patterns
5. **Keep it Short**: One concept per section
### Step 5: Update the Index (if needed)
If you added a new section or the code-spec status changed, update the category's `index.md`.
---
## Update Templates
### Mandatory Template for Infra/Cross-Layer Work
```markdown
## Scenario: <name>
### 1. Scope / Trigger
- Trigger: <why this requires code-spec depth>
### 2. Signatures
- Backend command/API/DB signature(s)
### 3. Contracts
- Request fields (name, type, constraints)
- Response fields (name, type, constraints)
- Environment keys (required/optional)
### 4. Validation & Error Matrix
- <condition> -> <error>
### 5. Good/Base/Bad Cases
- Good: ...
- Base: ...
- Bad: ...
### 6. Tests Required
- Unit/Integration/E2E with assertion points
### 7. Wrong vs Correct
#### Wrong
...
#### Correct
...
```
### Adding a Design Decision
```markdown
### Design Decision: [Decision Name]
**Context**: What problem were we solving?
**Options Considered**:
1. Option A - brief description
2. Option B - brief description
**Decision**: We chose Option X because...
**Example**:
\`\`\`typescript
// How it's implemented
code example
\`\`\`
**Extensibility**: How to extend this in the future...
```
### Adding a Project Convention
```markdown
### Convention: [Convention Name]
**What**: Brief description of the convention.
**Why**: Why we do it this way in this project.
**Example**:
\`\`\`typescript
// How to follow this convention
code example
\`\`\`
**Related**: Links to related conventions or specs.
```
### Adding a New Pattern
```markdown
### Pattern Name
**Problem**: What problem does this solve?
**Solution**: Brief description of the approach.
**Example**:
\`\`\`
// Good
code example
// Bad
code example
\`\`\`
**Why**: Explanation of why this works better.
```
### Adding a Forbidden Pattern
```markdown
### Don't: Pattern Name
**Problem**:
\`\`\`
// Don't do this
bad code example
\`\`\`
**Why it's bad**: Explanation of the issue.
**Instead**:
\`\`\`
// Do this instead
good code example
\`\`\`
```
### Adding a Common Mistake
```markdown
### Common Mistake: Description
**Symptom**: What goes wrong
**Cause**: Why this happens
**Fix**: How to correct it
**Prevention**: How to avoid it in the future
```
### Adding a Gotcha
```markdown
> **Warning**: Brief description of the non-obvious behavior.
>
> Details about when this happens and how to handle it.
```
---
## Interactive Mode
If you're unsure what to update, answer these prompts:
1. **What did you just finish?**
- [ ] Fixed a bug
- [ ] Implemented a feature
- [ ] Refactored code
- [ ] Had a discussion about approach
2. **What did you learn or decide?**
- Design decision (why X over Y)
- Project convention (how we do X)
- Non-obvious behavior (gotcha)
- Better approach (pattern)
3. **Would future AI/developers need to know this?**
- To understand how the code works → Yes, update spec
- To maintain or extend the feature → Yes, update spec
- To avoid repeating mistakes → Yes, update spec
- Purely one-off implementation detail → Maybe skip
4. **Which area does it relate to?**
- [ ] Backend code
- [ ] Frontend code
- [ ] Cross-layer data flow
- [ ] Code organization/reuse
- [ ] Quality/testing
---
## Quality Checklist
Before finishing your code-spec update:
- [ ] Is the content specific and actionable?
- [ ] Did you include a code example?
- [ ] Did you explain WHY, not just WHAT?
- [ ] Did you include executable signatures/contracts?
- [ ] Did you include validation and error matrix?
- [ ] Did you include Good/Base/Bad cases?
- [ ] Did you include required tests with assertion points?
- [ ] Is it in the right code-spec file?
- [ ] Does it duplicate existing content?
- [ ] Would a new team member understand it?
---
## Relationship to Other Commands
```
Development Flow:
Learn something → /trellis:update-spec → Knowledge captured
↑ ↓
/trellis:break-loop ←──────────────────── Future sessions benefit
(deep bug analysis)
```
- `/trellis:break-loop` - Analyzes bugs deeply, often reveals spec updates needed
- `/trellis:update-spec` - Actually makes the updates
- `/trellis:finish-work` - Reminds you to check if specs need updates
---
## Core Philosophy
> **Code-specs are living documents. Every debugging session, every "aha moment" is an opportunity to make the implementation contract clearer.**
The goal is **institutional memory**:
- What one person learns, everyone benefits from
- What AI learns in one session, persists to future sessions
- Mistakes become documented guardrails