feat(installer): add memtrace bootstrap installer and test suite (Epic 1)
- Add install-bmad-memtrace.sh: aggressive cleanup, .memtrace-workspace generation, MCP JSON config injection - Add test/test-inject-mcp-config.js: MCP config injection tests - Add test/verify-installer.js: installer integration verification tests
This commit is contained in:
parent
bf572f945f
commit
58f073ebdc
|
|
@ -0,0 +1,113 @@
|
|||
#!/bin/bash
|
||||
# install-bmad-memtrace.sh
|
||||
# Installation script for BMad Memtrace Integration
|
||||
# Aggressively cleans up the repository clone, preserving only necessary files.
|
||||
|
||||
# --- Interactive mode selection ---
|
||||
# First step: confirm the user wants Memtrace (not Vanilla BMad).
|
||||
while true; do
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " BMad-Memtrace Installation"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "This installer sets up the Memtrace-integrated fork of BMad Method."
|
||||
echo ""
|
||||
read -r -p "Choose mode [Memtrace / Vanilla]: " mode_choice || { echo "Error: Unexpected end of input. Aborting."; exit 1; }
|
||||
mode_choice=$(printf '%s' "$mode_choice" | tr '[:upper:]' '[:lower:]')
|
||||
case "$mode_choice" in
|
||||
memtrace)
|
||||
echo ""
|
||||
echo "Proceeding with Memtrace-integrated installation..."
|
||||
echo ""
|
||||
break
|
||||
;;
|
||||
vanilla)
|
||||
echo ""
|
||||
echo "You selected Vanilla BMad Method."
|
||||
echo "This fork includes Memtrace structural analysis integration."
|
||||
echo "For the official BMad Method without Memtrace, clone:"
|
||||
echo " https://github.com/bmad-code-org/BMAD-METHOD.git"
|
||||
echo ""
|
||||
echo "Installation aborted. No files were modified."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
echo "Invalid choice: '${mode_choice}'. Please type 'Memtrace' or 'Vanilla'."
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
set -e
|
||||
|
||||
echo "Starting BMad Memtrace standalone environment setup..."
|
||||
|
||||
INSTALL_DIR="bmad-install"
|
||||
|
||||
# Create a safe staging directory
|
||||
echo "Creating staging directory: $INSTALL_DIR"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
# Move essential files to the staging directory
|
||||
echo "Copying core files to staging directory..."
|
||||
[ -d "_bmad" ] && cp -a _bmad "$INSTALL_DIR/"
|
||||
[ -d ".agents" ] && cp -a .agents "$INSTALL_DIR/"
|
||||
[ -f "package.json" ] && cp -a package.json "$INSTALL_DIR/"
|
||||
# Also copy docs if present
|
||||
[ -d "docs" ] && cp -a docs "$INSTALL_DIR/"
|
||||
|
||||
# Remove explicit non-essential bmad cloned files and .git
|
||||
echo "Cleaning up legacy clone files via git index..."
|
||||
if [ -d .git ] && command -v git &> /dev/null; then
|
||||
# Use git to get the exact list of cloned files to guarantee precision
|
||||
git ls-files | while IFS= read -r file; do
|
||||
if [ "$file" != "install-bmad-memtrace.sh" ]; then
|
||||
rm -f "$file"
|
||||
fi
|
||||
done
|
||||
else
|
||||
# Fallback if git is not available
|
||||
rm -f README.md LICENSE .gitignore .eslintrc.json tsconfig.json webpack.config.js || true
|
||||
rm -rf _bmad .agents package.json docs || true
|
||||
fi
|
||||
|
||||
echo "Removing .git directory..."
|
||||
rm -rf .git
|
||||
|
||||
# Cleanup any empty directories left behind
|
||||
find . -type d -empty -delete 2>/dev/null || true
|
||||
|
||||
# Copy files back to the root of the project
|
||||
echo "Restoring core files to root..."
|
||||
# We use * to avoid copying the script over itself if it were in the staging dir,
|
||||
# though we intentionally didn't stage it this time to prevent 'Text file busy' issues.
|
||||
cp -a "$INSTALL_DIR"/* . 2>/dev/null || true
|
||||
|
||||
# Remove staging directory
|
||||
echo "Removing staging directory..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
|
||||
# Generate local workspace anchor file (.memtrace-workspace) to prevent 0-nodes errors
|
||||
echo "Generating workspace anchor file..."
|
||||
if [ ! -f .memtrace-workspace ]; then
|
||||
touch .memtrace-workspace
|
||||
echo "Created .memtrace-workspace anchor."
|
||||
else
|
||||
echo ".memtrace-workspace anchor already exists."
|
||||
fi
|
||||
|
||||
# Configure local MCP servers in Claude Desktop and OpenCode
|
||||
if command -v node &> /dev/null; then
|
||||
echo "Configuring local MCP servers..."
|
||||
node _bmad/scripts/memtrace/inject-mcp-config.mjs --mode claude || echo "Warning: Failed to configure Claude Desktop config."
|
||||
node _bmad/scripts/memtrace/inject-mcp-config.mjs --mode opencode || echo "Warning: Failed to configure OpenCode config."
|
||||
else
|
||||
echo "Warning: node command not found. Skipping MCP config injection."
|
||||
echo "To configure MCP manually, please run:"
|
||||
echo " node _bmad/scripts/memtrace/inject-mcp-config.mjs --mode claude"
|
||||
echo " node _bmad/scripts/memtrace/inject-mcp-config.mjs --mode opencode"
|
||||
fi
|
||||
|
||||
echo "Cleanup and configuration complete! You now have a clean, standalone BMad-Memtrace runtime environment."
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
/**
|
||||
* Test Suite for inject-mcp-config.mjs
|
||||
*
|
||||
* Verifies that the MCP server injector works correctly for both Claude Desktop
|
||||
* and OpenCode configurations under different initial file states.
|
||||
*
|
||||
* Usage: node test/test-inject-mcp-config.js
|
||||
*/
|
||||
|
||||
const fs = require('node:fs/promises');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const { exec } = require('node:child_process');
|
||||
|
||||
const colors = {
|
||||
reset: '\u001B[0m',
|
||||
green: '\u001B[32m',
|
||||
red: '\u001B[31m',
|
||||
yellow: '\u001B[33m',
|
||||
cyan: '\u001B[36m',
|
||||
dim: '\u001B[2m',
|
||||
};
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(condition, testName, errorMessage = '') {
|
||||
if (condition) {
|
||||
console.log(`${colors.green}✓${colors.reset} ${testName}`);
|
||||
passed++;
|
||||
} else {
|
||||
console.log(`${colors.red}✗${colors.reset} ${testName}`);
|
||||
if (errorMessage) {
|
||||
console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
|
||||
}
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
// Executes the injector script in a subprocess with custom env variables
|
||||
function runInjector(mode, envOverrides = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const scriptPath = path.resolve(__dirname, '../_bmad/scripts/memtrace/inject-mcp-config.mjs');
|
||||
const command = `node "${scriptPath}" --mode ${mode}`;
|
||||
|
||||
exec(command, {
|
||||
env: { ...process.env, ...envOverrides }
|
||||
}, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject({ error, stdout, stderr });
|
||||
} else {
|
||||
resolve({ stdout, stderr });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
console.log(`${colors.cyan}========================================`);
|
||||
console.log('MCP Config Injector Unit Tests');
|
||||
console.log(`========================================${colors.reset}\n`);
|
||||
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-injector-test-'));
|
||||
|
||||
// ============================================================
|
||||
// Test Suite 1: Claude Desktop configuration injection
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 1: Claude Desktop Configuration${colors.reset}\n`);
|
||||
|
||||
const claudeTestFile = path.join(tempDir, 'claude_desktop_config.json');
|
||||
|
||||
// Test 1.1: File does not exist (creates skeleton)
|
||||
try {
|
||||
await runInjector('claude', { TEST_CLAUDE_CONFIG_PATH: claudeTestFile });
|
||||
const content = await fs.readFile(claudeTestFile, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
|
||||
assert(
|
||||
config.mcpServers !== undefined && config.mcpServers.memtrace !== undefined,
|
||||
'Test 1.1: Creates new Claude config file and injects memtrace server skeleton'
|
||||
);
|
||||
assert(
|
||||
config.mcpServers.memtrace.command === 'memtrace' && config.mcpServers.memtrace.args[0] === 'mcp',
|
||||
'Test 1.1: Injected server details match expected schema format'
|
||||
);
|
||||
} catch (err) {
|
||||
assert(false, 'Test 1.1 Failed with error', err.message || JSON.stringify(err));
|
||||
}
|
||||
|
||||
// Test 1.2: File exists with other servers (preserves other servers)
|
||||
try {
|
||||
const preExistingConfig = {
|
||||
mcpServers: {
|
||||
otherServer: {
|
||||
command: 'node',
|
||||
args: ['other-path/server.js']
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(claudeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8');
|
||||
|
||||
await runInjector('claude', { TEST_CLAUDE_CONFIG_PATH: claudeTestFile });
|
||||
const content = await fs.readFile(claudeTestFile, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
|
||||
assert(
|
||||
config.mcpServers.otherServer !== undefined && config.mcpServers.otherServer.command === 'node',
|
||||
'Test 1.2: Preserves pre-existing mcpServers in Claude config'
|
||||
);
|
||||
assert(
|
||||
config.mcpServers.memtrace !== undefined && config.mcpServers.memtrace.command === 'memtrace',
|
||||
'Test 1.2: Correctly appends memtrace server configuration alongside existing ones'
|
||||
);
|
||||
} catch (err) {
|
||||
assert(false, 'Test 1.2 Failed with error', err.message || JSON.stringify(err));
|
||||
}
|
||||
|
||||
// Test 1.3: File exists and memtrace key already exists (overwrites memtrace key only)
|
||||
try {
|
||||
const preExistingConfig = {
|
||||
mcpServers: {
|
||||
otherServer: {
|
||||
command: 'node',
|
||||
args: ['other-path/server.js']
|
||||
},
|
||||
memtrace: {
|
||||
command: 'old-command',
|
||||
args: ['old-arg']
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(claudeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8');
|
||||
|
||||
await runInjector('claude', { TEST_CLAUDE_CONFIG_PATH: claudeTestFile });
|
||||
const content = await fs.readFile(claudeTestFile, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
|
||||
assert(
|
||||
config.mcpServers.otherServer !== undefined && config.mcpServers.otherServer.command === 'node',
|
||||
'Test 1.3: Overwriting preserves other servers'
|
||||
);
|
||||
assert(
|
||||
config.mcpServers.memtrace.command === 'memtrace' && config.mcpServers.memtrace.args[0] === 'mcp',
|
||||
'Test 1.3: Correctly overwrites only the memtrace key'
|
||||
);
|
||||
} catch (err) {
|
||||
assert(false, 'Test 1.3 Failed with error', err.message || JSON.stringify(err));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Test Suite 2: OpenCode configuration injection
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 2: OpenCode Configuration${colors.reset}\n`);
|
||||
|
||||
const opencodeTestFile = path.join(tempDir, 'opencode.json');
|
||||
|
||||
// Test 2.1: File does not exist (creates skeleton)
|
||||
try {
|
||||
await runInjector('opencode', { TEST_OPENCODE_CONFIG_PATH: opencodeTestFile });
|
||||
const content = await fs.readFile(opencodeTestFile, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
|
||||
assert(
|
||||
config.mcp !== undefined && config.mcp.memtrace !== undefined,
|
||||
'Test 2.1: Creates new OpenCode config file and injects memtrace server skeleton'
|
||||
);
|
||||
assert(
|
||||
config.mcp.memtrace.type === 'local' && config.mcp.memtrace.command[0] === 'memtrace' && config.mcp.memtrace.command[1] === 'mcp',
|
||||
'Test 2.1: Injected server details match expected OpenCode schema format'
|
||||
);
|
||||
} catch (err) {
|
||||
assert(false, 'Test 2.1 Failed with error', err.message || JSON.stringify(err));
|
||||
}
|
||||
|
||||
// Test 2.2: File exists with other keys (preserves other keys)
|
||||
try {
|
||||
const preExistingConfig = {
|
||||
mcp: {
|
||||
otherServer: {
|
||||
type: 'local',
|
||||
command: ['other-server']
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(opencodeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8');
|
||||
|
||||
await runInjector('opencode', { TEST_OPENCODE_CONFIG_PATH: opencodeTestFile });
|
||||
const content = await fs.readFile(opencodeTestFile, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
|
||||
assert(
|
||||
config.mcp.otherServer !== undefined && config.mcp.otherServer.type === 'local',
|
||||
'Test 2.2: Preserves pre-existing mcp in OpenCode config'
|
||||
);
|
||||
assert(
|
||||
config.mcp.memtrace !== undefined && config.mcp.memtrace.type === 'local',
|
||||
'Test 2.2: Correctly appends memtrace server configuration alongside existing ones'
|
||||
);
|
||||
} catch (err) {
|
||||
assert(false, 'Test 2.2 Failed with error', err.message || JSON.stringify(err));
|
||||
}
|
||||
|
||||
// Test 2.3: File exists and memtrace key already exists (overwrites memtrace key only)
|
||||
try {
|
||||
const preExistingConfig = {
|
||||
mcp: {
|
||||
otherServer: {
|
||||
type: 'local',
|
||||
command: ['other-server']
|
||||
},
|
||||
memtrace: {
|
||||
type: 'remote',
|
||||
command: ['old-memtrace']
|
||||
}
|
||||
}
|
||||
};
|
||||
await fs.writeFile(opencodeTestFile, JSON.stringify(preExistingConfig, null, 2), 'utf8');
|
||||
|
||||
await runInjector('opencode', { TEST_OPENCODE_CONFIG_PATH: opencodeTestFile });
|
||||
const content = await fs.readFile(opencodeTestFile, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
|
||||
assert(
|
||||
config.mcp.otherServer !== undefined && config.mcp.otherServer.type === 'local',
|
||||
'Test 2.3: Overwriting preserves other OpenCode servers'
|
||||
);
|
||||
assert(
|
||||
config.mcp.memtrace.type === 'local' && config.mcp.memtrace.command[0] === 'memtrace',
|
||||
'Test 2.3: Correctly overwrites only the memtrace key in OpenCode config'
|
||||
);
|
||||
} catch (err) {
|
||||
assert(false, 'Test 2.3 Failed with error', err.message || JSON.stringify(err));
|
||||
}
|
||||
|
||||
// Clean up
|
||||
try {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
} catch (err) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
|
||||
console.log(`\n${colors.cyan}========================================`);
|
||||
console.log(`Tests Run Summary: Passed: ${passed}, Failed: ${failed}`);
|
||||
console.log(`========================================${colors.reset}\n`);
|
||||
|
||||
if (failed > 0) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
runTests().catch(err => {
|
||||
console.error('Fatal test error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
/**
|
||||
* Verification Test for install-bmad-memtrace.sh
|
||||
*
|
||||
* Simulates the installation process in a clean temporary directory by:
|
||||
* 1. Initializing a mock Git repository.
|
||||
* 2. Committing some dummy "legacy clone" files.
|
||||
* 3. Adding the install script and helper script fixtures.
|
||||
* 4. Running the bash installer.
|
||||
* 5. Verifying cleanup, restore, workspace anchor generation, and MCP config injection.
|
||||
*
|
||||
* Usage: node test/verify-installer.js
|
||||
*/
|
||||
|
||||
const fs = require('node:fs/promises');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const { exec } = require('node:child_process');
|
||||
|
||||
const colors = {
|
||||
reset: '\u001B[0m',
|
||||
green: '\u001B[32m',
|
||||
red: '\u001B[31m',
|
||||
yellow: '\u001B[33m',
|
||||
cyan: '\u001B[36m',
|
||||
dim: '\u001B[2m',
|
||||
};
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(condition, testName, errorMessage = '') {
|
||||
if (condition) {
|
||||
console.log(`${colors.green}✓${colors.reset} ${testName}`);
|
||||
passed++;
|
||||
} else {
|
||||
console.log(`${colors.red}✗${colors.reset} ${testName}`);
|
||||
if (errorMessage) {
|
||||
console.log(` ${colors.dim}${errorMessage}${colors.reset}`);
|
||||
}
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function runCmd(command, cwd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(command, { cwd }, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject({ error, stdout, stderr });
|
||||
} else {
|
||||
resolve({ stdout, stderr });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to ensure parent directories exist
|
||||
async function ensureDir(filePath) {
|
||||
const dir = path.dirname(filePath);
|
||||
try {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
async function runVerification() {
|
||||
console.log(`${colors.cyan}========================================`);
|
||||
console.log('Standalone Installer Verification');
|
||||
console.log(`========================================${colors.reset}\n`);
|
||||
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-installer-verify-'));
|
||||
console.log(`Created temporary test environment at: ${tempDir}\n`);
|
||||
|
||||
try {
|
||||
// 1. Initialize git repository
|
||||
await runCmd('git init -b main', tempDir);
|
||||
await runCmd('git config user.name "Test"', tempDir);
|
||||
await runCmd('git config user.email "test@example.com"', tempDir);
|
||||
|
||||
// 2. Create and commit dummy cloned files (to simulate legacy clone cleanup)
|
||||
const dummyClonedFile = path.join(tempDir, 'README.md');
|
||||
await fs.writeFile(dummyClonedFile, '# Legacy README\n');
|
||||
await runCmd('git add README.md', tempDir);
|
||||
await runCmd('git commit -m "initial commit"', tempDir);
|
||||
|
||||
// 3. Create core BMad directory structure and helper script
|
||||
const helperSourcePath = path.resolve(__dirname, '../_bmad/scripts/memtrace/inject-mcp-config.mjs');
|
||||
const helperDestPath = path.join(tempDir, '_bmad/scripts/memtrace/inject-mcp-config.mjs');
|
||||
await ensureDir(helperDestPath);
|
||||
await fs.copyFile(helperSourcePath, helperDestPath);
|
||||
|
||||
// Also copy install script
|
||||
const installerSourcePath = path.resolve(__dirname, '../install-bmad-memtrace.sh');
|
||||
const installerDestPath = path.join(tempDir, 'install-bmad-memtrace.sh');
|
||||
await fs.copyFile(installerSourcePath, installerDestPath);
|
||||
|
||||
// 4. Run installer script in bash
|
||||
// We override TEST_CLAUDE_CONFIG_PATH and TEST_OPENCODE_CONFIG_PATH so it targets temp config files
|
||||
const claudeTestConfig = path.join(tempDir, 'claude_desktop_config.json');
|
||||
const opencodeTestConfig = path.join(tempDir, 'opencode.json');
|
||||
|
||||
console.log('Running install-bmad-memtrace.sh in temp directory...');
|
||||
const envOverrides = {
|
||||
...process.env,
|
||||
TEST_CLAUDE_CONFIG_PATH: claudeTestConfig,
|
||||
TEST_OPENCODE_CONFIG_PATH: opencodeTestConfig,
|
||||
};
|
||||
|
||||
// Run bash on Windows (locate git-bash path to avoid WSL-relay failures)
|
||||
let command = 'bash install-bmad-memtrace.sh';
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
const { execSync } = require('node:child_process');
|
||||
const gitPath = execSync('where git').toString().trim().split('\r\n')[0];
|
||||
if (gitPath) {
|
||||
const gitDir = path.dirname(gitPath); // C:\Program Files\Git\cmd
|
||||
const gitParent = path.dirname(gitDir);
|
||||
const possibleBash1 = path.join(gitParent, 'bin', 'bash.exe');
|
||||
const possibleBash2 = path.join(gitParent, 'bin', 'sh.exe');
|
||||
|
||||
if (await fs.access(possibleBash1).then(() => true).catch(() => false)) {
|
||||
command = `"${possibleBash1}" install-bmad-memtrace.sh`;
|
||||
} else if (await fs.access(possibleBash2).then(() => true).catch(() => false)) {
|
||||
command = `"${possibleBash2}" install-bmad-memtrace.sh`;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback
|
||||
const paths = [
|
||||
'C:\\Program Files\\Git\\bin\\bash.exe',
|
||||
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
||||
];
|
||||
for (const p of paths) {
|
||||
if (await fs.access(p).then(() => true).catch(() => false)) {
|
||||
command = `"${p}" install-bmad-memtrace.sh`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Running installer with command: ${command}`);
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
exec(command, { cwd: tempDir, env: envOverrides }, (error, stdout, stderr) => {
|
||||
if (error) reject({ error, stdout, stderr });
|
||||
else resolve({ stdout, stderr });
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`${colors.dim}${result.stdout}${colors.reset}\n`);
|
||||
|
||||
// 5. Assertions
|
||||
|
||||
// AC 1: .memtrace-workspace exists
|
||||
const anchorExists = await fs.access(path.join(tempDir, '.memtrace-workspace')).then(() => true).catch(() => false);
|
||||
assert(anchorExists, 'AC 1: .memtrace-workspace anchor file successfully created in project root');
|
||||
|
||||
// Legacy cleanup: README.md is deleted
|
||||
const readmeExists = await fs.access(dummyClonedFile).then(() => true).catch(() => false);
|
||||
assert(!readmeExists, 'Legacy cleanup: Tracked clone files (README.md) successfully deleted');
|
||||
|
||||
// Git removal: .git is deleted
|
||||
const gitExists = await fs.access(path.join(tempDir, '.git')).then(() => true).catch(() => false);
|
||||
assert(!gitExists, 'Security/Standalone: .git directory completely removed');
|
||||
|
||||
// Staging cleanup: bmad-install is deleted
|
||||
const stagingExists = await fs.access(path.join(tempDir, 'bmad-install')).then(() => true).catch(() => false);
|
||||
assert(!stagingExists, 'Runtime cleanup: bmad-install staging directory successfully removed');
|
||||
|
||||
// BMad preservation: _bmad directory remains
|
||||
const bmadExists = await fs.access(path.join(tempDir, '_bmad')).then(() => true).catch(() => false);
|
||||
assert(bmadExists, 'Core preservation: _bmad directory preserved post-cleanup');
|
||||
|
||||
// AC 2: Claude Desktop configuration successfully injected
|
||||
const claudeContent = await fs.readFile(claudeTestConfig, 'utf8');
|
||||
const claudeConfig = JSON.parse(claudeContent);
|
||||
assert(
|
||||
claudeConfig.mcpServers && claudeConfig.mcpServers.memtrace && claudeConfig.mcpServers.memtrace.command === 'memtrace',
|
||||
'AC 2: Claude Desktop config correctly created and populated with memtrace MCP server'
|
||||
);
|
||||
|
||||
// AC 3: OpenCode configuration successfully injected
|
||||
const opencodeContent = await fs.readFile(opencodeTestConfig, 'utf8');
|
||||
const opencodeConfig = JSON.parse(opencodeContent);
|
||||
assert(
|
||||
opencodeConfig.mcp && opencodeConfig.mcp.memtrace && opencodeConfig.mcp.memtrace.type === 'local',
|
||||
'AC 3: OpenCode config correctly created and populated with memtrace local definition'
|
||||
);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Verification failed with error:', err);
|
||||
if (err.stdout) console.error('stdout:', err.stdout);
|
||||
if (err.stderr) console.error('stderr:', err.stderr);
|
||||
failed++;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
try {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
} catch (err) {}
|
||||
|
||||
console.log(`\n${colors.cyan}========================================`);
|
||||
console.log(`Verification Summary: Passed: ${passed}, Failed: ${failed}`);
|
||||
console.log(`========================================${colors.reset}\n`);
|
||||
|
||||
if (failed > 0) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
runVerification().catch(err => {
|
||||
console.error('Fatal verification error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Reference in New Issue