diff --git a/README.md b/README.md
index ac552ed8f..1fc8e6c21 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,11 @@
[](https://www.npmjs.com/package/rea-agents)
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
-[](#89-tools-for-investigation)
+[](#tool-catalog-for-investigation)
[](https://nodejs.org/)
[](LICENSE)
-[Quick start](#quick-start) · [Current status](#current-status) · [Investigation model](#the-investigation-model) · [89 tools](#89-tools-for-investigation) · [Roadmap](#roadmap) · [How it works](#how-it-works)
+[Quick start](#quick-start) · [Current status](#current-status) · [Investigation model](#the-investigation-model) · [Tool catalog](#tool-catalog-for-investigation) · [Roadmap](#roadmap) · [How it works](#how-it-works)
@@ -320,7 +320,7 @@ REA handles the app analysis in steps 1–5. The agent performs step 6 with its
- Analyze Swift and Objective-C metadata without manually untangling every mangled symbol.
- Leave names, comments, and bookmarks in Hopper so human and agent analysis reinforce each other.
-## 89 tools for investigation
+## Tool catalog for investigation
| Tool family | Count | Examples |
| ------------------------- | ----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
diff --git a/README_ar.md b/README_ar.md
index 35715586c..6c693e856 100644
--- a/README_ar.md
+++ b/README_ar.md
@@ -10,11 +10,11 @@
[](https://www.npmjs.com/package/rea-agents)
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
-[](#منصة-من-89-أداة)
+[](#منصة-أدوات-التحقيق)
[](https://nodejs.org/)
[](LICENSE)
-[البدء السريع](#البدء-السريع) · [من الملف التنفيذي إلى السلوك](#من-الملف-التنفيذي-إلى-السلوك) · [89 أداة](#منصة-من-89-أداة) · [كيف يعمل](#كيف-يعمل) · [الأسئلة الشائعة](#الأسئلة-الشائعة)
+[البدء السريع](#البدء-السريع) · [من الملف التنفيذي إلى السلوك](#من-الملف-التنفيذي-إلى-السلوك) · [دليل الأدوات](#منصة-أدوات-التحقيق) · [كيف يعمل](#كيف-يعمل) · [الأسئلة الشائعة](#الأسئلة-الشائعة)
@@ -157,7 +157,7 @@ rea uninstall --purge-data
افحص التحقق من الإدخال، والتشفير، والصلاحيات، والتخزين، ومسارات الأخطاء من دون رفع الملف التنفيذي إلى خدمة بعيدة.
-## منصة من 89 أداة
+## منصة أدوات التحقيق
| عائلة الأدوات | العدد | أمثلة |
| --------------------- | ----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
diff --git a/README_ja.md b/README_ja.md
index 84405f1bd..a6ddce770 100644
--- a/README_ja.md
+++ b/README_ja.md
@@ -10,11 +10,11 @@
[](https://www.npmjs.com/package/rea-agents)
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
-[](#89-ツールのワークベンチ)
+[](#調査ツールカタログ)
[](https://nodejs.org/)
[](LICENSE)
-[クイックスタート](#クイックスタート) · [バイナリから動作へ](#バイナリから動作へ) · [89 ツール](#89-ツールのワークベンチ) · [仕組み](#仕組み) · [FAQ](#faq)
+[クイックスタート](#クイックスタート) · [バイナリから動作へ](#バイナリから動作へ) · [ツールカタログ](#調査ツールカタログ) · [仕組み](#仕組み) · [FAQ](#faq)
@@ -147,7 +147,7 @@ REA は手順 1〜5 のバイナリ解析を処理し、手順 6 はエージェ
- Swift / Objective-C のメタデータを解析する。
- Hopper に名前、コメント、ブックマークを残し、人間とエージェントの調査を共有する。
-## 89 ツールのワークベンチ
+## 調査ツールカタログ
| ツール群 | 数 | 例 |
| ---------------------- | --: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
diff --git a/README_ko.md b/README_ko.md
index 5b11f0eef..244f590f3 100644
--- a/README_ko.md
+++ b/README_ko.md
@@ -10,11 +10,11 @@
[](https://www.npmjs.com/package/rea-agents)
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
-[](#89개-도구로-구성된-워크벤치)
+[](#조사-도구-카탈로그)
[](https://nodejs.org/)
[](LICENSE)
-[빠른 시작](#빠른-시작) · [바이너리에서 동작까지](#바이너리에서-동작까지) · [89개 도구](#89개-도구로-구성된-워크벤치) · [작동 방식](#작동-방식) · [FAQ](#faq)
+[빠른 시작](#빠른-시작) · [바이너리에서 동작까지](#바이너리에서-동작까지) · [도구 카탈로그](#조사-도구-카탈로그) · [작동 방식](#작동-방식) · [FAQ](#faq)
@@ -147,7 +147,7 @@ REA는 1–5단계의 바이너리 분석을 처리합니다. 6단계는 에이
- Swift 및 Objective-C 메타데이터를 분석합니다.
- Hopper에 이름, 주석, 북마크를 남겨 사람과 에이전트의 분석을 연결합니다.
-## 89개 도구로 구성된 워크벤치
+## 조사 도구 카탈로그
| 도구 그룹 | 수 | 예시 |
| ------------------- | --: | --------------------------------------------------------------------------------------------------------------------------------------------- |
diff --git a/README_zh.md b/README_zh.md
index 456674d9d..9d30dd0a8 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -10,11 +10,11 @@
[](https://www.npmjs.com/package/rea-agents)
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
-[](#89-个工具组成的工作台)
+[](#调查工具目录)
[](https://nodejs.org/)
[](LICENSE)
-[快速开始](#快速开始) · [从二进制到行为](#从二进制到行为) · [89 个工具](#89-个工具组成的工作台) · [工作原理](#工作原理) · [常见问题](#常见问题)
+[快速开始](#快速开始) · [从二进制到行为](#从二进制到行为) · [工具目录](#调查工具目录) · [工作原理](#工作原理) · [常见问题](#常见问题)
@@ -147,7 +147,7 @@ REA 负责第 1–5 步中的二进制分析。第 6 步由智能体使用其常
- 分析 Swift 和 Objective-C 元数据。
- 在 Hopper 中留下名称、注释与书签,使人与智能体的分析互相增强。
-## 89 个工具组成的工作台
+## 调查工具目录
| 工具类别 | 数量 | 示例 |
| -------------- | ---: | ------------------------------------------------------------------------------------------------------------------------- |
diff --git a/docs/architecture.mermaid b/docs/architecture.mermaid
index 091fcf491..db6384914 100644
--- a/docs/architecture.mermaid
+++ b/docs/architecture.mermaid
@@ -33,7 +33,7 @@ graph TD
end
subgraph "Contracts Layer"
- CONTRACTS["src/contracts/
Caller-visible tool schemas
86 analysis, managed, browser, Electron, application, and session tools"]
+ CONTRACTS["src/contracts/
Caller-visible schemas for analysis, managed, browser, Electron, application, and session tools"]
end
subgraph "Domain Layer"
diff --git a/docs/mcp-prompts.md b/docs/mcp-prompts.md
index e6349a580..ff2ce7aec 100644
--- a/docs/mcp-prompts.md
+++ b/docs/mcp-prompts.md
@@ -1,7 +1,7 @@
# Guided MCP prompts and completion
REA exposes six provider-neutral investigation workflows through the MCP
-`prompts` capability. They complement the 89 tools; they do not add, remove, or
+`prompts` capability. They complement the generated tool catalog; they do not add, remove, or
invoke tools by themselves.
## Prompt inventory
diff --git a/scripts/generate-skill-metadata.mjs b/scripts/generate-skill-metadata.mjs
new file mode 100644
index 000000000..3c683aca6
--- /dev/null
+++ b/scripts/generate-skill-metadata.mjs
@@ -0,0 +1,35 @@
+import { readFile } from "node:fs/promises";
+import { dirname, join } from "node:path";
+import { fileURLToPath, pathToFileURL } from "node:url";
+
+import { ensureGeneratedFile } from "./lib/generated-file.mjs";
+
+const arguments_ = new Set(process.argv.slice(2));
+for (const argument of arguments_)
+ if (argument !== "--check")
+ throw new Error(`Unknown skill metadata option: ${argument}`);
+
+const root = join(dirname(fileURLToPath(import.meta.url)), "..");
+const path = join(root, "skills/rea-analysis/SKILL.md");
+const current = await readFile(path, "utf8");
+const { CATALOG_IDENTITY } = await import(
+ `${pathToFileURL(join(root, "dist/catalogIdentity.js")).href}?${String(Date.now())}`
+);
+const withCount = current.replace(
+ /^\s{2}tool_count:\s*\d+\s*$/mu,
+ ` tool_count: ${String(CATALOG_IDENTITY.counts.mcp_tools)}`,
+);
+const digestLine = ` catalog_digest: "${CATALOG_IDENTITY.digests.combined_sha256}"`;
+const source = /^\s{2}catalog_digest:/mu.test(withCount)
+ ? withCount.replace(
+ /^\s{2}catalog_digest:\s*"[a-f0-9]{64}"\s*$/mu,
+ digestLine,
+ )
+ : withCount.replace(/^(\s{2}tool_count:\s*\d+\s*)$/mu, `$1\n${digestLine}`);
+
+await ensureGeneratedFile({
+ path,
+ source,
+ check: arguments_.has("--check"),
+ generateCommand: "npm run docs:generate",
+});
diff --git a/scripts/lib/docs-facts.mjs b/scripts/lib/docs-facts.mjs
index 4171544a1..3c14951b2 100644
--- a/scripts/lib/docs-facts.mjs
+++ b/scripts/lib/docs-facts.mjs
@@ -10,26 +10,20 @@ const README_PATHS = [
];
const familyLabel = (id) => (id === "electron" ? "Electron" : id);
-const tableCounts = (content, path, total) => {
+const tableCounts = (content, path, expectedCounts) => {
const lines = content.split(/\r?\n/u);
- const headingIndex = lines.findIndex(
- (line) => line.startsWith("## ") && line.includes(String(total)),
- );
- if (headingIndex === -1) {
- throw new Error(`Missing ${total}-tool heading in ${path}`);
- }
- const counts = [];
- for (const line of lines.slice(headingIndex + 1)) {
- if (line.startsWith("## ")) break;
- if (!line.trim().startsWith("|") || /^\|\s*-/u.test(line)) continue;
- const count = Number(line.split("|")[2]?.trim());
- if (!Number.isInteger(count)) {
- if (counts.length === 0) continue;
- throw new Error(`Non-numeric tool count in ${path}`);
+ for (const [index, line] of lines.entries()) {
+ if (!/^\|\s*-/u.test(line)) continue;
+ const counts = [];
+ for (const row of lines.slice(index + 1)) {
+ if (!row.trim().startsWith("|")) break;
+ const count = Number(row.split("|")[2]?.trim());
+ if (!Number.isInteger(count)) break;
+ counts.push(count);
}
- counts.push(count);
+ if (counts.length === expectedCounts.length) return counts;
}
- return counts;
+ throw new Error(`Missing tool-family inventory table in ${path}`);
};
const requireText = (issues, path, content, expected) => {
@@ -42,16 +36,11 @@ export const documentationFactIssues = async (root, catalog) => {
const expectedCounts = catalog.tools.families.map(({ count }) => count);
for (const path of README_PATHS) {
const content = await readFile(join(root, path), "utf8");
- requireText(
- issues,
- path,
- content,
- `MCP_tools-${String(catalog.tools.total)}`,
- );
+ requireText(issues, path, content, "MCP-tool_catalog");
for (const client of catalog.setup_clients)
requireText(issues, path, content, client.display_name);
try {
- const actualCounts = tableCounts(content, path, catalog.tools.total);
+ const actualCounts = tableCounts(content, path, expectedCounts);
if (JSON.stringify(actualCounts) !== JSON.stringify(expectedCounts))
issues.push(
`${path}: tool family counts ${JSON.stringify(actualCounts)} do not match ${JSON.stringify(expectedCounts)}`,
diff --git a/skills/rea-analysis/SKILL.md b/skills/rea-analysis/SKILL.md
index a82349914..280c2111f 100644
--- a/skills/rea-analysis/SKILL.md
+++ b/skills/rea-analysis/SKILL.md
@@ -4,6 +4,7 @@ description: Reverse engineer native, Electron/JavaScript, and web applications
metadata:
version: "20"
tool_count: 89
+ catalog_digest: "59253c96793f162725d5dc08e611c1e0c19938b23de1e9490dcc1cc800dde955"
---
# REA
diff --git a/src/application/Doctor.ts b/src/application/Doctor.ts
index 1df5b9e7c..4bbfa28c7 100644
--- a/src/application/Doctor.ts
+++ b/src/application/Doctor.ts
@@ -77,10 +77,18 @@ export interface DoctorHost {
providerInspections?(): Promise;
installationPaths?(): Promise;
installedSkillVersion?(): Promise;
+ installedSkillIdentity?(): Promise;
clientRegistrations?(): Promise;
javascriptReplayCheck?(): Promise;
}
+/** Parsed identity committed by an installed REA skill. */
+interface InstalledSkillIdentity {
+ readonly version: string | null;
+ readonly toolCount: number | null;
+ readonly catalogDigest: string | null;
+}
+
/**
* Check requirements and an optional target without mutating the host.
* Every failed check includes remediation suitable for either the one-shot CLI
@@ -109,7 +117,9 @@ export const runDoctor = async (
};
readonly skill: {
readonly installed_version: string | null;
- readonly state: "aligned" | "stale" | "unknown";
+ readonly installed_tool_count: number | null;
+ readonly installed_catalog_digest: string | null;
+ readonly state: "aligned" | "stale" | "missing";
readonly remediation: string | null;
};
readonly registrations: readonly ClientRegistrationStatus[];
@@ -216,7 +226,37 @@ export const runDoctor = async (
}),
);
const installationPaths = (await host.installationPaths?.()) ?? [];
- const installedSkillVersion = await host.installedSkillVersion?.();
+ const observedSkillIdentity = await host.installedSkillIdentity?.();
+ const legacySkillVersion =
+ observedSkillIdentity === undefined
+ ? await host.installedSkillVersion?.()
+ : undefined;
+ const installedSkillIdentity =
+ observedSkillIdentity ??
+ (legacySkillVersion === undefined
+ ? undefined
+ : {
+ version: legacySkillVersion,
+ toolCount: null,
+ catalogDigest: null,
+ });
+ const installedSkillVersion = installedSkillIdentity?.version ?? undefined;
+ const skillAligned =
+ installedSkillIdentity?.version === PRODUCT_IDENTITY.skillVersion &&
+ installedSkillIdentity.toolCount === CATALOG_IDENTITY.counts.mcp_tools &&
+ installedSkillIdentity.catalogDigest ===
+ CATALOG_IDENTITY.digests.combined_sha256;
+ if (!skillAligned)
+ checks.push({
+ name: "skill:identity",
+ ok: false,
+ classification: "config_drift",
+ detail:
+ installedSkillIdentity === undefined
+ ? "Installed REA skill identity is missing."
+ : "Installed REA skill identity is stale.",
+ remediation: "Run rea setup to update the installed REA skill.",
+ });
const registrations = (await host.clientRegistrations?.()) ?? [];
for (const registration of registrations)
if (registration.state !== "aligned")
@@ -263,17 +303,17 @@ export const runDoctor = async (
},
skill: {
installed_version: installedSkillVersion ?? null,
+ installed_tool_count: installedSkillIdentity?.toolCount ?? null,
+ installed_catalog_digest: installedSkillIdentity?.catalogDigest ?? null,
state:
- installedSkillVersion === undefined
- ? "unknown"
- : installedSkillVersion === PRODUCT_IDENTITY.skillVersion
+ installedSkillIdentity === undefined
+ ? "missing"
+ : skillAligned
? "aligned"
: "stale",
- remediation:
- installedSkillVersion === undefined ||
- installedSkillVersion === PRODUCT_IDENTITY.skillVersion
- ? null
- : "Run rea setup to update the installed REA skill.",
+ remediation: skillAligned
+ ? null
+ : "Run rea setup to update the installed REA skill.",
},
registrations,
},
@@ -407,6 +447,33 @@ export const systemDoctorHost = (
return undefined;
}
},
+ async installedSkillIdentity() {
+ try {
+ const content = await readFile(
+ join(
+ homedir(),
+ ".agents/skills",
+ PRODUCT_IDENTITY.skillName,
+ "SKILL.md",
+ ),
+ "utf8",
+ );
+ const version =
+ /^\s{2}version:\s*"([^"]+)"\s*$/mu.exec(content)?.[1] ?? null;
+ const countText = /^\s{2}tool_count:\s*(\d+)\s*$/mu.exec(content)?.[1];
+ const catalogDigest =
+ /^\s{2}catalog_digest:\s*"([a-f0-9]{64})"\s*$/mu.exec(content)?.[1] ??
+ null;
+ return {
+ version,
+ toolCount:
+ countText === undefined ? null : Number.parseInt(countText, 10),
+ catalogDigest,
+ };
+ } catch {
+ return undefined;
+ }
+ },
clientRegistrations: () => readClientRegistrationStatuses(homedir()),
});
diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts
index e7ed68d2c..b949912b0 100644
--- a/tests/doctor.test.ts
+++ b/tests/doctor.test.ts
@@ -1,5 +1,7 @@
import { describe, expect, it } from "vitest";
import { runDoctor, type DoctorHost } from "../src/application/Doctor.js";
+import { CATALOG_IDENTITY } from "../src/catalogIdentity.js";
+import { PRODUCT_IDENTITY } from "../src/identity.js";
const host = (overrides: Partial = {}): DoctorHost => ({
platform: "darwin",
@@ -12,6 +14,12 @@ const host = (overrides: Partial = {}): DoctorHost => ({
linuxDemoRuntimeReady: () => Promise.resolve(true),
brewHopperPath: () => Promise.resolve(undefined),
manualHopperPaths: () => Promise.resolve([]),
+ installedSkillIdentity: () =>
+ Promise.resolve({
+ version: PRODUCT_IDENTITY.skillVersion,
+ toolCount: CATALOG_IDENTITY.counts.mcp_tools,
+ catalogDigest: CATALOG_IDENTITY.digests.combined_sha256,
+ }),
...overrides,
});
@@ -85,7 +93,12 @@ describe("doctor", () => {
host({
installationPaths: () =>
Promise.resolve(["/usr/local/bin/rea", "/home/user/bin/rea"]),
- installedSkillVersion: () => Promise.resolve("10"),
+ installedSkillIdentity: () =>
+ Promise.resolve({
+ version: "10",
+ toolCount: null,
+ catalogDigest: null,
+ }),
}),
);
@@ -99,6 +112,25 @@ describe("doctor", () => {
},
});
});
+ it("reports a missing installed skill as unhealthy", async () => {
+ const result = await runDoctor(
+ undefined,
+ host({ installedSkillIdentity: () => Promise.resolve(undefined) }),
+ );
+
+ expect(result.healthy).toBe(false);
+ expect(result.identity?.skill).toMatchObject({
+ state: "missing",
+ remediation: "Run rea setup to update the installed REA skill.",
+ });
+ expect(result.checks).toContainEqual({
+ name: "skill:identity",
+ ok: false,
+ classification: "config_drift",
+ detail: "Installed REA skill identity is missing.",
+ remediation: "Run rea setup to update the installed REA skill.",
+ });
+ });
it("reports stale client registration paths without claiming live state", async () => {
const result = await runDoctor(
undefined,
diff --git a/tests/ghidraDoctor.test.ts b/tests/ghidraDoctor.test.ts
index 7dd2edc6c..fd798614e 100644
--- a/tests/ghidraDoctor.test.ts
+++ b/tests/ghidraDoctor.test.ts
@@ -7,6 +7,8 @@ import {
type GhidraInstallationInspection,
} from "../src/ghidra/GhidraInstallation.js";
import { projectGhidraDoctorInspection } from "../src/ghidra/GhidraDoctor.js";
+import { CATALOG_IDENTITY } from "../src/catalogIdentity.js";
+import { PRODUCT_IDENTITY } from "../src/identity.js";
const INSTALL = "/opt/ghidra_12.1.2_PUBLIC";
const installationHost = (
@@ -65,6 +67,12 @@ const doctorHost = (
manualHopperPaths: () => Promise.resolve([]),
providerInspections: () =>
Promise.resolve([projectGhidraDoctorInspection(ghidra)]),
+ installedSkillIdentity: () =>
+ Promise.resolve({
+ version: PRODUCT_IDENTITY.skillVersion,
+ toolCount: CATALOG_IDENTITY.counts.mcp_tools,
+ catalogDigest: CATALOG_IDENTITY.digests.combined_sha256,
+ }),
});
describe("Ghidra doctor integration", () => {
diff --git a/tests/identity.test.ts b/tests/identity.test.ts
index 98fca65bd..8243f482a 100644
--- a/tests/identity.test.ts
+++ b/tests/identity.test.ts
@@ -14,7 +14,8 @@ describe("package identity", () => {
scripts: z.object({
start: z.string(),
prepare: z.string(),
- prepack: z.string().optional(),
+ prepack: z.string(),
+ prepublishOnly: z.string(),
}),
})
.parse(
@@ -30,6 +31,11 @@ describe("package identity", () => {
expect(PRODUCT_IDENTITY.mcpCommand).toBe(`npx -y ${packageJson.name} mcp`);
expect(packageJson.scripts.start).toBe("node scripts/rea.mjs mcp");
expect(packageJson.scripts.prepare).toBe("npm run build && husky");
- expect(packageJson.scripts.prepack).toBeUndefined();
+ expect(packageJson.scripts.prepack).toBe(
+ "npm run deps:check && node scripts/generate-skill-metadata.mjs --check",
+ );
+ expect(packageJson.scripts.prepublishOnly).toBe(
+ "npm run deps:check && node scripts/generate-skill-metadata.mjs --check",
+ );
});
});
diff --git a/tests/readmeConsistency.test.ts b/tests/readmeConsistency.test.ts
index 12af7244e..ce0a56c8d 100644
--- a/tests/readmeConsistency.test.ts
+++ b/tests/readmeConsistency.test.ts
@@ -6,7 +6,6 @@ import {
ENHANCED_TOOL_CONTRACTS,
OFFICIAL_TOOL_CONTRACTS,
SESSION_TOOL_CONTRACTS,
- TOOL_CONTRACTS,
} from "../src/contracts/toolContracts.js";
import { NATIVE_TOOL_CONTRACTS } from "../src/contracts/nativeToolContracts.js";
import { ARTIFACT_TOOL_CONTRACTS } from "../src/contracts/artifactToolContracts.js";
@@ -39,30 +38,18 @@ const expectedToolCounts = [
const toolCountsFromReadme = (content: string, path: string): number[] => {
const lines = content.split(/\r?\n/u);
- const headingIndex = lines.findIndex((line) =>
- new RegExp(`^## .*${String(TOOL_CONTRACTS.length)}`, "u").test(line),
- );
- if (headingIndex === -1)
- throw new Error(
- `Missing ${String(TOOL_CONTRACTS.length)}-tool heading in ${path}`,
- );
-
- const counts: number[] = [];
- for (const line of lines.slice(headingIndex + 1)) {
- if (line.startsWith("## ")) break;
- if (!line.trim().startsWith("|") || /^\|\s*-/.test(line)) continue;
- const cells = line.split("|").map((cell) => cell.trim());
- const countCell = cells[2];
- if (countCell === undefined)
- throw new Error(`Malformed table row in ${path}`);
- const count = Number(countCell);
- if (!Number.isInteger(count)) {
- if (counts.length === 0) continue;
- throw new Error(`Non-numeric tool count in ${path}`);
+ for (const [index, line] of lines.entries()) {
+ if (!/^\|\s*-/u.test(line)) continue;
+ const counts: number[] = [];
+ for (const row of lines.slice(index + 1)) {
+ if (!row.trim().startsWith("|")) break;
+ const count = Number(row.split("|")[2]?.trim());
+ if (!Number.isInteger(count)) break;
+ counts.push(count);
}
- counts.push(count);
+ if (counts.length === expectedToolCounts.length) return counts;
}
- return counts;
+ throw new Error(`Missing tool-family inventory table in ${path}`);
};
describe("localized README product facts", () => {
@@ -87,7 +74,7 @@ describe("localized README product facts", () => {
expect(content).toContain(client.displayName);
if (path === "README_ar.md")
expect(content).toContain("Windows غير مدعوم حاليًا");
- expect(content).toContain(`MCP_tools-${String(TOOL_CONTRACTS.length)}`);
+ expect(content).toContain("MCP-tool_catalog");
expect(toolCountsFromReadme(content, path)).toEqual(expectedToolCounts);
},
);