Development workflow
Every change to GSKILL is built test-first and must pass a single quality gate. That gate is enforced identically in three places, so nothing slips through:
| Layer | Mechanism | When it runs |
|---|---|---|
| Developer / AI agent | scripts/verify.sh |
On demand — the definition of done |
| Git hooks | pre-commit framework |
Fast checks on commit, full gate on push |
| CI (source of truth) | .github/workflows/ci.yml |
Every push to main and every PR |
All three run the same shell scripts, so a green local gate and a green CI run
mean the same thing. The harness deliberately has no Makefile — the scripts in
scripts/ are the batch layer. For the full CI/CD gate catalog (required checks,
action-pinning convention, how to reproduce a gate locally), see
docs/ci-cd.md.
Quick start
Section titled “Quick start”# 1. One-time: install pinned dev tools into ./bin and activate git hooks./scripts/bootstrap.sh
# 2. Run the gate — exit 0 means the work is correct ("definition of done")./scripts/verify.shbootstrap.sh is reproducible: it installs each tool at the exact version pinned
in .config/tool-versions into a project-local ./bin (gitignored), so your
machine, the AI agent, the git hooks, and CI all run byte-identical tool versions.
The gate
Section titled “The gate”scripts/verify.sh runs these checks in order and stops at the first failure:
go mod tidycheck — fails ifgo.mod/go.sumare not tidy- format check —
golangci-lint fmt --diff(gofmt + gofumpt) - lint —
go vet+golangci-lint run(40 linters via.golangci.yml) - tests —
go test -race -shuffle=onwith a coverage profile - coverage floor — total coverage must be ≥
COVERAGE_MIN(default0) - vulnerabilities —
govulncheckon called code paths - secrets —
gitleaksscan of the working tree - npm channel —
node --test npm/test/*.test.mjsfor the npm distribution launcher/packaging (requires Node.js ≥ 20 — a platform prerequisite like Go, not vendored into./bin; CI runs the same script in a dedicatednpm-testjob)
Scripts reference
Section titled “Scripts reference”Each script is independently runnable, sources scripts/lib.sh, and resolves
tools from ./bin first.
| Script | Purpose | Notable flags |
|---|---|---|
scripts/bootstrap.sh |
Install pinned tools into ./bin; install git hooks |
— |
scripts/verify.sh |
The full gate / definition of done | — |
scripts/tdd.sh |
Watch *.go and re-run tests on change (inner TDD loop) |
— |
scripts/fmt.sh |
Format Go code | --check (no writes; for CI/hooks) |
scripts/lint.sh |
go vet + golangci-lint |
passes args through (e.g. --fix) |
scripts/test.sh |
Race + coverage tests → coverage.out |
--short (fast, no race/coverage) |
scripts/cover.sh |
Enforce COVERAGE_MIN against the profile |
reads COVERAGE_MIN env |
scripts/vuln.sh |
govulncheck |
— |
scripts/secrets.sh |
gitleaks secret scan |
— |
scripts/npm-test.sh |
npm distribution channel tests (node --test npm/test/*.test.mjs) |
— |
scripts/image.sh |
Build and smoke-test the container image with ko; the release image’s tag policy and publish step | build --all, check, smoke REF |
TDD workflow (red → green → refactor)
Section titled “TDD workflow (red → green → refactor)”- Red — write the smallest failing test for the next behavior. Run
scripts/test.sh(orscripts/tdd.shto watch) and see it fail. - Green — write the minimal code to pass; run the tests again.
- Refactor — clean up with tests green.
- Done — only when
scripts/verify.shexits0.
Tools live in ./bin at pinned versions from .config/tool-versions — don’t rely
on system installs.
Pinned tooling
Section titled “Pinned tooling”Versions live in .config/tool-versions — the single source of truth read by
both bootstrap.sh and CI:
| Tool | What it does |
|---|---|
golangci-lint (v2) |
Lint + bundled formatters (gofmt, gofumpt) |
govulncheck |
Known-vulnerability scanning |
gitleaks |
Secret detection |
ko |
Builds the container image (scripts/image.sh) |
crane |
Pushes the release image in image-release.yml; CI only, not installed by bootstrap.sh |
shellcheck is provided by the pre-commit hook, not installed into ./bin.
Git hooks (pre-commit framework)
Section titled “Git hooks (pre-commit framework)”.pre-commit-config.yaml wires the framework to the harness scripts (logic lives
in one place, not duplicated in YAML):
- on commit (fast): format check, lint,
go test --short, secret scan, plus stock hooks (trailing whitespace, end-of-file, YAML/TOML checks, merge-conflict and large-file guards) andshellcheckonscripts/*.sh. - on push: the full
scripts/verify.sh.
bootstrap.sh installs the hooks automatically (pre-commit must be installed).
Project layout
Section titled “Project layout”cmd/gskill/ # CLI entrypointinternal/ # application, CLI, TUI, and domain packagesscripts/ # the batch layer: bootstrap, verify, and per-check scriptsdocs/ # user and contributor documentation.config/tool-versions # pinned dev-tool versions (single source of truth).golangci.yml # lint + formatter configuration (40 linters).gitleaks.toml # secret-scan config (allowlists non-committed tooling dirs).shellcheckrc # shellcheck configuration for the scripts.pre-commit-config.yaml # git-hook wiring → harness scripts.github/workflows/ci.yml # CI gatebin/ # gitignored; pinned tools land here after bootstrapRequirements
Section titled “Requirements”- Go 1.26+
- pre-commit (for git hooks)
- A
gitbinary - Docker or Podman, only to run the container image and for
scripts/image.sh check
Building
Section titled “Building”go build ./... # build everythinggo run ./cmd/gskill # print the dev build markerBuild the container image
Section titled “Build the container image”ko builds the image from the Go module, so building needs no Docker daemon. ko comes from
scripts/bootstrap.sh.
./scripts/image.sh build # this machine's platform, to dist/image/gskill.tardocker load -i dist/image/gskill.tar # or: podman load -i dist/image/gskill.tardocker run --rm gskill.test/gskill:dev version./scripts/image.sh build --all # linux/amd64 and linux/arm64, to the OCI layout dist/image/oci./scripts/image.sh check # needs Docker; what the CI image job runsA local image reports the version dev and the commit it was built from. Its name,
gskill.test/gskill, is under the reserved .test domain, so it cannot be pushed anywhere by
mistake. check builds both platforms, loads this machine’s into Docker, and runs the smoke
suite (scripts/image.sh smoke) as your user.
See also
Section titled “See also”- docs/ci-cd.md — the full CI/CD gate catalog and action-pinning convention.