cvcite 0.2.0__tar.gz → 0.2.2__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 (79) hide show
  1. cvcite-0.2.2/.github/dependabot.yml +20 -0
  2. cvcite-0.2.2/.github/scripts/render-templates.sh +12 -0
  3. cvcite-0.2.2/.github/workflows/ci.yml +125 -0
  4. cvcite-0.2.2/.github/workflows/lint.yml +31 -0
  5. cvcite-0.2.2/.github/workflows/publish.yml +84 -0
  6. cvcite-0.2.2/PKG-INFO +355 -0
  7. cvcite-0.2.2/README.md +331 -0
  8. cvcite-0.2.2/assets/emblem.svg +10 -0
  9. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/.claude-plugin/plugin.json +1 -1
  10. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/README.md +1 -1
  11. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/skills/cv-ingest/SKILL.md +3 -4
  12. {cvcite-0.2.0 → cvcite-0.2.2}/pyproject.toml +2 -0
  13. {cvcite-0.2.0 → cvcite-0.2.2}/requirements/action.txt +3 -3
  14. cvcite-0.2.2/requirements/build.in +1 -0
  15. cvcite-0.2.2/requirements/build.txt +29 -0
  16. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/__init__.py +1 -1
  17. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/_rendercv_shim.py +4 -4
  18. cvcite-0.2.2/tests/test_versions.py +22 -0
  19. cvcite-0.2.0/.github/dependabot.yml +0 -13
  20. cvcite-0.2.0/.github/scripts/verify-action-pins.sh +0 -76
  21. cvcite-0.2.0/.github/workflows/ci.yml +0 -78
  22. cvcite-0.2.0/.github/workflows/lint.yml +0 -71
  23. cvcite-0.2.0/PKG-INFO +0 -178
  24. cvcite-0.2.0/README.md +0 -156
  25. {cvcite-0.2.0 → cvcite-0.2.2}/.claude-plugin/marketplace.json +0 -0
  26. {cvcite-0.2.0 → cvcite-0.2.2}/.github/CODEOWNERS +0 -0
  27. {cvcite-0.2.0 → cvcite-0.2.2}/.github/scripts/check-template-pins.sh +0 -0
  28. {cvcite-0.2.0 → cvcite-0.2.2}/.gitignore +0 -0
  29. {cvcite-0.2.0 → cvcite-0.2.2}/LICENSE +0 -0
  30. {cvcite-0.2.0 → cvcite-0.2.2}/action.yml +0 -0
  31. {cvcite-0.2.0 → cvcite-0.2.2}/docs/icon.typ +0 -0
  32. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/.claude-plugin/icon.png +0 -0
  33. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/agents/cv-claim-reviewer.md +0 -0
  34. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/skills/cv-build/SKILL.md +0 -0
  35. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/skills/cv-fact-check/SKILL.md +0 -0
  36. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/skills/cv-presence-audit/SKILL.md +0 -0
  37. {cvcite-0.2.0 → cvcite-0.2.2}/plugin/skills/cv-tailor/SKILL.md +0 -0
  38. {cvcite-0.2.0 → cvcite-0.2.2}/requirements/test.in +0 -0
  39. {cvcite-0.2.0 → cvcite-0.2.2}/requirements/test.txt +0 -0
  40. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/__main__.py +0 -0
  41. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/checks.py +0 -0
  42. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/cli.py +0 -0
  43. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/compare.py +0 -0
  44. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/config.py +0 -0
  45. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/doctor.py +0 -0
  46. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/gate.py +0 -0
  47. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/ingest.py +0 -0
  48. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/overlay.py +0 -0
  49. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/package.py +0 -0
  50. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/render.py +0 -0
  51. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/scaffold.py +0 -0
  52. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/AGENTS.md +0 -0
  53. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/cv.yaml +0 -0
  54. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/cvcite.toml +0 -0
  55. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/facts.yaml +0 -0
  56. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/github/workflows/cv-review.yml +0 -0
  57. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/github/workflows/cv.yml +0 -0
  58. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/github/workflows/release.yml +0 -0
  59. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/gitignore +0 -0
  60. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/repo/variants/general.yaml +0 -0
  61. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/sample/cv.yaml +0 -0
  62. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/sample/cvcite.toml +0 -0
  63. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/sample/facts.yaml +0 -0
  64. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/sample/variants/manager.yaml +0 -0
  65. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/templates/sample/variants/staff-engineer.yaml +0 -0
  66. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/vendor/fontawesome-0.6.0/LICENSE +0 -0
  67. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/vendor/fontawesome-0.6.0/lib-gen-func.typ +0 -0
  68. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/vendor/fontawesome-0.6.0/lib-gen-map.typ +0 -0
  69. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/vendor/fontawesome-0.6.0/lib-impl.typ +0 -0
  70. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/vendor/fontawesome-0.6.0/lib.typ +0 -0
  71. {cvcite-0.2.0 → cvcite-0.2.2}/src/cvcite/vendor/fontawesome-0.6.0/typst.toml +0 -0
  72. {cvcite-0.2.0 → cvcite-0.2.2}/tests/__init__.py +0 -0
  73. {cvcite-0.2.0 → cvcite-0.2.2}/tests/conftest.py +0 -0
  74. {cvcite-0.2.0 → cvcite-0.2.2}/tests/test_build_tools.py +0 -0
  75. {cvcite-0.2.0 → cvcite-0.2.2}/tests/test_checks.py +0 -0
  76. {cvcite-0.2.0 → cvcite-0.2.2}/tests/test_gate.py +0 -0
  77. {cvcite-0.2.0 → cvcite-0.2.2}/tests/test_hardening.py +0 -0
  78. {cvcite-0.2.0 → cvcite-0.2.2}/tests/test_ingest.py +0 -0
  79. {cvcite-0.2.0 → cvcite-0.2.2}/tests/test_render.py +0 -0
