@mindfoldhq/trellis 0.4.0-beta.7 → 0.4.0-beta.9

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.
Files changed (121) hide show
  1. package/README.md +10 -5
  2. package/dist/cli/index.js +2 -0
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/init.d.ts +2 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +34 -10
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +33 -2
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/configurators/codex.d.ts +7 -4
  12. package/dist/configurators/codex.d.ts.map +1 -1
  13. package/dist/configurators/codex.js +40 -10
  14. package/dist/configurators/codex.js.map +1 -1
  15. package/dist/configurators/copilot.d.ts +9 -0
  16. package/dist/configurators/copilot.d.ts.map +1 -0
  17. package/dist/configurators/copilot.js +34 -0
  18. package/dist/configurators/copilot.js.map +1 -0
  19. package/dist/configurators/index.d.ts +11 -1
  20. package/dist/configurators/index.d.ts.map +1 -1
  21. package/dist/configurators/index.js +59 -4
  22. package/dist/configurators/index.js.map +1 -1
  23. package/dist/configurators/windsurf.d.ts +8 -0
  24. package/dist/configurators/windsurf.d.ts.map +1 -0
  25. package/dist/configurators/windsurf.js +18 -0
  26. package/dist/configurators/windsurf.js.map +1 -0
  27. package/dist/migrations/manifests/0.4.0-beta.8.json +34 -0
  28. package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
  29. package/dist/templates/claude/commands/trellis/record-session.md +2 -1
  30. package/dist/templates/claude/hooks/inject-subagent-context.py +8 -1
  31. package/dist/templates/claude/hooks/ralph-loop.py +8 -1
  32. package/dist/templates/claude/hooks/session-start.py +31 -7
  33. package/dist/templates/claude/hooks/statusline.py +211 -0
  34. package/dist/templates/claude/settings.json +4 -0
  35. package/dist/templates/codex/agents/check.toml +23 -0
  36. package/dist/templates/codex/agents/implement.toml +19 -0
  37. package/dist/templates/codex/agents/research.toml +26 -0
  38. package/dist/templates/codex/codex-skills/parallel/SKILL.md +194 -0
  39. package/dist/templates/codex/config.toml +5 -0
  40. package/dist/templates/codex/hooks/session-start.py +228 -0
  41. package/dist/templates/codex/hooks.json +16 -0
  42. package/dist/templates/codex/index.d.ts +27 -5
  43. package/dist/templates/codex/index.d.ts.map +1 -1
  44. package/dist/templates/codex/index.js +60 -8
  45. package/dist/templates/codex/index.js.map +1 -1
  46. package/dist/templates/codex/skills/improve-ut/SKILL.md +69 -0
  47. package/dist/templates/codex/skills/record-session/SKILL.md +2 -1
  48. package/dist/templates/copilot/hooks/session-start.py +218 -0
  49. package/dist/templates/copilot/hooks.json +11 -0
  50. package/dist/templates/copilot/index.d.ts +23 -0
  51. package/dist/templates/copilot/index.d.ts.map +1 -0
  52. package/dist/templates/copilot/index.js +54 -0
  53. package/dist/templates/copilot/index.js.map +1 -0
  54. package/dist/templates/copilot/prompts/before-dev.prompt.md +33 -0
  55. package/dist/templates/copilot/prompts/brainstorm.prompt.md +491 -0
  56. package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
  57. package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
  58. package/dist/templates/copilot/prompts/check.prompt.md +29 -0
  59. package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
  60. package/dist/templates/copilot/prompts/finish-work.prompt.md +157 -0
  61. package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
  62. package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
  63. package/dist/templates/copilot/prompts/parallel.prompt.md +196 -0
  64. package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
  65. package/dist/templates/copilot/prompts/start.prompt.md +397 -0
  66. package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
  67. package/dist/templates/cursor/commands/trellis-record-session.md +2 -1
  68. package/dist/templates/extract.d.ts +29 -0
  69. package/dist/templates/extract.d.ts.map +1 -1
  70. package/dist/templates/extract.js +51 -0
  71. package/dist/templates/extract.js.map +1 -1
  72. package/dist/templates/gemini/commands/trellis/record-session.toml +2 -1
  73. package/dist/templates/iflow/commands/trellis/record-session.md +2 -1
  74. package/dist/templates/iflow/hooks/inject-subagent-context.py +8 -1
  75. package/dist/templates/iflow/hooks/ralph-loop.py +8 -1
  76. package/dist/templates/iflow/hooks/session-start.py +31 -7
  77. package/dist/templates/kilo/workflows/record-session.md +2 -1
  78. package/dist/templates/kiro/skills/record-session/SKILL.md +2 -1
  79. package/dist/templates/markdown/agents.md +4 -0
  80. package/dist/templates/markdown/spec/backend/directory-structure.md +1 -1
  81. package/dist/templates/markdown/workspace-index.md +2 -0
  82. package/dist/templates/opencode/agents/dispatch.md +20 -19
  83. package/dist/templates/opencode/commands/trellis/record-session.md +2 -1
  84. package/dist/templates/opencode/lib/trellis-context.js +42 -2
  85. package/dist/templates/opencode/plugins/session-start.js +7 -27
  86. package/dist/templates/qoder/skills/record-session/SKILL.md +2 -1
  87. package/dist/templates/trellis/scripts/add_session.py +69 -16
  88. package/dist/templates/trellis/scripts/common/__init__.py +2 -0
  89. package/dist/templates/trellis/scripts/common/cli_adapter.py +108 -16
  90. package/dist/templates/trellis/scripts/common/developer.py +2 -2
  91. package/dist/templates/trellis/scripts/common/paths.py +57 -6
  92. package/dist/templates/trellis/scripts/common/task_store.py +6 -4
  93. package/dist/templates/trellis/scripts/common/task_utils.py +14 -8
  94. package/dist/templates/trellis/scripts/multi_agent/plan.py +7 -6
  95. package/dist/templates/trellis/scripts/multi_agent/start.py +16 -11
  96. package/dist/templates/trellis/scripts/task.py +1 -1
  97. package/dist/templates/windsurf/index.d.ts +21 -0
  98. package/dist/templates/windsurf/index.d.ts.map +1 -0
  99. package/dist/templates/windsurf/index.js +44 -0
  100. package/dist/templates/windsurf/index.js.map +1 -0
  101. package/dist/templates/windsurf/workflows/trellis-before-dev.md +31 -0
  102. package/dist/templates/windsurf/workflows/trellis-brainstorm.md +491 -0
  103. package/dist/templates/windsurf/workflows/trellis-break-loop.md +111 -0
  104. package/dist/templates/windsurf/workflows/trellis-check-cross-layer.md +157 -0
  105. package/dist/templates/windsurf/workflows/trellis-check.md +27 -0
  106. package/dist/templates/windsurf/workflows/trellis-create-command.md +154 -0
  107. package/dist/templates/windsurf/workflows/trellis-finish-work.md +147 -0
  108. package/dist/templates/windsurf/workflows/trellis-integrate-skill.md +220 -0
  109. package/dist/templates/windsurf/workflows/trellis-onboard.md +362 -0
  110. package/dist/templates/windsurf/workflows/trellis-record-session.md +66 -0
  111. package/dist/templates/windsurf/workflows/trellis-start.md +373 -0
  112. package/dist/templates/windsurf/workflows/trellis-update-spec.md +358 -0
  113. package/dist/types/ai-tools.d.ts +15 -3
  114. package/dist/types/ai-tools.d.ts.map +1 -1
  115. package/dist/types/ai-tools.js +34 -2
  116. package/dist/types/ai-tools.js.map +1 -1
  117. package/dist/utils/template-fetcher.d.ts +17 -4
  118. package/dist/utils/template-fetcher.d.ts.map +1 -1
  119. package/dist/utils/template-fetcher.js +94 -12
  120. package/dist/utils/template-fetcher.js.map +1 -1
  121. package/package.json +1 -1
@@ -51,6 +51,7 @@ EOF
51
51
  **Auto-completes**:
52
52
  - [OK] Appends session to journal-N.md
53
53
  - [OK] Auto-detects line count, creates new file if >2000 lines
54
+ - [OK] Auto-detects Branch context (`--branch` override; otherwise Branch = task.json -> current git branch; missing values are omitted gracefully)
54
55
  - [OK] Updates index.md (Total Sessions +1, Last Active, line stats, history)
55
56
  - [OK] Auto-commits .trellis/workspace and .trellis/tasks changes
56
57
 
@@ -61,6 +62,6 @@ EOF
61
62
  | Command | Purpose |
62
63
  |---------|---------|
63
64
  | `python3 ./.trellis/scripts/get_context.py --mode record` | Get context for record-session |
64
- | `python3 ./.trellis/scripts/add_session.py --title "..." --commit "..."` | **One-click add session (recommended)** |
65
+ | `python3 ./.trellis/scripts/add_session.py --title "..." --commit "..."` | **One-click add session (recommended, branch auto-complete)** |
65
66
  | `python3 ./.trellis/scripts/task.py archive <name>` | Archive completed task (auto-commits) |
66
67
  | `python3 ./.trellis/scripts/task.py list` | List active tasks |
@@ -13,6 +13,10 @@ Use `@/.trellis/` to learn:
13
13
  - Project structure guidelines (`spec/`)
