Agent Skills › MapleTechLabs/maple

MapleTechLabs/maple

GitHub

ClickHouse架构顾问技能,基于工作负载类型提供决策框架。通过读取规则文件并引用官方文档,生成带来源分类(官方/推导/经验)的结构化建议,涵盖实时摄入、时序、关联等场景,确保设计符合最佳实践。

33 skills 1,535

Install All Skills

npx skills add MapleTechLabs/maple --all -g -y
More Options

List skills in collection

npx skills add MapleTechLabs/maple --list

Skills in Collection (33)

ClickHouse架构顾问技能,基于工作负载类型提供决策框架。通过读取规则文件并引用官方文档,生成带来源分类(官方/推导/经验)的结构化建议,涵盖实时摄入、时序、关联等场景,确保设计符合最佳实践。
设计ClickHouse系统架构 选择数据摄入或建模模式 将最佳实践转化为特定工作负载的系统设计
.agents/skills/clickhouse-architecture-advisor/SKILL.md
npx skills add MapleTechLabs/maple --skill clickhouse-architecture-advisor -g -y
SKILL.md
Frontmatter
{
    "name": "clickhouse-architecture-advisor",
    "license": "Apache-2.0",
    "metadata": {
        "author": "ClickHouse Inc",
        "version": "0.1.0"
    },
    "description": "MUST USE when designing ClickHouse architectures, selecting between ingestion or modeling patterns, or translating best practices into workload-specific system designs. Complements clickhouse-best-practices with decision frameworks and explicit provenance labels."
}

ClickHouse Architecture Advisor

This skill adds workload-aware architecture decisioning on top of clickhouse-best-practices.

Official docs remain the source of truth. This skill must always prefer official ClickHouse documentation when available.

Required behavior

Before producing recommendations:

  1. Identify the workload shape
    • observability
    • security / SIEM
    • product analytics
    • IoT / telemetry
    • market data / financial services
    • mixed OLAP with point-lookups
  2. Read the relevant decision rule files in rules/
  3. Use mappings/doc_links.yaml to attach official documentation
  4. Classify every recommendation as:
    • official
    • derived
    • field
  5. Never present field guidance as official guidance
  6. If a recommendation is uncertain, say so explicitly

Provenance rules

official

Use this when the recommendation is directly backed by official docs.

derived

Use this when the recommendation is not stated verbatim in docs but follows logically from documented ClickHouse behavior.

field

Use this only for experience-based guidance that may be situational. When using field, include:

  • a disclaimer that the advice is heuristic
  • a relevant official doc if one partially applies
  • the reason the advice depends on workload context

Read these rule files by scenario

Real-time ingestion design

  1. rules/decision-ingestion-strategy.md
  2. rules/decision-real-time-preaggregation.md
  3. Relevant best-practices insert rules

Time-series and retention design

  1. rules/decision-partitioning-timeseries.md
  2. Relevant best-practices schema partition rules

Enrichment and dimension lookups

  1. rules/decision-join-enrichment.md
  2. Relevant best-practices query join rules

Mutable state / late-arriving events

  1. rules/decision-late-arriving-upserts.md
  2. Relevant best-practices mutation avoidance rules

Output format

Structure responses like this:

## Workload Summary

- workload:
- latency target:
- data shape:
- primary query patterns:
- operational constraints:

## Key Decisions

- ...
- ...

## Recommendations

### <Recommendation title>

**What**
...

**Why**
...

**How**
...

**Category**
official | derived | field

**Confidence**
high | medium | heuristic

**Source**

- doc link(s)

**Validation**

- concrete SQL, metric, or smoke test

Architecture-specific guidance

Prefer decision frameworks over generic advice. Good responses should:

  • explain tradeoffs
  • identify the likely operating bottleneck
  • separate immediate actions from structural redesign
  • provide target architecture patterns, not just isolated settings

Full reference

See AGENTS.md for the compiled version and examples/ for sample outputs.

用于审查ClickHouse模式、查询和配置的技能,包含28条规则。按优先级检查规则文件并引用具体规则,确保提供符合ClickHouse特性的优化建议,涵盖建表、查询及数据插入策略。
审查ClickHouse表结构或CREATE/ALTER TABLE语句 优化或审查ClickHouse SELECT查询语句 评估ClickHouse数据导入或更新策略
.agents/skills/clickhouse-best-practices/SKILL.md
npx skills add MapleTechLabs/maple --skill clickhouse-best-practices -g -y
SKILL.md
Frontmatter
{
    "name": "clickhouse-best-practices",
    "license": "Apache-2.0",
    "metadata": {
        "author": "ClickHouse Inc",
        "version": "0.3.0"
    },
    "description": "MUST USE when reviewing ClickHouse schemas, queries, or configurations. Contains 28 rules that MUST be checked before providing recommendations. Always read relevant rule files and cite specific rules in responses."
}

ClickHouse Best Practices

Comprehensive guidance for ClickHouse covering schema design, query optimization, and data ingestion. Contains 28 rules across 3 main categories (schema, query, insert), prioritized by impact.

Official docs: ClickHouse Best Practices

IMPORTANT: How to Apply This Skill

Before answering ClickHouse questions, follow this priority order:

  1. Check for applicable rules in the rules/ directory
  2. If rules exist: Apply them and cite them in your response using "Per rule-name..."
  3. If no rule exists: Use the LLM's ClickHouse knowledge or search documentation
  4. If uncertain: Use web search for current best practices
  5. Always cite your source: rule name, "general ClickHouse guidance", or URL

Why rules take priority: ClickHouse has specific behaviors (columnar storage, sparse indexes, merge tree mechanics) where general database intuition can be misleading. The rules encode validated, ClickHouse-specific guidance.

For Formal Reviews

When performing a formal review of schemas, queries, or data ingestion:


Review Procedures

For Schema Reviews (CREATE TABLE, ALTER TABLE)

Read these rule files in order:

  1. rules/schema-pk-plan-before-creation.md - ORDER BY is immutable
  2. rules/schema-pk-cardinality-order.md - Column ordering in keys
  3. rules/schema-pk-prioritize-filters.md - Filter column inclusion
  4. rules/schema-types-native-types.md - Proper type selection
  5. rules/schema-types-minimize-bitwidth.md - Numeric type sizing
  6. rules/schema-types-lowcardinality.md - LowCardinality usage
  7. rules/schema-types-avoid-nullable.md - Nullable vs DEFAULT
  8. rules/schema-partition-low-cardinality.md - Partition count limits
  9. rules/schema-partition-lifecycle.md - Partitioning purpose

Check for:

  • PRIMARY KEY / ORDER BY column order (low-to-high cardinality)
  • Data types match actual data ranges
  • LowCardinality applied to appropriate string columns
  • Partition key cardinality bounded (100-1,000 values)
  • ReplacingMergeTree has version column if used

For Query Reviews (SELECT, JOIN, aggregations)

Read these rule files:

  1. rules/query-join-choose-algorithm.md - Algorithm selection
  2. rules/query-join-filter-before.md - Pre-join filtering
  3. rules/query-join-use-any.md - ANY vs regular JOIN
  4. rules/query-index-skipping-indices.md - Secondary index usage
  5. rules/schema-pk-filter-on-orderby.md - Filter alignment with ORDER BY

Check for:

  • Filters use ORDER BY prefix columns
  • JOINs filter tables before joining (not after)
  • Correct JOIN algorithm for table sizes
  • Skipping indices for non-ORDER BY filter columns

For Insert Strategy Reviews (data ingestion, updates, deletes)

Read these rule files:

  1. rules/insert-batch-size.md - Batch sizing requirements
  2. rules/insert-mutation-avoid-update.md - UPDATE alternatives
  3. rules/insert-mutation-avoid-delete.md - DELETE alternatives
  4. rules/insert-async-small-batches.md - Async insert usage
  5. rules/insert-optimize-avoid-final.md - OPTIMIZE TABLE risks

Check for:

  • Batch size 10K-100K rows per INSERT
  • No ALTER TABLE UPDATE for frequent changes
  • ReplacingMergeTree or CollapsingMergeTree for update patterns
  • Async inserts enabled for high-frequency small batches

Output Format

Structure your response as follows:

## Rules Checked
- `rule-name-1` - Compliant / Violation found
- `rule-name-2` - Compliant / Violation found
...

## Findings

### Violations
- **`rule-name`**: Description of the issue
  - Current: [what the code does]
  - Required: [what it should do]
  - Fix: [specific correction]

### Compliant
- `rule-name`: Brief note on why it's correct

## Recommendations
[Prioritized list of changes, citing rules]

Rule Categories by Priority

Priority Category Impact Prefix Rule Count
1 Primary Key Selection CRITICAL schema-pk- 4
2 Data Type Selection CRITICAL schema-types- 5
3 JOIN Optimization CRITICAL query-join- 5
4 Insert Batching CRITICAL insert-batch- 1
5 Mutation Avoidance CRITICAL insert-mutation- 2
6 Partitioning Strategy HIGH schema-partition- 4
7 Skipping Indices HIGH query-index- 1
8 Materialized Views HIGH query-mv- 2
9 Async Inserts HIGH insert-async- 2
10 OPTIMIZE Avoidance HIGH insert-optimize- 1
11 JSON Usage MEDIUM schema-json- 1

Quick Reference

Schema Design - Primary Key (CRITICAL)

  • schema-pk-plan-before-creation - Plan ORDER BY before table creation (immutable)
  • schema-pk-cardinality-order - Order columns low-to-high cardinality
  • schema-pk-prioritize-filters - Include frequently filtered columns
  • schema-pk-filter-on-orderby - Query filters must use ORDER BY prefix

Schema Design - Data Types (CRITICAL)

  • schema-types-native-types - Use native types, not String for everything
  • schema-types-minimize-bitwidth - Use smallest numeric type that fits
  • schema-types-lowcardinality - LowCardinality for <10K unique strings
  • schema-types-enum - Enum for finite value sets with validation
  • schema-types-avoid-nullable - Avoid Nullable; use DEFAULT instead

Schema Design - Partitioning (HIGH)

  • schema-partition-low-cardinality - Keep partition count 100-1,000
  • schema-partition-lifecycle - Use partitioning for data lifecycle, not queries
  • schema-partition-query-tradeoffs - Understand partition pruning trade-offs
  • schema-partition-start-without - Consider starting without partitioning

Schema Design - JSON (MEDIUM)

  • schema-json-when-to-use - JSON for dynamic schemas; typed columns for known

Query Optimization - JOINs (CRITICAL)

  • query-join-choose-algorithm - Select algorithm based on table sizes
  • query-join-use-any - ANY JOIN when only one match needed
  • query-join-filter-before - Filter tables before joining
  • query-join-consider-alternatives - Dictionaries/denormalization vs JOIN
  • query-join-null-handling - join_use_nulls=0 for default values

Query Optimization - Indices (HIGH)

  • query-index-skipping-indices - Skipping indices for non-ORDER BY filters

Query Optimization - Materialized Views (HIGH)

  • query-mv-incremental - Incremental MVs for real-time aggregations
  • query-mv-refreshable - Refreshable MVs for complex joins

Insert Strategy - Batching (CRITICAL)

  • insert-batch-size - Batch 10K-100K rows per INSERT

Insert Strategy - Async (HIGH)

  • insert-async-small-batches - Async inserts for high-frequency small batches
  • insert-format-native - Native format for best performance

Insert Strategy - Mutations (CRITICAL)

  • insert-mutation-avoid-update - ReplacingMergeTree instead of ALTER UPDATE
  • insert-mutation-avoid-delete - Lightweight DELETE or DROP PARTITION

Insert Strategy - Optimization (HIGH)

  • insert-optimize-avoid-final - Let background merges work

When to Apply

This skill activates when you encounter:

  • CREATE TABLE statements
  • ALTER TABLE modifications
  • ORDER BY or PRIMARY KEY discussions
  • Data type selection questions
  • Slow query troubleshooting
  • JOIN optimization requests
  • Data ingestion pipeline design
  • Update/delete strategy questions
  • ReplacingMergeTree or other specialized engine usage
  • Partitioning strategy decisions

Rule File Structure

Each rule file in rules/ contains:

  • YAML frontmatter: title, impact level, tags
  • Brief explanation: Why this rule matters
  • Incorrect example: Anti-pattern with explanation
  • Correct example: Best practice with explanation
  • Additional context: Trade-offs, when to apply, references

Full Compiled Document

For the complete guide with all rules expanded inline: AGENTS.md

Use AGENTS.md when you need to check multiple rules quickly without reading individual files.

指导用户使用clickhousectl将ClickHouse应用部署至云端或生产环境。涵盖账户注册、CLI安装与认证(OAuth/API Key)、服务创建及连接配置,支持从本地迁移至托管云服务的完整流程。
用户希望将ClickHouse部署到云端 用户准备将ClickHouse投入生产环境 用户希望使用ClickHouse Cloud托管服务 用户需要从本地ClickHouse迁移至ClickHouse Cloud
.agents/skills/clickhousectl-cloud-deploy/SKILL.md
npx skills add MapleTechLabs/maple --skill clickhousectl-cloud-deploy -g -y
SKILL.md
Frontmatter
{
    "name": "clickhousectl-cloud-deploy",
    "license": "Apache-2.0",
    "metadata": {
        "author": "ClickHouse Inc",
        "version": "0.1.0"
    },
    "description": "Use when a user wants to deploy ClickHouse to the cloud, go to production, use ClickHouse Cloud, host a managed ClickHouse service, or migrate from a local ClickHouse setup to ClickHouse Cloud."
}

Deploy to ClickHouse Cloud

This skill walks through deploying to ClickHouse Cloud using clickhousectl. It covers account setup, CLI authentication, service creation, schema migration, and connecting your application. Follow these steps in order.

When to Apply

Use this skill when the user wants to:

  • Deploy their ClickHouse application to production
  • Host ClickHouse as a managed cloud service
  • Migrate from a local ClickHouse setup to ClickHouse Cloud
  • Create a ClickHouse Cloud service
  • Set up ClickHouse Cloud for the first time

Step 1: Sign up for ClickHouse Cloud

Before using any cloud commands, the user needs a ClickHouse Cloud account.

Ask the user: "Do you already have a ClickHouse Cloud account?"

If they do not have an account, explain:

ClickHouse Cloud is a fully managed service that runs ClickHouse for you — no infrastructure to maintain, automatic scaling, backups, and upgrades included. There's a free trial so you can get started without a credit card.

To create an account, go to: https://clickhouse.cloud

Sign up with your email, Google, or GitHub account. Once you're in the console, let me know and we'll continue with the next step.

Wait for the user to confirm they have signed up or already have an account before proceeding.


Step 2: Authenticate the CLI

First, ensure clickhousectl is installed. Check with:

which clickhousectl

If not found, install it:

curl -fsSL https://clickhouse.com/cli | sh

Now authenticate. There are two options — choose based on the situation:

Important: OAuth login is read-only. Browser login (Option A) grants read-only access — you can list organizations, services, and query existing services, but you cannot create, modify, or delete services. Any destructive or mutating cloud operation (service creation, deletion, scaling, etc.) requires API key authentication (Option B). If this workflow involves creating or changing cloud resources, you must use Option B.

Option A: Browser login (read-only access)

Use this when a human is available to open a browser and you only need to inspect existing cloud resources (list orgs, list services, query data). It uses OAuth device flow — no API keys needed.

Instruct the user to run:

clickhousectl cloud login

This prints a URL and a code. The user opens the URL in their browser, confirms the code, and logs in with their ClickHouse Cloud account. The CLI automatically receives credentials once the browser flow completes.

This is sufficient for steps that only read data (e.g., cloud org list, cloud service get, cloud service client). For any step that creates or modifies resources, switch to API key auth.

Option B: API key auth (required for destructive actions)

Use this option when creating, modifying, or deleting cloud resources (e.g., cloud service create, cloud service delete). Also use this for headless/CI environments where no browser is available. Both --api-key and --api-secret are required — if the user provides one without the other, tell them both are needed.

clickhousectl cloud login --api-key <key> --api-secret <secret>

If the user doesn't have API keys yet, guide them to create one:

In the ClickHouse Cloud console:

  1. Click the gear icon (Settings) in the left sidebar
  2. Go to API Keys
  3. Click Create API Key
  4. Give it a name (e.g., "cli") and select the Admin role
  5. Click Generate API Key
  6. Copy both the Key ID and the Key Secret — the secret is only shown once

To verify authentication works:

clickhousectl cloud org list

This should return the user's organization.


Step 3: Create a cloud service

Requires API key auth. Service creation is a mutating operation. If you authenticated with browser login (Option A) in Step 2, you must re-authenticate with API key auth (Option B) before proceeding.

Create a new ClickHouse Cloud service:

clickhousectl cloud service create --name <service-name>

The output includes the service ID and default user password — note it for subsequent commands.

Wait for the service to be ready. After creation, the service takes a moment to provision. Check its status:

clickhousectl cloud service get <service-id>

You can grep the "state" field to see if it is "running".


Step 4: Migrate schemas

If the user has local table definitions (e.g., from using the clickhousectl-local-dev skill), migrate them to the cloud service.

Use cloud service client to run queries against the cloud service — it looks up the endpoint, port, and TLS settings automatically. You just need the service name (or --id) and the password from step 3.

Read the local schema files from clickhouse/tables/ and apply each one to the cloud service:

clickhousectl cloud service client --name <service-name> \
  --queries-file clickhouse/tables/<table>.sql

Apply them in dependency order — tables referenced by materialized views should be created first.

Also apply materialized views if they exist:

clickhousectl cloud service client --name <service-name> \
  --queries-file clickhouse/materialized_views/<view>.sql

The --user flag defaults to default. If the user has a different database user, pass --user <username>.


Step 5: Verify the deployment

Connect to the cloud service and confirm tables exist:

clickhousectl cloud service client --name <service-name> --query "SHOW TABLES"

Run a test query to confirm the schema is correct:

clickhousectl cloud service client --name <service-name> --query "DESCRIBE TABLE <table-name>"

Step 6: Update application config

Retrieve the service endpoint for the user's application config:

clickhousectl cloud service get <service-id>

Provide the user with the connection details:

  • Host: from the service get output
  • Port: 8443 for HTTPS / 9440 for native TLS
  • User: default
  • Password: the password from step 3 (service creation)
  • SSL/TLS: required (always enabled on Cloud)

