openpitstop 1.9.0 → 1.10.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/PRIVACY.md +77 -90
- package/README.md +365 -887
- package/dist/agentBudget.d.ts +33 -0
- package/dist/agentBudget.js +165 -0
- package/dist/agentBudget.js.map +1 -0
- package/dist/analyzers/ledger/index.d.ts +2 -1
- package/dist/analyzers/ledger/index.js +10 -1
- package/dist/analyzers/ledger/index.js.map +1 -1
- package/dist/analyzers/types.d.ts +2 -0
- package/dist/candidate.d.ts +20 -0
- package/dist/candidate.js +87 -0
- package/dist/candidate.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/ask.d.ts +0 -8
- package/dist/commands/ask.js +36 -34
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/budget.d.ts +1 -0
- package/dist/commands/budget.js +12 -3
- package/dist/commands/budget.js.map +1 -1
- package/dist/commands/drive.js +77 -51
- package/dist/commands/drive.js.map +1 -1
- package/dist/commands/fix.d.ts +7 -0
- package/dist/commands/fix.js +90 -108
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/gate.js +6 -2
- package/dist/commands/gate.js.map +1 -1
- package/dist/commands/pen.d.ts +3 -1
- package/dist/commands/pen.js +31 -42
- package/dist/commands/pen.js.map +1 -1
- package/dist/commands/readyCheck.d.ts +1 -1
- package/dist/commands/readyCheck.js +6 -36
- package/dist/commands/readyCheck.js.map +1 -1
- package/dist/commands/scan.d.ts +6 -5
- package/dist/commands/scan.js +26 -28
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/verify.d.ts +3 -1
- package/dist/commands/verify.js +5 -1
- package/dist/commands/verify.js.map +1 -1
- package/dist/evidence.js +4 -0
- package/dist/evidence.js.map +1 -1
- package/dist/installer/targets.js +1 -1
- package/dist/installer/targets.js.map +1 -1
- package/dist/intent.d.ts +4 -7
- package/dist/intent.js +41 -77
- package/dist/intent.js.map +1 -1
- package/dist/pen/dynamic.d.ts +1 -24
- package/dist/pen/dynamic.js +10 -1
- package/dist/pen/dynamic.js.map +1 -1
- package/dist/pen/fix.js +5 -1
- package/dist/pen/fix.js.map +1 -1
- package/dist/repro/generate.js +1 -0
- package/dist/repro/generate.js.map +1 -1
- package/dist/repro/pen.js +1 -0
- package/dist/repro/pen.js.map +1 -1
- package/dist/repro/run.d.ts +1 -0
- package/dist/repro/run.js +17 -1
- package/dist/repro/run.js.map +1 -1
- package/dist/sandbox/container.d.ts +9 -0
- package/dist/sandbox/container.js +129 -0
- package/dist/sandbox/container.js.map +1 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +41 -0
- package/dist/sandbox/worker.js.map +1 -0
- package/dist/scanCache.d.ts +27 -0
- package/dist/scanCache.js +89 -0
- package/dist/scanCache.js.map +1 -0
- package/dist/verify/acceptance.d.ts +5 -1
- package/dist/verify/acceptance.js +29 -16
- package/dist/verify/acceptance.js.map +1 -1
- package/dist/verify/architecture.d.ts +5 -1
- package/dist/verify/architecture.js +6 -1
- package/dist/verify/architecture.js.map +1 -1
- package/dist/verify/baseline.d.ts +5 -1
- package/dist/verify/baseline.js +5 -1
- package/dist/verify/baseline.js.map +1 -1
- package/dist/verify/chain.js +17 -0
- package/dist/verify/chain.js.map +1 -1
- package/dist/verify/flow.d.ts +2 -0
- package/dist/verify/flow.js +18 -3
- package/dist/verify/flow.js.map +1 -1
- package/dist/verify/gateMatrix.d.ts +4 -0
- package/dist/verify/gateMatrix.js +23 -12
- package/dist/verify/gateMatrix.js.map +1 -1
- package/dist/verify/holdout.d.ts +5 -1
- package/dist/verify/holdout.js +8 -2
- package/dist/verify/holdout.js.map +1 -1
- package/dist/verify/regression.d.ts +5 -1
- package/dist/verify/regression.js +6 -1
- package/dist/verify/regression.js.map +1 -1
- package/dist/verify/stack.d.ts +5 -1
- package/dist/verify/stack.js +6 -1
- package/dist/verify/stack.js.map +1 -1
- package/dist/verify/state.d.ts +5 -1
- package/dist/verify/state.js +6 -1
- package/dist/verify/state.js.map +1 -1
- package/dist/verify/verifier.d.ts +5 -1
- package/dist/verify/verifier.js +6 -1
- package/dist/verify/verifier.js.map +1 -1
- package/docs/release-controls.md +107 -0
- package/package.json +70 -69
- package/templates/pitstop.command.md +17 -5
- package/templates/pitstop.prompt.md +390 -390
- package/scripts/__pycache__/gen-gifs.cpython-313.pyc +0 -0
- package/scripts/gen-gifs.py +0 -231
|
Binary file
|
package/scripts/gen-gifs.py
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
gen-gifs.py — render OpenPitStop CLI output into valid, animated GIFs.
|
|
4
|
-
|
|
5
|
-
For every clip it runs the real built CLI (dist/cli.js), captures the
|
|
6
|
-
ANSI-colored output, parses it into a terminal buffer, and renders an
|
|
7
|
-
animated "typing" GIF on a dark terminal background. Output lands in
|
|
8
|
-
docs/media/<name>.gif, replacing any broken previous captures.
|
|
9
|
-
|
|
10
|
-
Run: python scripts/gen-gifs.py
|
|
11
|
-
"""
|
|
12
|
-
import os, re, sys, io, shutil, subprocess, tempfile
|
|
13
|
-
from PIL import Image, ImageDraw, ImageFont
|
|
14
|
-
|
|
15
|
-
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
16
|
-
CLI = os.path.join(ROOT, "dist", "cli.js")
|
|
17
|
-
MEDIA = os.path.join(ROOT, "docs", "media")
|
|
18
|
-
FONT = r"C:\Windows\Fonts\consola.ttf"
|
|
19
|
-
|
|
20
|
-
BG = (11, 15, 26) # #0B0F1A
|
|
21
|
-
DEFAULT_FG = (230, 237, 243) # light grey
|
|
22
|
-
COLORS = {
|
|
23
|
-
30:(220, 90, 90), 31:(248, 81, 73), 32:(63, 185, 80), 33:(210, 153, 34),
|
|
24
|
-
34:(88, 166, 255), 35:(255, 123, 209), 36:(34, 211, 238), 37:(200, 210, 220),
|
|
25
|
-
90:(130, 140, 150), 91:(255, 120, 120), 92:(80, 210, 110), 93:(230, 180, 60),
|
|
26
|
-
94:(120, 190, 255), 95:(255, 150, 220), 96:(120, 220, 245), 97:(235, 240, 245),
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
# ---------------------------------------------------------------- CLI runners
|
|
30
|
-
def run_cli(args, cwd=None, extra_env=None):
|
|
31
|
-
env = dict(os.environ)
|
|
32
|
-
env["FORCE_COLOR"] = "1"
|
|
33
|
-
env["COLUMNS"] = "120"
|
|
34
|
-
if extra_env:
|
|
35
|
-
env.update(extra_env)
|
|
36
|
-
p = subprocess.run(["node", CLI] + list(args), cwd=cwd or ROOT,
|
|
37
|
-
env=env, capture_output=True, timeout=300)
|
|
38
|
-
return p.stdout.decode("utf-8", "replace") + p.stderr.decode("utf-8", "replace")
|
|
39
|
-
|
|
40
|
-
def run_shell(cmd, cwd):
|
|
41
|
-
env = dict(os.environ); env["FORCE_COLOR"] = "1"; env["COLUMNS"] = "120"
|
|
42
|
-
p = subprocess.run(cmd, cwd=cwd, env=env, shell=True,
|
|
43
|
-
capture_output=True, timeout=300)
|
|
44
|
-
return p.stdout.decode("utf-8", "replace") + p.stderr.decode("utf-8", "replace")
|
|
45
|
-
|
|
46
|
-
# ----------------------------------------------------------- ANSI -> buffer
|
|
47
|
-
def strip_osc(s):
|
|
48
|
-
out = []; i = 0; n = len(s)
|
|
49
|
-
while i < n:
|
|
50
|
-
if s[i] == "\x1b" and i + 1 < n and s[i + 1] == "]":
|
|
51
|
-
j = s.find("\x07", i + 2); k = s.find("\x1b\\", i + 2)
|
|
52
|
-
if j == -1 and k == -1:
|
|
53
|
-
break
|
|
54
|
-
i = (j if (j != -1 and (k == -1 or j < k)) else k) + (1 if k != -1 and (j == -1 or k < j) else 1)
|
|
55
|
-
if j != -1 and (k == -1 or j < k):
|
|
56
|
-
i = j + 1
|
|
57
|
-
else:
|
|
58
|
-
i = k + 2
|
|
59
|
-
continue
|
|
60
|
-
if s[i] == "\x1b" and i + 1 < n and s[i + 1] == "[":
|
|
61
|
-
if re.match(r"\x1b\[[0-9;?]*[A-Za-z]", s[i:]):
|
|
62
|
-
m = re.match(r"\x1b\[[0-9;?]*[A-Za-z]", s[i:])
|
|
63
|
-
i += m.end(); continue
|
|
64
|
-
i += 2; continue
|
|
65
|
-
out.append(s[i]); i += 1
|
|
66
|
-
return "".join(out)
|
|
67
|
-
|
|
68
|
-
def parse_ansi(text):
|
|
69
|
-
text = strip_osc(text).replace("\r\n", "\n").replace("\r", "\n")
|
|
70
|
-
lines = []
|
|
71
|
-
for line in text.split("\n"):
|
|
72
|
-
cells = []; fg = DEFAULT_FG
|
|
73
|
-
pos = 0
|
|
74
|
-
while pos < len(line):
|
|
75
|
-
if line[pos] == "\x1b" and pos + 1 < len(line) and line[pos + 1] == "[":
|
|
76
|
-
m = re.match(r"\x1b\[([0-9;]*)m", line[pos:])
|
|
77
|
-
if m:
|
|
78
|
-
codes = [int(x) for x in m.group(1).split(";") if x != ""]
|
|
79
|
-
j = 0
|
|
80
|
-
while j < len(codes):
|
|
81
|
-
c = codes[j]
|
|
82
|
-
if c == 0: fg = DEFAULT_FG
|
|
83
|
-
elif c == 1: pass
|
|
84
|
-
elif c == 2: pass
|
|
85
|
-
elif c == 4: pass
|
|
86
|
-
elif c == 22: pass
|
|
87
|
-
elif c == 39: fg = DEFAULT_FG
|
|
88
|
-
elif 30 <= c <= 37: fg = COLORS.get(c, DEFAULT_FG)
|
|
89
|
-
elif 90 <= c <= 97: fg = COLORS.get(c, DEFAULT_FG)
|
|
90
|
-
elif c in (38, 48):
|
|
91
|
-
if j + 1 < len(codes) and codes[j + 1] == 5: j += 2
|
|
92
|
-
elif j + 1 < len(codes) and codes[j + 1] == 2: j += 4
|
|
93
|
-
else: j += 1
|
|
94
|
-
j += 1
|
|
95
|
-
pos += m.end(); continue
|
|
96
|
-
pos += 1; continue
|
|
97
|
-
ch = line[pos]
|
|
98
|
-
if ch == "\t":
|
|
99
|
-
for _ in range(4): cells.append((" ", fg))
|
|
100
|
-
elif ch == "\x08":
|
|
101
|
-
if cells: cells.pop()
|
|
102
|
-
else:
|
|
103
|
-
cells.append((ch, fg))
|
|
104
|
-
pos += 1
|
|
105
|
-
lines.append(cells)
|
|
106
|
-
return lines
|
|
107
|
-
|
|
108
|
-
def slice_from(text, marker):
|
|
109
|
-
lines = parse_ansi(text)
|
|
110
|
-
for i, line in enumerate(lines):
|
|
111
|
-
if marker.lower() in "".join(c for c, _ in line).lower():
|
|
112
|
-
return lines[i:]
|
|
113
|
-
return lines
|
|
114
|
-
|
|
115
|
-
# --------------------------------------------------------------- cheat arc
|
|
116
|
-
def make_cheat_arc():
|
|
117
|
-
tmp = tempfile.mkdtemp(prefix="pitstop-gate-")
|
|
118
|
-
src = os.path.join(ROOT, "demo-repo-integrity")
|
|
119
|
-
shutil.copytree(src, tmp, ignore=shutil.ignore_patterns("node_modules"),
|
|
120
|
-
dirs_exist_ok=True)
|
|
121
|
-
run_shell('git init -q && git config user.email p@d.local && '
|
|
122
|
-
'git config user.name Demo && git add -A && git commit -q -m baseline', tmp)
|
|
123
|
-
parts = []
|
|
124
|
-
parts.append("=== OpenPitStop — the agent finally has a referee (cheat-catch demo) ===")
|
|
125
|
-
parts.append(run_cli(["scan", "."], cwd=tmp))
|
|
126
|
-
parts.append("\n--- ACT 2: a lazy 'fix' focuses the suite on passing tests -> SUSPICIOUS ---")
|
|
127
|
-
sf = os.path.join(tmp, "test", "money.test.js")
|
|
128
|
-
t = open(sf, encoding="utf-8").read()
|
|
129
|
-
t = t.replace('it("quotes base', 'fit("quotes base').replace('it("splits a bill', 'fit("splits a bill')
|
|
130
|
-
open(sf, "w", encoding="utf-8").write(t)
|
|
131
|
-
parts.append(run_cli(["gate", "."], cwd=tmp))
|
|
132
|
-
parts.append("\n--- ACT 3: the desperate 'fix' deletes the failing test -> CONFIRMED_CHEAT ---")
|
|
133
|
-
run_shell("git rm -q -f test/money.test.js", tmp)
|
|
134
|
-
parts.append(run_cli(["gate", "."], cwd=tmp))
|
|
135
|
-
shutil.rmtree(tmp, ignore_errors=True)
|
|
136
|
-
return "\n".join(parts)
|
|
137
|
-
|
|
138
|
-
def make_drive_clip():
|
|
139
|
-
try:
|
|
140
|
-
out = run_cli(["drive", "security-0d098ed0", "demo-repo"], timeout=120)
|
|
141
|
-
if out.strip():
|
|
142
|
-
return out
|
|
143
|
-
except Exception:
|
|
144
|
-
pass
|
|
145
|
-
return run_cli(["drive", "--help"])
|
|
146
|
-
|
|
147
|
-
# ------------------------------------------------------------- rendering
|
|
148
|
-
font = ImageFont.truetype(FONT, 16)
|
|
149
|
-
CW = max(int(round(font.getlength("M"))), int(round(font.getlength("─"))))
|
|
150
|
-
CH = int(round(16 * 1.45))
|
|
151
|
-
PAD = 12
|
|
152
|
-
|
|
153
|
-
def render(window, W, ROWS, cursor=False):
|
|
154
|
-
img = Image.new("RGB", (PAD * 2 + W * CW, PAD * 2 + ROWS * CH), BG)
|
|
155
|
-
d = ImageDraw.Draw(img)
|
|
156
|
-
for y, line in enumerate(window):
|
|
157
|
-
run = ""; run_color = None; rx = PAD
|
|
158
|
-
def flush():
|
|
159
|
-
nonlocal run, run_color, rx
|
|
160
|
-
if run:
|
|
161
|
-
d.text((rx, PAD + y * CH), run, fill=run_color, font=font)
|
|
162
|
-
rx += len(run) * CW
|
|
163
|
-
run = ""
|
|
164
|
-
for ch, col in line:
|
|
165
|
-
if col == run_color:
|
|
166
|
-
run += ch
|
|
167
|
-
else:
|
|
168
|
-
flush(); run_color = col; run = ch
|
|
169
|
-
flush()
|
|
170
|
-
if cursor and y == len(window) - 1:
|
|
171
|
-
d.rectangle([PAD + len(line) * CW, PAD + y * CH,
|
|
172
|
-
PAD + len(line) * CW + CW, PAD + y * CH + CH],
|
|
173
|
-
fill=(34, 211, 238))
|
|
174
|
-
return img
|
|
175
|
-
|
|
176
|
-
def make_frames(lines, V=42):
|
|
177
|
-
H = len(lines)
|
|
178
|
-
W = min(max((len(l) for l in lines), default=0), 120)
|
|
179
|
-
clipped = [l[:W] for l in lines]
|
|
180
|
-
ROWS = min(H, V)
|
|
181
|
-
step = max(1, (H + 23) // 24)
|
|
182
|
-
frames = []; revealed = 0
|
|
183
|
-
while revealed < H:
|
|
184
|
-
revealed = min(H, revealed + step)
|
|
185
|
-
window = clipped[:revealed] if revealed <= ROWS else clipped[revealed - ROWS:revealed]
|
|
186
|
-
frames.append(render(window, W, ROWS))
|
|
187
|
-
for b in range(6):
|
|
188
|
-
window = clipped[max(0, H - ROWS):H]
|
|
189
|
-
frames.append(render(window, W, ROWS, cursor=(b % 2 == 0)))
|
|
190
|
-
return frames
|
|
191
|
-
|
|
192
|
-
def save_gif(frames, path, duration=80):
|
|
193
|
-
q = [im.quantize(colors=48, dither=Image.NONE) for im in frames]
|
|
194
|
-
q[0].save(path, save_all=True, append_images=q[1:],
|
|
195
|
-
duration=duration, loop=0, disposal=2)
|
|
196
|
-
|
|
197
|
-
# --------------------------------------------------------------- clips
|
|
198
|
-
CLIPS = [
|
|
199
|
-
("pitstop-try", lambda: run_cli(["try", "demo-repo"]), None),
|
|
200
|
-
("pitstop-scan", lambda: run_cli(["scan", "demo-repo"]), None),
|
|
201
|
-
("pitstop-security", lambda: run_cli(["scan", "demo-repo"]), "Security fixes"),
|
|
202
|
-
("pitstop-gate", make_cheat_arc, None),
|
|
203
|
-
("pitstop-integrity", lambda: run_cli(["integrity", "demo-repo"]), None),
|
|
204
|
-
("pitstop-pen", lambda: run_cli(["pen", "demo-repo"]), None),
|
|
205
|
-
("pitstop-pyramid", lambda: run_cli(["test", "demo-repo"]), None),
|
|
206
|
-
("pitstop-honesty", lambda: run_cli(["honesty"]), None),
|
|
207
|
-
("pitstop-next", lambda: run_cli(["next", "demo-repo"]), None),
|
|
208
|
-
("pitstop-drive", make_drive_clip, None),
|
|
209
|
-
("pitstop-semgrep", lambda: run_cli(["scan", "demo-repo"],
|
|
210
|
-
extra_env={"PITSTOP_SEMGREP_CONFIG": "auto"}),
|
|
211
|
-
None),
|
|
212
|
-
]
|
|
213
|
-
|
|
214
|
-
def main():
|
|
215
|
-
os.makedirs(MEDIA, exist_ok=True)
|
|
216
|
-
for name, func, section in CLIPS:
|
|
217
|
-
sys.stdout.write(f"rendering {name}.gif ... ")
|
|
218
|
-
sys.stdout.flush()
|
|
219
|
-
raw = func()
|
|
220
|
-
lines = slice_from(raw, section) if section else parse_ansi(raw)
|
|
221
|
-
if not lines:
|
|
222
|
-
lines = parse_ansi("(no output)")
|
|
223
|
-
sys.stdout.write(f"({len(lines)} lines) "); sys.stdout.flush()
|
|
224
|
-
frames = make_frames(lines)
|
|
225
|
-
out = os.path.join(MEDIA, name + ".gif")
|
|
226
|
-
save_gif(frames, out)
|
|
227
|
-
im = Image.open(out)
|
|
228
|
-
print(f"ok {os.path.getsize(out)//1024} KB {im.size[0]}x{im.size[1]} {getattr(im,'n_frames',1)} frames")
|
|
229
|
-
|
|
230
|
-
if __name__ == "__main__":
|
|
231
|
-
main()
|