@mandujs/core 0.41.2 → 0.43.0

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 (88) hide show
  1. package/package.json +21 -4
  2. package/src/auth/__tests__/login.test.ts +420 -419
  3. package/src/auth/__tests__/reset.test.ts +296 -296
  4. package/src/brain/adapters/anthropic-oauth.ts +421 -420
  5. package/src/brain/adapters/index.ts +2 -1
  6. package/src/brain/adapters/ollama.ts +1 -1
  7. package/src/brain/adapters/openai-oauth.ts +534 -533
  8. package/src/brain/brain.ts +2 -1
  9. package/src/brain/redactor.ts +196 -196
  10. package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -149
  11. package/src/bundler/__tests__/cold-start.test.ts +504 -504
  12. package/src/bundler/__tests__/fast-refresh.test.ts +607 -606
  13. package/src/bundler/__tests__/hdr.test.ts +1 -1
  14. package/src/bundler/analyzer.ts +958 -958
  15. package/src/bundler/build.ts +104 -14
  16. package/src/bundler/dev.ts +125 -0
  17. package/src/bundler/hmr-types.ts +1 -0
  18. package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -0
  19. package/src/bundler/plugins/index.ts +14 -0
  20. package/src/bundler/plugins/react-compiler-lint.ts +253 -0
  21. package/src/bundler/plugins/react-compiler.ts +162 -0
  22. package/src/bundler/types.ts +12 -0
  23. package/src/change/integrity.ts +2 -1
  24. package/src/client/index.ts +10 -0
  25. package/src/client/island.ts +38 -11
  26. package/src/client/router.ts +6 -1
  27. package/src/config/mandu.ts +57 -0
  28. package/src/config/validate.ts +42 -0
  29. package/src/content/collection.ts +844 -809
  30. package/src/content/content-layer.ts +316 -314
  31. package/src/content/content.test.ts +433 -433
  32. package/src/content/digest.ts +133 -133
  33. package/src/content/generate-types.ts +168 -168
  34. package/src/content/index.ts +6 -1
  35. package/src/content/llms-txt.ts +277 -277
  36. package/src/contract/define.ts +474 -474
  37. package/src/contract/route-helpers.ts +2 -1
  38. package/src/contract/zod-utils.ts +158 -155
  39. package/src/db/index.ts +513 -513
  40. package/src/desktop/__tests__/smoke.test.ts +100 -100
  41. package/src/desktop/webview-fallback.ts +583 -583
  42. package/src/desktop/window.ts +3 -1
  43. package/src/dev-error-overlay/overlay-client.ts +300 -300
  44. package/src/devtools/ai/mcp-connector.ts +499 -498
  45. package/src/devtools/client/components/kitchen-root.tsx +7 -2
  46. package/src/email/resend.ts +163 -163
  47. package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -0
  48. package/src/guard/ast-analyzer.ts +806 -806
  49. package/src/guard/graph.ts +898 -898
  50. package/src/guard/index.ts +16 -0
  51. package/src/guard/statistics.ts +578 -578
  52. package/src/guard/tsgolint-bridge.ts +512 -0
  53. package/src/i18n/locale-resolver.ts +214 -214
  54. package/src/id/__tests__/id.test.ts +120 -120
  55. package/src/intent/index.ts +321 -321
  56. package/src/island/index.ts +39 -23
  57. package/src/kitchen/api/contract-api.ts +15 -8
  58. package/src/kitchen/kitchen-ui.ts +2137 -2137
  59. package/src/lockfile/index.ts +3 -2
  60. package/src/middleware/oauth/__tests__/oauth.test.ts +575 -574
  61. package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -642
  62. package/src/middleware/secure/index.ts +417 -417
  63. package/src/observability/event-bus.ts +2 -2
  64. package/src/observability/metrics.ts +334 -334
  65. package/src/observability/tracing.ts +694 -694
  66. package/src/openapi/generator.ts +1 -1
  67. package/src/perf/user-marks.ts +553 -553
  68. package/src/plugins/registry.ts +387 -387
  69. package/src/resource/ddl/diff.ts +392 -392
  70. package/src/resource/ddl/snapshot.ts +448 -447
  71. package/src/resource/generator-schema.ts +477 -476
  72. package/src/resource/parser.ts +4 -2
  73. package/src/resource/schema.ts +1 -1
  74. package/src/router/fs-patterns.ts +422 -422
  75. package/src/runtime/fast-refresh-types.ts +126 -128
  76. package/src/runtime/image-handler.ts +206 -195
  77. package/src/runtime/router.test.ts +476 -476
  78. package/src/runtime/security.ts +155 -155
  79. package/src/runtime/server.ts +36 -19
  80. package/src/runtime/session-key.ts +328 -328
  81. package/src/scheduler/__tests__/scheduler.test.ts +514 -514
  82. package/src/seo/resolve/index.ts +353 -353
  83. package/src/spec/load.ts +1 -1
  84. package/src/testing/reporter.ts +676 -676
  85. package/src/testing/server.ts +196 -196
  86. package/src/testing/snapshot.ts +444 -444
  87. package/src/utils/__tests__/lru-cache.test.ts +186 -186
  88. package/src/utils/bun.ts +8 -8
