@qilitt-mickey/vue3-temp-skill 1.1.68 → 1.1.70
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.
- package/SKILL.md +2 -2
- package/bin/cli.js +6 -2
- package/package.json +2 -1
- package/parser/index.js +30 -4
- package/references/design-apply.md +20 -0
- package/references/steps/step1-match.md +1 -1
- package/references/steps/step1b-audit.md +1 -1
- package/scripts/apply-final-gate.mjs +170 -0
package/SKILL.md
CHANGED
|
@@ -32,7 +32,7 @@ tags: [vue3, typescript, element-plus, pinia, vite, unocss, crud, demo, code-qua
|
|
|
32
32
|
|
|
33
33
|
1. **项目探查**:读取目标项目的 `package.json`、路由、页面、`Re*` 组件、Hooks、API 和样式;以源码事实为准。
|
|
34
34
|
2. **能力路由**:运行 `scripts/match.py "需求" --json`,**仅**按输出中的 `load_files` 阅读 reference;禁止自行展开全部 `references/`。无 Python 时按下方能力组表手动匹配。
|
|
35
|
-
3. **质量闸门**:代码完成后读取 `references/code-quality.md`,执行清单和项目命令。设计变更集落地后追加**设计层闭环审计**:`node scripts/design-audit.mjs --project <落地项目> --container-width <内容区实际宽>`(ERROR
|
|
35
|
+
3. **质量闸门**:代码完成后读取 `references/code-quality.md`,执行清单和项目命令。设计变更集落地后追加**设计层闭环审计**:`node scripts/design-audit.mjs --project <落地项目> --container-width <内容区实际宽>`(ERROR 必须清零;无落地项目时自动跳过)。**会话结束前必须跑通终态凭证闸**:`node scripts/apply-final-gate.mjs --project <落地项目> --container-width <内容区实际宽>`(串联 changeset-selfcheck → parser match → parser audit → design-audit 四闸,`FINAL-GATE: PASS` 才算交付完成,凭证写入 `.codebuddy/design/ledger.json`)。
|
|
36
36
|
|
|
37
37
|
## 能力组路由
|
|
38
38
|
|
|
@@ -51,7 +51,7 @@ tags: [vue3, typescript, element-plus, pinia, vite, unocss, crud, demo, code-qua
|
|
|
51
51
|
| `files-and-editors` | 附件、预览、下载、富文本、二维码 | `file-management.md`、`download-export.md`、`rich-text.md`、`qrcode-barcode.md` |
|
|
52
52
|
| `realtime-and-mobile` | WebSocket、移动端、微信、验证码、动效 | 对应专项 reference |
|
|
53
53
|
| `data-compare` | 编辑页变更对比和汇总 | `data-compare.md` |
|
|
54
|
-
| `design-apply` | 设计变更集落地:语义 ID 三级匹配、结构/形态/两层样式转换、锚点与结构施工、覆写提权、渲染态验收、合规闸门。**主入口 `design-apply.md`(红线+契约+步骤索引),各步骤明细见 `references/steps/`——做到哪一步读哪一份** | `design-apply.md`、`steps/`、`mapping/`、`parser/`、`scripts/design-audit.mjs` |
|
|
54
|
+
| `design-apply` | 设计变更集落地:语义 ID 三级匹配、结构/形态/两层样式转换、锚点与结构施工、覆写提权、渲染态验收、合规闸门。**主入口 `design-apply.md`(红线+契约+步骤索引),各步骤明细见 `references/steps/`——做到哪一步读哪一份** | `design-apply.md`、`steps/`、`mapping/`、`parser/`、`scripts/design-audit.mjs`、`scripts/apply-final-gate.mjs` |
|
|
55
55
|
|
|
56
56
|
## 工作流
|
|
57
57
|
|
package/bin/cli.js
CHANGED
|
@@ -30,8 +30,12 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
30
30
|
const SKILL_DIR = path.resolve(__dirname, "..");
|
|
31
31
|
const SKILL_NAME = "vue3-temp-skill";
|
|
32
32
|
const TRACK_FILE = path.join(HOME, ".vue3-temp-skill.json");
|
|
33
|
-
// 只复制 AI 运行时真正需要的文件;validate/
|
|
34
|
-
|
|
33
|
+
// 只复制 AI 运行时真正需要的文件;validate/cli 属作者侧词典维护工具,不随技能分发。
|
|
34
|
+
// design-audit.mjs / check.mjs / apply-final-gate.mjs 必须随包分发:E11 交付完整性 / E12 设计层
|
|
35
|
+
// 累积性(ledger 台账)/ 终态机读凭证(四闸串联,FINAL-GATE: PASS)是落地会话的运行时闸——
|
|
36
|
+
// 缺了它们「只挑主题令牌落地 + 覆盖历史成果 + 以自述结束会话」无法被静态拦截
|
|
37
|
+
// (实证:2026-09-24 孔雀绿轮,运行时副本无审计闸,残缺口变更集一路绿灯)。
|
|
38
|
+
const RUNTIME_SCRIPT_FILES = ["match.py", "modules.json", "design-audit.mjs", "check.mjs", "apply-final-gate.mjs"];
|
|
35
39
|
const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(SKILL_DIR, "package.json"), "utf-8")).version;
|
|
36
40
|
|
|
37
41
|
// ============================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilitt-mickey/vue3-temp-skill",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.70",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vue 3 企业级中后台项目开发规范技能包 — core-kernel 架构、按需功能模块",
|
|
6
6
|
"bin": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"prepack": "node scripts/check.mjs",
|
|
32
32
|
"validate": "node scripts/validate.mjs",
|
|
33
33
|
"audit": "node scripts/design-audit.mjs",
|
|
34
|
+
"final-gate": "node scripts/apply-final-gate.mjs",
|
|
34
35
|
"match": "python scripts/match.py",
|
|
35
36
|
"match:batch": "python scripts/match.py --batch",
|
|
36
37
|
"mapping:build": "node parser/index.js build",
|
package/parser/index.js
CHANGED
|
@@ -569,7 +569,8 @@ function checkBannedWords(changeset) {
|
|
|
569
569
|
|
|
570
570
|
for (const [i, c] of (changeset.changes || []).entries()) {
|
|
571
571
|
const label = `changes[${i}]`;
|
|
572
|
-
|
|
572
|
+
// global 为设计侧伪点位(对应变更集顶层 global 节点,同 match 路径口径),豁免 ID 命名规范
|
|
573
|
+
if (!c.id || (c.id !== GLOBAL_PSEUDO_ID && !ID_RE.test(c.id)))
|
|
573
574
|
violations.push(`${label}.id "${c.id}" 不符合语义 ID 命名规范(小写下划线、2~4 段点分层级)`);
|
|
574
575
|
if (c.position !== undefined && c.position !== null)
|
|
575
576
|
scan(`${label}.position`, c.position);
|
|
@@ -1177,11 +1178,24 @@ function checkRegistryCoverage(changeset, plan, opts = {}) {
|
|
|
1177
1178
|
console.log(` ${item.rule} ${registrySids(item).join(" + ")} 缺 dims=[${missing.join(",")}]`);
|
|
1178
1179
|
}
|
|
1179
1180
|
|
|
1180
|
-
// 实物校验:标注缺口的权威来源是效果图 HTML 里的 data-sid,而不是自报的 oracle.sids
|
|
1181
|
+
// 实物校验:标注缺口的权威来源是效果图 HTML 里的 data-sid,而不是自报的 oracle.sids。
|
|
1182
|
+
// 两类合法豁免(与 mockup-extraction-spec §7.1 / 设计侧 D3b 同源):
|
|
1183
|
+
// ① hiddenSids:visible:false 隐藏点设计本身不渲染,基线图上无元素可标;
|
|
1184
|
+
// ② 令牌通道 sid 无元素锚点,由同登记项内已标注的兄弟 sid 以 some 语义覆盖。
|
|
1181
1185
|
let physicalGap = false;
|
|
1186
|
+
const hiddenSids = new Set(plan.filter(p => p.change && p.change.visible === false).map(p => p.id));
|
|
1187
|
+
const sidCoveredByItem = new Map();
|
|
1188
|
+
for (const it of items) {
|
|
1189
|
+
const sids = registrySids(it);
|
|
1190
|
+
const anyAnnotated = sids.some(s => mockupSids.has(s));
|
|
1191
|
+
for (const s of sids)
|
|
1192
|
+
sidCoveredByItem.set(s, anyAnnotated);
|
|
1193
|
+
}
|
|
1182
1194
|
if (physicalSids) {
|
|
1183
1195
|
console.log(` 效果图实物:${mockupFile}(实测 data-sid ${physicalSids.size} 个)`);
|
|
1184
|
-
const declaredNoPhysical = [...declaredSids].filter(
|
|
1196
|
+
const declaredNoPhysical = [...declaredSids].filter(
|
|
1197
|
+
s => !physicalSids.has(s) && !hiddenSids.has(s) && !sidCoveredByItem.get(s),
|
|
1198
|
+
);
|
|
1185
1199
|
const physicalNoDeclared = [...physicalSids].filter(s => !declaredSids.has(s));
|
|
1186
1200
|
if (declaredNoPhysical.length > 0) {
|
|
1187
1201
|
console.log(` ⚠ oracle.sids 声明了 ${declaredNoPhysical.length} 个点位,但效果图实物里没有对应 data-sid(无基线):${declaredNoPhysical.join("、")}`);
|
|
@@ -1195,7 +1209,11 @@ function checkRegistryCoverage(changeset, plan, opts = {}) {
|
|
|
1195
1209
|
}
|
|
1196
1210
|
|
|
1197
1211
|
// 效果图标注完整性:恒存在的子项必须能在效果图上找到同名 data-sid,否则基线根本抽不出来
|
|
1198
|
-
const unannotated = items.filter(
|
|
1212
|
+
const unannotated = items.filter(
|
|
1213
|
+
it => it.always === true
|
|
1214
|
+
&& !registrySids(it).every(s => hiddenSids.has(s))
|
|
1215
|
+
&& !registrySids(it).some(s => mockupSids.has(s)),
|
|
1216
|
+
);
|
|
1199
1217
|
if (unannotated.length > 0) {
|
|
1200
1218
|
const bySid = new Map();
|
|
1201
1219
|
for (const it of unannotated) {
|
|
@@ -1910,6 +1928,12 @@ function cmdAudit(args, dataset) {
|
|
|
1910
1928
|
let problems = 0;
|
|
1911
1929
|
const clean = [];
|
|
1912
1930
|
for (const id of targetIds) {
|
|
1931
|
+
if (id === GLOBAL_PSEUDO_ID) {
|
|
1932
|
+
// 与 match 路径同口径:伪点位对应变更集顶层 global 节点(brand_primary 等基座令牌),
|
|
1933
|
+
// 走 :root/design-tokens 令牌通道对账,不入词典、不做词典点检。
|
|
1934
|
+
console.log(`\nℹ ${id} —— 设计侧伪点位(变更集顶层 global 节点,不入词典):基座令牌走 T 通道对账,词典点检豁免`);
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1913
1937
|
const entry = index[id];
|
|
1914
1938
|
if (!entry) {
|
|
1915
1939
|
console.log(`\n✘ ${id} —— 词典中无此条目(L3 新增点位,须当场落地并回写临时映射)`);
|
|
@@ -2112,6 +2136,8 @@ function cmdVerify(args, dataset) {
|
|
|
2112
2136
|
console.log(`selector 挂载点存在性由 audit 静态校验;本计划只回答「按什么定位」。\n`);
|
|
2113
2137
|
let missing = 0;
|
|
2114
2138
|
for (const sid of sids) {
|
|
2139
|
+
if (sid === GLOBAL_PSEUDO_ID)
|
|
2140
|
+
continue; // 伪点位不入词典(同 match / audit 路径口径),基座令牌不走 selector 抽取
|
|
2115
2141
|
const entry = index[sid];
|
|
2116
2142
|
if (!entry) {
|
|
2117
2143
|
console.log(` ✘ ${sid} —— 词典无此条目(L3 新点位,先落地并回写临时映射)`);
|
|
@@ -134,3 +134,23 @@ parser match(确定性翻译)→ 施工坐标 → AI 按坐标改代码 →
|
|
|
134
134
|
| 回归 | [`regression.md`](steps/regression.md) | 端到端回归 A-V · 配套工具 | 改 parser/mapping 后必跑 |
|
|
135
135
|
|
|
136
136
|
**红线摘要(全文适用,细节在各步子文件)**:出厂中性 · 变更集驱动(上文);先结构后样式(第 2 步先于第 3 步);缺口只分「能力缺口(当场落地)」与「意图缺口(回流设计侧)」两类;**效果图/大文件只读一次**——期望值落盘 `oracle.json` 后,后续环节一律消费落盘产物,禁止反复重读。环境无 Node 时按「环境备注」(见 maintenance.md)人工执行。
|
|
137
|
+
|
|
138
|
+
## 会话结束条件(终态硬闸,违反 = 交付未完成)
|
|
139
|
+
|
|
140
|
+
**落地会话不许「做一批、停下来等用户说继续」。** 「继续」只是异常中断后的恢复手段,不是交付形态——把恢复手段当成默认流程 = 把 1:1 还原降级为多次半成品。本轮做完一批点位后,**必须自动进入下一批**,直到同时满足以下三条才允许结束会话:
|
|
141
|
+
|
|
142
|
+
| # | 结束条件 | 判定方式 |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| 1 | **施工对账清零**:变更集全部已声明点位(`changes[]` ∪ `visible:false` ∪ `pending` 兑现项)全部落地 | 对照 `.codebuddy/design/ledger.json` 台账与 parser match 输出逐条核销;剩余 >0 → 继续施工 |
|
|
145
|
+
| 2 | **静态闸清零**:`parser audit` 与 `scripts/design-audit.mjs` 的 ERROR = 0 | 两闸任一 ERROR 即返工,不得带错收尾 |
|
|
146
|
+
| 3 | **验收闭环**:变更集含 oracle 时跑第 6 步 verify(本环境无浏览器 → 渲染态抽取显式移交用户执行,并说明移交项) | verify 全绿或移交说明已给出 |
|
|
147
|
+
| 4 | **终态机读凭证**:跑通 `node <技能>/scripts/apply-final-gate.mjs --project <项目根> --container-width <内容区宽>`,取得 `FINAL-GATE: PASS` | 该命令串联四闸(changeset-selfcheck → parser match → parser audit → design-audit),任何一步非零即 `FAIL`,会话**不得以完成态结尾**;PASS 凭证自动写入 `.codebuddy/design/ledger.json` 的 `finalGate` 字段——终态判定以凭证为准,不以会话自述为准 |
|
|
148
|
+
|
|
149
|
+
> 条件 4 是 1–3 的机器汇总,不替代前三条的人读核对;前 3 条满足而凭证 FAIL = 仍有静态闸未清零,按 FAIL 输出逐项修复。
|
|
150
|
+
|
|
151
|
+
**禁止的收尾话术(出现即判定为本轮交付失败,必须继续)**:
|
|
152
|
+
- 「全量覆盖是迭代项 / 随迭代补齐 / 后续迭代再补其余细项 / 不阻塞已落地代码」——**覆盖率欠账 = 交付未完成**,不是迭代计划;设计侧变更集 `coverage.note` 出现同类表述同样视为交付失败(设计侧闸 D4/D9 会先拦,话术绕过闸的在这里兜底)。
|
|
153
|
+
- 「核心点位已落地,其余点位影响不大」——影响大小不由落地端裁定,范围只由变更集 `coverage` 与登记表裁定。
|
|
154
|
+
- 「已落地主题色与关键视觉,还原度已达 X%」——还原度以 verify / 登记表对账为准,不得以点位抽样自评。
|
|
155
|
+
|
|
156
|
+
**环境能力边界**:无浏览器只豁免「渲染态抽取与 verify」,**不豁免逐点施工**——静态可落地的 190 子项必须在会话内全部落地,施工不需要浏览器。
|
|
@@ -19,7 +19,7 @@ node parser/index.js match --batch <changesets-dir>
|
|
|
19
19
|
|------|------|------|
|
|
20
20
|
| **L1 完整命中** | ID 在词典中直接命中 | 按条目施工坐标直接落地(预期 100% 还原) |
|
|
21
21
|
| **L2 父级命中** | ID 的父模块命中 | 继承父模块样式约定,按子级定位施工(85-90% 还原) |
|
|
22
|
-
| **L3 未命中** | 词典无此 ID(新增点位) | **当场按第 4 步定制布局落地**并写入 mapping 临时映射;只有设计侧未给完整意图(无值 /
|
|
22
|
+
| **L3 未命中** | 词典无此 ID(新增点位) | **当场按第 4 步定制布局落地**并写入 mapping 临时映射;只有设计侧未给完整意图(无值 / 值非法)才进缺口清单。**例外**:`global` 为设计侧伪点位(对应变更集顶层 global 节点的基座令牌,不入词典),按 T 通道对账,不按 L3 处理 |
|
|
23
23
|
| **字段级未命中** | ID 命中但 `style` 某字段两层映射都查不到 | 值合法且选择器可定位 → **当场生成作用域内自定义规则落设计层文件并申报**;只有「值非法 / 无法定位」才进缺口清单 |
|
|
24
24
|
|
|
25
25
|
**零缺口交付(禁止中途停顿)**:落地一次跑完,**不得因「词典没建这个 ID / 这个字段没枚举 / 这个值要不要确认」而停下来问用户**。缺口只有两类,去向各不相同:
|
|
@@ -16,7 +16,7 @@ node parser/index.js audit --project <目标项目根目录> [<changeset.json…
|
|
|
16
16
|
|---|---|---|
|
|
17
17
|
| **挂载点缺失 / 可疑** | `selector` / `anchors.target` 里的类名在项目里**只在样式文件出现、模板没挂**,或**完全找不到** | 列未绑 `class-name` → 金额列右对齐 / 缩略图尺寸永不生效;操作栏规则有定义无引用 |
|
|
18
18
|
| **死通道** | `anchors` 里的 CSS 变量在项目中**无 `var()` 消费者**(两类豁免:`--el-*` 组件库变量由 EP 运行时消费;设计层已显式接管定义的变量属故意 suppress) | 变量有定义无引用,翻译再准也不生效 |
|
|
19
|
-
| **词典无此条目** | 变更集引用了词典未建的 ID | L3
|
|
19
|
+
| **词典无此条目** | 变更集引用了词典未建的 ID | L3 新增点位须当场落地并回写临时映射。**例外**:`global` 为设计侧伪点位(对应变更集顶层 global 节点的基座令牌),与 match 路径同口径不入词典、不做词典点检 |
|
|
20
20
|
| **G2 structure_ops 施工证据** | 变更集声明 `structure_ops` 的点位,按词典 anchors/selector 语义类名 + `op.node` 提取探针,在**非设计层结构源码**中查证施工痕迹;零命中 = 纯 CSS 冒充结构施工 | 操作列分割线 `add_node` 声明了却从未施工 |
|
|
21
21
|
| **G4 T 通道对账** | `global.brand_primary`(语义值解析 hex)↔ `platform-config.EpThemeColor`;页签 `form` ↔ `ShowModel`;`visible:false` 点位的 `visible_prop` 开关 ↔ platform-config 实值 | 变更集孔雀绿、platform-config 残留旧主题;flat 页签叠加 chrome 结构 |
|
|
22
22
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 终态机读凭证闸(apply-final-gate)
|
|
5
|
+
*
|
|
6
|
+
* 定位:design-apply 会话的**唯一合法结束凭证**。「会话结束条件」过去是文档条款(人读),
|
|
7
|
+
* 执行会话可以不跑、绕过、或以自述结束——孔雀绿轮实证:项目侧审计 ERROR 28 的交付物
|
|
8
|
+
* 以「已完成」收尾。本脚本把终态判定权交给机器:
|
|
9
|
+
*
|
|
10
|
+
* 串联四道闸 → 全部清零才输出 FINAL-GATE: PASS 并把凭证写入交付台账;
|
|
11
|
+
* 任何一步非零 → FINAL-GATE: FAIL,会话不得以完成态结尾,必须继续修复。
|
|
12
|
+
*
|
|
13
|
+
* 闸序(前置闸先跑,贵的后跑):
|
|
14
|
+
* 1. changeset-selfcheck(设计侧交付闸 D0–D13,含 G2 还原语义覆盖现算)
|
|
15
|
+
* 2. parser match(通道 / 挂载点 / G 闸)
|
|
16
|
+
* 3. parser audit(挂载点存在性 + 死通道静态审计)
|
|
17
|
+
* 4. design-audit(设计层闭环审计 E0–E16,含层叠胜负真算与列宽预算链)
|
|
18
|
+
*
|
|
19
|
+
* 用法:
|
|
20
|
+
* node scripts/apply-final-gate.mjs --project <落地项目根目录> [--container-width <px>] [--changeset <变更集.json>]
|
|
21
|
+
*
|
|
22
|
+
* 退出码:0 = PASS(凭证已写入 ledger.json);1 = FAIL;2 = 用法/环境错误。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { execFileSync } from "node:child_process";
|
|
26
|
+
import fs from "node:fs";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
|
|
30
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const NODE = process.execPath;
|
|
32
|
+
|
|
33
|
+
const argv = process.argv.slice(2);
|
|
34
|
+
const argValue = (flag, fallback = null) => {
|
|
35
|
+
const i = argv.indexOf(flag);
|
|
36
|
+
return i >= 0 && argv[i + 1] ? argv[i + 1] : fallback;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const PROJECT = path.resolve(argValue("--project", "") || "");
|
|
40
|
+
if (!PROJECT || !fs.existsSync(PROJECT)) {
|
|
41
|
+
console.error("用法:node scripts/apply-final-gate.mjs --project <落地项目根目录> [--container-width <px>] [--changeset <变更集.json>]");
|
|
42
|
+
console.error(` 项目不存在:${PROJECT || "(未提供 --project)"}`);
|
|
43
|
+
process.exit(2);
|
|
44
|
+
}
|
|
45
|
+
const CONTAINER_WIDTH = argValue("--container-width", "1200");
|
|
46
|
+
|
|
47
|
+
// ── 变更集发现(与 design-audit 同口径:多目录 + 显式 --changeset 优先)──
|
|
48
|
+
const DESIGN_DIRS = [".codebuddy/design", "design", ".changeset"];
|
|
49
|
+
let changeset = argValue("--changeset") ? path.resolve(argValue("--changeset")) : null;
|
|
50
|
+
if (!changeset) {
|
|
51
|
+
const found = [];
|
|
52
|
+
for (const d of DESIGN_DIRS) {
|
|
53
|
+
const full = path.join(PROJECT, d);
|
|
54
|
+
if (!fs.existsSync(full)) continue;
|
|
55
|
+
for (const f of fs.readdirSync(full)) {
|
|
56
|
+
if (!/changeset.*\.json$/i.test(f) || /^_/.test(f)) continue;
|
|
57
|
+
const fullFile = path.join(full, f);
|
|
58
|
+
if (fs.statSync(fullFile).isFile()) found.push({ file: fullFile, mtime: fs.statSync(fullFile).mtimeMs });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
found.sort((a, b) => b.mtime - a.mtime);
|
|
62
|
+
changeset = found[0]?.file || null;
|
|
63
|
+
}
|
|
64
|
+
if (!changeset || !fs.existsSync(changeset)) {
|
|
65
|
+
console.error(`FINAL-GATE: FAIL(无变更集)`);
|
|
66
|
+
console.error(` ${PROJECT} 下未发现变更集(查找 .codebuddy/design、design、.changeset)——没有交付物就没有终态。`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── 跨技能脚本定位(部署副本是同级技能目录;开发期 E 盘源同构)──
|
|
71
|
+
const UI_DESIGN_ROOT =
|
|
72
|
+
argValue("--ui-design-root") ||
|
|
73
|
+
[path.resolve(__dirname, "..", "..", "project-ui-design"), path.resolve(__dirname, "..", "project-ui-design")].find((p) =>
|
|
74
|
+
fs.existsSync(path.join(p, "scripts", "changeset-selfcheck.mjs")),
|
|
75
|
+
) ||
|
|
76
|
+
null;
|
|
77
|
+
const SELFCHECK = UI_DESIGN_ROOT ? path.join(UI_DESIGN_ROOT, "scripts", "changeset-selfcheck.mjs") : null;
|
|
78
|
+
const PARSER = path.resolve(__dirname, "..", "parser", "index.js");
|
|
79
|
+
const DESIGN_AUDIT = path.join(__dirname, "design-audit.mjs");
|
|
80
|
+
|
|
81
|
+
const steps = [];
|
|
82
|
+
const run = (name, cmd, args) => {
|
|
83
|
+
const label = `${name}(${args.filter((a) => !a.startsWith("--") || a === "--project" || a === "--registry").map((a) => (a.length > 60 ? a.slice(0, 57) + "…" : a)).join(" ")})`;
|
|
84
|
+
try {
|
|
85
|
+
const out = execFileSync(cmd, args, { encoding: "buffer", timeout: 300000 }).toString("utf-8");
|
|
86
|
+
steps.push({ name, ok: true, label, output: out });
|
|
87
|
+
return true;
|
|
88
|
+
} catch (e) {
|
|
89
|
+
const out = `${e.stdout ? e.stdout.toString("utf-8") : ""}${e.stderr ? e.stderr.toString("utf-8") : ""}`;
|
|
90
|
+
steps.push({ name, ok: false, label, output: out });
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
console.log(`\n=== 终态机读凭证闸(apply-final-gate)===`);
|
|
96
|
+
console.log(` 项目:${PROJECT}`);
|
|
97
|
+
console.log(` 变更集:${path.relative(PROJECT, changeset).replace(/\\/g, "/")}\n`);
|
|
98
|
+
|
|
99
|
+
let allOk = true;
|
|
100
|
+
|
|
101
|
+
// 1. 设计侧交付闸
|
|
102
|
+
if (SELFCHECK) {
|
|
103
|
+
allOk = run("① 交付闸 changeset-selfcheck", NODE, [SELFCHECK, "--changeset", changeset]) && allOk;
|
|
104
|
+
} else {
|
|
105
|
+
steps.push({
|
|
106
|
+
name: "① 交付闸 changeset-selfcheck",
|
|
107
|
+
ok: false,
|
|
108
|
+
label: "未找到 project-ui-design 技能",
|
|
109
|
+
output: "",
|
|
110
|
+
});
|
|
111
|
+
console.error(" ✖ 未定位到 project-ui-design/scripts/changeset-selfcheck.mjs——部署不完整,按失败处理(fail-closed)。");
|
|
112
|
+
allOk = false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2/3. parser 闸
|
|
116
|
+
if (fs.existsSync(PARSER)) {
|
|
117
|
+
allOk = run("② parser match(通道与 G 闸)", NODE, [PARSER, "match", changeset]) && allOk;
|
|
118
|
+
allOk = run("③ parser audit(挂载点静态审计)", NODE, [PARSER, "audit", "--project", PROJECT, changeset]) && allOk;
|
|
119
|
+
} else {
|
|
120
|
+
console.error(" ✖ 未定位到 parser/index.js——技能部署不完整,按失败处理(fail-closed)。");
|
|
121
|
+
steps.push({ name: "②/③ parser 闸", ok: false, label: "parser/index.js 缺失", output: "" });
|
|
122
|
+
allOk = false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 4. 设计层闭环审计(由它维护 ledger 轮次;--changeset 固定同一份,避免多份并存时各闸看错对象)
|
|
126
|
+
allOk = run("④ design-audit(设计层闭环审计)", NODE, [DESIGN_AUDIT, "--project", PROJECT, "--container-width", CONTAINER_WIDTH, "--changeset", changeset]) && allOk;
|
|
127
|
+
|
|
128
|
+
// ── 汇总 + 凭证落账 ─────────────────────────────────────────
|
|
129
|
+
console.log(`\n${"=".repeat(56)}`);
|
|
130
|
+
for (const s of steps) {
|
|
131
|
+
console.log(` ${s.ok ? "✓" : "✗"} ${s.name}`);
|
|
132
|
+
if (!s.ok) {
|
|
133
|
+
const tail = s.output.trim().split("\n").slice(-14).join("\n ");
|
|
134
|
+
if (tail) console.log(` ${tail}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const status = allOk ? "PASS" : "FAIL";
|
|
139
|
+
console.log(`\nFINAL-GATE: ${status}`);
|
|
140
|
+
if (allOk) {
|
|
141
|
+
console.log(` 四闸清零,交付完成。凭证已写入 .codebuddy/design/ledger.json(finalGate)。`);
|
|
142
|
+
} else {
|
|
143
|
+
const firstBad = steps.find((s) => !s.ok);
|
|
144
|
+
console.log(` 终态未达成(首个未清零闸:${firstBad?.name || "?"})——会话不得以完成态结尾,须继续修复后复跑。`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 凭证写入 ledger.json(不改动 design-audit 维护的轮次历史,只追加 finalGate 字段)
|
|
148
|
+
try {
|
|
149
|
+
const ledgerPath = path.join(PROJECT, ".codebuddy", "design", "ledger.json");
|
|
150
|
+
let ledger = {};
|
|
151
|
+
if (fs.existsSync(ledgerPath)) {
|
|
152
|
+
try {
|
|
153
|
+
ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf-8"));
|
|
154
|
+
} catch {
|
|
155
|
+
ledger = {};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
ledger.finalGate = {
|
|
159
|
+
status,
|
|
160
|
+
at: new Date().toISOString(),
|
|
161
|
+
changeset: path.relative(PROJECT, changeset).replace(/\\/g, "/"),
|
|
162
|
+
steps: steps.map((s) => ({ name: s.name, ok: s.ok })),
|
|
163
|
+
};
|
|
164
|
+
fs.mkdirSync(path.dirname(ledgerPath), { recursive: true });
|
|
165
|
+
fs.writeFileSync(ledgerPath, JSON.stringify(ledger, null, 2) + "\n", "utf-8");
|
|
166
|
+
} catch {
|
|
167
|
+
/* 台账写入失败不改变终态判定,凭证以本命令退出码为准 */
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
process.exit(allOk ? 0 : 1);
|