Add swarm templates for predefined multi-agent workflows#19
Conversation
Introduce `ppg swarm <template>` command that spawns multiple agents from YAML-defined swarm templates in `.pg/swarms/`. Supports shared (single worktree) and isolated (per-agent worktree) strategies, template variables via `--var`, and targeting existing worktrees with `--worktree`. Includes bundled `code-review` swarm with quality, security, and regression review prompts. Templates and prompts are written during `ppg init`. Also adds `ppg list swarms` and updates conductor skill references.
📝 WalkthroughWalkthroughAdds swarm template support: new CLI Changes
Sequence DiagramsequenceDiagram
actor User
participant CLI
participant SwarmCmd as "swarmCommand"
participant SwarmCore as "loadSwarms / listSwarms"
participant Manifest
participant Spawner
participant TMUX
participant FS
User->>CLI: ppg swarm code-review --var KEY=value
CLI->>SwarmCmd: invoke swarmCommand(template, options)
SwarmCmd->>Manifest: load manifest
SwarmCmd->>SwarmCore: loadSwarm(projectRoot, "code-review")
SwarmCore->>FS: read swarm YAML
FS-->>SwarmCore: YAML content
SwarmCore-->>SwarmCmd: SwarmTemplate
SwarmCmd->>SwarmCmd: choose strategy (shared / isolated / existing)
alt Shared
SwarmCmd->>FS: create worktree
FS-->>SwarmCmd: worktree path
SwarmCmd->>TMUX: create session/window
loop per agent
SwarmCmd->>FS: load prompt file
FS-->>SwarmCmd: prompt content
SwarmCmd->>Spawner: spawn agent (rendered prompt)
Spawner->>TMUX: attach agent to window
TMUX-->>Spawner: spawned
end
else Isolated
loop per agent
SwarmCmd->>FS: create worktree for agent
SwarmCmd->>TMUX: create session/window
SwarmCmd->>Spawner: spawn agent
end
else Existing
SwarmCmd->>TMUX: ensure window/session
loop new agents
SwarmCmd->>Spawner: spawn agent into worktree
end
end
SwarmCmd->>Manifest: register worktrees & agents
Manifest-->>SwarmCmd: updated
SwarmCmd-->>User: output JSON or human-readable result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
Validate template name argument and YAML swarm name against SAFE_NAME regex before using as filesystem path or branch name components. Return new object from loadSwarm instead of mutating parsed YAML in place.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f390ebffed
ℹ️ 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".
| const wtName = `${baseName}-${swarmAgent.prompt}`; | ||
| const branchName = `ppg/${wtName}`; |
There was a problem hiding this comment.
Make isolated swarm worktree names collision-safe
In isolated mode, each branch/worktree name is derived only from swarmAgent.prompt, so a template with two agents sharing the same prompt (for example, same prompt text with different agent backends) produces duplicate ppg/<base>-<prompt> branch names. The second createWorktree call then fails because the branch already exists, aborting the swarm before all agents are launched.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in commit cf7e984. swarmIsolated() now tracks used names in a Set and appends an incrementing suffix (e.g., -2, -3) when a duplicate baseName-prompt combination is detected. This ensures unique branch names even when multiple agents share the same prompt name.
See src/commands/swarm.ts lines 192-199.
There was a problem hiding this comment.
Fixed in cf7e984. Isolated mode now tracks used names with a Set<string> and appends a numeric suffix (-2, -3, etc.) when a collision is detected. This handles templates with duplicate prompt references without aborting the swarm.
The fix is in src/commands/swarm.ts:192-201. Tests added in src/core/swarm.test.ts to cover the dedup behavior.
| swarmNames.map(async (name) => { | ||
| const swarm = await loadSwarm(projectRoot, name); | ||
| return { | ||
| name: swarm.name, |
There was a problem hiding this comment.
List swarms using the invokable template key
This command loads swarm files by filename (name from listSwarms) but reports name: swarm.name, which can differ from the filename; in that case ppg list swarms advertises a name users cannot actually pass to ppg swarm <template>. For example, legacy.yml with name: code-review will be listed as code-review, but only ppg swarm legacy resolves.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in commit cf7e984. listSwarmsCommand() now displays the filename-based key (from listSwarms()) instead of swarm.name from the YAML content, so the listed name matches what users pass to ppg swarm <template>.
See src/commands/list.ts line 82: changed name: swarm.name to name (the filename key).
There was a problem hiding this comment.
Fixed in cf7e984. ppg list swarms now displays the filename key (the value returned by listSwarms()) instead of swarm.name. This ensures the listed name matches what ppg swarm <template> actually resolves.
The change is a one-liner in src/commands/list.ts:82: name: swarm.name → name (uses the filename key from the map callback).
| let filePath = path.join(dir, `${name}.yaml`); | ||
| try { | ||
| await fs.access(filePath); | ||
| } catch { | ||
| filePath = path.join(dir, `${name}.yml`); |
There was a problem hiding this comment.
Reject traversal in swarm template names
The template argument is concatenated into path.join(dir, ${name}.yaml)/.yml without validating name, so inputs like ../../tmp/custom resolve outside .pg/swarms and allow loading arbitrary YAML files from elsewhere on disk. This breaks the documented scope of swarm templates and should be blocked with a safe-name check before path construction.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in commit b7e8930. loadSwarm() now validates the template name argument against SAFE_NAME (/^[\w-]+$/) before constructing any path, rejecting traversal patterns like ../../tmp/custom. The YAML name field is also validated the same way. Tests added in swarm.test.ts covering path traversal, slashes, and spaces for both inputs.
There was a problem hiding this comment.
Already addressed in b7e8930. The SAFE_NAME regex (/^[\w-]+$/) validation was present in the original commit at src/core/swarm.ts:35-40 — it rejects any name containing path traversal characters (., /, \) before the path is constructed.
Additionally, parsed.name and each agent.prompt are also validated against SAFE_NAME during template loading (lines 64 and 87).
Add index suffix to deduplicate worktree names when isolated strategy has agents with the same prompt. Show filename-based key in list swarms output so users can invoke the correct template name.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/commands/init.ts (1)
209-215:⚠️ Potential issue | 🟡 MinorInconsistency:
.pg/swarms/is not gitignored while.pg/prompts/is.After
ppg init,.pg/swarms/code-review.yamlwill appear as an untracked file in git, while.pg/prompts/review-*.mdfiles are invisible (gitignored). Since both are written from the same bundled-content workflow and regenerated on eachppg init, they should be treated consistently — either both gitignored (generated artifacts) or both tracked (shareable config).🔍 Proposed fix (treating swarms as tracked config)
If swarm templates should be team-shareable (committed), remove
.pg/prompts/from the gitignore list and let users decide for both. If both are generated artifacts, add.pg/swarms/to the list:const entriesToAdd = [ '.pg/results/', '.pg/logs/', '.pg/manifest.json', '.pg/prompts/', '.pg/conductor-context.md', + '.pg/swarms/', ];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/init.ts` around lines 209 - 215, The entriesToAdd array currently gitignores '.pg/prompts/' but not '.pg/swarms/', causing '.pg/swarms/code-review.yaml' to be left untracked; update the entriesToAdd constant to include '.pg/swarms/' (or, if you prefer to keep swarms tracked, remove '.pg/prompts/' instead) so both '.pg/prompts/' and '.pg/swarms/' are treated consistently; modify the entriesToAdd array in init.ts to reflect the chosen option.
🧹 Nitpick comments (2)
src/commands/swarm.ts (2)
92-101: Template-level agent vars silently override user--varvalues.The spread order on lines 99-100 means
swarmAgent.varswins overuserVarswhen keys collide. If this is intentional (template author locks values), consider documenting it. If user vars should take precedence, swap the spread order....userVars, ...(swarmAgent.vars ?? {}), // ← overwrites matching userVars keys🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/swarm.ts` around lines 92 - 101, The TemplateContext construction currently spreads userVars then swarmAgent.vars so template-level vars in swarmAgent.vars override user-specified --var values; update the spread order in the ctx object so that swarmAgent.vars is spread first and userVars spread after (i.e., ensure userVars wins on key collisions), or if the opposite behavior is intentional, add clear documentation/comments near TemplateContext/ctx explaining that swarmAgent.vars intentionally lock/override user vars.
140-168: Partial failure leaves spawned agents untracked in the manifest.If
spawnSwarmAgentthrows for the N-th agent, agents0…N-1are already running in tmux but the manifest is never updated (line 165 is only reached after the entire loop completes). Those orphaned agents won't appear inppg statusorppg kill --all.Consider writing agents into the manifest incrementally, or wrapping the loop in a try/catch that persists whatever was successfully spawned before re-throwing.
The same pattern exists in
swarmIntoExistingWorktree(lines 275-297).swarmIsolatedalready handles this correctly by updating the manifest per iteration.♻️ Sketch: incremental manifest update for swarmShared
+ // Create the worktree entry early so partial spawns are tracked + const worktreeEntry: WorktreeEntry = { + id: wtId, + name, + path: wtPath, + branch: branchName, + baseBranch, + status: 'active', + tmuxWindow: windowTarget, + agents: {}, + createdAt: new Date().toISOString(), + }; + + await updateManifest(projectRoot, (m) => { + m.worktrees[wtId] = worktreeEntry; + return m; + }); + const agents: AgentEntry[] = []; for (let i = 0; i < swarm.agents.length; i++) { const target = i === 0 ? windowTarget : await tmux.createWindow(sessionName, `${name}-${i}`, wtPath); const agentEntry = await spawnSwarmAgent({ projectRoot, config, swarmAgent: swarm.agents[i], wtPath, branchName, tmuxTarget: target, taskName: name, userVars, }); agents.push(agentEntry); + await updateManifest(projectRoot, (m) => { + m.worktrees[wtId].agents[agentEntry.id] = agentEntry; + return m; + }); } - const worktreeEntry: WorktreeEntry = { ... }; - await updateManifest(projectRoot, (m) => { - m.worktrees[wtId] = worktreeEntry; - return m; - });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/swarm.ts` around lines 140 - 168, The loop that calls spawnSwarmAgent inside the swarm command can leave already-started agents unrecorded if a later spawn throws; persist each successful agent to the manifest as you go (or catch errors, write the partial state, then re-throw) so orphaned tmux agents are tracked. Specifically, after each successful spawnSwarmAgent return (inside the for loop that builds agents[] and before continuing to the next iteration), call updateManifest to add that agent into m.worktrees[wtId].agents (or update the existing worktree entry) so partial successes are durable; apply the same defensive change to swarmIntoExistingWorktree where the same pattern appears and ensure updateManifest usage references the same worktree id and agent.id keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@skills/ppg-conductor/references/commands.md`:
- Around line 216-217: Add a blank line between the "**Options:**" heading and
the table header to satisfy markdownlint rule MD058; locate the "**Options:**"
text in references/commands.md and insert one empty line immediately after it so
the table starts on the next line.
In `@src/commands/init.ts`:
- Around line 113-121: Replace the inline path construction with the existing
helper: import promptFile (add it to the imports on line 5) and use
promptFile(projectRoot, name) instead of path.join(promptsDir(projectRoot),
`${name}.md`) inside the loop where bundledPrompts is iterated (the block that
currently assigns pPath and calls fs.access/fs.writeFile and info). Keep the
rest of the logic (try/catch, fs.access, fs.writeFile, info) unchanged.
In `@src/commands/list.ts`:
- Around line 73-76: The early-return for empty swarms (the if
(swarmNames.length === 0) block) currently prints plain text and returns; update
it to honor the command's JSON flag by checking the flag (the same --json/flags
variable used elsewhere in this file) and when JSON is requested print a
machine-readable empty result like JSON.stringify({swarms: []}) to stdout,
otherwise keep the existing console.log('No swarm templates found in
.pg/swarms/'); then return as before. Ensure you reference the existing json
flag variable used by this command so the behavior matches other code paths.
In `@src/core/swarm.test.ts`:
- Around line 27-34: The test assumes stable ordering from fs.readdir; update
listSwarms to return a deterministically ordered list (e.g., sort the filenames
after stripping the .yaml extension) so consumers and tests get a stable,
alphabetical array, and then update/keep the test ('given a directory with .yaml
files, should return names without extension') to assert the sorted result;
locate and change the listSwarms function to perform the sort before returning
the names.
- Around line 151-154: The test reveals that loadSwarm's parsing step can return
null (yaml.parse('') === null) and the subsequent guard in loadSwarm that checks
parsed.name/agents causes a TypeError instead of the intended PgError
INVALID_ARGS; update loadSwarm to first check for a null/undefined parsed result
(e.g., if (!parsed) throw new PgError('missing name or agents', 'INVALID_ARGS'))
before accessing parsed.name or parsed.agents so empty YAML files produce the
proper INVALID_ARGS PgError (add the corresponding test that writes an
empty.yaml and expects INVALID_ARGS to confirm the behavior).
In `@src/core/swarm.ts`:
- Around line 50-55: YAML.parse can return null for empty files, so add a
null-safe check immediately after calling YAML.parse in the function where
`parsed` is created (the `parsed` variable from `YAML.parse` cast to
`SwarmTemplate`) and before accessing `parsed.name` or `parsed.agents`; replace
the current validation with a guard like "if (!parsed || typeof parsed !==
'object' || !parsed.name || !parsed.agents || !Array.isArray(parsed.agents))
throw new PgError(...)" so the code throws the intended PgError (same message
and code) instead of allowing a TypeError.
---
Outside diff comments:
In `@src/commands/init.ts`:
- Around line 209-215: The entriesToAdd array currently gitignores
'.pg/prompts/' but not '.pg/swarms/', causing '.pg/swarms/code-review.yaml' to
be left untracked; update the entriesToAdd constant to include '.pg/swarms/'
(or, if you prefer to keep swarms tracked, remove '.pg/prompts/' instead) so
both '.pg/prompts/' and '.pg/swarms/' are treated consistently; modify the
entriesToAdd array in init.ts to reflect the chosen option.
---
Nitpick comments:
In `@src/commands/swarm.ts`:
- Around line 92-101: The TemplateContext construction currently spreads
userVars then swarmAgent.vars so template-level vars in swarmAgent.vars override
user-specified --var values; update the spread order in the ctx object so that
swarmAgent.vars is spread first and userVars spread after (i.e., ensure userVars
wins on key collisions), or if the opposite behavior is intentional, add clear
documentation/comments near TemplateContext/ctx explaining that swarmAgent.vars
intentionally lock/override user vars.
- Around line 140-168: The loop that calls spawnSwarmAgent inside the swarm
command can leave already-started agents unrecorded if a later spawn throws;
persist each successful agent to the manifest as you go (or catch errors, write
the partial state, then re-throw) so orphaned tmux agents are tracked.
Specifically, after each successful spawnSwarmAgent return (inside the for loop
that builds agents[] and before continuing to the next iteration), call
updateManifest to add that agent into m.worktrees[wtId].agents (or update the
existing worktree entry) so partial successes are durable; apply the same
defensive change to swarmIntoExistingWorktree where the same pattern appears and
ensure updateManifest usage references the same worktree id and agent.id keys.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
skills/ppg-conductor/references/commands.mdskills/ppg-conductor/references/conductor.mdskills/ppg-conductor/references/modes.mdsrc/bundled/prompts.tssrc/bundled/swarms.tssrc/cli.tssrc/commands/init.tssrc/commands/list.tssrc/commands/swarm.tssrc/core/swarm.test.tssrc/core/swarm.tssrc/lib/paths.ts
…leanup - Add null guard after YAML.parse to handle empty/malformed YAML files - Honor --json flag in empty swarms early exit for machine-readable output - Use promptFile() path helper in init.ts instead of inline construction - Add blank lines before tables in commands.md (MD058) - Use order-independent assertion for listSwarms test - Add test case for empty YAML file edge case
…ence - Write worktree to manifest before spawning agents and add each agent incrementally so partial failures leave tracked state - User --var flags now take precedence over template-level agent vars - Apply same incremental pattern to swarmIntoExistingWorktree
There was a problem hiding this comment.
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/commands/list.ts (1)
24-32:⚠️ Potential issue | 🟡 MinorHonor
--jsonwhen no templates exist.
The empty-templates early return prints plain text even when JSON is requested, which breaks machine-readable output.🔧 Proposed fix
if (templateNames.length === 0) { - console.log('No templates found in .pg/templates/'); + if (options.json) { + output({ templates: [] }, true); + } else { + console.log('No templates found in .pg/templates/'); + } return; }As per coding guidelines: "Every command must support
--jsonflag for machine-readable output. Useoutput(data, json)andoutputError(error, json)fromlib/output.ts."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/list.ts` around lines 24 - 32, The early-return in listTemplatesCommand currently prints plain text when templateNames is empty; change it to honor options.json by calling output({ templates: [] }, options.json) (or outputError if you want an error payload) instead of console.log, i.e. import and use output/exported helpers from lib/output.ts and replace the console.log('No templates...') branch so it returns a machine-readable JSON payload when options.json is true; reference listTemplatesCommand, ListOptions, templateNames, output and outputError to locate and update the logic accordingly.
🤖 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/commands/swarm.ts`:
- Around line 193-237: The worktree is created (createWorktree,
tmux.createWindow) but only written to the manifest after spawnSwarmAgent
succeeds, so if spawnSwarmAgent throws the worktree and tmux window are
orphaned; fix by registering the worktreeEntry in updateManifest immediately
after creating the worktree/tmux (set agents: {} initially and status 'active'),
then call spawnSwarmAgent and, on success, update the manifest entry to add the
returned agentEntry; also wrap spawnSwarmAgent in try/catch to rollback (remove
manifest entry and close tmux window / delete worktree) if it throws to avoid
orphaned resources (refer to functions/genWorktreeId, worktreeEntry,
spawnSwarmAgent, updateManifest, tmux.createWindow).
---
Outside diff comments:
In `@src/commands/list.ts`:
- Around line 24-32: The early-return in listTemplatesCommand currently prints
plain text when templateNames is empty; change it to honor options.json by
calling output({ templates: [] }, options.json) (or outputError if you want an
error payload) instead of console.log, i.e. import and use output/exported
helpers from lib/output.ts and replace the console.log('No templates...') branch
so it returns a machine-readable JSON payload when options.json is true;
reference listTemplatesCommand, ListOptions, templateNames, output and
outputError to locate and update the logic accordingly.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
skills/ppg-conductor/references/commands.mdsrc/commands/init.tssrc/commands/list.tssrc/commands/swarm.tssrc/core/swarm.test.tssrc/core/swarm.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/core/swarm.test.ts
- src/commands/init.ts
- src/core/swarm.ts
| const worktrees: Array<{ id: string; name: string; branch: string; path: string; tmuxWindow: string }> = []; | ||
| const allAgents: AgentEntry[] = []; | ||
|
|
||
| const usedNames = new Set<string>(); | ||
| for (const swarmAgent of swarm.agents) { | ||
| const wtId = genWorktreeId(); | ||
| let wtName = `${baseName}-${swarmAgent.prompt}`; | ||
| if (usedNames.has(wtName)) { | ||
| let suffix = 2; | ||
| while (usedNames.has(`${wtName}-${suffix}`)) suffix++; | ||
| wtName = `${wtName}-${suffix}`; | ||
| } | ||
| usedNames.add(wtName); | ||
| const branchName = `ppg/${wtName}`; | ||
|
|
||
| info(`Creating worktree ${wtId} on branch ${branchName}`); | ||
| const wtPath = await createWorktree(projectRoot, wtId, { | ||
| branch: branchName, | ||
| base: baseBranch, | ||
| }); | ||
|
|
||
| await setupWorktreeEnv(projectRoot, wtPath, config); | ||
| const windowTarget = await tmux.createWindow(sessionName, wtName, wtPath); | ||
|
|
||
| const agentEntry = await spawnSwarmAgent({ | ||
| projectRoot, config, swarmAgent, | ||
| wtPath, branchName, tmuxTarget: windowTarget, taskName: wtName, userVars, | ||
| }); | ||
|
|
||
| const worktreeEntry: WorktreeEntry = { | ||
| id: wtId, | ||
| name: wtName, | ||
| path: wtPath, | ||
| branch: branchName, | ||
| baseBranch, | ||
| status: 'active', | ||
| tmuxWindow: windowTarget, | ||
| agents: { [agentEntry.id]: agentEntry }, | ||
| createdAt: new Date().toISOString(), | ||
| }; | ||
|
|
||
| await updateManifest(projectRoot, (m) => { | ||
| m.worktrees[wtId] = worktreeEntry; | ||
| return m; | ||
| }); |
There was a problem hiding this comment.
Register isolated worktrees before spawning agents.
If spawnSwarmAgent throws, the worktree and tmux window are created but never recorded in the manifest, leaving orphaned resources and making cleanup harder.
🛠️ Proposed fix
- const agentEntry = await spawnSwarmAgent({
- projectRoot, config, swarmAgent,
- wtPath, branchName, tmuxTarget: windowTarget, taskName: wtName, userVars,
- });
-
- const worktreeEntry: WorktreeEntry = {
+ const worktreeEntry: WorktreeEntry = {
id: wtId,
name: wtName,
path: wtPath,
branch: branchName,
baseBranch,
status: 'active',
tmuxWindow: windowTarget,
- agents: { [agentEntry.id]: agentEntry },
+ agents: {},
createdAt: new Date().toISOString(),
};
await updateManifest(projectRoot, (m) => {
m.worktrees[wtId] = worktreeEntry;
return m;
});
+
+ const agentEntry = await spawnSwarmAgent({
+ projectRoot, config, swarmAgent,
+ wtPath, branchName, tmuxTarget: windowTarget, taskName: wtName, userVars,
+ });
+
+ await updateManifest(projectRoot, (m) => {
+ m.worktrees[wtId].agents[agentEntry.id] = agentEntry;
+ return m;
+ });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/swarm.ts` around lines 193 - 237, The worktree is created
(createWorktree, tmux.createWindow) but only written to the manifest after
spawnSwarmAgent succeeds, so if spawnSwarmAgent throws the worktree and tmux
window are orphaned; fix by registering the worktreeEntry in updateManifest
immediately after creating the worktree/tmux (set agents: {} initially and
status 'active'), then call spawnSwarmAgent and, on success, update the manifest
entry to add the returned agentEntry; also wrap spawnSwarmAgent in try/catch to
rollback (remove manifest entry and close tmux window / delete worktree) if it
throws to avoid orphaned resources (refer to functions/genWorktreeId,
worktreeEntry, spawnSwarmAgent, updateManifest, tmux.createWindow).
Addressing CodeRabbit review body itemsResponding to the 3 items from the CodeRabbit review body that were outside the inline comment scope: 1. Outside diff:
|
Summary
ppg swarm <template>command that spawns multiple agents from YAML-defined swarm templates in.pg/swarms/--var, and targeting existing worktrees with--worktreecode-reviewswarm with quality, security, and regression review prompts — written duringppg initppg list swarmssubcommand and updates conductor skill references to prefer swarm templatesChanges since initial review
SAFE_NAMEregex to prevent path traversalYAML.parsenull result handled before accessing properties (prevents TypeError on empty files)listSwarms()sorts results alphabetically{"swarms":[]}when--jsonis passed--varflags now override template-level agent varsppg list swarmsdisplays the invokable filename, not the YAMLnamefieldinit.tsusespromptFile()instead of inlinepath.join.pg/swarms/added to gitignore entries for consistency with.pg/prompts/Test plan
npm test— 25 tests pass inswarm.test.ts(listSwarms, loadSwarm validation, name sanitization, empty YAML)npm run typecheck— cleanppg initin fresh repo — confirm.pg/swarms/code-review.yamland.pg/prompts/review-*.mdcreatedppg list swarms— confirm code-review swarm listed with filename keyppg swarm code-review --var CONTEXT="test" --no-open --json— verify agents spawn in shared worktreeppg swarm code-review --worktree <existing-wt> --var CONTEXT="test" --no-open— verify agents attach to existing worktree