7bf16500a1
Expose /health and /api/health/observations behind MEMIND_HEALTH_ENABLED, redirect ordinary chat health intents without persisting, and run a dedicated WeChat menu flow with confirm-before-write semantics. Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
2.6 KiB
JavaScript
86 lines
2.6 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const wechatRoot = path.join(root, 'wechat');
|
|
const forbiddenWechatImports = [
|
|
'mindspace-public-finish-sync.mjs',
|
|
'chat-finish-sync.mjs',
|
|
'useTKMindChat',
|
|
'src/hooks/useTKMindChat',
|
|
];
|
|
const forbiddenH5ImportPatterns = [
|
|
/from\s+['"][^'"]*\/wechat\//,
|
|
/import\s*\(\s*['"][^'"]*\/wechat\//,
|
|
/require\s*\(\s*['"][^'"]*\/wechat\//,
|
|
];
|
|
|
|
function walk(dir, acc = []) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) walk(full, acc);
|
|
else if (entry.name.endsWith('.mjs') || entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) acc.push(full);
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
function checkWechatImports() {
|
|
const violations = [];
|
|
for (const file of walk(wechatRoot)) {
|
|
const text = fs.readFileSync(file, 'utf8');
|
|
for (const token of forbiddenWechatImports) {
|
|
if (text.includes(token)) violations.push({ file, token });
|
|
}
|
|
}
|
|
return violations;
|
|
}
|
|
|
|
function isAllowedH5WechatImport(file, text) {
|
|
const rel = path.relative(root, file);
|
|
if (rel !== 'mindspace-public-finish-sync.mjs') return false;
|
|
const allowed = text.match(/from\s+['"](\.\/wechat\/[^'"]+)['"]/g) ?? [];
|
|
return (
|
|
allowed.length > 0 &&
|
|
allowed.every((entry) => /from\s+['"]\.\/wechat\/verify\//.test(entry))
|
|
);
|
|
}
|
|
|
|
function checkH5ImportsWechat() {
|
|
const violations = [];
|
|
const h5Paths = [
|
|
path.join(root, 'src'),
|
|
path.join(root, 'chat-finish-sync.mjs'),
|
|
path.join(root, 'mindspace-public-finish-sync.mjs'),
|
|
].filter((p) => fs.existsSync(p));
|
|
|
|
for (const base of h5Paths) {
|
|
const files = fs.statSync(base).isDirectory() ? walk(base) : [base];
|
|
for (const file of files) {
|
|
const text = fs.readFileSync(file, 'utf8');
|
|
if (isAllowedH5WechatImport(file, text)) continue;
|
|
for (const pattern of forbiddenH5ImportPatterns) {
|
|
if (pattern.test(text)) violations.push({ file, token: pattern.source });
|
|
}
|
|
}
|
|
}
|
|
return violations;
|
|
}
|
|
|
|
const wechatViolations = checkWechatImports();
|
|
const h5Violations = checkH5ImportsWechat();
|
|
|
|
if (wechatViolations.length === 0 && h5Violations.length === 0) {
|
|
console.log('wechat channel isolation check passed');
|
|
process.exit(0);
|
|
}
|
|
|
|
for (const item of wechatViolations) {
|
|
console.error(`wechat forbidden import: ${item.token} in ${path.relative(root, item.file)}`);
|
|
}
|
|
for (const item of h5Violations) {
|
|
console.error(`h5 forbidden import: ${item.token} in ${path.relative(root, item.file)}`);
|
|
}
|
|
process.exit(1);
|