mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-09-04 03:05:32 +00:00
134 lines
5.6 KiB
Markdown
134 lines
5.6 KiB
Markdown
# PSProxmoxVE — Claude Code Instructions
|
||
|
||
## Project Overview
|
||
|
||
C# binary PowerShell module for managing Proxmox VE (PVE) infrastructure. Two projects:
|
||
- `src/PSProxmoxVE/` — Cmdlets and module surface (targets netstandard2.0)
|
||
- `src/PSProxmoxVE.Core/` — Services, models, HTTP client (targets netstandard2.0)
|
||
|
||
Tests: xUnit (`tests/PSProxmoxVE.Core.Tests/`) and Pester 5 (`tests/PSProxmoxVE.Tests/`).
|
||
|
||
## Development Workflow
|
||
|
||
**All changes go through pull requests.** The `main` branch has branch protection enabled
|
||
(required build checks, required review, admin enforced). Never push directly to main.
|
||
|
||
```bash
|
||
# Create a feature branch
|
||
git checkout -b feat/my-feature
|
||
|
||
# ... make changes ...
|
||
|
||
# Commit using conventional commits
|
||
git commit -m "feat: add new cmdlet"
|
||
|
||
# Push and create PR
|
||
git push -u origin feat/my-feature
|
||
gh pr create
|
||
```
|
||
|
||
### Agent pushes and commit identity
|
||
|
||
**Default: the `github` MCP tools.** Agent-authored branches go up with `create_branch` +
|
||
`push_files`, which commits as the `goodolclint-claude` App and produces a **verified**
|
||
commit. `push_files` re-uploads full file contents, so byte-verify before opening the PR:
|
||
commit the identical change locally, `git fetch`, and `git diff <local-commit>
|
||
origin/<branch> --` must be empty. `push_files` cannot express a removal — use
|
||
`delete_file` for deletions, and a rename is `push_files` of the new path plus
|
||
`delete_file` of the old.
|
||
|
||
**Fallback: local `git push`, for large pushes only.** Re-uploading full contents inline
|
||
is impractical past a certain size. `.claude/settings.json` sets `GIT_AUTHOR_*` /
|
||
`GIT_COMMITTER_*` to `goodolclint-claude[bot]` so those commits are still attributed to the
|
||
App — but they are **not verified**, because the signature comes from committing through
|
||
the API, not from the author name. Use this path when needed, not by default.
|
||
|
||
Claude Code picks the env block up immediately — the session that adds it already commits
|
||
as the bot, no restart needed. A `Co-Authored-By` trailer is redundant once it is in
|
||
effect, since the App is the commit author.
|
||
|
||
### Dev container (recommended)
|
||
|
||
A Docker-based dev environment replicates the full CI setup locally. Works on ARM Macs
|
||
(build + test) and x86 (full provisioning flow).
|
||
|
||
```powershell
|
||
./tests/dev.ps1 # Open pwsh shell in dev container
|
||
./tests/dev.ps1 build # Build the module
|
||
./tests/dev.ps1 test # Run unit tests (ARM + x86)
|
||
./tests/dev.ps1 integration # Provision nested PVE, run tests, cleanup (x86 only)
|
||
./tests/dev.ps1 provision # Provision nested PVE only, no tests (x86 only)
|
||
```
|
||
|
||
Configure parent PVE credentials by copying `tests/.env.test.example` to `tests/.env.test`.
|
||
|
||
### Build & test without container
|
||
|
||
```bash
|
||
# Build
|
||
dotnet build PSProxmoxVE.sln
|
||
|
||
# xUnit tests
|
||
dotnet test tests/PSProxmoxVE.Core.Tests/
|
||
|
||
# Pester tests (requires pwsh)
|
||
pwsh -Command "Invoke-Pester tests/PSProxmoxVE.Tests/ -Output Detailed"
|
||
|
||
# Run all tests via dev container
|
||
./tests/dev.ps1 test
|
||
```
|
||
|
||
## Key Conventions
|
||
|
||
- All cmdlets use `Pve` noun prefix
|
||
- All cmdlet classes must be `sealed`
|
||
- All cmdlets must have `[OutputType]` attribute
|
||
- Destructive cmdlets must set `ConfirmImpact = ConfirmImpact.High`
|
||
- VmId parameters: `[ValidateRange(100, 999999999)]`, nullable when optional
|
||
- JSON: Newtonsoft.Json only (`[JsonProperty]`), no System.Text.Json attributes
|
||
- Task polling: always use `TaskService.WaitForTask`, never inline loops
|
||
- Passwords: `SecureString` type, never plain `string`
|
||
- URL paths: `Uri.EscapeDataString()` on all dynamic path segments
|
||
- No bare `catch {}` blocks — use specific or filtered exceptions
|
||
- Verb class constants required (`VerbsCommon.Get`, not `"Get"`)
|
||
|
||
## Review System
|
||
|
||
This repo uses a structured review system to track findings and prevent regressions.
|
||
|
||
### Key files
|
||
- `docs/review/findings.json` — stable findings database. IDs are permanent (F001, F002...).
|
||
Never renumber. Read this before any coding session to understand open issues.
|
||
- `docs/review/REVIEW_REPORT.md` — latest full review report (scan-9, 2026-03-26, F001–F085)
|
||
- `DECISIONS.md` — architectural decisions and anti-patterns. **Read this before writing
|
||
any new code.** It documents patterns that were deliberately chosen or changed and must
|
||
not be reintroduced.
|
||
|
||
### Before starting a coding session
|
||
1. Read `DECISIONS.md` to understand established patterns
|
||
2. Check `docs/review/findings.json` for open findings relevant to the area you're working in
|
||
3. Do not introduce patterns listed as anti-patterns in DECISIONS.md
|
||
|
||
### Finding ID stability
|
||
Finding IDs (F001, F002...) are permanent. A resolved finding is never deleted from
|
||
findings.json — it is marked `resolved` with evidence of the fix. If a finding reappears,
|
||
it is marked `regressed` and retains its original ID.
|
||
|
||
## Releasing to PSGallery
|
||
|
||
Tag-driven: pushing a `v*` tag to `main` triggers `.github/workflows/publish.yml` (build →
|
||
PS 5.1 smoke test → publish to PSGallery → create GitHub Release with auto-generated notes).
|
||
|
||
Each release PR must update **three** things in lockstep before the tag is cut:
|
||
|
||
1. `ModuleVersion` in `src/PSProxmoxVE/PSProxmoxVE.psd1` (semver patch for bug-fix-only;
|
||
minor for new features; major for breaking changes).
|
||
2. `ReleaseNotes` in the same psd1 — this is what PSGallery surfaces on the version page.
|
||
Replace the previous version's notes; do not append.
|
||
3. `CHANGELOG.md` — cut the `[Unreleased]` section into a new `[X.Y.Z] - YYYY-MM-DD`
|
||
block and reset `[Unreleased]` to empty.
|
||
|
||
After merge, tag `main` with `vX.Y.Z` and push the tag. The publish workflow rewrites
|
||
the psd1 `ModuleVersion` in the build artifact from the tag, so the tag and the source
|
||
version must match.
|