diff --git a/.claude/skills/developer-standards/SKILL.md b/.claude/skills/developer-standards/SKILL.md new file mode 100644 index 00000000..6e76f6b5 --- /dev/null +++ b/.claude/skills/developer-standards/SKILL.md @@ -0,0 +1,27 @@ +--- +name: developer-standards +description: > + Use when planning new code, writing code, or reviewing code. + Loads project code guidelines and quality gates from CONTRIBUTING.md and CLAUDE.md. +--- + +Use this skill when: +- You are planning new code, writing code, or reviewing code + +## Steps + +### 1 — Read code guidelines + +Read `CONTRIBUTING.md` for code guidelines: naming conventions, file structure, logging standards, and test conventions. + +### 2 — Read quality gates + +Read `CLAUDE.md` for quality gates and operational conventions specific to this repo. + +### 3 — Read `.editorconfig` + +Read `.editorconfig` and treat it as the authoritative code style specification. Follow every rule +exactly as written — including indentation style, tab width, line endings, charset, trailing +whitespace, final newlines, and any file-type-specific sections. Do not override any setting. + +Apply all three sets of standards to every file you write or review. diff --git a/.claude/skills/ensure-working-branch/SKILL.md b/.claude/skills/ensure-working-branch/SKILL.md new file mode 100644 index 00000000..0cbacf98 --- /dev/null +++ b/.claude/skills/ensure-working-branch/SKILL.md @@ -0,0 +1,111 @@ +--- +name: ensure-working-branch +description: > + Ensures the repository is on the correct working branch for a task, creating it from the + correct base branch if it does not yet exist. + Use this skill before reading or writing any repository files to confirm the branch is ready. +argument-hint: +--- + +Use this skill when: +- You are about to write code or modify files and need to be on the correct working branch + +Do NOT use this skill when: +- You already know the working branch is checked out and up to date + +## Arguments + +- First argument — a **work-item-id** (e.g. `ADR-123`, `Issue-444`). Required. + +## Determining work-item-type + +Derive `work-item-type` from the `work-item-id` pattern (see `identify-project-work-items`): + +| work-item-id pattern | work-item-type | +|---|---| +| `ADR-\d+` (Jira key) | `jira` | +| `Issue-\d+` (GitHub issue) | `github` | + +## Steps + +### 1 — Compute the working branch name + +The working branch is always `dev/claude/`. Derive it directly from the ID. + +### 2 — Determine the base branch + +#### 2a — Search the repo for a spec file + +Search the repository for `_spec_*.md` files that contain the work-item-id: + +```bash +grep -rl "" . --include="_spec_*.md" +``` + +If a spec file is found, read it and look for a Jira key (pattern `[A-Z]+-\d+`) that +appears in a heading or field labelled `Epic:`, `Parent:`, or `Epic ID:`. That key is the +Epic ID. + +#### 2b — Query Jira if no Epic ID found in spec + +If no Epic ID was found in step 2a and `work-item-type` is `jira`: + +Call `mcp__jira__getJiraIssue` with the `work-item-id`. Look for a `parent` or `epic` +field on the returned issue and extract its key (e.g. `ADR-200`). That key is the Epic +ID. If the issue has no parent or the parent is not a Jira key, continue with no Epic ID. + +#### 2c — Find the epic feature branch + +If an Epic ID was found in step 2a or 2b, search remote branches for it: + +```bash +git fetch origin +git branch -r | grep "feature/" +``` + +Strip the `origin/` prefix from the matching branch name (e.g. `feature/ADR-200-infrastructure`). That is the base branch. If more than one branch matches, prefer the one most recently pushed. + +#### 2d — Fallback: nearest ancestor feature branch + +If no Epic ID was found and step 2c was not reached, check for the nearest ancestor +`feature/*` remote branch: + +```bash +git branch -r --merged HEAD | grep "feature/" +``` + +Use the closest ancestor `feature/*` branch as the base branch. + +#### 2e — Fallback or error + +If no `feature/*` branch has been found: +- `work-item-type` is `jira`: stop and report an error — a feature branch is required for + Jira tasks. +- Otherwise: use `main` as the base branch. + +### 3 — Prepare the working branch + +Fetch the latest state from the remote: + +```bash +git fetch origin +``` + +If the working branch already exists locally or on the remote, check it out and pull: + +```bash +git checkout dev/claude/ +git pull origin dev/claude/ +``` + +If the working branch does not yet exist, create it from the base branch: + +```bash +git checkout -b dev/claude/ origin/ +``` + +--- + +If all steps complete successfully, respond with one word: `successful` + +If any step fails, stop and report the failure in detail. diff --git a/.claude/skills/find-repo-documentation/SKILL.md b/.claude/skills/find-repo-documentation/SKILL.md new file mode 100644 index 00000000..d11d7ca3 --- /dev/null +++ b/.claude/skills/find-repo-documentation/SKILL.md @@ -0,0 +1,39 @@ +--- +name: find-repo-documentation +description: > + Use when you need to learn the architecture from documentation in this repo. + Discovers available architecture docs and reads the ones relevant to the current task. +argument-hint: +--- + +Use this skill when: +- You need to learn the architecture from documentation in this repo + +## File format and locations + +Architecture documents are named `_doc_*.md` and live at the repo root and in subdirectories. Each one starts with a `Summary:` line that describes what it covers. + +## Steps + +### 1 — Discover available docs + +Find all architecture documents and their summaries in one pass: + +```bash +grep -rn "^Summary:" . --include="_doc_*.md" +``` + +Each output line contains a file path and its `Summary:` value. Use this output directly to build your list of available docs and their topics — no additional reads are required for this step. + +### 2 — Select relevant docs + +From the task context or area you have been given, identify which subsystems and topics the task will touch. Select the docs whose summaries match those areas. + +### 3 — Read and summarize + +Read each selected doc in full. For each one, note: +- File path +- What it says about the areas this task will touch +- Any constraints, patterns, or conventions the implementer must follow + +Internalize these findings as working context for the current task. For each doc consulted, note its file path and the constraints, patterns, or conventions that apply to the work ahead. diff --git a/.claude/skills/identify-project-work-items/SKILL.md b/.claude/skills/identify-project-work-items/SKILL.md new file mode 100644 index 00000000..62463b15 --- /dev/null +++ b/.claude/skills/identify-project-work-items/SKILL.md @@ -0,0 +1,40 @@ +--- +name: identify-project-work-items +description: > + Defines the work item patterns for this project. + Use this skill when you need to know the active work-item-id or work-item-type, which can be used to create branches, look up specs, or access work item information online. +--- + +# Work item tracking for AdaptiveRemote and related projects + +Use this skill when: +- Another skill requires `work-item-id` in `ADR-###` or `Issue-###` format + +Do NOT use this skill when: +- You already know the `work-item-id` and `work-item-type` that is under active development + +This project uses Jira for work item planning and GitHub issues for tracking bugs and public discussions. The project prefix for all Jira work items is `ADR-`. + + +## Patterns for recognizing work items + +Look for the following patterns in user input or previous discussion to identify the work item that is under active development. + +| Example patterns | Canonical `work-item-id` | `work-item-type` | `numeric-id` | +|---|---|---|---| +| ADR-123, Task 123, Epic 123, Jira 123 | `ADR-123` | `jira` | `123` | +| `#42`, `Issue 42`, `GitHub 42` | `Issue-42` | `github` | `42` | + +Note: The canonical `work-item-id` is used by other skills to create git branch names and file paths. The `Issue-42` pattern is used for GitHub because the GitHub standard `#42` would not be valid in those contexts. + +## Output + +Return these fields as a short structured block: + +``` +work-item-id: ADR-123 +work-item-type: jira +numeric-id: 123 +``` + +If the input does not contain any matches for any known pattern, stop and ask: `What work item do you want to work on?` \ No newline at end of file diff --git a/.claude/skills/spec-task-work-items/SKILL.md b/.claude/skills/spec-task-work-items/SKILL.md new file mode 100644 index 00000000..471d160b --- /dev/null +++ b/.claude/skills/spec-task-work-items/SKILL.md @@ -0,0 +1,30 @@ +--- +name: spec-task-work-items +description: > + Use when you are writing a new spec or a new part of an existing spec. + Updates the Jira epic and task descriptions with summaries after the spec is finalized. +argument-hint: +--- + +Use this skill when: +- You are writing a new spec or a new part of an existing spec +- The spec is finalized and Jira work items need to reflect the decisions + +## Steps + +### 1 — Update the Jira epic description + +If `work-with-Jira-tasks` is listed as an available skill, invoke it to update the Jira epic's description. Otherwise, call `mcp__jira__editJiraIssue` directly. Update the Jira epic's description with a concise summary of the finalized design decisions from the spec: + +- Replace the original description (which typically contains early design thoughts) with a brief overview and a bulleted list of the key decisions and their outcomes +- Include a link to the spec file in the repo + +### 2 — Update task descriptions + +For each Jira task created from the spec, update its description to reflect the finalized content: + +- One-paragraph overview of what the task implements +- Bulleted list of key decisions and their outcomes relevant to this task +- Reference to the spec section: `See spec: ` + +Replace initial notes or placeholders entirely — do not append. diff --git a/.claude/skills/write-e2e-test/SKILL.md b/.claude/skills/write-e2e-test/SKILL.md new file mode 100644 index 00000000..40f59fbd --- /dev/null +++ b/.claude/skills/write-e2e-test/SKILL.md @@ -0,0 +1,40 @@ +--- +name: write-e2e-test +description: > + Use when you are writing E2E tests. + Establishes where to put feature files, how to write Gherkin scenarios, and how step definitions should be structured. +--- + +Use this skill when: +- You are writing E2E tests + +## Location + +Write new feature files in the headless E2E host: + +``` +test/AdaptiveRemote.EndToEndTests.Host.Headless/Features/ +``` + +Follow all conventions in `test/_doc_EndToEndTests.md`. + +## Scenario writing rules + +**Use existing steps whenever possible.** Before writing a new step, search for existing step definitions and their patterns: + +```bash +grep -rEn "\[(Given|When|Then)\(" test/ --include="*.cs" +``` + +**Write generalized step phrasing.** Each `Given`, `When`, and `Then` step must describe something a human could do or observe manually — not an internal implementation detail. + +- Good: `When the user opens the settings panel` +- Bad: `When SettingsViewModel.OpenCommand is executed` + +**One scenario per behaviour.** Keep scenarios focused. A scenario that covers multiple independent behaviours is harder to diagnose when it fails. + +**Represent the correct behaviour.** For bug investigations, first write the scenario to observe the bad behaviour (it should pass), then modify it to assert the correct behaviour (it should now fail). This failing test is the investigation anchor. + +## Step definition rules + +Step definitions must delegate logic to test service methods — they must not contain application logic. The step definition's only job is to translate the human-readable step into a call to the appropriate service method, verifying inputs have valid values when necessary. diff --git a/.claude/skills/write-repo-documentation/SKILL.md b/.claude/skills/write-repo-documentation/SKILL.md new file mode 100644 index 00000000..2a8443f8 --- /dev/null +++ b/.claude/skills/write-repo-documentation/SKILL.md @@ -0,0 +1,45 @@ +--- +name: write-repo-documentation +description: > + Use when you are drafting or updating architecture documentation in this repo. + Establishes where to put new documents, what they must contain, and the expected structure. +--- + +Use this skill when: +- You are drafting or updating architecture documentation in this repo + +## File naming and location + +Architecture documents are named `_doc_.md` in PascalCase and live in the directory of the code they describe — not at the repo root. + +Each `_spec_*.md` file includes a status note indicating it will become a `_doc_` file once implementation is complete: + +``` +> **Will become:** `_doc_.md` once implementation is complete +``` + +When implementation is done, rename the spec file to `_doc_.md` and remove the `> **Will become:** ...` status line. + +## Required content + +Every `_doc_*.md` must start with a `Summary:` line — a single line description of what the document covers. This is used by tooling and other skills to discover relevant docs: + +``` +Summary: +``` + +After the summary, include: + +- **Overview:** what this subsystem does and why it exists +- **Responsibilities & Boundaries:** what it owns, what it delegates, what it integrates with +- **Key Design Decisions:** decisions already made and their trade-offs — use the same ADR format as the spec +- **Key Classes / Interfaces:** the public surface — classes, interfaces, and their responsibilities +- **Data Flow:** how data moves through the subsystem + +Omit sections that don't apply. Do not add sections not listed here. Do not go into implementation details, just link to the source files for implementation details. + +## Updating an existing doc + +When an implementation changes the design described in an existing `_doc_` file, update the doc as part of the same PR. A PR that changes a subsystem without updating its doc is incomplete. + +If a new interface, responsibility, or dependency is added, reflect it in the doc. If a decision is reversed, update the Key Design Decisions section and note why. diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..e01bf2a8 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,85 @@ +FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 +# Includes: vscode user (UID 1000) with passwordless sudo, git, curl, wget, jq, etc. + +# 1. GitHub CLI +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + -o /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] \ + https://cli.github.com/packages stable main" \ + > /etc/apt/sources.list.d/github-cli.list \ + && apt-get update && apt-get install -y --no-install-recommends gh \ + && rm -rf /var/lib/apt/lists/* + +# 2. .NET 10 SDK + PowerShell +# global.json pins sdk.version 10.0.100 with rollForward:latestFeature. +# pwsh is required to run playwright.ps1 on Linux. +RUN curl -fsSL https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb \ + -o /tmp/packages-microsoft-prod.deb \ + && dpkg -i /tmp/packages-microsoft-prod.deb && rm /tmp/packages-microsoft-prod.deb \ + && apt-get update \ + && apt-get install -y --no-install-recommends dotnet-sdk-10.0 powershell \ + && rm -rf /var/lib/apt/lists/* + +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \ + DOTNET_NOLOGO=1 + +# 3. Playwright OS-level Chromium dependencies +# Baked into the image so the heavy apt work is cached. +# playwright.ps1 install --with-deps in post-create will be a no-op for these. +# Ubuntu 24.04 renamed libasound2 → libasound2t64. +RUN apt-get update && apt-get install -y --no-install-recommends \ + libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libdbus-1-3 \ + libxkbcommon0 libatspi2.0-0 libx11-6 libxcomposite1 libxdamage1 libxext6 \ + libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2t64 \ + libxshmfence1 fonts-liberation fonts-noto-color-emoji xdg-utils \ + && rm -rf /var/lib/apt/lists/* + +# 4. Node.js LTS (for Claude Code CLI) +# Explicit keyring + pinned node_24.x instead of curl|bash for auditability. +RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ + | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/nodesource.gpg] \ + https://deb.nodesource.com/node_24.x nodistro main" \ + > /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +# 5. Claude Code CLI (global npm install, accessible to all users) +RUN npm install -g @anthropic-ai/claude-code + +# 6. Python ML stack in isolated venv at /opt/ml-env +# World-readable so the vscode user can use python/pip from it. +# python3-venv is a separate package from python3 on Debian/Ubuntu. +COPY ml/requirements.txt /tmp/ml-requirements.txt +RUN apt-get update \ + && apt-get install -y --no-install-recommends python3-venv \ + && rm -rf /var/lib/apt/lists/* \ + && python3 -m venv /opt/ml-env \ + && /opt/ml-env/bin/pip install --upgrade pip setuptools wheel \ + && /opt/ml-env/bin/pip install -r /tmp/ml-requirements.txt \ + && /opt/ml-env/bin/pip cache purge \ + && rm /tmp/ml-requirements.txt \ + && chmod -R a+rX /opt/ml-env + +RUN echo 'export PATH="/opt/ml-env/bin:$PATH"' >> /etc/profile.d/ml-env.sh \ + && chmod +x /etc/profile.d/ml-env.sh +ENV PATH="/opt/ml-env/bin:$PATH" + +COPY --chmod=755 .devcontainer/git-askpass.sh /usr/local/bin/git-askpass + +USER vscode + +# Create ~/.claude as vscode so Docker initialises the named volume with the +# correct ownership. Without this the volume root is owned by root and the +# vscode user cannot write auth credentials after login. +RUN mkdir -p /home/vscode/.claude + +# Pre-bake Claude Code user settings template into the image at a path outside +# the named volume. post-create.sh copies it to ~/.claude/settings.json on first +# container creation, so the plugin marketplace and permissions are pre-configured. +COPY --chown=vscode:vscode .devcontainer/claude-settings.json /home/vscode/.claude-defaults/settings.json + +WORKDIR /workspaces/AdaptiveRemote +SHELL ["/bin/bash", "-l", "-c"] diff --git a/.devcontainer/claude-settings.json b/.devcontainer/claude-settings.json new file mode 100644 index 00000000..bfbd686b --- /dev/null +++ b/.devcontainer/claude-settings.json @@ -0,0 +1,36 @@ +{ + "permissions": { + "allow": [ + "Bash(git:*)", + "Bash(dotnet:*)", + "Bash(npm:*)", + "Bash(node:*)", + "Bash(pwsh:*)", + "Bash(find:*)", + "Bash(grep:*)", + "Bash(ls:*)", + "Bash(xargs:*)", + "Bash(mkdir:*)", + "Bash(cp:*)", + "Bash(mv:*)", + "Bash(cat:*)", + "Bash(echo:*)", + "Bash(curl:*)", + "Bash(gh:*)", + "Bash(jq:*)", + "Bash(sed:*)", + "Bash(awk:*)", + "Bash(sort:*)", + "Bash(head:*)", + "Bash(tail:*)", + "Bash(wc:*)", + "WebFetch(*)", + "WebSearch(*)", + "Monitor", + "mcp__microsoft-learn__*", + "mcp__github__*", + "mcp__jira__*", + "mcp__claude_ai_Atlassian_Rovo__*" + ] + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..6516b708 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +{ + "name": "AdaptiveRemote Dev Container", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "workspaceFolder": "/workspaces/AdaptiveRemote", + "mounts": [ + "source=adaptiveremote-claude-home,target=/home/vscode/.claude,type=volume" + ], + "remoteUser": "vscode", + "postCreateCommand": "bash .devcontainer/post-create.sh", + "containerEnv": { + "DOTNET_CLI_TELEMETRY_OPTOUT": "1", + "DOTNET_NOLOGO": "1", + "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}", + "GH_TOKEN": "${localEnv:GH_TOKEN}", + "GIT_ASKPASS": "/usr/local/bin/git-askpass" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "python.defaultInterpreterPath": "/opt/ml-env/bin/python3" + }, + "extensions": [ + "ms-dotnettools.csdevkit", + "ms-python.python" + ] + } + } +} diff --git a/.devcontainer/git-askpass.sh b/.devcontainer/git-askpass.sh new file mode 100644 index 00000000..ac23dc85 --- /dev/null +++ b/.devcontainer/git-askpass.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Git ASKPASS helper: supplies GITHUB_TOKEN for all password prompts. +# Git calls this script with a prompt string; we return the token for +# password prompts and a placeholder for username prompts. +case "$1" in + Username*) echo "x-token-auth" ;; + Password*) echo "${GITHUB_TOKEN}" ;; +esac diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 00000000..6e6450f0 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Runs once after container creation and workspace mount (as the vscode user). +# Heavy deps are already in the image; this only handles repo-dependent steps. +set -euo pipefail + +WORKSPACE_DIR="/workspaces/AdaptiveRemote" +cd "$WORKSPACE_DIR" + +echo "==> [post-create] Step 1: Restoring NuGet packages..." +dotnet restore + +echo "==> [post-create] Step 2: Building solution (emits playwright.ps1)..." +# /warnaserror omitted intentionally: this is not a quality gate. +dotnet build --no-restore + +echo "==> [post-create] Step 3: Installing Playwright Chromium browser..." +PLAYWRIGHT_PS1="$WORKSPACE_DIR/src/AdaptiveRemote.Headless/bin/Debug/net10.0/playwright.ps1" + +if [ -f "$PLAYWRIGHT_PS1" ]; then + pwsh "$PLAYWRIGHT_PS1" install chromium --with-deps +else + echo "==> [post-create] WARNING: playwright.ps1 not found; falling back to dotnet tool..." + dotnet tool install --global Microsoft.Playwright.CLI 2>/dev/null || true + # Global tools install to ~/.dotnet/tools which may not be on PATH yet. + export PATH="$HOME/.dotnet/tools:$PATH" + playwright install chromium --with-deps +fi + +echo "==> [post-create] Step 4: Seeding Claude Code user settings (first-run only)..." +CLAUDE_SETTINGS="$HOME/.claude/settings.json" +CLAUDE_DEFAULTS="/home/vscode/.claude-defaults/settings.json" + +if [ ! -f "$CLAUDE_SETTINGS" ]; then + cp "$CLAUDE_DEFAULTS" "$CLAUDE_SETTINGS" + echo "==> [post-create] Installed default settings to ~/.claude/settings.json" +else + echo "==> [post-create] ~/.claude/settings.json already exists — not overwriting." +fi + +echo "==> [post-create] Done. Quality gates:" +echo " scripts/validate-build.sh — clean build, zero warnings" +echo " scripts/validate-tests.sh — unit + headless E2E tests" diff --git a/.github/workflows/devcontainer-build.yml b/.github/workflows/devcontainer-build.yml new file mode 100644 index 00000000..a7a2eb89 --- /dev/null +++ b/.github/workflows/devcontainer-build.yml @@ -0,0 +1,65 @@ +name: Devcontainer Validate + +on: + push: + branches: [main] + pull_request: + +jobs: + gate: + runs-on: ubuntu-latest + outputs: + run: ${{ steps.decide.outputs.run }} + steps: + - uses: actions/checkout@v4 + + - name: Decide whether validation is needed + id: decide + env: + BASE_REF: ${{ github.base_ref }} + EVENT_NAME: ${{ github.event_name }} + run: | + if [[ "$EVENT_NAME" == "push" ]] || [[ "$BASE_REF" == "main" ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + git fetch origin "$BASE_REF" --depth=1 + if git diff --name-only "origin/$BASE_REF" HEAD \ + | grep -qE '^(\.devcontainer/|ml/requirements\.txt$|Directory\.Packages\.props$)'; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi + + devcontainer-validate: + needs: gate + if: needs.gate.outputs.run == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for Nerdbank.GitVersioning + + - name: Compute lowercase image name + id: image + run: | + echo "name=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')/devcontainer" >> "$GITHUB_OUTPUT" + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and validate devcontainer + uses: devcontainers/ci@v0.3 + with: + imageName: ${{ steps.image.outputs.name }} + cacheFrom: ${{ steps.image.outputs.name }} + runCmd: bash scripts/validate.sh diff --git a/.gitignore b/.gitignore index 7a8ddecc..00473d2c 100644 --- a/.gitignore +++ b/.gitignore @@ -368,4 +368,9 @@ FodyWeavers.xsd # Claude logs .claude/logs -.claude/settings.local.json + +# dotnet runfile-discovery cache (generated by Claude Code run-file tooling) +dotnet/ + +# Local configuration files +*.local.* diff --git a/ml/requirements.txt b/ml/requirements.txt index 4bdbb8d0..bf2dc3b7 100644 --- a/ml/requirements.txt +++ b/ml/requirements.txt @@ -3,3 +3,8 @@ pyyaml>=6.0 soundfile>=0.13.1 edge-tts>=6.1.9 librosa>=0.10 +numba>=0.59.0 +numpy>=1.22 +pandas==3.0.3 +tensorflow==2.21.0 +dvc==3.67.1 diff --git a/scripts/validate-build.sh b/scripts/validate-build.sh old mode 100644 new mode 100755 diff --git a/scripts/validate-tests.sh b/scripts/validate-tests.sh old mode 100644 new mode 100755 diff --git a/scripts/validate.cmd b/scripts/validate.cmd index 9fcfa9cd..19ff6f50 100644 --- a/scripts/validate.cmd +++ b/scripts/validate.cmd @@ -1,4 +1,11 @@ @echo off +echo Checking required tools... +for %%T in (dotnet pwsh node python3 claude) do ( + where %%T >nul 2>&1 || ( + echo ERROR: Required tool '%%T' is not installed or not on PATH. + exit /b 1 + ) +) call "%~dp0validate-build.cmd" if %ERRORLEVEL% neq 0 exit /b %ERRORLEVEL% call "%~dp0validate-tests.cmd" diff --git a/scripts/validate.sh b/scripts/validate.sh old mode 100644 new mode 100755 index 70a27833..384609b5 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -1,5 +1,21 @@ #!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +check_tool() { + local tool="$1" + if ! command -v "$tool" &>/dev/null; then + echo "ERROR: Required tool '$tool' is not installed or not on PATH." >&2 + exit 1 + fi +} + +echo 'Checking required tools...' +check_tool dotnet +check_tool pwsh +check_tool node +check_tool python3 +check_tool claude + "$SCRIPT_DIR/validate-build.sh" "$SCRIPT_DIR/validate-tests.sh"