@pennixrv/trellis 0.7.1 → 0.7.2-beta.2
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 +10 -7
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +6 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/ablate.d.ts.map +1 -1
- package/dist/commands/ablate.js +2 -0
- package/dist/commands/ablate.js.map +1 -1
- package/dist/commands/backup.d.ts +4 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +75 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +705 -681
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mem.d.ts.map +1 -1
- package/dist/commands/mem.js +49 -7
- package/dist/commands/mem.js.map +1 -1
- package/dist/commands/task-progress.d.ts +1 -0
- package/dist/commands/task-progress.d.ts.map +1 -1
- package/dist/commands/task-progress.js +67 -25
- package/dist/commands/task-progress.js.map +1 -1
- package/dist/commands/teamwork/adapters/acp.d.ts +20 -0
- package/dist/commands/teamwork/adapters/acp.d.ts.map +1 -0
- package/dist/commands/teamwork/adapters/acp.js +268 -0
- package/dist/commands/teamwork/adapters/acp.js.map +1 -0
- package/dist/commands/teamwork/adapters/claude.d.ts +1 -0
- package/dist/commands/teamwork/adapters/claude.d.ts.map +1 -1
- package/dist/commands/teamwork/adapters/claude.js +5 -0
- package/dist/commands/teamwork/adapters/claude.js.map +1 -1
- package/dist/commands/teamwork/adapters/codex.d.ts +3 -2
- package/dist/commands/teamwork/adapters/codex.d.ts.map +1 -1
- package/dist/commands/teamwork/adapters/codex.js +14 -15
- package/dist/commands/teamwork/adapters/codex.js.map +1 -1
- package/dist/commands/teamwork/adapters/index.d.ts +23 -1
- package/dist/commands/teamwork/adapters/index.d.ts.map +1 -1
- package/dist/commands/teamwork/adapters/index.js +163 -24
- package/dist/commands/teamwork/adapters/index.js.map +1 -1
- package/dist/commands/teamwork/agent-loader.d.ts +2 -1
- package/dist/commands/teamwork/agent-loader.d.ts.map +1 -1
- package/dist/commands/teamwork/agent-loader.js +2 -1
- package/dist/commands/teamwork/agent-loader.js.map +1 -1
- package/dist/commands/teamwork/artifact.d.ts +42 -14
- package/dist/commands/teamwork/artifact.d.ts.map +1 -1
- package/dist/commands/teamwork/artifact.js +188 -85
- package/dist/commands/teamwork/artifact.js.map +1 -1
- package/dist/commands/teamwork/batch-reservation.d.ts +16 -0
- package/dist/commands/teamwork/batch-reservation.d.ts.map +1 -0
- package/dist/commands/teamwork/batch-reservation.js +43 -0
- package/dist/commands/teamwork/batch-reservation.js.map +1 -0
- package/dist/commands/teamwork/batch.d.ts +23 -0
- package/dist/commands/teamwork/batch.d.ts.map +1 -0
- package/dist/commands/teamwork/batch.js +415 -0
- package/dist/commands/teamwork/batch.js.map +1 -0
- package/dist/commands/teamwork/context-loader.d.ts +1 -1
- package/dist/commands/teamwork/context-loader.d.ts.map +1 -1
- package/dist/commands/teamwork/context-loader.js +19 -6
- package/dist/commands/teamwork/context-loader.js.map +1 -1
- package/dist/commands/teamwork/coordination.d.ts +88 -0
- package/dist/commands/teamwork/coordination.d.ts.map +1 -0
- package/dist/commands/teamwork/coordination.js +153 -0
- package/dist/commands/teamwork/coordination.js.map +1 -0
- package/dist/commands/teamwork/coordinator.d.ts +33 -6
- package/dist/commands/teamwork/coordinator.d.ts.map +1 -1
- package/dist/commands/teamwork/coordinator.js +948 -342
- package/dist/commands/teamwork/coordinator.js.map +1 -1
- package/dist/commands/teamwork/create.d.ts +1 -0
- package/dist/commands/teamwork/create.d.ts.map +1 -1
- package/dist/commands/teamwork/create.js +2 -1
- package/dist/commands/teamwork/create.js.map +1 -1
- package/dist/commands/teamwork/dev-parse-trace.d.ts +2 -1
- package/dist/commands/teamwork/dev-parse-trace.d.ts.map +1 -1
- package/dist/commands/teamwork/dev-parse-trace.js +2 -0
- package/dist/commands/teamwork/dev-parse-trace.js.map +1 -1
- package/dist/commands/teamwork/foreground-wait.d.ts +6 -0
- package/dist/commands/teamwork/foreground-wait.d.ts.map +1 -0
- package/dist/commands/teamwork/foreground-wait.js +65 -0
- package/dist/commands/teamwork/foreground-wait.js.map +1 -0
- package/dist/commands/teamwork/guard.d.ts +2 -2
- package/dist/commands/teamwork/guard.d.ts.map +1 -1
- package/dist/commands/teamwork/guard.js +25 -7
- package/dist/commands/teamwork/guard.js.map +1 -1
- package/dist/commands/teamwork/index.d.ts.map +1 -1
- package/dist/commands/teamwork/index.js +163 -10
- package/dist/commands/teamwork/index.js.map +1 -1
- package/dist/commands/teamwork/kill.d.ts +2 -0
- package/dist/commands/teamwork/kill.d.ts.map +1 -1
- package/dist/commands/teamwork/kill.js +79 -38
- package/dist/commands/teamwork/kill.js.map +1 -1
- package/dist/commands/teamwork/messages.d.ts.map +1 -1
- package/dist/commands/teamwork/messages.js +3 -0
- package/dist/commands/teamwork/messages.js.map +1 -1
- package/dist/commands/teamwork/plan.d.ts +22 -0
- package/dist/commands/teamwork/plan.d.ts.map +1 -0
- package/dist/commands/teamwork/plan.js +288 -0
- package/dist/commands/teamwork/plan.js.map +1 -0
- package/dist/commands/teamwork/process-identity.d.ts +3 -0
- package/dist/commands/teamwork/process-identity.d.ts.map +1 -0
- package/dist/commands/teamwork/process-identity.js +24 -0
- package/dist/commands/teamwork/process-identity.js.map +1 -0
- package/dist/commands/teamwork/profiles.d.ts +43 -6
- package/dist/commands/teamwork/profiles.d.ts.map +1 -1
- package/dist/commands/teamwork/profiles.js +138 -110
- package/dist/commands/teamwork/profiles.js.map +1 -1
- package/dist/commands/teamwork/prompt.d.ts +4 -0
- package/dist/commands/teamwork/prompt.d.ts.map +1 -0
- package/dist/commands/teamwork/prompt.js +33 -0
- package/dist/commands/teamwork/prompt.js.map +1 -0
- package/dist/commands/teamwork/result.d.ts +55 -0
- package/dist/commands/teamwork/result.d.ts.map +1 -0
- package/dist/commands/teamwork/result.js +94 -0
- package/dist/commands/teamwork/result.js.map +1 -0
- package/dist/commands/teamwork/rm.d.ts.map +1 -1
- package/dist/commands/teamwork/rm.js +64 -64
- package/dist/commands/teamwork/rm.js.map +1 -1
- package/dist/commands/teamwork/run.d.ts +5 -0
- package/dist/commands/teamwork/run.d.ts.map +1 -1
- package/dist/commands/teamwork/run.js +120 -24
- package/dist/commands/teamwork/run.js.map +1 -1
- package/dist/commands/teamwork/runtime.d.ts +9 -0
- package/dist/commands/teamwork/runtime.d.ts.map +1 -0
- package/dist/commands/teamwork/runtime.js +401 -0
- package/dist/commands/teamwork/runtime.js.map +1 -0
- package/dist/commands/teamwork/send.d.ts +1 -0
- package/dist/commands/teamwork/send.d.ts.map +1 -1
- package/dist/commands/teamwork/send.js +2 -1
- package/dist/commands/teamwork/send.js.map +1 -1
- package/dist/commands/teamwork/spawn.d.ts +10 -3
- package/dist/commands/teamwork/spawn.d.ts.map +1 -1
- package/dist/commands/teamwork/spawn.js +42 -27
- package/dist/commands/teamwork/spawn.js.map +1 -1
- package/dist/commands/teamwork/store/lock.js +28 -4
- package/dist/commands/teamwork/store/lock.js.map +1 -1
- package/dist/commands/teamwork/supervisor/inbox.d.ts +1 -0
- package/dist/commands/teamwork/supervisor/inbox.d.ts.map +1 -1
- package/dist/commands/teamwork/supervisor/inbox.js +28 -9
- package/dist/commands/teamwork/supervisor/inbox.js.map +1 -1
- package/dist/commands/teamwork/supervisor/stdout.d.ts.map +1 -1
- package/dist/commands/teamwork/supervisor/stdout.js +2 -3
- package/dist/commands/teamwork/supervisor/stdout.js.map +1 -1
- package/dist/commands/teamwork/supervisor/warning.d.ts.map +1 -1
- package/dist/commands/teamwork/supervisor/warning.js +2 -3
- package/dist/commands/teamwork/supervisor/warning.js.map +1 -1
- package/dist/commands/teamwork/supervisor.d.ts +5 -0
- package/dist/commands/teamwork/supervisor.d.ts.map +1 -1
- package/dist/commands/teamwork/supervisor.js +21 -12
- package/dist/commands/teamwork/supervisor.js.map +1 -1
- package/dist/commands/teamwork/teammate-adapters.d.ts +0 -5
- package/dist/commands/teamwork/teammate-adapters.d.ts.map +1 -1
- package/dist/commands/teamwork/teammate-adapters.js +9 -56
- package/dist/commands/teamwork/teammate-adapters.js.map +1 -1
- package/dist/commands/teamwork/teammate-config.d.ts +5 -1
- package/dist/commands/teamwork/teammate-config.d.ts.map +1 -1
- package/dist/commands/teamwork/teammate-config.js +62 -19
- package/dist/commands/teamwork/teammate-config.js.map +1 -1
- package/dist/commands/teamwork/teammate-launch.d.ts +15 -6
- package/dist/commands/teamwork/teammate-launch.d.ts.map +1 -1
- package/dist/commands/teamwork/teammate-launch.js +129 -22
- package/dist/commands/teamwork/teammate-launch.js.map +1 -1
- package/dist/commands/teamwork/wait.d.ts +2 -0
- package/dist/commands/teamwork/wait.d.ts.map +1 -1
- package/dist/commands/teamwork/wait.js +44 -3
- package/dist/commands/teamwork/wait.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +92 -84
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +635 -592
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +94 -89
- package/dist/commands/workflow.js.map +1 -1
- package/dist/configurators/shared.js +2 -2
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +14 -32
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +3 -10
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +4 -13
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.7.1-beta.0.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.1.json +186 -0
- package/dist/migrations/manifests/0.7.1-beta.10.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.11.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.12.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.13.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.14.json +11 -0
- package/dist/migrations/manifests/0.7.1-beta.15.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.16.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.17.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.18.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.19.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.2.json +522 -0
- package/dist/migrations/manifests/0.7.1-beta.3.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.4.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.5.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.6.json +10 -0
- package/dist/migrations/manifests/0.7.1-beta.7.json +8 -0
- package/dist/migrations/manifests/0.7.1-beta.8.json +481 -0
- package/dist/migrations/manifests/0.7.1-beta.9.json +191 -0
- package/dist/migrations/manifests/0.7.2-beta.0.json +11 -0
- package/dist/migrations/manifests/0.7.2-beta.1.json +11 -0
- package/dist/migrations/manifests/0.7.2-beta.2.json +11 -0
- package/dist/templates/claude/hooks/statusline.py +9 -4
- package/dist/templates/codex/hooks/session-start.py +14 -11
- package/dist/templates/codex/hooks.json +10 -0
- package/dist/templates/common/bundled-skills/trellis-decision-space-construction-and-merging/workflow.md +2 -0
- package/dist/templates/common/bundled-skills/trellis-goal-exploration-and-requirements/workflow.md +2 -0
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +5 -5
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +3 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-teamwork.md +14 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +8 -2
- package/dist/templates/common/bundled-skills/trellis-session-insight/references/cli-quick-reference.md +2 -1
- package/dist/templates/common/bundled-skills/trellis-session-insight/references/triggering-patterns.md +2 -2
- package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/workflow.md +8 -0
- package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/mcp-setup.md +4 -2
- package/dist/templates/common/bundled-skills/trellis-spec-bootstrap/references/spec-task-planning.md +2 -2
- package/dist/templates/common/bundled-skills/trellis-task-implementation/workflow.md +6 -0
- package/dist/templates/common/bundled-skills/trellis-teamwork/SKILL.md +18 -3
- package/dist/templates/common/bundled-skills/trellis-teamwork/references/coordination-modes.md +34 -3
- package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-configuration-matrix.md +37 -26
- package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-processes.md +128 -14
- package/dist/templates/common/bundled-skills/trellis-teamwork/references/teamwork-plan-construction.md +152 -20
- package/dist/templates/common/commands/start.md +2 -2
- package/dist/templates/common/skills/before-dev.md +1 -4
- package/dist/templates/common/skills/break-loop.md +7 -18
- package/dist/templates/common/skills/check.md +7 -11
- package/dist/templates/common/skills/{brainstorm.md → request-intake.md} +5 -3
- package/dist/templates/common/skills/update-spec.md +5 -7
- package/dist/templates/copilot/hooks/session-start.py +14 -11
- package/dist/templates/copilot/index.d.ts +1 -6
- package/dist/templates/copilot/index.d.ts.map +1 -1
- package/dist/templates/copilot/index.js +1 -14
- package/dist/templates/copilot/index.js.map +1 -1
- package/dist/templates/extract.js +4 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/markdown/spec/backend/index.md.txt +4 -35
- package/dist/templates/markdown/spec/frontend/index.md.txt +4 -36
- package/dist/templates/omp/extensions/trellis/index.ts.txt +2 -2
- package/dist/templates/opencode/lib/session-utils.js +301 -209
- package/dist/templates/pi/extensions/trellis/index.ts.txt +17 -1
- package/dist/templates/shared-hooks/guard-teamwork-wait.py +58 -0
- package/dist/templates/shared-hooks/index.d.ts +1 -1
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +1 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-workflow-state.py +8 -3
- package/dist/templates/shared-hooks/session-start.py +16 -12
- package/dist/templates/snow/SNOW.md +1 -1
- package/dist/templates/trellis/agents/delegated-worktree-primary-agent.md +9 -0
- package/dist/templates/trellis/agents/profiles/architecture.md +9 -0
- package/dist/templates/trellis/agents/profiles/diagnosis.md +9 -0
- package/dist/templates/trellis/agents/profiles/exploration.md +9 -0
- package/dist/templates/trellis/agents/profiles/research.md +9 -0
- package/dist/templates/trellis/agents/profiles/review.md +9 -0
- package/dist/templates/trellis/agents/profiles/verification.md +9 -0
- package/dist/templates/trellis/agents/teammate-profiles.json +56 -4
- package/dist/templates/trellis/agents/teammate.md +4 -52
- package/dist/templates/trellis/config.yaml +2 -5
- package/dist/templates/trellis/index.d.ts +4 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +22 -4
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +1 -1
- package/dist/templates/trellis/scripts/common/active_task.py +3 -47
- package/dist/templates/trellis/scripts/common/config.py +5 -0
- package/dist/templates/trellis/scripts/common/paths.py +9 -25
- package/dist/templates/trellis/scripts/common/safe_commit.py +8 -8
- package/dist/templates/trellis/scripts/common/task_context.py +11 -51
- package/dist/templates/trellis/scripts/common/task_continuity.py +191 -60
- package/dist/templates/trellis/scripts/common/task_layout.py +80 -0
- package/dist/templates/trellis/scripts/common/task_move.py +345 -0
- package/dist/templates/trellis/scripts/common/task_store.py +51 -57
- package/dist/templates/trellis/scripts/common/task_utils.py +23 -19
- package/dist/templates/trellis/scripts/common/task_workflow.py +395 -49
- package/dist/templates/trellis/scripts/common/tasks.py +6 -5
- package/dist/templates/trellis/scripts/hooks/linear_sync.py +13 -3
- package/dist/templates/trellis/scripts/task.py +76 -24
- package/dist/templates/trellis/teamwork.json +10 -0
- package/dist/templates/trellis/workflow.md +11 -2
- package/dist/utils/ablation-store.d.ts +1 -1
- package/dist/utils/ablation-store.d.ts.map +1 -1
- package/dist/utils/ablation-store.js +58 -24
- package/dist/utils/ablation-store.js.map +1 -1
- package/dist/utils/asset-backup.d.ts +138 -0
- package/dist/utils/asset-backup.d.ts.map +1 -0
- package/dist/utils/asset-backup.js +525 -0
- package/dist/utils/asset-backup.js.map +1 -0
- package/dist/utils/atomic-write.d.ts +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/atomic-write.js +6 -3
- package/dist/utils/atomic-write.js.map +1 -1
- package/dist/utils/manifest-prune.d.ts +4 -1
- package/dist/utils/manifest-prune.d.ts.map +1 -1
- package/dist/utils/manifest-prune.js +17 -6
- package/dist/utils/manifest-prune.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +4 -19
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +9 -45
- package/dist/utils/template-fetcher.js.map +1 -1
- package/dist/utils/template-hash.d.ts +5 -4
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +24 -17
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +3 -2
- package/dist/templates/copilot/prompts/before-dev.prompt.md +0 -39
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +0 -13
- package/dist/templates/copilot/prompts/break-loop.prompt.md +0 -129
- package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +0 -170
- package/dist/templates/copilot/prompts/check.prompt.md +0 -110
- package/dist/templates/copilot/prompts/create-command.prompt.md +0 -116
- package/dist/templates/copilot/prompts/finish-work.prompt.md +0 -22
- package/dist/templates/copilot/prompts/integrate-skill.prompt.md +0 -223
- package/dist/templates/copilot/prompts/onboard.prompt.md +0 -362
- package/dist/templates/copilot/prompts/record-session.prompt.md +0 -58
- package/dist/templates/copilot/prompts/start.prompt.md +0 -65
- package/dist/templates/copilot/prompts/update-spec.prompt.md +0 -358
package/dist/commands/init.js
CHANGED
|
@@ -6,6 +6,7 @@ import chalk from "chalk";
|
|
|
6
6
|
import figlet from "figlet";
|
|
7
7
|
import inquirer from "inquirer";
|
|
8
8
|
import { createWorkflowStructure } from "../configurators/workflow.js";
|
|
9
|
+
import { withAssetMaintenance } from "../utils/asset-backup.js";
|
|
9
10
|
import { getInitToolChoices, resolveCliFlag, configurePlatform, getConfiguredPlatforms, getPlatformsWithPythonHooks, } from "../configurators/index.js";
|
|
10
11
|
import { getPythonCommandForPlatform, setResolvedPythonCommand, } from "../configurators/shared.js";
|
|
11
12
|
import { AI_TOOLS } from "../types/ai-tools.js";
|
|
@@ -19,10 +20,20 @@ import { initializeHashes, removeHash } from "../utils/template-hash.js";
|
|
|
19
20
|
import { NATIVE_WORKFLOW_ID, resolveWorkflowTemplate, } from "../utils/workflow-resolver.js";
|
|
20
21
|
import { isCwdHomedir, homedirGuardMessage, homedirBypassEnabled, } from "../utils/cwd-guard.js";
|
|
21
22
|
import { writeSpecRegistryConfig, } from "../utils/registry-config.js";
|
|
22
|
-
import {
|
|
23
|
+
import { probeRegistryIndex, downloadTemplateById, downloadRegistryDirect, parseRegistrySource, TIMEOUTS, } from "../utils/template-fetcher.js";
|
|
23
24
|
import { setupProxy, maskProxyUrl } from "../utils/proxy.js";
|
|
24
25
|
import { toPosix } from "../utils/posix.js";
|
|
25
26
|
import { updateHashes } from "../utils/template-hash.js";
|
|
27
|
+
import { getTaskProgress } from "./task-progress.js";
|
|
28
|
+
function tasksAreEmpty(cwd) {
|
|
29
|
+
const progress = getTaskProgress(cwd);
|
|
30
|
+
return (!progress.partial &&
|
|
31
|
+
progress.planning +
|
|
32
|
+
progress.in_progress +
|
|
33
|
+
progress.completed +
|
|
34
|
+
progress.cancelled ===
|
|
35
|
+
0);
|
|
36
|
+
}
|
|
26
37
|
const MIN_PYTHON_MAJOR = 3;
|
|
27
38
|
const MIN_PYTHON_MINOR = 9;
|
|
28
39
|
const PYTHON_VERSION_RE = /Python (\d+)\.(\d+)/;
|
|
@@ -221,9 +232,19 @@ export function slugifyDeveloperName(name) {
|
|
|
221
232
|
* anything. Shared by both creator bootstrap and joiner onboarding flows.
|
|
222
233
|
*/
|
|
223
234
|
function writeTaskSkeleton(cwd, taskName, taskJson, prdContent) {
|
|
224
|
-
const taskDir = path.join(cwd, PATHS.TASKS, taskName);
|
|
225
|
-
|
|
226
|
-
|
|
235
|
+
const taskDir = path.join(cwd, PATHS.TASKS, "planning", taskName);
|
|
236
|
+
const tasksRoot = path.join(cwd, PATHS.TASKS);
|
|
237
|
+
const completedRoot = path.join(tasksRoot, "completed");
|
|
238
|
+
const candidates = ["planning", "in_progress", "cancelled"].map((status) => path.join(tasksRoot, status, taskName));
|
|
239
|
+
if (fs.existsSync(completedRoot))
|
|
240
|
+
for (const month of fs.readdirSync(completedRoot, { withFileTypes: true }))
|
|
241
|
+
if (month.isDirectory() && /^\d{4}-\d{2}$/.test(month.name))
|
|
242
|
+
candidates.push(path.join(completedRoot, month.name, taskName));
|
|
243
|
+
const existing = candidates.filter((candidate) => fs.existsSync(candidate));
|
|
244
|
+
if (existing.length > 1)
|
|
245
|
+
throw new Error(`Ambiguous existing task ${taskName}; reconcile task layout before initialization`);
|
|
246
|
+
if (existing.length === 1)
|
|
247
|
+
return true;
|
|
227
248
|
try {
|
|
228
249
|
fs.mkdirSync(taskDir, { recursive: true });
|
|
229
250
|
fs.writeFileSync(path.join(taskDir, FILE_NAMES.TASK_JSON), JSON.stringify(taskJson, null, 2), "utf-8");
|
|
@@ -281,7 +302,7 @@ function getBootstrapPrdContent(projectType, pythonCmd, packages) {
|
|
|
281
302
|
**You (the AI) are running this task. The developer does not read this file.**
|
|
282
303
|
|
|
283
304
|
The developer just ran \`trellis init\` on this project for the first time.
|
|
284
|
-
\`.trellis/\` now
|
|
305
|
+
\`.trellis/\` now contains indexes and general thinking guides, not validated project conventions. This bootstrap task
|
|
285
306
|
exists under \`.trellis/tasks/\`. When they want to work on it, they should start
|
|
286
307
|
this task from a session that provides Trellis session identity.
|
|
287
308
|
|
|
@@ -312,7 +333,6 @@ ${checklistMarkdown}
|
|
|
312
333
|
| File | What to document |
|
|
313
334
|
|------|------------------|
|
|
314
335
|
| \`.trellis/spec/backend/directory-structure.md\` | Where different file types go (routes, services, utils) |
|
|
315
|
-
| \`.trellis/spec/backend/database-guidelines.md\` | ORM, migrations, query patterns, naming conventions |
|
|
316
336
|
| \`.trellis/spec/backend/error-handling.md\` | How errors are caught, logged, and returned |
|
|
317
337
|
| \`.trellis/spec/backend/logging-guidelines.md\` | Log levels, format, what to log |
|
|
318
338
|
| \`.trellis/spec/backend/quality-guidelines.md\` | Code review standards, testing requirements |
|
|
@@ -341,6 +361,14 @@ best practices. Customize only if something clearly doesn't fit this project.
|
|
|
341
361
|
|
|
342
362
|
## How to fill the spec
|
|
343
363
|
|
|
364
|
+
Use \`trellis-spec-bootstrap\` to derive applicable package/layer guidelines
|
|
365
|
+
from the actual source. The tables are candidate topics, not files that must
|
|
366
|
+
all exist. Omit irrelevant layers and technologies; create database or framework
|
|
367
|
+
guidelines only when the project actually uses them. Follow the project's
|
|
368
|
+
language rules, preserve existing custom specs, and cite concrete source paths.
|
|
369
|
+
For a new project without source, record only confirmed boundaries and add
|
|
370
|
+
examples as implementation supplies evidence. Do not invent existing patterns.
|
|
371
|
+
|
|
344
372
|
### Step 1: Import from existing convention files first (preferred)
|
|
345
373
|
|
|
346
374
|
Search the repo for existing convention docs. If any exist, read them and
|
|
@@ -554,7 +582,7 @@ code every session.
|
|
|
554
582
|
|
|
555
583
|
File layout (mention when they ask "where does what live"):
|
|
556
584
|
- \`.trellis/.runtime/sessions/<session>.json\` — session active-task state, gitignored
|
|
557
|
-
- \`.trellis/tasks/<task>/{implement,check}.jsonl\` — per-task context manifests
|
|
585
|
+
- \`.trellis/tasks/<task-dir>/{implement,check}.jsonl\` — per-task context manifests
|
|
558
586
|
- \`.trellis/spec/\` — project-wide conventions (source of truth)
|
|
559
587
|
- \`.trellis/workspace/${developer}/journal-*.md\` — their session log,
|
|
560
588
|
rotated at ~2000 lines
|
|
@@ -827,746 +855,742 @@ function writeMonorepoConfig(cwd, packages) {
|
|
|
827
855
|
fs.writeFileSync(configPath, content.trimEnd() + "\n" + lines.join("\n") + "\n", "utf-8");
|
|
828
856
|
}
|
|
829
857
|
export async function init(options) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
options.
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
858
|
+
return withAssetMaintenance(process.cwd(), async () => {
|
|
859
|
+
// Refuse to run in $HOME — running here would scoop platform runtime data
|
|
860
|
+
// (Claude/Codex/OpenCode session histories etc.) into the trellis hash
|
|
861
|
+
// manifest, and a subsequent `trellis uninstall` would wipe it.
|
|
862
|
+
if (isCwdHomedir() && !homedirBypassEnabled()) {
|
|
863
|
+
console.error(chalk.red(homedirGuardMessage("init")));
|
|
864
|
+
process.exit(1);
|
|
865
|
+
}
|
|
866
|
+
// Deprecated alias: --windsurf → --devin (Windsurf was renamed to Devin).
|
|
867
|
+
// Normalize here too so programmatic callers (not just the CLI action) map
|
|
868
|
+
// correctly. The CLI action prints the deprecation notice.
|
|
869
|
+
if (options.windsurf) {
|
|
870
|
+
options.devin = true;
|
|
871
|
+
delete options.windsurf;
|
|
872
|
+
}
|
|
873
|
+
const cwd = process.cwd();
|
|
874
|
+
const isFirstInit = !fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW));
|
|
875
|
+
// Captured here (before createWorkflowStructure + init_developer run) so
|
|
876
|
+
// the three-branch dispatch at the bottom can tell "fresh clone joiner"
|
|
877
|
+
// (.trellis/ exists, .developer missing) apart from "creator first init".
|
|
878
|
+
const hadDeveloperFileAtStart = fs.existsSync(path.join(cwd, DIR_NAMES.WORKFLOW, FILE_NAMES.DEVELOPER));
|
|
879
|
+
// Generate ASCII art banner dynamically using FIGlet "Rebel" font
|
|
880
|
+
const banner = figlet.textSync("Trellis", { font: "Rebel" });
|
|
881
|
+
console.log(chalk.cyan(`\n${banner.trimEnd()}`));
|
|
882
|
+
console.log(chalk.gray("\n All-in-one AI framework & toolkit for Claude Code & Cursor\n"));
|
|
883
|
+
// Set up proxy before any network calls
|
|
884
|
+
const proxyUrl = setupProxy();
|
|
885
|
+
if (proxyUrl) {
|
|
886
|
+
console.log(chalk.gray(` Using proxy: ${maskProxyUrl(proxyUrl)}\n`));
|
|
887
|
+
}
|
|
888
|
+
// Set write mode based on options
|
|
889
|
+
let writeMode = "ask";
|
|
890
|
+
if (options.force) {
|
|
891
|
+
writeMode = "force";
|
|
892
|
+
console.log(chalk.gray("Mode: Force overwrite existing files\n"));
|
|
893
|
+
}
|
|
894
|
+
else if (options.skipExisting) {
|
|
895
|
+
writeMode = "skip";
|
|
896
|
+
console.log(chalk.gray("Mode: Skip existing files\n"));
|
|
897
|
+
}
|
|
898
|
+
else if (options.yes) {
|
|
899
|
+
// -y implies non-interactive: never prompt on conflicts. Default to skip
|
|
900
|
+
// (preserve user files) — explicit --force is required to overwrite.
|
|
901
|
+
writeMode = "skip";
|
|
902
|
+
console.log(chalk.gray("Mode: Non-interactive (skip existing files)\n"));
|
|
903
|
+
}
|
|
904
|
+
setWriteMode(writeMode);
|
|
905
|
+
// Detect developer name from git config or options
|
|
906
|
+
let developerName = options.user;
|
|
907
|
+
if (!developerName) {
|
|
908
|
+
// Only detect from git if current directory is a git repo
|
|
909
|
+
const isGitRepo = fs.existsSync(path.join(cwd, ".git"));
|
|
910
|
+
if (isGitRepo) {
|
|
911
|
+
try {
|
|
912
|
+
developerName = execSync("git config user.name", {
|
|
913
|
+
cwd,
|
|
914
|
+
encoding: "utf-8",
|
|
915
|
+
}).trim();
|
|
916
|
+
}
|
|
917
|
+
catch {
|
|
918
|
+
// Git not available or no user.name configured
|
|
919
|
+
}
|
|
890
920
|
}
|
|
891
921
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
console.log(chalk.blue("👤 Developer:"), chalk.gray(developerName));
|
|
895
|
-
}
|
|
896
|
-
const { command: pythonCmd } = resolveSupportedPython();
|
|
897
|
-
// ==========================================================================
|
|
898
|
-
// Re-init fast path: skip full flow when .trellis/ already exists
|
|
899
|
-
// ==========================================================================
|
|
900
|
-
// Aborted-init recovery (issue #204): if .trellis/ exists but tasks/ is
|
|
901
|
-
// empty, the previous init never reached bootstrap creation. Fall through
|
|
902
|
-
// to the full flow so the main-dispatch tasksEmpty fallback fires —
|
|
903
|
-
// handleReinit's joiner branch would otherwise mis-route the recovery.
|
|
904
|
-
const tasksDirEarly = path.join(cwd, PATHS.TASKS);
|
|
905
|
-
const tasksEmptyEarly = !fs.existsSync(tasksDirEarly) || fs.readdirSync(tasksDirEarly).length === 0;
|
|
906
|
-
const hasTemplateRequest = !!options.template || !!options.registry;
|
|
907
|
-
if (!isFirstInit &&
|
|
908
|
-
!options.force &&
|
|
909
|
-
!options.skipExisting &&
|
|
910
|
-
!tasksEmptyEarly &&
|
|
911
|
-
!hasTemplateRequest) {
|
|
912
|
-
const reinitDone = await handleReinit(cwd, options, developerName, pythonCmd);
|
|
913
|
-
if (reinitDone)
|
|
914
|
-
return;
|
|
915
|
-
// reinitDone === false means user chose "full re-initialize" → fall through
|
|
916
|
-
}
|
|
917
|
-
if (!developerName && !options.yes) {
|
|
918
|
-
// Ask for developer name if not detected and not in yes mode
|
|
919
|
-
console.log(chalk.gray("\nTrellis supports team collaboration - each developer has their own\n" +
|
|
920
|
-
`workspace directory (${PATHS.WORKSPACE}/{name}/) to track AI sessions.\n` +
|
|
921
|
-
"Tip: Usually this is your git username (git config user.name).\n"));
|
|
922
|
-
developerName = await askInput("Your name: ");
|
|
923
|
-
while (!developerName) {
|
|
924
|
-
console.log(chalk.yellow("Name is required"));
|
|
925
|
-
developerName = await askInput("Your name: ");
|
|
922
|
+
if (developerName) {
|
|
923
|
+
console.log(chalk.blue("👤 Developer:"), chalk.gray(developerName));
|
|
926
924
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
925
|
+
const { command: pythonCmd } = resolveSupportedPython();
|
|
926
|
+
// ==========================================================================
|
|
927
|
+
// Re-init fast path: skip full flow when .trellis/ already exists
|
|
928
|
+
// ==========================================================================
|
|
929
|
+
// Aborted-init recovery (issue #204): if .trellis/ exists but tasks/ is
|
|
930
|
+
// empty, the previous init never reached bootstrap creation. Fall through
|
|
931
|
+
// to the full flow so the main-dispatch tasksEmpty fallback fires —
|
|
932
|
+
// handleReinit's joiner branch would otherwise mis-route the recovery.
|
|
933
|
+
const tasksEmptyEarly = tasksAreEmpty(cwd);
|
|
934
|
+
const hasTemplateRequest = !!options.template || !!options.registry;
|
|
935
|
+
if (!isFirstInit &&
|
|
936
|
+
!options.force &&
|
|
937
|
+
!options.skipExisting &&
|
|
938
|
+
!tasksEmptyEarly &&
|
|
939
|
+
!hasTemplateRequest) {
|
|
940
|
+
const reinitDone = await handleReinit(cwd, options, developerName, pythonCmd);
|
|
941
|
+
if (reinitDone)
|
|
942
|
+
return;
|
|
943
|
+
// reinitDone === false means user chose "full re-initialize" → fall through
|
|
938
944
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
945
|
+
if (!developerName && !options.yes) {
|
|
946
|
+
// Ask for developer name if not detected and not in yes mode
|
|
947
|
+
console.log(chalk.gray("\nTrellis supports team collaboration - each developer has their own\n" +
|
|
948
|
+
`workspace directory (${PATHS.WORKSPACE}/{name}/) to track AI sessions.\n` +
|
|
949
|
+
"Tip: Usually this is your git username (git config user.name).\n"));
|
|
950
|
+
developerName = await askInput("Your name: ");
|
|
951
|
+
while (!developerName) {
|
|
952
|
+
console.log(chalk.yellow("Name is required"));
|
|
953
|
+
developerName = await askInput("Your name: ");
|
|
954
|
+
}
|
|
955
|
+
console.log(chalk.blue("👤 Developer:"), chalk.gray(developerName));
|
|
942
956
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
templateStrategy = "append";
|
|
951
|
-
}
|
|
952
|
-
// ==========================================================================
|
|
953
|
-
// Monorepo Detection
|
|
954
|
-
// ==========================================================================
|
|
955
|
-
let monorepoPackages;
|
|
956
|
-
let remoteSpecPackages;
|
|
957
|
-
if (options.monorepo !== false) {
|
|
958
|
-
// options.monorepo: true = --monorepo, false = --no-monorepo, undefined = auto
|
|
959
|
-
const detected = detectMonorepo(cwd);
|
|
960
|
-
if (options.monorepo === true && !detected) {
|
|
961
|
-
console.log(chalk.red("Error: --monorepo specified but no multi-package layout detected."));
|
|
962
|
-
console.log("");
|
|
963
|
-
console.log(chalk.gray("Checked:"));
|
|
964
|
-
console.log(chalk.gray(" ✗ pnpm-workspace.yaml"));
|
|
965
|
-
console.log(chalk.gray(" ✗ package.json workspaces"));
|
|
966
|
-
console.log(chalk.gray(" ✗ Cargo.toml [workspace]"));
|
|
967
|
-
console.log(chalk.gray(" ✗ go.work"));
|
|
968
|
-
console.log(chalk.gray(" ✗ pyproject.toml [tool.uv.workspace]"));
|
|
969
|
-
console.log(chalk.gray(" ✗ .gitmodules"));
|
|
970
|
-
console.log(chalk.gray(" ✗ sibling .git directories (need ≥ 2)"));
|
|
971
|
-
console.log("");
|
|
972
|
-
console.log("To configure manually, add to .trellis/config.yaml:");
|
|
973
|
-
console.log("");
|
|
974
|
-
console.log(chalk.cyan(" packages:"));
|
|
975
|
-
console.log(chalk.cyan(" frontend:"));
|
|
976
|
-
console.log(chalk.cyan(" path: ./frontend"));
|
|
977
|
-
console.log(chalk.cyan(" git: true # if it has its own .git"));
|
|
978
|
-
console.log(chalk.cyan(" backend:"));
|
|
979
|
-
console.log(chalk.cyan(" path: ./backend"));
|
|
980
|
-
console.log(chalk.cyan(" git: true"));
|
|
981
|
-
return;
|
|
957
|
+
// Detect project type (silent - no output)
|
|
958
|
+
const detectedType = detectProjectType(cwd);
|
|
959
|
+
// Parse custom registry source early (needed by both monorepo + single-repo flows)
|
|
960
|
+
let registry;
|
|
961
|
+
let registrySourceForConfig;
|
|
962
|
+
if (options.template && !options.registry) {
|
|
963
|
+
throw new Error("--template requires an explicit --registry; default spec initialization uses source-backed bootstrap");
|
|
982
964
|
}
|
|
983
|
-
if (
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
965
|
+
if (options.registry) {
|
|
966
|
+
try {
|
|
967
|
+
registry = parseRegistrySource(options.registry);
|
|
968
|
+
registrySourceForConfig = options.registry;
|
|
987
969
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
970
|
+
catch (error) {
|
|
971
|
+
console.log(chalk.red(error instanceof Error ? error.message : "Invalid registry source"));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Determine template strategy from flags (needed before monorepo template downloads)
|
|
976
|
+
let templateStrategy = "skip";
|
|
977
|
+
if (options.overwrite) {
|
|
978
|
+
templateStrategy = "overwrite";
|
|
979
|
+
}
|
|
980
|
+
else if (options.append) {
|
|
981
|
+
templateStrategy = "append";
|
|
982
|
+
}
|
|
983
|
+
// ==========================================================================
|
|
984
|
+
// Monorepo Detection
|
|
985
|
+
// ==========================================================================
|
|
986
|
+
let monorepoPackages;
|
|
987
|
+
let remoteSpecPackages;
|
|
988
|
+
if (options.monorepo !== false) {
|
|
989
|
+
// options.monorepo: true = --monorepo, false = --no-monorepo, undefined = auto
|
|
990
|
+
const detected = detectMonorepo(cwd);
|
|
991
|
+
if (options.monorepo === true && !detected) {
|
|
992
|
+
console.log(chalk.red("Error: --monorepo specified but no multi-package layout detected."));
|
|
1002
993
|
console.log("");
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
994
|
+
console.log(chalk.gray("Checked:"));
|
|
995
|
+
console.log(chalk.gray(" ✗ pnpm-workspace.yaml"));
|
|
996
|
+
console.log(chalk.gray(" ✗ package.json workspaces"));
|
|
997
|
+
console.log(chalk.gray(" ✗ Cargo.toml [workspace]"));
|
|
998
|
+
console.log(chalk.gray(" ✗ go.work"));
|
|
999
|
+
console.log(chalk.gray(" ✗ pyproject.toml [tool.uv.workspace]"));
|
|
1000
|
+
console.log(chalk.gray(" ✗ .gitmodules"));
|
|
1001
|
+
console.log(chalk.gray(" ✗ sibling .git directories (need ≥ 2)"));
|
|
1002
|
+
console.log("");
|
|
1003
|
+
console.log("To configure manually, add to .trellis/config.yaml:");
|
|
1004
|
+
console.log("");
|
|
1005
|
+
console.log(chalk.cyan(" packages:"));
|
|
1006
|
+
console.log(chalk.cyan(" frontend:"));
|
|
1007
|
+
console.log(chalk.cyan(" path: ./frontend"));
|
|
1008
|
+
console.log(chalk.cyan(" git: true # if it has its own .git"));
|
|
1009
|
+
console.log(chalk.cyan(" backend:"));
|
|
1010
|
+
console.log(chalk.cyan(" path: ./backend"));
|
|
1011
|
+
console.log(chalk.cyan(" git: true"));
|
|
1012
|
+
return;
|
|
1012
1013
|
}
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1014
|
+
if (detected && detected.length > 0) {
|
|
1015
|
+
let enableMonorepo = false;
|
|
1016
|
+
if (options.monorepo === true || options.yes) {
|
|
1017
|
+
enableMonorepo = true;
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
// Show detected packages and ask
|
|
1021
|
+
console.log(chalk.blue("\n🔍 Detected monorepo packages:"));
|
|
1018
1022
|
for (const pkg of detected) {
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1023
|
+
const tag = pkg.isSubmodule
|
|
1024
|
+
? chalk.gray(" (submodule)")
|
|
1025
|
+
: pkg.isGitRepo
|
|
1026
|
+
? chalk.gray(" (git repo)")
|
|
1027
|
+
: "";
|
|
1028
|
+
console.log(chalk.gray(` - ${pkg.name}`) +
|
|
1029
|
+
chalk.gray(` (${pkg.path})`) +
|
|
1030
|
+
chalk.gray(` [${pkg.type}]`) +
|
|
1031
|
+
tag);
|
|
1032
|
+
}
|
|
1033
|
+
console.log("");
|
|
1034
|
+
const { useMonorepo } = await inquirer.prompt([
|
|
1035
|
+
{
|
|
1036
|
+
type: "confirm",
|
|
1037
|
+
name: "useMonorepo",
|
|
1038
|
+
message: "Enable monorepo mode?",
|
|
1039
|
+
default: true,
|
|
1040
|
+
},
|
|
1041
|
+
]);
|
|
1042
|
+
enableMonorepo = useMonorepo;
|
|
1043
|
+
}
|
|
1044
|
+
if (enableMonorepo) {
|
|
1045
|
+
monorepoPackages = detected;
|
|
1046
|
+
remoteSpecPackages = new Set();
|
|
1047
|
+
// Per-package template selection (unless -y mode: all use blank spec)
|
|
1048
|
+
if (!options.yes && !options.template && registry) {
|
|
1049
|
+
for (const pkg of detected) {
|
|
1050
|
+
const { specSource } = await inquirer.prompt([
|
|
1051
|
+
{
|
|
1052
|
+
type: "list",
|
|
1053
|
+
name: "specSource",
|
|
1054
|
+
message: `Spec source for ${pkg.name} (${pkg.path}):`,
|
|
1055
|
+
choices: [
|
|
1056
|
+
{ name: "From scratch (Trellis default)", value: "blank" },
|
|
1057
|
+
{ name: "Download remote template", value: "remote" },
|
|
1058
|
+
],
|
|
1059
|
+
default: "blank",
|
|
1060
|
+
},
|
|
1061
|
+
]);
|
|
1062
|
+
if (specSource === "remote") {
|
|
1063
|
+
// Use existing template download flow, targeting spec/<name>/
|
|
1064
|
+
const destDir = path.join(cwd, PATHS.SPEC, sanitizePkgName(pkg.name));
|
|
1065
|
+
console.log(chalk.blue(`📦 Select template for ${pkg.name}...`));
|
|
1066
|
+
// Fetch templates if not already done
|
|
1067
|
+
const probe = await probeRegistryIndex(`${registry.rawBaseUrl}/index.json`, registry);
|
|
1068
|
+
if (probe.error)
|
|
1069
|
+
throw probe.error;
|
|
1070
|
+
const templates = probe.templates;
|
|
1071
|
+
const specTemplates = templates
|
|
1072
|
+
.filter((t) => t.type === "spec")
|
|
1073
|
+
.map((t) => ({
|
|
1074
|
+
name: `${t.id} (${t.name})`,
|
|
1075
|
+
value: t.id,
|
|
1076
|
+
}));
|
|
1077
|
+
if (specTemplates.length > 0) {
|
|
1078
|
+
const { templateId } = await inquirer.prompt([
|
|
1079
|
+
{
|
|
1080
|
+
type: "list",
|
|
1081
|
+
name: "templateId",
|
|
1082
|
+
message: `Select template for ${pkg.name}:`,
|
|
1083
|
+
choices: specTemplates,
|
|
1084
|
+
},
|
|
1085
|
+
]);
|
|
1086
|
+
const result = await downloadTemplateById(cwd, templateId, templateStrategy, templates.find((t) => t.id === templateId), registry, destDir);
|
|
1087
|
+
if (result.success) {
|
|
1088
|
+
console.log(chalk.green(` ${result.message}`));
|
|
1089
|
+
remoteSpecPackages.add(sanitizePkgName(pkg.name));
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
console.log(chalk.yellow(` ${result.message}`));
|
|
1093
|
+
console.log(chalk.gray(" Falling back to blank spec..."));
|
|
1094
|
+
}
|
|
1056
1095
|
}
|
|
1057
1096
|
else {
|
|
1058
|
-
console.log(chalk.
|
|
1059
|
-
console.log(chalk.gray(" Falling back to blank spec..."));
|
|
1097
|
+
console.log(chalk.gray(" No templates available. Using blank spec."));
|
|
1060
1098
|
}
|
|
1061
1099
|
}
|
|
1062
|
-
else {
|
|
1063
|
-
console.log(chalk.gray(" No templates available. Using blank spec."));
|
|
1064
|
-
}
|
|
1065
1100
|
}
|
|
1066
1101
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1102
|
+
else if (options.template) {
|
|
1103
|
+
// --template as default for all packages
|
|
1104
|
+
for (const pkg of detected) {
|
|
1105
|
+
const destDir = path.join(cwd, PATHS.SPEC, sanitizePkgName(pkg.name));
|
|
1106
|
+
const result = await downloadTemplateById(cwd, options.template, templateStrategy, undefined, registry, destDir);
|
|
1107
|
+
if (result.success && !result.skipped) {
|
|
1108
|
+
remoteSpecPackages.add(sanitizePkgName(pkg.name));
|
|
1109
|
+
}
|
|
1075
1110
|
}
|
|
1076
1111
|
}
|
|
1077
1112
|
}
|
|
1078
1113
|
}
|
|
1079
1114
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
tools = explicitTools;
|
|
1089
|
-
}
|
|
1090
|
-
else if (options.yes) {
|
|
1091
|
-
// No explicit tools + -y: default to Cursor and Claude
|
|
1092
|
-
tools = TOOLS.filter((t) => t.defaultChecked).map((t) => t.key);
|
|
1093
|
-
}
|
|
1094
|
-
else {
|
|
1095
|
-
// Interactive mode
|
|
1096
|
-
const answers = await inquirer.prompt([
|
|
1097
|
-
{
|
|
1098
|
-
type: "checkbox",
|
|
1099
|
-
name: "tools",
|
|
1100
|
-
message: "Select AI tools to configure:",
|
|
1101
|
-
choices: TOOLS.map((t) => ({
|
|
1102
|
-
name: t.name,
|
|
1103
|
-
value: t.key,
|
|
1104
|
-
checked: t.defaultChecked,
|
|
1105
|
-
})),
|
|
1106
|
-
},
|
|
1107
|
-
]);
|
|
1108
|
-
tools = answers.tools;
|
|
1109
|
-
}
|
|
1110
|
-
// Treat unknown project type as fullstack
|
|
1111
|
-
const projectType = detectedType === "unknown" ? "fullstack" : detectedType;
|
|
1112
|
-
if (tools.length === 0) {
|
|
1113
|
-
console.log(chalk.yellow("No tools selected. At least one tool is required."));
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
// Opt-in Claude Code statusLine: confirm interactively when the flag wasn't passed
|
|
1117
|
-
await maybePromptStatuslineOptIn(options, tools);
|
|
1118
|
-
// ==========================================================================
|
|
1119
|
-
// Template Selection (single-repo only; monorepo handles templates above)
|
|
1120
|
-
// ==========================================================================
|
|
1121
|
-
let selectedTemplate = null;
|
|
1122
|
-
// Pre-fetched templates list (used to pass selected SpecTemplate to downloadTemplateById)
|
|
1123
|
-
let fetchedTemplates = [];
|
|
1124
|
-
let registryBackend;
|
|
1125
|
-
// Determine the index URL based on registry
|
|
1126
|
-
const indexUrl = registry
|
|
1127
|
-
? `${registry.rawBaseUrl}/index.json`
|
|
1128
|
-
: TEMPLATE_INDEX_URL;
|
|
1129
|
-
if (monorepoPackages) {
|
|
1130
|
-
// Monorepo: template selection already handled above
|
|
1131
|
-
}
|
|
1132
|
-
else if (options.template) {
|
|
1133
|
-
// Template specified via --template flag
|
|
1134
|
-
selectedTemplate = options.template;
|
|
1135
|
-
if (registry) {
|
|
1136
|
-
const probeResult = await probeRegistryIndex(indexUrl, registry);
|
|
1137
|
-
registryBackend = probeResult.backend;
|
|
1138
|
-
if (probeResult.error) {
|
|
1139
|
-
console.log(chalk.red(`Error: ${probeResult.error.message}`));
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
|
-
if (probeResult.isNotFound) {
|
|
1143
|
-
console.log(chalk.red("Error: Registry has no index.json. Remove --template to use direct download mode."));
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
fetchedTemplates = probeResult.templates;
|
|
1115
|
+
// Tool definitions derived from platform registry
|
|
1116
|
+
const TOOLS = getInitToolChoices();
|
|
1117
|
+
// Build tools from explicit flags
|
|
1118
|
+
const explicitTools = TOOLS.filter((t) => options[t.key]).map((t) => t.key);
|
|
1119
|
+
let tools;
|
|
1120
|
+
if (explicitTools.length > 0) {
|
|
1121
|
+
// Explicit flags take precedence (works with or without -y)
|
|
1122
|
+
tools = explicitTools;
|
|
1147
1123
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
const timeoutSec = TIMEOUTS.INDEX_FETCH_MS / 1000;
|
|
1152
|
-
const sourceLabel = registry ? registry.gigetSource : TEMPLATE_INDEX_URL;
|
|
1153
|
-
console.log(chalk.gray(` Fetching available templates from ${sourceLabel}`));
|
|
1154
|
-
let elapsed = 0;
|
|
1155
|
-
const ticker = setInterval(() => {
|
|
1156
|
-
elapsed++;
|
|
1157
|
-
process.stdout.write(`\r${chalk.gray(` Loading... ${elapsed}s/${timeoutSec}s`)}`);
|
|
1158
|
-
}, 1000);
|
|
1159
|
-
process.stdout.write(chalk.gray(` Loading... 0s/${timeoutSec}s`));
|
|
1160
|
-
let templates;
|
|
1161
|
-
let registryProbeNotFound = false;
|
|
1162
|
-
let registryProbeError;
|
|
1163
|
-
if (registry) {
|
|
1164
|
-
const probeResult = await probeRegistryIndex(indexUrl, registry);
|
|
1165
|
-
templates = probeResult.templates;
|
|
1166
|
-
registryProbeNotFound = probeResult.isNotFound;
|
|
1167
|
-
registryProbeError = probeResult.error;
|
|
1168
|
-
registryBackend = probeResult.backend;
|
|
1124
|
+
else if (options.yes) {
|
|
1125
|
+
// No explicit tools + -y: default to Cursor and Claude
|
|
1126
|
+
tools = TOOLS.filter((t) => t.defaultChecked).map((t) => t.key);
|
|
1169
1127
|
}
|
|
1170
1128
|
else {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1129
|
+
// Interactive mode
|
|
1130
|
+
const answers = await inquirer.prompt([
|
|
1131
|
+
{
|
|
1132
|
+
type: "checkbox",
|
|
1133
|
+
name: "tools",
|
|
1134
|
+
message: "Select AI tools to configure:",
|
|
1135
|
+
choices: TOOLS.map((t) => ({
|
|
1136
|
+
name: t.name,
|
|
1137
|
+
value: t.key,
|
|
1138
|
+
checked: t.defaultChecked,
|
|
1139
|
+
})),
|
|
1140
|
+
},
|
|
1141
|
+
]);
|
|
1142
|
+
tools = answers.tools;
|
|
1180
1143
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1144
|
+
// Treat unknown project type as fullstack
|
|
1145
|
+
const projectType = detectedType === "unknown" ? "fullstack" : detectedType;
|
|
1146
|
+
if (tools.length === 0) {
|
|
1147
|
+
console.log(chalk.yellow("No tools selected. At least one tool is required."));
|
|
1184
1148
|
return;
|
|
1185
1149
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1150
|
+
// Opt-in Claude Code statusLine: confirm interactively when the flag wasn't passed
|
|
1151
|
+
await maybePromptStatuslineOptIn(options, tools);
|
|
1152
|
+
// ==========================================================================
|
|
1153
|
+
// Template Selection (single-repo only; monorepo handles templates above)
|
|
1154
|
+
// ==========================================================================
|
|
1155
|
+
let selectedTemplate = null;
|
|
1156
|
+
// Pre-fetched templates list (used to pass selected SpecTemplate to downloadTemplateById)
|
|
1157
|
+
let fetchedTemplates = [];
|
|
1158
|
+
let registryBackend;
|
|
1159
|
+
// Determine the index URL based on registry
|
|
1160
|
+
const indexUrl = registry ? `${registry.rawBaseUrl}/index.json` : "";
|
|
1161
|
+
if (monorepoPackages) {
|
|
1162
|
+
// Monorepo: template selection already handled above
|
|
1189
1163
|
}
|
|
1190
|
-
if (
|
|
1191
|
-
//
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
let
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1164
|
+
else if (options.template) {
|
|
1165
|
+
// Template specified via --template flag
|
|
1166
|
+
selectedTemplate = options.template;
|
|
1167
|
+
if (registry) {
|
|
1168
|
+
const probeResult = await probeRegistryIndex(indexUrl, registry);
|
|
1169
|
+
registryBackend = probeResult.backend;
|
|
1170
|
+
if (probeResult.error) {
|
|
1171
|
+
console.log(chalk.red(`Error: ${probeResult.error.message}`));
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (probeResult.isNotFound) {
|
|
1175
|
+
console.log(chalk.red("Error: Registry has no index.json. Remove --template to use direct download mode."));
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
fetchedTemplates = probeResult.templates;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
else if (!options.yes && registry) {
|
|
1182
|
+
// Interactive mode: show template selection
|
|
1183
|
+
const timeoutSec = TIMEOUTS.INDEX_FETCH_MS / 1000;
|
|
1184
|
+
const sourceLabel = registry.gigetSource;
|
|
1185
|
+
console.log(chalk.gray(` Fetching available templates from ${sourceLabel}`));
|
|
1186
|
+
let elapsed = 0;
|
|
1187
|
+
const ticker = setInterval(() => {
|
|
1188
|
+
elapsed++;
|
|
1189
|
+
process.stdout.write(`\r${chalk.gray(` Loading... ${elapsed}s/${timeoutSec}s`)}`);
|
|
1190
|
+
}, 1000);
|
|
1191
|
+
process.stdout.write(chalk.gray(` Loading... 0s/${timeoutSec}s`));
|
|
1192
|
+
const probeResult = await probeRegistryIndex(indexUrl, registry);
|
|
1193
|
+
const templates = probeResult.templates;
|
|
1194
|
+
const registryProbeNotFound = probeResult.isNotFound;
|
|
1195
|
+
const registryProbeError = probeResult.error;
|
|
1196
|
+
registryBackend = probeResult.backend;
|
|
1197
|
+
clearInterval(ticker);
|
|
1198
|
+
// Clear the loading line
|
|
1199
|
+
process.stdout.write("\r\x1b[2K");
|
|
1200
|
+
fetchedTemplates = templates;
|
|
1201
|
+
if (templates.length === 0 && registry && registryProbeNotFound) {
|
|
1202
|
+
// Custom registry: confirmed no index.json — will try direct download later
|
|
1203
|
+
console.log(chalk.gray(" No index.json found at registry. Will download as direct spec template."));
|
|
1204
|
+
}
|
|
1205
|
+
else if (templates.length === 0 && registry) {
|
|
1206
|
+
// Custom registry: transient error (not a 404) — abort, don't misclassify
|
|
1207
|
+
console.log(chalk.red(` ${registryProbeError?.message ?? "Could not reach registry. Check your connection and try again."}`));
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
else if (templates.length === 0) {
|
|
1211
|
+
console.log(chalk.gray(" Could not fetch templates (offline or server unavailable)."));
|
|
1212
|
+
console.log(chalk.gray(" Using blank templates.\n"));
|
|
1213
|
+
}
|
|
1214
|
+
if (templates.length > 0) {
|
|
1215
|
+
// Build template choices
|
|
1216
|
+
const specTemplates = templates
|
|
1217
|
+
.filter((t) => t.type === "spec")
|
|
1218
|
+
.map((t) => ({
|
|
1219
|
+
name: `${t.id} (${t.name})`,
|
|
1220
|
+
value: t.id,
|
|
1221
|
+
}));
|
|
1222
|
+
const templateChoices = registry
|
|
1223
|
+
? specTemplates
|
|
1224
|
+
: [
|
|
1225
|
+
{
|
|
1226
|
+
name: "from scratch (default)",
|
|
1227
|
+
value: "blank",
|
|
1228
|
+
},
|
|
1229
|
+
...specTemplates,
|
|
1230
|
+
{
|
|
1231
|
+
name: "custom (enter a registry source)",
|
|
1232
|
+
value: "__custom__",
|
|
1233
|
+
},
|
|
1234
|
+
];
|
|
1235
|
+
// Loop to allow returning from custom source input back to the picker
|
|
1236
|
+
let templatePicked = false;
|
|
1237
|
+
while (templateChoices.length > 0 && !templatePicked) {
|
|
1238
|
+
const templateAnswer = await inquirer.prompt([
|
|
1239
|
+
{
|
|
1240
|
+
type: "list",
|
|
1241
|
+
name: "template",
|
|
1242
|
+
message: "Select a spec template:",
|
|
1243
|
+
choices: templateChoices,
|
|
1244
|
+
default: registry ? undefined : "blank",
|
|
1245
|
+
},
|
|
1246
|
+
]);
|
|
1247
|
+
if (templateAnswer.template === "__custom__") {
|
|
1248
|
+
// Prompt for custom registry source (empty → back to picker)
|
|
1249
|
+
const customSource = await askInput("Enter registry source (e.g., gh:myorg/myrepo/specs), or press Enter to go back: ");
|
|
1250
|
+
if (!customSource) {
|
|
1251
|
+
continue; // Back to picker
|
|
1252
|
+
}
|
|
1253
|
+
try {
|
|
1254
|
+
registry = parseRegistrySource(customSource);
|
|
1255
|
+
registrySourceForConfig = customSource;
|
|
1256
|
+
fetchedTemplates = []; // Reset so direct-download guard works correctly
|
|
1257
|
+
// Probe index.json to detect marketplace vs direct download
|
|
1258
|
+
const customIndexUrl = `${registry.rawBaseUrl}/index.json`;
|
|
1259
|
+
console.log(chalk.gray(` Checking for templates at ${registry.gigetSource}...`));
|
|
1260
|
+
const customProbe = await probeRegistryIndex(customIndexUrl, registry);
|
|
1261
|
+
const customTemplates = customProbe.templates;
|
|
1262
|
+
registryBackend = customProbe.backend;
|
|
1263
|
+
if (customTemplates.length > 0) {
|
|
1264
|
+
// Marketplace mode: show picker with custom templates
|
|
1265
|
+
fetchedTemplates = customTemplates;
|
|
1266
|
+
const customChoices = customTemplates
|
|
1267
|
+
.filter((t) => t.type === "spec")
|
|
1268
|
+
.map((t) => ({
|
|
1269
|
+
name: `${t.id} (${t.name})`,
|
|
1270
|
+
value: t.id,
|
|
1271
|
+
}));
|
|
1272
|
+
if (customChoices.length > 0) {
|
|
1273
|
+
const customAnswer = await inquirer.prompt([
|
|
1264
1274
|
{
|
|
1265
1275
|
type: "list",
|
|
1266
|
-
name: "
|
|
1267
|
-
message:
|
|
1268
|
-
choices:
|
|
1269
|
-
{ name: "Skip (keep existing)", value: "skip" },
|
|
1270
|
-
{
|
|
1271
|
-
name: "Overwrite (replace all)",
|
|
1272
|
-
value: "overwrite",
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
name: "Append (add missing files only)",
|
|
1276
|
-
value: "append",
|
|
1277
|
-
},
|
|
1278
|
-
],
|
|
1279
|
-
default: "skip",
|
|
1276
|
+
name: "template",
|
|
1277
|
+
message: "Select a spec template:",
|
|
1278
|
+
choices: customChoices,
|
|
1280
1279
|
},
|
|
1281
1280
|
]);
|
|
1282
|
-
|
|
1281
|
+
selectedTemplate = customAnswer.template;
|
|
1282
|
+
// Check if spec directory already exists and ask what to do
|
|
1283
|
+
const specDir = path.join(cwd, PATHS.SPEC);
|
|
1284
|
+
if (fs.existsSync(specDir) &&
|
|
1285
|
+
!options.overwrite &&
|
|
1286
|
+
!options.append) {
|
|
1287
|
+
const actionAnswer = await inquirer.prompt([
|
|
1288
|
+
{
|
|
1289
|
+
type: "list",
|
|
1290
|
+
name: "action",
|
|
1291
|
+
message: `Directory ${PATHS.SPEC} already exists. What do you want to do?`,
|
|
1292
|
+
choices: [
|
|
1293
|
+
{ name: "Skip (keep existing)", value: "skip" },
|
|
1294
|
+
{
|
|
1295
|
+
name: "Overwrite (replace all)",
|
|
1296
|
+
value: "overwrite",
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
name: "Append (add missing files only)",
|
|
1300
|
+
value: "append",
|
|
1301
|
+
},
|
|
1302
|
+
],
|
|
1303
|
+
default: "skip",
|
|
1304
|
+
},
|
|
1305
|
+
]);
|
|
1306
|
+
templateStrategy = actionAnswer.action;
|
|
1307
|
+
}
|
|
1283
1308
|
}
|
|
1309
|
+
templatePicked = true;
|
|
1310
|
+
}
|
|
1311
|
+
else if (customProbe.isNotFound) {
|
|
1312
|
+
// No index.json → direct download mode
|
|
1313
|
+
templatePicked = true;
|
|
1314
|
+
}
|
|
1315
|
+
else {
|
|
1316
|
+
// Transient error (not 404) — loop back, don't misclassify
|
|
1317
|
+
console.log(chalk.yellow(` ${customProbe.error?.message ?? "Could not reach registry. Try again or enter a different source."}`));
|
|
1318
|
+
registry = undefined; // Reset so we don't fall through to direct download
|
|
1319
|
+
registrySourceForConfig = undefined;
|
|
1284
1320
|
}
|
|
1285
|
-
templatePicked = true;
|
|
1286
|
-
}
|
|
1287
|
-
else if (customProbe.isNotFound) {
|
|
1288
|
-
// No index.json → direct download mode
|
|
1289
|
-
templatePicked = true;
|
|
1290
1321
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1322
|
+
catch (error) {
|
|
1323
|
+
console.log(chalk.red(error instanceof Error
|
|
1324
|
+
? error.message
|
|
1325
|
+
: "Invalid registry source"));
|
|
1326
|
+
// Loop back to picker
|
|
1296
1327
|
}
|
|
1297
1328
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
},
|
|
1326
|
-
],
|
|
1327
|
-
default: "skip",
|
|
1328
|
-
},
|
|
1329
|
-
]);
|
|
1330
|
-
templateStrategy = actionAnswer.action;
|
|
1329
|
+
else {
|
|
1330
|
+
templatePicked = true;
|
|
1331
|
+
if (templateAnswer.template !== "blank") {
|
|
1332
|
+
selectedTemplate = templateAnswer.template;
|
|
1333
|
+
// Check if spec directory already exists and ask what to do
|
|
1334
|
+
const specDir = path.join(cwd, PATHS.SPEC);
|
|
1335
|
+
if (fs.existsSync(specDir) &&
|
|
1336
|
+
!options.overwrite &&
|
|
1337
|
+
!options.append) {
|
|
1338
|
+
const actionAnswer = await inquirer.prompt([
|
|
1339
|
+
{
|
|
1340
|
+
type: "list",
|
|
1341
|
+
name: "action",
|
|
1342
|
+
message: `Directory ${PATHS.SPEC} already exists. What do you want to do?`,
|
|
1343
|
+
choices: [
|
|
1344
|
+
{ name: "Skip (keep existing)", value: "skip" },
|
|
1345
|
+
{ name: "Overwrite (replace all)", value: "overwrite" },
|
|
1346
|
+
{
|
|
1347
|
+
name: "Append (add missing files only)",
|
|
1348
|
+
value: "append",
|
|
1349
|
+
},
|
|
1350
|
+
],
|
|
1351
|
+
default: "skip",
|
|
1352
|
+
},
|
|
1353
|
+
]);
|
|
1354
|
+
templateStrategy = actionAnswer.action;
|
|
1355
|
+
}
|
|
1331
1356
|
}
|
|
1332
1357
|
}
|
|
1333
1358
|
}
|
|
1334
1359
|
}
|
|
1335
1360
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1361
|
+
// -y mode with --registry (no --template): probe index.json to detect mode
|
|
1362
|
+
// Skip when monorepo mode already handled templates above
|
|
1363
|
+
if (options.yes && registry && !selectedTemplate && !monorepoPackages) {
|
|
1364
|
+
const probeResult = await probeRegistryIndex(`${registry.rawBaseUrl}/index.json`, registry);
|
|
1365
|
+
registryBackend = probeResult.backend;
|
|
1366
|
+
if (probeResult.templates.length > 0) {
|
|
1367
|
+
// Marketplace mode requires interactive selection — can't auto-select
|
|
1368
|
+
console.log(chalk.red("Error: Registry is a marketplace with multiple templates. " +
|
|
1369
|
+
"Use --template <id> to specify which one, or remove -y for interactive selection."));
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (!probeResult.isNotFound) {
|
|
1373
|
+
// Transient error (not 404) — abort, don't misclassify as direct-download
|
|
1374
|
+
console.log(chalk.red(`Error: ${probeResult.error?.message ?? "Could not reach registry. Check your connection and try again."}`));
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
// isNotFound=true → no index.json, proceed with direct download (fetchedTemplates stays empty)
|
|
1352
1378
|
}
|
|
1353
|
-
//
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1379
|
+
// ==========================================================================
|
|
1380
|
+
// Download Remote Template (if selected or direct registry download)
|
|
1381
|
+
// ==========================================================================
|
|
1382
|
+
let useRemoteTemplate = false;
|
|
1383
|
+
let registrySpecConfigToPersist = null;
|
|
1384
|
+
if (selectedTemplate) {
|
|
1385
|
+
// Marketplace mode: download specific template by ID
|
|
1386
|
+
console.log(chalk.blue(`📦 Downloading template "${selectedTemplate}"...`));
|
|
1387
|
+
console.log(chalk.gray(" This may take a moment on slow connections."));
|
|
1388
|
+
// Find pre-fetched SpecTemplate to avoid double-fetch
|
|
1389
|
+
const prefetched = fetchedTemplates.find((t) => t.id === selectedTemplate);
|
|
1390
|
+
const result = await downloadTemplateById(cwd, selectedTemplate, templateStrategy, prefetched, registry, undefined, registryBackend);
|
|
1391
|
+
if (result.success) {
|
|
1392
|
+
if (result.skipped) {
|
|
1393
|
+
console.log(chalk.gray(` ${result.message}`));
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
console.log(chalk.green(` ${result.message}`));
|
|
1397
|
+
useRemoteTemplate = true;
|
|
1398
|
+
if (registry) {
|
|
1399
|
+
registrySpecConfigToPersist = {
|
|
1400
|
+
source: registrySourceForConfig ?? registry.gigetSource,
|
|
1401
|
+
template: selectedTemplate,
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1370
1405
|
}
|
|
1371
1406
|
else {
|
|
1372
|
-
console.log(chalk.
|
|
1373
|
-
|
|
1374
|
-
|
|
1407
|
+
console.log(chalk.yellow(` ${result.message}`));
|
|
1408
|
+
console.log(chalk.gray(" Falling back to blank templates..."));
|
|
1409
|
+
const retryCmd = registry
|
|
1410
|
+
? `trellis init --registry ${registry.gigetSource} --template ${selectedTemplate}`
|
|
1411
|
+
: `trellis init --template ${selectedTemplate}`;
|
|
1412
|
+
console.log(chalk.gray(` You can retry later: ${retryCmd}`));
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
else if (registry && fetchedTemplates.length === 0) {
|
|
1416
|
+
// Direct download mode: registry has no index.json, download directory directly
|
|
1417
|
+
console.log(chalk.blue(`📦 Downloading spec from ${registry.gigetSource}...`));
|
|
1418
|
+
console.log(chalk.gray(" This may take a moment on slow connections."));
|
|
1419
|
+
// Ask about existing spec dir in interactive mode
|
|
1420
|
+
if (!options.yes && !options.overwrite && !options.append) {
|
|
1421
|
+
const specDir = path.join(cwd, PATHS.SPEC);
|
|
1422
|
+
if (fs.existsSync(specDir)) {
|
|
1423
|
+
const actionAnswer = await inquirer.prompt([
|
|
1424
|
+
{
|
|
1425
|
+
type: "list",
|
|
1426
|
+
name: "action",
|
|
1427
|
+
message: `Directory ${PATHS.SPEC} already exists. What do you want to do?`,
|
|
1428
|
+
choices: [
|
|
1429
|
+
{ name: "Skip (keep existing)", value: "skip" },
|
|
1430
|
+
{ name: "Overwrite (replace all)", value: "overwrite" },
|
|
1431
|
+
{ name: "Append (add missing files only)", value: "append" },
|
|
1432
|
+
],
|
|
1433
|
+
default: "skip",
|
|
1434
|
+
},
|
|
1435
|
+
]);
|
|
1436
|
+
templateStrategy = actionAnswer.action;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
const result = await downloadRegistryDirect(cwd, registry, templateStrategy, undefined, registryBackend);
|
|
1440
|
+
if (result.success) {
|
|
1441
|
+
if (result.skipped) {
|
|
1442
|
+
console.log(chalk.gray(` ${result.message}`));
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
console.log(chalk.green(` ${result.message}`));
|
|
1446
|
+
useRemoteTemplate = true;
|
|
1375
1447
|
registrySpecConfigToPersist = {
|
|
1376
1448
|
source: registrySourceForConfig ?? registry.gigetSource,
|
|
1377
|
-
template: selectedTemplate,
|
|
1378
1449
|
};
|
|
1379
1450
|
}
|
|
1380
1451
|
}
|
|
1381
|
-
}
|
|
1382
|
-
else {
|
|
1383
|
-
console.log(chalk.yellow(` ${result.message}`));
|
|
1384
|
-
console.log(chalk.gray(" Falling back to blank templates..."));
|
|
1385
|
-
const retryCmd = registry
|
|
1386
|
-
? `trellis init --registry ${registry.gigetSource} --template ${selectedTemplate}`
|
|
1387
|
-
: `trellis init --template ${selectedTemplate}`;
|
|
1388
|
-
console.log(chalk.gray(` You can retry later: ${retryCmd}`));
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
else if (registry && fetchedTemplates.length === 0) {
|
|
1392
|
-
// Direct download mode: registry has no index.json, download directory directly
|
|
1393
|
-
console.log(chalk.blue(`📦 Downloading spec from ${registry.gigetSource}...`));
|
|
1394
|
-
console.log(chalk.gray(" This may take a moment on slow connections."));
|
|
1395
|
-
// Ask about existing spec dir in interactive mode
|
|
1396
|
-
if (!options.yes && !options.overwrite && !options.append) {
|
|
1397
|
-
const specDir = path.join(cwd, PATHS.SPEC);
|
|
1398
|
-
if (fs.existsSync(specDir)) {
|
|
1399
|
-
const actionAnswer = await inquirer.prompt([
|
|
1400
|
-
{
|
|
1401
|
-
type: "list",
|
|
1402
|
-
name: "action",
|
|
1403
|
-
message: `Directory ${PATHS.SPEC} already exists. What do you want to do?`,
|
|
1404
|
-
choices: [
|
|
1405
|
-
{ name: "Skip (keep existing)", value: "skip" },
|
|
1406
|
-
{ name: "Overwrite (replace all)", value: "overwrite" },
|
|
1407
|
-
{ name: "Append (add missing files only)", value: "append" },
|
|
1408
|
-
],
|
|
1409
|
-
default: "skip",
|
|
1410
|
-
},
|
|
1411
|
-
]);
|
|
1412
|
-
templateStrategy = actionAnswer.action;
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
const result = await downloadRegistryDirect(cwd, registry, templateStrategy, undefined, registryBackend);
|
|
1416
|
-
if (result.success) {
|
|
1417
|
-
if (result.skipped) {
|
|
1418
|
-
console.log(chalk.gray(` ${result.message}`));
|
|
1419
|
-
}
|
|
1420
1452
|
else {
|
|
1421
|
-
console.log(chalk.
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
source: registrySourceForConfig ?? registry.gigetSource,
|
|
1425
|
-
};
|
|
1453
|
+
console.log(chalk.yellow(` ${result.message}`));
|
|
1454
|
+
console.log(chalk.gray(" Falling back to blank templates..."));
|
|
1455
|
+
console.log(chalk.gray(` You can retry later: trellis init --registry ${registry.gigetSource}`));
|
|
1426
1456
|
}
|
|
1427
1457
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
source: options.workflowSource,
|
|
1445
|
-
});
|
|
1446
|
-
if (resolved.id !== NATIVE_WORKFLOW_ID) {
|
|
1447
|
-
workflowMdOverride = resolved.content;
|
|
1448
|
-
console.log(chalk.blue(`🧭 Using workflow template: ${chalk.cyan(resolved.id)}`));
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
// ==========================================================================
|
|
1452
|
-
// Create Workflow Structure
|
|
1453
|
-
// ==========================================================================
|
|
1454
|
-
// Record every successful write from here through createRootFiles. The
|
|
1455
|
-
// captured set is the source of truth for `.template-hashes.json`'s
|
|
1456
|
-
// platform/root entries — replacing the previous "walk every managed dir"
|
|
1457
|
-
// approach that swept user-owned runtime files into the manifest
|
|
1458
|
-
// (.codex/sessions/, .claude/projects/, pre-existing AGENTS.md).
|
|
1459
|
-
const writtenPaths = startRecordingWrites(cwd);
|
|
1460
|
-
try {
|
|
1461
|
-
// Create workflow structure with project type
|
|
1462
|
-
console.log(chalk.blue("📁 Creating workflow structure..."));
|
|
1463
|
-
await createWorkflowStructure(cwd, {
|
|
1464
|
-
projectType,
|
|
1465
|
-
skipSpecTemplates: useRemoteTemplate,
|
|
1466
|
-
packages: monorepoPackages,
|
|
1467
|
-
remoteSpecPackages,
|
|
1468
|
-
workflowMdOverride,
|
|
1469
|
-
});
|
|
1470
|
-
// Write monorepo packages to config.yaml (non-destructive patch)
|
|
1471
|
-
if (monorepoPackages) {
|
|
1472
|
-
writeMonorepoConfig(cwd, monorepoPackages);
|
|
1473
|
-
console.log(chalk.blue("📦 Monorepo packages written to config.yaml"));
|
|
1458
|
+
// ==========================================================================
|
|
1459
|
+
// Resolve workflow template (default: native bundled)
|
|
1460
|
+
// ==========================================================================
|
|
1461
|
+
const workflowIdInput = options.workflow?.trim();
|
|
1462
|
+
const workflowId = workflowIdInput && workflowIdInput.length > 0
|
|
1463
|
+
? workflowIdInput
|
|
1464
|
+
: NATIVE_WORKFLOW_ID;
|
|
1465
|
+
let workflowMdOverride;
|
|
1466
|
+
if (workflowId !== NATIVE_WORKFLOW_ID || options.workflowSource) {
|
|
1467
|
+
const resolved = await resolveWorkflowTemplate(workflowId, {
|
|
1468
|
+
source: options.workflowSource,
|
|
1469
|
+
});
|
|
1470
|
+
if (resolved.id !== NATIVE_WORKFLOW_ID) {
|
|
1471
|
+
workflowMdOverride = resolved.content;
|
|
1472
|
+
console.log(chalk.blue(`🧭 Using workflow template: ${chalk.cyan(resolved.id)}`));
|
|
1473
|
+
}
|
|
1474
1474
|
}
|
|
1475
|
-
//
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
//
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1475
|
+
// ==========================================================================
|
|
1476
|
+
// Create Workflow Structure
|
|
1477
|
+
// ==========================================================================
|
|
1478
|
+
// Record every successful write from here through createRootFiles. The
|
|
1479
|
+
// captured set is the source of truth for `.template-hashes.json`'s
|
|
1480
|
+
// platform/root entries — replacing the previous "walk every managed dir"
|
|
1481
|
+
// approach that swept user-owned runtime files into the manifest
|
|
1482
|
+
// (.codex/sessions/, .claude/projects/, pre-existing AGENTS.md).
|
|
1483
|
+
const writtenPaths = startRecordingWrites(cwd);
|
|
1484
|
+
try {
|
|
1485
|
+
// Create workflow structure with project type
|
|
1486
|
+
console.log(chalk.blue("📁 Creating workflow structure..."));
|
|
1487
|
+
await createWorkflowStructure(cwd, {
|
|
1488
|
+
projectType,
|
|
1489
|
+
skipSpecTemplates: useRemoteTemplate,
|
|
1490
|
+
packages: monorepoPackages,
|
|
1491
|
+
remoteSpecPackages,
|
|
1492
|
+
workflowMdOverride,
|
|
1493
|
+
});
|
|
1494
|
+
// Write monorepo packages to config.yaml (non-destructive patch)
|
|
1495
|
+
if (monorepoPackages) {
|
|
1496
|
+
writeMonorepoConfig(cwd, monorepoPackages);
|
|
1497
|
+
console.log(chalk.blue("📦 Monorepo packages written to config.yaml"));
|
|
1498
|
+
}
|
|
1499
|
+
// Write version file for update tracking
|
|
1500
|
+
const versionPath = path.join(cwd, DIR_NAMES.WORKFLOW, ".version");
|
|
1501
|
+
fs.writeFileSync(versionPath, VERSION);
|
|
1502
|
+
// Configure selected tools by copying entire directories (dogfooding)
|
|
1503
|
+
for (const tool of tools) {
|
|
1504
|
+
const platformId = resolveCliFlag(tool);
|
|
1505
|
+
if (platformId) {
|
|
1506
|
+
console.log(chalk.blue(`📝 Configuring ${AI_TOOLS[platformId].name}...`));
|
|
1507
|
+
await configurePlatform(platformId, cwd, {
|
|
1508
|
+
withStatusline: options.withStatusline,
|
|
1509
|
+
});
|
|
1510
|
+
if (platformId === "claude-code" && options.withStatusline) {
|
|
1511
|
+
console.log(chalk.gray(" ↳ Trellis statusLine installed (--with-statusline)"));
|
|
1512
|
+
}
|
|
1488
1513
|
}
|
|
1489
1514
|
}
|
|
1515
|
+
const pythonPlatforms = getPlatformsWithPythonHooks();
|
|
1516
|
+
const hasSelectedPythonPlatform = pythonPlatforms.some((id) => tools.includes(AI_TOOLS[id].cliFlag));
|
|
1517
|
+
if (hasSelectedPythonPlatform) {
|
|
1518
|
+
logPythonAdaptationNotice(pythonCmd);
|
|
1519
|
+
}
|
|
1520
|
+
// Create root files (skip if exists)
|
|
1521
|
+
await createRootFiles(cwd);
|
|
1490
1522
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
if (hasSelectedPythonPlatform) {
|
|
1494
|
-
logPythonAdaptationNotice(pythonCmd);
|
|
1495
|
-
}
|
|
1496
|
-
// Create root files (skip if exists)
|
|
1497
|
-
await createRootFiles(cwd);
|
|
1498
|
-
}
|
|
1499
|
-
finally {
|
|
1500
|
-
stopRecordingWrites();
|
|
1501
|
-
}
|
|
1502
|
-
if (registrySpecConfigToPersist) {
|
|
1503
|
-
writeSpecRegistryConfig(cwd, registrySpecConfigToPersist);
|
|
1504
|
-
}
|
|
1505
|
-
// Initialize template hashes for modification tracking
|
|
1506
|
-
const hashedCount = initializeHashes(cwd, { trackedPaths: writtenPaths });
|
|
1507
|
-
if (useRemoteTemplate) {
|
|
1508
|
-
const specFilesToHash = new Map();
|
|
1509
|
-
for (const relativePath of collectSpecPaths(cwd)) {
|
|
1510
|
-
const content = fs.readFileSync(path.join(cwd, relativePath), "utf-8");
|
|
1511
|
-
specFilesToHash.set(relativePath, content);
|
|
1523
|
+
finally {
|
|
1524
|
+
stopRecordingWrites();
|
|
1512
1525
|
}
|
|
1513
|
-
if (
|
|
1514
|
-
|
|
1526
|
+
if (registrySpecConfigToPersist) {
|
|
1527
|
+
writeSpecRegistryConfig(cwd, registrySpecConfigToPersist);
|
|
1515
1528
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
// Initialize developer identity (silent - no output)
|
|
1528
|
-
if (developerName) {
|
|
1529
|
-
try {
|
|
1530
|
-
const scriptPath = path.join(cwd, PATHS.SCRIPTS, "init_developer.py");
|
|
1531
|
-
execSync(`${pythonCmd} "${scriptPath}" "${developerName}"`, {
|
|
1532
|
-
cwd,
|
|
1533
|
-
stdio: "pipe", // Silent
|
|
1534
|
-
});
|
|
1529
|
+
// Initialize template hashes for modification tracking
|
|
1530
|
+
const hashedCount = initializeHashes(cwd, { trackedPaths: writtenPaths });
|
|
1531
|
+
if (useRemoteTemplate) {
|
|
1532
|
+
const specFilesToHash = new Map();
|
|
1533
|
+
for (const relativePath of collectSpecPaths(cwd)) {
|
|
1534
|
+
const content = fs.readFileSync(path.join(cwd, relativePath), "utf-8");
|
|
1535
|
+
specFilesToHash.set(relativePath, content);
|
|
1536
|
+
}
|
|
1537
|
+
if (specFilesToHash.size > 0) {
|
|
1538
|
+
updateHashes(cwd, specFilesToHash);
|
|
1539
|
+
}
|
|
1535
1540
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1541
|
+
if (hashedCount > 0) {
|
|
1542
|
+
console.log(chalk.gray(`📋 Tracking ${hashedCount} template files for updates`));
|
|
1538
1543
|
}
|
|
1539
|
-
//
|
|
1540
|
-
//
|
|
1541
|
-
//
|
|
1542
|
-
//
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
//
|
|
1546
|
-
// Tasks-empty fallback (issue #204): if .trellis/ exists but tasks dir is
|
|
1547
|
-
// empty, the previous init aborted before creating the bootstrap task. Run
|
|
1548
|
-
// bootstrap creation regardless of isFirstInit. writeTaskSkeleton is
|
|
1549
|
-
// idempotent so repeated triggers are safe.
|
|
1550
|
-
//
|
|
1551
|
-
// Runs OUTSIDE the init_developer try/catch (which uses stdio: "pipe")
|
|
1552
|
-
// so joiner failures surface as warnings instead of being silently
|
|
1553
|
-
// swallowed.
|
|
1554
|
-
const tasksDir = path.join(cwd, PATHS.TASKS);
|
|
1555
|
-
const tasksEmpty = !fs.existsSync(tasksDir) || fs.readdirSync(tasksDir).length === 0;
|
|
1556
|
-
if (isFirstInit || tasksEmpty) {
|
|
1557
|
-
createBootstrapTask(cwd, developerName, pythonCmd, projectType, monorepoPackages);
|
|
1544
|
+
// Non-native workflow is user-managed local content. Drop the
|
|
1545
|
+
// `.trellis/workflow.md` hash entry so `trellis update` classifies it as
|
|
1546
|
+
// modified and does not silently restore native bytes. See design.md
|
|
1547
|
+
// "Durable-state contract".
|
|
1548
|
+
if (workflowMdOverride !== undefined && workflowId !== NATIVE_WORKFLOW_ID) {
|
|
1549
|
+
removeHash(cwd, PATHS.WORKFLOW_GUIDE_FILE);
|
|
1558
1550
|
}
|
|
1559
|
-
|
|
1551
|
+
// Initialize developer identity (silent - no output)
|
|
1552
|
+
if (developerName) {
|
|
1560
1553
|
try {
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1554
|
+
const scriptPath = path.join(cwd, PATHS.SCRIPTS, "init_developer.py");
|
|
1555
|
+
execSync(`${pythonCmd} "${scriptPath}" "${developerName}"`, {
|
|
1556
|
+
cwd,
|
|
1557
|
+
stdio: "pipe", // Silent
|
|
1558
|
+
});
|
|
1564
1559
|
}
|
|
1565
|
-
catch
|
|
1566
|
-
|
|
1560
|
+
catch {
|
|
1561
|
+
// Silent failure - user can run init_developer.py manually
|
|
1562
|
+
}
|
|
1563
|
+
// Three-branch dispatch using flags captured at init() start (before
|
|
1564
|
+
// createWorkflowStructure/init_developer ran, so they reflect the disk
|
|
1565
|
+
// state of the user's checkout, not the state this init just produced):
|
|
1566
|
+
// isFirstInit=true → creator bootstrap (new project)
|
|
1567
|
+
// isFirstInit=false + no .developer file → joiner onboarding (fresh clone)
|
|
1568
|
+
// isFirstInit=false + .developer exists → same-dev re-init, no task
|
|
1569
|
+
//
|
|
1570
|
+
// Tasks-empty fallback (issue #204): if .trellis/ exists but tasks dir is
|
|
1571
|
+
// empty, the previous init aborted before creating the bootstrap task. Run
|
|
1572
|
+
// bootstrap creation regardless of isFirstInit. writeTaskSkeleton is
|
|
1573
|
+
// idempotent so repeated triggers are safe.
|
|
1574
|
+
//
|
|
1575
|
+
// Runs OUTSIDE the init_developer try/catch (which uses stdio: "pipe")
|
|
1576
|
+
// so joiner failures surface as warnings instead of being silently
|
|
1577
|
+
// swallowed.
|
|
1578
|
+
const tasksEmpty = tasksAreEmpty(cwd);
|
|
1579
|
+
if (isFirstInit || tasksEmpty) {
|
|
1580
|
+
createBootstrapTask(cwd, developerName, pythonCmd, projectType, monorepoPackages);
|
|
1581
|
+
}
|
|
1582
|
+
else if (!hadDeveloperFileAtStart) {
|
|
1583
|
+
try {
|
|
1584
|
+
if (!createJoinerOnboardingTask(cwd, developerName, pythonCmd)) {
|
|
1585
|
+
console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
catch (err) {
|
|
1589
|
+
console.warn(chalk.yellow(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
1590
|
+
}
|
|
1567
1591
|
}
|
|
1568
1592
|
}
|
|
1569
|
-
}
|
|
1593
|
+
});
|
|
1570
1594
|
}
|
|
1571
1595
|
/**
|
|
1572
1596
|
* Simple readline-based input (no flickering like inquirer)
|