Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .claude/skills/update-deploy-py/SKILL.md
Original file line number Diff line number Diff line change
@@ -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/<short-date-or-topic>`, 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.
53 changes: 53 additions & 0 deletions .github/workflows/sync-deploy-py.yaml
Original file line number Diff line number Diff line change
@@ -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.
29 changes: 20 additions & 9 deletions script/mhd
Original file line number Diff line number Diff line change
@@ -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
Loading