Files
portal/tools/ruflo-h7-patch.mjs
T
Дмитрий be755dd8eb fix(ruflo): harden ruflo-h7-patch.mjs — argv guard + unknown-flag rejection
Code-review fixes: guard pathToFileURL against undefined argv[1];
reject unrecognised flags with exit 2 before any filesystem access
(prevents a --revert typo from silently applying the patch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:36:56 +03:00

123 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
/**
* ruflo H7 patch re-apply tool.
*
* H7: `ruflo memory store` routes through the AgentDB-v3 bridge
* (memory-bridge.js:bridgeStoreEntry) which inserts into an in-RAM sql.js DB
* and never flushes to disk — entries are lost between CLI invocations.
*
* Fix: force getBridge() in @claude-flow/cli's memory-initializer.js to
* return null, so memory ops fall through to the raw sql.js path that DOES
* persist (db.export() + writeFileRestricted).
*
* The patch lives in the GLOBAL npm install and is overwritten by
* `ruflo update` — re-run this script after any ruflo upgrade.
*
* Usage:
* node tools/ruflo-h7-patch.mjs apply the patch (idempotent)
* node tools/ruflo-h7-patch.mjs --check exit 0 if patched, 1 if not
* node tools/ruflo-h7-patch.mjs --revert remove the patch
*
* See docs/superpowers/specs/2026-05-15-ruflo-memory-h7-fix-and-advisory-hook-design.md §4
*/
import { execSync } from 'node:child_process';
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
export const PATCH_MARKER = 'LIDERRA-H7-PATCH';
export const PATCH_LINE =
' return null; /* LIDERRA-H7-PATCH: AgentDB bridge writes in-RAM sql.js, never flushes — force raw persisting path */';
const ANCHOR = 'async function getBridge() {';
/** True if the file content already contains the patch. */
export function isPatched(content) {
return content.includes(PATCH_MARKER);
}
/**
* Insert the patch line as the first statement of getBridge().
* Idempotent. Throws if the anchor is absent (ruflo upstream changed).
*/
export function applyPatch(content) {
if (isPatched(content)) return { content, changed: false };
const idx = content.indexOf(ANCHOR);
if (idx === -1) {
throw new Error(
`anchor "${ANCHOR}" not found — ruflo upstream changed getBridge(); revise this patch manually`,
);
}
const insertAt = idx + ANCHOR.length;
const patched = content.slice(0, insertAt) + '\n' + PATCH_LINE + content.slice(insertAt);
return { content: patched, changed: true };
}
/** Remove the patch line (the line carrying PATCH_MARKER). */
export function revertPatch(content) {
if (!isPatched(content)) return { content, changed: false };
const lines = content.split('\n');
const kept = lines.filter((l) => !l.includes(PATCH_MARKER));
return { content: kept.join('\n'), changed: true };
}
/**
* Resolve the path to @claude-flow/cli's memory-initializer.js inside the
* global npm tree. Handles both nested (under ruflo/) and hoisted layouts.
*/
export function resolveTargetPath(npmRoot) {
const tail = ['@claude-flow', 'cli', 'dist', 'src', 'memory', 'memory-initializer.js'];
const candidates = [
join(npmRoot, 'ruflo', 'node_modules', ...tail),
join(npmRoot, ...tail),
];
return candidates.find((c) => existsSync(c)) ?? null;
}
function main() {
const mode = process.argv[2];
const VALID_MODES = [undefined, '--check', '--revert'];
if (!VALID_MODES.includes(mode)) {
console.error(`[ruflo-h7-patch] unknown flag: ${mode}`);
console.error('Usage: node tools/ruflo-h7-patch.mjs [--check | --revert]');
process.exit(2);
}
let npmRoot;
try {
npmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
} catch (e) {
console.error('[ruflo-h7-patch] failed to run `npm root -g`: ' + e.message);
process.exit(2);
}
const target = resolveTargetPath(npmRoot);
if (!target) {
console.error('[ruflo-h7-patch] memory-initializer.js not found under ' + npmRoot);
process.exit(2);
}
const content = readFileSync(target, 'utf8');
if (mode === '--check') {
const patched = isPatched(content);
console.log(`[ruflo-h7-patch] ${target}\n ${patched ? 'PATCHED' : 'UNPATCHED'}`);
process.exit(patched ? 0 : 1);
}
if (mode === '--revert') {
const { content: out, changed } = revertPatch(content);
if (changed) writeFileSync(target, out);
console.log(`[ruflo-h7-patch] revert: ${changed ? 'patch removed' : 'was not patched'}`);
process.exit(0);
}
try {
const { content: out, changed } = applyPatch(content);
if (changed) writeFileSync(target, out);
console.log(`[ruflo-h7-patch] apply: ${changed ? 'patched' : 'already patched'}`);
process.exit(0);
} catch (e) {
console.error('[ruflo-h7-patch] ' + e.message);
process.exit(3);
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main();
}