Skip to content

chore: rolling promotion dev -> main - #1041

Merged
namastex888 merged 28 commits into
mainfrom
dev
Apr 5, 2026
Merged

namastex888 merged 28 commits into
mainfrom
dev

Conversation

@namastex888

@namastex888 namastex888 commented Apr 4, 2026 •

Copy link
Copy Markdown
Contributor

Rolling Promotion PR

Auto-maintained rolling promotion PR from dev to main.

Process:

  • This PR is automatically created and kept open
  • Human reviews and merges when ready
  • Label ready-to-merge added when all checks pass

IMPORTANT: Merge with "Create a merge commit" — NEVER squash.
Squash merging breaks history sync between dev and main,
causing the next rolling PR to show all commits again.

Human approval required for merge to production.

Summary by CodeRabbit

  • Bug Fixes

    • Branch guard: PR creation targeting main/master is permitted when an explicit --base is provided.
  • New Features

    • New hook handlers: brain-inject, freshness, and audit-context to provide contextual warnings and enrichments.
    • CLI: explicit brain subcommands (install/uninstall/update/version) and stricter numeric flag validation.
    • Provider surface extended to accept a new provider value (app-pty) and Claude-equivalent provider handling.
  • Documentation

    • Added/updated architecture, CLI reference, agent frontmatter, auto-resume, co-orchestration, and executor guides.
  • Tests

    • Added test suites for new hooks and team/worktree behavior.
  • Chores

    • Version bumped to 4.260404.2; metrics run state updated.

@coderabbitai

coderabbitai Bot commented Apr 4, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Bumps the genie package/plugin version; records a successful metrics-updater run; relaxes branch-guard to require explicit --base for gh pr create; adds strict numeric CLI parsing; extends provider handling (claude-sdk, app-pty) and Claude SDK wiring; adds three PreToolUse hook handlers and registers them; adds spawn/team post-clone init and multiple docs.

Changes

Cohort / File(s) Summary
Version / Manifest
\.claude-plugin/marketplace.json, package.json, plugins/genie/.claude-plugin/plugin.json
Bumped genie package/plugin version 4.260404.1 → 4.260404.2.
Agent Metrics / State
.genie/agents/metrics-updater/runs.jsonl, .genie/agents/metrics-updater/state.json
Appended a successful metrics-updater run for 2026-04-04; updated last_run and last_run_status and last_metrics.releases_24h.
Branch Guard
src/hooks/handlers/branch-guard.ts, src/hooks/__tests__/branch-guard.test.ts
Removed explicit denial for `gh pr create --base/-B main
CLI Numeric Parsing
src/genie.ts, src/term-commands/agent/spawn.ts, src/term-commands/dir.ts
Added numeric parsing helpers (parseNumericFlag/toSafeNumber) and switched --sdk-max-turns, --sdk-max-budget and related budget options to throw on invalid numeric input instead of producing NaN.
Providers & Executors
src/lib/protocol-router-spawn.ts, src/lib/provider-adapters.ts, src/lib/providers/claude-sdk.ts, src/services/executors/claude-sdk.ts
Treat claude-sdk as Claude-equivalent for spawn routing and use process executor transport for it; extend provider enum to include app-pty; ensure falsy-but-explicit SDK boolean options are preserved; pass entry.sdk into state.provider.runQuery(...).
New Hook Handlers & Registry
src/hooks/handlers/brain-inject.ts, src/hooks/handlers/freshness.ts, src/hooks/handlers/audit-context.ts, src/hooks/index.ts, src/hooks/handlers/__tests__/*
Added brain-inject, freshness, and audit-context PreToolUse handlers (with tests); registered them in handlers registry with priorities and matchers.
Team / Worktree Post-clone Init
src/lib/team-manager.ts, src/lib/team-manager.test.ts
Added postCloneInit to create node_modules symlink from parent when present and run .genie/init.sh if present; tests added/updated to validate behavior.
Term Commands / Brain & Team
src/term-commands/brain.ts, src/term-commands/team.ts
Refactored genie brain to explicit subcommands (install, uninstall, update, version); added getRecentBroadcasts and broadcast injection into leader kickoff prompt.
Spawn / Protocol Router
src/lib/protocol-router-spawn.ts, src/term-commands/agent/spawn.ts
Claude-equivalent detection updated to include claude-sdk; spawn param wiring adjusted for resume/nativeTeam behavior; agent spawn CLI numeric parsing uses new validator.
Docs
docs/AGENT-FRONTMATTER.md, docs/ARCHITECTURE.md, docs/CLI-REFERENCE.md, docs/CO-ORCHESTRATION-GUIDE.md, docs/SPAWN-AUTO-RESUME.md, docs/sdk-executor-guide.md, plugins/genie/agents/council/SOUL.md
Added/updated extensive documentation (frontmatter schema, architecture, CLI reference, co-orchestration/serve changes, spawn auto-resume, minor SDK doc tweaks) and council prompt addition.
Tests / Misc
src/hooks/handlers/__tests__/*, src/lib/team-manager.test.ts, src/hooks/__tests__/branch-guard.test.ts
Added multiple new Bun test suites for new hook handlers and updated existing branch-guard tests; small test refinements for fs permissions/symlink behaviors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: rolling promotion dev -> main' accurately describes the PR's purpose—a standard rolling promotion of changes from the dev branch to main.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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.

@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 updates the version of the genie plugin and the associated package files from 4.260404.1 to 4.260404.2 across the repository. I have no feedback to provide.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.genie/agents/metrics-updater/runs.jsonl:
- Line 19: Normalize run status when a run contains errors/fallback or zero API
calls: if the JSON record's errors array is non-empty or fallback is true or
api_calls == 0 then set status to "error" (or a distinct "partial_failure")
instead of "success"; update the code that writes/normalizes runs (the logic
that sets the "status" field) to inspect errors, fallback, and api_calls and
derive status accordingly, ensuring downstream monitors see failures; update any
tests or validation that assert status values to reflect this normalization for
records like the one containing errors/errors + fallback.

In @.genie/agents/metrics-updater/state.json:
- Around line 2-5: The state JSON uses last_run and last_run_status but
run-metrics.sh writes last_run_at and never updates last_run_status; update
run-metrics.sh to consistently use one timestamp key (prefer last_run_at to
match existing writer usage) and ensure it also writes/updates a status field
(rename or replace last_run_status with last_run_status or last_run_state
consistently) on every run—modify the code paths in run-metrics.sh that write
the state (the section around the timestamp write at line ~208) to write the
chosen timestamp key (last_run_at) and set the status key to the current result
(e.g., "success" or "failure") every time so the state.json no longer
accumulates stale/dead keys.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 90b09a5d-e22f-4f60-a77d-f0246b498eb9

📥 Commits

Reviewing files that changed from the base of the PR and between a34d834 and e67161f.

⛔ Files ignored due to path filters (1)
  • README.md is excluded by !*.md
📒 Files selected for processing (2)
  • .genie/agents/metrics-updater/runs.jsonl
  • .genie/agents/metrics-updater/state.json

{"timestamp":"2026-04-01T00:00:00Z","duration_ms":8000,"api_calls":0,"tools_generated":0,"errors":["gh CLI unavailable","GitHub MCP tools not loaded in session"],"status":"failed","fallback":false,"metrics":null}
{"timestamp":"2026-04-02T00:00:00.000Z","duration_ms":15000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":false,"metrics":null}
{"timestamp":"2026-04-03T00:00:00.000Z","duration_ms":3200,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":false,"metrics":null}
{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

status: "success" conflicts with this run’s error state.

Line 19 records errors plus fallback:true and zero API calls, but marks status as success. Any status-only monitor will treat this as healthy and miss the tooling outage.

Proposed normalization
-{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
+{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
📝 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.

Suggested change
{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"success","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
{"timestamp":"2026-04-04T00:00:00.000Z","duration_ms":30000,"api_calls":0,"tools_generated":0,"errors":["gh CLI not available","GitHub MCP tools not present in session"],"status":"failed","fallback":true,"metrics":{"releases_24h":2,"merged_prs_7d":35,"avg_merge_time_h":0.3,"ship_rate_pct":85}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.genie/agents/metrics-updater/runs.jsonl at line 19, Normalize run status
when a run contains errors/fallback or zero API calls: if the JSON record's
errors array is non-empty or fallback is true or api_calls == 0 then set status
to "error" (or a distinct "partial_failure") instead of "success"; update the
code that writes/normalizes runs (the logic that sets the "status" field) to
inspect errors, fallback, and api_calls and derive status accordingly, ensuring
downstream monitors see failures; update any tests or validation that assert
status values to reflect this normalization for records like the one containing
errors/errors + fallback.

Comment on lines +2 to +5
"last_run": "2026-04-04T00:00:00.000Z",
"last_run_status": "success",
"last_metrics": {
"releases_24h": 0,
"releases_24h": 2,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Writer paths =="
rg -n -C2 "last_run_at|last_run_status|last_run" .genie/agents/metrics-updater/tools/run-metrics.sh

echo
echo "== Readers/usages in .genie =="
rg -n -C2 "\"last_run_at\"|\"last_run_status\"|\"last_run\"" .genie

Repository: automagik-dev/genie

Length of output: 719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== State file reader locations =="
rg -r -n "state.json" .genie --max-count=20

echo
echo "== Python code reading/accessing state dict =="
rg -n "state\[" .genie/agents/metrics-updater/tools/ -A 2 -B 1

echo
echo "== Any references to last_run or last_run_at in Python code =="
rg -n "last_run" .genie/agents/metrics-updater/ --type py -A 2 -B 1

Repository: automagik-dev/genie

Length of output: 4514


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Contents of metrics-updater agent directory =="
ls -la .genie/agents/metrics-updater/

echo
echo "== Content of actual state file (-n) =="
cat .genie/agents/metrics-updater/-n

echo
echo "== Check if state.json exists =="
if [ -f .genie/agents/metrics-updater/state.json ]; then
  cat .genie/agents/metrics-updater/state.json
else
  echo "state.json does NOT exist"
fi

echo
echo "== Search for code that reads/uses last_run_status =="
rg -n "last_run_status" .genie --type py --type sh

Repository: automagik-dev/genie

Length of output: 580


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full content of state.json =="
cat .genie/agents/metrics-updater/state.json

echo
echo "== Examine run-metrics.sh write logic more carefully =="
sed -n '200,220p' .genie/agents/metrics-updater/tools/run-metrics.sh

Repository: automagik-dev/genie

Length of output: 1012


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Code that reads/accesses last_run, last_run_at, or last_run_status =="
rg -n "last_run|last_run_at|last_run_status" .genie/agents/metrics-updater/tools/ --type py --type sh -A 3 -B 1

echo
echo "== Any health checks or status reporting that might use last_run_status =="
rg -n "status" .genie/agents/metrics-updater/AGENT.md -A 2 -B 1

Repository: automagik-dev/genie

Length of output: 1410


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for external code reading state.json fields =="
rg -n "last_run_status|last_run_at|last_run" . --type py --type sh --type json -g "!\.genie" | head -20

echo
echo "== Check if there are any monitoring or health check scripts =="
fd -e sh -e py | xargs grep -l "health\|status\|metrics-updater" 2>/dev/null | head -10

echo
echo "== Verify the actual schema inconsistency by checking what keys exist after next write =="
echo "After run-metrics.sh completes, state.json will have:"
echo "  - last_metrics (updated)"
echo "  - last_run_at (updated/added)"
echo "  - run_count (updated)"
echo "  - last_run (STALE - never touched)"
echo "  - last_run_status (STALE - never touched)"

Repository: automagik-dev/genie

Length of output: 119


Align state.json keys with the writer's actual field names.

The state file contains last_run and last_run_status, but run-metrics.sh writes last_run_at (line 208) and never updates last_run_status. After each run, the state file accumulates dead keys (last_run untouched) and perpetually stale last_run_status. Standardize on one timestamp field name and ensure status is actively maintained on each update.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.genie/agents/metrics-updater/state.json around lines 2 - 5, The state JSON
uses last_run and last_run_status but run-metrics.sh writes last_run_at and
never updates last_run_status; update run-metrics.sh to consistently use one
timestamp key (prefer last_run_at to match existing writer usage) and ensure it
also writes/updates a status field (rename or replace last_run_status with
last_run_status or last_run_state consistently) on every run—modify the code
paths in run-metrics.sh that write the state (the section around the timestamp
write at line ~208) to write the chosen timestamp key (last_run_at) and set the
status key to the current result (e.g., "success" or "failure") every time so
the state.json no longer accumulates stale/dead keys.

Test and others added 2 commits April 4, 2026 19:03
Removed the deny rule that blocked PR creation targeting production branches.
Agents CAN propose PRs to any branch — PRs are proposals for human review.
The protection is on merging, not proposing.

Kept: require explicit --base flag, block merge commands, block direct push.
…lease-prs

fix: allow agents to create release PRs to any branch

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/__tests__/branch-guard.test.ts (1)

96-106: 🧹 Nitpick | 🔵 Trivial

Missing test coverage for --base=value syntax.

The gh CLI accepts both --base main and --base=main. If you fix the regex issue in the handler, add test cases to ensure both syntaxes are handled:

Suggested additions
       // PR targeting main/master (creating PRs is proposing, not merging)
       'gh pr create --base main --title "test"',
       'gh pr create --base master --title "test"',
       'gh pr create -B main --title "test"',
       'gh pr create -B master --title "test"',
+      'gh pr create --base=main --title "test"',
+      'gh pr create --base=dev --title "test"',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/__tests__/branch-guard.test.ts` around lines 96 - 106, The tests in
src/hooks/__tests__/branch-guard.test.ts only cover space-separated flag syntax
and miss the equals form; add test cases mirroring the existing ones but using
--base=dev, --base=main, --base=master and -B=main/-B=master forms (e.g., 'gh pr
create --base=dev', 'gh pr create -B=main') to ensure both syntaxes are
accepted, and update the branch-guard hook's flag-parsing regex (the handler
logic that parses the 'gh pr create' command in the branch-guard hook) to accept
both '--base value' and '--base=value' (and equivalent -B=) forms so the tests
pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/handlers/branch-guard.ts`:
- Around line 29-36: The test predicate in the branch guard (the object with
test: (cmd) => /gh\s+pr\s+create\b/.test(cmd) && !/(--base|-B)\s+\S/.test(cmd))
fails to catch --base=value syntax; update the regex used in that test so it
accepts either whitespace or an equals sign after --base (and similarly handle
-B), e.g. change the negative check to look for (--base|-B) followed by either =
or whitespace before the value (so --base=main and --base main both match)
within the same test function.

---

Outside diff comments:
In `@src/hooks/__tests__/branch-guard.test.ts`:
- Around line 96-106: The tests in src/hooks/__tests__/branch-guard.test.ts only
cover space-separated flag syntax and miss the equals form; add test cases
mirroring the existing ones but using --base=dev, --base=main, --base=master and
-B=main/-B=master forms (e.g., 'gh pr create --base=dev', 'gh pr create
-B=main') to ensure both syntaxes are accepted, and update the branch-guard
hook's flag-parsing regex (the handler logic that parses the 'gh pr create'
command in the branch-guard hook) to accept both '--base value' and
'--base=value' (and equivalent -B=) forms so the tests pass.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 35d32d55-a78f-429b-b32a-4f49a1a6e0ff

📥 Commits

Reviewing files that changed from the base of the PR and between e67161f and 64b62cc.

📒 Files selected for processing (2)
  • src/hooks/__tests__/branch-guard.test.ts
  • src/hooks/handlers/branch-guard.ts

Comment on lines +29 to 36
// Agents CAN create PRs targeting any branch (including main/master).
// PRs are proposals for human review — the protection is on MERGING, not proposing.
{
// gh pr create --base main, gh pr create -B master
test: (cmd) => /gh\s+pr\s+create\b/.test(cmd) && /(--base|-B)\s+(main|master)\b/.test(cmd),
reason: 'BLOCKED: PRs must target dev, not main/master. Use: gh pr create --base dev',
},
{
// gh pr create without --base (defaults to main)
// gh pr create without --base (defaults to main — require explicit intent)
test: (cmd) => /gh\s+pr\s+create\b/.test(cmd) && !/(--base|-B)\s+\S/.test(cmd),
reason:
'BLOCKED: gh pr create requires explicit --base flag. Without it, GitHub defaults to main. Use: gh pr create --base dev',
'BLOCKED: gh pr create requires explicit --base flag. Use: gh pr create --base dev (or --base main for releases)',
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Regex misses --base=value syntax.

The pattern /(--base|-B)\s+\S/ requires whitespace after --base, but gh CLI also accepts --base=main. A user running gh pr create --base=main --title "x" would be blocked incorrectly.

Proposed fix
  {
-   // gh pr create without --base (defaults to main — require explicit intent)
-   test: (cmd) => /gh\s+pr\s+create\b/.test(cmd) && !/(--base|-B)\s+\S/.test(cmd),
+   // gh pr create without --base (defaults to main — require explicit intent)
+   test: (cmd) => /gh\s+pr\s+create\b/.test(cmd) && !/(--base[=\s]|-B\s)\S/.test(cmd),
    reason:
      'BLOCKED: gh pr create requires explicit --base flag. Use: gh pr create --base dev (or --base main for releases)',
  },
📝 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.

Suggested change
// Agents CAN create PRs targeting any branch (including main/master).
// PRs are proposals for human review — the protection is on MERGING, not proposing.
{
// gh pr create --base main, gh pr create -B master
test: (cmd) => /gh\s+pr\s+create\b/.test(cmd) && /(--base|-B)\s+(main|master)\b/.test(cmd),
reason: 'BLOCKED: PRs must target dev, not main/master. Use: gh pr create --base dev',
},
{
// gh pr create without --base (defaults to main)
// gh pr create without --base (defaults to main — require explicit intent)
test: (cmd) => /gh\s+pr\s+create\b/.test(cmd) && !/(--base|-B)\s+\S/.test(cmd),
reason:
'BLOCKED: gh pr create requires explicit --base flag. Without it, GitHub defaults to main. Use: gh pr create --base dev',
'BLOCKED: gh pr create requires explicit --base flag. Use: gh pr create --base dev (or --base main for releases)',
},
// Agents CAN create PRs targeting any branch (including main/master).
// PRs are proposals for human review — the protection is on MERGING, not proposing.
{
// gh pr create without --base (defaults to main — require explicit intent)
test: (cmd) => /gh\s+pr\s+create\b/.test(cmd) && !/(--base[=\s]|-B\s)\S/.test(cmd),
reason:
'BLOCKED: gh pr create requires explicit --base flag. Use: gh pr create --base dev (or --base main for releases)',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/handlers/branch-guard.ts` around lines 29 - 36, The test predicate
in the branch guard (the object with test: (cmd) =>
/gh\s+pr\s+create\b/.test(cmd) && !/(--base|-B)\s+\S/.test(cmd)) fails to catch
--base=value syntax; update the regex used in that test so it accepts either
whitespace or an equals sign after --base (and similarly handle -B), e.g. change
the negative check to look for (--base|-B) followed by either = or whitespace
before the value (so --base=main and --base main both match) within the same
test function.

