"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/sub-agents/scout/index.ts
var scout_exports = {};
__export(scout_exports, {
  AiNewsletterFeedsScanner: () => AiNewsletterFeedsScanner,
  AnthropicAnnouncementsScanner: () => AnthropicAnnouncementsScanner,
  ConferenceAnnouncementsScanner: () => ConferenceAnnouncementsScanner,
  ConsoleScoutLogger: () => ConsoleScoutLogger,
  DEFAULT_SUB_AGENT_REGISTRY: () => DEFAULT_SUB_AGENT_REGISTRY,
  DigestBuilder: () => DigestBuilder,
  FetchScoutHttpClient: () => FetchScoutHttpClient,
  FirecrawlChangelogScanner: () => FirecrawlChangelogScanner,
  GithubTrendingScanner: () => GithubTrendingScanner,
  GoogleAiAnnouncementsScanner: () => GoogleAiAnnouncementsScanner,
  HackernewsAiScanner: () => HackernewsAiScanner,
  ImpactScorer: () => ImpactScorer,
  InMemoryScoutFindingStore: () => InMemoryScoutFindingStore,
  InMemoryScoutHttpClient: () => InMemoryScoutHttpClient,
  InboxPusher: () => InboxPusher,
  IntegrationEffortScorer: () => IntegrationEffortScorer,
  IntegrationRecommender: () => IntegrationRecommender,
  McpRegistryScanner: () => McpRegistryScanner,
  OpenaiAnnouncementsScanner: () => OpenaiAnnouncementsScanner,
  ProductHuntAiScanner: () => ProductHuntAiScanner,
  RelevanceScorer: () => RelevanceScorer,
  SCOUT_USER_AGENT: () => SCOUT_USER_AGENT,
  Scout: () => Scout,
  SilentScoutLogger: () => SilentScoutLogger,
  SkillsRegistryScanner: () => SkillsRegistryScanner,
  flattenedCapabilityTags: () => flattenedCapabilityTags
});
module.exports = __toCommonJS(scout_exports);

// src/sub-agents/scout/scout.ts
var import_crypto = require("crypto");

// src/sub-agents/scout/http.ts
var SCOUT_USER_AGENT = "ScoutSubAgent/0.35 (+https://github.com/bw-advisory/sentinel-ai-agent-kit)";
var FetchScoutHttpClient = class {
  constructor(opts = {}) {
    this.opts = opts;
  }
  opts;
  async get(url, headers = {}) {
    const timeoutMs = this.opts.timeoutMs ?? 15e3;
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const response = await fetch(url, {
        method: "GET",
        headers: {
          "User-Agent": SCOUT_USER_AGENT,
          Accept: "application/json,text/plain,*/*",
          ...headers
        },
        signal: controller.signal
      });
      const body = await response.text();
      const responseHeaders = {};
      response.headers.forEach((value, key) => {
        responseHeaders[key] = value;
      });
      return { status: response.status, body, headers: responseHeaders };
    } finally {
      clearTimeout(timer);
    }
  }
};
var InMemoryScoutHttpClient = class {
  constructor(responder) {
    this.responder = responder;
  }
  responder;
  calls = [];
  async get(url, headers) {
    this.calls.push({ url, headers });
    const r = this.responder(url);
    return { status: r.status, body: r.body, headers: r.headers ?? {} };
  }
};
var ConsoleScoutLogger = class {
  info(message, ctx) {
    if (ctx) console.log(`[scout] ${message}`, ctx);
    else console.log(`[scout] ${message}`);
  }
  warn(message, ctx) {
    if (ctx) console.warn(`[scout] ${message}`, ctx);
    else console.warn(`[scout] ${message}`);
  }
  error(message, ctx) {
    if (ctx) console.error(`[scout] ${message}`, ctx);
    else console.error(`[scout] ${message}`);
  }
};
var SilentScoutLogger = class {
  info() {
  }
  warn() {
  }
  error() {
  }
};

// src/sub-agents/scout/memory-store.ts
var InMemoryScoutFindingStore = class {
  bySource = /* @__PURE__ */ new Map();
  key(source, externalId) {
    return `${source}::${externalId}`;
  }
  async isNew(source, externalId) {
    return !this.bySource.has(this.key(source, externalId));
  }
  async upsert(scored) {
    this.bySource.set(
      this.key(scored.finding.source, scored.finding.externalId),
      scored
    );
  }
  async listSince(since, limit = 200) {
    return [...this.bySource.values()].filter((s) => s.finding.discoveredAt.getTime() >= since.getTime()).slice(0, limit);
  }
  async listTopRanked(since, limit) {
    return [...this.bySource.values()].filter((s) => s.finding.discoveredAt.getTime() >= since.getTime()).sort((a, b) => b.priorityScore - a.priorityScore).slice(0, limit);
  }
};