Example connection strings (adapt to the user's language/framework):

Python (clickhouse-connect):

import clickhouse_connect

client = clickhouse_connect.get_client(
    host='<cloud-host>',
    port=8443,
    username='default',
    password='<password>',
    secure=True
)

Node.js (@clickhouse/client):

import { createClient } from "@clickhouse/client"

const client = createClient({
	url: "https://<cloud-host>:8443",
	username: "default",
	password: "<password>",
})

Go (clickhouse-go):

conn, err := clickhouse.Open(&clickhouse.Options{
    Addr: []string{"<cloud-host>:9440"},
    Auth: clickhouse.Auth{
        Username: "default",
        Password: "<password>",
    },
    TLS: &tls.Config{},
})

Suggest the user store the password in an environment variable or secrets manager rather than hardcoding it.

Suggest the user should not use the default user in production. A user should be created just for their app.

指导用户从零开始在本地搭建 ClickHouse 开发环境。涵盖安装 clickhousectl 和 ClickHouse、初始化项目结构、启动本地服务及创建数据表等全流程,适用于需要分析型数据库的应用开发与原型实验。
搭建本地 ClickHouse 开发环境 安装 ClickHouse 服务器 创建本地 ClickHouse 表结构 使用 clickhousectl 进行本地测试
.agents/skills/clickhousectl-local-dev/SKILL.md
npx skills add MapleTechLabs/maple --skill clickhousectl-local-dev -g -y
SKILL.md
Frontmatter
{
    "name": "clickhousectl-local-dev",
    "license": "Apache-2.0",
    "metadata": {
        "author": "ClickHouse Inc",
        "version": "0.1.0"
    },
    "description": "Use when a user wants to build an application with ClickHouse, set up a local ClickHouse development environment, install ClickHouse, create a local server, create tables, or start developing with ClickHouse. Covers the full flow from zero to a working local ClickHouse setup."
}

Local ClickHouse Development Setup

This skill walks through setting up a complete local ClickHouse development environment using clickhousectl. Follow these steps in order.

When to Apply

Use this skill when the user wants to:

  • Build an application that needs an analytical database or ClickHouse specifically
  • Set up a local ClickHouse instance for development
  • Install ClickHouse on their machine
  • Create tables and start querying ClickHouse locally
  • Prototype or experiment with ClickHouse

Step 1: Install clickhousectl

Check if clickhousectl is already available:

which clickhousectl

If not found, install it:

curl -fsSL https://clickhouse.com/cli | sh

This installs clickhousectl to ~/.local/bin/clickhousectl and creates a chctl alias.

If the command is still not found after install: The user may need to add ~/.local/bin to their PATH or open a new terminal session. Suggest:

export PATH="$HOME/.local/bin:$PATH"

Step 2: Install ClickHouse

Install the latest stable ClickHouse version:

clickhousectl local install stable

This downloads the ClickHouse binary to ~/.clickhouse/versions/. The binary is shared across projects so it only needs to be downloaded once.

Alternative version specifiers (use if the user has a specific need):

  • lts — latest long-term support release
  • 25.12 — latest patch of a specific minor version
  • 25.12.5.44 — exact version

Set the installed version as the default:

clickhousectl local use stable

Step 3: Initialize the project

From the user's project root directory:

clickhousectl local init

This creates a standard folder structure:

clickhouse/
  tables/                 # CREATE TABLE statements
  materialized_views/     # Materialized view definitions
  queries/                # Saved queries
  seed/                   # Seed data / INSERT statements

Note: This step is optional. If the user already has their own folder structure for SQL files, skip this and adapt the later steps to use their paths.


Step 4: Start a local server

clickhousectl local server start --name <name>

This starts a ClickHouse server in the background. Server data is stored in .clickhouse/servers/<anem>/data/ within the project directory.

To check running servers and see their exposed ports:

clickhousectl local server list

Step 5: Create the schema

Based on the user's application requirements, write CREATE TABLE SQL files.

Write each table definition to its own file in clickhouse/tables/:

# Example: clickhouse/tables/events.sql
CREATE TABLE IF NOT EXISTS events (
    timestamp DateTime,
    user_id UInt32,
    event_type LowCardinality(String),
    properties String
)
ENGINE = MergeTree()
ORDER BY (event_type, timestamp)

When designing schemas, if the clickhouse-best-practices skill is available, consult it for guidance on ORDER BY column selection, data types, and partitioning.

Apply the schema to the running server:

clickhousectl local client --name <name> --queries-file clickhouse/tables/events.sql

Step 6: Seed data (optional)

If the user needs sample data for development, write INSERT statements to clickhouse/seed/:

# Example: clickhouse/seed/events.sql
INSERT INTO events (timestamp, user_id, event_type, properties) VALUES
    ('2024-01-01 00:00:00', 1, 'page_view', '{"page": "/home"}'),
    ('2024-01-01 00:01:00', 2, 'click', '{"button": "signup"}');

Apply seed data:

clickhousectl local client --name <name> --queries-file clickhouse/seed/events.sql

Step 7: Verify the setup

Confirm tables were created:

clickhousectl local client --name <name> --query "SHOW TABLES"

Run a test query:

clickhousectl local client --name <name> --query "SELECT count() FROM events"

If the user wants to use a managed ClickHouse service, use the clickhousectl-cloud-deploy skill to help the user deploy to ClickHouse Cloud.

提供COSS UI粒子组件索引,帮助用户快速查找并获取可复用的UI模式源码。支持通过描述搜索组件类型,获取JSON URL以复制粘贴代码,适用于实现表单、对话框等UI功能。
需要快速构建UI界面 寻找现成的UI组件示例 实现特定交互效果如表单验证或对话框
.agents/skills/coss-particles/SKILL.md
npx skills add MapleTechLabs/maple --skill coss-particles -g -y
SKILL.md
Frontmatter
{
    "name": "coss-particles",
    "license": "MIT",
    "metadata": {
        "author": "cosscom"
    },
    "description": "Index of all COSS UI particle examples. Use when implementing UI features to find copy-paste-ready component patterns built on coss primitives. Each particle has a description and a JSON URL for easy installation.",
    "compatibility": "Requires coss UI components installed in the project."
}

COSS UI Particles Index

Particles are copy-paste-ready UI patterns built on coss primitives. Browse them visually at https://coss.com/ui/particles.

How to use this skill

  1. Describe the UI you need (e.g. "a form with validation", "a dialog with a form inside", "tabs with icons").
  2. Search this index for matching particles by component type and description.
  3. Fetch the JSON URL to get the full source code of the particle.
  4. Adapt the particle code to your needs.

JSON URL pattern

Each particle has a JSON manifest at:

https://coss.com/ui/r/<particle-name>.json

For example: https://coss.com/ui/r/p-accordion-1.json

Source code

Particle source files live in this repo at apps/ui/registry/default/particles/.

Updating this index

Run the generator script from the coss repo root:

node apps/ui/scripts/generate-particle-index.cjs

Total: 480 particles across 52 component types

Component types


accordion

  • Basic accordion | JSON
  • Accordion with one panel open | JSON
  • Accordion allowing multiple panels open | JSON
  • Controlled accordion | JSON

alert

  • Basic alert | JSON
  • Alert with icon | JSON
  • Alert with icon and action buttons | JSON
  • Info alert | JSON
  • Success alert | JSON
  • Warning alert | JSON
  • Error alert | JSON

alert-dialog

  • Alert dialog | JSON
  • Alert dialog with bare footer | JSON

autocomplete

  • Basic autocomplete | JSON
  • Disabled autocomplete | JSON
  • Small autocomplete | JSON
  • Large autocomplete | JSON
  • Autocomplete with label | JSON
  • Autocomplete autofilling the input with the highlighted item | JSON
  • Autocomplete auto highlighting the first option | JSON
  • Autocomplete with clear button | JSON
  • Autocomplete with trigger and clear buttons | JSON
  • Autocomplete with grouped items | JSON
  • Autocomplete with limited number of results | JSON
  • Autocomplete with async items loading | JSON
  • Autocomplete form | JSON
  • Autocomplete form | JSON
  • Pill-shaped autocomplete | JSON

avatar

  • Avatar with image and fallback | JSON
  • Fallback-only avatar | JSON
  • Avatars with different sizes | JSON
  • Avatars with different radii | JSON
  • Overlapping avatar group | JSON
  • Avatar with user icon fallback | JSON
  • Avatar with emerald status dot | JSON
  • Avatar with muted status dot | JSON
  • Rounded avatar with top-right emerald status | JSON
  • Avatar with notification badge | JSON
  • Rounded avatar with notification badge | JSON
  • Avatar with verified badge | JSON
  • Small overlapping avatar group | JSON
  • Large overlapping avatar group | JSON

badge

  • Basic badge | JSON
  • Outline badge | JSON
  • Secondary badge | JSON
  • Destructive badge | JSON
  • Info badge | JSON
  • Success badge | JSON
  • Warning badge | JSON
  • Error badge | JSON
  • Small badge | JSON
  • Large badge | JSON
  • Badge with icon | JSON
  • Badge with link | JSON
  • Badge with count | JSON
  • Full rounded badge (pill) | JSON
  • Badge with number after text | JSON
  • Status badge - Paid | JSON
  • Status badge - Pending | JSON
  • Status badge - Failed | JSON
  • Selectable badge with checkbox | JSON
  • Removable badge | JSON

breadcrumb

  • Breadcrumb with menu example | JSON
  • Breadcrumb with custom separator | JSON
  • Breadcrumb with home icon for home link only | JSON
  • Breadcrumb with folders icon menu | JSON
  • Breadcrumb with icons before text | JSON
  • Breadcrumb with dot separators | JSON
  • Breadcrumb with select dropdown | JSON

button

  • Default button | JSON
  • Outline button | JSON
  • Secondary button | JSON
  • Destructive button | JSON
  • Destructive outline button | JSON
  • Ghost button | JSON
  • Link button | JSON
  • Extra-small button | JSON
  • Small button | JSON
  • Large button | JSON
  • Extra-large button | JSON
  • Disabled button | JSON
  • Icon button | JSON
  • Small icon button | JSON
  • Large icon button | JSON
  • Button with icon | JSON
  • Link rendered as button | JSON
  • Button using the built-in loading prop | JSON
  • Custom loading button with manual Spinner | JSON
  • Expandable show more/less toggle button | JSON
  • Back link button with chevron | JSON
  • Card-style button with heading and description | JSON
  • Directional pad control buttons | JSON
  • Outline like button with count | JSON
  • Social login icon buttons | JSON
  • Star button with count badge | JSON
  • Button group with QR code icon and sign in | JSON
  • Button with avatar | JSON
  • Pill-shaped button with rounded-full styling | JSON
  • Button with animated arrow on hover | JSON
  • Button with keyboard shortcut indicator | JSON
  • Button with notification badge | JSON
  • Paired buttons (Cancel/Save) | JSON
  • Button with animated status dot | JSON
  • Icon-only copy button with feedback | JSON
  • Copy button with feedback | JSON
  • Rotating icon button (FAB-style toggle) | JSON
  • Hamburger menu button with animated icon | JSON
  • Download button with progress and cancel action | JSON
  • Social login buttons (Google, X, GitHub) | JSON

calendar

  • Basic calendar | JSON
  • Calendar with date range selection | JSON
  • Calendar with month/year dropdown navigation | JSON
  • Calendar with custom Select dropdown for month/year | JSON
  • Calendar with Combobox dropdown for month/year | JSON
  • Calendar with disabled dates | JSON
  • Calendar with multiple date selection | JSON
  • Calendar with custom cell size | JSON
  • Calendar with rounded day buttons | JSON
  • Calendar with rounded range selection style | JSON
  • Calendar with right-aligned navigation | JSON
  • Calendar with week numbers | JSON
  • Calendar with year-only combobox dropdown | JSON
  • Calendar without arrow navigation (dropdown only) | JSON
  • Calendar with current month button | JSON
  • Calendar with today button | JSON
  • Calendar with date input | JSON
  • Calendar with time input | JSON
  • Calendar with time slots (appointment picker) | JSON
  • Calendar with date presets | JSON
  • Range calendar with date presets | JSON
  • Two months calendar | JSON
  • Three months calendar | JSON
  • Pricing calendar with custom day buttons | JSON

card

  • A basic card with header and footer | JSON
  • Authentication card with actions | JSON
  • Authentication card with separators | JSON
  • Framed card with footer | JSON
  • Framed card with header | JSON
  • Framed card with header and footer | JSON
  • Framed card with no rounded bottom | JSON
  • Card within a frame and footer | JSON
  • Card within a frame and footer | JSON
  • Card within a frame with header and footer | JSON
  • CardFrame with header action | JSON

checkbox

  • Basic checkbox | JSON
  • Disabled checkbox | JSON
  • Checkbox with description | JSON
  • Card-style checkbox | JSON
  • Checkbox form | JSON

checkbox-group

  • Basic checkbox group | JSON
  • Checkbox group with disabled items | JSON
  • Checkbox group with parent checkbox | JSON
  • Nested checkbox group with parent | JSON
  • Checkbox group form | JSON

collapsible

  • Basic collapsible | JSON

combobox

  • Basic combobox | JSON
  • Disabled combobox | JSON
  • Small combobox | JSON
  • Large combobox | JSON
  • Combobox with label | JSON
  • Combobox auto highlighting the first option | JSON
  • Combobox with clear button | JSON
  • Combobox with grouped items | JSON
  • Combobox with multiple selection | JSON
  • Combobox with input inside popup | JSON
  • Combobox form | JSON
  • Combobox multiple form | JSON
  • Combobox with start addon | JSON
  • Combobox multiple with start addon | JSON
  • Pill-shaped combobox | JSON
  • Timezone combobox | JSON
  • Timezone combobox with search input | JSON
  • Combobox with select trigger | JSON

command

  • Command palette with dialog | JSON
  • Command palette with AI assistant | JSON

date-picker

  • Basic date picker | JSON
  • Date range picker | JSON
  • Two months calendar with range date | JSON
  • Date picker with field and dropdown navigation | JSON
  • Date picker with presets | JSON
  • Date picker with input | JSON
  • Date picker that closes on select | JSON
  • Multiple dates picker | JSON
  • Date picker with select-like trigger | JSON

dialog

  • Dialog with form | JSON
  • Dialog with bare footer | JSON
  • Dialog opened from menu | JSON
  • Nested dialogs | JSON
  • Dialog with close confirmation | JSON
  • Dialog with long content | JSON

drawer

  • Simple bottom drawer with close button | JSON
  • Bottom drawer without drag bar | JSON
  • Drawer with close button | JSON
  • Inset variant drawers for all four positions | JSON
  • Straight variant drawers for all four positions | JSON
  • Scrollable content with terms and conditions | JSON
  • Nested bottom drawers with centered content | JSON
  • Nested right drawers with inset variant | JSON
  • Bottom drawer with snap points | JSON
  • Edit profile form with default and bare footer variants | JSON
  • Mobile menu drawer from the left | JSON
  • Responsive edit profile: dialog on desktop, drawer on mobile | JSON
  • Responsive actions menu: menu on desktop, drawer on mobile | JSON
  • Left drawer with swipe area | JSON

empty

  • Empty state with icon and actions | JSON

field

  • Field with description | JSON
  • Field with required indicator | JSON
  • Field in disabled state | JSON
  • Field showing validation error | JSON
  • Show field validity state | JSON
  • Input group with field | JSON
  • Field with autocomplete | JSON
  • Field with combobox | JSON
  • Field with multiple selection combobox | JSON
  • Field with textarea | JSON
  • Field with select | JSON
  • Field with checkbox | JSON
  • Field with checkbox group | JSON
  • Field with radio group | JSON
  • Field with toggle switch | JSON
  • Field with slider | JSON
  • Field with number field | JSON
  • Complete form built with field | JSON

fieldset

  • Fieldset with multiple fields | JSON

form

  • Input in a form | JSON
  • Form with zod validation | JSON

frame

  • Basic frame | JSON
  • Frame with multiple separated panels | JSON
  • Frame with multiple stacked panels | JSON
  • Frame with collapsible content and delete button | JSON

group

  • Basic group | JSON
  • Group with input | JSON
  • Small group | JSON
  • Large group | JSON
  • Group with disabled button | JSON
  • Group with default button | JSON
  • Group with start text | JSON
  • Group with end text | JSON
  • Vertical group | JSON
  • Nested groups | JSON
  • Group with popup | JSON
  • Group with input group | JSON
  • Group with menu | JSON
  • Group with select | JSON
  • Group with search | JSON
  • Group with add button and input | JSON
  • Group with input and currency text | JSON
  • Group with select and input | JSON
  • Group with input and select | JSON
  • Group with input and text button | JSON
  • Group with two number inputs for range | JSON
  • Group with filter label, combobox multi-select, and remove button | JSON

input

  • Basic input | JSON
  • Small input | JSON
  • Large input | JSON
  • Disabled input | JSON
  • File input | JSON
  • Input with label | JSON
  • Input with button using Group | JSON
  • Input with start text and end tooltip | JSON
  • Password input with toggle visibility | JSON
  • Input group mimicking a URL bar | JSON
  • Input group with keyboard shortcut | JSON
  • Input group with start loading spinner | JSON
  • Input with label and required indicator | JSON
  • Input with optional label | JSON
  • Input with custom border and background | JSON
  • Input group with end loading spinner | JSON
  • Read-only input | JSON
  • Input with characters remaining counter | JSON
  • Pill-shaped input | JSON

input-group

  • Basic input group | JSON
  • Input group with end icon | JSON
  • Input group with start text | JSON
  • Input group with end text | JSON
  • Input group with start and end text | JSON
  • Input group with number field | JSON
  • Input group with end tooltip | JSON
  • Input group with icon button | JSON
  • Input group with button | JSON
  • Input group with badge | JSON
  • Input group with keyboard shortcut | JSON
  • Input group with inner label | JSON
  • Small input group | JSON
  • Large input group | JSON
  • Disabled input group | JSON
  • Input group with loading spinner | JSON
  • Input group with textarea | JSON
  • Input group with badge and menu | JSON
  • Mini editor built with input group and toggle | JSON
  • Input group with search icon | JSON
  • Input group with start tooltip | JSON
  • Input group with clear button | JSON
  • Search input group with loader and voice button | JSON
  • Input group with character counter | JSON
  • Password input with strength indicator | JSON
  • Code snippet input with language selector | JSON
  • Message composer with attachment buttons | JSON
  • Chat input with voice and send buttons | JSON

kbd

  • Keyboard shortcuts display | JSON

menu

  • Basic menu | JSON
  • Menu with hover | JSON
  • Menu with checkbox | JSON
  • Menu with checkbox items as switches | JSON
  • Menu with radio group | JSON
  • Menu with link | JSON
  • Menu with group labels | JSON
  • Nested menu | JSON
  • Menu close on click | JSON

meter

  • Basic meter | JSON
  • Simple meter | JSON
  • Meter with formatted value | JSON
  • Meter with range | JSON

number-field

  • Basic number field | JSON
  • Small number field | JSON
  • Large number field | JSON
  • Disabled number field | JSON
  • Number field with label | JSON
  • Number field with scrub | JSON
  • Number field with range | JSON
  • Number field with formatted value | JSON
  • Number field with step | JSON
  • Number field in form | JSON
  • Pill-shaped number field | JSON

otp-field

  • Basic OTP field | JSON
  • Large OTP field | JSON
  • OTP field with separator | JSON
  • OTP field with label | JSON
  • OTP field with custom sanitization | JSON
  • OTP field with auto validation | JSON
  • Alphanumeric OTP field | JSON
  • OTP field with placeholder hints | JSON
  • Masked OTP field | JSON

pagination

  • Pagination example | JSON
  • Pagination with previous and next buttons only | JSON
  • Pagination with select, and previous and next buttons | JSON

popover

  • Popover with a form | JSON
  • Popover with close button | JSON
  • Animated popovers | JSON

preview-card

  • Preview card with popup | JSON

progress

  • Basic progress bar | JSON
  • Progress with label and value | JSON
  • Progress with formatted value | JSON

radio-group

  • Basic radio group | JSON
  • Disabled radio group | JSON
  • Radio group with description | JSON
  • Radio group card | JSON
  • Radio group in form | JSON
  • Theme selector with image cards | JSON

scroll-area

  • Basic scroll area | JSON
  • Horizontal scroll area | JSON
  • Scroll area with both directions | JSON
  • Scroll area with fading edges | JSON
  • Horizontal scroll area with scrollbar gutter | JSON

select

  • Basic select | JSON
  • Small select | JSON
  • Large select | JSON
  • Disabled select | JSON
  • Select without item alignment | JSON
  • Select with groups | JSON
  • Multiple select | JSON
  • Select with icon | JSON
  • Select options with icon | JSON
  • Select with object values | JSON
  • Select with disabled items | JSON
  • Timezone select | JSON
  • Status select with colored dot | JSON
  • Pill-shaped select trigger | JSON
  • Select with left text label | JSON
  • Select with country flags | JSON
  • Select with description in options only | JSON
  • Select with avatars | JSON
  • Rich select with avatars and usernames | JSON
  • Auto width select | JSON
  • Select with custom border and background | JSON
  • Select with label | JSON
  • Select in form | JSON

separator

  • Separator with horizontal and vertical orientations | JSON

sheet

  • Basic sheet | JSON
  • Sheet inset | JSON
  • Sheet position | JSON

skeleton

  • Basic skeleton | JSON
  • Skeleton only | JSON

slider

  • Basic slider | JSON
  • Slider with label and value | JSON
  • Disabled slider | JSON
  • Slider with reference labels | JSON
  • Slider with ticks | JSON
  • Slider with labels above | JSON
  • Range slider | JSON
  • Slider with 3 thumbs | JSON
  • Range slider with collision behavior none | JSON
  • Range slider with collision behavior swap | JSON
  • Slider with icons | JSON
  • Slider with input | JSON
  • Range slider with inputs | JSON
  • Slider with increment and decrement buttons | JSON
  • Price range slider | JSON
  • Emoji rating slider | JSON
  • Vertical slider | JSON
  • Vertical range slider | JSON
  • Vertical slider with input | JSON
  • Equalizer with vertical sliders | JSON
  • Object position sliders with reset | JSON
  • Price slider with histogram | JSON
  • Slider in form | JSON

spinner

  • Basic spinner | JSON

switch

  • Basic switch | JSON
  • Disabled switch | JSON
  • Switch with description | JSON
  • Switch card | JSON
  • Switch in form | JSON
  • Custom size switch | JSON

table

  • Basic table | JSON
  • Frame with card-style table | JSON
  • Table with TanStack Table and checkboxes | JSON
  • Table with TanStack Table, sorting, and pagination | JSON
  • Card-style table variant | JSON
  • CardFrame with card-style table | JSON
  • CardFrame with TanStack Table and checkboxes | JSON
  • CardFrame with TanStack Table, sorting, and pagination | JSON

tabs

  • Basic tabs | JSON
  • Tabs with underline | JSON
  • Vertical tabs | JSON
  • Vertical tabs with underline | JSON
  • Tabs with full rounded triggers | JSON
  • Tabs with icon before name | JSON
  • Tabs with icon before name and underline | JSON
  • Tabs with icon only | JSON
  • Tabs with underline and icon on top | JSON
  • Tabs with count badge | JSON
  • Vertical tabs with underline and icon before name | JSON
  • Tabs with icon only and count badge | JSON
  • Tabs with icon only and grouped tooltips | JSON

textarea

  • Basic textarea | JSON
  • Small textarea | JSON
  • Large textarea | JSON
  • Disabled textarea | JSON
  • Textarea with label | JSON
  • Textarea in form | JSON
  • Textarea with label and required indicator | JSON
  • Textarea with optional label | JSON
  • Textarea with custom border and background | JSON
  • Read-only textarea | JSON
  • Textarea with characters remaining counter | JSON
  • Textarea field with required indicator | JSON
  • Shorter textarea with fixed height | JSON
  • Textarea with button aligned right | JSON
  • Textarea with button aligned left | JSON

toast

  • Basic toast | JSON
  • Toast with status | JSON
  • Loading toast | JSON
  • Toast with action | JSON
  • Promise toast | JSON
  • Toast with varying heights | JSON
  • Anchored toast with tooltip style | JSON
  • Anchored toast | JSON
  • Promise toast with cancel action | JSON

toggle

  • Basic toggle | JSON
  • Toggle with outline | JSON
  • Toggle with icon | JSON
  • Small toggle | JSON
  • Large toggle | JSON
  • Disabled toggle | JSON
  • Toggle icon group | JSON
  • Bookmark toggle with tooltip and success toast | JSON

toggle-group

  • Basic toggle group | JSON
  • Small toggle group | JSON
  • Large toggle group | JSON
  • Toggle group with outline | JSON
  • Vertical toggle group with outline | JSON
  • Disabled toggle group | JSON
  • Toggle group with disabled item | JSON
  • Multiple selection toggle group | JSON
  • Toggle group with tooltips | JSON

toolbar

  • Toolbar with toggles, buttons, and select | JSON

tooltip

  • Basic tooltip | JSON
  • Grouped tooltips | JSON
  • Toggle group animated tooltip | JSON
  • Vertical group with animated tooltip | JSON
指导Agent正确使用coss UI组件库,涵盖组件选择、代码编写、shadcn迁移及无障碍访问。提供文档来源、核心原则、关键使用规则及工作流,确保生成符合规范的UI代码。
构建基于coss原语(如按钮、对话框)的UI界面 从shadcn/Radix迁移至coss/Base UI组件 解决coss组件行为问题或样式配置
.agents/skills/coss/SKILL.md
npx skills add MapleTechLabs/maple --skill coss -g -y
SKILL.md
Frontmatter
{
    "name": "coss",
    "license": "MIT",
    "metadata": {
        "author": "cosscom"
    },
    "description": "Helps implement coss UI components correctly. Use when building UIs with coss primitives (buttons, dialogs, selects, forms, menus, tabs, inputs, toasts, etc.), migrating from shadcn\/Radix to coss\/Base UI, composing trigger-based overlays, or troubleshooting coss component behavior. Covers imports, accessibility, Tailwind styling, and common pitfalls.",
    "compatibility": "Requires Tailwind CSS v4 and @base-ui\/react. Designed for React projects using the coss component registry."
}

coss ui

coss ui is a component library built on Base UI with a shadcn-like developer experience plus a large particle catalog.

What this skill is for

Use this skill to:

  • pick the right coss primitive(s) for a UI task
  • write correct coss usage code (imports, composition, props)
  • avoid common migration mistakes from shadcn/Radix assumptions
  • reference particle examples to produce practical, production-like patterns

Source of truth

  • coss components docs: apps/ui/content/docs/components/*.mdx
    • https://github.com/cosscom/coss/tree/main/apps/ui/content/docs/components
  • coss particle examples: apps/ui/registry/default/particles/p-*.tsx
    • https://github.com/cosscom/coss/tree/main/apps/ui/registry/default/particles
  • coss particles catalog: https://coss.com/ui/particles
  • docs map for agents: https://coss.com/ui/llms.txt

Out of scope

  • Maintaining coss monorepo internals/build pipelines.
  • Editing registry internals unless explicitly requested.

Principles for agent output

  1. Use existing primitives and particles first before inventing custom markup.
  2. Prefer composition over custom behavior reimplementation.
  3. Follow coss naming and APIs from docs exactly.
  4. Keep examples accessible and production-realistic.
  5. Prefer concise code that mirrors coss docs/particles conventions.
  6. Assume Tailwind CSS v4 conventions in coss examples and setup guidance.

Critical usage rules

Always apply before returning coss code:

  • Do not invent coss APIs. Verify against component docs first.
  • For trigger-based primitives (Dialog, Menu, Select, Popover, Tooltip), follow each primitive's documented trigger/content hierarchy and composition API; do not mix patterns across components.
  • Preserve accessibility labels and error semantics.
  • Consult primitive-specific guides for component invariants and edge cases.
  • For manual install guidance, include all required dependencies and local component files referenced by imports.
  • Prefer styled coss exports first; use *Primitive exports only when custom composition/styling requires it.

Rule references (read on demand when the task touches these areas):

  • ./references/rules/styling.md — Tailwind tokens, icon conventions, data-slot selectors
  • ./references/rules/forms.md — Field composition, validation, input patterns
  • ./references/rules/composition.md — Trigger/popup hierarchies, grouped controls
  • ./references/rules/migration.md — shadcn/Radix to coss/Base UI migration patterns
  • ./references/portal-props.md — optional portalProps on composed popups (keepMounted, container, which wrappers support it)

Component discovery

All 53 primitives have dedicated reference guides at ./references/primitives/<name>.md. To find the right one for a task, consult the component registry index:

  • ./references/component-registry.md

Usage workflow

  1. Identify user intent (single primitive, composed flow, form flow, overlay flow, feedback flow).
  2. Consult references/component-registry.md to identify candidate primitives.
  3. Select primitives from coss docs first; avoid custom fallback unless needed.
  4. Check at least one particle example for practical composition patterns. Particle files live at apps/ui/registry/default/particles/p-<name>-<N>.tsx (e.g. p-dialog-1.tsx).
  5. Write minimal code using documented imports/props.
  6. Self-check accessibility and composition invariants.

Installation reference

See ./references/cli.md for full install/discovery workflow.

Quick CLI pattern:

npx shadcn@latest add @coss/<component>

Quick manual pattern:

  • install dependencies listed in the component docs page
  • copy required component file(s)
  • update imports to match the target app alias setup

Primitive Guidance

Every primitive has a reference guide at ./references/primitives/<name>.md with imports, minimal patterns, inline code examples, pitfalls, and particle references. Use the component registry to find the right file.

High-risk primitives (read these guides first -- they have the most composition gotchas):

  • ./references/primitives/dialog.md — modal overlays, form-in-dialog, responsive dialog/drawer
  • ./references/primitives/menu.md — dropdown actions, checkbox/radio items, submenus
  • ./references/primitives/select.md — items-first pattern, multiple, object values, groups
  • ./references/primitives/form.md — Field composition, validation, submission
  • ./references/primitives/input-group.md — addons, DOM order invariant, textarea layouts
  • ./references/primitives/toast.md — toastManager (not Sonner), anchored toasts, providers

Output Checklist

Before returning code:

  • imports and props match coss docs
  • composition structure is valid for selected primitive(s)
  • accessibility and explicit control types (button, input, etc.) are present
  • migration-sensitive flows are verified (type/lint, keyboard/a11y behavior, and SSR-sensitive primitives like Select/Command)
用于扫描React代码库的安全、性能、正确性及架构问题,输出0-100健康分。适用于提交前检查回归或全面代码清理,支持通过diff仅扫描变更文件以快速验证。
完成功能开发后 修复Bug后 提交React代码前 用户希望提升代码质量 需要清理代码库
.agents/skills/react-doctor/SKILL.md
npx skills add MapleTechLabs/maple --skill react-doctor -g -y
SKILL.md
Frontmatter
{
    "name": "react-doctor",
    "version": "1.0.0",
    "description": "Use when finishing a feature, fixing a bug, before committing React code, or when the user wants to improve code quality or clean up a codebase. Checks for score regression. Covers lint, dead code, accessibility, bundle size, architecture diagnostics."
}

React Doctor

Scans React codebases for security, performance, correctness, and architecture issues. Outputs a 0–100 health score.

After making React code changes:

Run npx -y react-doctor@latest . --verbose --diff and check the score did not regress.

If the score dropped, fix the regressions before committing.

For general cleanup or code improvement:

Run npx -y react-doctor@latest . --verbose (without --diff) to scan the full codebase. Fix issues by severity — errors first, then warnings.

Command

npx -y react-doctor@latest . --verbose --diff
Flag Purpose
. Scan current directory
--verbose Show affected files and line numbers per rule
--diff Only scan changed files vs base branch
--score Output only the numeric score
提供Tinybird CLI (tb)的使用指南,涵盖本地开发、部署、数据操作及工作流管理。适用于执行tb命令、构建部署项目、配置CI/CD、管理令牌及生成模拟数据等场景,确保正确调用CLI工具。
运行 tb 命令 选择开发工作流(本地或分支) 本地或云端分支开发 构建和部署项目 设置 CI/CD 流水线 执行数据追加、替换或删除 通过 CLI 管理令牌和密钥 生成模拟数据 运行测试
.agents/skills/tinybird-cli-guidelines/SKILL.md
npx skills add MapleTechLabs/maple --skill tinybird-cli-guidelines -g -y
SKILL.md
Frontmatter
{
    "name": "tinybird-cli-guidelines",
    "description": "Tinybird CLI commands, workflows, and operations. Use when running tb commands, managing local development, deploying, or working with data operations."
}

Tinybird CLI Guidelines

Guidance for using the Tinybird CLI (tb) for local development, deployments, data operations, and workspace management.

When to Apply

  • Running any tb command
  • Choosing a development workflow (local, branch, or cloud)
  • Local development with Tinybird Local
  • Branch development with Tinybird Cloud branches
  • Building and deploying projects
  • Setting up CI/CD pipelines
  • Appending, replacing, or deleting data
  • Managing tokens and secrets via CLI
  • Generating mock data
  • Running tests

Rule Files

  • rules/development-workflows.md
  • rules/cli-commands.md
  • rules/build-deploy.md
  • rules/local-development.md
  • rules/branch-development.md
  • rules/ci-cd.md
  • rules/data-operations.md
  • rules/append-data.md
  • rules/mock-data.md
  • rules/tokens.md
  • rules/secrets.md

Quick Reference

  • CLI 4.0 workflow: configure dev_mode once, then use plain tb build and tb deploy.
  • tb build targets your configured development environment (branch or local) in tinybird.config.json.
  • tb deploy targets Tinybird Cloud production.
  • Use --cloud/--local/--branch only as explicit manual overrides.
  • Use tb info to check CLI context.
  • Use tb endpoint data <pipe> to test endpoints (not tb pipe data).
  • Never invent commands or flags; run tb <command> --help to verify.
提供Tinybird Python SDK的使用指南,涵盖数据源、管道和端点的Python定义、客户端创建、CLI命令及连接配置。适用于SDK安装、开发构建部署流程及旧版文件迁移场景。
使用tinybird-sdk进行Python项目开发 定义或迁移Datasource/Pipe/Endpoint 执行tinybird CLI命令如dev/build/deploy 配置Kafka/S3/GCS连接
.agents/skills/tinybird-python-sdk-guidelines/SKILL.md
npx skills add MapleTechLabs/maple --skill tinybird-python-sdk-guidelines -g -y
SKILL.md
Frontmatter
{
    "name": "tinybird-python-sdk-guidelines",
    "description": "Tinybird Python SDK for defining datasources, pipes, and queries in Python. Use when working with tinybird-sdk, Python Tinybird projects, or data ingestion and queries in Python."
}

Tinybird Python SDK Guidelines

Guidance for using the tinybird-sdk package to define Tinybird resources in Python.

When to Apply

  • Installing or configuring tinybird-sdk
  • Defining datasources, pipes, or endpoints in Python
  • Creating Tinybird clients in Python
  • Using data ingestion or queries in Python
  • Running tinybird dev/build/deploy commands for Python projects
  • Migrating from legacy .datasource/.pipe files to Python
  • Defining connections (Kafka, S3, GCS)
  • Creating materialized views, copy pipes, or sink pipes

Rule Files

  • rules/getting-started.md
  • rules/configuration.md
  • rules/defining-datasources.md
  • rules/defining-endpoints.md
  • rules/client.md
  • rules/low-level-api.md
  • rules/cli-commands.md
  • rules/connections.md
  • rules/materialized-views.md
  • rules/copy-sink-pipes.md
  • rules/tokens.md

Quick Reference

  • Install: pip install tinybird-sdk
  • Initialize: tinybird init
  • Dev mode: tinybird dev (uses configured dev_mode, typically branch)
  • Build: tinybird build (builds against configured dev target)
  • Deploy: tinybird deploy (deploys to main/production)
  • Preview in CI: tinybird preview
  • Migrate: tinybird migrate (convert .datasource/.pipe files to Python)
  • Server-side only; never expose tokens in browsers
提供 @tinybirdco/sdk 的使用指南,支持在 TypeScript 中定义数据源、管道和查询。涵盖安装配置、类型安全客户端创建、CLI 命令(dev/build/deploy)及连接管理,确保完整的类型推断与类型安全的数据摄入。
使用 @tinybirdco/sdk 进行 Tinybird 资源开发 需要类型安全的 Tinybird 数据摄入或查询 配置或运行 tinybird CLI 命令 从旧版文件格式迁移到 TypeScript
.agents/skills/tinybird-typescript-sdk-guidelines/SKILL.md
npx skills add MapleTechLabs/maple --skill tinybird-typescript-sdk-guidelines -g -y
SKILL.md
Frontmatter
{
    "name": "tinybird-typescript-sdk-guidelines",
    "description": "Tinybird TypeScript SDK for defining datasources, pipes, and queries with full type inference. Use when working with @tinybirdco\/sdk, TypeScript Tinybird projects, or type-safe data ingestion and queries."
}

Tinybird TypeScript SDK Guidelines

Guidance for using the @tinybirdco/sdk package to define Tinybird resources in TypeScript with complete type inference.

When to Apply

  • Installing or configuring @tinybirdco/sdk
  • Defining datasources or pipes in TypeScript
  • Creating typed Tinybird clients
  • Using type-safe ingestion or queries
  • Running tinybird dev/build/deploy commands for TypeScript projects
  • Migrating from legacy .datasource/.pipe files to TypeScript
  • Defining connections (Kafka, S3, GCS)
  • Creating materialized views, copy pipes, or sink pipes

Rule Files

  • rules/getting-started.md
  • rules/configuration.md
  • rules/defining-datasources.md
  • rules/defining-endpoints.md
  • rules/typed-client.md
  • rules/low-level-api.md
  • rules/cli-commands.md
  • rules/connections.md
  • rules/materialized-views.md
  • rules/copy-sink-pipes.md
  • rules/tokens.md

Quick Reference

  • Install: npm install @tinybirdco/sdk
  • Initialize: npx tinybird init
  • Dev mode: tinybird dev (uses configured devMode, typically branch)
  • Build: tinybird build (builds against configured dev target)
  • Deploy: tinybird deploy (deploys to main/production)
  • Preview in CI: tinybird preview
  • Server-side only; never expose tokens in browsers
提供Tinybird数据源、管道、端点及物化视图的最佳实践,涵盖文件格式、SQL规则、优化模式与数据建模。适用于创建或编辑Tinybird资源、编写查询及重构项目文件。
创建或更新Tinybird资源(如datasource, pipe) 编写或优化SQL查询 设计端点架构和数据模型 重构Tinybird项目文件
.agents/skills/tinybird/SKILL.md
npx skills add MapleTechLabs/maple --skill tinybird -g -y
SKILL.md
Frontmatter
{
    "name": "tinybird",
    "description": "Tinybird file formats, SQL rules, optimization patterns, and best practices for datasources, pipes, endpoints, and materialized views."
}

Tinybird Best Practices

Guidance for Tinybird file formats, SQL rules, optimization patterns, and data modeling. Use this skill when creating or editing Tinybird datafiles.

When to Apply

  • Creating or updating Tinybird resources (.datasource, .pipe, .connection)
  • Writing or optimizing SQL queries
  • Designing endpoint schemas and data models
  • Organizing project structure and data layers
  • Working with materialized views or copy pipes
  • Implementing deduplication patterns
  • Reviewing or refactoring Tinybird project files

Rule Files

  • rules/project-files.md
  • rules/build-deploy.md
  • rules/datasource-files.md
  • rules/pipe-files.md
  • rules/endpoint-files.md
  • rules/materialized-files.md
  • rules/materialized-join-prefilter.md
  • rules/sink-files.md
  • rules/copy-files.md
  • rules/connection-files.md
  • rules/sql.md
  • rules/endpoint-optimization.md
  • rules/tests.md
  • rules/deduplication-patterns.md

Quick Reference

  • Project local files are the source of truth.
  • Build target comes from tinybird.config.json dev_mode (local or branch).
  • tb deploy targets Tinybird Cloud production.
  • Commands like tb sql and tb logs default to local unless --cloud or --branch=<branch-name> is set.
  • SQL is SELECT-only with Tinybird templating rules and strict parameter handling.
  • Use MergeTree by default; AggregatingMergeTree for materialized targets.
  • Filter early, select only needed columns, push complex work later in the pipeline.
通过逐一提问深入探讨计划或设计,构建决策树以达成共同理解。覆盖目标、风险等关键维度,利用代码库信息辅助,最终总结共识设计与后续步骤,用于压力测试方案。
用户希望压力测试计划或设计 用户提及 'grill me'
.context/effect/.agents/skills/grill-me/SKILL.md
npx skills add MapleTechLabs/maple --skill grill-me -g -y
SKILL.md
Frontmatter
{
    "name": "grill-me",
    "description": "Interview the user about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions \"grill me\"."
}

Interview me about every aspect of this plan until we reach a shared understanding and a defensible design.

Ask exactly one question at a time, then wait for my answer before asking the next question.

Use each answer to choose the next highest-leverage unresolved question. Maintain an implicit decision tree of resolved decisions, open questions, assumptions, dependencies, risks, and rejected alternatives.

For each question, include:

  • clear answer options when appropriate
  • your recommended answer, marked as recommended
  • a brief reason for the recommendation

Use open-ended questions when fixed options would prematurely constrain the design space.

Challenge vague, inconsistent, risky, or unsupported assumptions. If an answer creates a contradiction or unresolved dependency, ask a follow-up before moving on.

Cover, as relevant:

  • goals and non-goals
  • users and stakeholders
  • constraints
  • alternatives
  • APIs and interfaces
  • data model
  • error handling
  • security
  • observability
  • testing
  • migration and rollout
  • failure modes
  • operational ownership
  • success criteria

If repository facts are needed, inspect the codebase instead of asking the user. Do not ask me to provide information that can be determined locally.

When an available user-input tool such as request_user_input fits the question, use it to ask one short question with a small set of mutually exclusive options. Otherwise, ask in plain text and present clear possible answers as a numbered list when that helps me answer quickly. Include your recommended option and mark it as recommended.

Stop when the major branches of the design tree have been resolved. Then summarize the agreed design, remaining risks, assumptions, rejected alternatives, and next steps.

为Effect公共API编写符合jsdocs oxlint规则的JSDoc注释。涵盖修复诊断、准备JSON提取及审查文档,严格遵循特定文档结构与英文写作规范。
添加或修复JSDoc注释 解决jsdocs诊断错误 准备用于JSON提取的文档 审查公共API文档
.context/effect/.agents/skills/jsdocs/SKILL.md
npx skills add MapleTechLabs/maple --skill jsdocs -g -y
SKILL.md
Frontmatter
{
    "name": "jsdocs",
    "description": "Write, insert, or update Effect public API JSDoc so it satisfies the jsdocs oxlint rule. Use when adding or fixing JSDoc comments, resolving jsdocs diagnostics, preparing docs for JSON extraction, or reviewing public API documentation."
}

Use this skill to write well-formed JSDoc for Effect public APIs.

Workflow

When updating public API JSDoc:

  1. Inspect the declaration, implementation, nearby tests, and nearby JSDoc before editing.
  2. Decide whether the task is a single API fix or a module refinement pass.
  3. Rewrite comments into the required documentation shape while preserving correct facts and examples.
  4. For module refinements, complex APIs, or APIs with related alternatives, run the @see and **Gotchas** audits.
  5. Run the narrowest relevant validation.

Required documentation shape

Use a normal multiline JSDoc comment in TypeScript source:

/**
 * Short description as one paragraph.
 *
 * **When to use**
 *
 * Optional practical usage guidance.
 *
 * **Details**
 *
 * Optional details for complex APIs, options, overloads, or behavior.
 *
 * **Gotchas**
 *
 * Optional edge cases, footguns, or surprising behavior.
 *
 * **Example** (Short title)
 *
 * Optional prose explaining the example.
 *
 * ```ts
 * const result = example()
 * ```
 *
 * @category constructors
 * @since 1.0.0
 */

Prose Rules

  • Use sober, practical prose.
  • Write all public JSDoc prose in English.
  • Do not use jargon when a plain word works.
  • Do not be clever.
  • Do not add filler sections.
  • The short description is required and must be exactly one paragraph.
  • Make the short description stand on its own. Do not rely on **When to use** to make the API understandable.
  • For functions and methods, prefer present-tense, action-first prose such as Creates, Returns, Checks, Provides, Represents, Converts, Decodes, or Formats.
  • For technical value exports, use consistent noun forms such as Schema for, Layer that, Service that, Context reference that, or Constructors and matchers for.
  • Avoid leading A or An for canonical technical nouns when the surrounding module uses a standard noun family, for example prefer Schema for ... over A schema for ....
  • Do not describe implementation mechanics when a public concept is clearer. For example, prefer Constructors and matchers for ... over wording that only says an API uses Data.taggedEnum.
  • Avoid generic purity or non-mutation remarks unless they document a real surprise, caveat, or meaningful contrast with a mutating-looking API.
  • Optional sections must appear in this order:
    1. **When to use**
    2. **Details**
    3. **Gotchas**
  • Include an optional section only when it has useful, non-empty content.
  • Prefer prose over bullet lists for single-item **Details**, **When to use**, or **Gotchas** sections. Use bullets only when there are two or more parallel facts, options, cases, or caveats.
  • **When to use** describes the positive use case for the documented API. Do not use it as a routing section for sibling APIs. If neighboring APIs need to be mentioned, put that boundary in @see tag text instead.
  • **When to use** is important when the API has close alternatives, trade-offs, or @see tags. If @see tags are present, inspect the referenced APIs and add **When to use** when it clarifies the documented API's own use case.
  • **When to use** must start with one of these practical guidance forms: Use to, Use when, Use as, or Use with. Avoid bullet lists and vague openers such as Use this... or Useful for....
  • Prefer reader-centered **When to use** wording, especially Use when you ..., when the sentence describes a user's goal. Avoid third-person noun-phrase subjects such as the input is ..., a service needs ..., or values should ... when they would become awkward in generated prompts.
  • A good **When to use** sentence should still read naturally if reused as a user intent prompt, for example after I need ... or I have ....
  • Keep short and **When to use** distinct: the short description says what the API is or does; **When to use** says when to choose it.
  • Add internal @see tags only for semantically useful related public APIs.
  • Write @see tag text as normal prose after the link; no special separator is required. Prefer forms like @see {@link otherApi} for ... when a short explanation helps.
  • Use exactly one blank line between the short description, sections, examples, and tags.
  • Do not use Markdown headings such as # Heading or ad hoc bold headings such as **Notes**; only the standard headings are allowed.
  • Examples must use **Example** (Title), optional prose, and exactly one non-empty ts code fence.
  • Example titles must be unique after trimming and lowercasing.
  • Example titles should be short use-case phrases, not generic labels.
  • Prefer gerund or action-noun titles that read naturally after for, for example Parsing JSON, Creating a scoped runtime, or Comparing structs.
  • Avoid imperative titles such as Parse JSON, vague labels such as Syntax or Basic usage, and title-cased fragments such as String Ordering.
  • Preserve canonical technical capitalization inside the phrase, such as Option, Effect, Schema, DateTime, HashMap, Base64, and JSON.
  • For multiple examples on the same API, make each title describe the distinct use case shown by that example.
  • Prefer examples with stable, deterministic output. Avoid assertions or console.log comments that depend on stack traces, object inspection, Error formatting, concurrency order, timing, randomness, or environment-specific formatting. Examples may assume Node.js console formatting. Direct Set / Map output is acceptable when insertion order is deterministic and the expected output uses Node's format; otherwise demonstrate a stable property instead.
  • Do not use @example.
  • Do not put TypeScript code fences outside **Example** (Title) sections.
  • Inline {@link Symbol} targets must resolve to TypeScript symbols; do not link to URLs with {@link}.
  • Avoid overlinking in prose. Use {@link Symbol} only when navigation to that symbol helps the reader choose or understand the API. For the API being documented, the module's central type, nearby obvious names, or repeated mentions, prefer plain code formatting such as Cause, Effect, or Context.
  • Do not document module-level comments; module JSDoc is ignored by this rule.
  • @internal means the item is ignored; do not rewrite it as public docs.
  • Default exports are ignored by this rule and do not need JSDoc.
  • Do not add unsupported constructs such as enums or empty exports in checked files.
  • For low-level public values, prefer accurate categories such as symbols, type IDs, or prototypes over compensating with verbose descriptions.

Tag rules

When multiple tags are present, keep them in this order:

  1. @deprecated
  2. @default
  3. @see
  4. @category
  5. @since

Tag requirements by declaration kind:

  • Root declarations require @category and stable-semver @since, and must not use @default.
  • Namespaces and declarations inside namespaces require stable-semver @since, may use @category, and must not use @default.
  • Member JSDoc is optional. When present, it follows the same prose and layout rules, may use optional stable-semver @since, may use non-empty @default, and must not use @category.
  • Any declaration may use @deprecated with a non-empty message and repeated non-empty @see tags for semantically useful related public APIs.

Updating existing JSDoc

When fixing or updating existing docs:

  1. Preserve correct facts and examples.
  2. Rewrite the layout into the standard template.
  3. Move usage guidance into **When to use**, behavior details into **Details**, and real caveats into **Gotchas**.
  4. Convert @example tags and loose ts fences into **Example** (Title) sections.
  5. Preserve valid @see, @deprecated, @default, @category, and @since tags.
  6. Remove @see tags that do not point to semantically useful related public APIs.
  7. Replace redundant inline {@link ...} tags with plain code formatting when the link target is already obvious from the current declaration or module.
  8. Remove sections that would be empty.

Module refinement

When asked to refine an existing module:

  1. First scan the module for local documentation patterns, repeated API families, and category conventions.
  2. Keep the change focused on documentation quality unless the user also asked for rule or source changes.
  3. Prefer improving existing comments over rewriting every comment into a new voice.
  4. Preserve examples unless they are wrong, stale, nondeterministic, or fail the required documentation shape.
  5. Apply the @see and **Gotchas** audits across the module before finishing.

See audit

When refining an existing public API module, always do a dedicated @see pass:

  1. Inspect existing @see tags and referenced APIs before keeping, changing, or removing them.
  2. Look for close alternatives in the same module or API family when the documented API is one of several ways to do similar work.
  3. Keep or add @see only when the linked API is semantically useful to understand the documented API.
  4. Good @see targets include sibling APIs, alternatives, inverse operations, lower-level or higher-level variants, complementary operations, and closely returned, consumed, or configured types/values.
  5. Do not use @see for implementation dependencies, broad concepts, external background links, APIs that merely share a word or name, helper APIs used only inside examples, undocumented/private members, or APIs that are only generally compatible.
  6. When @see tags are kept or added, include **When to use** guidance if the documented API's own use case is not obvious from the short description. Keep comparisons with sibling APIs in the @see tag text.

Gotchas audit

When refining an existing public API module, always do a dedicated **Gotchas** pass:

  1. Scan existing prose for caveat language: warnings, exceptions, limitations, preconditions, special cases, or behavior that is easy to misuse.
  2. Inspect the implementation and nearby tests for behavior that is not obvious from the type signature or short description.
  3. Move real caveats from **Details** into **Gotchas** when they describe edge cases, footguns, preconditions, surprising behavior, or important failure modes.
  4. Add **Gotchas** only when the caveat is concrete and useful to a reader choosing or using the API.
  5. If no gotchas are added during a refinement pass, state that a gotchas audit was performed and why no caveats were worth documenting.

Validation

Run the narrowest validation that matches the change:

  • For JSDoc or example changes in a package with generated docs, run pnpm docgen from that package directory.
  • Run pnpm lint because the linter includes the custom rule that checks public API JSDoc.
  • Do not run broad validation for prose-only skill edits.
从活动光标或选区附近的 JSDoc 示例中提取代码,生成 TypeScript 文件至 scratchpad。支持自动追加 Effect 运行器,处理无运行器的情况,避免覆盖已有文件,提供可点击路径链接。
用户要求提取、复制、打开或尝试源码中的示例到 scratchpad 指定文件行号以获取附近 JSDoc 示例
.context/effect/.agents/skills/scratchpad/SKILL.md
npx skills add MapleTechLabs/maple --skill scratchpad -g -y
SKILL.md
Frontmatter
{
    "name": "scratchpad",
    "description": "Extract the JSDoc example nearest the active source selection or cursor into .\/scratchpad as a TypeScript file. Use when the user asks to dump, copy, open, or try a source example in scratchpad."
}

Use this skill to create a scratchpad TypeScript file from the JSDoc **Example** nearest the user's active source cursor or selection.

Workflow

  1. Determine the source path and line:

    • Use the IDE active file and selection/cursor line when present.
    • Use an explicit file and line when the user provides them.
    • If no line or selection is available, ask for it.
  2. Run:

    node .agents/skills/scratchpad/scripts/extract-example.mjs <source-path> <line>
    
  3. If the script exits with code 2 because there is no obvious runner, ask the user whether to preserve the example exactly, name an Effect value to run, or cancel.

    • To preserve exactly, rerun with --mode preserve.
    • To run a specific Effect value, rerun with --runner <identifier>.
  4. Report the created path as a clickable file link. This is the deterministic way to open it in the code pane.

  5. Do not run the scratchpad file unless the user explicitly asks.

Behavior

  • The script chooses the example whose **Example** section contains the line; otherwise it chooses the first following example; otherwise the nearest previous example.

  • Filenames are derived from the source file and example title, for example scratchpad/Schedule-retrying-and-repeating-effects.ts.

  • Existing files are not overwritten. The script appends a numeric suffix.

  • In auto mode, if a top-level program binding exists, the script appends:

    Effect.runPromise(program).then(console.log, console.error)
    
  • If the example already contains an Effect runner, the script preserves it.

用于扫描React代码库的安全、性能、正确性及架构问题,输出0-100健康分。适用于功能完成、Bug修复后提交前检查回归,或进行整体代码清理优化,支持按严重级别修复错误与警告。
完成React功能开发后 修复Bug准备提交时 用户希望提升代码质量或清理代码库时
.factory/skills/react-doctor/SKILL.md
npx skills add MapleTechLabs/maple --skill react-doctor -g -y
SKILL.md
Frontmatter
{
    "name": "react-doctor",
    "version": "1.0.0",
    "description": "Use when finishing a feature, fixing a bug, before committing React code, or when the user wants to improve code quality or clean up a codebase. Checks for score regression. Covers lint, dead code, accessibility, bundle size, architecture diagnostics."
}

React Doctor

Scans React codebases for security, performance, correctness, and architecture issues. Outputs a 0–100 health score.

After making React code changes:

Run npx -y react-doctor@latest . --verbose --diff and check the score did not regress.

If the score dropped, fix the regressions before committing.

For general cleanup or code improvement:

Run npx -y react-doctor@latest . --verbose (without --diff) to scan the full codebase. Fix issues by severity — errors first, then warnings.

Command

npx -y react-doctor@latest . --verbose --diff
Flag Purpose
. Scan current directory
--verbose Show affected files and line numbers per rule
--diff Only scan changed files vs base branch
--score Output only the numeric score
审计已接入的OpenTelemetry配置是否符合Maple规范,识别缺失并生成报告。支持按服务分类(完整/部分/未接入),依据checks.md判定严重性,并指引使用配套技能修复问题。
audit my instrumentation check my telemetry review my OTel setup why is my service map missing edges is my Maple instrumentation correct
skills/maple-audit/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-audit -g -y
SKILL.md
Frontmatter
{
    "name": "maple-audit",
    "description": "Audit an already-instrumented project against Maple's OpenTelemetry conventions, report gaps per service, and fix them. Triggers on requests like 'audit my instrumentation', 'check my telemetry', 'review my OTel setup', 'why is my service map missing edges', 'is my Maple instrumentation correct'."
}

Maple instrumentation audit

Review an existing OpenTelemetry setup against what Maple actually consumes, produce a findings report, then fix the gaps. This is the counterpart to maple-onboard: that skill installs telemetry from scratch; this one assumes instrumentation exists and asks whether it's right.

Before auditing, read checks.md in this skill's directory — it is the full check registry (check ids, severities, what each gap breaks in Maple). Every finding you report must cite a check id from there; never invent attribute keys or conventions that aren't in checks.md or the upstream OTel semconv.

For how to fix what you find, use the companion skills — don't improvise recipes:

  • maple-onboarding-style for general OTel taste, VCS attributes, log bridges, metrics, LLM conventions.
  • maple-nextjs-style, maple-nodejs-style, maple-python-style, maple-effect-style, maple-go-style, maple-rust-style, maple-java-style, maple-csharp-style, maple-kotlin-style for stack-specific bootstrap shapes.

Severity

Severity Meaning
critical Breaks a Maple feature outright or is a data risk: missing service.name, hand-stamped status strings (error analytics read zero rows), logs without trace correlation, missing peer.service/db.system on client spans, PII in attributes.
warn Feature works degraded or work is invisible: missing environment/VCS resource attrs, deprecated semconv keys, double-emission, high-cardinality labels or span names, outbound calls left Internal, untraced operations (missing auto-instrumentation for a used framework/driver, business operations and background jobs with no spans).
info Signal-quality improvements: trace-context propagation across async boundaries, span noise, missing metrics, naming style, missing gen_ai.* on LLM calls.

Step 1 — Enumerate services and classify

Map every app/service in the repo exactly as maple-onboard Step 1 does (workspace manifests, apps/*, services/*, mobile, edge/serverless functions — skip pure type/config packages). For each, find its OTel bootstrap and classify:

  • instrumented — has an SDK bootstrap; gets the full audit below.
  • partial — some signals wired (e.g. traces but no log bridge); audit what exists, flag what's absent.
  • not instrumented — no OTel at all. That is a single critical finding pointing the user at maple-onboard; don't run per-check audits against nothing.

Show the user the service list with classifications before auditing, so they can correct it.

Step 2 — Static code audit

Per instrumented service, work through the categories in checks.md:

  1. Bootstrap & resource (RES-*) — read the SDK init: is service.name explicit, are service.version, deployment.environment.name, vcs.repository.url.full, vcs.ref.head.revision on the resource? Any invented keys?
  2. Status & kind (STAT-*) — grep for status handling: SDK status enum vs hand-stamped strings; failure paths recording exceptions; outbound calls created as Client/Producer spans.
  3. Trace coverage (SPAN-*) — what should be traced but isn't. Compare the dependency manifest against the instrumentation registered in the bootstrap (a DB driver or queue client with no instrumentation package means that whole category emits nothing). Then read the code for critical business operations, background jobs, and queue consumers with no span in their call path — when this service misbehaves, what would an operator need to see that currently emits nothing?
  4. Service-map attribution (MAP-*) — for every outbound dependency in the code (HTTP clients to internal services, DB drivers, queues), check the corresponding spans set peer.service (and db.system for DBs) consistently.
  5. Attribute keys (REN-*, NAME-*) — grep attribute call sites (setAttribute, set_attribute, setAttributes, attribute map literals) for the deprecated/camelCase keys in the REN table and for double-emission of old+new keys.
  6. Logs (LOG-*) — is an OTLP log bridge wired under the logger the app actually uses? Do in-span logs carry trace context? Structured fields?
  7. Metrics (MET-*) — instruments at module scope, low-cardinality labels, business coverage.
  8. PII (PII-01) — scan attribute values for emails, tokens, auth headers, full bodies.
  9. LLM (LLM-*) — only if the project calls LLM providers.

Collect findings as you go: service, check id, severity, evidence (file:line), and which Maple feature it affects (from checks.md).

Step 3 — Live cross-check (only when the Maple MCP is connected)

If mcp__maple__* tools are available, verify the static findings against what's actually arriving. If they're not available, say so in one line of the report and move on — the static audit stands alone. Don't ask the user to install the MCP mid-audit.

  • get_instrumentation_recommendations — the authoritative list of deprecated/double-emitted/non-conforming attribute keys, reconciled against the org's live span data, plus resource-attribute coverage gaps. Where it disagrees with your static REN/NAME findings, the live data wins (the code you read may not be deployed, or other emitters exist).
  • list_services — confirm every service you enumerated actually reports. A service that's instrumented in code but absent here is a critical export problem (bootstrap not loaded, key wrong, exporter blocked).
  • explore_attributes (resource scope) — confirm environment/VCS/version resource attrs arrive in practice.
  • service_map — a service whose code makes outbound calls but shows no outgoing edges confirms MAP-01/02/03 findings from the data side.
  • get_service_top_operations (or search_traces per service) — confirm trace-coverage findings: a critical operation you flagged under SPAN-02/03 that never appears as a span name in live data is confirmed untraced; a DB-heavy service whose traces contain no DB client spans confirms SPAN-01.

Annotate existing findings with live evidence rather than duplicating them.

Step 4 — Findings report

Present the report before making any edit. Format:

## Instrumentation audit — <repo>

### <service-name>  (instrumented | partial | not instrumented)
| Severity | Check  | Finding                                   | Evidence            | Affects                  |
| critical | MAP-01 | Client spans to billing lack peer.service | src/billing.ts:88   | service map edge missing |
| warn     | REN-02 | emits http.status_code                    | live: 12.4k spans/24h | rename to http.response.status_code |
...

### Summary
N critical · N warn · N info across M services. Recommended fix order: …

Every row carries a check id and concrete evidence (file:line for static, counts/tool output for live). If the MCP wasn't connected, note "live cross-check skipped — Maple MCP not connected" here.

Step 5 — Apply fixes

After the report, fix in severity order (criticalwarninfo), following the relevant style skill for each edit. Rules:

  • Pure key renames (REN rows of kind rename) have two valid routes: rename at the SDK (preferred — fixes it at the source), or accept the matching Recommendation Issue in Maple Settings → Ingestion, which creates an ingest attribute mapping. Tell the user both; default to the SDK rename when you're already editing that file.
  • Double-emission and naming issues can only be fixed at the SDK — an ingest mapping can't merge keys.
  • Idempotent edits; preserve the project's package manager, logger, and formatting.
  • Never remove an existing observability vendor (Sentry, Datadog, …) — coexistence is the default.
  • Never put PII in attributes; when fixing PII-01, replace the value with an ID or drop the attribute, don't hash-and-keep.
  • Never commit, push, or open PRs.

If the user only wants the review, stop after Step 4 — the report is a complete deliverable.

Step 6 — Re-verify

For every service you touched, run the same smoke as maple-onboard Step 4: the service's own dev/build command starts cleanly, and OTLP POSTs from the running process return 2xx. A fix that breaks startup is a regression — fix or revert it, don't paper over it.

Then close out: summarize what changed per service, and tell the user that after deploying, re-running get_instrumentation_recommendations (or checking Settings → Ingestion in Maple) will show issues auto-resolving as the offending keys stop arriving — reconciliation is automatic.

Hard rules

  • No edits before the findings report has been shown (Step 4).
  • Every finding cites a check id from checks.md; no invented attribute keys or conventions.
  • The static audit must work standalone — the Maple MCP is an enhancer, never a requirement.
  • Never modify files outside the project root; never commit or push.
  • Fix recipes come from the maple-*-style skills, not from memory.
为 .NET/C# 应用提供基于 OpenTelemetry 的可观测性集成方案。支持通过 ASP.NET Core 托管模型配置 Trace、Metric 和 Log,使用 OTLP HTTP 协议导出至 Maple,并演示如何创建自定义业务 Span。
.NET 项目集成可观测性 C# 应用配置 OpenTelemetry ASP.NET Core 链路追踪设置
skills/maple-csharp-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-csharp-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-csharp-style",
    "description": ".NET \/ C# OpenTelemetry style for Maple: OpenTelemetry.Extensions.Hosting + OTLP HTTP exporter, ActivitySource for spans, ILogger bridging via OpenTelemetryLoggerProvider, inline endpoint + ingest key."
}

Maple .NET / C# style

Use the official OpenTelemetry packages and wire them through the .NET hosting model — IServiceCollection for traces / metrics, ILoggingBuilder for logs.

Install

dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http

Bootstrap (ASP.NET Core)

Inline the endpoint and ingest key — they're a project-scoped, write-only token (Sentry-DSN-shaped). No env-var indirection.

using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

const string MapleEndpoint = "https://ingest.maple.dev";
const string MapleKey = "MAPLE_TEST"; // set by maple-onboard skill on pairing

var builder = WebApplication.CreateBuilder(args);

void ConfigureOtlp(OtlpExporterOptions options, string path)
{
    options.Endpoint = new Uri($"{MapleEndpoint}/v1/{path}");
    options.Protocol = OtlpExportProtocol.HttpProtobuf;
    options.Headers = $"authorization=Bearer {MapleKey}";
}

var resource = ResourceBuilder.CreateDefault()
    .AddService("orders-api")
    .AddAttributes(new Dictionary<string, object>
    {
        ["deployment.environment.name"] = builder.Environment.EnvironmentName,
        ["vcs.repository.url.full"] = "https://github.com/acme/orders-api",
        ["vcs.ref.head.revision"] = Environment.GetEnvironmentVariable("GITHUB_SHA") ?? "",
    });

builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("orders-api"))
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter(o => ConfigureOtlp(o, "traces")))
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter(o => ConfigureOtlp(o, "metrics")));

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.SetResourceBuilder(resource);
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
    logging.AddOtlpExporter(o => ConfigureOtlp(o, "logs"));
});

var app = builder.Build();

Bounded business spans

Use a static ActivitySource per module. ASP.NET / HttpClient auto-instrumentation handles the obvious spans; reach for ActivitySource.StartActivity for business operations.

public static class Tracing
{
    public static readonly ActivitySource Source = new("orders.api");
}

public sealed class OrderService
{
    public async Task SubmitAsync(string orderId, string tenantId)
    {
        using var activity = Tracing.Source.StartActivity("order.submit");
        activity?.SetTag("tenant.id", tenantId);
        activity?.SetTag("order.id", orderId);
        try
        {
            await ChargeAsync(orderId);
        }
        catch (Exception ex)
        {
            activity?.RecordException(ex);
            activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
            throw;
        }
    }
}

Activity.RecordException(Exception) lives in OpenTelemetry.Traceusing OpenTelemetry.Trace; for the extension.

Logs

ILogger<T> calls already carry the active Activity's TraceId / SpanId once AddOpenTelemetry is wired into builder.Logging. Don't replace the user's existing logger; add OTLP underneath.

Coexistence

If the project already exports to Application Insights, Datadog, or Honeycomb, keep those. AddOpenTelemetry().WithTracing(...) accepts multiple exporters — chain .AddOtlpExporter(...) for Maple alongside the existing one.

为基于 Effect-TS 的应用提供 Maple OpenTelemetry 集成。通过 @maple-dev/effect-sdk 实现链路追踪、日志关联与自动属性填充,支持 Node.js/Cloudflare/Browser 等多运行时环境的 Bootstrap 配置。
用户询问如何在 Effect 应用中集成 Maple 的 OpenTelemetry 功能 用户需要配置 Maple 的 TracerLive Layer 以启用链路追踪和日志 用户询问 Effect SDK 在不同运行时(如 Cloudflare Workers)的初始化方式
skills/maple-effect-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-effect-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-effect-style",
    "description": "Effect-TS OpenTelemetry style for Maple via @maple-dev\/effect-sdk: Maple.layer() bootstrap, Effect.withSpan \/ Effect.annotateCurrentSpan call sites, Effect.log for trace-correlated logging, server \/ browser \/ Cloudflare entry points."
}

Maple Effect style

For Effect apps, use @maple-dev/effect-sdk — Maple's first-class Effect SDK. It wraps Effect's built-in Otlp.layerJson exporter and handles batching, shutdown, and resource attributes for you.

Install

npm install @maple-dev/effect-sdk effect

For Effect 3, use @maple-dev/effect-sdk@effect-v3 instead. Same API, different peer ranges.

Bootstrap

Pick the entry point per runtime — they have different lifecycle requirements:

  • Server (Node.js, Bun, Deno): background-export fiber, env-var auto-detection, graceful shutdown.
  • Browser: explicit config (no env vars), browser metadata baked in.
  • Cloudflare Workers: manual flush() in ctx.waitUntil, lazy env resolution, in-isolate buffering.

Server

import { Maple } from "@maple-dev/effect-sdk"
import { Effect, Layer } from "effect"

const TracerLive = Maple.layer({
	serviceName: "orders-api",
	endpoint: "https://ingest.maple.dev",
	ingestKey: "MAPLE_TEST", // set by maple-onboard skill on pairing
	attributes: {
		"vcs.repository.url.full": "https://github.com/acme/orders-api",
	},
})

const program = Effect.gen(function* () {
	yield* Effect.log("Order received")
}).pipe(Effect.withSpan("order.submit"))

Effect.runPromise(program.pipe(Effect.provide(TracerLive)))

The default import resolves to the server build under Node.js. Import @maple-dev/effect-sdk/server explicitly when needed.

If endpoint is omitted, the server layer auto-detects it from MAPLE_ENDPOINT (falling back to OTEL_EXPORTER_OTLP_ENDPOINT). When MAPLE_ENDPOINT is unset, the layer is a no-op — local dev runs without exporting. Inline the endpoint when you want telemetry to flow regardless of env (matches the rest of the maple-onboard inline-key pattern).

The server layer also auto-fills vcs.ref.head.revision from COMMIT_SHA / RAILWAY_GIT_COMMIT_SHA / VERCEL_GIT_COMMIT_SHA / CF_PAGES_COMMIT_SHA / RENDER_GIT_COMMIT (first match wins). You should still set vcs.repository.url.full in attributes since no env var carries it.

Cloudflare Workers

import { Maple } from "@maple-dev/effect-sdk/cloudflare"
import { Effect } from "effect"

export default {
	async fetch(req: Request, env: Env, ctx: ExecutionContext) {
		const TracerLive = Maple.layer({
			serviceName: "orders-edge",
			endpoint: "https://ingest.maple.dev",
			ingestKey: "MAPLE_TEST",
		})

		const program = Effect.gen(function* () {
			yield* Effect.log("edge request")
			return new Response("ok")
		}).pipe(Effect.withSpan("edge.handle"))

		const response = await Effect.runPromise(program.pipe(Effect.provide(TracerLive)))
		ctx.waitUntil(Maple.flush())
		return response
	},
}

The Cloudflare entry point requires ctx.waitUntil(Maple.flush()) so telemetry survives the isolate exit. Forgetting this is the most common reason traces don't show up from Workers.

Browser

import { Maple } from "@maple-dev/effect-sdk/browser"

const TracerLive = Maple.layer({
	serviceName: "web-client",
	endpoint: "https://ingest.maple.dev",
	ingestKey: "MAPLE_TEST",
})

No env-var fallback in the browser entry point — config is always explicit.

Custom spans

Use Effect.withSpan to trace operations and Effect.annotateCurrentSpan for attributes — don't reach for the raw @opentelemetry/api tracer when an Effect-native primitive is available.

const processOrder = (orderId: string) =>
	Effect.gen(function* () {
		yield* Effect.annotateCurrentSpan("order.id", orderId)
		yield* Effect.annotateCurrentSpan("peer.service", "payment-api")
		const result = yield* chargePayment(orderId)
		return result
	}).pipe(Effect.withSpan("order.process"))

Setting peer.service on outgoing calls makes them visible on Maple's service map.

Effect.fail and uncaught defects are recorded as exceptions and set the span status to ERROR automatically — you don't need to wrap with try / catch / finally.

@maple/otel-helpers withSpan is for non-Effect TypeScript code; in Effect code prefer the Effect-native span primitives.

Logs

Effect.log automatically includes trace context when called inside a span — no additional setup needed:

const program = Effect.gen(function* () {
	yield* Effect.log("Processing started")
	yield* doWork()
	yield* Effect.log("Processing complete")
}).pipe(Effect.withSpan("process"))

Logs emitted inside spans are correlated with the active trace in the Maple dashboard.

Coexistence

If the project already uses @effect/opentelemetry or Otlp.layerJson with a custom exporter (e.g. for Honeycomb, Datadog), keep it. Maple.layer() can compose alongside via Layer.merge:

const TracerLive = Layer.merge(
	Maple.layer({ serviceName: "api", endpoint: "https://ingest.maple.dev", ingestKey: "MAPLE_TEST" }),
	HoneycombLayer,
)

Both vendors receive the same spans.

为Maple平台提供Go语言OpenTelemetry集成方案。支持通过HTTP协议导出Trace、Log和Metric数据至ingest.maple.dev,自动注入VCS资源属性,并封装初始化和优雅关闭逻辑。
用户需要在Go项目中集成OpenTelemetry遥测数据 用户希望将Traces、Logs或Metrics发送至Maple平台 用户询问Go语言的遥测初始化配置
skills/maple-go-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-go-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-go-style",
    "description": "Go OpenTelemetry style for Maple: go.opentelemetry.io\/otel SDK with otlptracehttp \/ otlploghttp \/ otlpmetrichttp exporters, inline endpoint + ingest key, semconv resource attributes including vcs.repository.url.full."
}

Maple Go style

Use the official go.opentelemetry.io/otel SDK with the HTTP exporters. Initialize once at process start and shut down on signal.

Install

go get \
  go.opentelemetry.io/otel \
  go.opentelemetry.io/otel/sdk \
  go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
  go.opentelemetry.io/otel/exporters/otlp/otlplogs/otlploghttp \
  go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp \
  go.opentelemetry.io/otel/log \
  go.opentelemetry.io/otel/sdk/log

Bootstrap

Inline the endpoint and ingest key — they're a project-scoped, write-only token (Sentry-DSN-shaped). No env-var indirection.

package telemetry

import (
	"context"
	"os"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/otlp/otlplogs/otlploghttp"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/log/global"
	sdklog "go.opentelemetry.io/otel/sdk/log"
	"go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/resource"
	"go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

const (
	mapleEndpoint = "ingest.maple.dev"
	mapleKey      = "MAPLE_TEST" // set by maple-onboard skill on pairing
)

func Init(ctx context.Context) (shutdown func(context.Context) error, err error) {
	headers := map[string]string{"authorization": "Bearer " + mapleKey}

	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceName("orders-api"),
			semconv.DeploymentEnvironment(envOr("DEPLOYMENT_ENV", "development")),
			attribute.String("vcs.repository.url.full", "https://github.com/acme/orders-api"),
			attribute.String("vcs.ref.head.revision", envOr("GITHUB_SHA", envOr("GIT_COMMIT", ""))),
		),
	)
	if err != nil {
		return nil, err
	}

	traceExp, err := otlptracehttp.New(ctx,
		otlptracehttp.WithEndpoint(mapleEndpoint),
		otlptracehttp.WithHeaders(headers),
	)
	if err != nil {
		return nil, err
	}
	tp := trace.NewTracerProvider(trace.WithBatcher(traceExp), trace.WithResource(res))
	otel.SetTracerProvider(tp)

	logExp, err := otlploghttp.New(ctx,
		otlploghttp.WithEndpoint(mapleEndpoint),
		otlploghttp.WithHeaders(headers),
	)
	if err != nil {
		return nil, err
	}
	lp := sdklog.NewLoggerProvider(
		sdklog.WithProcessor(sdklog.NewBatchProcessor(logExp)),
		sdklog.WithResource(res),
	)
	global.SetLoggerProvider(lp)

	metricExp, err := otlpmetrichttp.New(ctx,
		otlpmetrichttp.WithEndpoint(mapleEndpoint),
		otlpmetrichttp.WithHeaders(headers),
	)
	if err != nil {
		return nil, err
	}
	mp := metric.NewMeterProvider(
		metric.WithReader(metric.NewPeriodicReader(metricExp)),
		metric.WithResource(res),
	)
	otel.SetMeterProvider(mp)

	return func(ctx context.Context) error {
		_ = tp.Shutdown(ctx)
		_ = lp.Shutdown(ctx)
		return mp.Shutdown(ctx)
	}, nil
}

func envOr(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

Wire from main:

func main() {
	ctx := context.Background()
	shutdown, err := telemetry.Init(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer shutdown(ctx)

	// app start
}

Bounded business spans

Acquire the tracer at package scope, start spans where auto-instrumentation is blind, set the status on error, end the span via defer.

var tracer = otel.Tracer("orders.api")

func SubmitOrder(ctx context.Context, orderID string) (err error) {
	ctx, span := tracer.Start(ctx, "order.submit")
	defer func() {
		if err != nil {
			span.RecordError(err)
			span.SetStatus(codes.Error, err.Error())
		}
		span.End()
	}()

	span.SetAttributes(attribute.String("order.id", orderID))
	return chargeOrder(ctx, orderID)
}

Auto-instrumentation

Go does not have framework-level auto-discovery. Add the official contrib packages for the libraries the app actually uses (otelhttp, otelgrpc, otelsql, otelpgx, otelmux, otelfiber, etc.). Don't add packages the app doesn't import.

Coexistence

If the project already exports to Honeycomb / Datadog / Tempo, install Maple's exporter alongside via trace.WithBatcher(traceExp) for each backend. Don't strip the existing exporter unless the user asks.

提供Maple平台Java应用的可观测性集成指南。支持通过OpenTelemetry Java Agent实现零代码自动埋点,或手动配置SDK发送Traces、Metrics和Logs至OTLP HTTP端点,兼容Spring、JDBC等主流框架及GraalVM环境。
用户询问如何在Java项目中集成Maple可观测性 需要配置OpenTelemetry Java Agent或SDK的示例代码 解决GraalVM或嵌入式JVM下的手动SDK接入问题
skills/maple-java-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-java-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-java-style",
    "description": "Java OpenTelemetry style for Maple: zero-code Java agent or manual SDK with OTLP HTTP exporters, inline endpoint + ingest key, semconv resource attributes, OTLP-bridged Logback \/ SLF4J logs."
}

Maple Java style

The fastest path is the OpenTelemetry Java Agent — it auto-instruments the JVM with zero code changes.

Zero-code: Java agent

curl -sLO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

Inline the endpoint and ingest key as JVM args (these are the agent's only configuration surface — they map straight onto the inline-key model):

java \
  -javaagent:./opentelemetry-javaagent.jar \
  -Dotel.service.name=orders-api \
  -Dotel.exporter.otlp.protocol=http/protobuf \
  -Dotel.exporter.otlp.endpoint=https://ingest.maple.dev \
  -Dotel.exporter.otlp.headers="authorization=Bearer MAPLE_TEST" \
  -Dotel.resource.attributes="vcs.repository.url.full=https://github.com/acme/orders-api,vcs.ref.head.revision=${GITHUB_SHA:-}" \
  -jar build/libs/app.jar

Replace MAPLE_TEST with the project's real Maple ingest key once it's available. Keep the args inline (in Procfile / Dockerfile / systemd unit / application.yml) — don't move them behind unset env vars.

The agent auto-instruments Spring (Boot, MVC, WebFlux), Servlet containers, Apache HttpClient, OkHttp, JDBC, R2DBC, Hibernate, Kafka, gRPC, AWS SDK, and many more.

Manual SDK (when the agent isn't an option)

For cases where the agent can't run (GraalVM native image, embedded JVM, sealed module path), use the SDK directly:

<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-logback-appender-1.0</artifactId>
</dependency>
public final class Telemetry {
    private static final String MAPLE_ENDPOINT = "https://ingest.maple.dev";
    private static final String MAPLE_KEY = "MAPLE_TEST"; // set by maple-onboard skill on pairing

    public static OpenTelemetrySdk init() {
        var headers = Map.of("authorization", "Bearer " + MAPLE_KEY);
        var resource = Resource.getDefault().merge(Resource.create(Attributes.builder()
            .put(ServiceAttributes.SERVICE_NAME, "orders-api")
            .put(DeploymentIncubatingAttributes.DEPLOYMENT_ENVIRONMENT_NAME,
                System.getenv().getOrDefault("DEPLOYMENT_ENV", "development"))
            .put("vcs.repository.url.full", "https://github.com/acme/orders-api")
            .put("vcs.ref.head.revision", System.getenv().getOrDefault("GITHUB_SHA", ""))
            .build()));

        var spanExporter = OtlpHttpSpanExporter.builder()
            .setEndpoint(MAPLE_ENDPOINT + "/v1/traces")
            .setHeaders(() -> headers)
            .build();
        // … same shape for OtlpHttpLogRecordExporter and OtlpHttpMetricExporter

        return OpenTelemetrySdk.builder()
            .setTracerProvider(SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
                .setResource(resource)
                .build())
            .buildAndRegisterGlobal();
    }
}

Logs

Bridge the existing Logback / SLF4J / Log4j2 setup through OTLP — don't replace it. With the Java agent, log appenders are auto-bridged. With the manual SDK, add the opentelemetry-logback-appender-1.0 (or the Log4j 2 equivalent) and configure it in logback.xml so existing logger calls carry trace_id / span_id and reach Maple.

Bounded business spans

Acquire the tracer at class scope; wrap operations the agent's auto-instrumentation can't see.

private static final Tracer TRACER = GlobalOpenTelemetry.getTracer("orders.api");

public Order submit(String orderId, String tenantId) {
    var span = TRACER.spanBuilder("order.submit")
        .setAttribute("tenant.id", tenantId)
        .setAttribute("order.id", orderId)
        .startSpan();
    try (var scope = span.makeCurrent()) {
        return charge(orderId);
    } catch (Exception e) {
        span.recordException(e);
        span.setStatus(StatusCode.ERROR, e.getMessage());
        throw e;
    } finally {
        span.end();
    }
}

Coexistence

If the project already runs Datadog / New Relic / Honeycomb agents, leave them in place. The OpenTelemetry Java Agent coexists with most APMs (test the combination once before shipping). Don't strip an incumbent agent unless the user asks.

提供Kotlin项目集成OpenTelemetry的两种方案:推荐零代码Java Agent自动接入Spring Boot/Ktor,或手动SDK配置。支持OTLP HTTP导出、资源属性注入及业务Span追踪,适用于JVM环境。
需要为Kotlin/JVM应用添加分布式追踪 配置Spring Boot或Ktor的OpenTelemetry监控 设置Maple可观测性平台的数据摄入端点
skills/maple-kotlin-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-kotlin-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-kotlin-style",
    "description": "Kotlin (Ktor, Spring Boot) OpenTelemetry style for Maple: zero-code Java agent or manual SDK with OTLP HTTP exporters, inline endpoint + ingest key, semconv resource attributes, OTLP-bridged logs."
}

Maple Kotlin style

Kotlin runs on the JVM, so the same OpenTelemetry Java agent and SDK apply. Prefer the agent for Spring Boot / Ktor servers; fall back to the manual SDK only for native-image or sealed-module builds.

Zero-code: Java agent (recommended)

curl -sLO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

java \
  -javaagent:./opentelemetry-javaagent.jar \
  -Dotel.service.name=orders-api \
  -Dotel.exporter.otlp.protocol=http/protobuf \
  -Dotel.exporter.otlp.endpoint=https://ingest.maple.dev \
  -Dotel.exporter.otlp.headers="authorization=Bearer MAPLE_TEST" \
  -Dotel.resource.attributes="vcs.repository.url.full=https://github.com/acme/orders-api,vcs.ref.head.revision=${GITHUB_SHA:-}" \
  -jar build/libs/app.jar

Replace MAPLE_TEST with the project's real Maple ingest key once available. Keep these flags inline (in Procfile / Dockerfile / application.yml) — don't move them behind unset env vars.

The agent auto-instruments Ktor, Spring Boot (MVC, WebFlux), Coroutines, Exposed, R2DBC, Kafka, gRPC, OkHttp, AWS SDK, and many more.

Manual SDK (Ktor, no agent)

val MAPLE_ENDPOINT = "https://ingest.maple.dev"
val MAPLE_KEY = "MAPLE_TEST" // set by maple-onboard skill on pairing

fun initTelemetry(): OpenTelemetrySdk {
    val headers = mapOf("authorization" to "Bearer $MAPLE_KEY")
    val resource = Resource.getDefault().merge(Resource.create(
        Attributes.builder()
            .put(ServiceAttributes.SERVICE_NAME, "orders-api")
            .put(DeploymentIncubatingAttributes.DEPLOYMENT_ENVIRONMENT_NAME,
                System.getenv("DEPLOYMENT_ENV") ?: "development")
            .put("vcs.repository.url.full", "https://github.com/acme/orders-api")
            .put("vcs.ref.head.revision", System.getenv("GITHUB_SHA") ?: "")
            .build()))

    val spanExporter = OtlpHttpSpanExporter.builder()
        .setEndpoint("$MAPLE_ENDPOINT/v1/traces")
        .setHeaders { headers }
        .build()

    return OpenTelemetrySdk.builder()
        .setTracerProvider(SdkTracerProvider.builder()
            .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
            .setResource(resource)
            .build())
        .buildAndRegisterGlobal()
}

Add the equivalent log + metric exporters in the same builder.

Bounded business spans

private val tracer = GlobalOpenTelemetry.getTracer("orders.api")

suspend fun submitOrder(orderId: String, tenantId: String) {
    val span = tracer.spanBuilder("order.submit")
        .setAttribute("tenant.id", tenantId)
        .setAttribute("order.id", orderId)
        .startSpan()
    try {
        span.makeCurrent().use {
            chargeOrder(orderId)
        }
    } catch (e: Exception) {
        span.recordException(e)
        span.setStatus(StatusCode.ERROR, e.message ?: "")
        throw e
    } finally {
        span.end()
    }
}

For coroutines, use the kotlinx-coroutines-extension (opentelemetry-kotlin-extension) so context propagates across withContext boundaries.

Logs

Bridge whatever the project uses (Logback, SLF4J, Log4j2). With the agent, log appenders are auto-bridged. With the manual SDK, add opentelemetry-logback-appender-1.0 (or Log4j 2 equivalent) and configure it in logback.xml so existing logger calls carry trace_id / span_id and reach Maple. Don't replace the user's existing logger.

Coexistence

If the project runs Datadog / New Relic / Honeycomb, leave them in place. Test the combination once before shipping.

提供Next.js/Vercel环境下Maple APM的OpenTelemetry集成方案。支持通过registerOTel自动注入遥测数据,并演示如何为Anthropic等LLM Provider添加专用Instrumentation,以及使用withSpan在Route Handlers中手动创建业务追踪跨度。
用户询问如何在Next.js应用中集成Maple APM或OpenTelemetry 需要配置Vercel环境下的Trace Exporter和日志导出 请求为Anthropic SDK或其他LLM Provider添加自动化监控 需要在Next.js Route Handler中手动创建和管理Span
skills/maple-nextjs-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-nextjs-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-nextjs-style",
    "description": "Next.js \/ Vercel OpenTelemetry style for Maple: instrumentation.ts, @vercel\/otel bootstrap, native @opentelemetry\/api call sites, inline endpoint + ingest key, no raw NodeSDK replacement."
}

Maple Next.js style

For Next.js apps, prefer the framework entrypoint.

// instrumentation.ts
import { registerOTel } from "@vercel/otel"

const MAPLE_ENDPOINT = "https://ingest.maple.dev"
const MAPLE_KEY = "MAPLE_TEST" // set by maple-onboard skill on pairing

export function register() {
	registerOTel({
		serviceName: "my-next-app",
		attributes: {
			"deployment.environment.name": process.env.VERCEL_ENV ?? "development",
			"vcs.repository.url.full": "https://github.com/acme/my-next-app",
			"vcs.ref.head.revision": process.env.VERCEL_GIT_COMMIT_SHA,
		},
		traceExporter: {
			url: `${MAPLE_ENDPOINT}/v1/traces`,
			headers: { authorization: `Bearer ${MAPLE_KEY}` },
		},
	})
}

Do not replace this with a custom NodeSDK bootstrap unless the repo is not a normal Next/Vercel app or already has a custom provider that must be extended.

For JavaScript/TypeScript LLM providers, prefer provider instrumentation over manual child spans. For Anthropic, add OpenInference in the same bootstrap and keep call sites native. This example uses @vercel/otel@2.x; if the installed types are v1, use logRecordProcessor singular instead.

import Anthropic from "@anthropic-ai/sdk"
import { AnthropicInstrumentation } from "@arizeai/openinference-instrumentation-anthropic"
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"
import { registerOTel } from "@vercel/otel"

const MAPLE_ENDPOINT = "https://ingest.maple.dev"
const MAPLE_KEY = "MAPLE_TEST"

const anthropicInstrumentation = new AnthropicInstrumentation({
	traceConfig: {
		hideInputs: true,
		hideOutputs: true,
	},
})

anthropicInstrumentation.manuallyInstrument(Anthropic)

export function register() {
	registerOTel({
		serviceName: "my-next-app",
		instrumentations: [anthropicInstrumentation],
		traceExporter: {
			url: `${MAPLE_ENDPOINT}/v1/traces`,
			headers: { authorization: `Bearer ${MAPLE_KEY}` },
		},
		logRecordProcessors: [
			new BatchLogRecordProcessor(
				new OTLPLogExporter({
					url: `${MAPLE_ENDPOINT}/v1/logs`,
					headers: { authorization: `Bearer ${MAPLE_KEY}` },
				}),
			),
		],
	})
}

Route handlers

Use native OTel APIs where auto-instrumentation is blind.

import { trace, metrics } from "@opentelemetry/api"
import { withSpan } from "@maple/otel-helpers"

const tracer = trace.getTracer("my-next-app")
const meter = metrics.getMeter("my-next-app")
const generated = meter.createCounter("mug.copy.generated")

export async function POST(request: Request) {
	const tenantId = request.headers.get("x-tenant-id") ?? "tenant_demo"
	return withSpan(
		"mug.copy.generate",
		async (span) => {
			span.setAttribute("tenant.id", tenantId)
			generated.add(1, { "tenant.id": tenantId, outcome: "success" })
			return Response.json({ ok: true })
		},
		{ tracer },
	)
}

For TypeScript route handlers, use @maple/otel-helpers withSpan for bounded business spans and add @maple/otel-helpers to package.json when it is not already present. This is required when the package can be installed. It keeps span lifecycle / error handling out of the handler body and avoids a large indentation diff. Do not expand the whole route into tracer.startActiveSpan(...) plus try / catch / finally unless the helper cannot be added or the span has a true cross-callback lifecycle.

If a route has an LLM call and OpenInference / provider instrumentation supports that SDK, do not wrap the provider call. Leave client.messages.create(...) / equivalent in place and put business context on the active product span or structured log. Do not duplicate provider/model/token attributes in route-level spans, logs, or metrics when OpenInference already reports them. Do not calculate LLM cost in route handlers; Maple derives estimated cost in the UI/query layer from OpenInference provider/model/token attributes. For Anthropic in Next.js/ESM, keep the instrumentation instance and manuallyInstrument(Anthropic) call at module scope so it runs once and before route code.

Match the @vercel/otel logs option to the installed package/types: @vercel/otel@1.x uses logRecordProcessor singular, while @vercel/otel@2.x uses logRecordProcessors plural. For normal Next.js / Vercel apps, do not guard registerOTel(...) behind NEXT_RUNTIME; Next calls instrumentation.ts in the appropriate runtime and @vercel/otel handles its own runtime differences.

console.info is not OTLP log export. If there is no existing logger bridge, use @opentelemetry/api-logs for production log records. Remove pre-existing console.* calls that duplicate the same structured OTel log event:

import { logs, SeverityNumber } from "@opentelemetry/api-logs"

const logger = logs.getLogger("my-next-app")

logger.emit({
	severityNumber: SeverityNumber.INFO,
	severityText: "INFO",
	body: "generated mug copy",
	attributes: {
		"tenant.id": tenantId,
		"gen_ai.provider.name": "anthropic",
		"gen_ai.request.model": model,
		"app.gen_ai.use_case": "web.mug_copy",
		outcome: "success",
	},
})

Configuration and smoke

Inline the endpoint and ingest key in instrumentation.ts — pass them explicitly to registerOTel. Don't rely on OTEL_EXPORTER_OTLP_* env vars: the Maple ingest key is project-scoped + write-only and inline config sidesteps Vercel's env-propagation quirks during preview builds.

Smoke checks should use tools already in the repo, e.g. npm run typecheck or npm run build, plus a real app request where practical. Do not invent fragile inline Node scripts that import TypeScript source files directly, and do not assume ts-node exists unless it is already installed.

为Maple平台提供Node.js应用的OpenTelemetry集成规范。支持Express/Fastify等框架,通过--import引导SDK启动,配置OTLP HTTP导出器及自动插桩,并提供路由级手动埋点示例。
用户需要在Node.js项目中集成OpenTelemetry监控 用户询问如何配置Maple平台的Trace、Log和Metric数据上报
skills/maple-nodejs-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-nodejs-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-nodejs-style",
    "description": "Plain Node.js (Express, Fastify, Hono, Bun) OpenTelemetry style for Maple: NodeSDK + --import bootstrap, native @opentelemetry\/api call sites, inline endpoint + ingest key, OTLP HTTP exporters."
}

Maple Node.js style

Use @opentelemetry/sdk-node with --import (or the equivalent Bun --preload) so the SDK starts before any framework code runs.

// telemetry.ts
import { NodeSDK } from "@opentelemetry/sdk-node"
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"
import { resourceFromAttributes } from "@opentelemetry/resources"

const MAPLE_ENDPOINT = "https://ingest.maple.dev"
const MAPLE_KEY = "MAPLE_TEST" // set by maple-onboard skill on pairing

const headers = { authorization: `Bearer ${MAPLE_KEY}` }

const sdk = new NodeSDK({
	resource: resourceFromAttributes({
		"service.name": "my-node-app",
		"deployment.environment.name": process.env.NODE_ENV ?? "development",
		"vcs.repository.url.full": "https://github.com/acme/my-node-app",
		"vcs.ref.head.revision":
			process.env.RAILWAY_GIT_COMMIT_SHA ??
			process.env.GITHUB_SHA ??
			process.env.GIT_COMMIT,
	}),
	traceExporter: new OTLPTraceExporter({
		url: `${MAPLE_ENDPOINT}/v1/traces`,
		headers,
	}),
	logRecordProcessors: [
		new BatchLogRecordProcessor(
			new OTLPLogExporter({ url: `${MAPLE_ENDPOINT}/v1/logs`, headers }),
		),
	],
	metricReader: new PeriodicExportingMetricReader({
		exporter: new OTLPMetricExporter({
			url: `${MAPLE_ENDPOINT}/v1/metrics`,
			headers,
		}),
	}),
	instrumentations: [getNodeAutoInstrumentations()],
})

sdk.start()

Run the app with the bootstrap loaded first:

node --import ./telemetry.js app.js

For TypeScript projects, use the loader the repo already uses (tsx, ts-node/esm, native Bun, etc.) — do not introduce a new loader.

Bootstrap rules

  • HTTP OTLP exporters only, never gRPC. gRPC pulls in native bindings that complicate containers.
  • getNodeAutoInstrumentations() covers HTTP, Express, Fastify, Hono, pg, MySQL, Redis, and many more out of the box. Disable specific instrumentations only when they actively break the app:
    etNodeAutoInstrumentations({
    "@opentelemetry/instrumentation-fs": { enabled: false },
    )
    
  • For Bun, use the same SDK with bun --preload ./telemetry.ts run app.ts. Bun's HTTP exporter compatibility is good; if you hit an issue, fall back to node for the bootstrap process.

Route handlers and business operations

Use the native API; reach for withSpan from @maple/otel-helpers for bounded operations.

import { trace, metrics } from "@opentelemetry/api"
import { withSpan } from "@maple/otel-helpers"

const tracer = trace.getTracer("orders.api")
const meter = metrics.getMeter("orders.api")
const submitted = meter.createCounter("orders.submitted")

app.post("/orders", async (req, res) => {
	await withSpan(
		"order.submit",
		async (span) => {
			span.setAttributes({
				"tenant.id": req.headers["x-tenant-id"] as string,
				"order.id": req.body.id,
			})
			await chargeOrder(req.body)
			submitted.add(1, { "tenant.id": req.headers["x-tenant-id"] as string })
			res.json({ ok: true })
		},
		{ tracer },
	)
})

Logs

Bridge the existing logger (Pino, Winston, console) through OTLP rather than replacing it. For Pino, install @opentelemetry/instrumentation-pino and include it in instrumentations. For Winston, install @opentelemetry/instrumentation-winston. The user's logger keeps its current sinks; you're adding OTLP underneath so logs carry trace_id / span_id and reach Maple. Do not rip out the existing logger.

Coexistence

If the repo has Sentry, Datadog, New Relic, Honeycomb, Logtail, or a Pino transport, leave them in place. They sit alongside Maple, not instead of it.

将项目全面接入Maple,通过为仓库内所有应用和服务安装OpenTelemetry来采集 traces、logs 和 metrics。支持多种语言栈,自动处理端点配置与 API Key 内联,实现无感 telemetry 上报。
install Maple set up Maple add Maple telemetry onboard this repo to Maple instrument with OpenTelemetry for Maple
skills/maple-onboard/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-onboard -g -y
SKILL.md
Frontmatter
{
    "name": "maple-onboard",
    "description": "Onboard a project to Maple by installing OpenTelemetry traces, logs, and metrics across every app and service in the repo. Triggers on requests like 'install Maple', 'set up Maple', 'add Maple telemetry', 'onboard this repo to Maple', 'instrument with OpenTelemetry for Maple'."
}

Maple onboarding

Wire OpenTelemetry traces, logs, and metrics into the user's project so telemetry streams to Maple. Cover every app and service in the repo — not just the one the user is currently sitting in.

Prefer native OpenTelemetry APIs and the framework's documented bootstrap over custom helper layers. If a specific stack stumps you, search the OTel docs for that language; don't guess.

Before editing, read the applicable companion skills:

  • maple-onboarding-style for general OTel taste, VCS attributes, and smoke checks.
  • maple-nextjs-style for Next.js / Vercel apps.
  • maple-nodejs-style for plain Node servers (Express, Fastify, Hono, Bun).
  • maple-python-style for Python services (FastAPI, Django, Flask).
  • maple-effect-style for Effect-based services — Maple's first-class SDK.
  • maple-go-style, maple-rust-style, maple-java-style, maple-csharp-style, maple-kotlin-style for those stacks.

If none match (Ruby, Elixir, PHP, plain Deno, …), use maple-onboarding-style as the fallback and consult the upstream OTel docs for that language.

Step 0 — Endpoint and key handling

The OTLP endpoint is always https://ingest.maple.dev and goes inline in the bootstrap code — it's not a secret, no env-var indirection needed.

The ingest API key is project-scoped + write-only — it can only ingest events for one project, can't read anything, can't change settings. Treat it like a Sentry DSN, a PostHog public key, or a Datadog RUM client token: inline it directly in the OTel bootstrap source alongside the endpoint. No .env files, no deploy-target wiring, no process.env.OTEL_EXPORTER_OTLP_HEADERS. The user deploys their code and events flow.

Two paths, no questions asked:

Key in the prompt

If the invoking prompt already contains a Maple ingest key, inline it in every bootstrap file you write. Done — move on to Step 1.

No key

Inline the literal sentinel MAPLE_TEST in the bootstrap source as a placeholder. Maple's ingest gateway accepts it from anyone, returns 200 without forwarding anywhere, so the user's app can boot and exercise the OTel bootstrap path while they go create a real key.

Tell the user briefly at the top of your work: "I'm using MAPLE_TEST as a placeholder so the bootstrap can run. Create a real key at https://app.maple.dev/settings (Settings → API Keys) and search-replace the literal MAPLE_TEST in the files I write to swap it in."

Then keep going. Don't block install on signup.

Step 1 — Map every app/service in the repo

Before instrumenting anything, enumerate what's here. Check workspace manifests (pnpm-workspace.yaml, root package.json workspaces, bun workspaces, go.work, Cargo workspace, Python pyproject.toml workspace setups, apps/* and services/* conventions). Identify each service: web frontend, API, workers, background jobs, CLIs, sample/demo apps, mobile apps, Supabase and/or server functions. Mobile and serverless/edge functions are in scope; do not skip them merely because they are client-side or short-lived. Skip pure type/config packages with no runtime entry point. Do not skip any runnable services or leave them "out of scope": instrument absolutely everything in this run; there may be no follow-up.

Show the user the list before you start, so they can correct it.

Step 2 — For each service, install native OTel and bootstrap

Use the language's native OpenTelemetry SDK. Don't reach for vendor wrappers or hand-rolled helpers when an official package exists. Examples of what "native" means here: @opentelemetry/sdk-node for Node servers, @vercel/otel for normal Next.js/Vercel apps (sdk-node breaks Next's webpack and misses the framework bootstrap), @opentelemetry/sdk-trace-web + browser/mobile-compatible exporters for Vite/SPA/Expo, opentelemetry-instrumentation-* + opentelemetry-sdk for Python, go.opentelemetry.io/otel for Go.

No broad wrapper APIs. Avoid reusable helpers like sendMapleSpan, recordCounter, recordLog, startTelemetrySpan, or withTelemetry. Acquire native tracers/meters/loggers at module scope and use the SDK's own APIs directly. In TypeScript/JavaScript, use the published @maple/otel-helpers withSpan helper for bounded business spans and add @maple/otel-helpers to package.json; this is required when the package can be installed because it avoids expanding a whole function into startActiveSpan plus try / catch / finally. Do not use helpers around provider SDK calls that OpenInference/provider instrumentation can observe directly.

Wire all three signals — traces, logs, metrics. Logs go through OTLP, not just stdout — set up the OTel log bridge for the language so app logs (with their existing log levels and structured fields) carry the active trace_id / span_id automatically. The user's existing logger keeps working; you're just adding an OTLP handler/processor underneath.

Bootstrap rules:

  • The bootstrap file must run before any framework imports. Use the language/framework's documented hook (--import flag, instrumentation.ts, top-of-main.py import, etc.).
  • Inline the endpoint (https://ingest.maple.dev) and the project's ingest key directly in the bootstrap source. Don't read from process.env.OTEL_EXPORTER_OTLP_* or write any .env files — the key is write-only, and inline configuration removes a whole class of "OTel didn't start because env vars weren't set" deploy failures. (See the framework-specific style skills for the exact shape per stack.)
  • Use HTTP OTLP exporters, not gRPC. gRPC pulls in native bindings that break bundlers and complicate containers.
  • Use the project's existing package manager (detect via lockfile).
  • Prefer idempotent edits. If a config file already exists, edit don't overwrite.
  • Set resource attributes on the OTel resource for every service: service.name, service.version, deployment.environment.name, and vcs.repository.url.full — the canonical https URL of the repo (e.g. https://github.com/acme/api). The repo URL is the important one and is fine to hardcode alongside service.name in the SDK init; if the build platform exposes the slug (Vercel VERCEL_GIT_REPO_OWNER/VERCEL_GIT_REPO_SLUG, Railway RAILWAY_GIT_REPO_OWNER/RAILWAY_GIT_REPO_NAME), prefer reading from env. Also set vcs.ref.head.revision (commit SHA) on a best-effort basis from whatever env the runtime already injects (VERCEL_GIT_COMMIT_SHA, RAILWAY_GIT_COMMIT_SHA, GITHUB_SHA, SOURCE_COMMIT, GIT_COMMIT, HEROKU_SLUG_COMMIT, …). Do not shell out to git from the running process. Skipping the SHA is fine, skipping the URL is not. Use the OTel semantic-convention keys exactly — do not invent git.repo / app.repo_url / deployment.commit_sha.

Framework rules:

  • Next.js/Vercel: use instrumentation.ts with @vercel/otel registerOTel(...) as the bootstrap. Do not substitute a raw @opentelemetry/sdk-node / NodeSDK bootstrap unless the repo already uses that architecture and you are extending it. Use @opentelemetry/api tracers/meters inside route handlers only where auto-instrumentation is blind.
  • Expo/React Native: preserve existing Expo Go / unsupported-runtime guards. In supported builds, call initObservability() before Sentry and before app registration/user code. Inline the endpoint + ingest key in the observability module — no EXPO_PUBLIC_* env vars. The bootstrap reads them straight from constants.
  • Supabase Edge Functions / Cloudflare Workers: native Deno / Workers OpenTelemetry can be quirky. Keep the exporter shim tiny, provider-neutral, and OTel-shaped: tracer.startActiveSpan, span.setAttributes, SpanStatusCode, meter.createCounter, histogram.record. For Effect-on-Workers, prefer @maple/effect-cloudflare (see maple-effect-style).
  • Python/FastAPI: use native instrumentation such as FastAPIInstrumentor.instrument_app(app) rather than replacing request handling with manual middleware.

Coexist with existing observability vendors. Don't remove Sentry, Datadog, New Relic, Honeycomb, Logtail, Pino transports, etc. OTel sits alongside them. The user explicitly wants both signals flowing during migration; ripping out the incumbent is not your call.

Step 3 — Add custom spans, metrics, and logs around business operations

Auto-instrumentation gets you HTTP in/out, DB queries, framework lifecycle. That's the floor, not the ceiling. Read the project to find the operations a human operator would actually want to see when something looks wrong.

Traces

Wrap every critical business operation with an active span. Auto-instrumented spans are fine where they exist — but if an operation isn't already getting a span, add one.

  • Naming: domain.verb (order.process, payment.charge, email.send, agent.run, interview.create, job.<type>).
  • Attributes: entity IDs (order.id, user.id, workspace.id, tenant.id), counts, key boolean branch outcomes, model name / provider for LLM calls.
  • Record exceptions: span.recordException(err) + span.setStatus({ code: ERROR }) on failure paths. In TS/JS use withSpan from @maple/otel-helpers so the body stays flat instead of nested under try / catch / finally.
  • For Python functions with clear boundaries, prefer @tracer.start_as_current_span("operation.name"). Use a context manager when a decorator does not fit. Do not use detached start_span() + manual end() for bounded work.
  • Skip trivial getters, pure transforms, internal helpers — anything with no real latency or failure mode.
  • Never put PII in attributes (emails, passwords, tokens, full request bodies).

Logs

Make sure logs are structured and carry operation context. Concretely: every log line emitted inside a span should arrive at Maple with trace_id / span_id populated and any structured fields (orderId, userId, etc.) preserved as attributes. Trace/span context may be added natively by the log bridge or integration, or may require additional work.

Use logs for narrative ("starting batch reconcile", "retrying after 3xx") and exceptional events. An error log must only be emitted if the operation cannot recover and manual intervention is required.

Metrics

Cover business + performance + cost. Three categories to look for:

  • Business logic counters. Every meaningful state transition: created, started, completed, failed, retried. Per-tenant, per-channel, per-status — low-cardinality dimensions only (never user/order IDs).
  • Performance histograms. Latency of operations the user cares about, queue depth, batch sizes, payload sizes. Reuse existing timing instrumentation if the project already has any (time.perf_counter blocks, custom LatencyTrackers, "[TIMING]" log lines) — emit a histogram from those measurements rather than measuring twice.
  • Costs — especially LLM costs. If the project calls OpenAI / Anthropic / Google / any LLM provider, prefer provider instrumentation such as OpenInference where available so native SDK calls stay readable. Do not add pricing constants or LLM cost math in product handlers; Maple computes estimated cost centrally in the UI/query layer from captured provider/model/token attributes. Avoid duplicating token counters already captured by provider instrumentation.

Get the meter once at module level, create instruments at module level, increment in the hot path. Don't create a fresh meter per call.

Step 4 — Verify the app still works

Per service:

  1. Run the project's own dev or build command (whatever its package.json / pyproject / Makefile already wires up). Confirm it starts cleanly with no errors that trace back to your OTel install. Also run a telemetry bootstrap smoke that imports or starts the app, so provider setup, exporter construction, log bridging, and framework instrumentation all initialize. For a Python server this can be an import/startup command such as uv run python -c 'from app.main import app; print(app.title)'; for Node/Next use the repo's build/start path. For a server, hit at least one route with curl so traffic flows through the instrumentation; choose a route that exercises an instrumented operation when practical, not only a static health route. For a CLI, invoke a real command. Don't ship if the app's own startup is now broken — that's a regression.
  2. Confirm telemetry leaves the process. With the inline MAPLE_TEST (or real key) in the bootstrap, OTLP POSTs from the dev server should return 2xx — that proves the bootstrap is reaching the network. As an extra sanity check, you can curl -X POST https://ingest.maple.dev/v1/traces -H "authorization: Bearer MAPLE_TEST" -H "content-type: application/json" -d '{}' and confirm 200. The real signal is the running app's own POSTs succeeding by the time the dev server shuts down — if they don't, the bootstrap is wrong (most often: SDK loaded too late, or shutdown not flushing).

A bootstrap that loads but never POSTs is not a partial success. Fix it before moving on.

Step 5 — Hand-off (final message to the user)

What changed

3–7 short factual bullets covering: packages installed, files created/modified, business spans/metrics added. Per service if changes differed, grouped if uniform. Mention any existing observability vendor (Sentry, Datadog, Logtail, Pino transports, etc.) you intentionally left in place so the coexistence is explicit.

Swap the placeholder if you used one

If MAPLE_TEST is still inline, tell the user:

"The bootstrap currently uses MAPLE_TEST as a placeholder so the install could complete end-to-end. Create a real key at https://app.maple.dev (Settings → API Keys), then search-replace MAPLE_TEST in the files I wrote with your new key and redeploy."

Deploy

Tell the user to deploy as they normally would — push to their hosting platform, run their existing CI, or run locally. There are no env vars to wire and nothing platform-specific to configure: the endpoint and key are inline in the bootstrap, so events start flowing the moment the instrumented code runs.

If the user asks "where do I put the key in production?" — the answer is "you already did, it's in the source you just deployed."

Step 6 — Drive Maple MCP install

Suggest installing the Maple MCP server so the agent (Claude Code, Codex, Cursor, etc.) can query telemetry directly next time the user is debugging — search logs, pull traces, check error rates from inside the chat without context-switching to the dashboard.

For Claude Code (most common — that's where this skill is running), offer to run it for them:

claude mcp add --transport http maple https://api.maple.dev/mcp

This edits the user's Claude Code config. Confirm before running (the user may have a custom MCP scope or want to install elsewhere). If they decline, print the command so they can run it themselves later.

For other agents the user might also use, mention but do not run:

  • Codex: codex mcp add maple --url https://api.maple.dev/mcp
  • Cursor / others: copy the mcpServers snippet from https://app.maple.dev/mcp.

When done (or skipped), close out with a single line directing the user to deploy their app — they're ready to ship.

Hard rules

  • Never modify files outside the project root.
  • Never commit, push, or open PRs.
  • Inline the ingest key in source. It's a project-scoped, write-only token (think Sentry DSN); env-var indirection just adds deploy-time failure modes for no gain.
  • Never remove an existing observability vendor unless the user asks for it.
  • Use the project's existing package manager and existing logger.
  • Prefer native OTel packages for the language; don't reinvent telemetry plumbing the SDK already provides.
  • If the dev/build command errors out because of your instrumentation, that's a failure — fix it or report it, don't paper over it.
定义Maple项目OpenTelemetry接入规范,强制使用原生API及@maple/otel-helpers的withSpan工具。要求端点与密钥内联配置,采用低基数语义化命名,并补充VCS资源属性以确保信号质量与一致性。
需要初始化或配置OpenTelemetry时 编写业务Span代码时 涉及遥测指标创建时
skills/maple-onboarding-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-onboarding-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-onboarding-style",
    "description": "General OpenTelemetry onboarding style for Maple: native APIs, signal quality, inline keys, VCS resource attributes, LLM metrics, and smoke checks."
}

Maple OTel onboarding style

Use native OpenTelemetry APIs. Do not invent helper APIs.

In TypeScript/JavaScript, use the published @maple/otel-helpers withSpan helper for bounded business spans and add @maple/otel-helpers to package.json when it is not already present. This is required when the package can be installed. withSpan is the intended replacement for expanding a whole function into tracer.startActiveSpan(...) plus try / catch / finally. Do not use helpers to wrap provider SDK calls that OpenInference / provider instrumentation can observe directly.

Do:

import { trace, metrics } from "@opentelemetry/api"
import { withSpan } from "@maple/otel-helpers"

const tracer = trace.getTracer("orders.api")
const meter = metrics.getMeter("orders.api")
const ordersSubmitted = meter.createCounter("orders.submitted")

await withSpan(
	"order.submit",
	async (span) => {
		span.setAttributes({
			"tenant.id": tenantId,
			"order.id": orderId,
			outcome: "success",
		})
		ordersSubmitted.add(1, { "tenant.id": tenantId, outcome: "success" })
	},
	{ tracer },
)

Do not:

await sendMapleSpan(...)
recordCounter(...)
withTelemetry(...)

Naming

  • Files/functions are provider-neutral: telemetry.ts, observability.ts, initTelemetry(), initObservability().
  • The word Maple belongs only in endpoint/key setup comments or PR instructions.
  • Span names are conventional and low-cardinality: checkout.process, voice.session, llm.generate_copy.
  • Prefer semantic product-operation span names over provider transport names. llm.generate_copy or llm.voice_response is usually more useful than llm.anthropic.messages.create.

Endpoint and key

Inline the endpoint and the project's ingest key directly in the bootstrap source — don't read from OTEL_EXPORTER_OTLP_* env vars and don't write .env files. The Maple ingest key is project-scoped + write-only (Sentry DSN shaped), so source-level configuration is the right default; env-var indirection only adds deploy-time failure modes.

MAPLE_ENDPOINT = "https://ingest.maple.dev"
MAPLE_KEY      = "maple_…"        # or "MAPLE_TEST" while pairing

While pairing is in flight, use the literal MAPLE_TEST sentinel — Maple's ingest accepts it without forwarding events anywhere, so the bootstrap exercises the full code path before the real key arrives.

Pass the inline values to the SDK explicitly via the exporter constructor's endpoint / headers options. Do not configure the SDK off implicit env-var reads.

If the repo can call telemetry init from multiple paths, guard provider/exporter setup so repeated imports, tests, reloads, or framework callbacks do not install duplicate processors or log handlers. For a single-entrypoint app that starts cleanly, keep this simple.

Include standard resource attributes when values are available: service.name, service.version, deployment.environment.name, and the VCS attributes below.

VCS resource attributes

Set vcs.repository.url.full on the OTel resource for every instrumented service. The value is the canonical https URL of the repo (e.g. https://github.com/acme/api) — the same URL the user would paste in a browser, not an SSH URL, not a local working-tree path. This is the important one: it lets Maple link telemetry back to the source of truth. It is fine to hardcode this string alongside service.name in the SDK init; if a build env already exposes the slug (e.g. VERCEL_GIT_REPO_OWNER + VERCEL_GIT_REPO_SLUG, RAILWAY_GIT_REPO_OWNER + RAILWAY_GIT_REPO_NAME), prefer reading from env so a fork or rename doesn't drift.

Also set vcs.ref.head.revision (the commit SHA) on a best-effort basis. Read it from whatever env var the runtime/build platform already injects: VERCEL_GIT_COMMIT_SHA, RAILWAY_GIT_COMMIT_SHA, GITHUB_SHA, SOURCE_COMMIT, GIT_COMMIT, HEROKU_SLUG_COMMIT, etc. Do not shell out to git from the running process — many production images do not have git or a working tree. If no env source is available, omit the attribute; skipping the SHA is fine, skipping the URL is not.

Use vcs.repository.url.full and vcs.ref.head.revision exactly as named — these are the OTel semantic-convention keys. Do not invent parallel attributes like git.repo, app.repo_url, or deployment.commit_sha.

Signals

  • Traces: all critical operations have spans with relevant attributes.
  • Logs: structured, concise, OTLP-forwarded, and trace/span-correlated.
  • Metrics: critical operations have low-cardinality counters/histograms.
  • Tenant/org/project information is included where available.
  • Do not put raw user ids or request ids in metric tags unless the repo already treats them as bounded tenant-like ids.

LLM metrics

If the app uses LLMs, first look for provider instrumentation that already captures model/provider/token/error spans. In JavaScript/TypeScript, prefer OpenInference packages such as @arizeai/openinference-instrumentation-anthropic for supported SDKs. Keep the real provider call native and readable.

const response = await client.messages.create({
	model,
	max_tokens: 100,
	messages,
})

Every provider/call site still needs enough telemetry to answer usage questions. Let provider instrumentation own model/provider/token spans where it supports them. Do not duplicate those attributes at every application call site, and do not put provider pricing tables or cost math in product handlers. Maple computes estimated LLM cost centrally in the UI/query layer from captured provider/model/token data.

llmInputTokens.add(inputTokens, {
	"tenant.id": tenantId,
	"gen_ai.provider.name": "anthropic",
	"gen_ai.request.model": model,
	"app.gen_ai.use_case": "voice.initial_greeting",
	"app.gen_ai.call_site": "_callMugCopyLlm",
	outcome: "success",
})

Use counters for additive totals only when provider instrumentation cannot capture token usage:

  • llm.tokens.input
  • llm.tokens.output

Token counters use unit="tokens" or the SDK equivalent. If OpenInference / provider instrumentation already captures token usage, do not duplicate token counters just to mirror it. Do not add llm.cost_usd or equivalent app-side cost metrics for normal LLM calls; cost belongs in Maple's central pricing layer.

Use histograms for latency/duration distributions.

Prefer current gen_ai.* semantic-convention-style attribute names for LLM provider/model/token attributes, plus app.gen_ai.* for bounded application dimensions such as use case and call site. Avoid inventing parallel llm.* attributes unless the repo already standardizes on them.

If the app has OpenAI, Anthropic, and Google callers, instrument all three.

Smoke checks

Add a durable smoke path when the repo has a natural place for it: README, TESTING guide, script, npm command, pytest, or checked-in command note.

The smoke should explicitly prove startup/import with the OTel bootstrap loaded so provider setup, exporter construction, log bridging, and framework instrumentation initialize without errors. Then, where practical, exercise an actual instrumented span/log/metric or OTLP export attempt. A generic health route only proves the server responds; prefer an operation that crosses the instrumentation you added.

定义Maple平台Python项目的OpenTelemetry规范,涵盖模块级初始化、装饰器/上下文管理器使用、错误记录及OTLP日志集成。
用户询问Maple Python项目中的OpenTelemetry配置方式 需要遵循Maple规范的Python代码生成或审查
skills/maple-python-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-python-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-python-style",
    "description": "Python OpenTelemetry style for Maple: module-scope tracers\/meters, decorators for bounded work, error spans, OTLP-bridged logs via LoggingHandler + LoggingInstrumentor, inline endpoint + ingest key, and no helper-API wrappers."
}

Maple Python style

Acquire OTel objects at module scope.

from opentelemetry import metrics, trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer("orders.api")
meter = metrics.get_meter("orders.api")

orders_submitted = meter.create_counter("orders.submitted", unit="1")

Bounded work

Prefer decorators for functions with clear boundaries.

@tracer.start_as_current_span("order.submit")
async def submit_order(*, tenant_id: str, order_id: str) -> None:
    span = trace.get_current_span()
    span.set_attributes({
        "tenant.id": tenant_id,
        "order.id": order_id,
    })

Use a context manager when a decorator does not fit.

with tracer.start_as_current_span("order.validate") as span:
    span.set_attribute("tenant.id", tenant_id)
    validate_order(order)

Do not use detached tracer.start_span(...); span.end() for bounded work.

Error paths

Record exceptions on the active span.

try:
    result = await client.messages.create(...)
except Exception as exc:
    span = trace.get_current_span()
    span.record_exception(exc)
    span.set_status(Status(StatusCode.ERROR))
    logger.exception("llm call failed", extra={"tenant_id": tenant_id})
    raise

Logs

If logs are claimed as OTLP-forwarded, configure all of:

  • An OTel LoggerProvider + OTLPLogExporter + LoggingHandler
  • set_logger_provider(logger_provider) from opentelemetry._logs
  • Log correlation for existing records, e.g. LoggingInstrumentor().instrument(set_logging_format=True)

Preserve existing logging.basicConfig, console / file handlers, and log levels. The user's logger keeps working — you're adding an OTLP handler underneath so log lines carry trace_id / span_id and reach Maple.

Init behavior

Inline the endpoint and ingest key directly in the init module — don't read them from env. The ingest key is project-scoped + write-only (Sentry DSN shaped), so source-level configuration is the right default; env indirection just adds a class of "OTel didn't start because env wasn't set" deploy failures.

# telemetry.py
import logging

from opentelemetry import _logs, metrics, trace
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.logging import LoggingInstrumentor
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

MAPLE_ENDPOINT = "https://ingest.maple.dev"
MAPLE_KEY = "MAPLE_TEST"  # set by maple-onboard skill on pairing

_INITIALIZED = False


def init_observability() -> None:
    global _INITIALIZED
    if _INITIALIZED:
        return
    _INITIALIZED = True

    headers = {"authorization": f"Bearer {MAPLE_KEY}"}
    resource = Resource.create({
        "service.name": "my-python-app",
        "deployment.environment.name": os.getenv("DEPLOYMENT_ENV", "development"),
        "vcs.repository.url.full": "https://github.com/acme/my-python-app",
        "vcs.ref.head.revision": os.getenv("RAILWAY_GIT_COMMIT_SHA")
            or os.getenv("GITHUB_SHA")
            or os.getenv("GIT_COMMIT"),
    })

    tracer_provider = TracerProvider(resource=resource)
    tracer_provider.add_span_processor(
        BatchSpanProcessor(
            OTLPSpanExporter(endpoint=f"{MAPLE_ENDPOINT}/v1/traces", headers=headers),
        ),
    )
    trace.set_tracer_provider(tracer_provider)

    logger_provider = LoggerProvider(resource=resource)
    logger_provider.add_log_record_processor(
        BatchLogRecordProcessor(
            OTLPLogExporter(endpoint=f"{MAPLE_ENDPOINT}/v1/logs", headers=headers),
        ),
    )
    _logs.set_logger_provider(logger_provider)
    logging.getLogger().addHandler(LoggingHandler(logger_provider=logger_provider))
    LoggingInstrumentor().instrument(set_logging_format=True)

    meter_provider = MeterProvider(
        resource=resource,
        metric_readers=[
            PeriodicExportingMetricReader(
                OTLPMetricExporter(
                    endpoint=f"{MAPLE_ENDPOINT}/v1/metrics", headers=headers,
                ),
            ),
        ],
    )
    metrics.set_meter_provider(meter_provider)

Add the _INITIALIZED guard only when the app can realistically call this function more than once (FastAPI lifespan + workers, pytest fixtures, etc.).

Metrics

Counters:

  • llm.tokens.input
  • llm.tokens.output
  • requests/events/jobs/errors

Use semantic units when the SDK supports them: token counters use unit="tokens". Do not add app-side llm.cost_usd pricing metrics for normal LLM calls; Maple estimates cost centrally from provider/model/token data.

Histograms:

  • duration
  • latency
  • payload size

Avoid raw high-cardinality values in metric attributes. Prefer tenant/org/project, operation/use case, provider/model, and outcome dimensions over user-level metric tags.

FastAPI

Use the native instrumentation rather than replacing request handling with manual middleware.

from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

import telemetry

telemetry.init_observability()
app = FastAPI()
FastAPIInstrumentor.instrument_app(app)

Import telemetry (and call init_observability()) before any other module that needs tracing.

提供Maple平台的Rust OpenTelemetry集成方案。通过HTTP OTLP导出器发送Traces、Logs和Metrics,利用tracing-opentelemetry桥接现有日志宏,自动注入服务名、环境及版本资源属性,实现全栈可观测性数据上报。
需要配置Rust项目的OpenTelemetry监控 需要将Rust应用日志和指标发送到Maple平台 需要设置HTTP OTLP导出器
skills/maple-rust-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-rust-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-rust-style",
    "description": "Rust OpenTelemetry style for Maple: opentelemetry + opentelemetry_sdk + opentelemetry-otlp HTTP exporter, tracing-opentelemetry bridge for the tracing crate, inline endpoint + ingest key, semconv resource attributes."
}

Maple Rust style

Use the official opentelemetry + opentelemetry_sdk crates with opentelemetry-otlp (HTTP exporter, not gRPC). Bridge the tracing crate via tracing-opentelemetry so existing info! / error! calls flow through OTLP.

Cargo.toml

[dependencies]
opentelemetry = "0.27"
opentelemetry_sdk = { version = "0.27", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.27", features = ["http-proto", "reqwest-client", "logs", "metrics"] }
opentelemetry-semantic-conventions = "0.27"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-opentelemetry = "0.28"

Bootstrap

Inline the endpoint and ingest key — they're a project-scoped, write-only token (Sentry-DSN-shaped).

use opentelemetry::{global, KeyValue};
use opentelemetry_otlp::{LogExporter, MetricExporter, Protocol, SpanExporter, WithExportConfig};
use opentelemetry_sdk::{
    logs::LoggerProvider, metrics::SdkMeterProvider, trace::TracerProvider, Resource,
};
use opentelemetry_semantic_conventions::resource::{
    DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_NAME,
};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

const MAPLE_ENDPOINT: &str = "https://ingest.maple.dev";
const MAPLE_KEY: &str = "MAPLE_TEST"; // set by maple-onboard skill on pairing

pub fn init() -> Result<(TracerProvider, LoggerProvider, SdkMeterProvider), opentelemetry_otlp::ExporterBuildError> {
    let auth = format!("Bearer {MAPLE_KEY}");
    let mut headers = std::collections::HashMap::new();
    headers.insert("authorization".to_string(), auth);

    let resource = Resource::builder()
        .with_attributes([
            KeyValue::new(SERVICE_NAME, "orders-api"),
            KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, std::env::var("DEPLOYMENT_ENV").unwrap_or_else(|_| "development".into())),
            KeyValue::new("vcs.repository.url.full", "https://github.com/acme/orders-api"),
            KeyValue::new("vcs.ref.head.revision", std::env::var("GITHUB_SHA").unwrap_or_default()),
        ])
        .build();

    let trace_exporter = SpanExporter::builder()
        .with_http()
        .with_endpoint(format!("{MAPLE_ENDPOINT}/v1/traces"))
        .with_headers(headers.clone())
        .with_protocol(Protocol::HttpJson)
        .build()?;
    let tracer_provider = TracerProvider::builder()
        .with_batch_exporter(trace_exporter)
        .with_resource(resource.clone())
        .build();
    global::set_tracer_provider(tracer_provider.clone());

    let log_exporter = LogExporter::builder()
        .with_http()
        .with_endpoint(format!("{MAPLE_ENDPOINT}/v1/logs"))
        .with_headers(headers.clone())
        .with_protocol(Protocol::HttpJson)
        .build()?;
    let logger_provider = LoggerProvider::builder()
        .with_batch_exporter(log_exporter)
        .with_resource(resource.clone())
        .build();

    let metric_exporter = MetricExporter::builder()
        .with_http()
        .with_endpoint(format!("{MAPLE_ENDPOINT}/v1/metrics"))
        .with_headers(headers)
        .with_protocol(Protocol::HttpJson)
        .build()?;
    let meter_provider = SdkMeterProvider::builder()
        .with_periodic_exporter(metric_exporter)
        .with_resource(resource)
        .build();
    global::set_meter_provider(meter_provider.clone());

    let otel_layer = tracing_opentelemetry::layer().with_tracer(global::tracer("orders.api"));
    let otel_log_layer = opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge::new(&logger_provider);

    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::from_default_env())
        .with(tracing_subscriber::fmt::layer())
        .with(otel_layer)
        .with(otel_log_layer)
        .init();

    Ok((tracer_provider, logger_provider, meter_provider))
}

Call from main and shut down on exit:

#[tokio::main]
async fn main() {
    let (tracer_provider, logger_provider, meter_provider) =
        telemetry::init().expect("telemetry init");

    // app run …

    let _ = tracer_provider.shutdown();
    let _ = logger_provider.shutdown();
    let _ = meter_provider.shutdown();
}

Bounded business spans via tracing

The point of bridging tracing is so existing instrumentation works unchanged. Use #[tracing::instrument] on bounded async operations:

#[tracing::instrument(name = "order.submit", skip_all, fields(order.id = %order_id))]
async fn submit_order(order_id: &str) -> Result<(), Error> {
    charge_order(order_id).await?;
    Ok(())
}

tracing::error! and ?err field interpolation will record the exception and set the span status to ERROR via the bridge.

Coexistence

If the project already uses tracing with a Honeycomb / Datadog / Jaeger layer, leave it in place — add Maple's tracing-opentelemetry layer alongside. Don't strip the existing exporter unless the user asks.

基于ClickHouse的高性能pandas替代方案。支持16+数据源和10+文件格式,提供与pandas相同的API但速度提升10-100倍。适用于数据分析、跨源连接及加速慢查询,不支持原生SQL或数据库管理。
需要以pandas风格语法分析数据 优化缓慢的pandas代码性能 将远程数据库或云存储作为DataFrame查询 执行跨不同数据源的连接操作
.agents/skills/chdb-datastore/SKILL.md
npx skills add MapleTechLabs/maple --skill chdb-datastore -g -y
SKILL.md
Frontmatter
{
    "name": "chdb-datastore",
    "license": "Apache-2.0",
    "metadata": {
        "author": "chdb-io",
        "version": "4.1",
        "homepage": "https:\/\/clickhouse.com\/docs\/chdb"
    },
    "description": "Drop-in pandas replacement with ClickHouse performance. Use `import chdb.datastore as pd` (or `from datastore import DataStore`) and write standard pandas code — same API, 10-100x faster on large datasets. Supports 16+ data sources (MySQL, PostgreSQL, S3, MongoDB, ClickHouse, Iceberg, Delta Lake, etc.) and 10+ file formats (Parquet, CSV, JSON, Arrow, ORC, etc.) with cross-source joins. Use this skill when the user wants to analyze data with pandas-style syntax, speed up slow pandas code, query remote databases or cloud storage as DataFrames, or join data across different sources — even if they don't explicitly mention chdb or DataStore. Do NOT use for raw SQL queries, ClickHouse server administration, or non-Python languages.",
    "compatibility": "Requires Python 3.9+, macOS or Linux. pip install chdb."
}

chdb DataStore — It's Just Faster Pandas

The Key Insight

# Change this:
import pandas as pd
# To this:
import chdb.datastore as pd
# Everything else stays the same.

DataStore is a lazy, ClickHouse-backed pandas replacement. Your existing pandas code works unchanged — but operations compile to optimized SQL and execute only when results are needed (e.g., print(), len(), iteration).

pip install chdb

Decision Tree: Pick the Right Approach

1. "I have a file/database and want to analyze it with pandas"
   → DataStore.from_file() / from_mysql() / from_s3() etc.
   → See references/connectors.md

2. "I need to join data from different sources"
   → Create DataStores from each source, use .join()
   → See examples/examples.md #3-5

3. "My pandas code is too slow"
   → import chdb.datastore as pd — change one line, keep the rest

4. "I need raw SQL queries"
   → Use the chdb-sql skill instead

Connect to Any Data Source — One Pattern

from datastore import DataStore

# Local file (auto-detects .parquet, .csv, .json, .arrow, .orc, .avro, .tsv, .xml)
ds = DataStore.from_file("sales.parquet")

# Database
ds = DataStore.from_mysql(host="db:3306", database="shop", table="orders", user="root", password="pass")

# Cloud storage
ds = DataStore.from_s3("s3://bucket/data.parquet", nosign=True)

# URI shorthand — auto-detects source type
ds = DataStore.uri("mysql://root:pass@db:3306/shop/orders")

All 16+ sources and URI schemes → connectors.md

After Connecting — Full Pandas API

result = ds[ds["age"] > 25]                                          # filter
result = ds[["name", "city"]]                                        # select columns
result = ds.sort_values("revenue", ascending=False)                  # sort
result = ds.groupby("dept")["salary"].mean()                         # groupby
result = ds.assign(margin=lambda x: x["profit"] / x["revenue"])     # computed column
ds["name"].str.upper()                                               # string accessor
ds["date"].dt.year                                                   # datetime accessor
result = ds1.join(ds2, on="id")                                      # join
result = ds.head(10)                                                 # preview
print(ds.to_sql())                                                   # see generated SQL

209 DataFrame methods supported. Full API → api-reference.md

Cross-Source Join — The Killer Feature

from datastore import DataStore

customers = DataStore.from_mysql(host="db:3306", database="crm", table="customers", user="root", password="pass")
orders = DataStore.from_file("orders.parquet")

result = (orders
    .join(customers, left_on="customer_id", right_on="id")
    .groupby("country")
    .agg({"amount": "sum", "rating": "mean"})
    .sort_values("sum", ascending=False))
print(result)

More join examples → examples.md

Writing Data

source = DataStore.from_mysql(host="db:3306", database="shop", table="orders", user="root", password="pass")
target = DataStore("file", path="summary.parquet", format="Parquet")

target.insert_into("category", "total", "count").select_from(
    source.groupby("category").select("category", "sum(amount) AS total", "count() AS count")
).execute()

Troubleshooting

Problem Fix
ImportError: No module named 'chdb' pip install chdb
ImportError: cannot import 'DataStore' Use from datastore import DataStore or from chdb.datastore import DataStore
Database connection timeout Include port in host: host="db:3306" not host="db"
Join returns empty result Check key types match (both int or both string); use .to_sql() to inspect
Unexpected results Call ds.to_sql() to see the generated SQL and debug
Environment check Run python scripts/verify_install.py (from skill directory)

References

Note: This skill teaches how to use chdb DataStore. For raw SQL queries, use the chdb-sql skill. For contributing to chdb source code, see CLAUDE.md in the project root.

在Python进程中直接运行ClickHouse SQL,无需服务器。支持查询本地文件、远程数据库及云存储,提供chdb.query()单次查询、Session状态管道及DB-API连接。适用于高级SQL分析,非Pandas操作请使用chdb-datastore。
用户需要直接在Python中执行ClickHouse SQL语法 需要对Parquet/CSV/JSON文件或S3/Mysql等数据源进行SQL查询 用户提及chdb.query或需要跨数据源Join 构建基于ClickHouse的状态分析管道
.agents/skills/chdb-sql/SKILL.md
npx skills add MapleTechLabs/maple --skill chdb-sql -g -y
SKILL.md
Frontmatter
{
    "name": "chdb-sql",
    "license": "Apache-2.0",
    "metadata": {
        "author": "chdb-io",
        "version": "4.1",
        "homepage": "https:\/\/clickhouse.com\/docs\/chdb"
    },
    "description": "In-process ClickHouse SQL engine for Python — run ClickHouse SQL queries directly on local files, remote databases, and cloud storage without a server. Use when the user wants to write SQL queries against Parquet\/CSV\/ JSON files, use ClickHouse table functions (mysql(), s3(), postgresql(), iceberg(), deltaLake() etc.), build stateful analytical pipelines with Session, use parametrized queries, window functions, or other advanced ClickHouse SQL features. Also use when the user explicitly mentions chdb.query(), ClickHouse SQL syntax, or wants cross-source SQL joins. Do NOT use for pandas-style DataFrame operations — use chdb-datastore instead.",
    "compatibility": "Requires Python 3.9+, macOS or Linux. pip install chdb."
}

chdb SQL — ClickHouse in Your Python Process

Run ClickHouse SQL directly in Python — no server needed. Query local files, remote databases, and cloud storage with full ClickHouse SQL power.

pip install chdb

Decision Tree: Pick the Right API

1. One-off query on files or databases → chdb.query()
2. Multi-step analysis with tables      → Session
3. DB-API 2.0 connection                → chdb.connect()
4. Pandas-style DataFrame operations    → Use chdb-datastore skill instead

chdb.query() — One Line, Any Data

import chdb

chdb.query("SELECT * FROM file('data.parquet', Parquet) WHERE price > 100 LIMIT 10")       # local files
chdb.query("SELECT * FROM mysql('db:3306', 'shop', 'orders', 'root', 'pass')")              # databases
chdb.query("SELECT * FROM s3('s3://bucket/data.parquet', NOSIGN) LIMIT 10")                 # cloud storage
chdb.query("SELECT * FROM deltaLake('s3://bucket/delta/table', NOSIGN) LIMIT 10")           # data lakes

# Cross-source join
chdb.query("""
    SELECT u.name, o.amount FROM mysql('db:3306', 'crm', 'users', 'root', 'pass') AS u
    JOIN file('orders.parquet', Parquet) AS o ON u.id = o.user_id ORDER BY o.amount DESC
""")

data = {"name": ["Alice", "Bob"], "score": [95, 87]}
chdb.query("SELECT * FROM Python(data) ORDER BY score DESC")                                # Python data
df = chdb.query("SELECT * FROM numbers(10)", "DataFrame")                                   # output formats
chdb.query("SELECT toDate({d:String}) + number FROM numbers({n:UInt64})",
    "DataFrame", params={"d": "2025-01-01", "n": 30})                                      # parametrized

Table functions → table-functions.md | SQL functions → sql-functions.md | Full API → api-reference.md

Session — Stateful Analysis Pipelines

from chdb import session as chs
sess = chs.Session("./analytics_db")   # persistent; Session() for in-memory

sess.query("CREATE TABLE users ENGINE=MergeTree() ORDER BY id AS SELECT * FROM mysql('db:3306','crm','users','root','pass')")
sess.query("CREATE TABLE events ENGINE=MergeTree() ORDER BY (ts,user_id) AS SELECT * FROM s3('s3://logs/events/*.parquet',NOSIGN)")
sess.query("""
    SELECT u.country, count() AS cnt, uniqExact(e.user_id) AS users
    FROM events e JOIN users u ON e.user_id = u.id
    WHERE e.ts >= today() - 7 GROUP BY u.country ORDER BY cnt DESC
""", "Pretty").show()
sess.close()

Connection API (DB-API 2.0)

from chdb import dbapi
conn = dbapi.connect()
cur = conn.cursor()
cur.execute("SELECT * FROM file('data.parquet', Parquet) WHERE value > 100")
print(cur.fetchall())
cur.close()
conn.close()

Troubleshooting

Problem Fix
ImportError: No module named 'chdb' pip install chdb
DB::Exception: FILE_NOT_FOUND Check file path; use absolute path or verify cwd
DB::Exception: Unknown table function Check function name spelling (e.g., deltaLake not deltalake)
Connection refused to remote DB Check host:port format; ensure remote DB allows connections
Environment check Run python scripts/verify_install.py (from skill directory)

References

Note: This skill teaches how to use chdb SQL. For pandas-style operations, use the chdb-datastore skill. For contributing to chdb source code, see CLAUDE.md in the project root.

提供Maple平台跨语言OpenTelemetry规范,涵盖自定义属性键、Title Case状态码及资源属性双发等核心约定。用于指导代码编写、审查及OTLP配置,确保与Tinybird数据管道兼容。
添加或审查OpenTelemetry span属性设置代码 配置OTLP导出器或资源属性 通过WarehouseQueryService执行SQL查询并生成遥测数据
.agents/skills/maple-telemetry-conventions/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-telemetry-conventions -g -y
SKILL.md
Frontmatter
{
    "name": "maple-telemetry-conventions",
    "version": "1.0.0",
    "description": "Maple's OpenTelemetry conventions — custom span attribute keys (`maple.*` vendor namespace, `query.context`, `db.query.*`, `result.*`, `cache.*`, `tenant.*`), Title Case status codes (`Ok`\/`Error`\/`Unset`), resource attribute dual-emit (`deployment.environment` + `deployment.environment.name`), span kinds, Tinybird MV pre-extracted columns, loop-prevention filters, and sampling. Use whenever writing or reviewing instrumentation code in any language (TypeScript, Rust, Python) in this repo — adding `setAttribute`\/`setAttributes`\/`record`\/`#[instrument(fields(...))]` calls, setting span status, configuring an OTLP exporter, defining a new resource attribute, or wiring a new query through `WarehouseQueryService.sqlQuery()`."
}

Maple Telemetry Conventions

Reference for the language-agnostic OpenTelemetry conventions Maple uses across TypeScript (apps/api, Cloudflare workers in lib/effect-sdk/), Rust (apps/ingest), and future Python services. These conventions are load-bearing — Tinybird materialized views pre-extract certain attribute keys into columns, dashboards filter on Title Case status strings, and sampling-aware throughput math relies on the SampleRate column. Use the exact attribute spellings here in every language.

When to apply

  • Adding setAttribute / Effect.annotateCurrentSpan / Span::current().record(...) / #[instrument(fields(...))] to any code path
  • Setting span status (Ok / Error / Unset)
  • Wiring a new query through WarehouseQueryService.sqlQuery() (the context and profile options become span attributes)
  • Configuring an OTLP exporter, tracer provider, or resource builder
  • Introducing a new pre-extracted MV column or a new vendor attribute under maple.*
  • Reviewing a PR that touches apps/api/src/services/WarehouseQueryService.ts, apps/ingest/src/main.rs, apps/api/src/app.ts, lib/effect-sdk/src/cloudflare/, or packages/domain/src/tinybird/materializations.ts

Index

  • rules/span-attributes.md — Master reference of every custom attribute key Maple emits, grouped by namespace, with file:line citations.
  • rules/status-and-kind.md — Title Case status code rule (Ok/Error/Unset) and span kind conventions (Server / Client / Internal).
  • rules/resource-attributes.mdservice.* identity, deployment.environment.name resolution order, the legacy deployment.environment dual-emit, and maple_org_id.
  • rules/language-bindings.md — Parallel TypeScript / Rust / Python snippets that emit the same attribute keys.
  • rules/mv-first-class-columns.md — Which span and resource attributes Tinybird MVs pre-extract into columns (and the rule for adding new ones).
  • rules/service-map-attribution.md — Required span and resource attributes for the service map to render edges, runtime icons, and platform badges. Includes the canonical peer.service registry.
  • rules/loop-prevention.md — The three guards that prevent Maple's self-traffic from creating a feedback loop: API TracerDisabledWhen, ingest loopback guard, sampling.

Quick reference

Topic Rule
Status codes Always Title Case: "Ok", "Error", "Unset". Never OK, ERROR, SUCCESS, FAILED.
Vendor namespace Custom attributes go under maple.*. Sub-namespaces: maple.ingest.*, maple.cloudflare.*.
Standard semconv Use OTel semconv keys verbatim: service.name, http.request.method, db.system.name, error.type.
Org identity orgId (camelCase) in TypeScript spans, maple.org_id (dotted) in Rust spans. Don't unify until MVs migrate.
Deployment env Dual-emit deployment.environment + deployment.environment.name. Keep both until MV coalesce() migration lands.
Warehouse SQL spans Every span from WarehouseQueryService.executeSql carries db.system.name, peer.service, db.query.text, db.query.fingerprint, db.duration_ms, result.rowCount, orgId, query.context, query.profile. Legacy spans (pre 2026-06) use db.statement*/db.system; warehouse readers coalesce both.
Service map Outbound spans need peer.service (HTTP/RPC) or db.system.name (DB) on a Client/Producer span. Resource attrs need process.runtime.name, cloud.platform, maple.sdk.type for runtime icon + platform badge. See rules/service-map-attribution.md.
Loop prevention Never remove HttpMiddleware.TracerDisabledWhen (apps/api/src/app.ts:169-175) or the ingest loopback guard (apps/ingest/src/main.rs:499-514).

Canonical references (do not modify from this skill)

  • apps/api/src/services/WarehouseQueryService.ts:441-510executeSql span emission (the canonical example for TS).
  • apps/ingest/src/otel.rs — Resource builder + platform detection + forward-span helper for Rust. The canonical example for Rust resource and outbound-span attribution.
  • apps/ingest/src/main.rshandle_signal and handle_cloudflare_logpush (search for tracing::info_span! with otel.kind = "server") — Server-kind span macros for OTLP inbound.
  • apps/api/src/app.ts:169-175TracerDisabledWhen filter.
  • lib/effect-sdk/src/cloudflare/index.tsMapleCloudflareSDK tracer setup.
  • packages/domain/src/tinybird/materializations.ts — MV SELECT lists that pre-extract attribute keys into columns.
专注于优化注册后引导、用户激活及首次体验的CRO技能。旨在帮助用户快速达成‘顿悟时刻’,通过减少价值获取路径、简化首屏目标及利用空状态等策略,提升新用户留存与长期活跃。
优化注册后引导流程 提升用户激活率 改善首次运行体验 缩短时间到价值 解决用户注册后流失问题
.agents/skills/onboarding-cro/SKILL.md
npx skills add MapleTechLabs/maple --skill onboarding-cro -g -y
SKILL.md
Frontmatter
{
    "name": "onboarding-cro",
    "metadata": {
        "version": "1.1.0"
    },
    "description": "When the user wants to optimize post-signup onboarding, user activation, first-run experience, or time-to-value. Also use when the user mentions \"onboarding flow,\" \"activation rate,\" \"user activation,\" \"first-run experience,\" \"empty states,\" \"onboarding checklist,\" \"aha moment,\" \"new user experience,\" \"users aren't activating,\" \"nobody completes setup,\" \"low activation rate,\" \"users sign up but don't use the product,\" \"time to value,\" or \"first session experience.\" Use this whenever users are signing up but not sticking around. For signup\/registration optimization, see signup-flow-cro. For ongoing email sequences, see email-sequence."
}

Onboarding CRO

You are an expert in user onboarding and activation. Your goal is to help users reach their "aha moment" as quickly as possible and establish habits that lead to long-term retention.

Initial Assessment

Check for product marketing context first: If .agents/product-marketing-context.md exists (or .claude/product-marketing-context.md in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.

Before providing recommendations, understand:

  1. Product Context - What type of product? B2B or B2C? Core value proposition?
  2. Activation Definition - What's the "aha moment"? What action indicates a user "gets it"?
  3. Current State - What happens after signup? Where do users drop off?

Core Principles

1. Time-to-Value Is Everything

Remove every step between signup and experiencing core value.

2. One Goal Per Session

Focus first session on one successful outcome. Save advanced features for later.

3. Do, Don't Show

Interactive > Tutorial. Doing the thing > Learning about the thing.

4. Progress Creates Motivation

Show advancement. Celebrate completions. Make the path visible.


Defining Activation

Find Your Aha Moment

The action that correlates most strongly with retention:

  • What do retained users do that churned users don't?
  • What's the earliest indicator of future engagement?

Examples by product type:

  • Project management: Create first project + add team member
  • Analytics: Install tracking + see first report
  • Design tool: Create first design + export/share
  • Marketplace: Complete first transaction

Activation Metrics

  • % of signups who reach activation
  • Time to activation
  • Steps to activation
  • Activation by cohort/source

Onboarding Flow Design

Immediate Post-Signup (First 30 Seconds)

Approach Best For Risk
Product-first Simple products, B2C, mobile Blank slate overwhelm
Guided setup Products needing personalization Adds friction before value
Value-first Products with demo data May not feel "real"

Whatever you choose:

  • Clear single next action
  • No dead ends
  • Progress indication if multi-step

Onboarding Checklist Pattern

When to use:

  • Multiple setup steps required
  • Product has several features to discover
  • Self-serve B2B products

Best practices:

  • 3-7 items (not overwhelming)
  • Order by value (most impactful first)
  • Start with quick wins
  • Progress bar/completion %
  • Celebration on completion
  • Dismiss option (don't trap users)

Empty States

Empty states are onboarding opportunities, not dead ends.

Good empty state:

  • Explains what this area is for
  • Shows what it looks like with data
  • Clear primary action to add first item
  • Optional: Pre-populate with example data

Tooltips and Guided Tours

When to use: Complex UI, features that aren't self-evident, power features users might miss

Best practices:

  • Max 3-5 steps per tour
  • Dismissable at any time
  • Don't repeat for returning users

Multi-Channel Onboarding

Email + In-App Coordination

Trigger-based emails:

  • Welcome email (immediate)
  • Incomplete onboarding (24h, 72h)
  • Activation achieved (celebration + next step)
  • Feature discovery (days 3, 7, 14)

Email should:

  • Reinforce in-app actions, not duplicate them
  • Drive back to product with specific CTA
  • Be personalized based on actions taken

Handling Stalled Users

Detection

Define "stalled" criteria (X days inactive, incomplete setup)

Re-engagement Tactics

  1. Email sequence - Reminder of value, address blockers, offer help
  2. In-app recovery - Welcome back, pick up where left off
  3. Human touch - For high-value accounts, personal outreach

Measurement

Key Metrics

Metric Description
Activation rate % reaching activation event
Time to activation How long to first value
Onboarding completion % completing setup
Day 1/7/30 retention Return rate by timeframe

Funnel Analysis

Track drop-off at each step:

Signup → Step 1 → Step 2 → Activation → Retention
100%      80%       60%       40%         25%

Identify biggest drops and focus there.


Output Format

Onboarding Audit

For each issue: Finding → Impact → Recommendation → Priority

Onboarding Flow Design

  • Activation goal
  • Step-by-step flow
  • Checklist items (if applicable)
  • Empty state copy
  • Email sequence triggers
  • Metrics plan

Common Patterns by Product Type

Product Type Key Steps
B2B SaaS Setup wizard → First value action → Team invite → Deep setup
Marketplace Complete profile → Browse → First transaction → Repeat loop
Mobile App Permissions → Quick win → Push setup → Habit loop
Content Platform Follow/customize → Consume → Create → Engage

Experiment Ideas

When recommending experiments, consider tests for:

  • Flow simplification (step count, ordering)
  • Progress and motivation mechanics
  • Personalization by role or goal
  • Support and help availability

For comprehensive experiment ideas: See references/experiments.md


Task-Specific Questions

  1. What action most correlates with retention?
  2. What happens immediately after signup?
  3. Where do users currently drop off?
  4. What's your activation rate target?
  5. Do you have cohort analysis on successful vs. churned users?

Related Skills

  • signup-flow-cro: For optimizing the signup before onboarding
  • email-sequence: For onboarding email series
  • paywall-upgrade-cro: For converting to paid during/after onboarding
  • ab-test-setup: For testing onboarding changes
指导通过MCP构建、修复或审查Maple仪表盘组件。涵盖原始JSON构造、数据源判别字段规范、自定义whereClause语法及聚合规则,强调避免常见陷阱并验证查询结果。
create_dashboard add_dashboard_widget update_dashboard_widget dashboard widget JSON QueryDraft trace dashboard widget Invalid input for getQueryBuilderTimeseries
skills/maple-dashboard-widgets/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-dashboard-widgets -g -y
SKILL.md
Frontmatter
{
    "name": "maple-dashboard-widgets",
    "description": "Build, repair, or review Maple dashboard widgets via the MCP. Triggers on phrases like 'create_dashboard', 'add_dashboard_widget', 'update_dashboard_widget', 'dashboard widget JSON', 'QueryDraft', 'trace dashboard widget', 'Invalid input for getQueryBuilderTimeseries', or any session that submits raw widget JSON to the maple MCP. Covers the source-discriminated QueryDraft shape, the custom whereClause grammar, valid aggregations per data source, groupBy prefix conventions, the stat-widget `reduceToValue` transform, hiding auxiliary series on formula charts, and the verification step (MCP success ≠ query success)."
}

Maple dashboard widgets via MCP

When to use this skill

When you are constructing raw widget JSON for any of:

  • mcp__maple__create_dashboard with a dashboard_json payload
  • mcp__maple__add_dashboard_widget
  • mcp__maple__update_dashboard_widget

If you are creating a fresh dashboard, prefer the simplified widgets array on create_dashboard (SimpleWidgetSpec at apps/api/src/mcp/tools/create-dashboard.ts:94). It side-steps every trap below — fill in title, source, metric, optional group_by, optional service_name, and the tool builds the full shape for you. Raw JSON is for cases the simplified spec can't express (multi-query charts, formulas, hidden series, non-default transforms).

Trap 1 — Query drafts are source-discriminated

The query draft schema (packages/domain/src/http/query-engine.ts) is a union discriminated on dataSource. The metric-only fields — metricName, metricType, isMonotonic, signalSource — exist only on dataSource: "metrics" queries.

  • For dataSource: "traces" or "logs": do not include metricName, metricType, isMonotonic, or signalSource. They are not part of the trace/log query shape.
  • For dataSource: "metrics": include metricName (the metric to query), metricType, and optionally isMonotonic / signalSource.

Never add metricName/metricType/isMonotonic/signalSource to a trace or log query — they belong solely to dataSource: "metrics" queries. (Older stored dashboards were migrated to drop them; new widgets must not reintroduce them.)

Trap 2 — whereClause is a custom grammar, not SQL

Parser: packages/domain/src/where-clause.ts:7. Supported operators (the only supported operators):

Operator Example
= != service.name = "ingest", error.type != "Timeout"
> < >= <= http.request.body.size > 1000
contains !contains http.route contains "v1", http.route !contains "/health"
exists !exists maple.signal exists, db.system !exists

Rules:

  • Clauses join with AND (case-insensitive). No OR, no parentheses.
  • Keys are normalized to lowercase by the parser.
  • Quoted values use double quotes.
  • There is no IS NULL / IS NOT NULL. To require an attribute be present, use <key> exists; to require it absent, <key> !exists. This is the single most common mistake.
  • Attribute filters work directly. On dataSource: "traces" you can filter by any span/resource attribute — query.context = "tracesList", error.type != "Timeout", db.system = "clickhouse". Bare keys outside the small structured allowlist (service.name, span.name, deployment.environment, deployment.commit_sha, root_only, has_error) are auto-treated as span attributes; you can also write them explicitly as attr.<key> / resource.<key>. Cap: 5 attr.* + 5 resource.* filters per query.
  • Unhonored clauses are now rejected at write time. add_dashboard_widget / update_dashboard_widget / replace_dashboard_widgets run the builder before persisting and FAIL (nothing saved) if a clause can't be honored — e.g. exceeding the attr cap, or an unsupported logs/metrics filter key. (This used to be a silent drop.)

Wrong:

service.name = "ingest" AND maple.signal IS NOT NULL

Right:

service.name = "ingest" AND maple.signal exists

Minimum-viable trace chart widget JSON

Use as a template. Fill whereClause, groupBy, aggregation, display.title, display.unit, layout:

{
  "id": "w0",
  "visualization": "chart",
  "dataSource": {
    "endpoint": "custom_query_builder_timeseries",
    "params": {
      "queries": [
        {
          "id": "q-w0",
          "name": "A",
          "enabled": true,
          "hidden": false,
          "dataSource": "traces",
          "whereClause": "service.name = \"ingest\" AND maple.signal exists",
          "aggregation": "count",
          "stepInterval": "",
          "orderByDirection": "desc",
          "addOns": {
            "groupBy": true,
            "having": false,
            "orderBy": false,
            "limit": false,
            "legend": false
          },
          "groupBy": ["maple.signal"],
          "having": "",
          "orderBy": "",
          "limit": "",
          "legend": ""
        }
      ],
      "formulas": [],
      "comparison": { "mode": "none", "includePercentChange": true },
      "debug": false
    }
  },
  "display": {
    "title": "Requests by Signal",
    "chartId": "query-builder-bar",
    "chartPresentation": { "legend": "visible" },
    "stacked": true,
    "curveType": "linear",
    "unit": "number"
  },
  "layout": { "x": 0, "y": 0, "w": 6, "h": 4 }
}

Stat widget delta

For visualization: "stat", add dataSource.transform.reduceToValue. Transform schema is at apps/web/src/components/dashboard-builder/types.ts:42:

"transform": {
  "reduceToValue": { "field": "value", "aggregate": "sum" }
}

Valid aggregate values: "sum" | "first" | "count" | "avg" | "max" | "min". No "last". Without reduceToValue, the series array passes through to the renderer and the stat shows [object Object],....

Gauge widget delta

visualization: "gauge" renders the same scalar as a stat, but on a 180° radial arc. It needs the same reduceToValue transform as a stat widget. Add gauge presentation under display:

"display": {
  "unit": "percent",
  "gauge": { "min": 0, "max": 100 },
  "thresholds": [
    { "value": 70, "color": "var(--chart-3)" },
    { "value": 90, "color": "var(--destructive)" }
  ]
}

display.thresholds color the arc (the highest threshold ≤ the value wins) and place tick marks. gauge.min/max default to 0/100. Arc color falls back to var(--chart-1) when no threshold matches.

Threshold lines on time-series charts

display.thresholds also works on chart widgets — each entry draws a dashed horizontal ReferenceLine across line/area/bar charts, with an optional label. Reuse it to mark SLO/alert boundaries.

Valid aggregation values per dataSource

From normalizeTraceAggregation / normalizeMetricsAggregation in apps/web/src/components/dashboard-builder/ai/normalize-widget-proposal.ts:148:

  • traces: count, avg_duration, p50_duration, p95_duration, p99_duration, error_rate
  • metrics: rate, increase, avg, sum, count, min, max, p50, p95, p99
  • logs: count

rate / sum / increase are invalid for traces — a common mistake when porting metrics widgets to traces.

groupBy prefix conventions

The query-builder groupBy accepts a small literal allowlist per source; everything else MUST be prefixed attr.<key>. A token that isn't recognized and isn't attr.-prefixed is dropped — and now causes the widget mutation tools to reject the write rather than silently breaking the breakdown.

  • traces: bare literals are only service / service.name, span / span.name, status / status.code, http.method, none / all. Every other attribute needs the attr. prefixattr.maple.signal, attr.error.type, attr.http.response.status_code, attr.maple.org_id, etc. (Writing error.type bare in groupBy does NOT work — use attr.error.type.)
  • logs: bare literals only — service / service.name, severity, none. Logs do not support attr.* group by.
  • metrics: bare service / none; everything else uses attr.attr.signal, attr.status, attr.org_id.

If a groupBy on an attribute finds zero distinct values, the chart collapses to a single all series. inspect_chart_data now flags this as EMPTY_GROUPING (verdict broken) instead of silently showing the ungrouped total.

display.unit is mandatory

Always set display.unit on chart and stat widgets. The default is "number". Pick more specific where applicable:

  • duration_ms for latency aggregations (avg_duration, p50_duration, p95_duration, p99_duration)
  • percent for error_rate
  • number for count
  • bytes / GB for size aggregations

Hiding auxiliary queries on charts with formulas

When a chart uses formulas and the auxiliary queries shouldn't render on their own, query.hidden: true is not enough on its own for the raw-JSON path — that flag is only consumed by the UI builder to generate the actual transform. For raw JSON, pair it with dataSource.transform.hideSeries.baseNames:

"transform": {
  "hideSeries": { "baseNames": ["A", "B"] }
}

baseNames matches each hidden query's legend || name. Otherwise the auxiliary series render at full scale and skew percent-axis charts to absurd values (raw counts showing as "1200%").

Verification — MCP success ≠ query success

The widget mutation tools now do two things automatically that used to be silent gaps:

  • Pre-persist rejection: unhonored where-clause/groupBy clauses fail the write (nothing saved) — so a IS NULL, a 6th attr filter, or an unsupported logs/metrics key surfaces as an error instead of degrading to wrong/empty data.
  • Auto-validation: the response includes an inspect_chart_data summary (verdict + flags). inspect_chart_data now also evaluates formulas[] (so formula/hit-rate widgets verify end-to-end) and applies reduceToValue with the same first-numeric-field fallback the renderer uses (so stat-tile reducedValue reflects what renders). SUSPICIOUS_GAP is informational only — sparse/bursty data no longer downgrades the verdict.

Still confirm after submitting:

  1. Read the returned validation summary; if verdict is suspicious/broken, fix and resubmit.
  2. Call mcp__maple__inspect_chart_data for a deeper look, or mcp__maple__get_dashboard to read back the stored JSON, or load the dashboard URL.

Verdict flags worth knowing: EMPTY_GROUPING (groupBy found zero distinct values → one all series), METRIC_NOT_FOUND (a metrics widget references a metric name not in the warehouse — distinct from a real metric with no recent data), BUILDER_WARNINGS (a clause the engine couldn't honor).

If you see Invalid input for getQueryBuilderTimeseries, the culprit is almost always Trap 1 (metric fields on a trace/log query) or a malformed query draft.

Rebuilding many widgets at once

To replace a dashboard's whole widget list in one validated, atomic write, use mcp__maple__replace_dashboard_widgets (widgets_json = a JSON array of widget objects, same shape as widgets[] from get_dashboard). id and layout are optional per widget (auto-generated / auto-placed). Every widget is validated before anything persists — if one widget's query can't be honored, nothing is saved. This is the safe middle ground between N incremental add_dashboard_widget calls and the corruption-prone full dashboard_json replace.

Quick checklist before submitting widget JSON

  • Trace/log queries omit metricName/metricType/isMonotonic/signalSource; metrics queries include metricName + metricType.
  • whereClause uses only =, !=, >, <, >=, <=, contains, !contains, exists, !exists, joined by AND (no SQL IS NULL).
  • aggregation is valid for the chosen dataSource (no rate/sum on traces).
  • groupBy uses the right prefix: bare only for the per-source allowlist (traces: service/span/status/http.method); every other attribute needs attr.<key> (traces & metrics). Logs: bare service/severity only.
  • display.unit is set (and matches the aggregation — duration_ms, percent, etc.).
  • Stat widgets include dataSource.transform.reduceToValue.
  • Formula charts with hidden queries include dataSource.transform.hideSeries.baseNames.
  • After submitting, read the auto-validation summary; verify suspicious/broken widgets with inspect_chart_data or by loading the dashboard.
审查代码对OpenTelemetry v1.58.0规范的合规性。基于docs/otel-spec/源码,针对OTLP服务端、自监控及数据消费者场景,输出MUST/SHOULD违规项及引用。
检查PR或diff是否符合OTel规范 审查apps/ingest中OTLP处理逻辑 验证部分成功处理与重试状态码 检查自监控代码中的tracer设置
skills/maple-otel-spec-review/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-otel-spec-review -g -y
SKILL.md
Frontmatter
{
    "name": "maple-otel-spec-review",
    "description": "Review a diff, PR, or specific file in this repo for OpenTelemetry *specification* compliance, grounded in the source-linked spec corpus at docs\/otel-spec\/ (snapshot v1.58.0). Triggers on requests like 'is this spec compliant', 'review this PR against the OTel spec', 'spec-review this diff', 'check my partial-success handling', 'are these retryable status codes right', 'does apps\/ingest honor the OTLP spec', and on reviews of changes touching the OTLP server surface in apps\/ingest (partial success, retryable set {429, 502, 503, 504}, protobuf Status bodies, gzip, OTLP\/JSON encoding), self-instrumentation (apps\/api tracer setup, apps\/ingest\/src\/otel.rs, lib\/effect-sdk), or consumers of span status \/ SeverityNumber \/ db.query.text (WarehouseQueryService, query-engine). Spec MUSTs and SHOULDs only — for Maple house conventions use maple-telemetry-conventions; for whole-project instrumentation audits use maple-audit; for general diff correctness use \/code-review."
}

OTel spec-compliance review

Review a diff, PR, or named file of Maple's own code against the OpenTelemetry specification as snapshotted in docs/otel-spec/ (spec v1.58.0, researched 2026-07-05). This is not maple-audit (whole-project instrumentation-quality audit against Maple conventions, mostly for external projects), not maple-telemetry-conventions (the house-conventions reference — cross-referenced here, never enforced from here), and not generic /code-review (diff correctness). The deliverable is spec findings: MUST/SHOULD violations with normative citations.

docs/otel-spec/README.md is the routing hub; the nine spec files are the only authority this skill may cite. Every section in them carries an inline Source: deep link to the official spec — findings copy that link.

Severity

Severity comes from the spec's own normative word, not intuition:

Severity Meaning
critical Violates a MUST / MUST NOT / SHALL / REQUIRED in a Stable spec section: failing a whole batch instead of returning partial success, wrong retryable status set, non-protobuf error body, an SDK code path that can throw into the host app, dropping gzip decode.
warn Violates a SHOULD / SHOULD NOT, or newly relies on a deprecated path: fresh span-event usage for event-shaped data, deprecated semconv keys without coalescing, missing throttle hints on 429/503.
info MAY/recommendation divergence; anything grounded in a Development/Experimental-stability section; any finding touching a README known-gap item (mandatory cap, tagged "unverified").

Routing table

Map what the diff touches to the minimal spec sections. Route by heading text, never line numbers. Surfaces are the three from docs/otel-spec/README.md: 1 ingest gateway as OTLP server · 2 self-instrumentation as SDK consumer · 3 UI/query as data consumer.

Diff touches (path or content signal) Surface Load (file → ##/### headings)
apps/ingest OTLP handlers: request decode, response building, status codes 1 otlp.md → Request/response shapes (esp. Partial success (server MUST rules), When to use partial success vs full failure (server MUST)), Failure handling (esp. HTTP status codes — retryable matrix, Throttling / backpressure (server SHOULD))
OTLP/JSON parsing or serialization (IDs, enums, unknown fields) 1 otlp.md → JSON encoding (OTLP/HTTP JSON) — deviations from standard protobuf JSON; proto field questions → Proto schema essentials
gzip / content-type / transport handling in ingest 1 otlp.md → Transports: gRPC vs HTTP (esp. Compression, Content types)
Inbound traceparent/tracestate/baggage handling; propagator config 1/2 context-propagation.md → W3C Trace Context — traceparent (esp. Practical MUSTs for an ingest gateway), W3C Trace Context — tracestate, Baggage; propagator wiring → Propagators API
Span creation/status/kind sites: apps/ingest/src/otel.rs, tracing::info_span! / otel.kind, apps/api tracer setup, lib/effect-sdk/ 2 traces.md → 3. Span, 4. SpanKind, 6. TracerProvider / Tracer; error-handling posture → stability-and-compliance.md → 5. Error-handling principles (fail-safe requirements); scope naming → stability-and-compliance.md → 7. Library / instrumentation guidelines
Span processors, exporters, sampling config 2 traces.md → 12. Sampling (SDK), 15. Span Processor, 16. Span Exporter, 13. Span Limits
Resource attributes, OTEL_* env vars, exporter endpoints 2 resource-and-config.md → 1. Resource: definition, immutability, merge rules, 3. Key resource semantic conventions, 5. SDK environment-variable spec (consolidated table); endpoint rules → otlp.md Exporter configuration ('OTEL_EXPORTER_OTLP_*')
New/renamed attribute keys (setAttribute, annotateCurrentSpan, record(...), #[instrument(fields(...))]) 2/3 semantic-conventions.md → General rules + the one matching domain section (HTTP, Database, Messaging, RPC, Exceptions, Gen-AI)
Consumers of status / severity / db keys: WarehouseQueryService, packages/query-engine, packages/domain/src/tinybird/, UI classification code 3 semantic-conventions.md → matching domain section (HTTP status rule: Span status mapping (the important compliance rule)); logs.md → 2. Log Data Model (esp. 2.2 SeverityNumber reference table), 5. Events
Metrics instruments, views, temporality, cardinality 2 metrics.md → 1. Metrics API (esp. 1.4 Naming rules), 2. Metrics SDK (esp. 2.2 Aggregations — default per instrument, 2.5 Cardinality limits); data-model questions only → 3. Metrics Data Model
Log bridge wiring / log record emission 2 logs.md → 3. Logs API ("Bridge API"), 4. Logs SDK
Schema URLs, Prometheus interop, profiles 2/3 emerging-and-compat.md → 2. Telemetry schemas (deep dive) / 3. Prometheus & OpenMetrics compatibility / 1. Profiles signal
Anything OTel-shaped that doesn't map above README Files table → pick ONE file; read its Relevance to Maple intro (where present), then decide which sections to load

Loading rule: Grep -n '^##' docs/otel-spec/<file>.md to locate the routed heading, then Read with offset/limit from that line to the next same-level heading. If the file has a Relevance to Maple intro (traces, metrics, logs, resource-and-config, context-propagation, emerging-and-compat), read that first (~30 lines) as orientation.

Budget: never read a spec file end-to-end (traces.md is 801 lines). Default is ≤2 spec files per review, section-scoped. If a third is genuinely needed, say why in the report's "Spec sections loaded" line.

Load-bearing MUSTs (quick table)

The most common review facts, cached so trivial reviews load zero spec files. This table is a cache, not an authority: any critical finding still opens the cited section to quote the normative sentence and copy its Source: link.

# Surface Rule Spec ref
1 1 Partially-bad batch → HTTP 200 + partial_success with rejected_* count and error_message. Never fail the whole batch for a few bad items. otlp.md § Partial success (server MUST rules)
2 1 400 is only for fully undecodable/invalid requests, and is non-retryable. otlp.md § When to use partial success vs full failure (server MUST)
3 1 The retryable HTTP status set is exactly {429, 502, 503, 504}. otlp.md § HTTP status codes — retryable matrix
4 1 Every 4xx/5xx response body MUST be a protobuf Status. otlp.md § Failure handling
5 1 Server MUST accept gzip-compressed request bodies. otlp.md § Transports: gRPC vs HTTP → Compression
6 1 OTLP/JSON: hex trace/span IDs, lowerCamelCase field names, enums accepted as numbers, unknown fields MUST be ignored. otlp.md § JSON encoding — deviations
7 1 When throttling, server SHOULD respond 429/503 and MAY include Retry-After; clients SHOULD honor it. otlp.md § Throttling / backpressure (server SHOULD)
8 2 API/SDK calls MUST NOT throw into the host application — fail-safe, self-diagnose to own log. stability-and-compliance.md § 5. Error-handling principles (fail-safe requirements)
9 2 HTTP SERVER span status = Error only for 5xx; 4xx leaves server-span status Unset. (CLIENT spans: Error on any 4xx/5xx.) semantic-conventions.md § HTTP → Span status mapping (the important compliance rule)
10 2 Per-component instrumentation scope names, not one global tracer. stability-and-compliance.md § 7 → Instrumentation Scope — identity and naming
11 2 deployment.environment.name is canonical; legacy deployment.environment is deprecated. resource-and-config.md § 3.2
12 2 Resource is immutable once the provider is built; merge = updating resource wins per key. resource-and-config.md § 1. Resource: definition, immutability, merge rules
13 2 Metric instrument names: first char alphabetic, ≤255 chars, case-insensitive ASCII, charset A–Z a–z 0–9 _ . - /. metrics.md § 1.4 Naming rules
14 3 Log error classification keys on SeverityNumber >= 17 (ERROR), never SeverityText string-matching. logs.md § 2.2 SeverityNumber reference table
15 3 An "event" is a LogRecord with EventName set; span events / RecordException() are deprecating toward log-based events. logs.md § 5. Events
16 1/2 All-zero trace-id/parent-id in traceparent is invalid → treat as absent, start fresh; extraction never throws — malformed degrades to "no context extracted". context-propagation.md § traceparent → Practical MUSTs for an ingest gateway

Step 1 — Scope the diff

Resolve what's under review: the working diff (git diff / git diff --staged), a PR (its diff via the GitHub tooling available), a commit (git show <sha>), or files the user named. List the touched files grouped by path. If nothing touches a compliance surface or OTel-shaped content (no spans, exporters, OTLP handling, telemetry attributes, or telemetry consumers), say so in two lines and stop — don't spec-review generic code.

Step 2 — Classify

Map each file group to surface(s) and routing-table row(s). Show the classification before reviewing so the user can correct it:

apps/ingest/src/main.rs (response building)  → surface 1 → otlp.md §Request/response shapes, §Failure handling
apps/api/src/services/WarehouseQueryService.ts → surface 3 → semantic-conventions.md §Database

Step 3 — Load minimally

Check the quick table first. If every implicated rule is covered there and no finding looks critical, skip spec loading entirely. Otherwise grep-then-read only the routed sections per the loading rule. Track what you loaded — it goes in the report header.

Step 4 — Review

Walk the diff hunks against the loaded normative text. For each candidate finding: quote (to yourself) the normative sentence, take severity from its MUST/SHOULD/MAY word and the section's stability label, and record evidence as file:line in the diff. Keep two piles as you go: spec findings, and house-convention observations (Maple-specific rules from maple-telemetry-conventions that the spec doesn't mandate).

Step 5 — Findings report

Present the report before making any edit. Format:

## OTel spec review — <diff/PR ref>

Spec snapshot: v1.58.0, researched 2026-07-05 (docs/otel-spec/). Re-verify Source links before quoting externally.
Surfaces touched: <1 OTLP server | 2 SDK consumer | 3 data consumer | none>
Spec sections loaded: otlp.md §Partial success, §HTTP status codes — retryable matrix <or "none — quick table sufficed">

| Severity | Spec ref | Finding | Evidence | Source |
| critical | otlp.md §Partial success (server MUST rules) | returns 400 for a batch with 3 bad spans instead of 200 + rejected_spans | apps/ingest/src/main.rs:512 | <Source: URL copied from that section> |
...

### House-convention notes (not spec findings)
- <observation> — see maple-telemetry-conventions rules/<file>.md

### Summary
N critical · N warn · N info. Known-gap caveats: <or "none">.

If the user only wants the review, stop here — the report is a complete deliverable.

Step 6 — Apply fixes (only if asked)

Fix in severity order. What to change comes from the cited spec section; how to write it defers to the style skills (maple-rust-style for apps/ingest, maple-effect-style for apps/api) and maple-telemetry-conventions for attribute spellings. After edits, re-verify: the touched app's build/dev command starts cleanly (bun typecheck for TS, cargo check for apps/ingest). A fix that breaks the build is a regression — fix or revert it.

Hard rules

  • Every finding cites a docs/otel-spec/ file + section heading and copies that section's inline Source: link. If the corpus is silent on a question, report "not covered by the corpus — not reviewed"; never reason from model memory about what the spec says.
  • The README known-gap items (Jaeger uber-trace-id, W3C Baggage doc status, global metrics cardinality-limit env var, env-var vs declarative-config precedence) are capped at info and tagged "unverified"; re-check the canonical URL before asserting them as fact.
  • Documented intentional Maple deviations are not findings: title-case "Ok"/"Error"/"Unset" status storage (one-time translation at the OTLP boundary), the deployment.environment dual-emit migration bridge, and db.statementdb.query.text read-side coalescing (all in README §Maple's three compliance surfaces). Flag one only if the diff breaks the documented pattern.
  • No edits before the findings report. Never commit, push, or open PRs. Never expand into a whole-repo audit — that's maple-audit.
  • Maple-convention issues go under "House-convention notes" pointing at the specific maple-telemetry-conventions rules/*.md, never mislabeled as spec violations. A line violating both gets a spec finding plus a cross-reference.
  • Respect the loading budget: section-scoped reads only, ≤2 spec files by default, never a full file.

ホーム - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-21 03:43
浙ICP备14020137号-1 $お客様$