@blulotus/trellis 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli/index.js +2 -29
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +0 -22
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -12
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mem.d.ts +2 -2
- package/dist/commands/mem.d.ts.map +1 -1
- package/dist/commands/mem.js +7 -40
- package/dist/commands/mem.js.map +1 -1
- package/dist/commands/uninstall.d.ts +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +4 -34
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +2 -28
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +7 -73
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/shared.d.ts +18 -70
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +20 -299
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/workflow.d.ts +0 -14
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +1 -37
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/migrations/manifests/0.1.9.json +0 -12
- package/dist/migrations/manifests/0.2.14.json +0 -84
- package/dist/migrations/manifests/0.3.0-beta.0.json +0 -87
- package/dist/migrations/manifests/0.3.4.json +1 -13
- package/dist/migrations/manifests/0.4.0-beta.1.json +24 -176
- package/dist/migrations/manifests/0.5.0-beta.0.json +67 -1203
- package/dist/migrations/manifests/0.5.0-beta.15.json +0 -96
- package/dist/migrations/manifests/0.5.0-beta.5.json +0 -168
- package/dist/migrations/manifests/0.5.0-beta.9.json +0 -20
- package/dist/migrations/manifests/0.6.0-beta.23.json +0 -66
- package/dist/migrations/manifests/0.6.3.json +1 -16
- package/dist/migrations/manifests/0.6.6.json +1 -9
- package/dist/migrations/manifests/0.7.0.json +9 -0
- package/dist/migrations/manifests/0.7.1.json +9 -0
- package/dist/migrations/manifests/0.7.2.json +9 -0
- package/dist/migrations/manifests/0.8.0.json +9 -0
- package/dist/templates/codex/hooks/session-start.py +37 -13
- package/dist/templates/codex/hooks.json +5 -0
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +5 -5
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +0 -10
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +1 -13
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +2 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/bundled-skills.md +9 -28
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +5 -5
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +1 -17
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +4 -12
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +6 -6
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +5 -75
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +0 -16
- 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 +2 -3
- package/dist/templates/common/commands/continue.md +24 -4
- package/dist/templates/common/commands/finish-work.md +19 -9
- package/dist/templates/common/commands/start.md +38 -29
- package/dist/templates/extract.d.ts +0 -10
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +0 -24
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +7 -9
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +4 -8
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +5 -5
- package/dist/templates/shared-hooks/index.d.ts +11 -43
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +9 -86
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-spec-context.py +1 -1
- package/dist/templates/shared-hooks/inject-subagent-context.py +23 -85
- package/dist/templates/shared-hooks/inject-workflow-state.py +56 -86
- package/dist/templates/shared-hooks/session-start.py +76 -98
- package/dist/templates/trellis/config.yaml +8 -8
- package/dist/templates/trellis/index.d.ts +1 -4
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +1 -5
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +12 -11
- package/dist/templates/trellis/scripts/common/__init__.py +2 -0
- package/dist/templates/trellis/scripts/common/active_task.py +80 -152
- package/dist/templates/trellis/scripts/common/config.py +7 -7
- package/dist/templates/trellis/scripts/common/git_context.py +1 -1
- package/dist/templates/trellis/scripts/common/session_context.py +2 -2
- package/dist/templates/trellis/scripts/common/spec_inject.py +2 -2
- package/dist/templates/trellis/scripts/common/task_store.py +8 -28
- package/dist/templates/trellis/scripts/common/workflow_phase.py +4 -8
- package/dist/templates/trellis/scripts/get_context.py +2 -2
- package/dist/templates/trellis/scripts/init_developer.py +1 -1
- package/dist/templates/trellis/scripts/task.py +90 -57
- package/dist/templates/trellis/workflow.md +44 -66
- package/dist/types/ai-tools.d.ts +10 -10
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +3 -434
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/uninstall-scrubbers.d.ts +3 -18
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
- package/dist/utils/uninstall-scrubbers.js +24 -154
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/package.json +2 -2
- package/dist/configurators/antigravity.d.ts +0 -7
- package/dist/configurators/antigravity.d.ts.map +0 -1
- package/dist/configurators/antigravity.js +0 -11
- package/dist/configurators/antigravity.js.map +0 -1
- package/dist/configurators/codebuddy.d.ts +0 -10
- package/dist/configurators/codebuddy.d.ts.map +0 -1
- package/dist/configurators/codebuddy.js +0 -24
- package/dist/configurators/codebuddy.js.map +0 -1
- package/dist/configurators/copilot.d.ts +0 -14
- package/dist/configurators/copilot.d.ts.map +0 -1
- package/dist/configurators/copilot.js +0 -46
- package/dist/configurators/copilot.js.map +0 -1
- package/dist/configurators/cursor.d.ts +0 -10
- package/dist/configurators/cursor.d.ts.map +0 -1
- package/dist/configurators/cursor.js +0 -23
- package/dist/configurators/cursor.js.map +0 -1
- package/dist/configurators/devin.d.ts +0 -8
- package/dist/configurators/devin.d.ts.map +0 -1
- package/dist/configurators/devin.js +0 -12
- package/dist/configurators/devin.js.map +0 -1
- package/dist/configurators/droid.d.ts +0 -10
- package/dist/configurators/droid.d.ts.map +0 -1
- package/dist/configurators/droid.js +0 -24
- package/dist/configurators/droid.js.map +0 -1
- package/dist/configurators/dsh.d.ts +0 -26
- package/dist/configurators/dsh.d.ts.map +0 -1
- package/dist/configurators/dsh.js +0 -76
- package/dist/configurators/dsh.js.map +0 -1
- package/dist/configurators/gemini.d.ts +0 -18
- package/dist/configurators/gemini.d.ts.map +0 -1
- package/dist/configurators/gemini.js +0 -39
- package/dist/configurators/gemini.js.map +0 -1
- package/dist/configurators/grok.d.ts +0 -17
- package/dist/configurators/grok.d.ts.map +0 -1
- package/dist/configurators/grok.js +0 -38
- package/dist/configurators/grok.js.map +0 -1
- package/dist/configurators/kilo.d.ts +0 -7
- package/dist/configurators/kilo.d.ts.map +0 -1
- package/dist/configurators/kilo.js +0 -11
- package/dist/configurators/kilo.js.map +0 -1
- package/dist/configurators/kimi.d.ts +0 -26
- package/dist/configurators/kimi.d.ts.map +0 -1
- package/dist/configurators/kimi.js +0 -74
- package/dist/configurators/kimi.js.map +0 -1
- package/dist/configurators/kiro.d.ts +0 -11
- package/dist/configurators/kiro.d.ts.map +0 -1
- package/dist/configurators/kiro.js +0 -30
- package/dist/configurators/kiro.js.map +0 -1
- package/dist/configurators/omp.d.ts +0 -6
- package/dist/configurators/omp.d.ts.map +0 -1
- package/dist/configurators/omp.js +0 -28
- package/dist/configurators/omp.js.map +0 -1
- package/dist/configurators/opencode.d.ts +0 -5
- package/dist/configurators/opencode.d.ts.map +0 -1
- package/dist/configurators/opencode.js +0 -84
- package/dist/configurators/opencode.js.map +0 -1
- package/dist/configurators/pi.d.ts +0 -5
- package/dist/configurators/pi.d.ts.map +0 -1
- package/dist/configurators/pi.js +0 -39
- package/dist/configurators/pi.js.map +0 -1
- package/dist/configurators/qoder.d.ts +0 -12
- package/dist/configurators/qoder.d.ts.map +0 -1
- package/dist/configurators/qoder.js +0 -30
- package/dist/configurators/qoder.js.map +0 -1
- package/dist/configurators/reasonix.d.ts +0 -17
- package/dist/configurators/reasonix.d.ts.map +0 -1
- package/dist/configurators/reasonix.js +0 -37
- package/dist/configurators/reasonix.js.map +0 -1
- package/dist/configurators/snow.d.ts +0 -25
- package/dist/configurators/snow.d.ts.map +0 -1
- package/dist/configurators/snow.js +0 -69
- package/dist/configurators/snow.js.map +0 -1
- package/dist/configurators/trae.d.ts +0 -16
- package/dist/configurators/trae.d.ts.map +0 -1
- package/dist/configurators/trae.js +0 -34
- package/dist/configurators/trae.js.map +0 -1
- package/dist/configurators/zcode.d.ts +0 -23
- package/dist/configurators/zcode.d.ts.map +0 -1
- package/dist/configurators/zcode.js +0 -63
- package/dist/configurators/zcode.js.map +0 -1
- package/dist/templates/codebuddy/agents/trellis-check.md +0 -115
- package/dist/templates/codebuddy/agents/trellis-implement.md +0 -115
- package/dist/templates/codebuddy/agents/trellis-research.md +0 -137
- package/dist/templates/codebuddy/index.d.ts +0 -15
- package/dist/templates/codebuddy/index.d.ts.map +0 -1
- package/dist/templates/codebuddy/index.js +0 -15
- package/dist/templates/codebuddy/index.js.map +0 -1
- package/dist/templates/codebuddy/settings.json +0 -69
- package/dist/templates/copilot/copilot-instructions.md +0 -11
- package/dist/templates/copilot/hooks/session-start.py +0 -564
- package/dist/templates/copilot/hooks.json +0 -19
- package/dist/templates/copilot/index.d.ts +0 -28
- package/dist/templates/copilot/index.d.ts.map +0 -1
- package/dist/templates/copilot/index.js +0 -61
- package/dist/templates/copilot/index.js.map +0 -1
- package/dist/templates/copilot/prompts/before-dev.prompt.md +0 -39
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +0 -173
- package/dist/templates/copilot/prompts/break-loop.prompt.md +0 -129
- package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +0 -170
- package/dist/templates/copilot/prompts/check.prompt.md +0 -110
- package/dist/templates/copilot/prompts/create-command.prompt.md +0 -116
- package/dist/templates/copilot/prompts/finish-work.prompt.md +0 -99
- package/dist/templates/copilot/prompts/integrate-skill.prompt.md +0 -223
- package/dist/templates/copilot/prompts/onboard.prompt.md +0 -362
- package/dist/templates/copilot/prompts/parallel.prompt.md +0 -204
- package/dist/templates/copilot/prompts/record-session.prompt.md +0 -66
- package/dist/templates/copilot/prompts/start.prompt.md +0 -63
- package/dist/templates/copilot/prompts/update-spec.prompt.md +0 -358
- package/dist/templates/cursor/agents/trellis-check.md +0 -114
- package/dist/templates/cursor/agents/trellis-implement.md +0 -114
- package/dist/templates/cursor/agents/trellis-research.md +0 -136
- package/dist/templates/cursor/hooks.json +0 -24
- package/dist/templates/cursor/index.d.ts +0 -13
- package/dist/templates/cursor/index.d.ts.map +0 -1
- package/dist/templates/cursor/index.js +0 -13
- package/dist/templates/cursor/index.js.map +0 -1
- package/dist/templates/droid/droids/trellis-check.md +0 -107
- package/dist/templates/droid/droids/trellis-implement.md +0 -107
- package/dist/templates/droid/droids/trellis-research.md +0 -137
- package/dist/templates/droid/index.d.ts +0 -15
- package/dist/templates/droid/index.d.ts.map +0 -1
- package/dist/templates/droid/index.js +0 -15
- package/dist/templates/droid/index.js.map +0 -1
- package/dist/templates/droid/settings.json +0 -69
- package/dist/templates/dsh/DSH.md +0 -80
- package/dist/templates/dsh/agents/trellis-check.md +0 -99
- package/dist/templates/dsh/agents/trellis-implement.md +0 -106
- package/dist/templates/dsh/agents/trellis-research.md +0 -134
- package/dist/templates/dsh/index.d.ts +0 -22
- package/dist/templates/dsh/index.d.ts.map +0 -1
- package/dist/templates/dsh/index.js +0 -27
- package/dist/templates/dsh/index.js.map +0 -1
- package/dist/templates/gemini/agents/trellis-check.md +0 -107
- package/dist/templates/gemini/agents/trellis-implement.md +0 -107
- package/dist/templates/gemini/agents/trellis-research.md +0 -136
- package/dist/templates/gemini/index.d.ts +0 -13
- package/dist/templates/gemini/index.d.ts.map +0 -1
- package/dist/templates/gemini/index.js +0 -13
- package/dist/templates/gemini/index.js.map +0 -1
- package/dist/templates/gemini/settings.json +0 -40
- package/dist/templates/grok/agents/trellis-check.md +0 -106
- package/dist/templates/grok/agents/trellis-implement.md +0 -121
- package/dist/templates/grok/agents/trellis-research.md +0 -146
- package/dist/templates/grok/index.d.ts +0 -16
- package/dist/templates/grok/index.d.ts.map +0 -1
- package/dist/templates/grok/index.js +0 -19
- package/dist/templates/grok/index.js.map +0 -1
- package/dist/templates/kimi/agents/trellis-check.md +0 -104
- package/dist/templates/kimi/agents/trellis-implement.md +0 -120
- package/dist/templates/kimi/agents/trellis-research.md +0 -144
- package/dist/templates/kimi/index.d.ts +0 -20
- package/dist/templates/kimi/index.d.ts.map +0 -1
- package/dist/templates/kimi/index.js +0 -23
- package/dist/templates/kimi/index.js.map +0 -1
- package/dist/templates/kiro/agents/trellis-check.json +0 -26
- package/dist/templates/kiro/agents/trellis-implement.json +0 -26
- package/dist/templates/kiro/agents/trellis-research.json +0 -30
- package/dist/templates/kiro/agents/trellis.json +0 -34
- package/dist/templates/kiro/hooks/trellis-workflow-state.kiro.hook +0 -14
- package/dist/templates/kiro/index.d.ts +0 -32
- package/dist/templates/kiro/index.d.ts.map +0 -1
- package/dist/templates/kiro/index.js +0 -28
- package/dist/templates/kiro/index.js.map +0 -1
- package/dist/templates/omp/agents/trellis-check.md +0 -41
- package/dist/templates/omp/agents/trellis-implement.md +0 -55
- package/dist/templates/omp/agents/trellis-research.md +0 -30
- package/dist/templates/omp/extensions/trellis/index.ts.txt +0 -592
- package/dist/templates/omp/index.d.ts +0 -4
- package/dist/templates/omp/index.d.ts.map +0 -1
- package/dist/templates/omp/index.js +0 -9
- package/dist/templates/omp/index.js.map +0 -1
- package/dist/templates/opencode/agents/trellis-check.md +0 -122
- package/dist/templates/opencode/agents/trellis-implement.md +0 -123
- package/dist/templates/opencode/agents/trellis-research.md +0 -145
- package/dist/templates/opencode/lib/context-visibility.js +0 -86
- package/dist/templates/opencode/lib/session-utils.js +0 -526
- package/dist/templates/opencode/lib/trellis-context.js +0 -627
- package/dist/templates/opencode/package.json +0 -5
- package/dist/templates/opencode/plugins/inject-spec-context.js +0 -121
- package/dist/templates/opencode/plugins/inject-subagent-context.js +0 -569
- package/dist/templates/opencode/plugins/inject-workflow-state.js +0 -340
- package/dist/templates/opencode/plugins/session-start.js +0 -74
- package/dist/templates/pi/agents/trellis-check.md +0 -37
- package/dist/templates/pi/agents/trellis-implement.md +0 -52
- package/dist/templates/pi/agents/trellis-research.md +0 -25
- package/dist/templates/pi/extensions/trellis/index.ts.txt +0 -2033
- package/dist/templates/pi/index.d.ts +0 -5
- package/dist/templates/pi/index.d.ts.map +0 -1
- package/dist/templates/pi/index.js +0 -12
- package/dist/templates/pi/index.js.map +0 -1
- package/dist/templates/pi/settings.json +0 -9
- package/dist/templates/qoder/agents/trellis-check.md +0 -108
- package/dist/templates/qoder/agents/trellis-implement.md +0 -108
- package/dist/templates/qoder/agents/trellis-research.md +0 -137
- package/dist/templates/qoder/index.d.ts +0 -15
- package/dist/templates/qoder/index.d.ts.map +0 -1
- package/dist/templates/qoder/index.js +0 -15
- package/dist/templates/qoder/index.js.map +0 -1
- package/dist/templates/qoder/settings.json +0 -59
- package/dist/templates/reasonix/agents/trellis-check.md +0 -36
- package/dist/templates/reasonix/agents/trellis-implement.md +0 -51
- package/dist/templates/reasonix/index.d.ts +0 -13
- package/dist/templates/reasonix/index.d.ts.map +0 -1
- package/dist/templates/reasonix/index.js +0 -16
- package/dist/templates/reasonix/index.js.map +0 -1
- package/dist/templates/shared-hooks/inject-shell-session-context.py +0 -292
- package/dist/templates/snow/SNOW.md +0 -86
- package/dist/templates/snow/agents/trellis-check.md +0 -136
- package/dist/templates/snow/agents/trellis-implement.md +0 -158
- package/dist/templates/snow/agents/trellis-research.md +0 -114
- package/dist/templates/snow/hooks/beforeSubAgentStart.json +0 -15
- package/dist/templates/snow/hooks/onSessionStart.json +0 -15
- package/dist/templates/snow/hooks/onUserMessage.json +0 -15
- package/dist/templates/snow/hooks/write-trellis-context.py +0 -644
- package/dist/templates/snow/index.d.ts +0 -26
- package/dist/templates/snow/index.d.ts.map +0 -1
- package/dist/templates/snow/index.js +0 -36
- package/dist/templates/snow/index.js.map +0 -1
- package/dist/templates/trae/agents/trellis-check.md +0 -108
- package/dist/templates/trae/agents/trellis-implement.md +0 -108
- package/dist/templates/trae/agents/trellis-research.md +0 -137
- package/dist/templates/trae/hooks.json +0 -39
- package/dist/templates/trae/index.d.ts +0 -15
- package/dist/templates/trae/index.d.ts.map +0 -1
- package/dist/templates/trae/index.js +0 -15
- package/dist/templates/trae/index.js.map +0 -1
- package/dist/templates/trellis/gitattributes.txt +0 -9
- package/dist/templates/trellis/scripts/common/cli_adapter.py +0 -982
- package/dist/templates/zcode/agents/trellis-check.md +0 -110
- package/dist/templates/zcode/agents/trellis-implement.md +0 -116
- package/dist/templates/zcode/agents/trellis-research.md +0 -133
- package/dist/templates/zcode/config.json +0 -52
- package/dist/templates/zcode/index.d.ts +0 -25
- package/dist/templates/zcode/index.d.ts.map +0 -1
- package/dist/templates/zcode/index.js +0 -29
- package/dist/templates/zcode/index.js.map +0 -1
|
@@ -1,627 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Trellis Context Manager
|
|
3
|
-
*
|
|
4
|
-
* Utility class for OpenCode plugins providing file reading,
|
|
5
|
-
* JSONL parsing, and context building capabilities.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, readFileSync, appendFileSync, readdirSync, realpathSync, statSync } from "fs"
|
|
9
|
-
import { isAbsolute, join, relative } from "path"
|
|
10
|
-
import { platform } from "os"
|
|
11
|
-
import { execSync } from "child_process"
|
|
12
|
-
import { createHash } from "crypto"
|
|
13
|
-
import { Buffer, isUtf8 } from "buffer"
|
|
14
|
-
import process from "process"
|
|
15
|
-
|
|
16
|
-
const PYTHON_CMD = platform() === "win32" ? "python" : "python3"
|
|
17
|
-
// Debug logging
|
|
18
|
-
const DEBUG_LOG = "/tmp/trellis-plugin-debug.log"
|
|
19
|
-
|
|
20
|
-
function debugLog(prefix, ...args) {
|
|
21
|
-
const timestamp = new Date().toISOString()
|
|
22
|
-
const msg = `[${timestamp}] [${prefix}] ${args.map(a => typeof a === "object" ? JSON.stringify(a) : a).join(" ")}\n`
|
|
23
|
-
try {
|
|
24
|
-
appendFileSync(DEBUG_LOG, msg)
|
|
25
|
-
} catch {
|
|
26
|
-
// ignore
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function stringValue(value) {
|
|
31
|
-
return typeof value === "string" && value.trim() ? value.trim() : null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function sanitizeKey(raw) {
|
|
35
|
-
const safe = raw.trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^[._-]+|[._-]+$/g, "")
|
|
36
|
-
return safe ? safe.slice(0, 160) : ""
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function hashValue(raw) {
|
|
40
|
-
return createHash("sha256").update(raw).digest("hex").slice(0, 24)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function lookupString(data, keys) {
|
|
44
|
-
if (!data || typeof data !== "object") return null
|
|
45
|
-
for (const key of keys) {
|
|
46
|
-
const value = stringValue(data[key])
|
|
47
|
-
if (value) return value
|
|
48
|
-
}
|
|
49
|
-
for (const nestedKey of ["input", "properties", "event", "hook_input", "hookInput"]) {
|
|
50
|
-
const nested = data[nestedKey]
|
|
51
|
-
if (nested && typeof nested === "object") {
|
|
52
|
-
const value = lookupString(nested, keys)
|
|
53
|
-
if (value) return value
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return null
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function buildContextKey(platformName, kind, value) {
|
|
60
|
-
if (kind === "transcript") {
|
|
61
|
-
return `${platformName}_transcript_${hashValue(value)}`
|
|
62
|
-
}
|
|
63
|
-
const safeValue = sanitizeKey(value)
|
|
64
|
-
return safeValue ? `${platformName}_${safeValue}` : `${platformName}_${hashValue(value)}`
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Matches `trellis-implement`, `trellis-check`, `trellis-research` exactly.
|
|
68
|
-
// Used by chat.message plugins to skip injection inside Trellis sub-agent turns.
|
|
69
|
-
const TRELLIS_SUBAGENT_RE = /^trellis-(implement|check|research)$/
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Return true when the OpenCode `chat.message` input represents a Trellis
|
|
73
|
-
* sub-agent turn. `input.agent` is set by OpenCode when a Task tool spawns a
|
|
74
|
-
* child session with a custom agent (see `packages/opencode/src/tool/task.ts`).
|
|
75
|
-
*/
|
|
76
|
-
export function isTrellisSubagent(input) {
|
|
77
|
-
if (!input || typeof input !== "object") return false
|
|
78
|
-
const agent = typeof input.agent === "string" ? input.agent.trim() : ""
|
|
79
|
-
return TRELLIS_SUBAGENT_RE.test(agent)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ============================================================
|
|
83
|
-
// Context Injection Limits (issue #441)
|
|
84
|
-
//
|
|
85
|
-
// Notice text and behavior mirrored byte-for-byte from the shared-hooks
|
|
86
|
-
// Python sub-agent context injection hook and the Pi extension. Changing
|
|
87
|
-
// wording here requires changing it there too.
|
|
88
|
-
// ============================================================
|
|
89
|
-
|
|
90
|
-
const DEFAULT_CONTEXT_INJECTION_LIMITS = {
|
|
91
|
-
max_file_bytes: 32768,
|
|
92
|
-
max_artifact_bytes: 65536,
|
|
93
|
-
max_total_bytes: 131072,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Truncate `buf` to at most `cap` bytes without splitting a UTF-8
|
|
98
|
-
* multi-byte sequence. `cap <= 0` means "no limit".
|
|
99
|
-
*/
|
|
100
|
-
function truncateUtf8(buf, cap) {
|
|
101
|
-
if (cap <= 0 || buf.length <= cap) return buf
|
|
102
|
-
let i = cap
|
|
103
|
-
// Back off over continuation bytes (10xxxxxx) to find the lead byte.
|
|
104
|
-
while (i > 0 && (buf[i - 1] & 0xc0) === 0x80) i--
|
|
105
|
-
if (i === 0) return Buffer.alloc(0)
|
|
106
|
-
const lead = buf[i - 1]
|
|
107
|
-
if (lead & 0x80) {
|
|
108
|
-
let seqLen = 1
|
|
109
|
-
if ((lead & 0xe0) === 0xc0) seqLen = 2
|
|
110
|
-
else if ((lead & 0xf0) === 0xe0) seqLen = 3
|
|
111
|
-
else if ((lead & 0xf8) === 0xf0) seqLen = 4
|
|
112
|
-
// Drop the lead byte too if its full sequence didn't fit.
|
|
113
|
-
if (i - 1 + seqLen > cap) i--
|
|
114
|
-
}
|
|
115
|
-
return buf.subarray(0, i)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function stripInlineComment(value) {
|
|
119
|
-
let inQuote = null
|
|
120
|
-
for (let idx = 0; idx < value.length; idx++) {
|
|
121
|
-
const ch = value[idx]
|
|
122
|
-
if (inQuote) {
|
|
123
|
-
if (ch === inQuote) inQuote = null
|
|
124
|
-
continue
|
|
125
|
-
}
|
|
126
|
-
if (ch === '"' || ch === "'") {
|
|
127
|
-
inQuote = ch
|
|
128
|
-
continue
|
|
129
|
-
}
|
|
130
|
-
if (ch === "#" && (idx === 0 || /\s/.test(value[idx - 1]))) {
|
|
131
|
-
return value.slice(0, idx)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return value
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function unquoteYaml(s) {
|
|
138
|
-
if (s.length >= 2 && s[0] === s[s.length - 1] && (s[0] === '"' || s[0] === "'")) {
|
|
139
|
-
return s.slice(1, -1)
|
|
140
|
-
}
|
|
141
|
-
return s
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Line-based parser for ONLY the `context_injection:` block of
|
|
146
|
-
* `.trellis/config.yaml`. Not a general YAML parser — mirrors
|
|
147
|
-
* `common.config.get_context_injection_limits()` semantics for this
|
|
148
|
-
* section only (missing keys keep the default; invalid/negative values
|
|
149
|
-
* fall back to the default for that key with a debugLog warning).
|
|
150
|
-
*/
|
|
151
|
-
function readContextInjectionLimits(repoRoot) {
|
|
152
|
-
const limits = { ...DEFAULT_CONTEXT_INJECTION_LIMITS }
|
|
153
|
-
let text = null
|
|
154
|
-
try {
|
|
155
|
-
text = readFileSync(join(repoRoot, ".trellis", "config.yaml"), "utf-8")
|
|
156
|
-
} catch {
|
|
157
|
-
return limits
|
|
158
|
-
}
|
|
159
|
-
if (!text) return limits
|
|
160
|
-
|
|
161
|
-
let inSection = false
|
|
162
|
-
let sectionIndent = -1
|
|
163
|
-
for (const rawLine of text.split(/\r?\n/)) {
|
|
164
|
-
const trimmed = rawLine.trim()
|
|
165
|
-
if (!inSection) {
|
|
166
|
-
if (/^context_injection\s*:\s*(#.*)?$/.test(trimmed)) {
|
|
167
|
-
inSection = true
|
|
168
|
-
sectionIndent = rawLine.length - rawLine.trimStart().length
|
|
169
|
-
}
|
|
170
|
-
continue
|
|
171
|
-
}
|
|
172
|
-
if (!trimmed || trimmed.startsWith("#")) continue
|
|
173
|
-
const indent = rawLine.length - rawLine.trimStart().length
|
|
174
|
-
if (indent <= sectionIndent) break
|
|
175
|
-
const m = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$/)
|
|
176
|
-
if (!m) continue
|
|
177
|
-
const key = m[1]
|
|
178
|
-
if (!(key in limits)) continue
|
|
179
|
-
const raw = unquoteYaml(stripInlineComment(m[2]).trim()).trim()
|
|
180
|
-
if (!/^-?\d+$/.test(raw) || parseInt(raw, 10) < 0) {
|
|
181
|
-
// invalid/negative -> keep default (Python warns on stderr)
|
|
182
|
-
debugLog("context", `invalid context_injection.${key} value: ${raw}; using default ${limits[key]}`)
|
|
183
|
-
continue
|
|
184
|
-
}
|
|
185
|
-
limits[key] = parseInt(raw, 10)
|
|
186
|
-
}
|
|
187
|
-
return limits
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** Tracks the running total of bytes emitted into the sub-agent context. */
|
|
191
|
-
class ContextBudget {
|
|
192
|
-
constructor(maxTotalBytes) {
|
|
193
|
-
this.maxTotalBytes = maxTotalBytes
|
|
194
|
-
this.used = 0
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
hasRoom(size) {
|
|
198
|
-
if (this.maxTotalBytes <= 0) return true
|
|
199
|
-
return this.used + size <= this.maxTotalBytes
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
add(size) {
|
|
203
|
-
this.used += size
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function truncateNotice(path, cap) {
|
|
208
|
-
return `\n[Trellis: truncated at ${cap} bytes — read ${path} for the full content]`
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function isBinaryContent(data) {
|
|
212
|
-
return data.includes(0) || !isUtf8(data)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function binaryNotice(path, size, reason) {
|
|
216
|
-
return `[Trellis: not inlined (binary file) — ${path} (${size} bytes): ${reason}]`
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function indexNotice(path, size, reason) {
|
|
220
|
-
return `[Trellis: not inlined (total context limit reached) — ${path} (${size} bytes): ${reason}]`
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Return an inlined `=== header ===` block, or degrade to an index
|
|
225
|
-
* notice once the total context budget is exhausted.
|
|
226
|
-
*/
|
|
227
|
-
function budgetedBlock(budget, header, plainPath, content, reason, sizeForIndex) {
|
|
228
|
-
const block = `=== ${header} ===\n${content}`
|
|
229
|
-
const blockBytes = Buffer.byteLength(block, "utf-8")
|
|
230
|
-
if (!budget.hasRoom(blockBytes)) {
|
|
231
|
-
const notice = indexNotice(plainPath, sizeForIndex, reason)
|
|
232
|
-
budget.add(Buffer.byteLength(notice, "utf-8"))
|
|
233
|
-
return notice
|
|
234
|
-
}
|
|
235
|
-
budget.add(blockBytes)
|
|
236
|
-
return block
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/** Read raw file bytes, return null if file doesn't exist. */
|
|
240
|
-
function readFileBytes(basePath, filePath) {
|
|
241
|
-
const fullPath = isAbsolute(filePath) ? filePath : join(basePath, filePath)
|
|
242
|
-
try {
|
|
243
|
-
if (!statSync(fullPath).isFile()) return null
|
|
244
|
-
} catch {
|
|
245
|
-
return null
|
|
246
|
-
}
|
|
247
|
-
try {
|
|
248
|
-
return readFileSync(fullPath)
|
|
249
|
-
} catch {
|
|
250
|
-
return null
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/** Read a JSONL-referenced file, apply the per-file cap, then budget it. */
|
|
255
|
-
function materializeFile(basePath, filePath, reason, limits, budget) {
|
|
256
|
-
const data = readFileBytes(basePath, filePath)
|
|
257
|
-
if (data === null) return null
|
|
258
|
-
|
|
259
|
-
const size = data.length
|
|
260
|
-
if (isBinaryContent(data)) {
|
|
261
|
-
const notice = binaryNotice(filePath, size, reason)
|
|
262
|
-
budget.add(Buffer.byteLength(notice, "utf-8"))
|
|
263
|
-
return notice
|
|
264
|
-
}
|
|
265
|
-
const cap = limits.max_file_bytes
|
|
266
|
-
const truncated = truncateUtf8(data, cap)
|
|
267
|
-
let content = truncated.toString("utf-8")
|
|
268
|
-
if (truncated.length < size) content += truncateNotice(filePath, cap)
|
|
269
|
-
|
|
270
|
-
return budgetedBlock(budget, filePath, filePath, content, reason, size)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Read all .md files in a directory, applying the same per-file and
|
|
275
|
-
* total caps as a single-file JSONL entry.
|
|
276
|
-
*/
|
|
277
|
-
function materializeDirectory(basePath, dirPath, reason, limits, budget, maxFiles = 20) {
|
|
278
|
-
const blocks = []
|
|
279
|
-
const fullPath = isAbsolute(dirPath) ? dirPath : join(basePath, dirPath)
|
|
280
|
-
|
|
281
|
-
let files
|
|
282
|
-
try {
|
|
283
|
-
if (!statSync(fullPath).isDirectory()) return blocks
|
|
284
|
-
files = readdirSync(fullPath)
|
|
285
|
-
.filter(f => f.endsWith(".md") && statSync(join(fullPath, f)).isFile())
|
|
286
|
-
.sort()
|
|
287
|
-
} catch {
|
|
288
|
-
return blocks
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
for (const filename of files.slice(0, maxFiles)) {
|
|
292
|
-
const relativePath = join(dirPath, filename)
|
|
293
|
-
const block = materializeFile(basePath, relativePath, reason, limits, budget)
|
|
294
|
-
if (block) blocks.push(block)
|
|
295
|
-
}
|
|
296
|
-
return blocks
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Read a task artifact (prd/design/implement.md), apply the per-artifact
|
|
301
|
-
* cap, then budget it.
|
|
302
|
-
*/
|
|
303
|
-
function materializeArtifact(basePath, filePath, headerLabel, reason, limits, budget) {
|
|
304
|
-
const data = readFileBytes(basePath, filePath)
|
|
305
|
-
if (data === null) return null
|
|
306
|
-
|
|
307
|
-
const size = data.length
|
|
308
|
-
const cap = limits.max_artifact_bytes
|
|
309
|
-
const truncated = truncateUtf8(data, cap)
|
|
310
|
-
let content = truncated.toString("utf-8")
|
|
311
|
-
if (truncated.length < size) content += truncateNotice(filePath, cap)
|
|
312
|
-
|
|
313
|
-
return budgetedBlock(budget, headerLabel, filePath, content, reason, size)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Trellis Context Manager
|
|
318
|
-
*/
|
|
319
|
-
export class TrellisContext {
|
|
320
|
-
constructor(directory) {
|
|
321
|
-
this.directory = directory
|
|
322
|
-
debugLog("context", "TrellisContext initialized", { directory })
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// ============================================================
|
|
326
|
-
// Trellis Project Detection
|
|
327
|
-
// ============================================================
|
|
328
|
-
|
|
329
|
-
isTrellisProject() {
|
|
330
|
-
return existsSync(join(this.directory, ".trellis"))
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// OpenCode exports no session identity into the environment: no OPENCODE_*
|
|
334
|
-
// name in the 1.17.18 binary or the 1.18.13 source carries one. The plugin
|
|
335
|
-
// hook input is the only source, and the `export TRELLIS_CONTEXT_ID=` prefix
|
|
336
|
-
// this plugin adds to Bash commands is the only way that identity reaches a
|
|
337
|
-
// child process. TRELLIS_CONTEXT_ID is honored here so a nested Trellis run
|
|
338
|
-
// inherits its parent's key; do not add platform-native env names next to it
|
|
339
|
-
// without evidence a vendor sets them.
|
|
340
|
-
getContextKey(platformInput = null) {
|
|
341
|
-
const override = stringValue(process.env.TRELLIS_CONTEXT_ID)
|
|
342
|
-
if (override) {
|
|
343
|
-
return sanitizeKey(override) || hashValue(override)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const input = platformInput && typeof platformInput === "object" ? platformInput : null
|
|
347
|
-
if (!input) return null
|
|
348
|
-
|
|
349
|
-
const sessionID = lookupString(input, ["session_id", "sessionId", "sessionID"])
|
|
350
|
-
if (sessionID) return buildContextKey("opencode", "session", sessionID)
|
|
351
|
-
|
|
352
|
-
const conversationID = lookupString(input, ["conversation_id", "conversationId", "conversationID"])
|
|
353
|
-
if (conversationID) return buildContextKey("opencode", "conversation", conversationID)
|
|
354
|
-
|
|
355
|
-
const transcriptPath = lookupString(input, ["transcript_path", "transcriptPath", "transcript"])
|
|
356
|
-
if (transcriptPath) return buildContextKey("opencode", "transcript", transcriptPath)
|
|
357
|
-
|
|
358
|
-
return null
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
readContext(contextKey) {
|
|
362
|
-
try {
|
|
363
|
-
const contextPath = join(this.directory, ".trellis", ".runtime", "sessions", `${contextKey}.json`)
|
|
364
|
-
if (!existsSync(contextPath)) return null
|
|
365
|
-
return JSON.parse(readFileSync(contextPath, "utf-8"))
|
|
366
|
-
} catch {
|
|
367
|
-
return null
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Get active task from session runtime context.
|
|
373
|
-
*
|
|
374
|
-
* Resolution order (mirrors Python `active_task.resolve_active_task`):
|
|
375
|
-
* 1. Lookup the runtime file for the input-derived context key.
|
|
376
|
-
* 2. If that misses and exactly one session runtime file exists locally,
|
|
377
|
-
* use it (`_resolveSingleSessionFallback`). Refuses to guess when 0 or
|
|
378
|
-
* ≥2 files exist so multi-window isolation holds.
|
|
379
|
-
*/
|
|
380
|
-
getActiveTask(platformInput = null) {
|
|
381
|
-
const contextKey = this.getContextKey(platformInput)
|
|
382
|
-
if (contextKey) {
|
|
383
|
-
const context = this.readContext(contextKey)
|
|
384
|
-
const taskRef = this.normalizeTaskRef(context?.current_task || "")
|
|
385
|
-
if (taskRef) {
|
|
386
|
-
const taskDir = this.resolveTaskDir(taskRef)
|
|
387
|
-
return {
|
|
388
|
-
taskPath: taskRef,
|
|
389
|
-
source: `session:${contextKey}`,
|
|
390
|
-
stale: !taskDir || !existsSync(taskDir),
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const fallback = this._resolveSingleSessionFallback()
|
|
396
|
-
if (fallback) {
|
|
397
|
-
return fallback
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return { taskPath: null, source: "none", stale: false }
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Mirror of Python `_resolve_single_session_fallback`. Returns the task
|
|
405
|
-
* pointed at by the sole session runtime file when exactly one exists,
|
|
406
|
-
* else null.
|
|
407
|
-
*/
|
|
408
|
-
_resolveSingleSessionFallback() {
|
|
409
|
-
const sessionsDir = join(this.directory, ".trellis", ".runtime", "sessions")
|
|
410
|
-
if (!existsSync(sessionsDir)) return null
|
|
411
|
-
|
|
412
|
-
let files
|
|
413
|
-
try {
|
|
414
|
-
files = readdirSync(sessionsDir)
|
|
415
|
-
.filter(name => name.endsWith(".json"))
|
|
416
|
-
.sort()
|
|
417
|
-
} catch {
|
|
418
|
-
return null
|
|
419
|
-
}
|
|
420
|
-
if (files.length !== 1) return null
|
|
421
|
-
|
|
422
|
-
const sessionFile = join(sessionsDir, files[0])
|
|
423
|
-
let context
|
|
424
|
-
try {
|
|
425
|
-
context = JSON.parse(readFileSync(sessionFile, "utf-8"))
|
|
426
|
-
} catch {
|
|
427
|
-
return null
|
|
428
|
-
}
|
|
429
|
-
const taskRef = this.normalizeTaskRef(context?.current_task || "")
|
|
430
|
-
if (!taskRef) return null
|
|
431
|
-
|
|
432
|
-
const taskDir = this.resolveTaskDir(taskRef)
|
|
433
|
-
const fallbackKey = files[0].replace(/\.json$/, "")
|
|
434
|
-
return {
|
|
435
|
-
taskPath: taskRef,
|
|
436
|
-
source: `session-fallback:${fallbackKey}`,
|
|
437
|
-
stale: !taskDir || !existsSync(taskDir),
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
getCurrentTask(platformInput = null) {
|
|
442
|
-
return this.getActiveTask(platformInput).taskPath
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
normalizeTaskRef(taskRef) {
|
|
446
|
-
if (!taskRef) {
|
|
447
|
-
return ""
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (isAbsolute(taskRef)) {
|
|
451
|
-
return taskRef.trim()
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
let normalized = taskRef.trim().replace(/\\/g, "/")
|
|
455
|
-
while (normalized.startsWith("./")) {
|
|
456
|
-
normalized = normalized.slice(2)
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (normalized.startsWith("tasks/")) {
|
|
460
|
-
return `.trellis/${normalized}`
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return normalized
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Return `candidate` when it lands inside the project, else null.
|
|
468
|
-
*
|
|
469
|
-
* A task ref is not always something the user typed. `task.py` now refuses
|
|
470
|
-
* to store one that leaves the project, but a session file written before
|
|
471
|
-
* that fix can still hold one, and `trellis update` does not rewrite session
|
|
472
|
-
* files — so a poisoned pointer outlives the upgrade that closed the writer.
|
|
473
|
-
* Both sides are resolved so a task directory symlinked outside is refused
|
|
474
|
-
* too, but the original `candidate` is returned on success: callers build
|
|
475
|
-
* paths relative to `this.directory`, and handing them a realpath would break
|
|
476
|
-
* that whenever the project itself sits behind a symlink.
|
|
477
|
-
*/
|
|
478
|
-
containInProject(candidate) {
|
|
479
|
-
try {
|
|
480
|
-
const rel = relative(realpathSync(this.directory), realpathSync(candidate))
|
|
481
|
-
if (rel !== "" && (rel.startsWith("..") || isAbsolute(rel))) {
|
|
482
|
-
return null
|
|
483
|
-
}
|
|
484
|
-
return candidate
|
|
485
|
-
} catch {
|
|
486
|
-
return null
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
resolveTaskDir(taskRef) {
|
|
491
|
-
const normalized = this.normalizeTaskRef(taskRef)
|
|
492
|
-
if (!normalized) {
|
|
493
|
-
return null
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const candidate = isAbsolute(normalized)
|
|
497
|
-
? normalized
|
|
498
|
-
: normalized.startsWith(".trellis/")
|
|
499
|
-
? join(this.directory, normalized)
|
|
500
|
-
: join(this.directory, ".trellis", "tasks", normalized)
|
|
501
|
-
|
|
502
|
-
return this.containInProject(candidate)
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// ============================================================
|
|
506
|
-
// File Reading Utilities
|
|
507
|
-
// ============================================================
|
|
508
|
-
|
|
509
|
-
readFile(filePath) {
|
|
510
|
-
try {
|
|
511
|
-
if (existsSync(filePath)) {
|
|
512
|
-
return readFileSync(filePath, "utf-8")
|
|
513
|
-
}
|
|
514
|
-
} catch {
|
|
515
|
-
// Ignore read errors
|
|
516
|
-
}
|
|
517
|
-
return null
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
readProjectFile(relativePath) {
|
|
521
|
-
return this.readFile(join(this.directory, relativePath))
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
runScript(scriptPath, cwd = null, contextKey = null) {
|
|
525
|
-
try {
|
|
526
|
-
const result = execSync(`${PYTHON_CMD} "${scriptPath}"`, {
|
|
527
|
-
cwd: cwd || this.directory,
|
|
528
|
-
timeout: 10000,
|
|
529
|
-
encoding: "utf-8",
|
|
530
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
531
|
-
env: {
|
|
532
|
-
...process.env,
|
|
533
|
-
...(contextKey ? { TRELLIS_CONTEXT_ID: contextKey } : {}),
|
|
534
|
-
},
|
|
535
|
-
})
|
|
536
|
-
return result || ""
|
|
537
|
-
} catch {
|
|
538
|
-
return ""
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// ============================================================
|
|
543
|
-
// JSONL Reading
|
|
544
|
-
// ============================================================
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Read a JSONL file and materialize referenced files/directories into
|
|
548
|
-
* context blocks, applying per-file caps and the shared total budget
|
|
549
|
-
* (issue #441). Mirrors Python `_materialize_jsonl_entries`.
|
|
550
|
-
* Supports:
|
|
551
|
-
* {"file": "path/to/file.md", "reason": "..."}
|
|
552
|
-
* {"file": "path/to/dir/", "type": "directory", "reason": "..."}
|
|
553
|
-
*
|
|
554
|
-
* Missing referenced files are skipped silently (Python `_materialize_file`
|
|
555
|
-
* returns None for them).
|
|
556
|
-
*/
|
|
557
|
-
readJsonlWithFiles(jsonlPath, limits, budget) {
|
|
558
|
-
const blocks = []
|
|
559
|
-
const content = this.readFile(jsonlPath)
|
|
560
|
-
if (!content) return blocks
|
|
561
|
-
|
|
562
|
-
for (const line of content.split("\n")) {
|
|
563
|
-
if (!line.trim()) continue
|
|
564
|
-
try {
|
|
565
|
-
const item = JSON.parse(line)
|
|
566
|
-
const file = item.file || item.path
|
|
567
|
-
const entryType = item.type || "file"
|
|
568
|
-
const reason = item.reason || "-"
|
|
569
|
-
|
|
570
|
-
if (!file) continue
|
|
571
|
-
|
|
572
|
-
if (entryType === "directory") {
|
|
573
|
-
blocks.push(...materializeDirectory(this.directory, file, reason, limits, budget))
|
|
574
|
-
} else {
|
|
575
|
-
const block = materializeFile(this.directory, file, reason, limits, budget)
|
|
576
|
-
if (block) blocks.push(block)
|
|
577
|
-
}
|
|
578
|
-
} catch {
|
|
579
|
-
// Ignore parse errors for individual lines
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
return blocks
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
buildContextFromEntries(blocks) {
|
|
586
|
-
return blocks.join("\n\n")
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// ============================================================
|
|
591
|
-
// Context Collector (for session deduplication)
|
|
592
|
-
// ============================================================
|
|
593
|
-
|
|
594
|
-
class ContextCollector {
|
|
595
|
-
constructor() {
|
|
596
|
-
this.processed = new Set()
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
markProcessed(sessionID) {
|
|
600
|
-
this.processed.add(sessionID)
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
isProcessed(sessionID) {
|
|
604
|
-
return this.processed.has(sessionID)
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
clear(sessionID) {
|
|
608
|
-
this.processed.delete(sessionID)
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Singleton instance
|
|
613
|
-
export const contextCollector = new ContextCollector()
|
|
614
|
-
|
|
615
|
-
// Export debug log for plugins
|
|
616
|
-
export { debugLog }
|
|
617
|
-
|
|
618
|
-
// Context injection limits (issue #441) — exported for plugins and tests
|
|
619
|
-
export {
|
|
620
|
-
DEFAULT_CONTEXT_INJECTION_LIMITS,
|
|
621
|
-
truncateUtf8,
|
|
622
|
-
readContextInjectionLimits,
|
|
623
|
-
ContextBudget,
|
|
624
|
-
materializeFile,
|
|
625
|
-
materializeDirectory,
|
|
626
|
-
materializeArtifact,
|
|
627
|
-
}
|