Test and others added 17 commits April 4, 2026 19:13
Four boolean fields in translateSdkConfig() used truthiness checks that
silently dropped explicit `false` values. Changed to `!= null` guards
matching the pattern already used by adjacent fields.

- enableFileCheckpointing
- includePartialMessages
- promptSuggestions
- agentProgressSummaries
)

The SDK executor's _processDelivery() was calling runQuery() with only
4 arguments, missing the 5th sdkConfig parameter. This caused SDK agents
spawned via Omni to lose their directory config (settingSources, maxTurns,
effort, thinking).
- Match 'claude-sdk' in isClaude checks for session ID generation and
  native team registration in protocol-router-spawn
- Route claude-sdk executor transport to 'process' instead of 'tmux'
- Add 'app-pty' to spawnParamsSchema Zod enum to match ProviderName type
…cleanup, concurrency guard

- Log warning + send buffer-full reply when MAX_BUFFER_PER_CHAT exceeded (was silent drop)
- Shut down all active executor sessions in stop() before clearing (was resource leak)
- Count spawning entries in concurrency limit to prevent oversubscription
- Re-queue buffered messages on spawn failure instead of losing them

Closes #1035
The function is only used within the module — no need to export it.
Fixes dead-code check (knip) warning.
Reject non-numeric strings at parse time for --sdk-max-turns,
--sdk-max-budget, and --sdk-thinking budgetTokens. Commander's
bare Number parser silently passes NaN through; replaced with
parseNumericFlag() in genie.ts and spawn.ts, and toSafeNumber()
in dir.ts. NaN never reaches the database or SDK config.
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.
…k-executor-guide

