failproofai 0.0.11-beta.9 → 0.0.11

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 (152) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  22. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  26. package/.next/standalone/.next/server/app/api/audit/invite/route.js +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/logout/route.js +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/status/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  42. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/index.html +1 -1
  45. package/.next/standalone/.next/server/app/index.rsc +15 -15
  46. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  47. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  48. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  49. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  50. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  51. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  55. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  65. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/chunks/[externals]__1_g_b3t._.js +3 -0
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dwpg-h._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lnenda._.js +3 -0
  70. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13i_sva._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_mqemn._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/node_modules_0-tu4ot._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/node_modules_1bnh1y0._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +3 -3
  75. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0e446gb._.js → [root-of-the-server]__00uwqi6._.js} +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0808sha._.js +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e4-6d8._.js +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ehe24g._.js +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g253ve._.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0k65l27._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0wprfyc._.js → [root-of-the-server]__0kjb_s4._.js} +2 -2
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vxf0_g._.js +2 -2
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12mcauo._.js +2 -2
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mt35_w._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pcxxwg._.js +2 -2
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1uvfwgr._.js +2 -2
  90. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +49 -21
  92. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0y4a-0q.js → node_modules_html-to-image_es_index_0ihmbv4.js} +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  96. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  97. package/.next/standalone/.next/server/pages/404.html +1 -1
  98. package/.next/standalone/.next/server/pages/500.html +1 -1
  99. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  100. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  101. package/.next/standalone/.next/static/chunks/{3ty6dhcuogout.js → 02fywjt0by40a.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/0xdx2ehtbdoeg.js +1 -0
  103. package/.next/standalone/.next/static/chunks/{07_d165p5h5ys.js → 1-a5rvq67k7ed.js} +1 -1
  104. package/.next/standalone/.next/static/chunks/{3nj6g3xu9uy78.js → 15csyj1_rf0-w.js} +1 -1
  105. package/.next/standalone/.next/static/chunks/1o0xa47736gi9.css +2 -0
  106. package/.next/standalone/.next/static/chunks/24cv31x607n7k.js +1 -0
  107. package/.next/standalone/.next/static/chunks/2n_s8v1ae38_a.js +69 -0
  108. package/.next/standalone/.next/static/chunks/{277oc363p56n6.js → 2y-jmvrjxz60x.js} +2 -2
  109. package/.next/standalone/.next/static/chunks/{1kvadxkgnapyj.js → 3eik_d9qrvoft.js} +1 -1
  110. package/.next/standalone/.next/static/chunks/{168k-8z6k7e8z.css → 3i27c3hcriawq.css} +1 -1
  111. package/.next/standalone/.next/static/chunks/{2z42u62k-8-_q.js → 3v61675vr6jav.js} +1 -1
  112. package/.next/standalone/app/api/audit/invite/route.ts +10 -1
  113. package/.next/standalone/app/api/audit/run/route.ts +35 -0
  114. package/.next/standalone/app/api/auth/login-request/route.ts +2 -2
  115. package/.next/standalone/app/api/auth/login-verify/route.ts +10 -2
  116. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +9 -1
  117. package/.next/standalone/app/audit/_components/audit-poster.tsx +11 -7
  118. package/.next/standalone/app/audit/_components/auth-dialog.tsx +6 -4
  119. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +23 -3
  120. package/.next/standalone/app/audit/_components/invite-dialog.tsx +6 -3
  121. package/.next/standalone/app/audit/_components/share-templates.ts +58 -28
  122. package/.next/standalone/app/audit/audit-styles.css +17 -22
  123. package/.next/standalone/app/globals.css +27 -2
  124. package/.next/standalone/app/policies/hooks-client.tsx +33 -24
  125. package/.next/standalone/components/reach-developers.tsx +10 -25
  126. package/.next/standalone/lib/auth/api-server-client.ts +5 -2
  127. package/.next/standalone/lib/client-telemetry.ts +4 -0
  128. package/.next/standalone/package.json +6 -4
  129. package/.next/standalone/server.js +1 -1
  130. package/README.md +2 -2
  131. package/bin/failproofai.mjs +24 -5
  132. package/dist/cli.mjs +2328 -381
  133. package/lib/auth/api-server-client.ts +5 -2
  134. package/lib/client-telemetry.ts +4 -0
  135. package/package.json +6 -4
  136. package/scripts/launch.ts +30 -4
  137. package/scripts/postinstall.mjs +10 -1
  138. package/scripts/skew-log-filter.ts +46 -0
  139. package/scripts/validate-mdx.ts +139 -0
  140. package/src/audit/cli.ts +330 -0
  141. package/src/audit/open-browser.ts +69 -0
  142. package/src/auth/cli.ts +16 -13
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1r1h8v9._.js +0 -3
  144. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1uatkiv._.js +0 -3
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1y6gxxb._.js +0 -3
  146. package/.next/standalone/.next/static/chunks/28mkxkl_d91-l.js +0 -1
  147. package/.next/standalone/.next/static/chunks/28x7jvo3kxd3u.js +0 -41
  148. package/.next/standalone/.next/static/chunks/29nrs5xs9c4hx.css +0 -2
  149. package/.next/standalone/.next/static/chunks/29tg7deqmq32l.js +0 -1
  150. /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → P_MIRSeoE296wkbE-Icin}/_buildManifest.js +0 -0
  151. /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → P_MIRSeoE296wkbE-Icin}/_clientMiddlewareManifest.js +0 -0
  152. /package/.next/standalone/.next/static/{NYPiJP6Rv_exQdSFVS8HP → P_MIRSeoE296wkbE-Icin}/_ssgManifest.js +0 -0
