quickshell-mcp 2.0.0__tar.gz → 2.2.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.
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.gitignore +5 -0
- quickshell_mcp-2.2.0/.releaserc.js +61 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/AGENTS.md +65 -3
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/PKG-INFO +17 -1
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/README.md +16 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/pyproject.toml +1 -1
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/__init__.py +36 -0
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/assistant.py +28 -0
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/debugging.py +16 -0
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/generation.py +28 -0
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/knowledge.py +71 -0
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/migration.py +19 -0
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/project.py +45 -0
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/registry.py +167 -0
- quickshell_mcp-2.2.0/quickshell_mcp/capabilities/validation.py +32 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/server.py +309 -62
- quickshell_mcp-2.2.0/quickshell_mcp/sources/__init__.py +4 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/assistant.py +69 -9
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/generate.py +136 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/migrate.py +13 -4
- quickshell_mcp-2.2.0/quickshell_mcp/sources/project.py +417 -0
- quickshell_mcp-2.2.0/quickshell_mcp/sources/project_intel.py +527 -0
- quickshell_mcp-2.2.0/quickshell_mcp/sources/project_validate.py +356 -0
- quickshell_mcp-2.2.0/quickshell_mcp/sources/refactor.py +222 -0
- quickshell_mcp-2.2.0/quickshell_mcp/sources/style_match.py +108 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/validate.py +17 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/scripts/smoke_test.py +14 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/medium/components/ClockWidget.qml +6 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/medium/components/VolumeWidget.qml +6 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/medium/config.toml +1 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/medium/main.qml +14 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/medium/services/VolumeService.qml +5 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/small/config.json +3 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/small/main.qml +12 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/small/util.js +3 -0
- quickshell_mcp-2.2.0/tests/fixtures/projects/small/widgets/VolumeWidget.qml +6 -0
- quickshell_mcp-2.2.0/tests/test_capabilities.py +227 -0
- quickshell_mcp-2.2.0/tests/test_diagnostics.py +202 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_migrate.py +9 -3
- quickshell_mcp-2.2.0/tests/test_project.py +427 -0
- quickshell_mcp-2.2.0/tests/test_project_generate.py +199 -0
- quickshell_mcp-2.2.0/tests/test_project_intel.py +206 -0
- quickshell_mcp-2.2.0/tests/test_project_validate.py +161 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_validate.py +1 -0
- quickshell_mcp-2.0.0/.releaserc.json +0 -55
- quickshell_mcp-2.0.0/quickshell_mcp/sources/__init__.py +0 -1
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.coderabbit.yaml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.github/ISSUE_TEMPLATE/config_help.yml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.github/pull_request_template.md +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.github/workflows/ci.yml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/.github/workflows/semantic-release.yml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/CLAUDE.md +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/CODE_OF_CONDUCT.md +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/CONTRIBUTING.md +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/Dockerfile +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/LICENSE +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/flake.lock +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/flake.nix +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/__init__.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/__main__.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/caches.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/config.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/extraction.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/compat.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/docs.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/examples.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/explain_error.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/find_pattern.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/implementations.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/qt_docs.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/sources/search_all.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/utils.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/quickshell_mcp/versions.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/server.json +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/conftest.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/about.html +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/example_readme.md +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/examples_contents_root.json +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/examples_repo_info.json +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/guide_index.html +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/hyprland_monitor.html +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/impl_caelestia_anchoranim.qml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/impl_caelestia_repo_info.json +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/impl_caelestia_tree.json +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/impl_dots_audio.qml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/impl_dots_repo_info.json +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/impl_dots_tree.json +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/impl_noctalia_nbutton.qml +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/impl_noctalia_tree.json +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/qml_language.html +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/qt_qml_qtquick_rectangle.html +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/qt_qtquick_controls_qmlmodule.html +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/fixtures/qt_qtquick_qmlmodule.html +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_assistant.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_code_quality.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_compat.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_content_search.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_disk_cache.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_errors_retry.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_examples.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_explain_error.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_extraction.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_find_pattern.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_generate.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_implementations.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_index_search.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_live.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_qt_docs.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_search_all.py +0 -0
- {quickshell_mcp-2.0.0 → quickshell_mcp-2.2.0}/tests/test_versions.py +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const notesPattern = (sel) => new RegExp(`^[\\s|*]*(${sel}):\\s*(.*)`, "i");
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
branches: ["main"],
|
|
5
|
+
tagFormat: "v${version}",
|
|
6
|
+
plugins: [
|
|
7
|
+
[
|
|
8
|
+
"@semantic-release/commit-analyzer",
|
|
9
|
+
{
|
|
10
|
+
parserOpts: {
|
|
11
|
+
headerPattern: "^([\\w.\\-*/]+):\\s?(.*)$",
|
|
12
|
+
headerCorrespondence: ["scope", "subject"],
|
|
13
|
+
noteKeywords: ["BREAKING CHANGE", "Semver"],
|
|
14
|
+
notesPattern,
|
|
15
|
+
},
|
|
16
|
+
releaseRules: [
|
|
17
|
+
{ notes: [{ title: "Semver", text: "minor" }], release: "minor" },
|
|
18
|
+
{ notes: [{ title: "Semver", text: "major" }], release: "major" },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
"@semantic-release/release-notes-generator",
|
|
24
|
+
{
|
|
25
|
+
parserOpts: {
|
|
26
|
+
headerPattern: "^([\\w.\\-*/]+):\\s?(.*)$",
|
|
27
|
+
headerCorrespondence: ["scope", "subject"],
|
|
28
|
+
noteKeywords: null,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
"@google/semantic-release-replace-plugin",
|
|
34
|
+
{
|
|
35
|
+
replacements: [
|
|
36
|
+
{
|
|
37
|
+
files: ["pyproject.toml"],
|
|
38
|
+
from: "version = \"[0-9]+\\.[0-9]+\\.[0-9]+[^\"]*\"",
|
|
39
|
+
to: "version = \"${nextRelease.version}\"",
|
|
40
|
+
results: [{ file: "pyproject.toml", hasChanged: true }],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
"@semantic-release/git",
|
|
47
|
+
{
|
|
48
|
+
assets: ["pyproject.toml"],
|
|
49
|
+
message: "release: v${nextRelease.version} [skip ci]",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
"@semantic-release/exec",
|
|
54
|
+
{
|
|
55
|
+
prepareCmd:
|
|
56
|
+
'echo "released=true" >> "$GITHUB_OUTPUT" && echo "version=${nextRelease.version}" >> "$GITHUB_OUTPUT"',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
"@semantic-release/github",
|
|
60
|
+
],
|
|
61
|
+
};
|
|
@@ -22,8 +22,9 @@ An MCP server that serves live Quickshell documentation from quickshell.org so L
|
|
|
22
22
|
- `caches.py`: 30-minute in-memory cache per fetch, plus a disk layer that persists built indexes (guide/type/qt) across restarts (30-day default TTL); `QUICKSHELL_DOCS_MCP_DISK_CACHE` env var to disable/redirect
|
|
23
23
|
- `utils.py`: logging, HTTP client, `_fetch_raw` (retry + stats), citation prefix; domain modules call `utils._fetch_raw` by module attribute so tests monkeypatch one seam
|
|
24
24
|
- `versions.py`, `extraction.py`: runtime version discovery; HTML→Markdown with per-source strip rules
|
|
25
|
-
- `sources/`:
|
|
26
|
-
- `
|
|
25
|
+
- `sources/`: shared-services layer — data-source access, index building, parsing, and low-level analysis (`docs`, `qt_docs`, `examples`, `implementations`, `compat`, `migrate`, `validate`, `generate`, `assistant`, `explain_error`, `find_pattern`, `search_all`); no MCP decorators here and it never imports `capabilities/` (dependencies point down)
|
|
26
|
+
- `capabilities/`: capability/domain layer between the tools and the sources — one module per capability (`knowledge`, `validation`, `generation`, `migration`, `debugging`, `assistant`), each declaring `CAPABILITY_NAME`/`CAPABILITY_TOOLS`/`CAPABILITY_DEPENDS_ON` and re-exporting the source entry points; `registry.py` aggregates the tool→capability map and a cycle-checked `dependency_order()`. Planned-but-unimplemented capabilities (project, runtime, inspection, testing, performance) are registry entries only — never empty placeholder modules
|
|
27
|
+
- `server.py`: FastMCP instance, every `@mcp.tool()` wrapper + docstring (the trigger surface), `main()`, and re-exports of helper names so tests keep addressing `srv.<helper>`; tool logic is routed through `capabilities/`
|
|
27
28
|
- `tests/`: offline pytest suite driven by saved snapshots in `tests/fixtures/`. `conftest.py` provides `mock_fetch` (patches `utils._fetch_raw`), `docs_fixture_urls`, `http_404`, and an autouse cache/stats reset.
|
|
28
29
|
- `scripts/smoke_test.py`: end-to-end stdio client; drives the server like a real MCP session against the live sites.
|
|
29
30
|
- `flake.nix` / `Dockerfile` / `.github/workflows/ci.yml`: packaging + CI. `CLAUDE.md` is a symlink to this file.
|
|
@@ -118,6 +119,65 @@ Run checks when warranted, not reflexively. If none apply, say so instead of run
|
|
|
118
119
|
5. **CI pipeline** (`.github/workflows/ci.yml`): flake check → nix build + dist/twine → lint → offline pytest with coverage → Dockerfile validation; live-smoke runs only on manual dispatch. If you add a check locally, wire the equivalent into CI.
|
|
119
120
|
6. **Implementation references are not documentation.** Caelestia/Noctalia/dots-hyprland results carry `kind: real-world implementation` and every tool docstring says the docs win on API disputes. Noctalia's ref is pinned to its `legacy-v4` branch deliberately (its `main` moved to a C++ compositor); a GitHub `truncated: true` tree response is refused rather than silently under-indexed. dots-hyprland's QML root is nested under `dots/.config/quickshell/ii/` and paths are reported relative to it.
|
|
120
121
|
|
|
122
|
+
## ProjectContext (`quickshell_mcp/sources/project.py`)
|
|
123
|
+
|
|
124
|
+
A reusable internal abstraction for representing a Quickshell project on disk.
|
|
125
|
+
It is **not** a capability or an MCP tool — it is shared infrastructure in the
|
|
126
|
+
`sources/` layer that capabilities consume.
|
|
127
|
+
|
|
128
|
+
### Consumer pattern
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from ..sources.project import _build_project_context
|
|
132
|
+
|
|
133
|
+
ctx = _build_project_context("/path/to/shell")
|
|
134
|
+
info = ctx.discover({"qml_files", "imports", "compositor"})
|
|
135
|
+
# info = {"qml_files": [...], "imports": [...], "compositor": ["Hyprland"], ...}
|
|
136
|
+
# ctx.detection_status("compositor") -> "inferred"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Available fields (all lazy)
|
|
140
|
+
|
|
141
|
+
| Field | Status | Description |
|
|
142
|
+
|-------|--------|-------------|
|
|
143
|
+
| `qml_files` | detected | Every `*.qml` under the project root |
|
|
144
|
+
| `js_files` | detected | Every `*.js` under the project root |
|
|
145
|
+
| `entrypoints` | detected | QML files whose root object is a window type |
|
|
146
|
+
| `imports` | detected | All QML import statements across every file |
|
|
147
|
+
| `quickshell_modules` | detected / unknown | Imports starting with `Quickshell` |
|
|
148
|
+
| `quickshell_version` | inferred / unknown | Version string from `Quickshell` import versions |
|
|
149
|
+
| `qt_version` | inferred / unknown | Version string from `Qt*` module import versions |
|
|
150
|
+
| `compositor` | inferred / unknown | Quickshell integration namespaces (e.g. `Hyprland`) |
|
|
151
|
+
| `config_paths` | detected | Config files at the project root |
|
|
152
|
+
| `dependencies` | detected | All distinct import modules |
|
|
153
|
+
| `conventions` | inferred | Naming, layout, structure patterns |
|
|
154
|
+
| `components` | detected / unknown | QML files under `components/` or `widgets/` dirs |
|
|
155
|
+
| `services` | detected / inferred / unknown | `Quickshell.Services.*` imports + `*Service`-named objects |
|
|
156
|
+
| `runtime_dependencies` | detected / inferred / unknown | Runtime-hook QML types (Process, IpcHandler, ...) + config keyword hints |
|
|
157
|
+
| `environment` | inferred / unknown | `process.env.*` variables referenced in QML |
|
|
158
|
+
|
|
159
|
+
### Status semantics
|
|
160
|
+
|
|
161
|
+
- **detected** — directly observed in the filesystem (file lists, import statements)
|
|
162
|
+
- **inferred** — derived from detected data (version strings, compositor, conventions)
|
|
163
|
+
- **unknown** — no data could be found (no QML files, no version-bearing imports)
|
|
164
|
+
|
|
165
|
+
### Design rules
|
|
166
|
+
|
|
167
|
+
- **Never fabricates.** A field without data is `None` / `[]` with status `"unknown"`.
|
|
168
|
+
- **Lazy by default.** `discover({needs})` computes only the requested fields.
|
|
169
|
+
- **Instance-cached.** Repeated `discover()` calls on the same `_ProjectContext` reuse precomputed values.
|
|
170
|
+
- **Shared-cached.** The expensive file-tree scan is cached in the 30-minute in-memory cache keyed by the resolved root path, so a second `_build_project_context` for the same directory skips re-scanning.
|
|
171
|
+
- **No HTTP.** ProjectContext is purely local-filesystem: it walks the tree and parses QML text. Any version resolution against the live site is the caller's responsibility.
|
|
172
|
+
- **Compositor-independent.** The compositor field reports Quickshell integration namespaces (e.g. `Hyprland`) from `Quickshell.<Name>` imports after excluding core namespaces. No compositor-name list is hardcoded.
|
|
173
|
+
|
|
174
|
+
### Adding a new field
|
|
175
|
+
|
|
176
|
+
1. Add the field name to `_ALL_FIELDS` in `project.py`.
|
|
177
|
+
2. Add a `_discover_<name>` method that calls `_set(...)` or `_set_inferred(...)`.
|
|
178
|
+
3. Call `_scan()` for the raw file/import data; derive your field from it.
|
|
179
|
+
4. Add a test in `tests/test_project.py`.
|
|
180
|
+
|
|
121
181
|
## MCP client configuration
|
|
122
182
|
|
|
123
183
|
```json
|
|
@@ -150,7 +210,9 @@ Scoped Commits (`<scope>: <description>`). Small diffs, one logical change per c
|
|
|
150
210
|
> ⚠️ A semantic-release CI runs on every merge to `main`. Every commit defaults to a **patch** release. A new feature/tool must signal a minor bump explicitly — there is no type-prefix heuristic anymore. Forgetting `Semver: minor` on a new tool means no minor release.
|
|
151
211
|
|
|
152
212
|
- `Semver: minor` — new feature, tool, or user-facing addition
|
|
153
|
-
- `
|
|
213
|
+
- `Semver: major` — breaking API/config change (preferred over `BREAKING CHANGE:`)
|
|
214
|
+
|
|
215
|
+
Both are matched as trailing lines in the commit body, e.g. a final `Semver: minor` line. Matching requires a literal colon after `Semver:`/`BREAKING CHANGE:`, so prose that merely *mentions* "BREAKING CHANGE" or "Semver" never triggers a bump.
|
|
154
216
|
|
|
155
217
|
Example:
|
|
156
218
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickshell-mcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: MCP server that fetches live Quickshell docs from quickshell.org so the model reads real, current docs instead of hallucinating.
|
|
5
5
|
Project-URL: Homepage, https://github.com/franklinnolasco7/quickshell-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/franklinnolasco7/quickshell-mcp
|
|
@@ -417,6 +417,22 @@ Use `refresh=True` to bypass the short-lived cache where supported. The cache lo
|
|
|
417
417
|
|
|
418
418
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, workflow, and how to submit changes.
|
|
419
419
|
|
|
420
|
+
### Internal architecture
|
|
421
|
+
|
|
422
|
+
The server is structured into four layers:
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
MCP tool (server.py, tool registration + docstrings)
|
|
426
|
+
↓
|
|
427
|
+
capabilities/ (domain layer: knowledge, validation, generation, migration, debugging, assistant)
|
|
428
|
+
↓
|
|
429
|
+
sources/ (shared services: data-source access, index building, parsing, compatibility)
|
|
430
|
+
↓
|
|
431
|
+
config, caches, utils, versions, extraction (infrastructure)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Each capability module declares its tools and its explicit dependencies on other capabilities. The dependency graph is acyclic and verified by `tests/test_capabilities.py`. Planned capabilities (project, runtime, inspection, testing, performance) are documented in the registry; no empty modules are created until features land.
|
|
435
|
+
|
|
420
436
|
## Limitations
|
|
421
437
|
|
|
422
438
|
- Validation is static and heuristic; it complements `qmlls`.
|
|
@@ -380,6 +380,22 @@ Use `refresh=True` to bypass the short-lived cache where supported. The cache lo
|
|
|
380
380
|
|
|
381
381
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, workflow, and how to submit changes.
|
|
382
382
|
|
|
383
|
+
### Internal architecture
|
|
384
|
+
|
|
385
|
+
The server is structured into four layers:
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
MCP tool (server.py, tool registration + docstrings)
|
|
389
|
+
↓
|
|
390
|
+
capabilities/ (domain layer: knowledge, validation, generation, migration, debugging, assistant)
|
|
391
|
+
↓
|
|
392
|
+
sources/ (shared services: data-source access, index building, parsing, compatibility)
|
|
393
|
+
↓
|
|
394
|
+
config, caches, utils, versions, extraction (infrastructure)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Each capability module declares its tools and its explicit dependencies on other capabilities. The dependency graph is acyclic and verified by `tests/test_capabilities.py`. Planned capabilities (project, runtime, inspection, testing, performance) are documented in the registry; no empty modules are created until features land.
|
|
398
|
+
|
|
383
399
|
## Limitations
|
|
384
400
|
|
|
385
401
|
- Validation is static and heuristic; it complements `qmlls`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quickshell-mcp"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.2.0"
|
|
4
4
|
description = "MCP server that fetches live Quickshell docs from quickshell.org so the model reads real, current docs instead of hallucinating."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Capability / domain layer.
|
|
2
|
+
|
|
3
|
+
Each capability module sits between the MCP tool wrappers in ``server.py``
|
|
4
|
+
and the shared services in ``sources/``. Capabilities own the tool-to-domain
|
|
5
|
+
mapping, declare explicit inter-capability dependencies, and re-export the
|
|
6
|
+
entry-point helpers that the tool wrappers call.
|
|
7
|
+
|
|
8
|
+
The dependency graph among implemented capabilities is acyclic:
|
|
9
|
+
|
|
10
|
+
knowledge (root)
|
|
11
|
+
├── validation
|
|
12
|
+
├── debugging
|
|
13
|
+
validation ──┬── generation
|
|
14
|
+
└── migration
|
|
15
|
+
knowledge, validation, debugging, generation, migration ── assistant
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from . import (
|
|
19
|
+
assistant,
|
|
20
|
+
debugging,
|
|
21
|
+
generation,
|
|
22
|
+
knowledge,
|
|
23
|
+
migration,
|
|
24
|
+
registry,
|
|
25
|
+
validation,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"assistant",
|
|
30
|
+
"debugging",
|
|
31
|
+
"generation",
|
|
32
|
+
"knowledge",
|
|
33
|
+
"migration",
|
|
34
|
+
"registry",
|
|
35
|
+
"validation",
|
|
36
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""The assistant capability: orchestrate a plain-language request across every
|
|
2
|
+
other capability.
|
|
3
|
+
|
|
4
|
+
Depends on: knowledge, validation, generation, migration, debugging.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ..sources.assistant import ( # noqa: F401
|
|
10
|
+
_classify_intent,
|
|
11
|
+
_coding_assistant,
|
|
12
|
+
_detect_compositor,
|
|
13
|
+
_project_info,
|
|
14
|
+
_resolve_version_hint,
|
|
15
|
+
_safe_step,
|
|
16
|
+
)
|
|
17
|
+
from ..sources.project import _build_project_context # noqa: F401
|
|
18
|
+
|
|
19
|
+
CAPABILITY_NAME = "assistant"
|
|
20
|
+
CAPABILITY_TOOLS = ("quickshell_coding_assistant",)
|
|
21
|
+
CAPABILITY_DEPENDS_ON = (
|
|
22
|
+
"knowledge",
|
|
23
|
+
"validation",
|
|
24
|
+
"generation",
|
|
25
|
+
"migration",
|
|
26
|
+
"debugging",
|
|
27
|
+
"project",
|
|
28
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""The debugging capability: explain QML errors and suggest fixes.
|
|
2
|
+
|
|
3
|
+
Depends on: knowledge (debugging uses the docs and Qt indexes).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from ..sources.explain_error import ( # noqa: F401
|
|
9
|
+
_categorize_error,
|
|
10
|
+
_explain_error,
|
|
11
|
+
_extract_type_from_code,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
CAPABILITY_NAME = "debugging"
|
|
15
|
+
CAPABILITY_TOOLS = ("quickshell_explain_error",)
|
|
16
|
+
CAPABILITY_DEPENDS_ON = ("knowledge",)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""The generation capability: produce grounded QML component code.
|
|
2
|
+
|
|
3
|
+
Depends on: knowledge, validation (generation uses compat, find_pattern,
|
|
4
|
+
search_all, and validate).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ..sources.generate import ( # noqa: F401
|
|
10
|
+
_build_section,
|
|
11
|
+
_generate_component,
|
|
12
|
+
_generate_panel,
|
|
13
|
+
_generate_service,
|
|
14
|
+
_interpret_component_query,
|
|
15
|
+
)
|
|
16
|
+
from ..sources.refactor import _apply_patch, _refactor # noqa: F401
|
|
17
|
+
from ..sources.style_match import _style_match # noqa: F401
|
|
18
|
+
|
|
19
|
+
CAPABILITY_NAME = "generation"
|
|
20
|
+
CAPABILITY_TOOLS = (
|
|
21
|
+
"quickshell_generate_component",
|
|
22
|
+
"quickshell_generate_service",
|
|
23
|
+
"quickshell_generate_panel",
|
|
24
|
+
"quickshell_refactor",
|
|
25
|
+
"quickshell_apply_patch",
|
|
26
|
+
"quickshell_style_match",
|
|
27
|
+
)
|
|
28
|
+
CAPABILITY_DEPENDS_ON = ("knowledge", "validation")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""The knowledge capability: read and search Quickshell docs, Qt types,
|
|
2
|
+
official examples, and real-world implementations.
|
|
3
|
+
|
|
4
|
+
Depends on: (none in the capability layer — pulls directly from shared services).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ..sources.docs import ( # noqa: F401
|
|
10
|
+
GUIDE_LINK_RE,
|
|
11
|
+
TYPE_LINK_RE,
|
|
12
|
+
_build_index,
|
|
13
|
+
_guide_content_index,
|
|
14
|
+
_guide_page,
|
|
15
|
+
_resolve_version,
|
|
16
|
+
_search_guide_content,
|
|
17
|
+
_search_type_content,
|
|
18
|
+
_type_page,
|
|
19
|
+
)
|
|
20
|
+
from ..sources.examples import ( # noqa: F401
|
|
21
|
+
_example_file,
|
|
22
|
+
_examples_branch,
|
|
23
|
+
_examples_known_paths,
|
|
24
|
+
_examples_listing,
|
|
25
|
+
)
|
|
26
|
+
from ..sources.find_pattern import _find_pattern, _interpret_query # noqa: F401
|
|
27
|
+
from ..sources.implementations import ( # noqa: F401
|
|
28
|
+
_GITHUB_API,
|
|
29
|
+
_IMPL_QUERY_STOPWORDS,
|
|
30
|
+
_IMPL_TOPICS,
|
|
31
|
+
_impl_branch,
|
|
32
|
+
_impl_component,
|
|
33
|
+
_impl_entry_meta,
|
|
34
|
+
_impl_file,
|
|
35
|
+
_impl_repo_config,
|
|
36
|
+
_impl_topics_for_query,
|
|
37
|
+
_norm_source,
|
|
38
|
+
_search_implementations,
|
|
39
|
+
)
|
|
40
|
+
from ..sources.qt_docs import ( # noqa: F401
|
|
41
|
+
_QT_ANCHOR_RE,
|
|
42
|
+
_QT_MODULE_LINK_RE,
|
|
43
|
+
_QT_TYPE_LINK_RE,
|
|
44
|
+
_VALUE_TYPES_BUCKET,
|
|
45
|
+
_build_qt_index,
|
|
46
|
+
_normalize_qt_module,
|
|
47
|
+
_qt_type_page,
|
|
48
|
+
_resolve_qt_slug,
|
|
49
|
+
)
|
|
50
|
+
from ..sources.search_all import _search_everything # noqa: F401
|
|
51
|
+
|
|
52
|
+
CAPABILITY_NAME = "knowledge"
|
|
53
|
+
CAPABILITY_TOOLS = (
|
|
54
|
+
"quickshell_list_versions",
|
|
55
|
+
"quickshell_about",
|
|
56
|
+
"quickshell_changelog",
|
|
57
|
+
"quickshell_list_guide_pages",
|
|
58
|
+
"quickshell_get_guide_page",
|
|
59
|
+
"quickshell_list_types",
|
|
60
|
+
"quickshell_get_type",
|
|
61
|
+
"quickshell_search",
|
|
62
|
+
"quickshell_search_all",
|
|
63
|
+
"quickshell_find_pattern",
|
|
64
|
+
"quickshell_list_qt_types",
|
|
65
|
+
"quickshell_get_qt_type",
|
|
66
|
+
"quickshell_list_examples",
|
|
67
|
+
"quickshell_get_example",
|
|
68
|
+
"quickshell_search_implementations",
|
|
69
|
+
"quickshell_get_implementation",
|
|
70
|
+
)
|
|
71
|
+
CAPABILITY_DEPENDS_ON: tuple[str, ...] = ()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""The migration capability: analyse what code must change between versions.
|
|
2
|
+
|
|
3
|
+
Depends on: knowledge, validation (migration uses compat, docs, and validate).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from ..sources.migrate import ( # noqa: F401
|
|
9
|
+
_behavioral_scan,
|
|
10
|
+
_collect_api_refs,
|
|
11
|
+
_import_diff,
|
|
12
|
+
_migrate,
|
|
13
|
+
_migration_plan,
|
|
14
|
+
_symbol_issue,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
CAPABILITY_NAME = "migration"
|
|
18
|
+
CAPABILITY_TOOLS = ("quickshell_migrate",)
|
|
19
|
+
CAPABILITY_DEPENDS_ON = ("knowledge", "validation")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""The project capability: analyze a Quickshell project on disk.
|
|
2
|
+
|
|
3
|
+
Provides structured project intelligence built on the shared
|
|
4
|
+
``ProjectContext``: an overview, a static relationship map, project-scoped
|
|
5
|
+
search, dependency classification, and config-convention detection. All
|
|
6
|
+
tools are read-only and never modify the project.
|
|
7
|
+
|
|
8
|
+
Depends on: knowledge (project search reuses the shared search/index
|
|
9
|
+
infrastructure).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from ..sources.project import ( # noqa: F401
|
|
15
|
+
_ALL_FIELDS,
|
|
16
|
+
_build_project_context,
|
|
17
|
+
_ProjectContext,
|
|
18
|
+
)
|
|
19
|
+
from ..sources.project_intel import ( # noqa: F401
|
|
20
|
+
_analyze_project,
|
|
21
|
+
_config_conventions,
|
|
22
|
+
_map_project,
|
|
23
|
+
_project_dependencies,
|
|
24
|
+
_search_project,
|
|
25
|
+
)
|
|
26
|
+
from ..sources.project_validate import ( # noqa: F401
|
|
27
|
+
_lint_project,
|
|
28
|
+
_migrate_project,
|
|
29
|
+
_project_compatibility,
|
|
30
|
+
_validate_project,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
CAPABILITY_NAME = "project"
|
|
34
|
+
CAPABILITY_TOOLS = (
|
|
35
|
+
"quickshell_project_analyze",
|
|
36
|
+
"quickshell_project_map",
|
|
37
|
+
"quickshell_project_find",
|
|
38
|
+
"quickshell_project_dependencies",
|
|
39
|
+
"quickshell_project_config",
|
|
40
|
+
"quickshell_project_validate",
|
|
41
|
+
"quickshell_project_lint",
|
|
42
|
+
"quickshell_project_compatibility",
|
|
43
|
+
"quickshell_project_migrate",
|
|
44
|
+
)
|
|
45
|
+
CAPABILITY_DEPENDS_ON = ("knowledge", "validation")
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Capability registry: the boundary map between MCP tools and the domain layer.
|
|
2
|
+
|
|
3
|
+
Each capability module (``knowledge``, ``validation``, ``generation``,
|
|
4
|
+
``migration``, ``debugging``, ``assistant``) declares which tools it owns and
|
|
5
|
+
which other capabilities it depends on. This module aggregates those
|
|
6
|
+
declarations into a single map plus a cycle-checked topological ordering, so
|
|
7
|
+
server.py and tests can answer "which capability owns this tool?" and prove
|
|
8
|
+
the capability graph is acyclic.
|
|
9
|
+
|
|
10
|
+
Capabilities that are planned but not implemented yet (runtime, inspection,
|
|
11
|
+
testing, performance) are registered with ``status="planned"`` and no tools.
|
|
12
|
+
No modules are created for them until the features land.
|
|
13
|
+
|
|
14
|
+
Every capability carries a ``safety_level``:
|
|
15
|
+
|
|
16
|
+
* **read-only** — analysis only; no state changes, no file writes, no process
|
|
17
|
+
execution (default for all capabilities today).
|
|
18
|
+
* **mutating** — alters live state (runtime sessions, explicit file writes via
|
|
19
|
+
``apply_patch``). Requires explicit invocation.
|
|
20
|
+
* **high-risk** — arbitrary evaluation or destructive filesystem operations.
|
|
21
|
+
Reserved for future eval/patch capabilities; not yet assigned.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
|
|
28
|
+
from . import assistant, debugging, generation, knowledge, migration, project, validation
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"ALL_CAPABILITIES",
|
|
32
|
+
"CAPABILITIES",
|
|
33
|
+
"Capability",
|
|
34
|
+
"PLANNED_CAPABILITIES",
|
|
35
|
+
"SAFETY_LEVELS",
|
|
36
|
+
"capability_for_tool",
|
|
37
|
+
"classify_capability",
|
|
38
|
+
"dependency_order",
|
|
39
|
+
"safety_level_for_tool",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class Capability:
|
|
45
|
+
name: str
|
|
46
|
+
tools: tuple[str, ...]
|
|
47
|
+
depends_on: tuple[str, ...]
|
|
48
|
+
status: str = "implemented"
|
|
49
|
+
safety_level: str = "read-only"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Mutating capabilities alter live state rather than only reading project/docs.
|
|
53
|
+
# high-risk is not yet assigned to any capability; it is reserved for future
|
|
54
|
+
# evaluation/patch capabilities.
|
|
55
|
+
_MUTATING_CAPABILITIES = ("runtime", "testing")
|
|
56
|
+
|
|
57
|
+
# Per-tool safety overrides. A tool listed here overrides its owning
|
|
58
|
+
# capability's default safety level.
|
|
59
|
+
_TOOL_SAFETY_LEVELS: dict[str, str] = {
|
|
60
|
+
"quickshell_apply_patch": "mutating",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _safety_level(name: str) -> str:
|
|
65
|
+
return "mutating" if name in _MUTATING_CAPABILITIES else "read-only"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
_CAPABILITY_MODULES = (
|
|
69
|
+
knowledge,
|
|
70
|
+
validation,
|
|
71
|
+
generation,
|
|
72
|
+
migration,
|
|
73
|
+
debugging,
|
|
74
|
+
project,
|
|
75
|
+
assistant,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
CAPABILITIES: dict[str, Capability] = {
|
|
79
|
+
mod.CAPABILITY_NAME: Capability(
|
|
80
|
+
name=mod.CAPABILITY_NAME,
|
|
81
|
+
tools=mod.CAPABILITY_TOOLS,
|
|
82
|
+
depends_on=mod.CAPABILITY_DEPENDS_ON,
|
|
83
|
+
safety_level=_safety_level(mod.CAPABILITY_NAME),
|
|
84
|
+
)
|
|
85
|
+
for mod in _CAPABILITY_MODULES
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
PLANNED_CAPABILITIES: dict[str, Capability] = {
|
|
89
|
+
name: Capability(
|
|
90
|
+
name=name,
|
|
91
|
+
tools=(),
|
|
92
|
+
depends_on=(),
|
|
93
|
+
status="planned",
|
|
94
|
+
safety_level=_safety_level(name),
|
|
95
|
+
)
|
|
96
|
+
for name in ("runtime", "inspection", "testing", "performance")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ALL_CAPABILITIES: dict[str, Capability] = {**CAPABILITIES, **PLANNED_CAPABILITIES}
|
|
100
|
+
|
|
101
|
+
# Convenience dict derived from the authoritative dataclass fields.
|
|
102
|
+
SAFETY_LEVELS: dict[str, str] = {name: cap.safety_level for name, cap in ALL_CAPABILITIES.items()}
|
|
103
|
+
|
|
104
|
+
# `quickshell_stats` reports session telemetry and lives entirely in server.py;
|
|
105
|
+
# it is not owned by any domain capability.
|
|
106
|
+
_SYSTEM_TOOLS = ("quickshell_stats",)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def capability_for_tool(tool: str) -> str | None:
|
|
110
|
+
"""Return the capability that owns *tool*, or None for system tools."""
|
|
111
|
+
for cap in CAPABILITIES.values():
|
|
112
|
+
if tool in cap.tools:
|
|
113
|
+
return cap.name
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def classify_capability(name: str) -> str:
|
|
118
|
+
"""Return the safety level of a registered capability.
|
|
119
|
+
|
|
120
|
+
Levels: ``read-only`` (analysis), ``mutating`` (changes live state),
|
|
121
|
+
``high-risk`` (arbitrary evaluation or file mutation; not yet assigned).
|
|
122
|
+
"""
|
|
123
|
+
cap = ALL_CAPABILITIES.get(name)
|
|
124
|
+
if cap is None:
|
|
125
|
+
raise ValueError(f"Unknown capability '{name}'")
|
|
126
|
+
return cap.safety_level
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def safety_level_for_tool(tool: str) -> str:
|
|
130
|
+
"""Return the safety level of the capability owning *tool*.
|
|
131
|
+
|
|
132
|
+
A per-tool override (``_TOOL_SAFETY_LEVELS``) wins; otherwise the level
|
|
133
|
+
is inherited from the owning capability. System tools (e.g.
|
|
134
|
+
``quickshell_stats``) are telemetry and are always classified read-only.
|
|
135
|
+
"""
|
|
136
|
+
if tool in _TOOL_SAFETY_LEVELS:
|
|
137
|
+
return _TOOL_SAFETY_LEVELS[tool]
|
|
138
|
+
cap = capability_for_tool(tool)
|
|
139
|
+
if cap is not None:
|
|
140
|
+
return classify_capability(cap)
|
|
141
|
+
if tool in _SYSTEM_TOOLS:
|
|
142
|
+
return "read-only"
|
|
143
|
+
raise ValueError(f"Unknown tool '{tool}'")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def dependency_order() -> list[str]:
|
|
147
|
+
"""Topologically order the implemented capabilities (raises on a cycle)."""
|
|
148
|
+
graph = {name: set(cap.depends_on) for name, cap in CAPABILITIES.items()}
|
|
149
|
+
for name, deps in graph.items():
|
|
150
|
+
missing = deps - set(CAPABILITIES)
|
|
151
|
+
if missing:
|
|
152
|
+
raise ValueError(f"capability {name!r} depends on unregistered {sorted(missing)!r}")
|
|
153
|
+
order: list[str] = []
|
|
154
|
+
resolved: set[str] = set()
|
|
155
|
+
while len(order) < len(graph):
|
|
156
|
+
progress = False
|
|
157
|
+
for name, deps in graph.items():
|
|
158
|
+
if name in resolved:
|
|
159
|
+
continue
|
|
160
|
+
if deps <= resolved:
|
|
161
|
+
order.append(name)
|
|
162
|
+
resolved.add(name)
|
|
163
|
+
progress = True
|
|
164
|
+
if not progress:
|
|
165
|
+
cycle = sorted(set(graph) - resolved)
|
|
166
|
+
raise ValueError(f"circular capability dependency: {cycle}")
|
|
167
|
+
return order
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""The validation capability: statically validate QML against the docs and
|
|
2
|
+
check version compatibility.
|
|
3
|
+
|
|
4
|
+
Depends on: knowledge (validation builds on the type/guide indexes).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ..sources.compat import ( # noqa: F401
|
|
10
|
+
_api_in_version,
|
|
11
|
+
_changelog_hits,
|
|
12
|
+
_changelog_sections,
|
|
13
|
+
_check_compatibility,
|
|
14
|
+
_compat_at,
|
|
15
|
+
_compat_from_code,
|
|
16
|
+
_incorporate_range,
|
|
17
|
+
_parse_api_ref,
|
|
18
|
+
_scan_versions,
|
|
19
|
+
)
|
|
20
|
+
from ..sources.validate import ( # noqa: F401
|
|
21
|
+
_parse_members,
|
|
22
|
+
_parse_structure,
|
|
23
|
+
_tokenize,
|
|
24
|
+
_validate,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
CAPABILITY_NAME = "validation"
|
|
28
|
+
CAPABILITY_TOOLS = (
|
|
29
|
+
"quickshell_validate_qml",
|
|
30
|
+
"quickshell_check_compatibility",
|
|
31
|
+
)
|
|
32
|
+
CAPABILITY_DEPENDS_ON = ("knowledge",)
|