Replace legacy scripts with separated ots/node hooks

- Remove deprecated scripts/ directory (6 files)
- Add hooks/post-commit-ots (ots CLI version)
- Add hooks/pre-commit-ots (ots CLI backfill)
- Add hooks/post-commit-node (Node.js version, local proofs only)
- Add hooks/pre-commit-node (Node.js backfill, no upgrade)
- Update hooks/install.sh to auto-detect and install matching hooks
- Update documentation (SKILL.md, README.md, AGENTS.md)
- Add node_modules/ to .gitignore for Node.js mode

Breaking change: Scripts removed, use new hooks/ directory.
This commit is contained in:
Otto 2026-03-08 00:38:43 +01:00
parent eec64d16c6
commit b1e305d831
14 changed files with 484 additions and 616 deletions

View file

@ -1,6 +1,6 @@
#!/bin/bash
# Git OpenTimestamp Hooks Installer
# Copies self-contained hooks to git repository
# Detects available tools and installs matching hooks
set -e
@ -9,21 +9,50 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$REPO_PATH"
# Verify we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "Error: not a git repository" >&2
exit 1
fi
HOOKS_DIR="$(git rev-parse --git-dir)/hooks"
MODE=""
echo "Installing OpenTimestamp hooks to: $HOOKS_DIR"
# Detect available tools
if command -v ots &> /dev/null; then
MODE="ots"
echo "Detected: ots CLI"
elif command -v node &> /dev/null && node -e "require('@opentimestamps/ots')" &> /dev/null 2>&1; then
MODE="node"
echo "Detected: @opentimestamps/ots (Node.js)"
echo "Note: Node version creates local proofs only (no calendar submission)"
echo ""
# Install node package locally if not already present
if [ ! -d "node_modules/@opentimestamps" ]; then
echo "Installing @opentimestamps/ots locally..."
npm install @opentimestamps/ots
echo "✓ Package installed"
fi
else
echo "Error: Neither ots CLI nor @opentimestamps/ots found" >&2
echo "Install one of:" >&2
echo " pipx install opentimestamps-client (recommended)" >&2
echo " npm install @opentimestamps/ots (local proofs only)" >&2
exit 1
fi
# Copy hooks
cp "$SCRIPT_DIR/post-commit" "$HOOKS_DIR/post-commit"
cp "$SCRIPT_DIR/pre-commit" "$HOOKS_DIR/pre-commit"
echo ""
echo "Installing $MODE hooks to: $HOOKS_DIR"
# Copy appropriate hooks
if [ "$MODE" = "ots" ]; then
cp "$SCRIPT_DIR/post-commit-ots" "$HOOKS_DIR/post-commit"
cp "$SCRIPT_DIR/pre-commit-ots" "$HOOKS_DIR/pre-commit"
else
cp "$SCRIPT_DIR/post-commit-node" "$HOOKS_DIR/post-commit"
cp "$SCRIPT_DIR/pre-commit-node" "$HOOKS_DIR/pre-commit"
fi
# Make executable
chmod +x "$HOOKS_DIR/post-commit" "$HOOKS_DIR/pre-commit"
echo "✓ Post-commit hook installed"
@ -32,19 +61,33 @@ echo "✓ Pre-commit backfill hook installed"
# Setup .gitignore
GITIGNORE=".gitignore"
if [ ! -f "$GITIGNORE" ]; then
echo ".ots/.attestation-cache" > "$GITIGNORE"
cat > "$GITIGNORE" << 'EOF'
.ots/.attestation-cache
node_modules/
EOF
echo "✓ Created .gitignore"
elif ! grep -q ".ots/.attestation-cache" "$GITIGNORE"; then
echo "" >> "$GITIGNORE"
echo ".ots/.attestation-cache" >> "$GITIGNORE"
if [ "$MODE" = "node" ] && ! grep -q "node_modules/" "$GITIGNORE"; then
echo "node_modules/" >> "$GITIGNORE"
fi
echo "✓ Updated .gitignore"
fi
echo ""
echo "Mode: $MODE"
if [ "$MODE" = "ots" ]; then
echo " - Full Bitcoin attestation via calendars"
echo " - Proofs submitted to remote calendars"
else
echo " - Local proofs only (no calendar submission)"
echo " - For full attestation, use: pipx install opentimestamps-client"
fi
echo ""
echo "Next steps:"
echo "1. Make a test commit to verify hooks work"
echo "2. Commit the .ots/ directory: git add .ots/"
echo "3. Commit with message: git commit -m 'Add OpenTimestamp proofs'"
echo "1. Make a test commit"
echo "2. Commit proofs: git add .ots/ && git commit -m 'Add OpenTimestamp proofs'"
echo ""
echo "To uninstall:"
echo " rm $HOOKS_DIR/post-commit $HOOKS_DIR/pre-commit"

