paseo-req-kit 1.14.0-beta.3 → 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 (97) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/req.mjs +8 -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 +8 -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 +14 -2
  8. package/engine/src/cli-args.ts +4 -1
  9. package/engine/src/code-review.ts +419 -0
  10. package/engine/src/codeowners.ts +87 -0
  11. package/engine/src/main.ts +13 -3
  12. package/engine/src/preflight.ts +10 -1
  13. package/engine/src/review-assets/code.css +93 -0
  14. package/engine/src/review-assets/code.html +43 -0
  15. package/engine/src/review-assets/code.js +402 -0
  16. package/engine/src/review.ts +83 -1
  17. package/package.json +1 -1
  18. package/paseo-plugin/client/data.ts +2 -2
  19. package/paseo-plugin/client/detail.tsx +7 -2
  20. package/paseo-plugin/client/nav.ts +5 -0
  21. package/paseo-plugin/client/review.tsx +16 -6
  22. package/paseo-plugin/package.json +1 -1
  23. package/paseo-plugin/server/handlers.ts +1 -1
  24. package/paseo-plugin/shared/contracts.ts +4 -2
  25. package/site-dist/404.html +2 -2
  26. package/site-dist/assets/{app.BpC99f5a.js → app.Cp7WwPWE.js} +1 -1
  27. package/site-dist/assets/changelog.md.Zi-_3Rjh.js +1 -0
  28. package/site-dist/assets/chunks/@localSearchIndexroot.COxp5Rrc.js +1 -0
  29. package/site-dist/assets/chunks/{VPLocalSearchBox.CI43rhTA.js → VPLocalSearchBox.CMTXQK-Q.js} +1 -1
  30. package/site-dist/assets/chunks/{theme.S6pNEF23.js → theme.DvVUfKr3.js} +2 -2
  31. package/site-dist/assets/{guide_getting-started.md.a7iNP1Ky.js → guide_getting-started.md.D8XlIQOg.js} +1 -1
  32. package/site-dist/assets/{guide_user.md.D9GpGJ7y.js → guide_user.md.B4N9ywmr.js} +1 -1
  33. package/site-dist/assets/{reference_contracts_cli-json.md.BV1HtTmr.js → reference_contracts_cli-json.md.CCvKLasM.js} +1 -1
  34. package/site-dist/assets/{reference_contracts_cli-json.md.BV1HtTmr.lean.js → reference_contracts_cli-json.md.CCvKLasM.lean.js} +1 -1
  35. package/site-dist/changelog.html +5 -5
  36. package/site-dist/guide/getting-started.html +5 -5
  37. package/site-dist/guide/how-it-works.html +4 -4
  38. package/site-dist/guide/index.html +4 -4
  39. package/site-dist/guide/install.html +4 -4
  40. package/site-dist/guide/maintainer.html +4 -4
  41. package/site-dist/guide/phases.html +4 -4
  42. package/site-dist/guide/project.html +4 -4
  43. package/site-dist/guide/user.html +5 -5
  44. package/site-dist/hashmap.json +1 -1
  45. package/site-dist/index.html +4 -4
  46. package/site-dist/reference/adr/0001-step-agents-under-origin-chat.html +4 -4
  47. package/site-dist/reference/adr/0002-no-directory-switch-in-chat.html +4 -4
  48. package/site-dist/reference/adr/0003-script-engine-not-hub.html +4 -4
  49. package/site-dist/reference/adr/0004-result-files.html +4 -4
  50. package/site-dist/reference/adr/0005-no-default-branch-local-commits-only.html +4 -4
  51. package/site-dist/reference/adr/0006-skill-install-targets.html +4 -4
  52. package/site-dist/reference/adr/0007-own-interview-skill.html +4 -4
  53. package/site-dist/reference/adr/0008-entries-do-not-call-entries.html +4 -4
  54. package/site-dist/reference/adr/0009-versioned-install-layout.html +4 -4
  55. package/site-dist/reference/adr/0010-generated-feature-overview.html +4 -4
  56. package/site-dist/reference/adr/0011-resume-after-completion.html +4 -4
  57. package/site-dist/reference/adr/0012-review-panel.html +4 -4
  58. package/site-dist/reference/adr/0013-no-release-tooling.html +4 -4
  59. package/site-dist/reference/adr/0014-notifier-slot.html +4 -4
  60. package/site-dist/reference/adr/0015-incremental-continue.html +4 -4
  61. package/site-dist/reference/adr/0016-upgrade-from-tags.html +4 -4
  62. package/site-dist/reference/adr/0017-use-paseo-capabilities.html +4 -4
  63. package/site-dist/reference/adr/0018-own-worktree-option.html +4 -4
  64. package/site-dist/reference/adr/0019-parallel-orchestration.html +4 -4
  65. package/site-dist/reference/adr/0020-scheduling-through-the-chat.html +4 -4
  66. package/site-dist/reference/adr/0021-npm-version-manager.html +4 -4
  67. package/site-dist/reference/adr/0022-develop-and-verify-phases.html +4 -4
  68. package/site-dist/reference/adr/0023-types-gate-slices.html +4 -4
  69. package/site-dist/reference/adr/0024-confirm-early-review-axes.html +4 -4
  70. package/site-dist/reference/adr/0025-joint-requirements.html +4 -4
  71. package/site-dist/reference/adr/0026-machine-records-and-api.html +4 -4
  72. package/site-dist/reference/adr/0027-paseo-plugin.html +4 -4
  73. package/site-dist/reference/adr/0028-external-integrations.html +4 -4
  74. package/site-dist/reference/adr/0029-requirements-from-cards.html +4 -4
  75. package/site-dist/reference/adr/0030-card-intake-process.html +4 -4
  76. package/site-dist/reference/adr/0031-fewer-interventions.html +4 -4
  77. package/site-dist/reference/adr/0032-npm-only-install.html +4 -4
  78. package/site-dist/reference/adr/0033-prototype-review-and-ui-check.html +4 -4
  79. package/site-dist/reference/adr/index.html +4 -4
  80. package/site-dist/reference/commands.html +4 -4
  81. package/site-dist/reference/config.html +4 -4
  82. package/site-dist/reference/contracts/cli-json.html +5 -5
  83. package/site-dist/reference/contracts/feature-files.html +4 -4
  84. package/site-dist/reference/contracts/index.html +4 -4
  85. package/site-dist/reference/contracts/notifier-event.html +4 -4
  86. package/site-dist/reference/contracts/result-file.html +4 -4
  87. package/site-dist/reference/contracts/spec.html +4 -4
  88. package/site-dist/reference/contracts/state.html +4 -4
  89. package/site-dist/reference/contracts/usage-record.html +4 -4
  90. package/site-dist/reference/contracts/verify.html +4 -4
  91. package/site-dist/reference/scope.html +4 -4
  92. package/skills/req/SKILL.md +1 -0
  93. package/site-dist/assets/changelog.md.m4cSU5e4.js +0 -1
  94. package/site-dist/assets/chunks/@localSearchIndexroot.D6ywcTJ5.js +0 -1
  95. /package/site-dist/assets/{changelog.md.m4cSU5e4.lean.js → changelog.md.Zi-_3Rjh.lean.js} +0 -0
  96. /package/site-dist/assets/{guide_getting-started.md.a7iNP1Ky.lean.js → guide_getting-started.md.D8XlIQOg.lean.js} +0 -0
  97. /package/site-dist/assets/{guide_user.md.D9GpGJ7y.lean.js → guide_user.md.B4N9ywmr.lean.js} +0 -0
