blot-git 0.0.1__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.
- blot_git-0.0.1/.gitattributes +2 -0
- blot_git-0.0.1/.github/ISSUE_TEMPLATE/bench_case.md +19 -0
- blot_git-0.0.1/.github/ISSUE_TEMPLATE/bug_report.md +18 -0
- blot_git-0.0.1/.github/ISSUE_TEMPLATE/check_idea.md +14 -0
- blot_git-0.0.1/.github/pull_request_template.md +6 -0
- blot_git-0.0.1/.github/workflows/ci.yml +27 -0
- blot_git-0.0.1/.github/workflows/release.yml +48 -0
- blot_git-0.0.1/.gitignore +5 -0
- blot_git-0.0.1/.pre-commit-hooks.yaml +11 -0
- blot_git-0.0.1/CONTRIBUTING.md +34 -0
- blot_git-0.0.1/LICENSE +21 -0
- blot_git-0.0.1/PKG-INFO +46 -0
- blot_git-0.0.1/README.md +77 -0
- blot_git-0.0.1/assets/sora/blue/alert.svg +1 -0
- blot_git-0.0.1/assets/sora/blue/faces.svg +1 -0
- blot_git-0.0.1/assets/sora/blue/happy.svg +1 -0
- blot_git-0.0.1/assets/sora/blue/idle.svg +1 -0
- blot_git-0.0.1/assets/sora/blue/sad.svg +1 -0
- blot_git-0.0.1/assets/sora/blue/sleepy.svg +1 -0
- blot_git-0.0.1/assets/sora/blue/walk.svg +1 -0
- blot_git-0.0.1/assets/sora/blue/worried.svg +1 -0
- blot_git-0.0.1/assets/sora/build_sprites.py +150 -0
- blot_git-0.0.1/assets/sora/mustard/alert.svg +1 -0
- blot_git-0.0.1/assets/sora/mustard/faces.svg +1 -0
- blot_git-0.0.1/assets/sora/mustard/happy.svg +1 -0
- blot_git-0.0.1/assets/sora/mustard/idle.svg +1 -0
- blot_git-0.0.1/assets/sora/mustard/sad.svg +1 -0
- blot_git-0.0.1/assets/sora/mustard/sleepy.svg +1 -0
- blot_git-0.0.1/assets/sora/mustard/walk.svg +1 -0
- blot_git-0.0.1/assets/sora/mustard/worried.svg +1 -0
- blot_git-0.0.1/assets/sora/orange/alert.svg +1 -0
- blot_git-0.0.1/assets/sora/orange/faces.svg +1 -0
- blot_git-0.0.1/assets/sora/orange/happy.svg +1 -0
- blot_git-0.0.1/assets/sora/orange/idle.svg +1 -0
- blot_git-0.0.1/assets/sora/orange/sad.svg +1 -0
- blot_git-0.0.1/assets/sora/orange/sleepy.svg +1 -0
- blot_git-0.0.1/assets/sora/orange/walk.svg +1 -0
- blot_git-0.0.1/assets/sora/orange/worried.svg +1 -0
- blot_git-0.0.1/assets/sora/sage/alert.svg +1 -0
- blot_git-0.0.1/assets/sora/sage/faces.svg +1 -0
- blot_git-0.0.1/assets/sora/sage/happy.svg +1 -0
- blot_git-0.0.1/assets/sora/sage/idle.svg +1 -0
- blot_git-0.0.1/assets/sora/sage/sad.svg +1 -0
- blot_git-0.0.1/assets/sora/sage/sleepy.svg +1 -0
- blot_git-0.0.1/assets/sora/sage/walk.svg +1 -0
- blot_git-0.0.1/assets/sora/sage/worried.svg +1 -0
- blot_git-0.0.1/bench/README.md +19 -0
- blot_git-0.0.1/bench/cases.json +17 -0
- blot_git-0.0.1/docs/EXPLAINER.md +86 -0
- blot_git-0.0.1/docs/PRD.md +201 -0
- blot_git-0.0.1/docs/PYPI.md +22 -0
- blot_git-0.0.1/docs/competitors.md +39 -0
- blot_git-0.0.1/pyproject.toml +41 -0
- blot_git-0.0.1/src/blot/__init__.py +3 -0
- blot_git-0.0.1/src/blot/__main__.py +3 -0
- blot_git-0.0.1/src/blot/checks/__init__.py +18 -0
- blot_git-0.0.1/src/blot/checks/base.py +31 -0
- blot_git-0.0.1/src/blot/checks/cochange.py +24 -0
- blot_git-0.0.1/src/blot/checks/readded_bug.py +23 -0
- blot_git-0.0.1/src/blot/checks/removed_fix.py +22 -0
- blot_git-0.0.1/src/blot/classify.py +88 -0
- blot_git-0.0.1/src/blot/cli.py +108 -0
- blot_git-0.0.1/src/blot/config.py +71 -0
- blot_git-0.0.1/src/blot/diff.py +68 -0
- blot_git-0.0.1/src/blot/git.py +17 -0
- blot_git-0.0.1/src/blot/hooks.py +77 -0
- blot_git-0.0.1/src/blot/index.py +144 -0
- blot_git-0.0.1/src/blot/report.py +24 -0
- blot_git-0.0.1/src/blot/store.py +163 -0
- blot_git-0.0.1/tests/conftest.py +36 -0
- blot_git-0.0.1/tests/test_classify.py +58 -0
- blot_git-0.0.1/tests/test_cli.py +26 -0
- blot_git-0.0.1/tests/test_config.py +34 -0
- blot_git-0.0.1/tests/test_diff.py +39 -0
- blot_git-0.0.1/tests/test_hooks.py +54 -0
- blot_git-0.0.1/tests/test_index.py +78 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bench case
|
|
3
|
+
about: Suggest a real change Blot should (or should not) warn about
|
|
4
|
+
labels: bench, good first issue
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Repo and commit**
|
|
8
|
+
<!-- e.g. https://github.com/pallets/flask at d086db85 -->
|
|
9
|
+
|
|
10
|
+
**The change**
|
|
11
|
+
<!-- Which file and lines, and what the edit does. -->
|
|
12
|
+
|
|
13
|
+
**Expected result**
|
|
14
|
+
- Check: `removed-fix` / `readded-bug` / `forgot-related-file`
|
|
15
|
+
- Should Blot warn? yes / no
|
|
16
|
+
- Commit the warning should cite:
|
|
17
|
+
|
|
18
|
+
**Why**
|
|
19
|
+
<!-- Link the PR or issue that explains the original code. -->
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug or false alarm
|
|
3
|
+
about: Blot crashed, was slow, or warned about something it shouldn't have
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**What happened**
|
|
8
|
+
<!-- Paste Blot's output. For a false alarm, say why the warning was wrong. -->
|
|
9
|
+
|
|
10
|
+
**What you expected**
|
|
11
|
+
|
|
12
|
+
**Repo / commit (if public)**
|
|
13
|
+
<!-- A public repo + commit lets us turn this into a bench case. -->
|
|
14
|
+
|
|
15
|
+
**Environment**
|
|
16
|
+
- OS:
|
|
17
|
+
- Python version:
|
|
18
|
+
- `blot --version`:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Check idea
|
|
3
|
+
about: Propose a new history-aware check
|
|
4
|
+
labels: enhancement
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**The mistake it catches**
|
|
8
|
+
<!-- One sentence, e.g. "Changing a config default that a past incident set on purpose." -->
|
|
9
|
+
|
|
10
|
+
**How history reveals it**
|
|
11
|
+
<!-- What in git log / blame / PRs / issues would signal this? -->
|
|
12
|
+
|
|
13
|
+
**How it stays quiet**
|
|
14
|
+
<!-- When should it NOT warn? False alarms are the main risk. -->
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: ${{ matrix.os }} / Python ${{ matrix.python }}
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
16
|
+
python: ["3.10", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python }}
|
|
22
|
+
- name: Install
|
|
23
|
+
run: python -m pip install -e ".[dev]"
|
|
24
|
+
- name: CLI smoke test
|
|
25
|
+
run: blot --help
|
|
26
|
+
- name: Tests
|
|
27
|
+
run: python -m pytest -q
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing a GitHub release (tag vX.Y.Z) builds Blot and uploads it to PyPI.
|
|
4
|
+
# Uses PyPI trusted publishing: no API token is stored anywhere.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.13"
|
|
18
|
+
- name: Check tag matches package version
|
|
19
|
+
run: |
|
|
20
|
+
version=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
21
|
+
if [ "v$version" != "${GITHUB_REF_NAME}" ]; then
|
|
22
|
+
echo "::error::Tag ${GITHUB_REF_NAME} does not match pyproject version v$version"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
- name: Build
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install build twine
|
|
28
|
+
python -m build
|
|
29
|
+
python -m twine check dist/*
|
|
30
|
+
- uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: dist
|
|
33
|
+
path: dist/
|
|
34
|
+
|
|
35
|
+
publish:
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment:
|
|
39
|
+
name: pypi
|
|
40
|
+
url: https://pypi.org/p/blot-git
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/download-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
|
48
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Lets users of the pre-commit framework add blot with:
|
|
2
|
+
# - repo: https://github.com/Sym-jay/Blot
|
|
3
|
+
# rev: v0.1.0
|
|
4
|
+
# hooks: [{id: blot}]
|
|
5
|
+
- id: blot
|
|
6
|
+
name: blot (history-aware checks)
|
|
7
|
+
entry: blot check --staged
|
|
8
|
+
language: python
|
|
9
|
+
pass_filenames: false
|
|
10
|
+
always_run: true
|
|
11
|
+
stages: [pre-commit]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Contributing to Blot
|
|
2
|
+
|
|
3
|
+
Thanks for helping! There are three easy ways in.
|
|
4
|
+
|
|
5
|
+
## 1. Add a benchmark case (no deep code knowledge needed)
|
|
6
|
+
`bench/cases.json` is a list of real changes from public repos, each with the warning blot
|
|
7
|
+
*should* (or should *not*) produce. Each new case is its own PR. See `bench/README.md`.
|
|
8
|
+
|
|
9
|
+
## 2. Add or improve a check
|
|
10
|
+
Every check is one file in `src/blot/checks/` implementing `Check` from `checks/base.py`,
|
|
11
|
+
registered in `checks/__init__.py`. A new check needs:
|
|
12
|
+
- a unit test using the `make_repo` fixture in `tests/conftest.py`
|
|
13
|
+
- at least two bench cases (one that should warn, one that should stay quiet)
|
|
14
|
+
|
|
15
|
+
## 3. Improve fix detection
|
|
16
|
+
`src/blot/classify.py` decides whether a commit is a bug fix or a refactor. Better heuristics
|
|
17
|
+
(more languages' commit conventions, issue-tracker formats) are very welcome. Measure the effect
|
|
18
|
+
with `blot bench`.
|
|
19
|
+
|
|
20
|
+
## Dev setup
|
|
21
|
+
```bash
|
|
22
|
+
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
23
|
+
pip install -e ".[dev]"
|
|
24
|
+
pytest
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Ground rules
|
|
28
|
+
- Core stays **stdlib-only** and the `check` command stays **under 500 ms**.
|
|
29
|
+
- Quiet beats clever: a new warning must not raise the false-alarm rate on the bench.
|
|
30
|
+
|
|
31
|
+
## Releasing (maintainers)
|
|
32
|
+
1. Bump `version` in `pyproject.toml` and merge to `main`.
|
|
33
|
+
2. Create a GitHub release with tag `vX.Y.Z` matching that version.
|
|
34
|
+
3. The `Release` workflow builds and uploads to PyPI as `blot-git` (trusted publishing, no tokens).
|
blot_git-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shyam
|
|
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.
|
blot_git-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: blot-git
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A pre-commit check that has read your project's history: warns when a change removes a fix, re-adds a fixed bug, or forgets a file that always changes too.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Sym-jay/Blot
|
|
6
|
+
Project-URL: Issues, https://github.com/Sym-jay/Blot/issues
|
|
7
|
+
Author: Shyam
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: code-review,developer-tools,git,git-history,pre-commit
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
18
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
22
|
+
Provides-Extra: github
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Blot
|
|
26
|
+
|
|
27
|
+
**A pre-commit check that has read your project's history.**
|
|
28
|
+
|
|
29
|
+
Blot warns you *before you commit* when your change:
|
|
30
|
+
|
|
31
|
+
- **removes a fix**: deletes or edits code that was added to fix a bug
|
|
32
|
+
- **re-adds a bug**: brings back a line that a bug fix deliberately removed
|
|
33
|
+
- **forgets a related file**: changes `A` but not `B`, when the two almost always change together
|
|
34
|
+
|
|
35
|
+
It runs locally, needs no account or AI, and stays quiet unless the history gives it a real reason to speak.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install blot-git
|
|
39
|
+
cd your-repo
|
|
40
|
+
blot index # one-time, builds .git/blot.db
|
|
41
|
+
blot install-hook # adds Blot to your pre-commit hook
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> **Status:** pre-alpha. Indexing and the git hook work; the three checks arrive in the next milestone.
|
|
45
|
+
|
|
46
|
+
Meet Sora the slime, read the docs, or contribute on GitHub: https://github.com/Sym-jay/Blot
|
blot_git-0.0.1/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Blot
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Sym-jay/Blot/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
<img src="assets/sora/orange/walk.svg" width="480" alt="Sora, the Blot slime, sliding back and forth">
|
|
6
|
+
|
|
7
|
+
**A pre-commit check that has read your project's history.**
|
|
8
|
+
|
|
9
|
+
Meet **Sora**, the slime who remembers every fix.
|
|
10
|
+
|
|
11
|
+
> Don't take down a fence until you know why it was put up.
|
|
12
|
+
|
|
13
|
+
Blot warns you *before you commit* when your change:
|
|
14
|
+
|
|
15
|
+
- **removes a fix**: deletes or edits code that was added to fix a bug
|
|
16
|
+
- **re-adds a bug**: brings back a line that a bug fix deliberately removed
|
|
17
|
+
- **forgets a related file**: changes `A` but not `B`, when the two almost always change together
|
|
18
|
+
|
|
19
|
+
It runs locally, needs no account or AI, and stays quiet unless the history gives it a real reason to speak.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
$ git commit -m "simplify static route"
|
|
23
|
+
blot: 1 warning
|
|
24
|
+
! removed-fix src/flask/app.py:355
|
|
25
|
+
You're changing a line added by a bug fix:
|
|
26
|
+
8efea0cc "Break reference cycle created by default in Flask instances" (fixes #3761)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> **Status:** pre-alpha. Milestone M1 is done: `blot index` builds the history index (Flask's 3,800 commits in ~2 s) and `blot install-hook` wires up git. The three checks land in M2, so `blot check` doesn't warn yet. See the [roadmap in the PRD](docs/PRD.md#12-milestones-4-weeks-part-time).
|
|
30
|
+
|
|
31
|
+
## Meet Sora
|
|
32
|
+
|
|
33
|
+
<img src="assets/sora/orange/faces.svg" width="168" alt="Sora cycling through expressions" align="right">
|
|
34
|
+
|
|
35
|
+
Sora is Blot's slime. You'll see a different face depending on what Blot finds:
|
|
36
|
+
|
|
37
|
+
| | Face | When |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| <img src="assets/sora/orange/idle.svg" width="84"> | Idle | Checking your commit |
|
|
40
|
+
| <img src="assets/sora/orange/happy.svg" width="84"> | Happy | Nothing to worry about |
|
|
41
|
+
| <img src="assets/sora/orange/alert.svg" width="84"> | Alert | Found a fence: this line fixed a bug |
|
|
42
|
+
| <img src="assets/sora/orange/worried.svg" width="84"> | Worried | Risky change, take a look |
|
|
43
|
+
| <img src="assets/sora/orange/sleepy.svg" width="84"> | Sleepy | Indexing history |
|
|
44
|
+
| <img src="assets/sora/orange/sad.svg" width="84"> | Sad | A fix is being removed |
|
|
45
|
+
|
|
46
|
+
Sora comes in four warm palettes:
|
|
47
|
+
|
|
48
|
+
<img src="assets/sora/orange/walk.svg" width="240" alt="Orange Sora"> <img src="assets/sora/sage/walk.svg" width="240" alt="Sage Sora">
|
|
49
|
+
<img src="assets/sora/blue/walk.svg" width="240" alt="Blue Sora"> <img src="assets/sora/mustard/walk.svg" width="240" alt="Mustard Sora">
|
|
50
|
+
|
|
51
|
+
## Quickstart
|
|
52
|
+
|
|
53
|
+
Not on PyPI yet, so install from GitHub:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install git+https://github.com/Sym-jay/Blot
|
|
57
|
+
cd your-repo
|
|
58
|
+
blot index # one-time, builds .git/blot.db (refreshes incrementally after that)
|
|
59
|
+
blot install-hook # adds Blot to .git/hooks/pre-commit without touching existing hooks
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Remove it any time with `blot install-hook --uninstall`. Settings go in `[tool.blot]` in `pyproject.toml` or a `.blot.toml` file (see `src/blot/config.py`).
|
|
63
|
+
|
|
64
|
+
Or with the [pre-commit](https://pre-commit.com) framework, see `.pre-commit-hooks.yaml`.
|
|
65
|
+
|
|
66
|
+
## Docs
|
|
67
|
+
|
|
68
|
+
- [What is this? (simple explanation)](docs/EXPLAINER.md)
|
|
69
|
+
- [Product requirements](docs/PRD.md)
|
|
70
|
+
- [Competitor tests](docs/competitors.md)
|
|
71
|
+
- [Contributing](CONTRIBUTING.md)
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
76
|
+
|
|
77
|
+
Sora's body shape is based on [Animated Pixel Slime](https://rvros.itch.io/pixel-art-animated-slime) by rvros (CC0), recolored and given new expressions. Regenerate the sprites with `python assets/sora/build_sprites.py`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 20" shape-rendering="crispEdges" role="img" aria-label="Sora, alert"><rect x="4" y="0" width="1" height="1" fill="#E8A33B"/><rect x="4" y="1" width="1" height="1" fill="#E8A33B"/><rect x="4" y="2" width="1" height="1" fill="#E8A33B"/><rect x="4" y="4" width="1" height="1" fill="#E8A33B"/><rect x="10" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="11" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="12" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="13" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="14" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="15" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="16" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="7" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="8" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="9" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="10" y="7" width="1" height="1" fill="#6F86B8"/><rect x="11" y="7" width="1" height="1" fill="#6F86B8"/><rect x="12" y="7" width="1" height="1" fill="#6F86B8"/><rect x="13" y="7" width="1" height="1" fill="#6F86B8"/><rect x="14" y="7" width="1" height="1" fill="#6F86B8"/><rect x="15" y="7" width="1" height="1" fill="#6F86B8"/><rect x="16" y="7" width="1" height="1" fill="#6F86B8"/><rect x="17" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="18" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="19" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="5" y="8" width="1" height="1" fill="#B9C7E0"/><rect x="6" y="8" width="1" height="1" fill="#B9C7E0"/><rect x="7" y="8" width="1" height="1" fill="#6F86B8"/><rect x="8" y="8" width="1" height="1" fill="#6F86B8"/><rect x="9" y="8" width="1" height="1" fill="#6F86B8"/><rect x="10" y="8" width="1" height="1" fill="#3F5689"/><rect x="11" y="8" width="1" height="1" fill="#3F5689"/><rect x="12" y="8" width="1" height="1" fill="#3F5689"/><rect x="13" y="8" width="1" height="1" fill="#3F5689"/><rect x="14" y="8" width="1" height="1" fill="#3F5689"/><rect x="15" y="8" width="1" height="1" fill="#3F5689"/><rect x="16" y="8" width="1" height="1" fill="#3F5689"/><rect x="17" y="8" width="1" height="1" fill="#6F86B8"/><rect x="18" y="8" width="1" height="1" fill="#6F86B8"/><rect x="19" y="8" width="1" height="1" fill="#6F86B8"/><rect x="20" y="8" width="1" height="1" fill="#B9C7E0"/><rect x="21" y="8" width="1" height="1" fill="#B9C7E0"/><rect x="4" y="9" width="1" height="1" fill="#B9C7E0"/><rect x="5" y="9" width="1" height="1" fill="#6F86B8"/><rect x="6" y="9" width="1" height="1" fill="#6F86B8"/><rect x="7" y="9" width="1" height="1" fill="#3F5689"/><rect x="8" y="9" width="1" height="1" fill="#3F5689"/><rect x="9" y="9" width="1" height="1" fill="#3F5689"/><rect x="10" y="9" width="1" height="1" fill="#3F5689"/><rect x="11" y="9" width="1" height="1" fill="#3F5689"/><rect x="12" y="9" width="1" height="1" fill="#3F5689"/><rect x="13" y="9" width="1" height="1" fill="#3F5689"/><rect x="14" y="9" width="1" height="1" fill="#3F5689"/><rect x="15" y="9" width="1" height="1" fill="#3F5689"/><rect x="16" y="9" width="1" height="1" fill="#3F5689"/><rect x="17" y="9" width="1" height="1" fill="#3F5689"/><rect x="18" y="9" width="1" height="1" fill="#3F5689"/><rect x="19" y="9" width="1" height="1" fill="#3F5689"/><rect x="20" y="9" width="1" height="1" fill="#6F86B8"/><rect x="21" y="9" width="1" height="1" fill="#6F86B8"/><rect x="22" y="9" width="1" height="1" fill="#B9C7E0"/><rect x="3" y="10" width="1" height="1" fill="#B9C7E0"/><rect x="4" y="10" width="1" height="1" fill="#6F86B8"/><rect x="5" y="10" width="1" height="1" fill="#3F5689"/><rect x="6" y="10" width="1" height="1" fill="#3F5689"/><rect x="7" y="10" width="1" height="1" fill="#3F5689"/><rect x="8" y="10" width="1" height="1" fill="#3F5689"/><rect x="9" y="10" width="1" height="1" fill="#3F5689"/><rect x="10" y="10" width="1" height="1" fill="#3F5689"/><rect x="11" y="10" width="1" height="1" fill="#3F5689"/><rect x="12" y="10" width="1" height="1" fill="#3F5689"/><rect x="13" y="10" width="1" height="1" fill="#3F5689"/><rect x="14" y="10" width="1" height="1" fill="#3F5689"/><rect x="15" y="10" width="1" height="1" fill="#3F5689"/><rect x="16" y="10" width="1" height="1" fill="#3F5689"/><rect x="17" y="10" width="1" height="1" fill="#3F5689"/><rect x="18" y="10" width="1" height="1" fill="#3F5689"/><rect x="19" y="10" width="1" height="1" fill="#3F5689"/><rect x="20" y="10" width="1" height="1" fill="#3F5689"/><rect x="21" y="10" width="1" height="1" fill="#3F5689"/><rect x="22" y="10" width="1" height="1" fill="#6F86B8"/><rect x="23" y="10" width="1" height="1" fill="#B9C7E0"/><rect x="2" y="11" width="1" height="1" fill="#B9C7E0"/><rect x="3" y="11" width="1" height="1" fill="#6F86B8"/><rect x="4" y="11" width="1" height="1" fill="#3F5689"/><rect x="5" y="11" width="1" height="1" fill="#3F5689"/><rect x="6" y="11" width="1" height="1" fill="#FFF6E8"/><rect x="7" y="11" width="1" height="1" fill="#D9663F"/><rect x="8" y="11" width="1" height="1" fill="#25345C"/><rect x="9" y="11" width="1" height="1" fill="#3F5689"/><rect x="10" y="11" width="1" height="1" fill="#3F5689"/><rect x="11" y="11" width="1" height="1" fill="#3F5689"/><rect x="12" y="11" width="1" height="1" fill="#3F5689"/><rect x="13" y="11" width="1" height="1" fill="#3F5689"/><rect x="14" y="11" width="1" height="1" fill="#3F5689"/><rect x="15" y="11" width="1" height="1" fill="#3F5689"/><rect x="16" y="11" width="1" height="1" fill="#3F5689"/><rect x="17" y="11" width="1" height="1" fill="#3F5689"/><rect x="18" y="11" width="1" height="1" fill="#3F5689"/><rect x="19" y="11" width="1" height="1" fill="#3F5689"/><rect x="20" y="11" width="1" height="1" fill="#3F5689"/><rect x="21" y="11" width="1" height="1" fill="#3F5689"/><rect x="22" y="11" width="1" height="1" fill="#3F5689"/><rect x="23" y="11" width="1" height="1" fill="#6F86B8"/><rect x="24" y="11" width="1" height="1" fill="#B9C7E0"/><rect x="2" y="12" width="1" height="1" fill="#B9C7E0"/><rect x="3" y="12" width="1" height="1" fill="#6F86B8"/><rect x="4" y="12" width="1" height="1" fill="#3F5689"/><rect x="5" y="12" width="1" height="1" fill="#25345C"/><rect x="6" y="12" width="1" height="1" fill="#FFF6E8"/><rect x="7" y="12" width="1" height="1" fill="#D9663F"/><rect x="8" y="12" width="1" height="1" fill="#25345C"/><rect x="9" y="12" width="1" height="1" fill="#25345C"/><rect x="10" y="12" width="1" height="1" fill="#3F5689"/><rect x="11" y="12" width="1" height="1" fill="#3F5689"/><rect x="12" y="12" width="1" height="1" fill="#3F5689"/><rect x="13" y="12" width="1" height="1" fill="#3F5689"/><rect x="14" y="12" width="1" height="1" fill="#3F5689"/><rect x="15" y="12" width="1" height="1" fill="#3F5689"/><rect x="16" y="12" width="1" height="1" fill="#3F5689"/><rect x="17" y="12" width="1" height="1" fill="#3F5689"/><rect x="18" y="12" width="1" height="1" fill="#3F5689"/><rect x="19" y="12" width="1" height="1" fill="#3F5689"/><rect x="20" y="12" width="1" height="1" fill="#3F5689"/><rect x="21" y="12" width="1" height="1" fill="#3F5689"/><rect x="22" y="12" width="1" height="1" fill="#3F5689"/><rect x="23" y="12" width="1" height="1" fill="#3F5689"/><rect x="24" y="12" width="1" height="1" fill="#6F86B8"/><rect x="25" y="12" width="1" height="1" fill="#B9C7E0"/><rect x="1" y="13" width="1" height="1" fill="#B9C7E0"/><rect x="2" y="13" width="1" height="1" fill="#6F86B8"/><rect x="3" y="13" width="1" height="1" fill="#3F5689"/><rect x="4" y="13" width="1" height="1" fill="#3F5689"/><rect x="5" y="13" width="1" height="1" fill="#25345C"/><rect x="6" y="13" width="1" height="1" fill="#D9663F"/><rect x="7" y="13" width="1" height="1" fill="#D9663F"/><rect x="8" y="13" width="1" height="1" fill="#25345C"/><rect x="9" y="13" width="1" height="1" fill="#25345C"/><rect x="10" y="13" width="1" height="1" fill="#25345C"/><rect x="11" y="13" width="1" height="1" fill="#3F5689"/><rect x="12" y="13" width="1" height="1" fill="#3F5689"/><rect x="13" y="13" width="1" height="1" fill="#3F5689"/><rect x="14" y="13" width="1" height="1" fill="#3F5689"/><rect x="15" y="13" width="1" height="1" fill="#3F5689"/><rect x="16" y="13" width="1" height="1" fill="#3F5689"/><rect x="17" y="13" width="1" height="1" fill="#3F5689"/><rect x="18" y="13" width="1" height="1" fill="#3F5689"/><rect x="19" y="13" width="1" height="1" fill="#3F5689"/><rect x="20" y="13" width="1" height="1" fill="#3F5689"/><rect x="21" y="13" width="1" height="1" fill="#3F5689"/><rect x="22" y="13" width="1" height="1" fill="#3F5689"/><rect x="23" y="13" width="1" height="1" fill="#3F5689"/><rect x="24" y="13" width="1" height="1" fill="#3F5689"/><rect x="25" y="13" width="1" height="1" fill="#6F86B8"/><rect x="26" y="13" width="1" height="1" fill="#B9C7E0"/><rect x="1" y="14" width="1" height="1" fill="#B9C7E0"/><rect x="2" y="14" width="1" height="1" fill="#6F86B8"/><rect x="3" y="14" width="1" height="1" fill="#3F5689"/><rect x="4" y="14" width="1" height="1" fill="#25345C"/><rect x="5" y="14" width="1" height="1" fill="#25345C"/><rect x="6" y="14" width="1" height="1" fill="#25345C"/><rect x="7" y="14" width="1" height="1" fill="#25345C"/><rect x="8" y="14" width="1" height="1" fill="#25345C"/><rect x="9" y="14" width="1" height="1" fill="#25345C"/><rect x="10" y="14" width="1" height="1" fill="#25345C"/><rect x="11" y="14" width="1" height="1" fill="#25345C"/><rect x="12" y="14" width="1" height="1" fill="#25345C"/><rect x="13" y="14" width="1" height="1" fill="#25345C"/><rect x="14" y="14" width="1" height="1" fill="#3F5689"/><rect x="15" y="14" width="1" height="1" fill="#3F5689"/><rect x="16" y="14" width="1" height="1" fill="#3F5689"/><rect x="17" y="14" width="1" height="1" fill="#3F5689"/><rect x="18" y="14" width="1" height="1" fill="#3F5689"/><rect x="19" y="14" width="1" height="1" fill="#3F5689"/><rect x="20" y="14" width="1" height="1" fill="#3F5689"/><rect x="21" y="14" width="1" height="1" fill="#3F5689"/><rect x="22" y="14" width="1" height="1" fill="#3F5689"/><rect x="23" y="14" width="1" height="1" fill="#3F5689"/><rect x="24" y="14" width="1" height="1" fill="#3F5689"/><rect x="25" y="14" width="1" height="1" fill="#3F5689"/><rect x="26" y="14" width="1" height="1" fill="#6F86B8"/><rect x="1" y="15" width="1" height="1" fill="#6F86B8"/><rect x="2" y="15" width="1" height="1" fill="#3F5689"/><rect x="3" y="15" width="1" height="1" fill="#3F5689"/><rect x="4" y="15" width="1" height="1" fill="#25345C"/><rect x="5" y="15" width="1" height="1" fill="#25345C"/><rect x="6" y="15" width="1" height="1" fill="#25345C"/><rect x="7" y="15" width="1" height="1" fill="#25345C"/><rect x="8" y="15" width="1" height="1" fill="#25345C"/><rect x="9" y="15" width="1" height="1" fill="#25345C"/><rect x="10" y="15" width="1" height="1" fill="#25345C"/><rect x="11" y="15" width="1" height="1" fill="#25345C"/><rect x="12" y="15" width="1" height="1" fill="#25345C"/><rect x="13" y="15" width="1" height="1" fill="#25345C"/><rect x="14" y="15" width="1" height="1" fill="#25345C"/><rect x="15" y="15" width="1" height="1" fill="#25345C"/><rect x="16" y="15" width="1" height="1" fill="#25345C"/><rect x="17" y="15" width="1" height="1" fill="#25345C"/><rect x="18" y="15" width="1" height="1" fill="#25345C"/><rect x="19" y="15" width="1" height="1" fill="#25345C"/><rect x="20" y="15" width="1" height="1" fill="#25345C"/><rect x="21" y="15" width="1" height="1" fill="#3F5689"/><rect x="22" y="15" width="1" height="1" fill="#3F5689"/><rect x="23" y="15" width="1" height="1" fill="#3F5689"/><rect x="24" y="15" width="1" height="1" fill="#3F5689"/><rect x="25" y="15" width="1" height="1" fill="#3F5689"/><rect x="26" y="15" width="1" height="1" fill="#6F86B8"/><rect x="0" y="16" width="1" height="1" fill="#6F86B8"/><rect x="1" y="16" width="1" height="1" fill="#6F86B8"/><rect x="2" y="16" width="1" height="1" fill="#3F5689"/><rect x="3" y="16" width="1" height="1" fill="#3F5689"/><rect x="4" y="16" width="1" height="1" fill="#3F5689"/><rect x="5" y="16" width="1" height="1" fill="#25345C"/><rect x="6" y="16" width="1" height="1" fill="#25345C"/><rect x="7" y="16" width="1" height="1" fill="#25345C"/><rect x="8" y="16" width="1" height="1" fill="#25345C"/><rect x="9" y="16" width="1" height="1" fill="#25345C"/><rect x="10" y="16" width="1" height="1" fill="#25345C"/><rect x="11" y="16" width="1" height="1" fill="#25345C"/><rect x="12" y="16" width="1" height="1" fill="#25345C"/><rect x="13" y="16" width="1" height="1" fill="#25345C"/><rect x="14" y="16" width="1" height="1" fill="#25345C"/><rect x="15" y="16" width="1" height="1" fill="#25345C"/><rect x="16" y="16" width="1" height="1" fill="#25345C"/><rect x="17" y="16" width="1" height="1" fill="#25345C"/><rect x="18" y="16" width="1" height="1" fill="#25345C"/><rect x="19" y="16" width="1" height="1" fill="#25345C"/><rect x="20" y="16" width="1" height="1" fill="#25345C"/><rect x="21" y="16" width="1" height="1" fill="#25345C"/><rect x="22" y="16" width="1" height="1" fill="#25345C"/><rect x="23" y="16" width="1" height="1" fill="#3F5689"/><rect x="24" y="16" width="1" height="1" fill="#3F5689"/><rect x="25" y="16" width="1" height="1" fill="#3F5689"/><rect x="26" y="16" width="1" height="1" fill="#6F86B8"/><rect x="27" y="16" width="1" height="1" fill="#6F86B8"/><rect x="0" y="17" width="1" height="1" fill="#6F86B8"/><rect x="1" y="17" width="1" height="1" fill="#6F86B8"/><rect x="2" y="17" width="1" height="1" fill="#6F86B8"/><rect x="3" y="17" width="1" height="1" fill="#3F5689"/><rect x="4" y="17" width="1" height="1" fill="#3F5689"/><rect x="5" y="17" width="1" height="1" fill="#3F5689"/><rect x="6" y="17" width="1" height="1" fill="#3F5689"/><rect x="7" y="17" width="1" height="1" fill="#3F5689"/><rect x="8" y="17" width="1" height="1" fill="#3F5689"/><rect x="9" y="17" width="1" height="1" fill="#3F5689"/><rect x="10" y="17" width="1" height="1" fill="#3F5689"/><rect x="11" y="17" width="1" height="1" fill="#3F5689"/><rect x="12" y="17" width="1" height="1" fill="#3F5689"/><rect x="13" y="17" width="1" height="1" fill="#3F5689"/><rect x="14" y="17" width="1" height="1" fill="#3F5689"/><rect x="15" y="17" width="1" height="1" fill="#3F5689"/><rect x="16" y="17" width="1" height="1" fill="#3F5689"/><rect x="17" y="17" width="1" height="1" fill="#3F5689"/><rect x="18" y="17" width="1" height="1" fill="#3F5689"/><rect x="19" y="17" width="1" height="1" fill="#3F5689"/><rect x="20" y="17" width="1" height="1" fill="#3F5689"/><rect x="21" y="17" width="1" height="1" fill="#3F5689"/><rect x="22" y="17" width="1" height="1" fill="#3F5689"/><rect x="23" y="17" width="1" height="1" fill="#3F5689"/><rect x="24" y="17" width="1" height="1" fill="#3F5689"/><rect x="25" y="17" width="1" height="1" fill="#6F86B8"/><rect x="26" y="17" width="1" height="1" fill="#6F86B8"/><rect x="27" y="17" width="1" height="1" fill="#6F86B8"/><rect x="0" y="18" width="1" height="1" fill="#6F86B8"/><rect x="1" y="18" width="1" height="1" fill="#6F86B8"/><rect x="2" y="18" width="1" height="1" fill="#6F86B8"/><rect x="3" y="18" width="1" height="1" fill="#6F86B8"/><rect x="4" y="18" width="1" height="1" fill="#6F86B8"/><rect x="5" y="18" width="1" height="1" fill="#6F86B8"/><rect x="6" y="18" width="1" height="1" fill="#6F86B8"/><rect x="7" y="18" width="1" height="1" fill="#6F86B8"/><rect x="8" y="18" width="1" height="1" fill="#6F86B8"/><rect x="9" y="18" width="1" height="1" fill="#6F86B8"/><rect x="10" y="18" width="1" height="1" fill="#6F86B8"/><rect x="11" y="18" width="1" height="1" fill="#6F86B8"/><rect x="12" y="18" width="1" height="1" fill="#6F86B8"/><rect x="13" y="18" width="1" height="1" fill="#6F86B8"/><rect x="14" y="18" width="1" height="1" fill="#6F86B8"/><rect x="15" y="18" width="1" height="1" fill="#6F86B8"/><rect x="16" y="18" width="1" height="1" fill="#6F86B8"/><rect x="17" y="18" width="1" height="1" fill="#6F86B8"/><rect x="18" y="18" width="1" height="1" fill="#6F86B8"/><rect x="19" y="18" width="1" height="1" fill="#6F86B8"/><rect x="20" y="18" width="1" height="1" fill="#6F86B8"/><rect x="21" y="18" width="1" height="1" fill="#6F86B8"/><rect x="22" y="18" width="1" height="1" fill="#6F86B8"/><rect x="23" y="18" width="1" height="1" fill="#6F86B8"/><rect x="24" y="18" width="1" height="1" fill="#6F86B8"/><rect x="25" y="18" width="1" height="1" fill="#6F86B8"/><rect x="26" y="18" width="1" height="1" fill="#6F86B8"/><rect x="27" y="18" width="1" height="1" fill="#6F86B8"/><rect x="1" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="2" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="3" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="4" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="5" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="6" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="7" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="8" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="9" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="10" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="11" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="12" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="13" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="14" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="15" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="16" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="17" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="18" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="19" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="20" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="21" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="22" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="23" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="24" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="25" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="26" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/></svg>
|