diff --git a/.git-hooks/pre_commit/check_executable_shebangs.rb b/.git-hooks/pre_commit/check_executable_shebangs.rb
new file mode 100644
index 0000000..5829de1
--- /dev/null
+++ b/.git-hooks/pre_commit/check_executable_shebangs.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Any tracked file with the executable bit set must start with a shebang
+ # (previously pre-commit's check-executables-have-shebangs and
+ # check-shebang-scripts-are-executable, combined).
+ class CheckExecutableShebangs < Base
+ def run
+ errors = []
+
+ `git ls-files -sz`.split("\0").each do |entry|
+ mode_part, path = entry.split("\t", 2)
+ mode = mode_part.split(' ').first.to_i(8) & 0o111
+ next if mode.zero?
+
+ next unless File.exist?(path) && !File.directory?(path)
+
+ first = File.open(path) { |f| f.readline } rescue ''
+ if !first.start_with?('#!')
+ errors << "#{path}: executable file is missing a shebang"
+ elsif (File.stat(path).mode & 0o111).zero?
+ errors << "#{path}: script starts with a shebang but is not executable"
+ end
+ end
+
+ return :pass if errors.empty?
+
+ [:fail, errors.join("\n")]
+ end
+ end
+end
diff --git a/.git-hooks/pre_commit/codespell.rb b/.git-hooks/pre_commit/codespell.rb
new file mode 100644
index 0000000..cfd1756
--- /dev/null
+++ b/.git-hooks/pre_commit/codespell.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `codespell` on the files being committed.
+ class Codespell < Base
+ def run
+ result = execute(
+ %w[codespell --ignore-words-list=Claus,Technik --skip=_site,.git,node_modules],
+ args: applicable_files,
+ )
+ return :pass if result.success?
+
+ [:fail, (result.stdout + result.stderr).chomp]
+ end
+ end
+end
diff --git a/.git-hooks/pre_commit/final_newline.rb b/.git-hooks/pre_commit/final_newline.rb
new file mode 100644
index 0000000..7c4e18d
--- /dev/null
+++ b/.git-hooks/pre_commit/final_newline.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Checks that files end with a single newline (previously pre-commit's
+ # end-of-file-fixer, without the auto-fix).
+ class FinalNewline < Base
+ def run
+ errors = []
+
+ applicable_files.each do |file|
+ next unless File.exist?(file) && !File.directory?(file)
+
+ content = File.binread(file)
+ # Skip binaries and files that are not UTF-8 text (ends-of-files
+ # only make sense for text; the previous pre-commit hook ignored
+ # binaries too).
+ next if content.empty?
+ next if content.include?("\0") || !content.valid_encoding?
+
+ unless content.end_with?("\n")
+ errors << "#{file}: missing final newline"
+ end
+ end
+
+ return :pass if errors.empty?
+
+ [:fail, errors.join("\n")]
+ end
+ end
+end
diff --git a/.git-hooks/pre_commit/refactoring_sample_width.rb b/.git-hooks/pre_commit/refactoring_sample_width.rb
new file mode 100644
index 0000000..278fc6c
--- /dev/null
+++ b/.git-hooks/pre_commit/refactoring_sample_width.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Enforces the 72-char pane width on refactoring sample sources
+ # (see the refactoring skill's LESSONS.md 'pane width').
+ class RefactoringSampleWidth < Base
+ MAX_WIDTH = 72
+
+ def run
+ errors = []
+
+ applicable_files.each do |file|
+ next unless File.exist?(file)
+
+ File.foreach(file).with_index(1) do |line, line_no|
+ content = line.chomp
+ next if content.length <= MAX_WIDTH
+
+ errors << "#{file}: line #{line_no} is #{content.length} chars " \
+ "(> #{MAX_WIDTH})"
+ end
+ end
+
+ return :pass if errors.empty?
+
+ [:fail, errors.join("\n")]
+ end
+ end
+end
diff --git a/.git-hooks/pre_commit/spellr.rb b/.git-hooks/pre_commit/spellr.rb
new file mode 100644
index 0000000..b520f7d
--- /dev/null
+++ b/.git-hooks/pre_commit/spellr.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs spellr (the Ruby spell checker) on the files being committed.
+ # spellr splits camelCase and snake_case identifiers, skips URLs and
+ # hex-like strings via heuristics, obeys .gitignore, and consults
+ # .spellr.yml plus the wordlists in .spellr_wordlists/. It runs from
+ # the repo root so the config is found wherever the hook triggers.
+ #
+ # NOTE: spellr applies .spellr.yml excludes only when given a directory
+ # or nothing; explicit file arguments bypass them. Overcommit passes
+ # files, so re-apply the same excludes here.
+ class Spellr < Base
+ EXCLUDES = [
+ # match on the repo-relative path and the bare basename, because
+ # overcommit passes absolute paths in --run mode
+ lambda do |path|
+ rel = path.sub(%r{#{Regexp.escape(Overcommit::Utils.repo_root)}/?}, '')
+ rel =~ %r{^\.claude/} ||
+ rel =~ %r{\.(scala|hs|js)$} ||
+ rel == 'run.sh' ||
+ rel =~ %r{^pages/refactorings/} ||
+ rel == '.gitignore' ||
+ File.basename(rel).start_with?('.')
+ end
+ ].freeze
+
+ def run
+ files = applicable_files.reject { |f| EXCLUDES.any? { |re| re.call(f) } }
+ return :pass if files.empty?
+
+ result = execute(%w[bundle exec spellr], args: files)
+ return :pass if result.success?
+
+ [:fail, (result.stdout + result.stderr).chomp]
+ end
+ end
+end
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..df94bfd
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,27 @@
+name: Lint
+
+on:
+ pull_request:
+ branches: [master]
+ push:
+ branches: [master]
+
+permissions:
+ contents: read
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: "3.3"
+ bundler-cache: true
+
+ - name: Run the lint suite
+ run: scripts/lint.sh
+
+ - name: Build the site
+ run: bundle exec jekyll build
diff --git a/.gitignore b/.gitignore
index 51dfb96..c3a3d18 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,3 +71,6 @@ typings/
dist-newstyle/
*.hi
*.o
+
+# Local linting venv (see README "Lint")
+/.venv/
diff --git a/.mdlrc b/.mdlrc
new file mode 100644
index 0000000..1c3046d
--- /dev/null
+++ b/.mdlrc
@@ -0,0 +1,12 @@
+# Markdown style for the site, shared by every entry point that runs mdl
+# (the overcommit Mdl hook, `scripts/lint.sh`, CI, and ad-hoc manual runs).
+# mdl picks this up automatically from the current directory.
+
+# Jekyll pages and posts start with YAML front matter, which is not prose.
+ignore_front_matter true
+
+# When given a directory, only look at files git tracks, so generated
+# output (_site/, .jekyll-cache/) and dependency dirs are never linted.
+git_recurse true
+
+style "#{File.dirname(__FILE__)}/mdl_style.rb"
diff --git a/.overcommit.yml b/.overcommit.yml
new file mode 100644
index 0000000..3fbb89d
--- /dev/null
+++ b/.overcommit.yml
@@ -0,0 +1,94 @@
+# Linting for the site, Ruby/bundler based (replaces the previous
+# pre-commit/python setup with the same checks):
+#
+# Local hooks: bundle exec overcommit --install
+# Whole repo: scripts/lint.sh (= `bundle exec overcommit --run`)
+# CI: .github/workflows/lint.yml runs scripts/lint.sh
+#
+# Local hooks and CI execute this same configuration, so they cannot drift.
+#
+# Signature verification is off so CI can run `overcommit --run` on a fresh
+# checkout (signatures live in the local git config, which CI never has).
+verify_signatures: false
+
+# Make the installed git hooks resolve gems through the bundle, so hooks find
+# `mdl` and friends without a system-wide gem install.
+gemfile: Gemfile
+
+# Only print hook output when a hook warns or fails.
+quiet: true
+
+PreCommit:
+ AuthorName:
+ enabled: false
+
+ AuthorEmail:
+ enabled: false
+
+ # Hygiene checks (previously pre-commit-hooks):
+ # trailing whitespace -> TrailingWhitespace, CRLF check dropped (repo is
+ # LF only), case conflicts -> CaseConflicts, merge markers ->
+ # MergeConflicts, YAML/JSON syntax -> YamlSyntax/JsonSyntax, final
+ # newline -> FinalNewline, shebang/exec-bit consistency ->
+ # CheckExecutableShebangs.
+
+ CaseConflicts:
+ enabled: true
+
+ MergeConflicts:
+ enabled: true
+
+ TrailingWhitespace:
+ enabled: true
+ requires_files: true
+
+ JsonSyntax:
+ enabled: true
+ requires_files: true
+ exclude:
+ # search.json is a Jekyll/Liquid template with front matter,
+ # not a plain JSON file.
+ - 'assets/data/search.json'
+
+ YamlSyntax:
+ enabled: true
+ requires_files: true
+
+ FinalNewline:
+ enabled: true
+ requires_files: true
+
+ CheckExecutableShebangs:
+ enabled: true
+ requires_files: true
+
+ # Markdown style (previously markdownlint-cli2). Rules live in .mdlrc +
+ # mdl_style.rb, shared with any editor or manual `mdl` run.
+ Mdl:
+ enabled: true
+ requires_files: true
+ command: ['bundle', 'exec', 'mdl']
+ exclude:
+ # The refactorings section carries raw-HTML citation blocks (single
+ #
lines with entities/URLs) and ref-link prose that the
+ # refactoring skill writes deliberately; those can't be wrapped to
+ # 80 cols without breaking the markup. Exempt only that section.
+ - 'pages/refactorings/**/*'
+ # .claude/skills are agent-internal docs, not site prose.
+ - '.claude/**/*'
+ # The catalogue index page has the same citation conventions.
+ - 'pages/refactorings.md'
+
+ # Spelling with spellr (Ruby, replaces codespell — see
+ # .git-hooks/pre_commit/spellr.rb). Wordlist: .spellr_wordlists/.
+ Spellr:
+ enabled: true
+ requires_files: true
+
+ # Refactoring sample sources must stay within the 72-char pane width
+ # (previously scripts/check-sample-width.sh via pre-commit).
+ RefactoringSampleWidth:
+ enabled: true
+ requires_files: true
+ include:
+ - 'pages/refactorings/**/*.{scala,hs}'
diff --git a/.spellr.yml b/.spellr.yml
new file mode 100644
index 0000000..f5bb93a
--- /dev/null
+++ b/.spellr.yml
@@ -0,0 +1,29 @@
+# Spellr configuration (https://github.com/robotdana/spellr), the Ruby/bundler
+# spell checker. Checked files obey .gitignore; these excludes are for
+# Jekyll/generated trees that git ignores anyway, kept explicit so `spellr`
+# behaves identically from any directory.
+#
+# Policy: spellr checks the site's prose (Markdown, YAML/JSON data, HTML,
+# SCSS). Non-prose files are excluded: refactoring source samples (.scala .hs)
+# and scripts are code with their own vocabulary, and .claude/skills are
+# agent-internal docs full of tool-specific terms — none are site spelling.
+excludes:
+ - .git/
+ - _site/
+ - .jekyll-cache/
+ - vendor/
+ - node_modules/
+ - .venv/
+ - Gemfile.lock
+ - .claude/
+ - '*.scala'
+ - '*.hs'
+ - '*.js'
+ - 'run.sh'
+ - pages/refactorings/
+
+# Site-specific spelling lives in .spellr_wordlists/english.txt; regenerate
+# or extend it with `bundle exec spellr --wordlist`.
+languages:
+ english:
+ locale: US
diff --git a/.spellr_wordlists/english.txt b/.spellr_wordlists/english.txt
new file mode 100644
index 0000000..2cbeb5d
--- /dev/null
+++ b/.spellr_wordlists/english.txt
@@ -0,0 +1,195 @@
+addons
+adt
+affero
+agpl
+apec
+apu
+arial
+artefact
+artefacts
+async
+avro
+backend
+barcamp
+baseurl
+behaviour
+bfc
+bodireel
+btn
+catalogue
+cdn
+centred
+cet
+charset
+cli
+cname
+codebase
+codebases
+codespell
+colour
+colours
+concat
+config
+consolas
+const
+copyleft
+cov
+crlf
+cta
+cto
+ctx
+deliverables
+deps
+dirname
+dirs
+disqus
+doctype
+dotenv
+dpr
+droste
+elsif
+enablement
+endcapture
+endcase
+endcomment
+endfor
+endif
+endunless
+ensp
+env
+eslint
+eslintcache
+euo
+eval
+evals
+favicon
+fira
+fowler
+fraunces
+gadt
+gemfile
+gfx
+github
+gitignore
+glm
+globals
+gpl
+gsap
+headerless
+helvetica
+href
+ico
+img
+instituto
+intec
+ior
+javascript
+jmh
+jscoverage
+json
+jspm
+katex
+kinded
+kleisli
+kramdown
+kvk
+lambek
+linecap
+linejoin
+linkedin
+linted
+linting
+llm
+markdownlint
+mdl
+mdlrc
+memoised
+menlo
+mixin
+modernisation
+msa
+navbar
+nbsp
+nda
+neue
+newline
+newlines
+noncommercially
+noopener
+noreply
+npm
+num
+onclick
+opdyke
+opsz
+oss
+otravo
+pacifico
+peaceiris
+perceus
+permalink
+pexels
+pids
+png
+pointerleave
+pointermove
+preconnect
+programme
+pucmm
+pullquote
+readme
+recurse
+reddit
+refactorings
+refactorings
+repl
+rhansen
+rivoli
+roboto
+rodolfohansen
+rohan
+rossjrw
+rss
+runtimes
+scaffeine
+scalafix
+scoverage
+scss
+segoe
+seo
+specialised
+src
+statsub
+strix
+stylesheet
+subdirs
+svg
+sylhare
+tagless
+technik
+tecnológico
+testid
+tful
+tgz
+thinsp
+tok
+txt
+uniq
+universidad
+unrepresentable
+unstyled
+untyped
+usr
+utf
+utils
+venv
+viewport
+viewports
+visibilitychange
+webfont
+wght
+wipo
+wordlist
+wscript
+yaml
+yml
diff --git a/CNAME b/CNAME
index 5ace4e5..747f0e4 100644
--- a/CNAME
+++ b/CNAME
@@ -1 +1 @@
-www.constructive.dev
\ No newline at end of file
+www.constructive.dev
diff --git a/Gemfile b/Gemfile
index 1291498..f90b0df 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,2 +1,11 @@
source "https://rubygems.org"
-gemspec
\ No newline at end of file
+gemspec
+
+group :development do
+ # Linting harness + markdown linter. Hooks are installed with
+ # `bundle exec overcommit --install`; CI runs the same checks via
+ # scripts/lint.sh (see .overcommit.yml).
+ gem "overcommit", "~> 0.68"
+ gem "mdl", "~> 0.13"
+ gem "spellr", "~> 0.12"
+end
diff --git a/README.md b/README.md
index 51d6110..631cf59 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,32 @@
# website
-Source for [www.constructive.dev](https://www.constructive.dev). Jekyll, built on the [Type-on-Strap](https://github.com/sylhare/Type-on-Strap) remote theme, deployed to GitHub Pages from the `gh-pages` branch.
+Source for [www.constructive.dev](https://www.constructive.dev). Jekyll,
+built on the [Type-on-Strap](https://github.com/sylhare/Type-on-Strap) remote
+theme, deployed to GitHub Pages from the `gh-pages` branch.
-- **Build & deploy** (`.github/workflows/build.yml`): on push to `master`, builds with `JEKYLL_ENV=production` and force-pushes `_site/` to `gh-pages`.
-- **PR previews** (`.github/workflows/preview.yml`): each PR gets a preview at `https://www.constructive.dev/pr-preview/pr-/` via [rossjrw/pr-preview-action](https://github.com/rossjrw/pr-preview-action).
+- **Build & deploy** (`.github/workflows/build.yml`): on push to `master`,
+ builds with `JEKYLL_ENV=production` and force-pushes `_site/` to `gh-pages`.
+- **PR previews** (`.github/workflows/preview.yml`): each PR gets a preview at
+ `https://www.constructive.dev/pr-preview/pr-/` via
+ [rossjrw/pr-preview-action](https://github.com/rossjrw/pr-preview-action).
+- **Lint** (`.github/workflows/lint.yml`): runs
+ [`scripts/lint.sh`](scripts/lint.sh) — which is exactly
+ `bundle exec overcommit --run` — on every PR and push to `master`. The
+ same overcommit configuration runs locally on commit; install once, then
+ it just happens:
+
+ ```sh
+ bundle install
+ bundle exec overcommit --install
+ ```
+
+ Rules: Markdown (`mdl`, style in [`.mdlrc`](.mdlrc) +
+ [`mdl_style.rb`](mdl_style.rb)), spelling (spellr, wordlist in
+ [`.spellr_wordlists/`](.spellr_wordlists/)), hygiene checks
+ (trailing whitespace, final newline, YAML/JSON syntax, merge markers,
+ case conflicts, executable/shebang consistency) and a 72-char pane-width
+ cap on the refactoring sample sources. Run everything at any time with
+ `scripts/lint.sh`.
```sh
bundle install
diff --git a/_config.yml b/_config.yml
index 8a7b84b..068d713 100644
--- a/_config.yml
+++ b/_config.yml
@@ -11,7 +11,9 @@ favicon: assets/img/favicon.svg
# Hero
header_feature_image: ""
header_text: Constructive Programming
-header_subtitle: Ship at AI speed. Skip the gamble. Production code with AI, inside engineering guardrails that make most wrong versions impossible to build.
+header_subtitle: >-
+ Ship at AI speed. Skip the gamble. Production code with AI, inside
+ engineering guardrails that make most wrong versions impossible to build.
# Footer
footer_text: "Copyright Constructive Programming,"
diff --git a/mdl_style.rb b/mdl_style.rb
new file mode 100644
index 0000000..418e74e
--- /dev/null
+++ b/mdl_style.rb
@@ -0,0 +1,30 @@
+# mdl style for the site (loaded from .mdlrc). Start from mdl's default
+# style and adjust for the site's actual content conventions.
+
+all
+
+# Fenced code blocks may omit a language tag where it adds nothing.
+exclude_rule 'fenced-code-language'
+
+# Pages start with a heading only after their front matter (which mdl skips
+# entirely), and Liquid-driven pages have no heading at all.
+exclude_rule 'first-line-h1'
+
+# The refactoring pages carry citations and koans as raw HTML blocks (the
+# refactoring skill writes them that way because kramdown won't process
+# markdown inside raw block HTML). Inline HTML is fine here by design.
+exclude_rule 'no-inline-html'
+
+# The refactorings post steps are numbered 1., 2., 3., ...; require a
+# consistent ordered sequence instead of mdl's default "one" style.
+rule 'MD029', :style => :ordered
+
+# Headings may end in '?' (e.g. '## Not a fit?') — markdownlint's MD026
+# default punctuation (".,;:!") is what this site always used.
+rule 'MD026', :punctuation => '.,;:!'
+
+# Prose wraps at 80 chars. Code blocks, tables and headings carry their own
+# width rules (the refactoring panes are capped at 72 chars by
+# scripts/check-sample-width.sh), so exempt them here.
+rule 'MD013', :line_length => 80, :ignore_code_blocks => true,
+ :tables => false, :headings => false
diff --git a/pages/about.md b/pages/about.md
index b70233d..2c42fd9 100644
--- a/pages/about.md
+++ b/pages/about.md
@@ -22,10 +22,11 @@ criteria:
- **Termination** — every computation eventually finishes.
The methodology is the lens through which we read code, design APIs, and
-prepare a codebase to be co-piloted by LLMs. The [Libraries]({{ '/libraries/' | relative_url }})
-page lists the OSS that puts the methodology into Scala practice, and the
-home page introduces [Fixed](https://github.com/Constructive-Programming/fixed) —
-a new language being designed around the same principles.
+prepare a codebase to be co-piloted by LLMs. The
+[Libraries]({{ '/libraries/' | relative_url }}) page lists the OSS that
+puts the methodology into Scala practice, and the home page introduces
+[Fixed](https://github.com/Constructive-Programming/fixed) — a new
+language being designed around the same principles.
## The team
@@ -63,7 +64,8 @@ delivering at web scale and web speed, and constantly asking what the
type system could do to make wrong programs harder to write.
**Find Rodolfo:** [LinkedIn](https://www.linkedin.com/in/rodolfohansen/) ·
-[GitHub](https://github.com/kryptt) · [Medium](https://rodolfohansen.medium.com/)
+[GitHub](https://github.com/kryptt) ·
+[Medium](https://rodolfohansen.medium.com/)
#### Talks
@@ -104,9 +106,10 @@ banner, get in touch at
## Working with us
We work on architecture-review, fractional-advisory, embedded-IC, and
-workshops/training shapes — see the [home page]({{ '/' | relative_url }}#consulting)
-for the engagement bullets. We are available for select engagements;
-based in the Netherlands; comfortable with EU MSAs and standard NDAs.
+workshops/training shapes — see the
+[home page]({{ '/' | relative_url }}#consulting) for the engagement bullets. We
+are available for select engagements; based in the Netherlands; comfortable
+with EU MSAs and standard NDAs.
The fastest path is
[booking a 30-minute scoping call](https://cal.constructive.dev/rhansen)
diff --git a/pages/engagements.md b/pages/engagements.md
index 9121e6d..8357e04 100644
--- a/pages/engagements.md
+++ b/pages/engagements.md
@@ -9,8 +9,9 @@ tags: engagements
We have three main offerings. Each has a fixed structure so you know
what you are buying; the actual content is shaped on the scoping call.
-If your need does not fit any of the three, the [scoping call](https://cal.constructive.dev/rhansen)
-is the right place to figure it out — there is no charge for it.
+If your need does not fit any of the three, the
+[scoping call](https://cal.constructive.dev/rhansen) is the right place to
+figure it out — there is no charge for it.
## Which one is right for you
@@ -121,4 +122,5 @@ the scoping call.
## Get in touch
[Book a 30-minute scoping call](https://cal.constructive.dev/rhansen)
-or email [consulting@constructive.dev](mailto:consulting@constructive.dev?subject=Engagement%20inquiry).
+or email
+[consulting@constructive.dev](mailto:consulting@constructive.dev?subject=Engagement%20inquiry).
diff --git a/pages/libraries.md b/pages/libraries.md
index 802108e..4b7f91d 100644
--- a/pages/libraries.md
+++ b/pages/libraries.md
@@ -44,5 +44,6 @@ or moving toward agent-native development with constructive guardrails,
[book a 30-minute scoping call](https://cal.constructive.dev/rhansen)
or email
[consulting@constructive.dev](mailto:consulting@constructive.dev?subject=Scoping%20call).
-The libraries above are the ground we stand on; the [consulting offer]({{ '/#consulting' | relative_url }})
-is how we bring it to your codebase.
+The libraries above are the ground we stand on; the
+[consulting offer]({{ '/#consulting' | relative_url }}) is how we
+bring it to your codebase.
diff --git a/pages/refactorings/extract-method.md b/pages/refactorings/extract-method.md
index 63be413..3573939 100644
--- a/pages/refactorings/extract-method.md
+++ b/pages/refactorings/extract-method.md
@@ -408,6 +408,3 @@ with hedgehog installed or Docker, and ends with `all properties passed`.
Jacob Stanley and contributors. Hedgehog: release with confidence, state-of-the-art property testing. https://github.com/hedgehogqa/haskell-hedgehog and https://github.com/hedgehogqa/scala-hedgehog
Mirko Stocker. Scala Refactoring. Master’s thesis, HSR Hochschule für Technik Rapperswil, 2010. https://eprints.ost.ch/id/eprint/286/
-
-
-
diff --git a/scripts/lint.sh b/scripts/lint.sh
new file mode 100755
index 0000000..a65fc3c
--- /dev/null
+++ b/scripts/lint.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# Run the full lint suite against every tracked file. This is the exact same
+# configuration that runs locally on commit (bundle exec overcommit --install);
+# CI calls this script, so local and CI can't drift.
+set -euo pipefail
+cd "$(dirname "$0")/.."
+# OVERCOMMIT_NO_VERIFY: overcommit records hook signatures in the local
+# git config, which a fresh checkout (CI) never has, so skip that check.
+# (verify_signatures is already off in .overcommit.yml; this makes the very
+# first run work without an interactive `overcommit --sign`.)
+export OVERCOMMIT_NO_VERIFY=1
+export GIT_AUTHOR_NAME="${GIT_AUTHOR_NAME:-CI}"
+export GIT_AUTHOR_EMAIL="${GIT_AUTHOR_EMAIL:-ci@example.com}"
+exec bundle exec overcommit --run
diff --git a/type-on-strap.gemspec b/type-on-strap.gemspec
index ee7ab36..637bd65 100644
--- a/type-on-strap.gemspec
+++ b/type-on-strap.gemspec
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
* Use _data/ for social and language customization
- * Vist https://github.com/sylhare/Type-on-Strap
+ * Visit https://github.com/sylhare/Type-on-Strap
for more info.
----------------------------------------------------------