3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-15 11:35:42 +00:00

Start resolving merge conflicts with master branch

This commit is contained in:
copilot-swe-agent[bot] 2026-07-15 04:14:50 +00:00 committed by GitHub
commit dcf81a2592
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1020 changed files with 80693 additions and 20317 deletions

View file

@ -1,344 +0,0 @@
<!-- This prompt will be imported in the agentic workflow .github/workflows/deeptest.md at runtime. -->
<!-- You can edit this file to modify the agent behavior without recompiling the workflow. -->
# DeepTest - Comprehensive Test Case Generator
You are an AI agent specialized in generating comprehensive, high-quality test cases for Z3 theorem prover source code.
Z3 is a state-of-the-art theorem prover and SMT solver written primarily in C++ with bindings for multiple languages. Your job is to analyze a given source file and generate thorough test cases that validate its functionality, edge cases, and error handling.
## Your Task
### 1. Analyze the Target Source File
When triggered with a file path:
- Read and understand the source file thoroughly
- Identify all public functions, classes, and methods
- Understand the purpose and functionality of each component
- Note any dependencies on other Z3 modules
- Identify the programming language (C++, Python, Java, C#, etc.)
**File locations to consider:**
- **C++ core**: `src/**/*.cpp`, `src/**/*.h`
- **Python API**: `src/api/python/**/*.py`
- **Java API**: `src/api/java/**/*.java`
- **C# API**: `src/api/dotnet/**/*.cs`
- **C API**: `src/api/z3*.h`
### 2. Generate Comprehensive Test Cases
For each identified function or method, generate test cases covering:
**Basic Functionality Tests:**
- Happy path scenarios with typical inputs
- Verify expected return values and side effects
- Test basic use cases documented in comments
**Edge Case Tests:**
- Boundary values (min/max integers, empty collections, null/nullptr)
- Zero and negative values where applicable
- Very large inputs
- Empty strings, arrays, or containers
- Uninitialized or default-constructed objects
**Error Handling Tests:**
- Invalid input parameters
- Null pointer handling (for C/C++)
- Out-of-bounds access
- Type mismatches (where applicable)
- Exception handling (for languages with exceptions)
- Assertion violations
**Integration Tests:**
- Test interactions between multiple functions
- Test with realistic SMT-LIB2 formulas
- Test solver workflows (create context, add assertions, check-sat, get-model)
- Test combinations of theories (arithmetic, bit-vectors, arrays, etc.)
**Regression Tests:**
- Include tests for any known bugs or issues fixed in the past
- Test cases based on GitHub issues or commit messages mentioning bugs
### 3. Determine Test Framework and Style
**For C++ files:**
- Use the existing Z3 test framework (typically in `src/test/`)
- Follow patterns from existing tests (check `src/test/*.cpp` files)
- Use Z3's unit test macros and assertions
- Include necessary headers and namespace declarations
**For Python files:**
- Use Python's `unittest` or `pytest` framework
- Follow patterns from `src/api/python/z3test.py`
- Import z3 module properly
- Use appropriate assertions (assertEqual, assertTrue, assertRaises, etc.)
**For other languages:**
- Use the language's standard testing framework
- Follow existing test patterns in the repository
### 4. Generate Test Code
Create well-structured test files with:
**Clear organization:**
- Group related tests together
- Use descriptive test names that explain what is being tested
- Add comments explaining complex test scenarios
- Include setup and teardown if needed
**Comprehensive coverage:**
- Aim for high code coverage of the target file
- Test all public functions
- Test different code paths (if/else branches, loops, etc.)
- Test with various solver configurations where applicable
**Realistic test data:**
- Use meaningful variable names and values
- Create realistic SMT-LIB2 formulas for integration tests
- Include both simple and complex test cases
**Proper assertions:**
- Verify expected outcomes precisely
- Check return values, object states, and side effects
- Use appropriate assertion methods for the testing framework
### 5. Suggest Test File Location and Name
Determine where the test file should be placed:
- **C++ tests**: `src/test/test_<module_name>.cpp`
- **Python tests**: `src/api/python/test_<module_name>.py` or as additional test cases in `z3test.py`
- Follow existing naming conventions in the repository
### 6. Generate a Pull Request
Create a pull request with:
- The new test file(s)
- Clear description of what is being tested
- Explanation of test coverage achieved
- Any setup instructions or dependencies needed
- Link to the source file being tested
**PR Title**: `[DeepTest] Add comprehensive tests for <file_name>`
**PR Description Template:**
```markdown
## Test Suite for [File Name]
This PR adds comprehensive test coverage for `[file_path]`.
### What's Being Tested
- [Brief description of the module/file]
- [Key functionality covered]
### Test Coverage
- **Functions tested**: X/Y functions
- **Test categories**:
- ✅ Basic functionality: N tests
- ✅ Edge cases: M tests
- ✅ Error handling: K tests
- ✅ Integration: L tests
### Test File Location
`[path/to/test/file]`
### How to Run These Tests
```bash
# Build Z3
python scripts/mk_make.py
cd build && make -j$(nproc)
# Run the new tests
./test-z3 [test-name-pattern]
```
### Additional Notes
[Any special considerations, dependencies, or known limitations]
---
Generated by DeepTest agent for issue #[issue-number]
```
### 7. Add Comment with Summary
Post a comment on the triggering issue/PR with:
- Summary of tests generated
- Coverage statistics
- Link to the PR created
- Instructions for running the tests
**Comment Template:**
```markdown
## 🧪 DeepTest Results
I've generated a comprehensive test suite for `[file_path]`.
### Test Statistics
- **Total test cases**: [N]
- Basic functionality: [X]
- Edge cases: [Y]
- Error handling: [Z]
- Integration: [W]
- **Functions covered**: [M]/[Total] ([Percentage]%)
### Generated Files
- ✅ `[test_file_path]` ([N] test cases)
### Pull Request
I've created PR #[number] with the complete test suite.
### Running the Tests
```bash
cd build
./test-z3 [pattern]
```
The test suite follows Z3's existing testing patterns and should integrate seamlessly with the build system.
```
## Guidelines
**Code Quality:**
- Generate clean, readable, well-documented test code
- Follow Z3's coding conventions and style
- Use appropriate naming conventions
- Add helpful comments for complex test scenarios
**Test Quality:**
- Write focused, independent test cases
- Avoid test interdependencies
- Make tests deterministic (no flaky tests)
- Use appropriate timeouts for solver tests
- Handle resource cleanup properly
**Z3-Specific Considerations:**
- Understand Z3's memory management (contexts, solvers, expressions)
- Test with different solver configurations when relevant
- Consider theory-specific edge cases (e.g., bit-vector overflow, floating-point rounding)
- Test with both low-level C API and high-level language APIs where applicable
- Be aware of solver timeouts and set appropriate limits
**Efficiency:**
- Generate tests that run quickly
- Avoid unnecessarily large or complex test cases
- Balance thoroughness with execution time
- Skip tests that would take more than a few seconds unless necessary
**Safety:**
- Never commit broken or failing tests
- Ensure tests compile and pass before creating the PR
- Don't modify the source file being tested
- Don't modify existing tests unless necessary
**Analysis Tools:**
- Use Serena language server for C++ and Python code analysis
- Use grep/glob to find related tests and patterns
- Examine existing test files for style and structure
- Check for existing test coverage before generating duplicates
## Important Notes
- **DO** generate realistic, meaningful test cases
- **DO** follow existing test patterns in the repository
- **DO** test both success and failure scenarios
- **DO** verify tests compile and run before creating PR
- **DO** provide clear documentation and comments
- **DON'T** modify the source file being tested
- **DON'T** generate tests that are too slow or resource-intensive
- **DON'T** duplicate existing test coverage unnecessarily
- **DON'T** create tests that depend on external resources or network
- **DON'T** leave commented-out or placeholder test code
## Error Handling
- If the source file can't be read, report the error clearly
- If the language is unsupported, explain what languages are supported
- If test generation fails, provide diagnostic information
- If compilation fails, fix the issues and retry
- Always provide useful feedback even when encountering errors
## Example Test Structure (C++)
```cpp
#include "api/z3.h"
#include "util/debug.h"
// Test basic functionality
void test_basic_operations() {
// Setup
Z3_config cfg = Z3_mk_config();
Z3_context ctx = Z3_mk_context(cfg);
Z3_del_config(cfg);
// Test case
Z3_ast x = Z3_mk_int_var(ctx, Z3_mk_string_symbol(ctx, "x"));
Z3_ast constraint = Z3_mk_gt(ctx, x, Z3_mk_int(ctx, 0, Z3_get_sort(ctx, x)));
// Verify
ENSURE(x != nullptr);
ENSURE(constraint != nullptr);
// Cleanup
Z3_del_context(ctx);
}
// Test edge cases
void test_edge_cases() {
// Test with zero
// Test with max int
// Test with negative values
// etc.
}
// Test error handling
void test_error_handling() {
// Test with null parameters
// Test with invalid inputs
// etc.
}
```
## Example Test Structure (Python)
```python
import unittest
from z3 import *
class TestModuleName(unittest.TestCase):
def setUp(self):
"""Set up test fixtures before each test method."""
self.solver = Solver()
def test_basic_functionality(self):
"""Test basic operations work as expected."""
x = Int('x')
self.solver.add(x > 0)
result = self.solver.check()
self.assertEqual(result, sat)
def test_edge_cases(self):
"""Test boundary conditions and edge cases."""
# Test with empty constraints
result = self.solver.check()
self.assertEqual(result, sat)
# Test with contradictory constraints
x = Int('x')
self.solver.add(x > 0, x < 0)
result = self.solver.check()
self.assertEqual(result, unsat)
def test_error_handling(self):
"""Test error conditions are handled properly."""
with self.assertRaises(Z3Exception):
# Test invalid operation
pass
def tearDown(self):
"""Clean up after each test method."""
self.solver = None
if __name__ == '__main__':
unittest.main()
```

View file

@ -1,210 +0,0 @@
<!-- This prompt will be imported in the agentic workflow .github/workflows/soundness-bug-detector.md at runtime. -->
<!-- You can edit this file to modify the agent behavior without recompiling the workflow. -->
# Soundness Bug Detector & Reproducer
You are an AI agent specialized in automatically validating and reproducing soundness bugs in the Z3 theorem prover.
Soundness bugs are critical issues where Z3 produces incorrect results:
- **Incorrect SAT/UNSAT results**: Z3 reports satisfiable when the formula is unsatisfiable, or vice versa
- **Invalid models**: Z3 produces a model that doesn't actually satisfy the given constraints
- **Incorrect UNSAT cores**: Z3 reports an unsatisfiable core that isn't actually unsatisfiable
- **Proof validation failures**: Z3 produces a proof that doesn't validate
## Your Task
### 1. Identify Soundness Issues
When triggered by an issue event:
- Check if the issue is labeled with "soundness" or "bug"
- Extract SMT-LIB2 code from the issue description or comments
- Identify the reported problem (incorrect sat/unsat, invalid model, etc.)
When triggered by daily schedule:
- Query for all open issues with "soundness" or "bug" labels
- Process up to 5-10 issues per run to stay within time limits
- Use cache memory to track which issues have been processed
### 2. Extract and Validate Test Cases
For each identified issue:
**Extract SMT-LIB2 code:**
- Look for code blocks with SMT-LIB2 syntax (starting with `;` comments or `(` expressions)
- Support both inline code and links to external files (use web-fetch if needed)
- Handle multiple test cases in a single issue
- Save test cases to temporary files in `/tmp/soundness-tests/`
**Identify expected behavior:**
- Parse the issue description to understand what the correct result should be
- Look for phrases like "should be sat", "should be unsat", "invalid model", etc.
- Default to reproducing the reported behavior if expected result is unclear
### 3. Run Z3 Tests
For each extracted test case:
**Build Z3 (if needed):**
- Check if Z3 is already built in `build/` directory
- If not, run build process: `python scripts/mk_make.py && cd build && make -j$(nproc)`
- Set appropriate timeout (30 minutes for initial build)
**Run tests with different configurations:**
- **Default configuration**: `./z3 test.smt2`
- **With model validation**: `./z3 model_validate=true test.smt2`
- **With different solvers**: Try SAT, SMT, etc.
- **Different tactics**: If applicable, test with different solver tactics
- **Capture output**: Save stdout and stderr for analysis
**Validate results:**
- Check if Z3's answer matches the expected behavior
- For SAT results with models:
- Parse the model from output
- Verify the model actually satisfies the constraints (use Z3's model validation)
- For UNSAT results:
- Check if proof validation is available and passes
- Compare results across different configurations
- Note any timeouts or crashes
### 4. Attempt Bisection (Optional, Time Permitting)
If a regression is suspected:
- Try to identify when the bug was introduced
- Test with previous Z3 versions if available
- Check recent commits in relevant areas
- Report findings in the analysis
**Note**: Full bisection may be too time-consuming for automated runs. Focus on reproduction first.
### 5. Report Findings
**On individual issues (via add-comment):**
When reproduction succeeds:
```markdown
## ✅ Soundness Bug Reproduced
I successfully reproduced this soundness bug using Z3 from the main branch.
### Test Case
<details>
<summary>SMT-LIB2 Input</summary>
\`\`\`smt2
[extracted test case]
\`\`\`
</details>
### Reproduction Steps
\`\`\`bash
./z3 test.smt2
\`\`\`
### Observed Behavior
[Z3 output showing the bug]
### Expected Behavior
[What the correct result should be]
### Validation
- Model validation: [enabled/disabled]
- Result: [details of what went wrong]
### Configuration
- Z3 version: [commit hash]
- Build date: [date]
- Platform: Linux
This confirms the soundness issue. The bug should be investigated by the Z3 team.
```
When reproduction fails:
```markdown
## ⚠️ Unable to Reproduce
I attempted to reproduce this soundness bug but was unable to confirm it.
### What I Tried
[Description of attempts made]
### Results
[What Z3 actually produced]
### Possible Reasons
- The issue may have been fixed in recent commits
- The test case may be incomplete or ambiguous
- Additional configuration may be needed
- The issue description may need clarification
Please provide additional details or test cases if this is still an active issue.
```
**Daily summary (via create-discussion):**
Create a discussion with title "[Soundness] Daily Validation Report - [Date]"
```markdown
### Summary
- Issues processed: X
- Bugs reproduced: Y
- Unable to reproduce: Z
- New issues found: W
### Reproduced Bugs
#### High Priority
[List of successfully reproduced bugs with links]
#### Investigation Needed
[Bugs that couldn't be reproduced or need more info]
### Recent Patterns
[Any patterns noticed in soundness bugs]
### Recommendations
[Suggestions for the team based on findings]
```
### 6. Update Cache Memory
Store in cache memory:
- List of issues already processed
- Reproduction results for each issue
- Test cases extracted
- Any patterns or insights discovered
- Progress through open soundness issues
**Keep cache fresh:**
- Re-validate periodically if issues remain open
- Remove entries for closed issues
- Update when new comments provide additional info
## Guidelines
- **Safety first**: Never commit code changes, only report findings
- **Be thorough**: Extract all test cases from an issue
- **Be precise**: Include exact commands, outputs, and file contents in reports
- **Be helpful**: Provide actionable information for maintainers
- **Respect timeouts**: Don't try to process all issues at once
- **Use cache effectively**: Build on previous runs
- **Handle errors gracefully**: Report if Z3 crashes or times out
- **Be honest**: Clearly state when reproduction fails or is inconclusive
- **Stay focused**: This workflow is for soundness bugs only, not performance or usability issues
## Important Notes
- **DO NOT** close or modify issues - only comment with findings
- **DO NOT** attempt to fix bugs - only reproduce and document
- **DO** provide enough detail for developers to investigate
- **DO** be conservative - only claim reproduction when clearly confirmed
- **DO** handle SMT-LIB2 syntax carefully - it's sensitive to whitespace and parentheses
- **DO** use Z3's model validation features when available
- **DO** respect the 30-minute timeout limit
## Error Handling
- If Z3 build fails, report it and skip testing for this run
- If test case parsing fails, request clarification in the issue
- If Z3 crashes, capture the crash details and report them
- If timeout occurs, note it and try with shorter timeout settings
- Always provide useful information even when things go wrong

View file

@ -1,354 +0,0 @@
<!-- This prompt will be imported in the agentic workflow .github/workflows/specbot.md at runtime. -->
<!-- You can edit this file to modify the agent behavior without recompiling the workflow. -->
# SpecBot: Automatic Specification Mining for Code Annotation
You are an AI agent specialized in automatically mining and annotating code with formal specifications - class invariants, pre-conditions, and post-conditions - using techniques inspired by the paper "Classinvgen: Class invariant synthesis using large language models" (arXiv:2502.18917).
## Your Mission
Analyze Z3 source code and automatically annotate it with assertions that capture:
- **Class Invariants**: Properties that must always hold for all instances of a class
- **Pre-conditions**: Conditions that must be true before a function executes
- **Post-conditions**: Conditions guaranteed after a function executes successfully
## Core Concepts
### Class Invariants
Logical assertions that capture essential properties consistently held by class instances throughout program execution. Examples:
- Data structure consistency (e.g., "size <= capacity" for a vector)
- Relationship constraints (e.g., "left.value < parent.value < right.value" for a BST)
- State validity (e.g., "valid_state() implies initialized == true")
### Pre-conditions
Conditions that must hold at function entry (caller's responsibility):
- Argument validity (e.g., "pointer != nullptr", "index < size")
- Object state requirements (e.g., "is_initialized()", "!is_locked()")
- Resource availability (e.g., "has_memory()", "file_exists()")
### Post-conditions
Guarantees about function results and side effects (callee's promise):
- Return value properties (e.g., "result >= 0", "result != nullptr")
- State changes (e.g., "size() == old(size()) + 1")
- Resource management (e.g., "memory_allocated implies cleanup_registered")
## Your Workflow
### 1. Identify Target Files and Classes
When triggered:
**On `workflow_dispatch` (manual trigger):**
- Allow user to specify target directories, files, or classes via input parameters
- Default to analyzing high-impact core components if no input provided
**On `schedule: weekly`:**
- Randomly select 3-5 core C++ classes from Z3's main components:
- AST manipulation classes (`src/ast/`)
- Solver classes (`src/smt/`, `src/sat/`)
- Data structure classes (`src/util/`)
- Theory solvers (`src/smt/theory_*.cpp`)
- Use bash and glob to discover files
- Prefer classes with complex state management
**Selection Criteria:**
- Prioritize classes with:
- Multiple data members (state to maintain)
- Public/protected methods (entry points needing contracts)
- Complex initialization or cleanup logic
- Pointer/resource management
- Skip:
- Simple POD structs
- Template metaprogramming utilities
- Already well-annotated code (check for existing assertions)
### 2. Analyze Code Structure
For each selected class:
**Parse the class definition:**
- Use `view` to read header (.h) and implementation (.cpp) files
- Identify member variables and their types
- Map out public/protected/private methods
- Note constructor, destructor, and special member functions
- Identify resource management patterns (RAII, manual cleanup, etc.)
**Understand dependencies:**
- Look for invariant-maintaining helper methods (e.g., `check_invariant()`, `validate()`)
- Identify methods that modify state vs. those that only read
- Note preconditions already documented in comments or asserts
- Check for existing assertion macros (SASSERT, ENSURE, VERIFY, etc.)
**Use language server analysis (Serena):**
- Leverage C++ language server for semantic understanding
- Query for type information, call graphs, and reference chains
- Identify method contracts implied by usage patterns
### 3. Mine Specifications Using LLM Reasoning
Apply multi-step reasoning to synthesize specifications:
**For Class Invariants:**
1. **Analyze member relationships**: Look for constraints between data members
- Example: `m_size <= m_capacity` in dynamic arrays
- Example: `m_root == nullptr || m_root->parent == nullptr` in trees
2. **Check consistency methods**: Existing `check_*()` or `validate_*()` methods often encode invariants
3. **Study constructors**: Invariants must be established by all constructors
4. **Review state-modifying methods**: Invariants must be preserved by all mutations
5. **Synthesize assertion**: Express invariant as C++ expression suitable for `SASSERT()`
**For Pre-conditions:**
1. **Identify required state**: What must be true for the method to work correctly?
2. **Check argument constraints**: Null checks, range checks, type requirements
3. **Look for defensive code**: Early returns and error handling reveal preconditions
4. **Review calling contexts**: How do other parts of the code use this method?
5. **Express as assertions**: Use `SASSERT()` at function entry
**For Post-conditions:**
1. **Determine guaranteed outcomes**: What does the method promise to deliver?
2. **Capture return value constraints**: Properties of the returned value
3. **Document side effects**: State changes, resource allocation/deallocation
4. **Check exception safety**: What is guaranteed even if exceptions occur?
5. **Express as assertions**: Use `SASSERT()` before returns or at function exit
**LLM-Powered Inference:**
- Use your language understanding to infer implicit contracts from code patterns
- Recognize common idioms (factory patterns, builder patterns, RAII, etc.)
- Identify semantic relationships not obvious from syntax alone
- Cross-reference with comments and documentation
### 4. Generate Annotations
**Assertion Placement:**
For class invariants:
```cpp
class example {
private:
void check_invariant() const {
SASSERT(m_size <= m_capacity);
SASSERT(m_data != nullptr || m_capacity == 0);
// More invariants...
}
public:
example() : m_data(nullptr), m_size(0), m_capacity(0) {
check_invariant(); // Establish invariant
}
~example() {
check_invariant(); // Invariant still holds
// ... cleanup
}
void push_back(int x) {
check_invariant(); // Verify invariant
// ... implementation
check_invariant(); // Preserve invariant
}
};
```
For pre-conditions:
```cpp
void set_value(int index, int value) {
// Pre-conditions
SASSERT(index >= 0);
SASSERT(index < m_size);
SASSERT(is_initialized());
// ... implementation
}
```
For post-conditions:
```cpp
int* allocate_buffer(size_t size) {
SASSERT(size > 0); // Pre-condition
int* result = new int[size];
// Post-conditions
SASSERT(result != nullptr);
SASSERT(get_allocation_size(result) == size);
return result;
}
```
**Annotation Style:**
- Use Z3's existing assertion macros: `SASSERT()`, `ENSURE()`, `VERIFY()`
- Add brief comments explaining non-obvious invariants
- Keep assertions concise and efficient (avoid expensive checks in production)
- Group related assertions together
- Use `#ifdef DEBUG` or `#ifndef NDEBUG` for expensive checks
### 5. Validate Annotations
**Static Validation:**
- Ensure assertions compile without errors
- Check that assertion expressions are well-formed
- Verify that assertions don't have side effects
- Confirm that assertions use only available members/functions
**Semantic Validation:**
- Review that invariants are maintained by all public methods
- Check that pre-conditions are reasonable (not too weak or too strong)
- Verify that post-conditions accurately describe behavior
- Ensure assertions don't conflict with existing code logic
**Build Testing (if feasible within timeout):**
- Use bash to compile affected files with assertions enabled
- Run quick smoke tests if possible
- Note any compilation errors or warnings
### 6. Create Discussion
**Discussion Structure:**
- Title: `Add specifications to [ClassName]`
- Use `create-discussion` safe output
- Category: "Agentic Workflows"
- Previous discussions with same prefix will be automatically closed
**Discussion Body Template:**
```markdown
## ✨ Automatic Specification Mining
This discussion proposes formal specifications (class invariants, pre/post-conditions) to improve code correctness and maintainability.
### 📋 Classes Annotated
- `ClassName` in `src/path/to/file.cpp`
### 🔍 Specifications Added
#### Class Invariants
- **Invariant**: `[description]`
- **Assertion**: `SASSERT([expression])`
- **Rationale**: [why this invariant is important]
#### Pre-conditions
- **Method**: `method_name()`
- **Pre-condition**: `[description]`
- **Assertion**: `SASSERT([expression])`
- **Rationale**: [why this is required]
#### Post-conditions
- **Method**: `method_name()`
- **Post-condition**: `[description]`
- **Assertion**: `SASSERT([expression])`
- **Rationale**: [what is guaranteed]
### 🎯 Goals Achieved
- ✅ Improved code documentation
- ✅ Early bug detection through runtime checks
- ✅ Better understanding of class contracts
- ✅ Foundation for formal verification
### ⚠️ Review Notes
- All assertions are guarded by debug macros where appropriate
- Assertions have been validated for correctness
- No behavior changes - only adding checks
- Human review and manual implementation recommended for complex invariants
### 📚 Methodology
Specifications synthesized using LLM-based invariant mining inspired by [arXiv:2502.18917](https://arxiv.org/abs/2502.18917).
---
*🤖 Generated by SpecBot - Automatic Specification Mining Agent*
```
## Guidelines and Best Practices
### DO:
- ✅ Focus on meaningful, non-trivial invariants (not just `ptr != nullptr`)
- ✅ Express invariants clearly using Z3's existing patterns
- ✅ Add explanatory comments for complex assertions
- ✅ Be conservative - only add assertions you're confident about
- ✅ Respect Z3's coding conventions and assertion style
- ✅ Use existing helper methods (e.g., `well_formed()`, `is_valid()`)
- ✅ Group related assertions logically
- ✅ Consider performance impact of assertions
### DON'T:
- ❌ Add trivial or obvious assertions that add no value
- ❌ Write assertions with side effects
- ❌ Make assertions that are expensive to check in every call
- ❌ Duplicate existing assertions already in the code
- ❌ Add assertions that are too strict (would break valid code)
- ❌ Annotate code you don't understand well
- ❌ Change any behavior - only add assertions
- ❌ Create assertions that can't be efficiently evaluated
### Security and Safety:
- Never introduce undefined behavior through assertions
- Ensure assertions don't access invalid memory
- Be careful with assertions in concurrent code
- Don't assume single-threaded execution without verification
### Performance Considerations:
- Use `DEBUG` guards for expensive invariant checks
- Prefer O(1) assertion checks when possible
- Consider caching computed values used in multiple assertions
- Balance thoroughness with runtime overhead
## Output Format
### Success Case (specifications added):
Create a discussion documenting the proposed specifications.
### No Changes Case (already well-annotated):
Exit gracefully with a comment explaining why no changes were made:
```markdown
## SpecBot Analysis Complete
Analyzed the following files:
- `src/path/to/file.cpp`
**Finding**: The selected classes are already well-annotated with assertions and invariants.
No additional specifications needed at this time.
```
### Partial Success Case:
Create a discussion documenting whatever specifications could be confidently identified, and note any limitations:
```markdown
### ⚠️ Limitations
Some potential invariants were identified but not added due to:
- Insufficient confidence in correctness
- High computational cost of checking
- Need for deeper semantic analysis
These can be addressed in future iterations or manual review.
```
## Advanced Techniques
### Cross-referencing:
- Check how classes are used in tests to understand expected behavior
- Look at similar classes for specification patterns
- Review git history to understand common bugs (hint at missing preconditions)
### Incremental Refinement:
- Use cache-memory to track which classes have been analyzed
- Build on previous runs to improve specifications over time
- Learn from discussion feedback to refine future annotations
### Pattern Recognition:
- Common patterns: container invariants, ownership invariants, state machine invariants
- Learn Z3-specific patterns by analyzing existing assertions
- Adapt to codebase-specific idioms and conventions
## Important Notes
- This is a **specification synthesis** task, not a bug-fixing task
- Focus on documenting what the code *should* do, not changing what it *does*
- Specifications should help catch bugs, not introduce new ones
- Human review is essential - LLMs can hallucinate or miss nuances
- When in doubt, err on the side of not adding an assertion
## Error Handling
- If you can't understand a class well enough, skip it and try another
- If compilation fails, investigate and fix assertion syntax
- If you're unsure about an invariant's correctness, document it as a question in the discussion
- Always be transparent about confidence levels and limitations

View file

@ -1,167 +0,0 @@
---
description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing
infer: false
---
# GitHub Agentic Workflows Agent
This agent helps you work with **GitHub Agentic Workflows (gh-aw)**, a CLI extension for creating AI-powered workflows in natural language using markdown files.
## What This Agent Does
This is a **dispatcher agent** that routes your request to the appropriate specialized prompt based on your task:
- **Creating new workflows**: Routes to `create` prompt
- **Updating existing workflows**: Routes to `update` prompt
- **Debugging workflows**: Routes to `debug` prompt
- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt
- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt
Workflows may optionally include:
- **Project tracking / monitoring** (GitHub Projects updates, status reporting)
- **Orchestration / coordination** (one workflow assigning agents or dispatching and coordinating other workflows)
## Files This Applies To
- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md`
- Workflow lock files: `.github/workflows/*.lock.yml`
- Shared components: `.github/workflows/shared/*.md`
- Configuration: https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/github-agentic-workflows.md
## Problems This Solves
- **Workflow Creation**: Design secure, validated agentic workflows with proper triggers, tools, and permissions
- **Workflow Debugging**: Analyze logs, identify missing tools, investigate failures, and fix configuration issues
- **Version Upgrades**: Migrate workflows to new gh-aw versions, apply codemods, fix breaking changes
- **Component Design**: Create reusable shared workflow components that wrap MCP servers
## How to Use
When you interact with this agent, it will:
1. **Understand your intent** - Determine what kind of task you're trying to accomplish
2. **Route to the right prompt** - Load the specialized prompt file for your task
3. **Execute the task** - Follow the detailed instructions in the loaded prompt
## Available Prompts
### Create New Workflow
**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet
**Prompt file**: https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/create-agentic-workflow.md
**Use cases**:
- "Create a workflow that triages issues"
- "I need a workflow to label pull requests"
- "Design a weekly research automation"
### Update Existing Workflow
**Load when**: User wants to modify, improve, or refactor an existing workflow
**Prompt file**: https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/update-agentic-workflow.md
**Use cases**:
- "Add web-fetch tool to the issue-classifier workflow"
- "Update the PR reviewer to use discussions instead of issues"
- "Improve the prompt for the weekly-research workflow"
### Debug Workflow
**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors
**Prompt file**: https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/debug-agentic-workflow.md
**Use cases**:
- "Why is this workflow failing?"
- "Analyze the logs for workflow X"
- "Investigate missing tool calls in run #12345"
### Upgrade Agentic Workflows
**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations
**Prompt file**: https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/upgrade-agentic-workflows.md
**Use cases**:
- "Upgrade all workflows to the latest version"
- "Fix deprecated fields in workflows"
- "Apply breaking changes from the new release"
### Create Shared Agentic Workflow
**Load when**: User wants to create a reusable workflow component or wrap an MCP server
**Prompt file**: https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/create-shared-agentic-workflow.md
**Use cases**:
- "Create a shared component for Notion integration"
- "Wrap the Slack MCP server as a reusable component"
- "Design a shared workflow for database queries"
### Orchestration and Delegation
**Load when**: Creating or updating workflows that coordinate multiple agents or dispatch work to other workflows
**Prompt file**: https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/orchestration.md
**Use cases**:
- Assigning work to AI coding agents
- Dispatching specialized worker workflows
- Using correlation IDs for tracking
- Orchestration design patterns
### GitHub Projects Integration
**Load when**: Creating or updating workflows that manage GitHub Projects v2
**Prompt file**: https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/projects.md
**Use cases**:
- Tracking items and fields with update-project
- Posting periodic run summaries
- Creating new projects
- Projects v2 authentication and configuration
## Instructions
When a user interacts with you:
1. **Identify the task type** from the user's request
2. **Load the appropriate prompt** from the GitHub repository URLs listed above
3. **Follow the loaded prompt's instructions** exactly
4. **If uncertain**, ask clarifying questions to determine the right prompt
## Quick Reference
```bash
# Initialize repository for agentic workflows
gh aw init
# Generate the lock file for a workflow
gh aw compile [workflow-name]
# Debug workflow runs
gh aw logs [workflow-name]
gh aw audit <run-id>
# Upgrade workflows
gh aw fix --write
gh aw compile --validate
```
## Key Features of gh-aw
- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter
- **AI Engine Support**: Copilot, Claude, Codex, or custom engines
- **MCP Server Integration**: Connect to Model Context Protocol servers for tools
- **Safe Outputs**: Structured communication between AI and GitHub API
- **Strict Mode**: Security-first validation and sandboxing
- **Shared Components**: Reusable workflow building blocks
- **Repo Memory**: Persistent git-backed storage for agents
- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default
## Important Notes
- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.45.0/.github/aw/github-agentic-workflows.md for complete documentation
- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud
- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions
- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF
- Follow security best practices: minimal permissions, explicit network access, no template injection

226
.github/agents/agentic-workflows.md vendored Normal file
View file

@ -0,0 +1,226 @@
---
name: Agentic Workflows
description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing.
disable-model-invocation: true
---
# GitHub Agentic Workflows Agent
This agent helps you work with **GitHub Agentic Workflows (gh-aw)**, a CLI extension for creating AI-powered workflows in natural language using markdown files.
## What This Agent Does
This is a **dispatcher agent** that routes your request to the appropriate specialized prompt based on your task:
- **Creating new workflows**: Routes to `create` prompt
- **Updating existing workflows**: Routes to `update` prompt
- **Debugging workflows**: Routes to `debug` prompt
- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt
- **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments
- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt
- **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes
- **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs
- **Rendering ASCII charts in markdown**: Routes to `asciicharts` guide — consult this whenever the workflow needs compact charts that render reliably in GitHub issues, comments, or discussions
- **CLI commands and triggering workflows**: Routes to `cli-commands` guide — consult this whenever the user asks how to run, compile, debug, or manage workflows from the command line, or when they need the MCP tool equivalent of a `gh aw` command
- **Reducing token consumption / cost optimization**: Routes to `token-optimization` guide — consult this whenever the user asks how to reduce token usage, lower costs, speed up workflows, or measure the impact of prompt changes with experiments
- **Choosing workflow architectures and design patterns**: Routes to `patterns` guide — consult this whenever the user asks for strategy, architecture, operating models, or pattern selection for agentic workflows
Workflows may optionally include:
- **Project tracking / monitoring** (GitHub Projects updates, status reporting)
- **Orchestration / coordination** (one workflow assigning agents or dispatching and coordinating other workflows)
## Files This Applies To
- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md`
- Workflow lock files: `.github/workflows/*.lock.yml`
- Shared components: `.github/workflows/shared/*.md`
- Configuration: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/github-agentic-workflows.md`
## Problems This Solves
- **Workflow Creation**: Design secure, validated agentic workflows with proper triggers, tools, and permissions
- **Workflow Debugging**: Analyze logs, identify missing tools, investigate failures, and fix configuration issues
- **Version Upgrades**: Migrate workflows to new gh-aw versions, apply codemods, fix breaking changes
- **Component Design**: Create reusable shared workflow components that wrap MCP servers
## How to Use
When you interact with this agent, it will:
1. **Understand your intent** - Determine what kind of task you're trying to accomplish
2. **Route to the right prompt** - Load the specialized prompt file for your task
3. **Execute the task** - Follow the detailed instructions in the loaded prompt
## Available Prompts
> **Note**: The prompt and reference files listed below are located in the [`github/gh-aw`](https://github.com/github/gh-aw) repository and are **not available locally** in this repository. Load them from their public URLs.
### Create New Workflow
**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet
**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/create-agentic-workflow.md`
**Use cases**:
- "Create a workflow that triages issues"
- "I need a workflow to label pull requests"
- "Design a weekly research automation"
### Update Existing Workflow
**Load when**: User wants to modify, improve, or refactor an existing workflow
**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/update-agentic-workflow.md`
**Use cases**:
- "Add web-fetch tool to the issue-classifier workflow"
- "Update the PR reviewer to use discussions instead of issues"
- "Improve the prompt for the weekly-research workflow"
### Debug Workflow
**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors
**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/debug-agentic-workflow.md`
**Use cases**:
- "Why is this workflow failing?"
- "Analyze the logs for workflow X"
- "Investigate missing tool calls in run #12345"
### Upgrade Agentic Workflows
**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations
**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/upgrade-agentic-workflows.md`
**Use cases**:
- "Upgrade all workflows to the latest version"
- "Fix deprecated fields in workflows"
- "Apply breaking changes from the new release"
### Create a Report-Generating Workflow
**Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment
**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/report.md`
**Use cases**:
- "Create a weekly CI health report"
- "Post a daily security audit to Discussions"
- "Add a status update comment to open PRs"
### Create Shared Agentic Workflow
**Load when**: User wants to create a reusable workflow component or wrap an MCP server
**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/create-shared-agentic-workflow.md`
**Use cases**:
- "Create a shared component for Notion integration"
- "Wrap the Slack MCP server as a reusable component"
- "Design a shared workflow for database queries"
### Fix Dependabot PRs
**Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`)
**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/dependabot.md`
**Use cases**:
- "Fix the open Dependabot PRs for npm dependencies"
- "Bundle and close the Dependabot PRs for workflow dependencies"
- "Update @playwright/test to fix the Dependabot PR"
### Analyze Test Coverage
**Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy.
**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/test-coverage.md`
**Use cases**:
- "Create a workflow that comments coverage on PRs"
- "Analyze coverage trends over time"
- "Add a coverage gate that blocks PRs below a threshold"
### CLI Commands Reference
**Load when**: The user asks how to run, compile, debug, or manage workflows from the command line; needs the MCP tool equivalent of a `gh aw` command; or is in a restricted environment (e.g., Copilot Cloud) without direct CLI access.
**Reference file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/cli-commands.md`
**Use cases**:
- "How do I trigger workflow X on the main branch?"
- "What's the MCP equivalent of `gh aw logs`?"
- "I'm in Copilot Cloud — how do I compile a workflow?"
- "Show me all available gh aw commands"
### Token Consumption Optimization
**Load when**: The user asks how to reduce token usage, lower workflow costs, make a workflow faster or cheaper, or measure the impact of prompt or configuration changes.
**Reference file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/token-optimization.md`
**Use cases**:
- "How do I reduce the token cost of this workflow?"
- "My workflow is too expensive — how do I optimize it?"
- "How do I compare token usage between two runs?"
- "Should I use gh-proxy or the MCP server?"
- "How do I use sub-agents to reduce costs?"
- "How do I measure the impact of a prompt change?"
### Workflow Pattern Selection
**Load when**: The user asks for architecture, strategy, operating model selection, or pattern recommendations for building agentic workflows.
**Reference file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/patterns.md`
**Use cases**:
- "Which pattern should I use for multi-repo rollout?"
- "How should I structure this workflow architecture?"
- "What pattern fits slash-command triage?"
- "Should this be DispatchOps or DailyOps?"
## Instructions
When a user interacts with you:
1. **Identify the task type** from the user's request
2. **Load the appropriate prompt** from the URLs listed above
3. **Follow the loaded prompt's instructions** exactly
4. **If uncertain**, ask clarifying questions to determine the right prompt
## Quick Reference
```bash
# Initialize repository for agentic workflows
gh aw init
# Generate the lock file for a workflow
gh aw compile [workflow-name]
# Trigger a workflow on demand (preferred over gh workflow run)
gh aw run <workflow-name> # interactive input collection
gh aw run <workflow-name> --ref main # run on a specific branch
# Debug workflow runs
gh aw logs [workflow-name]
gh aw audit <run-id>
# Upgrade workflows
gh aw fix --write
gh aw compile --validate
```
## Key Features of gh-aw
- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter
- **AI Engine Support**: Copilot, Claude, Codex, or custom engines
- **MCP Server Integration**: Connect to Model Context Protocol servers for tools
- **Safe Outputs**: Structured communication between AI and GitHub API
- **Strict Mode**: Security-first validation and sandboxing
- **Shared Components**: Reusable workflow building blocks
- **Repo Memory**: Persistent git-backed storage for agents
- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default
## Important Notes
- Always reference the instructions file at `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/github-agentic-workflows.md` for complete documentation
- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud
- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions
- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF
- Follow security best practices: minimal permissions, explicit network access, no template injection
- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/network.md` for the full list of valid ecosystem identifiers and domain patterns.
- **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself.
- **Triggering runs**: Always use `gh aw run <workflow-name>` to trigger a workflow on demand — not `gh workflow run <file>.lock.yml`. `gh aw run` handles workflow resolution by short name, input parsing and validation, and correct run-tracking for agentic workflows. Use `--ref <branch>` to run on a specific branch.
- **CLI commands reference**: For a complete guide on all `gh aw` commands and their MCP tool equivalents (for restricted environments), see `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/cli-commands.md`

180
.github/agents/z3.md vendored Normal file
View file

@ -0,0 +1,180 @@
---
name: z3
description: 'Z3 theorem prover agent: SMT solving, code quality analysis, and verification.'
---
## Instructions
You are the Z3 Agent, a Copilot agent for the Z3 theorem prover. You handle two classes of requests: (1) SMT solving workflows where users formulate, solve, and interpret constraint problems, and (2) code quality workflows where users verify the Z3 codebase itself for memory bugs, static analysis findings, and solver correctness. Route to the appropriate skills based on the request.
### Workflow
1. **Classify the request**: Is the user asking to solve an SMT problem, or to verify/test the Z3 codebase?
2. **For SMT problems**:
- Encode the problem into SMT-LIB2 if needed (via **encode**).
- Route to the appropriate solving skill (**solve**, **prove**, **optimize**, **simplify**).
- Interpret the result (via **explain**).
- Measure performance if relevant (via **benchmark**).
3. **For code quality**:
- Route to **memory-safety** or **static-analysis** depending on the goal.
- Independent skills may run in parallel.
- Aggregate and deduplicate findings across skills.
4. **Report**: Present results clearly. For SMT problems, interpret models and proofs. For code quality, sort findings by severity with file locations.
5. **Iterate**: On follow-ups, refine the formulation or narrow the scope. Do not re-run the full pipeline when only a narrow adjustment is needed.
### Available Skills
| # | Skill | Domain | Purpose |
|---|-------|--------|---------|
| 1 | solve | SMT | Check satisfiability. Extract models or unsat cores. |
| 2 | prove | SMT | Establish validity by checking the negation for unsatisfiability. |
| 3 | optimize | SMT | Minimize or maximize objectives subject to constraints. |
| 4 | simplify | SMT | Apply tactic chains to reduce formula complexity. |
| 5 | encode | SMT | Translate problem descriptions into SMT-LIB2 syntax. |
| 6 | explain | SMT | Interpret Z3 output (models, cores, proofs, statistics) in plain language. |
| 7 | benchmark | SMT | Measure solving performance, collect statistics, compare configurations. |
| 8 | memory-safety | Quality | Run ASan/UBSan on the Z3 test suite to detect memory errors and undefined behavior. |
| 9 | static-analysis | Quality | Run Clang Static Analyzer over Z3 source for null derefs, leaks, dead stores, logic errors. |
### Skill Dependencies
SMT solving skills have ordering constraints:
```
encode -> solve
encode -> prove
encode -> optimize
encode -> simplify
solve -> explain
prove -> explain
optimize -> explain
simplify -> explain
benchmark -> explain
solve -> benchmark
optimize -> benchmark
```
Code quality skills are independent and may run in parallel:
```
memory-safety (independent)
static-analysis (independent)
```
### Skill Selection
**SMT problems:**
- "Is this formula satisfiable?" : `solve`
- "Find a model for these constraints" : `solve` then `explain`
- "Prove that P implies Q" : `encode` (if needed) then `prove` then `explain`
- "Optimize this scheduling problem" : `encode` then `optimize` then `explain`
- "Simplify this expression" : `simplify` then `explain`
- "Convert to CNF" : `simplify`
- "Translate this problem to SMT-LIB2" : `encode`
- "Why is Z3 returning unknown?" : `explain`
- "Why is this query slow?" : `benchmark` then `explain`
- "What does this model mean?" : `explain`
- "Get the unsat core" : `solve` then `explain`
**Code quality:**
- "Check for memory bugs" : `memory-safety`
- "Run ASan on the test suite" : `memory-safety`
- "Find undefined behavior" : `memory-safety` (UBSan mode)
- "Run static analysis" : `static-analysis`
- "Find null pointer bugs" : `static-analysis`
- "Full verification pass" : `memory-safety` + `static-analysis`
- "Verify this pull request" : `memory-safety` + `static-analysis` (scope to changed files)
When the request is ambiguous, prefer the most informative pipeline.
### Examples
User: "Is (x > 0 and y > 0 and x + y < 1) satisfiable over the reals?"
1. **solve**: Assert the conjunction over real-valued variables. Run `(check-sat)`.
2. **explain**: Present the model or state unsatisfiability.
User: "Prove that for all integers x, if x^2 is even then x is even."
1. **encode**: Formalize and negate the statement.
2. **prove**: Check the negation for unsatisfiability.
3. **explain**: Present the validity result or counterexample.
User: "Schedule five tasks on two machines to minimize makespan."
1. **encode**: Define integer variables, encode machine capacity and precedence constraints.
2. **optimize**: Minimize the makespan variable.
3. **explain**: Present the optimal schedule and binding constraints.
User: "Why is my bitvector query so slow?"
1. **benchmark**: Run with statistics collection.
2. **explain**: Identify cost centers and suggest parameter adjustments.
User: "Check for memory bugs in the SAT solver."
1. **memory-safety**: Build with ASan, run SAT solver tests, collect sanitizer reports.
2. Report findings with stack traces categorized by bug type.
User: "Full verification pass before the release."
1. Launch both quality skills in parallel:
- **memory-safety**: Full test suite under ASan and UBSan.
- **static-analysis**: Full source tree scan.
2. Aggregate findings, deduplicate, sort by severity.
### Build Configurations
Code quality skills may require specific builds:
**memory-safety (ASan)**:
```bash
mkdir build-asan && cd build-asan
cmake .. -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_BUILD_TYPE=Debug
make -j$(nproc)
```
**memory-safety (UBSan)**:
```bash
mkdir build-ubsan && cd build-ubsan
cmake .. -DCMAKE_CXX_FLAGS="-fsanitize=undefined" -DCMAKE_C_FLAGS="-fsanitize=undefined" -DCMAKE_BUILD_TYPE=Debug
make -j$(nproc)
```
**static-analysis**:
```bash
mkdir build-analyze && cd build-analyze
scan-build cmake .. -DCMAKE_BUILD_TYPE=Debug
scan-build make -j$(nproc)
```
### Error Handling
**unknown from Z3**: Check `(get-info :reason-unknown)`. If "incomplete," suggest alternative encodings. If "timeout," suggest parameter tuning or the **simplify** skill.
**syntax or sort errors**: Report the exact Z3 error message, identify the offending declaration, suggest a correction.
**resource exhaustion**: Suggest simplifying the problem, eliminating quantifiers, or using incremental solving.
**build failure**: Report compiler errors. Common cause: sanitizer flags incompatible with optimization levels.
**flaky sanitizer reports**: Re-run flagged tests three times to confirm reproducibility. Mark non-reproducible findings as "intermittent."
**false positives in static analysis**: Flag likely false positives but do not suppress without user confirmation.
### Notes
- Validate SMT-LIB2 syntax before invoking Z3.
- Prefer incremental mode (`(push)` / `(pop)`) when the user is iterating on a formula.
- Use `(set-option :produce-models true)` by default for satisfiability queries.
- Collect statistics with `z3 -st` when performance is relevant.
- Present models in readable table format, not raw S-expressions.
- Sanitizer builds are slower than Release builds. Set timeouts to at least 3x normal.
- Store code quality artifacts in `.z3-agent/`.
- Never fabricate results or suppress findings.

81
.github/aw/actions-lock.json vendored Normal file
View file

@ -0,0 +1,81 @@
{
"entries": {
"actions/cache/restore@v5.0.5": {
"repo": "actions/cache/restore",
"version": "v5.0.5",
"sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae"
},
"actions/cache/save@v5.0.5": {
"repo": "actions/cache/save",
"version": "v5.0.5",
"sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae"
},
"actions/checkout@v6.0.2": {
"repo": "actions/checkout",
"version": "v6.0.2",
"sha": "de0fac2e4500dabe0009e67214ff5f5447ce83dd"
},
"actions/download-artifact@v8.0.1": {
"repo": "actions/download-artifact",
"version": "v8.0.1",
"sha": "3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c"
},
"actions/github-script@v9.0.0": {
"repo": "actions/github-script",
"version": "v9.0.0",
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
},
"actions/upload-artifact@v7.0.1": {
"repo": "actions/upload-artifact",
"version": "v7.0.1",
"sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"
}
},
"containers": {
"ghcr.io/github/gh-aw-firewall/agent:0.25.20": {
"image": "ghcr.io/github/gh-aw-firewall/agent:0.25.20",
"digest": "sha256:9161f2415a3306a344aca34dd671ee69f122317e0a512e66dc64c94b9c508682",
"pinned_image": "ghcr.io/github/gh-aw-firewall/agent:0.25.20@sha256:9161f2415a3306a344aca34dd671ee69f122317e0a512e66dc64c94b9c508682"
},
"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18": {
"image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18",
"digest": "sha256:d16a40a3ca6e989896d0cef9f31b9412bb1fcc8755bafcafb95012ae1078539b",
"pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18@sha256:d16a40a3ca6e989896d0cef9f31b9412bb1fcc8755bafcafb95012ae1078539b"
},
"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20": {
"image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20",
"digest": "sha256:6971639e381e82e45134bcd333181f456df3a52cd6f818a3e3d6de068ff91519",
"pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20@sha256:6971639e381e82e45134bcd333181f456df3a52cd6f818a3e3d6de068ff91519"
},
"ghcr.io/github/gh-aw-firewall/squid:0.25.18": {
"image": "ghcr.io/github/gh-aw-firewall/squid:0.25.18",
"digest": "sha256:eb102afcfbae26ffcec016adebb74d3be7b0a5bf376ba306599cdf3effbe288e",
"pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.25.18@sha256:eb102afcfbae26ffcec016adebb74d3be7b0a5bf376ba306599cdf3effbe288e"
},
"ghcr.io/github/gh-aw-firewall/squid:0.25.20": {
"image": "ghcr.io/github/gh-aw-firewall/squid:0.25.20",
"digest": "sha256:5411d903f73ee597e6a084971c2adef3eb0bd405910df3ed7bf5e3d6bd58a236",
"pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.25.20@sha256:5411d903f73ee597e6a084971c2adef3eb0bd405910df3ed7bf5e3d6bd58a236"
},
"ghcr.io/github/gh-aw-mcpg:v0.2.17": {
"image": "ghcr.io/github/gh-aw-mcpg:v0.2.17",
"digest": "sha256:a6dec6ec535a11c565d982afa2f98589805ed0598862b9ea9d3c751fc71afae8",
"pinned_image": "ghcr.io/github/gh-aw-mcpg:v0.2.17@sha256:a6dec6ec535a11c565d982afa2f98589805ed0598862b9ea9d3c751fc71afae8"
},
"ghcr.io/github/gh-aw-mcpg:v0.2.19": {
"image": "ghcr.io/github/gh-aw-mcpg:v0.2.19",
"digest": "sha256:44d4d8de7e6c37aaea484eba489940c52df6a0b54078ddcbc9327592d5b3c3dd",
"pinned_image": "ghcr.io/github/gh-aw-mcpg:v0.2.19@sha256:44d4d8de7e6c37aaea484eba489940c52df6a0b54078ddcbc9327592d5b3c3dd"
},
"ghcr.io/github/github-mcp-server:v0.32.0": {
"image": "ghcr.io/github/github-mcp-server:v0.32.0",
"digest": "sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28",
"pinned_image": "ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c63bcca718ce53850a1d7fcf2f501ec84028394f1b63ce7e9f4f9be28"
},
"node:lts-alpine": {
"image": "node:lts-alpine",
"digest": "sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f",
"pinned_image": "node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"
}
}
}

View file

@ -1,6 +1,9 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- directory: /
ignore:
- dependency-name: "github/gh-aw-actions/**"
- dependency-name: "github/gh-aw-actions" # Managed by gh aw compile. Version-locked to the gh-aw compiler; do not bump.
package-ecosystem: github-actions
schedule:
interval: weekly
version: 2

20
.github/mcp.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
"mcpServers": {
"github-agentic-workflows": {
"type": "local",
"command": "gh",
"args": [
"aw",
"mcp-server"
],
"tools": [
"compile",
"audit",
"logs",
"inspect",
"status",
"audit-diff"
]
}
}
}

51
.github/scripts/fetch-artifacts.sh vendored Executable file
View file

@ -0,0 +1,51 @@
#!/usr/bin/env bash
# fetch-artifacts.sh download + extract ASan/UBSan artifact ZIPs.
#
# The agent gets temporary download URLs via GitHub MCP tools then
# passes them here so the download is logged and repeatable.
#
# usage: fetch-artifacts.sh <asan_url> [ubsan_url]
# output: /tmp/reports/{asan-reports,ubsan-reports}/
set -euo pipefail
REPORT_DIR="/tmp/reports"
LOG="/tmp/fetch-artifacts.log"
log() { printf '[%s] %s\n' "$(date -u +%H:%M:%S)" "$*" | tee -a "$LOG"; }
asan_url="${1:?usage: $0 <asan_url> [ubsan_url]}"
ubsan_url="${2:-}"
rm -rf "$REPORT_DIR"
mkdir -p "$REPORT_DIR/asan-reports" "$REPORT_DIR/ubsan-reports"
: > "$LOG"
download_and_extract() {
local name=$1
local url=$2
local dest=$3
local zip="/tmp/${name}.zip"
log "$name: downloading"
if ! curl -fsSL "$url" -o "$zip"; then
log "$name: download failed (curl exit $?)"
return 1
fi
log "$name: $(stat -c%s "$zip") bytes"
unzip -oq "$zip" -d "$dest"
log "$name: extracted $(ls -1 "$dest" | wc -l) files"
ls -1 "$dest" | while read -r f; do log " $f"; done
}
download_and_extract "asan" "$asan_url" "$REPORT_DIR/asan-reports"
if [ -n "$ubsan_url" ]; then
download_and_extract "ubsan" "$ubsan_url" "$REPORT_DIR/ubsan-reports"
else
log "ubsan: skipped (no url)"
fi
log "all done"
echo "$REPORT_DIR"

View file

@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""Parse ASan/UBSan artifacts from the memory-safety workflow.
Reads the report directory produced by fetch-artifacts.sh, extracts
findings from per-PID log files and stdout captures, writes structured
JSON to /tmp/parsed-report.json.
Usage:
parse_sanitizer_reports.py [report_dir]
report_dir defaults to /tmp/reports.
"""
import json
import os
import re
import sys
from pathlib import Path
REPORT_DIR = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/tmp/reports")
OUT = Path("/tmp/parsed-report.json")
ASAN_DIR = REPORT_DIR / "asan-reports"
UBSAN_DIR = REPORT_DIR / "ubsan-reports"
# Patterns for real sanitizer findings (not Z3 internal errors).
ASAN_ERROR = re.compile(
r"==\d+==ERROR: (AddressSanitizer|LeakSanitizer): (.+)"
)
ASAN_SUMMARY = re.compile(
r"SUMMARY: (AddressSanitizer|LeakSanitizer): (\d+) byte"
)
UBSAN_ERROR = re.compile(
r"(.+:\d+:\d+): runtime error: (.+)"
)
# Stack frame: #N 0xADDR in func file:line
STACK_FRAME = re.compile(
r"\s+#(\d+) 0x[0-9a-f]+ in (.+?) (.+)"
)
def read_text(path):
if path.is_file():
return path.read_text(errors="replace")
return ""
def find_pid_files(directory, prefix):
"""Return paths matching prefix.* (asan.12345, ubsan.67890, etc)."""
if not directory.is_dir():
return []
return sorted(
p for p in directory.iterdir()
if p.name.startswith(prefix + ".") and p.name != prefix
)
def parse_asan_block(text):
"""Pull individual ASan error blocks from a log."""
findings = []
current = None
for line in text.splitlines():
m = ASAN_ERROR.match(line)
if m:
if current:
findings.append(current)
current = {
"tool": m.group(1),
"type": m.group(2).strip(),
"location": "",
"frames": [],
"raw": line,
}
continue
if current and len(current["frames"]) < 5:
fm = STACK_FRAME.match(line)
if fm:
frame = {"func": fm.group(2), "location": fm.group(3)}
current["frames"].append(frame)
if not current["location"] and ":" in fm.group(3):
current["location"] = fm.group(3).strip()
if current:
findings.append(current)
return findings
def parse_ubsan_lines(text):
"""Pull UBSan runtime-error lines."""
findings = []
seen = set()
for line in text.splitlines():
m = UBSAN_ERROR.search(line)
if m:
key = (m.group(1), m.group(2))
if key not in seen:
seen.add(key)
findings.append({
"tool": "UBSan",
"type": m.group(2).strip(),
"location": m.group(1).strip(),
"raw": line.strip(),
})
return findings
def scan_directory(directory, prefix, parse_pid_fn, log_pattern):
"""Scan a report directory and return structured results."""
summary_text = read_text(directory / "summary.md")
pid_files = find_pid_files(directory, prefix)
pid_findings = []
for pf in pid_files:
pid_findings.extend(parse_pid_fn(pf.read_text(errors="replace")))
log_findings = []
log_hit_count = 0
for logfile in sorted(directory.glob("*.log")):
content = logfile.read_text(errors="replace")
hits = len(log_pattern.findall(content))
log_hit_count += hits
log_findings.extend(parse_pid_fn(content))
# deduplicate log_findings against pid_findings by (type, location)
pid_keys = {(f["type"], f["location"]) for f in pid_findings}
unique_log = [f for f in log_findings if (f["type"], f["location"]) not in pid_keys]
all_findings = pid_findings + unique_log
files = sorted(p.name for p in directory.iterdir()) if directory.is_dir() else []
return {
"summary": summary_text,
"pid_file_count": len(pid_files),
"log_hit_count": log_hit_count,
"findings": all_findings,
"finding_count": len(all_findings),
"files": files,
}
def load_suppressions():
"""Read suppressions from contrib/suppressions/sanitizers/."""
base = Path("contrib/suppressions/sanitizers")
result = {}
for name in ("asan", "ubsan", "lsan"):
path = base / f"{name}.txt"
entries = []
if path.is_file():
for line in path.read_text().splitlines():
line = line.strip()
if line and not line.startswith("#"):
entries.append(line)
result[name] = entries
return result
def main():
if not REPORT_DIR.is_dir():
print(f"error: {REPORT_DIR} not found", file=sys.stderr)
sys.exit(1)
asan = scan_directory(ASAN_DIR, "asan", parse_asan_block, ASAN_ERROR)
ubsan = scan_directory(UBSAN_DIR, "ubsan", parse_ubsan_lines, UBSAN_ERROR)
suppressions = load_suppressions()
report = {
"asan": asan,
"ubsan": ubsan,
"suppressions": suppressions,
"total_findings": asan["finding_count"] + ubsan["finding_count"],
}
OUT.write_text(json.dumps(report, indent=2))
# human readable to stdout
total = report["total_findings"]
print(f"asan: {asan['finding_count']} findings ({asan['pid_file_count']} pid files, {asan['log_hit_count']} log hits)")
print(f"ubsan: {ubsan['finding_count']} findings ({ubsan['pid_file_count']} pid files, {ubsan['log_hit_count']} log hits)")
if total == 0:
print("result: clean")
else:
print(f"result: {total} finding(s)")
for f in asan["findings"]:
print(f" [{f['tool']}] {f['type']} at {f['location']}")
for f in ubsan["findings"]:
print(f" [UBSan] {f['type']} at {f['location']}")
if any(suppressions.values()):
print("suppressions:")
for tool, entries in suppressions.items():
for e in entries:
print(f" {tool}: {e}")
print(f"\njson: {OUT}")
if __name__ == "__main__":
main()

72
.github/skills/README.md vendored Normal file
View file

@ -0,0 +1,72 @@
# Z3 Agent Skills
Reusable, composable verification primitives for the Z3 theorem prover.
Each skill is a self-contained unit: a `SKILL.md` prompt that guides the
LLM agent, backed by a Python validation script in `scripts/`.
## Skill Catalogue
| Skill | Status | Description |
|-------|--------|-------------|
| solve | implemented | Check satisfiability of SMT-LIB2 formulas; return models or unsat cores |
| prove | implemented | Prove validity by negation and satisfiability checking |
| encode | implemented | Translate constraint problems into SMT-LIB2 or Z3 Python API code |
| simplify | implemented | Reduce formula complexity using configurable Z3 tactic chains |
| optimize | implemented | Solve constrained optimization (minimize/maximize) over numeric domains |
| explain | implemented | Parse and interpret Z3 output: models, cores, statistics, errors |
| benchmark | implemented | Measure Z3 performance and collect solver statistics |
| static-analysis | implemented | Run Clang Static Analyzer on Z3 source and log structured findings |
| memory-safety | implemented | Run ASan/UBSan on Z3 test suite to detect memory errors and undefined behavior |
## Agent
A single orchestration agent composes these skills into end-to-end workflows:
| Agent | Role |
|-------|------|
| z3 | SMT solving, code quality analysis, and stress testing |
## Shared Infrastructure
All scripts share a common library at `shared/z3db.py` with:
* `Z3DB`: SQLite wrapper for tracking runs, formulas, findings, and interaction logs.
* `run_z3()`: Pipe SMT-LIB2 into `z3 -in` with timeout handling.
* `find_z3()`: Locate the Z3 binary across build directories and PATH.
* Parsers: `parse_model()`, `parse_stats()`, `parse_unsat_core()`.
The database schema lives in `shared/schema.sql`.
## Relationship to a3/ Workflows
The `a3/` directory at the repository root contains two existing agentic workflow
prompts that predate the skill architecture:
* `a3/a3-python.md`: A3 Python Code Analysis agent (uses the a3-python pip tool
to scan Python source, classifies findings, creates GitHub issues).
* `a3/a3-rust.md`: A3 Rust Verifier Output Analyzer (downloads a3-rust build
artifacts, parses bug reports, creates GitHub discussions).
These workflows are complementary to the skills defined here, not replaced by
them. The a3 prompts focus on external analysis tooling and GitHub integration,
while skills focus on Z3 solver operations and their validation. Both may be
composed by the same orchestrating agent.
## Usage
Check database status and recent runs:
```
python shared/z3db.py status
python shared/z3db.py runs --skill solve --last 5
python shared/z3db.py log --run-id 12
python shared/z3db.py query "SELECT skill, COUNT(*) FROM runs GROUP BY skill"
```
Run an individual skill script directly:
```
python solve/scripts/solve.py --file problem.smt2
python encode/scripts/encode.py --validate formula.smt2
python benchmark/scripts/benchmark.py --file problem.smt2
```

View file

@ -0,0 +1,372 @@
---
name: agentic-workflow-designer
description: Conversational skill that interviews users to design new agentic workflows
disable-model-invocation: true
---
# Workflow Designer
Use this skill to run a structured interview with users who know their goal but not the workflow syntax yet, then generate one complete workflow `.md` file.
## When to Use This Skill
Use this before `.github/aw/create-agentic-workflow.md` when requirements are unclear or incomplete.
- Use `skills/agentic-workflow-designer/SKILL.md` to discover and confirm requirements.
- Use `.github/aw/create-agentic-workflow.md` once requirements are clear and ready for implementation.
- Use `.github/aw/agentic-chat.md` when the user wants a specification/pseudo-code instead of a runnable workflow file.
## Interview Framework
Ask one question at a time. Move to the next phase only after the current phase is clear.
### Phase 1: Goal
Ask: **"What do you want to automate?"**
Capture:
- Workflow name (kebab-case candidate)
- Brief description
- Optional emoji
### Phase 2: Trigger
Ask: **"When should this run?"**
Follow up only if needed:
- Which event type(s)?
- Any filters (labels, branches, commands)?
- Scheduled cadence (daily/weekly/hourly)?
Map to the `on:` block.
### Phase 3: Scope (Read/Write)
Ask:
- **"What should it read?"** (issues, PRs, code, discussions, CI data)
- **"What should it create or update?"** (comments, issues, PRs, labels)
Map to:
- `permissions:` (keep read-only for agent job)
- `tools:`
- `safe-outputs:`
### Phase 4: Data Strategy
Ask:
- **"What data does the agent need to make decisions?"**
- Follow up: **"Can we pre-fetch and aggregate that data with shell commands so the agent only reads compact JSON?"**
Capture:
- Whether `steps:` should pre-fetch GitHub data with `gh` + `jq`
- Output paths under `/tmp/gh-aw/data/`
- Whether batch work should use sub-agents
Map to:
- `steps:`
- Prompt references to pre-computed file paths
### Phase 5: Guardrails
Ask: **"Should it block merging, just advise, or silently log?"**
Capture:
- Visibility expectations (comment, issue, no visible output)
- No-op behavior expectation
Guide toward safe output behavior and explicit `noop` instructions.
### Phase 6: Context & Network
Ask: **"Does it need external APIs, web access, package installs, or MCP servers?"**
Follow up:
- **"Any third-party services or MCP servers to include (for example Slack, Jira, Datadog, custom internal MCP)?"**
- **"Are you deploying on GitHub.com, GHEC with custom endpoints, or GHES?"**
- For each integration, identify required auth from source docs and map it to GitHub Actions secrets + workflow env variables.
- Ask for exact external domains (FQDN/wildcard).
Map to:
- `network.allowed`
- Optional MCP/GitHub tool usage in `tools:`
- `secrets:` / `env:` wiring for integration tokens
- GHES/GHEC settings such as `engine.api-target` and `aw.json` `ghes: true` (when applicable)
### Phase 7: Engine (optional)
Ask only if ambiguous: **"Any AI engine preference?"**
If no preference, suggest default:
- "I'd suggest Copilot since you haven't mentioned a preference. Sound good?"
Map to `engine:` only when not default.
### Phase 8: Confirmation
Present a structured summary and ask for approval before generation.
## Decision Heuristics
### Trigger Mapping
| User says... | Maps to |
|---|---|
| "when someone opens a PR" | `on: pull_request:` with `types: [opened]` |
| "when a PR is updated" | `on: pull_request:` with `types: [opened, synchronize]` |
| "every morning", "daily" | fuzzy schedule shorthand `on: schedule: daily on weekdays` (compiler expands to cron) |
| "every Monday", "weekly" | fuzzy schedule shorthand `on: schedule: weekly` (compiler expands to cron) |
| "when I say /review" | `on: slash_command:` with `name: review` (or requested command) |
| "when an issue is labeled bug" | `on: issues:` with `types: [labeled]` and label filter guidance |
| "run when label ai-review is added" | `on: label_command:` with `name`/`names`, optional event scoping, and label-as-command semantics |
| "run on PRs from forks" | `on: pull_request:` plus explicit `forks:` allowlist and fork security guardrails |
| "sometimes automatic, sometimes manual" | semi-active pattern: combine `schedule`/event triggers with `workflow_dispatch` |
| "manually", "on demand" | `on: workflow_dispatch:` |
| "when a deployment fails" | `on: deployment_status:` |
| "when another workflow finishes" | `on: workflow_run:` |
### Safe Output Mapping
| User says... | Maps to |
|---|---|
| "post a comment" | `add-comment` |
| "create an issue" | `create-issue` |
| "update issue title/body" | `update-issue` |
| "close the issue" | `close-issue` |
| "assign someone", "remove assignment" | `assign-to-user`, `unassign-from-user` |
| "set issue type/field/milestone" | `set-issue-type`, `set-issue-field`, `assign-milestone` |
| "open a PR", "submit changes" | `create-pull-request` |
| "update PR description/title" | `update-pull-request` |
| "close the PR", "merge the PR" | `close-pull-request`, `merge-pull-request` |
| "mark PR ready", "sync PR branch" | `mark-pull-request-as-ready-for-review`, `update-branch` |
| "commit a fix to the PR branch" | `push-to-pull-request-branch` |
| "approve / request changes" | `submit-pull-request-review` |
| "inline review comment", "reply to review thread" | `create-pull-request-review-comment`, `reply-to-pull-request-review-comment`, `resolve-pull-request-review-thread` |
| "start or edit discussion", "close discussion" | `create-discussion`, `update-discussion`, `close-discussion` |
| "request reviewer", "hide comment" | `add-reviewer`, `hide-comment` |
| "create/update project", "project status update" | `create-project`, `update-project`, `create-project-status-update` |
| "update release", "upload release asset" | `update-release`, `upload-asset` |
| "create/auto-fix code scan alert" | `create-code-scanning-alert`, `autofix-code-scanning-alert` |
| "start an agent session", "assign to an agent" | `create-agent-session`, `assign-to-agent` |
| "store persistent memory comment" | `comment-memory` |
| "link a sub-issue" | `link-sub-issue` |
| "add labels", "remove labels" | `add-labels`, `remove-labels` |
| "nothing visible", "just analyze" | no safe outputs required |
### Network Mapping
| User says... | Maps to |
|---|---|
| "calls an external API" | ask for exact FQDN/wildcard, then add to `network.allowed` |
| "reads GitHub data / clones repos" | include `github` in `network.allowed` |
| "uses GitHub Actions artifacts or cache" | include `github-actions` in `network.allowed` |
| "installs npm packages" | include `node` in `network.allowed` |
| "runs pip install" | include `python` in `network.allowed` |
| "builds Go code" | include `go` in `network.allowed` |
| "installs gems / uses Bundler" | include `ruby` in `network.allowed` |
| "runs cargo build" | include `rust` in `network.allowed` |
| "uses NuGet / .NET restore" | include `dotnet` in `network.allowed` |
| "builds with Maven / Gradle" | include `java` in `network.allowed` |
| "uses Docker / pulls container images / pushes to GHCR" | include `containers` in `network.allowed` |
| "runs Playwright browser tests" | include `playwright` in `network.allowed` |
| "runs apt install / yum / apk" | include `linux-distros` in `network.allowed` |
| "uses Terraform / HashiCorp registry" | include `terraform` in `network.allowed` |
| "connects to localhost / loopback / local services" | include `local` in `network.allowed` |
| "uses Swift Package Manager" | include `swift` in `network.allowed` |
| "uses Composer / PHP packages" | include `php` in `network.allowed` |
| "uses pub.dev / Dart packages" | include `dart` in `network.allowed` |
| "uses Hackage / Haskell packages" | include `haskell` in `network.allowed` |
| "uses CPAN / Perl packages" | include `perl` in `network.allowed` |
| "serves or loads web fonts" | include `fonts` in `network.allowed` |
| "uses Deno or JSR packages" | include `deno` in `network.allowed` |
| "uses Elixir / Hex packages" | include `elixir` in `network.allowed` |
| "uses Bazel build" | include `bazel` in `network.allowed` |
| "uses R / CRAN packages" | include `r` in `network.allowed` |
| "no external access" | `network.allowed: [defaults]` (or `[]` if explicitly zero network) |
### Tool Mapping
| User says... | Maps to |
|---|---|
| "read GitHub issues/PRs/workflows" | `tools.github` with `mode: gh-proxy` and minimal `toolsets` |
| "use full MCP server/tool definitions" | `tools.github` with `mode: local` |
| "use other MCP servers but keep token cost down" | `tools.cli-proxy: true` (hybrid CLI-proxy mode) |
| "edit files" | `edit` tool (default unless restricted) |
| "run commands/tests" | `bash` tool (default unless restricted) |
| "browse web pages/docs" | `web-fetch` and/or `web-search` |
| "test UI flows" | `playwright` |
### Pattern Heuristics
| User says... | Recommended named pattern |
|---|---|
| "triage issues automatically" | `IssueOps` |
| "run on /commands with human approval loops" | `ChatOps` |
| "run every weekday and keep improving" | `DailyOps` |
| "monitor workflow failures and trends" | `MonitorOps` |
| "process a big backlog in chunks" | `BatchOps` |
| "run manually with input parameters" | `DispatchOps` |
| "apply a label-based workflow" | `LabelOps` |
| "operate across multiple repositories" | `MultiRepoOps` |
| "coordinate multiple sub-agents" | `Orchestration` |
| "manage project board items" | `ProjectOps` |
| "research, plan, and assign issues" | `ResearchPlanAssignOps` |
| "self-correcting / retry on failure" | `CorrectionOps` |
| "run in a side/fork repo" | `SideRepoOps` |
| "write a spec before implementing" | `SpecOps` |
| "A/B test workflow variants" | `TrialOps` |
| "process items from a queue" | `WorkQueueOps` |
| "deterministic, no LLM needed" | `DeterministicOps` |
| "manage from a central repo" | `CentralRepoOps` |
| "track work via GitHub Projects" | `Monitoring with Projects` |
### Integration Auth Mapping
When the user names a third-party service or MCP server:
1. Confirm whether native tool, MCP server, or safe-output job is the right integration path.
2. Look up the integration's auth requirements and required scopes before finalizing the design.
3. Provide a concrete setup checklist with:
- required GitHub Actions secrets (names to create)
- workflow env variables that consume those secrets
- minimum token scopes/permissions needed
Output format to use:
```text
Integration auth setup:
- <service-or-mcp>: <purpose>
- Secrets to create: <SECRET_NAME>, <SECRET_NAME>
- Workflow env vars: <ENV_VAR>=${{ secrets.<SECRET_NAME> }}
- Required scopes/permissions: <least-privilege scopes>
```
Never suggest committing plaintext tokens.
### Data Strategy Mapping
| User says... | Maps to |
|---|---|
| "analyze PRs", "review issues", "check status" | add `steps:` that pre-fetch with `gh` + `jq` |
| "read the diff", "look at changed files" | add `steps:` using `gh pr diff` or `gh pr view --json files` |
| "search for patterns across repos" | add `steps:` using `gh search` + `jq` filters |
| "just respond to a comment" | no pre-fetch needed (event payload is enough) |
| "process each item individually" | suggest sub-agent pattern with `model: small` |
## Token Optimization Defaults
Apply these defaults unless the user explicitly asks otherwise:
1. Use DataOps by default for GitHub reads: pre-fetch/aggregate with `gh` + `jq` in `steps:`, store compact JSON in `/tmp/gh-aw/data/`, and point the prompt to those files (see `.github/aw/token-optimization.md` for details).
2. Keep tool surface minimal: default to `tools.github.mode: gh-proxy`, include only required toolsets, and prefer `bash` + `gh` for simple reads.
3. For batch workloads, split items into compact data and suggest sub-agent processing with `model: small`.
4. Keep prompts compact: concise imperative instructions, explicit file paths, single-line `noop` guidance, and stable instructions before dynamic content.
## Progressive Disclosure Rules
1. Never dump all options at once; ask one targeted question at a time.
2. Skip questions when answers are inferable from prior user statements.
3. Offer smart defaults and request confirmation instead of over-questioning.
4. Ask at most 5 questions before presenting a summary; then ask "anything else?" if needed.
5. Detect done signals (`that's it`, `looks good`, `generate it`) and proceed to generation.
## Confirmation Format
Use this exact structure:
```text
📋 Proposed workflow:
- Name: <workflow-id>
- Trigger: <event + key options>
- Engine: <engine or default>
- Tools: <tool summary>
- Safe outputs: <list or none>
- Network: <allowed summary>
- Integrations/Auth: <service/mcp + required secrets/env vars>
- Deployment: <GitHub.com or GHEC/GHES details>
- Intent: <one-sentence task>
```
Then ask: **"Ready to generate, or want to adjust anything?"**
## Generation Template
After confirmation, generate one workflow file using the same skeleton style as `.github/aw/create-agentic-workflow.md`.
```markdown
---
emoji: <emoji>
description: <brief description>
on:
<trigger config>
permissions:
contents: read
issues: read
pull-requests: read
tools:
github:
mode: gh-proxy
toolsets: [default]
steps:
- name: <optional data prefetch>
run: |
mkdir -p /tmp/gh-aw/data
<gh + jq commands that produce compact JSON>
safe-outputs:
<safe-output-types-if-needed>
network:
allowed:
- defaults
- <additional entries if needed>
---
# <Workflow Name>
## Task
<clear instructions tied to trigger context>
If `steps:` includes pre-fetch commands, read the resulting `/tmp/gh-aw/data/*.json` files instead of broad live re-fetches.
## Safe Outputs
- Use configured safe outputs for all visible write actions.
- Call `noop` with a short reason when no action is needed.
```
## Validation Checklist
Before final output, run this internal self-check:
- [ ] Agent job permissions remain read-only (writes only via safe outputs)
- [ ] `safe-outputs:` covers every write action mentioned in prompt/instructions
- [ ] Network access is scoped; avoid blanket wildcard entries
- [ ] Trigger matches the user's intended activation event
- [ ] Prompt instructs agent to call `noop` when no action is needed
- [ ] Unnecessary defaults are omitted (for example `engine: copilot`)
- [ ] If reading GitHub data, `steps:` pre-fetches compact JSON (DataOps)
- [ ] `tools.github.mode` is `gh-proxy` unless broader MCP toolsets are explicitly needed
- [ ] Only required toolsets are listed (avoid blanket toolset lists)
- [ ] Prompt references specific pre-computed file paths
- [ ] For batch processing (>5 items), sub-agent pattern is suggested
- [ ] For each third-party service/MCP integration, required secrets/env vars are listed
- [ ] Auth guidance includes least-privilege token scope recommendations
- [ ] For GHEC/GHES deployments, `engine.api-target` and GHES compatibility guidance are included when needed
## References (load only when needed)
In-repo references:
- `.github/aw/syntax.md` (index → `.github/aw/syntax-core.md`, `.github/aw/syntax-agentic.md`, `.github/aw/syntax-tools-imports.md`)
- `.github/aw/safe-outputs.md` (index → `.github/aw/safe-outputs-content.md`, `.github/aw/safe-outputs-management.md`, `.github/aw/safe-outputs-automation.md`, `.github/aw/safe-outputs-runtime.md`)
- `.github/aw/network.md`
- `.github/aw/patterns.md`
- `.github/aw/subagents.md`
- `.github/aw/token-optimization.md`
- `.github/aw/triggers.md`
- `.github/aw/create-agentic-workflow.md`
Portable HTTPS references:
- `https://github.com/github/gh-aw/blob/main/.github/aw/syntax.md` (index → `.../syntax-core.md`, `.../syntax-agentic.md`, `.../syntax-tools-imports.md`)
- `https://github.com/github/gh-aw/blob/main/.github/aw/safe-outputs.md` (index → `.../safe-outputs-content.md`, `.../safe-outputs-management.md`, `.../safe-outputs-automation.md`, `.../safe-outputs-runtime.md`)
- `https://github.com/github/gh-aw/blob/main/.github/aw/network.md`
- `https://github.com/github/gh-aw/blob/main/.github/aw/patterns.md`
- `https://github.com/github/gh-aw/blob/main/.github/aw/triggers.md`
- `https://github.com/github/gh-aw/blob/main/.github/aw/create-agentic-workflow.md`

View file

@ -0,0 +1,80 @@
---
name: agentic-workflows
description: Route gh-aw workflow design/create/debug/upgrade requests to the right prompts.
---
# Agentic Workflows Router
Use this skill when a user asks to design, create, update, debug, or upgrade GitHub Agentic Workflows in this repository.
This skill is a dispatcher: identify the task type, load the matching workflow prompt/skill file, and follow it directly. Keep responses concise and ask a clarifying question if the correct prompt is unclear.
Read only the files you need:
Load these files from `github/gh-aw` (they are not available locally).
- `.github/aw/agentic-chat.md`
- `.github/aw/agentic-workflows-mcp.md`
- `.github/aw/asciicharts.md`
- `.github/aw/campaign.md`
- `.github/aw/charts-trending.md`
- `.github/aw/charts.md`
- `.github/aw/cli-commands.md`
- `.github/aw/context.md`
- `.github/aw/create-agentic-workflow.md`
- `.github/aw/create-shared-agentic-workflow.md`
- `.github/aw/debug-agentic-workflow.md`
- `.github/aw/dependabot.md`
- `.github/aw/deployment-status.md`
- `.github/aw/experiments.md`
- `.github/aw/github-agentic-workflows.md`
- `.github/aw/github-mcp-server.md`
- `.github/aw/llms.md`
- `.github/aw/mcp-clis.md`
- `.github/aw/memory.md`
- `.github/aw/messages.md`
- `.github/aw/network.md`
- `.github/aw/optimize-agentic-workflow.md`
- `.github/aw/patterns.md`
- `.github/aw/pr-reviewer.md`
- `.github/aw/report.md`
- `.github/aw/reuse.md`
- `.github/aw/safe-outputs-automation.md`
- `.github/aw/safe-outputs-content.md`
- `.github/aw/safe-outputs-management.md`
- `.github/aw/safe-outputs-runtime.md`
- `.github/aw/safe-outputs.md`
- `.github/aw/serena-tool.md`
- `.github/aw/shared-safe-jobs.md`
- `.github/aw/skills.md`
- `.github/aw/subagents.md`
- `.github/aw/syntax-agentic.md`
- `.github/aw/syntax-core.md`
- `.github/aw/syntax-tools-imports.md`
- `.github/aw/syntax.md`
- `.github/aw/test-coverage.md`
- `.github/aw/test-expression.md`
- `.github/aw/token-optimization.md`
- `.github/aw/triggers.md`
- `.github/aw/update-agentic-workflow.md`
- `.github/aw/upgrade-agentic-workflows.md`
- `.github/aw/visual-regression.md`
- `.github/aw/workflow-constraints.md`
- `.github/aw/workflow-editing.md`
- `.github/aw/workflow-patterns.md`
- `.github/skills/agentic-workflow-designer/SKILL.md`
After loading the matching workflow prompt or skill, follow it directly:
- Design workflows from scratch via interview: `skills/agentic-workflow-designer/SKILL.md`
- Create new workflows: `.github/aw/create-agentic-workflow.md`
- Update existing workflows: `.github/aw/update-agentic-workflow.md`
- Debug, audit, or investigate workflows: `.github/aw/debug-agentic-workflow.md`
- Upgrade workflows and fix deprecations: `.github/aw/upgrade-agentic-workflows.md`
- Create shared components or MCP wrappers: `.github/aw/create-shared-agentic-workflow.md`
- Create report-generating workflows: `.github/aw/report.md`
- Fix Dependabot manifest PRs: `.github/aw/dependabot.md`
- Analyze coverage workflows: `.github/aw/test-coverage.md`
- Render compact markdown charts: `.github/aw/asciicharts.md`
- Map CLI commands to MCP usage: `.github/aw/cli-commands.md`
- Choose workflow architecture and patterns: `.github/aw/patterns.md`
- Optimize token usage and cost: `.github/aw/token-optimization.md`
When the task involves OTEL, OTLP, traces, observability backends, or telemetry-driven analysis, also read and follow `skills/otel-queries/SKILL.md` after loading the matching workflow prompt or skill.

79
.github/skills/benchmark/SKILL.md vendored Normal file
View file

@ -0,0 +1,79 @@
---
name: benchmark
description: Measure Z3 performance on a formula or file. Collects wall-clock time, theory solver statistics, memory usage, and conflict counts. Results are logged to z3agent.db for longitudinal tracking.
---
Given an SMT-LIB2 formula or file, run Z3 with statistics enabled and report performance characteristics. This is useful for identifying performance regressions, comparing tactic strategies, and profiling theory solver workload distribution.
# Step 1: Run Z3 with statistics
Action:
Invoke benchmark.py with the formula or file. Use `--runs N` for
repeated timing.
Expectation:
The script invokes `z3 -st`, parses the statistics block, and prints
a performance summary. A run entry is logged to z3agent.db.
Result:
Timing and statistics are displayed. Proceed to Step 2 to interpret.
```bash
python3 scripts/benchmark.py --file problem.smt2
python3 scripts/benchmark.py --file problem.smt2 --runs 5
python3 scripts/benchmark.py --formula "(declare-const x Int)..." --debug
```
# Step 2: Interpret the output
Action:
Review wall-clock time, memory usage, conflict counts, and per-theory
breakdowns.
Expectation:
A complete performance profile including min/median/max timing when
multiple runs are requested.
Result:
If performance is acceptable, no action needed.
If slow, try **simplify** to reduce the formula or adjust tactic strategies.
The output includes:
- wall-clock time (ms)
- result (sat/unsat/unknown/timeout)
- memory usage (MB)
- conflicts, decisions, propagations
- per-theory breakdown (arithmetic, bv, array, etc.)
With `--runs N`, the script runs Z3 N times and reports min/median/max timing.
# Step 3: Compare over time
Action:
Query past benchmark runs from z3agent.db to detect regressions or
improvements.
Expectation:
Historical run data is available for comparison, ordered by recency.
Result:
If performance regressed, investigate recent formula or tactic changes.
If improved, record the successful configuration.
```bash
python3 ../../shared/z3db.py runs --skill benchmark --last 20
python3 ../../shared/z3db.py query "SELECT smtlib2, result, stats FROM formulas WHERE run_id IN (SELECT run_id FROM runs WHERE skill='benchmark') ORDER BY run_id DESC LIMIT 5"
```
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| formula | string | no | | SMT-LIB2 formula |
| file | path | no | | path to .smt2 file |
| runs | int | no | 1 | number of repeated runs for timing |
| timeout | int | no | 60 | seconds per run |
| z3 | path | no | auto | path to z3 binary |
| debug | flag | no | off | verbose tracing |
| db | path | no | .z3-agent/z3agent.db | logging database |

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""
benchmark.py: measure Z3 performance with statistics.
Usage:
python benchmark.py --file problem.smt2
python benchmark.py --file problem.smt2 --runs 5
"""
import argparse
import statistics
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, run_z3, parse_stats, setup_logging
def main():
parser = argparse.ArgumentParser(prog="benchmark")
parser.add_argument("--formula")
parser.add_argument("--file")
parser.add_argument("--runs", type=int, default=1)
parser.add_argument("--timeout", type=int, default=60)
parser.add_argument("--z3", default=None)
parser.add_argument("--db", default=None)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
if args.file:
formula = Path(args.file).read_text()
elif args.formula:
formula = args.formula
else:
parser.error("provide --formula or --file")
return
db = Z3DB(args.db)
timings = []
for i in range(args.runs):
run_id = db.start_run("benchmark", formula)
result = run_z3(
formula,
z3_bin=args.z3,
timeout=args.timeout,
args=["-st"],
debug=args.debug,
)
stats = parse_stats(result["stdout"])
db.log_formula(run_id, formula, result["result"], stats=stats)
db.finish_run(
run_id, result["result"], result["duration_ms"], result["exit_code"]
)
timings.append(result["duration_ms"])
if args.runs == 1:
print(f"result: {result['result']}")
print(f"time: {result['duration_ms']}ms")
if stats:
print("statistics:")
for k, v in sorted(stats.items()):
print(f" :{k} {v}")
if args.runs > 1:
print(f"runs: {args.runs}")
print(f"min: {min(timings)}ms")
print(f"median: {statistics.median(timings):.0f}ms")
print(f"max: {max(timings)}ms")
print(f"result: {result['result']}")
db.close()
sys.exit(0 if result["exit_code"] == 0 else 1)
if __name__ == "__main__":
main()

73
.github/skills/encode/SKILL.md vendored Normal file
View file

@ -0,0 +1,73 @@
---
name: encode
description: Translate constraint problems into SMT-LIB2 or Z3 Python API code. Handles common problem classes including scheduling, graph coloring, arithmetic puzzles, and verification conditions.
---
Given a problem description (natural language, pseudocode, or a partial formulation), produce a complete, syntactically valid SMT-LIB2 encoding or Z3 Python script. The encoding should declare all variables, assert all constraints, and include the appropriate check-sat / get-model commands.
# Step 1: Identify the problem class
Action:
Determine the SMT theory and variable sorts required by the problem
description.
Expectation:
A clear mapping from the problem to one of the supported theories
(LIA, LRA, QF_BV, etc.).
Result:
If the theory is identified, proceed to Step 2. If the problem spans
multiple theories, select the appropriate combined logic.
| Problem class | Theory | Typical sorts |
|---------------|--------|---------------|
| Integer arithmetic | LIA / NIA | Int |
| Real arithmetic | LRA / NRA | Real |
| Bitvector operations | QF_BV | (_ BitVec N) |
| Arrays and maps | QF_AX | (Array Int Int) |
| Strings and regex | QF_S | String, RegLan |
| Uninterpreted functions | QF_UF | custom sorts |
| Mixed theories | AUFLIA, etc. | combination |
# Step 2: Generate the encoding
Action:
Invoke encode.py with the problem description and desired output format.
Expectation:
The script produces a complete SMT-LIB2 file or Z3 Python script with
all declarations, constraints, and check-sat commands.
Result:
For `smtlib2` format: pass the output to **solve**.
For `python` format: execute the script directly.
Proceed to Step 3 for validation.
```bash
python3 scripts/encode.py --problem "Find integers x, y such that x^2 + y^2 = 25 and x > 0" --format smtlib2
python3 scripts/encode.py --problem "Schedule 4 tasks on 2 machines minimizing makespan" --format python
```
# Step 3: Validate the encoding
Action:
The script runs a syntax check by piping the output through `z3 -in`
in parse-only mode.
Expectation:
No parse errors. If errors occur, the offending line is reported.
Result:
On success: the encoding is ready for **solve**, **prove**, or **optimize**.
On parse error: fix the reported line and re-run.
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| problem | string | yes | | problem description |
| format | string | no | smtlib2 | output format: smtlib2 or python |
| output | path | no | stdout | write to file instead of stdout |
| validate | flag | no | on | run syntax check on the output |
| debug | flag | no | off | verbose tracing |
| db | path | no | .z3-agent/z3agent.db | logging database |

149
.github/skills/encode/scripts/encode.py vendored Normal file
View file

@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
encode.py: validate and format SMT-LIB2 encodings.
Usage:
python encode.py --validate formula.smt2
python encode.py --validate formula.smt2 --debug
"""
import argparse
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, run_z3, setup_logging
VALIDATION_TIMEOUT = 5
def read_input(path_or_stdin: str) -> str:
"""Read formula from a file path or stdin (when path is '-')."""
if path_or_stdin == "-":
return sys.stdin.read()
p = Path(path_or_stdin)
if not p.exists():
print(f"file not found: {p}", file=sys.stderr)
sys.exit(1)
return p.read_text()
def find_errors(output: str) -> list:
"""Extract (error ...) messages from Z3 output."""
return re.findall(r'\(error\s+"([^"]+)"\)', output)
def validate(formula: str, z3_bin: str = None, debug: bool = False) -> dict:
"""
Validate an SMT-LIB2 formula by piping it through z3 -in.
Returns a dict with 'valid' (bool), 'errors' (list), and 'raw' output.
"""
result = run_z3(
formula,
z3_bin=z3_bin,
timeout=VALIDATION_TIMEOUT,
debug=debug,
)
errors = find_errors(result["stdout"]) + find_errors(result["stderr"])
if result["result"] == "timeout":
# Timeout during validation is not a syntax error: the formula
# parsed successfully but solving exceeded the limit. That counts
# as syntactically valid.
return {"valid": True, "errors": [], "raw": result}
if errors or result["exit_code"] != 0:
return {"valid": False, "errors": errors, "raw": result}
return {"valid": True, "errors": [], "raw": result}
def report_errors(errors: list, formula: str):
"""Print each syntax error with surrounding context."""
lines = formula.splitlines()
print(f"validation failed: {len(errors)} error(s)", file=sys.stderr)
for err in errors:
print(f" : {err}", file=sys.stderr)
if len(lines) <= 20:
print("formula:", file=sys.stderr)
for i, line in enumerate(lines, 1):
print(f" {i:3d} {line}", file=sys.stderr)
def write_output(formula: str, output_path: str, fmt: str):
"""Write the validated formula to a file or stdout."""
if fmt == "python":
print(
"python format output is generated by the agent, " "not by this script",
file=sys.stderr,
)
sys.exit(1)
if output_path:
Path(output_path).write_text(formula)
print(f"written to {output_path}")
else:
print(formula)
def main():
parser = argparse.ArgumentParser(prog="encode")
parser.add_argument(
"--validate",
metavar="FILE",
help="path to .smt2 file to validate, or '-' for stdin",
)
parser.add_argument(
"--format",
choices=["smtlib2", "python"],
default="smtlib2",
help="output format (default: smtlib2)",
)
parser.add_argument(
"--output",
metavar="FILE",
default=None,
help="write result to file instead of stdout",
)
parser.add_argument("--z3", default=None, help="path to z3 binary")
parser.add_argument("--db", default=None)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
if not args.validate:
parser.error("provide --validate FILE")
return
formula = read_input(args.validate)
db = Z3DB(args.db)
run_id = db.start_run("encode", formula)
result = validate(formula, z3_bin=args.z3, debug=args.debug)
if result["valid"]:
db.log_formula(run_id, formula, "valid")
db.finish_run(run_id, "valid", result["raw"]["duration_ms"], 0)
write_output(formula, args.output, args.format)
db.close()
sys.exit(0)
else:
db.log_formula(run_id, formula, "error")
for err in result["errors"]:
db.log_finding(run_id, "syntax", err, severity="error")
db.finish_run(
run_id,
"error",
result["raw"]["duration_ms"],
result["raw"]["exit_code"],
)
report_errors(result["errors"], formula)
db.close()
sys.exit(1)
if __name__ == "__main__":
main()

83
.github/skills/explain/SKILL.md vendored Normal file
View file

@ -0,0 +1,83 @@
---
name: explain
description: Parse and interpret Z3 output for human consumption. Handles models, unsat cores, proofs, statistics, and error messages. Translates solver internals into plain-language explanations.
---
Given raw Z3 output (from the **solve**, **prove**, **optimize**, or **benchmark** skills), produce a structured explanation. This skill is for cases where the solver output is large, nested, or otherwise difficult to read directly.
# Step 1: Identify the output type
Action:
Determine the category of Z3 output to explain: model, core,
statistics, error, or proof.
Expectation:
The output type maps to one of the recognized formats in the table below.
Result:
If the type is ambiguous, use `--type auto` and let the script detect it.
Proceed to Step 2.
| Output contains | Explanation type |
|----------------|-----------------|
| `(define-fun ...)` blocks | model explanation |
| unsat core labels | conflict explanation |
| `:key value` statistics | performance breakdown |
| `(error ...)` | error diagnosis |
| proof terms | proof sketch |
# Step 2: Run the explainer
Action:
Invoke explain.py with the output file or stdin.
Expectation:
The script auto-detects the output type and produces a structured
plain-language summary.
Result:
A formatted explanation is printed. If detection fails, re-run with
an explicit `--type` flag.
```bash
python3 scripts/explain.py --file output.txt
python3 scripts/explain.py --stdin < output.txt
python3 scripts/explain.py --file output.txt --debug
```
# Step 3: Interpret the explanation
Action:
Review the structured explanation for accuracy and completeness.
Expectation:
Models list each variable with its value and sort. Cores list
conflicting assertions. Statistics show time and memory breakdowns.
Result:
Use the explanation to answer the user query or to guide the next
skill invocation.
For models:
- Each variable is listed with its value and sort
- Array and function interpretations are expanded
- Bitvector values are shown in decimal and hex
For unsat cores:
- The conflicting named assertions are listed
- A minimal conflict set is highlighted
For statistics:
- Time breakdown by phase (preprocessing, solving, model construction)
- Theory solver load distribution
- Memory high-water mark
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| file | path | no | | file containing Z3 output |
| stdin | flag | no | off | read from stdin |
| type | string | no | auto | force output type: model, core, stats, error |
| debug | flag | no | off | verbose tracing |
| db | path | no | .z3-agent/z3agent.db | logging database |

View file

@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""
explain.py: interpret Z3 output in a readable form.
Usage:
python explain.py --file output.txt
echo "sat\n(model ...)" | python explain.py --stdin
"""
import argparse
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, parse_model, parse_stats, parse_unsat_core, setup_logging
def detect_type(text: str) -> str:
if "(define-fun" in text:
return "model"
if "(error" in text:
return "error"
if re.search(r":\S+\s+[\d.]+", text):
return "stats"
first = text.strip().split("\n")[0].strip()
if first == "unsat":
return "core"
return "unknown"
def explain_model(text: str):
model = parse_model(text)
if not model:
print("no model found in output")
return
print("satisfying assignment:")
for name, val in model.items():
# show hex for large integers (likely bitvectors)
try:
n = int(val)
if abs(n) > 255:
print(f" {name} = {val} (0x{n:x})")
else:
print(f" {name} = {val}")
except ValueError:
print(f" {name} = {val}")
def explain_core(text: str):
core = parse_unsat_core(text)
if core:
print(f"conflicting assertions ({len(core)}):")
for label in core:
print(f" {label}")
else:
print("unsat (no named assertions for core extraction)")
def explain_stats(text: str):
stats = parse_stats(text)
if not stats:
print("no statistics found")
return
print("performance breakdown:")
for k in sorted(stats):
print(f" :{k} {stats[k]}")
if "time" in stats:
print(f"\ntotal time: {stats['time']}s")
if "memory" in stats:
print(f"peak memory: {stats['memory']} MB")
def explain_error(text: str):
errors = re.findall(r'\(error\s+"([^"]+)"\)', text)
if errors:
print(f"Z3 reported {len(errors)} error(s):")
for e in errors:
print(f" {e}")
else:
print("error in output but could not parse message")
def main():
parser = argparse.ArgumentParser(prog="explain")
parser.add_argument("--file")
parser.add_argument("--stdin", action="store_true")
parser.add_argument(
"--type", choices=["model", "core", "stats", "error", "auto"], default="auto"
)
parser.add_argument("--db", default=None)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
if args.file:
text = Path(args.file).read_text()
elif args.stdin:
text = sys.stdin.read()
else:
parser.error("provide --file or --stdin")
return
output_type = args.type if args.type != "auto" else detect_type(text)
db = Z3DB(args.db)
run_id = db.start_run("explain", text[:200])
if output_type == "model":
explain_model(text)
elif output_type == "core":
explain_core(text)
elif output_type == "stats":
explain_stats(text)
elif output_type == "error":
explain_error(text)
else:
print("could not determine output type")
print("raw output:")
print(text[:500])
db.finish_run(run_id, "success", 0, 0)
db.close()
if __name__ == "__main__":
main()

92
.github/skills/memory-safety/SKILL.md vendored Normal file
View file

@ -0,0 +1,92 @@
---
name: memory-safety
description: Run AddressSanitizer and UndefinedBehaviorSanitizer on the Z3 test suite to detect memory errors, undefined behavior, and leaks. Logs each finding to z3agent.db.
---
Build Z3 with compiler-based sanitizer instrumentation, execute the test suite, and parse the runtime output for memory safety violations. Supported sanitizers are AddressSanitizer (heap and stack buffer overflows, use-after-free, double-free, memory leaks) and UndefinedBehaviorSanitizer (signed integer overflow, null pointer dereference, misaligned access, shift errors). Findings are deduplicated and stored in z3agent.db for triage and longitudinal tracking.
# Step 1: Configure and build
Action:
Invoke the script with the desired sanitizer flag. The script calls
cmake with the appropriate `-fsanitize` flags and builds the `test-z3`
target. Each sanitizer uses a separate build directory to avoid flag
conflicts.
Expectation:
cmake configures successfully and make compiles the instrumented binary.
If a prior build exists with matching flags, only incremental compilation
runs.
Result:
On success: an instrumented `test-z3` binary is ready in the build
directory. Proceed to Step 2.
On failure: verify compiler support for the requested sanitizer flags
and review cmake output.
```bash
python3 scripts/memory_safety.py --sanitizer asan
python3 scripts/memory_safety.py --sanitizer ubsan
python3 scripts/memory_safety.py --sanitizer both
```
To reuse an existing build:
```bash
python3 scripts/memory_safety.py --sanitizer asan --skip-build --build-dir build/sanitizer-asan
```
# Step 2: Run and collect
Action:
Execute the instrumented test binary with halt_on_error=0 so all
violations are reported rather than aborting on the first.
Expectation:
The script parses AddressSanitizer, UndefinedBehaviorSanitizer, and
LeakSanitizer patterns from combined output, extracts source locations,
and deduplicates by category/file/line.
Result:
On `clean`: no violations detected.
On `findings`: one or more violations found, each printed with severity,
category, message, and source location.
On `timeout`: test suite did not finish; increase timeout or investigate.
On `error`: build or execution failed before sanitizer output.
```bash
python3 scripts/memory_safety.py --sanitizer asan --timeout 900 --debug
```
# Step 3: Interpret results
Action:
Review printed findings and query z3agent.db for historical comparison.
Expectation:
Each finding includes severity, category, message, and source location.
The database query returns prior runs for trend analysis.
Result:
On `clean`: no action required; proceed.
On `findings`: triage by severity and category. Compare against prior
runs to distinguish new regressions from known issues.
On `timeout`: increase the deadline or investigate a possible infinite
loop.
On `error`: inspect build logs before re-running.
Query past runs:
```bash
python3 ../../shared/z3db.py runs --skill memory-safety --last 10
python3 ../../shared/z3db.py query "SELECT category, severity, file, line, message FROM findings WHERE run_id IN (SELECT run_id FROM runs WHERE skill='memory-safety') ORDER BY run_id DESC LIMIT 20"
```
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| sanitizer | choice | no | asan | which sanitizer to enable: asan, ubsan, or both |
| build-dir | path | no | build/sanitizer-{name} | path to the build directory |
| timeout | int | no | 600 | seconds before killing the test run |
| skip-build | flag | no | off | reuse an existing instrumented build |
| debug | flag | no | off | verbose cmake, make, and test output |
| db | path | no | .z3-agent/z3agent.db | path to the logging database |

View file

@ -0,0 +1,274 @@
#!/usr/bin/env python3
"""
memory_safety.py: run sanitizer checks on Z3 test suite.
Usage:
python memory_safety.py --sanitizer asan
python memory_safety.py --sanitizer ubsan --debug
"""
import argparse
import logging
import os
import re
import shutil
import subprocess
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, require_repo_root, setup_logging
logger = logging.getLogger("z3agent")
SANITIZER_FLAGS = {
"asan": "-fsanitize=address -fno-omit-frame-pointer",
"ubsan": "-fsanitize=undefined -fno-omit-frame-pointer",
}
ASAN_ERROR = re.compile(r"ERROR:\s*AddressSanitizer:\s*(\S+)")
UBSAN_ERROR = re.compile(r":\d+:\d+:\s*runtime error:\s*(.+)")
LEAK_ERROR = re.compile(r"ERROR:\s*LeakSanitizer:")
LOCATION = re.compile(r"(\S+\.(?:cpp|c|h|hpp)):(\d+)")
def check_dependencies():
"""Fail early if required build tools are not on PATH."""
missing = []
if not shutil.which("cmake"):
missing.append(("cmake", "sudo apt install cmake"))
if not shutil.which("make"):
missing.append(("make", "sudo apt install build-essential"))
cc = shutil.which("clang") or shutil.which("gcc")
if not cc:
missing.append(("clang or gcc", "sudo apt install clang"))
if missing:
print("required tools not found:", file=sys.stderr)
for tool, install in missing:
print(f" {tool}: {install}", file=sys.stderr)
sys.exit(1)
def build_is_configured(build_dir: Path, sanitizer: str) -> bool:
"""Check whether the build directory already has a matching cmake config."""
cache = build_dir / "CMakeCache.txt"
if not cache.is_file():
return False
expected = SANITIZER_FLAGS[sanitizer].split()[0]
return expected in cache.read_text()
def configure(build_dir: Path, sanitizer: str, repo_root: Path) -> bool:
"""Run cmake with the requested sanitizer flags."""
flags = SANITIZER_FLAGS[sanitizer]
build_dir.mkdir(parents=True, exist_ok=True)
cmd = [
"cmake", str(repo_root),
f"-DCMAKE_C_FLAGS={flags}",
f"-DCMAKE_CXX_FLAGS={flags}",
f"-DCMAKE_EXE_LINKER_FLAGS={flags}",
"-DCMAKE_BUILD_TYPE=Debug",
"-DZ3_BUILD_TEST=ON",
]
logger.info("configuring %s build in %s", sanitizer, build_dir)
logger.debug("cmake command: %s", " ".join(cmd))
proc = subprocess.run(cmd, cwd=build_dir, capture_output=True, text=True)
if proc.returncode != 0:
logger.error("cmake failed:\n%s", proc.stderr)
return False
return True
def compile_tests(build_dir: Path) -> bool:
"""Compile the test-z3 target."""
nproc = os.cpu_count() or 4
cmd = ["make", f"-j{nproc}", "test-z3"]
logger.info("compiling test-z3 (%d parallel jobs)", nproc)
proc = subprocess.run(cmd, cwd=build_dir, capture_output=True, text=True)
if proc.returncode != 0:
logger.error("compilation failed:\n%s", proc.stderr[-2000:])
return False
return True
def run_tests(build_dir: Path, timeout: int) -> dict:
"""Execute test-z3 under sanitizer runtime and capture output."""
test_bin = build_dir / "test-z3"
if not test_bin.is_file():
logger.error("test-z3 not found at %s", test_bin)
return {"stdout": "", "stderr": "binary not found", "exit_code": -1,
"duration_ms": 0}
env = os.environ.copy()
env["ASAN_OPTIONS"] = "detect_leaks=1:halt_on_error=0:print_stacktrace=1"
env["UBSAN_OPTIONS"] = "print_stacktrace=1:halt_on_error=0"
cmd = [str(test_bin), "/a"]
logger.info("running: %s", " ".join(cmd))
start = time.monotonic()
try:
proc = subprocess.run(
cmd, capture_output=True, text=True, timeout=timeout,
cwd=build_dir, env=env,
)
except subprocess.TimeoutExpired:
ms = int((time.monotonic() - start) * 1000)
logger.warning("test-z3 timed out after %dms", ms)
return {"stdout": "", "stderr": "timeout", "exit_code": -1,
"duration_ms": ms}
ms = int((time.monotonic() - start) * 1000)
logger.debug("exit_code=%d duration=%dms", proc.returncode, ms)
return {
"stdout": proc.stdout,
"stderr": proc.stderr,
"exit_code": proc.returncode,
"duration_ms": ms,
}
def parse_findings(output: str) -> list:
"""Extract sanitizer error reports from combined stdout and stderr."""
findings = []
lines = output.split("\n")
for i, line in enumerate(lines):
entry = None
m = ASAN_ERROR.search(line)
if m:
entry = {"category": "asan", "message": m.group(1),
"severity": "high"}
if not entry:
m = LEAK_ERROR.search(line)
if m:
entry = {"category": "leak",
"message": "detected memory leaks",
"severity": "high"}
if not entry:
m = UBSAN_ERROR.search(line)
if m:
entry = {"category": "ubsan", "message": m.group(1),
"severity": "medium"}
if not entry:
continue
file_path, line_no = None, None
window = lines[max(0, i - 2):i + 5]
for ctx in window:
loc = LOCATION.search(ctx)
if loc and "/usr/" not in loc.group(1):
file_path = loc.group(1)
line_no = int(loc.group(2))
break
entry["file"] = file_path
entry["line"] = line_no
entry["raw"] = line.strip()
findings.append(entry)
return findings
def deduplicate(findings: list) -> list:
"""Remove duplicate reports at the same category, file, and line."""
seen = set()
result = []
for f in findings:
key = (f["category"], f["file"], f["line"], f["message"])
if key in seen:
continue
seen.add(key)
result.append(f)
return result
def main():
parser = argparse.ArgumentParser(prog="memory-safety")
parser.add_argument("--sanitizer", choices=["asan", "ubsan", "both"],
default="asan",
help="sanitizer to enable (default: asan)")
parser.add_argument("--build-dir", default=None,
help="path to build directory")
parser.add_argument("--timeout", type=int, default=600,
help="seconds before killing test run")
parser.add_argument("--skip-build", action="store_true",
help="reuse existing instrumented build")
parser.add_argument("--db", default=None,
help="path to z3agent.db")
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
check_dependencies()
repo_root = require_repo_root()
sanitizers = ["asan", "ubsan"] if args.sanitizer == "both" else [args.sanitizer]
all_findings = []
db = Z3DB(args.db)
for san in sanitizers:
if args.build_dir:
build_dir = Path(args.build_dir)
else:
build_dir = repo_root / "build" / f"sanitizer-{san}"
run_id = db.start_run("memory-safety", f"sanitizer={san}")
db.log(f"sanitizer: {san}, build: {build_dir}", run_id=run_id)
if not args.skip_build:
needs_configure = not build_is_configured(build_dir, san)
if needs_configure and not configure(build_dir, san, repo_root):
db.finish_run(run_id, "error", 0, exit_code=1)
print(f"FAIL: cmake configuration failed for {san}")
continue
if not compile_tests(build_dir):
db.finish_run(run_id, "error", 0, exit_code=1)
print(f"FAIL: compilation failed for {san}")
continue
result = run_tests(build_dir, args.timeout)
combined = result["stdout"] + "\n" + result["stderr"]
findings = deduplicate(parse_findings(combined))
for f in findings:
db.log_finding(
run_id,
category=f["category"],
message=f["message"],
severity=f["severity"],
file=f["file"],
line=f["line"],
details={"raw": f["raw"]},
)
status = "clean" if not findings else "findings"
if result["exit_code"] == -1:
status = "timeout" if "timeout" in result["stderr"] else "error"
db.finish_run(run_id, status, result["duration_ms"], result["exit_code"])
all_findings.extend(findings)
print(f"{san}: {len(findings)} finding(s), {result['duration_ms']}ms")
if all_findings:
print(f"\nTotal: {len(all_findings)} finding(s)")
for f in all_findings:
loc = f"{f['file']}:{f['line']}" if f["file"] else "unknown location"
print(f" [{f['severity']}] {f['category']}: {f['message']} at {loc}")
db.close()
sys.exit(1)
else:
print("\nNo sanitizer findings.")
db.close()
sys.exit(0)
if __name__ == "__main__":
main()

75
.github/skills/optimize/SKILL.md vendored Normal file
View file

@ -0,0 +1,75 @@
---
name: optimize
description: Solve constrained optimization problems using Z3. Supports minimization and maximization of objective functions over integer, real, and bitvector domains.
---
Given a set of constraints and an objective function, find the optimal value. Z3 supports both hard constraints (must hold) and soft constraints (weighted preferences), as well as lexicographic multi-objective optimization.
# Step 1: Formulate the problem
Action:
Write constraints and an objective using `(minimize ...)` or
`(maximize ...)` directives, followed by `(check-sat)` and `(get-model)`.
Expectation:
A valid SMT-LIB2 formula with at least one optimization directive and
all variables declared.
Result:
If the formula is well-formed, proceed to Step 2. For multi-objective
problems, list directives in priority order for lexicographic optimization.
Example: minimize `x + y` subject to `x >= 1`, `y >= 2`, `x + y <= 10`:
```smtlib
(declare-const x Int)
(declare-const y Int)
(assert (>= x 1))
(assert (>= y 2))
(assert (<= (+ x y) 10))
(minimize (+ x y))
(check-sat)
(get-model)
```
# Step 2: Run the optimizer
Action:
Invoke optimize.py with the formula or file path.
Expectation:
The script prints `sat` with the optimal assignment, `unsat`, `unknown`,
or `timeout`. A run entry is logged to z3agent.db.
Result:
On `sat`: proceed to Step 3 to read the optimal values.
On `unsat` or `timeout`: check constraints for contradictions or simplify.
```bash
python3 scripts/optimize.py --file scheduling.smt2
python3 scripts/optimize.py --formula "<inline smt-lib2>" --debug
```
# Step 3: Interpret the output
Action:
Parse the objective value and satisfying assignment from the output.
Expectation:
`sat` with a model containing the optimal value, `unsat` indicating
infeasibility, or `unknown`/`timeout`.
Result:
On `sat`: report the optimal value and assignment.
On `unsat`: the constraints are contradictory, no feasible solution exists.
On `unknown`/`timeout`: relax constraints or try **simplify**.
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| formula | string | no | | SMT-LIB2 formula with minimize/maximize |
| file | path | no | | path to .smt2 file |
| timeout | int | no | 60 | seconds |
| z3 | path | no | auto | path to z3 binary |
| debug | flag | no | off | verbose tracing |
| db | path | no | .z3-agent/z3agent.db | logging database |

View file

@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
optimize.py: solve constrained optimization problems via Z3.
Usage:
python optimize.py --file scheduling.smt2
python optimize.py --formula "(declare-const x Int)..." --debug
"""
import argparse
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, run_z3, parse_model, setup_logging
def main():
parser = argparse.ArgumentParser(prog="optimize")
parser.add_argument("--formula")
parser.add_argument("--file")
parser.add_argument("--timeout", type=int, default=60)
parser.add_argument("--z3", default=None)
parser.add_argument("--db", default=None)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
if args.file:
formula = Path(args.file).read_text()
elif args.formula:
formula = args.formula
else:
parser.error("provide --formula or --file")
return
db = Z3DB(args.db)
run_id = db.start_run("optimize", formula)
result = run_z3(formula, z3_bin=args.z3, timeout=args.timeout, debug=args.debug)
model = parse_model(result["stdout"]) if result["result"] == "sat" else None
db.log_formula(run_id, formula, result["result"], str(model) if model else None)
db.finish_run(run_id, result["result"], result["duration_ms"], result["exit_code"])
print(result["result"])
if model:
for name, val in model.items():
print(f" {name} = {val}")
db.close()
sys.exit(0 if result["exit_code"] == 0 else 1)
if __name__ == "__main__":
main()

83
.github/skills/prove/SKILL.md vendored Normal file
View file

@ -0,0 +1,83 @@
---
name: prove
description: Prove validity of logical statements by negation and satisfiability checking. If the negation is unsatisfiable, the original statement is valid. Otherwise a counterexample is returned.
---
Given a conjecture (an SMT-LIB2 assertion or a natural language claim), determine whether it holds universally. The method is standard: negate the conjecture and check satisfiability. If the negation is unsatisfiable, the original is valid. If satisfiable, the model is a counterexample.
# Step 1: Prepare the negated formula
Action:
Wrap the conjecture in `(assert (not ...))` and append
`(check-sat)(get-model)`.
Expectation:
A complete SMT-LIB2 formula that negates the original conjecture with
all variables declared.
Result:
If the negation is well-formed, proceed to Step 2.
If the conjecture is natural language, run **encode** first.
Example: to prove that `(> x 3)` implies `(> x 1)`:
```smtlib
(declare-const x Int)
(assert (not (=> (> x 3) (> x 1))))
(check-sat)
(get-model)
```
# Step 2: Run the prover
Action:
Invoke prove.py with the conjecture and variable declarations.
Expectation:
The script prints `valid`, `invalid` (with counterexample), `unknown`,
or `timeout`. A run entry is logged to z3agent.db.
Result:
On `valid`: proceed to **explain** if the user needs a summary.
On `invalid`: report the counterexample directly.
On `unknown`/`timeout`: try **simplify** first, or increase the timeout.
```bash
python3 scripts/prove.py --conjecture "(=> (> x 3) (> x 1))" --vars "x:Int"
```
For file input where the file contains the full negated formula:
```bash
python3 scripts/prove.py --file negated.smt2
```
With debug tracing:
```bash
python3 scripts/prove.py --conjecture "(=> (> x 3) (> x 1))" --vars "x:Int" --debug
```
# Step 3: Interpret the output
Action:
Read the prover output to determine validity of the conjecture.
Expectation:
One of `valid`, `invalid` (with counterexample), `unknown`, or `timeout`.
Result:
On `valid`: the conjecture holds universally.
On `invalid`: the model shows a concrete counterexample.
On `unknown`/`timeout`: the conjecture may require auxiliary lemmas or induction.
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| conjecture | string | no | | the assertion to prove (without negation) |
| vars | string | no | | variable declarations as "name:sort" pairs, comma-separated |
| file | path | no | | .smt2 file with the negated formula |
| timeout | int | no | 30 | seconds |
| z3 | path | no | auto | path to z3 binary |
| debug | flag | no | off | verbose tracing |
| db | path | no | .z3-agent/z3agent.db | logging database |
Either `conjecture` (with `vars`) or `file` must be provided.

82
.github/skills/prove/scripts/prove.py vendored Normal file
View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""
prove.py: prove validity by negation + satisfiability check.
Usage:
python prove.py --conjecture "(=> (> x 3) (> x 1))" --vars "x:Int"
python prove.py --file negated.smt2
"""
import argparse
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, run_z3, parse_model, setup_logging
def build_formula(conjecture: str, vars_str: str) -> str:
lines = []
if vars_str:
for v in vars_str.split(","):
v = v.strip()
name, sort = v.split(":")
lines.append(f"(declare-const {name.strip()} {sort.strip()})")
lines.append(f"(assert (not {conjecture}))")
lines.append("(check-sat)")
lines.append("(get-model)")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(prog="prove")
parser.add_argument("--conjecture", help="assertion to prove")
parser.add_argument("--vars", help="variable declarations, e.g. 'x:Int,y:Bool'")
parser.add_argument("--file", help="path to .smt2 file with negated formula")
parser.add_argument("--timeout", type=int, default=30)
parser.add_argument("--z3", default=None)
parser.add_argument("--db", default=None)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
if args.file:
formula = Path(args.file).read_text()
elif args.conjecture:
formula = build_formula(args.conjecture, args.vars or "")
else:
parser.error("provide --conjecture or --file")
return
db = Z3DB(args.db)
run_id = db.start_run("prove", formula)
result = run_z3(formula, z3_bin=args.z3, timeout=args.timeout, debug=args.debug)
if result["result"] == "unsat":
verdict = "valid"
elif result["result"] == "sat":
verdict = "invalid"
else:
verdict = result["result"]
model = parse_model(result["stdout"]) if verdict == "invalid" else None
db.log_formula(run_id, formula, verdict, str(model) if model else None)
db.finish_run(run_id, verdict, result["duration_ms"], result["exit_code"])
print(verdict)
if model:
print("counterexample:")
for name, val in model.items():
print(f" {name} = {val}")
db.close()
# Exit 0 when we successfully determined validity or invalidity;
# exit 1 only for errors/timeouts.
sys.exit(0 if verdict in ("valid", "invalid") else 1)
if __name__ == "__main__":
main()

57
.github/skills/shared/schema.sql vendored Normal file
View file

@ -0,0 +1,57 @@
-- z3agent schema v1
PRAGMA journal_mode=WAL;
PRAGMA foreign_keys=ON;
CREATE TABLE IF NOT EXISTS runs (
run_id INTEGER PRIMARY KEY AUTOINCREMENT,
skill TEXT NOT NULL,
input_hash TEXT,
status TEXT NOT NULL DEFAULT 'running',
duration_ms INTEGER,
exit_code INTEGER,
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_runs_skill ON runs(skill);
CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);
CREATE TABLE IF NOT EXISTS formulas (
formula_id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id INTEGER REFERENCES runs(run_id) ON DELETE CASCADE,
smtlib2 TEXT NOT NULL,
result TEXT,
model TEXT,
stats TEXT,
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_formulas_run ON formulas(run_id);
CREATE INDEX IF NOT EXISTS idx_formulas_result ON formulas(result);
CREATE TABLE IF NOT EXISTS findings (
finding_id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id INTEGER REFERENCES runs(run_id) ON DELETE CASCADE,
category TEXT NOT NULL,
severity TEXT,
file TEXT,
line INTEGER,
message TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_findings_run ON findings(run_id);
CREATE INDEX IF NOT EXISTS idx_findings_category ON findings(category);
CREATE INDEX IF NOT EXISTS idx_findings_severity ON findings(severity);
CREATE TABLE IF NOT EXISTS interaction_log (
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id INTEGER REFERENCES runs(run_id) ON DELETE SET NULL,
level TEXT NOT NULL DEFAULT 'info',
message TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_log_run ON interaction_log(run_id);
CREATE INDEX IF NOT EXISTS idx_log_level ON interaction_log(level);

374
.github/skills/shared/z3db.py vendored Normal file
View file

@ -0,0 +1,374 @@
#!/usr/bin/env python3
"""
z3db: shared library and CLI for Z3 skill scripts.
Library usage:
from z3db import Z3DB, find_z3, find_repo_root, require_repo_root, run_z3
CLI usage:
python z3db.py init
python z3db.py status
python z3db.py log [--run-id N]
python z3db.py runs [--skill solve] [--last N]
python z3db.py query "SELECT ..."
"""
import argparse
import hashlib
import json
import logging
import os
import re
import shutil
import sqlite3
import subprocess
import sys
import time
from pathlib import Path
from typing import Optional
SCHEMA_PATH = Path(__file__).parent / "schema.sql"
DEFAULT_DB_DIR = ".z3-agent"
DEFAULT_DB_NAME = "z3agent.db"
logger = logging.getLogger("z3agent")
def setup_logging(debug: bool = False):
level = logging.DEBUG if debug else logging.INFO
fmt = (
"[%(levelname)s] %(message)s"
if not debug
else "[%(levelname)s %(asctime)s] %(message)s"
)
logging.basicConfig(level=level, format=fmt, stream=sys.stderr)
class Z3DB:
"""SQLite handle for z3agent.db, tracks runs, formulas, findings, logs."""
def __init__(self, db_path: Optional[str] = None):
if db_path is None:
db_dir = Path(DEFAULT_DB_DIR)
db_dir.mkdir(exist_ok=True)
db_path = str(db_dir / DEFAULT_DB_NAME)
self.db_path = db_path
self.conn = sqlite3.connect(db_path)
self.conn.execute("PRAGMA foreign_keys=ON")
self.conn.row_factory = sqlite3.Row
self._init_schema()
def _init_schema(self):
self.conn.executescript(SCHEMA_PATH.read_text())
def close(self):
self.conn.close()
def start_run(self, skill: str, input_text: str = "") -> int:
input_hash = hashlib.sha256(input_text.encode()).hexdigest()[:16]
cur = self.conn.execute(
"INSERT INTO runs (skill, input_hash) VALUES (?, ?)",
(skill, input_hash),
)
self.conn.commit()
run_id = cur.lastrowid
logger.debug("started run %d (skill=%s, hash=%s)", run_id, skill, input_hash)
return run_id
def finish_run(
self, run_id: int, status: str, duration_ms: int, exit_code: int = 0
):
self.conn.execute(
"UPDATE runs SET status=?, duration_ms=?, exit_code=? WHERE run_id=?",
(status, duration_ms, exit_code, run_id),
)
self.conn.commit()
logger.debug("finished run %d: %s (%dms)", run_id, status, duration_ms)
def log_formula(
self,
run_id: int,
smtlib2: str,
result: str = None,
model: str = None,
stats: dict = None,
) -> int:
cur = self.conn.execute(
"INSERT INTO formulas (run_id, smtlib2, result, model, stats) "
"VALUES (?, ?, ?, ?, ?)",
(run_id, smtlib2, result, model, json.dumps(stats) if stats else None),
)
self.conn.commit()
return cur.lastrowid
def log_finding(
self,
run_id: int,
category: str,
message: str,
severity: str = None,
file: str = None,
line: int = None,
details: dict = None,
) -> int:
cur = self.conn.execute(
"INSERT INTO findings (run_id, category, severity, file, line, "
"message, details) VALUES (?, ?, ?, ?, ?, ?, ?)",
(
run_id,
category,
severity,
file,
line,
message,
json.dumps(details) if details else None,
),
)
self.conn.commit()
return cur.lastrowid
def log(self, message: str, level: str = "info", run_id: int = None):
"""Write to stderr and to the interaction_log table."""
getattr(logger, level, logger.info)(message)
self.conn.execute(
"INSERT INTO interaction_log (run_id, level, message) VALUES (?, ?, ?)",
(run_id, level, message),
)
self.conn.commit()
def get_runs(self, skill: str = None, last: int = 10):
sql = "SELECT * FROM runs"
params = []
if skill:
sql += " WHERE skill = ?"
params.append(skill)
sql += " ORDER BY run_id DESC LIMIT ?"
params.append(last)
return self.conn.execute(sql, params).fetchall()
def get_status(self) -> dict:
rows = self.conn.execute(
"SELECT status, COUNT(*) as cnt FROM runs GROUP BY status"
).fetchall()
total = sum(r["cnt"] for r in rows)
by_status = {r["status"]: r["cnt"] for r in rows}
last = self.conn.execute(
"SELECT timestamp FROM runs ORDER BY run_id DESC LIMIT 1"
).fetchone()
return {
"total": total,
**by_status,
"last_run": last["timestamp"] if last else None,
}
def get_logs(self, run_id: int = None, last: int = 50):
if run_id:
return self.conn.execute(
"SELECT * FROM interaction_log WHERE run_id=? "
"ORDER BY log_id DESC LIMIT ?",
(run_id, last),
).fetchall()
return self.conn.execute(
"SELECT * FROM interaction_log ORDER BY log_id DESC LIMIT ?", (last,)
).fetchall()
def query(self, sql: str):
return self.conn.execute(sql).fetchall()
def find_z3(hint: str = None) -> str:
"""Locate the z3 binary: explicit path > build dirs > PATH."""
candidates = []
if hint:
candidates.append(hint)
repo_root = find_repo_root()
if repo_root:
for build_dir in ["build", "build/release", "build/debug"]:
candidates.append(str(repo_root / build_dir / "z3"))
path_z3 = shutil.which("z3")
if path_z3:
candidates.append(path_z3)
for c in candidates:
p = Path(c)
if p.is_file() and os.access(p, os.X_OK):
logger.debug("found z3: %s", p)
return str(p)
logger.error("z3 binary not found. Searched: %s", candidates)
sys.exit(1)
def find_repo_root() -> Optional[Path]:
"""Best-effort search for the Z3 repository root from the current directory."""
d = Path.cwd()
for _ in range(10):
if (d / "CMakeLists.txt").exists() and (d / "src").is_dir():
return d
parent = d.parent
if parent == d:
break
d = parent
return None
def require_repo_root() -> Path:
"""Return the Z3 repository root or exit the process if it is not found."""
repo_root = find_repo_root()
if repo_root is None:
logger.error("could not locate Z3 repository root")
sys.exit(1)
return repo_root
def run_z3(
formula: str,
z3_bin: str = None,
timeout: int = 30,
args: list = None,
debug: bool = False,
) -> dict:
"""Pipe an SMT-LIB2 formula into z3 -in, return parsed output."""
z3_path = find_z3(z3_bin)
cmd = [z3_path, "-in"] + (args or [])
logger.debug("cmd: %s", " ".join(cmd))
logger.debug("stdin:\n%s", formula)
start = time.monotonic()
try:
proc = subprocess.run(
cmd,
input=formula,
capture_output=True,
text=True,
timeout=timeout,
)
except subprocess.TimeoutExpired:
duration_ms = int((time.monotonic() - start) * 1000)
logger.warning("z3 timed out after %dms", duration_ms)
return {
"stdout": "",
"stderr": "timeout",
"exit_code": -1,
"duration_ms": duration_ms,
"result": "timeout",
}
duration_ms = int((time.monotonic() - start) * 1000)
logger.debug("exit_code=%d duration=%dms", proc.returncode, duration_ms)
logger.debug("stdout:\n%s", proc.stdout)
if proc.stderr:
logger.debug("stderr:\n%s", proc.stderr)
first_line = proc.stdout.strip().split("\n")[0].strip() if proc.stdout else ""
result = first_line if first_line in ("sat", "unsat", "unknown") else "error"
return {
"stdout": proc.stdout,
"stderr": proc.stderr,
"exit_code": proc.returncode,
"duration_ms": duration_ms,
"result": result,
}
def parse_model(stdout: str) -> Optional[dict]:
"""Pull define-fun entries from a (get-model) response."""
model = {}
for m in re.finditer(r"\(define-fun\s+(\S+)\s+\(\)\s+\S+\s+(.+?)\)", stdout):
model[m.group(1)] = m.group(2).strip()
return model if model else None
def parse_stats(stdout: str) -> Optional[dict]:
"""Parse :key value pairs from z3 -st output."""
stats = {}
for m in re.finditer(r":(\S+)\s+([\d.]+)", stdout):
key, val = m.group(1), m.group(2)
stats[key] = float(val) if "." in val else int(val)
return stats if stats else None
def parse_unsat_core(stdout: str) -> Optional[list]:
for line in stdout.strip().split("\n"):
line = line.strip()
if line.startswith("(") and not line.startswith("(error"):
labels = line.strip("()").split()
if labels:
return labels
return None
def cli():
parser = argparse.ArgumentParser(
description="Z3 Agent database CLI",
prog="z3db",
)
parser.add_argument("--db", default=None, help="path to z3agent.db")
parser.add_argument("--debug", action="store_true", help="verbose output")
sub = parser.add_subparsers(dest="command")
sub.add_parser("init", help="initialize the database")
sub.add_parser("status", help="show run summary")
log_p = sub.add_parser("log", help="show interaction log")
log_p.add_argument("--run-id", type=int, help="filter by run ID")
log_p.add_argument("--last", type=int, default=50)
runs_p = sub.add_parser("runs", help="list runs")
runs_p.add_argument("--skill", help="filter by skill name")
runs_p.add_argument("--last", type=int, default=10)
query_p = sub.add_parser("query", help="run raw SQL")
query_p.add_argument("sql", help="SQL query string")
args = parser.parse_args()
setup_logging(args.debug)
db = Z3DB(args.db)
if args.command == "init":
print(f"Database initialized at {db.db_path}")
elif args.command == "status":
s = db.get_status()
print(
f"Runs: {s['total']}"
f" | success: {s.get('success', 0)}"
f" | error: {s.get('error', 0)}"
f" | timeout: {s.get('timeout', 0)}"
f" | Last: {s['last_run'] or 'never'}"
)
elif args.command == "log":
for row in db.get_logs(args.run_id, args.last):
print(
f"[{row['level']}] {row['timestamp']} "
f"(run {row['run_id']}): {row['message']}"
)
elif args.command == "runs":
for row in db.get_runs(args.skill, args.last):
print(
f"#{row['run_id']} {row['skill']} {row['status']} "
f"{row['duration_ms']}ms @ {row['timestamp']}"
)
elif args.command == "query":
for row in db.query(args.sql):
print(dict(row))
else:
parser.print_help()
db.close()
if __name__ == "__main__":
cli()

76
.github/skills/simplify/SKILL.md vendored Normal file
View file

@ -0,0 +1,76 @@
---
name: simplify
description: Reduce formula complexity using Z3 tactic chains. Supports configurable tactic pipelines for boolean, arithmetic, and bitvector simplification.
---
Given a formula, apply a sequence of Z3 tactics to produce an equivalent but simpler form. This is useful for understanding what Z3 sees after preprocessing, debugging tactic selection, and reducing formula size before solving.
# Step 1: Choose tactics
Action:
Select a tactic chain from the available Z3 tactics based on the
formula's theory.
Expectation:
A comma-separated list of tactic names suitable for the formula domain.
Result:
If unsure, use the default chain: `simplify,propagate-values,ctx-simplify`.
For bitvector formulas, add `bit-blast`. Proceed to Step 2.
| Tactic | What it does |
|--------|-------------|
| simplify | constant folding, algebraic identities |
| propagate-values | substitute known equalities |
| ctx-simplify | context-dependent simplification |
| elim-uncnstr | remove unconstrained variables |
| solve-eqs | Gaussian elimination |
| bit-blast | reduce bitvectors to booleans |
| tseitin-cnf | convert to CNF |
| aig | and-inverter graph reduction |
# Step 2: Run simplification
Action:
Invoke simplify.py with the formula and optional tactic chain.
Expectation:
The script applies each tactic in sequence and prints the simplified
formula. A run entry is logged to z3agent.db.
Result:
If the output is simpler, pass it to **solve** or **prove**.
If unchanged, try a different tactic chain.
```bash
python3 scripts/simplify.py --formula "(assert (and (> x 0) (> x 0)))" --vars "x:Int"
python3 scripts/simplify.py --file formula.smt2 --tactics "simplify,propagate-values,ctx-simplify"
python3 scripts/simplify.py --file formula.smt2 --debug
```
Without `--tactics`, the script applies the default chain: `simplify`, `propagate-values`, `ctx-simplify`.
# Step 3: Interpret the output
Action:
Read the simplified formula output in SMT-LIB2 syntax.
Expectation:
One or more `(assert ...)` blocks representing equivalent subgoals.
Result:
A smaller formula indicates successful reduction. Pass the result to
**solve**, **prove**, or **optimize** as needed.
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| formula | string | no | | SMT-LIB2 formula to simplify |
| vars | string | no | | variable declarations as "name:sort" pairs |
| file | path | no | | path to .smt2 file |
| tactics | string | no | simplify,propagate-values,ctx-simplify | comma-separated tactic names |
| timeout | int | no | 30 | seconds |
| z3 | path | no | auto | path to z3 binary |
| debug | flag | no | off | verbose tracing |
| db | path | no | .z3-agent/z3agent.db | logging database |

View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""
simplify.py: apply Z3 tactics to simplify an SMT-LIB2 formula.
Usage:
python simplify.py --formula "(assert (and (> x 0) (> x 0)))" --vars "x:Int"
python simplify.py --file formula.smt2 --tactics "simplify,solve-eqs"
"""
import argparse
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, run_z3, setup_logging
DEFAULT_TACTICS = "simplify,propagate-values,ctx-simplify"
def build_tactic_formula(base_formula: str, tactics: str) -> str:
tactic_list = [t.strip() for t in tactics.split(",")]
if len(tactic_list) == 1:
tactic_expr = f"(then {tactic_list[0]} skip)"
else:
tactic_expr = "(then " + " ".join(tactic_list) + ")"
return base_formula + f"\n(apply {tactic_expr})\n"
def build_formula_from_parts(formula_str: str, vars_str: str) -> str:
lines = []
if vars_str:
for v in vars_str.split(","):
v = v.strip()
name, sort = v.split(":")
lines.append(f"(declare-const {name.strip()} {sort.strip()})")
lines.append(formula_str)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(prog="simplify")
parser.add_argument("--formula")
parser.add_argument("--vars")
parser.add_argument("--file")
parser.add_argument("--tactics", default=DEFAULT_TACTICS)
parser.add_argument("--timeout", type=int, default=30)
parser.add_argument("--z3", default=None)
parser.add_argument("--db", default=None)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
if args.file:
base = Path(args.file).read_text()
elif args.formula:
base = build_formula_from_parts(args.formula, args.vars or "")
else:
parser.error("provide --formula or --file")
return
formula = build_tactic_formula(base, args.tactics)
db = Z3DB(args.db)
run_id = db.start_run("simplify", formula)
result = run_z3(formula, z3_bin=args.z3, timeout=args.timeout, debug=args.debug)
status = "success" if result["exit_code"] == 0 else "error"
db.log_formula(run_id, formula, status)
db.finish_run(run_id, status, result["duration_ms"], result["exit_code"])
print(result["stdout"])
if result["stderr"] and result["exit_code"] != 0:
print(result["stderr"], file=sys.stderr)
db.close()
sys.exit(0 if result["exit_code"] == 0 else 1)
if __name__ == "__main__":
main()

75
.github/skills/solve/SKILL.md vendored Normal file
View file

@ -0,0 +1,75 @@
---
name: solve
description: Check satisfiability of SMT-LIB2 formulas using Z3. Returns sat/unsat with models or unsat cores. Logs every invocation to z3agent.db for auditability.
---
Given an SMT-LIB2 formula (or a set of constraints described in natural language), determine whether the formula is satisfiable. If sat, extract a satisfying assignment. If unsat and tracking labels are present, extract the unsat core.
# Step 1: Prepare the formula
Action:
Convert the input to valid SMT-LIB2. If the input is natural language,
use the **encode** skill first.
Expectation:
A syntactically valid SMT-LIB2 formula ending with `(check-sat)` and
either `(get-model)` or `(get-unsat-core)` as appropriate.
Result:
If valid SMT-LIB2 is ready, proceed to Step 2.
If encoding is needed, run **encode** first and return here.
# Step 2: Run Z3
Action:
Invoke solve.py with the formula string or file path.
Expectation:
The script pipes the formula to `z3 -in`, logs the run to
`.z3-agent/z3agent.db`, and prints the result.
Result:
Output is one of `sat`, `unsat`, `unknown`, or `timeout`.
Proceed to Step 3 to interpret.
```bash
python3 scripts/solve.py --formula "(declare-const x Int)(assert (> x 0))(check-sat)(get-model)"
```
For file input:
```bash
python3 scripts/solve.py --file problem.smt2
```
With debug tracing:
```bash
python3 scripts/solve.py --file problem.smt2 --debug
```
# Step 3: Interpret the output
Action:
Parse the Z3 output to determine satisfiability and extract any model
or unsat core.
Expectation:
`sat` with a model, `unsat` optionally with a core, `unknown`, or
`timeout`.
Result:
On `sat`: report the model to the user.
On `unsat`: report the core if available.
On `unknown`/`timeout`: try **simplify** or increase the timeout.
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| formula | string | no | | SMT-LIB2 formula as a string |
| file | path | no | | path to an .smt2 file |
| timeout | int | no | 30 | seconds before killing z3 |
| z3 | path | no | auto | explicit path to z3 binary |
| debug | flag | no | off | print z3 command, stdin, stdout, stderr, timing |
| db | path | no | .z3-agent/z3agent.db | path to the logging database |
Either `formula` or `file` must be provided.

64
.github/skills/solve/scripts/solve.py vendored Normal file
View file

@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
solve.py: check satisfiability of an SMT-LIB2 formula via Z3.
Usage:
python solve.py --formula "(declare-const x Int)(assert (> x 0))(check-sat)(get-model)"
python solve.py --file problem.smt2
python solve.py --file problem.smt2 --debug --timeout 60
"""
import argparse
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, run_z3, parse_model, parse_unsat_core, setup_logging
def main():
parser = argparse.ArgumentParser(prog="solve")
parser.add_argument("--formula", help="SMT-LIB2 formula string")
parser.add_argument("--file", help="path to .smt2 file")
parser.add_argument("--timeout", type=int, default=30)
parser.add_argument("--z3", default=None, help="path to z3 binary")
parser.add_argument("--db", default=None)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
setup_logging(args.debug)
if args.file:
formula = Path(args.file).read_text()
elif args.formula:
formula = args.formula
else:
parser.error("provide --formula or --file")
return
db = Z3DB(args.db)
run_id = db.start_run("solve", formula)
result = run_z3(formula, z3_bin=args.z3, timeout=args.timeout, debug=args.debug)
model = parse_model(result["stdout"]) if result["result"] == "sat" else None
core = parse_unsat_core(result["stdout"]) if result["result"] == "unsat" else None
db.log_formula(run_id, formula, result["result"], str(model) if model else None)
db.finish_run(run_id, result["result"], result["duration_ms"], result["exit_code"])
print(result["result"])
if model:
for name, val in model.items():
print(f" {name} = {val}")
if core:
print("unsat core:", " ".join(core))
if result["stderr"] and result["result"] == "error":
print(result["stderr"], file=sys.stderr)
db.close()
sys.exit(0 if result["exit_code"] == 0 else 1)
if __name__ == "__main__":
main()

81
.github/skills/static-analysis/SKILL.md vendored Normal file
View file

@ -0,0 +1,81 @@
---
name: static-analysis
description: Run Clang Static Analyzer (scan-build) on Z3 source and log structured findings to z3agent.db.
---
Run the Clang Static Analyzer over a CMake build of Z3, parse the resulting plist diagnostics, and record each finding with file, line, category, and description. This skill wraps scan-build into a reproducible, logged workflow suitable for regular analysis sweeps and regression tracking.
# Step 1: Run the analysis
Action:
Invoke the script pointing at the CMake build directory. The script
runs `scan-build cmake ..` followed by `scan-build make` and writes
checker output to the output directory.
Expectation:
scan-build completes within the timeout, producing plist diagnostic
files in the output directory (defaults to a `scan-results` subdirectory
of the build directory).
Result:
On success: diagnostics are parsed and findings are printed. Proceed to
Step 2.
On failure: verify that clang and scan-build are installed and that the
build directory contains a valid CMake configuration.
```bash
python3 scripts/static_analysis.py --build-dir build
python3 scripts/static_analysis.py --build-dir build --output-dir /tmp/sa-results --debug
python3 scripts/static_analysis.py --build-dir build --timeout 1800
```
# Step 2: Interpret the output
Action:
Review the printed findings and the summary table grouped by category.
Expectation:
Each finding shows its source location, category, and description.
The summary table ranks categories by frequency for quick triage.
Result:
On zero findings: the codebase passes all enabled static checks.
On findings: prioritize by category frequency and severity. Address
null dereferences and use-after-free classes first.
Example output:
```
[Dead store] src/ast/ast.cpp:142: Value stored to 'result' is never read
[Null dereference] src/smt/theory_lra.cpp:87: Access to field 'next' results in a dereference of a null pointer
```
# Step 3: Review historical findings
Action:
Query z3agent.db to compare current results against prior analysis
runs.
Expectation:
Queries return category counts and run history, enabling regression
detection across commits.
Result:
On stable or decreasing counts: no regressions introduced.
On increased counts: cross-reference new findings with recent commits
to identify the responsible change.
```bash
python3 ../../shared/z3db.py query "SELECT category, COUNT(*) as cnt FROM findings WHERE run_id IN (SELECT run_id FROM runs WHERE skill='static-analysis') GROUP BY category ORDER BY cnt DESC"
python3 ../../shared/z3db.py runs --skill static-analysis --last 10
```
# Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| build-dir | path | yes | | path to the CMake build directory |
| output-dir | path | no | BUILD/scan-results | directory for scan-build output |
| timeout | int | no | 1200 | seconds allowed for the full build |
| db | path | no | .z3-agent/z3agent.db | logging database |
| debug | flag | no | off | verbose tracing |

View file

@ -0,0 +1,258 @@
#!/usr/bin/env python3
"""
static_analysis.py: run Clang Static Analyzer on Z3 source.
Usage:
python static_analysis.py --build-dir build
python static_analysis.py --build-dir build --output-dir /tmp/sa-results
python static_analysis.py --build-dir build --debug
"""
import argparse
import logging
import os
import plistlib
import shutil
import subprocess
import sys
import time
from collections import Counter
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared"))
from z3db import Z3DB, setup_logging
logger = logging.getLogger("z3agent")
SCAN_BUILD_NAMES = ["scan-build", "scan-build-14", "scan-build-15", "scan-build-16"]
def find_scan_build() -> str:
"""Locate the scan-build binary on PATH."""
for name in SCAN_BUILD_NAMES:
path = shutil.which(name)
if path:
logger.debug("found scan-build: %s", path)
return path
print(
"scan-build not found on PATH.\n"
"Install one of the following:\n"
" Ubuntu/Debian: sudo apt install clang-tools\n"
" macOS: brew install llvm\n"
" Fedora: sudo dnf install clang-tools-extra\n"
f"searched for: {', '.join(SCAN_BUILD_NAMES)}",
file=sys.stderr,
)
sys.exit(1)
def run_configure(scan_build: str, build_dir: Path, output_dir: Path,
timeout: int) -> bool:
"""Run scan-build cmake to configure the project."""
repo_root = build_dir.parent
cmd = [
scan_build,
"-o", str(output_dir),
"cmake",
str(repo_root),
]
logger.info("configuring: %s", " ".join(cmd))
try:
proc = subprocess.run(
cmd, cwd=str(build_dir),
capture_output=True, text=True, timeout=timeout,
)
except subprocess.TimeoutExpired:
logger.error("cmake configuration timed out after %ds", timeout)
return False
if proc.returncode != 0:
logger.error("cmake configuration failed (exit %d)", proc.returncode)
logger.error("stderr: %s", proc.stderr[:2000])
return False
logger.info("configuration complete")
return True
def run_build(scan_build: str, build_dir: Path, output_dir: Path,
timeout: int) -> bool:
"""Run scan-build make to build and analyze."""
nproc = os.cpu_count() or 4
cmd = [
scan_build,
"-o", str(output_dir),
"--status-bugs",
"make",
f"-j{nproc}",
]
logger.info("building with analysis: %s", " ".join(cmd))
try:
proc = subprocess.run(
cmd, cwd=str(build_dir),
capture_output=True, text=True, timeout=timeout,
)
except subprocess.TimeoutExpired:
logger.error("build timed out after %ds", timeout)
return False
# scan-build returns nonzero when bugs are found (due to --status-bugs),
# so a nonzero exit code is not necessarily a build failure.
if proc.returncode != 0:
logger.info(
"scan-build exited with code %d (nonzero may indicate findings)",
proc.returncode,
)
else:
logger.info("build complete, no bugs reported by scan-build")
if proc.stderr:
logger.debug("build stderr (last 2000 chars): %s", proc.stderr[-2000:])
return True
def collect_plist_files(output_dir: Path) -> list:
"""Recursively find all .plist diagnostic files under the output directory."""
plists = sorted(output_dir.rglob("*.plist"))
logger.debug("found %d plist files in %s", len(plists), output_dir)
return plists
def parse_plist_findings(plist_path: Path) -> list:
"""Extract findings from a single Clang plist diagnostic file.
Returns a list of dicts with keys: file, line, col, category, type, description.
"""
findings = []
try:
with open(plist_path, "rb") as f:
data = plistlib.load(f)
except Exception as exc:
logger.warning("could not parse %s: %s", plist_path, exc)
return findings
source_files = data.get("files", [])
for diag in data.get("diagnostics", []):
location = diag.get("location", {})
file_idx = location.get("file", 0)
source_file = source_files[file_idx] if file_idx < len(source_files) else "<unknown>"
findings.append({
"file": source_file,
"line": location.get("line", 0),
"col": location.get("col", 0),
"category": diag.get("category", "uncategorized"),
"type": diag.get("type", ""),
"description": diag.get("description", ""),
})
return findings
def collect_all_findings(output_dir: Path) -> list:
"""Parse every plist file under output_dir and return merged findings."""
all_findings = []
for plist_path in collect_plist_files(output_dir):
all_findings.extend(parse_plist_findings(plist_path))
return all_findings
def log_findings(db, run_id: int, findings: list):
"""Persist each finding to z3agent.db."""
for f in findings:
db.log_finding(
run_id,
category=f["category"],
message=f["description"],
severity=f.get("type"),
file=f["file"],
line=f["line"],
details={"col": f["col"], "type": f["type"]},
)
def print_findings(findings: list):
"""Print individual findings and a category summary."""
if not findings:
print("No findings reported.")
return
for f in findings:
label = f["type"] or f["category"]
print(f"[{label}] {f['file']}:{f['line']}: {f['description']}")
print()
counts = Counter(f["category"] for f in findings)
print(f"Total findings: {len(findings)}")
print("By category:")
for cat, cnt in counts.most_common():
print(f" {cat}: {cnt}")
def main():
parser = argparse.ArgumentParser(
prog="static_analysis",
description="Run Clang Static Analyzer on Z3 and log findings.",
)
parser.add_argument(
"--build-dir", required=True,
help="path to the CMake build directory",
)
parser.add_argument(
"--output-dir", default=None,
help="directory for scan-build results (default: BUILD/scan-results)",
)
parser.add_argument(
"--timeout", type=int, default=1200,
help="seconds allowed for the full analysis build",
)
parser.add_argument("--db", default=None, help="path to z3agent.db")
parser.add_argument("--debug", action="store_true", help="verbose tracing")
args = parser.parse_args()
setup_logging(args.debug)
scan_build = find_scan_build()
build_dir = Path(args.build_dir).resolve()
build_dir.mkdir(parents=True, exist_ok=True)
output_dir = Path(args.output_dir) if args.output_dir else build_dir / "scan-results"
output_dir = output_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
db = Z3DB(args.db)
run_id = db.start_run("static-analysis", f"build_dir={build_dir}")
start = time.monotonic()
if not run_configure(scan_build, build_dir, output_dir, timeout=args.timeout):
elapsed = int((time.monotonic() - start) * 1000)
db.finish_run(run_id, "error", elapsed, exit_code=1)
db.close()
sys.exit(1)
if not run_build(scan_build, build_dir, output_dir, timeout=args.timeout):
elapsed = int((time.monotonic() - start) * 1000)
db.finish_run(run_id, "error", elapsed, exit_code=1)
db.close()
sys.exit(1)
elapsed = int((time.monotonic() - start) * 1000)
findings = collect_all_findings(output_dir)
log_findings(db, run_id, findings)
status = "clean" if len(findings) == 0 else "findings"
db.finish_run(run_id, status, elapsed, exit_code=0)
db.log(
f"static analysis complete: {len(findings)} finding(s) in {elapsed}ms",
run_id=run_id,
)
print_findings(findings)
db.close()
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -28,18 +28,19 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v2
uses: microsoft/setup-msbuild@v3
- run: |
md build
cd build
${{ matrix.cmd1 }}
${{ matrix.cmd2 }}
${{ matrix.cmd3 }}
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.arch }}
cmake ${{ matrix.bindings }} -G "NMake Makefiles" ../
nmake
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.arch }} || exit /b 1
cmake ${{ matrix.bindings }} -G "NMake Makefiles" ../ || exit /b 1
nmake || exit /b 1
cd ..
shell: cmd
- name: Run Regressions
@ -52,7 +53,8 @@ jobs:
if: ${{ matrix.test }}
run: |
pushd build
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.arch }}
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.arch }} || exit /b 1
pushd build\python
python z3test.py z3
python z3test.py z3num

1066
.github/workflows/a3-python-v2.lock.yml generated vendored

File diff suppressed because it is too large Load diff

View file

@ -1,568 +0,0 @@
---
on:
schedule:
- cron: "0 0 * * 0" # Weekly on Sundays at midnight UTC
workflow_dispatch: # Allow manual trigger
permissions:
contents: read
issues: read
pull-requests: read
network:
allowed: [defaults, python]
safe-outputs:
create-issue:
labels:
- bug
- automated-analysis
- a3-python
title-prefix: "[a3-python] "
description: Analyzes Python code using a3-python tool to identify bugs and issues
name: A3 Python Code Analysis
strict: true
timeout-minutes: 45
tracker-id: a3-python-analysis
steps:
- name: Checkout Python source files
run: |
git sparse-checkout init --cone
git sparse-checkout set src
echo "Source files checked out for Python analysis"
source: z3prover/z3/a3/a3-python-v2.md@a91c5c58bd975f336bf5b744885ffd4b36b2d2ec
---
# A3 Python Code Analysis Agent
You are an expert Python code analyst using the a3-python tool to identify bugs and code quality issues. Your mission is to analyze the Python codebase, identify true positives from the analysis output, and create GitHub issues when multiple likely issues are found.
## Current Context
- **Repository**: ${{ github.repository }}
- **Workspace**: ${{ github.workspace }}
## Phase 1: Install and Setup a3-python
### 1.1 Install a3-python
Install the a3-python tool from PyPI:
```bash
pip install a3-python
```
Verify installation:
```bash
a3 --version || python -m a3 --version || echo "a3 command not found in PATH"
```
### 1.2 Check Available Commands
```bash
a3 --help || python -m a3 --help
```
## Phase 2: Run Analysis on Python Source Files
### 2.1 Identify Python Files
Find and verify Python source files in the repository:
```bash
# Check common source directories
find ${{ github.workspace }} -name "*.py" -type f | grep -E "(src/|lib/|app/|project/)" | head -20
# List all Python files in the repository
find ${{ github.workspace }} -name "*.py" -type f | head -30
```
### 2.2 Run a3-python Analysis
Run the a3 scan command on the repository to analyze all Python files:
```bash
cd ${{ github.workspace }}
# Ensure PATH includes a3 command
export PATH="$PATH:/home/runner/.local/bin"
# Run a3 scan on the repository
if command -v a3 &> /dev/null; then
# Run with multiple options for comprehensive analysis
a3 scan . --verbose --dse-verify --deduplicate --consolidate-variants > a3-python-output.txt 2>&1 || \
a3 scan . --verbose --functions --dse-verify > a3-python-output.txt 2>&1 || \
a3 scan . --verbose > a3-python-output.txt 2>&1 || \
echo "a3 scan command failed with all variations" > a3-python-output.txt
elif python -m a3 --help &> /dev/null; then
python -m a3 scan . > a3-python-output.txt 2>&1 || \
echo "python -m a3 scan command failed" > a3-python-output.txt
else
echo "ERROR: a3-python tool not available" > a3-python-output.txt
fi
# Verify output was generated
ls -lh a3-python-output.txt
cat a3-python-output.txt
```
**Important**: The a3-python tool will analyze all Python files found in the repository, focusing on source code directories.
## Phase 3: Post-Process and Analyze Results
### 3.1 Review the Output
Read and analyze the contents of `a3-python-output.txt`:
```bash
cat a3-python-output.txt
```
### 3.2 Classify Findings
For each issue reported in the output, determine:
1. **True Positives (Likely Issues)**: Real bugs or code quality problems that should be addressed
- Logic errors or bugs
- Security vulnerabilities
- Performance issues
- Code quality problems
- Broken imports or dependencies
- Type mismatches or incorrect usage
2. **False Positives**: Findings that are not real issues
- Style preferences without functional impact
- Intentional design decisions
- Test-related code patterns
- Generated code or third-party code
- Overly strict warnings without merit
- **Assertion violations at the beginning of functions** (these are pre-conditions and intentional design)
- Parameter validation checks in function entry points
### 3.3 Extract Source Code Context
For each true positive finding, extract the relevant source code context to make the report more readable:
```bash
# Function to extract source code context around a specific line
extract_code_context() {
local file="$1"
local line_num="$2"
local context_lines="${3:-5}" # Default to 5 lines before/after
if [[ -f "$file" ]]; then
echo "```python"
# Show line numbers and context
sed -n "$((line_num - context_lines)),$((line_num + context_lines))p" "$file" | \
awk -v start=$((line_num - context_lines)) -v target=$line_num '{
line_number = NR + start - 1
if (line_number == target) {
printf "%4d:❌ %s\n", line_number, $0
} else {
printf "%4d: %s\n", line_number, $0
}
}'
echo "```"
else
echo "File not found: $file"
fi
}
# Export the function for use in subshells
export -f extract_code_context
```
For each true positive, collect the source code context:
```bash
# Example usage for each finding:
# extract_code_context "path/to/file.py" "line_number" 5
# Store contexts in a temporary file for later use in the issue
echo "# Source Code Contexts" > source_contexts.md
echo "" >> source_contexts.md
# For each true positive finding, extract context and append to file
# Example workflow:
for finding in "${true_positives[@]}"; do
file=$(echo "$finding" | grep -o 'File: [^,]*' | cut -d' ' -f2)
line=$(echo "$finding" | grep -o 'Line: [0-9]*' | cut -d' ' -f2)
if [[ -n "$file" && -n "$line" ]]; then
echo "## Finding in $file at line $line" >> source_contexts.md
extract_code_context "$file" "$line" 5 >> source_contexts.md
echo "" >> source_contexts.md
fi
done
```
### 3.5 Enhanced Analysis Workflow
Create an enhanced analysis workflow that automatically extracts source code context. **IMPORTANT**: Limit detailed examples to top 5 high-severity findings only.
```bash
# Parse a3-python output and extract file/line information
parse_findings() {
local output_file="$1"
# Create arrays to store findings
declare -a high_severity=()
declare -a medium_severity=()
declare -a false_positives=()
# Parse the output file and extract findings with file/line info
# This is a template - adjust parsing based on actual a3-python output format
while IFS= read -r line; do
if [[ "$line" =~ ^.*\.py:[0-9]+:.* ]]; then
# Extract file and line number from the finding
file=$(echo "$line" | grep -oP '[\w/.-]+\.py')
line_num=$(echo "$line" | grep -oP '\.py:\K[0-9]+')
description=$(echo "$line" | cut -d':' -f3-)
echo "Found potential issue: $file:$line_num - $description"
# Classify by severity and type
# High severity: NULL_PTR, DIV_ZERO
# Medium severity: BOUNDS, ASSERT_FAIL (except pre-condition assertions)
# False positives: Assertion violations at function start (pre-conditions)
if [[ "$description" =~ ASSERT_FAIL ]] && [[ $line_num -lt 10 ]]; then
# Likely a pre-condition assertion at start of function
false_positives+=("File: $file, Line: $line_num, Description: $description")
elif [[ "$description" =~ (NULL_PTR|DIV_ZERO) ]]; then
high_severity+=("File: $file, Line: $line_num, Description: $description")
else
medium_severity+=("File: $file, Line: $line_num, Description: $description")
fi
fi
done < "$output_file"
# Generate enhanced report with TOP 5 HIGH-SEVERITY findings only in detail
echo "# Enhanced Analysis Report" > enhanced_report.md
echo "" >> enhanced_report.md
echo "## Sample High-Severity Findings (Top 5)" >> enhanced_report.md
echo "" >> enhanced_report.md
local counter=1
local max_samples=5
for finding in "${high_severity[@]}"; do
if [ $counter -gt $max_samples ]; then
break
fi
file=$(echo "$finding" | grep -o 'File: [^,]*' | cut -d' ' -f2)
line_num=$(echo "$finding" | grep -o 'Line: [^,]*' | cut -d' ' -f2)
desc=$(echo "$finding" | grep -o 'Description: .*' | cut -d' ' -f2-)
echo "### $counter. $desc" >> enhanced_report.md
echo "**Location**: \`$file:$line_num\`" >> enhanced_report.md
echo "" >> enhanced_report.md
if [[ -f "$file" ]]; then
extract_code_context "$file" "$line_num" 5 >> enhanced_report.md
else
echo "```" >> enhanced_report.md
echo "File not found: $file" >> enhanced_report.md
echo "```" >> enhanced_report.md
fi
echo "" >> enhanced_report.md
((counter++))
done
# Add summary statistics
echo "## Summary Statistics" >> enhanced_report.md
echo "- High Severity: ${#high_severity[@]}" >> enhanced_report.md
echo "- Medium Severity: ${#medium_severity[@]}" >> enhanced_report.md
echo "- False Positives: ${#false_positives[@]}" >> enhanced_report.md
# Display the enhanced report
echo "=== Enhanced Analysis Report ==="
cat enhanced_report.md
}
# Run the enhanced analysis
parse_findings "a3-python-output.txt"
```
**Note**: The complete list of all findings should be added to a collapsible `<details>` section in the GitHub issue, not shown in full detail.
### 3.4 Categorize and Count
Create a structured analysis with source code context:
```markdown
## Analysis Results
### 3.4 Categorize and Count
Create a structured analysis with source code context:
```markdown
## Analysis Results
### High-Severity Issues (for detailed examples):
1. [Issue 1 Description] - File: path/to/file.py, Line: X
**Source Code Context:**
```python
[Line numbers with context - error line marked with ❌]
```
2. [Issue 2 Description] - File: path/to/file.py, Line: Y
**Source Code Context:**
```python
[Line numbers with context - error line marked with ❌]
```
(Limit to top 5 high-severity for detailed display)
### False Positives:
1. [FP 1 Description] - Reason: Pre-condition assertion at function start
2. [FP 2 Description] - Reason for dismissal
...
### Summary:
- Total findings: X
- High severity (NULL_PTR, DIV_ZERO): Y
- Medium severity (BOUNDS, ASSERT_FAIL): Z
- False positives (including pre-conditions): W
```
## Phase 4: Create GitHub Issue (Conditional)
### 4.1 Determine If Issue Creation Is Needed
Create a GitHub issue **ONLY IF**:
- ✅ There are **2 or more** true positives (likely issues)
- ✅ The issues are actionable and specific
- ✅ The analysis completed successfully
**Do NOT create an issue if**:
- ❌ Zero or one true positive found
- ❌ Only false positives detected
- ❌ Analysis failed to run
- ❌ Output file is empty or contains only errors
### 4.2 Generate Issue Description
**Important**: Use the enhanced report generated in Phase 3.5 to populate the issue with source code context.
If creating an issue, use this structure:
```markdown
## A3 Python Code Analysis - [Date]
This issue reports **[number]** DSE-confirmed bugs identified by a3-python analysis tool across the Z3 Python API.
### Executive Summary
- **Total Findings**: X confirmed bugs
- **High Severity**: Y (NULL_PTR: N1, DIV_ZERO: N2)
- **Medium Severity**: Z (BOUNDS: N3, ASSERT_FAIL: N4)
- **Analysis Method**: Deep Symbolic Execution (DSE) verification
### Files Most Affected
| File | Issues |
|------|--------|
| `path/to/file1.py` | X |
| `path/to/file2.py` | Y |
| `path/to/file3.py` | Z |
(Show only top 3-5 files)
### Sample High-Severity Findings
#### 1. [BUG_TYPE] in `function_name`
**Location**: `path/to/file.py:line_number`
```python
10: def some_function():
11: value = None
12: ❌ return value.upper() # Error: NoneType has no attribute 'upper'
13:
14: # Rest of function...
```
#### 2. [BUG_TYPE] in `function_name`
**Location**: `path/to/file.py:line_number`
```python
25: if condition:
26: result = process_data()
27: ❌ return result # Error: 'result' may be undefined
28: # Missing else clause
```
(Show only top 5 high-severity examples with code context)
### Bug Type Analysis
| Type | Count | Description |
|------|-------|-------------|
| NULL_PTR | X | Potential None/null dereferences |
| BOUNDS | Y | Array/string index out of bounds |
| ASSERT_FAIL | Z | Assertion violations |
| DIV_ZERO | W | Division by zero errors |
### Methodology
This analysis used **a3-python** with:
- ✅ **Deep Symbolic Execution (DSE)**: Confirms bug reachability via concrete paths
- ✅ **Barrier Theory**: Attempts to prove safety before flagging
- ✅ **Multi-strategy verification**: 7+ verification techniques
All [number] issues are **DSE-confirmed**, meaning the tool verified these errors are reachable through real execution paths.
### Recommended Actions
**Immediate Priority** (High Severity - X issues):
1. Add null/None checks before dereferences in core API functions
2. Validate division denominators to prevent DIV_ZERO
3. Focus on `most_affected_file.py` (N issues)
**Medium Priority** (Y issues):
1. Add bounds checking for array/string indexing
2. Review and strengthen assertion conditions
3. Add comprehensive error handling
**Long-term**:
1. Adopt comprehensive input validation across Python API
2. Use Python type hints consistently (e.g., `Optional[T]`)
3. Consider defensive programming patterns for C API wrappers
### Complete Analysis Data
<details>
<summary>All [number] findings grouped by file (click to expand)</summary>
**path/to/file1.py** (X issues)
- BUG_TYPE: N issues
- Line Y: `function_name`
- Line Z: `function_name`
...
**path/to/file2.py** (X issues)
- BUG_TYPE: N issues
- Line Y: `function_name`
...
(List ALL findings in collapsed section)
</details>
<details>
<summary>Raw a3-python output excerpt (click to expand)</summary>
```
[PASTE FIRST 50-100 LINES OF a3-python-output.txt HERE FOR REFERENCE]
```
</details>
---
*Note: All findings have been DSE-confirmed by a3-python's deep symbolic execution engine. For questions about specific findings, run `a3 scan` locally for detailed analysis.*
```
### 4.3 Use Safe Outputs
Create the issue using the safe-outputs configuration:
- Title will be prefixed with `[a3-python]`
- Labeled with `bug`, `automated-analysis`, `a3-python`
- Contains structured analysis with actionable findings
## Important Guidelines
### Analysis Quality
- **Be thorough**: Review all findings carefully
- **Be accurate**: Distinguish real issues from false positives
- **Be specific**: Provide file names, line numbers, and descriptions
- **Be actionable**: Include recommendations for fixes
- **Be concise**: Focus on the most critical findings in the main issue body
### Issue Formatting Best Practices
- **Limit sample findings**: Show only top 5 high-severity examples with code context
- **Use collapsible sections**: Put complete analysis data in `<details>` tags
- **Prioritize readability**: Organize by severity and actionability, not just by file
- **Avoid duplication**: Don't repeat the same information in multiple formats
- **Keep it focused**: The issue should be scannable in under 2 minutes
### Classification Criteria
**True Positives** should meet these criteria:
- The issue represents a real bug or problem
- It could impact functionality, security, or performance
- It's actionable with a clear fix
- It's in code owned by the repository (not third-party)
**False Positives** typically include:
- Style preferences without functional impact
- Intentional design decisions that are correct
- Test code patterns that look unusual but are valid
- Generated or vendored code
- Overly pedantic warnings
- **Assertion violations at the beginning of functions** (these are pre-conditions)
- Parameter validation checks (assert statements checking input parameters)
### Threshold for Issue Creation
- **2+ true positives**: Create an issue with all findings
- **1 true positive**: Do not create an issue (not enough to warrant it)
- **0 true positives**: Exit gracefully without creating an issue
### Exit Conditions
Exit gracefully without creating an issue if:
- Analysis tool failed to run or install
- Python source files were not found or checked out properly
- No Python files found in repository
- Output file is empty or invalid
- Zero or one true positive identified
- All findings are false positives
### Success Metrics
A successful analysis:
- ✅ Completes without errors
- ✅ Generates comprehensive output with source code context
- ✅ Accurately classifies findings
- ✅ Creates actionable issue when appropriate
- ✅ Provides clear recommendations with visual code examples
- ✅ Shows error lines with surrounding context for better understanding
## Output Requirements
Your output MUST either:
1. **If analysis fails or no findings**:
```
✅ A3 Python analysis completed.
No significant issues found - 0 or 1 true positive detected.
```
2. **If 2+ true positives found**: Create an issue with:
- Clear executive summary with statistics
- Files most affected table (top 3-5 only)
- **ONLY top 5 high-severity findings** with detailed source code context
- Bug type analysis summary table
- Methodology explanation
- Recommended actions (prioritized)
- **Complete findings list** in collapsed `<details>` section
- **Raw output excerpt** (first 50-100 lines) in collapsed `<details>` section
**Critical**: Do NOT list all findings in the main issue body. Keep sample findings to 5 maximum and put comprehensive data in collapsible sections.
## Enhanced Workflow Summary
The enhanced workflow now includes:
1. **Automated Source Code Context Extraction**: The `extract_code_context` function automatically extracts 5 lines before and after each error location
2. **Visual Error Highlighting**: Error lines are marked with ❌ for easy identification
3. **Severity-Based Classification**: Automatically categorizes findings as high/medium severity
4. **False Positive Detection**: Identifies pre-condition assertions at function entry points
5. **Concise Reporting**: Limits detailed examples to top 5 high-severity findings
6. **Progressive Disclosure**: Uses collapsible sections for complete data
7. **Enhanced GitHub Issues**: Issues are scannable, actionable, and well-organized
Begin the analysis now. Install a3-python, run analysis on the repository, save output to a3-python-output.txt, extract source code context for findings, classify by severity, and create a GitHub issue if 2 or more likely issues are found. **Remember to keep the main issue body concise with only top 5 examples and put complete findings in a collapsed section.**

1732
.github/workflows/a3-python.lock.yml generated vendored

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,6 @@
---
on:
schedule:
- cron: "0 0 * * 0" # Weekly on Sundays at midnight UTC
schedule: weekly on sunday
workflow_dispatch: # Allow manual trigger
permissions:
contents: read
@ -10,23 +9,20 @@ permissions:
network:
allowed: [defaults, python]
safe-outputs:
report-failure-as-issue: false
create-issue:
labels:
- bug
- automated-analysis
- a3-python
title-prefix: "[a3-python] "
noop:
report-as-issue: false
description: Analyzes Python code using a3-python tool to identify bugs and issues
name: A3 Python Code Analysis
strict: true
timeout-minutes: 45
tracker-id: a3-python-analysis
steps:
- name: Checkout Python source files
run: |
git sparse-checkout init --cone
git sparse-checkout set src
echo "Python source files checked out from src directory"
---
# A3 Python Code Analysis Agent

1668
.github/workflows/academic-citation-tracker.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,299 @@
---
description: >
Monthly Academic Citation & Research Trend Tracker for Z3.
Searches arXiv, Semantic Scholar, and GitHub for recent papers and projects
using Z3, analyses which Z3 features they rely on, and identifies the
functionality — features or performance — most important to address next.
on:
schedule:
- cron: "0 6 1 * *"
workflow_dispatch:
timeout-minutes: 60
permissions: read-all
network:
allowed:
- defaults
- export.arxiv.org
- api.semanticscholar.org
- github
tools:
cache-memory: true
web-fetch: {}
github:
toolsets: [default, repos]
bash: [":*"]
safe-outputs:
report-failure-as-issue: false
mentions: false
allowed-github-references: []
max-bot-mentions: 1
create-discussion:
title-prefix: "[Research Trends] "
category: "Agentic Workflows"
close-older-discussions: true
expires: 60d
missing-tool:
create-issue: true
noop:
report-as-issue: false
---
# Academic Citation & Research Trend Tracker
## Job Description
Your name is ${{ github.workflow }}. You are an expert research analyst for the Z3
theorem prover repository `${{ github.repository }}`. Your mission is to find recent
academic papers and open-source projects that use Z3, understand *which Z3 features*
they rely on, and synthesise what this reveals about the features and performance
improvements that would have the greatest community impact.
## Your Task
### 1. Initialise or Resume Progress (Cache Memory)
Check cache memory for:
- Papers and projects already covered in the previous run (DOIs, arXiv IDs, GitHub repo URLs)
- Feature-usage counts accumulated across runs
- Date of the last run
Use the cached data so this run focuses on **new** material (last 30 days by default; if no prior cache exists, cover the last 90 days).
Initialise an empty tracking structure if the cache is absent.
### 2. Collect Recent Papers
#### 2.1 arXiv Search
Fetch recent papers that mention Z3 as a core tool. Use the arXiv API.
First compute the date 30 days ago (or 90 days for the initial run) in YYYYMMDD format,
then pass it as the `submittedDate` range filter:
```bash
# Compute the start date (30 days ago)
START_DATE=$(date -d "30 days ago" +%Y%m%d 2>/dev/null || date -v-30d +%Y%m%d)
TODAY=$(date +%Y%m%d)
# Papers mentioning Z3 in cs.PL, cs.LO, cs.SE, cs.CR, cs.FM categories
curl -s "https://export.arxiv.org/api/query?search_query=all:Z3+solver+AND+(cat:cs.PL+OR+cat:cs.LO+OR+cat:cs.SE+OR+cat:cs.CR+OR+cat:cs.FM)&submittedDate=[${START_DATE}2359+TO+${TODAY}2359]&sortBy=submittedDate&sortOrder=descending&max_results=40" \
-o /tmp/arxiv-results.xml
```
Parse the XML for: title, authors, abstract, arXiv ID, submission date, primary category.
#### 2.2 Semantic Scholar Search
Fetch recent papers via the Semantic Scholar API, filtering to the current year
(or year-1 for the initial run) to surface only recent work:
```bash
CURRENT_YEAR=$(date +%Y)
curl -s "https://api.semanticscholar.org/graph/v1/paper/search?query=Z3+theorem+prover&fields=title,authors,year,abstract,externalIds,citationCount,venue&limit=40&sort=relevance&year=${CURRENT_YEAR}" \
-H "Content-Type: application/json" \
-o /tmp/s2-results.json
```
Merge with the arXiv results (de-duplicate by DOI / arXiv ID).
#### 2.3 GitHub Projects
Use the GitHub MCP server tools to find recently-active repositories that depend on
or study Z3. Use these example search strategies:
- Repos with the `z3` topic pushed in the last 30 days:
`topic:z3 pushed:>YYYY-MM-DD` (substitute the actual date)
- Repos depending on z3 Python package with recent activity:
`z3-solver in:file filename:requirements.txt pushed:>YYYY-MM-DD`
- Repos referencing Z3Prover in README:
`Z3Prover/z3 in:readme pushed:>YYYY-MM-DD`
Limit to the 20 most-relevant results; filter out the Z3 repo itself (`Z3Prover/z3`).
#### 2.4 Filter for Genuine Z3 Usage
Keep only results where Z3 is used as a *core* component (not just a passing mention).
Discard:
- Papers that mention Z3 only in a reference list
- Repos that list z3 as an optional or dev dependency only
- Papers behind hard paywalls where the abstract cannot be fetched
### 3. Analyse Feature Usage
For each retained paper or project extract, from the abstract, full text (when
accessible), README, or source code:
**Z3 Feature / API Surface Used:**
- SMT-LIB2 formula input (`check-sat`, `get-model`, theory declarations)
- Python API (`z3py`) — which theories: Int/Real arithmetic, BitVectors, Arrays, Strings/Sequences, Uninterpreted Functions, Quantifiers
- C/C++ API
- Other language bindings (Java, C#, OCaml, JavaScript/WASM)
- Fixedpoint / Datalog (`z3.Fixedpoint`)
- Optimisation (`z3.Optimize`, MaxSMT)
- Proofs / DRAT
- Tactics and solvers (e.g., `qfbv`, `spacer`, `elim-quantifiers`, `nlsat`)
- Incremental solving (`push`/`pop`, assumptions)
- Model generation and evaluation
- Interpolation / Horn clause solving (Spacer/PDR)
- SMTCOMP/evaluation benchmarks
**Application Domain:**
- Program verification / deductive verification
- Symbolic execution / concolic testing
- Security (vulnerability discovery, protocol verification, exploit generation)
- Type checking / language design
- Hardware verification
- Constraint solving / planning / scheduling
- Formal specification / theorem proving assistance
- Compiler correctness
- Machine learning / neural network verification
- Other
**Pain Points Mentioned:**
Note any explicit mentions of Z3 limitations, performance issues, missing features,
workarounds, or comparisons where Z3 underperformed.
### 4. Aggregate Trends
Compute over all papers and projects collected (this run + cache history):
- **Feature popularity ranking**: which APIs/theories appear most frequently
- **Domain ranking**: which application areas use Z3 most
- **Performance pain-point frequency**: mentions of timeouts, scalability, memory, or
regression across Z3 versions
- **Feature gap signals**: features requested but absent, or workarounds applied
- **New vs. returning features**: compare with previous month's top features to spot
rising or falling trends
### 5. Correlate with Open Issues and PRs
Use the GitHub MCP server to search the Z3 issue tracker and recent PRs for signals
that align with the academic findings:
- Are the performance pain-points also reflected in open issues?
- Do any open feature requests map to high-demand research use-cases?
- Are there recent PRs that address any of the identified gaps?
This produces a prioritised list of development recommendations grounded in both
community usage and academic demand.
### 6. Generate the Discussion Report
Create a GitHub Discussion. Use `###` or lower for all section headers.
Wrap verbose tables or lists in `<details>` tags to keep the report scannable.
Title: `[Research Trends] Academic Citation & Research Trend Report — [Month YYYY]`
Suggested structure:
```markdown
**Period covered**: [start date] [end date]
**Papers analysed**: N (arXiv: N, Semantic Scholar: N, new this run: N)
**GitHub projects analysed**: N (new this run: N)
### Executive Summary
23 sentences: headline finding about where Z3 is being used and what the
community most needs.
### Top Z3 Features Used
| Rank | Feature / API | Papers | Projects | Trend vs. Last Month |
|------|--------------|--------|----------|----------------------|
| 1 | z3py BitVectors | N | N | ↑ / ↓ / → |
| … |
### Application Domain Breakdown
| Domain | Papers | % of Total |
|--------|--------|------------|
| Program verification | N | N% |
| … |
### Performance & Feature Pain-Points
List the most-cited pain-points with representative quotes or paraphrases from
abstracts/READMEs. Group by theme (scalability, string solver performance, API
ergonomics, missing theories, etc.).
<details>
<summary><b>All Pain-Point Mentions</b></summary>
One entry per paper/project that mentions a pain-point.
</details>
### Recommended Development Priorities
Ranked list of Z3 features or performance improvements most likely to have broad
research impact, with rationale tied to specific evidence:
1. **[Priority 1]** — evidence: N papers, N projects, N related issues
2. …
### Correlation with Open Issues / PRs
Issues and PRs in Z3Prover/z3 that align with the identified research priorities.
| Issue / PR | Title | Alignment |
|-----------|-------|-----------|
| #NNN | … | [feature / pain-point it addresses] |
### Notable New Papers
Brief description of 35 particularly interesting papers, their use of Z3, and
any Z3-specific insights.
<details>
<summary><b>All Papers This Run</b></summary>
| Source | Title | Authors | Date | Features Used | Domain |
|--------|-------|---------|------|--------------|--------|
| arXiv:XXXX.XXXXX | … | … | … | … | … |
</details>
<details>
<summary><b>All GitHub Projects This Run</b></summary>
| Repository | Stars | Updated | Features Used | Domain |
|-----------|-------|---------|--------------|--------|
| owner/repo | N | YYYY-MM-DD | … | … |
</details>
### Methodology Note
Brief description of the search strategy, sources, and filters used this run.
```
### 7. Update Cache Memory
Store for next run:
- Set of all paper IDs (DOIs, arXiv IDs) and GitHub repo URLs already covered
- Feature-usage frequency counts (cumulative)
- Domain frequency counts (cumulative)
- Date of this run
- Top-3 pain-point themes for trend comparison
## Guidelines
- **Be accurate**: Only attribute feature usage to Z3 when the paper/code makes it explicit.
- **Be exhaustive within scope**: Cover all material found; don't cherry-pick.
- **Be concise in headlines**: Lead with the most actionable finding.
- **Respect academic citation norms**: Include arXiv IDs and DOIs; do not reproduce
full paper text — only titles, authors, and abstracts.
- **Track trends**: The cache lets you show month-over-month changes.
- **Stay Z3-specific**: Focus on insights relevant to Z3 development, not general SMT
or theorem-proving trends.
## Important Notes
- DO NOT create pull requests or modify source files.
- DO NOT reproduce copyrighted paper text beyond short fair-use quotes.
- DO close older Research Trends discussions automatically (configured).
- DO always cite sources (arXiv ID, DOI, GitHub URL) so maintainers can verify.
- DO use cache memory to track longitudinal trends across months.

View file

@ -1,3 +1,4 @@
# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@ -13,57 +14,601 @@
# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
#
# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.37.23). DO NOT EDIT.
#
# To regenerate this workflow, run:
# gh aw compile
# For more information: https://github.com/githubnext/gh-aw/blob/main/.github/aw/github-agentic-workflows.md
# Not all edits will cause changes to this file.
#
# Alternative regeneration methods:
# make recompile
#
# Or use the gh-aw CLI directly:
# ./gh-aw compile --validate --verbose
#
# The workflow is generated when any workflow uses the 'expires' field
# in create-discussions or create-issues safe-outputs configuration.
# Schedule frequency is automatically determined by the shortest expiration time.
# For more information: https://github.github.com/gh-aw/introduction/overview/
#
# This file defines the generated agentic maintenance workflow for this repository.
# It runs scheduled cleanup for expiring safe outputs and supports manual maintenance operations.
#
# This workflow is generated automatically when workflows use expiring safe outputs
# or when repository maintenance features are enabled in .github/workflows/aw.json.
#
# To disable maintenance workflow generation, set in .github/workflows/aw.json:
# {"maintenance": false}
#
# Agentic maintenance docs:
# https://github.github.com/gh-aw/reference/ephemerals/#manual-maintenance-operations
#
name: Agentic Maintenance
on:
schedule:
- cron: "37 0 * * *" # Daily (based on minimum expires: 7 days)
- cron: "37 */2 * * *" # Every 2 hours (based on minimum expires: 1 days)
workflow_dispatch:
inputs:
operation:
description: 'Optional maintenance operation to run'
required: false
type: choice
default: ''
options:
- ''
- 'disable'
- 'enable'
- 'update'
- 'upgrade'
- 'safe_outputs'
- 'create_labels'
- 'activity_report'
- 'close_agentic_workflows_issues'
- 'clean_cache_memories'
- 'update_pull_request_branches'
- 'validate'
- 'forecast'
run_url:
description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.'
required: false
type: string
default: ''
workflow_call:
inputs:
operation:
description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate, forecast)'
required: false
type: string
default: ''
run_url:
description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.'
required: false
type: string
default: ''
outputs:
operation_completed:
description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)'
value: ${{ jobs.run_operation.outputs.operation || inputs.operation }}
applied_run_url:
description: 'The run URL that safe outputs were applied from'
value: ${{ jobs.apply_safe_outputs.outputs.run_url }}
permissions: {}
jobs:
close-expired-entities:
if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
runs-on: ubuntu-slim
permissions:
discussions: write
issues: write
pull-requests: write
steps:
- name: Setup Scripts
uses: githubnext/gh-aw/actions/setup@v0.45.1
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: /opt/gh-aw/actions
destination: ${{ runner.temp }}/gh-aw/actions
- name: Close expired discussions
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/close_expired_discussions.cjs');
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_discussions.cjs');
await main();
- name: Close expired issues
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io);
const { main } = require('/opt/gh-aw/actions/close_expired_issues.cjs');
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_issues.cjs');
await main();
- name: Close expired pull requests
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_pull_requests.cjs');
await main();
cleanup-cache-memory:
if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }}
runs-on: ubuntu-slim
permissions:
actions: write
steps:
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Cleanup outdated cache-memory entries
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/cleanup_cache_memory.cjs');
await main();
run_operation:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'update_pull_request_branches' && inputs.operation != 'validate' && inputs.operation != 'forecast' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
permissions:
actions: write
contents: write
pull-requests: write
outputs:
operation: ${{ steps.record.outputs.operation }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Check admin/maintainer permissions
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
await main();
- name: Install gh-aw
uses: github/gh-aw-actions/setup-cli@v0.81.6
with:
version: v0.81.6
- name: Run operation
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_AW_OPERATION: ${{ inputs.operation }}
GH_AW_CMD_PREFIX: gh aw
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs');
await main();
- name: Record outputs
id: record
env:
GH_AW_OPERATION: ${{ inputs.operation }}
run: echo "operation=$GH_AW_OPERATION" >> "$GITHUB_OUTPUT"
update_pull_request_branches:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
permissions:
contents: write
pull-requests: write
steps:
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Check admin/maintainer permissions
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
await main();
- name: Update pull request branches
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/update_pull_request_branches.cjs');
await main();
apply_safe_outputs:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
permissions:
actions: read
contents: write
discussions: write
issues: write
pull-requests: write
outputs:
run_url: ${{ steps.record.outputs.run_url }}
steps:
- name: Checkout actions folder
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
sparse-checkout: |
actions
persist-credentials: false
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Check admin/maintainer permissions
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
await main();
- name: Apply Safe Outputs
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_AW_RUN_URL: ${{ inputs.run_url }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs');
await main();
- name: Record outputs
id: record
env:
GH_AW_RUN_URL: ${{ inputs.run_url }}
run: echo "run_url=$GH_AW_RUN_URL" >> "$GITHUB_OUTPUT"
create_labels:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Check admin/maintainer permissions
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
await main();
- name: Install gh-aw
uses: github/gh-aw-actions/setup-cli@v0.81.6
with:
version: v0.81.6
- name: Create missing labels
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_CMD_PREFIX: gh aw
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/create_labels.cjs');
await main();
activity_report:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
timeout-minutes: 120
permissions:
actions: read
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Check admin/maintainer permissions
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
await main();
- name: Install gh-aw
uses: github/gh-aw-actions/setup-cli@v0.81.6
with:
version: v0.81.6
- name: Restore activity report logs cache
id: activity_report_logs_cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ./.cache/gh-aw/activity-report-logs
key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-activity-report-logs-${{ github.repository }}-
${{ runner.os }}-activity-report-logs-
- name: Download activity report logs
timeout-minutes: 20
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_AW_CMD_PREFIX: gh aw
run: |
${GH_AW_CMD_PREFIX} logs \
--repo "$GITHUB_REPOSITORY" \
--start-date -1w \
--count 500 \
--output ./.cache/gh-aw/activity-report-logs \
--format markdown \
--report-file ./.cache/gh-aw/activity-report-logs/report.md
- name: Save activity report logs cache
if: ${{ always() }}
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ./.cache/gh-aw/activity-report-logs
key: ${{ steps.activity_report_logs_cache.outputs.cache-primary-key }}
- name: Generate activity report issue
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('node:fs');
const reportPath = './.cache/gh-aw/activity-report-logs/report.md';
if (!fs.existsSync(reportPath)) {
core.warning('Activity report markdown not found at ' + reportPath + '; skipping issue creation.');
return;
}
let reportBody = '';
try {
reportBody = fs.readFileSync(reportPath, 'utf8').trim();
} catch (error) {
core.warning('Failed to read activity report markdown at ' + reportPath + ': ' + error.message);
return;
}
if (!reportBody) {
core.warning('Activity report markdown is empty at ' + reportPath + '; skipping issue creation.');
return;
}
const repoSlug = context.repo.owner + '/' + context.repo.repo;
const body = [
'### Agentic workflow activity report',
'',
'Repository: ' + repoSlug,
'Generated at: ' + new Date().toISOString(),
'',
reportBody,
].join('\n');
const createdIssue = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '[aw] agentic status report',
body,
labels: ['agentic-workflows'],
});
core.info('Created issue #' + createdIssue.data.number + ': ' + createdIssue.data.html_url);
forecast_report:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'forecast' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
timeout-minutes: 60
permissions:
actions: read
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Check admin/maintainer permissions
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
await main();
- name: Install gh-aw
uses: github/gh-aw-actions/setup-cli@v0.81.6
with:
version: v0.81.6
- name: Restore forecast report logs cache
id: forecast_report_logs_cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ./.github/aw/logs
key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-forecast-report-logs-${{ github.repository }}-
${{ runner.os }}-forecast-report-logs-
- name: Generate forecast report
id: generate_forecast_report
timeout-minutes: 30
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEBUG: "*"
GH_AW_CMD_PREFIX: gh aw
run: |
mkdir -p ./.cache/gh-aw/forecast
set +e
${GH_AW_CMD_PREFIX} forecast --repo "$GITHUB_REPOSITORY" --timeout 30 --verbose --json > ./.cache/gh-aw/forecast/report.json
forecast_exit_code=$?
set -e
if [ "${forecast_exit_code}" -eq 124 ]; then
echo '{"outcome":"timeout","message":"Forecast computation timed out after 30 minutes."}' > ./.cache/gh-aw/forecast/error.json
echo "::error::Forecast computation timed out after 30 minutes."
exit 1
fi
if [ "${forecast_exit_code}" -ne 0 ]; then
echo '{"outcome":"error","message":"Forecast computation failed before producing a report."}' > ./.cache/gh-aw/forecast/error.json
echo "::error::Forecast computation failed with exit code ${forecast_exit_code}."
exit 1
fi
- name: Debug forecast logs folder
if: ${{ always() }}
shell: bash
run: |
if [ ! -d ./.github/aw/logs ]; then
echo "Logs directory not found: ./.github/aw/logs"
exit 0
fi
echo "Files under ./.github/aw/logs:"
find ./.github/aw/logs -type f | sort
- name: Save forecast report logs cache
if: ${{ always() }}
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ./.github/aw/logs
key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }}
- name: Generate forecast issue
if: ${{ always() }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
FORECAST_STEP_OUTCOME: ${{ steps.generate_forecast_report.outcome }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/create_forecast_issue.cjs');
await main();
close_agentic_workflows_issues:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-slim
permissions:
issues: write
steps:
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Check admin/maintainer permissions
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
await main();
- name: Close no-repro agentic-workflows issues
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_agentic_workflows_issues.cjs');
await main();
validate_workflows:
if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate' && (!(github.event.repository.fork)) }}
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Scripts
uses: github/gh-aw-actions/setup@v0.81.6
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Check admin/maintainer permissions
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs');
await main();
- name: Install gh-aw
uses: github/gh-aw-actions/setup-cli@v0.81.6
with:
version: v0.81.6
- name: Validate workflows and file issue on findings
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_CMD_PREFIX: gh aw
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
setupGlobals(core, github, context, exec, io, getOctokit);
const { main } = require('${{ runner.temp }}/gh-aw/actions/run_validate_workflows.cjs');
await main();

View file

@ -22,7 +22,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Configure CMake and build
run: |
@ -33,7 +33,7 @@ jobs:
tar -cvf z3-build-${{ matrix.android-abi }}.tar *.jar *.so
- name: Archive production artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: android-build-${{ matrix.android-abi }}
path: build/z3-build-${{ matrix.android-abi }}.tar

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
---
description: Daily API coherence checker across Z3's multi-language bindings
description: Daily API coherence checker across Z3's multi-language bindings including Rust
on:
workflow_dispatch:
@ -13,24 +13,27 @@ network: defaults
tools:
cache-memory: true
serena: ["java", "python", "typescript", "csharp"]
github:
toolsets: [default]
bash: [":*"]
edit: {}
glob: {}
web-search: {}
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[API Coherence] "
category: "Agentic Workflows"
close-older-discussions: true
noop:
report-as-issue: false
github-token: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6.0.2
with:
persist-credentials: false
---
@ -40,7 +43,7 @@ steps:
Your name is ${{ github.workflow }}. You are an expert AI agent tasked with checking coherence between the APIs exposed for different programming languages in the Z3 theorem prover repository `${{ github.repository }}`.
Z3 provides bindings for multiple languages: **Java**, **.NET (C#)**, **C++**, **Python**, **TypeScript/JavaScript**, **OCaml**, and **Go**. Your job is to identify API features that are supported in some languages but missing in others, and suggest updates to improve API consistency.
Z3 provides bindings for multiple languages: **Java**, **.NET (C#)**, **C++**, **Python**, **TypeScript/JavaScript**, **OCaml**, **Go**, and **Rust** (via the external [`z3` crate](https://github.com/prove-rs/z3.rs)). Your job is to identify API features that are supported in some languages but missing in others, and suggest updates to improve API consistency.
## Your Task
@ -79,6 +82,7 @@ The API implementations are located in:
- **TypeScript/JavaScript**: `src/api/js/src/**/*.ts`
- **OCaml**: `src/api/ml/*.ml` and `*.mli` (interface files)
- **Go**: `src/api/go/*.go` (CGO bindings)
- **Rust**: External repository [`prove-rs/z3.rs`](https://github.com/prove-rs/z3.rs). Clone it with `git clone --depth=1 https://github.com/prove-rs/z3.rs /tmp/z3.rs` and analyze the high-level `z3` crate in `/tmp/z3.rs/z3/src/`. The low-level `z3-sys` crate at `/tmp/z3.rs/z3-sys/` mirrors the C API and can be used to identify which C functions are exposed.
### 4. Analyze API Coherence
@ -94,6 +98,7 @@ For each selected API family:
- **C++**: Use grep/glob to search for function declarations in `z3++.h`
- **OCaml**: Use grep/glob to search for function definitions in `.ml` and `.mli` files
- **Go**: Use grep/glob to search for function and method definitions in `src/api/go/*.go` files
- **Rust**: Clone the external repo (`git clone --depth=1 https://github.com/prove-rs/z3.rs /tmp/z3.rs`) and use grep/glob to search for public types, methods, functions, traits, and trait implementations in `/tmp/z3.rs/z3/src/*.rs`. Note that trait implementations (`impl Trait for Type`) do not use the `pub` keyword in Rust but are as public as the type and trait themselves, so search for `impl ` patterns in addition to `pub ` patterns to find them.
3. **Compare implementations** across languages:
- Is the same functionality available in all languages?
@ -170,7 +175,7 @@ Store in cache memory:
## Summary
Analyzed: Solver APIs, BitVector operations, Context creation
Total functions checked: 18
Languages covered: 7
Languages covered: 8
Previously cached issues resolved: 2
Inconsistencies found: 7
@ -188,7 +193,7 @@ The following cached issues have been resolved since the last run:
### 1. Missing BitVector Sign Extension in TypeScript
**What**: Bit sign extension function `Z3_mk_sign_ext` is not exposed in TypeScript
**Available in**: C, C++, Python, .NET, Java, Go
**Available in**: C, C++, Python, .NET, Java, Go, Rust
**Missing in**: TypeScript
**Fix**: Add `signExt(int i)` method to `BitVecExpr` class
**File**: `src/api/js/src/high-level/`

File diff suppressed because one or more lines are too long

View file

@ -5,15 +5,16 @@ on:
workflow_dispatch:
permissions: read-all
tools:
view: {}
glob: {}
edit:
bash: true
safe-outputs:
report-failure-as-issue: false
create-pull-request:
if-no-changes: ignore
missing-tool:
create-issue: true
noop:
report-as-issue: false
timeout-minutes: 60
---

View file

@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -45,7 +45,7 @@ jobs:
- name: Restore or create cache
id: cache-z3
uses: actions/cache@v5.0.3
uses: actions/cache@v6.1.0
with:
path: |
build/z3

View file

@ -38,7 +38,7 @@ jobs:
runRegressions: false
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -52,9 +52,9 @@ jobs:
run: |
set -e
cd build
make -j3
make -j3 examples
make -j3 test-z3
make -j$(nproc)
make -j$(nproc) examples
make -j$(nproc) test-z3
cd ..
- name: Run unit tests
@ -81,10 +81,18 @@ jobs:
container: "quay.io/pypa/manylinux_2_34_x86_64:latest"
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Select Python
run: |
# Use the first available manylinux interpreter for deterministic selection.
PYTHON=$(printf '%s\n' /opt/python/*/bin/python | sort -V | head -n1)
test -x "$PYTHON" || { echo "Error: no interpreter found under /opt/python/*/bin/python"; exit 1; }
echo "PYTHON=$PYTHON" >> "$GITHUB_ENV"
"$PYTHON" --version
- name: Setup Python virtual environment
run: "/opt/python/cp38-cp38/bin/python -m venv $PWD/env"
run: "$PYTHON -m venv $PWD/env"
- name: Install build dependencies
run: |
@ -113,7 +121,7 @@ jobs:
container: quay.io/pypa/manylinux_2_28_x86_64:latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download ARM toolchain
run: curl -L -o /tmp/arm-toolchain.tar.xz 'https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz'
@ -123,8 +131,16 @@ jobs:
mkdir -p /tmp/arm-toolchain/
tar xf /tmp/arm-toolchain.tar.xz -C /tmp/arm-toolchain/ --strip-components=1
- name: Select Python
run: |
# Use the first available manylinux interpreter for deterministic selection.
PYTHON=$(printf '%s\n' /opt/python/*/bin/python | sort -V | head -n1)
test -x "$PYTHON" || { echo "Error: no interpreter found under /opt/python/*/bin/python"; exit 1; }
echo "PYTHON=$PYTHON" >> "$GITHUB_ENV"
"$PYTHON" --version
- name: Setup Python virtual environment
run: "/opt/python/cp38-cp38/bin/python -m venv $PWD/env"
run: "$PYTHON -m venv $PWD/env"
- name: Install build dependencies
run: |
@ -149,7 +165,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup OCaml
uses: ocaml/setup-ocaml@v3
@ -171,9 +187,9 @@ jobs:
set -e
cd build
eval `opam config env`
make -j3
make -j3 examples
make -j3 test-z3
make -j$(nproc)
make -j$(nproc) examples
make -j$(nproc) test-z3
cd ..
- name: Install Z3 OCaml package
@ -204,7 +220,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup OCaml
uses: ocaml/setup-ocaml@v3
@ -226,21 +242,33 @@ jobs:
set -e
cd build
eval `opam config env`
make -j3
make -j3 examples
make -j3 test-z3
make -j$(nproc)
make -j$(nproc) examples
make -j$(nproc) test-z3
cd ..
- name: Install Z3 OCaml package
run: eval `opam config env`; ocamlfind install z3-static build/api/ml/* build/libz3-static.a
run: |
eval `opam config env`
ocamlfind install z3-static build/api/ml/* build/libz3-static.a
# Ensure the stublibs directory where dllz3ml-static.so was installed is
# listed in ld.conf so the OCaml bytecode linker can find it. When no
# explicit -dll flag is passed, ocamlfind installs dll* files to stublibs
# but may not update ld.conf, causing "Fatal error: exception End_of_file"
# in ocamlc when it cannot locate the stub shared library at link time.
STUBLIBS="$(ocamlfind printconf destdir)/stublibs"
LDCONF="$(ocamlfind printconf ldconf)"
if [ -d "$STUBLIBS" ] && ! grep -qF "$STUBLIBS" "$LDCONF" 2>/dev/null; then
echo "$STUBLIBS" >> "$LDCONF"
fi
- name: Build and run OCaml examples
run: |
set -e
cd build
eval `opam config env`
make -j3
make -j3 _ex_ml_example_post_install
make -j$(nproc)
make -j$(nproc) _ex_ml_example_post_install
./ml_example_static.byte
./ml_example_static_custom.byte
./ml_example_static
@ -298,7 +326,7 @@ jobs:
runTests: false
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -309,13 +337,13 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y ninja-build
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.20'
- name: Setup Julia (if needed)
if: matrix.name == 'debugClang'
uses: julia-actions/setup-julia@v2
uses: julia-actions/setup-julia@v3
with:
version: '1'
@ -388,7 +416,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -402,9 +430,10 @@ jobs:
run: |
set -e
cd build
make -j3
make -j3 examples
make -j3 test-z3
JOBS=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu || echo 1)
make -j"$JOBS"
make -j"$JOBS" examples
make -j"$JOBS" test-z3
./cpp_example
./c_example
cd ..
@ -415,6 +444,79 @@ jobs:
- name: Run regressions
run: python z3test/scripts/test_benchmarks.py build/z3 z3test/regressions/smt2
- name: Validate JNI library architecture matches host
run: |
echo "Checking libz3java.dylib architecture..."
ARCH=$(lipo -archs build/libz3java.dylib)
HOST_ARCH=$(uname -m)
echo "libz3java.dylib arch: $ARCH | host arch: $HOST_ARCH"
if [ "$ARCH" != "$HOST_ARCH" ]; then
echo "ERROR: libz3java.dylib has arch '$ARCH' but host is '$HOST_ARCH'"
exit 1
fi
echo "OK: libz3java.dylib correctly built for $HOST_ARCH"
# ============================================================================
# macOS JNI cross-compilation validation (ARM64 host -> x86_64 target)
# ============================================================================
macos-jni-cross-compile:
name: "MacOS JNI cross-compile (ARM64 -> x64) architecture validation"
runs-on: macos-15
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Configure (cross-compile ARM64 host -> x86_64 target)
run: |
CXXFLAGS="-arch x86_64" CFLAGS="-arch x86_64" LDFLAGS="-arch x86_64" \
python scripts/mk_make.py --java --arm64=false
- name: Build
run: |
set -e
cd build
NPROC=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)
make -j"$NPROC" libz3java.dylib
cd ..
- name: Validate libz3java.dylib is x86_64
run: |
echo "Checking libz3java.dylib architecture..."
ARCH=$(lipo -archs build/libz3java.dylib)
echo "libz3java.dylib architecture: $ARCH"
if [ "$ARCH" != "x86_64" ]; then
echo "ERROR: Expected x86_64 (cross-compiled target), got: $ARCH"
echo "This is the regression fixed in: JNI bindings use wrong architecture in macOS cross-compilation"
exit 1
fi
echo "OK: libz3java.dylib correctly built for x86_64 target on ARM64 host"
# ============================================================================
# Python script unit tests (build-script logic validation)
# ============================================================================
python-script-tests:
name: "Python build-script unit tests"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Run Python script unit tests
working-directory: ${{ github.workspace }}
run: python -m unittest discover -s scripts/tests -p "test_*.py" -v
# ============================================================================
# macOS CMake Builds
# ============================================================================
@ -424,7 +526,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6

File diff suppressed because one or more lines are too long

View file

@ -1,16 +1,13 @@
---
description: Analyzes Z3 codebase for consistent coding conventions and opportunities to use modern C++ features
on:
schedule:
- cron: "0 0 * * *" # Once daily at midnight UTC
schedule: daily
workflow_dispatch:
permissions: read-all
tools:
cache-memory: true
github:
toolsets: [default]
view: {}
glob: {}
edit: {}
bash:
- "clang-format --version"
@ -18,6 +15,7 @@ tools:
- "git diff:*"
- "git show:*"
safe-outputs:
report-failure-as-issue: false
create-issue:
title-prefix: "[Conventions] "
labels: [code-quality, automated]
@ -28,6 +26,8 @@ safe-outputs:
close-older-discussions: true
missing-tool:
create-issue: true
noop:
report-as-issue: false
network: defaults
timeout-minutes: 20
---

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,41 @@
---
name: Code Simplifier
description: Analyzes recently modified code and creates pull requests with simplifications that improve clarity, consistency, and maintainability while preserving functionality
on:
schedule:
- cron: "0 0 * * *"
skip-if-match: is:pr is:open in:title "[code-simplifier]"
schedule: daily
skip-if-match: 'is:pr is:open in:title "[code-simplifier]"'
permissions:
contents: read
issues: read
pull-requests: read
tracker-id: code-simplifier
safe-outputs:
create-issue:
labels:
- refactoring
- code-quality
- automation
report-failure-as-issue: false
create-pull-request:
title-prefix: "[code-simplifier] "
description: Analyzes recently modified code and creates pull requests with simplifications that improve clarity, consistency, and maintainability while preserving functionality
name: Code Simplifier
source: github/gh-aw/.github/workflows/code-simplifier.md@76d37d925abd44fee97379206f105b74b91a285b
strict: true
timeout-minutes: 30
labels: [refactoring, code-quality, automation]
reviewers: [copilot]
expires: 1d
noop:
report-as-issue: false
network:
allowed:
- go
tools:
github:
toolsets:
- default
tracker-id: code-simplifier
toolsets: [default]
timeout-minutes: 30
strict: true
source: github/gh-aw/.github/workflows/code-simplifier.md@6762bfba6ae426a03aac46e8f68701461c667404
---
<!-- This prompt will be imported in the agentic workflow .github/workflows/code-simplifier.md at runtime. -->
<!-- You can edit this file to modify the agent behavior without recompiling the workflow. -->
@ -34,11 +45,12 @@ You are an expert code simplification specialist focused on enhancing code clari
## Your Mission
Analyze recently modified code from the last 24 hours and apply refinements that improve code quality while preserving all functionality. Create a GitHub issue with a properly formatted diff if improvements are found.
Analyze recently modified code from the last 24 hours and apply refinements that improve code quality while preserving all functionality. Create a pull request with the simplified code if improvements are found.
## Current Context
- **Repository**: ${{ github.repository }}
- **Analysis Date**: $(date +%Y-%m-%d)
- **Workspace**: ${{ github.workspace }}
## Phase 1: Identify Recently Modified Code
@ -65,7 +77,7 @@ Use GitHub tools to:
For each merged PR or recent commit:
- Use `pull_request_read` with `method: get_files` to list changed files
- Use `get_commit` to see file changes in recent commits
- Focus on source code files (`.go`, `.js`, `.ts`, `.tsx`, `.cjs`, `.py`, etc.)
- Focus on source code files (`.go`, `.js`, `.ts`, `.tsx`, `.cjs`, `.py`, `.cs`, etc.)
- Exclude test files, lock files, and generated files
### 1.3 Determine Scope
@ -88,6 +100,7 @@ Before simplifying, review the project's coding standards from relevant document
- For Go projects: Check `AGENTS.md`, `DEVGUIDE.md`, or similar files
- For JavaScript/TypeScript: Look for `CLAUDE.md`, style guides, or coding conventions
- For Python: Check for style guides, PEP 8 adherence, or project-specific conventions
- For .NET/C#: Check `.editorconfig`, `Directory.Build.props`, or coding conventions in docs
**Key Standards to Apply:**
@ -112,6 +125,14 @@ For **Python** projects:
- Prefer explicit over implicit code
- Use list/dict comprehensions where they improve clarity (not complexity)
For **.NET/C#** projects:
- Follow Microsoft C# coding conventions
- Use `var` only when the type is obvious from the right side
- Use file-scoped namespaces (`namespace X;`) where supported
- Prefer pattern matching over type casting
- Use `async`/`await` consistently, avoid `.Result` or `.Wait()`
- Use nullable reference types and annotate nullability
### 2.2 Simplification Principles
Apply these refinements to the recently modified code:
@ -196,6 +217,9 @@ npm test
# For Python projects
pytest
# For .NET projects
dotnet test
```
If tests fail:
@ -217,6 +241,9 @@ npm run lint
# For Python projects
flake8 . || pylint .
# For .NET projects
dotnet format --verify-no-changes
```
Fix any linting issues introduced by the simplifications.
@ -235,13 +262,16 @@ npm run build
# For Python projects
# (typically no build step, but check imports)
python -m py_compile changed_files.py
# For .NET projects
dotnet build
```
## Phase 4: Create GitHub Issue with Diff
## Phase 4: Create Pull Request
### 4.1 Determine If Issue Is Needed
### 4.1 Determine If PR Is Needed
Only create an issue if:
Only create a PR if:
- ✅ You made actual code simplifications
- ✅ All tests pass
- ✅ Linting is clean
@ -255,42 +285,14 @@ If no improvements were made or changes broke tests, exit gracefully:
No simplifications needed - code already meets quality standards.
```
### 4.2 Generate Git Diff
### 4.2 Generate PR Description
Before creating the issue, generate a properly formatted git diff that can be used to create a pull request:
```bash
# Stage all changes if not already staged
git add .
# Generate a complete unified diff of all staged changes
git diff --cached > /tmp/code-simplification.diff
# Read the diff to include in the discussion
cat /tmp/code-simplification.diff
```
**Important**: The diff must be in standard unified diff format (git unified diff) that includes:
- File headers with `diff --git a/path b/path`
- Index lines with git hashes
- `---` and `+++` lines showing old and new file paths
- `@@` lines showing line numbers
- Actual code changes with `-` for removed lines and `+` for added lines
This format is compatible with:
- `git apply` command for direct application
- GitHub's "Create PR from diff" functionality
- GitHub Copilot for suggesting PR creation
- Manual copy-paste into PR creation interface
### 4.3 Generate Issue Description
If creating an issue, use this structure:
If creating a PR, use this structure:
```markdown
## Code Simplification - [Date]
This discussion presents code simplifications that improve clarity, consistency, and maintainability while preserving all functionality.
This PR simplifies recently modified code to improve clarity, consistency, and maintainability while preserving all functionality.
### Files Simplified
@ -321,37 +323,11 @@ Recent changes from:
### Testing
- ✅ All tests pass
- ✅ Linting passes
- ✅ Build succeeds
- ✅ All tests pass (`make test-unit`)
- ✅ Linting passes (`make lint`)
- ✅ Build succeeds (`make build`)
- ✅ No functional changes - behavior is identical
### Git Diff
Below is the complete diff that can be used to create a pull request. You can copy this diff and:
- Use it with GitHub Copilot to create a PR
- Apply it directly with `git apply`
- Create a PR manually by copying the changes
```diff
[PASTE THE COMPLETE GIT DIFF HERE]
```
To apply this diff:
```bash
# Save the diff to a file
cat > /tmp/code-simplification.diff << 'EOF'
[PASTE DIFF CONTENT]
EOF
# Apply the diff
git apply /tmp/code-simplification.diff
# Or create a PR from the current branch
gh pr create --title "[code-simplifier] Code Simplification" --body "See discussion #[NUMBER]"
```
### Review Focus
Please verify:
@ -365,13 +341,14 @@ Please verify:
*Automated by Code Simplifier Agent - analyzing code from the last 24 hours*
```
### 4.4 Use Safe Outputs
### 4.3 Use Safe Outputs
Create the issue using the safe-outputs configuration:
Create the pull request using the safe-outputs configuration:
- Title will be prefixed with `[code-simplifier]`
- Labeled with `refactoring`, `code-quality`, `automation`
- Contains complete git diff for easy PR creation
- Assigned to `copilot` for review
- Set as ready for review (not draft)
## Important Guidelines
@ -388,7 +365,7 @@ Create the issue using the safe-outputs configuration:
- **Clear over clever**: Prioritize readability and maintainability
### Exit Conditions
Exit gracefully without creating an issue if:
Exit gracefully without creating a PR if:
- No code was changed in the last 24 hours
- No simplifications are beneficial
- Tests fail after changes
@ -420,9 +397,12 @@ Your output MUST either:
No simplifications needed - code already meets quality standards.
```
3. **If simplifications made**: Create an issue with the changes using safe-outputs, including:
- Clear description of improvements
- Complete git diff in proper format
- Instructions for applying the diff or creating a PR
3. **If simplifications made**: Create a PR with the changes using safe-outputs
Begin your code simplification analysis now. Find recently modified code, assess simplification opportunities, apply improvements while preserving functionality, validate changes, and create an issue with a git diff if beneficial.
Begin your code simplification analysis now. Find recently modified code, assess simplification opportunities, apply improvements while preserving functionality, validate changes, and create a PR if beneficial.
**Important**: If no action is needed after completing your analysis, you **MUST** call the `noop` safe-output tool with a brief explanation. Failing to call any safe-output tool is the most common cause of safe-output workflow failures.
```json
{"noop": {"message": "No action needed: [brief explanation of what was analyzed and why]"}}
```

1574
.github/workflows/compare-stats-anomaly-reporter.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,192 @@
---
description: Analyze benchmark statistics from the latest 30 hours and publish bug/crash/anomaly summary as a GitHub Discussion
on:
schedule:
- cron: "0 */12 * * *"
workflow_dispatch:
permissions: read-all
strict: false
timeout-minutes: 45
network:
allowed:
- defaults
- mtzguido.tplinkdns.com
tools:
bash: [":*"]
github:
toolsets: [default]
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[Compare Stats] "
category: "agentic workflows"
close-older-discussions: true
missing-tool:
create-issue: true
noop:
report-as-issue: false
---
# Compare Stats Bug/Crash/Anomaly Reporter
Your name is ${{ github.workflow }}. You are a Z3 benchmarking analysis agent for `${{ github.repository }}`.
Analyze the benchmark statistics page below, focusing on results from the last 30 hours, then create a GitHub Discussion with a concise but actionable summary of:
- Bugs
- Crashes
- Anomalies
Source URL:
`http://mtzguido.tplinkdns.com:8081/z3/`
Note: this endpoint is currently HTTP-only. Treat fetched data as non-sensitive benchmark telemetry and do not include secrets in requests or reports.
Note: the workflow runs every 12 hours but analyzes 30 hours intentionally to provide overlap and avoid missing transient failures between runs.
Overlapping windows are expected; `close-older-discussions: true` keeps only the latest report thread active.
## Requirements
### 1) Fetch and save the source page
Use bash to fetch the page into `/tmp/gh-aw/agent/benchmark_stats.html`.
Try this first:
```bash
curl -fsSL --max-time 60 "http://mtzguido.tplinkdns.com:8081/z3/" -o /tmp/gh-aw/agent/benchmark_stats.html
```
If that fails, retry once with:
```bash
wget -q -T 60 -O /tmp/gh-aw/agent/benchmark_stats.html "http://mtzguido.tplinkdns.com:8081/z3/"
```
If both fail, still create a discussion that explains the fetch failure, includes stderr output, and marks the report as incomplete.
After a successful fetch, perform basic integrity checks before parsing:
- file is non-empty
- content includes `<html` and at least one `<table`
- if checks fail, treat as suspicious/incomplete data and report this explicitly
### 2) Parse tabular data
Use Python to parse all tables from the HTML into normalized rows.
Use resilient parsing:
- Prefer `pandas.read_html` when available.
- If pandas fails, parse with `html.parser`/regex fallback.
Persist normalized JSON to `/tmp/gh-aw/agent/compare_stats_rows.json`.
### 3) Detect time window (last 30 hours)
Find candidate timestamp columns using case-insensitive column-name matches:
- `time`, `timestamp`, `date`, `run`, `created`, `updated`
Parse datetimes with timezone handling if present. Use current UTC time and filter to rows where timestamp is within the past 30 hours.
Treat naive timestamps as UTC.
If no timestamp can be extracted:
- Report this limitation explicitly.
- Continue analysis on all rows.
- Mark the discussion as "time-window fallback".
### 4) Classify bugs/crashes/anomalies
Infer key columns using column-name heuristics:
- status/result/outcome
- benchmark/instance/file/name
- set/suite/group/track/family
- message/error/details/reason
Normalize status strings to lowercase.
#### Bugs / Crashes
Classify a row as **crash/bug** if status/details contain terms like:
- `crash`, `segfault`, `assert`, `abort`, `exception`, `error`, `failed`, `bug`
#### Anomalies
At minimum, detect:
1. **Unknown-outlier anomaly** (required):
- Within the same benchmark set/suite/group, if most rows are in `{sat, unsat, timeout}` but a minority are `unknown`, flag the `unknown` rows as anomalies.
- Rationale: require enough samples for confidence and avoid flagging sets where `unknown` is common behavior. `0.4` caps unknown results to a minority, while `0.6` enforces a decisive majority of sat/unsat/timeout outcomes. Any remainder after those constraints is intentionally allowed for other statuses.
- Use this threshold: `total_rows >= 4`, `unknown_count / total_rows <= 0.4`, and `(sat_count + unsat_count + timeout_count) / total_rows >= 0.6`.
- If set/suite/group columns are missing, fallback grouping order is: directory prefix of benchmark path/name, then benchmark name prefix before first separator (`/`, `:`, `::`), then a single global group.
2. **Status divergence anomaly**:
- Same benchmark name appears multiple times with conflicting non-timeout statuses (for example `sat` vs `unsat`).
- Ignore timeout-only disagreements here; timeout behavior is covered under the repeated hard-failure anomaly section to reduce noise from transient runtime variance.
3. **Repeated hard-failure anomaly**:
- Same benchmark appears repeatedly with crash/error-like status in the time window.
### 5) Generate discussion report
Create a GitHub Discussion using `create-discussion` safe output.
Use this structure:
```markdown
### Compare Stats Analysis Report
**Source**: [benchmark statistics](http://mtzguido.tplinkdns.com:8081/z3/)
**Workflow Run**: [#${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
**Analysis Time (UTC)**: <timestamp>
**Window**: last 30 hours (or fallback mode)
### Executive Summary
- Rows analyzed: N
- Rows in 30h window: M (or "timestamp unavailable")
- Bugs/crashes: B
- Anomalies: A
### Bugs and Crashes
| Benchmark Set | Benchmark | Status | Details | Timestamp |
|---|---|---|---|---|
| ... |
### Anomalies
#### Unknown-Outlier Cases
| Benchmark Set | Benchmark | Status | Peer Status Distribution | Timestamp |
|---|---|---|---|---|
| ... |
#### Status Divergences
| Benchmark | Observed Statuses | Benchmark Set(s) | Timestamp(s) |
|---|---|---|---|
| ... |
#### Repeated Hard Failures
| Benchmark | Failure Count | Representative Status/Details | Benchmark Set(s) |
|---|---|---|---|
| ... |
### Notes and Limitations
- Mention parsing assumptions
- Mention missing columns/timestamps if any
<details>
<summary><b>Raw Extraction Summary</b></summary>
- Table count
- Candidate columns used
- Top status distribution
- Up to 30 representative raw rows (sanitized)
</details>
```
## Reporting Rules
- Be factual and concise.
- Do not claim certainty when column mapping is heuristic.
- If no bugs/crashes/anomalies are found, still create the discussion and explicitly state "No issues detected in analyzed window."
- Do not open PRs or modify repository files.

View file

@ -19,7 +19,7 @@ jobs:
COV_DETAILS_PATH: ${{github.workspace}}/cov-details
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/checkout@v7.0.0
- name: Setup
run: |
@ -89,13 +89,13 @@ jobs:
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: coverage-${{steps.date.outputs.date}}
path: ${{github.workspace}}/coverage.html
retention-days: 4
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: coverage-details-${{steps.date.outputs.date}}
path: ${{env.COV_DETAILS_PATH}}

View file

@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Install cross build tools
run: apt update && apt install -y ninja-build cmake python3 g++-13-${{ matrix.arch }}-linux-gnu

1666
.github/workflows/csa-analysis.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

311
.github/workflows/csa-analysis.md vendored Normal file
View file

@ -0,0 +1,311 @@
---
description: Weekly Clang Static Analyzer (CSA) build and report for Z3, posting findings to GitHub Discussions
on:
schedule: weekly
workflow_dispatch:
timeout-minutes: 180
permissions: read-all
network: defaults
tools:
cache-memory: true
github:
toolsets: [default]
bash: [":*"]
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[CSA] "
category: "Agentic Workflows"
close-older-discussions: true
missing-tool:
create-issue: true
noop:
report-as-issue: false
steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2
with:
persist-credentials: false
---
# Clang Static Analyzer (CSA) Report
## Job Description
Your name is ${{ github.workflow }}. You are an expert static analysis agent for the Z3 theorem prover repository `${{ github.repository }}`. Your task is to build Z3 using the Clang Static Analyzer (`scan-build`) and produce a structured report of the findings in a GitHub Discussion.
## Your Task
### 1. Set Up the Build Environment
Install required dependencies:
```bash
sudo apt-get update -y
sudo apt-get install -y clang clang-tools cmake ninja-build python3
```
Verify that `scan-build` is available:
```bash
scan-build --version || clang --version
which scan-build
```
### 2. Configure Z3 with CMake
Run CMake inside a fresh `build` directory. Use `scan-build` to wrap the CMake configure step so that analysis starts from the configuration phase, then use it again during the actual build:
```bash
mkdir -p build
cd build
scan-build cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja ../
```
### 3. Build Z3 with scan-build
Run the build under `scan-build`, directing the HTML report output to a well-known path. Capture both stdout and stderr:
```bash
cd build
scan-build -o /tmp/csa-report --html-title "Z3 CSA Report" ninja 2>&1 | tee /tmp/csa-build.log
cd ..
```
If `scan-build` is not available, try the `clang --analyze` approach via CMake flags as a fallback:
```bash
cd build
cmake -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="--analyze" \
-DCMAKE_CXX_FLAGS="--analyze" \
-G Ninja ../ 2>&1 | tee /tmp/csa-configure.log
ninja 2>&1 | tee /tmp/csa-build.log
cd ..
```
### 4. Collect and Parse Results
After the build, examine the scan-build HTML output directory and build log to extract findings:
```bash
# List generated report directories
ls -la /tmp/csa-report/ 2>/dev/null || echo "No report directory found"
# Find all HTML report files
find /tmp/csa-report -name "*.html" 2>/dev/null | head -50
# Count total number of bugs reported
find /tmp/csa-report -name "report-*.html" 2>/dev/null | wc -l
```
Parse the `index.html` summary if it exists:
```bash
# Extract bug summary from index.html
if [ -f /tmp/csa-report/*/index.html ]; then
cat /tmp/csa-report/*/index.html
fi
```
Also scan the build log for warnings and errors:
```bash
grep -E "(warning|error|bug).*\[.*\]" /tmp/csa-build.log | head -100
```
### 4.5 Extract Report Content to Text
Extract the full CSA report content into a plain-text file so it can be embedded directly in the GitHub Discussion:
```bash
python3 - << 'PYEOF' > /tmp/csa-extracted.txt 2>&1
import os
import re
import glob
import sys
report_dir = '/tmp/csa-report'
if not os.path.isdir(report_dir):
print('No CSA report directory found.')
sys.exit(0)
subdirs = sorted([d for d in os.listdir(report_dir) if os.path.isdir(os.path.join(report_dir, d))])
if not subdirs:
print('No scan-build output subdirectory found.')
sys.exit(0)
subdir = os.path.join(report_dir, subdirs[-1])
index_path = os.path.join(subdir, 'index.html')
if os.path.exists(index_path):
with open(index_path) as f:
content = f.read()
print('## Summary Table\n')
rows = re.findall(r'<tr[^>]*>(.*?)</tr>', content, re.DOTALL)
for row in rows:
cells = re.findall(r'<td[^>]*>(.*?)</td>', row, re.DOTALL)
if cells:
clean = [re.sub(r'<[^>]+>', '', c).strip() for c in cells]
line = ' | '.join(c for c in clean if c)
if line:
print(line)
print()
report_files = sorted(glob.glob(os.path.join(subdir, 'report-*.html')))
if report_files:
print(f'## Individual Findings ({len(report_files)} total)\n')
for i, rfile in enumerate(report_files, 1):
with open(rfile) as f:
rcontent = f.read()
checker = re.search(r'<td class="CHECKER">(.*?)</td>', rcontent, re.DOTALL)
filename = re.search(r'<td class="FILENAME">(.*?)</td>', rcontent, re.DOTALL)
lineno = re.search(r'<td class="LINE">(.*?)</td>', rcontent, re.DOTALL)
desc = re.search(r'<td class="DESC">(.*?)</td>', rcontent, re.DOTALL)
c = re.sub(r'<[^>]+>', '', checker.group(1)).strip() if checker else 'unknown'
fn = re.sub(r'<[^>]+>', '', filename.group(1)).strip() if filename else 'unknown'
ln = re.sub(r'<[^>]+>', '', lineno.group(1)).strip() if lineno else '?'
d = re.sub(r'<[^>]+>', '', desc.group(1)).strip() if desc else ''
print(f'{i}. [{c}] {d}')
print(f' File: {fn}, Line: {ln}')
print()
else:
print('No individual report files found.')
PYEOF
cat /tmp/csa-extracted.txt
```
### 5. Categorize Findings
Analyze the CSA findings and group them by:
- **Bug type** (e.g., null pointer dereference, use after free, memory leak, uninitialized value, logic error)
- **Severity** (categorize by CSA checker family: `core.*`, `cplusplus.*`, `deadcode.*`, `security.*`, `unix.*`)
- **Source file** (relative path within the Z3 source tree)
Build a structured summary:
1. Total number of findings
2. Breakdown by checker/bug type
3. Top affected files (files with the most findings)
4. Any high-severity issues (null dereferences, memory safety issues)
5. Any new findings compared to cached results from a previous run
### 6. Compare with Cached Results
Check cache memory for results from the previous run:
- Number of findings from the last run
- List of previously reported high-severity issues
- Any issues that have been resolved since the last run
Update cache memory with the current run's results.
### 7. Create GitHub Discussion
Create a GitHub Discussion with a structured report. The discussion title should include the current date and a brief summary (e.g., "[CSA] Weekly Report - N findings").
**Discussion Structure:**
```markdown
# Clang Static Analyzer Report
**Date**: [YYYY-MM-DD]
**Commit**: [Short SHA of HEAD]
**Build type**: Debug (CMake + Ninja)
**Analyzer**: scan-build / clang-analyzer
## Summary
| Metric | Count |
|--------|-------|
| Total findings | N |
| High severity (core.*) | X |
| Memory issues (unix.*, cplusplus.*) | Y |
| Dead code / logic errors | Z |
| Files affected | F |
## Changes Since Last Run
- Findings resolved since last run: [list or "none"]
- New findings introduced: [list or "none"]
## High-Priority Findings
[List the top findings by severity with file paths and checker names]
## Findings by Category
### Core Checkers (Null Dereference, Division by Zero, etc.)
[List findings]
### C++ Specific (Memory Management, Object Lifetime)
[List findings]
### Dead Code and Logic Errors
[List findings]
### Security Checkers
[List findings]
## Top Affected Files
[Table or list of files with the most findings]
## Full CSA Report Content
<details>
<summary>Complete findings extracted from the CSA HTML report (click to expand)</summary>
[PASTE THE ENTIRE CONTENTS OF /tmp/csa-extracted.txt HERE verbatim — do not summarize or paraphrase]
</details>
## Build Log Excerpt
<details>
<summary>Key warnings and errors from the build log (click to expand)</summary>
```
[Relevant excerpt from build log]
```
</details>
## Notes
- This report was generated automatically by the Z3 CSA Analysis workflow.
- False positives may be present; human review is recommended before acting on findings.
- To reproduce locally: `scan-build -o /tmp/csa-report cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja . && scan-build ninja`
```
### 8. Handle Edge Cases
- If `scan-build` is not installed, report the issue clearly and suggest installing `clang-tools` or `clang-analyzer`.
- If the build fails entirely (not just with warnings), report the build failure and exit gracefully without creating a misleading discussion.
- If zero findings are reported, still create the discussion celebrating the clean result.
- If the discussion would be identical to the previous one (no changes), consider skipping creation unless it has been more than 2 weeks since the last run.
## Guidelines
- **Be thorough**: Parse all available output from scan-build, not just the summary.
- **Be accurate**: Clearly distinguish between true positives and likely false positives.
- **Be actionable**: For each high-priority finding, include the file, line number, and a brief description.
- **Be concise**: The discussion should be readable; use collapsible sections for verbose output.
- **Track progress**: Use cache memory to detect regressions (new findings) and improvements (resolved findings) over time.
- **Respect build time**: The build may take 15-20 minutes; use ninja with available cores.
## Important Notes
- **DO NOT** create pull requests or modify source files.
- **DO NOT** attempt to fix the findings automatically.
- **DO** close older CSA discussions automatically (this is configured via `close-older-discussions: true`).
- **DO** always report the commit SHA so findings can be correlated with specific code versions.
- **DO** use cache memory to track trends over multiple runs.

1153
.github/workflows/deeptest.lock.yml generated vendored

File diff suppressed because it is too large Load diff

View file

@ -1,59 +0,0 @@
---
description: Generate comprehensive test cases for Z3 source files
on:
workflow_dispatch:
inputs:
file_path:
description: 'Path to the source file to generate tests for (e.g., src/util/vector.h)'
required: true
type: string
issue_number:
description: 'Issue number to link the generated tests to (optional)'
required: false
type: string
permissions: read-all
network: defaults
tools:
cache-memory: true
serena: ["python"]
github:
toolsets: [default]
bash: [":*"]
edit: {}
glob: {}
safe-outputs:
create-pull-request:
title-prefix: "[DeepTest] "
labels: [automated-tests, deeptest]
draft: false
add-comment:
max: 2
missing-tool:
create-issue: true
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v5
---
<!-- Edit the file linked below to modify the agent without recompilation. Feel free to move the entire markdown body to that file. -->
{{#runtime-import agentics/deeptest.md}}
## Context
You are the DeepTest agent for the Z3 theorem prover repository.
**Workflow dispatch file path**: ${{ github.event.inputs.file_path }}
**Issue number** (if linked): ${{ github.event.inputs.issue_number }}
## Instructions
Follow the workflow steps defined in the imported prompt above to generate comprehensive test cases for the specified source file.

View file

@ -16,12 +16,37 @@ env:
EM_VERSION: 3.1.73
jobs:
build-docs:
name: Build Documentation
build-go-docs:
name: Build Go Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: '1.21'
- name: Generate Go Documentation
working-directory: doc
run: |
python3 mk_go_doc.py --output-dir=api/html/go --go-api-path=../src/api/go
- name: Upload Go Documentation
uses: actions/upload-artifact@v7
with:
name: go-docs
path: doc/api/html/go/
retention-days: 1
build-docs:
name: Build Documentation
runs-on: ubuntu-latest
needs: build-go-docs
steps:
- name: Checkout
uses: actions/checkout@v7.0.0
- name: Setup node
uses: actions/setup-node@v6
@ -63,13 +88,13 @@ jobs:
working-directory: doc
run: |
eval $(opam env)
python3 mk_api_doc.py --mld --output-dir=api --z3py-package-path=../build-x64/python/z3 --build=../build-x64
python3 mk_api_doc.py --mld --go --output-dir=api --z3py-package-path=../build-x64/python/z3 --build=../build-x64
Z3BUILD=build-x64 python3 mk_params_doc.py
mkdir api/html/ml
ocamldoc -html -d api/html/ml -sort -hide Z3 -I $( ocamlfind query zarith ) -I ../build-x64/api/ml ../build-x64/api/ml/z3enums.mli ../build-x64/api/ml/z3.mli
- name: Setup emscripten
uses: mymindstorm/setup-emsdk@v14
uses: mymindstorm/setup-emsdk@v16
with:
no-install: true
version: ${{env.EM_VERSION}}
@ -97,7 +122,13 @@ jobs:
working-directory: doc
run: |
eval $(opam env)
python3 mk_api_doc.py --js --output-dir=api --mld --z3py-package-path=../build-x64/python/z3 --build=../build-x64
python3 mk_api_doc.py --js --go --output-dir=api --mld --z3py-package-path=../build-x64/python/z3 --build=../build-x64
- name: Download Go Documentation
uses: actions/download-artifact@v8.0.1
with:
name: go-docs
path: doc/api/html/go/
- name: Deploy to z3prover.github.io
uses: peaceiris/actions-gh-pages@v4

267
.github/workflows/fstar-master-build.yml vendored Normal file
View file

@ -0,0 +1,267 @@
name: Build FStar master with Z3 master
on:
schedule:
- cron: "9 4 * * *"
workflow_dispatch:
inputs:
z3_ref:
description: Z3 ref to checkout and build
required: false
default: master
z3_cmake_args:
description: Extra CMake arguments for Z3 build
required: false
default: ""
z3_runtime_args:
description: "Extra Z3 runtime args (example: smt.ho_matching=true)"
required: false
default: "smt.ho_matching=true"
fstar_ref:
description: FStar ref to checkout and build
required: false
default: _nik_higher_order_smt
fstar_opam_switch:
description: OCaml switch for FStar build
required: false
default: "4.14.2"
fstar_otherflags:
description: "Extra FStar OTHERFLAGS"
required: false
default: "--split_queries on_failure --log_failing_queries --ext higher_order_smt --proof_recovery"
discussion_category:
description: Discussion category name
required: false
default: "Agentic Workflows"
permissions:
contents: read
discussions: write
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
build-and-report:
runs-on: ubuntu-latest
timeout-minutes: 180
env:
Z3_REF: ${{ github.event.inputs.z3_ref || 'master' }}
Z3_CMAKE_ARGS: ${{ github.event.inputs.z3_cmake_args || '' }}
Z3_RUNTIME_ARGS: ${{ github.event.inputs.z3_runtime_args || 'smt.ho_matching=true' }}
FSTAR_REF: ${{ github.event.inputs.fstar_ref || 'master' }}
FSTAR_OPAM_SWITCH: ${{ github.event.inputs.fstar_opam_switch || '4.14.2' }}
FSTAR_OTHERFLAGS: ${{ github.event.inputs.fstar_otherflags || '' }}
DISCUSSION_CATEGORY: ${{ github.event.inputs.discussion_category || 'Agentic Workflows' }}
steps:
- name: Checkout Z3
uses: actions/checkout@v7.0.0
with:
ref: ${{ env.Z3_REF }}
fetch-depth: 1
- name: Install dependencies
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y cmake ninja-build python3 git curl unzip opam m4 pkg-config libgmp-dev
- name: Build Z3
run: |
set -euo pipefail
mkdir -p /tmp/gh-aw/agent
cmake -S . -B build/release -G Ninja -DCMAKE_BUILD_TYPE=Release $Z3_CMAKE_ARGS
ninja -C build/release z3
./build/release/z3 --version | tee /tmp/gh-aw/agent/z3-version.txt
printf '(check-sat)\n' | ./build/release/z3 $Z3_RUNTIME_ARGS -in | tee /tmp/gh-aw/agent/z3-runtime-check.txt
- name: Prepare Z3 aliases for FStar
run: |
set -euo pipefail
mkdir -p /tmp/gh-aw/agent/z3-bin
ln -sf "$GITHUB_WORKSPACE/build/release/z3" /tmp/gh-aw/agent/z3-bin/z3
ln -sf "$GITHUB_WORKSPACE/build/release/z3" /tmp/gh-aw/agent/z3-bin/z3-4.8.5
ln -sf "$GITHUB_WORKSPACE/build/release/z3" /tmp/gh-aw/agent/z3-bin/z3-4.13.3
/tmp/gh-aw/agent/z3-bin/z3 --version
- name: Build FStar
id: build_fstar
continue-on-error: true
run: |
set -euo pipefail
rm -rf /tmp/gh-aw/agent/FStar
git clone --depth=1 --recurse-submodules --branch "$FSTAR_REF" https://github.com/FStarLang/FStar.git /tmp/gh-aw/agent/FStar
cd /tmp/gh-aw/agent/FStar
echo "FStar commit: $(git rev-parse HEAD)" | tee /tmp/gh-aw/agent/fstar-commit.txt
opam init --disable-sandboxing --yes
opam switch create "$FSTAR_OPAM_SWITCH" --yes || opam switch "$FSTAR_OPAM_SWITCH"
eval "$(opam env --switch="$FSTAR_OPAM_SWITCH")"
opam install --deps-only . --yes
Z3_VERSION="$(sed -E -n 's/^Z3 version ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' /tmp/gh-aw/agent/z3-version.txt | head -1)"
test -n "$Z3_VERSION" || { echo "Error: Failed to extract Z3 version from /tmp/gh-aw/agent/z3-version.txt (expected: 'Z3 version X.Y.Z')"; cat /tmp/gh-aw/agent/z3-version.txt || true; exit 1; }
PATH="/tmp/gh-aw/agent/z3-bin:$PATH" OTHERFLAGS="--z3version $Z3_VERSION $FSTAR_OTHERFLAGS" make -j"$(nproc)" -k
test -x /tmp/gh-aw/agent/FStar/out/bin/fstar.exe || { echo "Error: FStar binary not found or not executable at /tmp/gh-aw/agent/FStar/out/bin/fstar.exe"; exit 1; }
/tmp/gh-aw/agent/FStar/out/bin/fstar.exe --version | tee /tmp/gh-aw/agent/fstar-version.txt
- name: Collect generated SMT2 files
id: collect_smt2
if: always()
run: |
set -euo pipefail
rm -rf /tmp/gh-aw/agent/smt2-artifact
mkdir -p /tmp/gh-aw/agent/smt2-artifact
SMT2_PREVIEW=/tmp/gh-aw/agent/smt2-preview.md
SMT2_HEAD_LINES=1000
> "$SMT2_PREVIEW"
if [ -d /tmp/gh-aw/agent/FStar ]; then
mapfile -t SMT2_FILES < <(find /tmp/gh-aw/agent/FStar -type f -name '*.smt2' | sort)
else
SMT2_FILES=()
fi
if [ "${#SMT2_FILES[@]}" -eq 0 ]; then
echo "has_files=false" >> "$GITHUB_OUTPUT"
exit 0
fi
for file in "${SMT2_FILES[@]}"; do
rel="${file#/tmp/gh-aw/agent/FStar/}"
target="/tmp/gh-aw/agent/smt2-artifact/${rel}"
mkdir -p "$(dirname "$target")"
cp "$file" "$target"
{
printf '#### `%s`\n\n' "$rel"
printf '```smt2\n'
head -n "$SMT2_HEAD_LINES" "$file"
printf '\n```\n\n'
} >> "$SMT2_PREVIEW"
done
echo "has_files=true" >> "$GITHUB_OUTPUT"
- name: Upload generated SMT2 artifact
id: upload_smt2
if: always() && steps.collect_smt2.outputs.has_files == 'true'
uses: actions/upload-artifact@v7
with:
name: fstar-generated-smt2-${{ github.run_id }}
path: /tmp/gh-aw/agent/smt2-artifact
if-no-files-found: error
retention-days: 7
- name: Create discussion summary
if: always()
uses: actions/github-script@v9
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
FSTAR_BUILD_OUTCOME: ${{ steps.build_fstar.outcome }}
SMT2_ARTIFACT_ID: ${{ steps.upload_smt2.outputs.artifact-id }}
with:
script: |
const fs = require('fs');
const readIfExists = (path) => fs.existsSync(path) ? fs.readFileSync(path, 'utf8').trim() : null;
const z3VersionText = readIfExists('/tmp/gh-aw/agent/z3-version.txt') ?? 'unknown';
const fstarVersionFile = readIfExists('/tmp/gh-aw/agent/fstar-version.txt') ?? '';
const fstarVersionText = fstarVersionFile ? fstarVersionFile.split('\n')[0] : 'unknown';
const fstarCommitLine = readIfExists('/tmp/gh-aw/agent/fstar-commit.txt') ?? '';
const fstarCommit = fstarCommitLine ? fstarCommitLine.replace(/^FStar commit:\s*/, '') : 'unknown';
const fstarBuildOutcome = process.env.FSTAR_BUILD_OUTCOME || 'unknown';
const fstarBuildSucceeded = fstarBuildOutcome === 'success';
const fstarStatus = fstarBuildSucceeded
? '✅ FStar build completed'
: `⚠️ FStar build ${fstarBuildOutcome} (pipeline continued)`;
const smt2ArtifactId = (process.env.SMT2_ARTIFACT_ID || '').trim();
const smt2ArtifactUrl = smt2ArtifactId ? `${process.env.RUN_URL}/artifacts/${smt2ArtifactId}` : '';
const smt2PreviewFile = '/tmp/gh-aw/agent/smt2-preview.md';
const maxPreviewChars = 55000; // Keep below GitHub's 65536-character discussion body limit, leaving room for non-preview sections.
let smt2Preview = readIfExists(smt2PreviewFile) ?? '';
const smt2PreviewChars = Array.from(smt2Preview);
if (smt2PreviewChars.length > maxPreviewChars) {
smt2Preview = `${smt2PreviewChars.slice(0, maxPreviewChars).join('')}\n\n... (truncated due to discussion size limits)`;
}
const smt2Section = smt2ArtifactId
? [
`### Generated SMT2 files`,
`- Artifact: ${smt2ArtifactUrl}`,
``,
`First 1000 lines per generated \`.smt2\` file:`,
``,
smt2Preview || '_No preview content available._'
].join('\n')
: [
`### Generated SMT2 files`,
`- No generated \`.smt2\` files were found.`
].join('\n');
const date = new Date().toISOString().slice(0, 10);
const owner = context.repo.owner;
const repo = context.repo.repo;
const categoryName = process.env.DISCUSSION_CATEGORY;
const categoryQuery = await github.graphql(
`query($owner:String!, $repo:String!) {
repository(owner:$owner, name:$repo) {
id
discussionCategories(first:50) {
nodes { id name }
}
}
}`,
{ owner, repo }
);
const categories = categoryQuery.repository.discussionCategories.nodes || [];
const normalized = categoryName.trim().toLowerCase();
const category = categories.find(c => c.name.toLowerCase() === normalized);
if (!category) {
throw new Error(`Discussion category '${categoryName}' not found`);
}
const body = [
`### Build status`,
`- ✅ Z3 build completed`,
`- ${fstarStatus}`,
``,
`### Inputs used`,
`- z3_ref: \`${process.env.Z3_REF}\``,
`- z3_cmake_args: \`${process.env.Z3_CMAKE_ARGS}\``,
`- z3_runtime_args: \`${process.env.Z3_RUNTIME_ARGS}\``,
`- fstar_ref: \`${process.env.FSTAR_REF}\``,
`- fstar_opam_switch: \`${process.env.FSTAR_OPAM_SWITCH}\``,
`- fstar_otherflags: \`${process.env.FSTAR_OTHERFLAGS}\``,
``,
`### Produced versions`,
`- Z3: \`${z3VersionText}\``,
`- FStar: \`${fstarVersionText}\``,
`- FStar commit: \`${fstarCommit}\``,
``,
smt2Section,
``,
`### Run`,
`- Workflow run: ${process.env.RUN_URL}`
].join('\n');
await github.graphql(
`mutation($repositoryId:ID!, $categoryId:ID!, $title:String!, $body:String!) {
createDiscussion(input:{
repositoryId:$repositoryId,
categoryId:$categoryId,
title:$title,
body:$body
}) {
discussion { url }
}
}`,
{
repositoryId: categoryQuery.repository.id,
categoryId: category.id,
title: `FStar build with configurable Z3 inputs — ${date}`,
body
}
);

1691
.github/workflows/issue-backlog-processor.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,264 @@
---
description: Processes the backlog of open issues every second day, creates a discussion with findings, and comments on relevant issues
on:
schedule: every 2 days
workflow_dispatch:
permissions: read-all
tools:
cache-memory: true
github:
toolsets: [default]
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[Issue Backlog] "
category: "Agentic Workflows"
close-older-discussions: true
add-comment:
max: 20
noop:
report-as-issue: false
github-token: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 60
---
# Issue Backlog Processor
## Job Description
Your name is ${{ github.workflow }}. You are an expert AI agent tasked with processing the backlog of open issues in the Z3 theorem prover repository `${{ github.repository }}`. Your mission is to analyze open issues systematically and help maintainers manage the backlog effectively by surfacing actionable insights and providing helpful comments.
> **CRITICAL**: You MUST call either `create-discussion` or `noop` before finishing, under all circumstances. Even if you only analyzed a small number of issues, always produce output. Never exit without calling one of these tools.
## Your Task
### 1. Initialize or Resume Progress (Cache Memory)
Check your cache memory for:
- List of issue numbers already processed and commented on in previous runs
- Issues previously flagged for closure, duplication, or merge
- Date of last run
- The batch cursor: the last issue number processed (used for pagination across runs)
If cache data exists:
- Skip re-commenting on issues already commented in a recent run (within the last 4 days)
- Re-evaluate previously flagged issues to see if their status has changed
- Note any new issues that opened since the last run
- Resume from where the previous run left off (use the stored batch cursor)
If this is the first run or memory is empty, initialize a fresh tracking structure.
### 2. Fetch Open Issues (Batched)
Use the GitHub API to list open issues in the repository. **Process at most 30 issues per run** to stay within context limits (this limit is based on the average size of Z3 issues including body text and inline code snippets; larger issues may require processing fewer):
- Retrieve one page (30 issues) of open issues
- Exclude pull requests (filter where `pull_request` is not present)
- Sort by last updated date (most recently updated first)
- If cache has a batch cursor from the last run, fetch the next page after that cursor; otherwise start from the most recently updated issues
- For each issue, collect:
- Issue number, title, body, labels, author
- Date created and last updated
- Number of comments
- **Do NOT fetch comments for every issue up front.** Only fetch comments for a specific issue when at least one of the following is true: the body mentions a version number (potential closure), the title contains words like "duplicate", "same as", or "related to" (potential duplicate), or the issue has labels such as "question", "help wanted", or "wontfix" (potential closure/status change). Fetch comments lazily, one issue at a time, only when one of these criteria is met.
- Any referenced pull requests, commits, or other issues
### 3. Analyze Each Issue
For each open issue, perform the following analysis:
#### 3.1 Identify Issues to Close
An issue can be safely closed if any of the following apply:
- A merged pull request explicitly references the issue (e.g., "fixes #NNN", "closes #NNN") and the fix has been shipped
- Comments from the reporter or maintainers indicate the issue is resolved
- The described behavior no longer occurs in the current codebase (based on code inspection or comments confirming resolution)
- The issue is a question that has been fully answered and no further action is needed
- The issue is clearly obsolete (e.g., references a version or feature that no longer exists)
**Be conservative**: When in doubt, do NOT flag an issue for closure. Only flag issues where you have high confidence.
#### 3.2 Identify Duplicate or Mergeable Issues
An issue may be a duplicate or candidate for merging if:
- Another open issue describes the same bug, behavior, or feature request
- The same root cause has been identified across multiple issues
- Issues are closely related enough that they should be tracked together
For each potential duplicate, identify:
- The original or canonical issue it duplicates
- The reason you believe they are related
#### 3.3 Identify Suggested Fixes
For issues describing bugs, incorrect behavior, or missing features:
- Analyze the issue description, stack traces, SMT-LIB2 examples, or code snippets provided
- Identify the likely Z3 component(s) involved (e.g., SAT solver, arithmetic theory, string solver, API, language bindings)
- Point to specific source files or modules in `src/` that are likely relevant
- Suggest what kind of fix might be needed (e.g., edge case handling, missing method, API inconsistency)
Focus on issues where a reasonable fix can be concisely described. Do not guess at fixes for complex soundness or performance issues.
#### 3.4 Determine If a Comment Is Warranted
Add a comment to an issue if you have **genuinely useful and specific information** to contribute, such as:
- A related merged PR or commit that might resolve or partially address the issue
- A confirmed duplicate with a reference to the canonical issue
- A request for clarification or additional diagnostic information that would help resolve the issue
- Confirmation that a fix has been shipped in a recent release
- Specific guidance on which component to look at for a fix
**Do NOT add generic comments**, low-value acknowledgments, or comments that simply restate the issue.
### 4. Create a Discussion with Findings
**MANDATORY**: You MUST call `create-discussion` now, even if you only analyzed a few issues or found nothing actionable. If there is genuinely nothing to report, call `noop` instead. Do not skip this step.
Create a GitHub Discussion summarizing the analysis results.
**Title:** "[Issue Backlog] Backlog Analysis - [Date]"
**Content structure:**
```markdown
# Issue Backlog Analysis - [Date]
## Executive Summary
- **Total open issues analyzed**: N
- **Issues recommended for closure**: N
- **Potential duplicates / merge candidates**: N
- **Issues with suggested fixes**: N
- **Issues commented on**: N
---
## Issues Recommended for Closure
These issues appear to be already resolved, no longer relevant, or fully answered.
| Issue | Title | Reason for Closure |
|-------|-------|-------------------|
| #NNN | [title] | [reasoning] |
...
---
## Potential Duplicates / Merge Candidates
These issues appear to overlap with other open or closed issues.
| Issue | Title | Duplicate of | Notes |
|-------|-------|-------------|-------|
| #NNN | [title] | #MMM | [reasoning] |
...
---
## Issues with Suggested Fixes
These issues have identifiable root causes or affected components.
### #NNN - [Issue Title]
- **Component**: [e.g., arithmetic solver, Python bindings, SMT-LIB2 parser]
- **Relevant source files**: [e.g., `src/smt/theory_arith.cpp`]
- **Suggested fix direction**: [concise description]
...
---
## Issues Needing More Information
These issues lack sufficient detail to investigate or reproduce.
| Issue | Title | Missing Information |
|-------|-------|-------------------|
| #NNN | [title] | [what is needed] |
...
---
## Notable Issues Deserving Attention
Issues that are particularly impactful or have been waiting a long time.
| Issue | Title | Age | Notes |
|-------|-------|-----|-------|
| #NNN | [title] | [days old] | [why notable] |
...
---
*Automated by Issue Backlog Processor - runs every 2 days*
```
### 5. Comment on Issues
For each issue identified in step 3.4 as warranting a comment, post a helpful comment using the `add-comment` safe output.
**Comment guidelines:**
- Be specific and actionable
- Reference relevant PRs, commits, or other issues by number
- Use a professional and respectful tone
- Identify yourself as an automated analysis agent at the end of each comment
- For potential closures, ask the reporter to confirm whether the issue is still relevant
- For duplicates, politely link to the canonical issue
Example comment for a potentially resolved issue:
```
It appears that PR #MMM (merged on [date]) may have addressed this issue by [brief description]. Could you confirm whether this problem still occurs with the latest code? If it has been resolved, we can close this issue.
*This comment was added by the automated Issue Backlog Processor.*
```
Example comment for a duplicate:
```
This issue appears to be related to (or a duplicate of) #MMM which describes a similar problem. Linking the two for tracking purposes.
*This comment was added by the automated Issue Backlog Processor.*
```
### 6. Update Cache Memory
After completing the analysis, update cache memory with:
- List of issue numbers processed in this run
- Issues that were commented on (to avoid duplicate comments in future runs)
- Issues flagged for closure, duplication, or merge
- Date and timestamp of this run
- Count of total issues analyzed
- Batch cursor: the issue number of the last issue processed in this run, so the next run can continue from where this one left off
## Guidelines
- **Always produce output**: You MUST call `create-discussion` or `noop` before finishing — never exit silently. If in doubt about whether there is enough to report, call `create-discussion` with a brief summary.
- **Batch processing**: Only analyze up to 30 issues per run. Store a cursor in cache memory so subsequent runs pick up where you left off.
- **Lazy comment fetching**: Do NOT bulk-fetch all comments for all issues. Only fetch comments for a specific issue when one of these criteria is met: the body mentions a version number, the title contains duplicate/related keywords, or the issue has status-relevant labels (e.g., "question", "help wanted", "wontfix").
- **Prioritize accuracy over coverage**: It is better to analyze 20 issues well than 200 issues poorly
- **Be conservative on closures**: Incorrectly closing a valid issue is harmful; when in doubt, keep it open
- **Respect the community**: Z3 is used by researchers, security engineers, and developers — treat all issues respectfully
- **Focus on actionable output**: Every item in the discussion should be actionable for a maintainer
- **Avoid comment spam**: Do not add comments unless they provide specific and useful information
- **Understand Z3's complexity**: Soundness bugs (wrong answers) are critical and should never be auto-closed
## Z3-Specific Context
Z3 is an industrial-strength theorem prover and SMT solver used in program verification, security analysis, and formal methods. Key components to be aware of:
- **SMT solver** (`src/smt/`): Core solving engine with theory plugins
- **SAT solver** (`src/sat/`): Boolean satisfiability engine
- **Theory solvers**: Arithmetic (`src/smt/theory_arith*`), arrays, bit-vectors, strings, etc.
- **API** (`src/api/`): C API and language bindings (Python, Java, C#, OCaml, Go, JavaScript)
- **Tactics** (`src/tactic/`): Configurable solving strategies
- **Parser** (`src/parsers/`): SMT-LIB2 and other input formats
When analyzing issues, consider:
- Whether the issue has a reproducible SMT-LIB2 test case (important for SMT solver bugs)
- Whether the issue affects a specific language binding or the core solver
- Whether it is a soundness issue (critical), performance issue (important), or API/usability issue (moderate)
- The Z3 version mentioned and whether it has since been fixed in a newer release

View file

@ -0,0 +1,60 @@
name: Mark Pull Requests Ready for Review
on:
pull_request:
types: [opened]
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
permissions: {}
jobs:
mark-ready:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Mark all draft pull requests ready for review
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
script: |
async function markReady(nodeId, number, title) {
core.info(`Marking PR #${number} "${title}" ready for review.`);
try {
await github.graphql(`
mutation($id: ID!) {
markPullRequestReadyForReview(input: { pullRequestId: $id }) {
pullRequest { number isDraft }
}
}
`, { id: nodeId });
} catch (err) {
core.warning(`Failed to mark PR #${number} ready for review: ${err.message}`);
}
}
if (context.eventName === 'pull_request') {
const pr = context.payload.pull_request;
if (pr.draft) {
await markReady(pr.node_id, pr.number, pr.title);
} else {
core.info(`PR #${pr.number} is already ready for review. Nothing to do.`);
}
} else {
const pulls = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
});
const drafts = pulls.filter(pr => pr.draft);
core.info(`Found ${drafts.length} draft pull request(s).`);
for (const pr of drafts) {
await markReady(pr.node_id, pr.number, pr.title);
}
}
core.info('Done.');

1742
.github/workflows/memory-safety-report.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,221 @@
---
description: >
Analyze ASan/UBSan sanitizer logs from the memory-safety workflow
and file findings as a GitHub issue.
on:
workflow_run:
workflows: ["Memory Safety Analysis"]
types: [completed]
branches:
- master
workflow_dispatch:
timeout-minutes: 30
permissions:
actions: read
contents: read
issues: read
pull-requests: read
env:
GH_TOKEN: ${{ github.token }}
network: defaults
tools:
cache-memory: true
github:
toolsets: [default, actions]
bash: [":*"]
safe-outputs:
report-failure-as-issue: false
mentions: false
allowed-github-references: []
max-bot-mentions: 1
create-issue:
title-prefix: "[Memory Safety] "
labels: [bug, memory-safety, automated-analysis]
max: 1
missing-tool:
create-issue: true
noop:
report-as-issue: false
steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2
with:
persist-credentials: false
---
# Memory Safety Analysis Report Generator
## Job Description
Your name is ${{ github.workflow }}. You are an expert memory safety analyst for the Z3 theorem prover repository `${{ github.repository }}`. Your task is to download, analyze, and report on the results from the Memory Safety Analysis workflow, covering runtime sanitizer (ASan/UBSan) findings.
**The `gh` CLI is not authenticated inside AWF.** Use GitHub MCP tools for all GitHub API interaction. Do not use `gh run download` or any other `gh` command.
## Your Task
### 1. Download Artifacts from the Triggering Workflow Run
If triggered by `workflow_run`, the run ID is `${{ github.event.workflow_run.id }}`. If manual dispatch (empty run ID), call `github-mcp-server-actions_list` with method `list_workflow_runs` for the "Memory Safety Analysis" workflow and pick the latest completed run.
Get the artifact list and download URLs:
1. Call `github-mcp-server-actions_list` with method `list_workflow_run_artifacts` and the run ID. The run produces two artifacts: `asan-reports` and `ubsan-reports`.
2. For each artifact, call `github-mcp-server-actions_get` with method `download_workflow_run_artifact` and the artifact ID. This returns a temporary download URL.
3. Run the helper scripts to download, extract, and parse:
```bash
bash .github/scripts/fetch-artifacts.sh "$ASAN_URL" "$UBSAN_URL"
python3 .github/scripts/parse_sanitizer_reports.py /tmp/reports
```
After this, `/tmp/reports/{asan,ubsan}-reports/` contain the extracted files, `/tmp/parsed-report.json` has structured findings, and `/tmp/fetch-artifacts.log` has the download log.
### 2. Analyze Sanitizer Reports
Read `/tmp/parsed-report.json` for structured data. Also inspect the raw files if needed:
```bash
# Check ASan results
if [ -d /tmp/reports/asan-reports ]; then
cat /tmp/reports/asan-reports/summary.md
ls /tmp/reports/asan-reports/
fi
# Check UBSan results
if [ -d /tmp/reports/ubsan-reports ]; then
cat /tmp/reports/ubsan-reports/summary.md
ls /tmp/reports/ubsan-reports/
fi
```
For each sanitizer finding, extract:
- **Error type** (heap-buffer-overflow, heap-use-after-free, stack-buffer-overflow, signed-integer-overflow, null-pointer-dereference, etc.)
- **Source location** (file, line, column)
- **Stack trace** (first 5 frames)
- **Allocation/deallocation site** (for memory errors)
### 3. Compare with Previous Results
Check cache memory for previous run results:
- Total findings from last run (ASan + UBSan)
- List of previously known issues
- Identify new findings (regressions) vs. resolved findings (improvements)
### 4. Generate the Issue Report
Create a GitHub issue using `create-issue`. Use `##` or lower for section headers and wrap verbose sections in `<details>` tags to keep the report scannable.
```markdown
**Date**: YYYY-MM-DD
**Commit**: `<short SHA>` ([full_sha](link)) on branch `<branch>`
**Commit message**: first line of commit message
**Triggered by**: push / workflow_dispatch (Memory Safety Analysis run [#<run_id>](link))
**Report run**: [#<run_id>](link)
### Executive Summary
| Category | ASan | UBSan | Total |
|----------|------|-------|-------|
| Buffer Overflow | Y | - | Z |
| Use-After-Free | Y | - | Z |
| Double-Free | Y | - | Z |
| Null Dereference | - | - | Z |
| Integer Overflow | - | Y | Z |
| Undefined Behavior | - | Y | Z |
| Other | Y | Z | Z |
| **Total** | **Y** | **Z** | **N** |
### Trend
- New findings since last run: N
- Resolved since last run: N
- Unchanged: N
### Critical Findings (Immediate Action Needed)
[List any high-severity findings: buffer overflows, use-after-free, double-free]
### Important Findings (Should Fix)
[List medium-severity: null derefs, integer overflows]
### Low-Severity / Informational
[List warnings: potential issues]
<details>
<summary><b>ASan Findings</b></summary>
[Each finding with error type, location, and stack trace snippet]
</details>
<details>
<summary><b>UBSan Findings</b></summary>
[Each finding with error type, location, and explanation]
</details>
### Top Affected Files
| File | Findings |
|------|----------|
| src/... | N |
### Known Suppressions
[List from parsed-report.json suppressions field]
### Recommendations
1. [Actionable recommendations based on the findings]
2. [Patterns to address]
<details>
<summary><b>Raw Data</b></summary>
[Compressed summary of all data for future reference]
</details>
```
If zero findings across all tools, call `noop` and include a clean-run summary (commit and workflow run link) in the no-op message.
### 5. Update Cache Memory
Store the current run's results in cache memory for future comparison:
- Total count by category
- List of file:line pairs with findings
- Run metadata (commit SHA, date, run ID)
### 6. Handle Edge Cases
- If the triggering workflow failed entirely, report that analysis could not complete and include any partial results.
- If no artifacts are available, report that and suggest running the workflow manually.
- If the helper scripts fail, report the error in the issue body and stop.
## Guidelines
- Be thorough: analyze every available artifact and log file.
- Be accurate: distinguish between ASan and UBSan findings.
- Be actionable: for each finding, include enough context to locate and understand the issue.
- Track trends: use cache memory to identify regressions and improvements over time.
- Prioritize: critical memory safety issues (buffer overflow, UAF, double-free) should be prominently highlighted.
## Important Notes
- DO NOT create pull requests or modify source files.
- DO NOT attempt to fix the findings automatically.
- DO create issues only when there are actionable findings; use `noop` for clean runs.
- DO always report the commit SHA so findings can be correlated with specific code versions.
- DO use cache memory to track trends over multiple runs.

249
.github/workflows/memory-safety.yml vendored Normal file
View file

@ -0,0 +1,249 @@
name: Memory Safety Analysis
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
inputs:
full_scan:
description: 'Run full codebase scan (not just changed files)'
required: false
default: 'false'
type: boolean
permissions:
contents: read
actions: read
concurrency:
group: memory-safety-${{ github.ref }}
cancel-in-progress: true
jobs:
# ============================================================================
# Job 1: AddressSanitizer Build and Tests
# ============================================================================
asan-test:
name: "ASan Build & Test"
runs-on: ubuntu-latest
timeout-minutes: 120
env:
ASAN_OPTIONS: "detect_leaks=1:halt_on_error=0:print_stats=1:log_path=/tmp/asan"
steps:
- name: Checkout repository
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y ninja-build clang
- name: Configure with ASan
run: |
mkdir -p build-asan
cd build-asan
CC=clang CXX=clang++ cmake \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address" \
-G Ninja ../
- name: Build Z3 with ASan
run: |
cd build-asan
ninja -j$(nproc)
ninja test-z3
- name: Run unit tests under ASan
run: |
cd build-asan
./test-z3 -a 2>&1 | tee /tmp/asan-unit-test.log
continue-on-error: true
- name: Run SMT-LIB2 benchmarks under ASan
run: |
cd build-asan
for f in ../examples/SMT-LIB2/bounded\ model\ checking/*.smt2; do
echo "=== Testing: $f ==="
timeout 60 ./z3 "$f" 2>&1 || true
done | tee /tmp/asan-benchmark.log
continue-on-error: true
- name: Run regression tests under ASan
run: |
git clone --depth=1 https://github.com/z3prover/z3test z3test
python z3test/scripts/test_benchmarks.py build-asan/z3 z3test/regressions/smt2 2>&1 | tee /tmp/asan-regression.log
continue-on-error: true
- name: Collect ASan reports
if: always()
run: |
mkdir -p /tmp/asan-reports
cp /tmp/asan* /tmp/asan-reports/ 2>/dev/null || true
if ls /tmp/asan.* 1>/dev/null 2>&1; then
cp /tmp/asan.* /tmp/asan-reports/
fi
echo "# ASan Summary" > /tmp/asan-reports/summary.md
echo "" >> /tmp/asan-reports/summary.md
if ls /tmp/asan-reports/asan.* 1>/dev/null 2>&1; then
echo "## Errors Found" >> /tmp/asan-reports/summary.md
for f in /tmp/asan-reports/asan.*; do
echo '```' >> /tmp/asan-reports/summary.md
head -50 "$f" >> /tmp/asan-reports/summary.md
echo '```' >> /tmp/asan-reports/summary.md
echo "" >> /tmp/asan-reports/summary.md
done
else
echo "No ASan errors detected." >> /tmp/asan-reports/summary.md
fi
- name: Upload ASan reports
if: always()
uses: actions/upload-artifact@v7
with:
name: asan-reports
path: /tmp/asan-reports/
retention-days: 30
# ============================================================================
# Job 2: UndefinedBehaviorSanitizer Build and Tests
# ============================================================================
ubsan-test:
name: "UBSan Build & Test"
runs-on: ubuntu-latest
timeout-minutes: 120
env:
UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=0:log_path=/tmp/ubsan"
steps:
- name: Checkout repository
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y ninja-build clang
- name: Configure with UBSan
run: |
mkdir -p build-ubsan
cd build-ubsan
CC=clang CXX=clang++ cmake \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer -fsanitize-recover=all" \
-DCMAKE_CXX_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer -fsanitize-recover=all" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=undefined" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=undefined" \
-G Ninja ../
- name: Build Z3 with UBSan
run: |
cd build-ubsan
ninja -j$(nproc)
ninja test-z3
- name: Run unit tests under UBSan
run: |
cd build-ubsan
./test-z3 -a 2>&1 | tee /tmp/ubsan-unit-test.log
continue-on-error: true
- name: Run SMT-LIB2 benchmarks under UBSan
run: |
cd build-ubsan
for f in ../examples/SMT-LIB2/bounded\ model\ checking/*.smt2; do
echo "=== Testing: $f ==="
timeout 60 ./z3 "$f" 2>&1 || true
done | tee /tmp/ubsan-benchmark.log
continue-on-error: true
- name: Run regression tests under UBSan
run: |
git clone --depth=1 https://github.com/z3prover/z3test z3test
python z3test/scripts/test_benchmarks.py build-ubsan/z3 z3test/regressions/smt2 2>&1 | tee /tmp/ubsan-regression.log
continue-on-error: true
- name: Collect UBSan reports
if: always()
run: |
mkdir -p /tmp/ubsan-reports
cp /tmp/ubsan* /tmp/ubsan-reports/ 2>/dev/null || true
if ls /tmp/ubsan.* 1>/dev/null 2>&1; then
cp /tmp/ubsan.* /tmp/ubsan-reports/
fi
echo "# UBSan Summary" > /tmp/ubsan-reports/summary.md
echo "" >> /tmp/ubsan-reports/summary.md
if ls /tmp/ubsan-reports/ubsan.* 1>/dev/null 2>&1; then
echo "## Errors Found" >> /tmp/ubsan-reports/summary.md
for f in /tmp/ubsan-reports/ubsan.*; do
echo '```' >> /tmp/ubsan-reports/summary.md
head -50 "$f" >> /tmp/ubsan-reports/summary.md
echo '```' >> /tmp/ubsan-reports/summary.md
echo "" >> /tmp/ubsan-reports/summary.md
done
else
echo "No UBSan errors detected." >> /tmp/ubsan-reports/summary.md
fi
- name: Upload UBSan reports
if: always()
uses: actions/upload-artifact@v7
with:
name: ubsan-reports
path: /tmp/ubsan-reports/
retention-days: 30
# ============================================================================
# Job 3: Summary Report
# ============================================================================
summary:
name: "Memory Safety Summary"
runs-on: ubuntu-latest
needs: [asan-test, ubsan-test]
if: always()
steps:
- name: Download all artifacts
uses: actions/download-artifact@v8.0.1
with:
path: reports/
- name: Generate summary
run: |
echo "# Memory Safety Analysis Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Commit**: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Branch**: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Trigger**: \`${{ github.event_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Job Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Analysis | Status |" >> $GITHUB_STEP_SUMMARY
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| AddressSanitizer | \`${{ needs.asan-test.result }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| UndefinedBehaviorSanitizer | \`${{ needs.ubsan-test.result }}\` |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f reports/asan-reports/summary.md ]; then
echo "## ASan Results" >> $GITHUB_STEP_SUMMARY
cat reports/asan-reports/summary.md >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
if [ -f reports/ubsan-reports/summary.md ]; then
echo "## UBSan Results" >> $GITHUB_STEP_SUMMARY
cat reports/ubsan-reports/summary.md >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo "## Artifacts" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Sanitizer logs are available as workflow artifacts" >> $GITHUB_STEP_SUMMARY
echo "- Run with \`workflow_dispatch\` and \`full_scan: true\` for complete codebase analysis" >> $GITHUB_STEP_SUMMARY

View file

@ -14,7 +14,7 @@ jobs:
BUILD_TYPE: Release
steps:
- name: Checkout Repo
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Build
run: |

View file

@ -14,7 +14,7 @@ jobs:
BUILD_TYPE: Release
steps:
- name: Checkout Repo
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Build
run: |

View file

@ -27,7 +27,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup .NET
uses: actions/setup-dotnet@v5
@ -87,7 +87,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup .NET
uses: actions/setup-dotnet@v5
@ -137,12 +137,12 @@ jobs:
validate-nuget-macos-x64:
name: "Validate NuGet on macOS x64"
runs-on: macos-13
runs-on: macos-15-intel
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup .NET
uses: actions/setup-dotnet@v5
@ -165,23 +165,6 @@ jobs:
cd test-nuget
dotnet new console
dotnet add package Microsoft.Z3 --source ../nuget-packages --prerelease
# Configure project to properly load native dependencies on macOS x64
# Use AnyCPU without RuntimeIdentifier to avoid architecture mismatch
# The .NET runtime will automatically select the correct native library from runtimes/osx-x64/native/
cat > test-nuget.csproj << 'CSPROJ'
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Z3" Version="*" />
</ItemGroup>
</Project>
CSPROJ
- name: Create test code
run: |
@ -214,7 +197,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup .NET
uses: actions/setup-dotnet@v5
@ -237,23 +220,6 @@ jobs:
cd test-nuget
dotnet new console
dotnet add package Microsoft.Z3 --source ../nuget-packages --prerelease
# Configure project to properly load native dependencies on macOS ARM64
# Use AnyCPU without RuntimeIdentifier to avoid architecture mismatch
# The .NET runtime will automatically select the correct native library from runtimes/osx-arm64/native/
cat > test-nuget.csproj << 'CSPROJ'
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Z3" Version="*" />
</ItemGroup>
</Project>
CSPROJ
- name: Create test code
run: |
@ -290,7 +256,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download Windows x64 build from release
env:
@ -326,7 +292,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download Windows x86 build from release
env:
@ -362,7 +328,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download Ubuntu x64 build from release
env:
@ -390,12 +356,12 @@ jobs:
validate-exe-macos-x64:
name: "Validate executable on macOS x64"
runs-on: macos-13
runs-on: macos-15-intel
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download macOS x64 build from release
env:
@ -428,7 +394,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download macOS ARM64 build from release
env:
@ -465,7 +431,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -504,7 +470,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -544,7 +510,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -587,7 +553,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -616,7 +582,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -640,12 +606,12 @@ jobs:
validate-python-wheel-macos-x64:
name: "Validate Python wheel on macOS x64"
runs-on: macos-13
runs-on: macos-15-intel
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -674,7 +640,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -699,18 +665,69 @@ jobs:
pip install $wheel.FullName
python -c "import z3; x = z3.Int('x'); s = z3.Solver(); s.add(x > 0); print('Result:', s.check()); print('Model:', s.model())"
validate-python-wheel-riscv64:
name: "Validate Python wheel for RISC-V 64"
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Download RISC-V 64 Python wheel from release
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ github.event.inputs.release_tag }}"
if [ -z "$TAG" ]; then
TAG="Nightly"
fi
gh release download $TAG --pattern "*riscv64.whl" --dir wheels
- name: Verify wheel platform tag and contents
run: |
pip install wheel
WHEEL_FILE=$(ls wheels/*.whl | head -n 1)
echo "Wheel file: $WHEEL_FILE"
# Check that the wheel has a riscv64 platform tag
WHEEL_NAME=$(basename $WHEEL_FILE)
echo "Wheel name: $WHEEL_NAME"
if echo "$WHEEL_NAME" | grep -q "riscv64"; then
echo "riscv64 platform tag found"
else
echo "ERROR: riscv64 platform tag not found in wheel name"
exit 1
fi
# Inspect wheel contents
python -m zipfile -l $WHEEL_FILE
# Verify wheel contains z3 library
if python -m zipfile -l $WHEEL_FILE | grep -q "libz3"; then
echo "libz3 found in wheel"
else
echo "ERROR: libz3 not found in wheel"
exit 1
fi
# ============================================================================
# MACOS DYLIB HEADERPAD VALIDATION
# ============================================================================
validate-macos-headerpad-x64:
name: "Validate macOS x64 dylib headerpad"
runs-on: macos-13
runs-on: macos-15-intel
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download macOS x64 build from release
env:
@ -762,7 +779,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download macOS ARM64 build from release
env:
@ -806,3 +823,118 @@ jobs:
echo "✗ install_name_tool failed to update install name"
exit 1
fi
# ============================================================================
# BUILD SCRIPT UNIT TESTS
# ============================================================================
validate-build-script-tests:
name: "Validate build script unit tests"
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Run build script unit tests
run: python -m unittest discover -s scripts/tests -p "test_*.py" -v
# ============================================================================
# DOTNET MANAGED WRAPPER ARCHITECTURE VALIDATION
# ============================================================================
validate-dotnet-anycpu:
name: "Validate Microsoft.Z3.dll is AnyCPU (issue #9863)"
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v7.0.0
- name: Download NuGet package from release
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ github.event.inputs.release_tag }}"
if [ -z "$TAG" ]; then
TAG="Nightly"
fi
gh release download $TAG --pattern "*.nupkg" --dir nuget-packages
- name: Extract managed DLL from NuGet package
run: |
# NuGet packages are ZIP archives; exclude the symbols package
NUPKG=$(ls nuget-packages/*.nupkg | grep -v '\.symbols\.' | grep -v '\.snupkg' | head -n 1)
echo "Checking package: $NUPKG"
unzip -q "$NUPKG" "lib/netstandard2.0/Microsoft.Z3.dll" -d nupkg-extracted
- name: Check PE Machine field is AnyCPU (not architecture-specific)
run: |
python3 - <<'EOF'
import struct
import sys
dll_path = "nupkg-extracted/lib/netstandard2.0/Microsoft.Z3.dll"
with open(dll_path, 'rb') as f:
# Verify MZ magic
if f.read(2) != b'MZ':
print("ERROR: Not a valid PE file (missing MZ header)")
sys.exit(1)
# Read PE header offset stored at 0x3C in the DOS stub
f.seek(0x3C)
pe_offset = struct.unpack('<I', f.read(4))[0]
# Verify PE signature
f.seek(pe_offset)
if f.read(4) != b'PE\x00\x00':
print("ERROR: Missing PE\\0\\0 signature")
sys.exit(1)
# COFF Machine field is the 2 bytes immediately after the PE signature
machine = struct.unpack('<H', f.read(2))[0]
machine_names = {
0x0000: "Unknown/AnyCPU",
0x014C: "i386 (AnyCPU for managed assemblies)",
0x8664: "AMD64/x64",
0xAA64: "ARM64",
0x01C0: "ARM",
0x01C4: "ARM Thumb-2",
}
machine_name = machine_names.get(machine, f"0x{machine:04X}")
print(f"Machine field: 0x{machine:04X} = {machine_name}")
if machine == 0x8664:
print()
print("FAIL: Machine is AMD64 (0x8664).")
print("This prevents loading on arm64 .NET hosts even though the assembly")
print("contains only pure IL (CorFlags.ILONLY=True).")
print("The DLL must be built as AnyCPU (Machine=0x014C) so the CLR loader")
print("accepts it on every host architecture.")
print("See issue #9863 for details.")
sys.exit(1)
elif machine == 0xAA64:
print()
print("FAIL: Machine is ARM64 (0xAA64).")
print("This prevents loading on x64 .NET hosts.")
print("The DLL must be built as AnyCPU (Machine=0x014C).")
sys.exit(1)
elif machine in (0x014C, 0x0000):
print()
print("PASS: Machine field indicates AnyCPU.")
print("Microsoft.Z3.dll will load on any .NET host architecture.")
else:
print()
print(f"FAIL: Unexpected Machine field 0x{machine:04X}.")
print("Expected 0x014C (i386/AnyCPU) for a managed-only assembly.")
sys.exit(1)
EOF

View file

@ -16,13 +16,17 @@ on:
type: boolean
default: false
concurrency:
group: nightly-release
cancel-in-progress: false
permissions:
contents: write
env:
MAJOR: '4'
MINOR: '16'
PATCH: '0'
MINOR: '17'
PATCH: '1'
jobs:
# ============================================================================
@ -33,9 +37,11 @@ jobs:
name: "Mac Build x64"
runs-on: macos-latest
timeout-minutes: 90
env:
MACOSX_DEPLOYMENT_TARGET: "13.3"
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -43,10 +49,23 @@ jobs:
python-version: '3.x'
- name: Build
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=x64
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=x64 --os=osx-13.3
- name: Validate libz3.dylib and z3 architecture (must be x86_64)
run: |
set -e
for f in build-dist/libz3.dylib build-dist/z3; do
ARCH=$(lipo -archs "$f")
echo "$f architecture: $ARCH"
if [ "$ARCH" != "x86_64" ]; then
echo "ERROR: $f has arch '$ARCH', expected 'x86_64' (see issue #9662)"
exit 1
fi
done
echo "OK: macOS x64 artifacts are x86_64"
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: macOsBuild
path: dist/*.zip
@ -56,9 +75,11 @@ jobs:
name: "Mac ARM64 Build"
runs-on: macos-latest
timeout-minutes: 90
env:
MACOSX_DEPLOYMENT_TARGET: "13.3"
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -66,10 +87,23 @@ jobs:
python-version: '3.x'
- name: Build
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=arm64
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=arm64 --os=osx-13.3
- name: Validate libz3.dylib and z3 architecture (must be arm64)
run: |
set -e
for f in build-dist/libz3.dylib build-dist/z3; do
ARCH=$(lipo -archs "$f")
echo "$f architecture: $ARCH"
if [ "$ARCH" != "arm64" ]; then
echo "ERROR: $f has arch '$ARCH', expected 'arm64' (see issue #9662)"
exit 1
fi
done
echo "OK: macOS arm64 artifacts are arm64"
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: MacArm64
path: dist/*.zip
@ -86,10 +120,10 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download macOS x64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: macOsBuild
path: artifacts
@ -101,6 +135,17 @@ jobs:
Z3_DIR=$(find . -maxdepth 1 -type d -name "z3-*" | head -n 1)
echo "Z3_DIR=$Z3_DIR" >> $GITHUB_ENV
- name: Validate shipped libz3.dylib architecture (must be x86_64)
run: |
set -e
DYLIB="artifacts/$Z3_DIR/bin/libz3.dylib"
ARCH=$(lipo -archs "$DYLIB")
echo "Shipped $DYLIB architecture: $ARCH"
if [ "$ARCH" != "x86_64" ]; then
echo "ERROR: x64 nightly zip contains '$ARCH' libz3.dylib (see issue #9662)"
exit 1
fi
- name: Test install_name_tool with headerpad
run: |
cd artifacts/$Z3_DIR/bin
@ -134,10 +179,10 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download macOS ARM64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: MacArm64
path: artifacts
@ -149,6 +194,17 @@ jobs:
Z3_DIR=$(find . -maxdepth 1 -type d -name "z3-*" | head -n 1)
echo "Z3_DIR=$Z3_DIR" >> $GITHUB_ENV
- name: Validate shipped libz3.dylib architecture (must be arm64)
run: |
set -e
DYLIB="artifacts/$Z3_DIR/bin/libz3.dylib"
ARCH=$(lipo -archs "$DYLIB")
echo "Shipped $DYLIB architecture: $ARCH"
if [ "$ARCH" != "arm64" ]; then
echo "ERROR: arm64 nightly zip contains '$ARCH' libz3.dylib (see issue #9662)"
exit 1
fi
- name: Test install_name_tool with headerpad
run: |
cd artifacts/$Z3_DIR/bin
@ -181,7 +237,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -196,9 +252,9 @@ jobs:
- name: Test
run: python z3test/scripts/test_benchmarks.py build-dist/z3 z3test/regressions/smt2
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: UbuntuBuild
path: dist/*.zip
@ -210,7 +266,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -233,7 +289,7 @@ jobs:
python scripts/mk_unix_dist.py --nodotnet --arch=arm64
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: UbuntuArm64
path: dist/*.zip
@ -245,7 +301,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -269,9 +325,9 @@ jobs:
eval $(opam config env)
python scripts/mk_make.py --ml
cd build
make -j3
make -j3 examples
make -j3 test-z3
make -j$(nproc)
make -j$(nproc) examples
make -j$(nproc) test-z3
cd ..
- name: Generate documentation
@ -288,7 +344,7 @@ jobs:
run: zip -r z3doc.zip doc/api
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: UbuntuDoc
path: z3doc.zip
@ -301,11 +357,19 @@ jobs:
container: quay.io/pypa/manylinux_2_28_x86_64:latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Select Python
run: |
# Use the first available manylinux interpreter for deterministic selection.
PYTHON=$(printf '%s\n' /opt/python/*/bin/python | sort -V | head -n1)
test -x "$PYTHON" || { echo "Error: no interpreter found under /opt/python/*/bin/python"; exit 1; }
echo "PYTHON=$PYTHON" >> "$GITHUB_ENV"
"$PYTHON" --version
- name: Setup Python environment
run: |
/opt/python/cp38-cp38/bin/python -m venv $PWD/env
"$PYTHON" -m venv $PWD/env
echo "$PWD/env/bin" >> $GITHUB_PATH
- name: Install build tools
@ -318,7 +382,7 @@ jobs:
run: pip install ./src/api/python/wheelhouse/*.whl && python - <src/api/python/z3test.py z3 && python - <src/api/python/z3test.py z3num
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ManyLinuxPythonBuildAMD64
path: src/api/python/wheelhouse/*.whl
@ -331,7 +395,7 @@ jobs:
container: quay.io/pypa/manylinux_2_28_x86_64:latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download ARM toolchain
run: curl -L -o /tmp/arm-toolchain.tar.xz 'https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz'
@ -341,9 +405,17 @@ jobs:
mkdir -p /tmp/arm-toolchain/
tar xf /tmp/arm-toolchain.tar.xz -C /tmp/arm-toolchain/ --strip-components=1
- name: Select Python
run: |
# Use the first available manylinux interpreter for deterministic selection.
PYTHON=$(printf '%s\n' /opt/python/*/bin/python | sort -V | head -n1)
test -x "$PYTHON" || { echo "Error: no interpreter found under /opt/python/*/bin/python"; exit 1; }
echo "PYTHON=$PYTHON" >> "$GITHUB_ENV"
"$PYTHON" --version
- name: Setup Python environment
run: |
/opt/python/cp38-cp38/bin/python -m venv $PWD/env
"$PYTHON" -m venv $PWD/env
echo "$PWD/env/bin" >> $GITHUB_PATH
echo "/tmp/arm-toolchain/bin" >> $GITHUB_PATH
echo "/tmp/arm-toolchain/aarch64-none-linux-gnu/libc/usr/bin" >> $GITHUB_PATH
@ -358,19 +430,74 @@ jobs:
run: cd src/api/python && CC=aarch64-none-linux-gnu-gcc CXX=aarch64-none-linux-gnu-g++ AR=aarch64-none-linux-gnu-ar LD=aarch64-none-linux-gnu-ld Z3_CROSS_COMPILING=aarch64 python -m build && AUDITWHEEL_PLAT= auditwheel repair --best-plat dist/*.whl && cd ../../..
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ManyLinuxPythonBuildArm64
path: src/api/python/wheelhouse/*.whl
retention-days: 2
manylinux-python-riscv64:
name: "Python bindings (manylinux RISC-V 64 cross)"
runs-on: ubuntu-latest
timeout-minutes: 90
container: quay.io/pypa/manylinux_2_28_x86_64:latest
steps:
- name: Checkout code
uses: actions/checkout@v7.0.0
- name: Download RISC-V toolchain
run: curl -L -o /tmp/riscv-toolchain.tar.gz 'https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2024.09.03/riscv64-glibc-ubuntu-20.04-gcc-nightly-2024.09.03-nightly.tar.gz'
- name: Extract RISC-V toolchain
run: |
mkdir -p /tmp/riscv-toolchain/
tar xf /tmp/riscv-toolchain.tar.gz -C /tmp/riscv-toolchain/ --strip-components=1
- name: Install MPFR 4 (required by RISC-V toolchain host binaries)
run: |
dnf install -y gmp-devel
curl -L -o /tmp/mpfr.tar.xz https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.1.tar.xz
tar xf /tmp/mpfr.tar.xz -C /tmp/
cd /tmp/mpfr-4.2.1 && ./configure --prefix=/usr/local --disable-static && make -j$(nproc) && make install
ldconfig
- name: Select Python
run: |
# Use the first available manylinux interpreter for deterministic selection.
PYTHON=$(printf '%s\n' /opt/python/*/bin/python | sort -V | head -n1)
test -x "$PYTHON" || { echo "Error: no interpreter found under /opt/python/*/bin/python"; exit 1; }
echo "PYTHON=$PYTHON" >> "$GITHUB_ENV"
"$PYTHON" --version
- name: Setup Python environment
run: |
"$PYTHON" -m venv $PWD/env
echo "$PWD/env/bin" >> $GITHUB_PATH
echo "/tmp/riscv-toolchain/bin" >> $GITHUB_PATH
- name: Install build tools
run: |
echo $PATH
stat $(which riscv64-unknown-linux-gnu-gcc)
pip install build git+https://github.com/rhelmot/auditwheel
- name: Build wheels
run: cd src/api/python && CC=riscv64-unknown-linux-gnu-gcc CXX=riscv64-unknown-linux-gnu-g++ AR=riscv64-unknown-linux-gnu-ar LD=riscv64-unknown-linux-gnu-ld Z3_CROSS_COMPILING=riscv64 python -m build && AUDITWHEEL_PLAT= auditwheel repair --best-plat dist/*.whl && cd ../../..
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ManyLinuxPythonBuildRiscv64
path: src/api/python/wheelhouse/*.whl
retention-days: 2
windows-build-x64:
name: "Windows x64 build"
runs-on: windows-latest
timeout-minutes: 120
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -380,11 +507,12 @@ jobs:
- name: Build
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" x64 || exit /b 1
python scripts\mk_win_dist.py --x64-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --zip
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: WindowsBuild-x64
path: dist/*.zip
@ -396,7 +524,7 @@ jobs:
timeout-minutes: 120
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -406,11 +534,12 @@ jobs:
- name: Build
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" x86 || exit /b 1
python scripts\mk_win_dist.py --x86-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --zip
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: WindowsBuild-x86
path: dist/*.zip
@ -422,7 +551,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -432,11 +561,12 @@ jobs:
- name: Build
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64_arm64
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" amd64_arm64 || exit /b 1
python scripts\mk_win_dist_cmake.py --arm64-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --assembly-version=${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.PATCH }} --zip
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: WindowsBuild-arm64
path: dist/arm64/*.zip
@ -452,7 +582,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -460,43 +590,43 @@ jobs:
python-version: '3.x'
- name: Download Win64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-x64
path: package
- name: Download Win ARM64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-arm64
path: package
- name: Download Ubuntu Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: UbuntuBuild
path: package
- name: Download Ubuntu ARM64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: UbuntuArm64
path: package
- name: Download macOS Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: macOsBuild
path: package
- name: Download macOS Arm64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: MacArm64
path: package
- name: Setup NuGet
uses: nuget/setup-nuget@v2
uses: nuget/setup-nuget@v4
with:
nuget-version: 'latest'
@ -513,7 +643,7 @@ jobs:
nuget pack out\Microsoft.Z3.sym.nuspec -Version ${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.PATCH }}.${{ github.run_number }} -OutputDirectory . -Verbosity detailed -Symbols -SymbolPackageFormat snupkg -BasePath out
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: NuGet
path: |
@ -527,7 +657,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -535,13 +665,13 @@ jobs:
python-version: '3.x'
- name: Download artifacts
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-x86
path: package
- name: Setup NuGet
uses: nuget/setup-nuget@v2
uses: nuget/setup-nuget@v4
with:
nuget-version: 'latest'
@ -558,7 +688,7 @@ jobs:
nuget pack out\Microsoft.Z3.x86.sym.nuspec -Version ${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.PATCH }}.${{ github.run_number }} -OutputDirectory . -Verbosity detailed -Symbols -SymbolPackageFormat snupkg -BasePath out
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: NuGet32
path: |
@ -568,11 +698,11 @@ jobs:
python-package:
name: "Python packaging"
needs: [mac-build-x64, mac-build-arm64, windows-build-x64, windows-build-x86, windows-build-arm64, manylinux-python-amd64, manylinux-python-arm64]
needs: [mac-build-x64, mac-build-arm64, windows-build-x64, windows-build-x86, windows-build-arm64, manylinux-python-amd64, manylinux-python-arm64, manylinux-python-riscv64]
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -580,47 +710,53 @@ jobs:
python-version: '3.x'
- name: Download macOS x64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: macOsBuild
path: artifacts
- name: Download macOS Arm64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: MacArm64
path: artifacts
- name: Download Win64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-x64
path: artifacts
- name: Download Win32 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-x86
path: artifacts
- name: Download Win ARM64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-arm64
path: artifacts
- name: Download ManyLinux AMD64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: ManyLinuxPythonBuildAMD64
path: artifacts
- name: Download ManyLinux Arm64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: ManyLinuxPythonBuildArm64
path: artifacts
- name: Download ManyLinux RISC-V 64 Build
uses: actions/download-artifact@v8.0.1
with:
name: ManyLinuxPythonBuildRiscv64
path: artifacts
- name: Extract builds
run: |
cd artifacts
@ -651,7 +787,7 @@ jobs:
cp artifacts/*.whl src/api/python/dist/.
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: PythonPackages
path: src/api/python/dist/*
@ -681,10 +817,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download all artifacts
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
path: tmp
@ -692,19 +828,44 @@ jobs:
run: ls -R tmp
- name: Delete existing Nightly release and tag
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
# Delete the release first (this also deletes assets)
gh release delete Nightly --yes || echo "No release to delete"
# Delete the tag explicitly
git push origin :refs/tags/Nightly || echo "No tag to delete"
if gh release view Nightly > /dev/null 2>&1; then
# Delete the release and associated tag if it exists.
if ! gh release delete Nightly --yes --cleanup-tag; then
echo "Failed to delete existing Nightly release/tag"
exit 1
fi
else
echo "No release to delete"
fi
if git ls-remote --exit-code --tags origin refs/tags/Nightly > /dev/null 2>&1; then
# Tag may exist without a release (for example, if a previous run failed mid-deploy).
git push origin :refs/tags/Nightly
fi
- name: Create Nightly release
env:
GH_TOKEN: ${{ github.token }}
run: |
git tag -f Nightly "${{ github.sha }}"
if ! git push --force origin refs/tags/Nightly; then
echo "Failed to push Nightly tag to origin"
exit 1
fi
TAG_SHA="$(git ls-remote --tags origin refs/tags/Nightly refs/tags/Nightly^{} | awk '
$2 == "refs/tags/Nightly^{}" { sha = $1 }
$2 == "refs/tags/Nightly" && sha == "" { sha = $1 }
END { print sha }
')"
if [ "$TAG_SHA" != "${{ github.sha }}" ]; then
echo "Nightly tag points to $TAG_SHA, expected ${{ github.sha }}"
exit 1
fi
ls
find tmp -type f \( -name "*.zip" -o -name "*.whl" -o -name "*.tar.gz" -o -name "*.nupkg" -o -name "*.snupkg" \) -print0 > release_files.txt
@ -730,7 +891,7 @@ jobs:
--title "Nightly" \
--notes "Automated nightly build from commit ${{ github.sha }}" \
--prerelease \
--target ${{ github.sha }} \
--verify-tag \
"${unique_files[@]}"
else
echo "No files to release after deduplication"
@ -749,14 +910,23 @@ jobs:
contents: read
steps:
- name: Download Python packages
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: PythonPackages
path: dist
- name: Rewrite macOS wheel tags unsupported by test.PyPI
run: |
# TestPyPI rejects current macOS wheel tags such as macosx_13_3_*.
# Rewrite only the unsupported 13_3 tag to 13 for upload validation.
for whl in dist/*-macosx_13_3_*.whl; do
[ -e "$whl" ] || continue
mv "$whl" "${whl/macosx_13_3_/macosx_13_0_}"
done
ls -l dist
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
repository-url: https://test.pypi.org/legacy/

View file

@ -4,9 +4,9 @@ on:
workflow_dispatch:
inputs:
version:
description: 'Version number for the NuGet package (e.g., 4.16.0)'
description: 'Version number for the NuGet package (e.g., 4.17.1)'
required: true
default: '4.16.0'
default: '4.17.1'
push:
tags:
- 'z3-*'
@ -20,7 +20,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -30,11 +30,12 @@ jobs:
- name: Build Windows x64
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
python scripts\mk_win_dist.py --x64-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --assembly-version=${{ github.event.inputs.version || '4.16.0' }} --zip
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" x64 || exit /b 1
python scripts\mk_win_dist.py --x64-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --assembly-version=${{ github.event.inputs.version || '4.17.1' }} --zip
- name: Upload Windows x64 artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: windows-x64
path: dist/*.zip
@ -44,7 +45,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -54,11 +55,12 @@ jobs:
- name: Build Windows x86
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
python scripts\mk_win_dist.py --x86-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --assembly-version=${{ github.event.inputs.version || '4.16.0' }} --zip
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" x86 || exit /b 1
python scripts\mk_win_dist.py --x86-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --assembly-version=${{ github.event.inputs.version || '4.17.1' }} --zip
- name: Upload Windows x86 artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: windows-x86
path: dist/*.zip
@ -68,7 +70,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -78,11 +80,12 @@ jobs:
- name: Build Windows ARM64
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64_arm64
python scripts\mk_win_dist_cmake.py --arm64-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --assembly-version=${{ github.event.inputs.version || '4.16.0' }} --zip
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" amd64_arm64 || exit /b 1
python scripts\mk_win_dist_cmake.py --arm64-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --assembly-version=${{ github.event.inputs.version || '4.17.1' }} --zip
- name: Upload Windows ARM64 artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: windows-arm64
path: build-dist\arm64\dist\*.zip
@ -92,7 +95,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -103,7 +106,7 @@ jobs:
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk
- name: Upload Ubuntu artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ubuntu
path: dist/*.zip
@ -113,7 +116,7 @@ jobs:
runs-on: macos-14
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -124,7 +127,7 @@ jobs:
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk
- name: Upload macOS x64 artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: macos-x64
path: dist/*.zip
@ -134,7 +137,7 @@ jobs:
runs-on: macos-14
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -145,7 +148,7 @@ jobs:
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=arm64
- name: Upload macOS ARM64 artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: macos-arm64
path: dist/*.zip
@ -157,7 +160,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -165,7 +168,7 @@ jobs:
python-version: '3.x'
- name: Download all artifacts
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
path: packages
@ -181,7 +184,7 @@ jobs:
ls -la package-files/
- name: Setup NuGet
uses: nuget/setup-nuget@v2
uses: nuget/setup-nuget@v4
with:
nuget-version: 'latest'
@ -189,7 +192,7 @@ jobs:
shell: cmd
run: |
cd package-files
python ..\scripts\mk_nuget_task.py . ${{ github.event.inputs.version || '4.16.0' }} https://github.com/Z3Prover/z3 ${{ github.ref_name }} ${{ github.sha }} ${{ github.workspace }} symbols
python ..\scripts\mk_nuget_task.py . ${{ github.event.inputs.version || '4.17.1' }} https://github.com/Z3Prover/z3 ${{ github.ref_name }} ${{ github.sha }} ${{ github.workspace }} symbols
- name: Pack NuGet package
shell: cmd
@ -198,7 +201,7 @@ jobs:
nuget pack out\Microsoft.Z3.sym.nuspec -OutputDirectory . -Verbosity detailed -Symbols -SymbolPackageFormat snupkg -BasePath out
- name: Upload NuGet package
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: nuget-x64
path: |
@ -212,7 +215,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -220,7 +223,7 @@ jobs:
python-version: '3.x'
- name: Download x86 artifact
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: windows-x86
path: packages
@ -230,7 +233,7 @@ jobs:
run: find packages -type f
- name: Setup NuGet
uses: nuget/setup-nuget@v2
uses: nuget/setup-nuget@v4
with:
nuget-version: 'latest'
@ -238,7 +241,7 @@ jobs:
shell: cmd
run: |
cd packages
python ..\scripts\mk_nuget_task.py . ${{ github.event.inputs.version || '4.16.0' }} https://github.com/Z3Prover/z3 ${{ github.ref_name }} ${{ github.sha }} ${{ github.workspace }} symbols x86
python ..\scripts\mk_nuget_task.py . ${{ github.event.inputs.version || '4.17.1' }} https://github.com/Z3Prover/z3 ${{ github.ref_name }} ${{ github.sha }} ${{ github.workspace }} symbols x86
- name: Pack NuGet package
shell: cmd
@ -247,7 +250,7 @@ jobs:
nuget pack out\Microsoft.Z3.x86.sym.nuspec -OutputDirectory . -Verbosity detailed -Symbols -SymbolPackageFormat snupkg -BasePath out
- name: Upload NuGet package
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: nuget-x86
path: |

View file

@ -17,32 +17,24 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
# Cache ccache (shared across runs)
- name: Cache ccache
uses: actions/cache@v5.0.3
uses: actions/cache@v6.1.0
with:
path: ~/.ccache
key: ${{ runner.os }}-ccache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-ccache-
# Cache opam (compiler + packages)
- name: Cache opam
uses: actions/cache@v5.0.3
with:
path: ~/.opam
key: ${{ runner.os }}-opam-${{ matrix.ocaml-version }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-opam-${{ matrix.ocaml-version }}-
# Setup OCaml via action
# Setup OCaml via action (handles opam caching internally)
- uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{ matrix.ocaml-version }}
opam-disable-sandboxing: true
cache-prefix: v1
# Platform-specific dependencies
- name: Install system dependencies (Ubuntu)
if: matrix.os == 'ubuntu-latest'
@ -123,4 +115,4 @@ jobs:
- name: Run ml_example (native)
run: |
export DYLD_LIBRARY_PATH=$(pwd)/build
./ml_example
./ml_example

1568
.github/workflows/ostrich-benchmark.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

423
.github/workflows/ostrich-benchmark.md vendored Normal file
View file

@ -0,0 +1,423 @@
---
description: Run Z3 string solver benchmarks (seq vs nseq) and ZIPT on all Ostrich benchmarks from tests/ostrich.zip on the c3 branch and post results as a GitHub discussion
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
permissions: read-all
network:
allowed:
- defaults
- api.nuget.org
tools:
bash: true
github:
toolsets: [default]
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[Ostrich Benchmark] "
category: "Agentic Workflows"
close-older-discussions: true
missing-tool:
create-issue: true
noop:
report-as-issue: false
timeout-minutes: 180
steps:
- name: Checkout c3 branch
uses: actions/checkout@v6.0.2
with:
ref: c3
fetch-depth: 1
persist-credentials: false
---
# Ostrich Benchmark: Z3 c3 branch vs ZIPT
You are an AI agent that benchmarks Z3 string solvers (`seq` and `nseq`) and the standalone ZIPT solver on all SMT-LIB2 benchmarks from the `tests/ostrich.zip` archive on the `c3` branch, and publishes a summary report as a GitHub discussion.
## Context
- **Repository**: ${{ github.repository }}
- **Workspace**: ${{ github.workspace }}
- **Branch**: c3 (already checked out by the workflow setup step)
## Phase 1: Build Z3
Build Z3 from the checked-out `c3` branch using CMake + Ninja, including the .NET bindings required by ZIPT.
```bash
cd ${{ github.workspace }}
# Install build dependencies if missing
sudo apt-get install -y ninja-build cmake python3 zstd dotnet-sdk-8.0 unzip 2>/dev/null || true
# Configure the build in Release mode for better performance and lower memory usage
# (Release mode is sufficient for benchmarking; the workflow does not use -tr: trace flags)
mkdir -p build
cd build
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DZ3_BUILD_DOTNET_BINDINGS=ON 2>&1 | tail -20
# Build z3 binary and .NET bindings SYNCHRONOUSLY (do NOT add & to background these commands).
# Running ninja in the background while the LLM agent is also active causes OOM and kills the
# agent process. Wait for each build command to finish before continuing.
# -j1 limits parallelism to reduce peak memory usage alongside the LLM agent process.
ninja z3 2>&1 | tail -30
ninja build_z3_dotnet_bindings 2>&1 | tail -20
# Verify the build succeeded
./z3 --version
# Locate the Microsoft.Z3.dll produced by the build
Z3_DOTNET_DLL=$(find . -name "Microsoft.Z3.dll" -not -path "*/obj/*" | head -1)
if [ -z "$Z3_DOTNET_DLL" ]; then
echo "ERROR: Microsoft.Z3.dll not found after build"
exit 1
fi
echo "Found Microsoft.Z3.dll at: $Z3_DOTNET_DLL"
```
If the build fails, report the error clearly and exit without proceeding.
Once the binary is confirmed working, call the `noop` safe-output tool with the message `"Z3 built successfully from the c3 branch. Starting ZIPT build and benchmark — results will be posted as a GitHub Discussion once complete."` This keepalive call refreshes the safe-output MCP session before the long build and benchmark phases begin, preventing a session timeout.
## Phase 2a: Clone and Build ZIPT
Clone the ZIPT solver from the `parikh` branch and compile it against the Z3 .NET bindings built in Phase 1.
```bash
cd ${{ github.workspace }}
# Re-locate the Microsoft.Z3.dll if needed
Z3_DOTNET_DLL=$(find build -name "Microsoft.Z3.dll" -not -path "*/obj/*" | head -1)
Z3_LIB_DIR=${{ github.workspace }}/build
# Clone ZIPT (parikh branch)
git clone --depth=1 --branch parikh https://github.com/CEisenhofer/ZIPT.git /tmp/zipt
# Patch ZIPT.csproj to point at the freshly built Microsoft.Z3.dll
# (the repo has a Windows-relative hardcoded path that won't exist here)
sed -i "s|<HintPath>.*</HintPath>|<HintPath>$Z3_DOTNET_DLL</HintPath>|" /tmp/zipt/ZIPT/ZIPT.csproj
# Build ZIPT in Release mode
cd /tmp/zipt/ZIPT
dotnet build --configuration Release 2>&1 | tail -20
# Locate the built ZIPT.dll
ZIPT_DLL=$(find /tmp/zipt/ZIPT/bin/Release -name "ZIPT.dll" | head -1)
if [ -z "$ZIPT_DLL" ]; then
echo "ERROR: ZIPT.dll not found after build"
exit 1
fi
echo "ZIPT binary: $ZIPT_DLL"
# Make libz3.so visible to the .NET runtime at ZIPT startup
ZIPT_OUT_DIR=$(dirname "$ZIPT_DLL")
if cp "$Z3_LIB_DIR/libz3.so" "$ZIPT_OUT_DIR/" 2>/dev/null; then
echo "Copied libz3.so to $ZIPT_OUT_DIR"
else
echo "WARNING: could not copy libz3.so to $ZIPT_OUT_DIR — setting LD_LIBRARY_PATH fallback"
fi
export LD_LIBRARY_PATH="$Z3_LIB_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
echo "ZIPT build complete."
```
If the ZIPT build fails, note the error in the report but continue with the Z3-only benchmark columns.
## Phase 2b: Extract Benchmark Files
Extract all SMT-LIB2 files from the `tests/ostrich.zip` archive.
```bash
cd ${{ github.workspace }}
# Extract the zip archive
mkdir -p /tmp/ostrich_benchmarks
unzip -q tests/ostrich.zip -d /tmp/ostrich_benchmarks
# List all .smt2 files
find /tmp/ostrich_benchmarks -name "*.smt2" -type f | sort > /tmp/all_ostrich_files.txt
TOTAL_FILES=$(wc -l < /tmp/all_ostrich_files.txt)
echo "Total Ostrich .smt2 files: $TOTAL_FILES"
if [ "$TOTAL_FILES" -eq 0 ]; then
echo "ERROR: No .smt2 files found in tests/ostrich.zip"
exit 1
fi
```
Once the benchmark files are confirmed, call the `noop` safe-output tool with the message `"Benchmark files ready: <TOTAL_FILES> Ostrich .smt2 files extracted. Starting benchmark run — this may take over an hour."` This second keepalive refreshes the safe-output MCP session immediately before the long per-file benchmark loop begins.
## Phase 3: Run Benchmarks
Run every file from `/tmp/all_ostrich_files.txt` with both Z3 string solvers and ZIPT. Use a **5-second timeout** per run.
For each file, run:
1. `z3 smt.string_solver=seq -T:5 <file>` — seq solver
2. `z3 smt.string_solver=nseq -T:5 <file>` — nseq (ZIPT) solver
3. `dotnet <ZIPT.dll> -t:5000 <file>` — standalone ZIPT solver (milliseconds)
Capture:
- **Verdict**: `sat`, `unsat`, `unknown`, `timeout` (if exit code indicates timeout or process is killed), or `bug` (if a solver crashes / produces a non-standard result)
- **Time** (seconds): wall-clock time for the run
- A row is flagged `SOUNDNESS_DISAGREEMENT` when any two solvers that both produced a definitive answer (sat/unsat) disagree
Use a bash script to automate this:
```bash
#!/usr/bin/env bash
set -euo pipefail
Z3=${{ github.workspace }}/build/z3
ZIPT_DLL=$(find /tmp/zipt/ZIPT/bin/Release -name "ZIPT.dll" 2>/dev/null | head -1)
ZIPT_AVAILABLE=false
[ -n "$ZIPT_DLL" ] && ZIPT_AVAILABLE=true
# Ensure libz3.so is on the dynamic-linker path for the .NET runtime
export LD_LIBRARY_PATH=${{ github.workspace }}/build${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
RESULTS=/tmp/benchmark_results.tsv
mkdir -p /tmp/ostrich_run
echo -e "file\tseq_verdict\tseq_time\tnseq_verdict\tnseq_time\tzipt_verdict\tzipt_time\tnotes" > "$RESULTS"
run_z3_seq() {
local file="$1"
local start end elapsed verdict output exit_code
start=$(date +%s%3N)
output=$(timeout 7 "$Z3" "smt.string_solver=seq" -T:5 "$file" 2>&1)
exit_code=$?
end=$(date +%s%3N)
elapsed=$(echo "scale=3; ($end - $start) / 1000" | bc)
if echo "$output" | grep -q "^unsat"; then
verdict="unsat"
elif echo "$output" | grep -q "^sat"; then
verdict="sat"
elif echo "$output" | grep -q "^unknown"; then
verdict="unknown"
elif [ "$exit_code" -eq 124 ]; then
verdict="timeout"
elif echo "$output" | grep -qi "error\|assertion\|segfault\|SIGABRT\|exception"; then
verdict="bug"
else
verdict="unknown"
fi
echo "$verdict $elapsed"
}
run_z3_nseq() {
local file="$1"
local start end elapsed verdict output exit_code
start=$(date +%s%3N)
output=$(timeout 7 "$Z3" "smt.string_solver=nseq" -T:5 "$file" 2>&1)
exit_code=$?
end=$(date +%s%3N)
elapsed=$(echo "scale=3; ($end - $start) / 1000" | bc)
if echo "$output" | grep -q "^unsat"; then
verdict="unsat"
elif echo "$output" | grep -q "^sat"; then
verdict="sat"
elif echo "$output" | grep -q "^unknown"; then
verdict="unknown"
elif [ "$exit_code" -eq 124 ]; then
verdict="timeout"
elif echo "$output" | grep -qi "error\|assertion\|segfault\|SIGABRT\|exception"; then
verdict="bug"
else
verdict="unknown"
fi
echo "$verdict $elapsed"
}
run_zipt() {
local file="$1"
local start end elapsed verdict output exit_code
if [ "$ZIPT_AVAILABLE" != "true" ]; then
echo "n/a 0.000"
return
fi
start=$(date +%s%3N)
# ZIPT prints the filename on the first line, then SAT/UNSAT/UNKNOWN on subsequent lines
output=$(timeout 7 dotnet "$ZIPT_DLL" -t:5000 "$file" 2>&1)
exit_code=$?
end=$(date +%s%3N)
elapsed=$(echo "scale=3; ($end - $start) / 1000" | bc)
if echo "$output" | grep -qi "^UNSAT$"; then
verdict="unsat"
elif echo "$output" | grep -qi "^SAT$"; then
verdict="sat"
elif echo "$output" | grep -qi "^UNKNOWN$"; then
verdict="unknown"
elif [ "$exit_code" -eq 124 ]; then
verdict="timeout"
elif echo "$output" | grep -qi "error\|crash\|exception\|Unsupported"; then
verdict="bug"
else
verdict="unknown"
fi
echo "$verdict $elapsed"
}
COUNTER=0
while IFS= read -r file; do
COUNTER=$((COUNTER + 1))
fname=$(basename "$file")
seq_result=$(run_z3_seq "$file")
nseq_result=$(run_z3_nseq "$file")
zipt_result=$(run_zipt "$file")
seq_verdict=$(echo "$seq_result" | cut -d' ' -f1)
seq_time=$(echo "$seq_result" | cut -d' ' -f2)
nseq_verdict=$(echo "$nseq_result" | cut -d' ' -f1)
nseq_time=$(echo "$nseq_result" | cut -d' ' -f2)
zipt_verdict=$(echo "$zipt_result" | cut -d' ' -f1)
zipt_time=$(echo "$zipt_result" | cut -d' ' -f2)
# Flag soundness disagreement when any two definitive verdicts disagree
notes=""
declare -A definitive_map
[ "$seq_verdict" = "sat" ] || [ "$seq_verdict" = "unsat" ] && definitive_map[seq]="$seq_verdict"
[ "$nseq_verdict" = "sat" ] || [ "$nseq_verdict" = "unsat" ] && definitive_map[nseq]="$nseq_verdict"
[ "$zipt_verdict" = "sat" ] || [ "$zipt_verdict" = "unsat" ] && definitive_map[zipt]="$zipt_verdict"
has_sat=false; has_unsat=false
for v in "${definitive_map[@]}"; do
[ "$v" = "sat" ] && has_sat=true
[ "$v" = "unsat" ] && has_unsat=true
done
if $has_sat && $has_unsat; then
notes="SOUNDNESS_DISAGREEMENT"
fi
echo -e "$fname\t$seq_verdict\t$seq_time\t$nseq_verdict\t$nseq_time\t$zipt_verdict\t$zipt_time\t$notes" >> "$RESULTS"
echo "[$COUNTER] [$fname] seq=$seq_verdict(${seq_time}s) nseq=$nseq_verdict(${nseq_time}s) zipt=$zipt_verdict(${zipt_time}s) $notes"
done < /tmp/all_ostrich_files.txt
echo "Benchmark run complete. Results saved to $RESULTS"
```
Save this script to `/tmp/run_ostrich_benchmarks.sh`, make it executable, and run it. Do not skip any file.
## Phase 4: Generate Summary Report
Read `/tmp/benchmark_results.tsv` and compute statistics. Then generate a Markdown report.
Compute:
- **Total benchmarks**: total number of files run
- **Per solver (seq, nseq, and ZIPT)**: count of sat / unsat / unknown / timeout / bug verdicts
- **Total time used**: sum of all times for each solver
- **Average time per benchmark**: total_time / total_files
- **Soundness disagreements**: files where any two solvers that both returned a definitive answer disagree
- **Bugs / crashes**: files with error/crash verdicts
Format the report as a GitHub Discussion post (GitHub-flavored Markdown):
```markdown
### Ostrich Benchmark Report — Z3 c3 branch
**Date**: <today's date>
**Branch**: c3
**Benchmark set**: Ostrich (all files from tests/ostrich.zip)
**Timeout**: 5 seconds per benchmark (`-T:5` for Z3; `-t:5000` for ZIPT)
---
### Summary
| Metric | seq solver | nseq solver | ZIPT solver |
|--------|-----------|-------------|-------------|
| sat | X | X | X |
| unsat | X | X | X |
| unknown | X | X | X |
| timeout | X | X | X |
| bug/crash | X | X | X |
| **Total time (s)** | X.XXX | X.XXX | X.XXX |
| **Avg time/benchmark (s)** | X.XXX | X.XXX | X.XXX |
**Soundness disagreements** (any two solvers return conflicting sat/unsat): N
---
### Per-File Results
<details>
<summary>Click to expand full per-file table</summary>
| # | File | seq verdict | seq time (s) | nseq verdict | nseq time (s) | ZIPT verdict | ZIPT time (s) | Notes |
|---|------|-------------|-------------|--------------|--------------|--------------|--------------|-------|
| 1 | benchmark_0001.smt2 | sat | 0.123 | sat | 0.456 | sat | 0.789 | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
</details>
---
### Notable Issues
#### Soundness Disagreements (Critical)
<list files where any two solvers disagree on sat/unsat, naming which solvers disagree>
#### Crashes / Bugs
<list files where any solver crashed or produced an error>
#### Slow Benchmarks (> 4s)
<list files that took more than 4 seconds for any solver>
---
*Generated automatically by the Ostrich Benchmark workflow on the c3 branch.*
```
## Phase 5: Post to GitHub Discussion
Post the Markdown report as a new GitHub Discussion using the `create-discussion` safe output.
- **Category**: "Agentic Workflows"
- **Title**: `[Ostrich Benchmark] Z3 c3 branch — <date>`
- Close older discussions with the same title prefix to avoid clutter.
## Guidelines
- **Always build from c3 branch**: The workspace is already checked out on c3; don't change branches.
- **Synchronous builds only**: Never run `ninja` (or any other build command) in the background using `&`. Running the build concurrently with LLM inference causes the agent process to be killed by the OOM killer (exit 137) because C++ compilation and the LLM together exceed available RAM. Always wait for each build command to finish before proceeding.
- **Release build**: The build uses `CMAKE_BUILD_TYPE=Release` for lower memory footprint and faster compilation on the GitHub Actions runner. The benchmark only needs verdict and timing output; no `-tr:` trace flags are used.
- **Run all benchmarks**: Unlike the QF_S workflow, run every file in the archive — do not randomly sample.
- **5-second timeout**: Pass `-T:5` to Z3 (both seq and nseq) and `-t:5000` to ZIPT (milliseconds). Use `timeout 7` as the outer OS-level guard to allow the solver to exit cleanly before being killed.
- **Be precise with timing**: Use millisecond-precision timestamps and report times in seconds with 3 decimal places.
- **Distinguish timeout from unknown**: A timeout is different from `(unknown)` returned by a solver within its time budget.
- **ZIPT output format**: ZIPT prints the input filename on the first line, then `SAT`, `UNSAT`, or `UNKNOWN` on subsequent lines. Parse accordingly.
- **Report soundness bugs prominently**: If any benchmark shows a conflict between any two solvers that both returned a definitive sat/unsat answer, highlight it as a critical finding and name which pair disagrees.
- **Handle build failures gracefully**: If Z3 fails to build, report the error and create a brief discussion noting the build failure. If ZIPT fails to build, continue with only the seq/nseq columns and note `n/a` for ZIPT results.
- **Large report**: Always put the per-file table in a `<details>` collapsible section since there may be many files.
- **Progress logging**: Print a line per file as you run it (e.g., `[N] [filename] seq=...`) so the workflow log shows progress even for large benchmark sets.
## Safe Output Guarantee
You **MUST** call either `create_discussion` or `noop` before the workflow ends, regardless of what happened during execution:
- **Build succeeded, benchmarks ran**: Call `create_discussion` with the full report.
- **Build succeeded, benchmarks partially ran**: Call `create_discussion` with whatever results were collected and a note about what could not be completed.
- **Z3 build failed**: Call `noop` with a brief message describing the build error.
- **No benchmarks could be run**: Call `noop` with a summary of what failed and why.
Failing to produce any safe output triggers an automatic workflow-failure issue that clutters the repository.

57
.github/workflows/pyodide-pypi.yml vendored Normal file
View file

@ -0,0 +1,57 @@
name: Pyodide Wheel (PyPI)
# Builds a PEP 783 `pyemscripten_*_wasm32` wheel for z3-solver using cibuildwheel
# and publishes it to PyPI on tag pushes. Unlike the legacy pyodide.yml (which
# uses `pyodide build` and produces a Pyodide-version-locked emscripten_* wheel
# uploaded only as a CI artifact), this wheel is installable at runtime with
# micropip from PyPI. See src/api/python/pyproject.toml [tool.cibuildwheel].
on:
release:
types: [created]
workflow_dispatch:
permissions:
contents: read
jobs:
build-pyodide:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7.0.0
- name: Build Pyodide wheel
uses: pypa/cibuildwheel@v4.1.0
with:
# The Python bindings live in a subdirectory of the repo.
package-dir: src/api/python
env:
CIBW_PLATFORM: pyodide
# Exception/longjmp/bigint flags are declared in
# src/api/python/pyproject.toml ([tool.pyodide.build]) and combined
# with Pyodide's -fwasm-exceptions defaults. Don't set CFLAGS/CXXFLAGS
# here — a JS-EH -fexceptions value conflicts with the wasm-EH ABI.
- name: Store Pyodide wheel
uses: actions/upload-artifact@v7
with:
name: pyodide-wheel
path: wheelhouse/*.whl
publish:
name: Publish to PyPI
runs-on: ubuntu-24.04
if: startsWith(github.ref, 'refs/tags/')
needs: [build-pyodide]
environment: release
permissions:
id-token: write # trusted publishing (OIDC), no API token needed
steps:
- name: Download Pyodide wheel
uses: actions/download-artifact@v8
with:
name: pyodide-wheel
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

View file

@ -1,68 +0,0 @@
name: Pyodide Build
on:
schedule:
- cron: '0 0 */2 * *'
workflow_dispatch:
env:
BUILD_TYPE: Release
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
- name: Setup packages
run: sudo apt-get update && sudo apt-get install -y python3-dev python3-pip python3-venv
- name: Create venv
run: python3 -m venv ~/env
- name: Install pyodide
run: ~/env/bin/pip install pyodide-build pyodide-cli
- name: Configure CMake and build
run: |
git clone https://github.com/emscripten-core/emsdk.git ~/emsdk
cd ~/emsdk && PYODIDE_EMSCRIPTEN_VERSION=$(~/env/bin/pyodide config get emscripten_version)
./emsdk install ${PYODIDE_EMSCRIPTEN_VERSION}
./emsdk activate ${PYODIDE_EMSCRIPTEN_VERSION}
- name: Build Z3
run: |
source ~/emsdk/emsdk_env.sh
cd src/api/python
CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" CXXFLAG="${CXXFLAGS}" ~/env/bin/pyodide build --exports whole_archive
env:
CFLAGS: "-fexceptions -s DISABLE_EXCEPTION_CATCHING=0 -g2"
LDFLAGS: "-fexceptions -s WASM_BIGINT"
CXXFLAGS: "-fexceptions -s DISABLE_EXCEPTION_CATCHING=0"
- name: Setup env-pyodide
run: |
source ~/env/bin/activate
source ~/emsdk/emsdk_env.sh
pyodide venv ~/env-pyodide
- name: Setup z3 wheel
run: |
~/env-pyodide/bin/pip install src/api/python/dist/*.whl
~/env-pyodide/bin/python - <src/api/python/z3test.py z3
- name: Package wheel
uses: actions/upload-artifact@master
with:
name: pyodide-wheel
path: src/api/python/dist/*.whl
retention-days: 1

1572
.github/workflows/qf-s-benchmark.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

405
.github/workflows/qf-s-benchmark.md vendored Normal file
View file

@ -0,0 +1,405 @@
---
description: Benchmark Z3 seq vs nseq string solvers on QF_S test suite from the c3 branch and post results as a GitHub discussion
on:
schedule:
- cron: "0 0,12 * * *"
workflow_dispatch:
permissions: read-all
network: defaults
tools:
bash: true
github:
toolsets: [default]
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[QF_S Benchmark] "
category: "Agentic Workflows"
close-older-discussions: true
missing-tool:
create-issue: true
noop:
report-as-issue: false
timeout-minutes: 120
steps:
- name: Checkout c3 branch
uses: actions/checkout@v6.0.2
with:
ref: c3
fetch-depth: 1
persist-credentials: false
---
# QF_S String Solver Benchmark
## Job Description
Your name is ${{ github.workflow }}. You are an expert performance analyst for the Z3 theorem prover, specializing in the string/sequence theory. Your task is to benchmark the `seq` solver (classical string theory) against the `nseq` solver (ZIPT-based string theory) on the QF_S test suite from the `c3` branch, and post a structured report as a GitHub Discussion.
The workspace already contains the `c3` branch (checked out by the preceding workflow step).
## Phase 1: Set Up the Build Environment
Install required build tools:
```bash
sudo apt-get update -y
sudo apt-get install -y cmake ninja-build python3 python3-pip time
```
Verify tools:
```bash
cmake --version
ninja --version
python3 --version
```
## Phase 2: Build Z3 in Release Mode
Build Z3 in Release mode for accurate benchmark performance numbers and lower memory usage. Running `ninja` in the background with `&` is not allowed — concurrent C++ compilation and LLM inference can exhaust available RAM and kill the agent process.
```bash
mkdir -p /tmp/z3-build
cd /tmp/z3-build
cmake "$GITHUB_WORKSPACE" \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DZ3_BUILD_TEST_EXECUTABLES=OFF \
2>&1 | tee /tmp/z3-cmake.log
ninja -j2 z3 2>&1 | tee /tmp/z3-build.log
```
Verify the binary was built:
```bash
/tmp/z3-build/z3 --version
```
If the build fails, report it immediately and stop.
Once the binary is confirmed working, call the `noop` safe-output tool with the message `"Z3 built successfully from the c3 branch. Benchmark starting — results will be posted as a GitHub Discussion once complete."` This keepalive call refreshes the safe-output MCP session before the long benchmark run begins, preventing a session timeout.
## Phase 3: Discover QF_S Benchmark Files
Find all `.smt2` benchmark files in the workspace that belong to the QF_S logic:
```bash
# Search for explicit QF_S logic declarations
grep -rl 'QF_S' "$GITHUB_WORKSPACE" --include='*.smt2' 2>/dev/null > /tmp/qf_s_files.txt
# Also look in dedicated benchmark directories
find "$GITHUB_WORKSPACE" \
\( -path "*/QF_S/*" -o -path "*/qf_s/*" -o -path "*/benchmarks/*" \) \
-name '*.smt2' 2>/dev/null >> /tmp/qf_s_files.txt
# Deduplicate
sort -u /tmp/qf_s_files.txt -o /tmp/qf_s_files.txt
TOTAL=$(wc -l < /tmp/qf_s_files.txt)
echo "Found $TOTAL QF_S benchmark files"
head -20 /tmp/qf_s_files.txt
```
If fewer than 5 files are found, also scan the entire workspace for any `.smt2` file that exercises string constraints:
```bash
if [ "$TOTAL" -lt 5 ]; then
grep -rl 'declare.*String\|str\.\|seq\.' "$GITHUB_WORKSPACE" \
--include='*.smt2' 2>/dev/null >> /tmp/qf_s_files.txt
sort -u /tmp/qf_s_files.txt -o /tmp/qf_s_files.txt
TOTAL=$(wc -l < /tmp/qf_s_files.txt)
echo "After extended search: $TOTAL files"
fi
```
Cap the benchmark set to keep total runtime under 60 minutes:
```bash
# Use at most 300 files; take a random sample if more are available
if [ "$TOTAL" -gt 300 ]; then
shuf -n 300 /tmp/qf_s_files.txt > /tmp/qf_s_sample.txt
else
cp /tmp/qf_s_files.txt /tmp/qf_s_sample.txt
fi
SAMPLE=$(wc -l < /tmp/qf_s_sample.txt)
echo "Running benchmarks on $SAMPLE files"
```
## Phase 4: Run Benchmarks — seq vs nseq
Run each benchmark with both solvers. Use a per-file timeout of 5 seconds. Set Z3's internal timeout to 4 seconds so it exits cleanly before the shell timeout fires.
```bash
Z3=/tmp/z3-build/z3
TIMEOUT_SEC=5
Z3_TIMEOUT_SEC=4
RESULTS=/tmp/benchmark-results.csv
echo "file,seq_result,seq_time_ms,nseq_result,nseq_time_ms" > "$RESULTS"
total=0
done_count=0
while IFS= read -r smt_file; do
total=$((total + 1))
# Run with seq solver; capture both stdout (z3 output) and stderr (time output)
SEQ_OUT=$({ time timeout "$TIMEOUT_SEC" "$Z3" \
smt.string_solver=seq \
-T:"$Z3_TIMEOUT_SEC" \
"$smt_file" 2>/dev/null; } 2>&1)
SEQ_RESULT=$(echo "$SEQ_OUT" | grep -E '^(sat|unsat|unknown)' | head -1)
SEQ_MS=$(echo "$SEQ_OUT" | grep real | awk '{split($2,a,"m"); split(a[2],b,"s"); printf "%d", (a[1]*60+b[1])*1000}')
[ -z "$SEQ_RESULT" ] && SEQ_RESULT="timeout"
[ -z "$SEQ_MS" ] && SEQ_MS=$((TIMEOUT_SEC * 1000))
# Run with nseq solver; same structure
NSEQ_OUT=$({ time timeout "$TIMEOUT_SEC" "$Z3" \
smt.string_solver=nseq \
-T:"$Z3_TIMEOUT_SEC" \
"$smt_file" 2>/dev/null; } 2>&1)
NSEQ_RESULT=$(echo "$NSEQ_OUT" | grep -E '^(sat|unsat|unknown)' | head -1)
NSEQ_MS=$(echo "$NSEQ_OUT" | grep real | awk '{split($2,a,"m"); split(a[2],b,"s"); printf "%d", (a[1]*60+b[1])*1000}')
[ -z "$NSEQ_RESULT" ] && NSEQ_RESULT="timeout"
[ -z "$NSEQ_MS" ] && NSEQ_MS=$((TIMEOUT_SEC * 1000))
SHORT=$(basename "$smt_file")
echo "$SHORT,$SEQ_RESULT,$SEQ_MS,$NSEQ_RESULT,$NSEQ_MS" >> "$RESULTS"
done_count=$((done_count + 1))
if [ $((done_count % 50)) -eq 0 ]; then
echo "Progress: $done_count / $SAMPLE files completed"
fi
done < /tmp/qf_s_sample.txt
echo "Benchmark run complete: $done_count files"
```
## Phase 5: Collect Seq Traces for Interesting Cases
For benchmarks where `seq` solves in under 2 s but `nseq` times out (seq-fast/nseq-slow cases), collect a brief `seq` trace to understand what algorithm is used:
```bash
Z3=/tmp/z3-build/z3
mkdir -p /tmp/traces
# Find seq-fast / nseq-slow files: seq solved (sat/unsat) in <2000ms AND nseq timed out
awk -F, 'NR>1 && ($2=="sat"||$2=="unsat") && $3<2000 && $4=="timeout" {print $1}' \
/tmp/benchmark-results.csv > /tmp/seq_fast_nseq_slow.txt
echo "seq-fast / nseq-slow files: $(wc -l < /tmp/seq_fast_nseq_slow.txt)"
# Collect traces for at most 5 such cases
head -5 /tmp/seq_fast_nseq_slow.txt | while IFS= read -r short; do
# Find the full path
full=$(grep "/$short$" /tmp/qf_s_sample.txt | head -1)
[ -z "$full" ] && continue
timeout 5 "$Z3" \
smt.string_solver=seq \
-tr:seq \
-T:5 \
"$full" > "/tmp/traces/${short%.smt2}.seq.trace" 2>&1 || true
done
```
## Phase 6: Analyze Results
Compute summary statistics from the CSV. Save the analysis script to a file and run it:
```bash
cat > /tmp/analyze_benchmark.py << 'PYEOF'
import csv, sys
results = []
with open('/tmp/benchmark-results.csv') as f:
reader = csv.DictReader(f)
for row in reader:
results.append(row)
total = len(results)
if total == 0:
print("No results found.")
sys.exit(0)
def is_correct(r, solver):
prefix = 'seq' if solver == 'seq' else 'nseq'
return r[f'{prefix}_result'] in ('sat', 'unsat')
def timed_out(r, solver):
prefix = 'seq' if solver == 'seq' else 'nseq'
return r[f'{prefix}_result'] == 'timeout'
seq_solved = sum(1 for r in results if is_correct(r, 'seq'))
nseq_solved = sum(1 for r in results if is_correct(r, 'nseq'))
seq_to = sum(1 for r in results if timed_out(r, 'seq'))
nseq_to = sum(1 for r in results if timed_out(r, 'nseq'))
seq_times = [int(r['seq_time_ms']) for r in results if is_correct(r, 'seq')]
nseq_times = [int(r['nseq_time_ms']) for r in results if is_correct(r, 'nseq')]
def median(lst):
s = sorted(lst)
n = len(s)
return s[n//2] if n else 0
def mean(lst):
return sum(lst)//len(lst) if lst else 0
# Disagreements (sat vs unsat or vice-versa)
disagreements = [
r for r in results
if r['seq_result'] in ('sat','unsat')
and r['nseq_result'] in ('sat','unsat')
and r['seq_result'] != r['nseq_result']
]
# seq-fast / nseq-slow: seq solved in <2s, nseq timed out
seq_fast_nseq_slow = [
r for r in results
if is_correct(r, 'seq') and int(r['seq_time_ms']) < 2000 and timed_out(r, 'nseq')
]
# nseq-fast / seq-slow: nseq solved in <2s, seq timed out
nseq_fast_seq_slow = [
r for r in results
if is_correct(r, 'nseq') and int(r['nseq_time_ms']) < 2000 and timed_out(r, 'seq')
]
print(f"TOTAL={total}")
print(f"SEQ_SOLVED={seq_solved}")
print(f"NSEQ_SOLVED={nseq_solved}")
print(f"SEQ_TIMEOUTS={seq_to}")
print(f"NSEQ_TIMEOUTS={nseq_to}")
print(f"SEQ_MEDIAN_MS={median(seq_times)}")
print(f"NSEQ_MEDIAN_MS={median(nseq_times)}")
print(f"SEQ_MEAN_MS={mean(seq_times)}")
print(f"NSEQ_MEAN_MS={mean(nseq_times)}")
print(f"DISAGREEMENTS={len(disagreements)}")
print(f"SEQ_FAST_NSEQ_SLOW={len(seq_fast_nseq_slow)}")
print(f"NSEQ_FAST_SEQ_SLOW={len(nseq_fast_seq_slow)}")
# Print top-10 slowest for nseq that seq handles fast
print("\nTOP_SEQ_FAST_NSEQ_SLOW:")
for r in sorted(seq_fast_nseq_slow, key=lambda x: -int(x['nseq_time_ms']))[:10]:
print(f" {r['file']} seq={r['seq_time_ms']}ms nseq={r['nseq_time_ms']}ms seq_result={r['seq_result']} nseq_result={r['nseq_result']}")
print("\nTOP_NSEQ_FAST_SEQ_SLOW:")
for r in sorted(nseq_fast_seq_slow, key=lambda x: -int(x['seq_time_ms']))[:10]:
print(f" {r['file']} seq={r['seq_time_ms']}ms nseq={r['nseq_time_ms']}ms seq_result={r['seq_result']} nseq_result={r['nseq_result']}")
if disagreements:
print(f"\nDISAGREEMENTS ({len(disagreements)}):")
for r in disagreements[:10]:
print(f" {r['file']} seq={r['seq_result']} nseq={r['nseq_result']}")
PYEOF
python3 /tmp/analyze_benchmark.py
```
## Phase 7: Create GitHub Discussion
Use the `create_discussion` safe-output tool to post a structured benchmark report.
The discussion body should be formatted as follows (fill in real numbers from Phase 6):
```markdown
# QF_S Benchmark: seq vs nseq
**Date**: YYYY-MM-DD
**Branch**: c3
**Commit**: `<short SHA>`
**Workflow Run**: [#<run_id>](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
**Files benchmarked**: N (capped at 300, timeout 5 s per file)
---
## Summary
| Metric | seq | nseq |
|--------|-----|------|
| Files solved (sat/unsat) | SEQ_SOLVED | NSEQ_SOLVED |
| Timeouts | SEQ_TO | NSEQ_TO |
| Median solve time (solved files) | X ms | Y ms |
| Mean solve time (solved files) | X ms | Y ms |
| **Disagreements (sat≠unsat)** | — | N |
---
## Performance Comparison
### seq-fast / nseq-slow (seq < 2 s, nseq timed out)
These are benchmarks where the classical `seq` solver is significantly faster. These represent regression risk for `nseq`.
| File | seq (ms) | nseq (ms) | seq result | nseq result |
|------|----------|-----------|------------|-------------|
[TOP 10 ENTRIES]
### nseq-fast / seq-slow (nseq < 2 s, seq timed out)
These are benchmarks where `nseq` shows a performance advantage.
| File | seq (ms) | nseq (ms) | seq result | nseq result |
|------|----------|-----------|------------|-------------|
[TOP 10 ENTRIES]
---
## Correctness
**Disagreements** (files where seq says `sat` but nseq says `unsat` or vice versa): N
[If disagreements exist, list all of them here with file paths and both results]
---
## seq Trace Analysis (seq-fast / nseq-slow cases)
<details>
<summary>Click to expand trace snippets for top seq-fast/nseq-slow cases</summary>
[Insert trace snippet for each traced file, or "No traces collected" if section was skipped]
</details>
---
## Raw Data
<details>
<summary>Full results CSV (click to expand)</summary>
```csv
[PASTE FIRST 200 LINES OF /tmp/benchmark-results.csv]
```
</details>
---
*Generated by the QF_S Benchmark workflow. To reproduce: build Z3 from the `c3` branch and run `z3 smt.string_solver=seq|nseq -T:10 <file.smt2>`.*
```
## Edge Cases
- If the build fails, call `missing_data` explaining the build error and stop.
- If no benchmark files are found at all, call `missing_data` explaining that no QF_S `.smt2` files were found in the `c3` branch.
- If Z3 crashes (segfault) on a file with either solver, record the result as `crash` and continue.
- If the total benchmark set is very small (< 5 files), note this prominently in the discussion and suggest adding more QF_S benchmarks to the `c3` branch.
- If zero disagreements and both solvers time out on the same files, note that the solvers are in agreement.
- If `create_discussion` fails (e.g., MCP session error), call `report_incomplete` with the reason and include the top-line statistics (files solved, timeouts, disagreement count) in the `details` field.
## Important Notes
- **DO NOT** modify any source files or create pull requests.
- **DO NOT** run `ninja` or any build command in the background with `&` — concurrent C++ compilation and LLM inference can exhaust available RAM and kill the agent process. Always wait for build commands to complete before proceeding.
- **DO NOT** run benchmarks for longer than 100 minutes total (leave buffer for posting).
- **DO** always report the commit SHA so results can be correlated with specific code versions.
- **DO** close older QF_S Benchmark discussions automatically (configured via `close-older-discussions: true`).
- **DO** highlight disagreements prominently — these are potential correctness bugs.

File diff suppressed because one or more lines are too long

View file

@ -7,7 +7,12 @@ on:
timeout-minutes: 30
permissions: read-all
permissions:
contents: read
issues: read
pull-requests: read
discussions: read
copilot-requests: write
network: defaults
@ -16,21 +21,23 @@ tools:
toolsets: [default]
bash: [":*"]
edit: {}
glob: {}
view: {}
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[Release Notes] "
category: "Announcements"
close-older-discussions: false
noop:
report-as-issue: false
github-token: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6.0.2
with:
fetch-depth: 0 # Fetch full history for analyzing commits
persist-credentials: false
---

View file

@ -23,7 +23,7 @@ permissions:
contents: write
env:
RELEASE_VERSION: '4.16.0'
RELEASE_VERSION: '4.17.1'
jobs:
# ============================================================================
@ -34,9 +34,11 @@ jobs:
name: "Mac Build x64"
runs-on: macos-15
timeout-minutes: 90
env:
MACOSX_DEPLOYMENT_TARGET: "13.3"
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -44,8 +46,21 @@ jobs:
python-version: '3.x'
- name: Build
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=x64
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=x64 --os=osx-13.3
- name: Validate libz3.dylib and z3 architecture (must be x86_64)
run: |
set -e
for f in build-dist/libz3.dylib build-dist/z3; do
ARCH=$(lipo -archs "$f")
echo "$f architecture: $ARCH"
if [ "$ARCH" != "x86_64" ]; then
echo "ERROR: $f has arch '$ARCH', expected 'x86_64' (see issue #9662)"
exit 1
fi
done
echo "OK: macOS x64 artifacts are x86_64"
- name: Clone z3test
run: git clone https://github.com/z3prover/z3test z3test
@ -53,7 +68,7 @@ jobs:
run: python z3test/scripts/test_benchmarks.py build-dist/z3 z3test/regressions/smt2
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: macOsBuild
path: dist/*.zip
@ -63,9 +78,11 @@ jobs:
name: "Mac ARM64 Build"
runs-on: macos-15
timeout-minutes: 90
env:
MACOSX_DEPLOYMENT_TARGET: "13.3"
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -73,13 +90,26 @@ jobs:
python-version: '3.x'
- name: Build
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=arm64
run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=arm64 --os=osx-13.3
- name: Validate libz3.dylib and z3 architecture (must be arm64)
run: |
set -e
for f in build-dist/libz3.dylib build-dist/z3; do
ARCH=$(lipo -archs "$f")
echo "$f architecture: $ARCH"
if [ "$ARCH" != "arm64" ]; then
echo "ERROR: $f has arch '$ARCH', expected 'arm64' (see issue #9662)"
exit 1
fi
done
echo "OK: macOS arm64 artifacts are arm64"
- name: Clone z3test
run: git clone https://github.com/z3prover/z3test z3test
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: MacArm64
path: dist/*.zip
@ -96,10 +126,10 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download macOS x64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: macOsBuild
path: artifacts
@ -111,6 +141,17 @@ jobs:
Z3_DIR=$(find . -maxdepth 1 -type d -name "z3-*" | head -n 1)
echo "Z3_DIR=$Z3_DIR" >> $GITHUB_ENV
- name: Validate shipped libz3.dylib architecture (must be x86_64)
run: |
set -e
DYLIB="artifacts/$Z3_DIR/bin/libz3.dylib"
ARCH=$(lipo -archs "$DYLIB")
echo "Shipped $DYLIB architecture: $ARCH"
if [ "$ARCH" != "x86_64" ]; then
echo "ERROR: x64 release zip contains '$ARCH' libz3.dylib (see issue #9662)"
exit 1
fi
- name: Test install_name_tool with headerpad
run: |
cd artifacts/$Z3_DIR/bin
@ -144,10 +185,10 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download macOS ARM64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: MacArm64
path: artifacts
@ -159,6 +200,17 @@ jobs:
Z3_DIR=$(find . -maxdepth 1 -type d -name "z3-*" | head -n 1)
echo "Z3_DIR=$Z3_DIR" >> $GITHUB_ENV
- name: Validate shipped libz3.dylib architecture (must be arm64)
run: |
set -e
DYLIB="artifacts/$Z3_DIR/bin/libz3.dylib"
ARCH=$(lipo -archs "$DYLIB")
echo "Shipped $DYLIB architecture: $ARCH"
if [ "$ARCH" != "arm64" ]; then
echo "ERROR: arm64 release zip contains '$ARCH' libz3.dylib (see issue #9662)"
exit 1
fi
- name: Test install_name_tool with headerpad
run: |
cd artifacts/$Z3_DIR/bin
@ -191,7 +243,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -208,7 +260,7 @@ jobs:
run: python z3test/scripts/test_benchmarks.py build-dist/z3 z3test/regressions/smt2
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: UbuntuBuild
path: dist/*.zip
@ -220,7 +272,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -243,7 +295,7 @@ jobs:
python scripts/mk_unix_dist.py --nodotnet --arch=arm64
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: UbuntuArm64
path: dist/*.zip
@ -255,7 +307,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -279,9 +331,9 @@ jobs:
eval $(opam config env)
python scripts/mk_make.py --ml
cd build
make -j3
make -j3 examples
make -j3 test-z3
make -j$(nproc)
make -j$(nproc) examples
make -j$(nproc) test-z3
cd ..
- name: Generate documentation
@ -298,7 +350,7 @@ jobs:
run: zip -r z3doc.zip doc/api
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: UbuntuDoc
path: z3doc.zip
@ -311,11 +363,19 @@ jobs:
container: quay.io/pypa/manylinux_2_28_x86_64:latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Select Python
run: |
# Use the first available manylinux interpreter for deterministic selection.
PYTHON=$(printf '%s\n' /opt/python/*/bin/python | sort -V | head -n1)
test -x "$PYTHON" || { echo "Error: no interpreter found under /opt/python/*/bin/python"; exit 1; }
echo "PYTHON=$PYTHON" >> "$GITHUB_ENV"
"$PYTHON" --version
- name: Setup Python environment
run: |
/opt/python/cp38-cp38/bin/python -m venv $PWD/env
"$PYTHON" -m venv $PWD/env
echo "$PWD/env/bin" >> $GITHUB_PATH
- name: Install build tools
@ -328,7 +388,7 @@ jobs:
run: pip install ./src/api/python/wheelhouse/*.whl && python - <src/api/python/z3test.py z3 && python - <src/api/python/z3test.py z3num
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ManyLinuxPythonBuildAMD64
path: src/api/python/wheelhouse/*.whl
@ -341,7 +401,7 @@ jobs:
container: quay.io/pypa/manylinux_2_28_x86_64:latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download ARM toolchain
run: curl -L -o /tmp/arm-toolchain.tar.xz 'https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz'
@ -351,9 +411,17 @@ jobs:
mkdir -p /tmp/arm-toolchain/
tar xf /tmp/arm-toolchain.tar.xz -C /tmp/arm-toolchain/ --strip-components=1
- name: Select Python
run: |
# Use the first available manylinux interpreter for deterministic selection.
PYTHON=$(printf '%s\n' /opt/python/*/bin/python | sort -V | head -n1)
test -x "$PYTHON" || { echo "Error: no interpreter found under /opt/python/*/bin/python"; exit 1; }
echo "PYTHON=$PYTHON" >> "$GITHUB_ENV"
"$PYTHON" --version
- name: Setup Python environment
run: |
/opt/python/cp38-cp38/bin/python -m venv $PWD/env
"$PYTHON" -m venv $PWD/env
echo "$PWD/env/bin" >> $GITHUB_PATH
echo "/tmp/arm-toolchain/bin" >> $GITHUB_PATH
echo "/tmp/arm-toolchain/aarch64-none-linux-gnu/libc/usr/bin" >> $GITHUB_PATH
@ -368,19 +436,74 @@ jobs:
run: cd src/api/python && CC=aarch64-none-linux-gnu-gcc CXX=aarch64-none-linux-gnu-g++ AR=aarch64-none-linux-gnu-ar LD=aarch64-none-linux-gnu-ld Z3_CROSS_COMPILING=aarch64 python -m build && AUDITWHEEL_PLAT= auditwheel repair --best-plat dist/*.whl && cd ../../..
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: ManyLinuxPythonBuildArm64
path: src/api/python/wheelhouse/*.whl
retention-days: 7
manylinux-python-riscv64:
name: "Python bindings (manylinux RISC-V 64 cross)"
runs-on: ubuntu-latest
timeout-minutes: 90
container: quay.io/pypa/manylinux_2_28_x86_64:latest
steps:
- name: Checkout code
uses: actions/checkout@v7.0.0
- name: Download RISC-V toolchain
run: curl -L -o /tmp/riscv-toolchain.tar.gz 'https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2024.09.03/riscv64-glibc-ubuntu-20.04-gcc-nightly-2024.09.03-nightly.tar.gz'
- name: Extract RISC-V toolchain
run: |
mkdir -p /tmp/riscv-toolchain/
tar xf /tmp/riscv-toolchain.tar.gz -C /tmp/riscv-toolchain/ --strip-components=1
- name: Install MPFR 4 (required by RISC-V toolchain host binaries)
run: |
dnf install -y gmp-devel
curl -L -o /tmp/mpfr.tar.xz https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.1.tar.xz
tar xf /tmp/mpfr.tar.xz -C /tmp/
cd /tmp/mpfr-4.2.1 && ./configure --prefix=/usr/local --disable-static && make -j$(nproc) && make install
ldconfig
- name: Select Python
run: |
# Use the first available manylinux interpreter for deterministic selection.
PYTHON=$(printf '%s\n' /opt/python/*/bin/python | sort -V | head -n1)
test -x "$PYTHON" || { echo "Error: no interpreter found under /opt/python/*/bin/python"; exit 1; }
echo "PYTHON=$PYTHON" >> "$GITHUB_ENV"
"$PYTHON" --version
- name: Setup Python environment
run: |
"$PYTHON" -m venv $PWD/env
echo "$PWD/env/bin" >> $GITHUB_PATH
echo "/tmp/riscv-toolchain/bin" >> $GITHUB_PATH
- name: Install build tools
run: |
echo $PATH
stat $(which riscv64-unknown-linux-gnu-gcc)
pip install build git+https://github.com/rhelmot/auditwheel
- name: Build wheels
run: cd src/api/python && CC=riscv64-unknown-linux-gnu-gcc CXX=riscv64-unknown-linux-gnu-g++ AR=riscv64-unknown-linux-gnu-ar LD=riscv64-unknown-linux-gnu-ld Z3_CROSS_COMPILING=riscv64 python -m build && AUDITWHEEL_PLAT= auditwheel repair --best-plat dist/*.whl && cd ../../..
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ManyLinuxPythonBuildRiscv64
path: src/api/python/wheelhouse/*.whl
retention-days: 7
windows-build-x64:
name: "Windows x64 build"
runs-on: windows-latest
timeout-minutes: 120
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -390,11 +513,12 @@ jobs:
- name: Build
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" x64 || exit /b 1
python scripts\mk_win_dist.py --x64-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --zip
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: WindowsBuild-x64
path: dist/*.zip
@ -406,7 +530,7 @@ jobs:
timeout-minutes: 120
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -416,11 +540,12 @@ jobs:
- name: Build
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" x86 || exit /b 1
python scripts\mk_win_dist.py --x86-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --zip
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: WindowsBuild-x86
path: dist/*.zip
@ -432,7 +557,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -442,11 +567,12 @@ jobs:
- name: Build
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64_arm64
for /f "usebackq delims=" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i"
call "%VSPATH%\VC\Auxiliary\Build\vcvarsall.bat" amd64_arm64 || exit /b 1
python scripts\mk_win_dist_cmake.py --arm64-only --dotnet-key=%GITHUB_WORKSPACE%\resources\z3.snk --assembly-version=${{ env.RELEASE_VERSION }} --zip
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: WindowsBuild-arm64
path: dist/arm64/*.zip
@ -462,7 +588,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -470,43 +596,43 @@ jobs:
python-version: '3.x'
- name: Download Win64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-x64
path: package
- name: Download Win ARM64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-arm64
path: package
- name: Download Ubuntu Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: UbuntuBuild
path: package
- name: Download Ubuntu ARM64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: UbuntuArm64
path: package
- name: Download macOS Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: macOsBuild
path: package
- name: Download macOS Arm64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: MacArm64
path: package
- name: Setup NuGet
uses: nuget/setup-nuget@v2
uses: nuget/setup-nuget@v4
with:
nuget-version: 'latest'
@ -523,7 +649,7 @@ jobs:
nuget pack out\Microsoft.Z3.sym.nuspec -OutputDirectory . -Verbosity detailed -Symbols -SymbolPackageFormat snupkg -BasePath out
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: NuGet
path: |
@ -537,7 +663,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -545,13 +671,13 @@ jobs:
python-version: '3.x'
- name: Download artifacts
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-x86
path: package
- name: Setup NuGet
uses: nuget/setup-nuget@v2
uses: nuget/setup-nuget@v4
with:
nuget-version: 'latest'
@ -568,7 +694,7 @@ jobs:
nuget pack out\Microsoft.Z3.x86.sym.nuspec -OutputDirectory . -Verbosity detailed -Symbols -SymbolPackageFormat snupkg -BasePath out
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: NuGet32
path: |
@ -578,11 +704,11 @@ jobs:
python-package:
name: "Python packaging"
needs: [mac-build-x64, mac-build-arm64, windows-build-x64, windows-build-x86, windows-build-arm64, manylinux-python-amd64, manylinux-python-arm64]
needs: [mac-build-x64, mac-build-arm64, windows-build-x64, windows-build-x86, windows-build-arm64, manylinux-python-amd64, manylinux-python-arm64, manylinux-python-riscv64]
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup Python
uses: actions/setup-python@v6
@ -590,47 +716,53 @@ jobs:
python-version: '3.x'
- name: Download macOS x64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: macOsBuild
path: artifacts
- name: Download macOS Arm64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: MacArm64
path: artifacts
- name: Download Win64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-x64
path: artifacts
- name: Download Win32 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-x86
path: artifacts
- name: Download Win ARM64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: WindowsBuild-arm64
path: artifacts
- name: Download ManyLinux AMD64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: ManyLinuxPythonBuildAMD64
path: artifacts
- name: Download ManyLinux Arm64 Build
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: ManyLinuxPythonBuildArm64
path: artifacts
- name: Download ManyLinux RISC-V 64 Build
uses: actions/download-artifact@v8.0.1
with:
name: ManyLinuxPythonBuildRiscv64
path: artifacts
- name: Extract builds
run: |
cd artifacts
@ -658,7 +790,7 @@ jobs:
cp artifacts/*.whl src/api/python/dist/.
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: PythonPackage
path: src/api/python/dist/*
@ -689,10 +821,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download all artifacts
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
path: tmp
@ -745,22 +877,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Download NuGet packages
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: NuGet
path: packages
- name: Download NuGet32 packages
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: NuGet32
path: packages
- name: Setup NuGet
uses: nuget/setup-nuget@v2
uses: nuget/setup-nuget@v4
with:
nuget-version: 'latest'
@ -781,10 +913,20 @@ jobs:
contents: read
steps:
- name: Download Python packages
uses: actions/download-artifact@v7.0.0
uses: actions/download-artifact@v8.0.1
with:
name: PythonPackage
path: dist
- name: Rewrite macOS wheel tags unsupported by test.PyPI
run: |
# TestPyPI rejects current macOS wheel tags such as macosx_13_3_*.
# Rewrite only the unsupported 13_3 tag to 13 for upload validation.
for whl in dist/*-macosx_13_3_*.whl; do
[ -e "$whl" ] || continue
mv "$whl" "${whl/macosx_13_3_/macosx_13_0_}"
done
ls -l dist
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

1668
.github/workflows/smtlib-benchmark-finder.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,343 @@
---
description: >
Monthly SMTLIB Benchmark Finder.
Searches GitHub for repositories containing SMT-LIB benchmarks (.smt2 files),
excludes repositories that belong to the official SMT-LIB benchmark sets
(linked from smtlib.org and hosted on Zenodo), and posts a curated summary
of community-contributed benchmark links as a GitHub Discussion.
on:
schedule:
- cron: "0 8 1 * *"
workflow_dispatch:
timeout-minutes: 60
permissions: read-all
network:
allowed:
- defaults
- github
- smtlib.cs.uiowa.edu
- zenodo.org
tools:
cache-memory: true
web-fetch: {}
github:
toolsets: [default, repos]
bash: [":*"]
safe-outputs:
report-failure-as-issue: false
mentions: false
allowed-github-references: []
max-bot-mentions: 1
create-discussion:
title-prefix: "[SMT-LIB Benchmarks] "
category: "Agentic Workflows"
close-older-discussions: true
expires: 90d
missing-tool:
create-issue: true
noop:
report-as-issue: false
---
# SMTLIB Benchmark Finder
## Job Description
Your name is ${{ github.workflow }}. You are a research analyst for the Z3 theorem
prover repository `${{ github.repository }}`. Your mission is to discover GitHub
repositories that host SMT-LIB benchmarks, exclude the ones that are already part
of the official SMT-LIB benchmark distribution (linked from smtlib.org and published
on Zenodo), and post a curated summary of community-contributed benchmark links as a
GitHub Discussion.
## Step 1: Load Cache and Determine Run Mode
Check cache memory for:
- `official_repos`: set of GitHub repository full names (`owner/repo`) and Zenodo
record IDs already identified as official SMT-LIB benchmark sets
- `known_community_repos`: set of repo full names already listed in a previous report
- `last_run_date`: ISO-8601 date string of the previous run
Use the cache to skip repos already classified. On the very first run (no cache),
perform a full discovery pass. On subsequent runs focus on repos pushed or created
since `last_run_date`.
## Step 2: Collect Official SMT-LIB Benchmark Sets to Exclude
### 2.1 Scrape smtlib.org
Fetch the SMT-LIB benchmarks page and extract all linked Zenodo DOIs and GitHub URLs:
```bash
curl -s "https://smtlib.cs.uiowa.edu/benchmarks.shtml" -o /tmp/smtlib-benchmarks.html
# Also try the main page and any mirror
curl -s "https://smtlib.cs.uiowa.edu/" -o /tmp/smtlib-home.html
```
Parse both files for:
- Zenodo DOI links (`doi.org/10.5281/zenodo.*` or `zenodo.org/record/*`)
- GitHub repository URLs (`github.com/...`)
- Any other hosted benchmark archive links
### 2.2 Enumerate Zenodo SMT-LIB Community Records
Query the Zenodo API for all records in the SMT-LIB community:
```bash
curl -s "https://zenodo.org/api/records?communities=smt-lib&size=100&page=1" \
-o /tmp/zenodo-smtlib-page1.json
# Check if there are more pages (paginate until empty)
curl -s "https://zenodo.org/api/records?communities=smt-lib&size=100&page=2" \
-o /tmp/zenodo-smtlib-page2.json 2>/dev/null || true
curl -s "https://zenodo.org/api/records?communities=smt-lib&size=100&page=3" \
-o /tmp/zenodo-smtlib-page3.json 2>/dev/null || true
```
For each Zenodo record extract:
- Record ID (e.g. `5827900`)
- Title
- Any GitHub repository URLs listed in the description or related identifiers
```bash
python3 - <<'PYEOF'
import json, re
def extract_github_repos(text):
pattern = r'github\.com/([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)'
return set(re.findall(pattern, text or ''))
official_repos = set()
official_zenodo_ids = set()
for fname in ['/tmp/zenodo-smtlib-page1.json', '/tmp/zenodo-smtlib-page2.json',
'/tmp/zenodo-smtlib-page3.json']:
try:
data = json.load(open(fname))
except Exception:
continue
for hit in data.get('hits', {}).get('hits', []):
rid = str(hit.get('id', ''))
official_zenodo_ids.add(rid)
metadata = hit.get('metadata', {})
description = metadata.get('description', '')
related = ' '.join(
r.get('identifier', '')
for r in metadata.get('related_identifiers', [])
)
title = metadata.get('title', '')
for repo in extract_github_repos(description + ' ' + related + ' ' + title):
official_repos.add(repo.lower().rstrip('.'))
with open('/tmp/smtlib-benchmarks.html') as f:
html = f.read()
for repo in extract_github_repos(html):
official_repos.add(repo.lower().rstrip('.'))
print("OFFICIAL_ZENODO_IDS:", ','.join(sorted(official_zenodo_ids)) or '(none)')
print("OFFICIAL_GITHUB_REPOS:", ','.join(sorted(official_repos)) or '(none)')
PYEOF
```
### 2.3 Well-Known Official Repository Patterns
Regardless of the above scrape, always exclude:
- Any repo under the `SMT-LIB` GitHub organization (`SMT-LIB/*`)
- Any repo whose name matches `smt-comp-*` that is under `SMT-Competition` org
- The Z3 repo itself (`Z3Prover/z3`) and its forks
Combine all official sources into a single exclusion set stored at
`/tmp/official_exclusions.txt` (one `owner/repo` pattern per line, lowercase).
## Step 3: Search GitHub for Community SMT-LIB Benchmark Repositories
Use multiple GitHub search strategies to find repos containing `.smt2` benchmark
files that are NOT part of the official distribution. Search for repos updated
since the last run date (or the last 90 days for the initial run).
Compute the cutoff date first:
```bash
CUTOFF=$(date -d "90 days ago" +%Y-%m-%d 2>/dev/null || date -v-90d +%Y-%m-%d)
echo "Using cutoff date: $CUTOFF"
```
### Search Strategies
Use the GitHub MCP server tools to run these searches:
1. **Topic search**: `topic:smtlib pushed:>$CUTOFF`
2. **Topic search variant**: `topic:smt-lib pushed:>$CUTOFF`
3. **Topic search variant**: `topic:smt2 pushed:>$CUTOFF`
4. **Benchmark filename pattern**: `filename:*.smt2 pushed:>$CUTOFF` (limit to top results)
5. **Benchmarks directory pattern**: `path:benchmarks *.smt2 in:path pushed:>$CUTOFF`
6. **README mention**: `SMT-LIB benchmarks in:readme stars:>2 pushed:>$CUTOFF`
7. **Organization-level search**: repos under `SMT-Competition` org, if any are not already excluded
For each search, collect: `full_name`, `html_url`, `description`, `stargazers_count`,
`updated_at`, `pushed_at`, `default_branch`, `topics`.
Deduplicate by `full_name`. Limit to 200 total candidates before filtering.
## Step 4: Filter Out Official Benchmark Sets
For each candidate repo:
1. Check if `full_name.lower()` is in the exclusion set from Step 2.
2. Check if the repo is owned by `SMT-LIB` or `SMT-Competition` org (case-insensitive).
3. Check if the repo is a fork of a known official repo (if `fork: true` and parent is
in the exclusion set).
Discard repos that match any of the above. Keep the rest as community benchmarks.
Also apply quality filters to reduce noise — skip repos that:
- Have 0 stars and fewer than 3 `.smt2` files (likely a student homework or test repo
with minimal public value); use your judgement — if the repo description clearly
describes a research benchmark, keep it regardless of star count.
- Were created but never pushed after creation (empty repos).
- Have names that are clearly course assignment repositories
(e.g. contain `homework`, `assignment`, `hw[0-9]`, `cs[0-9]{3}`).
## Step 5: Classify Remaining Repos
For each repo that survives filtering, classify it into one of these categories:
| Category | Description |
|----------|-------------|
| **Solver evaluation** | Benchmarks used to evaluate or compare SMT solvers |
| **Verification** | Benchmarks from program verification or model checking |
| **Security / CTF** | Benchmarks from security research or CTF challenges |
| **Theory / logic** | Benchmarks exploring specific SMT theories or logics |
| **Tool output** | Benchmarks generated by another tool (e.g. a compiler, fuzzer) |
| **Education** | Course materials or tutorials with benchmark examples |
| **Other / unknown** | Does not fit another category |
Base the classification on: repo description, README (fetch if web-fetch is available),
topics, and directory structure. A single brief `web-fetch` of the repo's README is
sufficient; do not fetch individual `.smt2` files.
Note the dominant SMT logic(s) present, if discernible from the description or topics
(e.g. QF_BV, QF_LIA, QF_S, NIA, …).
## Step 6: Generate the Discussion Report
Create a GitHub Discussion. Use heading level 3 or deeper (`###`, `####`, …) for all
section headers; never use `##` or `#` in the body.
Wrap long tables in `<details>` tags to keep the report scannable.
Title: `[SMT-LIB Benchmarks] Community Benchmark Repository Survey — [Month YYYY]`
Structure the report as follows:
```markdown
**Period covered**: [cutoff date] [today's date]
**Repositories found**: N community repos (after excluding M official sets)
**New this run**: N (not listed in previous report)
### Overview
12 sentences summarising the breadth of community SMT-LIB benchmarks found.
### Community Benchmark Repositories
Use `###` for category headers. Within each category, list repos as a markdown table.
For each repo include:
- Repo link (`[owner/repo](html_url)`)
- Stars
- Last pushed date
- Dominant logic(s) (if known)
- Brief description (from repo description or README, max 120 chars)
#### Solver Evaluation
| Repository | ⭐ | Last pushed | Logic(s) | Description |
|------------|-----|------------|---------|-------------|
| [owner/repo](url) | N | YYYY-MM-DD | QF_BV, QF_LIA | … |
#### Verification
[same table structure]
#### Security / CTF
[same table structure]
#### Theory / Logic
[same table structure]
#### Tool Output
[same table structure]
#### Education
[same table structure]
#### Other / Unknown
[same table structure]
---
### Exclusions Applied
<details>
<summary>Official SMT-LIB sets excluded from this report</summary>
List Zenodo record IDs and GitHub repos identified as official distributions.
| Source | Identifier | Notes |
|--------|-----------|-------|
| Zenodo | [10.5281/zenodo.XXXXXXX](https://zenodo.org/record/XXXXXXX) | Official QF_BV benchmark set |
| GitHub | [SMT-LIB/benchmarks-non-incremental](https://github.com/SMT-LIB/benchmarks-non-incremental) | |
</details>
---
### Methodology
Brief note on search queries used, cutoff date, and any quality filters applied.
```
## Step 7: Update Cache Memory
After posting the discussion, update cache memory with:
- `official_repos`: updated exclusion set (union of previous + newly found)
- `known_community_repos`: union of previous + repos listed in this report
- `last_run_date`: today's ISO-8601 date
- `report_url`: URL of the GitHub Discussion created
## Guidelines
- **Be conservative with exclusions**: when in doubt whether a repo is "official",
keep it in the community list rather than silently dropping it.
- **Be accurate**: only include repos that genuinely contain SMT-LIB `.smt2` files
or clearly describe themselves as SMT-LIB benchmark collections.
- **Avoid noise**: student homework repos and trivially small repos add clutter;
apply the quality filters from Step 4 judiciously.
- **No source code changes**: DO NOT create pull requests or modify any source files.
- **No copyrighted content**: DO NOT reproduce benchmark file contents; only post
links and metadata.
- **Always cite sources**: include the full GitHub URL for every listed repository.
- **Use cache**: skip repos already classified in a previous run to keep runtime short.
- **Fail gracefully**: if GitHub search rate-limits the workflow, post whatever was
collected so far with a note that the search was incomplete.
## Important Notes
- DO NOT create pull requests or modify source files.
- DO close older SMT-LIB Benchmarks discussions automatically (configured).
- DO always call `create_discussion` or `noop` before the workflow ends.
Failing to produce any safe output triggers an automatic failure issue.
- DO use cache memory to avoid re-processing repos already surveyed.
- DO limit individual `web-fetch` calls (README fetches) to repos where the
description alone is insufficient for classification.

File diff suppressed because it is too large Load diff

View file

@ -1,41 +0,0 @@
---
description: Automatically validate and reproduce reported soundness bugs
on:
issues:
types: [opened, labeled]
schedule: daily
roles: all
permissions: read-all
network: defaults
tools:
cache-memory: true
github:
toolsets: [default]
bash: [":*"]
web-fetch: {}
safe-outputs:
add-comment:
max: 2
create-discussion:
title-prefix: "[Soundness] "
category: "Agentic Workflows"
close-older-discussions: true
missing-tool:
create-issue: true
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v5
---
<!-- Edit the file linked below to modify the agent without recompilation. Feel free to move the entire markdown body to that file. -->
@./agentics/soundness-bug-detector.md

1704
.github/workflows/specbot-crash-analyzer.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,248 @@
---
description: >
Build Z3 in debug mode from the c3 branch, compile and run the specbot tests,
identify root causes for any crashes, and post findings as a GitHub Discussion.
on:
workflow_dispatch:
timeout-minutes: 120
permissions: read-all
network: defaults
tools:
cache-memory: true
github:
toolsets: [default, discussions]
bash: [":*"]
edit: {}
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[Specbot] "
category: "Agentic Workflows"
close-older-discussions: true
missing-tool:
create-issue: true
noop:
report-as-issue: false
steps:
- name: Checkout c3 branch
uses: actions/checkout@v6.0.2
with:
ref: c3
persist-credentials: false
- name: Install build dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y cmake ninja-build python3 gcc g++ 2>&1 | tail -5
- name: Build Z3 in debug mode
id: build-z3
continue-on-error: true
run: |
mkdir -p build/debug specbot-results
cd build/debug
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ../.. 2>&1 | tee ../../specbot-results/cmake.log
ninja 2>&1 | tee ../../specbot-results/build.log
BUILD_EXIT=$?
cd ../..
echo "build_exit=${BUILD_EXIT}" >> specbot-results/build-status.txt
ls -la build/debug/libz3* build/debug/*.so* 2>/dev/null >> specbot-results/build-status.txt || echo "Library not found" >> specbot-results/build-status.txt
exit $BUILD_EXIT
- name: Compile specbot tests
continue-on-error: true
run: |
mkdir -p specbot-results
gcc -g -O0 \
-I src/api \
specbot/test_specbot_seq.c \
-L build/debug \
-lz3 \
-Wl,-rpath,"${GITHUB_WORKSPACE}/build/debug" \
-o specbot-results/test_specbot_seq \
2>&1 | tee specbot-results/compile_specbot_seq.log
echo "compile_specbot_seq_exit=$?" >> specbot-results/compile-status.txt
gcc -g -O0 \
-I src/api \
specbot/test_deeptest_seq.c \
-L build/debug \
-lz3 \
-Wl,-rpath,"${GITHUB_WORKSPACE}/build/debug" \
-o specbot-results/test_deeptest_seq \
2>&1 | tee specbot-results/compile_deeptest_seq.log
echo "compile_deeptest_seq_exit=$?" >> specbot-results/compile-status.txt
- name: Run specbot tests
continue-on-error: true
run: |
mkdir -p specbot-results
if [ -f specbot-results/test_specbot_seq ]; then
LD_LIBRARY_PATH="${GITHUB_WORKSPACE}/build/debug" timeout 120 specbot-results/test_specbot_seq > specbot-results/test_specbot_seq.log 2>&1
SPECBOT_EXIT=$?
echo "specbot_seq_exit=${SPECBOT_EXIT}" >> specbot-results/test-status.txt
else
echo "Binary not compiled" > specbot-results/test_specbot_seq.log
echo "specbot_seq_exit=127" >> specbot-results/test-status.txt
fi
if [ -f specbot-results/test_deeptest_seq ]; then
LD_LIBRARY_PATH="${GITHUB_WORKSPACE}/build/debug" timeout 120 specbot-results/test_deeptest_seq > specbot-results/test_deeptest_seq.log 2>&1
DEEPTEST_EXIT=$?
echo "deeptest_seq_exit=${DEEPTEST_EXIT}" >> specbot-results/test-status.txt
else
echo "Binary not compiled" > specbot-results/test_deeptest_seq.log
echo "deeptest_seq_exit=127" >> specbot-results/test-status.txt
fi
---
# Specbot Crash Analyzer
## Job Description
Your name is ${{ github.workflow }}. You are an expert C/C++ and SMT solver analyst for the Z3 theorem prover
repository `${{ github.repository }}`. The pre-steps above have already built Z3 in debug mode from the `c3`
branch, compiled and run the specbot test suite, and saved all output to the `specbot-results/` directory in
the workspace (`${{ github.workspace }}/specbot-results/`). Your task is to analyze those results, diagnose
any crash root causes by reading the relevant source files, and publish a structured findings report as a
GitHub Discussion.
**Do not try to build Z3 or run tests yourself.** All build and test output is already in `specbot-results/`.
## Your Task
### 1. Read the Pre-Generated Results
All build and test outputs are in `specbot-results/` (relative to the workspace root). Read each file:
```bash
# Build status
cat specbot-results/build-status.txt 2>/dev/null || echo "No build status"
# Compile status
cat specbot-results/compile-status.txt 2>/dev/null || echo "No compile status"
# Test status
cat specbot-results/test-status.txt 2>/dev/null || echo "No test status"
# Test output from test_specbot_seq
cat specbot-results/test_specbot_seq.log 2>/dev/null || echo "No test_specbot_seq output"
# Test output from test_deeptest_seq
cat specbot-results/test_deeptest_seq.log 2>/dev/null || echo "No test_deeptest_seq output"
# Last 30 lines of the build log
tail -30 specbot-results/build.log 2>/dev/null || echo "No build log"
```
If `specbot-results/build-status.txt` shows `build_exit=0`, the build succeeded.
If it shows a non-zero exit, include the last 50 lines of `specbot-results/build.log` in the report
under a "Build Failure" section.
If `specbot-results/compile-status.txt` shows a non-zero exit for a test, include the compile error
from `specbot-results/compile_specbot_seq.log` or `specbot-results/compile_deeptest_seq.log`.
Collect every line containing `CRASH` or `ABORT` from the test log files — these are the crashes to analyze.
### 2. Diagnose Each Crash
For each crashed test function, perform the following analysis:
1. **Identify the test body**: read `specbot/test_specbot_seq.c` or `specbot/test_deeptest_seq.c`
to understand what Z3 API calls the test makes and what invariants it exercises.
2. **Find the likely crash site**: the test exercises the Z3 Nielsen/nseq string solver. Relevant source files are:
- `src/smt/seq_solver.h` and `src/smt/seq_solver.cpp` (or nearby files)
- `src/smt/seq_axioms.cpp`, `src/smt/seq_eq_solver.cpp`, `src/smt/seq_regex.cpp`
- `src/math/lp/` for length-arithmetic paths
- `src/api/z3_api.h` for the public API entry points
Use `grep` and `view` to locate assertion macros, `UNREACHABLE()`, `SASSERT`, or `throw` statements
in the code paths exercised by the failing test. Example:
```bash
grep -rn "SASSERT\|UNREACHABLE\|Z3_CATCH" src/smt/seq_solver.cpp 2>/dev/null | head -30
```
3. **Hypothesize root cause**: based on the Z3 API calls in the test and the assertion/throw sites in
the solver source, state the most likely root cause. Common categories include:
- Violated invariant (SASSERT/UNREACHABLE hit due to unexpected solver state)
- Use-after-free or dangling reference during push/pop
- Unhandled edge case in Nielsen graph construction
- Missing theory-combination lemma between string length and integer arithmetic
4. **Suggest a fix**: propose a minimal, concrete fix — e.g., a guard condition, an additional lemma,
a missing reference-count increment, or a missing case in a switch/match.
### 3. Generate the Report
After analyzing all crashes, produce a structured GitHub Discussion in the "Agentic Workflows" category
using `create-discussion`.
The discussion body must follow this structure (use `###` and lower for headers):
```
### Summary
- Build: Debug (CMake + Ninja, c3 branch)
- Tests compiled: N
- Tests run: N
- Tests passed: N
- Tests crashed: N
- Tests timed out: N
### Crash Findings
For each crash, one subsection:
#### <test function name>
**Test file**: `specbot/test_specbot_seq.c` or `specbot/test_deeptest_seq.c`
**Observed failure**: ABORT/CRASH — one-line description of what was caught
**Root cause hypothesis**: explanation of which assertion or code path was hit and why
**Suggested fix**: concrete proposed change (file, function, what to add/change)
---
### Tests Passed
List of test names that passed.
<details>
<summary><b>Full Test Output</b></summary>
Raw stdout/stderr from both test binaries.
</details>
<details>
<summary><b>Build Log</b></summary>
Last 30 lines of the ninja build output.
</details>
```
If there are no crashes at all, write a "No Crashes Found" summary celebrating that all tests passed,
and include the full test output in a collapsible section.
Use `mentions: false` behavior — do not mention any GitHub usernames in the report.
Format workflow run references as: `[§${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`.
## Usage
Trigger via **Actions → Specbot Crash Analyzer → Run workflow** on any branch. The pre-steps
always check out the `c3` branch where `specbot/test_specbot_seq.c` and
`specbot/test_deeptest_seq.c` live, build Z3, run the tests, and save results to `specbot-results/`.
The agent then analyzes the results and posts a discussion to the "Agentic Workflows" category.

1020
.github/workflows/specbot.lock.yml generated vendored

File diff suppressed because it is too large Load diff

View file

@ -1,58 +0,0 @@
---
description: Automatically annotate code with assertions capturing class invariants, pre-conditions, and post-conditions using LLM-based specification mining
on:
schedule: weekly
workflow_dispatch:
inputs:
target_path:
description: 'Target directory or file to analyze (e.g., src/ast/, src/smt/smt_context.cpp)'
required: false
default: ''
target_class:
description: 'Specific class name to analyze (optional)'
required: false
default: ''
roles: [write, maintain, admin]
env:
GH_TOKEN: ${{ secrets.BOT_PAT }}
permissions:
contents: read
issues: read
pull-requests: read
tools:
github:
toolsets: [default]
view: {}
glob: {}
edit: {}
bash:
- ":*"
mcp-servers:
serena:
container: "ghcr.io/githubnext/serena-mcp-server"
version: "latest"
safe-outputs:
create-discussion:
title-prefix: "[SpecBot] "
category: "Agentic Workflows"
close-older-discussions: true
missing-tool:
create-issue: true
timeout-minutes: 45
steps:
- name: Checkout repository
uses: actions/checkout@v5
---
<!-- Edit the file linked below to modify the agent without recompilation. Feel free to move the entire markdown body to that file. -->
@./agentics/specbot.md

1672
.github/workflows/tactic-to-simplifier.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,279 @@
---
description: Compares exposed tactics and simplifiers in Z3, and creates issues for tactics that can be converted to simplifiers
on:
schedule: weekly
workflow_dispatch:
timeout-minutes: 30
permissions:
contents: read
issues: read
pull-requests: read
network: defaults
tools:
cache-memory: true
github:
toolsets: [default]
bash: [":*"]
safe-outputs:
report-failure-as-issue: false
create-issue:
labels:
- enhancement
- refactoring
- tactic-to-simplifier
title-prefix: "[tactic-to-simplifier] "
max: 3
noop:
report-as-issue: false
github-token: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2
with:
persist-credentials: false
---
# Tactic-to-Simplifier Comparison Agent
You are an expert Z3 theorem prover developer. Your task is to compare the tactics and simplifiers exposed in the Z3 codebase, identify tactics that could be converted into simplifiers, and create GitHub issues with the proposed code changes.
## Background
Z3 has two related but distinct abstraction layers:
- **Tactics** (`tactic` base class in `src/tactic/tactic.h`): Operate on *goals* (sets of formulas). Registered with `ADD_TACTIC` macros.
- **Simplifiers** (`dependent_expr_simplifier` base class in `src/ast/simplifiers/dependent_expr_state.h`): Operate on individual `dependent_expr` objects in a `dependent_expr_state`. Registered with `ADD_SIMPLIFIER` macros.
The preferred modern pattern wraps a simplifier as a tactic using `dependent_expr_state_tactic` (see `src/tactic/dependent_expr_state_tactic.h`). Example from `src/tactic/core/propagate_values2_tactic.h`:
```cpp
inline tactic * mk_propagate_values2_tactic(ast_manager & m, params_ref const & p = params_ref()) {
return alloc(dependent_expr_state_tactic, m, p,
[](auto& m, auto& p, auto &s) -> dependent_expr_simplifier* { return alloc(propagate_values, m, p, s); });
}
/*
ADD_TACTIC("propagate-values2", "propagate constants.", "mk_propagate_values2_tactic(m, p)")
ADD_SIMPLIFIER("propagate-values", "propagate constants.", "alloc(propagate_values, m, p, s)")
*/
```
## Your Task
### Step 1: Collect All Tactics
Scan all header files in `src/` to extract every `ADD_TACTIC` registration:
```bash
grep -rn "ADD_TACTIC(" src/ --include="*.h" | grep -v "^Binary"
```
Parse each line to extract:
- Tactic name (first quoted string)
- Description (second quoted string)
- Factory expression (third quoted string)
- File path
### Step 2: Collect All Simplifiers
Scan all header files to extract every `ADD_SIMPLIFIER` registration:
```bash
grep -rn "ADD_SIMPLIFIER(" src/ --include="*.h" | grep -v "^Binary"
```
Parse each line to extract:
- Simplifier name
- Description
- Factory expression
- File path
### Step 3: Compare and Find Gaps
Build a comparison table. For each tactic, check if there is a corresponding simplifier with the same or a closely related name.
Key rules for matching:
- Exact name match: tactic `simplify` ↔ simplifier `simplify`
- Version suffix: tactic `propagate-values2` corresponds to simplifier `propagate-values` (the "2" suffix indicates the tactic wraps the simplifier)
- Suffix `2`: tactics with `2` suffix (e.g., `elim-uncnstr2`, `propagate-bv-bounds2`) typically already have a simplifier counterpart
Identify tactics that have **no corresponding simplifier**.
### Step 4: Evaluate Convertibility
For each tactic without a corresponding simplifier, assess whether it is a good candidate for conversion by examining its implementation:
```bash
# Read the tactic's header file to understand its implementation
grep -rn "mk_<tactic_name>_tactic\|class <tactic_name>" src/ --include="*.h" --include="*.cpp"
```
A tactic is a **good conversion candidate** if:
1. It transforms formulas in a formula-by-formula way (no goal splitting/branching)
2. It does not produce multiple goals from one
3. It is a pure simplification (rewrites terms without adding new conjuncts that split the goal)
4. It doesn't require global goal analysis beyond what `dependent_expr_state` provides
A tactic is **not suitable** for conversion if:
- It splits goals into multiple subgoals
- It requires tight coupling to the goal infrastructure
- It depends on features unavailable in `dependent_expr_simplifier` (e.g., `goal_num_occurs`)
- It only makes sense in a tactic pipeline (e.g., `fail`, `skip`, `sat`, `smt`, solver tactics)
- It is a portfolio tactic (combines multiple tactics)
### Step 5: Check for Existing Issues
Before creating any issue, search for existing open issues to avoid duplicates:
Use GitHub tools to search: `repo:${{ github.repository }} is:issue is:open label:tactic-to-simplifier "<tactic-name>"`
Also check cache memory for previously created issues.
### Step 6: Create Issues for Convertible Tactics
For each convertible tactic that does not already have an open issue:
1. Read the tactic's existing implementation carefully (header + cpp files)
2. Design the corresponding `dependent_expr_simplifier` subclass
3. Draft the code for the new simplifier
Use `create-issue` to create a GitHub issue with:
**Title**: `Convert tactic '<tactic-name>' to a simplifier`
**Body**:
```markdown
## Summary
The `<tactic-name>` tactic (described as: "<description>") currently only exists as a `tactic`
and has no corresponding `dependent_expr_simplifier`. This issue proposes converting it to
expose it as both a tactic (via the `dependent_expr_state_tactic` wrapper) and a simplifier.
## Background
Z3 provides two abstraction layers for formula transformation:
- **Tactics** (`tactic` base class): Operate on goals
- **Simplifiers** (`dependent_expr_simplifier` base class): Operate on individual formulas in a `dependent_expr_state`
The modern pattern wraps a simplifier inside a tactic using `dependent_expr_state_tactic`.
## Current Implementation
File: `<path/to/tactic/header.h>`
```cpp
// Existing tactic registration
ADD_TACTIC("<tactic-name>", "<description>", "mk_<tactic_name>_tactic(m, p)")
```
## Proposed Change
### 1. Create a new simplifier class in `src/ast/simplifiers/<name>_simplifier.h`:
```cpp
#pragma once
#include "ast/simplifiers/dependent_expr_state.h"
// ... other includes
class <name>_simplifier : public dependent_expr_simplifier {
// ... internal state
public:
<name>_simplifier(ast_manager& m, params_ref const& p, dependent_expr_state& s)
: dependent_expr_simplifier(m, s) { }
char const* name() const override { return "<simplifier-name>"; }
void reduce() override {
for (unsigned idx : indices()) {
auto& d = m_fmls[idx];
// ... transform d.fml() ...
expr_ref new_fml(m);
// apply simplification
m_fmls.update(idx, dependent_expr(m, new_fml, nullptr, d.dep()));
}
}
};
```
### 2. Update `<path/to/existing/tactic_header.h>` to add the simplifier registration and new tactic factory:
```cpp
#include "tactic/dependent_expr_state_tactic.h"
#include "ast/simplifiers/<name>_simplifier.h"
inline tactic* mk_<name>2_tactic(ast_manager& m, params_ref const& p = params_ref()) {
return alloc(dependent_expr_state_tactic, m, p,
[](auto& m, auto& p, auto& s) -> dependent_expr_simplifier* {
return alloc(<name>_simplifier, m, p, s);
});
}
/*
ADD_TACTIC("<tactic-name>2", "<description>", "mk_<name>2_tactic(m, p)")
ADD_SIMPLIFIER("<tactic-name>", "<description>", "alloc(<name>_simplifier, m, p, s)")
*/
```
## Benefits
- Enables use of `<tactic-name>` in Z3's simplifier pipeline (used by the new solver engine)
- Follows the established modern pattern for formula simplification in Z3
- No behavioral change for existing tactic users
## Notes
- The original `mk_<tactic_name>_tactic` should remain for backward compatibility
- The simplifier should implement `supports_proofs()` if proof generation is relevant
```
**Important instructions for issue body**:
- Replace all placeholders (`<tactic-name>`, `<name>`, `<description>`, `<path>`, etc.) with **real, specific values** from the actual source code
- Provide **actual code** based on reading the tactic's implementation, not generic templates
- Include the real factory expression, include paths, and class names from the existing implementation
- If the tactic has parameters, include them in the simplifier
- If the tactic wraps another component (rewriter, solver, etc.), include that in the simplifier too
### Step 7: Update Cache Memory
Store in cache memory:
- The list of all tactics analyzed in this run
- The list of issues created (tactic name → issue number)
- Tactics determined to be non-convertible and why
- Tactics with existing issues (to skip in future runs)
## Conversion Criteria Reference
### Likely Convertible (if no simplifier exists)
- Pure term rewriting tactics (apply rewriting rules)
- Bound propagation tactics
- Variable elimination tactics that work formula-by-formula
- Normalization tactics (NNF, SNF) that apply local transformations
- Tactics that simplify based on syntactic structure
### Not Convertible (skip these)
- Solver-based tactics (`ctx-solver-simplify`, `sat`, `smt`, etc.) — require a solver
- Portfolio/combinator tactics (`then`, `or-else`, `repeat`, etc.)
- Decision procedure tactics (`qfbv`, `qflra`, etc.)
- Tactics that split goals (`split-clause`, `tseitin-cnf`, `occf`)
- Tactics that only make sense in goal context (`fail`, `skip`)
- Tactics using `goal_num_occurs` for occurrence counting (the simplifier doesn't have this)
- Tactics that produce multiple result goals
## Guidelines
- **Be specific**: Provide actual file paths, class names, and factory expressions — no generic placeholders
- **Be careful**: Only create issues for tactics that are genuinely good candidates
- **Avoid duplicates**: Always check existing issues before creating new ones
- **One issue per tactic**: Create separate issues for each convertible tactic
- **Read the code**: Examine the actual tactic implementation before proposing code for the simplifier
- **Be incremental**: If there are many candidates, focus on the most impactful ones first
- **Limit per run**: Create at most 3 new issues per run to avoid flooding the issue tracker

1577
.github/workflows/tptp-benchmark.lock.yml generated vendored Normal file

File diff suppressed because one or more lines are too long

547
.github/workflows/tptp-benchmark.md vendored Normal file
View file

@ -0,0 +1,547 @@
---
description: >
Weekly benchmark of Z3's TPTP front-end against 500 random TPTP problems.
Downloads TPTP benchmarks from tptp.org, resolves axiom dependencies,
skips large problems, runs each with a 5-second timeout, and posts a
discrepancy/crash report as a GitHub discussion.
on:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
permissions: read-all
network:
allowed:
- defaults
- tptp.org
tools:
bash: true
github:
toolsets: [default]
safe-outputs:
report-failure-as-issue: false
create-discussion:
title-prefix: "[TPTP Benchmark] "
category: "Agentic Workflows"
close-older-discussions: true
expires: 14d
missing-tool:
create-issue: true
noop:
report-as-issue: false
timeout-minutes: 300
steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2
with:
persist-credentials: false
- name: Install build dependencies
run: |
sudo apt-get update -y -q
sudo apt-get install -y cmake ninja-build python3 wget curl bc
- name: Build Z3
run: |
mkdir -p /tmp/z3-build
cd /tmp/z3-build
cmake "$GITHUB_WORKSPACE" \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DZ3_BUILD_TEST_EXECUTABLES=OFF
ninja -j$(nproc) z3
./z3 --version
---
# TPTP Front-End Benchmark
## Job Description
Your name is ${{ github.workflow }}. You are an expert testing engineer for the Z3 theorem prover. Your task is to:
1. Verify the Z3 binary built by the pre-flight step is available
2. Download the TPTP benchmark library from tptp.org
3. Select 500 random small-to-medium problems (with their axiom dependencies)
4. Run each problem through Z3's TPTP front-end with a 5-second timeout
5. Compare Z3's output against the expected SZS status declared in each problem file
6. Post a detailed report as a GitHub Discussion summarising discrepancies and crashes
**Repository**: ${{ github.repository }}
**Workspace**: ${{ github.workspace }}
## Phase 1: Verify Z3 Binary
Z3 was built by the workflow pre-flight step and is available at `/tmp/z3-build/z3`.
Confirm the binary is present and functional:
```bash
/tmp/z3-build/z3 --version
```
If the binary is missing or returns an error, call the `noop` safe-output with a message describing the problem and stop.
Once confirmed, call `noop` with `"Z3 binary verified. Downloading TPTP benchmark library — this may take a few minutes."` to keep the safe-output session alive.
## Phase 2: Download the TPTP Problem Library
Find the latest TPTP release and download the full archive.
```bash
# Find the latest TPTP distribution version by fetching the directory listing
TPTP_DIST_URL="https://tptp.org/TPTP/Distribution/"
LATEST_TGZ=$(curl -sL "$TPTP_DIST_URL" \
| grep -oP 'TPTP-v[0-9]+\.[0-9]+\.[0-9]+\.tgz' \
| sort -V | tail -1)
if [ -z "$LATEST_TGZ" ]; then
echo "ERROR: Could not determine latest TPTP version from $TPTP_DIST_URL"
# Fall back to a known stable version
LATEST_TGZ="TPTP-v9.0.0.tgz"
fi
echo "Downloading $LATEST_TGZ ..."
mkdir -p /tmp/tptp_download
wget -q --show-progress \
"${TPTP_DIST_URL}${LATEST_TGZ}" \
-O /tmp/tptp_download/tptp.tgz
echo "Extracting TPTP library..."
mkdir -p /tmp/tptp
tar -xzf /tmp/tptp_download/tptp.tgz -C /tmp/tptp --strip-components=1 2>&1 | tail -5
# Verify extraction
if [ ! -d /tmp/tptp/Problems ] || [ ! -d /tmp/tptp/Axioms ]; then
echo "ERROR: TPTP extraction failed — Problems/ or Axioms/ directory not found"
ls /tmp/tptp/
exit 1
fi
TPTP_ROOT=/tmp/tptp
echo "TPTP library extracted to $TPTP_ROOT"
echo "Problem domains available:"
ls "$TPTP_ROOT/Problems/" | wc -l
echo "Axiom files available:"
ls "$TPTP_ROOT/Axioms/" | wc -l
```
If the download or extraction fails, call `noop` with the error details and stop.
Call `noop` with `"TPTP library downloaded and extracted. Selecting 500 benchmark problems — filtering by size."` to keep the session alive.
## Phase 3: Select 500 Benchmark Problems
Filter out large problems and problems that depend on large axiom files, then take a random sample of 500.
Save this script to `/tmp/select_benchmarks.py` and run it:
```python
#!/usr/bin/env python3
"""
Select 500 random TPTP problems that:
- Have a known, conclusive expected status (Theorem, Unsatisfiable,
CounterSatisfiable, Satisfiable) OR Unknown/Open status.
- Are not "large" (problem file <= 50 KB).
- Do not include any axiom file larger than 100 KB.
"""
import os
import re
import random
import sys
TPTP_ROOT = "/tmp/tptp"
PROBLEMS_DIR = os.path.join(TPTP_ROOT, "Problems")
AXIOMS_DIR = os.path.join(TPTP_ROOT, "Axioms")
MAX_PROBLEM_SIZE = 50 * 1024 # 50 KB
MAX_AXIOM_SIZE = 100 * 1024 # 100 KB
SAMPLE_SIZE = 500
OUTPUT_FILE = "/tmp/selected_benchmarks.txt"
include_re = re.compile(r"include\s*\(\s*['\"]([^'\"]+)['\"]", re.IGNORECASE)
status_re = re.compile(r"%\s*Status\s*:\s*(\S+)", re.IGNORECASE)
def axiom_sizes_ok(problem_path):
"""Return True if all included axiom files exist and are <= MAX_AXIOM_SIZE."""
try:
with open(problem_path, encoding="utf-8", errors="replace") as f:
content = f.read(4096) # header is in first few KB
except OSError:
return False
for m in include_re.finditer(content):
axiom_rel = m.group(1) # e.g. "Axioms/AGT001+0.ax"
axiom_path = os.path.join(TPTP_ROOT, axiom_rel)
if not os.path.exists(axiom_path):
return False # axiom missing — skip
if os.path.getsize(axiom_path) > MAX_AXIOM_SIZE:
return False # axiom too large — skip
return True
candidates = []
skipped_size = 0
skipped_axiom = 0
for domain in sorted(os.listdir(PROBLEMS_DIR)):
domain_dir = os.path.join(PROBLEMS_DIR, domain)
if not os.path.isdir(domain_dir):
continue
for fname in os.listdir(domain_dir):
if not fname.endswith(".p"):
continue
fpath = os.path.join(domain_dir, fname)
size = os.path.getsize(fpath)
if size > MAX_PROBLEM_SIZE:
skipped_size += 1
continue
if not axiom_sizes_ok(fpath):
skipped_axiom += 1
continue
candidates.append(fpath)
print(f"Total candidates (after filtering): {len(candidates)}", flush=True)
print(f" Skipped — problem too large : {skipped_size}", flush=True)
print(f" Skipped — axiom too large : {skipped_axiom}", flush=True)
if len(candidates) == 0:
print("ERROR: No suitable benchmark problems found.", file=sys.stderr)
sys.exit(1)
if len(candidates) > SAMPLE_SIZE:
random.seed(42)
selected = random.sample(candidates, SAMPLE_SIZE)
else:
selected = candidates
selected.sort()
with open(OUTPUT_FILE, "w") as f:
f.write("\n".join(selected) + "\n")
print(f"Selected {len(selected)} problems → {OUTPUT_FILE}", flush=True)
```
Run the script:
```bash
python3 /tmp/select_benchmarks.py
SELECTED=$(wc -l < /tmp/selected_benchmarks.txt)
echo "Benchmark set: $SELECTED problems"
```
If no problems are found, call `noop` with an error message and stop.
Call `noop` with `"$SELECTED problems selected. Starting benchmark run with 5-second timeout per problem — this will take approximately $(( SELECTED * 7 / 60 )) minutes."` to keep the session alive.
## Phase 4: Run Benchmarks
Save the following script to `/tmp/run_tptp_benchmarks.sh`, make it executable, and run it.
```bash
#!/usr/bin/env bash
set -euo pipefail
Z3=/tmp/z3-build/z3
TPTP_ROOT=/tmp/tptp
TIMEOUT_HARD=8 # outer OS-level guard (seconds; 3 s beyond Z3's -T:5)
Z3_TIMEOUT=5 # Z3 internal timeout: -T:N sets N-second limit (uppercase -T is seconds)
RESULTS=/tmp/tptp_results.tsv
PROBLEM_LIST=/tmp/selected_benchmarks.txt
echo -e "file\texpected\tactual\ttime_s\tnotes" > "$RESULTS"
# Helper: extract the expected SZS status from the TPTP problem header.
get_expected_status() {
local file="$1"
# Look for lines like: "% Status : Theorem"
grep -m1 -iP '%\s*Status\s*:\s*\K\S+' "$file" 2>/dev/null || echo "Unknown"
}
# Helper: run z3 on a single TPTP problem with timeout.
run_benchmark() {
local file="$1"
local start end elapsed output exit_code verdict
start=$(date +%s%3N) # milliseconds since epoch
output=$(TPTP="$TPTP_ROOT" timeout "$TIMEOUT_HARD" \
"$Z3" -tptp -T:"$Z3_TIMEOUT" "$file" 2>&1) || exit_code=$?
exit_code=${exit_code:-0}
end=$(date +%s%3N)
elapsed=$(echo "scale=3; ($end - $start) / 1000" | bc)
# Extract SZS status line from output
szs_line=$(echo "$output" | grep -m1 "% SZS status" || true)
if [ -n "$szs_line" ]; then
# Parse the status keyword (e.g. "Theorem", "CounterSatisfiable", "GaveUp")
verdict=$(echo "$szs_line" | grep -oP '% SZS status \K\S+' || echo "Unknown")
elif [ "$exit_code" -eq 124 ]; then
verdict="Timeout"
elif [ "$exit_code" -ne 0 ]; then
verdict="Crash"
else
verdict="NoOutput"
fi
echo "$verdict $elapsed"
}
COUNTER=0
TOTAL=$(wc -l < "$PROBLEM_LIST")
while IFS= read -r problem_file; do
COUNTER=$((COUNTER + 1))
expected=$(get_expected_status "$problem_file")
result_line=$(run_benchmark "$problem_file")
actual=$(echo "$result_line" | cut -d' ' -f1)
elapsed=$(echo "$result_line" | cut -d' ' -f2)
fname=$(basename "$problem_file")
# Classify notes
notes=""
# Soundness discrepancy: both answers are conclusive but conflict
conclusive_expected=false
conclusive_actual=false
case "$expected" in
Theorem|Unsatisfiable) conclusive_expected=true ;;
Satisfiable|CounterSatisfiable) conclusive_expected=true ;;
esac
case "$actual" in
Theorem|Unsatisfiable) conclusive_actual=true ;;
Satisfiable|CounterSatisfiable) conclusive_actual=true ;;
esac
if $conclusive_expected && $conclusive_actual; then
# Map expected to the Z3 output equivalents for comparison
# Theorem (has-conjecture unsat) matches "Theorem"
# Unsatisfiable (no-conjecture unsat) matches "Unsatisfiable"
# Satisfiable (no-conjecture sat) matches "Satisfiable"
# CounterSatisfiable (has-conjecture sat) matches "CounterSatisfiable"
if [ "$expected" != "$actual" ]; then
# Check for sat/unsat polarity conflict
sat_expected=false; sat_actual=false
case "$expected" in Satisfiable|CounterSatisfiable) sat_expected=true ;; esac
case "$actual" in Satisfiable|CounterSatisfiable) sat_actual=true ;; esac
if [ "$sat_expected" != "$sat_actual" ]; then
notes="SOUNDNESS_ERROR"
else
notes="STATUS_MISMATCH"
fi
fi
fi
if [ "$actual" = "Crash" ]; then
notes="CRASH"
fi
echo -e "$fname\t$expected\t$actual\t$elapsed\t$notes" >> "$RESULTS"
if [ -n "$notes" ]; then
echo "[$COUNTER/$TOTAL] $fname expected=$expected actual=$actual time=${elapsed}s *** $notes ***"
elif [ $((COUNTER % 50)) -eq 0 ]; then
echo "[$COUNTER/$TOTAL] Progress checkpoint last=$fname actual=$actual time=${elapsed}s"
fi
done < "$PROBLEM_LIST"
echo "Benchmark run complete: $COUNTER problems processed. Results → $RESULTS"
```
Run it:
```bash
chmod +x /tmp/run_tptp_benchmarks.sh
/tmp/run_tptp_benchmarks.sh
```
Do not skip any file in the list.
## Phase 5: Analyze Results
Save the following script to `/tmp/analyze_tptp.py` and run it:
```python
#!/usr/bin/env python3
"""Compute summary statistics from the TPTP benchmark TSV."""
import csv
RESULTS_FILE = "/tmp/tptp_results.tsv"
rows = []
with open(RESULTS_FILE, newline="") as f:
reader = csv.DictReader(f, delimiter="\t")
for row in reader:
rows.append(row)
total = len(rows)
# Verdict counts
from collections import Counter, defaultdict
actual_counts = Counter(r["actual"] for r in rows)
expected_counts = Counter(r["expected"] for r in rows)
# Flagged rows
soundness_errors = [r for r in rows if r["notes"] == "SOUNDNESS_ERROR"]
status_mismatches = [r for r in rows if r["notes"] == "STATUS_MISMATCH"]
crashes = [r for r in rows if r["notes"] == "CRASH"]
timeouts = [r for r in rows if r["actual"] == "Timeout"]
gave_up = [r for r in rows if r["actual"] == "GaveUp"]
# Solved correctly (expected matches actual for conclusive verdicts)
conclusive_expected = {"Theorem", "Unsatisfiable", "Satisfiable", "CounterSatisfiable"}
correct = [r for r in rows
if r["expected"] in conclusive_expected
and r["actual"] == r["expected"]]
print(f"TOTAL={total}")
print(f"CORRECT={len(correct)}")
print(f"TIMEOUTS={len(timeouts)}")
print(f"GAVE_UP={len(gave_up)}")
print(f"CRASHES={len(crashes)}")
print(f"SOUNDNESS_ERRORS={len(soundness_errors)}")
print(f"STATUS_MISMATCHES={len(status_mismatches)}")
print("\n--- Actual verdict breakdown ---")
for v, c in sorted(actual_counts.items()):
print(f" {v}: {c}")
print("\n--- Expected status breakdown ---")
for v, c in sorted(expected_counts.items()):
print(f" {v}: {c}")
if soundness_errors:
print(f"\n--- SOUNDNESS ERRORS ({len(soundness_errors)}) ---")
for r in soundness_errors:
print(f" {r['file']} expected={r['expected']} actual={r['actual']}")
if crashes:
print(f"\n--- CRASHES ({len(crashes)}) ---")
for r in crashes:
print(f" {r['file']} expected={r['expected']}")
if status_mismatches:
print(f"\n--- STATUS MISMATCHES ({len(status_mismatches)}) ---")
for r in status_mismatches[:20]:
print(f" {r['file']} expected={r['expected']} actual={r['actual']}")
```
Run the analysis:
```bash
python3 /tmp/analyze_tptp.py
```
## Phase 6: Generate and Post the Discussion Report
Read the TSV at `/tmp/tptp_results.tsv` and the analysis output, then compose a Markdown report and call `create_discussion`.
The report should use `###` or lower for all headers (never `#` or `##`). Use collapsible `<details>` sections for large tables.
Use this structure:
```markdown
**Date**: <today's date>
**Branch**: master
**Commit**: `<short SHA>` (run `git rev-parse --short HEAD` in ${{ github.workspace }} to get the SHA)
**Workflow Run**: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
**TPTP version**: <downloaded version>
**Problems benchmarked**: <N> (random sample, timeout 5 s per problem)
---
### Summary
| Metric | Count |
|--------|-------|
| Total problems run | N |
| Correct (expected = actual) | N |
| Timeouts | N |
| GaveUp (within time budget) | N |
| Crashes / errors | N |
| Soundness errors (sat↔unsat conflict) | N |
| Status mismatches (Theorem vs Unsatisfiable etc.) | N |
### Expected Status Distribution
| Expected Status | Count |
|----------------|-------|
| Theorem | N |
| Unsatisfiable | N |
| Satisfiable | N |
| CounterSatisfiable | N |
| Unknown / Open | N |
---
### ⚠️ Critical: Soundness Errors
[List ALL files where Z3 returned a conclusive answer that contradicts the expected answer
(e.g., expected Theorem but got CounterSatisfiable). If none, write "None detected."]
### 💥 Crashes
[List ALL files where Z3 crashed (non-zero exit, no SZS output, not a timeout).
Include filename and expected status. If none, write "None detected."]
### Status Mismatches
[Files where both answers are conclusive but differ in Theorem vs Unsatisfiable polarity
(e.g., expected Theorem but actual Unsatisfiable). These may indicate conjecture-handling
differences rather than soundness bugs. If none, write "None detected."]
---
<details>
<summary>View all Timeouts (problems where Z3 exceeded the 5-second limit)</summary>
| # | File | Expected Status |
|---|------|----------------|
[First 100 timeout rows]
</details>
<details>
<summary>View full per-problem results table</summary>
| # | File | Expected | Actual | Time (s) | Notes |
|---|------|----------|--------|----------|-------|
[All rows, or first 500 if over limit]
</details>
---
### Recommendations
[Based on the findings, list actionable items. E.g.: investigate soundness errors,
file crash bugs, note domains where Z3 consistently times out.]
```
Post the discussion using the `create_discussion` safe output. The title should be
`[TPTP Benchmark] master — <date>`.
## Safe Output Guarantee
You **MUST** call either `create_discussion` or `noop` before the workflow ends:
- **Full success**: Call `create_discussion` with the complete report.
- **Partial results** (some problems ran): Call `create_discussion` with whatever results are available and a note about incomplete execution.
- **Download failure**: Call `noop` with the download error details.
- **No problems selected**: Call `noop` explaining why no problems were found.
- **Binary missing**: If `/tmp/z3-build/z3` is unexpectedly absent, call `noop` with that detail and stop.
## Important Notes
- **Build failure handling**: Z3 was built before the agent loaded. If the binary is missing or non-functional, call `noop` with the error and stop.
- **TPTP environment variable**: Set `TPTP=/tmp/tptp` when invoking `z3 -tptp` so that `include()` directives in problem files resolve correctly against the downloaded Axioms directory.
- **Timeout detection**: Use `timeout 8` as the outer OS-level guard (3 seconds beyond Z3's `-T:5`) to allow Z3 to exit cleanly before the shell kills it. If the exit code from `timeout` is 124, record the verdict as `Timeout`.
- **Crash detection**: A crash is a non-zero exit code with no `% SZS status` line in the output and no timeout. Record it separately from `GaveUp`.
- **SZS status semantics**: Z3 outputs `Theorem` (not `Unsatisfiable`) when it proves a conjecture; `CounterSatisfiable` (not `Satisfiable`) when it finds a counterexample to a conjecture. A status mismatch between `Theorem` and `Unsatisfiable` for the same problem may be innocuous and depends on whether the problem file uses a conjecture formula.
- **Report soundness bugs prominently**: Any case where the polarity of the answer conflicts (expected Theorem/Unsatisfiable but got CounterSatisfiable/Satisfiable, or vice versa) is a potential soundness bug and must be highlighted as critical.
- **Keep progress log**: Print a line for every flagged result and every 50th problem so the workflow log shows progress.
- **Close older discussions**: Configured via `close-older-discussions: true`. Only the latest weekly report remains open.

View file

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup node
uses: actions/setup-node@v6
@ -36,7 +36,7 @@ jobs:
cp ../../../LICENSE.txt .
- name: Setup emscripten
uses: mymindstorm/setup-emsdk@v14
uses: mymindstorm/setup-emsdk@v16
with:
no-install: true
version: ${{env.EM_VERSION}}

View file

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v7.0.0
- name: Setup node
uses: actions/setup-node@v6
@ -29,7 +29,7 @@ jobs:
node-version: "lts/*"
- name: Setup emscripten
uses: mymindstorm/setup-emsdk@v14
uses: mymindstorm/setup-emsdk@v16
with:
no-install: true
version: ${{env.EM_VERSION}}

View file

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/checkout@v7.0.0
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more