tinta-mcp 0.2.2__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.
Files changed (56) hide show
  1. tinta_mcp-0.2.2/.agents/AGENTS.md +60 -0
  2. tinta_mcp-0.2.2/.agents/rules/architecture.md +62 -0
  3. tinta_mcp-0.2.2/.agents/skills/mcp-tool-builder/SKILL.md +113 -0
  4. tinta_mcp-0.2.2/.agents/skills/tinta-designer/SKILL.md +56 -0
  5. tinta_mcp-0.2.2/.agents/skills/tinta-dev-workflow/SKILL.md +94 -0
  6. tinta_mcp-0.2.2/.editorconfig +18 -0
  7. tinta_mcp-0.2.2/.gitattributes +5 -0
  8. tinta_mcp-0.2.2/.github/actionlint.yaml +4 -0
  9. tinta_mcp-0.2.2/.github/workflows/ci.yml +194 -0
  10. tinta_mcp-0.2.2/.github/workflows/release.yml +319 -0
  11. tinta_mcp-0.2.2/.gitignore +34 -0
  12. tinta_mcp-0.2.2/.pre-commit-config.yaml +26 -0
  13. tinta_mcp-0.2.2/.python-version +1 -0
  14. tinta_mcp-0.2.2/LICENSE +661 -0
  15. tinta_mcp-0.2.2/PKG-INFO +167 -0
  16. tinta_mcp-0.2.2/README.md +158 -0
  17. tinta_mcp-0.2.2/assets/.gitkeep +1 -0
  18. tinta_mcp-0.2.2/assets/tinta-mcp-banner.png +0 -0
  19. tinta_mcp-0.2.2/assets/tinta-mcp-banner.svg +368 -0
  20. tinta_mcp-0.2.2/assets/tinta-mcp-logo.png +0 -0
  21. tinta_mcp-0.2.2/assets/tinta-mcp-logo.svg +225 -0
  22. tinta_mcp-0.2.2/docs/releasing.md +89 -0
  23. tinta_mcp-0.2.2/extension/tinta_mcp.inx +14 -0
  24. tinta_mcp-0.2.2/extension/tinta_mcp.py +671 -0
  25. tinta_mcp-0.2.2/extension/tinta_mcp_ops/__init__.py +1 -0
  26. tinta_mcp-0.2.2/extension/tinta_mcp_ops/common.py +103 -0
  27. tinta_mcp-0.2.2/extension/tinta_mcp_ops/document_operations.py +291 -0
  28. tinta_mcp-0.2.2/extension/tinta_mcp_ops/element_mapping.py +139 -0
  29. tinta_mcp-0.2.2/install_windows.bat +2 -0
  30. tinta_mcp-0.2.2/pyproject.toml +66 -0
  31. tinta_mcp-0.2.2/scripts/install_linux.sh +166 -0
  32. tinta_mcp-0.2.2/scripts/install_windows.bat +7 -0
  33. tinta_mcp-0.2.2/scripts/install_windows.ps1 +288 -0
  34. tinta_mcp-0.2.2/scripts/release.py +146 -0
  35. tinta_mcp-0.2.2/scripts/smoke_installed_package.py +46 -0
  36. tinta_mcp-0.2.2/scripts/verify_release_artifacts.py +92 -0
  37. tinta_mcp-0.2.2/tests/conftest.py +185 -0
  38. tinta_mcp-0.2.2/tests/inkex_cases.py +345 -0
  39. tinta_mcp-0.2.2/tests/mcp_worker.py +18 -0
  40. tinta_mcp-0.2.2/tests/test_dbus_smoke.py +253 -0
  41. tinta_mcp-0.2.2/tests/test_extension_handlers.py +31 -0
  42. tinta_mcp-0.2.2/tests/test_installer.py +18 -0
  43. tinta_mcp-0.2.2/tests/test_ipc_protocol.py +178 -0
  44. tinta_mcp-0.2.2/tests/test_live_safety.py +37 -0
  45. tinta_mcp-0.2.2/tests/test_preview_contracts.py +97 -0
  46. tinta_mcp-0.2.2/tests/test_release.py +144 -0
  47. tinta_mcp-0.2.2/tests/test_release_artifacts.py +60 -0
  48. tinta_mcp-0.2.2/tests/test_structured_operations.py +340 -0
  49. tinta_mcp-0.2.2/tests/test_tintamcp.py +187 -0
  50. tinta_mcp-0.2.2/tinta-designer-skill.zip +0 -0
  51. tinta_mcp-0.2.2/tintamcp/__init__.py +1 -0
  52. tinta_mcp-0.2.2/tintamcp/gdbus_util.py +186 -0
  53. tinta_mcp-0.2.2/tintamcp/installer.py +199 -0
  54. tinta_mcp-0.2.2/tintamcp/main.py +36 -0
  55. tinta_mcp-0.2.2/tintamcp/server.py +1189 -0
  56. tinta_mcp-0.2.2/uv.lock +2200 -0
