squackit 0.5.0__tar.gz → 0.7.0__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/CHANGELOG.md +56 -0
- {squackit-0.5.0 → squackit-0.7.0}/PKG-INFO +5 -3
- {squackit-0.5.0 → squackit-0.7.0}/README.md +4 -2
- squackit-0.7.0/docs/proposals/2026-06-04-response-type-improvements.md +134 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md +12 -2
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/skills/pluck-chains/SKILL.md +26 -8
- {squackit-0.5.0 → squackit-0.7.0}/pyproject.toml +1 -1
- {squackit-0.5.0 → squackit-0.7.0}/scripts/iterative_eval.py +1 -1
- {squackit-0.5.0 → squackit-0.7.0}/squackit/lackpy_integration.py +2 -2
- squackit-0.7.0/squackit/runtime.py +167 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/server.py +166 -6
- {squackit-0.5.0 → squackit-0.7.0}/squackit/tool_config.py +7 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/tools.py +7 -1
- {squackit-0.5.0 → squackit-0.7.0}/squackit/workflows.py +90 -14
- squackit-0.7.0/tests/test_fts_root.py +60 -0
- squackit-0.7.0/tests/test_runtime_config.py +175 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_workflows.py +45 -0
- squackit-0.5.0/CHANGELOG.md +0 -27
- {squackit-0.5.0 → squackit-0.7.0}/.github/workflows/release.yml +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/.gitignore +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/.mcp.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/.readthedocs.yaml +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/.readthedocs.yml +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/CLAUDE.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/LICENSE +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/agent-system-prompt.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/architecture.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/configuration.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/index.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/prompts.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/quickstart.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/resources.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-10-phase1-handoff.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-10-squawkit-extraction.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-11-phase3-pluckit-rewire.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-12-cli-tool-exposure.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-12-pluckit-tool-integration.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-12-session-handoff.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-16-session-handoff.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-26-named-fts-collections.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/plans/2026-04-27-kibitzer-fts-doc-retrieval.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/skills/exploring-unfamiliar-docs/SKILL.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/skills/refactoring-with-squackit/SKILL.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/skills/using-squackit/SKILL.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/specs/2026-04-10-squawkit-design.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/specs/2026-04-12-cli-tool-exposure-design.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/specs/2026-04-12-pluckit-tool-integration-design.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/specs/2026-04-16-training-data-pipeline-design.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/specs/2026-04-26-named-fts-collections-design.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/docs/tools.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/mkdocs.yml +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/EXPERIMENT_LOG.md +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/benchmark_models.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/cascade_prototype.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/combo_eval.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/generate_examples.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/grid_generate_examples.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_task_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_task_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_task_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_task_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_task_results/qwen3.5_2b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_task_results/qwen3_8b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_task_results/scoring_input.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_task_results/summary.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/hard_tasks.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/harvest_grid.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/iterative_results/summary.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/iterative_results/summary_deepseek_plain.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/iterative_results/summary_distilled_xml.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/iterative_results/summary_editor_3b_7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/iterative_results/summary_fulldocs.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/iterative_results/summary_plaintext_3b7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/codegemma_2b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/codegemma_7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/deepseek-llm_7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/gemma_latest.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/glm4_9b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/granite-code_3b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/granite3.1-dense_2b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/granite3.3_2b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/llama2-uncensored_7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/llama3.2-vision_11b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/llama3.2_1b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/llama3.2_latest.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/phi3_latest.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/phi4-mini_latest.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen2.5-coder_1.5b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen2.5_1.5b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen2.5_3b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen2.5_7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen2_latest.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen3.5_0.8b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen3.5_2b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen3.5_4b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen3.5_9b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen3_0.6b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/qwen3_8b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/smollm2_latest.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_card_results/summary.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/model_cards.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/multiline_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/multiline_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/multiline_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/multiline_results/qwen3.5_2b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/multiline_results/qwen3_8b.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/multiline_results/summary.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/multiline_tasks.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/qm_retrieval_eval.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/quartermaster_eval.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/quartermaster_results/retrieval_summary.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/quartermaster_results/summary.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/selector_eval.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/scripts/selector_results/summary.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/__init__.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/__main__.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/cli.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/data/examples.json +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/db.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/defaults.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/formatting.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/prompts.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/squackit/session.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/conftest.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_cli_tools.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_defaults.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_formatting_json.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_prompts.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_resources.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_session.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_smoke.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_tool_config.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_tools.py +0 -0
- {squackit-0.5.0 → squackit-0.7.0}/tests/test_truncation.py +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Added — `investigate(path=)` for cross-project scoping
|
|
6
|
+
`investigate(name, path=)` accepts an explicit `path` argument and scopes the
|
|
7
|
+
symbol-lookup to that path's `scoped_code_pattern`. Without `path=`, defaults
|
|
8
|
+
to the process `cwd`-scoped pattern (was: the global registry pattern, which
|
|
9
|
+
substring-matched across every indexed project — e.g. `investigate("main")`
|
|
10
|
+
returned hits from vendored JS in unrelated repos). Pre-existing
|
|
11
|
+
`file_pattern=` overrides still work.
|
|
12
|
+
|
|
13
|
+
### Added — per-root FTS search
|
|
14
|
+
`search_*` tools now accept `root=<dir>` to index + search any repo on the
|
|
15
|
+
fly. Indexes are LRU-cached per root for the session, so repeated queries
|
|
16
|
+
in the same root stay cheap.
|
|
17
|
+
|
|
18
|
+
### Fixed — agent-ergonomics findings
|
|
19
|
+
- FTS now fails loud on index errors instead of silently returning empty.
|
|
20
|
+
- `investigate` no longer mislabels callers in cross-file results.
|
|
21
|
+
- Selector docs verified end-to-end against the running tool surface.
|
|
22
|
+
- `lackpy` 0.12 dropped top-level re-exports; switched to submodule imports
|
|
23
|
+
to keep import-time light.
|
|
24
|
+
|
|
25
|
+
### Docs
|
|
26
|
+
- `:has(.call#NAME)` works now that pluckit delegates selector compilation
|
|
27
|
+
to sitting_duck (was a notable known-bug in 0.5.0).
|
|
28
|
+
- Tool count corrected in README (~20 was wrong; actually ~35 / ~40 full).
|
|
29
|
+
- Receiver-qualified call selectors (#4) marked DONE.
|
|
30
|
+
- Response-type improvement proposals collected in `docs/proposals/`.
|
|
31
|
+
|
|
32
|
+
## 0.5.0
|
|
33
|
+
|
|
34
|
+
### Changed — migrate off pluckit/fledgling private internals
|
|
35
|
+
squackit no longer reaches into private connection attributes; it now uses the
|
|
36
|
+
public, SemVer'd contract from fledgling 0.10 and pluckit 0.13:
|
|
37
|
+
|
|
38
|
+
- `con._con` → `con.con`; `con._tools` → `con.tools` (`server.py`, `cli.py`).
|
|
39
|
+
- The lazy FTS rebuild (which poked `con._con` + a private `_fts_built` flag)
|
|
40
|
+
now delegates to fledgling's `Connection.ensure_fts()`. squackit keeps only
|
|
41
|
+
the `_FTS_MACROS` gating (which tools need FTS).
|
|
42
|
+
- `plucker._registry.pluckins` → `plucker.pluckins`; `Chain._MUTATION_OPS`
|
|
43
|
+
→ `Chain.MUTATION_OPS` (`tools.py`).
|
|
44
|
+
|
|
45
|
+
### Dependencies — declare what we actually use, with compatible ranges
|
|
46
|
+
- `ast-pluckit>=0.13.0,<0.14` (was `>=0.9.0`) — needs the public `pluckins`
|
|
47
|
+
accessor + `pluckins.search`/`viewer`.
|
|
48
|
+
- `fledgling-mcp>=0.10.0,<0.11` — **newly declared.** squackit imports
|
|
49
|
+
`fledgling.tools.ToolInfo` and needs the `Connection.con/.tools/.ensure_fts`
|
|
50
|
+
contract; declaring it makes `Plucker.connection` always a `fledgling.Connection`
|
|
51
|
+
(ending the bare-duckdb surprise that caused the `'_con' missing` failures).
|
|
52
|
+
|
|
53
|
+
### Net
|
|
54
|
+
The brittle, undocumented, conditionally-present coupling is gone: a grep for
|
|
55
|
+
private reach-ins (`con._con`, `con._tools`, `_registry`, `_MUTATION_OPS`,
|
|
56
|
+
`_fts_built`) returns zero. The suite now composes by a versioned public API.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: squackit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
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
|
|
@@ -71,8 +71,10 @@ Tab completion is built in (`eval "$(_SQUACKIT_COMPLETE=bash_source squackit)"`)
|
|
|
71
71
|
|
|
72
72
|
## What agents get via MCP
|
|
73
73
|
|
|
74
|
-
-
|
|
75
|
-
doc navigation, diagnostics
|
|
74
|
+
- **~35 tools** — CSS-selector code queries (via pluckit), file I/O, git history,
|
|
75
|
+
doc navigation, conversation analytics, and diagnostics. The exact set is dynamic
|
|
76
|
+
(fledgling macros + pluckit tools + pluckins); a full install exposes ~40 MCP tools
|
|
77
|
+
including the workflows below.
|
|
76
78
|
- **4 compound workflows** — `explore`, `investigate`, `review`, `search`
|
|
77
79
|
- **3 prompt templates** — pre-loaded with live project data
|
|
78
80
|
- **5 resources** — project overview, docs, git status, session log, diagnostics
|
|
@@ -42,8 +42,10 @@ Tab completion is built in (`eval "$(_SQUACKIT_COMPLETE=bash_source squackit)"`)
|
|
|
42
42
|
|
|
43
43
|
## What agents get via MCP
|
|
44
44
|
|
|
45
|
-
-
|
|
46
|
-
doc navigation, diagnostics
|
|
45
|
+
- **~35 tools** — CSS-selector code queries (via pluckit), file I/O, git history,
|
|
46
|
+
doc navigation, conversation analytics, and diagnostics. The exact set is dynamic
|
|
47
|
+
(fledgling macros + pluckit tools + pluckins); a full install exposes ~40 MCP tools
|
|
48
|
+
including the workflows below.
|
|
47
49
|
- **4 compound workflows** — `explore`, `investigate`, `review`, `search`
|
|
48
50
|
- **3 prompt templates** — pre-loaded with live project data
|
|
49
51
|
- **5 resources** — project overview, docs, git status, session log, diagnostics
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Response-type improvements — surfaced from dogfooding
|
|
2
|
+
|
|
3
|
+
Empirical findings from an exploration session against `~/Projects/blq` on 2026-06-04. The skill descriptions accurately steered me to the right verbs (`explore` → `find_names` → `investigate` → `find`), but several response-shape issues forced workarounds I wouldn't have needed. Each item below is a discrete change; cherry-pick what's worth doing.
|
|
4
|
+
|
|
5
|
+
## 1. `explore` — partial failures masquerade as success
|
|
6
|
+
|
|
7
|
+
**Symptom**
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
### Key Definitions (top 20 by complexity)
|
|
11
|
+
(could not load)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The overall call returned `{"result": "..."}` — success — but the most useful section silently degraded. No reason given, no signal in the response shape that anything went wrong. A caller programming against this can't distinguish "no definitions" from "extraction failed."
|
|
15
|
+
|
|
16
|
+
**Proposal**
|
|
17
|
+
|
|
18
|
+
Either of:
|
|
19
|
+
|
|
20
|
+
- **(a) Structured result** — switch from one giant markdown string to `{languages: ..., key_definitions: {status, data, reason?}, docs: ..., recent_activity: {status, data, reason?}}`. Each section reports its own status. A failed section still has a `reason` field ("FTS index not populated for /path", "AST extraction unsupported for language X", etc.).
|
|
21
|
+
- **(b) Inline error markers** — keep markdown but replace `(could not load)` with `(error: FTS index not populated — run \`fts_stats()\` to check, then rebuild)`. Same string format, just useful enough to act on.
|
|
22
|
+
|
|
23
|
+
(a) is the bigger improvement but (b) is one-line. Maybe both: (a) for the API, (b) as a fallback for the rendered output.
|
|
24
|
+
|
|
25
|
+
## 2. `explore` — `recent_activity` ignores the `path` argument
|
|
26
|
+
|
|
27
|
+
**Symptom**
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
explore(path="/home/teague/Projects/blq")
|
|
31
|
+
# Languages, Documentation sections correctly reflect /Projects/blq
|
|
32
|
+
# Recent Activity shows commits from /home/teague/.dotfiles (session cwd)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The git-log subquery in `recent_activity` runs against cwd, not against the requested `path`. Language scan and doc scan both honor `path`; only this one section leaks.
|
|
36
|
+
|
|
37
|
+
**Proposal**
|
|
38
|
+
|
|
39
|
+
Pipe `path` through to the git-context resolver in `explore`'s `recent_activity` builder, same way it reaches `project_overview`'s language scan. If `path` isn't a git repo, return an empty `recent_activity` with `reason="not a git repository"` rather than silently falling back to cwd.
|
|
40
|
+
|
|
41
|
+
## 3. `investigate` — Source section truncates without signaling
|
|
42
|
+
|
|
43
|
+
**Symptom**
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
### Definition
|
|
47
|
+
| ... | start_line | end_line | ... |
|
|
48
|
+
| ... | 187 | 242 | ... |
|
|
49
|
+
|
|
50
|
+
### Source
|
|
51
|
+
187 def _apply_mcp_thread_limits() -> None:
|
|
52
|
+
...
|
|
53
|
+
236 ThreadPoolExecutor(max_workers=async_workers, ...),
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Definition says the function ends at line 242. Source stops at 236. No "...truncated" footer, no `max_lines` parameter in the call to bump. The caller has to notice the line-number mismatch to know there's missing source.
|
|
57
|
+
|
|
58
|
+
**Proposal**
|
|
59
|
+
|
|
60
|
+
Either:
|
|
61
|
+
|
|
62
|
+
- Always render Source through line `end_line` (the def is bounded; serving the full body is the point of `investigate`).
|
|
63
|
+
- OR add a `max_source_lines` parameter (default high enough to fit most functions) plus a footer `[truncated — N more lines; fetch with read_source(file_path, lines="237-242")]` when capped.
|
|
64
|
+
|
|
65
|
+
## 4. Selectors match call names, not qualified attribute paths — DONE (sitting_duck c458a11)
|
|
66
|
+
|
|
67
|
+
**Symptom**
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
find(source="/blq/**/*.py", selector=".call#connect")
|
|
71
|
+
# Returns 28 rows: 21 duckdb.connect(...), 4 db.connect(), 3 bare connect()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`.call#connect` matches any call whose attribute name is `connect`, regardless of receiver. To ask "every call to `duckdb.connect`", I had to inspect `peek` post-hoc.
|
|
75
|
+
|
|
76
|
+
**Implemented**
|
|
77
|
+
|
|
78
|
+
New `[receiver=X]` attribute filter on the existing `[attr op value]` selector surface. Supports `=`, `*=`, `^=`, `$=`. Examples:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
find(source="/blq/**/*.py", selector=".call#connect[receiver=duckdb]")
|
|
82
|
+
# 21 rows — only duckdb.connect(...)
|
|
83
|
+
|
|
84
|
+
find(source="/blq/**/*.py", selector=".call#connect[receiver=db]")
|
|
85
|
+
# 4 rows — only db.connect()
|
|
86
|
+
|
|
87
|
+
find(source="/blq/**/*.py", selector=".call#connect[receiver$=connect]")
|
|
88
|
+
# Calls whose receiver chain ENDS with "connect" (rare but expressive)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Chose attribute syntax over `.call#duckdb.connect` because the latter isn't valid CSS (`#` can only appear once; `.` separates classes). Receivers containing parens (`(complex).method()`) and computed receivers (`getattr(...)()`) are out of scope for the first cut.
|
|
92
|
+
|
|
93
|
+
Requires sitting_duck rebuild for the change to land in production. Tests at `sitting_duck/test/sql/ast_select_native.test` (4 cases covering positive/negative + 3 operators).
|
|
94
|
+
|
|
95
|
+
## 5. `find` returns ~15 columns; ~4 are useful
|
|
96
|
+
|
|
97
|
+
**Symptom**
|
|
98
|
+
|
|
99
|
+
A `find` row currently includes: `node_id, type, semantic_type, flags, name, qualified_name, signature_type, parameters, modifiers, annotations, file_path, language, start_line, end_line, parent_id, depth, sibling_index, children_count, descendant_count, scope, peek`.
|
|
100
|
+
|
|
101
|
+
For 99% of caller intents, only `file_path`, `start_line`, `end_line`, `name`, and `peek` matter. The rest is internal AST bookkeeping that bloats responses (and Claude's context window).
|
|
102
|
+
|
|
103
|
+
**Proposal**
|
|
104
|
+
|
|
105
|
+
Default to a compact column set — `file_path | start_line | end_line | name | peek`. Add `verbose=True` for the current full set when the caller actually needs it (e.g., debugging AST structure).
|
|
106
|
+
|
|
107
|
+
This is also the cheapest improvement on the list — table-projection at the response layer, no logic changes.
|
|
108
|
+
|
|
109
|
+
## 6. Consistent shape across verbs
|
|
110
|
+
|
|
111
|
+
`explore` returns markdown with sections. `find` returns a markdown table. `investigate` returns markdown with sub-headings and embedded tables. Each is sensible alone but reading three in one session means three different parse strategies.
|
|
112
|
+
|
|
113
|
+
**Proposal**
|
|
114
|
+
|
|
115
|
+
Either fully commit to markdown (with consistent section-header conventions across verbs) or move to JSON+embedded-markdown. The current mix forces callers (human or agent) to context-switch.
|
|
116
|
+
|
|
117
|
+
## 7. Truncation signaling
|
|
118
|
+
|
|
119
|
+
`explore`'s doc table did this correctly: `--- omitted 1283 of 1293 rows ---`. But `find` and `investigate` don't always indicate when results are capped at `max_results`/`max_lines`. A caller seeing 50 rows can't tell whether 50 is the universe or just the first page.
|
|
120
|
+
|
|
121
|
+
**Proposal**
|
|
122
|
+
|
|
123
|
+
Standardize a footer like `[showing N of M; pass max_results=K to widen]` on every verb that has a default cap.
|
|
124
|
+
|
|
125
|
+
## Priority suggestion
|
|
126
|
+
|
|
127
|
+
If you only do two of these:
|
|
128
|
+
|
|
129
|
+
1. **#5 (compact columns)** — biggest reduction in noise across every `find`/`find_names` call.
|
|
130
|
+
2. **#2 (recent_activity path leak)** — cleanest correctness bug; one-liner fix.
|
|
131
|
+
|
|
132
|
+
If three: add **#3 (investigate source truncation)** — that one bit me directly and the "Definition end_line vs Source last line" mismatch is unambiguous.
|
|
133
|
+
|
|
134
|
+
The selector-qualification work (#4) is the most expressive improvement but also the most design. Worth its own thread.
|
{squackit-0.5.0 → squackit-0.7.0}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md
RENAMED
|
@@ -64,13 +64,23 @@ Returns:
|
|
|
64
64
|
|
|
65
65
|
This one tool gives you the definition *and* its neighborhood. Usually enough to decide if this is the right function to focus on.
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
**Working on a project other than the server's own?** Two scoping rules:
|
|
68
|
+
|
|
69
|
+
- **Full-text search** (`search_code` / `search_content` / `search_docs`) defaults to the
|
|
70
|
+
server's project and ignores the structural `source` glob. To FTS a *different* repo, pass
|
|
71
|
+
**`root=<repository directory>`** (absolute path) — squackit indexes it on first use and
|
|
72
|
+
caches it. `search_code(query="...", root="/abs/repo")` works across repos.
|
|
73
|
+
- **`investigate`** is scoped to the server's project; on a different project it returns "No
|
|
74
|
+
definition found." Substitute Phase 2 with the structural tools (`find` / `view` /
|
|
75
|
+
`find_names` / `read_source`), which take an absolute `source` glob per call:
|
|
68
76
|
|
|
69
77
|
```
|
|
70
78
|
# Equivalent of investigate(name="X") for a non-rooted project:
|
|
71
79
|
find(source="/abs/path/**/*.py", selector=".fn#X") # → definition + line range
|
|
72
80
|
view(source="/abs/path/**/*.py", selector=".fn#X") # → source body
|
|
73
|
-
#
|
|
81
|
+
find(source="/abs/path/**/*.py", selector=".call#X") # → callers: each row's scope.function is the enclosing caller
|
|
82
|
+
# (a function passed as a VALUE — pool.submit(X), a decorator, a callback — is not a call
|
|
83
|
+
# site, so .call#X won't list it; search the bare name "X" to find those references.)
|
|
74
84
|
```
|
|
75
85
|
|
|
76
86
|
**When to repeat vs. pivot:**
|
|
@@ -121,14 +121,32 @@ pluck(argv="--plugin AstViewer src/auth.py find .fn#validate_token view")
|
|
|
121
121
|
|
|
122
122
|
## Selector cheat sheet
|
|
123
123
|
|
|
124
|
-
Same CSS grammar as the single-purpose tools
|
|
125
|
-
|
|
126
|
-
- `.fn` / `.class` / `.call` — by kind
|
|
127
|
-
- `#name` — by exact name
|
|
128
|
-
|
|
129
|
-
-
|
|
130
|
-
- `.fn
|
|
131
|
-
|
|
124
|
+
Same CSS grammar as the single-purpose tools. **Verified working:**
|
|
125
|
+
|
|
126
|
+
- `.fn` / `.class` / `.call` — by kind (`.function` / `.func` / `.def` all alias `.fn`)
|
|
127
|
+
- `#name` — by exact name: `.fn#main` (function def `main`), `.class#Foo`, and crucially
|
|
128
|
+
`.call#foo` (every **call site** of `foo(...)`)
|
|
129
|
+
- `[attr]` — attribute match: `.fn[name*="test"]`, `.fn[name^=test_]`, `.class[name*="Test"]`
|
|
130
|
+
- `.class > .fn` — direct children (a class's methods)
|
|
131
|
+
|
|
132
|
+
### Finding callers — `.call#NAME` or `.fn:has(.call#NAME)`
|
|
133
|
+
|
|
134
|
+
Two reliable ways to find who calls a function:
|
|
135
|
+
|
|
136
|
+
- `find ".call#get_notifications"` → every call site of `get_notifications()`. Each result
|
|
137
|
+
row's `scope.function` field is the **enclosing function** (i.e. the caller). Exact and cheap.
|
|
138
|
+
- `find ".fn:has(.call#get_notifications)"` → the caller functions directly. (This now works:
|
|
139
|
+
the `#NAME` filter inside `:has` is honored — selector matching is delegated to sitting_duck's
|
|
140
|
+
`ast_select`. Earlier pluckit versions dropped it and over-matched; that's fixed.)
|
|
141
|
+
- A function passed as a **value** (`pool.submit(fn)`, a decorator, a callback) is **not** a
|
|
142
|
+
call site, so neither form lists it. To find those references, search the bare name.
|
|
143
|
+
|
|
144
|
+
### Filtering by decorator
|
|
145
|
+
|
|
146
|
+
Use `:decorated` (functions with any decorator) or filter on the `annotations` /
|
|
147
|
+
`modifiers` column that `find` / `find_names` return (e.g. `['@mcp.tool()']`, `['@property']`)
|
|
148
|
+
when you need a specific decorator. Likewise attribute-chain calls (`os.environ.get(...)`) match
|
|
149
|
+
on the final name: try `.call#get`, but verify with a quick `find` before relying on it.
|
|
132
150
|
|
|
133
151
|
Escape whitespace in args with quotes: `containing "return None"`.
|
|
134
152
|
|
|
@@ -38,7 +38,7 @@ def setup_lackpy():
|
|
|
38
38
|
"""Initialize lackpy service with squackit tools."""
|
|
39
39
|
project_root = Path(__file__).parent.parent
|
|
40
40
|
sys.path.insert(0, str(project_root))
|
|
41
|
-
from lackpy import LackpyService
|
|
41
|
+
from lackpy.service import LackpyService
|
|
42
42
|
from squackit.lackpy_integration import register_squackit_kit
|
|
43
43
|
|
|
44
44
|
svc = LackpyService(workspace=project_root)
|
|
@@ -6,7 +6,7 @@ work to squackit.
|
|
|
6
6
|
|
|
7
7
|
Usage::
|
|
8
8
|
|
|
9
|
-
from lackpy import LackpyService
|
|
9
|
+
from lackpy.service import LackpyService
|
|
10
10
|
from squackit.lackpy_integration import register_squackit_kit
|
|
11
11
|
|
|
12
12
|
svc = LackpyService()
|
|
@@ -153,7 +153,7 @@ def _read_source_tool(file_path: str, lines: str | None = None) -> str:
|
|
|
153
153
|
|
|
154
154
|
|
|
155
155
|
def _make_tool_specs() -> list[Any]:
|
|
156
|
-
from lackpy import ArgSpec, ToolSpec # type: ignore
|
|
156
|
+
from lackpy.kit.toolbox import ArgSpec, ToolSpec # type: ignore
|
|
157
157
|
|
|
158
158
|
return [
|
|
159
159
|
ToolSpec(
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""In-memory runtime configuration for the squackit MCP server.
|
|
2
|
+
|
|
3
|
+
Distinct from ProjectDefaults (which is inferred-at-startup + file-loaded
|
|
4
|
+
overrides from .fledgling-python/config.toml). This module holds *session*
|
|
5
|
+
state that the MCP `config()` tool reads and writes — wiped on server
|
|
6
|
+
restart, no disk persistence.
|
|
7
|
+
|
|
8
|
+
Env vars at launch seed the initial values (e.g. SQUACKIT_ACTIVE_ROOT set
|
|
9
|
+
in .mcp.json env block). Resetting reverts to those env-seeded values, not
|
|
10
|
+
hardcoded defaults.
|
|
11
|
+
|
|
12
|
+
Shape mirrors jetsam.config.runtime; see retritis bench/config-endpoint-plan.md
|
|
13
|
+
for the cross-suite design.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from dataclasses import dataclass, field, fields, replace
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_ENV_PREFIX = "SQUACKIT_"
|
|
24
|
+
|
|
25
|
+
_VALID_LOG_LEVELS = {"debug", "info", "warn", "warning", "error"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class SquackitRuntimeConfig:
|
|
30
|
+
"""Session-scoped runtime knobs for the squackit MCP server.
|
|
31
|
+
|
|
32
|
+
Common keys (suite-wide convention, also on jetsam):
|
|
33
|
+
active_root: fallback when a tool's `path=`/`root=` arg is omitted.
|
|
34
|
+
None means use the server's process cwd (today's behavior).
|
|
35
|
+
log_level: debug | info | warn | error.
|
|
36
|
+
|
|
37
|
+
Squackit-specific:
|
|
38
|
+
max_results_default: default cap for token-aware result truncation
|
|
39
|
+
when tools don't get an explicit max_results.
|
|
40
|
+
complexity_max_results_default: separate cap for `complexity` tool,
|
|
41
|
+
which is usually requested in smaller batches.
|
|
42
|
+
fts_cache_size: LRU size for per-root FTS indexes. Larger = more
|
|
43
|
+
roots cached cheaply; smaller = less RAM but more rebuilds.
|
|
44
|
+
Note: takes effect on NEW roots; existing cache entries keep
|
|
45
|
+
their slot until they age out naturally.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
active_root: str | None = None
|
|
49
|
+
log_level: str = "info"
|
|
50
|
+
max_results_default: int = 50
|
|
51
|
+
complexity_max_results_default: int = 20
|
|
52
|
+
fts_cache_size: int = 8
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_env(cls, env: dict[str, str] | None = None) -> "SquackitRuntimeConfig":
|
|
56
|
+
"""Build a config seeded from environment variables.
|
|
57
|
+
|
|
58
|
+
Variables read (each optional):
|
|
59
|
+
SQUACKIT_ACTIVE_ROOT
|
|
60
|
+
SQUACKIT_LOG_LEVEL
|
|
61
|
+
SQUACKIT_MAX_RESULTS_DEFAULT
|
|
62
|
+
SQUACKIT_COMPLEXITY_MAX_RESULTS_DEFAULT
|
|
63
|
+
SQUACKIT_FTS_CACHE_SIZE
|
|
64
|
+
|
|
65
|
+
Invalid values fall back to the dataclass default.
|
|
66
|
+
"""
|
|
67
|
+
e = env if env is not None else os.environ
|
|
68
|
+
cfg = cls()
|
|
69
|
+
|
|
70
|
+
if v := e.get(f"{_ENV_PREFIX}ACTIVE_ROOT"):
|
|
71
|
+
cfg.active_root = v
|
|
72
|
+
if v := e.get(f"{_ENV_PREFIX}LOG_LEVEL"):
|
|
73
|
+
if v.lower() in _VALID_LOG_LEVELS:
|
|
74
|
+
cfg.log_level = v.lower()
|
|
75
|
+
if v := e.get(f"{_ENV_PREFIX}MAX_RESULTS_DEFAULT"):
|
|
76
|
+
try:
|
|
77
|
+
cfg.max_results_default = max(1, int(v))
|
|
78
|
+
except ValueError:
|
|
79
|
+
pass
|
|
80
|
+
if v := e.get(f"{_ENV_PREFIX}COMPLEXITY_MAX_RESULTS_DEFAULT"):
|
|
81
|
+
try:
|
|
82
|
+
cfg.complexity_max_results_default = max(1, int(v))
|
|
83
|
+
except ValueError:
|
|
84
|
+
pass
|
|
85
|
+
if v := e.get(f"{_ENV_PREFIX}FTS_CACHE_SIZE"):
|
|
86
|
+
try:
|
|
87
|
+
cfg.fts_cache_size = max(1, int(v))
|
|
88
|
+
except ValueError:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
return cfg
|
|
92
|
+
|
|
93
|
+
def to_dict(self) -> dict[str, Any]:
|
|
94
|
+
"""Flat dict for the MCP `config()` response."""
|
|
95
|
+
return {f.name: getattr(self, f.name) for f in fields(self)}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Module-level singleton, seeded from env on import.
|
|
99
|
+
_runtime: SquackitRuntimeConfig = SquackitRuntimeConfig.from_env()
|
|
100
|
+
_seed: SquackitRuntimeConfig = replace(_runtime)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_runtime() -> SquackitRuntimeConfig:
|
|
104
|
+
"""Return the current runtime config."""
|
|
105
|
+
return _runtime
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def update_runtime(values: dict[str, Any]) -> SquackitRuntimeConfig:
|
|
109
|
+
"""Merge values into the runtime config. Validates atomically.
|
|
110
|
+
|
|
111
|
+
Raises ValueError on unknown keys or invalid values, leaving the runtime
|
|
112
|
+
config unchanged.
|
|
113
|
+
"""
|
|
114
|
+
global _runtime
|
|
115
|
+
valid_keys = {f.name for f in fields(SquackitRuntimeConfig)}
|
|
116
|
+
unknown = set(values) - valid_keys
|
|
117
|
+
if unknown:
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"unknown config key(s): {sorted(unknown)}. "
|
|
120
|
+
f"Valid keys: {sorted(valid_keys)}"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
candidate = replace(_runtime)
|
|
124
|
+
for key, value in values.items():
|
|
125
|
+
_validate_one(key, value)
|
|
126
|
+
setattr(candidate, key, value)
|
|
127
|
+
|
|
128
|
+
_runtime = candidate
|
|
129
|
+
return _runtime
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def reset_runtime() -> SquackitRuntimeConfig:
|
|
133
|
+
"""Reset to the env-seeded values captured at module import time."""
|
|
134
|
+
global _runtime
|
|
135
|
+
_runtime = replace(_seed)
|
|
136
|
+
return _runtime
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def resolve_scope_path(explicit_path: str | None = None) -> str:
|
|
140
|
+
"""Resolve a scope path with the runtime config fallback chain.
|
|
141
|
+
|
|
142
|
+
Precedence: explicit_path -> runtime.active_root -> process cwd.
|
|
143
|
+
Used by tools that scope queries to a directory (investigate, etc.)
|
|
144
|
+
so a single `config(set={"active_root": X})` call configures the whole
|
|
145
|
+
session.
|
|
146
|
+
"""
|
|
147
|
+
if explicit_path is not None:
|
|
148
|
+
return explicit_path
|
|
149
|
+
runtime_root = _runtime.active_root
|
|
150
|
+
if runtime_root is not None:
|
|
151
|
+
return runtime_root
|
|
152
|
+
return os.getcwd()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _validate_one(key: str, value: Any) -> None:
|
|
156
|
+
"""Per-key value validation."""
|
|
157
|
+
if key == "log_level":
|
|
158
|
+
if not isinstance(value, str) or value.lower() not in _VALID_LOG_LEVELS:
|
|
159
|
+
raise ValueError(
|
|
160
|
+
f"log_level must be one of {sorted(_VALID_LOG_LEVELS)}, got {value!r}"
|
|
161
|
+
)
|
|
162
|
+
elif key in ("max_results_default", "complexity_max_results_default", "fts_cache_size"):
|
|
163
|
+
if not isinstance(value, int) or isinstance(value, bool) or value < 1:
|
|
164
|
+
raise ValueError(f"{key} must be a positive int, got {value!r}")
|
|
165
|
+
elif key == "active_root":
|
|
166
|
+
if value is not None and not isinstance(value, str):
|
|
167
|
+
raise ValueError(f"active_root must be str or None, got {type(value).__name__}")
|