@mindfoldhq/trellis 0.5.0-beta.9 → 0.5.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +60 -95
  2. package/dist/cli/index.js +7 -0
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/init.d.ts +3 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +117 -117
  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 +289 -33
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/configurators/antigravity.d.ts.map +1 -1
  12. package/dist/configurators/antigravity.js +2 -8
  13. package/dist/configurators/antigravity.js.map +1 -1
  14. package/dist/configurators/claude.d.ts.map +1 -1
  15. package/dist/configurators/claude.js +4 -10
  16. package/dist/configurators/claude.js.map +1 -1
  17. package/dist/configurators/codebuddy.d.ts.map +1 -1
  18. package/dist/configurators/codebuddy.js +3 -3
  19. package/dist/configurators/codebuddy.js.map +1 -1
  20. package/dist/configurators/codex.d.ts.map +1 -1
  21. package/dist/configurators/codex.js +5 -13
  22. package/dist/configurators/codex.js.map +1 -1
  23. package/dist/configurators/copilot.d.ts.map +1 -1
  24. package/dist/configurators/copilot.js +5 -19
  25. package/dist/configurators/copilot.js.map +1 -1
  26. package/dist/configurators/cursor.d.ts.map +1 -1
  27. package/dist/configurators/cursor.js +3 -3
  28. package/dist/configurators/cursor.js.map +1 -1
  29. package/dist/configurators/droid.d.ts.map +1 -1
  30. package/dist/configurators/droid.js +3 -3
  31. package/dist/configurators/droid.js.map +1 -1
  32. package/dist/configurators/gemini.d.ts.map +1 -1
  33. package/dist/configurators/gemini.js +3 -5
  34. package/dist/configurators/gemini.js.map +1 -1
  35. package/dist/configurators/index.d.ts.map +1 -1
  36. package/dist/configurators/index.js +37 -49
  37. package/dist/configurators/index.js.map +1 -1
  38. package/dist/configurators/kilo.d.ts.map +1 -1
  39. package/dist/configurators/kilo.js +2 -8
  40. package/dist/configurators/kilo.js.map +1 -1
  41. package/dist/configurators/kiro.d.ts.map +1 -1
  42. package/dist/configurators/kiro.js +3 -3
  43. package/dist/configurators/kiro.js.map +1 -1
  44. package/dist/configurators/opencode.d.ts.map +1 -1
  45. package/dist/configurators/opencode.js +7 -4
  46. package/dist/configurators/opencode.js.map +1 -1
  47. package/dist/configurators/pi.d.ts +3 -0
  48. package/dist/configurators/pi.d.ts.map +1 -0
  49. package/dist/configurators/pi.js +44 -0
  50. package/dist/configurators/pi.js.map +1 -0
  51. package/dist/configurators/qoder.d.ts.map +1 -1
  52. package/dist/configurators/qoder.js +3 -5
  53. package/dist/configurators/qoder.js.map +1 -1
  54. package/dist/configurators/shared.d.ts +28 -6
  55. package/dist/configurators/shared.d.ts.map +1 -1
  56. package/dist/configurators/shared.js +47 -15
  57. package/dist/configurators/shared.js.map +1 -1
  58. package/dist/configurators/windsurf.d.ts.map +1 -1
  59. package/dist/configurators/windsurf.js +2 -8
  60. package/dist/configurators/windsurf.js.map +1 -1
  61. package/dist/constants/paths.d.ts +2 -0
  62. package/dist/constants/paths.d.ts.map +1 -1
  63. package/dist/constants/paths.js +2 -0
  64. package/dist/constants/paths.js.map +1 -1
  65. package/dist/migrations/manifests/0.5.0-beta.0.json +2 -0
  66. package/dist/migrations/manifests/0.5.0-beta.10.json +9 -0
  67. package/dist/migrations/manifests/0.5.0-beta.11.json +9 -0
  68. package/dist/migrations/manifests/0.5.0-beta.12.json +9 -0
  69. package/dist/migrations/manifests/0.5.0-beta.13.json +9 -0
  70. package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
  71. package/dist/migrations/manifests/0.5.0-beta.15.json +116 -0
  72. package/dist/migrations/manifests/0.5.0-beta.16.json +9 -0
  73. package/dist/migrations/manifests/0.5.0-beta.17.json +9 -0
  74. package/dist/migrations/manifests/0.5.0-beta.18.json +9 -0
  75. package/dist/migrations/manifests/0.5.0-beta.19.json +9 -0
  76. package/dist/migrations/manifests/0.5.0-beta.5.json +2 -0
  77. package/dist/migrations/manifests/0.5.0-rc.0.json +9 -0
  78. package/dist/templates/claude/agents/trellis-research.md +1 -1
  79. package/dist/templates/claude/settings.json +0 -4
  80. package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
  81. package/dist/templates/codex/agents/trellis-research.toml +3 -2
  82. package/dist/templates/codex/hooks/session-start.py +126 -26
  83. package/dist/templates/codex/skills/finish-work/SKILL.md +41 -109
  84. package/dist/templates/codex/skills/start/SKILL.md +12 -9
  85. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +73 -0
  86. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
  87. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +54 -0
  88. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +81 -0
  89. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
  90. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +78 -0
  91. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
  92. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
  93. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +64 -0
  94. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
  95. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
  96. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
  97. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
  98. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
  99. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +101 -0
  100. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
  101. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
  102. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +79 -0
  103. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
  104. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +59 -0
  105. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +74 -0
  106. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +83 -0
  107. package/dist/templates/common/commands/continue.md +9 -5
  108. package/dist/templates/common/commands/finish-work.md +34 -10
  109. package/dist/templates/common/index.d.ts +22 -2
  110. package/dist/templates/common/index.d.ts.map +1 -1
  111. package/dist/templates/common/index.js +53 -4
  112. package/dist/templates/common/index.js.map +1 -1
  113. package/dist/templates/common/skills/brainstorm.md +3 -0
  114. package/dist/templates/copilot/hooks/session-start.py +127 -30
  115. package/dist/templates/copilot/prompts/finish-work.prompt.md +44 -112
  116. package/dist/templates/copilot/prompts/start.prompt.md +12 -9
  117. package/dist/templates/cursor/agents/trellis-check.md +1 -1
  118. package/dist/templates/cursor/agents/trellis-implement.md +1 -1
  119. package/dist/templates/cursor/agents/trellis-research.md +2 -2
  120. package/dist/templates/cursor/hooks.json +7 -1
  121. package/dist/templates/droid/droids/trellis-research.md +1 -1
  122. package/dist/templates/extract.d.ts +6 -0
  123. package/dist/templates/extract.d.ts.map +1 -1
  124. package/dist/templates/extract.js +14 -0
  125. package/dist/templates/extract.js.map +1 -1
  126. package/dist/templates/gemini/agents/trellis-research.md +1 -1
  127. package/dist/templates/kiro/agents/trellis-research.json +1 -1
  128. package/dist/templates/markdown/agents.md +19 -12
  129. package/dist/templates/markdown/gitignore.txt +3 -0
  130. package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +24 -0
  131. package/dist/templates/opencode/agents/trellis-check.md +1 -1
  132. package/dist/templates/opencode/agents/trellis-implement.md +7 -4
  133. package/dist/templates/opencode/agents/trellis-research.md +2 -2
  134. package/dist/templates/opencode/lib/trellis-context.js +100 -13
  135. package/dist/templates/opencode/plugins/inject-subagent-context.js +70 -5
  136. package/dist/templates/opencode/plugins/inject-workflow-state.js +38 -58
  137. package/dist/templates/opencode/plugins/session-start.js +76 -31
  138. package/dist/templates/pi/agents/trellis-check.md +28 -0
  139. package/dist/templates/pi/agents/trellis-implement.md +33 -0
  140. package/dist/templates/pi/agents/trellis-research.md +25 -0
  141. package/dist/templates/pi/extensions/trellis/index.ts.txt +997 -0
  142. package/dist/templates/pi/index.d.ts +5 -0
  143. package/dist/templates/pi/index.d.ts.map +1 -0
  144. package/dist/templates/pi/index.js +12 -0
  145. package/dist/templates/pi/index.js.map +1 -0
  146. package/dist/templates/pi/settings.json +12 -0
  147. package/dist/templates/qoder/agents/trellis-research.md +1 -1
  148. package/dist/templates/shared-hooks/index.d.ts +31 -0
  149. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  150. package/dist/templates/shared-hooks/index.js +59 -0
  151. package/dist/templates/shared-hooks/index.js.map +1 -1
  152. package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
  153. package/dist/templates/shared-hooks/inject-subagent-context.py +156 -27
  154. package/dist/templates/shared-hooks/inject-workflow-state.py +85 -105
  155. package/dist/templates/shared-hooks/session-start.py +222 -36
  156. package/dist/templates/trellis/gitignore.txt +3 -0
  157. package/dist/templates/trellis/index.d.ts +1 -0
  158. package/dist/templates/trellis/index.d.ts.map +1 -1
  159. package/dist/templates/trellis/index.js +2 -0
  160. package/dist/templates/trellis/index.js.map +1 -1
  161. package/dist/templates/trellis/scripts/common/__init__.py +8 -0
  162. package/dist/templates/trellis/scripts/common/active_task.py +593 -0
  163. package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -14
  164. package/dist/templates/trellis/scripts/common/paths.py +61 -58
  165. package/dist/templates/trellis/scripts/common/session_context.py +12 -0
  166. package/dist/templates/trellis/scripts/common/task_context.py +27 -194
  167. package/dist/templates/trellis/scripts/common/task_store.py +102 -26
  168. package/dist/templates/trellis/scripts/common/tasks.py +4 -1
  169. package/dist/templates/trellis/scripts/common/workflow_phase.py +15 -3
  170. package/dist/templates/trellis/scripts/task.py +99 -34
  171. package/dist/templates/trellis/workflow.md +332 -69
  172. package/dist/types/ai-tools.d.ts +12 -3
  173. package/dist/types/ai-tools.d.ts.map +1 -1
  174. package/dist/types/ai-tools.js +29 -0
  175. package/dist/types/ai-tools.js.map +1 -1
  176. package/dist/utils/file-writer.d.ts.map +1 -1
  177. package/dist/utils/file-writer.js +7 -2
  178. package/dist/utils/file-writer.js.map +1 -1
  179. package/dist/utils/posix.d.ts +13 -0
  180. package/dist/utils/posix.d.ts.map +1 -0
  181. package/dist/utils/posix.js +15 -0
  182. package/dist/utils/posix.js.map +1 -0
  183. package/dist/utils/template-fetcher.d.ts +22 -6
  184. package/dist/utils/template-fetcher.d.ts.map +1 -1
  185. package/dist/utils/template-fetcher.js +405 -27
  186. package/dist/utils/template-fetcher.js.map +1 -1
  187. package/dist/utils/template-hash.d.ts +22 -3
  188. package/dist/utils/template-hash.d.ts.map +1 -1
  189. package/dist/utils/template-hash.js +99 -19
  190. package/dist/utils/template-hash.js.map +1 -1
  191. package/package.json +7 -7
  192. package/dist/templates/shared-hooks/statusline.py +0 -218
