chore: upgrade Trellis dogfood to 0.6.12 - #520
lifan-builds wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughThis PR adds Codex sub-agent context injection, expands platform detection and hook output handling, updates Pi and OpenCode context management, adds session-start workflows, and synchronizes platform documentation, workflow requirements, configuration, and template metadata. ChangesPlatform integration documentation
Hook and sub-agent context flow
Pi and OpenCode context lifecycle
Workflow and release metadata
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Session as SessionStart
participant Hook as Platform hook
participant Task as Trellis task context
participant Agent as Sub-agent
Session->>Hook: Start session and detect platform
Hook->>Task: Resolve active task and context
Task-->>Hook: Return bounded context
Hook-->>Agent: Inject session or sub-agent context
Agent->>Hook: Request workflow-state update
Hook-->>Agent: Return platform-specific output
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
.pi/extensions/trellis/index.ts (2)
1164-1172: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winDirectory entries in the curated JSONL produce no context on Pi.
Line 1168 skips every entry whose
typeisdirectory. The Python hook materializes those entries through_materialize_directory, which inlines up to 20.mdfiles under the referenced directory. A developer who curates a directory entry inimplement.jsonltherefore gets that context on Codex and Claude but not on Pi, with no notice.Either materialize directory entries here, or emit a notice so the divergence is visible.
♻️ Proposed change to inline directory entries
if (jsonlName) { for (const entry of readJsonlEntries(dir, jsonlName)) { - if (entry.type === "directory") continue; - const block = materializeFile(root, entry.file, entry.reason, limits, budget); - if (block) specBlocks.push(block); + if (entry.type === "directory") { + for (const block of materializeDirectory( + root, + entry.file, + entry.reason, + limits, + budget, + )) + specBlocks.push(block); + continue; + } + const block = materializeFile(root, entry.file, entry.reason, limits, budget); + if (block) specBlocks.push(block); } }Add the helper next to
materializeFile:function materializeDirectory( basePath: string, dirPath: string, reason: string, limits: ContextInjectionLimits, budget: ContextBudget, maxFiles = 20, ): string[] { const full = join(basePath, dirPath); const blocks: string[] = []; try { if (!statSync(full).isDirectory()) return blocks; const mdFiles = readdirSync(full) .filter((f) => f.endsWith(".md")) .sort() .slice(0, maxFiles); for (const filename of mdFiles) { const block = materializeFile( basePath, `${dirPath}/${filename}`, reason, limits, budget, ); if (block) blocks.push(block); } } catch {} return blocks; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.pi/extensions/trellis/index.ts around lines 1164 - 1172, Update the curated JSONL processing in the block that builds specBlocks so directory entries are materialized instead of skipped. Add a materializeDirectory helper alongside materializeFile that validates the referenced directory, selects up to 20 sorted Markdown files, and reuses materializeFile for each; append all returned blocks while preserving the existing file-entry behavior.
1674-1693: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winThe three new context caches grow without eviction.
startupCtxCache,taskCtxSnapshot,lastSentTaskCtx, andlastSentRuntimeCtxadd one entry per context key and never remove entries.taskCtxSnapshotandlastSentTaskCtxeach hold a full task-context string, which reachesmax_total_bytes(131072 bytes by default). Thesession_shutdownhandler at Line 1878 clearsnativeCardsbut leaves these maps in place, so a long-lived process that spans many context keys retains every snapshot.Clear the entries for a context key on
session_shutdown, or cap the maps.♻️ Proposed cleanup on session shutdown
pi.on?.("session_shutdown", () => { nativeCards.clear(); activeSubagentToolCallId = null; + startupCtxCache.clear(); + taskCtxSnapshot.clear(); + lastSentTaskCtx.clear(); + lastSentRuntimeCtx.clear(); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.pi/extensions/trellis/index.ts around lines 1674 - 1693, Update the session_shutdown handler to delete the matching context-key entries from startupCtxCache, taskCtxSnapshot, lastSentTaskCtx, and lastSentRuntimeCtx when a session ends. Preserve cleanup of nativeCards and ensure the key used matches the one used by getStartupCtx and the other context maps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
@.claude/skills/trellis-meta/references/customize-local/change-skills-or-commands.md:
- Line 90: Update the shared-layer platform list in the routing guidance to
include Pi Agent alongside Codex and Gemini CLI, matching the platform names
used in platform-map.md and the .agents/skills/ deployment entry.
In @.codex/hooks/inject-subagent-context.py:
- Around line 191-222: Update truncate_utf8 in
.codex/hooks/inject-subagent-context.py lines 191-222 to count removed trailing
continuation bytes and restore them when trailing + 1 equals the complete
sequence length, preserving characters that end exactly at the cap; otherwise
continue dropping an incomplete lead byte. Apply the identical logic to the
mirrored implementation in .pi/extensions/trellis/index.ts lines 763-779 so both
implementations remain aligned.
- Around line 1011-1012: Update the tool_name extraction in the PreToolUse
handling path to pass both host-provided values through the existing
_string_value helper before calling lower(). Preserve the current fallback
between tool_name and toolName, and keep the existing task/agent/subagent
matching behavior.
In @.cursor/hooks/session-start.py:
- Around line 198-200: Unify project-root resolution so both _detect_platform()
and main() use the same precedence, with ZCODE_PROJECT_DIR classified as zcode
before CLAUDE_PROJECT_DIR as claude and the remaining existing entries
preserved. Remove the conflicting direct CLAUDE_PROJECT_DIR-first resolution and
ensure platform classification does not override the shared root choice; add a
regression test using different ZCODE_PROJECT_DIR and CLAUDE_PROJECT_DIR values
to verify the ZCode root is selected.
In @.opencode/lib/trellis-context.js:
- Around line 260-264: Update the binary-content branch in materializeFile to
call budget.hasRoom() before adding and returning the binaryNotice result. If no
capacity remains, return the bounded-context fallback instead of emitting the
notice; preserve the existing budget.add behavior when room is available.
In @.opencode/plugins/inject-workflow-state.js:
- Around line 91-93: Update readSkipKeyword() to parse the YAML value and return
DEFAULT_PROMPT_INJECTION_SKIP_KEYWORD for null, booleans, numbers, and
collections; preserve only explicitly quoted empty strings as the disable value,
while continuing to return unquoted non-empty strings normally.
In @.pi/extensions/trellis/index.ts:
- Line 12: Update the Node engine floor for the Pi extension and its
corresponding package metadata from >=18.17.0 to >=19.4.0, ensuring the
declarations covering packages/cli and packages/core are consistent with the
isUtf8 dependency used by isBinaryContent.
In @.trellis/.template-hashes.json:
- Around line 5-14: Regenerate the manifest entries in
.trellis/.template-hashes.json from the current working files, including
restoring the missing .opencode/package.json entry, so every referenced template
has its current hash before merge.
---
Nitpick comments:
In @.pi/extensions/trellis/index.ts:
- Around line 1164-1172: Update the curated JSONL processing in the block that
builds specBlocks so directory entries are materialized instead of skipped. Add
a materializeDirectory helper alongside materializeFile that validates the
referenced directory, selects up to 20 sorted Markdown files, and reuses
materializeFile for each; append all returned blocks while preserving the
existing file-entry behavior.
- Around line 1674-1693: Update the session_shutdown handler to delete the
matching context-key entries from startupCtxCache, taskCtxSnapshot,
lastSentTaskCtx, and lastSentRuntimeCtx when a session ends. Preserve cleanup of
nativeCards and ensure the key used matches the one used by getStartupCtx and
the other context maps.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b9c06402-177a-44f3-b26c-74e407aeb819
📒 Files selected for processing (104)
.agents/skills/trellis-meta/references/customize-local/change-agents.md.agents/skills/trellis-meta/references/customize-local/change-hooks.md.agents/skills/trellis-meta/references/customize-local/change-skills-or-commands.md.agents/skills/trellis-meta/references/local-architecture/bundled-skills.md.agents/skills/trellis-meta/references/local-architecture/generated-files.md.agents/skills/trellis-meta/references/platform-files/agents.md.agents/skills/trellis-meta/references/platform-files/overview.md.agents/skills/trellis-meta/references/platform-files/platform-map.md.claude/hooks/inject-subagent-context.py.claude/hooks/inject-workflow-state.py.claude/hooks/session-start.py.claude/skills/trellis-brainstorm/SKILL.md.claude/skills/trellis-meta/references/customize-local/change-agents.md.claude/skills/trellis-meta/references/customize-local/change-hooks.md.claude/skills/trellis-meta/references/customize-local/change-skills-or-commands.md.claude/skills/trellis-meta/references/local-architecture/bundled-skills.md.claude/skills/trellis-meta/references/local-architecture/generated-files.md.claude/skills/trellis-meta/references/platform-files/agents.md.claude/skills/trellis-meta/references/platform-files/overview.md.claude/skills/trellis-meta/references/platform-files/platform-map.md.codex/agents/trellis-check.toml.codex/agents/trellis-implement.toml.codex/agents/trellis-research.toml.codex/hooks.json.codex/hooks/inject-subagent-context.py.codex/hooks/inject-workflow-state.py.codex/hooks/session-start.py.cursor/hooks/inject-subagent-context.py.cursor/hooks/session-start.py.cursor/skills/trellis-brainstorm/SKILL.md.cursor/skills/trellis-meta/references/customize-local/change-agents.md.cursor/skills/trellis-meta/references/customize-local/change-hooks.md.cursor/skills/trellis-meta/references/customize-local/change-skills-or-commands.md.cursor/skills/trellis-meta/references/local-architecture/bundled-skills.md.cursor/skills/trellis-meta/references/local-architecture/generated-files.md.cursor/skills/trellis-meta/references/platform-files/agents.md.cursor/skills/trellis-meta/references/platform-files/overview.md.cursor/skills/trellis-meta/references/platform-files/platform-map.md.opencode/commands/trellis/start.md.opencode/lib/session-utils.js.opencode/lib/trellis-context.js.opencode/plugins/inject-workflow-state.js.opencode/skills/trellis-brainstorm/SKILL.md.opencode/skills/trellis-meta/SKILL.md.opencode/skills/trellis-meta/references/customize-local/change-agents.md.opencode/skills/trellis-meta/references/customize-local/change-hooks.md.opencode/skills/trellis-meta/references/customize-local/change-skills-or-commands.md.opencode/skills/trellis-meta/references/local-architecture/bundled-skills.md.opencode/skills/trellis-meta/references/local-architecture/generated-files.md.opencode/skills/trellis-meta/references/platform-files/agents.md.opencode/skills/trellis-meta/references/platform-files/hooks-and-settings.md.opencode/skills/trellis-meta/references/platform-files/overview.md.opencode/skills/trellis-meta/references/platform-files/platform-map.md.opencode/skills/trellis-meta/references/platform-files/skills-and-commands.md.pi/extensions/trellis/index.ts.pi/prompts/trellis-start.md.pi/settings.json.pi/skills/trellis-before-dev/SKILL.md.pi/skills/trellis-brainstorm/SKILL.md.pi/skills/trellis-break-loop/SKILL.md.pi/skills/trellis-channel/SKILL.md.pi/skills/trellis-channel/references/command-reference.md.pi/skills/trellis-channel/references/forum.md.pi/skills/trellis-channel/references/progress-debugging.md.pi/skills/trellis-channel/references/workers.md.pi/skills/trellis-channel/references/workflows.md.pi/skills/trellis-check/SKILL.md.pi/skills/trellis-meta/SKILL.md.pi/skills/trellis-meta/references/customize-local/add-project-local-conventions.md.pi/skills/trellis-meta/references/customize-local/change-agents.md.pi/skills/trellis-meta/references/customize-local/change-context-loading.md.pi/skills/trellis-meta/references/customize-local/change-hooks.md.pi/skills/trellis-meta/references/customize-local/change-skills-or-commands.md.pi/skills/trellis-meta/references/customize-local/change-spec-structure.md.pi/skills/trellis-meta/references/customize-local/change-task-lifecycle.md.pi/skills/trellis-meta/references/customize-local/change-workflow.md.pi/skills/trellis-meta/references/customize-local/overview.md.pi/skills/trellis-meta/references/local-architecture/bundled-skills.md.pi/skills/trellis-meta/references/local-architecture/context-injection.md.pi/skills/trellis-meta/references/local-architecture/generated-files.md.pi/skills/trellis-meta/references/local-architecture/multi-agent-channel.md.pi/skills/trellis-meta/references/local-architecture/overview.md.pi/skills/trellis-meta/references/local-architecture/spec-system.md.pi/skills/trellis-meta/references/local-architecture/task-system.md.pi/skills/trellis-meta/references/local-architecture/workflow.md.pi/skills/trellis-meta/references/local-architecture/workspace-memory.md.pi/skills/trellis-meta/references/platform-files/agents.md.pi/skills/trellis-meta/references/platform-files/hooks-and-settings.md.pi/skills/trellis-meta/references/platform-files/overview.md.pi/skills/trellis-meta/references/platform-files/platform-map.md.pi/skills/trellis-meta/references/platform-files/skills-and-commands.md.pi/skills/trellis-session-insight/SKILL.md.pi/skills/trellis-session-insight/references/cli-quick-reference.md.pi/skills/trellis-session-insight/references/triggering-patterns.md.pi/skills/trellis-spec-bootstrap/SKILL.md.pi/skills/trellis-spec-bootstrap/references/mcp-setup.md.pi/skills/trellis-spec-bootstrap/references/repository-analysis.md.pi/skills/trellis-spec-bootstrap/references/spec-task-planning.md.pi/skills/trellis-spec-bootstrap/references/spec-writing.md.pi/skills/trellis-update-spec/SKILL.md.trellis/.template-hashes.json.trellis/.version.trellis/config.yaml.trellis/scripts/common/session_context.py
💤 Files with no reviewable changes (44)
- .pi/skills/trellis-session-insight/references/cli-quick-reference.md
- .pi/skills/trellis-meta/references/local-architecture/generated-files.md
- .pi/skills/trellis-meta/references/local-architecture/task-system.md
- .pi/skills/trellis-meta/references/customize-local/change-hooks.md
- .pi/skills/trellis-channel/references/progress-debugging.md
- .pi/skills/trellis-before-dev/SKILL.md
- .pi/skills/trellis-meta/references/customize-local/overview.md
- .pi/skills/trellis-meta/references/customize-local/change-agents.md
- .pi/skills/trellis-session-insight/SKILL.md
- .pi/skills/trellis-meta/references/local-architecture/context-injection.md
- .pi/skills/trellis-spec-bootstrap/references/repository-analysis.md
- .pi/settings.json
- .pi/skills/trellis-meta/references/platform-files/skills-and-commands.md
- .pi/skills/trellis-brainstorm/SKILL.md
- .pi/skills/trellis-meta/references/customize-local/add-project-local-conventions.md
- .pi/skills/trellis-session-insight/references/triggering-patterns.md
- .pi/skills/trellis-break-loop/SKILL.md
- .pi/skills/trellis-channel/references/workers.md
- .pi/skills/trellis-check/SKILL.md
- .pi/skills/trellis-update-spec/SKILL.md
- .pi/skills/trellis-meta/references/customize-local/change-workflow.md
- .pi/skills/trellis-spec-bootstrap/references/spec-writing.md
- .pi/skills/trellis-meta/references/platform-files/platform-map.md
- .pi/skills/trellis-meta/references/customize-local/change-task-lifecycle.md
- .pi/skills/trellis-meta/references/platform-files/overview.md
- .pi/skills/trellis-meta/references/local-architecture/spec-system.md
- .pi/skills/trellis-channel/SKILL.md
- .pi/skills/trellis-channel/references/workflows.md
- .pi/skills/trellis-meta/references/local-architecture/workspace-memory.md
- .pi/skills/trellis-meta/references/platform-files/agents.md
- .pi/skills/trellis-meta/references/local-architecture/overview.md
- .pi/skills/trellis-meta/references/customize-local/change-skills-or-commands.md
- .pi/skills/trellis-meta/references/platform-files/hooks-and-settings.md
- .pi/skills/trellis-channel/references/command-reference.md
- .pi/skills/trellis-meta/references/local-architecture/workflow.md
- .pi/skills/trellis-meta/SKILL.md
- .pi/skills/trellis-spec-bootstrap/references/mcp-setup.md
- .pi/skills/trellis-meta/references/customize-local/change-spec-structure.md
- .pi/skills/trellis-spec-bootstrap/SKILL.md
- .pi/skills/trellis-meta/references/local-architecture/bundled-skills.md
- .pi/skills/trellis-spec-bootstrap/references/spec-task-planning.md
- .pi/skills/trellis-meta/references/customize-local/change-context-loading.md
- .pi/skills/trellis-meta/references/local-architecture/multi-agent-channel.md
- .pi/skills/trellis-channel/references/forum.md
| | GitHub Copilot | `.github/skills/`, `.github/prompts/` | | ||
| | Factory Droid | `.factory/skills/`, `.factory/commands/` | | ||
| | Pi Agent | `.pi/skills/` | | ||
| | Pi Agent | `.agents/skills/` | |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the shared-layer platform list to include Pi Agent.
Line 90 now deploys Pi Agent skills to .agents/skills/. Line 106 still names only Codex and Gemini CLI as shared-layer platforms. platform-map.md line 76 names Codex, Gemini CLI, Pi Agent, and Kimi Code. Align line 106 so routing guidance stays correct.
📝 Proposed documentation fix
-For multi-platform projects, add equivalent versions in each platform skill directory, or use `.agents/skills/` on platforms that support the shared layer (Codex, Gemini CLI).
+For multi-platform projects, add equivalent versions in each platform skill directory, or use `.agents/skills/` on platforms that support the shared layer (Codex, Gemini CLI, Pi Agent, Kimi Code).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
@.claude/skills/trellis-meta/references/customize-local/change-skills-or-commands.md
at line 90, Update the shared-layer platform list in the routing guidance to
include Pi Agent alongside Codex and Gemini CLI, matching the platform names
used in platform-map.md and the .agents/skills/ deployment entry.
| def truncate_utf8(data: bytes, cap: int) -> bytes: | ||
| """Truncate ``data`` to at most ``cap`` bytes without splitting a UTF-8 | ||
| multi-byte sequence. | ||
|
|
||
| ``cap <= 0`` means "no limit" — returns ``data`` unchanged. | ||
| """ | ||
| if cap <= 0 or len(data) <= cap: | ||
| return data | ||
|
|
||
| truncated = data[:cap] | ||
| i = len(truncated) | ||
| # Back off over continuation bytes (10xxxxxx) to find the lead byte. | ||
| while i > 0 and (truncated[i - 1] & 0xC0) == 0x80: | ||
| i -= 1 | ||
| if i == 0: | ||
| return b"" | ||
|
|
||
| lead = truncated[i - 1] | ||
| if lead & 0x80: | ||
| if (lead & 0xE0) == 0xC0: | ||
| seq_len = 2 | ||
| elif (lead & 0xF0) == 0xE0: | ||
| seq_len = 3 | ||
| elif (lead & 0xF8) == 0xF0: | ||
| seq_len = 4 | ||
| else: | ||
| seq_len = 1 | ||
| # Drop the lead byte too if its full sequence didn't fit. | ||
| if (i - 1) + seq_len > len(truncated): | ||
| i -= 1 | ||
|
|
||
| return truncated[:i] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
UTF-8 truncation drops a complete character at an exact cap boundary in both mirrored implementations. Each version strips every trailing continuation byte before it checks whether the sequence is complete. When a multi-byte character ends exactly at cap, the index lands on the byte after the lead byte, the completeness check compares 0 + seq_len > cap and evaluates false, and only the lead byte survives. The decode step then emits a replacement character into the injected context. Track how many continuation bytes you removed and restore them when the sequence is complete.
.codex/hooks/inject-subagent-context.py#L191-L222: count the continuation bytes removed by the loop at Line 203, then restore them instead of dropping the lead byte whentrailing + 1 == seq_len..pi/extensions/trellis/index.ts#L763-L779: apply the same change to the loop at Line 767 and the check at Line 776 so the two implementations stay byte-for-byte aligned, as the header comment at Lines 749-751 requires.
📍 Affects 2 files
.codex/hooks/inject-subagent-context.py#L191-L222(this comment).pi/extensions/trellis/index.ts#L763-L779
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.codex/hooks/inject-subagent-context.py around lines 191 - 222, Update
truncate_utf8 in .codex/hooks/inject-subagent-context.py lines 191-222 to count
removed trailing continuation bytes and restore them when trailing + 1 equals
the complete sequence length, preserving characters that end exactly at the cap;
otherwise continue dropping an incomplete lead byte. Apply the identical logic
to the mirrored implementation in .pi/extensions/trellis/index.ts lines 763-779
so both implementations remain aligned.
| tool_name = input_data.get("tool_name", "") or input_data.get("toolName", "") | ||
| if tool_name.lower() in ("task", "agent", "subagent"): |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Coerce tool_name before calling .lower().
Line 1011 reads tool_name straight from host JSON. If a host sends a non-string value, Line 1012 raises AttributeError. The PreToolUse path in main has no top-level guard, so the hook exits non-zero and emits no JSON. The rest of this file already routes host fields through _string_value.
🛡️ Proposed fix
- tool_name = input_data.get("tool_name", "") or input_data.get("toolName", "")
+ tool_name = _string_value(input_data.get("tool_name")) or _string_value(
+ input_data.get("toolName")
+ )
if tool_name.lower() in ("task", "agent", "subagent"):📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| tool_name = input_data.get("tool_name", "") or input_data.get("toolName", "") | |
| if tool_name.lower() in ("task", "agent", "subagent"): | |
| tool_name = _string_value(input_data.get("tool_name")) or _string_value( | |
| input_data.get("toolName") | |
| ) | |
| if tool_name.lower() in ("task", "agent", "subagent"): |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.codex/hooks/inject-subagent-context.py around lines 1011 - 1012, Update the
tool_name extraction in the PreToolUse handling path to pass both host-provided
values through the existing _string_value helper before calling lower().
Preserve the current fallback between tool_name and toolName, and keep the
existing task/agent/subagent matching behavior.
| # ZCode may set both ZCODE_PROJECT_DIR and CLAUDE_PROJECT_DIR; check | ||
| # ZCODE first so ZCode sessions aren't misdetected as claude. | ||
| "ZCODE_PROJECT_DIR": "zcode", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file exists and line counts =="
wc -l .cursor/hooks/session-start.py || true
echo "== relevant source sections =="
sed -n '160,220p' .cursor/hooks/session-start.py
printf '\n---\n'
sed -n '730,775p' .cursor/hooks/session-start.py
echo "== tests mentioning ZCODE/CLAUDE roots =="
git ls-files | xargs rg -n "ZCODE_PROJECT_DIR|CLAUDE_PROJECT_DIR|detect_platform|PROJECT_ROOT|trellis" || true
echo "== git diff stat/name status =="
git diff --stat HEAD || true
git diff -- .cursor/hooks/session-start.py | sed -n '1,220p' || trueRepository: mindfold-ai/Trellis
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant project-start.py sections =="
sed -n '168,210p' .cursor/hooks/session-start.py
printf '\n---\n'
sed -n '725,765p' .cursor/hooks/session-start.py
echo "== focused project-root references in file =="
rg -n "PROJECT_ROOT|root|ZCODE_PROJECT_DIR|CLAUDE_PROJECT_DIR|\.trellis" .cursor/hooks/session-start.py || true
echo "== tests/repo references exactly for ZCODE/CLAUDE env and diff roots =="
rg -n "ZCODE_PROJECT_DIR|CLAUDE_PROJECT_DIR|PROJECT_ROOT" --glob '!packages/**' --glob '!docs/**' --glob '!src/**' . || trueRepository: mindfold-ai/Trellis
Length of output: 5478
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== platform detection exact block =="
sed -n '190,218p' .cursor/hooks/session-start.py
echo "== project root selection exact block =="
sed -n '745,775p' .cursor/hooks/session-start.py
echo "== behavioral probe of precedence with different roots =="
python3 - <<'PY'
from pathlib import Path
env_map = {
"ZCODE_PROJECT_DIR": "zcode",
"CLAUDE_PROJECT_DIR": "claude",
}
project_dir_env_vars = [
"CLAUDE_PROJECT_DIR",
"ZCODE_PROJECT_DIR",
]
os = type("os", (), {"environ": {"ZCODE_PROJECT_DIR": "z_root", "CLAUDE_PROJECT_DIR": "c_root"}})
def current_detect_platform(input_data):
for var, platform in env_map.items():
if os.environ.get(var):
return platform
return None
def current_resolve_project_directory():
for var in project_dir_env_vars:
val = os.environ.get(var)
if val:
return Path(val).resolve()
print("detect:", current_detect_platform({}))
print("project_dir:", current_resolve_project_directory())
print("trellis_dir:", current_resolve_project_directory() / ".trellis")
print("repo_root_from_project_dir:", current_resolve_project_directory())
def intended_detect_platform(input_data):
for var, platform in project_dir_env_vars:
if var in env_map and os.environ.get(var):
return env_map[var]
print("intended_detect:", intended_detect_platform({}))
PYRepository: mindfold-ai/Trellis
Length of output: 2837
Use one shared project-root precedence for ZCode root resolution.
_detect_platform() prefers ZCODE_PROJECT_DIR, but main() resolves CLAUDE_PROJECT_DIR first and also classifies by hook path before calling that function. With both variables set to different roots, ZCode can read .trellis/... from the Claude root. Extract a shared precedence list with {ZCODE_PROJECT_DIR: "zcode", CLAUDE_PROJECT_DIR: "claude", ...}, and add a regression test that feeds different ZCODE_PROJECT_DIR and CLAUDE_PROJECT_DIR values.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.cursor/hooks/session-start.py around lines 198 - 200, Unify project-root
resolution so both _detect_platform() and main() use the same precedence, with
ZCODE_PROJECT_DIR classified as zcode before CLAUDE_PROJECT_DIR as claude and
the remaining existing entries preserved. Remove the conflicting direct
CLAUDE_PROJECT_DIR-first resolution and ensure platform classification does not
override the shared root choice; add a regression test using different
ZCODE_PROJECT_DIR and CLAUDE_PROJECT_DIR values to verify the ZCode root is
selected.
| if (isBinaryContent(data)) { | ||
| const notice = binaryNotice(filePath, size, reason) | ||
| budget.add(Buffer.byteLength(notice, "utf-8")) | ||
| return notice | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 8 'class ContextBudget|function ContextBudget|hasRoom\(|add\(' .opencode/lib/trellis-context.js
rg -n -C 6 'materializeFile\(' .opencode/lib/trellis-context.js
rg -n -C 4 'binary file|not inlined|max_total_bytes|max.*bytes' -g '*test*' .Repository: mindfold-ai/Trellis
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
wc -l .opencode/lib/trellis-context.js
sed -n '1,280p' .opencode/lib/trellis-context.js
sed -n '298,345p' .opencode/lib/trellis-context.js
python3 - <<'PY'
class ContextBudget:
def __init__(self,maxTotalBytes):
self.maxTotalBytes=maxTotalBytes
self.used=0
def hasRoom(self,size):
if self.maxTotalBytes<=0:
return True
return self.used+size<=self.maxTotalBytes
def add(self,size):
self.used+=size
def binary_notice_bytes():
return len(f"\n[Trellis: not inlined (binary file) — path.bin ({1000} bytes): reason]\n")
for max_total_bytes in [50, None, 0]:
max_total_bytes = max_total_bytes if max_total_bytes is not None else -1
b=ContextBudget(max_total_bytes if max_total_bytes>=0 else -1)
for i in range(3):
b.add(binary_notice_bytes())
print("max=", max_total_bytes, "used=", b.used, "has_room_next=", b.hasRoom(binary_notice_bytes()))
PYRepository: mindfold-ai/Trellis
Length of output: 10672
Check capacity before emitting binary notices.
ContextBudget.add() only increments the used byte count and does not enforce max_total_bytes. In materializeFile(), use budget.hasRoom() before returning binaryNotice() so bounded contexts do not exceed the limit.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.opencode/lib/trellis-context.js around lines 260 - 264, Update the
binary-content branch in materializeFile to call budget.hasRoom() before adding
and returning the binaryNotice result. If no capacity remains, return the
bounded-context fallback instead of emitting the notice; preserve the existing
budget.add behavior when room is available.
| const m = trimmed.match(/^skip_keyword\s*:\s*(.*)$/) | ||
| if (!m) continue | ||
| return unquoteYaml(stripInlineComment(m[1]).trim()) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Preserve the default for non-string YAML values.
readSkipKeyword() returns the raw text for every scalar. For example, skip_keyword: false enables false as the escape hatch, and skip_keyword: disables the escape hatch. Parsed YAML treats both values as non-strings, so the shared Python behavior retains no-trellis.
Return DEFAULT_PROMPT_INJECTION_SKIP_KEYWORD for null, boolean, numeric, and collection values. Keep only an explicitly quoted empty string as the disable setting.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.opencode/plugins/inject-workflow-state.js around lines 91 - 93, Update
readSkipKeyword() to parse the YAML value and return
DEFAULT_PROMPT_INJECTION_SKIP_KEYWORD for null, booleans, numbers, and
collections; preserve only explicitly quoted empty strings as the disable value,
while continuing to return unquoted non-empty strings normally.
| resolve, | ||
| } from "node:path"; | ||
| import { spawn, spawnSync } from "node:child_process"; | ||
| import { isUtf8 } from "node:buffer"; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect the declared Node version floor for the repository and the Pi extension.
set -euo pipefail
fd -H -t f -g 'package.json' -E node_modules | while IFS= read -r f; do
node -e '
const fs = require("fs");
const p = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
if (p.engines || p.volta) console.log(process.argv[1], JSON.stringify({engines: p.engines, volta: p.volta}));
' "$f"
done
fd -H -t f -g '.nvmrc' -E node_modules --exec cat -n
fd -H -t f -g '.node-version' -E node_modules --exec cat -n
fd -H -t f -g '.tool-versions' -E node_modules --exec cat -n
rg -n 'node-version' -g '*.yml' -g '*.yaml' .github 2>/dev/null || trueRepository: mindfold-ai/Trellis
Length of output: 457
🌐 Web query:
Which Node.js version added isUtf8 to node:buffer?
💡 Result:
The isUtf8 function in the node:buffer module was added to Node.js in version 19.4.0 [1]. It was introduced to provide a way to validate UTF-8-encoded data [2][1].
Citations:
- 1: https://github.com/nodejs/node/releases/tag/v19.4.0
- 2: buffer: add buffer.isUtf8 for utf8 validation nodejs/node#45947
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Relevant Node dependency/version files:"
fd -H -t f -E node_modules -E dist -E build '^(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb|package\.json|\.nvmrc|\.node-version|\.tool-versions)$' . | sed 's#^\./##' | sort | head -200
echo
echo "Root/required version constraints mentioning Node:"
rg -n '"node"|node[': -g 'package.json' -g 'biome.json' -g 'tsconfig.json' -g '.npmrc' -g 'README.md' -g 'pnpm-workspace.yaml' -g 'package-lock.json' -g 'pnpm-lock.yaml' -g 'yarn.lock' . | head -200
echo
echo "Relevant extension file imports and function:"
wc -l .pi/extensions/trellis/index.ts
ast-grep outline .pi/extensions/trellis/index.ts --match isUtf8 --view compact || true
ast-grep outline .pi/extensions/trellis/index.ts --match isBinaryContent --view compact || true
sed -n '1,80p' .pi/extensions/trellis/index.ts
sed -n '835,870p' .pi/extensions/trellis/index.ts
echo
echo "Resolved npm package metadata for `@types/node` and node (if network/package metadata available):"
npm view `@types/node`@latest version dist.tarball 2>/dev/null || true
npm view node@latest engines 2>/dev/null || trueRepository: mindfold-ai/Trellis
Length of output: 504
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Relevant Node dependency/version files:"
fd -H -t f -E node_modules -E dist -E build '^(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb|package\.json|\.nvmrc|\.node-version|\.tool-versions)$' . | sed 's#^\./##' | sort | head -200
echo
echo "Root/required version constraints mentioning Node:"
rg -n '"node"|node:' -g 'package.json' -g 'biome.json' -g 'tsconfig.json' -g '.npmrc' -g 'README.md' -g 'pnpm-workspace.yaml' -g 'package-lock.json' -g 'pnpm-lock.yaml' -g 'yarn.lock' . | head -200 || true
echo
echo "Relevant extension file imports and function:"
wc -l .pi/extensions/trellis/index.ts
ast-grep outline .pi/extensions/trellis/index.ts --match isUtf8 --view compact || true
ast-grep outline .pi/extensions/trellis/index.ts --match isBinaryContent --view compact || true
sed -n '1,80p' .pi/extensions/trellis/index.ts
sed -n '835,870p' .pi/extensions/trellis/index.ts
echo
echo "Resolved npm package metadata for node (if network/package metadata available):"
npm view node@latest engines 2>/dev/null || true
npm view `@types/node`@latest version dist.tarball 2>/dev/null || trueRepository: mindfold-ai/Trellis
Length of output: 14783
Update the Node floor to >=19.4.0.
isUtf8 from node:buffer is not available in Node 18.17.0, but packages/cli/package.json and packages/core/package.json declare node >=18.17.0, so the Pi extension can run on a runtime where isUtf8 is undefined and isBinaryContent throws on file reads.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.pi/extensions/trellis/index.ts at line 12, Update the Node engine floor for
the Pi extension and its corresponding package metadata from >=18.17.0 to
>=19.4.0, ensuring the declarations covering packages/cli and packages/core are
consistent with the isUtf8 dependency used by isBinaryContent.
| ".claude/skills/trellis-meta/references/customize-local/change-agents.md": "4216ac3cc570038fd8ce3319a85932bb537fea9e0a7f4d11fa315cfa645c9c85", | ||
| ".claude/skills/trellis-meta/references/customize-local/change-context-loading.md": "c7be53e038eac99ff4d0abb3fafc67cfdfd90352744ce09ad11e7ef5085cf933", | ||
| ".claude/skills/trellis-meta/references/customize-local/change-hooks.md": "91892f2cff53ae003736007e95172945acabf48b2fc889bd627cd2406ce449c4", | ||
| ".claude/skills/trellis-meta/references/customize-local/change-skills-or-commands.md": "a6994e2418cdf5bcad10b4236b02741179ae794bc4fdd811a64f443293b69268", | ||
| ".claude/skills/trellis-meta/references/customize-local/change-hooks.md": "4c18b134f05d5ba1609c517ec94c97c9442b5f0670aa2211c487c31ed2a4f358", | ||
| ".claude/skills/trellis-meta/references/customize-local/change-skills-or-commands.md": "5b942e9f512e049a75e8dd9e8cc4d49786f6da61b22afc82bb447acf808f9fe2", | ||
| ".claude/skills/trellis-meta/references/customize-local/change-spec-structure.md": "a3fc9da294448b4a3525ae1ab7c8c9b895ef8f3b53bcf37a13ce6afbdc040fe6", | ||
| ".claude/skills/trellis-meta/references/customize-local/change-task-lifecycle.md": "60ff9efb93604b87a461a4af30322d76750402a51e40f31531a7ff88d309996d", | ||
| ".claude/skills/trellis-meta/references/customize-local/change-workflow.md": "43fa780a2ca580de121b10893d49b99f978873deebbf45008c466e5ac6651519", | ||
| ".claude/skills/trellis-meta/references/customize-local/overview.md": "ce8f09e9f93ce9a48500763fb3a4db2b3908a5fbf4f985ab71dacebb404cf8f4", | ||
| ".claude/skills/trellis-meta/references/local-architecture/context-injection.md": "31286b9c05e600db7d179100eca533f9b8a4aab3a9c255cb69e8dccacb4e8375", | ||
| ".claude/skills/trellis-meta/references/local-architecture/generated-files.md": "ca4211b43aff79cf263c83255f557994386723d10da344c0c4e1ec211c7fcd37", | ||
| ".claude/skills/trellis-meta/references/local-architecture/generated-files.md": "7eb2d452eddb4f4226f7578c2ec6d5ee0434ed172ba4c36107cc8bdff7554dc6", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import hashlib
import json
from pathlib import Path
manifest = json.loads(Path(".trellis/.template-hashes.json").read_text())
failures = []
for relative_path, expected_hash in manifest["hashes"].items():
path = Path(relative_path)
if not path.is_file():
failures.append(f"missing: {relative_path}")
continue
actual_hash = hashlib.sha256(path.read_bytes()).hexdigest()
if actual_hash != expected_hash:
failures.append(
f"mismatch: {relative_path}: expected {expected_hash}, got {actual_hash}"
)
if failures:
print("\n".join(failures))
raise SystemExit(1)
print("All template hashes match.")
PYRepository: mindfold-ai/Trellis
Length of output: 14622
Regenerate .trellis/.template-hashes.json before merge.
Many manifest entries no longer match their referenced files, including .opencode/package.json as missing. Update the hashes from the working files so trellis update does not restore stale files or skip changed files.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.trellis/.template-hashes.json around lines 5 - 14, Regenerate the manifest
entries in .trellis/.template-hashes.json from the current working files,
including restoring the missing .opencode/package.json entry, so every
referenced template has its current hash before merge.
|
Closing at the repository owner's request. The fleet rollout should exclude the Trellis source repository itself. |
Upgrades the repository-local dogfood installation from 0.6.2 to 0.6.12, adopts native Codex SubagentStart context injection and recovery, refreshes cross-platform hooks/skills, preserves the monorepo package map, and completes the Pi shared-skill migration to .agents/skills.\n\nValidation: pnpm lint, pnpm typecheck, pnpm build, 333 core tests passed (1 skipped), and 1,586 CLI tests passed.
Summary by CodeRabbit
New Features
Documentation
Bug Fixes