pkg-scaffold 3.3.4 โ†’ 3.3.5

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.
@@ -81,11 +81,15 @@ export class OxcAnalyzer {
81
81
  const specifier = node.source.value;
82
82
  fileNode.explicitImports.add(specifier);
83
83
 
84
+ // Track external package usage for dependency analysis
85
+ if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
86
+ fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
87
+ }
88
+
84
89
  if (node.specifiers) {
85
90
  node.specifiers.forEach((spec) => {
86
91
  if (spec.type === "ImportSpecifier") {
87
92
  const importedName = spec.imported.name;
88
- const localName = spec.local.name;
89
93
  fileNode.importedSymbols.add(`${specifier}:${importedName}`);
90
94
  } else if (spec.type === "ImportDefaultSpecifier") {
91
95
  fileNode.importedSymbols.add(`${specifier}:default`);
@@ -103,20 +107,45 @@ export class OxcAnalyzer {
103
107
  }
104
108
 
105
109
  if (node.type === "ExportAllDeclaration") {
106
- if (node.exported && node.exported.type === "ExportNamespaceSpecifier") {
107
- // export * as name from 'module'
108
- const name = node.exported.name;
109
- fileNode.internalExports.set(name, { type: "re-export-namespace", source: node.source.value, originalName: "*", start: node.start, end: node.end });
110
- } else {
111
- // export * from 'module'
112
- fileNode.internalExports.set("*", { type: "re-export-all", source: node.source.value });
110
+ const sourceSpecifier = node.source ? node.source.value : null;
111
+ if (sourceSpecifier) {
112
+ // FIX: Register re-export source as an explicit import so the graph linker
113
+ // creates an incomingEdge on the re-exported file.
114
+ fileNode.explicitImports.add(sourceSpecifier);
115
+
116
+ // Track external package usage from re-exports
117
+ if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
118
+ fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
119
+ }
120
+
121
+ if (node.exported) {
122
+ // export * as name from 'module'
123
+ const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
124
+ if (name) {
125
+ fileNode.internalExports.set(name, { type: "re-export-namespace", source: sourceSpecifier, originalName: "*", start: node.start, end: node.end });
126
+ fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
127
+ }
128
+ } else {
129
+ // export * from 'module'
130
+ fileNode.internalExports.set("*", { type: "re-export-all", source: sourceSpecifier });
131
+ // FIX: Register as wildcard importedSymbol so graph linker creates incomingEdge
132
+ fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
133
+ }
113
134
  }
114
135
  return;
115
136
  }
116
137
 
117
138
  if (node.source) {
118
- // Re-export
139
+ // Re-export with source: export { x } from 'module'
119
140
  const specifier = node.source.value;
141
+ // FIX: Register re-export source as an explicit import
142
+ fileNode.explicitImports.add(specifier);
143
+
144
+ // Track external package usage from re-exports
145
+ if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
146
+ fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
147
+ }
148
+
120
149
  if (node.specifiers) {
121
150
  node.specifiers.forEach((spec) => {
122
151
  const exportedName = spec.exported.name;
@@ -128,6 +157,8 @@ export class OxcAnalyzer {
128
157
  start: node.start,
129
158
  end: node.end,
130
159
  });
160
+ // FIX: Register as importedSymbol so barrel-tracer can resolve origin file
161
+ fileNode.importedSymbols.add(`${specifier}:${localName}`);
131
162
  });
132
163
  }
133
164
  } else if (node.declaration) {
@@ -184,12 +215,28 @@ export class OxcAnalyzer {
184
215
  }
185
216
 
186
217
  handleCallExpression(node, fileNode) {
187
- if (node.callee.type === "Import" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
218
+ // Dynamic import(): import('./module')
219
+ if (node.callee.type === "Import" && node.arguments.length > 0) {
220
+ const arg = node.arguments[0];
221
+ if (arg.type === "StringLiteral") {
222
+ const specifier = arg.value;
223
+ fileNode.explicitImports.add(specifier);
224
+ fileNode.dynamicImports.add(specifier);
225
+ if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
226
+ fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
227
+ }
228
+ } else {
229
+ // Non-literal dynamic import: record as calculated
230
+ if (fileNode.calculatedDynamicImports) {
231
+ fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
232
+ }
233
+ }
234
+ } else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
188
235
  const specifier = node.arguments[0].value;
189
236
  fileNode.explicitImports.add(specifier);
190
- fileNode.dynamicImports.add(specifier);
191
- } else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
192
- fileNode.explicitImports.add(node.arguments[0].value);
237
+ if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
238
+ fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
239
+ }
193
240
  }
194
241
  }