14
14
  - Developer workspace (`workspace/`)
15
15
 
16
+ If you're using Codex, project-scoped helpers may also live in:
17
+ - `.agents/skills/` for reusable Trellis skills
18
+ - `.codex/agents/` for optional custom subagents
19
+
16
20
  Keep this managed block so 'trellis update' can refresh the instructions.
17
21
 
18
22
  <!-- TRELLIS:END -->
@@ -239,7 +239,7 @@ Templates use `.txt` extension to:
239
239
  ```typescript
240
240
  import { downloadTemplate } from "giget";
241
241
 
242
- await downloadTemplate("gh:mindfold-ai/Trellis/marketplace/specs/electron-fullstack", {
242
+ await downloadTemplate("gh:mindfold-ai/marketplace/specs/electron-fullstack", {
243
243
  dir: destDir,
244
244
  preferOffline: true,
245
245
  });
@@ -73,6 +73,7 @@ This will:
73
73
 
74
74
  Each session should include:
75
75
  - Summary: One-line description
76
+ - Branch: Which branch the work was done on
76
77
  - Main Changes: What was modified
77
78
  - Git Commits: Commit hashes and messages
78
79
  - Next Steps: What to do next
@@ -88,6 +89,7 @@ Use this template when recording sessions:
88
89
 
89
90
  **Date**: YYYY-MM-DD
90
91
  **Task**: {task-name}
92
+ **Branch**: `{branch-name}`
91
93
 
92
94
  ### Summary
93
95
 
@@ -71,6 +71,10 @@ Execute each step in `phase` order.
71
71
 
72
72
  > Hook will auto-inject all specs, requirements, and technical design to subagent context.
73
73
  > Dispatch only needs to issue simple call commands.
74
+ >
75
+ > **OpenCode dispatch rule**: Call subagents synchronously (`run_in_background: false`).
76
+ > Do NOT use `TaskOutput` or background polling as the completion signal for child phases.
77
+ > The background wrapper can finish before the real subagent session is actually done.
74
78
 
75
79
  ### action: "implement"
76
80
 
@@ -79,7 +83,7 @@ Task(
79
83
  subagent_type: "implement",
80
84
  prompt: "Implement the feature described in prd.md in the task directory",
81
85
  model: "opus",
82
- run_in_background: true
86
+ run_in_background: false
83
87
  )
84
88
  ```
85
89
 
@@ -98,7 +102,7 @@ Task(
98
102
  subagent_type: "check",
99
103
  prompt: "Check code changes, fix issues yourself",
100
104
  model: "opus",
101
- run_in_background: true
105
+ run_in_background: false
102
106
  )
103
107
  ```
104
108
 
@@ -116,7 +120,7 @@ Task(
116
120
  subagent_type: "debug",
117
121
  prompt: "Fix the issues described in the task context",
118
122
  model: "opus",
119
- run_in_background: true
123
+ run_in_background: false
120
124
  )
121
125
  ```
122
126
 
@@ -132,7 +136,7 @@ Task(
132
136
  subagent_type: "check",
133
137
  prompt: "[finish] Execute final completion check before PR",
134
138
  model: "opus",
135
- run_in_background: true
139
+ run_in_background: false
136
140
  )
137
141
  ```
138
142
 
@@ -168,27 +172,23 @@ This will:
168
172
  ### Basic Pattern
169
173
 
170
174
  ```
