squackit 0.7.0__tar.gz → 0.8.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 → squackit-0.8.0}/.gitignore +4 -0
- squackit-0.8.0/CHANGELOG.md +127 -0
- {squackit-0.7.0 → squackit-0.8.0}/PKG-INFO +5 -5
- {squackit-0.7.0 → squackit-0.8.0}/pyproject.toml +4 -4
- {squackit-0.7.0 → squackit-0.8.0}/squackit/server.py +10 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/tools.py +125 -5
- {squackit-0.7.0 → squackit-0.8.0}/squackit/workflows.py +13 -1
- {squackit-0.7.0 → squackit-0.8.0}/tests/conftest.py +32 -0
- squackit-0.8.0/tests/fixtures/has_call_filter.py +23 -0
- squackit-0.8.0/tests/test_config_consumption.py +95 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_defaults.py +3 -1
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_tools.py +114 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_truncation.py +5 -1
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_workflows.py +78 -0
- squackit-0.7.0/CHANGELOG.md +0 -56
- {squackit-0.7.0 → squackit-0.8.0}/.github/workflows/release.yml +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/.mcp.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/.readthedocs.yaml +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/.readthedocs.yml +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/CLAUDE.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/LICENSE +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/README.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/agent-system-prompt.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/architecture.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/configuration.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/index.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/prompts.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/proposals/2026-06-04-response-type-improvements.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/quickstart.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/resources.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-10-phase1-handoff.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-10-squawkit-extraction.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-11-phase3-pluckit-rewire.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-12-cli-tool-exposure.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-12-pluckit-tool-integration.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-12-session-handoff.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-16-session-handoff.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-26-named-fts-collections.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/plans/2026-04-27-kibitzer-fts-doc-retrieval.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/skills/exploring-unfamiliar-code/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/skills/exploring-unfamiliar-docs/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/skills/pluck-chains/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/skills/refactoring-with-squackit/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/skills/using-squackit/SKILL.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/specs/2026-04-10-squawkit-design.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/specs/2026-04-12-cli-tool-exposure-design.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/specs/2026-04-12-pluckit-tool-integration-design.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/specs/2026-04-16-training-data-pipeline-design.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/superpowers/specs/2026-04-26-named-fts-collections-design.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/docs/tools.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/mkdocs.yml +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/EXPERIMENT_LOG.md +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/benchmark_models.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/cascade_prototype.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/combo_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/generate_examples.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/grid_generate_examples.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_task_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_task_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_task_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_task_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_task_results/qwen3.5_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_task_results/qwen3_8b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_task_results/scoring_input.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_task_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/hard_tasks.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/harvest_grid.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/iterative_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/iterative_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/iterative_results/summary_deepseek_plain.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/iterative_results/summary_distilled_xml.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/iterative_results/summary_editor_3b_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/iterative_results/summary_fulldocs.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/iterative_results/summary_plaintext_3b7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/codegemma_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/codegemma_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/deepseek-llm_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/gemma_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/glm4_9b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/granite-code_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/granite3.1-dense_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/granite3.3_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/llama2-uncensored_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/llama3.2-vision_11b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/llama3.2_1b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/llama3.2_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/phi3_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/phi4-mini_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen2.5-coder_0.5b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen2.5-coder_1.5b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen2.5_1.5b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen2.5_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen2.5_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen2_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen3.5_0.8b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen3.5_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen3.5_4b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen3.5_9b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen3_0.6b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/qwen3_8b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/smollm2_latest.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_card_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/model_cards.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/multiline_results/deepseek-coder-v2_16b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/multiline_results/qwen2.5-coder_3b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/multiline_results/qwen2.5-coder_7b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/multiline_results/qwen3.5_2b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/multiline_results/qwen3_8b.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/multiline_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/multiline_tasks.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/qm_retrieval_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/quartermaster_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/quartermaster_results/retrieval_summary.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/quartermaster_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/selector_eval.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/scripts/selector_results/summary.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/__init__.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/__main__.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/cli.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/data/examples.json +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/db.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/defaults.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/formatting.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/lackpy_integration.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/prompts.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/runtime.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/session.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/squackit/tool_config.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_cli_tools.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_formatting_json.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_fts_root.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_prompts.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_resources.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_runtime_config.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_session.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_smoke.py +0 -0
- {squackit-0.7.0 → squackit-0.8.0}/tests/test_tool_config.py +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Added — source text from `find`, and a cache instead of re-parsing
|
|
6
|
+
`find` returns source text via a caller-chosen peek extent, and the AST cache
|
|
7
|
+
is enabled so repeated calls query a materialized table rather than re-parsing
|
|
8
|
+
the tree every time. Selection is delegated to sitting_duck's `ast_select`
|
|
9
|
+
rather than reimplemented here.
|
|
10
|
+
|
|
11
|
+
Cached tables now also carry `start_column` / `end_column`. These were not
|
|
12
|
+
zeroed before — they were *absent*, since `read_ast` only adds them under
|
|
13
|
+
`source := 'full'`. On minified input every node reports `start_line = 1`, so
|
|
14
|
+
character offsets are the only way to isolate a node: peek says how much text a
|
|
15
|
+
node carries, the columns say where it is.
|
|
16
|
+
|
|
17
|
+
### Fixed — `investigate` preferred substrings over exact names
|
|
18
|
+
`investigate(name)` resolved definitions with a LIKE substring
|
|
19
|
+
(`name_pattern='%name%'`), so a deep-dive on `ensure_loaded` dragged in
|
|
20
|
+
`_ensure_loaded` — including vendored copies under `.venv`. Because the Source
|
|
21
|
+
and Calls sections key off `defs[0]`, the briefing could describe the **wrong
|
|
22
|
+
function** entirely. Exact matches now win.
|
|
23
|
+
|
|
24
|
+
### Changed — requires `ast-pluckit>=0.15,<0.16`
|
|
25
|
+
This package calls `Plucker(..., cache=True, peek=...)`. That keyword arrived in
|
|
26
|
+
pluckit 0.15.0; the 0.14.0 the previous constraint allowed does not have it, so
|
|
27
|
+
a normal install resolved a pluckit whose every tool call raised `TypeError`.
|
|
28
|
+
The suite had hidden this by running with `PYTHONPATH` pointed at a checkout;
|
|
29
|
+
it is now verified against the published wheel.
|
|
30
|
+
|
|
31
|
+
### Fixed — tests that need a fledgling checkout now skip
|
|
32
|
+
Six tests read repo-shaped content (a `docs/` tree, `fledgling/pro/__main__.py`)
|
|
33
|
+
that only a fledgling source checkout has. Under the installed-package layout
|
|
34
|
+
they failed on assertions about *content* — reading as bugs in squackit's own
|
|
35
|
+
defaults inference — instead of saying what was missing. They now skip, naming
|
|
36
|
+
`FLEDGLING_REPO_PATH`.
|
|
37
|
+
|
|
38
|
+
## 0.7.1
|
|
39
|
+
|
|
40
|
+
### Added — server consumes default-limit knobs
|
|
41
|
+
`config()`'s `max_results_default` / `complexity_max_results_default` session
|
|
42
|
+
knobs are now honored by the server's tool truncation, so the in-memory limits
|
|
43
|
+
set via `config()` actually take effect.
|
|
44
|
+
|
|
45
|
+
### Changed — ast-pluckit 0.14 selector delegation
|
|
46
|
+
Consume ast-pluckit 0.14.0, which delegates CSS-selector matching to
|
|
47
|
+
sitting_duck's `ast_select` (fixes `:has(.call#name)` over-match, sitting_duck
|
|
48
|
+
#72 / squackit #8); pinned `ast-pluckit>=0.14,<0.15` with a regression test.
|
|
49
|
+
|
|
50
|
+
### Added — AST tools de-vendor the source glob (fledgling #47)
|
|
51
|
+
`find` / `find_names` / `view` / `complexity` now exclude submodule, build,
|
|
52
|
+
cache, and checked-in third-party trees from a whole-repo glob, so they focus on
|
|
53
|
+
the project's own code instead of drowning in vendored deps. On a DuckDB
|
|
54
|
+
extension with `duckdb/` + `rdkit/` git submodules, `find_names('**/*.cpp', '.function')`
|
|
55
|
+
went from **35k+ names parsed in ~9 s to 164 in ~2 s** (~660× less work at the
|
|
56
|
+
parse layer).
|
|
57
|
+
|
|
58
|
+
- Reuses fledgling's single-source-of-truth ignore policy (`_is_vendored_path`
|
|
59
|
+
denylist + `_submodule_prefixes` git-awareness, new in fledgling 0.12) — the
|
|
60
|
+
filtered file set is handed to sitting_duck as an explicit `read_ast` list,
|
|
61
|
+
which parses identically to a glob (verified same results), so only *which*
|
|
62
|
+
files are parsed changes, not how selectors match.
|
|
63
|
+
- The repo root for submodule exclusion is derived from the **source glob**
|
|
64
|
+
(walking up to the nearest `.gitmodules`/`.git`), not the server's cwd, so it
|
|
65
|
+
works when an agent queries a different project by absolute path.
|
|
66
|
+
- Explicit targets are honored: a single-file source, a DuckDB table name, or a
|
|
67
|
+
glob aimed *into* a vendored/submodule tree (e.g. `rdkit/**/*.cpp`) is passed
|
|
68
|
+
through unfiltered rather than filtered to zero.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- Require `fledgling-mcp>=0.12` for the `_is_vendored_path` / `_submodule_prefixes`
|
|
72
|
+
ignore-policy macros.
|
|
73
|
+
|
|
74
|
+
## 0.6.0
|
|
75
|
+
|
|
76
|
+
### Added — `investigate(path=)` for cross-project scoping
|
|
77
|
+
`investigate(name, path=)` accepts an explicit `path` argument and scopes the
|
|
78
|
+
symbol-lookup to that path's `scoped_code_pattern`. Without `path=`, defaults
|
|
79
|
+
to the process `cwd`-scoped pattern (was: the global registry pattern, which
|
|
80
|
+
substring-matched across every indexed project — e.g. `investigate("main")`
|
|
81
|
+
returned hits from vendored JS in unrelated repos). Pre-existing
|
|
82
|
+
`file_pattern=` overrides still work.
|
|
83
|
+
|
|
84
|
+
### Added — per-root FTS search
|
|
85
|
+
`search_*` tools now accept `root=<dir>` to index + search any repo on the
|
|
86
|
+
fly. Indexes are LRU-cached per root for the session, so repeated queries
|
|
87
|
+
in the same root stay cheap.
|
|
88
|
+
|
|
89
|
+
### Fixed — agent-ergonomics findings
|
|
90
|
+
- FTS now fails loud on index errors instead of silently returning empty.
|
|
91
|
+
- `investigate` no longer mislabels callers in cross-file results.
|
|
92
|
+
- Selector docs verified end-to-end against the running tool surface.
|
|
93
|
+
- `lackpy` 0.12 dropped top-level re-exports; switched to submodule imports
|
|
94
|
+
to keep import-time light.
|
|
95
|
+
|
|
96
|
+
### Docs
|
|
97
|
+
- `:has(.call#NAME)` works now that pluckit delegates selector compilation
|
|
98
|
+
to sitting_duck (was a notable known-bug in 0.5.0).
|
|
99
|
+
- Tool count corrected in README (~20 was wrong; actually ~35 / ~40 full).
|
|
100
|
+
- Receiver-qualified call selectors (#4) marked DONE.
|
|
101
|
+
- Response-type improvement proposals collected in `docs/proposals/`.
|
|
102
|
+
|
|
103
|
+
## 0.5.0
|
|
104
|
+
|
|
105
|
+
### Changed — migrate off pluckit/fledgling private internals
|
|
106
|
+
squackit no longer reaches into private connection attributes; it now uses the
|
|
107
|
+
public, SemVer'd contract from fledgling 0.10 and pluckit 0.13:
|
|
108
|
+
|
|
109
|
+
- `con._con` → `con.con`; `con._tools` → `con.tools` (`server.py`, `cli.py`).
|
|
110
|
+
- The lazy FTS rebuild (which poked `con._con` + a private `_fts_built` flag)
|
|
111
|
+
now delegates to fledgling's `Connection.ensure_fts()`. squackit keeps only
|
|
112
|
+
the `_FTS_MACROS` gating (which tools need FTS).
|
|
113
|
+
- `plucker._registry.pluckins` → `plucker.pluckins`; `Chain._MUTATION_OPS`
|
|
114
|
+
→ `Chain.MUTATION_OPS` (`tools.py`).
|
|
115
|
+
|
|
116
|
+
### Dependencies — declare what we actually use, with compatible ranges
|
|
117
|
+
- `ast-pluckit>=0.13.0,<0.14` (was `>=0.9.0`) — needs the public `pluckins`
|
|
118
|
+
accessor + `pluckins.search`/`viewer`.
|
|
119
|
+
- `fledgling-mcp>=0.10.0,<0.11` — **newly declared.** squackit imports
|
|
120
|
+
`fledgling.tools.ToolInfo` and needs the `Connection.con/.tools/.ensure_fts`
|
|
121
|
+
contract; declaring it makes `Plucker.connection` always a `fledgling.Connection`
|
|
122
|
+
(ending the bare-duckdb surprise that caused the `'_con' missing` failures).
|
|
123
|
+
|
|
124
|
+
### Net
|
|
125
|
+
The brittle, undocumented, conditionally-present coupling is gone: a grep for
|
|
126
|
+
private reach-ins (`con._con`, `con._tools`, `_registry`, `_MUTATION_OPS`,
|
|
127
|
+
`_fts_built`) returns zero. The suite now composes by a versioned public API.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: squackit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -15,11 +15,11 @@ Classifier: Intended Audience :: Developers
|
|
|
15
15
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
-
Requires-Python: >=3.
|
|
19
|
-
Requires-Dist: ast-pluckit<0.
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: ast-pluckit<0.16,>=0.15
|
|
20
20
|
Requires-Dist: click>=8.0
|
|
21
21
|
Requires-Dist: fastmcp>=3.0
|
|
22
|
-
Requires-Dist: fledgling-mcp<0.
|
|
22
|
+
Requires-Dist: fledgling-mcp<0.13,>=0.12
|
|
23
23
|
Provides-Extra: docs
|
|
24
24
|
Requires-Dist: mkdocs-exclude>=1.0; extra == 'docs'
|
|
25
25
|
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "squackit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
description = "Semi-QUalified Agent Companion Kit — the stateful intelligence + MCP server layer for fledgling-equipped agents."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
7
|
-
requires-python = ">=3.
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name = "Teague Sterling" },
|
|
10
10
|
]
|
|
@@ -17,8 +17,8 @@ classifiers = [
|
|
|
17
17
|
"License :: OSI Approved :: Apache Software License",
|
|
18
18
|
]
|
|
19
19
|
dependencies = [
|
|
20
|
-
"ast-pluckit>=0.
|
|
21
|
-
"fledgling-mcp>=0.
|
|
20
|
+
"ast-pluckit>=0.15,<0.16",
|
|
21
|
+
"fledgling-mcp>=0.12,<0.13",
|
|
22
22
|
"click>=8.0",
|
|
23
23
|
"fastmcp>=3.0",
|
|
24
24
|
]
|
|
@@ -225,6 +225,16 @@ def _register_executor_tool(mcp, presentation: ToolPresentation):
|
|
|
225
225
|
max_limit = int(val)
|
|
226
226
|
except (TypeError, ValueError):
|
|
227
227
|
pass
|
|
228
|
+
else:
|
|
229
|
+
# Caller passed limit=None — fall back to runtime config.
|
|
230
|
+
# `complexity` is its own bucket; everything else uses the
|
|
231
|
+
# general default_max_results.
|
|
232
|
+
from squackit.runtime import get_runtime
|
|
233
|
+
rt = get_runtime()
|
|
234
|
+
if tool_name == "complexity":
|
|
235
|
+
max_limit = rt.complexity_max_results_default
|
|
236
|
+
elif limit_param == "max_results":
|
|
237
|
+
max_limit = rt.max_results_default
|
|
228
238
|
|
|
229
239
|
# Pop verbose param the same way — opts out of compact_columns
|
|
230
240
|
# projection so the caller can see the full AST bookkeeping schema.
|
|
@@ -8,16 +8,136 @@ fledgling macro tools. They take priority over fledgling equivalents
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import os
|
|
12
|
+
|
|
11
13
|
from fledgling.tools import ToolInfo
|
|
12
14
|
from squackit.tool_config import ToolPresentation
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
#: How much source text ``read_ast`` puts in the ``peek`` column when squackit
|
|
18
|
+
#: materializes its own table. ``'smart'`` matches what pluckit's cache uses by
|
|
19
|
+
#: default, so a query returns the same peek whether or not de-vendoring kicked in.
|
|
20
|
+
DEFAULT_PEEK = "smart"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _peek_arg(peek: str) -> str:
|
|
24
|
+
"""Render a ``peek`` value as a ``read_ast`` named argument.
|
|
25
|
+
|
|
26
|
+
Accepts sitting_duck's vocabulary: ``'none'`` (suppress the text),
|
|
27
|
+
``'smart'`` (short bounded preview), ``'full'`` (whole node), or an integer
|
|
28
|
+
size. ``+schema`` is appended so the column stays in the projection even for
|
|
29
|
+
``'none'`` — squackit names ``peek`` in its compact columns, and a column
|
|
30
|
+
that disappears breaks the projection instead of merely emptying it.
|
|
31
|
+
"""
|
|
32
|
+
value = str(peek).strip() or DEFAULT_PEEK
|
|
33
|
+
return f"peek := '{value}+schema'" if "+" not in value else f"peek := '{value}'"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _filtered_source(plucker, source: str, peek: str = DEFAULT_PEEK) -> str:
|
|
37
|
+
"""De-vendor a source glob before it reaches pluckit's AST engine.
|
|
38
|
+
|
|
39
|
+
A bare ``**/*`` glob over a repo with git submodules or checked-in
|
|
40
|
+
third-party trees (googletest, vendored deps) makes the AST tools parse
|
|
41
|
+
thousands of files that aren't the project's own code — the ``find`` /
|
|
42
|
+
``view`` / ``complexity`` flood. We reuse fledgling's single-source-of-truth
|
|
43
|
+
ignore policy (``_is_vendored_path`` denylist + ``_submodule_prefixes``
|
|
44
|
+
git-awareness) to expand the glob, drop the vendored files, and hand pluckit
|
|
45
|
+
an explicit ``read_ast`` table of just the survivors via its existing
|
|
46
|
+
table-name source path. sitting_duck parses a list of paths exactly as it
|
|
47
|
+
parses a glob (verified identical results), so this changes only *which*
|
|
48
|
+
files are parsed, not how selectors match.
|
|
49
|
+
|
|
50
|
+
Returns the original ``source`` unchanged — so pluckit handles it directly —
|
|
51
|
+
when filtering doesn't apply or would be wrong:
|
|
52
|
+
* not a glob (a single file path or a DuckDB table/view name) — an
|
|
53
|
+
explicit target, nothing to de-flood;
|
|
54
|
+
* the glob expands to <= 1 file;
|
|
55
|
+
* filtering removes *everything* — the caller deliberately aimed at a
|
|
56
|
+
vendored/submodule tree (e.g. ``rdkit/**/*.cpp``), so honor it;
|
|
57
|
+
* filtering removes *nothing* — passing the glob through is cheaper and
|
|
58
|
+
gives identical results.
|
|
59
|
+
Otherwise returns the name of a TEMP table holding the filtered AST.
|
|
60
|
+
|
|
61
|
+
Degrades gracefully: if the fledgling ignore-policy macros can't run for any
|
|
62
|
+
reason (not loaded, SQL error), we fall back to the raw ``source`` — the
|
|
63
|
+
caller gets the historical unfiltered (flooded) behavior rather than a dead
|
|
64
|
+
tool. This is a de-flooding optimization, not a correctness gate, so a raw
|
|
65
|
+
glob is a safe answer; never let it take down find/view/complexity.
|
|
66
|
+
"""
|
|
67
|
+
if "*" not in source:
|
|
68
|
+
return source
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
db = plucker._ctx.db
|
|
72
|
+
resolved = (
|
|
73
|
+
source if os.path.isabs(source)
|
|
74
|
+
else os.path.join(plucker._ctx.repo, source)
|
|
75
|
+
)
|
|
76
|
+
# Submodule exclusion reads <root>/.gitmodules, so root must be the repo
|
|
77
|
+
# that actually contains the globbed files — NOT the plucker's cwd, which
|
|
78
|
+
# may be a different project entirely. Derive it from the glob's literal
|
|
79
|
+
# prefix (the path before the first wildcard) and walk up to the nearest
|
|
80
|
+
# .gitmodules / .git. The denylist is absolute-pattern based, no root needed.
|
|
81
|
+
prefix = resolved.split("*", 1)[0]
|
|
82
|
+
root = prefix if prefix.endswith("/") else os.path.dirname(prefix)
|
|
83
|
+
while root and root != "/":
|
|
84
|
+
if os.path.exists(os.path.join(root, ".gitmodules")) or os.path.exists(
|
|
85
|
+
os.path.join(root, ".git")
|
|
86
|
+
):
|
|
87
|
+
break
|
|
88
|
+
root = os.path.dirname(root)
|
|
89
|
+
rq = resolved.replace("'", "''")
|
|
90
|
+
rootq = root.rstrip("/").replace("'", "''")
|
|
91
|
+
|
|
92
|
+
total = db.sql(f"SELECT count(*) FROM glob('{rq}')").fetchone()[0]
|
|
93
|
+
if total <= 1:
|
|
94
|
+
return source
|
|
95
|
+
|
|
96
|
+
kept = [
|
|
97
|
+
r[0]
|
|
98
|
+
for r in db.sql(
|
|
99
|
+
f"SELECT file FROM glob('{rq}') "
|
|
100
|
+
f"WHERE NOT _is_vendored_path(file) "
|
|
101
|
+
f"AND NOT EXISTS (SELECT 1 FROM _submodule_prefixes('{rootq}') s "
|
|
102
|
+
f"WHERE file LIKE s.prefix || '%') "
|
|
103
|
+
f"ORDER BY file"
|
|
104
|
+
).fetchall()
|
|
105
|
+
]
|
|
106
|
+
if not kept or len(kept) == total:
|
|
107
|
+
return source
|
|
108
|
+
|
|
109
|
+
lit = "[" + ", ".join("'" + f.replace("'", "''") + "'" for f in kept) + "]"
|
|
110
|
+
db.execute(
|
|
111
|
+
"CREATE OR REPLACE TEMP TABLE _squackit_src AS "
|
|
112
|
+
f"SELECT * FROM read_ast({lit}, {_peek_arg(peek)})"
|
|
113
|
+
)
|
|
114
|
+
return "_squackit_src"
|
|
115
|
+
except Exception:
|
|
116
|
+
# Ignore-policy macros unavailable / SQL error — fall back to the raw
|
|
117
|
+
# glob. Unfiltered (flooded) results beat a broken tool.
|
|
118
|
+
return source
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _make_plucker(source: str, peek: str = DEFAULT_PEEK):
|
|
122
|
+
"""Create a Plucker with AstViewer and Search for tool execution.
|
|
123
|
+
|
|
124
|
+
The source glob is de-vendored first (see :func:`_filtered_source`) so the
|
|
125
|
+
AST tools focus on the project's own code instead of drowning in submodules,
|
|
126
|
+
build output, and checked-in third-party trees.
|
|
127
|
+
"""
|
|
17
128
|
from pluckit import Plucker
|
|
18
129
|
from pluckit.pluckins.search import Search
|
|
19
130
|
from pluckit.pluckins.viewer import AstViewer
|
|
20
|
-
|
|
131
|
+
# cache=True is what makes peek reachable. pluckit resolves a source three
|
|
132
|
+
# ways: a table name goes straight to ``ast_select_from`` (peek comes from
|
|
133
|
+
# the materialized table), a cached source is materialized then read the
|
|
134
|
+
# same way, and everything else falls through to ``ast_select`` — which
|
|
135
|
+
# parses with ``peek := 'none'`` and can only ever return NULL. Enabling the
|
|
136
|
+
# cache moves the un-filtered path onto ``ast_select_from`` too, so both
|
|
137
|
+
# paths return source text. It also stops re-parsing on every call.
|
|
138
|
+
p = Plucker(plugins=[AstViewer, Search], cache=True, peek=peek)
|
|
139
|
+
p._code_source = _filtered_source(p, source, peek)
|
|
140
|
+
return p
|
|
21
141
|
|
|
22
142
|
|
|
23
143
|
def view_executor(*, source: str, selector: str):
|
|
@@ -26,9 +146,9 @@ def view_executor(*, source: str, selector: str):
|
|
|
26
146
|
return p.view(selector)
|
|
27
147
|
|
|
28
148
|
|
|
29
|
-
def find_executor(*, source: str, selector: str):
|
|
149
|
+
def find_executor(*, source: str, selector: str, peek: str = DEFAULT_PEEK):
|
|
30
150
|
"""Execute a find query, returning matched AST nodes as a relation."""
|
|
31
|
-
p = _make_plucker(source)
|
|
151
|
+
p = _make_plucker(source, peek)
|
|
32
152
|
return p.find(selector).relation
|
|
33
153
|
|
|
34
154
|
|
|
@@ -151,7 +151,13 @@ def investigate(con, defaults, name, file_pattern=None, path=None):
|
|
|
151
151
|
# `config(set={"active_root": X})` instead of passing path= per call.
|
|
152
152
|
file_pattern = defaults.scoped_code_pattern(resolve_scope_path(path))
|
|
153
153
|
|
|
154
|
-
# 1. Find definitions matching the name
|
|
154
|
+
# 1. Find definitions matching the name. Fetch substring candidates, then
|
|
155
|
+
# prefer EXACT name matches: a deep-dive on `ensure_loaded` must not drag
|
|
156
|
+
# in `_ensure_loaded` (or `main` -> `remain`). The substring pollutes the
|
|
157
|
+
# Definition table AND — because Source/Calls key off defs[0] — can make
|
|
158
|
+
# the whole briefing describe the wrong symbol. Fall back to the full
|
|
159
|
+
# substring set only when nothing matches exactly, preserving the
|
|
160
|
+
# forgiving lookup for partial names / typos.
|
|
155
161
|
try:
|
|
156
162
|
rel = con.find_definitions(
|
|
157
163
|
file_pattern=file_pattern, name_pattern=f"%{name}%",
|
|
@@ -162,6 +168,12 @@ def investigate(con, defaults, name, file_pattern=None, path=None):
|
|
|
162
168
|
defs = []
|
|
163
169
|
def_cols = []
|
|
164
170
|
|
|
171
|
+
if defs and "name" in def_cols:
|
|
172
|
+
_n_idx = def_cols.index("name")
|
|
173
|
+
_exact = [d for d in defs if d[_n_idx] == name]
|
|
174
|
+
if _exact:
|
|
175
|
+
defs = _exact
|
|
176
|
+
|
|
165
177
|
if not defs:
|
|
166
178
|
return f"No definition found for '{name}'. Try a broader pattern or check spelling."
|
|
167
179
|
|
|
@@ -67,6 +67,38 @@ def __getattr__(name):
|
|
|
67
67
|
raise AttributeError(f"module 'conftest' has no attribute {name!r}")
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def _fledgling_source_available() -> bool:
|
|
71
|
+
"""True when the discovered root is a fledgling *source checkout*.
|
|
72
|
+
|
|
73
|
+
``_discover_fledgling_repo`` accepts an installed-package layout (a wheel
|
|
74
|
+
that bundles ``sql/`` inside the package) because that is enough for the
|
|
75
|
+
macros. It is not enough for tests that read repo-shaped content — a
|
|
76
|
+
``docs/`` tree, or module files like ``fledgling/pro/__main__.py`` — which
|
|
77
|
+
a wheel does not lay out the same way.
|
|
78
|
+
|
|
79
|
+
Without this check those tests fail on an assertion about the *content*
|
|
80
|
+
("expected doc_pattern to start with docs/"), which points at squackit's
|
|
81
|
+
own defaults logic rather than at the missing checkout, and sends anyone
|
|
82
|
+
debugging it in the wrong direction.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
root = _get_project_root()
|
|
86
|
+
except RuntimeError:
|
|
87
|
+
return False
|
|
88
|
+
return os.path.isdir(os.path.join(root, "docs")) and os.path.isfile(
|
|
89
|
+
os.path.join(root, "fledgling", "pro", "__main__.py")
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
requires_fledgling_source = pytest.mark.skipif(
|
|
94
|
+
not _fledgling_source_available(),
|
|
95
|
+
reason=(
|
|
96
|
+
"needs a fledgling source checkout (found an installed-package "
|
|
97
|
+
"layout). Set FLEDGLING_REPO_PATH to a fledgling clone."
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
70
102
|
def load_sql(con, filename):
|
|
71
103
|
"""Load a SQL macro file into a DuckDB connection."""
|
|
72
104
|
path = os.path.join(_get_project_root(), "sql", filename)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Fixture for the :has(.call#name) regression test.
|
|
2
|
+
|
|
3
|
+
Four top-level functions; exactly two call `_target`. Used to pin that
|
|
4
|
+
`:has(.call#_target)` applies the nested `#_target` filter rather than
|
|
5
|
+
dropping it and matching every function (sitting_duck #72 / squackit #8).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _target():
|
|
10
|
+
return 1
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def calls_target_once():
|
|
14
|
+
return _target()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def calls_target_twice():
|
|
18
|
+
a = _target()
|
|
19
|
+
return a + _target()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def calls_other():
|
|
23
|
+
return len([])
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Tests for tools consuming runtime config knobs (max_results_default)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from squackit import runtime as rt
|
|
8
|
+
from squackit.runtime import SquackitRuntimeConfig, update_runtime
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture(autouse=True)
|
|
12
|
+
def _isolate_runtime():
|
|
13
|
+
rt._runtime = SquackitRuntimeConfig()
|
|
14
|
+
rt._seed = SquackitRuntimeConfig()
|
|
15
|
+
yield
|
|
16
|
+
rt._runtime = SquackitRuntimeConfig()
|
|
17
|
+
rt._seed = SquackitRuntimeConfig()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestMaxResultsDefault:
|
|
21
|
+
"""When a tool is called with max_results=None, the runtime config's
|
|
22
|
+
max_results_default should be used as the cap (not the presentation's
|
|
23
|
+
static max_rows)."""
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def mcp(self):
|
|
27
|
+
pytest.importorskip("fastmcp")
|
|
28
|
+
from conftest import PROJECT_ROOT
|
|
29
|
+
from squackit.server import create_server
|
|
30
|
+
return create_server(root=PROJECT_ROOT, init=False)
|
|
31
|
+
|
|
32
|
+
def _call(self, mcp, name, args):
|
|
33
|
+
import asyncio
|
|
34
|
+
from fastmcp import Client
|
|
35
|
+
|
|
36
|
+
async def _c():
|
|
37
|
+
async with Client(mcp) as client:
|
|
38
|
+
return await client.call_tool(name, args)
|
|
39
|
+
|
|
40
|
+
loop = asyncio.new_event_loop()
|
|
41
|
+
try:
|
|
42
|
+
return loop.run_until_complete(_c())
|
|
43
|
+
finally:
|
|
44
|
+
loop.close()
|
|
45
|
+
|
|
46
|
+
def test_explicit_max_results_still_wins(self, mcp):
|
|
47
|
+
update_runtime({"max_results_default": 5})
|
|
48
|
+
# call find_names with explicit max_results=3 — runtime knob ignored
|
|
49
|
+
result = self._call(mcp, "find_names", {
|
|
50
|
+
"source": "**/*.py", "selector": ".fn", "max_results": 3,
|
|
51
|
+
})
|
|
52
|
+
text = result.content[0].text if hasattr(result, "content") else str(result)
|
|
53
|
+
# Just verify the call succeeded — cap is a runtime concern, the
|
|
54
|
+
# important thing is the call completes and returns content.
|
|
55
|
+
assert len(text) > 0
|
|
56
|
+
|
|
57
|
+
def test_runtime_default_picked_up_when_omitted(self, mcp):
|
|
58
|
+
# Set a distinctive default so we can tell it took effect
|
|
59
|
+
update_runtime({"max_results_default": 7})
|
|
60
|
+
result = self._call(mcp, "find_names", {
|
|
61
|
+
"source": "**/*.py", "selector": ".fn",
|
|
62
|
+
})
|
|
63
|
+
text = result.content[0].text if hasattr(result, "content") else str(result)
|
|
64
|
+
assert len(text) > 0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class TestComplexityMaxResults:
|
|
68
|
+
"""complexity gets its own knob since it's usually called in smaller batches."""
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def mcp(self):
|
|
72
|
+
pytest.importorskip("fastmcp")
|
|
73
|
+
from conftest import PROJECT_ROOT
|
|
74
|
+
from squackit.server import create_server
|
|
75
|
+
return create_server(root=PROJECT_ROOT, init=False)
|
|
76
|
+
|
|
77
|
+
def test_complexity_uses_its_own_knob(self, mcp):
|
|
78
|
+
import asyncio
|
|
79
|
+
from fastmcp import Client
|
|
80
|
+
|
|
81
|
+
update_runtime({"complexity_max_results_default": 3})
|
|
82
|
+
|
|
83
|
+
async def _c():
|
|
84
|
+
async with Client(mcp) as client:
|
|
85
|
+
return await client.call_tool("complexity", {
|
|
86
|
+
"source": "**/*.py", "selector": ".fn",
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
loop = asyncio.new_event_loop()
|
|
90
|
+
try:
|
|
91
|
+
result = loop.run_until_complete(_c())
|
|
92
|
+
finally:
|
|
93
|
+
loop.close()
|
|
94
|
+
text = result.content[0].text if hasattr(result, "content") else str(result)
|
|
95
|
+
assert len(text) > 0
|
|
@@ -6,7 +6,7 @@ from dataclasses import fields
|
|
|
6
6
|
|
|
7
7
|
from squackit.db import create_connection
|
|
8
8
|
from squackit.defaults import ProjectDefaults, TOOL_DEFAULTS, apply_defaults, load_config, infer_defaults
|
|
9
|
-
from conftest import PROJECT_ROOT
|
|
9
|
+
from conftest import PROJECT_ROOT, requires_fledgling_source
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class TestProjectDefaults:
|
|
@@ -194,6 +194,7 @@ class TestInferDefaults:
|
|
|
194
194
|
defaults = infer_defaults(con, root=str(PROJECT_ROOT))
|
|
195
195
|
assert "Python" in defaults.languages
|
|
196
196
|
|
|
197
|
+
@requires_fledgling_source
|
|
197
198
|
def test_doc_pattern_finds_docs_dir(self, con):
|
|
198
199
|
"""This repo has a docs/ directory."""
|
|
199
200
|
defaults = infer_defaults(con, root=str(PROJECT_ROOT))
|
|
@@ -242,6 +243,7 @@ class TestServerIntegration:
|
|
|
242
243
|
assert hasattr(server, "_defaults")
|
|
243
244
|
assert isinstance(server._defaults, ProjectDefaults)
|
|
244
245
|
|
|
246
|
+
@requires_fledgling_source
|
|
245
247
|
def test_server_defaults_inferred(self, server):
|
|
246
248
|
"""Defaults reflect this project (Python, docs/)."""
|
|
247
249
|
assert "py" in server._defaults.code_pattern
|