Goal: make **"AI able to operate Cloud Development 100%"Verifiable — every high-frequency control-plane scenario has a recipe that can be run through by following it, and each one is built onpublic APIs**.
When to use
- Add a recipe, or modify an existing one
- Decide whether a certain capabilityshouldenter the recipe set (admission)
- Update the
config/source/skills/cloud-api-operations/references/recipes/README.mdtable of contents and status column - Review whether existing recipes still meet admission (whether the Action is already public, whether it is already covered by a dedicated MCP tool)
- Someone (or their Agent) wants to submit a recipe — give
./references/contributing.mdto them
Why not wrap everything into MCP / CLI (division-of-labor principle)
In product roadmaps, "opening up APIs" is usually not a priority, so there is a fixed gap:a feature is available in the console, but AI cannot operate it. The causal chain is one-way — without a public API, CLI / MCP have nothing to wrap, and AI can only use a mouse.
There are two ways to close the gap, and we do not take the "wrap everything" one:
| Approach | Cost |
|---|---|
| Fill in all capabilities as APIs, then wrap them all as tools | The tool surface keeps expanding: the probability of the model choosing the wrong tool rises, tool descriptions eat into the context budget, and every tool must be maintained alongside API changes |
| Wrap only the core; long-tail capabilities go through recipes | One more document must be maintained, but the tool surface stays stable, and long-tail capabilities can still be operated by AI |
Core capabilities have already been wrapped(query* / manage* tool family + CLI), and the large number of remaining interfacesdo not needwrapping. Where a capability should go:
| Capability form | Belongs to |
|---|---|
| Has a public API + high-frequency core | Already in MCP / CLI tools — use it directly, do not write a separate recipe |
| Has a public API + long tail (chains multiple Actions, has non-trivial prerequisites, has pitfalls or obligations to look back up) | Write a recipe(this skill) |
| Has a public API + a single interface, callable just by reading the docs | Neither wrap it nor write a recipe — the agent looks it up in the index on the spot |
| No public API(visible only in the console) | No one can wrap it — product gap, push the product to open up the API first; there is no recipe to write here either |
The last row is a hard constraint:without a public API, a capability's usability for AI is 0, and this is not a matter of "we have not wrapped enough".
Admission: API first (hard threshold, pass this before starting to write)
All three must be satisfied at the same time:
- Has a public cloud API: the target Action is listed in the public API overview / interface documentation (
https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/api-reference.mdor the product's official documentation). - Language-agnostic and callable: direct cloud API calls / official SDK / CLI / MCP — any one of these paths is fine, but it must bea wrapper around a public API, and not the reverse, "only an SDK wrapper in some language, with no public Action".
- Write operations can be completed independently by the user: Includes signatures, necessary prerequisite queries, and encryption/decryption. If a key step depends on internal configuration that the client cannot access, it does not qualify.
Handling when not satisfied:
- Do notwrite into
config/source/skills/(that is the external release surface) - At the same time, asproduct gapfeedback: ask the official team to include these Actions in the public overview, or provide a language-agnostic invocation path
- Archive: maintainers put it in
specs/cloud-api-skill/pending-recipes/(internal directory, not under version control), and write clearly at the top of the body "why it was not approved + unblocking conditions"; external contributors do not submit a PR, but according to./references/contributing.mdopen an Issue to explain
For encountered examples, see the "negative cases" below — after rejection they do not go into the public set, and do not copy the archived content back.
Negative list (should not write a recipe)
- Those that can be called directly by reading the public API overview + a single interface document (single Action, parameters can be looked up, no chaining needed) → let the agent go through
SKILL.md §1's discovery process and look it up on the spot - Those already covered by dedicated MCP tools (
query*/manage*family) → route to the tool, do not write documentation - Pure parameter meaning / concept explanations → belong to official documentation, route there
- Usage that only holds for a certain SDK language (there is no language-agnostic path other than that SDK)
Positive list (must write a recipe)
- Requireschaining multiple Actions or multiple products(example: monitor policy family + postgres
DescribeDBInstances) - Hasnon-trivial prerequisites: CAM authorization upgrade, temporary credentials, environment binding, signing or encryption
- Hasactually encountered pitfalls: error message → correct approach, cannot be inferred by reading the documentation
- After invocation, hasre-check / cleanup obligations: idempotent duplicate checking, unbinding, residual dimension cleanup
Negative cases (what an admission rejection looks like)
Integration Center UserKey key pair management — the full text is in the internal archive specs/cloud-api-skill/pending-recipes/integration-userkey.md(not under version control, external readers cannot see the content; write it yourself according to "write three things on the archived page after rejection" below).
It at the timepassed two items on the positive list: had non-trivial prerequisites (password field encryption), had actually encountered pitfalls (leaving the SDK results in plaintext being stored in the database). But it failed all three hard thresholds:
| Hard threshold | Actual |
|---|---|
| Has a public cloud API | 6 Actions (GetUserKeyList / CreateUserKey / ...) are in the public overview 0 hits |
| Language-agnostic and invokable | The only path is @cloudbase/manager-node 's wrapper; Python / Go / Java users have no corresponding entry point |
| Write operations can be completed independently | The encryption key for password is fetched internally by the SDK from QicaiShi (QueryRainbowConfig), there is no entry point on the public surface → bare calls to the cloud API cannot encrypt, and the write path does not work |
What should be remembered is the missed-judgment mechanism, not this conclusion: treating "it is fully documented in the official SDK documentation and runs locally" as "the capability is within the public API contract".SDK documentation public ≠ Action public。
Self-check before writing: Before starting to write, take the target Action name and search it once in the public overview / official API documentation (for entry points, see Admission §1 above). If there are 0 hits, stop; do not add it to the public set.
After rejection, the archive page records three things(copy from the pending-recipes/ version): ① why it was not approved, mapped item by item to the three thresholds above; ② unblock conditions — meeting any one of them allows it to be moved back to the public set; ③ explicitly prohibit "copying it back as a public skill."
Permission model (maintainer's perspective: should this step be preset away?)
What users see is "click the authorization link"; what we need to decide iswhether to make this step disappear. There are three dispositions:
| Disposition | What situation | How to do it |
|---|---|---|
| Preset into the role family | The capability is general-purpose, high-frequency, and within TCB product semantics (TCR pulling images and CloudBase Run accessing VPC/CVM fall into this category) | Push the product to TCB_QcsRole to attach a preset policy, with naming following QcloudAccessForTCBRoleIn<X> → users take zero action |
| Let users append on demand | Long-tail capabilities, cross-product, or the preset policy granularity is too coarse (QcloudMonitorFullAccess is full read-write) |
Provide a one-click authorization link, and write it into the recipe's 前置权限。Go this route by default — attaching broad permissions to the default role is equivalent to expanding the default authorization scope for all users |
| Do none | The capability has no public API (the admission-rejection category) | Stop here; do not write a recipe or preset it; provide it as product gap feedback |
Order of judgment: first pass admission (if there is no public API, stop), then see whether it is worth adding to the role family; if it passes neither, leave only the one-click authorization link.
Before starting to write a preset, do an identity check first: confirm that the credentials for this path are really initiated as that role.auth get_temp_credentials What is exported is the Web / device login'ssametemporary credentials (not a separate role assumption), so only if a real test with an identity whose permissions are narrowed to exactly lack this Action reports UnauthorizedOperationcan it be determined that the role policy takes effect on this path. If it cannot be determined, provide the one-click authorization link; do not preset it.
One observed clue: account-level device login credentials can pass cam:CreateRole、cdn:DescribeDomains, while the same account's TCB_QcsRole mounted policies do not include these two items — this tends to indicate that what it obtainsis notthat role. But this result would also be obtained by logging in with the main account, so it only counts if retested with a narrow-permission sub-account; before retesting, externally write only "judge by credential identity" and do not externally assert that the CLI / MCP uses the role.
Settled attribution(by capability family, not by product):
| Capability | Disposition | Basis |
|---|---|---|
| Cloud Development's own domains (static hosting, gateway custom domain binding / unbinding / task query) | Preset — already in the role family | Goes through tcb:* and cdn:Tcb*, within TCB product semantics |
| Certificate read-only (list certificates / details / verification chain) | Preset — already in the role family | Binding a custom domain requires choosing one from the certificate list, which is a necessary read permission |
| Cloud Monitor alert family | Preset — already in the role family | Delivered with the base policy monitor:*, not attached separately QcloudMonitorFullAccess |
General CDN acceleration domain (AddCdnDomain etc.) |
Let users append on demand | Cross-product long tail;QcloudCDNFullAccess granularity too coarse |
| DNS resolution (DNSPod) | Let users append on demand | Cross-product long tail: resolution record operations have no product semantic binding with Cloud Development |
| Certificate write (apply / renew / delete / deploy) | Let users append on demand | Same as above;QcloudSSLFullAccess is full read-write, granularity somewhat coarse |
The policies attached to roles change with product iterations, and the table above may be outdated — refer to ListAttachedRolePolicies + GetPolicy the actual policy content read, do not treat historical conclusions as facts.
The services used must be in the MCP whitelist
The services appearing in the recipe must be able to be called directly via callCloudApi . The whitelist is mcp/src/tools/capi.ts 's SERVICE_VERSIONS(enumeration + official version mapping). When writing a recipe, if you find the target product is not in the table,add it to the table first, then write the recipe: for single-version products write service: ["YYYY-MM-DD"], for multi-version products list all officially used versions in the array (when absent, the caller must explicitly pass version). Version numbers are taken from the official SDK directory tencentcloud/<service>/v<YYYYMMDD>, do not fill them in from memory.
After modifying SERVICE_VERSIONS you must synctwo placesand then run the artifact chain: the complete index on the user side config/source/skills/cloud-api-operations/references/service-versions.md(the grouped table grouped by product line), and references/calling-methods.md §1's "most commonly used few" small table (only touch this table when core products change).
For link concatenation, role carrier reading, and policy coverage scope, see the external skill's config/source/skills/cloud-api-operations/references/calling-methods.md §3 — maintainer-perspective contentis written only in this skill(the external skill only writes "how to click to authorize", not this section).
Run it through yourself before writing (empirical standard)
Passing admission does not mean you can write — first use a real account to get theread-onlypath working, then decide which go into the main sequence and which go into the "pitfall list".
- When the target service is not in
SERVICE_VERSIONS, add it to the table first(mcp/src/tools/capi.ts), then rebuild the local artifacts: runbuild:webpack,do not runnpm run build— itsprebuild = rm -rf distwill be blocked by the safe-delete hook, and the whole chain breaks at the first step. Version numbers are taken from the official API documentation page or SDK directorytencentcloud/<service>/v<YYYYMMDD>, fill them in only if the two match. callCloudApihas an environment binding gate: after account-level login, the first call will still returnENV_REQUIRED("logged in, but not yet bound to an environment"). Firstauth(set_env, envId=…)bind a normal environment — account-level business like ICP filing is unrelated to the environment, binding is only to pass the gate.- Use a stdio probe to directly drive the local
mcp/dist/cli.cjs(initialize→tools/call), much faster than hooking into a client; for local MCP startup and probe scripts see skillcloudbase-mcp-local-probe。 - only run
Describe*/Check*/Validate*, leave all write operations for the user to execute manually. If a write path can only be initiated with parameters from within an order flow, that precisely shows it does not go into the main sequence. - But when you have your own freely disposable test environment, write operations must actually be run, and restored after running — otherwise, the re-check step after the write operation will forever be stuck at "document verification," and the entire piece will have to be marked
🟡 部分实证. Choose a pay-as-you-go, rebuildable environment that does not affect others, and run the entire sequence (submit → poll progress → re-check result → change back to the original value and confirm again);both directions must be run, because scaling up and scaling down often take different branches and also differ in duration (measured: 174 s to scale up / 67 s to scale down, same specification and same region). A real run will also expose things that read-only can never see: the task step table grows as it runs, and the input parameters and task details use two different sets of enums for the same field. - When a write API has a built-in "validate only" switch (
DryRun/预检), you can test it under read-only conditions — but both conditions must be met: (1) the official documentation or the console's own implementation explicitly states that itdoes not create an order and does not initiate a change; (2) choose parameters where "target value = current value," so that if the switch does not take effect, it will only hit the idempotent branch and will not actually change anything. After running, you must read the current state once more to confirm nothing changed (e.g., after a configuration-change precheck, re-read the instance attributes and compare the specification withUpdateTime)。 - The console's real routing and "which Action it is calling" are both discoverable, no need to guess: in the frontend repository,
src/framework/routes/groups/**is the source of truth for routing (get the page hash path),src/services/capi-action-definitions/**is the source of truth for "which cloud API the console calls, what each field means, and the null-value conventions of the response body"; the page's own hooks / utils can also show the data source andunit conversions(same field name but different units — most likely only documented here). Before writing a recipe, read these three places first; compared with reading only the official documentation, you gain three things: "the real call sequence + field semantics + pitfalls." - First check whether a dedicated tool already covers it, then decide whether this section should write about the cloud API: the recipe's readers have MCP tools on hand; if a dedicated tool can be used, do not teach them to go through the raw cloud API — the dedicated tool has already handled the troublesome things for them, such as normalizing activation status, polling asynchronous tasks, and inconsistent error messages.
queryCloudRun/manageCloudRunFor tools like the action list is its capability boundary(only what is not in the list falls through tocallCloudApi). When a capability has both a dedicated tool and a cloud API, write only the dedicated tool in the recipe; only write the raw cloud API for the part not covered by a dedicated tool, and explain in the text why it is bypassed (e.g., Cloud Hosting fixed IP has an Action intcbr, but it is not in the action lists of the two Cloud Hosting tools). - The same environment can span multiple services; do not assume by default that they belong to the same product, and do not assume the capability is already activated: a Cloud Development environment ID is also the
tcbrof Cloud Hosting (EnvId), but Cloud Hosting must be activated separately. When writing the "prerequisite permissions" table, split services by capability, and state clearly in the body that "this capability may not yet be activated, how to tell, and how to activate it"; otherwise readers will mistake a null value for "activated and not enabled." - Null-value criteria need one more layer of tracing: the same null value often corresponds to two or more states (Cloud Hosting not activated vs. activated but fixed IP turned off;
Statusare both empty strings). Before using it as a criterion, first find the call that can distinguish the states and put it earlier in the sequence; otherwise, when readers get a null value, they will not know what to do next.When a criterion field has a value domain, write it out completely according to the value domain; do not use approximations like "null value = off": For fields with enum comments in the type definition (e.g.status: string; // normal | isolate), write it as "normal= enabled, all other values including the empty string = disabled"; otherwise, whoever reads the third value won't know how to judge it. - ⚠️ Before testing, first confirm the credential can see the target: read interfaces oftensilently return success + empty valuefor "targets that don't belong to the current credential", rather than reporting an error — it's very easy to mistake "queried an empty result" for "verification passed". Test an envId that doesn't belong to the current account:
tcb/DescribeICPResourcesInforeturnssuccess+ an empty array (looks like "qualified"),tcbr/DescribeStableIPreturnssuccess+ emptyStatus、queryCloudRun(action="envStatus")returnsunopened, in the same batch of probesonlytcb/DescribeEnvInforeportsenv not found. Before hands-on verification, first use an interface that is "guaranteed to error" to probe target reachability; otherwise the conclusions of the entire round of testing are built on empty values. - ⚠️ Distinguish the attribution between "not enabled" and "unreachable": the same empty value may come from "the capability is not enabled", or from "the target is not under the current credential at all". To establish that it's the former, there must be a sample wherethe target does belong to the current credential and the capability is not enabled; an empty value measured from an unreachable target can only support "silently returns empty when unreachable", and cannot be written as "returns empty when not enabled" — this is a mistake that has already been made once (treating an environment that doesn't belong to the current account as a sample of "Cloud Hosting not enabled").
- ⚠️ Account-level login ≠ having operation permissions for that product; but "being denied" should first be attributed to the account, not to the product:
baFor operation-level authorization products like this, account-level credentials may indeed be denied across the entire family (all nine read-only interfacesUnauthorizedOperation, the action names are recognized normally and it gets stuck at the policy layer). Butthe same batch of interfaces returns data normally under the credential of the account that owns the business entity — both sides have been tested, so the real variable iswhether the credential and the target are in the same account. In "Prerequisite Permissions", neither write an unverified optimistic assertion like "an account reading its own data generally doesn't need extra policies", nor write a single denial as "this product requires extra policies"; state the account variable clearly and give the expectation after switching to the correct account. Before verification, first confirm the credential and the target are in the same account; otherwise the entire round of conclusions is an illusion caused by "wrong account". - Treat "which required parameter is missing" as a criterion, not as an obstacle: returning
missing the required parameter Skey / IcpOrderID / Uinparameters that can only be obtained from internal processes is equivalent to declaring that the interface cannot be used independently — it should be written into the "pitfall list" as a counterexample, not into the main sequence. - After running, record each Action'sactual return shape(field names, empty-value behavior), so that the "what to fetch" column in the main sequence has a basis; a read-only empty result is a normal result, and you cannot judge from it that the interface is not working.
- When the criterion lands in the console, turn it into a clickable link; don't just write "go check the console". During testing, note down the page hash path along the way, and write it into the recipe using
https://tcb.cloud.tencent.com/dev?envId=<envId>#/<path>to concatenate.The single source of truth for console paths isconfig/source/skills/cloudbase-platform/SKILL.md'sEntry pointslist —— First fill in that copy at the path where it does not exist, then reference it in the recipe; otherwise, having a copy written in each of the two places will inevitably drift. For the same criterion, there is often an error page that 'looks like it should be here' (for example, the fixed IP switch for ICP filing access is on the ICP filing management page, and there is no card on the environment settings page). Writing these kinds of comparisons into the 'pitfall list' saves users more time than giving only one correct link.
Structure (five fixed sections; add a sixth only for special entry points)
Fixed order:When to use → 前置权限 → 接口序列 → 踩坑清单 → 验证步骤(+ optional 入口). What is published externally is this order itself; do not add or remove headings.
| Section | What to write | Completion condition (determinable) |
|---|---|---|
When to use |
What symptoms should make someone turn to this article (determinable trigger conditions, not a topic summary); end with a sentence that draws the boundary: what this article does not cover and where to go instead | The trigger conditions can match the error/phenomenon the user has in hand |
前置权限 |
The required service / version, credential identity (account-level / environment-level / service role), which CAM policies are needed; official documentation entry points that can be used directly | When permissions are insufficient, can directly provide aclickable one-click authorization link(role name + policy name + principal), rather than "go to the CAM console and add the policy" |
接口序列 |
Minimal runnable sequence (code block or step table), with return key points for each step; methods/parameters are written in full in onlyone placeWrite it in full | The return value of each step can correspond to the input parameters of the next step;every step in the table has a service column(header is written as service or service / version); when multiple services are used, write them in subsections, and each subsection's header must have that column —— omit this column, and readers can only guess whose API this step is |
踩坑清单 |
Table: pitfall / phenomenon (paste the real error text) / correct approach | Each row can correspond to a real error or reproducible phenomenon |
验证步骤 |
How to self-check after calling (read-only self-check, post-write recheck, idempotency duplicate check) | Each item has a clear pass criterion (field has a value / counts match), without using words like "reasonable" |
Writing rules (extracted from writing-for-agents, with an implementation method for each item)
| Leverage | Implementation in this repository | Counterexample |
|---|---|---|
| Single source of truth | The sequence is written only once in a code block or table; the index table does not repeat its content | The same sequence is written "once in the table and once in the code" |
| context pointer | Title and first sentence state thesymptom, so the agent can determine "whether to turn to this article" | The title is written as a topic word ("About alerts") |
| Information hierarchy + progressive disclosure | in-file step > in-file reference > disclosed reference: details needed only byone branchshould be pushed down into the branch or point to something that can be looked up, not stuffed into the main sequence | Writing the full AES/IV/base64 set into the main text, when it is needed only for "implementing encryption yourself outside the SDK" |
| co-location | Put related things in the same section, and create fewer headings | "Because this article needs to solve what" + "capability entry point" are two headings discussing the same thing |
| Completion condition | Give decidable criteria for each section; when legwork is required, state explicitly what to read | 「TotalCount "reasonable" cannot be decided and will induce early wrap-up |
| leading word | Reuse existing compact terms, don't coin your own | Name "silently writing plaintext when the SDK cannot retrieve the key" plaintext persistence, forming the same vocabulary pair with "ciphertext persistence self-check" |
| Positive phrasing | Write "change and check X", "go through Y", don't write a string of "don't..." (prohibited words drag the behavior into context) | "Don't guess the Action name, don't use indexes, don't..." |
| The environment is the source of truth | Write commands and paths so they can be executed directly in the current environment; don't cache what can be looked up locally | Cache node_modules implementation details that can be read right there |
| Pruning | Delete no-ops, delete sediment, delete content duplicated elsewhere | The same fact written once each in SKILL.md / README / recipe |
Copy red lines (external-facing files, user's perspective)
- Don't write: sources and verification process (which document/source code it came from, which versions were compared, how we checked it), test status narration, maintainer to-dos
- Don't write: internal identifiers (AppId / Uin / internal environment names / in-repo paths)
- "Actually tested" is a value-level marker: attach it to a specific parameter, interface, or return value, at most one;don'tattach "(all actually tested)" to an entire section or table — it has no per-row resolution, and unverified content added later will inherit this label, which is exactly what makes AI adopt unverified things with high confidence
- Record credibility in only one place:
recipes/README.md: the "Status" column of the directory table (✅ 生产实证(日期)/🟡 部分实证(日期)/🟡 文档核对,未实跑); don't repeat it in SKILL.md entries or recipe body text - Grade status by the range actually run, don't paste
✅across the whole document: it's normal for "run" and "not run" to be mixed in one document — write operations require real destruction, and permission or billing limits may block things.✅requires the entire interface sequence to have been run end to end; if only part was run (only one of the paths went through, or only the read-only steps were run), use🟡 部分实证,and write the parts not run in the status column, so users know which few to be cautious about. The cost of pasting✅across the whole document is the same as attaching "(all actually tested)" to an entire section: unverified content added later will inherit confidence it didn't earn. (2026-09-24 lesson: both recipes had only some paths actually run, but✅was mistakenly pasted first, and it was only discovered during self-check — after writing the status column, go back and check it against "which steps in this sequence were actually executed.") - Interface wording is neither "actually tested" nor "official documentation", and must be marked separately: prompt text the console shows users can be quoted verbatim; for unit price constants in usage estimates, write "the usage estimate on the console submission page is given according to this wording", so users can verify it themselves in the interface. Don't attach the "actually tested" label (that wasn't produced by running), and don't cite it as an official documentation contract — both would make users rely on it under wrong premises. This type of newly added content must likewise be written into the status column, naming that it was not actually run, so it doesn't follow the interface sequence's
✅are adopted together with high confidence. (2026-09-25: when adding "no plan upgrade needed" to the PG recipe, the console's resource-point unit price constant was brought in; the body states it is the UI wording, and the status column separately notes one entry has not actually been run.) - The authoritative copy of the above external rules is in
config/source/skills/cloud-api-operations/references/recipes/README.md; when changing structure/red lines, look at both places together
Index maintenance
- Register a new recipe in the
recipes/README.mddirectory table: number + scenario name + document link + status - One scenario per article; when one recipe grows to two independent sequences, split by invocation or sequence rather than continuing to add sections
- For scenarios not yet written as an independent recipe, write them into "Scenarios without an independent recipe" and specify which article to reuse and what parameters to change
Contribution and self-testing (for external contributors)
For the complete process of adding a recipe, what the PR should say, and the three layers of self-testing that must be done before submitting a PR (machine validation / end-to-end actual run / multi-model cold start), see ./references/contributing.md。
When reading this file directly from a raw link: relative paths appended after https://raw.githubusercontent.com/TencentCloudBase/CloudBase-AI-Toolkit/main/ will retrieve the corresponding file.
Artifact chain and validation (must run after changes)
export PATH=/usr/local/bin:$PATH # 缺它 → git-lfs 钩子报错,&& 链会静默断掉
NODE=/Users/bookerzhao/.workbuddy/binaries/node/versions/22.22.2-3/bin/node
$NODE scripts/generate-prompts-data.mjs && $NODE scripts/generate-prompts.mjs
$NODE scripts/sync-claude-skills-mirror.mjs && $NODE scripts/build-compat-config.mjs
$NODE scripts/check-prompts-sync.mjs && $NODE scripts/sync-claude-skills-mirror.mjs --check
$NODE scripts/diff-compat-config.mjs # Has blocking diff: NO
$NODE skills/cloud-api-recipe-authoring/scripts/check-recipes.mjs # 表格结构 + 编号交叉引用
-
After changing the structure of an interface sequence (deleting sections, adding sections, reordering step numbers), you must run
check-recipes.mjs。 Step numbers are written in two unrelated places — the cell in the "Step" column and the "Step N" sentence in the body — changing one will not make the other report an error; it will only make readers look for a number that does not exist (or points to another step). The script also checks the number of table columns (including\|escapes, separator rows, blank lines after tables) and backtick pairing; a non-zero exit code means there is a problem.Do not use your eyes instead of it: an off-by-one number looks completely normal. -
Adding or deletingany text surface will make compat-diff report blocking, and you must refresh
config/source/editor-config/compat-baseline.json -
Writing a new recipe = adding a file, so only a full refresh can be used.
update-compat-baseline.mjs --only <skill>only changesexistingkeys; when it matches a new file it will record it inunseenand prompt to do a full refresh — it will not add it for you (existence-level changes can only be recalculated as a whole). Therefore:- Changing the body of an existing recipe → a targeted refresh is enough (
--only cloud-api-operations), and it also avoids incidentally whitewashing other people's drift - Adding / deleting a recipe → full
npm run update:compat-baseline
- Changing the body of an existing recipe → a targeted refresh is enough (
-
After a full refresh, check the diff scope before committing, do not assume by default that it swallowed other people's drift. Actual test (2026-09-24, full refresh after adding a recipe): all changed paths in the baseline diff fell on the two skills touched this time, with none falling on other skills — because the baseline at the fork point is consistent with the source. The criterion is mechanically checkable: deduplicate the paths in the diff; only the skill names you changed should appear; if a third one appears, stop and check whether that skill's source or baseline is outdated.
-
A new recipe must also include two derived artifacts:
config/.claude/skills/**mirror anddoc/prompts/cloud-api-operations.mdx. When running tests, note that/usr/local/bin/nodewill make vitest fail to start directly; for the workaround, see skillcloudbase-mcp-artifact-pipeline -
Do not claim the change is complete before validation is finished: all three pre-checks pass +
git diffcontains no non-target changed lines
Common pitfalls
- Chinese proofreadingDo not use shell
grep: In this session, actual testing silently returns empty and exit 0 (false negative); use the built-in retrieval tool - Authorization guidance must land on "role name + policy name + clickable link"; writing "go to the CAM console to add a policy" amounts to throwing the cost of finding the way back to the user
- The sandbox PATH does not contain
/usr/local/bin(git-lfs is there),git checkout <file>returns non-zero and breaks the&&chain - ⚠️ "I can't reproduce it now" does not equal "it was never tested back then" — removing an annotation and changing a conclusion both require evidence: When reviewing an existing recipe, first check who added this annotation, when, and under what conditions (
git log -S"<原句>"find the introducing commit, then look through that day's actual test records). Test conditions change (different account, different region, different build version); writing "conditions differ so it can't be reproduced" as "the annotation has no evidence" amounts to creating a rollback yourself. This round it happened: a value-level "actually tested" annotation nearly got removed as having no evidence, when the real return could actually be found in that round's probe records. - When deleting a recipe, besides the source file and the
config/.claude/mirror, also clear the references in SKILL.md and README, then run the artifact chain - After deleting sections or reordering numbering, the "Step" column of the table must be changed separately again: "Step N" in the body text and the table cells are two independent sets of text; changing only one place will not produce any error. Actually tested (2026-09-25): after deleting an entire section, the body text had been renumbered to 8/9/10, while the table was still stuck at 9/10/11 — off by one, so a reader following 8 to the table would land on a different step. Run
scripts/check-recipes.mjsdetection, don't rely on "it looks fine" during review