diff --git a/skills/app-dev/SKILL.md b/skills/app-dev/SKILL.md index b7ee124..c09aed1 100644 --- a/skills/app-dev/SKILL.md +++ b/skills/app-dev/SKILL.md @@ -1,6 +1,6 @@ --- name: app-dev -description: 在独立仓库按 TickShield(刻盾云)平台契约开发、审查、发布生态应用(micro/service):listingKey 四位一体、app.manifest.json、tickshield-node-runtime 基础镜像、@aigrego/guard 四道闸与 tenant_id 租户隔离、micro 前端 portal-sdk 宿主桥与平台同款 UI 皮肤(--sk-* 令牌 + ts-skin 换肤跟随)、release-cli 发布与商店上架部署。凡用户要求「开发一个平台应用 / 新建 app / 写个 micro 应用 / 做个像 secret-manager / demo-app 那样的应用 / 在门户里加个自己的应用 / 接入 TickShield / 上架应用到商店 / 审查这个 app 能不能部署到平台 / 帮我看看接入还缺什么 / 审一下这个应用的 UI / 界面跟平台不一致 / 换肤不跟随 / 深色模式瞎了 / 配色不统一 / 把界面改成平台那样」,或带着 注册不上去 / 401 missing app credentials / 心跳 404 / host must equal listingKey / 四道闸 403 / 跨租户数据串了 / image registry not registered / invalid manifest skipped / 应用中心导入失败 / 应用装上了但 /svc 不通 / PROD 白屏 CSP 报错 / 切到暗夜皮肤应用还是白底 / 青瓷皮肤下按钮还是蓝色 / 应用跟平台长得不一样 等症状,或提到 listingKey / app.manifest.json / 四道闸 / TDT / tenant_id / portal-sdk / release-cli / tickshield-node-runtime / --sk- 令牌 / ts-skin / 皮肤 时,务必使用本 skill——即使用户没说「应用接入」四个字。Use whenever the user develops, scaffolds, audits, reviews (including UI/skin consistency), onboards, publishes or deploys a TickShield platform app (micro or service) from an independent repository. 边界:非平台场景的通用 Dockerfile 选型评审用 runtime-image;通用 Gitea Actions 工作流规范用 gitea-actions;CI 工作流的发布 job 改造/新建发布工作流用 app-publish;git 提交推送与日报用 push-and-report;平台基座本身(platform/gateway/packages/* 控制面)的开发与平台设计系统(DESIGN.md / skins/index.ts)的修改不归本 skill。 +description: 在独立仓库按 TickShield(刻盾云)平台契约开发、审查、发布生态应用(micro/service):listingKey 四位一体、app.manifest.json、tickshield-node-runtime 基础镜像、@aigrego/guard 四道闸与 tenant_id 租户隔离、micro 前端 portal-sdk 宿主桥与平台同款 UI 皮肤(--sk-* 令牌 + ts-skin 换肤跟随)、release-cli 发布与商店上架部署。凡用户要求「开发一个平台应用 / 新建 app / 写个 micro 应用 / 做个像 secret-manager / demo-app 那样的应用 / 在门户里加个自己的应用 / 接入 TickShield / 上架应用到商店 / 审查这个 app 能不能部署到平台 / 帮我看看接入还缺什么 / 审一下这个应用的 UI / 界面跟平台不一致 / 换肤不跟随 / 深色模式瞎了 / 配色不统一 / 把界面改成平台那样」,或带着 注册不上去 / 401 missing app credentials / 心跳 404 / host must equal listingKey / 四道闸 403 / 跨租户数据串了 / image registry not registered / invalid manifest skipped / 应用中心导入失败 / 应用装上了但 /svc 不通 / PROD 白屏 CSP 报错 / 切到暗夜皮肤应用还是白底 / 青瓷皮肤下按钮还是蓝色 / 应用跟平台长得不一样 等症状,或提到 应用是否合规 / 能不能发布 / 体检 / 自检 / dist 超 64MB / 大产物 / 外置静态资源 / connect-src / CORS / wasm 核心 / listingKey / app.manifest.json / 四道闸 / TDT / tenant_id / portal-sdk / release-cli / tickshield-node-runtime / --sk- 令牌 / ts-skin / 皮肤 时,务必使用本 skill——即使用户没说「应用接入」四个字。Use whenever the user develops, scaffolds, audits, reviews (including UI/skin consistency), onboards, publishes or deploys a TickShield platform app (micro or service) from an independent repository. 边界:非平台场景的通用 Dockerfile 选型评审用 runtime-image;通用 Gitea Actions 工作流规范用 gitea-actions;CI 工作流的发布 job 改造/新建发布工作流用 app-publish;git 提交推送与日报用 push-and-report;平台基座本身(platform/gateway/packages/* 控制面)的开发与平台设计系统(DESIGN.md / skins/index.ts)的修改不归本 skill。 --- # app-dev — TickShield 生态应用开发规范(独立仓库 → 可直接部署上架) @@ -12,20 +12,20 @@ TickShield 生态应用的交付物只有两样:**镜像 + `app.manifest.json` micro 应用还有第三条硬要求:**UI 与平台基座同一套视觉,且跟随皮肤切换**(light / dark / jade)。基准见 `references/ui-style.md`,令牌由 `scripts/sync-skin-tokens.sh` 从平台 `skins/index.ts` 生成,跟随机制是 `localStorage['ts-skin']` + `storage` 事件。这不是"美化建议",是接入审核的 H 节(写死色值 / 没有皮肤桥 = die)。 -契约锚点:本 skill 基线 = TickShield 平台 v4.0 + APPSTORE-RUNTIME + FR-101 registries 白名单 + FR-102/TKT-557 导航与图标规范(2026-10)+ 平台设计系统 DESIGN.md / skins/index.ts(2026-10-22 快照)。与平台冲突时,以平台仓 `docs/reference/应用开发与上架指引.md`、`apps/portal/internal/modules/registry/manifest.go` 的校验代码、`apps/web/src/skins/index.ts` 的令牌定义为准,并回本仓库更新本 skill。`docs/reference/App出仓与集成指引.md`、`App开发者指南.html` 是**已废弃的历史文档**(@xgent 旧体系),禁止照搬。 +契约锚点:本 skill 基线 = TickShield 平台 v4.0 + APPSTORE-RUNTIME + FR-101 registries 白名单 + FR-102/TKT-557 导航与图标规范(2026-10)+ 大产物外置 / per-app CSP(2026-10,embedCsp.connectSrc)+ 平台设计系统 DESIGN.md / skins/index.ts(2026-10-22 快照)。与平台冲突时,以平台仓 `docs/reference/应用开发与上架指引.md`、`apps/platform/internal/modules/registry/manifest.go`(校验)、`apps/platform/internal/modules/registry/csp.go` 与 `apps/platform/cmd/platform/main.go`(CSP 下发)、`apps/web/src/skins/index.ts`(令牌定义)为准,并回本仓库更新本 skill。`docs/reference/App出仓与集成指引.md`、`App开发者指南.html` 是**已废弃的历史文档**(@xgent 旧体系),禁止照搬。 ## 总流程 ``` 第 0 步 备料分流(判模式 + 采集四要素) -新建:第 1 步 生成骨架(含 UI 皮肤层)→ 第 2 步 静态自检(scripts/check-app.sh) -审查:第 1 步 逐项审计(checklist.md A–H)→ 第 2 步 确认后修正 + 静态自检 +新建:第 1 步 生成骨架(含 UI 皮肤层 + 仓库 AGENTS.md)→ 第 2 步 静态自检(scripts/check-app.sh) +审查:第 1 步 逐项审计(checklist.md A–I)→ 第 2 步 确认后修正 + 静态自检 汇合:第 3 步 联调验证(需 dev 栈,含换肤跟随)→ 第 4 步 发布指引 → 第 5 步 汇报 ``` 除非用户明确只要聊聊思路,完整走全部 5 步。 -**调用形态分流(硬规则)**:裸调用(无任务输入)= **只读审计态**——对 app 做**全量审核(含 UI)**,只出审计报告与修复/新建方案,**永不主动改文件、永不执行任何写操作**;报告与方案交付即停,等用户确认后才进入对应模式的执行段。空仓库裸调用同理:只输出「接入差距评估 + 新建方案」供确认,不直接生成骨架。带任务输入(如「帮我开发 X 应用」「按报告修」「把界面改成平台那样」)= 任务即授权对应动作,按流程走;但发布等写平台操作仍需显式确认(见「人机权责」)。 +**调用形态分流(硬规则)**:裸调用(无任务输入)= **只读审计态**——**第一步先执行 `bash <本skill目录>/scripts/check-app.sh .`**(不需要用户写任何任务提示词),把输出的 `RESULT: PASS|FAIL` 与 die/warn 明细作为报告第一段;再对照 `references/checklist.md`(A–I)做全量审核(含 UI),只出审计报告与修复/新建方案,**永不主动改文件、永不执行任何写操作**;报告与方案交付即停,等用户确认后才进入对应模式的执行段。空仓库裸调用同理:只输出「接入差距评估 + 新建方案」供确认,不直接生成骨架。带任务输入(如「帮我开发 X 应用」「按报告修」「把界面改成平台那样」)= 任务即授权对应动作,按流程走;但发布等写平台操作仍需显式确认(见「人机权责」)。 **UI 审核不单独开关**:裸调用就是审核,审的就是「接入契约 + UI 一致性」两件事,不给 `--ui` 之类的子命令——用户说「/app-dev」而没带任何要求时,默认动作是审,不是改。 @@ -60,6 +60,7 @@ micro 应用还有第三条硬要求:**UI 与平台基座同一套视觉,且 - `web/app.css` 是外置样式(PROD CSP 下不靠内联 style),颜色全部 `var(--sk-*)`;构建脚本要把它拷进 `dist/`。 - `.gitea/workflows/release.yaml`(照 `gitea-app-release.yaml`,提示配 secrets) - `docs/PRD_app-.md` 草稿(照 `PRD_app-template.md`,文档状态标「草稿」) +- `AGENTS.md`(照 `references/templates/AGENTS.md`:仓库根硬规则,让后续任何 Agent 在不写任务提示词时也先跑 `check-app.sh`;已有 AGENTS.md 时只在末尾追加带标记的「TickShield 接入自检」段,不覆盖原文) - `.aigrego-registry.env.example` + 在 `.gitignore` 追加 `.aigrego-registry.env` 生成后逐文件说明「这个文件哪几处必须改、哪几处永远别动」;UI 层额外说明一句:**颜色改 class 不改色值,皮肤跟随不要自己实现**(照 `src/skin/apply.ts` 用)。 @@ -68,13 +69,13 @@ micro 应用还有第三条硬要求:**UI 与平台基座同一套视觉,且 ## 第 1~2 步(审查):逐项审计 → 修正 -1. 读 `references/checklist.md`,对照逐项审计,输出三档结论表:**通过 / 不符(die 级)/ 风险(warn 级)**,每项给证据(文件:行号)。 +1. 读 `references/checklist.md`,对照逐项审计,输出三档结论表:**通过 / 不符(die 级)/ 风险(warn 级)**,每项给证据(文件:行号)。报告固定含一项「自检入口」:仓库根 `AGENTS.md` 是否写死「开工前先跑 `check-app.sh`」(缺失 = warn,用户确认后照 `references/templates/AGENTS.md` 补,这是「不写任务提示词也能体检」的落点)。 2. **UI 审核(H 节)单独成段**:先读 `references/ui-style.md` 建立基准,再按 H1–H12 审。要点是"换肤能不能跟随",不是"好不好看"——判据永远是「写死色值了吗 / 有皮肤桥吗 / 首帧前落令牌了吗 / 三套皮肤实机切过吗」。H11 需要 dev 栈实机切皮肤,跑不了就写「未验证」,不许拿静态检查绿冒充。 3. 修正清单列给用户,**经确认后**逐项修;修完跑静态自检。 **裸调用到此为止**:审计报告 + 修复方案就是交付物,不执行任何修正;用户回复确认(如「按方案修」)后才进入第 2 步修正。裸调用的报告必须同时含「接入契约结论」与「UI 一致性结论」两段,缺一段等于没审。 -→ 验收:审计表覆盖 checklist 全部 die 级项(A–H 全覆盖);修正经用户逐批确认。 +→ 验收:审计表覆盖 checklist 全部 die 级项(A–I 全覆盖);修正经用户逐批确认。 ## 第 2 步:静态自检 @@ -82,7 +83,7 @@ micro 应用还有第三条硬要求:**UI 与平台基座同一套视觉,且 bash <本skill目录>/scripts/check-app.sh <仓库根> ``` -die 级不过 → 修了再往下走;warn 级列进汇报。check-app.sh 只查静态契约(manifest 字段/Dockerfile 三要素/入口接线/保留路径/密钥卫生/**UI 令牌与皮肤桥**),不替代第 3 步真环境联调。UI 部分它只能证明"没写死色值、桥接代码在",**不能证明换肤真的跟随**——那一步必须实机切。 +die 级不过 → 修了再往下走;warn 级列进汇报。**末尾有一行机器可读结论 `RESULT: PASS|FAIL`**(CI/Agent 直接引用它),并附 `die=N warn=N`。check-app.sh 覆盖 checklist A–I 的 `[S]` 项:manifest 字段(含 `embedCsp` 结构与去重)/Dockerfile 三要素/入口接线/保留路径/密钥卫生/**UI 令牌与皮肤桥**/**大产物体积与外置加载方式**;不替代第 3 步真环境联调。UI 部分它只能证明"没写死色值、桥接代码在",**不能证明换肤真的跟随**——那一步必须实机切。 → 验收:脚本 exit 0(或仅剩 warn 且已列清单)。 @@ -104,7 +105,6 @@ die 级不过 → 修了再往下走;warn 级列进汇报。check-app.sh 只 ## 第 4 步:发布指引 发布是**写操作**:默认只给命令草稿,用户显式说「发布」才执行。详见 `references/publish-deploy.md`: - ```bash export AIGREGO_PORTAL_URL=<平台地址> APP_KEY= APP_SECRET= aigrego-release whoami # 先验凭证(放在构建之前) @@ -113,7 +113,8 @@ aigrego-release publish --manifest app.manifest.json [--image ] [--store] [ ``` - **manifest 进平台是双通道、同一份文件**:CLI `publish --manifest`(上)或平台侧 console「应用中心」按向导导入——同一条 submit 管道(同一套校验、治理字段变更同样 202 进审核队列)。不管走哪条,仓库里的 `app.manifest.json` 都必须最新且合法;走手动通道时把这份文件交付给平台管理员。 -- 治理字段(`scopes`/`aclManifest`/`exchangeTargets`/`dependencies`)变更 → 202 进审核队列(exit 0 但**不生效**),批准前不影响已安装租户;首个版本直接生效。 +- **大产物分流(先量再发)**:dist tgz ≤64 MiB → 照旧 `publish --dist`;超限(已压缩 wasm/`.data` 几乎压不动)→ 核心放对象存储 `apps///`、dist 只留壳、manifest 声明 `embedCsp.connectSrc`。只放开 `connect-src`(fetch/XHR/WebSocket/wasm 流式加载);Worker/动态 import/外链 script/样式/字体**不行**。桶必须配 CORS(含 `Allow-Headers: Range`)。细节见 `references/contract.md §7.1`。 +- 治理字段(`scopes`/`aclManifest`/`exchangeTargets`/`dependencies`/`mounts`/`embedCsp`)变更 → 202 进审核队列(exit 0 但**不生效**),批准前不影响已安装租户;首个版本直接生效。 - 商店上架/授权/安装/启停/卸载是平台侧 console 动作;镜像 host 必须在 registries 白名单(空表=全拒)。 - 输出「待平台侧操作」清单:auto_publish 开关、registries 登记、治理审核批准、授权安装——这些 Agent 做不到,不绕道不假装完成。 @@ -217,7 +218,7 @@ die 消息统一三段式:是什么 + 为什么(后果) + 怎么办。 |---|---| | `references/contract.md` | 需要契约细节时:manifest 字段全集/注册心跳报文/四道闸 API/tdb 行为/令牌与交换/部署形态 | | `references/publish-deploy.md` | 第 4 步发布时:release-cli 全参数/商店链路/quickstart catalog 物料/验收矩阵 | -| `references/checklist.md` | 审查模式必读:逐项审计清单 A–H(die/warn 分级 + 验证方法) | +| `references/checklist.md` | 审查模式必读:逐项审计清单 A–I(die/warn 分级 + 验证方法) | | `references/ui-style.md` | 写 micro 前端或审 UI 时必读:令牌表/视觉刻度/皮肤桥机制/组件基线/换肤验证步骤 | | `references/troubleshooting.md` | 带症状来时:排障对照全表 | | `references/templates/` | 新建模式必读:全部可复制模板(`web/src/skin/` 与 `web/app.css` 是 UI 层) | diff --git a/skills/app-dev/references/checklist.md b/skills/app-dev/references/checklist.md index 848b68d..5fd746d 100644 --- a/skills/app-dev/references/checklist.md +++ b/skills/app-dev/references/checklist.md @@ -16,6 +16,7 @@ | A8 | `navItems`/`footerNavItems` 每项含 `id/label/path`;`icon` 为 emoji/字符或 lucide 名(写错名静默兜底成字符,须眼见为实);`aclManifest.pages` 含 `key/label`,`audience` ∈ admin/viewer/all | die `[S]` | 对照契约表 §2.1 | | A9 | `color` 为 `#RRGGBB`;`icon`≤64 / `cat`≤64 / `tagline`≤120 / `desc`≤2000 | warn `[S]` | 长度/格式 | | A10 | 治理字段变更知悉审核语义(202 进队列,批准前不生效) | log | 口头/文档确认 | +| A11 | 仓库根 `AGENTS.md` 含「开工前先跑 check-app.sh」硬规则(模板 `references/templates/AGENTS.md`)——没有它,Agent 只能靠用户每次明说才会体检 | warn `[S]` | grep AGENTS.md | ## B. 镜像与入口 @@ -104,3 +105,19 @@ | H10 | 无 `!important`(`prefers-reduced-motion` 兜底除外) | warn `[S]` | grep | | H11 | 换肤实机验证:三套皮肤各切一次,应用当场跟随(强调色随皮肤变,青瓷下不能还是蓝色),刷新后保持 | die(上线前) | 截图留档 | | H12 | 无障碍底线:正文/状态文字对比度 ≥ 4.5:1;`prefers-reduced-motion` 有降级 | warn | 抽查三套皮肤 | + +## I. 大产物与静态资源外置(dist tgz > 64 MiB 或核心为已压缩大文件时) + +判定顺序:先量 dist → 超限才要求外置 → 外置必须有 `embedCsp` → 再查加载方式是否被 CSP 覆盖。全部 `[S]` 项由 `scripts/check-app.sh` 自动查。 + +| # | 项 | 档 | 验证方法 | +|---|---|---|---| +| I1 | dist 打包成 tgz 后 ≤64 MiB(平台请求体硬上限);未压缩体积超限但实测 tgz 达标 → 仅提示 | die(tgz 超限)/ warn(压缩率依赖) | 脚本实算 `tar czf -` | +| I2 | dist 内无单文件 >8 MiB 的已压缩资源(`.wasm`/`.data`/`.br`/`.gz`/`.zip`/`.7z`/`.mp4`);有则必须外置 | warn,tgz 超限时 die | 文件大小扫描 | +| I3 | 需要外置时 manifest 必须声明 `embedCsp.connectSrc`(非空) | die | 脚本联动 I1/I2 | +| I4 | `embedCsp` 结构合法:仅 `type=micro`;≤8 条;每项 `scheme://host[:port]`(可 `*.`);无路径/查询/裸 `*`/分号/空白/重复 | die | 正则 + 去重 | +| I5 | 声明与用点对得上:源码有外部 origin 的 `fetch`/`instantiateStreaming`,清单未声明 → warn;声明了但源码找不到使用点 → warn(死配置) | warn | grep 源码与构建产物 | +| I6 | 不使用 connect-src 不覆盖的跨域加载:`new Worker('https://…')`、`import('https://…')`、`importScripts('https://…')`、外链 ` + + +HTML + cat >"$dir/web/src/main.tsx" <<'TS' +import { initSkin } from './skin/apply'; +initSkin(); +TS + printf "export const SKINS = [{ key: 'light' }, { key: 'dark' }, { key: 'jade' }];\n// SKIN_SNAPSHOT_DATE = '2026-10-22'\n" >"$dir/web/src/skin/tokens.ts" + printf ":root { font-family: system-ui, -apple-system, 'PingFang SC'; font-size: 13px; }\n" >"$dir/web/app.css" + echo 'app' >"$dir/dist/index.html" + echo "$dir" +} + +# set_embed :写入 embedCsp.connectSrc(node 优先,退化 python3)。 +set_embed() { + local f="$1" origins="$2" + if command -v node >/dev/null 2>&1; then + node -e 'const fs=require("fs");const [p,src]=process.argv.slice(1);const o=JSON.parse(fs.readFileSync(p));o.embedCsp={connectSrc:JSON.parse(src)};fs.writeFileSync(p,JSON.stringify(o,null,2));' "$f" "$origins" + else + python3 - "$f" "$origins" <<'PY' +import json, sys +p, origins = sys.argv[1], json.loads(sys.argv[2]) +o = json.load(open(p)) +o['embedCsp'] = {'connectSrc': origins} +json.dump(o, open(p, 'w'), ensure_ascii=False, indent=2) +PY + fi +} + +# expect +expect() { + local name="$1" dir="$2" pat="$3" want="$4" out got + out=$(bash "$CHECK" "$dir" 2>&1) + N=$((N + 1)) + if printf '%s\n' "$out" | grep -qE "$pat"; then got=1; else got=0; fi + if [ "$got" = "$want" ]; then + echo "ok $name" + else + BAD=$((BAD + 1)) + echo "FAIL $name(期望命中=$want 模式=$pat)" + printf '%s\n' "$out" | sed 's/^/ /' + fi +} + +# 1) dist tgz 超限且未声明 embedCsp → 两条 die + RESULT FAIL。 +D=$(fixture over-limit) +dd if=/dev/urandom of="$D/dist/core.bin" bs=1048576 count=70 2>/dev/null +expect "tgz 超限诊断" "$D" 'dist 打 tgz 后 [0-9]+MiB > 平台 64MiB' 1 +expect "超限未声明外置" "$D" 'dist 超限但 manifest 未声明 embedCsp' 1 +expect "超限 RESULT FAIL" "$D" '^RESULT: FAIL$' 1 + +# 2) embedCsp 带路径 → 结构非法。 +D=$(fixture embed-bad) +set_embed "$D/app.manifest.json" '["https://cdn.example.com/assets"]' +expect "embedCsp 路径非法" "$D" 'embedCsp 非法' 1 + +# 3) 外域 Worker → connect-src 不覆盖。 +D=$(fixture worker-ext) +echo "new Worker('https://cdn.example.com/w.js');" >>"$D/web/src/main.tsx" +expect "外域 Worker 拦截" "$D" 'connect-src 不覆盖' 1 + +# 4) 合规声明 + 使用点 + 小 dist → 不误报。 +D=$(fixture embed-ok) +set_embed "$D/app.manifest.json" '["https://cdn.example.com"]' +echo "fetch('https://cdn.example.com/apps/selftest-app/1.0.0/core.data');" >>"$D/web/src/main.tsx" +expect "合规声明不报非法" "$D" 'embedCsp 非法' 0 +expect "合规声明不报超限" "$D" 'dist 打 tgz 后' 0 +expect "合规声明不报死配置" "$D" '找不到使用点' 0 +expect "输出始终带结论行" "$D" '^RESULT: (PASS|FAIL)$' 1 + +# 5) 免提示自检入口:无 AGENTS.md → warn;有且含 check-app.sh → 不再 warn。 +expect "缺 AGENTS.md 提示" "$D" '缺 AGENTS.md 自检硬规则' 1 +printf '# AGENTS.md\n开工前先跑 `bash .agents/skills/app-dev/scripts/check-app.sh .`。\n' >"$D/AGENTS.md" +expect "有 AGENTS.md 不提示" "$D" '缺 AGENTS.md 自检硬规则' 0 + +echo "---" +echo "selftest: $N 项,失败 $BAD" +[ "$BAD" -gt 0 ] && exit 1 +exit 0