// @crash-shield-ignore-file — config/cache reader for Planu-controlled JSON; writer is this codebase, shape guaranteed by build/seed.
// tools/git/hook-ops.ts — Hook operations: setup-hooks with manager detection
import { writeFile, mkdir, access, readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { git, resolveProjectPath, mergeConfig, DEFAULT_PROTECTED_BRANCHES } from './git-helpers.js';
import { compactResult, formatList } from '../output-formatter.js';
async function detectHookManager(projectPath) {
    // Check for husky
    try {
        await access(join(projectPath, '.husky'));
        return 'husky';
    }
    catch {
        /* continue */
    }
    // Check for lefthook
    try {
        await access(join(projectPath, 'lefthook.yml'));
        return 'lefthook';
    }
    catch {
        /* continue */
    }
    try {
        await access(join(projectPath, 'lefthook.yaml'));
        return 'lefthook';
    }
    catch {
        /* continue */
    }
    // Check for simple-git-hooks in package.json
    try {
        const pkgRaw = await readFile(join(projectPath, 'package.json'), 'utf-8');
        const pkg = JSON.parse(pkgRaw);
        const sgh = pkg['simple-git-hooks'];
        if (sgh && typeof sgh === 'object' && Object.keys(sgh).length > 0) {
            return 'simple-git-hooks';
        }
    }
    catch {
        /* continue */
    }
    return 'native';
}
async function resolveHooksDir(projectPath) {
    try {
        const { stdout } = await git(projectPath, ['rev-parse', '--git-dir']);
        return join(projectPath, stdout.trim(), 'hooks');
    }
    catch {
        return join(projectPath, '.git', 'hooks');
    }
}
function buildPreCommitScript(requireSpecInCommit, stalenessThreshold, baseBranch) {
    return [
        '#!/bin/sh',
        '# Planu SDD pre-commit hook',
        '',
        'BRANCH=$(git rev-parse --abbrev-ref HEAD)',
        '',
        requireSpecInCommit
            ? [
                'SPEC_PATTERN="^(feat|fix|refactor|infra|docs|project)/SPEC-[0-9]{3}-"',
                'if ! echo "$BRANCH" | grep -qE "$SPEC_PATTERN"; then',
                '  if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ] && [ "$BRANCH" != "develop" ]; then',
                '    echo "WARNING: Branch name \'$BRANCH\' does not follow Planu convention."',
                '    echo "Expected: <type>/SPEC-XXX-<slug>"',
                '  fi',
                'fi',
            ].join('\n')
            : '# Spec-based branch validation disabled',
        '',
        '# Validate branch naming convention (non-blocking)',
        'BRANCH_CONVENTION="^(feat|fix|refactor|infra|docs|project|chore|test|perf|ci|build|style|revert)/"',
        'if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ] && [ "$BRANCH" != "develop" ]; then',
        '  if ! echo "$BRANCH" | grep -qE "$BRANCH_CONVENTION"; then',
        '    echo "WARNING: Branch \'$BRANCH\' does not follow naming convention."',
        '    echo "Expected: feat/*, fix/*, chore/*, docs/*, refactor/*, test/*, perf/*"',
        '  fi',
        'fi',
        '',
        '# Staleness check',
        `STALENESS_THRESHOLD=${stalenessThreshold}`,
        `BASE_BRANCH="${baseBranch}"`,
        'if [ "$STALENESS_THRESHOLD" -gt 0 ] && git rev-parse --verify "$BASE_BRANCH" >/dev/null 2>&1; then',
        '  BEHIND=$(git rev-list --count HEAD.."$BASE_BRANCH" 2>/dev/null || echo 0)',
        '  if [ "$BEHIND" -gt "$STALENESS_THRESHOLD" ]; then',
        '    echo "WARNING: Branch is $BEHIND commits behind $BASE_BRANCH (threshold: $STALENESS_THRESHOLD)."',
        '    echo "Run: manage_git sync-branch to sync."',
        '  fi',
        'fi',
        '',
        '# Check for secrets in staged files',
        "SECRETS_PATTERN='(PRIVATE_KEY|SECRET_KEY|API_KEY|PASSWORD|TOKEN)\\s*=\\s*\\S+'",
        'STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)',
        'for FILE in $STAGED_FILES; do',
        '  if grep -qE "$SECRETS_PATTERN" "$FILE" 2>/dev/null; then',
        '    echo "ERROR: Potential secret found in $FILE"',
        '    exit 1',
        '  fi',
        'done',
        '',
        '# Run linter',
        'if command -v pnpm &>/dev/null && [ -f "package.json" ]; then',
        '  pnpm lint --max-warnings 0 || exit 1',
        'fi',
        '',
        'exit 0',
    ].join('\n');
}
function buildCommitMsgScript(commitFormat) {
    return [
        '#!/bin/sh',
        '# Planu SDD commit-msg hook',
        '',
        'COMMIT_MSG_FILE="$1"',
        'COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")',
        '',
        commitFormat === 'conventional'
            ? [
                'CONVENTIONAL_PATTERN="^(feat|fix|refactor|docs|test|chore|ci|perf|style|build|revert)(\\(.+\\))?!?:\\s.+"',
                'if ! echo "$COMMIT_MSG" | head -1 | grep -qE "$CONVENTIONAL_PATTERN"; then',
                '  echo "ERROR: Commit message does not follow conventional format."',
                '  echo "Expected: <type>(<scope>): <description>"',
                '  exit 1',
                'fi',
            ].join('\n')
            : commitFormat === 'spec-id'
                ? [
                    'SPEC_PATTERN="\\[SPEC-[0-9]{3}\\]"',
                    'if ! echo "$COMMIT_MSG" | grep -qE "$SPEC_PATTERN"; then',
                    '  echo "WARNING: Commit message does not reference a spec ID."',
                    '  echo "Recommended format: [SPEC-XXX] <description>"',
                    'fi',
                ].join('\n')
                : '# Custom commit format — no validation',
        '',
        'exit 0',
    ].join('\n');
}
function buildPrePushScript(protectedBranches, stalenessThreshold, baseBranch) {
    return [
        '#!/bin/sh',
        '# Planu SDD pre-push hook',
        '',
        'BRANCH=$(git rev-parse --abbrev-ref HEAD)',
        `PROTECTED_BRANCHES="${protectedBranches.join(' ')}"`,
        '',
        'for PROTECTED in $PROTECTED_BRANCHES; do',
        '  if [ "$BRANCH" = "$PROTECTED" ]; then',
        '    echo "ERROR: Direct push to \'$BRANCH\' is not allowed."',
        '    echo "Create a feature branch and submit a PR instead."',
        '    exit 1',
        '  fi',
        'done',
        '',
        '# Staleness check before push',
        `STALENESS_THRESHOLD=${stalenessThreshold}`,
        `BASE_BRANCH="${baseBranch}"`,
        'if [ "$STALENESS_THRESHOLD" -gt 0 ] && git rev-parse --verify "$BASE_BRANCH" >/dev/null 2>&1; then',
        '  BEHIND=$(git rev-list --count HEAD.."$BASE_BRANCH" 2>/dev/null || echo 0)',
        '  if [ "$BEHIND" -gt "$STALENESS_THRESHOLD" ]; then',
        '    echo "WARNING: Branch is $BEHIND commits behind $BASE_BRANCH."',
        '    echo "Consider running: manage_git sync-branch before pushing."',
        '  fi',
        'fi',
        '',
        '# Dependency freshness gate for pnpm projects',
        'if command -v pnpm >/dev/null 2>&1 && [ -f "package.json" ] && [ -f "pnpm-lock.yaml" ]; then',
        '  echo "[Planu] Checking dependency freshness..."',
        '  pnpm install --frozen-lockfile --ignore-scripts >/dev/null || {',
        '    echo "ERROR: package.json and pnpm-lock.yaml are not synchronized."',
        '    echo "Run: pnpm install --lockfile-only --ignore-scripts"',
        '    exit 1',
        '  }',
        '  pnpm audit --audit-level=high || {',
        '    echo "ERROR: High or critical dependency vulnerabilities found."',
        '    echo "Run: pnpm audit and update the affected packages before pushing."',
        '    exit 1',
        '  }',
        '  OUTDATED_JSON=$(mktemp)',
        '  OUTDATED_ERR=$(mktemp)',
        '  pnpm outdated --format=json >"$OUTDATED_JSON" 2>"$OUTDATED_ERR"',
        '  OUTDATED_STATUS=$?',
        '  if [ "$OUTDATED_STATUS" -gt 1 ]; then',
        '    echo "ERROR: Failed to query dependency freshness from the registry."',
        '    cat "$OUTDATED_ERR"',
        '    rm -f "$OUTDATED_JSON" "$OUTDATED_ERR"',
        '    exit 1',
        '  fi',
        "  node -e \"const fs=require('fs');const raw=fs.readFileSync(process.argv[1],'utf8').trim();const data=raw?JSON.parse(raw):{};const count=Array.isArray(data)?data.length:Object.keys(data).length;if(count>0){console.error('ERROR: Outdated dependencies found. Run: bash scripts/check-updates.sh --apply, pnpm update, or update intentionally before pushing.');process.exit(1)}\" \"$OUTDATED_JSON\" || {",
        '    rm -f "$OUTDATED_JSON" "$OUTDATED_ERR"',
        '    exit 1',
        '  }',
        '  rm -f "$OUTDATED_JSON" "$OUTDATED_ERR"',
        'fi',
        '',
        'exit 0',
    ].join('\n');
}
/**
 * SPEC-666: Build the post-commit hook script that detects project stack drift.
 * Pure shell — no Node startup — runs in <100ms.
 * Writes an NDJSON line to .planu/drift-commits.ndjson when drift is detected.
 */
function buildPostCommitDriftScript() {
    return [
        '#!/bin/sh',
        '# Planu drift detector — post-commit hook (SPEC-666)',
        '# Detects new dependencies and file types; queues signals for background processing.',
        '# Exits silently with no output when no drift is detected (AC5: <50ms).',
        '',
        'DRIFT_DIR=".planu"',
        'DRIFT_FILE="$DRIFT_DIR/drift-commits.ndjson"',
        'mkdir -p "$DRIFT_DIR" 2>/dev/null || true',
        '',
        '# Get files changed in the last commit (up to 50 entries)',
        'CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null | head -50)',
        '[ -z "$CHANGED" ] && exit 0',
        '',
        '# Build JSON array of file paths',
        'FILES_JSON="["',
        'FIRST=1',
        'while IFS= read -r FILE_PATH; do',
        '  ESCAPED=$(printf \'%s\' "$FILE_PATH" | sed \'s/\\\\/\\\\\\\\/g; s/"/\\\\"/g\')',
        '  if [ "$FIRST" -eq 0 ]; then',
        '    FILES_JSON="$FILES_JSON,"',
        '  fi',
        '  FILES_JSON="$FILES_JSON\\"$ESCAPED\\""',
        '  FIRST=0',
        'done <<PLANU_CHANGED_FILES',
        '$CHANGED',
        'PLANU_CHANGED_FILES',
        'FILES_JSON="$FILES_JSON]"',
        '',
        "TS=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u)",
        '',
        '# Check for drift-worthy files (dep manifests or uncommon extensions)',
        'if echo "$CHANGED" | grep -qE \'(^|/)(package\\.json|go\\.mod|requirements\\.txt|Cargo\\.toml|pom\\.xml|build\\.gradle|pyproject\\.toml|Gemfile|composer\\.json)$\' 2>/dev/null; then',
        '  HAS_DEP=1',
        'else',
        '  HAS_DEP=0',
        'fi',
        "if echo \"$CHANGED\" | grep -oE '\\.[a-zA-Z]+$' | grep -qvE '\\.(ts|tsx|js|jsx|mjs|cjs|json|md|mdx|sh|bash|txt|yaml|yml|toml|lock|sum|mod|py|go|rs|java|kt|rb|php|cs|dart|swift|html|css|scss|vue|svelte|env|sql|xml|csv)$' 2>/dev/null; then",
        '  HAS_EXT=1',
        'else',
        '  HAS_EXT=0',
        'fi',
        '',
        '# Only write if there is something worth processing',
        'if [ "$HAS_DEP" -gt 0 ] || [ "$HAS_EXT" -gt 0 ]; then',
        '  printf \'{"ts":"%s","files":%s}\\n\' "$TS" "$FILES_JSON" >> "$DRIFT_FILE" 2>/dev/null || true',
        'fi',
        '',
        'exit 0',
    ].join('\n');
}
export async function handleSetupHooks(projectId, config) {
    const projectPath = await resolveProjectPath(projectId);
    const hookManager = await detectHookManager(projectPath);
    const hooksDir = await resolveHooksDir(projectPath);
    await mkdir(hooksDir, { recursive: true });
    const mergedConfig = mergeConfig(config);
    const installedHooks = [];
    /* v8 ignore start */
    const protectedBranches = mergedConfig.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES;
    const stalenessThreshold = mergedConfig.stalenessThreshold ?? 50;
    const baseBranch = mergedConfig.baseBranch ?? 'develop';
    /* v8 ignore end */
    const preCommitScript = buildPreCommitScript(
    /* v8 ignore next */ mergedConfig.requireSpecInCommit ?? true, stalenessThreshold, baseBranch);
    await writeFile(join(hooksDir, 'pre-commit'), preCommitScript, { mode: 0o755 });
    installedHooks.push('pre-commit');
    /* v8 ignore next */
    const commitMsgScript = buildCommitMsgScript(mergedConfig.commitFormat ?? 'conventional');
    await writeFile(join(hooksDir, 'commit-msg'), commitMsgScript, { mode: 0o755 });
    installedHooks.push('commit-msg');
    const prePushScript = buildPrePushScript(protectedBranches, stalenessThreshold, baseBranch);
    await writeFile(join(hooksDir, 'pre-push'), prePushScript, { mode: 0o755 });
    installedHooks.push('pre-push');
    // SPEC-666: Post-commit drift detector hook
    const postCommitScript = buildPostCommitDriftScript();
    await writeFile(join(hooksDir, 'post-commit'), postCommitScript, { mode: 0o755 });
    installedHooks.push('post-commit');
    const text = [
        `Installed ${installedHooks.length} git hook(s) via ${hookManager}: ${installedHooks.join(', ')}`,
        '',
        formatList(installedHooks, 'Hooks Installed'),
        '',
        `**hookManager**: ${hookManager}`,
    ].join('\n');
    return compactResult(text);
}
//# sourceMappingURL=hook-ops.js.map