diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100755
index 000000000..38f701798
--- /dev/null
+++ b/.githooks/pre-commit
@@ -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
diff --git a/.githooks/pre-push b/.githooks/pre-push
new file mode 100755
index 000000000..2d9862c60
--- /dev/null
+++ b/.githooks/pre-push
@@ -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 --exec \"git commit --amend --no-edit --author='Your Name '\""
+ } >&2
+ exit 1
+fi
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 46b58614b..a39f8d133 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0defdbba0..b0a7525f6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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:
diff --git a/package.json b/package.json
index 637e61e2f..4d3156ab9 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/sdk/package.json b/sdk/package.json
index 259ed530e..aef1cc219 100644
--- a/sdk/package.json
+++ b/sdk/package.json
@@ -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",
diff --git a/tests/precommit-alias-drift-hook.test.cjs b/tests/precommit-alias-drift-hook.test.cjs
new file mode 100644
index 000000000..472ce9f21
--- /dev/null
+++ b/tests/precommit-alias-drift-hook.test.cjs
@@ -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');
+ });
+});
diff --git a/tests/prepush-enterprise-email-hook.test.cjs b/tests/prepush-enterprise-email-hook.test.cjs
new file mode 100644
index 000000000..81b3893d6
--- /dev/null
+++ b/tests/prepush-enterprise-email-hook.test.cjs
@@ -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',
+ });
+ });
+});