@delorenj/pjangler 1.1.5 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.mise/scripts/link-agentfiles.sh +9 -0
  2. package/dist/index.js +1492 -358
  3. package/dist/mcp-server.js +2391 -290
  4. package/package.json +5 -2
  5. package/templates/commonproject/.mise/scripts/create-plane-project.sh +26 -0
  6. package/templates/commonproject/copier.yml +2 -0
  7. package/templates/commonproject/template/.agents/hooks/README.md +151 -0
  8. package/templates/commonproject/template/.agents/hooks/hermes/hindsight-hook.sh +63 -0
  9. package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-journal-write.sh +327 -0
  10. package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-recall.sh +180 -0
  11. package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-retain.sh +74 -0
  12. package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-session-end.sh +57 -0
  13. package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-bank.sh +57 -0
  14. package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-journal.sh +48 -0
  15. package/templates/commonproject/template/.agents/hooks/hooks.master.json +97 -0
  16. package/templates/commonproject/template/.agents/hooks/lib/hook-guard.sh +30 -0
  17. package/templates/commonproject/template/.agents/hooks/lib/local-config.sh +51 -0
  18. package/templates/commonproject/template/.agents/hooks/reminder-for-skill-check.sh +20 -0
  19. package/templates/commonproject/template/.agents/hooks/sync.py +618 -0
  20. package/templates/commonproject/template/.agents/local.example.json +15 -0
  21. package/templates/commonproject/template/.mise/scripts/hindsight-setup.sh +62 -0
  22. package/templates/commonproject/template/.mise/scripts/link-project-skills-to-clis.sh +110 -0
  23. package/templates/commonproject/template/.mise/scripts/unlink-project-skills-from-clis.sh +45 -0
  24. package/templates/commonproject/template/mise.toml.jinja +42 -0
  25. package/templates/hermes-agent/.codegraph/daemon.pid +6 -0
  26. package/templates/hermes-agent/.omo/run-continuation/ses_0e5b28303ffeXxL53hZjKggXDW.json +10 -0
  27. package/templates/hermes-agent/README.md +16 -14
  28. package/templates/hermes-agent/copier.yml +7 -25
  29. package/templates/hermes-agent/docs/architecture.md +16 -18
  30. package/templates/hermes-agent/docs/fleet-control-plane/README.md +35 -0
  31. package/templates/hermes-agent/docs/fleet-control-plane/architecture.md +253 -0
  32. package/templates/hermes-agent/docs/fleet-control-plane/epics-and-stories.md +280 -0
  33. package/templates/hermes-agent/docs/fleet-control-plane/implementation-readiness-report-2026-06-27.md +118 -0
  34. package/templates/hermes-agent/docs/fleet-control-plane/prd.md +141 -0
  35. package/templates/hermes-agent/docs/operations.md +17 -19
  36. package/templates/hermes-agent/docs/runbooks/runtime-checkpoint-repair.md +17 -13
  37. package/templates/hermes-agent/docs/{scrum-master → sentinel}/README.md +47 -46
  38. package/templates/hermes-agent/docs/{scrum-master → sentinel}/architecture.md +39 -38
  39. package/templates/hermes-agent/docs/{scrum-master → sentinel}/development.md +63 -99
  40. package/templates/hermes-agent/docs/{scrum-master → sentinel}/providers.md +2 -2
  41. package/templates/hermes-agent/docs/sentinel.md +18 -0
  42. package/templates/hermes-agent/install-local.sh +22 -20
  43. package/templates/hermes-agent/runtime-scaffold/README.md +10 -9
  44. package/templates/hermes-agent/scripts/backfill-fleet-sot.sh +12 -21
  45. package/templates/hermes-agent/scripts/fleet-sync.sh +369 -0
  46. package/templates/hermes-agent/scripts/migrate-unify.sh +252 -0
  47. package/templates/hermes-agent/scripts/unify-pm.sh +148 -0
  48. package/templates/hermes-agent/template/.runtime-scaffold/README.md +10 -9
  49. package/templates/hermes-agent/template/.scripts/01-config.sh +2 -3
  50. package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +0 -2
  51. package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +47 -19
  52. package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +71 -27
  53. package/templates/hermes-agent/template/.scripts/30-telegram.sh +3 -3
  54. package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +3 -3
  55. package/templates/hermes-agent/template/.scripts/70-systemd.sh +48 -43
  56. package/templates/hermes-agent/template/.scripts/80-registry.sh +2 -2
  57. package/templates/hermes-agent/template/.scripts/99-summary.sh +3 -4
  58. package/templates/hermes-agent/template/.scripts/_lib.sh +4 -19
  59. package/templates/hermes-agent/template/.scripts/checkpoint.sh +36 -0
  60. package/templates/hermes-agent/template/.scripts/config.example.toml +11 -5
  61. package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.sh → heartbeat.sh} +84 -31
  62. package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +1 -1
  63. package/templates/hermes-agent/template/.scripts/providers/plane.sh +9 -3
  64. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/emit-event.py +5 -5
  65. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-autonomous-review.sh +6 -6
  66. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-close-gate.sh +2 -2
  67. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/autonomous-delegated-review.md +7 -7
  68. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/bloodbank-events.md +3 -3
  69. package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/continuous-ticket-orchestration.md +6 -6
  70. package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.prompt.md.jinja → sentinel.prompt.md.jinja} +5 -4
  71. package/templates/hermes-agent/template/SOUL.md.jinja +3 -6
  72. package/templates/hermes-agent/template/hermes.jinja +6 -15
  73. package/templates/hermes-agent/template/role.yaml.jinja +9 -8
  74. package/templates/commonproject/.claude/commands/bmad/bmb/agents/agent-builder.md +0 -14
  75. package/templates/commonproject/.claude/commands/bmad/bmb/agents/module-builder.md +0 -14
  76. package/templates/commonproject/.claude/commands/bmad/bmb/agents/workflow-builder.md +0 -14
  77. package/templates/commonproject/.claude/commands/bmad/bmb/workflows/agent.md +0 -5
  78. package/templates/commonproject/.claude/commands/bmad/bmb/workflows/module.md +0 -5
  79. package/templates/commonproject/.claude/commands/bmad/bmb/workflows/workflow.md +0 -5
  80. package/templates/commonproject/.claude/commands/bmad/bmm/agents/analyst.md +0 -14
  81. package/templates/commonproject/.claude/commands/bmad/bmm/agents/architect.md +0 -14
  82. package/templates/commonproject/.claude/commands/bmad/bmm/agents/dev.md +0 -14
  83. package/templates/commonproject/.claude/commands/bmad/bmm/agents/pm.md +0 -14
  84. package/templates/commonproject/.claude/commands/bmad/bmm/agents/quick-flow-solo-dev.md +0 -14
  85. package/templates/commonproject/.claude/commands/bmad/bmm/agents/sm.md +0 -14
  86. package/templates/commonproject/.claude/commands/bmad/bmm/agents/tea.md +0 -14
  87. package/templates/commonproject/.claude/commands/bmad/bmm/agents/tech-writer.md +0 -14
  88. package/templates/commonproject/.claude/commands/bmad/bmm/agents/ux-designer.md +0 -14
  89. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/check-implementation-readiness.md +0 -5
  90. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/code-review.md +0 -13
  91. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/correct-course.md +0 -13
  92. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-architecture.md +0 -5
  93. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-epics-and-stories.md +0 -5
  94. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-dataflow.md +0 -13
  95. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-diagram.md +0 -13
  96. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-flowchart.md +0 -13
  97. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-wireframe.md +0 -13
  98. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-product-brief.md +0 -5
  99. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-story.md +0 -13
  100. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-ux-design.md +0 -5
  101. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/dev-story.md +0 -13
  102. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/document-project.md +0 -13
  103. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/generate-project-context.md +0 -5
  104. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/prd.md +0 -5
  105. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-dev.md +0 -5
  106. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-spec.md +0 -5
  107. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/research.md +0 -5
  108. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/retrospective.md +0 -13
  109. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-planning.md +0 -13
  110. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-status.md +0 -13
  111. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-atdd.md +0 -13
  112. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-automate.md +0 -13
  113. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-ci.md +0 -13
  114. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-framework.md +0 -13
  115. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-nfr.md +0 -13
  116. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-design.md +0 -13
  117. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-review.md +0 -13
  118. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-trace.md +0 -13
  119. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-init.md +0 -13
  120. package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-status.md +0 -13
  121. package/templates/commonproject/.claude/commands/bmad/cis/agents/brainstorming-coach.md +0 -14
  122. package/templates/commonproject/.claude/commands/bmad/cis/agents/creative-problem-solver.md +0 -14
  123. package/templates/commonproject/.claude/commands/bmad/cis/agents/design-thinking-coach.md +0 -14
  124. package/templates/commonproject/.claude/commands/bmad/cis/agents/innovation-strategist.md +0 -14
  125. package/templates/commonproject/.claude/commands/bmad/cis/agents/presentation-master.md +0 -14
  126. package/templates/commonproject/.claude/commands/bmad/cis/agents/storyteller.md +0 -14
  127. package/templates/commonproject/.claude/commands/bmad/cis/workflows/design-thinking.md +0 -13
  128. package/templates/commonproject/.claude/commands/bmad/cis/workflows/innovation-strategy.md +0 -13
  129. package/templates/commonproject/.claude/commands/bmad/cis/workflows/problem-solving.md +0 -13
  130. package/templates/commonproject/.claude/commands/bmad/cis/workflows/storytelling.md +0 -13
  131. package/templates/commonproject/.claude/commands/bmad/core/agents/bmad-master.md +0 -14
  132. package/templates/commonproject/.claude/commands/bmad/core/tasks/index-docs.md +0 -9
  133. package/templates/commonproject/.claude/commands/bmad/core/tasks/shard-doc.md +0 -9
  134. package/templates/commonproject/.claude/commands/bmad/core/workflows/brainstorming.md +0 -5
  135. package/templates/commonproject/.claude/commands/bmad/core/workflows/party-mode.md +0 -5
  136. package/templates/commonproject/.claude/commands/bmad/custom/workflows/ticket-lifecycle.md +0 -5
  137. package/templates/hermes-agent/docs/scrum-master-sentinel.md +0 -17
  138. package/templates/hermes-agent/template/.scripts/75-scrum-master.sh +0 -117
  139. package/templates/hermes-agent/template/.scripts/90-chain-scrum-master.sh +0 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delorenj/pjangler",
