paseo-req-kit 1.14.0-beta.2 → 1.14.0-beta.4
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/CHANGELOG.md +25 -0
- package/bin/req.mjs +16 -0
- package/contracts/cli-json.md +1 -1
- package/docs/guide//344/275/277/347/224/250/350/200/205/346/214/207/345/215/227.md +15 -0
- package/engine/prompts/06-code-review.md +12 -1
- package/engine/prompts/incremental.md +2 -0
- package/engine/src/api.ts +18 -2
- package/engine/src/checklist-cmd.ts +37 -0
- package/engine/src/checklist.ts +254 -0
- package/engine/src/cli-args.ts +4 -1
- package/engine/src/code-review.ts +419 -0
- package/engine/src/codeowners.ts +87 -0
- package/engine/src/main.ts +13 -3
- package/engine/src/preflight.ts +10 -1
- package/engine/src/reply.ts +7 -4
- package/engine/src/review-assets/check.css +57 -0
- package/engine/src/review-assets/check.html +35 -0
- package/engine/src/review-assets/check.js +258 -0
- package/engine/src/review-assets/code.css +93 -0
- package/engine/src/review-assets/code.html +43 -0
- package/engine/src/review-assets/code.js +402 -0
- package/engine/src/review.ts +177 -2
- package/engine/src/try.ts +134 -0
- package/package.json +1 -1
- package/paseo-plugin/client/data.ts +3 -3
- package/paseo-plugin/client/detail.tsx +10 -2
- package/paseo-plugin/client/nav.ts +10 -0
- package/paseo-plugin/client/review.tsx +30 -6
- package/paseo-plugin/package.json +1 -1
- package/paseo-plugin/server/handlers.ts +2 -2
- package/paseo-plugin/shared/contracts.ts +4 -1
- package/site-dist/404.html +2 -2
- package/site-dist/assets/{app.gI1JF182.js → app.Cp7WwPWE.js} +1 -1
- package/site-dist/assets/changelog.md.Zi-_3Rjh.js +1 -0
- package/site-dist/assets/chunks/@localSearchIndexroot.COxp5Rrc.js +1 -0
- package/site-dist/assets/chunks/{VPLocalSearchBox.DNmhkkfS.js → VPLocalSearchBox.CMTXQK-Q.js} +1 -1
- package/site-dist/assets/chunks/{theme.CZzkCOAC.js → theme.DvVUfKr3.js} +2 -2
- package/site-dist/assets/{guide_getting-started.md.zmtXv15G.js → guide_getting-started.md.D8XlIQOg.js} +1 -1
- package/site-dist/assets/{guide_user.md.2v7b_fzd.js → guide_user.md.B4N9ywmr.js} +1 -1
- package/site-dist/assets/{guide_user.md.2v7b_fzd.lean.js → guide_user.md.B4N9ywmr.lean.js} +1 -1
- package/site-dist/assets/{reference_contracts_cli-json.md.BR6gwq-e.js → reference_contracts_cli-json.md.CCvKLasM.js} +1 -1
- package/site-dist/assets/{reference_contracts_cli-json.md.BR6gwq-e.lean.js → reference_contracts_cli-json.md.CCvKLasM.lean.js} +1 -1
- package/site-dist/changelog.html +5 -5
- package/site-dist/guide/getting-started.html +5 -5
- package/site-dist/guide/how-it-works.html +4 -4
- package/site-dist/guide/index.html +4 -4
- package/site-dist/guide/install.html +4 -4
- package/site-dist/guide/maintainer.html +4 -4
- package/site-dist/guide/phases.html +4 -4
- package/site-dist/guide/project.html +4 -4
- package/site-dist/guide/user.html +5 -5
- package/site-dist/hashmap.json +1 -1
- package/site-dist/index.html +4 -4
- package/site-dist/reference/adr/0001-step-agents-under-origin-chat.html +4 -4
- package/site-dist/reference/adr/0002-no-directory-switch-in-chat.html +4 -4
- package/site-dist/reference/adr/0003-script-engine-not-hub.html +4 -4
- package/site-dist/reference/adr/0004-result-files.html +4 -4
- package/site-dist/reference/adr/0005-no-default-branch-local-commits-only.html +4 -4
- package/site-dist/reference/adr/0006-skill-install-targets.html +4 -4
- package/site-dist/reference/adr/0007-own-interview-skill.html +4 -4
- package/site-dist/reference/adr/0008-entries-do-not-call-entries.html +4 -4
- package/site-dist/reference/adr/0009-versioned-install-layout.html +4 -4
- package/site-dist/reference/adr/0010-generated-feature-overview.html +4 -4
- package/site-dist/reference/adr/0011-resume-after-completion.html +4 -4
- package/site-dist/reference/adr/0012-review-panel.html +4 -4
- package/site-dist/reference/adr/0013-no-release-tooling.html +4 -4
- package/site-dist/reference/adr/0014-notifier-slot.html +4 -4
- package/site-dist/reference/adr/0015-incremental-continue.html +4 -4
- package/site-dist/reference/adr/0016-upgrade-from-tags.html +4 -4
- package/site-dist/reference/adr/0017-use-paseo-capabilities.html +4 -4
- package/site-dist/reference/adr/0018-own-worktree-option.html +4 -4
- package/site-dist/reference/adr/0019-parallel-orchestration.html +4 -4
- package/site-dist/reference/adr/0020-scheduling-through-the-chat.html +4 -4
- package/site-dist/reference/adr/0021-npm-version-manager.html +4 -4
- package/site-dist/reference/adr/0022-develop-and-verify-phases.html +4 -4
- package/site-dist/reference/adr/0023-types-gate-slices.html +4 -4
- package/site-dist/reference/adr/0024-confirm-early-review-axes.html +4 -4
- package/site-dist/reference/adr/0025-joint-requirements.html +4 -4
- package/site-dist/reference/adr/0026-machine-records-and-api.html +4 -4
- package/site-dist/reference/adr/0027-paseo-plugin.html +4 -4
- package/site-dist/reference/adr/0028-external-integrations.html +4 -4
- package/site-dist/reference/adr/0029-requirements-from-cards.html +4 -4
- package/site-dist/reference/adr/0030-card-intake-process.html +4 -4
- package/site-dist/reference/adr/0031-fewer-interventions.html +4 -4
- package/site-dist/reference/adr/0032-npm-only-install.html +4 -4
- package/site-dist/reference/adr/0033-prototype-review-and-ui-check.html +4 -4
- package/site-dist/reference/adr/index.html +4 -4
- package/site-dist/reference/commands.html +4 -4
- package/site-dist/reference/config.html +4 -4
- package/site-dist/reference/contracts/cli-json.html +5 -5
- package/site-dist/reference/contracts/feature-files.html +4 -4
- package/site-dist/reference/contracts/index.html +4 -4
- package/site-dist/reference/contracts/notifier-event.html +4 -4
- package/site-dist/reference/contracts/result-file.html +4 -4
- package/site-dist/reference/contracts/spec.html +4 -4
- package/site-dist/reference/contracts/state.html +4 -4
- package/site-dist/reference/contracts/usage-record.html +4 -4
- package/site-dist/reference/contracts/verify.html +4 -4
- package/site-dist/reference/scope.html +4 -4
- package/skills/req/SKILL.md +3 -0
- package/skills/req/playbooks/accept.md +2 -2
- package/site-dist/assets/changelog.md.2xf690VW.js +0 -1
- package/site-dist/assets/chunks/@localSearchIndexroot.CiFeI6qC.js +0 -1
- /package/site-dist/assets/{changelog.md.2xf690VW.lean.js → changelog.md.Zi-_3Rjh.lean.js} +0 -0
- /package/site-dist/assets/{guide_getting-started.md.zmtXv15G.lean.js → guide_getting-started.md.D8XlIQOg.lean.js} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #f6f7f9; --surface: #ffffff; --surface-2: #f1f3f6; --text: #1c2430; --muted: #5d6b7c; --border: #dfe3e9;
|
|
3
|
+
--accent: #2457d6; --accent-text: #ffffff; --accent-soft: #e8eefc; --ok: #15803d; --ok-soft: #e7f6ec; --danger: #b91c1c; --danger-soft: #fdecec; --warn: #b45309; --warn-soft: #fdf3e2;
|
|
4
|
+
color-scheme: light;
|
|
5
|
+
}
|
|
6
|
+
@media (prefers-color-scheme: dark) {
|
|
7
|
+
:root {
|
|
8
|
+
--bg: #12161c; --surface: #1a2029; --surface-2: #222a35; --text: #e6eaf0; --muted: #9aa7b8; --border: #2e3846;
|
|
9
|
+
--accent: #6f9bff; --accent-text: #0d1420; --accent-soft: #1f2c47; --ok: #4ade80; --ok-soft: #173322; --danger: #f87171; --danger-soft: #3a1c1c; --warn: #fbbf24; --warn-soft: #3a2c12;
|
|
10
|
+
color-scheme: dark;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
* { box-sizing: border-box; }
|
|
14
|
+
[hidden] { display: none !important; }
|
|
15
|
+
body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.6 system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; padding-bottom: 96px; }
|
|
16
|
+
.top { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; padding: 20px 24px 8px; flex-wrap: wrap; }
|
|
17
|
+
.eyebrow { font-size: 13px; color: var(--muted); }
|
|
18
|
+
h1 { margin: 2px 0 4px; font-size: 22px; }
|
|
19
|
+
.muted { color: var(--muted); }
|
|
20
|
+
main { padding: 0 24px; max-width: 980px; }
|
|
21
|
+
.intro { margin: 8px 0 16px; }
|
|
22
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; margin: 12px 0; }
|
|
23
|
+
.notes h2, .card h2 { font-size: 15px; margin: 0 0 6px; }
|
|
24
|
+
.notes ul { margin: 0; padding-left: 20px; }
|
|
25
|
+
.items { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 12px; }
|
|
26
|
+
.item { background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--border); border-radius: 12px; padding: 14px 16px; }
|
|
27
|
+
.item.pass { border-left-color: var(--ok); }
|
|
28
|
+
.item.fail { border-left-color: var(--danger); }
|
|
29
|
+
.item.skip { border-left-color: var(--warn); }
|
|
30
|
+
.item-head { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
|
|
31
|
+
.id { font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
32
|
+
.item-title { font-weight: 600; }
|
|
33
|
+
.badge { font-size: 12px; padding: 1px 8px; border-radius: 999px; background: var(--surface-2); color: var(--muted); white-space: nowrap; }
|
|
34
|
+
.badge.fail { background: var(--danger-soft); color: var(--danger); }
|
|
35
|
+
.badge.unverified { background: var(--warn-soft); color: var(--warn); }
|
|
36
|
+
.badge.pass { background: var(--ok-soft); color: var(--ok); }
|
|
37
|
+
.details { margin: 8px 0 0; padding-left: 18px; }
|
|
38
|
+
.details li { margin: 2px 0; }
|
|
39
|
+
.evidence { font-size: 13px; color: var(--muted); margin-top: 6px; }
|
|
40
|
+
.marks { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
|
|
41
|
+
.btn { font: inherit; border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: 8px; padding: 6px 14px; cursor: pointer; min-height: 36px; }
|
|
42
|
+
.btn:hover { border-color: var(--accent); }
|
|
43
|
+
.btn:disabled { opacity: 0.5; cursor: default; }
|
|
44
|
+
.btn.primary { background: var(--accent); color: var(--accent-text); border-color: var(--accent); font-weight: 600; }
|
|
45
|
+
.btn.on-pass { background: var(--ok); color: #fff; border-color: var(--ok); }
|
|
46
|
+
.btn.on-fail { background: var(--danger); color: #fff; border-color: var(--danger); }
|
|
47
|
+
.btn.on-skip { background: var(--warn); color: #fff; border-color: var(--warn); }
|
|
48
|
+
textarea { width: 100%; margin-top: 8px; font: inherit; color: var(--text); background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 8px; min-height: 64px; }
|
|
49
|
+
.bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; align-items: center; gap: 16px; padding: 12px 24px; background: var(--surface); border-top: 1px solid var(--border); flex-wrap: wrap; }
|
|
50
|
+
.summary { flex: 1; min-width: 200px; }
|
|
51
|
+
.reviewer input { font: inherit; color: var(--text); background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; width: 160px; }
|
|
52
|
+
.toast { position: fixed; right: 24px; bottom: 84px; background: var(--text); color: var(--bg); padding: 10px 14px; border-radius: 10px; max-width: 520px; white-space: pre-line; }
|
|
53
|
+
.services a { color: var(--accent); }
|
|
54
|
+
pre.howto { white-space: pre-wrap; font: inherit; margin: 6px 0 0; }
|
|
55
|
+
.done { background: var(--ok-soft); border-color: var(--ok); }
|
|
56
|
+
|
|
57
|
+
@media (max-width: 640px) { body { padding-bottom: 200px; } }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>验收清单</title>
|
|
7
|
+
<link rel="stylesheet" href="check.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="top">
|
|
11
|
+
<div>
|
|
12
|
+
<div class="eyebrow" id="project"></div>
|
|
13
|
+
<h1 id="title">验收清单</h1>
|
|
14
|
+
<div class="muted" id="sub"></div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="top-actions">
|
|
17
|
+
<button id="try" class="btn">把它跑起来试用</button>
|
|
18
|
+
<button id="try-stop" class="btn" hidden>停止试用</button>
|
|
19
|
+
</div>
|
|
20
|
+
</header>
|
|
21
|
+
<main>
|
|
22
|
+
<section id="try-box" class="card" hidden></section>
|
|
23
|
+
<section id="notes" class="card notes" hidden></section>
|
|
24
|
+
<p class="muted intro">一条一条看:照着"前置条件"准备,按"操作"去做,看到的和"预期结果"一样就点<b>通过</b>;不一样点<b>不对</b>,写下看到了什么。流水线没验证或没通过的排在最前面。进度会自动保存。</p>
|
|
25
|
+
<ol id="items" class="items"></ol>
|
|
26
|
+
</main>
|
|
27
|
+
<footer class="bar">
|
|
28
|
+
<div class="summary" id="summary"></div>
|
|
29
|
+
<label class="reviewer">验收人 <input id="reviewer" placeholder="你的名字(可不填)" maxlength="60"></label>
|
|
30
|
+
<button id="submit" class="btn primary" disabled>提交验收结果</button>
|
|
31
|
+
</footer>
|
|
32
|
+
<div id="toast" class="toast" hidden></div>
|
|
33
|
+
<script src="check.js"></script>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// The acceptance checklist page (1.14). Plain script, no framework: the server serves it next to
|
|
2
|
+
// its JSON API (api/info, api/draft, api/try, api/submit).
|
|
3
|
+
(function () {
|
|
4
|
+
"use strict";
|
|
5
|
+
var state = { info: null, answers: {}, reviewer: "", saveTimer: null, busy: false };
|
|
6
|
+
var $ = function (id) {
|
|
7
|
+
return document.getElementById(id);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function el(tag, attrs, children) {
|
|
11
|
+
var node = document.createElement(tag);
|
|
12
|
+
Object.keys(attrs || {}).forEach(function (k) {
|
|
13
|
+
if (attrs[k] === null || attrs[k] === undefined) return;
|
|
14
|
+
if (k === "text") node.textContent = attrs[k];
|
|
15
|
+
else if (k === "className") node.className = attrs[k];
|
|
16
|
+
else if (k.indexOf("on") === 0) node.addEventListener(k.slice(2), attrs[k]);
|
|
17
|
+
else node.setAttribute(k, attrs[k]);
|
|
18
|
+
});
|
|
19
|
+
(children || []).forEach(function (c) {
|
|
20
|
+
if (c) node.appendChild(typeof c === "string" ? document.createTextNode(c) : c);
|
|
21
|
+
});
|
|
22
|
+
return node;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toast(text) {
|
|
26
|
+
var t = $("toast");
|
|
27
|
+
t.textContent = text;
|
|
28
|
+
t.hidden = false;
|
|
29
|
+
clearTimeout(toast.timer);
|
|
30
|
+
toast.timer = setTimeout(function () {
|
|
31
|
+
t.hidden = true;
|
|
32
|
+
}, 6000);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function api(pathname, method, body) {
|
|
36
|
+
return fetch(pathname, { method: method || "GET", headers: body ? { "content-type": "application/json" } : {}, body: body ? JSON.stringify(body) : undefined }).then(function (r) {
|
|
37
|
+
return r.json().then(function (data) {
|
|
38
|
+
if (!r.ok && data && data.message) throw new Error(data.message);
|
|
39
|
+
return data;
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var PIPELINE = { pass: ["流水线:通过", "pass"], fail: ["流水线:没通过", "fail"], unverified: ["流水线:没验证", "unverified"], unknown: ["流水线:没有记录", ""] };
|
|
45
|
+
|
|
46
|
+
function answersList() {
|
|
47
|
+
return Object.keys(state.answers).map(function (id) {
|
|
48
|
+
return { id: id, mark: state.answers[id].mark, note: state.answers[id].note || "" };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function save() {
|
|
53
|
+
if (state.info.readOnly) return;
|
|
54
|
+
clearTimeout(state.saveTimer);
|
|
55
|
+
state.saveTimer = setTimeout(function () {
|
|
56
|
+
api("api/draft", "PUT", { answers: answersList(), reviewer: state.reviewer }).catch(function () {});
|
|
57
|
+
}, 600);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function summary() {
|
|
61
|
+
var items = state.info.items;
|
|
62
|
+
var c = { pass: 0, fail: 0, skip: 0, open: 0 };
|
|
63
|
+
var missing = 0;
|
|
64
|
+
var failedRequired = 0;
|
|
65
|
+
items.forEach(function (i) {
|
|
66
|
+
var a = state.answers[i.id];
|
|
67
|
+
if (!a) c.open++;
|
|
68
|
+
else c[a.mark]++;
|
|
69
|
+
if (i.required && (!a || a.mark === "skip")) missing++;
|
|
70
|
+
if (i.required && a && a.mark === "fail") failedRequired++;
|
|
71
|
+
});
|
|
72
|
+
var verdict = failedRequired > 0 ? "不通过" : missing === 0 ? "通过" : null;
|
|
73
|
+
$("summary").textContent =
|
|
74
|
+
"共 " + items.length + " 条:通过 " + c.pass + ",不对 " + c.fail + ",先不验 " + c.skip + ",还没看 " + c.open + "。" +
|
|
75
|
+
(verdict === "通过" ? "可以提交:验收通过。" : verdict === "不通过" ? "提交后结论是“不通过”,不对的地方会转给需求的对话。" : "必须验的都核对完才能提交“通过”。");
|
|
76
|
+
$("submit").disabled = state.info.readOnly || state.busy || verdict === null;
|
|
77
|
+
$("submit").textContent = verdict === "不通过" ? "提交:验收不通过" : "提交:验收通过";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderItem(item, n) {
|
|
81
|
+
var a = state.answers[item.id];
|
|
82
|
+
var li = el("li", { className: "item" + (a ? " " + a.mark : "") });
|
|
83
|
+
var p = PIPELINE[item.pipeline] || PIPELINE.unknown;
|
|
84
|
+
li.appendChild(
|
|
85
|
+
el("div", { className: "item-head" }, [
|
|
86
|
+
el("span", { className: "id", text: n + ". " + item.id }),
|
|
87
|
+
el("span", { className: "item-title", text: item.title }),
|
|
88
|
+
item.kind === "criterion" ? el("span", { className: "badge " + p[1], text: p[0] }) : el("span", { className: "badge unverified", text: "请你表态" }),
|
|
89
|
+
item.required ? null : el("span", { className: "badge", text: "可以以后再说" }),
|
|
90
|
+
]),
|
|
91
|
+
);
|
|
92
|
+
if (item.details.length) {
|
|
93
|
+
li.appendChild(
|
|
94
|
+
el(
|
|
95
|
+
"ul",
|
|
96
|
+
{ className: "details" },
|
|
97
|
+
item.details.map(function (d) {
|
|
98
|
+
return el("li", { text: d });
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (item.evidence) li.appendChild(el("div", { className: "evidence", text: "流水线的记录:" + item.evidence }));
|
|
104
|
+
var labels = item.kind === "decision" ? [["pass", "同意"], ["fail", "不同意"], ["skip", "先不表态"]] : [["pass", "通过"], ["fail", "不对"], ["skip", "先不验"]];
|
|
105
|
+
var marks = el(
|
|
106
|
+
"div",
|
|
107
|
+
{ className: "marks" },
|
|
108
|
+
labels.map(function (l) {
|
|
109
|
+
return el("button", {
|
|
110
|
+
className: "btn" + (a && a.mark === l[0] ? " on-" + l[0] : ""),
|
|
111
|
+
text: l[1],
|
|
112
|
+
disabled: state.info.readOnly ? "disabled" : null,
|
|
113
|
+
onclick: function () {
|
|
114
|
+
var cur = state.answers[item.id];
|
|
115
|
+
state.answers[item.id] = { mark: l[0], note: cur ? cur.note : "" };
|
|
116
|
+
save();
|
|
117
|
+
render();
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
if (state.info.readOnly) marks.querySelectorAll("button").forEach(function (b) {
|
|
123
|
+
b.disabled = true;
|
|
124
|
+
});
|
|
125
|
+
li.appendChild(marks);
|
|
126
|
+
if (a && a.mark === "fail") {
|
|
127
|
+
var ta = el("textarea", { placeholder: item.kind === "decision" ? "为什么不同意?希望怎样?" : "看到了什么?应该是什么样?(例如:点导出后提示“失败”,应该下载文件)" });
|
|
128
|
+
ta.value = a.note || "";
|
|
129
|
+
ta.addEventListener("input", function () {
|
|
130
|
+
state.answers[item.id].note = ta.value;
|
|
131
|
+
save();
|
|
132
|
+
});
|
|
133
|
+
li.appendChild(ta);
|
|
134
|
+
}
|
|
135
|
+
return li;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function render() {
|
|
139
|
+
var list = $("items");
|
|
140
|
+
list.innerHTML = "";
|
|
141
|
+
state.info.items.forEach(function (item, i) {
|
|
142
|
+
list.appendChild(renderItem(item, i + 1));
|
|
143
|
+
});
|
|
144
|
+
summary();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function showTry(result) {
|
|
148
|
+
var box = $("try-box");
|
|
149
|
+
box.hidden = false;
|
|
150
|
+
box.innerHTML = "";
|
|
151
|
+
box.appendChild(el("h2", { text: result.ok ? "已经跑起来了" : "没能一键跑起来" }));
|
|
152
|
+
box.appendChild(el("div", { text: (result.message || "").split("\n")[0] }));
|
|
153
|
+
var running = (result.services || []).filter(function (s) {
|
|
154
|
+
return s.running && s.url;
|
|
155
|
+
});
|
|
156
|
+
if (running.length) {
|
|
157
|
+
box.appendChild(
|
|
158
|
+
el(
|
|
159
|
+
"ul",
|
|
160
|
+
{ className: "services" },
|
|
161
|
+
running.map(function (s) {
|
|
162
|
+
return el("li", {}, [s.name + ":", el("a", { href: s.url, target: "_blank", rel: "noopener", text: s.url })]);
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
$("try-stop").hidden = false;
|
|
167
|
+
}
|
|
168
|
+
if (result.howTo && result.howTo.length) box.appendChild(el("pre", { className: "howto", text: result.howTo.join("\n") }));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function tryIt(stop) {
|
|
172
|
+
var button = stop ? $("try-stop") : $("try");
|
|
173
|
+
button.disabled = true;
|
|
174
|
+
button.textContent = stop ? "正在停止…" : "正在启动,可能要一两分钟…";
|
|
175
|
+
api("api/try", "POST", { stop: !!stop })
|
|
176
|
+
.then(function (r) {
|
|
177
|
+
if (stop) {
|
|
178
|
+
toast(r.message);
|
|
179
|
+
$("try-stop").hidden = true;
|
|
180
|
+
$("try-box").hidden = true;
|
|
181
|
+
} else showTry(r);
|
|
182
|
+
})
|
|
183
|
+
.catch(function (e) {
|
|
184
|
+
toast(e.message);
|
|
185
|
+
})
|
|
186
|
+
.then(function () {
|
|
187
|
+
button.disabled = false;
|
|
188
|
+
button.textContent = stop ? "停止试用" : "把它跑起来试用";
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function submit() {
|
|
193
|
+
state.busy = true;
|
|
194
|
+
summary();
|
|
195
|
+
api("api/submit", "POST", { answers: answersList(), reviewer: state.reviewer })
|
|
196
|
+
.then(function (r) {
|
|
197
|
+
toast(r.message);
|
|
198
|
+
var done = el("section", { className: "card done" }, [el("h2", { text: "已提交:验收" + r.verdict }), el("div", { text: r.message })]);
|
|
199
|
+
document.querySelector("main").insertBefore(done, document.querySelector("main").firstChild);
|
|
200
|
+
window.scrollTo(0, 0);
|
|
201
|
+
})
|
|
202
|
+
.catch(function (e) {
|
|
203
|
+
toast(e.message);
|
|
204
|
+
state.busy = false;
|
|
205
|
+
summary();
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function start() {
|
|
210
|
+
api("api/info").then(function (info) {
|
|
211
|
+
state.info = info;
|
|
212
|
+
document.title = "验收清单:" + info.title;
|
|
213
|
+
$("title").textContent = "验收清单:" + info.title;
|
|
214
|
+
$("project").textContent = info.project + " · " + info.featureDir;
|
|
215
|
+
$("sub").textContent = [info.reportVerdict ? "流水线结论:" + info.reportVerdict : null, info.accepted ? "上次验收:" + info.accepted : null, info.readOnly ? "只读:可以看,不能提交" : null]
|
|
216
|
+
.filter(Boolean)
|
|
217
|
+
.join(" ");
|
|
218
|
+
(info.draft && info.draft.answers ? info.draft.answers : []).forEach(function (a) {
|
|
219
|
+
state.answers[a.id] = { mark: a.mark, note: a.note || "" };
|
|
220
|
+
});
|
|
221
|
+
state.reviewer = (info.draft && info.draft.reviewer) || "";
|
|
222
|
+
$("reviewer").value = state.reviewer;
|
|
223
|
+
if (info.notes && info.notes.length) {
|
|
224
|
+
var n = $("notes");
|
|
225
|
+
n.hidden = false;
|
|
226
|
+
n.appendChild(el("h2", { text: "先看一下(来自提测说明)" }));
|
|
227
|
+
n.appendChild(
|
|
228
|
+
el(
|
|
229
|
+
"ul",
|
|
230
|
+
{},
|
|
231
|
+
info.notes.map(function (t) {
|
|
232
|
+
return el("li", { text: t });
|
|
233
|
+
}),
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (info.readOnly || info.running) $("try").hidden = true;
|
|
238
|
+
if (!info.items.length) $("items").appendChild(el("li", { className: "item", text: "需求说明里没有找到验收标准(AC-编号)。" }));
|
|
239
|
+
render();
|
|
240
|
+
}).catch(function (e) {
|
|
241
|
+
$("title").textContent = "打不开验收清单";
|
|
242
|
+
$("sub").textContent = e.message;
|
|
243
|
+
});
|
|
244
|
+
$("reviewer").addEventListener("input", function (e) {
|
|
245
|
+
state.reviewer = e.target.value;
|
|
246
|
+
save();
|
|
247
|
+
});
|
|
248
|
+
$("try").addEventListener("click", function () {
|
|
249
|
+
tryIt(false);
|
|
250
|
+
});
|
|
251
|
+
$("try-stop").addEventListener("click", function () {
|
|
252
|
+
tryIt(true);
|
|
253
|
+
});
|
|
254
|
+
$("submit").addEventListener("click", submit);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
start();
|
|
258
|
+
})();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #f6f7f9; --surface: #ffffff; --surface-2: #f1f3f6; --text: #1c2430; --muted: #5d6b7c; --border: #dfe3e9;
|
|
3
|
+
--accent: #2457d6; --accent-text: #ffffff; --ok: #15803d; --ok-soft: #e7f6ec; --danger: #b91c1c; --danger-soft: #fdecec; --warn: #b45309; --warn-soft: #fdf3e2;
|
|
4
|
+
--add: #e6f6ea; --del: #fdecec; --flash: #fff4c2;
|
|
5
|
+
color-scheme: light;
|
|
6
|
+
}
|
|
7
|
+
@media (prefers-color-scheme: dark) {
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #12161c; --surface: #1a2029; --surface-2: #222a35; --text: #e6eaf0; --muted: #9aa7b8; --border: #2e3846;
|
|
10
|
+
--accent: #6f9bff; --accent-text: #0d1420; --ok: #4ade80; --ok-soft: #173322; --danger: #f87171; --danger-soft: #3a1c1c; --warn: #fbbf24; --warn-soft: #3a2c12;
|
|
11
|
+
--add: #15301f; --del: #3a1c1c; --flash: #4a3f12;
|
|
12
|
+
color-scheme: dark;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
* { box-sizing: border-box; }
|
|
16
|
+
[hidden] { display: none !important; }
|
|
17
|
+
body { margin: 0; background: var(--bg); color: var(--text); font: 15px/1.6 system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; padding-bottom: 110px; }
|
|
18
|
+
.top { padding: 20px 24px 8px; }
|
|
19
|
+
.eyebrow { font-size: 13px; color: var(--muted); }
|
|
20
|
+
h1 { margin: 2px 0 4px; font-size: 22px; }
|
|
21
|
+
h2 { font-size: 15px; margin: 0 0 6px; }
|
|
22
|
+
.muted { color: var(--muted); }
|
|
23
|
+
.small { font-size: 13px; }
|
|
24
|
+
.pad { padding: 8px 12px; }
|
|
25
|
+
.banner { margin: 8px 24px; padding: 10px 14px; border-radius: 10px; background: var(--surface-2); border: 1px solid var(--border); }
|
|
26
|
+
.banner.ok { background: var(--ok-soft); border-color: var(--ok); }
|
|
27
|
+
.banner.warn { background: var(--warn-soft); border-color: var(--warn); }
|
|
28
|
+
.layout { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 16px; padding: 0 24px; }
|
|
29
|
+
.files { position: sticky; top: 8px; align-self: start; max-height: calc(100vh - 140px); overflow: auto; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 10px; font-size: 13px; }
|
|
30
|
+
.nav-title { font-weight: 600; margin-bottom: 6px; }
|
|
31
|
+
.files a { display: flex; justify-content: space-between; gap: 6px; padding: 4px 6px; border-radius: 6px; color: var(--text); text-decoration: none; word-break: break-all; }
|
|
32
|
+
.files a:hover { background: var(--surface-2); }
|
|
33
|
+
.files a.doc { color: var(--muted); }
|
|
34
|
+
main { min-width: 0; }
|
|
35
|
+
.intro { margin: 8px 0 12px; }
|
|
36
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; margin: 0 0 12px; }
|
|
37
|
+
.badge { font-size: 12px; padding: 1px 8px; border-radius: 999px; background: var(--surface-2); color: var(--muted); white-space: nowrap; }
|
|
38
|
+
.badge.danger { background: var(--danger-soft); color: var(--danger); }
|
|
39
|
+
.badge.warn { background: var(--warn-soft); color: var(--warn); }
|
|
40
|
+
.badge.ok { background: var(--ok-soft); color: var(--ok); }
|
|
41
|
+
.file { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 12px; overflow: hidden; }
|
|
42
|
+
.file-head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 8px 12px; background: var(--surface-2); cursor: pointer; border-bottom: 1px solid var(--border); }
|
|
43
|
+
.file.folded .file-body { display: none; }
|
|
44
|
+
.file.folded .file-head { border-bottom: 0; }
|
|
45
|
+
.path { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; font-weight: 600; word-break: break-all; }
|
|
46
|
+
.plus { color: var(--ok); font-size: 13px; }
|
|
47
|
+
.minus { color: var(--danger); font-size: 13px; }
|
|
48
|
+
.file-body { overflow-x: auto; }
|
|
49
|
+
table.diff { border-collapse: collapse; width: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; line-height: 1.5; }
|
|
50
|
+
.diff td { padding: 0 8px; vertical-align: top; }
|
|
51
|
+
.diff .num { width: 1%; min-width: 44px; text-align: right; color: var(--muted); user-select: none; }
|
|
52
|
+
.diff .code { white-space: pre; }
|
|
53
|
+
.diff .hunk td { color: var(--muted); background: var(--surface-2); padding: 2px 8px; }
|
|
54
|
+
.diff .line { cursor: pointer; }
|
|
55
|
+
.diff .line:hover td { outline: 1px solid var(--border); }
|
|
56
|
+
.diff .add td { background: var(--add); }
|
|
57
|
+
.diff .del td { background: var(--del); }
|
|
58
|
+
.diff .flash td, .flash { background: var(--flash) !important; }
|
|
59
|
+
.finding { border: 1px solid var(--border); border-left: 4px solid var(--border); border-radius: 8px; padding: 8px 10px; margin: 6px 0; background: var(--surface); font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 14px; white-space: normal; }
|
|
60
|
+
.finding.danger { border-left-color: var(--danger); }
|
|
61
|
+
.finding.warn { border-left-color: var(--warn); }
|
|
62
|
+
.finding.decided-ok { opacity: 0.6; }
|
|
63
|
+
.finding-row td, .comment-row td { padding: 4px 12px 8px; }
|
|
64
|
+
.finding-head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
65
|
+
.finding-text { margin: 4px 0; }
|
|
66
|
+
.where { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; color: var(--accent); }
|
|
67
|
+
.marks { display: flex; gap: 8px; margin-top: 6px; }
|
|
68
|
+
.comment { border: 1px solid var(--accent); border-radius: 8px; padding: 8px 10px; background: var(--surface); font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
|
|
69
|
+
textarea, input { font: inherit; color: inherit; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; width: 100%; }
|
|
70
|
+
.note-input { margin-top: 6px; }
|
|
71
|
+
.btn { font: inherit; border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: 8px; padding: 6px 14px; cursor: pointer; }
|
|
72
|
+
.btn.small { padding: 2px 10px; font-size: 13px; }
|
|
73
|
+
.btn.on-fix { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }
|
|
74
|
+
.btn.on-ok { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }
|
|
75
|
+
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
|
|
76
|
+
.btn.danger { border-color: var(--danger); color: var(--danger); }
|
|
77
|
+
.btn:disabled { opacity: 0.5; cursor: default; }
|
|
78
|
+
.bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: center; padding: 10px 24px; background: var(--surface); border-top: 1px solid var(--border); }
|
|
79
|
+
.bar .summary { flex: 1 1 240px; font-size: 13px; }
|
|
80
|
+
.field { display: flex; align-items: center; gap: 6px; font-size: 13px; white-space: nowrap; }
|
|
81
|
+
.field input { width: 200px; }
|
|
82
|
+
.actions { display: flex; gap: 8px; }
|
|
83
|
+
.toast { position: fixed; left: 50%; bottom: 90px; transform: translateX(-50%); max-width: min(640px, 92vw); background: var(--text); color: var(--bg); padding: 10px 14px; border-radius: 10px; box-shadow: 0 6px 24px rgba(0,0,0,.2); white-space: pre-line; }
|
|
84
|
+
@media (max-width: 860px) {
|
|
85
|
+
.layout { grid-template-columns: minmax(0, 1fr); padding: 0 12px; }
|
|
86
|
+
.files { position: static; max-height: 220px; }
|
|
87
|
+
.top { padding: 16px 12px 6px; }
|
|
88
|
+
.banner { margin: 8px 12px; }
|
|
89
|
+
.bar { padding: 10px 12px; }
|
|
90
|
+
.field { flex: 1 1 100%; }
|
|
91
|
+
.field input { width: 100%; }
|
|
92
|
+
body { padding-bottom: 220px; }
|
|
93
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>代码审查</title>
|
|
7
|
+
<link rel="stylesheet" href="code.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="top">
|
|
11
|
+
<div>
|
|
12
|
+
<div class="eyebrow" id="project"></div>
|
|
13
|
+
<h1 id="title">代码审查</h1>
|
|
14
|
+
<div class="muted" id="sub"></div>
|
|
15
|
+
</div>
|
|
16
|
+
</header>
|
|
17
|
+
<section id="status" class="banner" hidden></section>
|
|
18
|
+
<div class="layout">
|
|
19
|
+
<nav class="files" id="files" aria-label="改动的文件"></nav>
|
|
20
|
+
<main>
|
|
21
|
+
<section id="summary" class="card" hidden></section>
|
|
22
|
+
<section id="ai" class="card" hidden></section>
|
|
23
|
+
<p class="muted intro">点代码行写意见(只对这一行);AI 的意见可以标<b>要改</b>或<b>不用改</b>。看完在下面选择<b>退回修改</b>(流水线会按意见改,改完再评审、再验证)或<b>评审通过</b>。进度会自动保存。</p>
|
|
24
|
+
<div id="diffs"></div>
|
|
25
|
+
<section class="card">
|
|
26
|
+
<h2>总体意见(可不填)</h2>
|
|
27
|
+
<textarea id="note" rows="3" placeholder="例如:整体可以,但导出接口要加权限校验"></textarea>
|
|
28
|
+
</section>
|
|
29
|
+
</main>
|
|
30
|
+
</div>
|
|
31
|
+
<footer class="bar">
|
|
32
|
+
<div class="summary" id="count"></div>
|
|
33
|
+
<label class="field">审核人 <input id="reviewers" placeholder="@用户名,用逗号分开"></label>
|
|
34
|
+
<label class="field">评审人 <input id="reviewer" placeholder="你的名字" maxlength="60"></label>
|
|
35
|
+
<div class="actions">
|
|
36
|
+
<button id="changes" class="btn danger" disabled>退回修改</button>
|
|
37
|
+
<button id="approve" class="btn primary" disabled>评审通过</button>
|
|
38
|
+
</div>
|
|
39
|
+
</footer>
|
|
40
|
+
<div id="toast" class="toast" hidden></div>
|
|
41
|
+
<script src="code.js"></script>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|