@delorenj/pjangler 1.3.7 → 1.4.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 (60) hide show
  1. package/.mise/scripts/link-agentfiles.sh +38 -5
  2. package/README.md +20 -0
  3. package/dist/assets/project-notebook-skill/SHA256SUMS +10 -0
  4. package/dist/assets/project-notebook-skill/SKILL.md +64 -0
  5. package/dist/assets/project-notebook-skill/agents/openai.yaml +6 -0
  6. package/dist/assets/project-notebook-skill/export-manifest.json +56 -0
  7. package/dist/assets/project-notebook-skill/hooks/claude.settings.json +26 -0
  8. package/dist/assets/project-notebook-skill/hooks/hooks.master.json +26 -0
  9. package/dist/assets/project-notebook-skill/hooks/session-end.sh +228 -0
  10. package/dist/assets/project-notebook-skill/hooks/session-start.sh +228 -0
  11. package/dist/assets/project-notebook-skill/references/configuration.md +93 -0
  12. package/dist/assets/project-notebook-skill/references/recovery.md +54 -0
  13. package/dist/assets/project-notebook-skill/scripts/project-hooks.py +865 -0
  14. package/dist/assets/project-notebook-skill/tests/test_project_hooks.py +848 -0
  15. package/dist/index.js +16982 -8532
  16. package/dist/index.js.map +7 -0
  17. package/dist/mcp-server.js +14107 -8089
  18. package/dist/mcp-server.js.map +7 -0
  19. package/dist/prompt.js +2 -1
  20. package/dist/prompt.js.map +7 -0
  21. package/package.json +9 -4
  22. package/templates/commonproject/copier.yml +14 -5
  23. package/templates/commonproject/template/.mise/scripts/link-agentfiles.sh +38 -5
  24. package/templates/commonproject/template/.mise/scripts/provision-packs.py +60 -2
  25. package/templates/commonproject/template/.mise/scripts/sync-skills.py +479 -24
  26. package/templates/commonproject/template/mise.toml.jinja +12 -6
  27. package/templates/hermes-agent/copier.yml +16 -3
  28. package/templates/hermes-agent/template/.gitignore.jinja +1 -0
  29. package/templates/hermes-agent/template/.runtime-scaffold/memories/MEMORY.md +7 -4
  30. package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +88 -94
  31. package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +44 -21
  32. package/templates/hermes-agent/template/.scripts/30-telegram.sh +182 -171
  33. package/templates/hermes-agent/template/.scripts/31-slack.sh +260 -165
  34. package/templates/hermes-agent/template/.scripts/40-plane.sh +45 -36
  35. package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +210 -41
  36. package/templates/hermes-agent/template/.scripts/70-systemd.sh +140 -22
  37. package/templates/hermes-agent/template/.scripts/80-registry.sh +42 -6
  38. package/templates/hermes-agent/template/.scripts/99-summary.sh +69 -16
  39. package/templates/hermes-agent/template/.scripts/_lib.sh +827 -10
  40. package/templates/hermes-agent/template/.scripts/channel-transaction.py +2340 -0
  41. package/templates/hermes-agent/template/.scripts/config.example.toml +8 -2
  42. package/templates/hermes-agent/template/.scripts/credential-launch.sh +5 -1
  43. package/templates/hermes-agent/template/.scripts/heartbeat.sh +2 -3
  44. package/templates/hermes-agent/template/.scripts/lib/parse-fleet-env.py +29 -2
  45. package/templates/hermes-agent/template/.scripts/lib/profile-config-lock.py +182 -0
  46. package/templates/hermes-agent/template/.scripts/lib/profile-config-seed.py +108 -0
  47. package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +9 -0
  48. package/templates/hermes-agent/template/.scripts/lib/voice-config.py +546 -0
  49. package/templates/hermes-agent/template/.scripts/providers/linear.sh +24 -1
  50. package/templates/hermes-agent/template/.scripts/providers/plane.sh +72 -10
  51. package/templates/hermes-agent/template/.scripts/providers/trello.sh +25 -2
  52. package/templates/hermes-agent/template/.scripts/sentinel/bin/issue-autonomous-review.sh +5 -16
  53. package/templates/hermes-agent/template/.scripts/sentinel/bin/issue-close-gate.sh +2 -16
  54. package/templates/hermes-agent/template/.scripts/sentinel/docs/autonomous-delegated-review.md +13 -19
  55. package/templates/hermes-agent/template/.scripts/sentinel/docs/bloodbank-events.md +29 -36
  56. package/templates/hermes-agent/template/.scripts/sentinel.prompt.md.jinja +7 -8
  57. package/templates/hermes-agent/template/.scripts/store-onepassword-secret.py +260 -0
  58. package/templates/hermes-agent/template/SOUL.md.jinja +14 -16
  59. package/templates/hermes-agent/template/hermes.jinja +1 -1
  60. package/templates/hermes-agent/template/role.yaml.jinja +11 -4
