feat: add ccs plugin for symlink auto-setup and delegation workflows#96
feat: add ccs plugin for symlink auto-setup and delegation workflows#96masseater wants to merge 2 commits into
Conversation
Move ccs-handoff skill from mutils to the new ccs plugin and integrate ccs-delegation skill and /ccs commands. The SessionStart hook validates and repairs the CCS shared-context symlink chain automatically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces a new ChangesClaude Code Sessions Plugin
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/ccs/commands/ccs.md`:
- Around line 12-18: Add a language identifier to the fenced code block that
shows the /ccs examples so markdownlint MD040 is satisfied; locate the
triple-backtick block containing the lines beginning with "/ccs \"refactor
auth.js to use async/await\"", and change the opening fence from ``` to ```text
(or another appropriate language tag) so the example is explicitly marked as
plain text.
In `@plugins/ccs/commands/ccs/continue.md`:
- Around line 12-16: The fenced code block containing the example slash commands
(the three lines starting with "/ccs:continue ...") is missing a fence language
and triggers markdownlint MD040; update the opening triple-backtick so it
declares a language (e.g., ```text) for that code fence in
plugins/ccs/commands/ccs/continue.md so the example block that includes the
"/ccs:continue" variants is marked with a language identifier.
In `@plugins/ccs/hooks/entry/ccs-symlink-check.ts`:
- Around line 26-39: ensureSymlink currently never makes changes and only
handles the "missing target" case; update it to repair broken or wrong-type
entries and create the symlink when needed. Specifically, in function
ensureSymlink(linkPath, target) check if linkPath exists: if it's a symlink and
already points to target return false; otherwise remove the existing
file/dir/symlink (handle directories, files, and dangling symlinks), ensure
parent dir exists via ensureDir(path.dirname(linkPath)), ensure target exists
via ensureDir(target) if necessary, create the symlink with
fs.symlinkSync(target, linkPath), and return true to indicate a change. Apply
the same fix to the sibling occurrence referenced in the review (the other block
at lines 127-135).
- Around line 96-107: The parsed YAML may be null or not an object so avoid
directly dereferencing config.accounts; after calling parseYaml(raw) in the try
block, validate that config is a non-null object (e.g. typeof config ===
"object" && config !== null) and that it has an accounts property (or use
optional chaining) before assigning const accounts = config.accounts; if the
shape is invalid, return context.success({}) as currently done; update
references to the symbols parseYaml, config, configPath, and accounts to include
this guard so malformed/empty configs do not throw at runtime.
- Around line 64-83: The replaceWithSymlink function currently falls through and
calls fs.symlinkSync(target, src) when src is an existing regular file, causing
EEXIST; update replaceWithSymlink to handle regular files by removing them
before creating the symlink (e.g., detect stat.isFile() or !isSymbolicLink() &&
!isDirectory() and call fs.unlinkSync(src) prior to fs.symlinkSync(target,
src)), preserving the existing behaviors for symlinks and directories (keep the
current checks for stat.isSymbolicLink() and stat.isDirectory() and reuse
copyDirContents for directories).
In `@plugins/ccs/hooks/install-deps.sh`:
- Around line 6-11: The install marker node_modules/.cc-installed is never
invalidated when package.json changes; update the script so it records a
checksum or timestamp of package.json alongside the marker and compares it on
startup: compute a hash (or mtime) of package.json, read the stored value from
node_modules/.cc-installed (or a companion file), and only exit early if they
match; otherwise run the existing steps (delete bun.lock, bun install
--production, mkdir -p node_modules, touch/update node_modules/.cc-installed)
and write the new checksum/timestamp into the marker so future runs detect
changes.
In `@plugins/ccs/package.json`:
- Around line 10-17: The package.json in the CCS plugin is missing the
prerequisite dependency "plugin-mutils"; update the "dependencies" section in
plugins/ccs/package.json to add "plugin-mutils" (matching the package name used
in plugins/mutils/package.json) so the prereq rule is satisfied, keeping
versioning consistent with your repo policy (e.g., use the same version spec
style as the other entries).
In `@plugins/ccs/skills/ccs-delegation/references/troubleshooting.md`:
- Around line 9-10: The two cross-reference links in troubleshooting.md point to
non-existent docs (headless-workflow.md and delegation-guidelines.md); update
troubleshooting.md to either point to the correct existing filenames/paths or
add the missing documents. Locate the references to headless-workflow.md and
delegation-guidelines.md in the troubleshooting.md content and: a) if equivalent
docs already exist elsewhere in the repo, replace the two filenames with the
correct relative paths/names, or b) if they are missing, create new markdown
files named headless-workflow.md and delegation-guidelines.md with the intended
content and add them to the same docs directory so the links resolve.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 864a652d-abdc-491a-ab6b-8545150c2bfd
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (19)
.claude-plugin/marketplace.jsonAGENTS.mdknip.jsonplugins/ccs/AGENTS.mdplugins/ccs/CLAUDE.mdplugins/ccs/commands/ccs.mdplugins/ccs/commands/ccs/continue.mdplugins/ccs/hooks/entry/ccs-symlink-check.tsplugins/ccs/hooks/hooks.jsonplugins/ccs/hooks/install-deps.shplugins/ccs/package.jsonplugins/ccs/plugin.jsonplugins/ccs/skills/ccs-delegation/CLAUDE.md.templateplugins/ccs/skills/ccs-delegation/SKILL.mdplugins/ccs/skills/ccs-delegation/references/troubleshooting.mdplugins/ccs/skills/ccs-handoff/SKILL.mdplugins/ccs/skills/ccs-handoff/ccs-handoff.tsplugins/ccs/tsconfig.jsonplugins/mutils/AGENTS.md
💤 Files with no reviewable changes (1)
- plugins/mutils/AGENTS.md
| ``` | ||
| /ccs "refactor auth.js to use async/await" # Simple task | ||
| /ccs "analyze entire architecture" # Long-context task | ||
| /ccs "think about caching strategy" # Reasoning task | ||
| /ccs --glm "add tests for UserService" # Force specific profile | ||
| /ccs "/cook create landing page" # Nested slash command | ||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced example block.
The code fence at Line 12 has no language, which triggers markdownlint MD040.
Proposed fix
-```
+```text
/ccs "refactor auth.js to use async/await" # Simple task
/ccs "analyze entire architecture" # Long-context task
/ccs "think about caching strategy" # Reasoning task
/ccs --glm "add tests for UserService" # Force specific profile
/ccs "/cook create landing page" # Nested slash command</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **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.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 12-12: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/ccs/commands/ccs.md` around lines 12 - 18, Add a language identifier
to the fenced code block that shows the /ccs examples so markdownlint MD040 is
satisfied; locate the triple-backtick block containing the lines beginning with
"/ccs \"refactor auth.js to use async/await\"", and change the opening fence
from ``` to ```text (or another appropriate language tag) so the example is
explicitly marked as plain text.
| ``` | ||
| /ccs:continue "also update the examples section" # Use last profile | ||
| /ccs:continue --glm "add unit tests" # Switch profiles | ||
| /ccs:continue "/commit with message" # Nested slash command | ||
| ``` |
There was a problem hiding this comment.
Specify a language for the fenced examples.
Line 12 should declare a fence language to satisfy markdownlint MD040.
Proposed fix
-```
+```text
/ccs:continue "also update the examples section" # Use last profile
/ccs:continue --glm "add unit tests" # Switch profiles
/ccs:continue "/commit with message" # Nested slash command</details>
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>
[warning] 12-12: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @plugins/ccs/commands/ccs/continue.md around lines 12 - 16, The fenced code
block containing the example slash commands (the three lines starting with
"/ccs:continue ...") is missing a fence language and triggers markdownlint
MD040; update the opening triple-backtick so it declares a language (e.g.,
example block that includes the "/ccs:continue" variants is marked with a
language identifier.
| function ensureSymlink(linkPath: string, target: string): boolean { | ||
| if (fs.existsSync(linkPath)) { | ||
| const stat = fs.lstatSync(linkPath); | ||
| if (stat.isSymbolicLink()) { | ||
| const current = fs.readlinkSync(linkPath); | ||
| if (current === target) return false; | ||
| } | ||
| } | ||
| ensureDir(path.dirname(linkPath)); | ||
| if (!fs.existsSync(target)) { | ||
| ensureDir(target); | ||
| } | ||
| return false; | ||
| } |
There was a problem hiding this comment.
groupDir/projects repair is incomplete and misses broken existing links/directories.
ensureSymlink never mutates anything (always returns false), and the fallback block only handles the “missing path” case. If groupProjects exists but points elsewhere (or is a directory), the chain is not repaired.
Suggested fix
function ensureSymlink(linkPath: string, target: string): boolean {
- if (fs.existsSync(linkPath)) {
- const stat = fs.lstatSync(linkPath);
- if (stat.isSymbolicLink()) {
- const current = fs.readlinkSync(linkPath);
- if (current === target) return false;
- }
- }
+ if (fs.existsSync(linkPath)) {
+ const stat = fs.lstatSync(linkPath);
+ if (stat.isSymbolicLink()) {
+ if (fs.readlinkSync(linkPath) === target) return false;
+ fs.unlinkSync(linkPath);
+ } else if (stat.isDirectory()) {
+ if (!fs.existsSync(target)) ensureDir(target);
+ copyDirContents(linkPath, target);
+ fs.rmSync(linkPath, { recursive: true, force: true });
+ } else {
+ fs.rmSync(linkPath, { force: true });
+ }
+ }
ensureDir(path.dirname(linkPath));
- if (!fs.existsSync(target)) {
- ensureDir(target);
- }
- return false;
+ if (!fs.existsSync(target)) ensureDir(target);
+ fs.symlinkSync(target, linkPath);
+ return true;
}Also applies to: 127-135
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/ccs/hooks/entry/ccs-symlink-check.ts` around lines 26 - 39,
ensureSymlink currently never makes changes and only handles the "missing
target" case; update it to repair broken or wrong-type entries and create the
symlink when needed. Specifically, in function ensureSymlink(linkPath, target)
check if linkPath exists: if it's a symlink and already points to target return
false; otherwise remove the existing file/dir/symlink (handle directories,
files, and dangling symlinks), ensure parent dir exists via
ensureDir(path.dirname(linkPath)), ensure target exists via ensureDir(target) if
necessary, create the symlink with fs.symlinkSync(target, linkPath), and return
true to indicate a change. Apply the same fix to the sibling occurrence
referenced in the review (the other block at lines 127-135).
| if (fs.existsSync(src)) { | ||
| const stat = fs.lstatSync(src); | ||
| if (stat.isSymbolicLink()) { | ||
| const current = fs.readlinkSync(src); | ||
| if (current === target) return false; | ||
| fs.unlinkSync(src); | ||
| fs.symlinkSync(target, src); | ||
| return true; | ||
| } | ||
| if (stat.isDirectory()) { | ||
| copyDirContents(src, target); | ||
| fs.rmSync(src, { recursive: true, force: true }); | ||
| fs.symlinkSync(target, src); | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| ensureDir(path.dirname(src)); | ||
| fs.symlinkSync(target, src); | ||
| return true; |
There was a problem hiding this comment.
replaceWithSymlink fails when src is an existing regular file.
When src exists as a file, the function falls through to fs.symlinkSync(target, src) and throws EEXIST, aborting the hook.
Suggested fix
if (fs.existsSync(src)) {
const stat = fs.lstatSync(src);
...
if (stat.isDirectory()) {
copyDirContents(src, target);
fs.rmSync(src, { recursive: true, force: true });
fs.symlinkSync(target, src);
return true;
}
+ // regular file or other existing node
+ fs.rmSync(src, { force: true });
+ fs.symlinkSync(target, src);
+ return true;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/ccs/hooks/entry/ccs-symlink-check.ts` around lines 64 - 83, The
replaceWithSymlink function currently falls through and calls
fs.symlinkSync(target, src) when src is an existing regular file, causing
EEXIST; update replaceWithSymlink to handle regular files by removing them
before creating the symlink (e.g., detect stat.isFile() or !isSymbolicLink() &&
!isDirectory() and call fs.unlinkSync(src) prior to fs.symlinkSync(target,
src)), preserving the existing behaviors for symlinks and directories (keep the
current checks for stat.isSymbolicLink() and stat.isDirectory() and reuse
copyDirContents for directories).
| let config: CcsConfig; | ||
| try { | ||
| const raw = fs.readFileSync(configPath, "utf-8"); | ||
| config = parseYaml(raw) as CcsConfig; | ||
| } catch { | ||
| return context.success({}); | ||
| } | ||
|
|
||
| const accounts = config.accounts; | ||
| if (!accounts) { | ||
| return context.success({}); | ||
| } |
There was a problem hiding this comment.
Guard parsed YAML shape before dereferencing config.accounts.
parseYaml can return null/non-object; current cast allows config.accounts access to throw at runtime on malformed or empty config.
Suggested fix
- let config: CcsConfig;
+ let config: CcsConfig | null = null;
try {
const raw = fs.readFileSync(configPath, "utf-8");
- config = parseYaml(raw) as CcsConfig;
+ const parsed = parseYaml(raw);
+ if (parsed && typeof parsed === "object") {
+ config = parsed as CcsConfig;
+ }
} catch {
return context.success({});
}
- const accounts = config.accounts;
+ const accounts = config?.accounts;📝 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.
| let config: CcsConfig; | |
| try { | |
| const raw = fs.readFileSync(configPath, "utf-8"); | |
| config = parseYaml(raw) as CcsConfig; | |
| } catch { | |
| return context.success({}); | |
| } | |
| const accounts = config.accounts; | |
| if (!accounts) { | |
| return context.success({}); | |
| } | |
| let config: CcsConfig | null = null; | |
| try { | |
| const raw = fs.readFileSync(configPath, "utf-8"); | |
| const parsed = parseYaml(raw); | |
| if (parsed && typeof parsed === "object") { | |
| config = parsed as CcsConfig; | |
| } | |
| } catch { | |
| return context.success({}); | |
| } | |
| const accounts = config?.accounts; | |
| if (!accounts) { | |
| return context.success({}); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/ccs/hooks/entry/ccs-symlink-check.ts` around lines 96 - 107, The
parsed YAML may be null or not an object so avoid directly dereferencing
config.accounts; after calling parseYaml(raw) in the try block, validate that
config is a non-null object (e.g. typeof config === "object" && config !== null)
and that it has an accounts property (or use optional chaining) before assigning
const accounts = config.accounts; if the shape is invalid, return
context.success({}) as currently done; update references to the symbols
parseYaml, config, configPath, and accounts to include this guard so
malformed/empty configs do not throw at runtime.
| [ -f node_modules/.cc-installed ] && exit 0 | ||
| node -e 'var f=require("fs"),p=JSON.parse(f.readFileSync("package.json","utf8"));delete p.devDependencies;f.writeFileSync("package.json",JSON.stringify(p,null,2)+"\n")' | ||
| rm -f bun.lock | ||
| bun install --production | ||
| mkdir -p node_modules | ||
| touch node_modules/.cc-installed |
There was a problem hiding this comment.
Stale install marker can leave the plugin running with outdated dependencies.
node_modules/.cc-installed never invalidates when package.json changes, so future sessions may skip required reinstall/upgrade and run with mismatched runtime deps.
Suggested fix
-[ -f node_modules/.cc-installed ] && exit 0
+mkdir -p node_modules
+marker="node_modules/.cc-installed"
+hash_now="$(sha256sum package.json 2>/dev/null | awk '{print $1}')"
+if [ -f "$marker" ] && [ "$(cat "$marker" 2>/dev/null)" = "$hash_now" ]; then
+ exit 0
+fi
...
-bun install --production
-mkdir -p node_modules
-touch node_modules/.cc-installed
+bun install --production
+printf '%s\n' "$hash_now" > "$marker"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/ccs/hooks/install-deps.sh` around lines 6 - 11, The install marker
node_modules/.cc-installed is never invalidated when package.json changes;
update the script so it records a checksum or timestamp of package.json
alongside the marker and compares it on startup: compute a hash (or mtime) of
package.json, read the stored value from node_modules/.cc-installed (or a
companion file), and only exit early if they match; otherwise run the existing
steps (delete bun.lock, bun install --production, mkdir -p node_modules,
touch/update node_modules/.cc-installed) and write the new checksum/timestamp
into the marker so future runs detect changes.
| "dependencies": { | ||
| "@r_masseater/cc-plugin-lib": "0.0.6", | ||
| "cc-hooks-ts": "2.1.149", | ||
| "yaml": "^2.7.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@repo/ts-config": "*" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify plugin package manifests and inspect whether ccs includes mutils prerequisite.
fd -a package.json plugins | xargs -I{} sh -c 'echo "== {} =="; jq -r ".name, (.dependencies // {} | keys[]?)" "{}"'Repository: masseater/claude-code-plugin
Length of output: 5418
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show dependency name used by other plugins for mutils/mutils prerequisite
echo "== plugins/mutils/package.json =="
cat plugins/mutils/package.json
echo
echo "== plugins/ccs/package.json =="
cat plugins/ccs/package.json
echo
echo "== Find occurrences of 'mutils' in other plugins package.json =="
rg -n "\"mutils\"|\"plugin-mutils\"|\"`@r_masseater/mutils`\"|mutils" plugins -g 'package.json' || trueRepository: masseater/claude-code-plugin
Length of output: 1260
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== plugins/mutils/package.json (name + version) =="
node -e "const fs=require('fs'); const j=JSON.parse(fs.readFileSync('plugins/mutils/package.json','utf8')); console.log(j.name, '@', j.version);"
echo
echo "== plugins/ccs/package.json (dependencies keys) =="
node -e "const fs=require('fs'); const j=JSON.parse(fs.readFileSync('plugins/ccs/package.json','utf8')); console.log(Object.keys(j.dependencies||{}));"
echo
echo "== Check whether any plugin depends on the mutils prereq by common keys =="
rg -n "\"(mutils|plugin-mutils|`@r_masseater/mutils`)\"" plugins -g 'package.json' || trueRepository: masseater/claude-code-plugin
Length of output: 390
Add plugin-mutils as a prerequisite dependency in plugins/ccs/package.json.
plugins/ccs/package.json dependencies currently include @r_masseater/cc-plugin-lib, cc-hooks-ts, and yaml, but the prereq plugin’s package name is plugin-mutils (plugins/mutils/package.json).
Add plugin-mutils to plugins/ccs/package.json dependencies to satisfy the plugin prerequisite rule.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/ccs/package.json` around lines 10 - 17, The package.json in the CCS
plugin is missing the prerequisite dependency "plugin-mutils"; update the
"dependencies" section in plugins/ccs/package.json to add "plugin-mutils"
(matching the package name used in plugins/mutils/package.json) so the prereq
rule is satisfied, keeping versioning consistent with your repo policy (e.g.,
use the same version spec style as the other entries).
| - Technical details: `headless-workflow.md` | ||
| - Decision framework: `delegation-guidelines.md` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking referenced docs..."
fd -i '^headless-workflow\.md$'
fd -i '^delegation-guidelines\.md$'
echo
echo "Checking for relative-path candidates near troubleshooting.md..."
fd -i 'troubleshooting.md|headless-workflow.md|delegation-guidelines.md' pluginsRepository: masseater/claude-code-plugin
Length of output: 230
Fix broken cross-references in plugins/ccs/skills/ccs-delegation/references/troubleshooting.md (lines 9-10)
Lines 9-10 reference headless-workflow.md and delegation-guidelines.md, but neither file exists anywhere in the repo, so these links are broken. Add the missing docs or update the referenced filenames/paths.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/ccs/skills/ccs-delegation/references/troubleshooting.md` around lines
9 - 10, The two cross-reference links in troubleshooting.md point to
non-existent docs (headless-workflow.md and delegation-guidelines.md); update
troubleshooting.md to either point to the correct existing filenames/paths or
add the missing documents. Locate the references to headless-workflow.md and
delegation-guidelines.md in the troubleshooting.md content and: a) if equivalent
docs already exist elsewhere in the repo, replace the two filenames with the
correct relative paths/names, or b) if they are missing, create new markdown
files named headless-workflow.md and delegation-guidelines.md with the intended
content and add them to the same docs directory so the links resolve.
Summary
ccs-delegationスキルで最適プロファイル選択による CCS CLI 委譲を提供ccs-handoffスキルをmutilsから移行し、プロファイル間のセッション引き継ぎを実現/ccs/ccs:continueスラッシュコマンドを追加Test plan
bun install後に hook が正常にロードされることを確認/ccsコマンドでプロファイル選択・委譲が動作することを確認/ccs:continueで前回セッション継続が動作することを確認ccs-handoffスキルが mutils から正しく移行されていることを確認🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
/ccsand/ccs:continuecommands for delegating work to models with automatic profile selectionDocumentation