@delorenj/pjangler 1.1.5 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mise/scripts/link-agentfiles.sh +9 -0
- package/dist/index.js +1492 -358
- package/dist/mcp-server.js +2391 -290
- package/package.json +5 -2
- package/templates/commonproject/.mise/scripts/create-plane-project.sh +26 -0
- package/templates/commonproject/copier.yml +2 -0
- package/templates/commonproject/template/.agents/hooks/README.md +151 -0
- package/templates/commonproject/template/.agents/hooks/hermes/hindsight-hook.sh +63 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-journal-write.sh +327 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-recall.sh +180 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-retain.sh +74 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-session-end.sh +57 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-bank.sh +57 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-journal.sh +48 -0
- package/templates/commonproject/template/.agents/hooks/hooks.master.json +97 -0
- package/templates/commonproject/template/.agents/hooks/lib/hook-guard.sh +30 -0
- package/templates/commonproject/template/.agents/hooks/lib/local-config.sh +51 -0
- package/templates/commonproject/template/.agents/hooks/reminder-for-skill-check.sh +20 -0
- package/templates/commonproject/template/.agents/hooks/sync.py +618 -0
- package/templates/commonproject/template/.agents/local.example.json +15 -0
- package/templates/commonproject/template/.mise/scripts/hindsight-setup.sh +62 -0
- package/templates/commonproject/template/.mise/scripts/link-project-skills-to-clis.sh +110 -0
- package/templates/commonproject/template/.mise/scripts/unlink-project-skills-from-clis.sh +45 -0
- package/templates/commonproject/template/mise.toml.jinja +42 -0
- package/templates/hermes-agent/.codegraph/daemon.pid +6 -0
- package/templates/hermes-agent/.omo/run-continuation/ses_0e5b28303ffeXxL53hZjKggXDW.json +10 -0
- package/templates/hermes-agent/README.md +16 -14
- package/templates/hermes-agent/copier.yml +7 -25
- package/templates/hermes-agent/docs/architecture.md +16 -18
- package/templates/hermes-agent/docs/fleet-control-plane/README.md +35 -0
- package/templates/hermes-agent/docs/fleet-control-plane/architecture.md +253 -0
- package/templates/hermes-agent/docs/fleet-control-plane/epics-and-stories.md +280 -0
- package/templates/hermes-agent/docs/fleet-control-plane/implementation-readiness-report-2026-06-27.md +118 -0
- package/templates/hermes-agent/docs/fleet-control-plane/prd.md +141 -0
- package/templates/hermes-agent/docs/operations.md +17 -19
- package/templates/hermes-agent/docs/runbooks/runtime-checkpoint-repair.md +17 -13
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/README.md +47 -46
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/architecture.md +39 -38
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/development.md +63 -99
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/providers.md +2 -2
- package/templates/hermes-agent/docs/sentinel.md +18 -0
- package/templates/hermes-agent/install-local.sh +22 -20
- package/templates/hermes-agent/runtime-scaffold/README.md +10 -9
- package/templates/hermes-agent/scripts/backfill-fleet-sot.sh +12 -21
- package/templates/hermes-agent/scripts/fleet-sync.sh +369 -0
- package/templates/hermes-agent/scripts/migrate-unify.sh +252 -0
- package/templates/hermes-agent/scripts/unify-pm.sh +148 -0
- package/templates/hermes-agent/template/.runtime-scaffold/README.md +10 -9
- package/templates/hermes-agent/template/.scripts/01-config.sh +2 -3
- package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +0 -2
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +47 -19
- package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +71 -27
- package/templates/hermes-agent/template/.scripts/30-telegram.sh +3 -3
- package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +3 -3
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +48 -43
- package/templates/hermes-agent/template/.scripts/80-registry.sh +2 -2
- package/templates/hermes-agent/template/.scripts/99-summary.sh +3 -4
- package/templates/hermes-agent/template/.scripts/_lib.sh +4 -19
- package/templates/hermes-agent/template/.scripts/checkpoint.sh +36 -0
- package/templates/hermes-agent/template/.scripts/config.example.toml +11 -5
- package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.sh → heartbeat.sh} +84 -31
- package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +1 -1
- package/templates/hermes-agent/template/.scripts/providers/plane.sh +9 -3
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/emit-event.py +5 -5
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-autonomous-review.sh +6 -6
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-close-gate.sh +2 -2
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/autonomous-delegated-review.md +7 -7
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/bloodbank-events.md +3 -3
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/continuous-ticket-orchestration.md +6 -6
- package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.prompt.md.jinja → sentinel.prompt.md.jinja} +5 -4
- package/templates/hermes-agent/template/SOUL.md.jinja +3 -6
- package/templates/hermes-agent/template/hermes.jinja +6 -15
- package/templates/hermes-agent/template/role.yaml.jinja +9 -8
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/agent-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/module-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/workflow-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/agent.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/module.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/workflow.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/analyst.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/architect.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/dev.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/pm.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/quick-flow-solo-dev.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/sm.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/tea.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/tech-writer.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/ux-designer.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/check-implementation-readiness.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/code-review.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/correct-course.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-architecture.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-epics-and-stories.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-dataflow.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-diagram.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-flowchart.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-wireframe.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-product-brief.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-story.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-ux-design.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/dev-story.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/document-project.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/generate-project-context.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/prd.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-dev.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-spec.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/research.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/retrospective.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-planning.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-status.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-atdd.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-automate.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-ci.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-framework.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-nfr.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-design.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-review.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-trace.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-init.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-status.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/agents/brainstorming-coach.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/creative-problem-solver.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/design-thinking-coach.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/innovation-strategist.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/presentation-master.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/storyteller.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/design-thinking.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/innovation-strategy.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/problem-solving.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/storytelling.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/core/agents/bmad-master.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/core/tasks/index-docs.md +0 -9
- package/templates/commonproject/.claude/commands/bmad/core/tasks/shard-doc.md +0 -9
- package/templates/commonproject/.claude/commands/bmad/core/workflows/brainstorming.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/core/workflows/party-mode.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/custom/workflows/ticket-lifecycle.md +0 -5
- package/templates/hermes-agent/docs/scrum-master-sentinel.md +0 -17
- package/templates/hermes-agent/template/.scripts/75-scrum-master.sh +0 -117
- package/templates/hermes-agent/template/.scripts/90-chain-scrum-master.sh +0 -45
|
@@ -25,7 +25,7 @@ python3 - "$REGISTRY_FILE" "$AGENT_ID" "$REPO" "$ROLE" "$DISPLAY_NAME" \
|
|
|
25
25
|
"$PLANE_WORKSPACE" "$PLANE_PROJECT_ID" "$(yaml_get plane.identifier)" \
|
|
26
26
|
"$RUNTIME_REPO" "$HERMES_BIN" "$HERMES_AGENT_REPO" "$FLEET_ENV" \
|
|
27
27
|
"hermes-${AGENT_ID}-gateway.service" "hermes-${AGENT_ID}-consumer.service" \
|
|
28
|
-
"hermes-${AGENT_ID}-
|
|
28
|
+
"hermes-${AGENT_ID}-heartbeat.timer" <<'PYEOF'
|
|
29
29
|
import sys, pathlib, datetime
|
|
30
30
|
try:
|
|
31
31
|
import yaml # type: ignore
|
|
@@ -44,7 +44,7 @@ data.setdefault("agents", {})[agent_id] = {
|
|
|
44
44
|
"plane": {"workspace": plane_ws, "project_id": plane_id, "identifier": plane_ident},
|
|
45
45
|
"runtime_repo": runtime_repo,
|
|
46
46
|
"hermes": {"bin": hermes_bin, "repo": hermes_repo, "fleet_env": fleet_env},
|
|
47
|
-
"systemd": {"gateway_unit": gw, "consumer_unit": csm, "
|
|
47
|
+
"systemd": {"gateway_unit": gw, "consumer_unit": csm, "heartbeat_timer": ckpt},
|
|
48
48
|
"provisioned_at": datetime.datetime.utcnow().isoformat() + "Z",
|
|
49
49
|
}
|
|
50
50
|
p.write_text(yaml.safe_dump(data, sort_keys=False))
|
|
@@ -6,7 +6,7 @@ load_role_env
|
|
|
6
6
|
|
|
7
7
|
GW_UNIT="hermes-${AGENT_ID}-gateway.service"
|
|
8
8
|
CSM_UNIT="hermes-${AGENT_ID}-consumer.service"
|
|
9
|
-
|
|
9
|
+
HB_TIMER="hermes-${AGENT_ID}-heartbeat.timer"
|
|
10
10
|
|
|
11
11
|
cat >&2 <<EOF
|
|
12
12
|
|
|
@@ -14,16 +14,15 @@ cat >&2 <<EOF
|
|
|
14
14
|
│
|
|
15
15
|
│ Talk: @$BOT_HANDLE (Telegram DM)
|
|
16
16
|
│ Shell: $ROLE_DIR/hermes chat "status"
|
|
17
|
-
│ Profile: $PROFILE_NAME (config inherits from default; secrets stay local)
|
|
18
17
|
│ Board: $PLANE_BASE/$PLANE_WORKSPACE/projects/$(cat "$ROLE_DIR/.scripts/.plane-project-id" 2>/dev/null || echo "(skipped)")
|
|
19
|
-
│ Runtime: gh:$RUNTIME_REPO (
|
|
18
|
+
│ Runtime: gh:$RUNTIME_REPO (reconcile + checkpoint heartbeat)
|
|
20
19
|
│ Hermes bin: $HERMES_BIN
|
|
21
20
|
│ Fleet env: $FLEET_ENV
|
|
22
21
|
│
|
|
23
22
|
│ Start fleet daemons:
|
|
24
23
|
│ systemctl --user start $GW_UNIT
|
|
25
24
|
│ systemctl --user start $CSM_UNIT
|
|
26
|
-
│ systemctl --user start $
|
|
25
|
+
│ systemctl --user start $HB_TIMER
|
|
27
26
|
│
|
|
28
27
|
│ Tail logs:
|
|
29
28
|
│ journalctl --user -fu $GW_UNIT
|
|
@@ -147,15 +147,14 @@ if [[ -f "$FLEET_ENV" ]]; then
|
|
|
147
147
|
fi
|
|
148
148
|
|
|
149
149
|
# Tools we expect on the host
|
|
150
|
-
HERMES_BIN="${HERMES_BIN:-${HERMES_FLEET_BIN:-$(config_get fleet.hermes_bin
|
|
151
|
-
HERMES_AGENT_REPO="${HERMES_AGENT_REPO:-${HERMES_FLEET_REPO:-$(config_get fleet.hermes_repo
|
|
150
|
+
HERMES_BIN="${HERMES_BIN:-${HERMES_FLEET_BIN:-$(config_get fleet.hermes_bin "$HOME/.hermes/hermes-agent/.venv/bin/hermes")}}"
|
|
151
|
+
HERMES_AGENT_REPO="${HERMES_AGENT_REPO:-${HERMES_FLEET_REPO:-$(config_get fleet.hermes_repo "$HOME/.hermes/hermes-agent")}}"
|
|
152
152
|
HERMES_OAUTH_FILE="${HERMES_OAUTH_FILE:-${HERMES_FLEET_OAUTH_FILE:-$(config_get fleet.oauth_file "$HOME/.hermes/auth.json")}}"
|
|
153
153
|
CODEX_HOME="${CODEX_HOME:-${HERMES_FLEET_CODEX_HOME:-$(config_get fleet.codex_home "$HOME/.codex")}}"
|
|
154
|
-
FLEET_HOME="${HERMES_FLEET_HOME:-$(config_get fleet.home "$HOME/.hermes")}"
|
|
155
154
|
# Prefer a scaffold vendored into this agent directory; fall back to the configured template path.
|
|
156
155
|
RUNTIME_SCAFFOLD_DIR="${RUNTIME_SCAFFOLD_DIR:-$ROLE_DIR/.runtime-scaffold}"
|
|
157
156
|
if [[ ! -d "$RUNTIME_SCAFFOLD_DIR" ]]; then
|
|
158
|
-
RUNTIME_SCAFFOLD_DIR="${HERMES_TEMPLATE_RUNTIME_SCAFFOLD:-$(config_get fleet.runtime_scaffold_dir
|
|
157
|
+
RUNTIME_SCAFFOLD_DIR="${HERMES_TEMPLATE_RUNTIME_SCAFFOLD:-$(config_get fleet.runtime_scaffold_dir "$HOME/code/hermes-agent-template/runtime-scaffold")}"
|
|
159
158
|
fi
|
|
160
159
|
REGISTRY_FILE="${REGISTRY_FILE:-${HERMES_FLEET_REGISTRY_FILE:-$(config_get fleet.registry_file "$HOME/.hermes/agents-registry.yaml")}}"
|
|
161
160
|
|
|
@@ -168,25 +167,11 @@ BLOODBANK_COMPOSE_DIR="${BLOODBANK_COMPOSE_DIR:-$(config_get bloodbank.compose_d
|
|
|
168
167
|
PLANE_BASE="${PLANE_BASE:-$(config_get plane.base 'https://plane.delo.sh')}"
|
|
169
168
|
PLANE_API_KEY="${PLANE_API_KEY:-${PLANE_33GOD_API_KEY:-}}"
|
|
170
169
|
|
|
171
|
-
export FLEET_ENV HERMES_BIN HERMES_AGENT_REPO HERMES_OAUTH_FILE CODEX_HOME
|
|
170
|
+
export FLEET_ENV HERMES_BIN HERMES_AGENT_REPO HERMES_OAUTH_FILE CODEX_HOME \
|
|
172
171
|
RUNTIME_SCAFFOLD_DIR REGISTRY_FILE \
|
|
173
172
|
BLOODBANK_NATS_HOST BLOODBANK_NATS_PORT BLOODBANK_COMPOSE_DIR \
|
|
174
173
|
PLANE_BASE PLANE_API_KEY
|
|
175
174
|
|
|
176
|
-
profile_home() {
|
|
177
|
-
printf '%s/profiles/%s' "$FLEET_HOME" "$PROFILE_NAME"
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
profile_or_runtime_home() {
|
|
181
|
-
local profile
|
|
182
|
-
profile="$(profile_home)"
|
|
183
|
-
if [[ -d "$profile" ]]; then
|
|
184
|
-
printf '%s' "$profile"
|
|
185
|
-
else
|
|
186
|
-
printf '%s/runtime' "$ROLE_DIR"
|
|
187
|
-
fi
|
|
188
|
-
}
|
|
189
|
-
|
|
190
175
|
# systemd --user health check. Accept running/degraded/starting — only one
|
|
191
176
|
# broken unit shouldn't disqualify the rest of the user manager.
|
|
192
177
|
systemd_user_available() {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Auto-checkpoint commit for the agent's runtime submodule.
|
|
3
3
|
# Idempotent — exits 0 with no commit if there are no changes.
|
|
4
|
+
#
|
|
5
|
+
# Secret-scan gate (PJAN): before committing, the staged diff is scanned for
|
|
6
|
+
# high-signal credentials. On a hit the checkpoint ABORTS — it unstages, does
|
|
7
|
+
# NOT commit or push, logs loudly, and exits non-zero — so the known
|
|
8
|
+
# auto-commit secret-leak recurrence cannot re-leak through the heartbeat.
|
|
4
9
|
set -euo pipefail
|
|
5
10
|
|
|
6
11
|
RUNTIME_DIR="$(cd "$(dirname "$0")/../runtime" && pwd)"
|
|
@@ -9,9 +14,40 @@ cd "$RUNTIME_DIR"
|
|
|
9
14
|
# Skip if not a git repo (e.g. submodule not initialized)
|
|
10
15
|
[[ -d .git || -f .git ]] || exit 0
|
|
11
16
|
|
|
17
|
+
# Returns 0 when the staged diff looks clean, 1 when a likely secret is present.
|
|
18
|
+
secret_scan_ok() {
|
|
19
|
+
local added
|
|
20
|
+
added="$(git diff --cached -U0 --no-color 2>/dev/null | grep -E '^\+' | grep -vE '^\+\+\+ ' || true)"
|
|
21
|
+
[[ -n "$added" ]] || return 0
|
|
22
|
+
|
|
23
|
+
# Dependency-free, high-signal patterns (distinctive prefixes → ~zero false
|
|
24
|
+
# positives) over ADDED lines only.
|
|
25
|
+
if printf '%s\n' "$added" | grep -Eq \
|
|
26
|
+
'AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----|gh[posru]_[A-Za-z0-9]{20,}|xox[baprs]-[0-9A-Za-z-]{10,}|sk-[A-Za-z0-9]{20,}|glpat-[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{35}'; then
|
|
27
|
+
return 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Deeper scan when gitleaks is installed (exit 1 == leaks found; other
|
|
31
|
+
# non-zero == tool/usage error, which we ignore so checkpoints aren't blocked).
|
|
32
|
+
if command -v gitleaks >/dev/null 2>&1; then
|
|
33
|
+
local rc=0
|
|
34
|
+
git diff --cached --no-color 2>/dev/null | gitleaks stdin --no-banner --redact >/dev/null 2>&1 || rc=$?
|
|
35
|
+
[[ "$rc" -eq 1 ]] && return 1
|
|
36
|
+
fi
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
12
40
|
git add -A
|
|
13
41
|
if git diff --cached --quiet; then
|
|
14
42
|
exit 0
|
|
15
43
|
fi
|
|
44
|
+
|
|
45
|
+
if ! secret_scan_ok; then
|
|
46
|
+
printf '[checkpoint] ABORT: potential secret in staged runtime changes — not committing/pushing.\n' >&2
|
|
47
|
+
printf '[checkpoint] Inspect: git -C %s diff --cached (remove the secret or add a .gitignore rule)\n' "$RUNTIME_DIR" >&2
|
|
48
|
+
git reset -q || true
|
|
49
|
+
exit 3
|
|
50
|
+
fi
|
|
51
|
+
|
|
16
52
|
git -c commit.gpgsign=false commit -m "checkpoint $(date -Iseconds)" >/dev/null
|
|
17
53
|
git push origin HEAD 2>&1 | tail -1 || true
|
|
@@ -11,15 +11,13 @@
|
|
|
11
11
|
# somewhere other than $XDG_CONFIG_HOME/hermes-agent-template/config.toml.
|
|
12
12
|
|
|
13
13
|
[fleet]
|
|
14
|
-
# Root Hermes fleet home. Named profiles live under <home>/profiles/<name>.
|
|
15
|
-
home = "~/.hermes"
|
|
16
14
|
# Absolute path to the shared Hermes executable every agent launcher execs.
|
|
17
|
-
hermes_bin = "/
|
|
15
|
+
hermes_bin = "~/.hermes/hermes-agent/.venv/bin/hermes"
|
|
18
16
|
# Checkout of the hermes-agent repo. Its .venv is used to install nats-py, etc.
|
|
19
|
-
hermes_repo = "/
|
|
17
|
+
hermes_repo = "~/.hermes/hermes-agent"
|
|
20
18
|
# Fallback runtime scaffold dir — used only when an agent's vendored
|
|
21
19
|
# ./.runtime-scaffold is missing.
|
|
22
|
-
runtime_scaffold_dir = "
|
|
20
|
+
runtime_scaffold_dir = "~/code/hermes-agent-template/runtime-scaffold"
|
|
23
21
|
# Shared fleet source-of-truth env file + fleet registry. ~ is expanded.
|
|
24
22
|
fleet_env = "~/.hermes/fleet.env"
|
|
25
23
|
registry_file = "~/.hermes/agents-registry.yaml"
|
|
@@ -29,12 +27,20 @@ oauth_file = "~/.hermes/auth.json"
|
|
|
29
27
|
codex_home = "~/.codex"
|
|
30
28
|
# Canonical external skills dir mirrored into each agent profile.
|
|
31
29
|
canonical_skills_dir = "/home/delorenj/.agents/skills"
|
|
30
|
+
# Voxxy plugin + service defaults for Hermes PM agents.
|
|
31
|
+
voxxy_plugin_dir = "~/code/voxxy/plugins/tts/voxxy"
|
|
32
|
+
vox_url = "https://vox.delo.sh"
|
|
33
|
+
# Canonical PM config.yaml the provisioner seeds each runtime's config.yaml from.
|
|
34
|
+
# This is the fleet-wide Hermes inference/skill config (model, toolsets, etc.)
|
|
35
|
+
# every agent runtime starts from. ~ is expanded.
|
|
36
|
+
# canonical_pm_config = "~/.hermes/config.yaml"
|
|
32
37
|
# Skills symlinked into every agent's runtime skills root.
|
|
33
38
|
symlinked_runtime_skills = [
|
|
34
39
|
"delonet-conventions",
|
|
35
40
|
"delonet-dotenv",
|
|
36
41
|
"hermes-pm-template-maintenance",
|
|
37
42
|
"hindsight",
|
|
43
|
+
"33god-projects",
|
|
38
44
|
"subagent-driven-development",
|
|
39
45
|
]
|
|
40
46
|
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
2
|
+
# Unified agent heartbeat: continuous board-reconciliation sentinel pass +
|
|
3
|
+
# gated runtime checkpoint, fused into one systemd-timer tick.
|
|
3
4
|
#
|
|
4
|
-
# Runs often (systemd timer). Only invokes Hermes
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# ticket-provider adapter (Linear | Plane | Trello) — never a
|
|
5
|
+
# Runs often (systemd timer, ~1 min). Only invokes Hermes for a full
|
|
6
|
+
# reconciliation pass when local state says no worker is active, the worker
|
|
7
|
+
# heartbeat is stale, or the last full run is outside the cooldown window. The
|
|
8
|
+
# full pass executes the role's sentinel.prompt.md, which reasons about tickets
|
|
9
|
+
# through the ticket-provider adapter (Linear | Plane | Trello) — never a
|
|
10
|
+
# hardcoded backend. After the sentinel decision (skip OR full), it
|
|
11
|
+
# opportunistically checkpoints the runtime submodule (commit+push) at most once
|
|
12
|
+
# per HEARTBEAT_CHECKPOINT_MIN_INTERVAL_SECONDS, so memory/session state stays
|
|
13
|
+
# durable without pushing every minute.
|
|
9
14
|
set -euo pipefail
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
ROLE_DIR="$(cd "$ENGINE_DIR/../.." && pwd)"
|
|
16
|
+
ROLE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # agents/hermes/<role>
|
|
13
17
|
RUNTIME="$ROLE_DIR/runtime"
|
|
14
|
-
PROMPT_FILE="$
|
|
18
|
+
PROMPT_FILE="$ROLE_DIR/.scripts/sentinel.prompt.md"
|
|
15
19
|
STATE_FILE="$RUNTIME/continuous-ticket-sentinel-state.json"
|
|
16
20
|
LOCK_FILE="$RUNTIME/continuous-ticket-sentinel.lock"
|
|
17
21
|
ROLE_YAML="$ROLE_DIR/role.yaml"
|
|
18
|
-
LOG_FILE="$RUNTIME/logs/
|
|
22
|
+
LOG_FILE="$RUNTIME/logs/heartbeat.log"
|
|
23
|
+
CHECKPOINT_BIN="$ROLE_DIR/.scripts/checkpoint.sh"
|
|
24
|
+
CHECKPOINT_STAMP="$RUNTIME/.last-checkpoint"
|
|
19
25
|
|
|
20
26
|
# Hermes binary: explicit env > ~/.config/hermes-agent/hermes-bin > PATH.
|
|
21
27
|
HERMES_BIN="${HERMES_BIN:-}"
|
|
@@ -30,6 +36,24 @@ fi
|
|
|
30
36
|
ACTIVE_MAX_IDLE_SECONDS="${SENTINEL_ACTIVE_MAX_IDLE_SECONDS:-600}"
|
|
31
37
|
FULL_RUN_COOLDOWN_SECONDS="${SENTINEL_FULL_RUN_COOLDOWN_SECONDS:-300}"
|
|
32
38
|
BLOCKED_FULL_RUN_COOLDOWN_SECONDS="${SENTINEL_BLOCKED_FULL_RUN_COOLDOWN_SECONDS:-900}"
|
|
39
|
+
CHECKPOINT_MIN_INTERVAL_SECONDS="${HEARTBEAT_CHECKPOINT_MIN_INTERVAL_SECONDS:-3600}"
|
|
40
|
+
|
|
41
|
+
# Opportunistic runtime checkpoint, fused into the heartbeat. Runs at most once
|
|
42
|
+
# per interval; checkpoint.sh is itself a no-op on a clean tree, so this only
|
|
43
|
+
# commits+pushes when there is genuinely new state AND enough time has passed
|
|
44
|
+
# since the last push. Never fails the heartbeat — checkpoint is best-effort.
|
|
45
|
+
maybe_checkpoint() {
|
|
46
|
+
[[ -x "$CHECKPOINT_BIN" ]] || return 0
|
|
47
|
+
local now last
|
|
48
|
+
now="$(date +%s)"
|
|
49
|
+
last="$(cat "$CHECKPOINT_STAMP" 2>/dev/null || echo 0)"
|
|
50
|
+
[[ "$last" =~ ^[0-9]+$ ]] || last=0
|
|
51
|
+
if (( now - last >= CHECKPOINT_MIN_INTERVAL_SECONDS )); then
|
|
52
|
+
printf '%s' "$now" > "$CHECKPOINT_STAMP" 2>/dev/null || true
|
|
53
|
+
printf '[heartbeat] checkpoint tick (>= %ss since last push)\n' "$CHECKPOINT_MIN_INTERVAL_SECONDS"
|
|
54
|
+
"$CHECKPOINT_BIN" || true
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
33
57
|
|
|
34
58
|
yaml_value() {
|
|
35
59
|
python3 - "$ROLE_YAML" "$1" <<'PYEOF'
|
|
@@ -42,21 +66,37 @@ print(m.group(1).strip() if m else "")
|
|
|
42
66
|
PYEOF
|
|
43
67
|
}
|
|
44
68
|
|
|
69
|
+
yaml_block_value() {
|
|
70
|
+
python3 - "$ROLE_YAML" "$1" "$2" <<'PYEOF'
|
|
71
|
+
import re, sys
|
|
72
|
+
from pathlib import Path
|
|
73
|
+
path, block_name, key = sys.argv[1:4]
|
|
74
|
+
text = Path(path).read_text()
|
|
75
|
+
m = re.search(rf'(?m)^{re.escape(block_name)}:[ \t]*\n((?:[ \t]+\S.*\n?)*)', text)
|
|
76
|
+
block = m.group(1) if m else ""
|
|
77
|
+
me = re.search(rf'(?m)^[ \t]+{re.escape(key)}:[ \t]*([^\n#]*)', block)
|
|
78
|
+
value = me.group(1).strip() if me else ""
|
|
79
|
+
print(value.strip().strip('"').strip("'"))
|
|
80
|
+
PYEOF
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# True only when role.yaml has a reconcile: block with enabled: true. Block-aware
|
|
84
|
+
# so an unrelated `enabled:` leaf elsewhere in the file can't flip it on.
|
|
85
|
+
reconcile_enabled() {
|
|
86
|
+
python3 - "$ROLE_YAML" <<'PYEOF'
|
|
87
|
+
import re, sys
|
|
88
|
+
from pathlib import Path
|
|
89
|
+
text = Path(sys.argv[1]).read_text()
|
|
90
|
+
m = re.search(r'(?m)^reconcile:[ \t]*\n((?:[ \t]+\S.*\n?)*)', text)
|
|
91
|
+
block = m.group(1) if m else ""
|
|
92
|
+
me = re.search(r'(?m)^[ \t]+enabled:[ \t]*"?([A-Za-z]+)"?', block)
|
|
93
|
+
print("true" if (me and me.group(1).lower() == "true") else "false")
|
|
94
|
+
PYEOF
|
|
95
|
+
}
|
|
96
|
+
|
|
45
97
|
AGENT_ID="$(yaml_value agent_id)"
|
|
46
98
|
REPO_NAME="$(yaml_value repo)"
|
|
47
|
-
|
|
48
|
-
PROVIDER="$(yaml_value name)" # ticket_provider.name leaf
|
|
49
|
-
[[ -n "$PROFILE_NAME" ]] || PROFILE_NAME="$AGENT_ID"
|
|
50
|
-
FLEET_HOME="${HERMES_FLEET_HOME:-$HOME/.hermes}"
|
|
51
|
-
PROFILE_HOME="$FLEET_HOME/profiles/$PROFILE_NAME"
|
|
52
|
-
HERMES_PROFILE_HOME="${HERMES_HOME:-}"
|
|
53
|
-
if [[ -z "$HERMES_PROFILE_HOME" ]]; then
|
|
54
|
-
if [[ -d "$PROFILE_HOME" ]]; then
|
|
55
|
-
HERMES_PROFILE_HOME="$PROFILE_HOME"
|
|
56
|
-
else
|
|
57
|
-
HERMES_PROFILE_HOME="$RUNTIME"
|
|
58
|
-
fi
|
|
59
|
-
fi
|
|
99
|
+
PROVIDER="$(yaml_block_value ticket_provider name)"
|
|
60
100
|
|
|
61
101
|
repo_root() {
|
|
62
102
|
local dir="$ROLE_DIR"
|
|
@@ -75,7 +115,7 @@ mkdir -p "$RUNTIME/logs"
|
|
|
75
115
|
if command -v flock >/dev/null 2>&1; then
|
|
76
116
|
exec 9>"$LOCK_FILE"
|
|
77
117
|
if ! flock -n 9; then
|
|
78
|
-
printf '[
|
|
118
|
+
printf '[heartbeat] another heartbeat/full run is active; skipping\n'; exit 0
|
|
79
119
|
fi
|
|
80
120
|
else
|
|
81
121
|
LOCK_DIR="$LOCK_FILE.d"
|
|
@@ -83,14 +123,24 @@ else
|
|
|
83
123
|
# Steal a stale lock older than 60 minutes (a crashed run left it behind).
|
|
84
124
|
if [ -n "$(find "$LOCK_DIR" -maxdepth 0 -mmin +60 2>/dev/null)" ]; then
|
|
85
125
|
rmdir "$LOCK_DIR" 2>/dev/null && mkdir "$LOCK_DIR" 2>/dev/null \
|
|
86
|
-
|| { printf '[
|
|
126
|
+
|| { printf '[heartbeat] another run active; skipping\n'; exit 0; }
|
|
87
127
|
else
|
|
88
|
-
printf '[
|
|
128
|
+
printf '[heartbeat] another heartbeat/full run is active; skipping\n'; exit 0
|
|
89
129
|
fi
|
|
90
130
|
fi
|
|
91
131
|
trap 'rmdir "$LOCK_DIR" 2>/dev/null' EXIT
|
|
92
132
|
fi
|
|
93
133
|
|
|
134
|
+
# Reconcile gate: the autonomous board-reconciliation pass runs only when
|
|
135
|
+
# role.yaml's reconcile.enabled is true. Default off → the heartbeat just
|
|
136
|
+
# checkpoints (behaves like the legacy hourly checkpoint timer). Flip
|
|
137
|
+
# reconcile.enabled to opt a repo into autonomous board reconciliation.
|
|
138
|
+
if [[ "$(reconcile_enabled)" != "true" ]]; then
|
|
139
|
+
printf '[heartbeat] reconcile disabled (reconcile.enabled != true) — checkpoint-only tick\n'
|
|
140
|
+
maybe_checkpoint
|
|
141
|
+
exit 0
|
|
142
|
+
fi
|
|
143
|
+
|
|
94
144
|
decision="$(
|
|
95
145
|
python3 - "$STATE_FILE" "$ACTIVE_MAX_IDLE_SECONDS" "$FULL_RUN_COOLDOWN_SECONDS" "$BLOCKED_FULL_RUN_COOLDOWN_SECONDS" <<'PYEOF'
|
|
96
146
|
import json, subprocess, sys, time
|
|
@@ -157,7 +207,9 @@ state.update({"source":"hermes-continuous-ticket-sentinel","agent_id":agent_id,"
|
|
|
157
207
|
state.setdefault("updated_at", now_iso); state.setdefault("summary", state.get("reason") or decision)
|
|
158
208
|
tmp = path.with_suffix(path.suffix + ".tmp"); tmp.write_text(json.dumps(state, indent=2, sort_keys=True)+"\n"); tmp.replace(path)
|
|
159
209
|
PYEOF
|
|
160
|
-
printf '[
|
|
210
|
+
printf '[heartbeat] %s\n' "$decision"
|
|
211
|
+
maybe_checkpoint
|
|
212
|
+
exit 0
|
|
161
213
|
;;
|
|
162
214
|
esac
|
|
163
215
|
|
|
@@ -170,7 +222,7 @@ state = json.loads(path.read_text()) if path.exists() else {}
|
|
|
170
222
|
now = time.time()
|
|
171
223
|
state.update({"source":"hermes-continuous-ticket-sentinel","agent_id":agent_id,"repo":repo,
|
|
172
224
|
"ticket_provider":provider,"status":"checking",
|
|
173
|
-
"summary":"
|
|
225
|
+
"summary":"PM is reconciling the ticket board, evidence, and worker state.",
|
|
174
226
|
"log_path":log_path,"last_heartbeat_at":datetime.fromtimestamp(now,timezone.utc).isoformat(),
|
|
175
227
|
"last_decision":decision,"last_full_run_epoch":now,
|
|
176
228
|
"last_full_run_started_at":datetime.fromtimestamp(now,timezone.utc).isoformat()})
|
|
@@ -179,7 +231,7 @@ PYEOF
|
|
|
179
231
|
|
|
180
232
|
prompt="$(<"$PROMPT_FILE")"
|
|
181
233
|
set +e
|
|
182
|
-
env HERMES_HOME="$
|
|
234
|
+
env HERMES_HOME="$RUNTIME" "$HERMES_BIN" chat -Q --source cron --max-turns 90 -q "$prompt"
|
|
183
235
|
status=$?
|
|
184
236
|
set -e
|
|
185
237
|
|
|
@@ -197,11 +249,12 @@ state["last_runner_exit_code"] = exit_code; state["last_runner_completed_at"] =
|
|
|
197
249
|
state.setdefault("last_full_run_epoch", time.time())
|
|
198
250
|
if state.get("status") == "checking":
|
|
199
251
|
state["status"] = "idle" if exit_code == 0 else "error"
|
|
200
|
-
state["summary"] = ("
|
|
201
|
-
if exit_code == 0 else "
|
|
252
|
+
state["summary"] = ("PM completed the reconciliation pass without claiming work."
|
|
253
|
+
if exit_code == 0 else "PM reconciliation failed; inspect the heartbeat log.")
|
|
202
254
|
state.setdefault("updated_at", now_iso)
|
|
203
255
|
tmp = path.with_suffix(path.suffix + ".tmp"); tmp.write_text(json.dumps(state, indent=2, sort_keys=True)+"\n"); tmp.replace(path)
|
|
204
256
|
print(0 if state.get("status") in {"active","blocked","stalled","idle"} else exit_code)
|
|
205
257
|
PYEOF
|
|
206
258
|
)"
|
|
259
|
+
maybe_checkpoint
|
|
207
260
|
exit "$runner_exit"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# shellcheck shell=bash
|
|
2
|
-
# Ticket-provider adapter dispatcher — the single seam between the
|
|
2
|
+
# Ticket-provider adapter dispatcher — the single seam between the heartbeat
|
|
3
3
|
# sentinel engine and a concrete ticket system (Linear | Plane | Trello).
|
|
4
4
|
#
|
|
5
5
|
# The engine NEVER calls a provider directly. It calls `tp <op> [args...]`,
|
|
@@ -179,10 +179,16 @@ print(json.dumps({"id":i.get("id",""),"key":i.get("sequence_id",""),"title":i.ge
|
|
|
179
179
|
create_board)
|
|
180
180
|
NAME="${1:?usage: create_board <name> <ident> <desc>}"; IDENT="${2:-}"; DESC="${3:-}"
|
|
181
181
|
[ -n "$WS" ] || die "workspace not set"
|
|
182
|
-
EXIST="$(api GET "projects/?per_page=200" | IDENT="$IDENT" python3 -c 'import sys,json,os
|
|
182
|
+
EXIST="$(api GET "projects/?per_page=200" | NAME="$NAME" IDENT="$IDENT" python3 -c 'import sys,json,os
|
|
183
183
|
d=json.load(sys.stdin); rows=d.get("results", d if isinstance(d,list) else [])
|
|
184
|
-
ident=os.environ["IDENT"].upper()
|
|
185
|
-
|
|
184
|
+
name=os.environ["NAME"].strip().lower(); ident=os.environ["IDENT"].upper()
|
|
185
|
+
# Repo NAME is the primary key — links an existing repo board even if its
|
|
186
|
+
# identifier differs (Plane does not enforce unique names, so this prevents
|
|
187
|
+
# duplicate boards). Fall back to identifier match; empty -> create new.
|
|
188
|
+
pid=next((p["id"] for p in rows if str(p.get("name","")).strip().lower()==name), "")
|
|
189
|
+
if not pid and ident:
|
|
190
|
+
pid=next((p["id"] for p in rows if (p.get("identifier") or "").upper()==ident), "")
|
|
191
|
+
print(pid)')"
|
|
186
192
|
if [ -n "$EXIST" ]; then PID="$EXIST"; else
|
|
187
193
|
PID="$(api POST "projects/" \
|
|
188
194
|
"$(python3 -c 'import json,sys; print(json.dumps({"name":sys.argv[1],"identifier":sys.argv[2],"description":sys.argv[3]}))' "$NAME" "$IDENT" "$DESC")" \
|
package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/emit-event.py
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Emit a local BloodBank-style CloudEvents envelope (provider-agnostic).
|
|
3
3
|
|
|
4
|
-
Dependency-free. Appends JSONL locally so the
|
|
4
|
+
Dependency-free. Appends JSONL locally so the sentinel engine records an
|
|
5
5
|
event trail even when NATS/BloodBank is offline. Identical envelope shape to the
|
|
6
|
-
Hermes gateway; see .scripts/
|
|
6
|
+
Hermes gateway; see .scripts/sentinel/docs/bloodbank-events.md.
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
@@ -55,8 +55,8 @@ def main() -> int:
|
|
|
55
55
|
parser.add_argument("--field", action="append", type=parse_field, default=[])
|
|
56
56
|
parser.add_argument("--data-json")
|
|
57
57
|
parser.add_argument("--source")
|
|
58
|
-
parser.add_argument("--producer", default=os.environ.get("BLOODBANK_PRODUCER", "local-script:
|
|
59
|
-
parser.add_argument("--service", default=os.environ.get("BLOODBANK_SERVICE", "hermes-
|
|
58
|
+
parser.add_argument("--producer", default=os.environ.get("BLOODBANK_PRODUCER", "local-script:sentinel"))
|
|
59
|
+
parser.add_argument("--service", default=os.environ.get("BLOODBANK_SERVICE", "hermes-sentinel"))
|
|
60
60
|
parser.add_argument("--actor-id", default=os.environ.get("BLOODBANK_ACTOR_ID", os.environ.get("USER", "local-agent")))
|
|
61
61
|
parser.add_argument("--actor-cli", default=os.environ.get("BLOODBANK_ACTOR_CLI", "script"))
|
|
62
62
|
parser.add_argument("--correlation-id", default=os.environ.get("BLOODBANK_CORRELATION_ID"))
|
|
@@ -75,7 +75,7 @@ def main() -> int:
|
|
|
75
75
|
if not log_path.is_absolute():
|
|
76
76
|
log_path = root / log_path
|
|
77
77
|
|
|
78
|
-
source = args.source or "repo://
|
|
78
|
+
source = args.source or "repo://sentinel/bin/emit-event.py"
|
|
79
79
|
event = {
|
|
80
80
|
"specversion": "1.0",
|
|
81
81
|
"type": args.event_type,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# ticket-provider adapter (tp transition <id> completed), so the same logic works
|
|
18
18
|
# on Linear | Plane | Trello.
|
|
19
19
|
#
|
|
20
|
-
# Protocol: .scripts/
|
|
20
|
+
# Protocol: .scripts/sentinel/docs/autonomous-delegated-review.md
|
|
21
21
|
#
|
|
22
22
|
# Usage: issue-autonomous-review.sh ISSUE_ID REPORT_FILE [--close]
|
|
23
23
|
#
|
|
@@ -33,7 +33,7 @@ ISSUE="$1"; REPORT="$2"; CLOSE=0
|
|
|
33
33
|
case "$ISSUE" in *[!A-Za-z0-9_-]*) printf 'Invalid issue id: %s\n' "$ISSUE" >&2; exit 2 ;; esac
|
|
34
34
|
|
|
35
35
|
BIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
-
ENGINE_DIR="$(cd "$BIN_DIR/.." && pwd)" # .scripts/
|
|
36
|
+
ENGINE_DIR="$(cd "$BIN_DIR/.." && pwd)" # .scripts/sentinel
|
|
37
37
|
SCRIPTS_DIR="$(cd "$ENGINE_DIR/.." && pwd)" # .scripts
|
|
38
38
|
ROLE_DIR="$(cd "$SCRIPTS_DIR/.." && pwd)"
|
|
39
39
|
ROLE_YAML="$ROLE_DIR/role.yaml"
|
|
@@ -48,12 +48,12 @@ yget() { sed -n "s/^[[:space:]]*$1:[[:space:]]*//p" "$ROLE_YAML" 2>/dev/null | h
|
|
|
48
48
|
REPO="$(yget repo)"; REPO="${REPO:-unknown}"
|
|
49
49
|
# Informational only: recorded in the decision event/comment, never a blocking
|
|
50
50
|
# wait. Default 0 (no grace); an operator may set grace_hours>0 to reintroduce one.
|
|
51
|
-
GRACE_HOURS="${
|
|
51
|
+
GRACE_HOURS="${RECONCILE_GRACE_HOURS:-$(yget grace_hours)}"; GRACE_HOURS="${GRACE_HOURS:-0}"
|
|
52
52
|
AUTO="$(yget auto_review)"; AUTO="${AUTO:-true}"
|
|
53
53
|
EVT="bloodbank.v1.repo.$REPO.issue.autonomous_review.decided"
|
|
54
54
|
|
|
55
|
-
if [[ "${
|
|
56
|
-
printf 'Autonomous review is disabled (
|
|
55
|
+
if [[ "${RECONCILE_AUTO_REVIEW:-$AUTO}" == "false" || "${RECONCILE_AUTO_REVIEW:-}" == "off" ]]; then
|
|
56
|
+
printf 'Autonomous review is disabled (reconcile.auto_review=false).\n' >&2; exit 3
|
|
57
57
|
fi
|
|
58
58
|
[[ -f "$REPORT" ]] || { printf 'Missing review report file: %s\n' "$REPORT" >&2; exit 2; }
|
|
59
59
|
[[ -f "$EVIDENCE" ]] || { printf 'Missing issue evidence file: %s\n' "$EVIDENCE" >&2; exit 2; }
|
|
@@ -86,7 +86,7 @@ if sh "$CLOSE_GATE" "$ISSUE" "$ROOT" >/dev/null 2>&1 </dev/null; then GATE=pass;
|
|
|
86
86
|
if [[ -n "$HOLD" ]]; then DECISION=held; else DECISION=accepted; fi
|
|
87
87
|
|
|
88
88
|
python3 "$EMIT" "$EVT" --root "$ROOT" \
|
|
89
|
-
--source "repo://
|
|
89
|
+
--source "repo://sentinel/bin/issue-autonomous-review.sh" --actor-id "$REVIEWER" \
|
|
90
90
|
--field issue="$ISSUE" --field decision="$DECISION" --field drift="$DRIFT" \
|
|
91
91
|
--field close_gate="$GATE" --field reviewer_agent="$REVIEWER" \
|
|
92
92
|
--field evidence_file="$EVIDENCE" --field report_file="$REPORT" \
|
package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-close-gate.sh
RENAMED
|
@@ -50,13 +50,13 @@ grep -q 'Close recommendation: ready' "$FILE" || { printf 'Close recommendation
|
|
|
50
50
|
|
|
51
51
|
if [ "$FAIL" -ne 0 ]; then
|
|
52
52
|
python3 "$EMIT" "$EVT_PREFIX.gate.failed" --root "$ROOT" \
|
|
53
|
-
--source "repo://
|
|
53
|
+
--source "repo://sentinel/bin/issue-close-gate.sh" \
|
|
54
54
|
--field issue="$ISSUE" --field evidence_file="$FILE" --quiet </dev/null || true
|
|
55
55
|
printf '\nCLOSE GATE: FAIL for %s\n' "$ISSUE" >&2
|
|
56
56
|
exit 1
|
|
57
57
|
fi
|
|
58
58
|
|
|
59
59
|
python3 "$EMIT" "$EVT_PREFIX.gate.passed" --root "$ROOT" \
|
|
60
|
-
--source "repo://
|
|
60
|
+
--source "repo://sentinel/bin/issue-close-gate.sh" \
|
|
61
61
|
--field issue="$ISSUE" --field evidence_file="$FILE" --quiet </dev/null || true
|
|
62
62
|
printf 'CLOSE GATE: PASS for %s\n' "$ISSUE"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Autonomous adversarial review (act, do not wait)
|
|
2
2
|
|
|
3
|
-
Status:
|
|
3
|
+
Status: sentinel engine protocol (provider-agnostic)
|
|
4
4
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
|
7
|
-
When a ticket reaches the review lane, the
|
|
7
|
+
When a ticket reaches the review lane, the sentinel runs an
|
|
8
8
|
**independent adversarial review** of the work against the operator's locked
|
|
9
9
|
intent and acts on the verdict autonomously. This is the **normal per-pass
|
|
10
10
|
path** for review-lane tickets — not a deliberately narrow escape hatch, and not
|
|
@@ -55,8 +55,8 @@ exactly as before:
|
|
|
55
55
|
the implementer recorded in the evidence (`Worker:` / `Implemented by:`) and
|
|
56
56
|
from the delegating PM. An implementer NEVER clears their own work.
|
|
57
57
|
|
|
58
|
-
There is no mandatory grace window. `
|
|
59
|
-
override env `
|
|
58
|
+
There is no mandatory grace window. `reconcile.grace_hours` (role.yaml;
|
|
59
|
+
override env `RECONCILE_GRACE_HOURS`) defaults to `0` and is an
|
|
60
60
|
optional operator knob only — set `>0` to reintroduce a wait. The reviewer does
|
|
61
61
|
not wait for the operator's first right of refusal; it reviews and acts.
|
|
62
62
|
|
|
@@ -84,7 +84,7 @@ Only `none`/`minor` with no unresolved critical/high findings may be accepted.
|
|
|
84
84
|
Run from the role's bin (couples gate + drift + event + acceptance):
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
.scripts/
|
|
87
|
+
.scripts/sentinel/bin/issue-autonomous-review.sh <ISSUE> <REPORT>
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
- **accepted** — independent adversarial review cleared it (drift `none|minor`,
|
|
@@ -109,7 +109,7 @@ operator's deferred-QA queue — and is NOT auto-transitioned to `completed`.
|
|
|
109
109
|
loop:
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
.scripts/
|
|
112
|
+
.scripts/sentinel/bin/issue-autonomous-review.sh <ISSUE> <REPORT> --close
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
## Decision event
|
|
@@ -162,7 +162,7 @@ Write `<ISSUE>.review.md`; the script validates it:
|
|
|
162
162
|
|
|
163
163
|
## Operator override
|
|
164
164
|
|
|
165
|
-
`
|
|
165
|
+
`reconcile.auto_review: false` (role.yaml) or `RECONCILE_AUTO_REVIEW=off`
|
|
166
166
|
disables autonomous acceptance. Autonomous acceptances are fully traceable via
|
|
167
167
|
the decision events in
|
|
168
168
|
`_bmad-output/implementation-artifacts/bloodbank-events.jsonl`.
|
package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/bloodbank-events.md
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sentinel workflow events
|
|
2
2
|
|
|
3
|
-
Status:
|
|
3
|
+
Status: Sentinel engine protocol (provider-agnostic)
|
|
4
4
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ what happened.
|
|
|
12
12
|
## Emitter
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
.scripts/
|
|
15
|
+
.scripts/sentinel/bin/emit-event.py <event_type> --field key=value [...]
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Appends to `_bmad-output/implementation-artifacts/bloodbank-events.jsonl`
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# Continuous ticket orchestration (
|
|
1
|
+
# Continuous ticket orchestration (sentinel engine)
|
|
2
2
|
|
|
3
|
-
Status:
|
|
3
|
+
Status: sentinel engine protocol (provider-agnostic)
|
|
4
4
|
|
|
5
5
|
## Invariant
|
|
6
6
|
|
|
7
7
|
If a ready ticket exists, exactly one implementation worker must be actively
|
|
8
|
-
moving it, or the
|
|
8
|
+
moving it, or the sentinel records why none can. Prefer one live thread over
|
|
9
9
|
a quiet backlog. WIP limit: one active worker ticket.
|
|
10
10
|
|
|
11
|
-
The
|
|
12
|
-
implementation. The
|
|
11
|
+
The sentinel owns the watch loop. Workers (codex, opencode, copilot, …) own
|
|
12
|
+
implementation. The sentinel clears review-lane work via the autonomous
|
|
13
13
|
adversarial review (act, do not wait) protocol
|
|
14
14
|
(`autonomous-delegated-review.md`), and still does not write application code or
|
|
15
15
|
approve merges.
|
|
@@ -61,7 +61,7 @@ The loop never ends a pass with work parked waiting on the operator.
|
|
|
61
61
|
## Review and closure
|
|
62
62
|
|
|
63
63
|
1. Run ticket verification.
|
|
64
|
-
2. Run the close gate: `.scripts/
|
|
64
|
+
2. Run the close gate: `.scripts/sentinel/bin/issue-close-gate.sh <ISSUE>`.
|
|
65
65
|
3. Run the independent adversarial review — an adversarial microscope, with the
|
|
66
66
|
reviewer agent NOT the implementer (`autonomous-delegated-review.md`). On a
|
|
67
67
|
clean adversarial verdict the loop autonomously treats the ticket as done
|
|
@@ -6,9 +6,10 @@ A cheap systemd heartbeat already decided this full pass is needed.
|
|
|
6
6
|
Working repo: the git root containing this role at `agents/hermes/{{ role }}/`.
|
|
7
7
|
Ticket provider: **{{ ticket_provider }}** (reached only through the adapter — see below).
|
|
8
8
|
|
|
9
|
-
You are the **{{ target_repo }}
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
You are the **{{ target_repo }} PM**, running your continuous board-reconciliation
|
|
10
|
+
pass. Act autonomously, but stay inside the project contracts. Read
|
|
11
|
+
`agents/hermes/{{ role }}/SOUL.md` and the engine docs under
|
|
12
|
+
`.scripts/sentinel/docs/` before acting:
|
|
12
13
|
`continuous-ticket-orchestration.md`, `autonomous-delegated-review.md`,
|
|
13
14
|
`bloodbank-events.md`.
|
|
14
15
|
|
|
@@ -46,7 +47,7 @@ on Linear, Plane, or Trello.
|
|
|
46
47
|
independent reviewer and have it run, as a rigorous adversarial review:
|
|
47
48
|
|
|
48
49
|
```bash
|
|
49
|
-
.scripts/
|
|
50
|
+
.scripts/sentinel/bin/issue-autonomous-review.sh <ISSUE> <REPORT>
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
(NO `--close`.) This is an adversarial microscope: it holds the work against
|