171
- task_id = Task(
175
+ result = Task(
172
176
  subagent_type: "implement", // or "check", "debug"
173
177
  prompt: "Simple task description",
174
178
  model: "opus",
175
- run_in_background: true
179
+ run_in_background: false
176
180
  )
177
181
 
178
- // Poll for completion
179
- for i in 1..N:
180
- result = TaskOutput(task_id, block=true, timeout=300000)
181
- if result.status == "completed":
182
- break
182
+ // Wait for the Task call to return before starting the next phase.
183
+ // Do NOT call TaskOutput or use background polling inside OpenCode dispatch.
183
184
  ```
184
185
 
185
- ### Timeout Settings
186
+ ### Execution Rule
186
187
 
187
- | Phase | Max Time | Poll Count |
188
- |-------|----------|------------|
189
- | implement | 30 min | 6 times |
190
- | check | 15 min | 3 times |
191
- | debug | 20 min | 4 times |
188
+ - Run one phase at a time
189
+ - Start the next phase only after the current `Task(...)` call returns
190
+ - If a phase returns a clear timeout or failure, handle that result explicitly
191
+ - Do **not** simulate completion by polling a background task wrapper
192
192
 
193
193
  ---
194
194
 
@@ -196,7 +196,7 @@ for i in 1..N:
196
196
 
197
197
  ### Timeout
198
198
 
199
- If a subagent times out, notify the user and ask for guidance:
199
+ If a synchronous subagent call times out, notify the user and ask for guidance:
200
200
 
201
201
  ```
202
202
  "Subagent {phase} timed out after {time}. Options:
@@ -207,10 +207,11 @@ If a subagent times out, notify the user and ask for guidance:
207
207
 
208
208
  ### Subagent Failure
209
209
 
210
- If a subagent reports failure, read the output and decide:
210
+ If a synchronous subagent call reports failure, read the output and decide:
211
211
 
212
212
  - If recoverable: call debug agent to fix
213
213
  - If not recoverable: notify user and ask for guidance
214
+ - Do not switch back to `TaskOutput` polling for the same phase
214
215
 
215
216
  ---
216
217
 
@@ -46,6 +46,7 @@ EOF
46
46
  **Auto-completes**:
47
47
  - [OK] Appends session to journal-N.md
48
48
  - [OK] Auto-detects line count, creates new file if >2000 lines
49
+ - [OK] Auto-detects Branch context (`--branch` override; otherwise Branch = task.json -> current git branch; missing values are omitted gracefully)
49
50
  - [OK] Updates index.md (Total Sessions +1, Last Active, line stats, history)
50
51
  - [OK] Auto-commits .trellis/workspace and .trellis/tasks changes
51
52
 
@@ -56,6 +57,6 @@ EOF
56
57
  | Command | Purpose |
57
58
  |---------|---------|
58
59
  | `python3 ./.trellis/scripts/get_context.py --mode record` | Get context for record-session |
59
- | `python3 ./.trellis/scripts/add_session.py --title "..." --commit "..."` | **One-click add session (recommended)** |
60
+ | `python3 ./.trellis/scripts/add_session.py --title "..." --commit "..."` | **One-click add session (recommended, branch auto-complete)** |
60
61
  | `python3 ./.trellis/scripts/task.py archive <name>` | Archive completed task (auto-commits) |
61
62
  | `python3 ./.trellis/scripts/task.py list` | List active tasks |
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { existsSync, readFileSync, appendFileSync, readdirSync } from "fs"
14
- import { join } from "path"
14
+ import { isAbsolute, join } from "path"
15
15
  import { homedir, platform } from "os"
16
16
  import { execSync } from "child_process"
17
17
 
@@ -191,12 +191,52 @@ export class TrellisContext {
191
191
  if (!existsSync(currentTaskPath)) {
192
192
  return null
193
193
  }
194
- return readFileSync(currentTaskPath, "utf-8").trim()
194
+ const taskRef = readFileSync(currentTaskPath, "utf-8").trim()
195
+ const normalized = this.normalizeTaskRef(taskRef)
196
+ return normalized || null
195
197
  } catch {
196
198
  return null
197
199
  }
198
200
  }
199
201
 
202
+ normalizeTaskRef(taskRef) {
203
+ if (!taskRef) {
204
+ return ""
205
+ }
206
+
207
+ if (isAbsolute(taskRef)) {
208
+ return taskRef.trim()
209
+ }
210
+
211
+ let normalized = taskRef.trim().replace(/\\/g, "/")
212
+ while (normalized.startsWith("./")) {
213
+ normalized = normalized.slice(2)
214
+ }
215
+
216
+ if (normalized.startsWith("tasks/")) {
217
+ return `.trellis/${normalized}`
218
+ }
219
+
220
+ return normalized
221
+ }
222
+
223
+ resolveTaskDir(taskRef) {
224
+ const normalized = this.normalizeTaskRef(taskRef)
225
+ if (!normalized) {
226
+ return null
227
+ }
228
+
229
+ if (isAbsolute(normalized)) {
230
+ return normalized
231
+ }
232
+
233
+ if (normalized.startsWith(".trellis/")) {
234
+ return join(this.directory, normalized)
235
+ }
236
+
237
+ return join(this.directory, ".trellis", "tasks", normalized)
238
+ }
239
+
200
240
  // ============================================================