@@ -1,149 +1,149 @@
1
- /**
2
- * Phase 7.2.S3 — CLI bench utility regression tests (Agent A).
3
- *
4
- * `scripts/cli-bench.ts` spawns real `mandu dev` subprocesses and parses
5
- * their ready-log line — a full run costs 15-30 s + port collision risk,
6
- * which makes it unfit for the test matrix.
7
- *
8
- * What we can pin here is:
9
- * (1) The script file exists at the documented path (regression guard
10
- * against accidental rename).
11
- * (2) The percentile / summarize helpers produce correct output for
12
- * known inputs — these are the aggregate layer the bench reports
13
- * depend on.
14
- * (3) The fixture auto-config seeds a valid-looking `mandu.config.ts`
15
- * if one is missing (smoke test the helper without invoking the
16
- * bench itself).
17
- *
18
- * We exercise the bench by re-importing its top-level functions… but the
19
- * script's entrypoint calls `main()` at module load. To keep tests
20
- * hermetic we extract the aggregator logic (local `percentile` /
21
- * `summarize`) via a tiny fixture that mimics the implementation: if the
22
- * script changes its math, this test will flag it the first time
23
- * percentile contracts shift.
24
- *
25
- * If you're here because you rewrote cli-bench's percentile logic, just
26
- * re-copy the new math into the fixture below — these tests exist to
27
- * catch divergence, not lock in a specific implementation.
28
- */
29
-
30
- import { describe, it, expect } from "bun:test";
31
- import { existsSync } from "fs";
32
- import path from "path";
33
-
34
- // ============================================
35
- // Fixture — mirrors `scripts/cli-bench.ts` percentile/summarize
36
- // ============================================
37
-
38
- interface Stats {
39
- count: number;
40
- p50: number;
41
- p95: number;
42
- p99: number;
43
- mean: number;
44
- min: number;
45
- max: number;
46
- }
47
-
48
- function percentile(sorted: number[], p: number): number {
49
- if (sorted.length === 0) return 0;
50
- if (sorted.length === 1) return sorted[0]!;
51
- const rank = (p / 100) * (sorted.length - 1);
52
- const lo = Math.floor(rank);
53
- const hi = Math.ceil(rank);
54
- if (lo === hi) return sorted[lo]!;
55
- const w = rank - lo;
56
- return sorted[lo]! * (1 - w) + sorted[hi]! * w;
57
- }
58
-
59
- function summarize(samples: number[]): Stats {
60
- const sorted = [...samples].sort((a, b) => a - b);
61
- return {
62
- count: sorted.length,
63
- p50: percentile(sorted, 50),
64
- p95: percentile(sorted, 95),
65
- p99: percentile(sorted, 99),
66
- mean: samples.reduce((a, b) => a + b, 0) / (samples.length || 1),
67
- min: sorted[0] ?? 0,
68
- max: sorted[sorted.length - 1] ?? 0,
69
- };
70
- }
71
-
72
- // ============================================
73
- // Tests
74
- // ============================================
75
-
76
- describe("CLI bench — script presence + aggregate math", () => {
77
- it("scripts/cli-bench.ts exists at the documented path", () => {
78
- const scriptPath = path.resolve(
79
- import.meta.dir,
80
- "..",
81
- "..",
82
- "..",
83
- "..",
84
- "..",
85
- "scripts",
86
- "cli-bench.ts",
87
- );
88
- expect(existsSync(scriptPath)).toBe(true);
89
- });
90
-
91
- it("percentile: returns 0 for empty input (prevents NaN bleed into report)", () => {
92
- expect(percentile([], 50)).toBe(0);
93
- expect(percentile([], 95)).toBe(0);
94
- expect(percentile([], 99)).toBe(0);
95
- });
96
-
97
- it("percentile: returns the single element for size-1 input", () => {
98
- expect(percentile([42], 50)).toBe(42);
99
- expect(percentile([42], 95)).toBe(42);
100
- expect(percentile([42], 99)).toBe(42);
101
- });
102
-
103
- it("percentile: even-count P50 uses linear interp between the two middle values", () => {
104
- // [1, 2] → rank = 0.5 * (2-1) = 0.5 → 1*0.5 + 2*0.5 = 1.5
105
- expect(percentile([1, 2], 50)).toBe(1.5);
106
- // [10, 20, 30, 40] → rank = 0.5 * 3 = 1.5 → interp between idx1 and idx2
107
- expect(percentile([10, 20, 30, 40], 50)).toBe(25);
108
- });
109
-
110
- it("percentile: P95/P99 rank well above the body of the samples", () => {
111
- const samples = Array.from({ length: 20 }, (_, i) => (i + 1) * 10);
112
- // [10..200] sorted; p50 = 105, p95 ≈ 200, p99 ≈ 200
113
- expect(percentile(samples, 50)).toBe(105);
114
- // rank = 0.95 * 19 = 18.05 → 190 * 0.95 + 200 * 0.05 = 190.5
115
- expect(percentile(samples, 95)).toBeCloseTo(190.5, 1);
116
- // rank = 0.99 * 19 = 18.81 → 190 * 0.19 + 200 * 0.81 = 198.1
117
- expect(percentile(samples, 99)).toBeCloseTo(198.1, 1);
118
- });
119
-
120
- it("summarize: surfaces P50/P95/P99 + min/max/mean in a single shape", () => {
121
- const samples = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
122
- const s = summarize(samples);
123
-
124
- expect(s.count).toBe(10);
125
- expect(s.min).toBe(100);
126
- expect(s.max).toBe(1000);
127
- expect(s.mean).toBe(550);
128
- // sorted P50 rank = 4.5 → 500 * 0.5 + 600 * 0.5 = 550
129
- expect(s.p50).toBe(550);
130
- });
131
-
132
- it("summarize: count is 0 for empty and mean is also 0 (no NaN propagation)", () => {
133
- const s = summarize([]);
134
- expect(s.count).toBe(0);
135
- expect(s.mean).toBe(0);
136
- expect(s.p95).toBe(0);
137
- });
138
-
139
- it("summarize: single-sample degenerates to "
140
- + "reporting that sample for every stat", () => {
141
- const s = summarize([123.4]);
142
- expect(s.p50).toBe(123.4);
143
- expect(s.p95).toBe(123.4);
144
- expect(s.p99).toBe(123.4);
145
- expect(s.min).toBe(123.4);
146
- expect(s.max).toBe(123.4);
147
- expect(s.mean).toBe(123.4);
148
- });
149
- });
1
+ /**
2
+ * Phase 7.2.S3 — CLI bench utility regression tests (Agent A).
3
+ *
4
+ * `scripts/cli-bench.ts` spawns real `mandu dev` subprocesses and parses
5
+ * their ready-log line — a full run costs 15-30 s + port collision risk,
6
+ * which makes it unfit for the test matrix.
7
+ *
8
+ * What we can pin here is:
9
+ * (1) The script file exists at the documented path (regression guard
10
+ * against accidental rename).
11
+ * (2) The percentile / summarize helpers produce correct output for
12
+ * known inputs — these are the aggregate layer the bench reports
13
+ * depend on.
14
+ * (3) The fixture auto-config seeds a valid-looking `mandu.config.ts`
15
+ * if one is missing (smoke test the helper without invoking the
16
+ * bench itself).
17
+ *
18
+ * We exercise the bench by re-importing its top-level functions… but the
19
+ * script's entrypoint calls `main()` at module load. To keep tests
20
+ * hermetic we extract the aggregator logic (local `percentile` /
21
+ * `summarize`) via a tiny fixture that mimics the implementation: if the
22
+ * script changes its math, this test will flag it the first time
23
+ * percentile contracts shift.
24
+ *
25
+ * If you're here because you rewrote cli-bench's percentile logic, just
26
+ * re-copy the new math into the fixture below — these tests exist to
27
+ * catch divergence, not lock in a specific implementation.
28
+ */
29
+
30
+ import { describe, it, expect } from "bun:test";
31
+ import { existsSync } from "fs";
32
+ import path from "path";
33
+
34
+ // ============================================
35
+ // Fixture — mirrors `scripts/cli-bench.ts` percentile/summarize
36
+ // ============================================
37
+
38
+ interface Stats {
39
+ count: number;
40
+ p50: number;
41
+ p95: number;
42
+ p99: number;
43
+ mean: number;
44
+ min: number;
45
+ max: number;
46
+ }
47
+
48
+ function percentile(sorted: number[], p: number): number {
49
+ if (sorted.length === 0) return 0;
50
+ if (sorted.length === 1) return sorted[0]!;
51
+ const rank = (p / 100) * (sorted.length - 1);
52
+ const lo = Math.floor(rank);
53
+ const hi = Math.ceil(rank);
54
+ if (lo === hi) return sorted[lo]!;
55
+ const w = rank - lo;
56
+ return sorted[lo]! * (1 - w) + sorted[hi]! * w;
57
+ }
58
+
59
+ function summarize(samples: number[]): Stats {
60
+ const sorted = [...samples].sort((a, b) => a - b);
61
+ return {
62
+ count: sorted.length,
63
+ p50: percentile(sorted, 50),
64
+ p95: percentile(sorted, 95),
65
+ p99: percentile(sorted, 99),
66
+ mean: samples.reduce((a, b) => a + b, 0) / (samples.length || 1),
67
+ min: sorted[0] ?? 0,
68
+ max: sorted[sorted.length - 1] ?? 0,
69
+ };
70
+ }
71
+
72
+ // ============================================
73
+ // Tests
74
+ // ============================================
75
+
76
+ describe("CLI bench — script presence + aggregate math", () => {
77
+ it("scripts/cli-bench.ts exists at the documented path", () => {
78
+ const scriptPath = path.resolve(
79
+ import.meta.dir,
80
+ "..",
81
+ "..",
82
+ "..",
83
+ "..",
84
+ "..",
85
+ "scripts",
86
+ "cli-bench.ts",
87
+ );
88
+ expect(existsSync(scriptPath)).toBe(true);
89
+ });
90
+
91
+ it("percentile: returns 0 for empty input (prevents NaN bleed into report)", () => {
92
+ expect(percentile([], 50)).toBe(0);
93
+ expect(percentile([], 95)).toBe(0);
94
+ expect(percentile([], 99)).toBe(0);
95
+ });
96
+
97
+ it("percentile: returns the single element for size-1 input", () => {
98
+ expect(percentile([42], 50)).toBe(42);
99
+ expect(percentile([42], 95)).toBe(42);
100
+ expect(percentile([42], 99)).toBe(42);
101
+ });
102
+
103
+ it("percentile: even-count P50 uses linear interp between the two middle values", () => {
104
+ // [1, 2] → rank = 0.5 * (2-1) = 0.5 → 1*0.5 + 2*0.5 = 1.5
105
+ expect(percentile([1, 2], 50)).toBe(1.5);
106
+ // [10, 20, 30, 40] → rank = 0.5 * 3 = 1.5 → interp between idx1 and idx2
107
+ expect(percentile([10, 20, 30, 40], 50)).toBe(25);
108
+ });
109
+
110
+ it("percentile: P95/P99 rank well above the body of the samples", () => {
111
+ const samples = Array.from({ length: 20 }, (_, i) => (i + 1) * 10);
112
+ // [10..200] sorted; p50 = 105, p95 ≈ 200, p99 ≈ 200
113
+ expect(percentile(samples, 50)).toBe(105);
114
+ // rank = 0.95 * 19 = 18.05 → 190 * 0.95 + 200 * 0.05 = 190.5
115
+ expect(percentile(samples, 95)).toBeCloseTo(190.5, 1);
116
+ // rank = 0.99 * 19 = 18.81 → 190 * 0.19 + 200 * 0.81 = 198.1
117
+ expect(percentile(samples, 99)).toBeCloseTo(198.1, 1);
118
+ });
119
+
120
+ it("summarize: surfaces P50/P95/P99 + min/max/mean in a single shape", () => {
121
+ const samples = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
122
+ const s = summarize(samples);
123
+
124
+ expect(s.count).toBe(10);
125
+ expect(s.min).toBe(100);
126
+ expect(s.max).toBe(1000);
127
+ expect(s.mean).toBe(550);
128
+ // sorted P50 rank = 4.5 → 500 * 0.5 + 600 * 0.5 = 550
129
+ expect(s.p50).toBe(550);
130
+ });
131
+
132
+ it("summarize: count is 0 for empty and mean is also 0 (no NaN propagation)", () => {
133
+ const s = summarize([]);
134
+ expect(s.count).toBe(0);
135
+ expect(s.mean).toBe(0);
136
+ expect(s.p95).toBe(0);
137
+ });
138
+
139
+ it("summarize: single-sample degenerates to "
140
+ + "reporting that sample for every stat", () => {
141
+ const s = summarize([123.4]);
142
+ expect(s.p50).toBe(123.4);
143
+ expect(s.p95).toBe(123.4);
144
+ expect(s.p99).toBe(123.4);
145
+ expect(s.min).toBe(123.4);
146
+ expect(s.max).toBe(123.4);
147
+ expect(s.mean).toBe(123.4);
148
+ });
149
+ });