From 3479ee583779b31ca376b7d0d2cd162ea79f9005 Mon Sep 17 00:00:00 2001 From: Leon <41661196+leonfox28@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:58:23 +0800 Subject: [PATCH] chore(trellis): update to 0.6.15 --- .agents/skills/trellis-before-dev/SKILL.md | 13 ++++- .agents/skills/trellis-check/SKILL.md | 17 ++++++- .agents/skills/trellis-meta/SKILL.md | 2 +- .../references/platform-files/agents.md | 2 +- .../platform-files/hooks-and-settings.md | 2 +- .../references/platform-files/platform-map.md | 7 +-- .codex/agents/trellis-check.toml | 8 ++-- .codex/agents/trellis-implement.toml | 2 + .codex/agents/trellis-research.toml | 2 + .codex/hooks/inject-subagent-context.py | 34 +++++++++++-- .trellis/.template-hashes.json | 36 +++++++------- .trellis/.version | 2 +- .trellis/scripts/common/active_task.py | 48 ++++++++++++++++--- .trellis/scripts/common/paths.py | 43 +++++++++++++++-- .trellis/scripts/common/task_context.py | 6 +-- .trellis/scripts/common/task_store.py | 44 ++++++++++++++++- .trellis/scripts/common/task_utils.py | 37 +++++++++----- .trellis/scripts/task.py | 18 +++++-- .trellis/workflow.md | 20 ++++---- 19 files changed, 264 insertions(+), 79 deletions(-) mode change 100644 => 100755 .codex/hooks/inject-subagent-context.py diff --git a/.agents/skills/trellis-before-dev/SKILL.md b/.agents/skills/trellis-before-dev/SKILL.md index 5a4b852..5f1c28a 100644 --- a/.agents/skills/trellis-before-dev/SKILL.md +++ b/.agents/skills/trellis-before-dev/SKILL.md @@ -35,6 +35,17 @@ Execute these steps: cat .trellis/spec/guides/index.md ``` -7. Understand the coding standards and patterns you need to follow, then proceed with your development plan. +7. **For a non-trivial task, state the change boundary before writing code.** Non-trivial means it touches more than one file, crosses a layer, changes a public interface, or edits code you did not just write. Write down: + - the smallest behavior gap between what happens now and what should happen + - where that behavior actually lives (not where it is easiest to intercept) + - which files you expect to change, and why each one is necessary + - what you are explicitly not doing in this task + - if a local refactor is needed, how you will show it did not change behavior + + A small, well-scoped change does not need this — do it directly. + + If the real scope turns out to be clearly larger than this, say so and why before continuing. Do not widen the change on your own. + +8. Understand the coding standards and patterns you need to follow, then proceed with your development plan. This step is **mandatory** before writing any code. diff --git a/.agents/skills/trellis-check/SKILL.md b/.agents/skills/trellis-check/SKILL.md index c695abd..7e70dc6 100644 --- a/.agents/skills/trellis-check/SKILL.md +++ b/.agents/skills/trellis-check/SKILL.md @@ -62,6 +62,14 @@ Run the project's lint, type-check, and test commands. Fix any failures before p > "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. +### Scope Discipline + +- [ ] Any tidying of code the task did not require? +- [ ] Any abstraction, config or extension point added for a case that does not exist yet? +- [ ] Any speculative fallback for a state that cannot occur? +- [ ] Any file changed that the acceptance criteria do not mention? +- [ ] Any workaround added at the caller instead of a fix where the behavior actually lives? + ## Step 5: Cross-Layer Dimensions (if applicable) Skip this step if your change is confined to a single layer. @@ -79,7 +87,7 @@ Skip this step if your change is confined to a single layer. ```bash grep -r "pattern" src/ ``` -- [ ] If 2+ places define same value → extracted to shared constant? +- [ ] If the same value repeats, does it represent one stable concept whose callers must change together? Extract only then — two literals that merely happen to match today should stay separate. - [ ] After batch modification, all occurrences updated? ### C. Import/Dependency (creating new files) @@ -95,4 +103,9 @@ Skip this step if your change is confined to a single layer. ## Step 6: Report and Fix -Report violations found and fix them directly. Re-run project checks after fixes. +Report every violation you find. Then: + +- Mechanical and local (lint nit, missing type, wrong import, dead branch, failing assertion) → fix in place, then re-run project checks. +- Design or judgment (naming a shared concept, moving a module boundary, changing a public interface, reassigning where behavior lives) → record the evidence and your recommendation, and stop. Do not rewrite it silently. + +If a fix would touch files outside the current task's scope, say so and stop instead of widening the change. diff --git a/.agents/skills/trellis-meta/SKILL.md b/.agents/skills/trellis-meta/SKILL.md index 0ffe593..91b23b6 100644 --- a/.agents/skills/trellis-meta/SKILL.md +++ b/.agents/skills/trellis-meta/SKILL.md @@ -12,7 +12,7 @@ Trellis v0.6 adds three architectural surfaces on top of the pre-v0.6 workflow / 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/`, `.kimi-code/`, 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//SKILL.md`; subagent skills carry `runAs: subagent` frontmatter. Kimi Code keeps workflow skills in the shared `.agents/skills/` layer and delivers commands plus agent prompts as `.kimi-code/skills//SKILL.md`. +- Platform directories: `.claude/`, `.codex/`, `.cursor/`, `.opencode/`, `.kiro/`, `.gemini/`, `.qoder/`, `.codebuddy/`, `.github/`, `.factory/`, `.pi/`, `.reasonix/`, `.kilocode/`, `.agent/`, `.devin/`, `.kimi-code/`, 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//SKILL.md`; subagent skills carry `runAs: subagent` frontmatter. Kimi Code keeps workflow skills in the shared `.agents/skills/` layer, delivers commands plus agent prompts as `.kimi-code/skills//SKILL.md`, and installs the same agent prompts as custom sub-agents under `.kimi-code/agents/.md`. - Shared skill layer: `.agents/skills/`. - User-owned channel store outside the project tree: `~/.trellis/channels///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). diff --git a/.agents/skills/trellis-meta/references/platform-files/agents.md b/.agents/skills/trellis-meta/references/platform-files/agents.md index 26f472a..7e5e47f 100644 --- a/.agents/skills/trellis-meta/references/platform-files/agents.md +++ b/.agents/skills/trellis-meta/references/platform-files/agents.md @@ -34,7 +34,7 @@ Agent files should not become generic chat prompts. They should define input sou | Pi Agent | `.pi/agents/trellis-*.md` | | Reasonix | `.reasonix/skills/trellis-*/SKILL.md` (subagent frontmatter) | | ZCode | `.zcode/agents/trellis-*.md` | -| Kimi Code | `.kimi-code/skills/trellis-*/SKILL.md` (agent prompts as skills, dispatched to the built-in `coder`; research needs its file-editing tools to persist findings) | +| Kimi Code | `.kimi-code/agents/trellis-*.md` (custom sub-agents; the same prompts also ship as `.kimi-code/skills/trellis-*/SKILL.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. diff --git a/.agents/skills/trellis-meta/references/platform-files/hooks-and-settings.md b/.agents/skills/trellis-meta/references/platform-files/hooks-and-settings.md index a2ff389..85f7f11 100644 --- a/.agents/skills/trellis-meta/references/platform-files/hooks-and-settings.md +++ b/.agents/skills/trellis-meta/references/platform-files/hooks-and-settings.md @@ -29,7 +29,7 @@ Common files: | Pi Agent | `.pi/settings.json`, `.pi/extensions/trellis/` | | Trae IDE | `.trae/hooks.json` | -Reasonix is a pull-based platform whose agent files contain prelude instructions to read context after startup. ZCode uses `.zcode/config.json` with shared hooks, including PreToolUse for sub-agent prompt injection. Kimi Code is likewise pull-based and has no project-level settings/hooks file Trellis writes (hooks live only in the user-level `~/.kimi-code/config.toml`), so its agent prompts ship as skills with the same prelude. +Reasonix is a pull-based platform whose agent files contain prelude instructions to read context after startup. ZCode uses `.zcode/config.json` with shared hooks, including PreToolUse for sub-agent prompt injection. Kimi Code is likewise pull-based and has no project-level settings/hooks file Trellis writes (hooks live only in the user-level `~/.kimi-code/config.toml`), so its agent prompts ship as skills and `.kimi-code/agents/` sub-agent definitions with the same prelude. Whether these files exist in a project depends on which `trellis init --` flags the user ran. diff --git a/.agents/skills/trellis-meta/references/platform-files/platform-map.md b/.agents/skills/trellis-meta/references/platform-files/platform-map.md index 73b113a..f66e131 100644 --- a/.agents/skills/trellis-meta/references/platform-files/platform-map.md +++ b/.agents/skills/trellis-meta/references/platform-files/platform-map.md @@ -19,12 +19,13 @@ This page lists common Trellis file locations in a user project by platform. Whe | 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 | +| DeepSeek Harness (dsh) | `--dsh` | `.dsh/` | `.agents/skills/` (shared) + `.dsh/skills/` (entry skills) | None (workflow skills run implement/check inline) | None (class-2 pull-based; no project hooks/settings) | | Pi Agent | `--pi` | `.pi/` | `.agents/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/` | `.zcode/skills/` | `.zcode/agents/` | `.zcode/hooks/` + `.zcode/config.json` (SessionStart + UserPromptSubmit + PreToolUse Agent/Task); sub-agents use hook-injected context | | Grok Build | `--grok` | `.grok/` | `.grok/skills/` | `.grok/agents/` | pull-based prelude (no hooks; flat `.grok/commands/trellis-*.md`) | -| Kimi Code | `--kimi` | `.kimi-code/` | `.agents/skills/` (shared) + `.kimi-code/skills/` | None — agent prompts are skills under `.kimi-code/skills/` and dispatch to the built-in `coder` | None (pull-based prelude; no project hooks/settings) | +| Kimi Code | `--kimi` | `.kimi-code/` | `.agents/skills/` (shared) + `.kimi-code/skills/` | `.kimi-code/agents/` (custom sub-agents; same prompts also ship as skills) | None (pull-based prelude; no project hooks/settings) | | Snow CLI | `--snow` | `.snow/` | `.snow/skills/` | `.snow/agents/` (auto-discovered; primary path) | class-1: auto inject + project agents + `beforeSubAgentStart` (`.snow/hooks/` `session`/`user`/`subagent` modes -> `additionalContext` JSON); no legacy sub-agent JSON; commands `.snow/commands/trellis-*.json` | ## Capability Groups @@ -48,7 +49,7 @@ These platforms usually have `trellis-research`, `trellis-implement`, and `trell - Reasonix (delivered as skills with `runAs: subagent` under `.reasonix/skills/`, not as a separate `agents/` directory) - ZCode - Grok Build (`.grok/agents/`; dispatch via `spawn_subagent` with `subagent_type`) -- Kimi Code (delivered as skills under `.kimi-code/skills/`; dispatched to the built-in `coder`, including research because it must persist files) +- Kimi Code (`.kimi-code/agents/`; the same prompts are also delivered as skills under `.kimi-code/skills/`) - Snow CLI (`.snow/agents/`; auto-discovered project agents + class-1 hooks) When changing implementation/check/research behavior, look for the corresponding platform agent files first. @@ -73,7 +74,7 @@ When changing behavior, inspect workflows and skills first. Do not assume Trelli ### Shared `.agents/skills/` -Codex, Gemini CLI, Pi Agent, and Kimi Code write 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. ZCode keeps Trellis-managed skills under `.zcode/skills/`. +Codex, Gemini CLI, Pi Agent, Kimi Code, and DeepSeek Harness (dsh) write 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. ZCode keeps Trellis-managed skills under `.zcode/skills/`. ## Decision Rules When Modifying Platform Files diff --git a/.codex/agents/trellis-check.toml b/.codex/agents/trellis-check.toml index df89741..5b3c6f7 100644 --- a/.codex/agents/trellis-check.toml +++ b/.codex/agents/trellis-check.toml @@ -3,6 +3,8 @@ description = "Workspace-write Trellis reviewer that self-fixes spec drift, lint sandbox_mode = "workspace-write" model = "gpt-5.6-sol" model_reasoning_effort = "xhigh" +# model = "gpt-5.6-terra" +# model_reasoning_effort = "high" developer_instructions = """ You are running as the `trellis-check` sub-agent. The main session has dispatched you to review and self-fix. @@ -23,7 +25,7 @@ Trellis Context Loading Protocol: - If there is no saved-output notice and the marker is absent, use the active-task fallback below. - For the fallback, find `Active task: ` in your dispatch prompt. Read `/check.jsonl`, each file listed there, `/prd.md`, `/design.md` if present, and `/implement.md` if present before checking. If the dispatch prompt has no active-task path, ask the main session; do not guess or use another session's task. -Your job is to review code changes against specs AND fix issues directly — not just report them. You have write access; use it. +Your job is to review code changes against specs and fix what is safe to fix. Mechanical, local issues (lint, types, imports, dead branches, failing assertions) — fix them in place. Design or judgment calls, public interfaces, module boundaries, or anything outside the current task's scope — report the evidence and your recommendation, and leave the code alone. Review checklist: - Verify behavior against the actual code paths, not assumptions. @@ -33,7 +35,7 @@ Review checklist: - Run lint and type-check; fix any failures. - Prefer concrete findings over speculative warnings. -When you find an issue: +When you find a mechanical, local issue: 1. Fix it directly using edit/write tools. 2. Re-run lint and type-check until green. 3. Record what you changed and why. @@ -45,7 +47,7 @@ Output format: - Fix: ## Findings (not fixed) -Only list issues you could not self-fix (e.g. missing product decision, out-of-scope). Explain why. +List every issue you did not fix — the ones you could not (missing product decision) and the ones you chose not to (design or judgment call, public interface, module boundary, outside this task's scope). Explain which and why. ## Verification - Lint: pass/fail diff --git a/.codex/agents/trellis-implement.toml b/.codex/agents/trellis-implement.toml index 93f80a8..2b2a524 100644 --- a/.codex/agents/trellis-implement.toml +++ b/.codex/agents/trellis-implement.toml @@ -3,6 +3,8 @@ description = "Workspace-write Trellis implementer that follows specs and keeps sandbox_mode = "workspace-write" model = "gpt-5.6-sol" model_reasoning_effort = "high" +# model = "gpt-5.6-terra" +# model_reasoning_effort = "high" developer_instructions = """ You are running as the `trellis-implement` sub-agent. The main session has dispatched you to do the work. diff --git a/.codex/agents/trellis-research.toml b/.codex/agents/trellis-research.toml index a84d63c..3efd253 100644 --- a/.codex/agents/trellis-research.toml +++ b/.codex/agents/trellis-research.toml @@ -3,6 +3,8 @@ description = "Trellis researcher for specs, code patterns, and affected files. sandbox_mode = "workspace-write" model = "gpt-5.6-terra" model_reasoning_effort = "high" +# model = "gpt-5.6-terra" +# model_reasoning_effort = "high" developer_instructions = """ You are the Trellis researcher agent. diff --git a/.codex/hooks/inject-subagent-context.py b/.codex/hooks/inject-subagent-context.py old mode 100644 new mode 100755 index 3b74fc7..1d2dae6 --- a/.codex/hooks/inject-subagent-context.py +++ b/.codex/hooks/inject-subagent-context.py @@ -92,9 +92,16 @@ def _detect_platform(input_data: dict) -> str | None: return "codex" if isinstance(input_data.get("cursor_version"), str): return "cursor" + # CLAUDE_PROJECT_DIR is a compatibility alias that several hosts set + # alongside their own variable — CodeBuddy, ZCode and Trae all do. It must + # therefore be checked LAST, or every one of them is detected as claude and + # the context key becomes `claude_`. That key does not + # match the session file `task.py start` wrote under the host's real name, + # so the sub-agent starts with no task context while the pointer exists on + # disk. Same fix as inject-workflow-state.py and session-start.py; this + # third copy was missed when those two were corrected. env_map = { "ZCODE_PROJECT_DIR": "zcode", - "CLAUDE_PROJECT_DIR": "claude", "CURSOR_PROJECT_DIR": "cursor", "CODEBUDDY_PROJECT_DIR": "codebuddy", "FACTORY_PROJECT_DIR": "droid", @@ -102,6 +109,9 @@ def _detect_platform(input_data: dict) -> str | None: "QODER_PROJECT_DIR": "qoder", "KIRO_PROJECT_DIR": "kiro", "COPILOT_PROJECT_DIR": "copilot", + "TRAE_PROJECT_DIR": "trae", + # Last: the shared alias, only meaningful once no vendor key matched. + "CLAUDE_PROJECT_DIR": "claude", } for env_name, platform in env_map.items(): if os.environ.get(env_name): @@ -986,6 +996,8 @@ def _extract_subagent_type(tool_input: dict) -> str: "subagentType", "subagent_type_name", "subagentTypeName", + "subagent_name", + "subagentName", "agent_type", "agentType", "name", @@ -1001,7 +1013,8 @@ def _parse_hook_input(input_data: dict) -> tuple[str, str, dict]: Returns (subagent_type, original_prompt, tool_input). Handles: - - Claude Code / Qoder / CodeBuddy / Droid: tool_name=Task|Agent, tool_input.subagent_type + - Claude Code / Qoder / Droid: tool_name=Task|Agent, tool_input.subagent_type + - CodeBuddy: tool_name=task (IDE) or Task (CLI), tool_input.subagent_name - Cursor: tool_name=Task|Subagent, tool_input.subagent_type - Copilot CLI: toolName=task (camelCase key, lowercase value) - ZCode: toolName=Agent, toolInput/tool_input.subagent_type @@ -1080,8 +1093,21 @@ def main(): 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) + # Contain the pointer before reading anything through it. `task.py` now + # refuses to store a ref that leaves the repo, but a session file + # written before that fix can still hold one, and `trellis update` + # does not rewrite session files — so a poisoned pointer outlives the + # upgrade that closed the writer. This is the last hop before the + # task's prd.md/design.md reach the model prompt, so it checks again. + try: + root_real = os.path.realpath(repo_root) + task_dir_full = os.path.realpath(os.path.join(repo_root, task_dir)) + # ValueError on Windows when the two sit on different drives; that + # is outside the repo by definition, so it fails closed below. + if os.path.commonpath([root_real, task_dir_full]) != root_real: + sys.exit(0) + except (OSError, ValueError): + sys.exit(0) if not os.path.exists(task_dir_full): sys.exit(0) diff --git a/.trellis/.template-hashes.json b/.trellis/.template-hashes.json index 59f503e..583c349 100644 --- a/.trellis/.template-hashes.json +++ b/.trellis/.template-hashes.json @@ -4,10 +4,10 @@ ".agents/skills/trellis-continue/SKILL.md": "7723ccf49fbf19d8f086cacc7a080bd8be8db6fc70a32908b80f68efa318d7bf", ".agents/skills/trellis-finish-work/SKILL.md": "161060fbcd44f787440d3a5c297a9f5223ea7774bb3021a50e376875a9ac5b2d", ".agents/skills/trellis-start/SKILL.md": "79a5ba7a2aff3c72e06d7f4cd6942dc4f4f4092dd40f9c8e94f1838024a81e4d", - ".agents/skills/trellis-before-dev/SKILL.md": "859894f2e8258cbfa142d710363433761c54984c69f7ed7bd44512fb4eb165cd", + ".agents/skills/trellis-before-dev/SKILL.md": "00c9d1c83bc318e91b27a019bc954c2957cfad907ff20d854b596f16cad2c0f3", ".agents/skills/trellis-brainstorm/SKILL.md": "a0f226ddcb8a3e846acd2a35d121996e9ca55165ce76202095d0b65e2b48a5e8", ".agents/skills/trellis-break-loop/SKILL.md": "f5a93699832f29dee443b53c135a7459519b371f689af191d15c29e8ee5c7bde", - ".agents/skills/trellis-check/SKILL.md": "b21ff04b7680ebacb8c5ecbc48a22d627eb13e2b47fceb78c8ced0b43b60b282", + ".agents/skills/trellis-check/SKILL.md": "dfb0600e95c19c7a83200465b6a92a5514ec9778ba4083efa881bed541cc74a8", ".agents/skills/trellis-update-spec/SKILL.md": "003ce08a3404aeb50998029392c4d4e57b626edf526d3ebd585032bb92dcbb96", ".agents/skills/trellis-channel/references/command-reference.md": "c4df3d940d89814310bfdaded20e84dd1d6b96786f6772688de7a123338d3c8e", ".agents/skills/trellis-channel/references/forum.md": "407db39e8ebde47cea8637e3069bdb3d76ba42b43ed1d08ba97a56f26dbcfcb4", @@ -33,12 +33,12 @@ ".agents/skills/trellis-meta/references/local-architecture/task-system.md": "2b561d49c390f7d0db5391912946133be4bf73189231e2b8cc9afa1c5ac6165a", ".agents/skills/trellis-meta/references/local-architecture/workflow.md": "cfcdc6e4468a5d9c816e929fcca01640cd41cfdaaa4824118b40a8e460c927b6", ".agents/skills/trellis-meta/references/local-architecture/workspace-memory.md": "e6427b46aba744563c2444b30df4043cd856561b7709ec2dece26095416421fd", - ".agents/skills/trellis-meta/references/platform-files/agents.md": "9f41349b78f7ae64698a38490a317882561f3805b26a79bda587a13d03fda245", - ".agents/skills/trellis-meta/references/platform-files/hooks-and-settings.md": "154af08f7ee8afe7a704968ec0b9fc21e905b7cfb03871c9dbafcdd6cc654319", + ".agents/skills/trellis-meta/references/platform-files/agents.md": "b806b1f0de6dfc74720014aee15ede767a58925b2b3b964f2e557ed39a65bbc0", + ".agents/skills/trellis-meta/references/platform-files/hooks-and-settings.md": "91e9855e418673bcd7770411e24e28390738b123532eef000beba0a29cf154e1", ".agents/skills/trellis-meta/references/platform-files/overview.md": "1aec9087ccedd56a213af877b5db474131aa4b4789b6ac0e8da73b058aa43bd4", - ".agents/skills/trellis-meta/references/platform-files/platform-map.md": "9e476e500f10b2a1a05278dd700837deb731a77e3f3993e90f8102528a9bdcca", + ".agents/skills/trellis-meta/references/platform-files/platform-map.md": "3c0d4546461c06d1aaf006276e34e250e0ca7bbb5b853fb45c58114cebca4042", ".agents/skills/trellis-meta/references/platform-files/skills-and-commands.md": "e39831d860bd27a04e7757f7bd941ab83b3c5b11ad4460cec03975b655f26cc3", - ".agents/skills/trellis-meta/SKILL.md": "208e92ace3b8d979d8158b3bd0169185f508f2e1424fcd78fcd03cbcc39516a1", + ".agents/skills/trellis-meta/SKILL.md": "ee7bcfd0023def62688339c4b63c50f2fbeea12a34eb89e8bb63a992b58cb168", ".agents/skills/trellis-session-insight/references/cli-quick-reference.md": "c520353fe3fc00b9702ed4f780647c8fbd6d342e66527a03647f533a9fe09779", ".agents/skills/trellis-session-insight/references/triggering-patterns.md": "121ecd23be83d1567e8ce15c366a81073d7a2b1d3ad616fce235c07ca1f1cc20", ".agents/skills/trellis-session-insight/SKILL.md": "d20f1d20c6946e26ba6f848b9a016d9a16c803d87501462e5b11a2ced6f56643", @@ -47,22 +47,22 @@ ".agents/skills/trellis-spec-bootstrap/references/spec-task-planning.md": "ef493d028c3b0807a8a534bb71fb92a68129f273db763ad27ceb464a522e799d", ".agents/skills/trellis-spec-bootstrap/references/spec-writing.md": "e9800fe9ed4a4cd87062ea1829cf2caa8d170ec15e141678a6a30e74c497f47d", ".agents/skills/trellis-spec-bootstrap/SKILL.md": "97bfa68c06cebb558eb4464bc1b81f7d2d56040d75baa8de1ee5ad90cca0196a", - ".codex/agents/trellis-check.toml": "79070c63fa404fc53061cca5194bf66db839132b67a57b9c8d6a295037ba7308", - ".codex/agents/trellis-implement.toml": "388fb8f39797e0ee6cf4db447c859c79b4ac15f531f7e1e3c68c1d1d71a1c188", - ".codex/agents/trellis-research.toml": "4435ce73197ba1d29d40359a3279b6423f7e4f559a449f934c016808090066c4", + ".codex/agents/trellis-check.toml": "7ca59196a218c22fbac1765ac1e7b58b44fb45c44713694d17d9bc1f6728b116", + ".codex/agents/trellis-implement.toml": "7abe4a58bf11ff5ad731c26c65b34f5882fec127fc4d6bef4e8dba19ff6c2e1d", + ".codex/agents/trellis-research.toml": "526c8354c6e9c0fb7253a294db69ddb23ed0822c5374b106ceea7c2b494b28e3", ".codex/hooks/session-start.py": "14de3be1cf6eb9c9feba348d8998b407f3837d6c0756b74210c9200543440677", - ".codex/hooks/inject-subagent-context.py": "c2dff614dc1ca5cb9e225752d1c49291e277ef109e3640a065b835b61016bec6", + ".codex/hooks/inject-subagent-context.py": "7c2c5640445ea0d98ce6a13126ea105ec4b02ca661ef4a7711240b595efefbbf", ".codex/hooks/inject-workflow-state.py": "89cef2b197dd61a731b946114207d2601b240058941ccf28622ee211d9c62eb3", ".codex/hooks.json": "85a58ba7cdf1e19e7f75ddcc64e5680180c487ca266a74bd5005f31abeee2e02", ".codex/config.toml": "9f2d20e28f0bc9c886312eca3ad3bba41533ef4615aaaafe25e98152302267bb", "AGENTS.md": "6cacfe99748b435d0660c2463c697bc323d53798aecf3492283ca8eac1b29682", ".trellis/agents/check.md": "edb4f57361407249a53bf5998ebf91c40d2b969e826a2c5e1b4e813a08bcb175", ".trellis/agents/implement.md": "66e25ad046c94869442834bc3cdfbd5a9a7412d3ff54561d64d2886552c27e87", - ".trellis/config.yaml": "5aefbeeb08f5917957fca171b8fda2988f5423c42d17579adce87fcff6e03092", + ".trellis/config.yaml": "a966e6d374e9e6ff283cf761ccd99631323ee1754856cef51ad154ca0afb9dfa", ".trellis/scripts/__init__.py": "1242be5b972094c2e141aecbe81a4efd478f6534e3d5e28306374e6a18fcf46c", ".trellis/scripts/add_session.py": "876dad478edf70db59acccaae9cb4db646a155681f730bd99af48de72ddc9881", ".trellis/scripts/common/__init__.py": "3d5e9347141f0296319a5beb29d69ae714c5a474b9078caeb3edd7c5f6562e22", - ".trellis/scripts/common/active_task.py": "28a81f8828538fb70a15c88edd90eda9d685adbde8862f67f630bce5b27d9832", + ".trellis/scripts/common/active_task.py": "bd15c5ee7810814dad915d898889323b3ea97fde92777b053b7ae734d56768bc", ".trellis/scripts/common/cli_adapter.py": "5d6bd9d6f5c631e7e792db7dd343351317f9643bc87b73a9a98abd51cefb4307", ".trellis/scripts/common/config.py": "8d2e5f8ccfcd5f622cd2af002aa761f3d3ffcc653182fefb2268afd102e77bca", ".trellis/scripts/common/developer.py": "f5f833123abe68890171b4da825a324216d24913f6b5ad9245afc556424ffd7b", @@ -71,13 +71,13 @@ ".trellis/scripts/common/io.py": "75648caae03d5b1107d7aeccaa785d133b25762266e54a520d90ca8c76b43bdb", ".trellis/scripts/common/log.py": "471df6895cfac80f995edebbf9974f6b7440634b7a688f28b8331c868bc0f3cf", ".trellis/scripts/common/packages_context.py": "efe158d7c99c2268851d0216fbb08de22836e418a8dbeb73575b8cc249eed7b7", - ".trellis/scripts/common/paths.py": "05898ef136cc7c4d861b05fbf2b16d53ddd3e6f311a231d4fcfcb81bde7c45ee", + ".trellis/scripts/common/paths.py": "c26b75bc211b290e7c21ec96fb1db36f282b3ecbb7d2c81475d258a48ea55fb8", ".trellis/scripts/common/safe_commit.py": "baa5c82324eb62154374ec63394ecdc8609bb37d93892e3bcb88f452bb7d6446", ".trellis/scripts/common/session_context.py": "3379ef1766e4e5ca77cbb7c040dbba3883fcca2548580299e3b38dbf22f4f7d5", - ".trellis/scripts/common/task_context.py": "be5fa407f99c2400075194dbaa8b6ec996ce8aac2122d191bb9cb11e479a9476", + ".trellis/scripts/common/task_context.py": "6fc3abb9e483043bc8cc3477ae48e5503399402009fbfc58db80809472c5690b", ".trellis/scripts/common/task_queue.py": "0be61f713462b1fe4574927c82fc4704e678afe72dcb9813543aedf2f9e9e0c5", - ".trellis/scripts/common/task_store.py": "e3c2fbf8b79b591e39fc3c9f4e2f3ee0c840c8201c94a16709ec743fa45037f6", - ".trellis/scripts/common/task_utils.py": "90c0a6d50bad502c3f01cb24c1ccfeb0eece5e2c69efaff8d65eb827fba43871", + ".trellis/scripts/common/task_store.py": "9b05a40113439e2841271f2fc1e616fbef0b6929e7c064dd41350658e4ed203e", + ".trellis/scripts/common/task_utils.py": "07a599c028b2f7aa4014f56702374aade98c2d1a6ea87b314098e5e86cacf7fe", ".trellis/scripts/common/tasks.py": "4436a8b0b53c270a35989e26d9dbd92669408c6562d88c02083a404562da85fe", ".trellis/scripts/common/trellis_config.py": "e282e897183e3ec2f4e6e56349431946e5f98c1c31d3eca4de7fc44e1383a7bf", ".trellis/scripts/common/types.py": "9962081cc2608fb9d1deb32c6880e336f62cdca6b338e7ae813304701e155ee9", @@ -86,7 +86,7 @@ ".trellis/scripts/get_developer.py": "84c27076323c3e0f2c9c8ed16e8aa865e225d902a187c37e20ee1a46e7142d8f", ".trellis/scripts/hooks/linear_sync.py": "e09cc4ce4699aada908808718698f33f705a3edf55c4dcf8f777ad892f80ca79", ".trellis/scripts/init_developer.py": "f9e6c0d882406e81c8cd6b1c5abb204b0befc0069ff89cf650cd536a80f8c60e", - ".trellis/scripts/task.py": "e0ffed9f14994069f0c992141e3ec168524be5af32e3681e6ea30ba0a5da4bc4", - ".trellis/workflow.md": "e2c5ab7004ff83a5a804b50df81746aa1d558dd4480463287622605f86a82a76" + ".trellis/scripts/task.py": "152d7298db25b86756583faaba763eacd8611c0070e8f1c176b8257d687102b9", + ".trellis/workflow.md": "c694bb7901d48f224ccad940178cff81e28dda46e6e5b24bad67c83970d67808" } } \ No newline at end of file diff --git a/.trellis/.version b/.trellis/.version index 5c858bd..7e3c84c 100644 --- a/.trellis/.version +++ b/.trellis/.version @@ -1 +1 @@ -0.6.14 \ No newline at end of file +0.6.15 \ No newline at end of file diff --git a/.trellis/scripts/common/active_task.py b/.trellis/scripts/common/active_task.py index 702b864..97a7784 100755 --- a/.trellis/scripts/common/active_task.py +++ b/.trellis/scripts/common/active_task.py @@ -55,6 +55,7 @@ "kimi", "zcode", "snow", + "dsh", } # Every name below records how it was checked. Do NOT add a name by analogy @@ -64,6 +65,15 @@ # only "evidence" behind them. A platform with no verified name belongs in no # table; it resolves through TRELLIS_CONTEXT_ID or its hook/plugin bridge. _ENV_SESSION_KEYS: tuple[tuple[str, tuple[str, ...]], ...] = ( + # REAL (reported 2026-08-13 against DSH 0.1.0-rc.6 by @SajoLuo, from a live + # run: DSH exports DSH_SESSION_ID plus DSH_SHELL=1 into its managed shell). + # MUST STAY FIRST. A DSH session can inherit an outer host's identity — a + # DSH launched from Codex still carries CODEX_THREAD_ID — and the untargeted + # lookup below walks this table in order, so any earlier entry would claim + # the session and write a foreign `codex_` pointer for DSH work. + # DSH_SESSION_ID is the only name here no other vendor sets, so first place + # is safe: it cannot mis-claim a non-DSH session. + ("dsh", ("DSH_SESSION_ID",)), # REAL, undocumented (verified 2026-08-05 in a live Claude Code 2.1.221 bash # child; absent from code.claude.com/docs/en/env-vars). CLAUDE_SESSION_ID # was removed here — verified absent from that same live environment. @@ -189,19 +199,39 @@ def normalize_task_ref(task_ref: str) -> str: def resolve_task_ref(task_ref: str, repo_root: Path) -> Path | None: - """Resolve a task ref to an absolute task directory.""" + """Resolve a task ref to an absolute task directory inside the repo. + + Mirrors `paths.resolve_task_ref` (same containment check). Duplicated + rather than imported because this module is loaded standalone — hooks add + it to `sys.path` directly — so it stays zero-relative-import on purpose. + """ normalized = normalize_task_ref(task_ref) if not normalized: return None path_obj = Path(normalized) if path_obj.is_absolute(): - return path_obj + candidate = path_obj + elif normalized.startswith(f"{DIR_WORKFLOW}/"): + candidate = repo_root / path_obj + else: + candidate = repo_root / DIR_WORKFLOW / DIR_TASKS / path_obj + + # Both sides are resolved because repo_root itself may sit behind a + # symlink (/tmp on macOS does), and resolve() is what collapses `..` + # instead of leaving it for a lexical relative_to() to wave through. + try: + resolved = candidate.resolve() + root = repo_root.resolve() + except OSError: + return None - if normalized.startswith(f"{DIR_WORKFLOW}/"): - return repo_root / path_obj + try: + resolved.relative_to(root) + except ValueError: + return None - return repo_root / DIR_WORKFLOW / DIR_TASKS / path_obj + return resolved def _runtime_sessions_dir(repo_root: Path) -> Path: @@ -537,9 +567,13 @@ def _canonical_task_ref(task_path: str, repo_root: Path) -> str | None: if full_path is None or not full_path.is_dir(): return None try: - return full_path.relative_to(repo_root).as_posix() + return full_path.relative_to(repo_root.resolve()).as_posix() except ValueError: - return str(full_path) + # resolve_task_ref already refused everything outside the repo, so this + # is unreachable. Refuse rather than fall back to an absolute path — + # that fallback is how an out-of-repo ref used to reach the session + # pointer and get replayed on every later turn. + return None def _active_from_ref( diff --git a/.trellis/scripts/common/paths.py b/.trellis/scripts/common/paths.py index 1c5a58e..814e8e8 100755 --- a/.trellis/scripts/common/paths.py +++ b/.trellis/scripts/common/paths.py @@ -233,7 +233,25 @@ def normalize_task_ref(task_ref: str) -> str: def resolve_task_ref(task_ref: str, repo_root: Path | None = None) -> Path | None: - """Resolve a task ref to an absolute task directory path.""" + """Resolve a task ref to an absolute task directory path inside the repo. + + Returns None when the ref resolves outside `repo_root`. Every reader of the + active task — `task.py`, the shared hooks, the platform extensions — comes + through here, so containment is enforced at this one point rather than at + each call site. + + It matters because a ref is not always something the user typed. It round + trips through the session pointer under `.trellis/.runtime/sessions/`, and + `..` segments used to survive that trip intact: `_canonical_task_ref` + compares lexically, and a lexical `relative_to` accepts + `/.trellis/tasks/../../../elsewhere` because the string does start + with the root. The ref was then stored verbatim and replayed on every later + turn, so `task.py start .trellis/tasks/../../../elsewhere` both rewrote that + directory's `task.json` and fed its files to the model. + + Resolving here also normalises the path, so callers get a ref without `..` + to store. + """ if repo_root is None: repo_root = get_repo_root() @@ -243,12 +261,27 @@ def resolve_task_ref(task_ref: str, repo_root: Path | None = None) -> Path | Non path_obj = Path(normalized) if path_obj.is_absolute(): - return path_obj + candidate = path_obj + elif normalized.startswith(f"{DIR_WORKFLOW}/"): + candidate = repo_root / path_obj + else: + candidate = repo_root / DIR_WORKFLOW / DIR_TASKS / path_obj - if normalized.startswith(f"{DIR_WORKFLOW}/"): - return repo_root / path_obj + # resolve() collapses `..` and follows symlinks, so a task directory that + # links outside the repo is refused too. Both sides are resolved because + # repo_root itself may sit behind a symlink (/tmp on macOS does). + try: + resolved = candidate.resolve() + root = repo_root.resolve() + except OSError: + return None + + try: + resolved.relative_to(root) + except ValueError: + return None - return repo_root / DIR_WORKFLOW / DIR_TASKS / path_obj + return resolved def get_current_task( diff --git a/.trellis/scripts/common/task_context.py b/.trellis/scripts/common/task_context.py index 14994e9..5d10df7 100755 --- a/.trellis/scripts/common/task_context.py +++ b/.trellis/scripts/common/task_context.py @@ -65,7 +65,7 @@ def cmd_add_context(args: argparse.Namespace) -> int: path = args.path reason = args.reason or "Added manually" - if not target_dir.is_dir(): + if not target_dir or not target_dir.is_dir(): print(colored(f"Error: Directory not found: {target_dir}", Colors.RED)) return 1 @@ -115,7 +115,7 @@ def cmd_validate(args: argparse.Namespace) -> int: repo_root = get_repo_root() target_dir = resolve_task_dir(args.dir, repo_root) - if not target_dir.is_dir(): + if not target_dir or not target_dir.is_dir(): print(colored("Error: task directory required", Colors.RED)) return 1 @@ -323,7 +323,7 @@ def cmd_list_context(args: argparse.Namespace) -> int: repo_root = get_repo_root() target_dir = resolve_task_dir(args.dir, repo_root) - if not target_dir.is_dir(): + if not target_dir or not target_dir.is_dir(): print(colored("Error: task directory required", Colors.RED)) return 1 diff --git a/.trellis/scripts/common/task_store.py b/.trellis/scripts/common/task_store.py index 2cd9789..0cc8cbe 100755 --- a/.trellis/scripts/common/task_store.py +++ b/.trellis/scripts/common/task_store.py @@ -410,10 +410,10 @@ def cmd_create(args: argparse.Namespace) -> int: # Handle --parent: establish bidirectional link if args.parent: parent_dir = resolve_task_dir(args.parent, repo_root) - parent_json_path = parent_dir / FILE_TASK_JSON - if not parent_json_path.is_file(): + if not parent_dir or not (parent_dir / FILE_TASK_JSON).is_file(): print(colored(f"Warning: Parent task.json not found: {args.parent}", Colors.YELLOW), file=sys.stderr) else: + parent_json_path = parent_dir / FILE_TASK_JSON parent_data = read_json(parent_json_path) if parent_data: # Add child to parent's children list @@ -723,6 +723,14 @@ def cmd_add_subtask(args: argparse.Namespace) -> int: parent_dir = resolve_task_dir(args.parent_dir, repo_root) child_dir = resolve_task_dir(args.child_dir, repo_root) + if not parent_dir: + print(colored(f"Error: Parent task.json not found: {args.parent_dir}", Colors.RED), file=sys.stderr) + return 1 + + if not child_dir: + print(colored(f"Error: Child task.json not found: {args.child_dir}", Colors.RED), file=sys.stderr) + return 1 + parent_json_path = parent_dir / FILE_TASK_JSON child_json_path = child_dir / FILE_TASK_JSON @@ -776,6 +784,14 @@ def cmd_remove_subtask(args: argparse.Namespace) -> int: parent_dir = resolve_task_dir(args.parent_dir, repo_root) child_dir = resolve_task_dir(args.child_dir, repo_root) + if not parent_dir: + print(colored(f"Error: Parent task.json not found: {args.parent_dir}", Colors.RED), file=sys.stderr) + return 1 + + if not child_dir: + print(colored(f"Error: Child task.json not found: {args.child_dir}", Colors.RED), file=sys.stderr) + return 1 + parent_json_path = parent_dir / FILE_TASK_JSON child_json_path = child_dir / FILE_TASK_JSON @@ -827,6 +843,12 @@ def cmd_set_branch(args: argparse.Namespace) -> int: print("Usage: python3 task.py set-branch ") return 1 + if not target_dir: + # target_dir is None here, so it must not appear in the message. This + # is also the branch a ref pointing outside the repo lands in. + print(colored(f"Error: Task not found: {args.dir}", Colors.RED)) + return 1 + task_json = target_dir / FILE_TASK_JSON if not task_json.is_file(): print(colored(f"Error: task.json not found at {target_dir}", Colors.RED)) @@ -861,6 +883,12 @@ def cmd_set_base_branch(args: argparse.Namespace) -> int: print("This sets the target branch for PR (the branch your feature will merge into).") return 1 + if not target_dir: + # target_dir is None here, so it must not appear in the message. This + # is also the branch a ref pointing outside the repo lands in. + print(colored(f"Error: Task not found: {args.dir}", Colors.RED)) + return 1 + task_json = target_dir / FILE_TASK_JSON if not task_json.is_file(): print(colored(f"Error: task.json not found at {target_dir}", Colors.RED)) @@ -893,6 +921,12 @@ def cmd_set_scope(args: argparse.Namespace) -> int: print("Usage: python3 task.py set-scope ") return 1 + if not target_dir: + # target_dir is None here, so it must not appear in the message. This + # is also the branch a ref pointing outside the repo lands in. + print(colored(f"Error: Task not found: {args.dir}", Colors.RED)) + return 1 + task_json = target_dir / FILE_TASK_JSON if not task_json.is_file(): print(colored(f"Error: task.json not found at {target_dir}", Colors.RED)) @@ -925,6 +959,12 @@ def cmd_set_meta(args: argparse.Namespace) -> int: print("Usage: python3 task.py set-meta ") return 1 + if not target_dir: + # target_dir is None here, so it must not appear in the message. This + # is also the branch a ref pointing outside the repo lands in. + print(colored(f"Error: Task not found: {args.dir}", Colors.RED)) + return 1 + task_json = target_dir / FILE_TASK_JSON if not task_json.is_file(): print(colored(f"Error: task.json not found at {target_dir}", Colors.RED)) diff --git a/.trellis/scripts/common/task_utils.py b/.trellis/scripts/common/task_utils.py index 62368db..46cbec1 100755 --- a/.trellis/scripts/common/task_utils.py +++ b/.trellis/scripts/common/task_utils.py @@ -195,7 +195,7 @@ def archive_task_complete( # Task Directory Resolution # ============================================================================= -def resolve_task_dir(target_dir: str, repo_root: Path) -> Path: +def resolve_task_dir(target_dir: str, repo_root: Path) -> Path | None: """Resolve task directory to absolute path. Supports: @@ -208,7 +208,9 @@ def resolve_task_dir(target_dir: str, repo_root: Path) -> Path: repo_root: Repository root path. Returns: - Resolved absolute path. + Resolved absolute path, or None when it resolves outside + `repo_root`. Both sides are resolved before comparing, since + `repo_root` may itself sit behind a symlink (/tmp does on macOS). """ if not target_dir: return Path() @@ -219,20 +221,29 @@ def resolve_task_dir(target_dir: str, repo_root: Path) -> Path: # Absolute path if Path(target_dir).is_absolute(): - return Path(target_dir) - + candidate = Path(target_dir) # Relative path (contains path separator or starts with .trellis) - if "/" in normalized or normalized.startswith(".trellis"): - return repo_root / Path(normalized) + elif "/" in normalized or normalized.startswith(".trellis"): + candidate = repo_root / Path(normalized) + else: + # Task name - try to find in tasks directory; fall back to treating + # it as a relative path when not found. + tasks_dir = get_tasks_dir(repo_root) + found = find_task_by_name(target_dir, tasks_dir) + candidate = found if found else repo_root / Path(normalized) + + try: + resolved = candidate.resolve() + root = repo_root.resolve() + except OSError: + return None - # Task name - try to find in tasks directory - tasks_dir = get_tasks_dir(repo_root) - found = find_task_by_name(target_dir, tasks_dir) - if found: - return found + try: + resolved.relative_to(root) + except ValueError: + return None - # Fallback to treating as relative path - return repo_root / Path(normalized) + return resolved # ============================================================================= diff --git a/.trellis/scripts/task.py b/.trellis/scripts/task.py index 7e82eac..e67c763 100755 --- a/.trellis/scripts/task.py +++ b/.trellis/scripts/task.py @@ -82,16 +82,26 @@ def cmd_start(args: argparse.Namespace) -> int: # Resolve task directory (supports task name, relative path, or absolute path) full_path = resolve_task_dir(task_input, repo_root) - if not full_path.is_dir(): + if not full_path or not full_path.is_dir(): print(colored(f"Error: Task not found: {task_input}", Colors.RED)) print("Hint: Use task name (e.g., 'my-task') or full path (e.g., '.trellis/tasks/01-31-my-task')") return 1 - # Convert to relative path for storage + # Convert to relative path for storage. repo_root is resolved because + # full_path already is (resolve_task_dir only returns paths inside the + # resolved root), so an unresolved repo_root would mismatch under a + # symlink (e.g. /tmp on macOS) and reject a perfectly normal task. try: - task_dir = full_path.relative_to(repo_root).as_posix() + task_dir = full_path.relative_to(repo_root.resolve()).as_posix() except ValueError: - task_dir = str(full_path) + # resolve_task_dir already refused everything outside the repo, so + # this is unreachable in practice. Refuse rather than fall back to + # str(full_path) — that fallback (a lexical relative_to() paired with + # an absolute-path fallback) is exactly the pattern that let a `..` + # ref escape into storage before this fix. + print(colored(f"Error: Task not found: {task_input}", Colors.RED)) + print("Hint: Use task name (e.g., 'my-task') or full path (e.g., '.trellis/tasks/01-31-my-task')") + return 1 task_json_path = full_path / FILE_TASK_JSON diff --git a/.trellis/workflow.md b/.trellis/workflow.md index bf3d1c1..158142e 100644 --- a/.trellis/workflow.md +++ b/.trellis/workflow.md @@ -280,13 +280,13 @@ When a user request matches one of these intents inside an active task, route fi [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Snow, Reasonix, Trae, Grok, Kimi Code] -[codex-inline, Kilo, Antigravity, Devin] +[codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] - Planning or unclear requirements -> `trellis-brainstorm`. - Before editing -> `trellis-before-dev`; after editing -> `trellis-check`. - Repeated debugging -> `trellis-break-loop`; spec updates -> `trellis-update-spec`. -[/codex-inline, Kilo, Antigravity, Devin] +[/codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] ### Guardrails @@ -363,11 +363,11 @@ Spawn the research sub-agent: [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Snow, Reasonix, Trae, Grok, Kimi Code] -[codex-inline, Kilo, Antigravity, Devin] +[codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] Do the research in the main session directly and write findings into `{TASK_DIR}/research/`. `codex-inline` is the explicit mode that keeps work in the main session. -[/codex-inline, Kilo, Antigravity, Devin] +[/codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] **Research artifact conventions**: - One file per research topic (e.g. `research/auth-library-comparison.md`) @@ -427,11 +427,11 @@ Skip this step only when both files already have real curated entries. [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Snow, Reasonix, Trae, Grok, Kimi Code] -[codex-inline, Kilo, Antigravity, Devin] +[codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] Skip this step. Context is loaded directly by the `trellis-before-dev` skill in Phase 2. -[/codex-inline, Kilo, Antigravity, Devin] +[/codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] #### 1.4 Activate task `[required · once]` @@ -515,7 +515,7 @@ The platform prelude auto-handles the context load requirement: [/Kiro] -[codex-inline, Kilo, Antigravity, Devin] +[codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] 1. Load the `trellis-before-dev` skill to read project guidelines 2. Read `{TASK_DIR}/prd.md`, then `design.md` if present, then `implement.md` if present @@ -523,7 +523,7 @@ The platform prelude auto-handles the context load requirement: 4. Implement the code per reviewed artifacts 5. Run project lint and type-check -[/codex-inline, Kilo, Antigravity, Devin] +[/codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] #### 2.2 Quality check `[required · repeatable]` @@ -543,7 +543,7 @@ The check agent's job: [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Snow, Reasonix, Trae, Grok, Kimi Code] -[codex-inline, Kilo, Antigravity, Devin] +[codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] Load the `trellis-check` skill and verify the code per its guidance: - Spec compliance @@ -552,7 +552,7 @@ Load the `trellis-check` skill and verify the code per its guidance: If issues are found → fix → re-check, until green. -[/codex-inline, Kilo, Antigravity, Devin] +[/codex-inline, Kilo, Antigravity, Devin, DeepSeek Harness] **Final pass (before Phase 3.4 commit)**: the last 2.2 of a task must run full-scope, not just on the latest implement chunk. List all affected packages with `python3 ./.trellis/scripts/get_context.py --mode packages`, then load each package's spec index Quality Check section. This catches cross-layer / multi-package issues a mid-iteration local 2.2 cannot.