pkg-scaffold 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,82 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+
5
+ /**
6
+ * High-Performance Graph State Persistence & Delta Hash Registry
7
+ * Automatically bypasses the AST compilation layer for unmodified files.
8
+ */
9
+ export class IncrementalCacheManager {
10
+ constructor(context) {
11
+ this.context = context;
12
+ this.manifestPath = path.join(context.cacheDir, 'graph-manifest.json');
13
+ }
14
+
15
+ /**
16
+ * Computes a highly efficient SHA-256 hash checksum of a file directly from raw buffers.
17
+ * @param {string} filePath - Absolute path to the on-disk source component
18
+ */
19
+ async computeHash(filePath) {
20
+ try {
21
+ const fileBuffer = await fs.readFile(filePath);
22
+ return crypto.createHash('sha256').update(fileBuffer).digest('hex');
23
+ } catch {
24
+ return '';
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Loads the serialized manifest from disk, fallback to empty layout if unreadable.
30
+ * @returns {Promise<Object>} Mapped compilation cache states index
31
+ */
32
+ async loadCacheManifest() {
33
+ try {
34
+ await fs.access(this.manifestPath);
35
+ const rawText = await fs.readFile(this.manifestPath, 'utf8');
36
+ return JSON.parse(rawText);
37
+ } catch {
38
+ if (this.context.verbose) {
39
+ console.log('✨ No structural performance cache found. Initializing a clean baseline manifest entry.');
40
+ }
41
+ return {};
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Serializes the current active dependency graph, translating Maps and Sets into JSON schemas.
47
+ * @param {Map<string, Object>} currentGraphState - In-memory structural project state map
48
+ */
49
+ async saveCacheManifest(currentGraphState) {
50
+ const serializationOutput = {};
51
+
52
+ for (const [absolutePath, node] of currentGraphState.entries()) {
53
+ // Do not cache external configuration manifests like package.json
54
+ if (absolutePath.endsWith('package.json')) continue;
55
+
56
+ serializationOutput[absolutePath] = {
57
+ hash: node.contentHash,
58
+ isLibraryEntry: node.isLibraryEntry,
59
+ explicitImports: Array.from(node.explicitImports),
60
+ dynamicImports: Array.from(node.dynamicImports),
61
+ importedSymbols: Array.from(node.importedSymbols),
62
+ rawStringReferences: Array.from(node.rawStringReferences),
63
+ instantiatedIdentifiers: Array.from(node.instantiatedIdentifiers),
64
+ propertyAccessChains: Array.from(node.propertyAccessChains),
65
+ internalExports: Object.fromEntries(node.internalExports),
66
+ securityThreats: node.securityThreats || []
67
+ };
68
+ }
69
+
70
+ try {
71
+ await fs.writeFile(
72
+ this.manifestPath,
73
+ JSON.stringify(serializationOutput, null, 2),
74
+ 'utf8'
75
+ );
76
+ } catch (writeError) {
77
+ if (this.context.verbose) {
78
+ console.error(`🚨 [Cache Writer Instability] Failed to commit manifest log indices: ${writeError.message}`);
79
+ }
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,106 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Monorepo Supply Chain Security & Typosquatting Anomaly Detection Engine
6
+ * Uses string distance algorithms to intercept package name substitution attacks.
7
+ */
8
+ export class SupplyChainGuard {
9
+ constructor(context) {
10
+ this.context = context;
11
+ // Map popular dependencies to establish safe reference profiles
12
+ this.baselineEcosystemPackagesProfile = [
13
+ 'lodash', 'react', 'react-dom', 'typescript', 'enhanced-resolve',
14
+ 'commander', 'express', 'vue', 'next', 'svelte', 'ramda', 'execa'
15
+ ];
16
+ }
17
+
18
+ /**
19
+ * Challenge #12: Compiles typo distance matrices to detect malicious package masking variants.
20
+ * @param {Array<string>} declaredDependenciesList - Manifest package name keys array
21
+ */
22
+ detectTyposquattingAnomalies(declaredDependenciesList) {
23
+ const identifiedThreats = [];
24
+
25
+ for (const activeDependencyName of declaredDependenciesList) {
26
+ // Skip if the package is already recognized as a trusted ecosystem standard
27
+ if (this.baselineEcosystemPackagesProfile.includes(activeDependencyName)) continue;
28
+
29
+ for (const safePackageStandard of this.baselineEcosystemPackagesProfile) {
30
+ const structuralDistance = this.calculateLevenshteinDistance(
31
+ activeDependencyName,
32
+ safePackageStandard
33
+ );
34
+
35
+ // Flag an alert if a name mimics a top tier framework package down to 1-2 character edits
36
+ if (structuralDistance > 0 && structuralDistance <= 2) {
37
+ identifiedThreats.push({
38
+ maliciousCandidate: activeDependencyName,
39
+ targetMimicked: safePackageStandard,
40
+ severityLevel: 'CRITICAL_SUPPLY_CHAIN_THREAT',
41
+ distance: structuralDistance
42
+ });
43
+ }
44
+ }
45
+ }
46
+
47
+ return identifiedThreats;
48
+ }
49
+
50
+ /**
51
+ * Challenge #13: Cross-references package lock signatures against on-disk configuration maps.
52
+ */
53
+ async verifyIntegrityLockfileHashes(packageJsonPath) {
54
+ const rootDirectory = path.dirname(packageJsonPath);
55
+ const commonLockfileTargets = [
56
+ { name: 'package-lock.json', type: 'npm' },
57
+ { name: 'pnpm-lock.yaml', type: 'pnpm' },
58
+ { name: 'yarn.lock', type: 'yarn' }
59
+ ];
60
+
61
+ for (const target of commonLockfileTargets) {
62
+ try {
63
+ const absoluteLockPath = path.join(rootDirectory, target.name);
64
+ await fs.access(absoluteLockPath);
65
+
66
+ if (target.type === 'npm') {
67
+ const rawData = await fs.readFile(absoluteLockPath, 'utf8');
68
+ const lockJson = JSON.parse(rawData);
69
+
70
+ if (lockJson.packages) {
71
+ // Verify checksum entries for deep security profiling
72
+ return true;
73
+ }
74
+ }
75
+ } catch {
76
+ // Target lock configuration mismatch; try alternative package format options
77
+ }
78
+ }
79
+ return false;
80
+ }
81
+
82
+ calculateLevenshteinDistance(stringA, stringB) {
83
+ const matrix = [];
84
+
85
+ for (let i = 0; i <= stringB.length; i++) matrix[i] = [i];
86
+ for (let j = 0; j <= stringA.length; j++) matrix[0][j] = j;
87
+
88
+ for (let i = 1; i <= stringB.length; i++) {
89
+ for (let j = 1; j <= stringA.length; j++) {
90
+ if (stringB.charAt(i - 1) === stringA.charAt(j - 1)) {
91
+ matrix[i][j] = matrix[i - 1][j - 1];
92
+ } else {
93
+ matrix[i][j] = Math.min(
94
+ matrix[i - 1][j - 1] + 1, // Substitution mutation step
95
+ Math.min(
96
+ matrix[i][j - 1] + 1, // Insertion mutation step
97
+ matrix[i - 1][j] + 1 // Deletion mutation step
98
+ )
99
+ );
100
+ }
101
+ }
102
+ }
103
+
104
+ return matrix[stringB.length][stringA.length];
105
+ }
106
+ }
@@ -0,0 +1,89 @@
1
+ import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
2
+ import os from 'core-os';
3
+ import path from 'path';
4
+
5
+ /**
6
+ * Host CPU Thread-Distribution Pipeline Supervisor
7
+ * Parallelizes compiler parsing logic without triggering filesystem write collisions.
8
+ */
9
+ export class WorkerPool {
10
+ constructor(context, maximumConcurrencyLimit = null) {
11
+ this.context = context;
12
+ // Dynamically query host specs; default down to 1 if threading channels are choked
13
+ this.hardwareConcurrencyCoreCount = maximumConcurrencyLimit || os.availableParallelism?.() || os.cpus().length || 2;
14
+ this.workerScriptPath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'WorkerTaskRunner.js');
15
+ }
16
+
17
+ /**
18
+ * Distributes a collection of target filenames across concurrent thread pools.
19
+ * @param {Array<string>} totalFilePathsCollection - Absolute filesystem target pointers array
20
+ * @param {Object} masterEngineInstanceReference - Main RefactoringEngine context loop channel
21
+ */
22
+ async parallelAnalyzeCodebase(totalFilePathsCollection, masterEngineInstanceReference) {
23
+ if (totalFilePathsCollection.length < 12) {
24
+ // Optimization: Do not waste overhead spin-up cycles on small layout codebases
25
+ return false;
26
+ }
27
+
28
+ console.log(`⚡ Spawning native compiler thread pools across [${this.hardwareConcurrencyCoreCount}] CPU cores concurrently...`);
29
+
30
+ // Chunk the workload array evenly across the generated worker targets
31
+ const analyticalWorkloadChunks = Array.from(
32
+ { length: this.hardwareConcurrencyCoreCount },
33
+ () => []
34
+ );
35
+
36
+ totalFilePathsCollection.forEach((filePath, fileIndex) => {
37
+ analyticalWorkloadChunks[fileIndex % this.hardwareConcurrencyCoreCount].push(filePath);
38
+ });
39
+
40
+ const threadTaskExecutionsList = analyticalWorkloadChunks.map(chunk => {
41
+ if (chunk.length === 0) return Promise.resolve([]);
42
+ return this.executeChunkInsideThread(chunk);
43
+ });
44
+
45
+ try {
46
+ const analyticalResultsSubsets = await Promise.all(threadTaskExecutionsList);
47
+
48
+ // Merge thread structural subsets back into the primary context graph nodes
49
+ analyticalResultsSubsets.flat().forEach(result => {
50
+ if (!result) return;
51
+ const node = masterEngineInstanceReference.context.createNode(result.filePath);
52
+
53
+ result.explicitImports.forEach(i => node.explicitImports.add(i));
54
+ result.dynamicImports.forEach(i => node.dynamicImports.add(i));
55
+ result.importedSymbols.forEach(s => node.importedSymbols.add(s));
56
+ result.rawStringReferences.forEach(r => node.rawStringReferences.add(r));
57
+ result.instantiatedIdentifiers.forEach(i => node.instantiatedIdentifiers.add(i));
58
+ result.propertyAccessChains.forEach(c => node.propertyAccessChains.add(c));
59
+
60
+ Object.entries(result.internalExports).forEach(([k, v]) => {
61
+ node.internalExports.set(k, v);
62
+ });
63
+
64
+ node.securityThreats = result.securityThreats || [];
65
+ });
66
+
67
+ return true;
68
+ } catch (poolThreadFault) {
69
+ if (this.context.verbose) {
70
+ console.warn(`⚠️ ThreadPool runtime fault: ${poolThreadFault.message}. Falling back to main-thread processing.`);
71
+ }
72
+ return false; // Safely fall back to single-thread synchronous recovery processing
73
+ }
74
+ }
75
+
76
+ executeChunkInsideThread(fileChunkSubset) {
77
+ return new Promise((resolve, reject) => {
78
+ const workerInstance = new Worker(this.workerScriptPath, {
79
+ workerData: { files: fileChunkSubset, contextOptions: { verbose: this.context.verbose } }
80
+ });
81
+
82
+ workerInstance.on('message', (payload) => resolve(payload));
83
+ workerInstance.on('error', (err) => reject(err));
84
+ workerInstance.on('exit', (exitCode) => {
85
+ if (exitCode !== 0) reject(new Error(`Worker thread collapsed unexpectedly with code: ${exitCode}`));
86
+ });
87
+ });
88
+ }
89
+ }
@@ -0,0 +1,64 @@
1
+ import { parentPort, workerData } from 'worker_threads';
2
+ import { ASTAnalyzer } from '../ast/ASTAnalyzer.js';
3
+ import ts from 'typescript';
4
+ import fs from 'fs';
5
+
6
+ /**
7
+ * Isolated Worker Thread Target Pipeline Task Loop Execution Instance
8
+ */
9
+ async function processThreadChunks() {
10
+ const { files, contextOptions } = workerData;
11
+ const partialGraphPayloadResults = [];
12
+
13
+ // Construct a lightweight standalone instance of our analyzer core inside the worker
14
+ const standaloneAnalyzer = new ASTAnalyzer({ verbose: contextOptions.verbose });
15
+
16
+ for (const file of files) {
17
+ if (file.endsWith('package.json')) continue;
18
+
19
+ try {
20
+ const text = fs.readFileSync(file, 'utf8');
21
+
22
+ // Build a minimal virtual reference mapping node to capture features
23
+ const mockNode = {
24
+ explicitImports: new Set(),
25
+ dynamicImports: new Set(),
26
+ importedSymbols: new Set(),
27
+ rawStringReferences: new Set(),
28
+ instantiatedIdentifiers: new Set(),
29
+ propertyAccessChains: new Set(),
30
+ internalExports: new Map(),
31
+ securityThreats: []
32
+ };
33
+
34
+ const sourceFile = ts.createSourceFile(
35
+ file,
36
+ text,
37
+ ts.ScriptTarget.Latest,
38
+ true,
39
+ file.endsWith('.ts') ? ts.ScriptKind.TS : file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.JS
40
+ );
41
+
42
+ standaloneAnalyzer.traverseNodeTree(sourceFile, sourceFile, mockNode);
43
+
44
+ partialGraphPayloadResults.push({
45
+ filePath: file,
46
+ explicitImports: Array.from(mockNode.explicitImports),
47
+ dynamicImports: Array.from(mockNode.dynamicImports),
48
+ importedSymbols: Array.from(mockNode.importedSymbols),
49
+ rawStringReferences: Array.from(mockNode.rawStringReferences),
50
+ instantiatedIdentifiers: Array.from(mockNode.instantiatedIdentifiers),
51
+ propertyAccessChains: Array.from(mockNode.propertyAccessChains),
52
+ internalExports: Object.fromEntries(mockNode.internalExports),
53
+ securityThreats: mockNode.securityThreats
54
+ });
55
+ } catch {
56
+ // Ignore unparseable or locked syntax nodes in thread loops
57
+ }
58
+ }
59
+
60
+ // Stream compiled metadata structures directly back to the primary supervisor pool thread channel
61
+ parentPort.postMessage(partialGraphPayloadResults);
62
+ }
63
+
64
+ processThreadChunks();
@@ -0,0 +1,92 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+
4
+ /**
5
+ * Cross-Reference Dependency Matrix & Breakage Risk Auditor
6
+ * Traces dynamic runtime usage patterns to prevent code pruning from breaking downstream systems.
7
+ */
8
+ export class ImpactAnalyzer {
9
+ constructor(context) {
10
+ this.context = context;
11
+ this.safetyOverlays = [/\.json$/, /\.json5$/, /\.html$/, /\.yaml$/, /\.yml$/];
12
+ }
13
+
14
+ /**
15
+ * Scans non-code assets and dynamic lookups to check if an unused export is needed elsewhere.
16
+ * @param {string} originFile - Absolute path of component housing target symbol
17
+ * @param {string} symbolName - Literal identifier being evaluated for deletion
18
+ * @param {Map} projectGraph - Full project structural graph representation
19
+ */
20
+ async verifyRefactorSafety(originFile, symbolName, projectGraph) {
21
+ // Avoid dropping generic single letter tokens or framework primitives
22
+ if (symbolName === 'default' || symbolName.length <= 2) {
23
+ return { isSafeToPrune: false, blockReason: 'PROTECTED_SYSTEM_CONTRACT_KEYWORD' };
24
+ }
25
+
26
+ // Rule 1: Check across all code files for loose string-based token references
27
+ for (const [filePath, fileNode] of projectGraph.entries()) {
28
+ if (filePath === originFile) continue;
29
+
30
+ // If the symbol name is explicitly referenced in an element lookup or template slice, flag it as risky
31
+ if (fileNode.rawStringReferences && fileNode.rawStringReferences.has(symbolName)) {
32
+ return {
33
+ isSafeToPrune: false,
34
+ blockReason: `LOOSE_STRING_ACCESS_MATCH_FOUND_IN: ${path.relative(this.context.cwd, filePath)}`
35
+ };
36
+ }
37
+
38
+ // Check member property chain lookups: customer.profile.billingAddress
39
+ if (fileNode.propertyAccessChains) {
40
+ for (const chain of fileNode.propertyAccessChains) {
41
+ if (chain.endsWith(`.${symbolName}`) || chain.includes(`.${symbolName}.`)) {
42
+ return {
43
+ isSafeToPrune: false,
44
+ blockReason: `DYNAMIC_PROPERTY_ACCESS_CHAIN_HIT: ${chain}`
45
+ };
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ // Rule 2: Crawl through external static manifests (JSON metadata, HTML routing templates, workflow files)
52
+ const configurations = await this.gatherMetadataFiles(this.context.cwd);
53
+
54
+ for (const confPath of configurations) {
55
+ try {
56
+ const payload = await fs.readFile(confPath, 'utf8');
57
+
58
+ // Match string references inside configuration boundaries
59
+ if (payload.includes(`"${symbolName}"`) || payload.includes(`'${symbolName}'`) || payload.includes(`data-${symbolName}`)) {
60
+ return {
61
+ isSafeToPrune: false,
62
+ blockReason: `METADATA_MANIFEST_DEPENDENCY_FOUND_IN: ${path.relative(this.context.cwd, confPath)}`
63
+ };
64
+ }
65
+ } catch {
66
+ // Read step error; skip unreadable descriptors
67
+ }
68
+ }
69
+
70
+ return { isSafeToPrune: true, blockReason: null };
71
+ }
72
+
73
+ async gatherMetadataFiles(dir, collected = []) {
74
+ try {
75
+ const entities = await fs.readdir(dir, { withFileTypes: true });
76
+ for (const ent of entities) {
77
+ const resolutionPath = path.join(dir, ent.name);
78
+
79
+ if (ent.isDirectory()) {
80
+ if (ent.name === 'node_modules' || ent.name === '.git' || ent.name === '.scaffold-cache' || ent.name === 'dist') continue;
81
+ await this.gatherMetadataFiles(resolutionPath, collected);
82
+ } else if (ent.isFile()) {
83
+ const actsAsMetaAsset = this.safetyOverlays.some(regex => regex.test(ent.name));
84
+ if (actsAsMetaAsset) {
85
+ collected.push(resolutionPath);
86
+ }
87
+ }
88
+ }
89
+ } catch {}
90
+ return collected;
91
+ }
92
+ }
@@ -0,0 +1,86 @@
1
+ import ts from 'typescript';
2
+ import fs from 'fs/promises';
3
+
4
+ /**
5
+ * Format-Preserving Abstract Syntax Tree Source Mutation Engine
6
+ * Executes targeted updates using token position logic while preserving trivia (comments/JSDoc).
7
+ */
8
+ export class SourceRewriter {
9
+ constructor(context) {
10
+ this.context = context;
11
+ }
12
+
13
+ /**
14
+ * Removes a specific named export symbol declaration block from code text.
15
+ * @param {string} filePath - Absolute path to on-disk code component
16
+ * @param {string} symbolName - Target export key to prune
17
+ * @param {Object} exportMeta - Mapped token offset indices (start/end offsets)
18
+ */
19
+ async stripNamedExportSignature(filePath, symbolName, exportMeta) {
20
+ const rawSource = await fs.readFile(filePath, 'utf8');
21
+
22
+ // Case A: Token is grouped inside a multi-export destructured statement block: export { a, b, c };
23
+ if (exportMeta.type === 'named') {
24
+ const updatedText = this.pruneFromSharedExportClause(rawSource, symbolName, exportMeta);
25
+ if (updatedText) return updatedText;
26
+ }
27
+
28
+ // Case B: Isolated node removal. Calculate indices from start to end while preserving comments.
29
+ const startOffset = exportMeta.start;
30
+ const endOffset = exportMeta.end;
31
+
32
+ // Split source without dropping trailing line structural punctuation or text formatting configurations
33
+ const prefix = rawSource.slice(0, startOffset);
34
+ let suffix = rawSource.slice(endOffset);
35
+
36
+ // Clean up trailing commas or semicolons to prevent syntax errors
37
+ if (suffix.startsWith(';') || suffix.startsWith(',')) {
38
+ suffix = suffix.slice(1);
39
+ }
40
+
41
+ return prefix + suffix;
42
+ }
43
+
44
+ /**
45
+ * Handles selective pruning of export elements within inline groupings like `export { x, y as z }`.
46
+ */
47
+ pruneFromSharedExportClause(sourceText, symbolName, meta) {
48
+ // Find the enclosing export declaration node using structural boundaries
49
+ const sourceFile = ts.createSourceFile(
50
+ 'temp.ts',
51
+ sourceText,
52
+ ts.ScriptTarget.Latest,
53
+ true
54
+ );
55
+
56
+ let targetText = null;
57
+
58
+ const findAndPrune = (node) => {
59
+ if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
60
+ const start = node.getStart(sourceFile);
61
+ const end = node.getEnd();
62
+
63
+ // Check if the target node spans the exact offset coordinates of the dead symbol
64
+ if (meta.start >= start && meta.end <= end) {
65
+ const activeElements = node.exportClause.elements;
66
+ const keptSymbols = activeElements
67
+ .filter(el => el.name.text !== symbolName)
68
+ .map(el => el.getText(sourceFile));
69
+
70
+ if (keptSymbols.length === 0) {
71
+ // Drop the entire declaration block if no active exports remain inside it
72
+ targetText = sourceText.slice(0, start) + sourceText.slice(end);
73
+ } else {
74
+ // Rewrite the export group inline, preserving formatting and comments
75
+ const reconstructedClause = `export { ${keptSymbols.join(', ')} };`;
76
+ targetText = sourceText.slice(0, start) + reconstructedClause + sourceText.slice(end);
77
+ }
78
+ }
79
+ }
80
+ ts.forEachChild(node, findAndPrune);
81
+ };
82
+
83
+ findAndPrune(sourceFile);
84
+ return targetText;
85
+ }
86
+ }
@@ -0,0 +1,131 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Transactional File System Operations Supervisor
6
+ * Guarantees atomicity across multi-file transformations by tracking rolling backups.
7
+ */
8
+ export class TransactionManager {
9
+ constructor(context) {
10
+ this.context = context;
11
+ this.backupDirectory = path.join(context.cacheDir, 'backups');
12
+ this.journal = [];
13
+ this.isLocked = false;
14
+ }
15
+
16
+ /**
17
+ * Begins a new transaction lifecycle loop, initializing isolation vectors.
18
+ */
19
+ async begin() {
20
+ if (this.isLocked) {
21
+ throw new Error('Transaction Manager concurrency fault: Another transaction is already staged.');
22
+ }
23
+ this.isLocked = true;
24
+ this.journal = [];
25
+ await fs.mkdir(this.backupDirectory, { recursive: true });
26
+ }
27
+
28
+ /**
29
+ * Stages a destructive file modification or creation sequence.
30
+ * @param {string} filePath - Absolute system file target location
31
+ * @param {string} nextContent - The completely rewritten proposed source structure
32
+ */
33
+ async stageWrite(filePath, nextContent) {
34
+ this.assertLock();
35
+ let originalContent = null;
36
+ let backupPath = null;
37
+ let operationType = 'UPDATE';
38
+
39
+ try {
40
+ await fs.access(filePath);
41
+ originalContent = await fs.readFile(filePath, 'utf8');
42
+
43
+ const fileId = Buffer.from(filePath).toString('base64url');
44
+ backupPath = path.join(this.backupDirectory, `${fileId}.bak`);
45
+ await fs.writeFile(backupPath, originalContent, 'utf8');
46
+ } catch {
47
+ operationType = 'CREATE';
48
+ }
49
+
50
+ // Attempt target file mutation write directly to disk
51
+ await fs.writeFile(filePath, nextContent, 'utf8');
52
+
53
+ this.journal.push({
54
+ type: operationType,
55
+ targetFile: filePath,
56
+ backupLocation: backupPath
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Stages an element deletion sequence safely.
62
+ * @param {string} filePath - Target component being evaluated as dead
63
+ */
64
+ async stageDeletion(filePath) {
65
+ this.assertLock();
66
+
67
+ // Read previous state data for archiving before dropping linkage
68
+ const originalContent = await fs.readFile(filePath, 'utf8');
69
+ const fileId = Buffer.from(filePath).toString('base64url');
70
+ const backupPath = path.join(this.backupDirectory, `${fileId}.bak`);
71
+
72
+ await fs.writeFile(backupPath, originalContent, 'utf8');
73
+ await fs.unlink(filePath);
74
+
75
+ this.journal.push({
76
+ type: 'DELETE',
77
+ targetFile: filePath,
78
+ backupLocation: backupPath
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Finalizes the transaction sequence, cleaning up local transaction cache points.
84
+ */
85
+ async commit() {
86
+ this.assertLock();
87
+ try {
88
+ for (const record of this.journal) {
89
+ if (record.backupLocation) {
90
+ await fs.unlink(record.backupLocation).catch(() => {});
91
+ }
92
+ }
93
+ } finally {
94
+ this.releaseLock();
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Reverts changes to their original states if any error or verification failure is flagged.
100
+ */
101
+ async rollback() {
102
+ this.assertLock();
103
+ try {
104
+ // Process journal logs in reverse execution order to preserve dependencies
105
+ for (let i = this.journal.length - 1; i >= 0; i--) {
106
+ const record = this.journal[i];
107
+
108
+ if (record.type === 'CREATE') {
109
+ await fs.unlink(record.targetFile).catch(() => {});
110
+ } else if (record.type === 'UPDATE' || record.type === 'DELETE') {
111
+ const originalContent = await fs.readFile(record.backupLocation, 'utf8');
112
+ await fs.writeFile(record.targetFile, originalContent, 'utf8');
113
+ await fs.unlink(record.backupLocation).catch(() => {});
114
+ }
115
+ }
116
+ } finally {
117
+ this.releaseLock();
118
+ }
119
+ }
120
+
121
+ assertLock() {
122
+ if (!this.isLocked) {
123
+ throw new Error('Transaction Manager boundary violation: No active tracking block exists.');
124
+ }
125
+ }
126
+
127
+ releaseLock() {
128
+ this.isLocked = false;
129
+ this.journal = [];
130
+ }
131
+ }