@@ -0,0 +1,20 @@
1
+ # Weekly updates for the pinned actions in .github/workflows and action.yml, in one pull request.
2
+ # The workflows `init` writes live under src/ where Dependabot does not look; lint.yml fails when
3
+ # their pins differ from these, so a bump of an action they share is finished in the same pull
4
+ # request. A bump that reaches action.yml or src/ ships, so that pull request also raises the
5
+ # version in src/cvcite/__init__.py and the plugin's two copies, and "Release / Check the version"
6
+ # fails until it does. The Python locks in requirements/ are regenerated by hand (see their
7
+ # headers); Dependabot alerts still cover every package in them.
8
+ version: 2
9
+ updates:
10
+ - package-ecosystem: github-actions
11
+ directory: /
12
+ # A new release waits a week before Dependabot proposes it.
13
+ cooldown:
14
+ default-days: 7
15
+ schedule:
16
+ interval: weekly
17
+ groups:
18
+ actions:
19
+ patterns:
20
+ - '*'
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+ # Render the workflows `cvcite init` writes, with a placeholder action pin, so actionlint can lint
3
+ # them as the workflows they become in a user's repository.
4
+ #
5
+ # .github/scripts/render-templates.sh <out-dir>
6
+ set -euo pipefail
7
+ out=${1:?usage: render-templates.sh <out-dir>}
8
+ mkdir -p "$out"
9
+ for f in src/cvcite/templates/repo/github/workflows/*.yml; do
10
+ sed 's|{{ACTION_REF}}|agusgonzaleznic/cvcite@0000000000000000000000000000000000000000 # v0.0.0|' \
11
+ "$f" > "$out/$(basename "$f")"
12
+ done
@@ -0,0 +1,125 @@
1
+ # Tests on every pull request and every push to main, the action itself run end to end on the
2
+ # made-up sample, and the package built as a release builds it. The tests and the build run in the
3
+ # shared reusable workflows; everything is pinned to full commit SHAs with the release in a
4
+ # comment, and lint.yml proves each pin.
5
+ #
6
+ # The merge is the release. On a pull request, "Release / Check the version" fails a change to
7
+ # what ships unless __version__ in src/cvcite/__init__.py is newer than every release. On main,
8
+ # once the tests, the action and the build pass, the Release job tags that version when it is new
9
+ # and runs publish.yml on the tag, which publishes the GitHub release and the package on PyPI. The
10
+ # tag is the Action's release too. Nobody pushes a tag by hand.
11
+
12
+ name: CI
13
+
14
+ on:
15
+ pull_request:
16
+ push:
17
+ branches: [main]
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ # A pull request cancels its older runs. Runs on main never cancel a running one: it may be
23
+ # tagging a version and waiting for its release.
24
+ concurrency:
25
+ group: ci-${{ github.workflow }}-${{ github.ref }}
26
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
27
+
28
+ jobs:
29
+ # The job names here are half of the main ruleset's required checks ("Tests / Python 3.12",
30
+ # "Package build / Build sdist and wheel"; "Action on the sample" whole) and of
31
+ # "Release / Check the version": renaming one is a breaking change.
32
+ tests:
33
+ name: Tests
34
+ uses: agusgonzaleznic/github-reusable-workflows/.github/workflows/python-tests.yml@473236e155063a434af75d7da22312a2b6f27e54 # v1.6.0
35
+ with:
36
+ runs-on: ubuntu-24.04
37
+ requirements: requirements/action.txt requirements/test.txt
38
+ # Ghostscript is the gate's third extractor.
39
+ apt-packages: ghostscript
40
+ pythonpath: src
41
+
42
+ # Stays here, the one exception to the shared workflows: it runs this repository's own action
43
+ # (uses: ./) with cvcite's inputs and checks cvcite's release files, so a shared copy would be
44
+ # cvcite-only, and every new action input would cost a shared release and a pin bump.
45
+ action:
46
+ name: Action on the sample
47
+ runs-on: ubuntu-24.04
48
+ timeout-minutes: 15
49
+ steps:
50
+ - name: Check out the repository
51
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52
+ with:
53
+ persist-credentials: false
54
+
55
+ - name: Check, render, gate and package the sample
56
+ uses: ./
57
+ with:
58
+ working-directory: src/cvcite/templates/sample
59
+ package: "true"
60
+ # This repository is public, and the sample person is made up.
61
+ allow-public: "true"
62
+
63
+ - name: The release files are there
64
+ working-directory: src/cvcite/templates/sample
65
+ run: |
66
+ set -euo pipefail
67
+ unzip -l .tmp/release/build.zip
68
+ (cd .tmp/release && unzip -q build.zip && sha256sum -c SHA256SUMS)
69
+
70
+ package:
71
+ name: Package build
72
+ # The build publish.yml runs, on every change, so a stale requirements/build.txt or a broken
73
+ # build fails a pull request instead of a release (a v* tag can never move).
74
+ uses: agusgonzaleznic/github-reusable-workflows/.github/workflows/python-build.yml@8c19beae01a5978c5e7dd378f44307ab00dca651 # v1.7.0
75
+ with:
76
+ build-constraints: requirements/build.txt
77
+
78
+ # A change to what users get needs a new version: the package and its metadata, README.md and
79
+ # LICENSE (PyPI shows them and the sdist carries them), the Action (action.yml and the
80
+ # requirements it installs) and the Claude Code plugin. Tests, docs and workflows ship with the
81
+ # next version. The version is the one the package build read from the built wheel, and
82
+ # tests/test_versions.py keeps the plugin's two copies equal to it.
83
+ check-version:
84
+ name: Release
85
+ if: github.event_name == 'pull_request'
86
+ needs: package
87
+ uses: agusgonzaleznic/github-reusable-workflows/.github/workflows/version-tag.yml@1832ea3171529c36e663d34965917c9f14694ce5 # v1.9.0
88
+ permissions:
89
+ contents: read
90
+ with:
91
+ version: ${{ needs.package.outputs.version }}
92
+ release-workflow: publish.yml
93
+ release-paths: |
94
+ src/cvcite/
95
+ pyproject.toml
96
+ README.md
97
+ LICENSE
98
+ action.yml
99
+ requirements/action.txt
100
+ plugin/
101
+ .claude-plugin/
102
+
103
+ # Tags the version on the merge when it is new, then runs publish.yml on the tag and waits for
104
+ # it. A release that fails turns this run red. The Action ships with the tag, so its run on the
105
+ # sample gates the release like the tests.
106
+ release:
107
+ name: Release
108
+ if: github.event_name == 'push'
109
+ needs: [tests, action, package]
110
+ uses: agusgonzaleznic/github-reusable-workflows/.github/workflows/version-tag.yml@1832ea3171529c36e663d34965917c9f14694ce5 # v1.9.0
111
+ permissions:
112
+ contents: write
113
+ actions: write
114
+ with:
115
+ version: ${{ needs.package.outputs.version }}
116
+ release-workflow: publish.yml
117
+ release-paths: |
118
+ src/cvcite/
119
+ pyproject.toml
120
+ README.md
121
+ LICENSE
122
+ action.yml
123
+ requirements/action.txt
124
+ plugin/
125
+ .claude-plugin/
@@ -0,0 +1,31 @@
1
+ # Lints the workflows and proves every action pin, on every pull request and push to main, with
2
+ # the shared actions-lint.yml. The workflows that `cvcite init` writes are linted too, rendered
3
+ # with their placeholder filled in, since they run in every user's repository, and their pins
4
+ # must match this repository's (check-template-pins.sh), since Dependabot does not look under src/.
5
+
6
+ name: Lint
7
+
8
+ on:
9
+ pull_request:
10
+ push:
11
+ branches: [main]
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ concurrency:
17
+ group: ${{ github.workflow }}-${{ github.ref }}
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ # "Lint" is half of the main ruleset's required checks ("Lint / actionlint", "Lint / Verify
22
+ # action pins"): renaming it is a breaking change.
23
+ lint:
24
+ name: Lint
25
+ uses: agusgonzaleznic/github-reusable-workflows/.github/workflows/actions-lint.yml@473236e155063a434af75d7da22312a2b6f27e54 # v1.6.0
26
+ with:
27
+ runs-on: ubuntu-24.04
28
+ prepare-command: .github/scripts/render-templates.sh .tmp/rendered/.github/workflows
29
+ actionlint-files: .github/workflows/*.yml .tmp/rendered/.github/workflows/*.yml
30
+ pin-paths: .github/workflows action.yml src/cvcite/templates/repo/github/workflows
31
+ extra-pin-command: .github/scripts/check-template-pins.sh
@@ -0,0 +1,84 @@
1
+ # Releases cvcite from a version tag vX.Y.Z on main. ci.yml's Release job makes the tag when a
2
+ # merge brings a new version, and starts this workflow on it with workflow_dispatch, because
3
+ # GitHub starts no workflow for a tag its own token creates. The tests and the package build run
4
+ # first; then the GitHub release is published with the built files and their SHA256SUMS, from the
5
+ # trunk only; then the same files go to PyPI through Trusted Publishing. PyPI trusts this file
6
+ # (publish.yml) running in the GitHub environment pypi, which accepts only v* tags, so the
7
+ # workflow uses no PyPI token.
8
+ #
9
+ # Tests, build and GitHub release run in the shared reusable workflows. The upload has to stay in
10
+ # this file: PyPI only trusts a job defined in the workflow file registered with it, never one
11
+ # from another repository's reusable workflow (pypi/warehouse#11096). Actions are pinned to full
12
+ # commit SHAs, with the release in a comment.
13
+
14
+ name: Publish
15
+
16
+ on:
17
+ push:
18
+ tags:
19
+ - 'v[0-9]+.[0-9]+.[0-9]+'
20
+ workflow_dispatch:
21
+
22
+ permissions: {}
23
+
24
+ # One release at a time. GitHub keeps only the newest waiting run of a group and cancels an older
25
+ # one; ci.yml starts one release and waits for it before the next.
26
+ concurrency:
27
+ group: publish
28
+ cancel-in-progress: false
29
+
30
+ jobs:
31
+ tests:
32
+ name: Tests
33
+ uses: agusgonzaleznic/github-reusable-workflows/.github/workflows/python-tests.yml@473236e155063a434af75d7da22312a2b6f27e54 # v1.6.0
34
+ permissions:
35
+ contents: read
36
+ with:
37
+ runs-on: ubuntu-24.04
38
+ requirements: requirements/action.txt requirements/test.txt
39
+ apt-packages: ghostscript
40
+ pythonpath: src
41
+
42
+ # Fails when the tag differs from the version in src/cvcite/__init__.py.
43
+ build:
44
+ name: Build
45
+ uses: agusgonzaleznic/github-reusable-workflows/.github/workflows/python-build.yml@8c19beae01a5978c5e7dd378f44307ab00dca651 # v1.7.0
46
+ permissions:
47
+ contents: read
48
+ with:
49
+ build-constraints: requirements/build.txt
50
+
51
+ # Refuses a tag that is not on main, or that moved since the run started.
52
+ release:
53
+ name: Release
54
+ needs: [tests, build]
55
+ uses: agusgonzaleznic/github-reusable-workflows/.github/workflows/github-release.yml@1832ea3171529c36e663d34965917c9f14694ce5 # v1.9.0
56
+ permissions:
57
+ contents: write
58
+ with:
59
+ artifact-name: python-dist
60
+
61
+ publish:
62
+ name: Publish to PyPI
63
+ needs: [build, release]
64
+ runs-on: ubuntu-24.04
65
+ timeout-minutes: 10
66
+ environment:
67
+ name: pypi
68
+ url: https://pypi.org/project/cvcite/${{ needs.build.outputs.version }}/
69
+ permissions:
70
+ id-token: write
71
+ steps:
72
+ - name: Download the built files
73
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
74
+ with:
75
+ name: python-dist
76
+ path: dist/
77
+
78
+ # A short-lived token from Trusted Publishing, and signed attestations for both files;
79
+ # there is no password input on purpose. The workflow at a tag never changes, so a re-run
80
+ # of this job must be able to finish an upload that stopped after the first file.
81
+ - name: Upload to PyPI
82
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
83
+ with:
84
+ skip-existing: true
cvcite-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,355 @@
1
+ Metadata-Version: 2.5
2
+ Name: cvcite
3
+ Version: 0.2.2
4
+ Summary: Build a CV from one master YAML file and an overlay per target role, and gate every PDF the way applicant tracking systems read it.
5
+ Project-URL: Source, https://github.com/agusgonzaleznic/cvcite
6
+ Project-URL: Issues, https://github.com/agusgonzaleznic/cvcite/issues
7
+ Project-URL: Changelog, https://github.com/agusgonzaleznic/cvcite/releases
8
+ Author: Agustin Gonzalez Nicolini
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ats,claude-code,cv,rendercv,resume,typst
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Office/Business
16
+ Requires-Python: >=3.12
17
+ Requires-Dist: pdfminer-six>=20260107
18
+ Requires-Dist: pypdf>=6.21
19
+ Requires-Dist: rendercv[full]==2.8
20
+ Requires-Dist: ruamel-yaml>=0.19
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest>=8.0; extra == 'test'
23
+ Description-Content-Type: text/markdown
24
+
25
+ <div align="center">
26
+
27
+ <img src="https://raw.githubusercontent.com/agusgonzaleznic/cvcite/main/assets/emblem.svg?sanitize=true" width="96" alt="cvcite emblem: a CV page with a check mark">
28
+
29
+ # cvcite
30
+
31
+ ### One master CV. A tailored PDF per role. Every claim traced to a fact.
32
+
33
+ **Build your CV from one master YAML file, tailor it for each kind of role with a small overlay, and
34
+ gate every PDF the way applicant tracking systems (ATS) read it.** Every number on the CV has to
35
+ appear in a fact of your ledger, or the build fails.
36
+
37
+ <br>
38
+
39
+ [![PyPI](https://img.shields.io/pypi/v/cvcite?style=for-the-badge&labelColor=1A1D23&color=555D6D&logo=pypi&logoColor=FAFAFA)](https://pypi.org/project/cvcite/)
40
+ [![Python](https://img.shields.io/badge/python-3.12%2B-555D6D?style=for-the-badge&labelColor=1A1D23&logo=python&logoColor=FAFAFA)](https://www.python.org/downloads/)
41
+ [![GitHub Action](https://img.shields.io/badge/GitHub_Action-composite-555D6D?style=for-the-badge&labelColor=1A1D23&logo=githubactions&logoColor=FAFAFA)](#in-github-actions)
42
+ [![Claude Code plugin](https://img.shields.io/badge/plugin-Claude_Code-555D6D?style=for-the-badge&labelColor=1A1D23)](#-with-claude-code)
43
+ <br>
44
+ [![CI](https://img.shields.io/github/actions/workflow/status/agusgonzaleznic/cvcite/ci.yml?branch=main&style=for-the-badge&labelColor=1A1D23&color=555D6D&label=CI)](https://github.com/agusgonzaleznic/cvcite/actions/workflows/ci.yml)
45
+ [![Licence](https://img.shields.io/badge/licence-MIT-555D6D?style=for-the-badge&labelColor=1A1D23)](https://github.com/agusgonzaleznic/cvcite/blob/main/LICENSE)
46
+
47
+ <br>
48
+
49
+ **[What it is](#-what-it-is) · [Quick start](#-quick-start) · [Claude Code](#-with-claude-code) · [GitHub Actions](#in-github-actions) · [Checks](#-what-gets-checked) · [Commands](#commands) · [Rendering](#how-it-renders) · [Limits](#-what-it-does-not-do) · [Release](#-how-a-release-happens) · [Development](#development) · [Support](#-support-this-project)**
50
+
51
+ </div>
52
+
53
+ ---
54
+
55
+ ## 🧭 What it is
56
+
57
+ It comes in three parts that work together or alone:
58
+
59
+ | Part | What it is | Where it lives |
60
+ |---|---|---|
61
+ | **Command line** | `cvcite` checks, renders and gates your CV. No AI involved. | [PyPI](https://pypi.org/project/cvcite/) |
62
+ | **Claude Code plugin** | Skills that interview you, one multiple-choice question at a time. They turn an old CV or a LinkedIn export into a fact ledger, check every date and claim, tailor the CV for a job posting, and compare LinkedIn, your website and GitHub against it. | [`plugin/`](https://github.com/agusgonzaleznic/cvcite/tree/main/plugin), listed in [`.claude-plugin/marketplace.json`](https://github.com/agusgonzaleznic/cvcite/blob/main/.claude-plugin/marketplace.json) |
63
+ | **GitHub Action** | Builds and gates the CV on every change, publishes the PDFs as a private release when you ask, and can run a claim review on pull requests with your own key. | [`action.yml`](https://github.com/agusgonzaleznic/cvcite/blob/main/action.yml) |
64
+
65
+ Your CV lives in your own private repository. This one holds the tool and a made-up sample person.
66
+
67
+ ### Why
68
+
69
+ | Problem | What cvcite does |
70
+ |---|---|
71
+ | **Two CVs drift apart.** | You keep one master with every fact. Each variant is an overlay that only changes emphasis: headline, summary, bolded words, skills line, which bullets to drop. |
72
+ | **CVs overclaim.** | Every number on the CV must appear in a fact of the ledger (`facts.yaml`), where you note its source, or the build fails. Numbers and named terms from facts marked `private` can't reach the CV. |
73
+ | **A PDF that looks right can read wrong.** | Parsers split hyphenated words, lose ligature glyphs, shatter text into one word per line, or read columns in the wrong order. The gate reads each PDF with two to four independent extractors and fails it unless they agree and every bullet survives whole. |
74
+
75
+ ```mermaid
76
+ flowchart LR
77
+ M["cv.yaml<br/>master"] --> C
78
+ F["facts.yaml<br/>ledger"] --> C
79
+ O["variants/*.yaml<br/>one overlay per role"] --> C
80
+ C{"cvcite check<br/>dates, claims, privacy"} -->|pass| R["Typst render<br/>via RenderCV"]
81
+ C -->|fail| X["that variant fails<br/>with the reason"]
82
+ R --> G{"gate<br/>extractors agree,<br/>contrast, pages"}
83
+ G -->|pass| P["one CV.pdf per variant<br/>in build/"]
84
+ G -->|fail| X
85
+
86
+ classDef default fill:#FFFFFF,stroke:#89909F,color:#1A1D23
87
+ classDef decide fill:#E9EAED,stroke:#555D6D,color:#1A1D23
88
+ classDef outcome fill:#FFFFFF,stroke:#DE3412,stroke-width:2px,color:#1A1D23
89
+ class C,G decide
90
+ class P outcome
91
+ ```
92
+
93
+ ---
94
+
95
+ <a id="quick-start"></a>
96
+
97
+ ## ⚡ Quick start
98
+
99
+ You need Python 3.12 or newer, which RenderCV 2.8 needs. [uv](https://docs.astral.sh/uv/) is the
100
+ easiest way to install.
101
+ Ghostscript is optional: it's a third extractor for the gate.
102
+
103
+ ```sh
104
+ uv tool install cvcite # or: pipx install cvcite
105
+ cvcite init my-cv --sample # try it with the made-up sample; drop --sample to start blank
106
+ cd my-cv
107
+ cvcite build # check, render and gate every variant
108
+ ```
109
+
110
+ `cvcite init` looks up the release's commit on github.com to pin the action, so run it online.
111
+ The PDFs land in `build/<variant>/`, one folder per variant, each holding the file under the same
112
+ name, so a recruiter can't tell that variants exist. Keep the repository private:
113
+
114
+ ```sh
115
+ git init -b main && git add -A && git commit -m "Start my CV"
116
+ gh repo create my-cv --private --source . --push
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 🧩 With Claude Code
122
+
123
+ ```text
124
+ /plugin install cvcite --marketplace agusgonzaleznic/cvcite
125
+ ```
126
+
127
+ Or in two steps: `/plugin marketplace add agusgonzaleznic/cvcite`, then
128
+ `/plugin install cvcite@cvcite`.
129
+
130
+ Then ask in your own words, for example "start my CV from inputs/old-cv.pdf" or "tailor my CV
131
+ for this posting: `<link>`". The skills:
132
+
133
+ | Skill | What it does |
134
+ |---|---|
135
+ | `cv-ingest` | Reads your old CV, LinkedIn export or JSON Resume, then builds the fact ledger with you, role by role, before writing the CV |
136
+ | `cv-fact-check` | Runs the checks, then an adversarial reviewer that tries to refute every claim; you settle each finding |
137
+ | `cv-tailor` | Maps a job posting's requirements to your facts, writes an overlay for it, builds and reviews it, and names the gaps honestly |
138
+ | `cv-presence-audit` | Compares LinkedIn, your site and GitHub with the CV, and writes paste-ready texts with character counts |
139
+ | `cv-build` | Explains every gate failure and how to fix it |
140
+
141
+ The skills follow the open Agent Skills format, so other tools that read `SKILL.md` files can use
142
+ them too. `cvcite init` also writes an `AGENTS.md` with the rules into your CV repository.
143
+
144
+ The plugin holds only its skills and the reviewer agent, in `plugin/`. The skills run the `cvcite`
145
+ command line you installed above, and they ask before installing it if it's missing. What the
146
+ plugin runs, fetches and sends is listed in
147
+ [`plugin/README.md`](https://github.com/agusgonzaleznic/cvcite/blob/main/plugin/README.md).
148
+
149
+ > [!NOTE]
150
+ > **LinkedIn is read only.** The audit works from LinkedIn's own data export or a profile saved as
151
+ > PDF. Nothing signs in to your account or edits it: LinkedIn's terms forbid automated access.
152
+
153
+ ---
154
+
155
+ <a id="in-github-actions"></a>
156
+
157
+ ## ⚙️ In GitHub Actions
158
+
159
+ `cvcite init` writes three workflows into your CV repository:
160
+
161
+ | Workflow | When it runs | What it does |
162
+ |---|---|---|
163
+ | `cv.yml` | Every pull request and every push to `main` | Checks, renders and gates every variant |
164
+ | `release.yml` | By hand | Publishes the build as a release you can download anywhere. It refuses to run in a public repository, because the PDFs carry your contact details. |
165
+ | `cv-review.yml` | Pull requests that change the CV, in a private repository | Runs the claim reviewer. It does nothing until you add an `ANTHROPIC_API_KEY` secret, and each run is billed to that key. |
166
+
167
+ The reviewer sends `AGENTS.md`, `facts.yaml`, `cv.yaml` (contact details included), the overlays and
168
+ the diff to Anthropic's API. Facts in `facts.private.yaml` are never committed, so they're never
169
+ sent. The review can't run commands: it may only read and list files, with git's settings,
170
+ home-folder dotfiles and system folders blocked, and it's posted as plain text. Use a key made for
171
+ this alone, with a spending limit.
172
+
173
+ The action runs cvcite from the release you pin, with every Python package taken from a lock with
174
+ its hashes and no package built from source. `init` pins it to that release's full commit SHA. The
175
+ action refuses to build in a repository that isn't private; set `allow-public: "true"` only for a
176
+ made-up CV, as this repository does for its sample.
177
+
178
+ | Input | Default | Meaning |
179
+ |---|---|---|
180
+ | `working-directory` | `.` | The CV repository's folder, relative to the workspace |
181
+ | `package` | `"false"` | Also zip the build with checksums into `.tmp/release`, as a release needs |
182
+ | `allow-public` | `"false"` | Build even if the repository isn't private (made-up CVs only) |
183
+
184
+ These guards stop mistakes. Anyone who can push to your CV repository can still change its
185
+ workflows.
186
+
187
+ ---
188
+
189
+ ## ✅ What gets checked
190
+
191
+ `cvcite check` (no rendering) and every build:
192
+
193
+ - Dates: format, order, future dates, overlaps, gaps over six months.
194
+ - Every number in a bullet is present in a fact of the ledger; none comes from a `private` fact,
195
+ and none of the `terms` a private fact lists. Keep private facts in `facts.private.yaml`, which
196
+ git ignores. Every fact's visibility is one of `public`, `cv-only` or `private`.
197
+ - Every skill on a skills line is backed by a bullet of the same variant, and an overlay never adds
198
+ a skill the master lacks.
199
+ - Only RenderCV's built-in themes, and no render settings that move files: a custom theme runs code
200
+ from the repository.
201
+ - No TODO, no banned character (em dashes by default), no RenderCV trap.
202
+ - Privacy: no photo, date of birth, nationality, marital status, street address, identity or bank
203
+ number, or WhatsApp link.
204
+
205
+ The gate, on every PDF:
206
+
207
+ - The extractors agree on the words and their order, and every bullet appears whole in each.
208
+ - No word hyphenated across a line by the typesetter.
209
+ - No text layer shattered into single words.
210
+ - Every glyph plainly filled, in a colour with at least 4.5:1 contrast on white.
211
+ - The page count matches the budget in `cvcite.toml`.
212
+
213
+ The checks are tested in
214
+ [`tests/test_checks.py`](https://github.com/agusgonzaleznic/cvcite/blob/main/tests/test_checks.py),
215
+ and the gate in
216
+ [`tests/test_gate.py`](https://github.com/agusgonzaleznic/cvcite/blob/main/tests/test_gate.py),
217
+ which plants each defect (ligatures on, kerning on, typesetter hyphenation, light or invisible text,
218
+ shattered text, a dropped bullet), either in the rendered PDF or in one extractor's reading, and
219
+ expects the gate to fail.
220
+
221
+ ---
222
+
223
+ <a id="commands"></a>
224
+
225
+ ## ⌨️ Commands
226
+
227
+ | Command | What it does |
228
+ |---|---|
229
+ | `cvcite init [--sample]` | Creates a CV repository |
230
+ | `cvcite check` | The checks above, without rendering |
231
+ | `cvcite build [variant...]` | Check, render and gate; `-v` prints the full gate report |
232
+ | `cvcite gate <pdf> [yaml]` | Gates one PDF |
233
+ | `cvcite package` | Zips the build with checksums, as a release does |
234
+ | `cvcite ingest <file...>` | Text or data out of a CV (PDF, DOCX, ODT, HTML, Markdown), a LinkedIn export or a JSON Resume |
235
+ | `cvcite compare --linkedin <zip>` | The CV's roles against a LinkedIn export: titles and dates |
236
+ | `cvcite doctor` | Checks the toolchain |
237
+
238
+ ---
239
+
240
+ <a id="how-it-renders"></a>
241
+
242
+ ## 🖨️ How it renders
243
+
244
+ [RenderCV](https://github.com/rendercv/rendercv) turns the YAML into [Typst](https://typst.app) and
245
+ then into the PDF. cvcite renders with ligatures and kerning off: with ligatures on, one extractor
246
+ lost every word containing "ff", "ffi" or "ffl", and with kerning on, another read a bold "Airflow"
247
+ as two words. It also ships the one Typst package RenderCV's template imports and switches off
248
+ RenderCV's check for a newer release (a PyPI request made without verifying TLS), so a render makes
249
+ no network request and writes nothing outside the CV repository: the PDFs go to `build/` and the
250
+ scratch files to the git-ignored `.tmp/`.
251
+
252
+ ---
253
+
254
+ ## 🚫 What it does not do
255
+
256
+ - **It doesn't run any vendor's ATS.** The gate reads each PDF with open extractors (pypdf,
257
+ pdfminer.six, and Ghostscript and pdftotext when installed). Agreement between them is a strong
258
+ signal, not a promise about a specific employer's system.
259
+ - **It doesn't sign in to LinkedIn or edit any profile.** You paste the texts the audit writes.
260
+ - **It doesn't host your CV.** The PDFs stay in your private repository or in a private release.
261
+ - **It doesn't accept custom RenderCV themes** or settings that move files, because those run code
262
+ from the repository.
263
+ - **It doesn't make a public repository safe for a real CV.** The guards refuse to build or release
264
+ there, but they stop mistakes, not someone who can edit your workflows.
265
+
266
+ ---
267
+
268
+ ## 🔁 How a release happens
269
+
270
+ The merge of a pull request is the release. Nobody pushes a tag by hand.
271
+
272
+ ```mermaid
273
+ flowchart TD
274
+ A["Open a pull request"] --> B{"Does it change the package, README.md,<br/>LICENSE, the Action or the plugin?"}
275
+ B -->|no| C["Merge: nothing is released"]
276
+ B -->|yes| D["Raise the version in the package,<br/>the plugin manifest and the cv-ingest skill"]
277
+ D --> E["Merge to main"]
278
+ E --> F["Tests, the Action on the sample<br/>and the package build pass on main"]
279
+ F --> G["The Release job tags vX.Y.Z<br/>when the version is new"]
280
+ G --> H["publish.yml on the tag:<br/>tests, build, GitHub release, PyPI"]
281
+ H --> I["cvcite X.Y.Z is published"]
282
+
283
+ classDef default fill:#FFFFFF,stroke:#89909F,color:#1A1D23
284
+ classDef decide fill:#E9EAED,stroke:#555D6D,color:#1A1D23
285
+ classDef outcome fill:#FFFFFF,stroke:#DE3412,stroke-width:2px,color:#1A1D23
286
+ class B decide
287
+ class I outcome
288
+ ```
289
+
290
+ - A pull request that changes what ships (`src/cvcite/`, `pyproject.toml`, this README, `LICENSE`,
291
+ `action.yml` and the requirements it installs, `plugin/` or `.claude-plugin/`) raises the version
292
+ in `src/cvcite/__init__.py`, `plugin/.claude-plugin/plugin.json` and the install lines of the
293
+ cv-ingest skill. Its check `Release / Check the version` fails until it does, and a test fails
294
+ while the three disagree.
295
+ - The merge tags `vX.Y.Z` on main, and that tag is the Action's release too. `publish.yml` runs the
296
+ tests and the package build again on the tag, against the hash-locked build backend in
297
+ `requirements/build.txt`, publishes the GitHub release with the wheel, the source archive and
298
+ their `SHA256SUMS`, then uploads the same files to PyPI.
299
+ - The PyPI upload uses Trusted Publishing, so the workflow uses no PyPI token, and the `pypi`
300
+ environment accepts only `v*` tags.
301
+
302
+ ---
303
+
304
+ <a id="development"></a>
305
+
306
+ ## 🛠️ Development
307
+
308
+ ```sh
309
+ uv venv --python 3.13 && uv pip install -e ".[test]"
310
+ .venv/bin/python -m pytest
311
+ ```
312
+
313
+ Pull requests run the tests, the action end to end on the made-up sample, the package build and a
314
+ lint of every workflow, including the ones `cvcite init` writes. To report a security problem,
315
+ follow the [shared security policy](https://github.com/agusgonzaleznic/.github/blob/main/SECURITY.md).
316
+ The general rules are in the shared
317
+ [contributing guide](https://github.com/agusgonzaleznic/.github/blob/main/CONTRIBUTING.md).
318
+
319
+ ---
320
+
321
+ ## 💛 Support this project
322
+
323
+ It's free, and there's nothing to upsell.
324
+
325
+ Three things help:
326
+
327
+ - **Star the repo.** It's how the next person with the same problem finds it.
328
+ - **Open an issue** when a check is wrong or a gate failure confuses you. A CV that fails for a
329
+ reason the message doesn't explain is a bug.
330
+ - **Buy me a coffee on Ko-fi, or sponsor on GitHub**, if it saved you an evening. Ko-fi is a
331
+ one-off, and GitHub Sponsors can be monthly or one-time, with a custom amount.
332
+
333
+ <div align="center">
334
+ <br>
335
+
336
+ [![Ko-fi](https://img.shields.io/badge/Ko--fi-one--off-555D6D?style=for-the-badge&labelColor=1A1D23&logo=kofi&logoColor=FAFAFA)](https://ko-fi.com/agusgonzaleznic)
337
+ [![GitHub Sponsors](https://img.shields.io/badge/GitHub_Sponsors-monthly-555D6D?style=for-the-badge&labelColor=1A1D23&logo=githubsponsors&logoColor=FAFAFA)](https://github.com/sponsors/agusgonzaleznic)
338
+ <br>
339
+ [![website](https://img.shields.io/badge/website-agusgonzaleznic.com-555D6D?style=for-the-badge&labelColor=1A1D23)](https://agusgonzaleznic.com)
340
+ [![GitHub](https://img.shields.io/badge/GitHub-agusgonzaleznic-555D6D?style=for-the-badge&labelColor=1A1D23&logo=github&logoColor=FAFAFA)](https://github.com/agusgonzaleznic)
341
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-agusgonzaleznic-555D6D?style=for-the-badge&labelColor=1A1D23)](https://www.linkedin.com/in/agusgonzaleznic)
342
+
343
+ <br>
344
+
345
+ <sub>Built in Berlin by <b>Agustin Gonzalez Nicolini</b>. I work on engineering leadership and write
346
+ about it at <a href="https://agusgonzaleznic.com">agusgonzaleznic.com</a>.</sub>
347
+
348
+ </div>
349
+
350
+ ---
351
+
352
+ ## 📄 Licence
353
+
354
+ [MIT](https://github.com/agusgonzaleznic/cvcite/blob/main/LICENSE). RenderCV is MIT-licensed; the vendored Typst package `fontawesome` 0.6.0 is
355
+ MIT-licensed, with its licence in `src/cvcite/vendor/`.