195
242
 
@@ -233,4 +280,16 @@ export class OxcAnalyzer {
233
280
  });
234
281
  }
235
282
  }
283
+
284
+ /**
285
+ * Extracts the root npm package name from an import specifier.
286
+ * Handles scoped packages (@scope/pkg) and subpath imports (pkg/utils, @scope/pkg/utils).
287
+ */
288
+ _extractPackageName(specifier) {
289
+ if (specifier.startsWith('@')) {
290
+ const parts = specifier.split('/');
291
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
292
+ }
293
+ return specifier.split('/')[0];
294
+ }
236
295
  }
@@ -1,160 +1,82 @@
1
- import { execSync } from 'child_process';
1
+ import { execa } from 'execa';
2
2
  import path from 'path';
3
3
 
4
4
  /**
5
- * Ephemeral Git Sandbox & Version Control Islolation Gateway
6
- * Manages atomic branch switching, working-tree stashing, and systemic rollbacks.
5
+ * Deterministic Version Control Guard for Structural Healing Operations.
6
+ * Manages atomic state rollbacks when automated refactoring breaks the build.
7
7
  */
8
8
  export class GitSandbox {
9
9
  constructor(context) {
10
10
  this.context = context;
11
- this.originalBranch = null;
12
- this.sandboxBranch = null;
13
- this.hasStashedChanges = false;
14
- this.isGitRepository = false;
11
+ this.initialBranch = '';
12
+ this.healingBranch = `scaffold-healing-${Date.now()}`;
15
13
  }
16
14
 
17
15
  /**
18
- * Validates the active environment and isolates the working directory.
16
+ * Captures the current repository state before applying structural modifications.
19
17
  */
20
- async establishIsolationCheckpoint() {
21
- this.verifyGitRepositoryPresence();
22
- if (!this.isGitRepository) return;
23
-
18
+ async captureState() {
24
19
  try {
25
- // Capture the current branch name
26
- this.originalBranch = execSync('git rev-parse --abbrev-ref HEAD', {
27
- cwd: this.context.cwd,
28
- encoding: 'utf8'
29
- }).trim();
30
-
31
- // Check for uncommitted working directory changes
32
- const statusOutput = execSync('git status --porcelain', {
33
- cwd: this.context.cwd,
34
- encoding: 'utf8'
35
- }).trim();
36
-
37
- if (statusOutput.length > 0) {
38
- if (this.context.verbose) {
39
- console.log('๐Ÿ“ฆ Working tree contains uncommitted changes. Stashing before running pipeline...');
40
- }
41
- execSync('git stash save "pkg-scaffold: Ephemeral Checkpoint Stash"', {
42
- cwd: this.context.cwd,
43
- stdio: 'ignore'
44
- });
45
- this.hasStashedChanges = true;
46
- }
47
-
48
- // Generate an isolated tracking branch name
49
- this.sandboxBranch = `scaffold-heal-${Date.now()}`;
50
- execSync(`git checkout -b ${this.sandboxBranch}`, {
51
- cwd: this.context.cwd,
52
- stdio: 'ignore'
53
- });
54
-
20
+ const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: this.context.cwd });
21
+ this.initialBranch = stdout.trim();
22
+
23
+ // Create a temporary recovery branch
24
+ await execa('git', ['checkout', '-b', this.healingBranch], { cwd: this.context.cwd });
55
25
  if (this.context.verbose) {
56
- console.log(`๐ŸŒฟ Isolated sandbox branch successfully initialized: [${this.sandboxBranch}]`);
26
+ console.log(`[Git] State captured in temporary branch: ${this.healingBranch}`);
57
27
  }
58
- } catch (error) {
59
- throw new Error(`Git Sandbox separation failure: ${error.message}`);
28
+ } catch (e) {
29
+ throw new Error(`Git state capture failed: Ensure the directory is a git repository. (${e.message})`);
60
30
  }
61
31
  }
62
32
 
63
33
  /**
64
- * Commits all modifications within the sandbox branch for validation testing.
34
+ * Reverts all changes applied during the healing cycle if verification fails.
65
35
  */
