Skip to content
Open
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
411 changes: 308 additions & 103 deletions .claude/skills/records/SKILL.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions changes/record-object-kinds.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Draw commit records as semantic diffs of the compiler's objects, each kind of object in its own drawing, and extract the exact forms a record abstracts from the compiler with `scripts/recordFacts.js`. https://github.com/rescript-lang/rescript/pull/8882
74 changes: 74 additions & 0 deletions scripts/commitRecordHook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/bash

# commit-msg hook: the commit message is a commit record
# (.claude/skills/records/SKILL.md). Every commit carries the required
# trailers Area, Output, Tests and Signed-off-by, with Signed-off-by last; a
# commit that changes the compiler, the runtime, the build system or the
# editor tooling also has a Why: section and at least one change block naming
# the object it changes. Enable it in a checkout with
# ln -sf "$PWD/scripts/commitRecordHook.sh" "$(git rev-parse --git-path hooks)/commit-msg"

exec 1>&2

message_file=$1
git_dir=$(git rev-parse --git-dir)

# a merge commit has no record of its own
if [[ -f "$git_dir/MERGE_HEAD" ]]; then
exit 0
fi

body=$(grep -v '^#' "$message_file")
# fixup! and squash! commits are folded into a recorded commit
case "$body" in
fixup!*|squash!*|amend!*) exit 0 ;;
esac

trailers=$(printf '%s\n' "$body" | git interpret-trailers --parse)
value() {
printf '%s\n' "$trailers" | sed -n "s/^$1: //p" | head -n 1
}

problems=()
for key in Area Output Tests Signed-off-by; do
if ! printf '%s\n' "$trailers" | grep -q "^$key: "; then
problems+=("no '$key:' trailer")
fi
done
last=$(printf '%s\n' "$trailers" | tail -n 1)
if [[ -n "$last" && "$last" != Signed-off-by:* ]]; then
problems+=("the last trailer is '${last%%:*}'; Signed-off-by is the last trailer")
fi

output=$(value Output)
case "$output" in
unchanged*|changed*|"") ;;
*) problems+=("Output '$output' starts with neither 'unchanged' nor 'changed'") ;;
esac
tests=$(value Tests)
case "$tests" in
"none, because "?*|[+~-]?*|"") ;;
none*) problems+=("Tests: none gives its reason as 'none, because <reason>'") ;;
*) problems+=("Tests lists tests as +added ~changed -removed paths, or is 'none, because <reason>'") ;;
esac

if git diff --cached --name-only |
grep -Eq '^(compiler|rewatch/src|analysis|tools/src|packages/@rescript/(runtime|belt))/'; then
Comment on lines +55 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce records for the OCaml build system

Include rewatch-ocaml/ in this source-path predicate. When a commit stages a file such as rewatch-ocaml/build_state.ml, the condition is false, so the new hook accepts the commit without the required Why: section or change block even though this directory contains the OCaml build-system implementation.

AGENTS.md reference: AGENTS.md:L35-L38

Useful? React with 👍 / 👎.

kinds='Syntax|Format|AST|Transform|Typing|Typedtree|Diagnostic|Matching|Lambda|IR|Representation|JS|API|TypeScript|Editor|Build|Semantics|Code'
if ! printf '%s\n' "$body" | grep -q '^Why:'; then
problems+=("no 'Why:' section (the problem this commit addresses)")
fi
if ! printf '%s\n' "$body" | grep -Eq "^($kinds) (added|modified|removed)( \(.*\))?:"; then
problems+=("no change block such as 'Typing modified (rule name):' or 'Code added (module):'")
fi
fi

if (( ${#problems[@]} )); then
echo "The commit message is not a commit record:"
for p in "${problems[@]}"; do
echo " - $p"
done
echo "Format: .claude/skills/records/SKILL.md. The trailers are the last block of"
echo "the message, one 'Key: value' per line; git interpret-trailers --parse reads them."
exit 1
fi
Loading
Loading