@@ -0,0 +1,60 @@
1
+ # Tinta MCP agent guide
2
+
3
+ Tinta is a Model Context Protocol (MCP) server that enables AI assistants to control running Inkscape instances in real time via D-Bus.
4
+
5
+ ## Repository overview
6
+
7
+ - `tintamcp/`: Python MCP server package (`fastmcp`). Runs in the user's external Python environment, exposes tools, and translates requests into D-Bus calls.
8
+ - `extension/`: Inkscape extension (`tinta_mcp.py`, `tinta_mcp.inx`, and `tinta_mcp_ops/`). Runs inside Inkscape's internal Python interpreter when triggered by D-Bus.
9
+ - `tests/`: Automated unit tests and live D-Bus smoke tests using `pytest`.
10
+ - `scripts/`: Setup scripts for Windows (`install_windows.ps1`, `install_windows.bat`) and Linux/WSL (`install_linux.sh`).
11
+ - `.agents/`: Project rules, guidelines, and skills for AI assistants and contributors.
12
+ - `.github/workflows/`: CI matrix tests, security audits, headless D-Bus integration tests, and release packaging.
13
+
14
+ ## Core rules and invariants
15
+
16
+ 1. Runtime separation:
17
+ `tintamcp` must remain pure Python. Never add `inkex`, `lxml`, `pycairo`, or `pygobject` to `[project.dependencies]` in `pyproject.toml`. The `inkex` module is only imported inside `extension/` because it is provided by Inkscape at runtime.
18
+ 2. D-Bus identifier:
19
+ The registered D-Bus action name is strictly `org.tinta.mcp`. Do not introduce backwards-compatibility aliases (`inkmcp`).
20
+ 3. Cross-platform parity:
21
+ Support both Windows and Linux. On Windows, D-Bus calls use Inkscape's bundled `gdbus.exe`. On Linux, D-Bus calls use the system session bus `gdbus`. IPC lives under `%LOCALAPPDATA%\Temp\tintamcp\<user>` on Windows, or a verified private `$XDG_RUNTIME_DIR/tintamcp` with `/tmp/tintamcp-<uid>` fallback on Linux.
22
+ 4. Security and sanitization:
23
+ Validate XML element and attribute names using `is_valid_xml_name()`. Escape attribute values properly before inserting into SVG trees. Python code execution and textual command tools are not supported. Never evaluate client-provided Python with `exec`/`eval` or resolve operation names to arbitrary Python callables. Use fixed extension handlers receiving structured data; operation names and SVG element tags are separate fields.
24
+ 5. Code quality gates:
25
+ When modifying Python code (`extension/`, `tintamcp/`, `tests/`) or dependencies (`pyproject.toml`), verify that all quality gates pass:
26
+ ```bash
27
+ uv run ruff check extension tintamcp tests
28
+ uv run ruff format --check extension tintamcp tests
29
+ uv run pyright
30
+ uv run pytest
31
+ uv run pip-audit
32
+ ```
33
+ Pure documentation edits (such as updating `.md` files) do not require running Python test or lint gates.
34
+
35
+ ## Technical prose and documentation style
36
+
37
+ When writing documentation, guides, commit messages, or agent responses:
38
+ - State facts directly without rhetorical staging (*not X but Y*, fake alternatives, or dramatic one-line closers).
39
+ - Use sentence case for headings, avoid decorative emojis, and do not divide sections with horizontal rules (`---`).
40
+ - Do not use em dashes (`—`) or double hyphens (`--`) as universal connectors. Use colons, commas, periods, or parentheses instead.
41
+ - Write list items as natural sentences without forcing a bold title and colon on every bullet.
42
+ - Maintain a neutral, factual tone without marketing filler or AI buzzwords (*robust*, *seamless*, *testament*, *pivotal*, *delve*, *cutting-edge*, *effortless*).
43
+
44
+ ## Project rules and references
45
+
46
+ - Architecture and IPC boundaries: [rules/architecture.md](rules/architecture.md)
47
+
48
+ ## Project skills
49
+
50
+ - `mcp-tool-builder` ([skills/mcp-tool-builder/SKILL.md](skills/mcp-tool-builder/SKILL.md)): Workflow for adding, modifying, and testing MCP tools for Inkscape.
51
+ - `tinta-designer` ([skills/tinta-designer/SKILL.md](skills/tinta-designer/SKILL.md)): Best practices and technical guidelines for creating and editing vector graphics in Inkscape via Tinta.
52
+ - `tinta-dev-workflow` ([skills/tinta-dev-workflow/SKILL.md](skills/tinta-dev-workflow/SKILL.md)): Development standards, `uv` environment management, Conventional Commits, and CI quality gates.
53
+
54
+ ## Releasing a new version
55
+
56
+ 1. Bump version in `pyproject.toml` (`version = "X.Y.Z"`).
57
+ 2. Verify quality gates pass: `uv run pytest && uv run ruff check extension tintamcp tests && uv run pyright`.
58
+ 3. Commit using Conventional Commits: `git commit -am "chore(release): bump version to X.Y.Z"`.
59
+ 4. Tag and push: `git tag vX.Y.Z && git push origin main --tags`.
60
+ 5. GitHub Actions builds wheels, packages `tinta-extension.zip`, and creates the GitHub Release automatically.
@@ -0,0 +1,62 @@
1
+ ---
2
+ trigger: manual
3
+ ---
4
+
5
+ # Architecture and invariants
6
+
7
+ ## Process and runtime boundaries
8
+
9
+ ```
10
+ [ AI Assistant / Client ]
11
+ │
12
+ â–¼ (MCP protocol over stdio)
13
+ [ tintamcp server ] <-- Pure Python, managed by uv (fastmcp)
14
+ │
15
+ â–¼ (writes request JSON to user IPC folder)
16
+ [ gdbus / session bus ]
17
+ │
18
+ â–¼ (Action activation: org.tinta.mcp)
19
+ [ Inkscape process ]
20
+ │
21
+ â–¼ (runs extension/tinta_mcp.py via internal inkex)
22
+ [ Open SVG document ]
23
+ ```
24
+
25
+ - Server process (`tintamcp`): Orchestrates communication, tool definitions, schemas, and parameter validation. Does not touch SVG directly.
26
+ - Extension process (`extension`): Inkscape executes this inside its GTK event loop. It stages DOM mutations, serializes the SVG, then publishes the response JSON.
27
+
28
+ ## IPC protocol invariants
29
+
30
+ - The D-Bus action has no parameters. One process lock protects the shared request slot across server processes; a per-connection thread lock also serializes threads.
31
+ - The server atomically publishes `mcp_params.json` and `pending_request.json`; the extension claims the slot by exclusively creating `mcp_processing.json` before it reads the request.
32
+ - Request format:
33
+ ```json
34
+ {
35
+ "protocol_version": 1,
36
+ "session_id": "32 lowercase hexadecimal characters",
37
+ "request_id": "32 lowercase hexadecimal characters",
38
+ "request": {
39
+ "operation": "create-element" | "modify-elements" | "get-info" | "...",
40
+ "attributes": { ... }
41
+ }
42
+ }
43
+ ```
44
+ - Response path is derived from `request_id` as `mcp_response_<request_id>.json`; the request cannot choose a path. Responses repeat the version and both identifiers. The extension writes a temporary sibling and atomically renames it into place.
45
+ - The extension stages document mutations on a deep copy and publishes an error without using that copy if a handler fails. `save_raw()` serializes the SVG before it writes the response.
46
+ - Legacy requests with only a top-level `tag`, including `execute-code`, are rejected. There is no arbitrary Python or textual-command execution.
47
+ - Server and extension must be deployed together. Nesting the operation means a phase 1 extension sees no top-level operation and cannot execute a stale-format mutation. The server sends protocol version 1 and rejects missing or mismatched response identifiers, including the operation name.
48
+ - The server polls the derived response path for 5 seconds after D-Bus activation. Timeout, malformed response, or D-Bus failure means the outcome is unknown, not cancelled. The pending request remains on disk; later calls first reconcile its response and refuse to overwrite unresolved files.
49
+ - IPC uses `$XDG_RUNTIME_DIR/tintamcp` only when its owner and permissions are private, otherwise `/tmp/tintamcp-<uid>` with mode 0700. Windows uses the current user's local application data temp directory. Do not add global-temp fallbacks or caller-supplied IPC paths.
50
+
51
+ ## Tool pipeline design
52
+
53
+ - Prefer atomic operations where possible.
54
+ - The `create_elements` tool handles creation, canvas resizing, viewport fitting (`fit_canvas`), selection highlighting (`select_created`), and preview rendering (`return_preview`) in a single round-trip.
55
+ - Avoid forcing the LLM to make multiple serial round-trips for common design workflows.
56
+
57
+ ## Native GTK export actions
58
+
59
+ - Canvas preview exports use Inkscape GTK actions (`export-type`, `export-filename`, `export-overwrite`, `export-area-page`, `export-do`) triggered via `gdbus`.
60
+ - Never spawn a secondary headless Inkscape process to render previews: native actions take ~0.5s versus ~3.0s for CLI spawns.
61
+ - Every native preview action must be prepared successfully before `export-do`. Use a unique IPC destination, quote GVariant strings, cap the largest PNG dimension, and validate the complete PNG before returning it. A preview failure after an edit is a warning alongside the applied edit result.
62
+ - A successful D-Bus transport call is not an operation result. Extension errors must become MCP tool errors; inspection returns all reported metadata and selected elements without silent truncation.
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: mcp-tool-builder
3
+ description: "Step-by-step guide for creating, modifying, and testing MCP tools for Inkscape in Tinta."
4
+ ---
5
+
6
+ # MCP tool builder for Tinta
7
+
8
+ Use this skill when adding a new tool or modifying an existing tool in Tinta.
9
+
10
+ ## Dual-runtime architecture
11
+
12
+ Every tool in Tinta bridges two distinct environments:
13
+
14
+ 1. Host server (`tintamcp/`): Runs in external Python with FastMCP. Defines the tool signature, validates types, atomically writes a versioned request to the user's IPC directory, and calls D-Bus.
15
+ 2. Inkscape extension (`extension/`): Runs inside Inkscape's Python context via `inkex`. Claims and validates the IPC request, stages document edits, serializes the SVG, and atomically writes a correlated response.
16
+
17
+ ```
18
+ [Client / LLM] ──> FastMCP (@mcp.tool) ──> IPC Request File ──> gdbus ──> [Inkscape Extension] ──> SVG DOM
19
+ ```
20
+
21
+ ## Step 1: Define the tool in `tintamcp/server.py`
22
+
23
+ Register the tool on the `mcp` instance using clear, type-annotated parameters and concise docstrings designed for LLM consumption:
24
+
25
+ ```python
26
+ @mcp.tool()
27
+ def my_new_tool(element_id: str, radius: float = 10.0, color: str = "#000000") -> str:
28
+ """Short description of what the tool accomplishes on the canvas.
29
+
30
+ Args:
31
+ element_id: Unique identifier for the SVG element.
32
+ radius: Radius in user units (default 10.0).
33
+ color: Hex or CSS color string (e.g. '#ff0000').
34
+ """
35
+ payload = {
36
+ "operation": "my-operation",
37
+ "attributes": {
38
+ "id": element_id,
39
+ "radius": str(radius),
40
+ "color": color,
41
+ },
42
+ }
43
+ return str(dispatch_operation(payload))
44
+ ```
45
+
46
+ ### Design guidelines for MCP tools
47
+
48
+ - Use snake_case for Python parameters. Tinta automatically normalizes snake_case to kebab-case SVG attributes (`stroke_width` -> `stroke-width`).
49
+ - Provide sensible defaults to reduce round-trips for common actions.
50
+ - Prefer atomic operations. When an operation involves creating elements, fitting canvas, or generating previews, combine them into a single round-trip (see `create_elements`).
51
+
52
+ ### Docstring design for LLM consumers
53
+
54
+ Tool docstrings are injected directly into the client LLM system prompt. Follow these guidelines to maximize tool calling accuracy and minimize token overhead:
55
+ - State actions directly using imperative mood in the first line (`"Create an SVG rectangle."`, not `"This tool allows the user to seamlessly create..."`).
56
+ - Avoid marketing filler and buzzwords (*robust*, *seamless*, *powerful*, *effortless*, *cutting-edge*, *comprehensive*).
57
+ - Document units and formats explicitly (pixels, percentages, hex colors) and valid ranges in the `Args:` section.
58
+ - Explain non-obvious defaults concisely so the LLM can plan its workflow in fewer calls.
59
+
60
+ ## Step 2: Implement the operation in `extension/`
61
+
62
+ Operations live under `extension/tinta_mcp_ops/`:
63
+ - Shape and element creation: `extension/tinta_mcp_ops/element_mapping.py`
64
+ - Manipulation and attributes: `extension/tinta_mcp_ops/document_operations.py`
65
+
66
+ ### Handling the action in `extension/tinta_mcp.py`
67
+
68
+ Register a fixed handler in `DOCUMENT_OPERATIONS`, called by `ElementCreator.handle_operation`. Never generate Python source or expose an interpreter. Operation names are independent of SVG tags. For queries handled by the extension:
69
+
70
+ ```python
71
+ if operation == "my-operation":
72
+ # 1. Access the open document DOM via self.svg
73
+ elem = self.svg.getElementById(attributes.get("id"))
74
+
75
+ # 2. Mutate DOM using inkex elements
76
+ # 3. Return response data; the extension publishes it after SVG serialization.
77
+ ```
78
+
79
+ The extension never accepts a response path from an operation. Requests use protocol version 1 with generated session and request IDs; responses use the corresponding derived path.
80
+
81
+ ## Step 3: Test and debug
82
+
83
+ ### Manual D-Bus trigger (Windows and Linux)
84
+
85
+ Verify that Inkscape responds over the session bus:
86
+
87
+ Windows (PowerShell):
88
+ ```powershell
89
+ $gdbus = "C:\Program Files\Inkscape\bin\gdbus.exe"
90
+ & $gdbus call --session --dest org.inkscape.Inkscape --object-path /org/inkscape/Inkscape --method org.gtk.Actions.List | Select-String "org.tinta.mcp"
91
+ ```
92
+
93
+ Linux / WSL (Bash):
94
+ ```bash
95
+ gdbus call --session --dest org.inkscape.Inkscape --object-path /org/inkscape/Inkscape --method org.gtk.Actions.List | grep org.tinta.mcp
96
+ ```
97
+
98
+ ### Automated integration test
99
+
100
+ Add a test in [tests/test_dbus_smoke.py](../../../tests/test_dbus_smoke.py):
101
+
102
+ ```python
103
+ def test_my_new_tool_live():
104
+ result = my_new_tool(element_id="test_item_1", radius=15.0)
105
+ assert "success" in result.lower()
106
+ ```
107
+
108
+ Run tests locally:
109
+ ```bash
110
+ uv run pytest
111
+ ```
112
+
113
+ Use `uv run pytest tests/test_dbus_smoke.py --live-inkscape -v` for live tests. The fixture must own an isolated process/profile/document; never target the user's running session.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: tinta-designer
3
+ description: "Best practices, workflows, and technical guidelines for creating and editing vector graphics in Inkscape via Tinta MCP."
4
+ ---
5
+
6
+ # Tinta designer workflow
7
+
8
+ Use this skill when creating, modifying, or inspecting vector illustrations, diagrams, icons, or layouts in an open Inkscape session.
9
+
10
+ ## Core workflow
11
+
12
+ ### 1. Inspect existing state
13
+ Before creating or editing graphics, verify the current canvas state:
14
+ - Call `get_document_info` to inspect canvas dimensions, viewBox coordinates, and existing element counts.
15
+ - Call `get_selection` if the user refers to "selected items", "this shape", or wants to modify whatever is currently highlighted in the Inkscape UI.
16
+ - Do not clear the canvas unless the user explicitly asks to start from scratch or clear the document.
17
+
18
+ ### 2. Batch operations using `create_elements`
19
+ Avoid making multiple sequential tool calls to insert shapes one by one. Use `create_elements` to submit a structured batch:
20
+ - Combine element definitions, canvas fitting, and preview rendering in a single request.
21
+ - Use `fit_canvas=True` with `fit_padding` when drawing a composition whose final bounding box should define the page boundaries.
22
+ - Set `return_preview=True` to immediately obtain an image preview of the resulting render.
23
+
24
+ ### 3. Verify with visual feedback
25
+ Always inspect the visual result before concluding a design task:
26
+ - Use the preview returned by `create_elements(return_preview=True)` or call `export_preview()`.
27
+ - Check for overlapping text, clipping at viewBox boundaries, or unintended layer stacking.
28
+ - If an adjustment is needed, modify elements by ID with `modify_elements` (using `return_preview=True` for instant visual verification) rather than redrawing the entire canvas.
29
+
30
+ ## Technical SVG guidelines for Inkscape
31
+
32
+ ### Document hierarchy and grouping
33
+ Organize elements into logical groups using `<g id="...">`:
34
+ - Group related shapes together (e.g. `header_group`, `chart_bars`, `icon_background`).
35
+ - Grouped elements appear cleanly in Inkscape's Objects and Layers panel (`Ctrl+Shift+L`), allowing the user to select, hide, or transform components easily.
36
+ - Assign meaningful, unique IDs to major elements so they can be modified later without guessing.
37
+
38
+ ### Z-order and stacking
39
+ In SVG, rendering order follows document order:
40
+ - Elements defined earlier in the payload are drawn below later elements.
41
+ - Always declare background rectangles, cards, or base shapes first.
42
+ - Declare decorative details, strokes, icons, and text elements last.
43
+
44
+ ### Primitive shapes versus paths
45
+ - Use native primitives (`<rect>`, `<circle>`, `<ellipse>`, `<line>`) whenever possible. In Inkscape, native primitives retain parametric handles (e.g. dragging corner radius on a rectangle).
46
+ - Use `<path>` for custom curves, logos, irregular polygons, or arrows where primitives are insufficient.
47
+
48
+ ### Text positioning and baseline alignment
49
+ In SVG, the `y` coordinate of a `<text>` element refers to the typographic baseline, not the top-left bounding box corner:
50
+ - If placing text at the top of a container at `y=0`, the text will render above the visible area.
51
+ - Either offset `y` downwards by at least the font size (e.g. `y=24` for a `20px` font) or set `dominant-baseline="hanging"`.
52
+ - Use cross-platform fallback font stacks such as `system-ui, sans-serif, Arial, Helvetica, DejaVu Sans` so text renders reliably on both Windows and Linux installations.
53
+
54
+ ### Viewport and coordinate consistency
55
+ - Work within a consistent coordinate system defined by the `viewBox`.
56
+ - When sizing rectangles or positioning icons, calculate relative offsets from the parent group or anchor position rather than placing arbitrary coordinates across the canvas.
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: tinta-dev-workflow
3
+ description: "Developer and AI agent workflow for dependency management, code quality gates, and conventional commits in Tinta."
4
+ ---
5
+
6
+ # Tinta development and quality workflow
7
+
8
+ Use this skill for local environment setup, running quality gates, and crafting commits that pass CI.
9
+
10
+ ## Environment management with uv
11
+
12
+ Tinta uses `uv` for fast, reproducible dependency resolution.
13
+
14
+ ### Basic commands
15
+
16
+ ```bash
17
+ # Sync all dependencies (runtime + dev tools)
18
+ uv sync --all-groups
19
+
20
+ # Add a runtime dependency (must be pure Python, no C/inkex)
21
+ uv add <package-name>
22
+
23
+ # Add a development tool (linter, test helper)
24
+ uv add --group dev <package-name>
25
+
26
+ # Run commands within the managed environment
27
+ uv run pytest
28
+ uv run ruff check extension tintamcp tests
29
+ ```
30
+
31
+ ### Windows and WSL shared drive tip
32
+
33
+ When developing across both Windows and WSL on a shared drive:
34
+ - Windows `.venv` uses `.venv/Scripts/python.exe`.
35
+ - Linux `.venv` uses `.venv/bin/python`.
36
+ - Running `uv sync` switches between them in 1-2 seconds because all dependencies are pure Python wheels.
37
+
38
+ ## Quality gates checklist
39
+
40
+ Run these checks when modifying Python code (`extension/`, `tintamcp/`, `tests/`) or dependencies (`pyproject.toml`). Pure documentation edits do not require running test or lint gates.
41
+
42
+ ```bash
43
+ # 1. Linting and import sorting
44
+ uv run ruff check extension tintamcp tests
45
+
46
+ # 2. Code formatting
47
+ uv run ruff format --check extension tintamcp tests
48
+
49
+ # 3. Static type analysis
50
+ uv run pyright
51
+
52
+ # 4. Test suite and coverage report
53
+ uv run pytest
54
+
55
+ # 5. Dependency security audit
56
+ uv run pip-audit
57
+ ```
58
+
59
+ To automatically format files:
60
+ ```bash
61
+ uv run ruff format extension tintamcp tests
62
+ uv run ruff check --fix extension tintamcp tests
63
+ ```
64
+
65
+ ## Conventional Commits standard
66
+
67
+ All PR titles and commit messages must follow the Conventional Commits specification.
68
+
69
+ ### Format
70
+
71
+ ```text
72
+ <type>(<scope>): <short description in imperative mood>
73
+
74
+ [optional body explaining motivation and context]
75
+
76
+ [optional footer, e.g. BREAKING CHANGE or Closes #123]
77
+ ```
78
+
79
+ ### Allowed types
80
+
81
+ - `feat`: New feature or MCP tool (e.g. `feat(tools): add canvas_rotate tool`)
82
+ - `fix`: Bug fix in server, extension, or installer (e.g. `fix(ipc): handle spaces in username path`)
83
+ - `docs`: Documentation updates (e.g. `docs: clarify WSL setup in README`)
84
+ - `refactor`: Code restructuring without functional changes (e.g. `refactor(parser): extract xml validation`)
85
+ - `perf`: Performance improvement (e.g. `perf(export): use native GTK export actions`)
86
+ - `test`: Adding or updating tests (e.g. `test(dbus): add smoke test for element deletion`)
87
+ - `ci`: CI/CD workflow changes (e.g. `ci: add security audit gate`)
88
+ - `chore`: Tooling, dependency, or version updates (e.g. `chore(deps): update fastmcp to 4.1.0`)
89
+
90
+ ### Rules
91
+
92
+ - Use lower case for type and scope.
93
+ - Use imperative present tense (`add`, not `added` or `adds`).
94
+ - Do not add a period at the end of the subject line.
@@ -0,0 +1,18 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ indent_style = space
8
+ indent_size = 4
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.{bat,cmd,ps1}]
12
+ end_of_line = crlf
13
+
14
+ [*.{json,yml,yaml}]
15
+ indent_size = 2
16
+
17
+ [*.md]
18
+ trim_trailing_whitespace = false
@@ -0,0 +1,5 @@
1
+ * text=auto eol=lf
2
+ *.bat text eol=crlf
3
+ *.cmd text eol=crlf
4
+ *.ps1 text eol=crlf
5
+ *.png binary
@@ -0,0 +1,4 @@
1
+ # Ubuntu 26.04 is a hosted image not yet included in actionlint's built-in labels.
2
+ self-hosted-runner:
3
+ labels:
4
+ - ubuntu-26.04
@@ -0,0 +1,194 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths-ignore:
8
+ - "**.md"
9
+ - "assets/**"
10
+ - ".gitignore"
11
+ pull_request:
12
+ types:
13
+ - opened
14
+ - edited
15
+ - synchronize
16
+ - reopened
17
+ branches:
18
+ - main
19
+ paths-ignore:
20
+ - "**.md"
21
+ - "assets/**"
22
+ - ".gitignore"
23
+ workflow_dispatch:
24
+
25
+ concurrency:
26
+ group: ${{ github.workflow }}-${{ github.ref }}
27
+ cancel-in-progress: true
28
+
29
+ permissions:
30
+ pull-requests: read
31
+ contents: read
32
+
33
+ env:
34
+ PYTHONUTF8: "1"
35
+ PYTHONIOENCODING: utf-8
36
+
37
+ jobs:
38
+ pr-title:
39
+ name: Validate PR Title (Conventional Commits)
40
+ runs-on: ubuntu-26.04
41
+ if: github.event_name == 'pull_request'
42
+ steps:
43
+ - name: Validate PR Title
44
+ uses: amannn/action-semantic-pull-request@v6.1.1
45
+ env:
46
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47
+ with:
48
+ types: |
49
+ feat
50
+ fix
51
+ docs
52
+ style
53
+ refactor
54
+ perf
55
+ test
56
+ build
57
+ ci
58
+ chore
59
+ revert
60
+ requireScope: false
61
+
62
+ lint:
63
+ name: Lint, Format & Type Check
64
+ runs-on: ubuntu-26.04
65
+ steps:
66
+ - name: Checkout repository
67
+ uses: actions/checkout@v7.0.1
68
+
69
+ - name: Set up uv
70
+ uses: astral-sh/setup-uv@v10.3.0
71
+ with:
72
+ enable-cache: true
73
+
74
+ - name: Set up Python
75
+ run: uv python install 3.13
76
+
77
+ - name: Sync dependencies
78
+ run: uv sync --all-groups
79
+
80
+ - name: Check code formatting (ruff format)
81
+ run: uv run ruff format --check extension tintamcp scripts tests
82
+
83
+ - name: Check lints and imports (ruff check)
84
+ run: uv run ruff check extension tintamcp scripts tests
85
+
86
+ - name: Check static types (pyright)
87
+ run: uv run pyright
88
+
89
+ - name: Validate Linux installer syntax
90
+ run: bash -n scripts/install_linux.sh
91
+
92
+ audit:
93
+ name: Dependency Security Audit
94
+ runs-on: ubuntu-26.04
95
+ steps:
96
+ - name: Checkout repository
97
+ uses: actions/checkout@v7.0.1
98
+
99
+ - name: Set up uv
100
+ uses: astral-sh/setup-uv@v10.3.0
101
+ with:
102
+ enable-cache: true
103
+
104
+ - name: Set up Python
105
+ run: uv python install 3.13
106
+
107
+ - name: Sync dependencies
108
+ run: uv sync --all-groups
109
+
110
+ - name: Audit dependencies for known CVEs
111
+ run: uv run pip-audit
112
+
113
+ test:
114
+ name: Test (${{ matrix.os }}, Py ${{ matrix.python-version }})
115
+ needs: lint
116
+ runs-on: ${{ matrix.os }}
117
+ strategy:
118
+ fail-fast: false
119
+ matrix:
120
+ os: [ubuntu-26.04, windows-latest]
121
+ python-version: ["3.12", "3.13"]
122
+ steps:
123
+ - name: Checkout repository
124
+ uses: actions/checkout@v7.0.1
125
+
126
+ - name: Set up uv
127
+ uses: astral-sh/setup-uv@v10.3.0
128
+ with:
129
+ enable-cache: true
130
+
131
+ - name: Set up Python ${{ matrix.python-version }}
132
+ run: uv python install ${{ matrix.python-version }}
133
+
134
+ - name: Sync dependencies
135
+ run: uv sync --all-groups --python ${{ matrix.python-version }}
136
+
137
+ - name: Validate Windows installer syntax
138
+ if: matrix.os == 'windows-latest'
139
+ shell: pwsh
140
+ run: |
141
+ $tokens = $null
142
+ $parseErrors = $null
143
+ [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path scripts/install_windows.ps1), [ref]$tokens, [ref]$parseErrors) | Out-Null
144
+ if ($parseErrors.Count) {
145
+ $parseErrors | ForEach-Object { Write-Error $_.Message }
146
+ exit 1
147
+ }
148
+
149
+ - name: Run test suite with coverage
150
+ run: uv run pytest
151
+
152
+ - name: Upload coverage artifact
153
+ if: matrix.os == 'ubuntu-26.04' && matrix.python-version == '3.13'
154
+ uses: actions/upload-artifact@v7.0.2
155
+ with:
156
+ name: coverage-report
157
+ path: coverage.xml
158
+ retention-days: 7
159
+
160
+ dbus-e2e:
161
+ name: Headless D-Bus E2E Test (Linux)
162
+ needs: lint
163
+ runs-on: ubuntu-26.04
164
+ steps:
165
+ - name: Checkout repository
166
+ uses: actions/checkout@v7.0.1
167
+
168
+ - name: Install Inkscape and headless test tools
169
+ run: |
170
+ sudo apt-get update
171
+ sudo apt-get install -y inkscape libglib2.0-bin xvfb dbus-x11
172
+
173
+ - name: Set up uv
174
+ uses: astral-sh/setup-uv@v10.3.0
175
+ with:
176
+ enable-cache: true
177
+
178
+ - name: Set up Python
179
+ run: uv python install 3.13
180
+
181
+ - name: Sync dependencies
182
+ run: uv sync --all-groups
183
+
184
+ - name: Run live tests in isolated profiles and documents
185
+ run: timeout 15m xvfb-run -a dbus-run-session uv run pytest tests/test_dbus_smoke.py --live-inkscape -v --junitxml=live-inkscape-results.xml
186
+
187
+ - name: Upload live test results
188
+ if: always()
189
+ uses: actions/upload-artifact@v7.0.2
190
+ with:
191
+ name: live-inkscape-results
192
+ path: live-inkscape-results.xml
193
+ if-no-files-found: ignore
194
+ retention-days: 7