|
| 1 | +# Implementation Steps: Git Markdown Source |
| 2 | + |
| 3 | +## Step 1: Config model |
| 4 | + |
| 5 | +- [x] `GitConfig` record (`provider`, `repo`, `ref`, `paths`, `token`) with `hasToken()` |
| 6 | +- [x] Add optional `git` field to `ContentSource` (present only for `type: git`); existing call sites pass `null` |
| 7 | + |
| 8 | +**Related behaviors:** private-repo token; config binding |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## Step 2: `GitSourceStrategy` |
| 13 | + |
| 14 | +- [x] `@Component implements ContentSourceStrategy` for `SourceType.GIT` (auto-routed by `SourceStrategyRegistry`) |
| 15 | +- [x] `discover`: GitHub Trees API (`/repos/{repo}/git/trees/{ref}?recursive=1`), keep blobs matching `paths` globs (`AntPathMatcher`), SHA as change marker; a hard failure (auth) throws so the source is isolated (not mass-deleted) |
| 16 | +- [x] `fetch`: raw content (`raw.githubusercontent.com`), split YAML frontmatter from Markdown body, clean Hugo shortcodes, map path → canonical URL, derive locale from filename suffix, build `ContentDocument` (SHA = `lastmod`) |
| 17 | +- [x] Secrets: bearer token applied server-side only; never logged; empty-default env placeholder |
| 18 | + |
| 19 | +**Related behaviors:** discovery/filter; incremental SHA; frontmatter/shortcode/URL/locale extraction; token handling |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## Step 3: Config + tests |
| 24 | + |
| 25 | +- [x] `application.yaml`: example `oe-website-markdown` git source (disabled by default; token via `GITHUB_TOKEN_OE_WEBSITE`, empty default) |
| 26 | +- [x] `GitSourceStrategyTest` (MockRestServiceServer): discovery+filter+SHA, bearer token, discovery-failure, fetch extraction (frontmatter/shortcode/URL/locale), fetch-failure skip, and pure `mapToUrl`/`localeFromPath`/`parseMarkdown` |
| 27 | +- [x] `ConfiguredSourcesTest`: the git source binds as `SourceType.GIT` |
| 28 | + |
| 29 | +**Acceptance criteria:** |
| 30 | +- [x] All tests pass (`mvn test`); build green |
| 31 | + |
| 32 | +--- |
| 33 | + |
| 34 | +## Behavior Coverage |
| 35 | + |
| 36 | +| Scenario | Layer | Covered in Step | |
| 37 | +|----------|-------|-----------------| |
| 38 | +| Files discovered via the trees API and filtered by paths | Backend | Step 3 (`discoveryFiltersByPaths`) | |
| 39 | +| Non-matching files excluded | Backend | Step 3 (`discoveryFiltersByPaths` — README/png excluded) | |
| 40 | +| Unchanged file (same SHA) is skipped | Backend | SHA returned as `lastmod` (`discoveryFiltersByPaths`); the unchanged/changed diff is `ContentIndexer`'s (spec 008 tests) | |
| 41 | +| Changed file (new SHA) is re-indexed | Backend | As above — diff handled by `ContentIndexer`; strategy supplies the SHA | |
| 42 | +| Frontmatter becomes metadata | Backend | Step 3 (`fetchExtractsFrontmatterAndBody`) | |
| 43 | +| Hugo shortcodes are cleaned | Backend | Step 3 (`fetchExtractsFrontmatterAndBody` — no `{{<`) | |
| 44 | +| Path maps to canonical URL | Backend | Step 3 (`datedFilenameMapsToCanonicalUrl`, `fetchExtractsFrontmatterAndBody`) | |
| 45 | +| Locale from filename suffix | Backend | Step 3 (`localeFromFilenameSuffix`) | |
| 46 | +| Private repo accessed with token | Backend | Step 3 (`tokenIsSentForPrivateRepo`) | |
| 47 | +| Token is never logged or served | Backend | Design: token applied only as a request header, never logged (auth logs use `repo`, not token); MCP serves only indexed docs | |
| 48 | +| Missing token for private repo fails clearly | Backend | Step 3 (`missingTokenFailsClearly`) | |
| 49 | +| Git-sourced docs behave like website docs | Backend | Downstream unchanged — same `ContentDocument`/indexer/tools (specs 008/011/012) | |
| 50 | + |
| 51 | +All scenarios are backend; there is no frontend in this spec. |
0 commit comments