@pennixrv/trellis 0.7.0-beta.9 → 0.7.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +14 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/ablate.js +1 -1
- package/dist/commands/ablate.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +30 -56
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/task-progress.d.ts +2 -0
- package/dist/commands/task-progress.d.ts.map +1 -1
- package/dist/commands/task-progress.js +37 -30
- package/dist/commands/task-progress.js.map +1 -1
- package/dist/commands/{channel → teamwork}/adapters/claude.d.ts +3 -3
- package/dist/commands/teamwork/adapters/claude.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/adapters/claude.js +3 -3
- package/dist/commands/teamwork/adapters/claude.js.map +1 -0
- package/dist/commands/{channel → teamwork}/adapters/codex.d.ts +1 -15
- package/dist/commands/teamwork/adapters/codex.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/adapters/codex.js +5 -42
- package/dist/commands/teamwork/adapters/codex.js.map +1 -0
- package/dist/commands/{channel → teamwork}/adapters/index.d.ts +6 -6
- package/dist/commands/teamwork/adapters/index.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/adapters/index.js +16 -52
- package/dist/commands/teamwork/adapters/index.js.map +1 -0
- package/dist/commands/{channel → teamwork}/adapters/types.d.ts +2 -2
- package/dist/commands/teamwork/adapters/types.d.ts.map +1 -0
- package/dist/commands/teamwork/adapters/types.js.map +1 -0
- package/dist/commands/{channel → teamwork}/agent-loader.d.ts +2 -4
- package/dist/commands/teamwork/agent-loader.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/agent-loader.js +4 -35
- package/dist/commands/teamwork/agent-loader.js.map +1 -0
- package/dist/commands/teamwork/artifact.d.ts +177 -0
- package/dist/commands/teamwork/artifact.d.ts.map +1 -0
- package/dist/commands/teamwork/artifact.js +908 -0
- package/dist/commands/teamwork/artifact.js.map +1 -0
- package/dist/commands/{channel → teamwork}/context-loader.d.ts +1 -1
- package/dist/commands/teamwork/context-loader.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/context-loader.js +12 -12
- package/dist/commands/teamwork/context-loader.js.map +1 -0
- package/dist/commands/{channel → teamwork}/context-trust.d.ts +6 -6
- package/dist/commands/teamwork/context-trust.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/context-trust.js +16 -16
- package/dist/commands/teamwork/context-trust.js.map +1 -0
- package/dist/commands/teamwork/context.d.ts +16 -0
- package/dist/commands/teamwork/context.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/context.js +17 -17
- package/dist/commands/teamwork/context.js.map +1 -0
- package/dist/commands/teamwork/coordinator.d.ts +89 -0
- package/dist/commands/teamwork/coordinator.d.ts.map +1 -0
- package/dist/commands/teamwork/coordinator.js +1198 -0
- package/dist/commands/teamwork/coordinator.js.map +1 -0
- package/dist/commands/teamwork/create.d.ts +23 -0
- package/dist/commands/teamwork/create.d.ts.map +1 -0
- package/dist/commands/teamwork/create.js +39 -0
- package/dist/commands/teamwork/create.js.map +1 -0
- package/dist/commands/{channel → teamwork}/dev-parse-trace.d.ts +1 -1
- package/dist/commands/teamwork/dev-parse-trace.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/dev-parse-trace.js +1 -1
- package/dist/commands/teamwork/dev-parse-trace.js.map +1 -0
- package/dist/commands/{channel → teamwork}/guard.d.ts +27 -46
- package/dist/commands/teamwork/guard.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/guard.js +54 -156
- package/dist/commands/teamwork/guard.js.map +1 -0
- package/dist/commands/teamwork/index.d.ts +3 -0
- package/dist/commands/teamwork/index.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/index.js +135 -165
- package/dist/commands/teamwork/index.js.map +1 -0
- package/dist/commands/{channel → teamwork}/interrupt.d.ts +1 -1
- package/dist/commands/teamwork/interrupt.d.ts.map +1 -0
- package/dist/commands/teamwork/interrupt.js +22 -0
- package/dist/commands/teamwork/interrupt.js.map +1 -0
- package/dist/commands/teamwork/kill.d.ts +7 -0
- package/dist/commands/teamwork/kill.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/kill.js +21 -21
- package/dist/commands/teamwork/kill.js.map +1 -0
- package/dist/commands/teamwork/list.d.ts +17 -0
- package/dist/commands/teamwork/list.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/list.js +18 -27
- package/dist/commands/teamwork/list.js.map +1 -0
- package/dist/commands/{channel → teamwork}/messages.d.ts +1 -1
- package/dist/commands/teamwork/messages.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/messages.js +18 -18
- package/dist/commands/teamwork/messages.js.map +1 -0
- package/dist/commands/teamwork/profiles.d.ts +25 -0
- package/dist/commands/teamwork/profiles.d.ts.map +1 -0
- package/dist/commands/teamwork/profiles.js +125 -0
- package/dist/commands/teamwork/profiles.js.map +1 -0
- package/dist/commands/teamwork/rm.d.ts +27 -0
- package/dist/commands/teamwork/rm.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/rm.js +21 -22
- package/dist/commands/teamwork/rm.js.map +1 -0
- package/dist/commands/teamwork/run.d.ts +29 -0
- package/dist/commands/{channel → teamwork}/run.d.ts.map +1 -1
- package/dist/commands/teamwork/run.js +129 -0
- package/dist/commands/teamwork/run.js.map +1 -0
- package/dist/commands/{channel → teamwork}/send.d.ts +1 -1
- package/dist/commands/teamwork/send.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/send.js +7 -7
- package/dist/commands/teamwork/send.js.map +1 -0
- package/dist/commands/{channel → teamwork}/spawn.d.ts +8 -12
- package/dist/commands/teamwork/spawn.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/spawn.js +51 -112
- package/dist/commands/teamwork/spawn.js.map +1 -0
- package/dist/commands/teamwork/store/events.d.ts +22 -0
- package/dist/commands/teamwork/store/events.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/store/events.js +8 -8
- package/dist/commands/teamwork/store/events.js.map +1 -0
- package/dist/commands/teamwork/store/filter.d.ts +3 -0
- package/dist/commands/teamwork/store/filter.d.ts.map +1 -0
- package/dist/commands/teamwork/store/filter.js +2 -0
- package/dist/commands/teamwork/store/filter.js.map +1 -0
- package/dist/commands/{channel → teamwork}/store/lock.d.ts +1 -1
- package/dist/commands/teamwork/store/lock.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/store/lock.js +2 -2
- package/dist/commands/teamwork/store/lock.js.map +1 -0
- package/dist/commands/{channel → teamwork}/store/paths.d.ts +18 -32
- package/dist/commands/teamwork/store/paths.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/store/paths.js +42 -109
- package/dist/commands/teamwork/store/paths.js.map +1 -0
- package/dist/commands/teamwork/store/schema.d.ts +18 -0
- package/dist/commands/teamwork/store/schema.d.ts.map +1 -0
- package/dist/commands/teamwork/store/schema.js +23 -0
- package/dist/commands/teamwork/store/schema.js.map +1 -0
- package/dist/commands/{channel → teamwork}/store/thread-state.d.ts +3 -3
- package/dist/commands/teamwork/store/thread-state.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/store/thread-state.js +1 -1
- package/dist/commands/teamwork/store/thread-state.js.map +1 -0
- package/dist/commands/teamwork/store/watch.d.ts +19 -0
- package/dist/commands/teamwork/store/watch.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/store/watch.js +8 -8
- package/dist/commands/teamwork/store/watch.js.map +1 -0
- package/dist/commands/teamwork/supervisor/idle.d.ts.map +1 -0
- package/dist/commands/teamwork/supervisor/idle.js.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor/inbox.d.ts +2 -2
- package/dist/commands/teamwork/supervisor/inbox.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor/inbox.js +14 -14
- package/dist/commands/teamwork/supervisor/inbox.js.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor/shutdown.d.ts +3 -6
- package/dist/commands/teamwork/supervisor/shutdown.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor/shutdown.js +7 -17
- package/dist/commands/teamwork/supervisor/shutdown.js.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor/stdout.d.ts +3 -5
- package/dist/commands/teamwork/supervisor/stdout.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor/stdout.js +12 -23
- package/dist/commands/teamwork/supervisor/stdout.js.map +1 -0
- package/dist/commands/teamwork/supervisor/turns.d.ts.map +1 -0
- package/dist/commands/teamwork/supervisor/turns.js.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor/warning.d.ts +5 -5
- package/dist/commands/teamwork/supervisor/warning.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor/warning.js +6 -6
- package/dist/commands/teamwork/supervisor/warning.js.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor.d.ts +21 -21
- package/dist/commands/teamwork/supervisor.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/supervisor.js +53 -63
- package/dist/commands/teamwork/supervisor.js.map +1 -0
- package/dist/commands/teamwork/teammate-adapters.d.ts +31 -0
- package/dist/commands/teamwork/teammate-adapters.d.ts.map +1 -0
- package/dist/commands/teamwork/teammate-adapters.js +96 -0
- package/dist/commands/teamwork/teammate-adapters.js.map +1 -0
- package/dist/commands/teamwork/teammate-config.d.ts +19 -0
- package/dist/commands/teamwork/teammate-config.d.ts.map +1 -0
- package/dist/commands/teamwork/teammate-config.js +97 -0
- package/dist/commands/teamwork/teammate-config.js.map +1 -0
- package/dist/commands/teamwork/teammate-launch.d.ts +58 -0
- package/dist/commands/teamwork/teammate-launch.d.ts.map +1 -0
- package/dist/commands/teamwork/teammate-launch.js +125 -0
- package/dist/commands/teamwork/teammate-launch.js.map +1 -0
- package/dist/commands/teamwork/text-body.d.ts +13 -0
- package/dist/commands/teamwork/text-body.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/text-body.js +3 -3
- package/dist/commands/teamwork/text-body.js.map +1 -0
- package/dist/commands/teamwork/threads.d.ts +35 -0
- package/dist/commands/teamwork/threads.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/threads.js +19 -19
- package/dist/commands/teamwork/threads.js.map +1 -0
- package/dist/commands/teamwork/title.d.ts +12 -0
- package/dist/commands/teamwork/title.d.ts.map +1 -0
- package/dist/commands/teamwork/title.js +24 -0
- package/dist/commands/teamwork/title.js.map +1 -0
- package/dist/commands/{channel → teamwork}/wait.d.ts +1 -5
- package/dist/commands/teamwork/wait.d.ts.map +1 -0
- package/dist/commands/{channel → teamwork}/wait.js +8 -21
- package/dist/commands/teamwork/wait.js.map +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +48 -89
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/workflow.d.ts +1 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +9 -10
- package/dist/commands/workflow.js.map +1 -1
- package/dist/configurators/claude.d.ts +0 -17
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js +2 -61
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +2 -2
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/snow.js +1 -1
- package/dist/configurators/snow.js.map +1 -1
- package/dist/configurators/workflow.d.ts +1 -1
- package/dist/configurators/workflow.js +5 -5
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +1 -1
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +1 -1
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.7.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.15.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.17.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.18.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.19.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.20.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.21.json +17 -0
- package/dist/migrations/manifests/0.7.0-beta.22.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.23.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.24.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.25.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.26.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.27.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.28.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.29.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.30.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.31.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.32.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.33.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.34.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.35.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.36.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.37.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.38.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.39.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.40.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.41.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.42.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.43.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.44.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.45.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.46.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.47.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.48.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.49.json +13 -0
- package/dist/migrations/manifests/0.7.0-beta.50.json +12 -0
- package/dist/migrations/manifests/0.7.0-beta.51.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.52.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.53.json +27 -0
- package/dist/migrations/manifests/0.7.0-beta.54.json +8 -0
- package/dist/migrations/manifests/0.7.1-beta.0.json +9 -0
- package/dist/migrations/manifests/0.7.1.json +9 -0
- package/dist/templates/claude/hooks/statusline.py +0 -7
- package/dist/templates/codex/hooks/session-start.py +31 -38
- package/dist/templates/common/bundled-skills/trellis-decision-space-construction-and-merging/SKILL.md +10 -0
- package/dist/templates/common/bundled-skills/trellis-decision-space-construction-and-merging/workflow.md +40 -0
- package/dist/templates/common/bundled-skills/trellis-delivery-review-and-closeout/SKILL.md +10 -0
- package/dist/templates/common/bundled-skills/trellis-delivery-review-and-closeout/workflow.md +55 -0
- package/dist/templates/common/bundled-skills/trellis-goal-exploration-and-requirements/SKILL.md +10 -0
- package/dist/templates/common/bundled-skills/trellis-goal-exploration-and-requirements/workflow.md +33 -0
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +25 -25
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +13 -19
- 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-task-lifecycle.md +27 -75
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +17 -56
- 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/bundled-skills.md +14 -11
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +0 -33
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-teamwork.md +32 -0
- 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 +25 -111
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +19 -60
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-session-insight/references/cli-quick-reference.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/SKILL.md +10 -0
- package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/workflow.md +36 -0
- package/dist/templates/common/bundled-skills/trellis-task-implementation/SKILL.md +10 -0
- package/dist/templates/common/bundled-skills/trellis-task-implementation/workflow.md +29 -0
- package/dist/templates/common/bundled-skills/trellis-teamwork/SKILL.md +73 -0
- package/dist/templates/common/bundled-skills/trellis-teamwork/references/coordination-modes.md +29 -0
- package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-configuration-matrix.md +31 -0
- package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-processes.md +37 -0
- package/dist/templates/common/bundled-skills/trellis-teamwork/references/teamwork-plan-construction.md +78 -0
- package/dist/templates/common/commands/continue.md +9 -49
- package/dist/templates/common/commands/finish-work.md +10 -58
- package/dist/templates/common/commands/start.md +19 -49
- package/dist/templates/common/skills/brainstorm.md +11 -199
- package/dist/templates/copilot/hooks/session-start.py +30 -26
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +7 -160
- package/dist/templates/copilot/prompts/finish-work.prompt.md +11 -88
- package/dist/templates/copilot/prompts/record-session.prompt.md +4 -12
- package/dist/templates/copilot/prompts/start.prompt.md +12 -10
- package/dist/templates/dsh/DSH.md +2 -2
- package/dist/templates/kiro/agents/trellis.json +2 -2
- package/dist/templates/markdown/agents.md +14 -3
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +9 -8
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +1 -1
- package/dist/templates/markdown/workspace-index.md +0 -1
- package/dist/templates/omp/extensions/trellis/index.ts.txt +28 -17
- package/dist/templates/opencode/lib/session-utils.js +20 -25
- package/dist/templates/opencode/lib/trellis-context.js +4 -76
- package/dist/templates/opencode/plugins/inject-subagent-context.js +5 -4
- package/dist/templates/opencode/plugins/inject-workflow-state.js +14 -17
- package/dist/templates/shared-hooks/inject-shell-session-context.py +3 -3
- package/dist/templates/shared-hooks/inject-subagent-context.py +445 -67
- package/dist/templates/shared-hooks/inject-workflow-state.py +44 -36
- package/dist/templates/shared-hooks/session-start.py +21 -33
- package/dist/templates/trellis/agents/delegated-worktree-primary-agent.md +18 -0
- package/dist/templates/trellis/agents/teammate-profiles.json +8 -0
- package/dist/templates/trellis/agents/teammate.md +63 -0
- package/dist/templates/trellis/config.yaml +29 -38
- package/dist/templates/trellis/gitignore.txt +1 -0
- package/dist/templates/trellis/index.d.ts +8 -12
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +14 -25
- 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/__init__.py +0 -1
- package/dist/templates/trellis/scripts/common/active_task.py +136 -100
- package/dist/templates/trellis/scripts/common/config.py +7 -9
- package/dist/templates/trellis/scripts/common/git_context.py +1 -1
- package/dist/templates/trellis/scripts/common/io.py +19 -0
- package/dist/templates/trellis/scripts/common/paths.py +0 -1
- package/dist/templates/trellis/scripts/common/safe_commit.py +11 -90
- package/dist/templates/trellis/scripts/common/session_context.py +1 -8
- package/dist/templates/trellis/scripts/common/spec_match.py +1 -1
- package/dist/templates/trellis/scripts/common/task_context.py +152 -59
- package/dist/templates/trellis/scripts/common/task_continuity.py +598 -0
- package/dist/templates/trellis/scripts/common/task_store.py +70 -548
- package/dist/templates/trellis/scripts/common/task_utils.py +4 -98
- package/dist/templates/trellis/scripts/common/task_workflow.py +1096 -0
- package/dist/templates/trellis/scripts/common/tasks.py +5 -8
- package/dist/templates/trellis/scripts/common/workflow_phase.py +8 -7
- package/dist/templates/trellis/scripts/hooks/linear_sync.py +3 -3
- package/dist/templates/trellis/scripts/task.py +119 -377
- package/dist/templates/trellis/workflow.md +111 -706
- package/dist/utils/agent-refs.d.ts +5 -5
- package/dist/utils/agent-refs.js +6 -6
- package/dist/utils/file-writer.d.ts +3 -2
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +3 -2
- package/dist/utils/file-writer.js.map +1 -1
- package/dist/utils/managed-paths.d.ts +0 -7
- package/dist/utils/managed-paths.d.ts.map +1 -1
- package/dist/utils/managed-paths.js +0 -40
- package/dist/utils/managed-paths.js.map +1 -1
- package/dist/utils/manifest-prune.d.ts.map +1 -1
- package/dist/utils/manifest-prune.js +2 -11
- package/dist/utils/manifest-prune.js.map +1 -1
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
- package/dist/utils/uninstall-scrubbers.js +3 -3
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/dist/utils/workflow-resolver.d.ts +10 -16
- package/dist/utils/workflow-resolver.d.ts.map +1 -1
- package/dist/utils/workflow-resolver.js +25 -36
- package/dist/utils/workflow-resolver.js.map +1 -1
- package/package.json +2 -2
- package/dist/commands/channel/adapters/claude.d.ts.map +0 -1
- package/dist/commands/channel/adapters/claude.js.map +0 -1
- package/dist/commands/channel/adapters/codex.d.ts.map +0 -1
- package/dist/commands/channel/adapters/codex.js.map +0 -1
- package/dist/commands/channel/adapters/index.d.ts.map +0 -1
- package/dist/commands/channel/adapters/index.js.map +0 -1
- package/dist/commands/channel/adapters/types.d.ts.map +0 -1
- package/dist/commands/channel/adapters/types.js.map +0 -1
- package/dist/commands/channel/agent-loader.d.ts.map +0 -1
- package/dist/commands/channel/agent-loader.js.map +0 -1
- package/dist/commands/channel/context-loader.d.ts.map +0 -1
- package/dist/commands/channel/context-loader.js.map +0 -1
- package/dist/commands/channel/context-trust.d.ts.map +0 -1
- package/dist/commands/channel/context-trust.js.map +0 -1
- package/dist/commands/channel/context.d.ts +0 -16
- package/dist/commands/channel/context.d.ts.map +0 -1
- package/dist/commands/channel/context.js.map +0 -1
- package/dist/commands/channel/create.d.ts +0 -29
- package/dist/commands/channel/create.d.ts.map +0 -1
- package/dist/commands/channel/create.js +0 -47
- package/dist/commands/channel/create.js.map +0 -1
- package/dist/commands/channel/dev-parse-trace.d.ts.map +0 -1
- package/dist/commands/channel/dev-parse-trace.js.map +0 -1
- package/dist/commands/channel/guard.d.ts.map +0 -1
- package/dist/commands/channel/guard.js.map +0 -1
- package/dist/commands/channel/index.d.ts +0 -3
- package/dist/commands/channel/index.d.ts.map +0 -1
- package/dist/commands/channel/index.js.map +0 -1
- package/dist/commands/channel/interrupt.d.ts.map +0 -1
- package/dist/commands/channel/interrupt.js +0 -22
- package/dist/commands/channel/interrupt.js.map +0 -1
- package/dist/commands/channel/kill.d.ts +0 -7
- package/dist/commands/channel/kill.d.ts.map +0 -1
- package/dist/commands/channel/kill.js.map +0 -1
- package/dist/commands/channel/list.d.ts +0 -18
- package/dist/commands/channel/list.d.ts.map +0 -1
- package/dist/commands/channel/list.js.map +0 -1
- package/dist/commands/channel/messages.d.ts.map +0 -1
- package/dist/commands/channel/messages.js.map +0 -1
- package/dist/commands/channel/rm.d.ts +0 -27
- package/dist/commands/channel/rm.d.ts.map +0 -1
- package/dist/commands/channel/rm.js.map +0 -1
- package/dist/commands/channel/run.d.ts +0 -32
- package/dist/commands/channel/run.js +0 -141
- package/dist/commands/channel/run.js.map +0 -1
- package/dist/commands/channel/send.d.ts.map +0 -1
- package/dist/commands/channel/send.js.map +0 -1
- package/dist/commands/channel/spawn.d.ts.map +0 -1
- package/dist/commands/channel/spawn.js.map +0 -1
- package/dist/commands/channel/store/events.d.ts +0 -22
- package/dist/commands/channel/store/events.d.ts.map +0 -1
- package/dist/commands/channel/store/events.js.map +0 -1
- package/dist/commands/channel/store/filter.d.ts +0 -3
- package/dist/commands/channel/store/filter.d.ts.map +0 -1
- package/dist/commands/channel/store/filter.js +0 -2
- package/dist/commands/channel/store/filter.js.map +0 -1
- package/dist/commands/channel/store/lock.d.ts.map +0 -1
- package/dist/commands/channel/store/lock.js.map +0 -1
- package/dist/commands/channel/store/paths.d.ts.map +0 -1
- package/dist/commands/channel/store/paths.js.map +0 -1
- package/dist/commands/channel/store/schema.d.ts +0 -27
- package/dist/commands/channel/store/schema.d.ts.map +0 -1
- package/dist/commands/channel/store/schema.js +0 -34
- package/dist/commands/channel/store/schema.js.map +0 -1
- package/dist/commands/channel/store/thread-state.d.ts.map +0 -1
- package/dist/commands/channel/store/thread-state.js.map +0 -1
- package/dist/commands/channel/store/watch.d.ts +0 -19
- package/dist/commands/channel/store/watch.d.ts.map +0 -1
- package/dist/commands/channel/store/watch.js.map +0 -1
- package/dist/commands/channel/supervisor/idle.d.ts.map +0 -1
- package/dist/commands/channel/supervisor/idle.js.map +0 -1
- package/dist/commands/channel/supervisor/inbox.d.ts.map +0 -1
- package/dist/commands/channel/supervisor/inbox.js.map +0 -1
- package/dist/commands/channel/supervisor/shutdown.d.ts.map +0 -1
- package/dist/commands/channel/supervisor/shutdown.js.map +0 -1
- package/dist/commands/channel/supervisor/stdout.d.ts.map +0 -1
- package/dist/commands/channel/supervisor/stdout.js.map +0 -1
- package/dist/commands/channel/supervisor/turns.d.ts.map +0 -1
- package/dist/commands/channel/supervisor/turns.js.map +0 -1
- package/dist/commands/channel/supervisor/warning.d.ts.map +0 -1
- package/dist/commands/channel/supervisor/warning.js.map +0 -1
- package/dist/commands/channel/supervisor.d.ts.map +0 -1
- package/dist/commands/channel/supervisor.js.map +0 -1
- package/dist/commands/channel/text-body.d.ts +0 -13
- package/dist/commands/channel/text-body.d.ts.map +0 -1
- package/dist/commands/channel/text-body.js.map +0 -1
- package/dist/commands/channel/threads.d.ts +0 -39
- package/dist/commands/channel/threads.d.ts.map +0 -1
- package/dist/commands/channel/threads.js.map +0 -1
- package/dist/commands/channel/title.d.ts +0 -12
- package/dist/commands/channel/title.d.ts.map +0 -1
- package/dist/commands/channel/title.js +0 -24
- package/dist/commands/channel/title.js.map +0 -1
- package/dist/commands/channel/wait.d.ts.map +0 -1
- package/dist/commands/channel/wait.js.map +0 -1
- package/dist/commands/channel/workers.d.ts +0 -9
- package/dist/commands/channel/workers.d.ts.map +0 -1
- package/dist/commands/channel/workers.js +0 -31
- package/dist/commands/channel/workers.js.map +0 -1
- package/dist/templates/common/bundled-skills/trellis-channel/SKILL.md +0 -69
- package/dist/templates/common/bundled-skills/trellis-channel/references/command-reference.md +0 -549
- package/dist/templates/common/bundled-skills/trellis-channel/references/forum.md +0 -233
- package/dist/templates/common/bundled-skills/trellis-channel/references/progress-debugging.md +0 -237
- package/dist/templates/common/bundled-skills/trellis-channel/references/subnode-work.md +0 -222
- package/dist/templates/common/bundled-skills/trellis-channel/references/workers.md +0 -295
- package/dist/templates/common/bundled-skills/trellis-channel/references/workflows.md +0 -131
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/multi-agent-channel.md +0 -70
- package/dist/templates/common/bundled-skills/trellis-research-record/SKILL.md +0 -14
- package/dist/templates/copilot/prompts/parallel.prompt.md +0 -204
- package/dist/templates/trellis/agents/check.md +0 -70
- package/dist/templates/trellis/agents/implement.md +0 -71
- package/dist/templates/trellis/agents/subnode.env +0 -4
- package/dist/templates/trellis/agents/subnode.md +0 -76
- package/dist/templates/trellis/scripts/common/context_projection.py +0 -508
- package/dist/templates/trellis/scripts/common/continuation_record.py +0 -450
- package/dist/templates/trellis/scripts/common/ownership_record.py +0 -439
- package/dist/templates/trellis/scripts/subnode_artifact.py +0 -735
- package/dist/templates/trellis/scripts/workspace_note.py +0 -126
- /package/dist/commands/{channel → teamwork}/adapters/types.js +0 -0
- /package/dist/commands/{channel → teamwork}/supervisor/idle.d.ts +0 -0
- /package/dist/commands/{channel → teamwork}/supervisor/idle.js +0 -0
- /package/dist/commands/{channel → teamwork}/supervisor/turns.d.ts +0 -0
- /package/dist/commands/{channel → teamwork}/supervisor/turns.js +0 -0
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
"""Bounded, task-scoped Continuation Record runtime projection."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import hashlib
|
|
6
|
-
import json
|
|
7
|
-
import os
|
|
8
|
-
import re
|
|
9
|
-
import tempfile
|
|
10
|
-
from contextlib import contextmanager
|
|
11
|
-
from datetime import datetime, timezone
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Any
|
|
14
|
-
|
|
15
|
-
from .active_task import resolve_active_task
|
|
16
|
-
from .git import run_git
|
|
17
|
-
from .io import read_json_checked
|
|
18
|
-
from .paths import FILE_TASK_JSON, get_repo_root
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
SCHEMA_VERSION = 1
|
|
22
|
-
KIND = "trellis-continuation-record"
|
|
23
|
-
STATUS_ABSENT = "absent"
|
|
24
|
-
STATUS_READY = "ready"
|
|
25
|
-
STATUS_STALE = "stale"
|
|
26
|
-
STATUS_WITHHELD = "withheld"
|
|
27
|
-
MAX_RECORD_BYTES = 128 * 1024
|
|
28
|
-
MAX_REQUEST_BYTES = 64 * 1024
|
|
29
|
-
MAX_TEXT_BYTES = 4096
|
|
30
|
-
MAX_LIST = 32
|
|
31
|
-
MAX_EVIDENCE = 32
|
|
32
|
-
MAX_EVIDENCE_BYTES = 2 * 1024 * 1024
|
|
33
|
-
MAX_TASK_FILES = 512
|
|
34
|
-
MAX_TASK_FILE_BYTES = 2 * 1024 * 1024
|
|
35
|
-
SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
|
|
36
|
-
SHA256 = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
37
|
-
SECRET = re.compile(
|
|
38
|
-
r"(?:-----BEGIN [A-Z0-9 ]+ PRIVATE KEY-----|bearer\s+[A-Za-z0-9._~+/=-]{20,}|"
|
|
39
|
-
r"\b(?:sk|th)-[A-Za-z0-9_-]{20,}|(?:api[_-]?key|password|secret)\s*[:=]\s*\S+)",
|
|
40
|
-
re.IGNORECASE,
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class ContinuationError(ValueError):
|
|
45
|
-
"""Raised when a Continuation Record cannot be safely read or written."""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class NoDirectCurrentTask(ContinuationError):
|
|
49
|
-
"""Raised when the current session has no directly bound active task."""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _canonical(value: Any) -> bytes:
|
|
53
|
-
return json.dumps(value, ensure_ascii=True, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _digest(value: Any) -> str:
|
|
57
|
-
return "sha256:" + hashlib.sha256(_canonical(value)).hexdigest()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _text(value: Any, label: str, maximum: int = MAX_TEXT_BYTES) -> str:
|
|
61
|
-
if not isinstance(value, str) or not value.strip() or len(value.encode("utf-8")) > maximum:
|
|
62
|
-
raise ContinuationError(f"{label} is invalid")
|
|
63
|
-
if any(ord(char) < 32 and char not in "\n\t" for char in value) or SECRET.search(value):
|
|
64
|
-
raise ContinuationError(f"{label} is unsafe")
|
|
65
|
-
return value
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _list(value: Any, label: str) -> list[str]:
|
|
69
|
-
if not isinstance(value, list) or len(value) > MAX_LIST:
|
|
70
|
-
raise ContinuationError(f"{label} is invalid")
|
|
71
|
-
return [_text(item, f"{label}[{index}]") for index, item in enumerate(value)]
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _safe_task_id(task_id: Any) -> str:
|
|
75
|
-
value = _text(task_id, "task.id", 128)
|
|
76
|
-
if not SAFE_ID.fullmatch(value) or "/" in value or "\\" in value or value in {".", ".."}:
|
|
77
|
-
raise ContinuationError("task.id is unsafe")
|
|
78
|
-
return value
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _record_path(root: Path, task_id: str) -> Path:
|
|
82
|
-
safe_id = _safe_task_id(task_id)
|
|
83
|
-
runtime = root / ".trellis" / ".runtime"
|
|
84
|
-
records = runtime / "continuation-records"
|
|
85
|
-
task_dir = records / safe_id
|
|
86
|
-
if runtime.is_symlink() or records.is_symlink() or task_dir.is_symlink():
|
|
87
|
-
raise ContinuationError("continuation record directory is unsafe")
|
|
88
|
-
return task_dir / "record.json"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _regular(path: Path, label: str) -> Path:
|
|
92
|
-
if path.is_symlink() or not path.is_file():
|
|
93
|
-
raise ContinuationError(f"{label} is not a regular file")
|
|
94
|
-
return path
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def _project_file(root: Path, value: Any, label: str) -> Path:
|
|
98
|
-
relative = _text(value, label, 1024)
|
|
99
|
-
candidate = Path(relative)
|
|
100
|
-
if candidate.is_absolute() or ".." in candidate.parts or relative.startswith(".trellis/.runtime/"):
|
|
101
|
-
raise ContinuationError(f"{label} must be project-relative")
|
|
102
|
-
cursor = root
|
|
103
|
-
for part in candidate.parts:
|
|
104
|
-
cursor /= part
|
|
105
|
-
if cursor.is_symlink():
|
|
106
|
-
raise ContinuationError(f"{label} contains a symbolic path")
|
|
107
|
-
resolved = (root / candidate).resolve()
|
|
108
|
-
try:
|
|
109
|
-
resolved.relative_to(root.resolve())
|
|
110
|
-
except ValueError as exc:
|
|
111
|
-
raise ContinuationError(f"{label} escapes project") from exc
|
|
112
|
-
return resolved
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _file_digest(path: Path, maximum: int = MAX_EVIDENCE_BYTES) -> tuple[str, int]:
|
|
116
|
-
hasher = hashlib.sha256()
|
|
117
|
-
size = 0
|
|
118
|
-
with _regular(path, "evidence file").open("rb") as handle:
|
|
119
|
-
while chunk := handle.read(256 * 1024):
|
|
120
|
-
size += len(chunk)
|
|
121
|
-
if size > maximum:
|
|
122
|
-
raise ContinuationError("source file is too large")
|
|
123
|
-
hasher.update(chunk)
|
|
124
|
-
return "sha256:" + hasher.hexdigest(), size
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def _task_source(root: Path) -> dict[str, Any]:
|
|
128
|
-
active = resolve_active_task(root, allow_single_session_fallback=True)
|
|
129
|
-
if not active.task_path:
|
|
130
|
-
raise NoDirectCurrentTask("no_direct_current_task")
|
|
131
|
-
if active.source_type == "session-fallback":
|
|
132
|
-
raise ContinuationError("session_fallback_untrusted")
|
|
133
|
-
if active.source_type != "session" or not active.context_key:
|
|
134
|
-
raise NoDirectCurrentTask("no_direct_current_task")
|
|
135
|
-
if active.stale:
|
|
136
|
-
raise ContinuationError("current_session_pointer_stale")
|
|
137
|
-
task_path = _project_file(root, active.task_path, "task path")
|
|
138
|
-
task_file = _regular(task_path / FILE_TASK_JSON, "task.json")
|
|
139
|
-
data, reason = read_json_checked(task_file)
|
|
140
|
-
if data is None:
|
|
141
|
-
raise ContinuationError(f"task.json is {reason}")
|
|
142
|
-
task_id = _safe_task_id(data.get("id") or data.get("name"))
|
|
143
|
-
entries: list[dict[str, Any]] = []
|
|
144
|
-
for item in sorted(task_path.rglob("*")):
|
|
145
|
-
if item.is_symlink() or not item.is_file():
|
|
146
|
-
continue
|
|
147
|
-
if len(entries) >= MAX_TASK_FILES:
|
|
148
|
-
raise ContinuationError("task has too many material files")
|
|
149
|
-
item_digest, size = _file_digest(item, MAX_TASK_FILE_BYTES)
|
|
150
|
-
entries.append({"path": item.relative_to(root).as_posix(), "bytes": size, "sha256": item_digest})
|
|
151
|
-
return {
|
|
152
|
-
"id": task_id,
|
|
153
|
-
"path": active.task_path,
|
|
154
|
-
"status": _text(data.get("status"), "task.status", 64),
|
|
155
|
-
"material_digest": _digest(entries),
|
|
156
|
-
"context_key": active.context_key,
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _git_source(root: Path) -> dict[str, Any]:
|
|
161
|
-
def run(*args: str) -> str:
|
|
162
|
-
rc, out, _ = run_git(list(args), cwd=root, timeout=20)
|
|
163
|
-
if rc:
|
|
164
|
-
raise ContinuationError("git source snapshot failed")
|
|
165
|
-
return out.strip()
|
|
166
|
-
|
|
167
|
-
dirty = [
|
|
168
|
-
line
|
|
169
|
-
for line in run("status", "--porcelain=v1").splitlines()
|
|
170
|
-
if line and ".trellis/.runtime/continuation-records/" not in line[3:]
|
|
171
|
-
]
|
|
172
|
-
branch = run("branch", "--show-current")
|
|
173
|
-
head = run("rev-parse", "HEAD")
|
|
174
|
-
return {
|
|
175
|
-
"branch": branch or None,
|
|
176
|
-
"head": _text(head, "git.head", 128),
|
|
177
|
-
"worktree_state": "dirty" if dirty else "clean",
|
|
178
|
-
"dirty_paths_digest": _digest(dirty),
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def _evidence(root: Path, values: Any) -> list[dict[str, Any]]:
|
|
183
|
-
if not isinstance(values, list) or len(values) > MAX_EVIDENCE:
|
|
184
|
-
raise ContinuationError("evidence must be a bounded list")
|
|
185
|
-
result = []
|
|
186
|
-
seen: set[str] = set()
|
|
187
|
-
for item in values:
|
|
188
|
-
path = _project_file(root, item, "evidence path")
|
|
189
|
-
relative = path.relative_to(root).as_posix()
|
|
190
|
-
if relative in seen:
|
|
191
|
-
raise ContinuationError("evidence paths must be unique")
|
|
192
|
-
seen.add(relative)
|
|
193
|
-
file_digest, size = _file_digest(path)
|
|
194
|
-
result.append({"path": relative, "bytes": size, "sha256": file_digest})
|
|
195
|
-
return result
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def _source(root: Path, task: dict[str, Any], evidence: list[dict[str, Any]]) -> dict[str, Any]:
|
|
199
|
-
git = _git_source(root)
|
|
200
|
-
source = {
|
|
201
|
-
"task": {key: task[key] for key in ("id", "path", "status", "material_digest")},
|
|
202
|
-
"git": git,
|
|
203
|
-
"evidence": evidence,
|
|
204
|
-
}
|
|
205
|
-
source["source_digest"] = _digest(source)
|
|
206
|
-
return source
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def _request(root: Path, path: Path) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
210
|
-
_regular(path, "continuity request")
|
|
211
|
-
if path.stat().st_size > MAX_REQUEST_BYTES:
|
|
212
|
-
raise ContinuationError("continuity request is too large")
|
|
213
|
-
try:
|
|
214
|
-
value = json.loads(path.read_text(encoding="utf-8"))
|
|
215
|
-
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
216
|
-
raise ContinuationError("continuity request is invalid") from exc
|
|
217
|
-
if not isinstance(value, dict) or set(value) != {"objective", "decisions", "completed", "open_items", "blockers", "next_safe_action", "non_transferable_operations", "evidence"}:
|
|
218
|
-
raise ContinuationError("continuity request fields are invalid")
|
|
219
|
-
content = {key: _text(value[key], f"content.{key}") if key in {"objective", "next_safe_action"} else _list(value[key], f"content.{key}") for key in value if key != "evidence"}
|
|
220
|
-
return content, _evidence(root, value["evidence"])
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _request_path(root: Path, path: Path) -> Path:
|
|
224
|
-
try:
|
|
225
|
-
relative = path.relative_to(root)
|
|
226
|
-
except ValueError as exc:
|
|
227
|
-
raise ContinuationError("continuity request must be project-relative") from exc
|
|
228
|
-
return _project_file(root, relative.as_posix(), "continuity request")
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def _expected_digest(value: str) -> str:
|
|
232
|
-
if value == "absent" or SHA256.fullmatch(value):
|
|
233
|
-
return value
|
|
234
|
-
raise ContinuationError("expected record digest is invalid")
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def _ensure_record_parent(path: Path) -> None:
|
|
238
|
-
if path.parent.is_symlink() or path.parent.parent.is_symlink():
|
|
239
|
-
raise ContinuationError("continuation record directory is unsafe")
|
|
240
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
241
|
-
if path.parent.is_symlink() or path.parent.parent.is_symlink():
|
|
242
|
-
raise ContinuationError("continuation record directory is unsafe")
|
|
243
|
-
os.chmod(path.parent.parent, 0o700)
|
|
244
|
-
os.chmod(path.parent, 0o700)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
@contextmanager
|
|
248
|
-
def _record_lock(path: Path):
|
|
249
|
-
_ensure_record_parent(path)
|
|
250
|
-
lock = path.parent / ".lock"
|
|
251
|
-
if lock.is_symlink():
|
|
252
|
-
raise ContinuationError("continuation record lock is unsafe")
|
|
253
|
-
# ponytail: one advisory lock per task record; a lock service is unnecessary unless distributed writers are introduced.
|
|
254
|
-
with lock.open("a+", encoding="utf-8") as handle:
|
|
255
|
-
try:
|
|
256
|
-
import fcntl
|
|
257
|
-
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
|
|
258
|
-
except (ImportError, OSError) as exc:
|
|
259
|
-
raise ContinuationError("continuation record lock unavailable") from exc
|
|
260
|
-
try:
|
|
261
|
-
yield
|
|
262
|
-
finally:
|
|
263
|
-
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def _record_digest(record: dict[str, Any]) -> str:
|
|
267
|
-
return _digest({key: value for key, value in record.items() if key != "integrity"})
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def _decode(path: Path) -> dict[str, Any]:
|
|
271
|
-
_regular(path, "continuation record")
|
|
272
|
-
if path.stat().st_size > MAX_RECORD_BYTES:
|
|
273
|
-
raise ContinuationError("continuation record is too large")
|
|
274
|
-
data, reason = read_json_checked(path)
|
|
275
|
-
if data is None:
|
|
276
|
-
raise ContinuationError(f"continuation record is {reason}")
|
|
277
|
-
expected = {"schema_version", "kind", "task", "source", "content", "revision", "updated_at", "integrity"}
|
|
278
|
-
if set(data) != expected or data["schema_version"] != SCHEMA_VERSION or data["kind"] != KIND:
|
|
279
|
-
raise ContinuationError("continuation record schema is unsupported")
|
|
280
|
-
task = data["task"]
|
|
281
|
-
if not isinstance(task, dict) or set(task) != {"id", "path", "status", "material_digest"}:
|
|
282
|
-
raise ContinuationError("continuation record task is invalid")
|
|
283
|
-
_safe_task_id(task["id"])
|
|
284
|
-
task_path = _text(task["path"], "task.path", 1024)
|
|
285
|
-
task_path_value = Path(task_path)
|
|
286
|
-
if task_path_value.is_absolute() or ".." in task_path_value.parts or not task_path.startswith(".trellis/tasks/"):
|
|
287
|
-
raise ContinuationError("task.path is unsafe")
|
|
288
|
-
_text(task["status"], "task.status", 64)
|
|
289
|
-
if not SHA256.fullmatch(task["material_digest"]):
|
|
290
|
-
raise ContinuationError("task.material_digest is invalid")
|
|
291
|
-
source = data["source"]
|
|
292
|
-
if not isinstance(source, dict) or set(source) != {"task", "git", "evidence", "source_digest"}:
|
|
293
|
-
raise ContinuationError("continuation record source is invalid")
|
|
294
|
-
if source["task"] != task:
|
|
295
|
-
raise ContinuationError("continuation record task source is invalid")
|
|
296
|
-
git = source["git"]
|
|
297
|
-
if not isinstance(git, dict) or set(git) != {"branch", "head", "worktree_state", "dirty_paths_digest"}:
|
|
298
|
-
raise ContinuationError("continuation record git source is invalid")
|
|
299
|
-
if git["branch"] is not None:
|
|
300
|
-
_text(git["branch"], "git.branch", 512)
|
|
301
|
-
_text(git["head"], "git.head", 128)
|
|
302
|
-
if git["worktree_state"] not in {"clean", "dirty"} or not SHA256.fullmatch(git["dirty_paths_digest"]):
|
|
303
|
-
raise ContinuationError("continuation record git source is invalid")
|
|
304
|
-
if not isinstance(source["evidence"], list) or len(source["evidence"]) > MAX_EVIDENCE:
|
|
305
|
-
raise ContinuationError("continuation record evidence is invalid")
|
|
306
|
-
for index, item in enumerate(source["evidence"]):
|
|
307
|
-
if not isinstance(item, dict) or set(item) != {"path", "bytes", "sha256"}:
|
|
308
|
-
raise ContinuationError("continuation record evidence item is invalid")
|
|
309
|
-
_text(item["path"], f"evidence[{index}].path", 1024)
|
|
310
|
-
if not isinstance(item["bytes"], int) or item["bytes"] < 0 or not SHA256.fullmatch(item["sha256"]):
|
|
311
|
-
raise ContinuationError("continuation record evidence item is invalid")
|
|
312
|
-
if not SHA256.fullmatch(source["source_digest"]):
|
|
313
|
-
raise ContinuationError("source digest is invalid")
|
|
314
|
-
if source["source_digest"] != _digest({key: value for key, value in source.items() if key != "source_digest"}):
|
|
315
|
-
raise ContinuationError("source digest does not match")
|
|
316
|
-
content = data["content"]
|
|
317
|
-
if not isinstance(content, dict) or set(content) != {"objective", "decisions", "completed", "open_items", "blockers", "next_safe_action", "non_transferable_operations"}:
|
|
318
|
-
raise ContinuationError("continuation record content is invalid")
|
|
319
|
-
_text(content["objective"], "content.objective")
|
|
320
|
-
_text(content["next_safe_action"], "content.next_safe_action")
|
|
321
|
-
for key in ("decisions", "completed", "open_items", "blockers", "non_transferable_operations"):
|
|
322
|
-
_list(content[key], f"content.{key}")
|
|
323
|
-
if not isinstance(data["revision"], int) or data["revision"] < 1:
|
|
324
|
-
raise ContinuationError("revision is invalid")
|
|
325
|
-
_text(data["updated_at"], "updated_at", 128)
|
|
326
|
-
integrity = data["integrity"]
|
|
327
|
-
if not isinstance(integrity, dict) or set(integrity) != {"record_digest"} or not SHA256.fullmatch(integrity["record_digest"]):
|
|
328
|
-
raise ContinuationError("record digest is invalid")
|
|
329
|
-
if integrity["record_digest"] != _record_digest(data):
|
|
330
|
-
raise ContinuationError("record digest does not match")
|
|
331
|
-
return data
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
def status(root: Path | None = None) -> dict[str, Any]:
|
|
335
|
-
root = (root or get_repo_root()).resolve()
|
|
336
|
-
try:
|
|
337
|
-
task = _task_source(root)
|
|
338
|
-
except NoDirectCurrentTask:
|
|
339
|
-
return {"status": STATUS_ABSENT, "reason": "no_direct_current_task"}
|
|
340
|
-
except ContinuationError as exc:
|
|
341
|
-
return {"status": STATUS_WITHHELD, "reason": str(exc)}
|
|
342
|
-
try:
|
|
343
|
-
path = _record_path(root, task["id"])
|
|
344
|
-
except ContinuationError as exc:
|
|
345
|
-
return {"status": STATUS_WITHHELD, "reason": str(exc)}
|
|
346
|
-
if not path.exists():
|
|
347
|
-
return {"status": STATUS_ABSENT, "task": {key: task[key] for key in ("id", "path", "status")}}
|
|
348
|
-
try:
|
|
349
|
-
record = _decode(path)
|
|
350
|
-
current = _source(root, task, _evidence(root, [item["path"] for item in record["source"]["evidence"]]))
|
|
351
|
-
state = STATUS_READY if record["source"] == current else STATUS_STALE
|
|
352
|
-
result = {"status": state, "task": record["task"], "revision": record["revision"], "record_digest": record["integrity"]["record_digest"]}
|
|
353
|
-
if state == STATUS_READY:
|
|
354
|
-
result["content"] = record["content"]
|
|
355
|
-
else:
|
|
356
|
-
result["reason"] = "source_drift"
|
|
357
|
-
return result
|
|
358
|
-
except (ContinuationError, OSError) as exc:
|
|
359
|
-
return {"status": STATUS_WITHHELD, "task": {key: task[key] for key in ("id", "path", "status")}, "reason": str(exc)}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
def seal(root: Path, request_path: Path, expected: str) -> dict[str, Any]:
|
|
363
|
-
expected = _expected_digest(expected)
|
|
364
|
-
request_path = _request_path(root, request_path)
|
|
365
|
-
initial_task = _task_source(root)
|
|
366
|
-
path = _record_path(root, initial_task["id"])
|
|
367
|
-
with _record_lock(path):
|
|
368
|
-
task = _task_source(root)
|
|
369
|
-
if (
|
|
370
|
-
(task["id"], task["path"], task["context_key"])
|
|
371
|
-
!= (initial_task["id"], initial_task["path"], initial_task["context_key"])
|
|
372
|
-
):
|
|
373
|
-
raise ContinuationError("current task changed while acquiring record lock")
|
|
374
|
-
current_digest: str | None = None
|
|
375
|
-
current: dict[str, Any] | None = None
|
|
376
|
-
if path.exists():
|
|
377
|
-
current = _decode(path)
|
|
378
|
-
current_digest = current["integrity"]["record_digest"]
|
|
379
|
-
current_source = _source(root, task, _evidence(root, [item["path"] for item in current["source"]["evidence"]]))
|
|
380
|
-
if current["source"] != current_source:
|
|
381
|
-
raise ContinuationError("existing continuation record source drifted")
|
|
382
|
-
if expected != "absent" and expected != current_digest:
|
|
383
|
-
raise ContinuationError("expected record digest does not match")
|
|
384
|
-
if expected == "absent" and current_digest is not None:
|
|
385
|
-
raise ContinuationError("record already exists")
|
|
386
|
-
content, evidence = _request(root, request_path)
|
|
387
|
-
source = _source(root, task, evidence)
|
|
388
|
-
current_task = _task_source(root)
|
|
389
|
-
if task != current_task or source != _source(root, current_task, _evidence(root, [item["path"] for item in evidence])):
|
|
390
|
-
raise ContinuationError("continuation source changed while sealing")
|
|
391
|
-
record: dict[str, Any] = {
|
|
392
|
-
"schema_version": SCHEMA_VERSION,
|
|
393
|
-
"kind": KIND,
|
|
394
|
-
"task": {key: task[key] for key in ("id", "path", "status", "material_digest")},
|
|
395
|
-
"source": source,
|
|
396
|
-
"content": content,
|
|
397
|
-
"revision": 1 if current is None else current["revision"] + 1,
|
|
398
|
-
"updated_at": datetime.now(timezone.utc).isoformat(timespec="microseconds").replace("+00:00", "Z"),
|
|
399
|
-
}
|
|
400
|
-
record["integrity"] = {"record_digest": _record_digest(record)}
|
|
401
|
-
encoded = json.dumps(record, ensure_ascii=True, indent=2, sort_keys=True) + "\n"
|
|
402
|
-
if len(encoded.encode("utf-8")) > MAX_RECORD_BYTES:
|
|
403
|
-
raise ContinuationError("continuation record is too large")
|
|
404
|
-
temporary: Path | None = None
|
|
405
|
-
try:
|
|
406
|
-
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle:
|
|
407
|
-
temporary = Path(handle.name)
|
|
408
|
-
handle.write(encoded)
|
|
409
|
-
handle.flush()
|
|
410
|
-
os.fsync(handle.fileno())
|
|
411
|
-
os.chmod(temporary, 0o600)
|
|
412
|
-
os.replace(temporary, path)
|
|
413
|
-
directory_fd = os.open(path.parent, os.O_RDONLY)
|
|
414
|
-
try:
|
|
415
|
-
os.fsync(directory_fd)
|
|
416
|
-
finally:
|
|
417
|
-
os.close(directory_fd)
|
|
418
|
-
except OSError as exc:
|
|
419
|
-
if temporary is not None:
|
|
420
|
-
temporary.unlink(missing_ok=True)
|
|
421
|
-
raise ContinuationError("continuation record atomic write failed") from exc
|
|
422
|
-
return {"status": STATUS_READY, "revision": record["revision"], "record_digest": record["integrity"]["record_digest"]}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
def clear(root: Path, expected: str) -> dict[str, Any]:
|
|
426
|
-
expected = _expected_digest(expected)
|
|
427
|
-
initial_task = _task_source(root)
|
|
428
|
-
path = _record_path(root, initial_task["id"])
|
|
429
|
-
with _record_lock(path):
|
|
430
|
-
task = _task_source(root)
|
|
431
|
-
if (
|
|
432
|
-
(task["id"], task["path"], task["context_key"])
|
|
433
|
-
!= (initial_task["id"], initial_task["path"], initial_task["context_key"])
|
|
434
|
-
):
|
|
435
|
-
raise ContinuationError("current task changed while acquiring record lock")
|
|
436
|
-
if not path.exists():
|
|
437
|
-
if expected == "absent":
|
|
438
|
-
return {"status": STATUS_ABSENT}
|
|
439
|
-
raise ContinuationError("record is absent")
|
|
440
|
-
record = _decode(path)
|
|
441
|
-
actual = record["integrity"]["record_digest"]
|
|
442
|
-
if actual != expected:
|
|
443
|
-
raise ContinuationError("expected record digest does not match")
|
|
444
|
-
path.unlink()
|
|
445
|
-
directory_fd = os.open(path.parent, os.O_RDONLY)
|
|
446
|
-
try:
|
|
447
|
-
os.fsync(directory_fd)
|
|
448
|
-
finally:
|
|
449
|
-
os.close(directory_fd)
|
|
450
|
-
return {"status": STATUS_ABSENT, "cleared": actual}
|