49
hooks/post-commit-node Executable file
View file

@ -0,0 +1,49 @@
#!/bin/bash
# Git OpenTimestamp Post-Commit Hook (Node.js version)
# Requires: @opentimestamps/ots (npm install)
set -e
OUTPUT_DIR=".ots"
OUTPUT_FILE="$OUTPUT_DIR/proof.ots"
mkdir -p "$OUTPUT_DIR"
COMMIT_HASH=$(git rev-parse HEAD)
# Generate proof using Node.js
node -e "
const ots = require('@opentimestamps/ots');
const fs = require('fs');
const hash = '$COMMIT_HASH';
const output = '$OUTPUT_FILE';
const hashBuffer = Buffer.from(hash, 'hex');
ots.Timestamp.hash(hashBuffer).then(timestamp => {
// Note: This creates a local proof only, not submitted to calendars
// For full Bitcoin attestation, use the ots CLI version
const proof = {
hash: hash,
timestamp: new Date().toISOString(),
status: 'pending',
note: 'Local proof only - use ots CLI for calendar submission'
};
fs.writeFileSync(output, JSON.stringify(proof, null, 2));
console.log('[ots-node] Generated local proof: ' + hash.substring(0, 8));
}).catch(err => {
console.error('[ots-node] Error:', err.message);
process.exit(1);
});
"
# Save previous commit for chaining
PREV_COMMIT=$(git rev-parse HEAD^1 2>/dev/null || echo "")
if [ -n "$PREV_COMMIT" ]; then
echo "$PREV_COMMIT" > "$OUTPUT_DIR/prev-commit.txt"
fi
# Create individual proof file (JSON format for node version)
INDIVIDUAL_PROOF="$OUTPUT_DIR/${COMMIT_HASH}.ots"
cp "$OUTPUT_FILE" "$INDIVIDUAL_PROOF"
echo "[ots-node] Proof generated successfully"

41
hooks/post-commit-ots Executable file
View file

@ -0,0 +1,41 @@
#!/bin/bash
# Git OpenTimestamp Post-Commit Hook (ots CLI version)
# Requires: opentimestamps-client (pipx install opentimestamps-client)
set -e
OUTPUT_DIR=".ots"
OUTPUT_FILE="$OUTPUT_DIR/proof.ots"
mkdir -p "$OUTPUT_DIR"
COMMIT_HASH=$(git rev-parse HEAD)
# Generate proof using ots CLI
temp_file=$(mktemp)
temp_ots="${temp_file}.ots"
python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('$COMMIT_HASH'))" > "$temp_file"
ots stamp "$temp_file" 2>/dev/null
if [ -f "$temp_ots" ]; then
mv "$temp_ots" "$OUTPUT_FILE"
rm -f "$temp_file"
echo "[ots] Generated proof: ${COMMIT_HASH:0:8}"
else
rm -f "$temp_file"
echo "[ots] Warning: Failed to generate proof" >&2
exit 0
fi
# Save previous commit for chaining
PREV_COMMIT=$(git rev-parse HEAD^1 2>/dev/null || echo "")
if [ -n "$PREV_COMMIT" ]; then
echo "$PREV_COMMIT" > "$OUTPUT_DIR/prev-commit.txt"
fi
# Create individual proof file
INDIVIDUAL_PROOF="$OUTPUT_DIR/${COMMIT_HASH}.ots"
cp "$OUTPUT_FILE" "$INDIVIDUAL_PROOF"
echo "[ots] Proof generated successfully"