Replace 3 U+FFFD replacement characters with ▼ in the data flow diagram.
Move inline comments out of shell line-continuations to make examples
copy-pasteable.

Closes #1040
…roup 1 dependencies

- Re-export sanitizeWindowName (needed by test file)
- Add isPaneProcessRunning to tmux.ts (used by claude-code executor liveness check)
- Include claude-code.test.ts for sanitizeWindowName collision tests
Document the auto-resume system: spawn-time resume, daemon-level
resume, session matching criteria, dead session detection, resume
budget/cooldown, and the --no-auto-resume flag.
Document the AgentFrontmatterSchema from src/lib/frontmatter.ts:
- All 9 fields with types, defaults, and descriptions
- Warn-not-reject validation behavior
- 5 complete examples (minimal, role, council, SDK, empty)
- Field details for promptMode, provider, sdk, and tools
- API usage for parseFrontmatter and schema access
`genie brain --help` now shows install, uninstall, update, and version
subcommands with descriptions. Replaces manual args[0] checks with
commander's native subcommand routing while preserving pass-through
to the enterprise brain module for its own commands.

Closes #1002
…ed serve model

Create comprehensive architecture documentation covering:
- Serve/daemon unification (daemon is now alias for serve --headless)
- Service topology: pgserve, scheduler, event-router, inbox-watcher,
  session-capture, omni-bridge, service-registry
- Agent lifecycle: spawn → register → work → report → shutdown
- Data flow: CLI → PG → hooks → events → agents
- Hook system as event bus
- Directory structure and state file locations

Update CO-ORCHESTRATION-GUIDE.md to reference genie serve instead of
deprecated genie daemon commands, update architecture diagram, and
link to ARCHITECTURE.md for internals.

Closes #1003
Scan all registerXxxCommands() functions across src/term-commands/
and document every command with name, description, options (types
and defaults), subcommands, and usage examples.

