@raymondchins/agentmap 0.2.2 → 0.3.0

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 CHANGED
@@ -6,12 +6,13 @@
6
6
 
7
7
  **The repo map your coding agent is _forced_ to use — ~98% fewer tokens to understand your TS/JS codebase.**
8
8
 
9
- A queryable, ranked code-relationship map for TypeScript/JavaScript repos that answers
10
- "understand the codebase" questions in **~98% fewer tokens on average** (up to **99.9%
11
- per task**) vs reading raw files. Personalized PageRank importance, Aider-style symbol
12
- ranking, a token-budgeted digest, and a single `--any` router (file symbol feature →
13
- live git-grep) — wired straight into the agent loop so it actually gets used, not just
14
- published.
9
+ Your AI coding agent re-learns your codebase every session opening files and grepping to find
10
+ what connects to what, burning tokens before it writes a line. agentmap gives it a **queryable,
11
+ ranked code-relationship map for TypeScript/JavaScript repos** instead a `ts-morph` import/symbol
12
+ graph ranked by personalized PageRank. Ask it to *"add a field"* or *"fix the login bug"* and it
13
+ finds the right files, their imports, and what already exists in
14
+ **~98% fewer tokens on average** (up to **99.9% per task**) — kept current by a post-commit
15
+ auto-refresh and actually used via a `PreToolUse(Grep)` hook.
15
16
 
