#!/usr/bin/env node
/**
 * Rule #10 — Rationalization audit (PostToolUse).
 *
 * Reads the last assistant text + nearby tool history. Detects rationalization
 * phrases and weak-test signals. Appends each flag to a JSONL file consumed by
 * Rule #1 injection on next prompt.
 *
 * NEVER blocks — soft visibility. Failure modes:
 *   - skipped writing-plans for a feature task
 *   - prod-code edit without matching test in same turn (despite TDD-gate
 *     letting it through via override)
 *   - assistant text contains rationalization phrases
 *
 * Spec: docs/superpowers/specs/2026-05-25-enforce-hard-rules-design.md
 */

import {
  readStdin,
  parseEventJson,
  readTranscript,
  lastAssistantText,
  turnToolUses,
  appendRationalizationFlag,
  readRationalizationFlags,
  exitDisciplineDecision,
  isProductionCodePath,
} from './enforce-hook-helpers.mjs';

const RATIONALIZATION_PHRASES = [
  'just this once',
  'пока без',
  'сейчас быстрее',
  'потом разберусь',
  'временно',
  'просто рационализация',
  "i'll come back to",
  'i will come back to',
  'we can skip',
  'rationalize',
  'без церемоний',
  'без скила сейчас',
  // expanded vocabulary
  'давай разок',
  'только сейчас',
  'один раз без правил',
  'на этот раз без',
  'я знаю что не надо но',
];

export function stripQuotedContext(text) {
  if (typeof text !== 'string') return '';
  let stripped = text;
  stripped = stripped.replace(/```[\s\S]*?```/g, '');
  stripped = stripped.replace(/`[^`\n]*`/g, '');
  stripped = stripped.replace(/«[^»\n]*»/g, '');
  stripped = stripped.replace(/"[^"\n]{1,200}"/g, '');
  return stripped;
}

export function findRationalizationPhrases(text) {
  if (typeof text !== 'string') return [];
  const cleaned = stripQuotedContext(text);
  const lo = cleaned.toLowerCase();
  const hits = [];
  for (const p of RATIONALIZATION_PHRASES) {
    if (lo.includes(p)) hits.push(p);
  }
  return hits;
}

export function detectProdEditWithoutTest(toolUses) {
  // Look for Edit/Write on production code; check if any test edit accompanies it.
  const prodEdits = [];
  let hasTestEdit = false;
  for (const u of toolUses) {
    if (!['Edit', 'Write', 'MultiEdit'].includes(u.name)) continue;
    const p = (u.input && (u.input.file_path || u.input.notebook_path)) || '';
    if (/\.(test|spec)\.[a-z0-9]+$/i.test(p) || /Test\.php$/.test(p)) { hasTestEdit = true; continue; }
    if (isProductionCodePath(p)) prodEdits.push(p);
  }
  return prodEdits.length > 0 && !hasTestEdit ? prodEdits : [];
}

export function audit(transcriptEntries) {
  const flags = [];
  const text = lastAssistantText(transcriptEntries);
  const phrases = findRationalizationPhrases(text);
  for (const p of phrases) flags.push({ kind: 'rationalization-phrase', evidence: p });

  const toolUses = turnToolUses(transcriptEntries);
  const orphanProdEdits = detectProdEditWithoutTest(toolUses);
  for (const p of orphanProdEdits) flags.push({ kind: 'prod-edit-without-test', evidence: p });

  // Weak commit-message: git commit with very short message
  for (const u of toolUses) {
    if (u.name !== 'Bash') continue;
    const cmd = (u.input && u.input.command) || '';
    if (!/git\s+commit/.test(cmd)) continue;
    const m = cmd.match(/-m\s+["']([^"']+)["']/);
    if (m && m[1].length < 12) {
      flags.push({ kind: 'weak-commit-message', evidence: m[1] });
    }
  }
  return flags;
}

/**
 * Pure decision seam — injectable priorFlagCount for testability.
 * Blocks on 3rd flag of the same session (priorFlagCount >= 2).
 */
export function decide({ assistantText, sessionId: _sessionId, override = false, priorFlagCount = 0 }) {
  const detected = findRationalizationPhrases(assistantText || '');
  if (override) return { block: false, detected };
  if (priorFlagCount >= 2 && detected.length > 0) {
    return {
      block: true,
      message: `Rationalization detected (phrase: "${detected[0]}"). This is the ${priorFlagCount + 1}th flag in this session — blocking to prevent pattern escalation.`,
      detected,
    };
  }
  return { block: false, detected };
}

async function main() {
  const raw = await readStdin();
  const event = parseEventJson(raw);
  // М7 Фаза 4b: fail-CLOSE (любая внутренняя ошибка → блок, не тихий пропуск, анти-SE2).
  await exitDisciplineDecision(
    () => {
      const transcript = readTranscript(event.transcript_path);
      const flags = audit(transcript);
      // Count prior flags before appending new ones
      const priorFlagCount = readRationalizationFlags(event.session_id).length;
      for (const f of flags) appendRationalizationFlag(event.session_id, f.kind, f.evidence);
      // Halt-counter: язык-детектор мягкий (flags), блок только на 3-м флаге (decide).
      const text = lastAssistantText(transcript);
      const decision = decide({ assistantText: text, sessionId: event.session_id, priorFlagCount });
      return { block: decision.block, message: decision.block ? decision.message : undefined };
    },
    { label: 'enforce-rationalization-audit' },
  );
}

const isCli = process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/enforce-rationalization-audit.mjs');
if (isCli) main();
