diff --git a/README.md b/README.md index b3efff5..ec78e55 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,8 @@ script links are managed: - `hv-hermes-vikunja-task-updates` — polls Vikunja task updates - `hv-hermes-dev-team-manager` — runs the Dev Team Mode sidecar for board provisioning, worker dispatch, and worker process status +- `hv-hermes-dev-team-worker` — runs one Hermes worker agent inside the + sidecar-created isolated worktree - `hv-hermes-github-cricket-issues` — polls GitHub open issues labeled `cricket` diff --git a/docs/hermes-dev-team-mode.md b/docs/hermes-dev-team-mode.md index acbdc36..99eef17 100644 --- a/docs/hermes-dev-team-mode.md +++ b/docs/hermes-dev-team-mode.md @@ -66,8 +66,11 @@ Workers are per-task runs. For each dispatched issue, the manager: 7. Moves the main-board issue to `Review` for human QA. The worker command is configured locally with `HV_HERMES_WORKER_COMMAND` or the -equivalent Hermes config value. Workers must not edit the integration worktree -directly. +equivalent Hermes config value. `hv-hermes-dev-team-worker` is the default +worker wrapper supplied by have-config; it launches a non-interactive Hermes run +inside the sidecar-created worktree and passes the task/worktree context through +the `HERMES_DEV_TEAM_*` environment variables. Workers must not edit the +integration worktree directly. ## Manager Sidecar @@ -116,7 +119,7 @@ dev_team: - ~/Work/anytown/repos worktree_root: ~/.hermes/dev-team/worktrees integration_root: ~/.hermes/dev-team/integration - worker_command: "${HV_HERMES_WORKER_COMMAND}" + worker_command: "hv-hermes-dev-team-worker" main_buckets: [To-Do, Blocked, Doing, Review, Done] manager_buckets: [Queued, Working, Integrating, Blocked, Closed] status_updates: on_request diff --git a/hv/manifest.json b/hv/manifest.json index df842db..4efe7a7 100644 --- a/hv/manifest.json +++ b/hv/manifest.json @@ -53,6 +53,13 @@ "executable": true, "description": "Run the Hermes Dev Team Mode sidecar for Vikunja board provisioning, worker dispatch, and worker process status." }, + { + "agent": "no-agent", + "name": "hv-hermes-dev-team-worker", + "path": "reusable-scripts/hermes/no-agent/hv-hermes-dev-team-worker", + "executable": true, + "description": "Run one Hermes Dev Team worker agent inside the sidecar-created isolated worktree." + }, { "agent": "no-agent", "name": "hv-hermes-github-cricket-issues", diff --git a/reusable-scripts/hermes/no-agent/hv-hermes-dev-team-worker b/reusable-scripts/hermes/no-agent/hv-hermes-dev-team-worker new file mode 100755 index 0000000..75d5539 --- /dev/null +++ b/reusable-scripts/hermes/no-agent/hv-hermes-dev-team-worker @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Run a single Hermes Dev Team worker task inside the sidecar-created worktree. +# +# The manager sidecar sets HERMES_DEV_TEAM_* environment variables and invokes +# this script from the isolated worker worktree. The worker must leave reviewable +# changes in that worktree and exit 0 only when ready for manager review. +set -euo pipefail + +if ! command -v hermes >/dev/null 2>&1; then + echo "hv-hermes-dev-team-worker: hermes CLI not found on PATH" >&2 + exit 127 +fi + +required_vars=( + HERMES_DEV_TEAM_MAIN_TASK_ID + HERMES_DEV_TEAM_MAIN_TASK_URL + HERMES_DEV_TEAM_WORKER_TASK_ID + HERMES_DEV_TEAM_WORKER_TASK_URL + HERMES_DEV_TEAM_REPO_PATH + HERMES_DEV_TEAM_WORKTREE_PATH + HERMES_DEV_TEAM_BRANCH + HERMES_DEV_TEAM_INTEGRATION_ROOT + HERMES_DEV_TEAM_MANAGER_IDENTITY +) +for var in "${required_vars[@]}"; do + if [[ -z "${!var:-}" ]]; then + echo "hv-hermes-dev-team-worker: missing required environment variable $var" >&2 + exit 64 + fi +done + +if [[ ! -d "$HERMES_DEV_TEAM_WORKTREE_PATH" ]]; then + echo "hv-hermes-dev-team-worker: worktree does not exist: $HERMES_DEV_TEAM_WORKTREE_PATH" >&2 + exit 66 +fi + +cd "$HERMES_DEV_TEAM_WORKTREE_PATH" + +prompt_file="$(mktemp)" +cleanup() { + rm -f "$prompt_file" +} +trap cleanup EXIT + +cat >"$prompt_file" <<'PROMPT' +You are a Hermes Dev Team worker agent running in an isolated git worktree. + +Task context is provided in environment variables: +- HERMES_DEV_TEAM_MAIN_TASK_ID / HERMES_DEV_TEAM_MAIN_TASK_URL: canonical user-visible Vikunja issue. +- HERMES_DEV_TEAM_WORKER_TASK_ID / HERMES_DEV_TEAM_WORKER_TASK_URL: internal worker task. +- HERMES_DEV_TEAM_REPO_PATH: source repository path. +- HERMES_DEV_TEAM_WORKTREE_PATH: this isolated worktree; make changes here only. +- HERMES_DEV_TEAM_BRANCH: branch for this work. +- HERMES_DEV_TEAM_INTEGRATION_ROOT: manager-owned integration root; do not edit it. +- HERMES_DEV_TEAM_MANAGER_IDENTITY: manager identity. +- HERMES_DEV_TEAM_DEV_SERVER_URL: optional dev server URL. + +Follow this contract: +1. Inspect the worker task and linked main task if tools are available; otherwise use the URLs and repository context in this prompt. +2. Make only scoped changes for the linked main task in HERMES_DEV_TEAM_WORKTREE_PATH. +3. Do not edit the integration root and do not move main-board cards. +4. Run the smallest useful verification for the change. +5. Leave reviewable output in the worktree and summarize what changed, tests run, and blockers. +6. Exit non-zero if you cannot produce reviewable output. +PROMPT + +{ + printf '\nConcrete values for this run:\n' + printf -- '- Main task: #%s %s\n' "$HERMES_DEV_TEAM_MAIN_TASK_ID" "$HERMES_DEV_TEAM_MAIN_TASK_URL" + printf -- '- Worker task: #%s %s\n' "$HERMES_DEV_TEAM_WORKER_TASK_ID" "$HERMES_DEV_TEAM_WORKER_TASK_URL" + printf -- '- Repository: %s\n' "$HERMES_DEV_TEAM_REPO_PATH" + printf -- '- Worktree: %s\n' "$HERMES_DEV_TEAM_WORKTREE_PATH" + printf -- '- Branch: %s\n' "$HERMES_DEV_TEAM_BRANCH" + printf -- '- Integration root: %s\n' "$HERMES_DEV_TEAM_INTEGRATION_ROOT" + printf -- '- Manager: %s\n' "$HERMES_DEV_TEAM_MANAGER_IDENTITY" + if [[ -n "${HERMES_DEV_TEAM_DEV_SERVER_URL:-}" ]]; then + printf -- '- Dev server: %s\n' "$HERMES_DEV_TEAM_DEV_SERVER_URL" + fi +} >>"$prompt_file" + +# Use non-interactive Hermes. The sidecar already isolates each worker in a git +# worktree, so do not ask Hermes to create another one. +exec hermes chat \ + --query "$(cat "$prompt_file")" \ + --source "hermes-dev-team-worker" \ + --quiet diff --git a/scripts/test-hv-agent-resolver.sh b/scripts/test-hv-agent-resolver.sh index 9adabec..6ba7b23 100755 --- a/scripts/test-hv-agent-resolver.sh +++ b/scripts/test-hv-agent-resolver.sh @@ -100,6 +100,13 @@ cat > "$HAVE_CONFIG_DIR/hv/manifest.json" <<'JSON' "executable": true, "description": "fixture dev team manager script" }, + { + "agent": "no-agent", + "name": "hv-hermes-dev-team-worker", + "path": "reusable-scripts/hermes/no-agent/hv-hermes-dev-team-worker", + "executable": true, + "description": "fixture dev team worker script" + }, { "agent": "no-agent", "name": "inline-notify", @@ -135,6 +142,11 @@ cat > "$HAVE_CONFIG_DIR/reusable-scripts/hermes/no-agent/hv-hermes-dev-team-mana echo fixture dev team manager EOF +cat > "$HAVE_CONFIG_DIR/reusable-scripts/hermes/no-agent/hv-hermes-dev-team-worker" <<'EOF' +#!/usr/bin/env bash +echo fixture dev team worker +EOF + cat > "$HAVE_CONFIG_DIR/profiles/hermes/manifest.json" <<'JSON' { "schema": "https://happyvertical.com/hv-agent-manifest/v1", @@ -258,6 +270,9 @@ test -x "$HOME_DIR/.local/bin/fixture-notify" grep -q "fixture dev team manager" "$OUTPUT_DIR/scripts/hv-hermes-dev-team-manager" grep -q "fixture dev team manager" "$HOME_DIR/.local/bin/hv-hermes-dev-team-manager" test -x "$HOME_DIR/.local/bin/hv-hermes-dev-team-manager" +grep -q "fixture dev team worker" "$OUTPUT_DIR/scripts/hv-hermes-dev-team-worker" +grep -q "fixture dev team worker" "$HOME_DIR/.local/bin/hv-hermes-dev-team-worker" +test -x "$HOME_DIR/.local/bin/hv-hermes-dev-team-worker" grep -q "inline notify" "$OUTPUT_DIR/scripts/inline-notify" grep -q "inline notify" "$HOME_DIR/.local/bin/inline-notify" test -x "$HOME_DIR/.local/bin/inline-notify" @@ -269,6 +284,7 @@ grep -q '"key": "codex:command:review-cycle"' "$LOCK_PATH" grep -q '"key": "codex:skill:hermes-manager"' "$LOCK_PATH" grep -q '"key": "no-agent:script:fixture-notify"' "$LOCK_PATH" grep -q '"key": "no-agent:script:hv-hermes-dev-team-manager"' "$LOCK_PATH" +grep -q '"key": "no-agent:script:hv-hermes-dev-team-worker"' "$LOCK_PATH" grep -q '"key": "no-agent:script:inline-notify"' "$LOCK_PATH" grep -q '`dotfiles` priority 10: available' "$REPORT_PATH" grep -q "invalid declared priority 'dynamic' ignored; using fixed 30" "$REPORT_PATH"