100
hooks/pre-commit-node Executable file
View file

@ -0,0 +1,100 @@
#!/bin/bash
# Git OpenTimestamp Pre-Commit Backfill Hook (Node.js version)
# Requires: @opentimestamps/ots (npm install)
# Note: Node version only generates local proofs, no calendar submission
set -e
OUTPUT_DIR=".ots"
STATUS_CACHE="$OUTPUT_DIR/.attestation-cache"
mkdir -p "$OUTPUT_DIR"
# Initialize cache
if [ ! -f "$STATUS_CACHE" ]; then
echo "# Format: commit-hash:status:timestamp" > "$STATUS_CACHE"
fi
get_cached_status() {
local commit="$1"
local cache_line=$(grep "^$commit:" "$STATUS_CACHE" 2>/dev/null | tail -1)
if [ -n "$cache_line" ]; then
local status=$(echo "$cache_line" | cut -d: -f2)
local timestamp=$(echo "$cache_line" | cut -d: -f3)
local now=$(date +%s)
if [ "$((now - timestamp))" -lt 3600 ]; then
echo "$status"
return 0
fi
fi
return 1
}
cache_status() {
local commit="$1"
local status="$2"
echo "$commit:$status:$(date +%s)" >> "$STATUS_CACHE"
}
generate_proof_node() {
local hash="$1"
local output="$2"
node -e "
const ots = require('@opentimestamps/ots');
const fs = require('fs');
const hash = '$hash';
const output = '$output';
const hashBuffer = Buffer.from(hash, 'hex');
ots.Timestamp.hash(hashBuffer).then(timestamp => {
const proof = { hash: hash, timestamp: new Date().toISOString(), status: 'pending' };
fs.writeFileSync(output, JSON.stringify(proof, null, 2));
}).catch(err => { console.error('Error:', err); process.exit(1); });
"
}
echo "[ots-node] Backfilling proofs (local only, no calendar)..."
COMMITS=$(git rev-list --reverse HEAD)
TOTAL=$(echo "$COMMITS" | wc -l)
CURRENT=0
UPDATED=0
for COMMIT in $COMMITS; do
CURRENT=$((CURRENT + 1))
PROOF_FILE="$OUTPUT_DIR/${COMMIT}.ots"
if [ $CURRENT -le 3 ] || [ $CURRENT -eq $TOTAL ]; then
echo "[ots-node] Processing $CURRENT/$TOTAL: ${COMMIT:0:8}"
elif [ $CURRENT -eq 4 ]; then
echo "[ots-node] ... processing remaining ..."
fi
if [ -f "$PROOF_FILE" ]; then
# Node version doesn't support upgrade, just check cache
CACHED_STATUS=$(get_cached_status "$COMMIT" || echo "")
[ "$CACHED_STATUS" = "attested" ] && continue
# Mark as pending (node can't attest)
cache_status "$COMMIT" "pending"
else
if generate_proof_node "$COMMIT" "$PROOF_FILE"; then
cache_status "$COMMIT" "pending"
UPDATED=$((UPDATED + 1))
fi
fi
done
# Update latest proof
LATEST_COMMIT=$(git rev-parse HEAD)
[ -f "$OUTPUT_DIR/${LATEST_COMMIT}.ots" ] && cp "$OUTPUT_DIR/${LATEST_COMMIT}.ots" "$OUTPUT_DIR/proof.ots"
# Save commit chain
rm -f "$OUTPUT_DIR/commit-chain.txt"
git rev-list HEAD | while read COMMIT; do
PREV=$(git rev-parse ${COMMIT}^1 2>/dev/null || echo "")
[ -n "$PREV" ] && echo "$COMMIT:$PREV" >> "$OUTPUT_DIR/commit-chain.txt"
done
echo "[ots-node] Backfill complete: $UPDATED generated (local proofs only)"

125
hooks/pre-commit-ots Executable file
View file

