Skip to content

Commit b71cbca

Browse files
authored
Merge pull request #1 from lucasbellesi/codex/educational-improvement-plan
Improve educational quality gates and exercise contracts
2 parents 9d78faf + 5117441 commit b71cbca

31 files changed

Lines changed: 1494 additions & 463 deletions

File tree

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ For a faster loop while you are iterating, use a narrower command on the area yo
4040
- Lint: `./scripts/lint.ps1` or `bash ./scripts/lint.sh`
4141
- Smoke checks: `./scripts/smoke-languages.ps1` or `bash ./scripts/smoke-languages.sh`
4242
- Compiled-language builds: `./scripts/build-all.ps1` or `bash ./scripts/build-all.sh`
43+
- Generated artifact cleanup: `./scripts/clean-artifacts.ps1` or `bash ./scripts/clean-artifacts.sh`
4344

4445
If you are changing one C++ file and want a local spot check before the full pass, you can still compile it directly with:
4546

@@ -59,9 +60,10 @@ These smoke checks also compile standalone C# exercises by generating temporary
5960
TypeScript checks restore Node dependencies from `package-lock.json`, compile with `tsc`, and execute the emitted JavaScript with `node`.
6061

6162
The public PowerShell and Bash scripts are thin wrappers over the shared Python automation core in `scripts/automation.py`. Curriculum validation and smoke target metadata live in `scripts/automation_manifest.json`.
63+
The artifact cleanup command removes generated build outputs, reports, temporary binaries, and exercise report files while keeping restored dependencies such as `node_modules`.
6264

6365
Use [EDUCATIONAL_EXAMPLE_REVIEW_RUBRIC.md](EDUCATIONAL_EXAMPLE_REVIEW_RUBRIC.md) when reviewing `example/main.*` files for teaching clarity and parity.
64-
During focused cleanup work, run `python scripts/automation.py audit-education-quality --fail-on-findings` to make learner-quality findings fail locally instead of only writing the advisory report.
66+
`verify-repo` fails on blocking education-quality findings: low example-comment ratio, missing output explanation markers, or boilerplate comments. Oversized example findings remain advisory. During focused cleanup work, run `python scripts/automation.py audit-education-quality --fail-on-findings` to make every learner-quality finding fail locally.
6567

6668
4. Update related README files when behavior or structure changes.
6769
5. Open a pull request with a clear description of what changed and why.

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ bash ./scripts/verify-repo.sh
160160
bash ./scripts/lint.sh
161161
~~~
162162

163-
`verify-repo` validates curriculum structure, output contracts, and compiled-language builds. `lint` validates formatting and static checks for C++, Python, Go, C#, and TypeScript.
163+
`verify-repo` validates curriculum structure, the blocking education-quality gate, output contracts, and compiled-language builds. `lint` validates formatting and static checks for C++, Python, Go, C#, and TypeScript.
164164

165165
Use narrower commands only when you want a faster loop on one area:
166166

@@ -177,6 +177,7 @@ Use narrower commands only when you want a faster loop on one area:
177177
./scripts/lint.ps1
178178
./scripts/smoke-languages.ps1
179179
./scripts/build-all.ps1
180+
./scripts/clean-artifacts.ps1
180181
./scripts/verify-repo.ps1
181182
~~~
182183

@@ -193,19 +194,22 @@ bash ./scripts/audit-education-quality.sh
193194
bash ./scripts/lint.sh
194195
bash ./scripts/smoke-languages.sh
195196
bash ./scripts/build-all.sh
197+
bash ./scripts/clean-artifacts.sh
196198
bash ./scripts/verify-repo.sh
197199
~~~
198200

199201
GitHub Actions validates links, README structure, module completeness, checkpoint completeness, documentation sync, compiled-language builds, multi-language smoke checks, and Linux lint checks for C++, Python, Go, C#, and TypeScript.
200202

201203
The public PowerShell and Bash scripts remain the supported entrypoints, but they now delegate to a shared Python automation core under `scripts/automation.py` backed by `scripts/automation_manifest.json`.
202204

205+
Use `clean-artifacts` when you want to remove generated build outputs, reports, temporary binaries, and exercise report files without removing dependencies such as `node_modules`.
206+
203207
The multi-language smoke scripts also compile standalone C# exercises by generating temporary validation projects during the check and compile TypeScript programs before executing their smoke targets.
204208

205209
Use [EDUCATIONAL_EXAMPLE_REVIEW_RUBRIC.md](EDUCATIONAL_EXAMPLE_REVIEW_RUBRIC.md) to keep entry examples pedagogically consistent during reviews. The education audit command is advisory and writes markdown/json findings without failing CI.
206210
Use [EDUCATIONAL_ANTI_PATTERN_BACKLOG.md](EDUCATIONAL_ANTI_PATTERN_BACKLOG.md) for the prioritized anti-pattern vs corrected-example expansion plan.
207211

208-
When you want to enforce the education audit during focused cleanup work, run:
212+
`verify-repo` now fails on blocking education-quality findings: low example-comment ratio, missing output explanation markers, or boilerplate comments. Oversized example findings remain advisory. When you want the stricter local cleanup mode that also fails on oversized examples, run:
209213

210214
~~~bash
211215
python scripts/automation.py audit-education-quality --fail-on-findings

languages/cpp/03-advanced/structs-and-classes/example/main.cpp

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,29 @@
44

55
#include <iostream>
66
#include <string>
7-
#include <vector>
87
using namespace std;
98

109
struct Student {
10+
// A struct is a good fit for simple public data.
1111
string name;
12-
int age;
1312
double grade;
14-
15-
void print() const {
16-
cout << "Student{name=\"" << name << "\", age=" << age << ", grade=" << grade << "}\n";
17-
}
1813
};
1914

