@fermindi/pwn-cli 0.9.7 → 0.9.8
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/package.json
CHANGED
|
@@ -840,6 +840,54 @@ function spawnClaude(prompt, task, iteration, maxIter, done, total, phase, logFi
|
|
|
840
840
|
});
|
|
841
841
|
}
|
|
842
842
|
|
|
843
|
+
/**
|
|
844
|
+
* Ensure Python dev tools (pytest, ruff, mypy, etc.) exist in the venv.
|
|
845
|
+
* Extracts tool names from gate_commands and verifies they're importable.
|
|
846
|
+
* Installs missing ones to prevent wasted retries on environment issues.
|
|
847
|
+
* @param {string} taskCwd - Worktree directory (has .venv symlink)
|
|
848
|
+
* @param {string} mainCwd - Main repo directory for config
|
|
849
|
+
*/
|
|
850
|
+
async function ensureDevTools(taskCwd, mainCwd) {
|
|
851
|
+
const config = loadConfig(mainCwd);
|
|
852
|
+
const gateCommands = config.gate_commands;
|
|
853
|
+
if (!gateCommands) return;
|
|
854
|
+
|
|
855
|
+
// Find the python binary from gate commands
|
|
856
|
+
const sampleCmd = Object.values(gateCommands)[0] || '';
|
|
857
|
+
const pythonMatch = sampleCmd.match(/^(\S*python\S*)\s/);
|
|
858
|
+
if (!pythonMatch) return; // not a Python project
|
|
859
|
+
|
|
860
|
+
const python = pythonMatch[1];
|
|
861
|
+
|
|
862
|
+
// Extract module names from commands like ".venv/bin/python -m pytest --tb=short"
|
|
863
|
+
const modules = new Set();
|
|
864
|
+
for (const cmd of Object.values(gateCommands)) {
|
|
865
|
+
const modMatch = cmd.match(/-m\s+(\w+)/);
|
|
866
|
+
if (modMatch) modules.add(modMatch[1]);
|
|
867
|
+
}
|
|
868
|
+
if (modules.size === 0) return;
|
|
869
|
+
|
|
870
|
+
// Check which modules are missing
|
|
871
|
+
const missing = [];
|
|
872
|
+
for (const mod of modules) {
|
|
873
|
+
try {
|
|
874
|
+
await execAsync(`${python} -c "import ${mod}"`, { cwd: taskCwd, timeout: 10_000 });
|
|
875
|
+
} catch {
|
|
876
|
+
missing.push(mod);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (missing.length > 0) {
|
|
881
|
+
console.log(chalk.yellow(` Dev tools missing in venv: ${missing.join(', ')}. Installing...`));
|
|
882
|
+
try {
|
|
883
|
+
await execAsync(`${python} -m pip install ${missing.join(' ')} --quiet`, { cwd: taskCwd, timeout: 60_000 });
|
|
884
|
+
console.log(chalk.green(` Installed: ${missing.join(', ')}`));
|
|
885
|
+
} catch (err) {
|
|
886
|
+
console.log(chalk.red(` Failed to install dev tools: ${err.message}`));
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
843
891
|
/**
|
|
844
892
|
* Run quality gates with real-time PASS/FAIL display.
|
|
845
893
|
* @param {string} taskCwd - Directory where gates run (task worktree)
|
|
@@ -852,6 +900,9 @@ async function runGatesWithStatus(taskCwd, mainCwd = taskCwd) {
|
|
|
852
900
|
let allPassed = true;
|
|
853
901
|
let errorOutput = '';
|
|
854
902
|
|
|
903
|
+
// Pre-flight: ensure dev tools exist before running gates
|
|
904
|
+
await ensureDevTools(taskCwd, mainCwd);
|
|
905
|
+
|
|
855
906
|
for (const gate of gates) {
|
|
856
907
|
if (skipGates.includes(gate)) {
|
|
857
908
|
console.log(chalk.dim(` ${gate}: SKIP`));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* checkpointing, and signal handling.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync, appendFileSync, symlinkSync, unlinkSync, lstatSync } from 'fs';
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, appendFileSync, symlinkSync, unlinkSync, lstatSync, readlinkSync } from 'fs';
|
|
9
9
|
import { join } from 'path';
|
|
10
10
|
import os from 'os';
|
|
11
11
|
import { exec } from 'child_process';
|
|
@@ -244,7 +244,22 @@ export function prepareWorktree(worktreePath, mainCwd) {
|
|
|
244
244
|
for (const dir of DEP_DIRS) {
|
|
245
245
|
const source = join(mainCwd, dir);
|
|
246
246
|
const target = join(worktreePath, dir);
|
|
247
|
-
if (existsSync(source)
|
|
247
|
+
if (!existsSync(source)) continue;
|
|
248
|
+
|
|
249
|
+
// Validate existing symlinks (git may restore stale ones from history)
|
|
250
|
+
try {
|
|
251
|
+
const stats = lstatSync(target);
|
|
252
|
+
if (stats.isSymbolicLink()) {
|
|
253
|
+
if (readlinkSync(target) !== source) {
|
|
254
|
+
unlinkSync(target);
|
|
255
|
+
symlinkSync(source, target);
|
|
256
|
+
}
|
|
257
|
+
continue; // symlink exists and is correct
|
|
258
|
+
}
|
|
259
|
+
// Not a symlink (real dir) — leave it alone
|
|
260
|
+
continue;
|
|
261
|
+
} catch {
|
|
262
|
+
// Doesn't exist — create it
|
|
248
263
|
symlinkSync(source, target);
|
|
249
264
|
}
|
|
250
265
|
}
|