Journal
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
Progress
Section titled “Progress”- Course code: file-based apps, rejected snippets and exercise solutions, compared with their expected output by
check.sh - CI on Linux, Windows and macOS
- Lesson 1: install .NET and run your first program
- Lesson 2: variables, types and input
- Lesson 3: conditions and loops
- Lesson 4: methods, arrays and lists
- Lesson 5: classes and objects
- Lesson 6: records, structs and enums
- Lesson 7: interfaces and inheritance
- Lesson 8: exceptions and null safety
- Lesson 9: collections and LINQ
- Lesson 10: files and text
- Lesson 11: unit tests
- Lesson 12: a small project
| Expected | What happens | Where | Measurement | Status |
|---|---|---|---|---|
Printing a GA chord template shows its Name, as the base record’s ToString() => Name intends |
The derived records TonalModal and Analytical each synthesize their own ToString, which lists every property: TonalModal { Name = Major 7th, PitchClassSet = 0 4 7 E, … }. A derived record keeps the base’s ToString only when it is sealed (records, built-in formatting) |
ChordTemplate.cs:84; one caller, ContextualChordsController.cs:301, passes template.ToString() to a service that logs it (FretboardServices.cs:536) |
A console probe referencing GA.Domain.Core at 5c3a52a, SDK 10.0.112: the dump for ToString() and $"{template}" on both records; a replica with sealed printed maj7 (entry) |
Reproduced; fix proposed in GA PR #816, not merged yet (2026-10-04); still open on 2026-10-06, with the defect unchanged at main 40d33747 (2026-10-06) |
Nullable warnings show in GA’s code: GA.Domain.Core and GA.Core set <Nullable>enable</Nullable> |
Directory.Build.props puts 15 nullable warnings (CS8600, CS8602, CS8618, CS8625, …) in NoWarn “to achieve a clean baseline”, and Directory.Build.targets repeats them. The baseline is clean without them, so the lists only hide new mistakes |
Directory.Build.props:21, Directory.Build.targets:7 | A file with seven nullable mistakes, added to GA.Domain.Core at 5c3a52a: 0 warnings with GA’s configuration, 7 with both lists removed; without that file, 0 in GA.Domain.Core and GA.Core (entry, ga-probes) |
Reproduced; not reported to GA |
GA’s music knowledge services load ChordProgressions.yaml, GuitarTechniques.yaml and SpecializedTunings.yaml |
All three fail to deserialize: one string where the class has a List<string>, strings where it expects objects, a line that isn’t valid YAML. Each loader catches the exception, prints Error loading … and carries on with one default item |
ChordProgressions.yaml:93 against ChordProgressionsConfigLoader.cs:16; GuitarTechniques.yaml:23 against GuitarTechniquesConfigLoader.cs:20; SpecializedTunings.yaml:95; the catch of ChordProgressionsConfigLoader.cs:137, and the same in the two other loaders |
artists.cs at 5c3a52a, SDK 10.0.112: 17 iconic chords, then 1 item for each of the three files, and 16 distinct artists (entry). The three lines are unchanged at GA’s main ae196bf |
Reproduced; reported as GA issue #797 on 2026-10-03; fix proposed in GA PR #808, not merged yet (2026-10-04) |
GetArtistBreakdown returns the 20 artists with the most entries, the most first, as its comment // Top 20 artists says |
Take(20) cuts the alphabetical list before the sort by count, and the sorted result goes into a Dictionary, whose order the documentation leaves undefined |
MusicalKnowledgeService.cs:226 and :235; GetAllArtists sorts by name at :125 |
The same probe: with 16 artists, none is left out, and the dictionary comes out by count, ties in alphabetical order. The cut only bites past 20 artists, once the row above is fixed | Latent, read in the code; fix proposed in GA PR #811, not merged yet (2026-10-04) |
PitchClassSet.ScaleVideoUrl gives the video URL of a scale listed in scale_video_urls.json, which holds 1,490 |
The file is neither embedded in the library nor copied next to it: GA.Domain.Core.csproj has no rule for it. The resource name GA.Domain.Core.Scales.Data.scale_video_urls.json and the fallback path Scales/Data/ both miss its folder, Theory/Tonal/Scales/Data/, and LoadVideoUrls returns an empty dictionary without a message |
ScaleVideoUrlById.cs:30, :36-38; PitchClassSet.cs:350, ScaleMetadataRegistry.cs:36 | files.cs at 5c3a52a, SDK 10.0.112: 0 resources in GA.Domain.Core, no fallback file, 0 scales with a URL (entry). The resource name and the fallback path are unchanged at GA’s main on 2026-10-03 |
Reproduced; fix proposed in GA PR #817, not merged yet (2026-10-04); still open on 2026-10-06, with the defect unchanged at main 40d33747 (2026-10-06) |
IconicChordsConfigLoader.ReloadConfiguration() reads IconicChords.yaml again after a change |
It sets the static readonly field _configuration with FieldInfo.SetValue, which throws FieldAccessException since .NET Core 3.0 (breaking change) |
IconicChordsConfigLoader.cs:107-111, :12 | files.cs: FieldAccessException: Cannot set initonly static field '_configuration' after type 'GA.Domain.Services.IconicChordsConfigLoader' is initialized. (entry). Unchanged at GA’s main on 2026-10-03 |
Reproduced; fix proposed in GA PR #811, not merged yet (2026-10-04) |
A missing IconicChords.yaml is reported |
FindYamlFile tries four paths, two of them relative to the current directory. When none exists, an empty catch swallows its FileNotFoundException, and the loader returns one default chord without a message |
IconicChordsConfigLoader.cs:94-104, :25-28 | files.cs, after deleting the YAML files next to it: 1 chord, “C Major Triad”, started from ga-probes; 17 from the root of GA’s code, through <current directory>/Common/GA.Business.Config; nothing printed (entry) |
Reproduced; a design choice, not reported to GA |
GaCLI reads the appsettings.yaml that its build copies next to the program |
SetBasePath(Directory.GetCurrentDirectory()) reads it from the current directory, as an optional file: started from another folder, GaCLI would run without its settings, silently |
Program.cs:17-19, GaCLI.csproj:59-61 | Built at 5c3a52a and started from another folder: it prints its list of commands. The file’s two sections, EmbeddingService and LmStudio, are read in one place only, sync-mongodb, into settings that no service resolves (entry) |
Verified: the file changes nothing at this commit; not reported to GA |
| Each row of GA’s naturalness CSV has the 6 values of its header | :F2 and {deltaStretch} use the machine’s culture: under fr-FR, 2.50 is written 2,50, and a row gets 8 values; the reader splits on , |
NaturalnessTrainingDataGenerator.cs:160, :21; TrainNaturalnessModelCommand.cs:51-54 | l10_culture.cs with GA’s format string: 6 values under en-CA, 8 under fr-FR. GA’s generator itself not run: it needs a corpus of tablatures (entry) |
Reproduced with the format string; fix proposed in GA PR #811, not merged yet (2026-10-04) |
| GA’s command prints the number of data rows it wrote | csvContent.Split('\n').Length - 1 removes the empty item after the last line break but counts the header: one row too many |
GenerateNaturalnessDataCommand.cs:38 | The arithmetic of Split('\n'), measured by l10_line_endings.cs: 3 lines give 4 items. GA’s command not run |
Latent, read in the code; fix proposed in GA PR #811, not merged yet (2026-10-04) |
| A test of GA’s music knowledge services fails when their YAML files don’t load | Three of the eight tests are skipped with [Ignore("Configuration files not loaded in test environment")]. Of the five others, three only check that a count is greater than zero or that a list isn’t empty, which the one default item of each failed loader satisfies |
MusicalKnowledgeServiceTests.cs:108-110, :141-143, :207-209; :16, :31-33, :179-183 | Probe knowledge-tests, GA’s test file compiled unchanged, SDK 10.0.112: 5 passed, 3 skipped, 0 failed, while three of the four YAML files don’t load. The search for “jazz” passed on 1 result, the default chord progression (entry). Unchanged at GA’s main, 0843879 |
Reproduced; related to GA issue #797; fix proposed in GA PR #808, not merged yet (2026-10-04) |
GA’s [SetUpFixture] TestEnvironment runs before the configuration tests: it sets GA_TEST_MODE=1 and moves the current directory to the repository root |
It is alone in the namespace GA.Business.Core.Tests.TestBootstrap, and NUnit runs a setup fixture only for the tests of its namespace and the namespaces inside it (NUnit): it never runs |
TestEnvironment.cs:1, :7-14 | Probe knowledge-tests: a test in GA.Business.Core.Tests.Configuration printed GA_TEST_MODE: (not set) and Current directory is the test's bin folder: True (entry). At main, 0843879, the namespace still holds only this file |
Reproduced; fix proposed in GA PR #816, not merged yet (2026-10-04); still open on 2026-10-06, with the defect unchanged at main 40d33747 (2026-10-06) |
| The test files of GA.Business.Core.Tests are compiled and run | The project file removes 35 files from the build, “Exclude test files with missing service implementations”: 5 of the names match no file, and the other 30 hold 333 lines that start with [Test] or [TestCase] |
GA.Business.Core.Tests.csproj:39-75 | git ls-tree and a count at 5c3a52a; the project has 136 .cs files. The 35 entries are unchanged at main, 0843879 |
Counted in the code; not reported to GA |
| GA’s CI runs the tests of every test project | Its jobs build and test AllProjects.slnx, which lists 75 of the 111 project files. Six test projects are outside it and named by no workflow; their files hold 102 lines with a test attribute. One of them, GA.Business.Core.Graphiti.Tests, doesn’t compile: it uses GA.Business.Graphiti and references no project |
AllProjects.slnx, ci.yml:43, :103, GA.Business.Core.Graphiti.Tests.csproj | lesson12_counts.py at 5c3a52a; the build of the Graphiti tests stops with 5 errors, CS0234 on its two using GA.Business.Graphiti... lines and CS0246 |
Counted and built; fix proposed in GA PR #812, not merged yet (2026-10-04) |
The PackageReference Update items of GA’s Directory.Build.props align package versions, under “Centralized package version alignment and vulnerability remediation” |
Directory.Build.props is imported before the project’s own items, so Update finds nothing to update. 23 declarations of the six packages concerned keep another version, 13 of them in projects of the solution |
Directory.Build.props:32-58 | Restore of GA.Business.Core.Graphiti.Tests: Microsoft.Extensions.Http 9.0.10, as its project file asks. With the same Update line in Directory.Build.targets: 10.0.0 |
Reproduced, with a control; fix proposed in GA PR #812, not merged yet (2026-10-04) |
| GA’s security scan job reports vulnerable packages | Directory.Build.props and Directory.Build.targets turn off NU1902, NU1903 and NU1904 for every project. The scan step has continue-on-error: true, and dotnet list package --vulnerable exits with 0 when it lists a vulnerable package: the job can’t fail |
Directory.Build.props:16-17, Directory.Build.targets:5-11, ci.yml:398-401 | Exit code 0 with a vulnerable package, measured on a scratch project (Experiments table); GA’s scan not run | Read in the code; fix proposed in GA PR #812, not merged yet (2026-10-04) |
| GA’s Quality Check workflow runs a quality benchmark of GaCLI | benchmark-quality throws NotImplementedException, as 12 of GaCLI’s 28 commands do, analyze-chord, the first example of its help, included. The step has continue-on-error: true |
quality_check.yml:26-29, Program.cs:179-182 | Local build at 5c3a52a: unhandled NotImplementedException. GA’s run 37167745677, on a later commit whose workflow and Program.cs are the same files: exit code 134, run conclusion success |
Reproduced; not reported to GA |
| A GaCLI command that fails tells its caller | sync-mongodb catches its exception, prints Error during sync: Unable to resolve service for type 'GA.Data.MongoDB.Services.MusicalObjectsService' while attempting to activate 'GaCLI.Commands.Runner'. and exits with 0: Runner needs a service that the command doesn’t register |
Program.cs:356, :364-368 | Run from bin/Debug/net10.0 and from another folder: the same message, exit code 0 |
Reproduced; fix proposed in GA PR #811, not merged yet (2026-10-04) |
Experiments
Section titled “Experiments”| Question | Hypothesis, written before measuring | Result | Verdict | Entry and code |
|---|---|---|---|---|
How many nullable warnings does GA’s NoWarn hide in GA.Domain.Core? |
More than 100 | 0 in GA.Domain.Core and GA.Core with both lists removed; a test file with seven mistakes gets 7 warnings without the lists, 0 with them | Refuted | entry, ZzNullableCanary.cs |
What does GA’s Fret do with 50? |
new Fret(50), Fret.FromValue(50) and Fret f = 50; throw an ArgumentOutOfRangeException; Fret.TryCreate(50) returns the failure Fret number must be between -1 (muted) and 36, got 50 |
3 of 3 throw, for 50 and for -2; TryCreate’s message matches word for word |
Confirmed | entry, probe.cs, results.txt |
Does new PositionLocation(5, 3), the string and the fret swapped as plain numbers, compile silently? |
Both orders compile without a warning; the swapped call builds string 5, fret 3 | 0 warnings on the probe’s lines; it prints 5,3 |
Confirmed | entry, probe.cs |
In what order does a foreach read a dictionary after a Remove and an Add? |
Keys C, D, E, G, A, then Remove("D") and Add("F#"): F# takes D’s place, C F# E G A, not the last place |
C F# E G A |
Confirmed | entry, l09_dictionary_order.cs |
Does changing a dictionary inside its own foreach throw, as it does for a list? |
Remove doesn’t throw on .NET 10; adding a key throws; List<T>.Remove throws an InvalidOperationException |
Remove leaves C E without an exception; adding B, and the list’s Remove, both throw Collection was modified; enumeration operation may not execute. |
Confirmed | entry, l09_modify_dictionary.cs, l09_modify_while_looping.cs |
Which artists does GA’s GetArtistBreakdown leave out with Take(20)? |
More than 20 distinct artists: the first 20 of the alphabet are kept, and at least one artist left out has more entries than the smallest count kept; the dictionary comes out by descending count | 16 artists, none left out, because three of the four YAML files don’t load (QA table). The dictionary does come out by count, ties in alphabetical order | Refuted | entry, artists.cs, artists-results.txt |
On a 3-line file that ends with a line break, what do ReadAllText(path).Split('\n') and ReadAllLines return, with LF and with CRLF? |
Split gives 4 items, the last one empty, and with CRLF the first three end with \r; ReadAllLines gives 3 lines without \r for both |
Lengths LF 2 2 2 0 and 2 2 2, CRLF 3 3 3 0 and 2 2 2; with CRLF, split[0] == "E2" is False |
Confirmed | entry, l10_line_endings.cs |
How many bytes do WriteAllText(path, "Ré") and WriteAllText(path, "Ré", Encoding.UTF8) write? |
3 without an encoding, 6 with Encoding.UTF8, starting EF BB BF; ReadAllText returns Ré for both |
52C3A9, then EFBBBF52C3A9; Ré, 2 characters |
Confirmed | entry, l10_bom.cs |
Does File.Exists("data/ga-projects.csv") depend on the folder dotnet run is started from? |
True from code/csharp-beginner, False from the repository root; a path built from EntryPointFileDirectoryPath exists from both |
As predicted, and False from examples too; check.sh now runs both cases on three OSes. EntryPointFileDirectoryPath itself was seen before the hypothesis |
Confirmed | entry, l10_where.cs |
With the YAML files deleted next to the probe, how many iconic chords does GA load, started from GA’s root and from ga-probes? |
17 from GA’s root, through the third path; 1 default chord from ga-probes, with no exception and no message |
17, “Hendrix Chord” first, and 1, “C Major Triad”; nothing printed | Confirmed | entry, files.cs, files-without-yaml.txt, files-from-ga-root.txt |
What does GA’s IconicChordsConfigLoader.ReloadConfiguration() do on .NET 10? |
It throws FieldAccessException: SetValue on a static readonly field is refused since .NET Core 3.0 |
FieldAccessException: Cannot set initonly static field '_configuration' … |
Confirmed | entry, files.cs, files-results.txt |
How many comma-separated values does a row of GA’s naturalness CSV have under en-CA and under fr-FR? |
6 under en-CA, 1,2.50,2.50,2,1,4; 8 under fr-FR, 1,2,50,2,50,2,1,4. Seen before the hypothesis: $"{2.5}" gives 2,5 under fr-FR |
These two rows, exactly | Confirmed | entry, l10_culture.cs |
How long is a file after StreamWriter.Write("E2 A2 D3"), before and after Dispose? |
0 bytes before, 8 after | 0, then 8 | Confirmed | entry, l10_dispose.cs |
Does GA’s ScaleVideoUrlById find its data? |
No resource named …scale_video_urls.json in GA.Domain.Core, no fallback file next to the probe, an empty result, no exception. A subagent’s reading suggested it before I read the code |
0 resources at all, no fallback file, 0 scales with a URL | Confirmed | entry, files.cs |
What does dotnet new xunit create with SDK 10.0.112, and what does dotnet test print? |
xUnit v2, package xunit 2.9.x, with xunit.runner.visualstudio, Microsoft.NET.Test.Sdk and coverlet.collector; run by VSTest, ending with a Passed! line |
xunit 2.9.3, xunit.runner.visualstudio 3.1.4, Microsoft.NET.Test.Sdk 17.14.1, coverlet.collector 6.0.4; VSTest 18.0.2; Passed! - Failed: 0, Passed: 1, … |
Confirmed | entry, Fretboard.Tests.csproj |
Does a [Theory] with four [InlineData] rows count as 4 tests or 1? |
4 | 15 tests in the project: with the theory counted once, it would have been 12 | Confirmed | entry, GuitarTests.cs |
What do Assert.Equal(0.3, 0.1 + 0.2) and Assert.Equal(0.3, 0.1 + 0.2, 10) do? |
The first fails and shows the actual value as 0.30000000000000004; the second passes |
As predicted. Not predicted: the expected value is shown as 0.29999999999999999 |
Confirmed | entry, PitfallTests.cs |
When the method throws ArgumentOutOfRangeException, what do Assert.Throws<ArgumentException> and Assert.ThrowsAny<ArgumentException> do? |
Throws fails, it wants the exact type; ThrowsAny passes |
Assert.Throws() Failure: Exception type was not an exact match; ThrowsAny passes |
Confirmed | entry, PitfallTests.cs, GuitarTests.cs |
| Does xUnit create a new instance of the test class for each test? | Yes: a field incremented by each of two tests is 1 in both | Both tests pass | Confirmed | entry, InstanceTests.cs |
Run unchanged against GA’s services at 5c3a52a, what do GA’s MusicalKnowledgeServiceTests report? |
0 failed, 3 skipped, 5 passed, the specialized tunings test included, because the one default tuning makes its list non-empty. Written after reading the test file, before running it | 5 passed, 3 skipped, 0 failed; 1 specialized tuning, “Overall Valid: True” | Confirmed | entry, knowledge-tests, knowledge-results.txt |
Does GA’s [SetUpFixture] TestEnvironment run before the tests of GA.Business.Core.Tests.Configuration? |
No: it is alone in its namespace. A probe test next to GA’s sees GA_TEST_MODE unset and its bin folder as the current directory. Written after reading the bootstrap, before running |
GA_TEST_MODE: (not set), Current directory is the test's bin folder: True |
Confirmed | entry, knowledge-tests, knowledge-results.txt |
What does dotnet new sln create with SDK 10.0.112? |
A .slnx file, not a .sln |
Fretboard.slnx: <Solution> and </Solution>, no project |
Confirmed | entry, l12-project |
In the folder of a solution with a library, a console app and a test project, what do dotnet build and dotnet test build and run? |
dotnet build builds the three projects; dotnet test runs only the tests of the test project |
dotnet build: Fretboard, Fretboard.App, Fretboard.Tests. dotnet test: one test assembly, 6 passed. Not predicted: after dotnet clean, dotnet test leaves Fretboard.App unbuilt |
Confirmed | entry, l12-project |
With a compile error in the console app, what do dotnet test and dotnet build do in the solution’s folder? Written after seeing the app left unbuilt, before this run |
dotnet test passes and exits with 0; dotnet build fails |
dotnet test: passed, exit code 0. dotnet build: build failed, exit code 1 |
Confirmed | entry |
What does dotnet add package CsvHelper write, and does CsvHelper read a quoted value with a comma as one field? |
The latest stable version; yes, where Split(',') gives one field too many |
Version="33.1.0" on 2026-10-03; Split: 4 fields, CsvHelper: 3 |
Confirmed | entry, l12_csvhelper.cs |
Does #:package work in a file-based app, with and without a version? |
#:package CsvHelper@33.1.0 restores and runs; without @version the restore refuses it |
3 fields; without the version, error NU1015: The following PackageReference item(s) do not have a version specified: CsvHelper, exit code 1 |
Confirmed | entry, l12_package_no_version.cs |
Can CsvHelper’s GetRecords<Project>() fill a positional record that also has a computed property? |
Yes, through its constructor, by header name, ignoring SourceFiles |
12 records and 632 source files; a list of them equals a list built by hand | Confirmed | entry, ProjectFileTests.cs |
What does dotnet pack with no option make of a library that references CsvHelper? |
A Release build, bin/Release/Fretboard.1.0.0.nupkg, a zip with lib/net10.0/Fretboard.dll and a .nuspec that lists CsvHelper “>= 33.1.0” |
As predicted; the .nuspec writes version="33.1.0", NuGet’s notation for 33.1.0 or later, with exclude="Build,Analyzers". Not predicted: the readme message, the commit of HEAD in the .nuspec, and a second dotnet pack with nothing changed that prints neither line |
Confirmed | entry, l12_ex_package.cs |
Does a package with a known advisory warn, and does dotnet list package --vulnerable fail? |
Newtonsoft.Json 12.0.1 gives NU1903 at restore; the command lists it and exits with 0 | NU1903 with GHSA-5crp-9r3c-p9vr, printed twice by a file-based app; dotnet list package --vulnerable and dotnet package list --vulnerable print the table and exit with 0 |
Confirmed | entry, l12_audit.cs |
How long do two Task.Delay(500) take, awaited one after the other and with Task.WhenAll? |
At least 1000 ms; under 900 ms | Five runs: 1014 to 1023 ms, then 508 to 514 ms | Confirmed | entry, l12_async.cs |
What happens to an async call without await in a top-level program? |
Warning CS4014, and the line written after its delay never appears | CS4014 at (2,1); Before, After, exit code 0 |
Confirmed | entry, l12_forgotten_await.cs |
What does the compiler say about await in a constructor, and about an async constructor? |
CS4033; CS0106, modifier not valid | CS4033, as predicted. public async VoicingIndex() gives CS0246 for a type async and CS0542, a member named like its type: the parser reads a method |
Partly refuted | entry, l12_constructor_await.cs |
Does GA’s AllProjects.slnx list every project file of the repository? |
No: at least 5 .csproj or .fsproj are missing |
36 of the 111 are missing; none is listed without existing | Confirmed | entry, lesson12_counts.py, lesson12-results.txt |
| Do GA’s test projects outside the solution run in CI, and are projects outside it built anyway? Written after the previous count | At least one test project is named by no workflow; at least one project outside is referenced by a listed one | Six test projects, 102 lines with a test attribute, named by no workflow; 7 projects outside referenced, GA.Business.Core by 4 |
Confirmed | entry, lesson12_counts.py, lesson12-results.txt |
Does GA.Business.Core.Graphiti.Tests compile? Written after reading its project file and test |
No: it uses GA.Business.Graphiti without a ProjectReference, so CS0246 |
5 errors: CS0234 on lines 7 and 8, then CS0246 | Confirmed | entry, ga-probes |
| Is a package declared with several versions across GA’s project files? | Yes, at least one | 35 packages; Microsoft.Extensions.Hosting and MongoDB.Driver with five versions each |
Confirmed | entry, lesson12_counts.py, lesson12-results.txt |
Do the PackageReference Update items of GA’s Directory.Build.props change a version? Written after reading the file |
No: Microsoft.Extensions.Http stays at the 9.0.10 of the Graphiti tests’ project file |
9.0.10; with the same line in Directory.Build.targets, 10.0.0, while Microsoft.Extensions.Options, updated in the props file only, stays at 9.0.10 |
Confirmed | entry, ga-probes |
| Does GA’s non-test code block on tasks in at least 20 places? | Yes | 27 matches of .Result, .Wait() and GetAwaiter().GetResult(): 4 are SemaphoreSlim.Wait(), 7 read tasks after await Task.WhenAll, 5 are in files removed from compilation; 11 remain, 3 of them in constructors |
Refuted | entry, lesson12_counts.py, lesson12-results.txt |
Does GA have an async void method that isn’t an event handler? |
Yes, at least one | None at all | Refuted | entry, lesson12_counts.py |
Does GaCLI run from another folder without appsettings.yaml, and does sync-mongodb start? Written after reading Program.cs |
It prints its help; sync-mongodb stops on a missing MusicalObjectsService, from either folder |
Help printed; Error during sync: Unable to resolve service for type 'GA.Data.MongoDB.Services.MusicalObjectsService'... from both folders, exit code 0 |
Confirmed | entry, ga-probes |
2026-09-14 — The SDK and file-based apps
Section titled “2026-09-14 — The SDK and file-based apps”- My machine has two SDKs: 10.0.112 and 11.0.100-preview.3.26207.106. Without a
global.json,dotnetpicks the preview. The course folder pins10.0.100withrollForward: latestFeature, which selects 10.0.112 here. - WinGet offers
Microsoft.DotNet.SDK.10in version 10.0.401 on the same day: a newer feature band than mine. The file-based apps page marks#:includeas available from SDK 10.0.300, so lesson 1 says that a file-based app is one file by default, and that#:includeadds others from that SDK on. I haven’t tried#:include: my SDK is older. - The examples are file-based apps rather than one project per lesson: a beginner types one file and runs it, with nothing to set up. A single
dotnet run app.cstook 0.9 s the first time and 0.2 s the next. - Other
.csfiles in the same folder are not compiled with the app:hello.csran fine next to a file full of errors. - Warnings are printed only when the SDK compiles. A second
dotnet runof an unchanged file prints no warning, anddotnet run --no-cachedoesn’t help either: it skips the up-to-date check of the file-based app, but MSBuild still finds the compiled output up to date and skips the compiler.dotnet clean app.csbefore each run brings the warnings back;check.shdoes that, and lesson 3 tells the reader. - Compiler errors go to standard output, with the full path of the file;
The build failed. Fix the build errors and run again.goes to standard error.check.shmerges both and keeps only the file name, so that the expected files are the same on every machine. - Syntax errors hide the others: in the broken program of lesson 1’s exercise 2, the misspelled
Writeline(CS0117) is only reported once the missing;and quote are fixed. The exercise is built on that. - Standard input: when the input comes from a file, the typed text doesn’t appear in the output, so the expected files contain the prompts followed directly by the answers. The lessons show the terminal as a person sees it, with the typed lines, and say so.
- Culture: my Windows culture is
en-CA. Withes-ES,double.TryParse("1.5")returnstrueand 15, since the dot is the thousands separator in Spanish; withfr-FR, it returnsfalse. The example sets each culture explicitly, so the output is the same on every OS. The other examples only use formats that print the same inen-CA,en-USand the invariant culture of the Linux runner.
2026-09-14 — CI
Section titled “2026-09-14 — CI”- Commit
b19a296, run 34914488934: green on the three OSes.actions/setup-dotnetwith the course’sglobal.jsoninstalled SDK 10.0.401 on all three runners, while the outputs were captured with 10.0.112: every compiler message is identical. Jobs took 54 s on Linux, 1 min 36 s on macOS and 2 min 18 s on Windows, with adotnet cleanand a build for each of the 58 file-based apps. Math.Pow(2, 7 / 12.0)printed with all its digits,164.81377845643496, is the same on the three OSes.- The shebang line
#!/usr/bin/env -S dotnet --works on the Linux and macOS runners afterchmod +x, anddotnet runignores it on Windows. - Exit codes of an unhandled exception differ, so
check.shprints them and compares onlyexit crash:- Linux and macOS: 134 (the process aborts,
SIGABRT) for all three crashing examples; - Windows, seen from Git Bash: 127 for
IndexOutOfRangeExceptionandSwitchExpressionException, and 139 with a “Segmentation fault” message forNullReferenceException; - Windows, seen from PowerShell:
0xE0434352(-532462766), the code of a .NET exception, forIndexOutOfRangeException, but0xC0000005(-1073741819), an access violation, forNullReferenceException.
- Linux and macOS: 134 (the process aborts,
2026-09-14 — Dogfooding
Section titled “2026-09-14 — Dogfooding”- The examples use Guitar Alchemist as small data: the standard tuning of
Tuning.Defaultat commita826864, and twelve project names ofcode/ladybugdb/data/ga/projects.csv, extracted at commita26a7893. Nothing in these lessons revealed a problem in GA.
2026-09-29 — Classes and objects
Section titled “2026-09-29 — Classes and objects”- Lesson 5 now teaches construction, instance identity, private fields, public properties, methods and a shared
staticcount, then asks the reader to build aPracticeSessionclass. The English, French and Spanish pages share the same tested C# snippets and compiler output. - On Windows, SDK 10.0.112,
C:/Program Files/Git/bin/bash.exe check.shpassed the course’s examples, exercise solutions and rejected snippets, including the newl05_*files. The two new rejected snippets produced CS0122 and CS0200, respectively. The host’s defaultbashwas WSL and could not find the Windowsdotnet; that first run failed because of the test environment, not the source. - This local result is not a new three-OS CI run and is not proof that the lesson is publicly deployed. Lessons 6–12 remain only in the outline.
2026-09-30 — Lesson 5 in CI on three OSes
Section titled “2026-09-30 — Lesson 5 in CI on three OSes”- Run 36664604662 of the C# for beginners examples workflow, on commit
669d42aof PR #60, passed onubuntu-latest,windows-latestandmacos-latest. In each job’s output,check.shprintsokforl05_objects,l05_ex_practice,l05_get_only_propertyandl05_private_field: the outputs and the CS0122 and CS0200 diagnostics matchexpected/on the three OSes.
2026-10-01 — Records, structs and enums
Section titled “2026-10-01 — Records, structs and enums”- Lesson 6 contrasts a struct with a class (copy on assignment, copy as an argument,
Equals), then records (==on the data,ToString,with,readonly record struct) and enums (numbered from 0, default value, casts,Enum.IsDefined,Enum.Parse). It has four examples, three exercises and five rejected snippets, whose diagnostics are CS0019, CS1612, CS8852 (three times in two files) and CS0266. A script checked that the three locales share the same code blocks, comments aside, and that each block matches its file incode/csharp-beginnerandexpected/. - On Windows, SDK 10.0.112, under Git Bash,
check.shexited 0 with 75oklines, the 12l06_*files included. This is not yet a three-OS CI run. - Then in CI: run 36865237779 of the C# for beginners examples workflow, on commit
114b5edof PR #93, passed onubuntu-latest,windows-latestandmacos-latest. Each job printsokfor the 12l06_*files; the jobs count 76oklines on Linux and macOS and 75 on Windows, becausecheck.shruns the shebang example with./only on Linux and macOS. - Surprises, each kept in the lesson: CS8524 warns about a
switchexpression that has an arm for every name, and its example is(ChordQuality)5while the program fails on 7; the literal0converts to an enum without a cast, while2gives CS0266; aList<T>of mutable structs rejectsshape[0].Fret = 5with CS1612. - A draft of exercise 3 said that
shape[i].Fret += 2on a list ofreadonly record structgives the same CS1612. A probe compiled before publishing gave CS8852 instead; the text was corrected, andcompile_fail/l06_readonly_position.csnow keeps both CS8852 lines. Two other sentences rest on probes that stayed out of the course code: storing a modified copy back withshape[0] = position;printed 5, andList<T>.Containson a class without its own equality answeredFalse. - Dogfooding: at GA commit
5c3a52a,ChordQualitystarts withOther, andPositionLocation,StrandFretarereadonly record structtypes. Nothing in this lesson revealed a problem in GA.
2026-10-01 — Interfaces and inheritance
Section titled “2026-10-01 — Interfaces and inheritance”- Lesson 7 builds
GuitarandUkuleleon aStringInstrumentbase class (base(...),virtual,override,base.Describe(),ToString), then an abstractInstrumentwith an abstractPlay, then anIHasRangeinterface that a class and a record both implement. It has four examples, one of which compiles with warning CS0114, three exercises and five rejected snippets, whose diagnostics are CS0506, CS0509, CS0144, CS0534 and CS0535. The script used for lesson 6 checked the 20 code blocks of the three locales againstcode/csharp-beginnerandexpected/. - On Windows, SDK 10.0.112, under Git Bash,
check.shexited 0 with 87oklines, the 12l07_*files included. This is not yet a three-OS CI run. - Then in CI: run 36893689911 of the C# for beginners examples workflow, on commit
b347f7dof PR #95, passed onubuntu-latest,windows-latestandmacos-latest. Each job printsokfor the 12l07_*files; the jobs count 88oklines on Linux and macOS and 87 on Windows, for the same reason as in lesson 6. - Two sentences of the draft had no run behind them and changed before publishing. That only a variable of type
Ukulelefinds a hiding method is now a line ofexamples/l07_hiding_warning.cs, which printsa ukulele. “The MIDI numbers are those of lesson 4, where C4 is 60” was wrong, since lesson 4 gives no MIDI number; the sentence now says that MIDI numbers count semitones, 12 per octave. - Dogfooding: at GA commit
5c3a52a,ChordTemplateis an abstract record whose derived recordsTonalModalandAnalyticaloverrideName, and whose ownToString() => Nameis notsealed. The records page says that asealedToStringstops the compiler from synthesizing one in derived records (built-in formatting for display), so this one should be shadowed. A probe confirmed it: a console project referencing GA.Domain.Core and GA.Domain.Services at5c3a52a, built with SDK 10.0.112, printedTonalModal { Name = Major 7th, PitchClassSet = 0 4 7 E, … }forToString()and for$"{template}"on aTonalModal, on anAnalyticaland onChordTemplateFactory.CreateModalChords(...).First(), and reflection named the derived record as the type that declaresToString. A replica of the same shape withpublic sealed override string ToString() => Nameprintedmaj7. One call site depends on it: ContextualChordsController.cs:301 passestemplate.ToString()toGetVoicingsForChordAsync, which logs it (FretboardServices.cs:536); the HTTP response takes the chord name from the route, not from this string. The finding is in the QA table; it has not been reported to GA.
2026-10-02 — Exceptions and null safety
Section titled “2026-10-02 — Exceptions and null safety”- Lesson 8 catches exceptions with
tryandcatch, with a diagram of the exception classes; runsfinallyafter areturn, after acatchand before an exception leaves the method; throws anArgumentOutOfRangeExceptionwithnameof; then returns tonull: CS8600 and CS8618,required, the!operator, andWarningsAsErrors=nullableset with#:property. It has six examples, four exercise files (one of them the starting point of exercise 3, with four warnings) and five rejected snippets, whose diagnostics are CS0160, CS0165, CS0029, CS9035 and CS8602 as an error. The script used for lessons 6 and 7 checked the 28 code blocks of the three locales againstcode/csharp-beginnerandexpected/. - On Windows, SDK 10.0.112, under Git Bash,
check.shexited 0 with 102oklines, the 15l08_*files included. This is not yet a three-OS CI run. - The first CI run of PR #109 failed on Linux only (run 36968274101): the Linux runner printed the warnings of
l08_null_warnings.csandl08_ex_null_start.csin line order, while Windows, locally and in CI, and macOS printed CS8618 first. The messages themselves were identical.check.shnow puts the compiler messages first, errors before warnings, each group sorted by line and column, and the lesson shows that order. No expected file of lessons 1 to 7 changed: their messages already came in that order. - Then, with that fix: run 37009963497 of the C# for beginners examples workflow, on commit
e2a95efof PR #109, passed onubuntu-latest,windows-latestandmacos-latest. Each job printsokfor the 15l08_*files; the jobs count 103oklines on Linux and macOS and 102 on Windows, for the same reason as in lesson 6. - Surprises, each kept in the lesson:
throw "fret out of range";gives CS0029, a conversion error, where I expected CS0155 (The type caught or thrown must be derived from System.Exception);string capo = null;on a local variable gives CS8600, not CS8625, while GA’s test file below got CS8625 for anullargument; inl08_null_warnings.cs, CS8618 sits on the property’s declaration and the line that crashes has no warning;tuner offis printed before the value thatTunereturns. - One sentence rests on a probe that stayed out of the course code: without its check,
NoteOfString(standard, 7)fails with anIndexOutOfRangeException. - Dogfooding at GA commit
5c3a52a, with the hypotheses written before measuring in the Experiments table, and the probes and their instructions incode/csharp-beginner/ga-probes:NoWarnlists fifteen nullable warnings in Directory.Build.props:21, and again in Directory.Build.targets:7. I expected more than 100 hidden warnings in GA.Domain.Core; with both lists removed, GA.Domain.Core and GA.Core have none.ZzNullableCanary.cs, seven nullable mistakes added to GA.Domain.Core, compiled without a warning with the lists, and with its seven warnings (CS8600 ×2, CS8602 ×3, CS8618, CS8625) without them. The hypothesis is refuted, and the QA table keeps the finding: the lists only hide the mistakes still to come.new Fret(50),Fret.FromValue(50)andFret f = 50;all throw anArgumentOutOfRangeException, for 50 and for -2, as expected, andFret.TryCreate(50)returnsFailure(Fret number must be between -1 (muted) and 36, got 50). The exception’s message names a private constant,GA.Domain.Core.Instruments.Primitives.Fret value (50) cannot be greater than _maxValue (36). (Parameter 'value'), because IRangeValueObject.cs builds it from the caller’s expressions, and itsActualValuestays empty.- The open question of 2026-10-01 has its answer:
new PositionLocation(5, 3), with the string and the fret swapped as plain numbers, compiles without a warning and builds string 5, fret 3. A build with every analyzer on (AnalysisLevel=latest-all) only asked for explicit types instead ofvaron those lines. The implicit conversions fromint(Str.cs:45, Fret.cs:72) let the swap through.
2026-10-02 — Collections and LINQ
Section titled “2026-10-02 — Collections and LINQ”- Lesson 9 covers
Dictionary<TKey, TValue>: the indexer,Add,TryGetValue, counting withGetValueOrDefault, and the order of aforeach. It coversHashSet<T>, with its set operations on copies andSetEquals. It introduces lambdas, then LINQ on lesson 4’s GA projects, now records:Count,Where,Select,OrderBy,ThenBy,First,FirstOrDefault,Any,All,TakeandToList. It ends with deferred execution, and with changing a collection inside its ownforeach. It has ten examples, four exercises and four rejected snippets: CS1503, CS0021, CS0266, and CS0029 with CS1662 on the same column. The block checker matched the 37 code blocks of each locale withcode/csharp-beginnerandexpected/. - Lesson 4 said that lesson 5 would replace its two arrays of projects with one list; lesson 5 didn’t. Lesson 9 does, and lesson 4 now points to it.
- On Windows, SDK 10.0.112, under Git Bash,
check.shexited 0 with 120oklines, the 18l09_*files included. This is not yet a three-OS CI run. - Then in CI: run 37061776660 of the C# for beginners examples workflow, on commit
690201dof PR #113, passed onubuntu-latest,windows-latestandmacos-latest. Each job printsokfor the 18l09_*files; the jobs count 121oklines on Linux and macOS and 120 on Windows, for the same reason as in lesson 6. - Measured before writing:
- C# 14 has no dictionary expression.
Dictionary<string, int> d = ["C": 0, "E": 4];gives CS1003 and eight other syntax errors, so the lesson uses an index initializer,{ ["C"] = 0 }. Firstwith no match throwsSequence contains no matching element. This probe stayed out of the course code.- The two dictionary experiments of the table. The documentation backs both: “The order in which the items are returned is undefined”, and
Remove“may be safely called without invalidating active enumerators” since .NET Core 3.0.
- C# 14 has no dictionary expression.
- Dogfooding at GA commit
5c3a52a, with the hypothesis written before measuring in the Experiments table:- A survey of the collections in GA’s code, read at that commit, gave the lesson’s examples:
ChordVocabulary.PitchClasses(ChordVocabulary.cs:23), a case-insensitive dictionary read withTryGetValue, where"C#"and"Db"both give 1. Its remarks say that two earlier copies “had drifted”;git grepfor["Db"] = 1or{ "Db", 1 }finds 18 files with a table of their own, this one included.PitchClassSet, anIReadOnlySet<PitchClass>over anImmutableSortedSet(PitchClassSet.cs:37), equal when itsId, computed from the notes, is.
MusicalKnowledgeService.GetArtistBreakdownkeepsGetAllArtists().Take(20), the first 20 by name, then sorts them by count into aDictionary. I expected more than 20 artists, and some left out with more entries than one kept.artists.cs, built againstGA.Domain.Servicesextracted withgit archiveinto the ignored.gafolder, found 16, so none is left out. Two runs gave the same output.- The reason is the first QA row of the day: three of the four YAML files don’t deserialize, and each loader catches the exception and keeps a one-item default. The probe prints the loaders’ messages. A deserialization test outside the course code located the three lines, and I read each one against the class it fills. The three lines are unchanged at GA’s
mainae196bf, the latest the local clone knows. MusicalKnowledgeServiceTestsprintsArtistBreakdown.Take(5)under=== Top Artists ===(MusicalKnowledgeServiceTests.cs:51), which relies on the dictionary’s order too.
- A survey of the collections in GA’s code, read at that commit, gave the lesson’s examples:
2026-10-03 — A fretboard diagram drawn by C#
Section titled “2026-10-03 — A fretboard diagram drawn by C#”- Lesson 3 now shows the notes of frets 0 to 5 as a diagram, below the output of
l03_fretboard.cs. The question was whether ComfyUI or Blender should draw some of the course’s pictures. A generated image can get the number of strings or frets wrong, and this diagram must show exactly the notes the program prints, so a C# program draws it:tools/fretboard.cs, with the same open strings and note names as the lesson. - The SVG has no words, only note names and numbers, so the three locales share one file,
src/assets/csharp-beginner/l03-fretboard.svg. The lesson imports it as a component, like the music theory course, so it follows the light or dark theme. Lesson 3 became an.mdxfile for that; its URL doesn’t change. The description for screen readers is translated. check.shrunstools/fretboard.cs -- --check, which fails if the committed SVG differs from what the program draws. Before trusting it, I changed one note of the file by hand: the check printedis not up to dateand exited 1. A copy with Windows line endings passes. On Windows,check.shnow prints 121oklines.- The tool finds the repository from its own folder,
AppContext.GetData("EntryPointFileDirectoryPath"), not from the current directory, and writes every coordinate as anint: under thefr-FRculture,$"{2.5}"gives2,5. Both points matter for files, the subject of lesson 10. - Rendered with headless Edge in a light page, a dark page and at 343 pixels wide, a phone’s width: the 36 notes match the program’s output line by line. That day, headless Chrome and the DevTools screenshot both timed out on this machine.
- Then in CI: run 37142282699 of the C# for beginners examples workflow, on commit
9229f15of PR #126, passed onubuntu-latest,windows-latestandmacos-latest. Each job printsok fretboard svg; the jobs count 122oklines on Linux and macOS and 121 on Windows, for the same reason as in lesson 6.
2026-10-03 — Files and text
Section titled “2026-10-03 — Files and text”- Lesson 10 is written in three locales:
File,Path, the current directory, line breaks, a CSV file, numbers and cultures, streams andusing, encodings, and what Guitar Alchemist does with its files. It has 11 examples, 4 exercises and 3 rejected snippets, each compared withexpected/bycheck.sh. The lesson is an.mdxfile, for the tabs that show a path on each OS. data/ga-projects.csvis new: lesson 4’s twelve projects with their numbers of.csand.fsfiles at GA’s5c3a52a, counted withgit ls-treein each project’s folder. It has two surprises.GA.Business.Config, an F# project, holds 8 C# files, the services that lesson 9 met.GA.Presentationhas no source file, only its.csproj..gitattributesgives*.csvLF line endings, so the file is the same on every OS.- Eight hypotheses were written before measuring, and all eight are confirmed (Experiments table). Two weren’t blind: writing
tools/fretboard.csearlier that day had shownEntryPointFileDirectoryPath, and$"{2.5}"underfr-FR. E14’s hypothesis came from a subagent’s reading of GA, before I readScaleVideoUrlByIdmyself. check.shrunsl10_where.csa second time from the root of the repository and compares it withexpected/l10_where_root.txt, so the CI checks on three OSes that the same program misses its file from another folder. The root has noglobal.json: there, on my machine,dotnetpicks the SDK 11.0.100 preview instead of 10.0.112, another choice made by the current directory. The output is the same.TextFieldParsercompiled withusing Microsoft.VisualBasic.FileIO;and no package: it comes with .NET.- The probe
files.csruns GA’s loaders three times, as its README describes: as built, after deleting the YAML files next to itself, and from the root of the extracted GA code. Its build prints IL3000 on its own use ofAssembly.Location, the call thatScaleVideoUrlById’s fallback relies on: in a single-file app it returns an empty string. - Six new QA rows come from the probe and from reading GA’s file code. A subagent listed the candidates; I read each row’s lines again at
5c3a52abefore writing it. Two of the rows are read in the code only, not run. - Lesson 9’s YAML loading failure is now GuitarAlchemist/ga#797, filed on 2026-10-03 with links pinned to GA’s
main,ba3b9ac, where the three lines are unchanged. - On Windows with SDK 10.0.112,
check.shexits 0 with 140oklines, the 19 of lesson 10 included. This is not yet a three-OS CI run. - Then in CI: run 37145323198 of the C# for beginners examples workflow, on commit
8b9fc03of PR #129, passed onubuntu-latest,windows-latestandmacos-latest. Each job printsokfor the 19l10_*checks, the run from the repository root included; the jobs count 141oklines on Linux and macOS and 140 on Windows, for the same reason as in lesson 6.
2026-10-03 — Unit tests
Section titled “2026-10-03 — Unit tests”- Lesson 11 is written in three locales: a test by hand, a library and a test project,
[Fact]anddotnet test,[Theory], four tests that fail on purpose, one object per test, the public surface, and Guitar Alchemist’s tests. The code is inl11-tests: the libraryFretboard, with methods of lessons 4, 8 and 10 unchanged;Fretboard.Tests, 26 tests with the exercise solutions;Pitfalls.Tests, 6 tests of which 4 fail on purpose.examples/l11_by_hand.csandcompile_fail/l11_private.csrun with the other files. - The projects come from
dotnet new classlib,dotnet new xunitanddotnet add reference, run in the course folder; the lesson quotes their messages. The template creates an xUnit v2 project, while xUnit’s site says v2 is in maintenance mode and encourages v3. check.shrunsdotnet teston both test projects afterdotnet clean, so that the build’s warnings are printed every time, and keeps a summary: the warnings, each failed test and its message, the last line. It drops the paths, the durations and the stack traces, and sorts the failed tests by name: xUnit reported the four failures in another order than the file. The arrows↓and↑of a collection failure came through Git Bash on Windows; the CI will show whether they do on the three OSes.- Seven hypotheses were written before measuring, and all seven are confirmed (Experiments table). The last two were written after reading GA’s test file and its bootstrap, before running them.
- Observed without a hypothesis: from a test project, a library member marked
internalgives CS0117, “does not contain a definition”, not CS0122, in a scratch pair of projects; the analyzer of thexunitpackage warns about swapped arguments (xUnit2000) before the tests run. - The first failure of the lesson is a check by hand:
FretFrequency(246.94, 1)gives 261.62, and a table gives 261.63 for middle C, because the open B string is already rounded. The program ends with exit code 0 anyway, which motivates a framework. - The probe
knowledge-testsis an NUnit project that compiles four files of GA.Business.Core.Tests unchanged from the extracted code:MusicalKnowledgeServiceTests.cs,TestEnvironment.cs,GlobalUsings.csandAssemblyInfo.cs, with the package versions of that project.ProbeTests.csis its only own file. Its build prints only IDE0028 in GA’sScaleMetadataRegistry.cs. Its run: 9 tests, 6 passed (GA’s 5 and the probe’s), 3 skipped. - This answers the journal’s open question: no GA test fails when a YAML file of
GA.Business.Configstops loading. The question is removed from the list below. - On Windows with SDK 10.0.112,
check.shexits 0 with 144oklines, the 4 of lesson 11 included. This is not yet a three-OS CI run. - Then in CI: run 37170066128 of the C# for beginners examples workflow, on commit
c3427a9of PR #135, passed onubuntu-latest,windows-latestandmacos-latest. Each job printsokfor the 4l11_*checks; the jobs count 145oklines on Linux and macOS and 144 on Windows, for the same reason as in lesson 6.l11_Pitfalls.Testspasses on Windows too, so the arrows↓and↑of the collection failure come out of Git Bash unchanged there.
2026-10-03 — A small project
Section titled “2026-10-03 — A small project”- Lesson 12 is written in three locales: a solution with lesson 11’s library, a console app and a test project; exit codes; a package from NuGet in a file-based app and in the library; a package with a known vulnerability;
dotnet pack; a first look atasync; and Guitar Alchemist’s solution, package versions, command-line tool and blocking waits. The code is inl12-project:Fretboard, lesson 11’s library withGuitarunchanged,ProjectwithoutParse, andProjectFile, which reads the CSV file with CsvHelper 33.1.0;Fretboard.App, with three commands;Fretboard.Tests, 6 tests. Four examples, two rejected snippets and four exercise solutions are file-based apps. check.shpacks the library before the loop, sinceexercises/l12_ex_package.csreads the package, and deletes the old package first: with nothing changed,dotnet packkeeps it and prints neither of its two lines. It runsdotnet teston the solution afterdotnet cleanand checks that the console app is still unbuilt, thendotnet build, whose output it reduces to the project names, sorted, and the counts. It runs the app with six commands and prints each exit code. A newsedrule removes the folder from the NuGet messages of a#:packageline,file.csproj : warning NUxxxx.- Twelve hypotheses about the SDK and nine about GA were written before measuring, some after reading the code they test (Experiments table): 16 confirmed, 1 partly refuted, 2 refuted. The refuted ones are about GA’s waits: 11 compiled blocking waits, not 20 or more, and no
async voidat all. - Observed without a hypothesis:
dotnet teston a solution doesn’t build the console app, which led to the hypothesis on a compile error; the NuGet audit warning comes twice in a file-based app; the.nuspecrecords the commit ofHEAD; 12 of GaCLI’s 28 commands throwNotImplementedException, and GA’s Quality Check run 37167745677 is green with a step that ended with exit code 134. - The probes are in
ga-probes:lesson12_counts.pyreads the commit withgitonly, andlesson12-results.txtholds its output; the build ofGA.Business.Core.Graphiti.Testsand its restore, with a control copy whoseUpdateline is inDirectory.Build.targets; GaCLI, built from the 18 projects of its references in 28 s with 21 warnings and run from two folders. - This settles the item to verify about GaCLI started from another folder: it runs, and at this commit
appsettings.yamlchanges nothing. The item is removed from the list below, and the QA row is updated. - On Windows with SDK 10.0.112,
check.shexits 0 with 158oklines, the 14 of lesson 12 included. This is not yet a three-OS CI run. - The PR’s first CI run, 37174610693, failed
l12_auditandl12_package_no_versionon the three OSes and passed the 12 other checks of lesson 12. CI runs SDK 10.0.401, whichglobal.jsonallows (rollForward: latestFeature), and that SDK names the project behind a file-based appl12_audit.cs.csproj, where 10.0.112 saysl12_audit.csproj. Thesedrule expected no dot in the name and left the folder in.check.shnow writesfile.csprojfor both, and the lesson gives both names. - Then in CI: run 37175088215 of the C# for beginners examples workflow, on commit
87dbafdof PR #137, passed onubuntu-latest,windows-latestandmacos-latest, all three with SDK 10.0.401. Each job printsokfor the 14l12_*checks; the jobs count 159oklines on Linux and macOS and 158 on Windows, for the same reason as in lesson 6. The NuGet audit warning ofl12_audit.cscomes out on the three runners, and the sorted output of the build is the same.
2026-10-04 — Fixes proposed to Guitar Alchemist
Section titled “2026-10-04 — Fixes proposed to Guitar Alchemist”Thirteen rows of the QA table went back to GA as five pull requests, opened from a separate clone at main 75959835. Each one measures the defect before and after the fix, and GA’s CI passes on all five. None is merged yet: GA leaves the review and the merge to someone other than the author.
| PR | QA rows | Change | Before → after |
|---|---|---|---|
| #808 | The three YAML files (issue #797); the tests that can’t see a fallback | The three files load; the three [Ignore] tests run, and a new test fails when a file falls back to its default item |
With the old files, 5 of the 9 tests fail, the new one with Expected: greater than 1 But was: 1 for each broken file |
| #811 | ReloadConfiguration, GetArtistBreakdown, the CSV’s culture, its row count, sync-mongodb |
A new Lazy instead of FieldInfo.SetValue; the sort before Take(20); FormattableString.Invariant; the header left out of the count; Environment.Exit(1) |
FieldAccessException → the four files reload; < 6, 8, 8, … > values per row under fr-FR → 6; 3 rows counted for a header and two rows → 2; sync-mongodb exits with 0 → 1 |
| #812 | The test projects outside AllProjects.slnx, the PackageReference Update items, the security scan |
The items move to Directory.Build.targets; three test projects join the solution; the scan fails on an advisory that a baseline doesn’t list |
NU1605 downgrades hidden by NoWarn: 80 → 40; 56 more tests in CI; 21 advisories in the baseline |
| #816 | A chord template’s ToString, the [SetUpFixture] |
ChordTemplate.ToString is sealed; TestEnvironment moves to the project’s root namespace |
A dump of 442 or 475 characters → the name; GA_TEST_MODE unset → 1, with the repository root as current directory |
| #817 | ScaleVideoUrl |
GA.Domain.Core.csproj embeds scale_video_urls.json, and ScaleVideoUrlById reads it under its default name |
Sets with a video URL: 0 → 1,490 of 4,096 |
- With #808’s YAML, GA has 63 artists instead of 16, so
GetArtistBreakdown’s cut before the sort bites: #811’s test of the breakdown fails on that data with the old code. - Moving the
Updateitems alone failed restore,error NU1107: Version conflict detected for Microsoft.Extensions.Logging.Abstractionsin GA.Infrastructure, because only five Microsoft.Extensions packages were aligned. #812 puts the 19 that ship with .NET and ASP.NET Core at one version, 10.0.6, the highest one a project or a package asks for. - Three of the six test projects outside the solution, which no workflow names, join it: the Graphiti tests, with the project reference they lacked, ProbabilisticGrammar’s and TabConversion.Api’s.
GaChatbot.TestsandTests/GaApi.Testsdon’t compile onmaineither, andFloorManager.Tests.Playwrightneeds a running FloorManager. - GA’s
risk-reportcheck fails on #812 by design: its policy forbids an agent to change.github/workflowsand countsDirectory.Build.*,*.csprojand*.slnxas one-way doors, so a maintainer has to review it. - #817 changes a
.csproj, which GA’s risk policy counts as a one-way door, so it is a pull request of its own. - #816’s first CI run failed on a test of another project that gives PowerShell 10 seconds; the same test had failed on another branch on 2026-05-23, and the rerun passed.
2026-10-06 — GA PRs #816 and #817, rechecked
Section titled “2026-10-06 — GA PRs #816 and #817, rechecked”GA’s #816 and #817 are still open, with no review and no comment. Their CI passes, 16 checks with 5 skipped, and GitHub reports both as mergeable without conflict, although GA’s main has moved 14 commits past their base, to 40d33747. Read through the GitHub API, the three defects are unchanged at that commit:
ChordTemplate’sToString() => Nameis still notsealed(ChordTemplate.cs:84).ScaleVideoUrlByIdstill reads the resourceGA.Domain.Core.Scales.Data.scale_video_urls.jsonand the fallback pathScales/Data/(ScaleVideoUrlById.cs:30, :38), andGA.Domain.Core.csprojstill doesn’t name the file.TestEnvironmentis still alone inGA.Business.Core.Tests.TestBootstrap(TestEnvironment.cs:1).
Lesson 7 now shows the ToString trap next to ChordTemplate, with examples/l07_record_tostring.cs. A derived record prints Seventh { Name = Major 7th, Quality = Major 7th }; the same record under a base whose ToString is sealed prints Major 7th. Both outputs come from SDK 10.0.112, compared with expected/ the way check.sh does. The existing l07_inheritance.cs, run the same way, still matches its expected output. The GA probes weren’t rerun: they need a GA checkout outside the course’s code. Neither was check.sh as a whole.
To verify
Section titled “To verify”- How many artists GA would count once its three YAML files load. A PyYAML script over the raw files, outside the course code, estimates 63. By the same estimate,
Take(20)would leave out 8 artists that have more entries than the smallest count kept, The Beatles (3 entries) among them. - The installation commands for Linux and macOS: only the CI’s
setup-dotnetran on those OSes. - The debugger walkthrough of lesson 3 in VS Code, Visual Studio and Rider, for a file-based app and for a project. VS Code 1.118 and Rider are installed on my machine; Visual Studio isn’t.
- The license terms of the three editors, at the time of reading.
Open questions
Section titled “Open questions”- Do GA’s other projects, beyond GA.Domain.Core and GA.Core, have nullable warnings that the two
NoWarnlists hide? Only these two were measured (2026-10-02).