2015
class BankAccount {
2116
public:
2217
BankAccount(const string& ownerName, double initialBalance)
23-
: owner(ownerName), balance(initialBalance) {
24-
if (balance < 0.0) {
25-
balance = 0.0;
26-
}
27-
}
18+
: owner(ownerName), balance(initialBalance < 0.0 ? 0.0 : initialBalance) {}
2819

29-
bool deposit(double amount) {
30-
if (amount <= 0.0) {
20+
bool applyTransaction(double amount) {
21+
// A class can guard updates before private state changes.
22+
if (balance + amount < 0.0) {
3123
return false;
3224
}
3325
balance += amount;
3426
return true;
3527
}
3628

37-
bool withdraw(double amount) {
38-
if (amount <= 0.0 || amount > balance) {
39-
return false;
40-
}
41-
balance -= amount;
42-
return true;
43-
}
44-
45-
const string& getOwner() const { return owner; }
46-
47-
double getBalance() const { return balance; }
29+
void print() const { cout << "Owner: " << owner << "\nBalance: " << balance << '\n'; }
4830

4931
private:
5032
string owner;
@@ -54,21 +36,20 @@ class BankAccount {
5436
// Walk through one fixed scenario so structs and classes behavior stays repeatable.
5537
int main() {
5638
// Prepare sample inputs that exercise the key structs and classes path.
57-
vector<Student> students{{"Alex Johnson", 19, 8.7}, {"Maya Patel", 20, 9.1}};
39+
Student first{"Alex Johnson", 8.7};
40+
Student second{"Maya Patel", 9.1};
5841

5942
// Report values so learners can verify the structs and classes outcome.
6043
cout << "Students (struct example):\n";
61-
for (const Student& student : students) {
62-
student.print();
63-
}
44+
cout << "Student{name=\"" << first.name << "\", grade=" << first.grade << "}\n";
45+
cout << "Student{name=\"" << second.name << "\", grade=" << second.grade << "}\n";
6446

6547
BankAccount account("Alex Johnson", 100.0);
66-
account.deposit(40.0);
67-
account.withdraw(25.0);
48+
account.applyTransaction(40.0);
49+
account.applyTransaction(-25.0);
6850

6951
cout << "\nBank account (class example):\n";
70-
cout << "Owner: " << account.getOwner() << '\n';
71-
cout << "Balance: " << account.getBalance() << '\n';
52+
account.print();
7253

7354
return 0;
7455
}

languages/csharp/02-core/file-io-basics/example/main.cs

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ static bool TryParseScoreRow(string line, out string name, out int score)
1212
name = string.Empty;
1313
score = 0;
1414

15+
// Each valid row has one name token followed by one integer score.
1516
string[] parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
1617
if (parts.Length != 2 || !int.TryParse(parts[1], out score))
1718
{
@@ -26,50 +27,34 @@ static bool TryParseScoreRow(string line, out string name, out int score)
2627
static void Main()
2728
{
2829
// Prepare sample inputs that exercise the key file io basics path.
29-
string runDirectory = Path.Combine(Path.GetTempPath(), "learn-lang-file-io-csharp");
30-
Directory.CreateDirectory(runDirectory);
31-
string inputPath = Path.Combine(runDirectory, "scores.txt");
32-
string outputPath = Path.Combine(runDirectory, "summary.txt");
33-
34-
if (!File.Exists(inputPath))
35-
{
36-
File.WriteAllLines(inputPath, new[] { "ana 90", "bob 82", "invalid row", "carla 95" });
37-
}
30+
string inputPath = Path.Combine(Path.GetTempPath(), "learn-lang-file-io-csharp-scores.txt");
31+
string outputPath = Path.Combine(
32+
Path.GetTempPath(),
33+
"learn-lang-file-io-csharp-summary.txt"
34+
);
35+
File.WriteAllLines(inputPath, new[] { "ana 90", "bob 82", "invalid row", "carla 95" });
3836

3937
int validRows = 0;
4038
int sum = 0;
4139

42-
using (StreamReader reader = new StreamReader(inputPath))
40+
// Read one row at a time so malformed rows can be skipped safely.
41+
foreach (string line in File.ReadLines(inputPath))
4342
{
44-
string? line;
45-
while ((line = reader.ReadLine()) is not null)
43+
if (!TryParseScoreRow(line, out string name, out int score))
4644
{
47-
if (!TryParseScoreRow(line, out string name, out int score))
48-
{
49-
continue;
50-
}
51-
52-
validRows++;
53-
sum += score;
54-
// Report values so learners can verify the file io basics outcome.
55-
Console.WriteLine($"{name} -> {score}");
45+
continue;
5646
}
57-
}
5847

59-
if (validRows == 0)
60-
{
61-
Console.WriteLine("No valid rows found.");
62-
return;
48+
validRows++;
49+
sum += score;
50+
// Report values so learners can verify the file io basics outcome.
51+
Console.WriteLine($"{name} -> {score}");
6352
}
6453

6554
double average = (double)sum / validRows;
6655

67-
using (StreamWriter writer = new StreamWriter(outputPath, false))
68-
{
69-
writer.WriteLine($"Rows: {validRows}");
70-
writer.WriteLine($"Average: {average:F2}");
71-
}
72-
56+
// Persist the summary separately from the console walkthrough.
57+
File.WriteAllLines(outputPath, new[] { $"Rows: {validRows}", $"Average: {average:F2}" });
7358
Console.WriteLine($"Summary written to {outputPath}");
7459
}
7560
}

languages/csharp/02-core/input-validation/example/main.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ static int ReadIntInRange(string prompt, int minValue, int maxValue)
1212
{
1313
Console.Write(prompt);
1414
string? raw = Console.ReadLine();
15+
// Parse first, then validate the numeric range before returning.
1516
if (!int.TryParse(raw, out int value))
1617
{
1718
Console.WriteLine("Invalid input type. Please enter an integer.");
@@ -34,6 +35,7 @@ static double ReadDoubleInRange(string prompt, double minValue, double maxValue)
3435
{
3536
Console.Write(prompt);
3637
string? raw = Console.ReadLine();
38+
// The same validation shape works for decimal input too.
3739
if (!double.TryParse(raw, out double value))
3840
{
3941
Console.WriteLine("Invalid input type. Please enter a decimal number.");
@@ -54,6 +56,7 @@ static double ReadDoubleInRange(string prompt, double minValue, double maxValue)
5456
static void Main()
5557
{
5658
// Prepare sample inputs that exercise the key input validation path.
59+
// Only validated values reach the final summary.
5760
int age = ReadIntInRange("Enter your age (1-120): ", 1, 120);
5861
double gpa = ReadDoubleInRange("Enter your GPA (0.0-4.0): ", 0.0, 4.0);
5962

languages/csharp/03-advanced/inheritance-and-polymorphism/example/main.cs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// Helper setup for inheritance and polymorphism; this keeps the walkthrough readable.
88
abstract class Shape
99
{
10+
// The base type names the behavior that every concrete shape must provide.
1011
public abstract double Area();
1112
public abstract string Name { get; }
1213
}
@@ -16,16 +17,10 @@ class Rectangle : Shape
1617
private readonly double width;
1718
private readonly double height;
1819

19-
public Rectangle(double widthValue, double heightValue)
20-
{
21-
width = widthValue;
22-
height = heightValue;
23-
}
20+
public Rectangle(double widthValue, double heightValue) =>
21+
(width, height) = (widthValue, heightValue);
2422

25-
public override double Area()
26-
{
27-
return width * height;
28-
}
23+
public override double Area() => width * height;
2924

3025
public override string Name => "Rectangle";
3126
}
@@ -34,15 +29,9 @@ class Circle : Shape
3429
{
3530
private readonly double radius;
3631

37-
public Circle(double radiusValue)
38-
{
39-
radius = radiusValue;
40-
}
32+
public Circle(double radiusValue) => radius = radiusValue;
4133

42-
public override double Area()
43-
{
44-
return Math.PI * radius * radius;
45-
}
34+
public override double Area() => Math.PI * radius * radius;
4635

4736
public override string Name => "Circle";
4837
}
@@ -55,6 +44,7 @@ static void Main()
5544
// Prepare sample inputs that exercise the key inheritance and polymorphism path.
5645
List<Shape> shapes = new List<Shape> { new Rectangle(3.0, 4.0), new Circle(2.0) };
5746

47+
// The loop depends on the Shape contract, not on concrete type checks.
5848
foreach (Shape shape in shapes)
5949
{
6050
// Report values so learners can verify the inheritance and polymorphism outcome.

languages/csharp/04-expert/memory-management-and-raii/example/main.cs

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Helper setup for memory management and raii; this keeps the walkthrough readable.
77
sealed class BufferLease : IDisposable
88
{
9-
private int[]? values;
9+
private int[] values;
1010
private readonly string name;
1111
private bool disposed;
1212
private static int activeLeases;
@@ -24,34 +24,13 @@ public BufferLease(string name, int length)
2424

2525
public void FillSequence(int start, int step)
2626
{
27-
// Guard every public operation so use-after-dispose fails loudly.
28-
EnsureNotDisposed();
29-
3027
for (int i = 0; i < values!.Length; i++)
3128
{
3229
values[i] = start + (i * step);
3330
}
3431
}
3532

36-
public int Sum()
37-
{
38-
// Reading from the buffer is only valid while the lease is active.
39-
EnsureNotDisposed();
40-
41-
int total = 0;
42-
foreach (int value in values!)
43-
{
44-
total += value;
45-
}
46-
47-
return total;
48-
}
49-
50-
public string Describe()
51-
{
52-
EnsureNotDisposed();
53-
return string.Join(" ", values!);
54-
}
33+
public string Describe() => string.Join(" ", values);
5534

5635
public void Dispose()
5736
{
@@ -62,29 +41,11 @@ public void Dispose()
6241
}
6342

6443
disposed = true;
65-
values = null;
44+
values = Array.Empty<int>();
6645
activeLeases--;
6746
Console.WriteLine($"[dispose] {name} active={activeLeases}");
6847
GC.SuppressFinalize(this);
6948
}
70-
71-
~BufferLease()
72-
{
73-
// The finalizer is a last-resort safety net, not the normal cleanup path.
74-
if (!disposed)
75-
{
76-
activeLeases--;
77-
Console.WriteLine($"[finalizer] {name} active={activeLeases}");
78-
}
79-
}
80-
81-
private void EnsureNotDisposed()
82-
{
83-
if (disposed)
84-
{
85-
throw new ObjectDisposedException(name);
86-
}
87-
}
8849
}
8950

9051
class Program
@@ -98,18 +59,10 @@ static void Main()
9859
// The using block calls Dispose automatically when the scope exits.
9960
using (BufferLease scores = new BufferLease("scores", 5))
10061
{
101-
int threshold = 25;
10262
scores.FillSequence(10, 5);
10363

10464
// Report buffer state while the lease is still valid.
10565
Console.WriteLine($"Scores: {scores.Describe()}");
106-
Console.WriteLine($"Sum: {scores.Sum()}");
107-
Console.WriteLine($"Threshold for review: {threshold}");
108-
109-
// A nested using declaration owns a second resource until the outer scope ends.
110-
using BufferLease scratch = new BufferLease("scratch", 3);
111-
scratch.FillSequence(1, 1);
112-
Console.WriteLine($"Scratch buffer: {scratch.Describe()}");
11366
Console.WriteLine($"Active leases inside scope: {BufferLease.ActiveLeases}");
11467
}
11568

0 commit comments

Comments
 (0)