diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 20d0302..0000000 --- a/.appveyor.yml +++ /dev/null @@ -1,30 +0,0 @@ -#---------------------------------# -# Build Image # -#---------------------------------# -image: Visual Studio 2026 - -#---------------------------------# -# Custom Build Script # -#---------------------------------# -build: off -test: off - -build_script: -- powershell .\bootstrap.ps1 -buildNumber %APPVEYOR_BUILD_NUMBER% -branch "%APPVEYOR_REPO_BRANCH%" -buildPath "%APPVEYOR_BUILD_FOLDER%" -gitHubApiKey "%GitHubAPIKey%" -nuGetApiKey "%NuGetAPIKey%" - -#---------------------------------# -# Branches to build # -#---------------------------------# -branches: - # Allowed list - only: - - main - -# GitHub releases create tags, skip builds on tags -skip_tags: true - -# skip builds on some types of commits -skip_commits: - message: /^\[chore\]/ # message starts with [chore] - files: - - '**/README.md' # changes to readme files \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..afe3fa5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + paths-ignore: + - 'README.md' + - 'agent-context/**' + +jobs: + build-and-test: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Restore Cake tool + run: dotnet tool restore + + - name: Cake bootstrap + run: dotnet cake build.cake --bootstrap --target=Test --buildNumber=${{ github.run_number }} --branch="${{ github.ref_name }}" --buildPath="${{ github.workspace }}" + + - name: Build and test + run: dotnet cake build.cake --target=Test --buildNumber=${{ github.run_number }} --branch="${{ github.ref_name }}" --buildPath="${{ github.workspace }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0cea9cc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release + +on: + push: + branches: [main] + paths-ignore: + - 'README.md' + - 'agent-context/**' + +jobs: + build-test-publish: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Restore Cake tool + run: dotnet tool restore + + - name: Cake bootstrap + run: dotnet cake build.cake --bootstrap --buildNumber=${{ github.run_number }} --branch="${{ github.ref_name }}" --buildPath="${{ github.workspace }}" --gitHubApiKey="${{ secrets.RELEASE_GITHUB_API_KEY }}" --nuGetApiKey="${{ secrets.NUGET_API_KEY }}" + + - name: Build, test, and publish + run: dotnet cake build.cake --buildNumber=${{ github.run_number }} --branch="${{ github.ref_name }}" --buildPath="${{ github.workspace }}" --gitHubApiKey="${{ secrets.RELEASE_GITHUB_API_KEY }}" --nuGetApiKey="${{ secrets.NUGET_API_KEY }}" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a15179b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,11 @@ +# AGENTS.md + +## Agent skills + +### Issue tracker + +Issues and specs live as markdown files under `.scratch//`. See `docs/agents/issue-tracker.md`. + +### Domain docs + +Single-context: `CONTEXT.md` + `docs/adr/` at repo root (created lazily by `/domain-modeling`). See `docs/agents/domain.md`. diff --git a/README.md b/README.md index 7f17d84..9f07182 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A C# source generator that generates enums for all font codepoint files are cont Each resulting enum can be used for strongly-typed references to the byte value of specific glyphs in the associated font. # Links -[![Build status](https://ci.appveyor.com/api/projects/status/2lgs7mbehdvls38q?svg=true)](https://ci.appveyor.com/project/tpwalke2/codepointenumgenerator) +[![Build status](https://github.com/tpwalke2/CodePointEnumGenerator/actions/workflows/ci.yml/badge.svg)](https://github.com/tpwalke2/CodePointEnumGenerator/actions/workflows/ci.yml) [![NuGet](https://img.shields.io/nuget/v/CodePointEnumGenerator.svg)](https://www.nuget.org/packages/CodePointEnumGenerator/) diff --git a/agent-context/README.md b/agent-context/README.md new file mode 100644 index 0000000..99a8213 --- /dev/null +++ b/agent-context/README.md @@ -0,0 +1,25 @@ +# CodePointEnumGenerator — Agent Context + +Roslyn incremental source generator (C#, `netstandard2.0`). Reads font `.codepoints` files marked as `AdditionalFiles` and emits one strongly-typed enum per file mapping glyph names to hex byte values. Packed and shipped as a NuGet analyzer package. Built with Cake, CI on GitHub Actions, releases via GitHub + NuGet. + +## Context docs + +| Doc | Covers | +|---|---| +| `context/architecture.md` | Stack, layout, generator pipeline, helpers, `.codepoints` format | +| `context/testing.md` | xunit test layout, `GeneratorTestFactory` harness, how to run tests | +| `context/build-and-release.md` | Cake pipeline, GitHub Actions CI, versioning, GitHub/NuGet release flow | + +## Existing human docs +Root `README.md` — package usage snippet, troubleshooting, build pipeline requirements. + +## Document Scopes + +| Document | Codebase paths watched | +|---|---| +| `context/architecture.md` | `CodePointEnumGenerator/`, `CodePointEnumGenerator.csproj`, `CodePointEnumGenerator.sln` | +| `context/testing.md` | `CodePointEnumGenerator.Tests/` | +| `context/build-and-release.md` | `build.cake`, `build/`, `bootstrap.ps1`, `.github/workflows/`, `.config/dotnet-tools.json` | + +--- +*Last updated: 2026-09-20 | Verified against: 6b6c9cc* diff --git a/agent-context/context/architecture.md b/agent-context/context/architecture.md new file mode 100644 index 0000000..e045362 --- /dev/null +++ b/agent-context/context/architecture.md @@ -0,0 +1,55 @@ +# Architecture + +## What this is +Roslyn incremental source generator. Scans project `AdditionalFiles` ending in `.codepoints`, emits one C# enum per file mapping glyph names to hex byte values (for strongly-typed font glyph references). + +## Tech stack + +| Layer | Technology | +|---|---| +| Generator project | `netstandard2.0`, C# 14, `Microsoft.CodeAnalysis.CSharp` 5.9.0 | +| Test project | `net10.0`, xunit 2.9.3, `Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit` | +| Build | Cake (`build.cake` + `build/*.cake`), driven by `bootstrap.ps1` | +| CI | GitHub Actions (`.github/workflows/ci.yml`, `release.yml`), `windows-latest` | +| Packaging | NuGet (`CodePointEnumGenerator` package), GitHub Releases on publish | + +## Top-level layout + +| Path | Purpose | +|---|---| +| `CodePointEnumGenerator/` | The generator itself, packed as analyzer | +| `CodePointEnumGenerator/CodePointEnumGenerator.cs` | `IIncrementalGenerator` entry point | +| `CodePointEnumGenerator/Helpers/` | Pure helper logic (name parsing, code emission, number-to-words) | +| `CodePointEnumGenerator.Tests/` | xunit tests against generator and helpers | +| `build/` | Cake build scripts (config, paths, build/test/publish operations, GitHub API models) | +| `build.cake` | Cake entry: Clean → Build → Test → Publish tasks | +| `bootstrap.ps1` | Restores `dotnet tool` (cake.tool) and runs `build.cake` | +| `output/artifacts/` | Packed `.nupkg` output | +| `.config/dotnet-tools.json` | Pins `cake.tool` 3.0.0 | + +## Generator pipeline (`CodePointEnumGenerator/CodePointEnumGenerator.cs`) + +1. `GetCodePointFiles` filters `context.AdditionalTextsProvider` to paths ending `.codepoints`. +2. For each file, project a tuple: + - `Name` = `AdditionalTextExtensions.GetEnumFileName()` — filename minus `.codepoints`, minus `-`. + - `Content` = file text run through `StringExtensions.GetEnumValues()` — parses `name value` lines into `(enumEntryName, hexValue)` tuples, deduping names by suffixing a counter. + - `Namespace` = `StringExtensions.ToNamespace()` — derives a namespace from the file's folder path (skips drive letter, starts at first segment containing a `.`). +3. `context.RegisterSourceOutput` emits `{Name}.g.cs` via `CodeGeneration.BuildEnumFileContents`, which renders `namespace {ns}; public enum {Name} { ENTRY = 0xVALUE, ... }`. + +## Helpers (`CodePointEnumGenerator/Helpers/`) + +| File | Responsibility | +|---|---| +| `AdditionalTextExtensions.cs` | `GetEnumFileName()` — derive enum type name from file path | +| `StringExtensions.cs` | `ToNamespace()`, `ToEnumEntry()` (name → PascalCase enum member, numeric prefixes spelled out via `EnglishNumberToWordsConverter`), `GetEnumValues()` (parse `.codepoints` file contents) | +| `CodeGeneration.cs` | `BuildEnumFileContents()` — renders the final enum source text | +| `EnglishNumberToWordsConverter.cs` | Internal; spells out numeric enum-name prefixes (e.g. `1_foo` → `OneFoo`). Adapted from Humanizer | + +## `.codepoints` file format +Plain text, one glyph per line: ` ` (space-separated, exactly 2 tokens). Names run through `ToEnumEntry`: leading digits spelled as words, `_`-separated words PascalCased and concatenated. + +## Consumer usage +Consuming projects reference the package with `OutputItemType="Analyzer"` and mark `.codepoints` files with build action `AdditionalFiles`. See root `README.md` for the exact snippet and troubleshooting steps. + +--- +*Last updated: 2026-09-20 | Verified against: 6b6c9cc* diff --git a/agent-context/context/build-and-release.md b/agent-context/context/build-and-release.md new file mode 100644 index 0000000..8c38310 --- /dev/null +++ b/agent-context/context/build-and-release.md @@ -0,0 +1,35 @@ +# Build & Release + +## Local build +`bootstrap.ps1` restores the `dotnet tool` manifest (`.config/dotnet-tools.json`, pins `cake.tool` 3.0.0) then runs `dotnet cake build.cake` twice (bootstrap pass, then real run), passing `buildNumber`, `branch`, `buildPath`, `gitHubApiKey`, `nuGetApiKey`. Checks `$LASTEXITCODE` after each step and exits non-zero on failure — `gitHubApiKey`/`nuGetApiKey` may be omitted for non-release builds (see below). + +## Cake pipeline (`build.cake` + `build/*.cake`) +Tasks, in dependency order: `Clean` → `Build` → `Test` → `Publish` → `Default`. + +| Script | Role | +|---|---| +| `build/BuildConfig.cake` | CLI argument binding (`Cake.ArgumentBinder`) — target, buildNumber, branch, buildPath, API keys, verbose, maxDegreeOfParallelism, currentVersion | +| `build/BuildPaths.cake` | Resolves `output/`, `output/deploy/`, `output/deploy/test-results/`, `output/artifacts/` | +| `build/Utilities.cake` | `GetVersion` (`{CurrentRelease}.{BuildNumber}`), `ParallelInvoke`, `FlushDns` | +| `build/BuildOperations.cake` | `DoBuild` — parallel clean of Debug/Release, restore, `DotNetBuild` (Debug config) | +| `build/TestProject.cake` | `TestProject` record (path + target framework) | +| `build/TestOperations.cake` | `DoTest` — `dotnet test` per project, `trx` logger, into `output/deploy/test-results` | +| `build/PublishOperations.cake` | `DoPublish` — only runs `IsRelease(branch)` (branch == `main`); packs, creates a GitHub release (`Release-{version}` tag), uploads the `.nupkg` asset, pushes to NuGet | +| `build/GitHubApiModels.cake` | Request/response DTOs for the GitHub Releases REST API | + +Version = `CurrentRelease` (default `1.0.0`) + `.` + `buildNumber` argument, unless `currentVersion` is overridden. + +## CI (`.github/workflows/`) +Two GitHub Actions workflows, both `windows-latest` (Cake's `FlushDns` NuGet-publish workaround needs Windows), both ignore changes to `README.md` and `agent-context/**`: +- `ci.yml` — triggers on `pull_request` (any target) and `push` to `main`. Runs `dotnet tool restore` + both Cake steps directly (bypassing `bootstrap.ps1`, so a failed step fails the job natively). No secrets passed; `Publish` no-ops via `IsRelease(branch)`. +- `release.yml` — triggers on `push` to `main` only (no `pull_request`, no tags — avoids retriggering off the tag it creates). Same steps, plus `RELEASE_GITHUB_API_KEY`/`NUGET_API_KEY` repo secrets and `buildNumber=${{ github.run_number }}`. + +AppVeyor is retired (`.appveyor.yml` removed). + +## Release requirements +- GitHub PAT with read/write on Contents (release creation + asset upload), stored as the `RELEASE_GITHUB_API_KEY` repo secret. +- NuGet API key (push to `https://api.nuget.org/v3/index.json`), stored as the `NUGET_API_KEY` repo secret. Plan is to move to NuGet Trusted Publishing later. +- Every release on `main` creates a `Release-{version}` GitHub tag/release with auto-generated notes, and publishes the same `.nupkg` to NuGet. + +--- +*Last updated: 2026-09-20 | Verified against: 6b6c9cc* diff --git a/agent-context/context/testing.md b/agent-context/context/testing.md new file mode 100644 index 0000000..3d9a7ac --- /dev/null +++ b/agent-context/context/testing.md @@ -0,0 +1,29 @@ +# Testing + +## Framework +xunit 2.9.3 in `CodePointEnumGenerator.Tests`, targets `net10.0` (generator itself targets `netstandard2.0`). Test project references the generator via `ProjectReference`. + +## Layout + +| File | Covers | +|---|---| +| `CodePointEnumGeneratorTests.cs` | End-to-end: runs the generator via `GeneratorTestFactory` and checks emitted enum source | +| `GeneratorTestFactory.cs` | Test harness — builds a `CSharpCompilation`, runs `CodePointEnumGenerator` through `CSharpGeneratorDriver` against fake `AdditionalText` files, returns compilation/diagnostics/run-result | +| `AdditionalFile.cs` | Minimal `AdditionalText` implementation for feeding fake `.codepoints` content into the driver | +| `Helpers/GetEnumFileNameTests.cs` | `AdditionalTextExtensions.GetEnumFileName()` | +| `Helpers/GetEnumValuesTests.cs` | `StringExtensions.GetEnumValues()` | +| `Helpers/ToEnumEntryTests.cs` | `StringExtensions.ToEnumEntry()` | +| `Helpers/ToNamespaceTests.cs` | `StringExtensions.ToNamespace()` | + +## Running the generator in tests +`GeneratorTestFactory.RunGenerator(source, additionalFiles...)`: +- Parses `source` as a syntax tree, compiles against `object` + the generator assembly. +- Pre-verifies diagnostics, ignoring a fixed set of expected errors (`CS0012`, `CS0616`, `CS0246`, `CS0103` — incomplete-reference noise from the minimal test compilation). +- Wraps each `(path, contents)` tuple as an `AdditionalFile` and runs `CodePointEnumGenerator` through `CSharpGeneratorDriver.RunGeneratorsAndUpdateCompilation`. +- Returns `(Compilation, (DiagnosticsBefore, DiagnosticsAfter), GeneratorDriverRunResult)` for assertions. + +## Running tests +Via Cake: `Task("Test")` in `build.cake` runs `dotnet test` (framework `net10.0`, `trx` logger) against every `*Tests.csproj`, in parallel (`build/TestOperations.cake`, `build/TestProject.cake`). Locally: `dotnet test` from repo root or the test project directly. + +--- +*Last updated: 2026-09-20 | Verified against: 6b6c9cc* diff --git a/agent-context/reviews/copilot-review-2026-09-20.md b/agent-context/reviews/copilot-review-2026-09-20.md new file mode 100644 index 0000000..55dc6f1 --- /dev/null +++ b/agent-context/reviews/copilot-review-2026-09-20.md @@ -0,0 +1,14 @@ +# .github/workflows/ci.yml:25-29 + +- On pushes to main, this workflow still runs the default Cake target, which reaches Publish; because this job passes no API keys, DoGitHubRelease/DoNuGetPublish throw and the CI job fails (while the release workflow is running in parallel). Run the Test target for both commands here, or otherwise exclude main from this workflow. + +# agent-context/README.md:1-3 + +- This overview still says CI runs on AppVeyor, but this PR deletes .appveyor.yml and adds GitHub Actions workflows. Agents using this context will be directed to the retired CI system; update the sentence to describe GitHub Actions. +- This issue also appears in the following locations of the same file: + - line 11 + - line 22 + +# agent-context/context/architecture.md:10-13 + +- The architecture snapshot still identifies AppVeyor and .appveyor.yml as the CI implementation, but that file is removed and CI is now in .github/workflows/. Update this current-state row so the architecture documentation does not direct readers to a nonexistent pipeline. \ No newline at end of file diff --git a/bootstrap.ps1 b/bootstrap.ps1 index 37a781e..9cb2ab8 100644 --- a/bootstrap.ps1 +++ b/bootstrap.ps1 @@ -6,6 +6,10 @@ param ( [System.String]$nuGetApiKey) dotnet tool restore +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } dotnet cake build.cake --bootstrap --buildNumber=$buildNumber --branch="$branch" --buildPath="$buildPath" --gitHubApiKey="$gitHubApiKey" --nuGetApiKey="$nuGetApiKey" -dotnet cake build.cake --buildNumber=$buildNumber --branch="$branch" --buildPath="$buildPath" --gitHubApiKey="$gitHubApiKey" --nuGetApiKey="$nuGetApiKey" \ No newline at end of file +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +dotnet cake build.cake --buildNumber=$buildNumber --branch="$branch" --buildPath="$buildPath" --gitHubApiKey="$gitHubApiKey" --nuGetApiKey="$nuGetApiKey" +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } \ No newline at end of file diff --git a/build/BuildConfig.cake b/build/BuildConfig.cake index 8eef787..f4e136c 100644 --- a/build/BuildConfig.cake +++ b/build/BuildConfig.cake @@ -38,15 +38,15 @@ public class BuildConfig [StringArgument( "nuGetApiKey", - Description = "The API key for interacting with NuGet.", - Required = true + Description = "The API key for interacting with NuGet. Only required when publishing a release.", + DefaultValue = "" )] public string NuGetApiKey { get; set; } [StringArgument( "gitHubApiKey", - Description = "The API key for interacting with GitHub.", - Required = true + Description = "The API key for interacting with GitHub. Only required when publishing a release.", + DefaultValue = "" )] public string GitHubApiKey { get; set; } diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 0000000..b548c53 --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,51 @@ +# Domain Docs + +How the engineering skills should consume this repo's domain documentation when exploring the codebase. + +## Before exploring, read these + +- **`CONTEXT.md`** at the repo root, or +- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic. +- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src//docs/adr/` for context-scoped decisions. + +If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved. + +## File structure + +Single-context repo (most repos): + +``` +/ +├── CONTEXT.md +├── docs/adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +Multi-context repo (presence of `CONTEXT-MAP.md` at the root): + +``` +/ +├── CONTEXT-MAP.md +├── docs/adr/ ← system-wide decisions +└── src/ + ├── ordering/ + │ ├── CONTEXT.md + │ └── docs/adr/ ← context-specific decisions + └── billing/ + ├── CONTEXT.md + └── docs/adr/ +``` + +## Use the glossary's vocabulary + +When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids. + +If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`). + +## Flag ADR conflicts + +If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: + +> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_ diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 0000000..fbda5e0 --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,30 @@ +# Issue tracker: Local Markdown + +Issues and specs for this repo live as markdown files in `.scratch/`. + +## Conventions + +- One feature per directory: `.scratch//` +- The spec is `.scratch//spec.md` +- Implementation issues are one file per ticket at `.scratch//issues/-.md`, numbered from `01` — never a single combined tickets file +- Triage state is recorded as a `Status:` line near the top of each issue file (see `triage-labels.md` for the role strings) +- Comments and conversation history append to the bottom of the file under a `## Comments` heading + +## When a skill says "publish to the issue tracker" + +Create a new file under `.scratch//` (creating the directory if needed). + +## When a skill says "fetch the relevant ticket" + +Read the file at the referenced path. The user will normally pass the path or the issue number directly. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a file with one **child** file per ticket. + +- **Map**: `.scratch//map.md` — the Notes / Decisions-so-far / Fog body. +- **Child ticket**: `.scratch//issues/NN-.md`, numbered from `01`, with the question in the body. A `Type:` line records the ticket type (`research`/`prototype`/`grilling`/`task`); a `Status:` line records `claimed`/`resolved`. +- **Blocking**: a `Blocked by: NN, NN` line near the top. A ticket is unblocked when every file it lists is `resolved`. +- **Frontier**: scan `.scratch//issues/` for files that are open, unblocked, and unclaimed; first by number wins. +- **Claim**: set `Status: claimed` and save before any work. +- **Resolve**: append the answer under an `## Answer` heading, set `Status: resolved`, then append a context pointer (gist + link) to the map's Decisions-so-far in `map.md`.