@@ -7,6 +7,7 @@ import figlet from "figlet";
7
7
  import inquirer from "inquirer";
8
8
  import { createWorkflowStructure } from "../configurators/workflow.js";
9
9
  import { getInitToolChoices, resolveCliFlag, configurePlatform, getConfiguredPlatforms, getPlatformsWithPythonHooks, } from "../configurators/index.js";
10
+ import { getPythonCommandForPlatform } from "../configurators/shared.js";
10
11
  import { AI_TOOLS } from "../types/ai-tools.js";
11
12
  import { DIR_NAMES, FILE_NAMES, PATHS } from "../constants/paths.js";
12
13
  import { VERSION } from "../constants/version.js";
@@ -17,50 +18,54 @@ import { detectProjectType, detectMonorepo, sanitizePkgName, } from "../utils/pr
17
18
  import { initializeHashes } from "../utils/template-hash.js";
18
19
  import { fetchTemplateIndex, probeRegistryIndex, downloadTemplateById, downloadRegistryDirect, parseRegistrySource, TIMEOUTS, TEMPLATE_INDEX_URL, } from "../utils/template-fetcher.js";
19
20
  import { setupProxy, maskProxyUrl } from "../utils/proxy.js";
20
- /**
21
- * Detect available Python command (python3 or python) and verify version >= 3.10
22
- */
23
- function getPythonCommand() {
24
- const MIN_MAJOR = 3;
25
- const MIN_MINOR = 10;
26
- function checkVersion(cmd) {
27
- try {
28
- const output = execSync(`${cmd} --version`, { stdio: "pipe" })
29
- .toString()
30
- .trim();
31
- const match = output.match(/Python (\d+)\.(\d+)/);
32
- if (!match)
33
- return false;
34
- const [, major, minor] = match.map(Number);
35
- return major > MIN_MAJOR || (major === MIN_MAJOR && minor >= MIN_MINOR);
36
- }
37
- catch {
38
- return false;
39
- }
40
- }
41
- if (checkVersion("python3"))
42
- return "python3";
43
- if (checkVersion("python"))
44
- return "python";
45
- // Check if Python exists but is too old
21
+ const MIN_PYTHON_MAJOR = 3;
22
+ const MIN_PYTHON_MINOR = 9;
23
+ const PYTHON_VERSION_RE = /Python (\d+)\.(\d+)/;
24
+ export function isSupportedPythonVersion(versionOutput) {
25
+ const match = versionOutput.match(PYTHON_VERSION_RE);
26
+ if (!match)
27
+ return false;
28
+ const major = Number(match[1]);
29
+ const minor = Number(match[2]);
30
+ return (major > MIN_PYTHON_MAJOR ||
31
+ (major === MIN_PYTHON_MAJOR && minor >= MIN_PYTHON_MINOR));
32
+ }
33
+ function detectPythonVersion(command) {
46
34
  try {
47
- const output = execSync("python3 --version", { stdio: "pipe" })
48
- .toString()
49
- .trim();
50
- console.warn(chalk.yellow(`⚠️ ${output} detected, but Trellis requires Python ≥ 3.10`));
35
+ return execSync(`${command} --version`, {
36
+ encoding: "utf-8",
37
+ stdio: "pipe",
38
+ }).trim();
51
39
  }
52
40
  catch {
53
- try {
54
- const output = execSync("python --version", { stdio: "pipe" })
55
- .toString()
56
- .trim();
57
- console.warn(chalk.yellow(`⚠️ ${output} detected, but Trellis requires Python ≥ 3.10`));
58
- }
59
- catch {
60
- // No Python at all
61
- }
41
+ return null;
62
42
  }
63
- return "python3";
43
+ }
44
+ export function requireSupportedPython(command) {
45
+ const versionOutput = detectPythonVersion(command);
46
+ if (!versionOutput) {
47
+ throw new Error(`Python command "${command}" not found. Trellis init requires Python ≥ 3.9.`);
48
+ }
49
+ if (!isSupportedPythonVersion(versionOutput)) {
50
+ throw new Error(`${versionOutput} detected via "${command}", but Trellis init requires Python ≥ 3.9.`);
51
+ }
52
+ return versionOutput;
53
+ }
54
+ function getOsDisplayName(platform = process.platform) {
55
+ switch (platform) {
56
+ case "win32":
57
+ return "Windows";
58
+ case "darwin":
59
+ return "macOS";
60
+ case "linux":
61
+ return "Linux";
62
+ default:
63
+ return platform;
64
+ }
65
+ }
66
+ function logPythonAdaptationNotice(command) {
67
+ const osName = getOsDisplayName();
68
+ console.log(chalk.blue(`📌 ${osName} detected: Trellis rendered Python commands as "${command}" in generated hooks, settings, and help text`));
64
69
  }
65
70
  // =============================================================================
66
71
  // Bootstrap Task Creation
@@ -84,7 +89,7 @@ export function slugifyDeveloperName(name) {
84
89
  return slug || "user";
85
90
  }
86
91
  /**
87
- * Write a task skeleton (task.json + prd.md + .current-task pointer).
92
+ * Write a task skeleton (task.json + prd.md).
88
93
  *
89
94
  * Idempotent: if the task dir already exists, returns true without touching
90
95
  * anything. Shared by both creator bootstrap and joiner onboarding flows.
@@ -97,7 +102,6 @@ function writeTaskSkeleton(cwd, taskName, taskJson, prdContent) {
97
102
  fs.mkdirSync(taskDir, { recursive: true });
98
103
  fs.writeFileSync(path.join(taskDir, FILE_NAMES.TASK_JSON), JSON.stringify(taskJson, null, 2), "utf-8");
99
104
  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
105
  return true;
102
106
  }
103
107
  catch {
@@ -141,7 +145,7 @@ function getBootstrapRelatedFiles(projectType, packages) {
141
145
  }
142
146
  return [".trellis/spec/backend/", ".trellis/spec/frontend/"];
143
147
  }
144
- function getBootstrapPrdContent(projectType, packages) {
148
+ function getBootstrapPrdContent(projectType, pythonCmd, packages) {
145
149
  const checklistItems = getBootstrapChecklistItems(projectType, packages);
146
150
  const checklistMarkdown = checklistItems
147
151
  .map((item) => `- [ ] ${item}`)
@@ -151,9 +155,9 @@ function getBootstrapPrdContent(projectType, packages) {
151
155
  **You (the AI) are running this task. The developer does not read this file.**
152
156
 
153
157
  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.
158
+ \`.trellis/\` now exists with empty spec scaffolding, and this bootstrap task
159
+ exists under \`.trellis/tasks/\`. When they want to work on it, they should start
160
+ this task from a session that provides Trellis session identity.
157
161
 
158
162
  **Your job**: help them populate \`.trellis/spec/\` with the team's real
159
163
  coding conventions. Every future AI session — this project's
@@ -270,8 +274,8 @@ When the developer confirms the checklist items above are done with real
270
274
  examples (not placeholders), guide them to run:
271
275
 
272
276
  \`\`\`bash
273
- python3 ./.trellis/scripts/task.py finish
274
- python3 ./.trellis/scripts/task.py archive 00-bootstrap-guidelines
277
+ ${pythonCmd} ./.trellis/scripts/task.py finish
278
+ ${pythonCmd} ./.trellis/scripts/task.py archive 00-bootstrap-guidelines
275
279
  \`\`\`
276
280
 
277
281
  After archive, every new developer who joins this project will get a
@@ -341,9 +345,9 @@ function getBootstrapTaskJson(developer, projectType, packages) {
341
345
  /**
342
346
  * Create bootstrap task for first-time setup
343
347
  */
344
- function createBootstrapTask(cwd, developer, projectType, packages) {
348
+ function createBootstrapTask(cwd, developer, pythonCmd, projectType, packages) {
345
349
  const taskJson = getBootstrapTaskJson(developer, projectType, packages);
346
- const prdContent = getBootstrapPrdContent(projectType, packages);
350
+ const prdContent = getBootstrapPrdContent(projectType, pythonCmd, packages);
347
351
  return writeTaskSkeleton(cwd, BOOTSTRAP_TASK_NAME, taskJson, prdContent);
348
352
  }
349
353
  // =============================================================================
@@ -374,16 +378,16 @@ function getJoinerTaskJson(developer, taskName) {
374
378
  * PRD content for joiner onboarding. Kept concise (~80 lines) — deeper
375
379
  * guidance lives in skills and docs.
376
380
  */
377
- function getJoinerPrdContent(developer) {
381
+ function getJoinerPrdContent(developer, pythonCmd) {
378
382
  const slug = slugifyDeveloperName(developer);
379
383
  return `# Joiner Onboarding Task
380
384
 
381
385
  **You (the AI) are running this task. The developer does not read this file.**
382
386
 
383
387
  \`${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.
388
+ initialized", and will now start asking you questions in chat. This joiner task
389
+ exists under \`.trellis/tasks/\`; when they want to work on it, they should
390
+ start it from a session that provides Trellis session identity.
387
391
 
388
392
  Your job is to orient them to Trellis. Don't dump all of this at them — open
389
393
  with a short greeting, ask where they want to start, and fill in the rest as
@@ -404,8 +408,7 @@ code every session.
404
408
  - **Task lifecycle**: planning → in_progress → done → archive, under
405
409
  \`.trellis/tasks/\`.
406
410
  - **Core slash commands**:
407
- - \`/trellis:continue\` — resume the current task (their primary entry,
408
- since current-task is already set to this onboarding task)
411
+ - \`/trellis:continue\` — resume the current session's active task
409
412
  - \`/trellis:finish-work\` — wrap up a finished task
410
413
  - \`/trellis:start\` — session boot from scratch (not needed here; the
411
414
  SessionStart hook does its job automatically)
@@ -413,7 +416,7 @@ code every session.
413
416
  ### 2. Runtime mechanics (explain when they ask "how does it know what to do")
414
417
 
415
418
  - **SessionStart hook** runs \`get_context.py\` and injects identity, git
416
- status, current-task pointer, active tasks, and workflow phase into the AI
419
+ status, session active task, active tasks, and workflow phase into the AI
417
420
  conversation at every session start.
418
421
  - **\`<workflow-state>\` tag** is auto-injected with every user message,
419
422
  carrying the current task + phase hint.
@@ -428,7 +431,7 @@ code every session.
428
431
  — reviews changes against specs, auto-fixes issues, runs lint/typecheck.
429
432
 
430
433
  File layout (mention when they ask "where does what live"):
431
- - \`.trellis/.current-task\` — session pointer, gitignored, per-checkout
434
+ - \`.trellis/.runtime/sessions/<session>.json\` — session active-task state, gitignored
432
435
  - \`.trellis/tasks/<task>/{implement,check}.jsonl\` — per-task context manifests
433
436
  - \`.trellis/spec/\` — project-wide conventions (source of truth)
434
437
  - \`.trellis/workspace/${developer}/journal-*.md\` — their session log,
@@ -448,7 +451,7 @@ File layout (mention when they ask "where does what live"):
448
451
 
449
452
  - Check if \`.trellis/workspace/${developer}/\` already exists — if yes, it's
450
453
  their journal from another machine and worth mentioning.
451
- - Run \`python3 ./.trellis/scripts/task.py list --assignee ${developer}\` to
454
+ - Run \`${pythonCmd} ./.trellis/scripts/task.py list --assignee ${developer}\` to
452
455
  show tasks assigned to them. (Quote the name if it contains spaces.)
453
456
  - Remind them that the "My Tasks" section appears in the SessionStart context
454
457
  on every new session.
@@ -469,8 +472,8 @@ When they feel oriented (or after you've covered the four topics with
469
472
  reasonable back-and-forth), guide them to run:
470
473
 
471
474
  \`\`\`bash
472
- python3 ./.trellis/scripts/task.py finish
473
- python3 ./.trellis/scripts/task.py archive 00-join-${slug}
475
+ ${pythonCmd} ./.trellis/scripts/task.py finish
476
+ ${pythonCmd} ./.trellis/scripts/task.py archive 00-join-${slug}
474
477
  \`\`\`
475
478
 
476
479
  ---
@@ -488,18 +491,18 @@ hood, summarize the team's spec, or jump to what you're already curious about
488
491
  * project. Task name is slugified to be filesystem-safe for arbitrary
489
492
  * developer names (spaces, Unicode, punctuation).
490
493
  */
491
- function createJoinerOnboardingTask(cwd, developer) {
494
+ function createJoinerOnboardingTask(cwd, developer, pythonCmd) {
492
495
  const slug = slugifyDeveloperName(developer);
493
496
  const taskName = `00-join-${slug}`;
494
497
  const taskJson = getJoinerTaskJson(developer, taskName);
495
- const prdContent = getJoinerPrdContent(developer);
498
+ const prdContent = getJoinerPrdContent(developer, pythonCmd);
496
499
  return writeTaskSkeleton(cwd, taskName, taskJson, prdContent);
497
500
  }
498
501
  /**
499
502
  * Handle re-init when .trellis/ already exists.
500
503
  * Returns true if handled (caller should return), false if user chose full re-init.
501
504
  */
502
- async function handleReinit(cwd, options, developerName) {
505
+ async function handleReinit(cwd, options, developerName, pythonCmd) {
503
506
  const TOOLS = getInitToolChoices();
504
507
  const configuredPlatforms = getConfiguredPlatforms(cwd);
505
508
  const configuredNames = [...configuredPlatforms]
@@ -600,7 +603,6 @@ async function handleReinit(cwd, options, developerName) {
600
603
  // joiner onboarding onto an existing Trellis project.
601
604
  const hadDeveloperFileBefore = fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW, FILE_NAMES.DEVELOPER));
602
605
  try {
603
- const pythonCmd = getPythonCommand();
604
606
  const scriptPath = path.join(cwd, PATHS.SCRIPTS, "init_developer.py");
605
607
  execSync(`${pythonCmd} "${scriptPath}" "${devName}"`, {
606
608
  cwd,
@@ -610,13 +612,13 @@ async function handleReinit(cwd, options, developerName) {
610
612
  }
611
613
  catch {
612
614
  console.log(chalk.yellow("⚠ Could not initialize developer. Run manually:"));
613
- console.log(chalk.gray(` python3 .trellis/scripts/init_developer.py ${devName}`));
615
+ console.log(chalk.gray(` ${pythonCmd} .trellis/scripts/init_developer.py ${devName}`));
614
616
  }
615
617
  // Create joiner onboarding task for fresh checkouts (no prior .developer).
616
618
  // Runs outside the init_developer try/catch so failures surface as warnings.
617
619
  if (!hadDeveloperFileBefore) {
618
620
  try {
619
- if (!createJoinerOnboardingTask(cwd, devName)) {
621
+ if (!createJoinerOnboardingTask(cwd, devName, pythonCmd)) {
620
622
  console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
621
623
  }
622
624
  }
@@ -690,6 +692,12 @@ export async function init(options) {
690
692
  writeMode = "skip";
691
693
  console.log(chalk.gray("Mode: Skip existing files\n"));
692
694
  }
695
+ else if (options.yes) {
696
+ // -y implies non-interactive: never prompt on conflicts. Default to skip
697
+ // (preserve user files) — explicit --force is required to overwrite.
698
+ writeMode = "skip";
699
+ console.log(chalk.gray("Mode: Non-interactive (skip existing files)\n"));
700
+ }
693
701
  setWriteMode(writeMode);
694
702
  // Detect developer name from git config or options
695
703
  let developerName = options.user;
@@ -711,11 +719,22 @@ export async function init(options) {
711
719
  if (developerName) {
712
720
  console.log(chalk.blue("👤 Developer:"), chalk.gray(developerName));
713
721
  }
722
+ const pythonCmd = getPythonCommandForPlatform();
723
+ requireSupportedPython(pythonCmd);
714
724
  // ==========================================================================
715
725
  // Re-init fast path: skip full flow when .trellis/ already exists
716
726
  // ==========================================================================
717
- if (!isFirstInit && !options.force && !options.skipExisting) {
718
- const reinitDone = await handleReinit(cwd, options, developerName);
727
+ // Aborted-init recovery (issue #204): if .trellis/ exists but tasks/ is
728
+ // empty, the previous init never reached bootstrap creation. Fall through
729
+ // to the full flow so the main-dispatch tasksEmpty fallback fires —
730
+ // handleReinit's joiner branch would otherwise mis-route the recovery.
731
+ const tasksDirEarly = path.join(cwd, PATHS.TASKS);
732
+ const tasksEmptyEarly = !fs.existsSync(tasksDirEarly) || fs.readdirSync(tasksDirEarly).length === 0;
733
+ if (!isFirstInit &&
734
+ !options.force &&
735
+ !options.skipExisting &&
736
+ !tasksEmptyEarly) {
737
+ const reinitDone = await handleReinit(cwd, options, developerName, pythonCmd);
719
738
  if (reinitDone)
720
739
  return;
721
740
  // reinitDone === false means user chose "full re-initialize" → fall through
@@ -923,6 +942,7 @@ export async function init(options) {
923
942
  let selectedTemplate = null;
924
943
  // Pre-fetched templates list (used to pass selected SpecTemplate to downloadTemplateById)
925
944
  let fetchedTemplates = [];
945
+ let registryBackend;
926
946
  // Determine the index URL based on registry
927
947
  const indexUrl = registry
928
948
  ? `${registry.rawBaseUrl}/index.json`
@@ -947,10 +967,13 @@ export async function init(options) {
947
967
  process.stdout.write(chalk.gray(` Loading... 0s/${timeoutSec}s`));
948
968
  let templates;
949
969
  let registryProbeNotFound = false;
970
+ let registryProbeError;
950
971
  if (registry) {
951
- const probeResult = await probeRegistryIndex(indexUrl);
972
+ const probeResult = await probeRegistryIndex(indexUrl, registry);
952
973
  templates = probeResult.templates;
953
974
  registryProbeNotFound = probeResult.isNotFound;
975
+ registryProbeError = probeResult.error;
976
+ registryBackend = probeResult.backend;
954
977
  }
955
978
  else {
956
979
  templates = await fetchTemplateIndex(indexUrl);
@@ -965,7 +988,7 @@ export async function init(options) {
965
988
  }
966
989
  else if (templates.length === 0 && registry) {
967
990
  // Custom registry: transient error (not a 404) — abort, don't misclassify
968
- console.log(chalk.red(" Could not reach registry (network issue). Check your connection and try again."));
991
+ console.log(chalk.red(` ${registryProbeError?.message ?? "Could not reach registry. Check your connection and try again."}`));
969
992
  return;
970
993
  }
971
994
  else if (templates.length === 0) {
@@ -1017,8 +1040,9 @@ export async function init(options) {
1017
1040
  // Probe index.json to detect marketplace vs direct download
1018
1041
  const customIndexUrl = `${registry.rawBaseUrl}/index.json`;
1019
1042
  console.log(chalk.gray(` Checking for templates at ${registry.gigetSource}...`));
1020
- const customProbe = await probeRegistryIndex(customIndexUrl);
1043
+ const customProbe = await probeRegistryIndex(customIndexUrl, registry);
1021
1044
  const customTemplates = customProbe.templates;
1045
+ registryBackend = customProbe.backend;
1022
1046
  if (customTemplates.length > 0) {
1023
1047
  // Marketplace mode: show picker with custom templates
1024
1048
  fetchedTemplates = customTemplates;
@@ -1073,7 +1097,7 @@ export async function init(options) {
1073
1097
  }
1074
1098
  else {
1075
1099
  // Transient error (not 404) — loop back, don't misclassify
1076
- console.log(chalk.yellow(" Could not reach registry (network issue). Try again or enter a different source."));
1100
+ console.log(chalk.yellow(` ${customProbe.error?.message ?? "Could not reach registry. Try again or enter a different source."}`));
1077
1101
  registry = undefined; // Reset so we don't fall through to direct download
1078
1102
  }
1079
1103
  }
@@ -1119,7 +1143,8 @@ export async function init(options) {
1119
1143
  // -y mode with --registry (no --template): probe index.json to detect mode
1120
1144
  // Skip when monorepo mode already handled templates above
1121
1145
  if (options.yes && registry && !selectedTemplate && !monorepoPackages) {
1122
- const probeResult = await probeRegistryIndex(`${registry.rawBaseUrl}/index.json`);
1146
+ const probeResult = await probeRegistryIndex(`${registry.rawBaseUrl}/index.json`, registry);
1147
+ registryBackend = probeResult.backend;
1123
1148
  if (probeResult.templates.length > 0) {
1124
1149
  // Marketplace mode requires interactive selection — can't auto-select
1125
1150
  console.log(chalk.red("Error: Registry is a marketplace with multiple templates. " +
@@ -1128,7 +1153,7 @@ export async function init(options) {
1128
1153
  }
1129
1154
  if (!probeResult.isNotFound) {
1130
1155
  // Transient error (not 404) — abort, don't misclassify as direct-download
1131
- console.log(chalk.red("Error: Could not reach registry (network issue). Check your connection and try again."));
1156
+ console.log(chalk.red(`Error: ${probeResult.error?.message ?? "Could not reach registry. Check your connection and try again."}`));
1132
1157
  return;
1133
1158
  }
1134
1159
  // isNotFound=true → no index.json, proceed with direct download (fetchedTemplates stays empty)
@@ -1143,7 +1168,7 @@ export async function init(options) {
1143
1168
  console.log(chalk.gray(" This may take a moment on slow connections."));
1144
1169
  // Find pre-fetched SpecTemplate to avoid double-fetch
1145
1170
  const prefetched = fetchedTemplates.find((t) => t.id === selectedTemplate);
1146
- const result = await downloadTemplateById(cwd, selectedTemplate, templateStrategy, prefetched, registry);
1171
+ const result = await downloadTemplateById(cwd, selectedTemplate, templateStrategy, prefetched, registry, undefined, registryBackend);
1147
1172
  if (result.success) {
1148
1173
  if (result.skipped) {
1149
1174
  console.log(chalk.gray(` ${result.message}`));
@@ -1186,7 +1211,7 @@ export async function init(options) {
1186
1211
  templateStrategy = actionAnswer.action;
1187
1212
  }
1188
1213
  }
1189
- const result = await downloadRegistryDirect(cwd, registry, templateStrategy);
1214
+ const result = await downloadRegistryDirect(cwd, registry, templateStrategy, undefined, registryBackend);
1190
1215
  if (result.success) {
1191
1216
  if (result.skipped) {
1192
1217
  console.log(chalk.gray(` ${result.message}`));
@@ -1229,13 +1254,10 @@ export async function init(options) {
1229
1254
  await configurePlatform(platformId, cwd);
1230
1255
  }
1231
1256
  }
1232
- // Show Windows platform detection notice
1233
- if (process.platform === "win32") {
1234
- const pythonPlatforms = getPlatformsWithPythonHooks();
1235
- const hasSelectedPythonPlatform = pythonPlatforms.some((id) => tools.includes(AI_TOOLS[id].cliFlag));
1236
- if (hasSelectedPythonPlatform) {
1237
- console.log(chalk.yellow('📌 Windows detected: Using "python" for hooks'));
1238
- }
1257
+ const pythonPlatforms = getPlatformsWithPythonHooks();
1258
+ const hasSelectedPythonPlatform = pythonPlatforms.some((id) => tools.includes(AI_TOOLS[id].cliFlag));
1259
+ if (hasSelectedPythonPlatform) {
1260
+ logPythonAdaptationNotice(pythonCmd);
1239
1261
  }
1240
1262
  // Create root files (skip if exists)
1241
1263
  await createRootFiles(cwd);
@@ -1247,7 +1269,6 @@ export async function init(options) {
1247
1269
  // Initialize developer identity (silent - no output)
1248
1270
  if (developerName) {
1249
1271
  try {
1250
- const pythonCmd = getPythonCommand();
1251
1272
  const scriptPath = path.join(cwd, PATHS.SCRIPTS, "init_developer.py");
1252
1273
  execSync(`${pythonCmd} "${scriptPath}" "${developerName}"`, {
1253
1274
  cwd,
@@ -1264,15 +1285,22 @@ export async function init(options) {
1264
1285
  // isFirstInit=false + no .developer file → joiner onboarding (fresh clone)
1265
1286
  // isFirstInit=false + .developer exists → same-dev re-init, no task
1266
1287
  //
1288
+ // Tasks-empty fallback (issue #204): if .trellis/ exists but tasks dir is
1289
+ // empty, the previous init aborted before creating the bootstrap task. Run
1290
+ // bootstrap creation regardless of isFirstInit. writeTaskSkeleton is
1291
+ // idempotent so repeated triggers are safe.
1292
+ //
1267
1293
  // Runs OUTSIDE the init_developer try/catch (which uses stdio: "pipe")
1268
1294
  // so joiner failures surface as warnings instead of being silently
1269
1295
  // swallowed.
1270
- if (isFirstInit) {
1271
- createBootstrapTask(cwd, developerName, projectType, monorepoPackages);
1296
+ const tasksDir = path.join(cwd, PATHS.TASKS);
1297
+ const tasksEmpty = !fs.existsSync(tasksDir) || fs.readdirSync(tasksDir).length === 0;
1298
+ if (isFirstInit || tasksEmpty) {
1299
+ createBootstrapTask(cwd, developerName, pythonCmd, projectType, monorepoPackages);
1272
1300
  }
1273
1301
  else if (!hadDeveloperFileAtStart) {
1274
1302
  try {
1275
- if (!createJoinerOnboardingTask(cwd, developerName)) {
1303
+ if (!createJoinerOnboardingTask(cwd, developerName, pythonCmd)) {
1276
1304
  console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
1277
1305
  }
1278
1306
  }
@@ -1281,8 +1309,6 @@ export async function init(options) {
1281
1309
  }
1282
1310
  }
1283
1311
  }
1284
- // Print "What We Solve" section
1285
- printWhatWeSolve();
1286
1312
  }
1287
1313
  /**
1288
1314
  * Simple readline-based input (no flickering like inquirer)
@@ -1300,37 +1326,11 @@ function askInput(prompt) {
1300
1326
  });
1301
1327
  }
1302
1328
  async function createRootFiles(cwd) {
1303
- const agentsPath = path.join(cwd, "AGENTS.md");
1329
+ const agentsPath = path.join(cwd, FILE_NAMES.AGENTS);
1304
1330
  // Write AGENTS.md from template
1305
1331
  const agentsWritten = await writeFile(agentsPath, agentsMdContent);
1306
1332
  if (agentsWritten) {
1307
1333
  console.log(chalk.blue("📄 Created AGENTS.md"));
1308
1334
  }
1309
1335
  }
1310
- /**
1311
- * Print "What We Solve" section showing Trellis value proposition
1312
- * Styled like a meme/rant to resonate with developer pain points
1313
- */
1314
- function printWhatWeSolve() {
1315
- console.log(chalk.gray("\nSound familiar? ") +
1316
- chalk.bold("You'll never say these again!!\n"));
1317
- // Pain point 1: Bug loop → Thinking Guides + Check Loop
1318
- console.log(chalk.gray("✗ ") + '"Fix A → break B → fix B → break A..."');
1319
- console.log(chalk.green(" ✓ ") +
1320
- chalk.white("Thinking Guides + Check Loop: Think first, verify after"));
1321
- // Pain point 2: Instructions ignored/forgotten → Sub-agents + per-agent spec injection
1322
- console.log(chalk.gray("✗ ") +
1323
- '"Wrote CLAUDE.md, AI ignored it. Reminded AI, it forgot 5 turns later."');
1324
- console.log(chalk.green(" ✓ ") +
1325
- chalk.white("Spec Injection: Rules enforced per task, not per chat"));
1326
- // Pain point 3: Missing connections → Cross-Layer Guide
1327
- console.log(chalk.gray("✗ ") + '"Code works but nothing connects..."');
1328
- console.log(chalk.green(" ✓ ") +
1329
- chalk.white("Cross-Layer Guide: Map data flow before coding"));
1330
- // Pain point 4: Code explosion → Plan Agent
1331
- console.log(chalk.gray("✗ ") + '"Asked for a button, got 9000 lines"');
1332
- console.log(chalk.green(" ✓ ") +
1333
- chalk.white("Plan Agent: Rejects and splits oversized tasks"));
1334
- console.log("");
1335
- }
1336
1336
  //# sourceMappingURL=init.js.map