style(no-slop): remove every em-dash + banned words across all modules + capstone
Apply the no-ai-slop standard (now binding in AGENTS.md): the em-dash character is banned outright (restructured, not blind-replaced), plus the banned word/phrase list (delve, leverage, robust, seamless, truly, unlock, etc.). 0 em-dashes remain in modules + capstone; the only "robust" left is the planted M10 ai-change.patch trap. Module H1 titles use a colon separator. All deliberate teaching devices preserved; labs compile/parse (py/sh/yaml/json); no junk. AGENTS.md updated with the hard no-slop rules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Module 14 — Continuous Integration
|
||||
# Module 14: Continuous Integration
|
||||
|
||||
> **The AI writes code that looks right. CI checks whether it actually is: automatically, on every
|
||||
> push, before anyone trusts it.** This module turns the tests you wrote in Module 13 into a gate
|
||||
@@ -8,18 +8,18 @@
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Module 8 — Remotes and Hosting.** CI runs *on the forge*, triggered by pushes. You need a repo
|
||||
pushed to a remote (any forge — GitHub, GitLab, a self-hosted Forgejo/Gitea, whatever you set up
|
||||
- **Module 8: Remotes and Hosting.** CI runs *on the forge*, triggered by pushes. You need a repo
|
||||
pushed to a remote (any forge: GitHub, GitLab, a self-hosted Forgejo/Gitea, whatever you set up
|
||||
in Module 8) for there to be anything to trigger.
|
||||
- **Module 13 — Testing in the AI Era.** CI is mostly "run the tests, automatically." You need tests
|
||||
- **Module 13: Testing in the AI Era.** CI is mostly "run the tests, automatically." You need tests
|
||||
to run. If you skipped writing them, this module's lab ships a small suite so you're not blocked,
|
||||
but the real payoff is automating *your* tests.
|
||||
- **Module 2 — Version Control.** Pushes, commits, and the diff habit are the substrate CI sits on.
|
||||
- **Module 2: Version Control.** Pushes, commits, and the diff habit are the substrate CI sits on.
|
||||
|
||||
You do **not** need Docker, secrets management, or your own runner yet — those are Modules 16, 17,
|
||||
You do **not** need Docker, secrets management, or your own runner yet; those are Modules 16, 17,
|
||||
and 19. On a **SaaS forge** (GitHub, GitLab.com, Bitbucket, and the rest) this module uses the
|
||||
forge's hosted runners, which require zero setup. **One honesty note for the self-host track:** a
|
||||
self-hosted Forgejo/Gitea/GitLab CE has the CI *feature* but no hosted compute — nothing actually
|
||||
self-hosted Forgejo/Gitea/GitLab CE has the CI *feature* but no hosted compute; nothing actually
|
||||
runs until you attach a runner, and that's Module 19. The workflow you write here is correct either
|
||||
way and will run the moment a runner is registered; to watch it go green *now*, use a SaaS forge's
|
||||
hosted runners, then come back and own the compute end-to-end in Module 19.
|
||||
@@ -30,7 +30,7 @@ hosted runners, then come back and own the compute end-to-end in Module 19.
|
||||
|
||||
By the end of this module you can:
|
||||
|
||||
1. Explain what CI actually is — automated checks bound to a trigger — and why "on every push" is the
|
||||
1. Explain what CI actually is, automated checks bound to a trigger, and why "on every push" is the
|
||||
part that makes it valuable.
|
||||
2. Write a forge-native CI workflow that checks out your code, installs its tools, and runs a linter
|
||||
and your test suite.
|
||||
@@ -73,9 +73,9 @@ Three properties make CI more than a glorified shell script:
|
||||
Almost every CI configuration, on every forge, is the same four moves:
|
||||
|
||||
1. **Check out the code** onto the runner. The runner starts empty; first you put your repo on it.
|
||||
2. **Set up the environment** — install the language runtime, pin its version.
|
||||
3. **Install the tools** the checks need — the test runner, the linter.
|
||||
4. **Run the checks** — lint, then test. Any check that exits non-zero fails the whole run.
|
||||
2. **Set up the environment**: install the language runtime, pin its version.
|
||||
3. **Install the tools** the checks need: the test runner, the linter.
|
||||
4. **Run the checks**: lint, then test. Any check that exits non-zero fails the whole run.
|
||||
|
||||
That last point is the load-bearing one. CI's entire enforcement mechanism is the **exit code**.
|
||||
Every tool you'd run in a terminal returns 0 for success and non-zero for failure. `python -m
|
||||
@@ -88,13 +88,13 @@ testing system; you're wiring the tools you already have to a trigger.
|
||||
Three tiers of check, cheapest first, because a fast check that fails early saves you waiting on a
|
||||
slow one:
|
||||
|
||||
- **Lint** — static checks that don't run your code: style, unused imports, obvious mistakes. Fast,
|
||||
- **Lint.** Static checks that don't run your code: style, unused imports, obvious mistakes. Fast,
|
||||
cheap, catches a surprising amount. We use a linter as the example here; the principle is
|
||||
tool-agnostic.
|
||||
- **Build** — does the code even assemble? For an interpreted language like our Python example
|
||||
- **Build.** Does the code even assemble? For an interpreted language like our Python example
|
||||
there's no compile step, so "build" often collapses into "does it import without erroring." For
|
||||
compiled languages this is where a broken type or missing symbol gets caught.
|
||||
- **Test** — the Module 13 suite. The expensive, high-value tier: it actually runs your code and
|
||||
- **Test.** The Module 13 suite. The expensive, high-value tier: it actually runs your code and
|
||||
checks behavior.
|
||||
|
||||
Order them cheap-to-expensive so the fast checks fail fast. There's no reason to spend two minutes
|
||||
@@ -102,8 +102,8 @@ running the test suite if the linter would have rejected the push in three secon
|
||||
|
||||
### The worked example: a forge-native workflow
|
||||
|
||||
Here's a complete, real CI pipeline for the `tasks-app`. This is GitHub Actions YAML — the most
|
||||
common dialect, and our default example — but **read it as a concept, not a product.** Every forge
|
||||
Here's a complete, real CI pipeline for the `tasks-app`. This is GitHub Actions YAML, the most
|
||||
common dialect and our default example, but **read it as a concept, not a product.** Every forge
|
||||
has the exact same pipeline in its own dialect; the GitLab version is in the lab folder, and it's
|
||||
the same five moves.
|
||||
|
||||
@@ -133,7 +133,7 @@ jobs:
|
||||
```
|
||||
|
||||
Reading it top to bottom: `on:` is the trigger (push and pull request). `runs-on:` picks the clean
|
||||
machine. The `steps:` are the four moves — checkout, set up Python, install the tools, then the two
|
||||
machine. The `steps:` are the four moves: checkout, set up Python, install the tools, then the two
|
||||
checks. `uses:` pulls in a pre-built action (someone else's reusable step); `run:` is just a shell
|
||||
command. The linter runs first because it's cheap; the tests run last because they're the
|
||||
expensive, decisive check. Only the linter needs a `pip install` here; the tests run on Python's
|
||||
@@ -151,7 +151,7 @@ When CI goes red, the skill is triage, and it's fast once you know the shape:
|
||||
1. **Open the run.** The forge shows the job as a list of steps with a red X on the one that failed.
|
||||
2. **The first red step is the cause.** Steps run in order and stop at the first failure; everything
|
||||
after it is skipped, not broken. Don't get distracted by the skipped steps.
|
||||
3. **Read that step's log.** It's the same output the tool prints in your terminal — a failing
|
||||
3. **Read that step's log.** It's the same output the tool prints in your terminal: a failing
|
||||
`unittest` assertion, a `ruff` finding with a file and line number. CI didn't invent a new error
|
||||
format; it's showing you the command's own output.
|
||||
4. **Reproduce it locally.** The same command from the failed step (`python -m unittest` or
|
||||
@@ -213,12 +213,12 @@ break it on purpose and watch CI catch it.
|
||||
|
||||
- The `tasks-app` from Modules 1–2, **pushed to a forge** (Module 8). Any forge works.
|
||||
- The starter files in this module's `lab/`:
|
||||
- `ci-starter.yml` — the workflow (GitHub Actions flavor).
|
||||
- `gitlab-ci-starter.yml` — the same pipeline for GitLab, if that's your forge.
|
||||
- `test_tasks.py` — a small test suite (use your Module 13 tests instead if you have them).
|
||||
- `ci-starter.yml`: the workflow (GitHub Actions flavor).
|
||||
- `gitlab-ci-starter.yml`: the same pipeline for GitLab, if that's your forge.
|
||||
- `test_tasks.py`: a small test suite (use your Module 13 tests instead if you have them).
|
||||
- Python 3.10+ locally, and your agent. Examples use **Claude Code**; sub your own agent anywhere.
|
||||
|
||||
### Part A — Run the checks locally first
|
||||
### Part A: Run the checks locally first
|
||||
|
||||
Never push a workflow you haven't run by hand. CI just runs the same commands, so prove they work on
|
||||
your machine first.
|
||||
@@ -249,7 +249,7 @@ your machine first.
|
||||
If both are clean locally, CI will be green. If not, fix it here; it's faster than waiting on a
|
||||
runner. (Only the linter needs installing. The stdlib `unittest` runner ships with Python.)
|
||||
|
||||
### Part B — Add the workflow and watch it pass
|
||||
### Part B: Add the workflow and watch it pass
|
||||
|
||||
2. Direct the agent to put the workflow where your forge looks for it. Tell Claude Code which forge
|
||||
you're on and let it pick the path:
|
||||
@@ -277,7 +277,7 @@ your machine first.
|
||||
prerequisites; the workflow is correct, it just has no compute until you attach a runner in
|
||||
Module 19. Run this part on a SaaS forge to see green right now.)
|
||||
|
||||
### Part C — Break it on purpose and watch CI catch it
|
||||
### Part C: Break it on purpose and watch CI catch it
|
||||
|
||||
This is the whole point. You're going to ship the kind of plausible-but-wrong change AI produces,
|
||||
and watch CI stop it.
|
||||
@@ -336,7 +336,7 @@ the reviewer that caught a change you might have trusted.
|
||||
The honest caveats, because a skeptical audience trusts the limits more than the pitch:
|
||||
|
||||
- **CI only catches what your checks check.** A green run means "the linter found nothing and the
|
||||
tests passed" — not "the code is correct." If the AI broke behavior you have no test for, CI is
|
||||
tests passed," not "the code is correct." If the AI broke behavior you have no test for, CI is
|
||||
cheerfully green while the bug ships. CI is exactly as good as your test suite (Module 13), and no
|
||||
better. The flipped-comparison bug above got caught *because a test covered it.*
|
||||
- **Green CI is not "reviewed."** It checks behavior, not design, intent, security, or whether the
|
||||
@@ -344,7 +344,7 @@ The honest caveats, because a skeptical audience trusts the limits more than the
|
||||
in Module 15; it sits alongside them. Treating a green check as sign-off is how plausible-wrong
|
||||
code with no failing test sails straight through.
|
||||
- **The clean machine is a feature that feels like a bug.** Sooner or later CI fails in a way you
|
||||
can't reproduce locally — a dependency you have installed but never declared, a file outside the
|
||||
can't reproduce locally: a dependency you have installed but never declared, a file outside the
|
||||
repo your code quietly reads, a path that only exists on your machine. That's not flakiness; it's
|
||||
CI correctly catching that your code depends on something that isn't in the repo. Fix the
|
||||
dependency, don't blame the runner. (Module 16's containers make local and CI environments
|
||||
@@ -368,15 +368,15 @@ The honest caveats, because a skeptical audience trusts the limits more than the
|
||||
|
||||
- Your `tasks-app` has a committed CI workflow that runs a linter and your tests on every push, and
|
||||
you've watched it go green on the forge.
|
||||
- You pushed a plausible-but-wrong change and watched CI catch it — found the failed step, read the
|
||||
- You pushed a plausible-but-wrong change and watched CI catch it: found the failed step, read the
|
||||
log, reproduced the failure locally, and fixed it.
|
||||
- You can explain, in your own words, why CI specifically matters for AI-generated code (it checks
|
||||
behavior, not appearance) and the one thing a green check does *not* tell you (that the code is
|
||||
correct — only that your checks passed).
|
||||
correct; only that your checks passed).
|
||||
- You can point at the same pipeline in two forge dialects and see it's the same five moves.
|
||||
|
||||
When pushing a change and *expecting* the gate to either bless it or stop it feels automatic — when
|
||||
you'd be uneasy merging code that hadn't been through CI — you've got it. Module 15 adds the next
|
||||
When pushing a change and *expecting* the gate to either bless it or stop it feels automatic, when
|
||||
you'd be uneasy merging code that hadn't been through CI, you've got it. Module 15 adds the next
|
||||
gates on the same pushes: scanning for vulnerable dependencies, leaked secrets, and the packages AI
|
||||
hallucinates into existence.
|
||||
|
||||
@@ -392,10 +392,10 @@ Re-check at build time:
|
||||
- [ ] **Runner labels.** Confirm `ubuntu-latest` (and any GitLab `image:` tag) still resolves to a
|
||||
supported image; default runner OS versions roll forward.
|
||||
- [ ] **Trigger and config syntax.** Verify the `on:` keys and overall workflow schema against the
|
||||
forge's current docs — Actions YAML keys do change.
|
||||
forge's current docs; Actions YAML keys do change.
|
||||
- [ ] **Forge UI labels.** The tab names in the lab ("Actions," "CI/CD," "Pipelines") and the
|
||||
workflow file locations (`.github/workflows/`, `.gitlab-ci.yml`, `.forgejo/`, `.gitea/`) match
|
||||
what the current forge versions actually use.
|
||||
- [ ] **Tool names.** The example linter (`ruff`) is current, installable, and still behaves as
|
||||
described — or swap in the equivalent the rest of the course uses. (The test runner is Python's
|
||||
standard-library `unittest`, which ships with Python — no install, nothing to drift.)
|
||||
described, or swap in the equivalent the rest of the course uses. (The test runner is Python's
|
||||
standard-library `unittest`, which ships with Python; no install, nothing to drift.)
|
||||
|
||||
Reference in New Issue
Block a user