ci(drift): enforce alias freshness checks in CI and contributor flow (#2910)

Merging alias-drift guardrails and local hook hardening.
This commit is contained in:
Tom Boucher
2026-04-30 14:19:46 -04:00
committed by GitHub
parent 8051bc4fd8
commit 006cdafe8f
8 changed files with 292 additions and 0 deletions

6
.githooks/pre-commit Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.generated\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
npm run check:alias-drift
fi

48
.githooks/pre-push Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
zero_sha='0000000000000000000000000000000000000000'
blocked_regex="${GSD_BLOCKED_AUTHOR_REGEX:-}"
# Local-only guard: no-op unless the developer opts in via env var, e.g.
# export GSD_BLOCKED_AUTHOR_REGEX='@example-corp\.com$'
if [[ -z "$blocked_regex" ]]; then
exit 0
fi
violations=()
while read -r local_ref local_sha remote_ref remote_sha; do
# branch/tag deletion
if [[ "$local_sha" == "$zero_sha" ]]; then
continue
fi
if [[ "$remote_sha" == "$zero_sha" ]]; then
# New remote ref: inspect commits not already on any remote
commit_list=$(git rev-list "$local_sha" --not --remotes)
else
commit_list=$(git rev-list "$remote_sha..$local_sha")
fi
while read -r commit; do
[[ -z "$commit" ]] && continue
author_email=$(git show -s --format='%ae' "$commit")
lower_email=$(printf '%s' "$author_email" | tr '[:upper:]' '[:lower:]')
if printf '%s' "$lower_email" | grep -Eq "$blocked_regex"; then
violations+=("$commit <$author_email>")
fi
done <<< "$commit_list"
done
if [[ ${#violations[@]} -gt 0 ]]; then
{
echo "Push blocked: commit author email matched local blocked regex ($blocked_regex)."
echo "Rewrite author info before pushing these commits:"
for v in "${violations[@]}"; do
echo " - $v"
done
echo "Suggested fix: git rebase -i <base> --exec \"git commit --amend --no-edit --author='Your Name <non-enterprise@email>'\""
} >&2
exit 1
fi

View File

@@ -88,6 +88,18 @@ jobs:
- name: Build SDK dist (required by installer)
run: npm run build:sdk
# Seam contract gate: keep manifest -> generated aliases -> registry/CJS adapters aligned.
# Run once per workflow on the primary Linux node to avoid redundant matrix cost.
- name: SDK seam coverage tests
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 24
shell: bash
run: cd sdk && npx vitest run src/query/command-seam-coverage.test.ts
- name: SDK generated alias artifact drift check
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 24
shell: bash
run: node sdk/scripts/check-command-aliases-fresh.mjs
- name: Run tests with coverage
shell: bash
run: npm run test:coverage

View File

@@ -345,6 +345,73 @@ node --test tests/core.test.cjs
npm run test:coverage
```
### Pre-PR Seam Checks (Manifest/Alias Routing)
If you touched any of the command-manifest or generated alias files, run:
```bash
npm run check:alias-drift
```
This verifies generated alias artifacts are in sync with manifest source-of-truth.
Optional local pre-commit hook entry (Git-native):
```bash
# one-time setup
mkdir -p .githooks
cat > .githooks/pre-commit <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.generated\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
npm run check:alias-drift
fi
EOF
chmod +x .githooks/pre-commit
git config core.hooksPath .githooks
```
Optional local pre-push hook to block a private author-email pattern:
```bash
# set locally in your shell profile (example)
export GSD_BLOCKED_AUTHOR_REGEX='@example-corp\\.com$'
cat > .githooks/pre-push <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
zero_sha='0000000000000000000000000000000000000000'
blocked_regex="${GSD_BLOCKED_AUTHOR_REGEX:-}"
[[ -z "$blocked_regex" ]] && exit 0
violations=()
while read -r local_ref local_sha remote_ref remote_sha; do
[[ "$local_sha" == "$zero_sha" ]] && continue
if [[ "$remote_sha" == "$zero_sha" ]]; then
commits=$(git rev-list "$local_sha" --not --remotes)
else
commits=$(git rev-list "$remote_sha..$local_sha")
fi
while read -r commit; do
[[ -z "$commit" ]] && continue
email=$(git show -s --format='%ae' "$commit" | tr '[:upper:]' '[:lower:]')
if printf '%s' "$email" | grep -Eq "$blocked_regex"; then
violations+=("$commit <$email>")
fi
done <<< "$commits"
done
if [[ ${#violations[@]} -gt 0 ]]; then
echo "Push blocked: commit author email matched local blocked regex ($blocked_regex)." >&2
printf ' - %s\n' "${violations[@]}" >&2
exit 1
fi
EOF
chmod +x .githooks/pre-push
```
### CI Test Quality Checks
The following checks run on every PR in addition to the test suite:

View File

@@ -56,6 +56,7 @@
"scripts": {
"build:hooks": "node scripts/build-hooks.js",
"build:sdk": "cd sdk && npm ci && npm run build",
"check:alias-drift": "cd sdk && npm run check:alias-drift",
"prepublishOnly": "npm run build:hooks && npm run build:sdk",
"pretest": "npm run build:sdk",
"pretest:coverage": "npm run build:sdk",

View File

@@ -34,6 +34,7 @@
},
"scripts": {
"build": "tsc",
"check:alias-drift": "npm run build && node scripts/check-command-aliases-fresh.mjs",
"prepublishOnly": "rm -rf dist && tsc && chmod +x dist/cli.js",
"test": "vitest run",
"test:unit": "vitest run --project unit",

View File

@@ -0,0 +1,67 @@
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const { createTempDir, cleanup } = require('./helpers.cjs');
const ROOT = path.resolve(__dirname, '..');
const HOOK_PATH = path.join(ROOT, '.githooks', 'pre-commit');
function writeExec(filePath, content) {
fs.writeFileSync(filePath, content, { mode: 0o755 });
}
describe('.githooks/pre-commit alias drift guard', () => {
test('runs npm check when staged files include command-manifest/alias artifacts', (t) => {
const tmpDir = createTempDir('gsd-precommit-hook-');
t.after(() => cleanup(tmpDir));
const binDir = path.join(tmpDir, 'bin');
fs.mkdirSync(binDir, { recursive: true });
writeExec(path.join(binDir, 'git'), `#!/usr/bin/env bash\nprintf "%s\\n" "${'sdk/src/query/command-manifest.phase.ts'}"\n`);
writeExec(path.join(binDir, 'npm'), `#!/usr/bin/env bash\nprintf "called" > "$GSD_TEST_NPM_MARKER"\n`);
const marker = path.join(tmpDir, 'npm-called.txt');
execFileSync('bash', [HOOK_PATH], {
cwd: ROOT,
env: {
...process.env,
PATH: `${binDir}:${process.env.PATH}`,
GSD_TEST_NPM_MARKER: marker,
},
stdio: 'pipe',
});
assert.ok(fs.existsSync(marker), 'expected npm run check:alias-drift to be invoked');
});
test('does not run npm check when staged files are unrelated', (t) => {
const tmpDir = createTempDir('gsd-precommit-hook-');
t.after(() => cleanup(tmpDir));
const binDir = path.join(tmpDir, 'bin');
fs.mkdirSync(binDir, { recursive: true });
writeExec(path.join(binDir, 'git'), `#!/usr/bin/env bash\nprintf "%s\\n" "README.md"\n`);
writeExec(path.join(binDir, 'npm'), `#!/usr/bin/env bash\nprintf "called" > "$GSD_TEST_NPM_MARKER"\n`);
const marker = path.join(tmpDir, 'npm-called.txt');
execFileSync('bash', [HOOK_PATH], {
cwd: ROOT,
env: {
...process.env,
PATH: `${binDir}:${process.env.PATH}`,
GSD_TEST_NPM_MARKER: marker,
},
stdio: 'pipe',
});
assert.ok(!fs.existsSync(marker), 'expected npm check to be skipped for unrelated staged files');
});
});

View File

@@ -0,0 +1,90 @@
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const { createTempDir, cleanup } = require('./helpers.cjs');
const ROOT = path.resolve(__dirname, '..');
const HOOK_PATH = path.join(ROOT, '.githooks', 'pre-push');
function writeExec(filePath, content) {
fs.writeFileSync(filePath, content, { mode: 0o755 });
}
describe('.githooks/pre-push enterprise email guard', () => {
test('blocks push when any to-be-pushed commit matches local blocked regex', (t) => {
const tmpDir = createTempDir('gsd-prepush-hook-');
t.after(() => cleanup(tmpDir));
const binDir = path.join(tmpDir, 'bin');
fs.mkdirSync(binDir, { recursive: true });
writeExec(path.join(binDir, 'git'), `#!/usr/bin/env bash
set -euo pipefail
if [[ "$1" == "rev-list" ]]; then
echo "c1"
echo "c2"
exit 0
fi
if [[ "$1" == "show" ]]; then
commit="$(printf '%s\n' "$@" | tail -n 1)"
if [[ "$commit" == "c1" ]]; then
echo "trekkie@nomorestars.com"
else
echo "person@example-corp.com"
fi
exit 0
fi
exit 1
`);
assert.throws(() => {
execFileSync('bash', [HOOK_PATH], {
cwd: ROOT,
env: {
...process.env,
PATH: `${binDir}:${process.env.PATH}`,
GSD_BLOCKED_AUTHOR_REGEX: '@example-corp\\.com$',
},
input: 'refs/heads/pr refs-local-sha refs/heads/pr refs-remote-sha\n',
stdio: 'pipe',
});
}, /Push blocked: commit author email matched local blocked regex/);
});
test('allows push when to-be-pushed commits are non-enterprise emails', (t) => {
const tmpDir = createTempDir('gsd-prepush-hook-');
t.after(() => cleanup(tmpDir));
const binDir = path.join(tmpDir, 'bin');
fs.mkdirSync(binDir, { recursive: true });
writeExec(path.join(binDir, 'git'), `#!/usr/bin/env bash
set -euo pipefail
if [[ "$1" == "rev-list" ]]; then
echo "c1"
echo "c2"
exit 0
fi
if [[ "$1" == "show" ]]; then
echo "trekkie@nomorestars.com"
exit 0
fi
exit 1
`);
execFileSync('bash', [HOOK_PATH], {
cwd: ROOT,
env: {
...process.env,
PATH: `${binDir}:${process.env.PATH}`,
GSD_BLOCKED_AUTHOR_REGEX: '@example-corp\\.com$',
},
input: 'refs/heads/pr refs-local-sha refs/heads/pr refs-remote-sha\n',
stdio: 'pipe',
});
});
});