diff --git a/.claude/launch.json b/.claude/launch.json index 8f541789..9a23eaab 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -5,7 +5,14 @@ "name": "astro-dev", "runtimeExecutable": "npm", "runtimeArgs": ["run", "dev"], - "port": 4321 + "port": 4321, + "autoPort": true + }, + { + "name": "astro-preview", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "preview", "--", "--port", "4322"], + "port": 4322 } ] } diff --git a/.github/ai-docs-digest/docs-area-map.yml b/.github/ai-docs-digest/docs-area-map.yml deleted file mode 100644 index 147f294a..00000000 --- a/.github/ai-docs-digest/docs-area-map.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Additional fine-grained docs-area rules. -# These supplement product-area-map.yml with docs-specific path signals. -mappings: - - match: - paths: - - 'CHANGELOG*' - - 'BREAKING*' - - 'MIGRATION*' - - 'UPGRADE*' - docs_area: Release Notes / Migration guides - confidence: high - - - match: - paths: - - 'README*' - - 'docs/**' - - '*.md' - docs_area: General documentation - confidence: medium - - - match: - paths: - - 'docker-compose*' - - 'Dockerfile*' - - '.env.example' - - 'docker/**' - docs_area: On-Prem / Installation - confidence: medium - - - match: - paths: - - 'helm/**' - - 'charts/**' - - 'values*.yaml' - - 'values*.yml' - docs_area: On-Prem / Installation - confidence: high - - - match: - paths: - - 'openapi*' - - 'swagger*' - - 'api-spec*' - - 'api/**' - docs_area: API reference - confidence: high diff --git a/.github/ai-docs-digest/prompts/summarize-system.txt b/.github/ai-docs-digest/prompts/summarize-system.txt deleted file mode 100644 index 3b44c6d2..00000000 --- a/.github/ai-docs-digest/prompts/summarize-system.txt +++ /dev/null @@ -1,24 +0,0 @@ -You are a senior technical writer and product documentation specialist at KloudMate. - -Your task is to analyze a structured list of product changes from the previous week across multiple repositories and produce a **weekly customer-focused digest** in strict markdown format. - -## Your responsibilities - -1. **Prioritize customer-facing changes** — features, behavior changes, configuration changes, breaking changes, and user-visible fixes. -2. **De-emphasize or omit** purely internal changes: refactors, CI/CD only changes, internal tooling, lock-file bumps, test additions with no user impact. -3. **Be factual and accurate** — do not speculate about changes. If you are uncertain, state so explicitly. -4. **Organize output into the exact required sections** listed in the user prompt. Do not add extra top-level sections or rename them. -5. **Generate concise, documentation-friendly wording** — short sentences, active voice, action-oriented (e.g. "Added support for…", "Fixed an issue where…"). -6. **For the Docs Impact section**, reason carefully about which documentation pages are likely affected, cite the evidence (repo, PR number, changed file paths), assign confidence (High / Medium / Low), and suggest the likely docs area. -7. **Avoid hallucination** — every claim in the digest must be grounded in the provided change data. -8. **Be concise in the Executive Summary** — two to four sentences maximum. -9. If a section has no entries, write `_None this week._` rather than omitting it. -10. For the Source References section, list every PR and direct commit cited in the digest with its full URL. - -## Output contract - -- Return **only** the markdown document. No preamble, no trailing commentary. -- Use the exact section headings provided in the user prompt. -- Use bullet lists for individual change items. -- Use bold for product/feature names in list items. -- Frontmatter (YAML between `---` fences) must be the very first thing in the output. diff --git a/.github/ai-docs-digest/prompts/summarize-user.txt b/.github/ai-docs-digest/prompts/summarize-user.txt deleted file mode 100644 index b715bfb6..00000000 --- a/.github/ai-docs-digest/prompts/summarize-user.txt +++ /dev/null @@ -1,70 +0,0 @@ -Generate the weekly KloudMate product digest for the period below. - -## Period -Start: {{PERIOD_START}} -End: {{PERIOD_END}} - -## Change data -The following JSON array contains all normalized, classified change items collected from the configured repositories for this period. Each item includes heuristic priors for `visibility` (customer | internal | mixed | unknown) and `category` (new | changed | breaking | fix | internal). Use these as strong signals but apply your own judgment. - -Infer the product area and likely docs area for each change from the repository name, changed file paths, PR title, and description. Do not rely on pre-classified mappings — reason about what part of the product each change affects. - -```json -{{CHANGES_JSON}} -``` - -## Output format - -Produce a single markdown document with the following structure **exactly**: - -``` ---- -title: Weekly Product Digest — {{WEEK_LABEL}} -summary: Customer-facing product and documentation-impact summary for the week of {{PERIOD_START}}. -date: {{WEEK_LABEL}} -period_start: {{PERIOD_START}} -period_end: {{PERIOD_END}} -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: {{PERIOD_START}} to {{PERIOD_END}} - -## Executive Summary -<2–4 sentence overview of the week's highlights> - -## New - - -## Changed - - -## Breaking Changes - - -## Fixes - - -## Docs Impact - - -## Internal / Excluded Notes - - -## Source References - -``` - -### Docs Impact item format - -For each likely docs-impacting change, use: - -``` -- **** - - Reason: - - Likely docs area: - - Confidence: High | Medium | Low - - Evidence: , `` -``` - -Now generate the digest. diff --git a/.github/ai-docs-digest/repos.yml b/.github/ai-docs-digest/repos.yml deleted file mode 100644 index 4097d747..00000000 --- a/.github/ai-docs-digest/repos.yml +++ /dev/null @@ -1,7 +0,0 @@ -# List of repositories to include in the weekly AI docs digest. -# Add or remove repository names here; no workflow logic changes required. -repositories: - - kloudmate/kloudmate-frontend - - kloudmate/kloudmate-backend - - kloudmate/kloudmate-otel-backend - - kloudmate/km-agent diff --git a/.github/ai-docs-digest/scripts/.gitignore b/.github/ai-docs-digest/scripts/.gitignore deleted file mode 100644 index b9470778..00000000 --- a/.github/ai-docs-digest/scripts/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -dist/ diff --git a/.github/ai-docs-digest/scripts/build_prompt.ts b/.github/ai-docs-digest/scripts/build_prompt.ts deleted file mode 100644 index 95738841..00000000 --- a/.github/ai-docs-digest/scripts/build_prompt.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * build_prompt.ts - * - * Phase 4: Build the system + user prompts from classified change items and - * the prompt templates. Writes the combined prompt to an artifact file. - */ - -import { readFileSync, writeFileSync, mkdirSync } from "fs"; -import { resolve, dirname } from "path"; -import { fileURLToPath } from "url"; -import type { ChangeItem, PipelineState } from "./types.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const REPO_ROOT = resolve(__dirname, "..", "..", ".."); -const PROMPTS_DIR = resolve( - REPO_ROOT, - ".github", - "ai-docs-digest", - "prompts" -); - -/** - * Approximate token count heuristic (1 token ≈ 4 chars). - * Used to cap prompt size before sending to the LLM. - */ -const MAX_PROMPT_CHARS = 120_000; // ~30k tokens — safe for most models with 128k context - -/** Maximum number of changed files listed per change item in the prompt. */ -const MAX_FILES_PER_ITEM = 15; -/** Maximum number of PR comments included per change item in the prompt. */ -const MAX_COMMENTS_PER_ITEM = 10; -/** Maximum body length (chars) for each PR comment in the prompt. */ -const MAX_COMMENT_BODY_CHARS = 300; - -export interface BuiltPrompt { - systemPrompt: string; - userPrompt: string; -} - -export function buildPrompt(state: PipelineState): BuiltPrompt { - const allItems: ChangeItem[] = JSON.parse( - readFileSync(state.classifiedFile, "utf8") - ); - - // Separate customer-relevant from clearly internal. - const relevant = allItems.filter( - (i) => i.visibility !== "internal" || i.category !== "internal" - ); - const excluded = allItems.filter( - (i) => i.visibility === "internal" && i.category === "internal" - ); - - console.log( - `[build-prompt] Items in prompt: ${relevant.length}, excluded: ${excluded.length}` - ); - - // Trim changed file lists and comments to keep prompt size reasonable. - const trimmedItems = relevant.map((item) => ({ - ...item, - changedFiles: item.changedFiles.slice(0, MAX_FILES_PER_ITEM), - description: - item.description && item.description.length > 800 - ? item.description.slice(0, 800) + " …[truncated]" - : item.description, - comments: (item.comments ?? []).slice(0, MAX_COMMENTS_PER_ITEM).map((c) => ({ - author: c.author, - created_at: c.created_at, - body: - c.body.length > MAX_COMMENT_BODY_CHARS - ? c.body.slice(0, MAX_COMMENT_BODY_CHARS) + " …[truncated]" - : c.body, - })), - })); - - const changesJson = JSON.stringify(trimmedItems, null, 2); - - const systemTemplate = readFileSync( - resolve(PROMPTS_DIR, "summarize-system.txt"), - "utf8" - ); - const userTemplate = readFileSync( - resolve(PROMPTS_DIR, "summarize-user.txt"), - "utf8" - ); - - const userPrompt = userTemplate - .replace(/\{\{PERIOD_START\}\}/g, state.weekStart) - .replace(/\{\{PERIOD_END\}\}/g, state.weekEnd) - .replace(/\{\{WEEK_LABEL\}\}/g, state.weekLabel) - .replace(/\{\{CHANGES_JSON\}\}/g, changesJson); - - // Enforce token cap — trim the middle of the changes JSON if necessary. - const combined = systemTemplate + userPrompt; - const finalPrompt = - combined.length > MAX_PROMPT_CHARS - ? capPrompt(systemTemplate, userTemplate, trimmedItems, state) - : { systemPrompt: systemTemplate, userPrompt }; - - // Persist for debugging. - mkdirSync(dirname(state.promptFile), { recursive: true }); - writeFileSync( - state.promptFile, - `=== SYSTEM PROMPT ===\n${finalPrompt.systemPrompt}\n\n=== USER PROMPT ===\n${finalPrompt.userPrompt}` - ); - - console.log( - `[build-prompt] ✓ Prompt size: ${(finalPrompt.systemPrompt + finalPrompt.userPrompt).length} chars` - ); - - return finalPrompt; -} - -// ────────────────────────────────────────────────────────────────────────────── -// Helpers -// ────────────────────────────────────────────────────────────────────────────── - -/** Hard-cap the prompt by reducing the number of items included. */ -function capPrompt( - systemTemplate: string, - userTemplate: string, - items: ChangeItem[], - state: PipelineState -): BuiltPrompt { - // Keep only the most relevant items (customer/mixed first, breaking first). - const priority = ["breaking", "new", "fix", "changed", "internal"]; - const sorted = [...items].sort( - (a, b) => - priority.indexOf(a.category ?? "internal") - - priority.indexOf(b.category ?? "internal") - ); - - let subset = sorted; - let userPrompt = ""; - - while (subset.length > 0) { - const changesJson = JSON.stringify(subset, null, 2); - userPrompt = userTemplate - .replace(/\{\{PERIOD_START\}\}/g, state.weekStart) - .replace(/\{\{PERIOD_END\}\}/g, state.weekEnd) - .replace(/\{\{WEEK_LABEL\}\}/g, state.weekLabel) - .replace(/\{\{CHANGES_JSON\}\}/g, changesJson); - - if ((systemTemplate + userPrompt).length <= MAX_PROMPT_CHARS) break; - subset = subset.slice(0, Math.floor(subset.length * 0.8)); - } - - if (subset.length < items.length) { - console.warn( - `[build-prompt] ⚠ Prompt capped: ${subset.length}/${items.length} items included.` - ); - } - - return { systemPrompt: systemTemplate, userPrompt }; -} diff --git a/.github/ai-docs-digest/scripts/classify_changes.ts b/.github/ai-docs-digest/scripts/classify_changes.ts deleted file mode 100644 index cb9547d2..00000000 --- a/.github/ai-docs-digest/scripts/classify_changes.ts +++ /dev/null @@ -1,265 +0,0 @@ -/** - * classify_changes.ts - * - * Phase 3: Apply heuristics to score each change item: - * - Docs area from file-path mappings - * - Visibility (customer | internal | mixed | unknown) - * - Category (new | changed | breaking | fix | internal) - * - Product area is left for the LLM to infer - */ - -import { readFileSync, writeFileSync } from "fs"; -import { resolve, dirname } from "path"; -import { fileURLToPath } from "url"; -import yaml from "js-yaml"; -import { minimatch } from "minimatch"; -import type { - ChangeItem, - DocsAreaMappingRule, - PipelineState, -} from "./types.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const REPO_ROOT = resolve(__dirname, "..", "..", ".."); -const CONFIG_DIR = resolve(REPO_ROOT, ".github", "ai-docs-digest"); - -// ────────────────────────────────────────────────────────────────────────────── -// Internal-only signals -// ────────────────────────────────────────────────────────────────────────────── - -const INTERNAL_LABEL_PATTERNS = [ - /^internal$/i, - /^chore$/i, - /^refactor$/i, - /^ci$/i, - /^cd$/i, - /^deps$/i, - /^dependencies$/i, - /^dev-deps$/i, - /^no-release$/i, -]; - -const INTERNAL_PATH_GLOBS = [ - ".github/**", - ".circleci/**", - ".travis.yml", - "tests/**", - "test/**", - "__tests__/**", - "spec/**", - "scripts/**", - "*.lock", - "package-lock.json", - "yarn.lock", - "pnpm-lock.yaml", - "go.sum", - "Cargo.lock", - ".eslintrc*", - ".prettierrc*", - "jest.config*", - "vitest.config*", -]; - -const INTERNAL_TITLE_PATTERNS = [ - /^(chore|ci|test|refactor|style|build)\b/i, - /\[skip release\]/i, - /\[internal\]/i, - /bump .* from .* to /i, -]; - -// ────────────────────────────────────────────────────────────────────────────── -// Customer-facing signals -// ────────────────────────────────────────────────────────────────────────────── - -const CUSTOMER_LABEL_PATTERNS = [ - /^feature$/i, - /^feat$/i, - /^enhancement$/i, - /^bug$/i, - /^fix$/i, - /^breaking$/i, - /^breaking-change$/i, - /^customer-facing$/i, - /^security$/i, - /^deprecation$/i, - /^migration$/i, - /^upgrade$/i, -]; - -const CUSTOMER_TITLE_PATTERNS = [ - /^(feat|feature|add|added|adds|introduce|support|enable|allow)\b/i, - /^(fix|fixes|fixed|resolve|resolved|resolves|close|closes|closed)\b/i, - /^(improve|improved|improves|enhance|enhanced|update|updated|updates)\b/i, - /^(deprecate|deprecates|deprecated|remove|removes|removed|rename|renames|renamed)\b/i, - /\bbreaking\b/i, - /\bmigration\b/i, - /\bbreaking change/i, -]; - -// ────────────────────────────────────────────────────────────────────────────── -// Category signals -// ────────────────────────────────────────────────────────────────────────────── - -function inferCategory( - item: ChangeItem -): ChangeItem["category"] { - const text = `${item.title} ${item.description ?? ""}`.toLowerCase(); - const labelNames = item.labels.map((l) => l.toLowerCase()); - - // Breaking first — highest priority. - if ( - labelNames.some((l) => /breaking/.test(l)) || - /breaking|migration required|deprecated|removed?\b/.test(text) - ) { - return "breaking"; - } - - // New features. - if ( - labelNames.some((l) => /^(feature|feat|enhancement)$/.test(l)) || - /^(feat|feature|add|adds|added|introduce|support|enable|allow)\b/.test( - item.title.toLowerCase() - ) - ) { - return "new"; - } - - // Bug fixes. - if ( - labelNames.some((l) => /^(bug|fix|bugfix)$/.test(l)) || - /^(fix|fixes|fixed|resolve|resolves|resolved|close|closes|closed)\b/.test( - item.title.toLowerCase() - ) - ) { - return "fix"; - } - - // Internal. - if (isLikelyInternal(item)) return "internal"; - - // Default: changed. - return "changed"; -} - -function isLikelyInternal(item: ChangeItem): boolean { - // Label check. - const hasInternalLabel = item.labels.some((l) => - INTERNAL_LABEL_PATTERNS.some((p) => p.test(l)) - ); - if (hasInternalLabel) return true; - - // Title check. - if (INTERNAL_TITLE_PATTERNS.some((p) => p.test(item.title))) return true; - - // File path check: if ALL changed files are in internal paths, it's internal. - if (item.changedFiles.length > 0) { - const allInternal = item.changedFiles.every((f) => - INTERNAL_PATH_GLOBS.some((g) => minimatch(f, g, { dot: true })) - ); - if (allInternal) return true; - } - - return false; -} - -function inferVisibility(item: ChangeItem): ChangeItem["visibility"] { - // Check labels. - const hasCustomerLabel = item.labels.some((l) => - CUSTOMER_LABEL_PATTERNS.some((p) => p.test(l)) - ); - const hasInternalLabel = item.labels.some((l) => - INTERNAL_LABEL_PATTERNS.some((p) => p.test(l)) - ); - - if (hasCustomerLabel && hasInternalLabel) return "mixed"; - if (hasCustomerLabel) return "customer"; - if (hasInternalLabel) return "internal"; - - // Title-based signals. - const titleCustomer = CUSTOMER_TITLE_PATTERNS.some((p) => - p.test(item.title) - ); - const titleInternal = INTERNAL_TITLE_PATTERNS.some((p) => - p.test(item.title) - ); - - if (titleCustomer && !titleInternal) return "customer"; - if (titleInternal && !titleCustomer) return "internal"; - if (titleCustomer && titleInternal) return "mixed"; - - // File path signals. - if (item.changedFiles.length > 0) { - const internalFiles = item.changedFiles.filter((f) => - INTERNAL_PATH_GLOBS.some((g) => minimatch(f, g, { dot: true })) - ); - const ratio = internalFiles.length / item.changedFiles.length; - if (ratio === 1) return "internal"; - if (ratio > 0.5) return "mixed"; - if (ratio < 0.5) return "customer"; - } - - return "unknown"; -} - -// ────────────────────────────────────────────────────────────────────────────── -// Docs area mapping -// ────────────────────────────────────────────────────────────────────────────── - -function loadDocsAreaMap(): DocsAreaMappingRule[] { - const raw = yaml.load( - readFileSync( - resolve(CONFIG_DIR, "docs-area-map.yml"), - "utf8" - ) - ) as { mappings: DocsAreaMappingRule[] }; - return raw.mappings ?? []; -} - -function applyDocsAreaMapping( - item: ChangeItem, - rules: DocsAreaMappingRule[] -): void { - const docsAreas = new Set(item.docsArea ?? []); - - for (const rule of rules) { - const matched = item.changedFiles.some((f) => - rule.match.paths.some((g) => minimatch(f, g, { dot: true })) - ); - if (!matched) continue; - docsAreas.add(rule.docs_area); - } - - if (docsAreas.size > 0) { - item.docsArea = [...docsAreas]; - } -} - -// ────────────────────────────────────────────────────────────────────────────── -// Main export -// ────────────────────────────────────────────────────────────────────────────── - -export function classifyChanges(state: PipelineState): void { - const items: ChangeItem[] = JSON.parse( - readFileSync(state.normalizedFile, "utf8") - ); - - const docsAreaRules = loadDocsAreaMap(); - - for (const item of items) { - applyDocsAreaMapping(item, docsAreaRules); - item.visibility = inferVisibility(item); - item.category = inferCategory(item); - } - - writeFileSync(state.classifiedFile, JSON.stringify(items, null, 2)); - - const customerFacing = items.filter( - (i) => i.visibility === "customer" || i.visibility === "mixed" - ); - const internal = items.filter((i) => i.visibility === "internal"); - const unknown = items.filter((i) => i.visibility === "unknown"); - - console.log( - `[classify] ✓ ${items.length} items — customer: ${customerFacing.length}, internal: ${internal.length}, unknown: ${unknown.length}` - ); -} diff --git a/.github/ai-docs-digest/scripts/collect_changes.ts b/.github/ai-docs-digest/scripts/collect_changes.ts deleted file mode 100644 index d43b1670..00000000 --- a/.github/ai-docs-digest/scripts/collect_changes.ts +++ /dev/null @@ -1,355 +0,0 @@ -/** - * collect_changes.ts - * - * Phase 1: Collect merged PRs and direct commits for each configured repository - * within the weekly window. Saves raw JSON to the artifacts directory. - */ - -import { Octokit } from "@octokit/rest"; -import { readFileSync, writeFileSync, mkdirSync } from "fs"; -import { resolve, dirname } from "path"; -import { fileURLToPath } from "url"; -import yaml from "js-yaml"; -import type { RawPR, RawCommit, RawPRComment, PipelineState } from "./types.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -/** Root of the docs repository (two levels above scripts/). */ -const REPO_ROOT = resolve(__dirname, "..", "..", ".."); - -/** Maximum number of files to fetch per PR (to keep prompt size bounded). */ -const MAX_PR_FILES = 50; - -/** Maximum number of items fetched per repo. */ -const MAX_PRS_PER_REPO = 100; -const MAX_COMMITS_PER_REPO = 200; -/** Maximum number of comments to fetch per PR. */ -const MAX_PR_COMMENTS = 20; - -export async function collectChanges(state: PipelineState): Promise { - const token = process.env.GITHUB_TOKEN; - if (!token) throw new Error("GITHUB_TOKEN environment variable is required"); - - const octokit = new Octokit({ auth: token }); - - // Load repo list. - const reposConfig = yaml.load( - readFileSync( - resolve(REPO_ROOT, ".github", "ai-docs-digest", "repos.yml"), - "utf8" - ) - ) as { repositories: string[] }; - - const repos = reposConfig.repositories; - console.log(`[collect] Repositories: ${repos.join(", ")}`); - console.log( - `[collect] Window: ${state.weekStart} → ${state.weekEnd} (UTC)` - ); - - const since = `${state.weekStart}T00:00:00Z`; - const until = `${state.weekEnd}T23:59:59Z`; - - const allRawPRs: RawPR[] = []; - const allRawCommits: RawCommit[] = []; - const errors: string[] = []; - - for (const repoEntry of repos) { - // Support both "owner/repo" and bare "repo" formats. - const slashIdx = repoEntry.indexOf("/"); - const owner = slashIdx !== -1 ? repoEntry.slice(0, slashIdx) : "kloudmate"; - const repoName = slashIdx !== -1 ? repoEntry.slice(slashIdx + 1) : repoEntry; - - try { - console.log(`[collect] Processing ${owner}/${repoName} …`); - - // Resolve default branch. - let defaultBranch = "main"; - try { - const { data: repoMeta } = await octokit.repos.get({ - owner, - repo: repoName, - }); - defaultBranch = repoMeta.default_branch; - } catch (err: unknown) { - const msg = err instanceof Error ? err.message : String(err); - console.warn( - `[collect] ⚠ Could not fetch repo metadata for ${owner}/${repoName}: ${msg}` - ); - errors.push(`${owner}/${repoName}: failed to fetch repo metadata — ${msg}`); - continue; - } - - // ── Collect merged PRs ────────────────────────────────────────────────── - const prs = await collectMergedPRs( - octokit, - owner, - repoName, - defaultBranch, - since, - until - ); - console.log(`[collect] PRs merged: ${prs.length}`); - allRawPRs.push(...prs); - - // Build a set of merge commit SHAs so we can skip them in direct commits. - const prMergeSHAs = new Set(); - for (const pr of prs) { - if (pr.commits) { - for (const c of pr.commits) prMergeSHAs.add(c.sha); - } - } - - // ── Collect direct commits ────────────────────────────────────────────── - const commits = await collectDirectCommits( - octokit, - owner, - repoName, - defaultBranch, - since, - until, - prMergeSHAs - ); - console.log(`[collect] Direct commits: ${commits.length}`); - allRawCommits.push(...commits); - } catch (err: unknown) { - const msg = err instanceof Error ? err.message : String(err); - console.error(`[collect] ✗ Error processing ${owner}/${repoName}: ${msg}`); - errors.push(`${owner}/${repoName}: ${msg}`); - } - } - - // Persist raw data. - mkdirSync(dirname(state.rawPRsFile), { recursive: true }); - writeFileSync(state.rawPRsFile, JSON.stringify(allRawPRs, null, 2)); - writeFileSync(state.rawCommitsFile, JSON.stringify(allRawCommits, null, 2)); - - if (errors.length > 0) { - writeFileSync( - resolve(dirname(state.rawPRsFile), "collect-errors.json"), - JSON.stringify(errors, null, 2) - ); - console.warn(`[collect] ⚠ Completed with ${errors.length} error(s).`); - } - - console.log( - `[collect] ✓ Saved ${allRawPRs.length} PRs and ${allRawCommits.length} direct commits.` - ); -} - -// ────────────────────────────────────────────────────────────────────────────── -// Helpers -// ────────────────────────────────────────────────────────────────────────────── - -async function collectMergedPRs( - octokit: Octokit, - owner: string, - repo: string, - base: string, - since: string, - until: string -): Promise { - const results: RawPR[] = []; - let page = 1; - - while (results.length < MAX_PRS_PER_REPO) { - const { data: prs } = await octokit.pulls.list({ - owner, - repo, - state: "closed", - base, - sort: "updated", - direction: "desc", - per_page: 100, - page, - }); - - if (prs.length === 0) break; - - let reachedWindow = false; - for (const pr of prs) { - if (!pr.merged_at) continue; - if (pr.merged_at < since) { - reachedWindow = true; - break; - } - if (pr.merged_at > until) continue; - - // Fetch file list and commit list for this PR. - const files = await fetchPRFiles(octokit, owner, repo, pr.number); - const commits = await fetchPRCommitSHAs(octokit, owner, repo, pr.number); - const comments = await fetchPRComments(octokit, owner, repo, pr.number); - - // Fetch full PR details to get additions/deletions (not available in list response). - let additions = 0; - let deletions = 0; - let changed_files = 0; - try { - const { data: full } = await octokit.pulls.get({ - owner, - repo, - pull_number: pr.number, - }); - additions = full.additions ?? 0; - deletions = full.deletions ?? 0; - changed_files = full.changed_files ?? 0; - } catch { - /* non-fatal — leave as 0 */ - } - - results.push({ - repo, - number: pr.number, - title: pr.title, - body: pr.body, - state: pr.state, - merged_at: pr.merged_at, - user: pr.user ? { login: pr.user.login } : null, - labels: (pr.labels ?? []).map((l) => ({ name: l.name ?? "" })), - html_url: pr.html_url, - additions, - deletions, - changed_files, - files, - commits, - comments, - }); - } - - if (reachedWindow || prs.length < 100) break; - page++; - } - - return results; -} - -async function fetchPRFiles( - octokit: Octokit, - owner: string, - repo: string, - prNumber: number -): Promise> { - try { - const { data } = await octokit.pulls.listFiles({ - owner, - repo, - pull_number: prNumber, - per_page: MAX_PR_FILES, - }); - return data.map((f) => ({ filename: f.filename })); - } catch { - return []; - } -} - -async function fetchPRCommitSHAs( - octokit: Octokit, - owner: string, - repo: string, - prNumber: number -): Promise> { - try { - const { data } = await octokit.pulls.listCommits({ - owner, - repo, - pull_number: prNumber, - per_page: 250, - }); - return data.map((c) => ({ sha: c.sha })); - } catch { - return []; - } -} - -async function fetchPRComments( - octokit: Octokit, - owner: string, - repo: string, - prNumber: number -): Promise { - try { - const { data } = await octokit.issues.listComments({ - owner, - repo, - issue_number: prNumber, - per_page: MAX_PR_COMMENTS, - }); - return data.map((c) => ({ - author: c.user?.login ?? "unknown", - body: c.body ?? "", - created_at: c.created_at, - })); - } catch { - return []; - } -} - -async function collectDirectCommits( - octokit: Octokit, - owner: string, - repo: string, - branch: string, - since: string, - until: string, - skipSHAs: Set -): Promise { - const results: RawCommit[] = []; - let page = 1; - - while (results.length < MAX_COMMITS_PER_REPO) { - const { data: commits } = await octokit.repos.listCommits({ - owner, - repo, - sha: branch, - since, - until, - per_page: 100, - page, - }); - - if (commits.length === 0) break; - - for (const c of commits) { - // Skip merge commits that are already represented by PRs. - if (skipSHAs.has(c.sha)) continue; - // Skip typical merge commit messages. - const msg = c.commit.message ?? ""; - if (/^Merge pull request #\d+/i.test(msg)) continue; - - // Fetch file details for this commit (abbreviated). - let files: Array<{ filename: string; additions: number; deletions: number }> = []; - try { - const { data: detail } = await octokit.repos.getCommit({ - owner, - repo, - ref: c.sha, - }); - files = (detail.files ?? []).slice(0, MAX_PR_FILES).map((f) => ({ - filename: f.filename ?? "", - additions: f.additions ?? 0, - deletions: f.deletions ?? 0, - })); - } catch { - /* non-fatal */ - } - - results.push({ - repo, - sha: c.sha, - commit: { - message: c.commit.message ?? "", - author: c.commit.author - ? { name: c.commit.author.name ?? "", date: c.commit.author.date ?? "" } - : null, - }, - author: c.author ? { login: c.author.login } : null, - html_url: c.html_url, - files, - }); - } - - if (commits.length < 100) break; - page++; - } - - return results; -} diff --git a/.github/ai-docs-digest/scripts/commit_digest.ts b/.github/ai-docs-digest/scripts/commit_digest.ts deleted file mode 100644 index 7787888a..00000000 --- a/.github/ai-docs-digest/scripts/commit_digest.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * commit_digest.ts - * - * Phase 6: Commit the generated markdown digest to the docs repository. - * Uses the GitHub REST API (createOrUpdateFileContents) so that authentication - * works transparently from the GITHUB_TOKEN environment variable. - * - * Behaviour: - * - If the file does not yet exist, create it. - * - If the file already exists with the same content, skip the commit (idempotent). - * - If the file already exists with different content, update it. - */ - -import { readFileSync } from "fs"; -import { Octokit } from "@octokit/rest"; -import type { PipelineState } from "./types.js"; - -export async function commitDigest( - state: PipelineState, - digestContent: string -): Promise { - const token = process.env.GITHUB_TOKEN; - if (!token) throw new Error("GITHUB_TOKEN environment variable is required"); - - const repoFull = process.env.GITHUB_REPOSITORY ?? ""; - if (!repoFull.includes("/")) { - throw new Error( - "GITHUB_REPOSITORY must be set in the form owner/repo (e.g. kloudmate/docs)" - ); - } - const [owner, repo] = repoFull.split("/"); - - const octokit = new Octokit({ auth: token }); - - const filePath = state.digestFile; // e.g. content/engineering-digests/2026/2026-04-13-weekly-digest.md - const commitMessage = `docs: add weekly digest for ${state.weekLabel} [skip ci]`; - const encodedContent = Buffer.from(digestContent).toString("base64"); - - // Check if the file already exists so we can get its SHA (required for updates). - let existingSha: string | undefined; - let existingContent: string | undefined; - - try { - const { data } = await octokit.repos.getContent({ - owner, - repo, - path: filePath, - }); - - if (!Array.isArray(data) && data.type === "file") { - existingSha = data.sha; - existingContent = data.encoding === "base64" - ? Buffer.from(data.content.replace(/\n/g, ""), "base64").toString("utf8") - : undefined; - } - } catch (err: unknown) { - const status = (err as { status?: number }).status; - if (status !== 404) throw err; - // 404 → file doesn't exist yet, proceed to create. - } - - // Skip commit if content is unchanged. - if (existingContent !== undefined && existingContent === digestContent) { - console.log( - "[commit] ✓ Digest content unchanged — skipping commit." - ); - return; - } - - const action = existingSha ? "Updating" : "Creating"; - console.log(`[commit] ${action} ${filePath} …`); - - await octokit.repos.createOrUpdateFileContents({ - owner, - repo, - path: filePath, - message: commitMessage, - content: encodedContent, - sha: existingSha, - committer: { - name: "github-actions[bot]", - email: "github-actions[bot]@users.noreply.github.com", - }, - author: { - name: "github-actions[bot]", - email: "github-actions[bot]@users.noreply.github.com", - }, - }); - - console.log(`[commit] ✓ Digest committed: ${filePath}`); -} diff --git a/.github/ai-docs-digest/scripts/generate_digest.ts b/.github/ai-docs-digest/scripts/generate_digest.ts deleted file mode 100644 index 7e62f6b6..00000000 --- a/.github/ai-docs-digest/scripts/generate_digest.ts +++ /dev/null @@ -1,245 +0,0 @@ -/** - * generate_digest.ts - * - * Phase 5: Call the OpenRouter LLM API with the built prompt, validate the - * returned markdown structure, and write the final digest file. - * Falls back to a heuristic-based digest if the LLM call fails. - */ - -import { readFileSync, writeFileSync, mkdirSync } from "fs"; -import { resolve, dirname } from "path"; -import type { BuiltPrompt } from "./build_prompt.js"; -import type { ChangeItem, PipelineState } from "./types.js"; - -const OPENROUTER_BASE_URL = - process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1"; - -const OPENROUTER_MODEL = - process.env.OPENROUTER_MODEL ?? "anthropic/claude-3.5-sonnet"; - -/** Required section headings that must be present in the digest. */ -const REQUIRED_HEADINGS = [ - "## Executive Summary", - "## New", - "## Changed", - "## Breaking Changes", - "## Fixes", - "## Docs Impact", - "## Internal / Excluded Notes", - "## Source References", -]; - -export async function generateDigest( - state: PipelineState, - prompt: BuiltPrompt -): Promise { - const apiKey = process.env.OPENROUTER_API_KEY; - - let markdown: string; - - if (!apiKey) { - console.warn( - "[generate] ⚠ OPENROUTER_API_KEY not set — using fallback digest." - ); - markdown = buildFallbackDigest(state); - } else { - try { - markdown = await callOpenRouter(apiKey, prompt, state); - } catch (err: unknown) { - const msg = err instanceof Error ? err.message : String(err); - console.error(`[generate] ✗ LLM call failed: ${msg}`); - console.warn("[generate] ⚠ Falling back to heuristic digest."); - markdown = buildFallbackDigest(state); - } - } - - // Validate structure. - const missing = REQUIRED_HEADINGS.filter((h) => !markdown.includes(h)); - if (missing.length > 0) { - console.warn( - `[generate] ⚠ Digest missing sections: ${missing.join(", ")} — repairing.` - ); - markdown = repairDigest(markdown, missing); - } - - // Persist. - mkdirSync(dirname(state.digestAbsPath), { recursive: true }); - writeFileSync(state.digestAbsPath, markdown); - console.log(`[generate] ✓ Digest written to ${state.digestAbsPath}`); - - return markdown; -} - -// ────────────────────────────────────────────────────────────────────────────── -// OpenRouter API call -// ────────────────────────────────────────────────────────────────────────────── - -async function callOpenRouter( - apiKey: string, - prompt: BuiltPrompt, - state: PipelineState -): Promise { - console.log( - `[generate] Calling OpenRouter model: ${OPENROUTER_MODEL} …` - ); - - const body = { - model: OPENROUTER_MODEL, - messages: [ - { role: "system", content: prompt.systemPrompt }, - { role: "user", content: prompt.userPrompt }, - ], - temperature: 0.2, - max_tokens: 4096, - }; - - const response = await fetch(`${OPENROUTER_BASE_URL}/chat/completions`, { - method: "POST", - headers: { - Authorization: `Bearer ${apiKey}`, - "Content-Type": "application/json", - "HTTP-Referer": "https://github.com/kloudmate/docs", - "X-Title": "KloudMate AI Docs Digest", - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - const text = await response.text(); - throw new Error(`OpenRouter API error ${response.status}: ${text}`); - } - - const json = (await response.json()) as { - choices?: Array<{ message?: { content?: string } }>; - error?: { message: string }; - }; - - if (json.error) { - throw new Error(`OpenRouter error: ${json.error.message}`); - } - - const content = json.choices?.[0]?.message?.content; - if (!content) { - throw new Error("OpenRouter returned an empty response"); - } - - // Save raw LLM response for debugging. - writeFileSync(state.llmResponseFile, content); - console.log(`[generate] ✓ Received ${content.length} chars from LLM.`); - - return content.trim(); -} - -// ────────────────────────────────────────────────────────────────────────────── -// Repair digest if headings are missing -// ────────────────────────────────────────────────────────────────────────────── - -function repairDigest(markdown: string, missingHeadings: string[]): string { - let repaired = markdown; - for (const heading of missingHeadings) { - repaired += `\n\n${heading}\n_None this week._\n`; - } - return repaired; -} - -// ────────────────────────────────────────────────────────────────────────────── -// Fallback heuristic digest (used when LLM is unavailable) -// ────────────────────────────────────────────────────────────────────────────── - -function buildFallbackDigest(state: PipelineState): string { - let items: ChangeItem[] = []; - try { - items = JSON.parse(readFileSync(state.classifiedFile, "utf8")); - } catch { - /* fallback with empty items */ - } - - const byCategory = groupBy(items, (i) => i.category ?? "internal"); - const breaking = byCategory.get("breaking") ?? []; - const newItems = byCategory.get("new") ?? []; - const fixes = byCategory.get("fix") ?? []; - const changed = byCategory.get("changed") ?? []; - const internal = byCategory.get("internal") ?? []; - - const docsImpactItems = items.filter( - (i) => i.docsArea && i.docsArea.length > 0 && i.visibility !== "internal" - ); - - const allCustomerFacing = [...breaking, ...newItems, ...fixes, ...changed]; - const refs = allCustomerFacing.map( - (i) => - `- ${i.repo} ${i.sourceType === "pr" ? `PR #${i.id}` : `commit ${i.id.slice(0, 8)}`}${i.evidence.url ? ` — ${i.evidence.url}` : ""}` - ); - - const lines: string[] = [ - `---`, - `title: Weekly Product Digest — ${state.weekLabel}`, - `summary: Customer-facing product and documentation-impact summary for the week.`, - `date: ${state.weekLabel}`, - `period_start: ${state.weekStart}`, - `period_end: ${state.weekEnd}`, - `type: engineering-digest`, - `generated_by: fallback-heuristic`, - `---`, - ``, - `# Weekly KloudMate Product Digest`, - `Period: ${state.weekStart} to ${state.weekEnd}`, - ``, - `> ⚠️ This digest was generated using heuristics because the LLM call was unavailable.`, - ``, - `## Executive Summary`, - `${allCustomerFacing.length} customer-relevant change(s) collected across ${new Set(allCustomerFacing.map((i) => i.repo)).size} repository/repositories. See sections below for details.`, - ``, - `## New`, - formatItems(newItems) || "_None this week._", - ``, - `## Changed`, - formatItems(changed) || "_None this week._", - ``, - `## Breaking Changes`, - formatItems(breaking) || "_None this week._", - ``, - `## Fixes`, - formatItems(fixes) || "_None this week._", - ``, - `## Docs Impact`, - docsImpactItems.length > 0 - ? docsImpactItems - .map( - (i) => - `- **${i.productArea ?? "Unknown"}**\n - Reason: ${i.title}\n - Likely docs area: ${(i.docsArea ?? []).join(", ")}\n - Confidence: Low (heuristic)\n - Evidence: ${i.repo} ${i.sourceType === "pr" ? `PR #${i.id}` : `commit ${i.id.slice(0, 8)}`}` - ) - .join("\n") - : "_None identified._", - ``, - `## Internal / Excluded Notes`, - `Excluded ${internal.length} internal change(s) from primary summary.`, - ``, - `## Source References`, - refs.length > 0 ? refs.join("\n") : "_No sources._", - ]; - - return lines.join("\n"); -} - -function formatItems(items: ChangeItem[]): string { - return items - .map( - (i) => - `- **[${i.repo}]** ${i.title}${i.evidence.url ? ` ([ref](${i.evidence.url}))` : ""}` - ) - .join("\n"); -} - -function groupBy( - items: T[], - keyFn: (item: T) => K -): Map { - const map = new Map(); - for (const item of items) { - const key = keyFn(item); - if (!map.has(key)) map.set(key, []); - map.get(key)!.push(item); - } - return map; -} diff --git a/.github/ai-docs-digest/scripts/main.ts b/.github/ai-docs-digest/scripts/main.ts deleted file mode 100644 index 93a55367..00000000 --- a/.github/ai-docs-digest/scripts/main.ts +++ /dev/null @@ -1,229 +0,0 @@ -/** - * main.ts - * - * Orchestrates the AI docs digest pipeline phases in sequence. - * - * Usage: - * npx tsx main.ts # Run all phases - * npx tsx main.ts collect # Collect changes only - * npx tsx main.ts generate # Generate digest only (assumes collect ran) - * npx tsx main.ts commit # Commit digest only - * npx tsx main.ts post # Post GitHub summary only - * npx tsx main.ts slack # Send Slack notification only - * - * Environment variables: - * GITHUB_TOKEN — required for collection, commit, and issue posting - * OPENROUTER_API_KEY — required for LLM generation (skips to fallback if absent) - * OPENROUTER_MODEL — optional model override (default: anthropic/claude-3.5-sonnet) - * OPENROUTER_BASE_URL — optional base URL override - * GITHUB_REPOSITORY — owner/repo (set automatically by GitHub Actions) - * WEEK_START — optional YYYY-MM-DD; defaults to previous Monday - * ARTIFACTS_DIR — directory for intermediate files (default: /tmp/digest-artifacts) - * SLACK_WEBHOOK_URL — Slack incoming webhook URL for digest notifications - */ - -import { mkdirSync, writeFileSync, readFileSync, existsSync } from "fs"; -import { resolve, dirname } from "path"; -import { fileURLToPath } from "url"; - -import { collectChanges } from "./collect_changes.js"; -import { normalizeChanges } from "./normalize_changes.js"; -import { classifyChanges } from "./classify_changes.js"; -import { buildPrompt } from "./build_prompt.js"; -import { generateDigest } from "./generate_digest.js"; -import { commitDigest } from "./commit_digest.js"; -import { postGithubSummary } from "./post_github_summary.js"; -import { sendSlackNotification } from "./send_slack_notification.js"; -import type { PipelineState } from "./types.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const REPO_ROOT = resolve(__dirname, "..", "..", ".."); - -// ────────────────────────────────────────────────────────────────────────────── -// Date helpers -// ────────────────────────────────────────────────────────────────────────────── - -/** Parse a YYYY-MM-DD string into a UTC Date at midnight. */ -function parseUTCDate(s: string): Date { - const [y, m, d] = s.split("-").map(Number); - return new Date(Date.UTC(y, m - 1, d)); -} - -/** Format a Date as YYYY-MM-DD in UTC. */ -function formatDate(d: Date): string { - return d.toISOString().slice(0, 10); -} - -/** - * Compute the most recent Monday (inclusive of today if today is Monday). - * Returns YYYY-MM-DD. - */ -function previousMonday(from: Date = new Date()): string { - const d = new Date(from); - // JavaScript: 0=Sun, 1=Mon, … 6=Sat - const dayOfWeek = d.getUTCDay(); - const daysBack = dayOfWeek === 0 ? 6 : dayOfWeek - 1; - d.setUTCDate(d.getUTCDate() - daysBack); - return formatDate(d); -} - -function addDays(dateStr: string, days: number): string { - const d = parseUTCDate(dateStr); - d.setUTCDate(d.getUTCDate() + days); - return formatDate(d); -} - -// ────────────────────────────────────────────────────────────────────────────── -// State setup -// ────────────────────────────────────────────────────────────────────────────── - -function buildState(): PipelineState { - const artifactsDir = - process.env.ARTIFACTS_DIR ?? "/tmp/digest-artifacts"; - mkdirSync(artifactsDir, { recursive: true }); - - // Determine the weekly window. - let weekStart: string; - if (process.env.WEEK_START) { - weekStart = process.env.WEEK_START; - } else { - // Default: previous Monday relative to now. - weekStart = previousMonday(); - } - const weekEnd = addDays(weekStart, 6); // Sunday - - // File paths for intermediate artifacts. - const year = weekStart.slice(0, 4); - const weekLabel = weekStart; // same as YYYY-MM-DD of Monday - - const digestFile = `content/engineering-digests/${year}/${weekLabel}-weekly-digest.md`; - const digestAbsPath = resolve(REPO_ROOT, digestFile); - - return { - weekStart, - weekEnd, - weekLabel, - org: "kloudmate", - rawPRsFile: resolve(artifactsDir, "raw-prs.json"), - rawCommitsFile: resolve(artifactsDir, "raw-commits.json"), - normalizedFile: resolve(artifactsDir, "normalized-changes.json"), - classifiedFile: resolve(artifactsDir, "classified-changes.json"), - promptFile: resolve(artifactsDir, "llm-prompt.txt"), - llmResponseFile: resolve(artifactsDir, "llm-response.md"), - digestFile, - digestAbsPath, - }; -} - -// ────────────────────────────────────────────────────────────────────────────── -// Phase runners -// ────────────────────────────────────────────────────────────────────────────── - -async function runCollect(state: PipelineState): Promise { - console.log("\n── Phase 1: Collect ──────────────────────────────────────"); - await collectChanges(state); - console.log("── Phase 2: Normalize ────────────────────────────────────"); - normalizeChanges(state); - console.log("── Phase 3: Classify ─────────────────────────────────────"); - classifyChanges(state); -} - -async function runGenerate(state: PipelineState): Promise { - console.log("\n── Phase 4: Build prompt ─────────────────────────────────"); - const prompt = buildPrompt(state); - console.log("── Phase 5: Generate digest ──────────────────────────────"); - return generateDigest(state, prompt); -} - -async function runCommit( - state: PipelineState, - digest: string -): Promise { - console.log("\n── Phase 6: Commit digest ────────────────────────────────"); - await commitDigest(state, digest); -} - -async function runPost( - state: PipelineState, - digest: string -): Promise { - console.log("\n── Phase 7: Post GitHub summary ──────────────────────────"); - await postGithubSummary(state, digest); -} - -async function runSlack( - state: PipelineState, - digest: string -): Promise { - console.log("\n── Phase 8: Send Slack notification ──────────────────────"); - await sendSlackNotification(state, digest); -} - -// ────────────────────────────────────────────────────────────────────────────── -// Entry point -// ────────────────────────────────────────────────────────────────────────────── - -async function main(): Promise { - const phase = process.argv[2] ?? "all"; - const state = buildState(); - - // Write state file so phases can be run independently. - const stateFile = resolve( - process.env.ARTIFACTS_DIR ?? "/tmp/digest-artifacts", - "pipeline-state.json" - ); - writeFileSync(stateFile, JSON.stringify(state, null, 2)); - - console.log(`\n🗓 Weekly window: ${state.weekStart} → ${state.weekEnd}`); - console.log(`📁 Artifacts dir: ${dirname(state.rawPRsFile)}`); - console.log(`📄 Digest path: ${state.digestFile}`); - - let digest = ""; - - try { - switch (phase) { - case "collect": - await runCollect(state); - break; - - case "generate": - digest = await runGenerate(state); - break; - - case "commit": - digest = readFileSync(state.digestAbsPath, "utf8"); - await runCommit(state, digest); - break; - - case "post": - digest = readFileSync(state.digestAbsPath, "utf8"); - await runPost(state, digest); - break; - - case "slack": - digest = readFileSync(state.digestAbsPath, "utf8"); - await runSlack(state, digest); - break; - - case "all": - default: - await runCollect(state); - digest = await runGenerate(state); - await runCommit(state, digest); - await runPost(state, digest); - await runSlack(state, digest); - break; - } - - console.log("\n✅ Pipeline complete.\n"); - } catch (err: unknown) { - const msg = err instanceof Error ? err.message : String(err); - console.error(`\n❌ Pipeline failed: ${msg}`); - if (err instanceof Error && err.stack) { - console.error(err.stack); - } - process.exit(1); - } -} - -main(); diff --git a/.github/ai-docs-digest/scripts/normalize_changes.ts b/.github/ai-docs-digest/scripts/normalize_changes.ts deleted file mode 100644 index c211070d..00000000 --- a/.github/ai-docs-digest/scripts/normalize_changes.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * normalize_changes.ts - * - * Phase 2: Normalize raw PRs and commits into the unified ChangeItem shape - * and deduplicate commits that are already represented by merged PRs. - */ - -import { readFileSync, writeFileSync } from "fs"; -import type { ChangeItem, RawCommit, RawPR, PipelineState } from "./types.js"; - -export function normalizeChanges(state: PipelineState): void { - const rawPRs: RawPR[] = JSON.parse(readFileSync(state.rawPRsFile, "utf8")); - const rawCommits: RawCommit[] = JSON.parse( - readFileSync(state.rawCommitsFile, "utf8") - ); - - const items: ChangeItem[] = []; - - // ── Normalize PRs ──────────────────────────────────────────────────────────── - for (const pr of rawPRs) { - if (!pr.merged_at) continue; - - items.push({ - repo: pr.repo, - sourceType: "pr", - id: String(pr.number), - title: pr.title, - description: pr.body ?? undefined, - author: pr.user?.login, - mergedOrCommittedAt: pr.merged_at, - labels: pr.labels.map((l) => l.name), - changedFiles: (pr.files ?? []).map((f) => f.filename), - additions: pr.additions, - deletions: pr.deletions, - comments: (pr.comments ?? []).map((c) => ({ - author: c.author, - body: c.body, - created_at: c.created_at, - })), - evidence: { - url: pr.html_url, - refs: [pr.html_url], - }, - }); - } - - // Build a set of all commit SHAs that are covered by PRs. - const prCommitSHAs = new Set(); - for (const pr of rawPRs) { - for (const c of pr.commits ?? []) { - prCommitSHAs.add(c.sha); - } - } - - // ── Normalize direct commits ───────────────────────────────────────────────── - for (const commit of rawCommits) { - // Skip commits already represented by a merged PR. - if (prCommitSHAs.has(commit.sha)) continue; - - const firstLine = commit.commit.message.split("\n")[0].trim(); - const fullMessage = commit.commit.message.trim(); - const additions = (commit.files ?? []).reduce( - (sum, f) => sum + f.additions, - 0 - ); - const deletions = (commit.files ?? []).reduce( - (sum, f) => sum + f.deletions, - 0 - ); - - items.push({ - repo: commit.repo, - sourceType: "commit", - id: commit.sha, - title: firstLine, - description: fullMessage !== firstLine ? fullMessage : undefined, - author: - commit.author?.login ?? commit.commit.author?.name, - mergedOrCommittedAt: - commit.commit.author?.date ?? new Date().toISOString(), - labels: [], - changedFiles: (commit.files ?? []).map((f) => f.filename), - additions, - deletions, - evidence: { - url: commit.html_url, - refs: [commit.html_url], - }, - }); - } - - // Sort by date descending (most recent first). - items.sort( - (a, b) => - new Date(b.mergedOrCommittedAt).getTime() - - new Date(a.mergedOrCommittedAt).getTime() - ); - - writeFileSync(state.normalizedFile, JSON.stringify(items, null, 2)); - console.log(`[normalize] ✓ ${items.length} change items written.`); -} diff --git a/.github/ai-docs-digest/scripts/package-lock.json b/.github/ai-docs-digest/scripts/package-lock.json deleted file mode 100644 index 02552ab2..00000000 --- a/.github/ai-docs-digest/scripts/package-lock.json +++ /dev/null @@ -1,863 +0,0 @@ -{ - "name": "ai-docs-digest-scripts", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ai-docs-digest-scripts", - "version": "1.0.0", - "dependencies": { - "@octokit/rest": "^21.0.2", - "js-yaml": "^4.1.0", - "minimatch": "^9.0.7" - }, - "devDependencies": { - "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.0", - "tsx": "^4.19.2", - "typescript": "^5.7.3" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", - "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.2.2", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", - "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", - "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", - "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz", - "integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/request": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", - "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", - "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", - "license": "MIT", - "dependencies": { - "@octokit/core": "^6.1.4", - "@octokit/plugin-paginate-rest": "^11.4.2", - "@octokit/plugin-request-log": "^5.3.1", - "@octokit/plugin-rest-endpoint-methods": "^13.3.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", - "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "license": "Apache-2.0" - }, - "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "license": "ISC" - } - } -} diff --git a/.github/ai-docs-digest/scripts/package.json b/.github/ai-docs-digest/scripts/package.json deleted file mode 100644 index 72d98bed..00000000 --- a/.github/ai-docs-digest/scripts/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "ai-docs-digest-scripts", - "version": "1.0.0", - "description": "Scripts for the KloudMate AI docs weekly digest pipeline", - "private": true, - "type": "module", - "scripts": { - "start": "tsx main.ts", - "collect": "tsx main.ts collect", - "generate": "tsx main.ts generate", - "commit": "tsx main.ts commit", - "post": "tsx main.ts post", - "test": "node --import tsx --test *.test.ts" - }, - "dependencies": { - "@octokit/rest": "^21.0.2", - "js-yaml": "^4.1.0", - "minimatch": "^9.0.7" - }, - "devDependencies": { - "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.0", - "tsx": "^4.19.2", - "typescript": "^5.7.3" - } -} diff --git a/.github/ai-docs-digest/scripts/post_github_summary.ts b/.github/ai-docs-digest/scripts/post_github_summary.ts deleted file mode 100644 index 8511f427..00000000 --- a/.github/ai-docs-digest/scripts/post_github_summary.ts +++ /dev/null @@ -1,184 +0,0 @@ -/** - * post_github_summary.ts - * - * Phase 7: Post a concise weekly summary as a comment to the dedicated - * "Weekly Product + Docs Digest" tracking issue in the docs repository. - * - * - Creates the issue if it doesn't exist. - * - Appends a comment each week. - */ - -import { Octokit } from "@octokit/rest"; -import type { ChangeItem, PipelineState } from "./types.js"; -import { readFileSync } from "fs"; - -const TRACKING_ISSUE_TITLE = "Weekly Product + Docs Digest"; -const TRACKING_ISSUE_LABEL = "weekly-digest"; - -export async function postGithubSummary( - state: PipelineState, - digestContent: string -): Promise { - const token = process.env.GITHUB_TOKEN; - if (!token) throw new Error("GITHUB_TOKEN environment variable is required"); - - const repoFull = process.env.GITHUB_REPOSITORY ?? ""; - if (!repoFull.includes("/")) { - throw new Error( - "GITHUB_REPOSITORY must be set in the form owner/repo (e.g. kloudmate/docs)" - ); - } - const [owner, repo] = repoFull.split("/"); - - const octokit = new Octokit({ auth: token }); - - // Load classified items for summary stats. - let items: ChangeItem[] = []; - try { - items = JSON.parse(readFileSync(state.classifiedFile, "utf8")); - } catch { - /* non-fatal — proceed without stats */ - } - - const issueNumber = await findOrCreateTrackingIssue( - octokit, - owner, - repo - ); - - const commentBody = buildCommentBody(state, items, digestContent); - - await octokit.issues.createComment({ - owner, - repo, - issue_number: issueNumber, - body: commentBody, - }); - - console.log( - `[post] ✓ Summary comment posted to issue #${issueNumber}` - ); -} - -// ────────────────────────────────────────────────────────────────────────────── -// Find or create the tracking issue -// ────────────────────────────────────────────────────────────────────────────── - -async function findOrCreateTrackingIssue( - octokit: Octokit, - owner: string, - repo: string -): Promise { - // Search open issues first. - const { data: open } = await octokit.issues.listForRepo({ - owner, - repo, - state: "open", - labels: TRACKING_ISSUE_LABEL, - per_page: 10, - }); - - const existing = open.find((i) => i.title === TRACKING_ISSUE_TITLE); - if (existing) { - console.log(`[post] Found tracking issue #${existing.number}`); - return existing.number; - } - - // Ensure the label exists. - await ensureLabel(octokit, owner, repo); - - // Create the issue. - const { data: created } = await octokit.issues.create({ - owner, - repo, - title: TRACKING_ISSUE_TITLE, - labels: [TRACKING_ISSUE_LABEL], - body: [ - "## Weekly Product + Docs Digest — Tracking Issue", - "", - "This issue collects weekly AI-generated product + documentation digests for the KloudMate team.", - "", - "Each week, a comment is automatically added below with a summary of customer-facing changes and docs impact.", - "", - "**Do not close this issue** — it is the running broadcast channel for the digest workflow.", - ].join("\n"), - }); - - console.log(`[post] Created tracking issue #${created.number}`); - return created.number; -} - -async function ensureLabel( - octokit: Octokit, - owner: string, - repo: string -): Promise { - try { - await octokit.issues.getLabel({ owner, repo, name: TRACKING_ISSUE_LABEL }); - } catch (err: unknown) { - const status = (err as { status?: number }).status; - if (status === 404) { - await octokit.issues.createLabel({ - owner, - repo, - name: TRACKING_ISSUE_LABEL, - color: "0075ca", - description: "Automated weekly product digest", - }); - } - } -} - -// ────────────────────────────────────────────────────────────────────────────── -// Build comment body -// ────────────────────────────────────────────────────────────────────────────── - -function buildCommentBody( - state: PipelineState, - items: ChangeItem[], - _digestContent: string -): string { - const breaking = items.filter((i) => i.category === "breaking" && i.visibility !== "internal"); - const newItems = items.filter((i) => i.category === "new" && i.visibility !== "internal"); - const fixes = items.filter((i) => i.category === "fix" && i.visibility !== "internal"); - const changed = items.filter((i) => i.category === "changed" && i.visibility !== "internal"); - const docsImpact = items.filter( - (i) => i.docsArea && i.docsArea.length > 0 && i.visibility !== "internal" - ); - - const lines: string[] = [ - `## Weekly Digest — ${state.weekLabel}`, - ``, - `**Period:** ${state.weekStart} → ${state.weekEnd}`, - ``, - ]; - - if (breaking.length > 0) { - lines.push(`- ⚠️ **${breaking.length} breaking change(s)** — on-prem or API users may be affected`); - } - if (newItems.length > 0) { - lines.push(`- 🆕 **${newItems.length} new customer-facing feature(s)/improvement(s)**`); - } - if (fixes.length > 0) { - lines.push(`- 🐛 **${fixes.length} fix(es)** worth noting`); - } - if (changed.length > 0) { - lines.push(`- 🔄 **${changed.length} behavioral/config change(s)**`); - } - if (docsImpact.length > 0) { - lines.push(`- 📝 **${docsImpact.length} doc area(s) likely need review**`); - } - - if ( - breaking.length === 0 && - newItems.length === 0 && - fixes.length === 0 && - changed.length === 0 - ) { - lines.push("- _No significant customer-facing changes this week._"); - } - - lines.push(``, `**Full digest:** \`${state.digestFile}\``); - - return lines.join("\n"); -} diff --git a/.github/ai-docs-digest/scripts/send_slack_notification.test.ts b/.github/ai-docs-digest/scripts/send_slack_notification.test.ts deleted file mode 100644 index 8f36411c..00000000 --- a/.github/ai-docs-digest/scripts/send_slack_notification.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import assert from "node:assert/strict"; -import test from "node:test"; - -import { - buildSlackPayload, - formatDigestForSlack, -} from "./send_slack_notification.js"; -import type { ChangeItem, PipelineState } from "./types.js"; - -const baseState: PipelineState = { - weekStart: "2026-05-18", - weekEnd: "2026-05-24", - weekLabel: "2026-05-18", - org: "kloudmate", - rawPRsFile: "/tmp/raw-prs.json", - rawCommitsFile: "/tmp/raw-commits.json", - normalizedFile: "/tmp/normalized.json", - classifiedFile: "/tmp/classified.json", - promptFile: "/tmp/prompt.txt", - llmResponseFile: "/tmp/llm-response.md", - digestFile: "content/engineering-digests/2026/2026-05-18-weekly-digest.md", - digestAbsPath: "/tmp/2026-05-18-weekly-digest.md", -}; - -test("formatDigestForSlack removes wrapper metadata and source references", () => { - const digest = `--- -title: Weekly Product Digest — 2026-05-18 -summary: Customer-facing product and documentation-impact summary. ---- - -# Weekly KloudMate Product Digest -Period: 2026-05-18 to 2026-05-24 - -## Executive Summary -Digest summary text. - -## Changed -- **Progress styling** – Updated the appearance of progress bars. - -## Source References -- https://github.com/kloudmate/kloudmate-frontend/commit/123 -`; - - const formatted = formatDigestForSlack(digest); - - assert.equal( - formatted, - [ - "*Executive Summary*", - "Digest summary text.", - "", - "*Changed*", - "- **Progress styling** – Updated the appearance of progress bars.", - ].join("\n") - ); -}); - -test("buildSlackPayload inlines digest blocks instead of a repo link", () => { - const items: ChangeItem[] = [ - { - repo: "kloudmate-frontend", - sourceType: "commit", - id: "abc123", - title: "Adjust progress styling", - mergedOrCommittedAt: "2026-05-19T00:00:00Z", - labels: [], - changedFiles: ["src/components/PercentageProgress/index.js"], - category: "changed", - visibility: "customer", - evidence: { - refs: ["https://github.com/kloudmate/kloudmate-frontend/commit/abc123"], - }, - }, - ]; - - const digest = `--- -title: Weekly Product Digest — 2026-05-18 -summary: Customer-facing product and documentation-impact summary. ---- - -# Weekly KloudMate Product Digest -Period: 2026-05-18 to 2026-05-24 - -## Executive Summary -Digest summary text. - -## Changed -- **Progress styling** – Updated the appearance of progress bars. -`; - - const payload = buildSlackPayload(baseState, items, digest); - - assert.match(payload.text, /Weekly Digest 2026-05-18/u); - assert.equal(payload.blocks[2]?.type, "divider"); - - const digestSection = payload.blocks[3]; - assert.equal(digestSection?.type, "section"); - assert.match( - String("text" in (digestSection ?? {}) ? digestSection.text.text : ""), - /\*Executive Summary\*/u - ); - - const serialized = JSON.stringify(payload); - assert.doesNotMatch(serialized, /View full digest/u); -}); diff --git a/.github/ai-docs-digest/scripts/send_slack_notification.ts b/.github/ai-docs-digest/scripts/send_slack_notification.ts deleted file mode 100644 index 998a420e..00000000 --- a/.github/ai-docs-digest/scripts/send_slack_notification.ts +++ /dev/null @@ -1,288 +0,0 @@ -/** - * send_slack_notification.ts - * - * Phase 8: Send the weekly digest summary and inline changelog text to a Slack - * channel via an incoming webhook. - * - * Requires: - * SLACK_WEBHOOK_URL — Slack incoming webhook URL (stored as a GitHub secret) - */ - -import { readFileSync } from "fs"; -import type { ChangeItem, PipelineState } from "./types.js"; - -const SLACK_SECTION_TEXT_LIMIT = 2_800; -const SLACK_MAX_BLOCKS = 50; -const SLACK_BASE_BLOCK_COUNT = 3; // header + summary + divider -const SLACK_MAX_DIGEST_CHARS = 20_000; - -interface SlackTextObject { - type: "plain_text" | "mrkdwn"; - text: string; - emoji?: boolean; -} - -type SlackBlock = - | { type: "header"; text: SlackTextObject & { type: "plain_text" } } - | { type: "section"; text: SlackTextObject & { type: "mrkdwn" } } - | { type: "divider" }; - -export interface SlackPayload { - text: string; - blocks: SlackBlock[]; -} - -export async function sendSlackNotification( - state: PipelineState, - digestContent: string -): Promise { - const webhookUrl = process.env.SLACK_WEBHOOK_URL; - if (!webhookUrl) { - console.warn("[slack] SLACK_WEBHOOK_URL is not set — skipping Slack notification."); - return; - } - - // Load classified items for summary stats. - let items: ChangeItem[] = []; - try { - items = JSON.parse(readFileSync(state.classifiedFile, "utf8")); - } catch { - /* non-fatal — proceed without stats */ - } - - const payload = buildSlackPayload(state, items, digestContent); - - const response = await fetch(webhookUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const body = await response.text(); - throw new Error(`Slack webhook returned ${response.status}: ${body}`); - } - - console.log("[slack] ✓ Notification sent to Slack."); -} - -// ────────────────────────────────────────────────────────────────────────────── -// Build Slack Block Kit payload -// ────────────────────────────────────────────────────────────────────────────── - -/** - * Build the Slack webhook payload for the weekly digest notification. - */ -export function buildSlackPayload( - state: PipelineState, - items: ChangeItem[], - digestContent: string -): SlackPayload { - const breaking = items.filter((i) => i.category === "breaking" && i.visibility !== "internal"); - const newItems = items.filter((i) => i.category === "new" && i.visibility !== "internal"); - const fixes = items.filter((i) => i.category === "fix" && i.visibility !== "internal"); - const changed = items.filter((i) => i.category === "changed" && i.visibility !== "internal"); - const docsImpact = items.filter( - (i) => i.docsArea && i.docsArea.length > 0 && i.visibility !== "internal" - ); - - const summaryLines: string[] = []; - if (breaking.length > 0) summaryLines.push(`⚠️ *${breaking.length} breaking change(s)*`); - if (newItems.length > 0) summaryLines.push(`🆕 *${newItems.length} new feature(s)/improvement(s)*`); - if (fixes.length > 0) summaryLines.push(`🐛 *${fixes.length} fix(es)*`); - if (changed.length > 0) summaryLines.push(`🔄 *${changed.length} behavioral/config change(s)*`); - if (docsImpact.length > 0) summaryLines.push(`📝 *${docsImpact.length} doc area(s) need review*`); - if (summaryLines.length === 0) summaryLines.push("_No significant customer-facing changes this week._"); - - const digestSections = buildDigestBlocks(digestContent); - const blocks: SlackBlock[] = [ - { - type: "header", - text: { - type: "plain_text", - text: `📋 Weekly Digest — ${state.weekLabel}`, - emoji: true, - }, - }, - { - type: "section", - text: { - type: "mrkdwn", - text: `*Period:* ${state.weekStart} → ${state.weekEnd}\n\n${summaryLines.join("\n")}`, - }, - }, - { type: "divider" }, - ...digestSections, - ]; - - return { - text: `Weekly Digest ${state.weekLabel}: ${summaryLines.join(" | ")}`, - blocks, - }; -} - -/** - * Convert digest markdown into Slack-compatible blocks while staying within - * Block Kit size limits. - */ -function buildDigestBlocks(digestContent: string): SlackBlock[] { - const digestText = capSlackDigestText(formatDigestForSlack(digestContent)); - const chunks = chunkSlackText(digestText, SLACK_SECTION_TEXT_LIMIT); - const maxDigestBlocks = SLACK_MAX_BLOCKS - SLACK_BASE_BLOCK_COUNT; - - return chunks.slice(0, maxDigestBlocks).map((chunk) => ({ - type: "section", - text: { - type: "mrkdwn", - text: chunk, - }, - })); -} - -/** - * Strip frontmatter and low-signal metadata so Slack receives the digest body - * rather than repo-centric wrapper content. - */ -export function formatDigestForSlack(digestContent: string): string { - const withoutFrontmatter = digestContent - .replace(/^---\n[\s\S]*?\n---\n*/u, "") - .trim(); - - const withoutTitle = withoutFrontmatter - .replace(/^# .+\n+/u, "") - .replace(/^Period: .+\n+/u, "") - .trim(); - - const withoutReferences = withoutTitle - .replace(/\n## Source References[\s\S]*$/u, "") - .trim(); - - if (!withoutReferences) { - return "_Digest content unavailable._"; - } - - return withoutReferences - .split("\n") - .map((line) => { - if (line.startsWith("## ")) { - return `*${line.slice(3).trim()}*`; - } - if (line.startsWith("### ")) { - return `*${line.slice(4).trim()}*`; - } - return line; - }) - .join("\n") - .trim(); -} - -function capSlackDigestText(text: string): string { - if (text.length <= SLACK_MAX_DIGEST_CHARS) { - return text; - } - - const truncatedNotice = - "\n\n_Trimmed for Slack length limits. The full digest remains in the weekly digest file._"; - const maxBodyLength = SLACK_MAX_DIGEST_CHARS - truncatedNotice.length; - const cutIndex = findSafeCutIndex(text, maxBodyLength); - - return `${text.slice(0, cutIndex).trimEnd()}${truncatedNotice}`; -} - -function chunkSlackText(text: string, maxChunkLength: number): string[] { - const paragraphs = text - .split(/\n{2,}/u) - .map((paragraph) => paragraph.trim()) - .filter(Boolean); - - const chunks: string[] = []; - let currentChunk = ""; - - for (const paragraph of paragraphs) { - if (paragraph.length > maxChunkLength) { - if (currentChunk) { - chunks.push(currentChunk); - currentChunk = ""; - } - chunks.push(...splitLongParagraph(paragraph, maxChunkLength)); - continue; - } - - const candidate = currentChunk ? `${currentChunk}\n\n${paragraph}` : paragraph; - if (candidate.length <= maxChunkLength) { - currentChunk = candidate; - continue; - } - - chunks.push(currentChunk); - currentChunk = paragraph; - } - - if (currentChunk) { - chunks.push(currentChunk); - } - - return chunks; -} - -function splitLongParagraph(paragraph: string, maxChunkLength: number): string[] { - const lines = paragraph.split("\n"); - const chunks: string[] = []; - let currentChunk = ""; - - for (const line of lines) { - if (line.length > maxChunkLength) { - if (currentChunk) { - chunks.push(currentChunk); - currentChunk = ""; - } - chunks.push(...splitLongLine(line, maxChunkLength)); - continue; - } - - const candidate = currentChunk ? `${currentChunk}\n${line}` : line; - if (candidate.length <= maxChunkLength) { - currentChunk = candidate; - continue; - } - - chunks.push(currentChunk); - currentChunk = line; - } - - if (currentChunk) { - chunks.push(currentChunk); - } - - return chunks; -} - -function splitLongLine(line: string, maxChunkLength: number): string[] { - const chunks: string[] = []; - let remaining = line.trim(); - - while (remaining.length > maxChunkLength) { - const cutIndex = findSafeCutIndex(remaining, maxChunkLength); - chunks.push(remaining.slice(0, cutIndex).trimEnd()); - remaining = remaining.slice(cutIndex).trimStart(); - } - - if (remaining) { - chunks.push(remaining); - } - - return chunks; -} - -function findSafeCutIndex(text: string, maxLength: number): number { - const preferredBreaks = ["\n\n", "\n", " "]; - - for (const separator of preferredBreaks) { - const index = text.lastIndexOf(separator, maxLength); - if (index > 0) { - return index; - } - } - - return Math.max(1, maxLength); -} diff --git a/.github/ai-docs-digest/scripts/tsconfig.json b/.github/ai-docs-digest/scripts/tsconfig.json deleted file mode 100644 index 62603f0d..00000000 --- a/.github/ai-docs-digest/scripts/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "lib": ["ES2022"], - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "outDir": "dist", - "rootDir": "." - }, - "include": ["*.ts"], - "exclude": ["node_modules", "dist"] -} diff --git a/.github/ai-docs-digest/scripts/types.ts b/.github/ai-docs-digest/scripts/types.ts deleted file mode 100644 index c3e172a8..00000000 --- a/.github/ai-docs-digest/scripts/types.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Shared type definitions for the AI docs digest pipeline. - */ - -/** A normalized, deduplicated change item from a PR or direct commit. */ -export interface ChangeItem { - /** Repository name (without org prefix). */ - repo: string; - /** Whether this item originated from a merged PR or a direct commit. */ - sourceType: "pr" | "commit"; - /** PR number (as string) or commit SHA. */ - id: string; - /** PR title or first line of the commit message. */ - title: string; - /** PR body or full commit message. */ - description?: string; - /** GitHub username of the author. */ - author?: string; - /** ISO-8601 timestamp of merge (PR) or commit date. */ - mergedOrCommittedAt: string; - /** Labels attached to the PR (empty for direct commits). */ - labels: string[]; - /** List of changed file paths. */ - changedFiles: string[]; - /** Lines added. */ - additions?: number; - /** Lines deleted. */ - deletions?: number; - /** Primary product area derived from file path mapping. */ - productArea?: string; - /** Relevant documentation page groups derived from file path mapping. */ - docsArea?: string[]; - /** Audience visibility heuristic. */ - visibility?: "customer" | "internal" | "mixed" | "unknown"; - /** Semantic category heuristic. */ - category?: "new" | "changed" | "breaking" | "fix" | "internal"; - /** PR discussion comments (issue-level, not inline review comments). */ - comments?: Array<{ author: string; body: string; created_at: string }>; - /** Evidence for docs impact reasoning. */ - evidence: { - url?: string; - refs: string[]; - }; -} - -/** A single PR comment (issue-level discussion comment). */ -export interface RawPRComment { - author: string; - body: string; - created_at: string; -} - -/** Raw PR data as returned by the GitHub API (subset). */ -export interface RawPR { - repo: string; - number: number; - title: string; - body: string | null; - state: string; - merged_at: string | null; - user: { login: string } | null; - labels: Array<{ name: string }>; - html_url: string; - additions: number; - deletions: number; - changed_files: number; - files?: Array<{ filename: string }>; - commits?: Array<{ sha: string }>; - comments?: RawPRComment[]; -} - -/** Raw commit data as returned by the GitHub API (subset). */ -export interface RawCommit { - repo: string; - sha: string; - commit: { - message: string; - author: { name: string; date: string } | null; - }; - author: { login: string } | null; - html_url: string; - files?: Array<{ filename: string; additions: number; deletions: number }>; - /** SHA of the merge commit that introduced this commit (if known). */ - prMergeSha?: string; -} - -/** Docs area mapping rule. */ -export interface DocsAreaMappingRule { - match: { - paths: string[]; - }; - docs_area: string; - confidence: "high" | "medium" | "low"; -} - -/** Aggregated state written to the artifacts directory between pipeline stages. */ -export interface PipelineState { - weekStart: string; // YYYY-MM-DD - weekEnd: string; // YYYY-MM-DD - weekLabel: string; // YYYY-MM-DD - org: string; - rawPRsFile: string; - rawCommitsFile: string; - normalizedFile: string; - classifiedFile: string; - promptFile: string; - llmResponseFile: string; - digestFile: string; // path inside repo - digestAbsPath: string; // absolute path on disk -} diff --git a/.github/workflows/ai-docs-weekly-digest.yml b/.github/workflows/ai-docs-weekly-digest.yml deleted file mode 100644 index 79b51d52..00000000 --- a/.github/workflows/ai-docs-weekly-digest.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: AI Docs Weekly Digest - -on: - # Run every Monday at 03:30 UTC, summarising the previous Mon–Sun window. - schedule: - - cron: '30 3 * * 1' - - # Allow manual runs with an optional week-start override. - workflow_dispatch: - inputs: - week_start: - description: > - Week start date in YYYY-MM-DD format (Monday). - Leave blank to use the most recent Monday. - required: false - type: string - -# Minimal required permissions — no extra access granted. -permissions: - contents: write # commit the digest markdown file - issues: write # create/comment on the tracking issue - -jobs: - generate-digest: - name: Generate Weekly Digest - runs-on: ubuntu-latest - - env: - GITHUB_REPOSITORY: ${{ github.repository }} - WEEK_START: ${{ inputs.week_start || '' }} - ARTIFACTS_DIR: /tmp/digest-artifacts - OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} - OPENROUTER_MODEL: ${{ vars.OPENROUTER_MODEL || 'anthropic/claude-3.5-sonnet' }} - - steps: - # ── 1. Checkout ─────────────────────────────────────────────────────────── - - name: Checkout docs repo - uses: actions/checkout@v4 - with: - # Provide the token so the Octokit commit step can push to this repo. - token: ${{ secrets.GITHUB_TOKEN }} - - # ── 2. Node.js setup ────────────────────────────────────────────────────── - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: .github/ai-docs-digest/scripts/package-lock.json - - # ── 3. Install dependencies ─────────────────────────────────────────────── - - name: Install script dependencies - working-directory: .github/ai-docs-digest/scripts - run: npm ci - - # ── 4. Collect changes ──────────────────────────────────────────────────── - - name: Collect GitHub changes - working-directory: .github/ai-docs-digest/scripts - env: - # Use a PAT with org-wide read access so private repositories can be - # queried. Fall back to GITHUB_TOKEN for forks / public-only setups. - GITHUB_TOKEN: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} - run: npx tsx main.ts collect - - # ── 5. Generate AI digest ───────────────────────────────────────────────── - - name: Generate AI digest - working-directory: .github/ai-docs-digest/scripts - run: npx tsx main.ts generate - - # ── 6. Commit digest to repo ────────────────────────────────────────────── - - name: Commit digest - working-directory: .github/ai-docs-digest/scripts - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npx tsx main.ts commit - - # ── 7. Post summary to tracking issue ───────────────────────────────────── - - name: Post GitHub summary - working-directory: .github/ai-docs-digest/scripts - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npx tsx main.ts post - - # ── 8. Send Slack notification ──────────────────────────────────────────── - - name: Send Slack notification - if: success() - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - working-directory: .github/ai-docs-digest/scripts - run: npx tsx main.ts slack - - # ── 9. Upload debug artifacts ────────────────────────────────────────────── - - name: Upload debug artifacts - uses: actions/upload-artifact@v4 - if: always() - with: - name: digest-artifacts-${{ github.run_id }} - path: /tmp/digest-artifacts/ - retention-days: 30 diff --git a/astro.config.mjs b/astro.config.mjs index 46ce9f66..76e011be 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -72,6 +72,29 @@ export default defineConfig({ // { icon: 'github', label: 'GitHub', href: 'https://github.com/kloudmate' }, // ], customCss: ['./src/styles/brand.css'], + // KloudMate RUM. `init` runs from `onload` so the bundle is fully executed + // before it is called; the rolling /v2/ path picks up SDK patches without an + // edit here. Session recording is off, so this collects page performance and + // errors only. + head: [ + { + tag: 'script', + attrs: { + async: true, + crossorigin: 'anonymous', + src: 'https://cdn.kloudmate.com/rum/js/v2/km-rum.umd.min.js', + onload: `KloudMateRum.init({ + endpoint: 'https://otel.kloudmate.dev:4318', + rumAccessToken: 'pk_ef2d947bd7093beb2f120d79258871de', + applicationName: 'KloudMate Docs', + deploymentEnvironment: 'prod', + version: '', + sampleRate: 1.0, + sessionRecorder: { enabled: false }, + })`, + }, + }, + ], components: { Header: './src/components/Header.astro', // Wraps starlight-llm-actions' Page Actions dropdown in diff --git a/content/engineering-digests/2026/2026-04-13-weekly-digest.md b/content/engineering-digests/2026/2026-04-13-weekly-digest.md deleted file mode 100644 index 34018d48..00000000 --- a/content/engineering-digests/2026/2026-04-13-weekly-digest.md +++ /dev/null @@ -1,226 +0,0 @@ -We need to produce the weekly digest based on the change data. Steps: - -- Identify customer-facing changes: visibility customer (or unknown? we treat unknown as uncertain; we can include if likely customer but we should be cautious. The instruction: prioritize customer-facing changes; de-emphasize or omit purely internal changes. Use visibility as strong signal but apply judgment. For unknown visibility, we need to decide based on context. For PR 111 (rpm signing fix) visibility unknown; changed files: .github/workflows/release.yml, build/linux/rpm/kmagent.spec. This seems internal (release workflow) but could affect customer if they install RPM? Might be internal packaging. Likely internal. We'll treat as internal/excluded. - -- For each change, categorize into New, Changed, Breaking, Fix. - -We need to list bullet items for each section. - -We need to produce Docs Impact section: list likely documentation impact items with reason, likely docs area, confidence, evidence. - -We need to produce Internal/Excluded Notes: brief summary of what was excluded and why. - -We need Source References: list all PRs and commits cited in the digest. - -We must not add extra sections. - -We need frontmatter with title, summary, date, period_start, period_end, type. - -Title: Weekly Product Digest — 2026-04-13 (as per format). summary: Customer-facing product and documentation-impact summary for the week of 2026-04-13. - -date: 2026-04-13 (maybe the start date). period_start and period_end as given. - -type: engineering-digest. - -Now we need to go through each change item and decide if it's customer-facing and which category. - -Let's list all items with repo, id, title, visibility, category, changed files, description. - -We'll create a table mentally. - -I'll go through each JSON entry. - -1. PR 113 km-agent: fix gpg signing non interactive. visibility customer, category fix. Changed files: .github/workflows/release.yml, scripts/sign_rpm.exp. This is about GPG signing for release; likely internal packaging but visibility customer marked. Could affect customers who verify signatures? It's a fix for GPG signing non-interactive; maybe affects automated release process. Might be internal but we trust visibility customer. We'll include in Fixes. - -2. PR 857 kloudmate-frontend: Use COUNT_VALUES for logs panel. visibility customer, category changed. Changed file: src/modules/logs/components/Histogram/index.js. This changes the logs panel query to use COUNT_VALUES. Likely a UI/UX change affecting how logs panel displays counts. So Changed. - -3. PR 112 km-agent: fix github workflows. visibility customer, category fix. Changed files: .github/workflows/publish-config-updater.yml, .github/workflows/release.yml. Internal workflows but visibility customer. Might affect release process; but we'll include in Fixes. - -4. PR 111 km-agent: rpm signing fix. visibility unknown, category changed. Changed files: .github/workflows/release.yml, build/linux/rpm/kmagent.spec. This is about RPM signing; likely internal packaging. Since visibility unknown, we need to decide. I'd treat as internal/excluded because it's about packaging; not directly user-facing unless they install RPM. But could affect customers who install via RPM. However, we have no evidence of user impact. I'd exclude from customer-facing sections, put in Internal/Excluded Notes. - -5. Commit 197f14c9... kloudmate-backend: add agentScope. visibility customer, category new. Changed files: src/functions/ai-assistant/controllers/integrations.ts, src/functions/ai-assistant/handlers/integrations.ts, src/functions/ai-assistant/validator.ts. This adds agentScope to AI assistant integrations. Likely a new feature for AI assistant. So New. - -6. Commit c9471bc4... kloudmate-otel-backend: Implement grace period tracking with system validation and marker file creation. visibility customer, category changed. Changed files: licensing manager_base.go, manager_base_test.go, security_test.go. This is about licensing grace period tracking. Likely affects licensing behavior for OTEL backend. Could be customer-facing (license enforcement). So Changed. - -7. PR 109 km-agent: Deps update. visibility customer, category changed. Changed files: go.mod, go.sum, updater.Dockerfile. Dependency updates; could affect agent behavior but likely internal. However visibility customer. Might be internal but we'll include in Changed? It's a deps update; may affect performance/security but not user-visible. Might be considered internal. But we have to follow visibility. I'd treat as Changed (internal but visibility customer). However we could note it's internal and exclude. The instruction: de-emphasize or omit purely internal changes: refactors, CI/CD only changes, internal tooling, lock-file bumps, test additions with no user impact. This is a deps update (lock-file bumps) - could be considered internal. So we may exclude. But visibility customer flag may be inaccurate. We'll apply judgment: exclude from customer-facing sections, put in Internal/Excluded Notes. - -8. PR 855 kloudmate-frontend: fixed dataset in homepage panel. visibility customer, category fix. Changed file: src/modules/dashboard/utils.js. Fixes dataset in homepage panel. Likely a fix for dashboard panel data. So Fixes. - -9. PR 853 kloudmate-frontend: fixed widget aggregation configs. visibility customer, category fix. Changed file: src/modules/dashboard/utils.js. Fixes widget aggregation configs. So Fixes. - -10. Commit dfdbd11af... kloudmate-frontend: fix load db. visibility customer, category fix. Changed file: src/modules/dashboard/Home.js. Fixes loading DB for dashboard. So Fixes. - -11. PR 108 km-agent: go deps fix. visibility customer, category changed. Changed files: .github/workflows/release.yml, Dockerfile, kube.Dockerfile, updater.Dockerfile. Also has docsArea: On-Prem / Installation (provided). This is about Go deps fix; likely affects building/running agent. Could impact on-prem installation. So Changed. - -12. Commit 3b87f1af... kloudmate-otel-backend: fix agg query for ANY and LAST. visibility customer, category fix. Changed file: packages/api-server/src/validators/aggregate.ts. Fixes aggregate query for ANY and LAST. So Fixes. - -13. Commit 5510ee1d... kloudmate-backend: update package. visibility customer, category changed. Changed files: src/services/notifications/templates/investigations/completed-slack.test.ts, yarn.lock. Update package (likely yarn.lock). This is internal (test file and lock). Likely internal. We'll exclude. - -14. PR 107 km-agent: fix build: CI runner to use go v1.25.8. visibility customer, category fix. Changed files: .github/workflows/release.yml, go.mod. CI runner Go version. Internal CI but visibility customer. Likely internal; but we may include in Fixes? It's a CI fix; may affect build but not end-user. I'd exclude as internal. - -15. PR 106 km-agent: fix: rpm sign deps. visibility customer, category fix. Changed file: .github/workflows/release.yml. Internal. Exclude. - -16. Commit 10e4582f... kloudmate-backend: update docker. visibility customer, category changed. Changed file: Dockerfile. docsArea: On-Prem / Installation. This is Dockerfile update; affects on-prem installation. So Changed. - -17. Commit 9f10c472... kloudmate-otel-backend: support for any node to be used for eval condition. visibility customer, category new. Changed files: alarm condition mock, eval.spec.ts, eval.ts. This adds support for any node to be used for eval condition in alarms. Likely a new feature for alarm evaluation. So New. - -18. Commit 512fd724... kloudmate-backend: update docker. visibility customer, category changed. Changed file: Dockerfile. docsArea: On-Prem / Installation. Another Dockerfile update. Changed. - -19. Commit d936fc75... kloudmate-otel-backend: Implement cursor pagination for trace summaries and enhance query handling. visibility customer, category changed. Changed files: openapi.yaml, alarms buildExpressionRequests.spec.ts, traces index.test.ts, traces index.ts, traces utils.ts, validators/traces.ts. This adds cursor pagination for trace summaries. So Changed. - -20. PR 105 km-agent: fix: rpm signing. visibility customer, category fix. Changed files: .github/workflows/release.yml, deployment/helm/km-kube-agent/Chart.yaml, scripts/install_linux.sh, scripts/sign_rpm.exp. This is RPM signing fix; affects packaging. Could affect customers installing via RPM/helm. Might be customer-facing. We'll include in Fixes. - -21. Commit 0fa2d918... kloudmate-otel-backend: Add trace and group locator tables with associated query optimizations. visibility customer, category new. Many changed files: constants, services logs/traces, utils, locator, graphql, migrations. This adds new tables and optimizations. Likely a new feature (locator tables). So New. - -22? Actually it's a new feature. So New. - -22. Commit e5fdd... but we have. - -22. Commit e5fdd50ac... kloudmate-frontend: fix dashboard create causing conflict. visibility customer, category fix. So Fixes. - -Now we need to produce sections. - -We need to produce bullet lists. - -Let's compile. - -New: items: new features. - -From list: commit 197f14c94? Actually commit 197f14c9f14c9f197f14c9f197f14c947f14c9f197f197f14c9f197f14c9f197f14c9f14c9f14c9f6935de3b34bb4537efed7e1deeb159c. - -Ok. - -Now commit197f14c9f197f14c9f14c9471bc410f14c9f14c9f10c9471bc410? Actually commit c9471bc410d93b87f1af71be709471af71be709e5510ee1d864e7efc9471bc410d5e125da0fb9cbe58e619259ae8077. - -Now commit10fa2d918d6e2d9c56f16e1a86e512f10e4d724b4ed6242d063c1f1fc6d0876cdd4cd3e: update docker. - -Ok. - -Now we need to list New items: - -- commit 197f14c9... (add agentScope) - New (AI assistant integrations) -- commit 9f10c472... (support for any node to be used for eval condition) - New (alarm eval) -- commit 0fa2d918... (Add trace and group locator tables with associated query optimizations) - New (locator tables) -Potential also: any other new? PR 857 changed, not new. PR 855 fix. PR 853 fix. commit dfdbd11 fix. PR 108 changed. commit 10e4582f changed. commit 512fd724 changed. commit d936fc75 changed. commit 3b87f1af fix. commit 5510ee1d changed (internal). PR 107 fix (internal). PR 106 fix (internal). PR 105 fix (rpm signing) maybe fix. PR 113 fix. PR 112 fix. PR 111 unknown (internal). PR 109 deps update (internal). PR 108 changed (go deps fix) maybe internal but we have docsArea On-Prem/Installation; we could include as Changed. - -Thus New items: three. - -Now Changed items: we need to list those that are customer-facing changed (not new, not fix, not breaking). Let's identify: - -- PR 857: Use COUNT_VALUES for logs panel (changed) -- commit c9471bc4... (grace period tracking) changed -- PR 108? Actually commit c9471bc410d5e125da0fb9cbe58e61259ae8077: Implement grace period tracking with system validation and marker file creation) changed. - -- PR 108? Actually commit c9471bc410d5e125da0fb9cbe58e619259ae8077: Implement grace period tracking with system validation and marker file creation (changed) -- PR 108: go deps fix (changed) (docsArea On-Prem/Installation) -- commit 10e4582f... (update docker) changed (docsArea On-Prem/Installation) -- commit 512fd724... (update docker) changed (docsArea On-Prem/Installation) -- commit d936fc75... (Implement cursor pagination for trace summaries and enhance query handling) changed -- commit 5510ee1d... (update package) changed but internal (test + lock) maybe exclude. -- PR 109 deps update (internal) exclude. -- PR 107 fix (internal) exclude. -- PR 106 fix (internal) exclude. -- PR 111 unknown internal exclude. -- commit 0c1422e3... (no reasoning) changed (AI assistant chat handler) maybe internal? It's a change to chat.ts (one line). Might be internal but visibility customer. Could be a minor change; we could include as Changed but maybe internal. Let's see: changed file src/functions/ai-assistant/handlers/chat.ts, one addition one deletion, title "no reasoning". Likely internal tweak. We'll exclude as internal. - -- commit 3b87f1af fix (not changed) -- commit 9f10c472 new (not changed) -- commit 0fa2d918 new (not changed) -- commit dfdbd11 fix -- PR 855 fix -- PR 853 fix -- PR 105 fix -- PR 113 fix -- PR 112 fix -- PR 111 unknown internal exclude -- commit 5510ee1d changed internal exclude. - -Thus Changed items list: - -- PR 857 (logs panel COUNT_VALUES) -- commit c9471bc4 (grace period tracking) -- PR 108 (go deps fix) (docsArea On-Prem/Installation) -- commit 10e4582f (update docker) (docsArea On-Prem/Installation) -- commit 512fd724 (update docker) (docsArea On-Prem/Installation) -- commit d936fc75 (cursor pagination for trace summaries) - -Potentially also commit 0c1422e3? We'll exclude. - -Now Fixes: list customer-facing fixes. - -- PR 113 (fix gpg signing non interactive) -- PR 112 (fix github workflows) -- PR 855 (fixed dataset in homepage panel) -- PR 853 (fixed widget aggregation configs) -- commit dfdbd11 (fix load db) -- commit 3b87f1af (fix agg query for ANY and LAST) -- PR 105 (fix: rpm signing) -- Possibly PR 107? internal exclude. -- PR 106? internal exclude. -- commit 0c1422e3? internal exclude. -- commit 5510ee1d? internal exclude. -- PR 109? internal exclude. -- PR 108? changed not fix. -- commit 10e4582f? changed. -- commit 512fd724? changed. -- commit d936fc75? changed. -- commit c9471bc4? changed. -- commit 9f10c472? new. -- commit 0fa2d918? new. -- commit 197f14c9? new. -- commit ??? Actually also commit 0c1422e3? internal. - -Also PR 111 unknown internal exclude. - -Thus Fixes list: - -- PR 113 (km-agent) fix gpg signing non interactive -- PR 112 (km-agent) fix github workflows -- PR 855 (kloudmate-frontend) fixed dataset in homepage panel -- PR 853 (kloudmate-frontend) fixed widget aggregation configs -- commit dfdbd11af9848ba2555a52e84dfcecac712b4197 (kloudmate-frontend) fix load db -- commit 3b87f1af71be709e551c65bdcea2479f7eb934ed (kloudmate-otel-backend) fix agg query for ANY and LAST -- PR 105 (km-agent) fix: rpm signing - -Potentially also commit 0fa2d918? that's new. - -Now Breaking Changes: none identified. We'll output _None this week._ - -Now Docs Impact: we need to reason about which documentation pages likely affected, citing evidence, assign confidence. - -We need to look at changes that likely affect docs: any changes that modify user-facing behavior, configuration, installation, usage. - -From the data: - -- PR 108: go deps fix, docsArea provided: On-Prem / Installation. Changed files: .github/workflows - -## Executive Summary -_None this week._ - - -## New -_None this week._ - - -## Changed -_None this week._ - - -## Breaking Changes -_None this week._ - - -## Fixes -_None this week._ - - -## Docs Impact -_None this week._ - - -## Internal / Excluded Notes -_None this week._ - - -## Source References -_None this week._ diff --git a/content/engineering-digests/2026/2026-04-20-weekly-digest.md b/content/engineering-digests/2026/2026-04-20-weekly-digest.md deleted file mode 100644 index 67c88651..00000000 --- a/content/engineering-digests/2026/2026-04-20-weekly-digest.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Weekly Product Digest — 2026-04-20 -summary: Customer-facing product and documentation-impact summary for the week of 2026-04-20. -date: 2026-04-20 -period_start: 2026-04-20 -period_end: 2026-04-26 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-04-20 to 2026-04-26 - -## Executive Summary -No customer-facing changes were recorded across the monitored repositories during this week. All activity was internal or non-user-impacting. - -## New -_None this week._ - -## Changed -_None this week._ - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -_None identified._ - -## Internal / Excluded Notes -_None._ - -## Source References -_None._ \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-04-27-weekly-digest.md b/content/engineering-digests/2026/2026-04-27-weekly-digest.md deleted file mode 100644 index 9f499530..00000000 --- a/content/engineering-digests/2026/2026-04-27-weekly-digest.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Weekly Product Digest — 2026-04-27 -summary: Customer-facing product and documentation-impact summary for the week of 2026-04-27. -date: 2026-04-27 -period_start: 2026-04-27 -period_end: 2026-05-03 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-04-27 to 2026-05-03 - -## Executive Summary -No customer-facing changes were made during this week. All activity was internal or non-user-impacting. - -## New -_None this week._ - -## Changed -_None this week._ - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -_None identified._ - -## Internal / Excluded Notes -_None._ - -## Source References -_None._ \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-05-04-weekly-digest.md b/content/engineering-digests/2026/2026-05-04-weekly-digest.md deleted file mode 100644 index 630bc618..00000000 --- a/content/engineering-digests/2026/2026-05-04-weekly-digest.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Weekly Product Digest — 2026-05-04 -summary: Customer-facing product and documentation-impact summary for the week of 2026-05-04. -date: 2026-05-04 -period_start: 2026-05-04 -period_end: 2026-05-10 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-05-04 to 2026-05-10 - -## Executive Summary -No customer-facing changes were made during this week. All activity was internal or non-impacting. - -## New -_None this week._ - -## Changed -_None this week._ - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -_None identified._ - -## Internal / Excluded Notes -_None._ - -## Source References -_None._ \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-05-11-weekly-digest.md b/content/engineering-digests/2026/2026-05-11-weekly-digest.md deleted file mode 100644 index e98608c4..00000000 --- a/content/engineering-digests/2026/2026-05-11-weekly-digest.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Weekly Product Digest — 2026-05-11 -summary: Customer-facing product and documentation-impact summary for the week of 2026-05-11. -date: 2026-05-11 -period_start: 2026-05-11 -period_end: 2026-05-17 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-05-11 to 2026-05-17 - -## Executive Summary -No customer-facing changes were identified across the monitored repositories during this period. All activity was internal or non-user-impacting. - -## New -_None this week._ - -## Changed -_None this week._ - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -_None identified._ - -## Internal / Excluded Notes -_None._ - -## Source References -_None._ \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-05-18-weekly-digest.md b/content/engineering-digests/2026/2026-05-18-weekly-digest.md deleted file mode 100644 index 797919b8..00000000 --- a/content/engineering-digests/2026/2026-05-18-weekly-digest.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Weekly Product Digest — 2026-05-18 -summary: Customer-facing product and documentation-impact summary for the week of 2026-05-18. -date: 2026-05-18 -period_start: 2026-05-18 -period_end: 2026-05-24 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-05-18 to 2026-05-24 - -## Executive Summary -This week's updates focused on refining the user interface: progress indicator styling was adjusted across several modules, and various style fixes were applied to sidebar menus, buttons, and the theme palette. No new features or breaking changes were introduced. - -## New -_None this week._ - -## Changed -- **Progress styling** – Updated the appearance of progress bars and related table configurations in Hosts, Kubernetes, Lambda, Logs, and Showcase modules. - -## Breaking Changes -_None this week._ - -## Fixes -- **Sidebar and button styles** – Fixed styling issues in the Assistant, Incidents, and Synthetics sidebar submenus, as well as in the Button component and theme palette. - -## Docs Impact -- **Hosts, Kubernetes, Lambda, Logs, Showcase UI** - - Reason: Progress bar styling changes may affect how users perceive metric visualizations and tables. - - Likely docs area: Module-specific UI guides (Hosts, Kubernetes, Lambda, Logs, Showcase) - - Confidence: Medium - - Evidence: kloudmate-frontend commit 4c69500675a90f333de4d499340c2857eb7484d0, `src/components/PercentageProgress/index.js`, `src/modules/hosts/utils/tableConfig.js`, `src/modules/kubernetes/utils/tableConfig.js`, `src/modules/lambda/Lambda.js`, `src/modules/lambda/components/FunctionMetrics.js`, `src/modules/logs/components/InvocationsTable.js`, `src/modules/showcase/sections/CustomComponentsShowcase.js` - -- **Sidebar navigation, Button component, Theme** - - Reason: Style fixes to sidebar submenus, buttons, and palette could alter the visual presentation documented in UI/theme guides. - - Likely docs area: UI Components, Theme & Styling, Sidebar Navigation - - Confidence: Medium - - Evidence: kloudmate-frontend commit d830a05ef39b2419b0c4506e8ed158034144edbc, `src/components/Sidebar/AssistantSubMenu.js`, `src/components/Sidebar/IncidentsSubMenu.js`, `src/components/Sidebar/SyntheticsSubMenu.js`, `src/theme/components/button.js`, `src/theme/palette.js` - -## Internal / Excluded Notes -_None._ All changes in the dataset were marked as customer‑visible; no internal‑only changes (refactors, CI, test-only, lock‑file bumps) were present this week. - -## Source References -- https://github.com/kloudmate/kloudmate-frontend/commit/4c69500675a90f333de4d499340c2857eb7484d0 -- https://github.com/kloudmate/kloudmate-frontend/commit/d830a05ef39b2419b0c4506e8ed158034144edbc \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-05-25-weekly-digest.md b/content/engineering-digests/2026/2026-05-25-weekly-digest.md deleted file mode 100644 index 811df375..00000000 --- a/content/engineering-digests/2026/2026-05-25-weekly-digest.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Weekly Product Digest — 2026-05-25 -summary: Customer-facing product and documentation-impact summary for the week of 2026-05-25. -date: 2026-05-25 -period_start: 2026-05-25 -period_end: 2026-05-31 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-05-25 to 2026-05-31 - -## Executive Summary -No customer-facing changes were recorded during this week. All activity was internal or had no user impact. - -## New -_None this week._ - -## Changed -_None this week._ - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -_None identified._ - -## Internal / Excluded Notes -_None._ - -## Source References -_None._ \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-06-01-weekly-digest.md b/content/engineering-digests/2026/2026-06-01-weekly-digest.md deleted file mode 100644 index dc389749..00000000 --- a/content/engineering-digests/2026/2026-06-01-weekly-digest.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Weekly Product Digest — 2026-06-01 -summary: Customer-facing product and documentation-impact summary for the week. -date: 2026-06-01 -period_start: 2026-06-01 -period_end: 2026-06-07 -type: engineering-digest -generated_by: fallback-heuristic ---- - -# Weekly KloudMate Product Digest -Period: 2026-06-01 to 2026-06-07 - -> ⚠️ This digest was generated using heuristics because the LLM call was unavailable. - -## Executive Summary -0 customer-relevant change(s) collected across 0 repository/repositories. See sections below for details. - -## New -_None this week._ - -## Changed -_None this week._ - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -_None identified._ - -## Internal / Excluded Notes -Excluded 0 internal change(s) from primary summary. - -## Source References -_No sources._ \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-06-08-weekly-digest.md b/content/engineering-digests/2026/2026-06-08-weekly-digest.md deleted file mode 100644 index 4c3027c8..00000000 --- a/content/engineering-digests/2026/2026-06-08-weekly-digest.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Weekly Product Digest — 2026-06-08 -summary: Customer-facing product and documentation-impact summary for the week of 2026-06-08. -date: 2026-06-08 -period_start: 2026-06-08 -period_end: 2026-06-14 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-06-08 to 2026-06-14 - -## Executive Summary -This week included a single customer-facing fix addressing an incorrect SELECT table generation in the ClickHouse datasource. No new features, breaking changes, or other modifications were released. The fix improves query reliability for users leveraging ClickHouse as a data source. - -## New -_None this week._ - -## Changed -_None this week._ - -## Breaking Changes -_None this week._ - -## Fixes -- Fixed an issue where the ClickHouse datasource generated incorrect SELECT table statements, leading to query failures. - -## Docs Impact -- **ClickHouse Datasource** - - Reason: Fix for incorrect SELECT table generation may affect documentation examples or troubleshooting guides. - - Likely docs area: Datasources > ClickHouse configuration and query usage - - Confidence: Medium - - Evidence: kloudmate-backend commit 3f993247259c57aec7d0f8e94c612ca1e98caeb1, `src/datasources/kloudmate/utils/clickhouse.ts` - -## Internal / Excluded Notes -_None._ - -## Source References -- https://github.com/kloudmate/kloudmate-backend/commit/3f993247259c57aec7d0f8e94c612ca1e98caeb1 \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-06-15-weekly-digest.md b/content/engineering-digests/2026/2026-06-15-weekly-digest.md deleted file mode 100644 index 4a866b95..00000000 --- a/content/engineering-digests/2026/2026-06-15-weekly-digest.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Weekly Product Digest — 2026-06-15 -summary: Customer-facing product and documentation-impact summary for the week of 2026-06-15. -date: 2026-06-15 -period_start: 2026-06-15 -period_end: 2026-06-21 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-06-15 to 2026-06-21 - -## Executive Summary -This week focused on improving alert group functionality: backend aggregation of instance counts, UI fixes for display and navigation, and bug fixes to alert grouping logic and root cause analysis. No breaking changes were introduced. - -## New -_None this week._ - -## Changed -- **Alert Groups**: Added instance count aggregation for alarm groups in the backend. -- **Alert Groups**: Fixed alert group instance count display in the UI. -- **Alert Groups UI**: Applied UI fixes to alert group detail pages (tabs, panels, sidebar). - -## Breaking Changes -_None this week._ - -## Fixes -- **Alert Grouping**: Fixed RCA bug that caused incorrect root cause analysis in alert groups. -- **Alert Grouping**: Fixed alert grouping bug affecting notification and state handling. - -## Docs Impact -- **Alarm Groups** - - Reason: Backend addition of instance count aggregation and frontend fix for instance count display may require updates to documentation describing alarm group metrics and UI. - - Likely docs area: Alerting > Alarm Groups documentation (e.g., "Alarm Groups Overview", "Viewing Alarm Group Details") - - Confidence: High - - Evidence: kloudmate-backend commit fd0ef86f053c72ec48ed4745e8335dea65a5df17, kloudmate-frontend commit c6ad1ba703667ae190f9a13773399ae5b1974b31; `hasura-kloudmate/metadata/databases/default/tables/public_alarm_groups.yaml`, `src/modules/alarms/groups/instances.ts` -- **Alarm Groups UI** - - Reason: UI fixes to alarm group tabs, panels, and sidebar may affect screenshots and step-by-step guides in the UI documentation. - - Likely docs area: Alerting > Alarm Groups UI guide - - Confidence: Medium - - Evidence: kloudmate-frontend commit 812e7933ce21a503df12f41e5e26580fa365f86a; `src/modules/alarms/components/groups/GroupAlertsTab.tsx`, `src/modules/alarms/components/groups/GroupInstancesPanel.tsx` -- **Alert Grouping Logic** - - Reason: Fixes to RCA bug and general alert grouping behavior may impact documentation on alert grouping rules, RCA, and incident correlation. - - Likely docs area: Alerting > Alert Grouping and RCA - - Confidence: Medium - - Evidence: kloudmate-backend commit a680907daac73955b11bd06cc775808551800872, kloudmate-backend commit a2540271d8904de64211161c83dc564917ebb180; `src/apps/alarms-service/src/grouping/lib/dispatch.ts`, `src/apps/alarms-service/src/lib/notifiableStates.ts` - -## Internal / Excluded Notes -_None._ - -## Source References -- https://github.com/kloudmate/kloudmate-frontend/commit/c6ad1ba703667ae190f9a13773399ae5b1974b31 -- https://github.com/kloudmate/kloudmate-backend/commit/fd0ef86f053c72ec48ed4745e8335dea65a5df17 -- https://github.com/kloudmate/kloudmate-backend/commit/a680907daac73955b11bd06cc775808551800872 -- https://github.com/kloudmate/kloudmate-frontend/commit/812e7933ce21a503df12f41e5e26580fa365f86a -- https://github.com/kloudmate/kloudmate-backend/commit/a2540271d8904de64211161c83dc564917ebb180 \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-06-22-weekly-digest.md b/content/engineering-digests/2026/2026-06-22-weekly-digest.md deleted file mode 100644 index aba97265..00000000 --- a/content/engineering-digests/2026/2026-06-22-weekly-digest.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Weekly Product Digest — 2026-06-22 -summary: Customer-facing product and documentation-impact summary for the week of 2026-06-22. -date: 2026-06-22 -period_start: 2026-06-22 -period_end: 2026-06-28 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-06-22 to 2026-06-28 - -## Executive Summary -This week’s customer-facing updates include a fix for RUM stat panels that incorrectly showed “0” when no data was available, the addition of filter and sort capabilities to the Issue Tracker, and a change to the default time range across API Monitoring, Logs, and Traces modules to 1 hour. - -## New -_None this week._ - -## Changed -- **Issue Tracker**: Added filter and sort capabilities to the issues list. -- **UI Time Range**: Changed default time range to 1 hour for API Monitoring, Logs, and Traces modules. - -## Breaking Changes -_None this week._ - -## Fixes -- **RUM**: Fixed stat panels showing "0" instead of "No Data" when no data is available. - -## Docs Impact -- **RUM (Real User Monitoring)** - - Reason: UI text change from showing "0" to "No Data" in stat panels when no data is present. - - Likely docs area: RUM dashboard documentation, stat panel reference. - - Confidence: Medium - - Evidence: kloudmate-frontend PR #938, `src/modules/rum/components/NetworkResponseTime.js`, `src/modules/rum/components/Stats.js` -- **Issue Tracker** - - Reason: Added filter and sort UI controls to the issues list, changing how users interact with issue data. - - Likely docs area: Issue Tracker user guide, filtering and sorting section. - - Confidence: Medium - - Evidence: kloudmate-frontend commit 2b95832, `src/graphql/issues/queries.js`, `src/modules/api-monitoring/components/EndpointsTable.tsx`, `src/modules/issue-tracker/Issues.js` -- **UI Time Range (API Monitoring, Logs, Traces)** - - Reason: Changed default time range from unspecified to 1 hour across multiple modules, affecting initial view. - - Likely docs area: Time picker settings documentation for each module; default configuration. - - Confidence: Medium - - Evidence: kloudmate-frontend commit e7c9ce4, `src/modules/api-monitoring/containers/ApiMonitoring.tsx`, `src/modules/logs/Logs.js`, `src/modules/traces/components/SearchBar/index.js`, `src/modules/traces/components/TraceGroups/index.tsx`, `src/modules/traces/containers/Traces.js`, `src/modules/traces/utils/filterStore.js` - -## Internal / Excluded Notes -_None._ (All changes in the data are customer-facing; no internal-only changes were excluded.) - -## Source References -- https://github.com/kloudmate/kloudmate-frontend/pull/938 -- https://github.com/kloudmate/kloudmate-frontend/commit/2b958321d09cdaa5699da3db5392d7a7058389ec -- https://github.com/kloudmate/kloudmate-frontend/commit/e7c9ce443bab0d9a38d58745f0b16c9ae6fdb606 \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-06-29-weekly-digest.md b/content/engineering-digests/2026/2026-06-29-weekly-digest.md deleted file mode 100644 index bac4c002..00000000 --- a/content/engineering-digests/2026/2026-06-29-weekly-digest.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Weekly Product Digest — 2026-06-29 -summary: Customer-facing product and documentation-impact summary for the week of 2026-06-29. -date: 2026-06-29 -period_start: 2026-06-29 -period_end: 2026-07-05 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-06-29 to 2026-07-05 - -## Executive Summary -This week saw the Profiling feature finalized and a CPU profile exporter typo fixed, enhancing observability capabilities. Additionally, report generation and loading issues were resolved, and alert notification links were corrected to point to the correct group. - -## New -_None this week._ - -## Changed -- **Profiling feature** – Finalized the Profiling feature, adding new endpoints and query capabilities. -- **CPU profile exporter** – Fixed a typo in the CPU profile exporter configuration (`exporter_profiles.go`). - -## Breaking Changes -_None this week._ - -## Fixes -- **Report generation** – Fixed a bug causing report generation failures in the dashboard panel. -- **Report loading** – Fixed report loading issues and corrected alert notification links to point to the appropriate group. - -## Docs Impact -- **Profiling feature** - - Reason: Finalizing the Profiling feature adds new capabilities and may change existing behavior. - - Likely docs area: Profiling documentation - - Confidence: High - - Evidence: kloudmate-backend PR #762, `src/datasources/kloudmate/profiles/index.ts`, `src/datasources/kloudmate/profiles/queries.ts` -- **CPU profile exporter** - - Reason: Fixes a typo in the CPU profile exporter configuration; may affect documentation of exporter settings. - - Likely docs area: OTel Exporter configuration - - Confidence: Low - - Evidence: kloudmate-otel-backend PR #622, `packages/otel-collector/exporters/kmexporter/exporter_profiles.go` -- **Report generation** - - Reason: Fixes a bug in report generation UI; may clarify usage instructions. - - Likely docs area: Dashboard reports guide - - Confidence: Low - - Evidence: kloudmate-frontend commit a6809dfc822bc056cd59866100d17fb1782110ef, `src/modules/dashboards/components/Panel.js` -- **Report loading and alert notifications** - - Reason: Fixes report loading and corrects alert notification links; impacts user guides. - - Likely docs area: Reports and Alerting documentation - - Confidence: Medium - - Evidence: kloudmate-backend commit 02499b4fbdc6b72a0aadfeacf4aad348382dcc33, `src/services/reports/DashboardReport.ts`, `src/services/grouping/lifecyclePayload.ts` - -## Internal / Excluded Notes -_None._ All changes in this period were customer-facing; no internal-only changes were identified. - -## Source References -- https://github.com/kloudmate/kloudmate-frontend/commit/a6809dfc822bc056cd59866100d17fb1782110ef -- https://github.com/kloudmate/kloudmate-backend/commit/02499b4fbdc6b72a0aadfeacf4aad348382dcc33 -- https://github.com/kloudmate/kloudmate-backend/pull/762 -- https://github.com/kloudmate/kloudmate-otel-backend/pull/622 \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-07-06-weekly-digest.md b/content/engineering-digests/2026/2026-07-06-weekly-digest.md deleted file mode 100644 index dcdc088f..00000000 --- a/content/engineering-digests/2026/2026-07-06-weekly-digest.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Weekly Product Digest — 2026-07-06 -summary: Customer-facing product and documentation-impact summary for the week of 2026-07-06. -date: 2026-07-06 -period_start: 2026-07-06 -period_end: 2026-07-12 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-07-06 to 2026-07-12 - -## Executive Summary -This week included a customer-facing change to the Agent v2 configuration handling in the backend. The update modifies how agent configurations are processed, potentially affecting users who rely on the Agent v2 API. No new features, breaking changes, or other fixes were reported. - -## New -_None this week._ - -## Changed -- **Agent v2 configuration** – Fixed handling of agent v2 config in the backend controllers and catalog service. - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -- **Agent v2 Configuration** - - Reason: Changes to agent v2 config handling in controllers and catalog may affect how users configure agents via API or UI. - - Likely docs area: Agent Configuration guide / Agent v2 setup documentation - - Confidence: Medium - - Evidence: kloudmate-backend commit f477d97f9031e6d07b18c2776c92e2fdbde03226, `src/functions/agents/controllers/index.ts`, `src/services/agent/catalog.ts` - -## Internal / Excluded Notes -_None._ - -## Source References -- https://github.com/kloudmate/kloudmate-backend/commit/f477d97f9031e6d07b18c2776c92e2fdbde03226 \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-07-13-weekly-digest.md b/content/engineering-digests/2026/2026-07-13-weekly-digest.md deleted file mode 100644 index fd983fe3..00000000 --- a/content/engineering-digests/2026/2026-07-13-weekly-digest.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Weekly Product Digest — 2026-07-13 -summary: Customer-facing product and documentation-impact summary for the week of 2026-07-13. -date: 2026-07-13 -period_start: 2026-07-13 -period_end: 2026-07-19 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-07-13 to 2026-07-19 - -## Executive Summary -This week introduced a new AWS MCP integration for the KloudMate AI Assistant, enabling secure connections to AWS services. Additionally, the MCP settings UI in the Assistant was refreshed to improve usability and clarity. - -## New -- **AWS MCP Integration**: Added support for connecting the KloudMate AI Assistant to AWS via MCP, providing token management and integration with AWS services. - -## Changed -- **MCP Settings UI**: Updated the MCP integration settings dialog and page in the Assistant settings area for improved usability. - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -- **AI Assistant Integrations** - - Reason: New AWS MCP integration feature requires documentation on setup, configuration, and usage. - - Likely docs area: Integrations > AWS MCP or AI Assistant > Integrations - - Confidence: High - - Evidence: kloudmate-backend commit 3b4fac6370542cc341d64ad2362a3f669f6e4517, `docs/aws-marketplace-architecture.png`, `docs/aws-marketplace-architecture.svg`, `src/functions/ai-assistant/integrations/aws-mcp-token.ts` -- **Assistant Settings (MCP)** - - Reason: UI changes to MCP settings dialog may affect user guidance and screenshots in documentation. - - Likely docs area: Assistant Settings > MCP Configuration - - Confidence: Medium - - Evidence: kloudmate-frontend commit 491e05dcc9f7fbca67239adb00fcaafbce793aec, `src/modules/assistant/containers/settings/mcp/CreateIntegrationDialog.tsx`, `src/modules/assistant/containers/settings/mcp/index.tsx` - -## Internal / Excluded Notes -_None._ (All changes in this period are customer-facing; no internal-only changes were excluded.) - -## Source References -- https://github.com/kloudmate/kloudmate-frontend/commit/491e05dcc9f7fbca67239adb00fcaafbce793aec -- https://github.com/kloudmate/kloudmate-backend/commit/3b4fac6370542cc341d64ad2362a3f669f6e4517 \ No newline at end of file diff --git a/content/engineering-digests/2026/2026-07-20-weekly-digest.md b/content/engineering-digests/2026/2026-07-20-weekly-digest.md deleted file mode 100644 index 944bf416..00000000 --- a/content/engineering-digests/2026/2026-07-20-weekly-digest.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Weekly Product Digest — 2026-07-20 -summary: Customer-facing product and documentation-impact summary for the week of 2026-07-20. -date: 2026-07-20 -period_start: 2026-07-20 -period_end: 2026-07-26 -type: engineering-digest ---- - -# Weekly KloudMate Product Digest -Period: 2026-07-20 to 2026-07-26 - -## Executive Summary -This week, KloudMate enhanced its eBPF-based network monitoring in the km-agent, adding VPC details and enabling the feature by default. Updates include daemonset configuration changes and documentation revisions for the eBPF network flows guide. No new features, breaking changes, or user-visible fixes were reported. - -## New -_None this week._ - -## Changed -- **eBPF network monitoring**: Added daemonset configuration for eBPF network monitoring and updated default settings. -- **eBPF VPC details**: Enabled eBPF monitoring by default and added VPC details to the collected data. - -## Breaking Changes -_None this week._ - -## Fixes -_None this week._ - -## Docs Impact -- **eBPF network monitoring (Agent)** - - Reason: Documentation file for eBPF network flows was updated alongside code changes. - - Likely docs area: Agent v2 LLD eBPF network flows - - Confidence: High - - Evidence: km-agent commit fc90fd6652c3fbbed7ac1b9547d27ab22afae316, `docs/agentv2/lld/lld-ebpf-network-flows.md` - -- **eBPF network monitoring (Agent)** - - Reason: Documentation updated to reflect VPC details and default enablement. - - Likely docs area: Agent v2 LLD eBPF network flows - - Confidence: High - - Evidence: km-agent commit 8bbaf0e3d0e6912bf94beaef18ab18adff82f5d8, `docs/agentv2/lld/lld-ebpf-network-flows.md` - -## Internal / Excluded Notes -_None._ - -## Source References -- https://github.com/kloudmate/km-agent/commit/fc90fd6652c3fbbed7ac1b9547d27ab22afae316 -- https://github.com/kloudmate/km-agent/commit/8bbaf0e3d0e6912bf94beaef18ab18adff82f5d8 \ No newline at end of file diff --git a/content/engineering-digests/README.md b/content/engineering-digests/README.md deleted file mode 100644 index 554b2f79..00000000 --- a/content/engineering-digests/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Engineering Digests - -This directory contains the weekly AI-generated product + documentation digests. - -Each digest is named `YYYY-MM-DD-weekly-digest.md` where the date is the Monday that started the week. - -These files are automatically committed by the `ai-docs-weekly-digest` GitHub Actions workflow. diff --git a/src/content/docs/docs/rum/correlate-rum-traces-with-opentelemetry-backends.mdx b/src/content/docs/docs/rum/correlate-rum-traces-with-opentelemetry-backends.mdx index 3a0fcc35..d66fa01f 100644 --- a/src/content/docs/docs/rum/correlate-rum-traces-with-opentelemetry-backends.mdx +++ b/src/content/docs/docs/rum/correlate-rum-traces-with-opentelemetry-backends.mdx @@ -1,42 +1,35 @@ --- title: "Correlate RUM traces with OpenTelemetry backends" description: "Connect RUM sessions from your web apps to their backend traces for a unified frontend-to-backend view." +sidebar: + order: 5 --- -KloudMate's RUM lets you connect requests from your web apps to their related backend traces. This integration provides a unified view of your frontend and backend data, so you can identify issues throughout your stack and understand your users' experience. +Connect a request from your web app to the backend trace behind it, so a slow call in a session opens onto the service that made it slow. -:::note - The RUM SDK by default adds [context propagation headers (W3C)](https://opentelemetry.io/docs/concepts/context-propagation/#propagation) to fetch and XHR requests made to the same origin. -::: +The SDK already adds [W3C context propagation headers](https://opentelemetry.io/docs/concepts/context-propagation/#propagation) to `fetch` and XHR requests made to the same origin, so those are stitched with no work. To reach a backend on a different origin, configure the SDK to send the headers, then allow those headers in your backend's CORS policy. Do the first without the second and the browser blocks every call to that origin. ## Send the trace headers from the browser -Pass the origins you want to propagate to as `propagateTraceHeaderCorsUrls`. To propagate trace context headers to `https://api.example.com`, initialize the SDK like this: +Name the cross-origin targets in `propagateTraceHeaderCorsUrls`. Each entry is a substring or a regular expression, and it covers both `fetch` and `XMLHttpRequest`. To propagate trace context to `https://api.example.com`: ```javascript KloudMateRum.init({ endpoint: 'https://otel.kloudmate.com:4318', - rumAccessToken: '', + rumAccessToken: 'YOUR_PUBLIC_API_KEY', applicationName: 'my-app', version: '1', deploymentEnvironment: 'prod', sessionRecorder: { - enabled: true, + enabled: true, }, - instrumentations: { - fetch: { - propagateTraceHeaderCorsUrls: [new RegExp('https://api\\.example\\.com.*')] - }, - xhr: { - propagateTraceHeaderCorsUrls: [new RegExp('https://api\\.example\\.com.*')] - } - } + propagateTraceHeaderCorsUrls: [/https:\/\/api\.example\.com/], }); ``` -This adds context propagation headers to backend requests. The backend can then generate its spans using this context. +This adds context propagation headers to those requests. The backend can then generate its spans using this context. :::note You can find examples of context extraction on the backend here: [https://opentelemetry.io/docs/languages/js/propagation/#generic-example](https://opentelemetry.io/docs/languages/js/propagation/#generic-example) @@ -78,6 +71,8 @@ Match what you're seeing in the browser to the fix: To confirm the header is arriving, log `req.headers.traceparent` on the backend. A value means the trace is stitched: the ID it carries is the same one on the browser span. +Once it is stitched, **View full backend trace** on a request row in [What happened](../session-detail/#what-happened) opens the distributed trace behind that call. + **Sample Integration:** 1. Once the RUM and backend are integrated you can view the corresponding backend trace of a frontend request @@ -93,5 +88,6 @@ To confirm the header is arriving, log `req.headers.traceparent` on the backend. ### Related Resources - [What Is Real User Monitoring (RUM)?](../) -- [KloudMate RUM Interface](../rum-interface/) +- [RUM Interface](../rum-interface/) +- [Session Detail](../session-detail/) diff --git a/src/content/docs/docs/rum/images/rum-add-application.png b/src/content/docs/docs/rum/images/rum-add-application.png new file mode 100644 index 00000000..b8658d55 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-add-application.png differ diff --git a/src/content/docs/docs/rum/images/rum-applications-index.png b/src/content/docs/docs/rum/images/rum-applications-index.png new file mode 100644 index 00000000..54c17440 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-applications-index.png differ diff --git a/src/content/docs/docs/rum/images/rum-errors.png b/src/content/docs/docs/rum/images/rum-errors.png new file mode 100644 index 00000000..a1bc6f14 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-errors.png differ diff --git a/src/content/docs/docs/rum/images/rum-events-analyze.png b/src/content/docs/docs/rum/images/rum-events-analyze.png new file mode 100644 index 00000000..725b8270 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-events-analyze.png differ diff --git a/src/content/docs/docs/rum/images/rum-interface-1.png b/src/content/docs/docs/rum/images/rum-interface-1.png deleted file mode 100644 index 05d0cfc8..00000000 Binary files a/src/content/docs/docs/rum/images/rum-interface-1.png and /dev/null differ diff --git a/src/content/docs/docs/rum/images/rum-interface-2.png b/src/content/docs/docs/rum/images/rum-interface-2.png deleted file mode 100644 index 43c46570..00000000 Binary files a/src/content/docs/docs/rum/images/rum-interface-2.png and /dev/null differ diff --git a/src/content/docs/docs/rum/images/rum-interface-3.png b/src/content/docs/docs/rum/images/rum-interface-3.png deleted file mode 100644 index ab5e663a..00000000 Binary files a/src/content/docs/docs/rum/images/rum-interface-3.png and /dev/null differ diff --git a/src/content/docs/docs/rum/images/rum-interface-4.png b/src/content/docs/docs/rum/images/rum-interface-4.png deleted file mode 100644 index c87b0cfa..00000000 Binary files a/src/content/docs/docs/rum/images/rum-interface-4.png and /dev/null differ diff --git a/src/content/docs/docs/rum/images/rum-interface-5.png b/src/content/docs/docs/rum/images/rum-interface-5.png deleted file mode 100644 index 5525e5fd..00000000 Binary files a/src/content/docs/docs/rum/images/rum-interface-5.png and /dev/null differ diff --git a/src/content/docs/docs/rum/images/rum-interface-6.png b/src/content/docs/docs/rum/images/rum-interface-6.png deleted file mode 100644 index 86a9d56d..00000000 Binary files a/src/content/docs/docs/rum/images/rum-interface-6.png and /dev/null differ diff --git a/src/content/docs/docs/rum/images/rum-journeys-funnels.png b/src/content/docs/docs/rum/images/rum-journeys-funnels.png new file mode 100644 index 00000000..54f30fa1 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-journeys-funnels.png differ diff --git a/src/content/docs/docs/rum/images/rum-journeys-pathways.png b/src/content/docs/docs/rum/images/rum-journeys-pathways.png new file mode 100644 index 00000000..68f8dbbc Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-journeys-pathways.png differ diff --git a/src/content/docs/docs/rum/images/rum-overview-audience.png b/src/content/docs/docs/rum/images/rum-overview-audience.png new file mode 100644 index 00000000..b92d772f Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-overview-audience.png differ diff --git a/src/content/docs/docs/rum/images/rum-overview.png b/src/content/docs/docs/rum/images/rum-overview.png new file mode 100644 index 00000000..3166ed6c Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-overview.png differ diff --git a/src/content/docs/docs/rum/images/rum-pages.png b/src/content/docs/docs/rum/images/rum-pages.png new file mode 100644 index 00000000..09446260 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-pages.png differ diff --git a/src/content/docs/docs/rum/images/rum-performance-network.png b/src/content/docs/docs/rum/images/rum-performance-network.png new file mode 100644 index 00000000..8aac5744 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-performance-network.png differ diff --git a/src/content/docs/docs/rum/images/rum-performance.png b/src/content/docs/docs/rum/images/rum-performance.png new file mode 100644 index 00000000..c7c84355 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-performance.png differ diff --git a/src/content/docs/docs/rum/images/rum-releases.png b/src/content/docs/docs/rum/images/rum-releases.png new file mode 100644 index 00000000..1367ad33 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-releases.png differ diff --git a/src/content/docs/docs/rum/images/rum-session-attributes.png b/src/content/docs/docs/rum/images/rum-session-attributes.png new file mode 100644 index 00000000..01318025 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-session-attributes.png differ diff --git a/src/content/docs/docs/rum/images/rum-session-drawer.png b/src/content/docs/docs/rum/images/rum-session-drawer.png new file mode 100644 index 00000000..c965dac3 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-session-drawer.png differ diff --git a/src/content/docs/docs/rum/images/rum-session-errors.png b/src/content/docs/docs/rum/images/rum-session-errors.png new file mode 100644 index 00000000..6040ef7f Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-session-errors.png differ diff --git a/src/content/docs/docs/rum/images/rum-session-frustration.png b/src/content/docs/docs/rum/images/rum-session-frustration.png new file mode 100644 index 00000000..c7ddf020 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-session-frustration.png differ diff --git a/src/content/docs/docs/rum/images/rum-session-network.png b/src/content/docs/docs/rum/images/rum-session-network.png new file mode 100644 index 00000000..887b3e29 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-session-network.png differ diff --git a/src/content/docs/docs/rum/images/rum-sessions-list.png b/src/content/docs/docs/rum/images/rum-sessions-list.png new file mode 100644 index 00000000..4f0aa850 Binary files /dev/null and b/src/content/docs/docs/rum/images/rum-sessions-list.png differ diff --git a/src/content/docs/docs/rum/index.mdx b/src/content/docs/docs/rum/index.mdx index bd8f7e77..9f8c7967 100644 --- a/src/content/docs/docs/rum/index.mdx +++ b/src/content/docs/docs/rum/index.mdx @@ -13,11 +13,10 @@ This data provides insights into how users directly experience the system. Using RUM must be integrated with an APM backend such as KloudMate for the RUM data to be used effectively. This integration is essential as the APM backend, KloudMate in this case, provides the necessary infrastructure and tools to aggregate, process, and analyze the RUM data. -![image](./images/what-is-real-user-monitoring-_rum__-1.png) - ### How Does RUM Work? - Rum works by embedding small JavaScript snippets into web pages while they are in use. These snippets record and send performance metrics back to a monitoring platform where the data can be queried and visualized. The collected data is then analyzed using the monitoring platform's monitoring capabilities to understand various performance aspects. + +Rum works by embedding small JavaScript snippets into web pages while they are in use. These snippets record and send performance metrics back to a monitoring platform where the data can be queried and visualized. The collected data is then analyzed using the monitoring platform's monitoring capabilities to understand various performance aspects. The following diagram illustrates the step-by-step process of how Real User Monitoring (RUM) works: @@ -40,9 +39,10 @@ The following diagram illustrates the step-by-step process of how Real User Moni | Session | A session is a period during which a user interacts with an application or a website. It starts when the user first accesses the web or application page and ends after a predetermined period of inactivity or when the user closes the application. | | User Journey | The paths that users take through a website, including the sequence of pages and interactions. | | Session ID | A session ID is a unique identifier assigned to each user session. The session ID helps in correlating various user activities and interactions in a session, such as page views, clicks, form submissions, and other events, into a single coherent session. | -| Session Replay | A feature within KloudMate that allows capturing and replaying user interactions with a website for analysis. | +| Session Replay | A recording of what the page looked like during a session, played back frame by frame. Recording is off until it is turned on, and only a sampled share of sessions carry one. See [Record sessions for replay](./instrumentation-guide/web/#record-sessions-for-replay). | +| Frustration signal | A click the browser SDK judged unproductive at the moment it happened: a **rage click** (repeated clicks on the same element), a **dead click** (a click nothing responded to), or an **error click** (a click followed by a JavaScript error). See [Frustration signals](./session-detail/#frustration-signals). | | Network Response Time | The time it takes for a network request made by the user's browser to receive a response from the server. | -| Web Vitals | A set of metrics to quantify essential aspects of web page performance, including **Largest Contentful Paint (LCP)**, which measures how long it takes the largest visible content element to render; **Cumulative Layout Shift (CLS)**, which measures unexpected layout shifts over the page lifetime; **Interaction to Next Paint (INP)**, which captures the time between user interaction and the next paint event; and **First Input Delay (FID)**, which measures the delay between the first user interaction and when the browser starts processing it. | +| Web Vitals | Metrics that quantify how a page felt to load and respond: **Largest Contentful Paint (LCP)**, how long the largest visible element took to render; **Cumulative Layout Shift (CLS)**, how much the layout moved unexpectedly; **Interaction to Next Paint (INP)**, the delay between an interaction and the next paint; plus **Time to First Byte (TTFB)** and **First Contentful Paint (FCP)** on an individual session. INP replaced First Input Delay as a Core Web Vital in March 2024, and the RUM views report it instead. | *** @@ -50,3 +50,5 @@ The following diagram illustrates the step-by-step process of how Real User Moni - [Instrumentation Guide](./instrumentation-guide/) - [RUM Interface](./rum-interface/) +- [Session Detail](./session-detail/) +- [Correlate RUM traces with OpenTelemetry backends](./correlate-rum-traces-with-opentelemetry-backends/) diff --git a/src/content/docs/docs/rum/instrumentation-guide.mdx b/src/content/docs/docs/rum/instrumentation-guide.mdx deleted file mode 100644 index a2accfdd..00000000 --- a/src/content/docs/docs/rum/instrumentation-guide.mdx +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: "Instrumentation Guide" -description: "Learn how to instrument and set up Real User Monitoring (RUM) with KloudMate." -sidebar: - order: 2 ---- - -This guide helps you set up Real User Monitoring (RUM) with KloudMate for your websites. - -:::note - The RUM agent is compatible with all the supported versions of the following browsers: - - - Google Chrome - - Microsoft Edge - - Mozilla Firefox - - Apple Safari - - Chromium-based browsers -::: - -## Setting Up Real User Monitoring with KloudMate - -### Step 1: Navigate to RUM Module - -- Log in to your KloudMate account. -- Navigate to the Real User Monitoring page. - -### Step 2: Add the Host Website - -- Click Add New Website in the top-right corner of the RUM module. -- Enter the URL of the website you want to integrate. -- Click Continue. - -![image](./images/get-started-with-rum-1.png) - -### Step 3: Configure the Settings - -- Add an Application Name, Environment, and Version to identify the integration. -- Enable or disable Session Recording using the toggle, enabling it records user sessions. -- Set the Session Sample Rate to decide what percentage of total sessions get recorded. - -![image](./images/get-started-with-rum-2.png) - -### Step 4: Install the Script - -- Copy the generated script. -- Paste it into the `` section of your website. - -![image](./images/get-started-with-rum-3.png) - -- To verify installation, click the Verify Installation button, which shows the integration status. - -## Managing User Details - -- To capture the current user's ID and email, if available, add the following code: - -```javascript -KloudMateRum.setGlobalAttributes({ userId: 'u_123', userEmail: 'john@example.com'}) -``` - -- To clear out the user details, for example on user logout, use the following code: - -```javascript -KloudMateRum.setGlobalAttributes({ userId: null, userEmail: null }) -``` - -## SDK Methods - -### 1. init(options) - -Initializes the RUM SDK. - -`options` is an object that accepts the following properties: - -| Property | Type | Description | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| applicationName | string | The application's name. | -| endpoint | string (required) | KloudMate's collector endpoint: `https://otel.kloudmate.com:4318` | -| rumAccessToken | string (required) | KloudMate workspace's Public API key | -| deploymentEnvironment | string | The application's environment. Example: dev, prod, staging | -| version | string | The applications' version. Example: 0.0.1, 1 | -| globalAttributes | object | Key value pairs. These attributes will be added to every span | -| sessionRecorder | object
\{ enabled: boolean, options: [RRWebRecordConfig](https://github.com/rrweb-io/rrweb/blob/master/guide.md#options) } | Configure session recording. Set `enabled` to `true` to record the session. By default session recording is disabled. | -| ignoreUrls | `(string \| RegExp)[]` | URLs to ignore | - -### 2. setGlobalAttributes(attributes) - -| Parameter | Type | Description | -| ---------- | ------ | ---------------- | -| attributes | object | Key value pairs. | - -### 3. getGlobalAttributes() - -Returns the global attributes. - -## Data Attributes - -Some of the important attributes collected by the RUM SDK - -| **Attributes** | **Description** | -| -------------- | -------------------------------------------------------------------------------- | -| serviceName | The RUM application name. | -| rumSessionId | The RUM session's id | -| origin | Origin of the app instrumented. Eg., [https://example.com](https://example.com) | -| route | Part of the app's URL excluding origin and search | -| search | Search params of the app's URL | -| component | The instrumentation name that produced the span | -| eventType | The type of user interaction. eg. click, submit etc | -| userId | User's synthetic id | -| userEmail | User's email | -| browser | User's browser name | -| browserVersion | User's browser version | -| country | User's country | -| city | User's city | -| os | User's operating system | -| osVersion | User's operating system's version | -| device | User's device | -| deviceType | User's device type | - -*** - -## Related Resources - -- [What Is Real User Monitoring (RUM)?](../) -- [RUM Interface](../rum-interface/) diff --git a/src/content/docs/docs/rum/instrumentation-guide/_meta.json b/src/content/docs/docs/rum/instrumentation-guide/_meta.json new file mode 100644 index 00000000..fa2fa250 --- /dev/null +++ b/src/content/docs/docs/rum/instrumentation-guide/_meta.json @@ -0,0 +1,5 @@ +{ + "label": "Instrumentation Guide", + "order": 2, + "collapsed": true +} diff --git a/src/content/docs/docs/rum/instrumentation-guide/android.mdx b/src/content/docs/docs/rum/instrumentation-guide/android.mdx new file mode 100644 index 00000000..6ccaa60d --- /dev/null +++ b/src/content/docs/docs/rum/instrumentation-guide/android.mdx @@ -0,0 +1,83 @@ +--- +title: "Android" +description: "Install the KloudMate Android RUM SDK, capture HTTP requests through OkHttp, and identify users." +sidebar: + order: 3 +--- + +Add the dependency to your module, then initialize once from `Application.onCreate()`. The generated snippet carries the current version: + +```kotlin +// build.gradle.kts +implementation("com.kloudmate.rum:rum-core:") + +// Application.onCreate() +KloudMateRum.init( + context = this, + config = KloudMateRumConfig( + endpoint = "https://otel.kloudmate.com:4318", + rumAccessToken = "YOUR_PUBLIC_API_KEY", + applicationName = "MyApp", + deploymentEnvironment = "production", + version = "1.0.0", + sampleRate = 1.0, + sessionReplayEnabled = true, + replaySampleRate = 0.25, + ), +) +``` + +Get your key and the rest of the values from **Add application**. See [Add an application](../#add-an-application). + +`appId` defaults to the package name, so there is no need to pass it. + +Screen views, app start, slow and frozen frames, device vitals, ANRs, crashes, and tap spans are captured from this call on. + +## Name a Compose screen + +Screens come from Activity and Fragment lifecycle, which Compose navigation happens inside, so it is invisible to those callbacks. Wire it up explicitly: + +```kotlin +navController.addOnDestinationChangedListener { _, destination, _ -> + KloudMateRum.setCurrentScreen(destination.route ?: "unknown") +} +``` + +## Capture HTTP requests + +HTTP capture needs one extra step. OkHttp cannot be patched globally, so add the interceptor to every `OkHttpClient` you build: + +```kotlin +val client = OkHttpClient.Builder() + .addInterceptor(KloudMateRum.okHttpInterceptor()!!) + .build() +``` + +Without it the app reports no HTTP spans at all, and nothing says why. + +## Identify the user + +Set the user after sign-in: + +```kotlin +KloudMateRum.setUser(id = "u123", email = "alice@example.com") +``` + +Call `endSession()` on sign-out. Nothing else detects it, and a mobile session runs up to four hours, never times out while the app is in the foreground, and survives the process being killed. Without it, a sign-out followed by a different sign-in keeps one session ID across both people. + +```kotlin +fun onLogout() { + KloudMateRum.endSession() + // ... clear your own auth state +} +``` + +Everything `setUser()` set is cleared, values you set with `setGlobalAttributes()` are kept, and the next session starts at the next instrumented event rather than inside the call. + +*** + +## Related Resources + +- [Send custom events](../custom-events/) +- [React Native](../react-native/) +- [RUM Interface](../../rum-interface/) diff --git a/src/content/docs/docs/rum/instrumentation-guide/custom-events.mdx b/src/content/docs/docs/rum/instrumentation-guide/custom-events.mdx new file mode 100644 index 00000000..54bb3f42 --- /dev/null +++ b/src/content/docs/docs/rum/instrumentation-guide/custom-events.mdx @@ -0,0 +1,76 @@ +--- +title: "Send custom events" +description: "Record what a visitor accomplished with addEvent(), on web, Android, iOS, and React Native." +sidebar: + order: 6 +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +`addEvent()` records something the SDK cannot infer on its own: a signup, a checkout, a plan upgrade. Everything else RUM collects describes how the app behaved, and these describe what the visitor accomplished. + +A custom event carries the session, route, and user context of the moment it fired, which is what lets it work as a [funnel](../../rum-interface/#funnels) step and show up on the session's own timeline. Events are counted per session and per user, rolled up hourly, and kept for 400 days, so they answer questions about last quarter long after the raw sessions have aged out. + + + + ```javascript + KloudMateRum.addEvent('checkout_completed', { + value: 4999, + currency: 'INR', + items: 3, + }); + ``` + + With the npm package, import it instead: `import { addEvent } from '@kloudmate/rum-web';` + + + ```kotlin + KloudMateRum.addEvent( + "checkout_completed", + mapOf("value" to 4999, "currency" to "INR", "items" to 3), + ) + ``` + + + ```swift + KloudMateRum.shared.addEvent( + name: "checkout_completed", + attributes: ["value": 4999, "currency": "INR", "items": 3] + ) + ``` + + + ```javascript + import { addEvent } from '@kloudmate/rum-react-native'; + addEvent('checkout_completed', { + value: 4999, + currency: 'INR', + items: 3, + }); + ``` + + + +Custom events matter more on mobile than on web. A funnel built only from screen names cannot express "added to cart", and there are no URLs to fall back on. + +## Keep the event name constant + +Name the event for the thing that happened, never for the thing it happened to. `checkout_completed` is a name; `checkout_user_8f21c` is thousands of them. + +A workspace can hold only so many distinct event names per day. Past that, the rest are folded into one bucket that the [Events](../../rum-interface/#events) tab labels **Over the daily name limit**, so interpolated names do not give you fine-grained rows, they collapse into a single useless one. Put the varying part in the attributes. + +## `value` is reserved + +An attribute named exactly `value` is the one the platform sums and takes percentiles over. That is what makes revenue-style questions work without a schema per customer: pick **value** as the measure on the [Events](../../rum-interface/#events) tab and sum it, or read its p90. + +Pass it as a number. Types survive the wire, and a stringified `4999` is not something the query layer can aggregate. + +Both rules apply on every platform, and events from all of them land in the same place. + +*** + +## Related Resources + +- [The Events tab](../../rum-interface/#events) +- [Funnels](../../rum-interface/#funnels) +- [SDK reference](../sdk-reference/) diff --git a/src/content/docs/docs/rum/instrumentation-guide/index.mdx b/src/content/docs/docs/rum/instrumentation-guide/index.mdx new file mode 100644 index 00000000..19d60794 --- /dev/null +++ b/src/content/docs/docs/rum/instrumentation-guide/index.mdx @@ -0,0 +1,66 @@ +--- +title: "Instrumentation Guide" +description: "Set up Real User Monitoring for a website, an Android or iOS app, or a React Native or Expo app." +sidebar: + label: "Overview" + order: 1 +--- + +KloudMate has a RUM SDK for the browser, for native Android and iOS, and for React Native. Every one of them sends to the same collector and lands in the same [RUM interface](../rum-interface/), so a workspace can hold a website and its mobile apps side by side. + +## Supported platforms + +| Platform | SDK | Installed with | Setup | +| --- | --- | --- | --- | +| Web | `@kloudmate/rum-web` | A script tag, or npm | [Web](./web/) | +| Android | `com.kloudmate.rum:rum-core` | Gradle | [Android](./android/) | +| iOS | `KloudMateRum` | Swift Package Manager. Requires iOS 12 or later | [iOS](./ios/) | +| React Native, including Expo | `@kloudmate/rum-react-native` | npm, with autolinking | [React Native](./react-native/) | + +The browser SDK works on current versions of Chrome, Edge, Firefox, Safari, and other Chromium-based browsers. + +## Add an application + +Open **RUM** and click **Add application**. + +![The Add Application wizard on the Platform step, with Website, Android, and iOS options](../images/rum-add-application.png) + +The last step generates a snippet carrying your key and the current SDK version. Copy that one rather than the examples in these pages, which use placeholders. + +Two things are worth deciding before you click through. + +### The name is permanent + +Use only letters, digits, and `.`, `_`, or `-`. Anything else gets rewritten on the way in, and the rewritten name then disagrees with the original, which surfaces as panels that go blank. + +:::caution +There is no rename. A RUM application comes into existence when data arrives under a name, so a name you regret cannot be corrected later. Mobile apps set `applicationName` in source and never see this validation, so the same rule applies there by hand. +::: + +Set **Version** here too, or the [Releases](../rum-interface/#releases) tab has nothing to compare. + +### Sampling + +**Telemetry sampling** is the share of sessions that send data at all. An unsampled session registers no listeners and makes no requests, so this is the lever on ingest volume. **Replay sampling** then decides which of those sessions are recorded. + +The two multiply: 25% telemetry and 10% replay records one session in forty. + +## The ingest key + +RUM sends with a **Frontend** ingest key, which ships in client code where anyone can read it. That is why a frontend key carries a list of **allowed hosts**: data sent from any other origin is rejected. If an application is installed correctly and still reports nothing, check that list first. + +Keys live under **Settings → Ingest Keys** in the workspace, where you can also add one, edit its allowed hosts, or delete a key that has leaked. A key is shown once at creation and cannot be retrieved later, so rotating means creating a replacement, swapping it into your app, and then deleting the old one. See [API Keys](../../platform/settings/api-keys/). + +## Next + +- Install and configure the SDK: [Web](./web/), [Android](./android/), [iOS](./ios/), or [React Native](./react-native/). +- Record what a visitor accomplished, not only how the app behaved: [Send custom events](./custom-events/). +- Look up an option or an attribute: [SDK reference](./sdk-reference/). + +*** + +## Related Resources + +- [What Is Real User Monitoring (RUM)?](../) +- [RUM Interface](../rum-interface/) +- [Session Detail](../session-detail/) diff --git a/src/content/docs/docs/rum/instrumentation-guide/ios.mdx b/src/content/docs/docs/rum/instrumentation-guide/ios.mdx new file mode 100644 index 00000000..97ecd081 --- /dev/null +++ b/src/content/docs/docs/rum/instrumentation-guide/ios.mdx @@ -0,0 +1,67 @@ +--- +title: "iOS" +description: "Install the KloudMate iOS RUM SDK through Swift Package Manager, opt each URLSession into HTTP capture, and identify users." +sidebar: + order: 4 +--- + +Add the package in Xcode under **File > Add Package Dependencies**, using `https://github.com/kloudmate/rum-mobile-swift` and the **Up to Next Major** rule. Then initialize once from your `AppDelegate` or your `App` init: + +```swift +import KloudMateRum + +let config = KloudMateRumConfig( + endpoint: "https://otel.kloudmate.com:4318", + rumAccessToken: "YOUR_PUBLIC_API_KEY", + applicationName: "MyApp" +) +config.deploymentEnvironment = "production" +config.version = "1.0.0" +config.sampleRate = 1.0 +config.sessionReplayEnabled = true +config.replaySampleRate = 0.25 + +KloudMateRum.shared.doInit(config: config) +``` + +Get your key and the rest of the values from **Add application**. See [Add an application](../#add-an-application). + +`KloudMateRumConfig` takes either those three arguments or all of them, with nothing in between, so pass the first three and assign the rest. `appId` defaults to the bundle ID. + +Screen views, app start, crashes, app hangs, and tap spans are captured from this call on. A screen the SDK cannot see, such as a purely programmatic transition, needs `KloudMateRum.shared.setCurrentScreen(name: "Checkout")`. + +## Capture HTTP requests + +`URLSession.shared` is instrumented automatically. A session your app builds from its own configuration is not: it consults its own `protocolClasses`, which never contains a globally registered `NSURLProtocol`. Opt each one in: + +```swift +let configuration = URLSessionConfiguration.default +KloudMateRum.shared.instrument(configuration: configuration) +let session = URLSession(configuration: configuration) +``` + +Most apps build their own session, so skipping this usually means no HTTP spans at all. + +## Identify the user + +Set the user after sign-in: + +```swift +KloudMateRum.shared.setUser(id: "u123", email: "alice@example.com", extra: [:]) +``` + +Call `KloudMateRum.shared.endSession()` on sign-out. Nothing else detects it, and a mobile session runs up to four hours, never times out while the app is in the foreground, and survives the process being killed. + +## What reads differently from Android + +:::note +App-start time on iOS is measured from `KloudMateRum.init` rather than from process start, so it excludes everything before that and is not comparable to the Android figure. An app-hang stack is sampled just before the main thread blocks, because no supported API reads a blocked thread's stack, so it points near the cause rather than exactly at it. Screen tracking samples the view-controller hierarchy about once a second, so a screen that appears and disappears faster than that is missed. +::: + +*** + +## Related Resources + +- [Send custom events](../custom-events/) +- [React Native](../react-native/) +- [RUM Interface](../../rum-interface/) diff --git a/src/content/docs/docs/rum/instrumentation-guide/react-native.mdx b/src/content/docs/docs/rum/instrumentation-guide/react-native.mdx new file mode 100644 index 00000000..d75661bb --- /dev/null +++ b/src/content/docs/docs/rum/instrumentation-guide/react-native.mdx @@ -0,0 +1,85 @@ +--- +title: "React Native" +description: "Install the KloudMate React Native RUM SDK, set it up on an Expo development build, and capture HTTP requests." +sidebar: + order: 5 +--- + +One `init` covers both platforms. Autolinking registers the native module, so there are no manual linking steps: + +```bash +npm install @kloudmate/rum-react-native +cd ios && pod install # iOS only +``` + +Call `init()` once, at the top of your app entry file: + +```javascript +import { init } from '@kloudmate/rum-react-native'; + +init({ + endpoint: 'https://otel.kloudmate.com:4318', + rumAccessToken: 'YOUR_PUBLIC_API_KEY', + applicationName: 'my-app', + deploymentEnvironment: 'production', + version: '1.0.0', + sampleRate: 1.0, + sessionReplayEnabled: true, + replaySampleRate: 0.25, +}); +``` + +Get your key and the rest of the values from **Add application**. See [Add an application](../#add-an-application). + +Screens, taps, app start, crashes, and uncaught JS errors are captured from this call on. `captureJsErrors` turns the JS error handler off if you install your own. Name a screen with `setCurrentScreen()`, send a custom event with [`addEvent()`](../custom-events/), and report a handled error with `reportError()`. + +## Expo + +The SDK ships native code, so it does not run in Expo Go. Use a [development build](https://docs.expo.dev/develop/development-builds/introduction/). + +Add the config plugin to your app config: + +```json +{ + "expo": { + "plugins": ["@kloudmate/rum-react-native"] + } +} +``` + +Then create the development build: + +```bash +npx expo prebuild +npx expo run:android # or: npx expo run:ios +``` + +The plugin registers the native module on Android; iOS needs no extra setup. It works with the New Architecture, and everything else on this page applies unchanged. + +## Capture HTTP requests + +React Native sends `fetch` and `XMLHttpRequest` through OkHttp on Android, so one native line in `MainApplication.kt` covers both with no JavaScript involved: + +```kotlin +OkHttpClientProvider.setOkHttpClientFactory { + OkHttpClient.Builder() + .addInterceptor(KloudMateRum.okHttpInterceptor()!!) + .build() +} +``` + +:::caution +React Native on iOS captures no HTTP requests. Requests go through the framework's own `NSURLSession` inside `RCTHTTPRequestHandler`, which offers no injection point, so the `instrument(configuration:)` call a [native iOS](../ios/#capture-http-requests) app makes has nowhere to go. Screens, taps, app start, crashes, and JS errors are captured normally, and the Network tab on those sessions is empty. +::: + +## Identify the user + +Set the user after sign-in with `setUser()`, and call `endSession()` on sign-out. A mobile session runs up to four hours and survives the process being killed, so without it a sign-out followed by a different sign-in keeps one session ID across both people. + +*** + +## Related Resources + +- [Send custom events](../custom-events/) +- [Android](../android/) +- [iOS](../ios/) diff --git a/src/content/docs/docs/rum/instrumentation-guide/sdk-reference.mdx b/src/content/docs/docs/rum/instrumentation-guide/sdk-reference.mdx new file mode 100644 index 00000000..1614c519 --- /dev/null +++ b/src/content/docs/docs/rum/instrumentation-guide/sdk-reference.mdx @@ -0,0 +1,71 @@ +--- +title: "SDK reference" +description: "Every init option on the browser RUM SDK, its methods, and the attributes the SDKs collect." +sidebar: + order: 7 +--- + +## init(options) + +Initializes the browser RUM SDK. `options` accepts the following properties: + +| Property | Type | Description | +| --- | --- | --- | +| `applicationName` | string | The application's name. | +| `endpoint` | string (required) | KloudMate's collector endpoint: `https://otel.kloudmate.com:4318` | +| `rumAccessToken` | string (required) | The workspace's Frontend ingest key. See [The ingest key](../#the-ingest-key). | +| `deploymentEnvironment` | string | The application's environment, such as `dev`, `prod`, or `staging`. | +| `version` | string | The application's version, such as `0.0.1`. | +| `globalAttributes` | object | Key-value pairs added to every span. | +| `sampleRate` | number | Share of sessions that send telemetry at all, from 0 to 1. Defaults to 1. | +| `sessionRecorder` | object
\{ enabled: boolean, sampleRate: number, options: [RRWebRecordConfig](https://github.com/rrweb-io/rrweb/blob/master/guide.md#options) } | Configure session recording. Set `enabled` to `true` to record. Off by default. See [Record sessions for replay](../web/#record-sessions-for-replay). | +| `events` | object | Which automatic events to capture, including console output. See [Choose what the SDK captures](../web/#choose-what-the-sdk-captures). | +| `ignoreUrls` | `(string \| RegExp)[]` | URLs to skip instrumenting entirely. No span, and no trace header. | +| `propagateTraceHeaderCorsUrls` | `(string \| RegExp)[]` | Cross-origin URLs allowed to receive the `traceparent` header. See [Correlate RUM traces with OpenTelemetry backends](../../correlate-rum-traces-with-opentelemetry-backends/). | +| `excludeBots` | boolean \| function | Skip automated and crawler traffic so it never starts a session. Defaults to `true`. | +| `captureConsoleErrors` | boolean | Report `console.error(...)` as a real error, not only as a console line. Defaults to `true`. | + +## Methods + +| Method | Description | +| --- | --- | +| `setGlobalAttributes(attributes)` | Add key-value pairs to every subsequent span. See [Identify the user](../web/#identify-the-user). | +| `getGlobalAttributes()` | Return the current global attributes. | +| `addEvent(name, attributes)` | Record a custom event. See [Send custom events](../custom-events/). | + +The mobile SDKs share one `KloudMateRumConfig` across Android, iOS, and React Native. TypeScript definitions for the full React Native option list ship with the package. + +## Data attributes + +Some of the important attributes the RUM SDKs collect: + +| Attribute | Description | +| --- | --- | +| `serviceName` | The RUM application name. | +| `rumSessionId` | The RUM session's ID. | +| `origin` | Origin of the instrumented app, such as `https://example.com`. | +| `route` | Part of the app's URL excluding origin and search. | +| `search` | Search params of the app's URL. | +| `component` | The instrumentation that produced the span. | +| `eventType` | The type of user interaction, such as `click` or `submit`. | +| `userId` | User's synthetic ID. | +| `userEmail` | User's email. | +| `browser` | User's browser name. | +| `browserVersion` | User's browser version. | +| `country` | User's country. | +| `city` | User's city. | +| `os` | User's operating system. | +| `osVersion` | User's operating system version. | +| `device` | User's device. | +| `deviceType` | User's device type. | +| `screen.width` | Screen width in logical pixels. Shown as **Screen** in session detail, and behind the **Screen resolution** breakdown on Overview. | +| `screen.height` | Screen height in logical pixels. | +| `screen.density` | Display scale factor. Shown as **Screen Density** when it is not 1. | + +*** + +## Related Resources + +- [Web setup](../web/) +- [Send custom events](../custom-events/) +- [Session Detail](../../session-detail/) diff --git a/src/content/docs/docs/rum/instrumentation-guide/web.mdx b/src/content/docs/docs/rum/instrumentation-guide/web.mdx new file mode 100644 index 00000000..233de0b9 --- /dev/null +++ b/src/content/docs/docs/rum/instrumentation-guide/web.mdx @@ -0,0 +1,119 @@ +--- +title: "Web" +description: "Install the KloudMate browser RUM SDK, identify users, record sessions for replay, and choose what gets captured." +sidebar: + order: 2 +--- + +Paste the generated script into the `` of your site. `init` runs from the tag's `onload`, so the bundle is fully executed before it is called: + +```html + +``` + +The `/v2/` path is a rolling one: it carries patches and features but never a major version, so fixes reach your users without an edit here. + +If you would rather bundle it, install the package and call `init()` as early as possible in your entry point. Instrumentation only captures what happens after it runs: + +```bash +npm install @kloudmate/rum-web +``` + +```javascript +import { init } from '@kloudmate/rum-web'; + +init({ + endpoint: 'https://otel.kloudmate.com:4318', + rumAccessToken: 'YOUR_PUBLIC_API_KEY', + applicationName: 'my-app', + deploymentEnvironment: 'production', +}); +``` + +Get your key and the rest of the values from **Add application**. See [Add an application](../#add-an-application). + +## Identify the user + +Without an ID, every session in the list reads **Anonymous**. Set the current user's ID and email once they are known, usually right after sign-in: + +```javascript +KloudMateRum.setGlobalAttributes({ userId: 'u_123', userEmail: 'john@example.com' }) +``` + +Clear them on sign-out, so the next session on that browser is not attributed to the person who just left: + +```javascript +KloudMateRum.setGlobalAttributes({ userId: null, userEmail: null }) +``` + +## Record sessions for replay + +Session recording is off until it is turned on, and even then only a share of sessions are recorded. That share is `sessionRecorder.sampleRate`, a fraction between 0 and 1: + +```javascript +KloudMateRum.init({ + endpoint: 'https://otel.kloudmate.com:4318', + rumAccessToken: 'YOUR_PUBLIC_API_KEY', + applicationName: 'my-app', + sessionRecorder: { + enabled: true, + // Of the sessions that send telemetry, the share also recorded for replay. + sampleRate: 0.1, + options: { maskAllInputs: true }, + }, +}); +``` + +The rates compose. The top-level `sampleRate` decides whether a session sends anything at all, and `sessionRecorder.sampleRate` then decides whether a session that is already sending is also recorded. At `sampleRate: 0.25` and `sessionRecorder.sampleRate: 0.1`, one session in forty carries a replay. + +Sessions without a recording still show their timeline, network waterfall, console output, and errors. The Replay tab reads [No replay for this session](../../session-detail/#replay). + +Inputs are masked by default. Add `km-block` to an element's class list to keep it out of the recording entirely, or `km-ignore` to record the element but not what is typed into it. + +## Choose what the SDK captures + +Console output, navigation, and resource timing are controlled through the `events` option: + +```javascript +KloudMateRum.init({ + // ... + events: { + // Defaults to ['error', 'warn']. + console: ['error', 'warn', 'info'], + }, +}); +``` + +| Option | Default | What it does | +| --- | --- | --- | +| `events.enabled` | `true` | Master switch for automatic event capture. | +| `events.console` | `['error', 'warn']` | Fills the [Console tab](../../session-detail/#console). Pass an array of levels from `error`, `warn`, `log`, `info`, and `debug` to capture more, or `false` to capture none. | +| `events.navigation` | `true` | Route changes in a single-page app, and navigation timing on the initial load. | +| `events.resourceTiming` | `false` | One event per sub-resource. High volume, so opt in deliberately. | + +Web vitals are captured regardless of this setting, as spans, since they drive the p75 charts on Pages. [Frustration signals](../../session-detail/#frustration-signals) are always detected too, because rage, dead, and error clicks land as attributes on the click span that already exists. + +`captureConsoleErrors` is separate and on by default: it reports `console.error(...)` as a real error rather than only as a console line. Set it to `false` if `console.error` is used for logging that should not be counted against the error rate. + +## Keep the SDK current + +Upgrading matters most for replay on long sessions. Since `@kloudmate/rum-web` 2.2.4, the recorder writes a full DOM snapshot every 5,000 events instead of relying on the single snapshot taken at the start of the recording. Seeking then rebuilds from the nearest snapshot rather than replaying everything before the target, which is the difference between a jump into minute 20 of a session taking about a second and taking most of a minute. + +There is nothing to configure. Loading the current bundle is enough, and the rolling `/v2/` CDN path in the generated snippet picks up patches without any edit. + +*** + +## Related Resources + +- [Send custom events](../custom-events/) +- [SDK reference](../sdk-reference/) +- [Correlate RUM traces with OpenTelemetry backends](../../correlate-rum-traces-with-opentelemetry-backends/) diff --git a/src/content/docs/docs/rum/rum-interface.mdx b/src/content/docs/docs/rum/rum-interface.mdx index b0dfaba6..68a3d988 100644 --- a/src/content/docs/docs/rum/rum-interface.mdx +++ b/src/content/docs/docs/rum/rum-interface.mdx @@ -1,109 +1,195 @@ --- title: "RUM Interface" -description: "Explore, analyze, and visualize your Real User Monitoring data in KloudMate." +description: "Find your way around the Real User Monitoring tabs: Overview, Sessions, Pages, Performance, Errors, Releases, Journeys, and Events." +sidebar: + label: "RUM Interface" + order: 3 --- -The RUM Interface is where you process, analyze, and visualize your Real User Monitoring data. It shows statistics, interactive graphs, and detail on how users interact with your application or website. +The RUM interface is where you read what your instrumented apps sent. The tabs run in roughly the order the questions come up: whether anything is wrong, which pages and releases it is wrong on, and what a single visitor actually experienced. -To integrate Real User Monitoring, visit [Instrumentation Guide](../instrumentation-guide/). +To send data in the first place, see the [Instrumentation Guide](../instrumentation-guide/). + +## Pick an application first + +Opening **RUM** lands on the applications index, which lists every web, Android, and iOS application reporting to the workspace. Choose one to open its tabs. Each application is a `serviceName`, set as `applicationName` when the SDK initializes. + +![The RUM applications index listing web and mobile apps with sessions, users, crash rate, and cold start p90](./images/rum-applications-index.png) + +Each row shows the numbers that matter for its platform: **LCP P75** and **Error Rate** on web, **Crash Rate** and **Cold Start P90** on mobile. **Add application** starts the setup flow in the [Instrumentation Guide](../instrumentation-guide/). + +The tab set is the same on every platform, but the contents follow the application. A browser app has Core Web Vitals and no crash rate; a native app has crashes, app hangs, and app-start timings, and **Pages** is titled **Screens**. + +| Tab | Answers | +| --- | --- | +| [Overview](#overview) | Is anything wrong right now | +| [Sessions](#sessions) | What did one visitor experience | +| [Pages](#pages) | Which pages are slow or failing | +| [Performance](#performance) | What is the app waiting on | +| [Errors](#errors) | What is breaking, and how often | +| [Releases](#releases) | Did the last release make things worse | +| [Journeys](#journeys) | Where do people go, and where do they give up | +| [Events](#events) | What are the custom events doing | ## Overview -The overview screen is pre-populated with your RUM data and displays the following: +Start on **Overview** to find out whether anything needs attention right now. It runs top to bottom from the findings worth acting on, through the headline numbers and what is happening over time, to where it is happening and to whom. -- Various RUM metric data, such as Avg. LCP, CLS, and more -- Error list and graphs wrt page views and network requests -- A list of top browsers and related session details -- A map view of all the sessions by country +![The RUM Overview tab: a Needs attention list of findings above Core Web Vitals cards for LCP, INP, CLS, page views, and error rate](./images/rum-overview.png) -![image](./images/rum-interface-1.png) +- **Needs attention** leads with the findings worth acting on, each stating its evidence, such as `70 errors in this window, 3.7x the norm of 19 for the previous 7 days`, and the action that follows from it. +- **Core Web Vitals** gives LCP, INP, and CLS at p75 with the boundary each is judged against, plus page views and error rate. Web applications only. +- **Release health** gives the crash-free rate, on applications that can crash. +- **Volume and failures** plots sessions, page views, and errors over the range. +- **Worst pages by impact**, **Recent errors**, and **Delivery** each link through to the tab that owns them. +- **Audience** breaks sessions down by browser or device, version, **Screen resolution**, and country. Counts are distinct sessions, so one long session firing a thousand spans stays one visit. -### Core Web Vitals Best Practice +![The Audience row with Browser, Browser version, Screen resolution, and Country breakdowns](./images/rum-overview-audience.png) -WebVitals panels have color codes based on industry best practices, so users can identify if the website needs improvement. +### Core Web Vitals thresholds -| **Metrics** | **Green** (Good) | **Orange** (Need improvement) | **Red** (Poor) | -| ------------------------------- | --------------------- | ---------------------------------- | ------------- | -| LCP (Largest Contentful Paint) | ≤ **2.5s** | 2.5s – 4.0s | > **4.0s** | -| CLS (Cumulative Layout Shift) | ≤ **0.1** | 0.1 – 0.25 | > **0.1** | -| INP (Interaction to Next Paint) | ≤ **200ms** | 200ms – 500ms | > **500ms** | -| FID (First Input Delay) | ≤ **100ms** | 100ms – 300ms | > **300ms** | +The vitals panels are colored to Google's own boundaries, so a red panel means the same thing here as it does in Lighthouse or Search Console. + +| Metric | Good | Needs improvement | Poor | +| --- | --- | --- | --- | +| LCP (Largest Contentful Paint) | ≤ 2.5s | 2.5s to 4.0s | > 4.0s | +| INP (Interaction to Next Paint) | ≤ 200ms | 200ms to 500ms | > 500ms | +| CLS (Cumulative Layout Shift) | ≤ 0.1 | 0.1 to 0.25 | > 0.25 | + +INP replaced First Input Delay as a Core Web Vital in March 2024, and the RUM views report it instead. ## Sessions -Click on the **Sessions** tab located right next to the **Overview** tab to navigate to the Sessions screen. This screen displays a list of sessions along with some additional details about each session such as visitor info, errors, actions, and duration of the respective session. +**Sessions** lists individual visits, most recent first. + +![The Sessions tab listing sessions with client, date, ANR signal badges, errors, screens, duration, and a Play button](./images/rum-sessions-list.png) -![image](./images/rum-interface-2.png) +Two columns are worth knowing about. **Signals** badges the crashes, ANRs, and frustration clicks in a session, worst first and with counts, so a session worth opening says so from the list. See [Frustration signals](../session-detail/#frustration-signals). **Play** is disabled where there is no recording, which is the common case. -## Session Replay +Click any row to open the session. See [Session Detail](../session-detail/) for what is inside. -**Session Replay in Real User Monitoring (RUM)** captures and replays real users’ interactions with a website or application. +## Pages -**What it records:** -- User actions such as **mouse movements** , **clicks** , **scrolls** , **keystrokes (masked)**, **page navigation** , and **UI changes** -- Sessions are replayed as **videos** or **step-by-step timelines**, showing exactly how users experienced the application +**Pages** ranks the individual pages of a web app, or the screens of a mobile app, on the measures that decide whether they feel fast. -**How it works with other data:** +![The Pages tab with Views, Errors, Loading P75, LCP P75, INP P75, and CLS P75 columns, and a grouped /product/{param} row](./images/rum-pages.png) -- Works alongside RUM metrics like page load time, errors, and performance timings -- Adds behavioral context to quantitative metrics +Each row gives a page's views, errors, and its load time, LCP, INP, and CLS at p75, banded good, needs-improvement, or poor. Sort by a column to bring the worst to the top, then open a page for its own trend and its errors. -![image](./images/rum-interface-3.png) +**Group similar pages** collapses parameterized URLs, so `/orders/1041` and `/orders/1042` read as one `/product/{param}` row rather than as thousands. **Manage groups** turns a local tweak into a workspace rule. Screens are class names rather than paths, so this is web only. -## Resource Page +Page views that arrived without a page name collect in an **Unattributed** row below the table. -The **Resource Page** in Real User Monitoring provides detailed insights into how individual resources are loaded and executed during a user session. +## Performance -**What it displays:** -- Key performance metrics such as **DNS lookup time** , **Time to First Byte (TTFB)**, and **network request details** -- Helps teams understand **backend and network latency impacts** on user experience +**Performance** covers what the application waits on, and every panel on it is a way into the sessions behind the number. -**Additional insights:** -- Highlights **resource-level errors** , along with **request and response information** -- Shows the **duration of each resource load** -- Enables quick identification of **slow, failing, or inefficient resources** that may affect application performance +On web it opens with **Core Web Vitals**, the same measures Overview reports, at Google's 75th percentile. **TTFB P75** joins them here and names the sample count behind it. -![image](./images/rum-interface-4.png) +![The Performance tab: Core Web Vitals cards for LCP, INP, CLS, and TTFB above a banded distribution chart for LCP](./images/rum-performance.png) -## Deployment Page +The distribution below the cards splits one measure into buckets banded by Google's boundaries. A p75 on its own cannot tell a tail problem from an everyone problem, and this can; selecting a bar opens the sessions inside that bucket. -The **Deployment Page** in Real User Monitoring provides visibility into the impact of each application deployment on real user experience. +- **Slowest pages by impact** ranks pages by how much load time each adds in total, so a busy page that is slightly slow outranks a rare one that is much slower. That is a different question from [Pages](#pages), which ranks by value. +- **Who is slow** splits a measure by browser, device, OS, or country against the application's own percentile. +- **Long tasks** measures blocking time from the 50 ms threshold rather than summing raw durations, so a page full of 55 ms hitches does not outrank one that froze. -**What it tracks:** -- Key web vitals such as **Largest Contentful Paint (LCP)**, **Cumulative Layout Shift (CLS)**, **Interaction to Next Paint (INP)**, and **First Input Delay (FID)** -- **Error rates** and **overall performance metrics** , segmented by **deployment version** +**Network** covers the application's own `fetch` and XHR calls. -**Primary benefits:** -- Helps teams quickly assess whether a **new release has improved** or **degraded** user experience -- Enables **faster detection of regressions** and **confident, data-driven release validation** +![The Network section with Requests, Failed, and response time p50 and p90 cards above an endpoints table sorted by p75](./images/rum-performance-network.png) -![image](./images/rum-interface-5.png) +The endpoints table puts latency and failure rate on one row, grouped without the query string so one route is one row. Sites that send browser resource timing also get a **Resources** section. Panels an application sends nothing for are left out rather than drawn empty. -## Pages +On Android and iOS the tab reads **App start**, **Screen loads**, **Who is slow**, **Rendering**, and the same **Network** section. Cold start and **TTFD** are separate measures there: cold start is the launch, time to fully drawn runs until the first screen is usable, and it is far longer. + +:::note +This tab was called **Resources** on web and **Performance** on mobile. The two merged, and `/rum/resources` now redirects here, so existing bookmarks keep working. +::: + +## Errors + +**Errors** lists every error, crash, and app hang the SDKs reported, with a facet rail for narrowing and an error-volume chart above the list. + +![The RUM Errors tab with All errors, Crashes, and App Hangs toggles, a Narrow by facet rail, an error volume chart, and a grouped error list](./images/rum-errors.png) + +**All errors**, **Crashes**, and **App Hangs** split the list by kind, with **ANRs** in place of App Hangs on Android. **Grouped by issue** collapses identical errors into one row with its counts and first and last seen; switch to **Raw events** when the question is about one specific failure rather than a pattern. + +Opening an error shows its stack, its attributes, and the sessions it happened in, so the error and the session that produced it are one click apart. + +## Releases + +**Releases** answers what the version that just shipped changed. It opens on the current release against the one it replaced, and **Compare** swaps in any other pair. + +![The Releases tab: What this release changed with per-metric deltas, Who is affected by OS version, and Errors that got worse](./images/rum-releases.png) + +- **What this release changed** states each measure as before, after, and the change: `0.18 to 1.25` errors per session, `+595%`. +- **Who is affected** gives the share of sessions that hit an error by browser, OS version, device, or country. It reports how likely a session on that browser is to fail, not how many people use it. +- **Errors that got worse** ranks regressions by per-session rate rather than by count, since a release part way through its rollout has lower counts for everything. **New in \** catches the signatures that did not exist before. +- **Adoption**, **Sessions by version**, and **Version history** cover the rollout itself. -The **Individual Page Performance** view in Real User Monitoring provides detailed insights into how a specific page performs for real users. +Every number on the tab links into the rows behind it, scoped to that version. -**What it tracks:** -- Core web vitals, including **LCP** , **CLS** , **INP** , and **FID** -- **Error occurrences** +:::caution +Releases needs the SDK to send a `version`. Without it the tab reads **Version is not configured in the SDK** and has nothing to compare. See the setup snippet for your platform in the [Instrumentation Guide](../instrumentation-guide/). +::: + +Release data is counted hourly and kept for 180 days rather than read from raw spans, so a release stays comparable long after its sessions have aged out. That matters because you ask "is 4.2 worse than 4.1" weeks after 4.1 stopped shipping. A release too new to have enough sessions reads **Too few sessions to compare yet**. + +Releases replaced the Deployments tab, and `/rum/deployments` redirects here. + +## Journeys + +**Journeys** covers where people go through the app and where they stop. Both views work on one application at a time, so set an **Application** filter in the filter bar first. A path across two sites is two unrelated graphs drawn on top of each other. + +### Pathways -**What it enables:** -- Helps teams evaluate **page-level user experience** -- Identify **performance bottlenecks** and **detect errors impacting usability** -- Enables **targeted optimizations** for critical user journeys +Pathways draws the routes visitors actually took, as a left-to-right graph. Each node names a page with its session count and its exit rate, and each edge carries the number of sessions that took it. -![image](./images/rum-interface-6.png) +![The Journeys Pathways graph showing sessions flowing from the home page through category, search, product, cart, and checkout](./images/rum-journeys-pathways.png) -## Filtering RUM Data +Click a page to start the graph from there, and set how many steps forward it follows and how many sessions a path needs before it is drawn. Pages are grouped the same way as the [Pages](#pages) tab. -You can use the filters available within the sidebar menu of the RUM interface to narrow down the RUM data that you want to monitor or visualize. For further refined filtering, use multiple filters in combination at once. +Clicking a node opens its **Sessions**, the share who **Left here**, and the share who **Hit an error**. A high **Left here** mid-path is where people are giving up. -You can also set a time range for the data to be displayed using the time scale menu located on the top right corner of the screen. +### Funnels -![image](./images/rum-interface-1.jpeg) +Funnels measure conversion across up to five steps. A step is either a **route**, such as `/cart`, or an **Event**, such as `checkout_completed`, and either can match a prefix. + +![A Checkout flow funnel with Conversion, Biggest drop, and Time to convert cards above four event steps and their drop-off rates](./images/rum-journeys-funnels.png) + +**Conversion**, **Biggest drop**, and **Time to convert** sit above the chart, and each step gives its sessions and how many dropped off there. + +**Complete within** sets how long a session has to get from the first step to the last; steps further apart than that do not count as a conversion. **Compare to previous period** reports the change in percentage points against the preceding window. + +Clicking a step opens the sessions that stalled there, with the errors they hit and a **Play** button on every row. Save a funnel to track its conversion over time. + +:::caution +Funnels read raw RUM data, which is kept for a limited time and varies by workspace. A range longer than that retention covers fewer sessions than the dates suggest, and the tab says so when the selected range is long enough for it to matter. +::: + +## Events + +**Events** covers everything sent through [`addEvent()`](../instrumentation-guide/custom-events/). It splits into separate views, because the same events answer different kinds of question: + +- **Analyze** is the default: pick a measure, an aggregation, and something to group by, then render it as a table, over time, a top list, or a distribution. + +![The Events Analyze view as a top list, with Measure, aggregation, and group-by controls above a ranked list of custom event names](./images/rum-events-analyze.png) + +- **Raw events** lists individual events with their payloads. Use it for "which category is added most" or "what did this session do". +- **Totals** reads the hourly rollup, kept for 400 days. It is the only view that can answer "is signup up on last quarter", since the other two run on raw retention. The rollup carries no payload. + +Custom events are counted per session and per user, and can be used as [funnel](#funnels) steps. An event name over the workspace's daily limit is collected under **Over the daily name limit**, which usually means a name was interpolated. See [Keep the event name constant](../instrumentation-guide/custom-events/#keep-the-event-name-constant). + +## Filter the data + +The **Filter** bar narrows every tab by page, browser, version, country, and more. + +What a filter matches differs by tab, and the bar says which: **Matching whole sessions** on Sessions and Journeys, **Matching error events** on Errors, **Matching page views, counted hourly** on Pages. That is the difference between "sessions that visited `/checkout`" and "errors that happened on `/checkout`". + +Not every filter applies everywhere. A route filter does nothing on Journeys, since journeys match whole sessions and a session that opened one page also opened the others on its path. A filter that cannot apply is struck through with the reason and withheld from the queries, rather than dropped silently. :::note - Note that the Filters are designed to last 24 hours based on the selected time range. And a longer time range may slow down your query. +A longer range slows the query down, and the raw RUM data behind Sessions, Journeys, and Raw events is kept for a limited time that varies by workspace. ::: *** @@ -111,5 +197,5 @@ You can also set a time range for the data to be displayed using the time scale ### Related Resources - [What Is Real User Monitoring (RUM)?](../) +- [Session Detail](../session-detail/) - [Instrumentation Guide](../instrumentation-guide/) - diff --git a/src/content/docs/docs/rum/session-detail.mdx b/src/content/docs/docs/rum/session-detail.mdx new file mode 100644 index 00000000..fceea6a2 --- /dev/null +++ b/src/content/docs/docs/rum/session-detail.mdx @@ -0,0 +1,115 @@ +--- +title: "Session Detail" +description: "Read a single RUM session: the timeline, the replay, and the console, network, and error evidence behind it." +sidebar: + label: "Session Detail" + order: 4 +--- + +Opening a session shows one timeline with the replay and the evidence beside it. Everything on the screen runs on the same clock, so clicking an error in the list moves the replay to the moment it happened, and playing the replay moves the list along with it. + +![A RUM session panel: header with an errors badge, the session timeline, the What happened list on the left, and the replay on the right](./images/rum-session-drawer.png) + +## Open a session + +Click any row on the **Sessions** tab. The tab you land on is part of the URL, so a shared session link reopens where it was shared from. Sessions also open from outside RUM: a drop-off table in Journeys, an affected session on an issue, a browser span in Traces. + +## The session header + +Who this was, what they were on, and badges for the crashes, app hangs, errors, and rage clicks in the session. A session with no identity reads **Anonymous session**; see [Identify the user](../instrumentation-guide/web/#identify-the-user) to fill that in. + +Under it, **Entry page load** gives the web vitals for the page the visitor arrived on: **TTFB**, **FCP**, **LCP**, **INP**, and **CLS**, colored good, needs-improvement, or poor. Mobile sessions get **Start type** and **Time to first frame** instead. + +:::note +These are this session's own measurements, not workspace percentiles. The p75 figures on **Overview** and **Pages** answer a different question, and the two will rarely match. +::: + +## The timeline + +The bar below the header runs from 0:00 to the end of the recorded activity, and everything else on the screen moves with it. Click anywhere on it to move the playhead. + +**Segments** are the views the visitor was on, in order. Clicking one scopes **What happened** to that view, and clicking it again clears the scope. + +**Markers** flag the moments worth finding: errors, crashes, app hangs, frustration clicks, and long tasks. Plain clicks and successful requests are not marked, since a mark for every span would fill the track. + +## What happened + +The list on the left walks the session in order. Each row carries the time it happened, what kind of event it was, and enough of the detail to recognize it. Clicking a row seeks the replay; playing the replay highlights the row it has reached. + +**Important**, the default, keeps what explains a session: failures, frustration clicks, long tasks over 200 ms, views, requests, and [custom events](../instrumentation-guide/custom-events/). It drops plain clicks, which are most of a real session and bury the handful of events that matter. **All** shows every span the SDK sent. + +Rows carry an action where there is one to take: an error opens its issue, a request opens its backend trace, and any row expands its raw attributes. For the backend trace to reach past the browser span, see [Correlate RUM traces with OpenTelemetry backends](../correlate-rum-traces-with-opentelemetry-backends/). + +## Frustration signals + +Rage, dead, and error clicks are events in their own right. They appear as badges on the sessions list, as markers on the timeline, and as rows in **What happened**, so a session opened from a badge can point at the click behind it. + +![A session badged Rage click, with a RAGE CLICK row on button#ck_apply among repeated coupon_denied events](./images/rum-session-frustration.png) + +The example above is the shape these usually take: a coupon that will not apply, the same event firing over and over, and a rage click on the button that kept refusing. + +| Signal | What it means | +| --- | --- | +| **Rage click** | Three or more clicks on the same element inside one second. The row reports the real size of the burst, so ten angry clicks read as ten. | +| **Dead click** | A click that produced no DOM change and no navigation within 500 ms. Nothing visibly happened. | +| **Error click** | A click followed by a JavaScript error within one second. | + +A frustration row is titled with the element that was clicked, so it names the button or link that misbehaved. + +A single click gets one verdict. When more than one applies, rage outranks error, which outranks dead, so a burst on a button that also did nothing reads as rage. + +:::note +The browser SDK decides these at the moment of the click, using what the page did next. They are not inferred from the data after the fact, so a session recorded before the SDK shipped this cannot have them backfilled. Detection is on by default and needs no configuration. +::: + +## The evidence tabs + +The right-hand pane switches between six views of the same session: **Replay**, **Console**, **Network**, **Errors**, **Attributes**, and **Raw**. A tab carries a count where it has one, such as `Network (210)` or `Errors (14)`, so a pane worth opening says so before it is opened. + +### Replay + +The replay plays the session's recording at the position the timeline is holding, on web and on mobile. + +Most sessions do not carry a recording. Those read **No replay for this session**, and their **Play** button in the sessions list is disabled with the same explanation. Raising `sessionRecorder.sampleRate` records a larger share. See [Record sessions for replay](../instrumentation-guide/web/#record-sessions-for-replay). + +Playback runs at 1x through 8x, and **Skip inactivity** jumps the quiet stretches, which is most of a long session. + +Seeking a long session takes about a second: recordings load in parallel, and a large one loads only the segment around the playhead. How well that works depends on the SDK version. See [Keep the SDK current](../instrumentation-guide/web/#keep-the-sdk-current). + +### Console + +The browser console for this session, capturing `console.error` and `console.warn` by default. To capture more levels, or none, set the `events.console` option. See [Choose what the SDK captures](../instrumentation-guide/web/#choose-what-the-sdk-captures). + +### Network + +Every `fetch` and `XMLHttpRequest` the session made, drawn as a waterfall, with a summary line giving the request count, how many failed, and the slowest. + +![The Network tab as a waterfall: a GET returning 200 in 99ms and a POST taking 360ms, each with a bar placed against the session](./images/rum-session-network.png) + +The bar is what makes this a waterfall rather than a list in time order: it places each request against the whole session, so three calls stacked in the same second look like the slow page they are. Same-origin calls show the path alone and third-party calls show host and path, with the full URL on hover. Failed requests are red across the row. + +### Errors + +The errors, crashes, and app hangs from this session, each with its type and message. A clean session reads **Nothing failed in this session**. + +![The Errors tab listing two errors with their times and messages](./images/rum-session-errors.png) + +### Attributes + +The environment the session ran in: service, version, browser, OS, device, location, and referrer. + +![The Attributes tab showing service, environment, browser, OS, device type, Screen, Screen Density, platform, and location fields](./images/rum-session-attributes.png) + +**Screen** is the device's screen resolution and **Screen Density** the scale factor. Both are reported for web and mobile, and the same measurement drives the **Screen resolution** breakdown on [Overview](../rum-interface/#overview). + +### Raw + +Every span the session produced, with its full JSON. The place to check an attribute the curated views do not show. + +*** + +### Related Resources + +- [RUM Interface](../rum-interface/) +- [Instrumentation Guide](../instrumentation-guide/) +- [Correlate RUM traces with OpenTelemetry backends](../correlate-rum-traces-with-opentelemetry-backends/) diff --git a/src/content/docs/docs/synthetic/images/maintenance-windows-1.png b/src/content/docs/docs/synthetic/images/maintenance-windows-1.png index 57808c7a..179c628b 100644 Binary files a/src/content/docs/docs/synthetic/images/maintenance-windows-1.png and b/src/content/docs/docs/synthetic/images/maintenance-windows-1.png differ diff --git a/src/content/docs/docs/synthetic/images/maintenance-windows-2.png b/src/content/docs/docs/synthetic/images/maintenance-windows-2.png index ac16ffe3..db58f262 100644 Binary files a/src/content/docs/docs/synthetic/images/maintenance-windows-2.png and b/src/content/docs/docs/synthetic/images/maintenance-windows-2.png differ diff --git a/src/content/docs/docs/synthetic/images/maintenance-windows-3.png b/src/content/docs/docs/synthetic/images/maintenance-windows-3.png index e9177023..10811102 100644 Binary files a/src/content/docs/docs/synthetic/images/maintenance-windows-3.png and b/src/content/docs/docs/synthetic/images/maintenance-windows-3.png differ diff --git a/src/content/docs/docs/synthetic/images/synthetic-incidents-1.png b/src/content/docs/docs/synthetic/images/synthetic-incidents-1.png new file mode 100644 index 00000000..9b298436 Binary files /dev/null and b/src/content/docs/docs/synthetic/images/synthetic-incidents-1.png differ diff --git a/src/content/docs/docs/synthetic/index.mdx b/src/content/docs/docs/synthetic/index.mdx index c97db1a3..dbbf9c82 100644 --- a/src/content/docs/docs/synthetic/index.mdx +++ b/src/content/docs/docs/synthetic/index.mdx @@ -5,6 +5,9 @@ sidebar: label: "Overview" order: 1 --- + +import { LinkCard, CardGrid } from '@astrojs/starlight/components'; + Synthetic Monitoring can proactively test endpoints and services from multiple regions, to detect issues before they impact users. It supports many monitor types, including **HTTP**, **SSL**, **DNS**, **TCP**, **UDP**, **gRPC**, **WebSocket**, **ICMP**, and **Push** ping tests. @@ -33,14 +36,9 @@ If your endpoint or service is behind a firewall or blocks requests from public ## Main Components - -### [Monitors](monitors/) - - - -### [Synthetic-Incidents](synthetic-incidents/) - - - -### [Maintenance Windows](maintenance-windows/) + + + + + diff --git a/src/content/docs/docs/synthetic/monitors/dns-monitor.mdx b/src/content/docs/docs/synthetic/monitors/dns-monitor.mdx index 766f5e81..a024ceca 100644 --- a/src/content/docs/docs/synthetic/monitors/dns-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/dns-monitor.mdx @@ -24,8 +24,9 @@ The DNS Monitor helps ensure your domain’s DNS resolution is functioning corre - **Timeout** Define the maximum amount of time the monitor waits for a DNS response before marking it as failed. - **Monitoring Locations** - Choose one or multiple locations from which the DNS queries will be executed. This helps test DNS resolution from diverse geographic points. - + Choose one or multiple locations from which the DNS queries will be executed. This helps test DNS resolution from diverse geographic points. Selecting multiple locations is recommended to reduce false positives. +- **Downtime Rules — Failed checks before going down** + Set how many consecutive failed checks (about 30 seconds apart) are needed before the monitor counts as down, so a one-off timeout or network blip doesn't open an incident. Unconfirmed failures are ignored completely and never appear in uptime, response time, or incident history. ![image](./images/dns-monitor-verify-dns-resolution-and-records-1.png) diff --git a/src/content/docs/docs/synthetic/monitors/grpc-monitor.mdx b/src/content/docs/docs/synthetic/monitors/grpc-monitor.mdx index cbd03a76..f57cf15b 100644 --- a/src/content/docs/docs/synthetic/monitors/grpc-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/grpc-monitor.mdx @@ -25,6 +25,8 @@ The gRPC Monitor enables continuous health checks for gRPC services by invoking Set the maximum allowed time to receive a response before marking the check as failed. - **Monitoring Locations** Select one or more geographic locations from which the health check requests will be performed. +- **Downtime Rules — Failed checks before going down** + Set how many consecutive failed checks (about 30 seconds apart) are needed before the monitor counts as down, so a one-off timeout or network blip doesn't open an incident. Unconfirmed failures are ignored completely and never appear in uptime, response time, or incident history. ![image](./images/grpc-monitor-check-grpc-service-health-1.png) diff --git a/src/content/docs/docs/synthetic/monitors/http-monitor.mdx b/src/content/docs/docs/synthetic/monitors/http-monitor.mdx index 4573cb18..35a861b6 100644 --- a/src/content/docs/docs/synthetic/monitors/http-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/http-monitor.mdx @@ -23,6 +23,10 @@ The HTTP Monitor allows you to keep track of the availability and performance of Define how frequently the monitor sends requests to the endpoint to check its status and response. - **Timeout** Set the maximum amount of time the monitor will wait to receive a response from the endpoint before considering the check unsuccessful. +- **Monitoring Locations** + Choose one or multiple locations from which the checks will be executed. Selecting multiple locations is recommended to reduce false positives. +- **Downtime Rules — Failed checks before going down** + Set how many consecutive failed checks (about 30 seconds apart) are needed before the monitor counts as down, so a one-off timeout or network blip doesn't open an incident. Unconfirmed failures are ignored completely and never appear in uptime, response time, or incident history. ![image](./images/http-monitor-monitor-web-endpoints-and-apis-1.png) diff --git a/src/content/docs/docs/synthetic/monitors/icmp-monitor.mdx b/src/content/docs/docs/synthetic/monitors/icmp-monitor.mdx index 8a87628a..1f968be9 100644 --- a/src/content/docs/docs/synthetic/monitors/icmp-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/icmp-monitor.mdx @@ -25,6 +25,8 @@ The ICMP Monitor performs ping tests to verify basic network connectivity to hos Set the maximum time to wait for ping responses before marking the check as failed. - **Monitoring Locations** Select one or more geographic locations from which ping tests will originate. +- **Downtime Rules — Failed checks before going down** + Set how many consecutive failed checks (about 30 seconds apart) are needed before the monitor counts as down, so a one-off timeout or network blip doesn't open an incident. Unconfirmed failures are ignored completely and never appear in uptime, response time, or incident history. ![image](./images/icmp-monitor-ping-test-for-basic-connectivity-1.png) diff --git a/src/content/docs/docs/synthetic/monitors/images/dns-monitor-verify-dns-resolution-and-records-1.png b/src/content/docs/docs/synthetic/monitors/images/dns-monitor-verify-dns-resolution-and-records-1.png index b5a17eda..ffb785be 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/dns-monitor-verify-dns-resolution-and-records-1.png and b/src/content/docs/docs/synthetic/monitors/images/dns-monitor-verify-dns-resolution-and-records-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/grpc-monitor-check-grpc-service-health-1.png b/src/content/docs/docs/synthetic/monitors/images/grpc-monitor-check-grpc-service-health-1.png index 94a4771c..aada4791 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/grpc-monitor-check-grpc-service-health-1.png and b/src/content/docs/docs/synthetic/monitors/images/grpc-monitor-check-grpc-service-health-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/http-monitor-monitor-web-endpoints-and-apis-1.png b/src/content/docs/docs/synthetic/monitors/images/http-monitor-monitor-web-endpoints-and-apis-1.png index a33bcf4b..4ce36fa0 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/http-monitor-monitor-web-endpoints-and-apis-1.png and b/src/content/docs/docs/synthetic/monitors/images/http-monitor-monitor-web-endpoints-and-apis-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/icmp-monitor-ping-test-for-basic-connectivity-1.png b/src/content/docs/docs/synthetic/monitors/images/icmp-monitor-ping-test-for-basic-connectivity-1.png index e041ee1c..d2a159df 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/icmp-monitor-ping-test-for-basic-connectivity-1.png and b/src/content/docs/docs/synthetic/monitors/images/icmp-monitor-ping-test-for-basic-connectivity-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/monitors-1.png b/src/content/docs/docs/synthetic/monitors/images/monitors-1.png index 61692f1b..30644692 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/monitors-1.png and b/src/content/docs/docs/synthetic/monitors/images/monitors-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/monitors-2.png b/src/content/docs/docs/synthetic/monitors/images/monitors-2.png index 3a2bfdae..4440d571 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/monitors-2.png and b/src/content/docs/docs/synthetic/monitors/images/monitors-2.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/monitors-3.png b/src/content/docs/docs/synthetic/monitors/images/monitors-3.png index 95b3da8d..62ada0f9 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/monitors-3.png and b/src/content/docs/docs/synthetic/monitors/images/monitors-3.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/monitors-4.png b/src/content/docs/docs/synthetic/monitors/images/monitors-4.png index 63649c87..ed456cc1 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/monitors-4.png and b/src/content/docs/docs/synthetic/monitors/images/monitors-4.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/push-monitor-callback-url-to-confirm-availability-1.png b/src/content/docs/docs/synthetic/monitors/images/push-monitor-callback-url-to-confirm-availability-1.png index 8b476f50..5829c78b 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/push-monitor-callback-url-to-confirm-availability-1.png and b/src/content/docs/docs/synthetic/monitors/images/push-monitor-callback-url-to-confirm-availability-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/ssl-certificate-validity-monitor-check-ssl-certificate-expiration-and-validity-1.png b/src/content/docs/docs/synthetic/monitors/images/ssl-certificate-validity-monitor-check-ssl-certificate-expiration-and-validity-1.png index 3eaf4d1c..2a3b1bb8 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/ssl-certificate-validity-monitor-check-ssl-certificate-expiration-and-validity-1.png and b/src/content/docs/docs/synthetic/monitors/images/ssl-certificate-validity-monitor-check-ssl-certificate-expiration-and-validity-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/tcp-monitor-test-tcp-port-connectivity-1.png b/src/content/docs/docs/synthetic/monitors/images/tcp-monitor-test-tcp-port-connectivity-1.png index a17e2e42..a49eb914 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/tcp-monitor-test-tcp-port-connectivity-1.png and b/src/content/docs/docs/synthetic/monitors/images/tcp-monitor-test-tcp-port-connectivity-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/udp-monitor-monitor-udp-port-availability-1.png b/src/content/docs/docs/synthetic/monitors/images/udp-monitor-monitor-udp-port-availability-1.png index eea101da..e1b3a1a3 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/udp-monitor-monitor-udp-port-availability-1.png and b/src/content/docs/docs/synthetic/monitors/images/udp-monitor-monitor-udp-port-availability-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/images/websocket-monitor-monitor-websocket-connections-1.png b/src/content/docs/docs/synthetic/monitors/images/websocket-monitor-monitor-websocket-connections-1.png index 13f6544c..815ede45 100644 Binary files a/src/content/docs/docs/synthetic/monitors/images/websocket-monitor-monitor-websocket-connections-1.png and b/src/content/docs/docs/synthetic/monitors/images/websocket-monitor-monitor-websocket-connections-1.png differ diff --git a/src/content/docs/docs/synthetic/monitors/index.mdx b/src/content/docs/docs/synthetic/monitors/index.mdx index 01190b35..c8b43f77 100644 --- a/src/content/docs/docs/synthetic/monitors/index.mdx +++ b/src/content/docs/docs/synthetic/monitors/index.mdx @@ -14,8 +14,8 @@ When creating or editing any monitor (except Push), click **Test** next to **Sav - **Type**: Indicates the monitor type (e.g., HTTP, SSL, DNS, TCP, UDP, gRPC, WebSocket, ICMP, Push). - **Target**: Shows the endpoint or host the monitor is targeting (e.g., URL, host\:port, domain). - **Frequency**: Displays the check interval and number of monitoring locations used for each check. -- **Success (24h)**: Shows the success percentage of the monitor over the past 24 hours. -- **Status**: Indicates the current operational status of the monitor (e.g., Up, Down, Warning). +- **Success (7D)**: Shows the success percentage of the monitor over the past 7 days. +- **Status**: Indicates whether the monitor is **Enabled** or **Paused**. The colored dot next to each row shows its current health (up or down), separate from this enabled/paused state. ![image](./images/monitors-1.png) @@ -42,6 +42,12 @@ Clicking any monitor in the list opens its details page, with full performance a ![image](./images/monitors-4.png) +## Downtime Rules and Flapping Control + +Every monitor runs its checks from the **Monitoring Locations** you select. Selecting multiple locations is recommended to reduce false positives caused by an issue local to a single location. + +**Downtime Rules** controls when a failing check actually counts as downtime. Set **Failed checks before going down** to the number of consecutive failed checks (about 30 seconds apart) required before the monitor is marked down — for example, "2 checks in a row" means the monitor only goes down after two failures back to back. This filters out flapping, so a single transient blip or timeout doesn't open an incident. Failures that don't reach this threshold are unconfirmed: they're ignored completely and never appear in uptime, response time, or incident history. + ## Core Uptime and Status Metrics Metrics available to be monitored in Dashboard section. These metrics are applicable to all monitors. diff --git a/src/content/docs/docs/synthetic/monitors/push-monitor.mdx b/src/content/docs/docs/synthetic/monitors/push-monitor.mdx index 9fb136d0..96cd2ce0 100644 --- a/src/content/docs/docs/synthetic/monitors/push-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/push-monitor.mdx @@ -26,7 +26,7 @@ The service or job periodically calls the provided callback URL; if the monitori ## Notification Settings -Configure one or more notification channels (such as email, chat, or incident tooling) to receive alerts when the push monitor does not receive callbacks within the configured interval, indicating the job or service may be down. +Configure one or more notification channels (such as email, chat, or incident tooling) to receive alerts when the push monitor does not receive callbacks within the configured interval, indicating the job or service may be down. To open an incident in Incident Management when a check fails, pick a **KloudMate Incidents** channel. *** diff --git a/src/content/docs/docs/synthetic/monitors/ssl-monitor.mdx b/src/content/docs/docs/synthetic/monitors/ssl-monitor.mdx index c74f9f4a..0ec336dd 100644 --- a/src/content/docs/docs/synthetic/monitors/ssl-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/ssl-monitor.mdx @@ -25,6 +25,8 @@ The SSL Certificate Validity Monitor enables you to track the status of SSL/TLS Specify the maximum time allowed for the monitor to receive a response before marking the check as failed. - **Monitoring Locations** Select one or multiple geographic locations from which the certificate check will be performed. This helps simulate monitoring from different network perspectives. +- **Downtime Rules — Failed checks before going down** + Set how many consecutive failed checks (about 30 seconds apart) are needed before the monitor counts as down, so a one-off timeout or network blip doesn't open an incident. Unconfirmed failures are ignored completely and never appear in uptime, response time, or incident history. ![image](./images/ssl-certificate-validity-monitor-check-ssl-certificate-expiration-and-validity-1.png) diff --git a/src/content/docs/docs/synthetic/monitors/tcp-monitor.mdx b/src/content/docs/docs/synthetic/monitors/tcp-monitor.mdx index 6d7d9832..16e80d64 100644 --- a/src/content/docs/docs/synthetic/monitors/tcp-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/tcp-monitor.mdx @@ -25,6 +25,8 @@ The TCP Monitor tests the availability and responsiveness of a specific TCP host Set the maximum time the monitor will wait for a response before considering the check unsuccessful. - **Monitoring Locations** Select one or more geographic monitoring locations from which the TCP checks will be performed. +- **Downtime Rules — Failed checks before going down** + Set how many consecutive failed checks (about 30 seconds apart) are needed before the monitor counts as down, so a one-off timeout or network blip doesn't open an incident. Unconfirmed failures are ignored completely and never appear in uptime, response time, or incident history. ![image](./images/tcp-monitor-test-tcp-port-connectivity-1.png) diff --git a/src/content/docs/docs/synthetic/monitors/udp-monitor.mdx b/src/content/docs/docs/synthetic/monitors/udp-monitor.mdx index 62a34cce..84e59b58 100644 --- a/src/content/docs/docs/synthetic/monitors/udp-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/udp-monitor.mdx @@ -25,6 +25,8 @@ The UDP Monitor checks the availability of a specific UDP host and port by sendi Define the maximum time the monitor waits for a response before marking the check as failed. - **Monitoring Locations** Select one or multiple locations from where the UDP checks will be performed. +- **Downtime Rules — Failed checks before going down** + Set how many consecutive failed checks (about 30 seconds apart) are needed before the monitor counts as down, so a one-off timeout or network blip doesn't open an incident. Unconfirmed failures are ignored completely and never appear in uptime, response time, or incident history. ![image](./images/udp-monitor-monitor-udp-port-availability-1.png) diff --git a/src/content/docs/docs/synthetic/monitors/websocket-monitor.mdx b/src/content/docs/docs/synthetic/monitors/websocket-monitor.mdx index 53b3ce85..9fcb4324 100644 --- a/src/content/docs/docs/synthetic/monitors/websocket-monitor.mdx +++ b/src/content/docs/docs/synthetic/monitors/websocket-monitor.mdx @@ -25,6 +25,8 @@ The WebSocket Monitor verifies the availability and performance of WebSocket end Define the maximum time allowed for the monitor to complete the connection and response validation. - **Monitoring Locations** Choose one or more geographic locations from which the WebSocket checks will be executed. +- **Downtime Rules — Failed checks before going down** + Set how many consecutive failed checks (about 30 seconds apart) are needed before the monitor counts as down, so a one-off timeout or network blip doesn't open an incident. Unconfirmed failures are ignored completely and never appear in uptime, response time, or incident history. ![image](./images/websocket-monitor-monitor-websocket-connections-1.png) diff --git a/src/content/docs/docs/synthetic/synthetic-incidents.mdx b/src/content/docs/docs/synthetic/synthetic-incidents.mdx index 078c12d1..c290304c 100644 --- a/src/content/docs/docs/synthetic/synthetic-incidents.mdx +++ b/src/content/docs/docs/synthetic/synthetic-incidents.mdx @@ -16,7 +16,11 @@ The page displays a table summarizing all incidents for quick analysis: Timestamp when the incident was first detected. - **Resolved At** Timestamp when the incident was resolved, or indicates if still ongoing. -- **Duration** Total time period the incident remained active.*** +- **Duration** Total time period the incident remained active. + +![image](./images/synthetic-incidents-1.png) + +*** ## Related Resources