@@ -0,0 +1,87 @@
1
+ // CODEOWNERS (1.14): who should review a change, from the repository's own CODEOWNERS file
2
+ // (GitLab and GitHub format: "pattern @user @group email", the last matching line wins; GitLab
3
+ // "[Section]" headers and "^[Optional]" are accepted). Used as the default reviewers on the code
4
+ // review page, which the person can change before submitting.
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+
8
+ export const CODEOWNERS_FILES = ["CODEOWNERS", ".gitlab/CODEOWNERS", ".github/CODEOWNERS", "docs/CODEOWNERS"];
9
+
10
+ export interface OwnerRule {
11
+ pattern: string;
12
+ owners: string[];
13
+ /** The GitLab section the line is in, or null. */
14
+ section: string | null;
15
+ }
16
+
17
+ export function readCodeowners(repo: string): { file: string; rules: OwnerRule[] } | null {
18
+ for (const rel of CODEOWNERS_FILES) {
19
+ const file = path.join(repo, rel);
20
+ try {
21
+ return { file: rel, rules: parseCodeowners(fs.readFileSync(file, "utf8")) };
22
+ } catch {
23
+ // Not this one.
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+
29
+ export function parseCodeowners(text: string): OwnerRule[] {
30
+ const rules: OwnerRule[] = [];
31
+ let section: string | null = null;
32
+ let sectionOwners: string[] = [];
33
+ for (const raw of text.split(/\r?\n/)) {
34
+ const line = raw.replace(/(^|\s)#.*$/, "").trim();
35
+ if (!line) continue;
36
+ // GitLab: "[Section]", "^[Optional section]", "[Section][2] @default-owner".
37
+ const sec = /^\^?\[([^\]]+)\](?:\[\d+\])?\s*(.*)$/.exec(line);
38
+ if (sec) {
39
+ section = sec[1].trim();
40
+ sectionOwners = sec[2].split(/\s+/).filter(Boolean);
41
+ continue;
42
+ }
43
+ const parts = line.split(/\s+/);
44
+ const pattern = parts[0].replace(/\\ /g, " ");
45
+ const owners = parts.slice(1);
46
+ rules.push({ pattern, owners: owners.length ? owners : sectionOwners, section });
47
+ }
48
+ return rules;
49
+ }
50
+
51
+ /** gitignore-like matching of a CODEOWNERS pattern against a "/"-separated path. */
52
+ export function matchesPattern(pattern: string, file: string): boolean {
53
+ let p = pattern;
54
+ const anchored = p.startsWith("/") || p.slice(0, -1).includes("/");
55
+ p = p.replace(/^\//, "");
56
+ const dirOnly = p.endsWith("/");
57
+ if (dirOnly) p = p.slice(0, -1);
58
+ let re = "";
59
+ for (let i = 0; i < p.length; i++) {
60
+ const c = p[i];
61
+ if (c === "*" && p[i + 1] === "*") {
62
+ // "**/" any directories, "/**" everything below.
63
+ re += p[i + 2] === "/" ? "(?:.*/)?" : ".*";
64
+ i += p[i + 2] === "/" ? 2 : 1;
65
+ } else if (c === "*") re += "[^/]*";
66
+ else if (c === "?") re += "[^/]";
67
+ else re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
68
+ }
69
+ const prefix = anchored ? "^" : "^(?:.*/)?";
70
+ // A directory pattern (or a name that is a directory) covers everything below it.
71
+ const rx = new RegExp(`${prefix}${re}${dirOnly ? "/.*" : "(?:/.*)?"}$`);
72
+ return rx.test(file);
73
+ }
74
+
75
+ /** The owners of each file (last matching rule per section), and all of them together. */
76
+ export function ownersFor(rules: OwnerRule[], files: string[]): { byFile: Record<string, string[]>; all: string[] } {
77
+ const byFile: Record<string, string[]> = {};
78
+ const all = new Set<string>();
79
+ for (const file of files) {
80
+ const winners = new Map<string | null, string[]>();
81
+ for (const r of rules) if (matchesPattern(r.pattern, file)) winners.set(r.section, r.owners);
82
+ const owners = [...new Set([...winners.values()].flat())];
83
+ byFile[file] = owners;
84
+ for (const o of owners) all.add(o);
85
+ }
86
+ return { byFile, all: [...all] };
87
+ }
@@ -1,6 +1,7 @@
1
1
  // The long-running pipeline process. Normally started by launch.ts, detached
2
2
  // from the chat agent's shell; can also be run in the foreground for debugging.
3
3
 
4
+ import { pendingHumanReview, writeHumanReview } from "./code-review.js";
4
5
  import { execFileSync } from "node:child_process";
5
6
  import fs from "node:fs";
6
7
  import path from "node:path";
@@ -191,11 +192,20 @@ async function main(): Promise<number> {
191
192
  approval:
192
193
  args.approve && mode.previous.status === "waiting_for_approval"
193
194
  ? { attempt: mode.previous.attempt, status: "approved", at: now }
194
- : startOver
195
- ? null
196
- : (mode.previous.approval ?? null),
195
+ : // 1.14: code review comments do not change what the requester confirmed: no second confirmation.
196
+ args.reviewFix && mode.previous.approval?.status === "approved"
197
+ ? { ...mode.previous.approval, attempt: mode.previous.attempt + 1 }
198
+ : startOver
199
+ ? null
200
+ : (mode.previous.approval ?? null),
197
201
  gate: mode.kind === "restart" ? null : (mode.previous.gate ?? null),
198
202
  };
203
+ // 1.14: the person's code review comments are this attempt's to handle.
204
+ if (args.reviewFix && mode.kind === "resume-from-design") {
205
+ const featureAbs = path.join(repo, featureDir);
206
+ const pending = pendingHumanReview(featureAbs);
207
+ if (pending) writeHumanReview(featureAbs, { ...pending, consumedAttempt: mode.previous.attempt + 1 });
208
+ }
199
209
  if (checked.entersVerify) {
200
210
  // A develop-only run that ends with its development report gets a full report after verification.
201
211
  delete state.results.report;
@@ -1,3 +1,4 @@
1
+ import { pendingHumanReview } from "./code-review.js";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
@@ -14,7 +15,7 @@ import {
14
15
  } from "./config.js";
15
16
  import { gitInfo } from "./git.js";
16
17
  import { samePath } from "./paths.js";
17
- import { currentLockHolder, type PipelineState, readState, statePath } from "./state.js";
18
+ import { currentLockHolder, isAlive, type PipelineState, readState, statePath } from "./state.js";
18
19
 
19
20
  export interface LaunchArgs {
20
21
  repo: string;
@@ -33,6 +34,8 @@ export interface LaunchArgs {
33
34
  cardFields?: boolean;
34
35
  /** 1.14 --track: register a requirement being aligned, before the pipeline runs (shown in the plugin). */
35
36
  track?: boolean;
37
+ /** 1.14 --review-fix: a person's code review asked for changes; a new attempt handles them. */
38
+ reviewFix?: boolean;
36
39
  /** --spec-review: independent review of the draft spec, before confirmation. */
37
40
  specReview: boolean;
38
41
  /** --worktree: create a Paseo worktree workspace for this requirement and develop there. */
@@ -254,6 +257,12 @@ export function preflight(
254
257
  ? "这个需求的流水线已经完成。如需重新完整运行,请加 --restart。"
255
258
  : `这个需求已有运行记录(状态:${previous.status})。从中断处继续请加 --resume,从头开始请加 --restart。`;
256
259
  throw new PreflightError(hint);
260
+ } else if (args.reviewFix) {
261
+ // 1.14: the person's code review (plugin) asked for changes; the spec itself did not change.
262
+ if (previous.status === "running" && isAlive(previous.pid)) throw new PreflightError("流水线正在运行,等它结束后再处理人工评审意见。");
263
+ const pending = pendingHumanReview(path.join(repo, featureDir));
264
+ if (!pending) throw new PreflightError("没有待处理的人工代码评审意见(在插件的代码审查页上提交\"退回修改\"后才有)。");
265
+ mode = { kind: "resume-from-design", previous, reason: `人工代码评审提出了 ${pending.items.length} 条修改意见(见 code-review.md 的"人工评审"一节和 changes.md),需求说明不变,从方案设计开始只处理这些意见` };
257
266
  } else if (args.approve && previous.status !== "waiting_for_approval") {
258
267
  throw new PreflightError(`这个需求没有等待确认的方案(状态:${previous.status}),不需要 --approve。继续开发请用 --resume。`);
259
268
  } else if (previous.status === "waiting_for_approval" && currentSpecHash(specPath) !== previous.specHash) {
@@ -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>