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.
Files changed (105) hide show
  1. package/PRIVACY.md +77 -90
  2. package/README.md +365 -887
  3. package/dist/agentBudget.d.ts +33 -0
  4. package/dist/agentBudget.js +165 -0
  5. package/dist/agentBudget.js.map +1 -0
  6. package/dist/analyzers/ledger/index.d.ts +2 -1
  7. package/dist/analyzers/ledger/index.js +10 -1
  8. package/dist/analyzers/ledger/index.js.map +1 -1
  9. package/dist/analyzers/types.d.ts +2 -0
  10. package/dist/candidate.d.ts +20 -0
  11. package/dist/candidate.js +87 -0
  12. package/dist/candidate.js.map +1 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/cli.js.map +1 -1
  15. package/dist/commands/ask.d.ts +0 -8
  16. package/dist/commands/ask.js +36 -34
  17. package/dist/commands/ask.js.map +1 -1
  18. package/dist/commands/budget.d.ts +1 -0
  19. package/dist/commands/budget.js +12 -3
  20. package/dist/commands/budget.js.map +1 -1
  21. package/dist/commands/drive.js +77 -51
  22. package/dist/commands/drive.js.map +1 -1
  23. package/dist/commands/fix.d.ts +7 -0
  24. package/dist/commands/fix.js +90 -108
  25. package/dist/commands/fix.js.map +1 -1
  26. package/dist/commands/gate.js +6 -2
  27. package/dist/commands/gate.js.map +1 -1
  28. package/dist/commands/pen.d.ts +3 -1
  29. package/dist/commands/pen.js +31 -42
  30. package/dist/commands/pen.js.map +1 -1
  31. package/dist/commands/readyCheck.d.ts +1 -1
  32. package/dist/commands/readyCheck.js +6 -36
  33. package/dist/commands/readyCheck.js.map +1 -1
  34. package/dist/commands/scan.d.ts +6 -5
  35. package/dist/commands/scan.js +26 -28
  36. package/dist/commands/scan.js.map +1 -1
  37. package/dist/commands/verify.d.ts +3 -1
  38. package/dist/commands/verify.js +5 -1
  39. package/dist/commands/verify.js.map +1 -1
  40. package/dist/evidence.js +4 -0
  41. package/dist/evidence.js.map +1 -1
  42. package/dist/installer/targets.js +1 -1
  43. package/dist/installer/targets.js.map +1 -1
  44. package/dist/intent.d.ts +4 -7
  45. package/dist/intent.js +41 -77
  46. package/dist/intent.js.map +1 -1
  47. package/dist/pen/dynamic.d.ts +1 -24
  48. package/dist/pen/dynamic.js +10 -1
  49. package/dist/pen/dynamic.js.map +1 -1
  50. package/dist/pen/fix.js +5 -1
  51. package/dist/pen/fix.js.map +1 -1
  52. package/dist/repro/generate.js +1 -0
  53. package/dist/repro/generate.js.map +1 -1
  54. package/dist/repro/pen.js +1 -0
  55. package/dist/repro/pen.js.map +1 -1
  56. package/dist/repro/run.d.ts +1 -0
  57. package/dist/repro/run.js +17 -1
  58. package/dist/repro/run.js.map +1 -1
  59. package/dist/sandbox/container.d.ts +9 -0
  60. package/dist/sandbox/container.js +129 -0
  61. package/dist/sandbox/container.js.map +1 -0
  62. package/dist/sandbox/worker.d.ts +1 -0
  63. package/dist/sandbox/worker.js +41 -0
  64. package/dist/sandbox/worker.js.map +1 -0
  65. package/dist/scanCache.d.ts +27 -0
  66. package/dist/scanCache.js +89 -0
  67. package/dist/scanCache.js.map +1 -0
  68. package/dist/verify/acceptance.d.ts +5 -1
  69. package/dist/verify/acceptance.js +29 -16
  70. package/dist/verify/acceptance.js.map +1 -1
  71. package/dist/verify/architecture.d.ts +5 -1
  72. package/dist/verify/architecture.js +6 -1
  73. package/dist/verify/architecture.js.map +1 -1
  74. package/dist/verify/baseline.d.ts +5 -1
  75. package/dist/verify/baseline.js +5 -1
  76. package/dist/verify/baseline.js.map +1 -1
  77. package/dist/verify/chain.js +17 -0
  78. package/dist/verify/chain.js.map +1 -1
  79. package/dist/verify/flow.d.ts +2 -0
  80. package/dist/verify/flow.js +18 -3
  81. package/dist/verify/flow.js.map +1 -1
  82. package/dist/verify/gateMatrix.d.ts +4 -0
  83. package/dist/verify/gateMatrix.js +23 -12
  84. package/dist/verify/gateMatrix.js.map +1 -1
  85. package/dist/verify/holdout.d.ts +5 -1
  86. package/dist/verify/holdout.js +8 -2
  87. package/dist/verify/holdout.js.map +1 -1
  88. package/dist/verify/regression.d.ts +5 -1
  89. package/dist/verify/regression.js +6 -1
  90. package/dist/verify/regression.js.map +1 -1
  91. package/dist/verify/stack.d.ts +5 -1
  92. package/dist/verify/stack.js +6 -1
  93. package/dist/verify/stack.js.map +1 -1
  94. package/dist/verify/state.d.ts +5 -1
  95. package/dist/verify/state.js +6 -1
  96. package/dist/verify/state.js.map +1 -1
  97. package/dist/verify/verifier.d.ts +5 -1
  98. package/dist/verify/verifier.js +6 -1
  99. package/dist/verify/verifier.js.map +1 -1
  100. package/docs/release-controls.md +107 -0
  101. package/package.json +70 -69
  102. package/templates/pitstop.command.md +17 -5
  103. package/templates/pitstop.prompt.md +390 -390
  104. package/scripts/__pycache__/gen-gifs.cpython-313.pyc +0 -0
  105. package/scripts/gen-gifs.py +0 -231
@@ -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()