@mindfoldhq/trellis 0.5.0-beta.14 → 0.5.0-beta.16
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 +44 -13
- 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/opencode.d.ts.map +1 -1
- package/dist/configurators/opencode.js +4 -1
- package/dist/configurators/opencode.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.15.json +116 -0
- package/dist/migrations/manifests/0.5.0-beta.16.json +9 -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 +51 -21
- package/dist/templates/codex/skills/start/SKILL.md +1 -1
- package/dist/templates/copilot/hooks/session-start.py +51 -21
- 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 +48 -25
- package/dist/templates/opencode/plugins/session-start.js +29 -16
- 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 +99 -62
- package/dist/templates/shared-hooks/session-start.py +139 -24
- 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 +6 -8
- package/dist/templates/trellis/scripts/task.py +59 -17
- package/dist/templates/trellis/workflow.md +30 -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/posix.d.ts +13 -0
- package/dist/utils/posix.d.ts.map +1 -0
- package/dist/utils/posix.js +15 -0
- package/dist/utils/posix.js.map +1 -0
- 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 +22 -3
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +80 -18
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +1 -1
- package/dist/templates/shared-hooks/statusline.py +0 -219
|
@@ -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 " +
|
|
@@ -60,9 +67,19 @@ const FALLBACK_BREADCRUMBS = {
|
|
|
60
67
|
"Flow: trellis-implement → trellis-check → trellis-update-spec → finish\n" +
|
|
61
68
|
"Next required action: inspect conversation history + git status, then " +
|
|
62
69
|
"execute the next uncompleted step in that sequence.\n" +
|
|
63
|
-
"For agent-capable platforms,
|
|
64
|
-
"
|
|
65
|
-
"
|
|
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.",
|
|
66
83
|
completed:
|
|
67
84
|
"User commits changes; then run task.py archive.",
|
|
68
85
|
}
|
|
@@ -92,11 +109,14 @@ function loadBreadcrumbs(directory) {
|
|
|
92
109
|
/**
|
|
93
110
|
* Get (taskId, status) from active task, or null if no active task.
|
|
94
111
|
*/
|
|
95
|
-
function getActiveTask(ctx) {
|
|
96
|
-
const
|
|
112
|
+
function getActiveTask(ctx, platformInput = null) {
|
|
113
|
+
const active = ctx.getActiveTask(platformInput)
|
|
114
|
+
const taskRef = active.taskPath
|
|
97
115
|
if (!taskRef) return null
|
|
98
116
|
const taskDir = ctx.resolveTaskDir(taskRef)
|
|
99
|
-
if (!taskDir || !existsSync(taskDir))
|
|
117
|
+
if (active.stale || !taskDir || !existsSync(taskDir)) {
|
|
118
|
+
return { id: taskRef.split("/").pop(), status: "stale", source: active.source }
|
|
119
|
+
}
|
|
100
120
|
const taskJsonPath = join(taskDir, "task.json")
|
|
101
121
|
if (!existsSync(taskJsonPath)) return null
|
|
102
122
|
try {
|
|
@@ -104,7 +124,7 @@ function getActiveTask(ctx) {
|
|
|
104
124
|
const status = typeof data.status === "string" ? data.status : ""
|
|
105
125
|
if (!status) return null
|
|
106
126
|
const id = data.id || taskRef.split("/").pop()
|
|
107
|
-
return { id, status }
|
|
127
|
+
return { id, status, source: active.source }
|
|
108
128
|
} catch {
|
|
109
129
|
return null
|
|
110
130
|
}
|
|
@@ -116,12 +136,15 @@ function getActiveTask(ctx) {
|
|
|
116
136
|
* - Unknown status → generic "refer to workflow.md"
|
|
117
137
|
* - no_task pseudo-status (id === null) → header omits task info
|
|
118
138
|
*/
|
|
119
|
-
function buildBreadcrumb(id, status, templates) {
|
|
139
|
+
function buildBreadcrumb(id, status, templates, source = null) {
|
|
120
140
|
let body = templates[status]
|
|
121
141
|
if (body === undefined) {
|
|
122
142
|
body = "Refer to workflow.md for current step."
|
|
123
143
|
}
|
|
124
|
-
|
|
144
|
+
let header = id === null ? `Status: ${status}` : `Task: ${id} (${status})`
|
|
145
|
+
if (source) {
|
|
146
|
+
header = `${header}\nSource: ${source}`
|
|
147
|
+
}
|
|
125
148
|
return `<workflow-state>\n${header}\n${body}\n</workflow-state>`
|
|
126
149
|
}
|
|
127
150
|
|
|
@@ -142,9 +165,9 @@ export default async ({ directory }) => {
|
|
|
142
165
|
return
|
|
143
166
|
}
|
|
144
167
|
const templates = loadBreadcrumbs(directory)
|
|
145
|
-
const task = getActiveTask(ctx)
|
|
168
|
+
const task = getActiveTask(ctx, input)
|
|
146
169
|
const breadcrumb = task
|
|
147
|
-
? buildBreadcrumb(task.id, task.status, templates)
|
|
170
|
+
? buildBreadcrumb(task.id, task.status, templates, task.source)
|
|
148
171
|
: buildBreadcrumb(null, "no_task", templates)
|
|
149
172
|
|
|
150
173
|
const parts = output?.parts || []
|
|
@@ -160,9 +183,9 @@ export default async ({ directory }) => {
|
|
|
160
183
|
debugLog(
|
|
161
184
|
"workflow-state",
|
|
162
185
|
"Injected breadcrumb for task",
|
|
163
|
-
task.id,
|
|
186
|
+
task ? task.id : "none",
|
|
164
187
|
"status",
|
|
165
|
-
task.status,
|
|
188
|
+
task ? task.status : "no_task",
|
|
166
189
|
)
|
|
167
190
|
} catch (error) {
|
|
168
191
|
debugLog(
|
|
@@ -52,16 +52,17 @@ function hasCuratedJsonlEntry(jsonlPath) {
|
|
|
52
52
|
* Check current task status and return structured status string.
|
|
53
53
|
* JavaScript equivalent of _get_task_status in Claude's session-start.py.
|
|
54
54
|
*/
|
|
55
|
-
function getTaskStatus(ctx) {
|
|
56
|
-
const
|
|
55
|
+
function getTaskStatus(ctx, platformInput = null) {
|
|
56
|
+
const active = ctx.getActiveTask(platformInput)
|
|
57
|
+
const taskRef = active.taskPath
|
|
57
58
|
if (!taskRef) {
|
|
58
|
-
return
|
|
59
|
+
return `Status: NO ACTIVE TASK\nSource: ${active.source}\nNext: Describe what you want to work on`
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
const taskDir = ctx.resolveTaskDir(taskRef)
|
|
62
63
|
|
|
63
|
-
if (!taskDir || !existsSync(taskDir)) {
|
|
64
|
-
return `Status: STALE POINTER\nTask: ${taskRef}\nNext: Task directory not found. Run: python3 ./.trellis/scripts/task.py finish`
|
|
64
|
+
if (active.stale || !taskDir || !existsSync(taskDir)) {
|
|
65
|
+
return `Status: STALE POINTER\nTask: ${taskRef}\nSource: ${active.source}\nNext: Task directory not found. Run: python3 ./.trellis/scripts/task.py finish`
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
let taskData = {}
|
|
@@ -79,7 +80,7 @@ function getTaskStatus(ctx) {
|
|
|
79
80
|
|
|
80
81
|
if (taskStatus === "completed") {
|
|
81
82
|
const dirName = basename(taskDir)
|
|
82
|
-
return `Status: COMPLETED\nTask: ${taskTitle}\nNext: Archive with \`python3 ./.trellis/scripts/task.py archive ${dirName}\` or start a new task`
|
|
83
|
+
return `Status: COMPLETED\nTask: ${taskTitle}\nSource: ${active.source}\nNext: Archive with \`python3 ./.trellis/scripts/task.py archive ${dirName}\` or start a new task`
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
let hasContext = false
|
|
@@ -94,18 +95,23 @@ function getTaskStatus(ctx) {
|
|
|
94
95
|
const hasPrd = existsSync(join(taskDir, "prd.md"))
|
|
95
96
|
|
|
96
97
|
if (!hasPrd) {
|
|
97
|
-
return `Status: NOT READY\nTask: ${taskTitle}\nMissing: prd.md not created\nNext: Write PRD (see workflow.md Phase 1.1) then curate implement.jsonl per Phase 1.3`
|
|
98
|
+
return `Status: NOT READY\nTask: ${taskTitle}\nSource: ${active.source}\nMissing: prd.md not created\nNext: Write PRD (see workflow.md Phase 1.1) then curate implement.jsonl per Phase 1.3`
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
if (!hasContext) {
|
|
101
|
-
return `Status: NOT READY\nTask: ${taskTitle}\nMissing: implement.jsonl / check.jsonl missing or empty\nNext: Curate entries per workflow.md Phase 1.3 (spec + research files only), then \`task.py start\``
|
|
102
|
+
return `Status: NOT READY\nTask: ${taskTitle}\nSource: ${active.source}\nMissing: implement.jsonl / check.jsonl missing or empty\nNext: Curate entries per workflow.md Phase 1.3 (spec + research files only), then \`task.py start\``
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
return (
|
|
105
106
|
`Status: READY\nTask: ${taskTitle}\n` +
|
|
107
|
+
`Source: ${active.source}\n` +
|
|
106
108
|
"Next required action: dispatch `trellis-implement` per Phase 2.1. " +
|
|
107
|
-
"For agent-capable platforms,
|
|
108
|
-
"After implementation, dispatch `trellis-check` per Phase 2.2 before reporting completion
|
|
109
|
+
"For agent-capable platforms, the default is to NOT edit code in the main session. " +
|
|
110
|
+
"After implementation, dispatch `trellis-check` per Phase 2.2 before reporting completion.\n" +
|
|
111
|
+
"User override (per-turn escape hatch): if the user's CURRENT message explicitly tells the " +
|
|
112
|
+
"main session to handle it directly (\"你直接改\" / \"别派 sub-agent\" / \"main session 写就行\" / " +
|
|
113
|
+
"\"do it inline\" / \"不用 sub-agent\"), honor it for this turn and edit code directly. " +
|
|
114
|
+
"Per-turn only; do NOT invent an override the user did not say."
|
|
109
115
|
)
|
|
110
116
|
}
|
|
111
117
|
|
|
@@ -113,7 +119,7 @@ function getTaskStatus(ctx) {
|
|
|
113
119
|
* Load Trellis config for session-start decisions.
|
|
114
120
|
* Calls get_context.py --mode packages --json for reliable config data.
|
|
115
121
|
*/
|
|
116
|
-
function loadTrellisConfig(directory) {
|
|
122
|
+
function loadTrellisConfig(directory, contextKey = null) {
|
|
117
123
|
const scriptPath = join(directory, ".trellis", "scripts", "get_context.py")
|
|
118
124
|
if (!existsSync(scriptPath)) {
|
|
119
125
|
return { isMonorepo: false, packages: {}, specScope: null, activeTaskPackage: null, defaultPackage: null }
|
|
@@ -124,6 +130,10 @@ function loadTrellisConfig(directory) {
|
|
|
124
130
|
timeout: 5000,
|
|
125
131
|
encoding: "utf-8",
|
|
126
132
|
stdio: ["pipe", "pipe", "pipe"],
|
|
133
|
+
env: {
|
|
134
|
+
...process.env,
|
|
135
|
+
...(contextKey ? { TRELLIS_CONTEXT_ID: contextKey } : {}),
|
|
136
|
+
},
|
|
127
137
|
})
|
|
128
138
|
const data = JSON.parse(output)
|
|
129
139
|
if (data.mode !== "monorepo") {
|
|
@@ -226,11 +236,14 @@ function resolveSpecScope(config) {
|
|
|
226
236
|
/**
|
|
227
237
|
* Build session context for injection
|
|
228
238
|
*/
|
|
229
|
-
export function buildSessionContext(ctx) {
|
|
239
|
+
export function buildSessionContext(ctx, platformInput = null) {
|
|
230
240
|
const directory = ctx.directory
|
|
231
241
|
const trellisDir = join(directory, ".trellis")
|
|
242
|
+
const contextKey = typeof ctx.getContextKey === "function"
|
|
243
|
+
? ctx.getContextKey(platformInput)
|
|
244
|
+
: null
|
|
232
245
|
|
|
233
|
-
const config = loadTrellisConfig(directory)
|
|
246
|
+
const config = loadTrellisConfig(directory, contextKey)
|
|
234
247
|
const allowedPkgs = resolveSpecScope(config)
|
|
235
248
|
|
|
236
249
|
const parts = []
|
|
@@ -251,7 +264,7 @@ Read and follow all instructions below carefully.
|
|
|
251
264
|
// 2. Current Context (dynamic)
|
|
252
265
|
const contextScript = join(trellisDir, "scripts", "get_context.py")
|
|
253
266
|
if (existsSync(contextScript)) {
|
|
254
|
-
const output = ctx.runScript(contextScript)
|
|
267
|
+
const output = ctx.runScript(contextScript, undefined, contextKey)
|
|
255
268
|
if (output) {
|
|
256
269
|
parts.push("<current-state>")
|
|
257
270
|
parts.push(output)
|
|
@@ -385,7 +398,7 @@ Read and follow all instructions below carefully.
|
|
|
385
398
|
parts.push("</guidelines>")
|
|
386
399
|
|
|
387
400
|
// 6. Task status
|
|
388
|
-
const taskStatus = getTaskStatus(ctx)
|
|
401
|
+
const taskStatus = getTaskStatus(ctx, platformInput)
|
|
389
402
|
parts.push(`<task-status>\n${taskStatus}\n</task-status>`)
|
|
390
403
|
|
|
391
404
|
// 7. Final directive
|
|
@@ -514,7 +527,7 @@ export default async ({ directory, client }) => {
|
|
|
514
527
|
}
|
|
515
528
|
|
|
516
529
|
// Build context
|
|
517
|
-
const context = buildSessionContext(ctx)
|
|
530
|
+
const context = buildSessionContext(ctx, input)
|
|
518
531
|
debugLog("session", "Built context, length:", context.length)
|
|
519
532
|
|
|
520
533
|
// Inject context directly into output.parts so it gets persisted by updatePart
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trellis-check
|
|
3
|
+
description: |
|
|
4
|
+
Code quality check expert. Reviews changes against Trellis specs, fixes issues directly, and verifies quality gates.
|
|
5
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
# Check Agent
|
|
8
|
+
|
|
9
|
+
You are the Check Agent in the Trellis workflow.
|
|
10
|
+
|
|
11
|
+
## Core Responsibilities
|
|
12
|
+
|
|
13
|
+
1. Inspect the current git diff.
|
|
14
|
+
2. Read and follow the spec and research files listed in the task's `check.jsonl`.
|
|
15
|
+
3. Review all changed code against the task PRD and project specs.
|
|
16
|
+
4. Fix issues directly when they are within scope.
|
|
17
|
+
5. Run the relevant lint, typecheck, and focused tests available for the touched code.
|
|
18
|
+
|
|
19
|
+
## Review Priorities
|
|
20
|
+
|
|
21
|
+
- Behavioral regressions and missing requirements.
|
|
22
|
+
- Spec or platform contract violations.
|
|
23
|
+
- Missing or weak tests for logic changes.
|
|
24
|
+
- Cross-platform path, command, and encoding assumptions.
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
Report findings fixed, files changed, and verification results. If no issues remain, say that clearly.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trellis-implement
|
|
3
|
+
description: |
|
|
4
|
+
Code implementation expert. Understands Trellis specs and requirements, then implements features. No git commit allowed.
|
|
5
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
# Implement Agent
|
|
8
|
+
|
|
9
|
+
You are the Implement Agent in the Trellis workflow.
|
|
10
|
+
|
|
11
|
+
## Core Responsibilities
|
|
12
|
+
|
|
13
|
+
1. Understand the active task requirements.
|
|
14
|
+
2. Read and follow the spec and research files listed in the task's `implement.jsonl`.
|
|
15
|
+
3. Implement the requested change using existing project patterns.
|
|
16
|
+
4. Run the relevant lint, typecheck, and focused tests available for the touched code.
|
|
17
|
+
5. Report files changed and verification results.
|
|
18
|
+
|
|
19
|
+
## Forbidden Operations
|
|
20
|
+
|
|
21
|
+
Do not run:
|
|
22
|
+
|
|
23
|
+
- `git commit`
|
|
24
|
+
- `git push`
|
|
25
|
+
- `git merge`
|
|
26
|
+
|
|
27
|
+
## Working Rules
|
|
28
|
+
|
|
29
|
+
- Read adjacent code and tests before editing.
|
|
30
|
+
- Keep changes scoped to the task.
|
|
31
|
+
- Do not revert unrelated user or concurrent changes.
|
|
32
|
+
- Fix root causes rather than masking symptoms.
|
|
33
|
+
- Prefer existing local helpers and platform patterns over new abstractions.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trellis-research
|
|
3
|
+
description: |
|
|
4
|
+
Code and technical research expert. Finds relevant files, patterns, docs, and persists findings to the current task's research/ directory.
|
|
5
|
+
tools: Read, Write, Bash, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
# Research Agent
|
|
8
|
+
|
|
9
|
+
You are the Research Agent in the Trellis workflow.
|
|
10
|
+
|
|
11
|
+
## Core Principle
|
|
12
|
+
|
|
13
|
+
Persist every finding to a file. Chat context is temporary; files under the task directory survive compaction and handoff.
|
|
14
|
+
|
|
15
|
+
## Core Responsibilities
|
|
16
|
+
|
|
17
|
+
1. Resolve the active task with `python3 ./.trellis/scripts/task.py current --source`.
|
|
18
|
+
2. Create `<task-dir>/research/` when it does not exist.
|
|
19
|
+
3. Search internal code, specs, and relevant external documentation.
|
|
20
|
+
4. Write each distinct topic to `<task-dir>/research/<topic-slug>.md`.
|
|
21
|
+
5. Report only file paths and concise summaries to the caller.
|
|
22
|
+
|
|
23
|
+
## Scope Limits
|
|
24
|
+
|
|
25
|
+
Write only under the current task's `research/` directory. Do not edit code, specs, platform config, or task files outside research artifacts.
|