// Forbidden-token gate for the admin IDENTITY.md template.
// Mirrors runIdentityForbiddenTokenCheck() in
// platform/services/claude-session-manager/src/system-prompt.ts. The two
// implementations must stay in lockstep — the manager enforces the gate at
// per-account boot, this script enforces it at bundle time so a leak never
// reaches the payload. See Task 437 for the structural rationale.

const FORBIDDEN_COUNTER_TOKENS = [
  "learningsCount",
  "preferencesCount",
  "graphWriteCount",
  "typedEdgesCount",
  "nodesProcessed",
];

const FORBIDDEN_MCP_TOKENS = [
  "memory-search",
  "memory-write",
  "memory-update",
  "memory-report-list",
  "memory-report-read-latest",
  "memory-compiled-truth-history",
  "memory-typed-edge-pass",
  "profile-read",
  "database-operator",
  "session-retrospective-mark-complete",
  "pre-turn-graph-pass-skip",
  "per-turn-graph-pass-gate",
  "web-search",
  "WebSearch",
  "WebFetch",
];

const FORBIDDEN_HOOK_NAMES = [
  "UserPromptSubmit",
  "PreToolUse",
  "PostToolUse",
  "SessionStart",
  "SessionEnd",
  "SubagentStop",
  "Notification",
];

function stripVoiceSection(content) {
  // Scope past the "## How you sound" section through end of file. Everything
  // from that heading onward is voice/output/access prose that legitimately
  // names forbidden classes and skill-load invocations (the plain-English and
  // operator-voice passes) without being instances of them; only the
  // behavioural prose above it is scanned.
  const lines = content.split("\n");
  const start = lines.findIndex((l) => l.trim() === "## How you sound");
  if (start === -1) return content;
  return lines.slice(0, start).join("\n");
}

export function runIdentityForbiddenTokenCheck(identityContent) {
  const scanned = stripVoiceSection(identityContent);
  const violations = [];
  const seen = new Set();
  const push = (cls, token) => {
    const key = cls + ":" + token;
    if (seen.has(key)) return;
    seen.add(key);
    violations.push({ class: cls, token });
  };

  const scanRegex = (re, cls) => {
    for (const match of scanned.matchAll(re)) push(cls, match[0]);
  };

  scanRegex(/`[^`\n]+`/g, "backtick-token");
  scanRegex(/:[A-Z][A-Za-z][A-Za-z0-9]*/g, "node-label");
  scanRegex(/\bTasks?\s+\d+\b/g, "task-number");
  scanRegex(/\b[A-Za-z0-9_./-]*\.(?:ts|tsx|js|jsx|mjs|cjs|sh|py|json|yaml|yml|md)\b/g, "code-path");
  scanRegex(/<[a-z][a-z0-9-]*>/g, "angle-sentinel");

  for (const tok of FORBIDDEN_COUNTER_TOKENS) {
    if (new RegExp("\\b" + tok + "\\b").test(scanned)) push("counter-token", tok);
  }
  for (const tok of FORBIDDEN_MCP_TOKENS) {
    if (new RegExp("\\b" + tok + "\\b").test(scanned)) push("mcp-prefix", tok);
  }
  for (const tok of FORBIDDEN_HOOK_NAMES) {
    if (new RegExp("\\b" + tok + "\\b").test(scanned)) push("hook-name", tok);
  }

  return violations.length === 0 ? { ok: true } : { ok: false, violations };
}
