@mindfoldhq/trellis 0.6.8 → 0.6.9

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.
Files changed (90) hide show
  1. package/dist/cli/index.js +1 -0
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/commands/channel/agent-loader.d.ts +2 -2
  4. package/dist/commands/channel/agent-loader.d.ts.map +1 -1
  5. package/dist/commands/channel/agent-loader.js +9 -5
  6. package/dist/commands/channel/agent-loader.js.map +1 -1
  7. package/dist/commands/channel/context-loader.d.ts +1 -1
  8. package/dist/commands/channel/context-loader.d.ts.map +1 -1
  9. package/dist/commands/channel/context-loader.js +36 -15
  10. package/dist/commands/channel/context-loader.js.map +1 -1
  11. package/dist/commands/channel/context-trust.d.ts +31 -0
  12. package/dist/commands/channel/context-trust.d.ts.map +1 -0
  13. package/dist/commands/channel/context-trust.js +139 -0
  14. package/dist/commands/channel/context-trust.js.map +1 -0
  15. package/dist/commands/channel/spawn.d.ts.map +1 -1
  16. package/dist/commands/channel/spawn.js +4 -2
  17. package/dist/commands/channel/spawn.js.map +1 -1
  18. package/dist/commands/init.d.ts +1 -0
  19. package/dist/commands/init.d.ts.map +1 -1
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/update.d.ts.map +1 -1
  22. package/dist/commands/update.js +17 -0
  23. package/dist/commands/update.js.map +1 -1
  24. package/dist/configurators/codex.d.ts +36 -0
  25. package/dist/configurators/codex.d.ts.map +1 -1
  26. package/dist/configurators/codex.js +100 -1
  27. package/dist/configurators/codex.js.map +1 -1
  28. package/dist/configurators/index.d.ts.map +1 -1
  29. package/dist/configurators/index.js +5 -0
  30. package/dist/configurators/index.js.map +1 -1
  31. package/dist/configurators/shared.d.ts.map +1 -1
  32. package/dist/configurators/shared.js +1 -0
  33. package/dist/configurators/shared.js.map +1 -1
  34. package/dist/configurators/snow.d.ts +31 -0
  35. package/dist/configurators/snow.d.ts.map +1 -0
  36. package/dist/configurators/snow.js +95 -0
  37. package/dist/configurators/snow.js.map +1 -0
  38. package/dist/configurators/workflow.d.ts +14 -0
  39. package/dist/configurators/workflow.d.ts.map +1 -1
  40. package/dist/configurators/workflow.js +37 -1
  41. package/dist/configurators/workflow.js.map +1 -1
  42. package/dist/migrations/manifests/0.6.9.json +9 -0
  43. package/dist/templates/codex/agents/trellis-check.toml +2 -0
  44. package/dist/templates/codex/agents/trellis-implement.toml +2 -0
  45. package/dist/templates/codex/agents/trellis-research.toml +2 -0
  46. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +1 -1
  47. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +1 -1
  48. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +4 -2
  49. package/dist/templates/kimi/agents/trellis-research.md +7 -3
  50. package/dist/templates/omp/extensions/trellis/index.ts.txt +114 -4
  51. package/dist/templates/opencode/lib/trellis-context.js +263 -43
  52. package/dist/templates/opencode/plugins/inject-subagent-context.js +89 -35
  53. package/dist/templates/opencode/plugins/inject-workflow-state.js +90 -5
  54. package/dist/templates/pi/extensions/trellis/index.ts.txt +270 -22
  55. package/dist/templates/shared-hooks/inject-subagent-context.py +311 -83
  56. package/dist/templates/shared-hooks/inject-workflow-state.py +36 -1
  57. package/dist/templates/snow/SNOW.md +86 -0
  58. package/dist/templates/snow/agents/trellis-check.md +135 -0
  59. package/dist/templates/snow/agents/trellis-implement.md +153 -0
  60. package/dist/templates/snow/agents/trellis-research.md +114 -0
  61. package/dist/templates/snow/hooks/beforeSubAgentStart.json +15 -0
  62. package/dist/templates/snow/hooks/onSessionStart.json +15 -0
  63. package/dist/templates/snow/hooks/onUserMessage.json +15 -0
  64. package/dist/templates/snow/hooks/write-trellis-context.py +644 -0
  65. package/dist/templates/snow/index.d.ts +26 -0
  66. package/dist/templates/snow/index.d.ts.map +1 -0
  67. package/dist/templates/snow/index.js +36 -0
  68. package/dist/templates/snow/index.js.map +1 -0
  69. package/dist/templates/template-utils.d.ts.map +1 -1
  70. package/dist/templates/template-utils.js +5 -1
  71. package/dist/templates/template-utils.js.map +1 -1
  72. package/dist/templates/trellis/config.yaml +46 -0
  73. package/dist/templates/trellis/gitattributes.txt +9 -0
  74. package/dist/templates/trellis/index.d.ts +3 -1
  75. package/dist/templates/trellis/index.d.ts.map +1 -1
  76. package/dist/templates/trellis/index.js +3 -1
  77. package/dist/templates/trellis/index.js.map +1 -1
  78. package/dist/templates/trellis/scripts/add_session.py +94 -28
  79. package/dist/templates/trellis/scripts/common/active_task.py +4 -0
  80. package/dist/templates/trellis/scripts/common/config.py +89 -2
  81. package/dist/templates/trellis/scripts/common/task_context.py +82 -6
  82. package/dist/templates/trellis/scripts/common/task_store.py +63 -1
  83. package/dist/templates/trellis/scripts/common/trellis_config.py +2 -1
  84. package/dist/templates/trellis/scripts/task.py +21 -2
  85. package/dist/templates/trellis/workflow.md +14 -14
  86. package/dist/types/ai-tools.d.ts +3 -3
  87. package/dist/types/ai-tools.d.ts.map +1 -1
  88. package/dist/types/ai-tools.js +38 -0
  89. package/dist/types/ai-tools.js.map +1 -1
  90. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../src/configurators/workflow.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../src/configurators/workflow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "0.6.9",
