agent-petri-dish 0.1.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.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +36 -0
- data/LICENSE +21 -0
- data/README.md +145 -0
- data/bin/petri-dish +6 -0
- data/hooks/event-logger.sh +19 -0
- data/hooks/permission-handler.sh +35 -0
- data/lib/petri-dish.rb +3 -0
- data/lib/petri_dish/cli.rb +214 -0
- data/lib/petri_dish/config.rb +75 -0
- data/lib/petri_dish/environment.rb +146 -0
- data/lib/petri_dish/hook_log.rb +174 -0
- data/lib/petri_dish/preambles/escape-hatch.md +27 -0
- data/lib/petri_dish/preambles/guidance.md +14 -0
- data/lib/petri_dish/preambles/permissions.md +25 -0
- data/lib/petri_dish/preambles/sandbox.md +28 -0
- data/lib/petri_dish/results_builder.rb +127 -0
- data/lib/petri_dish/runner.rb +330 -0
- data/lib/petri_dish/transcript.rb +37 -0
- data/lib/petri_dish/version.rb +5 -0
- data/lib/petri_dish.rb +19 -0
- data/scripts/analyze-hooks.py +114 -0
- data/scripts/hook-block-pattern.sh +49 -0
- data/scripts/hook-logger.sh +18 -0
- data/scripts/inspect-session.sh +51 -0
- data/scripts/migrate-configs.rb +73 -0
- data/scripts/migrate-prompts.rb +75 -0
- metadata +70 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Migrates test configs from v1 (sidecar) to v2 (hooks) format.
|
|
5
|
+
# Uses text manipulation rather than YAML.dump to preserve formatting.
|
|
6
|
+
|
|
7
|
+
require "yaml"
|
|
8
|
+
|
|
9
|
+
TESTS_DIR = File.join(__dir__, "..", "tests")
|
|
10
|
+
|
|
11
|
+
Dir.glob("#{TESTS_DIR}/*/config.yml").sort.each do |path|
|
|
12
|
+
test_name = File.basename(File.dirname(path))
|
|
13
|
+
text = File.read(path)
|
|
14
|
+
|
|
15
|
+
# Skip if already migrated (has prompt_mode, no sidecar)
|
|
16
|
+
unless text.include?("sidecar:")
|
|
17
|
+
puts "Skip (no sidecar): #{test_name}"
|
|
18
|
+
next
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
data = YAML.safe_load(text)
|
|
22
|
+
sidecar = data["sidecar"] || {}
|
|
23
|
+
mode = sidecar["mode"] || "accept"
|
|
24
|
+
timeout = sidecar["timeout"] || 300
|
|
25
|
+
|
|
26
|
+
# 1. Remove the sidecar block (and trailing newline)
|
|
27
|
+
text.gsub!(/\nsidecar:\n(?: .+\n)*/, "\n")
|
|
28
|
+
|
|
29
|
+
# 2. Add prompt_mode at the end
|
|
30
|
+
text.rstrip!
|
|
31
|
+
text << "\n\nprompt_mode: #{mode}\n"
|
|
32
|
+
|
|
33
|
+
# 3. Add timeout to runtime if not already there
|
|
34
|
+
unless text.match?(/^ timeout:/)
|
|
35
|
+
# Insert timeout as last line of runtime block
|
|
36
|
+
text.sub!(/^(runtime:\n(?: .+\n)*)/) do
|
|
37
|
+
"#{$1} timeout: #{timeout}\n"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 4. Move permissions array into settings.permissions.allow
|
|
42
|
+
env = data["environment"] || {}
|
|
43
|
+
if env["permissions"].is_a?(Array)
|
|
44
|
+
perms = env["permissions"]
|
|
45
|
+
if perms.empty?
|
|
46
|
+
# Just remove empty permissions
|
|
47
|
+
text.sub!(/^ permissions: \[\]\n/, "")
|
|
48
|
+
else
|
|
49
|
+
# Build the settings.permissions.allow block
|
|
50
|
+
perms_yaml = perms.map { |p| " - #{p}" }.join("\n")
|
|
51
|
+
|
|
52
|
+
# Remove the old permissions block
|
|
53
|
+
text.sub!(/^ permissions:\n(?: - .+\n)*/, "")
|
|
54
|
+
|
|
55
|
+
# Add permissions into settings
|
|
56
|
+
if text.match?(/^ sandbox:/)
|
|
57
|
+
# Insert after settings block content
|
|
58
|
+
text.sub!(/^( settings:\n(?: .+\n(?: .+\n)*)*)/) do
|
|
59
|
+
"#{$1} permissions:\n allow:\n#{perms_yaml}\n"
|
|
60
|
+
end
|
|
61
|
+
elsif text.match?(/^ settings: \{\}/)
|
|
62
|
+
text.sub!(" settings: {}", " settings:\n permissions:\n allow:\n#{perms_yaml}")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 5. Update preamble path
|
|
68
|
+
text.gsub!("lib/preamble-sandbox-lean.md", "lib/preambles/sandbox.md")
|
|
69
|
+
text.gsub!("lib/preamble-sandbox.md", "lib/preambles/sandbox.md")
|
|
70
|
+
|
|
71
|
+
File.write(path, text)
|
|
72
|
+
puts "Migrated: #{test_name}"
|
|
73
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Migrates prompt.md files from v1 (prompt-check protocol) to v2 (hook-driven).
|
|
5
|
+
#
|
|
6
|
+
# Changes:
|
|
7
|
+
# 1. Replace Protocol sections that mention "Prompt check" with simplified version
|
|
8
|
+
# 2. Replace "After All Tests" sections with SIGNAL_FILE instruction
|
|
9
|
+
# 3. Add "After All Tests" + SIGNAL_FILE if missing
|
|
10
|
+
|
|
11
|
+
TESTS_DIR = File.join(__dir__, "..", "tests")
|
|
12
|
+
|
|
13
|
+
SIMPLE_PROTOCOL = <<~PROTOCOL.rstrip
|
|
14
|
+
## Protocol
|
|
15
|
+
|
|
16
|
+
Follow the test protocol from the preamble. Run each command, report the result, output a RESULT line, move to the next.
|
|
17
|
+
PROTOCOL
|
|
18
|
+
|
|
19
|
+
SIMPLE_AFTER = <<~AFTER.rstrip
|
|
20
|
+
## After All Tests
|
|
21
|
+
|
|
22
|
+
Write "done" to the SIGNAL_FILE.
|
|
23
|
+
AFTER
|
|
24
|
+
|
|
25
|
+
Dir.glob("#{TESTS_DIR}/*/prompt.md").sort.each do |path|
|
|
26
|
+
test_name = File.basename(File.dirname(path))
|
|
27
|
+
text = File.read(path)
|
|
28
|
+
changed = false
|
|
29
|
+
|
|
30
|
+
# Skip if already has SIGNAL_FILE (already v2)
|
|
31
|
+
if text.include?("SIGNAL_FILE") && !text.include?("Prompt check") && !text.include?("prompt check")
|
|
32
|
+
puts "Skip (already v2): #{test_name}"
|
|
33
|
+
next
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 1. Replace Protocol section that contains "Prompt check"
|
|
37
|
+
# Match from "## Protocol" to the next "##" heading or "## Test" heading
|
|
38
|
+
if text =~ /^## Protocol\n/
|
|
39
|
+
# Find the protocol section boundaries
|
|
40
|
+
proto_start = text.index("## Protocol\n")
|
|
41
|
+
# Find the next ## heading after the protocol
|
|
42
|
+
rest_after_proto = text[proto_start + "## Protocol\n".length..]
|
|
43
|
+
next_heading = rest_after_proto.index(/^## /)
|
|
44
|
+
|
|
45
|
+
if next_heading
|
|
46
|
+
old_protocol = text[proto_start, "## Protocol\n".length + next_heading]
|
|
47
|
+
if old_protocol.include?("Prompt check") || old_protocol.include?("prompt check")
|
|
48
|
+
text = text.sub(old_protocol, SIMPLE_PROTOCOL + "\n\n")
|
|
49
|
+
changed = true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# 2. Replace "After All Tests" section
|
|
55
|
+
if text =~ /^## After All Tests\n/
|
|
56
|
+
after_start = text.index("## After All Tests\n")
|
|
57
|
+
old_after = text[after_start..]
|
|
58
|
+
|
|
59
|
+
unless old_after.include?("SIGNAL_FILE")
|
|
60
|
+
text = text[0...after_start] + SIMPLE_AFTER + "\n"
|
|
61
|
+
changed = true
|
|
62
|
+
end
|
|
63
|
+
elsif !text.include?("SIGNAL_FILE")
|
|
64
|
+
# 3. Add After All Tests if missing entirely
|
|
65
|
+
text = text.rstrip + "\n\n" + SIMPLE_AFTER + "\n"
|
|
66
|
+
changed = true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if changed
|
|
70
|
+
File.write(path, text)
|
|
71
|
+
puts "Migrated: #{test_name}"
|
|
72
|
+
else
|
|
73
|
+
puts "Skip (no changes needed): #{test_name}"
|
|
74
|
+
end
|
|
75
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: agent-petri-dish
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Josh Nichols
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: petri-dish runs Claude Code sessions inside isolated cenv environments,
|
|
13
|
+
captures hook events, and correlates them into structured results. Your ~/.claude
|
|
14
|
+
is production; the dish is where you do science.
|
|
15
|
+
email:
|
|
16
|
+
- josh.nichols@gmail.com
|
|
17
|
+
executables:
|
|
18
|
+
- petri-dish
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- CONTRIBUTING.md
|
|
23
|
+
- LICENSE
|
|
24
|
+
- README.md
|
|
25
|
+
- bin/petri-dish
|
|
26
|
+
- hooks/event-logger.sh
|
|
27
|
+
- hooks/permission-handler.sh
|
|
28
|
+
- lib/petri-dish.rb
|
|
29
|
+
- lib/petri_dish.rb
|
|
30
|
+
- lib/petri_dish/cli.rb
|
|
31
|
+
- lib/petri_dish/config.rb
|
|
32
|
+
- lib/petri_dish/environment.rb
|
|
33
|
+
- lib/petri_dish/hook_log.rb
|
|
34
|
+
- lib/petri_dish/preambles/escape-hatch.md
|
|
35
|
+
- lib/petri_dish/preambles/guidance.md
|
|
36
|
+
- lib/petri_dish/preambles/permissions.md
|
|
37
|
+
- lib/petri_dish/preambles/sandbox.md
|
|
38
|
+
- lib/petri_dish/results_builder.rb
|
|
39
|
+
- lib/petri_dish/runner.rb
|
|
40
|
+
- lib/petri_dish/transcript.rb
|
|
41
|
+
- lib/petri_dish/version.rb
|
|
42
|
+
- scripts/analyze-hooks.py
|
|
43
|
+
- scripts/hook-block-pattern.sh
|
|
44
|
+
- scripts/hook-logger.sh
|
|
45
|
+
- scripts/inspect-session.sh
|
|
46
|
+
- scripts/migrate-configs.rb
|
|
47
|
+
- scripts/migrate-prompts.rb
|
|
48
|
+
homepage: https://github.com/technicalpickles/petri-dish
|
|
49
|
+
licenses:
|
|
50
|
+
- MIT
|
|
51
|
+
metadata:
|
|
52
|
+
homepage_uri: https://github.com/technicalpickles/petri-dish
|
|
53
|
+
rdoc_options: []
|
|
54
|
+
require_paths:
|
|
55
|
+
- lib
|
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.2'
|
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '0'
|
|
66
|
+
requirements: []
|
|
67
|
+
rubygems_version: 4.0.6
|
|
68
|
+
specification_version: 4
|
|
69
|
+
summary: Isolated, repeatable experiments against agentic coding tools.
|
|
70
|
+
test_files: []
|