scrum-skills 0.3.0 → 0.3.2
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/.agents/skills/scrum-planning/references/schema.md +82 -4
- package/.agents/skills/scrum-report/SKILL.md +4 -1
- package/.agents/skills/scrum-sprint/SKILL.md +1 -1
- package/.agents/skills/scrum-tdd/SKILL.md +19 -5
- package/README.md +11 -7
- package/assets/board/200.html +1 -1
- package/assets/board/404.html +1 -1
- package/assets/board/_nuxt/BbwJq5pb.js +1 -0
- package/assets/board/_nuxt/{CkF353aX.js → DkI1Qxpp.js} +49 -49
- package/assets/board/_nuxt/S08nFESZ.js +3 -0
- package/assets/board/_nuxt/builds/latest.json +1 -1
- package/assets/board/_nuxt/builds/meta/8e23aec9-5b67-4569-b8bf-d4f07e50ffdf.json +1 -0
- package/assets/board/_nuxt/{entry.Dx3CNA--.css → entry.CKHePurH.css} +1 -1
- package/assets/board/_nuxt/last-change.1Su4GrCN.css +1 -0
- package/assets/board/_nuxt/tests.dXlUX16A.css +1 -0
- package/assets/board/burndown/index.html +1 -1
- package/assets/board/epics/index.html +1 -1
- package/assets/board/gantt/index.html +1 -1
- package/assets/board/index.html +1 -1
- package/assets/board/last-change/index.html +1 -1
- package/assets/board/milestones/index.html +1 -1
- package/assets/board/tests/index.html +1 -0
- package/bin/scrum-skills.mjs +4 -2
- package/package.json +1 -1
- package/templates/AGENTS.md +12 -7
- package/tools/agile-tools.mjs +9 -3
- package/tools/ai.mjs +5 -5
- package/tools/git-commit.mjs +216 -0
- package/tools/projects.mjs +23 -2
- package/tools/validate.mjs +161 -0
- package/assets/board/_nuxt/DrprggWq.js +0 -3
- package/assets/board/_nuxt/builds/meta/d7f0931f-32b3-4c23-b05f-7cbf5851b226.json +0 -1
- package/assets/board/_nuxt/last-change.8FndoiOt.css +0 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 「自动提交」:把这次交付整份提交进本地仓库(git add -A + git commit)。
|
|
4
|
+
*
|
|
5
|
+
* 只提交,不 push、不改任何文件内容。所以这里的全部力气都花在「别在错误的时机动手」:
|
|
6
|
+
* · 不是 git 仓库 / 分离头指针 / 正在合并、摘取、回滚、变基 / 工作区有冲突 → 拒绝并说清原因;
|
|
7
|
+
* · 工作区干净 → 拒绝,不造空提交;
|
|
8
|
+
* · 提交信息首行必须是 Conventional Commits(feat: / fix: / chore: …),非法时连 git add 都不做。
|
|
9
|
+
*
|
|
10
|
+
* 用法(CLI 调试;看板走 /api/agile/git/* 两个接口):
|
|
11
|
+
* node tools/git-commit.mjs status [目录]
|
|
12
|
+
* node tools/git-commit.mjs commit <信息文件|-> [目录]
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
15
|
+
import { resolve } from 'node:path';
|
|
16
|
+
import { execFileSync } from 'node:child_process';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const TIMEOUT = 30000;
|
|
20
|
+
const MAX_MESSAGE = 8000;
|
|
21
|
+
|
|
22
|
+
/** 首行:type(scope)!: 内容。`!` 留给破坏性变更 */
|
|
23
|
+
export const CONVENTIONAL_RE =
|
|
24
|
+
/^(feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(\([^()\n]{1,32}\))?!?: \S/;
|
|
25
|
+
|
|
26
|
+
/** 这些文件还在,说明仓库卡在某个多步操作中间,这时候提交会把半截状态钉进历史 */
|
|
27
|
+
const IN_PROGRESS = [
|
|
28
|
+
['MERGE_HEAD', '正在合并(MERGE_HEAD 还在),先把冲突处理完再提交'],
|
|
29
|
+
['CHERRY_PICK_HEAD', '正在摘取提交(CHERRY_PICK_HEAD 还在),先处理完再提交'],
|
|
30
|
+
['REVERT_HEAD', '正在回滚提交(REVERT_HEAD 还在),先处理完再提交'],
|
|
31
|
+
['rebase-merge', '正在变基(rebase-merge 还在),先处理完再提交'],
|
|
32
|
+
['rebase-apply', '正在变基或应用补丁(rebase-apply 还在),先处理完再提交'],
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const CONFLICT_CODES = new Set(['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU']);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 统一走 execFileSync:参数按数组传,不经过 shell,中文路径也不被转义。
|
|
39
|
+
* (git 默认会把非 ASCII 路径写成 "\346\226\207" 这种转义,看板上读不了)
|
|
40
|
+
*/
|
|
41
|
+
const git = (root, args, opts = {}) =>
|
|
42
|
+
execFileSync('git', ['-c', 'core.quotepath=false', '-C', root, ...args], {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
45
|
+
timeout: TIMEOUT,
|
|
46
|
+
windowsHide: true,
|
|
47
|
+
...opts,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const stderrOf = e => String(e?.stderr ?? e?.message ?? '').trim();
|
|
51
|
+
|
|
52
|
+
const parseStatusLine = line => ({
|
|
53
|
+
code: line.slice(0, 2).trim(),
|
|
54
|
+
path: line.slice(3).replace(/^"(.*)"$/, '$1'),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 提交前预览:这个目录能不能提交、提交了会带走哪些文件。
|
|
59
|
+
* 永远不抛错——看板要把它整个显示出来,包括「不是仓库」这种情况。
|
|
60
|
+
*/
|
|
61
|
+
export function commitPreview(root = process.cwd()) {
|
|
62
|
+
const dir = resolve(root);
|
|
63
|
+
const base = { root: dir, isRepo: false, branch: null, blocked: null, files: [], clean: true, conflicted: false };
|
|
64
|
+
|
|
65
|
+
let inside = '';
|
|
66
|
+
try {
|
|
67
|
+
inside = git(dir, ['rev-parse', '--is-inside-work-tree']).trim();
|
|
68
|
+
} catch {
|
|
69
|
+
return base;
|
|
70
|
+
}
|
|
71
|
+
if (inside !== 'true') return base;
|
|
72
|
+
|
|
73
|
+
let branch = null;
|
|
74
|
+
try {
|
|
75
|
+
branch = git(dir, ['rev-parse', '--abbrev-ref', 'HEAD']).trim() || null;
|
|
76
|
+
} catch {
|
|
77
|
+
// 空仓库还没有 HEAD:不算错误,首次提交照样能做
|
|
78
|
+
}
|
|
79
|
+
const detached = branch === 'HEAD';
|
|
80
|
+
|
|
81
|
+
let gitDir = '';
|
|
82
|
+
try {
|
|
83
|
+
gitDir = git(dir, ['rev-parse', '--absolute-git-dir']).trim();
|
|
84
|
+
} catch {}
|
|
85
|
+
|
|
86
|
+
let files = [];
|
|
87
|
+
try {
|
|
88
|
+
files = git(dir, ['status', '--porcelain=v1', '-uall'])
|
|
89
|
+
.split('\n')
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.map(parseStatusLine);
|
|
92
|
+
} catch {
|
|
93
|
+
files = [];
|
|
94
|
+
}
|
|
95
|
+
const conflicted = files.some(f => CONFLICT_CODES.has(f.code));
|
|
96
|
+
|
|
97
|
+
const inProgress = gitDir ? IN_PROGRESS.find(([name]) => existsSync(resolve(gitDir, name))) : null;
|
|
98
|
+
let blocked = null;
|
|
99
|
+
if (inProgress) blocked = inProgress[1];
|
|
100
|
+
else if (detached) blocked = '当前是分离头指针(detached HEAD),在这里提交会丢,先切回分支';
|
|
101
|
+
else if (conflicted) blocked = '工作区里还有冲突(UU / AA / DD),先把冲突解决完再提交';
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
...base,
|
|
105
|
+
isRepo: true,
|
|
106
|
+
branch: detached ? null : branch,
|
|
107
|
+
blocked,
|
|
108
|
+
files,
|
|
109
|
+
clean: files.length === 0,
|
|
110
|
+
conflicted,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 提交信息归一:CRLF → LF、去掉行尾空白、压掉连续空行。
|
|
116
|
+
* 首行必须符合 Conventional Commits,空信息直接拒——不然会写出一个没意义的提交。
|
|
117
|
+
*/
|
|
118
|
+
export function normalizeCommitMessage(message) {
|
|
119
|
+
const text = String(message ?? '')
|
|
120
|
+
.replace(/\r\n?/g, '\n')
|
|
121
|
+
.replace(/[ \t]+$/gm, '')
|
|
122
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
123
|
+
.trim();
|
|
124
|
+
if (!text) throw new Error('提交信息是空的:先在弹窗里看清这次改了什么');
|
|
125
|
+
const subject = text.split('\n')[0];
|
|
126
|
+
if (!CONVENTIONAL_RE.test(subject)) {
|
|
127
|
+
throw new Error(`提交信息首行要符合 Conventional Commits(形如 "feat: 一句话"),现在是「${subject.slice(0, 40)}」`);
|
|
128
|
+
}
|
|
129
|
+
if (text.length > MAX_MESSAGE) throw new Error(`提交信息太长了(${text.length} 字符,上限 ${MAX_MESSAGE}),删掉一些再提交`);
|
|
130
|
+
return text;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 真提交:add -A → commit -F -(多行信息走 stdin,避开 shell 引号)→ 回短 hash。
|
|
135
|
+
* 任何一步失败都抛出人话,看板原样显示。
|
|
136
|
+
*/
|
|
137
|
+
export function commitAll(root = process.cwd(), message = '') {
|
|
138
|
+
const dir = resolve(root);
|
|
139
|
+
const text = normalizeCommitMessage(message);
|
|
140
|
+
|
|
141
|
+
const preview = commitPreview(dir);
|
|
142
|
+
if (!preview.isRepo) throw new Error('这个目录不是 git 仓库,没法自动提交(先 git init)');
|
|
143
|
+
if (preview.blocked) throw new Error(preview.blocked);
|
|
144
|
+
if (preview.clean) throw new Error('没有未提交的改动,不需要提交');
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
git(dir, ['add', '-A']);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
throw new Error(`git add 失败:${stderrOf(e) || '未知原因'}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
git(dir, ['commit', '-F', '-'], { stdio: ['pipe', 'pipe', 'pipe'], input: text });
|
|
154
|
+
} catch (e) {
|
|
155
|
+
const err = stderrOf(e);
|
|
156
|
+
if (/who are you|auto-detect email|user\.name|user\.email/i.test(err)) {
|
|
157
|
+
throw new Error('git 还没配提交身份,先跑:git config user.name "你的名字"、git config user.email "你的邮箱"');
|
|
158
|
+
}
|
|
159
|
+
throw new Error(`git commit 失败:${err || '未知原因'}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const hash = git(dir, ['rev-parse', '--short', 'HEAD']).trim();
|
|
163
|
+
return {
|
|
164
|
+
hash,
|
|
165
|
+
subject: text.split('\n')[0],
|
|
166
|
+
branch: preview.branch,
|
|
167
|
+
files: preview.files.length,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function main() {
|
|
172
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
173
|
+
const positional = rest.filter(a => !a.startsWith('--'));
|
|
174
|
+
|
|
175
|
+
if (cmd === 'status') {
|
|
176
|
+
const p = commitPreview(positional[0] ?? process.cwd());
|
|
177
|
+
if (!p.isRepo) {
|
|
178
|
+
console.log('· 不是 git 仓库(没法自动提交)');
|
|
179
|
+
process.exit(2);
|
|
180
|
+
}
|
|
181
|
+
console.log(`· 分支: ${p.branch ?? '(分离头指针)'}`);
|
|
182
|
+
console.log(`· 待提交: ${p.files.length} 个文件`);
|
|
183
|
+
for (const f of p.files) console.log(` ${f.code.padEnd(2)} ${f.path}`);
|
|
184
|
+
if (p.blocked) console.log(`· 现在不能提交: ${p.blocked}`);
|
|
185
|
+
process.exit(p.blocked ? 3 : 0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (cmd === 'commit') {
|
|
189
|
+
const src = positional[0];
|
|
190
|
+
if (!src) {
|
|
191
|
+
console.error('用法: node tools/git-commit.mjs commit <信息文件|-> [目录]');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
let message;
|
|
195
|
+
try {
|
|
196
|
+
message = src === '-' ? readFileSync(0, 'utf8') : readFileSync(resolve(src), 'utf8');
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.error(`✘ 读不到提交信息文件: ${e.message}`);
|
|
199
|
+
process.exit(3);
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const r = commitAll(positional[1] ?? process.cwd(), message);
|
|
203
|
+
console.log(`✔ 已提交 ${r.hash} · ${r.files} 个文件 · 分支 ${r.branch ?? '(分离头指针)'}`);
|
|
204
|
+
return;
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.error(`✘ ${e.message}`);
|
|
207
|
+
process.exit(4);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.error('用法:\n node tools/git-commit.mjs status [目录]\n node tools/git-commit.mjs commit <信息文件|-> [目录]');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const entry = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : '';
|
|
216
|
+
if (entry && entry === import.meta.url) main();
|
package/tools/projects.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { createAgileTools } from './agile-tools.mjs';
|
|
9
9
|
import { checkLastChange, listLastChangeHistory, readLastChangeAt } from './last-change.mjs';
|
|
10
|
+
import { commitAll, commitPreview } from './git-commit.mjs';
|
|
10
11
|
|
|
11
12
|
const exec = promisify(execFile);
|
|
12
13
|
const validator = fileURLToPath(new URL('./validate.mjs', import.meta.url));
|
|
@@ -86,10 +87,16 @@ function snapshot(root) {
|
|
|
86
87
|
try {
|
|
87
88
|
lastChange = read('last-change');
|
|
88
89
|
} catch {}
|
|
90
|
+
// tests.json 同样可选:没登记用例的项目就是空数组,页面给空状态。
|
|
91
|
+
// 坏文件交给 validate 报,这里吞掉异常,别让整个 /api/agile 挂掉。
|
|
92
|
+
let tests = null;
|
|
93
|
+
try {
|
|
94
|
+
tests = read('tests');
|
|
95
|
+
} catch {}
|
|
89
96
|
// 页面开头要说明「已提交才覆盖、未提交要合并」,所以状态跟着快照一起给出去
|
|
90
97
|
const { state, advice, hash, date, subject } = checkLastChange(root);
|
|
91
98
|
return {
|
|
92
|
-
backlog: read('backlog'), sprints: read('sprints'), journal: read('journal'), lastChange,
|
|
99
|
+
backlog: read('backlog'), sprints: read('sprints'), journal: read('journal'), lastChange, tests,
|
|
93
100
|
meta: { root, servedAt: new Date().toISOString(), lastChangeGit: { state, advice, hash, date, subject } },
|
|
94
101
|
};
|
|
95
102
|
}
|
|
@@ -158,7 +165,13 @@ export function createProjects({ root, store = defaultStore, pick = pickDirector
|
|
|
158
165
|
tools.patch({ file: 'backlog', patches, reason: `看板卡片操作:${from} → ${to}` });
|
|
159
166
|
return { id, from, to };
|
|
160
167
|
};
|
|
161
|
-
|
|
168
|
+
/**
|
|
169
|
+
* 「自动提交」:提交信息由看板按交付记录生成后整份带上来,这里只负责执行。
|
|
170
|
+
* 生成器只有一份(apps/board/app/utils/commit-message.js)——复制按钮与自动提交用的是同一段文本,
|
|
171
|
+
* 不会出现「复制的和提交的不一样」。tools/ 不 import 看板源码,发布包里也就不用带 apps/。
|
|
172
|
+
*/
|
|
173
|
+
const commit = ({ message } = {}) => commitAll(current, message);
|
|
174
|
+
return { list, read, open, select, setStoryStatus, commit };
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
export async function handleProjectApi(projects, { method, path, headers, body }) {
|
|
@@ -178,12 +191,20 @@ export async function handleProjectApi(projects, { method, path, headers, body }
|
|
|
178
191
|
if (method === 'GET' && url.pathname === '/api/agile/last-change/revision') {
|
|
179
192
|
return { status: 200, body: readLastChangeAt(projects.list().root, url.searchParams.get('hash')) };
|
|
180
193
|
}
|
|
194
|
+
// 自动提交前的预览(能不能提交、会带走哪些文件):只读,同样不要 token
|
|
195
|
+
if (method === 'GET' && url.pathname === '/api/agile/git/status') {
|
|
196
|
+
return { status: 200, body: commitPreview(projects.list().root) };
|
|
197
|
+
}
|
|
181
198
|
if (method !== 'POST') return { status: 405, body: { error: '不支持的请求方法。' } };
|
|
182
199
|
if (headers['x-project-token'] !== projects.list().token) return denied();
|
|
183
200
|
// 看板卡片的退回 / 下一步 / 取消:改的是当前打开项目的 backlog.json
|
|
184
201
|
if (url.pathname === '/api/agile/story-status') {
|
|
185
202
|
return { status: 200, body: projects.setStoryStatus({ id: body?.id, action: body?.action }) };
|
|
186
203
|
}
|
|
204
|
+
// 自动提交:真跑 git add -A + git commit(不 push)
|
|
205
|
+
if (url.pathname === '/api/agile/git/commit') {
|
|
206
|
+
return { status: 200, body: projects.commit({ message: body?.message }) };
|
|
207
|
+
}
|
|
187
208
|
if (url.pathname === '/api/projects/select') return { status: 200, body: await projects.select() };
|
|
188
209
|
if (url.pathname === '/api/projects/open') {
|
|
189
210
|
if (!projects.list().recent.includes(body?.root)) throw new Error('该项目不在最近列表中,请选择文件夹。');
|
package/tools/validate.mjs
CHANGED
|
@@ -491,6 +491,167 @@ for (const [tid, { task }] of taskById) {
|
|
|
491
491
|
}
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
+
// ---- tests.json(测试资产,可选文件) ----
|
|
495
|
+
// 不存在 = 这个项目还没登记测试用例,合法(老项目升级不受影响);
|
|
496
|
+
// 存在就必须能被 /tests 页原样渲染,且能支撑「覆盖率缺口」的判断。
|
|
497
|
+
{
|
|
498
|
+
const p = join(AGILE, 'tests.json');
|
|
499
|
+
if (existsSync(p)) {
|
|
500
|
+
const LAYER = ['frontend', 'backend', 'e2e'];
|
|
501
|
+
const LEVEL = ['unit', 'integration', 'e2e'];
|
|
502
|
+
const CASE_STATUS = ['pass', 'fail', 'skipped', 'blocked', 'not-run'];
|
|
503
|
+
const COVER_KEYS = ['lines', 'branches', 'functions', 'statements'];
|
|
504
|
+
const COVER_REF = /^(S-\d+)-A(\d+)$/;
|
|
505
|
+
let doc = null;
|
|
506
|
+
try {
|
|
507
|
+
doc = JSON.parse(readFileSync(p, 'utf8'));
|
|
508
|
+
} catch (e) {
|
|
509
|
+
err(`agile/tests.json: JSON 解析失败 - ${e.message}`);
|
|
510
|
+
}
|
|
511
|
+
if (doc) {
|
|
512
|
+
if (doc.schemaVersion !== 3) err(`agile/tests.json: schemaVersion 应为 3,实际 ${doc.schemaVersion}`);
|
|
513
|
+
const suites = Array.isArray(doc.suites) ? doc.suites : [];
|
|
514
|
+
const runs = Array.isArray(doc.runs) ? doc.runs : [];
|
|
515
|
+
if (!Array.isArray(doc.suites)) err('tests: suites 必须是数组(没有用例就写空数组)');
|
|
516
|
+
if (!Array.isArray(doc.runs)) err('tests: runs 必须是数组(没跑过就写空数组)');
|
|
517
|
+
|
|
518
|
+
const caseById = new Map();
|
|
519
|
+
const cases = [];
|
|
520
|
+
const coveredRefs = new Set();
|
|
521
|
+
const registeredStories = new Set();
|
|
522
|
+
|
|
523
|
+
for (const su of suites) {
|
|
524
|
+
claim(su.id, 'tests suite');
|
|
525
|
+
if (!su.name) err(`tests suite ${su.id ?? '?'}: 缺少 name`);
|
|
526
|
+
if (!LAYER.includes(su.layer)) err(`tests suite ${su.id}: layer 必须是 ${LAYER.join('/')},实际 ${su.layer}`);
|
|
527
|
+
if (!LEVEL.includes(su.level)) err(`tests suite ${su.id}: level 必须是 ${LEVEL.join('/')},实际 ${su.level}`);
|
|
528
|
+
if (su.files !== undefined && (!Array.isArray(su.files) || su.files.some(f => typeof f !== 'string')))
|
|
529
|
+
err(`tests suite ${su.id}: files 必须是字符串数组`);
|
|
530
|
+
if (!Array.isArray(su.cases) || su.cases.length === 0) {
|
|
531
|
+
warn(`tests suite ${su.id}: 没有用例(空套件不该留在清单里)`);
|
|
532
|
+
}
|
|
533
|
+
for (const c of su.cases ?? []) {
|
|
534
|
+
claim(c.id, `tests suite ${su.id} 的 case`);
|
|
535
|
+
if (!c.title) err(`tests case ${c.id ?? '?'}: 缺少 title`);
|
|
536
|
+
if (typeof c.automated !== 'boolean') err(`tests case ${c.id}: automated 必须是布尔值(手工用例也要登记)`);
|
|
537
|
+
if (!CASE_STATUS.includes(c.status))
|
|
538
|
+
err(`tests case ${c.id}: status 必须是 ${CASE_STATUS.join('/')},实际 ${c.status}`);
|
|
539
|
+
if (!c.expect) err(`tests case ${c.id}: 缺少 expect(预期结果,QA 交付物不能只有标题)`);
|
|
540
|
+
if (c.status !== 'not-run') {
|
|
541
|
+
if (!DATE_RE.test(c.lastRun ?? '')) err(`tests case ${c.id}: lastRun 格式应为 YYYY-MM-DD(没跑过就写 not-run)`);
|
|
542
|
+
else if (c.lastRun > today) warn(`tests case ${c.id}: lastRun ${c.lastRun} 在未来`);
|
|
543
|
+
}
|
|
544
|
+
if (c.durationMs !== undefined && (!Number.isFinite(c.durationMs) || c.durationMs < 0))
|
|
545
|
+
err(`tests case ${c.id}: durationMs 必须是非负数字`);
|
|
546
|
+
if (c.covers !== undefined && !Array.isArray(c.covers)) err(`tests case ${c.id}: covers 必须是数组`);
|
|
547
|
+
const covers = Array.isArray(c.covers) ? c.covers : [];
|
|
548
|
+
for (const ref of covers) {
|
|
549
|
+
const m = COVER_REF.exec(typeof ref === 'string' ? ref : '');
|
|
550
|
+
if (!m) { err(`tests case ${c.id}: covers 的 ${ref} 格式应为 S-xxx-A<n>`); continue; }
|
|
551
|
+
const st = storyById.get(m[1]);
|
|
552
|
+
if (!st) { err(`tests case ${c.id}: covers 指向不存在的故事 ${m[1]}`); continue; }
|
|
553
|
+
const total = (st.acceptance ?? []).length;
|
|
554
|
+
if (Number(m[2]) < 1 || Number(m[2]) > total)
|
|
555
|
+
err(`tests case ${c.id}: covers ${ref} 越界(${st.id} 只有 ${total} 条验收)`);
|
|
556
|
+
else { coveredRefs.add(ref); registeredStories.add(st.id); }
|
|
557
|
+
}
|
|
558
|
+
if (c.taskId) {
|
|
559
|
+
const hit = taskById.get(c.taskId);
|
|
560
|
+
if (!hit) err(`tests case ${c.id}: task ${c.taskId} 不存在`);
|
|
561
|
+
else {
|
|
562
|
+
const storyIds = new Set(covers.map(r => String(r).replace(/-A\d+$/, '')).filter(id => storyById.has(id)));
|
|
563
|
+
if (storyIds.size && !storyIds.has(hit.story.id))
|
|
564
|
+
err(`tests case ${c.id}: task ${c.taskId} 属于 ${hit.story.id},与 covers 指向的故事不一致`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
caseById.set(c.id, c);
|
|
568
|
+
cases.push(c);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// 执行记录:时间正序(最后一条 = 最近一次),汇总要自洽
|
|
573
|
+
let prev = '';
|
|
574
|
+
for (const [i, r] of runs.entries()) {
|
|
575
|
+
if (!stampOk(r.at)) err(`tests.runs[${i}]: at 格式应为 YYYY-MM-DD HH:mm:ss`);
|
|
576
|
+
else {
|
|
577
|
+
if (prev && r.at < prev) err(`tests.runs[${i}]: 必须按时间正序排(最新一条放最后),${prev} 之后出现了 ${r.at}`);
|
|
578
|
+
prev = r.at;
|
|
579
|
+
}
|
|
580
|
+
if (!r.command) warn(`tests.runs[${i}]: 缺少 command(这次是怎么跑起来的)`);
|
|
581
|
+
const t = r.totals;
|
|
582
|
+
if (!t || typeof t !== 'object') err(`tests.runs[${i}]: 缺少 totals`);
|
|
583
|
+
else {
|
|
584
|
+
for (const k of ['total', 'pass', 'fail', 'skipped']) {
|
|
585
|
+
if (!Number.isFinite(t[k])) err(`tests.runs[${i}].totals.${k}: 必须是数字`);
|
|
586
|
+
}
|
|
587
|
+
if (Number.isFinite(t.total) && (t.pass ?? 0) + (t.fail ?? 0) + (t.skipped ?? 0) > t.total)
|
|
588
|
+
err(`tests.runs[${i}].totals: 通过 + 失败 + 跳过 加总超过总数 ${t.total},执行记录自相矛盾`);
|
|
589
|
+
}
|
|
590
|
+
if (r.failedCases !== undefined && !Array.isArray(r.failedCases))
|
|
591
|
+
err(`tests.runs[${i}]: failedCases 必须是数组`);
|
|
592
|
+
for (const id of r.failedCases ?? []) {
|
|
593
|
+
if (!caseById.has(id)) err(`tests.runs[${i}]: failedCases 里的 ${id} 不在用例清单里`);
|
|
594
|
+
}
|
|
595
|
+
if (r.coverage !== undefined) {
|
|
596
|
+
if (!r.coverage || typeof r.coverage !== 'object' || Array.isArray(r.coverage))
|
|
597
|
+
err(`tests.runs[${i}].coverage: 必须是按层的对象(frontend / backend)`);
|
|
598
|
+
for (const [layer, cov] of Object.entries(r.coverage ?? {})) {
|
|
599
|
+
if (!LAYER.includes(layer)) err(`tests.runs[${i}].coverage: 层的键只能是 ${LAYER.join('/')},实际 ${layer}`);
|
|
600
|
+
if (!cov || typeof cov !== 'object') { err(`tests.runs[${i}].coverage.${layer}: 必须是对象`); continue; }
|
|
601
|
+
if (!cov.tool) warn(`tests.runs[${i}].coverage.${layer}: 缺少 tool(覆盖率是哪个工具出的)`);
|
|
602
|
+
for (const k of COVER_KEYS) {
|
|
603
|
+
if (cov[k] === undefined) continue;
|
|
604
|
+
if (!Number.isFinite(cov[k]) || cov[k] < 0 || cov[k] > 100)
|
|
605
|
+
err(`tests.runs[${i}].coverage.${layer}.${k}: 百分比必须在 0~100 之间,实际 ${cov[k]}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const latest = runs[runs.length - 1];
|
|
612
|
+
for (const id of latest?.failedCases ?? []) {
|
|
613
|
+
const c = caseById.get(id);
|
|
614
|
+
if (c && c.status !== 'fail')
|
|
615
|
+
err(`tests: 最新一次执行的 failedCases 里的 ${id} 状态是 ${c.status},应为 fail(执行记录与用例清单不一致)`);
|
|
616
|
+
}
|
|
617
|
+
if (latest?.totals?.total > cases.length) {
|
|
618
|
+
warn(`tests.json 只登记了 ${cases.length} 条用例,最近一次执行共 ${latest.totals.total} 条:还有 ${latest.totals.total - cases.length} 条没登记`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// 收口纪律:登记过用例的故事,不许漏 test 任务、不许漏验收
|
|
622
|
+
for (const s of stories) {
|
|
623
|
+
if (!registeredStories.has(s.id)) continue;
|
|
624
|
+
for (const t of s.tasks ?? []) {
|
|
625
|
+
if (t.type === 'test' && t.status === 'done' && !cases.some(c => c.taskId === t.id))
|
|
626
|
+
err(`task ${t.id}: 已 done 的 test 型任务没有登记用例(story ${s.id} 已经登记了用例,别漏这一条)`);
|
|
627
|
+
}
|
|
628
|
+
if (['review', 'done'].includes(s.status)) {
|
|
629
|
+
(s.acceptance ?? []).forEach((_, i) => {
|
|
630
|
+
const ref = `${s.id}-A${i + 1}`;
|
|
631
|
+
if (!coveredRefs.has(ref))
|
|
632
|
+
err(`story ${s.id}: 验收 ${ref} 没有任何用例覆盖(故事已到 ${s.status})`);
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
for (const c of cases) {
|
|
636
|
+
if (!c.taskId || !c.lastRun) continue;
|
|
637
|
+
const hit = taskById.get(c.taskId);
|
|
638
|
+
if (!hit || hit.story.id !== s.id) continue;
|
|
639
|
+
const passes = (journalByTask.get(c.taskId)?.entries ?? []).filter(e => /^pass/i.test(e.result ?? ''));
|
|
640
|
+
const lastPass = passes[passes.length - 1]?.date;
|
|
641
|
+
if (lastPass && c.lastRun < lastPass)
|
|
642
|
+
warn(`tests case ${c.id}: lastRun ${c.lastRun} 早于 task ${c.taskId} 最后一次通过 ${lastPass}(结果该刷新了)`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const doneStories = stories.filter(s => s.status === 'done');
|
|
647
|
+
const missing = doneStories.filter(s => !registeredStories.has(s.id));
|
|
648
|
+
if (missing.length) {
|
|
649
|
+
warn(`tests.json 只登记了 ${registeredStories.size} 个故事的用例,还有 ${missing.length} 个已完成故事完全没有用例`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
494
655
|
// ---- 输出 ----
|
|
495
656
|
for (const w of warns) console.warn(` WARN ${w}`);
|
|
496
657
|
if (errors.length === 0) {
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{D as e,E as t,Ht as n,On as r,T as i,Zn as a,_ as o,b as s,bt as c,ct as l,er as ee,g as u,it as d,k as te,mt as f,o as p,qt as m,tr as h,tt as ne,u as re,v as g,vn as _,y as v}from"./DZo3dYLJ.js";import{w as y}from"./51OWHj8c.js";import{t as ie}from"./CJ2mz0WY.js";import{t as ae}from"./rlePnzpc.js";import{O as b,i as x,s as oe}from"#entry";var S={added:`新增`,updated:`状态变更`,removed:`移除`},C=`更新交付记录`,w=`变更`,T=e=>typeof e==`string`?e.replace(/\s+/g,` `).trim():``;function se(e){let t=Array.isArray(e?.changes)?e.changes:[],n=T(e?.title)||C,r=t.some(e=>e?.action===`added`)?`feat`:`chore`,i=[];for(let e of t){let t=T(e?.id);if(!t)continue;let n=T(e.title)||T(e.note),r=S[e.action]||T(e.action)||w;e.action===`updated`&&(T(e.from)||T(e.to))&&(r+=` ${T(e.from)||`—`} → ${T(e.to)||`—`}`),i.push(`- ${t}${n?` ${n}`:``}(${r})`)}return i.length?`${r}: ${n}\n\n${i.join(`
|
|
2
|
-
`)}`:`${r}: ${n}`}var ce={class:`rule-top`},le={class:`rule-ic`},ue={class:`rule-state`},de={class:`mono`},fe={key:0},pe={class:`rule-cases`},me={class:`case-when`},he={class:`case-then`},ge={key:1,class:`lc-hist-bar`},_e={class:`hist-badge`},ve={class:`hist-readonly mono`},ye=[`title`,`disabled`,`onClick`],be={key:2,class:`card lc-empty`},xe={class:`em-ic`},Se={key:3,class:`lc`},Ce={class:`lc-hero`},we={class:`hero-body`},Te={class:`hero-kicker`},Ee={class:`k-date mono`},De={key:0,class:`k-time mono`},Oe={key:1,class:`k-today`},ke=[`title`],Ae={class:`hero-title`},je={key:0,class:`hero-sum`},Me={class:`hero-meta`},Ne={class:`hero-aside`},Pe={class:`ring-in`},Fe={class:`lc-nav`,"aria-label":`分区导航`},Ie=[`href`,`onClick`],Le={class:`lc-nav-lb`},Re={class:`lc-nav-ct`},ze={id:`lc-changes`,class:`card sec`},Be={class:`s-hd`},Ve={class:`s-ic`},He={class:`s-count`},Ue={key:0,class:`sec-empty dim`},We={key:1,class:`tbl-wrap`},Ge={class:`tbl lc-tbl`},Ke={class:`ent`},qe={class:`ent-top`},Je={key:0,class:`ent-title`},Ye={key:0,class:`flow mono`},Xe={class:`fl`},Ze={class:`fl`},Qe={class:`cell-note`},$e={class:`chips`},et=[`title`],tt={key:0,class:`dim`},nt={class:`two`},rt={id:`lc-extras`,class:`card sec`},it={class:`s-hd`},at={class:`s-ic`},ot={class:`s-count`},st={class:`sec-bd lc-extras`},ct={key:0,class:`dim`},lt={class:`x-body`},ut={class:`x-title`},dt={key:0,class:`x-detail`},ft={key:1,class:`chips x-files`},pt=[`title`],mt={id:`lc-checks`,class:`card sec`},ht={class:`s-hd`},gt={class:`s-ic tone-mint`},_t={class:`s-count`},vt={class:`sec-bd lc-checks`},yt={key:0,class:`dim`},bt={class:`v-body`},xt={class:`v-what`},St={key:0,class:`v-detail`},Ct={key:0,id:`lc-access`,class:`card sec lc-access`},wt={class:`s-hd`},Tt={class:`s-ic tone-sky`},Et={class:`s-count`},Dt={class:`sec-bd acc-grid`},Ot={key:0,class:`acc-col`},kt={class:`u-head`},At=[`href`],jt={class:`u-label`},Mt=[`onClick`],Nt={class:`u-url mono`},Pt={key:0,class:`u-note`},Ft={key:1,class:`acc-col`},It={class:`a-head`},Lt={key:0,class:`a-role`},Rt={class:`a-user mono`},zt=[`onClick`],Bt={class:`a-line`},Vt={key:0,class:`a-pass mono`},Ht={key:1,class:`a-none dim`},Ut={key:0,class:`a-note`},Wt={key:1,id:`lc-prep`,class:`card sec lc-prep`},Gt={class:`s-hd`},Kt={class:`s-ic tone-amber`},qt={class:`s-count`},Jt={class:`steps`},Yt={class:`step-bd`},Xt={class:`step-text`},Zt={key:0,class:`cmd-row`},Qt={class:`cmd mono`},$t=[`onClick`],en={key:1,class:`step-note`},tn={id:`lc-scenarios`,class:`lc-sc-sec`},nn={class:`s-hd solo`},rn={class:`s-ic tone-mint`},an={class:`s-count`},on={key:0,class:`dim sec-empty`},sn={key:1,class:`lc-sc-grid`},cn={class:`sc-hd`},ln={class:`sc-id mono`},un={class:`sc-title`},dn={key:0,class:`sc-body`},fn={class:`lc-sc-steps`},pn={class:`sc-acc`},mn={class:`sc-acc-h`},hn={class:`sc-acc-t`},gn={key:0,class:`sc-foot`},_n={key:0},vn={key:1,class:`sc-ids`},yn={class:`two`},bn={id:`lc-notes`,class:`card sec`},xn={class:`s-hd`},Sn={class:`s-ic`},Cn={class:`s-count`},wn={class:`sec-bd`},Tn={key:0,class:`dim`},En={key:1,class:`lc-notes`},Dn={key:0,id:`lc-open`,class:`card sec sec-warn`},On={class:`s-hd`},kn={class:`s-ic`},An={class:`s-count`},jn={class:`sec-bd`},Mn={class:`lc-notes warn`},Nn={class:`lc-hist`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`lc-hist-title`},Pn={class:`lc-hist-hd`},Fn={class:`lc-hist-hd-txt`},In={class:`lc-hist-sub`},Ln={key:0,class:`lc-hist-err`},Rn={key:1,class:`lc-hist-hint dim`},zn={key:2,class:`lc-hist-list`},Bn=[`disabled`,`onClick`],Vn={class:`hist-item-main`},Hn={class:`mono`},Un={class:`mono hist-item-hash`},Wn={key:0,class:`lc-hist-hint dim`},Gn={class:`lc-hist-ft`},E=x(te({__name:`last-change`,setup(te){let{lastChange:x,lastChangeGit:S}=oe(),C=_(null),w=u(()=>C.value?.doc?.lastChange??x.value),T=u(()=>S.value),E=_([]),D=_(!1),O=_(!1),k=_(``),A=_(!1),Kn=u(()=>C.value?[C.value]:[]);async function qn(){if(!O.value){O.value=!0,k.value=``;try{let e=await b(`/api/agile/last-change/history`);E.value=e.entries??[]}catch(e){k.value=e?.data?.error||e?.message||`读不到 git 历史`}finally{O.value=!1}}}async function j(){D.value=!0,E.value.length||await qn()}async function M(e){if(!A.value){A.value=!0,k.value=``;try{C.value=await b(`/api/agile/last-change/revision`,{query:{hash:e.hash}}),D.value=!1,window.scrollTo({top:0,behavior:`smooth`})}catch(e){k.value=e?.data?.error||e?.message||`取不到这个版本`}finally{A.value=!1}}}function Jn(){C.value=null,window.scrollTo({top:0,behavior:`smooth`})}let N=_(null);n(C,async()=>{await ne(),N.value?.querySelector(`.hist-tab.on`)?.scrollIntoView({inline:`nearest`,block:`nearest`})});function P(e){e.key===`Escape`&&D.value&&(D.value=!1)}l(()=>window.addEventListener(`keydown`,P)),d(()=>window.removeEventListener(`keydown`,P));let F={committed:{text:`已提交`,tone:`ok`},dirty:{text:`未提交改动`,tone:`warn`},untracked:{text:`未纳入版本库`,tone:`warn`},"no-git":{text:`非 git 仓库`,tone:`neutral`},missing:{text:`尚未生成`,tone:`neutral`}},I=u(()=>F[T.value?.state??`missing`]??F.missing),Yn=[{key:`committed`,when:`已提交`,then:`下次写入整份覆盖;上一版留在 git 历史里,点「查看历史」随时翻回来`},{key:`pending`,when:`未提交`,then:`下次写入合并:新交付接在旧记录后面,不冲掉还没入库的内容`}],Xn=u(()=>{let e=T.value?.state;return e===`committed`?`committed`:e===`dirty`||e===`untracked`||e===`no-git`?`pending`:``}),Zn=u(()=>(w.value?.mergedFrom?.length??0)+1),Qn=u(()=>(w.value?.mergedFrom??[]).map(e=>`${e.at??``} ${e.title??``}`.trim()).filter(Boolean).join(`
|
|
3
|
-
`)),$n={added:`新增`,updated:`状态变更`,removed:`移除`},er={story:`故事`,task:`任务`},tr={pass:`通过`,fail:`失败`,partial:`部分`,skipped:`未跑`},nr={pass:`i-lucide-check`,fail:`i-lucide-x`,partial:`i-lucide-minus`,skipped:`i-lucide-minus`},L=u(()=>w.value?.changes??[]),rr=u(()=>se(w.value??{})),R=u(()=>w.value?.extras??[]),z=u(()=>w.value?.verification??[]),B=u(()=>w.value?.scenarios??[]),V=u(()=>w.value?.preparation??[]),H=u(()=>w.value?.notes??[]),U=u(()=>w.value?.open??[]),W=u(()=>w.value?.access?.urls??[]),G=u(()=>w.value?.access?.accounts??[]),K=u(()=>z.value.filter(e=>e.result===`pass`).length),q=u(()=>z.value.length>0&&K.value===z.value.length),ir=u(()=>z.value.length?Math.round(K.value/z.value.length*100):0),ar=u(()=>[...new Set([...L.value.flatMap(e=>e.files??[]),...R.value.flatMap(e=>e.files??[])])]),or=e=>e.kind===`story`?e.id:e.id.replace(/-T\d+$/,``),sr=u(()=>[{id:`lc-changes`,label:`变更清单`,icon:`i-lucide-git-commit-horizontal`,count:`${L.value.length}`,tone:`violet`},{id:`lc-extras`,label:`额外做的事`,icon:`i-lucide-sparkles`,count:`${R.value.length}`,tone:`peach`},{id:`lc-checks`,label:`验证结果`,icon:`i-lucide-badge-check`,count:`${K.value}/${z.value.length}`,tone:`mint`},...W.value.length||G.value.length?[{id:`lc-access`,label:`调试入口与账号`,icon:`i-lucide-key-round`,count:`${W.value.length+G.value.length}`,tone:`sky`}]:[],...V.value.length?[{id:`lc-prep`,label:`测试前准备`,icon:`i-lucide-terminal-square`,count:`${V.value.length}`,tone:`amber`}]:[],{id:`lc-scenarios`,label:`测试场景`,icon:`i-lucide-flask-conical`,count:`${B.value.length}`,tone:`teal`},{id:`lc-notes`,label:`说明`,icon:`i-lucide-message-square-more`,count:`${H.value.length}`,tone:`slate`},...U.value.length?[{id:`lc-open`,label:`还没做的`,icon:`i-lucide-list-todo`,count:`${U.value.length}`,tone:`rose`}]:[]]);function J(e){let t=document.getElementById(e);t&&(t.scrollIntoView({behavior:`smooth`,block:`start`}),history.replaceState(null,``,`#${e}`))}let Y=new Date,cr=`${Y.getFullYear()}-${String(Y.getMonth()+1).padStart(2,`0`)}-${String(Y.getDate()).padStart(2,`0`)}`,X=u(()=>(w.value?.at??``).slice(0,10)),Z=u(()=>{let e=(w.value?.at??``).slice(10).replace(/^[T ]/,``);return/^\d{2}:\d{2}/.test(e)?e:``}),lr=u(()=>!!X.value&&X.value===cr),Q=_(``);async function $(e,t){try{await navigator.clipboard.writeText(e),Q.value=t,setTimeout(()=>{Q.value===t&&(Q.value=``)},1600)}catch{Q.value=``}}return(n,l)=>{let u=ae,d=ie;return f(),s(p,null,[r(C)?(f(),s(`div`,ge,[o(`span`,_e,[e(u,{name:`i-lucide-history`,class:`hist-badge-ic`}),l[8]||=t(`历史版本`,-1)]),o(`span`,ve,[t(`只读 · `+h(r(C).short),1),r(C).date?(f(),s(p,{key:0},[t(` · `+h(r(C).date),1)],64)):v(``,!0)]),o(`div`,{ref_key:`tabsRef`,ref:N,class:`lc-hist-tabs`},[o(`button`,{class:`hist-tab`,onClick:Jn},`最新`),(f(!0),s(p,null,c(r(Kn),e=>(f(),s(`button`,{key:e.hash,class:a([`hist-tab`,{on:r(C)?.hash===e.hash}]),title:`${e.date} ${e.subject??``}`,disabled:r(A),onClick:t=>M(e)},[t(h(e.short),1),o(`i`,null,h(e.date.slice(5,16)),1)],10,ye))),128))],512),o(`button`,{class:`rule-btn`,onClick:j},[e(u,{name:`i-lucide-list`,class:`rule-btn-ic`}),l[9]||=t(`选择历史…`,-1)])])):(f(),s(`div`,{key:0,class:a([`lc-rule`,`is-${r(I).tone}`])},[o(`div`,ce,[o(`span`,le,[e(u,{name:`i-lucide-git-branch`,class:`rule-svg`})]),l[6]||=o(`b`,{class:`rule-title`},`写入规则`,-1),l[7]||=o(`span`,{class:`rule-file mono`},`agile/last-change.json`,-1),o(`span`,ue,[o(`i`,{class:a([`rule-dot`,`tone-${r(I).tone}`])},null,2),t(` 当前 `+h(r(I).text),1),r(T)?.state===`committed`&&r(T).hash?(f(),s(p,{key:0},[l[4]||=t(` · `,-1),o(`span`,de,h(r(T).hash),1)],64)):v(``,!0)]),o(`button`,{class:`rule-btn`,onClick:j},[e(u,{name:`i-lucide-history`,class:`rule-btn-ic`}),l[5]||=t(`查看历史 `,-1),r(E).length?(f(),s(`i`,fe,h(r(E).length),1)):v(``,!0)])]),o(`ul`,pe,[(f(),s(p,null,c(Yn,t=>o(`li`,{key:t.key,class:a([`rule-case`,{on:r(Xn)===t.key}])},[o(`span`,me,h(t.when),1),e(u,{name:`i-lucide-arrow-right`,class:`case-arrow`}),o(`span`,he,h(t.then),1)],2)),64))])],2)),r(w)?(f(),s(`div`,Se,[o(`header`,Ce,[o(`div`,we,[o(`p`,Te,[l[11]||=o(`span`,{class:`k-dot`},null,-1),l[12]||=t(`最近一次交付 `,-1),l[13]||=o(`span`,{class:`k-sep`},null,-1),o(`span`,Ee,h(r(X)),1),r(Z)?(f(),s(`span`,De,h(r(Z)),1)):v(``,!0),r(lr)?(f(),s(`span`,Oe,`今天`)):v(``,!0),r(w).merged?(f(),s(`span`,{key:2,class:`k-merged`,title:r(Qn)},`合并记录 · `+h(r(Zn))+` 次交付`,9,ke)):v(``,!0)]),o(`h2`,Ae,h(r(w).title),1),r(w).summary?(f(),s(`p`,je,h(r(w).summary),1)):v(``,!0),o(`div`,Me,[o(`span`,null,[o(`b`,null,h(r(L).length),1),l[14]||=t(` 处变更`,-1)]),l[19]||=o(`i`,{class:`meta-sep`},null,-1),o(`span`,null,[o(`b`,null,h(r(ar).length),1),l[15]||=t(` 个文件`,-1)]),l[20]||=o(`i`,{class:`meta-sep`},null,-1),o(`span`,null,[o(`b`,null,h(r(B).length),1),l[16]||=t(` 条测试场景`,-1)]),r(R).length?(f(),s(p,{key:0},[l[18]||=o(`i`,{class:`meta-sep`},null,-1),o(`span`,null,[o(`b`,null,h(r(R).length),1),l[17]||=t(` 项额外工作`,-1)])],64)):v(``,!0)])]),o(`div`,Ne,[o(`div`,{class:a([`ring`,{warn:r(z).length&&!r(q)}]),style:ee({"--pct":r(ir)})},[o(`div`,Pe,[o(`b`,null,[t(h(r(K)),1),o(`small`,null,`/`+h(r(z).length||0),1)]),o(`span`,null,h(r(z).length?r(q)?`验证全通过`:`有未通过项`:`无验证记录`),1)])],6)])]),o(`nav`,Fe,[(f(!0),s(p,null,c(r(sr),t=>(f(),s(`a`,{key:t.id,class:a([`lc-nav-link`,`tone-${t.tone}`]),href:`#${t.id}`,onClick:y(e=>J(t.id),[`prevent`])},[e(u,{name:t.icon,class:`lc-nav-ic`},null,8,[`name`]),o(`span`,Le,h(t.label),1),o(`b`,Re,h(t.count),1)],10,Ie))),128))]),o(`section`,ze,[o(`div`,Be,[o(`span`,Ve,[e(u,{name:`i-lucide-git-commit-horizontal`,class:`s-svg`})]),l[21]||=o(`h2`,{class:`s-title`},`变更清单`,-1),o(`span`,He,h(r(L).length),1),l[22]||=o(`span`,{class:`s-hint`},`故事与任务各自是新增还是状态变更、动过哪些文件`,-1),r(L).length?(f(),s(`button`,{key:0,class:a([`rule-btn s-copy`,{ok:r(Q)===`commit`}]),title:`按 Conventional Commits 生成:首行是标题,正文每条变更带编号与名称`,onClick:l[0]||=e=>$(r(rr),`commit`)},[e(u,{name:r(Q)===`commit`?`i-lucide-check`:`i-lucide-clipboard-copy`,class:`rule-btn-ic`},null,8,[`name`]),t(` `+h(r(Q)===`commit`?`已复制`:`复制变更信息`),1)],2)):v(``,!0)]),r(L).length?(f(),s(`div`,We,[o(`table`,Ge,[l[24]||=o(`colgroup`,null,[o(`col`,{class:`c-ent`}),o(`col`,{class:`c-act`}),o(`col`),o(`col`,{class:`c-file`})],-1),l[25]||=o(`thead`,null,[o(`tr`,null,[o(`th`,null,`条目`),o(`th`,null,`变更`),o(`th`,null,`变更内容`),o(`th`,null,`涉及文件`)])],-1),o(`tbody`,null,[(f(!0),s(p,null,c(r(L),(n,r)=>(f(),s(`tr`,{key:`${n.kind}-${n.id}-${r}`},[o(`td`,null,[o(`div`,Ke,[o(`div`,qe,[o(`span`,{class:a([`kind`,`kind-${n.kind}`])},h(er[n.kind]??n.kind),3),e(d,{to:`/story/${or(n)}`,class:`ent-id mono`},{default:m(()=>[t(h(n.id),1)]),_:2},1032,[`to`])]),n.title?(f(),s(`p`,Je,h(n.title),1)):v(``,!0)])]),o(`td`,null,[o(`span`,{class:a([`act`,`act-${n.action}`])},h($n[n.action]??n.action),3),n.from||n.to?(f(),s(`p`,Ye,[o(`span`,Xe,h(n.from??`—`),1),l[23]||=o(`i`,null,`→`,-1),o(`span`,Ze,h(n.to??`—`),1)])):v(``,!0)]),o(`td`,Qe,h(n.note??`—`),1),o(`td`,null,[o(`div`,$e,[(f(!0),s(p,null,c(n.files,e=>(f(),s(`span`,{key:e,class:`fchip mono`,title:e},h(e),9,et))),128)),n.files?.length?v(``,!0):(f(),s(`span`,tt,`—`))])])]))),128))])])])):(f(),s(`p`,Ue,`这次没有涉及任何故事或任务。`))]),o(`div`,nt,[o(`section`,rt,[o(`div`,it,[o(`span`,at,[e(u,{name:`i-lucide-sparkles`,class:`s-svg`})]),l[26]||=o(`h2`,{class:`s-title`},`额外做的事`,-1),o(`span`,ot,h(r(R).length),1)]),o(`div`,st,[r(R).length?v(``,!0):(f(),s(`p`,ct,`没有计划外的改动。`)),(f(!0),s(p,null,c(r(R),(e,t)=>(f(),s(`article`,{key:t,class:`x-item`},[l[27]||=o(`span`,{class:`x-mark`},null,-1),o(`div`,lt,[o(`p`,ut,h(e.title),1),e.detail?(f(),s(`p`,dt,h(e.detail),1)):v(``,!0),e.files?.length?(f(),s(`div`,ft,[(f(!0),s(p,null,c(e.files,e=>(f(),s(`span`,{key:e,class:`fchip mono`,title:e},h(e),9,pt))),128))])):v(``,!0)])]))),128))])]),o(`section`,mt,[o(`div`,ht,[o(`span`,gt,[e(u,{name:`i-lucide-badge-check`,class:`s-svg`})]),l[28]||=o(`h2`,{class:`s-title`},`验证结果`,-1),o(`span`,_t,h(r(K))+`/`+h(r(z).length),1)]),o(`div`,vt,[r(z).length?v(``,!0):(f(),s(`p`,yt,`这次没有留下验证记录。`)),(f(!0),s(p,null,c(r(z),(t,n)=>(f(),s(`div`,{key:n,class:`v-item`},[o(`span`,{class:a([`v-ic`,`v-${t.result}`])},[e(u,{name:nr[t.result]??`i-lucide-circle`,class:`v-svg`},null,8,[`name`])],2),o(`div`,bt,[o(`p`,xt,h(t.what),1),t.detail?(f(),s(`p`,St,h(t.detail),1)):v(``,!0)]),o(`span`,{class:a([`v-res`,`v-${t.result}`])},h(tr[t.result]??t.result),3)]))),128))])])]),r(W).length||r(G).length?(f(),s(`section`,Ct,[o(`div`,wt,[o(`span`,Tt,[e(u,{name:`i-lucide-key-round`,class:`s-svg`})]),l[29]||=o(`h2`,{class:`s-title`},`调试入口与账号`,-1),o(`span`,Et,h(r(W).length+r(G).length),1),l[30]||=o(`span`,{class:`s-hint`},`照着这一栏就能自己跑起来点一遍`,-1)]),o(`div`,Dt,[r(W).length?(f(),s(`div`,Ot,[l[31]||=o(`p`,{class:`micro`},`访问地址`,-1),(f(!0),s(p,null,c(r(W),(n,i)=>(f(),s(`div`,{key:i,class:`u-item`},[o(`div`,kt,[o(`a`,{href:n.url,target:`_blank`,rel:`noreferrer`,class:`u-link`},[o(`span`,jt,h(n.label??`入口`),1),e(u,{name:`i-lucide-arrow-up-right`,class:`u-go`})],8,At),o(`button`,{class:a([`cpy`,{ok:r(Q)===`url-${i}`}]),onClick:e=>$(n.url,`url-${i}`)},[e(u,{name:r(Q)===`url-${i}`?`i-lucide-check`:`i-lucide-copy`,class:`cpy-ic`},null,8,[`name`]),t(` `+h(r(Q)===`url-${i}`?`已复制`:`复制`),1)],10,Mt)]),o(`p`,Nt,h(n.url),1),n.note?(f(),s(`p`,Pt,h(n.note),1)):v(``,!0)]))),128))])):v(``,!0),r(G).length?(f(),s(`div`,Ft,[l[33]||=o(`p`,{class:`micro`},`账号密码`,-1),(f(!0),s(p,null,c(r(G),(n,i)=>(f(),s(`div`,{key:i,class:`a-item`},[o(`div`,It,[n.role?(f(),s(`span`,Lt,h(n.role),1)):v(``,!0),o(`span`,Rt,h(n.username),1),o(`button`,{class:a([`cpy`,{ok:r(Q)===`acc-${i}`}]),onClick:e=>$(n.password?`账号:${n.username}\n密码:${n.password}`:n.username,`acc-${i}`)},[e(u,{name:r(Q)===`acc-${i}`?`i-lucide-check`:`i-lucide-copy`,class:`cpy-ic`},null,8,[`name`]),t(` `+h(r(Q)===`acc-${i}`?`已复制`:`复制`),1)],10,zt)]),o(`div`,Bt,[l[32]||=o(`span`,{class:`a-key`},`密码`,-1),n.password?(f(),s(`span`,Vt,h(n.password),1)):(f(),s(`span`,Ht,`无密码 / 走单点登录`))]),n.note?(f(),s(`p`,Ut,h(n.note),1)):v(``,!0)]))),128))])):v(``,!0)])])):v(``,!0),r(V).length?(f(),s(`section`,Wt,[o(`div`,Gt,[o(`span`,Kt,[e(u,{name:`i-lucide-terminal-square`,class:`s-svg`})]),l[34]||=o(`h2`,{class:`s-title`},`测试前准备`,-1),o(`span`,qt,h(r(V).length),1),l[35]||=o(`span`,{class:`s-hint`},`按顺序做完这些再开始点,命令可直接复制`,-1)]),o(`ol`,Jt,[(f(!0),s(p,null,c(r(V),(n,i)=>(f(),s(`li`,{key:i},[o(`div`,Yt,[o(`p`,Xt,h(n.step),1),n.command?(f(),s(`div`,Zt,[o(`code`,Qt,h(n.command),1),o(`button`,{class:a([`cpy`,{ok:r(Q)===`cmd-${i}`}]),onClick:e=>$(n.command??``,`cmd-${i}`)},[e(u,{name:r(Q)===`cmd-${i}`?`i-lucide-check`:`i-lucide-copy`,class:`cpy-ic`},null,8,[`name`]),t(` `+h(r(Q)===`cmd-${i}`?`已复制`:`复制`),1)],10,$t)])):v(``,!0),n.note?(f(),s(`p`,en,h(n.note),1)):v(``,!0)])]))),128))])])):v(``,!0),o(`section`,tn,[o(`div`,nn,[o(`span`,rn,[e(u,{name:`i-lucide-flask-conical`,class:`s-svg`})]),l[36]||=o(`h2`,{class:`s-title`},`测试场景`,-1),o(`span`,an,h(r(B).length),1),l[37]||=o(`span`,{class:`s-hint`},`一条一段:照着步骤走,末尾那句就是验收标准`,-1)]),r(B).length?(f(),s(`div`,sn,[(f(!0),s(p,null,c(r(B),(n,r)=>(f(),s(`article`,{key:n.id??r,class:`sc lc-sc`},[o(`header`,cn,[o(`span`,ln,h(n.id??`TC-${r+1}`),1),o(`h3`,un,h(n.title),1)]),n.steps?.length?(f(),s(`div`,dn,[l[38]||=o(`p`,{class:`micro`},`测试步骤`,-1),o(`ol`,fn,[(f(!0),s(p,null,c(n.steps,(e,t)=>(f(),s(`li`,{key:t},h(e),1))),128))])])):v(``,!0),o(`div`,pn,[o(`p`,mn,[e(u,{name:`i-lucide-badge-check`,class:`sc-acc-ic`}),l[39]||=t(`验收标准`,-1)]),o(`p`,hn,h(n.expect),1),n.acceptance||n.storyIds?.length?(f(),s(`div`,gn,[n.acceptance?(f(),s(`span`,_n,`对应验收:`+h(n.acceptance),1)):v(``,!0),n.storyIds?.length?(f(),s(`span`,vn,[(f(!0),s(p,null,c(n.storyIds,e=>(f(),g(d,{key:e,to:`/story/${e}`,class:`ent-id mono`},{default:m(()=>[t(h(e),1)]),_:2},1032,[`to`]))),128))])):v(``,!0)])):v(``,!0)])]))),128))])):(f(),s(`p`,on,`这次没有单独列测试场景。`))]),o(`div`,yn,[o(`section`,bn,[o(`div`,xn,[o(`span`,Sn,[e(u,{name:`i-lucide-message-square-more`,class:`s-svg`})]),l[40]||=o(`h2`,{class:`s-title`},`说明`,-1),o(`span`,Cn,h(r(H).length),1)]),o(`div`,wn,[r(H).length?(f(),s(`ul`,En,[(f(!0),s(p,null,c(r(H),(e,t)=>(f(),s(`li`,{key:t},h(e),1))),128))])):(f(),s(`p`,Tn,`没有额外说明。`))])]),r(U).length?(f(),s(`section`,Dn,[o(`div`,On,[o(`span`,kn,[e(u,{name:`i-lucide-list-todo`,class:`s-svg`})]),l[41]||=o(`h2`,{class:`s-title`},`还没做的`,-1),o(`span`,An,h(r(U).length),1)]),o(`div`,jn,[o(`ul`,Mn,[(f(!0),s(p,null,c(r(U),(e,t)=>(f(),s(`li`,{key:t},h(e),1))),128))])])])):v(``,!0)])])):(f(),s(`div`,be,[o(`span`,xe,[e(u,{name:`i-lucide-clipboard-list`,class:`em-svg`})]),l[10]||=i(`<h2 class="em-title" data-v-0bbfea97>还没有交付记录</h2><p class="em-sub" data-v-0bbfea97> 每完成一项功能,AI 都要把「改了哪些故事与任务、额外做了什么、验了什么、怎么测」 写进下面这个文件,这一页就会自动排出来。 </p><p class="em-path mono" data-v-0bbfea97>agile/last-change.json</p><p class="em-note" data-v-0bbfea97> 每次只保留最近一次(覆盖写,不追加)· 格式见 <span class="mono" data-v-0bbfea97>.agents/skills/scrum-planning/references/schema.md</span> 第 4 节 </p>`,4)])),(f(),g(re,{to:`body`},[r(D)?(f(),s(`div`,{key:0,class:`lc-hist-mask`,onClick:l[3]||=y(e=>D.value=!1,[`self`])},[o(`div`,Nn,[o(`header`,Pn,[o(`div`,Fn,[l[45]||=o(`h3`,{id:`lc-hist-title`,class:`lc-hist-title`},`历史版本`,-1),o(`p`,In,[l[42]||=t(` 从 git 里读 `,-1),l[43]||=o(`span`,{class:`mono`},`agile/last-change.json`,-1),l[44]||=t(` 的提交记录 `,-1),r(E).length?(f(),s(p,{key:0},[t(`· 共 `+h(r(E).length)+` 版`,1)],64)):v(``,!0)])]),o(`button`,{class:`lc-hist-x`,"aria-label":`关闭`,onClick:l[1]||=e=>D.value=!1},[e(u,{name:`i-lucide-x`,class:`lc-hist-x-ic`})])]),r(k)?(f(),s(`p`,Ln,h(r(k)),1)):r(O)?(f(),s(`div`,Rn,`正在读 git 历史…`)):(f(),s(`div`,zn,[(f(!0),s(p,null,c(r(E),(e,t)=>(f(),s(`button`,{key:e.hash,class:a([`hist-item`,{on:r(C)?.hash===e.hash}]),disabled:r(A),onClick:t=>M(e)},[o(`span`,{class:a([`hist-item-dot`,{live:t===0}])},null,2),o(`span`,Vn,[o(`b`,null,h(e.subject||`(没有提交说明)`),1),o(`i`,Hn,h(e.date),1)]),o(`span`,Un,h(e.short),1)],10,Bn))),128)),r(E).length?v(``,!0):(f(),s(`p`,Wn,` 这个文件还没有提交记录 —— 先 commit 一次,历史里才会出现版本。 `))])),o(`footer`,Gn,[l[46]||=o(`span`,{class:`dim`},`选一个版本即可在页面上只读查看,不会改动文件。`,-1),o(`button`,{class:`hist-close`,onClick:l[2]||=e=>D.value=!1},`关闭`)])])])):v(``,!0)]))],64)}}}),[[`__scopeId`,`data-v-0bbfea97`]]);export{E as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"d7f0931f-32b3-4c23-b05f-7cbf5851b226","timestamp":1793369473533,"prerendered":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.lc[data-v-0bbfea97]{gap:14px;display:grid}.lc [id^=lc-][data-v-0bbfea97]{scroll-margin-top:14px}.lc-nav[data-v-0bbfea97]{flex-wrap:wrap;gap:6px;padding:0 2px;display:flex}.lc-nav-link[data-v-0bbfea97]{border:1px solid var(--nv-line,var(--line));background:var(--nv-bg,#fff);height:28px;color:var(--nv-ink,var(--ink-3));border-radius:999px;align-items:center;gap:6px;padding:0 9px;font-size:11.4px;font-weight:580;transition:box-shadow .16s,transform .16s;display:inline-flex}.lc-nav-link.tone-violet[data-v-0bbfea97]{--nv-bg:var(--violet-soft);--nv-line:var(--violet-line);--nv-ink:var(--violet-ink)}.lc-nav-link.tone-peach[data-v-0bbfea97]{--nv-bg:var(--peach);--nv-line:var(--peach-line);--nv-ink:#a5382f}.lc-nav-link.tone-mint[data-v-0bbfea97]{--nv-bg:#f0fbdf;--nv-line:#dcf0bb;--nv-ink:#4f6f24}.lc-nav-link.tone-sky[data-v-0bbfea97]{--nv-bg:#eef5fd;--nv-line:var(--sky-line);--nv-ink:#2c5f96}.lc-nav-link.tone-amber[data-v-0bbfea97]{--nv-bg:#fdf4e2;--nv-line:var(--amber-line);--nv-ink:#8a6116}.lc-nav-link.tone-teal[data-v-0bbfea97]{--nv-bg:#e4f6f3;--nv-line:#bce5de;--nv-ink:#1f6f63}.lc-nav-link.tone-slate[data-v-0bbfea97]{--nv-bg:var(--sunken);--nv-line:var(--line-2);--nv-ink:var(--ink-2)}.lc-nav-link.tone-rose[data-v-0bbfea97]{--nv-bg:#fdeceb;--nv-line:var(--rose-line);--nv-ink:#b23a4b}.lc-nav-link[data-v-0bbfea97]:hover{box-shadow:0 7px 16px -12px var(--nv-ink,#8a8a8a);transform:translateY(-1px)}.lc-nav-ic[data-v-0bbfea97]{opacity:.72;flex:none;width:13px;height:13px}.act[data-v-0bbfea97],.ent-id[data-v-0bbfea97],.fchip[data-v-0bbfea97],.fl[data-v-0bbfea97],.lc-nav-lb[data-v-0bbfea97],.ring-in span[data-v-0bbfea97],.rule-state[data-v-0bbfea97],.s-title[data-v-0bbfea97],.u-url[data-v-0bbfea97]{white-space:nowrap}.lc-nav-ct[data-v-0bbfea97]{min-width:16px;color:inherit;text-align:center;font-variant-numeric:tabular-nums;background:#ffffffd1;border-radius:999px;flex:none;padding:0 5px;font-size:9.6px;font-weight:700;line-height:15px}.lc-nav-link:hover .lc-nav-ct[data-v-0bbfea97]{background:#fff}.lc-rule[data-v-0bbfea97]{border:1px solid var(--line);box-shadow:var(--shadow-1);background:linear-gradient(#fbfaff,#fff);border-radius:14px;gap:7px;margin-bottom:14px;padding:10px 13px 11px;display:grid}.rule-top[data-v-0bbfea97]{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.rule-ic[data-v-0bbfea97]{border:1px solid var(--violet-line);background:var(--violet-soft);width:26px;height:26px;color:var(--violet-ink);border-radius:9px;flex:none;place-items:center;display:grid}.rule-svg[data-v-0bbfea97]{width:14px;height:14px}.rule-title[data-v-0bbfea97]{letter-spacing:.01em;color:var(--ink);font-size:12px;font-weight:680}.rule-file[data-v-0bbfea97]{color:var(--ink-4);font-size:10px}.rule-state[data-v-0bbfea97]{border:1px solid var(--line-2);height:22px;color:var(--ink-2);background:#fff;border-radius:999px;align-items:center;gap:5px;margin-left:auto;padding:0 9px;font-size:10.4px;font-weight:640;display:inline-flex}.rule-state .mono[data-v-0bbfea97]{color:var(--ink-3);font-size:9.8px;font-weight:560}.rule-dot[data-v-0bbfea97]{background:var(--ink-4);border-radius:999px;flex:none;width:6px;height:6px}.rule-dot.tone-ok[data-v-0bbfea97]{background:#74a53c;box-shadow:0 0 0 3px #74a53c24}.rule-dot.tone-warn[data-v-0bbfea97]{background:#d79a2b;box-shadow:0 0 0 3px #d79a2b24}.rule-dot.tone-neutral[data-v-0bbfea97]{background:#b3b1aa;box-shadow:0 0 0 3px #b3b1aa24}.rule-cases[data-v-0bbfea97]{gap:3px;margin:0;padding:0;list-style:none;display:grid}.rule-case[data-v-0bbfea97]{color:var(--ink-3);border:1px solid #0000;border-radius:9px;align-items:baseline;gap:7px;padding:4px 9px;font-size:11.2px;line-height:1.55;display:flex}.rule-case.on[data-v-0bbfea97]{color:var(--ink-2);background:var(--sunken)}.case-when[data-v-0bbfea97]{flex:none;min-width:40px;font-weight:680}.rule-case.on .case-when[data-v-0bbfea97]{color:var(--ink)}.case-arrow[data-v-0bbfea97]{width:11px;height:11px;color:var(--ink-4);flex:none;align-self:center}.rule-case.on .case-arrow[data-v-0bbfea97]{color:var(--ink-3)}.case-then[data-v-0bbfea97],.hero-body[data-v-0bbfea97],.two[data-v-0bbfea97]>*{min-width:0}.is-ok .rule-ic[data-v-0bbfea97]{color:#4f6f24;background:#f0fbdf;border-color:#dcf0bb}.is-ok .rule-case.on[data-v-0bbfea97]{background:#f4fbea;border-color:#dcf0bb}.is-warn .rule-ic[data-v-0bbfea97]{color:#8a6116;background:#fdf4e2;border-color:#f3e2b8}.is-warn .rule-case.on[data-v-0bbfea97]{background:#fdf6e8;border-color:#f3e2b8}.sec[data-v-0bbfea97]{border-radius:20px;overflow:hidden}.two[data-v-0bbfea97]{flex-wrap:wrap;align-items:stretch;gap:14px;display:flex}.two[data-v-0bbfea97]>*{flex:336px}.s-hd[data-v-0bbfea97]{align-items:center;gap:9px;padding:13px 18px 12px;display:flex}.s-hd.solo[data-v-0bbfea97]{padding:2px 2px 0}.s-ic[data-v-0bbfea97]{border:1px solid var(--violet-line);background:var(--violet-soft);width:26px;height:26px;color:var(--violet-ink);border-radius:9px;flex:none;place-items:center;display:grid}.s-ic.tone-mint[data-v-0bbfea97]{color:#4f6f24;background:#f0fbdf;border-color:#dcf0bb}.s-ic.tone-amber[data-v-0bbfea97]{color:#8a6116;background:#fdf4e2;border-color:#f3e2b8}.s-ic.tone-sky[data-v-0bbfea97]{color:#2c5f96;background:#eef5fd;border-color:#d7e6f9}.s-svg[data-v-0bbfea97]{width:14px;height:14px}.s-title[data-v-0bbfea97]{letter-spacing:-.012em;font-size:14px;font-weight:650}.s-count[data-v-0bbfea97]{border:1px solid var(--line-2);background:var(--sunken);color:var(--ink-3);border-radius:999px;flex:none;padding:0 6px;font-size:10px;font-weight:660;line-height:16px}.s-hint[data-v-0bbfea97]{color:var(--ink-4);text-align:right;margin-left:auto;font-size:11px}.s-copy[data-v-0bbfea97]{margin-left:2px}.s-copy.ok[data-v-0bbfea97]{color:#47762b;background:#e9f8dd;border-color:#d5efc2}.sec-bd[data-v-0bbfea97],.sec-empty[data-v-0bbfea97]{padding:0 18px 16px}.sec-empty[data-v-0bbfea97]{font-size:12px}.micro[data-v-0bbfea97]{letter-spacing:.1em;text-transform:uppercase;color:var(--ink-4);font-size:9.6px;font-weight:660}.lc-hero[data-v-0bbfea97]{isolation:isolate;background:radial-gradient(120% 150% at 0 0,#fdfbff,#fff0 52%),radial-gradient(100% 140% at 100% 110%,#ece1ff,#ece1ff00 60%),linear-gradient(152deg,#f8f4ff,#f1eaff 64%,#faf7ff);border:1px solid #e6dcfb;border-radius:24px;align-items:center;gap:26px;padding:20px 22px;display:flex;position:relative;overflow:hidden}.lc-hero[data-v-0bbfea97]:after{content:"";filter:blur(6px);z-index:-1;background:radial-gradient(circle,#8b68f229,#8b68f200 70%);border-radius:50%;width:240px;height:240px;position:absolute;bottom:-90px;right:-70px}.hero-body[data-v-0bbfea97]{flex:1}.hero-kicker[data-v-0bbfea97]{letter-spacing:.1em;text-transform:uppercase;color:#7a63b8;align-items:center;gap:8px;font-size:10px;font-weight:680;display:flex}.k-dot[data-v-0bbfea97]{background:var(--violet);border-radius:999px;width:6px;height:6px;box-shadow:0 0 0 3px #6d4ff024}.k-sep[data-v-0bbfea97]{background:#dbd0f6;width:1px;height:10px}.k-date[data-v-0bbfea97]{text-transform:none;color:#6f5aa8;font-size:10.6px;font-weight:600}.k-date[data-v-0bbfea97],.k-time[data-v-0bbfea97]{letter-spacing:.02em}.k-time[data-v-0bbfea97]{color:var(--ink-3);font-variant-numeric:tabular-nums;align-items:center;gap:5px;font-size:10.4px;font-weight:620;display:inline-flex}.k-time[data-v-0bbfea97]:before{content:"";background:#c9bce6;border-radius:999px;width:3px;height:3px}.k-today[data-v-0bbfea97]{border:1px solid var(--violet-line);letter-spacing:.06em;color:var(--violet-ink);background:#fff}.k-merged[data-v-0bbfea97],.k-today[data-v-0bbfea97]{border-radius:999px;padding:0 6px;font-size:9.4px;line-height:15px}.k-merged[data-v-0bbfea97]{color:#8a6116;letter-spacing:.03em;text-transform:none;cursor:help;background:#fdf4e2;border:1px solid #f3e2b8}.hero-title[data-v-0bbfea97]{letter-spacing:-.026em;color:#241b3f;margin:9px 0 0;font-size:21px;font-weight:665;line-height:1.34}.hero-sum[data-v-0bbfea97]{color:#574c72;max-width:92ch;margin-top:7px;font-size:12.8px;line-height:1.68}.hero-meta[data-v-0bbfea97]{color:#6b5f85;flex-wrap:wrap;align-items:center;gap:11px;margin-top:13px;font-size:11.4px;display:flex}.hero-meta b[data-v-0bbfea97]{color:#2c2246;margin-right:2px;font-weight:690}.meta-sep[data-v-0bbfea97]{background:#c3b4e2;border-radius:999px;width:3px;height:3px}.hero-aside[data-v-0bbfea97]{flex:none;justify-items:center;display:grid}.ring[data-v-0bbfea97]{--pct:0;background:conic-gradient(from -90deg,#7f5cf2 calc(var(--pct) * 1%),#e7dffa 0);border-radius:50%;place-items:center;width:98px;height:98px;display:grid;box-shadow:0 10px 26px -18px #5836ba8c}.ring.warn[data-v-0bbfea97]{background:conic-gradient(from -90deg,#dfa437 calc(var(--pct) * 1%),#f4e8d2 0)}.ring-in[data-v-0bbfea97]{text-align:center;background:#fff;border-radius:50%;place-content:center;gap:1px;width:78px;height:78px;display:grid}.ring-in b[data-v-0bbfea97]{letter-spacing:-.03em;color:#2a1f47;font-size:17px;font-weight:680;line-height:1.15}.ring-in b small[data-v-0bbfea97]{letter-spacing:0;color:var(--ink-4);font-size:10.5px;font-weight:600}.ring-in span[data-v-0bbfea97]{letter-spacing:.04em;color:var(--ink-3);font-size:9.2px}.tbl-wrap[data-v-0bbfea97]{padding:0 8px 8px;overflow-x:auto}.lc-tbl[data-v-0bbfea97]{min-width:640px}.lc-tbl .c-ent[data-v-0bbfea97]{width:246px}.lc-tbl .c-act[data-v-0bbfea97]{width:92px}.lc-tbl .c-file[data-v-0bbfea97]{width:218px}.lc-tbl thead th[data-v-0bbfea97]{border-bottom:1px solid var(--line);letter-spacing:.1em;padding:6px 12px 8px;font-size:9.4px;font-weight:660}.lc-tbl tbody tr[data-v-0bbfea97]{cursor:default}.lc-tbl tbody td[data-v-0bbfea97]{border-top:0;border-bottom:1px solid var(--line);vertical-align:top;padding:11px 12px 12px}.lc-tbl tbody tr:last-child td[data-v-0bbfea97]{border-bottom:0}.lc-tbl tbody tr:hover td[data-v-0bbfea97]{background:#fbfaff}.ent-top[data-v-0bbfea97]{align-items:center;gap:6px;display:flex}.kind[data-v-0bbfea97]{letter-spacing:.03em;border-radius:5px;flex:none;padding:0 5px;font-size:9.4px;font-weight:680;line-height:15px}.kind-story[data-v-0bbfea97]{color:#5433cf;background:#efe9fe}.kind-task[data-v-0bbfea97]{color:#6b6f77;background:#f1f1ee}.ent-id[data-v-0bbfea97]{color:#6d5aa8;font-size:10.6px}.ent-id[data-v-0bbfea97]:hover{color:var(--violet-ink);text-underline-offset:2px;text-decoration:underline}.ent-title[data-v-0bbfea97]{color:var(--ink);margin-top:4px;font-size:12.5px;font-weight:590;line-height:1.5}.act[data-v-0bbfea97]{border-radius:999px;padding:0 7px;font-size:10px;font-weight:660;line-height:17px;display:inline-block}.act-added[data-v-0bbfea97]{color:#46742a;background:#e9f8dd}.act-updated[data-v-0bbfea97]{color:#2a5f9e;background:#e8f1fd}.act-removed[data-v-0bbfea97]{color:#a83a32;background:#fdeae9}.flow[data-v-0bbfea97]{color:var(--ink-3);flex-wrap:wrap;align-items:center;gap:3px;margin-top:5px;font-size:10px;line-height:1.4;display:flex}.flow i[data-v-0bbfea97]{color:var(--ink-4);font-style:normal}.cell-note[data-v-0bbfea97]{color:var(--ink-2);font-size:12.1px;line-height:1.62}.chips[data-v-0bbfea97]{flex-wrap:wrap;gap:4px;display:flex}.fchip[data-v-0bbfea97]{border:1px solid var(--line-2);background:var(--sunken);max-width:100%;color:var(--ink-2);text-overflow:ellipsis;border-radius:5px;padding:0 5px;font-size:9.9px;line-height:16px;overflow:hidden}.lc-extras[data-v-0bbfea97]{display:grid}.x-item[data-v-0bbfea97]{border-top:1px dashed var(--line-2);gap:9px;padding:11px 0;display:flex}.x-item[data-v-0bbfea97]:first-child{border-top:0;padding-top:0}.x-mark[data-v-0bbfea97]{background:#b9a5f5;border-radius:999px;flex:none;width:5px;height:5px;margin-top:7px;box-shadow:0 0 0 3px #b9a5f529}.step-bd[data-v-0bbfea97],.u-link[data-v-0bbfea97],.v-body[data-v-0bbfea97],.x-body[data-v-0bbfea97]{min-width:0}.x-title[data-v-0bbfea97]{font-size:12.4px;font-weight:600;line-height:1.55}.x-detail[data-v-0bbfea97]{color:var(--ink-2);margin-top:3px;font-size:11.8px;line-height:1.62}.x-files[data-v-0bbfea97]{margin-top:6px}.lc-checks[data-v-0bbfea97]{display:grid}.v-item[data-v-0bbfea97]{border-top:1px dashed var(--line-2);align-items:flex-start;gap:9px;padding:10px 0;display:flex}.v-item[data-v-0bbfea97]:first-child{border-top:0;padding-top:0}.v-ic[data-v-0bbfea97]{border-radius:7px;flex:none;place-items:center;width:20px;height:20px;margin-top:1px;display:grid}.u-go[data-v-0bbfea97],.v-svg[data-v-0bbfea97]{width:12px;height:12px}.v-ic.v-pass[data-v-0bbfea97]{color:#4b7a2c;background:#e9f8dd}.v-ic.v-fail[data-v-0bbfea97]{color:#ab3a32;background:#fdeae9}.v-ic.v-partial[data-v-0bbfea97]{color:#8a6116;background:#fdf3dd}.v-ic.v-skipped[data-v-0bbfea97]{color:#82868e;background:#f1f1ee}.v-body[data-v-0bbfea97]{flex:1}.v-what[data-v-0bbfea97]{word-break:break-word;font-size:12.2px;font-weight:590;line-height:1.5}.v-detail[data-v-0bbfea97]{color:var(--ink-3);margin-top:2px;font-size:11.4px;line-height:1.55}.v-res[data-v-0bbfea97]{letter-spacing:.02em;flex:none;margin-top:2px;font-size:10.2px;font-weight:660}.v-res.v-pass[data-v-0bbfea97]{color:#4b7a2c}.v-res.v-fail[data-v-0bbfea97]{color:#ab3a32}.v-res.v-partial[data-v-0bbfea97]{color:#8a6116}.v-res.v-skipped[data-v-0bbfea97]{color:#82868e}.acc-grid[data-v-0bbfea97]{grid-template-columns:repeat(auto-fit,minmax(320px,1fr));align-items:start;gap:16px 22px;display:grid}.acc-col[data-v-0bbfea97]{align-content:start;gap:9px;display:grid}.a-item[data-v-0bbfea97],.u-item[data-v-0bbfea97]{border-radius:var(--r-md);border:1px solid var(--line);background:var(--sunken-2);padding:10px 12px;transition:border-color .14s,background .14s}.a-item[data-v-0bbfea97]:hover,.u-item[data-v-0bbfea97]:hover{border-color:var(--line-3);background:#fff}.a-head[data-v-0bbfea97],.u-head[data-v-0bbfea97]{align-items:center;gap:6px;display:flex}.u-link[data-v-0bbfea97]{align-items:center;gap:4px;display:inline-flex}.u-label[data-v-0bbfea97]{font-size:12.2px;font-weight:640}.u-go[data-v-0bbfea97]{color:var(--violet);flex:none;transition:transform .14s}.u-link:hover .u-go[data-v-0bbfea97]{transform:translate(1px,-1px)}.u-link:hover .u-label[data-v-0bbfea97]{color:var(--violet-ink)}.u-url[data-v-0bbfea97]{color:var(--sky);text-overflow:ellipsis;margin-top:3px;font-size:11.3px;overflow:hidden}.u-note[data-v-0bbfea97]{color:var(--ink-3);margin-top:3px;font-size:10.9px;line-height:1.5}.a-role[data-v-0bbfea97]{border:1px solid var(--violet-line);background:var(--violet-soft);color:var(--violet-ink);letter-spacing:.03em;border-radius:999px;padding:0 6px;font-size:9.6px;font-weight:680;line-height:16px}.a-user[data-v-0bbfea97]{color:var(--ink);font-size:11.8px}.a-line[data-v-0bbfea97]{align-items:center;gap:8px;margin-top:6px;display:flex}.a-key[data-v-0bbfea97]{letter-spacing:.1em;text-transform:uppercase;color:var(--ink-4);font-size:9.4px;font-weight:660}.a-pass[data-v-0bbfea97]{border:1px solid var(--line-2);color:var(--ink);background:#fff;border-radius:6px;padding:1px 7px;font-size:11.4px}.a-none[data-v-0bbfea97]{font-size:11.2px}.a-note[data-v-0bbfea97]{margin-top:5px;font-size:10.9px}.a-note[data-v-0bbfea97],.cpy[data-v-0bbfea97]{color:var(--ink-3)}.cpy[data-v-0bbfea97]{cursor:pointer;background:0 0;border:1px solid #0000;border-radius:7px;flex:none;align-items:center;gap:4px;height:23px;margin-left:auto;padding:0 8px;font-size:10.6px;font-weight:600;transition:background .14s,border-color .14s,color .14s;display:inline-flex}.cpy[data-v-0bbfea97]:hover{border-color:var(--line-2);color:var(--ink);background:#fff}.cpy.ok[data-v-0bbfea97]{color:#47762b;background:#e9f8dd;border-color:#d5efc2}.cpy-ic[data-v-0bbfea97]{width:11px;height:11px}.steps[data-v-0bbfea97]{counter-reset:st;gap:13px;margin:0;padding:2px 18px 16px;list-style:none;display:grid}.steps>li[data-v-0bbfea97]{counter-increment:st;padding-left:33px;position:relative}.steps>li[data-v-0bbfea97]:before{content:counter(st);border:1px solid var(--violet-line);width:22px;height:22px;color:var(--violet-ink);background:#fff;border-radius:999px;place-items:center;font-size:10.4px;font-weight:700;display:grid;position:absolute;top:0;left:0}.steps>li[data-v-0bbfea97]:not(:last-child):after{content:"";background:linear-gradient(#ded2fb,#efeaf9);width:1px;position:absolute;top:26px;bottom:-13px;left:10.5px}.step-text[data-v-0bbfea97]{padding-top:1px;font-size:12.5px;font-weight:590;line-height:1.5}.cmd-row[data-v-0bbfea97]{flex-wrap:wrap;align-items:center;gap:8px;margin-top:6px;display:flex}.cmd[data-v-0bbfea97]{color:#f4f3ef;white-space:pre-wrap;word-break:break-all;background:#1b1c20;border-radius:7px;padding:4px 9px;font-size:11.1px;line-height:1.5}.cmd-row .cpy[data-v-0bbfea97]{margin-left:0}.step-note[data-v-0bbfea97]{color:var(--ink-3);margin-top:4px;font-size:11.2px;line-height:1.55}.lc-sc-sec[data-v-0bbfea97]{gap:11px;display:grid}.lc-sc-grid[data-v-0bbfea97]{flex-wrap:wrap;align-items:stretch;gap:12px;display:flex}.lc-sc-grid[data-v-0bbfea97]>*{flex:330px;min-width:0}.sc[data-v-0bbfea97]{border-radius:var(--r-lg);border:1px solid var(--line);box-shadow:var(--shadow-1);background:#fff;flex-direction:column;transition:transform .16s,box-shadow .16s,border-color .16s;display:flex;overflow:hidden}.sc[data-v-0bbfea97]:hover{border-color:#e3d9f8;transform:translateY(-1px);box-shadow:0 16px 34px -26px #231a3d73}.sc-hd[data-v-0bbfea97]{align-items:flex-start;gap:8px;padding:12px 14px 9px;display:flex}.sc-id[data-v-0bbfea97]{border:1px solid var(--line-2);background:var(--sunken);color:var(--ink-3);border-radius:5px;flex:none;margin-top:1px;padding:0 5px;font-size:9.6px;font-weight:660;line-height:16px}.sc-title[data-v-0bbfea97]{letter-spacing:-.008em;font-size:12.9px;font-weight:630;line-height:1.45}.sc-body[data-v-0bbfea97]{padding:0 14px 10px}.lc-sc-steps[data-v-0bbfea97]{counter-reset:ss;gap:5px;margin:6px 0 0;padding:0;list-style:none;display:grid}.lc-sc-steps li[data-v-0bbfea97]{counter-increment:ss;color:var(--ink-2);padding-left:19px;font-size:12px;line-height:1.55;position:relative}.lc-sc-steps li[data-v-0bbfea97]:before{content:counter(ss) ".";color:#a58cf8;font-variant-numeric:tabular-nums;font-size:10.6px;font-weight:680;position:absolute;top:0;left:0}.sc-acc[data-v-0bbfea97]{border-top:1px dashed var(--line-2);background:linear-gradient(#fbfdf6,#fff);margin-top:auto;padding:10px 14px 12px}.sc-acc-h[data-v-0bbfea97]{letter-spacing:.1em;text-transform:uppercase;color:#6b7a45;align-items:center;gap:4px;font-size:9.4px;font-weight:700;display:flex}.sc-acc-ic[data-v-0bbfea97]{flex:none;width:11px;height:11px}.sc-acc-t[data-v-0bbfea97]{color:#33401c;margin-top:4px;font-size:12px;line-height:1.62}.sc-foot[data-v-0bbfea97]{color:var(--ink-4);flex-wrap:wrap;align-items:center;gap:10px;margin-top:7px;font-size:10.6px;display:flex}.sc-ids[data-v-0bbfea97]{gap:6px;display:flex}.lc-notes[data-v-0bbfea97]{gap:8px;margin:0;padding:0;list-style:none;display:grid}.lc-notes li[data-v-0bbfea97]{color:var(--ink-2);padding-left:13px;font-size:12.1px;line-height:1.66;position:relative}.lc-notes li[data-v-0bbfea97]:before{content:"";background:#c3b2f7;border-radius:999px;width:4.5px;height:4.5px;position:absolute;top:7.5px;left:1px}.lc-notes.warn li[data-v-0bbfea97]:before{background:#e8bb5e}.sec-warn .s-ic[data-v-0bbfea97]{color:#8a6116;background:#fdf4e2;border-color:#f3e2b8}.lc-empty[data-v-0bbfea97]{text-align:center;border-radius:24px;justify-items:center;gap:9px;padding:52px 24px 46px;display:grid}.em-ic[data-v-0bbfea97]{background:var(--grad-lilac);width:48px;height:48px;color:var(--violet-ink);border:1px solid #dbcdfb;border-radius:16px;place-items:center;display:grid}.em-svg[data-v-0bbfea97]{width:21px;height:21px}.em-title[data-v-0bbfea97]{letter-spacing:-.02em;margin-top:2px;font-size:16px;font-weight:660}.em-sub[data-v-0bbfea97]{max-width:54ch;font-size:12.6px;line-height:1.7}.em-path[data-v-0bbfea97],.em-sub[data-v-0bbfea97]{color:var(--ink-2)}.em-path[data-v-0bbfea97]{border:1px dashed var(--line-3);background:var(--sunken);border-radius:999px;margin-top:3px;padding:4px 12px;font-size:11.4px}.em-note[data-v-0bbfea97]{color:var(--ink-4);font-size:11px}.rule-btn[data-v-0bbfea97]{border:1px solid var(--line-2);height:24px;color:var(--ink-2);cursor:pointer;background:#fff;border-radius:999px;flex:none;align-items:center;gap:4px;padding:0 9px;font-size:10.8px;font-weight:620;transition:border-color .14s,color .14s,background .14s;display:inline-flex}.rule-btn[data-v-0bbfea97]:hover{border-color:var(--violet-line);background:var(--violet-soft);color:var(--violet-ink)}.rule-btn i[data-v-0bbfea97]{background:var(--sunken);color:var(--ink-3);border-radius:999px;padding:0 4px;font-size:9.4px;font-style:normal;line-height:15px}.hist-badge-ic[data-v-0bbfea97],.rule-btn-ic[data-v-0bbfea97]{width:12px;height:12px}.lc-hist-bar[data-v-0bbfea97]{box-shadow:var(--shadow-1);background:linear-gradient(#faf7ff,#fff);border-radius:14px;flex-wrap:wrap;gap:10px;margin-bottom:14px;padding:9px 12px;display:flex}.hist-badge[data-v-0bbfea97],.lc-hist-bar[data-v-0bbfea97]{border:1px solid var(--violet-line);align-items:center}.hist-badge[data-v-0bbfea97]{background:var(--violet-soft);color:var(--violet-ink);border-radius:999px;flex:none;gap:4px;padding:1px 8px;font-size:10.4px;font-weight:680;line-height:18px;display:inline-flex}.hist-readonly[data-v-0bbfea97]{color:var(--ink-4);flex:none;font-size:10.2px}.lc-hist-tabs[data-v-0bbfea97]{flex:1;align-items:center;gap:4px;min-width:0;padding:1px 0;display:flex;overflow-x:auto}.lc-hist-tabs[data-v-0bbfea97]::-webkit-scrollbar{height:3px}.lc-hist-tabs[data-v-0bbfea97]::-webkit-scrollbar-thumb{background:#dcd8ef;border-radius:999px}.hist-tab[data-v-0bbfea97]{border:1px solid var(--line-2);height:24px;color:var(--ink-2);cursor:pointer;white-space:nowrap;background:#fff;border-radius:999px;flex:none;align-items:center;gap:4px;padding:0 9px;font-size:10.6px;font-weight:620;transition:background .14s,border-color .14s,color .14s;display:inline-flex}.hist-tab[data-v-0bbfea97]:hover{border-color:var(--violet-line);color:var(--violet-ink)}.hist-tab.on[data-v-0bbfea97]{border-color:var(--violet);background:var(--violet-soft);color:var(--violet-ink);font-weight:680}.hist-tab i[data-v-0bbfea97]{color:var(--ink-4);font-size:9.2px;font-style:normal;font-weight:560}.hist-tab.on i[data-v-0bbfea97]{color:#7a63b8}.lc-hist-mask[data-v-0bbfea97]{z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background:#14121e6b;place-items:center;padding:24px;animation:.14s lc-fade-0bbfea97;display:grid;position:fixed;inset:0}.lc-hist[data-v-0bbfea97]{border:1px solid var(--line);background:#fff;border-radius:20px;flex-direction:column;width:min(680px,100%);max-height:calc(min(76vh,720px));animation:.16s lc-pop-0bbfea97;display:flex;overflow:hidden;box-shadow:0 40px 80px -40px #14121e8c}@keyframes lc-fade-0bbfea97{0%{opacity:0}to{opacity:1}}@keyframes lc-pop-0bbfea97{0%{opacity:0;transform:translateY(6px)scale(.99)}to{opacity:1;transform:none}}.lc-hist-hd[data-v-0bbfea97]{border-bottom:1px solid var(--line);align-items:flex-start;gap:12px;padding:16px 18px 12px;display:flex}.lc-hist-hd-txt[data-v-0bbfea97]{min-width:0}.lc-hist-title[data-v-0bbfea97]{letter-spacing:-.015em;font-size:15px;font-weight:660}.lc-hist-sub[data-v-0bbfea97]{color:var(--ink-3);margin-top:3px;font-size:11.2px}.lc-hist-x[data-v-0bbfea97]{width:26px;height:26px;color:var(--ink-3);cursor:pointer;border-radius:9px;flex:none;place-items:center;margin-left:auto;transition:background .14s,color .14s;display:grid}.lc-hist-x[data-v-0bbfea97]:hover{background:var(--sunken);color:var(--ink)}.lc-hist-x-ic[data-v-0bbfea97]{width:14px;height:14px}.lc-hist-list[data-v-0bbfea97]{gap:2px;padding:8px;display:grid;overflow-y:auto}.hist-item[data-v-0bbfea97]{text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:10px;align-items:center;gap:10px;width:100%;padding:9px 11px;transition:background .14s,border-color .14s;display:flex}.hist-item[data-v-0bbfea97]:hover{background:var(--sunken-2);border-color:var(--line)}.hist-item.on[data-v-0bbfea97]{background:var(--violet-soft);border-color:var(--violet-line)}.hist-item[data-v-0bbfea97]:disabled{opacity:.6;cursor:wait}.hist-item-dot[data-v-0bbfea97]{background:#d3cfe6;border-radius:999px;flex:none;width:6px;height:6px}.hist-item-dot.live[data-v-0bbfea97]{background:#6aa844;box-shadow:0 0 0 3px #6aa84429}.hist-item-main[data-v-0bbfea97]{flex:1;gap:2px;min-width:0;display:grid}.hist-item-main b[data-v-0bbfea97]{text-overflow:ellipsis;white-space:nowrap;font-size:12.4px;font-weight:600;overflow:hidden}.hist-item-main i[data-v-0bbfea97]{color:var(--ink-4);font-size:10.2px;font-style:normal}.hist-item-hash[data-v-0bbfea97]{border:1px solid var(--line-2);color:var(--ink-3);background:#fff;border-radius:6px;flex:none;padding:0 6px;font-size:9.8px;line-height:16px}.lc-hist-hint[data-v-0bbfea97]{text-align:center;padding:18px 12px;font-size:12px}.lc-hist-err[data-v-0bbfea97]{border-radius:var(--r-md);color:#a5382f;background:#fdeae7;border:1px solid #f6d2cb;margin:12px 18px;padding:9px 11px;font-size:11.8px}.lc-hist-ft[data-v-0bbfea97]{border-top:1px solid var(--line);justify-content:space-between;align-items:center;gap:12px;padding:11px 18px;font-size:10.8px;display:flex}.hist-close[data-v-0bbfea97]{border:1px solid var(--ink-card);background:var(--ink-card);color:#fff;cursor:pointer;border-radius:999px;flex:none;height:26px;padding:0 12px;font-size:11px;font-weight:620}.hist-close[data-v-0bbfea97]:hover{background:#000}@media (width<=900px){.lc-hero[data-v-0bbfea97]{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:4px 14px;padding:18px;display:grid}.hero-body[data-v-0bbfea97]{grid-column:1}.hero-aside[data-v-0bbfea97]{grid-area:1/2}.ring[data-v-0bbfea97]{width:72px;height:72px}.ring-in[data-v-0bbfea97]{width:58px;height:58px}.ring-in b[data-v-0bbfea97]{font-size:14px}.ring-in span[data-v-0bbfea97]{font-size:8.2px}.s-hint[data-v-0bbfea97]{display:none}.s-copy[data-v-0bbfea97]{margin-left:auto}.s-hd[data-v-0bbfea97]{padding:12px 14px 10px}.sec-bd[data-v-0bbfea97],.sec-empty[data-v-0bbfea97],.steps[data-v-0bbfea97]{padding-left:14px;padding-right:14px}}@media (width<=700px){.hist-readonly[data-v-0bbfea97]{display:none}.lc-hist-bar .rule-btn[data-v-0bbfea97]{order:2;margin-left:auto}.lc-hist-tabs[data-v-0bbfea97]{flex-basis:100%;order:3}}@media (width<=560px){.lc-rule[data-v-0bbfea97]{padding:10px 12px}.ring-in span[data-v-0bbfea97],.rule-file[data-v-0bbfea97]{display:none}.lc-hero[data-v-0bbfea97]{gap:4px 10px;padding:15px}.hero-title[data-v-0bbfea97]{letter-spacing:-.02em;font-size:17.5px}.hero-sum[data-v-0bbfea97]{font-size:12.2px;line-height:1.62}.hero-meta[data-v-0bbfea97]{gap:8px;margin-top:10px;font-size:10.8px}.ring[data-v-0bbfea97]{width:56px;height:56px}.ring-in[data-v-0bbfea97]{width:46px;height:46px}.ring-in b[data-v-0bbfea97]{font-size:12.5px}.s-title[data-v-0bbfea97]{font-size:13.2px}}
|