Skip to content

feat(skills): 新增 objectstack-upgrade 升级剧本 —— AI 一键升级客户元数据项目 (#6111) - #6193

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-6111-v17-upgrade-skill
Aug 7, 2026
Merged

feat(skills): 新增 objectstack-upgrade 升级剧本 —— AI 一键升级客户元数据项目 (#6111)#6193
hotlong merged 2 commits into
mainfrom
claude/issue-6111-v17-upgrade-skill

Conversation

@hotlong

@hotlong hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #6111

新增发布侧升级剧本 skills/objectstack-upgrade/(domain: process,673 行,随 pm-dispatch 家族只带 SKILL.md)。剧本骑在 ADR-0087 D2 conversions 之上,不替代确定性转换,也不新增任何 CLI 命令 / 脚本 / 门。

三层如何落地

机械层 —— 只调用,AI 不重实现

剧本把 os migrate meta 当作既有底座来说明与编排,并把最容易踩的事实写在最显眼处:

  • 该命令不改写源文件,只在内存里重放链、打印带 conversionId 归属的 diff,唯一落盘的是 --out 的 JSON 快照。AST 改写 TS config 有损(丢注释、看不见来自 import 的值),所以「拿 --out 当 oracle 去 diff,而不是当成品提交」是剧本的硬要求。
  • 存量行:applyConversionsToStoredItem 在 rehydration 时无条件重放(含 includeRetired),所以 ⛔ 从不手改 sys_metadata;要落盘则 --stored(默认只读预览)/ --stored --apply
  • 跨 17 留给运维的两条 data migration(files-to-references / value-shapes)按名带进报告 —— 「没人被告知的门就没人守」。
  • --apply / --yes / --force / --type / --database-url 是 stored-only,误用会被拒收而非忽略;MigrationFloorError 的含义也写清了。

语义残余层 —— AI 的活

四个指令源,按实测可达性列表(这是本 PR 对报单描述的一处更正):@objectstack/specfiles 白名单只含 src/**/*.zod.ts,所以 ADR-0087 的两张登记表源码(src/migrations/registry.ts / src/conversions/registry.ts)与生成的 upgrade-guide 都不在 npm 包内,把客户 agent 指过去会扑空。消费侧真正可达的是:

客户项目里的位置
链自身的结果(首选,永不过期) os migrate meta --from 16 --json.specChanges
D4 投影(离线) node_modules/@objectstack/spec/spec-changes.json
retiredKey() 处方(可 grep) node_modules/@objectstack/spec/json-schema/**src/**/*.zod.ts
changeset 的 FROM→TO 表 node_modules/@objectstack/spec/CHANGELOG.md
报错本身 parse / tsc 输出

残余分三类:R1 意图选择(退役键无无损目标)、R2 自定义代码调用已退役 APIR3 仓内散文。R1 以被退役的 transform 为完整示例,给出三个真实去向(导入映射 transform / ETL 步骤 / 删除且承认变换从未发生 —— 第三条常常才是事实)。

「自己定 vs 问业主」判据(剧本的核心段):三条全中才自决(处方只有唯一目标 + 证据在客户仓里 grep 得到 + 判错会被门拦下);任一命中即问人(两个以上真实目标且属业务表态 / 改动不触发测试失败却对外可见如鉴权与行可见性 / 能力要重新声明到新位置故选错是静默丢能力 / 客户仓里根本没有可判依据)。并规定问的形式(逐条给站点 + 逐字处方 + 选项代价 + 推荐 + 验证方式),以及等待期间以 AWAITING DECISION 挂账、不阻塞机械层。

验收层

typed(tsc,墓碑把退役键定为 never)+ parse-gated(墓碑当场拒收并给处方)+ os validate 绿 + 人类可读 diff 报告模板(五节:机械改动 / 已决残余 / 未决 / 每部署待跑 / 未改动及原因)。

实测钉出的一处关键更正:validate 绿 不等于 链已跑完。field-required-notnull-explicit 是 migration-chain-only(默认翻转而非改名,loader 故意不自动应用,也没有墓碑拦截),只带这种形状的项目 validate 全绿而链仍有活。故剧本把**「从目标 major 重放必须报 Nothing to migrate」**升为与 validate 并列的判据 —— 只看 validate 的报告对这一整类是盲的。剧本另要求先跑一次升级前 validate 作为对照,以区分「升级残余」与「项目既有 lint 欠账」。

基线读数(origin/main@80f7dc6 = 17.0.0-rc.5)

按激活批复钉死,正文单列一节,并给出刷新命令与「读数与安装不符时以安装为准、增量记进报告而非改写钉住的数字」的规则,使 rc.5 之后的登记增量成为显式 delta。

读数 rc.5 实测值
@objectstack/spec 版本 17.0.0-rc.5(protocol 17.0.0)
链支持下限 protocol 10
major 17 的 D2 conversions 45
major 17 的 D3 semantic 条目 29
已发布 *.zod.ts 里的 retiredKey() 墓碑 113(32 个文件)
已发布 json-schema/ 里去重后的 [REMOVED] 处方 96

RETIRED_KEYS_BY_MAJOR[17](3 条,同一次退役:属性声明一次、被两个 .extend() 继承,故登记三次):

  • data/ExternalFieldMapping:transform
  • integration/ConnectorFieldMapping:transform
  • shared/FieldMapping:transform

RETIRED_DEFS_BY_MAJOR[17](1 条):shared/FieldMappingTransform

剧本同时说明为什么这两张表短而上面的计数不短:两表只记录「在建表后的精确键门下正式登记过的退役」,并非全部退役的回填;问「这次退役有没有被正式登记」查表,问「我要改什么」查 conversions 与墓碑 —— 升级问的是后者。

Walkthrough(真跑,非叙述)

临时目录里构造 v16 形状的客户项目(形状逐条抄自真实 CONVERSIONS_BY_MAJOR[17]fixture.before;process 家族不随附 fixture,故不入库),用 packages/cli/bin/run-dev.js 实跑。

1 · 机械层 —— os migrate meta --from 16 --step(exit 0)

  ℹ Chain:  protocol 16 → 17 (runtime 17.0.0)

  Applied 7 mechanical change(s):
    • actions[0].target: execute → target (action-execute-to-target)
    • objects[1].fields.due_date.requiredWhen: conditionalRequired → requiredWhen (field-conditionalRequired-to-requiredWhen)
    • agents[0].skills: tools → skills (agent-tools-to-skills)
    • objects[0].fields.name.storage.notNull: required: true (implied NOT NULL) → storage.notNull: true (field-required-notnull-explicit)
    • objects[0].fields.status.storage.notNull: required: true (implied NOT NULL) → storage.notNull: true (field-required-notnull-explicit)
    • connectors[0].fieldMappings[0].transform: transform → (removed) (field-mapping-transform-removed)
    • connectors[0].fieldMappings[1].transform: transform → (removed) (field-mapping-transform-removed)

  29 manual change(s) require your judgment:

--json 读数:applied 7 · todos 29 · schemaValid true,且 specChanges.converted = 45 / migrated = 29 —— 与上面基线表逐字吻合(基线数字由 CLI 实跑复核,非抄源码)。

2 · 验收层 —— 升级前 vs 升级后

升级前 os validate(exit 1,5 处 protocol 报错,每条自带处方,节选):

  ✗ Validation failed
  actions:
    ✗ actions.0.execute
      invalid_type: `execute` was removed in @objectstack/spec 17 (#3855) — use `target`.
      Rename the key; the value (a handler / flow / URL ref) is unchanged.
      Run `os migrate meta --from 16` to rewrite it automatically.
      expected: never
  connectors:
    ✗ connectors.0.fieldMappings.0.transform
      invalid_type: `FieldMapping.transform` … was removed in @objectstack/spec 17.0.0
      (#5552, ADR-0049) … Delete the key. The transform pipeline that IS enforced is the
      import mapping's: `mapping.fieldMapping[].transform` … Run `os migrate meta --from 16`
      to rewrite it automatically.
      expected: never

  5 validation error(s) total

把 7 处机械改动 port 进源、两条 R1 残余各作决定后,升级后 os validate(exit 0):

  → Validating against ObjectStack Protocol...
  → Running author-time rules (36)...
  ✓ Validation passed (78ms)

os migrate meta --from 17(exit 0):

  ✓ Nothing to migrate — the metadata is already canonical for this range.

诚实记录两处:(a) 首轮 schemaValid: false,查因是我的 fixture 缺 agent 的 label/role/instructions —— 转换 fixture 是逐条转换级的,拼起来不自动构成合法 stack;补齐后为 true,该教训已写进剧本。(b) 本 fixture 未触发两条 data-migration 提示,因为它没有 media / reference / structured-JSON 字段 —— 这正是 pendingDataMigrations 「只在客户元数据确实声明了该字段类时才提示」的设计,不是缺陷,故未在 PR 里假称跑出了该段。

反向验证(方向先声明,再运行)

A. 墓碑真的会开火 —— 预期方向:migrate 之后把退役键放回去,parse 必须失败且报错即处方。实测即上面的 validate exit 1 五连,处方逐字与剧本引用的一致(非 "unrecognized key",非静默)。

B. validate 绿 ≠ 链已跑完 —— 预期方向:构造只含 required: true不含任何墓碑键的探针,validate绿而链仍有活。实测:

PROBE validate exit=0        →  ✓ Validation passed (57ms)
PROBE migrate  exit=0        →  chain applied: 1
  [{"conversionId":"field-required-notnull-explicit",
    "from":"required: true (implied NOT NULL)","to":"storage.notNull: true",
    "path":"objects[0].fields.name.storage.notNull"}]

两个方向都按预声明的方向成立,B 直接改写了剧本的验收判据(见上)。

C. 门自己开了两次火,都是真红:

  1. check:skill-examples 首轮红,点名我 os:check 块里的 defineAgent 缺三个必填键 —— 与 fixture 踩的是同一个坑。
  2. CI 的 check:role-word(第二个 commit 修)。漏检原因值得记下来:该门与 check:nul-bytes 一样只扫 tracked 文件,而我首轮是在 git add 之前跑的,新文件根本不在扫描面内 —— 门形式绿、实则没看过我的文件。第二轮起全部门均在 staged 状态下重跑。

两次都说明这些门对新 skill 是真的在跑,不是形式绿。

门读数(第二个 commit,全部 staged 状态下重跑)

结果
check:nul-bytes ✅ 5921 tracked 文件(新文件已入扫描面);另做超出门的自扫 grep -naP 无命中
check:doc-authoring ✅ 365 files clean
check:role-word ✅ 44 baselined(新增一条 skills/objectstack-upgrade/SKILL.md: 1,见下)
check:skill-frame-sync ✅ 4 copies isomorphic,40 markdown 扫描无未申报副本(新 skill 未触发 anti-dormancy)
check:skill-docs ✅ in sync(skills/README.md + content/docs/ai/skills-reference.mdx 已重生成)
check:skill-refs ✅ 9 generated files in sync(process skill 无需 SKILL_MAP 条目)
check:skill-examples ✅ 208 prose examples type-check
check:upgrade-guide / check:spec-changes / check:generated ✅ / ✅ / ✅
check:published-files / check:adr-anchors / check:docs-audit-scope ✅ / ✅ / ✅
CI ESLint success(298f802)

role-word 基线的一条新增,及为什么它是对的

剧本的 os:check 块用了 1 次 role:role: 'Front-line support triage'。该处是 AgentSchema 自己的必填键 —— defineAgent 缺它编不过(check:skill-examples 已经替我证明过一次),不是 ADR-0090 D3 所指的 role 概念,属门自身报错文案点名的「genuine boundary」。同族先例:skills/objectstack-ai/SKILL.mdrole: 'Help Desk Assistant…' 早已在基线里(5 次)。按门的处方 --update 落基线,delta 精确为一条新条目,其余文件计数均未变动 —— 未顺带向下棘轮任何别的文件,以免掩盖其它漂移。

范围与一处必要的例外

packages/spec 的唯一改动是 scripts/build-skill-docs.tsDISPLAY 目录登记表加一行。该表就是派发词允许的「列出 skills 的索引/登记文件」:脚本显式做 catalog ⇄ DISPLAY 双向对账,新 skill 目录没有 DISPLAY 条目时 check:skill-docs 直接 exit 1(✗ SKILL.md without DISPLAY entry),不改则门必红。未触碰 spec schema、退役登记表、content/docs/releases/、digest/console 脚本,也未改任何既有 skill。

未做 .claude/ 内部对偶(派发词的默认):退役侧对偶 spec-property-retirement 作用于本仓 spec 故内部;本单作用于客户项目,受众是第三方 agent,只应发布。

changeset:走 route 2 —— skills/ 不经 npm 包发布,本 PR 无 user-visible 的包变更,故打 skip-changeset 标签,不写空 changeset。标签实测被 Auto Label 覆盖过一次(#5533 那个 race),已按并集重写并读回确认:documentation / size/l / tooling / skip-changeset;Check Changeset 现为 skipped

面向客户元数据项目的 v16 → v17 升级 agent 剧本,骑在 ADR-0087 D2 conversions
之上,不替代确定性转换。三层边界按维护者拍板落地:

- **机械层(只调用,不重实现)**:`os migrate meta --from N` 重放转换链;
  存量行由 `applyConversionsToStoredItem` 在 rehydration 时重放。剧本写清
  「该命令不改写源文件、只写 --out」这条最容易踩的事实,以及 `--stored`
  与跨 17 时留给运维的两条 data migration。
- **语义残余层(AI 的活)**:三类残余(意图选择 / 调用已退役 API 的自定义
  代码 / 仓内散文),四个指令源的**实测可达性**表 —— 两张 ADR-0087 登记表
  与生成的 upgrade-guide **不在 npm 包内**(`files` 只含 `src/**/*.zod.ts`),
  消费侧的投影是 `spec-changes.json` 与链自身的 `--json`;
  `retiredKey()` 处方经 `json-schema/**` 与 `*.zod.ts` 双通道可 grep。
  给出「自己定 vs 问业主」的判据(各三/四条,可检验)。
- **验收层**:typed + parse-gated + `validate` 绿 + 人类可读 diff 报告模板。
  实测钉出一条关键更正:`validate` 绿**不等于**链已跑完 ——
  `field-required-notnull-explicit` 属 migration-chain-only,无墓碑拦截,
  故补「从目标 major 重放必须 0 变更」为并列判据。

基线按激活批复钉在 `origin/main@80f7dc6`(17.0.0-rc.5),正文记录读数并给出
刷新规则,rc.5 之后的登记增量作为显式 delta,不推翻已写部分。

目录名 `objectstack-upgrade` 随家族 `objectstack-<domain>` 命名;`domain: process`
(同 pm-dispatch,只带 SKILL.md)。仅发布侧,不做 `.claude/` 内部对偶 ——
退役侧对偶 `spec-property-retirement` 作用于本仓 spec,本单作用于客户项目。

packages/spec 唯一改动是 `build-skill-docs.ts` 的 `DISPLAY` 目录登记表:
该表是「列出 skills 的索引」,缺项时 `check:skill-docs` 直接 exit 1。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 10:32am

Request Review

@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 7, 2026 — with Claude
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling and removed skip-changeset PR has no user-facing published change; bypasses the changeset gate labels Aug 7, 2026
@hotlong hotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed documentation Improvements or additions to documentation tooling labels Aug 7, 2026 — with Claude
CI 的 `check:role-word` 红:剧本的 `os:check` 块用了 1 次 `role`。该处是
`AgentSchema` 自己的**必填键** —— `defineAgent` 缺它编不过(同一处 fixture
也踩过),不是 ADR-0090 D3 所指的 role 概念,属门自身报错文案点名的
「genuine boundary」。同族先例:`skills/objectstack-ai/SKILL.md` 的
`role: 'Help Desk Assistant…'` 已在基线里(5 次)。

按门的处方 `--update` 落基线,delta 精确为一条新条目
(`skills/objectstack-upgrade/SKILL.md: 1`),其余文件计数均未变动 ——
未顺带向下棘轮任何别的文件,以免掩盖其它漂移。

漏检原因记录:首轮 `check:role-word` 在 `git add` 之前跑,而该门与
`check:nul-bytes` 一样只扫 tracked 文件,故新文件不在扫描面内、门形式绿。
本轮全部门均在 staged 状态下重跑。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Aug 7, 2026
@hotlong
hotlong marked this pull request as ready for review August 7, 2026 10:54
@hotlong
hotlong added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit a560537 Aug 7, 2026
26 checks passed
@hotlong
hotlong deleted the claude/issue-6111-v17-upgrade-skill branch August 7, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v17 GA 交付物:AI 一键升级客户元数据项目(升级 skill,骑在 D2 conversions 之上)

2 participants