@mindfoldhq/trellis 0.4.0-beta.8 → 0.4.0-rc.0
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 +10 -5
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +165 -13
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +14 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +2 -1
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts +9 -0
- package/dist/configurators/copilot.d.ts.map +1 -0
- package/dist/configurators/copilot.js +34 -0
- package/dist/configurators/copilot.js.map +1 -0
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +32 -1
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/windsurf.d.ts +8 -0
- package/dist/configurators/windsurf.d.ts.map +1 -0
- package/dist/configurators/windsurf.js +18 -0
- package/dist/configurators/windsurf.js.map +1 -0
- package/dist/migrations/manifests/0.4.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
- package/dist/migrations/manifests/0.4.0-rc.0.json +9 -0
- package/dist/templates/claude/hooks/inject-subagent-context.py +8 -1
- package/dist/templates/claude/hooks/ralph-loop.py +18 -10
- package/dist/templates/claude/hooks/session-start.py +60 -19
- package/dist/templates/claude/hooks/statusline.py +218 -0
- package/dist/templates/claude/settings.json +4 -0
- package/dist/templates/codex/hooks/session-start.py +60 -21
- package/dist/templates/codex/hooks.json +1 -1
- package/dist/templates/copilot/hooks/session-start.py +243 -0
- package/dist/templates/copilot/hooks.json +11 -0
- package/dist/templates/copilot/index.d.ts +23 -0
- package/dist/templates/copilot/index.d.ts.map +1 -0
- package/dist/templates/copilot/index.js +54 -0
- package/dist/templates/copilot/index.js.map +1 -0
- package/dist/templates/copilot/prompts/before-dev.prompt.md +33 -0
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +491 -0
- package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
- package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
- package/dist/templates/copilot/prompts/check.prompt.md +29 -0
- package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
- package/dist/templates/copilot/prompts/finish-work.prompt.md +157 -0
- package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
- package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
- package/dist/templates/copilot/prompts/parallel.prompt.md +196 -0
- package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
- package/dist/templates/copilot/prompts/start.prompt.md +397 -0
- package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
- package/dist/templates/extract.d.ts +18 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +32 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/iflow/hooks/inject-subagent-context.py +8 -1
- package/dist/templates/iflow/hooks/ralph-loop.py +8 -1
- package/dist/templates/iflow/hooks/session-start.py +60 -19
- package/dist/templates/markdown/spec/backend/directory-structure.md +1 -1
- package/dist/templates/opencode/agents/dispatch.md +20 -19
- package/dist/templates/opencode/lib/trellis-context.js +35 -239
- package/dist/templates/opencode/plugins/inject-subagent-context.js +71 -121
- package/dist/templates/opencode/plugins/session-start.js +150 -146
- package/dist/templates/trellis/scripts/add_session.py +6 -1
- package/dist/templates/trellis/scripts/common/__init__.py +2 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +87 -9
- package/dist/templates/trellis/scripts/common/paths.py +57 -6
- package/dist/templates/trellis/scripts/common/task_store.py +6 -4
- package/dist/templates/trellis/scripts/common/task_utils.py +14 -8
- package/dist/templates/trellis/scripts/multi_agent/start.py +9 -5
- package/dist/templates/trellis/scripts/task.py +1 -1
- package/dist/templates/trellis/workflow.md +17 -4
- package/dist/templates/windsurf/index.d.ts +21 -0
- package/dist/templates/windsurf/index.d.ts.map +1 -0
- package/dist/templates/windsurf/index.js +44 -0
- package/dist/templates/windsurf/index.js.map +1 -0
- package/dist/templates/windsurf/workflows/trellis-before-dev.md +31 -0
- package/dist/templates/windsurf/workflows/trellis-brainstorm.md +491 -0
- package/dist/templates/windsurf/workflows/trellis-break-loop.md +111 -0
- package/dist/templates/windsurf/workflows/trellis-check-cross-layer.md +157 -0
- package/dist/templates/windsurf/workflows/trellis-check.md +27 -0
- package/dist/templates/windsurf/workflows/trellis-create-command.md +154 -0
- package/dist/templates/windsurf/workflows/trellis-finish-work.md +147 -0
- package/dist/templates/windsurf/workflows/trellis-integrate-skill.md +220 -0
- package/dist/templates/windsurf/workflows/trellis-onboard.md +362 -0
- package/dist/templates/windsurf/workflows/trellis-record-session.md +66 -0
- package/dist/templates/windsurf/workflows/trellis-start.md +373 -0
- package/dist/templates/windsurf/workflows/trellis-update-spec.md +358 -0
- package/dist/types/ai-tools.d.ts +5 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +21 -1
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +17 -4
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +94 -12
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,15 +3,11 @@
|
|
|
3
3
|
* Trellis Session Start Plugin
|
|
4
4
|
*
|
|
5
5
|
* Injects context when user sends the first message in a session.
|
|
6
|
-
* Uses OpenCode's chat.message
|
|
7
|
-
*
|
|
8
|
-
* Compatibility:
|
|
9
|
-
* - If oh-my-opencode handles via .claude/hooks/, this plugin skips
|
|
10
|
-
* - Otherwise, this plugin handles injection
|
|
6
|
+
* Uses OpenCode's chat.message hook directly so the context persists in history.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
9
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs"
|
|
14
|
-
import { join } from "path"
|
|
10
|
+
import { basename, join } from "path"
|
|
15
11
|
import { execFileSync } from "child_process"
|
|
16
12
|
import { platform } from "os"
|
|
17
13
|
import { TrellisContext, contextCollector, debugLog } from "../lib/trellis-context.js"
|
|
@@ -23,40 +19,18 @@ const PYTHON_CMD = platform() === "win32" ? "python" : "python3"
|
|
|
23
19
|
* Check current task status and return structured status string.
|
|
24
20
|
* JavaScript equivalent of _get_task_status in Claude's session-start.py.
|
|
25
21
|
*/
|
|
26
|
-
function getTaskStatus(
|
|
27
|
-
const
|
|
28
|
-
const currentTaskFile = join(trellisDir, ".current-task")
|
|
29
|
-
|
|
30
|
-
if (!existsSync(currentTaskFile)) {
|
|
31
|
-
return "Status: NO ACTIVE TASK\nNext: Describe what you want to work on"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let taskRef
|
|
35
|
-
try {
|
|
36
|
-
taskRef = readFileSync(currentTaskFile, "utf-8").trim()
|
|
37
|
-
} catch {
|
|
38
|
-
return "Status: NO ACTIVE TASK\nNext: Describe what you want to work on"
|
|
39
|
-
}
|
|
40
|
-
|
|
22
|
+
function getTaskStatus(ctx) {
|
|
23
|
+
const taskRef = ctx.getCurrentTask()
|
|
41
24
|
if (!taskRef) {
|
|
42
25
|
return "Status: NO ACTIVE TASK\nNext: Describe what you want to work on"
|
|
43
26
|
}
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
let taskDir
|
|
47
|
-
if (taskRef.startsWith("/")) {
|
|
48
|
-
taskDir = taskRef
|
|
49
|
-
} else if (taskRef.startsWith(".trellis/")) {
|
|
50
|
-
taskDir = join(directory, taskRef)
|
|
51
|
-
} else {
|
|
52
|
-
taskDir = join(trellisDir, "tasks", taskRef)
|
|
53
|
-
}
|
|
28
|
+
const taskDir = ctx.resolveTaskDir(taskRef)
|
|
54
29
|
|
|
55
|
-
if (!existsSync(taskDir)) {
|
|
30
|
+
if (!taskDir || !existsSync(taskDir)) {
|
|
56
31
|
return `Status: STALE POINTER\nTask: ${taskRef}\nNext: Task directory not found. Run: python3 ./.trellis/scripts/task.py finish`
|
|
57
32
|
}
|
|
58
33
|
|
|
59
|
-
// Read task.json
|
|
60
34
|
let taskData = {}
|
|
61
35
|
const taskJsonPath = join(taskDir, "task.json")
|
|
62
36
|
if (existsSync(taskJsonPath)) {
|
|
@@ -71,11 +45,10 @@ function getTaskStatus(directory) {
|
|
|
71
45
|
const taskStatus = taskData.status || "unknown"
|
|
72
46
|
|
|
73
47
|
if (taskStatus === "completed") {
|
|
74
|
-
const dirName = taskDir
|
|
48
|
+
const dirName = basename(taskDir)
|
|
75
49
|
return `Status: COMPLETED\nTask: ${taskTitle}\nNext: Archive with \`python3 ./.trellis/scripts/task.py archive ${dirName}\` or start a new task`
|
|
76
50
|
}
|
|
77
51
|
|
|
78
|
-
// Check if context is configured (jsonl files exist and non-empty)
|
|
79
52
|
let hasContext = false
|
|
80
53
|
for (const jsonlName of ["implement.jsonl", "check.jsonl", "spec.jsonl"]) {
|
|
81
54
|
const jsonlPath = join(taskDir, jsonlName)
|
|
@@ -125,7 +98,6 @@ function loadTrellisConfig(directory) {
|
|
|
125
98
|
if (data.mode !== "monorepo") {
|
|
126
99
|
return { isMonorepo: false, packages: {}, specScope: null, activeTaskPackage: null, defaultPackage: null }
|
|
127
100
|
}
|
|
128
|
-
// Convert packages array to dict keyed by name
|
|
129
101
|
const pkgDict = {}
|
|
130
102
|
for (const pkg of (data.packages || [])) {
|
|
131
103
|
pkgDict[pkg.name] = pkg
|
|
@@ -155,7 +127,6 @@ function checkLegacySpec(directory, config) {
|
|
|
155
127
|
const specDir = join(directory, ".trellis", "spec")
|
|
156
128
|
if (!existsSync(specDir)) return null
|
|
157
129
|
|
|
158
|
-
// Check for legacy flat spec dirs
|
|
159
130
|
let hasLegacy = false
|
|
160
131
|
for (const name of ["backend", "frontend"]) {
|
|
161
132
|
if (existsSync(join(specDir, name, "index.md"))) {
|
|
@@ -165,7 +136,6 @@ function checkLegacySpec(directory, config) {
|
|
|
165
136
|
}
|
|
166
137
|
if (!hasLegacy) return null
|
|
167
138
|
|
|
168
|
-
// Check which packages are missing spec/<pkg>/ directory
|
|
169
139
|
const pkgNames = Object.keys(config.packages).sort()
|
|
170
140
|
const missing = pkgNames.filter(name => !existsSync(join(specDir, name)))
|
|
171
141
|
|
|
@@ -213,7 +183,6 @@ function resolveSpecScope(config) {
|
|
|
213
183
|
}
|
|
214
184
|
}
|
|
215
185
|
if (valid.size > 0) return valid
|
|
216
|
-
// All invalid: fallback
|
|
217
186
|
if (activeTaskPackage && activeTaskPackage in packages) return new Set([activeTaskPackage])
|
|
218
187
|
if (defaultPackage && defaultPackage in packages) return new Set([defaultPackage])
|
|
219
188
|
return null
|
|
@@ -229,10 +198,7 @@ function resolveSpecScope(config) {
|
|
|
229
198
|
function buildSessionContext(ctx) {
|
|
230
199
|
const directory = ctx.directory
|
|
231
200
|
const trellisDir = join(directory, ".trellis")
|
|
232
|
-
const claudeDir = join(directory, ".claude")
|
|
233
|
-
const opencodeDir = join(directory, ".opencode")
|
|
234
201
|
|
|
235
|
-
// Load config for scope filtering and legacy detection
|
|
236
202
|
const config = loadTrellisConfig(directory)
|
|
237
203
|
const allowedPkgs = resolveSpecScope(config)
|
|
238
204
|
|
|
@@ -261,11 +227,20 @@ Read and follow all instructions below carefully.
|
|
|
261
227
|
}
|
|
262
228
|
}
|
|
263
229
|
|
|
264
|
-
// 3. Workflow Guide
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
230
|
+
// 3. Workflow Guide (ToC only — lazy-load the full file on demand)
|
|
231
|
+
const workflowContent = ctx.readProjectFile(".trellis/workflow.md")
|
|
232
|
+
if (workflowContent) {
|
|
233
|
+
const tocLines = [
|
|
234
|
+
"# Development Workflow — Section Index",
|
|
235
|
+
"Full guide: .trellis/workflow.md (read on demand)",
|
|
236
|
+
"",
|
|
237
|
+
]
|
|
238
|
+
for (const line of workflowContent.split("\n")) {
|
|
239
|
+
if (line.startsWith("## ")) tocLines.push(line)
|
|
240
|
+
}
|
|
241
|
+
tocLines.push("", "To read a section: use the Read tool on .trellis/workflow.md")
|
|
267
242
|
parts.push("<workflow>")
|
|
268
|
-
parts.push(
|
|
243
|
+
parts.push(tocLines.join("\n"))
|
|
269
244
|
parts.push("</workflow>")
|
|
270
245
|
}
|
|
271
246
|
|
|
@@ -287,7 +262,6 @@ Read and follow all instructions below carefully.
|
|
|
287
262
|
}).sort()
|
|
288
263
|
|
|
289
264
|
for (const sub of subs) {
|
|
290
|
-
// Always include guides/ regardless of scope
|
|
291
265
|
if (sub === "guides") {
|
|
292
266
|
const indexFile = join(specDir, sub, "index.md")
|
|
293
267
|
if (existsSync(indexFile)) {
|
|
@@ -301,14 +275,11 @@ Read and follow all instructions below carefully.
|
|
|
301
275
|
|
|
302
276
|
const indexFile = join(specDir, sub, "index.md")
|
|
303
277
|
if (existsSync(indexFile)) {
|
|
304
|
-
// Flat spec dir: spec/<layer>/index.md (single-repo)
|
|
305
278
|
const content = ctx.readFile(indexFile)
|
|
306
279
|
if (content) {
|
|
307
280
|
parts.push(`## ${sub}\n${content}\n`)
|
|
308
281
|
}
|
|
309
282
|
} else {
|
|
310
|
-
// Nested package dirs (monorepo): spec/<pkg>/<layer>/index.md
|
|
311
|
-
// Apply scope filter
|
|
312
283
|
if (allowedPkgs !== null && !allowedPkgs.has(sub)) {
|
|
313
284
|
continue
|
|
314
285
|
}
|
|
@@ -342,131 +313,164 @@ Read and follow all instructions below carefully.
|
|
|
342
313
|
|
|
343
314
|
parts.push("</guidelines>")
|
|
344
315
|
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
if (!startMd) {
|
|
348
|
-
startMd = ctx.readFile(join(opencodeDir, "commands", "trellis", "start.md"))
|
|
349
|
-
}
|
|
350
|
-
if (startMd) {
|
|
351
|
-
parts.push("<instructions>")
|
|
352
|
-
parts.push(startMd)
|
|
353
|
-
parts.push("</instructions>")
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// 6. Task status (R2: check task state for session resume)
|
|
357
|
-
const taskStatus = getTaskStatus(directory)
|
|
316
|
+
// 6. Task status
|
|
317
|
+
const taskStatus = getTaskStatus(ctx)
|
|
358
318
|
parts.push(`<task-status>\n${taskStatus}\n</task-status>`)
|
|
359
319
|
|
|
360
|
-
// 7. Final directive
|
|
320
|
+
// 7. Final directive
|
|
361
321
|
parts.push(`<ready>
|
|
362
|
-
Context loaded.
|
|
363
|
-
|
|
322
|
+
Context loaded. Workflow index, project state, and guidelines are already injected above — do NOT re-read them.
|
|
323
|
+
Wait for the user's first message, then handle it following the workflow guide.
|
|
364
324
|
If there is an active task, ask whether to continue it.
|
|
365
325
|
</ready>`)
|
|
366
326
|
|
|
367
327
|
return parts.join("\n\n")
|
|
368
328
|
}
|
|
369
329
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
330
|
+
function getTrellisMetadata(metadata) {
|
|
331
|
+
if (!metadata || typeof metadata !== "object") {
|
|
332
|
+
return {}
|
|
333
|
+
}
|
|
373
334
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const sessionID = input.sessionID
|
|
379
|
-
const agent = input.agent || "unknown"
|
|
380
|
-
debugLog("session", "chat.message called, sessionID:", sessionID, "agent:", agent)
|
|
381
|
-
|
|
382
|
-
// Skip in non-interactive mode
|
|
383
|
-
if (process.env.OPENCODE_NON_INTERACTIVE === "1") {
|
|
384
|
-
debugLog("session", "Skipping - non-interactive mode")
|
|
385
|
-
return
|
|
386
|
-
}
|
|
335
|
+
const trellis = metadata.trellis
|
|
336
|
+
if (!trellis || typeof trellis !== "object") {
|
|
337
|
+
return {}
|
|
338
|
+
}
|
|
387
339
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
debugLog("session", "Skipping - omo will handle via .claude/hooks/")
|
|
391
|
-
return
|
|
392
|
-
}
|
|
340
|
+
return trellis
|
|
341
|
+
}
|
|
393
342
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
343
|
+
function markPartAsSessionStart(part) {
|
|
344
|
+
const metadata = part.metadata && typeof part.metadata === "object"
|
|
345
|
+
? part.metadata
|
|
346
|
+
: {}
|
|
347
|
+
part.metadata = {
|
|
348
|
+
...metadata,
|
|
349
|
+
trellis: {
|
|
350
|
+
...getTrellisMetadata(metadata),
|
|
351
|
+
sessionStart: true,
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
}
|
|
399
355
|
|
|
400
|
-
|
|
401
|
-
|
|
356
|
+
function hasSessionStartMarker(part) {
|
|
357
|
+
if (!part || part.type !== "text" || typeof part.text !== "string") {
|
|
358
|
+
return false
|
|
359
|
+
}
|
|
402
360
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
debugLog("session", "Built context, length:", context.length)
|
|
361
|
+
return getTrellisMetadata(part.metadata).sessionStart === true
|
|
362
|
+
}
|
|
406
363
|
|
|
407
|
-
|
|
408
|
-
|
|
364
|
+
export function hasInjectedTrellisContext(messages) {
|
|
365
|
+
if (!Array.isArray(messages)) {
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
409
368
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
369
|
+
return messages.some(message => {
|
|
370
|
+
if (!message?.info || message.info.role !== "user" || !Array.isArray(message.parts)) {
|
|
371
|
+
return false
|
|
372
|
+
}
|
|
414
373
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const { messages } = output
|
|
419
|
-
debugLog("session", "messages.transform called, messageCount:", messages?.length)
|
|
374
|
+
return message.parts.some(hasSessionStartMarker)
|
|
375
|
+
})
|
|
376
|
+
}
|
|
420
377
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
378
|
+
async function hasPersistedInjectedContext(client, directory, sessionID) {
|
|
379
|
+
try {
|
|
380
|
+
const response = await client.session.messages({
|
|
381
|
+
path: { id: sessionID },
|
|
382
|
+
query: { directory },
|
|
383
|
+
throwOnError: true,
|
|
384
|
+
})
|
|
385
|
+
return hasInjectedTrellisContext(response.data || [])
|
|
386
|
+
} catch (error) {
|
|
387
|
+
debugLog(
|
|
388
|
+
"session",
|
|
389
|
+
"Failed to read session history for dedupe:",
|
|
390
|
+
error instanceof Error ? error.message : String(error),
|
|
391
|
+
)
|
|
392
|
+
return false
|
|
393
|
+
}
|
|
394
|
+
}
|
|
424
395
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
break
|
|
431
|
-
}
|
|
432
|
-
}
|
|
396
|
+
export default {
|
|
397
|
+
id: "trellis.session-start",
|
|
398
|
+
server: async ({ directory, client }) => {
|
|
399
|
+
const ctx = new TrellisContext(directory)
|
|
400
|
+
debugLog("session", "Plugin loaded, directory:", directory)
|
|
433
401
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
402
|
+
return {
|
|
403
|
+
// Clear in-memory dedupe after compaction so context can be re-injected.
|
|
404
|
+
event: ({ event }) => {
|
|
405
|
+
try {
|
|
406
|
+
if (event?.type === "session.compacted" && event?.properties?.sessionID) {
|
|
407
|
+
const sessionID = event.properties.sessionID
|
|
408
|
+
contextCollector.clear(sessionID)
|
|
409
|
+
debugLog("session", "Cleared processed flag after compaction for session:", sessionID)
|
|
410
|
+
}
|
|
411
|
+
} catch (error) {
|
|
412
|
+
debugLog(
|
|
413
|
+
"session",
|
|
414
|
+
"Error in event hook:",
|
|
415
|
+
error instanceof Error ? error.message : String(error),
|
|
416
|
+
)
|
|
437
417
|
}
|
|
418
|
+
},
|
|
438
419
|
|
|
439
|
-
|
|
440
|
-
|
|
420
|
+
// chat.message - triggered when user sends a message.
|
|
421
|
+
// Modify the message in-place so the context is persisted with updateMessage/updatePart.
|
|
422
|
+
"chat.message": async (input, output) => {
|
|
423
|
+
try {
|
|
424
|
+
const sessionID = input.sessionID
|
|
425
|
+
const agent = input.agent || "unknown"
|
|
426
|
+
debugLog("session", "chat.message called, sessionID:", sessionID, "agent:", agent)
|
|
427
|
+
|
|
428
|
+
// Skip in non-interactive mode
|
|
429
|
+
if (process.env.OPENCODE_NON_INTERACTIVE === "1") {
|
|
430
|
+
debugLog("session", "Skipping - non-interactive mode")
|
|
431
|
+
return
|
|
432
|
+
}
|
|
441
433
|
|
|
442
|
-
|
|
434
|
+
// Only inject on first message
|
|
435
|
+
if (contextCollector.isProcessed(sessionID)) {
|
|
436
|
+
debugLog("session", "Skipping - session already processed")
|
|
437
|
+
return
|
|
438
|
+
}
|
|
443
439
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
440
|
+
if (await hasPersistedInjectedContext(client, ctx.directory, sessionID)) {
|
|
441
|
+
contextCollector.markProcessed(sessionID)
|
|
442
|
+
debugLog("session", "Skipping - session already contains persisted Trellis context")
|
|
443
|
+
return
|
|
444
|
+
}
|
|
448
445
|
|
|
449
|
-
|
|
450
|
-
|
|
446
|
+
// Build context
|
|
447
|
+
const context = buildSessionContext(ctx)
|
|
448
|
+
debugLog("session", "Built context, length:", context.length)
|
|
449
|
+
|
|
450
|
+
// Inject context directly into output.parts so it gets persisted by updatePart
|
|
451
|
+
const parts = output?.parts || []
|
|
452
|
+
const textPartIndex = parts.findIndex(
|
|
453
|
+
p => p.type === "text" && p.text !== undefined
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
if (textPartIndex !== -1) {
|
|
457
|
+
const originalText = parts[textPartIndex].text || ""
|
|
458
|
+
parts[textPartIndex].text = `${context}\n\n---\n\n${originalText}`
|
|
459
|
+
markPartAsSessionStart(parts[textPartIndex])
|
|
460
|
+
debugLog("session", "Injected context into chat.message text part, length:", context.length)
|
|
461
|
+
} else {
|
|
462
|
+
// No existing text part: prepend a new one
|
|
463
|
+
const injectedPart = { type: "text", text: context }
|
|
464
|
+
markPartAsSessionStart(injectedPart)
|
|
465
|
+
parts.unshift(injectedPart)
|
|
466
|
+
debugLog("session", "Prepended new text part with context, length:", context.length)
|
|
467
|
+
}
|
|
451
468
|
|
|
452
|
-
|
|
453
|
-
const textPartIndex = lastUserMessage.parts?.findIndex(
|
|
454
|
-
p => p.type === "text" && p.text !== undefined
|
|
455
|
-
)
|
|
469
|
+
contextCollector.markProcessed(sessionID)
|
|
456
470
|
|
|
457
|
-
|
|
458
|
-
debugLog("session", "
|
|
459
|
-
return
|
|
471
|
+
} catch (error) {
|
|
472
|
+
debugLog("session", "Error in chat.message:", error.message, error.stack)
|
|
460
473
|
}
|
|
461
|
-
|
|
462
|
-
// Prepend context to the text part (same approach as omo)
|
|
463
|
-
const originalText = lastUserMessage.parts[textPartIndex].text || ""
|
|
464
|
-
lastUserMessage.parts[textPartIndex].text = `${pending.content}\n\n---\n\n${originalText}`
|
|
465
|
-
|
|
466
|
-
debugLog("session", "Injected context by prepending to text, length:", pending.content.length)
|
|
467
|
-
|
|
468
|
-
} catch (error) {
|
|
469
|
-
debugLog("session", "Error in messages.transform:", error.message, error.stack)
|
|
470
474
|
}
|
|
471
475
|
}
|
|
472
476
|
}
|
|
@@ -316,11 +316,16 @@ def update_index(
|
|
|
316
316
|
def _auto_commit_workspace(repo_root: Path) -> None:
|
|
317
317
|
"""Stage .trellis/workspace and .trellis/tasks, then commit with a configured message."""
|
|
318
318
|
commit_msg = get_session_commit_message(repo_root)
|
|
319
|
-
subprocess.run(
|
|
319
|
+
add_result = subprocess.run(
|
|
320
320
|
["git", "add", "-A", ".trellis/workspace", ".trellis/tasks"],
|
|
321
321
|
cwd=repo_root,
|
|
322
322
|
capture_output=True,
|
|
323
|
+
text=True,
|
|
323
324
|
)
|
|
325
|
+
if add_result.returncode != 0:
|
|
326
|
+
print(f"[WARN] git add failed (exit {add_result.returncode}): {add_result.stderr.strip()}", file=sys.stderr)
|
|
327
|
+
print("[WARN] Please commit .trellis/ changes manually: git add .trellis && git commit", file=sys.stderr)
|
|
328
|
+
return
|
|
324
329
|
# Check if there are staged changes
|
|
325
330
|
result = subprocess.run(
|
|
326
331
|
["git", "diff", "--cached", "--quiet", "--", ".trellis/workspace", ".trellis/tasks"],
|