201
241
  // Hook Decision Logic
202
242
  // ============================================================
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { existsSync, readFileSync, readdirSync, statSync } from "fs"
14
- import { join } from "path"
14
+ import { basename, join } from "path"
15
15
  import { execFileSync } from "child_process"
16
16
  import { platform } from "os"
17
17
  import { TrellisContext, contextCollector, debugLog } from "../lib/trellis-context.js"
@@ -23,36 +23,16 @@ const PYTHON_CMD = platform() === "win32" ? "python" : "python3"
23
23
  * Check current task status and return structured status string.
24
24
  * JavaScript equivalent of _get_task_status in Claude's session-start.py.
25
25
  */
26
- function getTaskStatus(directory) {
27
- const trellisDir = join(directory, ".trellis")
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
-
26
+ function getTaskStatus(ctx) {
27
+ const taskRef = ctx.getCurrentTask()
41
28
  if (!taskRef) {
42
29
  return "Status: NO ACTIVE TASK\nNext: Describe what you want to work on"
43
30
  }
44
31
 
45
32
  // Resolve task directory
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
- }
33
+ const taskDir = ctx.resolveTaskDir(taskRef)
54
34
 
55
- if (!existsSync(taskDir)) {
35
+ if (!taskDir || !existsSync(taskDir)) {
56
36
  return `Status: STALE POINTER\nTask: ${taskRef}\nNext: Task directory not found. Run: python3 ./.trellis/scripts/task.py finish`
57
37
  }
58
38
 
@@ -71,7 +51,7 @@ function getTaskStatus(directory) {
71
51
  const taskStatus = taskData.status || "unknown"
72
52
 
73
53
  if (taskStatus === "completed") {
74
- const dirName = taskDir.split("/").pop()
54
+ const dirName = basename(taskDir)
75
55
  return `Status: COMPLETED\nTask: ${taskTitle}\nNext: Archive with \`python3 ./.trellis/scripts/task.py archive ${dirName}\` or start a new task`
76
56
  }
77
57
 
@@ -354,7 +334,7 @@ Read and follow all instructions below carefully.
354
334
  }
355
335
 
356
336
  // 6. Task status (R2: check task state for session resume)
357
- const taskStatus = getTaskStatus(directory)
337
+ const taskStatus = getTaskStatus(ctx)
358
338
  parts.push(`<task-status>\n${taskStatus}\n</task-status>`)
359
339
 
360
340
  // 7. Final directive (R3: active, not passive)
@@ -51,6 +51,7 @@ EOF
51
51
  **Auto-completes**:
52
52
  - [OK] Appends session to journal-N.md
53
53
  - [OK] Auto-detects line count, creates new file if >2000 lines
54
+ - [OK] Auto-detects Branch context (`--branch` override; otherwise Branch = task.json -> current git branch; missing values are omitted gracefully)
54
55
  - [OK] Updates index.md (Total Sessions +1, Last Active, line stats, history)
55
56
  - [OK] Auto-commits .trellis/workspace and .trellis/tasks changes
56
57
 
@@ -61,6 +62,6 @@ EOF
61
62
  | Command | Purpose |
62
63
  |---------|---------|
63
64
  | `python3 ./.trellis/scripts/get_context.py --mode record` | Get context for record-session |
64
- | `python3 ./.trellis/scripts/add_session.py --title "..." --commit "..."` | **One-click add session (recommended)** |
65
+ | `python3 ./.trellis/scripts/add_session.py --title "..." --commit "..."` | **One-click add session (recommended, branch auto-complete)** |
65
66
  | `python3 ./.trellis/scripts/task.py archive <name>` | Archive completed task (auto-commits) |
66
67
  | `python3 ./.trellis/scripts/task.py list` | List active tasks |
@@ -5,17 +5,23 @@ Add a new session to journal file and update index.md.
5
5
 
6
6
  Usage:
7
7
  python3 add_session.py --title "Title" --commit "hash" --summary "Summary" [--package cli]
8
+ python3 add_session.py --title "Title" --branch "feat/my-branch"
8
9
 
9
10
  # Pipe detailed content via stdin (use --stdin to opt in):
10
11
  cat << 'EOF' | python3 add_session.py --stdin --title "Title" --summary "Summary"
11
12
  <session content here>
12
13
  EOF
14
+
15
+ Branch resolution order:
16
+ 1. --branch CLI arg (explicit)
17
+ 2. task.json branch field (from active task)
18
+ 3. git branch --show-current (auto-detect)
19
+ 4. None (omitted gracefully)
13
20
  """
14
21
 
15
22
  from __future__ import annotations
16
23
 
17
24
  import argparse
18
- import json
19
25
  import re
20
26
  import subprocess
21
27
  import sys
@@ -30,6 +36,7 @@ from common.paths import (
30
36
  get_workspace_dir,
31
37
  )
32
38
  from common.developer import ensure_developer
39
+ from common.git import run_git
33
40
  from common.tasks import load_task
34
41
  from common.config import (
35
42
  get_packages,
@@ -139,6 +146,7 @@ def generate_session_content(
139
146
  extra_content: str,
140
147
  today: str,
141
148
  package: str | None = None,
149
+ branch: str | None = None,
142
150
  ) -> str:
143
151
  """Generate session content."""
144
152
  if commit and commit != "-":
@@ -151,13 +159,14 @@ def generate_session_content(
151
159
  commit_table = "(No commits - planning session)"
152
160
 
153
161
  package_line = f"\n**Package**: {package}" if package else ""
162
+ branch_line = f"\n**Branch**: `{branch}`" if branch else ""
154
163
 
155
164
  return f"""
156
165
 
157
166
  ## Session {session_num}: {title}
158
167
 
159
168
  **Date**: {today}
160
- **Task**: {title}{package_line}
169
+ **Task**: {title}{package_line}{branch_line}
161
170
 
162
171
  ### Summary
163
172
 
@@ -192,7 +201,8 @@ def update_index(
192
201
  commit: str,
193
202
  new_session: int,
194
203
  active_file: str,
195
- today: str
204
+ today: str,
205
+ branch: str | None = None,
196
206
  ) -> bool:
197
207
  """Update index.md with new session info."""
198
208
  # Format commit for display
@@ -271,10 +281,25 @@ def update_index(
271
281
  continue
272
282
 
273
283
  if in_session_history:
274
- new_lines.append(line)
275
- if re.match(r"^\|\s*-", line) and not header_written:
276
- new_lines.append(f"| {new_session} | {today} | {title} | {commit_display} |")
284
+ # Migrate old 4/6-column headers to 5-column Branch-only history.
285
+ if re.match(
286
+ r"^\|\s*#\s*\|\s*Date\s*\|\s*Title\s*\|\s*Commits\s*\|\s*Branch\s*\|\s*Base Branch\s*\|\s*$",
287
+ line,
288
+ ):
289
+ new_lines.append("| # | Date | Title | Commits | Branch |")
290
+ continue
291
+ if re.match(r"^\|\s*#\s*\|\s*Date\s*\|\s*Title\s*\|\s*Commits\s*\|\s*Branch\s*\|\s*$", line):
292
+ new_lines.append("| # | Date | Title | Commits | Branch |")
293
+ continue
294
+ if re.match(r"^\|\s*#\s*\|\s*Date\s*\|\s*Title\s*\|\s*Commits\s*\|\s*$", line):
295
+ new_lines.append("| # | Date | Title | Commits | Branch |")
296
+ continue
297
+ if re.match(r"^\|[-| ]+\|\s*$", line) and not header_written:
298
+ new_lines.append("|---|------|-------|---------|--------|")
299
+ new_lines.append(f"| {new_session} | {today} | {title} | {commit_display} | `{branch or '-'}` |")
277
300
  header_written = True
301
+ continue
302
+ new_lines.append(line)
278
303
  continue
279
304
 
280
305
  new_lines.append(line)
@@ -291,11 +316,16 @@ def update_index(
291
316
  def _auto_commit_workspace(repo_root: Path) -> None:
292
317
  """Stage .trellis/workspace and .trellis/tasks, then commit with a configured message."""
293
318
  commit_msg = get_session_commit_message(repo_root)
294
- subprocess.run(
319
+ add_result = subprocess.run(
295
320
  ["git", "add", "-A", ".trellis/workspace", ".trellis/tasks"],
296
321
  cwd=repo_root,
297
322
  capture_output=True,
323
+ text=True,
298
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
299
329
  # Check if there are staged changes
300
330
  result = subprocess.run(
301
331
  ["git", "diff", "--cached", "--quiet", "--", ".trellis/workspace", ".trellis/tasks"],
@@ -323,6 +353,7 @@ def add_session(
323
353
  extra_content: str = "(Add details)",
324
354
  auto_commit: bool = True,
325
355
  package: str | None = None,
356
+ branch: str | None = None,
326
357
  ) -> int:
327
358
  """Add a new session."""
328
359
  repo_root = get_repo_root()
@@ -348,7 +379,8 @@ def add_session(
348
379
  new_session = current_session + 1
349
380
 
350
381
  session_content = generate_session_content(
351
- new_session, title, commit, summary, extra_content, today, package
382
+ new_session, title, commit, summary, extra_content, today, package,
383
+ branch,
352
384
  )
353
385
  content_lines = len(session_content.splitlines())
354
386
 
@@ -385,7 +417,16 @@ def add_session(
385
417
 
386
418
  # Update index.md
387
419
  active_file = f"{FILE_JOURNAL_PREFIX}{target_num}.md"
388
- if not update_index(index_file, dev_dir, title, commit, new_session, active_file, today):
420
+ if not update_index(
421
+ index_file,
422
+ dev_dir,
423
+ title,
424
+ commit,
425
+ new_session,
426
+ active_file,
427
+ today,
428
+ branch,
429
+ ):
389
430
  return 1
390
431
 
391
432
  print("", file=sys.stderr)
@@ -419,6 +460,7 @@ def main() -> int:
419
460
  parser.add_argument("--summary", default="(Add summary)", help="Brief summary")
420
461
  parser.add_argument("--content-file", help="Path to file with detailed content")
421
462
  parser.add_argument("--package", help="Package name tag (e.g., cli, docs-site)")
463
+ parser.add_argument("--branch", help="Branch name (auto-detected if omitted)")
422
464
  parser.add_argument("--no-commit", action="store_true",
423
465
  help="Skip auto-commit of workspace changes")
424
466
  parser.add_argument("--stdin", action="store_true",
@@ -434,8 +476,11 @@ def main() -> int:
434
476
  elif args.stdin:
435
477
  extra_content = sys.stdin.read()
436
478
 
437
- # Resolve package: CLI active task default_package None
479
+ # Load active task once shared by package and branch resolution
438
480
  repo_root = get_repo_root()
481
+ current = get_current_task(repo_root)
482
+ task_data = load_task(repo_root / current) if current else None
483
+
439
484
  package = args.package
440
485
  if package:
441
486
  # CLI source: fail-fast in monorepo, ignore in single-repo
@@ -449,18 +494,26 @@ def main() -> int:
449
494
  return 1
450
495
  else:
451
496
  # Inferred: active task's task.json.package → default_package → None
452
- task_package = None
453
- current = get_current_task(repo_root)
454
- if current:
455
- ct = load_task(repo_root / current)
456
- if ct and ct.package:
457
- task_package = ct.package
497
+ task_package = task_data.package if task_data else None
458
498
  package = resolve_package(task_package, repo_root)
459
499
 
500
+ # Resolve branch: CLI → task.json → git auto-detect → None
501
+ branch = args.branch
502
+
503
+ if not branch:
504
+ if task_data and task_data.raw.get("branch"):
505
+ branch = task_data.raw["branch"]
506
+ else:
507
+ _, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
508
+ detected = branch_out.strip()
509
+ if detected:
510
+ branch = detected
511
+
460
512
  return add_session(
461
513
  args.title, args.commit, args.summary, extra_content,
462
514
  auto_commit=not args.no_commit,
463
515
  package=package,
516
+ branch=branch,
464
517
  )
465
518
 
466
519
 
@@ -75,6 +75,8 @@ from .paths import (
75
75
  count_lines,
76
76
  get_current_task,
77
77
  get_current_task_abs,
78
+ normalize_task_ref,
79
+ resolve_task_ref,
78
80
  set_current_task,
79
81
  clear_current_task,
80
82
  has_current_task,