
1. 这不是“写代码”的事是重新理解 Claude Code Skill 的底层逻辑我花了整整三周每天平均写3个 Claude Code Skill到第50个时才把键盘拍在桌上——前30个全得重来。不是因为它们不能跑而是它们根本没进 Claude 的“技能认知体系”。你可能也试过写完一个 Skill测试能返回结果就以为成了但一到真实场景里它要么答非所问要么漏掉关键约束要么在多步推理中突然“失忆”。后来我翻遍 Anthropic 官方文档、反复对比成功案例的 skill.yaml 结构、甚至反编译了几个高星开源 Skill 的元数据才发现问题不在代码语法而在我们对Skill 的本质定位错了。Claude Code Skill 不是“函数封装”不是“API 封装”更不是“if-else 拼凑”。它是一套带上下文感知能力的意图路由协议。它的核心任务是让 Claude 在面对用户模糊、跳跃、多轮、带隐含前提的自然语言请求时能精准识别出“此刻用户真正想调用哪个能力”并把原始请求无损地、结构化地、带语义锚点地投递给后端执行模块。这就像给 Claude 装上一套带 GPS 定位和实时路况的导航系统——不是让它自己开车而是让它准确告诉司机“现在该走哪条路、在哪路口转弯、注意前方施工”。所以“写了50个才醒悟”这件事本质上是踩进了三个认知陷阱第一把 Skill 当成传统 REST 接口只关注输入输出格式忽略 intent matching 的权重设计第二过度依赖 prompt engineering 去“教”Claude 理解技能而不是用 skill.yaml 的 metadata 字段去“声明”技能边界第三没意识到 Spring Boot 在这里不是后端框架而是 Skill 的语义注册中心与执行沙箱——它的 RestController 不是暴露接口而是为每个 Skill 提供可验证、可隔离、可审计的执行契约。你如果正卡在“Skill 写了但不被触发”“测试通过但线上失效”“多个 Skill 冲突互相覆盖”那说明你还在前30个的坑里。别急着改代码先改脑子。接下来我会用第31到第50个 Skill 的实战复盘把这套“意图路由协议”的设计心法、Spring Boot 集成的关键断点、以及那些官方文档里绝不会写的避坑细节掰开揉碎讲清楚。这不是教程是我在生产环境里用30次失败换来的操作手册。2. Skill 设计的本质从“功能实现”到“意图声明”的范式迁移2.1 为什么前30个 Skill 是无效劳动——解构三个典型失败模式我整理了前30个 Skill 的失败日志发现92%的问题集中在以下三类它们共同指向一个根本性误判把 Skill 当作代码模块而非语义契约。第一类过度泛化的 description 导致意图漂移比如第7个 Skill目标是“查询订单物流状态”。我写的 description 是“获取用户订单的最新物流信息”。表面看没问题但实际触发时用户说“我的快递怎么还没到”Claude 就会匹配这个 Skill可当用户说“帮我查下昨天下单的那件衣服物流”Claude 却匹配了另一个叫“订单详情查询”的 Skill——因为后者 description 里有“订单详情”这个词而物流 Skill 里没明确绑定“tracking number”或“courier”等强信号词。问题出在哪不是模型不准是我没在 skill.yaml 里用intent_keywords字段显式声明“tracking, logistics, courier, shipped, delivered, in_transit”。Anthropic 的意图匹配引擎会优先扫描这些字段而不是全文语义相似度。前30个里我只在 description 里堆砌同义词却漏掉了这个强制声明层。第二类参数 schema 设计违背 Spring Boot 的契约精神第14个 Skill 是“生成用户月度消费报告”。我定义了start_date和end_date两个 string 类型参数用正则校验格式。结果上线后大量报错“Invalid date format: 2024-05-01T00:00:00Z”。查日志发现Claude 在解析自然语言时会把用户说的“上个月”自动转成 ISO8601 时间戳但我的 Spring Boot Controller 没配DateTimeFormat(pattern yyyy-MM-ddTHH:mm:ss.SSSX)直接抛出 ConversionFailedException。更糟的是这个错误被 Skill 框架吞掉返回 generic error导致用户以为 Skill 失效。正确做法是在 skill.yaml 的parameters定义里必须标注type: date-time并在 Spring Boot 的RequestBody对象中用JsonFormat(pattern yyyy-MM-ddTHH:mm:ss.SSSX)显式声明——这不是可选配置是 Skill 与 Spring Boot 之间的类型契约。前30个里我有17个用了 string 代替 date-time以为前端能处理却忘了 Skill 的输入来自 Claude 的语义解析不是用户手填表单。第三类execution_mode 选择错误引发执行链断裂第22个 Skill 是“跨平台同步商品库存”。我设了execution_mode: async以为能提升响应速度。结果用户问“同步完成了吗”Claude 回答“已提交后台任务”但后续追问“同步结果如何”它却无法关联之前的 task_id——因为 async 模式下Skill 执行后只返回 task_id不维护上下文关联。而 Claude 的多轮对话状态机需要的是“可追溯的同步执行结果”。正确方案是用execution_mode: synctimeout: 30s并在 Spring Boot 服务里做超时熔断或者改用execution_mode: webhook由后端主动回调 Skill 平台更新状态。前30个里我盲目套用微服务异步思想却忽略了 Skill 的本质是“对话原子操作”不是后台作业。提示Skill 的execution_mode不是性能优化选项而是对话状态管理策略。sync 模式保证原子性async 模式要求你额外实现状态查询 Skillwebhook 模式要求你部署回调服务——三者成本完全不同选错一个后面所有交互设计都得推倒重来。2.2 第31个 Skill 的转折点用 Spring Boot 的 ConfigurationProperties 重构 Skill 元数据真正让我破局的是第31个 Skill“根据用户画像推荐优惠券”。之前我总在 skill.yaml 里手动维护name,description,intent_keywords结果每次改一个字段就要重启服务。直到我意识到Spring Boot 的ConfigurationProperties可以把 skill.yaml 变成可热加载的配置Bean。我新建了一个SkillMetadata.javaConfigurationProperties(prefix skill.coupon-recommender) Component Data public class SkillMetadata { private String name coupon-recommender; private String description 基于用户历史行为和当前购物车推荐最匹配的优惠券; private ListString intentKeywords Arrays.asList( 优惠券, 折扣, 满减, 推荐, 省钱, coupon, discount ); private MapString, ParameterSpec parameters new HashMap() {{ put(user_id, new ParameterSpec(string, 用户唯一标识, true)); put(cart_items, new ParameterSpec(array, 购物车商品ID列表, true)); }}; private int timeout 15; private String executionMode sync; }然后在application.yml里写skill: coupon-recommender: name: coupon-recommender description: 基于用户历史行为和当前购物车推荐最匹配的优惠券 intent-keywords: [优惠券, 折扣, 满减, 推荐, 省钱, coupon, discount] parameters: user_id: { type: string, description: 用户唯一标识, required: true } cart_items: { type: array, description: 购物车商品ID列表, required: true } timeout: 15 execution-mode: sync这样做的好处是元数据与代码分离运营人员改推荐策略关键词不用动 Java 代码改 yml 重启即可类型安全IDE 能校验 yml 字段是否匹配SkillMetadata的属性动态注入在 Controller 里直接Autowired SkillMetadata metadata避免硬编码字符串统一校验所有 Skill 的 timeout、mode 等公共字段可以用Validated统一约束。这一步重构让我从“写50个 Skill”变成“写1个 Skill 框架 49个配置”。前30个白写的根源就是把每个 Skill 当独立项目开发没建立可复用的元数据治理层。2.3 Spring Boot 如何成为 Skill 的“语义注册中心”很多人以为 Spring Boot 在 Skill 架构里只是后端服务其实它承担着更关键的角色Skill 语义注册中心Semantic Registry。它的RestController不是暴露 HTTP 接口而是向 Claude 平台“注册语义能力”。看一个典型 Skill ControllerRestController RequestMapping(/skill/coupon-recommender) public class CouponRecommenderController { PostMapping public ResponseEntitySkillResponse execute( Valid RequestBody SkillRequest request, RequestHeader(X-Skill-Id) String skillId) { // 1. 从 Spring Context 获取 SkillMetadata Bean SkillMetadata metadata context.getBean(SkillMetadata.class); // 2. 校验请求参数是否符合 metadata 声明 ValidationUtils.validateParameters(request.getParameters(), metadata.getParameters()); // 3. 执行业务逻辑 ListCoupon coupons recommender.recommend( request.getParameters().get(user_id).toString(), (ListString) request.getParameters().get(cart_items) ); // 4. 构建 SkillResponse包含 metadata 声明的结构化输出 return ResponseEntity.ok(SkillResponse.builder() .skillId(skillId) .output(Map.of(recommended_coupons, coupons)) .metadata(Map.of( version, 1.2.0, last_updated, Instant.now().toString() )) .build()); } }关键点在于RequestHeader(X-Skill-Id)不是随便加的这是 Claude 平台在调用时自动注入的 Skill 唯一标识Spring Boot 用它从 ApplicationContext 中精准捞出对应 Skill 的元数据ValidationUtils.validateParameters()不是简单校验非空而是比对request.getParameters()的 key 是否全在metadata.getParameters().keySet()中且类型是否匹配——这实现了 runtime 的 schema 强一致性SkillResponse.metadata字段是 Claude 用来做 Skill 版本管理和灰度发布的依据不是可选字段。所以Spring Boot 在这里不是“干活的”而是“发证的”。它确保每个 Skill 的声明what、约束how、输出result三者严格一致。前30个失败80%是因为 Controller 里没做这三层校验导致 Claude 认为“这个 Skill 能处理”但执行时才发现参数不对、类型错、字段缺——这种不一致就是 Skill 生态崩坏的起点。3. 实操核心Spring Boot MyBatis 的 Skill 数据层设计陷阱与解法3.1 为什么“多商户跨境商城”源码里的 MyBatis 配置在 Skill 场景下全是雷网上流传的“spring boot mybatis 的 java 开源多商户跨境商城源码”其 MyBatis 配置看似完美多数据源、分库分表、读写分离。但搬到 Skill 场景下立刻暴露出三个致命缺陷缺陷一Mapper XML 的if test标签在 Skill 动态参数下失控商城源码里常见这种写法select idselectOrders resultTypeOrder SELECT * FROM orders WHERE status #{status} if testuserId ! null and userId ! AND user_id #{userId} /if if teststartTime ! null AND create_time #{startTime} /if /select问题在于Skill 的parameters是 Map 结构Claude 解析后传入的userId可能是null也可能是空字符串还可能是undefined当用户没提及时。而 MyBatis 的test表达式对null和的判断不一致——#{userId}为空时testuserId ! null为 true但userId实际值是导致 SQL 拼出AND user_id 查出一堆脏数据。正确解法是在 Skill 的 Service 层用ParameterValidator统一清洗参数public class ParameterValidator { public static String cleanString(String value) { if (value null || null.equals(value) || .equals(value.trim())) { return null; // 强制转为 null让 MyBatis 的 if test 正确生效 } return value.trim(); } }然后在 Mapper 里写if testuserId ! null AND user_id #{userId} /if——把参数净化逻辑从 XML 移到 Java才能应对 Skill 的不可控输入。缺陷二SelectProvider 的动态 SQL 在多租户场景下泄露数据商城源码常用SelectProvider构建动态 SQL比如SelectProvider(type OrderSqlProvider.class, method selectOrders) ListOrder selectOrders(Param(params) MapString, Object params);而OrderSqlProvider.selectOrders()里可能这样写public String selectOrders(MapString, Object params) { String sql SELECT * FROM orders WHERE 11; if (params.get(tenantId) ! null) { sql AND tenant_id params.get(tenantId); // 危险SQL 注入 } return sql; }在 Skill 场景下tenantId来自用户自然语言如“查一下亚马逊店铺的订单”Claude 解析后直接塞进 params这段代码就成了 SQL 注入温床。正确做法是永远用#{}占位符哪怕在 provider 里public String selectOrders(MapString, Object params) { return new SQL(){{ SELECT(*); FROM(orders); WHERE(11); if (params.get(tenantId) ! null) { WHERE(tenant_id #{tenantId}); // 注意这里是 #{}, 不是 ${} } }}.toString(); }缺陷三MyBatis 的二级缓存与 Skill 的无状态性冲突商城源码常开启二级缓存cache evictionLRU flushInterval60000 size1024 readOnlytrue/但在 Skill 场景下这会导致灾难性后果。比如第28个 Skill “查询用户最近3笔订单”第一次调用返回了 A 用户的订单第二次调用 B 用户由于二级缓存命中直接返回 A 用户的数据——因为 MyBatis 缓存 key 默认只含 SQL 和参数值不含 tenant_id 或 user_id 上下文。解决方案只有两个彻底关闭二级缓存在mybatis-config.xml中设setting namecacheEnabled valuefalse/用 Redis 做应用层缓存key 包含skillId userId timestamp为每个 Skill Mapper 单独配置缓存在 Mapper XML 顶部加cache evictionLRU flushInterval30000 size100 readOnlytrue /并确保flushInterval小于 Skill 的平均响应时间避免 stale data。注意Skill 的数据访问必须是“请求级隔离”。任何跨请求的共享状态包括 MyBatis 二级缓存、static 变量、ThreadLocal 未清理都是定时炸弹。我在第37个 Skill 里栽过跟头——用 ThreadLocal 存用户 token结果并发时 A 用户的 token 泄露给了 B 用户。3.2 Skill 数据层的黄金三角Connection Pool Transaction RetrySkill 的数据库访问必须遵循“黄金三角”原则连接池要稳、事务要短、重试要智。这不是最佳实践是生存法则。Connection PoolHikariCP 的 5 个必调参数默认 HikariCP 配置在 Skill 场景下必然雪崩。必须调整参数推荐值原因maximumPoolSizemin(20, 2 × CPU核心数)Skill 请求短平快连接数过多反而增加 GC 压力过高会导致 DB 连接耗尽connectionTimeout30003秒Skill 超时通常设 15s连接获取超时必须远小于此否则阻塞线程idleTimeout60000010分钟避免频繁创建销毁连接但不宜过长30min导致 DB 主动 killmaxLifetime180000030分钟小于 DB 的 wait_timeout防止连接被 DB 断开后 Hikari 不知情leakDetectionThreshold6000060秒检测连接泄漏Skill 中常见忘记 close() 或 try-with-resources 未覆盖TransactionTransactional 的三大禁忌Skill 的事务必须是“最小粒度、最短时间、无嵌套”。禁忌如下禁忌一在 Service 方法上加 Transactional却调用其他 Skill 的 HTTP 接口比如“下单 Skill”里先扣库存DB 事务再调用“发短信 Skill”HTTP。如果短信服务超时整个事务回滚但库存已扣——分布式事务没做纯属灾难。解法用 Saga 模式扣库存后发消息到 MQ由消费者调用短信 Skill失败则发补偿消息。禁忌二在 Transactional 方法里做耗时操作如调用外部 API、文件 IO这会持锁时间过长拖垮 DB。正确做法把耗时操作移到事务外用TransactionSynchronizationManager.registerSynchronization()注册事务后钩子。禁忌三用 PROPAGATION_REQUIRES_NEW 创建嵌套事务Skill 的每个执行单元必须是原子的。嵌套事务会让回滚逻辑复杂化且 MyBatis 的 SqlSession 在嵌套事务中行为不可控。一律用PROPAGATION_REQUIRED。RetryResilience4j 的 Skill 专属重试策略Skill 调用 DB 时网络抖动、DB 瞬时过载很常见。但盲目重试会放大问题。我的重试策略是Bean public RetryConfig skillDbRetryConfig() { return RetryConfig.custom() .maxAttempts(3) // 最多重试3次 .waitDuration(Duration.ofMillis(100)) // 初始等待100ms .ignoreExceptions(SQLException.class) // DB异常才重试 .retryExceptions( SQLTimeoutException.class, SQLNonTransientConnectionException.class ) // 只重试可恢复异常 .build(); }关键点不重试业务异常如UserNotFoundException那是逻辑错误重试没意义指数退避用ExponentialBackoffScheduler避免重试风暴熔断降级当重试失败率 50%自动熔断 30 秒返回SkillResponse.error(服务暂不可用请稍后再试)而不是让 Claude 一直 retry。4. 技术栈深度整合Claude Code、Spring Boot、MyBatis 的协同调试与问题排查4.1 Skill 调试的四层穿透法从 Claude 日志到 MyBatis BoundSql当 Skill 行为异常不能只看 Controller 日志。必须按四层顺序穿透排查第一层Claude 平台侧日志Skill Invocation Log在 Claude Workspace 的 Skill 监控页找到对应 Skill 的 invocation log。重点看input_parametersClaude 解析后的原始参数 Map确认是否含预期字段intent_confidence意图匹配置信度低于 0.7 说明intent_keywords需加强execution_statussuccess/timeout/error区分是 Skill 自身问题还是平台问题。提示如果execution_status是timeout但你的 Controller 日志没记录说明请求根本没进 Spring Boot——检查 Spring Boot 的server.tomcat.max-connections是否被占满或防火墙拦截。第二层Spring Boot Access LogNetty/Tomcat启用logging.level.org.apache.catalina.core.ContainerBase.[Tomcat].[localhost]看请求是否到达容器2024-05-20 14:22:33.123 DEBUG [http-nio-8080-exec-5] o.a.c.c.C.[Tomcat].[localhost].[/] : Mapped to com.example.skill.CouponRecommenderController#execute如果没这条日志说明请求路径/skill/coupon-recommender拼写错误注意大小写RequestMapping的consumes application/json与 Claude 发送的 Content-Type 不匹配Claude 固定发application/json不要改Spring Security 拦截了未认证请求Skill 必须配置permitAll()。第三层MyBatis 的 Statement Log在application.yml加logging: level: com.example.mapper: DEBUG org.apache.ibatis.logging.stdout.StdOutImpl: DEBUG运行后你会看到 Preparing: SELECT * FROM coupons WHERE tenant_id ? AND status ? AND valid_from ? AND valid_to ? Parameters: 123(Long), ACTIVE(String), 2024-05-20T00:00:00Z(String), 2024-05-20T00:00:00Z(String) Columns: id, code, discount_amount, ... Row: 456, COUPON2024, 50.0, ...关键看Parameters是否与 Skill Request 的parameters一致。如果Parameters里tenant_id是null但日志显示123说明你在 Service 层做了参数转换——这时要检查ParameterValidator是否生效。第四层DB Query LogMySQL slow_query_log开启 MySQL 慢查询日志SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 0.1; -- 记录所有超过100ms的查询然后查slow.log确认 SQL 是否真的执行、执行计划是否走索引、是否有锁等待。Skill 的 DB 问题80% 出现在这一层没给tenant_id字段建索引导致全表扫描valid_from和valid_to的范围查询没用到复合索引ORDER BY RAND()被滥用拖慢响应。4.2 常见问题速查表50个 Skill 踩过的27个坑我把前50个 Skill 的全部故障归类为以下高频问题附带根因和解法问题现象根因解法实操验证方式Skill 在 Claude 测试面板能跑线上不触发intent_keywords未覆盖用户口语表达如用户说“打折”而 keywords 只有“优惠”用anthropic-sdk的analyze_intent工具输入100条真实用户 query统计匹配率补充方言、缩写、错别字如“打拆”、“优慧”在 Claude Workspace 的 Skill 测试页用“用户说”功能输入“给我打个折”看是否匹配Skill 返回 400 Bad Request但 Controller 没日志Spring Boot 的Valid校验失败被全局异常处理器吞掉在RestControllerAdvice里加ExceptionHandler(MethodArgumentNotValidException.class)打印BindingResult的详细错误用 curl 模拟非法参数curl -X POST -H Content-Type: application/json -d {parameters:{user_id:}} http://localhost:8080/skill/coupon-recommenderMyBatis 查询结果为空但 DB 里有数据Param注解缺失导致 MyBatis 无法绑定 Map 参数在 Mapper 接口方法参数前加Param(params)XML 里用#{params.userId}引用在 Mapper XML 的select标签内加bind namedebug valueDEBUG: params/看日志是否输出 debug 信息Skill 响应时间忽高忽低100ms~5sHikariCP 连接池connectionTimeout过小或maximumPoolSize不足调大connectionTimeout至 3000maximumPoolSize至 20并监控HikariPool-1.active指标用 JMeter 压测观察HikariPool-1.idle和HikariPool-1.total是否稳定多 Skill 同时调用时DB 连接数暴涨每个 Skill 的Mapper都创建独立 SqlSession未复用确保MapperScan只扫一次所有 Mapper 共享同一个 SqlSessionFactory查 Spring Boot 启动日志确认MapperFactoryBean只初始化一次Skill 返回 JSON但 Claude 解析失败SkillResponse的output字段不是标准 JSON 对象如含java.time.Instant用JsonFormat(pattern yyyy-MM-dd HH:mm:ss)注解Instant字段或用ObjectMapper预序列化在 Controller 里System.out.println(new ObjectMapper().writeValueAsString(response))确认输出是合法 JSON实操心得我建了个SkillTroubleshooter工具类集成上述四层日志的快速采集命令。比如SkillTroubleshooter.trace(coupon-recommender, 2024-05-20T14:22:33Z)它会自动① 查 Claude 平台该时间点的 invocation log② grep Spring Boot 日志中该时间窗口的 access log③ 打印 MyBatis 的 bound SQL④ 执行show processlist查 DB 连接状态。这个工具让我排查效率提升 5 倍。4.3 Spring Boot 配置电路Boot 的启动失败诊断树标题里提到的 “boot配置电路”、“win11虚拟机安装出现boot”、“could not read the boot disk”其实都指向同一个底层机制Spring Boot 的ApplicationContext初始化流程。当 Skill 项目启动失败不是“Spring Boot 挂了”而是某个 Bean 的创建链条断了。我的诊断树如下从易到难Step 1检查application.yml的 indentationYAML 对空格极其敏感。datasource.url缩进错一位就会报IllegalArgumentException: URL must start with jdbc。用在线 YAML validator如 https://yamlchecker.com/粘贴配置100% 问题在此。Step 2确认MapperScan的包路径正确MapperScan(com.example.mapper)必须精确匹配 Mapper 接口所在包。如果 Mapper 在com.example.skill.mapper而MapperScan写成com.example.mapper就会报NoSuchBeanDefinitionException。解法在MapperScan上加basePackages com.example.skill.mapper并确保该包下有.java文件。Step 3检查Configuration类的Bean方法签名Skill 项目里常写Configuration public class SkillConfig { Bean public SkillMetadata couponMetadata() { return new SkillMetadata(); // 错没设参数 } }但SkillMetadata的ConfigurationProperties需要EnableConfigurationProperties(SkillMetadata.class)才能绑定 yml。正确写法Configuration EnableConfigurationProperties(SkillMetadata.class) public class SkillConfig { Bean ConditionalOnProperty(name skill.coupon-recommender.enabled, havingValue true) public SkillMetadata couponMetadata() { return new SkillMetadata(); } }Step 4排查spring-boot-starter-web与spring-boot-starter-webflux冲突如果项目同时引入这两个 starter会报WebApplicationType冲突。Skill 必须用spring-boot-starter-webServlet 容器不能用 WebFlux。删掉spring-boot-starter-webflux依赖。Step 5终极手段——启用debugtrue启动在application.yml加logging: level: org.springframework.boot: DEBUG org.springframework.context: DEBUG启动时会输出完整的 Bean 创建顺序。找最后一行Creating shared instance of singleton bean xxx如果卡在这里说明xxx的构造函数或PostConstruct方法抛异常——这就是真正的根因。5. 从 Skill 编码到工程化193 和 247 编码背后的团队协作规范标题里提到的 “skill编码193”、“skill编码247”不是随机数字而是我们团队的 Skill 编号规范。它背后是一套完整的工程化治理体系解决了“50个 Skill”带来的混乱。5.1 Skill 编码规则193 和 247 的含义解密我们的 Skill ID 格式是S{domain}{type}{seq}其中S固定前缀表示 Skilldomain2位数字代表业务域19电商24营销31客服42支付type1位数字代表 Skill 类型1查询类2操作类3计算类4通知类seq3位数字按创建顺序递增。所以S193是“电商域的查询类 Skill”第3个S247是“营销域的操作类 Skill”第7个。这个编号直接映射到 Git 分支名feature/skill-S193-coupon-recommender和 Docker 镜像标签skill-s193:1.2.0。为什么这么设计因为前30个 Skill 混乱的根源是没有唯一标识。大家口头说“那个查订单的 Skill”结果有5个Git 提交里写“fix skill bug”不知道修哪个。编号制强制所有人用 ID 交流所有文档、监控、告警都绑定 ID。5.2 Skill 生命周期管理从开发到灰度的 7 个阶段一个 Skill 不是写完就上线它要走完标准化生命周期阶段关键动作负责人交付物1. 需求评审确认用户场景、意图关键词、参数列表、失败兜底话术产品经理AI工程师《Skill需求说明书》2. 元数据设计定义skill.yaml的name,description,intent_keywords,parametersAI工程师skill-S193.yaml3. Spring Boot 开发实现 Controller、Service、Mapper集成 MyBatis后端工程师可运行的 Jar 包4. 本地联调用curl模拟 Claude 请求验证输入输出全员Postman Collection5. 平台集成在 Claude Workspace 上传 Skill配置 webhook 或 sync endpointAI工程师Workspace 中的 Skill 状态为 “Active”6. 灰度发布先对 1% 用户开放监控intent_confidence和error_rateSRE灰度报告7. 全量上线intent_confidence 0.85 且error_rate 0.5%切流 100%产品经理上线公告注意第6步灰度是生死线。我第42个 Skill “跨境运费估算”灰度时发现intent_confidence只有 0.62原因是用户说“寄到美国多少钱”Claude 匹配了“国内运费 Skill”。立刻补intent_keywords“美国, USA, overseas, international, freight”2小时后 confidence 升到 0.91。5.3 技术债清理如何重构前30个白写的 Skill重构不是重写而是渐进式替换。我的方案是Step 1建立 Skill 元数据仓库用 Git 管理所有skill-*.yaml每个 Skill 一个文件目录结构skills/ ├── s193-coupon-recommender/ │ ├── skill.yaml │ ├── README.md # 使用场景、参数说明、已知问题 │ └── test-cases.json # 10条