linceo 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.
- linceo-0.1.0/.devsecops/config.toml +25 -0
- linceo-0.1.0/.dockerignore +38 -0
- linceo-0.1.0/.github/dependabot.yml +15 -0
- linceo-0.1.0/.github/workflows/ci.yml +125 -0
- linceo-0.1.0/.github/workflows/release.yml +117 -0
- linceo-0.1.0/.gitignore +33 -0
- linceo-0.1.0/.gitleaks.toml +22 -0
- linceo-0.1.0/.pre-commit-config.yaml +54 -0
- linceo-0.1.0/AGENTS.md +106 -0
- linceo-0.1.0/CONTRIBUTING.md +53 -0
- linceo-0.1.0/Dockerfile +253 -0
- linceo-0.1.0/LICENSE +202 -0
- linceo-0.1.0/NOTICE +12 -0
- linceo-0.1.0/PKG-INFO +125 -0
- linceo-0.1.0/README.md +100 -0
- linceo-0.1.0/SECURITY.md +31 -0
- linceo-0.1.0/azure-pipelines/examples/two-stage-scan.yml +88 -0
- linceo-0.1.0/azure-pipelines/templates/linceo-scan.yml +168 -0
- linceo-0.1.0/docs/ADOPTION.md +200 -0
- linceo-0.1.0/docs/RELEASING.md +181 -0
- linceo-0.1.0/docs/adr/ADR-000-arquitectura-base-y-alcance-v0.1.md +1897 -0
- linceo-0.1.0/pyproject.toml +168 -0
- linceo-0.1.0/src/linceo/__init__.py +13 -0
- linceo-0.1.0/src/linceo/adapters/__init__.py +13 -0
- linceo-0.1.0/src/linceo/adapters/gitleaks.py +251 -0
- linceo-0.1.0/src/linceo/adapters/subprocess_executor.py +69 -0
- linceo-0.1.0/src/linceo/adapters/trivy.py +505 -0
- linceo-0.1.0/src/linceo/cli/__init__.py +7 -0
- linceo-0.1.0/src/linceo/cli/doctor.py +243 -0
- linceo-0.1.0/src/linceo/cli/main.py +57 -0
- linceo-0.1.0/src/linceo/cli/scan.py +419 -0
- linceo-0.1.0/src/linceo/core/__init__.py +144 -0
- linceo-0.1.0/src/linceo/core/config.py +544 -0
- linceo-0.1.0/src/linceo/core/context.py +62 -0
- linceo-0.1.0/src/linceo/core/dedup.py +28 -0
- linceo-0.1.0/src/linceo/core/engine.py +278 -0
- linceo-0.1.0/src/linceo/core/execution.py +107 -0
- linceo-0.1.0/src/linceo/core/exit_codes.py +40 -0
- linceo-0.1.0/src/linceo/core/findings.py +133 -0
- linceo-0.1.0/src/linceo/core/fingerprint.py +103 -0
- linceo-0.1.0/src/linceo/core/gate.py +58 -0
- linceo-0.1.0/src/linceo/core/normalization.py +148 -0
- linceo-0.1.0/src/linceo/core/policy.py +496 -0
- linceo-0.1.0/src/linceo/core/ports.py +225 -0
- linceo-0.1.0/src/linceo/core/registry.py +86 -0
- linceo-0.1.0/src/linceo/core/report_schema.py +73 -0
- linceo-0.1.0/src/linceo/core/reporters.py +288 -0
- linceo-0.1.0/src/linceo/core/results.py +107 -0
- linceo-0.1.0/src/linceo/core/sarif.py +244 -0
- linceo-0.1.0/src/linceo/core/severity.py +88 -0
- linceo-0.1.0/src/linceo/core/table.py +131 -0
- linceo-0.1.0/src/linceo/core/tool_config.py +443 -0
- linceo-0.1.0/src/linceo/core/version_range.py +102 -0
- linceo-0.1.0/src/linceo/data/__init__.py +13 -0
- linceo-0.1.0/src/linceo/data/sarif-schema-2.1.0.json +3389 -0
- linceo-0.1.0/src/linceo/providers/__init__.py +27 -0
- linceo-0.1.0/src/linceo/providers/azure_devops.py +182 -0
- linceo-0.1.0/src/linceo/providers/detection.py +41 -0
- linceo-0.1.0/src/linceo/providers/environment.py +24 -0
- linceo-0.1.0/src/linceo/providers/local.py +115 -0
- linceo-0.1.0/src/linceo/py.typed +0 -0
- linceo-0.1.0/src/linceo/testing/__init__.py +13 -0
- linceo-0.1.0/src/linceo/testing/fakes.py +76 -0
- linceo-0.1.0/tests/integration/README.md +30 -0
- linceo-0.1.0/tests/integration/test_gitleaks_integration.py +195 -0
- linceo-0.1.0/tests/integration/test_trivy_integration.py +192 -0
- linceo-0.1.0/tests/unit/fixtures/gitleaks/README.md +30 -0
- linceo-0.1.0/tests/unit/fixtures/gitleaks/empty.json +1 -0
- linceo-0.1.0/tests/unit/fixtures/gitleaks/malformed_truncated.json +5 -0
- linceo-0.1.0/tests/unit/fixtures/gitleaks/many_findings.json +62 -0
- linceo-0.1.0/tests/unit/fixtures/gitleaks/missing_field.json +8 -0
- linceo-0.1.0/tests/unit/fixtures/gitleaks/one_finding.json +22 -0
- linceo-0.1.0/tests/unit/fixtures/trivy/README.md +56 -0
- linceo-0.1.0/tests/unit/fixtures/trivy/empty.json +12 -0
- linceo-0.1.0/tests/unit/fixtures/trivy/malformed_truncated.json +10 -0
- linceo-0.1.0/tests/unit/fixtures/trivy/many_findings.json +94 -0
- linceo-0.1.0/tests/unit/fixtures/trivy/missing_field.json +23 -0
- linceo-0.1.0/tests/unit/fixtures/trivy/one_finding.json +38 -0
- linceo-0.1.0/tests/unit/test_azure_devops_provider.py +157 -0
- linceo-0.1.0/tests/unit/test_cli.py +41 -0
- linceo-0.1.0/tests/unit/test_cli_doctor.py +79 -0
- linceo-0.1.0/tests/unit/test_cli_scan.py +520 -0
- linceo-0.1.0/tests/unit/test_cli_scan_platform.py +182 -0
- linceo-0.1.0/tests/unit/test_cli_scan_sca.py +312 -0
- linceo-0.1.0/tests/unit/test_config.py +670 -0
- linceo-0.1.0/tests/unit/test_dedup.py +52 -0
- linceo-0.1.0/tests/unit/test_doctor.py +191 -0
- linceo-0.1.0/tests/unit/test_engine.py +487 -0
- linceo-0.1.0/tests/unit/test_environment.py +27 -0
- linceo-0.1.0/tests/unit/test_fakes.py +88 -0
- linceo-0.1.0/tests/unit/test_fingerprint.py +189 -0
- linceo-0.1.0/tests/unit/test_gate.py +121 -0
- linceo-0.1.0/tests/unit/test_gitleaks.py +322 -0
- linceo-0.1.0/tests/unit/test_import_boundaries.py +149 -0
- linceo-0.1.0/tests/unit/test_local_provider.py +177 -0
- linceo-0.1.0/tests/unit/test_normalization.py +167 -0
- linceo-0.1.0/tests/unit/test_platform_detection.py +33 -0
- linceo-0.1.0/tests/unit/test_policy.py +471 -0
- linceo-0.1.0/tests/unit/test_registry.py +90 -0
- linceo-0.1.0/tests/unit/test_report_schema.py +41 -0
- linceo-0.1.0/tests/unit/test_reporters.py +395 -0
- linceo-0.1.0/tests/unit/test_sarif_reporter.py +488 -0
- linceo-0.1.0/tests/unit/test_severity.py +38 -0
- linceo-0.1.0/tests/unit/test_subprocess_executor.py +134 -0
- linceo-0.1.0/tests/unit/test_table.py +257 -0
- linceo-0.1.0/tests/unit/test_tool_config.py +279 -0
- linceo-0.1.0/tests/unit/test_trivy.py +657 -0
- linceo-0.1.0/tests/unit/test_version_range.py +63 -0
- linceo-0.1.0/uv.lock +995 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# linceo's own policy document — dogfooding this project's tool on its own
|
|
2
|
+
# source (ADR §4/R5's precedent: "gitleaks como hook de pre-commit desde el
|
|
3
|
+
# commit #1"). Not client configuration: R5 is about a *consumer's* values
|
|
4
|
+
# never living in this repository, and this repository is the one being
|
|
5
|
+
# scanned here, exactly the same way `.gitleaks.toml` (repo root) already
|
|
6
|
+
# is gitleaks' own equivalent for this same repository.
|
|
7
|
+
version = 1
|
|
8
|
+
|
|
9
|
+
# Equivalent strictness to what `gitleaks-action` enforced before this CI
|
|
10
|
+
# job started calling linceo instead: it failed the job on any leak found,
|
|
11
|
+
# with no severity concept of its own. Every `secrets` finding is at least
|
|
12
|
+
# HIGH by category default (ADR §6 — gitleaks emits no native severity), so
|
|
13
|
+
# `fail_on = "high"` blocks on the same set of findings `gitleaks-action`
|
|
14
|
+
# did, never more permissively.
|
|
15
|
+
fail_on = "high"
|
|
16
|
+
|
|
17
|
+
[tools.gitleaks]
|
|
18
|
+
# Keeps `.gitleaks.toml`'s own [extend]/[allowlist] — the synthetic-fixture
|
|
19
|
+
# allowlist under tests/ (see that file) — applying exactly as it did under
|
|
20
|
+
# `gitleaks-action`, which read it by gitleaks' own convention (a bare
|
|
21
|
+
# `.gitleaks.toml` at the scanned root). `GitleaksIntegration.build_command`
|
|
22
|
+
# (ADR §8.5) translates this into gitleaks' own `--config` flag; relative to
|
|
23
|
+
# the scanned workspace root, which is this repository's own root in CI
|
|
24
|
+
# (`.github/workflows/ci.yml`, job "secrets").
|
|
25
|
+
custom_rules_path = ".gitleaks.toml"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Only pyproject.toml, uv.lock, README.md, LICENSE, NOTICE, and src/ are
|
|
2
|
+
# ever COPYed into the build (see Dockerfile) — everything below is
|
|
3
|
+
# excluded purely to keep the build context small and fast, mirroring
|
|
4
|
+
# .gitignore plus the directories the image build itself never touches.
|
|
5
|
+
|
|
6
|
+
.git/
|
|
7
|
+
.github/
|
|
8
|
+
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
*.egg-info/
|
|
12
|
+
.eggs/
|
|
13
|
+
build/
|
|
14
|
+
dist/
|
|
15
|
+
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
.uv-cache/
|
|
19
|
+
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
.coverage.*
|
|
25
|
+
htmlcov/
|
|
26
|
+
coverage.xml
|
|
27
|
+
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
.DS_Store
|
|
31
|
+
|
|
32
|
+
.env
|
|
33
|
+
.env.*
|
|
34
|
+
|
|
35
|
+
docs/
|
|
36
|
+
tests/
|
|
37
|
+
CONTRIBUTING.md
|
|
38
|
+
AGENTS.md
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
env:
|
|
16
|
+
UV_LOCKED: "1"
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
lint:
|
|
20
|
+
name: Lint
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v7
|
|
24
|
+
- uses: astral-sh/setup-uv@v7
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
- run: uv sync --frozen
|
|
28
|
+
- run: uv run ruff check .
|
|
29
|
+
- run: uv run ruff format --check .
|
|
30
|
+
|
|
31
|
+
types:
|
|
32
|
+
name: Types
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
|
+
- uses: astral-sh/setup-uv@v7
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: true
|
|
39
|
+
- run: uv sync --frozen
|
|
40
|
+
- run: uv run mypy -p linceo
|
|
41
|
+
- run: uv run mypy tests
|
|
42
|
+
- run: uv run mypy --strict -p linceo.core
|
|
43
|
+
|
|
44
|
+
test:
|
|
45
|
+
name: Test (py${{ matrix.python-version }})
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
strategy:
|
|
48
|
+
fail-fast: false
|
|
49
|
+
matrix:
|
|
50
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v7
|
|
53
|
+
- uses: astral-sh/setup-uv@v7
|
|
54
|
+
with:
|
|
55
|
+
enable-cache: true
|
|
56
|
+
python-version: ${{ matrix.python-version }}
|
|
57
|
+
- run: uv sync --frozen
|
|
58
|
+
- run: uv run pytest
|
|
59
|
+
|
|
60
|
+
build:
|
|
61
|
+
name: Build package
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v7
|
|
65
|
+
- uses: astral-sh/setup-uv@v7
|
|
66
|
+
with:
|
|
67
|
+
enable-cache: true
|
|
68
|
+
- run: uv build
|
|
69
|
+
- name: Install the built wheel into a clean environment
|
|
70
|
+
run: |
|
|
71
|
+
uv venv /tmp/linceo-smoke-test
|
|
72
|
+
uv pip install --python /tmp/linceo-smoke-test/bin/python dist/*.whl
|
|
73
|
+
/tmp/linceo-smoke-test/bin/linceo --version
|
|
74
|
+
- uses: actions/upload-artifact@v7
|
|
75
|
+
with:
|
|
76
|
+
name: dist
|
|
77
|
+
path: dist/
|
|
78
|
+
|
|
79
|
+
secrets:
|
|
80
|
+
name: Secret scan
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
env:
|
|
83
|
+
GITLEAKS_VERSION: "8.30.1"
|
|
84
|
+
# Copied verbatim from gitleaks' own published
|
|
85
|
+
# gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz.sha256 entry for this
|
|
86
|
+
# release — the same version, and the same checksum-verification
|
|
87
|
+
# practice, the reference container image pins (ADR R4, Dockerfile).
|
|
88
|
+
GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
|
|
89
|
+
steps:
|
|
90
|
+
- uses: actions/checkout@v7
|
|
91
|
+
with:
|
|
92
|
+
# Full history, not just the pushed range: gitleaks (via linceo's
|
|
93
|
+
# `secrets` integration) scans it entirely by design (ADR §10) —
|
|
94
|
+
# this is also, precisely, what a range-based diff scan
|
|
95
|
+
# (gitleaks-action's approach) cannot do on a repository's first
|
|
96
|
+
# push, where the range's parent commit does not exist yet.
|
|
97
|
+
fetch-depth: 0
|
|
98
|
+
|
|
99
|
+
- name: Install gitleaks (pinned, checksum-verified)
|
|
100
|
+
run: |
|
|
101
|
+
set -eu
|
|
102
|
+
curl -fsSL -o gitleaks.tar.gz \
|
|
103
|
+
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
|
|
104
|
+
printf '%s gitleaks.tar.gz\n' "${GITLEAKS_SHA256}" > gitleaks.tar.gz.sha256
|
|
105
|
+
sha256sum -c gitleaks.tar.gz.sha256
|
|
106
|
+
tar -xzf gitleaks.tar.gz gitleaks
|
|
107
|
+
chmod 0755 gitleaks
|
|
108
|
+
sudo mv gitleaks /usr/local/bin/gitleaks
|
|
109
|
+
rm -f gitleaks.tar.gz gitleaks.tar.gz.sha256
|
|
110
|
+
gitleaks version
|
|
111
|
+
|
|
112
|
+
- uses: astral-sh/setup-uv@v7
|
|
113
|
+
with:
|
|
114
|
+
enable-cache: true
|
|
115
|
+
- run: uv sync --frozen
|
|
116
|
+
|
|
117
|
+
- name: linceo scan secrets (dogfooding this project's own tool)
|
|
118
|
+
run: uv run linceo scan secrets --format sarif > linceo-secrets.sarif
|
|
119
|
+
|
|
120
|
+
- name: Upload SARIF
|
|
121
|
+
if: always()
|
|
122
|
+
uses: actions/upload-artifact@v7
|
|
123
|
+
with:
|
|
124
|
+
name: linceo-secrets-sarif
|
|
125
|
+
path: linceo-secrets.sarif
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Triggered by pushing a release tag (`vX.Y.Z`, e.g. `v0.2.0`), never by a
|
|
4
|
+
# branch push — see docs/RELEASING.md for the full procedure, including the
|
|
5
|
+
# recovery steps for a release that fails partway through.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags:
|
|
9
|
+
- "v*.*.*"
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
# Scoped to the tag itself: two different release tags may build in
|
|
16
|
+
# parallel, but the same tag can't race itself. Never cancelled
|
|
17
|
+
# mid-flight — a publish to PyPI must not be interrupted partway.
|
|
18
|
+
group: release-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: false
|
|
20
|
+
|
|
21
|
+
env:
|
|
22
|
+
UV_LOCKED: "1"
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
# Mirrors the `lint`/`types`/`test` jobs in ci.yml: the publish job below
|
|
26
|
+
# must not run unless all three pass, and a release tag is not guaranteed
|
|
27
|
+
# to point at a commit that already went through ci.yml (a tag can be
|
|
28
|
+
# pushed against any commit) — so this workflow re-runs them itself
|
|
29
|
+
# rather than relying on ci.yml having already run.
|
|
30
|
+
lint:
|
|
31
|
+
name: Lint
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v7
|
|
35
|
+
- uses: astral-sh/setup-uv@v7
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: true
|
|
38
|
+
- run: uv sync --frozen
|
|
39
|
+
- run: uv run ruff check .
|
|
40
|
+
- run: uv run ruff format --check .
|
|
41
|
+
|
|
42
|
+
types:
|
|
43
|
+
name: Types
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v7
|
|
47
|
+
- uses: astral-sh/setup-uv@v7
|
|
48
|
+
with:
|
|
49
|
+
enable-cache: true
|
|
50
|
+
- run: uv sync --frozen
|
|
51
|
+
- run: uv run mypy -p linceo
|
|
52
|
+
- run: uv run mypy tests
|
|
53
|
+
- run: uv run mypy --strict -p linceo.core
|
|
54
|
+
|
|
55
|
+
test:
|
|
56
|
+
name: Test (py${{ matrix.python-version }})
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
strategy:
|
|
59
|
+
fail-fast: false
|
|
60
|
+
matrix:
|
|
61
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v7
|
|
64
|
+
- uses: astral-sh/setup-uv@v7
|
|
65
|
+
with:
|
|
66
|
+
enable-cache: true
|
|
67
|
+
python-version: ${{ matrix.python-version }}
|
|
68
|
+
- run: uv sync --frozen
|
|
69
|
+
- run: uv run pytest
|
|
70
|
+
|
|
71
|
+
publish:
|
|
72
|
+
name: Build and publish to PyPI
|
|
73
|
+
needs: [lint, types, test]
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
environment:
|
|
76
|
+
name: pypi
|
|
77
|
+
url: https://pypi.org/p/linceo
|
|
78
|
+
permissions:
|
|
79
|
+
contents: read
|
|
80
|
+
# Trusted Publishing (OIDC): PyPI exchanges this token for a
|
|
81
|
+
# short-lived upload credential — no `PYPI_API_TOKEN` secret exists
|
|
82
|
+
# in this repository. Requires the `pypi` environment below to match
|
|
83
|
+
# exactly what's registered on PyPI for this project (owner
|
|
84
|
+
# `jdiegoisaza`, repo `linceo`, workflow `release.yml`, environment
|
|
85
|
+
# `pypi`).
|
|
86
|
+
id-token: write
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v7
|
|
89
|
+
with:
|
|
90
|
+
# Full history *and* tags: hatch-vcs (see pyproject.toml) derives
|
|
91
|
+
# the package version from the pushed tag, and a shallow clone
|
|
92
|
+
# would hide it, silently falling back to a dev version instead.
|
|
93
|
+
fetch-depth: 0
|
|
94
|
+
|
|
95
|
+
- uses: astral-sh/setup-uv@v7
|
|
96
|
+
with:
|
|
97
|
+
enable-cache: true
|
|
98
|
+
|
|
99
|
+
- run: uv build
|
|
100
|
+
|
|
101
|
+
- name: Verify the git tag and the built package version match
|
|
102
|
+
run: |
|
|
103
|
+
set -eu
|
|
104
|
+
tag="${GITHUB_REF_NAME}"
|
|
105
|
+
expected_version="${tag#v}"
|
|
106
|
+
wheel="$(ls dist/*.whl)"
|
|
107
|
+
actual_version="$(basename "$wheel" | cut -d- -f2)"
|
|
108
|
+
if [ "$expected_version" != "$actual_version" ]; then
|
|
109
|
+
echo "::error::Tag $tag (expected version $expected_version) does not match the built package version $actual_version. Refusing to publish — see docs/RELEASING.md." >&2
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
echo "Verified: tag $tag matches package version $actual_version"
|
|
113
|
+
|
|
114
|
+
- name: Publish to PyPI (Trusted Publishing, OIDC)
|
|
115
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
116
|
+
with:
|
|
117
|
+
packages-dir: dist/
|
linceo-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# uv
|
|
14
|
+
.uv-cache/
|
|
15
|
+
|
|
16
|
+
# Test and coverage artifacts
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
htmlcov/
|
|
23
|
+
coverage.xml
|
|
24
|
+
|
|
25
|
+
# Editors and OS
|
|
26
|
+
.vscode/
|
|
27
|
+
.idea/
|
|
28
|
+
.DS_Store
|
|
29
|
+
|
|
30
|
+
# Local configuration overrides — never a source of truth for this
|
|
31
|
+
# repository (see AGENTS.md, "no client configuration" and ADR-000 §R5)
|
|
32
|
+
.env
|
|
33
|
+
.env.*
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
title = "linceo gitleaks configuration"
|
|
2
|
+
|
|
3
|
+
# Extends gitleaks' own default ruleset rather than replacing it — this
|
|
4
|
+
# project's dogfooding hook (AGENTS.md, ADR §4/R5) is meant to catch a real
|
|
5
|
+
# secret landing anywhere in the repository, not a narrower custom ruleset.
|
|
6
|
+
[extend]
|
|
7
|
+
useDefault = true
|
|
8
|
+
|
|
9
|
+
# `tests/` deliberately contains synthetic, gitleaks-rule-shaped values: the
|
|
10
|
+
# golden fixtures under `tests/unit/fixtures/gitleaks/` are literal captured
|
|
11
|
+
# output from real gitleaks runs against disposable repositories (see that
|
|
12
|
+
# directory's README.md), and a handful of test modules inline the same
|
|
13
|
+
# synthetic values to construct the fingerprints those fixtures should
|
|
14
|
+
# produce. None of these are real credentials — allowlisting the whole test
|
|
15
|
+
# tree is simpler to keep correct over time than chasing every new synthetic
|
|
16
|
+
# value with its own regex, and "a real secret would only ever land in
|
|
17
|
+
# `tests/`" is not a risk this project accepts anywhere else.
|
|
18
|
+
[allowlist]
|
|
19
|
+
description = "Synthetic secrets in test fixtures and test code (ADR §11) — not real credentials"
|
|
20
|
+
paths = [
|
|
21
|
+
'''^tests/.*''',
|
|
22
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-toml
|
|
8
|
+
- id: check-yaml
|
|
9
|
+
- id: check-merge-conflict
|
|
10
|
+
- id: check-added-large-files
|
|
11
|
+
|
|
12
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
13
|
+
rev: v0.16.6
|
|
14
|
+
hooks:
|
|
15
|
+
- id: ruff
|
|
16
|
+
args: [--fix]
|
|
17
|
+
- id: ruff-format
|
|
18
|
+
|
|
19
|
+
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
20
|
+
rev: 0.12.9
|
|
21
|
+
hooks:
|
|
22
|
+
- id: uv-lock
|
|
23
|
+
|
|
24
|
+
# Dogfooding: this project ships a secrets-scanning integration (§10) and
|
|
25
|
+
# runs it on its own history from the first commit (§4/R5).
|
|
26
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
27
|
+
rev: v8.30.1
|
|
28
|
+
hooks:
|
|
29
|
+
- id: gitleaks
|
|
30
|
+
|
|
31
|
+
# Local hook rather than mirrors-mypy: this project has a runtime
|
|
32
|
+
# dependency (Typer), and mirrors-mypy's isolated hook environment would
|
|
33
|
+
# need it declared separately and kept in sync by hand. Running through
|
|
34
|
+
# `uv run` reuses the same environment and pinned mypy version as CI.
|
|
35
|
+
- repo: local
|
|
36
|
+
hooks:
|
|
37
|
+
- id: mypy-project
|
|
38
|
+
name: mypy (project)
|
|
39
|
+
entry: uv run mypy -p linceo
|
|
40
|
+
language: system
|
|
41
|
+
pass_filenames: false
|
|
42
|
+
types: [python]
|
|
43
|
+
- id: mypy-tests
|
|
44
|
+
name: mypy (tests)
|
|
45
|
+
entry: uv run mypy tests
|
|
46
|
+
language: system
|
|
47
|
+
pass_filenames: false
|
|
48
|
+
types: [python]
|
|
49
|
+
- id: mypy-core-strict
|
|
50
|
+
name: mypy --strict (linceo.core)
|
|
51
|
+
entry: uv run mypy --strict -p linceo.core
|
|
52
|
+
language: system
|
|
53
|
+
pass_filenames: false
|
|
54
|
+
types: [python]
|
linceo-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Repository conventions
|
|
2
|
+
|
|
3
|
+
This document describes how this repository is organized and the hard rules
|
|
4
|
+
that apply to any change made to it. The architectural contract itself lives
|
|
5
|
+
in [`docs/adr/ADR-000-arquitectura-base-y-alcance-v0.1.md`](docs/adr/ADR-000-arquitectura-base-y-alcance-v0.1.md);
|
|
6
|
+
this file is the day-to-day operating summary of that contract, not a
|
|
7
|
+
substitute for it. Where the two disagree, the ADR wins.
|
|
8
|
+
|
|
9
|
+
## Layout
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/linceo/
|
|
13
|
+
core/ Domain and ports. No third-party dependencies, no imports
|
|
14
|
+
from adapters/, providers/, or cli/. See "Layer boundaries"
|
|
15
|
+
below.
|
|
16
|
+
adapters/ Concrete ToolIntegration implementations (Gitleaks, Trivy).
|
|
17
|
+
providers/ Concrete ContextProvider implementations (local, azure_devops).
|
|
18
|
+
The only layer allowed to read os.environ.
|
|
19
|
+
testing/ Permanent test doubles and fixtures (FakeContextProvider,
|
|
20
|
+
FakeToolExecutor, golden fixtures). Public API, versioned with
|
|
21
|
+
the same backward-compatibility guarantees as any other
|
|
22
|
+
public module — see ADR §11.
|
|
23
|
+
cli/ Console entry point. Argument parsing and translation into
|
|
24
|
+
domain objects only; no orchestration logic of its own.
|
|
25
|
+
|
|
26
|
+
tests/unit/ Fast tests, no real tool binaries required.
|
|
27
|
+
tests/integration/ Tests that require real tool binaries on PATH. See
|
|
28
|
+
tests/integration/README.md. Deselected by default
|
|
29
|
+
(pytest marker `integration`); always run in this
|
|
30
|
+
project's own CI.
|
|
31
|
+
docs/adr/ Architecture decision records.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Layer boundaries (hard rule)
|
|
35
|
+
|
|
36
|
+
- `core/` imports nothing beyond the standard library and other `core`
|
|
37
|
+
modules. No `adapters`, `providers`, `cli`, or third-party package —
|
|
38
|
+
Typer included.
|
|
39
|
+
- `adapters/` and `providers/` never import `typer`, `click`, or
|
|
40
|
+
`linceo.cli`.
|
|
41
|
+
- `os.environ` is read only inside `providers/`, and only within that
|
|
42
|
+
package.
|
|
43
|
+
|
|
44
|
+
`tests/unit/test_import_boundaries.py` enforces this by walking the AST of
|
|
45
|
+
every module under `src/linceo/`. A change that violates a boundary fails
|
|
46
|
+
that test, not just code review.
|
|
47
|
+
|
|
48
|
+
## No placeholder code
|
|
49
|
+
|
|
50
|
+
Nothing is committed as `pass`-only, a bare `...`, or a `TODO` / `FIXME`
|
|
51
|
+
comment standing in for real logic. If a piece of functionality is not
|
|
52
|
+
implemented, it is not created — including its empty function, its stub
|
|
53
|
+
class, or its unused parameter. `ruff`'s `FIX` rule set enforces the
|
|
54
|
+
TODO/FIXME/XXX/HACK part of this in CI; the rest is a review responsibility.
|
|
55
|
+
|
|
56
|
+
A package `__init__.py` containing only a module docstring is not a
|
|
57
|
+
placeholder — it documents what the package holds and makes it importable.
|
|
58
|
+
|
|
59
|
+
## Language
|
|
60
|
+
|
|
61
|
+
README, source code, docstrings, and commit messages are written in
|
|
62
|
+
English. This applies to every file in the repository except the ADRs
|
|
63
|
+
under `docs/adr/`, which are written in the language they were originally
|
|
64
|
+
recorded in.
|
|
65
|
+
|
|
66
|
+
Documentation in this repository is written for whoever reads the codebase
|
|
67
|
+
next — it describes the project, not the process that produced a given
|
|
68
|
+
change.
|
|
69
|
+
|
|
70
|
+
## CLI framework: Typer, confined to `cli/`
|
|
71
|
+
|
|
72
|
+
Typer is the only third-party dependency of the base package (see
|
|
73
|
+
ADR §8.3 for the full decision record). Three rules follow directly from
|
|
74
|
+
that decision:
|
|
75
|
+
|
|
76
|
+
1. Typer is imported only inside `cli/`.
|
|
77
|
+
2. The CLI layer translates arguments into domain objects and contains no
|
|
78
|
+
logic of its own — a full run must be executable from Python without
|
|
79
|
+
going through Typer.
|
|
80
|
+
3. It is pinned to a major version range (`>=0.12,<1.0`) and its lockfile
|
|
81
|
+
entry is reviewed like any other dependency change.
|
|
82
|
+
|
|
83
|
+
## Testing
|
|
84
|
+
|
|
85
|
+
- `pytest` runs with coverage and the `integration` marker deselected by
|
|
86
|
+
default; see `tests/integration/README.md` for what belongs there.
|
|
87
|
+
- Test doubles under `linceo.testing` are permanent, versioned public API —
|
|
88
|
+
not scaffolding to delete once real adapters exist (ADR §11).
|
|
89
|
+
- `FakeToolExecutor` replays previously recorded output from a real tool
|
|
90
|
+
rather than simulating it from scratch, to minimize drift between what
|
|
91
|
+
the fake allows and what the real tool produces.
|
|
92
|
+
|
|
93
|
+
## Plugin entry points
|
|
94
|
+
|
|
95
|
+
Third-party tool integrations and context providers register under the
|
|
96
|
+
`linceo.tool_integrations` and `linceo.context_providers` entry point
|
|
97
|
+
groups declared in `pyproject.toml`. These groups exist from the first
|
|
98
|
+
commit, even before any third-party plugin uses them, so the extension
|
|
99
|
+
point is a stable part of the package's public contract rather than
|
|
100
|
+
something bolted on later.
|
|
101
|
+
|
|
102
|
+
## Task commands
|
|
103
|
+
|
|
104
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full list of `uv run`
|
|
105
|
+
commands used to lint, type-check, test, and build this project. There is
|
|
106
|
+
no Makefile — `uv` is already the project's task runner (ADR §3).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
This project uses [`uv`](https://docs.astral.sh/uv/) for dependency
|
|
4
|
+
management, environments, and Python interpreter management — there is no
|
|
5
|
+
Makefile; every task below runs through `uv run` (see
|
|
6
|
+
[`docs/adr/ADR-000-arquitectura-base-y-alcance-v0.1.md`](docs/adr/ADR-000-arquitectura-base-y-alcance-v0.1.md),
|
|
7
|
+
§3). Repository conventions and hard rules live in
|
|
8
|
+
[`AGENTS.md`](AGENTS.md); read it before making a change.
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv sync # installs the project and dev dependency group
|
|
14
|
+
uv run pre-commit install # installs the git hooks used by this repository
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`uv sync` reads `uv.lock`. If `pyproject.toml` and `uv.lock` disagree, `uv
|
|
18
|
+
sync --frozen` (used in CI) fails loudly instead of silently re-resolving —
|
|
19
|
+
run `uv lock` locally and commit the updated lockfile if that happens.
|
|
20
|
+
|
|
21
|
+
## Task commands
|
|
22
|
+
|
|
23
|
+
| Task | Command |
|
|
24
|
+
|---|---|
|
|
25
|
+
| Lint | `uv run ruff check .` |
|
|
26
|
+
| Format check | `uv run ruff format --check .` |
|
|
27
|
+
| Apply formatting | `uv run ruff format .` |
|
|
28
|
+
| Type-check (project) | `uv run mypy -p linceo` |
|
|
29
|
+
| Type-check (tests) | `uv run mypy tests` |
|
|
30
|
+
| Type-check (`core/`, strict) | `uv run mypy --strict -p linceo.core` |
|
|
31
|
+
| Unit tests + coverage | `uv run pytest` |
|
|
32
|
+
| Integration tests | `uv run pytest -m integration` (requires real tool binaries on `PATH`; see `tests/integration/README.md`) |
|
|
33
|
+
| Build the package | `uv build` |
|
|
34
|
+
| Run all pre-commit hooks | `uv run pre-commit run --all-files` |
|
|
35
|
+
|
|
36
|
+
`uv run pytest` deselects the `integration` marker by default and enforces
|
|
37
|
+
full coverage of `src/linceo` (`--cov-fail-under=100`, set in
|
|
38
|
+
`pyproject.toml`). `core/` is checked with `mypy --strict` as a separate
|
|
39
|
+
invocation from the rest of the project, rather than as a per-module
|
|
40
|
+
override, because mypy has no `strict = true` config-file flag scoped to a
|
|
41
|
+
single module — expanding `--strict`'s flag set by hand would silently
|
|
42
|
+
drift from it whenever mypy changes what `--strict` means.
|
|
43
|
+
|
|
44
|
+
## Before opening a pull request
|
|
45
|
+
|
|
46
|
+
- `uv run pre-commit run --all-files` passes clean.
|
|
47
|
+
- New code in `core/`, `adapters/`, `providers/`, or `cli/` includes tests;
|
|
48
|
+
coverage does not regress.
|
|
49
|
+
- Commit messages are in English and describe the reasoning behind a
|
|
50
|
+
change, not just its mechanics.
|
|
51
|
+
- Changes to the architectural contract in `docs/adr/` are proposed as a
|
|
52
|
+
new ADR or an explicit amendment to an existing one — never as a silent
|
|
53
|
+
edit that erases the record of what was previously decided and why.
|