@fieldwangai/agentflow 0.1.123 → 0.1.125

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.
@@ -8169,11 +8169,186 @@ function prdWorkflowReviewDedentPlannedCode(lines) {
8169
8169
  return lines.map((line) => String(line || "").slice(indent));
8170
8170
  }
8171
8171
 
8172
+ const PRD_WORKFLOW_REVIEW_CODE_KEYWORDS = new Set([
8173
+ "abstract", "as", "async", "await", "break", "case", "catch", "class", "const",
8174
+ "continue", "default", "delete", "do", "else", "enum", "export", "extends",
8175
+ "final", "finally", "for", "from", "fun", "function", "goto", "if", "implements",
8176
+ "import", "in", "instanceof", "interface", "internal", "is", "native", "new",
8177
+ "object", "of", "open", "package", "private", "protected", "public", "return",
8178
+ "sealed", "static", "strictfp", "super", "switch", "synchronized", "throw",
8179
+ "throws", "transient", "try", "typeof", "val", "var", "void", "volatile",
8180
+ "when", "while", "with", "yield",
8181
+ ]);
8182
+
8183
+ const PRD_WORKFLOW_REVIEW_CODE_LITERALS = new Set([
8184
+ "false", "null", "this", "true", "undefined",
8185
+ ]);
8186
+
8187
+ const PRD_WORKFLOW_REVIEW_CODE_TYPES = new Set([
8188
+ "any", "boolean", "byte", "char", "double", "float", "int", "long", "never",
8189
+ "number", "short", "string", "unknown",
8190
+ ]);
8191
+
8192
+ function prdWorkflowReviewCodeLanguage(filePath) {
8193
+ const extension = String(filePath || "").trim().toLowerCase().match(/\.([a-z0-9]+)$/)?.[1] || "";
8194
+ return {
8195
+ c: "c",
8196
+ cc: "cpp",
8197
+ cpp: "cpp",
8198
+ cs: "csharp",
8199
+ go: "go",
8200
+ java: "java",
8201
+ js: "javascript",
8202
+ json: "json",
8203
+ jsx: "jsx",
8204
+ kt: "kotlin",
8205
+ kts: "kotlin",
8206
+ m: "objective-c",
8207
+ mm: "objective-cpp",
8208
+ py: "python",
8209
+ rs: "rust",
8210
+ sh: "shell",
8211
+ swift: "swift",
8212
+ ts: "typescript",
8213
+ tsx: "tsx",
8214
+ }[extension] || "text";
8215
+ }
8216
+
8217
+ function prdWorkflowReviewSyntaxToken(kind, value) {
8218
+ const escaped = htmlEscapeAttribute(prdWorkflowReviewNormalizeText(value));
8219
+ return kind ? `<span class="syntax-${kind}">${escaped}</span>` : escaped;
8220
+ }
8221
+
8222
+ function prdWorkflowReviewHighlightCodeLine(line, language, state) {
8223
+ const source = prdWorkflowReviewNormalizeText(line);
8224
+ let html = "";
8225
+ let cursor = 0;
8226
+ const isIdentifierStart = (char) => /[A-Za-z_$]/.test(char || "");
8227
+ const isIdentifierPart = (char) => /[A-Za-z0-9_$]/.test(char || "");
8228
+
8229
+ while (cursor < source.length) {
8230
+ if (state.blockComment) {
8231
+ const end = source.indexOf("*/", cursor);
8232
+ if (end < 0) {
8233
+ html += prdWorkflowReviewSyntaxToken("comment", source.slice(cursor));
8234
+ cursor = source.length;
8235
+ continue;
8236
+ }
8237
+ html += prdWorkflowReviewSyntaxToken("comment", source.slice(cursor, end + 2));
8238
+ state.blockComment = false;
8239
+ cursor = end + 2;
8240
+ continue;
8241
+ }
8242
+
8243
+ if (source.startsWith("//", cursor) || (
8244
+ language === "shell"
8245
+ && source[cursor] === "#"
8246
+ )) {
8247
+ html += prdWorkflowReviewSyntaxToken("comment", source.slice(cursor));
8248
+ break;
8249
+ }
8250
+
8251
+ if (source.startsWith("/*", cursor)) {
8252
+ const end = source.indexOf("*/", cursor + 2);
8253
+ if (end < 0) {
8254
+ html += prdWorkflowReviewSyntaxToken("comment", source.slice(cursor));
8255
+ state.blockComment = true;
8256
+ break;
8257
+ }
8258
+ html += prdWorkflowReviewSyntaxToken("comment", source.slice(cursor, end + 2));
8259
+ cursor = end + 2;
8260
+ continue;
8261
+ }
8262
+
8263
+ const char = source[cursor];
8264
+ if (char === "\"" || char === "'" || char === "`") {
8265
+ let end = cursor + 1;
8266
+ while (end < source.length) {
8267
+ if (source[end] === "\\") {
8268
+ end += 2;
8269
+ continue;
8270
+ }
8271
+ if (source[end] === char) {
8272
+ end += 1;
8273
+ break;
8274
+ }
8275
+ end += 1;
8276
+ }
8277
+ html += prdWorkflowReviewSyntaxToken("string", source.slice(cursor, end));
8278
+ cursor = end;
8279
+ continue;
8280
+ }
8281
+
8282
+ if (char === "@" && isIdentifierStart(source[cursor + 1])) {
8283
+ let end = cursor + 2;
8284
+ while (end < source.length && isIdentifierPart(source[end])) end += 1;
8285
+ html += prdWorkflowReviewSyntaxToken("annotation", source.slice(cursor, end));
8286
+ cursor = end;
8287
+ continue;
8288
+ }
8289
+
8290
+ if (/[0-9]/.test(char)) {
8291
+ const number = source.slice(cursor).match(/^(?:0[xX][\dA-Fa-f_]+|0[bB][01_]+|\d[\d_]*(?:\.\d[\d_]*)?(?:[eE][+-]?\d+)?[fFdDlL]?)/)?.[0] || char;
8292
+ html += prdWorkflowReviewSyntaxToken("number", number);
8293
+ cursor += number.length;
8294
+ continue;
8295
+ }
8296
+
8297
+ if (isIdentifierStart(char)) {
8298
+ let end = cursor + 1;
8299
+ while (end < source.length && isIdentifierPart(source[end])) end += 1;
8300
+ const word = source.slice(cursor, end);
8301
+ const next = source.slice(end).match(/^\s*(.)/)?.[1] || "";
8302
+ let kind = "";
8303
+ if (PRD_WORKFLOW_REVIEW_CODE_KEYWORDS.has(word)) kind = "keyword";
8304
+ else if (PRD_WORKFLOW_REVIEW_CODE_LITERALS.has(word)) kind = "literal";
8305
+ else if (/^[A-Z][A-Z0-9_]*$/.test(word)) kind = "constant";
8306
+ else if (PRD_WORKFLOW_REVIEW_CODE_TYPES.has(word) || /^[A-Z][A-Za-z0-9_$]*$/.test(word)) kind = "type";
8307
+ else if (next === "(") kind = "function";
8308
+ html += prdWorkflowReviewSyntaxToken(kind, word);
8309
+ cursor = end;
8310
+ continue;
8311
+ }
8312
+
8313
+ const operator = source.slice(cursor).match(/^(?:>>>=|===|!==|>>>|<<=|>>=|->|=>|==|!=|<=|>=|&&|\|\||\+\+|--|\+=|-=|\*=|\/=|%=|::|<<|>>|\?\.|\?:)/)?.[0];
8314
+ if (operator) {
8315
+ html += prdWorkflowReviewSyntaxToken("operator", operator);
8316
+ cursor += operator.length;
8317
+ continue;
8318
+ }
8319
+ if (/[+\-*/%=&|!<>?:~^]/.test(char)) {
8320
+ html += prdWorkflowReviewSyntaxToken("operator", char);
8321
+ cursor += 1;
8322
+ continue;
8323
+ }
8324
+ if (/[()[\]{},.;]/.test(char)) {
8325
+ html += prdWorkflowReviewSyntaxToken("punctuation", char);
8326
+ cursor += 1;
8327
+ continue;
8328
+ }
8329
+
8330
+ html += htmlEscapeAttribute(char);
8331
+ cursor += 1;
8332
+ }
8333
+
8334
+ return html || " ";
8335
+ }
8336
+
8337
+ function prdWorkflowReviewHighlightCodeLines(lines, filePath) {
8338
+ const language = prdWorkflowReviewCodeLanguage(filePath);
8339
+ const state = { blockComment: false };
8340
+ return {
8341
+ language,
8342
+ lines: lines.map((line) => prdWorkflowReviewHighlightCodeLine(line, language, state)),
8343
+ };
8344
+ }
8345
+
8172
8346
  function prdWorkflowReviewRenderPlannedCode(filePath, codeLines, startLine = 0, endLine = 0) {
8173
8347
  const safePath = htmlEscapeAttribute(
8174
8348
  String(filePath || "").trim().replace(/^`|`$/g, ""),
8175
8349
  );
8176
8350
  const normalizedLines = prdWorkflowReviewDedentPlannedCode(codeLines);
8351
+ const highlighted = prdWorkflowReviewHighlightCodeLines(normalizedLines, filePath);
8177
8352
  const firstLine = Number.isFinite(Number(startLine)) && Number(startLine) > 0
8178
8353
  ? Number(startLine)
8179
8354
  : 0;
@@ -8183,11 +8358,11 @@ function prdWorkflowReviewRenderPlannedCode(filePath, codeLines, startLine = 0,
8183
8358
  const lineLabel = firstLine
8184
8359
  ? `L${firstLine}${explicitEnd && explicitEnd !== firstLine ? `–L${explicitEnd}` : ""}`
8185
8360
  : "拟修改";
8186
- const rows = normalizedLines.map((line, index) => {
8361
+ const rows = highlighted.lines.map((line, index) => {
8187
8362
  const lineNumber = firstLine ? String(firstLine + index) : "·";
8188
- return `<span class="planned-code__line"><span class="planned-code__number">${lineNumber}</span><span class="planned-code__text">${htmlEscapeAttribute(prdWorkflowReviewNormalizeText(line)) || " "}</span></span>`;
8363
+ return `<span class="planned-code__line"><span class="planned-code__number">${lineNumber}</span><span class="planned-code__text">${line}</span></span>`;
8189
8364
  }).join("");
8190
- return `<section class="planned-code" data-file="${safePath}">
8365
+ return `<section class="planned-code" data-file="${safePath}" data-language="${highlighted.language}">
8191
8366
  <div class="planned-code__header">
8192
8367
  <code class="planned-code__file">${safePath}</code>
8193
8368
  <span class="planned-code__anchor">${htmlEscapeAttribute(lineLabel)}</span>
@@ -8211,7 +8386,8 @@ function prdWorkflowReviewParseChangeIntent(lines, startIndex) {
8211
8386
  base: "",
8212
8387
  insertNear: "",
8213
8388
  destination: "",
8214
- reference: null,
8389
+ references: [],
8390
+ annotations: [],
8215
8391
  proposals: [],
8216
8392
  };
8217
8393
  for (let i = startIndex + 1; i < lines.length; i += 1) {
@@ -8240,11 +8416,31 @@ function prdWorkflowReviewParseChangeIntent(lines, startIndex) {
8240
8416
  end += 1;
8241
8417
  }
8242
8418
  if (end >= lines.length) return null;
8243
- change.reference = {
8419
+ change.references.push({
8244
8420
  startLine: Number(reference[1]),
8245
8421
  endLine: Number(reference[2] || reference[1]),
8246
8422
  lines: body,
8247
- };
8423
+ });
8424
+ i = end;
8425
+ continue;
8426
+ }
8427
+ const annotation = trimmed.match(
8428
+ /^#annotation\s+line\s*(\d+)(?:\s*-\s*(\d+))?\s+(problem|change|preserve)\s*$/i,
8429
+ );
8430
+ if (annotation) {
8431
+ const body = [];
8432
+ let end = i + 1;
8433
+ while (end < lines.length && !/^#annotationend\s*$/i.test(String(lines[end] || "").trim())) {
8434
+ body.push(lines[end]);
8435
+ end += 1;
8436
+ }
8437
+ if (end >= lines.length) return null;
8438
+ change.annotations.push({
8439
+ startLine: Number(annotation[1]),
8440
+ endLine: Number(annotation[2] || annotation[1]),
8441
+ type: annotation[3].toLowerCase(),
8442
+ lines: body,
8443
+ });
8248
8444
  i = end;
8249
8445
  continue;
8250
8446
  }
@@ -8286,6 +8482,11 @@ function prdWorkflowReviewRenderChangeIntent(change) {
8286
8482
  pseudocode: "方案伪代码",
8287
8483
  code: "拟议代码 · 未写入",
8288
8484
  };
8485
+ const annotationLabels = {
8486
+ problem: "当前问题",
8487
+ change: "计划修改",
8488
+ preserve: "保持不变",
8489
+ };
8289
8490
  const operation = operationLabels[change.operation] || "变更";
8290
8491
  const target = targetLabels[change.target] || "目标";
8291
8492
  const locator = change.module || change.file || "未定位";
@@ -8300,23 +8501,66 @@ function prdWorkflowReviewRenderChangeIntent(change) {
8300
8501
  safeDestination ? `<span><strong>移动到</strong> <code>${safeDestination}</code></span>` : "",
8301
8502
  ].filter(Boolean).join("");
8302
8503
 
8504
+ const references = Array.isArray(change.references)
8505
+ ? change.references
8506
+ : (change.reference ? [change.reference] : []);
8507
+ const annotations = Array.isArray(change.annotations) ? change.annotations : [];
8508
+ const sourceLanguage = prdWorkflowReviewCodeLanguage(change.file || change.module);
8509
+ const sourceLineLabel = (item) => `L${item.startLine}${
8510
+ item.endLine !== item.startLine
8511
+ ? `–L${item.endLine}`
8512
+ : ""
8513
+ }`;
8303
8514
  let referenceHtml = "";
8304
- if (change.reference) {
8305
- const normalized = prdWorkflowReviewDedentPlannedCode(change.reference.lines);
8306
- const lineLabel = `L${change.reference.startLine}${
8307
- change.reference.endLine !== change.reference.startLine
8308
- ? `–L${change.reference.endLine}`
8309
- : ""
8310
- }`;
8311
- const rows = normalized.map((line, index) => (
8312
- `<span class="change-intent__source-line">`
8313
- + `<span class="change-intent__source-number">${change.reference.startLine + index}</span>`
8314
- + `<span class="change-intent__source-text">${htmlEscapeAttribute(prdWorkflowReviewNormalizeText(line)) || " "}</span>`
8315
- + "</span>"
8316
- )).join("");
8317
- referenceHtml = `<details class="change-intent__context" open>
8318
- <summary><span>当前上下文</span><span class="change-intent__line-anchor">${lineLabel}</span></summary>
8319
- <pre class="change-intent__source"><code>${rows}</code></pre>
8515
+ if (references.length) {
8516
+ const totalLines = references.reduce(
8517
+ (total, reference) => total + Math.max(0, reference.endLine - reference.startLine + 1),
8518
+ 0,
8519
+ );
8520
+ const hunks = references.map((reference, referenceIndex) => {
8521
+ const normalized = prdWorkflowReviewDedentPlannedCode(reference.lines);
8522
+ const highlighted = prdWorkflowReviewHighlightCodeLines(normalized, change.file || change.module);
8523
+ const rows = highlighted.lines.map((line, index) => (
8524
+ `<span class="change-intent__source-line">`
8525
+ + `<span class="change-intent__source-number">${reference.startLine + index}</span>`
8526
+ + `<span class="change-intent__source-text">${line}</span>`
8527
+ + "</span>"
8528
+ )).join("");
8529
+ const annotationHtml = annotations
8530
+ .filter((annotation) => (
8531
+ reference.startLine <= annotation.startLine
8532
+ && annotation.endLine <= reference.endLine
8533
+ ))
8534
+ .map((annotation) => {
8535
+ const type = annotation.type || "change";
8536
+ const label = annotationLabels[type] || "Review 指引";
8537
+ const body = prdWorkflowReviewMarkdownLinesToHtml(
8538
+ prdWorkflowReviewDedentPlannedCode(annotation.lines),
8539
+ );
8540
+ return `<aside class="change-intent__annotation is-${htmlEscapeAttribute(type)}">
8541
+ <div class="change-intent__annotation-header">
8542
+ <span class="change-intent__annotation-title">Review 指引</span>
8543
+ <span class="change-intent__annotation-badge">${htmlEscapeAttribute(label)}</span>
8544
+ <span class="change-intent__annotation-anchor">${htmlEscapeAttribute(sourceLineLabel(annotation))}</span>
8545
+ </div>
8546
+ <div class="change-intent__annotation-body">${body}</div>
8547
+ </aside>`;
8548
+ }).join("");
8549
+ return `<section class="change-intent__hunk">
8550
+ <div class="change-intent__hunk-header">
8551
+ <span>片段 ${referenceIndex + 1}</span>
8552
+ <span class="change-intent__line-anchor">${htmlEscapeAttribute(sourceLineLabel(reference))}</span>
8553
+ </div>
8554
+ <pre class="change-intent__source" data-language="${highlighted.language}"><code>${rows}</code></pre>
8555
+ ${annotationHtml}
8556
+ </section>`;
8557
+ }).join("");
8558
+ referenceHtml = `<details class="change-intent__context" data-language="${sourceLanguage}" open>
8559
+ <summary>
8560
+ <span>当前上下文</span>
8561
+ <span class="change-intent__context-stats">${references.length} 个片段 · ${totalLines} 行</span>
8562
+ </summary>
8563
+ <div class="change-intent__hunks">${hunks}</div>
8320
8564
  </details>`;
8321
8565
  }
8322
8566
 
@@ -8328,13 +8572,14 @@ function prdWorkflowReviewRenderChangeIntent(change) {
8328
8572
  if (type === "natural") {
8329
8573
  body = `<div class="change-intent__natural">${prdWorkflowReviewMarkdownLinesToHtml(normalized)}</div>`;
8330
8574
  } else if (type === "code") {
8331
- const rows = normalized.map((line) => (
8575
+ const highlighted = prdWorkflowReviewHighlightCodeLines(normalized, change.file || change.module);
8576
+ const rows = highlighted.lines.map((line) => (
8332
8577
  `<span class="change-intent__proposal-line is-code">`
8333
8578
  + '<span class="change-intent__proposal-mark">+</span>'
8334
- + `<span class="change-intent__proposal-text">${htmlEscapeAttribute(prdWorkflowReviewNormalizeText(line)) || " "}</span>`
8579
+ + `<span class="change-intent__proposal-text">${line}</span>`
8335
8580
  + "</span>"
8336
8581
  )).join("");
8337
- body = `<pre class="change-intent__proposal-body is-code"><code>${rows}</code></pre>`;
8582
+ body = `<pre class="change-intent__proposal-body is-code" data-language="${highlighted.language}"><code>${rows}</code></pre>`;
8338
8583
  } else {
8339
8584
  const rows = normalized.map((line) => (
8340
8585
  `<span class="change-intent__proposal-line">`
@@ -8681,6 +8926,17 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
8681
8926
  --change-modify: #7dcfff;
8682
8927
  --change-remove: #f7768e;
8683
8928
  --change-move: #bb9af7;
8929
+ --syntax-comment: #737aa2;
8930
+ --syntax-keyword: #bb9af7;
8931
+ --syntax-literal: #ff9e64;
8932
+ --syntax-type: #2ac3de;
8933
+ --syntax-function: #7aa2f7;
8934
+ --syntax-string: #9ece6a;
8935
+ --syntax-number: #ff9e64;
8936
+ --syntax-annotation: #e0af68;
8937
+ --syntax-constant: #ff9e64;
8938
+ --syntax-operator: #89ddff;
8939
+ --syntax-punctuation: #a9b1d6;
8684
8940
  --action-bg: #1f2335;
8685
8941
  --action-header: #24283b;
8686
8942
  --pending-bg: rgba(224,175,104,.10);
@@ -8726,6 +8982,17 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
8726
8982
  --change-modify: #007197;
8727
8983
  --change-remove: #c64343;
8728
8984
  --change-move: #7847bd;
8985
+ --syntax-comment: #8990a7;
8986
+ --syntax-keyword: #7847bd;
8987
+ --syntax-literal: #965027;
8988
+ --syntax-type: #007197;
8989
+ --syntax-function: #2e7de9;
8990
+ --syntax-string: #587539;
8991
+ --syntax-number: #965027;
8992
+ --syntax-annotation: #9a5200;
8993
+ --syntax-constant: #965027;
8994
+ --syntax-operator: #007197;
8995
+ --syntax-punctuation: #565a6e;
8729
8996
  --action-bg: #f3f3f5;
8730
8997
  --action-header: #e9e9ed;
8731
8998
  --pending-bg: rgba(177,92,0,.08);
@@ -8767,9 +9034,9 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
8767
9034
  .planned-code__file { min-width: 0; border: 0; background: transparent; color: var(--link); padding: 0; font-weight: 800; }
8768
9035
  .planned-code__anchor, .planned-code__badge { border: 1px solid var(--border); border-radius: 999px; padding: 3px 8px; color: var(--muted); font: 800 11px/1.35 "SFMono-Regular", Consolas, monospace; }
8769
9036
  .planned-code__badge { margin-left: auto; border-color: var(--planned-border); color: var(--complete-text); }
8770
- .planned-code__body { margin: 0; border: 0; border-radius: 0; padding: 10px 0; }
8771
- .planned-code__body code { display: block; }
8772
- .planned-code__line { display: grid; grid-template-columns: 4.25rem minmax(max-content, 1fr); min-height: 1.65em; }
9037
+ .planned-code__body { margin: 0; border: 0; border-radius: 0; padding: 10px 0; font: 500 13px/1.55 "SFMono-Regular", "JetBrains Mono", Consolas, monospace; }
9038
+ .planned-code__body code { display: block; font: inherit; }
9039
+ .planned-code__line { display: grid; grid-template-columns: 3.75rem minmax(max-content, 1fr); min-height: 1.55em; }
8773
9040
  .planned-code__line:hover { background: var(--planned-line); }
8774
9041
  .planned-code__number { border-right: 1px solid var(--border-soft); color: var(--planned-gutter); padding: 0 .8rem 0 .5rem; text-align: right; user-select: none; }
8775
9042
  .planned-code__text { padding: 0 1rem; white-space: pre; }
@@ -8787,13 +9054,26 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
8787
9054
  .change-intent__meta code { border: 0; background: transparent; padding: 0; color: var(--muted); }
8788
9055
  .change-intent__context { border-bottom: 1px solid var(--border-soft); background: var(--change-context); }
8789
9056
  .change-intent__context summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; cursor: pointer; padding: 10px 14px; color: var(--body); font-size: 13px; font-weight: 900; list-style-position: inside; }
8790
- .change-intent__line-anchor, .change-intent__proposal-badge { margin-left: auto; border: 1px solid var(--border); border-radius: 999px; padding: 3px 8px; color: var(--muted); font: 800 11px/1.35 "SFMono-Regular", Consolas, monospace; white-space: nowrap; }
8791
- .change-intent__source { margin: 0; border: 0; border-top: 1px solid var(--border-soft); border-radius: 0; padding: 10px 0; background: var(--code-block); }
8792
- .change-intent__source code, .change-intent__proposal-body code { display: block; }
8793
- .change-intent__source-line { display: grid; grid-template-columns: 4.25rem minmax(max-content, 1fr); min-height: 1.65em; }
9057
+ .change-intent__context-stats, .change-intent__line-anchor, .change-intent__proposal-badge, .change-intent__annotation-anchor { margin-left: auto; border: 1px solid var(--border); border-radius: 999px; padding: 3px 8px; color: var(--muted); font: 800 11px/1.35 "SFMono-Regular", Consolas, monospace; white-space: nowrap; }
9058
+ .change-intent__hunk + .change-intent__hunk { border-top: 8px solid var(--panel-strong); }
9059
+ .change-intent__hunk-header { display: flex; align-items: center; gap: 10px; border-top: 1px solid var(--border-soft); padding: 8px 14px; color: var(--muted); font-size: 12px; font-weight: 800; }
9060
+ .change-intent__source { margin: 0; border: 0; border-top: 1px solid var(--border-soft); border-radius: 0; padding: 10px 0; background: var(--code-block); font: 500 13px/1.55 "SFMono-Regular", "JetBrains Mono", Consolas, monospace; }
9061
+ .change-intent__source code, .change-intent__proposal-body code { display: block; font: inherit; }
9062
+ .change-intent__source-line { display: grid; grid-template-columns: 3.75rem minmax(max-content, 1fr); min-height: 1.55em; }
8794
9063
  .change-intent__source-line:hover { background: var(--planned-line); }
8795
9064
  .change-intent__source-number { border-right: 1px solid var(--border-soft); color: var(--planned-gutter); padding: 0 .8rem 0 .5rem; text-align: right; user-select: none; }
8796
9065
  .change-intent__source-text { padding: 0 1rem; white-space: pre; }
9066
+ .change-intent__annotation { border-top: 1px solid var(--border-soft); border-left: 3px solid var(--change-modify); background: var(--change-proposal); padding: 10px 14px 11px; }
9067
+ .change-intent__annotation.is-problem { border-left-color: var(--change-remove); }
9068
+ .change-intent__annotation.is-preserve { border-left-color: var(--change-add); }
9069
+ .change-intent__annotation-header { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; color: var(--body); }
9070
+ .change-intent__annotation-title { font-size: 12px; font-weight: 900; }
9071
+ .change-intent__annotation-badge { border: 1px solid currentColor; border-radius: 999px; color: var(--change-modify); padding: 3px 8px; font-size: 11px; font-weight: 900; line-height: 1.3; }
9072
+ .change-intent__annotation.is-problem .change-intent__annotation-badge { color: var(--change-remove); }
9073
+ .change-intent__annotation.is-preserve .change-intent__annotation-badge { color: var(--change-add); }
9074
+ .change-intent__annotation-body { margin-top: 7px; }
9075
+ .change-intent__annotation-body > *:first-child { margin-top: 0; }
9076
+ .change-intent__annotation-body > *:last-child { margin-bottom: 0; }
8797
9077
  .change-intent__proposal { background: var(--change-proposal); }
8798
9078
  .change-intent__proposal + .change-intent__proposal { border-top: 1px solid var(--border-soft); }
8799
9079
  .change-intent__proposal-header { display: flex; align-items: center; gap: 10px; padding: 10px 14px; color: var(--heading); font-size: 13px; font-weight: 900; }
@@ -8802,11 +9082,22 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
8802
9082
  .change-intent__natural { border-top: 1px solid var(--border-soft); padding: 11px 14px 13px; }
8803
9083
  .change-intent__natural > *:first-child { margin-top: 0; }
8804
9084
  .change-intent__natural > *:last-child { margin-bottom: 0; }
8805
- .change-intent__proposal-body { margin: 0; border: 0; border-top: 1px solid var(--border-soft); border-radius: 0; padding: 11px 14px 13px; background: var(--code-block); }
8806
- .change-intent__proposal-line { display: block; min-height: 1.65em; }
9085
+ .change-intent__proposal-body { margin: 0; border: 0; border-top: 1px solid var(--border-soft); border-radius: 0; padding: 11px 14px 13px; background: var(--code-block); font: 500 13px/1.55 "SFMono-Regular", "JetBrains Mono", Consolas, monospace; }
9086
+ .change-intent__proposal-line { display: block; min-height: 1.55em; }
8807
9087
  .change-intent__proposal-line.is-code { display: grid; grid-template-columns: 2rem minmax(max-content, 1fr); margin: 0 -14px; padding: 0 14px; background: var(--change-code); }
8808
9088
  .change-intent__proposal-mark { color: var(--change-add); font-weight: 900; text-align: center; user-select: none; }
8809
9089
  .change-intent__proposal-text { white-space: pre; }
9090
+ .syntax-comment { color: var(--syntax-comment); font-style: italic; }
9091
+ .syntax-keyword { color: var(--syntax-keyword); font-weight: 700; }
9092
+ .syntax-literal { color: var(--syntax-literal); font-weight: 650; }
9093
+ .syntax-type { color: var(--syntax-type); }
9094
+ .syntax-function { color: var(--syntax-function); }
9095
+ .syntax-string { color: var(--syntax-string); }
9096
+ .syntax-number { color: var(--syntax-number); }
9097
+ .syntax-annotation { color: var(--syntax-annotation); }
9098
+ .syntax-constant { color: var(--syntax-constant); }
9099
+ .syntax-operator { color: var(--syntax-operator); }
9100
+ .syntax-punctuation { color: var(--syntax-punctuation); }
8810
9101
  blockquote { margin: 1rem 0; border-left: 3px solid var(--purple); background: var(--panel-soft); padding: .75rem 1rem; color: var(--body); }
8811
9102
  a { color: var(--link); text-decoration-thickness: .08em; text-underline-offset: .16em; overflow-wrap: anywhere; }
8812
9103
  .table-wrap { max-width: 100%; overflow-x: auto; margin: 1rem 0 1.25rem; border: 1px solid var(--border-soft); border-radius: 10px; background: var(--panel-strong); }
@@ -8840,7 +9131,7 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
8840
9131
  main { padding: 28px 14px 48px; }
8841
9132
  .change-intent__header { align-items: flex-start; flex-direction: column; }
8842
9133
  .change-intent__locator { width: 100%; }
8843
- .change-intent__source-line { grid-template-columns: 3.35rem minmax(max-content, 1fr); }
9134
+ .change-intent__source-line, .planned-code__line { grid-template-columns: 3.25rem minmax(max-content, 1fr); }
8844
9135
  header { display: block; }
8845
9136
  .toolbar { justify-content: flex-start; margin-top: 14px; }
8846
9137
  article { padding: 18px; }
@@ -9000,6 +9291,50 @@ function prdWorkflowAppendAudit(scopedRoot, tapdId, event = {}) {
9000
9291
  } catch (_) {}
9001
9292
  }
9002
9293
 
9294
+ function prdWorkflowReadAuditEntries(scopedRoot, tapdId, limit = 500) {
9295
+ try {
9296
+ const p = prdWorkflowAuditPath(scopedRoot, tapdId);
9297
+ if (!fs.existsSync(p)) return [];
9298
+ return fs.readFileSync(p, "utf-8")
9299
+ .split(/\r?\n/)
9300
+ .filter(Boolean)
9301
+ .slice(-Math.max(1, Number(limit) || 500))
9302
+ .map((line) => {
9303
+ try {
9304
+ return JSON.parse(line);
9305
+ } catch {
9306
+ return null;
9307
+ }
9308
+ })
9309
+ .filter(Boolean);
9310
+ } catch {
9311
+ return [];
9312
+ }
9313
+ }
9314
+
9315
+ function prdWorkflowReadRecentActionAudit(scopedRoot, tapdId, limit = 24) {
9316
+ return prdWorkflowReadAuditEntries(scopedRoot, tapdId, 500)
9317
+ .filter((item) => item?.type === "snapshot-action-change")
9318
+ .slice(-Math.max(1, Number(limit) || 24));
9319
+ }
9320
+
9321
+ function prdWorkflowFirstPointerObservation(scopedRoot, tapdId, snapshot = {}) {
9322
+ const phase = String(snapshot?.phase || "").trim();
9323
+ const pointer = String(snapshot?.pointer || "").trim();
9324
+ if (!phase && !pointer) return "";
9325
+ let earliest = "";
9326
+ for (const entry of prdWorkflowReadAuditEntries(scopedRoot, tapdId, 5000)) {
9327
+ if (entry?.type !== "client-observation-stored") continue;
9328
+ if (phase && String(entry.phase || "").trim() !== phase) continue;
9329
+ if (pointer && String(entry.pointer || "").trim() !== pointer) continue;
9330
+ const candidate = String(entry.observedAt || entry.reportedAt || entry.at || "").trim();
9331
+ const candidateTime = Date.parse(candidate);
9332
+ if (!Number.isFinite(candidateTime)) continue;
9333
+ if (!earliest || candidateTime < Date.parse(earliest)) earliest = candidate;
9334
+ }
9335
+ return earliest;
9336
+ }
9337
+
9003
9338
  function prdWorkflowReadProjectState(scopedRoot, tapdId) {
9004
9339
  return prdWorkflowReadJsonFile(prdWorkflowProjectPath(scopedRoot, tapdId), {
9005
9340
  version: 1,
@@ -9092,6 +9427,160 @@ function prdWorkflowWriteClientObservation(scopedRoot, tapdId, meta, snapshot) {
9092
9427
  });
9093
9428
  }
9094
9429
 
9430
+ const PRD_WORKFLOW_SNAPSHOT_ACTION_ARRAY_KEYS = [
9431
+ "actions",
9432
+ "workflowActions",
9433
+ "workflow_actions",
9434
+ "timeline",
9435
+ "history",
9436
+ ];
9437
+
9438
+ function prdWorkflowSnapshotActionKey(action = {}) {
9439
+ const stageKey = String(
9440
+ action.stageKey ||
9441
+ action.stage_key ||
9442
+ action.stage ||
9443
+ action.actionId ||
9444
+ action.action_id ||
9445
+ action.action ||
9446
+ action.id ||
9447
+ "",
9448
+ ).trim();
9449
+ const issueKey = String(action.issueKey || action.issue_key || action.issue || "").trim();
9450
+ const platform = String(action.platform || "").trim().toLowerCase();
9451
+ return [stageKey, issueKey, platform].filter(Boolean).join("|");
9452
+ }
9453
+
9454
+ function prdWorkflowSnapshotActionTime(action = {}) {
9455
+ return String(
9456
+ action.stageEnteredAt ||
9457
+ action.stage_entered_at ||
9458
+ action.time ||
9459
+ action.at ||
9460
+ action.observedAt ||
9461
+ action.observed_at ||
9462
+ action.startedAt ||
9463
+ action.started_at ||
9464
+ action.completedAt ||
9465
+ action.completed_at ||
9466
+ action.updatedAt ||
9467
+ action.updated_at ||
9468
+ action.createdAt ||
9469
+ action.created_at ||
9470
+ "",
9471
+ ).trim();
9472
+ }
9473
+
9474
+ function prdWorkflowSnapshotSourceActionTime(action = {}) {
9475
+ return String(
9476
+ action.time ||
9477
+ action.at ||
9478
+ action.observedAt ||
9479
+ action.observed_at ||
9480
+ action.startedAt ||
9481
+ action.started_at ||
9482
+ action.completedAt ||
9483
+ action.completed_at ||
9484
+ action.updatedAt ||
9485
+ action.updated_at ||
9486
+ action.createdAt ||
9487
+ action.created_at ||
9488
+ "",
9489
+ ).trim();
9490
+ }
9491
+
9492
+ function prdWorkflowSnapshotActionMap(snapshot = {}) {
9493
+ const out = new Map();
9494
+ for (const key of PRD_WORKFLOW_SNAPSHOT_ACTION_ARRAY_KEYS) {
9495
+ const rows = Array.isArray(snapshot?.[key]) ? snapshot[key] : [];
9496
+ for (const action of rows) {
9497
+ if (!action || typeof action !== "object" || Array.isArray(action)) continue;
9498
+ const actionKey = prdWorkflowSnapshotActionKey(action);
9499
+ if (actionKey && !out.has(actionKey)) out.set(actionKey, action);
9500
+ }
9501
+ }
9502
+ return out;
9503
+ }
9504
+
9505
+ function prdWorkflowSnapshotActionChanges(previousSnapshot = {}, nextSnapshot = {}) {
9506
+ const previous = prdWorkflowSnapshotActionMap(previousSnapshot);
9507
+ const next = prdWorkflowSnapshotActionMap(nextSnapshot);
9508
+ const changes = [];
9509
+ const compact = (kind, action, previousAction = null) => ({
9510
+ kind,
9511
+ stageKey: String(action?.stageKey || action?.stage_key || action?.stage || action?.id || "").trim(),
9512
+ issueKey: String(action?.issueKey || action?.issue_key || action?.issue || "").trim(),
9513
+ platform: String(action?.platform || "").trim(),
9514
+ title: String(action?.title || action?.label || action?.name || "").trim(),
9515
+ status: String(action?.status || "").trim(),
9516
+ previousStatus: String(previousAction?.status || "").trim(),
9517
+ actionAt: prdWorkflowSnapshotActionTime(action),
9518
+ previousActionAt: prdWorkflowSnapshotActionTime(previousAction || {}),
9519
+ sourceActionAt: prdWorkflowSnapshotSourceActionTime(action),
9520
+ previousSourceActionAt: prdWorkflowSnapshotSourceActionTime(previousAction || {}),
9521
+ });
9522
+ for (const [key, action] of next) {
9523
+ const previousAction = previous.get(key);
9524
+ if (!previousAction) {
9525
+ changes.push(compact("added", action));
9526
+ continue;
9527
+ }
9528
+ const statusChanged = String(previousAction.status || "") !== String(action.status || "");
9529
+ const timeChanged =
9530
+ prdWorkflowSnapshotActionTime(previousAction) !== prdWorkflowSnapshotActionTime(action) ||
9531
+ prdWorkflowSnapshotSourceActionTime(previousAction) !== prdWorkflowSnapshotSourceActionTime(action);
9532
+ const titleChanged = String(previousAction.title || previousAction.label || "") !== String(action.title || action.label || "");
9533
+ if (statusChanged || timeChanged || titleChanged) {
9534
+ changes.push(compact(
9535
+ statusChanged ? "status-changed" : timeChanged ? "time-changed" : "title-changed",
9536
+ action,
9537
+ previousAction,
9538
+ ));
9539
+ }
9540
+ }
9541
+ for (const [key, action] of previous) {
9542
+ if (!next.has(key)) changes.push(compact("removed", action, action));
9543
+ }
9544
+ return changes.slice(0, 80);
9545
+ }
9546
+
9547
+ function prdWorkflowStampCurrentActionEntryTimes(scopedRoot, tapdId, snapshot = {}, clientState = {}, meta = {}) {
9548
+ const existingTimes = new Map();
9549
+ for (const client of Object.values(clientState?.clients || {})) {
9550
+ const observedAt = String(client?.observedAt || client?.reportedAt || "").trim();
9551
+ for (const [key, action] of prdWorkflowSnapshotActionMap(client?.snapshot || {})) {
9552
+ if (String(action?.status || "").trim().toLowerCase() !== "current") continue;
9553
+ const value = String(action.stageEnteredAt || action.stage_entered_at || observedAt).trim();
9554
+ if (!value || !Number.isFinite(Date.parse(value))) continue;
9555
+ const previous = existingTimes.get(key);
9556
+ if (!previous || Date.parse(value) < Date.parse(previous)) existingTimes.set(key, value);
9557
+ }
9558
+ }
9559
+ const auditedAt = prdWorkflowFirstPointerObservation(scopedRoot, tapdId, snapshot);
9560
+ const observedAt = String(meta.observedAt || meta.reportedAt || new Date().toISOString()).trim();
9561
+ const stamp = (action) => {
9562
+ if (!action || typeof action !== "object" || Array.isArray(action)) return action;
9563
+ if (String(action.status || "").trim().toLowerCase() !== "current") return action;
9564
+ const actionKey = prdWorkflowSnapshotActionKey(action);
9565
+ const candidates = [
9566
+ String(action.stageEnteredAt || action.stage_entered_at || "").trim(),
9567
+ existingTimes.get(actionKey) || "",
9568
+ auditedAt,
9569
+ observedAt,
9570
+ ].filter((value) => Number.isFinite(Date.parse(value)));
9571
+ const stageEnteredAt = candidates.sort((left, right) => Date.parse(left) - Date.parse(right))[0] || observedAt;
9572
+ return {
9573
+ ...action,
9574
+ stageEnteredAt,
9575
+ };
9576
+ };
9577
+ const next = { ...snapshot };
9578
+ for (const key of PRD_WORKFLOW_SNAPSHOT_ACTION_ARRAY_KEYS) {
9579
+ if (Array.isArray(snapshot?.[key])) next[key] = snapshot[key].map(stamp);
9580
+ }
9581
+ return next;
9582
+ }
9583
+
9095
9584
  const PRD_WORKFLOW_PROJECTION_SOURCE_KEYS = new Set([
9096
9585
  "projectionMode",
9097
9586
  "projectCacheScope",
@@ -9121,6 +9610,7 @@ function prdWorkflowStoredObservationSnapshot(snapshot, sourcePatch = {}) {
9121
9610
  delete clean.collaboration;
9122
9611
  delete clean.clientObservations;
9123
9612
  delete clean.clients;
9613
+ delete clean.snapshotAudit;
9124
9614
  clean.sources = prdWorkflowStoredObservationSources(snapshot.sources, sourcePatch);
9125
9615
  return clean;
9126
9616
  }
@@ -9351,6 +9841,7 @@ function prdWorkflowMaterializeSnapshot(root, scopedRoot, tapdId, userCtx = {},
9351
9841
  revision: String(materialized.revision || ""),
9352
9842
  },
9353
9843
  ];
9844
+ materialized.snapshotAudit = prdWorkflowReadRecentActionAudit(scopedRoot, tapdId);
9354
9845
  prdWorkflowAppendAudit(scopedRoot, tapdId, {
9355
9846
  type: "projection-materialized",
9356
9847
  flowSource,
@@ -11969,7 +12460,21 @@ export function startUiServer({
11969
12460
  issueKey: reportMeta.issueKey,
11970
12461
  stageKey: reportMeta.stageKey,
11971
12462
  };
11972
- const storedObservationSnapshot = prdWorkflowStoredObservationSnapshot(normalizedSnapshot, reportSource);
12463
+ const existingClientState = prdWorkflowReadClientState(scopedRoot, tapdId);
12464
+ const existingClientId = prdWorkflowSafeStateId(reportMeta.clientId || "anonymous");
12465
+ const previousClientSnapshot = existingClientState.clients?.[existingClientId]?.snapshot || null;
12466
+ const stampedSnapshot = prdWorkflowStampCurrentActionEntryTimes(
12467
+ scopedRoot,
12468
+ tapdId,
12469
+ normalizedSnapshot,
12470
+ existingClientState,
12471
+ reportMeta,
12472
+ );
12473
+ const storedObservationSnapshot = prdWorkflowStoredObservationSnapshot(stampedSnapshot, reportSource);
12474
+ const actionChanges = prdWorkflowSnapshotActionChanges(
12475
+ previousClientSnapshot || {},
12476
+ storedObservationSnapshot,
12477
+ );
11973
12478
  prdWorkflowWriteClientObservation(scopedRoot, tapdId, reportMeta, storedObservationSnapshot);
11974
12479
  prdWorkflowAppendAudit(scopedRoot, tapdId, {
11975
12480
  type: "client-observation-stored",
@@ -11988,12 +12493,51 @@ export function startUiServer({
11988
12493
  persistence: "runtime",
11989
12494
  note: "ordinary current snapshot stored as client observation; it must not overwrite project state",
11990
12495
  });
12496
+ for (const change of actionChanges) {
12497
+ const changeLabel = {
12498
+ added: "新增",
12499
+ removed: "移除",
12500
+ "status-changed": "状态变更",
12501
+ "time-changed": "时间更正",
12502
+ "title-changed": "标题变更",
12503
+ }[change.kind] || "变更";
12504
+ prdWorkflowAppendAudit(scopedRoot, tapdId, {
12505
+ type: "snapshot-action-change",
12506
+ change: change.kind,
12507
+ title: `Workflow Action ${changeLabel}${change.title ? `:${change.title}` : ""}`,
12508
+ detail: [
12509
+ change.stageKey,
12510
+ change.previousStatus && change.previousStatus !== change.status
12511
+ ? `${change.previousStatus} -> ${change.status}`
12512
+ : change.status,
12513
+ change.previousActionAt && change.previousActionAt !== change.actionAt
12514
+ ? `${change.previousActionAt} -> ${change.actionAt || "无时间"}`
12515
+ : change.actionAt,
12516
+ change.previousSourceActionAt !== change.sourceActionAt
12517
+ ? `来源时间 ${change.previousSourceActionAt || "无"} -> ${change.sourceActionAt || "无"}`
12518
+ : "",
12519
+ ].filter(Boolean).join(" · "),
12520
+ auditStatus: "observed",
12521
+ truth: "audit",
12522
+ authority: "agentflow",
12523
+ persistence: "runtime",
12524
+ clientId: reportMeta.clientId,
12525
+ userId: reportMeta.userId,
12526
+ observedAt: reportMeta.observedAt,
12527
+ reportedAt: reportMeta.reportedAt,
12528
+ revision: String(storedObservationSnapshot.revision || ""),
12529
+ previousRevision: String(previousClientSnapshot?.revision || ""),
12530
+ pointer: String(storedObservationSnapshot.pointer || ""),
12531
+ previousPointer: String(previousClientSnapshot?.pointer || ""),
12532
+ ...change,
12533
+ });
12534
+ }
11991
12535
 
11992
12536
  const projectFactSource = reportMeta.scope === "project"
11993
12537
  ? prdWorkflowProjectFactSource(payload, rawSnapshot)
11994
12538
  : null;
11995
12539
  const projectFactSnapshot = projectFactSource
11996
- ? prdWorkflowStoredObservationSnapshot(normalizedSnapshot, {
12540
+ ? prdWorkflowStoredObservationSnapshot(stampedSnapshot, {
11997
12541
  ...reportSource,
11998
12542
  ...projectFactSource,
11999
12543
  })