-
Notifications
You must be signed in to change notification settings - Fork 6
feat: add /migration-analysis Claude command for Go-to-TypeScript planning #3700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,315 @@ | ||
| # Go-to-TypeScript Migration Analysis | ||
|
|
||
| Analyze the Go backend package dependency graph, compute migration tiers, check existing TypeScript coverage, and generate a migration roadmap. | ||
|
|
||
| This command scans the live codebase so results stay accurate as migration progresses. Re-run it anytime to get an updated view. | ||
|
|
||
| ## Verbosity | ||
|
|
||
| Check the arguments: `$ARGUMENTS` | ||
|
|
||
| - If the arguments contain `--verbose` or `verbose`, run in **verbose mode**: show detailed output for every step (Steps 1-5) as you go, then show the full Step 6 roadmap. | ||
| - Otherwise, run in **default mode**: run all the data-gathering steps silently (do NOT print their intermediate tables/details to the user) and only present the final Step 6 Migration Roadmap output. | ||
|
|
||
| In both modes, all steps must execute — the data from Steps 1-5 feeds into Step 6. The only difference is whether you display the intermediate results. | ||
|
|
||
| --- | ||
|
|
||
| ## Step 1: Extract Go Package Inventory | ||
|
|
||
| Enumerate all packages under `internal/`, counting source files, lines of code, test files, and subpackages. | ||
|
|
||
| Run this bash command: | ||
|
|
||
| ```bash | ||
| echo "=== Go Package Inventory ===" | ||
| echo "" | ||
| for pkg in internal/*/; do | ||
| pkg_name=$(basename "$pkg") | ||
| go_files=$(find "$pkg" -name '*.go' ! -name '*_test.go' | wc -l | tr -d ' ') | ||
| test_files=$(find "$pkg" -name '*_test.go' | wc -l | tr -d ' ') | ||
| loc=$(find "$pkg" -name '*.go' ! -name '*_test.go' -exec cat {} + 2>/dev/null | wc -l | tr -d ' ') | ||
| subpkgs=$(find "$pkg" -mindepth 1 -type d | wc -l | tr -d ' ') | ||
| echo "$pkg_name: ${go_files} source files, ${loc} LOC, ${test_files} test files, ${subpkgs} subpackages" | ||
| done | ||
| ``` | ||
|
|
||
| **Verbose mode**: Present the results as a table sorted by LOC descending. Note which packages are the largest (most effort to migrate) and which are smallest (quick wins). | ||
|
|
||
| **Default mode**: Record the data internally for use in Step 6. Do not display the table. | ||
|
|
||
| --- | ||
|
|
||
| ## Step 2: Build Dependency Graph | ||
|
|
||
| Extract internal import dependencies from non-test Go files. Exclude test files because they import test utilities (e.g., `utiltest`, `loggingtest`) that create false dependencies. | ||
|
|
||
| Run this bash command: | ||
|
|
||
| ```bash | ||
| echo "=== Internal Import Dependencies ===" | ||
| echo "" | ||
| for pkg in internal/*/; do | ||
| pkg_name=$(basename "$pkg") | ||
| deps=$(grep -rh '"github.com/posit-dev/publisher/internal/' "$pkg" --include='*.go' --exclude='*_test.go' 2>/dev/null \ | ||
| | sed 's/.*"github.com\/posit-dev\/publisher\/internal\///' \ | ||
| | sed 's/".*//' \ | ||
| | sort -u) | ||
| if [ -n "$deps" ]; then | ||
| echo "--- $pkg_name imports ---" | ||
| echo "$deps" | ||
| echo "" | ||
| else | ||
| echo "--- $pkg_name imports ---" | ||
| echo "(none - leaf package)" | ||
| echo "" | ||
| fi | ||
| done | ||
| ``` | ||
|
|
||
| **Verbose mode**: Produce **two views**: | ||
|
|
||
| ### Top-Level Grouping | ||
|
|
||
| Show each package and the top-level packages it depends on. For example: | ||
|
|
||
| ``` | ||
| config -> [clients, contenttypes, interpreters, logging, schema, types, util] | ||
| ``` | ||
|
|
||
| ### Subpackage-Level Detail | ||
|
|
||
| Where a package imports a subpackage (e.g., `clients/types` rather than `clients` root), note this specifically. This matters because subpackages like `clients/types` can potentially be migrated independently of the full parent package. | ||
|
|
||
| **Default mode**: Parse and record the dependency data internally. Do not display the graph. | ||
|
|
||
| --- | ||
|
|
||
| ## Step 3: Compute Migration Tiers | ||
|
|
||
| Using the dependency data from Step 2, compute migration tiers using an iterative algorithm. Run this as a Python script, filling in the dependency dictionary from the Step 2 results: | ||
|
|
||
| ```bash | ||
| python3 -c " | ||
| import sys | ||
|
|
||
| # Fill this dict from Step 2 output. | ||
| # Key = package name, Value = set of top-level packages it depends on. | ||
| # IMPORTANT: Use only top-level package names (e.g., 'clients' not 'clients/types'). | ||
| # Exclude self-references. | ||
| deps = { | ||
| # Claude: populate this from the grep output above, e.g.: | ||
| # 'accounts': {'clients', 'credentials', 'logging', 'types', 'util'}, | ||
| # 'bundles': {'clients', 'config', 'logging', 'types', 'util'}, | ||
| # ...leaf packages have empty sets: | ||
| # 'contenttypes': set(), | ||
| # 'logging': set(), | ||
| } | ||
|
|
||
| all_pkgs = set(deps.keys()) | ||
| assigned = {} | ||
| tier = 0 | ||
|
|
||
| while True: | ||
| current_tier = set() | ||
| for pkg in all_pkgs - set(assigned.keys()): | ||
| pkg_deps = deps[pkg] - {pkg} # exclude self-refs | ||
| if pkg_deps.issubset(set(assigned.keys())): | ||
| current_tier.add(pkg) | ||
| if not current_tier: | ||
| break | ||
| for pkg in current_tier: | ||
| assigned[pkg] = tier | ||
| tier += 1 | ||
|
|
||
| unassigned = all_pkgs - set(assigned.keys()) | ||
|
|
||
| print('=== Migration Tiers ===') | ||
| print() | ||
| for t in range(tier): | ||
| pkgs_in_tier = sorted([p for p, v in assigned.items() if v == t]) | ||
| print(f'Tier {t}: {pkgs_in_tier}') | ||
| print() | ||
|
|
||
| if unassigned: | ||
| print(f'CIRCULAR DEPENDENCIES (unassigned): {sorted(unassigned)}') | ||
| print('These packages form dependency cycles and need manual analysis.') | ||
| # Show the remaining deps for cycle analysis | ||
| for pkg in sorted(unassigned): | ||
| remaining = deps[pkg] & unassigned | ||
| print(f' {pkg} -> {sorted(remaining)}') | ||
| else: | ||
| print('No circular dependencies detected - all packages assigned to tiers.') | ||
| " | ||
| ``` | ||
|
|
||
| **IMPORTANT**: You must fill in the `deps` dictionary with the actual data from Step 2 before running this script. Each key is a top-level package name, and each value is the set of top-level packages it imports (excluding itself). | ||
|
|
||
| **Verbose mode**: Present the tier results and explain: | ||
|
|
||
| - **Tier 0**: Leaf packages with no internal dependencies — migrate these first | ||
| - **Tier 1**: Packages that only depend on Tier 0 packages | ||
| - **Tier N**: Packages whose dependencies are all in tiers 0 through N-1 | ||
| - **Circular**: Packages in dependency cycles that need special handling (break the cycle by migrating subpackages first) | ||
|
|
||
| **Default mode**: Record the tier assignments internally. Do not display tier details. | ||
|
|
||
| --- | ||
|
|
||
| ## Step 4: Check Existing TypeScript Coverage | ||
|
|
||
| Search the TypeScript codebase for files that correspond to Go packages. This helps identify what's already been migrated or has partial TypeScript equivalents. | ||
|
|
||
| Search these key locations: | ||
|
|
||
| ```bash | ||
| echo "=== TypeScript Coverage Check ===" | ||
| echo "" | ||
|
|
||
| echo "--- Type definitions (extensions/vscode/src/api/types/) ---" | ||
| find extensions/vscode/src/api/types/ -name '*.ts' 2>/dev/null | sort | ||
|
|
||
| echo "" | ||
| echo "--- API resources (extensions/vscode/src/api/resources/) ---" | ||
| find extensions/vscode/src/api/resources/ -name '*.ts' 2>/dev/null | sort | ||
|
|
||
| echo "" | ||
| echo "--- Top-level modules ---" | ||
| find extensions/vscode/src/ -maxdepth 1 -name '*.ts' 2>/dev/null | sort | ||
|
|
||
| echo "" | ||
| echo "--- Connect client ---" | ||
| find extensions/vscode/src/api/ -name '*.ts' 2>/dev/null | sort | ||
| ``` | ||
|
|
||
| For each Go package, determine whether a TypeScript equivalent exists: | ||
|
|
||
| - **Full coverage**: A TypeScript module covers the same functionality | ||
| - **Partial coverage**: Some types or functions exist but the package isn't fully ported | ||
| - **No coverage**: No TypeScript equivalent found yet | ||
|
|
||
| Read key TypeScript files to understand what types and interfaces are already defined. Pay attention to: | ||
|
|
||
| - Type definitions that mirror Go structs | ||
| - API client methods that mirror Go client functions | ||
| - Enum definitions (e.g., `ServerType`, `ContentType`) | ||
|
|
||
| **Verbose mode**: Display the coverage table with per-package details and notes on what TS files exist. | ||
|
|
||
| **Default mode**: Record coverage status per package internally. Do not display. | ||
|
|
||
| --- | ||
|
|
||
| ## Step 5: Reverse Dependency Analysis | ||
|
|
||
| Compute which packages are depended on by the most other packages. These are high-risk migration targets because many consumers need to be updated when their API changes. | ||
|
|
||
| Run this bash command: | ||
|
|
||
| ```bash | ||
| echo "=== Reverse Dependencies (who depends on each package) ===" | ||
| echo "" | ||
| for target in internal/*/; do | ||
| target_name=$(basename "$target") | ||
| dependents="" | ||
| for pkg in internal/*/; do | ||
| pkg_name=$(basename "$pkg") | ||
| if [ "$pkg_name" = "$target_name" ]; then continue; fi | ||
| if grep -rqh "\"github.com/posit-dev/publisher/internal/${target_name}" "$pkg" --include='*.go' --exclude='*_test.go' 2>/dev/null; then | ||
| dependents="$dependents $pkg_name" | ||
| fi | ||
| done | ||
| count=$(echo "$dependents" | wc -w | tr -d ' ') | ||
| if [ "$count" -gt 0 ]; then | ||
| echo "$target_name (depended on by $count packages):$dependents" | ||
| else | ||
| echo "$target_name (depended on by 0 packages)" | ||
| fi | ||
| done | sort -t'(' -k2 -rn | ||
| ``` | ||
|
|
||
| **Verbose mode**: Display the full reverse dependency table and identify foundation/mid-tier/isolated packages: | ||
|
|
||
| - **Foundation packages** (depended on by 5+ packages): Need very careful API design during migration since many consumers rely on them | ||
| - **Mid-tier packages** (depended on by 2-4 packages): Moderate risk | ||
| - **Isolated packages** (depended on by 0-1 packages): Low risk, can be migrated with minimal impact | ||
|
|
||
| **Default mode**: Record reverse dependency counts internally. Do not display. | ||
|
|
||
| --- | ||
|
|
||
| ## Step 6: Generate Migration Roadmap | ||
|
|
||
| **This step is always displayed in full, regardless of verbosity mode.** | ||
|
|
||
| Compile all the analysis into a comprehensive report. Format it as follows: | ||
|
|
||
| ### Dependency Visualization | ||
|
|
||
| Create an ASCII visualization of the dependency tiers: | ||
|
|
||
| ``` | ||
| Tier 0 (leaf packages - no internal deps): | ||
| [contenttypes] [logging] [project] [server_type] ... | ||
|
|
||
| Tier 1 (depends only on Tier 0): | ||
| [types] [util] ... | ||
| | | | ||
| v v | ||
|
|
||
| Tier 2 (depends on Tier 0-1): | ||
| [schema] [events] ... | ||
| | | ||
| v | ||
|
|
||
| ... and so on | ||
| ``` | ||
|
|
||
| ### Migration Tiers Detail | ||
|
|
||
| For each tier, list the packages with: | ||
|
|
||
| - Lines of code (from Step 1) | ||
| - Number of subpackages | ||
| - Internal dependencies | ||
| - TypeScript coverage status (from Step 4) | ||
| - Reverse dependency count (from Step 5) | ||
| - Recommended migration order within the tier (smallest/simplest first) | ||
|
|
||
| ### Subpackage Extraction Opportunities | ||
|
|
||
| Identify cases where a package imports only a subpackage of another package (from Step 2 subpackage-level detail). These represent opportunities to: | ||
|
|
||
| - Migrate the subpackage independently | ||
| - Break apparent cycles (e.g., if `config` imports `clients/types` but not the full `clients` package) | ||
|
|
||
| ### Risk Assessment | ||
|
|
||
| For each foundation package (high reverse dependency count): | ||
|
|
||
| - List all consumers | ||
| - Note API surface area (exported functions/types) | ||
| - Flag if it's in a dependency cycle | ||
| - Recommend whether to migrate it early (to unblock others) or late (to stabilize the API first) | ||
|
|
||
| ### Recommended Migration Order | ||
|
|
||
| Provide a numbered list of packages in recommended migration order, considering: | ||
|
|
||
| 1. Tier assignment (lower tiers first) | ||
| 2. Within each tier: smallest LOC first | ||
| 3. Foundation packages may be prioritized despite size if they unblock many others | ||
| 4. Packages with existing TypeScript coverage can be prioritized as quick wins | ||
|
|
||
| ### Key Takeaways | ||
|
|
||
| End with 4-6 bullet points summarizing the most important findings: critical cycle-breakers, foundation packages, largest efforts, and quick wins. | ||
|
|
||
| --- | ||
|
|
||
| ## Notes | ||
|
|
||
| - This analysis excludes test file imports (`*_test.go`) because test helpers like `utiltest` and `loggingtest` create dependencies that don't exist in production code. | ||
| - Subpackage-level detail is important: `config` importing `clients/types` is very different from `config` importing the full `clients` package with its HTTP client logic. | ||
| - Re-run this command periodically as migration progresses to get updated tier assignments and coverage status. | ||
| - The Python tier computation may show circular dependencies. These typically need to be broken by extracting shared types into a separate package or migrating subpackages independently. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When it got to this block it had to re-run because of this:
Thought that was worth noting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I saw it catching some cycles like that we should investigate more closely.