@hasna/hooks 0.5.0 → 0.6.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/README.md +81 -8
- package/bin/index.js +1756 -382
- package/bin/serve.js +5258 -0
- package/dist/cf/provision.d.ts +24 -0
- package/dist/config.d.ts +18 -0
- package/dist/db/legacy-import.d.ts +1 -1
- package/dist/db/migrations/004_hooks_table.d.ts +9 -0
- package/dist/db/pg-migrations.d.ts +1 -1
- package/dist/db/storage-sync.d.ts +26 -6
- package/dist/index.d.ts +18 -2
- package/dist/index.js +5351 -286
- package/dist/lib/custom-install.d.ts +19 -0
- package/dist/lib/manifest.d.ts +70 -0
- package/dist/lib/resolve.d.ts +19 -0
- package/dist/lib/run.d.ts +37 -0
- package/dist/lib/store.d.ts +69 -0
- package/dist/lib/sync.d.ts +35 -0
- package/dist/serve.d.ts +36 -0
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +133 -42
- package/hooks/codewith-native-common.test.ts +15 -2
- package/hooks/hook-scanoutput/README.md +151 -0
- package/hooks/hook-scanoutput/package.json +12 -0
- package/hooks/hook-scanoutput/src/hook.test.ts +217 -0
- package/hooks/hook-scanoutput/src/hook.ts +319 -0
- package/hooks/hook-workspace-repos-guard/README.md +63 -0
- package/hooks/hook-workspace-repos-guard/package.json +12 -0
- package/hooks/hook-workspace-repos-guard/src/hook.test.ts +312 -0
- package/hooks/hook-workspace-repos-guard/src/hook.ts +352 -0
- package/hooks/hook-workspace-repos-guard/tsconfig.json +21 -0
- package/hooks/mention-context/README.md +109 -0
- package/hooks/mention-context/package.json +9 -0
- package/hooks/mention-context/src/hasna-mention-context.py +1218 -0
- package/hooks/mention-context/src/hasna-mention-warm.py +521 -0
- package/hooks/mention-context/src/hook.test.ts +68 -0
- package/hooks/mention-context/src/test_run_capture.py +345 -0
- package/package.json +9 -5
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# mention-context
|
|
2
|
+
|
|
3
|
+
A `UserPromptSubmit` hook. When a prompt mentions `hasna/<repo>` or `hasnaxyz/<repo>`,
|
|
4
|
+
it injects a short context block for that repository — local checkout HEAD, installed
|
|
5
|
+
version, worktrees, npm version, GitHub default branch and open PRs.
|
|
6
|
+
|
|
7
|
+
This is the first Python hook in this repository. Everything else under `hooks/` is
|
|
8
|
+
TypeScript. The language is not a preference: this hook runs on **every prompt
|
|
9
|
+
submission** under a sub-second budget, and starting a Python interpreter that exits
|
|
10
|
+
immediately when no token matches is measurably cheaper here than the alternative that
|
|
11
|
+
was available when it was written. A TypeScript port is a reasonable future change; it
|
|
12
|
+
is a rewrite, not a move, and it is out of scope for the defect this directory was
|
|
13
|
+
created to fix.
|
|
14
|
+
|
|
15
|
+
## Layout
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/hasna-mention-context.py the hook
|
|
19
|
+
src/hasna-mention-warm.py the out-of-band cache warmer — see "The two files are a pair"
|
|
20
|
+
src/hook.test.ts bun-test wrapper — runs the Python suite under `bun test`
|
|
21
|
+
src/test_run_capture.py the Python regression suite
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Running the tests
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun test hooks/mention-context # via the wrapper, as CI runs it
|
|
28
|
+
python3 hooks/mention-context/src/test_run_capture.py -v # directly
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The wrapper exists so the Python suite runs under the repository's existing `bun test`
|
|
32
|
+
step with no CI workflow change. It **fails** rather than skips when `python3` is
|
|
33
|
+
absent: a skip is indistinguishable from a pass in the summary line, and a regression
|
|
34
|
+
test that can silently not run is not a regression test.
|
|
35
|
+
|
|
36
|
+
## The two files are a pair, and each resolves the other by directory
|
|
37
|
+
|
|
38
|
+
`hasna-mention-context.py` and `hasna-mention-warm.py` must be installed **into the same
|
|
39
|
+
directory**. Neither takes a configured path for the other; each derives the other's
|
|
40
|
+
location from its own `__file__`. The coupling runs in both directions and the two
|
|
41
|
+
failures look nothing alike.
|
|
42
|
+
|
|
43
|
+
**Warmer missing, hook present — silent.** The hook computes
|
|
44
|
+
`WARM_BIN = <its own dir>/hasna-mention-warm.py` and guards the call with
|
|
45
|
+
`os.path.isfile`, so `request_warm()` simply returns. Nothing raises, nothing is logged,
|
|
46
|
+
and the prompt block still renders. What stops is the cold-cache self-heal: the hook
|
|
47
|
+
hands every degraded or GitHub-less token to the warmer precisely so the *next* prompt
|
|
48
|
+
is clean, and that hand-off never happens. A repository mentioned for the first time is
|
|
49
|
+
degraded once and then stays degraded on every later prompt, instead of being clean
|
|
50
|
+
thereafter. It does not recover on its own, because the hook's own live GitHub probe
|
|
51
|
+
cannot close the gap — by the measurement recorded in the hook's header, `gh api graphql`
|
|
52
|
+
takes 1.02–1.18 s against a whole-hook deadline of 0.900 s, so the probe is killed before
|
|
53
|
+
it can write the cache. The warmer is the only thing that reliably fills it.
|
|
54
|
+
|
|
55
|
+
**Hook missing, warmer present — loud.** The warmer computes
|
|
56
|
+
`HOOK_PATH = <its own dir>/hasna-mention-context.py` and imports it at *module scope*
|
|
57
|
+
(`H = load_hook()`), with no guard, to keep one definition of the cache paths, the entry
|
|
58
|
+
shapes and the sanitizer. An absent hook is an immediate `FileNotFoundError` and the
|
|
59
|
+
warmer does not start at all.
|
|
60
|
+
|
|
61
|
+
Because the warmer imports the hook, the hook's `CACHE_DIR`, cache entry shape and
|
|
62
|
+
sanitizer are a **contract with a second program in this directory**, not private
|
|
63
|
+
details. Changing them means changing both files together.
|
|
64
|
+
|
|
65
|
+
Note that the scheduled warming path does not depend on co-location: the cron entry
|
|
66
|
+
invokes the warmer by absolute path. Co-location is what the hook's self-heal path needs.
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
This directory is the source. The hook is installed by copying
|
|
71
|
+
`src/hasna-mention-context.py` to the path registered in the agent's settings
|
|
72
|
+
(`~/.hasna/hooks/bin/hasna-mention-context.py` on the current fleet) and is registered
|
|
73
|
+
there by absolute path under `UserPromptSubmit`.
|
|
74
|
+
|
|
75
|
+
Copy `src/hasna-mention-warm.py` to that **same directory** in the same step, and
|
|
76
|
+
schedule it. On the current fleet it runs from cron at minutes 1, 11, 21, 31, 41 and 51
|
|
77
|
+
under `flock`, and it holds its own lock, so overlapping runs are harmless. Installing
|
|
78
|
+
the hook alone is a supported thing to do — the hook works without the warmer — but it
|
|
79
|
+
costs the self-heal described above, silently, so it should be a decision rather than an
|
|
80
|
+
oversight.
|
|
81
|
+
|
|
82
|
+
Installation is deliberately **not** performed by merging this directory. The hook
|
|
83
|
+
renders into every agent's prompt on every firing, so landing the source and updating
|
|
84
|
+
the live path are two separately verified steps.
|
|
85
|
+
|
|
86
|
+
## The defect this directory was created to fix
|
|
87
|
+
|
|
88
|
+
`run_capture` derived its temp-file path from its `tag` argument alone, and
|
|
89
|
+
`probe_local_head` passed a constant `tag="gitlog"`. Repository probes run concurrently
|
|
90
|
+
against one shared temp directory, so every mentioned repository's `git log` wrote to
|
|
91
|
+
and read back the same `gitlog.out`, and the reader got whatever the last writer left.
|
|
92
|
+
|
|
93
|
+
The emitted value was always a **real sha from a real repository — just the wrong
|
|
94
|
+
one**, which is why it read as correct. Four consecutive firings, each a different wrong
|
|
95
|
+
pairing:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
17:44 loops=82a3acf (right) accounts=27cffd7 (right) logs=absent
|
|
99
|
+
17:48 loops=27cffd7 (WRONG) logs=82a3acf (WRONG) <- clean swap
|
|
100
|
+
18:11 loops=27cffd7 (WRONG) logs=146a70e (right)
|
|
101
|
+
18:17 loops=82a3acf (right) logs=82a3acf (WRONG) <- duplicate sha
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The race needs two or three mentions in one prompt. `MAX_TOKENS = 3`, so a single-repo
|
|
105
|
+
mention produces one probe, no concurrency, and always the correct answer.
|
|
106
|
+
|
|
107
|
+
The fix is in `run_capture`, which owns path construction, rather than at the call site:
|
|
108
|
+
threading `org`/`word` into `probe_local_head` would have matched its siblings but left
|
|
109
|
+
the invariant unenforced, so a fifth call site added later would inherit the bug.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mention-context",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "UserPromptSubmit hook that injects repository context for @hasna/<repo> mentions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/hasna-mention-context.py",
|
|
7
|
+
"author": "Hasna",
|
|
8
|
+
"license": "Apache-2.0"
|
|
9
|
+
}
|