diff --git a/.claude/skills/update-deploy-py/SKILL.md b/.claude/skills/update-deploy-py/SKILL.md new file mode 100644 index 00000000..f3fff1e7 --- /dev/null +++ b/.claude/skills/update-deploy-py/SKILL.md @@ -0,0 +1,63 @@ +--- +name: update-deploy-py +description: This skill should be used when the user asks to "update deploy-py", "port deploy-js to deploy-py", "sync deploy-py", "reconcile the python port", or wants to bring the deploy-py Python port back in line with this repo (deploy-js, the source of truth) and open a PR for any drift. +--- + +# Update deploy-py from deploy-js + +This repo (`deploy-js`, TypeScript) is the source of truth. `deploy-py` is a +Python port of it. When deploy-js changes, port the change into deploy-py and +open a PR there. This runs both in CI (push to `master`) and locally for a +developer driving Claude Code by hand. + +## Get latest deploy-py + +Always clone the latest default branch (`main`) of deploy-py into a fresh temp +dir. Do not reuse a local sibling checkout, which may be stale. deploy-py is the +target you edit; this repo (deploy-js) is read-only reference. + +```bash +DEPLOY_PY=$(mktemp -d) +gh repo clone mindhivenz/deploy-py "$DEPLOY_PY" -- --depth 1 --branch main +``` + +`gh` uses `GH_TOKEN` in CI and the developer's auth locally. + +## Task + +1. Identify what changed in deploy-js. In CI, look at the latest push to + `master` (`git log -p -1`, or diff against the previous commit). Locally, + review recent commits the developer points you at. Focus on `src/`. +2. Map each changed deploy-js source file/function to its deploy-py counterpart + under `$DEPLOY_PY/src/mindhivenz/deploy/`. Naming converts TS camelCase to + Python snake_case (e.g. `userRoleName.ts` -> `user_role_name.py`). +3. If deploy-py already reflects the change, STOP. Do not open a PR, do not + commit, do not create a branch. Print a short note that deploy-py is in sync + and exit. +4. Before doing any work, check deploy-py for an already-open PR from a previous + run (branch prefix `deploy-js-sync/` or PR title starting with + "Sync from deploy-js"). If one exists and already covers the change, STOP and + do nothing. +5. If there IS new drift to apply: in `$DEPLOY_PY`, create a branch named + `deploy-js-sync/`, edit only files under `$DEPLOY_PY` + (never anything in this deploy-js repo) to match the deploy-js change, then + run the tests with `uv run pytest`. Use `uv` / `uv run` for all Python + commands. +6. Commit and open a PR against `main` on deploy-py, requesting `timvan` as a + reviewer (`gh pr create --reviewer timvan`). If running locally as `timvan` + (you cannot request review from yourself), omit the flag. In the PR body, list + each ported change as: the deploy-js source (file + function), the deploy-py + target file, what changed, and the reason. Link the specific deploy-js commit + and lines on GitHub. Do not merge. + +## Constraints + +- deploy-js (this repo) is reference only. Never modify, commit to, or push it. +- Keep edits minimal and faithful to deploy-js. Port behavior, do not refactor + unrelated deploy-py code or invent functionality not present in deploy-js. +- Match deploy-py's existing idioms (typer, snake_case, project layout). Do not + transliterate TypeScript style into Python. +- Ignore changes confined to deploy-js build artifacts (`dist/`), tooling, or + packaging that have no behavioral equivalent in deploy-py. +- If tests fail and you cannot make them pass with a faithful port, open the PR + anyway as a draft and describe the failure in the body. diff --git a/.github/workflows/sync-deploy-py.yaml b/.github/workflows/sync-deploy-py.yaml new file mode 100644 index 00000000..5bf1a9ca --- /dev/null +++ b/.github/workflows/sync-deploy-py.yaml @@ -0,0 +1,53 @@ +name: Sync deploy-py from deploy-js + +# Runs on every push to master (this repo, deploy-js, is the source of truth) and +# asks Claude Code to port any source changes that have not yet been reflected in +# the deploy-py Python port. If it finds divergence, it opens a PR on deploy-py. +on: + push: + branches: + - master + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + sync-deploy-py: + runs-on: ubuntu-latest + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.MINDHIVE_CICD_GH_APP_ID }} + private-key: ${{ secrets.MINDHIVE_CICD_GH_PRIVATE_KEY }} + owner: mindhivenz + # App must be installed on both repos: + # - deploy-js: contents:read + # - deploy-py: contents:write, pull_requests:write + repositories: | + deploy-js + deploy-py + + - name: Checkout deploy-js (source of truth) + uses: actions/checkout@v4 + with: + token: ${{ steps.app-token.outputs.token }} + fetch-depth: 0 + + - name: Port changes to deploy-py and open PR + uses: anthropics/claude-code-action@v1 + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ steps.app-token.outputs.token }} + claude_args: | + --model claude-opus-4-8 + --max-turns 40 + --allowedTools Edit,Read,Write,Glob,Grep,Bash + prompt: | + Use the update-deploy-py skill to port any changes in this repo + (deploy-js, the source of truth) into the deploy-py Python port and + open a PR on deploy-py if it has drifted. Follow the skill exactly. diff --git a/script/mhd b/script/mhd index 44c5f440..6bb88b7a 100755 --- a/script/mhd +++ b/script/mhd @@ -1,32 +1,43 @@ #!/usr/bin/env bash -# Find project deploy directory and run gulp tasks in there -# Extra args are packaged into an environment var to be parsed by src/internal/args.ts +# Find project deploy directory and run deploy tasks in there +# Extra args are packaged into MHD_ARGS env var to be parsed by the task runner set -e ancestor_dir=$(git rev-parse --show-toplevel) project_dir=${ancestor_dir%%-control} cd "${project_dir}/deploy" >/dev/null + vault_opts="" mhd_args="" +task_args="" if [[ $# == 0 ]]; then - gulp_args="--tasks --compact-tasks" + task_args="" elif [[ $1 =~ ^--.* ]]; then - gulp_args="$*" + task_args="$*" else - gulp_args="$1" + task_args="$1" shift IFS=$'\t' mhd_args="$*" fi -if [[ $gulp_args =~ ^open:.* ]]; then +if [[ $task_args =~ ^open:.* ]]; then vault_opts="--duration=1h" fi +if [[ -f gulpfile.ts ]]; then + # TypeScript deploy: gulp tasks in gulpfile.ts + [[ -z $task_args ]] && task_args="--tasks --compact-tasks" + runner=(env TS_NODE_TRANSPILE_ONLY=true node_modules/.bin/gulp) +else + # Python deploy: uv + typer tasks in tasks.py + [[ -z $task_args ]] && task_args="--help" + runner=(uv run tasks.py) +fi + if [ "$CI" = "true" ]; then # shellcheck disable=SC2086 - TS_NODE_TRANSPILE_ONLY=true MHD_ARGS="$mhd_args" node_modules/.bin/gulp $gulp_args + MHD_ARGS="$mhd_args" "${runner[@]}" $task_args else # shellcheck disable=SC2086 - TS_NODE_TRANSPILE_ONLY=true MHD_ARGS="$mhd_args" aws-vault exec $vault_opts mindhive-ops -- \ - node_modules/.bin/gulp $gulp_args + MHD_ARGS="$mhd_args" aws-vault exec $vault_opts mindhive-ops -- "${runner[@]}" $task_args fi