Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{
"name": "ai-driven-development",
"description": "An umbrella collection of 25 skills + 1 hook for AI-driven development: agent self-configuration (MCP / hooks / settings / skills / plugins / cross-agent repository readiness / safe CLI installation), engineering practices (maintainable complexity, prompt engineering, FPF problem solving, bug-fix protocol, planning gate, repository-history investigation, repository activity summary, C# refactoring, Apple App Store submission review), AI infrastructure (OpenRouter endpoint ranking and routing), research & docs (Semantic Scholar deep research, URL-to-Markdown, ubiquitous-language thesaurus), multi-agent orchestration (consilium: review and stateful delegation), macOS & Windows health & cleanup, and niche utilities (clipboard, Windows QA). Plus a Claude Code Bash safety hook.",
"version": "9.8.0",
"version": "9.8.1",
"author": {
"name": "CodeAlive-AI",
"email": "contact@codealive.ai"
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ai-driven-development",
"description": "An umbrella collection of 25 skills + 1 hook for AI-driven development: agent self-configuration (MCP / hooks / settings / skills / plugins / cross-agent repository readiness / safe CLI installation), engineering practices (maintainable complexity, prompt engineering, FPF problem solving, bug-fix protocol, planning gate, repository-history investigation, repository activity summary, C# refactoring, Apple App Store submission review), AI infrastructure (OpenRouter endpoint ranking and routing), research & docs (Semantic Scholar deep research, URL-to-Markdown, ubiquitous-language thesaurus), multi-agent orchestration (consilium: review and stateful delegation), macOS & Windows health & cleanup, and niche utilities (clipboard, Windows QA). Plus a Claude Code Bash safety hook.",
"version": "9.8.0",
"version": "9.8.1",
"author": {
"name": "CodeAlive-AI",
"email": "contact@codealive.ai"
Expand Down
141 changes: 141 additions & 0 deletions .github/workflows/consilium-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
name: agents-consilium smoke

on:
pull_request:
paths:
- 'skills/agents-consilium/**'
- '.github/workflows/consilium-smoke.yml'
push:
branches: [main]
paths:
- 'skills/agents-consilium/**'
- '.github/workflows/consilium-smoke.yml'

jobs:
smoke:
name: smoke (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
defaults:
run:
shell: bash
working-directory: skills/agents-consilium
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Import every steer module
working-directory: skills/agents-consilium/scripts/lib
run: |
python - <<'PY'
import sys
sys.path.insert(0, '.')
import steer.util, steer.mailbox, steer.registry
import steer.jsonrpc, steer.control, steer.supervisor
from steer.adapters import claude, opencode
import terminal_guard
print('imports OK:', steer.registry.default_registry_root())
PY

- name: pid_alive probes without terminating
working-directory: skills/agents-consilium/scripts/lib
run: |
python - <<'PY'
import subprocess, sys, time
sys.path.insert(0, '.')
from steer.util import pid_alive
child = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(30)'])
assert pid_alive(child.pid), 'live child reported dead'
time.sleep(0.5)
assert child.poll() is None, 'pid_alive() terminated the process it probed'
assert not pid_alive(9999999), 'bogus pid reported alive'
child.kill()
child.wait()
assert not pid_alive(child.pid), 'reaped child still reported alive'
print('pid_alive OK')
PY

- name: Lock helpers round-trip on a non-empty lock file
working-directory: skills/agents-consilium/scripts/lib
run: |
python - <<'PY'
import sys, tempfile
from pathlib import Path
sys.path.insert(0, '.')
from steer.util import flock_exclusive, lock_exclusive, unlock
lock = Path(tempfile.mkdtemp()) / 'x.lock'
# Non-empty on purpose: msvcrt.locking() locks a byte range from the
# CURRENT offset, so a lock/unlock pair that disagrees on the offset
# leaks the lock and the next acquisition deadlocks.
lock.write_text('noise noise noise\n', encoding='utf-8')
for _ in range(3):
with flock_exclusive(lock):
pass
with open(lock, 'a+', encoding='utf-8') as fh:
lock_exclusive(fh); unlock(fh)
lock_exclusive(fh); unlock(fh)
print('lock round-trip OK')
PY

- name: Detach kwargs are valid for this platform
working-directory: skills/agents-consilium/scripts/lib
run: |
python - <<'PY'
import subprocess, sys
sys.path.insert(0, '.')
from steer.util import detached_popen_kwargs
kwargs = detached_popen_kwargs()
print('detach kwargs:', kwargs)
child = subprocess.Popen([sys.executable, '-c', 'pass'], **kwargs)
assert child.wait() == 0
print('detached spawn OK')
PY

- name: terminal_guard stops a completed backend
working-directory: skills/agents-consilium/scripts/lib
run: |
python - <<'PY'
import subprocess, sys, time
event = '{"type":"session.complete"}'
backend = f'import time; print({event!r}, flush=True); time.sleep(60)'
started = time.monotonic()
done = subprocess.run(
[sys.executable, 'terminal_guard.py', '--backend', 'opencode',
'--terminal-grace', '1', '--', sys.executable, '-c', backend],
stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=45,
)
elapsed = time.monotonic() - started
print(done.stdout, done.stderr, sep='\n')
assert event in done.stdout, 'terminal event was not forwarded'
assert elapsed < 30, f'guard did not stop the completed backend ({elapsed:.1f}s)'
print(f'terminal_guard OK ({elapsed:.1f}s)')
PY

- name: Config helpers emit LF-only agent ids
run: |
source scripts/lib/common.sh
source scripts/lib/config.sh
config_validate
config_enabled_agents > agents.txt
test -s agents.txt || { echo 'no enabled agents parsed'; exit 1; }
if grep -q $'\r' agents.txt; then
echo 'CR found in config_enabled_agents output:'
cat -A agents.txt
exit 1
fi
while IFS= read -r agent; do
backend="$(config_get_field "$agent" backend)" || { echo "backend lookup failed: [$agent]"; exit 1; }
test -n "$backend" || { echo "empty backend for: [$agent]"; exit 1; }
echo "$agent -> $backend"
done < agents.txt
rm -f agents.txt
echo 'config helpers OK'

- name: consilium --help
run: bash scripts/consilium --help
2 changes: 2 additions & 0 deletions skills/agents-consilium/scripts/lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# Shared utilities for consilium multi-agent scripts
#

export PYTHONIOENCODING="${PYTHONIOENCODING:-utf-8}"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
Expand Down
5 changes: 4 additions & 1 deletion skills/agents-consilium/scripts/lib/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ CONSILIUM_CONFIG="${CONSILIUM_CONFIG:-$SKILL_ROOT/config.json}"
# Internal: read JSON via python3.
# Usage: _cfg_python "script body that reads CONSILIUM_CONFIG"
_cfg_python() {
CONSILIUM_CONFIG_PATH="$CONSILIUM_CONFIG" python3 -c "$1"
CONSILIUM_CONFIG_PATH="$CONSILIUM_CONFIG" python3 -c "
import sys
sys.stdout.reconfigure(newline='\n')
$1"
}

# Validate config file exists and parses as JSON.
Expand Down
4 changes: 2 additions & 2 deletions skills/agents-consilium/scripts/lib/dedup-findings.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ def main() -> int:
sys.stderr.write(f"[dedup] skip empty/missing {p}\n")
continue
agent, role = parse_filename(p)
text = p.read_text(errors="replace")
text = p.read_text(encoding="utf-8", errors="replace")
for f in extract_findings(text):
all_findings.append(attach_source_attrs(f, agent, role))

all_findings.sort(key=sort_key)

out_path.parent.mkdir(parents=True, exist_ok=True)
with out_path.open("w") as fh:
with out_path.open("w", encoding="utf-8", newline="\n") as fh:
fh.write(f'<code-review-report total="{len(all_findings)}">\n')
for i, f in enumerate(all_findings, start=1):
fh.write(reindex(f, i))
Expand Down
17 changes: 10 additions & 7 deletions skills/agents-consilium/scripts/lib/discovery-pass.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,16 @@ fi
CONSILIUM_CONFIG="${CONSILIUM_CONFIG:-$SKILL_DIR/config.json}"
[[ -f "$CONSILIUM_CONFIG" ]] || { echo -e "${RED}Error: config not found: $CONSILIUM_CONFIG${NC}" >&2; exit 4; }

BACKEND="$(python3 -c "
import json, sys
d = json.load(open('$CONSILIUM_CONFIG'))['agents']
if '$AGENT' not in d:
sys.stderr.write('agent not in config: $AGENT\n'); sys.exit(1)
print(d['$AGENT']['backend'])
")" || exit 4
BACKEND="$(CONSILIUM_CONFIG_PATH="$CONSILIUM_CONFIG" AGENT_ID="$AGENT" python3 -c '
import json, os, sys
sys.stdout.reconfigure(newline="\n")
path = os.environ["CONSILIUM_CONFIG_PATH"]
agent = os.environ["AGENT_ID"]
d = json.load(open(path))["agents"]
if agent not in d:
sys.stderr.write(f"agent not in config: {agent}\n"); sys.exit(1)
print(d[agent]["backend"])
')" || exit 4

BACKEND_SCRIPT="$LIB_DIR/backend_run.sh"
[[ -x "$BACKEND_SCRIPT" || -f "$BACKEND_SCRIPT" ]] || {
Expand Down
17 changes: 10 additions & 7 deletions skills/agents-consilium/scripts/lib/judge-runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,16 @@ done
CONSILIUM_CONFIG="${CONSILIUM_CONFIG:-$SKILL_DIR/config.json}"
[[ -f "$CONSILIUM_CONFIG" ]] || { echo -e "${RED}Error: config not found: $CONSILIUM_CONFIG${NC}" >&2; exit 4; }

BACKEND="$(python3 -c "
import json, sys
d = json.load(open('$CONSILIUM_CONFIG'))['agents']
if '$AGENT' not in d:
sys.stderr.write('agent not in config: $AGENT\n'); sys.exit(1)
print(d['$AGENT']['backend'])
")" || exit 4
BACKEND="$(CONSILIUM_CONFIG_PATH="$CONSILIUM_CONFIG" AGENT_ID="$AGENT" python3 -c '
import json, os, sys
sys.stdout.reconfigure(newline="\n")
path = os.environ["CONSILIUM_CONFIG_PATH"]
agent = os.environ["AGENT_ID"]
d = json.load(open(path))["agents"]
if agent not in d:
sys.stderr.write(f"agent not in config: {agent}\n"); sys.exit(1)
print(d[agent]["backend"])
')" || exit 4

BACKEND_SCRIPT="$LIB_DIR/backend_run.sh"
[[ -f "$BACKEND_SCRIPT" ]] || { echo -e "${RED}Error: backend runner missing: $BACKEND_SCRIPT${NC}" >&2; exit 4; }
Expand Down
5 changes: 3 additions & 2 deletions skills/agents-consilium/scripts/lib/steer/adapters/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional

from ..util import kill_process_group
from ..util import detached_popen_kwargs, kill_process_group
from .base import AdapterEvent, BackendAdapter, DeliveryClass, SteerResult


Expand Down Expand Up @@ -65,8 +65,9 @@ def start(self, task: str) -> None:
stderr=subprocess.PIPE,
cwd=self.cwd,
text=True,
encoding="utf-8",
bufsize=1,
start_new_session=True,
**detached_popen_kwargs(),
)
self._reader = threading.Thread(target=self._read_loop, daemon=True)
self._reader.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from typing import Any, Dict, Iterator, List, Optional
from urllib.parse import quote, urlparse

from ..util import is_loopback_url, kill_process_group
from ..util import detached_popen_kwargs, is_loopback_url, kill_process_group
from .base import AdapterEvent, BackendAdapter, DeliveryClass, SteerResult

# Default OpenCode basic-auth username when OPENCODE_SERVER_USERNAME is unset.
Expand Down Expand Up @@ -139,8 +139,9 @@ def start(self, task: str) -> None:
cwd=self.cwd,
env=self._child_env(),
text=True,
encoding="utf-8",
bufsize=1,
start_new_session=True,
**detached_popen_kwargs(),
)
# Drain both pipes so a chatty serve process cannot block on a full buffer.
self._stderr_thread = threading.Thread(
Expand Down
36 changes: 33 additions & 3 deletions skills/agents-consilium/scripts/lib/steer/jsonrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,41 @@
from __future__ import annotations

import json
import os
import queue
import shutil
import subprocess
import threading
import time
from typing import Any, Callable, Dict, List, Optional, Tuple

from .util import detached_popen_kwargs


def _resolve_argv(argv: List[str]) -> List[str]:
"""Resolve argv[0] to an actually-executable path.

Native Windows Python's subprocess.Popen requires a real PE executable —
it cannot exec a shebang script directly, and does not consult PATHEXT
the way cmd.exe does. shutil.which() honors PATHEXT (finds .cmd/.bat/.exe
shims npm-style CLIs install); a plain-text script found that way still
needs a bash launcher to run its shebang line.
"""
if os.name != "nt" or not argv:
return argv
resolved = shutil.which(argv[0]) or argv[0]
if resolved.lower().endswith((".cmd", ".bat", ".exe", ".com")):
return [resolved] + argv[1:]
try:
with open(resolved, "rb") as f:
head = f.read(2)
if head == b"#!":
bash = shutil.which("bash.exe") or shutil.which("bash") or "bash"
return [bash, resolved] + argv[1:]
except OSError:
pass
return [resolved] + argv[1:]


class JsonRpcProcess:
"""
Expand Down Expand Up @@ -45,17 +74,18 @@ def __init__(
self._exit_code: Optional[int] = None

def start(self) -> None:
self.proc = subprocess.Popen(
self.argv,
popen_kwargs: Dict[str, Any] = dict(
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.cwd,
env=self.env,
text=True,
encoding="utf-8",
bufsize=1,
start_new_session=True,
)
popen_kwargs.update(detached_popen_kwargs())
self.proc = subprocess.Popen(_resolve_argv(self.argv), **popen_kwargs)
self._reader = threading.Thread(target=self._read_stdout, daemon=True)
self._reader.start()
self._stderr_thread = threading.Thread(target=self._read_stderr, daemon=True)
Expand Down
Loading