@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.
- package/.mise/scripts/link-agentfiles.sh +9 -0
- package/dist/index.js +1492 -358
- package/dist/mcp-server.js +2391 -290
- package/package.json +5 -2
- package/templates/commonproject/.mise/scripts/create-plane-project.sh +26 -0
- package/templates/commonproject/copier.yml +2 -0
- package/templates/commonproject/template/.agents/hooks/README.md +151 -0
- package/templates/commonproject/template/.agents/hooks/hermes/hindsight-hook.sh +63 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-journal-write.sh +327 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-recall.sh +180 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-retain.sh +74 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/hindsight-session-end.sh +57 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-bank.sh +57 -0
- package/templates/commonproject/template/.agents/hooks/hindsight/lib/hindsight-journal.sh +48 -0
- package/templates/commonproject/template/.agents/hooks/hooks.master.json +97 -0
- package/templates/commonproject/template/.agents/hooks/lib/hook-guard.sh +30 -0
- package/templates/commonproject/template/.agents/hooks/lib/local-config.sh +51 -0
- package/templates/commonproject/template/.agents/hooks/reminder-for-skill-check.sh +20 -0
- package/templates/commonproject/template/.agents/hooks/sync.py +618 -0
- package/templates/commonproject/template/.agents/local.example.json +15 -0
- package/templates/commonproject/template/.mise/scripts/hindsight-setup.sh +62 -0
- package/templates/commonproject/template/.mise/scripts/link-project-skills-to-clis.sh +110 -0
- package/templates/commonproject/template/.mise/scripts/unlink-project-skills-from-clis.sh +45 -0
- package/templates/commonproject/template/mise.toml.jinja +42 -0
- package/templates/hermes-agent/.codegraph/daemon.pid +6 -0
- package/templates/hermes-agent/.omo/run-continuation/ses_0e5b28303ffeXxL53hZjKggXDW.json +10 -0
- package/templates/hermes-agent/README.md +16 -14
- package/templates/hermes-agent/copier.yml +7 -25
- package/templates/hermes-agent/docs/architecture.md +16 -18
- package/templates/hermes-agent/docs/fleet-control-plane/README.md +35 -0
- package/templates/hermes-agent/docs/fleet-control-plane/architecture.md +253 -0
- package/templates/hermes-agent/docs/fleet-control-plane/epics-and-stories.md +280 -0
- package/templates/hermes-agent/docs/fleet-control-plane/implementation-readiness-report-2026-06-27.md +118 -0
- package/templates/hermes-agent/docs/fleet-control-plane/prd.md +141 -0
- package/templates/hermes-agent/docs/operations.md +17 -19
- package/templates/hermes-agent/docs/runbooks/runtime-checkpoint-repair.md +17 -13
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/README.md +47 -46
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/architecture.md +39 -38
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/development.md +63 -99
- package/templates/hermes-agent/docs/{scrum-master → sentinel}/providers.md +2 -2
- package/templates/hermes-agent/docs/sentinel.md +18 -0
- package/templates/hermes-agent/install-local.sh +22 -20
- package/templates/hermes-agent/runtime-scaffold/README.md +10 -9
- package/templates/hermes-agent/scripts/backfill-fleet-sot.sh +12 -21
- package/templates/hermes-agent/scripts/fleet-sync.sh +369 -0
- package/templates/hermes-agent/scripts/migrate-unify.sh +252 -0
- package/templates/hermes-agent/scripts/unify-pm.sh +148 -0
- package/templates/hermes-agent/template/.runtime-scaffold/README.md +10 -9
- package/templates/hermes-agent/template/.scripts/01-config.sh +2 -3
- package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +0 -2
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +47 -19
- package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +71 -27
- package/templates/hermes-agent/template/.scripts/30-telegram.sh +3 -3
- package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +3 -3
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +48 -43
- package/templates/hermes-agent/template/.scripts/80-registry.sh +2 -2
- package/templates/hermes-agent/template/.scripts/99-summary.sh +3 -4
- package/templates/hermes-agent/template/.scripts/_lib.sh +4 -19
- package/templates/hermes-agent/template/.scripts/checkpoint.sh +36 -0
- package/templates/hermes-agent/template/.scripts/config.example.toml +11 -5
- package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.sh → heartbeat.sh} +84 -31
- package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +1 -1
- package/templates/hermes-agent/template/.scripts/providers/plane.sh +9 -3
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/emit-event.py +5 -5
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-autonomous-review.sh +6 -6
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/bin/issue-close-gate.sh +2 -2
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/autonomous-delegated-review.md +7 -7
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/bloodbank-events.md +3 -3
- package/templates/hermes-agent/template/.scripts/{scrum-master → sentinel}/docs/continuous-ticket-orchestration.md +6 -6
- package/templates/hermes-agent/template/.scripts/{scrum-master/continuous-ticket-sentinel.prompt.md.jinja → sentinel.prompt.md.jinja} +5 -4
- package/templates/hermes-agent/template/SOUL.md.jinja +3 -6
- package/templates/hermes-agent/template/hermes.jinja +6 -15
- package/templates/hermes-agent/template/role.yaml.jinja +9 -8
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/agent-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/module-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/agents/workflow-builder.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/agent.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/module.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmb/workflows/workflow.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/analyst.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/architect.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/dev.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/pm.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/quick-flow-solo-dev.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/sm.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/tea.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/tech-writer.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/agents/ux-designer.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/check-implementation-readiness.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/code-review.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/correct-course.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-architecture.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-epics-and-stories.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-dataflow.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-diagram.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-flowchart.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-excalidraw-wireframe.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-product-brief.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-story.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/create-ux-design.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/dev-story.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/document-project.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/generate-project-context.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/prd.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-dev.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/quick-spec.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/research.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/retrospective.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-planning.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/sprint-status.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-atdd.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-automate.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-ci.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-framework.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-nfr.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-design.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-test-review.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/testarch-trace.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-init.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/bmm/workflows/workflow-status.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/agents/brainstorming-coach.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/creative-problem-solver.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/design-thinking-coach.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/innovation-strategist.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/presentation-master.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/agents/storyteller.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/design-thinking.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/innovation-strategy.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/problem-solving.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/cis/workflows/storytelling.md +0 -13
- package/templates/commonproject/.claude/commands/bmad/core/agents/bmad-master.md +0 -14
- package/templates/commonproject/.claude/commands/bmad/core/tasks/index-docs.md +0 -9
- package/templates/commonproject/.claude/commands/bmad/core/tasks/shard-doc.md +0 -9
- package/templates/commonproject/.claude/commands/bmad/core/workflows/brainstorming.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/core/workflows/party-mode.md +0 -5
- package/templates/commonproject/.claude/commands/bmad/custom/workflows/ticket-lifecycle.md +0 -5
- package/templates/hermes-agent/docs/scrum-master-sentinel.md +0 -17
- package/templates/hermes-agent/template/.scripts/75-scrum-master.sh +0 -117
- 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.
|
|
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
|