squackit 0.8.3__tar.gz → 0.8.4__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.3 → squackit-0.8.4}/CHANGELOG.md +37 -0
- {squackit-0.8.3 → squackit-0.8.4}/PKG-INFO +1 -1
- {squackit-0.8.3 → squackit-0.8.4}/pyproject.toml +1 -1
- {squackit-0.8.3 → squackit-0.8.4}/squackit/workflows.py +54 -7
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_error_visibility.py +55 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_sandbox_anchoring.py +95 -0
- {squackit-0.8.3 → squackit-0.8.4}/.github/workflows/release.yml +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/.gitignore +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/.mcp.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/.readthedocs.yaml +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/.readthedocs.yml +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/CLAUDE.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/LICENSE +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/README.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/agent-system-prompt.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/architecture.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/configuration.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/index.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/prompts.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/proposals/2026-06-04-response-type-improvements.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/quickstart.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/resources.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-10-phase1-handoff.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-10-squawkit-extraction.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-11-phase3-pluckit-rewire.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-12-cli-tool-exposure.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-12-pluckit-tool-integration.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-12-session-handoff.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-16-session-handoff.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-26-named-fts-collections.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-27-kibitzer-fts-doc-retrieval.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/skills/exploring-unfamiliar-docs/SKILL.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/skills/pluck-chains/SKILL.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/skills/refactoring-with-squackit/SKILL.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/skills/using-squackit/SKILL.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/specs/2026-04-10-squawkit-design.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/specs/2026-04-12-cli-tool-exposure-design.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/specs/2026-04-12-pluckit-tool-integration-design.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/specs/2026-04-16-training-data-pipeline-design.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/specs/2026-04-26-named-fts-collections-design.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/docs/tools.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/mkdocs.yml +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/EXPERIMENT_LOG.md +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/benchmark_models.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/cascade_prototype.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/combo_eval.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/generate_examples.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/grid_generate_examples.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_task_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_task_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_task_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_task_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_task_results/qwen3.5_2b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_task_results/qwen3_8b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_task_results/scoring_input.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_task_results/summary.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/hard_tasks.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/harvest_grid.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/iterative_eval.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/iterative_results/summary.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/iterative_results/summary_deepseek_plain.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/iterative_results/summary_distilled_xml.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/iterative_results/summary_editor_3b_7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/iterative_results/summary_fulldocs.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/iterative_results/summary_plaintext_3b7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/codegemma_2b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/codegemma_7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/deepseek-llm_7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/gemma_latest.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/glm4_9b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/granite-code_3b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/granite3.1-dense_2b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/granite3.3_2b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/llama2-uncensored_7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/llama3.2-vision_11b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/llama3.2_1b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/llama3.2_latest.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/phi3_latest.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/phi4-mini_latest.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen2.5-coder_1.5b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen2.5_1.5b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen2.5_3b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen2.5_7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen2_latest.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen3.5_0.8b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen3.5_2b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen3.5_4b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen3.5_9b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen3_0.6b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/qwen3_8b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/smollm2_latest.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_card_results/summary.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/model_cards.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/multiline_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/multiline_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/multiline_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/multiline_results/qwen3.5_2b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/multiline_results/qwen3_8b.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/multiline_results/summary.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/multiline_tasks.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/qm_retrieval_eval.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/quartermaster_eval.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/quartermaster_results/retrieval_summary.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/quartermaster_results/summary.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/selector_eval.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/scripts/selector_results/summary.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/__init__.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/__main__.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/cli.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/data/examples.json +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/db.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/defaults.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/formatting.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/lackpy_integration.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/prompts.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/runtime.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/server.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/session.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/tool_config.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/squackit/tools.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/conftest.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/fixtures/has_call_filter.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_cli_tools.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_config_consumption.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_defaults.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_formatting_json.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_fts_root.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_prompts.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_resources.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_runtime_config.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_session.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_smoke.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_tool_config.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_tools.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_truncation.py +0 -0
- {squackit-0.8.3 → squackit-0.8.4}/tests/test_workflows.py +0 -0
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.4
|
|
4
|
+
|
|
5
|
+
A review of 0.8.1–0.8.3 found both fixes had reached the macro-tool layer and
|
|
6
|
+
stopped there. The compound tools have their own parameters and never pass
|
|
7
|
+
through `apply_defaults`, so each gap was reachable through a different door.
|
|
8
|
+
|
|
9
|
+
### Fixed — the compound tools did not anchor explicit patterns
|
|
10
|
+
|
|
11
|
+
0.8.1 anchored caller-supplied relative patterns, but only in the macro-tool
|
|
12
|
+
wrapper. `investigate`, `review` and `search` take `file_pattern` themselves and
|
|
13
|
+
handed it to the macro unchanged, so a relative glob still resolved against the
|
|
14
|
+
process working directory. On a server whose cwd is not the corpus — the exact
|
|
15
|
+
deployment 0.8.1 was written for — this reproduced the original failure:
|
|
16
|
+
|
|
17
|
+
investigate(name="parse_config", file_pattern="src/**/*.py")
|
|
18
|
+
-> IOException: Failed to initialize file processing ... Permission
|
|
19
|
+
|
|
20
|
+
Verified against the published 0.8.3 before fixing. The 0.8.1 note that
|
|
21
|
+
"caller-supplied relative patterns are anchored too" was true of the macro path
|
|
22
|
+
only; it is now true of both. Absolute patterns are still passed through
|
|
23
|
+
untouched, and an explicit pattern still works without a defaults object.
|
|
24
|
+
|
|
25
|
+
### Fixed — briefing sections named no cause
|
|
26
|
+
|
|
27
|
+
`_section` wraps every part of `explore`, `review` and `search`, and
|
|
28
|
+
`investigate`'s Source/Called-by/Calls. It caught everything, logged at DEBUG —
|
|
29
|
+
invisible at normal levels — and rendered a bare `(could not load)`. A
|
|
30
|
+
permission error, a missing git revision and an unloaded macro were
|
|
31
|
+
indistinguishable from each other and from a section that legitimately had
|
|
32
|
+
nothing.
|
|
33
|
+
|
|
34
|
+
That is the same defect 0.8.3 fixed in `investigate`'s primary lookup and did
|
|
35
|
+
not carry to the siblings. Sections now report the exception type and message,
|
|
36
|
+
capped at 200 characters with the truncation marked, and log at WARNING. One
|
|
37
|
+
failing section still cannot take down a briefing.
|
|
38
|
+
|
|
39
|
+
|
|
3
40
|
## 0.8.3
|
|
4
41
|
|
|
5
42
|
Three fixes with one theme: a failure must not be representable as an ordinary
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: squackit
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
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
|
|
@@ -32,20 +32,41 @@ def _format_briefing(title: str, sections: list[tuple[str, str]]) -> str:
|
|
|
32
32
|
return "\n".join(parts)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
#: How much of a failing section's error text reaches the briefing. DuckDB
|
|
36
|
+
#: errors run to hundreds of characters and a briefing is prose, but a cause
|
|
37
|
+
#: the reader cannot see is the thing this whole clause exists to avoid.
|
|
38
|
+
_SECTION_ERROR_CHARS = 200
|
|
39
|
+
|
|
40
|
+
|
|
35
41
|
def _section(heading: str, fn):
|
|
36
42
|
"""Run fn() with error handling, return (heading, content) tuple.
|
|
37
43
|
|
|
38
|
-
Returns "(no data)" for empty/None results,
|
|
39
|
-
|
|
44
|
+
Returns "(no data)" for empty/None results, and "(could not load — <type>:
|
|
45
|
+
<message>)" on exceptions.
|
|
46
|
+
|
|
47
|
+
THE CAUSE IS IN THE TEXT ON PURPOSE. This wrapper covers every section of
|
|
48
|
+
explore/review/search and investigate's Source/Called-by/Calls. It used to
|
|
49
|
+
return a bare "(could not load)" and log at DEBUG, which is invisible at
|
|
50
|
+
normal levels — so a permission error, a missing git revision and a macro
|
|
51
|
+
that simply is not loaded all rendered identically, and none of them said
|
|
52
|
+
why. That is the same "a failure must not be representable as an ordinary
|
|
53
|
+
result" defect fixed in investigate's primary lookup in 0.8.3; it was not
|
|
54
|
+
extended to the sibling sections until 0.8.4.
|
|
55
|
+
|
|
56
|
+
Still non-fatal: one broken section must not take down the briefing, which
|
|
57
|
+
is why the guard stays.
|
|
40
58
|
"""
|
|
41
59
|
try:
|
|
42
60
|
content = fn()
|
|
43
61
|
if not content:
|
|
44
62
|
return (heading, "(no data)")
|
|
45
63
|
return (heading, content)
|
|
46
|
-
except Exception:
|
|
47
|
-
log.
|
|
48
|
-
|
|
64
|
+
except Exception as exc:
|
|
65
|
+
log.warning("section %r failed: %s", heading, exc, exc_info=True)
|
|
66
|
+
detail = str(exc).strip().replace("\n", " ")
|
|
67
|
+
if len(detail) > _SECTION_ERROR_CHARS:
|
|
68
|
+
detail = detail[:_SECTION_ERROR_CHARS] + "…"
|
|
69
|
+
return (heading, f"(could not load — {type(exc).__name__}: {detail})")
|
|
49
70
|
|
|
50
71
|
|
|
51
72
|
def _has_module(con, module_name: str) -> bool:
|
|
@@ -92,6 +113,31 @@ def _sorted_table(con, macro_name, kwargs, sort_col, max_rows, descending=True):
|
|
|
92
113
|
# ── Compound tools ─────────────────────────────────────────────────
|
|
93
114
|
|
|
94
115
|
|
|
116
|
+
def _anchored(defaults, file_pattern):
|
|
117
|
+
"""Root a caller-supplied file pattern, the way apply_defaults does.
|
|
118
|
+
|
|
119
|
+
The compound tools take `file_pattern` as their own parameter and never
|
|
120
|
+
pass through `apply_defaults`, so 0.8.1's anchoring of explicit relative
|
|
121
|
+
patterns reached the macro tools only. A relative glob handed to
|
|
122
|
+
`investigate`/`review`/`search` still resolved against the process cwd and,
|
|
123
|
+
under fledgling's sandbox, failed outright — the same deployment shape
|
|
124
|
+
0.8.1 was written for, reached through a different door.
|
|
125
|
+
|
|
126
|
+
`anchor()` passes absolute patterns through untouched, so a caller who
|
|
127
|
+
named a path in full still gets it.
|
|
128
|
+
|
|
129
|
+
Tolerates ``defaults=None``. An explicit ``file_pattern`` previously needed
|
|
130
|
+
no defaults object at all — the caller had already said where to look — and
|
|
131
|
+
callers rely on that: several tests drive ``investigate`` with a fake
|
|
132
|
+
connection and no defaults to isolate the name-matching logic. There is
|
|
133
|
+
also nothing to anchor *against* without a root, which is the same reason
|
|
134
|
+
``anchor()`` itself passes patterns through when ``root`` is None.
|
|
135
|
+
"""
|
|
136
|
+
if file_pattern is None or defaults is None:
|
|
137
|
+
return file_pattern
|
|
138
|
+
return defaults.anchor(file_pattern)
|
|
139
|
+
|
|
140
|
+
|
|
95
141
|
def explore(con, defaults, path=None):
|
|
96
142
|
"""First-contact codebase briefing."""
|
|
97
143
|
# Scope patterns to path if provided
|
|
@@ -144,6 +190,7 @@ def investigate(con, defaults, name, file_pattern=None, path=None):
|
|
|
144
190
|
path: Project root to scope the search to. Defaults to process cwd.
|
|
145
191
|
Only consulted when file_pattern is None.
|
|
146
192
|
"""
|
|
193
|
+
file_pattern = _anchored(defaults, file_pattern)
|
|
147
194
|
if file_pattern is None:
|
|
148
195
|
from squackit.runtime import resolve_scope_path
|
|
149
196
|
# Precedence: explicit path -> runtime.active_root -> process cwd.
|
|
@@ -288,7 +335,7 @@ def review(con, defaults, from_rev=None, to_rev=None, file_pattern=None):
|
|
|
288
335
|
"""Code review prep for a revision range."""
|
|
289
336
|
from_rev = from_rev or defaults.from_rev
|
|
290
337
|
to_rev = to_rev or defaults.to_rev
|
|
291
|
-
file_pattern = file_pattern or defaults.code_glob
|
|
338
|
+
file_pattern = _anchored(defaults, file_pattern) or defaults.code_glob
|
|
292
339
|
|
|
293
340
|
sections = []
|
|
294
341
|
|
|
@@ -368,7 +415,7 @@ def review(con, defaults, from_rev=None, to_rev=None, file_pattern=None):
|
|
|
368
415
|
|
|
369
416
|
def search(con, defaults, query, file_pattern=None):
|
|
370
417
|
"""Multi-source search across code, docs, and git."""
|
|
371
|
-
file_pattern = file_pattern or defaults.code_glob
|
|
418
|
+
file_pattern = _anchored(defaults, file_pattern) or defaults.code_glob
|
|
372
419
|
|
|
373
420
|
sections = []
|
|
374
421
|
|
|
@@ -196,3 +196,58 @@ class TestMutationNamedMacrosSayTheyDoNotMutate:
|
|
|
196
196
|
d = ToolPresentation(info=_Info("ast_replace", ["source"]),
|
|
197
197
|
description_override="Overridden.")
|
|
198
198
|
assert d.description == "Overridden."
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ── Briefing sections name their failure (#2 from the 0.8.3 review) ──
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class TestSectionFailuresAreNamed:
|
|
205
|
+
"""`_section` wraps every part of explore/review/search/investigate.
|
|
206
|
+
|
|
207
|
+
It caught everything, logged at DEBUG (invisible at normal levels) and
|
|
208
|
+
rendered "(could not load)" — a string that cannot be told apart from a
|
|
209
|
+
section that legitimately has nothing, and which names no cause. 0.8.3
|
|
210
|
+
fixed exactly this shape in investigate's primary lookup but never reached
|
|
211
|
+
the sibling sections, so the same permission error that motivated 0.8.1
|
|
212
|
+
still arrived as undifferentiated text in three of the four compound tools.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def test_a_failing_section_still_reports_could_not_load(self):
|
|
216
|
+
"""Keep the recognisable phrase — callers and tests match on it."""
|
|
217
|
+
from squackit.workflows import _section
|
|
218
|
+
|
|
219
|
+
heading, content = _section("Diffs", lambda: (_ for _ in ()).throw(
|
|
220
|
+
RuntimeError("boom")))
|
|
221
|
+
assert heading == "Diffs"
|
|
222
|
+
assert "could not load" in content.lower()
|
|
223
|
+
|
|
224
|
+
def test_a_failing_section_names_the_error(self):
|
|
225
|
+
from squackit.workflows import _section
|
|
226
|
+
|
|
227
|
+
_heading, content = _section("Diffs", lambda: (_ for _ in ()).throw(
|
|
228
|
+
RuntimeError("permission denied on **/*.py")))
|
|
229
|
+
assert "RuntimeError" in content, content
|
|
230
|
+
assert "permission denied" in content, content
|
|
231
|
+
|
|
232
|
+
def test_an_empty_section_is_distinguishable_from_a_failed_one(self):
|
|
233
|
+
from squackit.workflows import _section
|
|
234
|
+
|
|
235
|
+
_h, empty = _section("Docs", lambda: "")
|
|
236
|
+
_h2, failed = _section("Docs", lambda: (_ for _ in ()).throw(
|
|
237
|
+
RuntimeError("boom")))
|
|
238
|
+
assert empty != failed
|
|
239
|
+
assert "no data" in empty.lower()
|
|
240
|
+
|
|
241
|
+
def test_a_very_long_error_is_capped_visibly(self):
|
|
242
|
+
"""DuckDB errors run to hundreds of characters; a briefing is prose."""
|
|
243
|
+
from squackit.workflows import _section
|
|
244
|
+
|
|
245
|
+
_h, content = _section("Diffs", lambda: (_ for _ in ()).throw(
|
|
246
|
+
RuntimeError("x" * 4000)))
|
|
247
|
+
assert len(content) < 400, len(content)
|
|
248
|
+
assert "…" in content or "..." in content, content
|
|
249
|
+
|
|
250
|
+
def test_a_working_section_is_untouched(self):
|
|
251
|
+
from squackit.workflows import _section
|
|
252
|
+
|
|
253
|
+
assert _section("Docs", lambda: "| a | b |") == ("Docs", "| a | b |")
|
|
@@ -156,6 +156,19 @@ def repo(tmp_path_factory):
|
|
|
156
156
|
subprocess.run(["git", "add", "-A"], cwd=root, check=True)
|
|
157
157
|
subprocess.run(["git", "-c", "user.email=t@e", "-c", "user.name=t",
|
|
158
158
|
"commit", "-qm", "initial"], cwd=root, check=True)
|
|
159
|
+
# A SECOND commit, so HEAD~1 resolves. `review` defaults to HEAD~1..HEAD,
|
|
160
|
+
# and on a one-commit repo its git sections fail for that reason alone —
|
|
161
|
+
# which renders as "(could not load)", exactly like the sandbox failure
|
|
162
|
+
# this file is about. Without this the review test passes or fails for the
|
|
163
|
+
# wrong reason.
|
|
164
|
+
(root / "src" / "mod.py").write_text(
|
|
165
|
+
"def parse_widget_config(path):\n"
|
|
166
|
+
" '''Load the widget config.'''\n"
|
|
167
|
+
" return {'version': 1}\n"
|
|
168
|
+
)
|
|
169
|
+
subprocess.run(["git", "add", "-A"], cwd=root, check=True)
|
|
170
|
+
subprocess.run(["git", "-c", "user.email=t@e", "-c", "user.name=t",
|
|
171
|
+
"commit", "-qm", "second"], cwd=root, check=True)
|
|
159
172
|
return root
|
|
160
173
|
|
|
161
174
|
|
|
@@ -221,3 +234,85 @@ class TestServedFromForeignCwd:
|
|
|
221
234
|
mcp = create_server(root=str(repo), init=False)
|
|
222
235
|
result = _run_async(mcp.call_tool("doc_outline", {}))
|
|
223
236
|
assert "Widget Guide" in result.content[0].text or "guide.md" in result.content[0].text
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ── Workflow tools anchor explicit patterns too (0.8.4) ─────────────
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@requires_fastmcp
|
|
243
|
+
class TestWorkflowToolsAnchorExplicitPatterns:
|
|
244
|
+
"""The compound tools never passed through `apply_defaults`.
|
|
245
|
+
|
|
246
|
+
0.8.1 anchored caller-supplied relative patterns — but only on the macro
|
|
247
|
+
tool wrapper in server.py. `investigate`, `review` and `search` take
|
|
248
|
+
`file_pattern` as their own parameter and handed it to the macro unchanged,
|
|
249
|
+
so an explicit relative glob still resolved against the process cwd and,
|
|
250
|
+
under fledgling's sandbox, failed outright:
|
|
251
|
+
|
|
252
|
+
investigate(name="parse_config", file_pattern="src/**/*.py")
|
|
253
|
+
-> IOException: Failed to initialize file processing ... Permission
|
|
254
|
+
|
|
255
|
+
Same deployment shape 0.8.1 was written for, reached through a different
|
|
256
|
+
door. The CHANGELOG claim "caller-supplied relative patterns are anchored
|
|
257
|
+
too" was true of the macro path only.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
def _server(self, repo):
|
|
261
|
+
from squackit.server import create_server
|
|
262
|
+
return create_server(root=str(repo), init=False)
|
|
263
|
+
|
|
264
|
+
def test_investigate_with_an_explicit_relative_pattern(self, repo, outside_cwd):
|
|
265
|
+
mcp = self._server(repo)
|
|
266
|
+
text = _run_async(mcp.call_tool(
|
|
267
|
+
"investigate", {"name": "parse_widget_config",
|
|
268
|
+
"file_pattern": "src/**/*.py"})).content[0].text
|
|
269
|
+
assert "Could not look up" not in text, (
|
|
270
|
+
f"explicit relative pattern was not anchored:\n{text[:300]}")
|
|
271
|
+
assert "parse_widget_config" in text
|
|
272
|
+
|
|
273
|
+
def test_search_with_an_explicit_relative_pattern(self, repo, outside_cwd):
|
|
274
|
+
mcp = self._server(repo)
|
|
275
|
+
text = _run_async(mcp.call_tool(
|
|
276
|
+
"search", {"query": "parse", "file_pattern": "src/**/*.py"})).content[0].text
|
|
277
|
+
assert "could not load" not in text.lower(), (
|
|
278
|
+
f"search section failed with an explicit relative pattern:\n{text[:400]}")
|
|
279
|
+
|
|
280
|
+
def test_review_with_an_explicit_relative_pattern(self, repo, outside_cwd):
|
|
281
|
+
mcp = self._server(repo)
|
|
282
|
+
text = _run_async(mcp.call_tool(
|
|
283
|
+
"review", {"file_pattern": "src/**/*.py"})).content[0].text
|
|
284
|
+
assert "could not load" not in text.lower(), (
|
|
285
|
+
f"review section failed with an explicit relative pattern:\n{text[:400]}")
|
|
286
|
+
|
|
287
|
+
def test_an_explicit_absolute_pattern_is_still_honoured(self, repo, outside_cwd):
|
|
288
|
+
"""Anchoring must not re-root a path the caller gave in full."""
|
|
289
|
+
mcp = self._server(repo)
|
|
290
|
+
text = _run_async(mcp.call_tool(
|
|
291
|
+
"investigate", {"name": "parse_widget_config",
|
|
292
|
+
"file_pattern": f"{repo}/src/**/*.py"})).content[0].text
|
|
293
|
+
assert "parse_widget_config" in text
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class TestAnchoringToleratesMissingDefaults:
|
|
297
|
+
"""An explicit pattern needs no defaults object.
|
|
298
|
+
|
|
299
|
+
Regression: the 0.8.4 anchoring helper dereferenced `defaults` whenever a
|
|
300
|
+
pattern was supplied, which broke callers that pass `defaults=None` to
|
|
301
|
+
drive a workflow against a fake connection. The old code never touched
|
|
302
|
+
defaults on that path — the caller had already said where to look.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
def test_explicit_pattern_survives_a_none_defaults(self):
|
|
306
|
+
from squackit.workflows import _anchored
|
|
307
|
+
|
|
308
|
+
assert _anchored(None, "src/**/*.py") == "src/**/*.py"
|
|
309
|
+
|
|
310
|
+
def test_none_pattern_with_none_defaults(self):
|
|
311
|
+
from squackit.workflows import _anchored
|
|
312
|
+
|
|
313
|
+
assert _anchored(None, None) is None
|
|
314
|
+
|
|
315
|
+
def test_a_rootless_defaults_leaves_the_pattern_alone(self):
|
|
316
|
+
from squackit.workflows import _anchored
|
|
317
|
+
|
|
318
|
+
assert _anchored(ProjectDefaults(), "src/**/*.py") == "src/**/*.py"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-11-phase3-pluckit-rewire.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-12-pluckit-tool-integration.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-26-named-fts-collections.md
RENAMED
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/plans/2026-04-27-kibitzer-fts-doc-retrieval.md
RENAMED
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md
RENAMED
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/skills/exploring-unfamiliar-docs/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/skills/refactoring-with-squackit/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/specs/2026-04-12-cli-tool-exposure-design.md
RENAMED
|
File without changes
|
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/docs/superpowers/specs/2026-04-16-training-data-pipeline-design.md
RENAMED
|
File without changes
|
{squackit-0.8.3 → squackit-0.8.4}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|