66
- async stageAndCheckpointChanges(commitMessage = 'refactor(scaffold): apply structural optimizations') {
67
- if (!this.isGitRepository) return;
68
- this.assertActiveIsolation();
69
-
36
+ async rollback() {
70
37
  try {
71
- execSync('git add .', { cwd: this.context.cwd, stdio: 'ignore' });
72
-
73
- // Check if any mutations were actually staged
74
- const diffIndex = execSync('git diff --cached --name-only', {
75
- cwd: this.context.cwd,
76
- encoding: 'utf8'
77
- }).trim();
78
-
79
- if (diffIndex.length === 0) return; // No modifications to verify
80
-
81
- execSync(`git commit -m "${commitMessage}" --no-verify`, {
82
- cwd: this.context.cwd,
83
- stdio: 'ignore'
84
- });
85
- } catch (error) {
86
- throw new Error(`Failed to stage changes inside the sandbox: ${error.message}`);
38
+ console.log(`[Git] Rolling back structural modifications...`);
39
+ await execa('git', ['reset', '--hard', 'HEAD'], { cwd: this.context.cwd });
40
+ await execa('git', ['checkout', this.initialBranch], { cwd: this.context.cwd });
41
+ await execa('git', ['branch', '-D', this.healingBranch], { cwd: this.context.cwd });
42
+ } catch (e) {
43
+ console.error(`[Git] Critical rollback failure: ${e.message}`);
87
44
  }
88
45
  }
89
46
 
90
47
  /**
91
- * Merges modifications back into the mainline branch if all validation test suites pass.
48
+ * Finalizes the healing cycle by merging changes back to the original branch.
92
49
  */
93
- async acceptAndMergeOptimizations() {
94
- if (!this.isGitRepository) return;
95
- this.assertActiveIsolation();
96
-
50
+ async commit() {
97
51
  try {
98
- // Return to the original project branch boundary
99
- execSync(`git checkout ${this.originalBranch}`, { cwd: this.context.cwd, stdio: 'ignore' });
52
+ await execa('git', ['add', '.'], { cwd: this.context.cwd });
53
+ await execa('git', ['commit', '-m', 'chore: automated structural healing (pkg-scaffold)'], { cwd: this.context.cwd });
100
54
 
101
- // Merge the verified changes without creating a fast-forward bottleneck
102
- execSync(`git merge --squash ${this.sandboxBranch}`, { cwd: this.context.cwd, stdio: 'ignore' });
103
- execSync('git commit -m "chore(refactor): apply verified dead-code removals" --no-verify', {
104
- cwd: this.context.cwd,
105
- stdio: 'ignore'
106
- });
107
-
108
- // Purge the temporary branch tracking reference
109
- execSync(`git branch -D ${this.sandboxBranch}`, { cwd: this.context.cwd, stdio: 'ignore' });
110
- this.restorePreviousWorkingState();
111
- } catch (error) {
112
- throw new Error(`Failed to merge sandbox modifications into mainline branch: ${error.message}`);
55
+ await execa('git', ['checkout', this.initialBranch], { cwd: this.context.cwd });
56
+ await execa('git', ['merge', this.healingBranch], { cwd: this.context.cwd });
57
+ await execa('git', ['branch', '-D', this.healingBranch], { cwd: this.context.cwd });
58
+
59
+ if (this.context.verbose) {
60
+ console.log(`[Git] Structural modifications successfully merged into ${this.initialBranch}`);
61
+ }
62
+ } catch (e) {
63
+ console.error(`[Git] Commit failed: ${e.message}`);
113
64
  }
114
65
  }
115
66
 
116
67
  /**
117
- * Reverts all disk changes instantly if testing suites flag an error.
68
+ * Runs a verification command (e.g., npm test) to ensure structural integrity.
118
69
  */
119
- async rejectAndAbortOptimizations() {
120
- if (!this.isGitRepository) return;
121
- if (!this.sandboxBranch) return;
122
-
70
+ async verifyIntegrity() {
123
71
  try {
72
+ const [cmd, ...args] = this.context.testCommand.split(' ');
73
+ await execa(cmd, args, { cwd: this.context.cwd });
74
+ return true;
75
+ } catch (e) {
124
76
  if (this.context.verbose) {
125
- console.log('๐Ÿ”„ Aborting transaction loop. Resetting file systems...');
77
+ console.warn(`[Git] Integrity verification failed: ${e.message}`);
126
78
  }
127
-
128
- execSync('git add . && git reset --hard HEAD', { cwd: this.context.cwd, stdio: 'ignore' });
129
- execSync(`git checkout ${this.originalBranch}`, { cwd: this.context.cwd, stdio: 'ignore' });
130
- execSync(`git branch -D ${this.sandboxBranch}`, { cwd: this.context.cwd, stdio: 'ignore' });
131
-
132
- this.restorePreviousWorkingState();
133
- } catch (error) {
134
- console.error(`๐Ÿšจ Critical Recovery Error: Failed to drop sandbox branch cleanly: ${error.message}`);
135
- }
136
- }
137
-
138
- restorePreviousWorkingState() {
139
- if (this.hasStashedChanges) {
140
- execSync('git stash pop', { cwd: this.context.cwd, stdio: 'ignore' });
141
- this.hasStashedChanges = false;
142
- }
143
- this.sandboxBranch = null;
144
- }
145
-
146
- verifyGitRepositoryPresence() {
147
- try {
148
- execSync('git rev-parse --is-inside-work-tree', { cwd: this.context.cwd, stdio: 'ignore' });
149
- this.isGitRepository = true;
150
- } catch {
151
- this.isGitRepository = false;
152
- }
153
- }
154
-
155
- assertActiveIsolation() {
156
- if (!this.sandboxBranch) {
157
- throw new Error('Git Sandbox operation error: No active sandbox tracking context exists.');
79
+ return false;
158
80
  }
159
81
  }
160
82
  }
@@ -1,150 +1,49 @@
1
- import { spawn } from 'child_process';
2
- import path from 'path';
1
+ import ansis from 'ansis';
3
2
 
4
3
  /**
5
- * Self-Healing Test Orchestration & Regression Scanner
6
- * Runs validation suites, intercepts stack traces, and controls transaction rollbacks.
4
+ * Automated Structural Healing Orchestrator.
5
+ * Manages the lifecycle of applying structural fixes and verifying codebase health.
6
+ * This is a deterministic engine and does not use AI/LLMs.
7
7
  */
8
8
  export class SelfHealer {
9
- constructor(context, transactionManager, gitSandbox) {
9
+ constructor(context, txManager, gitSandbox) {
10
10
  this.context = context;
11
- this.transactionManager = transactionManager;
11
+ this.txManager = txManager;
12
12
  this.gitSandbox = gitSandbox;
13
- this.executionTimeout = 45000; // 45-second execution timeout wall
14
13
  }
15
14
 
16
15
  /**
17
- * Validates the stability of staged code changes by running your project's test suite.
18
- * @param {Function} refactorTask - The function containing code changes to evaluate
16
+ * Executes a structural healing cycle with automatic rollback on failure.
17
+ * @param {Function} refactorLogic - Async function that stages structural changes
19
18
  */
20
- async runSelfHealingLifecycle(refactorTask) {
21
- console.log('๐Ÿ›ก๏ธ Initializing transaction safety sandbox...');
19
+ async runSelfHealingLifecycle(refactorLogic) {
20
+ console.log(ansis.bold.blue('\n๐Ÿฉน Initiating Automated Structural Healing Cycle...'));
22
21
 
23
- // Step 1: Open the transaction boundaries
24
- await this.gitSandbox.establishIsolationCheckpoint();
25
- await this.transactionManager.begin();
26
-
27
22
  try {
28
- // Step 2: Execute the codebase pruning transformations
29
- await refactorTask();
23
+ // 1. Capture current stable state
24
+ await this.gitSandbox.captureState();
25
+
26
+ // 2. Execute the provided refactoring logic (staging deletions/writes)
27
+ await refactorLogic();
30
28
 
31
- // Step 3: Stage changes inside our Git tracking context
32
- await this.gitSandbox.stageAndCheckpointChanges();
29
+ // 3. Commit staged changes to disk
30
+ await this.txManager.commitAll();
33
31
 
34
- console.log('๐Ÿงช Running testing suites to verify workspace integrity...');
35
- const validationPassed = await this.verifyProjectHealthStatus();
32
+ // 4. Verify structural integrity (e.g., run tests)
33
+ console.log(ansis.dim('๐Ÿงช Verifying codebase integrity...'));
34
+ const isHealthy = await this.gitSandbox.verifyIntegrity();
36
35
 
37
- if (validationPassed) {
38
- console.log('โœจ Build stable. Merging verified code improvements.');
39
- await this.transactionManager.commit();
40
- await this.gitSandbox.acceptAndMergeOptimizations();
41
- return true;
36
+ if (isHealthy) {
37
+ console.log(ansis.bold.green('โœ… Structural integrity verified. Finalizing changes.'));
38
+ await this.gitSandbox.commit();
42
39
  } else {
43
- console.warn('โš ๏ธ Regression flagged during validation. Triggering self-healing rollback...');
44
- await this.revertWorkspaceToSafeState();
45
- return false;
40
+ console.log(ansis.bold.red('โŒ Structural integrity compromised. Rolling back changes.'));
41
+ await this.gitSandbox.rollback();
42
+ this.context.metrics.securityVulnerabilitiesMitigated = 0; // Reset metrics for failed cycle
46
43
  }
47
- } catch (pipelineError) {
48
- console.error(`๐Ÿšจ Critical Exception Intercepted: ${pipelineError.message}`);
49
- await this.revertWorkspaceToSafeState();
50
- return false;
51
- }
52
- }
53
-
54
- /**
55
- * Spawns testing processes to detect exit code alerts or compiler trace metrics.
56
- * @returns {Promise<boolean>} True if the test suite runs with an exit code of 0
57
- */
58
- verifyProjectHealthStatus() {
59
- return new Promise((resolve) => {
60
- const commandTokens = this.context.testCommand.split(' ');
61
- const executionBinary = commandTokens.shift();
62
-
63
- const processBuffer = [];
64
- const testProcess = spawn(executionBinary, commandTokens, {
65
- cwd: this.context.cwd,
66
- shell: true
67
- });
68
-
69
- // Implement an execution timeout monitor to prevent hanging test suites from blocking the pipeline
70
- const timeoutWatch = setTimeout(() => {
71
- console.warn('โฑ๏ธ Test execution exceeded timeout limit. Terminating subprocess...');
72
- testProcess.kill('SIGKILL');
73
- }, this.executionTimeout);
74
-
75
- testProcess.stdout.on('data', (data) => {
76
- processBuffer.push(data);
77
- if (this.context.verbose) {
78
- process.stdout.write(data);
79
- }
80
- });
81
-
82
- testProcess.stderr.on('data', (data) => {
83
- processBuffer.push(data);
84
- if (this.context.verbose) {
85
- process.stderr.write(data);
86
- }
87
- });
88
-
89
- testProcess.on('close', (exitCode) => {
90
- clearTimeout(timeoutWatch);
91
-
92
- if (exitCode !== 0) {
93
- const fullConsoleOutput = Buffer.concat(processBuffer).toString('utf8');
94
- this.diagnoseErrorOutput(fullConsoleOutput);
95
- resolve(false);
96
- } else {
97
- resolve(true);
98
- }
99
- });
100
-
101
- testProcess.on('error', (err) => {
102
- clearTimeout(timeoutWatch);
103
- if (this.context.verbose) {
104
- console.error(`Subprocess execution error: ${err.message}`);
105
- }
106
- resolve(false);
107
- });
108
- });
109
- }
110
-
111
- /**
112
- * Traces error codes and console log outputs to identify the root cause of a broken build.
113
- */
114
- diagnoseErrorOutput(consoleLogOutput) {
115
- console.log('\n๐Ÿ” [Diagnostics] Reviewing error output...');
116
-
117
- // Scan for standard TypeScript Compilation errors
118
- const tsErrorPattern = /error\s+TS(\d+):\s+([\s\S]*?)$/m;
119
- const tsMatches = consoleLogOutput.match(tsErrorPattern);
120
-
121
- if (tsMatches) {
122
- console.error(`โŒ Mapped Type Error Code [TS${tsMatches[1]}]: ${tsMatches[2].trim()}`);
123
- return;
124
- }
125
-
126
- // Scan for missing file imports or runtime resolution exceptions
127
- const missingModulePattern = /Cannot find module '([^']+)'/i;
128
- const moduleMatches = consoleLogOutput.match(missingModulePattern);
129
-
130
- if (moduleMatches) {
131
- console.error(`โŒ Missing Dependency Reference Pointer: Cannot locate element [${moduleMatches[1]}]`);
132
- return;
133
- }
134
-
135
- console.error('โŒ Test suite execution failed. Check standard console output for details.');
136
- }
137
-
138
- /**
139
- * Automatically executes a rollback event, restoring both file states and Git status.
140
- */
141
- async revertWorkspaceToSafeState() {
142
- try {
143
- await this.transactionManager.rollback();
144
- await this.gitSandbox.rejectAndAbortOptimizations();
145
- console.log('๐Ÿ”„ [Self-Healing] Rollback complete. Original project states restored.');
146
- } catch (recoveryError) {
147
- console.error(`๐Ÿšจ Fatal Recovery Error: Failed to restore prior workspace configuration: ${recoveryError.message}`);
44
+ } catch (error) {
45
+ console.error(ansis.bold.red(`\n๐Ÿšจ Healing Cycle Aborted: ${error.message}`));
46
+ await this.gitSandbox.rollback();
148
47
  }
149
48
  }
150
49
  }