@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
@@ -21,7 +21,8 @@ import type {
21
21
  import { DEFAULT_BRAIN_POLICY } from "./types";
22
22
  import { type LLMAdapter, NoopAdapter } from "./adapters/base";
23
23
  import { createOllamaAdapter } from "./adapters/ollama";
24
- import { SessionMemory, getSessionMemory } from "./memory";
24
+ import type { SessionMemory} from "./memory";
25
+ import { getSessionMemory } from "./memory";
25
26
  import {
26
27
  detectEnvironment,
27
28
  shouldEnableBrain,
@@ -1,196 +1,196 @@
1
- /**
2
- * Brain — pre-transmission secret redactor (Issue #235)
3
- *
4
- * Every payload destined for a cloud adapter is passed through
5
- * `redactSecrets()` FIRST. The function returns:
6
- *
7
- * - `{ redacted, hits }` — redacted payload + list of what matched, so
8
- * the CLI / adapter can print an audit line and append to
9
- * `.mandu/brain-redactions.jsonl` for user inspection.
10
- *
11
- * Design principles (user feedback — Mandu as connector, not owner):
12
- * - Mandu NEVER transmits a prompt without passing it through here.
13
- * - Patterns are conservative (false positives preferred over leaks).
14
- * - No regex is anchored; we scan the full text so embedded secrets
15
- * inside a diff block are still caught.
16
- * - All replacements collapse to a single token `[[REDACTED:<kind>]]`
17
- * so the cloud model sees a stable shape rather than a mangled
18
- * partial key that could still be reconstructed.
19
- *
20
- * @see docs/brain/oauth-adapters.md (if that ever ships — for now this
21
- * file + its tests are the source of truth).
22
- */
23
- export type RedactionKind =
24
- | "openai-key"
25
- | "stripe-key"
26
- | "github-token"
27
- | "slack-token"
28
- | "aws-key"
29
- | "bearer-token"
30
- | "env-ref"
31
- | "api-key-assignment"
32
- | "jwt"
33
- | "long-base64";
34
-
35
- export interface RedactionHit {
36
- /** The kind of secret matched (for audit logging). */
37
- kind: RedactionKind;
38
- /** Byte offset into the original input where the match started. */
39
- start: number;
40
- /** Byte offset into the original input where the match ended. */
41
- end: number;
42
- /**
43
- * Short sample of the redacted material — always prefix + ellipsis,
44
- * never the full secret. Safe to log.
45
- */
46
- sample: string;
47
- }
48
-
49
- export interface RedactionResult {
50
- /** Input with every match replaced by `[[REDACTED:<kind>]]`. */
51
- redacted: string;
52
- /** One entry per match, in scan order. */
53
- hits: RedactionHit[];
54
- }
55
-
56
- /**
57
- * Ordered pattern list. Earlier patterns win — specific formats (e.g.
58
- * `sk-...`) are checked before the generic "long base64" fallback so
59
- * their `kind` tag is accurate.
60
- *
61
- * Every pattern is intentionally over-conservative — we would rather
62
- * redact a false-positive hash than leak a real key. Users can inspect
63
- * `.mandu/brain-redactions.jsonl` to spot over-redaction.
64
- */
65
- const PATTERNS: Array<{ kind: RedactionKind; regex: RegExp }> = [
66
- // OpenAI-style keys: sk-XXXX, sk-proj-XXXX, pk-XXXX.
67
- { kind: "openai-key", regex: /\b(?:sk|pk)-(?:proj-)?[A-Za-z0-9_\-]{16,}\b/g },
68
- // Stripe live/test keys.
69
- { kind: "stripe-key", regex: /\brk_(?:live|test)_[A-Za-z0-9]{16,}\b/g },
70
- // GitHub PATs — ghp_, gho_, ghu_, ghs_, ghr_.
71
- { kind: "github-token", regex: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g },
72
- // Slack bot / user tokens.
73
- { kind: "slack-token", regex: /\bxox[abpors]-[A-Za-z0-9-]{10,}\b/g },
74
- // AWS access key id (fixed-width 20 char uppercase starting w/ AKIA / ASIA).
75
- { kind: "aws-key", regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
76
- // Authorization: Bearer ...
77
- {
78
- kind: "bearer-token",
79
- regex: /(?:Authorization:\s*)?\bBearer\s+[A-Za-z0-9._\-]{16,}/gi,
80
- },
81
- // `.env` path references — we redact the path itself, not the
82
- // surrounding sentence. "see .env" → "see [[REDACTED:env-ref]]".
83
- // Trailing lookahead matches sentence punctuation / whitespace /
84
- // end-of-string so "see .env." / ".env.production." both redact.
85
- { kind: "env-ref", regex: /\.env(?:\.[a-z]+)?(?=[\s,.;:!?'")`]|$)/g },
86
- // KEY="value" / KEY=value assignments, where KEY contains
87
- // API_KEY/SECRET/TOKEN/PASSWORD. Matches the whole assignment.
88
- {
89
- kind: "api-key-assignment",
90
- regex:
91
- /\b[A-Z][A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD|PASSWD)[A-Z0-9_]*\s*=\s*["']?[^\s"'\n]+["']?/g,
92
- },
93
- // JWTs (three base64url segments separated by dots).
94
- {
95
- kind: "jwt",
96
- regex: /\beyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\b/g,
97
- },
98
- // Generic long base64/hex blob (20+ chars, mixed case or digit-heavy).
99
- // Runs last so more specific kinds claim first.
100
- {
101
- kind: "long-base64",
102
- regex: /\b[A-Za-z0-9+/=_\-]{24,}\b/g,
103
- },
104
- ];
105
-
106
- /**
107
- * Sample a match for audit logging — never return the full secret.
108
- *
109
- * Returns `"<first 4 chars>...<last 2 chars>"` for entries longer
110
- * than 8 characters, and a hard-coded stub otherwise. The sample is
111
- * deliberately short so it cannot be used to reconstruct the key.
112
- */
113
- function sampleOf(match: string): string {
114
- if (match.length <= 8) return "***";
115
- return `${match.slice(0, 4)}...${match.slice(-2)}`;
116
- }
117
-
118
- /**
119
- * Scan `input` for secrets and return a redacted copy + hit list.
120
- *
121
- * The scan is non-overlapping: once a region is claimed by an earlier
122
- * pattern, later patterns cannot match inside it. This is enforced by
123
- * walking the hit set and rebuilding the string in one pass rather than
124
- * running `.replace()` per-pattern (which would allow the generic
125
- * "long-base64" to fire on the already-inserted `[[REDACTED:...]]`
126
- * marker).
127
- *
128
- * @param input Raw text about to be transmitted to a cloud adapter.
129
- * @returns Redacted text plus audit-safe hit list.
130
- */
131
- export function redactSecrets(input: string): RedactionResult {
132
- if (!input || input.length === 0) {
133
- return { redacted: input ?? "", hits: [] };
134
- }
135
-
136
- // 1. Collect every candidate hit across every pattern.
137
- const raw: RedactionHit[] = [];
138
- for (const { kind, regex } of PATTERNS) {
139
- regex.lastIndex = 0;
140
- let m: RegExpExecArray | null;
141
- while ((m = regex.exec(input)) !== null) {
142
- if (m[0].length === 0) {
143
- regex.lastIndex += 1;
144
- continue;
145
- }
146
- raw.push({
147
- kind,
148
- start: m.index,
149
- end: m.index + m[0].length,
150
- sample: sampleOf(m[0]),
151
- });
152
- }
153
- }
154
-
155
- if (raw.length === 0) {
156
- return { redacted: input, hits: [] };
157
- }
158
-
159
- // 2. Sort by start offset; on tie prefer the earlier pattern (which
160
- // means the more specific kind since PATTERNS is ordered).
161
- raw.sort((a, b) => (a.start - b.start) || (a.end - b.end));
162
-
163
- // 3. Greedy non-overlap: keep the first match; skip anything that
164
- // starts before the last accepted match ended.
165
- const accepted: RedactionHit[] = [];
166
- let cursor = -1;
167
- for (const h of raw) {
168
- if (h.start < cursor) continue;
169
- accepted.push(h);
170
- cursor = h.end;
171
- }
172
-
173
- // 4. Rebuild the output in one pass.
174
- let out = "";
175
- let i = 0;
176
- for (const h of accepted) {
177
- out += input.slice(i, h.start);
178
- out += `[[REDACTED:${h.kind}]]`;
179
- i = h.end;
180
- }
181
- out += input.slice(i);
182
-
183
- return { redacted: out, hits: accepted };
184
- }
185
-
186
- /**
187
- * Convenience — returns only the redacted string.
188
- *
189
- * Use `redactSecrets()` directly when you also need the hit list (for
190
- * audit logging). Use this when you just want "scrub and forward".
191
- */
192
- export function redact(input: string): string {
193
- return redactSecrets(input).hits.length === 0
194
- ? input
195
- : redactSecrets(input).redacted;
196
- }
1
+ /**
2
+ * Brain — pre-transmission secret redactor (Issue #235)
3
+ *
4
+ * Every payload destined for a cloud adapter is passed through
5
+ * `redactSecrets()` FIRST. The function returns:
6
+ *
7
+ * - `{ redacted, hits }` — redacted payload + list of what matched, so
8
+ * the CLI / adapter can print an audit line and append to
9
+ * `.mandu/brain-redactions.jsonl` for user inspection.
10
+ *
11
+ * Design principles (user feedback — Mandu as connector, not owner):
12
+ * - Mandu NEVER transmits a prompt without passing it through here.
13
+ * - Patterns are conservative (false positives preferred over leaks).
14
+ * - No regex is anchored; we scan the full text so embedded secrets
15
+ * inside a diff block are still caught.
16
+ * - All replacements collapse to a single token `[[REDACTED:<kind>]]`
17
+ * so the cloud model sees a stable shape rather than a mangled
18
+ * partial key that could still be reconstructed.
19
+ *
20
+ * @see docs/brain/oauth-adapters.md (if that ever ships — for now this
21
+ * file + its tests are the source of truth).
22
+ */
23
+ export type RedactionKind =
24
+ | "openai-key"
25
+ | "stripe-key"
26
+ | "github-token"
27
+ | "slack-token"
28
+ | "aws-key"
29
+ | "bearer-token"
30
+ | "env-ref"
31
+ | "api-key-assignment"
32
+ | "jwt"
33
+ | "long-base64";
34
+
35
+ export interface RedactionHit {
36
+ /** The kind of secret matched (for audit logging). */
37
+ kind: RedactionKind;
38
+ /** Byte offset into the original input where the match started. */
39
+ start: number;
40
+ /** Byte offset into the original input where the match ended. */
41
+ end: number;
42
+ /**
43
+ * Short sample of the redacted material — always prefix + ellipsis,
44
+ * never the full secret. Safe to log.
45
+ */
46
+ sample: string;
47
+ }
48
+
49
+ export interface RedactionResult {
50
+ /** Input with every match replaced by `[[REDACTED:<kind>]]`. */
51
+ redacted: string;
52
+ /** One entry per match, in scan order. */
53
+ hits: RedactionHit[];
54
+ }
55
+
56
+ /**
57
+ * Ordered pattern list. Earlier patterns win — specific formats (e.g.
58
+ * `sk-...`) are checked before the generic "long base64" fallback so
59
+ * their `kind` tag is accurate.
60
+ *
61
+ * Every pattern is intentionally over-conservative — we would rather
62
+ * redact a false-positive hash than leak a real key. Users can inspect
63
+ * `.mandu/brain-redactions.jsonl` to spot over-redaction.
64
+ */
65
+ const PATTERNS: Array<{ kind: RedactionKind; regex: RegExp }> = [
66
+ // OpenAI-style keys: sk-XXXX, sk-proj-XXXX, pk-XXXX.
67
+ { kind: "openai-key", regex: /\b(?:sk|pk)-(?:proj-)?[A-Za-z0-9_\-]{16,}\b/g },
68
+ // Stripe live/test keys.
69
+ { kind: "stripe-key", regex: /\brk_(?:live|test)_[A-Za-z0-9]{16,}\b/g },
70
+ // GitHub PATs — ghp_, gho_, ghu_, ghs_, ghr_.
71
+ { kind: "github-token", regex: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g },
72
+ // Slack bot / user tokens.
73
+ { kind: "slack-token", regex: /\bxox[abpors]-[A-Za-z0-9-]{10,}\b/g },
74
+ // AWS access key id (fixed-width 20 char uppercase starting w/ AKIA / ASIA).
75
+ { kind: "aws-key", regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
76
+ // Authorization: Bearer ...
77
+ {
78
+ kind: "bearer-token",
79
+ regex: /(?:Authorization:\s*)?\bBearer\s+[A-Za-z0-9._\-]{16,}/gi,
80
+ },
81
+ // `.env` path references — we redact the path itself, not the
82
+ // surrounding sentence. "see .env" → "see [[REDACTED:env-ref]]".
83
+ // Trailing lookahead matches sentence punctuation / whitespace /
84
+ // end-of-string so "see .env." / ".env.production." both redact.
85
+ { kind: "env-ref", regex: /\.env(?:\.[a-z]+)?(?=[\s,.;:!?'")`]|$)/g },
86
+ // KEY="value" / KEY=value assignments, where KEY contains
87
+ // API_KEY/SECRET/TOKEN/PASSWORD. Matches the whole assignment.
88
+ {
89
+ kind: "api-key-assignment",
90
+ regex:
91
+ /\b[A-Z][A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD|PASSWD)[A-Z0-9_]*\s*=\s*["']?[^\s"'\n]+["']?/g,
92
+ },
93
+ // JWTs (three base64url segments separated by dots).
94
+ {
95
+ kind: "jwt",
96
+ regex: /\beyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\b/g,
97
+ },
98
+ // Generic long base64/hex blob (20+ chars, mixed case or digit-heavy).
99
+ // Runs last so more specific kinds claim first.
100
+ {
101
+ kind: "long-base64",
102
+ regex: /\b[A-Za-z0-9+/=_\-]{24,}\b/g,
103
+ },
104
+ ];
105
+
106
+ /**
107
+ * Sample a match for audit logging — never return the full secret.
108
+ *
109
+ * Returns `"<first 4 chars>...<last 2 chars>"` for entries longer
110
+ * than 8 characters, and a hard-coded stub otherwise. The sample is
111
+ * deliberately short so it cannot be used to reconstruct the key.
112
+ */
113
+ function sampleOf(match: string): string {
114
+ if (match.length <= 8) return "***";
115
+ return `${match.slice(0, 4)}...${match.slice(-2)}`;
116
+ }
117
+
118
+ /**
119
+ * Scan `input` for secrets and return a redacted copy + hit list.
120
+ *
121
+ * The scan is non-overlapping: once a region is claimed by an earlier
122
+ * pattern, later patterns cannot match inside it. This is enforced by
123
+ * walking the hit set and rebuilding the string in one pass rather than
124
+ * running `.replace()` per-pattern (which would allow the generic
125
+ * "long-base64" to fire on the already-inserted `[[REDACTED:...]]`
126
+ * marker).
127
+ *
128
+ * @param input Raw text about to be transmitted to a cloud adapter.
129
+ * @returns Redacted text plus audit-safe hit list.
130
+ */
131
+ export function redactSecrets(input: string): RedactionResult {
132
+ if (!input || input.length === 0) {
133
+ return { redacted: input ?? "", hits: [] };
134
+ }
135
+
136
+ // 1. Collect every candidate hit across every pattern.
137
+ const raw: RedactionHit[] = [];
138
+ for (const { kind, regex } of PATTERNS) {
139
+ regex.lastIndex = 0;
140
+ let m: RegExpExecArray | null;
141
+ while ((m = regex.exec(input)) !== null) {
142
+ if (m[0].length === 0) {
143
+ regex.lastIndex += 1;
144
+ continue;
145
+ }
146
+ raw.push({
147
+ kind,
148
+ start: m.index,
149
+ end: m.index + m[0].length,
150
+ sample: sampleOf(m[0]),
151
+ });
152
+ }
153
+ }
154
+
155
+ if (raw.length === 0) {
156
+ return { redacted: input, hits: [] };
157
+ }
158
+
159
+ // 2. Sort by start offset; on tie prefer the earlier pattern (which
160
+ // means the more specific kind since PATTERNS is ordered).
161
+ raw.sort((a, b) => (a.start - b.start) || (a.end - b.end));
162
+
163
+ // 3. Greedy non-overlap: keep the first match; skip anything that
164
+ // starts before the last accepted match ended.
165
+ const accepted: RedactionHit[] = [];
166
+ let cursor = -1;
167
+ for (const h of raw) {
168
+ if (h.start < cursor) continue;
169
+ accepted.push(h);
170
+ cursor = h.end;
171
+ }
172
+
173
+ // 4. Rebuild the output in one pass.
174
+ let out = "";
175
+ let i = 0;
176
+ for (const h of accepted) {
177
+ out += input.slice(i, h.start);
178
+ out += `[[REDACTED:${h.kind}]]`;
179
+ i = h.end;
180
+ }
181
+ out += input.slice(i);
182
+
183
+ return { redacted: out, hits: accepted };
184
+ }
185
+
186
+ /**
187
+ * Convenience — returns only the redacted string.
188
+ *
189
+ * Use `redactSecrets()` directly when you also need the hit list (for
190
+ * audit logging). Use this when you just want "scrub and forward".
191
+ */
192
+ export function redact(input: string): string {
193
+ return redactSecrets(input).hits.length === 0
194
+ ? input
195
+ : redactSecrets(input).redacted;
196
+ }