#!/bin/bash
set -euo pipefail

project_dir="${CLAUDE_PROJECT_DIR:-$(pwd)}"
instruction_path="$project_dir/CLAUDE.md"

node - "$instruction_path" "$project_dir" <<'EOF'
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');

const instructionPath = process.argv[2];
const projectDir = process.argv[3];
const SPEC_FIRST_CLI_PATH = "__SPEC_FIRST_CLI_PATH__";
const BOOTSTRAP_START = '<!-- spec-first:bootstrap:start -->';
const BOOTSTRAP_END = '<!-- spec-first:bootstrap:end -->';
const missingMessage = [
  '[spec-first] using-spec-first SessionStart injection',
  'Managed using-spec-first bootstrap is missing from `CLAUDE.md`.',
  'Run `spec-first init` in this project and choose Claude Code when prompted to restore the managed bootstrap block.',
].join('\n');

let additionalContext = missingMessage;
// Read via try/catch (not existsSync + read) so an unreadable file (EACCES/EISDIR)
// or a TOCTOU delete degrades to a pointer instead of throwing and aborting the hook.
const instructionBody = readInstructionBody(instructionPath);
if (instructionBody !== null) {
  const startIdx = instructionBody.indexOf(BOOTSTRAP_START);
  const endIdx = instructionBody.indexOf(BOOTSTRAP_END);

  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
    // The managed bootstrap block already lives in CLAUDE.md, which Claude loads at
    // session start, so re-injecting its full body here would duplicate it verbatim.
    // Emit a short pointer instead: keep the workflow-entry trigger and the parent
    // multi-repo target_repo boundary salient without paying the duplication cost.
    additionalContext = [
      '[spec-first] using-spec-first SessionStart injection',
      "Workflow entry governance is active in this repo's CLAUDE.md (using-spec-first managed block): route substantial work through a public /spec:* workflow before non-trivial or risky edits, and in a parent multi-repo workspace keep writes within an explicit target_repo.",
      'Full routing policy: skills/using-spec-first/SKILL.md.',
    ].join('\n');
  }
}

if (additionalContext === missingMessage && instructionBody !== null) {
  additionalContext = [
    '[spec-first] using-spec-first SessionStart injection',
    'Managed using-spec-first bootstrap markers are missing or incomplete in `CLAUDE.md`.',
    'Run `spec-first init` in this project and choose Claude Code when prompted to restore the managed bootstrap block.',
  ].join('\n');
}

const startupReminder = readStartupReminder(projectDir);
if (startupReminder) {
  additionalContext = [
    additionalContext,
    '',
    startupReminder,
  ].join('\n');
}

process.stdout.write(JSON.stringify({
  hookSpecificOutput: {
    hookEventName: 'SessionStart',
    additionalContext,
  },
}));

function readInstructionBody(filePath) {
  try {
    return fs.readFileSync(filePath, 'utf8');
  } catch {
    return null;
  }
}

function readStartupReminder(cwd) {
  try {
    if (!isTrustedSpecFirstCliPath(SPEC_FIRST_CLI_PATH)) {
      return '';
    }

    const result = spawnSync(process.execPath, [SPEC_FIRST_CLI_PATH, 'startup-reminder', '--claude'], {
      cwd,
      encoding: 'utf8',
      // Deliberate session-start latency cap. The child records its cooldown only after a
      // successful display, so on a dead/slow network it re-spawns every session — a larger
      // timeout would block every offline session-start by that much. The reminder is a
      // best-effort cosmetic notice and still completes on a healthy network (~600ms).
      timeout: 1200,
    });

    if (result.error || result.status !== 0 || typeof result.stdout !== 'string') {
      return '';
    }

    return result.stdout.trim();
  } catch {
    return '';
  }
}

function isTrustedSpecFirstCliPath(cliPath) {
  if (
    typeof cliPath !== 'string'
    || cliPath.length === 0
    || cliPath === '__SPEC_FIRST_CLI_PATH__'
    || !path.isAbsolute(cliPath)
    || path.basename(cliPath) !== 'spec-first.js'
  ) {
    return false;
  }

  try {
    return fs.statSync(cliPath).isFile();
  } catch {
    return false;
  }
}
EOF