@@ -40,8 +40,10 @@ python3 - "$REGISTRY_FILE" "$AGENT_ID" "$REPO" "$ROLE" "$DISPLAY_NAME" \
40
40
  "$PLANE_WORKSPACE" "$PLANE_PROJECT_ID" "$(yaml_get plane.identifier)" \
41
41
  "$RUNTIME_REPO" "$HERMES_BIN" "$HERMES_AGENT_REPO" "$HERMES_RUNTIME_GIT_URL" \
42
42
  "$HERMES_RUNTIME_GIT_REF" "$HERMES_RUNTIME_GIT_SHA" "$FLEET_ENV" \
43
- "hermes-${AGENT_ID}-gateway.service" "hermes-${AGENT_ID}-heartbeat.timer" <<'PYEOF'
43
+ "hermes-${AGENT_ID}-gateway.service" "hermes-${AGENT_ID}-heartbeat.timer" \
44
+ "$(yaml_get service_state.gateway)" "$(yaml_get service_state.heartbeat)" <<'PYEOF'
44
45
  import datetime
46
+ import copy
45
47
  import errno
46
48
  import os
47
49
  import pathlib
@@ -56,11 +58,17 @@ except ImportError:
56
58
  slack_status, slack_team_id, slack_team_name, slack_user_id, slack_bot_id,
57
59
  slack_username, bloodbank_enabled, bloodbank_scope, bloodbank_target, plane_ws, plane_id,
58
60
  plane_ident, runtime_repo, hermes_bin, hermes_repo, hermes_git_url,
59
- hermes_git_ref, hermes_git_sha, fleet_env, gw, heartbeat) = sys.argv[1:33]
61
+ hermes_git_ref, hermes_git_sha, fleet_env, gw, heartbeat,
62
+ gateway_state, heartbeat_state) = sys.argv[1:35]
60
63
  p = pathlib.Path(path)
61
64
  if p.is_symlink():
62
65
  raise SystemExit(f"refusing to update registry symlink: {p}")
63
- data = yaml.safe_load(p.read_text()) or {"schema_version": 1, "agents": {}}
66
+ data = yaml.safe_load(p.read_text(encoding="utf-8")) or {"schema_version": 1, "agents": {}}
67
+ if not isinstance(data, dict):
68
+ raise SystemExit("fleet registry root must be a mapping")
69
+ agents = data.setdefault("agents", {})
70
+ if not isinstance(agents, dict):
71
+ raise SystemExit("fleet registry agents must be a mapping")
64
72
  if bloodbank_enabled == "":
65
73
  bloodbank_enabled_value = False
66
74
  elif bloodbank_enabled == "true":
@@ -69,7 +77,13 @@ elif bloodbank_enabled == "false":
69
77
  bloodbank_enabled_value = False
70
78
  else:
71
79
  raise SystemExit("bloodbank.enabled must be the strict YAML boolean true or false")
