@mandujs/core 0.8.2 → 0.9.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.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Brain v0.1 - Module Exports
3
+ *
4
+ * Brain handles two responsibilities:
5
+ * 1. Doctor (error recovery): Guard failure analysis + minimal patch suggestions
6
+ * 2. Watch (error prevention): File change warnings (no blocking)
7
+ */
8
+
9
+ // Types
10
+ export * from "./types";
11
+
12
+ // Adapters
13
+ export * from "./adapters";
14
+
15
+ // Permissions
16
+ export {
17
+ detectEnvironment,
18
+ shouldEnableBrain,
19
+ isSafeForModification,
20
+ isDangerousCommand,
21
+ validatePatchSuggestion,
22
+ filterSafePatchSuggestions,
23
+ isolatedBrainExecution,
24
+ } from "./permissions";
25
+
26
+ // Memory
27
+ export {
28
+ createSessionMemory,
29
+ SessionMemory,
30
+ getSessionMemory,
31
+ resetSessionMemory,
32
+ } from "./memory";
33
+
34
+ // Brain core
35
+ export {
36
+ Brain,
37
+ getBrain,
38
+ initializeBrain,
39
+ isBrainEnabled,
40
+ type BrainStatus,
41
+ type BrainInitOptions,
42
+ } from "./brain";
43
+
44
+ // Doctor
45
+ export * from "./doctor";
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Brain v0.1 - Session Memory (Lightweight)
3
+ *
4
+ * Session-only memory for Brain operations.
5
+ * No long-term persistence - avoids privacy/security/reproducibility issues.
6
+ */
7
+
8
+ import type { BrainMemory } from "./types";
9
+ import type { GuardViolation } from "../guard/rules";
10
+ import type { RoutesManifest } from "../spec/schema";
11
+
12
+ /**
13
+ * Create a new session memory
14
+ */
15
+ export function createSessionMemory(): BrainMemory {
16
+ const now = new Date();
17
+ return {
18
+ lastGuardResult: null,
19
+ lastDiff: null,
20
+ specSnapshot: null,
21
+ sessionStart: now,
22
+ lastActivity: now,
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Session memory manager
28
+ *
29
+ * Provides in-memory storage for the current Brain session.
30
+ * Memory is cleared when the process exits.
31
+ */
32
+ export class SessionMemory {
33
+ private memory: BrainMemory;
34
+
35
+ constructor() {
36
+ this.memory = createSessionMemory();
37
+ }
38
+
39
+ /**
40
+ * Update the last Guard result
41
+ */
42
+ setGuardResult(violations: GuardViolation[]): void {
43
+ this.memory.lastGuardResult = violations;
44
+ this.memory.lastActivity = new Date();
45
+ }
46
+
47
+ /**
48
+ * Get the last Guard result
49
+ */
50
+ getGuardResult(): GuardViolation[] | null {
51
+ return this.memory.lastGuardResult;
52
+ }
53
+
54
+ /**
55
+ * Update the last file diff
56
+ */
57
+ setDiff(diff: string): void {
58
+ this.memory.lastDiff = diff;
59
+ this.memory.lastActivity = new Date();
60
+ }
61
+
62
+ /**
63
+ * Get the last file diff
64
+ */
65
+ getDiff(): string | null {
66
+ return this.memory.lastDiff;
67
+ }
68
+
69
+ /**
70
+ * Update the spec snapshot
71
+ */
72
+ setSpecSnapshot(manifest: RoutesManifest): void {
73
+ this.memory.specSnapshot = manifest;
74
+ this.memory.lastActivity = new Date();
75
+ }
76
+
77
+ /**
78
+ * Get the spec snapshot
79
+ */
80
+ getSpecSnapshot(): RoutesManifest | null {
81
+ return this.memory.specSnapshot;
82
+ }
83
+
84
+ /**
85
+ * Get session duration in seconds
86
+ */
87
+ getSessionDuration(): number {
88
+ const now = new Date();
89
+ return Math.floor(
90
+ (now.getTime() - this.memory.sessionStart.getTime()) / 1000
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Get time since last activity in seconds
96
+ */
97
+ getIdleTime(): number {
98
+ const now = new Date();
99
+ return Math.floor(
100
+ (now.getTime() - this.memory.lastActivity.getTime()) / 1000
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Get full memory state (for debugging)
106
+ */
107
+ getState(): Readonly<BrainMemory> {
108
+ return { ...this.memory };
109
+ }
110
+
111
+ /**
112
+ * Clear all memory
113
+ */
114
+ clear(): void {
115
+ this.memory = createSessionMemory();
116
+ }
117
+
118
+ /**
119
+ * Check if memory has any data
120
+ */
121
+ hasData(): boolean {
122
+ return (
123
+ this.memory.lastGuardResult !== null ||
124
+ this.memory.lastDiff !== null ||
125
+ this.memory.specSnapshot !== null
126
+ );
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Global session memory instance
132
+ * Single instance per process for simplicity
133
+ */
134
+ let globalMemory: SessionMemory | null = null;
135
+
136
+ /**
137
+ * Get or create the global session memory
138
+ */
139
+ export function getSessionMemory(): SessionMemory {
140
+ if (!globalMemory) {
141
+ globalMemory = new SessionMemory();
142
+ }
143
+ return globalMemory;
144
+ }
145
+
146
+ /**
147
+ * Reset the global session memory
148
+ */
149
+ export function resetSessionMemory(): void {
150
+ if (globalMemory) {
151
+ globalMemory.clear();
152
+ }
153
+ globalMemory = null;
154
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Brain v0.1 - Permission Verification
3
+ *
4
+ * Ensures Brain operates safely within defined boundaries.
5
+ * Brain never blocks operations - only warns and suggests.
6
+ */
7
+
8
+ import type { BrainPolicy, EnvironmentInfo, PatchSuggestion } from "./types";
9
+ import { DEFAULT_BRAIN_POLICY } from "./types";
10
+
11
+ /**
12
+ * CI environment detection patterns
13
+ */
14
+ const CI_ENVIRONMENT_VARS = [
15
+ "CI",
16
+ "CONTINUOUS_INTEGRATION",
17
+ "GITHUB_ACTIONS",
18
+ "GITLAB_CI",
19
+ "CIRCLECI",
20
+ "TRAVIS",
21
+ "JENKINS_URL",
22
+ "BUILDKITE",
23
+ "TEAMCITY_VERSION",
24
+ "AZURE_PIPELINES",
25
+ "BITBUCKET_PIPELINES",
26
+ ] as const;
27
+
28
+ /**
29
+ * CI provider detection
30
+ */
31
+ const CI_PROVIDERS: Record<string, string> = {
32
+ GITHUB_ACTIONS: "GitHub Actions",
33
+ GITLAB_CI: "GitLab CI",
34
+ CIRCLECI: "CircleCI",
35
+ TRAVIS: "Travis CI",
36
+ JENKINS_URL: "Jenkins",
37
+ BUILDKITE: "Buildkite",
38
+ TEAMCITY_VERSION: "TeamCity",
39
+ AZURE_PIPELINES: "Azure Pipelines",
40
+ BITBUCKET_PIPELINES: "Bitbucket Pipelines",
41
+ };
42
+
43
+ /**
44
+ * Safe file patterns that Brain can suggest modifications to
45
+ */
46
+ const SAFE_FILE_PATTERNS = [
47
+ /^spec\/slots\/.+\.slot\.ts$/,
48
+ /^spec\/contracts\/.+\.contract\.ts$/,
49
+ /^spec\/routes\.manifest\.json$/,
50
+ /^mandu\.config\.(ts|js|json)$/,
51
+ ] as const;
52
+
53
+ /**
54
+ * Protected file patterns that Brain should never suggest modifying
55
+ */
56
+ const PROTECTED_FILE_PATTERNS = [
57
+ /^generated\//,
58
+ /node_modules\//,
59
+ /\.git\//,
60
+ /package-lock\.json$/,
61
+ /bun\.lockb$/,
62
+ /pnpm-lock\.yaml$/,
63
+ /yarn\.lock$/,
64
+ ] as const;
65
+
66
+ /**
67
+ * Dangerous commands that require extra confirmation
68
+ */
69
+ const DANGEROUS_COMMANDS = [
70
+ "rm -rf",
71
+ "git push --force",
72
+ "git reset --hard",
73
+ "DROP TABLE",
74
+ "DELETE FROM",
75
+ ] as const;
76
+
77
+ /**
78
+ * Detect the current environment
79
+ */
80
+ export function detectEnvironment(): EnvironmentInfo {
81
+ const env = typeof process !== "undefined" ? process.env : {};
82
+
83
+ // Check for CI environment
84
+ let isCI = false;
85
+ let ciProvider: string | undefined;
86
+
87
+ for (const envVar of CI_ENVIRONMENT_VARS) {
88
+ if (env[envVar]) {
89
+ isCI = true;
90
+ if (CI_PROVIDERS[envVar]) {
91
+ ciProvider = CI_PROVIDERS[envVar];
92
+ }
93
+ break;
94
+ }
95
+ }
96
+
97
+ // Check if generic CI is set
98
+ if (!ciProvider && (env.CI === "true" || env.CI === "1")) {
99
+ isCI = true;
100
+ ciProvider = "Unknown CI";
101
+ }
102
+
103
+ // Check for development environment
104
+ const isDevelopment =
105
+ !isCI &&
106
+ (env.NODE_ENV === "development" ||
107
+ env.NODE_ENV === undefined ||
108
+ env.NODE_ENV === "");
109
+
110
+ return {
111
+ isCI,
112
+ ciProvider,
113
+ isDevelopment,
114
+ modelAvailable: false, // Will be set by Brain after adapter check
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Determine if Brain should be enabled based on policy and environment
120
+ */
121
+ export function shouldEnableBrain(
122
+ policy: BrainPolicy = DEFAULT_BRAIN_POLICY,
123
+ env?: EnvironmentInfo
124
+ ): boolean {
125
+ const environment = env ?? detectEnvironment();
126
+
127
+ // Explicit override
128
+ if (policy.enabled === "always") return true;
129
+ if (policy.enabled === "never") return false;
130
+
131
+ // Auto mode: disable in CI
132
+ if (environment.isCI && !policy.ci) {
133
+ return false;
134
+ }
135
+
136
+ // Auto mode: enable locally
137
+ if (environment.isDevelopment) {
138
+ if (environment.modelAvailable) {
139
+ return policy.localWithModel;
140
+ }
141
+ return policy.localNoModel !== "disabled";
142
+ }
143
+
144
+ return false;
145
+ }
146
+
147
+ /**
148
+ * Check if a file path is safe for Brain to suggest modifications
149
+ */
150
+ export function isSafeForModification(filePath: string): boolean {
151
+ // Normalize path separators
152
+ const normalizedPath = filePath.replace(/\\/g, "/");
153
+
154
+ // Check protected patterns first
155
+ for (const pattern of PROTECTED_FILE_PATTERNS) {
156
+ if (pattern.test(normalizedPath)) {
157
+ return false;
158
+ }
159
+ }
160
+
161
+ // Check if it matches safe patterns
162
+ for (const pattern of SAFE_FILE_PATTERNS) {
163
+ if (pattern.test(normalizedPath)) {
164
+ return true;
165
+ }
166
+ }
167
+
168
+ // Default: not safe (conservative approach)
169
+ return false;
170
+ }
171
+
172
+ /**
173
+ * Check if a command is dangerous and needs confirmation
174
+ */
175
+ export function isDangerousCommand(command: string): boolean {
176
+ const normalizedCommand = command.toLowerCase().trim();
177
+
178
+ for (const dangerous of DANGEROUS_COMMANDS) {
179
+ if (normalizedCommand.includes(dangerous.toLowerCase())) {
180
+ return true;
181
+ }
182
+ }
183
+
184
+ return false;
185
+ }
186
+
187
+ /**
188
+ * Validate a patch suggestion for safety
189
+ */
190
+ export interface PatchValidation {
191
+ valid: boolean;
192
+ reason?: string;
193
+ requiresConfirmation: boolean;
194
+ }
195
+
196
+ export function validatePatchSuggestion(
197
+ patch: PatchSuggestion
198
+ ): PatchValidation {
199
+ // Check file safety for modify operations
200
+ if (patch.type === "modify" || patch.type === "add") {
201
+ if (!isSafeForModification(patch.file)) {
202
+ return {
203
+ valid: false,
204
+ reason: `File '${patch.file}' is not safe for automatic modification`,
205
+ requiresConfirmation: false,
206
+ };
207
+ }
208
+ }
209
+
210
+ // Check command safety
211
+ if (patch.type === "command" && patch.command) {
212
+ if (isDangerousCommand(patch.command)) {
213
+ return {
214
+ valid: true,
215
+ reason: `Command '${patch.command}' is potentially dangerous`,
216
+ requiresConfirmation: true,
217
+ };
218
+ }
219
+ }
220
+
221
+ // Check confidence threshold
222
+ if (patch.confidence < 0.3) {
223
+ return {
224
+ valid: true,
225
+ reason: "Low confidence suggestion",
226
+ requiresConfirmation: true,
227
+ };
228
+ }
229
+
230
+ return {
231
+ valid: true,
232
+ requiresConfirmation: false,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Filter patch suggestions to only include safe ones
238
+ */
239
+ export function filterSafePatchSuggestions(
240
+ patches: PatchSuggestion[]
241
+ ): PatchSuggestion[] {
242
+ return patches.filter((patch) => {
243
+ const validation = validatePatchSuggestion(patch);
244
+ return validation.valid;
245
+ });
246
+ }
247
+
248
+ /**
249
+ * Create an isolated execution context for Brain operations
250
+ * Ensures Brain failures don't affect Core functionality
251
+ */
252
+ export async function isolatedBrainExecution<T>(
253
+ operation: () => Promise<T>,
254
+ fallback: T
255
+ ): Promise<{ result: T; error?: Error }> {
256
+ try {
257
+ const result = await operation();
258
+ return { result };
259
+ } catch (error) {
260
+ // Log error but don't propagate
261
+ console.error(
262
+ "[Brain] Isolated error:",
263
+ error instanceof Error ? error.message : error
264
+ );
265
+ return {
266
+ result: fallback,
267
+ error: error instanceof Error ? error : new Error(String(error)),
268
+ };
269
+ }
270
+ }