3
- "version": "1.1.5",
3
+ "version": "1.2.2",
4
4
  "description": "Project subsystem bootstrapper CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,8 @@
12
12
  "files": [
13
13
  "dist",
14
14
  "templates",
15
- ".mise/scripts/versioning.sh"
15
+ ".mise/scripts/versioning.sh",
16
+ ".mise/scripts/link-agentfiles.sh"
16
17
  ],
17
18
  "engines": {
18
19
  "node": ">=20"
@@ -22,6 +23,7 @@
22
23
  "start": "node dist/index.js",
23
24
  "mcp": "node dist/mcp-server.js",
24
25
  "typecheck": "tsc --noEmit",
26
+ "test": "node tests/parity-migrate-regressions.mjs && node tests/mcp-catalog-regressions.mjs && node tests/mcp-server-regressions.mjs && node tests/project-registry-regressions.mjs",
25
27
  "prepublishOnly": "npm run build"
26
28
  },
27
29
  "keywords": [
@@ -37,6 +39,7 @@
37
39
  "@clack/prompts": "^1.4.0",
38
40
  "@modelcontextprotocol/sdk": "^1.29.0",
39
41
  "commander": "^14.0.2",
42
+ "yaml": "^2.9.0",
40
43
  "zod": "^4.4.3"
41
44
  },