// src/sub-agents/scout/scanners/ai-newsletter-feeds.ts
var DEFAULT_FEEDS = [
  { name: "TLDR AI", url: "https://tldr.tech/api/rss/ai" },
  { name: "Latent Space", url: "https://www.latent.space/feed" },
  { name: "Ben's Bites", url: "https://bensbites.beehiiv.com/feed" },
  { name: "Rundown AI", url: "https://www.therundown.ai/rss.xml" }
];
var AiNewsletterFeedsScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "ai_newsletter_feeds";
  async scan(since) {
    const feeds = this.opts.feeds ?? DEFAULT_FEEDS;
    const findings = [];
    for (const feed of feeds) {
      try {
        const res = await this.http.get(feed.url);
        if (res.status >= 400) {
          this.logger.warn("ai_newsletter_feeds non-2xx", {
            feed: feed.name,
            status: res.status
          });
          continue;
        }
        for (const item of extractRssItems(res.body)) {
          if (item.publishedAt && item.publishedAt.getTime() < since.getTime()) {
            continue;
          }
          findings.push({
            externalId: `news:${feed.name}:${item.guid}`,
            source: this.source,
            kind: "newsletter_item",
            title: `${feed.name}: ${item.title}`,
            url: item.link,
            summary: item.description.slice(0, 280) || item.title,
            metadata: { feed: feed.name },
            publishedAt: item.publishedAt,
            discoveredAt: /* @__PURE__ */ new Date()
          });
        }
      } catch (err) {
        this.logger.error("ai_newsletter_feeds scan failed", {
          feed: feed.name,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    return findings;
  }
};
function extractRssItems(xml) {
  const out = [];
  const itemRe = /<(item|entry)>([\s\S]*?)<\/\1>/g;
  let m;
  while ((m = itemRe.exec(xml)) !== null) {
    const block = m[2] ?? "";
    const title = pickTagContent(block, "title") ?? "(no title)";
    const link = pickTagContent(block, "link") ?? pickAtomLinkHref(block) ?? "";
    const description = pickTagContent(block, "description") ?? pickTagContent(block, "summary") ?? pickTagContent(block, "content") ?? "";
    const guid = pickTagContent(block, "guid") ?? pickTagContent(block, "id") ?? link;
    const dateRaw = pickTagContent(block, "pubDate") ?? pickTagContent(block, "published") ?? pickTagContent(block, "updated");
    const publishedAt = dateRaw ? new Date(dateRaw) : void 0;
    if (!link) continue;
    out.push({
      guid: guid || link,
      title: stripTags(title).trim(),
      link: link.trim(),
      description: stripTags(description).trim(),
      publishedAt: publishedAt && !isNaN(publishedAt.getTime()) ? publishedAt : void 0
    });
    if (out.length >= 50) break;
  }
  return out;
}
function pickTagContent(block, tag) {
  const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "i");
  const m = block.match(re);
  if (!m) return void 0;
  const raw = m[1] ?? "";
  return raw.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1");
}
function pickAtomLinkHref(block) {
  const m = block.match(/<link[^>]*href="([^"]+)"/i);
  return m?.[1];
}
function stripTags(s) {
  return s.replace(/<[^>]+>/g, "");
}

// src/sub-agents/scout/scanners/anthropic-announcements.ts
var FEEDS = [
  { feed: "news", url: "https://www.anthropic.com/news", kind: "announcement" },
  {
    feed: "engineering",
    url: "https://www.anthropic.com/engineering",
    kind: "announcement"
  },
  {
    feed: "sdk_python",
    url: "https://api.github.com/repos/anthropics/anthropic-sdk-python/releases?per_page=10",
    kind: "sdk_release"
  },
  {
    feed: "sdk_typescript",
    url: "https://api.github.com/repos/anthropics/anthropic-sdk-typescript/releases?per_page=10",
    kind: "sdk_release"
  },
  {
    feed: "cookbook",
    url: "https://api.github.com/repos/anthropics/anthropic-cookbook/commits?per_page=10",
    kind: "announcement"
  }
];
var AnthropicAnnouncementsScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "anthropic_announcements";
  async scan(since) {
    const headers = {};
    if (this.opts.githubToken) {
      headers.Authorization = `Bearer ${this.opts.githubToken}`;
    }
    const enabled = this.opts.feeds ?? FEEDS.map((f) => f.feed);
    const findings = [];
    for (const feed of FEEDS) {
      if (!enabled.includes(feed.feed)) continue;
      try {
        const res = await this.http.get(feed.url, headers);
        if (res.status >= 400) {
          this.logger.warn("anthropic_announcements non-2xx", {
            feed: feed.feed,
            status: res.status
          });
          continue;
        }
        if (feed.feed.startsWith("sdk_")) {
          const releases = JSON.parse(res.body);
          for (const r of releases) {
            const published = new Date(r.published_at);
            if (published.getTime() < since.getTime()) continue;
            findings.push({
              externalId: `anthropic:${feed.feed}:${r.id}`,
              source: this.source,
              kind: feed.kind,
              title: `Anthropic ${feed.feed === "sdk_python" ? "Python" : "TypeScript"} SDK ${r.tag_name}`,
              url: r.html_url,
              summary: (r.body ?? r.name ?? "").slice(0, 280) || r.name || r.tag_name,
              metadata: { vendor: "anthropic", feed: feed.feed, tag: r.tag_name },
              publishedAt: published,
              discoveredAt: /* @__PURE__ */ new Date()
            });
          }
        } else if (feed.feed === "cookbook") {
          const commits = JSON.parse(res.body);
          for (const c of commits) {
            const published = new Date(c.commit.author.date);
            if (published.getTime() < since.getTime()) continue;
            const title = c.commit.message.split("\n")[0] ?? c.sha;
            findings.push({
              externalId: `anthropic:cookbook:${c.sha}`,
              source: this.source,
              kind: feed.kind,
              title: `Cookbook: ${title}`,
              url: c.html_url,
              summary: c.commit.message.slice(0, 280),
              metadata: { vendor: "anthropic", feed: feed.feed, sha: c.sha },
              publishedAt: published,
              discoveredAt: /* @__PURE__ */ new Date()
            });
          }
        } else {
          const extracted = extractAnthropicNewsLinks(res.body, feed.url);
          for (const e of extracted) {
            findings.push({
              externalId: `anthropic:${feed.feed}:${e.slug}`,
              source: this.source,
              kind: feed.kind,
              title: e.title,
              url: e.url,
              summary: `Anthropic ${feed.feed} post \u2014 ${e.title}`,
              metadata: { vendor: "anthropic", feed: feed.feed },
              discoveredAt: /* @__PURE__ */ new Date()
            });
          }
        }
      } catch (err) {
        this.logger.error("anthropic_announcements scan failed", {
          feed: feed.feed,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    return findings;
  }
};
function extractAnthropicNewsLinks(html, feedUrl) {
  const seen = /* @__PURE__ */ new Set();
  const out = [];
  const pathPrefix = feedUrl.includes("/engineering") ? "/engineering/" : "/news/";
  const linkRe = new RegExp(
    `href="(${pathPrefix}[a-z0-9-]+)"[^>]*>([^<]{4,160})<`,
    "gi"
  );
  let match;
  while ((match = linkRe.exec(html)) !== null) {
    const href = match[1];
    const title = match[2];
    if (!href || !title) continue;
    const slug = href.slice(pathPrefix.length);
    if (seen.has(slug)) continue;
    seen.add(slug);
    out.push({
      slug,
      title: title.trim(),
      url: `https://www.anthropic.com${href}`
    });
    if (out.length >= 30) break;
  }
  return out;
}

// src/sub-agents/scout/scanners/conference-announcements.ts
var DEFAULT_CONFERENCES = [
  {
    name: "AI Engineer Summit",
    url: "https://www.ai.engineer/",
    titleRegex: /<h2[^>]*>([^<]{6,180})<\/h2>/gi
  }
];
var ConferenceAnnouncementsScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "conference_announcements";
  async scan(_since) {
    const confs = this.opts.conferences ?? DEFAULT_CONFERENCES;
    const findings = [];
    for (const conf of confs) {
      try {
        const res = await this.http.get(conf.url);
        if (res.status >= 400) {
          this.logger.warn("conference_announcements non-2xx", {
            conf: conf.name,
            status: res.status
          });
          continue;
        }
        const seen = /* @__PURE__ */ new Set();
        let m;
        const re = new RegExp(conf.titleRegex.source, conf.titleRegex.flags);
        while ((m = re.exec(res.body)) !== null) {
          const title = m[1]?.trim();
          if (!title) continue;
          const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 60);
          if (seen.has(slug)) continue;
          seen.add(slug);
          findings.push({
            externalId: `conf:${conf.name}:${slug}`,
            source: this.source,
            kind: "announcement",
            title: `${conf.name}: ${title}`,
            url: conf.url,
            summary: `Conference announcement from ${conf.name}: ${title}`,
            metadata: { conference: conf.name },
            discoveredAt: /* @__PURE__ */ new Date()
          });
          if (findings.length >= 30) break;
        }
      } catch (err) {
        this.logger.error("conference_announcements scan failed", {
          conf: conf.name,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    return findings;
  }
};

// src/sub-agents/scout/scanners/firecrawl-changelog.ts
var CHANGELOG_URL = "https://docs.firecrawl.dev/changelog";
var GH_RELEASES_URL = "https://api.github.com/repos/mendableai/firecrawl/releases?per_page=10";
var FirecrawlChangelogScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "firecrawl_changelog";
  async scan(since) {
    const findings = [];
    const ghHeaders = {};
    if (this.opts.githubToken) {
      ghHeaders.Authorization = `Bearer ${this.opts.githubToken}`;
    }
    try {
      const res = await this.http.get(GH_RELEASES_URL, ghHeaders);
      if (res.status < 400) {
        const releases = JSON.parse(res.body);
        for (const r of releases) {
          const published = new Date(r.published_at);
          if (published.getTime() < since.getTime()) continue;
          findings.push({
            externalId: `firecrawl:gh:${r.id}`,
            source: this.source,
            kind: "sdk_release",
            title: `Firecrawl ${r.tag_name}`,
            url: r.html_url,
            summary: (r.body ?? r.name ?? "").slice(0, 280) || r.tag_name,
            metadata: { vendor: "firecrawl", tag: r.tag_name },
            publishedAt: published,
            discoveredAt: /* @__PURE__ */ new Date()
          });
        }
      } else {
        this.logger.warn("firecrawl_changelog gh non-2xx", { status: res.status });
      }
    } catch (err) {
      this.logger.error("firecrawl_changelog gh fetch failed", {
        error: err instanceof Error ? err.message : String(err)
      });
    }
    try {
      const res = await this.http.get(CHANGELOG_URL);
      if (res.status < 400) {
        const seen = /* @__PURE__ */ new Set();
        const re = /<h(2|3)[^>]*>([^<]{4,160})<\/h\1>/gi;
        let m;
        while ((m = re.exec(res.body)) !== null) {
          const title = m[2]?.trim();
          if (!title) continue;
          const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 60);
          if (seen.has(slug)) continue;
          seen.add(slug);
          findings.push({
            externalId: `firecrawl:docs:${slug}`,
            source: this.source,
            kind: "announcement",
            title: `Firecrawl docs: ${title}`,
            url: CHANGELOG_URL,
            summary: `Firecrawl changelog entry: ${title}`,
            metadata: { vendor: "firecrawl" },
            discoveredAt: /* @__PURE__ */ new Date()
          });
          if (findings.length >= 50) break;
        }
      } else {
        this.logger.warn("firecrawl_changelog docs non-2xx", { status: res.status });
      }
    } catch (err) {
      this.logger.error("firecrawl_changelog docs fetch failed", {
        error: err instanceof Error ? err.message : String(err)
      });
    }
    return findings;
  }
};

// src/sub-agents/scout/scanners/github-trending.ts
var QUERIES = [
  "topic:agent created:>={SINCE}",
  "topic:llm-agent created:>={SINCE}",
  "topic:mcp-server created:>={SINCE}",
  "topic:claude-agent pushed:>={SINCE}"
];
var GithubTrendingScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "github_trending";
  async scan(since) {
    const sinceIso = since.toISOString().slice(0, 10);
    const headers = {
      Accept: "application/vnd.github+json",
      "X-GitHub-Api-Version": "2022-11-28"
    };
    if (this.opts.githubToken) {
      headers.Authorization = `Bearer ${this.opts.githubToken}`;
    }
    const seen = /* @__PURE__ */ new Map();
    const max = this.opts.maxResultsPerQuery ?? 15;
    for (const template of QUERIES) {
      const q = template.replace("{SINCE}", sinceIso);
      const url = `https://api.github.com/search/repositories?q=${encodeURIComponent(q)}&sort=stars&order=desc&per_page=${max}`;
      try {
        const res = await this.http.get(url, headers);
        if (res.status === 403 || res.status === 429) {
          this.logger.warn("github_trending rate-limited", {
            status: res.status,
            query: q
          });
          continue;
        }
        if (res.status >= 400) {
          this.logger.warn("github_trending non-2xx", {
            status: res.status,
            query: q
          });
          continue;
        }
        const parsed = JSON.parse(res.body);
        for (const item of parsed.items ?? []) {
          if (seen.has(item.full_name)) continue;
          seen.set(item.full_name, {
            externalId: `gh:${item.id}`,
            source: this.source,
            kind: inferKind(item.full_name, item.description ?? "", item.topics ?? []),
            title: item.full_name,
            url: item.html_url,
            summary: item.description ?? "(no description)",
            metadata: {
              stars: item.stargazers_count,
              vendor: item.owner?.login ?? "",
              topics: item.topics ?? [],
              query: q
            },
            publishedAt: new Date(item.pushed_at),
            discoveredAt: /* @__PURE__ */ new Date()
          });
        }
      } catch (err) {
        this.logger.error("github_trending scan failed", {
          query: q,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    return [...seen.values()];
  }
};
function inferKind(fullName, description, topics) {
  const lower = `${fullName} ${description} ${topics.join(" ")}`.toLowerCase();
  if (lower.includes("mcp-server") || lower.includes("mcp server")) {
    return "mcp_server";
  }
  if (lower.includes(".skill.md") || lower.includes("skill bundle")) {
    return "skill";
  }
  if (lower.includes("sdk") || lower.includes("client library")) {
    return "library";
  }
  return "github_repo";
}

// src/sub-agents/scout/scanners/google-ai-announcements.ts
var FEEDS2 = [
  {
    feed: "gemini_sdk_python",
    url: "https://api.github.com/repos/google-gemini/generative-ai-python/releases?per_page=10",
    kind: "sdk_release"
  },
  {
    feed: "gemini_sdk_node",
    url: "https://api.github.com/repos/google-gemini/generative-ai-js/releases?per_page=10",
    kind: "sdk_release"
  }
];
var GoogleAiAnnouncementsScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "google_ai_announcements";
  async scan(since) {
    const headers = {};
    if (this.opts.githubToken) {
      headers.Authorization = `Bearer ${this.opts.githubToken}`;
    }
    const findings = [];
    for (const feed of FEEDS2) {
      try {
        const res = await this.http.get(feed.url, headers);
        if (res.status >= 400) {
          this.logger.warn("google_ai_announcements non-2xx", {
            feed: feed.feed,
            status: res.status
          });
          continue;
        }
        const releases = JSON.parse(res.body);
        for (const r of releases) {
          const published = new Date(r.published_at);
          if (published.getTime() < since.getTime()) continue;
          findings.push({
            externalId: `google:${feed.feed}:${r.id}`,
            source: this.source,
            kind: feed.kind,
            title: `Google ${feed.feed} ${r.tag_name}`,
            url: r.html_url,
            summary: (r.body ?? r.name ?? "").slice(0, 280) || r.tag_name,
            metadata: { vendor: "google", feed: feed.feed, tag: r.tag_name },
            publishedAt: published,
            discoveredAt: /* @__PURE__ */ new Date()
          });
        }
      } catch (err) {
        this.logger.error("google_ai_announcements scan failed", {
          feed: feed.feed,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    return findings;
  }
};

// src/sub-agents/scout/scanners/hackernews-ai.ts
var QUERIES2 = [
  "anthropic",
  "openai",
  "claude agent",
  "mcp server",
  "ai agent",
  "agentic"
];
var HackernewsAiScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "hackernews_ai";
  async scan(since) {
    const sinceTs = Math.floor(since.getTime() / 1e3);
    const hitsPerQuery = this.opts.hitsPerQuery ?? 20;
    const minPoints = this.opts.minPoints ?? 25;
    const seen = /* @__PURE__ */ new Map();
    for (const q of QUERIES2) {
      const url = `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(q)}&tags=story&numericFilters=created_at_i>${sinceTs},points>=${minPoints}&hitsPerPage=${hitsPerQuery}`;
      try {
        const res = await this.http.get(url);
        if (res.status >= 400) {
          this.logger.warn("hackernews_ai non-2xx", { status: res.status, q });
          continue;
        }
        const parsed = JSON.parse(res.body);
        for (const hit of parsed.hits ?? []) {
          const title = hit.title ?? hit.story_title ?? "";
          const url2 = hit.url ?? hit.story_url ?? "";
          if (!title || !url2) continue;
          if (seen.has(hit.objectID)) continue;
          seen.set(hit.objectID, {
            externalId: `hn:${hit.objectID}`,
            source: this.source,
            kind: inferKind2(title),
            title,
            url: url2,
            summary: `HN: ${title} (${hit.points ?? 0} points, ${hit.num_comments ?? 0} comments)`,
            metadata: {
              points: hit.points ?? 0,
              numComments: hit.num_comments ?? 0,
              author: hit.author ?? "",
              tags: hit._tags ?? [],
              query: q
            },
            publishedAt: new Date(hit.created_at),
            discoveredAt: /* @__PURE__ */ new Date()
          });
        }
      } catch (err) {
        this.logger.error("hackernews_ai scan failed", {
          q,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    return [...seen.values()];
  }
};
function inferKind2(title) {
  const lower = title.toLowerCase();
  if (lower.startsWith("show hn")) return "tool";
  if (lower.includes("release") || lower.includes("v1.") || lower.includes("v2.")) {
    return "sdk_release";
  }
  if (lower.includes("mcp")) return "mcp_server";
  return "announcement";
}

// src/sub-agents/scout/scanners/mcp-registry.ts
var OFFICIAL_README = "https://raw.githubusercontent.com/modelcontextprotocol/servers/main/README.md";
var McpRegistryScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "mcp_registry";
  async scan(_since) {
    const urls = [OFFICIAL_README, ...this.opts.extraReadmeUrls ?? []];
    const findings = [];
    for (const url of urls) {
      try {
        const res = await this.http.get(url);
        if (res.status >= 400) {
          this.logger.warn("mcp_registry non-2xx", { url, status: res.status });
          continue;
        }
        for (const entry of extractMcpEntries(res.body, url)) {
          findings.push({
            externalId: `mcp:${entry.slug}`,
            source: this.source,
            kind: "mcp_server",
            title: entry.name,
            url: entry.url,
            summary: entry.description || `MCP server: ${entry.name}`,
            metadata: { sourceReadme: url, vendor: "modelcontextprotocol" },
            discoveredAt: /* @__PURE__ */ new Date()
          });
        }
      } catch (err) {
        this.logger.error("mcp_registry scan failed", {
          url,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    return findings;
  }
};
function extractMcpEntries(markdown, _sourceUrl) {
  const out = [];
  const seen = /* @__PURE__ */ new Set();
  const lineRe = /^\s*[-*]\s+\**\[([^\]]+)\]\((https?:\/\/[^)]+)\)\**\s*[—\-:]?\s*(.*)$/gm;
  let m;
  while ((m = lineRe.exec(markdown)) !== null) {
    const name = m[1]?.trim() ?? "";
    const url = m[2]?.trim() ?? "";
    const desc = (m[3] ?? "").trim();
    if (!name || !url) continue;
    const slug = `${name.toLowerCase().replace(/\s+/g, "-")}::${url}`;
    if (seen.has(slug)) continue;
    seen.add(slug);
    out.push({ slug, name, url, description: desc });
    if (out.length >= 200) break;
  }
  return out;
}

// src/sub-agents/scout/scanners/openai-announcements.ts
var FEEDS3 = [
  {
    feed: "sdk_python",
    url: "https://api.github.com/repos/openai/openai-python/releases?per_page=10",
    kind: "sdk_release"
  },
  {
    feed: "sdk_node",
    url: "https://api.github.com/repos/openai/openai-node/releases?per_page=10",
    kind: "sdk_release"
  },
  {
    feed: "agents_sdk",
    url: "https://api.github.com/repos/openai/openai-agents-python/releases?per_page=10",
    kind: "sdk_release"
  }
];
var OpenaiAnnouncementsScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "openai_announcements";
  async scan(since) {
    const headers = {};
    if (this.opts.githubToken) {
      headers.Authorization = `Bearer ${this.opts.githubToken}`;
    }
    const findings = [];
    for (const feed of FEEDS3) {
      try {
        const res = await this.http.get(feed.url, headers);
        if (res.status >= 400) {
          this.logger.warn("openai_announcements non-2xx", {
            feed: feed.feed,
            status: res.status
          });
          continue;
        }
        const releases = JSON.parse(res.body);
        for (const r of releases) {
          const published = new Date(r.published_at);
          if (published.getTime() < since.getTime()) continue;
          findings.push({
            externalId: `openai:${feed.feed}:${r.id}`,
            source: this.source,
            kind: feed.kind,
            title: `OpenAI ${feed.feed} ${r.tag_name}`,
            url: r.html_url,
            summary: (r.body ?? r.name ?? "").slice(0, 280) || r.tag_name,
            metadata: { vendor: "openai", feed: feed.feed, tag: r.tag_name },
            publishedAt: published,
            discoveredAt: /* @__PURE__ */ new Date()
          });
        }
      } catch (err) {
        this.logger.error("openai_announcements scan failed", {
          feed: feed.feed,
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    return findings;
  }
};

// src/sub-agents/scout/scanners/producthunt-ai.ts
var POSTS_QUERY = `
  query AiPosts($postedAfter: DateTime!) {
    posts(postedAfter: $postedAfter, topic: "artificial-intelligence", first: 20, order: VOTES) {
      edges {
        node {
          id
          name
          tagline
          url
          votesCount
          createdAt
          topics(first: 5) { edges { node { slug } } }
        }
      }
    }
  }
`;
var ProductHuntAiScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "producthunt_ai";
  async scan(since) {
    if (!this.opts.token) {
      this.logger.info(
        "producthunt_ai scanner deferred \u2014 no SCOUT_PRODUCTHUNT_TOKEN configured"
      );
      return [];
    }
    this.logger.warn(
      "producthunt_ai scanner deferred \u2014 ScoutHttpClient.post() not yet implemented",
      { sinceIso: since.toISOString(), variablesPostedAfter: since.toISOString(), querySnippet: POSTS_QUERY.slice(0, 40) }
    );
    return [];
  }
};

// src/sub-agents/scout/scanners/skills-registry.ts
var SkillsRegistryScanner = class {
  constructor(http, logger, opts = {}) {
    this.http = http;
    this.logger = logger;
    this.opts = opts;
  }
  http;
  logger;
  opts;
  source = "skills_registry";
  async scan(_since) {
    if (!this.opts.githubToken) {
      this.logger.info(
        "skills_registry scanner deferred \u2014 GitHub code search requires SCOUT_GITHUB_TOKEN"
      );
      return [];
    }
    const url = "https://api.github.com/search/code?q=" + encodeURIComponent("filename:SKILL.md OR filename:skill.md") + "&per_page=30";
    const headers = {
      Accept: "application/vnd.github+json",
      Authorization: `Bearer ${this.opts.githubToken}`
    };
    try {
      const res = await this.http.get(url, headers);
      if (res.status >= 400) {
        this.logger.warn("skills_registry non-2xx", { status: res.status });
        return [];
      }
      const parsed = JSON.parse(res.body);
      const out = [];
      const seen = /* @__PURE__ */ new Set();
      for (const item of parsed.items ?? []) {
        const key = `${item.repository.id}`;
        if (seen.has(key)) continue;
        seen.add(key);
        out.push({
          externalId: `skill:${item.repository.id}:${item.sha.slice(0, 8)}`,
          source: this.source,
          kind: "skill",
          title: `${item.repository.full_name} :: ${item.path}`,
          url: item.html_url,
          summary: item.repository.description ?? `Skill bundle in ${item.repository.full_name}`,
          metadata: {
            stars: item.repository.stargazers_count,
            vendor: item.repository.full_name.split("/")[0],
            path: item.path
          },
          discoveredAt: /* @__PURE__ */ new Date()
        });
      }
      return out;
    } catch (err) {
      this.logger.error("skills_registry scan failed", {
        error: err instanceof Error ? err.message : String(err)
      });
      return [];
    }
  }
};

// src/sub-agents/scout/recommenders/integration-recommender.ts
var EFFORT_LABEL = {
  1: "~1 day",
  5: "~1 week",
  20: "~1 month"
};
function pickRecommendation(scored) {
  const { relevanceScore, impactScore, integrationEffortDays } = scored;
  if (relevanceScore >= 70 && impactScore >= 60 && integrationEffortDays <= 5) {
    return "adopt";
  }
  if (relevanceScore >= 60 && impactScore >= 40) {
    return "prototype";
  }
  if (relevanceScore >= 45) {
    return "evaluate";
  }
  if (relevanceScore >= 25) {
    return "monitor";
  }
  return "skip";
}
function inferBenefits(scored) {
  const benefits = [];
  const components = scored.rationale.impact;
  const haystack = `${scored.finding.title} ${scored.finding.summary}`.toLowerCase();
  if (components.includes("cost-reduction signals")) {
    benefits.push("Lower per-call cost or token spend.");
  }
  if (components.includes("capability-unlock signals")) {
    benefits.push("Unlocks a capability the current stack lacks.");
  }
  if (components.includes("customer-facing signals")) {
    benefits.push("Direct customer-visible upgrade.");
  }
  if (scored.matchedSubAgent) {
    benefits.push(
      `Direct fit for ${scored.matchedSubAgent.name} (${scored.matchedSubAgent.module}).`
    );
  }
  if (haystack.includes("mcp") || scored.finding.kind === "mcp_server") {
    benefits.push("Plugs into the F2 MCP layer with no bespoke adapter.");
  }
  if (benefits.length === 0) {
    benefits.push("Adds optionality without displacing existing components.");
  }
  return benefits.slice(0, 3);
}
function inferRisk(scored) {
  const { integrationEffortDays, finding } = scored;
  const stars = Number(finding.metadata.stars ?? 0);
  if (integrationEffortDays === 20) {
    return "Significant integration scope \u2014 risk of distraction from priority sub-agent work.";
  }
  if (stars > 0 && stars < 100) {
    return "Early-stage project \u2014 API stability and maintainer commitment unproven.";
  }
  if (finding.kind === "service") {
    return "External service dependency \u2014 pricing, SLAs, and data residency need diligence.";
  }
  return "Low integration risk given drop-in shape \u2014 main risk is opportunity cost.";
}
function inferNextAction(scored, rec) {
  const { finding, matchedSubAgent } = scored;
  switch (rec) {
    case "adopt":
      return matchedSubAgent ? `Open a feat PR adopting ${finding.title} into the ${matchedSubAgent.module} module.` : `Open a feat PR adopting ${finding.title}.`;
    case "prototype":
      return `Timebox a 1-week spike: integrate ${finding.title} against ${matchedSubAgent?.name ?? "the relevant sub-agent"} and compare to current baseline.`;
    case "evaluate":
      return `Read ${finding.url} end-to-end; write a 1-page comparison vs. current approach; decide next step.`;
    case "monitor":
      return `Add to the Scout watch list \u2014 re-evaluate if stars/MRR/adoption signal strengthens.`;
    case "skip":
      return `Mark as not relevant \u2014 do not resurface.`;
  }
}
var IntegrationRecommender = class {
  recommend(scored) {
    const recommendation = pickRecommendation(scored);
    const benefits = inferBenefits(scored);
    const risk = inferRisk(scored);
    const nextAction = inferNextAction(scored, recommendation);
    const { finding, matchedSubAgent, integrationEffortDays } = scored;
    const currentApproachLine = matchedSubAgent ? `Currently ${matchedSubAgent.name} handles this in ${matchedSubAgent.module}.` : `No direct overlap with an existing sub-agent.`;
    const briefing = `${finding.title} \u2014 ${finding.summary} ${currentApproachLine} Estimated integration effort: ${EFFORT_LABEL[integrationEffortDays]}.`;
    return {
      finding: scored,
      recommendation,
      briefing,
      benefits,
      risk,
      nextAction
    };
  }
};

// src/sub-agents/scout/registry.ts
var DEFAULT_SUB_AGENT_REGISTRY = [
  {
    name: "Olive",
    module: "proactive-surfacing",
    role: "Operator-facing surfacer for cross-domain proactive signals.",
    capabilityTags: [
      "proactive",
      "surfacing",
      "telegram",
      "calibration",
      "morning brief",
      "rate limit",
      "debounce"
    ],
    knownGaps: ["smarter relevance scoring", "richer dashboard channel"]
  },
  {
    name: "Vera",
    module: "compliance-freshness",
    role: "Regulator watcher + compliance drift detector (AUSTRAC, ASIC, ATO, AHPRA, NDIS, OAIC, state gazettes).",
    capabilityTags: [
      "compliance",
      "regulator",
      "AUSTRAC",
      "ASIC",
      "ATO",
      "AHPRA",
      "NDIS",
      "OAIC",
      "drift",
      "scraping",
      "rss",
      "monitor",
      "firecrawl",
      "scheduled"
    ],
    knownGaps: ["lower-token regulator scraping", "incremental scraping", "change-only monitoring"]
  },
  {
    name: "Cora",
    module: "compliance-freshness",
    role: "Compliance intervention drafter \u2014 composes the operator-facing comms about regulatory changes.",
    capabilityTags: ["compliance", "drafting", "citation", "regulatory comms"]
  },
  {
    name: "Nyx",
    module: "relationship-cadence",
    role: "Relationship cadence heat-scorer + intervention recommender + outreach drafter.",
    capabilityTags: [
      "crm",
      "relationship",
      "outreach",
      "cadence",
      "hubspot",
      "salesforce",
      "engagement",
      "drafting",
      "personality"
    ],
    knownGaps: ["lightweight contact enrichment", "non-Hubspot CRM connectors"]
  },
  {
    name: "Ledger",
    module: "cost-monitoring",
    role: "Per-customer + per-model + per-agent spend tracker with anomaly detection.",
    capabilityTags: [
      "cost",
      "spend",
      "billing",
      "token",
      "anthropic",
      "openai",
      "pricing",
      "anomaly",
      "margin"
    ],
    knownGaps: ["multi-provider pricing feeds", "real-time spend dashboards"]
  },
  {
    name: "Tally",
    module: "cost-monitoring",
    role: "Model recommendation engine \u2014 suggests Haiku/Sonnet/Opus swaps for cost savings.",
    capabilityTags: ["model selection", "haiku", "sonnet", "opus", "cost optimization"],
    knownGaps: ["model evals", "side-by-side quality benchmarking"]
  },
  {
    name: "Mira",
    module: "pattern-analysis",
    role: "F7 cross-customer pattern detector + insight ranker \u2014 the flagship intelligence engine.",
    capabilityTags: [
      "pattern",
      "intelligence",
      "clustering",
      "insight",
      "cross-customer",
      "embeddings",
      "vector"
    ],
    knownGaps: [
      "embedding-based clustering",
      "vector search",
      "topic modelling",
      "richer signal sources"
    ]
  },
  {
    name: "LearningMind",
    module: "cross-agent-learning",
    role: "F3 fleet-wide error aggregation + capability emergence detector + rollout orchestrator.",
    capabilityTags: [
      "error",
      "observability",
      "telemetry",
      "rollout",
      "feature flag",
      "evaluation",
      "evals",
      "learning"
    ],
    knownGaps: ["agent-evals frameworks", "automated regression test generation"]
  },
  {
    name: "agent-kit MCP layer",
    module: "mcp",
    role: "F2 MCP server integration layer \u2014 registers MCP servers as first-class tool providers.",
    capabilityTags: [
      "mcp",
      "model context protocol",
      "tool",
      "tool calling",
      "stdio",
      "anthropic"
    ],
    knownGaps: ["streaming HTTP transport polish", "MCP server discovery", "broader MCP catalog coverage"]
  },
  {
    name: "agent-kit Voice",
    module: "voice",
    role: "Voice ingress + STT abstraction.",
    capabilityTags: ["voice", "stt", "transcription", "whisper", "deepgram", "audio"],
    knownGaps: ["cheaper STT providers", "real-time streaming STT"]
  },
  {
    name: "agent-kit Photo",
    module: "photo",
    role: "Photo orchestrator \u2014 vision extraction with Zod-typed schemas.",
    capabilityTags: ["vision", "photo", "ocr", "image", "extraction", "multimodal"],
    knownGaps: ["lower-cost vision models", "batch vision processing"]
  },
  {
    name: "agent-kit Personality",
    module: "personality",
    role: "Personality framework \u2014 voice/tone/system-prompt composition per surface.",
    capabilityTags: ["personality", "tone", "system prompt", "voice", "prompt engineering"]
  }
];
function flattenedCapabilityTags(registry) {
  const seen = /* @__PURE__ */ new Set();
  for (const entry of registry) {
    for (const tag of entry.capabilityTags) {
      seen.add(tag.toLowerCase());
    }
    for (const gap of entry.knownGaps ?? []) {
      seen.add(gap.toLowerCase());
    }
  }
  return [...seen];
}

// src/sub-agents/scout/scorers/impact-scorer.ts
var COST_REDUCTION_TERMS = [
  "token reduction",
  "cheaper",
  "lower cost",
  "cost savings",
  "save tokens",
  "fewer tokens",
  "/monitor",
  "incremental",
  "diff",
  "cache",
  "prompt cache",
  "batch api",
  "haiku"
];
var CAPABILITY_UNLOCK_TERMS = [
  "voice",
  "video",
  "ocr",
  "vision",
  "embedding",
  "vector",
  "rerank",
  "search",
  "memory",
  "long context",
  "tool use",
  "computer use",
  "reasoning",
  "extended thinking",
  "stt",
  "tts",
  "rag",
  "knowledge graph",
  "browser",
  "scraping"
];
var CUSTOMER_FACING_TERMS = [
  "dashboard",
  "ui",
  "widget",
  "report",
  "briefing",
  "telegram",
  "slack",
  "email",
  "whatsapp",
  "morning brief",
  "insight",
  "notification"
];
var BRAND_TERMS = [
  "anthropic",
  "openai",
  "model context protocol",
  "mcp",
  "agentic",
  "agent skill",
  "sub-agent"
];
function countMatches(text, terms) {
  let n = 0;
  for (const t of terms) if (text.includes(t)) n++;
  return n;
}
function bandToScore(matches, cap) {
  if (matches === 0) return 0;
  if (matches === 1) return Math.round(cap * 0.5);
  if (matches === 2) return Math.round(cap * 0.75);
  return cap;
}
var ImpactScorer = class {
  score(finding) {
    const haystack = `${finding.title}
${finding.summary}`.toLowerCase();
    const cr = countMatches(haystack, COST_REDUCTION_TERMS);
    const cu = countMatches(haystack, CAPABILITY_UNLOCK_TERMS);
    const cf = countMatches(haystack, CUSTOMER_FACING_TERMS);
    const br = countMatches(haystack, BRAND_TERMS);
    const components = {
      costReduction: bandToScore(cr, 25),
      capabilityUnlock: bandToScore(cu, 25),
      customerFacing: bandToScore(cf, 25),
      brand: bandToScore(br, 25)
    };
    const score = Math.min(
      100,
      components.costReduction + components.capabilityUnlock + components.customerFacing + components.brand
    );
    const parts = [];
    if (components.costReduction > 0)
      parts.push(`cost-reduction signals=${cr}`);
    if (components.capabilityUnlock > 0)
      parts.push(`capability-unlock signals=${cu}`);
    if (components.customerFacing > 0)
      parts.push(`customer-facing signals=${cf}`);
    if (components.brand > 0) parts.push(`brand signals=${br}`);
    return {
      score,
      rationale: parts.length ? parts.join("; ") : "no impact signals matched",
      components
    };
  }
};

// src/sub-agents/scout/scorers/integration-effort-scorer.ts
var ONE_MONTH_TERMS = [
  "platform",
  "framework",
  "rewrite",
  "migration",
  "self-hosted",
  "kubernetes",
  "infra",
  "infrastructure",
  "oauth provider",
  "saas suite"
];
var ONE_DAY_KINDS = [
  "mcp_server",
  "sdk_release",
  "skill"
];
var IntegrationEffortScorer = class {
  score(finding) {
    const haystack = `${finding.title}
${finding.summary}`.toLowerCase();
    if (ONE_MONTH_TERMS.some((t) => haystack.includes(t))) {
      return {
        effortDays: 20,
        rationale: "platform-shape signal \u2014 likely requires schema + UX work"
      };
    }
    if (ONE_DAY_KINDS.includes(finding.kind)) {
      return {
        effortDays: 1,
        rationale: `drop-in kind (${finding.kind}) \u2014 direct register-and-go`
      };
    }
    if (finding.kind === "library" || finding.kind === "service") {
      return {
        effortDays: 5,
        rationale: `${finding.kind} \u2014 needs a thin adapter`
      };
    }
    return { effortDays: 5, rationale: "default 1-week estimate" };
  }
};

// src/sub-agents/scout/scorers/relevance-scorer.ts
var REPUTABLE_VENDORS = /* @__PURE__ */ new Set([
  "anthropic",
  "openai",
  "google",
  "deepmind",
  "huggingface",
  "modelcontextprotocol",
  "vercel",
  "stripe",
  "firecrawl",
  "linear",
  "cloudflare"
]);
var HORIZONTAL_NOISE_TERMS = [
  "ai girlfriend",
  "ai boyfriend",
  "writing assistant",
  "chatbot builder",
  "chat with your pdf",
  "instagram caption",
  "tinder bio",
  "ai art generator",
  "ai song",
  "ai meme"
];
var CLOSED_STACK_TERMS = [
  "salesforce",
  "servicenow",
  ".net core",
  "sap fiori",
  "powerapps",
  "dynamics 365"
];
var AGENT_TERMS = ["agent", "agentic", "sub-agent", "subagent", "multi-agent"];
var SENTINEL_SDK_VENDORS = ["anthropic", "openai", "google", "modelcontextprotocol"];
var RelevanceScorer = class {
  constructor(registry, focus) {
    this.registry = registry;
    this.focus = focus;
  }
  registry;
  focus;
  score(finding) {
    const haystack = `${finding.title}
${finding.summary}`.toLowerCase();
    const reasons = [];
    let score = 0;
    let matched;
    for (const entry of this.registry) {
      const tagHit = entry.capabilityTags.find(
        (tag) => haystack.includes(tag.toLowerCase())
      );
      if (tagHit) {
        score += 35;
        matched = entry;
        reasons.push(`augments ${entry.name} (tag: "${tagHit}")`);
        break;
      }
    }
    for (const entry of this.registry) {
      for (const gap of entry.knownGaps ?? []) {
        if (haystack.includes(gap.toLowerCase())) {
          score += 20;
          if (!matched) matched = entry;
          reasons.push(`addresses ${entry.name} gap ("${gap}")`);
          break;
        }
      }
    }
    if (finding.kind === "mcp_server") {
      score += 15;
      reasons.push("MCP server (directly augments F2 layer)");
    } else if (finding.kind === "skill") {
      score += 15;
      reasons.push("packaged skill bundle");
    } else if (finding.kind === "sdk_release") {
      const vendor = String(finding.metadata.vendor ?? "").toLowerCase();
      if (SENTINEL_SDK_VENDORS.some((v) => vendor.includes(v))) {
        score += 15;
        reasons.push(`SDK release for vendor Sentinel uses (${vendor})`);
      }
    }
    if (AGENT_TERMS.some((t) => haystack.includes(t))) {
      score += 15;
      reasons.push("agent/agentic terminology present");
    }
    const publishedAt = finding.publishedAt ?? finding.discoveredAt;
    const ageMs = Date.now() - publishedAt.getTime();
    if (ageMs <= 30 * 24 * 60 * 60 * 1e3) {
      score += 10;
      reasons.push("published in last 30 days");
    }
    const stars = Number(finding.metadata.stars ?? 0);
    const mrrUsd = Number(finding.metadata.mrrUsd ?? 0);
    const vendorMeta = String(finding.metadata.vendor ?? "").toLowerCase();
    if (stars > 500 || mrrUsd > 1e3 || REPUTABLE_VENDORS.has(vendorMeta)) {
      score += 5;
      reasons.push("stability signal (stars/MRR/reputable vendor)");
    }
    if (this.focus) {
      const tagBoost = (this.focus.additionalTags ?? []).find(
        (t) => haystack.includes(t.toLowerCase())
      );
      if (tagBoost) {
        score += 10;
        reasons.push(`focus tag matched ("${tagBoost}")`);
      }
      if (matched && (this.focus.prioritisedSubAgents ?? []).includes(matched.name)) {
        score += 10;
        reasons.push(`focus priority for sub-agent ${matched.name}`);
      }
    }
    if (HORIZONTAL_NOISE_TERMS.some((t) => haystack.includes(t))) {
      score -= 25;
      reasons.push("horizontal AI noise term");
    }
    if (CLOSED_STACK_TERMS.some((t) => haystack.includes(t))) {
      score -= 20;
      reasons.push("closed/non-Sentinel stack");
    }
    const finalScore = Math.max(0, Math.min(100, score));
    return {
      score: finalScore,
      matchedSubAgent: matched,
      rationale: reasons.length ? reasons.join("; ") : "no signals matched"
    };
  }
};

// src/sub-agents/scout/surfacer/digest-builder.ts
var RECOMMENDATION_ICON = {
  adopt: "GO",
  prototype: "SPIKE",
  evaluate: "READ",
  monitor: "WATCH",
  skip: "PASS"
};
var DigestBuilder = class {
  constructor(opts = {}) {
    this.opts = opts;
  }
  opts;
  build(recommendations) {
    const topN = this.opts.topN ?? 3;
    const header = this.opts.header ?? "Scout's picks";
    if (recommendations.length === 0) {
      return `*${header}*

_No new high-relevance findings this scan._`;
    }
    const top = recommendations.slice().sort(
      (a, b) => b.finding.priorityScore - a.finding.priorityScore
    ).slice(0, topN);
    const lines = [`*${header}*`, ""];
    for (let i = 0; i < top.length; i++) {
      const r = top[i];
      if (!r) continue;
      const icon = RECOMMENDATION_ICON[r.recommendation] ?? "?";
      const { finding } = r;
      const subAgent = finding.matchedSubAgent ? ` \u2192 ${finding.matchedSubAgent.name}` : "";
      lines.push(
        `${i + 1}. *[${icon}] ${finding.finding.title}*${subAgent}`,
        `   ${r.briefing}`
      );
      if (r.benefits.length > 0) {
        lines.push(`   _Why:_ ${r.benefits.join(" \xB7 ")}`);
      }
      lines.push(`   _Risk:_ ${r.risk}`);
      lines.push(`   _Next:_ ${r.nextAction}`);
      lines.push(
        `   _Relevance ${finding.relevanceScore} \xB7 Impact ${finding.impactScore} \xB7 Priority ${finding.priorityScore.toFixed(1)} \xB7 ${finding.finding.url}_`
      );
      lines.push("");
    }
    return lines.join("\n").trimEnd();
  }
};

// src/sub-agents/scout/surfacer/inbox-pusher.ts
var InboxPusher = class {
  /** Holds events pushed via the default in-memory queue. Test-friendly. */
  inMemoryQueue = [];
  push;
  expiryHours;
  constructor(opts = {}) {
    this.expiryHours = opts.expiryHours ?? 24 * 7;
    if (opts.pushCandidate) {
      this.push = opts.pushCandidate;
    } else {
      this.push = async (event) => {
        this.inMemoryQueue.push(event);
      };
    }
  }
  async pushAll(recommendations) {
    for (const r of recommendations) {
      if (r.recommendation === "skip") continue;
      const event = {
        id: `scout-${r.finding.finding.source}-${r.finding.finding.externalId}`,
        sourceName: "intel",
        subAgentBadge: "scout",
        payload: {
          title: r.finding.finding.title,
          url: r.finding.finding.url,
          summary: r.finding.finding.summary,
          recommendation: r.recommendation,
          briefing: r.briefing,
          benefits: r.benefits,
          risk: r.risk,
          nextAction: r.nextAction,
          relevanceScore: r.finding.relevanceScore,
          impactScore: r.finding.impactScore,
          integrationEffortDays: r.finding.integrationEffortDays,
          priorityScore: r.finding.priorityScore,
          matchedSubAgent: r.finding.matchedSubAgent?.name,
          baseRelevance: Math.max(
            0,
            Math.min(1, r.finding.priorityScore / 100)
          )
        },
        occurredAt: r.finding.finding.discoveredAt,
        expiresAt: new Date(
          r.finding.finding.discoveredAt.getTime() + this.expiryHours * 60 * 60 * 1e3
        )
      };
      await this.push(event);
    }
  }
};

// src/sub-agents/scout/scout.ts
var Scout = class {
  subAgentRegistry;
  sourcesConfig;
  focus;
  http;
  logger;
  store;
  scanners;
  relevanceScorer;
  impactScorer;
  effortScorer;
  recommender;
  digestBuilder;
  inboxPusher;
  constructor(opts = {}) {
    this.subAgentRegistry = opts.subAgentRegistry ?? DEFAULT_SUB_AGENT_REGISTRY;
    this.sourcesConfig = opts.sources ?? {};
    this.focus = opts.focus;
    this.http = opts.http ?? new FetchScoutHttpClient();
    this.logger = opts.logger ?? new ConsoleScoutLogger();
    this.store = opts.store ?? new InMemoryScoutFindingStore();
    this.relevanceScorer = new RelevanceScorer(
      this.subAgentRegistry,
      this.focus
    );
    this.impactScorer = new ImpactScorer();
    this.effortScorer = new IntegrationEffortScorer();
    this.recommender = new IntegrationRecommender();
    this.digestBuilder = new DigestBuilder();
    this.inboxPusher = opts.inboxPusher ?? new InboxPusher();
    this.scanners = opts.scanners ?? this.defaultScanners(opts);
  }
  /** Returns the inbox pusher so consumers can read its in-memory queue. */
  get inbox() {
    return this.inboxPusher;
  }
  /** Returns the finding store so consumers can inspect history. */
  get findings() {
    return this.store;
  }
  defaultScanners(opts) {
    const allScanners = [
      {
        source: "github_trending",
        build: () => new GithubTrendingScanner(this.http, this.logger, {
          githubToken: opts.githubToken
        })
      },
      {
        source: "hackernews_ai",
        build: () => new HackernewsAiScanner(this.http, this.logger)
      },
      {
        source: "anthropic_announcements",
        build: () => new AnthropicAnnouncementsScanner(this.http, this.logger, {
          githubToken: opts.githubToken
        })
      },
      {
        source: "openai_announcements",
        build: () => new OpenaiAnnouncementsScanner(this.http, this.logger, {
          githubToken: opts.githubToken
        })
      },
      {
        source: "google_ai_announcements",
        build: () => new GoogleAiAnnouncementsScanner(this.http, this.logger, {
          githubToken: opts.githubToken
        })
      },
      {
        source: "mcp_registry",
        build: () => new McpRegistryScanner(this.http, this.logger)
      },
      {
        source: "skills_registry",
        build: () => new SkillsRegistryScanner(this.http, this.logger, {
          githubToken: opts.githubToken
        })
      },
      {
        source: "ai_newsletter_feeds",
        build: () => new AiNewsletterFeedsScanner(this.http, this.logger)
      },
      {
        source: "conference_announcements",
        build: () => new ConferenceAnnouncementsScanner(this.http, this.logger)
      },
      {
        source: "firecrawl_changelog",
        build: () => new FirecrawlChangelogScanner(this.http, this.logger, {
          githubToken: opts.githubToken
        })
      },
      {
        source: "producthunt_ai",
        build: () => new ProductHuntAiScanner(this.http, this.logger, {
          token: opts.productHuntToken
        })
      }
    ];
    return allScanners.filter(({ source }) => this.sourcesConfig[source] !== false).map(({ build }) => build());
  }
  // ─────────────────────────────────────────
  // Public operations
  // ─────────────────────────────────────────
  /**
   * Run every enabled scanner once, score everything, push to inbox, and
   * return a digest plus the top picks.
   */
  async dailyScan(options = {}) {
    const start = Date.now();
    const sinceHours = options.sinceHours ?? 24;
    const since = new Date(start - sinceHours * 60 * 60 * 1e3);
    const scanId = `scan_${(0, import_crypto.randomUUID)()}`;
    const ranAt = new Date(start);
    const scannersRun = [];
    const scannersSkipped = [];
    let allFindings = [];
    const settled = await Promise.allSettled(
      this.scanners.map(async (s) => {
        const findings = await s.scan(since);
        return { source: s.source, findings };
      })
    );
    for (const r of settled) {
      if (r.status === "fulfilled") {
        scannersRun.push(r.value.source);
        allFindings = allFindings.concat(r.value.findings);
      } else {
        this.logger.error("scout scanner failed", {
          error: r.reason instanceof Error ? r.reason.message : String(r.reason)
        });
      }
    }
    const byUrl = /* @__PURE__ */ new Map();
    for (const f of allFindings) {
      const key = `${f.url}`;
      if (!byUrl.has(key)) byUrl.set(key, f);
    }
    const deduped = [...byUrl.values()];
    const newOnes = [];
    for (const f of deduped) {
      if (await this.store.isNew(f.source, f.externalId)) {
        newOnes.push(f);
      }
    }
    const scored = newOnes.map((f) => this.scoreFinding(f));
    for (const s of scored) {
      await this.store.upsert(s);
    }
    const recommendations = scored.filter((s) => s.relevanceScore >= 25).map((s) => this.recommender.recommend(s));
    await this.inboxPusher.pushAll(recommendations);
    const topN = options.topN ?? 3;
    const topPicks = recommendations.slice().sort((a, b) => b.finding.priorityScore - a.finding.priorityScore).slice(0, topN);
    const digestBuilder = new DigestBuilder({ topN });
    const digest = digestBuilder.build(topPicks);
    const allConfiguredSources = this.scanners.map((s) => s.source);
    const totalSources = [
      "github_trending",
      "hackernews_ai",
      "anthropic_announcements",
      "openai_announcements",
      "google_ai_announcements",
      "mcp_registry",
      "skills_registry",
      "ai_newsletter_feeds",
      "conference_announcements",
      "firecrawl_changelog",
      "producthunt_ai"
    ];
    for (const s of totalSources) {
      if (!allConfiguredSources.includes(s)) scannersSkipped.push(s);
    }
    return {
      scanId,
      ranAt,
      scannersRun,
      scannersSkipped,
      totalFindings: deduped.length,
      newFindings: newOnes.length,
      topPicks,
      digest,
      durationMs: Date.now() - start
    };
  }
  /**
   * Aggregate the last 7 days (configurable) into a weekly summary digest.
   */
  async weeklyDigest(options = {}) {
    const sinceHours = options.sinceHours ?? 7 * 24;
    const windowEnd = /* @__PURE__ */ new Date();
    const windowStart = new Date(
      windowEnd.getTime() - sinceHours * 60 * 60 * 1e3
    );
    const topN = options.topN ?? 5;
    const stored = await this.store.listTopRanked(windowStart, topN);
    const recommendations = stored.map((s) => this.recommender.recommend(s));
    const builder = new DigestBuilder({
      topN,
      header: `Scout \u2014 weekly picks (${windowStart.toISOString().slice(0, 10)} \u2192 ${windowEnd.toISOString().slice(0, 10)})`
    });
    return {
      windowStart,
      windowEnd,
      totalFindings: stored.length,
      topPicks: recommendations,
      digest: builder.build(recommendations)
    };
  }
  /**
   * One-off evaluation of a specific URL. Builds a synthetic ScoutFinding,
   * scores it, and returns the recommendation. Used when an operator
   * wants Scout's view on a specific tool they encountered.
   */
  async evaluateTool(url, options = {}) {
    const finding = {
      externalId: `eval:${url}`,
      source: "github_trending",
      // closest semantic — manual eval
      kind: options.kind ?? "tool",
      title: options.title ?? url,
      url,
      summary: options.summary ?? `Manual evaluation request for ${url}`,
      metadata: options.metadata ?? {},
      discoveredAt: /* @__PURE__ */ new Date()
    };
    const scored = this.scoreFinding(finding);
    return this.recommender.recommend(scored);
  }
  // ─────────────────────────────────────────
  // Internal — scoring composition
  // ─────────────────────────────────────────
  scoreFinding(finding) {
    const relevance = this.relevanceScorer.score(finding);
    const impact = this.impactScorer.score(finding);
    const effort = this.effortScorer.score(finding);
    const effortPenalty = effort.effortDays === 20 ? 15 : effort.effortDays === 5 ? 5 : 0;
    const priorityScore = Math.max(
      0,
      Math.min(
        100,
        relevance.score * 0.5 + impact.score * 0.4 + 10 - effortPenalty
      )
    );
    return {
      finding,
      relevanceScore: relevance.score,
      impactScore: impact.score,
      integrationEffortDays: effort.effortDays,
      priorityScore,
      matchedSubAgent: relevance.matchedSubAgent,
      rationale: {
        relevance: relevance.rationale,
        impact: impact.rationale,
        effort: effort.rationale
      }
    };
  }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  AiNewsletterFeedsScanner,
  AnthropicAnnouncementsScanner,
  ConferenceAnnouncementsScanner,
  ConsoleScoutLogger,
  DEFAULT_SUB_AGENT_REGISTRY,
  DigestBuilder,
  FetchScoutHttpClient,
  FirecrawlChangelogScanner,
  GithubTrendingScanner,
  GoogleAiAnnouncementsScanner,
  HackernewsAiScanner,
  ImpactScorer,
  InMemoryScoutFindingStore,
  InMemoryScoutHttpClient,
  InboxPusher,
  IntegrationEffortScorer,
  IntegrationRecommender,
  McpRegistryScanner,
  OpenaiAnnouncementsScanner,
  ProductHuntAiScanner,
  RelevanceScorer,
  SCOUT_USER_AGENT,
  Scout,
  SilentScoutLogger,
  SkillsRegistryScanner,
  flattenedCapabilityTags
});
//# sourceMappingURL=index.js.map