@@ -249,17 +249,20 @@ export interface InviteSendResult {
249
249
  /**
250
250
  * Send invite emails to a batch of friends. The api-server pulls the sender's
251
251
  * email from the access-token claims and Cc's them on every outbound message
252
- * so the recipient sees who invited them.
252
+ * so the recipient sees who invited them. `score` (the sender's audit score,
253
+ * 0–100) is forwarded so the invite body can show "mine came out at N/100";
254
+ * omit it and the api-server renders score-free copy.
253
255
  *
254
256
  * Contract is handed over to the platform team separately.
255
257
  */
256
258
  export async function sendInvites(
257
259
  accessToken: string,
258
260
  to: string[],
261
+ score?: number,
259
262
  ): Promise<InviteSendResult> {
260
263
  return postJson<InviteSendResult>(
261
264
  "/v0/invite",
262
- { to },
265
+ score === undefined ? { to } : { to, score },
263
266
  { accessToken },
264
267
  );
265
268
  }
@@ -34,6 +34,10 @@ export function captureClientEvent(
34
34
  method: "POST",
35
35
  headers: { "Content-Type": "application/json" },
36
36
  body: payload,
37
+ // keepalive lets the request outlive a page navigation/unload — without it,
38
+ // events fired right before the page goes away (client_error / unhandled_*
39
+ // from an error boundary, or a share click that opens a new tab) are dropped.
40
+ keepalive: true,
37
41
  signal: AbortSignal.timeout(5000),
38
42
  }).catch(() => {});
39
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "0.0.11-beta.9",
3
+ "version": "0.0.11",
4
4
  "description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs"
@@ -38,7 +38,8 @@
38
38
  "translate:readme": "bun scripts/translate-docs/cli.ts --readme-only",
39
39
  "translate:docs": "bun scripts/translate-docs/cli.ts --docs-only",
40
40
  "translate:dry-run": "bun scripts/translate-docs/cli.ts --dry-run",
41
- "translate:validate": "bun scripts/translate-docs/cli.ts --validate"
41
+ "translate:validate": "bun scripts/translate-docs/cli.ts --validate",
42
+ "validate:mdx": "bun scripts/validate-mdx.ts"
42
43
  },
43
44
  "keywords": [
44
45
  "claude",
@@ -71,13 +72,14 @@
71
72
  "access": "public"
72
73
  },
