#!/usr/bin/env node
/**
 * lint-pieces: enforces that no piece YAML uses `COMPLETE` / `ABORT` / `ASK`
 * as `transition.rules[].next` values. Terminal moves go through the
 * `complete` tool (Phase 6 design).
 *
 * `default_next: COMPLETE` is allowed — that's an engine-internal sentinel
 * used by context-overflow / ASK-limit / SpawnSubTask-unavailable fallback
 * paths and is never exposed to the LLM.
 *
 * Usage:
 *   node scripts/lint-pieces.mjs             # lint all pieces/*.yaml
 *   node scripts/lint-pieces.mjs path/x.yaml # lint specific files
 *
 * Exit codes:
 *   0 — no violations
 *   1 — at least one piece uses a banned terminal in rules[].next
 *
 * History:
 *   - Phase 6a-1: introduced ALLOWLIST mechanism for the 11 unmigrated pieces
 *   - Phase 6a-2: ALLOWLIST drained to empty as pieces migrated
 *   - Phase 6b:   ALLOWLIST mechanism removed; violations are always hard fails
 */

import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { parse } from 'yaml';

const TERMINAL_NEXTS = new Set(['COMPLETE', 'ABORT', 'ASK']);

function collectPieceFiles(args) {
  if (args.length > 0) return args;
  const dir = 'pieces';
  if (!existsSync(dir)) {
    console.error(`[lint-pieces] directory not found: ${dir}`);
    process.exit(2);
  }
  return readdirSync(dir).filter((f) => f.endsWith('.yaml')).map((f) => join(dir, f));
}

function findTerminalNexts(piece) {
  const offenders = [];
  if (!piece || typeof piece !== 'object') return offenders;
  const movements = Array.isArray(piece.movements) ? piece.movements : [];
  for (const movement of movements) {
    const rules = Array.isArray(movement?.rules) ? movement.rules : [];
    for (const rule of rules) {
      if (rule && typeof rule.next === 'string' && TERMINAL_NEXTS.has(rule.next)) {
        offenders.push({
          movement: String(movement?.name ?? '<unnamed>'),
          condition: String(rule.condition ?? ''),
          next: rule.next,
        });
      }
    }
  }
  return offenders;
}

function main() {
  const files = collectPieceFiles(process.argv.slice(2));
  const violations = [];

  for (const filePath of files) {
    let parsed;
    try {
      parsed = parse(readFileSync(filePath, 'utf-8'));
    } catch (e) {
      console.error(`[lint-pieces] failed to parse ${filePath}: ${e.message}`);
      process.exit(2);
    }
    const offenders = findTerminalNexts(parsed);
    for (const offender of offenders) {
      violations.push({ file: filePath, ...offender });
    }
  }

  if (violations.length > 0) {
    console.error('\n[lint-pieces] ❌ pieces must not use COMPLETE/ABORT/ASK in rules[].next.');
    console.error('Use the `complete` tool for terminal moves (status: success | aborted | needs_user_input).');
    console.error('See docs/plans/2026-05-01-phase-6a-complete-tool.md.\n');
    for (const v of violations) {
      console.error(`  ${v.file} → movement="${v.movement}" rule.next="${v.next}" condition="${v.condition}"`);
    }
    process.exit(1);
  }
  console.log(`[lint-pieces] ✓ ${files.length} piece(s) checked, no violations`);
}

main();
