@delorenj/pjangler 1.2.4 → 1.2.8
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/dist/index.js +797 -737
- package/dist/mcp-server.js +753 -683
- package/package.json +1 -1
- package/templates/commonproject/AGENTS.md +3 -3
- package/templates/commonproject/README.md +12 -11
- package/templates/commonproject/copier.yml +58 -10
- package/templates/commonproject/mise.toml +3 -3
- package/templates/commonproject/template/.agents/hooks/sync.py +12 -1
- package/templates/commonproject/template/.agents/local.example.json +3 -1
- package/templates/commonproject/template/.project.json.jinja +13 -6
- package/templates/commonproject/template/mise.toml.jinja +8 -4
- package/templates/hermes-agent/README.md +8 -8
- package/templates/hermes-agent/copier.yml +3 -4
- package/templates/hermes-agent/docs/architecture.md +4 -4
- package/templates/hermes-agent/docs/operations.md +2 -2
- package/templates/hermes-agent/docs/sentinel/README.md +6 -8
- package/templates/hermes-agent/docs/sentinel/architecture.md +2 -1
- package/templates/hermes-agent/docs/sentinel/development.md +13 -12
- package/templates/hermes-agent/docs/sentinel/providers.md +12 -32
- package/templates/hermes-agent/install-local.sh +6 -13
- package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +14 -37
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +2 -0
- package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +6 -2
- package/templates/hermes-agent/template/.scripts/sentinel/docs/autonomous-delegated-review.md +2 -2
- package/templates/hermes-agent/template/.scripts/sentinel/docs/continuous-ticket-orchestration.md +1 -1
- package/templates/hermes-agent/template/.scripts/sentinel.prompt.md.jinja +6 -1
- package/templates/hermes-agent/template/SOUL.md.jinja +9 -3
- package/templates/hermes-agent/template/role.yaml.jinja +3 -6
- package/templates/hermes-agent/template/.scripts/40-plane.sh +0 -51
- package/templates/hermes-agent/template/.scripts/providers/linear.sh +0 -176
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Provider adapters
|
|
2
2
|
|
|
3
3
|
This guide is the reference for the ticket-provider abstraction: the contract
|
|
4
|
-
every adapter implements, the
|
|
4
|
+
every adapter implements, the two adapters that ship today, and a
|
|
5
5
|
step-by-step process for adding a new provider or verifying an existing one
|
|
6
6
|
against a live board. Read [Architecture](architecture.md) first for how the
|
|
7
7
|
adapter fits into the engine.
|
|
@@ -43,12 +43,8 @@ maps each to its back end's concrete state.
|
|
|
43
43
|
|
|
44
44
|
## The adapters that ship today
|
|
45
45
|
|
|
46
|
-
The repository includes
|
|
46
|
+
The repository includes two supported adapters with different verification status.
|
|
47
47
|
|
|
48
|
-
- **Linear** (`providers/linear.sh`) is the reference implementation. It uses
|
|
49
|
-
the Linear GraphQL API. It's verified live against a real board. Linear's
|
|
50
|
-
`issue(id:)` field accepts both the UUID and the human identifier (for
|
|
51
|
-
example, `DEL-42`), so the engine can pass identifiers through.
|
|
52
48
|
- **Plane** (`providers/plane.sh`) uses the Plane REST API with `X-API-Key`
|
|
53
49
|
authentication. A Plane project maps to the board, a cycle maps to the
|
|
54
50
|
milestone, and `state.group` maps to the state type. It's verified live. Note
|
|
@@ -64,21 +60,6 @@ The repository includes three adapters with different verification status.
|
|
|
64
60
|
|
|
65
61
|
Set credentials in the environment and the board binding in `role.yaml`.
|
|
66
62
|
|
|
67
|
-
<details>
|
|
68
|
-
<summary>Linear</summary>
|
|
69
|
-
|
|
70
|
-
- Credentials: `LINEAR_API_KEY`.
|
|
71
|
-
- Binding:
|
|
72
|
-
|
|
73
|
-
```yaml
|
|
74
|
-
ticket_provider:
|
|
75
|
-
name: linear
|
|
76
|
-
team: DEL # Linear team key
|
|
77
|
-
project: "" # optional, scopes milestone and issue queries
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
</details>
|
|
81
|
-
|
|
82
63
|
<details>
|
|
83
64
|
<summary>Plane</summary>
|
|
84
65
|
|
|
@@ -114,8 +95,9 @@ Set credentials in the environment and the board binding in `role.yaml`.
|
|
|
114
95
|
|
|
115
96
|
To add a fourth provider, for example GitHub Issues or Jira, follow these steps.
|
|
116
97
|
|
|
117
|
-
1. Create `template/.scripts/providers/<name>.sh`. Start from `
|
|
118
|
-
|
|
98
|
+
1. Create `template/.scripts/providers/<name>.sh`. Start from `plane.sh` if it
|
|
99
|
+
uses REST with a single API key, or `trello.sh` if it has a board/list/card
|
|
100
|
+
model.
|
|
119
101
|
2. Implement every operation in [the contract](#the-contract). Return the exact
|
|
120
102
|
JSON shapes shown in the table. Map your back end's states to the five
|
|
121
103
|
normalized states.
|
|
@@ -127,8 +109,7 @@ To add a fourth provider, for example GitHub Issues or Jira, follow these steps.
|
|
|
127
109
|
5. Extend `template/role.yaml.jinja` with a provider-specific binding block
|
|
128
110
|
under the `{% if ticket_provider == '<name>' %}` branch.
|
|
129
111
|
6. Teach `42-ticket-provider.sh` how to resolve or create the board for your
|
|
130
|
-
provider, if it differs from the existing `
|
|
131
|
-
cases.
|
|
112
|
+
provider, if it differs from the existing `plane` and `trello` cases.
|
|
132
113
|
7. Validate and verify with the steps below.
|
|
133
114
|
|
|
134
115
|
<!-- prettier-ignore -->
|
|
@@ -158,7 +139,7 @@ Before you touch a live board, confirm the adapter parses and fails cleanly.
|
|
|
158
139
|
exit, with no Python traceback:
|
|
159
140
|
|
|
160
141
|
```bash
|
|
161
|
-
( unset
|
|
142
|
+
( unset PLANE_API_KEY; sh template/.scripts/providers/plane.sh resolve )
|
|
162
143
|
```
|
|
163
144
|
|
|
164
145
|
## Verifying an adapter against a live board
|
|
@@ -169,15 +150,15 @@ never closes or modifies a ticket, so it's safe to run.
|
|
|
169
150
|
|
|
170
151
|
```bash
|
|
171
152
|
# 1. Load the credential into the environment only (never print it).
|
|
172
|
-
export
|
|
173
|
-
~/.hermes/<agent>.env | head -1 | sed -E 's/^(export )?
|
|
153
|
+
export PLANE_API_KEY="$(grep -E '^(export )?PLANE_API_KEY=' \
|
|
154
|
+
~/.hermes/<agent>.env | head -1 | sed -E 's/^(export )?PLANE_API_KEY=//; s/^"//; s/"$//')"
|
|
174
155
|
|
|
175
156
|
# 2. Stage a role directory bound to the real board.
|
|
176
157
|
T=$(mktemp -d); RD="$T/agents/hermes/pm"
|
|
177
158
|
mkdir -p "$RD/.scripts/lib" "$RD/.scripts/providers"
|
|
178
159
|
cp template/.scripts/lib/ticket-provider.sh "$RD/.scripts/lib/"
|
|
179
|
-
cp template/.scripts/providers/
|
|
180
|
-
printf 'repo: demo\nrole: pm\nticket_provider:\n name:
|
|
160
|
+
cp template/.scripts/providers/plane.sh "$RD/.scripts/providers/"
|
|
161
|
+
printf 'repo: demo\nrole: pm\nticket_provider:\n name: plane\n workspace: 33god\n project: <project-uuid>\n' \
|
|
181
162
|
> "$RD/role.yaml"
|
|
182
163
|
|
|
183
164
|
# 3. Run the read operations.
|
|
@@ -199,5 +180,4 @@ verification.
|
|
|
199
180
|
## Read next
|
|
200
181
|
|
|
201
182
|
- [Development guide](development.md): the full edit, validate, and propagate
|
|
202
|
-
workflow, plus the open roadmap, which includes live-verifying
|
|
203
|
-
Trello.
|
|
183
|
+
workflow, plus the open roadmap, which includes live-verifying Trello.
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
#
|
|
16
16
|
# Environment overrides (skip the prompts):
|
|
17
17
|
# HAT_REPO=<name> project/repo name (default: basename of CWD)
|
|
18
|
-
# HAT_PROVIDER=
|
|
18
|
+
# HAT_PROVIDER=plane|trello (default: plane)
|
|
19
19
|
# HAT_ROLES="pm" roles to install (default: pm)
|
|
20
20
|
# HAT_DRY_RUN=1 print actions, change nothing
|
|
21
|
-
# Provider creds:
|
|
21
|
+
# Provider creds: PLANE_API_KEY+PLANE_BASE | TRELLO_KEY+TRELLO_TOKEN
|
|
22
22
|
set -eu
|
|
23
23
|
|
|
24
24
|
say() { printf '\033[36m%s\033[0m\n' "$*"; }
|
|
@@ -100,7 +100,7 @@ TOML
|
|
|
100
100
|
fi
|
|
101
101
|
|
|
102
102
|
# --- 4. Provider credentials + board binding ---------------------------------
|
|
103
|
-
ask HAT_PROVIDER "Ticket provider (
|
|
103
|
+
ask HAT_PROVIDER "Ticket provider (plane|trello)" "plane"
|
|
104
104
|
PROVIDER="$HAT_PROVIDER"
|
|
105
105
|
ask HAT_REPO "Project/repo name" "$(basename "$PROJECT_DIR" | tr '[:upper:]' '[:lower:]')"
|
|
106
106
|
REPO="$HAT_REPO"
|
|
@@ -109,7 +109,7 @@ PM_ENV="$HOME/.hermes/${REPO}-pm.env"
|
|
|
109
109
|
# These get written into the pm role.yaml binding after render.
|
|
110
110
|
# Pre-seed from optional env knobs so the install can run non-interactively.
|
|
111
111
|
TP_WORKSPACE="${HAT_PLANE_WORKSPACE:-}"; TP_PROJECT="${HAT_PLANE_PROJECT:-}"
|
|
112
|
-
|
|
112
|
+
TP_BOARD="${HAT_TRELLO_BOARD:-}"
|
|
113
113
|
case "$PROVIDER" in
|
|
114
114
|
plane)
|
|
115
115
|
ask PLANE_BASE "Plane base URL" "https://app.plane.so"
|
|
@@ -136,12 +136,6 @@ PY
|
|
|
136
136
|
CRED_LINES="PLANE_API_KEY=$PLANE_API_KEY
|
|
137
137
|
PLANE_BASE=$PLANE_BASE"
|
|
138
138
|
;;
|
|
139
|
-
linear)
|
|
140
|
-
: "${LINEAR_API_KEY:?Set LINEAR_API_KEY in your environment, then re-run}"
|
|
141
|
-
ask TP_TEAM "Linear team key (for example DEL)" ""
|
|
142
|
-
[ -n "$TP_TEAM" ] || die "team key is required"
|
|
143
|
-
CRED_LINES="LINEAR_API_KEY=$LINEAR_API_KEY"
|
|
144
|
-
;;
|
|
145
139
|
trello)
|
|
146
140
|
: "${TRELLO_KEY:?Set TRELLO_KEY in your environment, then re-run}"
|
|
147
141
|
: "${TRELLO_TOKEN:?Set TRELLO_TOKEN in your environment, then re-run}"
|
|
@@ -170,7 +164,7 @@ for ROLE in $ROLES; do
|
|
|
170
164
|
DEST="$PROJECT_DIR/agents/hermes/$ROLE"
|
|
171
165
|
# Scrub provider creds from copier's environment so 42-ticket-provider skips
|
|
172
166
|
# board CREATION; we bind to the existing board in step 6 instead.
|
|
173
|
-
run "env -u PLANE_API_KEY -u PLANE_33GOD_API_KEY
|
|
167
|
+
run "env -u PLANE_API_KEY -u PLANE_33GOD_API_KEY \
|
|
174
168
|
-u TRELLO_KEY -u TRELLO_TOKEN \
|
|
175
169
|
copier copy '$TEMPLATE_SRC' '$DEST' --trust --defaults --overwrite \
|
|
176
170
|
--data target_repo='$REPO' --data role='$ROLE' --data ticket_provider='$PROVIDER'"
|
|
@@ -180,7 +174,7 @@ done
|
|
|
180
174
|
PM_ROLE="$PROJECT_DIR/agents/hermes/pm/role.yaml"
|
|
181
175
|
if [ -f "$PM_ROLE" ] && [ "${HAT_DRY_RUN:-0}" != "1" ]; then
|
|
182
176
|
say "6. binding pm to your $PROVIDER board"
|
|
183
|
-
TP_WORKSPACE="$TP_WORKSPACE" TP_PROJECT="$TP_PROJECT"
|
|
177
|
+
TP_WORKSPACE="$TP_WORKSPACE" TP_PROJECT="$TP_PROJECT" \
|
|
184
178
|
TP_BOARD="$TP_BOARD" PROVIDER="$PROVIDER" python3 - "$PM_ROLE" <<'PY'
|
|
185
179
|
import os, re, sys, pathlib
|
|
186
180
|
p = pathlib.Path(sys.argv[1]); t = p.read_text()
|
|
@@ -190,7 +184,6 @@ def setleaf(text, key, val):
|
|
|
190
184
|
return new if n else text
|
|
191
185
|
t = setleaf(t, "workspace", os.environ.get("TP_WORKSPACE",""))
|
|
192
186
|
t = setleaf(t, "project", os.environ.get("TP_PROJECT",""))
|
|
193
|
-
t = setleaf(t, "team", os.environ.get("TP_TEAM",""))
|
|
194
187
|
t = setleaf(t, "board", os.environ.get("TP_BOARD",""))
|
|
195
188
|
p.write_text(t)
|
|
196
189
|
print(" bound:", {k:v for k,v in os.environ.items() if k.startswith("TP_") and v})
|
|
@@ -46,13 +46,13 @@ PY
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
# pj_write — merge board binding (optional) + this agent into .project.json.
|
|
49
|
-
# args: set_provider(0|1) provider board_id board_url workspace identifier
|
|
49
|
+
# args: set_provider(0|1) provider board_id board_url workspace identifier
|
|
50
50
|
pj_write() {
|
|
51
51
|
REPO="$REPO" REPO_ROOT="$REPO_ROOT" AGENT_ID="$AGENT_ID" ROLE="$ROLE" \
|
|
52
52
|
ROLE_DIR_REL="$ROLE_DIR_REL" PROJECT_DESC="${PROJECT_DESC:-}" \
|
|
53
53
|
python3 - "$PROJECT_JSON" "$@" <<'PY'
|
|
54
54
|
import sys, os, json, pathlib
|
|
55
|
-
(path, set_provider, provider, board_id, board_url, workspace, identifier
|
|
55
|
+
(path, set_provider, provider, board_id, board_url, workspace, identifier) = sys.argv[1:8]
|
|
56
56
|
p = pathlib.Path(path)
|
|
57
57
|
try:
|
|
58
58
|
d = json.loads(p.read_text())
|
|
@@ -72,7 +72,6 @@ if set_provider == "1":
|
|
|
72
72
|
if identifier: tp["identifier"] = identifier
|
|
73
73
|
if board_id: tp["board_id"] = board_id
|
|
74
74
|
if board_url: tp["board_url"] = board_url
|
|
75
|
-
if team: tp["team"] = team
|
|
76
75
|
ag = d.setdefault("agents", {})
|
|
77
76
|
ag[os.environ["AGENT_ID"]] = {"role": os.environ["ROLE"], "role_dir": os.environ["ROLE_DIR_REL"]}
|
|
78
77
|
p.write_text(json.dumps(d, indent=2) + "\n")
|
|
@@ -82,8 +81,8 @@ PY
|
|
|
82
81
|
|
|
83
82
|
# Mirror the binding into role.yaml so legacy consumers keep working.
|
|
84
83
|
mirror_to_role_yaml() {
|
|
85
|
-
# mirror_to_role_yaml <provider> <board_id> <board_url> <workspace> <identifier>
|
|
86
|
-
local provider="$1" bid="$2" burl="$3" ws="$4" ident="$5"
|
|
84
|
+
# mirror_to_role_yaml <provider> <board_id> <board_url> <workspace> <identifier>
|
|
85
|
+
local provider="$1" bid="$2" burl="$3" ws="$4" ident="$5"
|
|
87
86
|
yaml_set ticket_provider.name "$provider" 2>/dev/null || true
|
|
88
87
|
[ -n "$bid" ] && yaml_set ticket_provider.board_id "$bid" 2>/dev/null || true
|
|
89
88
|
[ -n "$burl" ] && yaml_set ticket_provider.board_url "$burl" 2>/dev/null || true
|
|
@@ -98,8 +97,8 @@ mirror_to_role_yaml() {
|
|
|
98
97
|
trello)
|
|
99
98
|
[ -n "$bid" ] && yaml_set ticket_provider.board "$bid" 2>/dev/null || true
|
|
100
99
|
;;
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
*)
|
|
101
|
+
die "unsupported ticket provider: $provider (expected plane|trello)"
|
|
103
102
|
;;
|
|
104
103
|
esac
|
|
105
104
|
}
|
|
@@ -111,7 +110,6 @@ SOT_BOARD_ID="$(pj ticket_provider.board_id)"
|
|
|
111
110
|
SOT_URL="$(pj ticket_provider.board_url)"
|
|
112
111
|
SOT_WS="$(pj ticket_provider.workspace)"
|
|
113
112
|
SOT_IDENT="$(pj ticket_provider.identifier)"
|
|
114
|
-
SOT_TEAM="$(pj ticket_provider.team)"
|
|
115
113
|
|
|
116
114
|
# role.yaml provider comes from copier --data (the operator's pjangler choice).
|
|
117
115
|
ROLE_PROVIDER="$(yaml_get ticket_provider.name)"
|
|
@@ -122,9 +120,10 @@ if [ -n "$SOT_BOARD_ID" ]; then
|
|
|
122
120
|
if [ -n "$ROLE_PROVIDER" ] && [ "$ROLE_PROVIDER" != "$PROVIDER" ]; then
|
|
123
121
|
warn "[42] requested provider '$ROLE_PROVIDER' but repo board is '$PROVIDER' (.project.json wins); binding to existing board"
|
|
124
122
|
fi
|
|
123
|
+
case "$PROVIDER" in plane|trello) ;; *) die "unsupported ticket provider in .project.json: $PROVIDER (expected plane|trello)" ;; esac
|
|
125
124
|
log "[42] binding $AGENT_ID to existing repo board (provider=$PROVIDER, id=$SOT_BOARD_ID)"
|
|
126
|
-
mirror_to_role_yaml "$PROVIDER" "$SOT_BOARD_ID" "$SOT_URL" "$SOT_WS" "$SOT_IDENT"
|
|
127
|
-
pj_write 0 "$PROVIDER" "" "" "" ""
|
|
125
|
+
mirror_to_role_yaml "$PROVIDER" "$SOT_BOARD_ID" "$SOT_URL" "$SOT_WS" "$SOT_IDENT"
|
|
126
|
+
pj_write 0 "$PROVIDER" "" "" "" "" # register agent only; board already recorded
|
|
128
127
|
mark_done 42-ticket-provider
|
|
129
128
|
exit 0
|
|
130
129
|
fi
|
|
@@ -143,45 +142,23 @@ NAME="$(printf '%s' "$REPO" | tr '_-' ' ' | python3 -c 'import sys; print(" ".j
|
|
|
143
142
|
DESC="Ticket board for $REPO"
|
|
144
143
|
|
|
145
144
|
case "$PROVIDER" in
|
|
146
|
-
linear)
|
|
147
|
-
if [[ -z "${LINEAR_API_KEY:-}" ]]; then
|
|
148
|
-
warn "[42] LINEAR_API_KEY not set; set role.yaml/.project.json ticket_provider.team and re-run ./.scripts/42-ticket-provider.sh"
|
|
149
|
-
pj_write 1 linear "" "" "" "$IDENT" "$SOT_TEAM"
|
|
150
|
-
mark_done 42-ticket-provider; exit 0
|
|
151
|
-
fi
|
|
152
|
-
OUT="$(tp resolve 2>/dev/null || true)"
|
|
153
|
-
BID="$(printf '%s' "$OUT" | python3 -c 'import sys,json
|
|
154
|
-
try: print(json.load(sys.stdin).get("board_id",""))
|
|
155
|
-
except Exception: print("")')"
|
|
156
|
-
BURL="$(printf '%s' "$OUT" | python3 -c 'import sys,json
|
|
157
|
-
try: print(json.load(sys.stdin).get("board_url",""))
|
|
158
|
-
except Exception: print("")')"
|
|
159
|
-
if [ -n "$BID" ]; then
|
|
160
|
-
mirror_to_role_yaml linear "$BID" "$BURL" "" "$IDENT" "$SOT_TEAM"
|
|
161
|
-
pj_write 1 linear "$BID" "$BURL" "" "$IDENT" "$SOT_TEAM"
|
|
162
|
-
else
|
|
163
|
-
warn "[42] linear resolve returned no board; set ticket_provider.team and re-run"
|
|
164
|
-
pj_write 1 linear "" "" "" "$IDENT" "$SOT_TEAM"
|
|
165
|
-
fi
|
|
166
|
-
;;
|
|
167
|
-
|
|
168
145
|
plane|trello)
|
|
169
146
|
KEYVAR=PLANE_API_KEY; [ "$PROVIDER" = trello ] && KEYVAR=TRELLO_KEY
|
|
170
147
|
if [[ -z "${!KEYVAR:-}" ]]; then
|
|
171
148
|
warn "[42] $KEYVAR not set; skipping board creation. Set creds and re-run ./.scripts/42-ticket-provider.sh"
|
|
172
|
-
pj_write 1 "$PROVIDER" "" "" "${SOT_WS:-$PLANE_WORKSPACE}" "$IDENT"
|
|
149
|
+
pj_write 1 "$PROVIDER" "" "" "${SOT_WS:-${PLANE_WORKSPACE:-}}" "$IDENT"
|
|
173
150
|
mark_done 42-ticket-provider; exit 0
|
|
174
151
|
fi
|
|
175
152
|
OUT="$(tp create_board "$NAME" "$IDENT" "$DESC")" || die "create_board failed for $PROVIDER"
|
|
176
153
|
BID="$(printf '%s' "$OUT" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("board_id",""))')"
|
|
177
154
|
BURL="$(printf '%s' "$OUT" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("board_url",""))')"
|
|
178
|
-
WS="${SOT_WS:-$PLANE_WORKSPACE}"
|
|
155
|
+
WS="${SOT_WS:-${PLANE_WORKSPACE:-}}"
|
|
179
156
|
[ "$PROVIDER" = trello ] && WS=""
|
|
180
|
-
mirror_to_role_yaml "$PROVIDER" "$BID" "$BURL" "$WS" "$IDENT"
|
|
181
|
-
pj_write 1 "$PROVIDER" "$BID" "$BURL" "$WS" "$IDENT"
|
|
157
|
+
mirror_to_role_yaml "$PROVIDER" "$BID" "$BURL" "$WS" "$IDENT"
|
|
158
|
+
pj_write 1 "$PROVIDER" "$BID" "$BURL" "$WS" "$IDENT"
|
|
182
159
|
;;
|
|
183
160
|
|
|
184
|
-
*) die "unknown ticket provider: $PROVIDER (expected
|
|
161
|
+
*) die "unknown ticket provider: $PROVIDER (expected plane|trello)" ;;
|
|
185
162
|
esac
|
|
186
163
|
|
|
187
164
|
mark_done 42-ticket-provider
|
|
@@ -32,6 +32,8 @@ Type=simple
|
|
|
32
32
|
Environment=HERMES_HOME=$RUNTIME
|
|
33
33
|
Environment=HERMES_OAUTH_FILE=$HERMES_OAUTH_FILE
|
|
34
34
|
Environment=CODEX_HOME=$CODEX_HOME
|
|
35
|
+
Environment=HERMES_KANBAN_DISPATCH_IN_GATEWAY=0
|
|
36
|
+
Environment=HERMES_KANBAN_SLASH_ENABLED=0
|
|
35
37
|
EnvironmentFile=-$RUNTIME/.env
|
|
36
38
|
ExecStart=$HERMES_BIN gateway run --replace
|
|
37
39
|
Restart=on-failure
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# shellcheck shell=bash
|
|
2
2
|
# Ticket-provider adapter dispatcher — the single seam between the heartbeat
|
|
3
|
-
# sentinel engine and a concrete ticket system (
|
|
3
|
+
# sentinel engine and a concrete ticket system (Plane | Trello).
|
|
4
4
|
#
|
|
5
5
|
# The engine NEVER calls a provider directly. It calls `tp <op> [args...]`,
|
|
6
6
|
# which dispatches to providers/<provider>.sh. Swapping providers is a one-line
|
|
@@ -64,7 +64,7 @@ PY
|
|
|
64
64
|
)"
|
|
65
65
|
[ -n "$name" ] && { printf '%s\n' "$name"; return 0; }
|
|
66
66
|
fi
|
|
67
|
-
printf '
|
|
67
|
+
printf 'plane\n'
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
# Directory holding provider implementations (sibling of this lib).
|
|
@@ -81,6 +81,10 @@ tp() {
|
|
|
81
81
|
|
|
82
82
|
local name impl
|
|
83
83
|
name="$(tp_provider_name)"
|
|
84
|
+
case "$name" in
|
|
85
|
+
plane|trello) ;;
|
|
86
|
+
*) echo "tp: unsupported ticket provider '$name' (expected plane|trello)" >&2; return 2 ;;
|
|
87
|
+
esac
|
|
84
88
|
impl="$(tp_providers_dir)/${name}.sh"
|
|
85
89
|
|
|
86
90
|
if [ ! -f "$impl" ]; then
|
package/templates/hermes-agent/template/.scripts/sentinel/docs/autonomous-delegated-review.md
CHANGED
|
@@ -13,7 +13,7 @@ actively tries to break the work, surface unmet acceptance criteria, hidden
|
|
|
13
13
|
regressions, and drift. On a clean adversarial verdict the loop treats the
|
|
14
14
|
ticket as done and moves on — through the ticket-provider adapter
|
|
15
15
|
(`tp transition <id> <state>`) — and emits a BloodBank decision event carrying
|
|
16
|
-
the full report. This works identically on
|
|
16
|
+
the full report. This works identically on Plane or Trello.
|
|
17
17
|
|
|
18
18
|
The operator's verification is **deferred** to end-of-product QA over the review
|
|
19
19
|
lane, backed by a queryable decision trail. A downstream regression rollback is
|
|
@@ -144,7 +144,7 @@ Write `<ISSUE>.review.md`; the script validates it:
|
|
|
144
144
|
```markdown
|
|
145
145
|
# Autonomous Review Report: <ISSUE>
|
|
146
146
|
## Issue
|
|
147
|
-
-
|
|
147
|
+
- Provider issue: <ISSUE>
|
|
148
148
|
- Review lane reason:
|
|
149
149
|
## Reviewer
|
|
150
150
|
- Reviewer agent: <independent-agent-id>
|
package/templates/hermes-agent/template/.scripts/sentinel/docs/continuous-ticket-orchestration.md
CHANGED
|
@@ -19,7 +19,7 @@ approve merges.
|
|
|
19
19
|
All board access goes through the adapter (`tp`, from
|
|
20
20
|
`.scripts/lib/ticket-provider.sh`) and reasons in normalized states:
|
|
21
21
|
`backlog | unstarted | started | in_review | completed`. Never call the provider
|
|
22
|
-
directly — the engine is identical across
|
|
22
|
+
directly — the engine is identical across Plane and Trello.
|
|
23
23
|
|
|
24
24
|
## Work-state feed
|
|
25
25
|
|
|
@@ -6,6 +6,11 @@ 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
|
+
The configured ticket provider is the authoritative repo ticket board/SOT for
|
|
10
|
+
this PM. Hermes local Kanban must NOT be an active durable task lifecycle
|
|
11
|
+
surface. Local Kanban may only run as an explicitly configured ephemeral child
|
|
12
|
+
execution queue and must never close/move authoritative repo tickets.
|
|
13
|
+
|
|
9
14
|
You are the **{{ target_repo }} PM**, running your continuous board-reconciliation
|
|
10
15
|
pass. Act autonomously, but stay inside the project contracts. Read
|
|
11
16
|
`agents/hermes/{{ role }}/SOUL.md` and the engine docs under
|
|
@@ -27,7 +32,7 @@ tp transition <id> <normalized-state> # backlog|unstarted|started|in_review|com
|
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
Reason in **normalized states**, not provider terms. This pass works identically
|
|
30
|
-
on
|
|
35
|
+
on Plane or Trello.
|
|
31
36
|
|
|
32
37
|
## Pass
|
|
33
38
|
|
|
@@ -55,12 +55,18 @@ read it before publishing a type you haven't published before.
|
|
|
55
55
|
{% if role == "pm" -%}
|
|
56
56
|
You are the **project manager**. You triage incoming requests from Telegram /
|
|
57
57
|
Bloodbank command lanes, decompose them into discrete tasks on the
|
|
58
|
-
|
|
58
|
+
configured ticket board, and route work to other agents in the fleet (e.g. the dev role
|
|
59
59
|
on `bloodbank.cmd.v1.agent.{{ target_repo }}-dev.task.assign`). You do not
|
|
60
60
|
write application code. You do not approve merges.
|
|
61
61
|
|
|
62
|
+
The configured ticket provider (`{{ ticket_provider }}`) is the authoritative
|
|
63
|
+
repo ticket board/SOT for this PM. Hermes local Kanban must NOT be an active
|
|
64
|
+
durable task lifecycle surface. The kanban toolset is removed from the PM's cli
|
|
65
|
+
surface. Local Kanban may only run as an explicitly configured ephemeral child
|
|
66
|
+
execution queue and must never close/move authoritative repo tickets.
|
|
67
|
+
|
|
62
68
|
Default execution workflow for implementation delivery: use
|
|
63
|
-
`subagent-driven-development` in
|
|
69
|
+
`subagent-driven-development` in provider-ticket-orchestrated implementation mode
|
|
64
70
|
(WIP=1, spec review gate, quality review gate).
|
|
65
71
|
|
|
66
72
|
Decision events you commonly emit:
|
|
@@ -106,7 +112,7 @@ declared` event so the fleet knows what to route to you.
|
|
|
106
112
|
- **Hostnames**: Use `*.delo.sh` for external/cross-machine access (resolved
|
|
107
113
|
via Cloudflare Tunnel), `localhost` for same-host, Docker network service
|
|
108
114
|
names for container-to-container, Tailscale for private machine-to-machine.
|
|
109
|
-
- **
|
|
115
|
+
- **Ticketing**: Always include the configured provider's ticket reference in commit messages.
|
|
110
116
|
|
|
111
117
|
## Memory hygiene
|
|
112
118
|
|
|
@@ -21,17 +21,14 @@ profile: {{ agent_id }}
|
|
|
21
21
|
telegram:
|
|
22
22
|
bot_username: "{{ bot_handle }}"
|
|
23
23
|
|
|
24
|
-
# Ticket-provider binding (
|
|
24
|
+
# Ticket-provider binding (plane | trello).
|
|
25
25
|
# The heartbeat reconciliation pass talks ONLY to this via .scripts/lib/ticket-provider.sh;
|
|
26
26
|
# swapping providers is a one-line change here. Filled in by 42-ticket-provider.sh.
|
|
27
27
|
ticket_provider:
|
|
28
28
|
name: {{ ticket_provider }}
|
|
29
29
|
board_id: ""
|
|
30
30
|
board_url: ""
|
|
31
|
-
{%- if ticket_provider == '
|
|
32
|
-
team: "" # Linear team key, e.g. DEL (set before first sentinel run)
|
|
33
|
-
project: "" # optional Linear project name to scope milestones/issues
|
|
34
|
-
{%- elif ticket_provider == 'plane' %}
|
|
31
|
+
{%- if ticket_provider == 'plane' %}
|
|
35
32
|
workspace: "{{ plane_workspace }}"
|
|
36
33
|
project: "" # Plane project uuid (set by 42-ticket-provider.sh)
|
|
37
34
|
{%- elif ticket_provider == 'trello' %}
|
|
@@ -47,7 +44,7 @@ reconcile:
|
|
|
47
44
|
grace_hours: 0 # 0 = no human-approval wait (adversarially-reviewed review-lane = done); set >0 to require a wait
|
|
48
45
|
auto_review: true # autonomous adversarial review (act, do not wait) on/off
|
|
49
46
|
|
|
50
|
-
# Plane
|
|
47
|
+
# Plane metadata — retained for fleet-registry back-compat when provider=plane.
|
|
51
48
|
plane:
|
|
52
49
|
# Empty -> resolved from ~/.config/hermes-agent-template/config.toml [plane].workspace
|
|
53
50
|
workspace: "{{ plane_workspace }}"
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Create a Plane project in the configured workspace (one project per agent).
|
|
3
|
-
# Workspace + base URL come from role.yaml / config.toml (see _lib.sh).
|
|
4
|
-
# shellcheck source=_lib.sh
|
|
5
|
-
source "$(dirname "$0")/_lib.sh"
|
|
6
|
-
load_role_env
|
|
7
|
-
|
|
8
|
-
already_done 40-plane && { log "[40] plane already set up — skipping"; exit 0; }
|
|
9
|
-
[[ "${SKIP_PLANE:-0}" == "1" ]] && { log "[40] plane — SKIPPED"; mark_done 40-plane; exit 0; }
|
|
10
|
-
|
|
11
|
-
[[ -n "$PLANE_API_KEY" ]] || { warn "[40] PLANE_API_KEY not set; skipping. set PLANE_33GOD_API_KEY and re-run ./.scripts/40-plane.sh"; exit 0; }
|
|
12
|
-
|
|
13
|
-
# Build the 5-char identifier: <repo[0..2]><role[0..1]> uppercased
|
|
14
|
-
RAW=$(printf '%s%s' "${REPO:0:3}" "${ROLE:0:2}" | tr -cd '[:alnum:]' | tr '[:lower:]' '[:upper:]')
|
|
15
|
-
while (( ${#RAW} < 3 )); do RAW="${RAW}X"; done
|
|
16
|
-
IDENT="${RAW:0:5}"
|
|
17
|
-
|
|
18
|
-
# Project name = display_name (already smart-cased: "Bloodbank PM", "Hermes-Agent Dev").
|
|
19
|
-
# Plane forbids hyphens in names — substitute spaces.
|
|
20
|
-
NAME="${DISPLAY_NAME//-/ }"
|
|
21
|
-
|
|
22
|
-
log "[40] creating plane project '$NAME' [$IDENT]"
|
|
23
|
-
|
|
24
|
-
# Check for existing project with this identifier
|
|
25
|
-
EXISTING=$(curl -sS "$PLANE_BASE/api/v1/workspaces/$PLANE_WORKSPACE/projects/?per_page=200" \
|
|
26
|
-
-H "X-API-Key: $PLANE_API_KEY" \
|
|
27
|
-
| python3 -c "
|
|
28
|
-
import sys, json
|
|
29
|
-
d = json.load(sys.stdin)
|
|
30
|
-
ident = '$IDENT'
|
|
31
|
-
for p in d.get('results', []):
|
|
32
|
-
if (p.get('identifier') or '').upper() == ident:
|
|
33
|
-
print(p['id']); break")
|
|
34
|
-
|
|
35
|
-
if [[ -n "$EXISTING" ]]; then
|
|
36
|
-
log " plane project $IDENT already exists (id=$EXISTING) — reusing"
|
|
37
|
-
PROJECT_ID="$EXISTING"
|
|
38
|
-
else
|
|
39
|
-
RESP=$(curl -sS -X POST "$PLANE_BASE/api/v1/workspaces/$PLANE_WORKSPACE/projects/" \
|
|
40
|
-
-H "X-API-Key: $PLANE_API_KEY" \
|
|
41
|
-
-H "Content-Type: application/json" \
|
|
42
|
-
-d "$(python3 -c "import json,sys; print(json.dumps({'name': sys.argv[1], 'identifier': sys.argv[2], 'description': sys.argv[3]}))" \
|
|
43
|
-
"$NAME" "$IDENT" "Hermes agent board for $AGENT_ID")")
|
|
44
|
-
PROJECT_ID=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id') or '')")
|
|
45
|
-
[[ -n "$PROJECT_ID" ]] || die "plane create failed: $RESP"
|
|
46
|
-
log " plane project created id=$PROJECT_ID"
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
yaml_set plane.identifier "$IDENT"
|
|
50
|
-
echo "$PROJECT_ID" > "$ROLE_DIR/.scripts/.plane-project-id"
|
|
51
|
-
mark_done 40-plane
|