@mindfoldhq/trellis 0.5.0-beta.8 → 0.5.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.
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +380 -116
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +6 -21
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +8 -7
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/qoder.d.ts +7 -6
- package/dist/configurators/qoder.d.ts.map +1 -1
- package/dist/configurators/qoder.js +17 -9
- package/dist/configurators/qoder.js.map +1 -1
- package/dist/configurators/shared.d.ts +2 -0
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +18 -0
- package/dist/configurators/shared.js.map +1 -1
- package/dist/migrations/manifests/0.5.0-beta.9.json +48 -0
- package/dist/templates/common/skills/brainstorm.md +47 -4
- package/dist/templates/opencode/plugins/inject-workflow-state.js +21 -7
- package/dist/templates/shared-hooks/inject-workflow-state.py +21 -8
- package/dist/templates/shared-hooks/session-start.py +14 -4
- package/dist/templates/trellis/config.yaml +6 -0
- package/dist/templates/trellis/index.d.ts +0 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +0 -2
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/types.py +0 -2
- package/dist/templates/trellis/workflow.md +8 -3
- package/dist/utils/project-detector.d.ts +2 -0
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +120 -11
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/task-json.d.ts +46 -0
- package/dist/utils/task-json.d.ts.map +1 -0
- package/dist/utils/task-json.js +49 -0
- package/dist/utils/task-json.js.map +1 -0
- package/package.json +1 -1
- package/dist/templates/markdown/spec/backend/directory-structure.md +0 -292
- package/dist/templates/markdown/spec/backend/index.md +0 -40
- package/dist/templates/markdown/spec/backend/script-conventions.md +0 -742
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +0 -118
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +0 -394
- package/dist/templates/trellis/scripts/create_bootstrap.py +0 -298
package/dist/commands/init.js
CHANGED
|
@@ -12,6 +12,7 @@ import { DIR_NAMES, FILE_NAMES, PATHS } from "../constants/paths.js";
|
|
|
12
12
|
import { VERSION } from "../constants/version.js";
|
|
13
13
|
import { agentsMdContent } from "../templates/markdown/index.js";
|
|
14
14
|
import { setWriteMode, writeFile, } from "../utils/file-writer.js";
|
|
15
|
+
import { emptyTaskJson } from "../utils/task-json.js";
|
|
15
16
|
import { detectProjectType, detectMonorepo, sanitizePkgName, } from "../utils/project-detector.js";
|
|
16
17
|
import { initializeHashes } from "../utils/template-hash.js";
|
|
17
18
|
import { fetchTemplateIndex, probeRegistryIndex, downloadTemplateById, downloadRegistryDirect, parseRegistrySource, TIMEOUTS, TEMPLATE_INDEX_URL, } from "../utils/template-fetcher.js";
|
|
@@ -65,29 +66,120 @@ function getPythonCommand() {
|
|
|
65
66
|
// Bootstrap Task Creation
|
|
66
67
|
// =============================================================================
|
|
67
68
|
const BOOTSTRAP_TASK_NAME = "00-bootstrap-guidelines";
|
|
69
|
+
/**
|
|
70
|
+
* Slugify a developer name for safe use in task directory names.
|
|
71
|
+
*
|
|
72
|
+
* Unlike `sanitizePkgName` (which only strips npm @scope/ prefixes), this
|
|
73
|
+
* handles arbitrary developer input: spaces, Unicode letters, punctuation,
|
|
74
|
+
* path separators. Returns "user" fallback when input slugifies to empty.
|
|
75
|
+
*
|
|
76
|
+
* Exported for unit testing; not part of the public API.
|
|
77
|
+
*/
|
|
78
|
+
export function slugifyDeveloperName(name) {
|
|
79
|
+
const slug = name
|
|
80
|
+
.toLowerCase()
|
|
81
|
+
.normalize("NFKD")
|
|
82
|
+
.replace(/[^\p{Letter}\p{Number}]+/gu, "-")
|
|
83
|
+
.replace(/^-+|-+$/g, "");
|
|
84
|
+
return slug || "user";
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Write a task skeleton (task.json + prd.md + .current-task pointer).
|
|
88
|
+
*
|
|
89
|
+
* Idempotent: if the task dir already exists, returns true without touching
|
|
90
|
+
* anything. Shared by both creator bootstrap and joiner onboarding flows.
|
|
91
|
+
*/
|
|
92
|
+
function writeTaskSkeleton(cwd, taskName, taskJson, prdContent) {
|
|
93
|
+
const taskDir = path.join(cwd, PATHS.TASKS, taskName);
|
|
94
|
+
if (fs.existsSync(taskDir))
|
|
95
|
+
return true; // idempotent
|
|
96
|
+
try {
|
|
97
|
+
fs.mkdirSync(taskDir, { recursive: true });
|
|
98
|
+
fs.writeFileSync(path.join(taskDir, FILE_NAMES.TASK_JSON), JSON.stringify(taskJson, null, 2), "utf-8");
|
|
99
|
+
fs.writeFileSync(path.join(taskDir, FILE_NAMES.PRD), prdContent, "utf-8");
|
|
100
|
+
fs.writeFileSync(path.join(cwd, PATHS.CURRENT_TASK_FILE), `${PATHS.TASKS}/${taskName}`, "utf-8");
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Compute the bootstrap checklist items (previously stored as structured
|
|
109
|
+
* `subtasks: [{name, status}]` in task.json). Per task 04-21-task-schema-unify
|
|
110
|
+
* (D1), these live as markdown `- [ ]` items in prd.md instead, so task.json
|
|
111
|
+
* stays canonical with `subtasks: string[]` (child task dir names, same as
|
|
112
|
+
* task_store.py).
|
|
113
|
+
*/
|
|
114
|
+
function getBootstrapChecklistItems(projectType, packages) {
|
|
115
|
+
if (packages && packages.length > 0) {
|
|
116
|
+
const items = packages.map((pkg) => `Fill guidelines for ${pkg.name}`);
|
|
117
|
+
items.push("Add code examples");
|
|
118
|
+
return items;
|
|
119
|
+
}
|
|
120
|
+
if (projectType === "frontend") {
|
|
121
|
+
return ["Fill frontend guidelines", "Add code examples"];
|
|
122
|
+
}
|
|
123
|
+
if (projectType === "backend") {
|
|
124
|
+
return ["Fill backend guidelines", "Add code examples"];
|
|
125
|
+
}
|
|
126
|
+
return [
|
|
127
|
+
"Fill backend guidelines",
|
|
128
|
+
"Fill frontend guidelines",
|
|
129
|
+
"Add code examples",
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
function getBootstrapRelatedFiles(projectType, packages) {
|
|
133
|
+
if (packages && packages.length > 0) {
|
|
134
|
+
return packages.map((pkg) => `.trellis/spec/${sanitizePkgName(pkg.name)}/`);
|
|
135
|
+
}
|
|
136
|
+
if (projectType === "frontend") {
|
|
137
|
+
return [".trellis/spec/frontend/"];
|
|
138
|
+
}
|
|
139
|
+
if (projectType === "backend") {
|
|
140
|
+
return [".trellis/spec/backend/"];
|
|
141
|
+
}
|
|
142
|
+
return [".trellis/spec/backend/", ".trellis/spec/frontend/"];
|
|
143
|
+
}
|
|
68
144
|
function getBootstrapPrdContent(projectType, packages) {
|
|
69
|
-
const
|
|
145
|
+
const checklistItems = getBootstrapChecklistItems(projectType, packages);
|
|
146
|
+
const checklistMarkdown = checklistItems
|
|
147
|
+
.map((item) => `- [ ] ${item}`)
|
|
148
|
+
.join("\n");
|
|
149
|
+
const header = `# Bootstrap Task: Fill Project Development Guidelines
|
|
70
150
|
|
|
71
|
-
|
|
151
|
+
**You (the AI) are running this task. The developer does not read this file.**
|
|
72
152
|
|
|
73
|
-
|
|
153
|
+
The developer just ran \`trellis init\` on this project for the first time.
|
|
154
|
+
\`.trellis/\` now exists with empty spec scaffolding, and this task has been
|
|
155
|
+
set as their current task. They'll open their AI tool, run \`/trellis:continue\`,
|
|
156
|
+
and you'll land here.
|
|
74
157
|
|
|
75
|
-
|
|
76
|
-
|
|
158
|
+
**Your job**: help them populate \`.trellis/spec/\` with the team's real
|
|
159
|
+
coding conventions. Every future AI session — this project's
|
|
160
|
+
\`trellis-implement\` and \`trellis-check\` sub-agents — auto-loads spec files
|
|
161
|
+
listed in per-task jsonl manifests. Empty spec = sub-agents write generic
|
|
162
|
+
code. Real spec = sub-agents match the team's actual patterns.
|
|
77
163
|
|
|
78
|
-
|
|
164
|
+
Don't dump instructions. Open with a short greeting, figure out if the repo
|
|
165
|
+
has any existing convention docs (CLAUDE.md, .cursorrules, etc.), and drive
|
|
166
|
+
the rest conversationally.
|
|
79
167
|
|
|
80
168
|
---
|
|
81
169
|
|
|
82
|
-
##
|
|
170
|
+
## Status (update the checkboxes as you complete each item)
|
|
171
|
+
|
|
172
|
+
${checklistMarkdown}
|
|
173
|
+
|
|
174
|
+
---
|
|
83
175
|
|
|
84
|
-
|
|
176
|
+
## Spec files to populate
|
|
85
177
|
`;
|
|
86
178
|
const backendSection = `
|
|
87
179
|
|
|
88
|
-
### Backend
|
|
180
|
+
### Backend guidelines
|
|
89
181
|
|
|
90
|
-
| File | What to
|
|
182
|
+
| File | What to document |
|
|
91
183
|
|------|------------------|
|
|
92
184
|
| \`.trellis/spec/backend/directory-structure.md\` | Where different file types go (routes, services, utils) |
|
|
93
185
|
| \`.trellis/spec/backend/database-guidelines.md\` | ORM, migrations, query patterns, naming conventions |
|
|
@@ -97,9 +189,9 @@ Fill in the guideline files based on your **existing codebase**.
|
|
|
97
189
|
`;
|
|
98
190
|
const frontendSection = `
|
|
99
191
|
|
|
100
|
-
### Frontend
|
|
192
|
+
### Frontend guidelines
|
|
101
193
|
|
|
102
|
-
| File | What to
|
|
194
|
+
| File | What to document |
|
|
103
195
|
|------|------------------|
|
|
104
196
|
| \`.trellis/spec/frontend/directory-structure.md\` | Component/page/hook organization |
|
|
105
197
|
| \`.trellis/spec/frontend/component-guidelines.md\` | Component patterns, props conventions |
|
|
@@ -110,18 +202,20 @@ Fill in the guideline files based on your **existing codebase**.
|
|
|
110
202
|
`;
|
|
111
203
|
const footer = `
|
|
112
204
|
|
|
113
|
-
### Thinking
|
|
205
|
+
### Thinking guides (already populated)
|
|
114
206
|
|
|
115
|
-
|
|
116
|
-
|
|
207
|
+
\`.trellis/spec/guides/\` contains general thinking guides pre-filled with
|
|
208
|
+
best practices. Customize only if something clearly doesn't fit this project.
|
|
117
209
|
|
|
118
210
|
---
|
|
119
211
|
|
|
120
|
-
## How to
|
|
212
|
+
## How to fill the spec
|
|
121
213
|
|
|
122
|
-
### Step
|
|
214
|
+
### Step 1: Import from existing convention files first (preferred)
|
|
123
215
|
|
|
124
|
-
|
|
216
|
+
Search the repo for existing convention docs. If any exist, read them and
|
|
217
|
+
extract the relevant rules into the matching \`.trellis/spec/\` files —
|
|
218
|
+
usually much faster than documenting from scratch.
|
|
125
219
|
|
|
126
220
|
| File / Directory | Tool |
|
|
127
221
|
|------|------|
|
|
@@ -138,50 +232,60 @@ Many projects already have coding conventions documented. **Check these first**
|
|
|
138
232
|
| \`CONTRIBUTING.md\` | General project conventions |
|
|
139
233
|
| \`.editorconfig\` | Editor formatting rules |
|
|
140
234
|
|
|
141
|
-
|
|
235
|
+
### Step 2: Analyze the codebase for anything not covered by existing docs
|
|
142
236
|
|
|
143
|
-
|
|
237
|
+
Scan real code to discover patterns. Before writing each spec file:
|
|
238
|
+
- Find 2-3 real examples of each pattern in the codebase.
|
|
239
|
+
- Reference real file paths (not hypothetical ones).
|
|
240
|
+
- Document anti-patterns the team clearly avoids.
|
|
144
241
|
|
|
145
|
-
|
|
242
|
+
### Step 3: Document reality, not ideals
|
|
146
243
|
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
|
|
244
|
+
**Critical**: write what the code *actually does*, not what it should do.
|
|
245
|
+
Sub-agents match the spec, so aspirational patterns that don't exist in the
|
|
246
|
+
codebase will cause sub-agents to write code that looks out of place.
|
|
150
247
|
|
|
151
|
-
|
|
248
|
+
If the team has known tech debt, document the current state — improvement
|
|
249
|
+
is a separate conversation, not a bootstrap concern.
|
|
152
250
|
|
|
153
|
-
|
|
154
|
-
AI needs to match existing patterns, not introduce new ones.
|
|
251
|
+
---
|
|
155
252
|
|
|
156
|
-
|
|
157
|
-
- **Include file paths** - Reference real files as examples
|
|
158
|
-
- **List anti-patterns** - What does your team avoid?
|
|
253
|
+
## Quick explainer of the runtime (share when they ask "why do we need spec at all")
|
|
159
254
|
|
|
160
|
-
|
|
255
|
+
- Every AI coding task spawns two sub-agents: \`trellis-implement\` (writes
|
|
256
|
+
code) and \`trellis-check\` (verifies quality).
|
|
257
|
+
- Each task has \`implement.jsonl\` / \`check.jsonl\` manifests listing which
|
|
258
|
+
spec files to load.
|
|
259
|
+
- The platform hook auto-injects those spec files + the task's \`prd.md\`
|
|
260
|
+
into every sub-agent prompt, so the sub-agent codes/reviews per team
|
|
261
|
+
conventions without anyone pasting them manually.
|
|
262
|
+
- Source of truth: \`.trellis/spec/\`. That's why filling it well now pays
|
|
263
|
+
off forever.
|
|
161
264
|
|
|
162
|
-
|
|
265
|
+
---
|
|
163
266
|
|
|
164
|
-
|
|
165
|
-
- [ ] At least 2-3 real code examples in each guideline
|
|
166
|
-
- [ ] Anti-patterns documented
|
|
267
|
+
## Completion
|
|
167
268
|
|
|
168
|
-
When done
|
|
269
|
+
When the developer confirms the checklist items above are done with real
|
|
270
|
+
examples (not placeholders), guide them to run:
|
|
169
271
|
|
|
170
272
|
\`\`\`bash
|
|
171
273
|
python3 ./.trellis/scripts/task.py finish
|
|
172
274
|
python3 ./.trellis/scripts/task.py archive 00-bootstrap-guidelines
|
|
173
275
|
\`\`\`
|
|
174
276
|
|
|
175
|
-
|
|
277
|
+
After archive, every new developer who joins this project will get a
|
|
278
|
+
\`00-join-<slug>\` onboarding task instead of this bootstrap task.
|
|
176
279
|
|
|
177
|
-
|
|
280
|
+
---
|
|
178
281
|
|
|
179
|
-
|
|
282
|
+
## Suggested opening line
|
|
180
283
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
284
|
+
"Welcome to Trellis! Your init just set me up to help you fill the project
|
|
285
|
+
spec — a one-time setup so every future AI session follows the team's
|
|
286
|
+
conventions instead of writing generic code. Before we start, do you have
|
|
287
|
+
any existing convention docs (CLAUDE.md, .cursorrules, CONTRIBUTING.md,
|
|
288
|
+
etc.) I can pull from, or should I scan the codebase from scratch?"
|
|
185
289
|
`;
|
|
186
290
|
let content = header;
|
|
187
291
|
if (packages && packages.length > 0) {
|
|
@@ -214,43 +318,15 @@ After completing this task:
|
|
|
214
318
|
}
|
|
215
319
|
function getBootstrapTaskJson(developer, projectType, packages) {
|
|
216
320
|
const today = new Date().toISOString().split("T")[0];
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
status: "pending",
|
|
224
|
-
}));
|
|
225
|
-
subtasks.push({ name: "Add code examples", status: "pending" });
|
|
226
|
-
relatedFiles = packages.map((pkg) => `.trellis/spec/${sanitizePkgName(pkg.name)}/`);
|
|
227
|
-
}
|
|
228
|
-
else if (projectType === "frontend") {
|
|
229
|
-
subtasks = [
|
|
230
|
-
{ name: "Fill frontend guidelines", status: "pending" },
|
|
231
|
-
{ name: "Add code examples", status: "pending" },
|
|
232
|
-
];
|
|
233
|
-
relatedFiles = [".trellis/spec/frontend/"];
|
|
234
|
-
}
|
|
235
|
-
else if (projectType === "backend") {
|
|
236
|
-
subtasks = [
|
|
237
|
-
{ name: "Fill backend guidelines", status: "pending" },
|
|
238
|
-
{ name: "Add code examples", status: "pending" },
|
|
239
|
-
];
|
|
240
|
-
relatedFiles = [".trellis/spec/backend/"];
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
// fullstack
|
|
244
|
-
subtasks = [
|
|
245
|
-
{ name: "Fill backend guidelines", status: "pending" },
|
|
246
|
-
{ name: "Fill frontend guidelines", status: "pending" },
|
|
247
|
-
{ name: "Add code examples", status: "pending" },
|
|
248
|
-
];
|
|
249
|
-
relatedFiles = [".trellis/spec/backend/", ".trellis/spec/frontend/"];
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
321
|
+
const relatedFiles = getBootstrapRelatedFiles(projectType, packages);
|
|
322
|
+
// Canonical 24-field shape via emptyTaskJson factory.
|
|
323
|
+
// Checklist items (previously stored as structured `subtasks`) are now
|
|
324
|
+
// rendered as `- [ ]` items in prd.md; task.json.subtasks is always
|
|
325
|
+
// string[] (child task dir names) per the canonical schema.
|
|
326
|
+
return emptyTaskJson({
|
|
252
327
|
id: BOOTSTRAP_TASK_NAME,
|
|
253
|
-
name:
|
|
328
|
+
name: BOOTSTRAP_TASK_NAME,
|
|
329
|
+
title: "Bootstrap Guidelines",
|
|
254
330
|
description: "Fill in project development guidelines for AI agents",
|
|
255
331
|
status: "in_progress",
|
|
256
332
|
dev_type: "docs",
|
|
@@ -258,43 +334,166 @@ function getBootstrapTaskJson(developer, projectType, packages) {
|
|
|
258
334
|
creator: developer,
|
|
259
335
|
assignee: developer,
|
|
260
336
|
createdAt: today,
|
|
261
|
-
completedAt: null,
|
|
262
|
-
commit: null,
|
|
263
|
-
subtasks,
|
|
264
|
-
children: [],
|
|
265
|
-
parent: null,
|
|
266
337
|
relatedFiles,
|
|
267
338
|
notes: `First-time setup task created by trellis init (${projectType} project)`,
|
|
268
|
-
|
|
269
|
-
};
|
|
339
|
+
});
|
|
270
340
|
}
|
|
271
341
|
/**
|
|
272
342
|
* Create bootstrap task for first-time setup
|
|
273
343
|
*/
|
|
274
344
|
function createBootstrapTask(cwd, developer, projectType, packages) {
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
345
|
+
const taskJson = getBootstrapTaskJson(developer, projectType, packages);
|
|
346
|
+
const prdContent = getBootstrapPrdContent(projectType, packages);
|
|
347
|
+
return writeTaskSkeleton(cwd, BOOTSTRAP_TASK_NAME, taskJson, prdContent);
|
|
348
|
+
}
|
|
349
|
+
// =============================================================================
|
|
350
|
+
// Joiner Onboarding Task Creation
|
|
351
|
+
// =============================================================================
|
|
352
|
+
/**
|
|
353
|
+
* task.json factory for joiner onboarding. Mirrors the bootstrap factory but
|
|
354
|
+
* uses dev_type "docs", higher priority "P1", and the developer-specific task
|
|
355
|
+
* name (so multiple joiners in the same checkout don't collide).
|
|
356
|
+
*/
|
|
357
|
+
function getJoinerTaskJson(developer, taskName) {
|
|
358
|
+
const today = new Date().toISOString().split("T")[0];
|
|
359
|
+
return emptyTaskJson({
|
|
360
|
+
id: taskName,
|
|
361
|
+
name: taskName,
|
|
362
|
+
title: `Joining: Onboard to this Trellis project (${developer})`,
|
|
363
|
+
description: "Onboard a new developer to an existing Trellis project: learn the workflow, conventions, and find assigned work",
|
|
364
|
+
status: "in_progress",
|
|
365
|
+
dev_type: "docs",
|
|
366
|
+
priority: "P1",
|
|
367
|
+
creator: developer,
|
|
368
|
+
assignee: developer,
|
|
369
|
+
createdAt: today,
|
|
370
|
+
notes: "Generated by trellis init for a new developer joining an existing Trellis project",
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* PRD content for joiner onboarding. Kept concise (~80 lines) — deeper
|
|
375
|
+
* guidance lives in skills and docs.
|
|
376
|
+
*/
|
|
377
|
+
function getJoinerPrdContent(developer) {
|
|
378
|
+
const slug = slugifyDeveloperName(developer);
|
|
379
|
+
return `# Joiner Onboarding Task
|
|
380
|
+
|
|
381
|
+
**You (the AI) are running this task. The developer does not read this file.**
|
|
382
|
+
|
|
383
|
+
\`${developer}\` just ran \`trellis init\` on a fresh clone, saw "Developer
|
|
384
|
+
initialized", and will now start asking you questions in chat. This task is
|
|
385
|
+
already set as their current task (\`.trellis/.current-task\` points here), so
|
|
386
|
+
when they run \`/trellis:continue\` you'll land back on this PRD.
|
|
387
|
+
|
|
388
|
+
Your job is to orient them to Trellis. Don't dump all of this at them — open
|
|
389
|
+
with a short greeting, ask where they want to start, and fill in the rest as
|
|
390
|
+
they engage.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Topics to cover (adapt order to their questions)
|
|
395
|
+
|
|
396
|
+
### 1. What Trellis is + the workflow
|
|
397
|
+
|
|
398
|
+
Trellis is a workflow layer over Claude Code / Cursor / etc. that keeps AI
|
|
399
|
+
agents consistent with project-specific conventions instead of writing generic
|
|
400
|
+
code every session.
|
|
401
|
+
|
|
402
|
+
- **Three phases**: Plan (brainstorm → \`prd.md\`) → Execute (code + check) →
|
|
403
|
+
Finish (capture + wrap). Full reference: \`.trellis/workflow.md\`.
|
|
404
|
+
- **Task lifecycle**: planning → in_progress → done → archive, under
|
|
405
|
+
\`.trellis/tasks/\`.
|
|
406
|
+
- **Core slash commands**:
|
|
407
|
+
- \`/trellis:continue\` — resume the current task (their primary entry,
|
|
408
|
+
since current-task is already set to this onboarding task)
|
|
409
|
+
- \`/trellis:finish-work\` — wrap up a finished task
|
|
410
|
+
- \`/trellis:start\` — session boot from scratch (not needed here; the
|
|
411
|
+
SessionStart hook does its job automatically)
|
|
412
|
+
|
|
413
|
+
### 2. Runtime mechanics (explain when they ask "how does it know what to do")
|
|
414
|
+
|
|
415
|
+
- **SessionStart hook** runs \`get_context.py\` and injects identity, git
|
|
416
|
+
status, current-task pointer, active tasks, and workflow phase into the AI
|
|
417
|
+
conversation at every session start.
|
|
418
|
+
- **\`<workflow-state>\` tag** is auto-injected with every user message,
|
|
419
|
+
carrying the current task + phase hint.
|
|
420
|
+
- **\`/trellis:continue\`** loads the Phase Index, reads \`prd.md\` + recent
|
|
421
|
+
activity, and routes to the right skill (\`trellis-brainstorm\` for planning,
|
|
422
|
+
\`trellis-implement\` for coding, \`trellis-check\` for verification).
|
|
423
|
+
- **\`trellis-implement\` sub-agent** is spawned when code needs to be written.
|
|
424
|
+
The platform hook reads \`{TASK_DIR}/implement.jsonl\` and auto-injects those
|
|
425
|
+
spec files + \`prd.md\` into the sub-agent's prompt so it codes per project
|
|
426
|
+
conventions.
|
|
427
|
+
- **\`trellis-check\` sub-agent** follows the same pattern with \`check.jsonl\`
|
|
428
|
+
— reviews changes against specs, auto-fixes issues, runs lint/typecheck.
|
|
429
|
+
|
|
430
|
+
File layout (mention when they ask "where does what live"):
|
|
431
|
+
- \`.trellis/.current-task\` — session pointer, gitignored, per-checkout
|
|
432
|
+
- \`.trellis/tasks/<task>/{implement,check}.jsonl\` — per-task context manifests
|
|
433
|
+
- \`.trellis/spec/\` — project-wide conventions (source of truth)
|
|
434
|
+
- \`.trellis/workspace/${developer}/journal-*.md\` — their session log,
|
|
435
|
+
rotated at ~2000 lines
|
|
436
|
+
|
|
437
|
+
### 3. This project's actual conventions
|
|
438
|
+
|
|
439
|
+
- Summarize \`.trellis/spec/\` for them — what coding conventions this
|
|
440
|
+
specific team enforces.
|
|
441
|
+
- Point at the last 5 entries in \`.trellis/tasks/archive/\` as a rhythm
|
|
442
|
+
example of how people actually work here. **If archive is empty** (the
|
|
443
|
+
project just started), skip this — don't invent examples.
|
|
444
|
+
- Not your job in this onboarding to teach them the business code itself —
|
|
445
|
+
the README and their teammates handle that.
|
|
446
|
+
|
|
447
|
+
### 4. Their assigned work
|
|
448
|
+
|
|
449
|
+
- Check if \`.trellis/workspace/${developer}/\` already exists — if yes, it's
|
|
450
|
+
their journal from another machine and worth mentioning.
|
|
451
|
+
- Run \`python3 ./.trellis/scripts/task.py list --assignee ${developer}\` to
|
|
452
|
+
show tasks assigned to them. (Quote the name if it contains spaces.)
|
|
453
|
+
- Remind them that the "My Tasks" section appears in the SessionStart context
|
|
454
|
+
on every new session.
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Optional: walk through a small task end-to-end
|
|
459
|
+
|
|
460
|
+
If they want to practice before touching real work, offer to pick a tiny
|
|
461
|
+
P3 task or a typo fix and run the full cycle together: \`/trellis:continue\`
|
|
462
|
+
→ you implement via sub-agents → \`/trellis:finish-work\`.
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Completion
|
|
467
|
+
|
|
468
|
+
When they feel oriented (or after you've covered the four topics with
|
|
469
|
+
reasonable back-and-forth), guide them to run:
|
|
470
|
+
|
|
471
|
+
\`\`\`bash
|
|
472
|
+
python3 ./.trellis/scripts/task.py finish
|
|
473
|
+
python3 ./.trellis/scripts/task.py archive 00-join-${slug}
|
|
474
|
+
\`\`\`
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Suggested opening line
|
|
479
|
+
|
|
480
|
+
"Welcome! Your \`trellis init\` set me up to onboard you to this project. I
|
|
481
|
+
can walk you through the workflow, show you the runtime mechanics under the
|
|
482
|
+
hood, summarize the team's spec, or jump to what you're already curious about
|
|
483
|
+
— which would you prefer?"
|
|
484
|
+
`;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Create joiner onboarding task for a new developer on an existing Trellis
|
|
488
|
+
* project. Task name is slugified to be filesystem-safe for arbitrary
|
|
489
|
+
* developer names (spaces, Unicode, punctuation).
|
|
490
|
+
*/
|
|
491
|
+
function createJoinerOnboardingTask(cwd, developer) {
|
|
492
|
+
const slug = slugifyDeveloperName(developer);
|
|
493
|
+
const taskName = `00-join-${slug}`;
|
|
494
|
+
const taskJson = getJoinerTaskJson(developer, taskName);
|
|
495
|
+
const prdContent = getJoinerPrdContent(developer);
|
|
496
|
+
return writeTaskSkeleton(cwd, taskName, taskJson, prdContent);
|
|
298
497
|
}
|
|
299
498
|
/**
|
|
300
499
|
* Handle re-init when .trellis/ already exists.
|
|
@@ -396,6 +595,10 @@ async function handleReinit(cwd, options, developerName) {
|
|
|
396
595
|
devName = await askInput("Your name: ");
|
|
397
596
|
}
|
|
398
597
|
}
|
|
598
|
+
// Capture pre-init state: if .developer did not exist before we ran
|
|
599
|
+
// init_developer.py, this checkout had no identity → treat as a new
|
|
600
|
+
// joiner onboarding onto an existing Trellis project.
|
|
601
|
+
const hadDeveloperFileBefore = fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW, FILE_NAMES.DEVELOPER));
|
|
399
602
|
try {
|
|
400
603
|
const pythonCmd = getPythonCommand();
|
|
401
604
|
const scriptPath = path.join(cwd, PATHS.SCRIPTS, "init_developer.py");
|
|
@@ -409,6 +612,18 @@ async function handleReinit(cwd, options, developerName) {
|
|
|
409
612
|
console.log(chalk.yellow("⚠ Could not initialize developer. Run manually:"));
|
|
410
613
|
console.log(chalk.gray(` python3 .trellis/scripts/init_developer.py ${devName}`));
|
|
411
614
|
}
|
|
615
|
+
// Create joiner onboarding task for fresh checkouts (no prior .developer).
|
|
616
|
+
// Runs outside the init_developer try/catch so failures surface as warnings.
|
|
617
|
+
if (!hadDeveloperFileBefore) {
|
|
618
|
+
try {
|
|
619
|
+
if (!createJoinerOnboardingTask(cwd, devName)) {
|
|
620
|
+
console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
console.warn(chalk.yellow(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
412
627
|
}
|
|
413
628
|
return true;
|
|
414
629
|
}
|
|
@@ -438,6 +653,9 @@ function writeMonorepoConfig(cwd, packages) {
|
|
|
438
653
|
if (pkg.isSubmodule) {
|
|
439
654
|
lines.push(" type: submodule");
|
|
440
655
|
}
|
|
656
|
+
else if (pkg.isGitRepo) {
|
|
657
|
+
lines.push(" git: true");
|
|
658
|
+
}
|
|
441
659
|
}
|
|
442
660
|
// Use first non-submodule package as default, fallback to first package
|
|
443
661
|
const defaultPkg = packages.find((p) => !p.isSubmodule)?.name ?? packages[0]?.name;
|
|
@@ -449,6 +667,10 @@ function writeMonorepoConfig(cwd, packages) {
|
|
|
449
667
|
export async function init(options) {
|
|
450
668
|
const cwd = process.cwd();
|
|
451
669
|
const isFirstInit = !fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW));
|
|
670
|
+
// Captured here (before createWorkflowStructure + init_developer run) so
|
|
671
|
+
// the three-branch dispatch at the bottom can tell "fresh clone joiner"
|
|
672
|
+
// (.trellis/ exists, .developer missing) apart from "creator first init".
|
|
673
|
+
const hadDeveloperFileAtStart = fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW, FILE_NAMES.DEVELOPER));
|
|
452
674
|
// Generate ASCII art banner dynamically using FIGlet "Rebel" font
|
|
453
675
|
const banner = figlet.textSync("Trellis", { font: "Rebel" });
|
|
454
676
|
console.log(chalk.cyan(`\n${banner.trimEnd()}`));
|
|
@@ -540,7 +762,26 @@ export async function init(options) {
|
|
|
540
762
|
// options.monorepo: true = --monorepo, false = --no-monorepo, undefined = auto
|
|
541
763
|
const detected = detectMonorepo(cwd);
|
|
542
764
|
if (options.monorepo === true && !detected) {
|
|
543
|
-
console.log(chalk.red("Error: --monorepo specified but no
|
|
765
|
+
console.log(chalk.red("Error: --monorepo specified but no multi-package layout detected."));
|
|
766
|
+
console.log("");
|
|
767
|
+
console.log(chalk.gray("Checked:"));
|
|
768
|
+
console.log(chalk.gray(" ✗ pnpm-workspace.yaml"));
|
|
769
|
+
console.log(chalk.gray(" ✗ package.json workspaces"));
|
|
770
|
+
console.log(chalk.gray(" ✗ Cargo.toml [workspace]"));
|
|
771
|
+
console.log(chalk.gray(" ✗ go.work"));
|
|
772
|
+
console.log(chalk.gray(" ✗ pyproject.toml [tool.uv.workspace]"));
|
|
773
|
+
console.log(chalk.gray(" ✗ .gitmodules"));
|
|
774
|
+
console.log(chalk.gray(" ✗ sibling .git directories (need ≥ 2)"));
|
|
775
|
+
console.log("");
|
|
776
|
+
console.log("To configure manually, add to .trellis/config.yaml:");
|
|
777
|
+
console.log("");
|
|
778
|
+
console.log(chalk.cyan(" packages:"));
|
|
779
|
+
console.log(chalk.cyan(" frontend:"));
|
|
780
|
+
console.log(chalk.cyan(" path: ./frontend"));
|
|
781
|
+
console.log(chalk.cyan(" git: true # if it has its own .git"));
|
|
782
|
+
console.log(chalk.cyan(" backend:"));
|
|
783
|
+
console.log(chalk.cyan(" path: ./backend"));
|
|
784
|
+
console.log(chalk.cyan(" git: true"));
|
|
544
785
|
return;
|
|
545
786
|
}
|
|
546
787
|
if (detected && detected.length > 0) {
|
|
@@ -552,11 +793,15 @@ export async function init(options) {
|
|
|
552
793
|
// Show detected packages and ask
|
|
553
794
|
console.log(chalk.blue("\n🔍 Detected monorepo packages:"));
|
|
554
795
|
for (const pkg of detected) {
|
|
555
|
-
const
|
|
796
|
+
const tag = pkg.isSubmodule
|
|
797
|
+
? chalk.gray(" (submodule)")
|
|
798
|
+
: pkg.isGitRepo
|
|
799
|
+
? chalk.gray(" (git repo)")
|
|
800
|
+
: "";
|
|
556
801
|
console.log(chalk.gray(` - ${pkg.name}`) +
|
|
557
802
|
chalk.gray(` (${pkg.path})`) +
|
|
558
803
|
chalk.gray(` [${pkg.type}]`) +
|
|
559
|
-
|
|
804
|
+
tag);
|
|
560
805
|
}
|
|
561
806
|
console.log("");
|
|
562
807
|
const { useMonorepo } = await inquirer.prompt([
|
|
@@ -1008,14 +1253,33 @@ export async function init(options) {
|
|
|
1008
1253
|
cwd,
|
|
1009
1254
|
stdio: "pipe", // Silent
|
|
1010
1255
|
});
|
|
1011
|
-
// Create bootstrap task only on first init (not re-init for new platforms/devices)
|
|
1012
|
-
if (isFirstInit) {
|
|
1013
|
-
createBootstrapTask(cwd, developerName, projectType, monorepoPackages);
|
|
1014
|
-
}
|
|
1015
1256
|
}
|
|
1016
1257
|
catch {
|
|
1017
1258
|
// Silent failure - user can run init_developer.py manually
|
|
1018
1259
|
}
|
|
1260
|
+
// Three-branch dispatch using flags captured at init() start (before
|
|
1261
|
+
// createWorkflowStructure/init_developer ran, so they reflect the disk
|
|
1262
|
+
// state of the user's checkout, not the state this init just produced):
|
|
1263
|
+
// isFirstInit=true → creator bootstrap (new project)
|
|
1264
|
+
// isFirstInit=false + no .developer file → joiner onboarding (fresh clone)
|
|
1265
|
+
// isFirstInit=false + .developer exists → same-dev re-init, no task
|
|
1266
|
+
//
|
|
1267
|
+
// Runs OUTSIDE the init_developer try/catch (which uses stdio: "pipe")
|
|
1268
|
+
// so joiner failures surface as warnings instead of being silently
|
|
1269
|
+
// swallowed.
|
|
1270
|
+
if (isFirstInit) {
|
|
1271
|
+
createBootstrapTask(cwd, developerName, projectType, monorepoPackages);
|
|
1272
|
+
}
|
|
1273
|
+
else if (!hadDeveloperFileAtStart) {
|
|
1274
|
+
try {
|
|
1275
|
+
if (!createJoinerOnboardingTask(cwd, developerName)) {
|
|
1276
|
+
console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
catch (err) {
|
|
1280
|
+
console.warn(chalk.yellow(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1019
1283
|
}
|
|
1020
1284
|
// Print "What We Solve" section
|
|
1021
1285
|
printWhatWeSolve();
|