@delorenj/pjangler 1.2.18 → 1.2.21
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 +884 -295
- package/dist/mcp-server.js +892 -303
- package/package.json +8 -2
- package/templates/commonproject/AGENTS.md +3 -3
- package/templates/commonproject/README.md +11 -12
- package/templates/commonproject/copier.yml +11 -31
- package/templates/commonproject/template/.agents/hooks/README.md +13 -26
- package/templates/commonproject/template/.agents/hooks/lib/local-config.sh +4 -14
- package/templates/commonproject/template/.agents/hooks/sync.py +5 -6
- package/templates/commonproject/template/.agents/local.example.json +2 -8
- package/templates/commonproject/template/.agents/skills.json +6 -0
- package/templates/commonproject/template/.project.json.jinja +9 -13
- package/templates/commonproject/template/mise.toml.jinja +9 -16
- package/templates/hermes-agent/README.md +9 -9
- package/templates/hermes-agent/config.example.toml +1 -66
- package/templates/hermes-agent/copier.yml +4 -3
- package/templates/hermes-agent/docs/architecture.md +9 -12
- package/templates/hermes-agent/docs/fleet-control-plane/README.md +1 -1
- package/templates/hermes-agent/docs/operations.md +6 -5
- package/templates/hermes-agent/docs/sentinel/README.md +9 -7
- package/templates/hermes-agent/docs/sentinel/architecture.md +1 -2
- package/templates/hermes-agent/docs/sentinel/development.md +12 -13
- package/templates/hermes-agent/docs/sentinel/providers.md +34 -15
- package/templates/hermes-agent/install-local.sh +15 -14
- package/templates/hermes-agent/runtime-scaffold/README.md +1 -1
- package/templates/hermes-agent/runtime-scaffold/bloodbank-consumer.py +46 -14
- package/templates/hermes-agent/runtime-scaffold/memories/MEMORY.md +2 -2
- package/templates/hermes-agent/scripts/fleet-sync.sh +1 -64
- package/templates/hermes-agent/template/.gitignore.jinja +0 -2
- package/templates/hermes-agent/template/.runtime-scaffold/README.md +1 -1
- package/templates/hermes-agent/template/.runtime-scaffold/bloodbank-consumer.py +43 -9
- package/templates/hermes-agent/template/.runtime-scaffold/memories/MEMORY.md +2 -2
- package/templates/hermes-agent/template/.scripts/01-config.sh +0 -1
- package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +0 -9
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +3 -22
- package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +0 -22
- package/templates/hermes-agent/template/.scripts/40-plane.sh +51 -0
- package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +59 -21
- package/templates/hermes-agent/template/.scripts/60-bloodbank.sh +2 -1
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +1 -10
- package/templates/hermes-agent/template/.scripts/_lib.sh +3 -61
- package/templates/hermes-agent/template/.scripts/config.example.toml +0 -5
- package/templates/hermes-agent/template/.scripts/heartbeat.sh +33 -66
- package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +5 -9
- package/templates/hermes-agent/template/.scripts/momo-wip-lock.py +137 -0
- package/templates/hermes-agent/template/.scripts/providers/linear.sh +176 -0
- package/templates/hermes-agent/template/.scripts/providers/plane.sh +9 -29
- 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 -33
- package/templates/hermes-agent/template/.scripts/sentinel.prompt.md.jinja +22 -27
- package/templates/hermes-agent/template/SOUL.md.jinja +49 -30
- package/templates/hermes-agent/template/role.yaml.jinja +35 -4
- package/templates/hermes-agent/tests/test_bloodbank_consumer_contract.py +138 -0
- package/templates/commonproject/template/.mise/scripts/link-project-skills-to-clis.sh +0 -110
- package/templates/commonproject/template/.mise/scripts/unlink-project-skills-from-clis.sh +0 -45
- package/templates/hermes-agent/docs/bloodbank-gateway.md +0 -57
- package/templates/hermes-agent/docs/fleet-control-plane/n8n-service-hub.md +0 -60
|
@@ -92,23 +92,20 @@ happen to share a token, the second one's startup fails fast. The N×M cost
|
|
|
92
92
|
(N BotFather sessions per fleet) is the price we accept for zero custom
|
|
93
93
|
routing code.
|
|
94
94
|
|
|
95
|
-
## One
|
|
95
|
+
## One Plane project per agent
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
A Plane "project" is the natural unit of work isolation. Mixing agents into a
|
|
98
|
+
shared project would conflate decisions and break filters. 1:1 also makes
|
|
99
|
+
archive-on-retire clean.
|
|
100
100
|
|
|
101
101
|
## Bloodbank wiring
|
|
102
102
|
|
|
103
103
|
Each consumer subscribes to two lanes:
|
|
104
|
-
- `bloodbank.evt.v1.repo
|
|
105
|
-
- `bloodbank.cmd.v1.agent
|
|
104
|
+
- `bloodbank.evt.v1.repo.>` — canonical repo-domain events, filtered by `data.repo`
|
|
105
|
+
- `bloodbank.cmd.v1.agent.>` — canonical agent-domain commands, filtered by `data.target_agent_id`
|
|
106
106
|
|
|
107
107
|
Each agent emits CloudEvents 1.0 envelopes with `actor.agent_id`,
|
|
108
108
|
`producer = hermes-agent:<id>`, `source = hermes://agent/<id>`. The naming
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
capture. The target architecture is a first-class Hermes Bloodbank gateway where
|
|
113
|
-
command events enter the same turn queue as Telegram/web messages; see
|
|
114
|
-
[Bloodbank Gateway](bloodbank-gateway.md).
|
|
109
|
+
contract is owned by Bloodbank (`~/code/33GOD/bloodbank/docs/event-naming.md`).
|
|
110
|
+
Repo and agent identifiers belong in envelope data, actor, or source fields,
|
|
111
|
+
never in type or subject tokens.
|
|
@@ -24,7 +24,6 @@ The Fleet remains a set of independently useful parts:
|
|
|
24
24
|
- [prd.md](./prd.md) - Functional and non-functional requirements.
|
|
25
25
|
- [architecture.md](./architecture.md) - Technical architecture and ADRs.
|
|
26
26
|
- [epics-and-stories.md](./epics-and-stories.md) - Implementation backlog with acceptance criteria.
|
|
27
|
-
- [n8n-service-hub.md](./n8n-service-hub.md) - Concrete service-hub shape for n8n plus systemd fallback.
|
|
28
27
|
- [implementation-readiness-report-2026-06-27.md](./implementation-readiness-report-2026-06-27.md) - Readiness assessment and launch gates.
|
|
29
28
|
|
|
30
29
|
## Adapted BMAD Note
|
|
@@ -33,3 +32,4 @@ The formal BMAD workflow expects a project-local `_bmad` runtime, PRD, and
|
|
|
33
32
|
step-by-step user confirmations. This repo does not currently contain `_bmad`.
|
|
34
33
|
These artifacts preserve the BMAD intent and structure while using the live
|
|
35
34
|
architecture review and advanced elicitation decisions as input.
|
|
35
|
+
|
|
@@ -29,7 +29,7 @@ invocations).
|
|
|
29
29
|
| 10 hermes profile | `hermes profile create <repo>-<role> --clone --no-alias` + mirror skills/plugins/hooks from default + symlink canonical runtime skills (`delonet-conventions`, `delonet-dotenv`, `hermes-pm-template-maintenance`, `hindsight`, `subagent-driven-development`) from `/home/delorenj/.agents/skills`; PM roles also seed `VOX_URL` in profile `.env` | n/a |
|
|
30
30
|
| 20 runtime repo | Create gh:delorenj/agent-hm-<repo>-<role> (private), push scaffold from role-local `.runtime-scaffold/`, submodule-add into ./runtime/, symlink ~/.hermes/profiles/<id> → runtime; PM roles also link the Voxxy plugin and set `tts.provider: voxxy` | `SKIP_RUNTIME_REPO=1` |
|
|
31
31
|
| 30 telegram | Capture BotFather token, write to runtime/.env, enable hermes-telegram toolset | `SKIP_TELEGRAM=1` |
|
|
32
|
-
|
|
|
32
|
+
| 40 plane | Create Plane project in 33god workspace (1:1 with agent), patch identifier into role.yaml | `SKIP_PLANE=1` |
|
|
33
33
|
| 60 bloodbank | Install consumer (renders from scaffold w/ agent values), health-check NATS, install nats-py via uv if missing | `SKIP_BLOODBANK=1` |
|
|
34
34
|
| 70 systemd | Install user units: gateway, consumer, heartbeat timer (board-reconciliation sentinel pass + gated runtime checkpoint, one tick) | `SKIP_SYSTEMD=1` |
|
|
35
35
|
| 80 registry | Append entry to ~/.hermes/agents-registry.yaml | n/a |
|
|
@@ -118,7 +118,7 @@ systemctl --user start hermes-${AGENT}-gateway.service
|
|
|
118
118
|
| --- | --- |
|
|
119
119
|
| Telegram | DM `@<repo>_<role>_bot` (once Telegram is wired) |
|
|
120
120
|
| Local CLI | `./agents/hermes/<role>/hermes chat "..."` |
|
|
121
|
-
| Bloodbank | Publish to
|
|
121
|
+
| Bloodbank | Publish to `bloodbank.cmd.v1.agent.task.assign` with `data.target_agent_id = <agent_id>` |
|
|
122
122
|
|
|
123
123
|
## Inspect fleet state
|
|
124
124
|
|
|
@@ -187,10 +187,10 @@ systemctl --user disable --now hermes-${AGENT}-heartbeat.timer
|
|
|
187
187
|
# that's what you want!)
|
|
188
188
|
hermes profile delete ${AGENT}
|
|
189
189
|
|
|
190
|
-
# 3. Archive
|
|
190
|
+
# 3. Archive Plane project (Plane UI or API)
|
|
191
191
|
PROJECT_ID=$(python3 -c "import yaml,pathlib; print(yaml.safe_load(pathlib.Path.home().joinpath('.hermes/agents-registry.yaml').read_text())['agents']['${AGENT}']['plane']['project_id'])")
|
|
192
192
|
curl -X POST "https://plane.delo.sh/api/v1/workspaces/33god/projects/${PROJECT_ID}/archive/" \
|
|
193
|
-
-H "X-API-Key: ${
|
|
193
|
+
-H "X-API-Key: ${PLANE_33GOD_API_KEY}"
|
|
194
194
|
|
|
195
195
|
# 4. BotFather: /deletebot @<repo>_<role>_bot
|
|
196
196
|
# 5. Archive runtime repo (GitHub UI; we don't have delete_repo scope by default)
|
|
@@ -219,7 +219,8 @@ rm -rf agents/hermes/<role>
|
|
|
219
219
|
### Consumer not seeing events
|
|
220
220
|
- Verify NATS is up: `docker compose -f ~/code/33GOD/bloodbank/compose/docker-compose.yml ps`
|
|
221
221
|
- Tail consumer: `journalctl --user -fu hermes-<agent>-consumer.service`
|
|
222
|
-
- Make sure something is
|
|
222
|
+
- Make sure something is publishing canonical repo events such as
|
|
223
|
+
`bloodbank.evt.v1.repo.issue.updated` with `data.repo = <repo>`
|
|
223
224
|
|
|
224
225
|
### Heartbeat not checkpointing (runtime not pushing)
|
|
225
226
|
The checkpoint runs inside the heartbeat tick (after the board-reconciliation
|
|
@@ -26,7 +26,7 @@ that does the board-reconciliation pass and then a gated runtime checkpoint
|
|
|
26
26
|
`.scripts/sentinel/`.
|
|
27
27
|
|
|
28
28
|
It talks to the ticket board through a pluggable adapter, so the same engine
|
|
29
|
-
runs on Plane or Trello. The engine lives once in this template (the
|
|
29
|
+
runs on Linear, Plane, or Trello. The engine lives once in this template (the
|
|
30
30
|
single source of truth) and propagates to every deployment with
|
|
31
31
|
`copier update`.
|
|
32
32
|
|
|
@@ -37,6 +37,8 @@ The following is true as of June 1, 2026.
|
|
|
37
37
|
- The engine, the provider abstraction, and the autonomous adversarial-review
|
|
38
38
|
enforcement are built, syntax-clean, and validated offline with a mock
|
|
39
39
|
provider.
|
|
40
|
+
- The **Linear** adapter is verified live against a real board (a real
|
|
41
|
+
`DEL` team: `resolve`, `list_issues` returning 93 issues, and `get_issue`).
|
|
40
42
|
- The **Plane** adapter is verified live against a real workspace
|
|
41
43
|
(`resolve`, `list_issues`, `get_issue`, `comment`, and a
|
|
42
44
|
`transition` to `completed` on a disposable issue).
|
|
@@ -52,7 +54,7 @@ For a local, single-machine install (no GitHub runtime repo, no Telegram, no
|
|
|
52
54
|
NATS), use the one-command bootstrap. From inside the target project:
|
|
53
55
|
|
|
54
56
|
```bash
|
|
55
|
-
export
|
|
57
|
+
export PLANE_API_KEY=<key> # or LINEAR_API_KEY / TRELLO_KEY + TRELLO_TOKEN
|
|
56
58
|
curl -fsSL https://raw.githubusercontent.com/delorenj/hermes-agent-template/main/install-local.sh | sh
|
|
57
59
|
```
|
|
58
60
|
|
|
@@ -64,7 +66,7 @@ macOS). See [Development guide: local install](development.md#local-install-one-
|
|
|
64
66
|
<!-- prettier-ignore -->
|
|
65
67
|
> [!IMPORTANT]
|
|
66
68
|
> Full (non-local) provisioning is outward-facing. It can create a GitHub
|
|
67
|
-
> runtime repo, a Telegram bot, and a
|
|
69
|
+
> runtime repo, a Telegram bot, and a Plane project, and the Telegram step is
|
|
68
70
|
> interactive. Use `install-local.sh` or the `SKIP_*` flags described in
|
|
69
71
|
> [Development guide:
|
|
70
72
|
> provisioning](development.md#provisioning-the-pm-manual) for local or lean
|
|
@@ -77,9 +79,9 @@ All paths are relative to the repository root.
|
|
|
77
79
|
| Path | What it is |
|
|
78
80
|
| --- | --- |
|
|
79
81
|
| `copier.yml` | Questions (`role`, `ticket_provider`) and the `_tasks` provisioning chain. |
|
|
80
|
-
| `template/role.yaml.jinja` | The rendered role
|
|
82
|
+
| `template/role.yaml.jinja` | The rendered role manifest, including the `ticket_provider` binding and the `reconcile` knobs (`grace_hours`, `auto_review`). |
|
|
81
83
|
| `template/.scripts/lib/ticket-provider.sh` | The adapter dispatcher (`tp`). The engine's only seam to a ticket system. |
|
|
82
|
-
| `template/.scripts/providers/{plane,trello}.sh` | The provider adapters. |
|
|
84
|
+
| `template/.scripts/providers/{linear,plane,trello}.sh` | The provider adapters. |
|
|
83
85
|
| `template/.scripts/42-ticket-provider.sh` | Provisioning step that resolves or creates the board. |
|
|
84
86
|
| `template/.scripts/70-systemd.sh` | Provisioning step that installs the gateway, consumer, and the fused `heartbeat` timer (board-reconciliation sentinel pass + gated runtime checkpoint). |
|
|
85
87
|
| `install-local.sh` | One-command local install (no cloud, macOS + Linux). |
|
|
@@ -107,8 +109,8 @@ the agent to read at run time. Keep the two in sync when behavior changes.
|
|
|
107
109
|
|
|
108
110
|
The highest-value open work, in order:
|
|
109
111
|
|
|
110
|
-
1. Live-verify the **Trello** adapter against a real board (Plane
|
|
111
|
-
See [Providers: verifying an
|
|
112
|
+
1. Live-verify the **Trello** adapter against a real board (Linear and Plane are
|
|
113
|
+
done). See [Providers: verifying an
|
|
112
114
|
adapter](providers.md#verifying-an-adapter-against-a-live-board).
|
|
113
115
|
2. Confirm `install-local.sh` on a real macOS machine. The Linux path and the
|
|
114
116
|
Plane adapter are verified; the macOS `launchd` agent and `mkdir` lock get
|
|
@@ -71,8 +71,7 @@ function, `tp`, that dispatches to one provider implementation under
|
|
|
71
71
|
through the `ticket_provider` question and recorded in `role.yaml` under
|
|
72
72
|
`ticket_provider.name`.
|
|
73
73
|
|
|
74
|
-
This is what lets one engine serve
|
|
75
|
-
Trello.
|
|
74
|
+
This is what lets one engine serve three back ends: Trello, Plane, and Linear.
|
|
76
75
|
For the full contract and the per-provider details, see
|
|
77
76
|
[Providers](providers.md).
|
|
78
77
|
|
|
@@ -112,7 +112,7 @@ repository root instead of driving Copier by hand. From inside the target
|
|
|
112
112
|
project:
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
|
-
export
|
|
115
|
+
export PLANE_API_KEY=<key> # or LINEAR_API_KEY / TRELLO_KEY + TRELLO_TOKEN
|
|
116
116
|
curl -fsSL https://raw.githubusercontent.com/delorenj/hermes-agent-template/main/install-local.sh | sh
|
|
117
117
|
```
|
|
118
118
|
|
|
@@ -133,8 +133,8 @@ The script:
|
|
|
133
133
|
7. smoke-tests the board connection through the adapter.
|
|
134
134
|
|
|
135
135
|
Useful environment overrides (skip the prompts): `HAT_REPO`, `HAT_PROVIDER`,
|
|
136
|
-
`HAT_ROLES`, `HAT_PLANE_WORKSPACE`, `HAT_PLANE_PROJECT`, `
|
|
137
|
-
and `HAT_DRY_RUN=1` to preview without changing anything.
|
|
136
|
+
`HAT_ROLES`, `HAT_PLANE_WORKSPACE`, `HAT_PLANE_PROJECT`, `HAT_LINEAR_TEAM`,
|
|
137
|
+
`HAT_TRELLO_BOARD`, and `HAT_DRY_RUN=1` to preview without changing anything.
|
|
138
138
|
|
|
139
139
|
This local path does not wire Telegram or email. Those are convenience layers in
|
|
140
140
|
the `pjangler` provisioner, not requirements for a working agent — talk to the
|
|
@@ -151,7 +151,7 @@ cd /path/to/your-project
|
|
|
151
151
|
copier copy gh:delorenj/hermes-agent-template ./agents/hermes/pm \
|
|
152
152
|
--data role=pm \
|
|
153
153
|
--data target_repo=<repo> \
|
|
154
|
-
--data ticket_provider=<plane|trello>
|
|
154
|
+
--data ticket_provider=<linear|plane|trello>
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
The `_tasks` chain in `copier.yml` runs the numbered provisioning scripts in
|
|
@@ -162,7 +162,7 @@ order. Several of them reach outside the repository.
|
|
|
162
162
|
> Full provisioning is outward-facing and partly interactive.
|
|
163
163
|
> `20-runtime-repo.sh` creates a private GitHub repo with `gh repo create`,
|
|
164
164
|
> `30-telegram.sh` prompts for a BotFather token and blocks waiting for input,
|
|
165
|
-
> and `42-ticket-provider.sh` can create a
|
|
165
|
+
> and `42-ticket-provider.sh` can create a Plane project. Don't run full
|
|
166
166
|
> provisioning unattended.
|
|
167
167
|
|
|
168
168
|
Use the `SKIP_*` environment flags for a local or lean install. Each numbered
|
|
@@ -172,7 +172,7 @@ script checks its flag and skips cleanly.
|
|
|
172
172
|
| --- | --- |
|
|
173
173
|
| `SKIP_TELEGRAM` | The interactive BotFather token step. |
|
|
174
174
|
| `SKIP_RUNTIME_REPO` | Creating the GitHub runtime repo. |
|
|
175
|
-
| `SKIP_PLANE` | Creating
|
|
175
|
+
| `SKIP_PLANE` | Creating a Plane project. |
|
|
176
176
|
| `SKIP_BLOODBANK` | Installing the BloodBank consumer. |
|
|
177
177
|
| `SKIP_SYSTEMD` | Installing `systemd` units (gateway, consumer, heartbeat timer). |
|
|
178
178
|
|
|
@@ -181,14 +181,13 @@ For example, a local install that creates no cloud resources:
|
|
|
181
181
|
```bash
|
|
182
182
|
SKIP_TELEGRAM=1 SKIP_RUNTIME_REPO=1 SKIP_PLANE=1 SKIP_BLOODBANK=1 \
|
|
183
183
|
copier copy gh:delorenj/hermes-agent-template ./agents/hermes/pm \
|
|
184
|
-
--data role=pm --data target_repo=<repo> --data ticket_provider=
|
|
184
|
+
--data role=pm --data target_repo=<repo> --data ticket_provider=linear
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
After provisioning, set the board binding in
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
a `systemd` `EnvironmentFile` (for example
|
|
188
|
+
`agents/hermes/pm/role.yaml`. For Linear, set `ticket_provider.team`
|
|
189
|
+
to the team key. Make the provider key available to the heartbeat's environment:
|
|
190
|
+
on Linux through a `systemd` `EnvironmentFile` (for example
|
|
192
191
|
`~/.hermes/<agent_id>.env`); on macOS the `launchd` agent sources that same
|
|
193
192
|
per-agent env file, so write the key there.
|
|
194
193
|
|
|
@@ -257,8 +256,8 @@ These cost real debugging time. Watch for them.
|
|
|
257
256
|
|
|
258
257
|
The following work is open for the incoming agent, roughly in priority order.
|
|
259
258
|
|
|
260
|
-
1. **Live-verify the Trello adapter.** Plane
|
|
261
|
-
(
|
|
259
|
+
1. **Live-verify the Trello adapter.** Linear and Plane are verified live
|
|
260
|
+
(Plane includes `transition` and `comment`). Trello is implemented against
|
|
262
261
|
the contract but unverified. Follow [Providers: verifying an
|
|
263
262
|
adapter](providers.md#verifying-an-adapter-against-a-live-board) with Trello
|
|
264
263
|
credentials, and fix any endpoint or field mismatches.
|
|
@@ -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 three 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,8 +43,12 @@ 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 three 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.
|
|
48
52
|
- **Plane** (`providers/plane.sh`) uses the Plane REST API with `X-API-Key`
|
|
49
53
|
authentication. A Plane project maps to the board, a cycle maps to the
|
|
50
54
|
milestone, and `state.group` maps to the state type. It's verified live. Note
|
|
@@ -60,12 +64,26 @@ The repository includes two supported adapters with different verification statu
|
|
|
60
64
|
|
|
61
65
|
Set credentials in the environment and the board binding in `role.yaml`.
|
|
62
66
|
|
|
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
|
+
|
|
63
82
|
<details>
|
|
64
83
|
<summary>Plane</summary>
|
|
65
84
|
|
|
66
|
-
- Credentials: `PLANE_API_KEY`
|
|
67
|
-
`
|
|
68
|
-
`PLANE_BASE` (default `https://plane.delo.sh`).
|
|
85
|
+
- Credentials: `PLANE_API_KEY`. Endpoint: `PLANE_BASE` (default
|
|
86
|
+
`https://plane.delo.sh`).
|
|
69
87
|
- Binding:
|
|
70
88
|
|
|
71
89
|
```yaml
|
|
@@ -96,9 +114,8 @@ Set credentials in the environment and the board binding in `role.yaml`.
|
|
|
96
114
|
|
|
97
115
|
To add a fourth provider, for example GitHub Issues or Jira, follow these steps.
|
|
98
116
|
|
|
99
|
-
1. Create `template/.scripts/providers/<name>.sh`. Start from `
|
|
100
|
-
|
|
101
|
-
model.
|
|
117
|
+
1. Create `template/.scripts/providers/<name>.sh`. Start from `linear.sh` if the
|
|
118
|
+
back end uses GraphQL, or `plane.sh` if it uses REST with a single API key.
|
|
102
119
|
2. Implement every operation in [the contract](#the-contract). Return the exact
|
|
103
120
|
JSON shapes shown in the table. Map your back end's states to the five
|
|
104
121
|
normalized states.
|
|
@@ -110,7 +127,8 @@ To add a fourth provider, for example GitHub Issues or Jira, follow these steps.
|
|
|
110
127
|
5. Extend `template/role.yaml.jinja` with a provider-specific binding block
|
|
111
128
|
under the `{% if ticket_provider == '<name>' %}` branch.
|
|
112
129
|
6. Teach `42-ticket-provider.sh` how to resolve or create the board for your
|
|
113
|
-
provider, if it differs from the existing `plane
|
|
130
|
+
provider, if it differs from the existing `linear`, `plane`, and `trello`
|
|
131
|
+
cases.
|
|
114
132
|
7. Validate and verify with the steps below.
|
|
115
133
|
|
|
116
134
|
<!-- prettier-ignore -->
|
|
@@ -140,7 +158,7 @@ Before you touch a live board, confirm the adapter parses and fails cleanly.
|
|
|
140
158
|
exit, with no Python traceback:
|
|
141
159
|
|
|
142
160
|
```bash
|
|
143
|
-
( unset
|
|
161
|
+
( unset LINEAR_API_KEY; sh template/.scripts/providers/linear.sh resolve )
|
|
144
162
|
```
|
|
145
163
|
|
|
146
164
|
## Verifying an adapter against a live board
|
|
@@ -151,15 +169,15 @@ never closes or modifies a ticket, so it's safe to run.
|
|
|
151
169
|
|
|
152
170
|
```bash
|
|
153
171
|
# 1. Load the credential into the environment only (never print it).
|
|
154
|
-
export
|
|
155
|
-
~/.hermes/<agent>.env | head -1 | sed -E 's/^(export )?
|
|
172
|
+
export LINEAR_API_KEY="$(grep -E '^(export )?LINEAR_API_KEY=' \
|
|
173
|
+
~/.hermes/<agent>.env | head -1 | sed -E 's/^(export )?LINEAR_API_KEY=//; s/^"//; s/"$//')"
|
|
156
174
|
|
|
157
175
|
# 2. Stage a role directory bound to the real board.
|
|
158
176
|
T=$(mktemp -d); RD="$T/agents/hermes/pm"
|
|
159
177
|
mkdir -p "$RD/.scripts/lib" "$RD/.scripts/providers"
|
|
160
178
|
cp template/.scripts/lib/ticket-provider.sh "$RD/.scripts/lib/"
|
|
161
|
-
cp template/.scripts/providers/
|
|
162
|
-
printf 'repo: demo\nrole: pm\nticket_provider:\n name:
|
|
179
|
+
cp template/.scripts/providers/linear.sh "$RD/.scripts/providers/"
|
|
180
|
+
printf 'repo: demo\nrole: pm\nticket_provider:\n name: linear\n team: DEL\n' \
|
|
163
181
|
> "$RD/role.yaml"
|
|
164
182
|
|
|
165
183
|
# 3. Run the read operations.
|
|
@@ -181,4 +199,5 @@ verification.
|
|
|
181
199
|
## Read next
|
|
182
200
|
|
|
183
201
|
- [Development guide](development.md): the full edit, validate, and propagate
|
|
184
|
-
workflow, plus the open roadmap, which includes live-verifying
|
|
202
|
+
workflow, plus the open roadmap, which includes live-verifying Plane and
|
|
203
|
+
Trello.
|
|
@@ -15,11 +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=plane|trello (default: plane)
|
|
18
|
+
# HAT_PROVIDER=linear|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: PLANE_API_KEY
|
|
22
|
-
# | TRELLO_KEY+TRELLO_TOKEN
|
|
21
|
+
# Provider creds: LINEAR_API_KEY | PLANE_API_KEY+PLANE_BASE | TRELLO_KEY+TRELLO_TOKEN
|
|
23
22
|
set -eu
|
|
24
23
|
|
|
25
24
|
say() { printf '\033[36m%s\033[0m\n' "$*"; }
|
|
@@ -101,7 +100,7 @@ TOML
|
|
|
101
100
|
fi
|
|
102
101
|
|
|
103
102
|
# --- 4. Provider credentials + board binding ---------------------------------
|
|
104
|
-
ask HAT_PROVIDER "Ticket provider (plane|trello)" "plane"
|
|
103
|
+
ask HAT_PROVIDER "Ticket provider (linear|plane|trello)" "plane"
|
|
105
104
|
PROVIDER="$HAT_PROVIDER"
|
|
106
105
|
ask HAT_REPO "Project/repo name" "$(basename "$PROJECT_DIR" | tr '[:upper:]' '[:lower:]')"
|
|
107
106
|
REPO="$HAT_REPO"
|
|
@@ -110,17 +109,13 @@ PM_ENV="$HOME/.hermes/${REPO}-pm.env"
|
|
|
110
109
|
# These get written into the pm role.yaml binding after render.
|
|
111
110
|
# Pre-seed from optional env knobs so the install can run non-interactively.
|
|
112
111
|
TP_WORKSPACE="${HAT_PLANE_WORKSPACE:-}"; TP_PROJECT="${HAT_PLANE_PROJECT:-}"
|
|
113
|
-
TP_BOARD="${HAT_TRELLO_BOARD:-}"
|
|
112
|
+
TP_TEAM="${HAT_LINEAR_TEAM:-}"; TP_BOARD="${HAT_TRELLO_BOARD:-}"
|
|
114
113
|
case "$PROVIDER" in
|
|
115
114
|
plane)
|
|
116
115
|
ask PLANE_BASE "Plane base URL" "https://app.plane.so"
|
|
116
|
+
: "${PLANE_API_KEY:?Set PLANE_API_KEY in your environment, then re-run}"
|
|
117
117
|
ask TP_WORKSPACE "Plane workspace slug" ""
|
|
118
118
|
[ -n "$TP_WORKSPACE" ] || die "workspace is required"
|
|
119
|
-
TP_WORKSPACE_KEY="$(printf '%s' "$TP_WORKSPACE" | tr '[:lower:]' '[:upper:]' | sed 's/[^A-Z0-9]/_/g')"
|
|
120
|
-
TP_WORKSPACE_KEY="PLANE_${TP_WORKSPACE_KEY}_API_KEY"
|
|
121
|
-
eval "TP_WORKSPACE_API_KEY=\${$TP_WORKSPACE_KEY:-}"
|
|
122
|
-
PLANE_API_KEY="${PLANE_API_KEY:-$TP_WORKSPACE_API_KEY}"
|
|
123
|
-
: "${PLANE_API_KEY:?Set PLANE_API_KEY or $TP_WORKSPACE_KEY in your environment, then re-run}"
|
|
124
119
|
say " Plane projects in '$TP_WORKSPACE':"
|
|
125
120
|
_pj="$(mktemp)"
|
|
126
121
|
curl -fsS "$PLANE_BASE/api/v1/workspaces/$TP_WORKSPACE/projects/?per_page=100" \
|
|
@@ -138,9 +133,15 @@ PY
|
|
|
138
133
|
rm -f "$_pj"
|
|
139
134
|
ask TP_PROJECT "Plane project UUID to manage" ""
|
|
140
135
|
[ -n "$TP_PROJECT" ] || die "project UUID is required"
|
|
141
|
-
CRED_LINES="
|
|
136
|
+
CRED_LINES="PLANE_API_KEY=$PLANE_API_KEY
|
|
142
137
|
PLANE_BASE=$PLANE_BASE"
|
|
143
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
|
+
;;
|
|
144
145
|
trello)
|
|
145
146
|
: "${TRELLO_KEY:?Set TRELLO_KEY in your environment, then re-run}"
|
|
146
147
|
: "${TRELLO_TOKEN:?Set TRELLO_TOKEN in your environment, then re-run}"
|
|
@@ -164,13 +165,12 @@ fi
|
|
|
164
165
|
export SKIP_TELEGRAM=1 SKIP_EMAIL=1 SKIP_RUNTIME_REPO=1 SKIP_PLANE=1 \
|
|
165
166
|
SKIP_BLOODBANK=1 SKIP_SYSTEMD=1
|
|
166
167
|
ROLES="${HAT_ROLES:-pm}"
|
|
167
|
-
SCRUB_PLANE_KEY="${TP_WORKSPACE_KEY:-PLANE_DEFAULT_API_KEY}"
|
|
168
168
|
for ROLE in $ROLES; do
|
|
169
169
|
say "5. provisioning role: $ROLE"
|
|
170
170
|
DEST="$PROJECT_DIR/agents/hermes/$ROLE"
|
|
171
171
|
# Scrub provider creds from copier's environment so 42-ticket-provider skips
|
|
172
172
|
# board CREATION; we bind to the existing board in step 6 instead.
|
|
173
|
-
run "env -u PLANE_API_KEY -u
|
|
173
|
+
run "env -u PLANE_API_KEY -u PLANE_33GOD_API_KEY -u LINEAR_API_KEY \
|
|
174
174
|
-u TRELLO_KEY -u TRELLO_TOKEN \
|
|
175
175
|
copier copy '$TEMPLATE_SRC' '$DEST' --trust --defaults --overwrite \
|
|
176
176
|
--data target_repo='$REPO' --data role='$ROLE' --data ticket_provider='$PROVIDER'"
|
|
@@ -180,7 +180,7 @@ done
|
|
|
180
180
|
PM_ROLE="$PROJECT_DIR/agents/hermes/pm/role.yaml"
|
|
181
181
|
if [ -f "$PM_ROLE" ] && [ "${HAT_DRY_RUN:-0}" != "1" ]; then
|
|
182
182
|
say "6. binding pm to your $PROVIDER board"
|
|
183
|
-
TP_WORKSPACE="$TP_WORKSPACE" TP_PROJECT="$TP_PROJECT" \
|
|
183
|
+
TP_WORKSPACE="$TP_WORKSPACE" TP_PROJECT="$TP_PROJECT" TP_TEAM="$TP_TEAM" \
|
|
184
184
|
TP_BOARD="$TP_BOARD" PROVIDER="$PROVIDER" python3 - "$PM_ROLE" <<'PY'
|
|
185
185
|
import os, re, sys, pathlib
|
|
186
186
|
p = pathlib.Path(sys.argv[1]); t = p.read_text()
|
|
@@ -190,6 +190,7 @@ def setleaf(text, key, val):
|
|
|
190
190
|
return new if n else text
|
|
191
191
|
t = setleaf(t, "workspace", os.environ.get("TP_WORKSPACE",""))
|
|
192
192
|
t = setleaf(t, "project", os.environ.get("TP_PROJECT",""))
|
|
193
|
+
t = setleaf(t, "team", os.environ.get("TP_TEAM",""))
|
|
193
194
|
t = setleaf(t, "board", os.environ.get("TP_BOARD",""))
|
|
194
195
|
p.write_text(t)
|
|
195
196
|
print(" bound:", {k:v for k,v in os.environ.items() if k.startswith("TP_") and v})
|
|
@@ -13,7 +13,7 @@ and recoverable on failure.
|
|
|
13
13
|
| `memories/MEMORY.md` | yes | The condensed mental-model summary loaded each session |
|
|
14
14
|
| `memories/USER.md` | yes | The operator's persona (Jarad DeLorenzo, ...) |
|
|
15
15
|
| `sessions/sessions.db` | yes (LFS) | SQLite store of every conversation |
|
|
16
|
-
| `bloodbank-consumer.py` | yes | NATS subscriber
|
|
16
|
+
| `bloodbank-consumer.py` | yes | NATS subscriber using canonical subjects plus envelope routing data |
|
|
17
17
|
| `decisions/` | yes | Agent-emitted decisions, one file per important call |
|
|
18
18
|
| `.env` | **no** | API keys + Telegram bot token (per-machine secret) |
|
|
19
19
|
| `auth.json` | **no** | Deprecated local OAuth store; fleet auth defaults to `HERMES_OAUTH_FILE=~/.hermes/auth.json` |
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
Bloodbank consumer for hermes agent {{agent_id}}.
|
|
4
4
|
|
|
5
5
|
Subscribes to:
|
|
6
|
-
bloodbank.evt.v1.repo
|
|
7
|
-
bloodbank.cmd.v1.agent
|
|
6
|
+
bloodbank.evt.v1.repo.>
|
|
7
|
+
bloodbank.cmd.v1.agent.>
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
The Bloodbank subject identifies the fixed domain/entity/action route. Repo and
|
|
10
|
+
agent identifiers stay in envelope data, so this consumer filters repo events
|
|
11
|
+
by ``data.repo`` and agent commands by ``data.target_agent_id``.
|
|
12
|
+
|
|
13
|
+
For each event/command received, writes it to the agent's notification queue at
|
|
14
|
+
$HERMES_HOME/bloodbank-inbox/<timestamp>.json so the gateway can ingest it on
|
|
15
|
+
the next tick. Reconnects on broker churn; idempotent across restarts.
|
|
14
16
|
|
|
15
17
|
Generated by hermes-agent-template/runtime-scaffold/bloodbank-consumer.py and
|
|
16
18
|
rendered with role-specific values at provision time. Re-render by re-running
|
|
@@ -49,8 +51,8 @@ PRODUCER = f"hermes-agent:{AGENT_ID}"
|
|
|
49
51
|
SOURCE = f"hermes://agent/{AGENT_ID}"
|
|
50
52
|
|
|
51
53
|
SUBJECTS = [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
"bloodbank.evt.v1.repo.>",
|
|
55
|
+
"bloodbank.cmd.v1.agent.>",
|
|
54
56
|
]
|
|
55
57
|
|
|
56
58
|
KIND_MARKERS = {"event": "evt", "command": "cmd", "reply": "rpy"}
|
|
@@ -62,8 +64,8 @@ def _now():
|
|
|
62
64
|
|
|
63
65
|
def _subject_and_domain(ce_type, kind):
|
|
64
66
|
parts = ce_type.split(".")
|
|
65
|
-
if len(parts) != 5 or parts[0] != "bloodbank" or
|
|
66
|
-
raise ValueError(f"type {ce_type!r} must match bloodbank.
|
|
67
|
+
if len(parts) != 5 or parts[0] != "bloodbank" or parts[1] != "v1":
|
|
68
|
+
raise ValueError(f"type {ce_type!r} must match bloodbank.v1.<domain>.<entity>.<action>")
|
|
67
69
|
if kind not in KIND_MARKERS:
|
|
68
70
|
raise ValueError(f"unknown envelope kind {kind!r}")
|
|
69
71
|
_vendor, version, domain, entity, action = parts
|
|
@@ -95,6 +97,33 @@ def build_envelope(ce_type, data, *, kind="event", correlationid=None, causation
|
|
|
95
97
|
return env
|
|
96
98
|
|
|
97
99
|
|
|
100
|
+
def _is_for_consumer(subject, payload):
|
|
101
|
+
"""Route canonical subjects using identifiers carried in envelope data."""
|
|
102
|
+
if not isinstance(payload, dict):
|
|
103
|
+
return False
|
|
104
|
+
parts = subject.split(".")
|
|
105
|
+
if len(parts) != 6 or parts[0] != "bloodbank" or parts[2] != "v1":
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
_vendor, kind, version, domain, entity, action = parts
|
|
109
|
+
if kind not in KIND_MARKERS.values():
|
|
110
|
+
return False
|
|
111
|
+
if KIND_MARKERS.get(payload.get("kind")) != kind:
|
|
112
|
+
return False
|
|
113
|
+
if payload.get("subject") != subject:
|
|
114
|
+
return False
|
|
115
|
+
if payload.get("type") != f"bloodbank.{version}.{domain}.{entity}.{action}":
|
|
116
|
+
return False
|
|
117
|
+
data = payload.get("data")
|
|
118
|
+
if not isinstance(data, dict):
|
|
119
|
+
return False
|
|
120
|
+
if kind == "evt" and domain == "repo":
|
|
121
|
+
return data.get("repo") == REPO
|
|
122
|
+
if kind == "cmd" and domain == "agent":
|
|
123
|
+
return data.get("target_agent_id") == AGENT_ID
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
98
127
|
async def main():
|
|
99
128
|
nc = await nats.connect(NATS_URL, name=f"hermes-{AGENT_ID}")
|
|
100
129
|
stop = asyncio.Event()
|
|
@@ -103,7 +132,10 @@ async def main():
|
|
|
103
132
|
try:
|
|
104
133
|
payload = json.loads(msg.data.decode())
|
|
105
134
|
except Exception:
|
|
106
|
-
payload
|
|
135
|
+
sys.stderr.write(f"[bloodbank-consumer:{AGENT_ID}] skipped unroutable payload on {msg.subject}\n")
|
|
136
|
+
return
|
|
137
|
+
if not _is_for_consumer(msg.subject, payload):
|
|
138
|
+
return
|
|
107
139
|
entry = {
|
|
108
140
|
"received_at": _now(),
|
|
109
141
|
"subject": msg.subject,
|
|
@@ -125,7 +157,7 @@ async def main():
|
|
|
125
157
|
"bloodbank.v1.agent.online.changed",
|
|
126
158
|
{"agent_id": AGENT_ID, "repo": REPO, "role": ROLE, "state": "online"},
|
|
127
159
|
)
|
|
128
|
-
await nc.publish(
|
|
160
|
+
await nc.publish(online["subject"], json.dumps(online).encode())
|
|
129
161
|
|
|
130
162
|
def _shutdown(*_):
|
|
131
163
|
stop.set()
|
|
@@ -138,7 +170,7 @@ async def main():
|
|
|
138
170
|
"bloodbank.v1.agent.online.changed",
|
|
139
171
|
{"agent_id": AGENT_ID, "repo": REPO, "role": ROLE, "state": "offline"},
|
|
140
172
|
)
|
|
141
|
-
await nc.publish("
|
|
173
|
+
await nc.publish(offline["subject"], json.dumps(offline).encode())
|
|
142
174
|
await nc.drain()
|
|
143
175
|
|
|
144
176
|
|
|
@@ -7,8 +7,8 @@ short and high-signal — anything that should ALWAYS be in context.
|
|
|
7
7
|
|
|
8
8
|
You are **{{display_name}}**, the `{{role}}` agent for the `{{repo}}` repo.
|
|
9
9
|
Your scope is the repo at the project root. You communicate via Telegram
|
|
10
|
-
(`@{{repo}}_{{role}}_bot`) and Bloodbank events on
|
|
11
|
-
`bloodbank.evt.v1.repo.{{repo}}
|
|
10
|
+
(`@{{repo}}_{{role}}_bot`) and canonical Bloodbank repo events on
|
|
11
|
+
`bloodbank.evt.v1.repo.>`, filtered by `data.repo = {{repo}}`.
|
|
12
12
|
|
|
13
13
|
## Operator
|
|
14
14
|
|