@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.
Files changed (45) hide show
  1. package/dist/commands/init.d.ts +10 -0
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +380 -116
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/commands/update.d.ts.map +1 -1
  6. package/dist/commands/update.js +6 -21
  7. package/dist/commands/update.js.map +1 -1
  8. package/dist/configurators/index.d.ts.map +1 -1
  9. package/dist/configurators/index.js +8 -7
  10. package/dist/configurators/index.js.map +1 -1
  11. package/dist/configurators/qoder.d.ts +7 -6
  12. package/dist/configurators/qoder.d.ts.map +1 -1
  13. package/dist/configurators/qoder.js +17 -9
  14. package/dist/configurators/qoder.js.map +1 -1
  15. package/dist/configurators/shared.d.ts +2 -0
  16. package/dist/configurators/shared.d.ts.map +1 -1
  17. package/dist/configurators/shared.js +18 -0
  18. package/dist/configurators/shared.js.map +1 -1
  19. package/dist/migrations/manifests/0.5.0-beta.9.json +48 -0
  20. package/dist/templates/common/skills/brainstorm.md +47 -4
  21. package/dist/templates/opencode/plugins/inject-workflow-state.js +21 -7
  22. package/dist/templates/shared-hooks/inject-workflow-state.py +21 -8
  23. package/dist/templates/shared-hooks/session-start.py +14 -4
  24. package/dist/templates/trellis/config.yaml +6 -0
  25. package/dist/templates/trellis/index.d.ts +0 -1
  26. package/dist/templates/trellis/index.d.ts.map +1 -1
  27. package/dist/templates/trellis/index.js +0 -2
  28. package/dist/templates/trellis/index.js.map +1 -1
  29. package/dist/templates/trellis/scripts/common/types.py +0 -2
  30. package/dist/templates/trellis/workflow.md +8 -3
  31. package/dist/utils/project-detector.d.ts +2 -0
  32. package/dist/utils/project-detector.d.ts.map +1 -1
  33. package/dist/utils/project-detector.js +120 -11
  34. package/dist/utils/project-detector.js.map +1 -1
  35. package/dist/utils/task-json.d.ts +46 -0
  36. package/dist/utils/task-json.d.ts.map +1 -0
  37. package/dist/utils/task-json.js +49 -0
  38. package/dist/utils/task-json.js.map +1 -0
  39. package/package.json +1 -1
  40. package/dist/templates/markdown/spec/backend/directory-structure.md +0 -292
  41. package/dist/templates/markdown/spec/backend/index.md +0 -40
  42. package/dist/templates/markdown/spec/backend/script-conventions.md +0 -742
  43. package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +0 -118
  44. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +0 -394
  45. package/dist/templates/trellis/scripts/create_bootstrap.py +0 -298
@@ -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 header = `# Bootstrap: Fill Project Development Guidelines
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
- ## Purpose
151
+ **You (the AI) are running this task. The developer does not read this file.**
72
152
 
73
- Welcome to Trellis! This is your first task.
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
- AI agents use \`.trellis/spec/\` to understand YOUR project's coding conventions.
76
- **Starting from scratch = AI writes generic code that doesn't match your project style.**
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
- Filling these guidelines is a one-time setup that pays off for every future AI session.
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
- ## Your Task
170
+ ## Status (update the checkboxes as you complete each item)
171
+
172
+ ${checklistMarkdown}
173
+
174
+ ---
83
175
 
84
- Fill in the guideline files based on your **existing codebase**.
176
+ ## Spec files to populate
85
177
  `;
86
178
  const backendSection = `
87
179
 
88
- ### Backend Guidelines
180
+ ### Backend guidelines
89
181
 
90
- | File | What to Document |
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 Guidelines
192
+ ### Frontend guidelines
101
193
 
102
- | File | What to Document |
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 Guides (Optional)
205
+ ### Thinking guides (already populated)
114
206
 
115
- The \`.trellis/spec/guides/\` directory contains thinking guides that are already
116
- filled with general best practices. You can customize them for your project if needed.
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 Fill Guidelines
212
+ ## How to fill the spec
121
213
 
122
- ### Step 0: Import from Existing Specs (Recommended)
214
+ ### Step 1: Import from existing convention files first (preferred)
123
215
 
124
- Many projects already have coding conventions documented. **Check these first** before writing from scratch:
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
- If any of these exist, read them first and extract the relevant coding conventions into the corresponding \`.trellis/spec/\` files. This saves significant effort compared to writing everything from scratch.
235
+ ### Step 2: Analyze the codebase for anything not covered by existing docs
142
236
 
143
- ### Step 1: Analyze the Codebase
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
- Ask AI to help discover patterns from actual code:
242
+ ### Step 3: Document reality, not ideals
146
243
 
147
- - "Read all existing config files (CLAUDE.md, .cursorrules, etc.) and extract coding conventions into .trellis/spec/"
148
- - "Analyze my codebase and document the patterns you see"
149
- - "Find error handling / component / API patterns and document them"
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
- ### Step 2: Document Reality, Not Ideals
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
- Write what your codebase **actually does**, not what you wish it did.
154
- AI needs to match existing patterns, not introduce new ones.
251
+ ---
155
252
 
156
- - **Look at existing code** - Find 2-3 examples of each pattern
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
- ## Completion Checklist
265
+ ---
163
266
 
164
- - [ ] Guidelines filled for your project type
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
- ## Why This Matters
280
+ ---
178
281
 
179
- After completing this task:
282
+ ## Suggested opening line
180
283
 
181
- 1. AI will write code that matches your project style
182
- 2. Relevant \`/trellis:before-*-dev\` commands will inject real context
183
- 3. \`/trellis:check-*\` commands will validate against your actual standards
184
- 4. Future developers (human or AI) will onboard faster
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
- let subtasks;
218
- let relatedFiles;
219
- if (packages && packages.length > 0) {
220
- // Monorepo: subtask per package
221
- subtasks = packages.map((pkg) => ({
222
- name: `Fill guidelines for ${pkg.name}`,
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: "Bootstrap Guidelines",
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
- meta: {},
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 taskDir = path.join(cwd, PATHS.TASKS, BOOTSTRAP_TASK_NAME);
276
- const taskRelativePath = `${PATHS.TASKS}/${BOOTSTRAP_TASK_NAME}`;
277
- // Check if already exists
278
- if (fs.existsSync(taskDir)) {
279
- return true; // Already exists, not an error
280
- }
281
- try {
282
- // Create task directory
283
- fs.mkdirSync(taskDir, { recursive: true });
284
- // Write task.json
285
- const taskJson = getBootstrapTaskJson(developer, projectType, packages);
286
- fs.writeFileSync(path.join(taskDir, FILE_NAMES.TASK_JSON), JSON.stringify(taskJson, null, 2), "utf-8");
287
- // Write prd.md
288
- const prdContent = getBootstrapPrdContent(projectType, packages);
289
- fs.writeFileSync(path.join(taskDir, FILE_NAMES.PRD), prdContent, "utf-8");
290
- // Set as current task
291
- const currentTaskFile = path.join(cwd, PATHS.CURRENT_TASK_FILE);
292
- fs.writeFileSync(currentTaskFile, taskRelativePath, "utf-8");
293
- return true;
294
- }
295
- catch {
296
- return false;
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 monorepo configuration found."));
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 sub = pkg.isSubmodule ? chalk.gray(" (submodule)") : "";
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
- sub);
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();