From 35bbd2a02165c239ad60d96c9deb1a1d33cd271f Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Sun, 12 Jul 2026 14:44:52 +0200 Subject: [PATCH 1/3] chore(spec-007): mark source-strategy in progress, link issue #13 Refs #13 --- docs/specs/INDEX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md index b1d7587..9180dda 100644 --- a/docs/specs/INDEX.md +++ b/docs/specs/INDEX.md @@ -12,7 +12,7 @@ roadmap step, to be implemented sequentially (each builds on the previous). | 004 | 004-sitemap-crawler | Sitemap crawler | backend, crawler | `SitemapCrawler` — sitemap discovery of URLs + lastmod | #7 | done | | 005 | 005-page-fetcher | Page fetcher | backend, crawler | `PageFetcher` — robust HTTP fetch (ETag/IMS, rate limit, retry) | #9 | done | | 006 | 006-content-extractor | Content extractor | backend, crawler | `ContentExtractor` — jsoup content + metadata extraction | #11 | done | -| 007 | 007-source-strategy | Source strategy | backend, architecture | `ContentSourceStrategy` interface + `WebsiteSourceStrategy` | — | open | +| 007 | 007-source-strategy | Source strategy | backend, architecture | `ContentSourceStrategy` interface + `WebsiteSourceStrategy` | #13 | in progress | | 008 | 008-content-indexer | Content indexer | backend, search, crawler | `ContentIndexer` — orchestration, diff, upsert/delete | — | open | | 009 | 009-content-bootstrap-step | Content bootstrap step | backend, search | `ContentBootstrapStep` — initial reindex via `SearchIndexBootstrapStep` | — | open | | 010 | 010-content-refresh-scheduler | Content refresh scheduler | backend, scheduling | `ContentRefreshScheduler` — `@Scheduled` incremental re-crawl | — | open | From faf241fa03467bb933a35f120ed69d562b5670b7 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Sun, 12 Jul 2026 14:49:50 +0200 Subject: [PATCH 2/3] feat(spec-007): source strategy seam + WebsiteSourceStrategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ContentSourceStrategy interface (type/discover/fetch) — the seam the indexer depends on. - FetchOutcome(Result{INDEX,UNCHANGED,DELETE,SKIP}, document) — widens the design's fetch return so non-happy paths are first-class. - WebsiteSourceStrategy: discover -> SitemapCrawler; fetch maps PageFetcher FetchResult (304->UNCHANGED, 404->DELETE, error->SKIP, OK->extract->INDEX, empty->SKIP) and builds the ContentDocument (lastmod = discovery marker). - SourceStrategyRegistry: indexes injected strategies by type(), clear error for unknown types, fail-fast on duplicates; future git strategy auto-discovered. - Tests: registry (test doubles) + WebsiteSourceStrategy with real collaborators via MockRestServiceServer. 77 tests green. - steps.md; refresh CLAUDE.md. Refs #13 --- CLAUDE.md | 11 +- docs/specs/007-source-strategy/steps.md | 61 ++++++++ .../content/ContentSourceStrategy.java | 33 ++++ .../openelements/content/FetchOutcome.java | 42 +++++ .../content/SourceStrategyRegistry.java | 45 ++++++ .../content/WebsiteSourceStrategy.java | 80 ++++++++++ .../content/SourceStrategyRegistryTest.java | 77 ++++++++++ .../content/WebsiteSourceStrategyTest.java | 145 ++++++++++++++++++ 8 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 docs/specs/007-source-strategy/steps.md create mode 100644 src/main/java/com/openelements/content/ContentSourceStrategy.java create mode 100644 src/main/java/com/openelements/content/FetchOutcome.java create mode 100644 src/main/java/com/openelements/content/SourceStrategyRegistry.java create mode 100644 src/main/java/com/openelements/content/WebsiteSourceStrategy.java create mode 100644 src/test/java/com/openelements/content/SourceStrategyRegistryTest.java create mode 100644 src/test/java/com/openelements/content/WebsiteSourceStrategyTest.java diff --git a/CLAUDE.md b/CLAUDE.md index 911de0d..4fa022e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,7 +45,11 @@ extraction, Meilisearch indexing with scheduled refresh, and four MCP tools │ ├── DiscoveredItem.java # a discovered URL + lastmod change marker │ ├── SitemapCrawler.java # sitemap/index discovery + bounded fallback crawl │ ├── ContentExtractor.java # jsoup body + metadata extraction -│ └── ExtractedContent.java # extractor output (body + metadata) +│ ├── ExtractedContent.java # extractor output (body + metadata) +│ ├── ContentSourceStrategy.java # per-source-type discover/fetch seam +│ ├── WebsiteSourceStrategy.java # website strategy: crawler + fetcher + extractor +│ ├── FetchOutcome.java # INDEX/UNCHANGED/DELETE/SKIP + optional document +│ └── SourceStrategyRegistry.java # selects a strategy by ContentSource.type() ├── src/main/resources/application.yaml # datasource, JPA, OAuth2, MCP, Meilisearch, content config ├── src/test/java/com/openelements/content/ # behavior tests (context, MCP enabled/disabled, search-down, jsoup) └── docs/ @@ -87,6 +91,11 @@ fetched HTML into an `ExtractedContent` via jsoup: it selects the main container `contentSelector` (with a readability fallback), always strips scripts/styles, applies `contentExclude`, whitespace-normalizes the body, and reads metadata (title/excerpt/date/author/ categories/preview image/locale) from OpenGraph/Article ``, JSON-LD, and `