pkg-scaffold 2.4.0 â 3.1.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.
- package/.github/workflows/deploy.yml +55 -0
- package/README.md +104 -107
- package/bin/cli.js +87 -4
- package/docs/.vitepress/config.mts +40 -0
- package/docs/.vitepress/theme/index.ts +17 -0
- package/docs/.vitepress/theme/style.css +139 -0
- package/docs/guide.md +102 -0
- package/docs/index.md +64 -0
- package/docs/reference.md +52 -0
- package/index.js +1 -1
- package/package.json +21 -4
- package/pkg-scaffold/config.json +25 -0
- package/pkg-scaffold/plugins/README.md +19 -0
- package/src/EngineContext.js +46 -4
- package/src/ast/ASTAnalyzer.js +193 -240
- package/src/ast/BarrelParser.js +12 -0
- package/src/ast/MagicDetector.js +41 -87
- package/src/ast/OxcAnalyzer.js +114 -0
- package/src/healing/SelfHealer.js +1 -1
- package/src/index.js +112 -45
- package/src/performance/GraphCache.js +4 -1
- package/src/performance/SupplyChainGuard.js +41 -55
- package/src/performance/WorkerPool.js +12 -1
- package/src/performance/WorkerTaskRunner.js +11 -4
- package/src/plugins/BasePlugin.js +53 -0
- package/src/plugins/PluginRegistry.js +95 -0
- package/src/plugins/ecosystems/GenericPlugins.js +64 -0
- package/src/plugins/ecosystems/NextJsPlugin.js +33 -0
- package/src/resolution/ConfigLoader.js +59 -0
- package/src/resolution/DepencyResolver.js +11 -0
- package/src/resolution/DependencyProfiler.js +90 -0
- package/src/resolution/WorkSpaceGraph.js +1 -1
package/src/ast/MagicDetector.js
CHANGED
|
@@ -1,66 +1,23 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
|
+
import { PluginRegistry } from '../plugins/PluginRegistry.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Ecosystem Entry Point Manifest & Dynamic Framework Router Heuristic Validator
|
|
6
7
|
* Intercepts implicit conventions to handle cases where direct import statements are absent.
|
|
8
|
+
* Now refactored to use a pluggable architecture.
|
|
7
9
|
*/
|
|
8
10
|
export class MagicDetector {
|
|
9
11
|
constructor(context) {
|
|
10
12
|
this.context = context;
|
|
11
|
-
this.
|
|
13
|
+
this.registry = new PluginRegistry(context);
|
|
14
|
+
this.isInitialized = false;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
nextjs: {
|
|
20
|
-
configFiles: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
|
|
21
|
-
routePatterns: [
|
|
22
|
-
/\/pages\/api\//,
|
|
23
|
-
/\/pages\/[a-zA-Z0-9_\-\[\]]+/i,
|
|
24
|
-
/\/app\/([\w\-\[\]]+\/)+(page|route|layout|loading|error|not-found)\.(ts|tsx|js|jsx)$/
|
|
25
|
-
],
|
|
26
|
-
requiredSystemContracts: ['default', 'getServerSideProps', 'getStaticProps', 'getStaticPaths', 'generateMetadata', 'middleware']
|
|
27
|
-
},
|
|
28
|
-
nuxt: {
|
|
29
|
-
configFiles: ['nuxt.config.js', 'nuxt.config.ts'],
|
|
30
|
-
routePatterns: [
|
|
31
|
-
/\/pages\//,
|
|
32
|
-
/\/server\/(api|routes|middleware)\//,
|
|
33
|
-
/\/components\/[a-zA-Z0-9_\-\/]+\.vue$/
|
|
34
|
-
],
|
|
35
|
-
requiredSystemContracts: ['default']
|
|
36
|
-
},
|
|
37
|
-
remix: {
|
|
38
|
-
configFiles: ['remix.config.js', 'vite.config.js', 'vite.config.ts'],
|
|
39
|
-
routePatterns: [
|
|
40
|
-
/\/app\/routes\//,
|
|
41
|
-
/\/app\/root\.(tsx|jsx)$/
|
|
42
|
-
],
|
|
43
|
-
requiredSystemContracts: ['default', 'loader', 'action', 'meta', 'links']
|
|
44
|
-
},
|
|
45
|
-
sveltekit: {
|
|
46
|
-
configFiles: ['svelte.config.js', 'vite.config.ts'],
|
|
47
|
-
routePatterns: [
|
|
48
|
-
/\+page\.(svelte|ts|js)$/,
|
|
49
|
-
/\+page\.server\.(ts|js)$/,
|
|
50
|
-
/\+layout\.(svelte|ts|js)$/,
|
|
51
|
-
/\+server\.(ts|js)$/
|
|
52
|
-
],
|
|
53
|
-
requiredSystemContracts: ['load', 'actions', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH']
|
|
54
|
-
},
|
|
55
|
-
astro: {
|
|
56
|
-
configFiles: ['astro.config.mjs', 'astro.config.cjs', 'astro.config.ts'],
|
|
57
|
-
routePatterns: [
|
|
58
|
-
/\/src\/pages\/.*\.astro$/,
|
|
59
|
-
/\/src\/pages\/.*\.(ts|js)$/
|
|
60
|
-
],
|
|
61
|
-
requiredSystemContracts: ['default', 'getStaticPaths']
|
|
62
|
-
}
|
|
63
|
-
};
|
|
17
|
+
async ensureInitialized(baseDir) {
|
|
18
|
+
if (this.isInitialized) return;
|
|
19
|
+
await this.registry.init(baseDir || process.cwd());
|
|
20
|
+
this.isInitialized = true;
|
|
64
21
|
}
|
|
65
22
|
|
|
66
23
|
/**
|
|
@@ -68,19 +25,9 @@ export class MagicDetector {
|
|
|
68
25
|
* @param {string} baseContextDirectory - Root file workspace context execution vector path
|
|
69
26
|
*/
|
|
70
27
|
async identifyActiveProjectEcosystems(baseContextDirectory) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
for (const configFile of criteria.configFiles) {
|
|
75
|
-
try {
|
|
76
|
-
await fs.access(path.join(baseContextDirectory, configFile));
|
|
77
|
-
activeFrameworkFlags.push(frameworkKey);
|
|
78
|
-
break; // Stop scanning once config criteria is found
|
|
79
|
-
} catch {
|
|
80
|
-
// File path criteria absent; proceed to standard verification loops
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
28
|
+
await this.ensureInitialized(baseContextDirectory);
|
|
29
|
+
const activePlugins = await this.registry.getActivePlugins(baseContextDirectory);
|
|
30
|
+
const activeFrameworkFlags = activePlugins.map(p => p.name);
|
|
84
31
|
|
|
85
32
|
// Universal infrastructure overrides (testing platforms and common bundlers)
|
|
86
33
|
activeFrameworkFlags.push('universal-tooling-vectors');
|
|
@@ -90,15 +37,18 @@ export class MagicDetector {
|
|
|
90
37
|
/**
|
|
91
38
|
* Assesses if a file path acts as an implicit route entry point.
|
|
92
39
|
*/
|
|
93
|
-
isImplicitlyRequiredByEcosystem(absolutePath, activeFrameworks) {
|
|
40
|
+
async isImplicitlyRequiredByEcosystem(absolutePath, activeFrameworks, baseDir) {
|
|
41
|
+
await this.ensureInitialized();
|
|
94
42
|
const normalizedSystemPath = absolutePath.replace(/\\/g, '/');
|
|
95
43
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
44
|
+
const plugins = this.registry.getPlugins();
|
|
45
|
+
for (const plugin of plugins) {
|
|
46
|
+
if (activeFrameworks.includes(plugin.name)) {
|
|
47
|
+
const patterns = plugin.getRoutePatterns();
|
|
48
|
+
if (patterns.some(regex => regex.test(normalizedSystemPath))) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
102
52
|
}
|
|
103
53
|
|
|
104
54
|
// Apply baseline platform rules (Test suites, lint parameters, continuous integration files)
|
|
@@ -117,7 +67,8 @@ export class MagicDetector {
|
|
|
117
67
|
const testEnvironments = [
|
|
118
68
|
'jest.config.', 'vitest.config.', 'playwright.config.', 'cypress.config.',
|
|
119
69
|
'webpack.config.', 'vite.config.', 'rollup.config.', 'tailwind.config.',
|
|
120
|
-
'.eslintrc.', 'prettier.config.', '.postcssrc.', 'postcss.config.'
|
|
70
|
+
'.eslintrc.', 'prettier.config.', '.postcssrc.', 'postcss.config.',
|
|
71
|
+
'bin/cli.js', 'index.js', 'WorkerTaskRunner.js'
|
|
121
72
|
];
|
|
122
73
|
|
|
123
74
|
return testEnvironments.some(matchPattern => normalizedPath.includes(matchPattern));
|
|
@@ -126,28 +77,31 @@ export class MagicDetector {
|
|
|
126
77
|
/**
|
|
127
78
|
* Challenge #4 Framework Overrides. Protects interface boundaries from false positive report flags.
|
|
128
79
|
*/
|
|
129
|
-
injectVirtualConsumerEdges(filePath, fileNode, activeFrameworks) {
|
|
130
|
-
|
|
80
|
+
async injectVirtualConsumerEdges(filePath, fileNode, activeFrameworks) {
|
|
81
|
+
await this.ensureInitialized();
|
|
82
|
+
if (!await this.isImplicitlyRequiredByEcosystem(filePath, activeFrameworks)) return;
|
|
131
83
|
|
|
132
84
|
// Retain entry point elements within memory to keep verification safe
|
|
133
85
|
fileNode.isLibraryEntry = true;
|
|
134
86
|
|
|
135
87
|
// Apply dynamic exports coverage metrics based on active platform contracts
|
|
136
88
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
89
|
+
const plugins = this.registry.getPlugins();
|
|
137
90
|
|
|
138
|
-
for (const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
91
|
+
for (const plugin of plugins) {
|
|
92
|
+
if (activeFrameworks.includes(plugin.name)) {
|
|
93
|
+
const patterns = plugin.getRoutePatterns();
|
|
94
|
+
const appliesToFramework = patterns.some(regex => regex.test(normalizedPath));
|
|
95
|
+
|
|
96
|
+
if (appliesToFramework) {
|
|
97
|
+
const contracts = plugin.getRequiredSystemContracts();
|
|
98
|
+
contracts.forEach(contractMethodToken => {
|
|
99
|
+
if (fileNode.internalExports.has(contractMethodToken)) {
|
|
100
|
+
// Emulate active local reference linkages to protect the export
|
|
101
|
+
fileNode.instantiatedIdentifiers.add(contractMethodToken);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
151
105
|
}
|
|
152
106
|
}
|
|
153
107
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { parseSync } from 'oxc-parser';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* High-Performance AST Analyzer using OXC (Rust-based)
|
|
6
|
+
* Designed to outpace Knip v6 by utilizing the fastest parser in the JS ecosystem.
|
|
7
|
+
*/
|
|
8
|
+
export class OxcAnalyzer {
|
|
9
|
+
constructor(context) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async processFile(filePath, fileNode) {
|
|
14
|
+
try {
|
|
15
|
+
const sourceText = await fs.readFile(filePath, 'utf8');
|
|
16
|
+
|
|
17
|
+
// OXC is significantly faster than TypeScript for single-pass analysis
|
|
18
|
+
const { program } = parseSync(sourceText, {
|
|
19
|
+
sourceFilename: filePath,
|
|
20
|
+
sourceType: filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'script'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this.walkOxcNode(program, fileNode);
|
|
24
|
+
return true;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (this.context.verbose) {
|
|
27
|
+
console.error(`[OXC Error] Failed parsing: ${filePath}. Reason: ${err.message}`);
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
walkOxcNode(node, fileNode) {
|
|
34
|
+
if (!node) return;
|
|
35
|
+
|
|
36
|
+
// OXC AST structure is slightly different from TS
|
|
37
|
+
// We focus on imports, exports, and namespace members
|
|
38
|
+
if (node.type === 'ImportDeclaration') {
|
|
39
|
+
const specifier = node.source.value;
|
|
40
|
+
fileNode.explicitImports.add(specifier);
|
|
41
|
+
node.specifiers.forEach(s => {
|
|
42
|
+
if (s.type === 'ImportSpecifier') {
|
|
43
|
+
fileNode.importedSymbols.add(`${specifier}:${s.imported.name}`);
|
|
44
|
+
} else if (s.type === 'ImportDefaultSpecifier') {
|
|
45
|
+
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
46
|
+
} else if (s.type === 'ImportNamespaceSpecifier') {
|
|
47
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
53
|
+
if (node.declaration) {
|
|
54
|
+
this.handleOxcDeclaration(node.declaration, fileNode);
|
|
55
|
+
}
|
|
56
|
+
if (node.specifiers) {
|
|
57
|
+
node.specifiers.forEach(s => {
|
|
58
|
+
fileNode.internalExports.set(s.exported.name, { type: 'export-specifier' });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (node.type === 'ExportDefaultDeclaration') {
|
|
64
|
+
fileNode.internalExports.set('default', { type: 'default-export' });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Phase 3: Namespace Member Tracking
|
|
68
|
+
if (node.type === 'TSModuleDeclaration' && node.id.type === 'Identifier') {
|
|
69
|
+
const namespaceName = node.id.name;
|
|
70
|
+
if (node.body && node.body.type === 'TSModuleBlock') {
|
|
71
|
+
node.body.body.forEach(innerNode => {
|
|
72
|
+
if (innerNode.type === 'ExportNamedDeclaration' && innerNode.declaration) {
|
|
73
|
+
const decl = innerNode.declaration;
|
|
74
|
+
let memberName;
|
|
75
|
+
if (decl.type === 'VariableDeclaration') {
|
|
76
|
+
memberName = decl.declarations[0].id.name;
|
|
77
|
+
} else if (decl.id) {
|
|
78
|
+
memberName = decl.id.name;
|
|
79
|
+
}
|
|
80
|
+
if (memberName) {
|
|
81
|
+
fileNode.namespaceMembers = fileNode.namespaceMembers || new Set();
|
|
82
|
+
fileNode.namespaceMembers.add(`${namespaceName}.${memberName}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Recursively walk
|
|
90
|
+
for (const key in node) {
|
|
91
|
+
const child = node[key];
|
|
92
|
+
if (child && typeof child === 'object') {
|
|
93
|
+
if (Array.isArray(child)) {
|
|
94
|
+
child.forEach(c => this.walkOxcNode(c, fileNode));
|
|
95
|
+
} else {
|
|
96
|
+
this.walkOxcNode(child, fileNode);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
handleOxcDeclaration(decl, fileNode) {
|
|
103
|
+
let name;
|
|
104
|
+
if (decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration' || decl.type === 'TSEnumDeclaration' || decl.type === 'TSInterfaceDeclaration' || decl.type === 'TSTypeAliasDeclaration') {
|
|
105
|
+
name = decl.id?.name;
|
|
106
|
+
} else if (decl.type === 'VariableDeclaration') {
|
|
107
|
+
name = decl.declarations[0].id.name;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (name) {
|
|
111
|
+
fileNode.internalExports.set(name, { type: decl.type.toLowerCase() });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -29,7 +29,7 @@ export class SelfHealer {
|
|
|
29
29
|
await refactorTask();
|
|
30
30
|
|
|
31
31
|
// Step 3: Stage changes inside our Git tracking context
|
|
32
|
-
await this.gitSandbox.
|
|
32
|
+
await this.gitSandbox.stageAndCheckpointChanges();
|
|
33
33
|
|
|
34
34
|
console.log('đ§Ş Running testing suites to verify workspace integrity...');
|
|
35
35
|
const validationPassed = await this.verifyProjectHealthStatus();
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import fs from 'fs/promises';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import ansis from 'ansis';
|
|
12
|
+
import readline from 'readline/promises';
|
|
12
13
|
|
|
13
14
|
// Import local domain architecture sub-systems
|
|
14
15
|
import { EngineContext } from './EngineContext.js';
|
|
@@ -16,17 +17,17 @@ import { ASTAnalyzer } from './ast/ASTAnalyzer.js';
|
|
|
16
17
|
import { BarrelParser } from './ast/BarrelParser.js';
|
|
17
18
|
import { MagicDetector } from './ast/MagicDetector.js';
|
|
18
19
|
import { PathMapper } from './resolution/PathMapper.js';
|
|
19
|
-
import { WorkspaceGraph } from './resolution/
|
|
20
|
-
import { DependencyResolver } from './resolution/
|
|
21
|
-
import { TransactionManager } from './
|
|
22
|
-
import { ImpactAnalyzer } from './
|
|
23
|
-
import { SourceRewriter } from './
|
|
24
|
-
import { TypeIntegrity } from './
|
|
20
|
+
import { WorkspaceGraph } from './resolution/WorkSpaceGraph.js';
|
|
21
|
+
import { DependencyResolver } from './resolution/DepencyResolver.js';
|
|
22
|
+
import { TransactionManager } from './refractor/TransactionManager.js';
|
|
23
|
+
import { ImpactAnalyzer } from './refractor/ImpactAnalyzer.js';
|
|
24
|
+
import { SourceRewriter } from './refractor/SourceRewriter.js';
|
|
25
|
+
import { TypeIntegrity } from './refractor/TypeIntegrity.js';
|
|
25
26
|
import { GitSandbox } from './healing/GitSandbox.js';
|
|
26
27
|
import { SelfHealer } from './healing/SelfHealer.js';
|
|
27
28
|
import { IncrementalCacheManager } from './performance/GraphCache.js';
|
|
28
29
|
import { WorkerPool } from './performance/WorkerPool.js';
|
|
29
|
-
import { SupplyChainGuard } from './
|
|
30
|
+
import { SupplyChainGuard } from './performance/SupplyChainGuard.js';
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Primary Refactoring Engine Core Coordination Controller
|
|
@@ -67,6 +68,11 @@ export class RefactoringEngine {
|
|
|
67
68
|
try {
|
|
68
69
|
console.log(ansis.bold.green('đŻ Starting pkg-scaffold Operational Optimization Cycle...'));
|
|
69
70
|
|
|
71
|
+
const rl = readline.createInterface({
|
|
72
|
+
input: process.stdin,
|
|
73
|
+
output: process.stdout
|
|
74
|
+
});
|
|
75
|
+
|
|
70
76
|
// Pass 1: Boot environment contexts and load alias configuration maps
|
|
71
77
|
await this.context.initialize();
|
|
72
78
|
await this.pathMapper.loadMappings(this.context.tsconfigFilename);
|
|
@@ -103,25 +109,32 @@ export class RefactoringEngine {
|
|
|
103
109
|
parallelParseCompleted = await this.workerPool.parallelAnalyzeCodebase(sourceCodeFilesList, this);
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
// Synchronous fallback loop to parse remaining files or cache misses
|
|
112
|
+
// Synchronous fallback loop to parse remaining files or cache misses.
|
|
113
|
+
// IMPORTANT: The `parallelParseCompleted` flag only controls whether the *worker pool*
|
|
114
|
+
// already processed all files. It must NOT be set per-file inside this loop â doing so
|
|
115
|
+
// would cause all files after the first cache-hit to be silently skipped.
|
|
107
116
|
for (const filePath of sourceCodeFilesList) {
|
|
108
117
|
const node = this.context.createNode(filePath);
|
|
109
118
|
const currentHash = await this.cacheManager.computeHash(filePath);
|
|
110
119
|
node.contentHash = currentHash;
|
|
111
120
|
|
|
112
|
-
if
|
|
121
|
+
// Check if this individual file is already up-to-date in the cache
|
|
122
|
+
const isFileCached = cacheManifest[filePath] && cacheManifest[filePath].hash === currentHash;
|
|
123
|
+
|
|
124
|
+
if (isFileCached) {
|
|
113
125
|
this.context.metrics.cacheHits++;
|
|
114
126
|
this.hydrateNodeFromCache(node, cacheManifest[filePath]);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (!parallelParseCompleted) {
|
|
127
|
+
} else if (!parallelParseCompleted) {
|
|
128
|
+
// Only parse if the worker pool did not already handle this file
|
|
119
129
|
this.context.metrics.cacheMisses++;
|
|
120
130
|
await this.analyzer.processFile(filePath, node);
|
|
121
131
|
}
|
|
122
132
|
|
|
123
133
|
// Apply ecosystem overrides directly to our parsed memory maps
|
|
124
134
|
this.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
|
|
135
|
+
|
|
136
|
+
// Aggregate used external packages for the dependency audit
|
|
137
|
+
node.externalPackageUsage.forEach(pkg => this.context.usedExternalPackages.add(pkg));
|
|
125
138
|
}
|
|
126
139
|
|
|
127
140
|
// Pass 4: Evaluate graph edges and link connections across the codebase mesh
|
|
@@ -139,45 +152,73 @@ export class RefactoringEngine {
|
|
|
139
152
|
analysisSummary.structuralIssuesDetected.deadExports.length > 0;
|
|
140
153
|
|
|
141
154
|
if (structuralModificationsStaged) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
155
|
+
console.log(ansis.bold.yellow('\nđ Proposed Optimization Plan:'));
|
|
156
|
+
console.log(ansis.dim('------------------------------------------------------------'));
|
|
157
|
+
|
|
158
|
+
if (analysisSummary.structuralIssuesDetected.deadFiles.length > 0) {
|
|
159
|
+
console.log(ansis.bold(` đď¸ Delete ${analysisSummary.structuralIssuesDetected.deadFiles.length} orphaned files:`));
|
|
160
|
+
analysisSummary.structuralIssuesDetected.deadFiles.forEach(f => console.log(ansis.dim(` ⢠${f}`)));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (analysisSummary.structuralIssuesDetected.deadExports.length > 0) {
|
|
164
|
+
console.log(ansis.bold(` âď¸ Prune ${analysisSummary.structuralIssuesDetected.deadExports.length} unused named exports:`));
|
|
165
|
+
analysisSummary.structuralIssuesDetected.deadExports.forEach(e => console.log(ansis.dim(` ⢠${e.symbol} in ${e.file}:${e.line}`)));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (analysisSummary.structuralIssuesDetected.unusedDependencies.length > 0) {
|
|
169
|
+
console.log(ansis.bold(` đŚ Remove ${analysisSummary.structuralIssuesDetected.unusedDependencies.length} unused dependencies:`));
|
|
170
|
+
analysisSummary.structuralIssuesDetected.unusedDependencies.forEach(d => console.log(ansis.dim(` ⢠${d.package} (in ${d.manifest})`)));
|
|
171
|
+
}
|
|
172
|
+
console.log(ansis.dim('------------------------------------------------------------'));
|
|
173
|
+
|
|
174
|
+
let proceed = this.context.skipConfirm;
|
|
175
|
+
if (!proceed) {
|
|
176
|
+
const answer = await rl.question(ansis.bold.cyan('\nâ Apply these structural modifications? (y/N): '));
|
|
177
|
+
proceed = answer.toLowerCase() === 'y';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (proceed) {
|
|
181
|
+
await this.selfHealer.runSelfHealingLifecycle(async () => {
|
|
182
|
+
// Sub-Task A: Purge completely unreferenced dangling components
|
|
183
|
+
for (const relPath of analysisSummary.structuralIssuesDetected.deadFiles) {
|
|
184
|
+
const absPath = path.resolve(this.context.cwd, relPath);
|
|
185
|
+
console.log(ansis.red(`âď¸ Removing unreferenced file: ${relPath}`));
|
|
186
|
+
await this.txManager.stageDeletion(absPath);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Sub-Task B: Surgically remove unused named export blocks from active files
|
|
190
|
+
for (const unusedExport of analysisSummary.structuralIssuesDetected.deadExports) {
|
|
191
|
+
const absPath = path.resolve(this.context.cwd, unusedExport.file);
|
|
192
|
+
const node = this.context.graph.get(absPath);
|
|
168
193
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
if (!node) continue;
|
|
195
|
+
const meta = node.internalExports.get(unusedExport.symbol);
|
|
196
|
+
|
|
197
|
+
// Perform safety analysis to ensure the token isn't called via dynamic runtime methods
|
|
198
|
+
const safetyVerdict = await this.impactAnalyzer.verifyRefactorSafety(absPath, unusedExport.symbol, this.context.graph);
|
|
199
|
+
|
|
200
|
+
if (safetyVerdict.isSafeToPrune) {
|
|
201
|
+
console.log(ansis.yellow(`⥠Stripping unused export [${unusedExport.symbol}] from: ${unusedExport.file}:${unusedExport.line}`));
|
|
202
|
+
const nextText = await this.sourceRewriter.stripNamedExportSignature(absPath, unusedExport.symbol, meta);
|
|
203
|
+
|
|
204
|
+
await this.txManager.stageWrite(absPath, nextText);
|
|
205
|
+
|
|
206
|
+
// Align matching type declaration boundaries (.d.ts) to prevent compilation errors
|
|
207
|
+
await this.typeIntegrity.synchronizeDeclarationFile(absPath, unusedExport.symbol);
|
|
208
|
+
} else if (this.context.verbose) {
|
|
209
|
+
console.log(ansis.gray(`đĄď¸ Preserving symbol export [${unusedExport.symbol}] due to: ${safetyVerdict.blockReason}`));
|
|
210
|
+
}
|
|
173
211
|
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
console.log(ansis.bold.yellow('\nâ ď¸ Optimization plan aborted by user. No changes applied.'));
|
|
215
|
+
}
|
|
176
216
|
}
|
|
177
217
|
}
|
|
178
218
|
|
|
179
219
|
// Pass 7: Save optimized graph footprints back to the cache directory
|
|
180
220
|
await this.cacheManager.saveCacheManifest(this.context.graph);
|
|
221
|
+
rl.close();
|
|
181
222
|
console.log(ansis.bold.green('\n⨠Core optimization cycle completed smoothly. Codebase workspace is healthy.'));
|
|
182
223
|
|
|
183
224
|
} catch (criticalFault) {
|
|
@@ -240,6 +281,12 @@ export class RefactoringEngine {
|
|
|
240
281
|
const devDeps = Object.keys(data.devDependencies || {});
|
|
241
282
|
const totalDependencies = [...prodDeps, ...devDeps];
|
|
242
283
|
|
|
284
|
+
// Register dependencies for later audit
|
|
285
|
+
this.context.manifestDependencies.set(packageJsonPath, {
|
|
286
|
+
dependencies: prodDeps,
|
|
287
|
+
devDependencies: devDeps
|
|
288
|
+
});
|
|
289
|
+
|
|
243
290
|
// Pass dependencies down to our Levenshtein string-distance guard to find typosquat targets
|
|
244
291
|
const supplyChainThreats = this.supplyChainGuard.detectTyposquattingAnomalies(totalDependencies);
|
|
245
292
|
|
|
@@ -269,6 +316,17 @@ export class RefactoringEngine {
|
|
|
269
316
|
}
|
|
270
317
|
node.isLibraryEntry = cachedRecord.isLibraryEntry || false;
|
|
271
318
|
node.securityThreats = cachedRecord.securityThreats || [];
|
|
319
|
+
if (cachedRecord.localSuppressedRules) {
|
|
320
|
+
cachedRecord.localSuppressedRules.forEach(r => node.localSuppressedRules.add(r));
|
|
321
|
+
}
|
|
322
|
+
if (cachedRecord.symbolSourceLocations) {
|
|
323
|
+
Object.entries(cachedRecord.symbolSourceLocations).forEach(([k, v]) => {
|
|
324
|
+
node.symbolSourceLocations.set(k, v);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (cachedRecord.externalPackageUsage) {
|
|
328
|
+
cachedRecord.externalPackageUsage.forEach(p => node.externalPackageUsage.add(p));
|
|
329
|
+
}
|
|
272
330
|
}
|
|
273
331
|
|
|
274
332
|
/**
|
|
@@ -338,6 +396,15 @@ export class RefactoringEngine {
|
|
|
338
396
|
});
|
|
339
397
|
}
|
|
340
398
|
|
|
399
|
+
if (report.structuralIssuesDetected.unusedDependencies.length > 0) {
|
|
400
|
+
console.log(ansis.bold.red(`\nUnused External Dependencies Flagged (${report.structuralIssuesDetected.unusedDependencies.length}):`));
|
|
401
|
+
report.structuralIssuesDetected.unusedDependencies.forEach(d => {
|
|
402
|
+
console.log(` ⢠Package [${ansis.red(d.package)}] in ${ansis.dim(d.manifest)}`);
|
|
403
|
+
});
|
|
404
|
+
} else {
|
|
405
|
+
console.log(ansis.green('\n⨠No unused external dependencies found in manifest files.'));
|
|
406
|
+
}
|
|
407
|
+
|
|
341
408
|
console.log(ansis.dim('\n============================================================\n'));
|
|
342
409
|
}
|
|
343
410
|
}
|
|
@@ -63,7 +63,10 @@ export class IncrementalCacheManager {
|
|
|
63
63
|
instantiatedIdentifiers: Array.from(node.instantiatedIdentifiers),
|
|
64
64
|
propertyAccessChains: Array.from(node.propertyAccessChains),
|
|
65
65
|
internalExports: Object.fromEntries(node.internalExports),
|
|
66
|
-
securityThreats: node.securityThreats || []
|
|
66
|
+
securityThreats: node.securityThreats || [],
|
|
67
|
+
localSuppressedRules: Array.from(node.localSuppressedRules),
|
|
68
|
+
symbolSourceLocations: Object.fromEntries(node.symbolSourceLocations),
|
|
69
|
+
externalPackageUsage: Array.from(node.externalPackageUsage)
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
|