Files
GDevelop-Unlocked/scripts/extract-tier2.mjs
T
BillyOutlast 30da3f8b4e Surgical conversion: Tier-1 patcher + launcher hook, Tier-2 extraction (Phases 1-5)
Replaces the 205-file fork delivery vehicle with patches/ + launcher/
+ versioned packages, per the implementation plan:

Phase 1: seam tripwires (27 tests) freezing every surgical target in
upstream (Usage predicate, %16 gate, SubscriptionChecker, debugger,
LoadingScreen branding, Electron hook surface, no-plugin-registry
verdict).

Phase 2: 5 generated Tier-1 hunks (round-trip verified; 3 files reach
byte-identical fork parity) + apply driver (dry-run gate, marker
verify, asar extract/repack).

Phase 3: launcher hook (preload fetch/XHR spoof of the two usage GETs,
BrowserWindow wrap, webRequest fallback, updater pin) + 06 loader
patch. Module._load interception dropped: renderers run a webpack
bundle, so the spoof rides the axios/XHR path the provider hydrates
from instead.

Phase 4: Tier-2 verbatim extraction (CustomAIClient + 12 Studio
modules + offline mock harness, manifest-pinned) + 07/08 verbatim
glue patches + pure generation shim with fail-closed leak guard.

Phase 5: web userscript demo (Tier-1 only, documented CORS/LAN/churn
limits). Phase 6 runbook + upstream provider-interface RFC draft
included; fork archiving left to owners (destructive).

Suite: 66 tests green (seams, patcher incl. re-apply refusal,
launcher incl. vm-sandbox fallback eval, tier-2 provenance, web).
2026-10-10 20:41:35 -04:00

89 lines
3.4 KiB
JavaScript

// Extracts Tier-2 donor files VERBATIM from the fork into
// packages/ai-studio/ (Phase 4, issue #4). The fork is a donor, not a
// delivery vehicle: these files are never hand-edited here.
//
// node scripts/extract-tier2.mjs --fork review/GDevelop-Heretek
//
// Writes MANIFEST.json (sha256 per file); tests/tier2/extract.test.mjs
// re-verifies byte-identity. To refresh after a fork sync, re-run with
// the new fork checkout — any content change shows up as a manifest
// diff in code review.
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import { fileURLToPath } from 'node:url';
const here = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(here, '..');
const DEST = path.join(ROOT, 'packages', 'ai-studio');
// Donor paths, relative to the fork root. Kept explicit (not a glob)
// so additions/removals in the fork are noticed, not silently absorbed.
const DONOR_FILES = [
'newIDE/app/src/AI/CustomAIClient.js',
'newIDE/app/src/AI/CustomAIClient.spec.js',
'newIDE/app/src/AiGeneration/Studio/EditApprovalLabel.js',
'newIDE/app/src/AiGeneration/Studio/FinalizeSubAgents.js',
'newIDE/app/src/AiGeneration/Studio/LoopGuard.js',
'newIDE/app/src/AiGeneration/Studio/NudgePolicy.js',
'newIDE/app/src/AiGeneration/Studio/PlanStore.js',
'newIDE/app/src/AiGeneration/Studio/RequestWriteGate.js',
'newIDE/app/src/AiGeneration/Studio/Roles.js',
'newIDE/app/src/AiGeneration/Studio/RoleToolPolicy.js',
'newIDE/app/src/AiGeneration/Studio/SafeTruncation.js',
'newIDE/app/src/AiGeneration/Studio/SpawnSubAgents.js',
'newIDE/app/src/AiGeneration/Studio/UseStudioNudge.js',
'newIDE/app/src/AiGeneration/Studio/UseStudioRuntime.js',
'scripts/dev/mock-ai-provider.js',
'scripts/dev/mock-script.example.json',
'scripts/dev/mock-script-multi.json',
'scripts/dev/mock-script-specialists.json',
'scripts/dev/mock-child-scripts.example.json',
];
// Fork-relative -> package-relative layout.
function destFor(rel) {
if (rel.startsWith('newIDE/app/src/'))
return rel.slice('newIDE/app/src/'.length);
if (rel.startsWith('scripts/dev/'))
return 'dev-harness/' + rel.slice('scripts/dev/'.length);
throw new Error(`No destination mapping for donor file: ${rel}`);
}
function parseArgs(argv) {
const out = { fork: path.join(ROOT, 'review', 'GDevelop-Heretek') };
for (let i = 0; i < argv.length; i++) {
if (argv[i] === '--fork') out.fork = path.resolve(argv[++i]);
}
return out;
}
const args = parseArgs(process.argv.slice(2));
const manifest = { donor: args.fork, files: {} };
for (const rel of DONOR_FILES) {
const src = path.join(args.fork, rel);
if (!fs.existsSync(src)) {
throw new Error(
`Donor file missing: ${rel} (fork checkout at ${args.fork}). ` +
`If the fork moved it, update DONOR_FILES explicitly.`
);
}
const content = fs.readFileSync(src);
const dest = path.join(DEST, destFor(rel));
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.writeFileSync(dest, content);
manifest.files[destFor(rel)] = {
donor: rel,
bytes: content.length,
sha256: crypto.createHash('sha256').update(content).digest('hex'),
};
console.log(`extracted ${destFor(rel)} (${content.length} bytes)`);
}
fs.writeFileSync(
path.join(DEST, 'MANIFEST.json'),
JSON.stringify(manifest, null, 2) + '\n'
);
console.log(`wrote MANIFEST.json (${DONOR_FILES.length} files)`);