3
+ "description": "Snow CLI platform, sub-agent context injection caps (Python/Pi/OpenCode) with binary-file handling, no-trellis skip keyword, durable Codex sub-agent model config, channel trusted context dirs, and journal merge conflict relief.",
4
+ "breaking": false,
5
+ "recommendMigrate": false,
6
+ "changelog": "**Enhancements:**\n- feat(cli): add Snow CLI as class-1 Trellis platform — `trellis init --snow` (#443)\n- feat(hooks): cap sub-agent context injection with configurable limits across the Python hook, Pi extension, and OpenCode plugin — `context_injection` in config.yaml (#441, #456)\n- fix(cli): sub-agent context injection now detects binary referenced files and emits a reference-only notice instead of corrupting output (#471)\n- feat(hooks): `no-trellis` keyword in a prompt mutes per-turn workflow-state injection for that turn — `prompt_injection.skip_keyword` in config.yaml (#427)\n- feat(codex): user-set `model` / `model_reasoning_effort` in `.codex/agents/trellis-*.toml` now survive `trellis update` regeneration; templates ship commented hints (#459)\n- feat(channel): `channel.trusted_context_dirs` config plus narrow auto-trust of symlinked `.trellis/tasks` / `.trellis/workspace` for context loading (#414)\n- feat(scripts): `add_session.py` gains repeatable `--change` / `--test` / `--next-step` flags; empty sections are omitted instead of rendering placeholder text (#394)\n- feat(scripts): `task.py list` renders a dangling parent reference flat instead of hiding the task (#402)\n- feat(scripts): `task.py create --meta key=value` (repeatable) and `task.py set-meta` expose the task.json `meta` field\n\n**Bug Fixes:**\n- fix(kimi): `trellis-research` now persists findings through the writable built-in `coder` sub-agent instead of losing them (#457)\n- feat(workspace): `.gitattributes` ships `journal-*.md merge=union` so parallel-worktree session merges no longer conflict on append-only journal content; `index.md` conflicts are documented as expected and safe to resolve by picking either side (#415)",
7
+ "migrations": [],
8
+ "notes": "Pi Agent users on 0.6.8: skills already moved to .agents/skills/. Codex users: agent model config now persists across update — edit .codex/agents/trellis-*.toml directly."
9
+ }
@@ -1,6 +1,8 @@
1
1
  name = "trellis-check"
