@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.
- package/README.md +60 -95
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +117 -117
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +289 -33
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/antigravity.d.ts.map +1 -1
- package/dist/configurators/antigravity.js +2 -8
- package/dist/configurators/antigravity.js.map +1 -1
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js +4 -10
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/codebuddy.d.ts.map +1 -1
- package/dist/configurators/codebuddy.js +3 -3
- package/dist/configurators/codebuddy.js.map +1 -1
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +5 -13
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts.map +1 -1
- package/dist/configurators/copilot.js +5 -19
- package/dist/configurators/copilot.js.map +1 -1
- package/dist/configurators/cursor.d.ts.map +1 -1
- package/dist/configurators/cursor.js +3 -3
- package/dist/configurators/cursor.js.map +1 -1
- package/dist/configurators/droid.d.ts.map +1 -1
- package/dist/configurators/droid.js +3 -3
- package/dist/configurators/droid.js.map +1 -1
- package/dist/configurators/gemini.d.ts.map +1 -1
- package/dist/configurators/gemini.js +3 -5
- package/dist/configurators/gemini.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +37 -49
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/kilo.d.ts.map +1 -1
- package/dist/configurators/kilo.js +2 -8
- package/dist/configurators/kilo.js.map +1 -1
- package/dist/configurators/kiro.d.ts.map +1 -1
- package/dist/configurators/kiro.js +3 -3
- package/dist/configurators/kiro.js.map +1 -1
- package/dist/configurators/opencode.d.ts.map +1 -1
- package/dist/configurators/opencode.js +7 -4
- package/dist/configurators/opencode.js.map +1 -1
- package/dist/configurators/pi.d.ts +3 -0
- package/dist/configurators/pi.d.ts.map +1 -0
- package/dist/configurators/pi.js +44 -0
- package/dist/configurators/pi.js.map +1 -0
- package/dist/configurators/qoder.d.ts.map +1 -1
- package/dist/configurators/qoder.js +3 -5
- package/dist/configurators/qoder.js.map +1 -1
- package/dist/configurators/shared.d.ts +28 -6
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +47 -15
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/windsurf.d.ts.map +1 -1
- package/dist/configurators/windsurf.js +2 -8
- package/dist/configurators/windsurf.js.map +1 -1
- package/dist/constants/paths.d.ts +2 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +2 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.5.0-beta.0.json +2 -0
- package/dist/migrations/manifests/0.5.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.15.json +116 -0
- package/dist/migrations/manifests/0.5.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.17.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.18.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.19.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.5.json +2 -0
- package/dist/migrations/manifests/0.5.0-rc.0.json +9 -0
- package/dist/templates/claude/agents/trellis-research.md +1 -1
- package/dist/templates/claude/settings.json +0 -4
- package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
- package/dist/templates/codex/agents/trellis-research.toml +3 -2
- package/dist/templates/codex/hooks/session-start.py +126 -26
- package/dist/templates/codex/skills/finish-work/SKILL.md +41 -109
- package/dist/templates/codex/skills/start/SKILL.md +12 -9
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +73 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +54 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +81 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +78 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +64 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +101 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +79 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +59 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +74 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +83 -0
- package/dist/templates/common/commands/continue.md +9 -5
- package/dist/templates/common/commands/finish-work.md +34 -10
- package/dist/templates/common/index.d.ts +22 -2
- package/dist/templates/common/index.d.ts.map +1 -1
- package/dist/templates/common/index.js +53 -4
- package/dist/templates/common/index.js.map +1 -1
- package/dist/templates/common/skills/brainstorm.md +3 -0
- package/dist/templates/copilot/hooks/session-start.py +127 -30
- package/dist/templates/copilot/prompts/finish-work.prompt.md +44 -112
- package/dist/templates/copilot/prompts/start.prompt.md +12 -9
- package/dist/templates/cursor/agents/trellis-check.md +1 -1
- package/dist/templates/cursor/agents/trellis-implement.md +1 -1
- package/dist/templates/cursor/agents/trellis-research.md +2 -2
- package/dist/templates/cursor/hooks.json +7 -1
- package/dist/templates/droid/droids/trellis-research.md +1 -1
- package/dist/templates/extract.d.ts +6 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +14 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/gemini/agents/trellis-research.md +1 -1
- package/dist/templates/kiro/agents/trellis-research.json +1 -1
- package/dist/templates/markdown/agents.md +19 -12
- package/dist/templates/markdown/gitignore.txt +3 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +24 -0
- package/dist/templates/opencode/agents/trellis-check.md +1 -1
- package/dist/templates/opencode/agents/trellis-implement.md +7 -4
- package/dist/templates/opencode/agents/trellis-research.md +2 -2
- package/dist/templates/opencode/lib/trellis-context.js +100 -13
- package/dist/templates/opencode/plugins/inject-subagent-context.js +70 -5
- package/dist/templates/opencode/plugins/inject-workflow-state.js +38 -58
- package/dist/templates/opencode/plugins/session-start.js +76 -31
- package/dist/templates/pi/agents/trellis-check.md +28 -0
- package/dist/templates/pi/agents/trellis-implement.md +33 -0
- package/dist/templates/pi/agents/trellis-research.md +25 -0
- package/dist/templates/pi/extensions/trellis/index.ts.txt +997 -0
- package/dist/templates/pi/index.d.ts +5 -0
- package/dist/templates/pi/index.d.ts.map +1 -0
- package/dist/templates/pi/index.js +12 -0
- package/dist/templates/pi/index.js.map +1 -0
- package/dist/templates/pi/settings.json +12 -0
- package/dist/templates/qoder/agents/trellis-research.md +1 -1
- package/dist/templates/shared-hooks/index.d.ts +31 -0
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +59 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
- package/dist/templates/shared-hooks/inject-subagent-context.py +156 -27
- package/dist/templates/shared-hooks/inject-workflow-state.py +85 -105
- package/dist/templates/shared-hooks/session-start.py +222 -36
- package/dist/templates/trellis/gitignore.txt +3 -0
- package/dist/templates/trellis/index.d.ts +1 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/__init__.py +8 -0
- package/dist/templates/trellis/scripts/common/active_task.py +593 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -14
- package/dist/templates/trellis/scripts/common/paths.py +61 -58
- package/dist/templates/trellis/scripts/common/session_context.py +12 -0
- package/dist/templates/trellis/scripts/common/task_context.py +27 -194
- package/dist/templates/trellis/scripts/common/task_store.py +102 -26
- package/dist/templates/trellis/scripts/common/tasks.py +4 -1
- package/dist/templates/trellis/scripts/common/workflow_phase.py +15 -3
- package/dist/templates/trellis/scripts/task.py +99 -34
- package/dist/templates/trellis/workflow.md +332 -69
- package/dist/types/ai-tools.d.ts +12 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +29 -0
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +7 -2
- package/dist/utils/file-writer.js.map +1 -1
- package/dist/utils/posix.d.ts +13 -0
- package/dist/utils/posix.d.ts.map +1 -0
- package/dist/utils/posix.js +15 -0
- package/dist/utils/posix.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +22 -6
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +405 -27
- package/dist/utils/template-fetcher.js.map +1 -1
- package/dist/utils/template-hash.d.ts +22 -3
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +99 -19
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +7 -7
- package/dist/templates/shared-hooks/statusline.py +0 -218
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
function
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
35
|
+
return execSync(`${command} --version`, {
|
|
36
|
+
encoding: "utf-8",
|
|
37
|
+
stdio: "pipe",
|
|
38
|
+
}).trim();
|
|
51
39
|
}
|
|
52
40
|
catch {
|
|
53
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
274
|
-
|
|
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
|
|
385
|
-
|
|
386
|
-
|
|
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
|
|
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,
|
|
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/.
|
|
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
|
|
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
|
-
|
|
473
|
-
|
|
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(`
|
|
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
|
-
|
|
718
|
-
|
|
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("
|
|
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("
|
|
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(
|
|
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
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
-
|
|
1271
|
-
|
|
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,
|
|
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
|