twitter-developer-api 2.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.claude-plugin/marketplace.json +60 -0
  2. package/.claude-plugin/plugin.json +45 -0
  3. package/.codex-plugin/plugin.json +61 -0
  4. package/.mcp.json +8 -0
  5. package/LICENSE +21 -0
  6. package/README.md +279 -0
  7. package/assets/icon.svg +1 -0
  8. package/commands/post.md +15 -0
  9. package/commands/search.md +16 -0
  10. package/commands/trending.md +17 -0
  11. package/commands/user.md +19 -0
  12. package/context7.json +33 -0
  13. package/logo.png +0 -0
  14. package/openclaw.plugin.json +25 -0
  15. package/package.json +79 -0
  16. package/scripts/check-versions.mjs +381 -0
  17. package/scripts/release-guard/content-policy.mjs +337 -0
  18. package/scripts/release-guard/frontmatter.mjs +148 -0
  19. package/scripts/release-guard/policy.mjs +223 -0
  20. package/server.json +18 -0
  21. package/skills/x-twitter-scraper/SKILL.md +387 -0
  22. package/skills/x-twitter-scraper/metadata.json +38 -0
  23. package/skills/x-twitter-scraper/references/api-endpoints-api-keys.md +31 -0
  24. package/skills/x-twitter-scraper/references/api-endpoints-compose.md +33 -0
  25. package/skills/x-twitter-scraper/references/api-endpoints-credits.md +11 -0
  26. package/skills/x-twitter-scraper/references/api-endpoints-drafts.md +86 -0
  27. package/skills/x-twitter-scraper/references/api-endpoints-draws.md +75 -0
  28. package/skills/x-twitter-scraper/references/api-endpoints-error-codes.md +37 -0
  29. package/skills/x-twitter-scraper/references/api-endpoints-events.md +48 -0
  30. package/skills/x-twitter-scraper/references/api-endpoints-extractions.md +97 -0
  31. package/skills/x-twitter-scraper/references/api-endpoints-monitors.md +74 -0
  32. package/skills/x-twitter-scraper/references/api-endpoints-radar.md +49 -0
  33. package/skills/x-twitter-scraper/references/api-endpoints-support.md +47 -0
  34. package/skills/x-twitter-scraper/references/api-endpoints-trends.md +25 -0
  35. package/skills/x-twitter-scraper/references/api-endpoints-tweet-style-cache.md +155 -0
  36. package/skills/x-twitter-scraper/references/api-endpoints-webhooks.md +74 -0
  37. package/skills/x-twitter-scraper/references/api-endpoints-x-accounts.md +42 -0
  38. package/skills/x-twitter-scraper/references/api-endpoints-x-api.md +206 -0
  39. package/skills/x-twitter-scraper/references/api-endpoints-x-media.md +40 -0
  40. package/skills/x-twitter-scraper/references/api-endpoints-x-write.md +204 -0
  41. package/skills/x-twitter-scraper/references/api-endpoints.md +30 -0
  42. package/skills/x-twitter-scraper/references/draws.md +57 -0
  43. package/skills/x-twitter-scraper/references/extractions.md +237 -0
  44. package/skills/x-twitter-scraper/references/mcp-setup.md +273 -0
  45. package/skills/x-twitter-scraper/references/mcp-tools.md +174 -0
  46. package/skills/x-twitter-scraper/references/python-examples.md +182 -0
  47. package/skills/x-twitter-scraper/references/security.md +124 -0
  48. package/skills/x-twitter-scraper/references/types-account-identity.md +9 -0
  49. package/skills/x-twitter-scraper/references/types-account.md +26 -0
  50. package/skills/x-twitter-scraper/references/types-api-keys.md +22 -0
  51. package/skills/x-twitter-scraper/references/types-bookmarks-and-timeline.md +46 -0
  52. package/skills/x-twitter-scraper/references/types-credits.md +11 -0
  53. package/skills/x-twitter-scraper/references/types-download-media.md +22 -0
  54. package/skills/x-twitter-scraper/references/types-draws.md +60 -0
  55. package/skills/x-twitter-scraper/references/types-error.md +10 -0
  56. package/skills/x-twitter-scraper/references/types-events.md +43 -0
  57. package/skills/x-twitter-scraper/references/types-extractions.md +106 -0
  58. package/skills/x-twitter-scraper/references/types-mcp-add-webhook.md +14 -0
  59. package/skills/x-twitter-scraper/references/types-mcp-check-follow.md +10 -0
  60. package/skills/x-twitter-scraper/references/types-mcp-compose-tweet.md +31 -0
  61. package/skills/x-twitter-scraper/references/types-mcp-estimate-extraction.md +15 -0
  62. package/skills/x-twitter-scraper/references/types-mcp-get-account.md +26 -0
  63. package/skills/x-twitter-scraper/references/types-mcp-get-draw.md +30 -0
  64. package/skills/x-twitter-scraper/references/types-mcp-get-events.md +19 -0
  65. package/skills/x-twitter-scraper/references/types-mcp-get-trends.md +16 -0
  66. package/skills/x-twitter-scraper/references/types-mcp-get-user-info.md +16 -0
  67. package/skills/x-twitter-scraper/references/types-mcp-list-monitors.md +15 -0
  68. package/skills/x-twitter-scraper/references/types-mcp-lookup-tweet.md +25 -0
  69. package/skills/x-twitter-scraper/references/types-mcp-output-schemas.md +22 -0
  70. package/skills/x-twitter-scraper/references/types-mcp-refine-tweet.md +13 -0
  71. package/skills/x-twitter-scraper/references/types-mcp-run-draw.md +18 -0
  72. package/skills/x-twitter-scraper/references/types-mcp-run-extraction.md +12 -0
  73. package/skills/x-twitter-scraper/references/types-mcp-score-tweet.md +16 -0
  74. package/skills/x-twitter-scraper/references/types-mcp-search-tweets.md +17 -0
  75. package/skills/x-twitter-scraper/references/types-mcp-test-webhook.md +11 -0
  76. package/skills/x-twitter-scraper/references/types-monitors.md +20 -0
  77. package/skills/x-twitter-scraper/references/types-radar.md +40 -0
  78. package/skills/x-twitter-scraper/references/types-request-bodies.md +30 -0
  79. package/skills/x-twitter-scraper/references/types-rest-api-vs-mcp-field-naming.md +15 -0
  80. package/skills/x-twitter-scraper/references/types-support.md +25 -0
  81. package/skills/x-twitter-scraper/references/types-trends.md +18 -0
  82. package/skills/x-twitter-scraper/references/types-tweet-drafts.md +20 -0
  83. package/skills/x-twitter-scraper/references/types-tweet-style-cache.md +50 -0
  84. package/skills/x-twitter-scraper/references/types-user-activity.md +35 -0
  85. package/skills/x-twitter-scraper/references/types-webhooks.md +38 -0
  86. package/skills/x-twitter-scraper/references/types-x-accounts.md +16 -0
  87. package/skills/x-twitter-scraper/references/types-x-api.md +69 -0
  88. package/skills/x-twitter-scraper/references/types-x-articles.md +21 -0
  89. package/skills/x-twitter-scraper/references/types-x-write.md +39 -0
  90. package/skills/x-twitter-scraper/references/types.md +48 -0
  91. package/skills/x-twitter-scraper/references/usage.md +50 -0
  92. package/skills/x-twitter-scraper/references/webhooks.md +243 -0
  93. package/skills/x-twitter-scraper/references/workflows.md +202 -0
  94. package/skills/x-twitter-scraper/skill-card.md +76 -0
  95. package/skills/x-twitter-scraper/skillspector-report.md +17 -0
  96. package/skills/xquik-social-research/LICENSE +21 -0
  97. package/skills/xquik-social-research/SKILL.md +77 -0
  98. package/skills.sh.json +77 -0
  99. package/start.sh +2 -0
  100. package/stub-server.mjs +209 -0
  101. package/task-guides/check-mutuals.md +55 -0
  102. package/task-guides/clean-followers.md +71 -0
  103. package/task-guides/export-tweets-csv.md +70 -0
  104. package/task-guides/extract-followers.md +83 -0
  105. package/task-guides/find-bangers.md +61 -0
  106. package/task-guides/find-influencers.md +72 -0
  107. package/task-guides/find-viral-tweets.md +80 -0
  108. package/task-guides/follow-unfollow.md +83 -0
  109. package/task-guides/for-you-feed.md +63 -0
  110. package/task-guides/going-viral.md +68 -0
  111. package/task-guides/grow-followers.md +66 -0
  112. package/task-guides/monitor-accounts.md +81 -0
  113. package/task-guides/optimize-tweets.md +80 -0
  114. package/task-guides/post-tweets.md +108 -0
  115. package/task-guides/run-giveaway.md +90 -0
  116. package/task-guides/search-tweets.md +122 -0
  117. package/task-guides/send-dms.md +92 -0
  118. package/task-guides/top-replies.md +71 -0
  119. package/task-guides/track-competitors.md +73 -0
  120. package/task-guides/track-hashtags.md +86 -0
  121. package/task-guides/track-mentions.md +88 -0
  122. package/task-guides/trending-news.md +68 -0
  123. package/task-guides/tweet-analytics.md +129 -0
  124. package/task-guides/tweet-ideas.md +65 -0
  125. package/task-guides/tweet-replies.md +82 -0
  126. package/task-guides/tweet-style.md +72 -0
  127. package/task-guides/tweet-webhooks.md +84 -0
  128. package/task-guides/update-x-profile.md +75 -0
  129. package/task-guides/user-tweets.md +148 -0
  130. package/task-guides/who-liked.md +68 -0
  131. package/task-guides/who-quoted.md +68 -0
  132. package/task-guides/who-retweeted.md +67 -0
  133. package/task-guides/write-threads.md +72 -0
  134. package/task-guides/write-tweets.md +84 -0
  135. package/task-guides/x-articles.md +84 -0
  136. package/task-guides/x-bookmarks.md +63 -0
  137. package/task-guides/x-communities.md +71 -0
  138. package/task-guides/x-lists.md +70 -0
  139. package/task-guides/x-spaces.md +67 -0
  140. package/task-guides/x-trends.md +74 -0
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "twitter-developer-api",
3
+ "version": "2.5.3",
4
+ "description": "Xquik agent skill & plugin bundle for REST, MCP, webhooks, exports & confirmation-gated X workflows. Not affiliated with X Corp.",
5
+ "author": {
6
+ "name": "Xquik",
7
+ "url": "https://docs.xquik.com"
8
+ },
9
+ "license": "MIT",
10
+ "homepage": "https://docs.xquik.com",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/Xquik-dev/x-twitter-scraper.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/Xquik-dev/x-twitter-scraper/issues"
17
+ },
18
+ "keywords": [
19
+ "twitter",
20
+ "x-api",
21
+ "twitter-api",
22
+ "twitter-api-alternative",
23
+ "x-api-alternative",
24
+ "twitter-scraper-api",
25
+ "twitter-data-api",
26
+ "x-data-api",
27
+ "tweet-search-api",
28
+ "tweet-analytics",
29
+ "twitter-monitoring",
30
+ "twitter-follower-export",
31
+ "x-twitter-scraper",
32
+ "social-listening",
33
+ "sentiment-analysis",
34
+ "competitor-monitoring",
35
+ "mcp",
36
+ "mcp-server",
37
+ "x-mcp",
38
+ "agent-skill",
39
+ "skills",
40
+ "ai-agent",
41
+ "claude-code",
42
+ "codex",
43
+ "cursor",
44
+ "copilot",
45
+ "gemini-cli",
46
+ "windsurf",
47
+ "data-extraction",
48
+ "twitter-scraper",
49
+ "social-media",
50
+ "webhooks",
51
+ "monitoring",
52
+ "giveaway",
53
+ "usage-estimates",
54
+ "rest-api",
55
+ "scraper"
56
+ ],
57
+ "files": [
58
+ ".codex-plugin",
59
+ ".claude-plugin",
60
+ ".mcp.json",
61
+ "assets",
62
+ "commands",
63
+ "context7.json",
64
+ "logo.png",
65
+ "server.json",
66
+ "scripts",
67
+ "skills",
68
+ "!skills/_artifacts",
69
+ "skills.sh.json",
70
+ "openclaw.plugin.json",
71
+ "start.sh",
72
+ "stub-server.mjs",
73
+ "task-guides"
74
+ ],
75
+ "scripts": {
76
+ "check-versions": "node scripts/check-versions.mjs",
77
+ "prepublishOnly": "node scripts/check-versions.mjs"
78
+ }
79
+ }
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Pre-publish / pre-commit guard: fails if any known version surface
4
+ // disagrees with package.json. Registry metadata caches from these files, so
5
+ // drift across surfaces ships an inconsistent release. See Xquik-dev/xquik#2024.
6
+
7
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
+ import { join, dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ import { collectFrontmatterDrifts } from "./release-guard/frontmatter.mjs";
12
+ import { contentChecks } from "./release-guard/content-policy.mjs";
13
+ import {
14
+ jsonFieldExpectations,
15
+ manifestReferences,
16
+ markdownRoots,
17
+ skillFrontmatterExpectations,
18
+ taskGuideFrontmatterExpectations,
19
+ versionSurfaces,
20
+ } from "./release-guard/policy.mjs";
21
+
22
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
23
+ const expected = readJson("package.json").version;
24
+ const taskGuidePaths = readdirSync(join(root, "task-guides"))
25
+ .filter((fileName) => fileName.endsWith(".md"))
26
+ .map((fileName) => `task-guides/${fileName}`);
27
+ const taskGuideNames = new Set(
28
+ taskGuidePaths.map((path) => path.slice("task-guides/".length, -3)),
29
+ );
30
+
31
+ function readText(path) {
32
+ return readFileSync(join(root, path), "utf8");
33
+ }
34
+
35
+ function readJson(path) {
36
+ return JSON.parse(readText(path));
37
+ }
38
+
39
+ function asArray(value) {
40
+ return Array.isArray(value) ? value : [value];
41
+ }
42
+
43
+ function collectVersionDrifts() {
44
+ const drifts = [];
45
+ for (const surface of versionSurfaces) {
46
+ const raw = readText(surface.path);
47
+ for (const version of asArray(surface.get(raw))) {
48
+ if (version !== expected) {
49
+ drifts.push(
50
+ ` ${surface.path}: ${version ?? "<missing>"} (expected ${expected})`,
51
+ );
52
+ }
53
+ }
54
+ }
55
+ return drifts;
56
+ }
57
+
58
+ function collectContentDrifts() {
59
+ const drifts = [];
60
+ for (const check of contentChecks) {
61
+ const raw = readText(check.path);
62
+ drifts.push(...checkRequiredContent(check, raw));
63
+ drifts.push(...checkForbiddenContent(check, raw));
64
+ }
65
+ return drifts;
66
+ }
67
+
68
+ function checkRequiredContent(check, raw) {
69
+ const drifts = [];
70
+ for (const required of check.required) {
71
+ if (!raw.includes(required)) {
72
+ drifts.push(` ${check.path}: missing "${required}"`);
73
+ }
74
+ }
75
+ return drifts;
76
+ }
77
+
78
+ function checkForbiddenContent(check, raw) {
79
+ const drifts = [];
80
+ for (const forbidden of check.forbidden) {
81
+ const isMatch =
82
+ typeof forbidden === "string"
83
+ ? raw.includes(forbidden)
84
+ : forbidden.pattern.test(raw);
85
+ if (isMatch) {
86
+ const label =
87
+ typeof forbidden === "string" ? `"${forbidden}"` : forbidden.label;
88
+ drifts.push(` ${check.path}: stale ${label}`);
89
+ }
90
+ }
91
+ return drifts;
92
+ }
93
+
94
+ function collectSkillMetadataDrifts() {
95
+ const primarySkillPath = "skills/x-twitter-scraper/SKILL.md";
96
+ const portableSkillPaths = readdirSync(join(root, "skills"))
97
+ .filter((dir) => dir !== "x-twitter-scraper")
98
+ .map((dir) => `skills/${dir}/SKILL.md`);
99
+ return [
100
+ ...collectFrontmatterPolicyDrifts(
101
+ [primarySkillPath],
102
+ skillFrontmatterExpectations,
103
+ ),
104
+ ...collectFrontmatterPolicyDrifts(portableSkillPaths, {}),
105
+ ];
106
+ }
107
+
108
+ function collectTaskGuideMetadataDrifts() {
109
+ return collectFrontmatterPolicyDrifts(
110
+ taskGuidePaths,
111
+ withPackageVersion(taskGuideFrontmatterExpectations),
112
+ );
113
+ }
114
+
115
+ function collectFrontmatterPolicyDrifts(paths, expectations) {
116
+ const drifts = [];
117
+ for (const path of paths) {
118
+ drifts.push(...collectFrontmatterDrifts(path, readText(path), expectations));
119
+ }
120
+ return drifts;
121
+ }
122
+
123
+ function withPackageVersion(expectations) {
124
+ return {
125
+ ...expectations,
126
+ scalars: {
127
+ ...expectations.scalars,
128
+ "metadata.version": expected,
129
+ },
130
+ };
131
+ }
132
+
133
+ function collectTaskGuideUsageLanguageDrifts() {
134
+ const drifts = [];
135
+ for (const path of taskGuidePaths) {
136
+ const raw = readText(path);
137
+ if (raw.includes("| Free") || raw.includes("free read-only")) {
138
+ drifts.push(` ${path}: use "Included" instead of pricing-style "Free"`);
139
+ }
140
+ if (raw.includes("sibling skills") || raw.includes("` skill")) {
141
+ drifts.push(` ${path}: task guides must not be labeled skills`);
142
+ }
143
+ }
144
+ return drifts;
145
+ }
146
+
147
+ function collectSkillsShGroupingDrifts() {
148
+ const drifts = [];
149
+ const skillsSh = readJson("skills.sh.json");
150
+ const groupedSkills = new Set();
151
+ for (const group of skillsSh.groupings ?? []) {
152
+ for (const skill of group.skills ?? []) {
153
+ groupedSkills.add(skill);
154
+ const hasPrimarySkill = skill === "x-twitter-scraper";
155
+ const hasTaskGuide = taskGuideNames.has(skill);
156
+ if (!hasPrimarySkill && !hasTaskGuide) {
157
+ drifts.push(` skills.sh.json: grouped skill "${skill}" has no guide`);
158
+ }
159
+ }
160
+ }
161
+ for (const skill of taskGuideNames) {
162
+ if (!groupedSkills.has(skill)) {
163
+ drifts.push(` skills.sh.json: missing task guide "${skill}"`);
164
+ }
165
+ }
166
+ return drifts;
167
+ }
168
+
169
+ function collectMarkdownLinkDrifts() {
170
+ const drifts = [];
171
+ for (const path of collectMarkdownPaths()) {
172
+ const raw = readText(path);
173
+ for (const target of extractMarkdownLinks(raw)) {
174
+ const cleanTarget = target.split("#", 1)[0];
175
+ if (cleanTarget === "" || isExternalLink(cleanTarget)) {
176
+ continue;
177
+ }
178
+ const resolved = join(root, dirname(path), cleanTarget);
179
+ if (!existsSync(resolved)) {
180
+ drifts.push(` ${path}: broken markdown link "${target}"`);
181
+ }
182
+ }
183
+ }
184
+ return drifts;
185
+ }
186
+
187
+ function collectManifestReferenceDrifts() {
188
+ const drifts = [];
189
+ for (const [path, selector] of manifestReferences) {
190
+ const manifest = readJson(path);
191
+ for (const target of asArray(readSelector(manifest, selector))) {
192
+ if (typeof target !== "string" || !target.startsWith("./")) {
193
+ continue;
194
+ }
195
+ const resolved = join(root, target);
196
+ if (!existsSync(resolved)) {
197
+ drifts.push(
198
+ ` ${path}: missing manifest reference "${selector}" -> ${target}`,
199
+ );
200
+ }
201
+ }
202
+ }
203
+ return drifts;
204
+ }
205
+
206
+ function collectJsonFieldDrifts() {
207
+ const drifts = [];
208
+ for (const expectation of jsonFieldExpectations) {
209
+ const data = readJson(expectation.path);
210
+ for (const [selector, expectedValue] of Object.entries(
211
+ expectation.fields,
212
+ )) {
213
+ const actualValue = readSelector(data, selector);
214
+ if (actualValue !== expectedValue) {
215
+ drifts.push(
216
+ ` ${expectation.path}: ${selector} = ${formatValue(actualValue)} (expected ${formatValue(expectedValue)})`,
217
+ );
218
+ }
219
+ }
220
+ }
221
+ return drifts;
222
+ }
223
+
224
+ function collectPackageFileDrifts() {
225
+ const drifts = [];
226
+ const packageJson = readJson("package.json");
227
+ for (const command of Object.values(packageJson.scripts ?? {})) {
228
+ const scriptPath = command.match(/\bscripts\/[^\s]+/)?.[0];
229
+ if (scriptPath && !existsSync(join(root, scriptPath))) {
230
+ drifts.push(` package.json: script target missing "${scriptPath}"`);
231
+ }
232
+ }
233
+ const requiredPackageFiles = [
234
+ ".mcp.json",
235
+ ".claude-plugin/plugin.json",
236
+ ".codex-plugin/plugin.json",
237
+ "commands/post.md",
238
+ "task-guides/search-tweets.md",
239
+ "server.json",
240
+ "scripts/check-versions.mjs",
241
+ "scripts/release-guard/content-policy.mjs",
242
+ "scripts/release-guard/frontmatter.mjs",
243
+ "scripts/release-guard/policy.mjs",
244
+ "skills.sh.json",
245
+ "start.sh",
246
+ "stub-server.mjs",
247
+ ];
248
+ const packageFiles = new Set(collectPackageFiles(packageJson.files ?? []));
249
+ for (const path of requiredPackageFiles) {
250
+ if (!packageFiles.has(path)) {
251
+ drifts.push(` package.json files: missing "${path}"`);
252
+ }
253
+ }
254
+ if ((statSync(join(root, "start.sh")).mode & 0o111) === 0) {
255
+ drifts.push(" start.sh: must be executable");
256
+ }
257
+ return drifts;
258
+ }
259
+
260
+ function collectPackageFiles(patterns) {
261
+ const files = new Set(["package.json"]);
262
+ for (const pattern of patterns) {
263
+ if (pattern.startsWith("!")) {
264
+ continue;
265
+ }
266
+ const resolved = join(root, pattern);
267
+ if (!existsSync(resolved)) {
268
+ continue;
269
+ }
270
+ for (const path of collectFilesBelow(pattern)) {
271
+ files.add(path);
272
+ }
273
+ }
274
+ return files;
275
+ }
276
+
277
+ function collectFilesBelow(path) {
278
+ const resolved = join(root, path);
279
+ const stats = statSync(resolved);
280
+ if (stats.isFile()) {
281
+ return [path];
282
+ }
283
+ if (!stats.isDirectory()) {
284
+ return [];
285
+ }
286
+ return readdirSync(resolved, { withFileTypes: true }).flatMap((entry) => {
287
+ const childPath = `${path}/${entry.name}`;
288
+ return entry.isDirectory() ? collectFilesBelow(childPath) : [childPath];
289
+ });
290
+ }
291
+
292
+ function collectNestedReferenceDrifts() {
293
+ const drifts = [];
294
+ for (const skill of readdirSync(join(root, "skills"))) {
295
+ const referencesPath = `skills/${skill}/references`;
296
+ if (!existsSync(join(root, referencesPath))) {
297
+ continue;
298
+ }
299
+ for (const entry of readdirSync(join(root, referencesPath), {
300
+ withFileTypes: true,
301
+ })) {
302
+ if (entry.isDirectory()) {
303
+ drifts.push(
304
+ ` ${referencesPath}/${entry.name}: nested reference directories are not allowed`,
305
+ );
306
+ }
307
+ }
308
+ }
309
+ return drifts;
310
+ }
311
+
312
+ function readSelector(object, selector) {
313
+ return selector
314
+ .split(".")
315
+ .reduce((value, key) => value?.[selectorKey(key)], object);
316
+ }
317
+
318
+ function selectorKey(key) {
319
+ const index = Number(key);
320
+ return Number.isNaN(index) ? key : index;
321
+ }
322
+
323
+ function formatValue(value) {
324
+ return value === undefined ? "<missing>" : JSON.stringify(value);
325
+ }
326
+
327
+ function collectMarkdownPaths() {
328
+ return [
329
+ "README.md",
330
+ "CODE_OF_CONDUCT.md",
331
+ ...markdownRoots.flatMap((path) => collectMarkdownPathsBelow(path)),
332
+ ];
333
+ }
334
+
335
+ function collectMarkdownPathsBelow(path) {
336
+ const paths = [];
337
+ for (const entry of readdirSync(join(root, path), { withFileTypes: true })) {
338
+ const childPath = `${path}/${entry.name}`;
339
+ if (entry.isDirectory()) {
340
+ paths.push(...collectMarkdownPathsBelow(childPath));
341
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
342
+ paths.push(childPath);
343
+ }
344
+ }
345
+ return paths;
346
+ }
347
+
348
+ function extractMarkdownLinks(raw) {
349
+ return [...raw.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)].map((match) => match[1]);
350
+ }
351
+
352
+ function isExternalLink(target) {
353
+ return /^[a-z][a-z0-9+.-]*:/i.test(target);
354
+ }
355
+
356
+ const failures = [
357
+ ...collectVersionDrifts(),
358
+ ...collectContentDrifts(),
359
+ ...collectSkillMetadataDrifts(),
360
+ ...collectTaskGuideMetadataDrifts(),
361
+ ...collectTaskGuideUsageLanguageDrifts(),
362
+ ...collectSkillsShGroupingDrifts(),
363
+ ...collectMarkdownLinkDrifts(),
364
+ ...collectManifestReferenceDrifts(),
365
+ ...collectJsonFieldDrifts(),
366
+ ...collectPackageFileDrifts(),
367
+ ...collectNestedReferenceDrifts(),
368
+ ];
369
+
370
+ function reportFailures() {
371
+ process.stderr.write(
372
+ `Release guard failed (package.json = ${expected}):\n${failures.join("\n")}\n`,
373
+ );
374
+ process.exit(1);
375
+ }
376
+
377
+ if (failures.length > 0) {
378
+ reportFailures();
379
+ }
380
+
381
+ process.stdout.write(`All surfaces at ${expected}\n`);