Organized by category: core aliases, agent namespace, task management,
team management, board/pipeline, project, events, sessions, database,
scheduling, daemon, infrastructure, brain, omni, import/export,
dispatch, QA, tags, types, releases, templates, metrics, notifications,
hooks, directory management, utility commands, and executor debug.
docs: fix corrupted chars and shell examples in sdk-executor-guide
fix: SDK executor correctness — 4 bug fixes (#1037, #1038, #1039, #1046)

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/provider-adapters.ts`:
- Line 118: The zod enum for provider
(z.enum(['claude','codex','claude-sdk','app-pty'])) allows "app-pty" but
buildLaunchCommand's switch (function buildLaunchCommand) only handles 'claude',
'codex', and 'claude-sdk', causing a mismatch and an incomplete error message;
fix by adding an explicit 'app-pty' branch in buildLaunchCommand that either
implements its launch behavior or throws a clear, descriptive error (e.g.,
"provider 'app-pty' is not supported by buildLaunchCommand") and update the
existing default/error message to list all valid handled providers, or
alternatively remove 'app-pty' from the z.enum if that provider should not be
accepted by buildLaunchCommand.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f4dc6b1a-7166-4cb8-a55d-79f32a84fb44

📥 Commits

Reviewing files that changed from the base of the PR and between 64b62cc and c862194.

📒 Files selected for processing (14)
  • docs/AGENT-FRONTMATTER.md
  • docs/ARCHITECTURE.md
  • docs/CLI-REFERENCE.md
  • docs/CO-ORCHESTRATION-GUIDE.md
  • docs/SPAWN-AUTO-RESUME.md
  • docs/sdk-executor-guide.md
  • src/genie.ts
  • src/lib/protocol-router-spawn.ts
  • src/lib/provider-adapters.ts
  • src/lib/providers/claude-sdk.ts
  • src/services/executors/claude-sdk.ts
  • src/term-commands/agent/spawn.ts
  • src/term-commands/brain.ts
  • src/term-commands/dir.ts


const spawnParamsSchema = z.object({
provider: z.enum(['claude', 'codex', 'claude-sdk']),
provider: z.enum(['claude', 'codex', 'claude-sdk', 'app-pty']),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if buildLaunchCommand is ever called with app-pty provider
rg -n "buildLaunchCommand" --type=ts -C 3 | head -80
echo "---"
# Check how app-pty spawns are routed
rg -n "app-pty" --type=ts -C 2

Repository: automagik-dev/genie

Length of output: 7609


🏁 Script executed:

# Read the buildLaunchCommand implementation
sed -n '436,460p' src/lib/provider-adapters.ts

# Check protocol-router-spawn.ts usage of buildLaunchCommand
rg -n "buildLaunchCommand" src/lib/protocol-router-spawn.ts -A 10 -B 2

# Check how app-pty flows in agents.ts
rg -n "app-pty" src/term-commands/agents.ts -B 5 -A 5

Repository: automagik-dev/genie

Length of output: 2099


🏁 Script executed:

# Check what provider values are passed to buildLaunchCommand in agents.ts
rg -n "buildSpawnParams|buildLaunchCommand" src/term-commands/agents.ts -B 5 -A 2 | head -100

# Also check if app-pty is passed as provider anywhere in agents.ts
rg -n "provider.*app-pty|app-pty.*provider" src/term-commands/agents.ts

Repository: automagik-dev/genie

Length of output: 1565


🏁 Script executed:

# Find the buildSpawnParams function implementation in agents.ts
sed -n '1281,1350p' src/term-commands/agents.ts

# Also check buildFullResumeParams which is called at line 1807
rg -n "buildFullResumeParams" src/term-commands/agents.ts -A 20 | head -60

Repository: automagik-dev/genie

Length of output: 4338


🏁 Script executed:

# Check buildResumeParams to see what provider it uses
rg -n "buildResumeParams" src/term-commands/agents.ts -A 15 | head -40

# Check if app-pty can reach buildLaunchCommand via genie-app path
grep -r "createExecutor" packages/genie-app/src-backend/pty.ts -B 5 -A 5

# Verify if app-pty flows to protocol-router-spawn
rg -n "spawnWorker|protocol-router" src/term-commands/agents.ts | head -20

Repository: automagik-dev/genie

Length of output: 2519


Zod schema accepts app-pty but buildLaunchCommand doesn't handle it.

Line 118 allows app-pty through validation, but the switch statement (lines 439–454) only handles 'claude', 'codex', and 'claude-sdk'. The error message on line 452 also omits app-pty from the list of valid providers.

The genie-app backend bypasses buildLaunchCommand and calls the provider registry directly, so it never hits this case. However, this type mismatch is confusing—either add app-pty handling to the switch (even if just to throw a clear error) or remove it from the Zod enum if it's not meant for this function.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/provider-adapters.ts` at line 118, The zod enum for provider
(z.enum(['claude','codex','claude-sdk','app-pty'])) allows "app-pty" but
buildLaunchCommand's switch (function buildLaunchCommand) only handles 'claude',
'codex', and 'claude-sdk', causing a mismatch and an incomplete error message;
fix by adding an explicit 'app-pty' branch in buildLaunchCommand that either
implements its launch behavior or throws a clear, descriptive error (e.g.,
"provider 'app-pty' is not supported by buildLaunchCommand") and update the
existing default/error message to list all valid handled providers, or
alternatively remove 'app-pty' from the z.enum if that provider should not be
accepted by buildLaunchCommand.

Test added 3 commits April 4, 2026 20:06
…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.
feat: worktree DX — node_modules symlink, broadcast discovery, new hook handlers

@coderabbitai coderabbitai 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.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/handlers/__tests__/freshness.test.ts`:
- Around line 100-108: The test for freshness is too permissive because it
allows result to be undefined, so regressions in the warning path can pass;
update the test to assert that result is defined for the "recently committed by
another agent" scenario (e.g., add expect(result).toBeDefined() or split into
two explicit cases), then continue to assert result.hookSpecificOutput,
hookSpecificOutput.additionalContext contains '[freshness]', and
hookSpecificOutput.permissionDecision === 'allow' so the warning/allow path is
enforced; reference the freshness function and the properties
hookSpecificOutput, additionalContext, and permissionDecision when updating the
assertions.

In `@src/hooks/handlers/audit-context.ts`:
- Around line 20-25: The git log invocation uses execSync with a template string
(execSync(`git log ... ${JSON.stringify(filePath)}`)) which allows shell
injection; replace it with execFileSync and pass arguments as an argv array
(e.g., execFileSync('git', ['log','--oneline','-n', String(MAX_COMMITS),'--',
filePath], {encoding:'utf-8', timeout:5000, cwd, stdio:['pipe','pipe','pipe']})
so the filePath is not shell-interpolated; update the call in audit-context (the
execSync that assigns to log and references MAX_COMMITS, filePath, cwd) and make
the identical change in freshness.ts for both occurrences (the execSync at the
top and the other call around line 71).

In `@src/hooks/handlers/brain-inject.ts`:
- Around line 31-33: The isBrainAvailable() filesystem probe (which checks
existsSync(join(BRAIN_DIR, 'package.json'))) can falsely report the brain
package missing in workspace/monorepo setups; remove the redundant availability
check and the early return that uses it so the code relies solely on the dynamic
import('@automagik/genie-brain') wrapped in its try/catch. Specifically, delete
the isBrainAvailable function and remove the conditional that returns early (the
check around isBrainAvailable() that prevents enrichment), allowing the existing
try/catch import logic to determine availability and handle errors.
- Around line 26-28: The sessionKey function currently uses process.pid as a
fallback which collapses distinct requests in the same process; update
sessionKey (the function taking HookPayload/session_id) to produce a
deterministic per-session key when session_id is absent by incorporating
additional unique context from the payload (for example payload.cwd or working
directory, payload.request_id if present) or by generating a stable unique
identifier (UUID/timestamp+random) per incoming payload so different sessions in
the same process don’t share the same key; change only sessionKey to combine
process.pid with payload-specific data or a generated UUID to ensure uniqueness.

In `@src/hooks/handlers/freshness.ts`:
- Line 111: The code currently uses substring matching
(commitInfo.author.includes(currentAgent)) which can false-match; change this to
an exact identity check by comparing normalized full author names (e.g.,
commitInfo.author.trim().toLowerCase() === currentAgent.trim().toLowerCase())
or, if commitInfo.author can contain multiple names/emails, split it into an
array and check exact membership (e.g.,
commitInfo.author.split(/[;,]/).map(s=>s.trim().toLowerCase()).includes(currentAgent.trim().toLowerCase()));
update the check in the freshness handler where currentAgent and
commitInfo.author are referenced and remove the use of includes() for substring
matching.
- Around line 23-28: Replace unsafe shell-interpolated execSync calls with
execFileSync using argv arrays: locate the uses of execSync in this file (the
call that builds the git command with `-- ${JSON.stringify(filePath)}`) and in
audit-context.ts, and change them to call child_process.execFileSync('git',
['log', '-1', '--format=%at|%an|%s', '--', filePath], { encoding: 'utf-8',
timeout: 5000, cwd, stdio: ['pipe','pipe','pipe'] }) (and similarly for the
other git invocations), ensuring you pass filePath as a separate argv element
and keep existing options (encoding, timeout, cwd, stdio) intact to avoid shell
interpolation vulnerabilities.

In `@src/lib/team-manager.test.ts`:
- Around line 154-193: The tests create temporary fixtures (node_modules,
.genie/init.sh, .init-marker) inside TEST_REPO but clean them up inline, which
can leak state on assertion failures; update the test file to create and
teardown fixtures using a safe pattern (use tmpdir or ensure cleanup in
afterEach / try/finally) so teardown always runs: for the tests referencing
createTeam('feat/symlink-nm'...), createTeam('feat/no-nm'...), and
createTeam('feat/init-sh'...), move creation of parentNodeModules, genieDir,
init.sh and marker into per-test setup and remove them in an afterEach that
force-deletes those paths (or wrap each test body in try/finally) to guarantee
cleanup even if assertions fail.

In `@src/lib/team-manager.ts`:
- Around line 230-237: The code currently executes the repo's .genie/init.sh
unconditionally; require an explicit opt-in (env var or team config flag) before
running it by checking a named flag (e.g., process.env.RUN_GENIE_INIT or a
boolean on the team creation config) immediately before the block that computes
initScript and runs $`bash ${initScript}`; if the flag is not set skip execution
and log/debug that the init script was not run, and if it is set run the same
try/catch best-effort logic; reference the initScript variable and the bash
execution call in team-manager.ts to locate where to add the gate.

In `@src/term-commands/team.ts`:
- Around line 372-376: The recent broadcasts appended to kickoffPrompt (from
getRecentBroadcasts) are unbounded and can bloat initialPrompt; limit the
context by capping the number of broadcasts (e.g., take last N items), truncate
each broadcast.text to a max length (e.g., 512 chars) with an ellipsis, and/or
enforce a total character budget (e.g., 2KB) before joining; apply the same
guardrail where broadcasts are appended around lines 432-436 as well, ensuring
you update the mapping that builds broadcastLines and then append the
shortened/filtered string to kickoffPrompt (refer to getRecentBroadcasts,
kickoffPrompt, and the broadcastLines construction).
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4ba210d1-0540-4600-aa46-84180f944f27

📥 Commits

Reviewing files that changed from the base of the PR and between c862194 and 5963dd0.

📒 Files selected for processing (11)
  • plugins/genie/agents/council/SOUL.md
  • src/hooks/handlers/__tests__/audit-context.test.ts
  • src/hooks/handlers/__tests__/brain-inject.test.ts
  • src/hooks/handlers/__tests__/freshness.test.ts
  • src/hooks/handlers/audit-context.ts
  • src/hooks/handlers/brain-inject.ts
  • src/hooks/handlers/freshness.ts
  • src/hooks/index.ts
  • src/lib/team-manager.test.ts
  • src/lib/team-manager.ts
  • src/term-commands/team.ts

Comment on lines +20 to +25
const log = execSync(`git log --oneline -n ${MAX_COMMITS} -- ${JSON.stringify(filePath)}`, {
encoding: 'utf-8',
timeout: 5000,
cwd,
stdio: ['pipe', 'pipe', 'pipe'],
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Verify all shell-interpolated git execSync usages with filePath in hook handlers
rg -n --type ts 'execSync\(`git .*filePath' src/hooks/handlers

Repository: automagik-dev/genie

Length of output: 452


🏁 Script executed:

sed -n '15,30p' src/hooks/handlers/audit-context.ts

Repository: automagik-dev/genie

Length of output: 572


🏁 Script executed:

cat -n src/hooks/handlers/freshness.ts | sed -n '20,75p'

Repository: automagik-dev/genie

Length of output: 2443


🏁 Script executed:

head -20 src/hooks/handlers/audit-context.ts

Repository: automagik-dev/genie

Length of output: 868


Fix shell injection vulnerability: use execFileSync with argv form instead of execSync with template strings.

The current code is vulnerable to shell injection via crafted file paths. JSON.stringify() does not prevent shell metacharacters (backticks, $()) from breaking out. Use execFileSync with the argv array form to avoid shell parsing entirely.

Safer invocation (no shell parsing)
-import { execSync } from 'node:child_process';
+import { execFileSync } from 'node:child_process';
@@
-    const log = execSync(`git log --oneline -n ${MAX_COMMITS} -- ${JSON.stringify(filePath)}`, {
+    const log = execFileSync('git', ['log', '--oneline', '-n', String(MAX_COMMITS), '--', filePath], {
       encoding: 'utf-8',
       timeout: 5000,
       cwd,
       stdio: ['pipe', 'pipe', 'pipe'],
     });

Note: The same vulnerability pattern exists in src/hooks/handlers/freshness.ts (lines 23 and 71) and should be fixed as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/handlers/audit-context.ts` around lines 20 - 25, The git log
invocation uses execSync with a template string (execSync(`git log ...
${JSON.stringify(filePath)}`)) which allows shell injection; replace it with
execFileSync and pass arguments as an argv array (e.g., execFileSync('git',
['log','--oneline','-n', String(MAX_COMMITS),'--', filePath], {encoding:'utf-8',
timeout:5000, cwd, stdio:['pipe','pipe','pipe']}) so the filePath is not
shell-interpolated; update the call in audit-context (the execSync that assigns
to log and references MAX_COMMITS, filePath, cwd) and make the identical change
in freshness.ts for both occurrences (the execSync at the top and the other call
around line 71).

Comment on lines +26 to +28
function sessionKey(payload: HookPayload): string {
return payload.session_id ?? `${process.pid}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

process.pid fallback collapses distinct sessions without session_id.

When session_id is missing, all calls in the same process share one key, so only the first gets enrichment even across different working directories.

Suggested fix
 function sessionKey(payload: HookPayload): string {
-  return payload.session_id ?? `${process.pid}`;
+  const cwdPart = payload.cwd ?? process.cwd();
+  return payload.session_id ?? `${process.pid}:${cwdPart}`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/handlers/brain-inject.ts` around lines 26 - 28, The sessionKey
function currently uses process.pid as a fallback which collapses distinct
requests in the same process; update sessionKey (the function taking
HookPayload/session_id) to produce a deterministic per-session key when
session_id is absent by incorporating additional unique context from the payload
(for example payload.cwd or working directory, payload.request_id if present) or
by generating a stable unique identifier (UUID/timestamp+random) per incoming
payload so different sessions in the same process don’t share the same key;
change only sessionKey to combine process.pid with payload-specific data or a
generated UUID to ensure uniqueness.

Comment on lines +31 to +33
function isBrainAvailable(): boolean {
return existsSync(join(BRAIN_DIR, 'package.json'));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify current implementation uses cwd-relative filesystem probing
rg -n --type=ts -C2 "BRAIN_DIR|isBrainAvailable|existsSync\\(|import\\(BRAIN_PKG\\)" src/hooks/handlers/brain-inject.ts

# Verify payload provides cwd (potentially different from process cwd)
rg -n --type=ts -C3 "interface HookPayload|cwd\\??:|session_id\\??:" src/hooks/types.ts
rg -n --type=ts -C2 "payload\\.cwd|process\\.cwd\\(" src/hooks/handlers/brain-inject.ts

Repository: automagik-dev/genie

Length of output: 1107


isBrainAvailable() can silently disable enrichment in workspace setups.

Using existsSync(join('node_modules/@automagik/genie-brain', 'package.json')) checks a cwd-relative path that fails when node_modules lives in a parent directory (monorepos, workspaces). The early return at line 69 prevents enrichment even though import('@automagik/genie-brain') would succeed, since Node's module resolution is more sophisticated than filesystem probing. The actual import is already wrapped in try-catch (lines 37–40), so the redundant availability check should be removed to let the import fail gracefully only when needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/handlers/brain-inject.ts` around lines 31 - 33, The
isBrainAvailable() filesystem probe (which checks existsSync(join(BRAIN_DIR,
'package.json'))) can falsely report the brain package missing in
workspace/monorepo setups; remove the redundant availability check and the early
return that uses it so the code relies solely on the dynamic
import('@automagik/genie-brain') wrapped in its try/catch. Specifically, delete
the isBrainAvailable function and remove the conditional that returns early (the
check around isBrainAvailable() that prevents enrichment), allowing the existing
try/catch import logic to determine availability and handle errors.

Comment on lines +23 to +28
const output = execSync(`git log -1 --format="%at|%an|%s" -- ${JSON.stringify(filePath)}`, {
encoding: 'utf-8',
timeout: 5000,
cwd,
stdio: ['pipe', 'pipe', 'pipe'],
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find shell-interpolated git execSync usage in handlers
rg -n --type ts 'execSync\(`git .*filePath' src/hooks/handlers

Repository: automagik-dev/genie

Length of output: 452


🏁 Script executed:

cat -n src/hooks/handlers/freshness.ts | head -80

Repository: automagik-dev/genie

Length of output: 3439


🏁 Script executed:

cat -n src/hooks/handlers/audit-context.ts | head -30

Repository: automagik-dev/genie

Length of output: 1278


🏁 Script executed:

rg -n 'execSync\(`' src/hooks/handlers --type ts -A 4

Repository: automagik-dev/genie

Length of output: 2628


Replace shell-interpolated execSync calls with argv-based child-process APIs.

Three git calls across two handlers use unsafe shell-interpolated execSync: freshness.ts (lines 23 and 71) and audit-context.ts (line 20). Even with JSON.stringify, shell metacharacter injection is possible.

Replace all three with execFileSync and argv arrays:

Safer command execution pattern
-import { execSync } from 'node:child_process';
+import { execFileSync } from 'node:child_process';
@@
-const output = execSync(`git log -1 --format="%at|%an|%s" -- ${JSON.stringify(filePath)}`, {
+const output = execFileSync('git', ['log', '-1', '--format=%at|%an|%s', '--', filePath], {
@@
-const status = execSync(`git status --porcelain -- ${JSON.stringify(filePath)}`, {
+const status = execFileSync('git', ['status', '--porcelain', '--', filePath], {
@@
-const log = execSync(`git log --oneline -n ${MAX_COMMITS} -- ${JSON.stringify(filePath)}`, {
+const log = execFileSync('git', ['log', '--oneline', '-n', String(MAX_COMMITS), '--', filePath], {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/handlers/freshness.ts` around lines 23 - 28, Replace unsafe
shell-interpolated execSync calls with execFileSync using argv arrays: locate
the uses of execSync in this file (the call that builds the git command with `--
${JSON.stringify(filePath)}`) and in audit-context.ts, and change them to call
child_process.execFileSync('git', ['log', '-1', '--format=%at|%an|%s', '--',
filePath], { encoding: 'utf-8', timeout: 5000, cwd, stdio:
['pipe','pipe','pipe'] }) (and similarly for the other git invocations),
ensuring you pass filePath as a separate argv element and keep existing options
(encoding, timeout, cwd, stdio) intact to avoid shell interpolation
vulnerabilities.


if (commitInfo && commitInfo.age < STALENESS_THRESHOLD_SECS) {
// Skip warning if the current agent made the change
if (currentAgent && commitInfo.author.includes(currentAgent)) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use exact author matching, not substring matching.

includes() can treat different authors as the same agent (e.g., qa vs aqua) and incorrectly suppress warnings.

Safer identity check
-    if (currentAgent && commitInfo.author.includes(currentAgent)) return;
+    if (currentAgent && commitInfo.author.trim() === currentAgent.trim()) return;
📝 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.

Suggested change
if (currentAgent && commitInfo.author.includes(currentAgent)) return;
if (currentAgent && commitInfo.author.trim() === currentAgent.trim()) return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/handlers/freshness.ts` at line 111, The code currently uses
substring matching (commitInfo.author.includes(currentAgent)) which can
false-match; change this to an exact identity check by comparing normalized full
author names (e.g., commitInfo.author.trim().toLowerCase() ===
currentAgent.trim().toLowerCase()) or, if commitInfo.author can contain multiple
names/emails, split it into an array and check exact membership (e.g.,
commitInfo.author.split(/[;,]/).map(s=>s.trim().toLowerCase()).includes(currentAgent.trim().toLowerCase()));
update the check in the freshness handler where currentAgent and
commitInfo.author are referenced and remove the use of includes() for substring
matching.

Comment on lines +154 to +193
test('symlinks node_modules from parent repo into worktree', async () => {
// Create a fake node_modules in the test repo
const parentNodeModules = join(TEST_REPO, 'node_modules');
await mkdir(parentNodeModules, { recursive: true });
await writeFile(join(parentNodeModules, '.package-lock.json'), '{}');

const config = await createTeam('feat/symlink-nm', TEST_REPO, 'dev');
const worktreeNodeModules = join(config.worktreePath, 'node_modules');

expect(existsSync(worktreeNodeModules)).toBe(true);
expect(lstatSync(worktreeNodeModules).isSymbolicLink()).toBe(true);
expect(readlinkSync(worktreeNodeModules)).toBe(parentNodeModules);

// Clean up
await rm(parentNodeModules, { recursive: true, force: true });
});

test('skips node_modules symlink when parent has no node_modules', async () => {
const config = await createTeam('feat/no-nm', TEST_REPO, 'dev');
const worktreeNodeModules = join(config.worktreePath, 'node_modules');

expect(existsSync(worktreeNodeModules)).toBe(false);
});

test('runs .genie/init.sh in worktree after clone', async () => {
// Create a .genie/init.sh that writes a marker file
const genieDir = join(TEST_REPO, '.genie');
await mkdir(genieDir, { recursive: true });
await writeFile(join(genieDir, 'init.sh'), '#!/bin/bash\necho "init-ran" > .init-marker');
await chmod(join(genieDir, 'init.sh'), 0o755);

const config = await createTeam('feat/init-sh', TEST_REPO, 'dev');
const marker = join(config.worktreePath, '.init-marker');

expect(existsSync(marker)).toBe(true);

// Clean up
await rm(join(genieDir, 'init.sh'));
await rm(marker, { force: true });
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Make fixture cleanup failure-safe (afterEach or try/finally).

These tests clean up at the end of each test body. If an assertion fails early, state can leak (node_modules, .genie/init.sh, marker files) and cascade into later tests.

As per coding guidelines, "Use tmpdir with cleanup in afterEach for test fixtures".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/team-manager.test.ts` around lines 154 - 193, The tests create
temporary fixtures (node_modules, .genie/init.sh, .init-marker) inside TEST_REPO
but clean them up inline, which can leak state on assertion failures; update the
test file to create and teardown fixtures using a safe pattern (use tmpdir or
ensure cleanup in afterEach / try/finally) so teardown always runs: for the
tests referencing createTeam('feat/symlink-nm'...), createTeam('feat/no-nm'...),
and createTeam('feat/init-sh'...), move creation of parentNodeModules, genieDir,
init.sh and marker into per-test setup and remove them in an afterEach that
force-deletes those paths (or wrap each test body in try/finally) to guarantee
cleanup even if assertions fail.

Comment thread src/lib/team-manager.ts
Comment on lines +230 to +237
// Run .genie/init.sh if it exists in the repo (post-clone hook convention)
const initScript = join(repoPath, '.genie', 'init.sh');
if (existsSync(initScript)) {
try {
await $`bash ${initScript}`.cwd(worktreePath).quiet();
} catch {
// Best-effort — init script failure shouldn't block team creation
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Require explicit trust opt-in before running .genie/init.sh.

This runs repository script code automatically during team creation. That is implicit code execution and should be gated behind an explicit opt-in (env/config/flag) before execution.

Suggested hardening
-  if (existsSync(initScript)) {
+  const allowRepoInit = process.env.GENIE_ALLOW_REPO_INIT === '1';
+  if (allowRepoInit && existsSync(initScript)) {
     try {
       await $`bash ${initScript}`.cwd(worktreePath).quiet();
     } catch {
       // Best-effort — init script failure shouldn't block team creation
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/team-manager.ts` around lines 230 - 237, The code currently executes
the repo's .genie/init.sh unconditionally; require an explicit opt-in (env var
or team config flag) before running it by checking a named flag (e.g.,
process.env.RUN_GENIE_INIT or a boolean on the team creation config) immediately
before the block that computes initScript and runs $`bash ${initScript}`; if the
flag is not set skip execution and log/debug that the init script was not run,
and if it is set run the same try/catch best-effort logic; reference the
initScript variable and the bash execution call in team-manager.ts to locate
where to add the gate.

Comment thread src/term-commands/team.ts
Comment on lines +372 to +376
const broadcasts = await getRecentBroadcasts(config.name);
if (broadcasts.length > 0) {
const broadcastLines = broadcasts.map((b) => `[${b.sender}] ${b.text}`).join('\n');
kickoffPrompt += `\n\nRecent team broadcasts for context:\n${broadcastLines}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Cap broadcast context size before appending to initialPrompt.

Injected message bodies are unbounded here; large broadcasts can bloat the kickoff prompt and cause downstream spawn/arg-size failures.

Proposed guardrail
+  const MAX_BROADCAST_CHARS = 4000;
+  const MAX_LINE_CHARS = 300;
   const broadcasts = await getRecentBroadcasts(config.name);
   if (broadcasts.length > 0) {
-    const broadcastLines = broadcasts.map((b) => `[${b.sender}] ${b.text}`).join('\n');
+    const broadcastLines = broadcasts
+      .map((b) => `[${b.sender}] ${b.text.slice(0, MAX_LINE_CHARS)}`)
+      .join('\n')
+      .slice(0, MAX_BROADCAST_CHARS);
     kickoffPrompt += `\n\nRecent team broadcasts for context:\n${broadcastLines}`;
   }

Also applies to: 432-436

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/term-commands/team.ts` around lines 372 - 376, The recent broadcasts
appended to kickoffPrompt (from getRecentBroadcasts) are unbounded and can bloat
initialPrompt; limit the context by capping the number of broadcasts (e.g., take
last N items), truncate each broadcast.text to a max length (e.g., 512 chars)
with an ellipsis, and/or enforce a total character budget (e.g., 2KB) before
joining; apply the same guardrail where broadcasts are appended around lines
432-436 as well, ensuring you update the mapping that builds broadcastLines and
then append the shortened/filtered string to kickoffPrompt (refer to
getRecentBroadcasts, kickoffPrompt, and the broadcastLines construction).

Test and others added 2 commits April 4, 2026 21:30
Tests now run against a dedicated pgserve spawned in --ram mode (native
postgres with data in /dev/shm) on port 20900+, isolated from the
production daemon at port 19642 and from ~/.genie/data/pgserve.

Eliminates PG deadlock flakiness in team.test.ts that was blocking the
pre-push hook. Root cause: tests and the live genie serve daemon shared
one PostgreSQL database, and their asymmetric catalog-lock orderings
tripped PG's deadlock detector under concurrent load. Full analysis in
.genie/wishes/test-pg-ram-isolation/TRACE.md.

Changes:
- bunfig.toml: [test] preload = ["./src/lib/test-setup.ts"]
- src/lib/test-setup.ts: spawns pgserve --ram on 20900-20999, reaps
  orphaned wrappers from previous runs at startup (bun test hard-exits
  without firing exit hooks).
- src/lib/db.ts: ensurePgserve() honors GENIE_TEST_PG_PORT, production
  paths unchanged when unset.
- src/lib/test-setup.test.ts: proves preload wiring and port binding.

Validation:
- 5 consecutive full-suite runs: 2052 pass, 0 fail, ~51s each.
- bun run check passes on first attempt in 61s.
- Orphan reaper keeps steady state of 1 pgserve between runs.
fix(omni-bridge): harden session lifecycle + liveness check + collision-resistant window names
@gitguardian

gitguardian Bot commented Apr 5, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 4 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
18760271 Triggered Generic Password a506587 src/lib/test-setup.ts View secret
18760271 Triggered Generic Password a506587 src/lib/test-setup.ts View secret
18760271 Triggered Generic Password c866c9a src/lib/test-setup.ts View secret
18760271 Triggered Generic Password c866c9a src/lib/test-setup.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@namastex888
namastex888 merged commit 2c96b59 into main Apr 5, 2026
10 of 13 checks passed
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.

2 participants