agent-harness-kit 0.11.0 → 0.11.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +13 -5
- package/package.json +3 -2
- package/src/templates/.claude/keybindings.json.example +20 -0
- package/src/templates/.claude/skills/setup-nightly-eval/SKILL.md +118 -0
- package/src/templates/docs/env-vars.md +54 -0
- package/src/templates/docs/memory-cheatsheet.md +82 -0
- package/src/templates/scripts/_lib/jp.sh +53 -0
- package/src/templates/scripts/_lib/telemetry.sh +45 -0
- package/src/templates/scripts/notify-on-block.sh.hbs +6 -23
- package/src/templates/scripts/pre-compact.sh.hbs +2 -20
- package/src/templates/scripts/pre-push.sh +2 -20
- package/src/templates/scripts/precompletion-checklist.sh.hbs +5 -31
- package/src/templates/scripts/pretooluse-bash-guard.sh.hbs +2 -20
- package/src/templates/scripts/pretooluse-edit-guard.sh.hbs +2 -14
- package/src/templates/scripts/session-end.sh.hbs +2 -14
- package/src/templates/scripts/session-start.sh.hbs +2 -20
- package/src/templates/scripts/statusline.mjs +42 -31
- package/src/templates/scripts/structural-test-on-edit.sh.hbs +2 -14
- package/src/templates/scripts/subagent-stop.sh.hbs +7 -18
- package/src/templates/scripts/telemetry-on-skill.sh +14 -20
- package/src/templates/scripts/userprompt-guard.sh.hbs +2 -20
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"source": {
|
|
12
12
|
"source": "github",
|
|
13
13
|
"repo": "tuanle96/agent-harness-kit",
|
|
14
|
-
"ref": "v0.11.
|
|
14
|
+
"ref": "v0.11.2"
|
|
15
15
|
},
|
|
16
|
-
"version": "0.11.
|
|
16
|
+
"version": "0.11.2",
|
|
17
17
|
"description": "Solo-dev harness engineering kit — layered architecture, GC ritual, structural tests, review subagents.",
|
|
18
18
|
"category": "development",
|
|
19
19
|
"keywords": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-harness-kit",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Solo-dev harness engineering kit — layered architecture, garbage-collection ritual, structural tests, review subagents. Optimized for Claude Code 2.1+.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Tuan Le"
|
package/README.md
CHANGED
|
@@ -33,9 +33,11 @@ Option B: install as a Claude Code plugin
|
|
|
33
33
|
|
|
34
34
|
## What ships
|
|
35
35
|
|
|
36
|
-
-
|
|
37
|
-
`
|
|
38
|
-
`
|
|
36
|
+
- 17 skills (`add-adr`, `add-feature`, `debug-flow`, `deliver-html`,
|
|
37
|
+
`doc-drift-scan`, `eval-runner`, `garbage-collection`, `i18n-add-locale`,
|
|
38
|
+
`inspect-app`, `inspect-module`, `map-domain`,
|
|
39
|
+
`propose-harness-improvement`, `refactor-feature`, `review-this-pr`,
|
|
40
|
+
`setup-nightly-eval`, `structural-test-author`, `write-skill`)
|
|
39
41
|
- 5 read-only review subagents (`architecture-reviewer`, `security-reviewer`,
|
|
40
42
|
`reliability-reviewer`, `performance-reviewer`, `api-consistency-reviewer`)
|
|
41
43
|
- 1 PostToolUse hook (structural-test on every edit) + 1 Stop hook
|
|
@@ -60,6 +62,12 @@ Option B: install as a Claude Code plugin
|
|
|
60
62
|
| `/doc-drift-scan` | Find stale path/command references in `docs/` |
|
|
61
63
|
| `/debug-flow` | Run the failing flow before fixing it |
|
|
62
64
|
| `/deliver-html` | Ship an analysis/audit/plan as a self-contained HTML |
|
|
65
|
+
| `/i18n-add-locale <code>` | Scaffold a new translation locale for skills + CLAUDE.md |
|
|
66
|
+
| `/inspect-app` | Boot dev server + drive the failing flow before edits |
|
|
67
|
+
| `/map-domain` | Render layer config + flag config-vs-filesystem drift |
|
|
68
|
+
| `/refactor-feature` | Restructure steps in `feature_list.json` with proof gate |
|
|
69
|
+
| `/review-this-pr` | Deterministic diff review against the current base |
|
|
70
|
+
| `/setup-nightly-eval` | Enable the nightly eval GitHub Actions workflow |
|
|
63
71
|
|
|
64
72
|
## Philosophy (5 axioms)
|
|
65
73
|
|
|
@@ -104,7 +112,7 @@ your-repo/
|
|
|
104
112
|
├── harness.config.json
|
|
105
113
|
├── .claude/
|
|
106
114
|
│ ├── settings.json
|
|
107
|
-
│ ├── skills/ #
|
|
115
|
+
│ ├── skills/ # 17 skills as SKILL.md files
|
|
108
116
|
│ ├── agents/ # 5 reviewer personas
|
|
109
117
|
│ └── hooks/hooks.json
|
|
110
118
|
├── .harness/
|
|
@@ -159,7 +167,7 @@ agent-harness-kit --version
|
|
|
159
167
|
What this kit **does** differentiate from bare claude-cli (anecdotal + design-level):
|
|
160
168
|
|
|
161
169
|
- Opinionated CLAUDE.md template (50–80 lines) so context isn't blown on style
|
|
162
|
-
-
|
|
170
|
+
- 17 skills (`/add-feature`, `/garbage-collection`, `/propose-harness-improvement`, …) that codify Hashimoto/OpenAI rituals
|
|
163
171
|
- 5 read-only review subagents for cheap second-opinion passes
|
|
164
172
|
- `feature_list.json` + ADR template + GC ritual for solo-scale planning hygiene
|
|
165
173
|
- Solo-dev cost defaults (~$2/day) and per-run budget enforcement
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-harness-kit",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Solo-dev harness engineering kit for Claude Code. Layered architecture, structural tests, garbage-collection ritual, review subagents — without the enterprise overhead.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"lint": "echo 'no-op (kit is plain ESM JS)'",
|
|
43
43
|
"selftest": "node bin/cli.mjs --version",
|
|
44
44
|
"harness:eval": "node src/templates/_adapter-typescript/harness/eval-runner.mjs",
|
|
45
|
-
"harness:check": "node scripts/kit-structural-check.mjs"
|
|
45
|
+
"harness:check": "node scripts/kit-structural-check.mjs",
|
|
46
|
+
"check:skill-count": "node scripts/check-skill-count.mjs"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"@inquirer/prompts": "^7.0.0",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
|
|
3
|
+
"$docs": "https://code.claude.com/docs/en/keybindings",
|
|
4
|
+
"$comment": "agent-harness-kit sample. RENAME to ~/.claude/keybindings.json after editing. Claude Code's keybinding action list is FIXED — there is no action that runs a slash command (no `chat:runCommand` or similar), so we cannot bind keys to /gc, /add-feature, etc. The bindings below tune the chat workflow only. To run a slash command, type `/` and use autocomplete — that is the supported UX.",
|
|
5
|
+
"bindings": [
|
|
6
|
+
{
|
|
7
|
+
"context": "Chat",
|
|
8
|
+
"bindings": {
|
|
9
|
+
"ctrl+e": "chat:externalEditor"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"context": "Global",
|
|
14
|
+
"bindings": {
|
|
15
|
+
"ctrl+shift+t": "app:toggleTodos",
|
|
16
|
+
"ctrl+shift+r": "app:toggleTranscript"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup-nightly-eval
|
|
3
|
+
description: Use this skill when the user wants to schedule the harness eval to run every night, asks "how do I set up nightly evals", "schedule the eval", "run evals on a cron", or "nightly regression for the harness". The kit already ships a GitHub Actions workflow at .github/workflows/eval-nightly.yml — this skill walks the user through enabling it (secret setup, smoke run via workflow_dispatch, verifying the first scheduled run). Do NOT use this skill to "remind me to run eval every night in this Claude session" — that is the /loop skill or CronCreate (which is session-only), a different concern.
|
|
4
|
+
allowed-tools: Read, Bash(gh:*), Bash(ls:*), Bash(cat:*), Bash(test:*)
|
|
5
|
+
suggested-turns: 4
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Background — why GitHub Actions, not CronCreate
|
|
9
|
+
|
|
10
|
+
A common request is "use CronCreate to run the eval every night". That
|
|
11
|
+
does not do what the user wants:
|
|
12
|
+
|
|
13
|
+
- `CronCreate` jobs live only in the **current Claude Code session**.
|
|
14
|
+
Closing the REPL deletes them. Auto-expire after 7 days regardless.
|
|
15
|
+
- Jobs only fire while the REPL is **idle**, not when the laptop is
|
|
16
|
+
asleep or off.
|
|
17
|
+
- They run *Claude turns*, which spend tokens for every fire.
|
|
18
|
+
|
|
19
|
+
For a real nightly cadence ("runs at 6am whether I'm at the keyboard or
|
|
20
|
+
not"), the right substrate is OS-level cron / launchd / GitHub Actions.
|
|
21
|
+
This kit ships a GitHub Actions workflow as the default because:
|
|
22
|
+
|
|
23
|
+
1. No local daemon to babysit.
|
|
24
|
+
2. Free for public repos and within the free tier for most private ones.
|
|
25
|
+
3. Results land in workflow artifacts — visible from anywhere.
|
|
26
|
+
|
|
27
|
+
## When to use
|
|
28
|
+
|
|
29
|
+
Trigger phrases (English / Vietnamese):
|
|
30
|
+
|
|
31
|
+
- "set up nightly eval" / "lập lịch eval mỗi đêm"
|
|
32
|
+
- "schedule the harness eval"
|
|
33
|
+
- "make the eval run on a cron"
|
|
34
|
+
- "nightly regression for the harness"
|
|
35
|
+
|
|
36
|
+
Do **NOT** invoke for:
|
|
37
|
+
|
|
38
|
+
- One-off ad-hoc eval runs — use `/eval-runner` directly.
|
|
39
|
+
- In-session polling ("re-run every 10 min until I say stop") — that's
|
|
40
|
+
the `/loop` skill.
|
|
41
|
+
- Local-machine cron setup (launchd / crontab) — that path is on the
|
|
42
|
+
user's machine and a skill cannot install OS daemons. Print the
|
|
43
|
+
recipe and let them paste it.
|
|
44
|
+
|
|
45
|
+
## Steps
|
|
46
|
+
|
|
47
|
+
1. **Verify the workflow file exists.** It ships via `installCi: true`.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
test -f .github/workflows/eval-nightly.yml && echo OK || echo MISSING
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If MISSING: the user opted out of CI files at scaffold time. Tell
|
|
54
|
+
them to re-run `agent-harness-kit upgrade --ci` (or to manually copy
|
|
55
|
+
from `node_modules/agent-harness-kit/src/templates/_ci/`).
|
|
56
|
+
|
|
57
|
+
2. **Check the eval transport.** The workflow defaults to `mock`
|
|
58
|
+
transport unless `ANTHROPIC_API_KEY` is set in repo secrets. Decide
|
|
59
|
+
with the user:
|
|
60
|
+
|
|
61
|
+
- **Mock (free):** smoke-tests the eval runner shape — catches a
|
|
62
|
+
broken JSONL writer, but does not exercise the model. Good
|
|
63
|
+
default for forks / OSS.
|
|
64
|
+
- **claude-cli (real, costs tokens):** runs the actual model on
|
|
65
|
+
each task. Catches regressions caused by prompt/skill changes.
|
|
66
|
+
Costs ~$0.05–0.50/night depending on task set size.
|
|
67
|
+
|
|
68
|
+
3. **(If real transport) ensure the secret is set:**
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
gh secret list | grep ANTHROPIC_API_KEY
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If absent, ask the user to set it via:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
gh secret set ANTHROPIC_API_KEY
|
|
78
|
+
# paste the key when prompted (it never appears in shell history)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
4. **Trigger a first manual run** via `workflow_dispatch` so the user
|
|
82
|
+
does not wait 24h to confirm the wiring:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
gh workflow run eval-nightly.yml --field set=quick --field transport=mock
|
|
86
|
+
# then watch:
|
|
87
|
+
gh run watch
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
5. **Print the contract.** What the user just enabled:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
### Nightly eval enabled
|
|
94
|
+
**Workflow:** .github/workflows/eval-nightly.yml
|
|
95
|
+
**Cron:** 0 6 * * * UTC (offset: see `gh run list` for actual fire times)
|
|
96
|
+
**Transport:** mock | claude-cli
|
|
97
|
+
**Set:** quick (3 tasks) | full (all tasks)
|
|
98
|
+
**Results:** uploaded as `eval-results` artifact on each run
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Output contract
|
|
102
|
+
|
|
103
|
+
The skill prints a single block matching the shape above. Do not edit
|
|
104
|
+
the workflow file from here — if the user wants to change the cron, the
|
|
105
|
+
task set, or the transport default, they edit
|
|
106
|
+
`.github/workflows/eval-nightly.yml` directly (it is a normal yml file,
|
|
107
|
+
not a templated artifact, after install).
|
|
108
|
+
|
|
109
|
+
## When the workflow file is owned by the kit
|
|
110
|
+
|
|
111
|
+
Re-running `agent-harness-kit upgrade` will refresh
|
|
112
|
+
`.github/workflows/eval-nightly.yml`. If the user has hand-tuned cron
|
|
113
|
+
or transport defaults, mention this — they should either:
|
|
114
|
+
|
|
115
|
+
- Move their customisation into `harness.config.json#evals` (kit reads
|
|
116
|
+
it on next render) and let the workflow stay vanilla, or
|
|
117
|
+
- Document the customisation in a comment so the next upgrade does not
|
|
118
|
+
silently overwrite it.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Environment variables
|
|
2
|
+
|
|
3
|
+
Every kit hook and side-car honors one or more `AHK_*` env vars for opt-out,
|
|
4
|
+
debugging, or non-default behavior. Defaults are tuned for "just works" —
|
|
5
|
+
override only when you have a specific reason.
|
|
6
|
+
|
|
7
|
+
## Opt-out
|
|
8
|
+
|
|
9
|
+
| Var | Default | Effect |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `AHK_DISABLE_TELEMETRY` | unset | When `1`, the `telemetry-on-skill` and `subagent-stop` hooks exit before reading stdin — no `.harness/telemetry.jsonl` is created. Use when you do not want per-skill activity recorded. |
|
|
12
|
+
| `AHK_DISABLE_NOTIFY` | unset | When `1`, the `notify-on-block` hook skips the OS-native notification (osascript / notify-send). The telemetry row still logs the notification event. |
|
|
13
|
+
| `AHK_DISABLE_HTML_OPEN` | unset | When `1`, `/deliver-html` writes the HTML file but does not auto-open it in the browser. Also implied when `CI=true`. |
|
|
14
|
+
| `AHK_DISABLE_HTML_NUDGE` | unset | When `1`, suppresses the inline reminder that `/deliver-html` is available for analysis-style tasks. |
|
|
15
|
+
| `AHK_DISABLE_JQ` | unset | When `1`, hooks pretend `jq` is not on `$PATH` and use the Node fallback (`scripts/_lib/json-pick.mjs`). Used by tests to exercise the fallback path. |
|
|
16
|
+
|
|
17
|
+
## Bypass (audited)
|
|
18
|
+
|
|
19
|
+
| Var | Default | Effect |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `AHK_ALLOW_BYPASS` | unset | When `1`, `userprompt-guard`, `pretooluse-bash-guard`, and `pretooluse-edit-guard` allow the action through but append a record to `.harness/bypass.log` (timestamp + sha + reason + payload). The bypass leaves a paper trail so it cannot be silent. Use only with explicit intent — e.g. a mass-rename refactor that legitimately touches `.claude/`. |
|
|
22
|
+
| `AHK_HOOK_MODE` | unset | When `warn`, every gate hook (structural-test-on-edit, pretooluse-edit-guard, subagent-stop) logs the would-be violation to stderr but does not deny. Useful for one-off debugging; do not leave set in normal use. |
|
|
23
|
+
|
|
24
|
+
## Tuning
|
|
25
|
+
|
|
26
|
+
| Var | Default | Effect |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `AHK_TELEMETRY_MAX_LINES` | `5000` | Soft cap on `.harness/telemetry.jsonl` size. The `telemetry_append` helper rotates via `tail -n <N>` once the file grows past this number of lines. Set `0` to disable rotation entirely. Numeric only — non-numeric values fall back to the default rather than failing the hook. |
|
|
29
|
+
| `AHK_HEADLESS_RECOVER` | `0` | When `1`, the Stop hook spawns `claude -p` for one turn of recovery on failure. Costs tokens; off by default. Persistent equivalent: `harness.config.json#recovery.headless`. |
|
|
30
|
+
| `AHK_RECOVERY_LOCK_STALE_SECS` | `300` | How long the Stop-hook recovery lock is considered stale before a new recovery attempt can take it. Prevents stuck locks after a killed session. |
|
|
31
|
+
| `AHK_STATUSLINE_NO_COLOR` | unset | When `1`, the statusline emits plain text — no ANSI color escapes. Useful on terminals that do not render colors well, or when piping the output. |
|
|
32
|
+
|
|
33
|
+
## Where each variable lives
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
AHK_DISABLE_TELEMETRY → scripts/telemetry-on-skill.sh, scripts/subagent-stop.sh
|
|
37
|
+
AHK_DISABLE_NOTIFY → scripts/notify-on-block.sh
|
|
38
|
+
AHK_DISABLE_HTML_OPEN → .claude/skills/deliver-html/scripts/wrap-html.mjs
|
|
39
|
+
AHK_DISABLE_HTML_NUDGE → .claude/skills/deliver-html/SKILL.md
|
|
40
|
+
AHK_DISABLE_JQ → scripts/_lib/jp.sh (probed by every hook that parses JSON)
|
|
41
|
+
AHK_ALLOW_BYPASS → scripts/userprompt-guard.sh, scripts/pretooluse-*.sh
|
|
42
|
+
AHK_HOOK_MODE → scripts/structural-test-on-edit.sh, scripts/pretooluse-edit-guard.sh, scripts/subagent-stop.sh
|
|
43
|
+
AHK_TELEMETRY_MAX_LINES→ scripts/_lib/telemetry.sh (used by telemetry-on-skill, subagent-stop, notify-on-block)
|
|
44
|
+
AHK_HEADLESS_RECOVER → scripts/precompletion-checklist.sh
|
|
45
|
+
AHK_STATUSLINE_NO_COLOR→ scripts/statusline.mjs
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Disabling vs. removing
|
|
49
|
+
|
|
50
|
+
Prefer env-var opt-out over removing a hook from `.claude/settings.json` —
|
|
51
|
+
the kit's structural-test and version-sync checks expect every hook listed in
|
|
52
|
+
`hooks.json` to be present. Removing a hook leaves the index claiming a
|
|
53
|
+
contract the file system no longer fulfills, and `agent-harness-kit upgrade`
|
|
54
|
+
will keep re-installing it.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Auto-memory cheat sheet
|
|
2
|
+
|
|
3
|
+
Claude Code maintains a per-project memory directory at
|
|
4
|
+
`~/.claude/projects/<repo-slug>/memory/`. It persists across sessions and is
|
|
5
|
+
automatically loaded into the next conversation. The kit does **not**
|
|
6
|
+
manage this — Claude Code does. This file is a cheat sheet on when to push
|
|
7
|
+
something into it and when to leave it out.
|
|
8
|
+
|
|
9
|
+
## The four types
|
|
10
|
+
|
|
11
|
+
| Type | When to write | Half-life | Example |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| **user** | You learn something durable about the person you're working with — role, expertise, team, preferences | months to years | "data scientist, 10 years Go, new to React" |
|
|
14
|
+
| **feedback** | The user corrects an approach ("don't X") *or* validates an unusual choice ("yes the bundled PR was right") | months | "integration tests must hit a real DB, not mocks — burned by mock/prod divergence in Q3" |
|
|
15
|
+
| **project** | A fact about the work that the codebase itself does not show — deadlines, stakeholder decisions, the *why* behind a refactor | weeks to months | "auth rewrite is driven by legal compliance, not tech debt — scope toward compliance" |
|
|
16
|
+
| **reference** | A pointer to an external system — Linear project, Grafana dashboard, on-call runbook | until that system moves | "pipeline bugs in Linear project INGEST" |
|
|
17
|
+
|
|
18
|
+
## What's actually worth saving
|
|
19
|
+
|
|
20
|
+
Save when **all three** apply:
|
|
21
|
+
|
|
22
|
+
1. **Non-obvious from the code.** If `git log` or reading the file shows
|
|
23
|
+
it, the memory is dead weight.
|
|
24
|
+
2. **Survives across sessions.** Today's in-progress task is not memory —
|
|
25
|
+
it's a task list.
|
|
26
|
+
3. **Decision-shaping.** A future-you (or future-Claude) would behave
|
|
27
|
+
differently knowing it.
|
|
28
|
+
|
|
29
|
+
Trigger words from the user that mean "save this":
|
|
30
|
+
|
|
31
|
+
- "remember that …"
|
|
32
|
+
- "next time, do X / don't do X"
|
|
33
|
+
- "this is how we always do it"
|
|
34
|
+
- "the reason is …"
|
|
35
|
+
|
|
36
|
+
## What's NOT worth saving
|
|
37
|
+
|
|
38
|
+
- Code patterns, file paths, architecture diagrams — re-read the code.
|
|
39
|
+
- Git history, "who changed X last week" — `git log` is authoritative.
|
|
40
|
+
- Today's debug recipe — the fix lives in the commit; the commit message
|
|
41
|
+
has the context.
|
|
42
|
+
- A list of files you just edited — the diff has it.
|
|
43
|
+
- Anything documented in `CLAUDE.md` — it's already loaded.
|
|
44
|
+
|
|
45
|
+
Reject the request even if the user explicitly asks. Bigger memory is
|
|
46
|
+
*not* better memory — every dead entry is noise the next session will
|
|
47
|
+
have to scan past.
|
|
48
|
+
|
|
49
|
+
## Working with what's there
|
|
50
|
+
|
|
51
|
+
Claude Code loads `MEMORY.md` (the index) into every conversation. So:
|
|
52
|
+
|
|
53
|
+
- If you want a memory honored, make sure its index line is concise and
|
|
54
|
+
specific. Bad: `notes.md — stuff`. Good: `feedback_tests.md — never
|
|
55
|
+
mock the database in integration tests`.
|
|
56
|
+
- If a memory turns out wrong or stale, ask Claude to remove or update it
|
|
57
|
+
— don't let it accumulate.
|
|
58
|
+
- Memory is **not** automatically synced across machines. If your
|
|
59
|
+
workflow spans laptops, treat it as scratch, not source-of-truth.
|
|
60
|
+
|
|
61
|
+
## Privacy and scope
|
|
62
|
+
|
|
63
|
+
- Memory lives in `~/.claude/` — local-only. Nothing is uploaded.
|
|
64
|
+
- A project-scoped memory is keyed by repo slug. Cloning the repo on a
|
|
65
|
+
new machine does **not** carry memory over.
|
|
66
|
+
- If a memory contains something sensitive (credentials, customer names),
|
|
67
|
+
delete it. The kit's `userprompt-guard` hook does not see memory; the
|
|
68
|
+
burden of redaction is on you.
|
|
69
|
+
|
|
70
|
+
## Related kit features
|
|
71
|
+
|
|
72
|
+
- The **Stop hook** writes a JSONL row to `.harness/telemetry.jsonl` on
|
|
73
|
+
every Skill invocation — that is observability for the kit, not memory.
|
|
74
|
+
- The **SessionStart hook** inject branch + uncommitted diff + current
|
|
75
|
+
feature as `additionalContext`. That is per-session context, not
|
|
76
|
+
memory.
|
|
77
|
+
- The **PROGRESS.md** file at `.harness/PROGRESS.md` is the human-readable
|
|
78
|
+
session log appended by `SessionEnd`. Useful next-day rehydration; not
|
|
79
|
+
memory.
|
|
80
|
+
|
|
81
|
+
Memory ≠ context. Memory persists. Context is rebuilt every session from
|
|
82
|
+
files in the repo.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# _lib/jp.sh — source-only library. DO NOT execute directly.
|
|
3
|
+
#
|
|
4
|
+
# Provides three shared helpers used by every hook script that parses Claude
|
|
5
|
+
# Code's JSON stdin:
|
|
6
|
+
#
|
|
7
|
+
# have_jq — true iff jq is on PATH AND not disabled via env
|
|
8
|
+
# have_jp — true iff EITHER jq OR (node + _lib/json-pick.mjs) is usable
|
|
9
|
+
# jp <expr> [f] — run a jq-subset expression, preferring jq when present,
|
|
10
|
+
# else the Node fallback. Accepts optional file arg (some
|
|
11
|
+
# callers pass it; most read from stdin).
|
|
12
|
+
#
|
|
13
|
+
# Why this exists: the same ~14 lines were duplicated across 12 hook scripts.
|
|
14
|
+
# Single source of truth so fixing one bug (e.g. the "json-pick.mjs only
|
|
15
|
+
# supports one `// default` per expression" footgun documented in
|
|
16
|
+
# session-end.sh.hbs) only needs one edit, not twelve.
|
|
17
|
+
#
|
|
18
|
+
# Sourcing convention — the calling script MUST set _LIB_DIR before . :
|
|
19
|
+
#
|
|
20
|
+
# SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
21
|
+
# _LIB_DIR="$SCRIPT_DIR/_lib"
|
|
22
|
+
# . "$_LIB_DIR/jp.sh"
|
|
23
|
+
#
|
|
24
|
+
# Env vars:
|
|
25
|
+
# AHK_DISABLE_JQ=1 → pretend jq is missing; forces the Node fallback path.
|
|
26
|
+
# Lets us test the fallback on machines that have jq.
|
|
27
|
+
|
|
28
|
+
have_jq() {
|
|
29
|
+
[ "${AHK_DISABLE_JQ:-}" = "1" ] && return 1
|
|
30
|
+
command -v jq >/dev/null 2>&1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
have_jp() {
|
|
34
|
+
have_jq && return 0
|
|
35
|
+
command -v node >/dev/null 2>&1 \
|
|
36
|
+
&& [ -f "$_LIB_DIR/json-pick.mjs" ] \
|
|
37
|
+
&& return 0
|
|
38
|
+
return 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
jp() {
|
|
42
|
+
if have_jq; then
|
|
43
|
+
if [ -n "${2:-}" ]; then jq -r "$1" "$2"
|
|
44
|
+
else jq -r "$1"
|
|
45
|
+
fi
|
|
46
|
+
else
|
|
47
|
+
if [ -n "${2:-}" ]; then
|
|
48
|
+
node "$_LIB_DIR/json-pick.mjs" "$1" "$2"
|
|
49
|
+
else
|
|
50
|
+
node "$_LIB_DIR/json-pick.mjs" "$1"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# _lib/telemetry.sh — source-only library. DO NOT execute directly.
|
|
3
|
+
#
|
|
4
|
+
# Provides telemetry_append <jsonl-line> — write one line to
|
|
5
|
+
# .harness/telemetry.jsonl and rotate when the file grows past
|
|
6
|
+
# AHK_TELEMETRY_MAX_LINES (default 5000).
|
|
7
|
+
#
|
|
8
|
+
# Why centralised:
|
|
9
|
+
# - Two hooks append (telemetry-on-skill, notify-on-block). Rotation logic
|
|
10
|
+
# written once = one place to fix when the file format evolves.
|
|
11
|
+
# - harness-report.mjs only ever inspects the last 14 days; older lines are
|
|
12
|
+
# pure I/O cost. Bounding lines bounds report time at O(1).
|
|
13
|
+
#
|
|
14
|
+
# Env vars:
|
|
15
|
+
# AHK_DISABLE_TELEMETRY=1 → caller is expected to early-exit; this
|
|
16
|
+
# helper does NOT re-check (avoids double
|
|
17
|
+
# work) — gate in the caller.
|
|
18
|
+
# AHK_TELEMETRY_MAX_LINES=N → cap (default 5000). Set 0 to disable
|
|
19
|
+
# rotation entirely (keep unbounded).
|
|
20
|
+
|
|
21
|
+
telemetry_append() {
|
|
22
|
+
local line="$1"
|
|
23
|
+
[ -z "$line" ] && return 0
|
|
24
|
+
mkdir -p .harness
|
|
25
|
+
printf '%s\n' "$line" >> .harness/telemetry.jsonl
|
|
26
|
+
|
|
27
|
+
local limit="${AHK_TELEMETRY_MAX_LINES:-5000}"
|
|
28
|
+
# 0 = caller opted out of rotation explicitly.
|
|
29
|
+
[ "$limit" = "0" ] && return 0
|
|
30
|
+
# Non-numeric → fall back to default rather than failing the hook.
|
|
31
|
+
case "$limit" in
|
|
32
|
+
''|*[!0-9]*) limit=5000 ;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
# wc -l is sub-millisecond on files we care about (< 1MB at the default
|
|
36
|
+
# cap). Cheap enough to run every append; avoids needing a daemon.
|
|
37
|
+
local lines
|
|
38
|
+
lines=$(wc -l < .harness/telemetry.jsonl 2>/dev/null || echo 0)
|
|
39
|
+
if [ "$lines" -gt "$limit" ]; then
|
|
40
|
+
# tail to tmp + mv = atomic on POSIX. Reader can't catch a half-written
|
|
41
|
+
# file mid-rotate.
|
|
42
|
+
tail -n "$limit" .harness/telemetry.jsonl > .harness/telemetry.jsonl.tmp \
|
|
43
|
+
&& mv .harness/telemetry.jsonl.tmp .harness/telemetry.jsonl
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
@@ -6,26 +6,9 @@ set -eo pipefail
|
|
|
6
6
|
|
|
7
7
|
INPUT=$(cat)
|
|
8
8
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
have_jp() {
|
|
14
|
-
have_jq && return 0
|
|
15
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
16
|
-
return 1
|
|
17
|
-
}
|
|
18
|
-
jp() {
|
|
19
|
-
if have_jq; then
|
|
20
|
-
if [ -n "$2" ]; then jq -r "$1" "$2"; else jq -r "$1"; fi
|
|
21
|
-
else
|
|
22
|
-
if [ -n "$2" ]; then
|
|
23
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1" "$2"
|
|
24
|
-
else
|
|
25
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
26
|
-
fi
|
|
27
|
-
fi
|
|
28
|
-
}
|
|
9
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
10
|
+
. "$_LIB_DIR/jp.sh"
|
|
11
|
+
. "$_LIB_DIR/telemetry.sh"
|
|
29
12
|
|
|
30
13
|
if [ "${AHK_DISABLE_NOTIFY:-}" = "1" ]; then
|
|
31
14
|
exit 0
|
|
@@ -46,12 +29,12 @@ if [ -n "$TYPE" ]; then
|
|
|
46
29
|
fi
|
|
47
30
|
[ -z "$BODY" ] && BODY="Claude Code wants your attention."
|
|
48
31
|
|
|
49
|
-
mkdir -p .harness
|
|
50
32
|
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
51
33
|
ESCAPED_TITLE=${TITLE//\"/\\\"}
|
|
52
34
|
ESCAPED_BODY=${BODY//\"/\\\"}
|
|
53
|
-
printf '{"ts":"%s","hook":"Notification","type":"%s","title":"%s","body":"%s"}
|
|
54
|
-
"$TS" "$TYPE" "$ESCAPED_TITLE" "$ESCAPED_BODY"
|
|
35
|
+
LINE=$(printf '{"ts":"%s","hook":"Notification","type":"%s","title":"%s","body":"%s"}' \
|
|
36
|
+
"$TS" "$TYPE" "$ESCAPED_TITLE" "$ESCAPED_BODY")
|
|
37
|
+
telemetry_append "$LINE"
|
|
55
38
|
|
|
56
39
|
OS_KIND=$(uname -s 2>/dev/null || echo "Unknown")
|
|
57
40
|
case "$OS_KIND" in
|
|
@@ -28,26 +28,8 @@ set -eo pipefail
|
|
|
28
28
|
|
|
29
29
|
INPUT=$(cat)
|
|
30
30
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
command -v jq >/dev/null 2>&1
|
|
34
|
-
}
|
|
35
|
-
have_jp() {
|
|
36
|
-
have_jq && return 0
|
|
37
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
38
|
-
return 1
|
|
39
|
-
}
|
|
40
|
-
jp() {
|
|
41
|
-
if have_jq; then
|
|
42
|
-
if [ -n "$2" ]; then jq -r "$1" "$2"; else jq -r "$1"; fi
|
|
43
|
-
else
|
|
44
|
-
if [ -n "$2" ]; then
|
|
45
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1" "$2"
|
|
46
|
-
else
|
|
47
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
48
|
-
fi
|
|
49
|
-
fi
|
|
50
|
-
}
|
|
31
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
32
|
+
. "$_LIB_DIR/jp.sh"
|
|
51
33
|
|
|
52
34
|
TRIGGER=""
|
|
53
35
|
TOKENS=""
|
|
@@ -8,26 +8,8 @@ set -eo pipefail
|
|
|
8
8
|
# Without this fallback, `jq` missing on a fresh CI image silently disabled
|
|
9
9
|
# the baseline-monotonic guard — a known audit hole.
|
|
10
10
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
command -v jq >/dev/null 2>&1
|
|
14
|
-
}
|
|
15
|
-
have_jp() {
|
|
16
|
-
have_jq && return 0
|
|
17
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
18
|
-
return 1
|
|
19
|
-
}
|
|
20
|
-
jp() {
|
|
21
|
-
if have_jq; then
|
|
22
|
-
if [ -n "$2" ]; then jq -r "$1" "$2"; else jq -r "$1"; fi
|
|
23
|
-
else
|
|
24
|
-
if [ -n "$2" ]; then
|
|
25
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1" "$2"
|
|
26
|
-
else
|
|
27
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
28
|
-
fi
|
|
29
|
-
fi
|
|
30
|
-
}
|
|
11
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
12
|
+
. "$_LIB_DIR/jp.sh"
|
|
31
13
|
|
|
32
14
|
# Baseline monotonic guard. .harness/structural-baseline.json is decreasing-
|
|
33
15
|
# only — fixes REMOVE entries; no path should ADD them. Catches the "mask
|
|
@@ -14,37 +14,11 @@ INPUT=$(cat)
|
|
|
14
14
|
|
|
15
15
|
# Resolve the directory this hook lives in (used to find _lib/json-pick.mjs).
|
|
16
16
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
-
|
|
18
|
-
# have_jq
|
|
19
|
-
# used by tests to exercise the jq-less code path
|
|
20
|
-
# installed locally.
|
|
21
|
-
|
|
22
|
-
[ "${AHK_DISABLE_JQ:-}" = "1" ] && return 1
|
|
23
|
-
command -v jq >/dev/null 2>&1
|
|
24
|
-
}
|
|
25
|
-
# jp — JSON picker. Uses `jq` when available, else falls back to a bundled
|
|
26
|
-
# Node script with a jq-subset implementation. Keeps hooks portable on
|
|
27
|
-
# minimal CI / Windows where jq is not installed by default. Without this
|
|
28
|
-
# fallback, the entire pre-completion check used to be a silent no-op.
|
|
29
|
-
jp() {
|
|
30
|
-
if have_jq; then
|
|
31
|
-
if [ -n "$2" ]; then jq -r "$1" "$2"; else jq -r "$1"; fi
|
|
32
|
-
else
|
|
33
|
-
if [ -n "$2" ]; then
|
|
34
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1" "$2"
|
|
35
|
-
else
|
|
36
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
37
|
-
fi
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
40
|
-
# Probe: do we have either jq or the Node fallback? Node is always
|
|
41
|
-
# present (kit's `engines` field requires >=20), so this is just an explicit
|
|
42
|
-
# probe and a fail-loud branch if even node is missing.
|
|
43
|
-
have_jp() {
|
|
44
|
-
have_jq && return 0
|
|
45
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
46
|
-
return 1
|
|
47
|
-
}
|
|
17
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
18
|
+
# have_jq / have_jp / jp shared across all hook scripts. AHK_DISABLE_JQ=1
|
|
19
|
+
# forces the Node fallback, used by tests to exercise the jq-less code path
|
|
20
|
+
# on machines that have jq installed locally.
|
|
21
|
+
. "$_LIB_DIR/jp.sh"
|
|
48
22
|
|
|
49
23
|
# CRITICAL: avoid infinite loops. If the hook already ran, do not block again.
|
|
50
24
|
if have_jp; then
|
|
@@ -32,26 +32,8 @@ set -eo pipefail
|
|
|
32
32
|
|
|
33
33
|
INPUT=$(cat)
|
|
34
34
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
command -v jq >/dev/null 2>&1
|
|
38
|
-
}
|
|
39
|
-
have_jp() {
|
|
40
|
-
have_jq && return 0
|
|
41
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
42
|
-
return 1
|
|
43
|
-
}
|
|
44
|
-
jp() {
|
|
45
|
-
if have_jq; then
|
|
46
|
-
if [ -n "$2" ]; then jq -r "$1" "$2"; else jq -r "$1"; fi
|
|
47
|
-
else
|
|
48
|
-
if [ -n "$2" ]; then
|
|
49
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1" "$2"
|
|
50
|
-
else
|
|
51
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
52
|
-
fi
|
|
53
|
-
fi
|
|
54
|
-
}
|
|
35
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
36
|
+
. "$_LIB_DIR/jp.sh"
|
|
55
37
|
|
|
56
38
|
if ! have_jp; then
|
|
57
39
|
# Without a JSON parser we can't read the command. Skip rather than
|
|
@@ -23,20 +23,8 @@ set -eo pipefail
|
|
|
23
23
|
|
|
24
24
|
INPUT=$(cat)
|
|
25
25
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
command -v jq >/dev/null 2>&1
|
|
29
|
-
}
|
|
30
|
-
have_jp() {
|
|
31
|
-
have_jq && return 0
|
|
32
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
33
|
-
return 1
|
|
34
|
-
}
|
|
35
|
-
jp() {
|
|
36
|
-
if have_jq; then jq -r "$1"
|
|
37
|
-
else node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
26
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
27
|
+
. "$_LIB_DIR/jp.sh"
|
|
40
28
|
if ! have_jp; then exit 0; fi
|
|
41
29
|
|
|
42
30
|
# Resolve target file. Write/Edit ship .tool_input.file_path; MultiEdit ships
|
|
@@ -19,20 +19,8 @@ set -eo pipefail
|
|
|
19
19
|
|
|
20
20
|
INPUT=$(cat)
|
|
21
21
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
command -v jq >/dev/null 2>&1
|
|
25
|
-
}
|
|
26
|
-
have_jp() {
|
|
27
|
-
have_jq && return 0
|
|
28
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
29
|
-
return 1
|
|
30
|
-
}
|
|
31
|
-
jp() {
|
|
32
|
-
if have_jq; then jq -r "$1"
|
|
33
|
-
else node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
34
|
-
fi
|
|
35
|
-
}
|
|
22
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
23
|
+
. "$_LIB_DIR/jp.sh"
|
|
36
24
|
|
|
37
25
|
REASON=""
|
|
38
26
|
SESSION_ID=""
|
|
@@ -22,26 +22,8 @@ set -eo pipefail
|
|
|
22
22
|
|
|
23
23
|
INPUT=$(cat)
|
|
24
24
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
command -v jq >/dev/null 2>&1
|
|
28
|
-
}
|
|
29
|
-
have_jp() {
|
|
30
|
-
have_jq && return 0
|
|
31
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
32
|
-
return 1
|
|
33
|
-
}
|
|
34
|
-
jp() {
|
|
35
|
-
if have_jq; then
|
|
36
|
-
if [ -n "$2" ]; then jq -r "$1" "$2"; else jq -r "$1"; fi
|
|
37
|
-
else
|
|
38
|
-
if [ -n "$2" ]; then
|
|
39
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1" "$2"
|
|
40
|
-
else
|
|
41
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
42
|
-
fi
|
|
43
|
-
fi
|
|
44
|
-
}
|
|
25
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
26
|
+
. "$_LIB_DIR/jp.sh"
|
|
45
27
|
|
|
46
28
|
SOURCE=""
|
|
47
29
|
if have_jp; then
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// statusLine — "
|
|
2
|
+
// statusLine — "Dashboard Style" (Variant 4).
|
|
3
3
|
//
|
|
4
|
-
// LINE 1 — vitals (always emitted when any segment resolves):
|
|
5
|
-
// ▶ Opus│terse
|
|
4
|
+
// LINE 1 — vitals with rich icons (always emitted when any segment resolves):
|
|
5
|
+
// ▶ Opus 4.7 │ 📝 terse │ ⏱ 1h12m │ ⎇ main(±3) │ ✓ clean │ ▓▓▓▓░ 42% │ $0.83 │ +156/-23
|
|
6
6
|
//
|
|
7
|
-
// LINE 2 — alerts (only when ≥1 trigger fires
|
|
8
|
-
// ⚠ >200K — auto-compact next msg
|
|
7
|
+
// LINE 2 — alerts with clear segmentation (only when ≥1 trigger fires):
|
|
8
|
+
// ⚠ >200K — auto-compact next msg │ ⏳ 5h limit 78%, resets in 1h12m │ 🚫 last-block: <title>
|
|
9
9
|
//
|
|
10
10
|
// Payload (Claude Code v2.1.132+ schema):
|
|
11
11
|
// model.display_name, output_style.name, session_id, version,
|
|
@@ -236,61 +236,72 @@ function bar(pct, width = 10) {
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
// ---------------------------------------------------------------------------
|
|
239
|
-
// Line 1 — vitals.
|
|
239
|
+
// Line 1 — vitals (Dashboard Style with rich icons).
|
|
240
240
|
// ---------------------------------------------------------------------------
|
|
241
241
|
function renderLine1(payload, git, feat, config) {
|
|
242
|
-
const
|
|
243
|
-
const mid = []; // workspace group: branch, feat
|
|
244
|
-
const right = []; // burn group: ctx, cost, lines
|
|
242
|
+
const segments = [];
|
|
245
243
|
|
|
244
|
+
// Segment 1: Model with play icon
|
|
246
245
|
const modelName = payload?.model?.display_name;
|
|
247
|
-
if (modelName)
|
|
246
|
+
if (modelName) {
|
|
247
|
+
segments.push(`${green("▶")} ${cyan(modelName)}`);
|
|
248
|
+
}
|
|
248
249
|
|
|
250
|
+
// Segment 2: Output style with document icon
|
|
249
251
|
const styleName = payload?.output_style?.name;
|
|
250
|
-
if (styleName && styleName !== "default")
|
|
252
|
+
if (styleName && styleName !== "default") {
|
|
253
|
+
segments.push(`${dim("📝")} ${dim(styleName)}`);
|
|
254
|
+
}
|
|
251
255
|
|
|
256
|
+
// Segment 3: Duration with timer icon
|
|
252
257
|
const durMs = payload?.cost?.total_duration_ms;
|
|
253
|
-
if (durMs && durMs >= 1000)
|
|
258
|
+
if (durMs && durMs >= 1000) {
|
|
259
|
+
segments.push(`${dim("⏱")} ${dim(fmtDuration(durMs))}`);
|
|
260
|
+
}
|
|
254
261
|
|
|
262
|
+
// Segment 4: Branch with git icon
|
|
255
263
|
if (git?.branch) {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
264
|
+
const branchIcon = dim("⎇");
|
|
265
|
+
const branchText = git.conflict ? red(`${git.branch}!CONFLICT`)
|
|
266
|
+
: git.dirty > 0 ? `${yellow(git.branch)}${dim("(")}${yellow(`±${git.dirty}`)}${dim(")")}`
|
|
267
|
+
: yellow(git.branch);
|
|
268
|
+
segments.push(`${branchIcon} ${branchText}`);
|
|
260
269
|
}
|
|
261
270
|
|
|
271
|
+
// Segment 5: Feature status with checkmark/cross icon
|
|
262
272
|
if (feat) {
|
|
263
|
-
|
|
273
|
+
const featIcon = feat.open ? red("✗") : green("✓");
|
|
274
|
+
const featText = feat.open ? magenta(feat.open) : green("clean");
|
|
275
|
+
segments.push(`${featIcon} ${featText}`);
|
|
264
276
|
}
|
|
265
277
|
|
|
278
|
+
// Segment 6: Context usage (bar + percentage)
|
|
266
279
|
const pct = payload?.context_window?.used_percentage;
|
|
267
280
|
if (typeof pct === "number") {
|
|
268
281
|
const col = ctxColor(pct);
|
|
269
|
-
|
|
282
|
+
segments.push(`${col(bar(pct))} ${col(`${Math.round(pct)}%`)}`);
|
|
270
283
|
}
|
|
271
284
|
|
|
285
|
+
// Segment 7: Cost
|
|
272
286
|
const cost = payload?.cost?.total_cost_usd;
|
|
273
287
|
if (typeof cost === "number" && cost > 0) {
|
|
274
|
-
|
|
288
|
+
segments.push(costStr(cost));
|
|
275
289
|
}
|
|
276
290
|
|
|
291
|
+
// Segment 8: Lines changed
|
|
277
292
|
if (config.showLines) {
|
|
278
293
|
const add = num(payload?.cost?.total_lines_added, 0);
|
|
279
294
|
const rem = num(payload?.cost?.total_lines_removed, 0);
|
|
280
295
|
if (add > 0 || rem > 0) {
|
|
281
|
-
|
|
296
|
+
segments.push(`${green("+" + add)}/${dimRed("-" + rem)}`);
|
|
282
297
|
}
|
|
283
298
|
}
|
|
284
299
|
|
|
285
|
-
|
|
286
|
-
if (left.length) parts.push(left.join(dim("│")));
|
|
287
|
-
if (mid.length) parts.push(mid.join(" "));
|
|
288
|
-
if (right.length) parts.push(right.join(" "));
|
|
289
|
-
return parts.join(" ");
|
|
300
|
+
return segments.join(` ${dim("│")} `);
|
|
290
301
|
}
|
|
291
302
|
|
|
292
303
|
// ---------------------------------------------------------------------------
|
|
293
|
-
// Line 2 — alerts.
|
|
304
|
+
// Line 2 — alerts (Dashboard Style with rich icons and clear segmentation).
|
|
294
305
|
// ---------------------------------------------------------------------------
|
|
295
306
|
function renderLine2(payload, sessionId, config, lang) {
|
|
296
307
|
if (config.compact) return "";
|
|
@@ -299,19 +310,19 @@ function renderLine2(payload, sessionId, config, lang) {
|
|
|
299
310
|
|
|
300
311
|
// Order by severity: hardest stop first.
|
|
301
312
|
if (payload?.exceeds_200k_tokens === true) {
|
|
302
|
-
alerts.push(red(
|
|
313
|
+
alerts.push(`${red("⚠")} ${red(t.over_200k)}`);
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
const pct = payload?.context_window?.used_percentage;
|
|
306
317
|
if (typeof pct === "number" && pct >= 80 && payload?.exceeds_200k_tokens !== true) {
|
|
307
|
-
alerts.push(red(
|
|
318
|
+
alerts.push(`${red("⚠")} ${red(`ctx ${Math.round(pct)}%${t.compact_soon}`)}`);
|
|
308
319
|
}
|
|
309
320
|
|
|
310
321
|
if (config.showRateLimit) {
|
|
311
322
|
const five = payload?.rate_limits?.five_hour;
|
|
312
323
|
if (five && typeof five.used_percentage === "number" && five.used_percentage >= 75) {
|
|
313
324
|
const resetTxt = five.resets_at ? `${t.rate_resets}${fmtCountdown(five.resets_at)}` : "";
|
|
314
|
-
alerts.push(yellow(
|
|
325
|
+
alerts.push(`${yellow("⏳")} ${yellow(`5h limit ${Math.round(five.used_percentage)}%${resetTxt}`)}`);
|
|
315
326
|
}
|
|
316
327
|
}
|
|
317
328
|
|
|
@@ -319,11 +330,11 @@ function renderLine2(payload, sessionId, config, lang) {
|
|
|
319
330
|
const lb = fetchLastBlock(sessionId);
|
|
320
331
|
if (lb) {
|
|
321
332
|
const title = String(lb.title || "").slice(0, 40);
|
|
322
|
-
alerts.push(red(
|
|
333
|
+
alerts.push(`${red("🚫")} ${red(`${t.last_block}${title}`)}`);
|
|
323
334
|
}
|
|
324
335
|
}
|
|
325
336
|
|
|
326
|
-
return alerts.join("
|
|
337
|
+
return alerts.join(` ${dim("│")} `);
|
|
327
338
|
}
|
|
328
339
|
|
|
329
340
|
// ---------------------------------------------------------------------------
|
|
@@ -13,20 +13,8 @@ INPUT=$(cat)
|
|
|
13
13
|
# when jq is missing — silently skipping the structural check on jq-less
|
|
14
14
|
# environments (minimal CI, Windows without WSL+brew) was a known audit hole.
|
|
15
15
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
command -v jq >/dev/null 2>&1
|
|
19
|
-
}
|
|
20
|
-
have_jp() {
|
|
21
|
-
have_jq && return 0
|
|
22
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
23
|
-
return 1
|
|
24
|
-
}
|
|
25
|
-
jp() {
|
|
26
|
-
if have_jq; then jq -r "$1"
|
|
27
|
-
else node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
28
|
-
fi
|
|
29
|
-
}
|
|
16
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
17
|
+
. "$_LIB_DIR/jp.sh"
|
|
30
18
|
if ! have_jp; then
|
|
31
19
|
echo "[ahk] structural-test-on-edit: no JSON parser available (need jq OR node + scripts/_lib/json-pick.mjs)." >&2
|
|
32
20
|
exit 0
|
|
@@ -14,20 +14,9 @@ set -eo pipefail
|
|
|
14
14
|
|
|
15
15
|
INPUT=$(cat)
|
|
16
16
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
have_jp() {
|
|
22
|
-
have_jq && return 0
|
|
23
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
24
|
-
return 1
|
|
25
|
-
}
|
|
26
|
-
jp() {
|
|
27
|
-
if have_jq; then jq -r "$1"
|
|
28
|
-
else node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
29
|
-
fi
|
|
30
|
-
}
|
|
17
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
18
|
+
. "$_LIB_DIR/jp.sh"
|
|
19
|
+
. "$_LIB_DIR/telemetry.sh"
|
|
31
20
|
|
|
32
21
|
SUBAGENT="(unknown)"
|
|
33
22
|
if have_jp; then
|
|
@@ -35,12 +24,12 @@ if have_jp; then
|
|
|
35
24
|
fi
|
|
36
25
|
|
|
37
26
|
# Telemetry first so we record every subagent boundary, even if the
|
|
38
|
-
# structural-test bails below.
|
|
39
|
-
mkdir -p .harness
|
|
27
|
+
# structural-test bails below. telemetry_append handles rotation.
|
|
40
28
|
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
41
29
|
SHA=$(git rev-parse --short HEAD 2>/dev/null || echo 'no-git')
|
|
42
|
-
printf '{"ts":"%s","event":"subagent_stop","subagent":"%s","sha":"%s"}
|
|
43
|
-
"$TS" "$SUBAGENT" "$SHA"
|
|
30
|
+
LINE=$(printf '{"ts":"%s","event":"subagent_stop","subagent":"%s","sha":"%s"}' \
|
|
31
|
+
"$TS" "$SUBAGENT" "$SHA")
|
|
32
|
+
telemetry_append "$LINE"
|
|
44
33
|
|
|
45
34
|
# Skip if structural test disabled.
|
|
46
35
|
if [ -f harness.config.json ] \
|
|
@@ -8,25 +8,20 @@
|
|
|
8
8
|
# v0.7: migrated from `command -v jq` fail-open gate to the kit's jp() helper
|
|
9
9
|
# so the telemetry record still gets written on jq-less CI / Windows. Without
|
|
10
10
|
# the migration, telemetry quietly went dark anywhere jq wasn't installed.
|
|
11
|
+
# v0.10.3: jp/have_jq/have_jp extracted to _lib/jp.sh; AHK_DISABLE_TELEMETRY
|
|
12
|
+
# opt-out + AHK_TELEMETRY_MAX_LINES rotation added.
|
|
11
13
|
set -e
|
|
12
14
|
|
|
15
|
+
# Opt-out: respect AHK_DISABLE_TELEMETRY=1 before reading stdin so the user
|
|
16
|
+
# can fully disable observability without removing the hook from settings.
|
|
17
|
+
[ "${AHK_DISABLE_TELEMETRY:-}" = "1" ] && exit 0
|
|
18
|
+
|
|
13
19
|
INPUT=$(cat)
|
|
14
20
|
|
|
15
21
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
have_jp() {
|
|
21
|
-
have_jq && return 0
|
|
22
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
23
|
-
return 1
|
|
24
|
-
}
|
|
25
|
-
jp() {
|
|
26
|
-
if have_jq; then jq -r "$1"
|
|
27
|
-
else node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
28
|
-
fi
|
|
29
|
-
}
|
|
22
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
23
|
+
. "$_LIB_DIR/jp.sh"
|
|
24
|
+
. "$_LIB_DIR/telemetry.sh"
|
|
30
25
|
if ! have_jp; then exit 0; fi
|
|
31
26
|
|
|
32
27
|
TOOL=$(echo "$INPUT" | jp '.tool_name // empty')
|
|
@@ -35,14 +30,13 @@ TOOL=$(echo "$INPUT" | jp '.tool_name // empty')
|
|
|
35
30
|
SKILL=$(echo "$INPUT" | jp '.tool_input.skill // empty')
|
|
36
31
|
[ -z "$SKILL" ] && exit 0
|
|
37
32
|
|
|
38
|
-
mkdir -p .harness
|
|
39
33
|
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
40
34
|
SHA=$(git rev-parse --short HEAD 2>/dev/null || echo 'no-git')
|
|
41
35
|
|
|
42
36
|
# Compose JSONL line by hand — same shape as the previous jq-built record.
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
# Skill names are constrained to `[a-z0-9-]+` upstream so we don't need full
|
|
38
|
+
# JSON escaping here. telemetry_append handles mkdir, append, and rotation.
|
|
39
|
+
LINE=$(printf '{"ts":"%s","event":"skill_invoked","skill":"%s","sha":"%s"}' \
|
|
40
|
+
"$TS" "$SKILL" "$SHA")
|
|
41
|
+
telemetry_append "$LINE"
|
|
48
42
|
exit 0
|
|
@@ -14,26 +14,8 @@ set -eo pipefail
|
|
|
14
14
|
|
|
15
15
|
INPUT=$(cat)
|
|
16
16
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
command -v jq >/dev/null 2>&1
|
|
20
|
-
}
|
|
21
|
-
have_jp() {
|
|
22
|
-
have_jq && return 0
|
|
23
|
-
command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
|
|
24
|
-
return 1
|
|
25
|
-
}
|
|
26
|
-
jp() {
|
|
27
|
-
if have_jq; then
|
|
28
|
-
if [ -n "$2" ]; then jq -r "$1" "$2"; else jq -r "$1"; fi
|
|
29
|
-
else
|
|
30
|
-
if [ -n "$2" ]; then
|
|
31
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1" "$2"
|
|
32
|
-
else
|
|
33
|
-
node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
|
|
34
|
-
fi
|
|
35
|
-
fi
|
|
36
|
-
}
|
|
17
|
+
_LIB_DIR="$SCRIPT_DIR/_lib"
|
|
18
|
+
. "$_LIB_DIR/jp.sh"
|
|
37
19
|
|
|
38
20
|
if ! have_jp; then
|
|
39
21
|
exit 0
|