feat(cto): v1.0 MVP — executable orchestrator + cto-worker.sh helper
skills/cto-agent/SKILL.md: bumped 0.1.0 → 1.0.0; drop "v0.1 stub" banner;
operating loop now concrete (no more "v1.0 will…"); add explicit kanban
worker contract (kanban_complete | kanban_block required at task end —
fixes the protocol-violation noise observed in CTO validation testing).
Routing table updated: Python → cto-python-toolkit, Angular →
cto-angular-toolkit (the dedicated stack skills built earlier).
Added sot/-spec frontmatter fields (tier T2, status active, owner, source,
last_reviewed) per PROFILE-DISTRIBUTION-PROTOCOL §2.1.
lib/cto-worker.sh: orchestrator helper. 3 commands:
- sandcastle <work-id> <target> <prompt> [provider] → invoke sandcastle
via npx tsx + claudeCode + docker (default). Blocks reads against
read-only siblings (hermes-agent, hermes-webui, marketingskills,
sandcastle).
- open-pr <work-id> <target> <title> <body> → resolves github-pat via
credbridge (never in argv), pushes branch, creates PR. Returns URL.
- emit-5w <work-id> <status> <summary> → prints 5W block (stdout
captured by Hermes into kanban completion).
install.sh: invokes `hermes profile install --yes --force` for dispatch
readiness; chmod +x cto-worker.sh; drops v0.1 scaffold messages; sandcastle
sibling now REQUIRED (was just a WARN). Adds matching DRY echoes.
manifest.yaml + distribution.yaml: version 0.1.0 → 1.0.0; distribution_owned
adds lib/.
README.md: status v0.1 scaffold → v1.0 MVP; layout reflects 3 skills + lib/;
roadmap table refactored (v1.0 current / v1.1 next / v2 deferred).
Verified: hermes profile install → "✓ Installed 'cto-planb' v1.0.0".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bash
|
||||
# cto-worker.sh — CTO orchestrator helper. Wraps sandcastle invocation + PR opening + 5W reporting.
|
||||
#
|
||||
# Called by the cto-agent SKILL.md operating loop. Idempotent per WORK_ID.
|
||||
#
|
||||
# Usage:
|
||||
# cto-worker.sh sandcastle <work-id> <target-repo> <prompt-file> [provider=docker]
|
||||
# → invokes sandcastle.run(), returns JSON {commits, branch} on stdout
|
||||
#
|
||||
# cto-worker.sh open-pr <work-id> <target-repo> <title> <body>
|
||||
# → gh pr create on branch cto/<work-id>; returns PR URL on stdout
|
||||
#
|
||||
# cto-worker.sh emit-5w <work-id> <status> <summary>
|
||||
# → prints 5W block to stdout (Hermes captures into kanban completion)
|
||||
#
|
||||
# Env:
|
||||
# SANDCASTLE_REPO — default $HOME/workspaces/hermes/sandcastle
|
||||
# CTO_HOME — default $HOME/.hermes/cto-planb
|
||||
# GH_TOKEN — resolved via credbridge.sh; never set in argv
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SANDCASTLE_REPO="${SANDCASTLE_REPO:-$HOME/workspaces/hermes/sandcastle}"
|
||||
CTO_HOME="${CTO_HOME:-$HOME/.hermes/cto-planb}"
|
||||
CTO_REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
usage() { sed -n '2,17p' "$0"; exit 2; }
|
||||
|
||||
cmd_sandcastle() {
|
||||
local work_id="${1:?work-id required}"
|
||||
local target="${2:?target-repo required}"
|
||||
local prompt_file="${3:?prompt-file required}"
|
||||
local provider="${4:-docker}"
|
||||
|
||||
[ -d "$SANDCASTLE_REPO" ] || { echo "ERROR: $SANDCASTLE_REPO not found" >&2; return 1; }
|
||||
[ -d "$target" ] || { echo "ERROR: target repo $target not found" >&2; return 1; }
|
||||
[ -f "$prompt_file" ] || { echo "ERROR: prompt file $prompt_file not found" >&2; return 1; }
|
||||
|
||||
# Hard rule: never run against read-only workspace siblings.
|
||||
case "$(basename "$target")" in
|
||||
hermes-agent|hermes-webui|marketingskills|sandcastle)
|
||||
echo "BLOCK: refusing to sandcastle against read-only sibling: $(basename "$target")" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
local branch="cto/$work_id"
|
||||
cd "$SANDCASTLE_REPO"
|
||||
|
||||
npx tsx -e "
|
||||
import { run, claudeCode } from '@ai-hero/sandcastle';
|
||||
import { ${provider} } from '@ai-hero/sandcastle/sandboxes/${provider}';
|
||||
const result = await run({
|
||||
agent: claudeCode('claude-opus-4-7'),
|
||||
sandbox: ${provider}(),
|
||||
promptFile: '${prompt_file}',
|
||||
cwd: '${target}',
|
||||
branchStrategy: { type: 'branch', branch: '${branch}' },
|
||||
maxIterations: 5,
|
||||
});
|
||||
console.log(JSON.stringify({ work_id: '${work_id}', commits: result.commits, branch: result.branch }, null, 2));
|
||||
"
|
||||
}
|
||||
|
||||
cmd_open_pr() {
|
||||
local work_id="${1:?work-id required}"
|
||||
local target="${2:?target-repo required}"
|
||||
local title="${3:?title required}"
|
||||
local body="${4:?body required}"
|
||||
|
||||
[ -d "$target" ] || { echo "ERROR: target $target not found" >&2; return 1; }
|
||||
local branch="cto/$work_id"
|
||||
|
||||
# Resolve github-pat via credbridge (never via argv).
|
||||
source "$CTO_REPO/credbridge.sh"
|
||||
if ! cb_export GH_TOKEN github-pat 2>/dev/null; then
|
||||
echo "ERROR: github-pat not in credctl vault (add via 'credctl set github-pat')" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
cd "$target"
|
||||
# Ensure branch is pushed.
|
||||
if ! git -C "$target" rev-parse "refs/heads/$branch" >/dev/null 2>&1; then
|
||||
echo "ERROR: branch $branch not present locally — sandcastle did not commit?" >&2
|
||||
return 1
|
||||
fi
|
||||
git push -u origin "$branch" 2>&1 | tail -3
|
||||
|
||||
# Create PR — capture URL.
|
||||
gh pr create --head "$branch" --title "$title" --body "$body" 2>&1 | grep -oE 'https?://[^[:space:]]+'
|
||||
}
|
||||
|
||||
cmd_emit_5w() {
|
||||
local work_id="${1:?work-id required}"
|
||||
local status="${2:?status required}" # shipped | blocked | needs-decision
|
||||
local summary="${3:?summary required}"
|
||||
|
||||
cat <<EOF
|
||||
## WHAT — Shipped
|
||||
${summary}
|
||||
|
||||
## WHY — Approach
|
||||
sandcastle-orchestrated isolated execution per cto-agent SKILL.md operating loop
|
||||
|
||||
## HOW — Sandcastle invocations
|
||||
work_id=${work_id}; branch=cto/${work_id}; provider=docker (default); maxIterations=5
|
||||
|
||||
## WHO — Next
|
||||
JP for merge approval (deploy gate)
|
||||
|
||||
## WHEN — Status
|
||||
${status} — $(date -Iseconds)
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
sandcastle) shift; cmd_sandcastle "$@" ;;
|
||||
open-pr) shift; cmd_open_pr "$@" ;;
|
||||
emit-5w) shift; cmd_emit_5w "$@" ;;
|
||||
""|-h|--help) usage ;;
|
||||
*) echo "unknown command: $1"; usage ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user