@allansantos-dev/smart-tool 0.9.23 → 0.9.25

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.25 - beta
4
+
5
+ - A redirected search made while the first index of a folder is still being built no longer waits in the folder's queue
6
+ behind the build (22 s measured, past the hook's 15 s, so the agent got the native result with no notice): it is
7
+ answered at once by the keyword search of the disk, with the redirect notice and the index-loading warning.
8
+ - The first search of a cold folder also gets the index-loading sentence in the notice (it was read from a job field that
9
+ the answer text does not carry; it is now read from the answer's own warning).
10
+
11
+ ## 0.9.24 - beta
12
+
13
+ - A redirected search now tells the agent what happened: the answer opens with a line saying the search was redirected to
14
+ `smart_search` (index, related code, dependency map), that the index of the project is still loading when the answer is
15
+ a keyword search of the disk (the next searches will have it ready), and to keep using `smart_search`. Before, the
16
+ answer carried no word of the redirect.
17
+ - The deny text of a search that cannot be redirected no longer says that reading or searching inside one known file is
18
+ never blocked: no message the agent receives offers a way around the redirect (file by file, narrower scope).
19
+ `NoWayAroundTheRedirectTest` fails if any of those texts (deny, advice, redirect notice, cold-index and stale-index
20
+ warnings) suggests one.
21
+
3
22
  ## 0.9.23 - beta
4
23
 
5
24
  - The hook no longer blocks. A content search over a project folder (the Grep tool on a folder, `grep -r`, `rg`,
package/README.md CHANGED
@@ -15,7 +15,7 @@ registered as the `playwright` server where this machine lets a browser be drive
15
15
  An optional hook routes the agent's native Grep/Glob/Read/Bash/WebSearch/WebFetch calls to these tools when they
16
16
  are cheaper, or just tells the agent what Smart Tool would do.
17
17
 
18
- **Status:** 0.9.23 beta. Windows only. What changed in each version: [CHANGELOG.md](CHANGELOG.md).
18
+ **Status:** 0.9.25 beta. Windows only. What changed in each version: [CHANGELOG.md](CHANGELOG.md).
19
19
 
20
20
  ## How it works
21
21
 
package/hook_decision.py CHANGED
@@ -138,14 +138,14 @@ def _load_hint(client, tool):
138
138
 
139
139
  def redirect_message(client, tool_name, target, reason, cwd):
140
140
  """Deny text for a broad raw search: the exact tool and arguments to call (project_root from the folder the search
141
- reads), and that rewriting the same search in Bash, python, node or PowerShell is the bypass the rule stops."""
141
+ reads), and that rewriting the same search in Bash, python, node or PowerShell is the bypass the rule stops. It
142
+ never suggests a way around the rule (narrower or per-file searches): the only way offered is smart_search."""
142
143
  root = _search_root(target, cwd).replace("\\", "/")
143
144
  return (f"Blocked by Smart Tool, a routing rule set by the user, not a failure ({tool_name}: {clean_reason(reason)}). "
144
145
  f'Run this search with {_tool_name(client, "smart_search")}: project_root="{root}", query_identifiers = what '
145
146
  f"you are looking for in identifier terms (plus query_comments when the project has two languages)."
146
147
  f'{_load_hint(client, "smart_search")} Do not repeat the same search through Bash, python, node or '
147
- f"PowerShell: that bypasses the rule. Reading one known file, or searching inside one known file, is never "
148
- f"blocked.")
148
+ f"PowerShell: that bypasses the rule.")
149
149
 
150
150
 
151
151
  def web_message(client, tool_name, url, why):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allansantos-dev/smart-tool",
3
- "version": "0.9.23",
3
+ "version": "0.9.25",
4
4
  "description": "Local MCP server that gives coding agents (Claude Code, Codex) cheaper, sharper tools than their built-in search. Windows.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Allan Santos",
@@ -3654,12 +3654,27 @@ def _decide_within_budget(payload, client):
3654
3654
  return output
3655
3655
 
3656
3656
 
3657
+ def _cold_index_building(root):
3658
+ """Whether the folder has no index yet and the job that builds it is queued or running. A search job of that folder
3659
+ would wait behind it in the per-folder queue (21.9 s measured, past the hook's wait), so a redirected search is
3660
+ answered at once by `_perform_smart_search`'s cold path: keyword matches read from disk, with the notice."""
3661
+ if indexer.info(root).get("chunks") or not _cold_index(root, config.load_config(), _gateway_problem()):
3662
+ return False
3663
+ project_id = project_identity.project_id(root)
3664
+ with _JOBS_LOCK:
3665
+ return any(job.get("kind") == "project_index" and job.get("status") == "pending"
3666
+ and job.get("project_id") == project_id for job in _jobs.values())
3667
+
3668
+
3657
3669
  def _redirected_search(arguments):
3658
3670
  """smart_search text for a redirected native search, waiting up to SMART_SEARCH_SYNC_WAIT_S; raises when the search
3659
- is rejected, fails or is still running then. The hook's single_language_reason only passes the language check: the
3660
- answer stands for the native call's output and does not repeat it as a warning."""
3671
+ is rejected, fails or is still running then. The hook's single_language_reason only passes the language check, it is
3672
+ not repeated as a warning. The answer opens with `tool_redirect.search_notice`: the agent is told its search was
3673
+ redirected, whether the index is still loading, and to keep using smart_search."""
3661
3674
  checked = _smart_search_arguments(arguments)
3662
3675
  checked.pop("single_language_reason", None)
3676
+ if _cold_index_building(checked["project_root"]):
3677
+ return f"{tool_redirect.search_notice(True)}\n{_perform_smart_search(checked, '')}"
3663
3678
  job_id, _future = _submit_project_job("smart_search", checked)
3664
3679
  deadline = time.monotonic() + SMART_SEARCH_SYNC_WAIT_S
3665
3680
  payload = _smart_search_job_payload(job_id)
@@ -3668,7 +3683,8 @@ def _redirected_search(arguments):
3668
3683
  payload = _smart_search_job_payload(job_id)
3669
3684
  if payload["status"] != "done":
3670
3685
  raise RuntimeError(f"smart_search job {job_id} is {payload['status']}: {payload.get('error') or ''}"[:300])
3671
- return _agent_search_answer(payload)
3686
+ answer = _agent_search_answer(payload)
3687
+ return f"{tool_redirect.search_notice('Indexing in progress' in answer.partition(chr(10) + 'code:')[0])}\n{answer}"
3672
3688
 
3673
3689
 
3674
3690
  def _redirected_text(handler):
package/tool_redirect.py CHANGED
@@ -60,6 +60,15 @@ class Jobs:
60
60
  self._futures.pop(key, None)
61
61
 
62
62
 
63
+ def search_notice(indexing):
64
+ """First line of a redirected search's answer: the agent made a native search, Smart Tool answered it. While the
65
+ index loads the answer is a keyword search of the disk; either way the agent is told to keep using smart_search."""
66
+ state = ("The index of this project is still loading, so this answer is a keyword search of the files on disk; "
67
+ "the next searches will have the index ready. " if indexing else "")
68
+ return ("Smart Tool: your search was redirected to smart_search, which searches the project index and returns "
69
+ f"related code with its dependency map. {state}Keep using smart_search for searches of the project.")
70
+
71
+
63
72
  def output_for(tool_name, response, text):
64
73
  """`updatedToolOutput` for a native tool: its own response with the Smart Tool text in the field the model reads,
65
74
  or None when the response has an unknown shape."""
package/version.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Single source of the Smart Tool version and of the User-Agent sent to public APIs."""
2
2
  import re
3
3
 
4
- VERSION = "0.9.23"
4
+ VERSION = "0.9.25"
5
5
  _CONTACT_RE = re.compile(r"^(?:[^\s@()<>;]+@[^\s@()<>;]+\.[^\s@()<>;]+|https?://[^\s()<>;]+)$")
6
6
 
7
7