From 36536a583815059fa2c048f9e7bf9e627b721cae Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:15:56 +0700 Subject: [PATCH 01/17] =?UTF-8?q?chore(kanban):=20task=20018=20=E2=80=94?= =?UTF-8?q?=20rename=20diagnostics=20off=20TWA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture now owns TWA####; this package must move TWA001/TWA002 (and clean TW/TWG stew) to a distinct prefix (recommend TWS). --- ...efixes-off-twa-to-free-architecture-twa.md | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md diff --git a/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md b/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md new file mode 100644 index 0000000..c0df67a --- /dev/null +++ b/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md @@ -0,0 +1,62 @@ +# Rename diagnostic prefixes off TWA to free Architecture TWA + +## Description + +`timewarp-architecture` has renamed its diagnostic prefix **TWPA → TWA** (**T**ime**W**arp **A**rchitecture). Architecture IDs are now `TWA0001`–`TWA0010` (and will grow under `TWA####`). + +This package currently uses **TWA001** / **TWA002** (plus **TW1001**–**TW1003** and **TWG001**), which **collides** with Architecture’s product prefix. Rename this package’s diagnostics to a **distinct** prefix before either product freezes IDs on NuGet. + +## Context + +| Product | Package | Intended prefix | +|---------|---------|-----------------| +| TimeWarp Architecture | `TimeWarp.Architecture.Analyzers` / `.Generators` | **TWA** = TimeWarp Architecture | +| This repo | `TimeWarp.SourceGenerators` | **Not TWA** — free TWA for Architecture | + +Suggested target prefix (pick one and document): + +- **TWS** = TimeWarp Source-generators / tools (recommended), or +- **TW** only, with exclusive number ranges owned by this package + +Do **not** invent TWAA/TWAG role suffixes. Ecosystem convention is one product prefix + digits. + +### Live IDs to rename (code today) + +| Current | Feature | +|---------|---------| +| TWA001 | FileNameRuleAnalyzer (kebab-case) | +| TWA002 | XmlDocsToMarkdownAnalyzer | +| TW1001 | Interface delegation — class must be partial | +| TW1002 | Interface delegation — class doesn’t implement interface | +| TW1003 | Interface delegation — multiple fields for same interface | +| TWG001 | MarkdownDocsGenerator (Unshipped table) | + +Stale kanban/docs still mention TW0001–TW0004 / TW0003 — clean those to the new IDs while at it. + +## Requirements + +- Single product prefix for this package (prefer **TWS####** for all rules, or document a single alternate). +- Update: analyzer/generator source, AnalyzerReleases.*.md, readme, documentation, .editorconfig samples, tests. +- No remaining `TWA####` IDs owned by this package. +- Note Architecture collision and chosen mapping in readme or overview. +- Build green after rename. + +## Checklist + +- [ ] Lock new prefix (recommend TWS) and ID mapping table +- [ ] Rename all DiagnosticId / descriptor IDs in source +- [ ] Update AnalyzerReleases.Shipped.md / Unshipped.md +- [ ] Update readme.md, documentation/, .editorconfig samples +- [ ] Fix stale TW000x references in kanban notes if desired +- [ ] Tests / manual verify diagnostics fire under new IDs +- [ ] Commit + +## Notes + +- Architecture rename commit (timewarp-architecture): `refactor(analyzers): rename diagnostic prefix TWPA to TWA`. +- Related: Architecture packages NuGet as `TimeWarp.Architecture.*` (task 092); this package stays `TimeWarp.SourceGenerators`. +- Worktree: `/home/steve/worktrees/github.com/TimeWarpEngineering/timewarp-source-generators/Cramer-2026-06-30-dev` + +## Session + +- Created: 2026-07-15 (after Architecture TWPA→TWA; free TWA for that product) From 76a17f303576d372ae2afc233f79f7101bf9ed13 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:20:43 +0700 Subject: [PATCH 02/17] =?UTF-8?q?docs(kanban):=20018=20is=20full=20ID=20re?= =?UTF-8?q?name=20=E2=80=94=20no=20TWA=20leftovers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarify: rename every diagnostic in this package to one prefix (TWS); do not leave TWA001/TWA002. Product hygiene, not optional collision fix. --- ...efixes-off-twa-to-free-architecture-twa.md | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md b/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md index c0df67a..edc1336 100644 --- a/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md +++ b/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md @@ -1,62 +1,62 @@ -# Rename diagnostic prefixes off TWA to free Architecture TWA +# Rename ALL diagnostic IDs off TWA / TW / TWG to one product prefix ## Description -`timewarp-architecture` has renamed its diagnostic prefix **TWPA → TWA** (**T**ime**W**arp **A**rchitecture). Architecture IDs are now `TWA0001`–`TWA0010` (and will grow under `TWA####`). +Rename **every** diagnostic ID in `TimeWarp.SourceGenerators` to a **single** product prefix. +Nothing stays on **TWA**, **TW1xxx**, **TWG**, or stale **TW000x**. -This package currently uses **TWA001** / **TWA002** (plus **TW1001**–**TW1003** and **TWG001**), which **collides** with Architecture’s product prefix. Rename this package’s diagnostics to a **distinct** prefix before either product freezes IDs on NuGet. +Architecture owns **TWA** = TimeWarp Architecture (`TWA0001`–`TWA0010` and growing). This package +must stop using TWA entirely so the prefix means one product only. -## Context +This is a deliberate full rename, not an optional collision fix. Technical non-overlap of +`TWA001` vs `TWA0001` is irrelevant — we still rename **all** of them. -| Product | Package | Intended prefix | -|---------|---------|-----------------| -| TimeWarp Architecture | `TimeWarp.Architecture.Analyzers` / `.Generators` | **TWA** = TimeWarp Architecture | -| This repo | `TimeWarp.SourceGenerators` | **Not TWA** — free TWA for Architecture | +## Target prefix (recommended) -Suggested target prefix (pick one and document): +**TWS** = TimeWarp Source-generators (or document another single prefix if preferred). -- **TWS** = TimeWarp Source-generators / tools (recommended), or -- **TW** only, with exclusive number ranges owned by this package +One prefix for analyzers **and** generators that report diagnostics. No TWAA/TWAG role suffixes. -Do **not** invent TWAA/TWAG role suffixes. Ecosystem convention is one product prefix + digits. +### Full mapping (draft — adjust numbers if you want contiguous ranges) -### Live IDs to rename (code today) +| Current (leave none) | Suggested | Feature | +|----------------------|-----------|---------| +| TWA001 | **TWS0001** | FileNameRuleAnalyzer (kebab-case) | +| TWA002 | **TWS0002** | XmlDocsToMarkdownAnalyzer | +| TWG001 | **TWS0003** | MarkdownDocsGenerator (if it still reports) | +| TW1001 | **TWS1001** | Interface delegation — class must be partial | +| TW1002 | **TWS1002** | Interface delegation — doesn’t implement interface | +| TW1003 | **TWS1003** | Interface delegation — multiple fields same interface | -| Current | Feature | -|---------|---------| -| TWA001 | FileNameRuleAnalyzer (kebab-case) | -| TWA002 | XmlDocsToMarkdownAnalyzer | -| TW1001 | Interface delegation — class must be partial | -| TW1002 | Interface delegation — class doesn’t implement interface | -| TW1003 | Interface delegation — multiple fields for same interface | -| TWG001 | MarkdownDocsGenerator (Unshipped table) | +(Or renumber 1001–1003 into TWS0004–0006 — pick one scheme and stick to it.) -Stale kanban/docs still mention TW0001–TW0004 / TW0003 — clean those to the new IDs while at it. +Also scrub historical **TW0001–TW0004 / TW0003** mentions in kanban/docs so they don’t reintroduce confusion. ## Requirements -- Single product prefix for this package (prefer **TWS####** for all rules, or document a single alternate). -- Update: analyzer/generator source, AnalyzerReleases.*.md, readme, documentation, .editorconfig samples, tests. -- No remaining `TWA####` IDs owned by this package. -- Note Architecture collision and chosen mapping in readme or overview. +- **Zero** remaining `TWA*`, `TW1*`, `TWG*`, or `TW000*` diagnostic IDs in **live code**. +- Update: all DiagnosticId / descriptor IDs, AnalyzerReleases.*.md, readme, documentation, + .editorconfig samples, tests, any help links. +- Document: **TWS** (or chosen prefix) = this package only; Architecture = **TWA**. - Build green after rename. ## Checklist -- [ ] Lock new prefix (recommend TWS) and ID mapping table -- [ ] Rename all DiagnosticId / descriptor IDs in source -- [ ] Update AnalyzerReleases.Shipped.md / Unshipped.md -- [ ] Update readme.md, documentation/, .editorconfig samples -- [ ] Fix stale TW000x references in kanban notes if desired -- [ ] Tests / manual verify diagnostics fire under new IDs +- [ ] Lock prefix (TWS) and complete ID mapping table in Notes/Results +- [ ] Rename **all** IDs in source (no TWA leftovers) +- [ ] AnalyzerReleases.Shipped.md / Unshipped.md +- [ ] readme.md, documentation/, .editorconfig samples +- [ ] Tests assert new IDs +- [ ] Grep repo: no TWA001, TWA002, TW1001–1003, TWG001 in code - [ ] Commit ## Notes -- Architecture rename commit (timewarp-architecture): `refactor(analyzers): rename diagnostic prefix TWPA to TWA`. -- Related: Architecture packages NuGet as `TimeWarp.Architecture.*` (task 092); this package stays `TimeWarp.SourceGenerators`. +- Architecture already renamed TWPA → TWA (timewarp-architecture). +- Full rename of this package is intentional product hygiene, not “only if IDs collide.” - Worktree: `/home/steve/worktrees/github.com/TimeWarpEngineering/timewarp-source-generators/Cramer-2026-06-30-dev` ## Session -- Created: 2026-07-15 (after Architecture TWPA→TWA; free TWA for that product) +- Created: 2026-07-15 +- Clarified: rename **ALL** IDs; do not leave TWA001/TWA002 From b0ebbcb13aa1369a172fc7a439b23481b8eb2c4a Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:22:03 +0700 Subject: [PATCH 03/17] docs(kanban): 018 lock prefix TW for SourceGenerators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recommend TW0001–TW0006; Architecture keeps TWA. Drop TWS suggestion. --- ...efixes-off-twa-to-free-architecture-twa.md | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md b/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md index edc1336..ef770d3 100644 --- a/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md +++ b/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md @@ -1,62 +1,65 @@ -# Rename ALL diagnostic IDs off TWA / TW / TWG to one product prefix +# Rename ALL diagnostic IDs to TW#### (single product prefix) ## Description -Rename **every** diagnostic ID in `TimeWarp.SourceGenerators` to a **single** product prefix. -Nothing stays on **TWA**, **TW1xxx**, **TWG**, or stale **TW000x**. +Rename **every** diagnostic ID in `TimeWarp.SourceGenerators` to **TW** + four digits +(`TW0001`–`TW0006` mapping below). Drop **TWA**, **TWG**, and **TW1xxx** entirely. -Architecture owns **TWA** = TimeWarp Architecture (`TWA0001`–`TWA0010` and growing). This package -must stop using TWA entirely so the prefix means one product only. +Architecture owns **TWA** = TimeWarp Architecture. This package owns **TW** = TimeWarp +(generic tooling / `TimeWarp.SourceGenerators`). -This is a deliberate full rename, not an optional collision fix. Technical non-overlap of -`TWA001` vs `TWA0001` is irrelevant — we still rename **all** of them. +This is a deliberate full rename for product clarity — not an optional collision fix. -## Target prefix (recommended) +## Target prefix (locked recommendation) -**TWS** = TimeWarp Source-generators (or document another single prefix if preferred). +**TW** = TimeWarp (this package / generic tooling). Short, already used for interface-delegation +IDs, matches ecosystem “product acronym + digits.” Architecture keeps **TWA**; this repo does +**not** use TWA at all. -One prefix for analyzers **and** generators that report diagnostics. No TWAA/TWAG role suffixes. +One prefix for analyzers **and** generators. No TWAA/TWAG role suffixes. -### Full mapping (draft — adjust numbers if you want contiguous ranges) +### Full mapping (contiguous TW####) -| Current (leave none) | Suggested | Feature | -|----------------------|-----------|---------| -| TWA001 | **TWS0001** | FileNameRuleAnalyzer (kebab-case) | -| TWA002 | **TWS0002** | XmlDocsToMarkdownAnalyzer | -| TWG001 | **TWS0003** | MarkdownDocsGenerator (if it still reports) | -| TW1001 | **TWS1001** | Interface delegation — class must be partial | -| TW1002 | **TWS1002** | Interface delegation — doesn’t implement interface | -| TW1003 | **TWS1003** | Interface delegation — multiple fields same interface | +| Current (leave none) | New | Feature | +|----------------------|-----|---------| +| TWA001 | **TW0001** | FileNameRuleAnalyzer (kebab-case) | +| TWA002 | **TW0002** | XmlDocsToMarkdownAnalyzer | +| TWG001 | **TW0003** | MarkdownDocsGenerator (if it still reports) | +| TW1001 | **TW0004** | Interface delegation — class must be partial | +| TW1002 | **TW0005** | Interface delegation — doesn’t implement interface | +| TW1003 | **TW0006** | Interface delegation — multiple fields same interface | -(Or renumber 1001–1003 into TWS0004–0006 — pick one scheme and stick to it.) - -Also scrub historical **TW0001–TW0004 / TW0003** mentions in kanban/docs so they don’t reintroduce confusion. +Also scrub historical **TW000x** kanban/doc references so they match the new table (not old +TW0003 = kebab under a different scheme). ## Requirements -- **Zero** remaining `TWA*`, `TW1*`, `TWG*`, or `TW000*` diagnostic IDs in **live code**. +- **Zero** remaining `TWA*`, `TW1xxx` (old 1001–1003), or `TWG*` diagnostic IDs in **live code**. +- Single family: **TW0001+** under this package only. - Update: all DiagnosticId / descriptor IDs, AnalyzerReleases.*.md, readme, documentation, .editorconfig samples, tests, any help links. -- Document: **TWS** (or chosen prefix) = this package only; Architecture = **TWA**. +- Document: **TW** = TimeWarp.SourceGenerators; **TWA** = TimeWarp Architecture (other product). - Build green after rename. ## Checklist -- [ ] Lock prefix (TWS) and complete ID mapping table in Notes/Results -- [ ] Rename **all** IDs in source (no TWA leftovers) +- [ ] Apply TW0001–TW0006 mapping (or document deviation) +- [ ] Rename **all** IDs in source (no TWA / TWG / TW100x leftovers) - [ ] AnalyzerReleases.Shipped.md / Unshipped.md - [ ] readme.md, documentation/, .editorconfig samples - [ ] Tests assert new IDs -- [ ] Grep repo: no TWA001, TWA002, TW1001–1003, TWG001 in code +- [ ] Grep: no TWA001, TWA002, TW1001–1003, TWG001 in code - [ ] Commit ## Notes - Architecture already renamed TWPA → TWA (timewarp-architecture). -- Full rename of this package is intentional product hygiene, not “only if IDs collide.” +- Prefix choice: **TW** (not TWS) — simpler brand for the generic generators package. +- Full rename is intentional product hygiene, not “only if IDs collide.” - Worktree: `/home/steve/worktrees/github.com/TimeWarpEngineering/timewarp-source-generators/Cramer-2026-06-30-dev` ## Session - Created: 2026-07-15 - Clarified: rename **ALL** IDs; do not leave TWA001/TWA002 +- Prefix locked: **TW** (recommend contiguous TW0001–TW0006) From 701202364951a28068de9f692970a4b84d862261 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:23:50 +0700 Subject: [PATCH 04/17] chore: align repo with TimeWarp standards audit Bring the repo to pass `ganda repo audit` by scaffolding the dev CLI, standard MSBuild/CPM layout, banned-API rules, memsearch hooks, and VS Code window branding, and by targeting net10.0 so Nuru/Amuru package assets resolve. --- .envrc | 1 + .githooks/post-commit | 1 + .githooks/post-commit.cs | 19 +++ .githooks/post-merge | 1 + .githooks/post-merge.cs | 19 +++ .github/workflows/{ci-cd.yml => workflow.yml} | 2 +- .memsearch.toml | 50 ++++++++ .vscode/settings.json | 6 +- .vscode/tasks.json | 18 +++ BannedSymbols.txt | 3 + Directory.Build.props | 16 ++- Directory.Packages.props | 39 +++--- kanban/archived/.gitkeep | 0 msbuild/repository.props | 15 +++ nuget.config | 1 + skills/.gitkeep | 0 ...warp-source-generators-test-console.csproj | 2 +- tools/dev-cli/Directory.Build.props | 30 +++++ tools/dev-cli/dev.cs | 40 +++++++ tools/dev-cli/endpoints/build-command.cs | 112 ++++++++++++++++++ tools/dev-cli/endpoints/test-command.cs | 89 ++++++++++++++ .../endpoints/verify-samples-command.cs | 30 +++++ tools/dev-cli/endpoints/workflow-command.cs | 71 +++++++++++ tools/dev-cli/global-usings.cs | 21 ++++ tools/dev-cli/source/Directory.Build.props | 20 ++++ 25 files changed, 584 insertions(+), 22 deletions(-) create mode 100644 .envrc create mode 120000 .githooks/post-commit create mode 100755 .githooks/post-commit.cs create mode 120000 .githooks/post-merge create mode 100755 .githooks/post-merge.cs rename .github/workflows/{ci-cd.yml => workflow.yml} (96%) create mode 100644 .memsearch.toml create mode 100644 .vscode/tasks.json create mode 100644 BannedSymbols.txt create mode 100644 kanban/archived/.gitkeep create mode 100644 msbuild/repository.props create mode 100644 skills/.gitkeep create mode 100644 tools/dev-cli/Directory.Build.props create mode 100755 tools/dev-cli/dev.cs create mode 100644 tools/dev-cli/endpoints/build-command.cs create mode 100644 tools/dev-cli/endpoints/test-command.cs create mode 100644 tools/dev-cli/endpoints/verify-samples-command.cs create mode 100644 tools/dev-cli/endpoints/workflow-command.cs create mode 100644 tools/dev-cli/global-usings.cs create mode 100644 tools/dev-cli/source/Directory.Build.props diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..234188d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +PATH_add bin diff --git a/.githooks/post-commit b/.githooks/post-commit new file mode 120000 index 0000000..50e32c5 --- /dev/null +++ b/.githooks/post-commit @@ -0,0 +1 @@ +post-commit.cs \ No newline at end of file diff --git a/.githooks/post-commit.cs b/.githooks/post-commit.cs new file mode 100755 index 0000000..a6f0920 --- /dev/null +++ b/.githooks/post-commit.cs @@ -0,0 +1,19 @@ +#!/usr/bin/env -S dotnet -- +#:package TimeWarp.Amuru +#:package TimeWarp.Amuru.Tools +#:property NoWarn=CA2007 + +using TimeWarp.Amuru; + +string? root = Git.FindRoot(); +if (root is null) +{ + return 0; +} + +await Shell.Builder("ganda") + .WithArguments("memsearch", "index-repo", "--background") + .WithWorkingDirectory(root) + .WithNoValidation() + .RunAsync(); +return 0; \ No newline at end of file diff --git a/.githooks/post-merge b/.githooks/post-merge new file mode 120000 index 0000000..6031064 --- /dev/null +++ b/.githooks/post-merge @@ -0,0 +1 @@ +post-merge.cs \ No newline at end of file diff --git a/.githooks/post-merge.cs b/.githooks/post-merge.cs new file mode 100755 index 0000000..a6f0920 --- /dev/null +++ b/.githooks/post-merge.cs @@ -0,0 +1,19 @@ +#!/usr/bin/env -S dotnet -- +#:package TimeWarp.Amuru +#:package TimeWarp.Amuru.Tools +#:property NoWarn=CA2007 + +using TimeWarp.Amuru; + +string? root = Git.FindRoot(); +if (root is null) +{ + return 0; +} + +await Shell.Builder("ganda") + .WithArguments("memsearch", "index-repo", "--background") + .WithWorkingDirectory(root) + .WithNoValidation() + .RunAsync(); +return 0; \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/workflow.yml similarity index 96% rename from .github/workflows/ci-cd.yml rename to .github/workflows/workflow.yml index aa9e055..eb7631b 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/workflow.yml @@ -54,7 +54,7 @@ jobs: - name: 🛠️ Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '9.0.x' + dotnet-version: '10.0.x' - name: 📁 Create artifacts directory run: mkdir -p artifacts/packages diff --git a/.memsearch.toml b/.memsearch.toml new file mode 100644 index 0000000..00bcc0e --- /dev/null +++ b/.memsearch.toml @@ -0,0 +1,50 @@ +# MemSearch repo knowledge index — TimeWarp convention +# +# SPEC (proposed extension) +# ------------------------- +# memsearch's stock CLI ignores [index]; this file is honored by: +# ganda memsearch index-repo +# ganda hooks install memsearch (post-commit / post-merge runfiles) +# +# Purpose: index markdown knowledge in THIS repo only. One Milvus collection +# per repo (auto-derived from repo path). Global DB file stays at +# ~/.memsearch/milvus.db unless [milvus].uri is overridden below. +# +# [index].paths +# Directory roots (relative to repo root). memsearch indexes all .md files +# under each path recursively. Missing paths are skipped. +# +# [index].exclude_paths +# Directory paths removed from the resolved path list before indexing. +# Does not prune subdirectories when a parent path (e.g. "kanban/") is listed. +# +# [index].exclude_globs +# Reserved — file-level globs (e.g. "**/task-template.md"). Not implemented in v1. +# +# [index].enabled +# false disables hook indexing (conversation plugins still work). +# +# [milvus].collection +# Empty = auto-derive ms__<8char_sha256> from repo root. +# +# Search this repo only: +# memsearch search "query" -c --source-prefix "$(pwd)/kanban/" + +[index] +enabled = true +paths = [ + "kanban", + "documentation", + "okf", + "adr", + ".memsearch/memory", +] +exclude_paths = [] +exclude_globs = [ + "**/task-template.md", + "**/overview.md", +] + +[milvus] +# uri = ".memsearch/milvus.db" # optional per-repo DB file +collection = "" \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index e667d77..770dc45 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,7 @@ "buildtransitive", "contentfiles", "nugets" - ] -} \ No newline at end of file + ], + "window.title": "timewarp-source-generators · ${rootName}${separator}${activeEditorShort}", + "timewarp.blurImagePath": "assets/timewarp-source-generators-avatar.svg" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f82f57f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "ganda: window icon", + "type": "shell", + "command": "ganda repo avatar window", + "runOptions": { + "runOn": "folderOpen" + }, + "presentation": { + "reveal": "silent", + "echo": false + }, + "problemMatcher": [] + } + ] +} diff --git a/BannedSymbols.txt b/BannedSymbols.txt new file mode 100644 index 0000000..94da1d3 --- /dev/null +++ b/BannedSymbols.txt @@ -0,0 +1,3 @@ + +T:System.Console;Prefer injecting ITerminal. TimeWarp.Terminal.Terminal static class is available for migration. +T:System.Diagnostics.ProcessStartInfo;Use TimeWarp.Amuru Shell.Builder instead. See the 'amuru' skill for usage patterns. diff --git a/Directory.Build.props b/Directory.Build.props index fde08c3..f200957 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,6 @@ + + @@ -24,7 +26,7 @@ - net9.0 + net10.0 enable enable latest @@ -57,13 +59,13 @@ - + - + - + @@ -80,6 +82,12 @@ all + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index d7363a6..e426239 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,20 +1,31 @@ + - + + true + + + - - - - - - - + + + - - - - - - + + + + + + + + + + + + + + + + diff --git a/kanban/archived/.gitkeep b/kanban/archived/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/msbuild/repository.props b/msbuild/repository.props new file mode 100644 index 0000000..201f1be --- /dev/null +++ b/msbuild/repository.props @@ -0,0 +1,15 @@ + + + + timewarp-source-generators + $(MSBuildThisFileDirectory)../ + $(RepositoryRoot)source/ + $(RepositoryRoot)tests/ + $(RepositoryRoot)samples/ + $(RepositoryRoot)benchmarks/ + $(RepositoryRoot)runfiles/ + $(RepositoryRoot)tools/ + $(RepositoryRoot)artifacts/ + $(ArtifactsDirectory)packages/ + + diff --git a/nuget.config b/nuget.config index e112e11..fd1c467 100644 --- a/nuget.config +++ b/nuget.config @@ -1,6 +1,7 @@ + diff --git a/skills/.gitkeep b/skills/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj b/tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj index 8a7ec0a..c5c090e 100644 --- a/tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj +++ b/tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 TimeWarp.SourceGenerators.TestConsole enable enable diff --git a/tools/dev-cli/Directory.Build.props b/tools/dev-cli/Directory.Build.props new file mode 100644 index 0000000..e8255dd --- /dev/null +++ b/tools/dev-cli/Directory.Build.props @@ -0,0 +1,30 @@ + + + + + + $(NoWarn);CA1031;CA1303;CA1508;CA1515;CA1849;CA2007;CA2016;CA2000;CA1034;CA5399;IDE0065;IL2026;IL2104;IL3050;IL3053;RCS1046;IDE0005;IDE0052;IDE0055;IDE0058;IDE0160;IDE0211;IDE0290 + + + true + true + + + $(InterceptorsNamespaces);TimeWarp.Nuru.Generated + + + + + + + + + + + + + + + + + diff --git a/tools/dev-cli/dev.cs b/tools/dev-cli/dev.cs new file mode 100755 index 0000000..f820d42 --- /dev/null +++ b/tools/dev-cli/dev.cs @@ -0,0 +1,40 @@ +#!/usr/bin/env -S dotnet -- +// ═══════════════════════════════════════════════════════════════════════════════ +// DEV CLI - timewarp-source-generators DEVELOPMENT TOOL +// ═══════════════════════════════════════════════════════════════════════════════ +// +// Usage: +// As runfile: dotnet run tools/dev-cli/dev.cs -- +// As AOT: ./bin/dev +// +// Run `./bin/dev --help` for available commands. +// +// To bootstrap: +// dotnet run tools/dev-cli/dev.cs -- self-install +// direnv allow +// dev --help +// ═══════════════════════════════════════════════════════════════════════════════ + +#region Purpose +// Entry point for the dev CLI +#endregion +#region Design +// Thin wrapper around TimeWarp.Nuru to execute development commands +#endregion + +NuruApp app = NuruApp.CreateBuilder() + .WithName("dev") + .WithDescription("Development CLI for timewarp-source-generators") + .ConfigureServices(services => + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + }) + .DiscoverEndpoints() + .Build(); + +return await app.RunAsync(args); \ No newline at end of file diff --git a/tools/dev-cli/endpoints/build-command.cs b/tools/dev-cli/endpoints/build-command.cs new file mode 100644 index 0000000..5e3a4cf --- /dev/null +++ b/tools/dev-cli/endpoints/build-command.cs @@ -0,0 +1,112 @@ +#region Purpose +// Build all projects in the repository +#endregion +#region Design +// Discovers the repository root dynamically using Git.FindRoot() +// Handler stores Command and Ct as fields so private methods are zero-parameter +// Streams MSBuild output via Amuru RunAsync by default; --quiet uses CaptureAsync +#endregion + +namespace DevCli.Commands; + +[NuruRoute("build", Description = "Build all projects in the repository")] +internal sealed class BuildCommand : ICommand +{ + [Option("clean", "c", Description = "Clean before building")] + public bool Clean { get; set; } + + [Option("quiet", "q", Description = "Hide build output unless the command fails")] + public bool Quiet { get; set; } + + internal sealed class Handler : ICommandHandler + { + private readonly ITerminal Terminal; + private BuildCommand Command = null!; + private CancellationToken Ct; + private string RepoRoot = null!; + + public Handler(ITerminal terminal) + { + Terminal = terminal; + } + + public async ValueTask Handle(BuildCommand command, CancellationToken ct) + { + Command = command; + Ct = ct; + + if (!FindRepoRoot()) return Value; + if (!await CleanAsync()) return Value; + if (!await BuildAsync()) return Value; + + Terminal.WriteLine("\nBuild completed successfully!".Green()); + return Value; + } + + private bool FindRepoRoot() + { + string? root = Git.FindRoot(); + if (root is null) + { + Terminal.WriteErrorLine("Error: could not find repository root."); + Environment.ExitCode = 1; + return false; + } + RepoRoot = root; + Terminal.WriteLine($"Building repository at {RepoRoot}..."); + return true; + } + + private async Task CleanAsync() + { + if (!Command.Clean) return true; + + Terminal.WriteLine("\nCleaning before build..."); + CommandResult command = DotNet.Clean() + .WithWorkingDirectory(RepoRoot) + .WithNoValidation() + .Build(); + + return await ExecuteAsync(command, "Clean failed!"); + } + + private async Task BuildAsync() + { + Terminal.WriteLine("\nBuilding..."); + CommandResult command = DotNet.Build() + .WithConfiguration("Release") + .WithWorkingDirectory(RepoRoot) + .WithNoValidation() + .Build(); + + return await ExecuteAsync(command, "Build failed!"); + } + + private async Task ExecuteAsync(CommandResult command, string failureMessage) + { + if (Command.Quiet) + { + CommandOutput result = await command.CaptureAsync(Ct); + if (!result.Success) + { + Terminal.WriteErrorLine(result.Combined); + Terminal.WriteErrorLine(failureMessage.Red()); + Environment.ExitCode = 1; + return false; + } + + return true; + } + + int exitCode = await command.RunAsync(Ct); + if (exitCode != 0) + { + Terminal.WriteErrorLine(failureMessage.Red()); + Environment.ExitCode = exitCode; + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/tools/dev-cli/endpoints/test-command.cs b/tools/dev-cli/endpoints/test-command.cs new file mode 100644 index 0000000..53cc87a --- /dev/null +++ b/tools/dev-cli/endpoints/test-command.cs @@ -0,0 +1,89 @@ +#region Purpose +// Run the test suite +#endregion +#region Design +// Executes dotnet test for all tests in the repository +// Handler stores Command and Ct as fields so private methods are zero-parameter +// Streams test output via Amuru RunAsync by default; --quiet uses CaptureAsync +#endregion + +namespace DevCli.Commands; + +[NuruRoute("test", Description = "Run the test suite")] +internal sealed class TestCommand : ICommand +{ + [Option("quiet", "q", Description = "Hide test output unless the command fails")] + public bool Quiet { get; set; } + + internal sealed class Handler : ICommandHandler + { + private readonly ITerminal Terminal; + private TestCommand Command = null!; + private CancellationToken Ct; + private string RepoRoot = null!; + + public Handler(ITerminal terminal) + { + Terminal = terminal; + } + + public async ValueTask Handle(TestCommand command, CancellationToken ct) + { + Command = command; + Ct = ct; + + if (!FindRepoRoot()) return Value; + if (!await TestAsync()) return Value; + + Terminal.WriteLine("\nTests completed successfully!".Green()); + return Value; + } + + private bool FindRepoRoot() + { + string? root = Git.FindRoot(); + if (root is null) + { + Terminal.WriteErrorLine("Error: could not find repository root."); + Environment.ExitCode = 1; + return false; + } + RepoRoot = root; + Terminal.WriteLine("Running test suite..."); + return true; + } + + private async Task TestAsync() + { + CommandResult command = DotNet.Test() + .WithConfiguration("Release") + .WithWorkingDirectory(RepoRoot) + .WithNoValidation() + .Build(); + + if (Command.Quiet) + { + CommandOutput result = await command.CaptureAsync(Ct); + if (!result.Success) + { + Terminal.WriteErrorLine(result.Combined); + Terminal.WriteErrorLine("Tests failed!".Red()); + Environment.ExitCode = 1; + return false; + } + + return true; + } + + int exitCode = await command.RunAsync(Ct); + if (exitCode != 0) + { + Terminal.WriteErrorLine("Tests failed!".Red()); + Environment.ExitCode = exitCode; + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/tools/dev-cli/endpoints/verify-samples-command.cs b/tools/dev-cli/endpoints/verify-samples-command.cs new file mode 100644 index 0000000..c9c03f9 --- /dev/null +++ b/tools/dev-cli/endpoints/verify-samples-command.cs @@ -0,0 +1,30 @@ +#region Purpose +// Verifies that any code samples in the repository compile +#endregion +#region Design +// Stub command for sample verification +#endregion + +namespace DevCli.Commands; + +[NuruRoute("verify-samples", Description = "Verify code samples compile")] +internal sealed class VerifySamplesCommand : ICommand +{ + internal sealed class Handler : ICommandHandler + { + private readonly ITerminal Terminal; + + public Handler(ITerminal terminal) + { + Terminal = terminal; + } + + public ValueTask Handle(VerifySamplesCommand command, CancellationToken ct) + { + Terminal.WriteLine("Verifying samples..."); + // TODO: Implement sample verification logic specific to this repo + Terminal.WriteLine("Samples verified successfully!"); + return ValueTask.FromResult(Value); + } + } +} diff --git a/tools/dev-cli/endpoints/workflow-command.cs b/tools/dev-cli/endpoints/workflow-command.cs new file mode 100644 index 0000000..7fa980c --- /dev/null +++ b/tools/dev-cli/endpoints/workflow-command.cs @@ -0,0 +1,71 @@ +#region Purpose +// Executes the full CI workflow +#endregion +#region Design +// Runs clean, build, test sequentially by invoking ./bin/dev subcommands +// Handler stores RepoRoot and DevBin as fields so private methods are zero-parameter +// RunStepAsync DRYs up the identical shell-invoke/exit-code-check pattern +#endregion + +namespace DevCli.Commands; + +[NuruRoute("workflow", Description = "Execute full CI workflow")] +internal sealed class WorkflowCommand : ICommand +{ + internal sealed class Handler : ICommandHandler + { + private readonly ITerminal Terminal; + private CancellationToken Ct; + private string RepoRoot = null!; + private string DevBin = null!; + + public Handler(ITerminal terminal) + { + Terminal = terminal; + } + + public async ValueTask Handle(WorkflowCommand command, CancellationToken ct) + { + Ct = ct; + + if (!FindRepoRoot()) return Value; + if (!await RunStepAsync("clean", "Clean failed!")) return Value; + if (!await RunStepAsync("build", "Build failed!")) return Value; + if (!await RunStepAsync("test", "Tests failed!")) return Value; + + Terminal.WriteLine("\nWorkflow completed successfully!".Green()); + return Value; + } + + private bool FindRepoRoot() + { + string? root = Git.FindRoot(); + if (root is null) + { + Terminal.WriteErrorLine("Error: could not find repository root."); + Environment.ExitCode = 1; + return false; + } + RepoRoot = root; + DevBin = Path.Combine(RepoRoot, "bin", "dev"); + Terminal.WriteLine("Starting CI workflow..."); + return true; + } + + private async Task RunStepAsync(string subcommand, string failureMessage) + { + int exitCode = await Shell.Builder(DevBin) + .WithArguments(subcommand) + .WithNoValidation() + .RunAsync(Ct); + + if (exitCode != 0) + { + Terminal.WriteErrorLine(failureMessage.Red()); + Environment.ExitCode = exitCode; + return false; + } + return true; + } + } +} diff --git a/tools/dev-cli/global-usings.cs b/tools/dev-cli/global-usings.cs new file mode 100644 index 0000000..fff0e42 --- /dev/null +++ b/tools/dev-cli/global-usings.cs @@ -0,0 +1,21 @@ +#region Purpose +// Global usings for dev-cli +#endregion + +global using System; +global using System.IO; +global using System.Linq; +global using System.Threading; +global using System.Threading.Tasks; +global using System.Collections.Generic; +global using System.Diagnostics; +global using System.Runtime.CompilerServices; +global using System.Runtime.InteropServices; +global using System.Xml.Linq; + +global using TimeWarp.Nuru; +global using static TimeWarp.Nuru.Unit; +global using TimeWarp.Amuru; +global using TimeWarp.Terminal; +global using DevCli; +global using Microsoft.Extensions.DependencyInjection; \ No newline at end of file diff --git a/tools/dev-cli/source/Directory.Build.props b/tools/dev-cli/source/Directory.Build.props new file mode 100644 index 0000000..d59f3f2 --- /dev/null +++ b/tools/dev-cli/source/Directory.Build.props @@ -0,0 +1,20 @@ + + + + + + + 1.0.0-beta.1 + TimeWarpEngineering + https://github.com/TimeWarpEngineering/timewarp-source-generators + https://github.com/TimeWarpEngineering/timewarp-source-generators + git + Unlicense + logo.png + + + + + + + From e52a38f8438ef128220b75b5e70e25beb3b5ec41 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:28:43 +0700 Subject: [PATCH 05/17] docs(kanban): 019 modernize GitHub workflow to TimeWarp CI standard Track thin YAML + OIDC + dev-cli pipeline work after the standards audit left the old PowerShell release flow in place. --- ...github-workflow-to-timewarp-ci-standard.md | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 kanban/to-do/019-modernize-github-workflow-to-timewarp-ci-standard.md diff --git a/kanban/to-do/019-modernize-github-workflow-to-timewarp-ci-standard.md b/kanban/to-do/019-modernize-github-workflow-to-timewarp-ci-standard.md new file mode 100644 index 0000000..083512c --- /dev/null +++ b/kanban/to-do/019-modernize-github-workflow-to-timewarp-ci-standard.md @@ -0,0 +1,110 @@ +# Modernize GitHub workflow to TimeWarp CI standard + +## Description + +Bring `.github/workflows/workflow.yml` in line with peer TimeWarp repos (nuru, builder, +options-validation, terminal): thin YAML, OIDC NuGet Trusted Publishing, and +`tools/dev-cli` as the CI entry point. + +The file was renamed from `ci-cd.yml` → `workflow.yml` and the SDK bumped to `10.0.x` for +the standards audit. The pipeline body is still the old hand-rolled PowerShell flow and is +**not** up to current CI standards. + +## Requirements + +### Workflow YAML (thin pipeline) + +Match the peer pattern: + +1. Checkout with `fetch-depth: 0` +2. Setup .NET `10.0.x` +3. `nuget/login@v1` (OIDC) on release — `permissions: contents: read` + `id-token: write` +4. Run CI via dev-cli, e.g. + `dotnet run --file tools/dev-cli/dev.cs -- workflow` + (release: pass `--api-key` from nuget login when supported) +5. Upload `artifacts/packages/*.nupkg` (`if-no-files-found: ignore`, prefer `if: always()`) + +### Path filters + +Ensure changes that affect CI/build retrigger the workflow: + +- Keep: `source/**`, `tests/**`, `.github/workflows/**`, `Directory.Build.props` +- Add: `tools/**`, `Directory.Packages.props`, `msbuild/**` (or equivalent) +- Drop or justify loose `*.props` / `*.targets` if superseded by explicit paths + +### NuGet cache + +Either: + +- Cache `.nuget-cache/` (matches `RestorePackagesPath` in `Directory.Build.props`), or +- Stop setting a custom `RestorePackagesPath` and keep caching `~/.nuget/packages` + +Current cache of `~/.nuget/packages` is ineffective while restore uses `.nuget-cache/`. + +### Dev CLI (`workflow` command) + +Today `tools/dev-cli/endpoints/workflow-command.cs` only runs +`clean → build → test` via `./bin/dev` and has **no** pack/push/`--api-key` path. + +Either: + +- **A (preferred):** Extend `dev workflow` so it can own PR + release (mode/event detection, + pack, version check, NuGet push with optional API key) like peer repos, **or** +- **B:** Keep release pack/push as explicit YAML steps until A is done; still invoke + `dev` for build/test so the thin-YAML shape is partial but usable + +Document which option was chosen in Results. + +### Tests + +- Remove the dead TODO path `tests/timewarp-source-generators-tests/` +- Wire real verification (test console and/or `dev test` / `dev verify-samples`) so CI + fails on regressions — or document intentional deferral with a follow-up task + +### Auth + +- Prefer OIDC Trusted Publishing (`nuget/login@v1`, user `TimeWarp.Enterprises`) +- Retire `secrets.PUBLISH_TO_NUGET_ORG` unless still required as fallback + +## Checklist + +- [ ] Rewrite `.github/workflows/workflow.yml` to thin peer-style pipeline +- [ ] Add OIDC permissions + `nuget/login@v1` for release +- [ ] Fix path filters (`tools/**`, `Directory.Packages.props`, etc.) +- [ ] Fix NuGet cache path vs `RestorePackagesPath` +- [ ] Extend or document `dev workflow` for pack/push (option A or B) +- [ ] Enable real test/verify step; remove dead test path +- [ ] Drop legacy `PUBLISH_TO_NUGET_ORG` push when OIDC works +- [ ] Validate PR path: build (+ tests) without publish +- [ ] Validate release path: version check + pack + push (or dry-run notes) +- [ ] Commit + +## Notes + +### Current workflow issues (review 2026-07-15) + +| Issue | Detail | +|-------|--------| +| Legacy body | Inline pwsh: build project, optional version search, `dotnet nuget push` with secret | +| Auth | `secrets.PUBLISH_TO_NUGET_ORG` — peers use OIDC | +| Path filters | Missing `tools/**`, `Directory.Packages.props` | +| Cache | Caches `~/.nuget/packages`; restore uses `.nuget-cache/` | +| Tests | Commented; wrong folder name | +| `dev workflow` | Needs prebuilt `./bin/dev`; no pack/push/`--api-key` | + +### Reference workflows + +- `timewarp-nuru` / `timewarp-builder` / `timewarp-options-validation` / `timewarp-terminal` + → `.github/workflows/workflow.yml` + +### What’s already good + +- Filename `workflow.yml` (audit) +- SDK `10.0.x` +- Triggers: push/PR → master, release published, workflow_dispatch +- Version still lives in `source/Directory.Build.props` (`GeneratePackageOnBuild` → + `artifacts/packages/`) + +## Session + +- Created: 2026-07-15 (post `ganda repo audit --fix` + workflow review) From a97f4e69d4ac7ca34b80f6412e25de2f6a1618e5 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:29:07 +0700 Subject: [PATCH 06/17] =?UTF-8?q?chore(kanban):=20start=20018=20=E2=80=94?= =?UTF-8?q?=20rename=20diagnostic=20IDs=20to=20TW####?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move task to in-progress for orchestration. --- ...rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename kanban/{to-do => in-progress}/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md (100%) diff --git a/kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md b/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md similarity index 100% rename from kanban/to-do/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md rename to kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md From 6c7f552f24fae3144e8f1708fed2e7d806551cc0 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:30:44 +0700 Subject: [PATCH 07/17] =?UTF-8?q?docs(kanban):=20018=20implementation=20pl?= =?UTF-8?q?an=20for=20TW0001=E2=80=93TW0006=20rename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capture inventory, ordered steps, verification, and out-of-scope after planning. --- ...efixes-off-twa-to-free-architecture-twa.md | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md b/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md index ef770d3..aafeda9 100644 --- a/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md +++ b/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md @@ -58,8 +58,63 @@ TW0003 = kebab under a different scheme). - Full rename is intentional product hygiene, not “only if IDs collide.” - Worktree: `/home/steve/worktrees/github.com/TimeWarpEngineering/timewarp-source-generators/Cramer-2026-06-30-dev` +### Implementation plan (2026-07-15) + +**Confirmed:** TWG001 still reports on generator load — rename to TW0003, do not drop. + +#### Live source + +| File | Change | +|------|--------| +| `file-name-rule-analyzer.cs` | TWA001→TW0001; also `dotnet_diagnostic.TW0001.excluded_files` config key | +| `xml-docs-to-markdown-analyzer.cs` | TWA002→TW0002 | +| `markdown-docs-generator.cs` | TWG001→TW0003 | +| `interface-delegation-generator.cs` | TW1001→TW0004, TW1002→TW0005, TW1003→TW0006 | +| `AnalyzerReleases.Unshipped.md` | Replace all six rows with TW0001–TW0006 | +| `AnalyzerReleases.Shipped.md` | Leave empty (always-unshipped policy) | + +#### Config / docs / open kanban + +1. Root + test-console `.editorconfig` (TWA001/TWA002 → TW0001/TW0002) +2. `readme.md` + TW vs TWA product-prefix note +3. `documentation/**` — especially `configure-file-name-analyzer.md` (old TW0003=kebab → **TW0001**) +4. Open tasks 015, 016, 017 (fix + interface draft) — scrub wrong scheme IDs +5. Leave `kanban/done/*` history as-is (optional one-line notes only) + +#### Order + +1. Source descriptors + Unshipped (same change set — RS2008) +2. EditorConfig + readme +3. documentation/ +4. Open kanban scrub +5. Build + grep verify +6. Commit + +#### Verification + +```bash +dotnet build timewarp-source-generators.slnx -c Release +dotnet build tests/timewarp-source-generators-test-console/ -c Release +rg -n 'TWA00[12]|TWG001|TW100[1-3]' source/ documentation/ readme.md .editorconfig tests/ +rg -n 'TW000[1-6]' source/timewarp-source-generators/ +``` + +Pass: build green; zero old IDs on live product surface; Unshipped matches code. + +#### Out of scope + +- Renaming 017 folder name; implementing 015/016 code-fixes; unit-test project for diagnostics; + severity/behavior changes; Unshipped→Shipped move; dual-ID compatibility layer. + +#### Gotchas + +- Config key is a literal string separate from `DiagnosticId` — both must change. +- Do not blind-sed `TW0003` in docs after rename (it correctly means MarkdownDocs load). +- Change source + Unshipped together. + ## Session - Created: 2026-07-15 - Clarified: rename **ALL** IDs; do not leave TWA001/TWA002 - Prefix locked: **TW** (recommend contiguous TW0001–TW0006) +- Plan: 2026-07-15 (orchestrate-task phase 2–3) From 72e360e866bf06cf2c6906beb1f3b3235a59d53d Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:36:05 +0700 Subject: [PATCH 08/17] =?UTF-8?q?refactor:=20rename=20all=20diagnostic=20I?= =?UTF-8?q?Ds=20to=20TW0001=E2=80=93TW0006?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop TWA/TWG/TW1xxx so TimeWarp Architecture can own TWA. Single TW family for TimeWarp.SourceGenerators analyzers and generators; update Unshipped releases, docs, editorconfig, and open kanban references. --- .editorconfig | 4 +- .../conceptual/analyzer-release-tracking.md | 6 +- .../configure-file-name-analyzer.md | 140 +++++----- .../analyzers/file-name-rule-analyzer.md | 256 +++++++++--------- .../developer/reference/analyzers/overview.md | 96 +++---- documentation/developer/reference/overview.md | 2 +- .../reference/source-generators/overview.md | 2 +- documentation/overview.md | 72 ++--- .../task.md | 16 +- .../task.md | 8 +- .../task.md | 8 +- .../task.md | 8 +- readme.md | 10 +- .../AnalyzerReleases.Unshipped.md | 20 +- .../file-name-rule-analyzer.cs | 4 +- .../interface-delegation-generator.cs | 6 +- .../markdown-docs-generator.cs | 2 +- .../xml-docs-to-markdown-analyzer.cs | 2 +- .../.editorconfig | 8 +- 19 files changed, 336 insertions(+), 334 deletions(-) diff --git a/.editorconfig b/.editorconfig index 7c84cac..557c197 100644 --- a/.editorconfig +++ b/.editorconfig @@ -274,8 +274,8 @@ dotnet_naming_style.local_function_style.capitalization = pascal_case # dotnet_diagnostic options dotnet_diagnostic.CS1591.severity = none -dotnet_diagnostic.TWA001.severity = none # Set to warning/error to enforce kebab-case file naming -dotnet_diagnostic.TWA001.excluded_files = *.g.cs;*.Generated.cs;*.generated.cs;*.designer.cs;*.Designer.cs;*.razor.cs;Directory.Build.props;Directory.Build.targets;Directory.Packages.props;AssemblyInfo.cs;*.AssemblyAttributes.cs;AnalyzerReleases.Shipped.md;AnalyzerReleases.Unshipped.md +dotnet_diagnostic.TW0001.severity = none # Set to warning/error to enforce kebab-case file naming +dotnet_diagnostic.TW0001.excluded_files = *.g.cs;*.Generated.cs;*.generated.cs;*.designer.cs;*.Designer.cs;*.razor.cs;Directory.Build.props;Directory.Build.targets;Directory.Packages.props;AssemblyInfo.cs;*.AssemblyAttributes.cs;AnalyzerReleases.Shipped.md;AnalyzerReleases.Unshipped.md #### Analyzer settings #### dotnet_code_quality.null_check_validation_methods = NotNull diff --git a/documentation/developer/conceptual/analyzer-release-tracking.md b/documentation/developer/conceptual/analyzer-release-tracking.md index da4bc3a..e94ad94 100644 --- a/documentation/developer/conceptual/analyzer-release-tracking.md +++ b/documentation/developer/conceptual/analyzer-release-tracking.md @@ -71,9 +71,9 @@ AnalyzerReleases.Unshipped.md: ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- -TWG001 | SourceGenerator | Info | MarkdownDocsGenerator -TWA001 | Naming | Info | FileNameRuleAnalyzer, disabled by default -TWA002 | Documentation | Info | XmlDocsToMarkdownAnalyzer +TW0001 | Naming | Info | FileNameRuleAnalyzer, disabled by default +TW0002 | Documentation | Info | XmlDocsToMarkdownAnalyzer +TW0003 | SourceGenerator | Info | MarkdownDocsGenerator AnalyzerReleases.Shipped.md: ## Release 1.0 diff --git a/documentation/developer/how-to-guides/configure-file-name-analyzer.md b/documentation/developer/how-to-guides/configure-file-name-analyzer.md index dff5f10..a717bc9 100644 --- a/documentation/developer/how-to-guides/configure-file-name-analyzer.md +++ b/documentation/developer/how-to-guides/configure-file-name-analyzer.md @@ -1,70 +1,70 @@ -# How to Configure the File Name Analyzer - -This guide walks you through configuring the TW0003 file name analyzer to enforce kebab-case naming in your project. - -## Prerequisites - -- TimeWarp.SourceGenerators package referenced in your project -- An `.editorconfig` file in your project root - -## Step 1: Enable the Analyzer - -Add the following to your `.editorconfig`: - -```ini -[*.cs] -dotnet_diagnostic.TW0003.severity = warning -``` - -Severity options: -- `none` - Disabled (default) -- `suggestion` - Shows as info in IDE -- `warning` - Shows as warning -- `error` - Fails the build - -## Step 2: Configure Exceptions - -Add patterns for files that should be excluded: - -```ini -dotnet_diagnostic.TW0003.excluded_files = *.g.cs;*.Generated.cs;Program.cs -``` - -## Step 3: Test the Configuration - -1. Build your project -2. Check for TW0003 warnings on non-kebab-case files -3. Verify exceptions are working - -## Step 4: Gradual Adoption - -For existing projects, start with specific folders: - -```ini -# Global setting - suggestion only -[*.cs] -dotnet_diagnostic.TW0003.severity = suggestion - -# Strict for new features -[Features/**.cs] -dotnet_diagnostic.TW0003.severity = error -``` - -## Step 5: Fix Violations - -Rename files to kebab-case: -- `UserService.cs` → `user-service.cs` -- `IDataProcessor.cs` → `i-data-processor.cs` - -## Troubleshooting - -### Too Many Violations -Add more exception patterns or reduce severity to `suggestion`. - -### Exceptions Not Working -- Check pattern syntax (use `*` for wildcards) -- Verify `.editorconfig` is in the correct location - -### Analyzer Not Running -- Clean and rebuild the project -- Check that the package is properly referenced \ No newline at end of file +# How to Configure the File Name Analyzer + +This guide walks you through configuring the TW0001 file name analyzer to enforce kebab-case naming in your project. + +## Prerequisites + +- TimeWarp.SourceGenerators package referenced in your project +- An `.editorconfig` file in your project root + +## Step 1: Enable the Analyzer + +Add the following to your `.editorconfig`: + +```ini +[*.cs] +dotnet_diagnostic.TW0001.severity = warning +``` + +Severity options: +- `none` - Disabled (default) +- `suggestion` - Shows as info in IDE +- `warning` - Shows as warning +- `error` - Fails the build + +## Step 2: Configure Exceptions + +Add patterns for files that should be excluded: + +```ini +dotnet_diagnostic.TW0001.excluded_files = *.g.cs;*.Generated.cs;Program.cs +``` + +## Step 3: Test the Configuration + +1. Build your project +2. Check for TW0001 warnings on non-kebab-case files +3. Verify exceptions are working + +## Step 4: Gradual Adoption + +For existing projects, start with specific folders: + +```ini +# Global setting - suggestion only +[*.cs] +dotnet_diagnostic.TW0001.severity = suggestion + +# Strict for new features +[Features/**.cs] +dotnet_diagnostic.TW0001.severity = error +``` + +## Step 5: Fix Violations + +Rename files to kebab-case: +- `UserService.cs` → `user-service.cs` +- `IDataProcessor.cs` → `i-data-processor.cs` + +## Troubleshooting + +### Too Many Violations +Add more exception patterns or reduce severity to `suggestion`. + +### Exceptions Not Working +- Check pattern syntax (use `*` for wildcards) +- Verify `.editorconfig` is in the correct location + +### Analyzer Not Running +- Clean and rebuild the project +- Check that the package is properly referenced diff --git a/documentation/developer/reference/analyzers/file-name-rule-analyzer.md b/documentation/developer/reference/analyzers/file-name-rule-analyzer.md index 1c8bb26..0930838 100644 --- a/documentation/developer/reference/analyzers/file-name-rule-analyzer.md +++ b/documentation/developer/reference/analyzers/file-name-rule-analyzer.md @@ -1,128 +1,128 @@ -# FileNameRuleAnalyzer (TWA001) - -## Overview - -The FileNameRuleAnalyzer enforces kebab-case naming conventions for C# source files. This analyzer helps maintain consistent file naming across projects that prefer kebab-case (e.g., `my-component.cs`) over traditional PascalCase. - -## Rule Details - -- **Rule ID**: TWA001 -- **Category**: Naming -- **Default Severity**: Info (disabled by default) -- **Message**: File '{0}' should use kebab-case naming convention (e.g., 'my-file.cs') - -## Kebab-Case Pattern - -Valid kebab-case file names must: -- Start with a lowercase letter -- Use only lowercase letters and numbers -- Separate words with hyphens (-) -- Not contain consecutive hyphens -- End with `.cs` extension - -### Valid Examples -- `user-service.cs` -- `data-processor.cs` -- `my-component.cs` -- `api-client-v2.cs` - -### Invalid Examples -- `UserService.cs` (PascalCase) -- `userService.cs` (camelCase) -- `user_service.cs` (snake_case) -- `user--service.cs` (consecutive hyphens) -- `-user-service.cs` (starts with hyphen) - -## Configuration - -### Enabling the Analyzer - -The analyzer is disabled by default to avoid breaking existing projects. To enable it, add to your `.editorconfig`: - -```ini -# Enable with warning severity -dotnet_diagnostic.TWA001.severity = warning - -# Or as an error to fail builds -dotnet_diagnostic.TWA001.severity = error - -# Or as a suggestion for gentle nudging -dotnet_diagnostic.TWA001.severity = suggestion -``` - -### Exception Patterns - -Configure files that should be excluded from the rule: - -```ini -dotnet_diagnostic.TWA001.excluded_files = *.g.cs;*.Generated.cs;GlobalUsings.cs;Program.cs;Startup.cs -``` - -Default exceptions include: -- Generated files (`*.g.cs`, `*.Generated.cs`, `*.designer.cs`) -- Razor component code-behind files (`*.razor.cs`) - must match their `.razor` file names -- Build files (`Directory.Build.props`, `Directory.Build.targets`, `Directory.Packages.props`) -- Assembly metadata (`AssemblyInfo.cs`) -- Analyzer tracking files (`AnalyzerReleases.Shipped.md`, `AnalyzerReleases.Unshipped.md`) - -## Implementation Details - -The analyzer is implemented as an `IIncrementalGenerator` for optimal performance: -- Only re-analyzes changed files -- Efficient caching of analysis results -- Minimal impact on build times - -## Use Cases - -This analyzer is useful for: -- Teams transitioning from other languages (e.g., JavaScript/TypeScript) that use kebab-case -- Projects that want to align file naming with URL patterns -- Maintaining consistency with kebab-case component naming in frameworks - -## Examples - -### Project-Wide Configuration - -In your root `.editorconfig`: - -```ini -[*.cs] -# Enable kebab-case file naming for all C# files -dotnet_diagnostic.TWA001.severity = warning - -# But allow exceptions for specific patterns -dotnet_diagnostic.TWA001.excluded_files = *.g.cs;*.Generated.cs;*.designer.cs;Program.cs;Startup.cs;GlobalUsings.cs;AssemblyInfo.cs -``` - -### Gradual Adoption - -For existing projects, start with suggestion severity: - -```ini -[*.cs] -dotnet_diagnostic.TWA001.severity = suggestion - -# Only enforce in new feature folders -[Features/**.cs] -dotnet_diagnostic.TWA001.severity = warning -``` - -## Suppressing Violations - -For specific files that need exceptions beyond the configured patterns: - -```csharp -#pragma warning disable TWA001 // File name should use kebab-case -// File: LegacyAPIClient.cs -#pragma warning restore TWA001 -``` - -Or in the project file: - -```xml - - - TWA001 - - -``` \ No newline at end of file +# FileNameRuleAnalyzer (TW0001) + +## Overview + +The FileNameRuleAnalyzer enforces kebab-case naming conventions for C# source files. This analyzer helps maintain consistent file naming across projects that prefer kebab-case (e.g., `my-component.cs`) over traditional PascalCase. + +## Rule Details + +- **Rule ID**: TW0001 +- **Category**: Naming +- **Default Severity**: Info (disabled by default) +- **Message**: File '{0}' should use kebab-case naming convention (e.g., 'my-file.cs') + +## Kebab-Case Pattern + +Valid kebab-case file names must: +- Start with a lowercase letter +- Use only lowercase letters and numbers +- Separate words with hyphens (-) +- Not contain consecutive hyphens +- End with `.cs` extension + +### Valid Examples +- `user-service.cs` +- `data-processor.cs` +- `my-component.cs` +- `api-client-v2.cs` + +### Invalid Examples +- `UserService.cs` (PascalCase) +- `userService.cs` (camelCase) +- `user_service.cs` (snake_case) +- `user--service.cs` (consecutive hyphens) +- `-user-service.cs` (starts with hyphen) + +## Configuration + +### Enabling the Analyzer + +The analyzer is disabled by default to avoid breaking existing projects. To enable it, add to your `.editorconfig`: + +```ini +# Enable with warning severity +dotnet_diagnostic.TW0001.severity = warning + +# Or as an error to fail builds +dotnet_diagnostic.TW0001.severity = error + +# Or as a suggestion for gentle nudging +dotnet_diagnostic.TW0001.severity = suggestion +``` + +### Exception Patterns + +Configure files that should be excluded from the rule: + +```ini +dotnet_diagnostic.TW0001.excluded_files = *.g.cs;*.Generated.cs;GlobalUsings.cs;Program.cs;Startup.cs +``` + +Default exceptions include: +- Generated files (`*.g.cs`, `*.Generated.cs`, `*.designer.cs`) +- Razor component code-behind files (`*.razor.cs`) - must match their `.razor` file names +- Build files (`Directory.Build.props`, `Directory.Build.targets`, `Directory.Packages.props`) +- Assembly metadata (`AssemblyInfo.cs`) +- Analyzer tracking files (`AnalyzerReleases.Shipped.md`, `AnalyzerReleases.Unshipped.md`) + +## Implementation Details + +The analyzer is implemented as an `IIncrementalGenerator` for optimal performance: +- Only re-analyzes changed files +- Efficient caching of analysis results +- Minimal impact on build times + +## Use Cases + +This analyzer is useful for: +- Teams transitioning from other languages (e.g., JavaScript/TypeScript) that use kebab-case +- Projects that want to align file naming with URL patterns +- Maintaining consistency with kebab-case component naming in frameworks + +## Examples + +### Project-Wide Configuration + +In your root `.editorconfig`: + +```ini +[*.cs] +# Enable kebab-case file naming for all C# files +dotnet_diagnostic.TW0001.severity = warning + +# But allow exceptions for specific patterns +dotnet_diagnostic.TW0001.excluded_files = *.g.cs;*.Generated.cs;*.designer.cs;Program.cs;Startup.cs;GlobalUsings.cs;AssemblyInfo.cs +``` + +### Gradual Adoption + +For existing projects, start with suggestion severity: + +```ini +[*.cs] +dotnet_diagnostic.TW0001.severity = suggestion + +# Only enforce in new feature folders +[Features/**.cs] +dotnet_diagnostic.TW0001.severity = warning +``` + +## Suppressing Violations + +For specific files that need exceptions beyond the configured patterns: + +```csharp +#pragma warning disable TW0001 // File name should use kebab-case +// File: LegacyAPIClient.cs +#pragma warning restore TW0001 +``` + +Or in the project file: + +```xml + + + TW0001 + + +``` diff --git a/documentation/developer/reference/analyzers/overview.md b/documentation/developer/reference/analyzers/overview.md index 41b10a8..c77afc6 100644 --- a/documentation/developer/reference/analyzers/overview.md +++ b/documentation/developer/reference/analyzers/overview.md @@ -1,48 +1,48 @@ -# Analyzers Reference - -## Available Analyzers - -### TWA001 - File Name Rule Analyzer - -Enforces kebab-case naming convention for C# source files. - -- **Rule ID**: TWA001 -- **Category**: Naming -- **Default Severity**: Info (disabled) -- **Configuration**: `.editorconfig` - -[Full Reference](./file-name-rule-analyzer.md) - -## Common Configuration - -All analyzers are configured through `.editorconfig`: - -```ini -# Enable an analyzer -dotnet_diagnostic.TWA001.severity = warning - -# Configure exceptions -dotnet_diagnostic.TWA001.excluded_files = *.g.cs;*.Generated.cs -``` - -## Analyzer Categories - -- **Naming**: File and identifier naming conventions -- **SourceGenerator**: Diagnostics from source generators - -## Suppression Methods - -1. **File-level pragma**: - ```csharp - #pragma warning disable TWA001 - ``` - -2. **Project-wide**: - ```xml - TWA001 - ``` - -3. **EditorConfig**: - ```ini - dotnet_diagnostic.TWA001.severity = none - ``` \ No newline at end of file +# Analyzers Reference + +## Available Analyzers + +### TW0001 - File Name Rule Analyzer + +Enforces kebab-case naming convention for C# source files. + +- **Rule ID**: TW0001 +- **Category**: Naming +- **Default Severity**: Info (disabled) +- **Configuration**: `.editorconfig` + +[Full Reference](./file-name-rule-analyzer.md) + +## Common Configuration + +All analyzers are configured through `.editorconfig`: + +```ini +# Enable an analyzer +dotnet_diagnostic.TW0001.severity = warning + +# Configure exceptions +dotnet_diagnostic.TW0001.excluded_files = *.g.cs;*.Generated.cs +``` + +## Analyzer Categories + +- **Naming**: File and identifier naming conventions +- **SourceGenerator**: Diagnostics from source generators + +## Suppression Methods + +1. **File-level pragma**: + ```csharp + #pragma warning disable TW0001 + ``` + +2. **Project-wide**: + ```xml + TW0001 + ``` + +3. **EditorConfig**: + ```ini + dotnet_diagnostic.TW0001.severity = none + ``` diff --git a/documentation/developer/reference/overview.md b/documentation/developer/reference/overview.md index 75387e6..00a40ec 100644 --- a/documentation/developer/reference/overview.md +++ b/documentation/developer/reference/overview.md @@ -21,7 +21,7 @@ Complete reference for all source generators: ## API Reference ### DiagnosticDescriptor Properties -- **Id**: Unique identifier (e.g., TW0003) +- **Id**: Unique identifier (e.g., TW0001) - **Category**: Grouping for related rules - **Severity**: Default severity level - **IsEnabledByDefault**: Whether active without configuration diff --git a/documentation/developer/reference/source-generators/overview.md b/documentation/developer/reference/source-generators/overview.md index 709fa70..51d0956 100644 --- a/documentation/developer/reference/source-generators/overview.md +++ b/documentation/developer/reference/source-generators/overview.md @@ -2,7 +2,7 @@ ## Available Source Generators -### MarkdownDocsGenerator (TWG001) +### MarkdownDocsGenerator (TW0003) Generates XML documentation from markdown files for C# classes. diff --git a/documentation/overview.md b/documentation/overview.md index 86a0df0..56ddbb3 100644 --- a/documentation/overview.md +++ b/documentation/overview.md @@ -1,36 +1,36 @@ -# TimeWarp Source Generators Documentation - -## Quick Start - -1. **Add the package**: Reference TimeWarp.SourceGenerators -2. **Configure analyzers**: Edit `.editorconfig` -3. **Build**: See diagnostics and generated code - -## Documentation - -### [Developer Documentation](./developer/overview.md) -Technical documentation for developers using or contributing to the project. - -## What's Included - -### Source Generators -- **MarkdownDocsGenerator** - Generates markdown from XML docs - -### Analyzers -- **TWA001** - File name kebab-case enforcement - -## Configuration - -All configuration through `.editorconfig`: - -```ini -# Example: Enable kebab-case file names -dotnet_diagnostic.TW0003.severity = warning -``` - -## Philosophy - -- Everything uses kebab-case -- Opt-in by default -- Performance first -- Highly configurable \ No newline at end of file +# TimeWarp Source Generators Documentation + +## Quick Start + +1. **Add the package**: Reference TimeWarp.SourceGenerators +2. **Configure analyzers**: Edit `.editorconfig` +3. **Build**: See diagnostics and generated code + +## Documentation + +### [Developer Documentation](./developer/overview.md) +Technical documentation for developers using or contributing to the project. + +## What's Included + +### Source Generators +- **MarkdownDocsGenerator** - Generates markdown from XML docs + +### Analyzers +- **TW0001** - File name kebab-case enforcement + +## Configuration + +All configuration through `.editorconfig`: + +```ini +# Example: Enable kebab-case file names +dotnet_diagnostic.TW0001.severity = warning +``` + +## Philosophy + +- Everything uses kebab-case +- Opt-in by default +- Performance first +- Highly configurable diff --git a/kanban/in-progress/017_fix-tw0003-analyzer-ignoring-generated-files/task.md b/kanban/in-progress/017_fix-tw0003-analyzer-ignoring-generated-files/task.md index 58f3f35..9deff94 100644 --- a/kanban/in-progress/017_fix-tw0003-analyzer-ignoring-generated-files/task.md +++ b/kanban/in-progress/017_fix-tw0003-analyzer-ignoring-generated-files/task.md @@ -1,7 +1,7 @@ -# Fix TW0003 Analyzer to Ignore Generated Files +# Fix TW0001 Analyzer to Ignore Generated Files ## Description -The TW0003 file naming analyzer is incorrectly checking files in build output directories (obj/, bin/) which contain auto-generated build artifacts. The analyzer should skip validation for these directories as they are not user-written source code. +The TW0001 file naming analyzer is incorrectly checking files in build output directories (obj/, bin/) which contain auto-generated build artifacts. The analyzer should skip validation for these directories as they are not user-written source code. ## Problem The analyzer is reporting errors for generated files such as: @@ -11,21 +11,21 @@ The analyzer is reporting errors for generated files such as: These files are created by the build system and their naming conventions are controlled by the .NET SDK, not the user. ## Acceptance Criteria -- [ ] TW0003 analyzer skips files in `/obj/` directories -- [ ] TW0003 analyzer skips files in `/bin/` directories -- [ ] TW0003 analyzer skips files in other common build output directories +- [ ] TW0001 analyzer skips files in `/obj/` directories +- [ ] TW0001 analyzer skips files in `/bin/` directories +- [ ] TW0001 analyzer skips files in other common build output directories - [ ] User source files continue to be validated correctly - [ ] Tests verify that generated files are ignored - [ ] Tests verify that regular source files are still checked ## Technical Details -The fix should be implemented in the TW0003 analyzer by: +The fix should be implemented in the TW0001 analyzer by: 1. Checking if the file path contains `/obj/` or `/bin/` 2. Returning early without reporting diagnostics for these paths 3. Consider using normalized path comparison to handle different path separators ## Implementation Location -The fix should be applied in the TW0003 analyzer implementation, likely in the method that determines whether to analyze a given file. +The fix should be applied in the TW0001 analyzer implementation, likely in the method that determines whether to analyze a given file. ## Test Cases - Verify files in obj/ directory are ignored @@ -35,4 +35,4 @@ The fix should be applied in the TW0003 analyzer implementation, likely in the m - Verify edge cases like files named "obj.cs" in source directories are still checked ## References -- Original issue: `/home/steventcramer/worktrees/github.com/TimeWarpEngineering/timewarp-code/Cramer-2025-07-31-spike/analysis/tw0003-analyzer-issue.md` \ No newline at end of file +- Original issue: `/home/steventcramer/worktrees/github.com/TimeWarpEngineering/timewarp-code/Cramer-2025-07-31-spike/analysis/tw0003-analyzer-issue.md` diff --git a/kanban/to-do/015_create-kebab-case-file-name-code-fix/task.md b/kanban/to-do/015_create-kebab-case-file-name-code-fix/task.md index d4e8bb6..1602714 100644 --- a/kanban/to-do/015_create-kebab-case-file-name-code-fix/task.md +++ b/kanban/to-do/015_create-kebab-case-file-name-code-fix/task.md @@ -6,7 +6,7 @@ Code fix providers cannot be in the same assembly as source generators due to RS1038 error. The Microsoft.CodeAnalysis.Workspaces assembly (required for code fixes) is not provided during command line compilation scenarios. ## Description -Create a code fix provider for the FileNameRuleAnalyzer (TW0003) that automatically renames files from PascalCase or other naming conventions to kebab-case format. +Create a code fix provider for the FileNameRuleAnalyzer (TW0001) that automatically renames files from PascalCase or other naming conventions to kebab-case format. ## Acceptance Criteria - [ ] Code fix provider offers to rename file to kebab-case when diagnostic is reported @@ -18,13 +18,13 @@ Create a code fix provider for the FileNameRuleAnalyzer (TW0003) that automatica - [ ] Provides preview of the new file name before applying ## Technical Details -- Diagnostic ID: TW0003 +- Diagnostic ID: TW0001 - Fix Provider: FileNameRuleCodeFixProvider - Should integrate with existing FileNameRuleAnalyzer ## Implementation Steps 1. Create FileNameRuleCodeFixProvider class -2. Register the code fix for TW0003 diagnostic +2. Register the code fix for TW0001 diagnostic 3. Implement PascalCase to kebab-case conversion logic 4. Handle file renaming through Roslyn workspace APIs 5. Ensure all project references are updated @@ -40,7 +40,7 @@ Create a code fix provider for the FileNameRuleAnalyzer (TW0003) that automatica - Already kebab-case: `my-test.cs` → no change ## Dependencies -- Requires FileNameRuleAnalyzer (TW0003) to be working +- Requires FileNameRuleAnalyzer (TW0001) to be working - Should follow existing code fix provider patterns in the codebase ## Implementation Progress diff --git a/kanban/to-do/016_create-xml-docs-to-markdown-code-fix/task.md b/kanban/to-do/016_create-xml-docs-to-markdown-code-fix/task.md index 2c661b0..ccac926 100644 --- a/kanban/to-do/016_create-xml-docs-to-markdown-code-fix/task.md +++ b/kanban/to-do/016_create-xml-docs-to-markdown-code-fix/task.md @@ -1,10 +1,10 @@ # Create XML Docs to Markdown Code Fix Provider ## Description -Create a code fix provider for the XmlDocsToMarkdownAnalyzer (TW0004) that extracts XML documentation from C# code and converts it to markdown files compatible with the MarkdownDocsGenerator. +Create a code fix provider for the XmlDocsToMarkdownAnalyzer (TW0002) that extracts XML documentation from C# code and converts it to markdown files compatible with the MarkdownDocsGenerator. ## Acceptance Criteria -- [ ] Code fix provider responds to TW0004 diagnostics +- [ ] Code fix provider responds to TW0002 diagnostics - [ ] Extracts all XML documentation elements from type and members - [ ] Converts XML to markdown following MarkdownDocsGenerator format - [ ] Removes XML documentation from source code @@ -54,7 +54,7 @@ Since code fix providers run in IDE/compiler context: ### Code Structure - Use existing temporary file: `xml-docs-to-markdown-code-fix-provider.cs.temp` - Implement `CodeFixProvider` base class -- Register for `TW0004` diagnostic ID +- Register for `TW0002` diagnostic ID - Handle type and member documentation ## Technical Details @@ -65,7 +65,7 @@ Since code fix providers run in IDE/compiler context: ## Dependencies - Requires project configuration for code fix providers -- XmlDocsToMarkdownAnalyzer (TW0004) must be working +- XmlDocsToMarkdownAnalyzer (TW0002) must be working - Understanding of MarkdownDocsGenerator format ## References diff --git a/kanban/to-do/017_create-interface-delegation-generator/task.md b/kanban/to-do/017_create-interface-delegation-generator/task.md index 35bd5e0..fea14ef 100644 --- a/kanban/to-do/017_create-interface-delegation-generator/task.md +++ b/kanban/to-do/017_create-interface-delegation-generator/task.md @@ -183,10 +183,10 @@ public partial class Service : IDataProcessor ### Diagnostic Codes -- **TW1001**: Class is not marked as partial -- **TW1002**: Field/property type does not match any interface on the class -- **TW1003**: Multiple fields marked with [Implements] for the same interface -- **TW1004**: Field marked with [Implements] but class does not implement the interface +- **TW0004**: Class is not marked as partial +- **TW0005**: Field/property type does not match any interface on the class +- **TW0006**: Multiple fields marked with [Implements] for the same interface + ## Benefits diff --git a/readme.md b/readme.md index 3ac3e76..9ab9271 100644 --- a/readme.md +++ b/readme.md @@ -64,9 +64,9 @@ The generator will automatically create forwarding implementations for all inter #### Diagnostics -- **TW1001**: Class must be partial for interface delegation -- **TW1002**: Class does not implement the delegated interface -- **TW1003**: Multiple fields delegate the same interface +- **TW0004**: Class must be partial for interface delegation +- **TW0005**: Class does not implement the delegated interface +- **TW0006**: Multiple fields delegate the same interface ### File Name Rule Analyzer @@ -78,9 +78,11 @@ Configure exceptions in `.editorconfig`: ```ini [*.cs] -dotnet_diagnostic.TWA001.excluded_files = Program.cs;Startup.cs;*.Designer.cs +dotnet_diagnostic.TW0001.excluded_files = Program.cs;Startup.cs;*.Designer.cs ``` +Diagnostic IDs in this package use the **TW** prefix (TimeWarp.SourceGenerators). The **TWA** prefix is reserved for TimeWarp Architecture (a different product). + ## Getting started To quickly get started I recommend reviewing the samples in this repo. diff --git a/source/timewarp-source-generators/AnalyzerReleases.Unshipped.md b/source/timewarp-source-generators/AnalyzerReleases.Unshipped.md index bf7e595..67c3fa9 100644 --- a/source/timewarp-source-generators/AnalyzerReleases.Unshipped.md +++ b/source/timewarp-source-generators/AnalyzerReleases.Unshipped.md @@ -1,10 +1,10 @@ -### New Rules - -Rule ID | Category | Severity | Notes ---------|----------|----------|------- -TWG001 | SourceGenerator | Info | MarkdownDocsGenerator - Enhanced to support kebab-case file matching -TWA001 | Naming | Info | FileNameRuleAnalyzer, disabled by default -TWA002 | Documentation | Info | XmlDocsToMarkdownAnalyzer -TW1001 | InterfaceDelegation | Error | Class must be partial for interface delegation -TW1002 | InterfaceDelegation | Error | Class does not implement the delegated interface -TW1003 | InterfaceDelegation | Error | Multiple fields delegate the same interface +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +TW0001 | Naming | Info | FileNameRuleAnalyzer, disabled by default +TW0002 | Documentation | Info | XmlDocsToMarkdownAnalyzer +TW0003 | SourceGenerator | Info | MarkdownDocsGenerator - Enhanced to support kebab-case file matching +TW0004 | InterfaceDelegation | Error | Class must be partial for interface delegation +TW0005 | InterfaceDelegation | Error | Class does not implement the delegated interface +TW0006 | InterfaceDelegation | Error | Multiple fields delegate the same interface diff --git a/source/timewarp-source-generators/file-name-rule-analyzer.cs b/source/timewarp-source-generators/file-name-rule-analyzer.cs index b188927..79a7eb8 100644 --- a/source/timewarp-source-generators/file-name-rule-analyzer.cs +++ b/source/timewarp-source-generators/file-name-rule-analyzer.cs @@ -3,7 +3,7 @@ namespace TimeWarp.SourceGenerators; [Generator] public class FileNameRuleAnalyzer : IIncrementalGenerator { - public const string DiagnosticId = "TWA001"; + public const string DiagnosticId = "TW0001"; private const string Category = "Naming"; private static readonly DiagnosticDescriptor Rule = new( @@ -99,7 +99,7 @@ private string[] GetConfiguredExceptions(AnalyzerConfigOptionsProvider configOpt // Try to get configured exceptions from .editorconfig if (options.TryGetValue( - "dotnet_diagnostic.TWA001.excluded_files", + "dotnet_diagnostic.TW0001.excluded_files", out string? configuredExceptions) && !string.IsNullOrEmpty(configuredExceptions)) { // Split by semicolon and trim whitespace diff --git a/source/timewarp-source-generators/interface-delegation-generator.cs b/source/timewarp-source-generators/interface-delegation-generator.cs index 5ad7ac9..e11ab49 100644 --- a/source/timewarp-source-generators/interface-delegation-generator.cs +++ b/source/timewarp-source-generators/interface-delegation-generator.cs @@ -6,7 +6,7 @@ namespace TimeWarp.SourceGenerators; public class InterfaceDelegationGenerator : IIncrementalGenerator { private static readonly DiagnosticDescriptor ClassNotPartialDescriptor = new( - id: "TW1001", + id: "TW0004", title: "Class must be partial for interface delegation", messageFormat: "Class '{0}' must be marked as partial to use [Implements] attribute", category: "InterfaceDelegation", @@ -15,7 +15,7 @@ public class InterfaceDelegationGenerator : IIncrementalGenerator ); private static readonly DiagnosticDescriptor InterfaceNotImplementedDescriptor = new( - id: "TW1002", + id: "TW0005", title: "Class does not implement the delegated interface", messageFormat: "Class '{0}' must implement interface '{1}' to delegate to field/property '{2}'", category: "InterfaceDelegation", @@ -24,7 +24,7 @@ public class InterfaceDelegationGenerator : IIncrementalGenerator ); private static readonly DiagnosticDescriptor DuplicateDelegationDescriptor = new( - id: "TW1003", + id: "TW0006", title: "Multiple fields delegate the same interface", messageFormat: "Interface '{0}' is delegated by multiple fields/properties in class '{1}'", category: "InterfaceDelegation", diff --git a/source/timewarp-source-generators/markdown-docs-generator.cs b/source/timewarp-source-generators/markdown-docs-generator.cs index 14f618c..dcc580f 100644 --- a/source/timewarp-source-generators/markdown-docs-generator.cs +++ b/source/timewarp-source-generators/markdown-docs-generator.cs @@ -6,7 +6,7 @@ public class MarkdownDocsGenerator : IIncrementalGenerator // Regex pattern for checking if a file name is kebab-case private static readonly Regex KebabCasePattern = new(@"^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", RegexOptions.Compiled); private static readonly DiagnosticDescriptor MarkdownDocsGeneratorLoadedDescriptor = new( - id: "TWG001", + id: "TW0003", title: "MarkdownDocs Generator Loaded", messageFormat: "The MarkdownDocs generator has been loaded and initialized", category: "SourceGenerator", diff --git a/source/timewarp-source-generators/xml-docs-to-markdown-analyzer.cs b/source/timewarp-source-generators/xml-docs-to-markdown-analyzer.cs index b98468b..ba838fd 100644 --- a/source/timewarp-source-generators/xml-docs-to-markdown-analyzer.cs +++ b/source/timewarp-source-generators/xml-docs-to-markdown-analyzer.cs @@ -3,7 +3,7 @@ namespace TimeWarp.SourceGenerators; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class XmlDocsToMarkdownAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "TWA002"; + public const string DiagnosticId = "TW0002"; private const string Category = "Documentation"; private static readonly DiagnosticDescriptor Rule = new( diff --git a/tests/timewarp-source-generators-test-console/.editorconfig b/tests/timewarp-source-generators-test-console/.editorconfig index 157e1f5..f0767e3 100644 --- a/tests/timewarp-source-generators-test-console/.editorconfig +++ b/tests/timewarp-source-generators-test-console/.editorconfig @@ -4,7 +4,7 @@ root = true [*.cs] -dotnet_diagnostic.TWA001.severity = error # Set to warning/error to enforce kebab-case file naming -dotnet_diagnostic.TWA001.excluded_files = PascalCaseTest.cs -# Enable TWA002 - XML documentation to markdown analyzer -dotnet_diagnostic.TWA002.severity = suggestion \ No newline at end of file +dotnet_diagnostic.TW0001.severity = error # Set to warning/error to enforce kebab-case file naming +dotnet_diagnostic.TW0001.excluded_files = PascalCaseTest.cs +# Enable TW0002 - XML documentation to markdown analyzer +dotnet_diagnostic.TW0002.severity = suggestion \ No newline at end of file From 42f1108d926aeefeae782ad40bbeb661a7494d07 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:37:42 +0700 Subject: [PATCH 09/17] =?UTF-8?q?docs(kanban):=20align=20017=20diagnostic?= =?UTF-8?q?=20descriptions=20with=20TW0004=E2=80=93TW0006?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match live interface-delegation generator wording after 018 rename review. --- .../to-do/017_create-interface-delegation-generator/task.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kanban/to-do/017_create-interface-delegation-generator/task.md b/kanban/to-do/017_create-interface-delegation-generator/task.md index fea14ef..45aaebf 100644 --- a/kanban/to-do/017_create-interface-delegation-generator/task.md +++ b/kanban/to-do/017_create-interface-delegation-generator/task.md @@ -183,9 +183,9 @@ public partial class Service : IDataProcessor ### Diagnostic Codes -- **TW0004**: Class is not marked as partial -- **TW0005**: Field/property type does not match any interface on the class -- **TW0006**: Multiple fields marked with [Implements] for the same interface +- **TW0004**: Class must be partial for interface delegation +- **TW0005**: Class does not implement the delegated interface +- **TW0006**: Multiple fields delegate the same interface ## Benefits From 3c5332901ad3a9ec940b7205bfc5d2c6ef8f9e58 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:38:02 +0700 Subject: [PATCH 10/17] =?UTF-8?q?docs(kanban):=20018=20results=20=E2=80=94?= =?UTF-8?q?=20TW0001=E2=80=93TW0006=20rename=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document mapping, files, verification, and review outcomes before done. --- ...efixes-off-twa-to-free-architecture-twa.md | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md b/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md index aafeda9..311320b 100644 --- a/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md +++ b/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md @@ -43,14 +43,13 @@ TW0003 = kebab under a different scheme). ## Checklist -- [ ] Apply TW0001–TW0006 mapping (or document deviation) -- [ ] Rename **all** IDs in source (no TWA / TWG / TW100x leftovers) -- [ ] AnalyzerReleases.Shipped.md / Unshipped.md -- [ ] readme.md, documentation/, .editorconfig samples -- [ ] Tests assert new IDs -- [ ] Grep: no TWA001, TWA002, TW1001–1003, TWG001 in code -- [ ] Commit - +- [x] Apply TW0001–TW0006 mapping (or document deviation) +- [x] Rename **all** IDs in source (no TWA / TWG / TW100x leftovers) +- [x] AnalyzerReleases.Shipped.md / Unshipped.md +- [x] readme.md, documentation/, .editorconfig samples +- [x] Tests assert new IDs (test-console `.editorconfig` uses TW0001/TW0002; no unit-test project asserts IDs) +- [x] Grep: no TWA001, TWA002, TW1001–1003, TWG001 in code +- [x] Commit ## Notes - Architecture already renamed TWPA → TWA (timewarp-architecture). @@ -112,9 +111,51 @@ Pass: build green; zero old IDs on live product surface; Unshipped matches code. - Do not blind-sed `TW0003` in docs after rename (it correctly means MarkdownDocs load). - Change source + Unshipped together. +## Results + +### Summary + +Renamed every live diagnostic ID in `TimeWarp.SourceGenerators` to contiguous **TW0001–TW0006**. Dropped TWA/TWG/TW1xxx so TimeWarp Architecture can own **TWA**. Single **TW** family for this package’s analyzers and generators. + +| Old | New | Component | +|-----|-----|-----------| +| TWA001 | TW0001 | FileNameRuleAnalyzer (+ `excluded_files` key) | +| TWA002 | TW0002 | XmlDocsToMarkdownAnalyzer | +| TWG001 | TW0003 | MarkdownDocsGenerator | +| TW1001 | TW0004 | Interface delegation — partial | +| TW1002 | TW0005 | Interface delegation — not implement | +| TW1003 | TW0006 | Interface delegation — multiple fields | + +### Files changed + +**Source:** `file-name-rule-analyzer.cs`, `xml-docs-to-markdown-analyzer.cs`, `markdown-docs-generator.cs`, `interface-delegation-generator.cs`, `AnalyzerReleases.Unshipped.md` +**Config:** root `.editorconfig`, test-console `.editorconfig` +**Docs:** `readme.md`, `documentation/**` (overview, how-to, analyzer/SG refs, release tracking) +**Open kanban:** 015, 016, 017 (fix + interface draft) + +### Key decisions + +- Left `AnalyzerReleases.Shipped.md` empty (always-unshipped policy). +- No dual-ID compatibility layer (intentional full rename). +- File-name docs corrected from historical TW0003 scheme to **TW0001**. +- `kanban/done/*` history left as archaeology; open tasks scrubbed. +- Phantom TW1004 removed from interface-delegation draft task. + +### Verification + +- `dotnet build source/timewarp-source-generators/... -c Release` — **succeeded** (0 warnings/errors); package 1.0.0-beta.8 produced. +- Grep old IDs on `source/`, `documentation/`, `readme.md`, `.editorconfig`, `tests/` — **zero matches**. +- All six new IDs present in source + Unshipped. +- Review (`72e360e`): 0 bugs; 1 suggestion (017 prose) fixed in `42f1108`. + +### Note + +Solution-wide restore of the test console can fail with NU1102 when CPM `Version=$(Version)` does not resolve for the test project (pre-existing; not introduced by this rename). Library project builds clean. + ## Session - Created: 2026-07-15 - Clarified: rename **ALL** IDs; do not leave TWA001/TWA002 - Prefix locked: **TW** (recommend contiguous TW0001–TW0006) - Plan: 2026-07-15 (orchestrate-task phase 2–3) +- Implemented + reviewed: 2026-07-15 (orchestrate-task phases 4–5) From 42963c3638548ab2788cb1ae9bd4f20beb8c494b Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 11:38:02 +0700 Subject: [PATCH 11/17] =?UTF-8?q?chore(kanban):=20complete=20018=20?= =?UTF-8?q?=E2=80=94=20diagnostic=20IDs=20renamed=20to=20TW####?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move task to done after rename implementation and review. --- ...rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename kanban/{in-progress => done}/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md (100%) diff --git a/kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md b/kanban/done/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md similarity index 100% rename from kanban/in-progress/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md rename to kanban/done/018-rename-diagnostic-prefixes-off-twa-to-free-architecture-twa.md From e8505adf88b6f21072b35de4fbe0c55e5de070f7 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 13:01:16 +0700 Subject: [PATCH 12/17] =?UTF-8?q?chore(kanban):=20start=20019=20=E2=80=94?= =?UTF-8?q?=20modernize=20GitHub=20workflow=20to=20TimeWarp=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move task to in-progress for orchestration. --- .../019-modernize-github-workflow-to-timewarp-ci-standard.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename kanban/{to-do => in-progress}/019-modernize-github-workflow-to-timewarp-ci-standard.md (100%) diff --git a/kanban/to-do/019-modernize-github-workflow-to-timewarp-ci-standard.md b/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md similarity index 100% rename from kanban/to-do/019-modernize-github-workflow-to-timewarp-ci-standard.md rename to kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md From 41816c009b36d3d6f9d395933b0b47d2489bb830 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 13:05:42 +0700 Subject: [PATCH 13/17] =?UTF-8?q?docs(kanban):=20019=20implementation=20pl?= =?UTF-8?q?an=20=E2=80=94=20Option=20A=20thin=20CI=20+=20OIDC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capture peer template, T1 ProjectReference, workflow-command rewrite, and path/cache/auth decisions. --- ...github-workflow-to-timewarp-ci-standard.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md b/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md index 083512c..750309c 100644 --- a/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md +++ b/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md @@ -105,6 +105,60 @@ Document which option was chosen in Results. - Version still lives in `source/Directory.Build.props` (`GeneratePackageOnBuild` → `artifacts/packages/`) +### Implementation plan (2026-07-15) + +**Chosen approach: Option A** — full `dev workflow` ownership (peer consensus). +**Peer template:** timewarp-options-validation (single package + GeneratePackageOnBuild). + +#### Target + +``` +YAML (thin): checkout → setup-dotnet 10 → [release] nuget/login OIDC → + dotnet run --file tools/dev-cli/dev.cs -- workflow [--api-key] + → upload artifacts/packages/*.nupkg (always) + +PR/merge: clean → build → test console +Release: tag/version + NuGet not-published → clean → build → test → push +``` + +#### Key decisions + +| Topic | Decision | +|-------|----------| +| Option A vs B | **A** — extend workflow-command (in-process; no `./bin/dev`) | +| Tests | Rewrite `dev test` to build+run test console; remove dead path with YAML rewrite | +| Test console ref | **T1:** Analyzer `ProjectReference` (fix PackageReference/`Version=$(Version)` chicken-and-egg) | +| Cache | Remove cache step; keep `RestorePackagesPath=.nuget-cache/` (no peer cache) | +| Auth | OIDC only on `release`; retire `PUBLISH_TO_NUGET_ORG` | +| Path filters | Add `tools/**`, `Directory.Packages.props`, `msbuild/**`, `source/Directory.Build.props`, `nuget.config`; drop `*.props`/`*.targets` | +| Pack | No separate pack step — `GeneratePackageOnBuild` on Release build | + +#### Ordered steps + +1. Fix test console → Analyzer ProjectReference (+ drop self PackageVersion if unused) +2. Rewrite `test-command.cs` → build+run console +3. Rewrite `workflow-command.cs` → Option A (modes, tag/version, NuGet check, push) +4. Optional: pin `build-command` to source project +5. Rewrite `workflow.yml` thin peer pipeline +6. Local verify: `dotnet run --file tools/dev-cli/dev.cs -- workflow` +7. Commit + +#### Files + +- `.github/workflows/workflow.yml` +- `tools/dev-cli/endpoints/workflow-command.cs` +- `tools/dev-cli/endpoints/test-command.cs` +- `tests/.../timewarp-source-generators-test-console.csproj` +- `Directory.Packages.props` (self PackageVersion cleanup) +- Optionally `build-command.cs` + +#### Risks + +- OIDC Trusted Publishing must exist for package on nuget.org (ops) +- Do not shell `./bin/dev` from workflow (CI uses file-based run) +- `dotnet test` alone is false-green today — must use console + ## Session - Created: 2026-07-15 (post `ganda repo audit --fix` + workflow review) +- Plan: 2026-07-15 (orchestrate-task phase 2–3) From e7bf0057fffae8bb7d5ee46a6a818e19228cd6bd Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 13:09:51 +0700 Subject: [PATCH 14/17] ci: modernize workflow to thin YAML + Option A dev pipeline Replace hand-rolled PowerShell CI with peer-style OIDC workflow and in-process dev workflow (clean/build/test/push). Wire test console as Analyzer ProjectReference so CI verifies generators without local-feed version chicken-and-egg. --- .github/workflows/workflow.yml | 169 +++++-------- Directory.Packages.props | 4 - .../DataService.implements.g.cs | 2 +- ...warp-source-generators-test-console.csproj | 14 +- tools/dev-cli/endpoints/build-command.cs | 1 + tools/dev-cli/endpoints/test-command.cs | 44 +++- tools/dev-cli/endpoints/workflow-command.cs | 225 ++++++++++++++++-- tools/dev-cli/global-usings.cs | 1 + 8 files changed, 317 insertions(+), 143 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index eb7631b..0a715c9 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,105 +1,64 @@ -name: CI/CD Pipeline - -on: - push: - branches: - - master - paths: - - 'source/**' - - 'tests/**' - - '.github/workflows/**' - - 'Directory.Build.props' - - '*.props' - - '*.targets' - pull_request: - branches: - - master - paths: - - 'source/**' - - 'tests/**' - - '.github/workflows/**' - - 'Directory.Build.props' - - '*.props' - - '*.targets' - release: - types: [published] # Triggered when a release is published via GitHub Releases UI or gh CLI - workflow_dispatch: - -env: - DOTNET_NOLOGO: true - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: true - -jobs: - build: - runs-on: ubuntu-latest - defaults: - run: - shell: pwsh - steps: - - name: 🎉 Event Trigger - run: echo "The job was automatically triggered by a ${{ github.event_name }} event." - - - name: 📥 Check out repository code - uses: actions/checkout@v4 - - - name: 📦 Cache NuGet packages - uses: actions/cache@v4 - with: - path: ~/.nuget/packages - key: ${{ runner.os }}-nuget-${{ hashFiles('**/Directory.Packages.props') }} - restore-keys: | - ${{ runner.os }}-nuget- - - - name: 🛠️ Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '10.0.x' - - - name: 📁 Create artifacts directory - run: mkdir -p artifacts/packages - - - name: 🔨 Build - run: | - dotnet build source/timewarp-source-generators/timewarp-source-generators.csproj --configuration Release - - # TODO: Enable tests when ready - # - name: 🧪 Run Tests - # run: | - # cd tests/timewarp-source-generators-tests/ - # dotnet tool restore - # dotnet restore - # dotnet fixie --configuration Release - - - name: 🔍 Check if version already published (Releases only) - if: github.event_name == 'release' - run: | - $version = (Select-Xml -Path "source/Directory.Build.props" -XPath "//Version/text()").Node.Value - Write-Host "Checking if TimeWarp.SourceGenerators $version is already published on NuGet.org..." - - $searchResult = dotnet package search TimeWarp.SourceGenerators --exact-match --prerelease --source https://api.nuget.org/v3/index.json - if ($searchResult -match "TimeWarp\.SourceGenerators.*$version") { - Write-Host "⚠️ WARNING: TimeWarp.SourceGenerators $version is already published to NuGet.org" - Write-Host "❌ This version cannot be republished. Please increment the version in source/Directory.Build.props" - exit 1 - } else { - Write-Host "✅ TimeWarp.SourceGenerators $version is not yet published on NuGet.org" - } - - - name: 🚀 Publish to NuGet.org (Releases only) - if: github.event_name == 'release' - run: | - dotnet nuget push artifacts/packages/TimeWarp.SourceGenerators.*.nupkg ` - --api-key ${{ secrets.PUBLISH_TO_NUGET_ORG }} ` - --source https://api.nuget.org/v3/index.json ` - --skip-duplicate - - - name: 📤 Upload Artifacts - if: github.event_name == 'push' || github.event_name == 'release' - uses: actions/upload-artifact@v4 - with: - name: Packages-${{ github.run_number }} - path: artifacts/packages/*.nupkg - - - name: ✅ Job Status - run: echo "This job's status is ${{ job.status }}." \ No newline at end of file +name: CI/CD + +on: + push: + branches: [master] + paths: + - 'source/**' + - 'tests/**' + - 'tools/**' + - 'msbuild/**' + - '.github/workflows/**' + - 'Directory.Build.props' + - 'Directory.Packages.props' + - 'source/Directory.Build.props' + - 'nuget.config' + pull_request: + branches: [master] + paths: + - 'source/**' + - 'tests/**' + - 'tools/**' + - 'msbuild/**' + - '.github/workflows/**' + - 'Directory.Build.props' + - 'Directory.Packages.props' + - 'source/Directory.Build.props' + - 'nuget.config' + release: + types: [published] + workflow_dispatch: + +jobs: + ci: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: NuGet login (OIDC Trusted Publishing) + if: github.event_name == 'release' + id: nuget-login + uses: nuget/login@v1 + with: + user: TimeWarp.Enterprises + - name: Run CI Pipeline + run: | + if [ "${{ github.event_name }}" == "release" ]; then + dotnet run --file tools/dev-cli/dev.cs -- workflow --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" + else + dotnet run --file tools/dev-cli/dev.cs -- workflow + fi + - name: Upload Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: Packages-${{ github.run_number }} + path: artifacts/packages/*.nupkg + if-no-files-found: ignore diff --git a/Directory.Packages.props b/Directory.Packages.props index e426239..3be507b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,10 +4,6 @@ true - - - - diff --git a/tests/timewarp-source-generators-test-console/generated/timewarp-source-generators/TimeWarp.SourceGenerators.InterfaceDelegationGenerator/DataService.implements.g.cs b/tests/timewarp-source-generators-test-console/generated/timewarp-source-generators/TimeWarp.SourceGenerators.InterfaceDelegationGenerator/DataService.implements.g.cs index 9f17241..1a4e45f 100644 --- a/tests/timewarp-source-generators-test-console/generated/timewarp-source-generators/TimeWarp.SourceGenerators.InterfaceDelegationGenerator/DataService.implements.g.cs +++ b/tests/timewarp-source-generators-test-console/generated/timewarp-source-generators/TimeWarp.SourceGenerators.InterfaceDelegationGenerator/DataService.implements.g.cs @@ -3,7 +3,7 @@ namespace TimeWarp.SourceGenerators.TestConsole; -// Interface delegation for DataService - YO YO YO MAMA v2 with inherited interfaces! +// Interface delegation for DataService public partial class DataService { // Delegation to _logger for TimeWarp.SourceGenerators.TestConsole.ILogger diff --git a/tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj b/tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj index c5c090e..b564a05 100644 --- a/tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj +++ b/tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj @@ -9,13 +9,15 @@ preview diagnostic + + $(NoWarn);RS0030 - + - - all - build; analyzers - + + @@ -37,4 +39,4 @@ - \ No newline at end of file + diff --git a/tools/dev-cli/endpoints/build-command.cs b/tools/dev-cli/endpoints/build-command.cs index 5e3a4cf..a20f772 100644 --- a/tools/dev-cli/endpoints/build-command.cs +++ b/tools/dev-cli/endpoints/build-command.cs @@ -3,6 +3,7 @@ #endregion #region Design // Discovers the repository root dynamically using Git.FindRoot() +// Builds the solution at the repo root in Release (source packs via GeneratePackageOnBuild) // Handler stores Command and Ct as fields so private methods are zero-parameter // Streams MSBuild output via Amuru RunAsync by default; --quiet uses CaptureAsync #endregion diff --git a/tools/dev-cli/endpoints/test-command.cs b/tools/dev-cli/endpoints/test-command.cs index 53cc87a..b71fc74 100644 --- a/tools/dev-cli/endpoints/test-command.cs +++ b/tools/dev-cli/endpoints/test-command.cs @@ -2,9 +2,10 @@ // Run the test suite #endregion #region Design -// Executes dotnet test for all tests in the repository +// Builds and runs the test-console project in Release (no Fixie/dotnet test suite yet) +// Fails on non-zero exit from build or run // Handler stores Command and Ct as fields so private methods are zero-parameter -// Streams test output via Amuru RunAsync by default; --quiet uses CaptureAsync +// Streams output via Amuru RunAsync by default; --quiet uses CaptureAsync #endregion namespace DevCli.Commands; @@ -17,10 +18,14 @@ internal sealed class TestCommand : ICommand internal sealed class Handler : ICommandHandler { + private const string TestProjectRelativePath = + "tests/timewarp-source-generators-test-console/timewarp-source-generators-test-console.csproj"; + private readonly ITerminal Terminal; private TestCommand Command = null!; private CancellationToken Ct; private string RepoRoot = null!; + private string TestProjectPath = null!; public Handler(ITerminal terminal) { @@ -33,7 +38,8 @@ public async ValueTask Handle(TestCommand command, CancellationToken ct) Ct = ct; if (!FindRepoRoot()) return Value; - if (!await TestAsync()) return Value; + if (!await BuildTestProjectAsync()) return Value; + if (!await RunTestProjectAsync()) return Value; Terminal.WriteLine("\nTests completed successfully!".Green()); return Value; @@ -48,26 +54,48 @@ private bool FindRepoRoot() Environment.ExitCode = 1; return false; } + RepoRoot = root; + TestProjectPath = Path.Combine(RepoRoot, TestProjectRelativePath); Terminal.WriteLine("Running test suite..."); return true; } - private async Task TestAsync() + private async Task BuildTestProjectAsync() { - CommandResult command = DotNet.Test() + Terminal.WriteLine($"Building {TestProjectRelativePath} (Release)..."); + CommandResult command = DotNet.Build(TestProjectPath) .WithConfiguration("Release") .WithWorkingDirectory(RepoRoot) .WithNoValidation() .Build(); + return await ExecuteAsync(command, "Test project build failed!"); + } + + private async Task RunTestProjectAsync() + { + Terminal.WriteLine($"Running {TestProjectRelativePath} (Release)..."); + CommandResult command = DotNet.Run() + .WithProject(TestProjectPath) + .WithConfiguration("Release") + .WithNoBuild() + .WithWorkingDirectory(RepoRoot) + .WithNoValidation() + .Build(); + + return await ExecuteAsync(command, "Tests failed!"); + } + + private async Task ExecuteAsync(CommandResult command, string failureMessage) + { if (Command.Quiet) { CommandOutput result = await command.CaptureAsync(Ct); if (!result.Success) { Terminal.WriteErrorLine(result.Combined); - Terminal.WriteErrorLine("Tests failed!".Red()); + Terminal.WriteErrorLine(failureMessage.Red()); Environment.ExitCode = 1; return false; } @@ -78,7 +106,7 @@ private async Task TestAsync() int exitCode = await command.RunAsync(Ct); if (exitCode != 0) { - Terminal.WriteErrorLine("Tests failed!".Red()); + Terminal.WriteErrorLine(failureMessage.Red()); Environment.ExitCode = exitCode; return false; } @@ -86,4 +114,4 @@ private async Task TestAsync() return true; } } -} \ No newline at end of file +} diff --git a/tools/dev-cli/endpoints/workflow-command.cs b/tools/dev-cli/endpoints/workflow-command.cs index 7fa980c..5ffd150 100644 --- a/tools/dev-cli/endpoints/workflow-command.cs +++ b/tools/dev-cli/endpoints/workflow-command.cs @@ -1,27 +1,49 @@ #region Purpose -// Executes the full CI workflow +// Executes the full CI/CD pipeline with mode detection #endregion #region Design -// Runs clean, build, test sequentially by invoking ./bin/dev subcommands -// Handler stores RepoRoot and DevBin as fields so private methods are zero-parameter -// RunStepAsync DRYs up the identical shell-invoke/exit-code-check pattern +// Auto-detects mode from GITHUB_EVENT_NAME or accepts explicit --mode flag +// pr/merge: clean -> build -> test +// release: tag match + NuGet not-published -> clean -> build -> test -> push +// Invokes sibling command handlers in-process so CI needs no self-install +// Clean uses IRepoCleanService (no shell to ./bin/dev) +// Release version comes from Version in source/Directory.Build.props and +// must match the git tag (GITHUB_REF_NAME) on release events +// Pack is GeneratePackageOnBuild; push targets artifacts/packages/*.nupkg #endregion namespace DevCli.Commands; -[NuruRoute("workflow", Description = "Execute full CI workflow")] +[NuruRoute("workflow", Description = "Execute full CI/CD pipeline")] internal sealed class WorkflowCommand : ICommand { + [Option("mode", "m", Description = "CI mode: pr, merge, or release (auto-detected from GITHUB_EVENT_NAME if not specified)")] + public string? Mode { get; set; } + + [Option("api-key", Description = "NuGet API key for publishing (release mode only)")] + public string? ApiKey { get; set; } + internal sealed class Handler : ICommandHandler { + private const string PackageId = "TimeWarp.SourceGenerators"; + private const string NuGetSource = "https://api.nuget.org/v3/index.json"; + private readonly ITerminal Terminal; + private readonly IRepoCleanService RepoCleanService; + private readonly INuGetPackageService NuGetPackageService; private CancellationToken Ct; private string RepoRoot = null!; - private string DevBin = null!; - public Handler(ITerminal terminal) + public Handler + ( + ITerminal terminal, + IRepoCleanService repoCleanService, + INuGetPackageService nuGetPackageService + ) { Terminal = terminal; + RepoCleanService = repoCleanService; + NuGetPackageService = nuGetPackageService; } public async ValueTask Handle(WorkflowCommand command, CancellationToken ct) @@ -29,11 +51,22 @@ public async ValueTask Handle(WorkflowCommand command, CancellationToken c Ct = ct; if (!FindRepoRoot()) return Value; - if (!await RunStepAsync("clean", "Clean failed!")) return Value; - if (!await RunStepAsync("build", "Build failed!")) return Value; - if (!await RunStepAsync("test", "Tests failed!")) return Value; - Terminal.WriteLine("\nWorkflow completed successfully!".Green()); + CiMode mode = DetermineMode(command.Mode); + Terminal.WriteLine($"Starting CI workflow (mode: {mode})..."); + + if (mode == CiMode.Release) + { + await RunReleaseWorkflowAsync(command.ApiKey); + } + else + { + await RunPrWorkflowAsync(); + } + + if (Environment.ExitCode == 0) + Terminal.WriteLine("\nWorkflow completed successfully!".Green()); + return Value; } @@ -47,25 +80,179 @@ private bool FindRepoRoot() return false; } RepoRoot = root; - DevBin = Path.Combine(RepoRoot, "bin", "dev"); - Terminal.WriteLine("Starting CI workflow..."); return true; } - private async Task RunStepAsync(string subcommand, string failureMessage) + private CiMode DetermineMode(string? explicitMode) + { + if (!string.IsNullOrEmpty(explicitMode)) + { + return explicitMode.ToLowerInvariant() switch + { + "release" => CiMode.Release, + "merge" => CiMode.Merge, + _ => CiMode.Pr + }; + } + + string? eventName = Environment.GetEnvironmentVariable("GITHUB_EVENT_NAME"); + + CiMode mode = eventName switch + { + "push" => CiMode.Merge, + "release" => CiMode.Release, + // Manual runs are build/test only; use --mode release explicitly to publish + "workflow_dispatch" => CiMode.Pr, + _ => CiMode.Pr // pull_request and local dev + }; + + Terminal.WriteLine($"Detected GITHUB_EVENT_NAME: {eventName ?? "(not set)"} -> Mode: {mode}"); + return mode; + } + + private async Task RunPrWorkflowAsync() + { + if (!await RunStepAsync("Clean", CleanAsync)) return; + if (!await RunStepAsync("Build", () => new BuildCommand.Handler(Terminal).Handle(new BuildCommand(), Ct))) return; + await RunStepAsync("Test", () => new TestCommand.Handler(Terminal).Handle(new TestCommand(), Ct)); + } + + private async Task RunReleaseWorkflowAsync(string? apiKey) + { + string? version = ReadVersion(); + if (version is null) + { + Terminal.WriteErrorLine("Error: could not read Version from source/Directory.Build.props.".Red()); + Environment.ExitCode = 1; + return; + } + + if (!CheckVersionMatchesTag(version)) return; + if (!await CheckNotPublishedOnNuGetAsync(version)) return; + if (!await RunStepAsync("Clean", CleanAsync)) return; + if (!await RunStepAsync("Build", () => new BuildCommand.Handler(Terminal).Handle(new BuildCommand(), Ct))) return; + if (!await RunStepAsync("Test", () => new TestCommand.Handler(Terminal).Handle(new TestCommand(), Ct))) return; + await PushPackageAsync(version, apiKey); + } + + private async ValueTask CleanAsync() + { + Terminal.WriteLine("Cleaning repository artifacts..."); + CleanResult result = await RepoCleanService.CleanAsync(Ct); + Terminal.WriteLine + ( + $"Clean complete: {result.ObjDirectoriesDeleted} obj, {result.BinDirectoriesDeleted} bin, {result.RootBinFilesCleaned} root-bin items." + ); + return Value; + } + + private async Task RunStepAsync(string title, Func> step) + { + Terminal.WriteLine($"\n=== {title} ==="); + await step(); + + if (Environment.ExitCode != 0) + { + Terminal.WriteErrorLine($"{title} failed!".Red()); + return false; + } + return true; + } + + private string? ReadVersion() { - int exitCode = await Shell.Builder(DevBin) - .WithArguments(subcommand) + string propsPath = Path.Combine(RepoRoot, "source", "Directory.Build.props"); + if (!File.Exists(propsPath)) return null; + + Match match = Regex.Match(File.ReadAllText(propsPath), "(.+?)"); + return match.Success ? match.Groups[1].Value : null; + } + + private bool CheckVersionMatchesTag(string version) + { + // Only enforce on tag-triggered releases; workflow_dispatch has no tag + if (Environment.GetEnvironmentVariable("GITHUB_EVENT_NAME") != "release") return true; + + string? tag = Environment.GetEnvironmentVariable("GITHUB_REF_NAME"); + string? tagVersion = tag?.TrimStart('v'); + + if (tagVersion != version) + { + Terminal.WriteErrorLine($"Error: release tag '{tag}' does not match Version '{version}'.".Red()); + Environment.ExitCode = 1; + return false; + } + + Terminal.WriteLine($"Release tag '{tag}' matches Version '{version}'."); + return true; + } + + private async Task CheckNotPublishedOnNuGetAsync(string version) + { + Terminal.WriteLine($"\n=== Check NuGet (not published) ==="); + Terminal.WriteLine($"Checking if {PackageId} {version} is already on NuGet.org..."); + + NuGetSearchResult? search = await NuGetPackageService.SearchAsync(PackageId, Ct); + bool alreadyPublished = search?.Versions.Any + ( + v => string.Equals(v.Version, version, StringComparison.OrdinalIgnoreCase) + ) == true; + + if (alreadyPublished) + { + Terminal.WriteErrorLine + ( + $"Error: {PackageId} {version} is already published to NuGet.org. Increment Version in source/Directory.Build.props.".Red() + ); + Environment.ExitCode = 1; + return false; + } + + Terminal.WriteLine($"{PackageId} {version} is not yet published on NuGet.org."); + return true; + } + + private async Task PushPackageAsync(string version, string? apiKey) + { + Terminal.WriteLine("\n=== Push to NuGet ==="); + + string nupkgPath = Path.Combine(RepoRoot, "artifacts", "packages", $"{PackageId}.{version}.nupkg"); + if (!File.Exists(nupkgPath)) + { + Terminal.WriteErrorLine($"Error: package not found: {nupkgPath}".Red()); + Environment.ExitCode = 1; + return; + } + + Terminal.WriteLine($"Pushing {PackageId}.{version}.nupkg..."); + + List args = ["nuget", "push", nupkgPath, "--source", NuGetSource, "--skip-duplicate"]; + if (!string.IsNullOrEmpty(apiKey)) + { + args.AddRange(["--api-key", apiKey]); + } + + int exitCode = await Shell.Builder("dotnet") + .WithArguments([.. args]) + .WithWorkingDirectory(RepoRoot) .WithNoValidation() .RunAsync(Ct); if (exitCode != 0) { - Terminal.WriteErrorLine(failureMessage.Red()); + Terminal.WriteErrorLine("Push failed!".Red()); Environment.ExitCode = exitCode; - return false; + return; } - return true; + + Terminal.WriteLine($"\nPublished {PackageId} {version} to NuGet.org!".Green()); } } } + +internal enum CiMode +{ + Pr, + Merge, + Release +} diff --git a/tools/dev-cli/global-usings.cs b/tools/dev-cli/global-usings.cs index fff0e42..9052391 100644 --- a/tools/dev-cli/global-usings.cs +++ b/tools/dev-cli/global-usings.cs @@ -12,6 +12,7 @@ global using System.Runtime.CompilerServices; global using System.Runtime.InteropServices; global using System.Xml.Linq; +global using System.Text.RegularExpressions; global using TimeWarp.Nuru; global using static TimeWarp.Nuru.Unit; From eae9bb4a8937744ad88d1b7058ab791e0fec8106 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 13:14:31 +0700 Subject: [PATCH 15/17] =?UTF-8?q?docs(kanban):=20019=20results=20=E2=80=94?= =?UTF-8?q?=20Option=20A=20thin=20CI=20+=20OIDC=20delivered?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document YAML, workflow-command, T1 ProjectReference, verification, and ops notes. --- ...github-workflow-to-timewarp-ci-standard.md | 67 ++++++++++++++++--- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md b/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md index 750309c..5c481d0 100644 --- a/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md +++ b/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md @@ -68,17 +68,16 @@ Document which option was chosen in Results. ## Checklist -- [ ] Rewrite `.github/workflows/workflow.yml` to thin peer-style pipeline -- [ ] Add OIDC permissions + `nuget/login@v1` for release -- [ ] Fix path filters (`tools/**`, `Directory.Packages.props`, etc.) -- [ ] Fix NuGet cache path vs `RestorePackagesPath` -- [ ] Extend or document `dev workflow` for pack/push (option A or B) -- [ ] Enable real test/verify step; remove dead test path -- [ ] Drop legacy `PUBLISH_TO_NUGET_ORG` push when OIDC works -- [ ] Validate PR path: build (+ tests) without publish -- [ ] Validate release path: version check + pack + push (or dry-run notes) -- [ ] Commit - +- [x] Rewrite `.github/workflows/workflow.yml` to thin peer-style pipeline +- [x] Add OIDC permissions + `nuget/login@v1` for release +- [x] Fix path filters (`tools/**`, `Directory.Packages.props`, etc.) +- [x] Fix NuGet cache path vs `RestorePackagesPath` (removed cache step; kept RestorePackagesPath) +- [x] Extend or document `dev workflow` for pack/push (option A or B) — **Option A** +- [x] Enable real test/verify step; remove dead test path +- [x] Drop legacy `PUBLISH_TO_NUGET_ORG` push when OIDC works +- [x] Validate PR path: build (+ tests) without publish +- [x] Validate release path: version check + pack + push (or dry-run notes) +- [x] Commit ## Notes ### Current workflow issues (review 2026-07-15) @@ -158,7 +157,53 @@ Release: tag/version + NuGet not-published → clean → build → test → pus - Do not shell `./bin/dev` from workflow (CI uses file-based run) - `dotnet test` alone is false-green today — must use console +## Results + +### Summary + +**Option A** delivered: thin GitHub Actions YAML + in-process `dev workflow` owns PR and release. + +| Mode | Pipeline | +|------|----------| +| PR / merge / local / `workflow_dispatch` | clean → build → test | +| Release (event or `--mode release`) | tag match + NuGet not-published → clean → build → test → push | + +### Files changed + +- `.github/workflows/workflow.yml` — full rewrite (OIDC, path filters, no secret/cache/pwsh) +- `tools/dev-cli/endpoints/workflow-command.cs` — Option A modes + release gates + push +- `tools/dev-cli/endpoints/test-command.cs` — build+run test console +- `tools/dev-cli/endpoints/build-command.cs` — Design note only +- `tools/dev-cli/global-usings.cs` — Regex +- `tests/.../timewarp-source-generators-test-console.csproj` — Analyzer ProjectReference (T1) +- `Directory.Packages.props` — removed self PackageVersion +- Regenerated test console `DataService.implements.g.cs` comment + +### Key decisions + +- Peer template: timewarp-options-validation +- Clean via `IRepoCleanService` (no local CleanCommand type) +- NuGet not-published via `INuGetPackageService.SearchAsync` +- OIDC login only on `release` (not `workflow_dispatch`) +- `workflow_dispatch` → Pr mode (use `--mode release` to publish) +- Cache step removed; `RestorePackagesPath=.nuget-cache/` unchanged +- Pack still `GeneratePackageOnBuild` +- `RS0030` suppressed on smoke test console for intentional `System.Console` + +### Verification + +- `dotnet run --file tools/dev-cli/dev.cs -- workflow` → **exit 0** (clean → build nupkg → test console) +- Review `e7bf005`: **approve**, 0 issues +- Release path: code gates tag + NuGet + push; live OIDC requires nuget.org Trusted Publishing for this package (ops) + +### Ops follow-ups + +- Ensure NuGet.org Trusted Publishing is registered for `TimeWarp.SourceGenerators` + this GitHub repo +- Remove obsolete GitHub secret `PUBLISH_TO_NUGET_ORG` when convenient +- Bump `Version` before next release if `1.0.0-beta.8` is already on NuGet.org + ## Session - Created: 2026-07-15 (post `ganda repo audit --fix` + workflow review) - Plan: 2026-07-15 (orchestrate-task phase 2–3) +- Implemented + reviewed: 2026-07-15 (orchestrate-task phases 4–5) From 3b4219276c35d23ee7bd47bc4765f87b927694f8 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 13:14:31 +0700 Subject: [PATCH 16/17] =?UTF-8?q?chore(kanban):=20complete=20019=20?= =?UTF-8?q?=E2=80=94=20GitHub=20workflow=20modernized?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move task to done after Option A CI implementation and review. --- .../019-modernize-github-workflow-to-timewarp-ci-standard.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename kanban/{in-progress => done}/019-modernize-github-workflow-to-timewarp-ci-standard.md (100%) diff --git a/kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md b/kanban/done/019-modernize-github-workflow-to-timewarp-ci-standard.md similarity index 100% rename from kanban/in-progress/019-modernize-github-workflow-to-timewarp-ci-standard.md rename to kanban/done/019-modernize-github-workflow-to-timewarp-ci-standard.md From 5d959c1502cdf913164aa783b55aebdcbd3e8835 Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 15 Jul 2026 13:24:37 +0700 Subject: [PATCH 17/17] fix(ci): drop local NuGet feed so restore works on fresh clones NuGet fails with NU1301 when nuget.config points at artifacts/packages and the directory is missing (CI checkout). Peers use nuget.org only; PackageOutputPath still writes nupkgs for upload/push. --- nuget.config | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nuget.config b/nuget.config index fd1c467..d9a5a9c 100644 --- a/nuget.config +++ b/nuget.config @@ -3,6 +3,7 @@ - +