promptwheel 0.8.2 → 0.8.3
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/bin/promptwheel.mjs +18 -5
- package/package.json +1 -1
package/bin/promptwheel.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { execSync, execFileSync } from 'node:child_process';
|
|
|
16
16
|
import { createHash } from 'node:crypto';
|
|
17
17
|
import { readFileSync, writeFileSync, existsSync, mkdtempSync, symlinkSync, rmSync, mkdirSync, realpathSync, readdirSync, lstatSync, copyFileSync } from 'node:fs';
|
|
18
18
|
import { tmpdir } from 'node:os';
|
|
19
|
-
import { join, dirname, basename, isAbsolute, relative, sep } from 'node:path';
|
|
19
|
+
import { join, dirname, basename, isAbsolute, relative, sep, resolve } from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
21
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
@@ -36,6 +36,19 @@ const spread = (xs) => { const a = xs.filter((x) => x != null); return a.length
|
|
|
36
36
|
// repo is still measuring in (a long gate elsewhere must not lose its worktree mid-run)
|
|
37
37
|
function tmpOwnerTag(repo) { let r = repo; try { r = realpathSync(repo); } catch { /* keep as given */ } return createHash('sha1').update(String(r)).digest('hex').slice(0, 8); }
|
|
38
38
|
const tmpPrefix = (repo, kind) => `${kind}-${tmpOwnerTag(repo)}-`;
|
|
39
|
+
// where the engine makes its clean copies: the system temp folder, or, where that is read-only (Claude Code's sandbox,
|
|
40
|
+
// a locked-down CI runner), a folder inside the repo's own .git, which git status never shows
|
|
41
|
+
const tmpBases = new Map();
|
|
42
|
+
function tmpBase(repo) {
|
|
43
|
+
if (tmpBases.has(repo)) return tmpBases.get(repo);
|
|
44
|
+
let base = tmpdir();
|
|
45
|
+
try { rmSync(mkdtempSync(join(base, 'pw-probe-')), { recursive: true, force: true }); }
|
|
46
|
+
catch {
|
|
47
|
+
let gd = ''; try { gd = resolve(repo, execFileSync('git', ['-C', repo, 'rev-parse', '--git-common-dir'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()); } catch { /* */ }
|
|
48
|
+
if (gd) { base = join(gd, 'promptwheel-tmp'); try { mkdirSync(base, { recursive: true }); } catch { base = tmpdir(); } }
|
|
49
|
+
}
|
|
50
|
+
tmpBases.set(repo, base); return base;
|
|
51
|
+
}
|
|
39
52
|
// exactly the names mkdtemp makes from tmpPrefix: the kind, the 8-hex owner tag, then mkdtemp's 6 random
|
|
40
53
|
// characters. Self-heal removes nothing else, so a folder that only starts with "promptwheel-" is never ours.
|
|
41
54
|
const ENGINE_TMP = /^(promptwheel-src|promptwheel|pw-idx)-([0-9a-f]{8})-[A-Za-z0-9]{6}$/;
|
|
@@ -273,7 +286,7 @@ const setupFails = new WeakMap(); // a measureAt result -> how its setup faile
|
|
|
273
286
|
|
|
274
287
|
// measure every metric `repeat` times at a ref, in a throwaway worktree (never touches your tree)
|
|
275
288
|
function measureAt(repo, ref, cfg, repeat) {
|
|
276
|
-
const wt = mkdtempSync(join(
|
|
289
|
+
const wt = mkdtempSync(join(tmpBase(repo), tmpPrefix(repo, 'promptwheel')));
|
|
277
290
|
git(['worktree', 'add', '--quiet', '--detach', wt, ref], repo);
|
|
278
291
|
try {
|
|
279
292
|
const env = bridgeEnv(repo, wt, cfg);
|
|
@@ -489,7 +502,7 @@ function measureSourceOnlyAll(repo, base, head, defs, cfg, repeat) {
|
|
|
489
502
|
if (!defs.length) return out;
|
|
490
503
|
const { source } = changedSourcePaths(repo, base, head, evaluatorMatcher(cfg));
|
|
491
504
|
if (!source.length) { for (const m of defs) out[m.name] = { samples: [], hadSourceChange: false }; return out; } // a "win" with zero source edits = goalposts moved
|
|
492
|
-
const wt = mkdtempSync(join(
|
|
505
|
+
const wt = mkdtempSync(join(tmpBase(repo), tmpPrefix(repo, 'promptwheel-src')));
|
|
493
506
|
git(['worktree', 'add', '--quiet', '--detach', wt, base], repo);
|
|
494
507
|
try {
|
|
495
508
|
const env = bridgeEnv(repo, wt, cfg);
|
|
@@ -550,7 +563,7 @@ function judgeGaming(m, srcResult, threshold = 0.5) {
|
|
|
550
563
|
// touching the real index or files, so a gate sees a file an agent just added.
|
|
551
564
|
// Returns 'HEAD' when nothing changed.
|
|
552
565
|
function workingSnapshot(repo) {
|
|
553
|
-
const idxDir = mkdtempSync(join(
|
|
566
|
+
const idxDir = mkdtempSync(join(tmpBase(repo), tmpPrefix(repo, 'pw-idx')));
|
|
554
567
|
const env = {
|
|
555
568
|
...process.env, GIT_INDEX_FILE: join(idxDir, 'index'),
|
|
556
569
|
GIT_AUTHOR_NAME: 'promptwheel', GIT_AUTHOR_EMAIL: 'promptwheel@local',
|
|
@@ -589,7 +602,7 @@ function selfHeal(repo) {
|
|
|
589
602
|
git(['worktree', 'list', '--porcelain'], repo)
|
|
590
603
|
.split('\n').filter((l) => l.startsWith('worktree ')).map((l) => l.slice(9).trim()),
|
|
591
604
|
);
|
|
592
|
-
const tmp =
|
|
605
|
+
const tmp = tmpBase(repo), now = Date.now(), mine = tmpOwnerTag(repo);
|
|
593
606
|
for (const name of readdirSync(tmp)) {
|
|
594
607
|
const made = ENGINE_TMP.exec(name);
|
|
595
608
|
if (!made) continue; // not a name this engine generates (worktrees + temp-index dirs): never removed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promptwheel",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "A deterministic, zero-LLM check for your coding loop. A win must survive a re-run from the agent's source edits alone, so a test that went green by editing the test reads GAMED. Runs in CI or a Claude Code plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|