@ -0,0 +1,125 @@
#!/bin/bash
# Git OpenTimestamp Pre-Commit Backfill Hook (ots CLI version)
# Requires: opentimestamps-client (pipx install opentimestamps-client)
set -e
OUTPUT_DIR=".ots"
STATUS_CACHE="$OUTPUT_DIR/.attestation-cache"
mkdir -p "$OUTPUT_DIR"
# Initialize cache
if [ ! -f "$STATUS_CACHE" ]; then
echo "# Format: commit-hash:status:timestamp" > "$STATUS_CACHE"
fi
get_cached_status() {
local commit="$1"
local cache_line=$(grep "^$commit:" "$STATUS_CACHE" 2>/dev/null | tail -1)
if [ -n "$cache_line" ]; then
local status=$(echo "$cache_line" | cut -d: -f2)
local timestamp=$(echo "$cache_line" | cut -d: -f3)
local now=$(date +%s)
if [ "$((now - timestamp))" -lt 3600 ]; then
echo "$status"
return 0
fi
fi
return 1
}
cache_status() {
local commit="$1"
local status="$2"
echo "$commit:$status:$(date +%s)" >> "$STATUS_CACHE"
}
generate_proof() {
local hash="$1"
local output="$2"
local temp_file=$(mktemp)
local temp_ots="${temp_file}.ots"
python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('$hash'))" > "$temp_file"
ots stamp "$temp_file" 2>/dev/null
if [ -f "$temp_ots" ]; then
mv "$temp_ots" "$output"
rm -f "$temp_file"
return 0
fi
rm -f "$temp_file"
return 1
}
is_attested() {
local proof_file="$1"
local pending_count=$(ots info "$proof_file" 2>&1 | grep -c "PendingAttestation" || echo "0")
[ "$pending_count" -eq 0 ]
}
upgrade_proof() {
local proof_file="$1"
ots upgrade "$proof_file" 2>/dev/null
}
echo "[ots] Backfilling proofs..."
COMMITS=$(git rev-list --reverse HEAD)
TOTAL=$(echo "$COMMITS" | wc -l)
CURRENT=0
UPDATED=0
for COMMIT in $COMMITS; do
CURRENT=$((CURRENT + 1))
PROOF_FILE="$OUTPUT_DIR/${COMMIT}.ots"
if [ $CURRENT -le 3 ] || [ $CURRENT -eq $TOTAL ]; then
echo "[ots] Processing $CURRENT/$TOTAL: ${COMMIT:0:8}"
elif [ $CURRENT -eq 4 ]; then
echo "[ots] ... processing remaining ..."
fi
if [ -f "$PROOF_FILE" ]; then
CACHED_STATUS=$(get_cached_status "$COMMIT" || echo "")
[ "$CACHED_STATUS" = "attested" ] && continue
if is_attested "$PROOF_FILE"; then
cache_status "$COMMIT" "attested"
continue
fi
cache_status "$COMMIT" "pending"
CACHE_LINE=$(grep "^$COMMIT:" "$STATUS_CACHE" | tail -1)
CACHE_TIME=$(echo "$CACHE_LINE" | cut -d: -f3)
CACHE_AGE=$(($(date +%s) - CACHE_TIME))
[ "$CACHE_AGE" -lt 600 ] && continue
if upgrade_proof "$PROOF_FILE"; then
cache_status "$COMMIT" "attested"
UPDATED=$((UPDATED + 1))
fi
else
if generate_proof "$COMMIT" "$PROOF_FILE"; then
cache_status "$COMMIT" "pending"
UPDATED=$((UPDATED + 1))
fi
fi
done
# Update latest proof
LATEST_COMMIT=$(git rev-parse HEAD)
[ -f "$OUTPUT_DIR/${LATEST_COMMIT}.ots" ] && cp "$OUTPUT_DIR/${LATEST_COMMIT}.ots" "$OUTPUT_DIR/proof.ots"
# Save commit chain
rm -f "$OUTPUT_DIR/commit-chain.txt"
git rev-list HEAD | while read COMMIT; do
PREV=$(git rev-parse ${COMMIT}^1 2>/dev/null || echo "")
[ -n "$PREV" ] && echo "$COMMIT:$PREV" >> "$OUTPUT_DIR/commit-chain.txt"
done
echo "[ots] Backfill complete: $UPDATED updated"