@gitruck/cli 1.3.4 → 1.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/AGENT.md +25 -0
  2. package/CHANGELOG.md +22 -0
  3. package/README.en.md +50 -50
  4. package/README.md +50 -50
  5. package/assets/composite-kit/kit.css +57 -0
  6. package/assets/composite-kit/kit.js +246 -0
  7. package/assets/composite-kit/stage.html +265 -0
  8. package/assets/readme/wordmark.svg +6 -6
  9. package/contracts/README.md +16 -16
  10. package/contracts/gsap-emit-v1.md +163 -163
  11. package/contracts/handoff-contracts.json +10 -10
  12. package/dist/index.js +440 -286
  13. package/docs/development.en.md +47 -47
  14. package/docs/development.md +47 -47
  15. package/docs/reference.en.md +59 -15
  16. package/docs/reference.md +59 -15
  17. package/docs/workflow.en.md +72 -72
  18. package/docs/workflow.md +72 -72
  19. package/package.json +2 -2
  20. package/skills/gtrk-ai-drama/SKILL.md +10 -10
  21. package/skills/gtrk-ai-drama/references/sparse-key-scenes.md +38 -38
  22. package/skills/gtrk-ai-drama/scripts/validate-storyboard.mjs +119 -119
  23. package/skills/gtrk-composite/SKILL.md +194 -0
  24. package/skills/gtrk-cover/SKILL.md +3 -3
  25. package/skills/gtrk-cover/references/layout-recipes.md +337 -337
  26. package/skills/gtrk-cover/references/prompt-templates.md +119 -119
  27. package/skills/gtrk-food-recap/SKILL.md +9 -9
  28. package/skills/gtrk-live-slicing/SKILL.md +2 -1
  29. package/skills/gtrk-long2short/SKILL.md +2 -1
  30. package/skills/gtrk-matrix/SKILL.md +20 -10
  31. package/skills/gtrk-mg/SKILL.md +391 -391
  32. package/skills/gtrk-music-visualizer/SKILL.md +3 -3
  33. package/skills/gtrk-narration/SKILL.md +23 -23
  34. package/skills/gtrk-oralcut/SKILL.md +5 -4
  35. package/skills/gtrk-splitter/SKILL.md +298 -298
  36. package/skills/gtrk-splitter/references/example-visual-split.json +645 -645
  37. package/skills/gtrk-splitter/references/example-visual-split.md +439 -439
  38. package/skills/gtrk-splitter/references/field-schema.md +171 -171
  39. package/skills/gtrk-style-maker/SKILL.md +1 -1
  40. package/skills/gtrk-style-maker/references/ammo.md +16 -16
  41. package/skills/gtrk-style-maker/references/contracts-ref.md +9 -9
  42. package/skills/gtrk-style-maker/references/seeds/README.md +4 -4
  43. package/skills/gtrk-style-maker/references/seeds/seed-ecom-home-goods.md +37 -37
  44. package/skills/gtrk-style-maker/references/seeds/seed-psych-humanities.md +35 -35
  45. package/skills/gtrk-talking-head/SKILL.md +15 -7
  46. package/skills/gtrk-tools/SKILL.md +3 -2
  47. package/skills/gtrk-transcript/SKILL.md +4 -4
  48. package/skills/gtrk-transcript/agents/openai.yaml +4 -4
  49. package/skills/gtrk-travel-recap/SKILL.md +52 -52
  50. package/skills/gtrk-vlog-docu/SKILL.md +6 -6
  51. package/skills/gtrk-voiceover/SKILL.md +2 -2
