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.
Files changed (105) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/req.mjs +16 -0
  3. package/contracts/cli-json.md +1 -1
  4. package/docs/guide//344/275/277/347/224/250/350/200/205/346/214/207/345/215/227.md +15 -0
  5. package/engine/prompts/06-code-review.md +12 -1
  6. package/engine/prompts/incremental.md +2 -0
  7. package/engine/src/api.ts +18 -2
  8. package/engine/src/checklist-cmd.ts +37 -0
  9. package/engine/src/checklist.ts +254 -0
  10. package/engine/src/cli-args.ts +4 -1
  11. package/engine/src/code-review.ts +419 -0
  12. package/engine/src/codeowners.ts +87 -0
  13. package/engine/src/main.ts +13 -3
  14. package/engine/src/preflight.ts +10 -1
  15. package/engine/src/reply.ts +7 -4
  16. package/engine/src/review-assets/check.css +57 -0
  17. package/engine/src/review-assets/check.html +35 -0
  18. package/engine/src/review-assets/check.js +258 -0
  19. package/engine/src/review-assets/code.css +93 -0
  20. package/engine/src/review-assets/code.html +43 -0
  21. package/engine/src/review-assets/code.js +402 -0
  22. package/engine/src/review.ts +177 -2
  23. package/engine/src/try.ts +134 -0
  24. package/package.json +1 -1
  25. package/paseo-plugin/client/data.ts +3 -3
  26. package/paseo-plugin/client/detail.tsx +10 -2
  27. package/paseo-plugin/client/nav.ts +10 -0
  28. package/paseo-plugin/client/review.tsx +30 -6
  29. package/paseo-plugin/package.json +1 -1
  30. package/paseo-plugin/server/handlers.ts +2 -2
  31. package/paseo-plugin/shared/contracts.ts +4 -1
  32. package/site-dist/404.html +2 -2
  33. package/site-dist/assets/{app.gI1JF182.js → app.Cp7WwPWE.js} +1 -1
  34. package/site-dist/assets/changelog.md.Zi-_3Rjh.js +1 -0
  35. package/site-dist/assets/chunks/@localSearchIndexroot.COxp5Rrc.js +1 -0
  36. package/site-dist/assets/chunks/{VPLocalSearchBox.DNmhkkfS.js → VPLocalSearchBox.CMTXQK-Q.js} +1 -1
  37. package/site-dist/assets/chunks/{theme.CZzkCOAC.js → theme.DvVUfKr3.js} +2 -2
  38. package/site-dist/assets/{guide_getting-started.md.zmtXv15G.js → guide_getting-started.md.D8XlIQOg.js} +1 -1
  39. package/site-dist/assets/{guide_user.md.2v7b_fzd.js → guide_user.md.B4N9ywmr.js} +1 -1
  40. package/site-dist/assets/{guide_user.md.2v7b_fzd.lean.js → guide_user.md.B4N9ywmr.lean.js} +1 -1
  41. package/site-dist/assets/{reference_contracts_cli-json.md.BR6gwq-e.js → reference_contracts_cli-json.md.CCvKLasM.js} +1 -1
  42. package/site-dist/assets/{reference_contracts_cli-json.md.BR6gwq-e.lean.js → reference_contracts_cli-json.md.CCvKLasM.lean.js} +1 -1
  43. package/site-dist/changelog.html +5 -5
  44. package/site-dist/guide/getting-started.html +5 -5
  45. package/site-dist/guide/how-it-works.html +4 -4
  46. package/site-dist/guide/index.html +4 -4
  47. package/site-dist/guide/install.html +4 -4
  48. package/site-dist/guide/maintainer.html +4 -4
  49. package/site-dist/guide/phases.html +4 -4
  50. package/site-dist/guide/project.html +4 -4
  51. package/site-dist/guide/user.html +5 -5
  52. package/site-dist/hashmap.json +1 -1
  53. package/site-dist/index.html +4 -4
  54. package/site-dist/reference/adr/0001-step-agents-under-origin-chat.html +4 -4
  55. package/site-dist/reference/adr/0002-no-directory-switch-in-chat.html +4 -4
  56. package/site-dist/reference/adr/0003-script-engine-not-hub.html +4 -4
  57. package/site-dist/reference/adr/0004-result-files.html +4 -4
  58. package/site-dist/reference/adr/0005-no-default-branch-local-commits-only.html +4 -4
  59. package/site-dist/reference/adr/0006-skill-install-targets.html +4 -4
  60. package/site-dist/reference/adr/0007-own-interview-skill.html +4 -4
  61. package/site-dist/reference/adr/0008-entries-do-not-call-entries.html +4 -4
  62. package/site-dist/reference/adr/0009-versioned-install-layout.html +4 -4
  63. package/site-dist/reference/adr/0010-generated-feature-overview.html +4 -4
  64. package/site-dist/reference/adr/0011-resume-after-completion.html +4 -4
  65. package/site-dist/reference/adr/0012-review-panel.html +4 -4
  66. package/site-dist/reference/adr/0013-no-release-tooling.html +4 -4
  67. package/site-dist/reference/adr/0014-notifier-slot.html +4 -4
  68. package/site-dist/reference/adr/0015-incremental-continue.html +4 -4
  69. package/site-dist/reference/adr/0016-upgrade-from-tags.html +4 -4
  70. package/site-dist/reference/adr/0017-use-paseo-capabilities.html +4 -4
  71. package/site-dist/reference/adr/0018-own-worktree-option.html +4 -4
  72. package/site-dist/reference/adr/0019-parallel-orchestration.html +4 -4
  73. package/site-dist/reference/adr/0020-scheduling-through-the-chat.html +4 -4
  74. package/site-dist/reference/adr/0021-npm-version-manager.html +4 -4
  75. package/site-dist/reference/adr/0022-develop-and-verify-phases.html +4 -4
  76. package/site-dist/reference/adr/0023-types-gate-slices.html +4 -4
  77. package/site-dist/reference/adr/0024-confirm-early-review-axes.html +4 -4
  78. package/site-dist/reference/adr/0025-joint-requirements.html +4 -4
  79. package/site-dist/reference/adr/0026-machine-records-and-api.html +4 -4
  80. package/site-dist/reference/adr/0027-paseo-plugin.html +4 -4
  81. package/site-dist/reference/adr/0028-external-integrations.html +4 -4
  82. package/site-dist/reference/adr/0029-requirements-from-cards.html +4 -4
  83. package/site-dist/reference/adr/0030-card-intake-process.html +4 -4
  84. package/site-dist/reference/adr/0031-fewer-interventions.html +4 -4
  85. package/site-dist/reference/adr/0032-npm-only-install.html +4 -4
  86. package/site-dist/reference/adr/0033-prototype-review-and-ui-check.html +4 -4
  87. package/site-dist/reference/adr/index.html +4 -4
  88. package/site-dist/reference/commands.html +4 -4
  89. package/site-dist/reference/config.html +4 -4
  90. package/site-dist/reference/contracts/cli-json.html +5 -5
  91. package/site-dist/reference/contracts/feature-files.html +4 -4
  92. package/site-dist/reference/contracts/index.html +4 -4
  93. package/site-dist/reference/contracts/notifier-event.html +4 -4
  94. package/site-dist/reference/contracts/result-file.html +4 -4
  95. package/site-dist/reference/contracts/spec.html +4 -4
  96. package/site-dist/reference/contracts/state.html +4 -4
  97. package/site-dist/reference/contracts/usage-record.html +4 -4
  98. package/site-dist/reference/contracts/verify.html +4 -4
  99. package/site-dist/reference/scope.html +4 -4
  100. package/skills/req/SKILL.md +3 -0
  101. package/skills/req/playbooks/accept.md +2 -2
  102. package/site-dist/assets/changelog.md.2xf690VW.js +0 -1
  103. package/site-dist/assets/chunks/@localSearchIndexroot.CiFeI6qC.js +0 -1
  104. /package/site-dist/assets/{changelog.md.2xf690VW.lean.js → changelog.md.Zi-_3Rjh.lean.js} +0 -0
  105. /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>