72
- data.setdefault("agents", {})[agent_id] = {
80
+ existing = agents.get(agent_id, {})
81
+ if not isinstance(existing, dict):
82
+ raise SystemExit(f"fleet registry entry for {agent_id} must be a mapping")
83
+ provisioned_at = existing.get("provisioned_at")
84
+ if not isinstance(provisioned_at, str) or not provisioned_at:
85
+ provisioned_at = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
86
+ managed = {
73
87
  "repo": repo, "role": role, "display_name": display,
74
88
  "project_path": project, "role_dir": role_dir,
75
89
  "profile_name": profile,
@@ -101,9 +115,31 @@ data.setdefault("agents", {})[agent_id] = {
101
115
  "git_sha": hermes_git_sha,
102
116
  "fleet_env": fleet_env,
103
117
  },
104
- "systemd": {"gateway_unit": gw, "heartbeat_timer": heartbeat},
105
- "provisioned_at": datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z"),
118
+ "systemd": {
119
+ "gateway_unit": gw,
120
+ "heartbeat_timer": heartbeat,
121
+ "gateway_state": gateway_state,
122
+ "heartbeat_state": heartbeat_state,
123
+ },
124
+ "provisioned_at": provisioned_at,
106
125
  }
126
+
127
+ def merge_managed(current, update):
128
+ result = copy.deepcopy(current)
129
+ for key, value in update.items():
130
+ if isinstance(value, dict) and isinstance(result.get(key), dict):
131
+ result[key] = merge_managed(result[key], value)
132
+ else:
133
+ result[key] = copy.deepcopy(value)
134
+ return result
135
+
136
+ entry = merge_managed(existing, managed)
137
+ # This is retired managed schema, not extension metadata. Keeping it would
138
+ # falsely advertise a second per-agent Bloodbank execution path.
139
+ systemd = entry.get("systemd")
140
+ if isinstance(systemd, dict):
141
+ systemd.pop("consumer_unit", None)
142
+ agents[agent_id] = entry
107
143
  rendered = yaml.safe_dump(data, sort_keys=False)
108
144
  p.parent.mkdir(parents=True, exist_ok=True)
109
145
 
@@ -1,32 +1,85 @@
1
1
  #!/usr/bin/env bash
2
- # Print the final summary and next steps.
2
+ # Print a reconciliation summary derived from durable role/service state.
3
3
  # shellcheck source=_lib.sh
4
4
  source "$(dirname "$0")/_lib.sh"
5
5
  load_role_env
6
6
 
7
7
  GW_UNIT="hermes-${AGENT_ID}-gateway.service"
8
8
  HB_TIMER="hermes-${AGENT_ID}-heartbeat.timer"
9
+ GW_STATE="$(yaml_get service_state.gateway)"
10
+ HB_STATE="$(yaml_get service_state.heartbeat)"
11
+ TELEGRAM_STATE="$(yaml_get telegram.provisioning_status)"
12
+ SLACK_STATE="$(yaml_get slack.provisioning_status)"
13
+ RECONCILE_STATE="$(yaml_get reconcile.enabled)"
14
+ RECONCILE_OPT_OUT="$(yaml_get reconcile.explicit_opt_out)"
15
+ BOARD_ID="$(yaml_get ticket_provider.board_id)"
16
+
17
+ # Reconcile durable claims with live unit truth at presentation time. A stale
18
+ # role.yaml must never turn an exited launcher or failed heartbeat into an
19
+ # operational summary.
20
+ if [[ "$HB_STATE" == "active" || "$GW_STATE" =~ ^(active|deferred)$ ]]; then
21
+ if systemd_user_available; then
22
+ if [[ "$HB_STATE" == "active" ]]; then
23
+ hb_live="$(systemd_timer_health_snapshot "$HB_TIMER" "hermes-${AGENT_ID}-heartbeat.service")"
24
+ [[ "$hb_live" == ok\|* ]] || HB_STATE="error (stale active claim)"
25
+ fi
26
+ if [[ "$GW_STATE" == "active" ]]; then
27
+ gw_live="$(systemd_service_health_snapshot "$GW_UNIT" running)"
28
+ [[ "$gw_live" == ok\|* ]] || GW_STATE="error (stale active claim)"
29
+ elif [[ "$GW_STATE" == "deferred" ]]; then
30
+ gw_live="$(systemd_gateway_deferred_snapshot "$GW_UNIT")"
31
+ [[ "$gw_live" == "ok|deferred" ]] || GW_STATE="error (stale deferred claim)"
32
+ fi
33
+ else
34
+ [[ "$HB_STATE" != "active" ]] || HB_STATE="unverified (user manager unavailable)"
35
+ [[ "$GW_STATE" != "active" && "$GW_STATE" != "deferred" ]] \
36
+ || GW_STATE="unverified (user manager unavailable)"
37
+ fi
38
+ fi
39
+
40
+ case "$HB_STATE:$GW_STATE" in
41
+ active:active) MODE="OPERATIONAL" ;;
42
+ active:deferred) MODE="OPERATIONAL_WITH_GATEWAY_DEFERRED" ;;
43
+ installed:installed|installed:deferred) MODE="INSTALLED_NOT_ACTIVE" ;;
44
+ *) MODE="INCOMPLETE" ;;
45
+ esac
9
46
 
