content-core 2.0.7__tar.gz → 2.2.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.
- {content_core-2.0.7 → content_core-2.2.0}/.claude-plugin/plugin.json +1 -1
- {content_core-2.0.7 → content_core-2.2.0}/.codex-plugin/plugin.json +1 -1
- content_core-2.2.0/.gitattributes +2 -0
- {content_core-2.0.7 → content_core-2.2.0}/.github/workflows/claude-code-review.yml +5 -5
- {content_core-2.0.7 → content_core-2.2.0}/.github/workflows/create-tag.yml +2 -1
- {content_core-2.0.7 → content_core-2.2.0}/.github/workflows/package.yml +36 -4
- content_core-2.2.0/.github/workflows/publish.yml +52 -0
- {content_core-2.0.7 → content_core-2.2.0}/.gitignore +4 -0
- content_core-2.2.0/.maintainer/PROFILE.md +11 -0
- content_core-2.2.0/.maintainer/README.md +15 -0
- content_core-2.2.0/.maintainer/gotchas.md +31 -0
- content_core-2.2.0/.maintainer/profile.toml +51 -0
- content_core-2.2.0/.maintainer/release/runbook.md +128 -0
- content_core-2.2.0/.maintainer/release/test-matrix.md +23 -0
- content_core-2.0.7/CLAUDE.md → content_core-2.2.0/AGENTS.md +21 -11
- {content_core-2.0.7 → content_core-2.2.0}/CHANGELOG.md +47 -0
- content_core-2.2.0/CLAUDE.md +1 -0
- {content_core-2.0.7 → content_core-2.2.0}/CONTRIBUTING.md +20 -0
- {content_core-2.0.7 → content_core-2.2.0}/Makefile +8 -2
- {content_core-2.0.7 → content_core-2.2.0}/PKG-INFO +22 -12
- {content_core-2.0.7 → content_core-2.2.0}/README.md +12 -3
- {content_core-2.0.7 → content_core-2.2.0}/REFACTOR.md +1 -1
- {content_core-2.0.7 → content_core-2.2.0}/TEST_PLAN.md +0 -1
- {content_core-2.0.7 → content_core-2.2.0}/docs/cli.md +13 -2
- {content_core-2.0.7 → content_core-2.2.0}/docs/mcp.md +6 -1
- {content_core-2.0.7 → content_core-2.2.0}/docs/processors.md +11 -1
- {content_core-2.0.7 → content_core-2.2.0}/docs/usage.md +111 -2
- {content_core-2.0.7 → content_core-2.2.0}/pyproject.toml +12 -11
- content_core-2.2.0/src/content_core/__init__.py +52 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/cli.py +17 -7
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/common/__init__.py +11 -0
- content_core-2.2.0/src/content_core/common/exceptions.py +65 -0
- content_core-2.2.0/src/content_core/common/messages.py +15 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/common/retry.py +33 -1
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/config.py +45 -5
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/content/identification/file_detector.py +17 -2
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/extraction.py +85 -16
- content_core-2.2.0/src/content_core/logging.py +54 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/mcp/server.py +44 -9
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/document/__init__.py +24 -1
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/document/docling.py +3 -8
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/document/epub.py +2 -1
- content_core-2.2.0/src/content_core/processors/document/odf.py +218 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/document/pdf.py +3 -2
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/media/audio.py +75 -43
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/media/video.py +44 -59
- content_core-2.2.0/src/content_core/processors/text.py +139 -0
- content_core-2.2.0/src/content_core/processors/url/__init__.py +217 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/url/bs4.py +10 -12
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/url/crawl4ai.py +27 -8
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/url/firecrawl.py +3 -3
- content_core-2.2.0/src/content_core/processors/url/youtube.py +371 -0
- content_core-2.2.0/tests/input_content/file.odp +0 -0
- content_core-2.2.0/tests/input_content/file.ods +0 -0
- content_core-2.2.0/tests/input_content/file.odt +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/integration/test_extraction.py +54 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_cli.py +8 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_config_file.py +43 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_config_v2.py +73 -2
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_crawl4ai_docker.py +15 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_epub_extraction.py +8 -0
- content_core-2.2.0/tests/unit/test_exceptions.py +84 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_file_detector.py +106 -1
- content_core-2.2.0/tests/unit/test_html_file_extraction.py +162 -0
- content_core-2.2.0/tests/unit/test_logging.py +156 -0
- content_core-2.2.0/tests/unit/test_mcp_server_version.py +9 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_mcp_v2.py +22 -1
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_media_pipeline.py +188 -1
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_models_v2.py +1 -23
- content_core-2.2.0/tests/unit/test_office_extraction.py +340 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_pdf_extraction.py +22 -1
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_retry.py +54 -1
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_routing.py +292 -3
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_text_processing.py +70 -1
- content_core-2.2.0/tests/unit/test_url_engine_select.py +519 -0
- content_core-2.2.0/tests/unit/test_youtube_parsing.py +517 -0
- content_core-2.2.0/uv.lock +7720 -0
- content_core-2.0.7/.github/workflows/publish.yml +0 -42
- content_core-2.0.7/src/content_core/__init__.py +0 -27
- content_core-2.0.7/src/content_core/common/exceptions.py +0 -70
- content_core-2.0.7/src/content_core/logging.py +0 -15
- content_core-2.0.7/src/content_core/processors/protocol.py +0 -18
- content_core-2.0.7/src/content_core/processors/text.py +0 -83
- content_core-2.0.7/src/content_core/processors/url/__init__.py +0 -151
- content_core-2.0.7/src/content_core/processors/url/youtube.py +0 -216
- content_core-2.0.7/tests/unit/test_office_extraction.py +0 -99
- content_core-2.0.7/tests/unit/test_url_engine_select.py +0 -142
- content_core-2.0.7/tests/unit/test_youtube_parsing.py +0 -163
- content_core-2.0.7/uv.lock +0 -6687
- {content_core-2.0.7 → content_core-2.2.0}/.agent-smith/index.json +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/.agents/plugins/marketplace.json +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/.claude-plugin/marketplace.json +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/.github/workflows/claude.yml +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/.github/workflows/test.yml +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/.python-version +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/ARCHITECTURE.md +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/LICENSE +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/examples/main.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/prompts/content/cleanup.jinja +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/prompts/content/summarize.jinja +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/skills/content-core/SKILL.md +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/common/state.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/common/types.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/content/__init__.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/content/extraction/__init__.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/content/identification/__init__.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/content/summary/__init__.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/content/summary/core.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/mcp/__init__.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/models.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/document/docx.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/document/pptx.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/document/xlsx.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/media/__init__.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/url/jina.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/processors/url/reddit.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/py.typed +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/templated_message.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/tools/__init__.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/tools/extract.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/src/content_core/tools/summarize.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/conftest.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/e2e/__init__.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/e2e/test_docling.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/e2e/test_media.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/e2e/test_remote.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/e2e/test_url_engines.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/e2e/test_youtube.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.docx +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.epub +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.md +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.mp3 +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.mp4 +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.pdf +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.pptx +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.txt +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/file.xlsx +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/input_content/new_pdf.pdf +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/integration/conftest.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/integration/test_cli_v2.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_audio_concurrency.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_docling_extraction.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_file_detector_critical.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_file_detector_performance.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_pdf_helpers.py +0 -0
- {content_core-2.0.7 → content_core-2.2.0}/tests/unit/test_reddit_extraction.py +0 -0
|
@@ -12,11 +12,11 @@ on:
|
|
|
12
12
|
|
|
13
13
|
jobs:
|
|
14
14
|
claude-review:
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
|
|
15
|
+
# Fork PRs get no repository secrets, so the review could only fail there.
|
|
16
|
+
# (pull_request_target would expose the secrets to untrusted code.)
|
|
17
|
+
# To also filter by PR author, extend this expression with `&&` -- a job
|
|
18
|
+
# takes a single `if:`.
|
|
19
|
+
if: github.event.pull_request.head.repo.full_name == github.repository
|
|
20
20
|
|
|
21
21
|
runs-on: ubuntu-latest
|
|
22
22
|
permissions:
|
|
@@ -53,4 +53,5 @@ jobs:
|
|
|
53
53
|
if: steps.check.outputs.exists == 'false'
|
|
54
54
|
run: |
|
|
55
55
|
echo "✨ Tag v${{ steps.version.outputs.version }} has been created!"
|
|
56
|
-
echo "
|
|
56
|
+
echo "Publish a GitHub Release for this tag to start the PyPI workflow."
|
|
57
|
+
echo "Creating the tag alone does not publish to PyPI."
|
|
@@ -2,15 +2,25 @@ name: Package
|
|
|
2
2
|
|
|
3
3
|
# Reusable packaging gate: builds the wheel and exercises all three consumer
|
|
4
4
|
# surfaces (library, CLI, MCP server) from the built artifact, with the repo
|
|
5
|
-
# off the path. Called by test.yml (PRs / main) and by publish.yml (
|
|
6
|
-
# a
|
|
5
|
+
# off the path. Called by test.yml (PRs / main) and by publish.yml (published
|
|
6
|
+
# GitHub Releases), so a release cannot upload an artifact that failed this gate.
|
|
7
|
+
#
|
|
8
|
+
# The validated dist/ is uploaded as an artifact so publish.yml can release the
|
|
9
|
+
# exact files this gate checked, instead of rebuilding on another runner.
|
|
7
10
|
on:
|
|
8
11
|
workflow_call:
|
|
12
|
+
outputs:
|
|
13
|
+
artifact-name:
|
|
14
|
+
description: "Name of the uploaded artifact holding the validated dist/"
|
|
15
|
+
value: ${{ jobs.package.outputs.artifact-name }}
|
|
9
16
|
|
|
10
17
|
jobs:
|
|
11
18
|
package:
|
|
12
19
|
runs-on: ubuntu-latest
|
|
13
20
|
|
|
21
|
+
outputs:
|
|
22
|
+
artifact-name: ${{ steps.artifact.outputs.name }}
|
|
23
|
+
|
|
14
24
|
steps:
|
|
15
25
|
- uses: actions/checkout@v4
|
|
16
26
|
|
|
@@ -22,6 +32,12 @@ jobs:
|
|
|
22
32
|
- name: Install uv
|
|
23
33
|
uses: astral-sh/setup-uv@v4
|
|
24
34
|
|
|
35
|
+
# Unique per run attempt: upload-artifact@v4 rejects a duplicate name within
|
|
36
|
+
# a run, which would otherwise break "re-run failed jobs".
|
|
37
|
+
- name: Compute artifact name
|
|
38
|
+
id: artifact
|
|
39
|
+
run: echo "name=dist-${{ github.run_id }}-${{ github.run_attempt }}" >> "$GITHUB_OUTPUT"
|
|
40
|
+
|
|
25
41
|
- name: Build wheel and sdist
|
|
26
42
|
run: |
|
|
27
43
|
rm -rf dist
|
|
@@ -90,10 +106,12 @@ jobs:
|
|
|
90
106
|
uv run --isolated --no-project --with dist/content_core-*.whl \
|
|
91
107
|
python "$RUNNER_TEMP/mcp_smoke.py"
|
|
92
108
|
|
|
93
|
-
- name: Wheel version matches
|
|
109
|
+
- name: Wheel version matches project and release tag
|
|
110
|
+
env:
|
|
111
|
+
RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
|
|
94
112
|
run: |
|
|
95
113
|
python - <<'PY'
|
|
96
|
-
import glob, pathlib, re, sys
|
|
114
|
+
import glob, os, pathlib, re, sys
|
|
97
115
|
|
|
98
116
|
# tomllib is 3.11+, this job runs on 3.10 - read the [project] version directly.
|
|
99
117
|
text = pathlib.Path("pyproject.toml").read_text()
|
|
@@ -115,5 +133,19 @@ jobs:
|
|
|
115
133
|
sys.exit(f"could not parse version from wheel name {name!r}")
|
|
116
134
|
if match.group(1) != expected:
|
|
117
135
|
sys.exit(f"wheel version {match.group(1)!r} != pyproject version {expected!r}")
|
|
136
|
+
release_tag = os.environ.get("RELEASE_TAG", "")
|
|
137
|
+
if release_tag and release_tag not in (expected, f"v{expected}"):
|
|
138
|
+
sys.exit(f"release tag {release_tag!r} != project version {expected!r}")
|
|
118
139
|
print(f"version match OK: {expected}")
|
|
119
140
|
PY
|
|
141
|
+
|
|
142
|
+
# Publish.yml downloads this instead of rebuilding, so the released files
|
|
143
|
+
# are byte-for-byte the ones validated above. On PR runs it is just an
|
|
144
|
+
# unused short-lived upload.
|
|
145
|
+
- name: Upload validated dist
|
|
146
|
+
uses: actions/upload-artifact@v4
|
|
147
|
+
with:
|
|
148
|
+
name: ${{ steps.artifact.outputs.name }}
|
|
149
|
+
path: dist/
|
|
150
|
+
if-no-files-found: error
|
|
151
|
+
retention-days: 1
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
package:
|
|
9
|
+
uses: ./.github/workflows/package.yml
|
|
10
|
+
|
|
11
|
+
# Publishes the exact artifact the packaging gate validated - it is downloaded
|
|
12
|
+
# from the package job rather than rebuilt here, so a green gate always covers
|
|
13
|
+
# the files that reach PyPI (uploads are immutable, a rebuild could differ).
|
|
14
|
+
publish:
|
|
15
|
+
needs: package
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.10"
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v4
|
|
26
|
+
|
|
27
|
+
- name: Download validated dist
|
|
28
|
+
uses: actions/download-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: ${{ needs.package.outputs.artifact-name }}
|
|
31
|
+
path: dist
|
|
32
|
+
|
|
33
|
+
- name: Verify downloaded dist
|
|
34
|
+
run: |
|
|
35
|
+
set -euo pipefail
|
|
36
|
+
ls -la dist/
|
|
37
|
+
python - <<'PY'
|
|
38
|
+
import glob, sys
|
|
39
|
+
|
|
40
|
+
wheels = glob.glob("dist/content_core-*.whl")
|
|
41
|
+
sdists = glob.glob("dist/content_core-*.tar.gz")
|
|
42
|
+
if len(wheels) != 1:
|
|
43
|
+
sys.exit(f"expected exactly one wheel in dist/, got {wheels}")
|
|
44
|
+
if len(sdists) != 1:
|
|
45
|
+
sys.exit(f"expected exactly one sdist in dist/, got {sdists}")
|
|
46
|
+
print("dist/ OK:", wheels + sdists)
|
|
47
|
+
PY
|
|
48
|
+
|
|
49
|
+
- name: Publish to PyPI
|
|
50
|
+
env:
|
|
51
|
+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
52
|
+
run: uv publish --token "$PYPI_TOKEN"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Scope and communication
|
|
2
|
+
|
|
3
|
+
Content Core owns its extraction and summarization API, format processors, engine
|
|
4
|
+
routing, configuration, CLI, MCP server, package metadata and plugin manifests.
|
|
5
|
+
Hosted providers, upstream dependencies and their availability are external.
|
|
6
|
+
|
|
7
|
+
State observed behavior and validation limits directly. Credit contributors in
|
|
8
|
+
release notes. Do not promise upstream availability or delivery dates.
|
|
9
|
+
|
|
10
|
+
Do not publish credentials, local configuration, private paths, user input content
|
|
11
|
+
or raw logs containing those values. Publish evidence summaries and public CI links.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Maintainer workflows
|
|
2
|
+
|
|
3
|
+
This directory configures the oss-maintainer release workflow for Content Core.
|
|
4
|
+
It is not a list of maintainers.
|
|
5
|
+
|
|
6
|
+
- `profile.toml`: commands, required evidence, delivery and publication policy.
|
|
7
|
+
- `PROFILE.md`: scope and public communication conventions.
|
|
8
|
+
- `release/runbook.md`: release preparation, artifact checks and verification.
|
|
9
|
+
- `release/test-matrix.md`: risk-based checks; instantiate on each candidate.
|
|
10
|
+
- `gotchas.md`: verified limitations and lessons.
|
|
11
|
+
- `state/`: ignored run records, logs and artifact identities.
|
|
12
|
+
- `profile.local.toml`: optional ignored preferences; cannot weaken shared gates.
|
|
13
|
+
|
|
14
|
+
The canonical commands remain in `AGENTS.md`, `Makefile` and the existing GitHub
|
|
15
|
+
workflows. This profile configures releases only; other capabilities are not onboarded.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Release gotchas
|
|
2
|
+
|
|
3
|
+
- `make release` publishes the existing draft GitHub Release and starts PyPI
|
|
4
|
+
distribution through `release: published`. It requires the exact candidate GO.
|
|
5
|
+
`make tag` only creates/pushes the version tag under the current workflow; saving
|
|
6
|
+
a draft release does not distribute to PyPI. Tags pointing to old workflow
|
|
7
|
+
revisions can still carry the former tag-push trigger; do not use them for a cut.
|
|
8
|
+
- The GitHub Release must reference the validated candidate. Verify the remote
|
|
9
|
+
tag's resolved commit before publication. The Package job rejects a release tag
|
|
10
|
+
that differs from the project version (with or without the `v` prefix).
|
|
11
|
+
- `published` covers releases published directly or from drafts, including
|
|
12
|
+
prereleases. Editing an already published release does not trigger a new upload.
|
|
13
|
+
- `Publish` builds again in its reusable Package job, then uploads those validated
|
|
14
|
+
bytes to PyPI without rebuilding in the publish job. Pre-GO and release-run builds
|
|
15
|
+
are separate artifacts; compare hashes and verify the actual registry files.
|
|
16
|
+
- `uv build` alone is not the package gate. Exercise the installed library, CLI
|
|
17
|
+
and real MCP initialization response outside the checkout.
|
|
18
|
+
- `uv run --isolated --no-project` does not itself isolate Python imports. Use
|
|
19
|
+
a temporary working directory, absolute wheel/fixture paths and `python -I`.
|
|
20
|
+
- `make ruff` modifies files. Lint and mypy are not release gates (`AGENTS.md`).
|
|
21
|
+
- E2E suites may call paid services or download large models. Select and authorize
|
|
22
|
+
their scope before execution; skips and missing credentials are not passes.
|
|
23
|
+
- The default `python3` on the current maintainer machine is older than the plugin
|
|
24
|
+
scripts require. Use an available Python >=3.11 for profile/run-record scripts;
|
|
25
|
+
this does not change the package's Python >=3.10 compatibility requirement.
|
|
26
|
+
- On 2026-09-05 the Dependabot API returned HTTP 403 with alerts disabled. Security
|
|
27
|
+
alerts were unverified, not absent. Recheck on each release.
|
|
28
|
+
- `released` is not currently a repository label. Creating it or applying release
|
|
29
|
+
labels requires the owner's agreement; do not create labels during bootstrap.
|
|
30
|
+
- `.gitattributes` export-ignore affects Git archives, not necessarily Hatch's
|
|
31
|
+
sdist selection. Inspect the built sdist for accidental local-state inclusion.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
schema_version = 1
|
|
2
|
+
|
|
3
|
+
[project]
|
|
4
|
+
name = "Content Core"
|
|
5
|
+
repo = "lfnovo/content-core"
|
|
6
|
+
artifact = "pypi-library"
|
|
7
|
+
commands_doc = "AGENTS.md"
|
|
8
|
+
|
|
9
|
+
[commands.validator]
|
|
10
|
+
run = "make test"
|
|
11
|
+
cwd = "."
|
|
12
|
+
timeout = "15m"
|
|
13
|
+
|
|
14
|
+
[contributing]
|
|
15
|
+
conventions = "CONTRIBUTING.md"
|
|
16
|
+
|
|
17
|
+
[release]
|
|
18
|
+
process_doc = ".maintainer/release/runbook.md"
|
|
19
|
+
change_delivery = "pr"
|
|
20
|
+
versioning = "repository"
|
|
21
|
+
changelog = "CHANGELOG.md"
|
|
22
|
+
version_files = ["pyproject.toml", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json", "uv.lock"]
|
|
23
|
+
lock_command = "uv sync"
|
|
24
|
+
distribution_trigger = "make release"
|
|
25
|
+
publish_workflow = ".github/workflows/publish.yml"
|
|
26
|
+
notes_source = "CHANGELOG.md"
|
|
27
|
+
consumer_surfaces = ["public Python API", "CLI commands and flags", "MCP tools and parameters", "configuration keys and environment variables", "supported formats and engines"]
|
|
28
|
+
|
|
29
|
+
[release.gates]
|
|
30
|
+
mandatory = ["validator", "package-gate", "bucket-c", "notes-approved"]
|
|
31
|
+
optional = ["security-alerts"]
|
|
32
|
+
not_gates = ["ruff", "mypy", "test-e2e", "test-e2e-heavy"]
|
|
33
|
+
merge_own_prs = "ask-once-per-session"
|
|
34
|
+
alerts_policy = "Report alerts when accessible. Disabled or inaccessible alerts remain not-run; never report zero alerts from an API error. Report known unresolved vulnerabilities to the owner before GO."
|
|
35
|
+
|
|
36
|
+
[artifacts.pypi]
|
|
37
|
+
package = "content-core"
|
|
38
|
+
# Existing build entry point. package-gate passes only after ALL the runbook's
|
|
39
|
+
# artifact-gate steps, including clean-room smokes and extras installation.
|
|
40
|
+
gate = "uv build"
|
|
41
|
+
surfaces = ["library", "cli", "mcp"]
|
|
42
|
+
extras = ["docling", "crawl4ai", "langchain"]
|
|
43
|
+
identity = "sha256"
|
|
44
|
+
|
|
45
|
+
[comms]
|
|
46
|
+
owner_language = "pt-BR"
|
|
47
|
+
public_language = "en"
|
|
48
|
+
agent_attribution = "none"
|
|
49
|
+
|
|
50
|
+
[channels]
|
|
51
|
+
announce = []
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Release runbook
|
|
2
|
+
|
|
3
|
+
## Agreement and versioning
|
|
4
|
+
|
|
5
|
+
Follow `CONTRIBUTING.md` for PR delivery, `AGENTS.md` for canonical validation,
|
|
6
|
+
`ARCHITECTURE.md` for compatibility decisions and `CHANGELOG.md` for SemVer.
|
|
7
|
+
The existing architecture decision treats fixes to hidden extraction errors as
|
|
8
|
+
2.x fixes. New supported formats and public API additions are minor changes;
|
|
9
|
+
breaking supported consumer contracts require explicit version review. The owner
|
|
10
|
+
decides the release number. Artifact: pypi-library; distribution trigger: `make release`,
|
|
11
|
+
which publishes the reviewed draft GitHub Release (`release: published`).
|
|
12
|
+
|
|
13
|
+
Prepare changes in a branch and PR. Required branch protections and review remain
|
|
14
|
+
binding. Ask once per session before merging qualifying own PRs. A merge to main
|
|
15
|
+
runs tests but does not publish. Creating/pushing a tag or saving a draft release
|
|
16
|
+
does not publish to PyPI when the tag contains the current workflow. Only the exact
|
|
17
|
+
final candidate GO permits `make release`.
|
|
18
|
+
|
|
19
|
+
## Scope and source validation
|
|
20
|
+
|
|
21
|
+
Fetch tags after bootstrap. Compare the latest release with the candidate; audit
|
|
22
|
+
all behavior changes and their issue/PR references against the changelog. Record
|
|
23
|
+
the candidate SHA, pre-existing working-tree changes and chosen checks under
|
|
24
|
+
`.maintainer/state/`. Keep user files, including untracked files, intact.
|
|
25
|
+
|
|
26
|
+
Run `make test` from the candidate checkout and retain the log/exit status.
|
|
27
|
+
Require successful Test workflow jobs for Python 3.10, 3.11 and 3.12, plus Package,
|
|
28
|
+
on the final integrated candidate. `AGENTS.md` explicitly excludes ruff, mypy and
|
|
29
|
+
broad external-service E2E suites from validator gates. The selected plan defines
|
|
30
|
+
any optional external checks and authorized costs before those checks run.
|
|
31
|
+
|
|
32
|
+
## Artifact gate
|
|
33
|
+
|
|
34
|
+
The canonical builder is `uv build`; the canonical smoke implementations are
|
|
35
|
+
in `.github/workflows/package.yml`. A successful build alone is insufficient.
|
|
36
|
+
|
|
37
|
+
1. Use a fresh candidate checkout or an empty release-owned `dist/`; run `uv build`.
|
|
38
|
+
2. Require exactly one wheel and one sdist. Compare their version with
|
|
39
|
+
`pyproject.toml`, both plugin manifests and the content-core entry in `uv.lock`.
|
|
40
|
+
3. Run the workflow's runtime-assets inspection against this wheel. Inspect the
|
|
41
|
+
sdist too; exclude local state, credentials and accidental development artifacts.
|
|
42
|
+
4. Resolve the wheel and fixture paths to absolute paths. In a fresh temporary
|
|
43
|
+
working directory, use the workflow's `uv run --isolated --no-project --with`
|
|
44
|
+
commands against that wheel; use `python -I` for Python probes. Prove the module
|
|
45
|
+
origin is an installed environment, not this checkout. Import `content_core`,
|
|
46
|
+
its public API and public exception exports. Call `extract_content` on the
|
|
47
|
+
existing `tests/input_content/file.txt` fixture and assert non-empty content.
|
|
48
|
+
5. Exercise `content-core --help`, `content-core --version`, and the workflow's
|
|
49
|
+
real TXT and PDF extraction commands using absolute fixture paths. Check that
|
|
50
|
+
the JSON result contains expected extracted content.
|
|
51
|
+
6. Run the workflow's real stdio `initialize` handshake with `content-core-mcp`
|
|
52
|
+
from the isolated directory. Require a JSON-RPC result without an error and
|
|
53
|
+
`serverInfo.version` equal to the candidate version. Retain stdout/stderr.
|
|
54
|
+
7. Install each declared extra (`docling`, `crawl4ai`, `langchain`) with the exact
|
|
55
|
+
wheel in an isolated environment and check the dependency installation succeeds.
|
|
56
|
+
Installation is not evidence of successful live provider calls or model runs.
|
|
57
|
+
8. Record SHA-256 for both artifacts and link every result to the tested bytes.
|
|
58
|
+
Retain artifacts until post-publication verification finishes.
|
|
59
|
+
|
|
60
|
+
The release agent adapts the existing workflow commands to absolute paths in the
|
|
61
|
+
execution plan, before running them. It must not substitute editable/source tests
|
|
62
|
+
for these checks. Re-run affected checks after fixes and the full final artifact
|
|
63
|
+
gate after the version cut is integrated.
|
|
64
|
+
|
|
65
|
+
## Manual scope
|
|
66
|
+
|
|
67
|
+
The matrix distinguishes automated regression coverage from optional live checks.
|
|
68
|
+
The owner reviews results and explicit unverified paths; that acknowledgment is
|
|
69
|
+
`bucket-c`. If a manual check is made mandatory for the candidate, it must actually
|
|
70
|
+
pass. Missing credentials remain unverified and never receive an inferred pass.
|
|
71
|
+
Neither this profile nor its approval authorizes paid calls or large model runs.
|
|
72
|
+
|
|
73
|
+
## Cut and notes
|
|
74
|
+
|
|
75
|
+
Bump `pyproject.toml`, `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json`
|
|
76
|
+
together, then use the existing `uv sync` command to update `uv.lock`. Confirm only
|
|
77
|
+
intended lock changes and verify all package-version values agree. Leave the
|
|
78
|
+
marketplace's independent schema/catalog version unchanged.
|
|
79
|
+
|
|
80
|
+
Date the versioned changelog in the owner's timezone and open a fresh Unreleased
|
|
81
|
+
section. Commit all cut files together through the PR process. Collect contributor
|
|
82
|
+
credits and approve the exact notes before publication.
|
|
83
|
+
|
|
84
|
+
After the final candidate is integrated and validated, prepare its tag with
|
|
85
|
+
`make tag` from that exact commit. Verify the remote tag resolves to the candidate.
|
|
86
|
+
Create a draft with `gh release create <tag> --verify-tag --draft --title <version>
|
|
87
|
+
--notes-file .maintainer/state/release-notes.md`. A draft is preparation, not GO.
|
|
88
|
+
Do not use a tag from an older commit whose workflow still publishes on tag push.
|
|
89
|
+
|
|
90
|
+
## Publish — only after GO
|
|
91
|
+
|
|
92
|
+
Present all mandatory results, manual acknowledgment, security visibility, exact
|
|
93
|
+
candidate SHA, wheel/sdist SHA-256, approved notes and `make release`. Immediately
|
|
94
|
+
before the trigger, recheck HEAD, version files, the remote tag's resolved commit,
|
|
95
|
+
and the draft release's tag, notes and draft status. Confirm the version is still
|
|
96
|
+
absent from PyPI.
|
|
97
|
+
|
|
98
|
+
Run `make release` once on the approved candidate. It uses
|
|
99
|
+
`gh release edit <tag> --draft=false --latest`; publishing the draft emits
|
|
100
|
+
`release: published` and starts `.github/workflows/publish.yml`. Publishing a
|
|
101
|
+
release directly in GitHub is the same distribution action and requires the same
|
|
102
|
+
GO. Draft creation, tag pushes and edits to an already published release do not
|
|
103
|
+
start this workflow. The published event includes prereleases; their tag must
|
|
104
|
+
match the project's version (optionally prefixed with `v`).
|
|
105
|
+
|
|
106
|
+
Observe Publish to completion. Its Package job checks out the release tag, builds
|
|
107
|
+
and validates the artifacts, and checks the tag against the package version before
|
|
108
|
+
upload. A failure requires an owner decision; do not delete/recreate the release,
|
|
109
|
+
retry through another path or overwrite a version to work around it. Verify the
|
|
110
|
+
published release page still contains the approved notes.
|
|
111
|
+
|
|
112
|
+
## Post-publication verification
|
|
113
|
+
|
|
114
|
+
Download both published files from PyPI for the exact version and record their
|
|
115
|
+
SHA-256 separately from tested hashes. Compare them with the pre-GO artifacts and
|
|
116
|
+
the release-run Package artifact. The release workflow builds afresh, so report differences
|
|
117
|
+
and repeat the artifact smokes on the actual published wheel. Install the exact
|
|
118
|
+
version from PyPI in a fresh environment outside the checkout and repeat library,
|
|
119
|
+
CLI and MCP probes. Check the release page's tag, approved notes and latest status.
|
|
120
|
+
|
|
121
|
+
## Cleanup and closure
|
|
122
|
+
|
|
123
|
+
Record announcement disposition (none configured; the owner posts any later
|
|
124
|
+
approved text). Do not create/apply release labels without agreement.
|
|
125
|
+
Stop only processes started by this run and remove only its temporary environments,
|
|
126
|
+
fixtures and artifacts after retaining required evidence. Check the working tree
|
|
127
|
+
against its initial state. Record publish, verify, announcement disposition and
|
|
128
|
+
cleanup before closing the run with GO. Retro work is independent of delivery.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Release test matrix
|
|
2
|
+
|
|
3
|
+
Instantiate against the final candidate before execution. For 2.1.0, compare v2.0.7 with the final candidate: HTML ingestion, engine/configuration
|
|
4
|
+
errors, public exceptions, logging, MCP version, packaging and release publication.
|
|
5
|
+
Record the concrete SHA and artifact hashes in the run record.
|
|
6
|
+
|
|
7
|
+
| Bucket / risk | Command or concrete action | Expected evidence | Resources / authority | Timing / disposition |
|
|
8
|
+
|---|---|---|---|---|
|
|
9
|
+
| A: shared routing/configuration, exception, HTML, logging and MCP regressions | `make test` | No unit/integration failures; log and candidate SHA | Local fixtures, no API calls | Pre-GO, mandatory validator |
|
|
10
|
+
| A: Python compatibility | Existing Test workflow on final candidate, Python 3.10/3.11/3.12 | All three jobs passed with matching SHA | GitHub CI | Pre-GO, required project checks |
|
|
11
|
+
| A: wheel/sdist and consumer surfaces | `uv build`, then every Artifact gate step in runbook; existing Package workflow | Installed library call, CLI TXT/PDF extraction, real MCP response, assets/version agreement and both SHA-256 values | Isolated environments; package downloads; no paid API calls | Pre-GO, mandatory package-gate |
|
|
12
|
+
| A: optional dependency installation | Install exact wheel with each of docling/crawl4ai/langchain, as runbook specifies | All extras install; do not claim live engine coverage | Package downloads, potentially large ML dependencies; no model inference | Pre-GO, part of package-gate |
|
|
13
|
+
| A: release trigger and version identity | Validate workflow syntax; exercise Package version check with matching/mismatched release tags and wheel versions; dry-run Make targets | Only `release: published` triggers Publish; draft/tag preparation does not upload; mismatched versions fail; Package artifacts feed upload | Local workflow checks, no publication | Pre-GO, required for workflow changes |
|
|
14
|
+
| C: owner review of evidence and gaps | Review results and named unverified provider paths; approve exact notes including credits | Owner acknowledgment and exact approved text | Owner | Pre-GO, bucket-c and notes-approved |
|
|
15
|
+
| C: optional live URL routing | After scoped approval: `uv run pytest tests/e2e/test_url_engines.py -v -m e2e` | Per-test results; skips explicitly unverified | Network; Firecrawl credentials/usage, Jina and Crawl4AI environment; execution/count/cost requires separate agreement | Pre-GO signal; not a mandatory broad E2E gate |
|
|
16
|
+
| C: optional real Docling baseline | After scoped approval: `uv run pytest tests/e2e/test_docling.py::test_docling_pdf_extraction -v -m e2e_heavy` | Non-empty PDF extraction via explicit Docling | Large model downloads and local compute; separate scope approval | Pre-GO signal; unrun means unverified |
|
|
17
|
+
| C: unchanged media/network baselines | Select existing node IDs from tests/e2e/test_media.py, test_youtube.py and test_remote.py after credential/cost review | Per-test observed outcomes | Network; STT provider may incur cost | Optional; unrun means unverified |
|
|
18
|
+
| Signal: security alerts | Read repository Dependabot alerts; report unavailable API separately | Alert details or explicit not-run reason | Read-only GitHub access | Pre-GO, optional signal requiring honest disclosure |
|
|
19
|
+
| A: registry delivery | Download exact PyPI version, compare hashes and repeat isolated library/CLI/MCP smokes | Actual published digests, working surfaces, correct release page | Network, fresh environment | Post-publication, required delivery verification |
|
|
20
|
+
|
|
21
|
+
Bucket B: no new automation project is selected. Existing unit/integration and CI
|
|
22
|
+
packaging checks cover the deterministic changes. Any investment proposal is a
|
|
23
|
+
separate owner decision. This plan does not authorize paid/live suite execution.
|
|
@@ -37,7 +37,7 @@ uv sync --group dev
|
|
|
37
37
|
uv run pytest tests/unit tests/integration -v
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
This is the gate. It returns clean pass/fail in ~15s
|
|
40
|
+
This is the gate. It returns clean pass/fail in ~15s and requires no credentials or network. CI on PRs and `main` runs exactly these tests (`tests/unit tests/integration`) across Python 3.10/3.11/3.12, plus a packaging gate (`.github/workflows/package.yml`) that builds the wheel and exercises the library, the `content-core` CLI and the `content-core-mcp` server from the built artifact. The packaging gate needs no credentials either, but it is not reproducible from a plain pytest run — if you touch packaging metadata (`pyproject.toml`, entry points, package data), expect it to be the check that catches you. For targeted feedback during iteration, use `uv run pytest -k "<keyword>"` per the table in the Testing section below.
|
|
41
41
|
|
|
42
42
|
### Do NOT use as gates
|
|
43
43
|
|
|
@@ -51,7 +51,7 @@ If you believe a task genuinely requires lint or type cleanup, do it as a *separ
|
|
|
51
51
|
|
|
52
52
|
- Single processor changes (`processors/url/*.py`, `processors/document/*.py`, `processors/media/*.py`) are the natural unit of work — keep changes confined to one file plus its matching test in `tests/unit/`.
|
|
53
53
|
- Avoid touching `extraction.py` (orchestrator) or `config.py` unless the task explicitly targets routing or configuration.
|
|
54
|
-
- New processors
|
|
54
|
+
- New processors follow the function contract documented in `ARCHITECTURE.md` ("Two-layer contract"): a module-level `async def extract_<fmt>_file(file_path, config) -> ExtractionOutput` that raises on failure, with per-unit degradation in the inner parsing layer.
|
|
55
55
|
|
|
56
56
|
## Codebase Structure
|
|
57
57
|
|
|
@@ -76,8 +76,7 @@ src/content_core/
|
|
|
76
76
|
│ └── identification/ # File type detection (pure Python)
|
|
77
77
|
│
|
|
78
78
|
├── processors/
|
|
79
|
-
│ ├──
|
|
80
|
-
│ ├── text.py # Plain text + HTML-to-markdown
|
|
79
|
+
│ ├── text.py # Plain text + HTML-to-markdown (also .html/.htm files, <title> extraction)
|
|
81
80
|
│ ├── url/ # URL extraction engines
|
|
82
81
|
│ │ ├── __init__.py # Engine router + fallback chain
|
|
83
82
|
│ │ ├── bs4.py # BeautifulSoup + readability
|
|
@@ -93,6 +92,7 @@ src/content_core/
|
|
|
93
92
|
│ │ ├── docx.py # python-docx
|
|
94
93
|
│ │ ├── pptx.py # python-pptx
|
|
95
94
|
│ │ ├── xlsx.py # openpyxl
|
|
95
|
+
│ │ ├── odf.py # ODT/ODS/ODP via odfpy
|
|
96
96
|
│ │ └── docling.py # Optional Docling integration
|
|
97
97
|
│ └── media/ # Audio/video processing
|
|
98
98
|
│ ├── __init__.py # Video→audio pipeline
|
|
@@ -130,7 +130,7 @@ from content_core import ContentCoreConfig
|
|
|
130
130
|
config = ContentCoreConfig(url_engine="firecrawl", audio_concurrency=5)
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
Key settings: `url_engine`, `document_engine`, `audio_provider`, `audio_model`, `firecrawl_api_url`, `youtube_languages`, `llm_provider`, `llm_model`, `docling_ocr`, `docling_formulas`, `docling_vision`
|
|
133
|
+
Key settings: `url_engine`, `document_engine`, `audio_provider`, `audio_model`, `firecrawl_api_url`, `youtube_languages`, `youtube_cookies_file`, `youtube_proxy`, `llm_provider`, `llm_model`, `docling_ocr`, `docling_formulas`, `docling_vision`
|
|
134
134
|
|
|
135
135
|
Docling enrichment flags (`docling_ocr`, `docling_formulas`, `docling_vision`) control OCR, formula extraction, and image/chart processing when `document_engine="docling"`. These are also exposed as CLI flags (`--formulas`, `--pictures`, `--no-ocr`) and MCP parameters.
|
|
136
136
|
|
|
@@ -167,14 +167,15 @@ When you change a specific processor or module, run only the relevant tests for
|
|
|
167
167
|
| `processors/url/youtube.py` | `uv run pytest -k "youtube"` |
|
|
168
168
|
| `processors/url/reddit.py` | `uv run pytest -k "reddit"` |
|
|
169
169
|
| `processors/document/pdf.py` | `uv run pytest -k "pdf"` |
|
|
170
|
-
| `processors/document/docx.py` or `pptx.py` or `xlsx.py` | `uv run pytest -k "office"` |
|
|
170
|
+
| `processors/document/docx.py` or `pptx.py` or `xlsx.py` or `odf.py` | `uv run pytest -k "office"` |
|
|
171
171
|
| `processors/document/docling.py` | `uv run pytest -k "docling"` |
|
|
172
|
-
| `processors/text.py` | `uv run pytest -k "text_processing"` |
|
|
172
|
+
| `processors/text.py` | `uv run pytest -k "text_processing or html_file"` |
|
|
173
173
|
| `processors/media/audio.py` | `uv run pytest -k "audio or media_pipeline"` |
|
|
174
174
|
| `processors/media/video.py` | `uv run pytest -k "media_pipeline"` |
|
|
175
175
|
| `mcp/server.py` | `uv run pytest -k "mcp"` |
|
|
176
176
|
| `cli.py` | `uv run pytest -k "cli"` |
|
|
177
177
|
| `common/retry.py` | `uv run pytest -k "retry"` |
|
|
178
|
+
| `common/exceptions.py` | `uv run pytest -k "exceptions"` |
|
|
178
179
|
| `content/identification/` | `uv run pytest -k "file_detector"` |
|
|
179
180
|
| `common/state.py` (models) | `uv run pytest -k "models"` |
|
|
180
181
|
|
|
@@ -191,20 +192,22 @@ tests/
|
|
|
191
192
|
│ ├── test_pdf_extraction.py # PDF text cleaning, extraction with mocked pdfplumber
|
|
192
193
|
│ ├── test_pdf_helpers.py # Formula detection, table conversion helpers
|
|
193
194
|
│ ├── test_epub_extraction.py # EPUB extraction with mocked fast-ebook
|
|
194
|
-
│ ├── test_office_extraction.py # DOCX/PPTX/XLSX routing and extraction
|
|
195
|
+
│ ├── test_office_extraction.py # DOCX/PPTX/XLSX/ODT/ODS/ODP routing and extraction
|
|
195
196
|
│ ├── test_docling_extraction.py # Docling output formats with mocked converter
|
|
196
|
-
│ ├── test_text_processing.py # HTML detection, markdown conversion
|
|
197
|
+
│ ├── test_text_processing.py # HTML detection, markdown conversion, <title>/<head> handling
|
|
198
|
+
│ ├── test_html_file_extraction.py # Local .html file → markdown via text processor
|
|
197
199
|
│ ├── test_media_pipeline.py # Audio transcription, video pipeline, stream selection
|
|
198
200
|
│ ├── test_audio_concurrency.py # Semaphore, ordering, concurrency limits
|
|
199
201
|
│ ├── test_mcp_v2.py # MCP tools: extract + summarize
|
|
200
202
|
│ ├── test_cli.py # CLI: _build_input, commands, config subcommands
|
|
201
203
|
│ ├── test_config_file.py # TOML config file: read/write, set/delete, precedence
|
|
202
|
-
│ ├── test_models_v2.py # ExtractionInput/Output
|
|
204
|
+
│ ├── test_models_v2.py # ExtractionInput/Output data models
|
|
203
205
|
│ ├── test_retry.py # Retry decorators, exception classification
|
|
206
|
+
│ ├── test_exceptions.py # Exception taxonomy + public exports
|
|
204
207
|
│ └── test_file_detector*.py # MIME detection, performance, edge cases
|
|
205
208
|
│
|
|
206
209
|
├── integration/ # Local files, no network (~22 tests)
|
|
207
|
-
│ ├── test_extraction.py # Real file extraction (PDF, DOCX, PPTX, XLSX, EPUB, etc.)
|
|
210
|
+
│ ├── test_extraction.py # Real file extraction (PDF, DOCX, PPTX, XLSX, ODT/ODS/ODP, EPUB, HTML, etc.)
|
|
208
211
|
│ └── test_cli_v2.py # CLI subcommands via CliRunner with real extraction
|
|
209
212
|
│
|
|
210
213
|
└── e2e/
|
|
@@ -214,3 +217,10 @@ tests/
|
|
|
214
217
|
├── [e2e] test_media.py # Audio/video transcription via STT API
|
|
215
218
|
└── [e2e_heavy] test_docling.py # Docling extraction with enrichment flags
|
|
216
219
|
```
|
|
220
|
+
|
|
221
|
+
## Maintainer workflows
|
|
222
|
+
|
|
223
|
+
Maintainer profile lives in `.maintainer/`; do not run release, triage or discussions workflows without it.
|
|
224
|
+
PyPI publication is triggered by a published GitHub Release (`release: published`).
|
|
225
|
+
`make tag` prepares the tag; `make release` publishes its reviewed draft release.
|
|
226
|
+
Follow `.maintainer/release/runbook.md` for candidate validation and publication.
|
|
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.2.0] - 2026-10-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- OpenDocument extraction for `.odt`, `.ods` and `.odp` through the office route, using `odfpy` (new core dependency). Text documents become markdown paragraphs, headings, lists and tables; spreadsheets become one markdown table per sheet; presentations become one block per slide. Detection reads the ZIP's `mimetype` member, so a wrong or missing extension still works (#43).
|
|
14
|
+
- YouTube cookie and proxy support for the transcript path (#46). `youtube_cookies_file` (`CCORE_YOUTUBE_COOKIES_FILE`) loads a Netscape `cookies.txt` into the `youtube-transcript-api` session, unblocking IP-flagged networks; a missing or unreadable file raises `ConfigurationError`. `youtube_proxy` (`CCORE_YOUTUBE_PROXY`) routes both `youtube-transcript-api` and the `pytubefix` fallback through a proxy URL — use a residential proxy, as datacenter proxies are blocked. With neither set, behavior is unchanged. Cookie values are never logged.
|
|
15
|
+
- `audio_segment_minutes` setting (`CCORE_AUDIO_SEGMENT_MINUTES`) controls the length of the segments long audio is split into before transcription. The default stays 10 minutes; `0` sends the file whole, for self-hosted STT endpoints without an upload size limit (#94).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **URL extraction failures now raise instead of returning empty output** (#60). `content=""` now only means the source was genuinely empty. A named `url_engine` that fails raises `NetworkError` (connection/timeout/DNS), `NotFoundError` (HTTP 404/410 from the page, `simple` engine), `ExternalServiceError` (engine API or HTTP errors) or `ConfigurationError` (`crawl4ai` not installed). `auto` still falls through firecrawl → jina → crawl4ai → simple and raises the last error only when every engine failed. A malformed URL raises `InvalidInputError`. The `simple` engine no longer returns `"Failed to extract content: ..."` or `"No content found"` as page content. Remote-file downloads raise the same typed errors, and a download that cannot be saved locally raises `FileOperationError`.
|
|
19
|
+
- File extraction failures raise typed errors (#60): corrupted PDF, EPUB or unreadable text files raise `FileOperationError` (chained to the original cause) instead of bare `Exception`/`RuntimeError`; a missing file still raises `FileNotFoundError`. ffmpeg/ffprobe failures in audio and video processing raise `FileOperationError`: video extraction no longer returns empty content when ffprobe or ffmpeg fails. Speech-to-text provider failures (after retries) raise `ExternalServiceError`, and an STT model that cannot be created raises `ConfigurationError`.
|
|
20
|
+
- Retry classification now treats `ConfigurationError`, `UnsupportedTypeException`, `InvalidInputError` and `FileOperationError` as permanent and `NetworkError` as transient (#60).
|
|
21
|
+
- YouTube extraction raises instead of returning empty content when no transcript can be fetched (#60): `NoTranscriptFound` when neither youtube-transcript-api nor pytubefix finds a transcript, `ExternalServiceError` (chained to the provider error) when YouTube blocks or fails the request on both paths, e.g. `IpBlocked`, and `InvalidInputError` for a YouTube URL without a video ID. Provider errors from `get_best_transcript` and `extract_transcript_pytubefix` now propagate after retries instead of being swallowed (`extract_transcript_pytubefix` still returns `(None, None)` for a video without captions); a failure of the first path still falls back to the second.
|
|
22
|
+
- An explicit `audio_provider`/`audio_model` that cannot be created now raises `ConfigurationError` instead of silently transcribing with the default STT model, per "Explicit choice is a requirement" (#99).
|
|
23
|
+
- youtube-transcript-api failures are retried by type: blocked requests (`RequestBlocked`/`IpBlocked`) are retried, so a rotating proxy can get a new IP; other transcript errors that name a state of the video (unavailable, transcripts disabled, age-restricted...) are not (#99).
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- `youtube_proxy` now also applies to the YouTube video title lookup, which previously used only env-var proxies (#99).
|
|
27
|
+
- `requests`, imported directly for the YouTube cookie session, is declared as a dependency instead of arriving transitively (#99).
|
|
28
|
+
- A failed Crawl4AI crawl (navigation timeout, blocked page) now raises `ExternalServiceError` instead of failing with a pydantic `ValidationError` on `content=None`, and `auto` falls through to the next engine. Applies to both the local browser and the Docker API mode (#101).
|
|
29
|
+
|
|
30
|
+
### Security
|
|
31
|
+
- Raised minimum versions to releases without known vulnerabilities: `aiohttp>=3.14.3`, `pillow>=12.3.0`, `click>=8.3.3`, `pydantic-settings>=2.14.2`, and in the extras `docling>=2.94.0`, `crawl4ai>=0.9.0` and `langchain-core>=1.3.3`. Upgrading content-core upgrades these packages in an existing environment. The development lock was refreshed; it now also exercises `fastmcp` 4 and `mcp` 2, which fresh installs already resolve to (#102).
|
|
32
|
+
|
|
33
|
+
## [2.1.0] - 2026-09-06
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- Local HTML file ingestion through the simple engine, including `.html`/`.htm` routing, HTML-to-Markdown conversion, document titles, minimal HTML documents, BOMs and legacy character encodings (#59, #66).
|
|
37
|
+
- Public exception exports from `content_core`: `ContentCoreError`, `UnsupportedTypeException`, `InvalidInputError`, `ConfigurationError`, `NotFoundError`, `NoTranscriptFound`, `NetworkError`, `ExternalServiceError` and `FileOperationError` (#50, #52). Typed failures can be caught through the public API. Some existing failures still escape as untyped exceptions until #60; exporting the taxonomy does not complete that migration.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- PyPI publication starts when a GitHub Release is published, including publishing a draft. Creating or pushing a tag alone no longer publishes. The packaging gate verifies that the release tag matches the package version (#93).
|
|
41
|
+
- Library imports no longer configure process-wide Loguru handlers. Content Core logging is disabled by default for library consumers; use `logger.enable("content_core")` with the host application's handlers, or `content_core.configure_logging()` from an application entry point. CLI and MCP entry points configure their own stderr logging and honor debug settings (#47).
|
|
42
|
+
- Published wheel and sdist now come directly from the artifacts validated by the publishing workflow's packaging job, rather than being rebuilt in the upload job (#57).
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
- Unused `asciidoc` dependency (#58).
|
|
46
|
+
- Unused internal `Processor` Protocol; processor contracts remain documented in `ARCHITECTURE.md` (#53).
|
|
47
|
+
- Unused internal exception classes `DatabaseOperationError`, `AuthenticationError` and `RateLimitError` (#52). None had a raise site; direct imports from `content_core.common.exceptions` must be updated. The replacement taxonomy is public, but provider error wrapping is still incomplete pending #60.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
- Explicit `document_engine="docling"` now raises `ConfigurationError` when the optional engine is unavailable, instead of silently using the simple engine. The error names `pip install "content-core[docling]"` and the `CCORE_DOCUMENT_ENGINE=simple` alternative. `auto` still permits fallback, and `check_file_support` returns an unsupported verdict with the reason (#50).
|
|
51
|
+
- Invalid engine names are rejected at configuration entry points, including constructor arguments, environment variables, TOML files and `content-core config set`. Misspelled boolean values are also rejected before writing; CLI/MCP validate engine names for the input type (#51).
|
|
52
|
+
- MCP `serverInfo.version` reports the installed Content Core version instead of FastMCP's version (#56).
|
|
53
|
+
|
|
54
|
+
### Documentation
|
|
55
|
+
- Documented Firecrawl-compatible backends through `FIRECRAWL_API_URL` (#86).
|
|
56
|
+
|
|
10
57
|
## [2.0.7] - 2026-09-02
|
|
11
58
|
|
|
12
59
|
### Added
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|