2
2
  description = "Workspace-write Trellis reviewer that self-fixes spec drift, lint/type-check failures, and missing tests."
3
3
  sandbox_mode = "workspace-write"
4
+ # model = "gpt-5.6-terra"
5
+ # model_reasoning_effort = "high"
4
6
 
5
7
  developer_instructions = """
6
8
  You are running as the `trellis-check` sub-agent. The main session has dispatched you to review and self-fix.
@@ -1,6 +1,8 @@
1
1
  name = "trellis-implement"
2
2
  description = "Workspace-write Trellis implementer that follows specs and keeps generated templates in sync."
3
3
  sandbox_mode = "workspace-write"
4
+ # model = "gpt-5.6-terra"
5
+ # model_reasoning_effort = "high"
4
6
 
5
7
  developer_instructions = """
6
8
  You are running as the `trellis-implement` sub-agent. The main session has dispatched you to do the work.
@@ -1,6 +1,8 @@
1
1
  name = "trellis-research"
2
2
  description = "Trellis researcher for specs, code patterns, and affected files. Writes findings into {TASK_DIR}/research/ — read-only elsewhere."
3
3
  sandbox_mode = "workspace-write"
4
+ # model = "gpt-5.6-terra"
5
+ # model_reasoning_effort = "high"
4
6
 
