(cwd: string)
| 106 | * no `.trellis/` directory (the common case for non-Trellis projects). |
| 107 | */ |
| 108 | export async function loadTrellisContext(cwd: string): Promise<TrellisContext | null> { |
| 109 | const rootDir = await findTrellisRoot(cwd); |
| 110 | if (!rootDir) return null; |
| 111 | const base = path.join(rootDir, '.trellis'); |
| 112 | |
| 113 | const [spec, tasks, journalsAll] = await Promise.all([ |
| 114 | readMarkdownDir(path.join(base, 'spec')), |
| 115 | readMarkdownDir(path.join(base, 'tasks')), |
| 116 | readMarkdownDir(path.join(base, 'workspace')), |
| 117 | ]); |
| 118 | |
| 119 | // Specs: alphabetical (stable). Tasks: alphabetical. Journals: newest first, capped. |
| 120 | spec.sort((a, b) => a.name.localeCompare(b.name)); |
| 121 | tasks.sort((a, b) => a.name.localeCompare(b.name)); |
| 122 | const journals = journalsAll |
| 123 | .sort((a, b) => b.mtimeMs - a.mtimeMs) |
| 124 | .slice(0, MAX_JOURNAL_FILES); |
| 125 | |
| 126 | if (spec.length === 0 && tasks.length === 0 && journals.length === 0) { |
| 127 | return null; // .trellis exists but is empty — nothing to inject |
| 128 | } |
| 129 | |
| 130 | const sections: string[] = []; |
| 131 | sections.push( |
| 132 | `## Trellis project harness\n` + |
| 133 | `This project uses Trellis (.trellis/). Treat the spec below as binding ` + |
| 134 | `conventions, the tasks as the current work and its status, and the journals ` + |
| 135 | `as what happened in prior sessions. Follow the spec; continue the tasks; do ` + |
| 136 | `not re-derive decisions already recorded in the journals. When you finish a ` + |
| 137 | `unit of work or context fills up, update the relevant task status and append ` + |
| 138 | `a short journal entry under .trellis/workspace/.`, |
| 139 | ); |
| 140 | |
| 141 | if (spec.length > 0) { |
| 142 | sections.push(`### Conventions (.trellis/spec/)\n${packFiles(spec, MAX_SPEC_BYTES)}`); |
| 143 | } |
| 144 | if (tasks.length > 0) { |
| 145 | sections.push(`### Tasks (.trellis/tasks/)\n${packFiles(tasks, MAX_TASK_BYTES)}`); |
| 146 | } |
| 147 | if (journals.length > 0) { |
| 148 | sections.push( |
| 149 | `### Recent journals (.trellis/workspace/, newest first)\n` + |
| 150 | packFiles(journals, MAX_JOURNAL_BYTES), |
| 151 | ); |
| 152 | } |
| 153 | |
| 154 | const specBlock = spec.length > 0 |
| 155 | ? `## Trellis conventions (.trellis/spec/) — binding\n${packFiles(spec, MAX_SPEC_BYTES)}` |
| 156 | : null; |
| 157 | |
| 158 | return { |
| 159 | block: sections.join('\n\n'), |
| 160 | specBlock, |
| 161 | rootDir, |
| 162 | counts: { specFiles: spec.length, taskFiles: tasks.length, journalFiles: journals.length }, |
| 163 | }; |
| 164 | } |
no test coverage detected