Skip to content

feat: worktree DX — node_modules symlink, broadcast discovery, new hook handlers - #1054

Merged
namastex888 merged 5 commits into
devfrom
dx-fix
Apr 4, 2026
Merged

namastex888 merged 5 commits into
devfrom
dx-fix

Conversation

@namastex888

Copy link
Copy Markdown
Contributor

Summary

Three DX improvements from the worktree-dx-improvements wish.

Closes

Test plan

  • bun run build passes
  • bun test — 2059 pass
  • bun run check clean (lint + tests)
  • Worktree creation auto-symlinks node_modules
  • Council leader receives broadcast context on spawn
  • New hook handlers have tests

Test added 5 commits April 4, 2026 19:18
After git clone --shared, worktrees now get:
- A symlink to the parent repo's node_modules (so builds work immediately)
- Execution of .genie/init.sh if present (post-clone hook convention)

Both are best-effort — failures don't block team creation.
)

Three new hook handlers expanding the handler library:

- brain-inject: queries genie-brain on first tool call per session for
  relevant knowledge context (no-op when brain is not installed)
- audit-context: injects recent git history for files being written/edited
- freshness: warns when reading files recently modified by another agent

All handlers are informational (permissionDecision: allow) and best-effort.
…ompt

Closes #1015

When spawning a council/team leader via `genie team create --wish`, fetch any
broadcast messages sent to the team conversation in the last 5 minutes and
append them to the initial kickoff prompt. This lets the leader auto-discover
the broadcast topic without a manual `genie send` after spawn.

- Add `getRecentBroadcasts(teamName, minutesAgo=5)` — best-effort DB fetch with
  graceful fallback when PG is unavailable
- Update `plugins/genie/agents/council/SOUL.md` to instruct the council
  moderator to check for "Recent team broadcasts for context" in its prompt
- No change in behavior when no broadcasts exist — prompt unchanged
…xity

Biome reported cognitive complexity 21 (max 15) on the freshness handler.
Extract buildCommitWarning and checkUncommittedChanges helpers and flip
the early-return guard so the main function stays under the limit.
@coderabbitai

coderabbitai Bot commented Apr 4, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f4e70405-24e7-4b25-a35a-7aff322fce16

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dx-fix

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@namastex888
namastex888 merged commit 5963dd0 into dev Apr 4, 2026
5 of 6 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ec815719e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/** Get recent git log for a specific file. Returns null if git is unavailable or file is untracked. */
function getRecentGitHistory(filePath: string, cwd: string): string | null {
try {
const log = execSync(`git log --oneline -n ${MAX_COMMITS} -- ${JSON.stringify(filePath)}`, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass file paths to git without invoking a shell

getRecentGitHistory builds a shell command with ${JSON.stringify(filePath)}, which is not shell-safe for filenames containing command substitutions (for example $(...) or backticks). If an agent edits a tracked file with such a name, this PreToolUse hook will execute the embedded shell payload before git log runs, creating an RCE path. Use execFile/spawn with argv (no shell parsing) for the file path argument.

Useful? React with 👍 / 👎.

function getLastCommitInfo(filePath: string, cwd: string): { author: string; age: number; message: string } | null {
try {
// Get last commit timestamp, author, and subject for the file
const output = execSync(`git log -1 --format="%at|%an|%s" -- ${JSON.stringify(filePath)}`, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid shell command construction in freshness checks

The freshness handler also interpolates ${JSON.stringify(filePath)} into execSync shell commands, so crafted filenames can trigger shell expansion during read-time staleness checks. Because this runs automatically on PreToolUse:Read, a malicious path can execute arbitrary commands when the file is read. Switch these git calls to argument-based process APIs to avoid shell interpretation.

Useful? React with 👍 / 👎.

Comment on lines +65 to +66
if (enrichedSessions.has(key)) return;
enrichedSessions.add(key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist brain enrichment dedupe across hook invocations

The “only once per session” guard uses an in-memory Set, but hook dispatch is executed as a command per hook event, so this process-local state is lost between tool calls. In practice, sessions with genie-brain installed will repeatedly query brain and re-inject the same context on every PreToolUse, increasing latency and prompt noise instead of running once. The dedupe key needs persistence outside process memory.

Useful? React with 👍 / 👎.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several new hook handlers (audit-context, brain-inject, and freshness) to improve agent context awareness and safety, alongside infrastructure updates for team management, such as post-clone symlinking and broadcast injection. The review feedback highlights security improvements by recommending the use of Node.js native filesystem functions instead of shell commands for directory cleanup to prevent command injection. Additionally, the review suggests simplifying the brain-inject handler by removing redundant checks and cleaning up the freshness handler logic for better readability.

});

afterEach(() => {
execSync(`rm -rf ${repoDir}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

For safety and cross-platform compatibility, it's better to use Node.js's built-in filesystem functions for cleanup instead of shelling out to rm -rf. This avoids potential command injection vulnerabilities and works on Windows. This applies here and also to the cleanup on line 97. You'll need to add rmSync to your import from node:fs.

Suggested change
execSync(`rm -rf ${repoDir}`);
rmSync(repoDir, { recursive: true, force: true });


afterEach(() => {
process.env = { ...originalEnv };
execSync(`rm -rf ${repoDir}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

For safety and cross-platform compatibility, it's better to use Node.js's built-in filesystem functions for cleanup instead of shelling out to rm -rf. This avoids potential command injection vulnerabilities and works on Windows. You'll need to add rmSync to your import from node:fs.

Suggested change
execSync(`rm -rf ${repoDir}`);
rmSync(repoDir, { recursive: true, force: true });

enrichedSessions.add(key);

// Skip if brain is not installed
if (!isBrainAvailable()) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This check is redundant. The queryBrain function already gracefully handles cases where the @automagik/genie-brain package isn't installed by catching the error from the dynamic import(). Removing this check and the corresponding isBrainAvailable function (lines 30-33) and BRAIN_DIR constant (line 20) will simplify the code.

References
  1. Use dynamic imports (require() or import()) to break circular dependency cycles that would otherwise occur at module load time.

permissionDecision: 'allow',
additionalContext: `[freshness] Stale read warning: ${filePath} has uncommitted changes (modified ${diskAge}s ago). Another agent may be editing this file concurrently.`,
},
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check diskAge < STALENESS_THRESHOLD_SECS is redundant here, as it's already guaranteed by the outer if condition on line 66. Removing it will make the code slightly cleaner.

Suggested change
};
if (currentAgent) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant