@delorenj/pjangler 1.4.6 → 1.5.3

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.
Files changed (83) hide show
  1. package/README.md +41 -596
  2. package/dist/assets/project-notebook-skill/SHA256SUMS +5 -5
  3. package/dist/assets/project-notebook-skill/export-manifest.json +5 -5
  4. package/dist/assets/project-notebook-skill/hooks/session-end.sh +20 -1
  5. package/dist/assets/project-notebook-skill/hooks/session-start.sh +20 -1
  6. package/dist/assets/project-notebook-skill/references/configuration.md +2 -0
  7. package/dist/assets/project-notebook-skill/scripts/project-hooks.py +33 -3
  8. package/dist/assets/project-notebook-skill/tests/test_project_hooks.py +37 -0
  9. package/dist/index.js +9385 -18379
  10. package/dist/mcp-server.js +9432 -17852
  11. package/dist/notebook/journal-service-main.js +5682 -0
  12. package/dist/project-registry-service.js +748 -0
  13. package/dist/prompt.js +2 -1
  14. package/package.json +15 -10
  15. package/scripts/install-project-registry.mjs +34 -0
  16. package/scripts/migrate-project-registry.mjs +117 -0
  17. package/src/project/registryClient.ts +62 -0
  18. package/templates/commonproject/copier.yml +16 -19
  19. package/templates/commonproject/template/.agents/hooks/README.md +10 -4
  20. package/templates/commonproject/template/.agents/hooks/hermes/hindsight-hook.sh +12 -2
  21. package/templates/commonproject/template/.agents/hooks/hooks.master.json +10 -10
  22. package/templates/commonproject/template/.agents/hooks/lib/hook-guard.sh +13 -0
  23. package/templates/commonproject/template/.agents/hooks/sync.py +66 -17
  24. package/templates/commonproject/template/.agents/skills.json +0 -1
  25. package/templates/commonproject/template/.github/workflows/code-review.yml.jinja +7 -1
  26. package/templates/commonproject/template/.project.json.jinja +1 -1
  27. package/templates/commonproject/template/mise.toml.jinja +11 -21
  28. package/contracts/fleet-contract.yaml +0 -584
  29. package/templates/commonproject/template/.mise/scripts/provision-packs.py +0 -587
  30. package/templates/commonproject/template/.mise/scripts/sync-skills.py +0 -2183
  31. package/templates/hermes-agent/copier.yml +0 -186
  32. package/templates/hermes-agent/template/.gitignore.jinja +0 -11
  33. package/templates/hermes-agent/template/.runtime-scaffold/.gitattributes +0 -10
  34. package/templates/hermes-agent/template/.runtime-scaffold/.gitignore.jinja +0 -44
  35. package/templates/hermes-agent/template/.runtime-scaffold/README.md +0 -56
  36. package/templates/hermes-agent/template/.runtime-scaffold/decisions/.gitkeep +0 -2
  37. package/templates/hermes-agent/template/.runtime-scaffold/logs/.gitkeep +0 -1
  38. package/templates/hermes-agent/template/.runtime-scaffold/memories/MEMORY.md +0 -50
  39. package/templates/hermes-agent/template/.runtime-scaffold/memories/USER.md +0 -51
  40. package/templates/hermes-agent/template/.scripts/00-banner.sh +0 -21
  41. package/templates/hermes-agent/template/.scripts/01-config.sh +0 -60
  42. package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +0 -42
  43. package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +0 -222
  44. package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +0 -199
  45. package/templates/hermes-agent/template/.scripts/30-telegram.sh +0 -363
  46. package/templates/hermes-agent/template/.scripts/31-slack.sh +0 -457
  47. package/templates/hermes-agent/template/.scripts/40-plane.sh +0 -60
  48. package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +0 -373
  49. package/templates/hermes-agent/template/.scripts/60-bloodbank.sh +0 -18
  50. package/templates/hermes-agent/template/.scripts/70-systemd.sh +0 -354
  51. package/templates/hermes-agent/template/.scripts/80-registry.sh +0 -185
  52. package/templates/hermes-agent/template/.scripts/99-summary.sh +0 -87
  53. package/templates/hermes-agent/template/.scripts/_lib.sh +0 -1138
  54. package/templates/hermes-agent/template/.scripts/channel-transaction.py +0 -2340
  55. package/templates/hermes-agent/template/.scripts/checkpoint.sh +0 -82
  56. package/templates/hermes-agent/template/.scripts/config.example.toml +0 -70
  57. package/templates/hermes-agent/template/.scripts/credential-launch.sh +0 -85
  58. package/templates/hermes-agent/template/.scripts/heartbeat.sh +0 -283
  59. package/templates/hermes-agent/template/.scripts/lib/fleet-env.sh +0 -202
  60. package/templates/hermes-agent/template/.scripts/lib/parse-fleet-env.py +0 -761
  61. package/templates/hermes-agent/template/.scripts/lib/profile-config-lock.py +0 -182
  62. package/templates/hermes-agent/template/.scripts/lib/profile-config-seed.py +0 -108
  63. package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +0 -199
  64. package/templates/hermes-agent/template/.scripts/lib/voice-config.py +0 -546
  65. package/templates/hermes-agent/template/.scripts/lifecycle.sh +0 -126
  66. package/templates/hermes-agent/template/.scripts/momo-wip-lock.py +0 -137
  67. package/templates/hermes-agent/template/.scripts/providers/linear.sh +0 -329
  68. package/templates/hermes-agent/template/.scripts/providers/plane.sh +0 -671
  69. package/templates/hermes-agent/template/.scripts/providers/trello.sh +0 -241
  70. package/templates/hermes-agent/template/.scripts/reporter-watchdog.py +0 -242
  71. package/templates/hermes-agent/template/.scripts/secret-scan.py +0 -134
  72. package/templates/hermes-agent/template/.scripts/sentinel/bin/emit-event.py +0 -106
  73. package/templates/hermes-agent/template/.scripts/sentinel/bin/issue-autonomous-review.sh +0 -339
  74. package/templates/hermes-agent/template/.scripts/sentinel/bin/issue-close-gate.sh +0 -179
  75. package/templates/hermes-agent/template/.scripts/sentinel/docs/autonomous-delegated-review.md +0 -162
  76. package/templates/hermes-agent/template/.scripts/sentinel/docs/bloodbank-events.md +0 -42
  77. package/templates/hermes-agent/template/.scripts/sentinel/docs/continuous-ticket-orchestration.md +0 -77
  78. package/templates/hermes-agent/template/.scripts/sentinel.prompt.md.jinja +0 -116
  79. package/templates/hermes-agent/template/.scripts/store-onepassword-secret.py +0 -260
  80. package/templates/hermes-agent/template/SOUL.md.jinja +0 -197
  81. package/templates/hermes-agent/template/hermes.jinja +0 -109
  82. package/templates/hermes-agent/template/momo.jinja +0 -177
  83. package/templates/hermes-agent/template/role.yaml.jinja +0 -112
@@ -1,373 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Bind this agent to the repo's ONE ticket board.
3
- #
4
- # Source of truth is the repo-root .project.json `ticket_provider` block
5
- # (written by the CommonProject base template). The model is:
6
- # ONE board per repo — the PM owns it, and its heartbeat reconciliation pass
7
- # watches it. So we never mint a per-agent, role-suffixed board ("Foo PM" /
8
- # "Foo Sentinel"). Instead:
9
- # 1. If .project.json already names a board -> BIND to it (no creation).
10
- # 2. Otherwise (hermes run on a repo with no CommonProject board yet)
11
- # -> create ONE repo-named board and write it back into .project.json so
12
- # it becomes the SOT for every agent in this repo.
13
- # Either way we register this agent under .project.json `agents` and mirror the
14
- # binding into role.yaml for back-compat (80-registry.sh / 99-summary.sh).
15
-
16
- # The caller's negative board grant is authoritative. Check it before sourcing
17
- # _lib.sh so a skipped step cannot load fleet credentials, create logs/markers,
18
- # inspect bindings, or reach a provider adapter.
19
- if [[ "${SKIP_PLANE:-0}" == "1" ]]; then
20
- printf '%s\n' '[42] ticket provider — SKIPPED (SKIP_PLANE=1)' >&2
21
- exit 0
22
- fi
23
-
24
- # shellcheck source=_lib.sh
25
- source "$(dirname "$0")/_lib.sh"
26
- load_role_env
27
-
28
- # Locate the repo-root .project.json (the SOT).
29
- REPO_ROOT="$(project_repo_path 2>/dev/null || true)"
30
- [ -n "$REPO_ROOT" ] || REPO_ROOT="$(cd "$ROLE_DIR/../../.." 2>/dev/null && pwd)"
31
- PROJECT_JSON="$REPO_ROOT/.project.json"
32
- ROLE_DIR_REL="${ROLE_DIR#"$REPO_ROOT"/}"
33
-
34
- # Serialize the complete read / provider check-or-create / atomic write
35
- # transaction per project. The lock lives outside the checkout so a normal
36
- # provision cannot leave repository dirt behind.
37
- command -v flock >/dev/null 2>&1 \
38
- || die "flock is required for safe ticket-provider binding"
39
- PROJECT_LOCK_KEY="$(printf '%s' "$PROJECT_JSON" | sha256sum)"
40
- PROJECT_LOCK_KEY="${PROJECT_LOCK_KEY%% *}"
41
- PROJECT_LOCK_DIR="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/pjangler-ticket-provider"
42
- mkdir -p "$PROJECT_LOCK_DIR"
43
- PROJECT_LOCK_FILE="$PROJECT_LOCK_DIR/$PROJECT_LOCK_KEY.lock"
44
- [[ ! -L "$PROJECT_LOCK_FILE" ]] \
45
- || die "refusing ticket-provider lock symlink: $PROJECT_LOCK_FILE"
46
- exec {PROJECT_LOCK_FD}>"$PROJECT_LOCK_FILE"
47
- chmod 600 "$PROJECT_LOCK_FILE"
48
- flock -w "${PROJECT_LOCK_TIMEOUT_SECONDS:-30}" "$PROJECT_LOCK_FD" \
49
- || die "timed out waiting for ticket-provider lock: $PROJECT_JSON"
50
- project_lock_release() {
51
- flock -u "$PROJECT_LOCK_FD" 2>/dev/null || true
52
- exec {PROJECT_LOCK_FD}>&-
53
- }
54
- trap project_lock_release EXIT
55
-
56
- [[ ! -L "$PROJECT_JSON" ]] || die "refusing .project.json symlink: $PROJECT_JSON"
57
- if [[ -e "$PROJECT_JSON" ]]; then
58
- python3 - "$PROJECT_JSON" <<'PY' \
59
- || die "malformed .project.json; refusing ticket-provider mutation or board creation"
60
- import json
61
- import pathlib
62
- import sys
63
-
64
- path = pathlib.Path(sys.argv[1])
65
- try:
66
- value = json.loads(path.read_text(encoding="utf-8"))
67
- except (OSError, UnicodeError, json.JSONDecodeError):
68
- raise SystemExit(1)
69
- raise SystemExit(0 if isinstance(value, dict) else 1)
70
- PY
71
- fi
72
-
73
- # shellcheck source=lib/ticket-provider.sh
74
- source "$(dirname "$0")/lib/ticket-provider.sh"
75
-
76
- already_done 42-ticket-provider \
77
- && log "[42] ticket-provider marker found — revalidating canonical board binding"
78
-
79
- # pj <dotted.key> — read a string value from .project.json (empty if absent).
80
- pj() {
81
- [ -f "$PROJECT_JSON" ] || { printf ''; return 0; }
82
- python3 - "$PROJECT_JSON" "$1" <<'PY'
83
- import sys, json, pathlib
84
- d = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
85
- if not isinstance(d, dict):
86
- raise SystemExit(".project.json root must be an object")
87
- cur = d
88
- for k in sys.argv[2].split("."):
89
- if isinstance(cur, dict) and k in cur:
90
- cur = cur[k]
91
- else:
92
- print(""); raise SystemExit(0)
93
- print(cur if isinstance(cur, str) else "")
94
- PY
95
- }
96
-
97
- # pj_write — merge board binding (optional) + this agent into .project.json.
98
- # args: set_provider(0|1) provider board_id workspace identifier team
99
- # identifier_source
100
- #
101
- # identifier_source is REQUIRED whenever identifier is non-empty and must be
102
- # `provider` (read back out of the provider's own response) or `proposed` (a
103
- # local prefix the provider never confirmed, e.g. the string Trello echoes back
104
- # because Trello mints no key). There is deliberately no third option and no
105
- # default: an unstamped identifier is refused outright, so no locally-computed
106
- # string can ever land in .project.json wearing the authority of a live board.
107
- #
108
- # `provider` is further refused outright for a provider that assigns no
109
- # identifiers at all. Trello cannot have named a key it has no concept of, so
110
- # the claim is not merely unproven, it is impossible — and a downstream reader
111
- # must always be able to tell "Plane assigned PJAN" from "we picked INT and
112
- # Trello confirmed a board exists".
113
- #
114
- # Those are two different facts, so they get two different fields. Whether the
115
- # BOARD is real is stamped in board_confirmed_at, and that is what a link rests
116
- # on; whether the KEY came from the provider is identifier_source. Every
117
- # provider can answer the first. Only some can answer the second.
118
- pj_write() {
119
- REPO="$REPO" REPO_ROOT="$REPO_ROOT" AGENT_ID="$AGENT_ID" ROLE="$ROLE" \
120
- ROLE_DIR_REL="$ROLE_DIR_REL" PROJECT_DESC="${PROJECT_DESC:-}" \
121
- python3 - "$PROJECT_JSON" "$@" <<'PY'
122
- import datetime
123
- import errno
124
- import sys, os, json, pathlib, stat, tempfile
125
- (path, set_provider, provider, board_id, workspace, identifier, team,
126
- identifier_source) = sys.argv[1:9]
127
- # Providers that mint their own keys and hand them back on read.
128
- IDENTIFIER_ASSIGNING = ("plane", "linear")
129
- if identifier and identifier_source not in ("provider", "proposed"):
130
- raise SystemExit(
131
- "refusing to persist ticket_provider.identifier %r with source %r: "
132
- "every identifier must be stamped provider|proposed" % (identifier, identifier_source)
133
- )
134
- if identifier_source == "provider" and provider not in IDENTIFIER_ASSIGNING:
135
- raise SystemExit(
136
- "refusing to persist ticket_provider.identifier %r as provider-sourced: "
137
- "%s assigns no identifiers, so it cannot have named one" % (identifier, provider)
138
- )
139
- now = (
140
- datetime.datetime.now(datetime.timezone.utc)
141
- .isoformat(timespec="milliseconds")
142
- .replace("+00:00", "Z")
143
- )
144
- p = pathlib.Path(path)
145
- if p.is_symlink():
146
- raise SystemExit(f"refusing .project.json symlink: {p}")
147
- if p.exists():
148
- try:
149
- d = json.loads(p.read_text(encoding="utf-8"))
150
- except (OSError, UnicodeError, json.JSONDecodeError) as exc:
151
- raise SystemExit(f"malformed .project.json: {type(exc).__name__}")
152
- if not isinstance(d, dict):
153
- raise SystemExit(".project.json root must be an object")
154
- else:
155
- d = {}
156
- repo = os.environ.get("REPO", "")
157
- d.setdefault("project_name", repo)
158
- d.setdefault("project_slug", repo)
159
- if not d.get("repo_path"):
160
- d["repo_path"] = os.environ.get("REPO_ROOT", "")
161
- if set_provider == "1":
162
- tp = d.setdefault("ticket_provider", {})
163
- tp["type"] = provider
164
- if workspace: tp["workspace"] = workspace
165
- if identifier:
166
- tp["identifier"] = identifier
167
- tp["identifier_source"] = identifier_source
168
- if identifier_source == "provider":
169
- tp["identifier_fetched_at"] = now
170
- else:
171
- tp.pop("identifier_fetched_at", None)
172
- if board_id:
173
- tp["board_id"] = board_id
174
- # Every caller that reaches here with a board id got it out of the
175
- # provider itself (create_board or resolve), so this write IS the
176
- # confirmation and may stamp its own instant.
177
- tp["board_confirmed_at"] = now
178
- if team: tp["team"] = team
179
- tp.pop("board_url", None)
180
- tp["state"] = "linked" if board_id else "deferred"
181
- ag = d.setdefault("agents", {})
182
- entry = ag.get(os.environ["AGENT_ID"], {})
183
- if not isinstance(entry, dict):
184
- entry = {}
185
- entry.update({
186
- "role": os.environ["ROLE"],
187
- "role_dir": os.environ["ROLE_DIR_REL"],
188
- "provisioning_state": "linked" if board_id else "deferred",
189
- })
190
- ag[os.environ["AGENT_ID"]] = entry
191
- rendered = json.dumps(d, indent=2) + "\n"
192
- p.parent.mkdir(parents=True, exist_ok=True)
193
- mode = stat.S_IMODE(p.stat().st_mode) if p.exists() else 0o644
194
- fd, temporary = tempfile.mkstemp(prefix=f".{p.name}.ticket-provider-", dir=p.parent)
195
- try:
196
- os.fchmod(fd, mode)
197
- with os.fdopen(fd, "w", encoding="utf-8") as handle:
198
- handle.write(rendered)
199
- handle.flush()
200
- os.fsync(handle.fileno())
201
- os.replace(temporary, p)
202
- unsupported = {errno.EINVAL, getattr(errno, "ENOTSUP", errno.EINVAL), errno.ENOSYS}
203
- directory_fd = os.open(p.parent, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
204
- try:
205
- try:
206
- os.fsync(directory_fd)
207
- except OSError as exc:
208
- if exc.errno not in unsupported:
209
- raise
210
- finally:
211
- os.close(directory_fd)
212
- except BaseException:
213
- try:
214
- os.unlink(temporary)
215
- except FileNotFoundError:
216
- pass
217
- raise
218
- PY
219
- log " .project.json updated (agent=$AGENT_ID)"
220
- }
221
-
222
- # Mirror the binding into role.yaml so legacy consumers keep working.
223
- mirror_to_role_yaml() {
224
- # mirror_to_role_yaml <provider> <board_id> <workspace> <identifier> <team>
225
- local provider="$1" bid="$2" ws="$3" ident="$4" team="$5"
226
- yaml_set ticket_provider.name "$provider" 2>/dev/null || true
227
- [ -n "$bid" ] && yaml_set ticket_provider.board_id "$bid" 2>/dev/null || true
228
- case "$provider" in
229
- plane)
230
- [ -n "$bid" ] && echo "$bid" > "$ROLE_DIR/.scripts/.plane-project-id"
231
- [ -n "$ws" ] && yaml_set ticket_provider.workspace "$ws" 2>/dev/null || true
232
- [ -n "$bid" ] && yaml_set ticket_provider.project "$bid" 2>/dev/null || true
233
- [ -n "$ws" ] && yaml_set plane.workspace "$ws" 2>/dev/null || true
234
- [ -n "$ident" ] && yaml_set plane.identifier "$ident" 2>/dev/null || true
235
- ;;
236
- trello)
237
- [ -n "$bid" ] && yaml_set ticket_provider.board "$bid" 2>/dev/null || true
238
- ;;
239
- linear)
240
- [ -n "$team" ] && yaml_set ticket_provider.team "$team" 2>/dev/null || true
241
- ;;
242
- esac
243
- }
244
-
245
- # ── Provider resolution ──────────────────────────────────────────────────
246
- # An existing repo board (in .project.json) wins — every agent binds to it.
247
- SOT_TYPE="$(pj ticket_provider.type)"
248
- SOT_BOARD_ID="$(pj ticket_provider.board_id)"
249
- SOT_WS="$(pj ticket_provider.workspace)"
250
- SOT_IDENT="$(pj ticket_provider.identifier)"
251
- SOT_IDENT_SOURCE="$(pj ticket_provider.identifier_source)"
252
- SOT_TEAM="$(pj ticket_provider.team)"
253
-
254
- # role.yaml provider comes from copier --data (the operator's pjangler choice).
255
- ROLE_PROVIDER="$(yaml_get ticket_provider.name)"
256
-
257
- if [ -n "$SOT_BOARD_ID" ]; then
258
- # ── BIND to the repo's existing board ──────────────────────────────────
259
- PROVIDER="${SOT_TYPE:-${ROLE_PROVIDER:-plane}}"
260
- if [ -n "$ROLE_PROVIDER" ] && [ "$ROLE_PROVIDER" != "$PROVIDER" ]; then
261
- warn "[42] requested provider '$ROLE_PROVIDER' but repo board is '$PROVIDER' (.project.json wins); binding to existing board"
262
- fi
263
- log "[42] binding $AGENT_ID to existing repo board (provider=$PROVIDER, id=$SOT_BOARD_ID)"
264
- LIVE_IDENT="$SOT_IDENT"
265
- # An identifier already sitting in .project.json is only as trustworthy as
266
- # the source recorded beside it. A record written before sources existed
267
- # carries none, and an unsourced identifier is a proposal — not a fact.
268
- IDENT_SOURCE=""
269
- [ -z "$LIVE_IDENT" ] || IDENT_SOURCE="${SOT_IDENT_SOURCE:-proposed}"
270
- # …and a `provider` stamp beside a provider that assigns no identifiers is
271
- # not evidence either. It is a stale claim from a writer that conflated
272
- # "the board is confirmed" with "the provider named this key". Correct it
273
- # on the way through rather than carrying the lie into another manifest.
274
- case "$PROVIDER" in
275
- plane|linear) ;;
276
- *) if [ "$IDENT_SOURCE" = provider ]; then IDENT_SOURCE=proposed; fi ;;
277
- esac
278
- if [ "$PROVIDER" = plane ]; then
279
- OUT="$(tp resolve)" || die "existing Plane board could not be validated"
280
- LIVE_IDENT="$(printf '%s' "$OUT" | python3 -c 'import sys,json
281
- try: print(str(json.load(sys.stdin).get("identifier") or ""))
282
- except Exception: print("")')"
283
- [ -n "$LIVE_IDENT" ] || die "existing Plane board has no authoritative live identifier"
284
- IDENT_SOURCE=provider
285
- fi
286
- mirror_to_role_yaml "$PROVIDER" "$SOT_BOARD_ID" "$SOT_WS" "$LIVE_IDENT" "$SOT_TEAM"
287
- pj_write 1 "$PROVIDER" "$SOT_BOARD_ID" "$SOT_WS" "$LIVE_IDENT" "$SOT_TEAM" "$IDENT_SOURCE"
288
- mark_done 42-ticket-provider
289
- exit 0
290
- fi
291
-
292
- # ── No repo board yet: create ONE repo-named board (no role suffix) ───────
293
- PROVIDER="${ROLE_PROVIDER:-${SOT_TYPE:-plane}}"
294
- log "[42] no board in .project.json — bootstrapping a repo board (provider: $PROVIDER)"
295
-
296
- # Identifier PROPOSAL for a brand-new board only. It is sent to the provider in
297
- # the create request and is NEVER persisted as though it were confirmed: every
298
- # write below uses LIVE_IDENT, taken from the provider's own response, carrying
299
- # the source that response earns it (`provider` where the provider mints the
300
- # identifier, `proposed` where — as with Trello — it only echoes ours back).
301
- PROPOSED_RAW=$(printf '%s' "$REPO" | tr -cd '[:alnum:]' | tr '[:lower:]' '[:upper:]')
302
- while [ ${#PROPOSED_RAW} -lt 2 ]; do PROPOSED_RAW="${PROPOSED_RAW}X"; done
303
- PROPOSED_IDENT="${SOT_IDENT:-${PROPOSED_RAW:0:4}}"
304
- # Board NAME = repo name, separators->space, title-cased. NOT display_name —
305
- # display_name carries the role suffix and must never become the board name.
306
- NAME="$(printf '%s' "$REPO" | tr '_-' ' ' | python3 -c 'import sys; print(" ".join(w[:1].upper()+w[1:] for w in sys.stdin.read().split()))')"
307
- DESC="Ticket board for $REPO"
308
-
309
- case "$PROVIDER" in
310
- linear)
311
- # Linear DOES mint an authoritative identifier — the team key — so nothing
312
- # here ever persists the local proposal. Without a live team read there is
313
- # no identifier to record, and the record simply carries none.
314
- if [[ -z "${LINEAR_API_KEY:-}" ]]; then
315
- warn "[42] LINEAR_API_KEY not set; set role.yaml/.project.json ticket_provider.team and re-run ./.scripts/42-ticket-provider.sh"
316
- pj_write 1 linear "" "" "" "$SOT_TEAM" ""
317
- mark_done 42-ticket-provider; exit 0
318
- fi
319
- OUT="$(tp resolve 2>/dev/null || true)"
320
- BID="$(printf '%s' "$OUT" | python3 -c 'import sys,json
321
- try: print(json.load(sys.stdin).get("board_id",""))
322
- except Exception: print("")')"
323
- LIVE_IDENT="$(printf '%s' "$OUT" | python3 -c 'import sys,json
324
- try: print(str(json.load(sys.stdin).get("identifier") or ""))
325
- except Exception: print("")')"
326
- if [ -n "$BID" ]; then
327
- [ -n "$LIVE_IDENT" ] || die "linear resolved a team with no authoritative key"
328
- mirror_to_role_yaml linear "$BID" "" "$LIVE_IDENT" "$SOT_TEAM"
329
- pj_write 1 linear "$BID" "" "$LIVE_IDENT" "$SOT_TEAM" provider
330
- else
331
- warn "[42] linear resolve returned no board; set ticket_provider.team and re-run"
332
- pj_write 1 linear "" "" "" "$SOT_TEAM" ""
333
- fi
334
- ;;
335
-
336
- plane|trello)
337
- KEYVAR=PLANE_API_KEY; [ "$PROVIDER" = trello ] && KEYVAR=TRELLO_KEY
338
- if [[ -z "${!KEYVAR:-}" ]]; then
339
- warn "[42] $KEYVAR not set; skipping board creation. Set creds and re-run ./.scripts/42-ticket-provider.sh"
340
- # Deferred: no board was created, so there is no confirmed identifier.
341
- # Persist nothing rather than freezing the proposal into .project.json.
342
- pj_write 1 "$PROVIDER" "" "${SOT_WS:-$PLANE_WORKSPACE}" "" "" ""
343
- mark_done 42-ticket-provider; exit 0
344
- fi
345
- OUT="$(tp create_board "$NAME" "$PROPOSED_IDENT" "$DESC")" || die "create_board failed for $PROVIDER"
346
- BID="$(printf '%s' "$OUT" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("board_id",""))')"
347
- LIVE_IDENT="$(printf '%s' "$OUT" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("identifier","") or "")')"
348
- WS="${SOT_WS:-$PLANE_WORKSPACE}"
349
- [ "$PROVIDER" = trello ] && WS=""
350
- case "$PROVIDER" in
351
- plane)
352
- # Plane mints the identifier and hands it back on create; anything else
353
- # is a guess and must not be written.
354
- [ -n "$LIVE_IDENT" ] \
355
- || die "created/bound Plane board has no authoritative live identifier"
356
- IDENT_SOURCE=provider
357
- ;;
358
- trello)
359
- # Trello mints no key. create_board echoes back the prefix it was
360
- # handed, which confirms what the board is BOUND under — not a value
361
- # Trello chose. Record it stamped as the proposal it is, so nothing
362
- # downstream can read it as provider truth.
363
- IDENT_SOURCE=proposed
364
- ;;
365
- esac
366
- mirror_to_role_yaml "$PROVIDER" "$BID" "$WS" "$LIVE_IDENT" ""
367
- pj_write 1 "$PROVIDER" "$BID" "$WS" "$LIVE_IDENT" "" "$IDENT_SOURCE"
368
- ;;
369
-
370
- *) die "unknown ticket provider: $PROVIDER (expected linear|plane|trello)" ;;
371
- esac
372
-
373
- mark_done 42-ticket-provider
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Compatibility checkpoint for the fleet-shared Bloodbank gateway contract.
3
- # shellcheck source=_lib.sh
4
- source "$(dirname "$0")/_lib.sh"
5
- load_role_env
6
-
7
- already_done 60-bloodbank && { log "[60] bloodbank fleet-routing checkpoint already complete — skipping"; exit 0; }
8
-
9
- # SKIP_BLOODBANK used to suppress per-profile consumer installation. Keep the
10
- # flag harmless for existing unattended provisioning commands; there is no
11
- # profile-local process, dependency, broker probe, or file to skip anymore.
12
- if [[ "${SKIP_BLOODBANK:-0}" == "1" ]]; then
13
- log "[60] bloodbank — SKIP_BLOODBANK accepted as a compatibility no-op"
14
- else
15
- log "[60] bloodbank — fleet-shared gateway routes target_agent_id=$AGENT_ID"
16
- fi
17
-
18
- mark_done 60-bloodbank