promptwheel 0.8.2 → 0.8.4
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 +21 -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,22 @@ 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
|
+
let ok = false;
|
|
49
|
+
if (gd) { const g = join(gd, 'promptwheel-tmp'); try { mkdirSync(g, { recursive: true }); rmSync(mkdtempSync(join(g, 'pw-probe-')), { recursive: true, force: true }); base = g; ok = true; } catch { /* */ } }
|
|
50
|
+
// a sandboxed shell (Claude Code's sandbox makes the temp folder and .git read-only): say so plainly, not with a stack
|
|
51
|
+
if (!ok) throw new Error('this shell cannot write a temp folder or .git (a sandboxed agent shell). Run the check outside the sandbox; under attach, the Stop hook checks each turn outside it.');
|
|
52
|
+
}
|
|
53
|
+
tmpBases.set(repo, base); return base;
|
|
54
|
+
}
|
|
39
55
|
// exactly the names mkdtemp makes from tmpPrefix: the kind, the 8-hex owner tag, then mkdtemp's 6 random
|
|
40
56
|
// characters. Self-heal removes nothing else, so a folder that only starts with "promptwheel-" is never ours.
|
|
41
57
|
const ENGINE_TMP = /^(promptwheel-src|promptwheel|pw-idx)-([0-9a-f]{8})-[A-Za-z0-9]{6}$/;
|
|
@@ -273,7 +289,7 @@ const setupFails = new WeakMap(); // a measureAt result -> how its setup faile
|
|
|
273
289
|
|
|
274
290
|
// measure every metric `repeat` times at a ref, in a throwaway worktree (never touches your tree)
|
|
275
291
|
function measureAt(repo, ref, cfg, repeat) {
|
|
276
|
-
const wt = mkdtempSync(join(
|
|
292
|
+
const wt = mkdtempSync(join(tmpBase(repo), tmpPrefix(repo, 'promptwheel')));
|
|
277
293
|
git(['worktree', 'add', '--quiet', '--detach', wt, ref], repo);
|
|
278
294
|
try {
|
|
279
295
|
const env = bridgeEnv(repo, wt, cfg);
|
|
@@ -489,7 +505,7 @@ function measureSourceOnlyAll(repo, base, head, defs, cfg, repeat) {
|
|
|
489
505
|
if (!defs.length) return out;
|
|
490
506
|
const { source } = changedSourcePaths(repo, base, head, evaluatorMatcher(cfg));
|
|
491
507
|
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(
|
|
508
|
+
const wt = mkdtempSync(join(tmpBase(repo), tmpPrefix(repo, 'promptwheel-src')));
|
|
493
509
|
git(['worktree', 'add', '--quiet', '--detach', wt, base], repo);
|
|
494
510
|
try {
|
|
495
511
|
const env = bridgeEnv(repo, wt, cfg);
|
|
@@ -550,7 +566,7 @@ function judgeGaming(m, srcResult, threshold = 0.5) {
|
|
|
550
566
|
// touching the real index or files, so a gate sees a file an agent just added.
|
|
551
567
|
// Returns 'HEAD' when nothing changed.
|
|
552
568
|
function workingSnapshot(repo) {
|
|
553
|
-
const idxDir = mkdtempSync(join(
|
|
569
|
+
const idxDir = mkdtempSync(join(tmpBase(repo), tmpPrefix(repo, 'pw-idx')));
|
|
554
570
|
const env = {
|
|
555
571
|
...process.env, GIT_INDEX_FILE: join(idxDir, 'index'),
|
|
556
572
|
GIT_AUTHOR_NAME: 'promptwheel', GIT_AUTHOR_EMAIL: 'promptwheel@local',
|
|
@@ -589,7 +605,7 @@ function selfHeal(repo) {
|
|
|
589
605
|
git(['worktree', 'list', '--porcelain'], repo)
|
|
590
606
|
.split('\n').filter((l) => l.startsWith('worktree ')).map((l) => l.slice(9).trim()),
|
|
591
607
|
);
|
|
592
|
-
const tmp =
|
|
608
|
+
const tmp = tmpBase(repo), now = Date.now(), mine = tmpOwnerTag(repo);
|
|
593
609
|
for (const name of readdirSync(tmp)) {
|
|
594
610
|
const made = ENGINE_TMP.exec(name);
|
|
595
611
|
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.4",
|
|
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": {
|