ashlr-plugin 1.36.4 → 1.37.1
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 +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +49 -8
- package/commands/ashlr-doctor.md +29 -0
- package/commands/ashlr-status.md +1 -1
- package/docs/PHANTOM-USAGE.md +58 -0
- package/docs/architecture.md +5 -5
- package/docs/ashlr-handshake.md +36 -0
- package/docs/assets/og.svg +1 -1
- package/docs/benchmarks-v2.json +226 -177
- package/docs/benchmarks.md +20 -10
- package/docs/competitive-wozcode.md +2 -2
- package/docs/embeddings-gateway.md +39 -0
- package/docs/index.html +3 -3
- package/docs/legal.md +1 -1
- package/docs/llm-providers.md +15 -1
- package/docs/multi-host-mcp.md +25 -3
- package/docs/phantom-preflight.md +23 -0
- package/docs/team-genome.md +2 -1
- package/hooks/_hook-timing-ledger.ts +2 -9
- package/hooks/hooks.json +3 -106
- package/hooks/posttooluse-stale-result.ts +3 -0
- package/hooks/pretooluse-common.ts +1 -1
- package/hooks/pretooluse-notebookedit.ts +1 -1
- package/hooks/pretooluse-write.ts +1 -1
- package/hooks/session-start.ts +48 -26
- package/package.json +9 -4
- package/ports/cursor/mcp.json +1 -1
- package/scripts/_read-thresholds.ts +119 -0
- package/scripts/ashlr-mcp.mjs +15 -0
- package/scripts/ashlr-mcp.ts +3 -0
- package/scripts/ashlr-plugin.mjs +59 -0
- package/scripts/bench-read-real.ts +90 -0
- package/scripts/bootstrap.mjs +14 -4
- package/scripts/cli.ts +35 -10
- package/scripts/context-status.ts +7 -2
- package/scripts/deploy-smoke.sh +8 -4
- package/scripts/doctor.ts +171 -42
- package/scripts/genome-auto-consolidate.ts +4 -2
- package/scripts/genome-init.ts +3 -3
- package/scripts/genome-key.ts +64 -7
- package/scripts/handshake.mjs +51 -0
- package/scripts/hook-bootstrap.mjs +170 -17
- package/scripts/mcp-entrypoint.ts +3 -0
- package/scripts/ollama-setup.ts +5 -4
- package/scripts/run-benchmark.ts +28 -18
- package/scripts/upgrade-flow.ts +9 -14
- package/servers/_base-urls.ts +48 -0
- package/servers/_companions.ts +258 -0
- package/servers/_cwd-clamp.ts +38 -1
- package/servers/_edit-match.ts +12 -2
- package/servers/_embedding-model.ts +77 -0
- package/servers/_gateway-target.ts +179 -0
- package/servers/_llm-providers/anthropic.ts +10 -4
- package/servers/_llm-providers/cloud.ts +9 -7
- package/servers/_llm-providers/index.ts +8 -6
- package/servers/_llm-providers/local.ts +9 -5
- package/servers/_mcp-host.ts +6 -0
- package/servers/_miss-tracker.ts +121 -0
- package/servers/_phantom-exec.ts +135 -0
- package/servers/_phantom-preflight.ts +130 -0
- package/servers/_pid-alive.ts +40 -0
- package/servers/_pro-token-keychain.ts +149 -0
- package/servers/_redact-keys.ts +75 -0
- package/servers/_rg-text.ts +111 -0
- package/servers/_router.ts +20 -2
- package/servers/_run-with-timeout.ts +56 -12
- package/servers/_test-watch.ts +1 -10
- package/servers/_token-measure.ts +3 -1
- package/servers/_tool-base.ts +6 -0
- package/servers/bash-server.ts +1 -10
- package/servers/edit-server.ts +24 -1
- package/servers/efficiency-server.ts +12 -2
- package/servers/genome-server-handlers.ts +2 -2
- package/servers/genome-server.ts +17 -17
- package/servers/grep-server.ts +6 -2
- package/servers/orient-server-handlers.ts +3 -2
- package/servers/orient-server.ts +16 -9
- package/servers/read-server.ts +1 -1
- package/servers/write-server-handlers.ts +10 -2
|
@@ -25,7 +25,7 @@ Three technical capabilities are worth adopting or beating. One pricing model (p
|
|
|
25
25
|
| Per-tool measured savings | No (aggregate self-report) | Yes |
|
|
26
26
|
| Open source + auditable | No (minified, account-gated) | Yes |
|
|
27
27
|
| Opt-in telemetry | No (PostHog on-by-default, hardcoded token) | Yes |
|
|
28
|
-
| Tool breadth | Narrow (deliberate) | 40 tools,
|
|
28
|
+
| Tool breadth | Narrow (deliberate) | 40 MCP tools, 34 slash commands |
|
|
29
29
|
| `llms.txt` | Yes | No |
|
|
30
30
|
| Public savings leaderboard | Yes (gamified) | No |
|
|
31
31
|
| Multi-surface install (VS Code / Cursor / Windows) | Yes | macOS/Linux primary |
|
|
@@ -79,7 +79,7 @@ Message these proactively in docs, README, and the benchmark blog:
|
|
|
79
79
|
- **Open source + auditable.** WozCode is minified and requires account creation to inspect behavior. Ashlr is fully open — every tool, hook, and genome interaction is readable.
|
|
80
80
|
- **Measured savings, not self-reported.** Ashlr tracks per-tool token and cost savings with a verifiable methodology. WozCode's numbers are inconsistent across their own pages.
|
|
81
81
|
- **Opt-in telemetry.** WozCode ships PostHog with a hardcoded token, on by default. Ashlr's telemetry is opt-in and documented.
|
|
82
|
-
- **Breadth.** 40 tools,
|
|
82
|
+
- **Breadth.** 40 MCP tools, 34 slash commands, genome RAG, `/ashlr-brief`, `/ashlr-efficient`. WozCode's surface is deliberately narrow.
|
|
83
83
|
- **No account required.** Ashlr installs with one curl command and one slash command. WozCode requires account creation before the plugin is functional.
|
|
84
84
|
|
|
85
85
|
---
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Embeddings through LiteLLM or another OpenAI-compatible endpoint
|
|
2
|
+
|
|
3
|
+
This is opt-in and off by default. Without `ASHLR_EMBED_FORMAT=openai`, nothing changes. `ASHLR_EMBED_URL` keeps its Ollama format (`{ model, prompt }` in, `{ embedding }` out), sends no key, and the BM25 embedder still runs when no URL is set.
|
|
4
|
+
|
|
5
|
+
## Turning it on
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
ASHLR_EMBED_FORMAT=openai
|
|
9
|
+
ASHLR_EMBED_URL=http://127.0.0.1:4000/v1 # a local LiteLLM proxy, for example
|
|
10
|
+
ASHLR_EMBED_MODEL=text-embedding-3-small # whatever model name your gateway serves
|
|
11
|
+
ASHLR_EMBED_KEY=sk-... # optional
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
With the format set, the plugin POSTs `{ "model": ..., "input": <text> }` to `<ASHLR_EMBED_URL>/embeddings` and reads `data[0].embedding`. A URL that already ends in `/embeddings` is used as is. `ASHLR_EMBED_KEY` is sent as `Authorization: Bearer <key>` and only in this mode. In the Ollama format it is ignored.
|
|
15
|
+
|
|
16
|
+
Any failure falls back to the built-in BM25 embedder, as the Ollama path does. Failures include a refused URL, a timeout after 5 s, an error status, and a body without a numeric `data[0].embedding`.
|
|
17
|
+
|
|
18
|
+
## Gateway rules
|
|
19
|
+
|
|
20
|
+
These are the same rules as the other keyed model calls (see [llm-providers.md](llm-providers.md)):
|
|
21
|
+
|
|
22
|
+
- The URL must be https, or plain http only to `127.0.0.1` or `[::1]`. `localhost` by name is refused, as is plain http to any other host.
|
|
23
|
+
- Credentials in the URL (`https://user:pass@...`) are refused.
|
|
24
|
+
- Redirects are not followed. A 3xx is a refusal, so the key never follows one.
|
|
25
|
+
- With `ASHLR_GATEWAY_STRICT=1`, the target must also be loopback, an exact https `host:port` in `ASHLR_GATEWAY_HOSTS`, or a built-in provider host.
|
|
26
|
+
|
|
27
|
+
A refused URL makes no request.
|
|
28
|
+
|
|
29
|
+
## Phantom
|
|
30
|
+
|
|
31
|
+
Under `phantom exec -- <cmd>`, set `ASHLR_EMBED_KEY` to the session's `phm_` placeholder and `ASHLR_EMBED_URL` to the proxy URL for your provider. The proxy URL is `http://127.0.0.1:<port>/...`, so it passes the rules above. The placeholder is sent unchanged and the proxy swaps in the real key on the way out. The plugin never resolves a `phm:` vault reference and never runs `phantom reveal`. If `ASHLR_EMBED_KEY` is a `phm:` reference, no request is made and BM25 is used.
|
|
32
|
+
|
|
33
|
+
## What is never logged
|
|
34
|
+
|
|
35
|
+
The embedding call writes nothing to stdout or stderr: not the key, not the text being embedded, and not the response. `scripts/context-status.ts` names an openai-format endpoint as `scheme://host:port` only, so a Phantom session token in the URL path is not printed.
|
|
36
|
+
|
|
37
|
+
## Caveats
|
|
38
|
+
|
|
39
|
+
The URL handling, rules and request shape are covered by tests against local stub servers. The plugin has not been tested against a live LiteLLM or Phantom session here. A remote model's vectors have a different dimension than the 256-dim BM25 vectors, as with the existing Ollama mode, so keep one embedder per context database.
|
package/docs/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<meta property="og:site_name" content="ashlr" />
|
|
13
13
|
<meta property="og:url" content="https://plugin.ashlr.ai/" />
|
|
14
14
|
<meta property="og:title" content="ashlr · The Token Ledger" />
|
|
15
|
-
<meta property="og:description" content="Open-source Codex and Claude Code plugin.
|
|
15
|
+
<meta property="og:description" content="Open-source Codex and Claude Code plugin. Per-call savings: reads about −75%, greps about −50% (not end-to-end). MIT." />
|
|
16
16
|
<meta property="og:image" content="https://plugin.ashlr.ai/assets/og.png" />
|
|
17
17
|
<meta property="og:image:width" content="1200" />
|
|
18
18
|
<meta property="og:image:height" content="630" />
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<!-- Twitter / X -->
|
|
22
22
|
<meta name="twitter:card" content="summary_large_image" />
|
|
23
23
|
<meta name="twitter:title" content="ashlr · The Token Ledger" />
|
|
24
|
-
<meta name="twitter:description" content="Open-source Codex and Claude Code plugin.
|
|
24
|
+
<meta name="twitter:description" content="Open-source Codex and Claude Code plugin. Per-call savings: reads about −75%, greps about −50% (not end-to-end). MIT." />
|
|
25
25
|
<meta name="twitter:image" content="https://plugin.ashlr.ai/assets/og.png" />
|
|
26
26
|
|
|
27
27
|
<link rel="canonical" href="https://plugin.ashlr.ai/" />
|
|
@@ -1670,7 +1670,7 @@
|
|
|
1670
1670
|
<p style="margin-top: 28px; font-family: var(--font-display); font-variation-settings: 'opsz' 16; font-size: 16px; line-height: 1.5; color: var(--ink-55); max-width: 720px;">
|
|
1671
1671
|
<em>Files under 2 KB see 0% savings — snipCompact only fires on tool-results
|
|
1672
1672
|
over 2,000 chars. That's by design: there's nothing to trim on a small file.
|
|
1673
|
-
The story is: <strong style="color: var(--debit);">
|
|
1673
|
+
The story is: <strong style="color: var(--debit);">per-call reads about −75%, greps about −50% (not end-to-end).</strong> Agents read big files; big files see big savings.</em>
|
|
1674
1674
|
</p>
|
|
1675
1675
|
|
|
1676
1676
|
<p style="margin-top: 20px; font-family: var(--font-mono); font-size: 13px; color: var(--ink-55);">
|
package/docs/legal.md
CHANGED
|
@@ -10,7 +10,7 @@ Last reviewed: 2026-04-18
|
|
|
10
10
|
|
|
11
11
|
### Entity and policy text
|
|
12
12
|
|
|
13
|
-
- [x] **Entity name confirmed.** Privacy, Terms, and DPA now say "AshlrAI Inc" (
|
|
13
|
+
- [x] **Entity name confirmed.** Privacy, Terms, and DPA now say "AshlrAI Inc" (Virginia corporation). Contact email is `support@ashlr.ai` across all pages.
|
|
14
14
|
- [x] **Governing law clause.** Delaware, in `site/app/terms/page.tsx` section 10. Placeholder removed.
|
|
15
15
|
- [ ] **Data residency claims.** Privacy Policy section 8 states "US-East (iad)" for Fly.io and Neon. Verify this matches the actual region in `fly.toml` and the Neon project settings before go-live.
|
|
16
16
|
- [ ] **Age verification.** Both Privacy Policy (section 9) and Terms (section 3) set a 13+ minimum. If you ever target education or government markets you may need FERPA/COPPA review.
|
package/docs/llm-providers.md
CHANGED
|
@@ -46,7 +46,7 @@ Connects to an OpenAI-compatible server (LM Studio, Ollama, etc.).
|
|
|
46
46
|
|
|
47
47
|
| Var | Default | Purpose |
|
|
48
48
|
|-----|---------|---------|
|
|
49
|
-
| `ASHLR_LLM_URL` | `http://
|
|
49
|
+
| `ASHLR_LLM_URL` | `http://127.0.0.1:1234/v1` | Base URL of the local server |
|
|
50
50
|
| `ASHLR_LLM_KEY` | `local-llm` | Bearer token (most local servers don't validate) |
|
|
51
51
|
| `ASHLR_LLM_MODEL` | `qwen/qwen3-coder-30b@8bit` | Model name to request |
|
|
52
52
|
|
|
@@ -97,3 +97,17 @@ Implementation lives in `servers/_llm-providers/`:
|
|
|
97
97
|
| `index.ts` | `selectProvider()` + `summarizeIfLarge()` facade |
|
|
98
98
|
|
|
99
99
|
`servers/_summarize.ts` re-exports `summarizeIfLarge` from `index.ts` — all existing callers are unchanged.
|
|
100
|
+
|
|
101
|
+
## Strict gateway targets (opt-in)
|
|
102
|
+
|
|
103
|
+
Off by default. Set `ASHLR_GATEWAY_STRICT=1` to restrict where a model call that carries a key (`Authorization` or `x-api-key`) may go:
|
|
104
|
+
|
|
105
|
+
- `127.0.0.1` or `[::1]` by IP literal, over http or https on any port (a local LiteLLM, LM Studio or Ollama). `localhost` by name does not count.
|
|
106
|
+
- https to an exact `host:port` listed in `ASHLR_GATEWAY_HOSTS`, comma-separated, for example `ASHLR_GATEWAY_HOSTS=litellm.internal:4000,gw.example.com:443`. Port 443 is assumed when the URL has none. An entry without a port makes every listed target refuse until it is fixed.
|
|
107
|
+
- https to the built-in provider hosts on 443: `api.anthropic.com`, `api.openai.com`, `api.ashlr.ai`.
|
|
108
|
+
|
|
109
|
+
In strict mode, credentials in the URL are refused and redirects are never followed: a 3xx is a refusal, because fetch forwards a custom header such as `x-api-key` to the redirect target. A refused call fails the same way a network error does, so summarization falls back and orient and genome merging fall back to their offline paths. Refusal messages name the scheme, host and port only, never the key, path or query.
|
|
110
|
+
|
|
111
|
+
Covered calls: the local provider (`ASHLR_LLM_URL`, availability probe and chat), the legacy internal `endpointOverride` path, the Anthropic and cloud providers, token measurement, `ashlr__orient`'s LLM call, `ashlr__genome_consolidate`'s LLM merge, and the genome auto-consolidate reachability probes.
|
|
112
|
+
|
|
113
|
+
`ashlr__orient`, `ashlr__genome_consolidate`, the local and cloud providers (`ASHLR_LLM_URL`, `ASHLR_API_URL`), the Anthropic summarizer, token measurement and the genome auto-consolidate Anthropic and OpenAI probes apply the base rules even without strict mode (for the Anthropic and OpenAI calls the URL is `ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL`). For orient and genome, the endpoint comes only from `ASHLR_LLM_URL` (default `http://127.0.0.1:1234/v1`), which must be https or http to `127.0.0.1` / `[::1]`, with no credentials in the URL and no redirects followed. A tool argument cannot change it; genome's `model` argument only chooses `none` (offline) or LLM merging. Keyless calls (Ollama setup, `ASHLR_EMBED_URL` in its default Ollama format) are unchanged. The opt-in OpenAI-compatible embedding mode follows these rules; see [embeddings-gateway.md](embeddings-gateway.md).
|
package/docs/multi-host-mcp.md
CHANGED
|
@@ -47,6 +47,8 @@ ASHLR_MCP_HOST=claude-code # default — preserved for back-compat
|
|
|
47
47
|
ASHLR_MCP_HOST=cline
|
|
48
48
|
ASHLR_MCP_HOST=claude-desktop
|
|
49
49
|
ASHLR_MCP_HOST=codex-cli
|
|
50
|
+
ASHLR_MCP_HOST=gemini-cli
|
|
51
|
+
ASHLR_MCP_HOST=cursor
|
|
50
52
|
ASHLR_MCP_HOST=generic # safe default for anything else
|
|
51
53
|
```
|
|
52
54
|
|
|
@@ -61,7 +63,7 @@ If the env var is missing, the server infers `claude-code` when it sees
|
|
|
61
63
|
## Cline (cline_mcp_settings.json)
|
|
62
64
|
|
|
63
65
|
Cline's MCP config lives in your OS-specific Cline settings dir (see
|
|
64
|
-
[Cline MCP docs](https://
|
|
66
|
+
[Cline MCP docs](https://docs.cline.bot/mcp/mcp-overview)).
|
|
65
67
|
The file is typically at:
|
|
66
68
|
|
|
67
69
|
- macOS: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
|
|
@@ -121,6 +123,26 @@ Windows). Add:
|
|
|
121
123
|
|
|
122
124
|
Quit and reopen Claude Desktop. The tool count appears next to the input box.
|
|
123
125
|
|
|
126
|
+
## Gemini CLI (~/.gemini/settings.json)
|
|
127
|
+
|
|
128
|
+
Gemini CLI reads MCP servers from `~/.gemini/settings.json` (user) or
|
|
129
|
+
`.gemini/settings.json` (project) under `mcpServers`:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"mcpServers": {
|
|
134
|
+
"ashlr": {
|
|
135
|
+
"command": "bun",
|
|
136
|
+
"args": ["run", "/abs/path/to/ashlr-plugin/scripts/ashlr-mcp.ts"],
|
|
137
|
+
"env": { "ASHLR_MCP_HOST": "gemini-cli" }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Run `gemini mcp list` to confirm the server loads. Cursor uses the same shape in
|
|
144
|
+
`.cursor/mcp.json` with `ASHLR_MCP_HOST` set to `cursor`.
|
|
145
|
+
|
|
124
146
|
## OpenAI Codex CLI (~/.codex/config.toml)
|
|
125
147
|
|
|
126
148
|
Codex can install Ashlr as a plugin from `.codex-plugin/plugin.json`, or use
|
|
@@ -171,7 +193,7 @@ These features are host-agnostic and Just Work everywhere:
|
|
|
171
193
|
host. `ashlr-plugin stats --json` CLI works everywhere Bun runs.
|
|
172
194
|
- **Genome retrieval** — `.ashlrcode/genome/` lookups work identically.
|
|
173
195
|
Cloud-genome sync requires Pro/Team auth (see limitations below).
|
|
174
|
-
- **Telemetry consent** — `
|
|
196
|
+
- **Telemetry consent** — `ASHLR_TELEMETRY=on` honored identically.
|
|
175
197
|
No host ever auto-enables it.
|
|
176
198
|
- **Per-handler crash isolation** — one tool throwing won't kill the
|
|
177
199
|
process. Stack traces redirect to `~/.ashlr/crash-dumps/`.
|
|
@@ -203,7 +225,7 @@ conventions; we don't try to fight them.
|
|
|
203
225
|
Install Bun once: `curl -fsSL https://bun.sh/install | bash` (or use the
|
|
204
226
|
node trampoline shown above — it auto-installs Bun on first invocation).
|
|
205
227
|
|
|
206
|
-
**Tools register but
|
|
228
|
+
**Tools register but `ashlr-plugin stats --json` shows an empty ledger**
|
|
207
229
|
Stats are written on tool _call_, not on registration. Run any `ashlr__*`
|
|
208
230
|
tool from your host once, then `ashlr-plugin stats --json` will return data.
|
|
209
231
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Phantom agent preflight in `ashlr doctor`
|
|
2
|
+
|
|
3
|
+
`ashlr doctor` can run Phantom's value-free agent preflight,
|
|
4
|
+
`phantom agent doctor --json --strict`, and show its verdict as a
|
|
5
|
+
`phantom preflight` line in the install section. It is off by default.
|
|
6
|
+
|
|
7
|
+
| `ASHLR_PHANTOM_PREFLIGHT` | Line status |
|
|
8
|
+
|---|---|
|
|
9
|
+
| unset, `0` | No line; phantom is not run. |
|
|
10
|
+
| `1`, `true`, `on` | `ready` ok, `degraded` or `unknown` warn, `blocked` fail. |
|
|
11
|
+
| `strict` | `ready` ok, anything else fail. |
|
|
12
|
+
|
|
13
|
+
The preflight follows phantom-secrets
|
|
14
|
+
[docs/agent-preflight-contract.md](https://github.com/ashlrai/phantom-secrets/blob/main/docs/agent-preflight-contract.md)
|
|
15
|
+
(schema version 1; `--strict` exits 0 ready, 2 degraded, 1 blocked, 3 could
|
|
16
|
+
not evaluate). It reads only project files and environment variables: no vault
|
|
17
|
+
open, no keychain access.
|
|
18
|
+
|
|
19
|
+
The line shows the verdict and, when blocked, the critical check ids. It never
|
|
20
|
+
shows secret names, values or phantom's own messages. A missing `phantom`, or a
|
|
21
|
+
version without `agent doctor --json`, gives a warning line and nothing else.
|
|
22
|
+
Run `phantom agent doctor` for the full report and fix commands, and run tools
|
|
23
|
+
with `phantom exec -- <cmd>` so they get `phm_` placeholders instead of keys.
|
package/docs/team-genome.md
CHANGED
|
@@ -428,7 +428,8 @@ bun run scripts/genome-key.ts export <genomeId>
|
|
|
428
428
|
# Prints base32-encoded key after confirmation prompt
|
|
429
429
|
|
|
430
430
|
# On each team member's machine:
|
|
431
|
-
bun run scripts/genome-key.ts import <genomeId>
|
|
431
|
+
bun run scripts/genome-key.ts import <genomeId>
|
|
432
|
+
# paste the key when asked (input is hidden), or pipe it on stdin
|
|
432
433
|
```
|
|
433
434
|
|
|
434
435
|
Once the key file exists at `~/.ashlr/team-keys/<genomeId>.key`, every push is
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
writeFileSync,
|
|
17
17
|
} from "fs";
|
|
18
18
|
import { basename, dirname, join } from "path";
|
|
19
|
+
import { pidAlive } from "../servers/_pid-alive";
|
|
19
20
|
|
|
20
21
|
export const HOOK_TIMING_FILE_MAX_BYTES = 16 * 1024 * 1024;
|
|
21
22
|
export const HOOK_TIMING_BATCH_MAX_BYTES = 256 * 1024;
|
|
@@ -135,14 +136,6 @@ function readOwner(lockPath: string): LockOwner | null {
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
function processIsAlive(pid: number): boolean {
|
|
139
|
-
try {
|
|
140
|
-
process.kill(pid, 0);
|
|
141
|
-
return true;
|
|
142
|
-
} catch (error) {
|
|
143
|
-
return (error as NodeJS.ErrnoException).code !== "ESRCH";
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
139
|
|
|
147
140
|
interface LockIdentity {
|
|
148
141
|
lockDev: number;
|
|
@@ -219,7 +212,7 @@ function inspectStaleLock(lockPath: string): LockIdentity | null {
|
|
|
219
212
|
const age = Math.max(0, now - Math.min(created, mtimeMs));
|
|
220
213
|
if (age > LOCK_HARD_STALE_MS) return identity;
|
|
221
214
|
if (age <= LOCK_STALE_MS) return null;
|
|
222
|
-
return owner === null || !
|
|
215
|
+
return owner === null || !pidAlive(owner.pid) ? identity : null;
|
|
223
216
|
} catch {
|
|
224
217
|
return null;
|
|
225
218
|
}
|
package/hooks/hooks.json
CHANGED
|
@@ -5,31 +5,7 @@
|
|
|
5
5
|
"hooks": [
|
|
6
6
|
{
|
|
7
7
|
"type": "command",
|
|
8
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.ts\""
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"type": "command",
|
|
12
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-brief.ts\""
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"type": "command",
|
|
16
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-search.ts\""
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"type": "command",
|
|
20
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-lean-tools.ts\""
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"type": "command",
|
|
24
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-genome-author.ts\""
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"type": "command",
|
|
28
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-cost-refactor.ts\""
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"type": "command",
|
|
32
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-efficient.ts\""
|
|
8
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" --group \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-brief.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-search.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-lean-tools.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-genome-author.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-cost-refactor.ts\" \"${CLAUDE_PLUGIN_ROOT}/hooks/sessionstart-efficient.ts\""
|
|
33
9
|
}
|
|
34
10
|
]
|
|
35
11
|
}
|
|
@@ -168,90 +144,11 @@
|
|
|
168
144
|
],
|
|
169
145
|
"PostToolUse": [
|
|
170
146
|
{
|
|
171
|
-
"matcher": "mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write",
|
|
172
|
-
"hooks": [
|
|
173
|
-
{
|
|
174
|
-
"type": "command",
|
|
175
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-embedding.ts\""
|
|
176
|
-
}
|
|
177
|
-
]
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
"matcher": "Edit|MultiEdit|Write|Bash|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write",
|
|
181
|
-
"hooks": [
|
|
182
|
-
{
|
|
183
|
-
"type": "command",
|
|
184
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/audit-upload.ts\""
|
|
185
|
-
}
|
|
186
|
-
]
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
"matcher": "Edit|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural",
|
|
147
|
+
"matcher": "mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write|Edit|MultiEdit|Write|Bash|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__rename_file|Read|Grep|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep",
|
|
190
148
|
"hooks": [
|
|
191
149
|
{
|
|
192
150
|
"type": "command",
|
|
193
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/edit-batching-nudge.ts\""
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
"type": "command",
|
|
197
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/genome-scribe-hook.ts\""
|
|
198
|
-
}
|
|
199
|
-
]
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
"matcher": "Edit|Write|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__write|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__rename_file",
|
|
203
|
-
"hooks": [
|
|
204
|
-
{
|
|
205
|
-
"type": "command",
|
|
206
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-genome-refresh.ts\""
|
|
207
|
-
}
|
|
208
|
-
]
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
"matcher": "Read|Grep|Edit|Write|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write",
|
|
212
|
-
"hooks": [
|
|
213
|
-
{
|
|
214
|
-
"type": "command",
|
|
215
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/orient-nudge-hook.ts\""
|
|
216
|
-
}
|
|
217
|
-
]
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
"matcher": "Read|Grep|Edit|Write|MultiEdit",
|
|
221
|
-
"hooks": [
|
|
222
|
-
{
|
|
223
|
-
"type": "command",
|
|
224
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-native-nudge.ts\""
|
|
225
|
-
}
|
|
226
|
-
]
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
"matcher": "Read|Grep|Edit|Write|Bash|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write",
|
|
230
|
-
"hooks": [
|
|
231
|
-
{
|
|
232
|
-
"type": "command",
|
|
233
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-genome.ts\""
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
"type": "command",
|
|
237
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/session-log-append.ts\""
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
"type": "command",
|
|
241
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/pulse-emit.ts\""
|
|
242
|
-
}
|
|
243
|
-
]
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
"matcher": "Read|Grep|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep",
|
|
247
|
-
"hooks": [
|
|
248
|
-
{
|
|
249
|
-
"type": "command",
|
|
250
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-stale-result.ts\""
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
"type": "command",
|
|
254
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-dedupe-output.ts\""
|
|
151
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-bootstrap.mjs\" --group --match \"mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-embedding.ts\" --match \"Edit|MultiEdit|Write|Bash|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/audit-upload.ts\" --match \"Edit|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural\" \"${CLAUDE_PLUGIN_ROOT}/hooks/edit-batching-nudge.ts\" --match \"Edit|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural\" \"${CLAUDE_PLUGIN_ROOT}/hooks/genome-scribe-hook.ts\" --match \"Edit|Write|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__write|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__rename_file\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-genome-refresh.ts\" --match \"Read|Grep|Edit|Write|MultiEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/orient-nudge-hook.ts\" --match \"Read|Grep|Edit|Write|MultiEdit\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-native-nudge.ts\" --match \"Read|Grep|Edit|Write|Bash|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-use-genome.ts\" --match \"Read|Grep|Edit|Write|Bash|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/session-log-append.ts\" --match \"Read|Grep|Edit|Write|Bash|MultiEdit|NotebookEdit|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep|mcp__plugin_ashlr_ashlr__ashlr__edit|mcp__plugin_ashlr_ashlr__ashlr__multi_edit|mcp__plugin_ashlr_ashlr__ashlr__edit_structural|mcp__plugin_ashlr_ashlr__ashlr__notebook_edit|mcp__plugin_ashlr_ashlr__ashlr__write\" \"${CLAUDE_PLUGIN_ROOT}/hooks/pulse-emit.ts\" --match \"Read|Grep|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-stale-result.ts\" --match \"Read|Grep|mcp__plugin_ashlr_ashlr__ashlr__read|mcp__plugin_ashlr_ashlr__ashlr__grep\" \"${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-dedupe-output.ts\""
|
|
255
152
|
}
|
|
256
153
|
]
|
|
257
154
|
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
staleByteTotal,
|
|
32
32
|
STALE_BYTES_NUDGE_THRESHOLD,
|
|
33
33
|
} from "../servers/_history-tracker";
|
|
34
|
+
import { observeToolCall } from "../servers/_miss-tracker";
|
|
34
35
|
import { logPreCompactionNudgeEvent } from "../servers/_telemetry";
|
|
35
36
|
// logMultiTurnStaleEvent removed in v1.30 (1.5) — multi_turn_stale_estimate was never populated.
|
|
36
37
|
|
|
@@ -178,6 +179,8 @@ export function decide(
|
|
|
178
179
|
|
|
179
180
|
// Record this result in the history JSONL.
|
|
180
181
|
const content = extractContent(payload);
|
|
182
|
+
// Miss tracking (self-tuning P1): turn index of this call = entries so far.
|
|
183
|
+
observeToolCall(toolName, payload.tool_input, readHistory(sessionId, homeDir).length, sessionId, homeDir);
|
|
181
184
|
recordResult(toolName, content, sessionId, homeDir, now);
|
|
182
185
|
|
|
183
186
|
// -------------------------------------------------------------------------
|
|
@@ -416,7 +416,7 @@ export function buildToolRedirectBlock(opts: {
|
|
|
416
416
|
savingsPct: number;
|
|
417
417
|
bypassNote?: string;
|
|
418
418
|
}): RedirectBlockOutput {
|
|
419
|
-
const bypass = opts.bypassNote ??
|
|
419
|
+
const bypass = opts.bypassNote ?? 'set ASHLR_HOOK_MODE=nudge, or "hookMode": "nudge" in ~/.ashlr/config.json';
|
|
420
420
|
const reason =
|
|
421
421
|
`[ashlr bypass: ${bypass}] ~${opts.savingsPct}% token savings. Call this instead:\n\n` +
|
|
422
422
|
` tool_use: ${opts.mcpToolName}\n` +
|
|
@@ -112,7 +112,7 @@ if (!notebookPath || !isInsideCwd(notebookPath)) {
|
|
|
112
112
|
|
|
113
113
|
const mcp = "mcp__plugin_ashlr_ashlr__ashlr__notebook_edit";
|
|
114
114
|
const reason =
|
|
115
|
-
`[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge in ~/.ashlr/config.json. ` +
|
|
115
|
+
`[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge, or "hookMode": "nudge" in ~/.ashlr/config.json. ` +
|
|
116
116
|
`Current rule: blocking built-in NotebookEdit on ${notebookPath || "<notebook>"} — ` +
|
|
117
117
|
`call ${mcp} instead, which applies the cell edit and returns only the edited cell ` +
|
|
118
118
|
`plus one cell above and below (full notebook never echoed). ` +
|
|
@@ -118,7 +118,7 @@ if (!isInsideCwd(payload!.file_path)) {
|
|
|
118
118
|
|
|
119
119
|
const mcp = "mcp__plugin_ashlr_ashlr__ashlr__write";
|
|
120
120
|
const reason =
|
|
121
|
-
`[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge in ~/.ashlr/config.json. ` +
|
|
121
|
+
`[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge, or "hookMode": "nudge" in ~/.ashlr/config.json. ` +
|
|
122
122
|
`Current rule: blocking built-in Write on ${payload!.file_path} (${size} bytes) — ` +
|
|
123
123
|
`call ${mcp} instead, which delegates to ashlr__edit (diff format) for existing ` +
|
|
124
124
|
`files and returns only a compact diff summary, avoiding the full content echo ` +
|
package/hooks/session-start.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* (an empty additionalContext) rather than hang the session.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { keychainLoad, migrateEnvFileToken } from "../servers/_pro-token-keychain.ts";
|
|
25
26
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
26
27
|
import { homedir } from "os";
|
|
27
28
|
import { basename, dirname, join } from "path";
|
|
@@ -763,6 +764,22 @@ function sourceAshlrEnv(): void {
|
|
|
763
764
|
}
|
|
764
765
|
|
|
765
766
|
|
|
767
|
+
/**
|
|
768
|
+
* Move a plaintext ASHLR_PRO_TOKEN line from ~/.ashlr/env into the OS
|
|
769
|
+
* keychain (kept in the file if the keychain is unavailable), then load the
|
|
770
|
+
* token from the keychain when the environment does not already set it.
|
|
771
|
+
*/
|
|
772
|
+
function loadProTokenFromKeychain(): void {
|
|
773
|
+
try {
|
|
774
|
+
migrateEnvFileToken(join(homedir(), ".ashlr", "env"));
|
|
775
|
+
if (process.env["ASHLR_PRO_TOKEN"]) return;
|
|
776
|
+
const token = keychainLoad();
|
|
777
|
+
if (token) process.env["ASHLR_PRO_TOKEN"] = token;
|
|
778
|
+
} catch {
|
|
779
|
+
/* keychain is optional, never break the hook */
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
766
783
|
/**
|
|
767
784
|
* Detect the active ashlr tier for Q2 cloud delta sync. Mirrors
|
|
768
785
|
* scripts/check-tier.ts so we can call syncCloudDeltas without spawning a
|
|
@@ -789,10 +806,30 @@ async function detectTierForSync(): Promise<Tier> {
|
|
|
789
806
|
}
|
|
790
807
|
}
|
|
791
808
|
|
|
809
|
+
/**
|
|
810
|
+
* Race `work` against a `ms` ceiling and clear the timer once either side
|
|
811
|
+
* settles. A bare `setTimeout` in a Promise.race keeps the hook process alive
|
|
812
|
+
* until it fires, which held every SessionStart open for the full 2s.
|
|
813
|
+
*/
|
|
814
|
+
async function raceWithTimeout<T, F>(work: Promise<T>, ms: number, fallback: F): Promise<T | F> {
|
|
815
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
816
|
+
try {
|
|
817
|
+
return await Promise.race([
|
|
818
|
+
work,
|
|
819
|
+
new Promise<F>((resolve) => {
|
|
820
|
+
timer = setTimeout(() => resolve(fallback), ms);
|
|
821
|
+
}),
|
|
822
|
+
]);
|
|
823
|
+
} finally {
|
|
824
|
+
clearTimeout(timer);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
792
828
|
async function main(): Promise<void> {
|
|
793
829
|
// Source ~/.ashlr/env so ASHLR_PRO_TOKEN from the upgrade flow is available
|
|
794
830
|
// without requiring a shell restart.
|
|
795
831
|
sourceAshlrEnv();
|
|
832
|
+
loadProTokenFromKeychain();
|
|
796
833
|
|
|
797
834
|
// First-run: bootstrap dependencies if missing. Silent no-op otherwise.
|
|
798
835
|
ensureDepsInstalled();
|
|
@@ -846,24 +883,15 @@ async function main(): Promise<void> {
|
|
|
846
883
|
|
|
847
884
|
let result: BuildResult;
|
|
848
885
|
try {
|
|
849
|
-
result = await Promise.
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
additionalContext: "[ashlr baseline · timed out]",
|
|
859
|
-
},
|
|
860
|
-
},
|
|
861
|
-
notice: null,
|
|
862
|
-
}),
|
|
863
|
-
SCAN_BUDGET_MS,
|
|
864
|
-
)
|
|
865
|
-
),
|
|
866
|
-
]);
|
|
886
|
+
result = await raceWithTimeout(Promise.resolve(buildResponse()), SCAN_BUDGET_MS, {
|
|
887
|
+
output: {
|
|
888
|
+
hookSpecificOutput: {
|
|
889
|
+
hookEventName: "SessionStart",
|
|
890
|
+
additionalContext: "[ashlr baseline · timed out]",
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
notice: null,
|
|
894
|
+
});
|
|
867
895
|
} catch {
|
|
868
896
|
result = {
|
|
869
897
|
output: { hookSpecificOutput: { hookEventName: "SessionStart" } },
|
|
@@ -934,10 +962,7 @@ async function main(): Promise<void> {
|
|
|
934
962
|
// race only protects against the fire-and-forget reference outliving the
|
|
935
963
|
// hook (e.g. a pending fetch that the AbortController didn't actually
|
|
936
964
|
// abort because the runtime was wedged).
|
|
937
|
-
void
|
|
938
|
-
checkForUpdate({ currentVersion }),
|
|
939
|
-
new Promise<void>((resolve) => setTimeout(resolve, 2000)),
|
|
940
|
-
]).catch((e) => {
|
|
965
|
+
void raceWithTimeout(checkForUpdate({ currentVersion }), 2000, undefined).catch((e) => {
|
|
941
966
|
process.stderr.write(
|
|
942
967
|
"[ashlr-session-start] update check failed: " +
|
|
943
968
|
(e instanceof Error ? e.message : String(e)) +
|
|
@@ -969,10 +994,7 @@ async function main(): Promise<void> {
|
|
|
969
994
|
// fetch can never blow the SessionStart budget. Failures are swallowed.
|
|
970
995
|
try {
|
|
971
996
|
const tier = await detectTierForSync();
|
|
972
|
-
await
|
|
973
|
-
syncCloudDeltas({ tier }),
|
|
974
|
-
new Promise<void>((resolve) => setTimeout(resolve, 2000)),
|
|
975
|
-
]);
|
|
997
|
+
await raceWithTimeout(syncCloudDeltas({ tier }), 2000, undefined);
|
|
976
998
|
} catch (e) {
|
|
977
999
|
process.stderr.write(
|
|
978
1000
|
"[ashlr-session-start] cloud delta sync failed: " +
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ashlr-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.1",
|
|
4
|
+
"mcpName": "io.github.ashlrai/ashlr-plugin",
|
|
4
5
|
"description": "Multi-host AI coding efficiency layer for Codex and Claude Code, powered by @ashlr/core-efficiency.",
|
|
5
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ashlrai/ashlr-plugin.git"
|
|
10
|
+
},
|
|
6
11
|
"type": "module",
|
|
7
12
|
"bin": {
|
|
8
|
-
"ashlr-plugin": "./scripts/
|
|
9
|
-
"ashlr-mcp": "./scripts/ashlr-mcp.
|
|
13
|
+
"ashlr-plugin": "./scripts/ashlr-plugin.mjs",
|
|
14
|
+
"ashlr-mcp": "./scripts/ashlr-mcp.mjs"
|
|
10
15
|
},
|
|
11
16
|
"files": [
|
|
12
17
|
".agents/plugins/marketplace.json",
|
|
@@ -39,7 +44,7 @@
|
|
|
39
44
|
"version:check": "bun run scripts/check-version-sync.ts"
|
|
40
45
|
},
|
|
41
46
|
"dependencies": {
|
|
42
|
-
"@ashlr/core-efficiency": "
|
|
47
|
+
"@ashlr/core-efficiency": "0.3.0",
|
|
43
48
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
44
49
|
"minimatch": "^10.2.5",
|
|
45
50
|
"postgres": "^3.4.9",
|