@mindfoldhq/trellis 0.5.0-beta.13 → 0.5.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +24 -20
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +15 -12
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/claude.js +1 -1
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/codebuddy.js +1 -1
- package/dist/configurators/codebuddy.js.map +1 -1
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +3 -6
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts.map +1 -1
- package/dist/configurators/copilot.js +4 -11
- package/dist/configurators/copilot.js.map +1 -1
- package/dist/configurators/cursor.js +1 -1
- package/dist/configurators/cursor.js.map +1 -1
- package/dist/configurators/droid.js +1 -1
- package/dist/configurators/droid.js.map +1 -1
- package/dist/configurators/gemini.d.ts.map +1 -1
- package/dist/configurators/gemini.js +1 -3
- package/dist/configurators/gemini.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +24 -38
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/kiro.js +1 -1
- package/dist/configurators/kiro.js.map +1 -1
- package/dist/configurators/pi.d.ts +3 -0
- package/dist/configurators/pi.d.ts.map +1 -0
- package/dist/configurators/pi.js +39 -0
- package/dist/configurators/pi.js.map +1 -0
- package/dist/configurators/qoder.d.ts.map +1 -1
- package/dist/configurators/qoder.js +1 -3
- package/dist/configurators/qoder.js.map +1 -1
- package/dist/configurators/shared.d.ts +2 -4
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +6 -9
- package/dist/configurators/shared.js.map +1 -1
- package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.15.json +126 -0
- package/dist/templates/claude/agents/trellis-research.md +1 -1
- package/dist/templates/claude/settings.json +0 -4
- package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
- package/dist/templates/codex/agents/trellis-check.toml +0 -16
- package/dist/templates/codex/agents/trellis-implement.toml +0 -16
- package/dist/templates/codex/agents/trellis-research.toml +3 -2
- package/dist/templates/codex/hooks/session-start.py +82 -22
- package/dist/templates/codex/skills/start/SKILL.md +1 -1
- package/dist/templates/copilot/hooks/session-start.py +84 -26
- package/dist/templates/copilot/prompts/start.prompt.md +1 -1
- package/dist/templates/cursor/agents/trellis-check.md +1 -1
- package/dist/templates/cursor/agents/trellis-implement.md +1 -1
- package/dist/templates/cursor/agents/trellis-research.md +2 -2
- package/dist/templates/cursor/hooks.json +7 -1
- package/dist/templates/droid/droids/trellis-research.md +1 -1
- package/dist/templates/extract.d.ts +6 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +14 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/gemini/agents/trellis-research.md +1 -1
- package/dist/templates/kiro/agents/trellis-research.json +1 -1
- package/dist/templates/markdown/agents.md +11 -12
- package/dist/templates/markdown/gitignore.txt +3 -0
- package/dist/templates/opencode/agents/trellis-check.md +1 -1
- package/dist/templates/opencode/agents/trellis-implement.md +1 -1
- package/dist/templates/opencode/agents/trellis-research.md +2 -2
- package/dist/templates/opencode/lib/trellis-context.js +100 -13
- package/dist/templates/opencode/plugins/inject-subagent-context.js +54 -4
- package/dist/templates/opencode/plugins/inject-workflow-state.js +50 -23
- package/dist/templates/opencode/plugins/session-start.js +46 -21
- package/dist/templates/pi/agents/trellis-check.md +28 -0
- package/dist/templates/pi/agents/trellis-implement.md +33 -0
- package/dist/templates/pi/agents/trellis-research.md +25 -0
- package/dist/templates/pi/extensions/trellis/index.ts.txt +549 -0
- package/dist/templates/pi/index.d.ts +5 -0
- package/dist/templates/pi/index.d.ts.map +1 -0
- package/dist/templates/pi/index.js +12 -0
- package/dist/templates/pi/index.js.map +1 -0
- package/dist/templates/pi/settings.json +12 -0
- package/dist/templates/qoder/agents/trellis-research.md +1 -1
- package/dist/templates/shared-hooks/index.d.ts +31 -0
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +59 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
- package/dist/templates/shared-hooks/inject-subagent-context.py +128 -26
- package/dist/templates/shared-hooks/inject-workflow-state.py +101 -61
- package/dist/templates/shared-hooks/session-start.py +151 -28
- package/dist/templates/trellis/gitignore.txt +3 -0
- package/dist/templates/trellis/index.d.ts +1 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/__init__.py +8 -0
- package/dist/templates/trellis/scripts/common/active_task.py +593 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +43 -8
- package/dist/templates/trellis/scripts/common/paths.py +61 -58
- package/dist/templates/trellis/scripts/common/session_context.py +12 -0
- package/dist/templates/trellis/scripts/common/task_store.py +4 -6
- package/dist/templates/trellis/scripts/task.py +56 -14
- package/dist/templates/trellis/workflow.md +31 -26
- package/dist/types/ai-tools.d.ts +3 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +16 -0
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +22 -6
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +405 -27
- package/dist/utils/template-fetcher.js.map +1 -1
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +3 -2
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +1 -1
- package/dist/templates/shared-hooks/statusline.py +0 -218
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trellis-research",
|
|
3
3
|
"description": "Code and tech search expert. Persists findings to {TASK_DIR}/research/. No writes outside that directory.",
|
|
4
|
-
"instructions": "# Research Agent\n\nYou are the Research Agent in the Trellis workflow.\n\n## Core Principle\n\n**You do one thing: find, explain, and PERSIST information.**\n\nConversations 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.\n\n---\n\n## Core Responsibilities\n\n1. **Internal Search** — locate files/components, understand code logic, discover patterns (Glob, Grep, Read)\n2. **External Search** — library docs, API references, best practices (web search)\n3. **Persist** — write each research topic to `{TASK_DIR}/research/<topic>.md`\n4. **Report** — return file paths + one-line summaries to the main agent (not full content)\n\n---\n\n## Workflow\n\n### Step 1: Resolve Current Task\n\
|
|
4
|
+
"instructions": "# Research Agent\n\nYou are the Research Agent in the Trellis workflow.\n\n## Core Principle\n\n**You do one thing: find, explain, and PERSIST information.**\n\nConversations 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.\n\n---\n\n## Core Responsibilities\n\n1. **Internal Search** — locate files/components, understand code logic, discover patterns (Glob, Grep, Read)\n2. **External Search** — library docs, API references, best practices (web search)\n3. **Persist** — write each research topic to `{TASK_DIR}/research/<topic>.md`\n4. **Report** — return file paths + one-line summaries to the main agent (not full content)\n\n---\n\n## Workflow\n\n### Step 1: Resolve Current Task\n\nRun `python3 ./.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.\n\nEnsure `{TASK_DIR}/research/` exists:\n\n```bash\nmkdir -p <TASK_DIR>/research\n```\n\n### Step 2: Understand Search Request\n\nClassify: internal / external / mixed. Determine scope (global / specific directory) and expected shape (file list / pattern notes / tech comparison).\n\n### Step 3: Execute Search\n\nRun independent searches in parallel (Glob + Grep + web) for efficiency.\n\n### Step 4: Persist Each Topic\n\nFor each distinct research topic, Write a markdown file at `{TASK_DIR}/research/<topic-slug>.md`. Use the File Format below.\n\n### Step 5: Report to Main Agent\n\nReply with ONLY:\n\n- List of files written (paths relative to repo root)\n- One-line summary per file\n- Any critical caveats that the main agent needs to know right now\n\nDo NOT paste full research content into the reply. The files are the contract.\n\n---\n\n## Scope Limits (Strict)\n\n### Write ALLOWED\n\n- `{TASK_DIR}/research/*.md` — your own output\n- Creating `{TASK_DIR}/research/` if it doesn't exist (via `mkdir -p`)\n\n### Write FORBIDDEN\n\n- Code files (`src/`, `lib/`, …)\n- Spec files (`.trellis/spec/`) — main agent should use `update-spec` skill instead\n- `.trellis/scripts/`, `.trellis/workflow.md`, platform config (`.claude/`, `.cursor/`, etc.)\n- Other task directories\n- Any git operation (commit / push / branch / merge)\n\nIf the user asks you to edit code, decline and suggest spawning `implement` instead.\n\n---\n\n## File Format\n\nEach `{TASK_DIR}/research/<topic>.md` should follow:\n\n```markdown\n# Research: <topic>\n\n- **Query**: <original query>\n- **Scope**: <internal / external / mixed>\n- **Date**: <YYYY-MM-DD>\n\n## Findings\n\n### Files Found\n\n| File Path | Description |\n|---|---|\n| `src/services/xxx.ts` | Main implementation |\n| `src/types/xxx.ts` | Type definitions |\n\n### Code Patterns\n\n<describe patterns, cite file:line>\n\n### External References\n\n- [Library X docs](url) — <why relevant, version constraints>\n\n### Related Specs\n\n- `.trellis/spec/xxx.md` — <description>\n\n## Caveats / Not Found\n\n<anything incomplete or uncertain>\n```\n\n---\n\n## Guidelines\n\n### DO\n\n- Provide specific file paths and line numbers\n- Quote actual code snippets\n- Persist every topic to its own file\n- Return file paths in your reply, not the full content\n- Mark \"not found\" explicitly when searches come up empty\n\n### DON'T\n\n- Don't write code or modify files outside `{TASK_DIR}/research/`\n- Don't guess uncertain info\n- Don't paste full research text into the reply (files are the deliverable)\n- Don't propose improvements or critique implementation (that's not your role)\n",
|
|
5
5
|
"tools": [
|
|
6
6
|
"read",
|
|
7
7
|
"write",
|
|
@@ -3,20 +3,19 @@
|
|
|
3
3
|
|
|
4
4
|
These instructions are for AI assistants working in this project.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
- Initialize your developer identity
|
|
8
|
-
- Understand current project context
|
|
9
|
-
- Read relevant guidelines
|
|
6
|
+
This project is managed by Trellis. The working knowledge you need lives under `.trellis/`:
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
8
|
+
- `.trellis/workflow.md` — development phases, when to create tasks, skill routing
|
|
9
|
+
- `.trellis/spec/` — package- and layer-scoped coding guidelines (read before writing code in a given layer)
|
|
10
|
+
- `.trellis/workspace/` — per-developer journals and session traces
|
|
11
|
+
- `.trellis/tasks/` — active and archived tasks (PRDs, research, jsonl context)
|
|
15
12
|
|
|
16
|
-
If
|
|
17
|
-
- `.agents/skills/` for reusable Trellis skills
|
|
18
|
-
- `.codex/agents/` for optional custom subagents
|
|
13
|
+
If a Trellis command is available on your platform (e.g. `/trellis:finish-work`, `/trellis:continue`), prefer it over manual steps. Not every platform exposes every command.
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
If you're using Codex or another agent-capable tool, additional project-scoped helpers may live in:
|
|
16
|
+
- `.agents/skills/` — reusable Trellis skills
|
|
17
|
+
- `.codex/agents/` — optional custom subagents
|
|
18
|
+
|
|
19
|
+
Managed by Trellis. Edits outside this block are preserved; edits inside may be overwritten by a future `trellis update`.
|
|
21
20
|
|
|
22
21
|
<!-- TRELLIS:END -->
|
|
@@ -21,7 +21,7 @@ You are the Check Agent in the Trellis workflow.
|
|
|
21
21
|
|
|
22
22
|
Otherwise, load context yourself:
|
|
23
23
|
|
|
24
|
-
1.
|
|
24
|
+
1. Run `python3 ./.trellis/scripts/task.py current --source` → get active task directory and source (e.g., `Current task: .trellis/tasks/xxx`)
|
|
25
25
|
2. Read `{task_dir}/check.jsonl`
|
|
26
26
|
3. For each entry in JSONL:
|
|
27
27
|
- If `path` is a file → Read it
|
|
@@ -21,7 +21,7 @@ You are the Implement Agent in the Trellis workflow.
|
|
|
21
21
|
|
|
22
22
|
Otherwise, load context yourself:
|
|
23
23
|
|
|
24
|
-
1.
|
|
24
|
+
1. Run `python3 ./.trellis/scripts/task.py current --source` → get active task directory and source (e.g., `Current task: .trellis/tasks/xxx`)
|
|
25
25
|
2. Read `{task_dir}/implement.jsonl`
|
|
26
26
|
3. For each entry in JSONL (JSON object per line):
|
|
27
27
|
- Skip rows without a `"file"` field (e.g. `{"_example": "..."}` seed rows)
|
|
@@ -6,7 +6,7 @@ permission:
|
|
|
6
6
|
read: allow
|
|
7
7
|
write: deny
|
|
8
8
|
edit: deny
|
|
9
|
-
bash:
|
|
9
|
+
bash: allow
|
|
10
10
|
glob: allow
|
|
11
11
|
grep: allow
|
|
12
12
|
mcp__exa__*: allow
|
|
@@ -22,7 +22,7 @@ You are the Research Agent in the Trellis workflow.
|
|
|
22
22
|
|
|
23
23
|
Otherwise, if task-specific research is needed:
|
|
24
24
|
|
|
25
|
-
1.
|
|
25
|
+
1. Run `python3 ./.trellis/scripts/task.py current --source` → get active task directory and source (if set)
|
|
26
26
|
2. For each entry in JSONL (if task dir exists):
|
|
27
27
|
- If `path` is a file → Read it
|
|
28
28
|
- If `path` is a directory → Read all `.md` files in it
|
|
@@ -9,6 +9,8 @@ import { existsSync, readFileSync, appendFileSync, readdirSync } from "fs"
|
|
|
9
9
|
import { isAbsolute, join } from "path"
|
|
10
10
|
import { platform } from "os"
|
|
11
11
|
import { execSync } from "child_process"
|
|
12
|
+
import { createHash } from "crypto"
|
|
13
|
+
import process from "process"
|
|
12
14
|
|
|
13
15
|
const PYTHON_CMD = platform() === "win32" ? "python" : "python3"
|
|
14
16
|
// Debug logging
|
|
@@ -24,6 +26,43 @@ function debugLog(prefix, ...args) {
|
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
function stringValue(value) {
|
|
30
|
+
return typeof value === "string" && value.trim() ? value.trim() : null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sanitizeKey(raw) {
|
|
34
|
+
const safe = raw.trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^[._-]+|[._-]+$/g, "")
|
|
35
|
+
return safe ? safe.slice(0, 160) : ""
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hashValue(raw) {
|
|
39
|
+
return createHash("sha256").update(raw).digest("hex").slice(0, 24)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function lookupString(data, keys) {
|
|
43
|
+
if (!data || typeof data !== "object") return null
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
const value = stringValue(data[key])
|
|
46
|
+
if (value) return value
|
|
47
|
+
}
|
|
48
|
+
for (const nestedKey of ["input", "properties", "event", "hook_input", "hookInput"]) {
|
|
49
|
+
const nested = data[nestedKey]
|
|
50
|
+
if (nested && typeof nested === "object") {
|
|
51
|
+
const value = lookupString(nested, keys)
|
|
52
|
+
if (value) return value
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildContextKey(platformName, kind, value) {
|
|
59
|
+
if (kind === "transcript") {
|
|
60
|
+
return `${platformName}_transcript_${hashValue(value)}`
|
|
61
|
+
}
|
|
62
|
+
const safeValue = sanitizeKey(value)
|
|
63
|
+
return safeValue ? `${platformName}_${safeValue}` : `${platformName}_${hashValue(value)}`
|
|
64
|
+
}
|
|
65
|
+
|
|
27
66
|
/**
|
|
28
67
|
* Trellis Context Manager
|
|
29
68
|
*/
|
|
@@ -41,23 +80,67 @@ export class TrellisContext {
|
|
|
41
80
|
return existsSync(join(this.directory, ".trellis"))
|
|
42
81
|
}
|
|
43
82
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
83
|
+
getContextKey(platformInput = null) {
|
|
84
|
+
const override = stringValue(process.env.TRELLIS_CONTEXT_ID)
|
|
85
|
+
if (override) {
|
|
86
|
+
return sanitizeKey(override) || hashValue(override)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const runID = stringValue(process.env.OPENCODE_RUN_ID)
|
|
90
|
+
if (runID) return buildContextKey("opencode", "session", runID)
|
|
91
|
+
|
|
92
|
+
const input = platformInput && typeof platformInput === "object" ? platformInput : null
|
|
93
|
+
if (!input) return null
|
|
94
|
+
|
|
95
|
+
const sessionID = lookupString(input, ["session_id", "sessionId", "sessionID"])
|
|
96
|
+
if (sessionID) return buildContextKey("opencode", "session", sessionID)
|
|
97
|
+
|
|
98
|
+
const conversationID = lookupString(input, ["conversation_id", "conversationId", "conversationID"])
|
|
99
|
+
if (conversationID) return buildContextKey("opencode", "conversation", conversationID)
|
|
100
|
+
|
|
101
|
+
const transcriptPath = lookupString(input, ["transcript_path", "transcriptPath", "transcript"])
|
|
102
|
+
if (transcriptPath) return buildContextKey("opencode", "transcript", transcriptPath)
|
|
103
|
+
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
readContext(contextKey) {
|
|
48
108
|
try {
|
|
49
|
-
const
|
|
50
|
-
if (!existsSync(
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
const taskRef = readFileSync(currentTaskPath, "utf-8").trim()
|
|
54
|
-
const normalized = this.normalizeTaskRef(taskRef)
|
|
55
|
-
return normalized || null
|
|
109
|
+
const contextPath = join(this.directory, ".trellis", ".runtime", "sessions", `${contextKey}.json`)
|
|
110
|
+
if (!existsSync(contextPath)) return null
|
|
111
|
+
return JSON.parse(readFileSync(contextPath, "utf-8"))
|
|
56
112
|
} catch {
|
|
57
113
|
return null
|
|
58
114
|
}
|
|
59
115
|
}
|
|
60
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Get active task from session runtime context.
|
|
119
|
+
*/
|
|
120
|
+
getActiveTask(platformInput = null) {
|
|
121
|
+
const contextKey = this.getContextKey(platformInput)
|
|
122
|
+
if (!contextKey) {
|
|
123
|
+
return { taskPath: null, source: "none", stale: false }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const context = this.readContext(contextKey)
|
|
127
|
+
const taskRef = this.normalizeTaskRef(context?.current_task || "")
|
|
128
|
+
if (taskRef) {
|
|
129
|
+
const taskDir = this.resolveTaskDir(taskRef)
|
|
130
|
+
return {
|
|
131
|
+
taskPath: taskRef,
|
|
132
|
+
source: `session:${contextKey}`,
|
|
133
|
+
stale: !taskDir || !existsSync(taskDir),
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { taskPath: null, source: "none", stale: false }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getCurrentTask(platformInput = null) {
|
|
141
|
+
return this.getActiveTask(platformInput).taskPath
|
|
142
|
+
}
|
|
143
|
+
|
|
61
144
|
normalizeTaskRef(taskRef) {
|
|
62
145
|
if (!taskRef) {
|
|
63
146
|
return ""
|
|
@@ -115,13 +198,17 @@ export class TrellisContext {
|
|
|
115
198
|
return this.readFile(join(this.directory, relativePath))
|
|
116
199
|
}
|
|
117
200
|
|
|
118
|
-
runScript(scriptPath, cwd = null) {
|
|
201
|
+
runScript(scriptPath, cwd = null, contextKey = null) {
|
|
119
202
|
try {
|
|
120
203
|
const result = execSync(`${PYTHON_CMD} "${scriptPath}"`, {
|
|
121
204
|
cwd: cwd || this.directory,
|
|
122
205
|
timeout: 10000,
|
|
123
206
|
encoding: "utf-8",
|
|
124
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
207
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
208
|
+
env: {
|
|
209
|
+
...process.env,
|
|
210
|
+
...(contextKey ? { TRELLIS_CONTEXT_ID: contextKey } : {}),
|
|
211
|
+
},
|
|
125
212
|
})
|
|
126
213
|
return result || ""
|
|
127
214
|
} catch {
|
|
@@ -255,6 +255,46 @@ ${originalPrompt}
|
|
|
255
255
|
return templates[agentType] || originalPrompt
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
function shellQuote(value) {
|
|
259
|
+
return `'${String(value).replace(/'/g, "'\\''")}'`
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function getBashCommandKey(args) {
|
|
263
|
+
if (!args || typeof args !== "object") return null
|
|
264
|
+
if (typeof args.command === "string") return "command"
|
|
265
|
+
if (typeof args.cmd === "string") return "cmd"
|
|
266
|
+
return null
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function commandStartsWithTrellisContext(command) {
|
|
270
|
+
const firstCommand = command.trimStart().split(/[;&|]/, 1)[0].trimStart()
|
|
271
|
+
return (
|
|
272
|
+
/^TRELLIS_CONTEXT_ID\s*=/.test(firstCommand) ||
|
|
273
|
+
/^export\s+TRELLIS_CONTEXT_ID\s*=/.test(firstCommand) ||
|
|
274
|
+
/^env\s+(?:[^\s=]+\s+)*TRELLIS_CONTEXT_ID\s*=/.test(firstCommand)
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* OpenCode TUI may not expose OPENCODE_RUN_ID to Bash. The plugin hook still
|
|
280
|
+
* receives session identity, so inject it into Bash commands before execution.
|
|
281
|
+
*/
|
|
282
|
+
function injectTrellisContextIntoBash(ctx, input, output) {
|
|
283
|
+
const args = output?.args
|
|
284
|
+
const commandKey = getBashCommandKey(args)
|
|
285
|
+
if (!commandKey) return false
|
|
286
|
+
|
|
287
|
+
const command = args[commandKey]
|
|
288
|
+
if (!command.trim()) return false
|
|
289
|
+
if (commandStartsWithTrellisContext(command)) return false
|
|
290
|
+
|
|
291
|
+
const contextKey = ctx.getContextKey(input)
|
|
292
|
+
if (!contextKey) return false
|
|
293
|
+
|
|
294
|
+
args[commandKey] = `export TRELLIS_CONTEXT_ID=${shellQuote(contextKey)}; ${command}`
|
|
295
|
+
return true
|
|
296
|
+
}
|
|
297
|
+
|
|
258
298
|
// OpenCode plugin factory: `export default async (input) => hooks`.
|
|
259
299
|
// OpenCode 1.2.x iterates every module export and invokes it as a function
|
|
260
300
|
// (packages/opencode/src/plugin/index.ts — `for ([_, fn] of Object.entries(mod)) await fn(input)`);
|
|
@@ -270,6 +310,13 @@ export default async ({ directory }) => {
|
|
|
270
310
|
debugLog("inject", "tool.execute.before called, tool:", input?.tool)
|
|
271
311
|
|
|
272
312
|
const toolName = input?.tool?.toLowerCase()
|
|
313
|
+
if (toolName === "bash") {
|
|
314
|
+
if (injectTrellisContextIntoBash(ctx, input, output)) {
|
|
315
|
+
debugLog("inject", "Injected TRELLIS_CONTEXT_ID into Bash command")
|
|
316
|
+
}
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
273
320
|
if (toolName !== "task") {
|
|
274
321
|
return
|
|
275
322
|
}
|
|
@@ -277,21 +324,24 @@ export default async ({ directory }) => {
|
|
|
277
324
|
const args = output?.args
|
|
278
325
|
if (!args) return
|
|
279
326
|
|
|
280
|
-
const
|
|
327
|
+
const rawSubagentType = args.subagent_type
|
|
328
|
+
// Strip "trellis-" prefix added by v0.5.0-beta.5 agent rename migration
|
|
329
|
+
const subagentType = (rawSubagentType || "").replace(/^trellis-/, "")
|
|
281
330
|
const originalPrompt = args.prompt || ""
|
|
282
331
|
|
|
283
|
-
debugLog("inject", "Task tool called, subagent_type:",
|
|
332
|
+
debugLog("inject", "Task tool called, subagent_type:", rawSubagentType)
|
|
284
333
|
|
|
285
334
|
if (!AGENTS_ALL.includes(subagentType)) {
|
|
286
335
|
debugLog("inject", "Skipping - unsupported subagent_type")
|
|
287
336
|
return
|
|
288
337
|
}
|
|
289
338
|
|
|
290
|
-
//
|
|
291
|
-
const taskDir = ctx.getCurrentTask()
|
|
339
|
+
// Resolve active task through session runtime context.
|
|
340
|
+
const taskDir = ctx.getCurrentTask(input)
|
|
292
341
|
|
|
293
342
|
// Agents requiring task directory
|
|
294
343
|
if (AGENTS_REQUIRE_TASK.includes(subagentType)) {
|
|
344
|
+
// subagentType is already stripped of "trellis-" prefix above
|
|
295
345
|
if (!taskDir) {
|
|
296
346
|
debugLog("inject", "Skipping - no current task")
|
|
297
347
|
return
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Silently skips when:
|
|
19
19
|
* - No .trellis/ directory
|
|
20
|
-
* - No active task
|
|
20
|
+
* - No active task in the session runtime context
|
|
21
21
|
* - task.json malformed or missing status
|
|
22
22
|
*/
|
|
23
23
|
|
|
@@ -32,25 +32,32 @@ const TAG_RE = /\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n([\s\S]*?)\n\s*\[\/workf
|
|
|
32
32
|
// Hardcoded defaults for built-in Trellis statuses. Used when workflow.md
|
|
33
33
|
// is missing, malformed, or lacks the tag for this status.
|
|
34
34
|
//
|
|
35
|
-
// `no_task` is a pseudo-status emitted when
|
|
35
|
+
// `no_task` is a pseudo-status emitted when no session active task exists — keeps
|
|
36
36
|
// the Next-Action reminder flowing per-turn even without an active task.
|
|
37
37
|
const FALLBACK_BREADCRUMBS = {
|
|
38
38
|
no_task:
|
|
39
39
|
"No active task.\n" +
|
|
40
|
-
"Trigger words in the user message that
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
40
|
+
"Trigger words in the user message that suggest creating a task: " +
|
|
41
|
+
"重构 / 抽成 / 独立 / 分发 / 拆出来 / 搞一个 / 做成 / 接入 / 集成 / " +
|
|
42
|
+
"refactor / rewrite / extract / productize / publish / build X / design Y.\n" +
|
|
43
|
+
"Task is NOT required if ALL three hold: (a) zero file writes this turn, " +
|
|
44
|
+
"(b) answer fits in one reply with no multi-round plan, (c) no research " +
|
|
45
|
+
"beyond reading 1-2 repo files.\n" +
|
|
46
|
+
"When in doubt and no override below applies: prefer creating a task — " +
|
|
47
|
+
"over-tasking is cheap; under-tasking leaks plans and research into " +
|
|
48
|
+
"main context.\n" +
|
|
49
49
|
"Flow: load `trellis-brainstorm` skill → it creates the task via " +
|
|
50
50
|
"`python3 ./.trellis/scripts/task.py create` and drives requirements Q&A. " +
|
|
51
51
|
"For research-heavy work (tool comparison, docs, cross-platform survey), " +
|
|
52
52
|
"spawn `trellis-research` sub-agents via Task tool — NEVER do 3+ inline " +
|
|
53
|
-
"WebFetch/WebSearch/`gh api` calls in the main conversation
|
|
53
|
+
"WebFetch/WebSearch/`gh api` calls in the main conversation.\n" +
|
|
54
|
+
"User override (per-turn escape hatch): if the user's CURRENT message " +
|
|
55
|
+
"contains an explicit opt-out phrase (\"跳过 trellis\" / \"别走流程\" / " +
|
|
56
|
+
"\"小修一下\" / \"直接改\" / \"先别建任务\" / \"skip trellis\" / " +
|
|
57
|
+
"\"no task\" / \"just do it\" / \"don't create a task\"), honor it for " +
|
|
58
|
+
"this turn — briefly acknowledge (\"好,本轮跳过 trellis 流程\") and " +
|
|
59
|
+
"proceed without creating a task. Per-turn only; does not carry forward; " +
|
|
60
|
+
"do NOT invent an override the user did not say.",
|
|
54
61
|
planning:
|
|
55
62
|
"Complete prd.md via trellis-brainstorm skill; then run task.py start.\n" +
|
|
56
63
|
"Research belongs in `{task_dir}/research/*.md`, written by " +
|
|
@@ -58,7 +65,21 @@ const FALLBACK_BREADCRUMBS = {
|
|
|
58
65
|
"main session — PRD only links to research files.",
|
|
59
66
|
in_progress:
|
|
60
67
|
"Flow: trellis-implement → trellis-check → trellis-update-spec → finish\n" +
|
|
61
|
-
"
|
|
68
|
+
"Next required action: inspect conversation history + git status, then " +
|
|
69
|
+
"execute the next uncompleted step in that sequence.\n" +
|
|
70
|
+
"For agent-capable platforms, the default is to dispatch " +
|
|
71
|
+
"`trellis-implement` for implementation and `trellis-check` before " +
|
|
72
|
+
"reporting completion — do not edit code in the main session by default.\n" +
|
|
73
|
+
"Use the exact Trellis agent type names when spawning sub-agents: " +
|
|
74
|
+
"`trellis-implement`, `trellis-check`, or `trellis-research`. " +
|
|
75
|
+
"Generic/default/generalPurpose sub-agents do not receive " +
|
|
76
|
+
"`implement.jsonl` / `check.jsonl` injection.\n" +
|
|
77
|
+
"User override (per-turn escape hatch): if the user's CURRENT message " +
|
|
78
|
+
"explicitly tells the main session to handle it directly (\"你直接改\" / " +
|
|
79
|
+
"\"别派 sub-agent\" / \"main session 写就行\" / \"do it inline\" / " +
|
|
80
|
+
"\"不用 sub-agent\"), honor it for this turn and edit code directly. " +
|
|
81
|
+
"Per-turn only; does not carry forward; do NOT invent an override the " +
|
|
82
|
+
"user did not say.",
|
|
62
83
|
completed:
|
|
63
84
|
"User commits changes; then run task.py archive.",
|
|
64
85
|
}
|
|
@@ -88,11 +109,14 @@ function loadBreadcrumbs(directory) {
|
|
|
88
109
|
/**
|
|
89
110
|
* Get (taskId, status) from active task, or null if no active task.
|
|
90
111
|
*/
|
|
91
|
-
function getActiveTask(ctx) {
|
|
92
|
-
const
|
|
112
|
+
function getActiveTask(ctx, platformInput = null) {
|
|
113
|
+
const active = ctx.getActiveTask(platformInput)
|
|
114
|
+
const taskRef = active.taskPath
|
|
93
115
|
if (!taskRef) return null
|
|
94
116
|
const taskDir = ctx.resolveTaskDir(taskRef)
|
|
95
|
-
if (!taskDir || !existsSync(taskDir))
|
|
117
|
+
if (active.stale || !taskDir || !existsSync(taskDir)) {
|
|
118
|
+
return { id: taskRef.split("/").pop(), status: "stale", source: active.source }
|
|
119
|
+
}
|
|
96
120
|
const taskJsonPath = join(taskDir, "task.json")
|
|
97
121
|
if (!existsSync(taskJsonPath)) return null
|
|
98
122
|
try {
|
|
@@ -100,7 +124,7 @@ function getActiveTask(ctx) {
|
|
|
100
124
|
const status = typeof data.status === "string" ? data.status : ""
|
|
101
125
|
if (!status) return null
|
|
102
126
|
const id = data.id || taskRef.split("/").pop()
|
|
103
|
-
return { id, status }
|
|
127
|
+
return { id, status, source: active.source }
|
|
104
128
|
} catch {
|
|
105
129
|
return null
|
|
106
130
|
}
|
|
@@ -112,12 +136,15 @@ function getActiveTask(ctx) {
|
|
|
112
136
|
* - Unknown status → generic "refer to workflow.md"
|
|
113
137
|
* - no_task pseudo-status (id === null) → header omits task info
|
|
114
138
|
*/
|
|
115
|
-
function buildBreadcrumb(id, status, templates) {
|
|
139
|
+
function buildBreadcrumb(id, status, templates, source = null) {
|
|
116
140
|
let body = templates[status]
|
|
117
141
|
if (body === undefined) {
|
|
118
142
|
body = "Refer to workflow.md for current step."
|
|
119
143
|
}
|
|
120
|
-
|
|
144
|
+
let header = id === null ? `Status: ${status}` : `Task: ${id} (${status})`
|
|
145
|
+
if (source) {
|
|
146
|
+
header = `${header}\nSource: ${source}`
|
|
147
|
+
}
|
|
121
148
|
return `<workflow-state>\n${header}\n${body}\n</workflow-state>`
|
|
122
149
|
}
|
|
123
150
|
|
|
@@ -138,9 +165,9 @@ export default async ({ directory }) => {
|
|
|
138
165
|
return
|
|
139
166
|
}
|
|
140
167
|
const templates = loadBreadcrumbs(directory)
|
|
141
|
-
const task = getActiveTask(ctx)
|
|
168
|
+
const task = getActiveTask(ctx, input)
|
|
142
169
|
const breadcrumb = task
|
|
143
|
-
? buildBreadcrumb(task.id, task.status, templates)
|
|
170
|
+
? buildBreadcrumb(task.id, task.status, templates, task.source)
|
|
144
171
|
: buildBreadcrumb(null, "no_task", templates)
|
|
145
172
|
|
|
146
173
|
const parts = output?.parts || []
|
|
@@ -156,9 +183,9 @@ export default async ({ directory }) => {
|
|
|
156
183
|
debugLog(
|
|
157
184
|
"workflow-state",
|
|
158
185
|
"Injected breadcrumb for task",
|
|
159
|
-
task.id,
|
|
186
|
+
task ? task.id : "none",
|
|
160
187
|
"status",
|
|
161
|
-
task.status,
|
|
188
|
+
task ? task.status : "no_task",
|
|
162
189
|
)
|
|
163
190
|
} catch (error) {
|
|
164
191
|
debugLog(
|