Deterministic semantic review and CI for Excel workbook changes
Project description
Tabulint
Deterministic spreadsheet change review and CI for Excel workbooks.
Tabulint is an open-source spreadsheet review and CI tool that detects risky Excel changes, formula overwrites, structural modifications, dependency impacts, and business-rule violations before they reach production.
Tabulint 是一个开源的 Excel 变更审查与持续集成工具,用于在文件投入使用前发现公式覆盖、 结构变化、依赖影响和业务规则违规。
Install the current public source and compare two workbooks:
python -m pip install "tabulint @ git+https://github.com/huangyikai05/Tabulint.git@main"
tabulint compare before.xlsx after.xlsx --html report.html
The generated risky demo produces this real result:
Tabulint Review
Risk score: 100/100
Risk level: CRITICAL
Changed cells: 5
Changed formulas: 3
Formula overwrites: 1
Hidden sheets added: 1
External links added: 1
Rules: 4 failed, 1 warning, 1 skipped
Exit code: 1
The exact CLI summary is:
Risk 100/100 (CRITICAL); 5 changed cells; 1 formula overwrites; 4 failed, 1 warning, and 0 errored rules.
Run the safe demo · Run the risky demo · Record the 30–60 second demo · Read the v0.1.0 release notes
Install published releases from PyPI. To test unreleased changes, install the public
mainbranch or clone the repository as described below.
How it works
flowchart LR
B["before.xlsx"] --> S["Tabulint"]
A["after.xlsx"] --> S
P["optional tabulint.yml"] --> S
S --> D["Semantic diff"]
S --> F["Formula overwrite and pattern detection"]
S --> G["Bounded dependency impact"]
S --> R["Deterministic business rules"]
D --> E["Typed review evidence"]
F --> E
G --> E
R --> E
E --> J["JSON"]
E --> H["Offline HTML"]
E --> C["CLI / CI exit code"]
Programs produce every finding, risk contribution, rule result, and exit code. AI-generated text is never used as validation evidence or as a merge decision.
Why Tabulint
An Excel workbook is a ZIP-based document, not a line-oriented source file. A normal Git diff cannot explain that a formula became a fixed value, a total range became shorter, a hidden sheet appeared, or a critical downstream cell may be affected.
Tabulint converts workbook facts into stable, reviewable evidence:
- semantic cell and workbook changes instead of raw XML noise;
- explicit formula-overwrite, range-reduction, and copied-pattern findings;
- bounded upstream and downstream dependency evidence;
- project-specific YAML rules;
- explainable, capped risk contributions;
- one typed result shared by the CLI, reports, web page, Python API, and CI.
Tabulint is a review aid. It does not prove that a workbook is correct and does not replace professional financial, security, legal, or operational review.
Key features
- Bounded non-executing parser: reads
.xlsxand.xlsmfacts while enforcing archive, cell, merge, formula-expansion, and graph limits. - Semantic comparison: classifies values, text, blanks, formulas, types, styles, names, tables, validations, merges, hidden rows/columns, sheet visibility, VBA presence, and external link indicators.
- Formula review: detects formula replacement, range reduction, additions, and broken row/column copy patterns without calculating formulas.
- Dependency impact: reports direct and bounded downstream relationships, paths, cycles, critical-cell impact, and truncation state.
- Strict YAML policy: validates nine built-in deterministic rule types and rejects duplicate keys, unknown fields, and invalid values.
- Explainable risk: deduplicates named risk contributions and caps the total at 100.
- Portable reports: writes structured JSON and self-contained, autoescaped offline HTML.
- Multiple adapters: Typer CLI, source-checkout Streamlit page, public Python API, and a read-only GitHub Action use the same review service.
Quick start
Clone the repository to generate both reproducible demo cases:
git clone https://github.com/huangyikai05/Tabulint.git
cd Tabulint
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .
python examples/generate_demo_workbooks.py
tabulint compare examples/generated/before.xlsx examples/generated/after_safe.xlsx \
--config examples/tabulint.example.yml \
--json build/safe-review.json \
--html build/safe-review.html
The safe comparison exits 0 with 2/100 LOW. The risky comparison below
intentionally exits 1; that exit is the expected gate result, not a crash:
tabulint compare examples/generated/before.xlsx examples/generated/after_risky.xlsx \
--config examples/tabulint.example.yml \
--json build/risky-review.json \
--html build/risky-review.html
PowerShell setup:
py -3.11 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e .
Command Prompt setup:
py -3.11 -m venv .venv
.\.venv\Scripts\activate.bat
python -m pip install --upgrade pip
python -m pip install -e .
If PowerShell blocks script activation, either use Command Prompt or run the virtual
environment interpreter directly, for example
..venv\Scripts\python.exe -m pip install -e ..
Installation
Requirements:
- Python 3.11 or newer (release CI currently tests Python 3.11 and 3.12);
- a platform supported by Python and openpyxl;
- local access to the workbooks being reviewed.
Install a published release from PyPI:
python -m pip install tabulint
To test unreleased changes from the public repository:
python -m pip install "tabulint @ git+https://github.com/huangyikai05/Tabulint.git@main"
From a clone:
python -m pip install .
Optional local web interface:
python -m pip install ".[web]"
Contributor environment:
python -m pip install -e ".[dev,web]"
Verify the installed entry point with tabulint version,
tabulint --help, and tabulint compare --help.
The canonical package installation command is python -m pip install tabulint.
CLI usage
Compare two workbooks
tabulint compare BEFORE.xlsx AFTER.xlsx \
[--config tabulint.yml] \
[--json report.json] \
[--html report.html] \
[--fail-on LOW|MEDIUM|HIGH|CRITICAL]
The configuration controls the default blocking level. --fail-on overrides it.
A failed or errored rule always blocks.
Inspect one workbook
tabulint inspect workbook.xlsx
tabulint inspect workbook.xlsm --json build/snapshot.json
Inspection records workbook facts. It does not recalculate formulas or run VBA.
Validate a policy
tabulint rules validate tabulint.yml
tabulint version
Exit codes
| Code | Meaning |
|---|---|
| 0 | Review completed without a configured blocking condition. |
| 1 | Risk or a finding reached the blocking threshold, or a rule failed/errored. Reports are still written. |
| 2 | Workbook, path, output, or policy input was invalid. |
| 3 | An unexpected internal error occurred. Use --debug locally for a traceback. |
Web interface
Install the web extra and start Streamlit from the repository root:
python -m pip install -e ".[web]"
streamlit run web/app.py
The page accepts before/after workbooks and an optional policy, then provides summary tables and JSON/HTML downloads. Each upload is limited to 25 MiB. Files are copied to a private temporary directory for the review and removed afterward. The web page contains no alternate verdict logic.
YAML rules
Configuration is optional. It is loaded with a duplicate-key-rejecting safe YAML loader and validated by strict Pydantic models.
rules:
- name: Cash-flow formulas must remain formulas
type: formula_required
range: "现金流!B5:B20"
severity: high
- name: Forecast edits stay in the approved area
type: allowed_change_range
ranges:
- "预测!D5:H30"
severity: high
- name: Do not add external links
type: no_external_links
severity: critical
- name: Do not add hidden sheets
type: no_new_hidden_sheets
severity: high
critical_cells:
- "现金流!B22"
block_risk_level: HIGH
max_dependency_depth: 10
max_dependency_nodes: 10000
A numeric rule that targets a formula without a usable cached value is reported as
SKIPPED; Tabulint never invents the value or pretends to emulate Excel.
See the complete demo policy.
GitHub Actions
The repository provides:
- a normal
CIworkflow for tests, Ruff, Mypy, and compilation on supported Python versions; - a composite Tabulint action and a read-only workbook pull-request gate;
- a release workflow using PyPI Trusted Publishing.
The required workbook gate deliberately loads its implementation, helper, and policy from a
separate trusted base checkout. Pull-request workbooks are treated as Git-object input; PR code
is not imported or executed. Added or deleted workbooks are UNREVIEWABLE and block
by default because a semantic comparison needs both sides. The action supports an explicit
advisory mode for unpaired files.
The PyPI release workflow requires a protected pypi GitHub Environment and a
matching PyPI Trusted Publisher. It exchanges GitHub's short-lived OIDC identity for upload
authority, so no PyPI token belongs in the repository.
See the composite action and the workbook workflow.
Action artifact privacy
Tabulint itself does not upload workbooks or telemetry. A GitHub Actions workflow may, however, upload generated JSON and HTML reports as artifacts. Those reports can contain derived workbook content such as cell addresses, formulas, before/after values, sheet names, external-link names, and rule evidence. Repository owners should treat report artifacts according to the source workbook's sensitivity, restrict repository and artifact access, choose an appropriate retention period, and avoid public CI for confidential workbooks.
Example reports
The repository stores generators rather than proprietary workbook fixtures. Run
python examples/generate_demo_workbooks.py to create:
| Case | Risk | Evidence summary | Exit |
|---|---|---|---|
| Safe update | 2/100 LOW | 1 changed cell; no formula, structure, link, or blocking-rule finding | 0 |
| Risky update | 100/100 CRITICAL | 5 changed cells; 3 formula changes; 1 overwrite; 1 hidden sheet; 1 external link; 4 failed rules | 1 |
The commands write machine-readable JSON and a self-contained HTML report to build/.
Generated reports are intentionally not presented as static golden verdicts; regenerate them
from the synthetic source script and current deterministic implementation.
Python API
The supported top-level helper returns the same typed ReviewResult used by every
interface:
from tabulint import compare_workbooks
result = compare_workbooks(
before_path="before.xlsx",
after_path="after.xlsx",
config_path="tabulint.yml",
)
print(result.summary.risk_score)
print(result.summary.risk_level.value)
for finding in result.formula_changes:
print(finding.location, finding.change_type)
The helper performs deterministic review only. Callers should use typed fields and enum values, not parse human-readable descriptions to implement a gate.
Security model
- VBA presence is detected; VBA is never executed, decompiled, verified, or removed.
- Formulas are read as text; they are never executed or recalculated.
- External workbook links are recorded as evidence; they are never opened or fetched.
- Workbook-controlled shell text, embedded objects, and commands are never executed.
- YAML uses a safe loader with strict validation.
- OOXML paths and resource budgets are validated before materialization.
- HTML is autoescaped and contains no online CDN dependency.
- Core findings and gate decisions come from deterministic program logic, never a language model.
Read SECURITY.md before processing untrusted inputs and use its private reporting process for vulnerabilities.
Privacy
Local CLI, Python, and Streamlit reviews require no account, API key, database, or cloud service. Tabulint does not upload workbook contents or emit telemetry. Files remain on the machine unless the user or an integration explicitly copies or uploads an output. In particular, review the Action artifact privacy boundary before enabling CI for sensitive workbooks.
Known limitations
- Tabulint cannot fully simulate Excel's calculation engine. Cached values may be absent or stale, and Tabulint never fabricates them.
- Formula parsing is not a complete Excel grammar. Some complex formulas receive reference-level
analysis only; dynamic references such as
INDIRECTcannot be reliably resolved. - VBA is detected only; macro behavior is not analyzed.
- Charts, slicers, Power Query, data models, embedded objects, digital signatures, conditional-formatting semantics, and every style detail are not fully compared.
- ZIP64 and multi-disk OOXML packages are outside the current security profile.
- External-link detection does not guarantee that every connection type is found.
- Added/deleted workbook pairs are unreviewable by semantic diff and fail closed in the default CI gate.
- A low risk score does not prove correctness, and a high score does not prove malicious intent. The score prioritizes human review; it is not a probability or professional audit conclusion.
Roadmap
See ROADMAP.md for the public, non-date-bound roadmap. Proposed post-MVP work includes formula graph visualization, better formula parsing, optional PR comments, deterministic rule plugins, performance work, and carefully bounded evidence-explanation integrations. Roadmap items are proposals, not release promises.
Contributing
Read CONTRIBUTING.md and AGENTS.md. Core changes must include focused tests and preserve the rule that programs validate while AI may only explain evidence. Use synthetic minimal workbooks; never submit confidential, proprietary, or source-unknown Excel files.
License
Tabulint is available under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tabulint-0.1.0.tar.gz.
File metadata
- Download URL: tabulint-0.1.0.tar.gz
- Upload date:
- Size: 124.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b27c466c61d2b3b632f1fcfc63f4f5b057b931f9850cdfcd5435a176ac657c9e
|
|
| MD5 |
7f33b4b6eba1fc0f450c28fbd4078685
|
|
| BLAKE2b-256 |
d4587c8163aed17c071c4f685a6ef7a78ef0363ec5dd0a2f362c56244aaef6ea
|
Provenance
The following attestation bundles were made for tabulint-0.1.0.tar.gz:
Publisher:
release.yml on huangyikai05/Tabulint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tabulint-0.1.0.tar.gz -
Subject digest:
b27c466c61d2b3b632f1fcfc63f4f5b057b931f9850cdfcd5435a176ac657c9e - Sigstore transparency entry: 2189777894
- Sigstore integration time:
-
Permalink:
huangyikai05/Tabulint@069afb9e78b40296f3b39b17b389827cba19f97b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/huangyikai05
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@069afb9e78b40296f3b39b17b389827cba19f97b -
Trigger Event:
release
-
Statement type:
File details
Details for the file tabulint-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tabulint-0.1.0-py3-none-any.whl
- Upload date:
- Size: 66.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
012543b027cd0dd7fd61b66ad3e349733c3d25f4fa3af3a7b2697a33d872c20c
|
|
| MD5 |
5e84f0cc5b66f6754e9588fc3a91861f
|
|
| BLAKE2b-256 |
574ada04d4059a255fb21aacb0d547b7c4bcddfafa5ed58103ac96ef4787c687
|
Provenance
The following attestation bundles were made for tabulint-0.1.0-py3-none-any.whl:
Publisher:
release.yml on huangyikai05/Tabulint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tabulint-0.1.0-py3-none-any.whl -
Subject digest:
012543b027cd0dd7fd61b66ad3e349733c3d25f4fa3af3a7b2697a33d872c20c - Sigstore transparency entry: 2189777916
- Sigstore integration time:
-
Permalink:
huangyikai05/Tabulint@069afb9e78b40296f3b39b17b389827cba19f97b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/huangyikai05
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@069afb9e78b40296f3b39b17b389827cba19f97b -
Trigger Event:
release
-
Statement type: