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
6 changes: 6 additions & 0 deletions .cursor/context/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ Human-facing detail: [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md).
| **BuildMonitor.Infrastructure** | `src/Infrastructure/` | Orchestrator, process config, log store, port probe |
| **BuildMonitor.Tests** | `src/BuildMonitor.Tests/` | Unit tests for Core + Infrastructure |

## Engineering rules

- **No god-classes:** `.cursor/rules/code-structure.mdc` — extract from `ProjectOrchestrator` / `App.xaml.cs`; line limits.
- **Orchestration tests:** `.cursor/rules/testing.mdc` Tier 2 — orchestrator/runtime changes require tests in the same PR.
- **CI:** `.github/workflows/ci.yml` — build + test on every PR to `main`.

## Goals

- Monitor multiple local dotnet projects from the system tray
Expand Down
4 changes: 2 additions & 2 deletions .cursor/context/coding-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ C# style for this solution. Layout: `.cursor/rules/architecture.mdc`.
- Use file-scoped namespaces.
- Use nullable reference types.
- Prefer explicit types when clarity improves.
- Keep methods short.
- Keep methods short; keep files short — see `.cursor/rules/code-structure.mdc` (≤400 lines target, no god-class growth).
- Avoid magic strings for settings keys — use `LocalAppSettings` properties.
- Test pure logic in `BuildMonitor.Tests`.
- Test pure logic in `BuildMonitor.Tests`; orchestration changes need Tier 2 tests — see `.cursor/rules/testing.mdc`.
2 changes: 2 additions & 0 deletions .cursor/rules/architecture.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ alwaysApply: false
- Infrastructure must not depend on TrayApp.
- TrayApp references Infrastructure and Core only.
- Prefer small, testable helpers in Infrastructure/LocalBuild over logic in code-behind.
- **Class size:** see `code-structure.mdc` — no new god-classes; legacy files shrink on touch.
- **Orchestration tests:** see `testing.mdc` Tier 2 — orchestrator changes require tests in the same PR.
- Stack detail: `.cursor/context/architecture.md` and `docs/ARCHITECTURE.md`.
59 changes: 59 additions & 0 deletions .cursor/rules/code-structure.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
description: Class size limits and god-class prevention
globs:
- "src/**/*.cs"
alwaysApply: false
---

# Code structure — no god-classes

Large monolith files are how regressions hide. **Do not grow them.** Extract first, then add behaviour.

## Line limits

| Threshold | Rule |
|-----------|------|
| **≤ 400 lines** | Target maximum for any `.cs` file |
| **≤ 500 lines** | Hard cap for **new** files |
| **> 500 lines (legacy)** | Files already over limit (e.g. `ProjectOrchestrator.cs`, `App.xaml.cs`) — **no net line increase** in a feature PR unless the same PR extracts code to a smaller type and overall line count in those files **decreases** |

Check before commit:

```powershell
(Get-Content path\to\File.cs).Count
```

## Where logic belongs

| Layer | Allowed in file | Move elsewhere |
|-------|-----------------|----------------|
| **TrayApp** (`App.xaml.cs`, `*Window.xaml.cs`) | UI wiring, `Dispatcher` calls, event handlers that delegate | Business rules, parsing, process orchestration → `TrayApp/Services/*` or Infrastructure |
| **Infrastructure** (`ProjectOrchestrator.cs`) | Start/stop coordination, subscribing runtimes, applying settings | Build/test/watch steps, output handling, lock release, debounce → `ProjectRuntime`, `LocalBuild/*`, `Infrastructure/Services/*` |
| **Core** | Models, pure rules, settings shapes | I/O, processes, file system |

## Extraction triggers (mandatory)

Before adding **~30+ lines** of non-trivial logic to any file already **> 400 lines**:

1. Identify a cohesive unit (parser, policy, planner, health publisher, menu builder).
2. Add a new type in the correct project (prefer `Infrastructure/LocalBuild` or `TrayApp/Services`).
3. Add unit tests for the extracted type (`testing.mdc`).
4. Wire the thin caller from the original file.

## Naming and shape

- One primary type per file when practical.
- Prefer `sealed` helpers and static parsers over private regions hundreds of lines long.
- New orchestration types: `*Coordinator`, `*Planner`, `*Handler`, `*Publisher` — not more methods on an existing 2k-line class.

## PR rejection (agent)

Reject or split a PR that:

- Adds feature logic only inside `App.xaml.cs` or `ProjectOrchestrator.cs` without extraction.
- Increases line count in a legacy god-class without a paired extraction in the same PR.
- Introduces a new file expected to exceed **500 lines**.

## Legacy debt

Existing oversized files are **not** fixed in one go. Every touch should **shrink or stabilise** them: extract at least one helper when you change behaviour, and add orchestration tests (`testing.mdc`).
2 changes: 2 additions & 0 deletions .cursor/rules/core.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ alwaysApply: true
- Use async/await with `CancellationToken` for I/O in Infrastructure; do not block on asynchronous work.
- Never hardcode secrets; user settings live under `%LocalAppData%/BuildMonitor/` — document keys in `docs/SETTINGS.md`, never commit real settings files.
- **Layering:** **Core** (models, rules) ← **Infrastructure** (processes, logs, orchestration) ← **TrayApp** (WPF UI only). Keep business logic out of XAML code-behind where possible.
- **Structure:** See `code-structure.mdc` — line limits, no god-classes, extract from `App.xaml.cs` / `ProjectOrchestrator.cs`.
- **Tests:** See `testing.mdc` — orchestration changes require Tier 2 tests in the same PR; CI enforces build + test on every PR.
- Follow existing solution layout and naming unless a change is explicitly requested.
- **Document as you build.** When adding a feature, config key, or behaviour change, update the matching `docs/` page in the same change. See `documentation.mdc`, `adr.mdc`, and `feature-delivery.mdc`.
12 changes: 7 additions & 5 deletions .cursor/rules/feature-delivery.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ Use this checklist before declaring a feature complete. It complements `core.mdc
1. **Code**
- Build succeeds; **no new compiler warnings** in files this change adds or modifies (`build-warnings.mdc`).
- Async with `CancellationToken` on I/O in Infrastructure.
- Keep orchestration and parsing in Infrastructure/Core; TrayApp stays thin.
- Keep orchestration and parsing in Infrastructure/Core; TrayApp stays thin (`code-structure.mdc`).
- **No god-class growth** — extract before adding logic to oversized files.

2. **Tests** (default: include in the same change — see `testing.mdc`)
- New or changed logic in Core/Infrastructure has unit tests in `BuildMonitor.Tests`.
- UI-only XAML: document manual checks in the feature doc when tests are N/A.
- Existing tests still pass.
2. **Tests** (mandatory — see `testing.mdc`)
- Tier 1: parsers, rules, formatters in Core/Infrastructure.
- **Tier 2: any orchestrator/runtime/coalescer change includes new or updated tests in the same PR** — thin parser-only coverage alone is insufficient.
- UI-only XAML: manual checks in the feature doc when tests are N/A.
- CI build + test must pass on the PR (`.github/workflows/ci.yml`).

3. **Documentation**
- `docs/features/<file>.md` added or updated for user-facing changes.
Expand Down
66 changes: 57 additions & 9 deletions .cursor/rules/testing.mdc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Unit tests for Core and Infrastructure logic
description: Unit and orchestration tests for Core and Infrastructure
globs:
- "src/**/*.cs"
- "src/BuildMonitor.Tests/**/*.cs"
Expand All @@ -8,19 +8,67 @@ alwaysApply: false

# Testing rules

- Use xUnit in [`src/BuildMonitor.Tests/`](../../src/BuildMonitor.Tests/).
- Test Core rules and Infrastructure parsers/helpers — not WPF rendering.
- New or changed logic in Core/Infrastructure should have unit tests in the same PR unless genuinely UI-only.
**Thin integration coverage is not acceptable** for orchestration code. Parsers-only tests while `ProjectOrchestrator` grows untested is a defect, not a shortcut.

## Commands (user runs)
## Test project

xUnit in [`src/BuildMonitor.Tests/`](../../src/BuildMonitor.Tests/).

## Three tiers (all required where applicable)

### Tier 1 — Pure logic (mandatory)

Always unit-test when you add or change:

- Core rules and evaluators
- Static parsers (`BuildLogParser`, `DotNetRunOutputParser`, `DotNetTestOutputParser`, …)
- Formatters, planners, detectors with no I/O

### Tier 2 — Orchestration (mandatory — no exceptions)

Any PR that changes behaviour in these areas **must** add or update tests in the same PR:

| Area | Examples |
|------|----------|
| `ProjectOrchestrator.cs` | start/stop, rebuild, settings apply, health publish |
| `ProjectRuntime` (nested or extracted) | build/run/test lifecycle, output lines, state transitions |
| `HealthCoalescer` | coalesce timing, dirty flags, pause while menu open |
| `DotNetCliRunner` / `SupervisedProcess` | command construction, env sanitization (via test doubles) |
| `BuildLogStore` | persist/load/truncate semantics |
| New `Infrastructure/Services/*` or `LocalBuild/*` coordinators | lock release, repair, debounce application |

**What “orchestration test” means here:** not a full UI or real `dotnet` E2E. Use:

- Extracted pure functions (preferred)
- In-memory fakes (`FakeLogStore`, recording callbacks, stub `I*` interfaces)
- Table-driven tests over inputs → expected state / commands / events

**Minimum bar per PR:** at least one new or updated test method that would **fail** if the orchestration change were reverted.

### Tier 3 — TrayApp

- Do **not** test WPF rendering or window layout in xUnit.
- New non-trivial logic in code-behind → **extract** to `TrayApp/Services/*` and test there (Tier 1/2).
- Pure XAML-only changes → manual test plan in the PR and feature doc.

## Anti-patterns (reject the PR)

| Anti-pattern | Fix |
|--------------|-----|
| Orchestrator change + only parser tests | Add orchestration tests for the changed path |
| “Too hard to test” without extraction | Extract a testable unit first (`code-structure.mdc`) |
| Manual-only verification for Infrastructure behaviour | Add Tier 2 tests or split the PR |

## Commands (user runs locally; CI runs on PR)

```powershell
dotnet build BuildMonitor.slnx
dotnet test src/BuildMonitor.Tests/BuildMonitor.Tests.csproj
```

## When tests are N/A
## When tests are genuinely N/A

- Pure XAML layout with no new logic — document manual tray/status checks in `docs/features/`.
- Comment-only or docs-only changes.
- Comment-only, docs-only, or `.cursor` rules-only changes.
- Pure XAML with no new logic — document manual tray checks in `docs/features/`.

See `feature-delivery.mdc` and `feature-ship` skill for checklist enforcement.
See `feature-delivery.mdc`, `code-structure.mdc`, and CI workflow [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml).
4 changes: 4 additions & 0 deletions .cursor/rules/work-tracking.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ Before `git commit`, `git push`, **ship it**, or **check in** when the user did

**After linking:** set project Status to **In Progress** when starting substantial work; **Done** only when the user says ship/merge or work is complete on `main` (issue closed).

**Board backlog:** run `.\scripts\github\Sync-ProjectBoard.ps1` when retrospective or planned cards are missing; see [docs/ops/github-workflow.md](../../docs/ops/github-workflow.md).

**Hooks (mandatory locally):** run `.\scripts\install-githooks.ps1` once per clone — `commit-msg` rejects commits without `#<issue>` (or `feature/<id>-...` branch). PRs must reference an issue (CI workflow).

**Exception:** user explicitly says “no issue”, “chore only”, or “docs/rules only, skip issue”.

## Agent automation (`gh`)
Expand Down
3 changes: 2 additions & 1 deletion .cursor/skills/feature-ship/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ No commit, push, or merge unless the user triggered one of the phrases above.
- **Performance (inline):** if diff touches orchestrator output handling, log saves, port probe — no obvious hot-path blocking. Else N/A.
3. **User gates** — agent does **not** run `dotnet build` or `dotnet test`. Print commands; require user confirmation or pasted output before PR/merge.
4. **Git + GitHub** — per `work-tracking.mdc`: resolve `#<id>`; confirm issue is on **project #3** (add with `gh project item-add 3` if missing); commit `#<id>: …`; push; PR body `Closes #<id>`.
5. **Merge + Done** — only for full **ship it**: `gh pr merge --squash` after user confirms build/test; issue closes via `Closes #N`; project Status **Done** (automation or manual per [docs/ops/github-workflow.md](../../docs/ops/github-workflow.md)).
5. **CI** — PR should pass [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml) (build + test on `windows-latest`). User confirms green checks before merge.
6. **Merge + Done** — only for full **ship it**: `gh pr merge --squash` after user confirms build/test; issue closes via `Closes #N`; project Status **Done** (automation or manual per [docs/ops/github-workflow.md](../../docs/ops/github-workflow.md)).

```powershell
gh pr create --title "#42: Short title" --body "Closes #42`n`n## Summary`n- ...`n`n## Test plan`n- [x] dotnet build`n- [x] dotnet test"
Expand Down
27 changes: 27 additions & 0 deletions .githooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/sh
# Require a GitHub issue reference in every commit (enforces work-tracking.mdc).
# Install: .\scripts\install-githooks.ps1

MSG_FILE="$1"
MSG=$(cat "$MSG_FILE")

# Allow merge/revert/fixup commits
case "$MSG" in
Merge*|Revert*|fixup!*|squash!*) exit 0 ;;
esac

# Commit message contains #N
if printf '%s' "$MSG" | grep -qE '#[0-9]+'; then
exit 0
fi

# Branch feature/N-... without message ref (branch still ties to issue)
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || true)
case "$BRANCH" in
feature/[0-9]*-*) exit 0 ;;
esac

echo "commit-msg hook: commit must reference a GitHub issue (#N) in the message," >&2
echo "or use branch feature/<id>-short-name." >&2
echo "Create an issue, add it to project board #3, then commit with: #<id>: summary" >&2
exit 1
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

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

jobs:
build-and-test:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
include-prerelease: true

- name: Build solution
run: dotnet build BuildMonitor.slnx --configuration Release

- name: Test
run: dotnet test src/BuildMonitor.Tests/BuildMonitor.Tests.csproj --configuration Release --no-build --verbosity normal
32 changes: 32 additions & 0 deletions .github/workflows/pr-issue-link.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: PR issue link

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
require-issue:
runs-on: ubuntu-latest
steps:
- name: Check PR links a GitHub issue
env:
PR_BODY: ${{ github.event.pull_request.body }}
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
body="$PR_BODY"
title="$PR_TITLE"
combined="${title}
${body}"
if echo "$combined" | grep -qiE '(closes|fixes|resolves)\s+#[0-9]+'; then
echo "PR links an issue."
exit 0
fi
if echo "$combined" | grep -qE '#[0-9]+'; then
echo "PR references an issue number."
exit 0
fi
echo "::error::Pull request must reference a GitHub issue (Closes #N or #N in title/body)."
exit 1
51 changes: 51 additions & 0 deletions docs/ops/github-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,57 @@ Full ship: commit → push → PR → squash merge → issue closed → project

Load `.cursor/skills/feature-ship/SKILL.md` when the user says **ship** or **ship it**.

## Board sync (backlog)

The board must list **all shipped work (Done)** and **planned work (Todo)**. Run after creating retrospective issues or when the board drifts:

```powershell
.\scripts\github\Sync-ProjectBoard.ps1
```

Dry run: `.\scripts\github\Sync-ProjectBoard.ps1 -WhatIf`

The script is idempotent (skips issues whose titles already exist) and ensures closed issues **#2**, **#4**, **#6**–**#8**, **#10** are on project #3 with Status **Done**.

### Planned work (Todo on board)

| Theme | Issue title (created by sync script if missing) |
|-------|--------------------------------------------------|
| Tests | Run tests on file change (`OnFileChange` mode) |
| Tray UX | Open log viewer from tray context menu |
| Tray UX | WPF tray context menu (Phase 2, if #8 insufficient) |
| Optional module | Wire Azure DevOps polling module |
| Diagnostics | Verdict feedback loop for adaptive debounce |

Update this table when adding new planned issues.

## Enforce issue on every commit

**Local (required for agents and developers):**

```powershell
.\scripts\install-githooks.ps1
```

The `commit-msg` hook rejects commits whose message lacks `#<issue>` unless the branch is `feature/<id>-...` (merge/revert commits are exempt).

**CI:** [`.github/workflows/pr-issue-link.yml`](../../.github/workflows/pr-issue-link.yml) fails PRs that do not reference an issue (`Closes #N` or `#N` in title/body).

## CI build and test

[`.github/workflows/ci.yml`](../../.github/workflows/ci.yml) runs on every push to `main` and on every pull request targeting `main`:

| Step | Command |
|------|---------|
| Build | `dotnet build BuildMonitor.slnx --configuration Release` |
| Test | `dotnet test src/BuildMonitor.Tests/BuildMonitor.Tests.csproj --configuration Release --no-build` |

Runner: **windows-latest** (WPF / `net10.0-windows`). SDK: **10.0.x** (`include-prerelease: true` until .NET 10 GA).

**PRs should pass CI before merge.** Agents do not run build/test locally by default; use CI status on the PR.

Optional: branch protection on `main` → require status check **build-and-test**.

## Record of intent

Chat and Cursor plans are not the system of record. The **project board (#3)**, **Issues**, **PR descriptions**, and **`docs/`** are.
Loading
Loading