Skip to content

Commit 74e4489

Browse files
NagyViktNagyViktclaude
authored
fix(gx): enforce symlink parity pre-commit + document scripts layout convention (#553)
G5 — Symlink parity check now runs pre-commit .githooks/pre-commit (the gitguardex local shim) now invokes scripts/check-script-symlinks.sh before delegating to `gx hook run pre-commit`. A contributor who accidentally replaces a paired symlink with a regular file gets instant local feedback instead of waiting for CI to fail minutes later. The check is guarded by an existence test on scripts/check-script-symlinks.sh, so consumer repos (where that script is not scaffolded) silently skip the parity step. G6 — Document the scripts/ ↔ templates/scripts/ layout convention A long comment above TEMPLATE_FILES in src/context.js spells out the two patterns that coexist in this repo, eliminating the "which side is canonical?" ambiguity that motivated G6: 1. PAIRED files (10): tracked on both sides; scripts/<file> is a symlink into ../templates/scripts/<file> per PR #548. Edit the templates/ copy. 2. SCAFFOLD-ONLY files: tracked only under templates/; scaffolded into gitignored scripts/<file> by `gx setup`. Edit the templates/ copy. Future contributors adding a new file know exactly which list to update (check-script-symlinks.sh's required_symlinks vs the multiagent-safety .gitignore block). Verified G5 live: breaking a symlink and re-running the hook produces the FAIL output + "aborting commit" exit 1, matching the CI behavior. Co-authored-by: NagyVikt <nagy.viktordp@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7d8a7ca commit 74e4489

4 files changed

Lines changed: 53 additions & 0 deletions

File tree

.githooks/pre-commit

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4+
# When run inside the gitguardex repo itself, also enforce the
5+
# scripts/ <-> templates/scripts/ symlink parity invariant locally.
6+
# CI (.github/workflows/ci.yml) runs the same check; this gives
7+
# instant local feedback on broken symlinks instead of waiting for
8+
# the PR check to fail minutes later. Consumer repos don't have
9+
# scripts/check-script-symlinks.sh (it's not in the scaffold list),
10+
# so the test below silently noops there.
11+
hook_repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
12+
if [[ -n "$hook_repo_root" && -x "${hook_repo_root}/scripts/check-script-symlinks.sh" ]]; then
13+
if ! "${hook_repo_root}/scripts/check-script-symlinks.sh"; then
14+
echo "[gitguardex-shim] scripts/<->templates/scripts/ symlink parity check failed; aborting commit." >&2
15+
exit 1
16+
fi
17+
fi
18+
419
if [[ -n "${GUARDEX_CLI_ENTRY:-}" ]]; then
520
node_bin="${GUARDEX_NODE_BIN:-node}"
621
exec "$node_bin" "$GUARDEX_CLI_ENTRY" 'hook' 'run' 'pre-commit' "$@"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-11
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# agent-claude-g5-g6-symlink-precommit-gitignore-cleanu-2026-05-11-12-34 (minimal / T1)
2+
3+
Branch: `agent/<your-name>/<branch-slug>`
4+
5+
Describe the change in a sentence or two. Commit message is the spec of record.
6+
7+
## Handoff
8+
9+
- Handoff: change=`agent-claude-g5-g6-symlink-precommit-gitignore-cleanu-2026-05-11-12-34`; branch=`agent/<your-name>/<branch-slug>`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`.
10+
- Copy prompt: Continue `agent-claude-g5-g6-symlink-precommit-gitignore-cleanu-2026-05-11-12-34` on branch `agent/<your-name>/<branch-slug>`. Work inside the existing sandbox, review `openspec/changes/agent-claude-g5-g6-symlink-precommit-gitignore-cleanu-2026-05-11-12-34/notes.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`.
11+
12+
## Cleanup
13+
14+
- [ ] Run: `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`
15+
- [ ] Record PR URL + `MERGED` state in the completion handoff.
16+
- [ ] Confirm sandbox worktree is gone (`git worktree list`, `git branch -a`).

src/context.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,26 @@ function toDestinationPath(relativeTemplatePath) {
137137
throw new Error(`Unsupported template path: ${relativeTemplatePath}`);
138138
}
139139

140+
// scripts/ ↔ templates/scripts/ layout convention (single source of truth):
141+
//
142+
// 1. PAIRED files (10): tracked on both sides; scripts/<file> is a symlink
143+
// to ../templates/scripts/<file> per PR #548. See
144+
// scripts/check-script-symlinks.sh for the exact list. CI + the
145+
// .githooks/pre-commit shim both enforce that no symlink is ever
146+
// replaced with a regular file. Edit only the templates/scripts/ copy;
147+
// the symlink propagates.
148+
//
149+
// 2. SCAFFOLD-ONLY files (the 4 below + workflows + vscode extension):
150+
// tracked only under templates/; scaffolded into gitignored
151+
// scripts/<file> (or .githooks/<file>, etc.) by `gx setup`. Consumer
152+
// repos receive a regular file copy at the destination; gitguardex
153+
// itself receives the same copy and ignores it via the
154+
// multiagent-safety .gitignore block. Edit only the templates/ copy.
155+
//
156+
// If a file you're about to add fits pattern (1), also add it to
157+
// scripts/check-script-symlinks.sh's required_symlinks list. If it fits
158+
// pattern (2), append the destination path to .gitignore's multiagent-
159+
// safety block (auto-managed by syncManagedGitignoreLines below).
140160
const TEMPLATE_FILES = [
141161
'scripts/agent-session-state.js',
142162
'scripts/guardex-docker-loader.sh',

0 commit comments

Comments
 (0)