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.
Files changed (109) hide show
  1. linceo-0.1.0/.devsecops/config.toml +25 -0
  2. linceo-0.1.0/.dockerignore +38 -0
  3. linceo-0.1.0/.github/dependabot.yml +15 -0
  4. linceo-0.1.0/.github/workflows/ci.yml +125 -0
  5. linceo-0.1.0/.github/workflows/release.yml +117 -0
  6. linceo-0.1.0/.gitignore +33 -0
  7. linceo-0.1.0/.gitleaks.toml +22 -0
  8. linceo-0.1.0/.pre-commit-config.yaml +54 -0
  9. linceo-0.1.0/AGENTS.md +106 -0
  10. linceo-0.1.0/CONTRIBUTING.md +53 -0
  11. linceo-0.1.0/Dockerfile +253 -0
  12. linceo-0.1.0/LICENSE +202 -0
  13. linceo-0.1.0/NOTICE +12 -0
  14. linceo-0.1.0/PKG-INFO +125 -0
  15. linceo-0.1.0/README.md +100 -0
  16. linceo-0.1.0/SECURITY.md +31 -0
  17. linceo-0.1.0/azure-pipelines/examples/two-stage-scan.yml +88 -0
  18. linceo-0.1.0/azure-pipelines/templates/linceo-scan.yml +168 -0
  19. linceo-0.1.0/docs/ADOPTION.md +200 -0
  20. linceo-0.1.0/docs/RELEASING.md +181 -0
  21. linceo-0.1.0/docs/adr/ADR-000-arquitectura-base-y-alcance-v0.1.md +1897 -0
  22. linceo-0.1.0/pyproject.toml +168 -0
  23. linceo-0.1.0/src/linceo/__init__.py +13 -0
  24. linceo-0.1.0/src/linceo/adapters/__init__.py +13 -0
  25. linceo-0.1.0/src/linceo/adapters/gitleaks.py +251 -0
  26. linceo-0.1.0/src/linceo/adapters/subprocess_executor.py +69 -0
  27. linceo-0.1.0/src/linceo/adapters/trivy.py +505 -0
  28. linceo-0.1.0/src/linceo/cli/__init__.py +7 -0
  29. linceo-0.1.0/src/linceo/cli/doctor.py +243 -0
  30. linceo-0.1.0/src/linceo/cli/main.py +57 -0
  31. linceo-0.1.0/src/linceo/cli/scan.py +419 -0
  32. linceo-0.1.0/src/linceo/core/__init__.py +144 -0
  33. linceo-0.1.0/src/linceo/core/config.py +544 -0
  34. linceo-0.1.0/src/linceo/core/context.py +62 -0
  35. linceo-0.1.0/src/linceo/core/dedup.py +28 -0
  36. linceo-0.1.0/src/linceo/core/engine.py +278 -0
  37. linceo-0.1.0/src/linceo/core/execution.py +107 -0
  38. linceo-0.1.0/src/linceo/core/exit_codes.py +40 -0
  39. linceo-0.1.0/src/linceo/core/findings.py +133 -0
  40. linceo-0.1.0/src/linceo/core/fingerprint.py +103 -0
  41. linceo-0.1.0/src/linceo/core/gate.py +58 -0
  42. linceo-0.1.0/src/linceo/core/normalization.py +148 -0
  43. linceo-0.1.0/src/linceo/core/policy.py +496 -0
  44. linceo-0.1.0/src/linceo/core/ports.py +225 -0
  45. linceo-0.1.0/src/linceo/core/registry.py +86 -0
  46. linceo-0.1.0/src/linceo/core/report_schema.py +73 -0
  47. linceo-0.1.0/src/linceo/core/reporters.py +288 -0
  48. linceo-0.1.0/src/linceo/core/results.py +107 -0
  49. linceo-0.1.0/src/linceo/core/sarif.py +244 -0
  50. linceo-0.1.0/src/linceo/core/severity.py +88 -0
  51. linceo-0.1.0/src/linceo/core/table.py +131 -0
  52. linceo-0.1.0/src/linceo/core/tool_config.py +443 -0
  53. linceo-0.1.0/src/linceo/core/version_range.py +102 -0
  54. linceo-0.1.0/src/linceo/data/__init__.py +13 -0
  55. linceo-0.1.0/src/linceo/data/sarif-schema-2.1.0.json +3389 -0
  56. linceo-0.1.0/src/linceo/providers/__init__.py +27 -0
  57. linceo-0.1.0/src/linceo/providers/azure_devops.py +182 -0
  58. linceo-0.1.0/src/linceo/providers/detection.py +41 -0
  59. linceo-0.1.0/src/linceo/providers/environment.py +24 -0
  60. linceo-0.1.0/src/linceo/providers/local.py +115 -0
  61. linceo-0.1.0/src/linceo/py.typed +0 -0
  62. linceo-0.1.0/src/linceo/testing/__init__.py +13 -0
  63. linceo-0.1.0/src/linceo/testing/fakes.py +76 -0
  64. linceo-0.1.0/tests/integration/README.md +30 -0
  65. linceo-0.1.0/tests/integration/test_gitleaks_integration.py +195 -0
  66. linceo-0.1.0/tests/integration/test_trivy_integration.py +192 -0
  67. linceo-0.1.0/tests/unit/fixtures/gitleaks/README.md +30 -0
  68. linceo-0.1.0/tests/unit/fixtures/gitleaks/empty.json +1 -0
  69. linceo-0.1.0/tests/unit/fixtures/gitleaks/malformed_truncated.json +5 -0
  70. linceo-0.1.0/tests/unit/fixtures/gitleaks/many_findings.json +62 -0
  71. linceo-0.1.0/tests/unit/fixtures/gitleaks/missing_field.json +8 -0
  72. linceo-0.1.0/tests/unit/fixtures/gitleaks/one_finding.json +22 -0
  73. linceo-0.1.0/tests/unit/fixtures/trivy/README.md +56 -0
  74. linceo-0.1.0/tests/unit/fixtures/trivy/empty.json +12 -0
  75. linceo-0.1.0/tests/unit/fixtures/trivy/malformed_truncated.json +10 -0
  76. linceo-0.1.0/tests/unit/fixtures/trivy/many_findings.json +94 -0
  77. linceo-0.1.0/tests/unit/fixtures/trivy/missing_field.json +23 -0
  78. linceo-0.1.0/tests/unit/fixtures/trivy/one_finding.json +38 -0
  79. linceo-0.1.0/tests/unit/test_azure_devops_provider.py +157 -0
  80. linceo-0.1.0/tests/unit/test_cli.py +41 -0
  81. linceo-0.1.0/tests/unit/test_cli_doctor.py +79 -0
  82. linceo-0.1.0/tests/unit/test_cli_scan.py +520 -0
  83. linceo-0.1.0/tests/unit/test_cli_scan_platform.py +182 -0
  84. linceo-0.1.0/tests/unit/test_cli_scan_sca.py +312 -0
  85. linceo-0.1.0/tests/unit/test_config.py +670 -0
  86. linceo-0.1.0/tests/unit/test_dedup.py +52 -0
  87. linceo-0.1.0/tests/unit/test_doctor.py +191 -0
  88. linceo-0.1.0/tests/unit/test_engine.py +487 -0
  89. linceo-0.1.0/tests/unit/test_environment.py +27 -0
  90. linceo-0.1.0/tests/unit/test_fakes.py +88 -0
  91. linceo-0.1.0/tests/unit/test_fingerprint.py +189 -0
  92. linceo-0.1.0/tests/unit/test_gate.py +121 -0
  93. linceo-0.1.0/tests/unit/test_gitleaks.py +322 -0
  94. linceo-0.1.0/tests/unit/test_import_boundaries.py +149 -0
  95. linceo-0.1.0/tests/unit/test_local_provider.py +177 -0
  96. linceo-0.1.0/tests/unit/test_normalization.py +167 -0
  97. linceo-0.1.0/tests/unit/test_platform_detection.py +33 -0
  98. linceo-0.1.0/tests/unit/test_policy.py +471 -0
  99. linceo-0.1.0/tests/unit/test_registry.py +90 -0
  100. linceo-0.1.0/tests/unit/test_report_schema.py +41 -0
  101. linceo-0.1.0/tests/unit/test_reporters.py +395 -0
  102. linceo-0.1.0/tests/unit/test_sarif_reporter.py +488 -0
  103. linceo-0.1.0/tests/unit/test_severity.py +38 -0
  104. linceo-0.1.0/tests/unit/test_subprocess_executor.py +134 -0
  105. linceo-0.1.0/tests/unit/test_table.py +257 -0
  106. linceo-0.1.0/tests/unit/test_tool_config.py +279 -0
  107. linceo-0.1.0/tests/unit/test_trivy.py +657 -0
  108. linceo-0.1.0/tests/unit/test_version_range.py +63 -0
  109. 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,15 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ groups:
8
+ github-actions:
9
+ patterns:
10
+ - "*"
11
+
12
+ - package-ecosystem: "uv"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "weekly"
@@ -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/
@@ -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.