replico 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- replico-0.1.0/.freebuff/project-id +1 -0
- replico-0.1.0/.github/workflows/ci.yml +76 -0
- replico-0.1.0/.gitignore +36 -0
- replico-0.1.0/CHANGELOG.md +75 -0
- replico-0.1.0/LICENSE +21 -0
- replico-0.1.0/PKG-INFO +447 -0
- replico-0.1.0/README.md +392 -0
- replico-0.1.0/SECURITY.md +103 -0
- replico-0.1.0/pyproject.toml +77 -0
- replico-0.1.0/src/replico/__init__.py +7 -0
- replico-0.1.0/src/replico/__main__.py +6 -0
- replico-0.1.0/src/replico/analysis/__init__.py +1 -0
- replico-0.1.0/src/replico/analysis/classifier.py +304 -0
- replico-0.1.0/src/replico/analysis/logs.py +216 -0
- replico-0.1.0/src/replico/cli.py +339 -0
- replico-0.1.0/src/replico/cmds.py +298 -0
- replico-0.1.0/src/replico/config.py +189 -0
- replico-0.1.0/src/replico/environments/__init__.py +25 -0
- replico-0.1.0/src/replico/environments/base.py +64 -0
- replico-0.1.0/src/replico/environments/fingerprint.py +222 -0
- replico-0.1.0/src/replico/environments/node.py +35 -0
- replico-0.1.0/src/replico/environments/python.py +289 -0
- replico-0.1.0/src/replico/errors.py +80 -0
- replico-0.1.0/src/replico/execution/__init__.py +1 -0
- replico-0.1.0/src/replico/execution/docker.py +166 -0
- replico-0.1.0/src/replico/execution/runner.py +212 -0
- replico-0.1.0/src/replico/flows.py +840 -0
- replico-0.1.0/src/replico/github/__init__.py +1 -0
- replico-0.1.0/src/replico/github/client.py +258 -0
- replico-0.1.0/src/replico/github/refs.py +76 -0
- replico-0.1.0/src/replico/gitrepo.py +179 -0
- replico-0.1.0/src/replico/models.py +157 -0
- replico-0.1.0/src/replico/pipeline.py +740 -0
- replico-0.1.0/src/replico/security/__init__.py +34 -0
- replico-0.1.0/src/replico/security/guard.py +122 -0
- replico-0.1.0/src/replico/security/redaction.py +270 -0
- replico-0.1.0/src/replico/storage/__init__.py +1 -0
- replico-0.1.0/src/replico/storage/store.py +201 -0
- replico-0.1.0/src/replico/ui.py +146 -0
- replico-0.1.0/src/replico/util.py +136 -0
- replico-0.1.0/src/replico/workflow/__init__.py +1 -0
- replico-0.1.0/src/replico/workflow/detector.py +271 -0
- replico-0.1.0/src/replico/workflow/matcher.py +101 -0
- replico-0.1.0/src/replico/workflow/parser.py +363 -0
- replico-0.1.0/tests/conftest.py +253 -0
- replico-0.1.0/tests/test_classifier.py +78 -0
- replico-0.1.0/tests/test_cli.py +101 -0
- replico-0.1.0/tests/test_config.py +88 -0
- replico-0.1.0/tests/test_exit_codes.py +53 -0
- replico-0.1.0/tests/test_fingerprint.py +98 -0
- replico-0.1.0/tests/test_github_client.py +215 -0
- replico-0.1.0/tests/test_gitrepo.py +56 -0
- replico-0.1.0/tests/test_integration_reproduce.py +248 -0
- replico-0.1.0/tests/test_logs.py +77 -0
- replico-0.1.0/tests/test_refs.py +58 -0
- replico-0.1.0/tests/test_runner.py +77 -0
- replico-0.1.0/tests/test_security_guard.py +81 -0
- replico-0.1.0/tests/test_security_redact.py +136 -0
- replico-0.1.0/tests/test_store.py +116 -0
- replico-0.1.0/tests/test_workflow_detector.py +168 -0
- replico-0.1.0/tests/test_workflow_parser.py +142 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
49c8c90f-46e1-4c45-a454-0861ed60b128
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
name: test (${{ matrix.os }}, py${{ matrix.python }})
|
|
14
|
+
runs-on: ${{ matrix.os }}
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
19
|
+
python: ["3.11", "3.12", "3.13", "3.14"]
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python }}
|
|
25
|
+
cache: pip
|
|
26
|
+
- name: Install
|
|
27
|
+
run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
|
|
28
|
+
- name: Test (offline suite, mocked GitHub)
|
|
29
|
+
run: python -m pytest -q
|
|
30
|
+
env:
|
|
31
|
+
# Never reach out to the network during tests.
|
|
32
|
+
NO_PROXY: "*"
|
|
33
|
+
no_proxy: "*"
|
|
34
|
+
|
|
35
|
+
quality:
|
|
36
|
+
name: lint + types
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
strategy:
|
|
39
|
+
matrix:
|
|
40
|
+
python: ["3.11", "3.14"]
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
- uses: actions/setup-python@v5
|
|
44
|
+
with:
|
|
45
|
+
python-version: ${{ matrix.python }}
|
|
46
|
+
- name: Install
|
|
47
|
+
run: python -m pip install -e ".[dev]"
|
|
48
|
+
- name: Lint
|
|
49
|
+
run: ruff check src tests
|
|
50
|
+
- name: Format
|
|
51
|
+
run: ruff format --check src tests
|
|
52
|
+
- name: Type checks
|
|
53
|
+
run: mypy src/replico
|
|
54
|
+
|
|
55
|
+
package:
|
|
56
|
+
name: build + package validation
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
- uses: actions/setup-python@v5
|
|
61
|
+
with:
|
|
62
|
+
python-version: "3.13"
|
|
63
|
+
- name: Install build tooling
|
|
64
|
+
run: python -m pip install --upgrade pip build
|
|
65
|
+
- name: Build distributions
|
|
66
|
+
run: python -m build
|
|
67
|
+
- name: Validate wheel metadata
|
|
68
|
+
run: python -m pip install --force-reinstall dist/*.whl
|
|
69
|
+
- name: Smoke-test installed CLI
|
|
70
|
+
run: replico version && replico help >/dev/null
|
|
71
|
+
- name: Upload artifacts
|
|
72
|
+
uses: actions/upload-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: replico-dist
|
|
75
|
+
path: dist/
|
|
76
|
+
if-no-files-found: error
|
replico-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
env/
|
|
10
|
+
|
|
11
|
+
# Replico's own artifacts (when dogfooding against this repository)
|
|
12
|
+
.replico/
|
|
13
|
+
|
|
14
|
+
# Test / coverage artifacts
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
coverage.xml
|
|
18
|
+
htmlcov/
|
|
19
|
+
.mypy_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
|
|
22
|
+
# Build artifacts
|
|
23
|
+
build/
|
|
24
|
+
dist/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
*.egg
|
|
27
|
+
|
|
28
|
+
# OS / editor noise
|
|
29
|
+
.DS_Store
|
|
30
|
+
Thumbs.db
|
|
31
|
+
*.swp
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
|
|
35
|
+
# Scratch / probe files
|
|
36
|
+
_probe_*.py
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Replico are documented here. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and semantic
|
|
5
|
+
versioning (see [pyproject.toml](pyproject.toml)).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-09-04
|
|
8
|
+
|
|
9
|
+
First public release: GitHub Actions failures → local reproduction for
|
|
10
|
+
Python workflows.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Core command** — `replico <run-url>` (and `replico reproduce …`,
|
|
15
|
+
`replico run <run-id>`) walks a failed GitHub Actions run end to end:
|
|
16
|
+
run identification → failed job → failed step → workflow parsing →
|
|
17
|
+
environment/dependency detection → reproduction plan → safe local
|
|
18
|
+
execution → honest verdict.
|
|
19
|
+
- **GitHub integration** — URL/ref parsing for `actions/runs/<id>` links,
|
|
20
|
+
`GITHUB_TOKEN` and `gh` CLI auth, public-repo anonymous access, run/job/
|
|
21
|
+
step listing, log download, workflow YAML retrieval by commit SHA.
|
|
22
|
+
- **Workflow parser** — hardened PyYAML-based parser (memoized alias
|
|
23
|
+
expansion, node budgets, depth caps against billion-laughs/recursion
|
|
24
|
+
attacks) supporting `runs-on`, `steps`, `uses`, `run`, `with`, `env`,
|
|
25
|
+
`defaults`, `working-directory`, `shell`, `strategy.matrix`, `if`.
|
|
26
|
+
- **Job/step selection** — automatic failed-job detection with interactive
|
|
27
|
+
picker when several fail; `--job` to select explicitly; failed-step
|
|
28
|
+
identification from job conclusion + annotations.
|
|
29
|
+
- **Ecosystem adapters** — pluggable `EcosystemAdapter` protocol; v0.1
|
|
30
|
+
ships Python (setup-action versions, requirements/pyproject/setup files,
|
|
31
|
+
`pip install`/`pytest`/`unittest` detection). Node adapter registers but
|
|
32
|
+
reports unsupported until v0.3.
|
|
33
|
+
- **Local execution** — explicit-interpreter script runner (no `shell=True`),
|
|
34
|
+
per-shell availability probing, risk audit of every workflow command
|
|
35
|
+
(elevation/destructive/network-exec/suspicious patterns) with mandatory
|
|
36
|
+
confirmation, optional `--docker` isolation with automatic fallback.
|
|
37
|
+
- **Failure analysis** — log compression to relevant evidence lines,
|
|
38
|
+
evidence-based classification (test/build/dependency/version/missing
|
|
39
|
+
tool/file/env/timeout/network/permission/config/unknown), confidence
|
|
40
|
+
scoring, GitHub annotation anchors.
|
|
41
|
+
- **Verdicts** — `reproduced`, `partially_reproduced`, `not_reproduced`,
|
|
42
|
+
`unsupported`; environment parity estimate comparing local machine to the
|
|
43
|
+
CI runner; never over-claims reproduction.
|
|
44
|
+
- **SecretShield integration** — `Sanitizer` adapter over the public
|
|
45
|
+
SecretShield API (`redact`/`detect`/`configure`) plus a compatibility
|
|
46
|
+
layer for known low-entropy literal secrets; every UI line, JSON payload
|
|
47
|
+
and `.replico/` artifact passes through it. Debug/verbose modes respect
|
|
48
|
+
redaction; stream-level protection via `enable()` where appropriate.
|
|
49
|
+
- **`.replico/` store** — redacted `reproduction.json`, `environment.json`,
|
|
50
|
+
`workflow.yml`, `commands.txt`, `differences.json`, `README.md`;
|
|
51
|
+
`rerun`, `status`, `diff`, `env`, `clean`, `capture` commands.
|
|
52
|
+
- **CLI polish** — rich formatting with `--plain`, machine-readable `--json`,
|
|
53
|
+
stable exit codes (0–6, 70), progress lines, sensible colors.
|
|
54
|
+
- **Offline test suite** — mocked GitHub API (`FakeGitHub`), fixture
|
|
55
|
+
repositories created on the fly, security tests for leakage, injection,
|
|
56
|
+
traversal, malicious YAML/filenames/env.
|
|
57
|
+
- **Packaging/docs** — modern `pyproject.toml` (hatchling), console entry
|
|
58
|
+
point, README, SECURITY model, own GitHub Actions CI
|
|
59
|
+
(3 OS × Python 3.11–3.14 + lint/types/build validation).
|
|
60
|
+
|
|
61
|
+
### Security
|
|
62
|
+
|
|
63
|
+
- New dependency: `secretshield>=0.4.2` (see `replico/security/redaction.py`).
|
|
64
|
+
- Workflow commands are audited before execution; nothing elevated or
|
|
65
|
+
destructive runs without explicit `--yes`-free confirmation.
|
|
66
|
+
- Repository/job names are validated before touching URLs, paths or
|
|
67
|
+
subprocess arguments; `safe_join` blocks path traversal.
|
|
68
|
+
|
|
69
|
+
### Notes / limitations
|
|
70
|
+
|
|
71
|
+
- v0.1 targets Python workflows on public repositories. Node/Go/Rust and
|
|
72
|
+
Docker-first flows are recognized but reported as unsupported rather than
|
|
73
|
+
half-reproduced.
|
|
74
|
+
- The environment parity figure is a transparent heuristic over a handful of
|
|
75
|
+
weighted checks — an estimate, never a guarantee.
|
replico-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sam3360
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
replico-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: replico
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turn GitHub Actions CI failures into locally reproducible failures.
|
|
5
|
+
Author: Samarth Chugh (Sam3360)
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Sam3360
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Keywords: ci,developer-tools,github-actions,reproduction,testing
|
|
29
|
+
Classifier: Development Status :: 3 - Alpha
|
|
30
|
+
Classifier: Environment :: Console
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
39
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
40
|
+
Classifier: Topic :: Software Development :: Testing
|
|
41
|
+
Requires-Python: >=3.11
|
|
42
|
+
Requires-Dist: pyyaml>=6.0
|
|
43
|
+
Requires-Dist: requests>=2.28
|
|
44
|
+
Requires-Dist: rich>=13.7
|
|
45
|
+
Requires-Dist: secretshield>=0.4.2
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
48
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
51
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
52
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
53
|
+
Requires-Dist: types-requests>=2.31; extra == 'dev'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
# Replico
|
|
57
|
+
|
|
58
|
+
**CI failures, reproduced locally.**
|
|
59
|
+
|
|
60
|
+
GitHub Actions fails → `replico <run-url>` → the failing command runs on your
|
|
61
|
+
machine → you get a **reproducible failure** with evidence.
|
|
62
|
+
|
|
63
|
+
Replico is a local-first developer tool. It reads a failed GitHub Actions run,
|
|
64
|
+
figures out *which job failed, which step failed and why*, reconstructs the
|
|
65
|
+
relevant environment (Python version, dependencies, environment variables it
|
|
66
|
+
can), and replays the failing step on your machine. It then tells you — with
|
|
67
|
+
honest confidence levels — whether the CI failure was reproduced.
|
|
68
|
+
|
|
69
|
+
> **The core promise:** turn CI failures into locally reproducible failures
|
|
70
|
+
> whenever Replico can reconstruct the relevant conditions. Replico never
|
|
71
|
+
> claims to be a perfect clone of a GitHub-hosted runner, and never claims a
|
|
72
|
+
> reproduction it cannot back with evidence.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## The problem
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
Developer pushes code → CI fails → Developer reads logs
|
|
80
|
+
→ Guesses what went wrong → Changes code → Pushes again → CI fails again
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## How it works
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
GitHub Actions fails
|
|
87
|
+
↓
|
|
88
|
+
replico <run-url> # or: replico run <run-id>
|
|
89
|
+
↓
|
|
90
|
+
Analyze the failed workflow
|
|
91
|
+
↓
|
|
92
|
+
Reconstruct the relevant environment
|
|
93
|
+
↓
|
|
94
|
+
Run the failing command locally
|
|
95
|
+
↓
|
|
96
|
+
REPRODUCED ✓ / NOT REPRODUCED ✗ / PARTIAL ⚠ (with evidence and parity)
|
|
97
|
+
↓
|
|
98
|
+
.replico/ saved — iterate: fix code → replico rerun
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Quick start
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install replico
|
|
105
|
+
|
|
106
|
+
# from the checkout of the repository whose CI failed:
|
|
107
|
+
replico https://github.com/example/project/actions/runs/123456789
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
That's it. No account, no API key, no cloud service. Public repositories work
|
|
111
|
+
without any token. Private repositories need either `GITHUB_TOKEN` in your
|
|
112
|
+
environment or the GitHub CLI (`gh auth login`).
|
|
113
|
+
|
|
114
|
+
GitHub's API only serves **job log downloads to authenticated requests**, even
|
|
115
|
+
for public repositories. Without a token Replico still finds the failed run,
|
|
116
|
+
job and step, fetches the workflow YAML at the exact commit, and reproduces
|
|
117
|
+
from the workflow — but the log-level failure analysis needs a token:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
export GITHUB_TOKEN=ghp_... # or: gh auth login
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Example output (abridged):
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
─────────────────────────────────────────────────────────────
|
|
127
|
+
✓ failed job: Python 3.13
|
|
128
|
+
✓ failed step: Run tests
|
|
129
|
+
|
|
130
|
+
REPLICO REPRODUCTION PLAN
|
|
131
|
+
|
|
132
|
+
Repository: example/project
|
|
133
|
+
Commit: a82f91c
|
|
134
|
+
Workflow: Tests
|
|
135
|
+
Job: Python 3.13
|
|
136
|
+
Failed step: Run tests
|
|
137
|
+
Runner: ubuntu-24.04
|
|
138
|
+
Ecosystem: python
|
|
139
|
+
|
|
140
|
+
Detected setup:
|
|
141
|
+
✓ checkout
|
|
142
|
+
✓ Python 3.13
|
|
143
|
+
• pip install -r requirements.txt
|
|
144
|
+
|
|
145
|
+
Reproducing...
|
|
146
|
+
✓ environment ready
|
|
147
|
+
✓ dependencies installed
|
|
148
|
+
✗ the failing command FAILED locally (exit 1, 2.4 s)
|
|
149
|
+
|
|
150
|
+
REPLICO RESULT — CI FAILURE REPRODUCED
|
|
151
|
+
• the same failing test(s) reproduced locally: tests/test_auth.py::test_login
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
After a code change:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
replico rerun
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
REPLICO RERUN
|
|
162
|
+
✓ environment ready
|
|
163
|
+
✓ running the previously failing step…
|
|
164
|
+
|
|
165
|
+
✓ the failing command PASSED locally
|
|
166
|
+
|
|
167
|
+
REPLICO RESULT — CI FAILURE NOT REPRODUCED
|
|
168
|
+
• the previously reproduced failure no longer occurs locally.
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
(Wording is careful on purpose: a local pass does *not* prove CI will pass.)
|
|
172
|
+
|
|
173
|
+
## Installation
|
|
174
|
+
|
|
175
|
+
Requirements: **Python 3.11+**, `git`. Docker is optional but recommended
|
|
176
|
+
when the CI runner OS differs from your machine.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
pip install replico # installs the `replico` command
|
|
180
|
+
# or from source:
|
|
181
|
+
pip install -e ".[dev]" # development install
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
SecretShield (`secretshield>=0.4.2`) is a real dependency: every log line,
|
|
185
|
+
environment value, command output and artifact that Replico displays or saves
|
|
186
|
+
passes through SecretShield's detection/redaction (see [Security](#security)).
|
|
187
|
+
|
|
188
|
+
## Usage
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Reproduce a failed run (the flagship command — same as `replico reproduce …`)
|
|
192
|
+
replico https://github.com/owner/repo/actions/runs/123456789
|
|
193
|
+
replico reproduce https://github.com/owner/repo/actions/runs/123456789
|
|
194
|
+
replico run 123456789 # run id; repository read from git origin
|
|
195
|
+
|
|
196
|
+
# Iterate after code changes
|
|
197
|
+
replico rerun # re-run the saved reproduction
|
|
198
|
+
replico status # saved state vs current checkout
|
|
199
|
+
replico diff # what changed since the CI failure
|
|
200
|
+
|
|
201
|
+
# Inspect
|
|
202
|
+
replico env # sanitized local environment fingerprint
|
|
203
|
+
replico config # effective configuration (no secrets)
|
|
204
|
+
replico version
|
|
205
|
+
|
|
206
|
+
# Hygiene
|
|
207
|
+
replico clean # remove .replico/ (confirmed)
|
|
208
|
+
|
|
209
|
+
# Inside CI (e.g. under `if: failure()`), capture context for later:
|
|
210
|
+
replico capture
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Common flags:
|
|
214
|
+
|
|
215
|
+
| Flag | Meaning |
|
|
216
|
+
| --- | --- |
|
|
217
|
+
| `--job <job>` | which failed job to reproduce (multi-job runs) |
|
|
218
|
+
| `--step <name>` | which step to reproduce (default: the failing step) |
|
|
219
|
+
| `--docker` / `--no-docker` | force / forbid Docker isolation |
|
|
220
|
+
| `--offline` | use only locally saved data — no GitHub requests |
|
|
221
|
+
| `--json` | machine-readable JSON on stdout (all prose goes to stderr) |
|
|
222
|
+
| `--plain` | no colors, no decorations (CI/log capture) |
|
|
223
|
+
| `--yes` | accept confirmations non-interactively |
|
|
224
|
+
| `--verbose` / `--debug` | more detail (still redacted) |
|
|
225
|
+
| `--clean` | remove an existing `.replico/` before reproducing |
|
|
226
|
+
|
|
227
|
+
### Multiple failed jobs
|
|
228
|
+
|
|
229
|
+
```text
|
|
230
|
+
2 failed jobs found.
|
|
231
|
+
|
|
232
|
+
1. test-python
|
|
233
|
+
2. integration-linux
|
|
234
|
+
|
|
235
|
+
Use --job to select one:
|
|
236
|
+
replico <run-url> --job test-python
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
When exactly one job failed it is selected automatically. The same logic
|
|
240
|
+
applies inside the job: the first failed step is chosen.
|
|
241
|
+
|
|
242
|
+
### Matrix workflows
|
|
243
|
+
|
|
244
|
+
Matrix combinations are matched back to the workflow YAML (best effort:
|
|
245
|
+
job id, explicit `name:`, and matrix-expanded display names). The matching
|
|
246
|
+
combination's variables are used when rendering the steps.
|
|
247
|
+
|
|
248
|
+
## Supported workflows (v0.1)
|
|
249
|
+
|
|
250
|
+
| Ecosystem | Status |
|
|
251
|
+
| --- | --- |
|
|
252
|
+
| Python (`actions/setup-python`, `pip`, `pytest`, `python -m unittest`, …) | **supported** |
|
|
253
|
+
| Plain shell jobs (`run:` only, no package managers) | supported (generic) |
|
|
254
|
+
| Node.js / Go / Rust / Java / .NET | detected, reported as *unsupported* (roadmap v0.3) |
|
|
255
|
+
| Failing step is a third-party `uses:` action | reported as *unsupported* |
|
|
256
|
+
|
|
257
|
+
Replico understands checkout/setup actions, dependency install commands
|
|
258
|
+
(`pip install`, `-r` requirements, `pip install -e .`), `python -m pytest`,
|
|
259
|
+
environment blocks, `working-directory`, `defaults.run.shell`, `strategy`
|
|
260
|
+
matrices, and `${{ matrix.* }}` / basic `${{ github.* }}` expressions.
|
|
261
|
+
Secrets referenced as `${{ secrets.X }}` are never fetched; steps that
|
|
262
|
+
genuinely require them will fail locally in a deterministic way and Replico
|
|
263
|
+
will say so.
|
|
264
|
+
|
|
265
|
+
## Result states
|
|
266
|
+
|
|
267
|
+
Replico distinguishes — and never conflates:
|
|
268
|
+
|
|
269
|
+
| State | Meaning |
|
|
270
|
+
| --- | --- |
|
|
271
|
+
| `reproduced` | the failing command failed locally **and** the failure signature (failing test id / error category) matches CI |
|
|
272
|
+
| `partially_reproduced` | a failure occurred locally but its identity could not be confirmed against CI, **or** the local run passed under materially different conditions |
|
|
273
|
+
| `not_reproduced` | the failing command passed locally under adequate environment parity |
|
|
274
|
+
| `unsupported` | Replico does not yet know how to reproduce this workflow |
|
|
275
|
+
|
|
276
|
+
Verbal results are matched by stable exit codes:
|
|
277
|
+
|
|
278
|
+
| Code | Meaning |
|
|
279
|
+
| --- | --- |
|
|
280
|
+
| `0` | reproduction succeeded — nothing is failing locally (verdict `not_reproduced`; a `rerun` that now passes) |
|
|
281
|
+
| `1` | reproduced failure still exists (verdict `reproduced`; a `rerun` that still fails) |
|
|
282
|
+
| `2` | could not reproduce (blocked, or `partially_reproduced` without a local failure) |
|
|
283
|
+
| `3` | invalid input (bad URL, unknown `--job`, missing args) |
|
|
284
|
+
| `4` | authentication problem (private repo without a usable token) |
|
|
285
|
+
| `5` | unsupported workflow |
|
|
286
|
+
| `6` | environment/setup problem (missing tool, venv/Docker failure) |
|
|
287
|
+
| `70` | internal error |
|
|
288
|
+
|
|
289
|
+
## Environment parity
|
|
290
|
+
|
|
291
|
+
Replico fingerprints your machine (`replico env`) and compares it with the CI
|
|
292
|
+
job:
|
|
293
|
+
|
|
294
|
+
```text
|
|
295
|
+
ENVIRONMENT DIFFERENCES
|
|
296
|
+
✓ OS Windows 10/11: CI ubuntu-24.04 ← mismatch would be ✗ / a Docker hint
|
|
297
|
+
✗ Python 3.13: using 3.12.4
|
|
298
|
+
✓ git
|
|
299
|
+
✓ dependencies
|
|
300
|
+
✓ environment variables
|
|
301
|
+
Environment parity: 72% (estimate — not a guarantee)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Parity is a transparent, weighted heuristic (OS, Python version, isolation,
|
|
305
|
+
git, dependencies, env vars) — not a claim of byte-for-byte parity with
|
|
306
|
+
GitHub's runner images. When parity is low and the local run passed, Replico
|
|
307
|
+
will not let you claim the failure is gone.
|
|
308
|
+
|
|
309
|
+
## Isolation
|
|
310
|
+
|
|
311
|
+
* **Local (default, Python jobs):** dependencies are installed into a virtual
|
|
312
|
+
environment under `.replico/venv`, never into your global environment.
|
|
313
|
+
* **`--docker`:** the repository is mounted into a matching image
|
|
314
|
+
(`python:3.13-slim`, `ubuntu:24.04`, …) — the closest match for Linux
|
|
315
|
+
runners and the recommended mode when CI ran on a different OS.
|
|
316
|
+
* Automatic mode picks Docker when the CI runner OS (or requested Python
|
|
317
|
+
version) is not available locally and Docker is running.
|
|
318
|
+
|
|
319
|
+
Replico never runs `sudo`, administrator commands or destructive filesystem
|
|
320
|
+
operations without explicit confirmation. Commands extracted from workflow
|
|
321
|
+
files are audited first (`replico/security/guard.py`); risky ones require
|
|
322
|
+
`--yes` or an interactive confirm, and elevation is never performed for you.
|
|
323
|
+
|
|
324
|
+
## Security
|
|
325
|
+
|
|
326
|
+
Replico is local-first and privacy-conscious:
|
|
327
|
+
|
|
328
|
+
* Network access is limited to the GitHub API (plus dependency downloads the
|
|
329
|
+
workflow itself requests). **No source code, logs, environment values or
|
|
330
|
+
artifacts are uploaded anywhere. There is no telemetry.**
|
|
331
|
+
* Tokens come from `GITHUB_TOKEN` / `GH_TOKEN` / the GitHub CLI and travel
|
|
332
|
+
only in the `Authorization` header of API requests. They are never logged,
|
|
333
|
+
displayed or saved.
|
|
334
|
+
* **SecretShield** (`secretshield>=0.4.2`) is used wherever sensitive content
|
|
335
|
+
could appear:
|
|
336
|
+
* CI logs and command output are scanned/redacted before display or
|
|
337
|
+
persistence (`redact`/`detect`),
|
|
338
|
+
* `secretshield.enable()` protects `stdout`/`stderr` and the logging module
|
|
339
|
+
as a last line of defense,
|
|
340
|
+
* `replico/security/redaction.py` is the single adapter between Replico and
|
|
341
|
+
SecretShield; Replico adds *literal* known-secret redaction (values from
|
|
342
|
+
your environment) on top, because SecretShield is pattern/entropy based
|
|
343
|
+
and cannot know that a low-entropy string is your password.
|
|
344
|
+
* Secret-like environment variables are shown as `NAME = present` — never
|
|
345
|
+
their values — in `replico env`, fingerprints, JSON output, `--debug`, and
|
|
346
|
+
everything saved under `.replico/`.
|
|
347
|
+
* Environment values are kept out of child-process environments unless they
|
|
348
|
+
are workflow literals that CI itself would set; `${{ secrets.* }}` is never
|
|
349
|
+
resolved or injected.
|
|
350
|
+
* Malicious inputs are handled defensively: YAML is parsed with a
|
|
351
|
+
budgeted/memoized engine (alias-expansion bombs are neutralized),
|
|
352
|
+
repository/job names are validated before touching paths or URLs, command
|
|
353
|
+
lines are audited, subprocesses are spawned without `shell=True` for
|
|
354
|
+
Replico's own commands, and env var names are validated.
|
|
355
|
+
* Workflow YAML is stored redacted under `.replico/`; see
|
|
356
|
+
`replico/security/` and the tests in `tests/` for the details.
|
|
357
|
+
|
|
358
|
+
## Privacy
|
|
359
|
+
|
|
360
|
+
Your repository stays on your machine. Replico makes no network calls beyond
|
|
361
|
+
GitHub API requests that are required to read the run, its logs and its
|
|
362
|
+
workflow file, plus whatever the workflow itself runs (dependency installs).
|
|
363
|
+
There is no Replico server, no account, and telemetry is not collected — if
|
|
364
|
+
telemetry is ever introduced it will be opt-in only.
|
|
365
|
+
|
|
366
|
+
## Limitations (honest)
|
|
367
|
+
|
|
368
|
+
* Replico does **not** clone GitHub's runner images. Tools preinstalled on
|
|
369
|
+
GitHub-hosted runners (compilers, system libraries, caches) are generally
|
|
370
|
+
absent locally; parity numbers reflect that.
|
|
371
|
+
* Only `run:` steps are replayed. Third-party actions cannot be executed
|
|
372
|
+
locally without their container/runtime.
|
|
373
|
+
* v0.1 covers Python workflows well and plain shell jobs; Node/Go/Rust are
|
|
374
|
+
detected and reported as unsupported rather than half-executed.
|
|
375
|
+
* Log analysis is heuristic. When Replico cannot extract a confident failure
|
|
376
|
+
signature it says so instead of guessing.
|
|
377
|
+
* Multi-line steps are replayed as one script (matching GitHub's behavior)
|
|
378
|
+
with the shell GitHub would use (`bash -eo pipefail`, pwsh on Windows).
|
|
379
|
+
|
|
380
|
+
## Architecture
|
|
381
|
+
|
|
382
|
+
```text
|
|
383
|
+
replico/
|
|
384
|
+
├── cli.py argparse entry point, exit-code mapping
|
|
385
|
+
├── flows.py reproduce / rerun orchestration
|
|
386
|
+
├── pipeline.py run → plan → execute → verdict engine helpers
|
|
387
|
+
├── cmds.py status / diff / env / clean / config / capture
|
|
388
|
+
├── config.py .replico.toml (optional) + defaults
|
|
389
|
+
├── ui.py safe console output (rich, sanitized, JSON mode)
|
|
390
|
+
├── errors.py exceptions bound to stable exit codes
|
|
391
|
+
├── github/ URL parsing, REST client (token-safe), job/step models
|
|
392
|
+
├── workflow/ bomb-safe YAML parser, workflow model, job matcher,
|
|
393
|
+
│ environment/dependency detection
|
|
394
|
+
├── environments/ ecosystem adapters (base, python), fingerprinting
|
|
395
|
+
├── execution/ shell runner, Docker isolation
|
|
396
|
+
├── analysis/ log analysis (500 lines → 12 relevant), classifier
|
|
397
|
+
├── storage/ .replico/ store (redacted artifacts)
|
|
398
|
+
└── security/ SecretShield adapter, sanitizer, command/path guards
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Ecosystems plug in behind `EcosystemAdapter`:
|
|
402
|
+
|
|
403
|
+
```python
|
|
404
|
+
class EcosystemAdapter(ABC):
|
|
405
|
+
def detect(self, analysis: JobAnalysis) -> EcosystemDetection: ...
|
|
406
|
+
# see environments/base.py — Node (planned v0.3) already registers
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Development
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
pip install -e ".[dev]"
|
|
413
|
+
pytest # offline test suite (mocked GitHub)
|
|
414
|
+
ruff check . && ruff format --check .
|
|
415
|
+
mypy src/replico
|
|
416
|
+
python -m build # package validation
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Replico's own CI
|
|
420
|
+
|
|
421
|
+
`.github/workflows/ci.yml` tests Replico itself on Windows/Ubuntu/macOS and
|
|
422
|
+
Python 3.11–3.14, running tests, lint, type checks, build and package
|
|
423
|
+
validation. Dogfooding goal: Replico should eventually reproduce its own CI
|
|
424
|
+
failures (`replico capture` in a `if: failure()` step is the first step).
|
|
425
|
+
|
|
426
|
+
## Roadmap
|
|
427
|
+
|
|
428
|
+
* **v0.1 (this release)** — GitHub Actions (public repos), failed job/step
|
|
429
|
+
detection, Python reproduction, honest verdicts, `.replico/`, Windows /
|
|
430
|
+
Linux / macOS, offline test suite, SecretShield integration.
|
|
431
|
+
* **v0.2** — private repos everywhere (already works with a token), richer
|
|
432
|
+
log analysis, `rerun`/`status`/JSON polish, better environment
|
|
433
|
+
comparison, Docker auto-detection improvements.
|
|
434
|
+
* **v0.3** — Node.js, Go, Rust adapters, matrix/multi-job refinements,
|
|
435
|
+
deeper failure classification.
|
|
436
|
+
* **v0.4+** — GitHub Action, PR comments, reproduction artifacts, local
|
|
437
|
+
failure history, IDE integrations.
|
|
438
|
+
|
|
439
|
+
## Contributing
|
|
440
|
+
|
|
441
|
+
Issues and pull requests welcome. Before contributing, read the security
|
|
442
|
+
model (`replico/security/`) — secret safety is non-negotiable. All tests must
|
|
443
|
+
run offline; GitHub interactions are mocked.
|
|
444
|
+
|
|
445
|
+
## License
|
|
446
|
+
|
|
447
|
+
MIT — see [LICENSE](LICENSE).
|