Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .mintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,39 @@
# — but in practice the dev server still tries to MDX-parse files inside
# .github (e.g. pull_request_template.md, which uses HTML <!-- comments),
# so list it explicitly.
#
# The same trap catches the repo-meta markdown at the root. `mint
# broken-links` MDX-parses it, and MDX cannot represent an HTML comment
# — it fails the whole run with a hard "Syntax error … Unexpected
# character `!`", not a warning. CLAUDE.md hit this the moment the
# org-standards sync from tracebloc/.github wrote its <!-- org-standards:begin -->
# markers into it — and so `make check`, and the pre-push hook that runs
# it, were red on develop from the day they were added.
#
# Measured, not assumed — each candidate was probed by appending an HTML
# comment and re-running `mint broken-links` (2026-08-14, mint 4.2.802):
#
# CLAUDE.md PARSED -> listed below (this is the one that broke)
# AGENTS.md PARSED -> listed below
# TERMINOLOGY.md PARSED -> listed below
# .cursor/BUGBOT.md PARSED -> listed below (via .cursor/)
# README.md skipped -> auto-ignore genuinely holds here
# CONTRIBUTING.md skipped -> auto-ignore genuinely holds here
# .claude/** skipped -> auto-ignore genuinely holds here
#
# None of these are pages: no root markdown file appears in docs.json
# navigation, so ignoring them checks nothing less. They are listed
# pre-emptively rather than reactively — any of them can grow an HTML
# comment (the org-standards sync is automated and will keep writing
# them), and the failure mode is a red run for every contributor, not a
# local nuisance.
.github/
.github/**
.cursor/
.cursor/**
CLAUDE.md
AGENTS.md
TERMINOLOGY.md

# Draft content
drafts/
Expand Down
163 changes: 161 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,23 @@ help:
@echo " check mint broken-links (~2 s) — run this before every push"
@echo " check-all check + the SDK install-extras gate (needs network)"
@echo " setup npm i -g mint"
@echo " install-hooks (re)install the git pre-push hook that runs 'make check'"
@echo
@echo " dev mint dev — local preview on http://localhost:3000"

# ---- check -------------------------------------------------------
#
# MEASURED at 1.8 s on the current tree, and green.
# MEASURED at 1.8 s warm (2.9 s cold) on the current tree, and green.
#
# The "and green" half of that claim was false from the day it was
# written, and is only true again as of this commit. `mint broken-links`
# MDX-parses the repo-meta markdown at the root, and the org-standards
# sync had already written HTML comments — which MDX cannot represent —
# into CLAUDE.md on 2026-08-10, the day before this target landed. So
# `make check` was red on develop for its entire life up to here, and the
# pre-push hook that runs it would have blocked every push. Fixed by
# listing those files in .mintignore, which carries the reasoning and the
# per-file measurements.
#
# This repo has no test suite. The automated checks on a docs PR are the
# org-shared ones (gitleaks + house-rules, which need the shared checker
Expand All @@ -42,8 +53,12 @@ help:
# `mint broken-links` is what catches a bad link before pushing. It is
# also the check that would have caught the class of problem
# preview-page-coverage.yml exists to catch, one step earlier.
#
# The prerequisite is spelled `guard-toolchain` — the uniform name the pre-push
# hook asks for before it runs `make check` (backend#1995) — and in this repo
# that is guard-mint.
.PHONY: check
check: guard-mint
check: guard-toolchain
$(MINT) broken-links

# check-all adds the one gate that needs network: every documented
Expand All @@ -65,6 +80,7 @@ check-sdk-extras:
setup:
npm i -g mint
@echo "==> setup: mint installed; run 'make check'"
@$(MAKE) --no-print-directory install-hooks

# ---- individual targets ------------------------------------------

Expand All @@ -75,7 +91,150 @@ guard-mint:
echo " npm i -g mint (or: make setup)"; \
exit 1; }

# guard-toolchain: is the toolchain `check` needs on PATH at all?
#
# The uniform name every repo exposes, so the pre-push hook can ask one
# question — "can this shell run check at all?" — and skip itself on failure
# rather than hard-failing the push on "mint: command not found"
# (backend#1995). GUI/IDE git clients launch hooks with a minimal PATH, where
# /usr/bin/make is present but `npm i -g mint` output is not.
#
# Reuses guard-mint rather than making a second copy of that check: `check`
# depends on this target, so the mint probe lives in exactly one place and
# cannot drift as `check` changes. (`check-all` additionally needs python3,
# which is a base-system tool and is not gated here; the hook only runs
# `check`.)
#
# node is checked too, because mint IS a node program — its bin is a
# `#!/usr/bin/env node` script. mint on PATH without node on PATH is a REAL
# combination: `npm i -g mint` can land the shim in a prefix a GUI hook shell
# has while nvm/fnm/volta put node somewhere it does not. Then mint is found and
# dies with "env: node: No such file or directory", exit 127 — the same hard
# failure backend#1995 is about.
#
# TOOLS, NOT DEPENDENCIES: it asks whether mint and node are on PATH, never
# whether the docs tree is in a state mint would accept. A broken link is a real
# failure and must NOT be skipped just because "cannot run" and "runs and fails"
# look similar from the outside.
.PHONY: guard-toolchain
guard-toolchain: guard-mint
@command -v node >/dev/null 2>&1 || { \
echo "node is not on PATH — the Mintlify CLI is a Node program; install Node, then:"; \
echo " make setup"; \
exit 1; }

# dev: local preview, per README.md.
.PHONY: dev
dev: guard-mint
$(MINT) dev

# install-hooks: put a pre-push hook in place that runs `make check`, so the
# canon's "run the tests before you push" is carried by the tooling rather than
# by memory. Factored out of `setup` so it is independently runnable and
# testable, and so a contributor who only wants the hook need not rerun the
# full `make setup`.
#
# Honest by design: the hook catches FORGETTING, not defiance — `git push
# --no-verify` skips it and always will. And it refuses to clobber a pre-push
# hook that is already there and not ours (e.g. one the pre-commit framework
# manages), rather than silently stomping a contributor's setup.
#
# `git rev-parse --git-path hooks` (not a hard-coded `.git/hooks`) so it lands
# in the right place inside a linked worktree or a submodule, where the git dir
# is not `.git`.
#
# The core.hooksPath guard below resolves the HOOKS DIRECTORY ITSELF, not its
# parent (frontend-app#809). Two cases the parent-based version got wrong:
#
# symlink core.hooksPath=.githooks where .githooks is a symlink to a shared
# dir. `dirname` is the checkout root, which resolves in-repo, so it
# installed — and the write went THROUGH the symlink into the shared
# dir. The one path element that can point elsewhere was the only
# one never resolved.
# worktree a linked worktree whose core.hooksPath is the main repo's
# .git/hooks. The parent is outside the worktree's toplevel, so it
# skipped — even though that directory belongs to the SAME
# repository, and the worktree silently got no hook.
#
# So: resolve the hooks dir itself when it exists, else the deepest existing
# ancestor (there is no symlink left to resolve below that), and count it as
# in-repo if it is under EITHER the worktree toplevel OR the repo's common git
# dir. The common-git-dir arm is what fixes the worktree case without
# re-opening the shared-dir case the guard legitimately exists to catch. An
# unresolvable path still skips: "cannot tell" is not evidence that it is ours.
.PHONY: install-hooks
install-hooks:
@if ! git rev-parse --git-dir >/dev/null 2>&1; then \
echo "note: not a git checkout — skipping pre-push hook install"; \
elif hp="$$(git config --get core.hooksPath 2>/dev/null || true)"; [ -n "$$hp" ] && { \
hd="$$(git rev-parse --git-path hooks)"; \
case "$$hd" in /*) hdd="$$hd";; *) hdd="$$PWD/$$hd";; esac; \
hdx="$$hdd"; \
while [ ! -d "$$hdx" ] && [ "$$hdx" != "$$(dirname "$$hdx")" ]; do \
hdx="$$(dirname "$$hdx")"; \
done; \
chd="$$(cd "$$hdx" 2>/dev/null && pwd -P || true)"; \
ctop="$$(cd "$$(git rev-parse --show-toplevel)" && pwd -P)"; \
cgd="$$(cd "$$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P || true)"; \
inr=0; \
case "$$chd/" in "$$ctop/"*) inr=1;; esac; \
if [ -n "$$cgd" ]; then case "$$chd/" in "$$cgd/"*) inr=1;; esac; fi; \
[ -z "$$chd" ] || [ "$$inr" = 0 ]; \
}; then \
echo "note: core.hooksPath is set to '$$hp' (resolves to '$$chd'), outside this repo — skipping."; \
echo " That is a shared hooks dir; installing here would run 'make check' from every repo you push."; \
echo " Add 'make check' to that hook by hand if you want it everywhere."; \
else \
hook="$$(git rev-parse --git-path hooks)/pre-push"; \
if [ -e "$$hook" ] && ! grep -q 'tracebloc pre-push hook' "$$hook" 2>/dev/null; then \
echo "note: $$hook already exists and is not ours — leaving it untouched."; \
echo " add 'make check' to it, or remove it and re-run 'make install-hooks'."; \
else \
mkdir -p "$$(dirname "$$hook")" && \
printf '%s\n' \
'#!/bin/sh' \
'# tracebloc pre-push hook installed by make setup (backend#1606).' \
'# Runs make check so a push that would be red in CI is caught locally first.' \
'# It catches forgetting, not defiance: git push --no-verify skips it.' \
'#' \
'# Nothing to check on a delete/no-op push: a branch delete streams a' \
'# local sha of all-zeros on stdin (no new commits). Skip so a red tree' \
'# cannot block "git push --delete", and cleanup pushes stay free.' \
'z=0000000000000000000000000000000000000000' \
'had_update=0' \
'while read -r _ local_sha _ _; do' \
' [ "$$local_sha" != "$$z" ] && had_update=1' \
'done' \
'[ "$$had_update" = 0 ] && exit 0' \
'#' \
'# Degrade gracefully when the toolchain is absent: GUI/IDE git clients' \
'# (Tower, GitKraken, VS Code) launch hooks with a minimal PATH, so make' \
'# may be missing — and several do not expose --no-verify. Skipping beats' \
'# hard-blocking every push with "make: command not found".' \
'command -v make >/dev/null 2>&1 || exit 0' \
'#' \
'# Git exports GIT_DIR/GIT_WORK_TREE/etc into hook processes; a nested git' \
'# invocation (from a test, tool, or setuptools-scm) then fails in a linked' \
'# worktree with exit status 128. Clear them so the make runs below behave' \
'# as they do from an ordinary shell.' \
'unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX GIT_COMMON_DIR GIT_OBJECT_DIRECTORY' \
'#' \
'# Guarding on make alone was not enough (backend#1995): /usr/bin/make ships' \
'# with the Xcode CLT and sits on the default launchd PATH, while the tools' \
'# make check actually runs — yarn/node via nvm/fnm/volta, mint via npm -g —' \
'# are put on PATH by shell rc files this hook shell never sources. So the' \
'# skip above passed and the push then hard-failed on "command not found":' \
'# exactly the outcome the skip exists to prevent, and VS Code offers no' \
'# --no-verify on push.' \
'#' \
'# Ask the Makefile rather than restating the tool list here: guard-toolchain' \
'# is a prerequisite of check itself, so it cannot drift when check gains a' \
'# dependency. It guards on the TOOLS, not on installed dependencies — a' \
'# missing node_modules is a real failure and must not be skipped.' \
'make guard-toolchain >/dev/null 2>&1 || exit 0' \
'exec make check' > "$$hook" && \
chmod +x "$$hook" && \
echo "==> pre-push hook installed at $$hook" && \
echo " 'make check' now runs before each push (skip once with: git push --no-verify)"; \
fi; \
fi
Loading