@mindfoldhq/trellis 0.6.0-beta.2 → 0.6.0-beta.21
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 +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +58 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/channel/adapters/claude.d.ts +29 -0
- package/dist/commands/channel/adapters/claude.d.ts.map +1 -0
- package/dist/commands/channel/adapters/claude.js +203 -0
- package/dist/commands/channel/adapters/claude.js.map +1 -0
- package/dist/commands/channel/adapters/codex.d.ts +85 -0
- package/dist/commands/channel/adapters/codex.d.ts.map +1 -0
- package/dist/commands/channel/adapters/codex.js +505 -0
- package/dist/commands/channel/adapters/codex.js.map +1 -0
- package/dist/commands/channel/adapters/index.d.ts +84 -0
- package/dist/commands/channel/adapters/index.d.ts.map +1 -0
- package/dist/commands/channel/adapters/index.js +115 -0
- package/dist/commands/channel/adapters/index.js.map +1 -0
- package/dist/commands/channel/adapters/types.d.ts +33 -0
- package/dist/commands/channel/adapters/types.d.ts.map +1 -0
- package/dist/commands/channel/adapters/types.js +2 -0
- package/dist/commands/channel/adapters/types.js.map +1 -0
- package/dist/commands/channel/agent-loader.d.ts +32 -0
- package/dist/commands/channel/agent-loader.d.ts.map +1 -0
- package/dist/commands/channel/agent-loader.js +154 -0
- package/dist/commands/channel/agent-loader.js.map +1 -0
- package/dist/commands/channel/context-loader.d.ts +26 -0
- package/dist/commands/channel/context-loader.d.ts.map +1 -0
- package/dist/commands/channel/context-loader.js +290 -0
- package/dist/commands/channel/context-loader.js.map +1 -0
- package/dist/commands/channel/context.d.ts +16 -0
- package/dist/commands/channel/context.d.ts.map +1 -0
- package/dist/commands/channel/context.js +83 -0
- package/dist/commands/channel/context.js.map +1 -0
- package/dist/commands/channel/create.d.ts +27 -0
- package/dist/commands/channel/create.d.ts.map +1 -0
- package/dist/commands/channel/create.js +39 -0
- package/dist/commands/channel/create.js.map +1 -0
- package/dist/commands/channel/dev-parse-trace.d.ts +14 -0
- package/dist/commands/channel/dev-parse-trace.d.ts.map +1 -0
- package/dist/commands/channel/dev-parse-trace.js +70 -0
- package/dist/commands/channel/dev-parse-trace.js.map +1 -0
- package/dist/commands/channel/guard.d.ts +150 -0
- package/dist/commands/channel/guard.d.ts.map +1 -0
- package/dist/commands/channel/guard.js +474 -0
- package/dist/commands/channel/guard.js.map +1 -0
- package/dist/commands/channel/index.d.ts +3 -0
- package/dist/commands/channel/index.d.ts.map +1 -0
- package/dist/commands/channel/index.js +531 -0
- package/dist/commands/channel/index.js.map +1 -0
- package/dist/commands/channel/interrupt.d.ts +10 -0
- package/dist/commands/channel/interrupt.d.ts.map +1 -0
- package/dist/commands/channel/interrupt.js +22 -0
- package/dist/commands/channel/interrupt.js.map +1 -0
- package/dist/commands/channel/kill.d.ts +7 -0
- package/dist/commands/channel/kill.d.ts.map +1 -0
- package/dist/commands/channel/kill.js +121 -0
- package/dist/commands/channel/kill.js.map +1 -0
- package/dist/commands/channel/list.d.ts +17 -0
- package/dist/commands/channel/list.d.ts.map +1 -0
- package/dist/commands/channel/list.js +233 -0
- package/dist/commands/channel/list.js.map +1 -0
- package/dist/commands/channel/messages.d.ts +15 -0
- package/dist/commands/channel/messages.d.ts.map +1 -0
- package/dist/commands/channel/messages.js +245 -0
- package/dist/commands/channel/messages.js.map +1 -0
- package/dist/commands/channel/rm.d.ts +27 -0
- package/dist/commands/channel/rm.d.ts.map +1 -0
- package/dist/commands/channel/rm.js +216 -0
- package/dist/commands/channel/rm.js.map +1 -0
- package/dist/commands/channel/run.d.ts +30 -0
- package/dist/commands/channel/run.d.ts.map +1 -0
- package/dist/commands/channel/run.js +130 -0
- package/dist/commands/channel/run.js.map +1 -0
- package/dist/commands/channel/send.d.ts +11 -0
- package/dist/commands/channel/send.d.ts.map +1 -0
- package/dist/commands/channel/send.js +24 -0
- package/dist/commands/channel/send.js.map +1 -0
- package/dist/commands/channel/spawn.d.ts +40 -0
- package/dist/commands/channel/spawn.d.ts.map +1 -0
- package/dist/commands/channel/spawn.js +244 -0
- package/dist/commands/channel/spawn.js.map +1 -0
- package/dist/commands/channel/store/events.d.ts +39 -0
- package/dist/commands/channel/store/events.d.ts.map +1 -0
- package/dist/commands/channel/store/events.js +87 -0
- package/dist/commands/channel/store/events.js.map +1 -0
- package/dist/commands/channel/store/filter.d.ts +3 -0
- package/dist/commands/channel/store/filter.d.ts.map +1 -0
- package/dist/commands/channel/store/filter.js +2 -0
- package/dist/commands/channel/store/filter.js.map +1 -0
- package/dist/commands/channel/store/lock.d.ts +23 -0
- package/dist/commands/channel/store/lock.d.ts.map +1 -0
- package/dist/commands/channel/store/lock.js +99 -0
- package/dist/commands/channel/store/lock.js.map +1 -0
- package/dist/commands/channel/store/paths.d.ts +63 -0
- package/dist/commands/channel/store/paths.d.ts.map +1 -0
- package/dist/commands/channel/store/paths.js +246 -0
- package/dist/commands/channel/store/paths.js.map +1 -0
- package/dist/commands/channel/store/schema.d.ts +27 -0
- package/dist/commands/channel/store/schema.d.ts.map +1 -0
- package/dist/commands/channel/store/schema.js +34 -0
- package/dist/commands/channel/store/schema.js.map +1 -0
- package/dist/commands/channel/store/thread-state.d.ts +5 -0
- package/dist/commands/channel/store/thread-state.d.ts.map +1 -0
- package/dist/commands/channel/store/thread-state.js +16 -0
- package/dist/commands/channel/store/thread-state.js.map +1 -0
- package/dist/commands/channel/store/watch.d.ts +19 -0
- package/dist/commands/channel/store/watch.d.ts.map +1 -0
- package/dist/commands/channel/store/watch.js +146 -0
- package/dist/commands/channel/store/watch.js.map +1 -0
- package/dist/commands/channel/supervisor/idle.d.ts +46 -0
- package/dist/commands/channel/supervisor/idle.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/idle.js +72 -0
- package/dist/commands/channel/supervisor/idle.js.map +1 -0
- package/dist/commands/channel/supervisor/inbox.d.ts +30 -0
- package/dist/commands/channel/supervisor/inbox.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/inbox.js +160 -0
- package/dist/commands/channel/supervisor/inbox.js.map +1 -0
- package/dist/commands/channel/supervisor/shutdown.d.ts +68 -0
- package/dist/commands/channel/supervisor/shutdown.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/shutdown.js +146 -0
- package/dist/commands/channel/supervisor/shutdown.js.map +1 -0
- package/dist/commands/channel/supervisor/stdout.d.ts +51 -0
- package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/stdout.js +121 -0
- package/dist/commands/channel/supervisor/stdout.js.map +1 -0
- package/dist/commands/channel/supervisor/turns.d.ts +31 -0
- package/dist/commands/channel/supervisor/turns.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/turns.js +45 -0
- package/dist/commands/channel/supervisor/turns.js.map +1 -0
- package/dist/commands/channel/supervisor/warning.d.ts +48 -0
- package/dist/commands/channel/supervisor/warning.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/warning.js +77 -0
- package/dist/commands/channel/supervisor/warning.js.map +1 -0
- package/dist/commands/channel/supervisor.d.ts +59 -0
- package/dist/commands/channel/supervisor.d.ts.map +1 -0
- package/dist/commands/channel/supervisor.js +344 -0
- package/dist/commands/channel/supervisor.js.map +1 -0
- package/dist/commands/channel/text-body.d.ts +13 -0
- package/dist/commands/channel/text-body.d.ts.map +1 -0
- package/dist/commands/channel/text-body.js +47 -0
- package/dist/commands/channel/text-body.js.map +1 -0
- package/dist/commands/channel/threads.d.ts +39 -0
- package/dist/commands/channel/threads.d.ts.map +1 -0
- package/dist/commands/channel/threads.js +106 -0
- package/dist/commands/channel/threads.js.map +1 -0
- package/dist/commands/channel/title.d.ts +12 -0
- package/dist/commands/channel/title.d.ts.map +1 -0
- package/dist/commands/channel/title.js +24 -0
- package/dist/commands/channel/title.js.map +1 -0
- package/dist/commands/channel/wait.d.ts +17 -0
- package/dist/commands/channel/wait.d.ts.map +1 -0
- package/dist/commands/channel/wait.js +75 -0
- package/dist/commands/channel/wait.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +97 -42
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mem.d.ts +13 -117
- package/dist/commands/mem.d.ts.map +1 -1
- package/dist/commands/mem.js +168 -1074
- package/dist/commands/mem.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +28 -2
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +31 -111
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/upgrade.d.ts +28 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +84 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/workflow.d.ts +35 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +219 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js +1 -0
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +5 -3
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/shared.js +4 -4
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/workflow.d.ts +8 -0
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +3 -2
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/migrations/manifests/0.5.10.json +9 -0
- package/dist/migrations/manifests/0.5.11.json +16 -0
- package/dist/migrations/manifests/0.5.12.json +9 -0
- package/dist/migrations/manifests/0.5.13.json +9 -0
- package/dist/migrations/manifests/0.5.14.json +9 -0
- package/dist/migrations/manifests/0.5.15.json +9 -0
- package/dist/migrations/manifests/0.5.16.json +9 -0
- package/dist/migrations/manifests/0.5.17.json +9 -0
- package/dist/migrations/manifests/0.5.18.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.15.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.17.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.18.json +16 -0
- package/dist/migrations/manifests/0.6.0-beta.19.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.20.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.21.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.3.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.6.json +16 -0
- package/dist/migrations/manifests/0.6.0-beta.7.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.8.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.9.json +9 -0
- package/dist/templates/claude/agents/trellis-check.md +13 -7
- package/dist/templates/claude/agents/trellis-implement.md +8 -7
- package/dist/templates/claude/settings.json +4 -4
- package/dist/templates/codebuddy/agents/trellis-check.md +13 -7
- package/dist/templates/codebuddy/agents/trellis-implement.md +8 -7
- package/dist/templates/codebuddy/settings.json +4 -4
- package/dist/templates/codex/agents/trellis-check.toml +4 -4
- package/dist/templates/codex/agents/trellis-implement.toml +4 -4
- package/dist/templates/codex/config.toml +9 -16
- package/dist/templates/codex/hooks/session-start.py +205 -119
- package/dist/templates/codex/hooks.json +2 -2
- package/dist/templates/codex/skills/before-dev/SKILL.md +12 -6
- package/dist/templates/codex/skills/brainstorm/SKILL.md +69 -457
- package/dist/templates/codex/skills/check/SKILL.md +86 -18
- package/dist/templates/codex/skills/start/SKILL.md +33 -323
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +7 -4
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +3 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +5 -5
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +35 -6
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +5 -4
- package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/SKILL.md +41 -0
- package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/references/mcp-setup.md +90 -0
- package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/references/repository-analysis.md +59 -0
- package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/references/spec-task-planning.md +61 -0
- package/dist/templates/common/bundled-skills/trellis-spec-bootstarp/references/spec-writing.md +70 -0
- package/dist/templates/common/commands/continue.md +6 -5
- package/dist/templates/common/commands/start.md +9 -6
- package/dist/templates/common/skills/before-dev.md +12 -6
- package/dist/templates/common/skills/brainstorm.md +68 -504
- package/dist/templates/common/skills/check.md +7 -1
- package/dist/templates/copilot/hooks/session-start.py +219 -101
- package/dist/templates/copilot/hooks.json +2 -2
- package/dist/templates/copilot/prompts/before-dev.prompt.md +12 -6
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +69 -457
- package/dist/templates/copilot/prompts/check.prompt.md +86 -18
- package/dist/templates/copilot/prompts/parallel.prompt.md +16 -8
- package/dist/templates/copilot/prompts/start.prompt.md +33 -367
- package/dist/templates/cursor/agents/trellis-check.md +13 -7
- package/dist/templates/cursor/agents/trellis-implement.md +8 -7
- package/dist/templates/cursor/hooks.json +1 -7
- package/dist/templates/droid/droids/trellis-check.md +13 -7
- package/dist/templates/droid/droids/trellis-implement.md +8 -7
- package/dist/templates/droid/settings.json +4 -4
- package/dist/templates/gemini/agents/trellis-check.md +11 -5
- package/dist/templates/gemini/agents/trellis-implement.md +7 -6
- package/dist/templates/gemini/settings.json +2 -2
- package/dist/templates/kiro/agents/trellis-check.json +1 -1
- package/dist/templates/kiro/agents/trellis-implement.json +1 -1
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +127 -9
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +171 -6
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +333 -43
- package/dist/templates/markdown/spec/guides/index.md.txt +18 -0
- package/dist/templates/opencode/agents/trellis-check.md +13 -7
- package/dist/templates/opencode/agents/trellis-implement.md +9 -8
- package/dist/templates/opencode/lib/session-utils.js +212 -123
- package/dist/templates/opencode/lib/trellis-context.js +73 -11
- package/dist/templates/opencode/plugins/inject-subagent-context.js +131 -29
- package/dist/templates/opencode/plugins/inject-workflow-state.js +9 -5
- package/dist/templates/opencode/plugins/session-start.js +9 -1
- package/dist/templates/pi/agents/trellis-check.md +5 -4
- package/dist/templates/pi/agents/trellis-implement.md +5 -4
- package/dist/templates/pi/extensions/trellis/index.ts.txt +1357 -754
- package/dist/templates/qoder/agents/trellis-check.md +11 -5
- package/dist/templates/qoder/agents/trellis-implement.md +7 -6
- package/dist/templates/qoder/settings.json +4 -4
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +0 -1
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-subagent-context.py +36 -14
- package/dist/templates/shared-hooks/inject-workflow-state.py +40 -42
- package/dist/templates/shared-hooks/session-start.py +222 -171
- package/dist/templates/trellis/config.yaml +38 -0
- package/dist/templates/trellis/index.d.ts +1 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +50 -24
- package/dist/templates/trellis/scripts/common/config.py +57 -1
- package/dist/templates/trellis/scripts/common/safe_commit.py +285 -0
- package/dist/templates/trellis/scripts/common/session_context.py +384 -137
- package/dist/templates/trellis/scripts/common/task_context.py +3 -3
- package/dist/templates/trellis/scripts/common/task_store.py +161 -15
- package/dist/templates/trellis/scripts/common/workflow_phase.py +7 -10
- package/dist/templates/trellis/scripts/task.py +3 -3
- package/dist/templates/trellis/workflow.md +119 -98
- package/dist/utils/cwd-guard.d.ts +38 -0
- package/dist/utils/cwd-guard.d.ts.map +1 -0
- package/dist/utils/cwd-guard.js +62 -0
- package/dist/utils/cwd-guard.js.map +1 -0
- package/dist/utils/file-writer.d.ts +13 -0
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +59 -1
- package/dist/utils/file-writer.js.map +1 -1
- package/dist/utils/manifest-prune.d.ts +61 -0
- package/dist/utils/manifest-prune.d.ts.map +1 -0
- package/dist/utils/manifest-prune.js +136 -0
- package/dist/utils/manifest-prune.js.map +1 -0
- package/dist/utils/task-json.d.ts +9 -42
- package/dist/utils/task-json.d.ts.map +1 -1
- package/dist/utils/task-json.js +8 -45
- package/dist/utils/task-json.js.map +1 -1
- package/dist/utils/template-hash.d.ts +32 -6
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +53 -31
- package/dist/utils/template-hash.js.map +1 -1
- package/dist/utils/uninstall-scrubbers.d.ts +1 -0
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
- package/dist/utils/uninstall-scrubbers.js +21 -0
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/dist/utils/workflow-resolver.d.ts +86 -0
- package/dist/utils/workflow-resolver.d.ts.map +1 -0
- package/dist/utils/workflow-resolver.js +265 -0
- package/dist/utils/workflow-resolver.js.map +1 -0
- package/package.json +9 -8
|
@@ -14,8 +14,12 @@ Provides:
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import json
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
17
20
|
from pathlib import Path
|
|
18
21
|
|
|
22
|
+
from .active_task import resolve_context_key
|
|
19
23
|
from .config import get_git_packages
|
|
20
24
|
from .git import run_git
|
|
21
25
|
from .packages_context import get_packages_section
|
|
@@ -40,10 +44,146 @@ from .paths import (
|
|
|
40
44
|
# Helpers
|
|
41
45
|
# =============================================================================
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
_PACKAGE_NAME = "@mindfoldhq/trellis"
|
|
48
|
+
_UPDATE_CHECK_TIMEOUT_SECONDS = 1.0
|
|
49
|
+
_VERSION_RE = re.compile(
|
|
50
|
+
r"^\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?\s*$"
|
|
51
|
+
)
|
|
52
|
+
_VERSION_TOKEN_RE = re.compile(r"\b\d+(?:\.\d+){1,2}(?:-[0-9A-Za-z.-]+)?\b")
|
|
53
|
+
_POLYREPO_IGNORED_DIRS = {
|
|
54
|
+
"node_modules",
|
|
55
|
+
"target",
|
|
56
|
+
"dist",
|
|
57
|
+
"build",
|
|
58
|
+
"out",
|
|
59
|
+
"bin",
|
|
60
|
+
"obj",
|
|
61
|
+
"vendor",
|
|
62
|
+
"coverage",
|
|
63
|
+
"tmp",
|
|
64
|
+
"__pycache__",
|
|
65
|
+
}
|
|
66
|
+
_POLYREPO_SCAN_MAX_DEPTH = 2
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _is_git_worktree(path: Path) -> bool:
|
|
70
|
+
"""Return True when path is inside a Git worktree."""
|
|
71
|
+
rc, out, _ = run_git(["rev-parse", "--is-inside-work-tree"], cwd=path)
|
|
72
|
+
return rc == 0 and out.strip().lower() == "true"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _parse_recent_commits(log_output: str) -> list[dict]:
|
|
76
|
+
"""Parse `git log --oneline` output into structured commit entries."""
|
|
77
|
+
commits = []
|
|
78
|
+
for line in log_output.splitlines():
|
|
79
|
+
if not line.strip():
|
|
80
|
+
continue
|
|
81
|
+
parts = line.split(" ", 1)
|
|
82
|
+
if len(parts) >= 2:
|
|
83
|
+
commits.append({"hash": parts[0], "message": parts[1]})
|
|
84
|
+
elif len(parts) == 1:
|
|
85
|
+
commits.append({"hash": parts[0], "message": ""})
|
|
86
|
+
return commits
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _collect_git_repo_info(name: str, rel_path: str, repo_dir: Path) -> dict | None:
|
|
90
|
+
"""Collect Git status for one known repository directory."""
|
|
91
|
+
if not (repo_dir / ".git").exists():
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_dir)
|
|
95
|
+
branch = branch_out.strip() or "unknown"
|
|
96
|
+
|
|
97
|
+
_, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_dir)
|
|
98
|
+
changes = len([l for l in status_out.splitlines() if l.strip()])
|
|
99
|
+
|
|
100
|
+
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_dir)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"name": name,
|
|
104
|
+
"path": rel_path,
|
|
105
|
+
"branch": branch,
|
|
106
|
+
"isClean": changes == 0,
|
|
107
|
+
"uncommittedChanges": changes,
|
|
108
|
+
"recentCommits": _parse_recent_commits(log_out),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _collect_root_git_info(repo_root: Path) -> dict:
|
|
113
|
+
"""Collect root Git info without pretending a non-Git root is clean."""
|
|
114
|
+
if not _is_git_worktree(repo_root):
|
|
115
|
+
return {
|
|
116
|
+
"isRepo": False,
|
|
117
|
+
"branch": "",
|
|
118
|
+
"isClean": False,
|
|
119
|
+
"uncommittedChanges": 0,
|
|
120
|
+
"recentCommits": [],
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
124
|
+
branch = branch_out.strip() or "unknown"
|
|
125
|
+
|
|
126
|
+
_, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
|
|
127
|
+
status_lines = [line for line in status_out.splitlines() if line.strip()]
|
|
128
|
+
|
|
129
|
+
_, short_out, _ = run_git(["status", "--short"], cwd=repo_root)
|
|
130
|
+
|
|
131
|
+
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
"isRepo": True,
|
|
135
|
+
"branch": branch,
|
|
136
|
+
"isClean": len(status_lines) == 0,
|
|
137
|
+
"uncommittedChanges": len(status_lines),
|
|
138
|
+
"statusShort": short_out.splitlines(),
|
|
139
|
+
"recentCommits": _parse_recent_commits(log_out),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _discover_child_git_repos(repo_root: Path) -> list[tuple[str, str]]:
|
|
144
|
+
"""Discover child Git repositories using the init-time polyrepo heuristic."""
|
|
145
|
+
found: list[str] = []
|
|
146
|
+
|
|
147
|
+
def is_candidate_dir(path: Path) -> bool:
|
|
148
|
+
name = path.name
|
|
149
|
+
return not name.startswith(".") and name not in _POLYREPO_IGNORED_DIRS
|
|
45
150
|
|
|
46
|
-
|
|
151
|
+
def scan(rel_dir: Path, depth: int) -> None:
|
|
152
|
+
if depth >= _POLYREPO_SCAN_MAX_DEPTH:
|
|
153
|
+
return
|
|
154
|
+
abs_dir = repo_root / rel_dir
|
|
155
|
+
try:
|
|
156
|
+
children = sorted(abs_dir.iterdir(), key=lambda p: p.name)
|
|
157
|
+
except OSError:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
for child in children:
|
|
161
|
+
if not child.is_dir() or not is_candidate_dir(child):
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
child_rel = (
|
|
165
|
+
rel_dir / child.name if rel_dir != Path(".") else Path(child.name)
|
|
166
|
+
)
|
|
167
|
+
if (child / ".git").exists():
|
|
168
|
+
found.append(child_rel.as_posix())
|
|
169
|
+
continue
|
|
170
|
+
scan(child_rel, depth + 1)
|
|
171
|
+
|
|
172
|
+
scan(Path("."), 0)
|
|
173
|
+
if len(found) < 2:
|
|
174
|
+
return []
|
|
175
|
+
return [(path.replace("/", "_"), path) for path in sorted(found)]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _collect_package_git_info(
|
|
179
|
+
repo_root: Path,
|
|
180
|
+
discover_unconfigured: bool = False,
|
|
181
|
+
) -> list[dict]:
|
|
182
|
+
"""Collect Git status for independent package repositories.
|
|
183
|
+
|
|
184
|
+
Packages marked with ``git: true`` in config.yaml are authoritative.
|
|
185
|
+
When the Trellis root is not a Git repo and no configured package repos are
|
|
186
|
+
available, optionally fall back to the bounded polyrepo child scan.
|
|
47
187
|
|
|
48
188
|
Returns:
|
|
49
189
|
List of dicts with keys: name, path, branch, isClean,
|
|
@@ -51,41 +191,56 @@ def _collect_package_git_info(repo_root: Path) -> list[dict]:
|
|
|
51
191
|
Empty list if no git-repo packages are configured.
|
|
52
192
|
"""
|
|
53
193
|
git_pkgs = get_git_packages(repo_root)
|
|
54
|
-
if not git_pkgs:
|
|
55
|
-
return []
|
|
56
|
-
|
|
57
194
|
result = []
|
|
58
195
|
for pkg_name, pkg_path in git_pkgs.items():
|
|
59
196
|
pkg_dir = repo_root / pkg_path
|
|
60
|
-
|
|
61
|
-
|
|
197
|
+
info = _collect_git_repo_info(pkg_name, pkg_path, pkg_dir)
|
|
198
|
+
if info is not None:
|
|
199
|
+
result.append(info)
|
|
62
200
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
_, status_out, _ = run_git(["status", "--porcelain"], cwd=pkg_dir)
|
|
67
|
-
changes = len([l for l in status_out.splitlines() if l.strip()])
|
|
68
|
-
|
|
69
|
-
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=pkg_dir)
|
|
70
|
-
commits = []
|
|
71
|
-
for line in log_out.splitlines():
|
|
72
|
-
if line.strip():
|
|
73
|
-
parts = line.split(" ", 1)
|
|
74
|
-
if len(parts) >= 2:
|
|
75
|
-
commits.append({"hash": parts[0], "message": parts[1]})
|
|
76
|
-
elif len(parts) == 1:
|
|
77
|
-
commits.append({"hash": parts[0], "message": ""})
|
|
78
|
-
|
|
79
|
-
result.append({
|
|
80
|
-
"name": pkg_name,
|
|
81
|
-
"path": pkg_path,
|
|
82
|
-
"branch": branch,
|
|
83
|
-
"isClean": changes == 0,
|
|
84
|
-
"uncommittedChanges": changes,
|
|
85
|
-
"recentCommits": commits,
|
|
86
|
-
})
|
|
201
|
+
if result or not discover_unconfigured:
|
|
202
|
+
return result
|
|
87
203
|
|
|
88
|
-
|
|
204
|
+
discovered = []
|
|
205
|
+
for pkg_name, pkg_path in _discover_child_git_repos(repo_root):
|
|
206
|
+
info = _collect_git_repo_info(pkg_name, pkg_path, repo_root / pkg_path)
|
|
207
|
+
if info is not None:
|
|
208
|
+
discovered.append(info)
|
|
209
|
+
return discovered
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _append_root_git_context(lines: list[str], root_git_info: dict) -> None:
|
|
213
|
+
"""Append root Git status without misleading non-Git roots."""
|
|
214
|
+
lines.append("## GIT STATUS")
|
|
215
|
+
if not root_git_info["isRepo"]:
|
|
216
|
+
lines.append("Root is not a Git repository.")
|
|
217
|
+
lines.append("Run Git commands from the package repository paths listed below.")
|
|
218
|
+
else:
|
|
219
|
+
lines.append(f"Branch: {root_git_info['branch']}")
|
|
220
|
+
if root_git_info["isClean"]:
|
|
221
|
+
lines.append("Working directory: Clean")
|
|
222
|
+
else:
|
|
223
|
+
lines.append(
|
|
224
|
+
f"Working directory: {root_git_info['uncommittedChanges']} "
|
|
225
|
+
"uncommitted change(s)"
|
|
226
|
+
)
|
|
227
|
+
lines.append("")
|
|
228
|
+
lines.append("Changes:")
|
|
229
|
+
for line in root_git_info.get("statusShort", [])[:10]:
|
|
230
|
+
lines.append(line)
|
|
231
|
+
lines.append("")
|
|
232
|
+
|
|
233
|
+
lines.append("## RECENT COMMITS")
|
|
234
|
+
if not root_git_info["isRepo"]:
|
|
235
|
+
lines.append(
|
|
236
|
+
"Root has no Git commit history because it is not a Git repository."
|
|
237
|
+
)
|
|
238
|
+
elif root_git_info["recentCommits"]:
|
|
239
|
+
for commit in root_git_info["recentCommits"]:
|
|
240
|
+
lines.append(f"{commit['hash']} {commit['message']}")
|
|
241
|
+
else:
|
|
242
|
+
lines.append("(no commits)")
|
|
243
|
+
lines.append("")
|
|
89
244
|
|
|
90
245
|
|
|
91
246
|
def _append_package_git_context(lines: list[str], package_git_info: list[dict]) -> None:
|
|
@@ -109,6 +264,158 @@ def _append_package_git_context(lines: list[str], package_git_info: list[dict])
|
|
|
109
264
|
lines.append("")
|
|
110
265
|
|
|
111
266
|
|
|
267
|
+
def _read_project_version(repo_root: Path) -> str | None:
|
|
268
|
+
try:
|
|
269
|
+
version = (repo_root / DIR_WORKFLOW / ".version").read_text(
|
|
270
|
+
encoding="utf-8"
|
|
271
|
+
).strip()
|
|
272
|
+
except OSError:
|
|
273
|
+
return None
|
|
274
|
+
return version or None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _fetch_trellis_version_output() -> str | None:
|
|
278
|
+
try:
|
|
279
|
+
result = subprocess.run(
|
|
280
|
+
["trellis", "--version"],
|
|
281
|
+
capture_output=True,
|
|
282
|
+
text=True,
|
|
283
|
+
encoding="utf-8",
|
|
284
|
+
errors="replace",
|
|
285
|
+
timeout=_UPDATE_CHECK_TIMEOUT_SECONDS,
|
|
286
|
+
)
|
|
287
|
+
except (OSError, subprocess.SubprocessError, TimeoutError):
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
if result.returncode != 0:
|
|
291
|
+
return None
|
|
292
|
+
output = f"{result.stdout}\n{result.stderr}".strip()
|
|
293
|
+
return output or None
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _extract_available_update_version(output: str) -> str | None:
|
|
297
|
+
update_match = re.search(
|
|
298
|
+
r"Trellis update available:\s*"
|
|
299
|
+
r"(?P<current>\S+)\s*(?:→|->)\s*(?P<latest>\S+)",
|
|
300
|
+
output,
|
|
301
|
+
)
|
|
302
|
+
if update_match:
|
|
303
|
+
return update_match.group("latest").strip()
|
|
304
|
+
candidates = _VERSION_TOKEN_RE.findall(output)
|
|
305
|
+
return candidates[-1] if candidates else None
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _resolve_available_update_version() -> str | None:
|
|
309
|
+
output = _fetch_trellis_version_output()
|
|
310
|
+
if not output:
|
|
311
|
+
return None
|
|
312
|
+
return _extract_available_update_version(output)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _parse_version(version: str) -> tuple[tuple[int, int, int], tuple[str, ...] | None] | None:
|
|
316
|
+
match = _VERSION_RE.match(version)
|
|
317
|
+
if not match:
|
|
318
|
+
return None
|
|
319
|
+
major, minor, patch, prerelease = match.groups()
|
|
320
|
+
numbers = (int(major), int(minor or "0"), int(patch or "0"))
|
|
321
|
+
prerelease_parts = tuple(prerelease.split(".")) if prerelease else None
|
|
322
|
+
return numbers, prerelease_parts
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _compare_prerelease(
|
|
326
|
+
left: tuple[str, ...] | None,
|
|
327
|
+
right: tuple[str, ...] | None,
|
|
328
|
+
) -> int:
|
|
329
|
+
if left is None and right is None:
|
|
330
|
+
return 0
|
|
331
|
+
if left is None:
|
|
332
|
+
return 1
|
|
333
|
+
if right is None:
|
|
334
|
+
return -1
|
|
335
|
+
|
|
336
|
+
for left_part, right_part in zip(left, right):
|
|
337
|
+
if left_part == right_part:
|
|
338
|
+
continue
|
|
339
|
+
left_numeric = left_part.isdigit()
|
|
340
|
+
right_numeric = right_part.isdigit()
|
|
341
|
+
if left_numeric and right_numeric:
|
|
342
|
+
left_int = int(left_part)
|
|
343
|
+
right_int = int(right_part)
|
|
344
|
+
return (left_int > right_int) - (left_int < right_int)
|
|
345
|
+
if left_numeric:
|
|
346
|
+
return -1
|
|
347
|
+
if right_numeric:
|
|
348
|
+
return 1
|
|
349
|
+
return (left_part > right_part) - (left_part < right_part)
|
|
350
|
+
|
|
351
|
+
return (len(left) > len(right)) - (len(left) < len(right))
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _compare_versions(left: str, right: str) -> int | None:
|
|
355
|
+
parsed_left = _parse_version(left)
|
|
356
|
+
parsed_right = _parse_version(right)
|
|
357
|
+
if parsed_left is None or parsed_right is None:
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
left_numbers, left_prerelease = parsed_left
|
|
361
|
+
right_numbers, right_prerelease = parsed_right
|
|
362
|
+
if left_numbers != right_numbers:
|
|
363
|
+
return (left_numbers > right_numbers) - (left_numbers < right_numbers)
|
|
364
|
+
return _compare_prerelease(left_prerelease, right_prerelease)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _update_marker_path(repo_root: Path) -> Path:
|
|
368
|
+
context_key = resolve_context_key()
|
|
369
|
+
if not context_key:
|
|
370
|
+
terminal_key = os.environ.get("TERM_SESSION_ID", "").strip()
|
|
371
|
+
context_key = terminal_key or f"ppid-{os.getppid()}"
|
|
372
|
+
safe_key = re.sub(r"[^A-Za-z0-9._-]+", "_", context_key).strip("._-")
|
|
373
|
+
if not safe_key:
|
|
374
|
+
safe_key = "session"
|
|
375
|
+
return (
|
|
376
|
+
repo_root
|
|
377
|
+
/ DIR_WORKFLOW
|
|
378
|
+
/ ".runtime"
|
|
379
|
+
/ f"update-check-{safe_key[:160]}.marker"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _mark_update_check_attempted(repo_root: Path) -> bool:
|
|
384
|
+
marker_path = _update_marker_path(repo_root)
|
|
385
|
+
if marker_path.exists():
|
|
386
|
+
return False
|
|
387
|
+
try:
|
|
388
|
+
marker_path.parent.mkdir(parents=True, exist_ok=True)
|
|
389
|
+
marker_path.write_text("checked\n", encoding="utf-8")
|
|
390
|
+
except OSError:
|
|
391
|
+
pass
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _get_update_hint(repo_root: Path) -> str | None:
|
|
396
|
+
marker_path = _update_marker_path(repo_root)
|
|
397
|
+
if marker_path.exists():
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
current_version = _read_project_version(repo_root)
|
|
401
|
+
if not current_version:
|
|
402
|
+
return None
|
|
403
|
+
|
|
404
|
+
latest_version = _resolve_available_update_version()
|
|
405
|
+
if not latest_version:
|
|
406
|
+
return None
|
|
407
|
+
|
|
408
|
+
_mark_update_check_attempted(repo_root)
|
|
409
|
+
comparison = _compare_versions(current_version, latest_version)
|
|
410
|
+
if comparison is None or comparison >= 0:
|
|
411
|
+
return None
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
f"Trellis update available: {current_version} -> {latest_version}, "
|
|
415
|
+
"run trellis upgrade"
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
|
|
112
419
|
# =============================================================================
|
|
113
420
|
# JSON Output
|
|
114
421
|
# =============================================================================
|
|
@@ -137,24 +444,7 @@ def get_context_json(repo_root: Path | None = None) -> dict:
|
|
|
137
444
|
f"{DIR_WORKFLOW}/{DIR_WORKSPACE}/{developer}/{journal_file.name}"
|
|
138
445
|
)
|
|
139
446
|
|
|
140
|
-
|
|
141
|
-
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
142
|
-
branch = branch_out.strip() or "unknown"
|
|
143
|
-
|
|
144
|
-
_, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
|
|
145
|
-
git_status_count = len([line for line in status_out.splitlines() if line.strip()])
|
|
146
|
-
is_clean = git_status_count == 0
|
|
147
|
-
|
|
148
|
-
# Recent commits
|
|
149
|
-
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
|
|
150
|
-
commits = []
|
|
151
|
-
for line in log_out.splitlines():
|
|
152
|
-
if line.strip():
|
|
153
|
-
parts = line.split(" ", 1)
|
|
154
|
-
if len(parts) >= 2:
|
|
155
|
-
commits.append({"hash": parts[0], "message": parts[1]})
|
|
156
|
-
elif len(parts) == 1:
|
|
157
|
-
commits.append({"hash": parts[0], "message": ""})
|
|
447
|
+
root_git_info = _collect_root_git_info(repo_root)
|
|
158
448
|
|
|
159
449
|
# Tasks
|
|
160
450
|
tasks = [
|
|
@@ -169,15 +459,19 @@ def get_context_json(repo_root: Path | None = None) -> dict:
|
|
|
169
459
|
]
|
|
170
460
|
|
|
171
461
|
# Package git repos (independent sub-repositories)
|
|
172
|
-
pkg_git_info = _collect_package_git_info(
|
|
462
|
+
pkg_git_info = _collect_package_git_info(
|
|
463
|
+
repo_root,
|
|
464
|
+
discover_unconfigured=not root_git_info["isRepo"],
|
|
465
|
+
)
|
|
173
466
|
|
|
174
467
|
result = {
|
|
175
468
|
"developer": developer or "",
|
|
176
469
|
"git": {
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
470
|
+
"isRepo": root_git_info["isRepo"],
|
|
471
|
+
"branch": root_git_info["branch"],
|
|
472
|
+
"isClean": root_git_info["isClean"],
|
|
473
|
+
"uncommittedChanges": root_git_info["uncommittedChanges"],
|
|
474
|
+
"recentCommits": root_git_info["recentCommits"],
|
|
181
475
|
},
|
|
182
476
|
"tasks": {
|
|
183
477
|
"active": tasks,
|
|
@@ -241,39 +535,17 @@ def get_context_text(repo_root: Path | None = None) -> str:
|
|
|
241
535
|
lines.append(f"Name: {developer}")
|
|
242
536
|
lines.append("")
|
|
243
537
|
|
|
244
|
-
|
|
245
|
-
lines
|
|
246
|
-
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
247
|
-
branch = branch_out.strip() or "unknown"
|
|
248
|
-
lines.append(f"Branch: {branch}")
|
|
249
|
-
|
|
250
|
-
_, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
|
|
251
|
-
status_lines = [line for line in status_out.splitlines() if line.strip()]
|
|
252
|
-
status_count = len(status_lines)
|
|
253
|
-
|
|
254
|
-
if status_count == 0:
|
|
255
|
-
lines.append("Working directory: Clean")
|
|
256
|
-
else:
|
|
257
|
-
lines.append(f"Working directory: {status_count} uncommitted change(s)")
|
|
258
|
-
lines.append("")
|
|
259
|
-
lines.append("Changes:")
|
|
260
|
-
_, short_out, _ = run_git(["status", "--short"], cwd=repo_root)
|
|
261
|
-
for line in short_out.splitlines()[:10]:
|
|
262
|
-
lines.append(line)
|
|
263
|
-
lines.append("")
|
|
264
|
-
|
|
265
|
-
# Recent commits
|
|
266
|
-
lines.append("## RECENT COMMITS")
|
|
267
|
-
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
|
|
268
|
-
if log_out.strip():
|
|
269
|
-
for line in log_out.splitlines():
|
|
270
|
-
lines.append(line)
|
|
271
|
-
else:
|
|
272
|
-
lines.append("(no commits)")
|
|
273
|
-
lines.append("")
|
|
538
|
+
root_git_info = _collect_root_git_info(repo_root)
|
|
539
|
+
_append_root_git_context(lines, root_git_info)
|
|
274
540
|
|
|
275
541
|
# Package git repos — independent sub-repositories
|
|
276
|
-
_append_package_git_context(
|
|
542
|
+
_append_package_git_context(
|
|
543
|
+
lines,
|
|
544
|
+
_collect_package_git_info(
|
|
545
|
+
repo_root,
|
|
546
|
+
discover_unconfigured=not root_git_info["isRepo"],
|
|
547
|
+
),
|
|
548
|
+
)
|
|
277
549
|
|
|
278
550
|
# Current task
|
|
279
551
|
lines.append("## CURRENT TASK")
|
|
@@ -393,20 +665,7 @@ def get_context_record_json(repo_root: Path | None = None) -> dict:
|
|
|
393
665
|
developer = get_developer(repo_root)
|
|
394
666
|
tasks_dir = get_tasks_dir(repo_root)
|
|
395
667
|
|
|
396
|
-
|
|
397
|
-
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
398
|
-
branch = branch_out.strip() or "unknown"
|
|
399
|
-
|
|
400
|
-
_, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
|
|
401
|
-
git_status_count = len([line for line in status_out.splitlines() if line.strip()])
|
|
402
|
-
|
|
403
|
-
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
|
|
404
|
-
commits = []
|
|
405
|
-
for line in log_out.splitlines():
|
|
406
|
-
if line.strip():
|
|
407
|
-
parts = line.split(" ", 1)
|
|
408
|
-
if len(parts) >= 2:
|
|
409
|
-
commits.append({"hash": parts[0], "message": parts[1]})
|
|
668
|
+
root_git_info = _collect_root_git_info(repo_root)
|
|
410
669
|
|
|
411
670
|
# My tasks (single pass — collect statuses and filter by assignee)
|
|
412
671
|
all_tasks_list = list(iter_active_tasks(tasks_dir))
|
|
@@ -446,15 +705,19 @@ def get_context_record_json(repo_root: Path | None = None) -> dict:
|
|
|
446
705
|
}
|
|
447
706
|
|
|
448
707
|
# Package git repos
|
|
449
|
-
pkg_git_info = _collect_package_git_info(
|
|
708
|
+
pkg_git_info = _collect_package_git_info(
|
|
709
|
+
repo_root,
|
|
710
|
+
discover_unconfigured=not root_git_info["isRepo"],
|
|
711
|
+
)
|
|
450
712
|
|
|
451
713
|
result = {
|
|
452
714
|
"developer": developer or "",
|
|
453
715
|
"git": {
|
|
454
|
-
"
|
|
455
|
-
"
|
|
456
|
-
"
|
|
457
|
-
"
|
|
716
|
+
"isRepo": root_git_info["isRepo"],
|
|
717
|
+
"branch": root_git_info["branch"],
|
|
718
|
+
"isClean": root_git_info["isClean"],
|
|
719
|
+
"uncommittedChanges": root_git_info["uncommittedChanges"],
|
|
720
|
+
"recentCommits": root_git_info["recentCommits"],
|
|
458
721
|
},
|
|
459
722
|
"myTasks": my_tasks,
|
|
460
723
|
"currentTask": current_task_info,
|
|
@@ -509,39 +772,17 @@ def get_context_text_record(repo_root: Path | None = None) -> str:
|
|
|
509
772
|
lines.append("(no active tasks assigned to you)")
|
|
510
773
|
lines.append("")
|
|
511
774
|
|
|
512
|
-
|
|
513
|
-
lines
|
|
514
|
-
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
515
|
-
branch = branch_out.strip() or "unknown"
|
|
516
|
-
lines.append(f"Branch: {branch}")
|
|
517
|
-
|
|
518
|
-
_, status_out, _ = run_git(["status", "--porcelain"], cwd=repo_root)
|
|
519
|
-
status_lines = [line for line in status_out.splitlines() if line.strip()]
|
|
520
|
-
status_count = len(status_lines)
|
|
521
|
-
|
|
522
|
-
if status_count == 0:
|
|
523
|
-
lines.append("Working directory: Clean")
|
|
524
|
-
else:
|
|
525
|
-
lines.append(f"Working directory: {status_count} uncommitted change(s)")
|
|
526
|
-
lines.append("")
|
|
527
|
-
lines.append("Changes:")
|
|
528
|
-
_, short_out, _ = run_git(["status", "--short"], cwd=repo_root)
|
|
529
|
-
for line in short_out.splitlines()[:10]:
|
|
530
|
-
lines.append(line)
|
|
531
|
-
lines.append("")
|
|
532
|
-
|
|
533
|
-
# RECENT COMMITS
|
|
534
|
-
lines.append("## RECENT COMMITS")
|
|
535
|
-
_, log_out, _ = run_git(["log", "--oneline", "-5"], cwd=repo_root)
|
|
536
|
-
if log_out.strip():
|
|
537
|
-
for line in log_out.splitlines():
|
|
538
|
-
lines.append(line)
|
|
539
|
-
else:
|
|
540
|
-
lines.append("(no commits)")
|
|
541
|
-
lines.append("")
|
|
775
|
+
root_git_info = _collect_root_git_info(repo_root)
|
|
776
|
+
_append_root_git_context(lines, root_git_info)
|
|
542
777
|
|
|
543
778
|
# Package git repos — independent sub-repositories
|
|
544
|
-
_append_package_git_context(
|
|
779
|
+
_append_package_git_context(
|
|
780
|
+
lines,
|
|
781
|
+
_collect_package_git_info(
|
|
782
|
+
repo_root,
|
|
783
|
+
discover_unconfigured=not root_git_info["isRepo"],
|
|
784
|
+
),
|
|
785
|
+
)
|
|
545
786
|
|
|
546
787
|
# CURRENT TASK
|
|
547
788
|
lines.append("## CURRENT TASK")
|
|
@@ -571,4 +812,10 @@ def output_text(repo_root: Path | None = None) -> None:
|
|
|
571
812
|
Args:
|
|
572
813
|
repo_root: Repository root path. Defaults to auto-detected.
|
|
573
814
|
"""
|
|
815
|
+
if repo_root is None:
|
|
816
|
+
repo_root = get_repo_root()
|
|
817
|
+
update_hint = _get_update_hint(repo_root)
|
|
818
|
+
if update_hint:
|
|
819
|
+
print(update_hint)
|
|
820
|
+
print("")
|
|
574
821
|
print(get_context_text(repo_root))
|
|
@@ -10,9 +10,9 @@ Provides:
|
|
|
10
10
|
Note:
|
|
11
11
|
``cmd_init_context`` was removed in v0.5.0-beta.12. JSONL context files
|
|
12
12
|
are now seeded at ``task.py create`` time with a self-describing
|
|
13
|
-
``_example`` line; the AI agent curates real entries during
|
|
14
|
-
the
|
|
15
|
-
|
|
13
|
+
``_example`` line; the AI agent curates real entries during planning when
|
|
14
|
+
the task needs sub-agent/spec context. See ``.trellis/workflow.md`` for the
|
|
15
|
+
current planning artifact contract.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|