73
74
  "devDependencies": {
74
- "@anthropic-ai/sdk": "^0.104.2",
75
+ "@anthropic-ai/sdk": "^0.105.0",
76
+ "@mdx-js/mdx": "^3.1.1",
75
77
  "@tailwindcss/postcss": "^4.3.1",
76
78
  "@tanstack/react-virtual": "^3.14.3",
77
79
  "@testing-library/jest-dom": "^6.9.1",
78
80
  "@testing-library/react": "^16.3.2",
79
81
  "@testing-library/user-event": "^14.6.1",
80
- "@types/node": "25.9.3",
82
+ "@types/node": "26.0.0",
81
83
  "@types/react": "19.2.17",
82
84
  "@types/react-dom": "^19.2.3",
83
85
  "@vitejs/plugin-react": "^6.0.1",
package/scripts/launch.ts CHANGED
@@ -5,21 +5,23 @@ import { spawn } from "child_process";
5
5
  import { realpathSync, existsSync } from "node:fs";
6
6
  import { resolve, dirname } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
+ import { createInterface } from "node:readline";
8
9
  import { parseScriptArgs } from "./parse-script-args";
9
10
  import { diagnoseShadow } from "./install-diagnosis.mjs";
11
+ import { makeSkewLogFilter } from "./skew-log-filter";
10
12
  import { version } from "../package.json";
11
13
 
12
14
  export function launch(mode: "dev" | "start"): void {
13
15
  const { loggingLevel, disableTelemetry, allowedDevOrigins, remainingArgs } = parseScriptArgs(process.argv.slice(2));
14
16
 
15
17
  // Plain-text title + a labeled `Version` line that lines up with the
16
- // `Star us` / `Docs` / `Slack` lines below (all four labels pad to the
18
+ // `Star us` / `Docs` / `Discord` lines below (all four labels pad to the
17
19
  // same column so the values form a clean right-hand column).
18
20
  console.log(`\n failproof ai\n`);
19
21
  console.log(` 📦 Version: ${version}`);
20
22
  console.log(` ⭐ Star us: https://github.com/failproofai/failproofai`);
21
- console.log(` 📖 Docs: https://befailproof.ai`);
22
- console.log(` 💬 Slack: https://join.slack.com/t/failproofai/shared_invite/zt-3v63b7k5e-O3NBHmj8X6n9gZSGDx6ggQ\n`);
23
+ console.log(` 📖 Docs: https://docs.befailproof.ai/introduction`);
24
+ console.log(` 💬 Discord: https://discord.gg/2zjBZP7yQJ\n`);
23
25
 
24
26
  let cmd: string;
25
27
  let cmdArgs: string[];
@@ -79,16 +81,40 @@ export function launch(mode: "dev" | "start"): void {
79
81
  cmdArgs = ["--bun", "next", "dev", ...remainingArgs];
80
82
  }
81
83
 
84
+ // In `start` (the shipped standalone server) we pipe + filter the child's
85
+ // output to drop the benign "Failed to find Server Action" deployment-skew
86
+ // block — a stale browser tab POSTing an old action ID after a rebuild, which
87
+ // the client recovers from via Next's graceful 404 (see skew-log-filter.ts).
88
+ // `dev` keeps "inherit" so Next's interactive compile output is untouched.
89
+ // FORCE_COLOR keeps the piped child's output colored despite the non-TTY pipe.
90
+ const filterLogs = mode === "start";
91
+
82
92
  const nextProcess = spawn(cmd, cmdArgs, {
83
- stdio: "inherit",
93
+ stdio: filterLogs ? ["inherit", "pipe", "pipe"] : "inherit",
84
94
  env: {
85
95
  ...process.env,
96
+ ...(filterLogs ? { FORCE_COLOR: process.env.FORCE_COLOR ?? "1" } : {}),
86
97
  ...(loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {}),
87
98
  ...(disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {}),
88
99
  ...(allowedDevOrigins ? { FAILPROOFAI_ALLOWED_DEV_ORIGINS: allowedDevOrigins.join(",") } : {}),
89
100
  },
90
101
  });
91
102
 
103
+ if (filterLogs) {
104
+ // One filter instance per stream — the skew block is multi-line and stateful.
105
+ for (const [src, dest] of [
106
+ [nextProcess.stdout, process.stdout],
107
+ [nextProcess.stderr, process.stderr],
108
+ ] as const) {
109
+ if (!src) continue;
110
+ const filter = makeSkewLogFilter();
111
+ createInterface({ input: src }).on("line", (line) => {
112
+ const out = filter(line);
113
+ if (out !== null) dest.write(out + "\n");
114
+ });
115
+ }
116
+ }
117
+
92
118
  nextProcess.on("error", (error) => {
93
119
  console.error("Error starting Next.js:", error);
94
120
  process.exit(1);
@@ -27,6 +27,14 @@ if (!existsSync(serverJsPath)) {
27
27
  ` The package may not have been built correctly.\n` +
28
28
  ` Try reinstalling: npm install -g failproofai@latest\n`
29
29
  );
30
+ // Await so the event lands before process.exit(1) kills the in-flight fetch —
31
+ // a failed install is exactly the signal we most want and would otherwise lose.
32
+ await trackInstallEvent("package_install_failed", {
33
+ reason: "server_js_missing",
34
+ platform: platform(),
35
+ arch: arch(),
36
+ node_version: process.versions.node,
37
+ }).catch(() => {});
30
38
  process.exit(1);
31
39
  }
32
40
 
@@ -202,7 +210,7 @@ if (previousVersion === null) {
202
210
  arch: arch(),
203
211
  os_release: release(),
204
212
  node_version: process.versions.node,
205
- version: currentVersion,
213
+ // `version` is carried automatically as `failproofai_version` — no explicit dup.
206
214
  }).catch(() => {});
207
215
  } else {
208
216
  // Same version is a reinstall — still worth tracking; users hitting `npm install -g`
@@ -227,6 +235,7 @@ trackInstallEvent("package_installed", {
227
235
  platform: platform(),
228
236
  arch: arch(),
229
237
  os_release: release(),
238
+ node_version: process.versions.node,
230
239
  hostname_hash: hashToId(hostname()),
231
240
  hooks_configured: hooksResult.configured,
232
241
  hooks_registered: hooksResult.registered,
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Filters the benign Next.js "Failed to find Server Action" deployment-skew
3
+ * block out of the standalone server's forwarded output.
4
+ *
5
+ * Why this exists: the dashboard ships as a per-version Next standalone build,
6
+ * and Server Action IDs are hashed at build time. A browser tab left open
7
+ * across a rebuild/upgrade keeps POSTing a stale action ID the running build no
8
+ * longer has, so Next throws + logs (server-side, to stderr) a 3-line block:
9
+ *
10
+ * Error: Failed to find Server Action "<hash>". This request might be from an older or newer deployment.
11
+ * Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
12
+ * at ignore-listed frames
13
+ *
14
+ * The client receives a graceful 404 (`x-nextjs-action-not-found: 1`) and
15
+ * recovers on its own, so the server-side log is pure noise. `launch.ts` pipes
16
+ * the spawned server's stdout/stderr through this filter (one instance per
17
+ * stream) to drop that block while passing everything else through verbatim.
18
+ *
19
+ * Stateful by design: the block spans multiple lines, so each stream needs its
20
+ * own filter instance. Call the returned function with each line; it returns
21
+ * the line to emit, or `null` to drop it.
22
+ */
23
+ export function makeSkewLogFilter(): (line: string) => string | null {
24
+ let inSkewBlock = false;
25
+ return (line: string): string | null => {
26
+ if (line.includes("Failed to find Server Action")) {
27
+ inSkewBlock = true;
28
+ return null;
29
+ }
30
+ if (inSkewBlock) {
31
+ // Continuation lines of the same error block — drop them too.
32
+ const trimmed = line.trimStart();
33
+ if (
34
+ trimmed.startsWith("Read more:") ||
35
+ line.includes("failed-to-find-server-action") ||
36
+ line.includes("ignore-listed frames") ||
37
+ /^\s+at\s/.test(line)
38
+ ) {
39
+ return null;
40
+ }
41
+ // First line that isn't part of the block — stop dropping and emit it.
42
+ inSkewBlock = false;
43
+ }
44
+ return line;
45
+ };
46
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Validate that every docs MDX page parses with the same MDX engine Mintlify
3
+ * runs at deploy time.
4
+ *
5
+ * Why this exists: `mintlify validate` (the existing `docs` CI job) only checks
6
+ * `docs.json` structure and nav-link resolution — it does NOT parse page
7
+ * content. A page that is structurally referenced but contains an MDX syntax
8
+ * error (e.g. a `<slug>` that escaped its surrounding backticks because a
9
+ * translation dropped a closing `` ` ``) passes `mintlify validate` but fails
10
+ * the Mintlify deploy with:
11
+ *
12
+ * Failed to parse page content at path tr/cli/audit.mdx:
13
+ * Expected a closing tag for `<slug>` (61:127-61:133) before the end of `paragraph`
14
+ *
15
+ * That deploy runs post-merge, so the failure only surfaces on `main`. The
16
+ * auto-translation workflow regenerates these pages with an LLM, so this class
17
+ * of breakage recurs (see the `sanitizeJsxAttributes` / `stripStrayTrailingFence`
18
+ * heuristics in scripts/translate-docs/mdx-translator.ts — best-effort fixers
19
+ * that can't catch every case). This script is the deterministic safety net:
20
+ * run it on every PR so an unparseable page fails CI before it reaches `main`.
21
+ *
22
+ * The error string above is emitted by `@mdx-js/mdx`'s micromark MDX layer,
23
+ * which is the same engine Mintlify uses, so compiling here reproduces the
24
+ * deploy-time parse faithfully.
25
+ */
26
+ import { readdirSync, statSync, readFileSync } from "node:fs";
27
+ import { dirname, join, relative } from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+ import { compile } from "@mdx-js/mdx";
30
+
31
+ const __dirname = dirname(fileURLToPath(import.meta.url));
32
+ const DOCS_DIR = join(__dirname, "..", "docs");
33
+
34
+ export interface MdxParseError {
35
+ message: string;
36
+ line?: number;
37
+ column?: number;
38
+ }
39
+
40
+ /**
41
+ * Replace a leading YAML frontmatter block (`--- … ---`) with blank lines.
42
+ *
43
+ * Mintlify parses frontmatter as YAML, not MDX, so it never causes an MDX parse
44
+ * error. We blank it rather than delete it so the remaining content keeps its
45
+ * original line numbers — error positions then match the real file.
46
+ */
47
+ export function stripFrontmatter(source: string): string {
48
+ const match = /^---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/.exec(source);
49
+ if (!match) return source;
50
+ // Keep newlines, drop every other character, so line numbers stay aligned.
51
+ const blanked = match[0].replace(/[^\n]/g, "");
52
+ return blanked + source.slice(match[0].length);
53
+ }
54
+
55
+ /**
56
+ * Compile one MDX source string with the deploy-time parser. Returns `null`
57
+ * when it parses cleanly, or the parse error (with position) otherwise.
58
+ */
59
+ export async function findMdxParseError(
60
+ source: string,
61
+ ): Promise<MdxParseError | null> {
62
+ try {
63
+ await compile(stripFrontmatter(source));
64
+ return null;
65
+ } catch (err) {
66
+ const e = err as {
67
+ reason?: string;
68
+ message?: string;
69
+ line?: number;
70
+ column?: number;
71
+ place?: { start?: { line?: number; column?: number } };
72
+ };
73
+ return {
74
+ message: e.reason ?? e.message ?? String(err),
75
+ line: e.line ?? e.place?.start?.line,
76
+ column: e.column ?? e.place?.start?.column,
77
+ };
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Percent-encode a value for a GitHub Actions workflow command. Without this a
83
+ * multi-line MDX error message would be truncated at its first newline (and a
84
+ * literal `%` could mis-parse) when emitted as an `::error::` annotation.
85
+ * https://docs.github.com/actions/reference/workflow-commands-for-github-actions
86
+ */
87
+ export function encodeAnnotation(value: string): string {
88
+ return value
89
+ .replace(/%/g, "%25")
90
+ .replace(/\r/g, "%0D")
91
+ .replace(/\n/g, "%0A");
92
+ }
93
+
94
+ function collectMdxFiles(dir: string): string[] {
95
+ const out: string[] = [];
96
+ for (const entry of readdirSync(dir)) {
97
+ const full = join(dir, entry);
98
+ if (statSync(full).isDirectory()) out.push(...collectMdxFiles(full));
99
+ else if (entry.endsWith(".mdx")) out.push(full);
100
+ }
101
+ return out;
102
+ }
103
+
104
+ async function main(): Promise<void> {
105
+ const files = collectMdxFiles(DOCS_DIR).sort();
106
+ const failures: Array<{ file: string; error: MdxParseError }> = [];
107
+
108
+ for (const file of files) {
109
+ const error = await findMdxParseError(readFileSync(file, "utf-8"));
110
+ if (error) failures.push({ file: relative(process.cwd(), file), error });
111
+ }
112
+
113
+ if (failures.length === 0) {
114
+ console.log(`✓ ${files.length} MDX page(s) parsed cleanly`);
115
+ return;
116
+ }
117
+
118
+ console.error(
119
+ `✗ ${failures.length} of ${files.length} MDX page(s) failed to parse:\n`,
120
+ );
121
+ for (const { file, error } of failures) {
122
+ const pos = error.line
123
+ ? `:${error.line}${error.column ? `:${error.column}` : ""}`
124
+ : "";
125
+ console.error(` ${file}${pos}\n ${error.message}\n`);
126
+ // GitHub Actions inline annotation.
127
+ const loc =
128
+ (error.line ? `,line=${error.line}` : "") +
129
+ (error.column ? `,col=${error.column}` : "");
130
+ console.log(
131
+ `::error file=${encodeAnnotation(file)}${loc}::MDX parse error: ${encodeAnnotation(error.message)}`,
132
+ );
133
+ }
134
+ process.exitCode = 1;
135
+ }
136
+
137
+ if (import.meta.main) {
138
+ void main();
139
+ }