@mindfoldhq/trellis 0.5.0-beta.8 → 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 +13 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +474 -210
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +295 -54
- 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 +44 -55
- 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 +7 -6
- package/dist/configurators/qoder.d.ts.map +1 -1
- package/dist/configurators/qoder.js +18 -12
- package/dist/configurators/qoder.js.map +1 -1
- package/dist/configurators/shared.d.ts +30 -6
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +65 -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-beta.9.json +48 -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 +50 -4
- 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 -44
- 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 -92
- package/dist/templates/shared-hooks/session-start.py +232 -36
- package/dist/templates/trellis/config.yaml +6 -0
- package/dist/templates/trellis/gitignore.txt +3 -0
- package/dist/templates/trellis/index.d.ts +1 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -2
- 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/types.py +0 -2
- 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 -64
- 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/project-detector.d.ts +2 -0
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +120 -11
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/task-json.d.ts +46 -0
- package/dist/utils/task-json.d.ts.map +1 -0
- package/dist/utils/task-json.js +49 -0
- package/dist/utils/task-json.js.map +1 -0
- package/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/markdown/spec/backend/directory-structure.md +0 -292
- package/dist/templates/markdown/spec/backend/index.md +0 -40
- package/dist/templates/markdown/spec/backend/script-conventions.md +0 -742
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +0 -118
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +0 -394
- package/dist/templates/shared-hooks/statusline.py +0 -218
- package/dist/templates/trellis/scripts/create_bootstrap.py +0 -298
package/dist/commands/init.js
CHANGED
|
@@ -7,87 +7,183 @@ 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";
|
|
13
14
|
import { agentsMdContent } from "../templates/markdown/index.js";
|
|
14
15
|
import { setWriteMode, writeFile, } from "../utils/file-writer.js";
|
|
16
|
+
import { emptyTaskJson } from "../utils/task-json.js";
|
|
15
17
|
import { detectProjectType, detectMonorepo, sanitizePkgName, } from "../utils/project-detector.js";
|
|
16
18
|
import { initializeHashes } from "../utils/template-hash.js";
|
|
17
19
|
import { fetchTemplateIndex, probeRegistryIndex, downloadTemplateById, downloadRegistryDirect, parseRegistrySource, TIMEOUTS, TEMPLATE_INDEX_URL, } from "../utils/template-fetcher.js";
|
|
18
20
|
import { setupProxy, maskProxyUrl } from "../utils/proxy.js";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return false;
|
|
33
|
-
const [, major, minor] = match.map(Number);
|
|
34
|
-
return major > MIN_MAJOR || (major === MIN_MAJOR && minor >= MIN_MINOR);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (checkVersion("python3"))
|
|
41
|
-
return "python3";
|
|
42
|
-
if (checkVersion("python"))
|
|
43
|
-
return "python";
|
|
44
|
-
// 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) {
|
|
45
34
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
return execSync(`${command} --version`, {
|
|
36
|
+
encoding: "utf-8",
|
|
37
|
+
stdio: "pipe",
|
|
38
|
+
}).trim();
|
|
50
39
|
}
|
|
51
40
|
catch {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// No Python at all
|
|
60
|
-
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
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.`);
|
|
61
48
|
}
|
|
62
|
-
|
|
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`));
|
|
63
69
|
}
|
|
64
70
|
// =============================================================================
|
|
65
71
|
// Bootstrap Task Creation
|
|
66
72
|
// =============================================================================
|
|
67
73
|
const BOOTSTRAP_TASK_NAME = "00-bootstrap-guidelines";
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Slugify a developer name for safe use in task directory names.
|
|
76
|
+
*
|
|
77
|
+
* Unlike `sanitizePkgName` (which only strips npm @scope/ prefixes), this
|
|
78
|
+
* handles arbitrary developer input: spaces, Unicode letters, punctuation,
|
|
79
|
+
* path separators. Returns "user" fallback when input slugifies to empty.
|
|
80
|
+
*
|
|
81
|
+
* Exported for unit testing; not part of the public API.
|
|
82
|
+
*/
|
|
83
|
+
export function slugifyDeveloperName(name) {
|
|
84
|
+
const slug = name
|
|
85
|
+
.toLowerCase()
|
|
86
|
+
.normalize("NFKD")
|
|
87
|
+
.replace(/[^\p{Letter}\p{Number}]+/gu, "-")
|
|
88
|
+
.replace(/^-+|-+$/g, "");
|
|
89
|
+
return slug || "user";
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Write a task skeleton (task.json + prd.md).
|
|
93
|
+
*
|
|
94
|
+
* Idempotent: if the task dir already exists, returns true without touching
|
|
95
|
+
* anything. Shared by both creator bootstrap and joiner onboarding flows.
|
|
96
|
+
*/
|
|
97
|
+
function writeTaskSkeleton(cwd, taskName, taskJson, prdContent) {
|
|
98
|
+
const taskDir = path.join(cwd, PATHS.TASKS, taskName);
|
|
99
|
+
if (fs.existsSync(taskDir))
|
|
100
|
+
return true; // idempotent
|
|
101
|
+
try {
|
|
102
|
+
fs.mkdirSync(taskDir, { recursive: true });
|
|
103
|
+
fs.writeFileSync(path.join(taskDir, FILE_NAMES.TASK_JSON), JSON.stringify(taskJson, null, 2), "utf-8");
|
|
104
|
+
fs.writeFileSync(path.join(taskDir, FILE_NAMES.PRD), prdContent, "utf-8");
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Compute the bootstrap checklist items (previously stored as structured
|
|
113
|
+
* `subtasks: [{name, status}]` in task.json). Per task 04-21-task-schema-unify
|
|
114
|
+
* (D1), these live as markdown `- [ ]` items in prd.md instead, so task.json
|
|
115
|
+
* stays canonical with `subtasks: string[]` (child task dir names, same as
|
|
116
|
+
* task_store.py).
|
|
117
|
+
*/
|
|
118
|
+
function getBootstrapChecklistItems(projectType, packages) {
|
|
119
|
+
if (packages && packages.length > 0) {
|
|
120
|
+
const items = packages.map((pkg) => `Fill guidelines for ${pkg.name}`);
|
|
121
|
+
items.push("Add code examples");
|
|
122
|
+
return items;
|
|
123
|
+
}
|
|
124
|
+
if (projectType === "frontend") {
|
|
125
|
+
return ["Fill frontend guidelines", "Add code examples"];
|
|
126
|
+
}
|
|
127
|
+
if (projectType === "backend") {
|
|
128
|
+
return ["Fill backend guidelines", "Add code examples"];
|
|
129
|
+
}
|
|
130
|
+
return [
|
|
131
|
+
"Fill backend guidelines",
|
|
132
|
+
"Fill frontend guidelines",
|
|
133
|
+
"Add code examples",
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
function getBootstrapRelatedFiles(projectType, packages) {
|
|
137
|
+
if (packages && packages.length > 0) {
|
|
138
|
+
return packages.map((pkg) => `.trellis/spec/${sanitizePkgName(pkg.name)}/`);
|
|
139
|
+
}
|
|
140
|
+
if (projectType === "frontend") {
|
|
141
|
+
return [".trellis/spec/frontend/"];
|
|
142
|
+
}
|
|
143
|
+
if (projectType === "backend") {
|
|
144
|
+
return [".trellis/spec/backend/"];
|
|
145
|
+
}
|
|
146
|
+
return [".trellis/spec/backend/", ".trellis/spec/frontend/"];
|
|
147
|
+
}
|
|
148
|
+
function getBootstrapPrdContent(projectType, pythonCmd, packages) {
|
|
149
|
+
const checklistItems = getBootstrapChecklistItems(projectType, packages);
|
|
150
|
+
const checklistMarkdown = checklistItems
|
|
151
|
+
.map((item) => `- [ ] ${item}`)
|
|
152
|
+
.join("\n");
|
|
153
|
+
const header = `# Bootstrap Task: Fill Project Development Guidelines
|
|
70
154
|
|
|
71
|
-
|
|
155
|
+
**You (the AI) are running this task. The developer does not read this file.**
|
|
72
156
|
|
|
73
|
-
|
|
157
|
+
The developer just ran \`trellis init\` on this project for the first time.
|
|
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.
|
|
74
161
|
|
|
75
|
-
|
|
76
|
-
|
|
162
|
+
**Your job**: help them populate \`.trellis/spec/\` with the team's real
|
|
163
|
+
coding conventions. Every future AI session — this project's
|
|
164
|
+
\`trellis-implement\` and \`trellis-check\` sub-agents — auto-loads spec files
|
|
165
|
+
listed in per-task jsonl manifests. Empty spec = sub-agents write generic
|
|
166
|
+
code. Real spec = sub-agents match the team's actual patterns.
|
|
77
167
|
|
|
78
|
-
|
|
168
|
+
Don't dump instructions. Open with a short greeting, figure out if the repo
|
|
169
|
+
has any existing convention docs (CLAUDE.md, .cursorrules, etc.), and drive
|
|
170
|
+
the rest conversationally.
|
|
79
171
|
|
|
80
172
|
---
|
|
81
173
|
|
|
82
|
-
##
|
|
174
|
+
## Status (update the checkboxes as you complete each item)
|
|
83
175
|
|
|
84
|
-
|
|
176
|
+
${checklistMarkdown}
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Spec files to populate
|
|
85
181
|
`;
|
|
86
182
|
const backendSection = `
|
|
87
183
|
|
|
88
|
-
### Backend
|
|
184
|
+
### Backend guidelines
|
|
89
185
|
|
|
90
|
-
| File | What to
|
|
186
|
+
| File | What to document |
|
|
91
187
|
|------|------------------|
|
|
92
188
|
| \`.trellis/spec/backend/directory-structure.md\` | Where different file types go (routes, services, utils) |
|
|
93
189
|
| \`.trellis/spec/backend/database-guidelines.md\` | ORM, migrations, query patterns, naming conventions |
|
|
@@ -97,9 +193,9 @@ Fill in the guideline files based on your **existing codebase**.
|
|
|
97
193
|
`;
|
|
98
194
|
const frontendSection = `
|
|
99
195
|
|
|
100
|
-
### Frontend
|
|
196
|
+
### Frontend guidelines
|
|
101
197
|
|
|
102
|
-
| File | What to
|
|
198
|
+
| File | What to document |
|
|
103
199
|
|------|------------------|
|
|
104
200
|
| \`.trellis/spec/frontend/directory-structure.md\` | Component/page/hook organization |
|
|
105
201
|
| \`.trellis/spec/frontend/component-guidelines.md\` | Component patterns, props conventions |
|
|
@@ -110,18 +206,20 @@ Fill in the guideline files based on your **existing codebase**.
|
|
|
110
206
|
`;
|
|
111
207
|
const footer = `
|
|
112
208
|
|
|
113
|
-
### Thinking
|
|
209
|
+
### Thinking guides (already populated)
|
|
114
210
|
|
|
115
|
-
|
|
116
|
-
|
|
211
|
+
\`.trellis/spec/guides/\` contains general thinking guides pre-filled with
|
|
212
|
+
best practices. Customize only if something clearly doesn't fit this project.
|
|
117
213
|
|
|
118
214
|
---
|
|
119
215
|
|
|
120
|
-
## How to
|
|
216
|
+
## How to fill the spec
|
|
121
217
|
|
|
122
|
-
### Step
|
|
218
|
+
### Step 1: Import from existing convention files first (preferred)
|
|
123
219
|
|
|
124
|
-
|
|
220
|
+
Search the repo for existing convention docs. If any exist, read them and
|
|
221
|
+
extract the relevant rules into the matching \`.trellis/spec/\` files —
|
|
222
|
+
usually much faster than documenting from scratch.
|
|
125
223
|
|
|
126
224
|
| File / Directory | Tool |
|
|
127
225
|
|------|------|
|
|
@@ -138,50 +236,60 @@ Many projects already have coding conventions documented. **Check these first**
|
|
|
138
236
|
| \`CONTRIBUTING.md\` | General project conventions |
|
|
139
237
|
| \`.editorconfig\` | Editor formatting rules |
|
|
140
238
|
|
|
141
|
-
|
|
239
|
+
### Step 2: Analyze the codebase for anything not covered by existing docs
|
|
142
240
|
|
|
143
|
-
|
|
241
|
+
Scan real code to discover patterns. Before writing each spec file:
|
|
242
|
+
- Find 2-3 real examples of each pattern in the codebase.
|
|
243
|
+
- Reference real file paths (not hypothetical ones).
|
|
244
|
+
- Document anti-patterns the team clearly avoids.
|
|
144
245
|
|
|
145
|
-
|
|
246
|
+
### Step 3: Document reality, not ideals
|
|
146
247
|
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
|
|
248
|
+
**Critical**: write what the code *actually does*, not what it should do.
|
|
249
|
+
Sub-agents match the spec, so aspirational patterns that don't exist in the
|
|
250
|
+
codebase will cause sub-agents to write code that looks out of place.
|
|
150
251
|
|
|
151
|
-
|
|
252
|
+
If the team has known tech debt, document the current state — improvement
|
|
253
|
+
is a separate conversation, not a bootstrap concern.
|
|
152
254
|
|
|
153
|
-
|
|
154
|
-
AI needs to match existing patterns, not introduce new ones.
|
|
255
|
+
---
|
|
155
256
|
|
|
156
|
-
|
|
157
|
-
- **Include file paths** - Reference real files as examples
|
|
158
|
-
- **List anti-patterns** - What does your team avoid?
|
|
257
|
+
## Quick explainer of the runtime (share when they ask "why do we need spec at all")
|
|
159
258
|
|
|
160
|
-
|
|
259
|
+
- Every AI coding task spawns two sub-agents: \`trellis-implement\` (writes
|
|
260
|
+
code) and \`trellis-check\` (verifies quality).
|
|
261
|
+
- Each task has \`implement.jsonl\` / \`check.jsonl\` manifests listing which
|
|
262
|
+
spec files to load.
|
|
263
|
+
- The platform hook auto-injects those spec files + the task's \`prd.md\`
|
|
264
|
+
into every sub-agent prompt, so the sub-agent codes/reviews per team
|
|
265
|
+
conventions without anyone pasting them manually.
|
|
266
|
+
- Source of truth: \`.trellis/spec/\`. That's why filling it well now pays
|
|
267
|
+
off forever.
|
|
161
268
|
|
|
162
|
-
|
|
269
|
+
---
|
|
163
270
|
|
|
164
|
-
|
|
165
|
-
- [ ] At least 2-3 real code examples in each guideline
|
|
166
|
-
- [ ] Anti-patterns documented
|
|
271
|
+
## Completion
|
|
167
272
|
|
|
168
|
-
When done
|
|
273
|
+
When the developer confirms the checklist items above are done with real
|
|
274
|
+
examples (not placeholders), guide them to run:
|
|
169
275
|
|
|
170
276
|
\`\`\`bash
|
|
171
|
-
|
|
172
|
-
|
|
277
|
+
${pythonCmd} ./.trellis/scripts/task.py finish
|
|
278
|
+
${pythonCmd} ./.trellis/scripts/task.py archive 00-bootstrap-guidelines
|
|
173
279
|
\`\`\`
|
|
174
280
|
|
|
175
|
-
|
|
281
|
+
After archive, every new developer who joins this project will get a
|
|
282
|
+
\`00-join-<slug>\` onboarding task instead of this bootstrap task.
|
|
176
283
|
|
|
177
|
-
|
|
284
|
+
---
|
|
178
285
|
|
|
179
|
-
|
|
286
|
+
## Suggested opening line
|
|
180
287
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
288
|
+
"Welcome to Trellis! Your init just set me up to help you fill the project
|
|
289
|
+
spec — a one-time setup so every future AI session follows the team's
|
|
290
|
+
conventions instead of writing generic code. Before we start, do you have
|
|
291
|
+
any existing convention docs (CLAUDE.md, .cursorrules, CONTRIBUTING.md,
|
|
292
|
+
etc.) I can pull from, or should I scan the codebase from scratch?"
|
|
185
293
|
`;
|
|
186
294
|
let content = header;
|
|
187
295
|
if (packages && packages.length > 0) {
|
|
@@ -214,43 +322,15 @@ After completing this task:
|
|
|
214
322
|
}
|
|
215
323
|
function getBootstrapTaskJson(developer, projectType, packages) {
|
|
216
324
|
const today = new Date().toISOString().split("T")[0];
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
status: "pending",
|
|
224
|
-
}));
|
|
225
|
-
subtasks.push({ name: "Add code examples", status: "pending" });
|
|
226
|
-
relatedFiles = packages.map((pkg) => `.trellis/spec/${sanitizePkgName(pkg.name)}/`);
|
|
227
|
-
}
|
|
228
|
-
else if (projectType === "frontend") {
|
|
229
|
-
subtasks = [
|
|
230
|
-
{ name: "Fill frontend guidelines", status: "pending" },
|
|
231
|
-
{ name: "Add code examples", status: "pending" },
|
|
232
|
-
];
|
|
233
|
-
relatedFiles = [".trellis/spec/frontend/"];
|
|
234
|
-
}
|
|
235
|
-
else if (projectType === "backend") {
|
|
236
|
-
subtasks = [
|
|
237
|
-
{ name: "Fill backend guidelines", status: "pending" },
|
|
238
|
-
{ name: "Add code examples", status: "pending" },
|
|
239
|
-
];
|
|
240
|
-
relatedFiles = [".trellis/spec/backend/"];
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
// fullstack
|
|
244
|
-
subtasks = [
|
|
245
|
-
{ name: "Fill backend guidelines", status: "pending" },
|
|
246
|
-
{ name: "Fill frontend guidelines", status: "pending" },
|
|
247
|
-
{ name: "Add code examples", status: "pending" },
|
|
248
|
-
];
|
|
249
|
-
relatedFiles = [".trellis/spec/backend/", ".trellis/spec/frontend/"];
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
325
|
+
const relatedFiles = getBootstrapRelatedFiles(projectType, packages);
|
|
326
|
+
// Canonical 24-field shape via emptyTaskJson factory.
|
|
327
|
+
// Checklist items (previously stored as structured `subtasks`) are now
|
|
328
|
+
// rendered as `- [ ]` items in prd.md; task.json.subtasks is always
|
|
329
|
+
// string[] (child task dir names) per the canonical schema.
|
|
330
|
+
return emptyTaskJson({
|
|
252
331
|
id: BOOTSTRAP_TASK_NAME,
|
|
253
|
-
name:
|
|
332
|
+
name: BOOTSTRAP_TASK_NAME,
|
|
333
|
+
title: "Bootstrap Guidelines",
|
|
254
334
|
description: "Fill in project development guidelines for AI agents",
|
|
255
335
|
status: "in_progress",
|
|
256
336
|
dev_type: "docs",
|
|
@@ -258,49 +338,171 @@ function getBootstrapTaskJson(developer, projectType, packages) {
|
|
|
258
338
|
creator: developer,
|
|
259
339
|
assignee: developer,
|
|
260
340
|
createdAt: today,
|
|
261
|
-
completedAt: null,
|
|
262
|
-
commit: null,
|
|
263
|
-
subtasks,
|
|
264
|
-
children: [],
|
|
265
|
-
parent: null,
|
|
266
341
|
relatedFiles,
|
|
267
342
|
notes: `First-time setup task created by trellis init (${projectType} project)`,
|
|
268
|
-
|
|
269
|
-
};
|
|
343
|
+
});
|
|
270
344
|
}
|
|
271
345
|
/**
|
|
272
346
|
* Create bootstrap task for first-time setup
|
|
273
347
|
*/
|
|
274
|
-
function createBootstrapTask(cwd, developer, projectType, packages) {
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
348
|
+
function createBootstrapTask(cwd, developer, pythonCmd, projectType, packages) {
|
|
349
|
+
const taskJson = getBootstrapTaskJson(developer, projectType, packages);
|
|
350
|
+
const prdContent = getBootstrapPrdContent(projectType, pythonCmd, packages);
|
|
351
|
+
return writeTaskSkeleton(cwd, BOOTSTRAP_TASK_NAME, taskJson, prdContent);
|
|
352
|
+
}
|
|
353
|
+
// =============================================================================
|
|
354
|
+
// Joiner Onboarding Task Creation
|
|
355
|
+
// =============================================================================
|
|
356
|
+
/**
|
|
357
|
+
* task.json factory for joiner onboarding. Mirrors the bootstrap factory but
|
|
358
|
+
* uses dev_type "docs", higher priority "P1", and the developer-specific task
|
|
359
|
+
* name (so multiple joiners in the same checkout don't collide).
|
|
360
|
+
*/
|
|
361
|
+
function getJoinerTaskJson(developer, taskName) {
|
|
362
|
+
const today = new Date().toISOString().split("T")[0];
|
|
363
|
+
return emptyTaskJson({
|
|
364
|
+
id: taskName,
|
|
365
|
+
name: taskName,
|
|
366
|
+
title: `Joining: Onboard to this Trellis project (${developer})`,
|
|
367
|
+
description: "Onboard a new developer to an existing Trellis project: learn the workflow, conventions, and find assigned work",
|
|
368
|
+
status: "in_progress",
|
|
369
|
+
dev_type: "docs",
|
|
370
|
+
priority: "P1",
|
|
371
|
+
creator: developer,
|
|
372
|
+
assignee: developer,
|
|
373
|
+
createdAt: today,
|
|
374
|
+
notes: "Generated by trellis init for a new developer joining an existing Trellis project",
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* PRD content for joiner onboarding. Kept concise (~80 lines) — deeper
|
|
379
|
+
* guidance lives in skills and docs.
|
|
380
|
+
*/
|
|
381
|
+
function getJoinerPrdContent(developer, pythonCmd) {
|
|
382
|
+
const slug = slugifyDeveloperName(developer);
|
|
383
|
+
return `# Joiner Onboarding Task
|
|
384
|
+
|
|
385
|
+
**You (the AI) are running this task. The developer does not read this file.**
|
|
386
|
+
|
|
387
|
+
\`${developer}\` just ran \`trellis init\` on a fresh clone, saw "Developer
|
|
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.
|
|
391
|
+
|
|
392
|
+
Your job is to orient them to Trellis. Don't dump all of this at them — open
|
|
393
|
+
with a short greeting, ask where they want to start, and fill in the rest as
|
|
394
|
+
they engage.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Topics to cover (adapt order to their questions)
|
|
399
|
+
|
|
400
|
+
### 1. What Trellis is + the workflow
|
|
401
|
+
|
|
402
|
+
Trellis is a workflow layer over Claude Code / Cursor / etc. that keeps AI
|
|
403
|
+
agents consistent with project-specific conventions instead of writing generic
|
|
404
|
+
code every session.
|
|
405
|
+
|
|
406
|
+
- **Three phases**: Plan (brainstorm → \`prd.md\`) → Execute (code + check) →
|
|
407
|
+
Finish (capture + wrap). Full reference: \`.trellis/workflow.md\`.
|
|
408
|
+
- **Task lifecycle**: planning → in_progress → done → archive, under
|
|
409
|
+
\`.trellis/tasks/\`.
|
|
410
|
+
- **Core slash commands**:
|
|
411
|
+
- \`/trellis:continue\` — resume the current session's active task
|
|
412
|
+
- \`/trellis:finish-work\` — wrap up a finished task
|
|
413
|
+
- \`/trellis:start\` — session boot from scratch (not needed here; the
|
|
414
|
+
SessionStart hook does its job automatically)
|
|
415
|
+
|
|
416
|
+
### 2. Runtime mechanics (explain when they ask "how does it know what to do")
|
|
417
|
+
|
|
418
|
+
- **SessionStart hook** runs \`get_context.py\` and injects identity, git
|
|
419
|
+
status, session active task, active tasks, and workflow phase into the AI
|
|
420
|
+
conversation at every session start.
|
|
421
|
+
- **\`<workflow-state>\` tag** is auto-injected with every user message,
|
|
422
|
+
carrying the current task + phase hint.
|
|
423
|
+
- **\`/trellis:continue\`** loads the Phase Index, reads \`prd.md\` + recent
|
|
424
|
+
activity, and routes to the right skill (\`trellis-brainstorm\` for planning,
|
|
425
|
+
\`trellis-implement\` for coding, \`trellis-check\` for verification).
|
|
426
|
+
- **\`trellis-implement\` sub-agent** is spawned when code needs to be written.
|
|
427
|
+
The platform hook reads \`{TASK_DIR}/implement.jsonl\` and auto-injects those
|
|
428
|
+
spec files + \`prd.md\` into the sub-agent's prompt so it codes per project
|
|
429
|
+
conventions.
|
|
430
|
+
- **\`trellis-check\` sub-agent** follows the same pattern with \`check.jsonl\`
|
|
431
|
+
— reviews changes against specs, auto-fixes issues, runs lint/typecheck.
|
|
432
|
+
|
|
433
|
+
File layout (mention when they ask "where does what live"):
|
|
434
|
+
- \`.trellis/.runtime/sessions/<session>.json\` — session active-task state, gitignored
|
|
435
|
+
- \`.trellis/tasks/<task>/{implement,check}.jsonl\` — per-task context manifests
|
|
436
|
+
- \`.trellis/spec/\` — project-wide conventions (source of truth)
|
|
437
|
+
- \`.trellis/workspace/${developer}/journal-*.md\` — their session log,
|
|
438
|
+
rotated at ~2000 lines
|
|
439
|
+
|
|
440
|
+
### 3. This project's actual conventions
|
|
441
|
+
|
|
442
|
+
- Summarize \`.trellis/spec/\` for them — what coding conventions this
|
|
443
|
+
specific team enforces.
|
|
444
|
+
- Point at the last 5 entries in \`.trellis/tasks/archive/\` as a rhythm
|
|
445
|
+
example of how people actually work here. **If archive is empty** (the
|
|
446
|
+
project just started), skip this — don't invent examples.
|
|
447
|
+
- Not your job in this onboarding to teach them the business code itself —
|
|
448
|
+
the README and their teammates handle that.
|
|
449
|
+
|
|
450
|
+
### 4. Their assigned work
|
|
451
|
+
|
|
452
|
+
- Check if \`.trellis/workspace/${developer}/\` already exists — if yes, it's
|
|
453
|
+
their journal from another machine and worth mentioning.
|
|
454
|
+
- Run \`${pythonCmd} ./.trellis/scripts/task.py list --assignee ${developer}\` to
|
|
455
|
+
show tasks assigned to them. (Quote the name if it contains spaces.)
|
|
456
|
+
- Remind them that the "My Tasks" section appears in the SessionStart context
|
|
457
|
+
on every new session.
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Optional: walk through a small task end-to-end
|
|
462
|
+
|
|
463
|
+
If they want to practice before touching real work, offer to pick a tiny
|
|
464
|
+
P3 task or a typo fix and run the full cycle together: \`/trellis:continue\`
|
|
465
|
+
→ you implement via sub-agents → \`/trellis:finish-work\`.
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Completion
|
|
470
|
+
|
|
471
|
+
When they feel oriented (or after you've covered the four topics with
|
|
472
|
+
reasonable back-and-forth), guide them to run:
|
|
473
|
+
|
|
474
|
+
\`\`\`bash
|
|
475
|
+
${pythonCmd} ./.trellis/scripts/task.py finish
|
|
476
|
+
${pythonCmd} ./.trellis/scripts/task.py archive 00-join-${slug}
|
|
477
|
+
\`\`\`
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Suggested opening line
|
|
482
|
+
|
|
483
|
+
"Welcome! Your \`trellis init\` set me up to onboard you to this project. I
|
|
484
|
+
can walk you through the workflow, show you the runtime mechanics under the
|
|
485
|
+
hood, summarize the team's spec, or jump to what you're already curious about
|
|
486
|
+
— which would you prefer?"
|
|
487
|
+
`;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Create joiner onboarding task for a new developer on an existing Trellis
|
|
491
|
+
* project. Task name is slugified to be filesystem-safe for arbitrary
|
|
492
|
+
* developer names (spaces, Unicode, punctuation).
|
|
493
|
+
*/
|
|
494
|
+
function createJoinerOnboardingTask(cwd, developer, pythonCmd) {
|
|
495
|
+
const slug = slugifyDeveloperName(developer);
|
|
496
|
+
const taskName = `00-join-${slug}`;
|
|
497
|
+
const taskJson = getJoinerTaskJson(developer, taskName);
|
|
498
|
+
const prdContent = getJoinerPrdContent(developer, pythonCmd);
|
|
499
|
+
return writeTaskSkeleton(cwd, taskName, taskJson, prdContent);
|
|
298
500
|
}
|
|
299
501
|
/**
|
|
300
502
|
* Handle re-init when .trellis/ already exists.
|
|
301
503
|
* Returns true if handled (caller should return), false if user chose full re-init.
|
|
302
504
|
*/
|
|
303
|
-
async function handleReinit(cwd, options, developerName) {
|
|
505
|
+
async function handleReinit(cwd, options, developerName, pythonCmd) {
|
|
304
506
|
const TOOLS = getInitToolChoices();
|
|
305
507
|
const configuredPlatforms = getConfiguredPlatforms(cwd);
|
|
306
508
|
const configuredNames = [...configuredPlatforms]
|
|
@@ -396,8 +598,11 @@ async function handleReinit(cwd, options, developerName) {
|
|
|
396
598
|
devName = await askInput("Your name: ");
|
|
397
599
|
}
|
|
398
600
|
}
|
|
601
|
+
// Capture pre-init state: if .developer did not exist before we ran
|
|
602
|
+
// init_developer.py, this checkout had no identity → treat as a new
|
|
603
|
+
// joiner onboarding onto an existing Trellis project.
|
|
604
|
+
const hadDeveloperFileBefore = fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW, FILE_NAMES.DEVELOPER));
|
|
399
605
|
try {
|
|
400
|
-
const pythonCmd = getPythonCommand();
|
|
401
606
|
const scriptPath = path.join(cwd, PATHS.SCRIPTS, "init_developer.py");
|
|
402
607
|
execSync(`${pythonCmd} "${scriptPath}" "${devName}"`, {
|
|
403
608
|
cwd,
|
|
@@ -407,7 +612,19 @@ async function handleReinit(cwd, options, developerName) {
|
|
|
407
612
|
}
|
|
408
613
|
catch {
|
|
409
614
|
console.log(chalk.yellow("⚠ Could not initialize developer. Run manually:"));
|
|
410
|
-
console.log(chalk.gray(`
|
|
615
|
+
console.log(chalk.gray(` ${pythonCmd} .trellis/scripts/init_developer.py ${devName}`));
|
|
616
|
+
}
|
|
617
|
+
// Create joiner onboarding task for fresh checkouts (no prior .developer).
|
|
618
|
+
// Runs outside the init_developer try/catch so failures surface as warnings.
|
|
619
|
+
if (!hadDeveloperFileBefore) {
|
|
620
|
+
try {
|
|
621
|
+
if (!createJoinerOnboardingTask(cwd, devName, pythonCmd)) {
|
|
622
|
+
console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
console.warn(chalk.yellow(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
627
|
+
}
|
|
411
628
|
}
|
|
412
629
|
}
|
|
413
630
|
return true;
|
|
@@ -438,6 +655,9 @@ function writeMonorepoConfig(cwd, packages) {
|
|
|
438
655
|
if (pkg.isSubmodule) {
|
|
439
656
|
lines.push(" type: submodule");
|
|
440
657
|
}
|
|
658
|
+
else if (pkg.isGitRepo) {
|
|
659
|
+
lines.push(" git: true");
|
|
660
|
+
}
|
|
441
661
|
}
|
|
442
662
|
// Use first non-submodule package as default, fallback to first package
|
|
443
663
|
const defaultPkg = packages.find((p) => !p.isSubmodule)?.name ?? packages[0]?.name;
|
|
@@ -449,6 +669,10 @@ function writeMonorepoConfig(cwd, packages) {
|
|
|
449
669
|
export async function init(options) {
|
|
450
670
|
const cwd = process.cwd();
|
|
451
671
|
const isFirstInit = !fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW));
|
|
672
|
+
// Captured here (before createWorkflowStructure + init_developer run) so
|
|
673
|
+
// the three-branch dispatch at the bottom can tell "fresh clone joiner"
|
|
674
|
+
// (.trellis/ exists, .developer missing) apart from "creator first init".
|
|
675
|
+
const hadDeveloperFileAtStart = fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW, FILE_NAMES.DEVELOPER));
|
|
452
676
|
// Generate ASCII art banner dynamically using FIGlet "Rebel" font
|
|
453
677
|
const banner = figlet.textSync("Trellis", { font: "Rebel" });
|
|
454
678
|
console.log(chalk.cyan(`\n${banner.trimEnd()}`));
|
|
@@ -468,6 +692,12 @@ export async function init(options) {
|
|
|
468
692
|
writeMode = "skip";
|
|
469
693
|
console.log(chalk.gray("Mode: Skip existing files\n"));
|
|
470
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
|
+
}
|
|
471
701
|
setWriteMode(writeMode);
|
|
472
702
|
// Detect developer name from git config or options
|
|
473
703
|
let developerName = options.user;
|
|
@@ -489,11 +719,22 @@ export async function init(options) {
|
|
|
489
719
|
if (developerName) {
|
|
490
720
|
console.log(chalk.blue("👤 Developer:"), chalk.gray(developerName));
|
|
491
721
|
}
|
|
722
|
+
const pythonCmd = getPythonCommandForPlatform();
|
|
723
|
+
requireSupportedPython(pythonCmd);
|
|
492
724
|
// ==========================================================================
|
|
493
725
|
// Re-init fast path: skip full flow when .trellis/ already exists
|
|
494
726
|
// ==========================================================================
|
|
495
|
-
|
|
496
|
-
|
|
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);
|
|
497
738
|
if (reinitDone)
|
|
498
739
|
return;
|
|
499
740
|
// reinitDone === false means user chose "full re-initialize" → fall through
|
|
@@ -540,7 +781,26 @@ export async function init(options) {
|
|
|
540
781
|
// options.monorepo: true = --monorepo, false = --no-monorepo, undefined = auto
|
|
541
782
|
const detected = detectMonorepo(cwd);
|
|
542
783
|
if (options.monorepo === true && !detected) {
|
|
543
|
-
console.log(chalk.red("Error: --monorepo specified but no
|
|
784
|
+
console.log(chalk.red("Error: --monorepo specified but no multi-package layout detected."));
|
|
785
|
+
console.log("");
|
|
786
|
+
console.log(chalk.gray("Checked:"));
|
|
787
|
+
console.log(chalk.gray(" ✗ pnpm-workspace.yaml"));
|
|
788
|
+
console.log(chalk.gray(" ✗ package.json workspaces"));
|
|
789
|
+
console.log(chalk.gray(" ✗ Cargo.toml [workspace]"));
|
|
790
|
+
console.log(chalk.gray(" ✗ go.work"));
|
|
791
|
+
console.log(chalk.gray(" ✗ pyproject.toml [tool.uv.workspace]"));
|
|
792
|
+
console.log(chalk.gray(" ✗ .gitmodules"));
|
|
793
|
+
console.log(chalk.gray(" ✗ sibling .git directories (need ≥ 2)"));
|
|
794
|
+
console.log("");
|
|
795
|
+
console.log("To configure manually, add to .trellis/config.yaml:");
|
|
796
|
+
console.log("");
|
|
797
|
+
console.log(chalk.cyan(" packages:"));
|
|
798
|
+
console.log(chalk.cyan(" frontend:"));
|
|
799
|
+
console.log(chalk.cyan(" path: ./frontend"));
|
|
800
|
+
console.log(chalk.cyan(" git: true # if it has its own .git"));
|
|
801
|
+
console.log(chalk.cyan(" backend:"));
|
|
802
|
+
console.log(chalk.cyan(" path: ./backend"));
|
|
803
|
+
console.log(chalk.cyan(" git: true"));
|
|
544
804
|
return;
|
|
545
805
|
}
|
|
546
806
|
if (detected && detected.length > 0) {
|
|
@@ -552,11 +812,15 @@ export async function init(options) {
|
|
|
552
812
|
// Show detected packages and ask
|
|
553
813
|
console.log(chalk.blue("\n🔍 Detected monorepo packages:"));
|
|
554
814
|
for (const pkg of detected) {
|
|
555
|
-
const
|
|
815
|
+
const tag = pkg.isSubmodule
|
|
816
|
+
? chalk.gray(" (submodule)")
|
|
817
|
+
: pkg.isGitRepo
|
|
818
|
+
? chalk.gray(" (git repo)")
|
|
819
|
+
: "";
|
|
556
820
|
console.log(chalk.gray(` - ${pkg.name}`) +
|
|
557
821
|
chalk.gray(` (${pkg.path})`) +
|
|
558
822
|
chalk.gray(` [${pkg.type}]`) +
|
|
559
|
-
|
|
823
|
+
tag);
|
|
560
824
|
}
|
|
561
825
|
console.log("");
|
|
562
826
|
const { useMonorepo } = await inquirer.prompt([
|
|
@@ -678,6 +942,7 @@ export async function init(options) {
|
|
|
678
942
|
let selectedTemplate = null;
|
|
679
943
|
// Pre-fetched templates list (used to pass selected SpecTemplate to downloadTemplateById)
|
|
680
944
|
let fetchedTemplates = [];
|
|
945
|
+
let registryBackend;
|
|
681
946
|
// Determine the index URL based on registry
|
|
682
947
|
const indexUrl = registry
|
|
683
948
|
? `${registry.rawBaseUrl}/index.json`
|
|
@@ -702,10 +967,13 @@ export async function init(options) {
|
|
|
702
967
|
process.stdout.write(chalk.gray(` Loading... 0s/${timeoutSec}s`));
|
|
703
968
|
let templates;
|
|
704
969
|
let registryProbeNotFound = false;
|
|
970
|
+
let registryProbeError;
|
|
705
971
|
if (registry) {
|
|
706
|
-
const probeResult = await probeRegistryIndex(indexUrl);
|
|
972
|
+
const probeResult = await probeRegistryIndex(indexUrl, registry);
|
|
707
973
|
templates = probeResult.templates;
|
|
708
974
|
registryProbeNotFound = probeResult.isNotFound;
|
|
975
|
+
registryProbeError = probeResult.error;
|
|
976
|
+
registryBackend = probeResult.backend;
|
|
709
977
|
}
|
|
710
978
|
else {
|
|
711
979
|
templates = await fetchTemplateIndex(indexUrl);
|
|
@@ -720,7 +988,7 @@ export async function init(options) {
|
|
|
720
988
|
}
|
|
721
989
|
else if (templates.length === 0 && registry) {
|
|
722
990
|
// Custom registry: transient error (not a 404) — abort, don't misclassify
|
|
723
|
-
console.log(chalk.red("
|
|
991
|
+
console.log(chalk.red(` ${registryProbeError?.message ?? "Could not reach registry. Check your connection and try again."}`));
|
|
724
992
|
return;
|
|
725
993
|
}
|
|
726
994
|
else if (templates.length === 0) {
|
|
@@ -772,8 +1040,9 @@ export async function init(options) {
|
|
|
772
1040
|
// Probe index.json to detect marketplace vs direct download
|
|
773
1041
|
const customIndexUrl = `${registry.rawBaseUrl}/index.json`;
|
|
774
1042
|
console.log(chalk.gray(` Checking for templates at ${registry.gigetSource}...`));
|
|
775
|
-
const customProbe = await probeRegistryIndex(customIndexUrl);
|
|
1043
|
+
const customProbe = await probeRegistryIndex(customIndexUrl, registry);
|
|
776
1044
|
const customTemplates = customProbe.templates;
|
|
1045
|
+
registryBackend = customProbe.backend;
|
|
777
1046
|
if (customTemplates.length > 0) {
|
|
778
1047
|
// Marketplace mode: show picker with custom templates
|
|
779
1048
|
fetchedTemplates = customTemplates;
|
|
@@ -828,7 +1097,7 @@ export async function init(options) {
|
|
|
828
1097
|
}
|
|
829
1098
|
else {
|
|
830
1099
|
// Transient error (not 404) — loop back, don't misclassify
|
|
831
|
-
console.log(chalk.yellow("
|
|
1100
|
+
console.log(chalk.yellow(` ${customProbe.error?.message ?? "Could not reach registry. Try again or enter a different source."}`));
|
|
832
1101
|
registry = undefined; // Reset so we don't fall through to direct download
|
|
833
1102
|
}
|
|
834
1103
|
}
|
|
@@ -874,7 +1143,8 @@ export async function init(options) {
|
|
|
874
1143
|
// -y mode with --registry (no --template): probe index.json to detect mode
|
|
875
1144
|
// Skip when monorepo mode already handled templates above
|
|
876
1145
|
if (options.yes && registry && !selectedTemplate && !monorepoPackages) {
|
|
877
|
-
const probeResult = await probeRegistryIndex(`${registry.rawBaseUrl}/index.json
|
|
1146
|
+
const probeResult = await probeRegistryIndex(`${registry.rawBaseUrl}/index.json`, registry);
|
|
1147
|
+
registryBackend = probeResult.backend;
|
|
878
1148
|
if (probeResult.templates.length > 0) {
|
|
879
1149
|
// Marketplace mode requires interactive selection — can't auto-select
|
|
880
1150
|
console.log(chalk.red("Error: Registry is a marketplace with multiple templates. " +
|
|
@@ -883,7 +1153,7 @@ export async function init(options) {
|
|
|
883
1153
|
}
|
|
884
1154
|
if (!probeResult.isNotFound) {
|
|
885
1155
|
// Transient error (not 404) — abort, don't misclassify as direct-download
|
|
886
|
-
console.log(chalk.red(
|
|
1156
|
+
console.log(chalk.red(`Error: ${probeResult.error?.message ?? "Could not reach registry. Check your connection and try again."}`));
|
|
887
1157
|
return;
|
|
888
1158
|
}
|
|
889
1159
|
// isNotFound=true → no index.json, proceed with direct download (fetchedTemplates stays empty)
|
|
@@ -898,7 +1168,7 @@ export async function init(options) {
|
|
|
898
1168
|
console.log(chalk.gray(" This may take a moment on slow connections."));
|
|
899
1169
|
// Find pre-fetched SpecTemplate to avoid double-fetch
|
|
900
1170
|
const prefetched = fetchedTemplates.find((t) => t.id === selectedTemplate);
|
|
901
|
-
const result = await downloadTemplateById(cwd, selectedTemplate, templateStrategy, prefetched, registry);
|
|
1171
|
+
const result = await downloadTemplateById(cwd, selectedTemplate, templateStrategy, prefetched, registry, undefined, registryBackend);
|
|
902
1172
|
if (result.success) {
|
|
903
1173
|
if (result.skipped) {
|
|
904
1174
|
console.log(chalk.gray(` ${result.message}`));
|
|
@@ -941,7 +1211,7 @@ export async function init(options) {
|
|
|
941
1211
|
templateStrategy = actionAnswer.action;
|
|
942
1212
|
}
|
|
943
1213
|
}
|
|
944
|
-
const result = await downloadRegistryDirect(cwd, registry, templateStrategy);
|
|
1214
|
+
const result = await downloadRegistryDirect(cwd, registry, templateStrategy, undefined, registryBackend);
|
|
945
1215
|
if (result.success) {
|
|
946
1216
|
if (result.skipped) {
|
|
947
1217
|
console.log(chalk.gray(` ${result.message}`));
|
|
@@ -984,13 +1254,10 @@ export async function init(options) {
|
|
|
984
1254
|
await configurePlatform(platformId, cwd);
|
|
985
1255
|
}
|
|
986
1256
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
if (hasSelectedPythonPlatform) {
|
|
992
|
-
console.log(chalk.yellow('📌 Windows detected: Using "python" for hooks'));
|
|
993
|
-
}
|
|
1257
|
+
const pythonPlatforms = getPlatformsWithPythonHooks();
|
|
1258
|
+
const hasSelectedPythonPlatform = pythonPlatforms.some((id) => tools.includes(AI_TOOLS[id].cliFlag));
|
|
1259
|
+
if (hasSelectedPythonPlatform) {
|
|
1260
|
+
logPythonAdaptationNotice(pythonCmd);
|
|
994
1261
|
}
|
|
995
1262
|
// Create root files (skip if exists)
|
|
996
1263
|
await createRootFiles(cwd);
|
|
@@ -1002,23 +1269,46 @@ export async function init(options) {
|
|
|
1002
1269
|
// Initialize developer identity (silent - no output)
|
|
1003
1270
|
if (developerName) {
|
|
1004
1271
|
try {
|
|
1005
|
-
const pythonCmd = getPythonCommand();
|
|
1006
1272
|
const scriptPath = path.join(cwd, PATHS.SCRIPTS, "init_developer.py");
|
|
1007
1273
|
execSync(`${pythonCmd} "${scriptPath}" "${developerName}"`, {
|
|
1008
1274
|
cwd,
|
|
1009
1275
|
stdio: "pipe", // Silent
|
|
1010
1276
|
});
|
|
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
1277
|
}
|
|
1016
1278
|
catch {
|
|
1017
1279
|
// Silent failure - user can run init_developer.py manually
|
|
1018
1280
|
}
|
|
1281
|
+
// Three-branch dispatch using flags captured at init() start (before
|
|
1282
|
+
// createWorkflowStructure/init_developer ran, so they reflect the disk
|
|
1283
|
+
// state of the user's checkout, not the state this init just produced):
|
|
1284
|
+
// isFirstInit=true → creator bootstrap (new project)
|
|
1285
|
+
// isFirstInit=false + no .developer file → joiner onboarding (fresh clone)
|
|
1286
|
+
// isFirstInit=false + .developer exists → same-dev re-init, no task
|
|
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
|
+
//
|
|
1293
|
+
// Runs OUTSIDE the init_developer try/catch (which uses stdio: "pipe")
|
|
1294
|
+
// so joiner failures surface as warnings instead of being silently
|
|
1295
|
+
// swallowed.
|
|
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);
|
|
1300
|
+
}
|
|
1301
|
+
else if (!hadDeveloperFileAtStart) {
|
|
1302
|
+
try {
|
|
1303
|
+
if (!createJoinerOnboardingTask(cwd, developerName, pythonCmd)) {
|
|
1304
|
+
console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
catch (err) {
|
|
1308
|
+
console.warn(chalk.yellow(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1019
1311
|
}
|
|
1020
|
-
// Print "What We Solve" section
|
|
1021
|
-
printWhatWeSolve();
|
|
1022
1312
|
}
|
|
1023
1313
|
/**
|
|
1024
1314
|
* Simple readline-based input (no flickering like inquirer)
|
|
@@ -1036,37 +1326,11 @@ function askInput(prompt) {
|
|
|
1036
1326
|
});
|
|
1037
1327
|
}
|
|
1038
1328
|
async function createRootFiles(cwd) {
|
|
1039
|
-
const agentsPath = path.join(cwd,
|
|
1329
|
+
const agentsPath = path.join(cwd, FILE_NAMES.AGENTS);
|
|
1040
1330
|
// Write AGENTS.md from template
|
|
1041
1331
|
const agentsWritten = await writeFile(agentsPath, agentsMdContent);
|
|
1042
1332
|
if (agentsWritten) {
|
|
1043
1333
|
console.log(chalk.blue("📄 Created AGENTS.md"));
|
|
1044
1334
|
}
|
|
1045
1335
|
}
|
|
1046
|
-
/**
|
|
1047
|
-
* Print "What We Solve" section showing Trellis value proposition
|
|
1048
|
-
* Styled like a meme/rant to resonate with developer pain points
|
|
1049
|
-
*/
|
|
1050
|
-
function printWhatWeSolve() {
|
|
1051
|
-
console.log(chalk.gray("\nSound familiar? ") +
|
|
1052
|
-
chalk.bold("You'll never say these again!!\n"));
|
|
1053
|
-
// Pain point 1: Bug loop → Thinking Guides + Check Loop
|
|
1054
|
-
console.log(chalk.gray("✗ ") + '"Fix A → break B → fix B → break A..."');
|
|
1055
|
-
console.log(chalk.green(" ✓ ") +
|
|
1056
|
-
chalk.white("Thinking Guides + Check Loop: Think first, verify after"));
|
|
1057
|
-
// Pain point 2: Instructions ignored/forgotten → Sub-agents + per-agent spec injection
|
|
1058
|
-
console.log(chalk.gray("✗ ") +
|
|
1059
|
-
'"Wrote CLAUDE.md, AI ignored it. Reminded AI, it forgot 5 turns later."');
|
|
1060
|
-
console.log(chalk.green(" ✓ ") +
|
|
1061
|
-
chalk.white("Spec Injection: Rules enforced per task, not per chat"));
|
|
1062
|
-
// Pain point 3: Missing connections → Cross-Layer Guide
|
|
1063
|
-
console.log(chalk.gray("✗ ") + '"Code works but nothing connects..."');
|
|
1064
|
-
console.log(chalk.green(" ✓ ") +
|
|
1065
|
-
chalk.white("Cross-Layer Guide: Map data flow before coding"));
|
|
1066
|
-
// Pain point 4: Code explosion → Plan Agent
|
|
1067
|
-
console.log(chalk.gray("✗ ") + '"Asked for a button, got 9000 lines"');
|
|
1068
|
-
console.log(chalk.green(" ✓ ") +
|
|
1069
|
-
chalk.white("Plan Agent: Rejects and splits oversized tasks"));
|
|
1070
|
-
console.log("");
|
|
1071
|
-
}
|
|
1072
1336
|
//# sourceMappingURL=init.js.map
|