feat: add test validation command and improve environment variable handling

- Introduced a new command for validating tests, providing detailed instructions for running tests and fixing failures based on code changes.
- Updated the environment variable handling in the Claude provider to only allow explicitly defined variables, enhancing security and preventing leakage of sensitive information.
- Improved feature loading to handle errors more gracefully and load features concurrently, optimizing performance.
- Centralized port configuration for the Automaker application to prevent accidental termination of critical services.
This commit is contained in:
Test User
2025-12-31 20:36:20 -05:00
parent 3f4f2199eb
commit 2828431cca
9 changed files with 98 additions and 39 deletions

View File

@@ -15,22 +15,30 @@ import type {
ModelDefinition,
} from './types.js';
// Automaker-specific environment variables that should not pollute agent processes
// These are internal to Automaker and would interfere with user projects
// (e.g., PORT=3008 would cause Next.js/Vite to use the wrong port)
const AUTOMAKER_ENV_VARS = ['PORT', 'DATA_DIR', 'AUTOMAKER_API_KEY', 'NODE_PATH'];
// Explicit allowlist of environment variables to pass to the SDK.
// Only these vars are passed - nothing else from process.env leaks through.
const ALLOWED_ENV_VARS = [
'ANTHROPIC_API_KEY',
'PATH',
'HOME',
'SHELL',
'TERM',
'USER',
'LANG',
'LC_ALL',
];
/**
* Build a clean environment for the SDK, excluding Automaker-specific variables
* Build environment for the SDK with only explicitly allowed variables
*/
function buildCleanEnv(): Record<string, string | undefined> {
const cleanEnv: Record<string, string | undefined> = {};
for (const [key, value] of Object.entries(process.env)) {
if (!AUTOMAKER_ENV_VARS.includes(key)) {
cleanEnv[key] = value;
function buildEnv(): Record<string, string | undefined> {
const env: Record<string, string | undefined> = {};
for (const key of ALLOWED_ENV_VARS) {
if (process.env[key]) {
env[key] = process.env[key];
}
}
return cleanEnv;
return env;
}
export class ClaudeProvider extends BaseProvider {
@@ -75,9 +83,8 @@ export class ClaudeProvider extends BaseProvider {
systemPrompt,
maxTurns,
cwd,
// Pass clean environment to SDK, excluding Automaker-specific variables
// This prevents PORT, DATA_DIR, etc. from polluting agent-spawned processes
env: buildCleanEnv(),
// Pass only explicitly allowed environment variables to SDK
env: buildEnv(),
// Only restrict tools if explicitly set OR (no MCP / unrestricted disabled)
...(allowedTools && shouldRestrictTools && { allowedTools }),
...(!allowedTools && shouldRestrictTools && { allowedTools: defaultTools }),

View File

@@ -185,9 +185,8 @@ export class FeatureLoader {
})) as any[];
const featureDirs = entries.filter((entry) => entry.isDirectory());
// Load each feature
const features: Feature[] = [];
for (const dir of featureDirs) {
// Load all features concurrently (secureFs has built-in concurrency limiting)
const featurePromises = featureDirs.map(async (dir) => {
const featureId = dir.name;
const featureJsonPath = this.getFeatureJsonPath(projectPath, featureId);
@@ -199,13 +198,13 @@ export class FeatureLoader {
logger.warn(
`[FeatureLoader] Feature ${featureId} missing required 'id' field, skipping`
);
continue;
return null;
}
features.push(feature);
return feature as Feature;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
continue;
return null;
} else if (error instanceof SyntaxError) {
logger.warn(
`[FeatureLoader] Failed to parse feature.json for ${featureId}: ${error.message}`
@@ -216,8 +215,12 @@ export class FeatureLoader {
(error as Error).message
);
}
return null;
}
}
});
const results = await Promise.all(featurePromises);
const features = results.filter((f): f is Feature => f !== null);
// Sort by creation order (feature IDs contain timestamp)
features.sort((a, b) => {