42
45
  "devDependencies": {
@@ -59,6 +59,32 @@ PLANE_BASE_URL="${PLANE_BASE_URL:-https://plane.delo.sh}"
59
59
  echo " Identifier: $PROJECT_IDENTIFIER"
60
60
  } >&2
61
61
 
62
+ # ── Find-or-link by NAME first (repo name is the primary key) ────────────────
63
+ # Plane does NOT enforce unique project names, so a bare POST would create a
64
+ # DUPLICATE whenever a board of this name already exists (an earlier init, or
65
+ # the hermes-agent flow). Search by name and reuse the match before creating.
66
+ NAME_LIST=$(curl -s -X GET \
67
+ "${PLANE_BASE_URL}/api/v1/workspaces/${WORKSPACE}/projects/?per_page=200" \
68
+ -H "X-Api-Key: ${PLANE_API_KEY}" \
69
+ -H "Content-Type: application/json")
70
+ EXISTING_BY_NAME=$(echo "$NAME_LIST" | PLANE_NAME="$PROJECT_NAME" python3 -c "
71
+ import json, os, sys
72
+ want = os.environ['PLANE_NAME'].strip().lower()
73
+ try:
74
+ data = json.load(sys.stdin)
75
+ except Exception:
76
+ sys.exit(0)
77
+ items = data.get('results', data) if isinstance(data, dict) else data
78
+ for p in items or []:
79
+ if str(p.get('name', '')).strip().lower() == want:
80
+ print(p['id']); break
81
+ " 2>/dev/null || true)
82
+ if [[ -n "$EXISTING_BY_NAME" ]]; then
83
+ echo -e "${GREEN}✓ Linking existing Plane project by name '${PROJECT_NAME}' (id=${EXISTING_BY_NAME})${NC}" >&2
84
+ echo "$EXISTING_BY_NAME"
85
+ exit 0
86
+ fi
87
+
62
88
  # Create project via Plane API
63
89
  RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
64
90
  "${PLANE_BASE_URL}/api/v1/workspaces/${WORKSPACE}/projects/" \
@@ -83,6 +83,8 @@ primary_language:
83
83
 
84
84
  _tasks:
85
85
  - "cp ~/.config/git/ignore .gitignore 2>/dev/null || echo '# Add ignores here' > .gitignore"
86
+ # Ensure secrets + per-dev agent-hook/skill overrides are ignored (appended after the base ignore).
87
+ - "printf '\\n# Secrets + per-dev agent-hook/skill overrides (see .agents/local.example.json)\\n.env\\n.env.*\\n!.env.op\\n.agents/local.json\\n.kimi-code/\\n.lastagent\\n**/.claude/settings.local.json\\n' >> .gitignore"
86
88
  - "ln -sf AGENTS.md CLAUDE.md"
87
89
  - "ln -sf AGENTS.md GEMINI.md"
88
90
  # Stamp the repo's absolute path into .project.json (the SOT) post-render.
@@ -0,0 +1,151 @@
1
+ # Project-scoped agent hooks
2
+
3
+ Agent hooks auto-installed for **every dev** in this repo, fanned out to each
4
+ agent CLI (Claude Code, Codex, Hermes) from one source of truth — with a per-dev
5
+ opt-out and shared Hindsight credentials.
6
+
7
+ This is the [`/ssot-fanout`](https://github.com/delorenj/skillex) pattern, lean
8
+ variant: **`hooks.master.json` is the only file you hand-edit**; every per-agent
9
+ config is generated by `sync.py` and is idempotent (re-running with an unchanged
10
+ master writes zero bytes).
11
+
12
+ ## TL;DR
13
+
14
+ ```bash
15
+ mise run hooks-sync # fan out the master -> claude + codex + hermes
16
+ mise run hooks-check # drift gate (read-only; used in CI)
17
+ mise run hooks-uninstall # remove codex + hermes injections
18
+ mise run hindsight-setup # one-time: pull the shared CAF Hindsight key into .env
19
+ ```
20
+
21
+ You normally run nothing — `mise` does it on directory enter/leave (see
22
+ [mise.toml](../../mise.toml) `[hooks]`).
23
+
24
+ ## What gets installed where
25
+
26
+ | Agent | Target | Scope | When |
27
+ |---|---|---|---|
28
+ | **Claude Code** | committed `.claude/settings.json` (`hooks` key, `$CLAUDE_PROJECT_DIR`) | project, committed | nothing to do — every clone gets it |
29
+ | **Codex** | per-user `~/.codex/hooks.json` (absolute-path entries) | per-user, injected | `mise enter` → inject, `mise leave` → remove |
30
+ | **Kimi** | per-user `~/.kimi-code/config.toml` (sentinel `[[hooks]]` block) | per-user, injected | `mise enter` → inject, `mise leave` → remove |
31
+ | **Hermes** | runtime `config.yaml` `hooks:` + `shell-hooks-allowlist.json`, via an **adapter** | per-deployment | `mise enter` merges (idempotent, backed up) |
32
+
33
+ **Kimi** is fully Claude-compatible (identical event names; payload `{prompt}` /
34
+ `{tool_name, tool_input}` on stdin; `shell:true` commands; edit tools `Write`/`Edit`),
35
+ so it **reuses the same guard-wrapped scripts** as Claude/Codex — just injected as a
36
+ sentinel-bounded `[[hooks]]` block in the per-user `config.toml` (timeout in seconds;
37
+ `KIMI_CODE_HOME` respected; backed up to `config.toml.caf-bak`; byte-exact reversible).
38
+
39
+ Each injection is **surgical & reversible** — `sync.py` only touches entries
40
+ whose command points at this repo's `.agents/hooks/`, backs up before its first
41
+ write (`*.caf-bak`), and an uninstall restores the original data.
42
+
43
+ ## The hooks
44
+
45
+ | id | lifecycle → event | script | notes |
46
+ |---|---|---|---|
47
+ | `skill-check-reminder` | user prompt | `reminder-for-skill-check.sh` | project-skills + BMAD-recap nudge (path resolved relative to itself → portable) |
48
+ | `hindsight-recall` | user prompt | `hindsight/hindsight-recall.sh` | recalls memory; no-ops gracefully without the `hindsight` CLI |
49
+ | `hindsight-retain` | post-tool (`Write\|Edit\|MultiEdit`) | `hindsight/hindsight-retain.sh` | retains file edits |
50
+ | `hindsight-session-end` | session end / stop | `hindsight/hindsight-session-end.sh` | session summary + journal |
51
+
52
+ ### Hermes uses an adapter, not the raw scripts
53
+
54
+ Hermes runs hooks `shell=False` with the payload as JSON on stdin, has **no
55
+ user-prompt event**, and uses different `tool_input` keys than Claude. So the
56
+ Claude-shaped scripts would misparse. Instead Hermes calls
57
+ [`hermes/hindsight-hook.sh <event>`](hermes/hindsight-hook.sh), which translates
58
+ the Hermes payload and **pins the `CoachingAgentFramework` bank** (bank
59
+ auto-resolution from the Hermes runtime cwd would resolve to the PM *submodule*).
60
+ Wired: `post_tool_call` → retain, `on_session_end` → session marker. Recall is
61
+ not wired (no event to attach to).
62
+
63
+ ## Per-dev opt-out — `.agents/local.json`
64
+
65
+ Copy [`.agents/local.example.json`](../local.example.json) → `.agents/local.json`
66
+ (gitignored) to disable things **just for you**:
67
+
68
+ ```jsonc
69
+ {
70
+ "hooks": {
71
+ "disabled": ["hindsight-recall"], // hook ids — skipped at runtime, ALL agents
72
+ "disabled_agents": ["codex"] // claude|codex|hermes — codex/hermes injection skipped/removed
73
+ },
74
+ "skills": { "disabled": ["bmad-help"] } // skill dir names — not symlinked for you
75
+ }
76
+ ```
77
+
78
+ - **Disabled hooks** are enforced at **runtime** by
79
+ [`lib/hook-guard.sh`](lib/hook-guard.sh) — the committed Claude command is the
80
+ *guarded* form (`hook-guard.sh <id> <real-hook>`), which execs the hook unless
81
+ your `local.json` disables its id, in which case it exits 0 silently. This is
82
+ how you opt out of a hook whose config is committed and shared.
83
+ - **Disabled agents** are enforced at **install** time (codex/hermes injection is
84
+ skipped, and removed if previously installed). `CAF_HOOKS_SKIP_CODEX=1` is an
85
+ env shortcut for `disabled_agents:["codex"]`.
86
+ - **Disabled skills** are honored by the skill fan-out (below).
87
+
88
+ ## Skill fan-out — `.agents/skills` → each CLI
89
+
90
+ `.agents/skills/` is the project's enabled skill set (committed inherited skills +
91
+ `./skills/*` app skills symlinked in on enter). `link-project-skills-to-clis.sh`
92
+ fans it out as per-skill symlinks to every agent CLI that doesn't read `.agents/`
93
+ natively — a small `SKILL_TARGETS` table:
94
+
95
+ | CLI | target dir | scope |
96
+ |---|---|---|
97
+ | Codex | `~/.codex/skills` | **global** — link ours in, never clobber foreign entries, removed on `leave` |
98
+ | Kimi | `./.kimi-code/skills` | **local** — a project-scoped mirror we fully manage (stale real copies replaced with symlinks) |
99
+
100
+ Add a CLI = one `dir|scope` line. Per-dev control via `.agents/local.json`
101
+ `skills`:
102
+
103
+ - `disabled: [...]` — never link these; pruned from managed mirrors.
104
+ - `defer_to_global: true` — **only if you run a global `~/.agents/skills` system.**
105
+ Skips any project skill whose name also exists there, so your global copy wins
106
+ and you get **zero duplicates** (it even yields the slot in `~/.codex/skills`).
107
+ Teammates with no global layer leave it `false` (default) and **inherit the full
108
+ set**. Override the global dir with `AGENTS_GLOBAL_SKILLS_DIR`.
109
+
110
+ Tasks: `mise run skills-relink` (re-fan), `mise run link-project-skills-to-clis`,
111
+ `mise run unlink-project-skills-from-clis`.
112
+
113
+ ## Hindsight credentials — `mise run hindsight-setup`
114
+
115
+ The hooks shell out to `hindsight`, which needs an API key for the self-hosted
116
+ instance. Strategy: **one shared CAF-scoped key in 1Password**. `hindsight-setup`
117
+ resolves it via `op` and writes `HINDSIGHT_API_URL` + `HINDSIGHT_API_KEY` into the
118
+ gitignored `.env` (mise loads it, env vars outrank `~/.hindsight/config`). Point
119
+ it at the right item if the default ref is wrong:
120
+
121
+ ```bash
122
+ HINDSIGHT_OP_KEY_REF="op://DeLoSecrets/<item>/<field>" mise run hindsight-setup
123
+ ```
124
+
125
+ Without a key the hooks **no-op gracefully** — recall/retain just do nothing.
126
+
127
+ ## Adding / changing a hook
128
+
129
+ 1. Edit `hooks.master.json`.
130
+ 2. `mise run hooks-sync`.
131
+ 3. Commit the master **and** the regenerated `.claude/settings.json` together
132
+ (codex/hermes targets are per-dev/per-deployment, not committed).
133
+ 4. CI runs `mise run hooks-check` — fails if the committed Claude settings drift.
134
+
135
+ ## Files
136
+
137
+ ```
138
+ .agents/
139
+ local.example.json # per-dev override template (copy -> local.json, gitignored)
140
+ hooks/
141
+ hooks.master.json # SSOT — the only hand-edited config
142
+ sync.py # fan-out engine (--install / --uninstall / --check)
143
+ reminder-for-skill-check.sh
144
+ hindsight/ # recall / retain / session-end + journal writer + lib/
145
+ hermes/ # hindsight-hook.sh — the Hermes payload adapter
146
+ lib/ # local-config.sh (override helpers) + hook-guard.sh (runtime guard)
147
+ README.md
148
+ ```
149
+
150
+ `gitmark/` holds standalone git helpers — committed for convenience, **not** part
151
+ of the agent-hook fan-out.
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # Hermes -> Hindsight adapter.
3
+ #
4
+ # Hermes fires shell hooks with the payload piped as JSON on stdin:
5
+ # {"hook_event_name","tool_name","tool_input","session_id","cwd","extra"}
6
+ # and runs the command shell=False (argv), so this is invoked as:
7
+ # hindsight-hook.sh <on_session_end|post_tool_call>
8
+ #
9
+ # Why an adapter (not the Claude hindsight scripts verbatim):
10
+ # - Hermes has NO user-prompt event, so recall has no attach point here.
11
+ # - Hermes tool_input keys differ from Claude's (.file_path/.new_string).
12
+ # - resolve_bank() from the Hermes runtime cwd would resolve to the PM
13
+ # SUBMODULE, not this repo — so we PIN the CoachingAgentFramework bank.
14
+ #
15
+ # Fire-and-forget; never blocks Hermes. No-ops gracefully without the CLI.
16
+ set -uo pipefail
17
+
18
+ EVENT="${1:-}"
19
+ HERMES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # .agents/hooks/hermes
20
+ REPO_ROOT="$(cd "${HERMES_DIR}/../../.." && pwd)" # the project repo (NOT the PM submodule)
21
+
22
+ # Per-dev opt-out: reuse the SAME hook ids as the other agents so one
23
+ # local.json entry disables a hook everywhere.
24
+ # shellcheck source=../lib/local-config.sh
25
+ source "${HERMES_DIR}/../lib/local-config.sh"
26
+
27
+ HS_BIN="${HOME}/.local/bin/hindsight"
28
+ [[ -x "$HS_BIN" ]] || exit 0
29
+ command -v jq >/dev/null 2>&1 || exit 0
30
+
31
+ INPUT="$(cat 2>/dev/null || echo '{}')"
32
+ # Pin the project bank = repo basename (resolve_bank from the hermes runtime cwd
33
+ # would resolve to the PM submodule). Generic across projects; HINDSIGHT_BANK overrides.
34
+ BANK="${HINDSIGHT_BANK:-$(basename "$REPO_ROOT")}"
35
+
36
+ retain_detached() { # retain_detached <context> <text>
37
+ setsid nohup timeout 20 "$HS_BIN" memory retain "$BANK" "$2" --context "$1" \
38
+ < /dev/null > /dev/null 2>&1 &
39
+ disown 2>/dev/null || true
40
+ }
41
+
42
+ case "$EVENT" in
43
+ post_tool_call)
44
+ hook_disabled "hindsight-retain" && exit 0
45
+ tool=$(jq -r '.tool_name // "tool"' <<<"$INPUT" 2>/dev/null || echo tool)
46
+ path=$(jq -r '.tool_input.file_path // .tool_input.path // .tool_input.filename // .tool_input.file // empty' <<<"$INPUT" 2>/dev/null || echo "")
47
+ content=$(jq -r '.tool_input.new_string // .tool_input.content // .tool_input.text // empty' <<<"$INPUT" 2>/dev/null || echo "")
48
+ # Only retain file-ish edits; skip pure reads/terminal noise.
49
+ [[ -z "$path" && -z "$content" ]] && exit 0
50
+ snippet=$(printf '%s' "$content" | head -3 | tr '\n' ' ' | cut -c1-200)
51
+ retain_detached "code-edit" "Hermes ${tool} edited ${path:-<no-path>}: ${snippet}"
52
+ ;;
53
+ on_session_end)
54
+ hook_disabled "hindsight-session-end" && exit 0
55
+ sid=$(jq -r '.session_id // "unknown"' <<<"$INPUT" 2>/dev/null || echo unknown)
56
+ retain_detached "session-summary" "Hermes PM session ${sid} ended at $(date -u +%Y-%m-%dT%H:%M:%SZ)"
57
+ ;;
58
+ *)
59
+ : # unknown / unmapped event -> no-op
60
+ ;;
61
+ esac
62
+
63
+ exit 0
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env bash
2
+ # Hindsight Journal writer — invoked at session-end (Stop hook).
3
+ #
4
+ # Reads ~/.agents/journal/sessions/<session_id>.jsonl, synthesizes
5
+ # a plain-English journal entry covering the 6-section spec, and writes it to
6
+ # ~/.agents/journal/YYYY-mm-dd-h-m-s.md.
7
+ #
8
+ # Sections:
9
+ # 1. Search terms elicited & how they were selected
10
+ # 2. Why hindsight was skipped (if it was)
11
+ # 3. Bank resolved (incl. linked-bank fanout)
12
+ # 4. Effectiveness of returned data (LLM-reflected)
13
+ # 5. What could have been done differently (LLM-reflected)
14
+ # 6. What was retained — both telemetry + LLM-reflected rationale
15
+ #
16
+ # Inputs:
17
+ # - stdin: Claude Code Stop hook JSON ({session_id, transcript_path, ...})
18
+ #
19
+ # Env knobs:
20
+ # HS_JOURNAL_DIR Override journal root (default: ~/.agents/journal)
21
+ # HS_JOURNAL_REFLECT 0 to skip the LLM-reflection step (default: 1)
22
+ # HS_JOURNAL_REFLECT_BUDGET low|mid|high (default: low)
23
+ # HS_JOURNAL_REFLECT_BANK Bank to reflect into; default: bank of cwd
24
+ # HS_JOURNAL_REFLECT_DEBOUNCE_SECONDS Min seconds between reflect calls per session (default: 240).
25
+ # Claude Code's Stop hook fires on every turn, not just session-close,
26
+ # so without this we'd burn an LLM call per response.
27
+ # HS_JOURNAL_KEEP_TELEMETRY 1 to keep per-session JSONL after write; default: 1
28
+ #
29
+ # Exit codes: always 0 (fire-and-forget; hook failures must not break Stop).
30
+
31
+ set -uo pipefail
32
+
33
+ HS_BIN="${HOME}/.local/bin/hindsight"
34
+ HS_JOURNAL_DIR="${HS_JOURNAL_DIR:-${HOME}/.agents/journal}"
35
+ HS_JOURNAL_SESSIONS_DIR="${HS_JOURNAL_DIR}/sessions"
36
+ HS_JOURNAL_REFLECT="${HS_JOURNAL_REFLECT:-1}"
37
+ HS_JOURNAL_REFLECT_BUDGET="${HS_JOURNAL_REFLECT_BUDGET:-low}"
38
+ HS_JOURNAL_REFLECT_DEBOUNCE_SECONDS="${HS_JOURNAL_REFLECT_DEBOUNCE_SECONDS:-240}"
39
+ HS_JOURNAL_KEEP_TELEMETRY="${HS_JOURNAL_KEEP_TELEMETRY:-1}"
40
+
41
+ # Required tooling
42
+ command -v jq >/dev/null 2>&1 || exit 0
43
+
44
+ INPUT=$(cat 2>/dev/null || echo "{}")
45
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
46
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
47
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
48
+ [[ -z "$CWD" ]] && CWD=$(pwd)
49
+
50
+ if [[ -z "$SESSION_ID" ]]; then
51
+ exit 0
52
+ fi
53
+
54
+ LOG_FILE="${HS_JOURNAL_SESSIONS_DIR}/${SESSION_ID}.jsonl"
55
+ if [[ ! -f "$LOG_FILE" ]]; then
56
+ # No telemetry captured — nothing to journal.
57
+ exit 0
58
+ fi
59
+
60
+ # Resolve the primary bank (cwd-based, matching the recall hook's logic).
61
+ BANK_LIB="$(dirname "${BASH_SOURCE[0]}")/lib/hindsight-bank.sh"
62
+ if [[ -f "$BANK_LIB" ]]; then
63
+ # shellcheck source=lib/hindsight-bank.sh
64
+ source "$BANK_LIB"
65
+ PRIMARY_BANK=$( (cd "$CWD" 2>/dev/null && resolve_bank) || echo "general")
66
+ else
67
+ PRIMARY_BANK="general"
68
+ fi
69
+ REFLECT_BANK="${HS_JOURNAL_REFLECT_BANK:-$PRIMARY_BANK}"
70
+
71
+ mkdir -p "$HS_JOURNAL_DIR" 2>/dev/null || exit 0
72
+
73
+ # Filename: YYYY-mm-dd-h-m-s.md based on the SESSION START timestamp (earliest event
74
+ # in the JSONL), so every Stop event during the session overwrites the same file
75
+ # instead of spawning one journal per turn.
76
+ SESSION_START_UTC=$(jq -rs '
77
+ [.[] | .ts // empty] | sort | .[0] // empty
78
+ ' "$LOG_FILE" 2>/dev/null)
79
+
80
+ if [[ -n "$SESSION_START_UTC" ]]; then
81
+ # Convert UTC ISO-8601 to local time stamp like 2026-05-11-06-32-36
82
+ STAMP=$(date -d "$SESSION_START_UTC" +%Y-%m-%d-%H-%M-%S 2>/dev/null || date +%Y-%m-%d-%H-%M-%S)
83
+ else
84
+ STAMP=$(date +%Y-%m-%d-%H-%M-%S)
85
+ fi
86
+ OUT_FILE="${HS_JOURNAL_DIR}/${STAMP}.md"
87
+ REFLECT_CACHE="${HS_JOURNAL_SESSIONS_DIR}/${SESSION_ID}.reflect.txt"
88
+
89
+ # --- Compute factual summary from the telemetry log ---
90
+
91
+ # Each helper below either echoes a value, a JSON blob, or an empty string.
92
+
93
+ recall_count=$(jq -cs '[.[] | select(.event=="recall")] | length' "$LOG_FILE" 2>/dev/null || echo 0)
94
+ recall_skipped_count=$(jq -cs '[.[] | select(.event=="recall_skipped")] | length' "$LOG_FILE" 2>/dev/null || echo 0)
95
+ retain_count=$(jq -cs '[.[] | select(.event=="retain")] | length' "$LOG_FILE" 2>/dev/null || echo 0)
96
+
97
+ # Sum of result lines returned across all recall events (proxy for surfacing volume)
98
+ total_recall_lines=$(jq -cs '[.[] | select(.event=="recall") | .total_lines] | add // 0' "$LOG_FILE" 2>/dev/null || echo 0)
99
+ total_recall_chars=$(jq -cs '[.[] | select(.event=="recall") | .total_chars] | add // 0' "$LOG_FILE" 2>/dev/null || echo 0)
100
+ empty_recall_count=$(jq -cs '[.[] | select(.event=="recall" and .returned_anything==false)] | length' "$LOG_FILE" 2>/dev/null || echo 0)
101
+
102
+ # Sets of banks touched + skip reasons
103
+ banks_primary=$(jq -rs '[.[] | select(.event=="recall") | .bank] | unique | join(", ")' "$LOG_FILE" 2>/dev/null || echo "")
104
+ banks_linked=$(jq -rs '[.[] | select(.event=="recall") | .linked_banks // [] | .[]] | unique | join(", ")' "$LOG_FILE" 2>/dev/null || echo "")
105
+ skip_reasons=$(jq -rs '[.[] | select(.event=="recall_skipped") | .reason] | unique | join(", ")' "$LOG_FILE" 2>/dev/null || echo "")
106
+
107
+ # Build section-1 prompts list (the actual user prompts that elicited recalls)
108
+ prompts_list=$(jq -rs '
109
+ [.[] | select(.event=="recall" or .event=="recall_skipped")]
110
+ | to_entries
111
+ | map("- **Turn \(.key+1)** (`\(.value.event)`, len=\(.value.prompt_len)): \(.value.prompt_first120 | gsub("\n";" ") | gsub("`";"′"))")
112
+ | join("\n")
113
+ ' "$LOG_FILE" 2>/dev/null || echo "")
114
+
115
+ # Build section-3 bank breakdown
116
+ bank_breakdown=$(jq -rs '
117
+ [.[] | select(.event=="recall")]
118
+ | group_by(.bank)
119
+ | map({
120
+ bank: .[0].bank,
121
+ calls: length,
122
+ lines: (map(.total_lines) | add),
123
+ linked: (map(.linked_banks // []) | add | unique)
124
+ })
125
+ | map("- `\(.bank)` — \(.calls) call(s), \(.lines) total lines back, linked: \(if (.linked|length)==0 then "(none)" else (.linked | join(", ")) end)")
126
+ | join("\n")
127
+ ' "$LOG_FILE" 2>/dev/null || echo "")
128
+
129
+ # Build section-6a retained-fact list
130
+ retained_list=$(jq -rs '
131
+ [.[] | select(.event=="retain")]
132
+ | to_entries
133
+ | map("- `\(.value.context)` → bank `\(.value.bank)` — `\(.value.file)` (\(.value.content_len) chars edited)\n > \(.value.snippet | gsub("\n";" ") | .[0:160])")
134
+ | join("\n")
135
+ ' "$LOG_FILE" 2>/dev/null || echo "")
136
+
137
+ # --- LLM reflection for subjective sections (4, 5, 6b) ---
138
+
139
+ reflect_section=""
140
+ reflect_error=""
141
+ reflect_from_cache=0
142
+
143
+ # Reuse cached reflect output if it's fresher than the debounce window.
144
+ if [[ -f "$REFLECT_CACHE" ]]; then
145
+ cache_age=$(($(date +%s) - $(stat -c %Y "$REFLECT_CACHE" 2>/dev/null || echo 0)))
146
+ if [[ "$cache_age" -lt "$HS_JOURNAL_REFLECT_DEBOUNCE_SECONDS" ]]; then
147
+ reflect_section=$(cat "$REFLECT_CACHE" 2>/dev/null || echo "")
148
+ [[ -n "$reflect_section" ]] && reflect_from_cache=1
149
+ fi
150
+ fi
151
+
152
+ if [[ "$reflect_from_cache" -eq 0 ]] && [[ "$HS_JOURNAL_REFLECT" == "1" ]] && [[ -x "$HS_BIN" ]]; then
153
+ # Build a compact context payload describing the session.
154
+ ctx_payload=$(jq -cs --arg cwd "$CWD" --arg primary "$PRIMARY_BANK" '
155
+ {
156
+ cwd: $cwd,
157
+ primary_bank: $primary,
158
+ recall_events: [.[] | select(.event=="recall")],
159
+ recall_skipped: [.[] | select(.event=="recall_skipped")],
160
+ retain_events: [.[] | select(.event=="retain")]
161
+ }
162
+ ' "$LOG_FILE" 2>/dev/null || echo "{}")
163
+
164
+ reflect_query='Reflect on this Claude Code session, given the structured telemetry payload provided as context. Produce three short markdown subsections, each 2-4 sentences, plain English, no preamble:
165
+
166
+ ### 4. Effectiveness of returned data
167
+ Given the recall results (total_lines, returned_anything flags) and what was retained vs. what was likely needed, assess whether the recalled context plausibly saved tokens / reduced wasted exploration. Be concrete: cite line counts and the specific banks consulted. If recall returned nothing useful, say so plainly.
168
+
169
+ ### 5. What could have been done differently in past sessions
170
+ Speculate on prior-session habits — e.g. broader retains, better tagging, smaller recall budgets — that would have made *this* recall yield more relevant hits. Tie each suggestion to a concrete observation in the telemetry.
171
+
172
+ ### 6b. Why those exact words / why not references
173
+ For each retain event in the telemetry, evaluate whether the retained text reads as a self-contained fact future Claude could act on, OR whether it leans on session-local references (file paths, snippets without rationale) that will go stale. If there are no retains, write a single sentence acknowledging that.
174
+
175
+ Return ONLY the three subsections in the order shown. No top-level heading.'
176
+
177
+ reflect_raw=$("$HS_BIN" memory reflect "$REFLECT_BANK" "$reflect_query" \
178
+ --budget "$HS_JOURNAL_REFLECT_BUDGET" \
179
+ --context "$(printf '%s' "$ctx_payload" | head -c 8000)" \
180
+ --max-tokens 1500 \
181
+ --output json 2>/tmp/hs-journal-reflect.err) ||
182
+ reflect_error="reflect command failed (see /tmp/hs-journal-reflect.err)"
183
+
184
+ if [[ -n "$reflect_raw" ]]; then
185
+ reflect_section=$(printf '%s' "$reflect_raw" | jq -r '.text // empty' 2>/dev/null)
186
+ # Strip ANSI escapes defensively (hindsight pretty mode would emit them).
187
+ reflect_section=$(printf '%s' "$reflect_section" | sed -r 's/\x1b\[[0-9;]*[a-zA-Z]//g')
188
+
189
+ # Sanity-check: backend models sometimes emit tool-call stubs instead of an
190
+ # answer. If we see that pattern, drop the content and surface a clear note.
191
+ if [[ -z "$reflect_section" ]]; then
192
+ reflect_error="reflect returned empty text"
193
+ printf '%s' "$reflect_raw" >/tmp/hs-journal-reflect.out
194
+ elif printf '%s' "$reflect_section" | grep -qE '<(minimax|antml|tool):tool_call|<invoke name='; then
195
+ reflect_error="reflect backend returned a malformed tool-call stub instead of prose (raw saved to /tmp/hs-journal-reflect.out — likely a hindsight server prompt/template issue)"
196
+ printf '%s' "$reflect_section" >/tmp/hs-journal-reflect.out
197
+ reflect_section=""
198
+ elif [[ ${#reflect_section} -lt 80 ]]; then
199
+ reflect_error="reflect returned suspiciously short response (${#reflect_section} chars)"
200
+ printf '%s' "$reflect_section" >/tmp/hs-journal-reflect.out
201
+ reflect_section=""
202
+ fi
203
+ fi
204
+
205
+ # Cache a successful reflect so subsequent turns reuse it.
206
+ if [[ -n "$reflect_section" ]]; then
207
+ printf '%s' "$reflect_section" >"$REFLECT_CACHE" 2>/dev/null || true
208
+ fi
209
+ fi
210
+
211
+ # --- Compose the journal markdown ---
212
+
213
+ # Heredoc the file out. Sections with no data emit a "(none)" placeholder.
214
+ {
215
+ cat <<HEADER
216
+ ---
217
+ session_id: ${SESSION_ID}
218
+ written_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
219
+ cwd: ${CWD}
220
+ primary_bank: ${PRIMARY_BANK}
221
+ reflect_bank: ${REFLECT_BANK}
222
+ reflect_mode: $([[ "$HS_JOURNAL_REFLECT" == "1" ]] && echo "auto" || echo "off")
223
+ reflect_source: $([[ "$reflect_from_cache" -eq 1 ]] && echo "cache" || ([[ -n "$reflect_section" ]] && echo "fresh") || echo "none")
224
+ recall_events: ${recall_count}
225
+ recall_skipped: ${recall_skipped_count}
226
+ retain_events: ${retain_count}
227
+ total_recall_lines: ${total_recall_lines}
228
+ total_recall_chars: ${total_recall_chars}
229
+ ---
230
+
231
+ # Hindsight Journal — ${STAMP}
232
+
233
+ ## 1. Search terms elicited & how they were selected
234
+
235
+ The current pipeline does **not** extract discrete keywords. The full user prompt is passed verbatim to \`hindsight memory recall <bank> "<prompt>"\`, where the server-side embedding model handles semantic matching against the bank's stored chunks. Below is each prompt that *did* trigger (or was *evaluated for*) a recall this session:
236
+
237
+ HEADER
238
+
239
+ if [[ -n "$prompts_list" ]]; then
240
+ printf '%s\n\n' "$prompts_list"
241
+ else
242
+ printf '_(no prompts captured)_\n\n'
243
+ fi
244
+
245
+ cat <<SEC2
246
+ ## 2. Why hindsight was skipped (if it was)
247
+
248
+ SEC2
249
+
250
+ if [[ "$recall_skipped_count" -gt 0 ]]; then
251
+ printf 'Skipped **%s** prompt(s) this session. Reasons observed: `%s`.\n\n' \
252
+ "$recall_skipped_count" "${skip_reasons:-unknown}"
253
+ printf '%s\n' 'Skip rules currently enforced by `hindsight-recall.sh`:'
254
+ printf '%s\n' '- Prompt under 24 characters (`prompt_under_min_length`)'
255
+ printf '%s\n' '- Empty prompt body (`empty_prompt`)'
256
+ printf '%s\n\n' '- `hindsight` CLI not on PATH (`hindsight_binary_missing`)'
257
+ else
258
+ printf 'Recall was attempted on every eligible prompt — no skips this session.\n\n'
259
+ fi
260
+
261
+ cat <<SEC3
262
+ ## 3. Bank resolved
263
+
264
+ - **Primary bank** (cwd-resolved): \`${PRIMARY_BANK}\`
265
+ - **Banks touched by recall**: ${banks_primary:-_(none)_}
266
+ - **Linked banks reached via Dream fanout**: ${banks_linked:-_(none)_}
267
+
268
+ Per-bank breakdown:
269
+
270
+ SEC3
271
+
272
+ if [[ -n "$bank_breakdown" ]]; then
273
+ printf '%s\n\n' "$bank_breakdown"
274
+ else
275
+ printf '_(no recall events to break down)_\n\n'
276
+ fi
277
+
278
+ cat <<SEC6A
279
+ ## 6a. What was retained this session
280
+
281
+ SEC6A
282
+
283
+ if [[ "$retain_count" -gt 0 ]]; then
284
+ printf '%s\n\n' "$retained_list"
285
+ else
286
+ printf '_(no retain events captured — either nothing was saved, or retains were issued via paths the hooks do not observe)_\n\n'
287
+ fi
288
+
289
+ # Subjective sections
290
+ cat <<SUBJ_HEADER
291
+ ## Reflective sections (LLM-synthesized)
292
+
293
+ SUBJ_HEADER
294
+
295
+ if [[ -n "$reflect_section" ]]; then
296
+ printf '%s\n\n' "$reflect_section"
297
+ else
298
+ if [[ "$HS_JOURNAL_REFLECT" != "1" ]]; then
299
+ printf '_Reflection disabled (`HS_JOURNAL_REFLECT=0`). Run later with:_ `HS_JOURNAL_REFLECT=1 HS_JOURNAL_REFLECT_BANK=%s bash %s < <(echo %s)`\n\n' \
300
+ "$REFLECT_BANK" "$(realpath "${BASH_SOURCE[0]}" 2>/dev/null || echo "$0")" \
301
+ "$(printf '%s' "$INPUT" | jq -c .)"
302
+ elif [[ -n "$reflect_error" ]]; then
303
+ printf '_Reflection failed: %s_\n\n' "$reflect_error"
304
+ else
305
+ printf '_Reflection returned no content._\n\n'
306
+ fi
307
+ fi
308
+
309
+ cat <<FOOTER
310
+ ---
311
+
312
+ ### Raw telemetry
313
+
314
+ \`\`\`
315
+ $(cat "$LOG_FILE")
316
+ \`\`\`
317
+
318
+ _Telemetry log: \`${LOG_FILE}\`_
319
+ FOOTER
320
+ } >"$OUT_FILE" 2>/dev/null
321
+
322
+ # Optionally prune the telemetry file (default: keep it).
323
+ if [[ "$HS_JOURNAL_KEEP_TELEMETRY" != "1" ]]; then
324
+ rm -f "$LOG_FILE" 2>/dev/null || true
325
+ fi
326
+
327
+ exit 0