@mandujs/core 0.42.0 → 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 (87) 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/metrics.ts +334 -334
  64. package/src/observability/tracing.ts +694 -694
  65. package/src/openapi/generator.ts +1 -1
  66. package/src/perf/user-marks.ts +553 -553
  67. package/src/plugins/registry.ts +387 -387
  68. package/src/resource/ddl/diff.ts +392 -392
  69. package/src/resource/ddl/snapshot.ts +448 -447
  70. package/src/resource/generator-schema.ts +477 -476
  71. package/src/resource/parser.ts +4 -2
  72. package/src/resource/schema.ts +1 -1
  73. package/src/router/fs-patterns.ts +422 -422
  74. package/src/runtime/fast-refresh-types.ts +126 -128
  75. package/src/runtime/image-handler.ts +206 -195
  76. package/src/runtime/router.test.ts +476 -476
  77. package/src/runtime/security.ts +155 -155
  78. package/src/runtime/server.ts +36 -19
  79. package/src/runtime/session-key.ts +328 -328
  80. package/src/scheduler/__tests__/scheduler.test.ts +514 -514
  81. package/src/seo/resolve/index.ts +353 -353
  82. package/src/spec/load.ts +1 -1
  83. package/src/testing/reporter.ts +676 -676
  84. package/src/testing/server.ts +196 -196
  85. package/src/testing/snapshot.ts +444 -444
  86. package/src/utils/__tests__/lru-cache.test.ts +186 -186
  87. 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
+ });