From 1d647e01a050a8a040c1c33e621bde177cc1524d Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Tue, 5 May 2026 18:57:16 +0200 Subject: [PATCH 01/10] [RUM-13811] Add feature doc sync system for *_FEATURE.md files --- .claude/skills/update-feature-docs | 55 +++++++++++ .gitlab-ci.yml | 7 ++ CLAUDE.md | 1 + DatadogRUM/RUM_FEATURE.md | 39 ++++++-- .../SESSION_REPLAY_FEATURE.md | 19 +++- Makefile | 5 + tools/feature-docs-verify.sh | 99 +++++++++++++++++++ 7 files changed, 213 insertions(+), 12 deletions(-) create mode 100644 .claude/skills/update-feature-docs create mode 100755 tools/feature-docs-verify.sh diff --git a/.claude/skills/update-feature-docs b/.claude/skills/update-feature-docs new file mode 100644 index 0000000000..64230d1ce2 --- /dev/null +++ b/.claude/skills/update-feature-docs @@ -0,0 +1,55 @@ +# update-feature-docs + +Review and update the feature documentation files to match the current public API. + +## When to use + +- After adding or modifying a public API (configuration options, new types, new methods) +- Before opening a release PR, to make sure docs are in sync +- Any time you want to audit whether feature docs are still accurate + +## What this skill covers + +All `*_FEATURE.md` files in the repo. Each doc's frontmatter is the source of truth: +- `verified_against_commit` — the commit the doc was last verified against +- `tracked_files` — the public API source files whose changes should trigger a doc update + +To add a new feature doc to the system, just create a `*_FEATURE.md` file with the correct frontmatter — no script changes needed. + +## Steps + +1. **Discover feature docs** — find all `*_FEATURE.md` files in the repo (excluding `build/` and `artifacts/`). + +2. **For each doc, read its frontmatter** — extract `verified_against_commit` and `tracked_files`. + +3. **Get the diff since that commit** — run: + ``` + git diff ..HEAD -- + ``` + If there is no diff for a doc, it is up to date — skip it. + +4. **Read the current source files in full** — read each tracked source file to understand the current public API surface. + +5. **Compare against the feature doc** — identify every discrepancy: + - New configuration options or parameters missing from the doc + - Removed or renamed options still mentioned in the doc + - Changed defaults, behaviors, or platform availability + - New types, enums, or feature flags not documented + - Outdated code examples + +6. **Update the feature doc** — apply all necessary changes: + - Update the Quick Start example to reflect the current API + - Update the Configuration Categories section + - Update Troubleshooting if relevant + - Fix any stale descriptions or defaults + +7. **Update the frontmatter** — set: + - `verified_against_commit` → current HEAD commit hash (use `git rev-parse --short=9 HEAD`) + - `sdk_version` → current version from `DatadogCore.podspec` + - `last_updated` → today's date (YYYY-MM-DD) + +## Notes + +- Only update docs for features whose tracked source files actually changed. +- Do not rewrite sections that are still accurate — only fix what is wrong or missing. +- The Quick Start example should always compile against the current API. diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 86a134cbbb..1781b3ac10 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -137,6 +137,13 @@ API Surface Verify: - make clean repo-setup ENV=ci - make api-surface-verify +Feature Docs Verify: + stage: lint + rules: + - if: '$CI_COMMIT_BRANCH =~ /^(release|hotfix)\/.*/' + script: + - make feature-docs-verify + Unit Tests (iOS): stage: test rules: diff --git a/CLAUDE.md b/CLAUDE.md index b1a524cf2c..7532ecda10 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,6 +19,7 @@ Use these skills (via `/skill-name`) for common workflows: | `dd-sdk-ios:open-pr` | Opening a pull request against `develop` | | `dd-sdk-ios:running-tests` | Running unit, module, or integration tests | | `dd-sdk-ios:xcode-file-management` | Adding, removing, moving, or renaming Swift source files | +| `dd-sdk-ios:update-feature-docs` | Review and update all `*_FEATURE.md` docs after public API changes | ## CI Environment diff --git a/DatadogRUM/RUM_FEATURE.md b/DatadogRUM/RUM_FEATURE.md index dc571c14da..f13c40da3c 100644 --- a/DatadogRUM/RUM_FEATURE.md +++ b/DatadogRUM/RUM_FEATURE.md @@ -1,7 +1,11 @@ --- -last_updated: 2025-01-03 -sdk_version: 3.3.0 -verified_against_commit: 1d3e80ec5 +last_updated: 2026-05-05 +sdk_version: 3.10.0 +verified_against_commit: 228fb06d7 +tracked_files: + - DatadogRUM/Sources/RUM.swift + - DatadogRUM/Sources/RUMConfiguration.swift + - DatadogRUM/Sources/RUMMonitorProtocol.swift --- # RUM (Real User Monitoring) Feature @@ -48,12 +52,14 @@ RUM.enable( // Default: nil (disabled) // Or use custom: MyCustomViewsPredicate() // Note: Also requires uiKitViewsPredicate for SwiftUI tracking to work correctly + // Note: Experimental API - may change in future releases swiftUIViewsPredicate: DefaultSwiftUIRUMViewsPredicate(), // SwiftUI automatic action tracking - provide predicate to enable // Default: nil (disabled) // Or use custom: MyCustomActionsPredicate() // Note: Also requires uiKitActionsPredicate for SwiftUI tracking to work correctly + // Note: Experimental API - behavior differs between iOS 17 and below vs iOS 18+ swiftUIActionsPredicate: DefaultSwiftUIRUMActionsPredicate(isLegacyDetectionEnabled: true), // Automatic network resource tracking - provide config to enable @@ -67,7 +73,14 @@ RUM.enable( // Optional: Add custom attributes to resources resourceAttributesProvider: { request, response, data, error in return ["custom.attribute": "value"] - } + }, + // Optional: Capture HTTP headers from requests and responses + // Default: .disabled + // Options: + // .disabled - No header capture + // .defaults - Capture predefined common headers (cache-control, content-type, etag, etc.) + // .custom([rules]) - Capture headers by custom rules + trackResourceHeaders: .defaults ), // Track user frustrations (error taps following errors) @@ -148,7 +161,15 @@ RUM.enable( // Collect accessibility settings in view events // Default: false - collectAccessibility: false + collectAccessibility: false, + + // Track slow frames / view hitches + // Default: true + trackSlowFrames: true, + + // Experimental feature flags (currently no active flags for RUM) + // Default: [:] + featureFlags: [:] ) ) @@ -199,14 +220,16 @@ monitor.stopView(key: "ProductList") ### Automatic Tracking Requires configuration to be set, otherwise disabled by default: -- **View tracking**: `uiKitViewsPredicate`, `swiftUIViewsPredicate` -- **Action tracking**: `uiKitActionsPredicate`, `swiftUIActionsPredicate` +- **View tracking**: `uiKitViewsPredicate`, `swiftUIViewsPredicate` *(SwiftUI: experimental)* +- **Action tracking**: `uiKitActionsPredicate`, `swiftUIActionsPredicate` *(SwiftUI: experimental, behavior differs on iOS 17 vs iOS 18+)* - **Resource tracking**: `urlSessionTracking` (automatic), optionally call `URLSessionInstrumentation.enableDurationBreakdown(with: .init(delegateClass: YourSessionDelegate.self))` for detailed timing +- **Header capture**: `urlSessionTracking.trackResourceHeaders` — `.disabled` (default), `.defaults` (common headers), or `.custom([rules])` ### Performance Monitoring - **Long tasks**: `longTaskThreshold` (default: 0.1s) - **App hangs**: `appHangThreshold` (default: nil/disabled) - **Vitals**: `vitalsUpdateFrequency` (default: .average) +- **Slow frames**: `trackSlowFrames` (default: true) — captures view hitches and attaches them to the corresponding RUM view ### Sampling - **Sessions**: `sessionSampleRate` (default: 100%) @@ -231,7 +254,7 @@ Event mappers allow modifying or dropping events before upload: ### "Views or actions not tracked" 1. Check if predicates are configured in RUMConfiguration 2. For UIKit: `uiKitViewsPredicate` and `uiKitActionsPredicate` must be set -3. For SwiftUI: `swiftUIViewsPredicate` and `swiftUIActionssPredicate` must be set, as well as UIKit predicates +3. For SwiftUI: `swiftUIViewsPredicate` and `swiftUIActionsPredicate` must be set, as well as UIKit predicates ### "Network requests not tracked" 1. Verify `urlSessionTracking` is configured in RUMConfiguration (RUM.enable() handles URLSessionInstrumentation internally) diff --git a/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md b/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md index bd038012e7..f0ca781007 100644 --- a/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md +++ b/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md @@ -1,7 +1,10 @@ --- -last_updated: 2025-01-03 -sdk_version: 3.3.0 -verified_against_commit: 1d3e80ec5 +last_updated: 2026-05-05 +sdk_version: 3.10.0 +verified_against_commit: 228fb06d7 +tracked_files: + - DatadogSessionReplay/Sources/SessionReplay.swift + - DatadogSessionReplay/Sources/SessionReplayConfiguration.swift --- # Session Replay Feature @@ -84,8 +87,12 @@ SessionReplay.enable( // Feature flags for experimental features // Default: [.swiftui: false] + // Available flags: + // .swiftui - Enable SwiftUI recording (experimental) + // .heatmaps - Enable heatmap identifier computation (experimental) featureFlags: [ - .swiftui: true // Enable SwiftUI recording (experimental) + .swiftui: true, // Enable SwiftUI recording (experimental) + .heatmaps: false // Enable heatmap identifier computation (experimental) ] ) ) @@ -218,6 +225,10 @@ SessionReplayPrivacyView( 1. Enable SwiftUI feature flag: `featureFlags: [.swiftui: true]` 2. Note: Session Replay SwiftUI is experimental, and some components are not supported +### Available feature flags +- `.swiftui` — Enable SwiftUI recording (experimental, default: `false`) +- `.heatmaps` — Enable heatmap identifier computation (experimental, default: `false`) + ## Feature Interactions - **RUM**: Required - Session Replay cannot work without RUM enabled. View and action tracking must be configured. diff --git a/Makefile b/Makefile index c9312fd54e..1b96085d97 100644 --- a/Makefile +++ b/Makefile @@ -424,6 +424,11 @@ api-surface-verify: --output-file /tmp/api-surface-objc-generated \ ../../api-surface-objc +# Verify feature doc files are up to date +feature-docs-verify: + @$(ECHO_TITLE) "make feature-docs-verify" + @./tools/feature-docs-verify.sh + # Builds API documentation using the same process as Swift Package Index. spi-docs-build: @$(ECHO_TITLE) "make spi-docs-build" diff --git a/tools/feature-docs-verify.sh b/tools/feature-docs-verify.sh new file mode 100755 index 0000000000..1096f66326 --- /dev/null +++ b/tools/feature-docs-verify.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# Verifies that feature docs are up to date with the public API. +# Discovers all *_FEATURE.md files in the repo. Each doc's frontmatter is the +# source of truth: `verified_against_commit` and `tracked_files` drive the check. +# Run `make feature-docs-verify` to execute. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +python3 - "$REPO_ROOT" <<'EOF' +import os +import subprocess +import sys + +repo_root = sys.argv[1] +failed = False + +def parse_frontmatter(path): + """Return (verified_against_commit, tracked_files) from a doc's YAML frontmatter.""" + commit = None + files = [] + in_front = False # True once we've seen the opening --- + in_files = False # True while we're inside the tracked_files list + + with open(path) as f: + for line in f: + line = line.rstrip("\n") + + # --- marks both the opening and closing of the frontmatter block + if line == "---": + if not in_front: + in_front = True + continue + else: + break # closing ---, stop parsing + + if not in_front: + continue + + if line.startswith("verified_against_commit:"): + commit = line.split(":", 1)[1].strip() + in_files = False + elif line.startswith("tracked_files:"): + in_files = True + elif in_files and line.startswith(" - "): + # Each list entry is " - path/to/file.swift" + files.append(line[4:].strip()) + elif line and not line.startswith(" "): + # Any non-indented key ends the tracked_files list + in_files = False + + return commit, files + +# Discover all *_FEATURE.md files, skipping generated output directories +docs = [] +for dirpath, dirnames, filenames in os.walk(repo_root): + dirnames[:] = [d for d in dirnames if d not in ("build", "artifacts")] + for name in filenames: + if name.endswith("_FEATURE.md"): + docs.append(os.path.join(dirpath, name)) + +if not docs: + print("No *_FEATURE.md files found.") + sys.exit(0) + +for doc in sorted(docs): + doc_name = os.path.basename(doc) + commit, files = parse_frontmatter(doc) + + if not commit: + print(f"❌ {doc_name}: missing verified_against_commit in frontmatter.") + failed = True + continue + + if not files: + print(f"❌ {doc_name}: missing tracked_files in frontmatter.") + failed = True + continue + + # Check whether any tracked file changed since the doc was last verified + print(f"Checking {doc_name} against:") + for f in files: + print(f" - {f}") + abs_files = [os.path.join(repo_root, f) for f in files] + result = subprocess.run( + ["git", "-C", repo_root, "diff", f"{commit}..HEAD", "--", *abs_files], + capture_output=True, text=True + ) + + if result.stdout.strip(): + print(f"❌ {doc_name} may be out of date.") + print(f" Public API changed since commit {commit}.") + print(f" Run the '/dd-sdk-ios:update-feature-docs' skill in Claude Code to update it.") + failed = True + else: + print(f"✅ {doc_name} is up to date (verified at {commit}).") + +sys.exit(1 if failed else 0) +EOF From 42e02d2015b83837e73aae4aba30341326667f0e Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Tue, 5 May 2026 19:09:18 +0200 Subject: [PATCH 02/10] [RUM-13811] Fix update-feature-docs skill directory structure --- .../{update-feature-docs => update-feature-docs/SKILL.md} | 5 +++++ 1 file changed, 5 insertions(+) rename .claude/skills/{update-feature-docs => update-feature-docs/SKILL.md} (92%) diff --git a/.claude/skills/update-feature-docs b/.claude/skills/update-feature-docs/SKILL.md similarity index 92% rename from .claude/skills/update-feature-docs rename to .claude/skills/update-feature-docs/SKILL.md index 64230d1ce2..b92deb60d0 100644 --- a/.claude/skills/update-feature-docs +++ b/.claude/skills/update-feature-docs/SKILL.md @@ -1,3 +1,8 @@ +--- +name: dd-sdk-ios:update-feature-docs +description: Use when public API changes have been made to review and update all *_FEATURE.md documentation files, or to audit whether they are still accurate. +--- + # update-feature-docs Review and update the feature documentation files to match the current public API. From cb8acd413d333fa01e37d88db12a863bd6988cd6 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Fri, 8 May 2026 11:46:45 +0200 Subject: [PATCH 03/10] RUM-13811 Address comment + Improve script instructions --- .claude/skills/update-feature-docs/SKILL.md | 3 +++ tools/feature-docs-verify.sh | 27 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.claude/skills/update-feature-docs/SKILL.md b/.claude/skills/update-feature-docs/SKILL.md index b92deb60d0..b6b3e466cf 100644 --- a/.claude/skills/update-feature-docs/SKILL.md +++ b/.claude/skills/update-feature-docs/SKILL.md @@ -26,6 +26,8 @@ To add a new feature doc to the system, just create a `*_FEATURE.md` file with t 1. **Discover feature docs** — find all `*_FEATURE.md` files in the repo (excluding `build/` and `artifacts/`). 2. **For each doc, read its frontmatter** — extract `verified_against_commit` and `tracked_files`. + - If `tracked_files` is missing, derive the list from the doc's "Key Files" section (every source file path it references). Treat the doc as fully out of date and proceed to step 4 — the diff in step 3 cannot be computed. + - If `verified_against_commit` is missing, treat the doc as fully out of date and proceed to step 4 — the diff in step 3 cannot be computed. 3. **Get the diff since that commit** — run: ``` @@ -49,6 +51,7 @@ To add a new feature doc to the system, just create a `*_FEATURE.md` file with t - Fix any stale descriptions or defaults 7. **Update the frontmatter** — set: + - `tracked_files` → if it was missing or out of date, write the list derived in step 2 - `verified_against_commit` → current HEAD commit hash (use `git rev-parse --short=9 HEAD`) - `sdk_version` → current version from `DatadogCore.podspec` - `last_updated` → today's date (YYYY-MM-DD) diff --git a/tools/feature-docs-verify.sh b/tools/feature-docs-verify.sh index 1096f66326..15cb7f5f21 100755 --- a/tools/feature-docs-verify.sh +++ b/tools/feature-docs-verify.sh @@ -15,6 +15,12 @@ import sys repo_root = sys.argv[1] failed = False +def print_fix_instructions(full_clone=False): + """Print the standard recipe for fixing a feature-doc verification failure.""" + on_full_clone = " on a full clone" if full_clone else "" + print(f" Run the '/dd-sdk-ios:update-feature-docs' skill in Claude Code{on_full_clone} to refresh the doc,") + print(f" then `make feature-docs-verify` to confirm, and push the update.") + def parse_frontmatter(path): """Return (verified_against_commit, tracked_files) from a doc's YAML frontmatter.""" commit = None @@ -69,11 +75,13 @@ for doc in sorted(docs): if not commit: print(f"❌ {doc_name}: missing verified_against_commit in frontmatter.") + print_fix_instructions() failed = True continue if not files: print(f"❌ {doc_name}: missing tracked_files in frontmatter.") + print_fix_instructions() failed = True continue @@ -87,10 +95,25 @@ for doc in sorted(docs): capture_output=True, text=True ) - if result.stdout.strip(): + # `git diff` exits non-zero when the baseline commit isn't reachable + # (e.g. shallow clone on a release/hotfix branch, or a typo in the + # frontmatter). In that case stdout is empty, so we'd otherwise silently + # report the doc as up to date. Treat any non-zero exit as a failure and + # surface stderr so the engineer knows what to do. + if result.returncode != 0: + print(f"❌ {doc_name}: failed to diff against {commit}.") + stderr = result.stderr.strip() + if stderr: + for stderr_line in stderr.splitlines(): + print(f" {stderr_line}") + print(f" The baseline commit is not reachable in this checkout (often a shallow clone on a") + print(f" release/hotfix branch, or a typo in the frontmatter).") + print_fix_instructions(full_clone=True) + failed = True + elif result.stdout.strip(): print(f"❌ {doc_name} may be out of date.") print(f" Public API changed since commit {commit}.") - print(f" Run the '/dd-sdk-ios:update-feature-docs' skill in Claude Code to update it.") + print_fix_instructions() failed = True else: print(f"✅ {doc_name} is up to date (verified at {commit}).") From 058d261fc4020501099121314123f6e064f0e0d4 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Fri, 8 May 2026 14:47:26 +0200 Subject: [PATCH 04/10] RUM-13811 Update registries + Confluence workflow --- .claude/skills/update-feature-docs/SKILL.md | 7 ++- .../workflows/changelog-to-confluence.yaml | 4 +- tools/feature-docs-verify.sh | 46 +++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/.claude/skills/update-feature-docs/SKILL.md b/.claude/skills/update-feature-docs/SKILL.md index b6b3e466cf..e5be7b6cd0 100644 --- a/.claude/skills/update-feature-docs/SKILL.md +++ b/.claude/skills/update-feature-docs/SKILL.md @@ -33,7 +33,7 @@ To add a new feature doc to the system, just create a `*_FEATURE.md` file with t ``` git diff ..HEAD -- ``` - If there is no diff for a doc, it is up to date — skip it. + If there is no diff for a doc, it is up to date — skip steps 4–7 for that doc and move to the next one. **Do not skip step 8** — registry coverage must be checked even when every doc is fresh. 4. **Read the current source files in full** — read each tracked source file to understand the current public API surface. @@ -56,6 +56,11 @@ To add a new feature doc to the system, just create a `*_FEATURE.md` file with t - `sdk_version` → current version from `DatadogCore.podspec` - `last_updated` → today's date (YYYY-MM-DD) +8. **Update the registries** — when adding, renaming, or removing a `*_FEATURE.md` file, also update every place that hand-lists feature docs. `tools/feature-docs-verify.sh` enforces these and will fail CI otherwise: + - **`.github/workflows/changelog-to-confluence.yaml`** — both the `paths:` filter and the `cp` block. Use the relative path **without a leading slash** (`DatadogRUM/RUM_FEATURE.md`, not `/DatadogRUM/RUM_FEATURE.md`) — leading slashes silently never match in GitHub Actions `paths:` filters. The publish filename is kebab-case derived from the module + doc (`DatadogRUM/RUM_FEATURE.md` → `dd-sdk-ios-rum-feature.md`). + - **`AGENTS.md`** — add the doc to the file tree under "Feature-specific docs" and to the routing table ("Where to Look First"). + - **`docs/LLM_FEATURE_DOCS_GUIDELINES.md`** — add the doc to the expected feature-docs list. + ## Notes - Only update docs for features whose tracked source files actually changed. diff --git a/.github/workflows/changelog-to-confluence.yaml b/.github/workflows/changelog-to-confluence.yaml index 74749074b3..1dfb8be090 100644 --- a/.github/workflows/changelog-to-confluence.yaml +++ b/.github/workflows/changelog-to-confluence.yaml @@ -5,8 +5,8 @@ on: - master paths: - 'CHANGELOG.md' - - '/DatadogRUM/RUM_FEATURE.md' - - '/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md' + - 'DatadogRUM/RUM_FEATURE.md' + - 'DatadogSessionReplay/SESSION_REPLAY_FEATURE.md' permissions: contents: read jobs: diff --git a/tools/feature-docs-verify.sh b/tools/feature-docs-verify.sh index 15cb7f5f21..38ebd474dd 100755 --- a/tools/feature-docs-verify.sh +++ b/tools/feature-docs-verify.sh @@ -118,5 +118,51 @@ for doc in sorted(docs): else: print(f"✅ {doc_name} is up to date (verified at {commit}).") +# Each *_FEATURE.md must be wired into a few hand-maintained registries: +# - the Confluence publish workflow (operational — drift breaks publishing) +# - AGENTS.md (LLM doc map) +# - docs/LLM_FEATURE_DOCS_GUIDELINES.md (expected docs list) +# The workflow is checked strictly: a leading slash on a `paths:` entry never +# matches anything in GitHub Actions, so we flag that case explicitly. +REGISTRIES = [ + (".github/workflows/changelog-to-confluence.yaml", "Confluence publish workflow", True), + ("AGENTS.md", "AGENTS.md", False), + ("docs/LLM_FEATURE_DOCS_GUIDELINES.md", "LLM feature-docs guidelines", False), +] + +doc_rel_paths = sorted(os.path.relpath(d, repo_root) for d in docs) +registry_failed = False + +print() +print("Checking that each feature doc is registered in:") +for path, _, _ in REGISTRIES: + print(f" - {path}") + +for registry_path, label, strict in REGISTRIES: + abs_path = os.path.join(repo_root, registry_path) + if not os.path.exists(abs_path): + print(f"⚠️ {label}: file not found at {registry_path} — skipping.") + continue + with open(abs_path) as f: + content = f.read() + for rel in doc_rel_paths: + total = content.count(rel) + with_slash = content.count("/" + rel) + if total == 0: + print(f"❌ {label}: missing reference to '{rel}'.") + registry_failed = True + elif strict and with_slash > 0: + # In the workflow the path must never appear with a leading slash: + # GitHub Actions `paths:` filters treat `/path` as absolute and never match. + print(f"❌ {label}: '{rel}' is referenced with a leading slash (`/{rel}`),") + print(f" which never matches in GitHub Actions `paths:` filters. Remove the leading slash.") + registry_failed = True + +if registry_failed: + print_fix_instructions() + failed = True +else: + print(f"✅ All {len(doc_rel_paths)} feature doc(s) registered in every required location.") + sys.exit(1 if failed else 0) EOF From 0590f4d4a89da9fb8a3e051e0e3d4afa80e468fe Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Fri, 8 May 2026 18:01:47 +0200 Subject: [PATCH 05/10] RUM-13811 Verify each doc is registered in workflow paths: AND cp block Per review feedback, the workflow registration check now verifies the two distinct contexts (the trigger paths: filter and the cp block) separately. Previously a single substring match would silently pass if only one of the two was present, which left a footgun where a doc-only update would not trigger publishing, or the page would never get copied. --- tools/feature-docs-verify.sh | 62 ++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/tools/feature-docs-verify.sh b/tools/feature-docs-verify.sh index 38ebd474dd..06b213f3c5 100755 --- a/tools/feature-docs-verify.sh +++ b/tools/feature-docs-verify.sh @@ -119,15 +119,18 @@ for doc in sorted(docs): print(f"✅ {doc_name} is up to date (verified at {commit}).") # Each *_FEATURE.md must be wired into a few hand-maintained registries: -# - the Confluence publish workflow (operational — drift breaks publishing) -# - AGENTS.md (LLM doc map) -# - docs/LLM_FEATURE_DOCS_GUIDELINES.md (expected docs list) -# The workflow is checked strictly: a leading slash on a `paths:` entry never -# matches anything in GitHub Actions, so we flag that case explicitly. -REGISTRIES = [ - (".github/workflows/changelog-to-confluence.yaml", "Confluence publish workflow", True), - ("AGENTS.md", "AGENTS.md", False), - ("docs/LLM_FEATURE_DOCS_GUIDELINES.md", "LLM feature-docs guidelines", False), +# - the Confluence publish workflow +# - AGENTS.md +# - docs/LLM_FEATURE_DOCS_GUIDELINES.md +# The Confluence workflow needs the path in TWO distinct places (the trigger +# `paths:` filter AND the `cp` block), so it gets a dedicated check that +# verifies both contexts separately. Other registries just need any mention. + +WORKFLOW_PATH = ".github/workflows/changelog-to-confluence.yaml" +WORKFLOW_LABEL = "Confluence publish workflow" +SUBSTRING_REGISTRIES = [ + ("AGENTS.md", "AGENTS.md"), + ("docs/LLM_FEATURE_DOCS_GUIDELINES.md", "LLM feature-docs guidelines"), ] doc_rel_paths = sorted(os.path.relpath(d, repo_root) for d in docs) @@ -135,10 +138,37 @@ registry_failed = False print() print("Checking that each feature doc is registered in:") -for path, _, _ in REGISTRIES: +print(f" - {WORKFLOW_PATH} (paths: filter AND cp block)") +for path, _ in SUBSTRING_REGISTRIES: print(f" - {path}") -for registry_path, label, strict in REGISTRIES: +# --- Workflow check: two contexts must each contain the path --- +abs_workflow = os.path.join(repo_root, WORKFLOW_PATH) +if not os.path.exists(abs_workflow): + print(f"⚠️ {WORKFLOW_LABEL}: file not found at {WORKFLOW_PATH} — skipping.") +else: + with open(abs_workflow) as f: + workflow_content = f.read() + for rel in doc_rel_paths: + # `paths:` filter entries appear as quoted YAML list items. + in_paths_filter = (f"'{rel}'" in workflow_content) or (f'"{rel}"' in workflow_content) + # `cp` lines start with `cp ...`. + in_cp_block = f"cp {rel} " in workflow_content + + if not in_paths_filter and not in_cp_block: + print(f"❌ {WORKFLOW_LABEL}: '{rel}' is missing from BOTH the `paths:` filter and the `cp` block.") + registry_failed = True + elif not in_paths_filter: + print(f"❌ {WORKFLOW_LABEL}: '{rel}' is missing from the `paths:` filter") + print(f" (expected as `'{rel}'` under `on.push.paths`). Without it, doc-only updates won't trigger publishing.") + registry_failed = True + elif not in_cp_block: + print(f"❌ {WORKFLOW_LABEL}: '{rel}' is missing from the `cp` block") + print(f" (expected as `cp {rel} publish_folder/...`). Without it, the page is never copied for publishing.") + registry_failed = True + +# --- Substring registries (AGENTS.md, LLM guidelines): any mention is enough --- +for registry_path, label in SUBSTRING_REGISTRIES: abs_path = os.path.join(repo_root, registry_path) if not os.path.exists(abs_path): print(f"⚠️ {label}: file not found at {registry_path} — skipping.") @@ -146,17 +176,9 @@ for registry_path, label, strict in REGISTRIES: with open(abs_path) as f: content = f.read() for rel in doc_rel_paths: - total = content.count(rel) - with_slash = content.count("/" + rel) - if total == 0: + if rel not in content: print(f"❌ {label}: missing reference to '{rel}'.") registry_failed = True - elif strict and with_slash > 0: - # In the workflow the path must never appear with a leading slash: - # GitHub Actions `paths:` filters treat `/path` as absolute and never match. - print(f"❌ {label}: '{rel}' is referenced with a leading slash (`/{rel}`),") - print(f" which never matches in GitHub Actions `paths:` filters. Remove the leading slash.") - registry_failed = True if registry_failed: print_fix_instructions() From 76e731acb260f1259770f7a1aacee83854817f14 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Fri, 8 May 2026 18:37:05 +0200 Subject: [PATCH 06/10] RUM-13811 Fail when tracked_files references a missing path `git diff ..HEAD -- ` exits 0 with empty stdout, so a typo or stale renamed path in a doc's frontmatter would silently report as "up to date". Validate each tracked path resolves to a file in the working tree before invoking git diff, and surface the missing entries with the standard fix recipe. --- tools/feature-docs-verify.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/feature-docs-verify.sh b/tools/feature-docs-verify.sh index 06b213f3c5..4e1e9892c3 100755 --- a/tools/feature-docs-verify.sh +++ b/tools/feature-docs-verify.sh @@ -85,6 +85,20 @@ for doc in sorted(docs): failed = True continue + # Validate each tracked path actually exists. `git diff ..HEAD -- ` + # exits 0 with empty output, so without this check a typo or stale renamed + # path would silently report the doc as up to date. + missing = [f for f in files if not os.path.isfile(os.path.join(repo_root, f))] + if missing: + print(f"❌ {doc_name}: tracked_files references paths that don't exist in the working tree:") + for m in missing: + print(f" - {m}") + print(f" The file was likely renamed, moved, or deleted. Update the frontmatter") + print(f" to point at the current path(s).") + print_fix_instructions() + failed = True + continue + # Check whether any tracked file changed since the doc was last verified print(f"Checking {doc_name} against:") for f in files: From c96e865782452dba02acabc980a1538c80217598 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Fri, 8 May 2026 18:53:25 +0200 Subject: [PATCH 07/10] RUM-13811 Fix RUM and SR doc gaps and prevent recurrence in the skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple coverage gaps in the RUM and Session Replay feature docs flagged across two review passes: - RUM: tracked_files was missing DatadogRUM/Sources/RUMMonitor.swift. The doc's "Public API" section documents `RUMMonitor.shared(in:)` (the entry point for manual instrumentation), but the file was not tracked, so changes there could drift the doc while CI stayed green. - Session Replay: tracked_files was missing three public-API source files explicitly referenced in the doc's "Key Files" section: - SessionReplayPrivacyOverrides.swift (UIKit per-view privacy) - SessionReplayPrivacyView.swift (SwiftUI per-view privacy) - DatadogInternal/.../SessionReplayConfiguration.swift (privacy level enums) - Session Replay: doc enumerated SessionReplay.Configuration.FeatureFlag cases but omitted the still-public deprecated `screenChangeScheduling`. Document it in both the Quick Start example comment and the "Available feature flags" reference list, with a clear deprecation note. Strengthen the update-feature-docs skill to prevent both classes of issue from recurring: - Step 2 now audits tracked_files coverage against the doc's "Key Files" section. Every public-API source file path referenced in Key Files must also appear in tracked_files. The audit happens before the diff in step 3, otherwise drift in untracked files is silently ignored. - Step 5 now explicitly calls out deprecated public cases. Deprecated `@available(*, deprecated, message:)` enum cases, methods, and properties remain on the public API and must be documented with a deprecation note. The audit step in step 2 caught the additional RUM and SR gaps when the skill was re-run — confirming the prevention works. Bump verified_against_commit and last_updated on both docs. --- .claude/skills/update-feature-docs/SKILL.md | 2 ++ DatadogRUM/RUM_FEATURE.md | 5 +++-- DatadogSessionReplay/SESSION_REPLAY_FEATURE.md | 13 +++++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.claude/skills/update-feature-docs/SKILL.md b/.claude/skills/update-feature-docs/SKILL.md index e5be7b6cd0..af27ae1d81 100644 --- a/.claude/skills/update-feature-docs/SKILL.md +++ b/.claude/skills/update-feature-docs/SKILL.md @@ -28,6 +28,7 @@ To add a new feature doc to the system, just create a `*_FEATURE.md` file with t 2. **For each doc, read its frontmatter** — extract `verified_against_commit` and `tracked_files`. - If `tracked_files` is missing, derive the list from the doc's "Key Files" section (every source file path it references). Treat the doc as fully out of date and proceed to step 4 — the diff in step 3 cannot be computed. - If `verified_against_commit` is missing, treat the doc as fully out of date and proceed to step 4 — the diff in step 3 cannot be computed. + - **Audit `tracked_files` coverage** against the doc's "Key Files" section. Every public-API source file path referenced in "Key Files" must also appear in `tracked_files`. This audit must happen *here*, before step 3 — otherwise drift in untracked files is silently ignored. If any are missing, add them to `tracked_files` and treat the doc as fully out of date so the newly-tracked files are inspected in step 4. 3. **Get the diff since that commit** — run: ``` @@ -42,6 +43,7 @@ To add a new feature doc to the system, just create a `*_FEATURE.md` file with t - Removed or renamed options still mentioned in the doc - Changed defaults, behaviors, or platform availability - New types, enums, or feature flags not documented + - **Deprecated public cases not documented** — walk every public enum case, method, and property, including those marked `@available(*, deprecated, message:)`. Deprecated cases stay on the public API and must appear in the doc with a clear deprecation note, otherwise customers reading the doc won't know they exist or that they're being phased out. - Outdated code examples 6. **Update the feature doc** — apply all necessary changes: diff --git a/DatadogRUM/RUM_FEATURE.md b/DatadogRUM/RUM_FEATURE.md index f13c40da3c..fb47ea0233 100644 --- a/DatadogRUM/RUM_FEATURE.md +++ b/DatadogRUM/RUM_FEATURE.md @@ -1,10 +1,11 @@ --- -last_updated: 2026-05-05 +last_updated: 2026-05-08 sdk_version: 3.10.0 -verified_against_commit: 228fb06d7 +verified_against_commit: 6bc779cf6 tracked_files: - DatadogRUM/Sources/RUM.swift - DatadogRUM/Sources/RUMConfiguration.swift + - DatadogRUM/Sources/RUMMonitor.swift - DatadogRUM/Sources/RUMMonitorProtocol.swift --- diff --git a/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md b/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md index f0ca781007..ec84fee585 100644 --- a/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md +++ b/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md @@ -1,10 +1,13 @@ --- -last_updated: 2026-05-05 +last_updated: 2026-05-08 sdk_version: 3.10.0 -verified_against_commit: 228fb06d7 +verified_against_commit: 6bc779cf6 tracked_files: - DatadogSessionReplay/Sources/SessionReplay.swift - DatadogSessionReplay/Sources/SessionReplayConfiguration.swift + - DatadogSessionReplay/Sources/SessionReplayPrivacyOverrides.swift + - DatadogSessionReplay/Sources/SessionReplayPrivacyView.swift + - DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift --- # Session Replay Feature @@ -88,8 +91,9 @@ SessionReplay.enable( // Feature flags for experimental features // Default: [.swiftui: false] // Available flags: - // .swiftui - Enable SwiftUI recording (experimental) - // .heatmaps - Enable heatmap identifier computation (experimental) + // .swiftui - Enable SwiftUI recording (experimental) + // .heatmaps - Enable heatmap identifier computation (experimental) + // .screenChangeScheduling - DEPRECATED: now default and always enabled; setting it has no effect featureFlags: [ .swiftui: true, // Enable SwiftUI recording (experimental) .heatmaps: false // Enable heatmap identifier computation (experimental) @@ -228,6 +232,7 @@ SessionReplayPrivacyView( ### Available feature flags - `.swiftui` — Enable SwiftUI recording (experimental, default: `false`) - `.heatmaps` — Enable heatmap identifier computation (experimental, default: `false`) +- `.screenChangeScheduling` — **Deprecated.** Screen change scheduling is now the default and always enabled; setting this flag has no effect. Kept on the public API for backward compatibility. ## Feature Interactions From 09008a0466f3765f1fe538c82cf28edf5b1583c6 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Mon, 11 May 2026 13:22:51 +0200 Subject: [PATCH 08/10] RUM-13811 Reorder trackSlowFrames in RUM doc snippet to match init Swift named-argument calls must follow the initializer declaration order. In RUMConfiguration.init, trackSlowFrames sits between trackMemoryWarnings and telemetrySampleRate, but the Quick Start snippet placed it after telemetrySampleRate and collectAccessibility, so copy-pasting the example produced an "argument 'trackSlowFrames' must precede argument 'telemetrySampleRate'" compile error. Move trackSlowFrames into the correct slot and bump frontmatter. --- DatadogRUM/RUM_FEATURE.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DatadogRUM/RUM_FEATURE.md b/DatadogRUM/RUM_FEATURE.md index fb47ea0233..e17b462366 100644 --- a/DatadogRUM/RUM_FEATURE.md +++ b/DatadogRUM/RUM_FEATURE.md @@ -1,7 +1,7 @@ --- -last_updated: 2026-05-08 +last_updated: 2026-05-11 sdk_version: 3.10.0 -verified_against_commit: 6bc779cf6 +verified_against_commit: b584ef3af tracked_files: - DatadogRUM/Sources/RUM.swift - DatadogRUM/Sources/RUMConfiguration.swift @@ -156,6 +156,10 @@ RUM.enable( // Default: true trackMemoryWarnings: true, + // Track slow frames / view hitches + // Default: true + trackSlowFrames: true, + // SDK telemetry sampling rate (for Datadog internal monitoring) // Default: 20.0 telemetrySampleRate: 20.0, @@ -164,10 +168,6 @@ RUM.enable( // Default: false collectAccessibility: false, - // Track slow frames / view hitches - // Default: true - trackSlowFrames: true, - // Experimental feature flags (currently no active flags for RUM) // Default: [:] featureFlags: [:] From a3e45cb48332aa7be8aad3701c7a26e734facd70 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Mon, 11 May 2026 13:23:10 +0200 Subject: [PATCH 09/10] RUM-13811 Use full clone in the Feature Docs Verify GitLab job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitLab CI shallow-clones at depth 20 by default. tools/feature-docs-verify.sh runs `git diff ..HEAD` per doc, which requires the baseline commit to be in the local history. On release/hotfix branches, any doc whose verified_against_commit is older than 20 commits would otherwise silently fail the diff before the script can actually verify anything — turning a CI configuration quirk into a doc-verification false alarm. Stable feature docs that go several releases without changes would hit this every time on any bounded depth. The job is scoped to release/* and hotfix/* branches (a handful of runs per release cycle), so a full clone is the simplest reliable answer. --- .gitlab-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1781b3ac10..31bdf05b33 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -141,6 +141,14 @@ Feature Docs Verify: stage: lint rules: - if: '$CI_COMMIT_BRANCH =~ /^(release|hotfix)\/.*/' + # tools/feature-docs-verify.sh runs `git diff ..HEAD`, + # which requires that commit to be reachable in the local history. GitLab CI + # shallow-clones at depth 20 by default, which would silently bury baselines + # for any feature doc that hasn't been touched in a few releases (e.g. Logs). + # The job only runs on release/hotfix branches (a handful of times per cycle), + # so the cost of a full clone is negligible. + variables: + GIT_DEPTH: "0" script: - make feature-docs-verify From 743ff7e65bf15967a8f849472368a603604d7e16 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Tue, 12 May 2026 17:44:20 +0200 Subject: [PATCH 10/10] Fix commit in SR doc --- DatadogSessionReplay/SESSION_REPLAY_FEATURE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md b/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md index ec84fee585..a79d9175e7 100644 --- a/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md +++ b/DatadogSessionReplay/SESSION_REPLAY_FEATURE.md @@ -1,7 +1,7 @@ --- -last_updated: 2026-05-08 +last_updated: 2026-05-12 sdk_version: 3.10.0 -verified_against_commit: 6bc779cf6 +verified_against_commit: cab13ec55 tracked_files: - DatadogSessionReplay/Sources/SessionReplay.swift - DatadogSessionReplay/Sources/SessionReplayConfiguration.swift