Skip to content
Merged
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
33 changes: 33 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copilot Instructions for dcode

## Build & Test

```bash
# Run all tests
uv run pytest

# Run a single test by name
uv run pytest -k test_resolves_worktree_with_relative_gitdir

# Run a test class
uv run pytest -k TestResolveWorktree
```

Linting is via `ruff` (`uv run ruff check`). Versioning is automated: `hatch-vcs` derives the version from the latest git tag, and [release-please](https://github.com/googleapis/release-please) manages tags + the changelog from [Conventional Commits](https://www.conventionalcommits.org/). **Do not** edit a version field in `pyproject.toml` or `src/dcode/__init__.py` — both are derived from the git tag at install/build time via `importlib.metadata`.

## Architecture

dcode is a single-module CLI (`src/dcode/cli.py`) that constructs `vscode-remote://dev-container+<hex-encoded-host-path><workspace-folder>` URIs and launches VS Code with `--folder-uri`. The entrypoint is `dcode.cli:main`.

The core flow in `run_dcode()`:

1. **Worktree detection** — `resolve_worktree()` checks if `.git` is a file (not directory), parses the `gitdir:` pointer, and validates the path structure to distinguish worktrees from submodules. When the gitdir contains an absolute path from a different environment (e.g. a container), it falls back to walking ancestor directories for the real `.git` dir.
2. **Config lookup** — `find_devcontainer()` searches for `.devcontainer/devcontainer.json` or `.devcontainer.json` in the target (or main repo for worktrees).
3. **URI construction** — Two codepaths: `build_uri()` for native systems (hex-encodes the host path directly) and `build_uri_wsl()` for WSL (wraps a Windows UNC path in a JSON payload). For worktrees, the host path is always the main repo root so all worktrees share one container.

## Conventions

- `json5` is used to parse `devcontainer.json` (supports JSONC comments and trailing commas).
- All user-facing messages go to `sys.stderr`; stdout is reserved for machine output.
- Tests use `tmp_path` fixtures with mock filesystem layouts (fake `.git` files/dirs) — no real git repos needed. `subprocess.run` is always patched in integration tests.
- The helper `_make_worktree(tmp_path, name)` in the test file creates a complete fake main-repo + worktree layout for test reuse.
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # hatch-vcs needs full history to resolve version from tags

- name: Install uv
uses: astral-sh/setup-uv@v3

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --python ${{ matrix.python-version }}

- name: Lint
run: uv run ruff check

- name: Test
run: uv run pytest
18 changes: 18 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Release Please

on:
push:
branches: [main]

permissions:
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ __pycache__/
dist/
.pytest_cache/
uv.lock
src/dcode/_version.py
.ruff_cache/
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.4.1"
}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Changelog

All notable changes to this project will be documented in this file.