@@ -0,0 +1,265 @@
1
+ <!doctype html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>合成颗粒出片台</title>
6
+ <style>
7
+ html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: #000; }
8
+ .layer { position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; border: 0; }
9
+ </style>
10
+ </head>
11
+ <body>
12
+ <iframe id="back" class="layer" title="back"></iframe>
13
+ <canvas id="slots" class="layer" width="1920" height="1080"></canvas>
14
+ <iframe id="front" class="layer" title="front"></iframe>
15
+ <script>
16
+ /* 合成颗粒出片台(composite-kit@1;`gtrk composite lint --stills` 与 `gtrk composite bake` 共用):与客户端预览同一套规则——
17
+ * 两层图形 iframe(back / front)夹一块槽位画布。颗粒走宿主协议(init / apply → frame)。
18
+ *
19
+ * 槽位取帧(与客户端 `composite-slot-frames.ts` 的 `resolveSlotSource` 同口径):
20
+ * ref = 片段在时间线该时刻的画面(之前首帧、之后末帧),源时刻 = clip_st + (T − track_st) × speed;
21
+ * ref-lane = 按 lane_clip_id 找轨,空档定格前一段末帧,窗口前无片段取后一段首帧;
22
+ * own = 入点 + (f − starts_at_f) 帧。
23
+ * 取到的源时刻按「时间戳区间覆盖该时刻的那一帧」取帧(与客户端 VideoCache 同规则):
24
+ * - 给了中间文件(`segs`:素材 → [{url, off, ed, pts, tag, eof}])时,按中间文件实测的帧时间戳表找覆盖帧、定位到帧中点
25
+ * (不假设首帧时间戳为 0,也不靠帧率推算——可变帧率同样成立);画完读回中间文件底部的帧号条,核对确是那一帧
26
+ * (`seeked` 之后新帧未必已可画:满载时会画出上一帧或空帧),不对就等、必要时重新定位,始终不对则报错;
27
+ * - 没给时直接定位原素材(lint 静帧)。
28
+ * 绘制:先把视频帧 1:1 拷进一张画布,再从画布缩放画进槽位——与客户端同一种绘制源(客户端画的是 VideoCache 的 RGB 画布)。
29
+ * 每个槽位一个 <video>(同源取帧不共用解码,见客户端 R15);图片素材用 <img>。
30
+ *
31
+ * 用法:stageInit({ fps, entries, materials, images?, segs?, tracks }) → renderAt(时间线秒)。全部经 file:// 读本地文件。
32
+ * `window.SETTLE`:画完后等多少毫秒(缺省 60,lint 静帧用);烘焙置 -1(截帧由 beginFrame / 截图本身推进合成器)。 */
33
+ let FPS = 60, entries = [], matUrl = new Map(), imageIds = new Set(), segsOf = new Map(), byClip = new Map();
34
+ const back = document.getElementById("back"), front = document.getElementById("front");
35
+ const canvas = document.getElementById("slots"), ctx = canvas.getContext("2d");
36
+
37
+ window.stageInit = (data) => {
38
+ FPS = data.fps; entries = data.entries;
39
+ matUrl = new Map(Object.entries(data.materials));
40
+ imageIds = new Set(data.images || []);
41
+ segsOf = new Map(Object.entries(data.segs || {}));
42
+ byClip = new Map();
43
+ for (const t of data.tracks) {
44
+ const clips = t.clips.filter((c) => c.material).sort((a, b) => a.track_st - b.track_st);
45
+ for (const c of clips) byClip.set(c.clip_id, { clip: c, clips });
46
+ }
47
+ return true;
48
+ };
49
+
50
+ let current = null;
51
+ const waiters = new Map();
52
+ addEventListener("message", (ev) => {
53
+ const m = ev.data;
54
+ if (!m || m.ch !== "gtrk-composite") return;
55
+ const layer = ev.source === back.contentWindow ? "back" : ev.source === front.contentWindow ? "front" : null;
56
+ if (!layer) return;
57
+ if (m.type === "error") { console.error("[composite]", layer, m.message); window.__scriptErrors = (window.__scriptErrors || []).concat(m.message); }
58
+ const key = layer + ":" + (m.type === "frame" ? "frame:" + m.seq : m.type);
59
+ const w = waiters.get(key);
60
+ if (w) { waiters.delete(key); w(m); }
61
+ });
62
+ const waitFor = (key, ms = 15000) => new Promise((res, rej) => {
63
+ waiters.set(key, res);
64
+ setTimeout(() => { if (waiters.delete(key)) rej(new Error("超时:" + key)); }, ms);
65
+ });
66
+ const load = (frame, src) => new Promise((res) => { frame.onload = () => res(); frame.src = src; });
67
+ async function mount(entry) {
68
+ if (current && current.entry.id === entry.id) return current.ready;
69
+ const ready = (async () => {
70
+ await Promise.all([load(back, entry.url), load(front, entry.url)]);
71
+ for (const [frame, layer] of [[back, "back"], [front, "front"]]) {
72
+ const p = waitFor(layer + ":ready");
73
+ frame.contentWindow.postMessage({ ch: "gtrk-composite", type: "init", layer, fps: FPS, anchors: entry.anchors_f, texts: entry.texts }, "*");
74
+ await p;
75
+ }
76
+ })();
77
+ current = { entry, ready };
78
+ return ready;
79
+ }
80
+
81
+ const pool = new Map();
82
+ function videoFor(key, url) {
83
+ let v = pool.get(key);
84
+ if (!v || v.dataset.url !== url) {
85
+ v = document.createElement("video");
86
+ v.muted = true; v.preload = "auto"; v.dataset.url = url; v.src = url;
87
+ pool.set(key, v);
88
+ }
89
+ return v;
90
+ }
91
+ const images = new Map();
92
+ function imageFor(url) {
93
+ let p = images.get(url);
94
+ if (!p) {
95
+ p = new Promise((res) => { const i = new Image(); i.onload = () => res(i); i.onerror = () => res(null); i.src = url; });
96
+ images.set(url, p);
97
+ }
98
+ return p;
99
+ }
100
+ const once = (el, ev) => new Promise((res) => el.addEventListener(ev, res, { once: true }));
101
+ async function seek(v, sec, force = false) {
102
+ if (v.readyState < 1) await once(v, "loadedmetadata");
103
+ const target = Math.min(Math.max(sec, 0), Math.max(v.duration - 0.001, 0));
104
+ // 强制重定位:先挪到同一帧内略早的位置再回来(目标是帧中点,±2 毫秒仍在该帧内),逼解码器重出这一帧
105
+ if (force) { const d = once(v, "seeked"); v.currentTime = Math.max(target - 0.002, 0); await d; }
106
+ if (force || Math.abs(v.currentTime - target) > 1e-4 || v.readyState < 2) {
107
+ const done = once(v, "seeked");
108
+ v.currentTime = target;
109
+ await done;
110
+ }
111
+ if (v.readyState < 2) await once(v, "loadeddata");
112
+ return v;
113
+ }
114
+
115
+ /** 有序帧时间戳表里「≤ t 的最后一帧」的下标(二分)。 */
116
+ function coverIndex(pts, t) {
117
+ let lo = 0, hi = pts.length - 1;
118
+ if (t < pts[0]) return 0;
119
+ while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (pts[mid] <= t + 1e-6) lo = mid; else hi = mid - 1; }
120
+ return lo;
121
+ }
122
+ /** 源时刻 → { url, at(定位目标), srcFrame(该帧对应的源时刻,供核对)}。 */
123
+ function locate(materialId, sec) {
124
+ const segs = segsOf.get(materialId);
125
+ if (segs) {
126
+ // eof:中间文件转到了素材结尾,更晚的源时刻(片段出点超出素材长度)取它的末帧
127
+ const s = segs.find((x) => sec >= x.off - 1e-6 && (sec < x.ed || x.eof)) || null;
128
+ if (s) {
129
+ const local = s.pts[0] + (sec - s.off);
130
+ const i = coverIndex(s.pts, local);
131
+ const next = i + 1 < s.pts.length ? s.pts[i + 1] : s.pts[i] + (i > 0 ? s.pts[i] - s.pts[i - 1] : 1 / FPS);
132
+ return { url: s.url, at: (s.pts[i] + next) / 2, srcFrame: s.off + (s.pts[i] - s.pts[0]), viaSeg: true, idx: i, tag: s.tag || 0 };
133
+ }
134
+ }
135
+ return { url: matUrl.get(materialId), at: sec, srcFrame: sec, viaSeg: false, idx: -1, tag: 0 };
136
+ }
137
+
138
+ const speedOf = (c) => (typeof c.speed === "number" && c.speed > 0 ? c.speed : 1);
139
+ const clipSrc = (c, T) => c.clip_st + (T - c.track_st) * speedOf(c);
140
+ const lastFrameT = (c) => c.track_ed - 1 / FPS;
141
+ function sourceFor(b, T, f) {
142
+ if (b.kind === "ref") {
143
+ const hit = byClip.get(b.clip_id); if (!hit) return null;
144
+ const c = hit.clip;
145
+ const tt = Math.min(Math.max(T, c.track_st), lastFrameT(c));
146
+ return { material: c.material, sec: clipSrc(c, tt) };
147
+ }
148
+ if (b.kind === "ref-lane") {
149
+ const hit = byClip.get(b.lane_clip_id); if (!hit) return null;
150
+ const act = hit.clips.find((c) => T >= c.track_st && T < c.track_ed);
151
+ if (act) return { material: act.material, sec: clipSrc(act, T) };
152
+ const prev = hit.clips.filter((c) => c.track_ed <= T).pop();
153
+ if (prev) return { material: prev.material, sec: clipSrc(prev, lastFrameT(prev)) };
154
+ const next = hit.clips.find((c) => c.track_st > T);
155
+ return next ? { material: next.material, sec: next.clip_st } : null;
156
+ }
157
+ if (b.kind === "own") {
158
+ const k = Math.min(Math.max(f - b.starts_at_f, 0), b.dur_f - 1);
159
+ return { material: b.material, sec: b.src_in_ms / 1000 + k / FPS };
160
+ }
161
+ return null;
162
+ }
163
+ /** fit / framing / crop 合成的绘制矩形(与客户端 `slotDrawRect` 逐值相同;源或槽位尺寸为 0 时返回 null,不画)。 */
164
+ function drawRect(g, sw0, sh0, fit, framing) {
165
+ const [top, right, bottom, left] = g.crop;
166
+ const sx = sw0 * left, sy = sh0 * top, sw = sw0 * (1 - left - right), sh = sh0 * (1 - top - bottom);
167
+ if (!(sw > 0 && sh > 0 && g.w > 0 && g.h > 0)) return null;
168
+ const base = fit === "contain" ? Math.min(g.w / sw, g.h / sh) : Math.max(g.w / sw, g.h / sh);
169
+ const scale = base * (Math.max((framing && framing.scale_pm) || 1000, 1000) / 1000);
170
+ const dw = sw * scale, dh = sh * scale;
171
+ const cx = g.x + g.w / 2 + ((framing && framing.x_pm) || 0) / 1000 * g.w;
172
+ const cy = g.y + g.h / 2 + ((framing && framing.y_pm) || 0) / 1000 * g.h;
173
+ return [sx, sy, sw, sh, cx - dw / 2, cy - dh / 2, dw, dh];
174
+ }
175
+ function shape(g) {
176
+ ctx.beginPath();
177
+ if (g.shape === "circle") ctx.ellipse(g.x + g.w / 2, g.y + g.h / 2, g.w / 2, g.h / 2, 0, 0, Math.PI * 2);
178
+ else if (g.shape === "roundrect" && g.radius > 0) ctx.roundRect(g.x, g.y, g.w, g.h, Math.min(g.radius, g.w / 2, g.h / 2));
179
+ else ctx.rect(g.x, g.y, g.w, g.h);
180
+ }
181
+ /** 视频帧 1:1 拷进的中转画布(每个 <video> 一张,复用)。 */
182
+ const relay = new Map();
183
+ function relayOf(v) {
184
+ let k = relay.get(v);
185
+ if (!k) { k = document.createElement("canvas"); relay.set(v, k); }
186
+ if (k.width !== v.videoWidth || k.height !== v.videoHeight) { k.width = v.videoWidth; k.height = v.videoHeight; }
187
+ const c = k.getContext("2d", { willReadFrequently: true });
188
+ c.clearRect(0, 0, k.width, k.height);
189
+ c.drawImage(v, 0, 0);
190
+ return k;
191
+ }
192
+ /** 读中转画布底部帧号条:16 位黑白块(块宽 floor(宽/32)*2,与 CLI 的 drawbox 同式);没画上(透明)返回 -1。 */
193
+ function readTag(k, tag) {
194
+ const bw = Math.floor(k.width / 32) * 2;
195
+ const row = k.getContext("2d", { willReadFrequently: true }).getImageData(0, k.height - Math.ceil(tag / 2), k.width, 1).data;
196
+ let n = 0;
197
+ for (let b = 0; b < 16; b++) {
198
+ const o = (b * bw + (bw >> 1)) * 4;
199
+ if (row[o + 3] < 255) return -1;
200
+ if (row[o] > 128) n |= 1 << b;
201
+ }
202
+ return n;
203
+ }
204
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
205
+ /** 画到中转画布并核对帧号;不对就等新帧到位,每 40 次重新定位一次,约 3 秒仍不对即报错(该段会被重试)。 */
206
+ async function drawVerified(v, at) {
207
+ for (let i = 0; ; i++) {
208
+ const k = relayOf(v);
209
+ const got = readTag(k, at.tag);
210
+ if (got === (at.idx & 0xffff)) return { k, waits: i };
211
+ if (i >= 400) throw new Error("取帧校验失败:要第 " + at.idx + " 帧,画出的是 " + got + "(" + at.url + ")");
212
+ if (i % 40 === 39) await seek(v, at.at, true);
213
+ else await sleep(i < 20 ? 2 : 8);
214
+ }
215
+ }
216
+
217
+ let seq = 0;
218
+ window.renderAt = async (T) => {
219
+ const F = Math.round(T * FPS);
220
+ const entry = entries.find((p) => F >= p.window_f[0] && F < p.window_f[1]);
221
+ if (!entry) throw new Error("该时刻没有合成颗粒:" + T);
222
+ await mount(entry);
223
+ const f = Math.min(Math.max(F - entry.window_f[0], 0), entry.window_f[1] - entry.window_f[0] - 1);
224
+ const s = ++seq;
225
+ const pf = waitFor("front:frame:" + s), pb = waitFor("back:frame:" + s);
226
+ for (const fr of [back, front]) fr.contentWindow.postMessage({ ch: "gtrk-composite", type: "apply", seq: s, frame: f }, "*");
227
+ const [geo] = await Promise.all([pf, pb]);
228
+ const Tf = (entry.window_f[0] + f) / FPS;
229
+ const used = [];
230
+ const jobs = geo.slots.filter((g) => g.visible).map(async (g) => {
231
+ const b = entry.bindings[g.id];
232
+ if (!b || b.kind === "text") return null;
233
+ const src = sourceFor(b, Tf, f);
234
+ if (!src || !matUrl.has(src.material)) return null;
235
+ if (imageIds.has(src.material)) {
236
+ const img = await imageFor(matUrl.get(src.material));
237
+ used.push({ slot: g.id, material: src.material, sec: 0 });
238
+ return img ? { g, b, img } : null;
239
+ }
240
+ const at = locate(src.material, src.sec);
241
+ const v = await seek(videoFor(g.id + "|" + at.url, at.url), at.at);
242
+ const ver = at.tag ? await drawVerified(v, at) : { k: relayOf(v), waits: 0 };
243
+ used.push({ slot: g.id, material: src.material, sec: src.sec, srcFrame: at.srcFrame, viaSeg: at.viaSeg, waits: ver.waits });
244
+ return { g, b, v, k: ver.k, tag: at.tag };
245
+ });
246
+ const ready = (await Promise.all(jobs)).filter(Boolean);
247
+ ctx.clearRect(0, 0, 1920, 1080);
248
+ for (const { g, b, v, k, tag, img } of ready) {
249
+ // 中间文件底部的帧号条不进画面:源高度按去掉条带算
250
+ const r = img ? drawRect(g, img.naturalWidth, img.naturalHeight, b.fit || g.fit, b.framing) : drawRect(g, v.videoWidth, v.videoHeight - (tag || 0), b.fit || g.fit, b.framing);
251
+ if (!r) continue;
252
+ ctx.save();
253
+ shape(g); ctx.clip();
254
+ ctx.globalAlpha = g.opacity;
255
+ if (g.filter) ctx.filter = g.filter;
256
+ ctx.drawImage(img || k, ...r);
257
+ ctx.restore();
258
+ }
259
+ const settle = window.SETTLE ?? 60;
260
+ if (settle > 0) await new Promise((r) => setTimeout(r, settle));
261
+ return { id: entry.id, f, used };
262
+ };
263
+ </script>
264
+ </body>
265
+ </html>
@@ -1,6 +1,6 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="560" height="170" viewBox="0 0 560 170" role="img" aria-labelledby="title">
2
- <title id="title">gtrk CLI</title>
3
- <text x="84" y="128" fill="#f43b48" font-family="Segoe UI, Helvetica, Arial, sans-serif" font-size="150" font-weight="750" letter-spacing="-9">gtrk</text>
4
- <path d="M403 63v61" stroke="#f43b48" stroke-opacity=".3" stroke-width="2"/>
5
- <text x="425" y="106" fill="#888888" font-family="Consolas, monospace" font-size="28" letter-spacing="3">CLI</text>
6
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="560" height="170" viewBox="0 0 560 170" role="img" aria-labelledby="title">
2
+ <title id="title">gtrk CLI</title>
3
+ <text x="84" y="128" fill="#f43b48" font-family="Segoe UI, Helvetica, Arial, sans-serif" font-size="150" font-weight="750" letter-spacing="-9">gtrk</text>
4
+ <path d="M403 63v61" stroke="#f43b48" stroke-opacity=".3" stroke-width="2"/>
5
+ <text x="425" y="106" fill="#888888" font-family="Consolas, monospace" font-size="28" letter-spacing="3">CLI</text>
6
+ </svg>
@@ -1,16 +1,16 @@
1
- # 框架契约库(handoff contracts)
2
-
3
- 同合云管线的 handoff 契约**正本**,随 `@gitruck/cli` npm 包分发。创作 skill 以「契约名 + 版本」引用(如 `gsap-emit v1`);`handoff-contracts.json` 是「handoff 类型 → 契约」映射表,消费方查表、不硬编码。
4
-
5
- ## 收录双轴边界(handoff-contract-library spec,扩项须走 change 过审)
6
-
7
- - **轴一(管线性,审美零预设)**:契约只约束**机器可判定的管线消费属性**(封装/注册/确定性/依赖可达/产物格式)。**不得**规定颜色、字体、构图、红点数量等任何视觉取值或栏目词表词条;凡涉视觉取值处一律参数化/中性占位。「全片最多一个红点」这类审美铁律属栏目 skill 侧,**不入库**。
8
- - **轴二(脱敏)**:只收**自有管线**的合规契约——渲染引擎合规、`.gtrk` 契约、派单格式(封闭列举)。外部供应商/外部平台适配内容(AI 视频平台差异、文生图平台参数等)**永不收录**,那属于用户自己的 skill。
9
-
10
- ## 现有契约
11
-
12
- | 契约 | 版本 | 适用 handoff | 文档 |
13
- |---|---|---|---|
14
- | gsap-emit(HTML 动画颗粒逐帧 seek 合规) | v1 | MG(去品牌化前 RRV_MG,消费方查表前归一) | [gsap-emit-v1.md](gsap-emit-v1.md) |
15
-
16
- 维护者证据附录:[`gsap-emit-v1-evidence.md`](gsap-emit-v1-evidence.md)。该文件不属于 agent 执行时的必读契约。
1
+ # 框架契约库(handoff contracts)
2
+
3
+ 同合云管线的 handoff 契约**正本**,随 `@gitruck/cli` npm 包分发。创作 skill 以「契约名 + 版本」引用(如 `gsap-emit v1`);`handoff-contracts.json` 是「handoff 类型 → 契约」映射表,消费方查表、不硬编码。
4
+
5
+ ## 收录双轴边界(handoff-contract-library spec,扩项须走 change 过审)
6
+
7
+ - **轴一(管线性,审美零预设)**:契约只约束**机器可判定的管线消费属性**(封装/注册/确定性/依赖可达/产物格式)。**不得**规定颜色、字体、构图、红点数量等任何视觉取值或栏目词表词条;凡涉视觉取值处一律参数化/中性占位。「全片最多一个红点」这类审美铁律属栏目 skill 侧,**不入库**。
8
+ - **轴二(脱敏)**:只收**自有管线**的合规契约——渲染引擎合规、`.gtrk` 契约、派单格式(封闭列举)。外部供应商/外部平台适配内容(AI 视频平台差异、文生图平台参数等)**永不收录**,那属于用户自己的 skill。
9
+
10
+ ## 现有契约
11
+
12
+ | 契约 | 版本 | 适用 handoff | 文档 |
13
+ |---|---|---|---|
14
+ | gsap-emit(HTML 动画颗粒逐帧 seek 合规) | v1 | MG(去品牌化前 RRV_MG,消费方查表前归一) | [gsap-emit-v1.md](gsap-emit-v1.md) |
15
+
16
+ 维护者证据附录:[`gsap-emit-v1-evidence.md`](gsap-emit-v1-evidence.md)。该文件不属于 agent 执行时的必读契约。