Build a Workstation Readiness Doctor
Build a Go doctor for cross-platform cloud engineering workstation checks.
Introduction
30 Second Summary
A new computer can look ready until the first real task exposes a missing tool or a stalled command. A checklist says what should be installed without proving what actually works.
In this project, you will build a cross-platform Go workstation doctor that checks ten development capabilities. You will publish its positive and negative evidence as a complete engineering handover on GitHub.
What You'll Build
Picture running one command from a clean clone, watching ten named checks report PASS, then showing a hiring manager the public evidence that failures produce a trustworthy non-zero status.
By the end of this project, you'll have:
- A cross-platform workstation doctor that you can run on Windows, macOS, or Linux to check ten development capabilities.
- Readable PASS and FAIL evidence that names each check. Every result shows the parsed value plus a reason. The process returns an exit status that automation can trust.
- A public engineering handover that a hiring manager can reproduce from a clean clone. They can inspect your design choices. They can also follow your issue through a merged pull request to release v0.1.0.
- Secret Mission: Change the expected Node.js major on purpose. Prove the doctor catches the drift. Restore ten passing checks.
Are there any prerequisites?
The personal-learning path costs $0 with no payment card required.
You need a Windows, macOS, or Linux computer with administrator rights, internet access, more than 10 GB of free disk space, a free GitHub account, an existing editor, and an existing AI client.
Before We Start
Your first commitment is to ship a public workstation-readiness tool with evidence from one computer. Naming the operating system, editor, and AI client now keeps every later claim grounded in what you actually tested.
Complete Day Zero and Verify Your Toolchain
Large runtimes can consume your timed hour through downloads or restarts. Day Zero removes those blockers before your engineering work begins.
You will prepare a running container engine through Docker or Podman. You will also prepare Ollama, Rust, Git, Go, Node.js, Task, Kubernetes tools, and GitHub access.
In this step, get ready to:
- Complete Day Zero with verified installers and recorded download sizes.
- Install or confirm every timed command-line tool at its required version.
- Configure Git identity and authenticate GitHub CLI with Projects access.
Complete Day Zero safely
Day Zero happens before you start the 60-minute stopwatch. It prepares the large downloads and native prerequisites that could otherwise hide your engineering progress.
Keep Day Zero Verifiable
This project costs $0 for personal learning use. You do not need a payment card.
Installers can make system-level changes. Verify their origin before you run them so your setup evidence covers both readiness and supply-chain care.
- Record the documented or displayed download size beside each Day Zero item in your notes.
- Download installers from the linked vendor sources.
- Verify each bare archive against the checksum published beside it.
- Save any remote installation script before execution so you can read it first.
- Record any managed-device blocker with its expected clearance time for your later README.
First, check the Day Zero tools from a fresh terminal. A missing command is an expected outcome at this point.
Docker path
- Check Docker, Ollama, and Cargo by running these commands:
docker version
ollama --version
cargo --version
What Do These Checks Prove?
- The Docker output must include server information from a reachable engine.
- The Ollama output identifies the installed CLI version.
- The Cargo output confirms that the Rust toolchain is available on PATH.
Podman path
- Check Podman, Ollama, and Cargo by running these commands:
podman version
ollama --version
cargo --version
What Do These Checks Prove?
- The Podman output must identify a reachable engine.
- Desktop hosts need remote server data from Podman.
- Native Linux Podman can report its local engine directly.
- The remaining lines confirm that Ollama and Cargo are available on PATH.
✔️ I see every Day Zero version
Your existing Day Zero tools are ready. Confirm Ollama reports 0.34.1 and Cargo reports 1.98.1.
- Keep your selected engine running for the remaining checks.
- Record the installed engine choice in your notes.
ⓧ I see an older version
Update each older Day Zero tool to the pinned release for this project.
- On Windows, update Docker Desktop to 4.91.0 from the official Docker installation guide.
- On Windows, update Podman Desktop to 1.29.3 from the official Podman Desktop download page if that is your selected engine.
- On macOS, update your selected desktop engine from its official download page.
- On Linux, update Docker Engine or Podman through its signed package repository.
- Update Ollama to 0.34.1 from the official Ollama releases page.
- Update the Rust stable toolchain by running the documented command below:
rustup update stable
What Does This Command Do?
Rustup updates the stable toolchain to Rust 1.98.1. Cargo updates with the same toolchain.
🙋♀️ Update Blocked?
A managed device may require administrator approval for system software. Record the blocked tool and the expected clearance time instead of hiding the gap.
Ask for help with the exact output you received: Help me diagnose why my Day Zero tool update failed.
ⓧ One or more commands are missing
Install only the missing Day Zero components for your operating system.
- On Windows, install WSL first when it is absent by running this command:
wsl --install
What Does This Command Do?
Windows installs WSL and may request a restart. Complete that restart before installing your container engine.
- On Windows, install Docker Desktop 4.91.0 from the official Docker installation guide or Podman Desktop 1.29.3 from the official Podman Desktop download page.
- On Windows, require Authenticode status Valid for each downloaded installer before running it.
- On macOS, install Docker Desktop 4.91.0 or Podman Desktop 1.29.3 from its official download page.
- On macOS, verify installer packages with Apple's package signature check before running them.
- On macOS, verify applications or disk images with Apple's documented code-signing assessment.
- On Linux, install Docker Engine or Podman from a signed package repository.
- Install Ollama 0.34.1 from the official Ollama releases page.
- Install Rust stable through rustup from the official Rust installation page.
- On macOS, install the native compiler tools if Rust reports a missing linker by running this command:
xcode-select --install
Why Might Rust Need This?
Rust uses a native linker when it builds programs on macOS. Xcode Command Line Tools provides that linker.
- On Linux, add your account to the Docker group if Docker Engine requires non-root access by running this command:
sudo usermod -aG docker $USER
Understand the Docker Group
The Docker group grants root-level privileges. Log out after this change so your next login receives the new group membership.
🙋♀️ Installation Blocked?
Check that you selected the installer for your operating system and processor architecture. Reopen the terminal after installation so it receives the updated PATH.
Ask for help with the exact failing tool: Help me install my missing Day Zero tool safely.
The local model is a separate 1.4GB download. This can take a while on a slower connection, so keeping it outside the stopwatch protects the timed build.
- Start your selected container engine from its desktop application or system service.
- Wait until the engine reports that it is running.
- Download the required Ollama model by running this command:
ollama pull qwen3:1.7b
What Does This Command Do?
Ollama downloads the qwen3:1.7b model to local storage. Later projects can use this model without downloading it again.
🙋♀️ Model Download Stopped?
Confirm Ollama is running before retrying the download. Check that your machine still has enough free disk space.
Ask for help with the download output: Help me troubleshoot my Ollama model download.
Now verify the completed Day Zero state with the engine you selected.
Docker engine
- Verify Docker, Ollama, and Cargo by running these commands:
docker version
ollama --version
cargo --version
What Should You See?
Docker prints client and server details from the running engine. Ollama reports 0.34.1.
Cargo reports 1.98.1. These results close the Day Zero readiness gap.
Podman engine
- Verify Podman, Ollama, and Cargo by running these commands:
podman version
ollama --version
cargo --version
What Should You See?
Podman prints reachable engine information. Ollama reports 0.34.1.
Cargo reports 1.98.1. These results close the Day Zero readiness gap.
Verify the timed toolchain
The stopwatch begins only after Day Zero passes. The first ten minutes use a check-first sequence so you install only what the workstation lacks.
- Start your 60-minute stopwatch.
- Check Git, Go, Node.js, Task, kind, kubectl, and Helm in the required order by running these commands:
git --version
go version
node --version
task --version
kind version
kubectl version --client
helm version
What Do These Checks Establish?
Each command tests whether the matching executable is available through PATH. The output also exposes the installed version before you change anything.
The required pins are Git 2.55.0, Go 1.27.1, Node.js 24.21.0, Task 3.53.1, kind 0.33.0, kubectl 1.37.0, and Helm 4.3.0.
✔️ I see every required version
Your seven timed tools already match the required pins. Keep the current terminal open until you finish checking GitHub CLI.
ⓧ I see one or more older versions
Update only the tools whose output differs from the required pins.
- On Windows, update Git from the official Git download page.
- On Windows, update Go from the official Go downloads page.
- On Windows, update Node.js from the official Node.js download page.
- On Windows, update the applicable remaining tools by running only their commands below:
winget install Task.Task
winget install Kubernetes.kind
winget install -e --id Kubernetes.kubectl
winget install Helm.Helm
winget install --id GitHub.cli --source winget
What Do These Commands Install?
WinGet installs the pinned command-line tools from their registered packages. The final line installs GitHub CLI 2.101.0.
- On macOS, update Git from the official Git download page.
- On macOS, update Go from the official Go downloads page.
- On macOS, update Node.js from the official Node.js download page.
- On macOS, update the applicable remaining tools by running only their commands below:
brew install go-task
brew install kind
brew install kubernetes-cli
brew install helm
brew install gh
What Do These Commands Install?
Homebrew installs Task, kind, kubectl, Helm, and GitHub CLI. Run only the lines for tools that need an update.
- On Linux, update each available tool through your distribution's signed package repository.
- On Linux, use the verified vendor binary when the signed repository does not carry the required pin.
- Reopen the whole terminal after the updates finish.
Still Seeing the Old Version?
A terminal opened before installation can retain the old PATH. Close the whole terminal application before checking again.
Ask for help locating the executable being used: Help me find why my terminal still reports an older tool version.
ⓧ One or more commands are missing
Install only the missing timed tools for your operating system.
- On Windows, install Git 2.55.0 from the official Git download page.
- On Windows, install Go 1.27.1 from the official Go downloads page.
- On Windows, install Node.js 24.21.0 LTS from the official Node.js download page.
- On Windows, install the applicable remaining tools by running only their commands below:
winget install Task.Task
winget install Kubernetes.kind
winget install -e --id Kubernetes.kubectl
winget install Helm.Helm
winget install --id GitHub.cli --source winget
What Do These Commands Install?
These WinGet packages provide Task, kind, kubectl, Helm, and GitHub CLI. Their version checks become available after the terminal receives the updated PATH.
- On macOS, install Git 2.55.0 from the official Git download page.
- On macOS, install Go 1.27.1 from the official Go downloads page.
- On macOS, install Node.js 24.21.0 LTS from the official Node.js download page.
- On macOS, install the applicable remaining tools by running only their commands below:
brew install go-task
brew install kind
brew install kubernetes-cli
brew install helm
brew install gh
What Do These Commands Install?
Homebrew installs Task, kind, kubectl, Helm, and GitHub CLI. The packages place their commands on the Homebrew PATH.
- On Linux, install each available tool through your distribution's signed package repository.
- On Linux, download a vendor binary when the repository does not carry the required pin.
- On Linux, verify every downloaded binary against its vendor checksum.
- Reopen the whole terminal after the installations finish.
Command Still Missing?
Confirm that the installer finished successfully. A newly installed command may remain unavailable until every old terminal window is closed.
Ask for help with the missing command: Help me troubleshoot a command that is still missing from PATH.
Configure identity and prove readiness
Version checks prove the programs are present. Git identity and authenticated GitHub CLI access prepare the workstation for the public delivery workflow later in the project.
- Replace the placeholder name and email in the commands below with the identity you want attached to your commits.
- Configure your global Git identity by running these commands:
git config --global user.name "YOUR NAME"
git config --global user.email "YOUR_EMAIL"
What Does This Configuration Control?
Git attaches this name and email to new commits. The global setting applies the identity across repositories on this computer.
🙋♀️ Git Configuration Rejected?
Keep the quotation marks around names that contain spaces. Use an email address associated with your GitHub account if you want commits attributed to your profile.
Ask for help without sharing private credentials: Help me fix my global Git identity configuration.
GitHub CLI opens a browser-based authorization flow by default. You approve access in the browser before returning to the terminal.
- Start the GitHub CLI browser authorization flow by running this command:
gh auth login
What Happens During Login?
GitHub CLI guides you to a browser for authorization. The terminal continues after your account approves the request.
- Authorize the browser request with your existing GitHub account.
- Add the minimum Projects scope by running this command:
gh auth refresh -s project
Why Add the Project Scope?
The project scope lets GitHub CLI create and update the public Project required later. It keeps the permission aligned with the delivery task.
🙋♀️ Authentication Did Not Complete?
Return to the terminal after approving the browser request. Repeat the login flow if the authorization window expired.
Ask for help without sharing a token: Help me troubleshoot GitHub CLI authentication.
- Create a folder named machine-ready-doctor on your Desktop with your operating system's file manager.
- Open the machine-ready-doctor folder through your selected editor's folder-opening control.
Before you run the final check, decide whether you expect every command to report the pinned version or authenticated state.
Docker engine
- Run the complete Docker-path verification from the new terminal:
docker version
ollama --version
cargo --version
git --version
go version
node --version
task --version
kind version
kubectl version --client
helm version
gh auth status
What Should You See?
Each tool prints its version or authenticated account. Docker includes reachable server information.
GitHub CLI reports an authenticated account. Your workstation now satisfies the complete setup contract.
Podman engine
- Run the complete Podman-path verification from the new terminal:
podman version
ollama --version
cargo --version
git --version
go version
node --version
task --version
kind version
kubectl version --client
helm version
gh auth status
What Should You See?
Each tool prints its version or authenticated account. Podman reports reachable engine information.
GitHub CLI reports an authenticated account. Your workstation now satisfies the complete setup contract.
That is the largest setup hurdle cleared. Your engine is reachable and every required command is ready for the doctor you will build.
✔️ Awesome, I've got everything!
Confirm that the machine-ready-doctor folder is open in your editor. Keep your verification output available for reference.
ⓧ I'd like to double check the full setup
Your selected operating system is recorded. Git, Go, Node.js, Rust with Cargo, Task, kind, kubectl, Helm, Ollama with qwen3:1.7b, GitHub CLI, and Docker or Podman are available on PATH.
Your selected container engine is running and reachable. GitHub CLI is authenticated with Projects access.
Your workstation is ready. Next, you will define the design and ship the doctor's first visible check.
Design the Doctor and Add the First Check
Your workstation toolchain is ready. You can now turn that setup into a small engineering handover with a clear contract.
This step defines acceptance before implementation through requirements and reversible decisions. You will still earn an early visible win by running one Go check from a new Git repository.
In this step, get ready to:
- Write the acceptance requirements and exactly four decision records.
- Create the repository scaffold and commit the design baseline.
- Run one visible Go check from the working branch.
Define the acceptance contract
Acceptance criteria describe the evidence that makes the doctor trustworthy. The decision records preserve why each design choice exists and when it should change.
- Create a docs folder inside the project folder already open in your editor.
- Create docs/requirements.md by pasting this acceptance contract:
# Requirements
1. The timed build is 60 minutes, with setup capped at 10 minutes. Day Zero happens the day before but remains part of this project.
2. The project costs $0 for personal learning use and requires no payment card.
3. The guide names Windows, macOS and Linux and records which one was actually tested.
4. The implementation uses Go 1.27 and no additional code dependency.
5. `task doctor` runs ten checks in this order: Go, Node, Git LF, Cargo, Task, kind, kubectl, Helm, Ollama and the container engine.
6. Each external command has a five-second timeout.
7. Each result prints PASS or FAIL, the check name, literal command, parsed version or LF result, and a reason.
8. Version checks compare the installed major with the expected pin.
9. The engine tries `docker version`, then `podman version` only when Docker is absent or cannot reach its engine.
10. The Git check requires every tracked entry to report `i/lf` and rejects CRLF or mixed index content.
11. The doctor prints every result, exits zero only when all checks pass and exits non-zero otherwise.
12. Positive evidence contains ten PASS lines and exit code zero.
13. Negative evidence hides Helm from one terminal's PATH, names Helm as FAIL, returns non-zero and does not move or delete installed files.
14. The public repository contains the named source, policy, documentation and portfolio files, with no credentials or real client data.
15. Public delivery contains one closed issue, one merged pull request with `Closes #1`, a public linked four-column Project, and release `v0.1.0` with the recording attached.
16. The README records the tested operating system, actual minutes, two pasted validation runs, two reasoned operating-system notes, six named sections and a source map reviewed on 2026-09-15.
What Does This Contract Define?
- Criteria 1 through 4 define the project boundaries.
- Criteria 5 through 11 define the doctor's runtime behavior.
- Criteria 12 and 13 require evidence for both success and controlled failure.
- Criteria 14 through 16 define the public delivery standard.
- Save docs/requirements.md.
- Confirm the document shows 16 numbered acceptance criteria.
- Create docs/decisions.md by pasting the first decision record:
# Decisions
## DR-001: Use Go standard-library process execution
Decision: Use Go with `context` and `os/exec` for one cross-platform binary-shaped program.
Alternative: Maintain separate shell and PowerShell implementations.
Tradeoff: Go adds a compiler requirement but removes duplicated operating-system logic.
Reversal trigger: Reverse if the checks become shell-native configuration tasks that cannot be represented safely as direct processes.
Why Use Direct Process Execution?
The Go standard library can start each program directly. This keeps the doctor's process behavior consistent across Windows, macOS and Linux.
- Save docs/decisions.md.
- Confirm the document shows the DR-001 heading with four labeled fields.
- Append the public entry-point decision to docs/decisions.md by pasting this record below DR-001:
## DR-002: Use Task as the public entry point
Decision: Make `task doctor` the one command a stranger runs.
Alternative: Publish separate operating-system scripts or rely on editor tasks.
Tradeoff: Task is one more tool to install, but it gives the repository one readable command contract.
Reversal trigger: Reverse if the repository gains a standard build system that already provides an equally portable entry point.
Why Use One Entry Point?
Task gives every supported operating system the same public command. A stranger can validate the workstation without choosing an operating-system-specific wrapper.
- Save docs/decisions.md.
- Confirm the document now shows DR-001 followed by DR-002.
- Append the execution-order decision to docs/decisions.md by pasting this record below DR-002:
## DR-003: Run checks sequentially with five-second deadlines
Decision: Run all checks in a stable order and cancel each command after five seconds.
Alternative: Run checks concurrently for lower wall-clock time.
Tradeoff: Sequential checks take longer, but the output order is predictable and easier to explain on day one.
Reversal trigger: Reverse if measured runtime exceeds the budget and ordered result collection remains deterministic.
Why Keep the Checks Sequential?
A stable sequence makes terminal output easier to review. The reversal trigger keeps concurrency available if measured runtime later exceeds the budget.
- Save docs/decisions.md.
- Confirm the document now includes the five-second deadline decision.
- Append the engine-fallback decision to docs/decisions.md by pasting this record below DR-003:
## DR-004: Prefer Docker and fall back to Podman
Decision: Try `docker version` first and require reachable server data; try `podman version` only when Docker is absent or cannot reach its engine.
Alternative: Require one engine or treat a client-only version as ready.
Tradeoff: Fallback logic is more complex, but it supports the two named desktop choices without reporting a stopped engine as ready.
Reversal trigger: Reverse if one engine becomes mandatory for later projects or its documented version output no longer exposes reachable engine data.
Why Check the Engine Server?
A client command can exist while its engine is stopped. The decision requires reachable engine data before the workstation reports readiness.
- Save docs/decisions.md.
- Confirm the document contains exactly four headings from DR-001 through DR-004.
Missing a Decision Field?
Check that every record contains Decision, Alternative, Tradeoff and Reversal trigger on separate lines.
Compare the heading numbers if you see fewer or more than four records.
Help me check my four decision records.
Build the repository baseline
The repository scaffold turns the design into a reproducible starting point. Its README puts the architecture before the minute plan so reviewers see the system shape before the schedule.
- Create README.md in the open project folder by pasting the opening sections:
# Your Machine, Ready on Windows, macOS or Linux
This is day one of one hundred. It shows how I turn a workstation setup request into a small, testable engineering handover.
## 1. Quickstart
Cost before setup: $0 for personal learning use. No payment card is required.
## 2. Expected pins
## 3. Validation evidence
Actually tested operating system: REPLACE WITH WINDOWS, MACOS OR LINUX
Actual timed minutes: REPLACE WITH THE MEASURED NUMBER
What Do These Sections Reserve?
The opening establishes the project purpose and cost. The empty sections reserve space for the actual pins and observed evidence you will add later.
- Save README.md.
- Confirm the README shows the title followed by sections 1 through 3.
- Append the architecture section to README.md by pasting this content below the measured-minutes marker:
## 4. Architecture at a glance
```mermaid
flowchart LR
A[task doctor] --> B[Go doctor]
B --> C[Ten sequential checks]
C --> D[Five-second deadline]
D --> E[Parse version or LF state]
E --> F{All pass?}
F -->|Yes| G[Exit 0]
F -->|No| H[Print every result and exit 1]
```
How Does the Architecture Flow?
The diagram follows one request through sequential checks and bounded command execution. The final branch makes process exit status part of the public contract.
- Save README.md.
- Confirm the diagram source places the five-second deadline before parsing and exit selection.
- Append the minute plan and remaining section headings to README.md by pasting this content below the diagram:
Estimated 60-minute plan: setup 10, design and first check 7, table loop 6, hardening and validation 8, documentation and public delivery 13, secret mission 16.
## 5. Decision index and AI-work log
## 6. Limits, non-goals and source map
Why Record the Minute Plan?
The six estimates total 60 minutes. Recording the plan now gives the final README a baseline to compare with the learner's measured time.
- Save README.md.
- Confirm the architecture appears before the estimated minute plan.
- Create .editorconfig in the open project folder by pasting this editor policy:
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
[*.go]
indent_style = tab
indent_size = 4
What Does the Editor Policy Control?
The shared defaults use UTF-8 and LF line endings. Go files keep tab indentation while the remaining files use two spaces.
- Save .editorconfig.
- Confirm the file contains one shared section and one Go-specific section.
- Create .gitattributes in the open project folder by pasting this index policy:
* text=auto
*.go text eol=lf
*.md text eol=lf
*.yml text eol=lf
go.mod text eol=lf
What Does the Index Policy Enforce?
The policy normalizes the project's Go, Markdown, YAML and module files to LF in the Git index. That gives the later doctor a deterministic line-ending rule to inspect.
- Save .gitattributes.
- Confirm the file names all four tracked text-file patterns.
- Create .gitignore in the open project folder by pasting these exclusions:
.doctor/
doctor
doctor.exe
*.m4a
*.mp4
*.wav
*.webm
What Stays Out of Version Control?
The ignore rules exclude local doctor output and compiled binaries. They also keep the future talk-track recording out of repository history.
- Save .gitignore.
- Confirm the file includes four recording extensions.
- Create go.mod in the open project folder by pasting this module definition:
module machine-ready-doctor
go 1.27.1
What Does the Module File Establish?
The module name identifies this codebase as machine-ready-doctor. The language line records Go 1.27.1 as the project version.
- Save go.mod.
- Confirm the file contains the module name and one Go version line.
- Create Taskfile.yml in the open project folder by pasting this task definition:
version: '3'
tasks:
doctor:
desc: Check the workstation toolchain and exit non-zero on any failure
cmds:
- go run ./cmd/doctor
What Does the Task Define?
The doctor task gives the repository one public entry point. Its description records the final exit-status behavior before the implementation reaches that state.
- Save Taskfile.yml.
- Confirm the task contains one command beneath cmds.
- Create a portfolio folder inside the open project folder.
- Create portfolio/index.md by pasting this portfolio line:
- Your Machine, Ready on Windows, macOS or Linux: a public Go workstation doctor with cross-platform validation, design records and delivery evidence.
What Does the Portfolio Line Capture?
The sentence presents the doctor as a public engineering artifact. It highlights validation and design evidence instead of listing setup work.
- Save portfolio/index.md.
- Confirm the file contains one bullet describing the public doctor.
- Create a blank cmd/doctor/main.go path with your editor's new-file control.
- Confirm main.go appears under cmd/doctor in the editor's file tree.
The scaffold now captures the contract before implementation. Commit it on main so the first working program remains a separate growth commit.
- Initialize the repository and create the working branch by running these commands in the integrated terminal for the open project folder:
git init -b main
git add .
git commit -m "docs: add design baseline"
git switch -c machine-ready-doctor
git status -s
What Do These Commands Establish?
- The first command initializes the repository with main as its initial branch.
- The next two commands stage the scaffold and save the baseline commit.
- The branch command creates machine-ready-doctor for implementation work.
- The final command should print no changed files because the new branch starts clean.
🙋♀️ Baseline Commit Blocked?
Check that the integrated terminal is using the project folder already open in your editor.
A Git identity error means the terminal has not picked up the global name or email from setup. Reopen the terminal before retrying the commit.
Help me diagnose my baseline Git commit.
Run the first Go check
The first runnable slice checks only go version. Its limited output gives you a visible result while exposing the remaining gap to the ten-check contract.
- Ask your existing AI client to explain how exec.Command, CombinedOutput(), FindStringSubmatch() and TrimPrefix() contribute to this program.
- Type this program into cmd/doctor/main.go yourself after reviewing that explanation:
package main
import (
"fmt"
"os/exec"
"regexp"
"strings"
)
var semverPattern = regexp.MustCompile(`v?(\d+)\.(\d+)(?:\.(\d+))?`)
func main() {
output, err := exec.Command("go", "version").CombinedOutput()
if err != nil {
fmt.Printf("FAIL | Go | go version | version=unknown | %s\n", strings.TrimSpace(string(output)))
return
}
match := semverPattern.FindStringSubmatch(string(output))
if match == nil {
fmt.Println("FAIL | Go | go version | version=unknown | no semantic version found in command output")
return
}
fmt.Printf("PASS | Go | go version | version=%s | version parsed\n", strings.TrimPrefix(match[0], "v"))
}
What Does This Program Do?
- The import group supplies formatted output, direct process execution, regular-expression matching and string cleanup.
- The semverPattern variable finds a semantic version in the command output.
- The first failure path reports a command execution problem.
- The second failure path reports output that contains no semantic version.
- The success path prints the check name, literal command, parsed version and reason.
- Save cmd/doctor/main.go.
Before you run the doctor, how many result lines do you expect from a program that invokes only one command?
- Run the first doctor check with this command:
go run ./cmd/doctor
What Should You See?
You should see one line beginning with PASS | Go | go version. The line includes the parsed Go version and ends with version parsed.
That single result is the intended shortfall. Nine required capabilities remain absent from the output, which makes the next implementation goal visible.
🙋♀️ First Check Failing?
A compile error usually points to a missing quote, parenthesis or import. Compare the reported line with the program above.
A failed go version invocation means the integrated terminal cannot currently find Go on PATH. Reopen that terminal after confirming the setup terminal still finds Go.
Help me debug my first Go doctor check.
- Save the first working implementation as its own commit by running these commands:
git add .
git commit -m "feat: add one hard-coded doctor check"
git log --oneline
What Does the History Prove?
The newest history line should contain feat: add one hard-coded doctor check. The design-baseline commit should appear directly below it.
This split preserves the sequence from contract to runnable implementation.
✔️ Awesome, I've got everything!
Your design baseline and first growth commit are ready. Confirm the terminal still shows the one named Go PASS result.
ⓧ I'd like to double check the full code
Compare each project file with these exact end-of-step versions.
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
[*.go]
indent_style = tab
indent_size = 4
* text=auto
*.go text eol=lf
*.md text eol=lf
*.yml text eol=lf
go.mod text eol=lf
.doctor/
doctor
doctor.exe
*.m4a
*.mp4
*.wav
*.webm
# Your Machine, Ready on Windows, macOS or Linux
This is day one of one hundred. It shows how I turn a workstation setup request into a small, testable engineering handover.
## 1. Quickstart
Cost before setup: $0 for personal learning use. No payment card is required.
## 2. Expected pins
## 3. Validation evidence
Actually tested operating system: REPLACE WITH WINDOWS, MACOS OR LINUX
Actual timed minutes: REPLACE WITH THE MEASURED NUMBER
## 4. Architecture at a glance
```mermaid
flowchart LR
A[task doctor] --> B[Go doctor]
B --> C[Ten sequential checks]
C --> D[Five-second deadline]
D --> E[Parse version or LF state]
E --> F{All pass?}
F -->|Yes| G[Exit 0]
F -->|No| H[Print every result and exit 1]
```
Estimated 60-minute plan: setup 10, design and first check 7, table loop 6, hardening and validation 8, documentation and public delivery 13, secret mission 16.
## 5. Decision index and AI-work log
## 6. Limits, non-goals and source map
version: '3'
tasks:
doctor:
desc: Check the workstation toolchain and exit non-zero on any failure
cmds:
- go run ./cmd/doctor
package main
import (
"fmt"
"os/exec"
"regexp"
"strings"
)
var semverPattern = regexp.MustCompile(`v?(\d+)\.(\d+)(?:\.(\d+))?`)
func main() {
output, err := exec.Command("go", "version").CombinedOutput()
if err != nil {
fmt.Printf("FAIL | Go | go version | version=unknown | %s\n", strings.TrimSpace(string(output)))
return
}
match := semverPattern.FindStringSubmatch(string(output))
if match == nil {
fmt.Println("FAIL | Go | go version | version=unknown | no semantic version found in command output")
return
}
fmt.Printf("PASS | Go | go version | version=%s | version parsed\n", strings.TrimPrefix(match[0], "v"))
}
# Decisions
## DR-001: Use Go standard-library process execution
Decision: Use Go with `context` and `os/exec` for one cross-platform binary-shaped program.
Alternative: Maintain separate shell and PowerShell implementations.
Tradeoff: Go adds a compiler requirement but removes duplicated operating-system logic.
Reversal trigger: Reverse if the checks become shell-native configuration tasks that cannot be represented safely as direct processes.
## DR-002: Use Task as the public entry point
Decision: Make `task doctor` the one command a stranger runs.
Alternative: Publish separate operating-system scripts or rely on editor tasks.
Tradeoff: Task is one more tool to install, but it gives the repository one readable command contract.
Reversal trigger: Reverse if the repository gains a standard build system that already provides an equally portable entry point.
## DR-003: Run checks sequentially with five-second deadlines
Decision: Run all checks in a stable order and cancel each command after five seconds.
Alternative: Run checks concurrently for lower wall-clock time.
Tradeoff: Sequential checks take longer, but the output order is predictable and easier to explain on day one.
Reversal trigger: Reverse if measured runtime exceeds the budget and ordered result collection remains deterministic.
## DR-004: Prefer Docker and fall back to Podman
Decision: Try `docker version` first and require reachable server data; try `podman version` only when Docker is absent or cannot reach its engine.
Alternative: Require one engine or treat a client-only version as ready.
Tradeoff: Fallback logic is more complex, but it supports the two named desktop choices without reporting a stopped engine as ready.
Reversal trigger: Reverse if one engine becomes mandatory for later projects or its documented version output no longer exposes reachable engine data.
# Requirements
1. The timed build is 60 minutes, with setup capped at 10 minutes. Day Zero happens the day before but remains part of this project.
2. The project costs $0 for personal learning use and requires no payment card.
3. The guide names Windows, macOS and Linux and records which one was actually tested.
4. The implementation uses Go 1.27 and no additional code dependency.
5. `task doctor` runs ten checks in this order: Go, Node, Git LF, Cargo, Task, kind, kubectl, Helm, Ollama and the container engine.
6. Each external command has a five-second timeout.
7. Each result prints PASS or FAIL, the check name, literal command, parsed version or LF result, and a reason.
8. Version checks compare the installed major with the expected pin.
9. The engine tries `docker version`, then `podman version` only when Docker is absent or cannot reach its engine.
10. The Git check requires every tracked entry to report `i/lf` and rejects CRLF or mixed index content.
11. The doctor prints every result, exits zero only when all checks pass and exits non-zero otherwise.
12. Positive evidence contains ten PASS lines and exit code zero.
13. Negative evidence hides Helm from one terminal's PATH, names Helm as FAIL, returns non-zero and does not move or delete installed files.
14. The public repository contains the named source, policy, documentation and portfolio files, with no credentials or real client data.
15. Public delivery contains one closed issue, one merged pull request with `Closes #1`, a public linked four-column Project, and release `v0.1.0` with the recording attached.
16. The README records the tested operating system, actual minutes, two pasted validation runs, two reasoned operating-system notes, six named sections and a source map reviewed on 2026-09-15.
module machine-ready-doctor
go 1.27.1
- Your Machine, Ready on Windows, macOS or Linux: a public Go workstation doctor with cross-platform validation, design records and delivery evidence.
You have a design-first repository with one runnable check and a visible implementation gap. Next, you will replace the hard-coded call with a table that runs all ten workstation checks.
Build the Ten-Check Table
Your first Go check proves the doctor can run a command and print a visible result. The ten-check contract now needs the same clarity across the whole workstation.
Ten copied command blocks would make this tool difficult to review. A data-driven table keeps each capability in one specification while one loop handles the repeated work.
The final test asks the intermediate doctor to report a missing capability. You will then inspect what that printed failure means to another process.
In this step, get ready to:
- Model command checks with shared specifications and results.
- Run all ten workstation checks through one sequential loop.
- Force a named Helm failure and inspect the process status.
Define the check model and version helpers
A table-driven design stores each command as data. The shared result shape gives every check the same fields for its name, command, version, reason, and status.
- Switch back to cmd/doctor/main.go in the editor from earlier.
- Place these two types below semverPattern and above main():
type check struct {
name string
program string
args []string
expectedPin string
expectedMajor int
}
type result struct {
name string
command string
version string
reason string
passed bool
}
What do these types represent?
- The check type holds everything needed to run one capability check.
- The result type holds the stable fields printed after that check finishes.
- The table can now describe new checks without duplicating the execution shape.
- Save cmd/doctor/main.go.
- Confirm the new data model still compiles by running:
go run ./cmd/doctor
What did this compile check prove?
You should still see the single Go PASS line. That output proves the new types compile alongside the original check.
- Add the strconv import below regexp:
"strconv"
Why add strconv?
The parser receives version digits as text. strconv.Atoi converts the major-version group into an integer for comparison.
- Add these version helpers below the existing main() function:
func parseVersion(output string) (string, int, bool) {
match := semverPattern.FindStringSubmatch(output)
if match == nil {
return "", 0, false
}
major, err := strconv.Atoi(match[1])
if err != nil {
return "", 0, false
}
return strings.TrimPrefix(match[0], "v"), major, true
}
func majorFromVersion(version string) (int, error) {
match := semverPattern.FindStringSubmatch(version)
if match == nil {
return 0, fmt.Errorf("version not found")
}
return strconv.Atoi(match[1])
}
How does version parsing work?
- The shared regular expression finds a semantic version inside command output.
- The parser removes an optional leading v before returning the visible version.
- The numeric major becomes the value used by each version gate.
- Save cmd/doctor/main.go.
- Check the parser additions by running:
go run ./cmd/doctor
What should you see?
You should see the original Go PASS result again. The parser helpers now compile without changing the visible behavior of the first check.
🙋♀️ Seeing a compile error?
Check that strconv sits inside the existing import block. Confirm that both helper functions sit outside main().
Use the first reported line number to find a missing brace or copied character.
Help me fix the parser compile error.
Run every capability from one sequence
The execution helpers separate command handling from output formatting. This keeps main() focused on ordering the checks and printing their results.
- Add versionAfterMarker below majorFromVersion:
func versionAfterMarker(output, marker string) string {
index := strings.Index(output, marker)
if index == -1 {
return ""
}
for _, line := range strings.Split(output[index+len(marker):], "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "Version:") {
version, _, ok := parseVersion(trimmed)
if ok {
return version
}
}
}
return ""
}
Why search after a marker?
Container output can contain separate client and server sections. This helper starts at a named section before looking for its version line.
- Add these output helpers directly below versionAfterMarker:
func commandError(err error, output string) string {
message := strings.TrimSpace(output)
if message == "" {
if err == nil {
return "command failed without output"
}
message = err.Error()
}
return message
}
func clean(value string) string {
value = strings.TrimSpace(value)
value = strings.ReplaceAll(value, "\r", " ")
value = strings.ReplaceAll(value, "\n", "; ")
return value
}
How is output kept readable?
- The error helper preserves command output when a failed program explains the problem.
- The cleaning helper collapses multiline output into one stable result line.
- Save cmd/doctor/main.go.
- Verify the output helpers compile by running:
go run ./cmd/doctor
What did this check confirm?
The single Go PASS line should still print. The output helpers are now available for every result the table creates.
- Insert validateEngine immediately above parseVersion:
func validateEngine(name, command, version, pin string, expectedMajor int) result {
major, err := majorFromVersion(version)
if err != nil {
return result{name: "Engine", command: command, version: version, reason: name + " engine version could not be parsed", passed: false}
}
if major != expectedMajor {
return result{name: "Engine", command: command, version: version, reason: fmt.Sprintf("%s engine major %d does not match expected pin %s", name, major, pin), passed: false}
}
return result{name: "Engine", command: command, version: version, reason: fmt.Sprintf("%s engine answered and major %d matches expected pin %s", name, major, pin), passed: true}
}
What does engine validation prove?
The helper checks the reachable engine version against its expected major. Its result also records which engine answered.
- Insert runVersionCheck immediately below the existing main() function:
func runVersionCheck(item check) result {
output, err := exec.Command(item.program, item.args...).CombinedOutput()
command := strings.Join(append([]string{item.program}, item.args...), " ")
if err != nil {
return result{name: item.name, command: command, version: "unknown", reason: commandError(err, string(output)), passed: false}
}
version, major, ok := parseVersion(string(output))
if !ok {
return result{name: item.name, command: command, version: "unknown", reason: "no semantic version found in command output", passed: false}
}
if major != item.expectedMajor {
return result{name: item.name, command: command, version: version, reason: fmt.Sprintf("major %d does not match expected pin %s", major, item.expectedPin), passed: false}
}
return result{name: item.name, command: command, version: version, reason: fmt.Sprintf("major %d matches expected pin %s", major, item.expectedPin), passed: true}
}
What does one version check do?
- The helper runs the program and captures its combined output.
- The helper parses a semantic version before comparing the installed major with the expected pin.
- Every path returns the same result shape.
- Save cmd/doctor/main.go.
- Confirm the reusable version runner compiles by running:
go run ./cmd/doctor
What should remain visible?
You should still see one Go PASS line. The reusable runner now compiles before main() starts using it.
- Insert runLineEndingCheck directly below runVersionCheck:
func runLineEndingCheck() result {
const command = "git ls-files --eol"
output, err := exec.Command("git", "ls-files", "--eol").CombinedOutput()
if err != nil {
return result{name: "Git LF", command: command, version: "unknown", reason: commandError(err, string(output)), passed: false}
}
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
for _, line := range lines {
if !strings.Contains(line, "i/lf") {
return result{name: "Git LF", command: command, version: "not LF-only", reason: "tracked entry is not i/lf: " + strings.TrimSpace(line), passed: false}
}
}
return result{name: "Git LF", command: command, version: "LF-only", reason: fmt.Sprintf("all %d tracked entries report i/lf", len(lines)), passed: true}
}
How does the Git check differ?
The Git check validates tracked content instead of parsing a tool version. Every tracked entry must contain i/lf for this intermediate check to pass.
- Save cmd/doctor/main.go.
- Verify the line-ending check compiles by running:
go run ./cmd/doctor
What did this run prove?
The original Go result should still print. The dedicated LF check now compiles alongside it.
- Add the runtime import above strconv:
"runtime"
Why inspect the operating system?
Native Linux Podman can report its local engine directly. Desktop hosts require server data from the remote engine.
- Insert runEngineCheck directly below runLineEndingCheck:
func runEngineCheck() result {
dockerOutput, dockerErr := exec.Command("docker", "version").CombinedOutput()
if dockerErr == nil && strings.Contains(string(dockerOutput), "Server:") {
return validateEngine("Docker", "docker version", versionAfterMarker(string(dockerOutput), "Server:"), "29.8.0", 29)
}
podmanOutput, podmanErr := exec.Command("podman", "version").CombinedOutput()
if podmanErr != nil {
return result{name: "Engine", command: "docker version; podman version", version: "unknown", reason: "Docker: " + commandError(dockerErr, string(dockerOutput)) + "; Podman: " + commandError(podmanErr, string(podmanOutput)), passed: false}
}
version := ""
if strings.Contains(string(podmanOutput), "Server:") {
version = versionAfterMarker(string(podmanOutput), "Server:")
} else if runtime.GOOS == "linux" {
version, _, _ = parseVersion(string(podmanOutput))
}
if version == "" {
return result{name: "Engine", command: "podman version", version: "unknown", reason: "Podman client answered but no reachable engine version was found", passed: false}
}
return validateEngine("Podman", "podman version", version, "6", 6)
}
How does the engine fallback work?
The check tries Docker first and requires a reachable server section. It tries Podman when Docker does not provide that evidence.
The fallback proves a running engine instead of accepting a client binary by itself.
- Save cmd/doctor/main.go.
- Confirm the engine fallback compiles by running:
go run ./cmd/doctor
What does this compile checkpoint show?
You should see the original Go PASS line again. Every specialized helper required by the ten-check loop now compiles.
- Find the original main() function near the top of cmd/doctor/main.go. It currently looks like this:
func main() {
output, err := exec.Command("go", "version").CombinedOutput()
if err != nil {
fmt.Printf("FAIL | Go | go version | version=unknown | %s\n", strings.TrimSpace(string(output)))
return
}
match := semverPattern.FindStringSubmatch(string(output))
if match == nil {
fmt.Println("FAIL | Go | go version | version=unknown | no semantic version found in command output")
return
}
fmt.Printf("PASS | Go | go version | version=%s | version parsed\n", strings.TrimPrefix(match[0], "v"))
}
Why replace the original function?
The original function owns every part of one Go check. The replacement moves those repeated responsibilities into helpers and leaves main() to define order.
- Replace the original main() function with this table-driven version:
func main() {
checks := []check{
{name: "Go", program: "go", args: []string{"version"}, expectedPin: "1.27.1", expectedMajor: 1},
{name: "Node", program: "node", args: []string{"--version"}, expectedPin: "24.21.0", expectedMajor: 24},
{name: "Cargo", program: "cargo", args: []string{"--version"}, expectedPin: "1.98.1", expectedMajor: 1},
{name: "Task", program: "task", args: []string{"--version"}, expectedPin: "3.53.1", expectedMajor: 3},
{name: "kind", program: "kind", args: []string{"version"}, expectedPin: "0.33.0", expectedMajor: 0},
{name: "kubectl", program: "kubectl", args: []string{"version", "--client"}, expectedPin: "1.37.0", expectedMajor: 1},
{name: "Helm", program: "helm", args: []string{"version"}, expectedPin: "4.3.0", expectedMajor: 4},
{name: "Ollama", program: "ollama", args: []string{"--version"}, expectedPin: "0.34.1", expectedMajor: 0},
}
results := []result{runVersionCheck(checks[0]), runVersionCheck(checks[1]), runLineEndingCheck()}
for _, item := range checks[2:] {
results = append(results, runVersionCheck(item))
}
results = append(results, runEngineCheck())
for _, item := range results {
status := "PASS"
if !item.passed {
status = "FAIL"
}
fmt.Printf("%s | %-10s | %s | version=%s | %s\n", status, item.name, item.command, clean(item.version), clean(item.reason))
}
}
How does the table produce ten checks?
- The table defines eight version-based capabilities with their programs, arguments, pins, and expected majors.
- The result list places the Git LF check after Node before continuing through the remaining version checks.
- The engine check is appended last to preserve the required ten-check order.
- The final loop prints every result even when an earlier capability fails.
Before you run the table, how many named result lines do you expect the loop to print?
- Save cmd/doctor/main.go.
- Run the table-driven doctor with:
go run ./cmd/doctor
What should you see?
You should see ten named lines in this order: Go, Node, Git LF, Cargo, Task, kind, kubectl, Helm, Ollama, and Engine.
Each line includes a status, literal command, parsed version or LF result, and reason.
That is the table doing real work. One sequence now reports the whole workstation instead of one hard-coded command.
Missing results or seeing a compile error?
Check that the replacement main() appears only once. Confirm that each helper sits outside every other function.
If one capability reports FAIL, read that line's reason before changing the code. A valid failure result proves the loop reached that check.
Help me debug the ten-check loop.
✔️ Awesome, I've got everything!
Great. Confirm that cmd/doctor/main.go is saved and the doctor prints ten named results.
ⓧ I'd like to double check the full code
Compare your complete cmd/doctor/main.go with this intermediate version:
package main
import (
"fmt"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
)
var semverPattern = regexp.MustCompile(`v?(\d+)\.(\d+)(?:\.(\d+))?`)
type check struct {
name string
program string
args []string
expectedPin string
expectedMajor int
}
type result struct {
name string
command string
version string
reason string
passed bool
}
func main() {
checks := []check{
{name: "Go", program: "go", args: []string{"version"}, expectedPin: "1.27.1", expectedMajor: 1},
{name: "Node", program: "node", args: []string{"--version"}, expectedPin: "24.21.0", expectedMajor: 24},
{name: "Cargo", program: "cargo", args: []string{"--version"}, expectedPin: "1.98.1", expectedMajor: 1},
{name: "Task", program: "task", args: []string{"--version"}, expectedPin: "3.53.1", expectedMajor: 3},
{name: "kind", program: "kind", args: []string{"version"}, expectedPin: "0.33.0", expectedMajor: 0},
{name: "kubectl", program: "kubectl", args: []string{"version", "--client"}, expectedPin: "1.37.0", expectedMajor: 1},
{name: "Helm", program: "helm", args: []string{"version"}, expectedPin: "4.3.0", expectedMajor: 4},
{name: "Ollama", program: "ollama", args: []string{"--version"}, expectedPin: "0.34.1", expectedMajor: 0},
}
results := []result{runVersionCheck(checks[0]), runVersionCheck(checks[1]), runLineEndingCheck()}
for _, item := range checks[2:] {
results = append(results, runVersionCheck(item))
}
results = append(results, runEngineCheck())
for _, item := range results {
status := "PASS"
if !item.passed {
status = "FAIL"
}
fmt.Printf("%s | %-10s | %s | version=%s | %s\n", status, item.name, item.command, clean(item.version), clean(item.reason))
}
}
func runVersionCheck(item check) result {
output, err := exec.Command(item.program, item.args...).CombinedOutput()
command := strings.Join(append([]string{item.program}, item.args...), " ")
if err != nil {
return result{name: item.name, command: command, version: "unknown", reason: commandError(err, string(output)), passed: false}
}
version, major, ok := parseVersion(string(output))
if !ok {
return result{name: item.name, command: command, version: "unknown", reason: "no semantic version found in command output", passed: false}
}
if major != item.expectedMajor {
return result{name: item.name, command: command, version: version, reason: fmt.Sprintf("major %d does not match expected pin %s", major, item.expectedPin), passed: false}
}
return result{name: item.name, command: command, version: version, reason: fmt.Sprintf("major %d matches expected pin %s", major, item.expectedPin), passed: true}
}
func runLineEndingCheck() result {
const command = "git ls-files --eol"
output, err := exec.Command("git", "ls-files", "--eol").CombinedOutput()
if err != nil {
return result{name: "Git LF", command: command, version: "unknown", reason: commandError(err, string(output)), passed: false}
}
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
for _, line := range lines {
if !strings.Contains(line, "i/lf") {
return result{name: "Git LF", command: command, version: "not LF-only", reason: "tracked entry is not i/lf: " + strings.TrimSpace(line), passed: false}
}
}
return result{name: "Git LF", command: command, version: "LF-only", reason: fmt.Sprintf("all %d tracked entries report i/lf", len(lines)), passed: true}
}
func runEngineCheck() result {
dockerOutput, dockerErr := exec.Command("docker", "version").CombinedOutput()
if dockerErr == nil && strings.Contains(string(dockerOutput), "Server:") {
return validateEngine("Docker", "docker version", versionAfterMarker(string(dockerOutput), "Server:"), "29.8.0", 29)
}
podmanOutput, podmanErr := exec.Command("podman", "version").CombinedOutput()
if podmanErr != nil {
return result{name: "Engine", command: "docker version; podman version", version: "unknown", reason: "Docker: " + commandError(dockerErr, string(dockerOutput)) + "; Podman: " + commandError(podmanErr, string(podmanOutput)), passed: false}
}
version := ""
if strings.Contains(string(podmanOutput), "Server:") {
version = versionAfterMarker(string(podmanOutput), "Server:")
} else if runtime.GOOS == "linux" {
version, _, _ = parseVersion(string(podmanOutput))
}
if version == "" {
return result{name: "Engine", command: "podman version", version: "unknown", reason: "Podman client answered but no reachable engine version was found", passed: false}
}
return validateEngine("Podman", "podman version", version, "6", 6)
}
func validateEngine(name, command, version, pin string, expectedMajor int) result {
major, err := majorFromVersion(version)
if err != nil {
return result{name: "Engine", command: command, version: version, reason: name + " engine version could not be parsed", passed: false}
}
if major != expectedMajor {
return result{name: "Engine", command: command, version: version, reason: fmt.Sprintf("%s engine major %d does not match expected pin %s", name, major, pin), passed: false}
}
return result{name: "Engine", command: command, version: version, reason: fmt.Sprintf("%s engine answered and major %d matches expected pin %s", name, major, pin), passed: true}
}
func parseVersion(output string) (string, int, bool) {
match := semverPattern.FindStringSubmatch(output)
if match == nil {
return "", 0, false
}
major, err := strconv.Atoi(match[1])
if err != nil {
return "", 0, false
}
return strings.TrimPrefix(match[0], "v"), major, true
}
func majorFromVersion(version string) (int, error) {
match := semverPattern.FindStringSubmatch(version)
if match == nil {
return 0, fmt.Errorf("version not found")
}
return strconv.Atoi(match[1])
}
func versionAfterMarker(output, marker string) string {
index := strings.Index(output, marker)
if index == -1 {
return ""
}
for _, line := range strings.Split(output[index+len(marker):], "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "Version:") {
version, _, ok := parseVersion(trimmed)
if ok {
return version
}
}
}
return ""
}
func commandError(err error, output string) string {
message := strings.TrimSpace(output)
if message == "" {
if err == nil {
return "command failed without output"
}
message = err.Error()
}
return message
}
func clean(value string) string {
value = strings.TrimSpace(value)
value = strings.ReplaceAll(value, "\r", " ")
value = strings.ReplaceAll(value, "\n", "; ")
return value
}
What should you compare?
Check the import order, function order, table entries, and result loop. These details keep the cumulative file aligned with the intermediate design.
Prove a printed failure can still exit zero
The PATH environment variable controls which directories a terminal searches for commands. Removing only the dedicated Helm directory from one terminal creates a reversible failure without changing the installed files.
- Open a second terminal panel in the editor you are already using.
- Record your current PATH without the dedicated Helm binary directory here: your PATH without the dedicated Helm directory.
Before you run the doctor, do you think printing a Helm FAIL automatically makes the process return a failure status?
macOS
- Apply the isolated PATH to this terminal and inspect the doctor's status by running:
PATH="[[PATH_WITHOUT_HELM="your PATH without the dedicated Helm directory"]]"
export PATH
go run ./cmd/doctor
echo $?
What should you observe?
You should see all ten named result lines. The Helm line shows FAIL because this terminal cannot find the command.
The final number is 0. The loop reports failure in text without returning failure to the shell.
Windows
- Apply the isolated PATH to this PowerShell session and inspect the doctor's status by running:
$Env:PATH = "[[PATH_WITHOUT_HELM="your PATH without the dedicated Helm directory"]]"
go run ./cmd/doctor
$LASTEXITCODE
What should you observe?
You should see all ten named result lines. The Helm line shows FAIL because this PowerShell session cannot find the command.
The final number is 0. The loop reports failure in text without returning failure to the shell.
Linux
- Apply the isolated PATH to this terminal and inspect the doctor's status by running:
PATH="[[PATH_WITHOUT_HELM="your PATH without the dedicated Helm directory"]]"
export PATH
go run ./cmd/doctor
echo $?
What should you observe?
You should see all ten named result lines. The Helm line shows FAIL because this terminal cannot find the command.
The final number is 0. The loop reports failure in text without returning failure to the shell.
The mismatch is now visible. The doctor tells a person that Helm failed while telling automation that the run succeeded.
Helm still passing or other commands failing?
If Helm still passes, its directory remains somewhere in the isolated PATH. Remove only the dedicated Helm directory from the copied value.
If several commands disappear, restore the copied PATH entries for those tools. Keep the test limited to Helm.
Help me isolate Helm in PATH.
- Close the isolated terminal panel to restore the original PATH.
- Return to the primary terminal from earlier.
- Stage only cmd/doctor/main.go with your editor's source-control controls.
- Commit the staged change with the message refactor: run checks from a table.
- Confirm the branch history lists refactor: run checks from a table as the latest commit.
Your doctor now checks ten capabilities and exposes a real automation gap. Next up, you will add deadlines and make every failed prerequisite produce a trustworthy process status.
Harden the Doctor and Capture Evidence
Your table-driven Go doctor now runs all ten checks. Its Helm result exposed a contract gap because a FAIL line still ended with a successful process status.
You will add five-second deadlines before making the final status truthful. You will then capture both paths as README evidence.
In this step, get ready to:
- Put every external command behind a five-second deadline.
- Return a non-zero process status when any check fails.
- Capture successful output and a controlled Helm failure.
Add a five-second command boundary
A context deadline stops a stalled tool from holding the entire doctor open. Every command gets the same boundary through one helper.
- In cmd/doctor/main.go, replace the top of the file through commandTimeout with this exact section:
import (
"context"
"fmt"
"os"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"time"
)
const commandTimeout = 5 * time.Second
What does this section add?
- The context package carries the deadline into each process.
- The time package defines the five-second duration.
- The os package supports the final failure exit.
- The shared commandTimeout value keeps every check on the same limit.
- Add runCommand() above parseVersion() by copying this function:
func runCommand(program string, args ...string) (string, error, bool) {
ctx, cancel := context.WithTimeout(context.Background(), commandTimeout)
defer cancel()
command := exec.CommandContext(ctx, program, args...)
output, err := command.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return string(output), err, true
}
return string(output), err, false
}
How does the command boundary work?
- The context starts a five-second countdown for one command.
- The deferred cancellation releases its resources when the helper returns.
- The direct process call avoids separate shell behavior across operating systems.
- The final Boolean tells each check whether its command exceeded the deadline.
- Save cmd/doctor/main.go.
- Before you run this, predict whether the ten result lines change.
- Confirm the new helper compiles by running:
task doctor
What does this run prove?
You should still see all ten result lines in their original format. This run proves the shared timeout helper compiles before any check starts using it.
🙋♀️ Seeing a compile error?
Check that the three new imports sit inside the existing import block. Confirm that runCommand() appears outside every other function.
Ask for help with the exact compiler output: Help me fix this Go timeout helper.
- Replace the existing runVersionCheck() function with this deadline-aware version:
func runVersionCheck(item check) result {
output, err, timedOut := runCommand(item.program, item.args...)
command := strings.Join(append([]string{item.program}, item.args...), " ")
if timedOut {
return result{name: item.name, command: command, version: "unknown", reason: "command exceeded 5 seconds", passed: false}
}
if err != nil {
return result{name: item.name, command: command, version: "unknown", reason: commandError(err, output), passed: false}
}
version, major, ok := parseVersion(output)
if !ok {
return result{name: item.name, command: command, version: "unknown", reason: "no semantic version found in command output", passed: false}
}
if major != item.expectedMajor {
return result{name: item.name, command: command, version: version, reason: fmt.Sprintf("major %d does not match expected pin %s", major, item.expectedPin), passed: false}
}
return result{name: item.name, command: command, version: version, reason: fmt.Sprintf("major %d matches expected pin %s", major, item.expectedPin), passed: true}
}
How are versions gated?
- Each table entry passes its program and arguments into the shared runner.
- A timed-out command receives a short failure reason.
- A parsed major version must match the major from the expected pin.
- The installed patch remains visible without controlling the pass result.
- Replace the existing runLineEndingCheck() function with this stricter version:
func runLineEndingCheck() result {
const command = "git ls-files --eol"
output, err, timedOut := runCommand("git", "ls-files", "--eol")
if timedOut {
return result{name: "Git LF", command: command, version: "unknown", reason: "command exceeded 5 seconds", passed: false}
}
if err != nil {
return result{name: "Git LF", command: command, version: "unknown", reason: commandError(err, output), passed: false}
}
lines := strings.Split(strings.TrimSpace(output), "\n")
if len(lines) == 1 {
if strings.TrimSpace(lines[0]) == "" {
return result{name: "Git LF", command: command, version: "unknown", reason: "repository has no tracked files", passed: false}
}
}
for _, line := range lines {
if !strings.Contains(line, "i/lf") {
return result{name: "Git LF", command: command, version: "not LF-only", reason: "tracked entry is not i/lf: " + strings.TrimSpace(line), passed: false}
}
if strings.Contains(line, "i/crlf") {
return result{name: "Git LF", command: command, version: "not LF-only", reason: "tracked entry contains CRLF index content", passed: false}
}
if strings.Contains(line, "i/mixed") {
return result{name: "Git LF", command: command, version: "not LF-only", reason: "tracked entry contains mixed index content", passed: false}
}
}
return result{name: "Git LF", command: command, version: "LF-only", reason: fmt.Sprintf("all %d tracked entries report i/lf", len(lines)), passed: true}
}
What makes the LF check stricter?
- The Git command now uses the same deadline as every version check.
- An empty repository fails because it provides no tracked evidence.
- Every tracked entry must contain i/lf.
- Explicit CRLF or mixed index content also produces a named failure.
- Save cmd/doctor/main.go.
- Before you run this, predict what the Git LF result reports for the tracked repository.
- Check the two hardened paths by running:
task doctor
What should you see?
You should see all ten checks complete. The Git LF line should report LF-only with a reason that counts the tracked entries.
🙋♀️ Is the Git LF check failing?
Read the reason at the end of the Git LF line. It identifies the first tracked entry that does not meet the policy.
Use that file name when asking for help: Help me resolve this Git LF failure.
Harden the engine fallback and exit status
A desktop engine is ready only when its server answers. The fallback must also respect the same deadline before the final status becomes trustworthy.
- Inside runEngineCheck(), replace the Docker and Podman command section with this deadline-aware section:
dockerOutput, dockerErr, dockerTimedOut := runCommand("docker", "version")
if !dockerTimedOut {
if dockerErr == nil {
if strings.Contains(dockerOutput, "Server:") {
version := versionAfterMarker(dockerOutput, "Server:")
return validateEngine("Docker", "docker version", version, "29.8.0", 29)
}
}
}
podmanOutput, podmanErr, podmanTimedOut := runCommand("podman", "version")
if podmanTimedOut {
return result{name: "Engine", command: "podman version", version: "unknown", reason: "Docker did not answer and Podman exceeded 5 seconds", passed: false}
}
if podmanErr != nil {
return result{name: "Engine", command: "docker version; podman version", version: "unknown", reason: "Docker: " + commandError(dockerErr, dockerOutput) + "; Podman: " + commandError(podmanErr, podmanOutput), passed: false}
}
How does engine selection work?
- Docker passes only when the command completes within the deadline.
- Docker must also return server data before its version is validated.
- Podman runs only after Docker fails to provide a reachable engine.
- A stalled Podman command returns a short failure reason.
- Replace the remaining version-selection section in runEngineCheck() with this exact section:
version := ""
if strings.Contains(podmanOutput, "Server:") {
version = versionAfterMarker(podmanOutput, "Server:")
} else if runtime.GOOS == "linux" {
version, _, _ = parseVersion(podmanOutput)
}
if version == "" {
return result{name: "Engine", command: "podman version", version: "unknown", reason: "Podman client answered but no reachable engine version was found", passed: false}
}
return validateEngine("Podman", "podman version", version, "6", 6)
}
Why does Podman have two paths?
Desktop Podman must expose remote server data. Native Linux Podman can report its local engine version directly.
- Save cmd/doctor/main.go.
- Before you run this, predict which engine name appears in your tenth result line.
- Verify the engine path by running:
task doctor
What does the engine result prove?
The tenth line should name the reachable engine command. Its reason should confirm that the engine answered before comparing its major version.
🙋♀️ Is the engine check failing?
Confirm your selected engine is still running. A client-only response cannot satisfy the desktop readiness check.
Share the Engine line without any credentials when asking for help: Help me diagnose the engine result.
The result lines now describe failures accurately. The process exit status must summarize those lines for scripts and build systems.
- In main(), replace the existing result-printing loop through the end of the function with this aggregate-status section:
failed := 0
for _, item := range results {
status := "PASS"
if !item.passed {
status = "FAIL"
failed++
}
fmt.Printf("%s | %-10s | %s | version=%s | %s\n", status, item.name, item.command, clean(item.version), clean(item.reason))
}
if failed != 0 {
fmt.Printf("RESULT | FAIL | %d of %d checks failed\n", failed, len(results))
os.Exit(1)
}
fmt.Printf("RESULT | PASS | %d of %d checks passed\n", len(results), len(results))
}
How does the final status work?
- The loop counts failures while preserving every individual result line.
- Any failure produces a summary before os.Exit(1) returns a non-zero status.
- A clean run reaches the PASS summary without calling the failure exit.
- Save cmd/doctor/main.go.
- Before you run this, predict the final summary for your normal toolchain.
- Verify the finished doctor through its public entry point by running:
task doctor
What should the finished run show?
You should see ten PASS lines followed by RESULT | PASS | 10 of 10 checks passed. The command should complete with process status zero.
🙋♀️ Is a normal check failing?
Use the named failure reason to identify the affected command. Confirm the installed major matches the expected pin before changing the doctor.
Ask for help with the failed line: Help me diagnose this doctor failure.
✔️ Awesome, I've got everything!
Great. Double check that cmd/doctor/main.go is saved before capturing the evidence.
ⓧ I'd like to double check the full code
Compare your complete cmd/doctor/main.go file with this reference:
package main
import (
"context"
"fmt"
"os"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"time"
)
const commandTimeout = 5 * time.Second
var semverPattern = regexp.MustCompile(`v?(\d+)\.(\d+)(?:\.(\d+))?`)
type check struct {
name string
program string
args []string
expectedPin string
expectedMajor int
}
type result struct {
name string
command string
version string
reason string
passed bool
}
func main() {
checks := []check{
{name: "Go", program: "go", args: []string{"version"}, expectedPin: "1.27.1", expectedMajor: 1},
{name: "Node", program: "node", args: []string{"--version"}, expectedPin: "24.21.0", expectedMajor: 24},
{name: "Cargo", program: "cargo", args: []string{"--version"}, expectedPin: "1.98.1", expectedMajor: 1},
{name: "Task", program: "task", args: []string{"--version"}, expectedPin: "3.53.1", expectedMajor: 3},
{name: "kind", program: "kind", args: []string{"version"}, expectedPin: "0.33.0", expectedMajor: 0},
{name: "kubectl", program: "kubectl", args: []string{"version", "--client"}, expectedPin: "1.37.0", expectedMajor: 1},
{name: "Helm", program: "helm", args: []string{"version"}, expectedPin: "4.3.0", expectedMajor: 4},
{name: "Ollama", program: "ollama", args: []string{"--version"}, expectedPin: "0.34.1", expectedMajor: 0},
}
results := []result{runVersionCheck(checks[0]), runVersionCheck(checks[1]), runLineEndingCheck()}
for _, item := range checks[2:] {
results = append(results, runVersionCheck(item))
}
results = append(results, runEngineCheck())
failed := 0
for _, item := range results {
status := "PASS"
if !item.passed {
status = "FAIL"
failed++
}
fmt.Printf("%s | %-10s | %s | version=%s | %s\n", status, item.name, item.command, clean(item.version), clean(item.reason))
}
if failed != 0 {
fmt.Printf("RESULT | FAIL | %d of %d checks failed\n", failed, len(results))
os.Exit(1)
}
fmt.Printf("RESULT | PASS | %d of %d checks passed\n", len(results), len(results))
}
func runVersionCheck(item check) result {
output, err, timedOut := runCommand(item.program, item.args...)
command := strings.Join(append([]string{item.program}, item.args...), " ")
if timedOut {
return result{name: item.name, command: command, version: "unknown", reason: "command exceeded 5 seconds", passed: false}
}
if err != nil {
return result{name: item.name, command: command, version: "unknown", reason: commandError(err, output), passed: false}
}
version, major, ok := parseVersion(output)
if !ok {
return result{name: item.name, command: command, version: "unknown", reason: "no semantic version found in command output", passed: false}
}
if major != item.expectedMajor {
return result{name: item.name, command: command, version: version, reason: fmt.Sprintf("major %d does not match expected pin %s", major, item.expectedPin), passed: false}
}
return result{name: item.name, command: command, version: version, reason: fmt.Sprintf("major %d matches expected pin %s", major, item.expectedPin), passed: true}
}
func runLineEndingCheck() result {
const command = "git ls-files --eol"
output, err, timedOut := runCommand("git", "ls-files", "--eol")
if timedOut {
return result{name: "Git LF", command: command, version: "unknown", reason: "command exceeded 5 seconds", passed: false}
}
if err != nil {
return result{name: "Git LF", command: command, version: "unknown", reason: commandError(err, output), passed: false}
}
lines := strings.Split(strings.TrimSpace(output), "\n")
if len(lines) == 1 {
if strings.TrimSpace(lines[0]) == "" {
return result{name: "Git LF", command: command, version: "unknown", reason: "repository has no tracked files", passed: false}
}
}
for _, line := range lines {
if !strings.Contains(line, "i/lf") {
return result{name: "Git LF", command: command, version: "not LF-only", reason: "tracked entry is not i/lf: " + strings.TrimSpace(line), passed: false}
}
if strings.Contains(line, "i/crlf") {
return result{name: "Git LF", command: command, version: "not LF-only", reason: "tracked entry contains CRLF index content", passed: false}
}
if strings.Contains(line, "i/mixed") {
return result{name: "Git LF", command: command, version: "not LF-only", reason: "tracked entry contains mixed index content", passed: false}
}
}
return result{name: "Git LF", command: command, version: "LF-only", reason: fmt.Sprintf("all %d tracked entries report i/lf", len(lines)), passed: true}
}
func runEngineCheck() result {
dockerOutput, dockerErr, dockerTimedOut := runCommand("docker", "version")
if !dockerTimedOut {
if dockerErr == nil {
if strings.Contains(dockerOutput, "Server:") {
version := versionAfterMarker(dockerOutput, "Server:")
return validateEngine("Docker", "docker version", version, "29.8.0", 29)
}
}
}
podmanOutput, podmanErr, podmanTimedOut := runCommand("podman", "version")
if podmanTimedOut {
return result{name: "Engine", command: "podman version", version: "unknown", reason: "Docker did not answer and Podman exceeded 5 seconds", passed: false}
}
if podmanErr != nil {
return result{name: "Engine", command: "docker version; podman version", version: "unknown", reason: "Docker: " + commandError(dockerErr, dockerOutput) + "; Podman: " + commandError(podmanErr, podmanOutput), passed: false}
}
version := ""
if strings.Contains(podmanOutput, "Server:") {
version = versionAfterMarker(podmanOutput, "Server:")
} else if runtime.GOOS == "linux" {
version, _, _ = parseVersion(podmanOutput)
}
if version == "" {
return result{name: "Engine", command: "podman version", version: "unknown", reason: "Podman client answered but no reachable engine version was found", passed: false}
}
return validateEngine("Podman", "podman version", version, "6", 6)
}
func validateEngine(name, command, version, pin string, expectedMajor int) result {
major, err := majorFromVersion(version)
if err != nil {
return result{name: "Engine", command: command, version: version, reason: name + " engine version could not be parsed", passed: false}
}
if major != expectedMajor {
return result{name: "Engine", command: command, version: version, reason: fmt.Sprintf("%s engine major %d does not match expected pin %s", name, major, pin), passed: false}
}
return result{name: "Engine", command: command, version: version, reason: fmt.Sprintf("%s engine answered and major %d matches expected pin %s", name, major, pin), passed: true}
}
func runCommand(program string, args ...string) (string, error, bool) {
ctx, cancel := context.WithTimeout(context.Background(), commandTimeout)
defer cancel()
command := exec.CommandContext(ctx, program, args...)
output, err := command.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return string(output), err, true
}
return string(output), err, false
}
func parseVersion(output string) (string, int, bool) {
match := semverPattern.FindStringSubmatch(output)
if match == nil {
return "", 0, false
}
major, err := strconv.Atoi(match[1])
if err != nil {
return "", 0, false
}
return strings.TrimPrefix(match[0], "v"), major, true
}
func majorFromVersion(version string) (int, error) {
match := semverPattern.FindStringSubmatch(version)
if match == nil {
return 0, fmt.Errorf("version not found")
}
return strconv.Atoi(match[1])
}
func versionAfterMarker(output, marker string) string {
index := strings.Index(output, marker)
if index == -1 {
return ""
}
for _, line := range strings.Split(output[index+len(marker):], "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "Version:") {
version, _, ok := parseVersion(trimmed)
if ok {
return version
}
}
}
return ""
}
func commandError(err error, output string) string {
message := strings.TrimSpace(output)
if message == "" {
if err == nil {
return "command failed without output"
}
message = err.Error()
}
return message
}
func clean(value string) string {
value = strings.TrimSpace(value)
value = strings.ReplaceAll(value, "\r", " ")
value = strings.ReplaceAll(value, "\n", "; ")
return value
}
How to use this reference
Compare the imports, helper functions, check order, and final status logic with your file. Keep the exact command list unchanged.
Capture positive and controlled-failure evidence
Reproducible evidence shows that the doctor can pass a ready machine. A controlled failure proves the same program returns a status that automation can trust.
- Capture the ten PASS lines and PASS summary from the successful run you just completed.
- Switch back to the terminal from earlier where only the dedicated Helm binary directory is hidden from PATH.
- Before you run this, predict the named failure and the final process status.
- Run the hardened doctor in that terminal:
task doctor
What should the controlled failure show?
You should see FAIL | Helm while the remaining checks still print. The final RESULT line should report a failure before the command returns a non-zero process status.
🙋♀️ Does Helm still pass?
Confirm this terminal excludes only the dedicated Helm binary directory from PATH. A shared package-manager directory would hide unrelated tools and invalidate the test.
Ask for help without changing installed files: Help me isolate Helm in this terminal.
- Capture the named Helm failure and the non-zero process status.
- Confirm the Helm binary remains unchanged in its dedicated directory by using the file check from setup.
- Close the Helm-hidden terminal to restore its original PATH.
- In README.md, fill the Validation evidence section with your tested operating system, actual timed minutes, successful output, and controlled-failure output.
- Save README.md.
Your evidence now distinguishes observed results from the two reasoned operating-system paths. The remaining public-delivery placeholders stay in place for the next step.
- Return to the normal project terminal from earlier.
- Before you run this, predict which index line-ending value appears for every tracked text file.
- Verify the final tracked-file policy by running:
git ls-files --eol
What does the LF evidence prove?
Every tracked text file should report i/lf. You should see no i/crlf or i/mixed entries.
🙋♀️ Seeing a different EOL value?
Use the final path on the affected output line to find the file that needs normalization. Keep the LF policies in .gitattributes and .editorconfig unchanged.
Ask for help with the affected line: Help me normalize this tracked file.
- Create a commit from the hardened code and captured evidence with the message feat: enforce timeouts and failure exit by using your Git workflow from earlier.
- Before the final run, predict whether closing the Helm-hidden terminal restored all ten checks.
- Confirm the committed doctor still passes by running:
task doctor
What does the final run confirm?
You should see ten PASS lines followed by RESULT | PASS | 10 of 10 checks passed. That confirms the released state is ready for public delivery.
🙋♀️ Did Helm remain hidden?
Make sure you returned to the normal project terminal after closing the isolated one. Reopening a full terminal session restores the PATH inherited from your system.
Ask for help if the normal run still cannot find Helm: Help me restore Helm to PATH.
That is the reliability contract secured. Next, you will turn the committed doctor and its evidence into a public engineering handover.
Publish the Engineering Handover
Your doctor now proves ten workstation capabilities through repeatable evidence. Your Git history preserves the decisions behind those results.
Local code cannot prove collaboration or communication on its own. This step turns your work into a public GitHub handover that a hiring manager or project manager can inspect.
In this step, get ready to:
- Publish a finished README in a public repository.
- Create a visible issue-to-merge delivery trail.
- Release v0.1.0 with a two-minute talk-track recording.
Finish and publish the handover
Your README is the entry point for someone who did not watch you build the doctor. Every public claim must match the evidence you observed on your tested operating system.
- Switch back to README.md in the editor from earlier.
- Replace the AI client marker with the client you actually used.
- Confirm your tested operating system is recorded.
- Confirm your actual elapsed minutes are recorded.
- Confirm the positive evidence contains ten PASS lines plus exit code zero.
- Confirm the negative evidence contains the named Helm failure plus its non-zero exit status.
- Describe the other two operating-system paths as reasoned guidance.
- Confirm the source map says it was reviewed on 2026-09-15.
What makes the handover credible?
The README separates measured evidence from reasoned guidance. That boundary keeps your claims honest.
The architecture diagram shows the program flow. The decision index explains when each choice should be reversed.
- Confirm the Windows quickstart is complete.
- Confirm the macOS quickstart is complete.
- Confirm the Linux quickstart is complete.
- Confirm the expected pins table is complete.
- Confirm the architecture diagram appears before the 60-minute plan.
- Confirm the decision index lists DR-001 through DR-004.
- Confirm the limits plus non-goals remain visible.
- Confirm the portfolio sentence remains at the bottom of README.md.
Publishing is the point where private material could become public. Take a careful pass now so the proof remains safe to share.
- Remove every remaining REPLACE marker.
- Remove every remaining PASTE marker.
- Remove any key or token found during your review.
- Remove any real client data found during your review.
- Remove any private URL found during your review.
- Save README.md.
- Create the public machine-ready-doctor repository by running this command:
gh repo create machine-ready-doctor --public --source=. --remote=origin
What does this command do?
- The public setting makes the repository available without an invitation.
- The source setting uses your current local repository.
- The remote setting records the new repository as origin.
You should see the URL of the new public repository. That URL becomes the clean-clone starting point in your README.
- Record the returned repository URL here: https://github.com/your-name/machine-ready-doctor.
- Replace YOUR_PUBLIC_REPOSITORY_URL in README.md with https://github.com/your-name/machine-ready-doctor.
- Save README.md.
Repository creation failing?
Confirm GitHub CLI still reports your authenticated account. An expired session may ask you to complete the browser flow again.
Check whether your account already has an unrelated repository named machine-ready-doctor. Help me troubleshoot this repository creation error.
Before you run the finished doctor, do you expect a documentation-only change to affect any check result?
- Verify the release source by running:
task doctor
What should this prove?
The task runs the same Go doctor documented in the Quickstart. You should see ten PASS lines.
The final RESULT line should report that all ten checks passed. Your documentation now describes a working release state.
- Stage the finished README by running the commands below:
git add README.md
git commit -m "docs: finish engineering handover"
What do these commands save?
The first command stages the completed README.md. The second records the handover in a dedicated documentation commit.
The commit output should report one new commit. Your measured evidence now travels with the source.
- Publish both named branches by running these commands:
git push origin main
git push origin machine-ready-doctor
What does the push preserve?
The first command publishes the default branch. The second publishes the working branch with the three code-growth commits.
- Open https://github.com/your-name/machine-ready-doctor in your browser.
You should see a public repository with the README below its file list. The requirements document plus four decision records should appear in the repository.
Push rejected?
Confirm the command output names origin as the remote. Confirm your authenticated account owns the destination repository.
A rejected branch can indicate that its local name differs from the requested ref. Help me troubleshoot this branch push.
Build the collaboration trail
A public GitHub Project connects acceptance criteria to visible delivery work. The issue records the definition of done.
The pull request shows how the work reaches the default branch. Its merge preserves the growth commits for review.
- Create a Project owned by your current account by running:
gh project create --owner "@me" --title "Machine Ready Doctor"
What does this create?
The command creates a user-owned Project under your current account. The returned Project URL contains its numeric Project number.
- Record the numeric Project number here: your project number.
- Configure the public linked board by running these commands:
gh project edit [[PROJECT_NUMBER="your project number"]] --owner "@me" --visibility PUBLIC
gh project link [[PROJECT_NUMBER="your project number"]] --owner "@me" --repo machine-ready-doctor
gh project field-create [[PROJECT_NUMBER="your project number"]] --owner "@me" --name "Delivery" --data-type "SINGLE_SELECT" --single-select-options "To do,Doing,Review,Done"
What do these commands configure?
- The first command makes the Project public.
- The second command links the repository.
- The third command creates a single-select workflow field named Delivery.
- Open the configured Project by running:
gh project view [[PROJECT_NUMBER="your project number"]] --owner "@me" --web
What should you see?
Your browser should open the user-owned Project. The Project should show public visibility plus a link to machine-ready-doctor.
- Change the Project view to its board layout.
- Select View in the Project toolbar.
- Set Column field to Delivery.
You should see To do, Doing, Review, plus Done as the four board columns.
- Return to the public repository from earlier.
- Select Issues under the repository name.
- Select New issue.
- Select Open a blank issue if GitHub displays issue templates.
- Enter Machine ready: the doctor in the Title field.
What belongs in the issue?
The checklist turns your acceptance criteria into one reviewable delivery item. Each checked line should point to evidence already present in the repository.
- Add Doctor runs all ten checks in the required order. as a checklist item.
- Add Five-second timeouts are enforced. as a checklist item.
- Add Version gates and LF validation are proven. as a checklist item.
- Add Positive and controlled-failure evidence is recorded. as a checklist item.
- Add Public delivery artifacts are complete. as a checklist item.
- Select Submit new issue.
You should see issue #1 with the title Machine ready: the doctor. Its body should contain the definition-of-done checklist.
- Record the browser URL for issue #1 here: https://github.com/your-name/machine-ready-doctor/issues/1.
- Add issue #1 to the Project by running:
gh project item-add [[PROJECT_NUMBER="your project number"]] --owner "@me" --url [[ISSUE_URL="https://github.com/your-name/machine-ready-doctor/issues/1"]]
What does this connect?
The command adds the repository issue to your user-owned Project. The board card now points to the original checklist.
- Return to the Project board from earlier.
- Move issue #1 to To do.
- Move issue #1 to Doing.
The card should now sit in Doing. Its earlier position in To do records the start of the workflow.
The pull request body needs exactly three status lines. A fourth line links the merge to issue #1.
- Open the pull-request form for the working branch by running:
gh pr create --web --base main --head machine-ready-doctor
What does this compare?
The form compares machine-ready-doctor with main. The comparison keeps each growth commit visible.
- Enter Publish the engineering handover in the Title field.
- Enter done: doctor, evidence, and handover complete as the first body line.
- Enter blocked: none as the second body line.
- Enter next: publish v0.1.0 as the third body line.
- Enter Closes #1 as the fourth body line.
- Select Create Pull Request.
You should see a pull request from machine-ready-doctor to main. Its description should contain only the four required lines.
- Return to the Project board from earlier.
- Move issue #1 to Review.
GitHub CLI may ask you to confirm the merge. Approving that prompt creates the merge commit required by this handover.
- Merge the working branch by running:
gh pr merge machine-ready-doctor --merge
Why use a merge commit?
The merge strategy preserves the three growth commits. The default branch gains a separate commit that records the delivery event.
The Closes #1 line closes issue #1 when the pull request reaches the default branch.
- Return to issue #1 in your browser.
The issue should show a closed state. The linked pull request should show that it was merged into main.
- Return to the Project board from earlier.
- Move issue #1 to Done.
That is the collaboration trail complete. One issue has now moved through all four delivery states before closing in Done.
Project or pull request command failing?
Confirm your GitHub CLI authentication still includes the project scope. Check the failing command for the Project number or branch it could not find.
Keep the existing issue plus commit history intact while you troubleshoot. Help me troubleshoot this GitHub delivery command.
Record and release the talk track
The repository shows what you built. A short talk track proves that you can explain the design plus the controlled failure in your own words.
- Start a two-minute recording with a screen recorder available on your computer.
- Explain the workstation-readiness problem.
- Walk through the architecture diagram.
- Explain one decision from docs/decisions.md.
- Show the successful doctor evidence.
- Show the forced Helm failure evidence.
- State the next improvement you would make.
- Save the recording with an ignored extension from .gitignore.
- Record the saved file path here: path to your recording file.
Before you check the repository, do you expect Git to list the ignored recording as a change?
- Check the local release state by running:
git status --short
What should this prove?
The short status command lists uncommitted paths. Your recording should stay absent because its extension is ignored.
An empty result confirms that the local release source is clean. The recording remains available for the release upload.
The recording becomes a public release asset when you publish it. Review the audio plus the visible screen one last time so no private data leaves your machine.
- Return to the public repository from earlier.
- Select Releases beside the file list.
- Select Draft a new release.
- Open the Choose a tag menu.
- Enter v0.1.0 as the new tag.
- Select Create new tag.
- Set Target to main.
What does the tag identify?
A release is based on a Git tag. Targeting main places v0.1.0 on the merge commit at the tip of the default branch.
- Enter v0.1.0 in the Release title field.
- Enter This release publishes the cross-platform workstation doctor with its validation evidence. in the Describe this release field.
- Attach path to your recording file in the binaries box.
- Select Publish release.
You should see the public v0.1.0 release with one sentence of notes. The two-minute recording should appear as an attached asset.
Your project manager needs a three-line status update. Choose the outcome that matches your final workstation state.
✔️ No blockers remain
- Send passed: ten doctor checks, controlled failure, public handover, and release as the first line.
- Send still FAIL: none as the second line.
- Send clear: now as the third line.
ⓧ The engine is still blocked
- Send passed: doctor implementation, controlled failure, public handover, and release as the first line.
- Start the second line with still FAIL: your named engine blocker.
- Start the third line with clear: your expected clearance time.
Before the final check, do you expect every public artifact to tell the same delivery story?
- Return to the public repository URL from earlier.
You should see a public repository with the finished README. The file tree should contain the release-state source from this project.
- Return to the linked Project from earlier.
You should see public visibility. Issue #1 should sit in Done.
- Open the merged pull request from the repository.
You should see exactly three status lines followed by Closes #1. The pull request should show a merge into main.
- Open the repository's Releases page.
You should see release v0.1.0 with the recording attached. The tag should identify the merge commit on the default branch.
You have turned a local doctor into a public engineering handover. A reviewer can now inspect the design, evidence, delivery workflow, plus your explanation of the result.
Secret mission
Prove Pin Drift Is Visible
Introduce a temporary Node policy mismatch to prove your doctor catches version drift. Then restore the released policy and confirm the repository returns to a clean state without changing release v0.1.0.
Clean Up Your Resources
Clean Up Your Resources
This personal-learning path has no ongoing charges. Choose the Keep, Pause, or Delete option that matches what you want to preserve.
Resources you used:
- Local machine-ready-doctor source files with Git history.
- Public GitHub repository machine-ready-doctor with issue #1 plus the merged pull request.
- Public GitHub release v0.1.0 with the attached talk-track recording.
- Public linked four-column GitHub Project.
Keep everything running
No action is needed. Choose this option if you want your portfolio proof to remain available.
- Your local machine-ready-doctor clone stays available at no cost.
- Your public repository stays available for hiring managers to inspect.
- Your linked Project remains available as collaboration evidence.
- Your local container engine remains installed for future projects.
- Your Ollama model qwen3:1.7b continues to use local disk space.
- Your Rust toolchain remains available for future development work.
Pause - I'll come back to this later
Pausing frees local memory while preserving your source files. Your public GitHub artifacts remain available.
- Stop the container engine through the same application or Linux service controls you used on Day Zero.
- Stop Ollama through the same local application or service controls you used on Day Zero.
- Keep the local machine-ready-doctor folder in its current location.
- Leave the public repository unchanged.
- Leave the public linked Project unchanged.
- Leave release v0.1.0 unchanged.
Your local processor usage plus memory usage should drop. Every saved artifact stays ready for your return.
Delete - I don't want to use this again
This option is for learners who no longer want the public proof. Each resource has its own confirmation step, so you can stop before anything disappears.
- Save any evidence you want to preserve outside the repository.
- Open the public linked four-column GitHub Project.
- Open the Project settings area.
- Find the destructive-actions area.
- Use the Project deletion control.
- Confirm the Project title in the deletion prompt.
- Return to your Projects list to confirm the four-column Project no longer appears.
That removes the standalone planning board. Your public repository remains available for the next cleanup action.
- Open the public machine-ready-doctor repository.
- Open the repository settings area.
- Find the destructive-actions area.
- Use the repository deletion control.
- Confirm the repository identifier shown in the deletion prompt.
- Return to the former repository page to confirm it no longer loads.
You've now cleared the public delivery footprint. Issue #1 plus the merged pull request disappeared with the repository.
Release v0.1.0 disappeared with its recording attachment.
The final listed resource is the local clone. Use the tab for your operating system to remove it from the location where you created it.
macOS
- Use Finder to open the folder containing your existing machine-ready-doctor clone.
- Move the machine-ready-doctor folder to the Trash.
- Empty the Trash to release its disk space.
- Return to the former parent folder to confirm machine-ready-doctor is absent.
Windows
- Use File Explorer to open the folder containing your existing machine-ready-doctor clone.
- Move the machine-ready-doctor folder to the Recycle Bin.
- Empty the Recycle Bin to release its disk space.
- Return to the former parent folder to confirm machine-ready-doctor is absent.
Linux
- Use your file manager to open the folder containing your existing machine-ready-doctor clone.
- Move the machine-ready-doctor folder to the deleted-items area.
- Empty the deleted-items area to release its disk space.
- Return to the former parent folder to confirm machine-ready-doctor is absent.
That's the local project cleanup complete. Its Git history disappeared with the folder.
This option removes only the listed project resources. Your shared Day Zero tools remain installed.
Nice Work!
Nice Work!
You did it! Your public Go workstation doctor now proves ten prerequisites with trustworthy output.
You've learned how to:
- Built a ten-check doctor with version gates. Added five-second deadlines to prevent stalled commands. Made its exit status reflect the complete result.
- Enforced LF-only source control across the repository. Captured a successful run. Captured a controlled Helm failure without deleting installed files.
- Published a public engineering handover on GitHub. Preserved three growth commits through a merged pull request. Closed issue #1 through the pull request. Shipped a public linked Project. Attached a talk-track recording to the v0.1.0 release.
- Secret Mission: Proved that an intentional Node.js major pin mismatch produces a named failure. Confirmed the process exited non-zero. Restored the released value. Confirmed ten PASS lines with a clean working tree.
Ready to quiz yourself?