@mindfoldhq/trellis 0.3.10 → 0.4.0-beta.10
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 +19 -5
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +240 -43
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +206 -47
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/codebuddy.d.ts +11 -0
- package/dist/configurators/codebuddy.d.ts.map +1 -0
- package/dist/configurators/codebuddy.js +58 -0
- package/dist/configurators/codebuddy.js.map +1 -0
- package/dist/configurators/codex.d.ts +7 -4
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +40 -10
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts +9 -0
- package/dist/configurators/copilot.d.ts.map +1 -0
- package/dist/configurators/copilot.js +34 -0
- package/dist/configurators/copilot.js.map +1 -0
- package/dist/configurators/index.d.ts +11 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +72 -4
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/opencode.d.ts +1 -1
- package/dist/configurators/opencode.js +1 -1
- package/dist/configurators/windsurf.d.ts +8 -0
- package/dist/configurators/windsurf.d.ts.map +1 -0
- package/dist/configurators/windsurf.js +18 -0
- package/dist/configurators/windsurf.js.map +1 -0
- package/dist/configurators/workflow.d.ts +6 -2
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +90 -58
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -0
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/manifests/0.4.0-beta.1.json +228 -0
- package/dist/migrations/manifests/0.4.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.2.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.3.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.6.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.7.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.8.json +34 -0
- package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
- package/dist/templates/claude/agents/dispatch.md +1 -2
- package/dist/templates/claude/agents/implement.md +2 -3
- package/dist/templates/claude/commands/trellis/before-dev.md +29 -0
- package/dist/templates/claude/commands/trellis/check.md +25 -0
- package/dist/templates/claude/commands/trellis/create-command.md +2 -2
- package/dist/templates/claude/commands/trellis/onboard.md +13 -13
- package/dist/templates/claude/commands/trellis/parallel.md +1 -2
- package/dist/templates/claude/commands/trellis/record-session.md +3 -2
- package/dist/templates/claude/commands/trellis/start.md +8 -4
- package/dist/templates/claude/hooks/inject-subagent-context.py +29 -14
- package/dist/templates/claude/hooks/ralph-loop.py +18 -10
- package/dist/templates/claude/hooks/session-start.py +201 -9
- package/dist/templates/claude/hooks/statusline.py +211 -0
- package/dist/templates/claude/settings.json +4 -0
- package/dist/templates/codebuddy/commands/trellis/before-dev.md +29 -0
- package/dist/templates/codebuddy/commands/trellis/brainstorm.md +487 -0
- package/dist/templates/codebuddy/commands/trellis/break-loop.md +107 -0
- package/dist/templates/codebuddy/commands/trellis/check-cross-layer.md +153 -0
- package/dist/templates/codebuddy/commands/trellis/check.md +25 -0
- package/dist/templates/codebuddy/commands/trellis/create-command.md +154 -0
- package/dist/templates/codebuddy/commands/trellis/finish-work.md +143 -0
- package/dist/templates/codebuddy/commands/trellis/integrate-skill.md +219 -0
- package/dist/templates/codebuddy/commands/trellis/onboard.md +358 -0
- package/dist/templates/codebuddy/commands/trellis/record-session.md +61 -0
- package/dist/templates/codebuddy/commands/trellis/start.md +373 -0
- package/dist/templates/codebuddy/commands/trellis/update-spec.md +354 -0
- package/dist/templates/codebuddy/index.d.ts +25 -0
- package/dist/templates/codebuddy/index.d.ts.map +1 -0
- package/dist/templates/codebuddy/index.js +45 -0
- package/dist/templates/codebuddy/index.js.map +1 -0
- package/dist/templates/codex/agents/check.toml +23 -0
- package/dist/templates/codex/agents/implement.toml +19 -0
- package/dist/templates/codex/agents/research.toml +26 -0
- package/dist/templates/codex/codex-skills/parallel/SKILL.md +194 -0
- package/dist/templates/codex/config.toml +5 -0
- package/dist/templates/codex/hooks/session-start.py +228 -0
- package/dist/templates/codex/hooks.json +16 -0
- package/dist/templates/codex/index.d.ts +27 -5
- package/dist/templates/codex/index.d.ts.map +1 -1
- package/dist/templates/codex/index.js +60 -8
- package/dist/templates/codex/index.js.map +1 -1
- package/dist/templates/codex/skills/before-dev/SKILL.md +34 -0
- package/dist/templates/codex/skills/brainstorm/SKILL.md +1 -1
- package/dist/templates/codex/skills/break-loop/SKILL.md +1 -1
- package/dist/templates/codex/skills/check/SKILL.md +30 -0
- package/dist/templates/codex/skills/check-cross-layer/SKILL.md +1 -1
- package/dist/templates/codex/skills/create-command/SKILL.md +3 -3
- package/dist/templates/codex/skills/finish-work/SKILL.md +1 -1
- package/dist/templates/codex/skills/improve-ut/SKILL.md +69 -0
- package/dist/templates/codex/skills/integrate-skill/SKILL.md +1 -1
- package/dist/templates/codex/skills/onboard/SKILL.md +12 -12
- package/dist/templates/codex/skills/record-session/SKILL.md +4 -3
- package/dist/templates/codex/skills/start/SKILL.md +9 -4
- package/dist/templates/codex/skills/update-spec/SKILL.md +1 -1
- package/dist/templates/copilot/hooks/session-start.py +218 -0
- package/dist/templates/copilot/hooks.json +11 -0
- package/dist/templates/copilot/index.d.ts +23 -0
- package/dist/templates/copilot/index.d.ts.map +1 -0
- package/dist/templates/copilot/index.js +54 -0
- package/dist/templates/copilot/index.js.map +1 -0
- package/dist/templates/copilot/prompts/before-dev.prompt.md +33 -0
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +491 -0
- package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
- package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
- package/dist/templates/copilot/prompts/check.prompt.md +29 -0
- package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
- package/dist/templates/copilot/prompts/finish-work.prompt.md +157 -0
- package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
- package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
- package/dist/templates/copilot/prompts/parallel.prompt.md +196 -0
- package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
- package/dist/templates/copilot/prompts/start.prompt.md +397 -0
- package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
- package/dist/templates/cursor/commands/trellis-before-dev.md +29 -0
- package/dist/templates/cursor/commands/trellis-check.md +25 -0
- package/dist/templates/cursor/commands/trellis-create-command.md +2 -2
- package/dist/templates/cursor/commands/trellis-onboard.md +13 -13
- package/dist/templates/cursor/commands/trellis-record-session.md +3 -2
- package/dist/templates/cursor/commands/trellis-start.md +7 -16
- package/dist/templates/extract.d.ts +36 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +64 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/gemini/commands/trellis/before-dev.toml +33 -0
- package/dist/templates/gemini/commands/trellis/check.toml +29 -0
- package/dist/templates/gemini/commands/trellis/create-command.toml +2 -2
- package/dist/templates/gemini/commands/trellis/onboard.toml +2 -2
- package/dist/templates/gemini/commands/trellis/record-session.toml +3 -2
- package/dist/templates/gemini/commands/trellis/start.toml +9 -4
- package/dist/templates/iflow/agents/dispatch.md +1 -2
- package/dist/templates/iflow/agents/implement.md +2 -3
- package/dist/templates/iflow/commands/trellis/before-dev.md +29 -0
- package/dist/templates/iflow/commands/trellis/check.md +25 -0
- package/dist/templates/iflow/commands/trellis/create-command.md +2 -2
- package/dist/templates/iflow/commands/trellis/onboard.md +13 -13
- package/dist/templates/iflow/commands/trellis/parallel.md +1 -2
- package/dist/templates/iflow/commands/trellis/record-session.md +3 -2
- package/dist/templates/iflow/commands/trellis/start.md +8 -4
- package/dist/templates/iflow/hooks/inject-subagent-context.py +29 -14
- package/dist/templates/iflow/hooks/ralph-loop.py +8 -1
- package/dist/templates/iflow/hooks/session-start.py +187 -8
- package/dist/templates/kilo/workflows/before-dev.md +29 -0
- package/dist/templates/kilo/workflows/check.md +25 -0
- package/dist/templates/kilo/workflows/create-command.md +2 -2
- package/dist/templates/kilo/workflows/onboard.md +13 -13
- package/dist/templates/kilo/workflows/parallel.md +1 -2
- package/dist/templates/kilo/workflows/record-session.md +3 -2
- package/dist/templates/kilo/workflows/start.md +8 -3
- package/dist/templates/kiro/skills/before-dev/SKILL.md +34 -0
- package/dist/templates/kiro/skills/brainstorm/SKILL.md +1 -1
- package/dist/templates/kiro/skills/break-loop/SKILL.md +1 -1
- package/dist/templates/kiro/skills/check/SKILL.md +30 -0
- package/dist/templates/kiro/skills/check-cross-layer/SKILL.md +1 -1
- package/dist/templates/kiro/skills/create-command/SKILL.md +3 -3
- package/dist/templates/kiro/skills/finish-work/SKILL.md +1 -1
- package/dist/templates/kiro/skills/integrate-skill/SKILL.md +1 -1
- package/dist/templates/kiro/skills/onboard/SKILL.md +12 -12
- package/dist/templates/kiro/skills/record-session/SKILL.md +4 -3
- package/dist/templates/kiro/skills/start/SKILL.md +9 -4
- package/dist/templates/kiro/skills/update-spec/SKILL.md +1 -1
- package/dist/templates/markdown/agents.md +4 -0
- package/dist/templates/markdown/spec/backend/directory-structure.md +1 -1
- package/dist/templates/markdown/spec/backend/script-conventions.md +93 -0
- package/dist/templates/markdown/workspace-index.md +2 -0
- package/dist/templates/opencode/agents/dispatch.md +21 -21
- package/dist/templates/opencode/agents/implement.md +2 -2
- package/dist/templates/opencode/agents/research.md +1 -2
- package/dist/templates/opencode/commands/trellis/before-dev.md +29 -0
- package/dist/templates/opencode/commands/trellis/check.md +25 -0
- package/dist/templates/opencode/commands/trellis/create-command.md +2 -2
- package/dist/templates/opencode/commands/trellis/onboard.md +13 -13
- package/dist/templates/opencode/commands/trellis/parallel.md +1 -2
- package/dist/templates/opencode/commands/trellis/record-session.md +3 -2
- package/dist/templates/opencode/commands/trellis/start.md +8 -3
- package/dist/templates/opencode/lib/trellis-context.js +42 -2
- package/dist/templates/opencode/{plugin → plugins}/inject-subagent-context.js +45 -18
- package/dist/templates/opencode/{plugin → plugins}/session-start.js +156 -28
- package/dist/templates/qoder/skills/before-dev/SKILL.md +34 -0
- package/dist/templates/qoder/skills/brainstorm/SKILL.md +1 -1
- package/dist/templates/qoder/skills/break-loop/SKILL.md +1 -1
- package/dist/templates/qoder/skills/check/SKILL.md +30 -0
- package/dist/templates/qoder/skills/check-cross-layer/SKILL.md +1 -1
- package/dist/templates/qoder/skills/create-command/SKILL.md +3 -3
- package/dist/templates/qoder/skills/finish-work/SKILL.md +1 -1
- package/dist/templates/qoder/skills/integrate-skill/SKILL.md +1 -1
- package/dist/templates/qoder/skills/onboard/SKILL.md +14 -14
- package/dist/templates/qoder/skills/record-session/SKILL.md +4 -3
- package/dist/templates/qoder/skills/start/SKILL.md +9 -4
- package/dist/templates/qoder/skills/update-spec/SKILL.md +1 -1
- package/dist/templates/trellis/config.yaml +20 -0
- package/dist/templates/trellis/index.d.ts +11 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +22 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +111 -13
- package/dist/templates/trellis/scripts/common/__init__.py +2 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +164 -64
- package/dist/templates/trellis/scripts/common/config.py +192 -0
- package/dist/templates/trellis/scripts/common/developer.py +2 -2
- package/dist/templates/trellis/scripts/common/git.py +31 -0
- package/dist/templates/trellis/scripts/common/git_context.py +23 -586
- package/dist/templates/trellis/scripts/common/io.py +37 -0
- package/dist/templates/trellis/scripts/common/log.py +45 -0
- package/dist/templates/trellis/scripts/common/packages_context.py +238 -0
- package/dist/templates/trellis/scripts/common/paths.py +103 -6
- package/dist/templates/trellis/scripts/common/phase.py +50 -49
- package/dist/templates/trellis/scripts/common/registry.py +41 -72
- package/dist/templates/trellis/scripts/common/session_context.py +562 -0
- package/dist/templates/trellis/scripts/common/task_context.py +410 -0
- package/dist/templates/trellis/scripts/common/task_queue.py +27 -98
- package/dist/templates/trellis/scripts/common/task_store.py +536 -0
- package/dist/templates/trellis/scripts/common/task_utils.py +106 -10
- package/dist/templates/trellis/scripts/common/tasks.py +109 -0
- package/dist/templates/trellis/scripts/common/types.py +112 -0
- package/dist/templates/trellis/scripts/create_bootstrap.py +32 -27
- package/dist/templates/trellis/scripts/hooks/linear_sync.py +243 -0
- package/dist/templates/trellis/scripts/multi_agent/_bootstrap.py +17 -0
- package/dist/templates/trellis/scripts/multi_agent/cleanup.py +43 -48
- package/dist/templates/trellis/scripts/multi_agent/create_pr.py +336 -45
- package/dist/templates/trellis/scripts/multi_agent/plan.py +9 -32
- package/dist/templates/trellis/scripts/multi_agent/start.py +142 -68
- package/dist/templates/trellis/scripts/multi_agent/status.py +12 -753
- package/dist/templates/trellis/scripts/multi_agent/status_display.py +542 -0
- package/dist/templates/trellis/scripts/multi_agent/status_monitor.py +225 -0
- package/dist/templates/trellis/scripts/task.py +51 -976
- package/dist/templates/trellis/scripts-shell-archive/create-bootstrap.sh +1 -1
- package/dist/templates/trellis/workflow.md +38 -38
- package/dist/templates/windsurf/index.d.ts +21 -0
- package/dist/templates/windsurf/index.d.ts.map +1 -0
- package/dist/templates/windsurf/index.js +44 -0
- package/dist/templates/windsurf/index.js.map +1 -0
- package/dist/templates/windsurf/workflows/trellis-before-dev.md +31 -0
- package/dist/templates/windsurf/workflows/trellis-brainstorm.md +491 -0
- package/dist/templates/windsurf/workflows/trellis-break-loop.md +111 -0
- package/dist/templates/windsurf/workflows/trellis-check-cross-layer.md +157 -0
- package/dist/templates/windsurf/workflows/trellis-check.md +27 -0
- package/dist/templates/windsurf/workflows/trellis-create-command.md +154 -0
- package/dist/templates/windsurf/workflows/trellis-finish-work.md +147 -0
- package/dist/templates/windsurf/workflows/trellis-integrate-skill.md +220 -0
- package/dist/templates/windsurf/workflows/trellis-onboard.md +362 -0
- package/dist/templates/windsurf/workflows/trellis-record-session.md +66 -0
- package/dist/templates/windsurf/workflows/trellis-start.md +373 -0
- package/dist/templates/windsurf/workflows/trellis-update-spec.md +358 -0
- package/dist/types/ai-tools.d.ts +15 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +42 -2
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/types/migration.d.ts +3 -1
- package/dist/types/migration.d.ts.map +1 -1
- package/dist/utils/project-detector.d.ts +28 -0
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +371 -0
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts +19 -6
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +99 -17
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +1 -1
- package/dist/templates/claude/commands/trellis/before-backend-dev.md +0 -13
- package/dist/templates/claude/commands/trellis/before-frontend-dev.md +0 -13
- package/dist/templates/claude/commands/trellis/check-backend.md +0 -13
- package/dist/templates/claude/commands/trellis/check-frontend.md +0 -13
- package/dist/templates/codex/skills/before-backend-dev/SKILL.md +0 -18
- package/dist/templates/codex/skills/before-frontend-dev/SKILL.md +0 -18
- package/dist/templates/codex/skills/check-backend/SKILL.md +0 -18
- package/dist/templates/codex/skills/check-frontend/SKILL.md +0 -18
- package/dist/templates/cursor/commands/trellis-before-backend-dev.md +0 -13
- package/dist/templates/cursor/commands/trellis-before-frontend-dev.md +0 -13
- package/dist/templates/cursor/commands/trellis-check-backend.md +0 -13
- package/dist/templates/cursor/commands/trellis-check-frontend.md +0 -13
- package/dist/templates/gemini/commands/trellis/before-backend-dev.toml +0 -17
- package/dist/templates/gemini/commands/trellis/before-frontend-dev.toml +0 -17
- package/dist/templates/gemini/commands/trellis/check-backend.toml +0 -17
- package/dist/templates/gemini/commands/trellis/check-frontend.toml +0 -17
- package/dist/templates/iflow/commands/trellis/before-backend-dev.md +0 -13
- package/dist/templates/iflow/commands/trellis/before-frontend-dev.md +0 -13
- package/dist/templates/iflow/commands/trellis/check-backend.md +0 -13
- package/dist/templates/iflow/commands/trellis/check-frontend.md +0 -13
- package/dist/templates/kilo/workflows/before-backend-dev.md +0 -13
- package/dist/templates/kilo/workflows/before-frontend-dev.md +0 -13
- package/dist/templates/kilo/workflows/check-backend.md +0 -13
- package/dist/templates/kilo/workflows/check-frontend.md +0 -13
- package/dist/templates/kiro/skills/before-backend-dev/SKILL.md +0 -18
- package/dist/templates/kiro/skills/before-frontend-dev/SKILL.md +0 -18
- package/dist/templates/kiro/skills/check-backend/SKILL.md +0 -18
- package/dist/templates/kiro/skills/check-frontend/SKILL.md +0 -18
- package/dist/templates/opencode/commands/trellis/before-backend-dev.md +0 -13
- package/dist/templates/opencode/commands/trellis/before-frontend-dev.md +0 -13
- package/dist/templates/opencode/commands/trellis/check-backend.md +0 -13
- package/dist/templates/opencode/commands/trellis/check-frontend.md +0 -13
- package/dist/templates/qoder/skills/before-backend-dev/SKILL.md +0 -18
- package/dist/templates/qoder/skills/before-frontend-dev/SKILL.md +0 -18
- package/dist/templates/qoder/skills/check-backend/SKILL.md +0 -18
- package/dist/templates/qoder/skills/check-frontend/SKILL.md +0 -18
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task data access layer.
|
|
3
|
+
|
|
4
|
+
Single source of truth for loading and iterating task directories.
|
|
5
|
+
Replaces scattered task.json parsing across 9+ files.
|
|
6
|
+
|
|
7
|
+
Provides:
|
|
8
|
+
load_task — Load a single task by directory path
|
|
9
|
+
iter_active_tasks — Iterate all non-archived tasks (sorted)
|
|
10
|
+
get_all_statuses — Get {dir_name: status} map for children progress
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Iterator
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from .io import read_json
|
|
19
|
+
from .paths import FILE_TASK_JSON
|
|
20
|
+
from .types import TaskInfo
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_task(task_dir: Path) -> TaskInfo | None:
|
|
24
|
+
"""Load task from a directory containing task.json.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
task_dir: Absolute path to the task directory.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
TaskInfo if task.json exists and is valid, None otherwise.
|
|
31
|
+
"""
|
|
32
|
+
task_json = task_dir / FILE_TASK_JSON
|
|
33
|
+
if not task_json.is_file():
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
data = read_json(task_json)
|
|
37
|
+
if not data:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
return TaskInfo(
|
|
41
|
+
dir_name=task_dir.name,
|
|
42
|
+
directory=task_dir,
|
|
43
|
+
title=data.get("title") or data.get("name") or "unknown",
|
|
44
|
+
status=data.get("status", "unknown"),
|
|
45
|
+
assignee=data.get("assignee", ""),
|
|
46
|
+
priority=data.get("priority", "P2"),
|
|
47
|
+
children=tuple(data.get("children", [])),
|
|
48
|
+
parent=data.get("parent"),
|
|
49
|
+
package=data.get("package"),
|
|
50
|
+
raw=data,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def iter_active_tasks(tasks_dir: Path) -> Iterator[TaskInfo]:
|
|
55
|
+
"""Iterate all active (non-archived) tasks, sorted by directory name.
|
|
56
|
+
|
|
57
|
+
Skips the "archive" directory and directories without valid task.json.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
tasks_dir: Path to the tasks directory.
|
|
61
|
+
|
|
62
|
+
Yields:
|
|
63
|
+
TaskInfo for each valid task.
|
|
64
|
+
"""
|
|
65
|
+
if not tasks_dir.is_dir():
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
for d in sorted(tasks_dir.iterdir()):
|
|
69
|
+
if not d.is_dir() or d.name == "archive":
|
|
70
|
+
continue
|
|
71
|
+
info = load_task(d)
|
|
72
|
+
if info is not None:
|
|
73
|
+
yield info
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_all_statuses(tasks_dir: Path) -> dict[str, str]:
|
|
77
|
+
"""Get a {dir_name: status} mapping for all active tasks.
|
|
78
|
+
|
|
79
|
+
Useful for computing children progress without loading full TaskInfo.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
tasks_dir: Path to the tasks directory.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dict mapping directory names to status strings.
|
|
86
|
+
"""
|
|
87
|
+
return {t.dir_name: t.status for t in iter_active_tasks(tasks_dir)}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def children_progress(
|
|
91
|
+
children: tuple[str, ...] | list[str],
|
|
92
|
+
all_statuses: dict[str, str],
|
|
93
|
+
) -> str:
|
|
94
|
+
"""Format children progress string like " [2/3 done]".
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
children: List of child directory names.
|
|
98
|
+
all_statuses: Status map from get_all_statuses().
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Formatted string, or "" if no children.
|
|
102
|
+
"""
|
|
103
|
+
if not children:
|
|
104
|
+
return ""
|
|
105
|
+
done = sum(
|
|
106
|
+
1 for c in children
|
|
107
|
+
if all_statuses.get(c) in ("completed", "done")
|
|
108
|
+
)
|
|
109
|
+
return f" [{done}/{len(children)} done]"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core type definitions for Trellis task data.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
TaskData — TypedDict for task.json shape (read-path type hints only)
|
|
6
|
+
TaskInfo — Frozen dataclass for loaded task (the public API type)
|
|
7
|
+
AgentRecord — TypedDict for registry.json agent entries
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TypedDict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# task.json shape (TypedDict — used only for read-path type hints)
|
|
19
|
+
# =============================================================================
|
|
20
|
+
|
|
21
|
+
class TaskData(TypedDict, total=False):
|
|
22
|
+
"""Shape of task.json on disk.
|
|
23
|
+
|
|
24
|
+
Used only for type annotations when reading task.json.
|
|
25
|
+
Writes must use the original dict to avoid losing unknown fields.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
id: str
|
|
29
|
+
name: str
|
|
30
|
+
title: str
|
|
31
|
+
description: str
|
|
32
|
+
status: str
|
|
33
|
+
dev_type: str
|
|
34
|
+
scope: str | None
|
|
35
|
+
package: str | None
|
|
36
|
+
priority: str
|
|
37
|
+
creator: str
|
|
38
|
+
assignee: str
|
|
39
|
+
createdAt: str
|
|
40
|
+
completedAt: str | None
|
|
41
|
+
branch: str | None
|
|
42
|
+
base_branch: str | None
|
|
43
|
+
worktree_path: str | None
|
|
44
|
+
current_phase: int
|
|
45
|
+
next_action: list[dict]
|
|
46
|
+
commit: str | None
|
|
47
|
+
pr_url: str | None
|
|
48
|
+
subtasks: list[str]
|
|
49
|
+
children: list[str]
|
|
50
|
+
parent: str | None
|
|
51
|
+
relatedFiles: list[str]
|
|
52
|
+
notes: str
|
|
53
|
+
meta: dict
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# =============================================================================
|
|
57
|
+
# Loaded task object (frozen dataclass — the public API type)
|
|
58
|
+
# =============================================================================
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class TaskInfo:
|
|
62
|
+
"""Immutable view of a loaded task.
|
|
63
|
+
|
|
64
|
+
Created by load_task() / iter_active_tasks().
|
|
65
|
+
Contains the commonly accessed fields; the original dict
|
|
66
|
+
is preserved in `raw` for write-back and uncommon field access.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
dir_name: str
|
|
70
|
+
directory: Path
|
|
71
|
+
title: str
|
|
72
|
+
status: str
|
|
73
|
+
assignee: str
|
|
74
|
+
priority: str
|
|
75
|
+
children: tuple[str, ...]
|
|
76
|
+
parent: str | None
|
|
77
|
+
package: str | None
|
|
78
|
+
raw: dict # original dict — use for writes and uncommon fields
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def name(self) -> str:
|
|
82
|
+
"""Task name (id or name field)."""
|
|
83
|
+
return self.raw.get("name") or self.raw.get("id") or self.dir_name
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def description(self) -> str:
|
|
87
|
+
return self.raw.get("description", "")
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def branch(self) -> str | None:
|
|
91
|
+
return self.raw.get("branch")
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def meta(self) -> dict:
|
|
95
|
+
return self.raw.get("meta", {})
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# =============================================================================
|
|
99
|
+
# registry.json agent entry
|
|
100
|
+
# =============================================================================
|
|
101
|
+
|
|
102
|
+
class AgentRecord(TypedDict, total=False):
|
|
103
|
+
"""Shape of an agent entry in registry.json."""
|
|
104
|
+
|
|
105
|
+
id: str
|
|
106
|
+
pid: int
|
|
107
|
+
task_dir: str
|
|
108
|
+
worktree_path: str
|
|
109
|
+
branch: str
|
|
110
|
+
platform: str
|
|
111
|
+
started_at: str
|
|
112
|
+
status: str
|
|
@@ -36,6 +36,7 @@ from common.paths import (
|
|
|
36
36
|
get_tasks_dir,
|
|
37
37
|
set_current_task,
|
|
38
38
|
)
|
|
39
|
+
from common.config import get_spec_base, resolve_package
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
# =============================================================================
|
|
@@ -58,7 +59,7 @@ def write_prd_header() -> str:
|
|
|
58
59
|
Welcome to Trellis! This is your first task.
|
|
59
60
|
|
|
60
61
|
AI agents use `.trellis/spec/` to understand YOUR project's coding conventions.
|
|
61
|
-
**
|
|
62
|
+
**Starting from scratch = AI writes generic code that doesn't match your project style.**
|
|
62
63
|
|
|
63
64
|
Filling these guidelines is a one-time setup that pays off for every future AI session.
|
|
64
65
|
|
|
@@ -70,36 +71,36 @@ Fill in the guideline files based on your **existing codebase**.
|
|
|
70
71
|
"""
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
def write_prd_backend_section() -> str:
|
|
74
|
+
def write_prd_backend_section(spec_base: str) -> str:
|
|
74
75
|
"""Write PRD backend section."""
|
|
75
|
-
return """
|
|
76
|
+
return f"""
|
|
76
77
|
|
|
77
78
|
### Backend Guidelines
|
|
78
79
|
|
|
79
80
|
| File | What to Document |
|
|
80
81
|
|------|------------------|
|
|
81
|
-
| `.trellis/
|
|
82
|
-
| `.trellis/
|
|
83
|
-
| `.trellis/
|
|
84
|
-
| `.trellis/
|
|
85
|
-
| `.trellis/
|
|
82
|
+
| `.trellis/{spec_base}/backend/directory-structure.md` | Where different file types go (routes, services, utils) |
|
|
83
|
+
| `.trellis/{spec_base}/backend/database-guidelines.md` | ORM, migrations, query patterns, naming conventions |
|
|
84
|
+
| `.trellis/{spec_base}/backend/error-handling.md` | How errors are caught, logged, and returned |
|
|
85
|
+
| `.trellis/{spec_base}/backend/logging-guidelines.md` | Log levels, format, what to log |
|
|
86
|
+
| `.trellis/{spec_base}/backend/quality-guidelines.md` | Code review standards, testing requirements |
|
|
86
87
|
"""
|
|
87
88
|
|
|
88
89
|
|
|
89
|
-
def write_prd_frontend_section() -> str:
|
|
90
|
+
def write_prd_frontend_section(spec_base: str) -> str:
|
|
90
91
|
"""Write PRD frontend section."""
|
|
91
|
-
return """
|
|
92
|
+
return f"""
|
|
92
93
|
|
|
93
94
|
### Frontend Guidelines
|
|
94
95
|
|
|
95
96
|
| File | What to Document |
|
|
96
97
|
|------|------------------|
|
|
97
|
-
| `.trellis/
|
|
98
|
-
| `.trellis/
|
|
99
|
-
| `.trellis/
|
|
100
|
-
| `.trellis/
|
|
101
|
-
| `.trellis/
|
|
102
|
-
| `.trellis/
|
|
98
|
+
| `.trellis/{spec_base}/frontend/directory-structure.md` | Component/page/hook organization |
|
|
99
|
+
| `.trellis/{spec_base}/frontend/component-guidelines.md` | Component patterns, props conventions |
|
|
100
|
+
| `.trellis/{spec_base}/frontend/hook-guidelines.md` | Custom hook naming, patterns |
|
|
101
|
+
| `.trellis/{spec_base}/frontend/state-management.md` | State library, patterns, what goes where |
|
|
102
|
+
| `.trellis/{spec_base}/frontend/type-safety.md` | TypeScript conventions, type organization |
|
|
103
|
+
| `.trellis/{spec_base}/frontend/quality-guidelines.md` | Linting, testing, accessibility |
|
|
103
104
|
"""
|
|
104
105
|
|
|
105
106
|
|
|
@@ -168,17 +169,17 @@ After completing this task:
|
|
|
168
169
|
"""
|
|
169
170
|
|
|
170
171
|
|
|
171
|
-
def write_prd(task_dir: Path, project_type: str) -> None:
|
|
172
|
+
def write_prd(task_dir: Path, project_type: str, spec_base: str) -> None:
|
|
172
173
|
"""Write prd.md file."""
|
|
173
174
|
content = write_prd_header()
|
|
174
175
|
|
|
175
176
|
if project_type == "frontend":
|
|
176
|
-
content += write_prd_frontend_section()
|
|
177
|
+
content += write_prd_frontend_section(spec_base)
|
|
177
178
|
elif project_type == "backend":
|
|
178
|
-
content += write_prd_backend_section()
|
|
179
|
+
content += write_prd_backend_section(spec_base)
|
|
179
180
|
else: # fullstack
|
|
180
|
-
content += write_prd_backend_section()
|
|
181
|
-
content += write_prd_frontend_section()
|
|
181
|
+
content += write_prd_backend_section(spec_base)
|
|
182
|
+
content += write_prd_frontend_section(spec_base)
|
|
182
183
|
|
|
183
184
|
content += write_prd_footer()
|
|
184
185
|
|
|
@@ -190,7 +191,7 @@ def write_prd(task_dir: Path, project_type: str) -> None:
|
|
|
190
191
|
# Task JSON
|
|
191
192
|
# =============================================================================
|
|
192
193
|
|
|
193
|
-
def write_task_json(task_dir: Path, developer: str, project_type: str) -> None:
|
|
194
|
+
def write_task_json(task_dir: Path, developer: str, project_type: str, spec_base: str) -> None:
|
|
194
195
|
"""Write task.json file."""
|
|
195
196
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
196
197
|
|
|
@@ -200,20 +201,20 @@ def write_task_json(task_dir: Path, developer: str, project_type: str) -> None:
|
|
|
200
201
|
{"name": "Fill frontend guidelines", "status": "pending"},
|
|
201
202
|
{"name": "Add code examples", "status": "pending"},
|
|
202
203
|
]
|
|
203
|
-
related_files = [".trellis/
|
|
204
|
+
related_files = [f".trellis/{spec_base}/frontend/"]
|
|
204
205
|
elif project_type == "backend":
|
|
205
206
|
subtasks = [
|
|
206
207
|
{"name": "Fill backend guidelines", "status": "pending"},
|
|
207
208
|
{"name": "Add code examples", "status": "pending"},
|
|
208
209
|
]
|
|
209
|
-
related_files = [".trellis/
|
|
210
|
+
related_files = [f".trellis/{spec_base}/backend/"]
|
|
210
211
|
else: # fullstack
|
|
211
212
|
subtasks = [
|
|
212
213
|
{"name": "Fill backend guidelines", "status": "pending"},
|
|
213
214
|
{"name": "Fill frontend guidelines", "status": "pending"},
|
|
214
215
|
{"name": "Add code examples", "status": "pending"},
|
|
215
216
|
]
|
|
216
|
-
related_files = [".trellis/
|
|
217
|
+
related_files = [f".trellis/{spec_base}/backend/", f".trellis/{spec_base}/frontend/"]
|
|
217
218
|
|
|
218
219
|
task_data = {
|
|
219
220
|
"id": TASK_NAME,
|
|
@@ -264,6 +265,10 @@ def main() -> int:
|
|
|
264
265
|
print(f"Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <your-name>")
|
|
265
266
|
return 1
|
|
266
267
|
|
|
268
|
+
# Resolve spec base path (monorepo: spec/<package>, single-repo: spec)
|
|
269
|
+
package = resolve_package(repo_root=repo_root)
|
|
270
|
+
spec_base = get_spec_base(package, repo_root)
|
|
271
|
+
|
|
267
272
|
tasks_dir = get_tasks_dir(repo_root)
|
|
268
273
|
task_dir = tasks_dir / TASK_NAME
|
|
269
274
|
relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{TASK_NAME}"
|
|
@@ -277,8 +282,8 @@ def main() -> int:
|
|
|
277
282
|
task_dir.mkdir(parents=True, exist_ok=True)
|
|
278
283
|
|
|
279
284
|
# Write files
|
|
280
|
-
write_task_json(task_dir, developer, project_type)
|
|
281
|
-
write_prd(task_dir, project_type)
|
|
285
|
+
write_task_json(task_dir, developer, project_type, spec_base)
|
|
286
|
+
write_prd(task_dir, project_type, spec_base)
|
|
282
287
|
|
|
283
288
|
# Set as current task
|
|
284
289
|
set_current_task(relative_path, repo_root)
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Linear sync hook for Trellis task lifecycle.
|
|
3
|
+
|
|
4
|
+
Syncs task events to Linear via the `linearis` CLI.
|
|
5
|
+
|
|
6
|
+
Usage (called automatically by task.py hooks):
|
|
7
|
+
python3 .trellis/scripts/hooks/linear_sync.py create
|
|
8
|
+
python3 .trellis/scripts/hooks/linear_sync.py start
|
|
9
|
+
python3 .trellis/scripts/hooks/linear_sync.py archive
|
|
10
|
+
|
|
11
|
+
Manual usage:
|
|
12
|
+
TASK_JSON_PATH=.trellis/tasks/<name>/task.json python3 .trellis/scripts/hooks/linear_sync.py sync
|
|
13
|
+
|
|
14
|
+
Environment:
|
|
15
|
+
TASK_JSON_PATH - Absolute path to task.json (set by task.py)
|
|
16
|
+
|
|
17
|
+
Configuration:
|
|
18
|
+
.trellis/hooks.local.json - Local config (gitignored), example:
|
|
19
|
+
{
|
|
20
|
+
"linear": {
|
|
21
|
+
"team": "TEAM_KEY",
|
|
22
|
+
"project": "Project Name",
|
|
23
|
+
"assignees": {
|
|
24
|
+
"dev-name": "linear-user-id"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
import subprocess
|
|
35
|
+
import sys
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
# ─── Configuration ────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
# Trellis priority → Linear priority (1=Urgent, 2=High, 3=Medium, 4=Low)
|
|
41
|
+
PRIORITY_MAP = {"P0": 1, "P1": 2, "P2": 3, "P3": 4}
|
|
42
|
+
|
|
43
|
+
# Linear status names (must match your team's workflow)
|
|
44
|
+
STATUS_IN_PROGRESS = "In Progress"
|
|
45
|
+
STATUS_DONE = "Done"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _load_config() -> dict:
|
|
49
|
+
"""Load local hook config from .trellis/hooks.local.json."""
|
|
50
|
+
task_json_path = os.environ.get("TASK_JSON_PATH", "")
|
|
51
|
+
if task_json_path:
|
|
52
|
+
# Walk up from task.json to find .trellis/
|
|
53
|
+
trellis_dir = Path(task_json_path).parent.parent.parent
|
|
54
|
+
else:
|
|
55
|
+
trellis_dir = Path(".trellis")
|
|
56
|
+
|
|
57
|
+
config_path = trellis_dir / "hooks.local.json"
|
|
58
|
+
try:
|
|
59
|
+
with open(config_path, encoding="utf-8") as f:
|
|
60
|
+
return json.load(f)
|
|
61
|
+
except (OSError, json.JSONDecodeError):
|
|
62
|
+
return {}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
CONFIG = _load_config()
|
|
66
|
+
LINEAR_CFG = CONFIG.get("linear", {})
|
|
67
|
+
|
|
68
|
+
TEAM = LINEAR_CFG.get("team", "")
|
|
69
|
+
PROJECT = LINEAR_CFG.get("project", "")
|
|
70
|
+
ASSIGNEE_MAP = LINEAR_CFG.get("assignees", {})
|
|
71
|
+
|
|
72
|
+
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _read_task() -> tuple[dict, str]:
|
|
76
|
+
path = os.environ.get("TASK_JSON_PATH", "")
|
|
77
|
+
if not path:
|
|
78
|
+
print("TASK_JSON_PATH not set", file=sys.stderr)
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
with open(path, encoding="utf-8") as f:
|
|
81
|
+
return json.load(f), path
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _write_task(data: dict, path: str) -> None:
|
|
85
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
86
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
87
|
+
f.write("\n")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _linearis(*args: str) -> dict | None:
|
|
91
|
+
result = subprocess.run(
|
|
92
|
+
["linearis", *args],
|
|
93
|
+
capture_output=True,
|
|
94
|
+
text=True,
|
|
95
|
+
encoding="utf-8",
|
|
96
|
+
errors="replace",
|
|
97
|
+
)
|
|
98
|
+
if result.returncode != 0:
|
|
99
|
+
print(f"linearis error: {result.stderr.strip()}", file=sys.stderr)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
stdout = result.stdout.strip()
|
|
102
|
+
if stdout:
|
|
103
|
+
return json.loads(stdout)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _get_linear_issue(task: dict) -> str | None:
|
|
108
|
+
meta = task.get("meta")
|
|
109
|
+
if isinstance(meta, dict):
|
|
110
|
+
return meta.get("linear_issue")
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ─── Actions ──────────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def cmd_create() -> None:
|
|
118
|
+
if not TEAM:
|
|
119
|
+
print("No linear.team configured in hooks.local.json", file=sys.stderr)
|
|
120
|
+
sys.exit(1)
|
|
121
|
+
|
|
122
|
+
task, path = _read_task()
|
|
123
|
+
|
|
124
|
+
# Skip if already linked
|
|
125
|
+
if _get_linear_issue(task):
|
|
126
|
+
print(f"Already linked: {_get_linear_issue(task)}")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
title = task.get("title") or task.get("name") or "Untitled"
|
|
130
|
+
args = ["issues", "create", title, "--team", TEAM]
|
|
131
|
+
|
|
132
|
+
# Map priority
|
|
133
|
+
priority = PRIORITY_MAP.get(task.get("priority", ""), 0)
|
|
134
|
+
if priority:
|
|
135
|
+
args.extend(["-p", str(priority)])
|
|
136
|
+
|
|
137
|
+
# Set project
|
|
138
|
+
if PROJECT:
|
|
139
|
+
args.extend(["--project", PROJECT])
|
|
140
|
+
|
|
141
|
+
# Assign to Linear user
|
|
142
|
+
assignee = task.get("assignee", "")
|
|
143
|
+
linear_user_id = ASSIGNEE_MAP.get(assignee)
|
|
144
|
+
if linear_user_id:
|
|
145
|
+
args.extend(["--assignee", linear_user_id])
|
|
146
|
+
|
|
147
|
+
# Link to parent's Linear issue if available
|
|
148
|
+
parent_issue = _resolve_parent_linear_issue(task)
|
|
149
|
+
if parent_issue:
|
|
150
|
+
args.extend(["--parent-ticket", parent_issue])
|
|
151
|
+
|
|
152
|
+
result = _linearis(*args)
|
|
153
|
+
if result and "identifier" in result:
|
|
154
|
+
if not isinstance(task.get("meta"), dict):
|
|
155
|
+
task["meta"] = {}
|
|
156
|
+
task["meta"]["linear_issue"] = result["identifier"]
|
|
157
|
+
_write_task(task, path)
|
|
158
|
+
print(f"Created Linear issue: {result['identifier']}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def cmd_start() -> None:
|
|
162
|
+
task, _ = _read_task()
|
|
163
|
+
issue = _get_linear_issue(task)
|
|
164
|
+
if not issue:
|
|
165
|
+
return
|
|
166
|
+
_linearis("issues", "update", issue, "-s", STATUS_IN_PROGRESS)
|
|
167
|
+
print(f"Updated {issue} -> {STATUS_IN_PROGRESS}")
|
|
168
|
+
cmd_sync()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def cmd_archive() -> None:
|
|
172
|
+
task, _ = _read_task()
|
|
173
|
+
issue = _get_linear_issue(task)
|
|
174
|
+
if not issue:
|
|
175
|
+
return
|
|
176
|
+
_linearis("issues", "update", issue, "-s", STATUS_DONE)
|
|
177
|
+
print(f"Updated {issue} -> {STATUS_DONE}")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def cmd_sync() -> None:
|
|
181
|
+
"""Sync prd.md content to Linear issue description."""
|
|
182
|
+
task, _ = _read_task()
|
|
183
|
+
issue = _get_linear_issue(task)
|
|
184
|
+
if not issue:
|
|
185
|
+
print("No linear_issue in meta, run create first", file=sys.stderr)
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
|
|
188
|
+
# Find prd.md next to task.json
|
|
189
|
+
task_json_path = os.environ.get("TASK_JSON_PATH", "")
|
|
190
|
+
prd_path = Path(task_json_path).parent / "prd.md"
|
|
191
|
+
if not prd_path.is_file():
|
|
192
|
+
print(f"No prd.md found at {prd_path}", file=sys.stderr)
|
|
193
|
+
sys.exit(1)
|
|
194
|
+
|
|
195
|
+
description = prd_path.read_text(encoding="utf-8").strip()
|
|
196
|
+
_linearis("issues", "update", issue, "-d", description)
|
|
197
|
+
print(f"Synced prd.md to {issue} description")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ─── Parent Issue Resolution ─────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _resolve_parent_linear_issue(task: dict) -> str | None:
|
|
204
|
+
"""Find parent task's Linear issue identifier."""
|
|
205
|
+
parent_name = task.get("parent")
|
|
206
|
+
if not parent_name:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
task_json_path = os.environ.get("TASK_JSON_PATH", "")
|
|
210
|
+
if not task_json_path:
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
current_task_dir = Path(task_json_path).parent
|
|
214
|
+
tasks_dir = current_task_dir.parent
|
|
215
|
+
parent_json = tasks_dir / parent_name / "task.json"
|
|
216
|
+
|
|
217
|
+
if parent_json.exists():
|
|
218
|
+
try:
|
|
219
|
+
with open(parent_json, encoding="utf-8") as f:
|
|
220
|
+
parent_task = json.load(f)
|
|
221
|
+
return _get_linear_issue(parent_task)
|
|
222
|
+
except (json.JSONDecodeError, OSError):
|
|
223
|
+
pass
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ─── Main ─────────────────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
if __name__ == "__main__":
|
|
230
|
+
action = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
231
|
+
actions = {
|
|
232
|
+
"create": cmd_create,
|
|
233
|
+
"start": cmd_start,
|
|
234
|
+
"archive": cmd_archive,
|
|
235
|
+
"sync": cmd_sync,
|
|
236
|
+
}
|
|
237
|
+
fn = actions.get(action)
|
|
238
|
+
if fn:
|
|
239
|
+
fn()
|
|
240
|
+
else:
|
|
241
|
+
print(f"Unknown action: {action}", file=sys.stderr)
|
|
242
|
+
print(f"Valid actions: {', '.join(actions)}", file=sys.stderr)
|
|
243
|
+
sys.exit(1)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Bootstrap path setup for multi_agent scripts.
|
|
2
|
+
|
|
3
|
+
Import this module before importing from common/:
|
|
4
|
+
|
|
5
|
+
import _bootstrap # noqa: F401
|
|
6
|
+
|
|
7
|
+
This adds the parent scripts/ directory to sys.path so that
|
|
8
|
+
`from common.xxx import yyy` works when running scripts directly
|
|
9
|
+
via `python3 .trellis/scripts/multi_agent/some_script.py`.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
_scripts_dir = str(Path(__file__).resolve().parent.parent)
|
|
16
|
+
if _scripts_dir not in sys.path:
|
|
17
|
+
sys.path.insert(0, _scripts_dir)
|