squackit 0.8.2__tar.gz → 0.8.3__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.8.2 → squackit-0.8.3}/CHANGELOG.md +62 -0
- {squackit-0.8.2 → squackit-0.8.3}/PKG-INFO +1 -1
- {squackit-0.8.2 → squackit-0.8.3}/pyproject.toml +1 -1
- squackit-0.8.3/squackit/__init__.py +17 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/tool_config.py +32 -1
- {squackit-0.8.2 → squackit-0.8.3}/squackit/tools.py +17 -4
- {squackit-0.8.2 → squackit-0.8.3}/squackit/workflows.py +18 -1
- squackit-0.8.3/tests/test_error_visibility.py +198 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_smoke.py +17 -1
- squackit-0.8.2/squackit/__init__.py +0 -3
- {squackit-0.8.2 → squackit-0.8.3}/.github/workflows/release.yml +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/.gitignore +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/.mcp.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/.readthedocs.yaml +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/.readthedocs.yml +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/CLAUDE.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/LICENSE +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/README.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/agent-system-prompt.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/architecture.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/configuration.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/index.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/prompts.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/proposals/2026-06-04-response-type-improvements.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/quickstart.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/resources.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-10-phase1-handoff.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-10-squawkit-extraction.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-11-phase3-pluckit-rewire.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-12-cli-tool-exposure.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-12-pluckit-tool-integration.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-12-session-handoff.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-16-session-handoff.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-26-named-fts-collections.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-27-kibitzer-fts-doc-retrieval.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/skills/exploring-unfamiliar-docs/SKILL.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/skills/pluck-chains/SKILL.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/skills/refactoring-with-squackit/SKILL.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/skills/using-squackit/SKILL.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/specs/2026-04-10-squawkit-design.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/specs/2026-04-12-cli-tool-exposure-design.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/specs/2026-04-12-pluckit-tool-integration-design.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/specs/2026-04-16-training-data-pipeline-design.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/specs/2026-04-26-named-fts-collections-design.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/docs/tools.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/mkdocs.yml +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/EXPERIMENT_LOG.md +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/benchmark_models.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/cascade_prototype.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/combo_eval.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/generate_examples.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/grid_generate_examples.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_task_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_task_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_task_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_task_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_task_results/qwen3.5_2b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_task_results/qwen3_8b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_task_results/scoring_input.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_task_results/summary.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/hard_tasks.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/harvest_grid.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/iterative_eval.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/iterative_results/summary.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/iterative_results/summary_deepseek_plain.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/iterative_results/summary_distilled_xml.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/iterative_results/summary_editor_3b_7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/iterative_results/summary_fulldocs.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/iterative_results/summary_plaintext_3b7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/codegemma_2b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/codegemma_7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/deepseek-llm_7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/gemma_latest.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/glm4_9b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/granite-code_3b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/granite3.1-dense_2b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/granite3.3_2b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/llama2-uncensored_7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/llama3.2-vision_11b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/llama3.2_1b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/llama3.2_latest.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/phi3_latest.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/phi4-mini_latest.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen2.5-coder_1.5b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen2.5_1.5b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen2.5_3b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen2.5_7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen2_latest.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen3.5_0.8b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen3.5_2b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen3.5_4b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen3.5_9b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen3_0.6b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/qwen3_8b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/smollm2_latest.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_card_results/summary.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/model_cards.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/multiline_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/multiline_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/multiline_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/multiline_results/qwen3.5_2b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/multiline_results/qwen3_8b.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/multiline_results/summary.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/multiline_tasks.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/qm_retrieval_eval.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/quartermaster_eval.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/quartermaster_results/retrieval_summary.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/quartermaster_results/summary.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/selector_eval.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/scripts/selector_results/summary.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/__main__.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/cli.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/data/examples.json +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/db.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/defaults.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/formatting.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/lackpy_integration.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/prompts.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/runtime.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/server.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/squackit/session.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/conftest.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/fixtures/has_call_filter.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_cli_tools.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_config_consumption.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_defaults.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_formatting_json.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_fts_root.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_prompts.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_resources.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_runtime_config.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_sandbox_anchoring.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_session.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_tool_config.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_tools.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_truncation.py +0 -0
- {squackit-0.8.2 → squackit-0.8.3}/tests/test_workflows.py +0 -0
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.3
|
|
4
|
+
|
|
5
|
+
Three fixes with one theme: a failure must not be representable as an ordinary
|
|
6
|
+
result. Nothing here changes what a working call returns.
|
|
7
|
+
|
|
8
|
+
### Fixed — `investigate` reported failed lookups as confirmed absences (#14)
|
|
9
|
+
|
|
10
|
+
Every exception from the definition query was caught and rendered with the
|
|
11
|
+
sentence used for a genuine miss:
|
|
12
|
+
|
|
13
|
+
No definition found for 'X'. Try a broader pattern or check spelling.
|
|
14
|
+
|
|
15
|
+
So a permission error, a binder error and "that symbol isn't here" were
|
|
16
|
+
indistinguishable, and the advice actively misdirected — the pattern was never
|
|
17
|
+
the problem. This is how the 0.8.1 sandbox bug presented: every symbol in a
|
|
18
|
+
healthy project reported missing, for hours, while the server called all its
|
|
19
|
+
tools healthy.
|
|
20
|
+
|
|
21
|
+
A failed lookup now says so, names the underlying error, and reports the
|
|
22
|
+
pattern it searched. The friendly wording is kept for the genuine empty case,
|
|
23
|
+
where it is good advice.
|
|
24
|
+
|
|
25
|
+
### Fixed — a raising pluckin lost its tools silently (#14)
|
|
26
|
+
|
|
27
|
+
`collect_pluckin_tools` swallowed per-pluckin exceptions with a bare `pass`,
|
|
28
|
+
under a comment claiming the error would be "surfaced contextually". Nothing
|
|
29
|
+
surfaced it. The guard is right — one broken pluckin must not take down the
|
|
30
|
+
registry — but it now logs a warning naming the pluckin and the error.
|
|
31
|
+
|
|
32
|
+
Not reachable today (no shipped pluckin defines `squackit_tools`), fixed as
|
|
33
|
+
latent. The symptom it would produce — a silently shorter tool surface — was
|
|
34
|
+
independently hit through another mechanism and cost a four-combination version
|
|
35
|
+
bisection, because a shorter tool list doesn't read as a failure. It reads as a
|
|
36
|
+
different, reasonable version of the product.
|
|
37
|
+
|
|
38
|
+
### Fixed — `ast_replace` / `ast_patch` read as mutating and are not (#16)
|
|
39
|
+
|
|
40
|
+
Connection macros are published with a description generated from the
|
|
41
|
+
signature, so these carried no semantics at all:
|
|
42
|
+
|
|
43
|
+
Query: ast_replace(source, selector, new_text, language)
|
|
44
|
+
|
|
45
|
+
These are sitting_duck SQL macros. A SQL macro cannot write files — it computes
|
|
46
|
+
the patched source and returns it, which is coherent, and sitting_duck is not
|
|
47
|
+
misbehaving. But over MCP the caller reads a verb. One did: it called
|
|
48
|
+
`ast_replace` to repair a bug, got a successful-looking table back, and changed
|
|
49
|
+
nothing on disk.
|
|
50
|
+
|
|
51
|
+
Generated descriptions for macros whose *name* implies an effect now state that
|
|
52
|
+
they return the patched result and modify nothing. Read-only macros are
|
|
53
|
+
unchanged — a disclaimer on all ninety is context every agent pays for.
|
|
54
|
+
|
|
55
|
+
For an actually-mutating primitive, see #17.
|
|
56
|
+
|
|
57
|
+
### Fixed — `__version__` was four releases stale (#15, #2)
|
|
58
|
+
|
|
59
|
+
`squackit.__version__` was a hand-written `"0.4.1"` against a distribution
|
|
60
|
+
version of `0.8.2`, and survived `--force-reinstall` because it derived from
|
|
61
|
+
nothing. It is now read from `importlib.metadata`, and the smoke test asserts
|
|
62
|
+
that correspondence rather than a literal — which is what let both drift in
|
|
63
|
+
agreement with each other and with nothing else.
|
|
64
|
+
|
|
3
65
|
## 0.8.2
|
|
4
66
|
|
|
5
67
|
### Fixed — AST tools could not read a source outside the working directory
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: squackit
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.3
|
|
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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""squackit: Semi-QUalified Agent Companion Kit — the stateful intelligence + MCP server layer for fledgling-equipped agents."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version as _dist_version
|
|
4
|
+
|
|
5
|
+
# Derived, never written by hand. The literal that used to live here read
|
|
6
|
+
# "0.4.1" while the installed distribution was 0.8.2 — four releases stale, and
|
|
7
|
+
# immune to --force-reinstall because it was not derived from anything. Someone
|
|
8
|
+
# debugging an unrelated problem read it, concluded they had a mixed install,
|
|
9
|
+
# and went looking for a packaging fault that did not exist. A version string
|
|
10
|
+
# that lies makes a healthy install look broken, which is the most expensive
|
|
11
|
+
# direction to send a reader.
|
|
12
|
+
try:
|
|
13
|
+
__version__ = _dist_version("squackit")
|
|
14
|
+
except PackageNotFoundError: # running from a source tree with no install
|
|
15
|
+
__version__ = "0.0.0.dev0"
|
|
16
|
+
|
|
17
|
+
__all__ = ["__version__"]
|
|
@@ -37,6 +37,21 @@ _FALLBACK_NUMERIC = {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
#: Name tokens that read as an effect. A macro whose name contains one of these
|
|
41
|
+
#: gets an explicit "does not modify any file" note in its generated
|
|
42
|
+
#: description. Missing a verb costs nothing beyond the status quo; a false
|
|
43
|
+
#: positive adds a sentence that is true of every SQL macro anyway.
|
|
44
|
+
_MUTATION_VERBS = frozenset({
|
|
45
|
+
"replace", "patch", "apply", "write", "insert", "update", "delete",
|
|
46
|
+
"remove", "rename", "edit", "set", "modify", "save", "commit",
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _name_implies_mutation(name: str) -> bool:
|
|
51
|
+
"""True when a tool's NAME reads as an effect it does not perform."""
|
|
52
|
+
return any(token in _MUTATION_VERBS for token in name.split("_"))
|
|
53
|
+
|
|
54
|
+
|
|
40
55
|
@dataclass
|
|
41
56
|
class ToolPresentation:
|
|
42
57
|
"""Wraps fledgling ToolInfo with squackit's presentation/UX config."""
|
|
@@ -100,7 +115,23 @@ class ToolPresentation:
|
|
|
100
115
|
def description(self) -> str:
|
|
101
116
|
if self.description_override is not None:
|
|
102
117
|
return self.description_override
|
|
103
|
-
|
|
118
|
+
if self.info.description:
|
|
119
|
+
return self.info.description
|
|
120
|
+
signature = f"{self.name}({', '.join(self.params)})"
|
|
121
|
+
if _name_implies_mutation(self.name):
|
|
122
|
+
# SQL macros cannot write files — they compute and return a result
|
|
123
|
+
# set. But a caller reads a verb: `ast_replace` was called to repair
|
|
124
|
+
# a bug, returned a successful-looking table, and changed nothing on
|
|
125
|
+
# disk. The bare signature said nothing either way, and the only
|
|
126
|
+
# hint was the "Query:" prefix — a category label competing against
|
|
127
|
+
# an imperative. Spell it out for the names where it misleads, and
|
|
128
|
+
# only those: a disclaimer on all ~90 macros is context every agent
|
|
129
|
+
# pays for on every call.
|
|
130
|
+
return (
|
|
131
|
+
f"Query: {signature} — computes and RETURNS the patched result "
|
|
132
|
+
f"as text. Does not modify any file."
|
|
133
|
+
)
|
|
134
|
+
return f"Query: {signature}"
|
|
104
135
|
|
|
105
136
|
@property
|
|
106
137
|
def parameters_schema(self) -> dict | None:
|
|
@@ -9,6 +9,7 @@ fledgling macro tools. They take priority over fledgling equivalents
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import hashlib
|
|
12
|
+
import logging
|
|
12
13
|
import os
|
|
13
14
|
import re
|
|
14
15
|
import tempfile
|
|
@@ -16,6 +17,8 @@ import tempfile
|
|
|
16
17
|
from fledgling.tools import ToolInfo
|
|
17
18
|
from squackit.tool_config import ToolPresentation
|
|
18
19
|
|
|
20
|
+
log = logging.getLogger(__name__)
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
#: How much source text ``read_ast`` puts in the ``peek`` column when squackit
|
|
21
24
|
#: materializes its own table. ``'smart'`` matches what pluckit's cache uses by
|
|
@@ -305,10 +308,20 @@ def collect_pluckin_tools(plucker) -> list:
|
|
|
305
308
|
if callable(fn):
|
|
306
309
|
try:
|
|
307
310
|
tools.extend(fn())
|
|
308
|
-
except Exception:
|
|
309
|
-
#
|
|
310
|
-
#
|
|
311
|
-
|
|
311
|
+
except Exception as exc:
|
|
312
|
+
# Keep the guard — one broken pluckin must not take down the
|
|
313
|
+
# whole registry — but SAY SO. Dropping its tools silently
|
|
314
|
+
# yields a shorter tool surface, and a shorter tool list does
|
|
315
|
+
# not read as a failure: it reads as a different, reasonable
|
|
316
|
+
# version of the product, so callers reason forward from it
|
|
317
|
+
# instead of doubting it. (An earlier comment here claimed the
|
|
318
|
+
# server/CLI would "surface the error contextually". Nothing
|
|
319
|
+
# did.)
|
|
320
|
+
log.warning(
|
|
321
|
+
"pluckin %s failed to contribute squackit tools (%s); its "
|
|
322
|
+
"tools are missing from this session's registry",
|
|
323
|
+
type(pluckin).__name__, exc, exc_info=True,
|
|
324
|
+
)
|
|
312
325
|
return tools
|
|
313
326
|
|
|
314
327
|
|
|
@@ -160,13 +160,22 @@ def investigate(con, defaults, name, file_pattern=None, path=None):
|
|
|
160
160
|
# the whole briefing describe the wrong symbol. Fall back to the full
|
|
161
161
|
# substring set only when nothing matches exactly, preserving the
|
|
162
162
|
# forgiving lookup for partial names / typos.
|
|
163
|
+
# A FAILED LOOKUP IS NOT AN ABSENT SYMBOL. Both used to return the "no
|
|
164
|
+
# definition found" sentence below, which made a permission error, a binder
|
|
165
|
+
# error and a genuine miss indistinguishable — and told the reader to
|
|
166
|
+
# broaden a pattern that was never the problem. That is exactly how the
|
|
167
|
+
# 0.8.1 sandbox bug presented: every symbol in a healthy project reported
|
|
168
|
+
# missing, for hours, while the server called all its tools healthy.
|
|
169
|
+
lookup_error: Exception | None = None
|
|
163
170
|
try:
|
|
164
171
|
rel = con.find_definitions(
|
|
165
172
|
file_pattern=file_pattern, name_pattern=f"%{name}%",
|
|
166
173
|
)
|
|
167
174
|
defs = rel.fetchall()
|
|
168
175
|
def_cols = rel.columns
|
|
169
|
-
except Exception:
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
log.debug("find_definitions failed for %r", name, exc_info=True)
|
|
178
|
+
lookup_error = exc
|
|
170
179
|
defs = []
|
|
171
180
|
def_cols = []
|
|
172
181
|
|
|
@@ -176,6 +185,14 @@ def investigate(con, defaults, name, file_pattern=None, path=None):
|
|
|
176
185
|
if _exact:
|
|
177
186
|
defs = _exact
|
|
178
187
|
|
|
188
|
+
if lookup_error is not None:
|
|
189
|
+
return (
|
|
190
|
+
f"Could not look up '{name}' — the definition query failed, so "
|
|
191
|
+
f"whether this symbol exists is UNKNOWN.\n"
|
|
192
|
+
f"{type(lookup_error).__name__}: {lookup_error}\n"
|
|
193
|
+
f"Searched: {file_pattern}"
|
|
194
|
+
)
|
|
195
|
+
|
|
179
196
|
if not defs:
|
|
180
197
|
return f"No definition found for '{name}'. Try a broader pattern or check spelling."
|
|
181
198
|
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""A failure must not be representable as an ordinary result.
|
|
2
|
+
|
|
3
|
+
Three places used to violate that, each in a different way:
|
|
4
|
+
|
|
5
|
+
* ``investigate`` caught every query exception and rendered it with the same
|
|
6
|
+
sentence it uses for a genuine miss — so a permission error, a binder error
|
|
7
|
+
and "that symbol isn't here" were indistinguishable, and the advice offered
|
|
8
|
+
("try a broader pattern") actively misdirected. This is not hypothetical: it
|
|
9
|
+
is exactly how the 0.8.1 sandbox bug presented, for hours, while the server
|
|
10
|
+
reported every tool healthy.
|
|
11
|
+
* ``collect_pluckin_tools`` dropped a raising pluckin's tools with a bare
|
|
12
|
+
``pass`` under a comment claiming the error would be surfaced elsewhere.
|
|
13
|
+
Nothing surfaced it. A silently shorter tool surface is worse than a silently
|
|
14
|
+
empty result — an empty result prompts "did that work?", a shorter tool list
|
|
15
|
+
reads as a different, reasonable version of the product.
|
|
16
|
+
* Auto-generated macro descriptions were a bare signature, so a SQL macro named
|
|
17
|
+
``ast_replace`` — which computes patched source and writes nothing, because a
|
|
18
|
+
SQL macro *cannot* write — read as a mutation to any caller.
|
|
19
|
+
|
|
20
|
+
See issues #14 and #16.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
|
|
25
|
+
import pytest
|
|
26
|
+
|
|
27
|
+
from squackit.defaults import ProjectDefaults
|
|
28
|
+
from squackit.tool_config import ToolPresentation
|
|
29
|
+
from squackit.tools import collect_pluckin_tools
|
|
30
|
+
from squackit.workflows import investigate
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ── investigate: a failed query is not a miss ───────────────────────
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _Rel:
|
|
37
|
+
def __init__(self, rows, cols):
|
|
38
|
+
self._rows, self.columns = rows, cols
|
|
39
|
+
|
|
40
|
+
def fetchall(self):
|
|
41
|
+
return self._rows
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _ConRaises:
|
|
45
|
+
"""A connection whose lookup fails the way a sandboxed one does."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, exc):
|
|
48
|
+
self._exc = exc
|
|
49
|
+
|
|
50
|
+
def find_definitions(self, **kw):
|
|
51
|
+
raise self._exc
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _ConEmpty:
|
|
55
|
+
"""A connection that works and genuinely has no match."""
|
|
56
|
+
|
|
57
|
+
def find_definitions(self, **kw):
|
|
58
|
+
return _Rel([], ["file_path", "name", "kind",
|
|
59
|
+
"start_line", "end_line", "signature"])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.fixture
|
|
63
|
+
def defaults(tmp_path):
|
|
64
|
+
return ProjectDefaults(code_pattern="**/*.py", root=str(tmp_path))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TestInvestigateDistinguishesFailureFromMiss:
|
|
68
|
+
|
|
69
|
+
def test_genuine_miss_keeps_the_helpful_message(self, defaults):
|
|
70
|
+
"""The friendly wording is right when it IS a miss — keep it."""
|
|
71
|
+
out = investigate(_ConEmpty(), defaults, "nonexistent_symbol")
|
|
72
|
+
assert "No definition found" in out
|
|
73
|
+
assert "nonexistent_symbol" in out
|
|
74
|
+
|
|
75
|
+
def test_query_failure_does_NOT_claim_the_symbol_is_absent(self, defaults):
|
|
76
|
+
out = investigate(_ConRaises(RuntimeError("boom")), defaults, "parse_config")
|
|
77
|
+
assert "No definition found" not in out, (
|
|
78
|
+
"a failed lookup was reported as a confirmed absence"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def test_query_failure_says_it_failed(self, defaults):
|
|
82
|
+
out = investigate(_ConRaises(RuntimeError("boom")), defaults, "parse_config")
|
|
83
|
+
assert "could not" in out.lower() or "failed" in out.lower()
|
|
84
|
+
|
|
85
|
+
def test_query_failure_surfaces_the_underlying_error(self, defaults):
|
|
86
|
+
"""The 0.8.1 bug was diagnosable only from this text."""
|
|
87
|
+
exc = RuntimeError(
|
|
88
|
+
'Permission Error: Cannot access file "**/*.py" '
|
|
89
|
+
"- file system operations are disabled by configuration"
|
|
90
|
+
)
|
|
91
|
+
out = investigate(_ConRaises(exc), defaults, "parse_config")
|
|
92
|
+
assert "Permission Error" in out, out
|
|
93
|
+
|
|
94
|
+
def test_query_failure_does_NOT_advise_broadening_the_pattern(self, defaults):
|
|
95
|
+
"""That advice sends the reader at the wrong thing entirely."""
|
|
96
|
+
out = investigate(_ConRaises(RuntimeError("boom")), defaults, "parse_config")
|
|
97
|
+
assert "broader pattern" not in out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ── collect_pluckin_tools: a dropped pluckin is logged ───────────────
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class _Pluckin:
|
|
104
|
+
def __init__(self, tools=None, exc=None):
|
|
105
|
+
self._tools, self._exc = tools or [], exc
|
|
106
|
+
|
|
107
|
+
def squackit_tools(self):
|
|
108
|
+
if self._exc:
|
|
109
|
+
raise self._exc
|
|
110
|
+
return self._tools
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class _Plucker:
|
|
114
|
+
def __init__(self, pluckins):
|
|
115
|
+
self.pluckins = pluckins
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestPluckinFailuresAreVisible:
|
|
119
|
+
|
|
120
|
+
def test_a_raising_pluckin_does_not_break_the_registry(self):
|
|
121
|
+
"""The guard itself is correct and stays."""
|
|
122
|
+
good = _Pluckin(tools=["t1", "t2"])
|
|
123
|
+
bad = _Pluckin(exc=RuntimeError("plugin exploded"))
|
|
124
|
+
assert collect_pluckin_tools(_Plucker([bad, good])) == ["t1", "t2"]
|
|
125
|
+
|
|
126
|
+
def test_a_raising_pluckin_is_LOGGED(self, caplog):
|
|
127
|
+
bad = _Pluckin(exc=RuntimeError("plugin exploded"))
|
|
128
|
+
with caplog.at_level(logging.WARNING, logger="squackit.tools"):
|
|
129
|
+
collect_pluckin_tools(_Plucker([bad]))
|
|
130
|
+
assert caplog.records, "a pluckin's tools vanished with no log record"
|
|
131
|
+
|
|
132
|
+
def test_the_log_names_the_pluckin_and_the_error(self, caplog):
|
|
133
|
+
bad = _Pluckin(exc=RuntimeError("plugin exploded"))
|
|
134
|
+
with caplog.at_level(logging.WARNING, logger="squackit.tools"):
|
|
135
|
+
collect_pluckin_tools(_Plucker([bad]))
|
|
136
|
+
text = " ".join(r.getMessage() for r in caplog.records)
|
|
137
|
+
assert "_Pluckin" in text, text
|
|
138
|
+
assert "plugin exploded" in text, text
|
|
139
|
+
|
|
140
|
+
def test_healthy_pluckins_log_nothing(self, caplog):
|
|
141
|
+
with caplog.at_level(logging.WARNING, logger="squackit.tools"):
|
|
142
|
+
collect_pluckin_tools(_Plucker([_Pluckin(tools=["t1"])]))
|
|
143
|
+
assert not caplog.records
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ── generated descriptions state their semantics ─────────────────────
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class _Info:
|
|
150
|
+
"""Stand-in for fledgling's ToolInfo carrying no description of its own.
|
|
151
|
+
|
|
152
|
+
ToolPresentation derives `name` and `params` from the info object, so the
|
|
153
|
+
stub has to supply macro_name/params rather than the presentation taking
|
|
154
|
+
them directly.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def __init__(self, macro_name, params, description=None):
|
|
158
|
+
self.macro_name = macro_name
|
|
159
|
+
self.tool_name = None
|
|
160
|
+
self.params = list(params)
|
|
161
|
+
self.description = description
|
|
162
|
+
self.required = None
|
|
163
|
+
self.required_params = []
|
|
164
|
+
self.format = None
|
|
165
|
+
self.parameters_schema = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _presentation(name, params):
|
|
169
|
+
return ToolPresentation(info=_Info(name, params))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TestMutationNamedMacrosSayTheyDoNotMutate:
|
|
173
|
+
|
|
174
|
+
def test_ast_replace_states_it_does_not_modify(self):
|
|
175
|
+
d = _presentation("ast_replace", ["source", "selector", "new_text"])
|
|
176
|
+
assert "does not modify" in d.description.lower(), d.description
|
|
177
|
+
|
|
178
|
+
def test_ast_patch_states_it_does_not_modify(self):
|
|
179
|
+
d = _presentation("ast_patch", ["edits", "files"])
|
|
180
|
+
assert "does not modify" in d.description.lower(), d.description
|
|
181
|
+
|
|
182
|
+
def test_the_signature_is_still_there(self):
|
|
183
|
+
d = _presentation("ast_replace", ["source", "selector", "new_text"])
|
|
184
|
+
assert "ast_replace(source, selector, new_text)" in d.description
|
|
185
|
+
|
|
186
|
+
def test_read_only_macros_are_NOT_cluttered(self):
|
|
187
|
+
"""A disclaimer on all 90 macros is context every agent pays for."""
|
|
188
|
+
d = _presentation("find_definitions", ["file_pattern", "name_pattern"])
|
|
189
|
+
assert "does not modify" not in d.description.lower()
|
|
190
|
+
|
|
191
|
+
def test_a_real_description_still_wins(self):
|
|
192
|
+
info = _Info("ast_replace", ["source"], description="The macro's own words.")
|
|
193
|
+
assert ToolPresentation(info=info).description == "The macro's own words."
|
|
194
|
+
|
|
195
|
+
def test_an_override_still_wins(self):
|
|
196
|
+
d = ToolPresentation(info=_Info("ast_replace", ["source"]),
|
|
197
|
+
description_override="Overridden.")
|
|
198
|
+
assert d.description == "Overridden."
|
|
@@ -3,7 +3,23 @@
|
|
|
3
3
|
|
|
4
4
|
def test_import_squackit():
|
|
5
5
|
import squackit
|
|
6
|
-
assert squackit.__version__
|
|
6
|
+
assert squackit.__version__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_version_matches_the_installed_distribution():
|
|
10
|
+
"""Pin the exported version to the distribution, not to a literal.
|
|
11
|
+
|
|
12
|
+
Asserting a hardcoded string here is what let `__version__` drift four
|
|
13
|
+
releases behind pyproject.toml: bumping the release left the module and
|
|
14
|
+
this test agreeing with each other and with nothing else. See #2 and #15.
|
|
15
|
+
"""
|
|
16
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
17
|
+
import squackit
|
|
18
|
+
try:
|
|
19
|
+
expected = version("squackit")
|
|
20
|
+
except PackageNotFoundError:
|
|
21
|
+
expected = "0.0.0.dev0"
|
|
22
|
+
assert squackit.__version__ == expected
|
|
7
23
|
|
|
8
24
|
|
|
9
25
|
def test_fledgling_available():
|
|
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.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-11-phase3-pluckit-rewire.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-12-pluckit-tool-integration.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-26-named-fts-collections.md
RENAMED
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/plans/2026-04-27-kibitzer-fts-doc-retrieval.md
RENAMED
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md
RENAMED
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/skills/exploring-unfamiliar-docs/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/skills/refactoring-with-squackit/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/specs/2026-04-12-cli-tool-exposure-design.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/docs/superpowers/specs/2026-04-16-training-data-pipeline-design.md
RENAMED
|
File without changes
|
{squackit-0.8.2 → squackit-0.8.3}/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
|
|
File without changes
|