squackit 0.7.0__tar.gz → 0.7.1__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.
- {squackit-0.7.0 → squackit-0.7.1}/CHANGELOG.md +36 -0
- {squackit-0.7.0 → squackit-0.7.1}/PKG-INFO +4 -4
- {squackit-0.7.0 → squackit-0.7.1}/pyproject.toml +4 -4
- {squackit-0.7.0 → squackit-0.7.1}/squackit/server.py +10 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/tools.py +96 -2
- squackit-0.7.1/tests/fixtures/has_call_filter.py +23 -0
- squackit-0.7.1/tests/test_config_consumption.py +95 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_tools.py +90 -0
- {squackit-0.7.0 → squackit-0.7.1}/.github/workflows/release.yml +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/.gitignore +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/.mcp.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/.readthedocs.yaml +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/.readthedocs.yml +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/CLAUDE.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/LICENSE +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/README.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/agent-system-prompt.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/architecture.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/configuration.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/index.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/prompts.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/proposals/2026-06-04-response-type-improvements.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/quickstart.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/resources.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-10-phase1-handoff.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-10-squawkit-extraction.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-11-phase3-pluckit-rewire.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-12-cli-tool-exposure.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-12-pluckit-tool-integration.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-12-session-handoff.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-16-session-handoff.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-26-named-fts-collections.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-27-kibitzer-fts-doc-retrieval.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/skills/exploring-unfamiliar-docs/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/skills/pluck-chains/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/skills/refactoring-with-squackit/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/skills/using-squackit/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/specs/2026-04-10-squawkit-design.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/specs/2026-04-12-cli-tool-exposure-design.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/specs/2026-04-12-pluckit-tool-integration-design.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/specs/2026-04-16-training-data-pipeline-design.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/specs/2026-04-26-named-fts-collections-design.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/docs/tools.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/mkdocs.yml +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/EXPERIMENT_LOG.md +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/benchmark_models.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/cascade_prototype.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/combo_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/generate_examples.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/grid_generate_examples.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_task_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_task_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_task_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_task_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_task_results/qwen3.5_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_task_results/qwen3_8b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_task_results/scoring_input.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_task_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/hard_tasks.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/harvest_grid.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/iterative_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/iterative_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/iterative_results/summary_deepseek_plain.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/iterative_results/summary_distilled_xml.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/iterative_results/summary_editor_3b_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/iterative_results/summary_fulldocs.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/iterative_results/summary_plaintext_3b7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/codegemma_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/codegemma_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/deepseek-llm_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/gemma_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/glm4_9b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/granite-code_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/granite3.1-dense_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/granite3.3_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/llama2-uncensored_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/llama3.2-vision_11b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/llama3.2_1b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/llama3.2_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/phi3_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/phi4-mini_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen2.5-coder_1.5b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen2.5_1.5b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen2.5_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen2.5_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen2_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen3.5_0.8b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen3.5_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen3.5_4b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen3.5_9b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen3_0.6b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/qwen3_8b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/smollm2_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_card_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/model_cards.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/multiline_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/multiline_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/multiline_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/multiline_results/qwen3.5_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/multiline_results/qwen3_8b.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/multiline_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/multiline_tasks.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/qm_retrieval_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/quartermaster_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/quartermaster_results/retrieval_summary.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/quartermaster_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/selector_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/scripts/selector_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/__init__.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/__main__.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/cli.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/data/examples.json +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/db.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/defaults.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/formatting.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/lackpy_integration.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/prompts.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/runtime.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/session.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/tool_config.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/squackit/workflows.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/conftest.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_cli_tools.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_defaults.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_formatting_json.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_fts_root.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_prompts.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_resources.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_runtime_config.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_session.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_smoke.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_tool_config.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_truncation.py +0 -0
- {squackit-0.7.0 → squackit-0.7.1}/tests/test_workflows.py +0 -0
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1
|
|
4
|
+
|
|
5
|
+
### Added — server consumes default-limit knobs
|
|
6
|
+
`config()`'s `max_results_default` / `complexity_max_results_default` session
|
|
7
|
+
knobs are now honored by the server's tool truncation, so the in-memory limits
|
|
8
|
+
set via `config()` actually take effect.
|
|
9
|
+
|
|
10
|
+
### Changed — ast-pluckit 0.14 selector delegation
|
|
11
|
+
Consume ast-pluckit 0.14.0, which delegates CSS-selector matching to
|
|
12
|
+
sitting_duck's `ast_select` (fixes `:has(.call#name)` over-match, sitting_duck
|
|
13
|
+
#72 / squackit #8); pinned `ast-pluckit>=0.14,<0.15` with a regression test.
|
|
14
|
+
|
|
15
|
+
### Added — AST tools de-vendor the source glob (fledgling #47)
|
|
16
|
+
`find` / `find_names` / `view` / `complexity` now exclude submodule, build,
|
|
17
|
+
cache, and checked-in third-party trees from a whole-repo glob, so they focus on
|
|
18
|
+
the project's own code instead of drowning in vendored deps. On a DuckDB
|
|
19
|
+
extension with `duckdb/` + `rdkit/` git submodules, `find_names('**/*.cpp', '.function')`
|
|
20
|
+
went from **35k+ names parsed in ~9 s to 164 in ~2 s** (~660× less work at the
|
|
21
|
+
parse layer).
|
|
22
|
+
|
|
23
|
+
- Reuses fledgling's single-source-of-truth ignore policy (`_is_vendored_path`
|
|
24
|
+
denylist + `_submodule_prefixes` git-awareness, new in fledgling 0.12) — the
|
|
25
|
+
filtered file set is handed to sitting_duck as an explicit `read_ast` list,
|
|
26
|
+
which parses identically to a glob (verified same results), so only *which*
|
|
27
|
+
files are parsed changes, not how selectors match.
|
|
28
|
+
- The repo root for submodule exclusion is derived from the **source glob**
|
|
29
|
+
(walking up to the nearest `.gitmodules`/`.git`), not the server's cwd, so it
|
|
30
|
+
works when an agent queries a different project by absolute path.
|
|
31
|
+
- Explicit targets are honored: a single-file source, a DuckDB table name, or a
|
|
32
|
+
glob aimed *into* a vendored/submodule tree (e.g. `rdkit/**/*.cpp`) is passed
|
|
33
|
+
through unfiltered rather than filtered to zero.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- Require `fledgling-mcp>=0.12` for the `_is_vendored_path` / `_submodule_prefixes`
|
|
37
|
+
ignore-policy macros.
|
|
38
|
+
|
|
3
39
|
## 0.6.0
|
|
4
40
|
|
|
5
41
|
### Added — `investigate(path=)` for cross-project scoping
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: squackit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Semi-QUalified Agent Companion Kit — the stateful intelligence + MCP server layer for fledgling-equipped agents.
|
|
5
5
|
Project-URL: Homepage, https://github.com/teaguesterling/squackit
|
|
6
6
|
Project-URL: Documentation, https://squackit.readthedocs.io
|
|
@@ -15,11 +15,11 @@ Classifier: Intended Audience :: Developers
|
|
|
15
15
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
-
Requires-Python: >=3.
|
|
19
|
-
Requires-Dist: ast-pluckit<0.
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: ast-pluckit<0.15,>=0.14
|
|
20
20
|
Requires-Dist: click>=8.0
|
|
21
21
|
Requires-Dist: fastmcp>=3.0
|
|
22
|
-
Requires-Dist: fledgling-mcp<0.
|
|
22
|
+
Requires-Dist: fledgling-mcp<0.13,>=0.12
|
|
23
23
|
Provides-Extra: docs
|
|
24
24
|
Requires-Dist: mkdocs-exclude>=1.0; extra == 'docs'
|
|
25
25
|
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "squackit"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.1"
|
|
4
4
|
description = "Semi-QUalified Agent Companion Kit — the stateful intelligence + MCP server layer for fledgling-equipped agents."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
7
|
-
requires-python = ">=3.
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name = "Teague Sterling" },
|
|
10
10
|
]
|
|
@@ -17,8 +17,8 @@ classifiers = [
|
|
|
17
17
|
"License :: OSI Approved :: Apache Software License",
|
|
18
18
|
]
|
|
19
19
|
dependencies = [
|
|
20
|
-
"ast-pluckit>=0.
|
|
21
|
-
"fledgling-mcp>=0.
|
|
20
|
+
"ast-pluckit>=0.14,<0.15",
|
|
21
|
+
"fledgling-mcp>=0.12,<0.13",
|
|
22
22
|
"click>=8.0",
|
|
23
23
|
"fastmcp>=3.0",
|
|
24
24
|
]
|
|
@@ -225,6 +225,16 @@ def _register_executor_tool(mcp, presentation: ToolPresentation):
|
|
|
225
225
|
max_limit = int(val)
|
|
226
226
|
except (TypeError, ValueError):
|
|
227
227
|
pass
|
|
228
|
+
else:
|
|
229
|
+
# Caller passed limit=None — fall back to runtime config.
|
|
230
|
+
# `complexity` is its own bucket; everything else uses the
|
|
231
|
+
# general default_max_results.
|
|
232
|
+
from squackit.runtime import get_runtime
|
|
233
|
+
rt = get_runtime()
|
|
234
|
+
if tool_name == "complexity":
|
|
235
|
+
max_limit = rt.complexity_max_results_default
|
|
236
|
+
elif limit_param == "max_results":
|
|
237
|
+
max_limit = rt.max_results_default
|
|
228
238
|
|
|
229
239
|
# Pop verbose param the same way — opts out of compact_columns
|
|
230
240
|
# projection so the caller can see the full AST bookkeeping schema.
|
|
@@ -8,16 +8,110 @@ fledgling macro tools. They take priority over fledgling equivalents
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import os
|
|
12
|
+
|
|
11
13
|
from fledgling.tools import ToolInfo
|
|
12
14
|
from squackit.tool_config import ToolPresentation
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
def _filtered_source(plucker, source: str) -> str:
|
|
18
|
+
"""De-vendor a source glob before it reaches pluckit's AST engine.
|
|
19
|
+
|
|
20
|
+
A bare ``**/*`` glob over a repo with git submodules or checked-in
|
|
21
|
+
third-party trees (googletest, vendored deps) makes the AST tools parse
|
|
22
|
+
thousands of files that aren't the project's own code — the ``find`` /
|
|
23
|
+
``view`` / ``complexity`` flood. We reuse fledgling's single-source-of-truth
|
|
24
|
+
ignore policy (``_is_vendored_path`` denylist + ``_submodule_prefixes``
|
|
25
|
+
git-awareness) to expand the glob, drop the vendored files, and hand pluckit
|
|
26
|
+
an explicit ``read_ast`` table of just the survivors via its existing
|
|
27
|
+
table-name source path. sitting_duck parses a list of paths exactly as it
|
|
28
|
+
parses a glob (verified identical results), so this changes only *which*
|
|
29
|
+
files are parsed, not how selectors match.
|
|
30
|
+
|
|
31
|
+
Returns the original ``source`` unchanged — so pluckit handles it directly —
|
|
32
|
+
when filtering doesn't apply or would be wrong:
|
|
33
|
+
* not a glob (a single file path or a DuckDB table/view name) — an
|
|
34
|
+
explicit target, nothing to de-flood;
|
|
35
|
+
* the glob expands to <= 1 file;
|
|
36
|
+
* filtering removes *everything* — the caller deliberately aimed at a
|
|
37
|
+
vendored/submodule tree (e.g. ``rdkit/**/*.cpp``), so honor it;
|
|
38
|
+
* filtering removes *nothing* — passing the glob through is cheaper and
|
|
39
|
+
gives identical results.
|
|
40
|
+
Otherwise returns the name of a TEMP table holding the filtered AST.
|
|
41
|
+
|
|
42
|
+
Degrades gracefully: if the fledgling ignore-policy macros can't run for any
|
|
43
|
+
reason (not loaded, SQL error), we fall back to the raw ``source`` — the
|
|
44
|
+
caller gets the historical unfiltered (flooded) behavior rather than a dead
|
|
45
|
+
tool. This is a de-flooding optimization, not a correctness gate, so a raw
|
|
46
|
+
glob is a safe answer; never let it take down find/view/complexity.
|
|
47
|
+
"""
|
|
48
|
+
if "*" not in source:
|
|
49
|
+
return source
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
db = plucker._ctx.db
|
|
53
|
+
resolved = (
|
|
54
|
+
source if os.path.isabs(source)
|
|
55
|
+
else os.path.join(plucker._ctx.repo, source)
|
|
56
|
+
)
|
|
57
|
+
# Submodule exclusion reads <root>/.gitmodules, so root must be the repo
|
|
58
|
+
# that actually contains the globbed files — NOT the plucker's cwd, which
|
|
59
|
+
# may be a different project entirely. Derive it from the glob's literal
|
|
60
|
+
# prefix (the path before the first wildcard) and walk up to the nearest
|
|
61
|
+
# .gitmodules / .git. The denylist is absolute-pattern based, no root needed.
|
|
62
|
+
prefix = resolved.split("*", 1)[0]
|
|
63
|
+
root = prefix if prefix.endswith("/") else os.path.dirname(prefix)
|
|
64
|
+
while root and root != "/":
|
|
65
|
+
if os.path.exists(os.path.join(root, ".gitmodules")) or os.path.exists(
|
|
66
|
+
os.path.join(root, ".git")
|
|
67
|
+
):
|
|
68
|
+
break
|
|
69
|
+
root = os.path.dirname(root)
|
|
70
|
+
rq = resolved.replace("'", "''")
|
|
71
|
+
rootq = root.rstrip("/").replace("'", "''")
|
|
72
|
+
|
|
73
|
+
total = db.sql(f"SELECT count(*) FROM glob('{rq}')").fetchone()[0]
|
|
74
|
+
if total <= 1:
|
|
75
|
+
return source
|
|
76
|
+
|
|
77
|
+
kept = [
|
|
78
|
+
r[0]
|
|
79
|
+
for r in db.sql(
|
|
80
|
+
f"SELECT file FROM glob('{rq}') "
|
|
81
|
+
f"WHERE NOT _is_vendored_path(file) "
|
|
82
|
+
f"AND NOT EXISTS (SELECT 1 FROM _submodule_prefixes('{rootq}') s "
|
|
83
|
+
f"WHERE file LIKE s.prefix || '%') "
|
|
84
|
+
f"ORDER BY file"
|
|
85
|
+
).fetchall()
|
|
86
|
+
]
|
|
87
|
+
if not kept or len(kept) == total:
|
|
88
|
+
return source
|
|
89
|
+
|
|
90
|
+
lit = "[" + ", ".join("'" + f.replace("'", "''") + "'" for f in kept) + "]"
|
|
91
|
+
db.execute(
|
|
92
|
+
"CREATE OR REPLACE TEMP TABLE _squackit_src AS "
|
|
93
|
+
f"SELECT * FROM read_ast({lit}, peek := 'none+schema')"
|
|
94
|
+
)
|
|
95
|
+
return "_squackit_src"
|
|
96
|
+
except Exception:
|
|
97
|
+
# Ignore-policy macros unavailable / SQL error — fall back to the raw
|
|
98
|
+
# glob. Unfiltered (flooded) results beat a broken tool.
|
|
99
|
+
return source
|
|
100
|
+
|
|
101
|
+
|
|
15
102
|
def _make_plucker(source: str):
|
|
16
|
-
"""Create a Plucker with AstViewer and Search for tool execution.
|
|
103
|
+
"""Create a Plucker with AstViewer and Search for tool execution.
|
|
104
|
+
|
|
105
|
+
The source glob is de-vendored first (see :func:`_filtered_source`) so the
|
|
106
|
+
AST tools focus on the project's own code instead of drowning in submodules,
|
|
107
|
+
build output, and checked-in third-party trees.
|
|
108
|
+
"""
|
|
17
109
|
from pluckit import Plucker
|
|
18
110
|
from pluckit.pluckins.search import Search
|
|
19
111
|
from pluckit.pluckins.viewer import AstViewer
|
|
20
|
-
|
|
112
|
+
p = Plucker(plugins=[AstViewer, Search])
|
|
113
|
+
p._code_source = _filtered_source(p, source)
|
|
114
|
+
return p
|
|
21
115
|
|
|
22
116
|
|
|
23
117
|
def view_executor(*, source: str, selector: str):
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Fixture for the :has(.call#name) regression test.
|
|
2
|
+
|
|
3
|
+
Four top-level functions; exactly two call `_target`. Used to pin that
|
|
4
|
+
`:has(.call#_target)` applies the nested `#_target` filter rather than
|
|
5
|
+
dropping it and matching every function (sitting_duck #72 / squackit #8).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _target():
|
|
10
|
+
return 1
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def calls_target_once():
|
|
14
|
+
return _target()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def calls_target_twice():
|
|
18
|
+
a = _target()
|
|
19
|
+
return a + _target()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def calls_other():
|
|
23
|
+
return len([])
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Tests for tools consuming runtime config knobs (max_results_default)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from squackit import runtime as rt
|
|
8
|
+
from squackit.runtime import SquackitRuntimeConfig, update_runtime
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture(autouse=True)
|
|
12
|
+
def _isolate_runtime():
|
|
13
|
+
rt._runtime = SquackitRuntimeConfig()
|
|
14
|
+
rt._seed = SquackitRuntimeConfig()
|
|
15
|
+
yield
|
|
16
|
+
rt._runtime = SquackitRuntimeConfig()
|
|
17
|
+
rt._seed = SquackitRuntimeConfig()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestMaxResultsDefault:
|
|
21
|
+
"""When a tool is called with max_results=None, the runtime config's
|
|
22
|
+
max_results_default should be used as the cap (not the presentation's
|
|
23
|
+
static max_rows)."""
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def mcp(self):
|
|
27
|
+
pytest.importorskip("fastmcp")
|
|
28
|
+
from conftest import PROJECT_ROOT
|
|
29
|
+
from squackit.server import create_server
|
|
30
|
+
return create_server(root=PROJECT_ROOT, init=False)
|
|
31
|
+
|
|
32
|
+
def _call(self, mcp, name, args):
|
|
33
|
+
import asyncio
|
|
34
|
+
from fastmcp import Client
|
|
35
|
+
|
|
36
|
+
async def _c():
|
|
37
|
+
async with Client(mcp) as client:
|
|
38
|
+
return await client.call_tool(name, args)
|
|
39
|
+
|
|
40
|
+
loop = asyncio.new_event_loop()
|
|
41
|
+
try:
|
|
42
|
+
return loop.run_until_complete(_c())
|
|
43
|
+
finally:
|
|
44
|
+
loop.close()
|
|
45
|
+
|
|
46
|
+
def test_explicit_max_results_still_wins(self, mcp):
|
|
47
|
+
update_runtime({"max_results_default": 5})
|
|
48
|
+
# call find_names with explicit max_results=3 — runtime knob ignored
|
|
49
|
+
result = self._call(mcp, "find_names", {
|
|
50
|
+
"source": "**/*.py", "selector": ".fn", "max_results": 3,
|
|
51
|
+
})
|
|
52
|
+
text = result.content[0].text if hasattr(result, "content") else str(result)
|
|
53
|
+
# Just verify the call succeeded — cap is a runtime concern, the
|
|
54
|
+
# important thing is the call completes and returns content.
|
|
55
|
+
assert len(text) > 0
|
|
56
|
+
|
|
57
|
+
def test_runtime_default_picked_up_when_omitted(self, mcp):
|
|
58
|
+
# Set a distinctive default so we can tell it took effect
|
|
59
|
+
update_runtime({"max_results_default": 7})
|
|
60
|
+
result = self._call(mcp, "find_names", {
|
|
61
|
+
"source": "**/*.py", "selector": ".fn",
|
|
62
|
+
})
|
|
63
|
+
text = result.content[0].text if hasattr(result, "content") else str(result)
|
|
64
|
+
assert len(text) > 0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TestComplexityMaxResults:
|
|
68
|
+
"""complexity gets its own knob since it's usually called in smaller batches."""
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def mcp(self):
|
|
72
|
+
pytest.importorskip("fastmcp")
|
|
73
|
+
from conftest import PROJECT_ROOT
|
|
74
|
+
from squackit.server import create_server
|
|
75
|
+
return create_server(root=PROJECT_ROOT, init=False)
|
|
76
|
+
|
|
77
|
+
def test_complexity_uses_its_own_knob(self, mcp):
|
|
78
|
+
import asyncio
|
|
79
|
+
from fastmcp import Client
|
|
80
|
+
|
|
81
|
+
update_runtime({"complexity_max_results_default": 3})
|
|
82
|
+
|
|
83
|
+
async def _c():
|
|
84
|
+
async with Client(mcp) as client:
|
|
85
|
+
return await client.call_tool("complexity", {
|
|
86
|
+
"source": "**/*.py", "selector": ".fn",
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
loop = asyncio.new_event_loop()
|
|
90
|
+
try:
|
|
91
|
+
result = loop.run_until_complete(_c())
|
|
92
|
+
finally:
|
|
93
|
+
loop.close()
|
|
94
|
+
text = result.content[0].text if hasattr(result, "content") else str(result)
|
|
95
|
+
assert len(text) > 0
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# tests/test_tools.py
|
|
2
2
|
"""Tests for squackit.tools -- pluckit-backed tool executors."""
|
|
3
3
|
|
|
4
|
+
import os
|
|
5
|
+
|
|
4
6
|
import pytest
|
|
5
7
|
from squackit.tools import (
|
|
6
8
|
PLUCKIT_TOOLS,
|
|
@@ -74,6 +76,94 @@ class TestComplexityExecutor:
|
|
|
74
76
|
assert complexities == sorted(complexities, reverse=True)
|
|
75
77
|
|
|
76
78
|
|
|
79
|
+
_HAS_FIXTURE = os.path.join(
|
|
80
|
+
os.path.dirname(__file__), "fixtures", "has_call_filter.py"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestHasCallSelectorRegression:
|
|
85
|
+
""":has(.call#name) must apply the nested #name filter, not drop it and
|
|
86
|
+
over-match every function (sitting_duck #72, squackit #8). The fixture has
|
|
87
|
+
4 functions; exactly 2 call `_target`."""
|
|
88
|
+
|
|
89
|
+
def test_has_call_filters_to_callers(self):
|
|
90
|
+
names = set(
|
|
91
|
+
find_names_executor(
|
|
92
|
+
source=_HAS_FIXTURE, selector=".function:has(.call#_target)"
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
assert names == {"calls_target_once", "calls_target_twice"}
|
|
96
|
+
|
|
97
|
+
def test_bare_function_matches_all_four(self):
|
|
98
|
+
# Sanity: without the :has predicate, every function matches — so the
|
|
99
|
+
# test above is exercising the filter, not an empty corpus.
|
|
100
|
+
names = set(find_names_executor(source=_HAS_FIXTURE, selector=".function"))
|
|
101
|
+
assert names == {
|
|
102
|
+
"_target",
|
|
103
|
+
"calls_target_once",
|
|
104
|
+
"calls_target_twice",
|
|
105
|
+
"calls_other",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TestSourceDeVendoring:
|
|
110
|
+
"""find/view/complexity de-vendor the source glob via fledgling's ignore
|
|
111
|
+
policy so a whole-repo glob doesn't drown in submodules / vendored trees
|
|
112
|
+
(fledgling #47). A bare glob returns only the project's own code; an
|
|
113
|
+
explicitly vendor-targeted glob is still honored (not filtered to zero)."""
|
|
114
|
+
|
|
115
|
+
def _make_repo(self, tmp_path):
|
|
116
|
+
(tmp_path / "app.py").write_text("def own_func():\n return 1\n")
|
|
117
|
+
nm = tmp_path / "node_modules" / "dep"
|
|
118
|
+
nm.mkdir(parents=True)
|
|
119
|
+
(nm / "lib.py").write_text("def vendored_func():\n return 2\n")
|
|
120
|
+
return tmp_path
|
|
121
|
+
|
|
122
|
+
def test_whole_repo_glob_excludes_vendored(self, tmp_path):
|
|
123
|
+
repo = self._make_repo(tmp_path)
|
|
124
|
+
names = set(
|
|
125
|
+
find_names_executor(source=f"{repo}/**/*.py", selector=".function")
|
|
126
|
+
)
|
|
127
|
+
assert "own_func" in names
|
|
128
|
+
assert "vendored_func" not in names
|
|
129
|
+
|
|
130
|
+
def test_explicit_vendor_target_is_honored(self, tmp_path):
|
|
131
|
+
# Aiming straight at the vendored tree must still return it -- filtering
|
|
132
|
+
# it to zero would defeat an intentional query.
|
|
133
|
+
repo = self._make_repo(tmp_path)
|
|
134
|
+
names = set(
|
|
135
|
+
find_names_executor(
|
|
136
|
+
source=f"{repo}/node_modules/**/*.py", selector=".function"
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
assert "vendored_func" in names
|
|
140
|
+
|
|
141
|
+
def test_git_submodule_contents_excluded(self, tmp_path):
|
|
142
|
+
# A submodule dir with an arbitrary name no denylist could catch -- only
|
|
143
|
+
# .gitmodules-driven exclusion removes it. Root is derived from the glob.
|
|
144
|
+
(tmp_path / ".git").write_text("gitdir: /nowhere\n")
|
|
145
|
+
(tmp_path / ".gitmodules").write_text(
|
|
146
|
+
'[submodule "libfoo"]\n\tpath = libfoo\n\turl = https://x/libfoo\n'
|
|
147
|
+
)
|
|
148
|
+
(tmp_path / "main.py").write_text("def main_func():\n return 1\n")
|
|
149
|
+
sub = tmp_path / "libfoo"
|
|
150
|
+
sub.mkdir()
|
|
151
|
+
(sub / "vend.py").write_text("def sub_func():\n return 2\n")
|
|
152
|
+
names = set(
|
|
153
|
+
find_names_executor(source=f"{tmp_path}/**/*.py", selector=".function")
|
|
154
|
+
)
|
|
155
|
+
assert "main_func" in names
|
|
156
|
+
assert "sub_func" not in names
|
|
157
|
+
|
|
158
|
+
def test_single_file_source_passes_through(self, tmp_path):
|
|
159
|
+
# No glob -> explicit single target -> no filtering machinery.
|
|
160
|
+
(tmp_path / "solo.py").write_text("def solo_func():\n return 1\n")
|
|
161
|
+
names = set(
|
|
162
|
+
find_names_executor(source=f"{tmp_path}/solo.py", selector=".function")
|
|
163
|
+
)
|
|
164
|
+
assert "solo_func" in names
|
|
165
|
+
|
|
166
|
+
|
|
77
167
|
class TestPluckitToolsList:
|
|
78
168
|
|
|
79
169
|
def test_five_tools_defined(self):
|
|
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
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-11-phase3-pluckit-rewire.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-12-pluckit-tool-integration.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-26-named-fts-collections.md
RENAMED
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/plans/2026-04-27-kibitzer-fts-doc-retrieval.md
RENAMED
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md
RENAMED
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/skills/exploring-unfamiliar-docs/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/skills/refactoring-with-squackit/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/specs/2026-04-12-cli-tool-exposure-design.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/specs/2026-04-16-training-data-pipeline-design.md
RENAMED
|
File without changes
|
{squackit-0.7.0 → squackit-0.7.1}/docs/superpowers/specs/2026-04-26-named-fts-collections-design.md
RENAMED
|
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
|