10
47
  cat >&2 <<EOF
11
48
 
12
- ╭─ Provisioned: $AGENT_ID ───────────────────────────────────────────────╮
49
+ ╭─ Hermes deployment: $AGENT_ID ───────────────────────────────────────╮
13
50
  │
14
- │ Talk: @$BOT_HANDLE (Telegram DM)
15
- │ Shell: $ROLE_DIR/hermes chat "status"
16
- │ Board: $PLANE_BASE/$PLANE_WORKSPACE/projects/$(cat "$ROLE_DIR/.scripts/.plane-project-id" 2>/dev/null || echo "(skipped)")
17
- │ Runtime: $ROLE_DIR/runtime (pure-local, ignored)
18
- │ Hermes bin: $HERMES_BIN
19
- │ Fleet env: $FLEET_ENV
51
+ │ Mode: $MODE
52
+ │ Gateway: $GW_STATE ($GW_UNIT)
53
+ │ Heartbeat: $HB_STATE ($HB_TIMER)
54
+ │ Reconciliation: $RECONCILE_STATE (explicit opt-out: ${RECONCILE_OPT_OUT:-false})
55
+ │ Telegram: $TELEGRAM_STATE
56
+ │ Slack: $SLACK_STATE
57
+ │ Board: ${BOARD_ID:-deferred}
58
+ │ Runtime: $ROLE_DIR/runtime (pure-local, ignored)
59
+ │ Hermes bin: $HERMES_BIN
60
+ │ Fleet env: $FLEET_ENV
20
61
  │
21
- │ Start fleet daemons:
22
- │ systemctl --user start $GW_UNIT
23
- │ systemctl --user start $HB_TIMER
24
- │
25
- │ Tail logs:
26
- │ journalctl --user -fu $GW_UNIT
62
+ EOF
63
+
64
+ if [[ "$TELEGRAM_STATE" == "verified" ]]; then
65
+ printf '│ Talk: @%s (Telegram DM)\n' "$BOT_HANDLE" >&2
66
+ fi
67
+ if [[ "$SLACK_STATE" == "verified" ]]; then
68
+ printf '│ Slack bot: %s\n' "$(yaml_get slack.bot_username)" >&2
69
+ fi
70
+ printf '│ Shell: %s/hermes chat "status"\n' "$ROLE_DIR" >&2
71
+
72
+ if [[ "$GW_STATE" == "deferred" ]]; then
73
+ cat >&2 <<EOF
27
74
  │
28
- │ Fleet status:
29
- │ python3 -c "import yaml,pathlib; print(yaml.safe_dump(yaml.safe_load(pathlib.Path('$REGISTRY_FILE').read_text())['agents']))"
75
+ │ Gateway activation is deferred. Store and verify a dedicated Telegram or
76
+ │ Slack credential through the provisioner; do not start $GW_UNIT manually.
77
+ EOF
78
+ elif [[ "$GW_STATE" == "active" ]]; then
79
+ printf '│ Gateway log: journalctl --user -fu %s\n' "$GW_UNIT" >&2
80
+ fi
81
+
82
+ cat >&2 <<'EOF'
30
83
  │
31
84
  ╰────────────────────────────────────────────────────────────────────────╯
32
85