webbee 0.7.1__tar.gz → 0.7.2__tar.gz
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.
- {webbee-0.7.1 → webbee-0.7.2}/CHANGELOG.md +17 -0
- {webbee-0.7.1 → webbee-0.7.2}/PKG-INFO +3 -3
- {webbee-0.7.1 → webbee-0.7.2}/pyproject.toml +3 -3
- webbee-0.7.2/src/webbee/__init__.py +1 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/query.py +90 -3
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/service.py +18 -5
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/tools.py +1 -1
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_any_folder.py +21 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_query.py +30 -1
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tools.py +1 -1
- webbee-0.7.1/src/webbee/__init__.py +0 -1
- {webbee-0.7.1 → webbee-0.7.2}/.github/workflows/publish.yml +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/.gitignore +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/LICENSE +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/README.md +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/install.sh +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/__main__.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/_launch.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/account.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/active_sessions.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/autoupdate.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/banner_art.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/boot.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/browser.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/checkpoints.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/cli.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/clipboard.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/clipboard_read.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/clipboard_session.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/coding_context.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/commands.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/config.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/consent.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/details.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/events.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/frames.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/home.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/home_view.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/http.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/instance_lock.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/graph.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/outline.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/store.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/tree.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/intel/watch.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/media.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/modals.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/mode_store.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/native_files.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/native_read.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/newtab_mode.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/output_pane.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/pricing_constants.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/queue_panel.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/reflow.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/remote.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/render.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/repl.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/repo.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/selection.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/session.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/sessions.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/sizing.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/slots.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/steer.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/stream.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/tab_store.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/tabs.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/thread.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/tier_store.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/todo_panel.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/tokens.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/tui.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/update.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/urlopen.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/wallet.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/src/webbee/worktrees.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/__init__.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/conftest.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_account.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_active_sessions.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_audit_2026_07_25_findings.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_autoupdate.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_checkpoints.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_checkpoints_nested_1030.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_cli.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_clipboard.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_clipboard_read.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_code_map_fresh.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_coding_context.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_commands.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_config.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_cpc_outline.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_cross_loop_token.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_details.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_events.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_freeze_fix.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_home.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_home_view.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_home_view_mouse.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_inspect_page.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_instance_lock.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_cache_and_sync_worker.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_checksum_tree.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_chunker.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_embed.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_graph.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_service.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_store.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_vectors.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_intel_watch.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_launch.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_marathon.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_modals_e2e.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_mode_and_remote_0_6_8.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_mode_store.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_native_files.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_newtab_mode.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_packaging.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_probe_cost.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_queue_manage_0337.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_reflow.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_remote_code_reads.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_render.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_repl.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_repo.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_resilient_http.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_review_fixes_1030.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_session.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_session_review_1030.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_sessions.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_sizing.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_slots.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_steer.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_stream.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tab_bar_stability.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tab_restore_0337.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tabs.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_terminal_ux_0336.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_thread.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tier_invalidation.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tier_store.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_todo_panel.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tokens.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tools_eol_cancel_1030.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tools_vision_websearch.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tui.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_tui_hardening.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_update.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_update_badge_0340.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_urlopen.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_version.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_version_badge_corner.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_wallet.py +0 -0
- {webbee-0.7.1 → webbee-0.7.2}/tests/test_worktrees.py +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.2
|
|
4
|
+
|
|
5
|
+
* **The code graph answers correctly** (06.11 report):
|
|
6
|
+
* `impact_of_change` lists the files that really use a symbol, with the line, the user and the depth. A
|
|
7
|
+
function nobody calls had answered 481 unrelated files.
|
|
8
|
+
* Files that only share the name are listed apart under `by_name`.
|
|
9
|
+
* An unknown symbol is reported as such, never as "0 users".
|
|
10
|
+
* **Every major language is linked**:
|
|
11
|
+
* Go, Rust, Java, Kotlin, C#, PHP, Ruby;
|
|
12
|
+
* TS/JS monorepos (workspace packages, tsconfig `extends`, default exports, CommonJS, `.mts/.cts`);
|
|
13
|
+
* Vue, Svelte and Astro components;
|
|
14
|
+
* members through typed fields, constructors and return types.
|
|
15
|
+
* **Projects of every ecosystem** are recognised (`Cargo.toml`, `go.mod`, `pom.xml`, `*.csproj`, `composer.json`,
|
|
16
|
+
`Gemfile` ...), locally and on servers.
|
|
17
|
+
* `.gitignore` rules apply in folders without git and on machines without a git binary (Windows).
|
|
18
|
+
* Needs imperal-mcp 0.9.2 and imperal-sdk 6.5.0.
|
|
19
|
+
|
|
3
20
|
## 0.7.1
|
|
4
21
|
|
|
5
22
|
* **A missing code index says why**: when the index did not start (a failure while building it, or it is turned off),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: webbee
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Webbee 🐝 — the Imperal Cloud coding agent in your terminal
|
|
5
5
|
Project-URL: Homepage, https://imperal.io
|
|
6
6
|
Project-URL: Documentation, https://docs.imperal.io
|
|
@@ -31,8 +31,8 @@ Requires-Python: >=3.7
|
|
|
31
31
|
Requires-Dist: asyncssh>=2.14; python_version >= '3.10'
|
|
32
32
|
Requires-Dist: httpx-sse>=0.4; python_version >= '3.10'
|
|
33
33
|
Requires-Dist: httpx>=0.27; python_version >= '3.10'
|
|
34
|
-
Requires-Dist: imperal-mcp>=0.9.
|
|
35
|
-
Requires-Dist: imperal-sdk[universal-index]>=6.
|
|
34
|
+
Requires-Dist: imperal-mcp>=0.9.2; python_version >= '3.10'
|
|
35
|
+
Requires-Dist: imperal-sdk[universal-index]>=6.5.0; python_version >= '3.10'
|
|
36
36
|
Requires-Dist: model2vec>=0.8.2; python_version >= '3.10' and (sys_platform != 'darwin' or platform_machine != 'x86_64' or python_version < '3.13')
|
|
37
37
|
Requires-Dist: numpy>=2; python_version >= '3.10' and (sys_platform != 'darwin' or platform_machine != 'x86_64' or python_version < '3.13')
|
|
38
38
|
Requires-Dist: prompt-toolkit>=3; python_version >= '3.10'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "webbee"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.2"
|
|
4
4
|
description = "Webbee 🐝 — the Imperal Cloud coding agent in your terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "GPL-3.0-or-later"
|
|
@@ -13,8 +13,8 @@ dependencies = [
|
|
|
13
13
|
# release on a Python 3.12 it sets up once with uv -- `uv` comes as a wheel on 3.8+,
|
|
14
14
|
# 3.7 fetches it with uv's own installer.
|
|
15
15
|
'uv>=0.4; python_version >= "3.8" and python_version < "3.10"',
|
|
16
|
-
'imperal-mcp>=0.9.
|
|
17
|
-
'imperal-sdk[universal-index]>=6.
|
|
16
|
+
'imperal-mcp>=0.9.2; python_version >= "3.10"', # raised to the newest at each release
|
|
17
|
+
'imperal-sdk[universal-index]>=6.5.0; python_version >= "3.10"',
|
|
18
18
|
'asyncssh>=2.14; python_version >= "3.10"',
|
|
19
19
|
'httpx>=0.27; python_version >= "3.10"',
|
|
20
20
|
'httpx-sse>=0.4; python_version >= "3.10"',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.2"
|
|
@@ -186,14 +186,101 @@ def contract_slice(svc, query: str = "", kind: str = "all", k: int = 50) -> dict
|
|
|
186
186
|
return _env(items[:limit], has_more=len(items) > limit)
|
|
187
187
|
|
|
188
188
|
|
|
189
|
+
# The local answer of ``impact_of_change`` computes links the way the vault's graph does (imperal_sdk.code_links,
|
|
190
|
+
# 2026-11-06): before, a file "depended" on a symbol when the bare NAME appeared in it, and each depth step pulled in
|
|
191
|
+
# every definition of every hit file -- a function nobody calls answered 481 files (06.11 report).
|
|
192
|
+
# which files carry scope facts and which are resolution configs: the SDK's one answer (Python, TS/JS, Vue/Svelte/Astro,
|
|
193
|
+
# Go, Rust, Java, Kotlin, C#, PHP, Ruby; go.mod, Cargo.toml, tsconfig, package.json ...) -- never a list of our own
|
|
194
|
+
_FACTS: dict = {} # (root, path, content_hash) -> BlobFacts | None: a file parsed once per content
|
|
195
|
+
_FACTS_MAX = 60000
|
|
196
|
+
_READ_MAX = 2_000_000
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _read(root: str, rel: str):
|
|
200
|
+
import os
|
|
201
|
+
try:
|
|
202
|
+
full = os.path.join(root, rel)
|
|
203
|
+
if os.path.getsize(full) > _READ_MAX:
|
|
204
|
+
return None
|
|
205
|
+
with open(full, encoding="utf-8", errors="replace") as fh:
|
|
206
|
+
return fh.read()
|
|
207
|
+
except OSError:
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _is_config(rel: str) -> bool:
|
|
212
|
+
from imperal_sdk.code_index import is_resolution_config
|
|
213
|
+
return is_resolution_config(rel)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def linker(svc):
|
|
217
|
+
"""The repo's resolver (``imperal_sdk.code_links.Linker``) for the index's current graph -- built once per graph
|
|
218
|
+
(a rebuilt graph builds it again), each file parsed once per content."""
|
|
219
|
+
import os
|
|
220
|
+
g = svc.graph
|
|
221
|
+
if getattr(svc, "_linker_graph", None) is g and getattr(svc, "_linker", None) is not None:
|
|
222
|
+
return svc._linker
|
|
223
|
+
from imperal_sdk import code_index as ci
|
|
224
|
+
from imperal_sdk.code_links import Linker
|
|
225
|
+
root = str(getattr(svc, "root", "") or "")
|
|
226
|
+
if len(_FACTS) > _FACTS_MAX:
|
|
227
|
+
_FACTS.clear()
|
|
228
|
+
facts: dict = {}
|
|
229
|
+
for path, fi in list(svc.index.files.items()):
|
|
230
|
+
if not ci.has_scope_facts(path):
|
|
231
|
+
continue
|
|
232
|
+
key = (root, path, getattr(fi, "content_hash", ""))
|
|
233
|
+
if key not in _FACTS:
|
|
234
|
+
text = _read(root, path) if root else None
|
|
235
|
+
_FACTS[key] = ci.scope_facts(path, text) if text is not None else None
|
|
236
|
+
facts[path] = _FACTS[key]
|
|
237
|
+
configs = []
|
|
238
|
+
cands = {p for p in svc.index.files if _is_config(p)}
|
|
239
|
+
if root:
|
|
240
|
+
cands |= {n for n in os.listdir(root) if _is_config(n)} if os.path.isdir(root) else set()
|
|
241
|
+
for rel in sorted(cands):
|
|
242
|
+
text = _read(root, rel) if root else None
|
|
243
|
+
if text is None:
|
|
244
|
+
continue
|
|
245
|
+
try:
|
|
246
|
+
kind, data = ci.parse_config(rel, text)
|
|
247
|
+
except Exception:
|
|
248
|
+
continue
|
|
249
|
+
configs.append({"path": rel.replace("\\", "/"), "kind": kind, "data": data})
|
|
250
|
+
lk = Linker(facts, configs)
|
|
251
|
+
svc._linker, svc._linker_graph = lk, g
|
|
252
|
+
return lk
|
|
253
|
+
|
|
254
|
+
|
|
189
255
|
def impact_of_change(svc, symbols) -> dict:
|
|
256
|
+
"""Files that use ``symbols`` (resolved links: line, user, depth through the enclosing definition), code before
|
|
257
|
+
tests; ``by_name`` -- files that only share a name (a method on a local, a language without scope facts), marked
|
|
258
|
+
and bounded; ``resolved`` -- the definitions found (none: "not a definition in this index", never "0 users")."""
|
|
190
259
|
if svc is None or svc.graph is None:
|
|
191
260
|
return {"ok": False, "content": "index not ready"}
|
|
192
261
|
if isinstance(symbols, str):
|
|
193
262
|
symbols = [symbols]
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
263
|
+
from imperal_sdk.code_index import has_scope_facts
|
|
264
|
+
from imperal_sdk.code_links import BY_NAME_SHOWN, impact, is_test_path
|
|
265
|
+
syms = [str(s) for s in (symbols or []) if str(s).strip()]
|
|
266
|
+
out = impact(linker(svc), syms, depth=2)
|
|
267
|
+
# languages without scope facts: their uses are known by name only -- listed with the other by-name files
|
|
268
|
+
names = {s.split("::")[-1].split(":")[-1].rsplit(".", 1)[-1] for s in syms}
|
|
269
|
+
known = {b["path"] for b in out["by_name"]} | {i["id"] for i in out["data"]["items"]}
|
|
270
|
+
extra = []
|
|
271
|
+
for path, fi in svc.index.files.items():
|
|
272
|
+
if has_scope_facts(path) or path in known:
|
|
273
|
+
continue
|
|
274
|
+
if names & set(getattr(fi, "refs", ()) or ()):
|
|
275
|
+
extra.append({"path": path, "line": 0, "name": "", "group": "tests" if is_test_path(path) else "code",
|
|
276
|
+
"note": "no scope facts for this language: by name"})
|
|
277
|
+
if extra:
|
|
278
|
+
out["by_name_count"] = int(out.get("by_name_count") or 0) + len(extra)
|
|
279
|
+
out["by_name"] = sorted(out["by_name"] + extra, key=lambda b: (b["group"] == "tests", b["path"]))[:BY_NAME_SHOWN]
|
|
280
|
+
if not out["resolved"]:
|
|
281
|
+
out["note"] = f"{', '.join(syms)}: not a definition in this index"
|
|
282
|
+
out["link"] = "resolved locally (the vault's rules); by_name lists name-only matches apart"
|
|
283
|
+
return out
|
|
197
284
|
|
|
198
285
|
|
|
199
286
|
def orient(svc, q) -> dict:
|
|
@@ -171,10 +171,22 @@ def _index_listing(root: str, focus: str | None = None, limit: int = UNIT_MAX_FI
|
|
|
171
171
|
skipped = {"placeholder": 0, "over_cap": 0}
|
|
172
172
|
excluded = {str(e).strip("/") for e in exclude if str(e).strip("/")}
|
|
173
173
|
listed = _git_listed(root)
|
|
174
|
+
# no git answer (a folder that is no repository, the repositories inside it, a machine without git -- Windows,
|
|
175
|
+
# a minimal Linux): every .gitignore on the way applies, read here (06.11 report: none applied, ~19.6k dump and
|
|
176
|
+
# screenshot files were indexed). The same parser as the SFTP listing (imperal_sdk.code_index.gitignore_rules).
|
|
177
|
+
from imperal_sdk.code_index import gitignore_rules, gitignored
|
|
178
|
+
rules: list | None = None if listed is not None else []
|
|
174
179
|
for dp, dns, fns in os.walk(root):
|
|
175
180
|
here = os.path.relpath(dp, root).replace(os.sep, "/")
|
|
181
|
+
if rules is not None and ".gitignore" in fns:
|
|
182
|
+
try:
|
|
183
|
+
with open(os.path.join(dp, ".gitignore"), encoding="utf-8", errors="replace") as fh:
|
|
184
|
+
rules = rules + gitignore_rules(fh.read(), "" if here == "." else here)
|
|
185
|
+
except OSError:
|
|
186
|
+
pass
|
|
176
187
|
dns[:] = sorted(d for d in dns if not skipped_dir(dp, d)
|
|
177
|
-
and (d if here == "." else f"{here}/{d}") not in excluded
|
|
188
|
+
and (d if here == "." else f"{here}/{d}") not in excluded
|
|
189
|
+
and not (rules and gitignored(rules, d if here == "." else f"{here}/{d}", True)))
|
|
178
190
|
for fn in sorted(fns):
|
|
179
191
|
rel = fn if here == "." else f"{here}/{fn}"
|
|
180
192
|
if is_placeholder(rel):
|
|
@@ -182,6 +194,8 @@ def _index_listing(root: str, focus: str | None = None, limit: int = UNIT_MAX_FI
|
|
|
182
194
|
continue
|
|
183
195
|
if listed is not None and rel not in listed:
|
|
184
196
|
continue
|
|
197
|
+
if rules and gitignored(rules, rel, False):
|
|
198
|
+
continue
|
|
185
199
|
if kind_of(rel, root=root) is None:
|
|
186
200
|
continue
|
|
187
201
|
try:
|
|
@@ -990,9 +1004,8 @@ class IntelService:
|
|
|
990
1004
|
|
|
991
1005
|
# ── code graph everywhere (plan 3 Tasks 7, 9): the local workspace in the vault's one graph ─────────
|
|
992
1006
|
|
|
993
|
-
# what makes a folder a project for the code graph: version control or
|
|
994
|
-
#
|
|
995
|
-
_MANIFESTS = tuple(indexer.CONFIG_NAMES) + ("setup.py", "requirements.txt", "tsconfig.json")
|
|
1007
|
+
# what makes a folder a project for the code graph: version control or any ecosystem's manifest at its root -- the
|
|
1008
|
+
# sdk's one rule (``indexer.is_project_root``: Cargo.toml, go.mod, pom.xml, *.csproj, composer.json, Gemfile, ...)
|
|
996
1009
|
|
|
997
1010
|
|
|
998
1011
|
SUB_REPOS_MAX = 40 # a folder of projects: at most this many of its repositories are synced
|
|
@@ -1025,7 +1038,7 @@ def is_repository(root: str) -> bool:
|
|
|
1025
1038
|
names = set(os.listdir(root))
|
|
1026
1039
|
except OSError:
|
|
1027
1040
|
return False
|
|
1028
|
-
return
|
|
1041
|
+
return indexer.is_project_root(names)
|
|
1029
1042
|
|
|
1030
1043
|
|
|
1031
1044
|
_MACHINE_ID_FILE = os.path.join(os.path.expanduser("~"), ".local", "share", "webbee", "machine-id")
|
|
@@ -1370,7 +1370,7 @@ async def _no_repo_at(sftp, ci, root: str) -> str:
|
|
|
1370
1370
|
names = {e.filename for e in await sftp.readdir(repo_root)}
|
|
1371
1371
|
except Exception:
|
|
1372
1372
|
names = set()
|
|
1373
|
-
if not
|
|
1373
|
+
if not ci.is_project_root(names):
|
|
1374
1374
|
return f"{root} is in no repository (no .git or project manifest at it or above)"
|
|
1375
1375
|
return ""
|
|
1376
1376
|
|
|
@@ -95,3 +95,24 @@ def test_a_secret_written_later_is_never_read_and_the_profile_names_no_document(
|
|
|
95
95
|
assert "credentials.json" not in svc.index.files and not any("credentials.json" in o for o in opened)
|
|
96
96
|
prof = svc.repo_profile()
|
|
97
97
|
assert not any("Agreement" in s for s in prof["top_symbols"]) and prof["other_kinds"]["document"] >= 2
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_gitignore_applies_without_git(tmp_path, monkeypatch):
|
|
102
|
+
"""06.11 report: in a folder without git (or on a machine without the git binary) no .gitignore applied."""
|
|
103
|
+
from webbee.intel import service
|
|
104
|
+
(tmp_path / ".gitignore").write_text("dumps/\n*.png\n")
|
|
105
|
+
(tmp_path / "dumps").mkdir()
|
|
106
|
+
(tmp_path / "dumps" / "big.sql").write_text("x")
|
|
107
|
+
(tmp_path / "shot.png").write_bytes(b"\x89PNG")
|
|
108
|
+
(tmp_path / "app.py").write_text("x = 1\n")
|
|
109
|
+
(tmp_path / "sub").mkdir()
|
|
110
|
+
(tmp_path / "sub" / ".gitignore").write_text("local.py\n")
|
|
111
|
+
(tmp_path / "sub" / "local.py").write_text("y = 1\n")
|
|
112
|
+
(tmp_path / "sub" / "keep.py").write_text("z = 1\n")
|
|
113
|
+
rels, _stats, _skipped = service._index_listing(str(tmp_path))
|
|
114
|
+
assert "app.py" in rels and "sub/keep.py" in rels
|
|
115
|
+
assert not {"dumps/big.sql", "shot.png", "sub/local.py"} & set(rels)
|
|
116
|
+
# the same with git present but failing (no binary): the rules still apply
|
|
117
|
+
monkeypatch.setattr(service, "_git_listed", lambda root: None)
|
|
118
|
+
assert "sub/local.py" not in service._index_listing(str(tmp_path))[0]
|
|
@@ -5,7 +5,7 @@ from webbee.intel import query
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def _svc(tmp_path):
|
|
8
|
-
(tmp_path / "a.py").write_text("
|
|
8
|
+
(tmp_path / "a.py").write_text("from b import beta\n\n\ndef alpha():\n return beta()\n")
|
|
9
9
|
(tmp_path / "b.py").write_text("def beta():\n return 1\n")
|
|
10
10
|
s = IntelService(str(tmp_path), "rk", cache_dir=str(tmp_path / "c")); s.build(); return s
|
|
11
11
|
|
|
@@ -83,3 +83,32 @@ def test_search_code_kind_filter_does_not_nullify_vector_arm(tmp_path, monkeypat
|
|
|
83
83
|
monkeypatch.setattr(query, "_lexical_search", lambda *a, **k: [])
|
|
84
84
|
r = query.search_code(svc, "beta", kind="function")
|
|
85
85
|
assert r["ok"] and any(i["title"] == "beta" for i in r["data"]["items"])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# 06.11 report: a function nobody calls answered hundreds of files (links by bare name, depth through every def of a
|
|
89
|
+
# hit file). The local answer now follows the vault's rules (imperal_sdk.code_links).
|
|
90
|
+
|
|
91
|
+
def _svc2(tmp_path):
|
|
92
|
+
(tmp_path / "core.py").write_text("def used():\n return 1\n\n\ndef lonely():\n return 2\n")
|
|
93
|
+
(tmp_path / "user.py").write_text("from core import used\n\n\ndef run():\n return used()\n")
|
|
94
|
+
(tmp_path / "other.py").write_text("def go(obj):\n return obj.lonely()\n\n\ndef used_twice():\n return 3\n")
|
|
95
|
+
(tmp_path / "tests").mkdir()
|
|
96
|
+
(tmp_path / "tests" / "test_core.py").write_text("from core import used\n\n\ndef test_used():\n assert used()\n")
|
|
97
|
+
s = IntelService(str(tmp_path), "rk2", cache_dir=str(tmp_path / "c")); s.build(); return s
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_impact_nobody_calls_is_empty_and_by_name_is_apart(tmp_path):
|
|
101
|
+
r = query.impact_of_change(_svc2(tmp_path), ["lonely"])
|
|
102
|
+
assert r["data"]["items"] == [] and r["resolved"] == ["core.py::lonely"]
|
|
103
|
+
assert [b["path"] for b in r["by_name"]] == ["other.py"]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_impact_resolved_users_code_before_tests(tmp_path):
|
|
107
|
+
r = query.impact_of_change(_svc2(tmp_path), ["used"])
|
|
108
|
+
assert [i["id"] for i in r["data"]["items"]] == ["user.py", "tests/test_core.py"]
|
|
109
|
+
assert r["data"]["items"][0]["user"] == "run" and r["data"]["items"][0]["line"] == 5
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_impact_unknown_symbol_says_so(tmp_path):
|
|
113
|
+
r = query.impact_of_change(_svc2(tmp_path), ["nope"])
|
|
114
|
+
assert r["data"]["items"] == [] and "not a definition" in r["note"]
|
|
@@ -202,7 +202,7 @@ def test_cpc_graph_slice_coerces_stringified_symbols(tmp_path):
|
|
|
202
202
|
# false negative the brain reads as "no callers".
|
|
203
203
|
pytest.importorskip("tree_sitter")
|
|
204
204
|
from webbee.intel.service import IntelService
|
|
205
|
-
(tmp_path / "a.py").write_text("
|
|
205
|
+
(tmp_path / "a.py").write_text("from b import beta\n\n\ndef alpha():\n return beta()\n") # a real import: links are resolved
|
|
206
206
|
(tmp_path / "b.py").write_text("def beta():\n return 1\n")
|
|
207
207
|
svc = IntelService(str(tmp_path), "rk", cache_dir=str(tmp_path / "c"))
|
|
208
208
|
svc.build()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|