This project follows [Conventional Commits](https://www.conventionalcommits.org/) and
versions are managed by [release-please](https://github.com/googleapis/release-please).
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,20 @@ dcode .worktrees/pr-99
Constructs a `vscode-remote://dev-container+<hex-path>/workspaces/<name>` URI and launches VS Code with `--folder-uri`. VS Code handles the container lifecycle automatically.

For worktrees, the hex-encoded path points to the main repo (so all worktrees resolve to the same container), while the workspace folder is adjusted to open the worktree subfolder inside the container.

## 🐧 WSL behavior

When `dcode` runs inside WSL, it:

1. Builds the URI using a Windows UNC path (`\\wsl.localhost\<distro>\…`) so VS Code on Windows can resolve the folder.
2. Auto-edits your **Windows** VS Code `settings.json` (under `%APPDATA%\Code\User\` or `Code - Insiders`) to set:
- `"dev.containers.executeInWSL": true`
- `"dev.containers.executeInWSLDistro": "<your-distro>"`

This is required so the Dev Containers extension talks to Docker inside WSL instead of `docker.exe` on Windows. Comments and trailing commas in your `settings.json` are preserved (in-place patching, not a rewrite).

To opt out, pre-set those keys to whatever values you want — `dcode` only writes them when they're missing or differ from the desired values.

## 🤝 Contributing

This project uses [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `chore:`, `docs:`, etc.). Releases are automated by [release-please](https://github.com/googleapis/release-please) — merging a `feat:` or `fix:` commit to `main` opens/updates a release PR, and merging that PR creates the tag + GitHub Release.
76 changes: 76 additions & 0 deletions project/plans/2026-04-27-audit-cleanup/implementation-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Implementation Notes — dcode audit cleanup

## Status: Complete

All planned changes implemented. 39 tests pass. `ruff check` clean.

## Summary of changes

### Files modified
- `src/dcode/cli.py` — bug fixes (Fix 1–7 per plan); new `_patch_jsonc_settings` function preserves JSONC when patching `settings.json`.
- `src/dcode/__init__.py` — `__version__` now resolved via `importlib.metadata`.
- `pyproject.toml` — switched to `hatch-vcs` (dynamic version), added `[project.urls]`, added `ruff` to dev deps.
- `tests/test_cli.py` — renamed `test_skips_when_already_configured` → `test_adds_missing_distro_when_executeInWSL_already_true`; added 6 new tests; mass-updated `subprocess.run` patches to return `CompletedProcess(returncode=0)`.
- `.gitignore` — added `src/dcode/_version.py` (hatch-vcs build artifact) and `.ruff_cache/`.
- `.github/copilot-instructions.md` — removed manual-version-bump line; documented release-please + hatch-vcs flow.
- `README.md` — added "WSL behavior" section + Conventional Commits / release-please note.

### Files created
- `src/dcode/__main__.py` — enables `python -m dcode`.
- `ruff.toml` — line-length 100, py311 target, lint rules E/F/I/UP/B/SIM.
- `.github/workflows/ci.yml` — matrix py3.11/3.12/3.13 with `fetch-depth: 0` for hatch-vcs.
- `.github/workflows/release-please.yml` — release-please action v4.
- `release-please-config.json` — uses `release-type: simple` (not `python`) so it doesn't try to edit `pyproject.toml` (hatch-vcs handles versioning).
- `.release-please-manifest.json` — baseline `0.4.1`.
- `CHANGELOG.md` — seed file for release-please to append to.

## Deviations from the plan

1. **`release-type: simple` instead of `python`** — the plan called for `python` with `extra-files: []`, but `simple` is cleaner: it manages only the manifest + tag + changelog, never touching source files. With hatch-vcs, the tag IS the version, so this is exactly what we want.

2. **Added `[tool.hatch.build.hooks.vcs] version-file`** — not in the plan, but useful: hatch-vcs writes a `_version.py` at build time so introspection works even outside an installed package context. Gitignored.

3. **Test fixture pattern** — the plan didn't specify how to handle the new `subprocess.run().returncode` check across all existing tests. Solution: added a `_ok()` helper returning `CompletedProcess(returncode=0)` and updated all `patch("dcode.cli.subprocess.run")` calls via `sed` to include `return_value=_ok()`. Bare `MagicMock` would have made `returncode` truthy and triggered unwanted `SystemExit`.

4. **`--insiders` flag for `__main__.py`** — not added (would have been scope creep). `python -m dcode -i .` works through the existing `argparse`.

5. **`copilot-instructions.md` already updated by the user mid-implementation** — the file already had the new release-please/hatch-vcs language when checked. No edit needed (str_replace was a no-op due to identical input/output).

## Acceptance criteria verification

| Criterion | Status | Evidence |
|---|---|---|
| `uv run pytest` passes | ✅ | `39 passed in 0.05s` |
| `uv run ruff check` clean | ✅ | `All checks passed!` |
| `python -m dcode --help` works | ✅ | Shows full argparse help |
| Version auto-resolved via hatch-vcs | ✅ | `dcode.__version__` = `0.1.dev8+g5f9826397.d20260427` (resolved at install time, no static value anywhere) |
| `_ensure_wsl_docker_settings` no longer rewrites JSONC | ✅ | `test_preserves_jsonc_comments_and_trailing_commas` |
| Adds distro when `executeInWSL` already true | ✅ | `test_adds_missing_distro_when_executeInWSL_already_true` |
| Falls back to hint on un-patchable file | ✅ | `test_falls_back_to_hint_on_unpatchable_file` |
| Malformed `devcontainer.json` → friendly stderr | ✅ | `test_falls_back_on_malformed_devcontainer_json` |
| Non-zero exit propagated | ✅ | `test_propagates_nonzero_exit_from_code_launcher`, `test_propagates_nonzero_exit_with_devcontainer` |
| `[project.urls]` added | ✅ | Homepage / Issues / Source pointing at github.com/rosstaco/dcode |
| CI workflow runs ruff + pytest on py3.11/3.12/3.13 | ✅ | `.github/workflows/ci.yml` (not exercised — no PR yet) |
| README WSL section | ✅ | Documents auto-edit + opt-out |
| Manual macOS smoke test | ✅ | `python -m dcode --help` works; not run against a real devcontainer (would launch VS Code) |
| Manual WSL smoke test | ⚠️ Skipped | No WSL environment available on this macOS host |

## Pre-merge prerequisites for the user

1. **Tag the current main HEAD as `v0.4.1`** before merging this branch:
```bash
git tag v0.4.1 <main-sha-before-this-branch>
git push --tags
```
This gives `hatch-vcs` a baseline so post-merge installs from `main` resolve to `0.4.2.devN+g<sha>` rather than failing.

2. **First commit message after merge** should follow Conventional Commits (e.g. `feat:` or `fix:`) so release-please can open its first release PR.

3. **GitHub Actions permissions** — release-please needs `contents: write` and `pull-requests: write`. The workflow declares these, but the repo settings must allow Actions to create PRs (Settings → Actions → General → "Allow GitHub Actions to create and approve pull requests").

## Things to watch in Verify phase

- Run the actual `dcode` command (not `--help`) against a real devcontainer repo to confirm URI launches still work.
- On WSL: confirm `_patch_jsonc_settings` actually preserves a hand-edited `settings.json` (the unit test covers the algorithm but not the real file).
- After first merge to main, confirm release-please opens a PR within ~5 min.
- After merging the release PR, confirm `git tag` exists and `uv tool install --reinstall git+https://github.com/rosstaco/dcode` shows the new version.
Loading
Loading