|
| 1 | +--- |
| 2 | +name: port-pr |
| 3 | +description: Port a machine (C#) PR into machine.py (Python). Given a GitHub issue number for a porting task in sillsdev/machine.py, finds the linked machine (C#) PR, ports its changes to the Python codebase, runs the local checks (black/flake8/isort/pyright/pytest), and opens a PR that closes the issue. Use when asked to port a PR/issue from machine (C#), complete a "porting" issue, or sync a machine change into machine.py. |
| 4 | +--- |
| 5 | + |
| 6 | +# Port a machine (C#) PR into machine.py (Python) |
| 7 | + |
| 8 | +`machine` (C#) and `machine.py` (Python) are direct, intentionally-synced ports of each |
| 9 | +other. This skill ports a change that already landed in `machine` into `machine.py`, |
| 10 | +driven by a "porting" issue in `sillsdev/machine.py`. |
| 11 | + |
| 12 | +**Required argument:** the GitHub issue number in `sillsdev/machine.py` (always exists). |
| 13 | + |
| 14 | +## Repos |
| 15 | + |
| 16 | +- Python target repo: the current working directory (`sillsdev/machine.py`). |
| 17 | +- C# source repo: the sibling clone at `../machine` (`sillsdev/machine`). |
| 18 | + Use the local clone for reading surrounding context; use `gh ... --repo sillsdev/machine` |
| 19 | + for authoritative PR data. |
| 20 | + |
| 21 | +## Step 1 — Read the porting issue |
| 22 | + |
| 23 | +```bash |
| 24 | +gh issue view <ISSUE> --json title,body,labels |
| 25 | +``` |
| 26 | + |
| 27 | +- The body looks like: `Port any relevant changes in https://github.com/sillsdev/machine/pull/<PR> from machine to machine.py.` |
| 28 | + Extract `<PR>` — the machine (C#) PR number — from that URL. |
| 29 | +- The issue title looks like `Port '<Title>'`. Keep `<Title>` for the branch and PR. |
| 30 | +- If the body has no machine (C#) PR link, stop and ask the user for the source PR. |
| 31 | + |
| 32 | +## Step 2 — Understand the source change |
| 33 | + |
| 34 | +```bash |
| 35 | +gh pr view <PR> --repo sillsdev/machine --json title,body,files,commits |
| 36 | +gh pr diff <PR> --repo sillsdev/machine |
| 37 | +``` |
| 38 | + |
| 39 | +Read the full diff. For each changed C# file, open the corresponding file(s) in |
| 40 | +`../machine` to understand the surrounding context, and identify the Python counterpart |
| 41 | +(see mapping below). Read the existing Python code you're about to change so the port |
| 42 | +matches local idiom. |
| 43 | + |
| 44 | +Note: not every change ports. Skip C#-only concerns (`.csproj`/`.sln`/`Directory.*.props`, |
| 45 | +`AssemblyInfo`, `omnisharp.json`, csharpier/editorconfig formatting, NuGet packaging). |
| 46 | +The issue says "any *relevant* changes" — use judgment and call out anything you |
| 47 | +intentionally skip. |
| 48 | + |
| 49 | +## Step 3 — File & API mapping |
| 50 | + |
| 51 | +| machine (C#) | machine.py (Python) | |
| 52 | +|---|---| |
| 53 | +| `src/SIL.Machine/<PascalArea>/<PascalCase>.cs` (or the matching `SIL.Machine.*` project) | `machine/<area>/<snake_case>.py` | |
| 54 | +| `tests/SIL.Machine.Tests/<PascalArea>/<PascalCase>Tests.cs` (or the matching `*.Tests` project) | `tests/<area>/test_<snake_case>.py` | |
| 55 | +| `PascalCase` methods / `camelCase` locals / `_camelCase` fields | `snake_case` functions/vars | |
| 56 | +| `IReadOnlyList<T>` / `IDictionary<,>` / `ISet<T>` etc. | `Sequence[T]`/`list` / `Mapping`/`dict` / `Set`/`set` etc. | |
| 57 | +| NUnit `Assert.That(...)` | pytest plain `assert` (check neighboring test files) | |
| 58 | +| `AssemblyInfo`/`.csproj` `<Version>` | `pyproject.toml` `version` (poetry) | |
| 59 | + |
| 60 | +The top-level Python areas are: `annotations`, `clusterers`, `corpora`, `jobs`, |
| 61 | +`optimization`, `punctuation_analysis`, `scripture`, `sequence_alignment`, `statistics`, |
| 62 | +`tokenization`, `translation`, `utils`. Some C# code lives in tool/plugin projects |
| 63 | +(`SIL.Machine.Tool`, `SIL.Machine.Translation.Thot`, `SIL.Machine.Morphology.HermitCrab`, |
| 64 | +etc.); the Python equivalent may live under `machine/jobs` or a different area, or may not |
| 65 | +exist at all. Find the Python counterpart by searching for the type/method name (translated |
| 66 | +to snake_case): `grep -ri "<type_or_method_name>" machine tests` before assuming a path. |
| 67 | + |
| 68 | +Port the **behavior**, not the syntax. Match existing Python patterns in the neighboring |
| 69 | +code (naming, type hints, dataclasses, generators vs. loops, async conventions). Port the |
| 70 | +tests too. |
| 71 | + |
| 72 | +## Step 4 — Branch & apply |
| 73 | + |
| 74 | +Create a branch off `main` (do not commit to `main`): |
| 75 | + |
| 76 | +```bash |
| 77 | +git switch main && git pull && git switch -c port-<slug> |
| 78 | +``` |
| 79 | + |
| 80 | +where `<slug>` is a short kebab-case form of the issue title (e.g. |
| 81 | +`port-update-library-version-to-1.8.11`). |
| 82 | + |
| 83 | +Apply the ported changes with Edit/Write. |
| 84 | + |
| 85 | +## Step 5 — Verify locally |
| 86 | + |
| 87 | +Install, format, lint, type-check, and test (this is `local_check.sh`): |
| 88 | + |
| 89 | +```bash |
| 90 | +poetry install |
| 91 | +poetry run black . |
| 92 | +poetry run flake8 . |
| 93 | +poetry run isort . |
| 94 | +poetry run pyright |
| 95 | +poetry run pytest |
| 96 | +``` |
| 97 | + |
| 98 | +`black` and `isort` rewrite files in place; `flake8` and `pyright` are gates that must pass |
| 99 | +clean. Fix any formatting, lint, type, or test failures before proceeding. Report the test |
| 100 | +results plainly (pass/fail counts); don't claim success if anything failed. |
| 101 | + |
| 102 | +## Step 6 — Commit, push, open PR (pause first) |
| 103 | + |
| 104 | +Show the user a summary of the diff and the proposed PR title/body, and **confirm before |
| 105 | +pushing**. Then: |
| 106 | + |
| 107 | +```bash |
| 108 | +git add -A |
| 109 | +git commit # message: Port '<Title>' from machine PR #<PR> |
| 110 | +git push -u origin port-<slug> |
| 111 | +gh pr create --title "Port '<Title>' from machine" --body "<body>" |
| 112 | +``` |
| 113 | + |
| 114 | +Commit message footer: |
| 115 | + |
| 116 | +``` |
| 117 | +Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
| 118 | +``` |
| 119 | + |
| 120 | +### PR body template |
| 121 | + |
| 122 | +```markdown |
| 123 | +Ports [machine PR #<PR>](https://github.com/sillsdev/machine/pull/<PR>) — <one-line summary of the change>. |
| 124 | + |
| 125 | +## <Section per area changed> |
| 126 | +<What changed and why, mirroring the source PR. Include short before/after or code snippets where helpful.> |
| 127 | + |
| 128 | +## Tests |
| 129 | +<Tests ported / added.> |
| 130 | + |
| 131 | +Closes #<ISSUE> |
| 132 | +``` |
| 133 | + |
| 134 | +PR body footer: |
| 135 | + |
| 136 | +``` |
| 137 | +🤖 Generated with [Claude Code](https://claude.com/claude-code) |
| 138 | +``` |
| 139 | + |
| 140 | +## Notes |
| 141 | + |
| 142 | +- Keep the two codebases as similar as is reasonable for a Python-vs-C# port. |
| 143 | +- If the source PR spans multiple commits, the squashed PR diff is the source of truth, but |
| 144 | + reading individual commits can clarify intent. |
| 145 | +- If a change has no sensible Python counterpart, say so in the PR body rather than forcing it. |
0 commit comments