diff --git a/.dockerignore b/.dockerignore index fd384e7..d73f104 100644 --- a/.dockerignore +++ b/.dockerignore @@ -95,5 +95,5 @@ justfile .codespell-ignore-words.txt mkdocs.yml ARCHITECTURE-MCP.md -CHANGELOG.md CLAUDE.md +**/RELEASE_NOTES.md diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index f9cf4dc..318fa0f 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -11,6 +11,7 @@ on: - ".dockerignore" - "src/**/*.py" - "pyproject.toml" + - "uv.lock" - ".github/workflows/docker.yaml" pull_request: branches: [main, master] @@ -19,6 +20,7 @@ on: - ".dockerignore" - "src/**/*.py" - "pyproject.toml" + - "uv.lock" - ".github/workflows/docker.yaml" workflow_dispatch: workflow_call: @@ -106,8 +108,8 @@ jobs: - name: Test Docker image run: | - # Build for current platform only for testing - docker build --build-arg VERSION=${{ steps.version.outputs.VERSION }} -t ${{ env.IMAGE_NAME }}:test . + # Build for current platform only for testing (--no-cache ensures fresh build) + docker build --no-cache --build-arg VERSION=${{ steps.version.outputs.VERSION }} -t ${{ env.IMAGE_NAME }}:test . # Test that the container starts and responds to MCP initialize echo '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | \ @@ -129,6 +131,7 @@ jobs: uses: aquasecurity/trivy-action@0.33.1 with: image-ref: ${{ env.IMAGE_NAME }}:test + version: "v0.68.2" severity: "HIGH,CRITICAL" exit-code: "1" format: "table" @@ -139,6 +142,7 @@ jobs: uses: aquasecurity/trivy-action@0.33.1 with: image-ref: ${{ env.IMAGE_NAME }}:test + version: "v0.68.2" severity: "MEDIUM,LOW" exit-code: "0" format: "table" @@ -151,6 +155,7 @@ jobs: uses: aquasecurity/trivy-action@0.33.1 with: image-ref: ${{ env.IMAGE_NAME }}:test + version: "v0.68.2" format: "sarif" output: "trivy-results.sarif" cache-dir: .trivy-cache diff --git a/.github/workflows/macro-release-reusable.yaml b/.github/workflows/macro-release-reusable.yaml index 7719d46..4bc888a 100644 --- a/.github/workflows/macro-release-reusable.yaml +++ b/.github/workflows/macro-release-reusable.yaml @@ -222,7 +222,7 @@ jobs: echo "Created archives:" ls -la "${ARCHIVE_NAME}-${VERSION}."* - - name: Extract changelog section + - name: Extract release notes section id: changelog env: VERSION: ${{ steps.version.outputs.version }} @@ -233,19 +233,18 @@ jobs: README_FILE: ${{ inputs.readme_file }} WIKI_URL: ${{ inputs.wiki_url }} run: | - # Match header exactly as it appears in CHANGELOG.md - HEADER="### ${MACRO_NAME} Macro v${VERSION}" + RELEASE_NOTES="macros/${MACRO_DIR}/RELEASE_NOTES.md" - # Extract section between this version header and the next component header or separator - # Only exit on: "---" separator OR "### " followed by component name (capital letter) - # This allows #### Added, #### Changed, etc. to be included - CHANGELOG_CONTENT=$(awk -v header="$HEADER" ' + # Extract section for this version from RELEASE_NOTES.md + # Format: ## Version X.Y.Z (date) + CHANGELOG_CONTENT=$(awk -v version="$VERSION" ' BEGIN { found=0 } - $0 == header || $0 == header " " { found=1; next } - found && /^---$/ { exit } - found && /^### [A-Z]/ { exit } + /^## Version / { + if (found) exit + if (index($0, version) > 0) { found=1; next } + } found { print } - ' CHANGELOG.md) + ' "$RELEASE_NOTES" 2>/dev/null || echo "") # Build release body cat > release_body.md << EOF diff --git a/.github/workflows/mcp-server-release.yaml b/.github/workflows/mcp-server-release.yaml index 338fc31..21d6b57 100644 --- a/.github/workflows/mcp-server-release.yaml +++ b/.github/workflows/mcp-server-release.yaml @@ -347,23 +347,23 @@ jobs: name: python-package-distributions path: dist/ - - name: Extract changelog section + - name: Extract release notes section id: changelog run: | VERSION="${{ needs.validate-tag.outputs.version }}" - # Match header exactly as it appears in CHANGELOG.md - HEADER="### Robust MCP Server v${VERSION}" + RELEASE_NOTES="src/freecad_mcp/RELEASE_NOTES.md" - # Extract section between this version header and the next component header or separator - # Only exit on: "---" separator OR "### " followed by component name (capital letter) - # This allows #### Added, #### Changed, etc. to be included - CHANGELOG_CONTENT=$(awk -v header="$HEADER" ' + # Extract section for this version from RELEASE_NOTES.md + # Format: ## Version X.Y.Z (date) + # Extract everything between this version header and the next ## Version header + CHANGELOG_CONTENT=$(awk -v version="$VERSION" ' BEGIN { found=0 } - $0 == header || $0 == header " " { found=1; next } - found && /^---$/ { exit } - found && /^### [A-Z]/ { exit } + /^## Version / { + if (found) exit + if (index($0, version) > 0) { found=1; next } + } found { print } - ' CHANGELOG.md) + ' "$RELEASE_NOTES") # Write to file for the release body (handles multiline) echo "$CHANGELOG_CONTENT" > changelog_section.md @@ -371,10 +371,10 @@ jobs: # Check if we got content if [ -n "$CHANGELOG_CONTENT" ]; then echo "found=true" >> "$GITHUB_OUTPUT" - echo "Extracted changelog section for $HEADER" + echo "Extracted release notes for version $VERSION" else echo "found=false" >> "$GITHUB_OUTPUT" - echo "No changelog section found for $HEADER" + echo "No release notes found for version $VERSION (will use auto-generated notes)" fi - name: Create GitHub Release diff --git a/.github/workflows/mcp-workbench-release.yaml b/.github/workflows/mcp-workbench-release.yaml index 5a59812..cdb9965 100644 --- a/.github/workflows/mcp-workbench-release.yaml +++ b/.github/workflows/mcp-workbench-release.yaml @@ -162,23 +162,25 @@ jobs: echo "Created archives:" ls -la "freecad-mcp-workbench-${VERSION}."* - - name: Extract changelog section + - name: Extract release notes section id: changelog run: | VERSION="${{ steps.version.outputs.version }}" - # Match header exactly as it appears in CHANGELOG.md (with optional space before v) - HEADER="### Robust MCP Bridge Workbench v${VERSION}" + RELEASE_NOTES="addon/FreecadRobustMCPBridge/RELEASE_NOTES.md" - # Extract section between this version header and the next component header or separator - # Only exit on: "---" separator OR "### " followed by component name (capital letter) - # This allows #### Added, #### Changed, etc. to be included - CHANGELOG_CONTENT=$(awk -v header="$HEADER" ' + # Extract section for this version from RELEASE_NOTES.md + # Format: ## Version X.Y.Z (date) + # Use exact version match to avoid substring issues (e.g., 1.0.0 matching 1.0.0-alpha) + CHANGELOG_CONTENT=$(awk -v version="$VERSION" ' BEGIN { found=0 } - $0 == header || $0 == header " " { found=1; next } - found && /^---$/ { exit } - found && /^### [A-Z]/ { exit } + /^## Version / { + if (found) exit + # Extract version field (3rd word) and compare exactly + split($0, parts, " ") + if (parts[3] == version) { found=1; next } + } found { print } - ' CHANGELOG.md) + ' "$RELEASE_NOTES" 2>/dev/null || echo "") # Build release body with changelog content if available cat > release_body.md << 'STATIC_EOF' diff --git a/.mise.toml b/.mise.toml index 705c81a..a9def27 100644 --- a/.mise.toml +++ b/.mise.toml @@ -13,7 +13,7 @@ pre-commit = "4.5" # pre-commit hooks github-cli = "2.74" # GitHub CLI for PR/issue management # Security and code quality tools -trivy = "0.62" # container vulnerability scanner +trivy = "0.68" # container vulnerability scanner gitleaks = "8.30" # secrets scanner actionlint = "1.7" # GitHub Actions linter # Note: hadolint and shellcheck are managed by their pre-commit repos diff --git a/.safety-project.ini b/.safety-project.ini index f2f4469..956b58c 100644 --- a/.safety-project.ini +++ b/.safety-project.ini @@ -1,5 +1,5 @@ [project] -id = freecad-mcp-and-more -url = /codebases/freecad-mcp-and-more/findings -name = freecad-mcp-and-more +id = freecad-addon-robust-mcp-server +url = /codebases/freecad-addon-robust-mcp-server/findings +name = freecad-addon-robust-mcp-server diff --git a/.secrets.baseline b/.secrets.baseline index 96338c0..bd16472 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -92,7 +92,7 @@ }, { "path": "detect_secrets.filters.common.is_baseline_file", - "filename": "/Users/spkane/dev/spkane/freecad-mcp-and-more/.secrets.baseline" + "filename": "/Users/spkane/dev/spkane/FreeCAD-Components/mine/freecad-addon-robust-mcp-server/.secrets.baseline" }, { "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 6872c52..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,124 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -This is a multi-component project. Each component has its own versioning and release cycle: - -| Component | Tag Format | Distribution | -| ------------------------------ | ------------------------------------- | ------------------------ | -| Robust MCP Server | `robust-mcp-server-vX.Y.Z` | PyPI, Docker Hub, GitHub | -| Robust MCP Bridge Workbench | `robust-mcp-workbench-vX.Y.Z` | GitHub Release | -| Cut Object for Magnets Macro | `macro-cut-object-for-magnets-vX.Y.Z` | GitHub Release | -| Multi Export Macro | `macro-multi-export-vX.Y.Z` | GitHub Release | - ---- - -## [Unreleased] - -### Robust MCP Server - -#### Removed - -- Docker detection fields (`in_docker`, `docker_container_id`) from `get_mcp_server_environment()` tool - -### Robust MCP Bridge Workbench - -#### Fixed - -- Fixed GUI crashes during auto-start by deferring bridge initialization 2 seconds after `FreeCAD.GuiUp` becomes True -- Increased GUI wait timeout from 3 seconds to 60 seconds to accommodate slow FreeCAD startups on macOS -- Bridge no longer attempts to start on timeout (prevents crash from background thread usage) -- `startup_bridge.py` now has same defensive GUI wait logic as `Init.py` - -### Cut Object for Magnets Macro - -### Multi Export Macro - ---- - -## Initial Public Beta - 2026-01-05 - -### Robust MCP Server v0.5.0-beta - -Initial public beta release. - -#### Added - -- **Document management**: Create, open, save, close, recompute documents -- **Object manipulation**: Create primitives (box, cylinder, sphere, cone, torus, wedge, helix), boolean operations, transforms (scale, rotate, copy, mirror) -- **PartDesign workflow**: Bodies, sketches, pads, pockets, revolutions, fillets, chamfers, holes, patterns, lofts, sweeps -- **Import/Export**: STEP, STL, 3MF, OBJ, IGES, BREP formats -- **View control**: Screenshots, camera positioning, zoom, visibility, display modes -- **Macro support**: List, run, create, and manage FreeCAD macros -- **Undo/Redo**: Full undo/redo support with status queries -- **Connection modes**: XML-RPC (recommended), Socket, and Embedded (Linux only) - -#### Installation - -- **PyPI**: `pip install freecad-robust-mcp` -- **Docker**: `docker pull ghcr.io/spkane/freecad-robust-mcp` -- **Source**: Clone and install with `uv sync --all-extras` - -#### Known Limitations - -- Embedded mode only works on Linux (macOS/Windows must use xmlrpc or socket) -- Python 3.11 required (must match FreeCAD's bundled Python for ABI compatibility) -- GUI features (screenshots, visibility) not available in headless mode - ---- - -### Robust MCP Bridge Workbench v0.5.0-beta - -Initial public beta release. - -#### Added - -- **FreeCAD Workbench**: Installable via FreeCAD Addon Manager -- **XML-RPC server**: Runs on port 9875 for Robust MCP Server communication -- **Socket server**: Alternative JSON-RPC on port 9876 -- **Auto-start option**: Configure bridge to start with FreeCAD -- **GUI mode support**: Full 3D view integration with screenshots -- **Headless mode support**: Console-only operation for automation - ---- - -### Cut Object for Magnets Macro v0.5.0-beta - -Initial public beta release. - -#### Added - -- **Smart cutting**: Cut objects in half with aligned magnet holes for easy reassembly -- **Hole placement**: Automatic hole placement with overlap detection -- **Object support**: Works with solid and hollow objects -- **Hole methods**: Parametric PartDesign::Hole or boolean hole methods -- **GUI dialog**: Interactive parameter configuration - ---- - -### Multi Export Macro v0.5.0-beta - -Initial public beta release. - -#### Added - -- **Multi-format export**: Export selected objects to multiple formats simultaneously -- **Format support**: STL, STEP, 3MF, OBJ, IGES, BREP, PLY, AMF -- **Mesh control**: Configurable mesh tolerance for STL/3MF -- **Batch export**: Export multiple objects with progress feedback -- **GUI dialog**: Interactive format and option selection - ---- - -## Project Infrastructure (v0.5.0-beta) - -### Added - -- **CI/CD**: GitHub Actions workflows for testing, Docker builds, and PyPI releases -- **Quality**: Pre-commit hooks with Ruff, MyPy, Bandit, and secrets detection -- **Dependencies**: Dependabot for automated dependency updates -- **Security**: CodeQL security scanning -- **Documentation**: Comprehensive README, CLAUDE.md, and architecture docs diff --git a/CLAUDE.md b/CLAUDE.md index 22fe6b1..1e0daa1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -800,7 +800,6 @@ project-root/ ├── .pre-commit-config.yaml # Pre-commit hook configuration ├── .safety-policy.yml # Safety CLI scan policy ├── .secrets.baseline # detect-secrets baseline -├── CHANGELOG.md # Project changelog ├── CLAUDE.md # This file (AI assistant guidelines) ├── Dockerfile # Docker image definition ├── justfile # Main task runner (imports modules) @@ -1375,11 +1374,20 @@ This project uses **component-specific versioning**. Each component has its own *Stable releases (`X.Y.Z`) publish to PyPI; non-stable releases (alpha, beta, rc) publish to TestPyPI only. -### Changelog Management +### Release Notes Management -The project uses a single `CHANGELOG.md` with sections for each component. Before releasing: +Each component has its own `RELEASE_NOTES.md` file. Release workflows automatically extract the relevant section for GitHub Releases. -1. **Draft release notes** from conventional commits: +| Component | Release Notes File | +| ---------------------------- | ----------------------------------------------------- | +| MCP Server | `src/freecad_mcp/RELEASE_NOTES.md` | +| Robust MCP Bridge Workbench | `addon/FreecadRobustMCPBridge/RELEASE_NOTES.md` | +| Cut Object for Magnets Macro | `macros/Cut_Object_for_Magnets/RELEASE_NOTES.md` | +| Multi Export Macro | `macros/Multi_Export/RELEASE_NOTES.md` | + +**Before releasing a component:** + +1. **Draft release notes** from git commits since the last release: ```bash just release::draft-notes mcp-server @@ -1388,36 +1396,61 @@ The project uses a single `CHANGELOG.md` with sections for each component. Befor just release::draft-notes macro-export ``` -2. **Update CHANGELOG.md** with entries under the appropriate component header: +2. **Edit the component's RELEASE_NOTES.md** file, adding a new version section at the top: ```markdown - ## YYYY-MM-DD + ## Version X.Y.Z (YYYY-MM-DD) - ### MCP Server vX.Y.Z + Release notes for changes between vA.B.C and vX.Y.Z. - #### Added - - New feature + ### Added - --- + - New feature description - ### Robust MCP Bridge Workbench vX.Y.Z - ... + ### Changed + + - Change description + + ### Fixed + + - Bug fix description ``` -3. The release workflow automatically extracts the changelog section for GitHub Releases. +3. The release workflow automatically extracts the version section for GitHub Releases. ### Creating a Release -Use the `just release::` commands to create and push release tags: +**Step 1: Run release tests** to ensure everything passes: ```bash -# Check what has unreleased changes -just release::status +just testing::release-test +``` -# Preview changes since last release -just release::changes-since mcp-server -just release::changes-since workbench +This runs unit tests, integration tests (headless + GUI), Docker tests, and just command tests. All must pass before proceeding. +**Step 2: Update release notes** for each component you're releasing: + +```bash +# Generate draft notes from git commits +just release::draft-notes mcp-server + +# Edit the RELEASE_NOTES.md file (add version section at top) +# See "Release Notes Management" above for format +``` + +**Step 3: Bump versions** (for workbench/macros only - MCP server auto-bumps from tag): + +```bash +just release::bump-workbench 1.0.0 +just release::bump-macro-magnets 1.0.0 +just release::bump-macro-export 1.0.0 +``` + +**Step 4: Commit and push** your RELEASE_NOTES.md and version bump changes. + +**Step 5: Create release tags** (this triggers the release workflows): + +```bash # Release the MCP server (triggers PyPI, Docker, GitHub release) just release::tag-mcp-server 1.0.0 @@ -1427,10 +1460,23 @@ just release::tag-workbench 1.0.0 # Release macros just release::tag-macro-magnets 1.0.0 just release::tag-macro-export 1.0.0 +``` + +**Utility commands:** + +```bash +# Check what has unreleased changes +just release::status + +# Preview changes since last release +just release::changes-since mcp-server # View release tags just release::list-tags just release::latest-versions + +# Extract changelog for a version (for testing) +just release::extract-changelog mcp-server 1.0.0 ``` ### Version Format @@ -1485,14 +1531,24 @@ Each component can have a different version, and the release workflows automatic --- -## Changelog Maintenance +## Release Notes Maintenance -**IMPORTANT**: Update `CHANGELOG.md` at the end of any session where files in the repository are modified. +Each component has its own `RELEASE_NOTES.md` file that is updated before releases. Release workflows automatically extract the relevant version section for GitHub Releases. -- Follow [Keep a Changelog](https://keepachangelog.com/) format -- Group changes under: Added, Changed, Deprecated, Removed, Fixed, Security -- Include date for each release version -- Document all significant changes, not just code changes +**When preparing a release:** + +1. Use `just release::draft-notes ` to generate draft notes from commits +2. Edit the component's `RELEASE_NOTES.md` file, adding a new version section at the top +3. Follow the format: `## Version X.Y.Z (YYYY-MM-DD)` with `### Added`, `### Changed`, `### Fixed` sections + +**Release notes files:** + +| Component | File | +| ---------------------------- | ------------------------------------------------ | +| MCP Server | `src/freecad_mcp/RELEASE_NOTES.md` | +| Robust MCP Bridge Workbench | `addon/FreecadRobustMCPBridge/RELEASE_NOTES.md` | +| Cut Object for Magnets Macro | `macros/Cut_Object_for_Magnets/RELEASE_NOTES.md` | +| Multi Export Macro | `macros/Multi_Export/RELEASE_NOTES.md` | --- @@ -1707,4 +1763,4 @@ When working on this project, ALWAYS: - [ ] Run `just all` before finishing - everything must pass - [ ] Use latest stable library versions - [ ] Follow security best practices -- [ ] Update `CHANGELOG.md` with session changes +- [ ] Update component `RELEASE_NOTES.md` files before releases diff --git a/Dockerfile b/Dockerfile index 6625ae6..dadae37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,8 @@ RUN --mount=type=cache,target=/root/.cache/pip \ pip install --no-cache-dir --no-compile uv # Copy only dependency files first for better layer caching -COPY pyproject.toml README.md ./ +# Include uv.lock for reproducible builds with locked dependency versions +COPY pyproject.toml uv.lock README.md ./ COPY src/ ./src/ # Version for setuptools-scm when building without git (e.g., in Docker) @@ -44,12 +45,12 @@ COPY src/ ./src/ ARG VERSION=0.0.0.dev0 ENV SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} -# Create virtual environment and install dependencies +# Create virtual environment and install dependencies using locked versions # Using uv cache mount for faster rebuilds +# --frozen ensures uv.lock is used exactly without updates RUN --mount=type=cache,target=/root/.cache/uv \ uv venv /opt/venv && \ - . /opt/venv/bin/activate && \ - uv pip install --no-compile . + UV_PROJECT_ENVIRONMENT=/opt/venv uv sync --frozen --no-dev --no-editable # ============================================================================= # Stage 2: Runtime - Minimal image for running the server @@ -77,12 +78,17 @@ RUN apk upgrade --no-cache RUN addgroup -g 1000 mcpuser && \ adduser -u 1000 -G mcpuser -s /bin/sh -D mcpuser -# Upgrade system pip to fix CVE-2025-8869 (defense-in-depth) -# Note: Although PATH prefers /opt/venv/bin, we upgrade the system pip at -# /usr/local/bin/pip intentionally. This ensures no vulnerable pip exists -# in the image, even if the venv is bypassed or pip is invoked directly. +# Remove pip, setuptools, and wheel from system Python to fix CVEs +# - The base image has pip with CVE-2025-8869 +# - setuptools vendors jaraco.context 5.3.0 with GHSA-58pv-8j8x-9vj2 +# Since we use a pre-built venv, we don't need these in system Python at runtime. +# This eliminates the vulnerabilities without affecting functionality. # hadolint ignore=DL3013 -RUN pip install --no-cache-dir --upgrade "pip>=25.3" +RUN pip uninstall -y pip setuptools wheel 2>/dev/null || true && \ + rm -rf /usr/local/lib/python3.11/site-packages/pip* \ + /usr/local/lib/python3.11/site-packages/setuptools* \ + /usr/local/lib/python3.11/site-packages/wheel* \ + /usr/local/lib/python3.11/site-packages/pkg_resources* # Copy virtual environment from builder COPY --from=builder /opt/venv /opt/venv diff --git a/addon/FreecadRobustMCPBridge/RELEASE_NOTES.md b/addon/FreecadRobustMCPBridge/RELEASE_NOTES.md new file mode 100644 index 0000000..e967422 --- /dev/null +++ b/addon/FreecadRobustMCPBridge/RELEASE_NOTES.md @@ -0,0 +1,69 @@ +# Robust MCP Bridge Workbench Release Notes + +## Version 0.6.1 (2026-01-12) + +Release notes for changes between v0.5.0-beta and v0.6.1. + +### Major Change: Macro to Workbench + +The MCP Bridge has been completely rewritten from a simple macro (`Start MCP Bridge`) to a full **FreeCAD Workbench**. This provides: + +- Native FreeCAD Addon Manager installation +- Integrated toolbar with start/stop/status controls +- Preferences panel for configuration +- Real-time status widget showing connection state +- Proper lifecycle management with FreeCAD + +### Added + +- **FreeCAD Workbench**: Full workbench with toolbar, icons, and menus +- **Addon Manager support**: Install directly from FreeCAD's Addon Manager +- **Preferences panel**: Configure ports, auto-start, and logging from Edit > Preferences +- **Status widget**: Real-time display of bridge status, connected clients, and uptime +- **Auto-start option**: Optionally start the bridge automatically when FreeCAD launches +- **Start/Stop commands**: Toolbar buttons and menu items to control the bridge +- **Custom icons**: Professional SVG icons for all commands and status indicators +- **GUI mode support**: Full integration with FreeCAD's 3D view for screenshots +- **Headless mode support**: Works with `freecadcmd` for automation pipelines + +### Changed + +- **Architecture**: Complete rewrite from single macro to modular workbench structure +- **Server code**: Bridge server now lives in `freecad_mcp_bridge/` package within the addon +- **Configuration**: Settings now stored in FreeCAD's preference system instead of environment variables +- **Startup behavior**: Bridge waits for GUI initialization before starting (prevents crashes) + +### Fixed + +- **GUI crash on macOS**: Fixed race condition where bridge started before Qt event loop was ready +- **Thread safety**: All FreeCAD operations now execute on the main thread via queue processor +- **Startup timeout**: Increased GUI wait timeout to 60 seconds for slow FreeCAD startups +- **Clean shutdown**: Proper cleanup of servers and threads when FreeCAD exits + +### Removed + +- **Start MCP Bridge macro**: Replaced by the workbench (macro no longer needed) + +### Installation + +**Via FreeCAD Addon Manager (Recommended):** + +1. Open FreeCAD +2. Go to Tools > Addon Manager +3. Search for "Robust MCP Bridge" +4. Click Install +5. Restart FreeCAD + +**Manual Installation:** + +Copy the `FreecadRobustMCPBridge` folder to your FreeCAD Mod directory: + +- **macOS**: `~/Library/Application Support/FreeCAD/Mod/` +- **Linux**: `~/.local/share/FreeCAD/Mod/` +- **Windows**: `%APPDATA%/FreeCAD/Mod/` + +### Upgrade Notes + +- **Uninstall the old macro**: If you had `StartMCPBridge.FCMacro`, you can delete it +- **Auto-start disabled by default**: Enable in Preferences if you want the bridge to start automatically +- **Same ports**: Default ports remain 9875 (XML-RPC) and 9876 (JSON-RPC Socket) diff --git a/docs/development/contributing.md b/docs/development/contributing.md index f071247..c33f840 100644 --- a/docs/development/contributing.md +++ b/docs/development/contributing.md @@ -181,12 +181,13 @@ _result_ = {{"success": True}} Releases are automated via GitHub Actions: -1. Update `CHANGELOG.md` -1. Create a GitHub Release with a version tag +1. Update the component's `RELEASE_NOTES.md` file (see [Releasing](releasing.md) for details) +1. Bump versions for workbench/macros (MCP Server auto-bumps from tag) +1. Commit and push changes +1. Create release tag with `just release::tag- X.Y.Z` 1. CI builds and publishes: - - PyPI package - - Docker images - - Macro release archives + - PyPI package and Docker images (MCP Server) + - GitHub Release archives (workbench and macros) --- diff --git a/docs/development/releasing.md b/docs/development/releasing.md index 00fb318..c49bb52 100644 --- a/docs/development/releasing.md +++ b/docs/development/releasing.md @@ -12,9 +12,9 @@ just release::status # Check which components have unrele just release::changes-since mcp-server # View specific changes (or workbench, macro-magnets, macro-export) just all # Run all quality checks (must pass) -# 2. Update changelog +# 2. Update release notes just release::draft-notes mcp-server # Generate draft notes from commits -# Then manually edit CHANGELOG.md with appropriate section header +# Then edit the component's RELEASE_NOTES.md file (see "Release Notes Files" below) # 3. Version bump (workbench & macros only - MCP Server uses setuptools-scm) just release::bump-workbench 1.0.0 # or bump-macro-magnets, bump-macro-export @@ -130,9 +130,18 @@ For a more thorough check including integration tests: just all-with-integration ``` -### 4. Update CHANGELOG.md +### 4. Update Release Notes -The changelog uses a multi-component structure. Each release date section contains entries for each component that was released. +Each component has its own `RELEASE_NOTES.md` file. Release workflows automatically extract the relevant section for GitHub Releases. + +#### Release Notes Files + +| Component | Release Notes File | +| ---------------------------- | ----------------------------------------------------- | +| MCP Server | `src/freecad_mcp/RELEASE_NOTES.md` | +| Robust MCP Bridge Workbench | `addon/FreecadRobustMCPBridge/RELEASE_NOTES.md` | +| Cut Object for Magnets Macro | `macros/Cut_Object_for_Magnets/RELEASE_NOTES.md` | +| Multi Export Macro | `macros/Multi_Export/RELEASE_NOTES.md` | #### Draft Release Notes @@ -146,43 +155,36 @@ just release::draft-notes macro-magnets just release::draft-notes macro-export ``` -This categorizes commits by type (feat, fix, refactor, etc.) to help you write the changelog entry. +This categorizes commits by type (feat, fix, refactor, etc.) to help you write the release notes. -#### Changelog Structure +#### Release Notes Format -Add entries under the appropriate component section in `CHANGELOG.md`: +Add a new version section **at the top** of the component's `RELEASE_NOTES.md`: ```markdown -## YYYY-MM-DD - Release Title (optional) +## Version X.Y.Z (YYYY-MM-DD) -### MCP Server vX.Y.Z +Release notes for changes between vA.B.C and vX.Y.Z. -Brief description of the release. +### Added -#### Added - New feature description -#### Changed +### Changed + - Changed behavior description -#### Fixed +### Fixed + - Bug fix description - ---- - -### Robust MCP Bridge Workbench vX.Y.Z - -... ``` -**Important:** The changelog section header format must match exactly for the GitHub Release workflow to extract it: +**Important:** The version header format must match exactly for the GitHub Release workflow to extract it: -- MCP Server: `### MCP Server vX.Y.Z` -- Workbench: `### Robust MCP Bridge Workbench vX.Y.Z` -- Magnets Macro: `### Cut Object for Magnets Macro vX.Y.Z` -- Export Macro: `### Multi Export Macro vX.Y.Z` +- Format: `## Version X.Y.Z (YYYY-MM-DD)` +- Example: `## Version 1.0.0 (2026-01-15)` -The release workflows automatically extract the changelog section and include it in the GitHub Release body. +The release workflows automatically extract the version section and include it in the GitHub Release body. ## Releasing Each Component @@ -194,9 +196,9 @@ The MCP Server is the main Python package. It uses `setuptools-scm` to derive ve # 1. Ensure all changes are committed git status # Should show clean working tree -# 2. Update CHANGELOG.md and commit -git add CHANGELOG.md -git commit -m "docs: update changelog for MCP Server v1.0.0" +# 2. Update src/freecad_mcp/RELEASE_NOTES.md and commit +git add src/freecad_mcp/RELEASE_NOTES.md +git commit -m "docs: update release notes for MCP Server v1.0.0" # 3. Create and push the release tag just release::tag-mcp-server 1.0.0 @@ -474,7 +476,7 @@ just release::status # View changes for a component just release::changes-since mcp-server -# Draft changelog entries from commits +# Draft release notes from commits just release::draft-notes mcp-server just release::draft-notes workbench just release::draft-notes macro-magnets diff --git a/just/release.just b/just/release.just index 5ee7486..8e4f1af 100644 --- a/just/release.just +++ b/just/release.just @@ -1075,24 +1075,25 @@ draft-notes component: echo "" git log --oneline "$REV_RANGE" -- $PATHS 2>/dev/null | sed -E 's/^[a-f0-9]+ /- /' || echo "(no commits)" -# Extract changelog section for a specific component version (for GitHub Release body) +# Extract release notes section for a specific component version (for GitHub Release body) +# Reads from component-specific RELEASE_NOTES.md files extract-changelog component version: #!/usr/bin/env bash set -euo pipefail - # Match header exactly as it appears in CHANGELOG.md + # Map component to RELEASE_NOTES.md path case "{{component}}" in mcp-server|server) - HEADER="### Robust MCP Server v{{version}}" + RELEASE_NOTES="{{project_root}}/src/freecad_mcp/RELEASE_NOTES.md" ;; workbench) - HEADER="### Robust MCP Bridge Workbench v{{version}}" + RELEASE_NOTES="{{project_root}}/addon/FreecadRobustMCPBridge/RELEASE_NOTES.md" ;; macro-magnets|magnets) - HEADER="### Cut Object for Magnets Macro v{{version}}" + RELEASE_NOTES="{{project_root}}/macros/Cut_Object_for_Magnets/RELEASE_NOTES.md" ;; macro-export|export) - HEADER="### Multi Export Macro v{{version}}" + RELEASE_NOTES="{{project_root}}/macros/Multi_Export/RELEASE_NOTES.md" ;; *) echo "Unknown component: {{component}}" @@ -1100,18 +1101,21 @@ extract-changelog component version: ;; esac - CHANGELOG="{{project_root}}/CHANGELOG.md" + if [ ! -f "$RELEASE_NOTES" ]; then + echo "No RELEASE_NOTES.md found at: $RELEASE_NOTES" + exit 0 + fi - # Extract section between this version header and the next component header or separator - # Only exit on: "---" separator OR "### " followed by component name (capital letter) - # This allows #### Added, #### Changed, etc. to be included - awk -v header="$HEADER" ' + # Extract section for this version + # Format: ## Version X.Y.Z (date) + awk -v version="{{version}}" ' BEGIN { found=0 } - $0 == header || $0 == header " " { found=1; next } - found && /^---$/ { exit } - found && /^### [A-Z]/ { exit } + /^## Version / { + if (found) exit + if (index($0, version) > 0) { found=1; next } + } found { print } - ' "$CHANGELOG" + ' "$RELEASE_NOTES" # ============================================================================= # Dry Run Commands (preview without pushing) diff --git a/just/testing.just b/just/testing.just index 8b16748..c00fb3b 100644 --- a/just/testing.just +++ b/just/testing.just @@ -305,19 +305,47 @@ release-test: if [ "$TESTS_PASSED" = true ]; then echo "✓ ALL TESTS PASSED - Ready for release!" echo "" - echo "NOTE: There is no need to bump the MCP server version as it happens automatically from the release tag on git." + echo "============================================================" + echo " NEXT STEPS FOR RELEASE" + echo "============================================================" echo "" - echo "You can now create a release with:" - echo " just release::bump-workbench " - echo " just release::bump-macro-magnets " - echo " just release::bump-macro-export " + echo "1. UPDATE RELEASE NOTES (for each component you're releasing):" echo "" - echo "NOTE: Merge all code/PRs into 'main' that are in flight for the release, like the version bumps above!" + echo " Edit the RELEASE_NOTES.md file for each component:" + echo " - MCP Server: src/freecad_mcp/RELEASE_NOTES.md" + echo " - Workbench: addon/FreecadRobustMCPBridge/RELEASE_NOTES.md" + echo " - Cut Macro: macros/Cut_Object_for_Magnets/RELEASE_NOTES.md" + echo " - Export Macro: macros/Multi_Export/RELEASE_NOTES.md" echo "" - echo " just release::tag-mcp-server " - echo " just release::tag-workbench " - echo " just release::tag-macro-magnets " - echo " just release::tag-macro-export " + echo " Add a new version section at the top:" + echo " ## Version X.Y.Z (YYYY-MM-DD)" + echo " ### Added" + echo " - New feature description" + echo " ### Changed" + echo " - Change description" + echo " ### Fixed" + echo " - Bug fix description" + echo "" + echo " Tip: Use 'just release::draft-notes ' to generate draft notes" + echo " from git commits since the last release." + echo "" + echo "2. BUMP VERSIONS (for workbench/macros only - MCP server auto-bumps from tag):" + echo "" + echo " just release::bump-workbench " + echo " just release::bump-macro-magnets " + echo " just release::bump-macro-export " + echo "" + echo "3. COMMIT AND PUSH your RELEASE_NOTES.md and version bump changes" + echo "" + echo "4. CREATE RELEASE TAGS:" + echo "" + echo " just release::tag-mcp-server " + echo " just release::tag-workbench " + echo " just release::tag-macro-magnets " + echo " just release::tag-macro-export " + echo "" + echo "NOTE: The MCP server version is determined by the git tag, so no bump needed." + echo "NOTE: Release workflows extract notes from each component's RELEASE_NOTES.md." else echo "✗ SOME TESTS FAILED - Cannot proceed with release" echo "" diff --git a/macros/Cut_Object_for_Magnets/RELEASE_NOTES.md b/macros/Cut_Object_for_Magnets/RELEASE_NOTES.md new file mode 100644 index 0000000..c30096d --- /dev/null +++ b/macros/Cut_Object_for_Magnets/RELEASE_NOTES.md @@ -0,0 +1,42 @@ +# Cut Object for Magnets Macro Release Notes + +## Version 0.6.1 (2026-01-12) + +Release notes for changes between v0.5.0-beta and v0.6.1. + +### Added + +- **FreeCAD Addon Manager metadata**: Macro now includes standard metadata fields for better Addon Manager integration +- **FreeCAD Wiki page**: Official wiki documentation at [Macro Cut Object for Magnets](https://wiki.freecad.org/Macro_Cut_Object_for_Magnets) +- **Example images**: Added screenshots showing the dialog and example output + +### Changed + +- **Version tracking**: Version now managed via `__Version__` metadata field instead of inline comment + +### Fixed + +- **Ring-shaped objects**: Improved handling of inset point calculation for hollow/ring-shaped cut faces where the inset point could land in the hole +- **Code formatting**: Minor cleanup for consistent code style + +### No Functional Changes + +The core cutting algorithm, magnet hole placement, and collision detection remain unchanged from the beta release. This macro is stable for production use. + +### Installation + +**Via FreeCAD Addon Manager:** + +1. Open FreeCAD +2. Go to Macro > Macros... +3. Click "Download" tab +4. Search for "Cut Object for Magnets" +5. Click Install + +**Manual Installation:** + +Copy `CutObjectForMagnets.FCMacro` to your FreeCAD macro directory: + +- **macOS**: `~/Library/Application Support/FreeCAD/Macro/` +- **Linux**: `~/.local/share/FreeCAD/Macro/` +- **Windows**: `%APPDATA%/FreeCAD/Macro/` diff --git a/macros/Multi_Export/RELEASE_NOTES.md b/macros/Multi_Export/RELEASE_NOTES.md new file mode 100644 index 0000000..faed6e0 --- /dev/null +++ b/macros/Multi_Export/RELEASE_NOTES.md @@ -0,0 +1,45 @@ +# Multi Export Macro Release Notes + +## Version 0.6.1 (2026-01-12) + +Release notes for changes between v0.5.0-beta and v0.6.1. + +### Added + +- **FreeCAD Addon Manager metadata**: Macro now includes standard metadata fields for better Addon Manager integration +- **FreeCAD Wiki page**: Official wiki documentation at [Macro Multi Export](https://wiki.freecad.org/Macro_Multi_Export) +- **Dialog screenshot**: Added screenshot showing the export dialog + +### Changed + +- **Minimum FreeCAD version**: Updated requirement from FreeCAD 0.19 to FreeCAD 0.21 (for better 3MF support) +- **Version tracking**: Version now managed via `__Version__` metadata field instead of inline comment + +### No Functional Changes + +The core export functionality remains unchanged from the beta release: + +- Multi-format export (STL, STEP, 3MF, OBJ, IGES, BREP, PLY, AMF) +- Configurable mesh tolerance for tessellated formats +- Batch export with progress feedback +- Format-specific options + +This macro is stable for production use. + +### Installation + +**Via FreeCAD Addon Manager:** + +1. Open FreeCAD +2. Go to Macro > Macros... +3. Click "Download" tab +4. Search for "Multi Export" +5. Click Install + +**Manual Installation:** + +Copy `MultiExport.FCMacro` to your FreeCAD macro directory: + +- **macOS**: `~/Library/Application Support/FreeCAD/Macro/` +- **Linux**: `~/.local/share/FreeCAD/Macro/` +- **Windows**: `%APPDATA%/FreeCAD/Macro/` diff --git a/pyproject.toml b/pyproject.toml index 6183f10..cdb1597 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ Homepage = "https://github.com/spkane/freecad-robust-mcp-and-more" Documentation = "https://github.com/spkane/freecad-robust-mcp-and-more#readme" Repository = "https://github.com/spkane/freecad-robust-mcp-and-more.git" Issues = "https://github.com/spkane/freecad-robust-mcp-and-more/issues" -Changelog = "https://github.com/spkane/freecad-robust-mcp-and-more/blob/main/CHANGELOG.md" +"Release Notes" = "https://github.com/spkane/freecad-robust-mcp-and-more/blob/main/src/freecad_mcp/RELEASE_NOTES.md" "Docker Hub" = "https://hub.docker.com/r/spkane/freecad-robust-mcp" [tool.hatch.version] diff --git a/src/freecad_mcp/RELEASE_NOTES.md b/src/freecad_mcp/RELEASE_NOTES.md new file mode 100644 index 0000000..7cba9ea --- /dev/null +++ b/src/freecad_mcp/RELEASE_NOTES.md @@ -0,0 +1,50 @@ +# Robust MCP Server Release Notes + +## Version 0.6.1 (2026-01-12) + +Release notes for changes between v0.5.0-beta and v0.6.1. + +### Added + +- **Instance ID tracking**: Each server instance now has a unique UUID for identifying connections +- **Lifespan management**: Proper async lifecycle handling for bridge connections +- **Enhanced logging**: Improved startup logging with connection mode, transport type, and FreeCAD version +- **Connection validation**: Server validates FreeCAD bridge connection at startup with clear error messages + +### Changed + +- **Renamed from "FreeCAD MCP" to "Robust MCP Server"**: Reflects the broader scope and stability improvements +- **Bridge architecture refactored**: Cleaner separation between MCP server and FreeCAD bridge components +- **Plugin code moved to workbench**: The embedded FreeCAD plugin (`freecad_plugin/`) is now part of the separate Robust MCP Bridge Workbench addon +- **Simplified tool registration**: Tools now use consistent async patterns with typed callbacks +- **Export tools improved**: Better error handling and format validation for STEP, STL, 3MF, OBJ, IGES exports + +### Removed + +- **Docker detection fields**: Removed `in_docker` and `docker_container_id` from `get_mcp_server_environment()` tool (unreliable detection) +- **Embedded plugin code**: The `freecad_plugin/` directory moved to the workbench addon for cleaner separation + +### Fixed + +- **XML-RPC connection handling**: Improved timeout and retry logic for bridge connections +- **Console output capture**: More reliable capture of FreeCAD console messages +- **Type annotations**: Added proper type hints throughout for better IDE support + +### Installation + +```bash +# PyPI (recommended) +pip install freecad-robust-mcp + +# Or with uv +uv tool install freecad-robust-mcp + +# Docker (use 'latest' or a specific version tag) +docker pull spkane/freecad-robust-mcp:latest +``` + +### Upgrade Notes + +- The server now requires the **Robust MCP Bridge Workbench** to be installed in FreeCAD (replaces the old Start MCP Bridge macro) +- Environment variable names unchanged (`FREECAD_MODE`, `FREECAD_SOCKET_HOST`, etc.) +- All existing MCP tools remain compatible diff --git a/uv.lock b/uv.lock index 1098e91..7b11792 100644 --- a/uv.lock +++ b/uv.lock @@ -561,11 +561,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.2" +version = "3.20.3" source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" } -sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" } +sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ - { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" }, + { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] @@ -833,14 +833,14 @@ wheels = [ [[package]] name = "jaraco-context" -version = "6.0.2" +version = "6.1.0" source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" } dependencies = [ { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/8d/7d/41acf8e22d791bde812cb6c2c36128bb932ed8ae066bcb5e39cb198e8253/jaraco_context-6.0.2.tar.gz", hash = "sha256:953ae8dddb57b1d791bf72ea1009b32088840a7dd19b9ba16443f62be919ee57", size = 14994, upload-time = "2025-12-24T19:21:35.784Z" } +sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/cb/9c/a788f5bb29c61e456b8ee52ce76dbdd32fd72cd73dd67bc95f42c7a8d13c/jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f", size = 15850, upload-time = "2026-01-13T02:53:53.847Z" } wheels = [ - { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/c7/0c/1e0096ced9c55f9c6c6655446798df74165780375d3f5ab5f33751e087ae/jaraco_context-6.0.2-py3-none-any.whl", hash = "sha256:55fc21af4b4f9ca94aa643b6ee7fe13b1e4c01abf3aeb98ca4ad9c80b741c786", size = 6988, upload-time = "2025-12-24T19:21:34.557Z" }, + { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl", hash = "sha256:a43b5ed85815223d0d3cfdb6d7ca0d2bc8946f28f30b6f3216bda070f68badda", size = 7065, upload-time = "2026-01-13T02:53:53.031Z" }, ] [[package]] @@ -2633,16 +2633,16 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.36.0" +version = "20.36.1" source = { registry = "https://pkgs.safetycli.com/repository/techlabssh/pypi/simple/" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/78/49/87e23d8f742f10f965bce5d6b285fc88a4f436b11daf6b6225d4d66f8492/virtualenv-20.36.0.tar.gz", hash = "sha256:a3601f540b515a7983508113f14e78993841adc3d83710fa70f0ac50f43b23ed", size = 6032237, upload-time = "2026-01-07T17:20:04.975Z" } +sdist = { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } wheels = [ - { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/eb/6a/0af36875e0023a1f2d0b66b4051721fc26740e947696922df1665b75e5d3/virtualenv-20.36.0-py3-none-any.whl", hash = "sha256:e7ded577f3af534fd0886d4ca03277f5542053bedb98a70a989d3c22cfa5c9ac", size = 6008261, upload-time = "2026-01-07T17:20:02.87Z" }, + { url = "https://pkgs.safetycli.com/package/techlabssh/pypi/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, ] [[package]]