5
7
  developer_instructions = """
6
8
  You are the Trellis researcher agent.
@@ -34,7 +34,7 @@ Agent files should not become generic chat prompts. They should define input sou
34
34
  | Pi Agent | `.pi/agents/trellis-*.md` |
35
35
  | Reasonix | `.reasonix/skills/trellis-*/SKILL.md` (subagent frontmatter) |
36
36
  | ZCode | `.zcode/agents/trellis-*.md` |
37
- | Kimi Code | `.kimi-code/skills/trellis-*/SKILL.md` (agent prompts as skills, dispatched to the built-in coder/explore/plan sub-agents) |
37
+ | Kimi Code | `.kimi-code/skills/trellis-*/SKILL.md` (agent prompts as skills, dispatched to the built-in `coder`; research needs its file-editing tools to persist findings) |
38
38
 
39
39
  GitHub Copilot agent/prompt support is provided by a combination of directories such as `.github/agents/`, `.github/prompts/`, and `.github/skills/`; inspect the files actually generated in the user project.
40
40
 
@@ -5,7 +5,7 @@ Trellis connects the same local architecture to different AI tools. `.trellis/`
5
5
  When a local AI modifies Trellis, it should distinguish two file categories first:
6
6
 
7
7
  - **Shared files**: `.trellis/workflow.md`, `.trellis/tasks/`, `.trellis/spec/`, `.trellis/scripts/`.
8
- - **Platform files**: `.claude/`, `.codex/`, `.cursor/`, `.opencode/`, `.kiro/`, `.gemini/`, `.qoder/`, `.codebuddy/`, `.github/`, `.factory/`, `.pi/`, `.trae/`, `.kilocode/`, `.agent/`, `.devin/`, `.reasonix/`, `.zcode/`, `.kimi-code/`, and similar directories.
8
+ - **Platform files**: `.claude/`, `.snow/`, `.codex/`, `.cursor/`, `.opencode/`, `.kiro/`, `.gemini/`, `.qoder/`, `.codebuddy/`, `.github/`, `.factory/`, `.pi/`, `.trae/`, `.kilocode/`, `.agent/`, `.devin/`, `.reasonix/`, `.zcode/`, `.kimi-code/`, and similar directories.
9
9
 
10
10
  Platform files do not store business state. They let the corresponding AI tool read Trellis state, call Trellis scripts, and load Trellis skills/agents/hooks.
11
11
 
@@ -24,7 +24,8 @@ This page lists common Trellis file locations in a user project by platform. Whe
24
24
  | Reasonix | `--reasonix` | `.reasonix/` | `.reasonix/skills/` | None — sub-agents are skills with `runAs: subagent` frontmatter | None |
25
25
  | ZCode | `--zcode` | `.zcode/` | `.zcode/skills/` | `.zcode/agents/` | `.zcode/hooks/` + `.zcode/config.json` (SessionStart + UserPromptSubmit + PreToolUse Agent/Task); sub-agents use hook-injected context |
26
26
  | Grok Build | `--grok` | `.grok/` | `.grok/skills/` | `.grok/agents/` | pull-based prelude (no hooks; flat `.grok/commands/trellis-*.md`) |
27
- | Kimi Code | `--kimi` | `.kimi-code/` | `.agents/skills/` (shared) + `.kimi-code/skills/` | None — agent prompts are skills under `.kimi-code/skills/` (built-in coder/explore/plan sub-agents only) | None (pull-based prelude; no project hooks/settings) |
27
+ | Kimi Code | `--kimi` | `.kimi-code/` | `.agents/skills/` (shared) + `.kimi-code/skills/` | None — agent prompts are skills under `.kimi-code/skills/` and dispatch to the built-in `coder` | None (pull-based prelude; no project hooks/settings) |
28
+ | Snow CLI | `--snow` | `.snow/` | `.snow/skills/` | `.snow/agents/` (auto-discovered; primary path) | class-1: auto inject + project agents + `beforeSubAgentStart` (`.snow/hooks/` `session`/`user`/`subagent` modes -> `additionalContext` JSON); no legacy sub-agent JSON; commands `.snow/commands/trellis-*.json` |
28
29
 
29
30
  ## Capability Groups
30
31
 
@@ -47,7 +48,8 @@ These platforms usually have `trellis-research`, `trellis-implement`, and `trell
47
48
  - Reasonix (delivered as skills with `runAs: subagent` under `.reasonix/skills/`, not as a separate `agents/` directory)
48
49
  - ZCode
49
50
  - Grok Build (`.grok/agents/`; dispatch via `spawn_subagent` with `subagent_type`)
50
- - Kimi Code (delivered as skills under `.kimi-code/skills/`; dispatched to the built-in coder/explore/plan sub-agents)
51
+ - Kimi Code (delivered as skills under `.kimi-code/skills/`; dispatched to the built-in `coder`, including research because it must persist files)
52
+ - Snow CLI (`.snow/agents/`; auto-discovered project agents + class-1 hooks)
51
53
 
52
54
  When changing implementation/check/research behavior, look for the corresponding platform agent files first.
53
55
 
@@ -4,8 +4,8 @@ description: |
4
4
  Code and tech search expert for Trellis. Finds files, patterns, and tech
5
5
  solutions, and PERSISTS every finding to the current task's research/
6
6
  directory. No code modifications outside that directory. On Kimi Code the
7
- main session dispatches the built-in explore sub-agent with these
8
- instructions.
7
+ main session dispatches the built-in coder sub-agent with these instructions
8
+ because it is the only built-in sub-agent with file-editing tools.
9
9
  ---
10
10
  # Research Agent
11
11
 
@@ -19,7 +19,11 @@ Conversations get compacted; files don't. Every research output MUST end up as a
19
19
 
20
20
  ## Dispatch note (main session)
21
21
 
22
- Kimi Code has no project-level custom sub-agent definitions — only the built-in `coder` / `explore` / `plan` sub-agents. The main session dispatches the built-in `explore` sub-agent via the Agent tool with a prompt that starts with `Active task: <path from task.py current>` and includes this skill's instructions (`.kimi-code/skills/trellis-research/SKILL.md`).
22
+ Kimi Code has no project-level custom sub-agent definitions — only the built-in `coder` / `explore` / `plan` sub-agents. The built-in `explore` sub-agent is read-only, so it cannot satisfy this agent's persistence contract. The main session dispatches the built-in `coder` sub-agent via the Agent tool with a prompt that:
23
+
24
+ 1. Starts with `Active task: <path from task.py current>`
25
+ 2. Includes this skill's instructions (`.kimi-code/skills/trellis-research/SKILL.md`)
26
+ 3. States that the spawned agent is already `trellis-research` and may write only under the active task's `research/` directory
23
27
 
24
28
  ---
25
29
 
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
2
- import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
2
+ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
3
3
  import { join, dirname, isAbsolute, relative, resolve } from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { createHash } from "node:crypto";
@@ -58,11 +58,118 @@ function isInsideRoot(root: string, candidate: string): boolean {
58
58
  return rel === "" || (rel !== ".." && !rel.startsWith("../") && !rel.startsWith("..\\") && !isAbsolute(rel));
59
59
  }
60
60
 
61
- function resolveProjectFile(projectRoot: string, file: string): string | null {
61
+ // ---------------------------------------------------------------------------
62
+ // Trusted context roots (mirrors packages/cli/src/commands/channel/context-trust.ts;
63
+ // standalone copy since templates don't import from the CLI package).
64
+ // ---------------------------------------------------------------------------
65
+
66
+ const AUTO_TRUST_ENTRIES = ["tasks", "workspace"];
67
+
68
+ function stripTrustValue(s: string): string {
69
+ return s.trim().replace(/\s*#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
70
+ }
71
+
72
+ function parseChannelTrustSection(content: string): { trustedDirs: string[]; autoTrustSymlinks?: boolean } {
73
+ const lines = content.split("\n");
74
+ const trustedDirs: string[] = [];
75
+ let autoTrustSymlinks: boolean | undefined;
76
+ let inChannel = false;
77
+ let inList = false;
78
+
79
+ for (const raw of lines) {
80
+ const line = raw.replace(/\r$/, "");
81
+ const trimmed = line.trimEnd();
82
+ if (trimmed.trim().startsWith("#")) continue;
83
+
84
+ if (/^channel:\s*$/.test(trimmed)) {
85
+ inChannel = true;
86
+ inList = false;
87
+ continue;
88
+ }
89
+ if (!inChannel) continue;
90
+
91
+ if (trimmed.trim() !== "" && /^\S/.test(line)) {
92
+ inChannel = false;
93
+ inList = false;
94
+ continue;
95
+ }
96
+ if (trimmed.trim() === "") continue;
97
+
98
+ if (inList) {
99
+ const item = trimmed.match(/^ {4}-\s*(.+)$/);
100
+ if (item) {
101
+ const val = stripTrustValue(item[1]!);
102
+ if (val) trustedDirs.push(val);
103
+ continue;
104
+ }
105
+ inList = false;
106
+ }
107
+
108
+ if (/^ {2}trusted_context_dirs:\s*$/.test(trimmed)) {
109
+ inList = true;
110
+ continue;
111
+ }
112
+
113
+ const boolMatch = trimmed.match(/^ {2}auto_trust_trellis_symlinks:\s*(.+)$/);
114
+ if (boolMatch) {
115
+ const val = stripTrustValue(boolMatch[1]!).toLowerCase();
116
+ if (val === "false") autoTrustSymlinks = false;
117
+ else if (val === "true") autoTrustSymlinks = true;
118
+ else process.stderr.write(`[channel] channel.auto_trust_trellis_symlinks: invalid value '${val}', ignoring\n`);
119
+ continue;
120
+ }
121
+ }
122
+
123
+ return { trustedDirs, autoTrustSymlinks };
124
+ }
125
+
126
+ function resolveTrustedRoots(projectRoot: string): string[] {
127
+ const configPath = join(projectRoot, ".trellis", "config.yaml");
128
+ let config: { trustedDirs: string[]; autoTrustSymlinks?: boolean } = { trustedDirs: [] };
129
+ if (existsSync(configPath)) {
130
+ try {
131
+ config = parseChannelTrustSection(readFileSync(configPath, "utf-8"));
132
+ } catch {
133
+ // ignore
134
+ }
135
+ }
136
+
137
+ const roots: string[] = [];
138
+ for (const entry of config.trustedDirs) {
139
+ try {
140
+ roots.push(realpathSync(resolve(projectRoot, entry)));
141
+ } catch {
142
+ // entry not found or invalid — skip
143
+ }
144
+ }
145
+
146
+ if (config.autoTrustSymlinks !== false) {
147
+ for (const entryName of AUTO_TRUST_ENTRIES) {
148
+ const entryPath = join(projectRoot, ".trellis", entryName);
149
+ try {
150
+ if (lstatSync(entryPath).isSymbolicLink()) {
151
+ roots.push(realpathSync(entryPath));
152
+ }
153
+ } catch {
154
+ // missing / broken symlink — nothing to trust
155
+ }
156
+ }
157
+ }
158
+
159
+ return [...new Set(roots)];
160
+ }
161
+
162
+ function resolveProjectFile(
163
+ projectRoot: string,
164
+ file: string,
165
+ trustedRoots: string[],
166
+ ): string | null {
62
167
  try {
63
168
  const rootReal = realpathSync(projectRoot);
64
169
  const targetReal = realpathSync(resolve(projectRoot, file));
65
- return isInsideRoot(rootReal, targetReal) ? targetReal : null;
170
+ if (isInsideRoot(rootReal, targetReal)) return targetReal;
171
+ if (trustedRoots.some((root) => isInsideRoot(root, targetReal))) return targetReal;
172
+ return null;
66
173
  } catch {
67
174
  return null;
68
175
  }
@@ -171,6 +278,9 @@ type AgentType = "trellis-implement" | "trellis-check" | "trellis-research" | nu
171
278
 
172
279
  function buildTaskContext(projectRoot: string, taskDir: string, agentType?: AgentType): string {
173
280
  const parts: string[] = [];
281
+ // Resolved once per call (not per referenced file) — avoids re-parsing
282
+ // config.yaml for every jsonl row.
283
+ const trustedRoots = resolveTrustedRoots(projectRoot);
174
284
 
175
285
  // prd.md and info.md — always included
176
286
  let prd = "";
@@ -212,7 +322,7 @@ function buildTaskContext(projectRoot: string, taskDir: string, agentType?: Agen
212
322
  const row = JSON.parse(trimmed) as Record<string, unknown>;
213
323
  const file = typeof row.file === "string" ? row.file.trim() : "";
214
324
  if (!file) continue;
215
- const targetPath = resolveProjectFile(projectRoot, file);
325
+ const targetPath = resolveProjectFile(projectRoot, file, trustedRoots);
216
326
  if (!targetPath) continue;
217
327
  let content = "";
218
328
  try { content = readFileSync(targetPath, "utf-8"); } catch { }
@@ -5,11 +5,12 @@
5
5
  * JSONL parsing, and context building capabilities.
6
6
  */
7
7
 
8
- import { existsSync, readFileSync, appendFileSync, readdirSync } from "fs"
8
+ import { existsSync, readFileSync, appendFileSync, readdirSync, statSync } from "fs"
9
9
  import { isAbsolute, join } from "path"
10
10
  import { platform } from "os"
11
11
  import { execSync } from "child_process"
12
12
  import { createHash } from "crypto"
13
+ import { Buffer, isUtf8 } from "buffer"
13
14
  import process from "process"
14
15
 
15
16
  const PYTHON_CMD = platform() === "win32" ? "python" : "python3"
@@ -78,6 +79,240 @@ export function isTrellisSubagent(input) {
78
79
  return TRELLIS_SUBAGENT_RE.test(agent)
79
80
  }
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
+
81
316
  /**
82
317
  * Trellis Context Manager
83
318
  */
@@ -282,44 +517,21 @@ export class TrellisContext {
282
517
  // JSONL Reading
283
518
  // ============================================================
284
519
 
285
- readDirectoryMdFiles(dirPath, maxFiles = 20) {
286
- const results = []
287
- const fullPath = join(this.directory, dirPath)
288
-
289
- if (!existsSync(fullPath)) {
290
- return results
291
- }
292
-
293
- try {
294
- const files = readdirSync(fullPath)
295
- .filter(f => f.endsWith(".md"))
296
- .sort()
297
- .slice(0, maxFiles)
298
-
299
- for (const filename of files) {
300
- const filePath = join(dirPath, filename)
301
- const content = this.readProjectFile(filePath)
302
- if (content) {
303
- results.push({ path: filePath, content })
304
- }
305
- }
306
- } catch {
307
- // Ignore directory read errors
308
- }
309
-
310
- return results
311
- }
312
-
313
520
  /**
314
- * Read a JSONL file and load referenced files/directories
521
+ * Read a JSONL file and materialize referenced files/directories into
522
+ * context blocks, applying per-file caps and the shared total budget
523
+ * (issue #441). Mirrors Python `_materialize_jsonl_entries`.
315
524
  * Supports:
316
525
  * {"file": "path/to/file.md", "reason": "..."}
317
526
  * {"file": "path/to/dir/", "type": "directory", "reason": "..."}
527
+ *
528
+ * Missing referenced files are skipped silently (Python `_materialize_file`
529
+ * returns None for them).
318
530
  */
319
- readJsonlWithFiles(jsonlPath) {
320
- const results = []
531
+ readJsonlWithFiles(jsonlPath, limits, budget) {
532
+ const blocks = []
321
533
  const content = this.readFile(jsonlPath)
322
- if (!content) return results
534
+ if (!content) return blocks
323
535
 
324
536
  for (const line of content.split("\n")) {
325
537
  if (!line.trim()) continue
@@ -327,28 +539,25 @@ export class TrellisContext {
327
539
  const item = JSON.parse(line)
328
540
  const file = item.file || item.path
329
541
  const entryType = item.type || "file"
542
+ const reason = item.reason || "-"
330
543
 
331
544
  if (!file) continue
332
545
 
333
546
  if (entryType === "directory") {
334
- const dirEntries = this.readDirectoryMdFiles(file)
335
- results.push(...dirEntries)
547
+ blocks.push(...materializeDirectory(this.directory, file, reason, limits, budget))
336
548
  } else {
337
- const fullPath = join(this.directory, file)
338
- const fileContent = this.readFile(fullPath)
339
- if (fileContent) {
340
- results.push({ path: file, content: fileContent })
341
- }
549
+ const block = materializeFile(this.directory, file, reason, limits, budget)
550
+ if (block) blocks.push(block)
342
551
  }
343
552
  } catch {
344
553
  // Ignore parse errors for individual lines
345
554
  }
346
555
  }
347
- return results
556
+ return blocks
348
557
  }
349
558
 
350
- buildContextFromEntries(entries) {
351
- return entries.map(e => `=== ${e.path} ===\n${e.content}`).join("\n\n")
559
+ buildContextFromEntries(blocks) {
560
+ return blocks.join("\n\n")
352
561
  }
353
562
  }
354
563
 
@@ -379,3 +588,14 @@ export const contextCollector = new ContextCollector()
379
588
 
380
589
  // Export debug log for plugins
381
590
  export { debugLog }
591
+
592
+ // Context injection limits (issue #441) — exported for plugins and tests
593
+ export {
594
+ DEFAULT_CONTEXT_INJECTION_LIMITS,
595
+ truncateUtf8,
596
+ readContextInjectionLimits,
597
+ ContextBudget,
598
+ materializeFile,
599
+ materializeDirectory,
600
+ materializeArtifact,
601
+ }