16
17
  [![npm](https://img.shields.io/npm/v/@raymondchins/agentmap)](https://www.npmjs.com/package/@raymondchins/agentmap)
17
18
  [![CI](https://github.com/raymondchins/agentmap/actions/workflows/ci.yml/badge.svg)](https://github.com/raymondchins/agentmap/actions/workflows/ci.yml)
@@ -23,13 +24,61 @@ published.
23
24
 
24
25
  ---
25
26
 
27
+ ## Benchmark
28
+
29
+ Every task you hand a coding agent starts with the same hidden step — *find the relevant code*.
30
+ Here's the token cost of that step, **reading raw files vs querying agentmap**, on a real 154-file
31
+ Next.js app ([vercel/ai-chatbot](https://github.com/vercel/ai-chatbot)). Every figure is captured
32
+ tool output (`node benchmark/bench.mjs <repo>` at the pinned sha):
33
+
34
+ <table width="100%">
35
+ <thead>
36
+ <tr>
37
+ <th align="left">The question the agent has to answer first</th>
38
+ <th align="right">Reading files</th>
39
+ <th align="right">With agentmap</th>
40
+ <th align="right">Saved</th>
41
+ </tr>
42
+ </thead>
43
+ <tbody>
44
+ <tr><td align="left">Where is this symbol defined?</td><td align="right">1,950</td><td align="right">20</td><td align="right">99%</td></tr>
45
+ <tr><td align="left">Does a helper for this already exist? <i>(reuse)</i></td><td align="right">14,740</td><td align="right">19</td><td align="right">99.9%</td></tr>
46
+ <tr><td align="left">What breaks if I change this file? <i>(blast radius)</i></td><td align="right">81,038</td><td align="right">616</td><td align="right">99.2%</td></tr>
47
+ <tr><td align="left">What files make up this feature?</td><td align="right">6,121</td><td align="right">1,025</td><td align="right">83.3%</td></tr>
48
+ <tr><td align="left">Give me a repo overview</td><td align="right">3,065</td><td align="right">1,127</td><td align="right">63.2%</td></tr>
49
+ <tr><td align="left">Load the whole repo into context</td><td align="right">150,281</td><td align="right">1,127</td><td align="right">99.3%</td></tr>
50
+ <tr><td align="left">What does this one file import?</td><td align="right">583</td><td align="right">517</td><td align="right">11.3%</td></tr>
51
+ <tr><td align="left"><b>All 7 tasks combined</b></td><td align="right"><b>257,778</b></td><td align="right"><b>4,451</b></td><td align="right"><b>98.3%</b></td></tr>
52
+ </tbody>
53
+ </table>
54
+
55
+ <sub>Context tokens the agent burns to answer each question — token est = chars/4, applied to both sides.</sub>
56
+
57
+ That's the agent reaching the same answer on **58× fewer tokens** overall — and the pattern holds
58
+ across [zod](https://github.com/colinhacks/zod) (367 files, **99.2%**) and
59
+ [taxonomy](https://github.com/shadcn-ui/taxonomy) (125 files, **96.0%**), peaking at **646× fewer**
60
+ on a single whole-repo map. Reproducible at pinned shas; full per-scenario tables in
61
+ **[`./benchmark/RESULTS.md`](./benchmark/RESULTS.md)**.
62
+
63
+ **Speed:** a cold build (parse + PageRank + symbol graph) takes **~1.2s**; a warm cached query
64
+ returns in **~0.1s** (the lazy-loaded path added in 0.2.2) — the agent has a ranked answer back
65
+ before it would have finished opening the first handful of files.
66
+
67
+ Honest notes: the win scales with the work — the small rows above (63%, 11%) are the floor, and a
68
+ *trivial single-file* lookup can even cost **more** than `cat`+`grep` (taxonomy's file-import task
69
+ hit −313%; we leave it in). Numbers measure **context-token volume**, not answer quality or wall-clock.
70
+
71
+ ---
72
+
26
73
  ## Why it's different
27
74
 
28
- Most "repo context" tools are one-shot: they pack the repository (or a slice of it) into a
29
- prompt and stop there. agentmap is a **queryable, ranked, and self-refreshing** map that an
30
- agent can interrogate flag-by-flag — and, crucially, is **wired into the agent loop** via a
31
- post-commit auto-refresh and a `PreToolUse` hook that nudges the agent to use the map
32
- *before* it falls back to serial grep.
75
+ Most "repo context" tools are a photocopy: they dump your repository (or a slice of it) into
76
+ the prompt once and walk away. The copy goes stale the moment you edit a file, and nothing
77
+ makes the agent actually read it.
78
+
79
+ agentmap is the opposite a **queryable, ranked, self-refreshing** map the agent interrogates
80
+ flag-by-flag, that **rebuilds itself on every commit**, and that a `PreToolUse` hook steers the
81
+ agent toward *before* it falls back to serial grep.
33
82
 
34
83
  | | **agentmap** | Aider repo map | RepoMapper | Repomix | code2prompt |
35
84
  | --- | --- | --- | --- | --- | --- |
@@ -47,6 +96,84 @@ and it's a **file-level import graph**, not a full call-site/reference resolver
47
96
 
48
97
  ---
49
98
 
99
+ ## The agent loop (the actual point)
100
+
101
+ Here's the quiet failure of every other repo-map tool: it builds a beautiful map, and then the
102
+ agent forgets it exists and greps anyway. A map the agent doesn't open is just dead weight.
103
+
104
+ agentmap closes that loop. Two hooks (in [`./hooks/`](./hooks/)) do the work: the map
105
+ **refreshes itself after every commit**, and the agent gets **nudged to query it before it
106
+ serial-greps**. You wire it once — then it stays current on its own, and stays used.
107
+
108
+ ### 1. Auto-refresh on commit
109
+
110
+ [`hooks/post-commit`](./hooks/post-commit) rebuilds `.claude/agentmap.json` after each
111
+ commit, detached + silenced so it never slows the commit. It skips during
112
+ rebase/merge/cherry-pick and no-ops if Node is missing.
113
+
114
+ The hooks ship inside the npm package. The simplest setup:
115
+
116
+ ```bash
117
+ npx @raymondchins/agentmap --install-hooks
118
+ ```
119
+
120
+ This copies `hooks/post-commit` into `.git/hooks/`, sets it executable, ensures
121
+ `.claude/agentmap.json` is in `.gitignore`, and **auto-wires the `PreToolUse` nudge
122
+ hook into `.claude/settings.json`** (merge-safe + idempotent) so map enforcement is
123
+ on by default — no manual paste. Manual alternative for just the post-commit hook:
124
+
125
+ ```bash
126
+ # from your repo root
127
+ cp hooks/post-commit .git/hooks/post-commit
128
+ chmod +x .git/hooks/post-commit
129
+ ```
130
+
131
+ The hook auto-locates the builder: a local `agentmap.mjs`, then `scripts/agentmap.mjs`, then
132
+ the installed `agentmap` binary, then `npx --no-install @raymondchins/agentmap`.
133
+
134
+ ### 2. Force the agent to use it — `PreToolUse` hook
135
+
136
+ [`hooks/agentmap-nudge.mjs`](./hooks/agentmap-nudge.mjs) is a **non-blocking** hook for
137
+ Claude Code that covers **both** the `Grep` tool and raw Bash text-searchers
138
+ (`grep`/`rg`/`egrep`/`fgrep`/`ag`/`ack`). When either looks like a dependency /
139
+ who-imports / component-usage / reuse / where-is-symbol search, it injects a reminder
140
+ steering the agent to `agentmap --any` first. It never denies the call, and stays silent
141
+ for raw-string / Tailwind-class / lowercase-HTML-tag sweeps and for pipe-filtered commands
142
+ like `ps aux | grep node` — so it's high-signal, not nagging.
143
+
144
+ **Fires on:** `import`/`require`/`export`/`from '...'` patterns, JSX component tags
145
+ (`<Hero`, `<ProviderCard`), explicit intent words (`where is`, `who imports`, `reuse`,
146
+ `existing component`), and — in the Bash branch — bare multi-hump PascalCase identifiers
147
+ (`ProviderCard`, `TopProviders`) that almost always mean "where is this symbol / who uses
148
+ it". The Bash branch only fires when the searcher is the *primary* command (at the start,
149
+ or after `;`/`&&`); piped log-filters stay silent.
150
+
151
+ `--install-hooks` writes both matchers into `.claude/settings.json` for you (merge-safe —
152
+ preserves existing settings, won't duplicate on re-run). The single hook file dispatches
153
+ internally on `tool_name`. For reference, or to wire it by hand:
154
+
155
+ ```json
156
+ {
157
+ "hooks": {
158
+ "PreToolUse": [
159
+ {
160
+ "matcher": "Grep",
161
+ "hooks": [{ "type": "command", "command": "node ./hooks/agentmap-nudge.mjs" }]
162
+ },
163
+ {
164
+ "matcher": "Bash",
165
+ "hooks": [{ "type": "command", "command": "node ./hooks/agentmap-nudge.mjs" }]
166
+ }
167
+ ]
168
+ }
169
+ }
170
+ ```
171
+
172
+ That's the "forced to use it" in the tagline: the map stays current on its own, and the
173
+ agent is steered to it the moment it reaches for a dependency-shaped grep or Bash search.
174
+
175
+ ---
176
+
50
177
  ## Quickstart
51
178
 
52
179
  No install needed:
@@ -77,8 +204,9 @@ agentmap: 154 files | 4 features | top hub: lib/utils.ts (deg 52, pr 0.105171)
77
204
 
78
205
  ## The `--any` router
79
206
 
80
- One flag, no flag-picking. `--any <query>` resolves your query through a cascade and
81
- returns the first layer that hits:
207
+ Don't want to learn eight flags? You don't have to. Throw anything at `--any` a filename, a
208
+ function, a feature, even a raw string — and it figures out what you meant, returning the first
209
+ layer that hits:
82
210
 
83
211
  ```
84
212
  --any <query>
@@ -346,70 +474,6 @@ $ node agentmap.mjs --print | jq '.hubs[0]'
346
474
 
347
475
  ---
348
476
 
349
- ## The agent loop (the actual point)
350
-
351
- A repo map only helps if the agent uses it. agentmap ships two hooks (in [`./hooks/`](./hooks/))
352
- that close the loop: the map refreshes itself after every commit, and the agent gets nudged
353
- to query the map before it serial-greps.
354
-
355
- ### 1. Auto-refresh on commit
356
-
357
- [`hooks/post-commit`](./hooks/post-commit) rebuilds `.claude/agentmap.json` after each
358
- commit, detached + silenced so it never slows the commit. It skips during
359
- rebase/merge/cherry-pick and no-ops if Node is missing.
360
-
361
- The hooks ship inside the npm package. The simplest setup:
362
-
363
- ```bash
364
- npx @raymondchins/agentmap --install-hooks
365
- ```
366
-
367
- This copies `hooks/post-commit` into `.git/hooks/`, sets it executable, ensures
368
- `.claude/agentmap.json` is in `.gitignore`, and **auto-wires the `PreToolUse` nudge
369
- hook into `.claude/settings.json`** (merge-safe + idempotent) so map enforcement is
370
- on by default — no manual paste. Manual alternative for just the post-commit hook:
371
-
372
- ```bash
373
- # from your repo root
374
- cp hooks/post-commit .git/hooks/post-commit
375
- chmod +x .git/hooks/post-commit
376
- ```
377
-
378
- The hook auto-locates the builder: a local `agentmap.mjs`, then `scripts/agentmap.mjs`, then
379
- the installed `agentmap` binary, then `npx --no-install @raymondchins/agentmap`.
380
-
381
- ### 2. Force the agent to use it — `PreToolUse` hook
382
-
383
- [`hooks/agentmap-nudge.mjs`](./hooks/agentmap-nudge.mjs) is a **non-blocking** `PreToolUse(Grep)`
384
- hook for Claude Code. When a `Grep` looks like a dependency / who-imports / component-usage /
385
- reuse search, it injects a reminder steering the agent to `agentmap --any` first. It never
386
- denies the grep, and stays silent for raw-string / Tailwind-class / lowercase-HTML-tag
387
- sweeps — so it's high-signal, not nagging.
388
-
389
- `--install-hooks` writes this into `.claude/settings.json` for you (merge-safe — it
390
- preserves existing settings and won't duplicate on re-run). For reference, or to wire
391
- it by hand:
392
-
393
- ```json
394
- {
395
- "hooks": {
396
- "PreToolUse": [
397
- {
398
- "matcher": "Grep",
399
- "hooks": [
400
- { "type": "command", "command": "node ./hooks/agentmap-nudge.mjs" }
401
- ]
402
- }
403
- ]
404
- }
405
- }
406
- ```
407
-
408
- That's the "forced to use it" in the tagline: the map stays current on its own, and the
409
- agent is steered to it the moment it reaches for a dependency-shaped grep.
410
-
411
- ---
412
-
413
477
  ## Scope & limitations
414
478
 
415
479
  Honesty first — this is deliberately a small, sharp tool, not a universal code-graph.
@@ -436,24 +500,6 @@ Honesty first — this is deliberately a small, sharp tool, not a universal code
436
500
 
437
501
  ---
438
502
 
439
- ## Benchmark
440
-
441
- Measured across **7 agent tasks on 3 real public repos** — reproducible with `node benchmark/bench.mjs <repo>`:
442
-
443
- | Repo | Files | Tokens saved |
444
- |------|------:|-------------:|
445
- | [vercel/ai-chatbot](https://github.com/vercel/ai-chatbot) | 154 | **98.3%** |
446
- | [colinhacks/zod](https://github.com/colinhacks/zod) | 367 | **99.2%** |
447
- | [shadcn-ui/taxonomy](https://github.com/shadcn-ui/taxonomy) | 125 | **96.0%** |
448
-
449
- Per-task peaks (real, across the three repos): **whole-repo map 99.8%**, **reuse-before-rebuild lookup 99.9%**, **blast-radius 99.2%**, **find-symbol 99%**. Cold build (parse + PageRank + symbol graph) **~1.2s**; warm cached query **~0.2s**.
450
-
451
- Honest notes: the win scales with repo size — a *trivial single-file* `--any` lookup can actually cost **more** than `cat`+`grep` (taxonomy showed −313% on that one task; we leave it in). Numbers measure **context-token volume**, not end-to-end retrieval accuracy. Token est = `chars / 4`, applied to both sides.
452
-
453
- Full methodology, per-repo tables, and all caveats: **[`./benchmark/RESULTS.md`](./benchmark/RESULTS.md)**.
454
-
455
- ---
456
-
457
503
  ## Contributing
458
504
 
459
505
  Issues and PRs welcome. High-value directions:
package/agentmap.mjs CHANGED
@@ -545,7 +545,7 @@ function fileBlock(key, f) {
545
545
  // ---------------------------------------------------------------------------
546
546
  // --install-hooks: copy the package post-commit hook into .git/hooks, ensure
547
547
  // .claude/agentmap.json is gitignored, and auto-wire the Claude Code
548
- // PreToolUse(Grep) nudge into the project's .claude/settings.json so map
548
+ // PreToolUse(Grep|Bash) nudge into the project's .claude/settings.json so map
549
549
  // enforcement is ON by default (no manual copy-paste). Merge-safe + idempotent.
550
550
  // Throws on any failure so the caller can stderr+exit 1.
551
551
  // ---------------------------------------------------------------------------
@@ -574,11 +574,13 @@ function installHooks() {
574
574
  writeFileSync(".gitignore", IGNORE_LINE + "\n");
575
575
  }
576
576
 
577
- // Auto-wire the PreToolUse(Grep) enforcement nudge into the PROJECT settings
578
- // (.claude/settings.json) so "the agent is forced to use the map" is ON by
579
- // default — not a manual paste. Merge-safe + idempotent: preserves any
580
- // existing settings/hooks, never duplicates our entry. Uses a project-relative
581
- // command so a committed settings.json stays portable across machines.
577
+ // Auto-wire the PreToolUse(Grep|Bash) enforcement nudge into the PROJECT
578
+ // settings (.claude/settings.json) so "the agent is forced to use the map"
579
+ // is ON by default — not a manual paste. Merge-safe + idempotent: preserves
580
+ // any existing settings/hooks, never duplicates our entry. Uses a project-
581
+ // relative command so a committed settings.json stays portable across machines.
582
+ // Both the Grep tool AND raw Bash searchers (grep/rg/ag/ack) are covered by
583
+ // a single hook file — the nudge routes internally based on tool_name.
582
584
  const NUDGE_CMD = "node node_modules/@raymondchins/agentmap/hooks/agentmap-nudge.mjs";
583
585
  const settingsPath = ".claude/settings.json";
584
586
  let settings = {};
@@ -588,11 +590,21 @@ function installHooks() {
588
590
  }
589
591
  settings.hooks ??= {};
590
592
  settings.hooks.PreToolUse ??= [];
591
- const alreadyWired = settings.hooks.PreToolUse.some(
592
- (e) => Array.isArray(e?.hooks) && e.hooks.some((h) => typeof h?.command === "string" && h.command.includes("agentmap-nudge")),
593
+ // Check whether BOTH matchers are already present.
594
+ const hasGrep = settings.hooks.PreToolUse.some(
595
+ (e) => e?.matcher === "Grep" && Array.isArray(e?.hooks) && e.hooks.some((h) => typeof h?.command === "string" && h.command.includes("agentmap-nudge")),
593
596
  );
594
- if (!alreadyWired) {
597
+ const hasBash = settings.hooks.PreToolUse.some(
598
+ (e) => e?.matcher === "Bash" && Array.isArray(e?.hooks) && e.hooks.some((h) => typeof h?.command === "string" && h.command.includes("agentmap-nudge")),
599
+ );
600
+ const alreadyWired = hasGrep && hasBash;
601
+ if (!hasGrep) {
595
602
  settings.hooks.PreToolUse.push({ matcher: "Grep", hooks: [{ type: "command", command: NUDGE_CMD }] });
603
+ }
604
+ if (!hasBash) {
605
+ settings.hooks.PreToolUse.push({ matcher: "Bash", hooks: [{ type: "command", command: NUDGE_CMD }] });
606
+ }
607
+ if (!alreadyWired) {
596
608
  mkdirSync(".claude", { recursive: true });
597
609
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
598
610
  }
@@ -601,8 +613,8 @@ function installHooks() {
601
613
  console.log(`installed post-commit hook → ${dest}`);
602
614
  console.log(ignoredAlready ? `.gitignore already has ${IGNORE_LINE}` : `added ${IGNORE_LINE} to .gitignore`);
603
615
  console.log(alreadyWired
604
- ? `${settingsPath} already wires the PreToolUse(Grep) → agentmap nudge — left as-is`
605
- : `wired PreToolUse(Grep) → agentmap nudge into ${settingsPath} (map enforcement on by default)`);
616
+ ? `${settingsPath} already wires the PreToolUse(Grep|Bash) → agentmap nudge — left as-is`
617
+ : `wired PreToolUse(Grep|Bash) → agentmap nudge into ${settingsPath} (map enforcement on by default)`);
606
618
  console.log("\nDone — the map auto-refreshes on commit, and greps are nudged to agentmap first.");
607
619
  }
608
620
 
@@ -1,24 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
  // SPDX-License-Identifier: MIT
3
3
  // ============================================================================
4
- // agentmap — PreToolUse(Grep) nudge hook
4
+ // agentmap — PreToolUse nudge hook (Grep tool + Bash text-searchers)
5
5
  //
6
- // Steers dependency / who-imports / reuse / component-usage greps toward the
7
- // agentmap repo-map instead of serial grep. NON-BLOCKING: only ever injects a
8
- // reminder via `additionalContext`; never denies the Grep. Exits 0 on every
9
- // path. Dependency-free (Node stdlib only) — Claude Code pipes the tool-call
10
- // JSON on stdin.
6
+ // Steers dependency / who-imports / reuse / component-usage / where-is-symbol
7
+ // searches toward the agentmap repo-map instead of serial grep. NON-BLOCKING:
8
+ // only ever injects a reminder via `additionalContext`; never denies the call.
9
+ // Exits 0 on every path. Dependency-free (Node stdlib only) — Claude Code
10
+ // pipes the tool-call JSON on stdin.
11
11
  //
12
- // Heuristic: fires when the grep PATTERN looks like (a) a dependency hunt
12
+ // Why the Bash branch: the original hook only watched the Grep TOOL, so any
13
+ // search run as raw `grep`/`rg` via Bash bypassed the nudge entirely — the
14
+ // exact gap that let an agent forget agentmap and fall back to manual
15
+ // Read/sed/awk. This closes it.
16
+ //
17
+ // Heuristic: fires when the search looks like (a) a dependency hunt
13
18
  // (import/require/export / "from '..." / who-imports), (b) a component /
14
19
  // "where-is" / reuse lookup (a JSX component tag like <Heading, or where-is /
15
- // who-uses / reuse / existing-component intent words). A raw string or
16
- // Tailwind-class search (e.g. "bg-white", "text-3xl") and lowercase HTML-tag
17
- // sweeps (<div, <h1) produce NO output no nagging.
20
+ // who-uses / reuse / existing-component intent words), (c) Bash only — a
21
+ // bare multi-hump PascalCase identifier (ProviderCard, TopProviders), almost
22
+ // always a "where is this symbol / who uses it" hunt. A raw string or
23
+ // Tailwind-class search (bg-white, text-3xl) and lowercase HTML-tag sweeps
24
+ // (<div, <h1) produce NO output — no nagging.
25
+ //
26
+ // Bash branch only fires when grep/rg/ag is the PRIMARY command (at start, or
27
+ // after `;` / `&&` — NOT after a pipe, so `… | grep SomeError` log-filtering
28
+ // stays silent).
18
29
  //
19
- // agentmap's `--any` router falls back to a live git-grep on its own, so it
20
- // still covers the raw-string / copy case but only when the agent reaches
21
- // for it deliberately, not on every content sweep.
30
+ // Injection-safe: the user's pattern/command is ONLY regex-tested, never
31
+ // interpolated into the emitted message or executed. Output is a single fixed
32
+ // JSON object.
22
33
  // ============================================================================
23
34
 
24
35
  let raw = "";
@@ -27,14 +38,8 @@ process.stdin.on("data", (c) => (raw += c));
27
38
  process.stdin.on("end", () => {
28
39
  try {
29
40
  const payload = JSON.parse(raw || "{}");
41
+ const tool = String(payload.tool_name || "");
30
42
  const ti = payload.tool_input || {};
31
- const pattern = String(ti.pattern || "");
32
-
33
- // Defensive guard: pathological-input belt-and-suspenders.
34
- // If the pattern is unreasonably long, skip nudging entirely.
35
- if (pattern.length > 2000) {
36
- process.exit(0);
37
- }
38
43
 
39
44
  // (a) Dependency / who-imports / reuse intent signals in the pattern itself.
40
45
  const DEP_RE =
@@ -45,11 +50,14 @@ process.stdin.on("end", () => {
45
50
  // raw HTML/content sweeps of <div>/<h1> silent, so it stays high-signal for
46
51
  // "where is this component used/defined".
47
52
  //
48
- // Denylist: common TS generic/utility type containers that start with an
49
- // uppercase letter but are NOT React components. Without this, a grep for
50
- // `<Promise<` or `<Record<string` fires the nudge spuriously.
53
+ // Denylist: common TS generic/utility type containers (e.g. <Promise<,
54
+ // <Record<string) that look like a component tag but are NOT React components.
55
+ // NOT ^-anchored matches ANYWHERE, because the Bash branch tests the whole
56
+ // command (the generic sits mid-string, e.g. `rg "<Promise<Foo>"`) and a
57
+ // generic can also appear mid-pattern in Grep (e.g. useState<Promise>). The
58
+ // `\b` after the name keeps real components like <PromiseCard / <MapView firing.
51
59
  const GENERIC_DENYLIST =
52
- /^<(Promise|Array|Map|Set|Record|Partial|Readonly|Pick|Omit|Required|Exclude|Extract|NonNullable|ReturnType|Awaited|Parameters|InstanceType)\b/;
60
+ /<(Promise|Array|Map|Set|Record|Partial|Readonly|Pick|Omit|Required|Exclude|Extract|NonNullable|ReturnType|Awaited|Parameters|InstanceType)\b/;
53
61
  const COMPONENT_TAG_RE = /<[A-Z][\w.]*/;
54
62
 
55
63
  // (c) Explicit reuse / "where-is" intent words (case-insensitive): "where is",
@@ -58,21 +66,55 @@ process.stdin.on("end", () => {
58
66
  const INTENT_RE =
59
67
  /\bwhere\s+is\b|\bwho\s+(imports|uses|renders)\b|\breuse\b|\b(existing|shared)\s+(util|component|hook|helper)\b|\bis\s+there\s+(an?\s+)?(existing|shared)\b/i;
60
68
 
61
- if (
62
- pattern &&
63
- (DEP_RE.test(pattern) ||
64
- (COMPONENT_TAG_RE.test(pattern) && !GENERIC_DENYLIST.test(pattern)) ||
65
- INTENT_RE.test(pattern))
66
- ) {
69
+ // (d) Bare multi-hump PascalCase identifier (e.g. ProviderCard, TopProviders).
70
+ // Bash branch only. Two humps required so all-caps (TODO, API), single-word
71
+ // (Error, Button) and lowercase (useState) stay silent → high signal, no
72
+ // Tailwind/raw-string noise.
73
+ const SYMBOL_RE = /\b[A-Z][a-z0-9]+[A-Z][A-Za-z0-9]*\b/;
74
+
75
+ let fire = false;
76
+
77
+ if (tool === "Grep") {
78
+ const pattern = String(ti.pattern || "");
79
+
80
+ // Defensive guard: pathological-input belt-and-suspenders.
81
+ // If the pattern is unreasonably long, skip nudging entirely.
82
+ if (pattern.length > 2000) {
83
+ process.exit(0);
84
+ }
85
+
86
+ fire =
87
+ !!pattern &&
88
+ (DEP_RE.test(pattern) ||
89
+ (COMPONENT_TAG_RE.test(pattern) && !GENERIC_DENYLIST.test(pattern)) ||
90
+ INTENT_RE.test(pattern));
91
+ } else if (tool === "Bash") {
92
+ const cmd = String(ti.command || "");
93
+ // Only when grep/rg/ag is the PRIMARY command (start, or after ; / && — NOT
94
+ // after a pipe, so `… | grep SomeError` log-filtering stays silent). Then
95
+ // test the whole command, plus the symbol rule for bare-identifier symbol
96
+ // hunts.
97
+ const SEARCHER_RE = /(^|[;&]\s*)(rg|ripgrep|grep|egrep|fgrep|ag|ack)\b/;
98
+ if (SEARCHER_RE.test(cmd)) {
99
+ fire =
100
+ DEP_RE.test(cmd) ||
101
+ (COMPONENT_TAG_RE.test(cmd) && !GENERIC_DENYLIST.test(cmd)) ||
102
+ INTENT_RE.test(cmd) ||
103
+ SYMBOL_RE.test(cmd);
104
+ }
105
+ }
106
+
107
+ if (fire) {
108
+ const AM = "node node_modules/@raymondchins/agentmap/agentmap.mjs";
67
109
  const msg =
68
- "This Grep looks like a dependency / component / who-imports / reuse search. " +
69
- "Use agentmap FIRST — it's faster than serial grep. Easiest: " +
70
- "`npx @raymondchins/agentmap --any <query>` (or `node agentmap.mjs --any <query>`) " +
71
- "one command, auto-routes file → symbol → feature → live content. " +
72
- "Or be specific: `--relates <path>` (blast radius / who-imports), " +
73
- "`--find <symbol>` (reuse-before-rebuild / where a component is defined), " +
74
- "`--feature <name>` (files in a feature), `--hubs` (most-imported files). " +
75
- "Rebuild the map with `npx @raymondchins/agentmap` (or `node agentmap.mjs`) if it's stale. " +
110
+ "This looks like a dependency / component / who-imports / reuse / where-is-symbol " +
111
+ "search. Use agentmap FIRST — it's faster than serial grep. Easiest: " +
112
+ "`" + AM + " --any <query>` " +
113
+ "(one command auto-routes file → symbol → feature → live content). " +
114
+ "Or be specific: `" + AM + " --relates <path>` (blast radius / who-imports), " +
115
+ "`" + AM + " --find <symbol>` (reuse-before-rebuild / where a component is defined), " +
116
+ "`" + AM + " --feature <name>` (files in a feature). " +
117
+ "Rebuild the map with `npm run agentmap` (or `npx @raymondchins/agentmap`) if it's stale. " +
76
118
  "Only fall back to grep if agentmap doesn't cover it.";
77
119
  process.stdout.write(
78
120
  JSON.stringify({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raymondchins/agentmap",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },