@fieldwangai/agentflow 0.1.124 → 0.1.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -8141,7 +8141,7 @@ function prdWorkflowReviewRenderFrontmatter(frontmatter) {
|
|
|
8141
8141
|
}
|
|
8142
8142
|
}
|
|
8143
8143
|
flush();
|
|
8144
|
-
return `<details class="frontmatter"
|
|
8144
|
+
return `<details class="frontmatter"><summary>文档元数据</summary><table><colgroup><col class="frontmatter-key-column"><col></colgroup>${rows}</table></details>`;
|
|
8145
8145
|
}
|
|
8146
8146
|
|
|
8147
8147
|
function prdWorkflowReviewRenderTable(lines) {
|
|
@@ -8386,7 +8386,8 @@ function prdWorkflowReviewParseChangeIntent(lines, startIndex) {
|
|
|
8386
8386
|
base: "",
|
|
8387
8387
|
insertNear: "",
|
|
8388
8388
|
destination: "",
|
|
8389
|
-
|
|
8389
|
+
references: [],
|
|
8390
|
+
annotations: [],
|
|
8390
8391
|
proposals: [],
|
|
8391
8392
|
};
|
|
8392
8393
|
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
|
@@ -8415,11 +8416,31 @@ function prdWorkflowReviewParseChangeIntent(lines, startIndex) {
|
|
|
8415
8416
|
end += 1;
|
|
8416
8417
|
}
|
|
8417
8418
|
if (end >= lines.length) return null;
|
|
8418
|
-
change.
|
|
8419
|
+
change.references.push({
|
|
8419
8420
|
startLine: Number(reference[1]),
|
|
8420
8421
|
endLine: Number(reference[2] || reference[1]),
|
|
8421
8422
|
lines: body,
|
|
8422
|
-
};
|
|
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
|
+
});
|
|
8423
8444
|
i = end;
|
|
8424
8445
|
continue;
|
|
8425
8446
|
}
|
|
@@ -8461,6 +8482,11 @@ function prdWorkflowReviewRenderChangeIntent(change) {
|
|
|
8461
8482
|
pseudocode: "方案伪代码",
|
|
8462
8483
|
code: "拟议代码 · 未写入",
|
|
8463
8484
|
};
|
|
8485
|
+
const annotationLabels = {
|
|
8486
|
+
problem: "当前问题",
|
|
8487
|
+
change: "计划修改",
|
|
8488
|
+
preserve: "保持不变",
|
|
8489
|
+
};
|
|
8464
8490
|
const operation = operationLabels[change.operation] || "变更";
|
|
8465
8491
|
const target = targetLabels[change.target] || "目标";
|
|
8466
8492
|
const locator = change.module || change.file || "未定位";
|
|
@@ -8475,24 +8501,66 @@ function prdWorkflowReviewRenderChangeIntent(change) {
|
|
|
8475
8501
|
safeDestination ? `<span><strong>移动到</strong> <code>${safeDestination}</code></span>` : "",
|
|
8476
8502
|
].filter(Boolean).join("");
|
|
8477
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
|
+
}`;
|
|
8478
8514
|
let referenceHtml = "";
|
|
8479
|
-
if (
|
|
8480
|
-
const
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
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>
|
|
8496
8564
|
</details>`;
|
|
8497
8565
|
}
|
|
8498
8566
|
|
|
@@ -8546,7 +8614,7 @@ function prdWorkflowReviewRenderChangeIntent(change) {
|
|
|
8546
8614
|
function prdWorkflowReviewMarkdownLinesToHtml(lines) {
|
|
8547
8615
|
const html = [];
|
|
8548
8616
|
let paragraph = [];
|
|
8549
|
-
let list =
|
|
8617
|
+
let list = null;
|
|
8550
8618
|
let code = null;
|
|
8551
8619
|
const flushParagraph = () => {
|
|
8552
8620
|
if (!paragraph.length) return;
|
|
@@ -8554,9 +8622,11 @@ function prdWorkflowReviewMarkdownLinesToHtml(lines) {
|
|
|
8554
8622
|
paragraph = [];
|
|
8555
8623
|
};
|
|
8556
8624
|
const flushList = () => {
|
|
8557
|
-
if (!list
|
|
8558
|
-
|
|
8559
|
-
list =
|
|
8625
|
+
if (!list?.items?.length) return;
|
|
8626
|
+
const tag = list.ordered ? "ol" : "ul";
|
|
8627
|
+
const start = list.ordered && list.start !== 1 ? ` start="${list.start}"` : "";
|
|
8628
|
+
html.push(`<${tag}${start}>${list.items.map((item) => `<li>${item}</li>`).join("")}</${tag}>`);
|
|
8629
|
+
list = null;
|
|
8560
8630
|
};
|
|
8561
8631
|
const flushBlocks = () => {
|
|
8562
8632
|
flushParagraph();
|
|
@@ -8645,13 +8715,31 @@ function prdWorkflowReviewMarkdownLinesToHtml(lines) {
|
|
|
8645
8715
|
html.push(`<blockquote>${prdWorkflowReviewInlineMarkdown(quote[1])}</blockquote>`);
|
|
8646
8716
|
continue;
|
|
8647
8717
|
}
|
|
8648
|
-
const
|
|
8649
|
-
if (
|
|
8718
|
+
const listItem = line.match(/^(\s*)([-*]|\d+[.)])\s+(?:\[( |x|X)\]\s+)?(.+)$/);
|
|
8719
|
+
if (listItem) {
|
|
8650
8720
|
flushParagraph();
|
|
8651
|
-
const
|
|
8652
|
-
list.
|
|
8721
|
+
const ordered = /^\d/.test(listItem[2]);
|
|
8722
|
+
if (list && list.ordered !== ordered) flushList();
|
|
8723
|
+
if (!list) {
|
|
8724
|
+
list = {
|
|
8725
|
+
ordered,
|
|
8726
|
+
start: ordered ? Number.parseInt(listItem[2], 10) || 1 : 1,
|
|
8727
|
+
indent: listItem[1].length,
|
|
8728
|
+
items: [],
|
|
8729
|
+
};
|
|
8730
|
+
}
|
|
8731
|
+
const checked = listItem[3]
|
|
8732
|
+
? `<input type="checkbox" disabled${listItem[3].toLowerCase() === "x" ? " checked" : ""}> `
|
|
8733
|
+
: "";
|
|
8734
|
+
list.items.push(`${checked}${prdWorkflowReviewInlineMarkdown(listItem[4])}`);
|
|
8735
|
+
continue;
|
|
8736
|
+
}
|
|
8737
|
+
if (list?.items?.length && /^\s{2,}\S/.test(line)) {
|
|
8738
|
+
const lastIndex = list.items.length - 1;
|
|
8739
|
+
list.items[lastIndex] += ` ${prdWorkflowReviewInlineMarkdown(trimmed)}`;
|
|
8653
8740
|
continue;
|
|
8654
8741
|
}
|
|
8742
|
+
if (list) flushList();
|
|
8655
8743
|
paragraph.push(trimmed);
|
|
8656
8744
|
}
|
|
8657
8745
|
if (code) html.push(`<pre><code>${htmlEscapeAttribute(prdWorkflowReviewNormalizeText(code.lines.join("\n")))}</code></pre>`);
|
|
@@ -8947,18 +9035,20 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
8947
9035
|
.raw:hover, .theme-toggle:hover { border-color: var(--interactive); color: var(--link); }
|
|
8948
9036
|
.theme-toggle { cursor: pointer; font-family: inherit; }
|
|
8949
9037
|
.lifecycle { margin-top: 10px; display: inline-flex; max-width: 100%; border: 1px solid var(--border); border-radius: 999px; background: var(--button); color: var(--muted); padding: 6px 10px; font-size: 12px; font-weight: 800; line-height: 1.35; overflow-wrap: anywhere; }
|
|
8950
|
-
article { min-width: 0; border:
|
|
9038
|
+
article { min-width: 0; border: 0; border-radius: 14px; background: var(--panel); box-shadow: 0 18px 50px var(--shadow); padding: clamp(20px, 4vw, 34px); }
|
|
8951
9039
|
article > *:first-child { margin-top: 0; }
|
|
8952
9040
|
article > *:last-child { margin-bottom: 0; }
|
|
8953
9041
|
h2, h3, h4, h5, h6 { margin: 1.7em 0 .65em; line-height: 1.25; letter-spacing: 0; color: var(--heading); overflow-wrap: anywhere; }
|
|
8954
9042
|
h2 { padding-bottom: .4rem; border-bottom: 1px solid var(--border-soft); font-size: 1.5rem; }
|
|
8955
9043
|
h3 { font-size: 1.2rem; }
|
|
8956
|
-
p, li, td, th, blockquote { font-size: 15px; line-height: 1.
|
|
9044
|
+
p, li, td, th, blockquote { font-size: 15px; line-height: 1.75; overflow-wrap: anywhere; word-break: break-word; }
|
|
8957
9045
|
p { margin: .75rem 0; color: var(--body); }
|
|
8958
|
-
ul { margin: .
|
|
8959
|
-
|
|
9046
|
+
ul, ol { margin: .75rem 0 1.1rem; padding-left: 1.55rem; }
|
|
9047
|
+
ol { padding-left: 1.8rem; }
|
|
9048
|
+
li { margin: .48rem 0; padding-left: .12rem; color: var(--body); }
|
|
9049
|
+
li::marker { color: var(--interactive); font-weight: 750; }
|
|
8960
9050
|
li input { margin-right: .38rem; transform: translateY(1px); }
|
|
8961
|
-
code { display: inline; max-width: 100%; border: 1px solid var(--border-soft); border-radius:
|
|
9051
|
+
code { display: inline; max-width: 100%; border: 1px solid var(--border-soft); border-radius: 4px; background: color-mix(in srgb, var(--interactive) 8%, transparent); color: var(--code-inline); padding: .06rem .24rem; font-family: "SFMono-Regular", Consolas, monospace; font-size: .9em; white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
|
|
8962
9052
|
pre { max-width: 100%; overflow: auto; border: 1px solid var(--border); border-radius: 10px; background: var(--code-block); padding: 16px; line-height: 1.65; }
|
|
8963
9053
|
pre code { border: 0; background: transparent; color: var(--code-block-text); padding: 0; white-space: pre; overflow-wrap: normal; word-break: normal; }
|
|
8964
9054
|
.planned-code { max-width: 100%; margin: 1rem 0 1.2rem; overflow: hidden; border: 1px solid var(--planned-border); border-radius: 10px; background: var(--code-block); }
|
|
@@ -8986,13 +9076,26 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
8986
9076
|
.change-intent__meta code { border: 0; background: transparent; padding: 0; color: var(--muted); }
|
|
8987
9077
|
.change-intent__context { border-bottom: 1px solid var(--border-soft); background: var(--change-context); }
|
|
8988
9078
|
.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; }
|
|
8989
|
-
.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; }
|
|
9079
|
+
.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; }
|
|
9080
|
+
.change-intent__hunk + .change-intent__hunk { border-top: 8px solid var(--panel-strong); }
|
|
9081
|
+
.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; }
|
|
8990
9082
|
.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; }
|
|
8991
9083
|
.change-intent__source code, .change-intent__proposal-body code { display: block; font: inherit; }
|
|
8992
9084
|
.change-intent__source-line { display: grid; grid-template-columns: 3.75rem minmax(max-content, 1fr); min-height: 1.55em; }
|
|
8993
9085
|
.change-intent__source-line:hover { background: var(--planned-line); }
|
|
8994
9086
|
.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; }
|
|
8995
9087
|
.change-intent__source-text { padding: 0 1rem; white-space: pre; }
|
|
9088
|
+
.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; }
|
|
9089
|
+
.change-intent__annotation.is-problem { border-left-color: var(--change-remove); }
|
|
9090
|
+
.change-intent__annotation.is-preserve { border-left-color: var(--change-add); }
|
|
9091
|
+
.change-intent__annotation-header { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; color: var(--body); }
|
|
9092
|
+
.change-intent__annotation-title { font-size: 12px; font-weight: 900; }
|
|
9093
|
+
.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; }
|
|
9094
|
+
.change-intent__annotation.is-problem .change-intent__annotation-badge { color: var(--change-remove); }
|
|
9095
|
+
.change-intent__annotation.is-preserve .change-intent__annotation-badge { color: var(--change-add); }
|
|
9096
|
+
.change-intent__annotation-body { margin-top: 7px; }
|
|
9097
|
+
.change-intent__annotation-body > *:first-child { margin-top: 0; }
|
|
9098
|
+
.change-intent__annotation-body > *:last-child { margin-bottom: 0; }
|
|
8996
9099
|
.change-intent__proposal { background: var(--change-proposal); }
|
|
8997
9100
|
.change-intent__proposal + .change-intent__proposal { border-top: 1px solid var(--border-soft); }
|
|
8998
9101
|
.change-intent__proposal-header { display: flex; align-items: center; gap: 10px; padding: 10px 14px; color: var(--heading); font-size: 13px; font-weight: 900; }
|
|
@@ -9027,7 +9130,8 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
9027
9130
|
.frontmatter { margin: 0 0 1.35rem; border: 1px solid var(--border-soft); border-radius: 10px; background: var(--panel-strong); padding: .75rem .9rem; }
|
|
9028
9131
|
.frontmatter summary { cursor: pointer; color: var(--body); font-weight: 800; }
|
|
9029
9132
|
.frontmatter table { min-width: 0; margin-top: .7rem; }
|
|
9030
|
-
.frontmatter
|
|
9133
|
+
.frontmatter-key-column { width: clamp(10rem, 22%, 16rem); }
|
|
9134
|
+
.frontmatter th { background: var(--panel-soft); color: var(--body); }
|
|
9031
9135
|
.frontmatter-list { margin: 0; padding-left: 1.1rem; }
|
|
9032
9136
|
.action-index { position: sticky; top: 10px; z-index: 4; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 1rem 0 1.25rem; border: 1px solid var(--border); border-radius: 12px; background: var(--panel-strong); box-shadow: 0 8px 22px var(--shadow); padding: 10px 12px; }
|
|
9033
9137
|
.action-index__label { margin-right: 2px; color: var(--muted); font-size: 12px; font-weight: 800; }
|
|
@@ -9210,6 +9314,50 @@ function prdWorkflowAppendAudit(scopedRoot, tapdId, event = {}) {
|
|
|
9210
9314
|
} catch (_) {}
|
|
9211
9315
|
}
|
|
9212
9316
|
|
|
9317
|
+
function prdWorkflowReadAuditEntries(scopedRoot, tapdId, limit = 500) {
|
|
9318
|
+
try {
|
|
9319
|
+
const p = prdWorkflowAuditPath(scopedRoot, tapdId);
|
|
9320
|
+
if (!fs.existsSync(p)) return [];
|
|
9321
|
+
return fs.readFileSync(p, "utf-8")
|
|
9322
|
+
.split(/\r?\n/)
|
|
9323
|
+
.filter(Boolean)
|
|
9324
|
+
.slice(-Math.max(1, Number(limit) || 500))
|
|
9325
|
+
.map((line) => {
|
|
9326
|
+
try {
|
|
9327
|
+
return JSON.parse(line);
|
|
9328
|
+
} catch {
|
|
9329
|
+
return null;
|
|
9330
|
+
}
|
|
9331
|
+
})
|
|
9332
|
+
.filter(Boolean);
|
|
9333
|
+
} catch {
|
|
9334
|
+
return [];
|
|
9335
|
+
}
|
|
9336
|
+
}
|
|
9337
|
+
|
|
9338
|
+
function prdWorkflowReadRecentActionAudit(scopedRoot, tapdId, limit = 24) {
|
|
9339
|
+
return prdWorkflowReadAuditEntries(scopedRoot, tapdId, 500)
|
|
9340
|
+
.filter((item) => item?.type === "snapshot-action-change")
|
|
9341
|
+
.slice(-Math.max(1, Number(limit) || 24));
|
|
9342
|
+
}
|
|
9343
|
+
|
|
9344
|
+
function prdWorkflowFirstPointerObservation(scopedRoot, tapdId, snapshot = {}) {
|
|
9345
|
+
const phase = String(snapshot?.phase || "").trim();
|
|
9346
|
+
const pointer = String(snapshot?.pointer || "").trim();
|
|
9347
|
+
if (!phase && !pointer) return "";
|
|
9348
|
+
let earliest = "";
|
|
9349
|
+
for (const entry of prdWorkflowReadAuditEntries(scopedRoot, tapdId, 5000)) {
|
|
9350
|
+
if (entry?.type !== "client-observation-stored") continue;
|
|
9351
|
+
if (phase && String(entry.phase || "").trim() !== phase) continue;
|
|
9352
|
+
if (pointer && String(entry.pointer || "").trim() !== pointer) continue;
|
|
9353
|
+
const candidate = String(entry.observedAt || entry.reportedAt || entry.at || "").trim();
|
|
9354
|
+
const candidateTime = Date.parse(candidate);
|
|
9355
|
+
if (!Number.isFinite(candidateTime)) continue;
|
|
9356
|
+
if (!earliest || candidateTime < Date.parse(earliest)) earliest = candidate;
|
|
9357
|
+
}
|
|
9358
|
+
return earliest;
|
|
9359
|
+
}
|
|
9360
|
+
|
|
9213
9361
|
function prdWorkflowReadProjectState(scopedRoot, tapdId) {
|
|
9214
9362
|
return prdWorkflowReadJsonFile(prdWorkflowProjectPath(scopedRoot, tapdId), {
|
|
9215
9363
|
version: 1,
|
|
@@ -9302,6 +9450,160 @@ function prdWorkflowWriteClientObservation(scopedRoot, tapdId, meta, snapshot) {
|
|
|
9302
9450
|
});
|
|
9303
9451
|
}
|
|
9304
9452
|
|
|
9453
|
+
const PRD_WORKFLOW_SNAPSHOT_ACTION_ARRAY_KEYS = [
|
|
9454
|
+
"actions",
|
|
9455
|
+
"workflowActions",
|
|
9456
|
+
"workflow_actions",
|
|
9457
|
+
"timeline",
|
|
9458
|
+
"history",
|
|
9459
|
+
];
|
|
9460
|
+
|
|
9461
|
+
function prdWorkflowSnapshotActionKey(action = {}) {
|
|
9462
|
+
const stageKey = String(
|
|
9463
|
+
action.stageKey ||
|
|
9464
|
+
action.stage_key ||
|
|
9465
|
+
action.stage ||
|
|
9466
|
+
action.actionId ||
|
|
9467
|
+
action.action_id ||
|
|
9468
|
+
action.action ||
|
|
9469
|
+
action.id ||
|
|
9470
|
+
"",
|
|
9471
|
+
).trim();
|
|
9472
|
+
const issueKey = String(action.issueKey || action.issue_key || action.issue || "").trim();
|
|
9473
|
+
const platform = String(action.platform || "").trim().toLowerCase();
|
|
9474
|
+
return [stageKey, issueKey, platform].filter(Boolean).join("|");
|
|
9475
|
+
}
|
|
9476
|
+
|
|
9477
|
+
function prdWorkflowSnapshotActionTime(action = {}) {
|
|
9478
|
+
return String(
|
|
9479
|
+
action.stageEnteredAt ||
|
|
9480
|
+
action.stage_entered_at ||
|
|
9481
|
+
action.time ||
|
|
9482
|
+
action.at ||
|
|
9483
|
+
action.observedAt ||
|
|
9484
|
+
action.observed_at ||
|
|
9485
|
+
action.startedAt ||
|
|
9486
|
+
action.started_at ||
|
|
9487
|
+
action.completedAt ||
|
|
9488
|
+
action.completed_at ||
|
|
9489
|
+
action.updatedAt ||
|
|
9490
|
+
action.updated_at ||
|
|
9491
|
+
action.createdAt ||
|
|
9492
|
+
action.created_at ||
|
|
9493
|
+
"",
|
|
9494
|
+
).trim();
|
|
9495
|
+
}
|
|
9496
|
+
|
|
9497
|
+
function prdWorkflowSnapshotSourceActionTime(action = {}) {
|
|
9498
|
+
return String(
|
|
9499
|
+
action.time ||
|
|
9500
|
+
action.at ||
|
|
9501
|
+
action.observedAt ||
|
|
9502
|
+
action.observed_at ||
|
|
9503
|
+
action.startedAt ||
|
|
9504
|
+
action.started_at ||
|
|
9505
|
+
action.completedAt ||
|
|
9506
|
+
action.completed_at ||
|
|
9507
|
+
action.updatedAt ||
|
|
9508
|
+
action.updated_at ||
|
|
9509
|
+
action.createdAt ||
|
|
9510
|
+
action.created_at ||
|
|
9511
|
+
"",
|
|
9512
|
+
).trim();
|
|
9513
|
+
}
|
|
9514
|
+
|
|
9515
|
+
function prdWorkflowSnapshotActionMap(snapshot = {}) {
|
|
9516
|
+
const out = new Map();
|
|
9517
|
+
for (const key of PRD_WORKFLOW_SNAPSHOT_ACTION_ARRAY_KEYS) {
|
|
9518
|
+
const rows = Array.isArray(snapshot?.[key]) ? snapshot[key] : [];
|
|
9519
|
+
for (const action of rows) {
|
|
9520
|
+
if (!action || typeof action !== "object" || Array.isArray(action)) continue;
|
|
9521
|
+
const actionKey = prdWorkflowSnapshotActionKey(action);
|
|
9522
|
+
if (actionKey && !out.has(actionKey)) out.set(actionKey, action);
|
|
9523
|
+
}
|
|
9524
|
+
}
|
|
9525
|
+
return out;
|
|
9526
|
+
}
|
|
9527
|
+
|
|
9528
|
+
function prdWorkflowSnapshotActionChanges(previousSnapshot = {}, nextSnapshot = {}) {
|
|
9529
|
+
const previous = prdWorkflowSnapshotActionMap(previousSnapshot);
|
|
9530
|
+
const next = prdWorkflowSnapshotActionMap(nextSnapshot);
|
|
9531
|
+
const changes = [];
|
|
9532
|
+
const compact = (kind, action, previousAction = null) => ({
|
|
9533
|
+
kind,
|
|
9534
|
+
stageKey: String(action?.stageKey || action?.stage_key || action?.stage || action?.id || "").trim(),
|
|
9535
|
+
issueKey: String(action?.issueKey || action?.issue_key || action?.issue || "").trim(),
|
|
9536
|
+
platform: String(action?.platform || "").trim(),
|
|
9537
|
+
title: String(action?.title || action?.label || action?.name || "").trim(),
|
|
9538
|
+
status: String(action?.status || "").trim(),
|
|
9539
|
+
previousStatus: String(previousAction?.status || "").trim(),
|
|
9540
|
+
actionAt: prdWorkflowSnapshotActionTime(action),
|
|
9541
|
+
previousActionAt: prdWorkflowSnapshotActionTime(previousAction || {}),
|
|
9542
|
+
sourceActionAt: prdWorkflowSnapshotSourceActionTime(action),
|
|
9543
|
+
previousSourceActionAt: prdWorkflowSnapshotSourceActionTime(previousAction || {}),
|
|
9544
|
+
});
|
|
9545
|
+
for (const [key, action] of next) {
|
|
9546
|
+
const previousAction = previous.get(key);
|
|
9547
|
+
if (!previousAction) {
|
|
9548
|
+
changes.push(compact("added", action));
|
|
9549
|
+
continue;
|
|
9550
|
+
}
|
|
9551
|
+
const statusChanged = String(previousAction.status || "") !== String(action.status || "");
|
|
9552
|
+
const timeChanged =
|
|
9553
|
+
prdWorkflowSnapshotActionTime(previousAction) !== prdWorkflowSnapshotActionTime(action) ||
|
|
9554
|
+
prdWorkflowSnapshotSourceActionTime(previousAction) !== prdWorkflowSnapshotSourceActionTime(action);
|
|
9555
|
+
const titleChanged = String(previousAction.title || previousAction.label || "") !== String(action.title || action.label || "");
|
|
9556
|
+
if (statusChanged || timeChanged || titleChanged) {
|
|
9557
|
+
changes.push(compact(
|
|
9558
|
+
statusChanged ? "status-changed" : timeChanged ? "time-changed" : "title-changed",
|
|
9559
|
+
action,
|
|
9560
|
+
previousAction,
|
|
9561
|
+
));
|
|
9562
|
+
}
|
|
9563
|
+
}
|
|
9564
|
+
for (const [key, action] of previous) {
|
|
9565
|
+
if (!next.has(key)) changes.push(compact("removed", action, action));
|
|
9566
|
+
}
|
|
9567
|
+
return changes.slice(0, 80);
|
|
9568
|
+
}
|
|
9569
|
+
|
|
9570
|
+
function prdWorkflowStampCurrentActionEntryTimes(scopedRoot, tapdId, snapshot = {}, clientState = {}, meta = {}) {
|
|
9571
|
+
const existingTimes = new Map();
|
|
9572
|
+
for (const client of Object.values(clientState?.clients || {})) {
|
|
9573
|
+
const observedAt = String(client?.observedAt || client?.reportedAt || "").trim();
|
|
9574
|
+
for (const [key, action] of prdWorkflowSnapshotActionMap(client?.snapshot || {})) {
|
|
9575
|
+
if (String(action?.status || "").trim().toLowerCase() !== "current") continue;
|
|
9576
|
+
const value = String(action.stageEnteredAt || action.stage_entered_at || observedAt).trim();
|
|
9577
|
+
if (!value || !Number.isFinite(Date.parse(value))) continue;
|
|
9578
|
+
const previous = existingTimes.get(key);
|
|
9579
|
+
if (!previous || Date.parse(value) < Date.parse(previous)) existingTimes.set(key, value);
|
|
9580
|
+
}
|
|
9581
|
+
}
|
|
9582
|
+
const auditedAt = prdWorkflowFirstPointerObservation(scopedRoot, tapdId, snapshot);
|
|
9583
|
+
const observedAt = String(meta.observedAt || meta.reportedAt || new Date().toISOString()).trim();
|
|
9584
|
+
const stamp = (action) => {
|
|
9585
|
+
if (!action || typeof action !== "object" || Array.isArray(action)) return action;
|
|
9586
|
+
if (String(action.status || "").trim().toLowerCase() !== "current") return action;
|
|
9587
|
+
const actionKey = prdWorkflowSnapshotActionKey(action);
|
|
9588
|
+
const candidates = [
|
|
9589
|
+
String(action.stageEnteredAt || action.stage_entered_at || "").trim(),
|
|
9590
|
+
existingTimes.get(actionKey) || "",
|
|
9591
|
+
auditedAt,
|
|
9592
|
+
observedAt,
|
|
9593
|
+
].filter((value) => Number.isFinite(Date.parse(value)));
|
|
9594
|
+
const stageEnteredAt = candidates.sort((left, right) => Date.parse(left) - Date.parse(right))[0] || observedAt;
|
|
9595
|
+
return {
|
|
9596
|
+
...action,
|
|
9597
|
+
stageEnteredAt,
|
|
9598
|
+
};
|
|
9599
|
+
};
|
|
9600
|
+
const next = { ...snapshot };
|
|
9601
|
+
for (const key of PRD_WORKFLOW_SNAPSHOT_ACTION_ARRAY_KEYS) {
|
|
9602
|
+
if (Array.isArray(snapshot?.[key])) next[key] = snapshot[key].map(stamp);
|
|
9603
|
+
}
|
|
9604
|
+
return next;
|
|
9605
|
+
}
|
|
9606
|
+
|
|
9305
9607
|
const PRD_WORKFLOW_PROJECTION_SOURCE_KEYS = new Set([
|
|
9306
9608
|
"projectionMode",
|
|
9307
9609
|
"projectCacheScope",
|
|
@@ -9331,6 +9633,7 @@ function prdWorkflowStoredObservationSnapshot(snapshot, sourcePatch = {}) {
|
|
|
9331
9633
|
delete clean.collaboration;
|
|
9332
9634
|
delete clean.clientObservations;
|
|
9333
9635
|
delete clean.clients;
|
|
9636
|
+
delete clean.snapshotAudit;
|
|
9334
9637
|
clean.sources = prdWorkflowStoredObservationSources(snapshot.sources, sourcePatch);
|
|
9335
9638
|
return clean;
|
|
9336
9639
|
}
|
|
@@ -9561,6 +9864,7 @@ function prdWorkflowMaterializeSnapshot(root, scopedRoot, tapdId, userCtx = {},
|
|
|
9561
9864
|
revision: String(materialized.revision || ""),
|
|
9562
9865
|
},
|
|
9563
9866
|
];
|
|
9867
|
+
materialized.snapshotAudit = prdWorkflowReadRecentActionAudit(scopedRoot, tapdId);
|
|
9564
9868
|
prdWorkflowAppendAudit(scopedRoot, tapdId, {
|
|
9565
9869
|
type: "projection-materialized",
|
|
9566
9870
|
flowSource,
|
|
@@ -12179,7 +12483,21 @@ export function startUiServer({
|
|
|
12179
12483
|
issueKey: reportMeta.issueKey,
|
|
12180
12484
|
stageKey: reportMeta.stageKey,
|
|
12181
12485
|
};
|
|
12182
|
-
const
|
|
12486
|
+
const existingClientState = prdWorkflowReadClientState(scopedRoot, tapdId);
|
|
12487
|
+
const existingClientId = prdWorkflowSafeStateId(reportMeta.clientId || "anonymous");
|
|
12488
|
+
const previousClientSnapshot = existingClientState.clients?.[existingClientId]?.snapshot || null;
|
|
12489
|
+
const stampedSnapshot = prdWorkflowStampCurrentActionEntryTimes(
|
|
12490
|
+
scopedRoot,
|
|
12491
|
+
tapdId,
|
|
12492
|
+
normalizedSnapshot,
|
|
12493
|
+
existingClientState,
|
|
12494
|
+
reportMeta,
|
|
12495
|
+
);
|
|
12496
|
+
const storedObservationSnapshot = prdWorkflowStoredObservationSnapshot(stampedSnapshot, reportSource);
|
|
12497
|
+
const actionChanges = prdWorkflowSnapshotActionChanges(
|
|
12498
|
+
previousClientSnapshot || {},
|
|
12499
|
+
storedObservationSnapshot,
|
|
12500
|
+
);
|
|
12183
12501
|
prdWorkflowWriteClientObservation(scopedRoot, tapdId, reportMeta, storedObservationSnapshot);
|
|
12184
12502
|
prdWorkflowAppendAudit(scopedRoot, tapdId, {
|
|
12185
12503
|
type: "client-observation-stored",
|
|
@@ -12198,12 +12516,51 @@ export function startUiServer({
|
|
|
12198
12516
|
persistence: "runtime",
|
|
12199
12517
|
note: "ordinary current snapshot stored as client observation; it must not overwrite project state",
|
|
12200
12518
|
});
|
|
12519
|
+
for (const change of actionChanges) {
|
|
12520
|
+
const changeLabel = {
|
|
12521
|
+
added: "新增",
|
|
12522
|
+
removed: "移除",
|
|
12523
|
+
"status-changed": "状态变更",
|
|
12524
|
+
"time-changed": "时间更正",
|
|
12525
|
+
"title-changed": "标题变更",
|
|
12526
|
+
}[change.kind] || "变更";
|
|
12527
|
+
prdWorkflowAppendAudit(scopedRoot, tapdId, {
|
|
12528
|
+
type: "snapshot-action-change",
|
|
12529
|
+
change: change.kind,
|
|
12530
|
+
title: `Workflow Action ${changeLabel}${change.title ? `:${change.title}` : ""}`,
|
|
12531
|
+
detail: [
|
|
12532
|
+
change.stageKey,
|
|
12533
|
+
change.previousStatus && change.previousStatus !== change.status
|
|
12534
|
+
? `${change.previousStatus} -> ${change.status}`
|
|
12535
|
+
: change.status,
|
|
12536
|
+
change.previousActionAt && change.previousActionAt !== change.actionAt
|
|
12537
|
+
? `${change.previousActionAt} -> ${change.actionAt || "无时间"}`
|
|
12538
|
+
: change.actionAt,
|
|
12539
|
+
change.previousSourceActionAt !== change.sourceActionAt
|
|
12540
|
+
? `来源时间 ${change.previousSourceActionAt || "无"} -> ${change.sourceActionAt || "无"}`
|
|
12541
|
+
: "",
|
|
12542
|
+
].filter(Boolean).join(" · "),
|
|
12543
|
+
auditStatus: "observed",
|
|
12544
|
+
truth: "audit",
|
|
12545
|
+
authority: "agentflow",
|
|
12546
|
+
persistence: "runtime",
|
|
12547
|
+
clientId: reportMeta.clientId,
|
|
12548
|
+
userId: reportMeta.userId,
|
|
12549
|
+
observedAt: reportMeta.observedAt,
|
|
12550
|
+
reportedAt: reportMeta.reportedAt,
|
|
12551
|
+
revision: String(storedObservationSnapshot.revision || ""),
|
|
12552
|
+
previousRevision: String(previousClientSnapshot?.revision || ""),
|
|
12553
|
+
pointer: String(storedObservationSnapshot.pointer || ""),
|
|
12554
|
+
previousPointer: String(previousClientSnapshot?.pointer || ""),
|
|
12555
|
+
...change,
|
|
12556
|
+
});
|
|
12557
|
+
}
|
|
12201
12558
|
|
|
12202
12559
|
const projectFactSource = reportMeta.scope === "project"
|
|
12203
12560
|
? prdWorkflowProjectFactSource(payload, rawSnapshot)
|
|
12204
12561
|
: null;
|
|
12205
12562
|
const projectFactSnapshot = projectFactSource
|
|
12206
|
-
? prdWorkflowStoredObservationSnapshot(
|
|
12563
|
+
? prdWorkflowStoredObservationSnapshot(stampedSnapshot, {
|
|
12207
12564
|
...reportSource,
|
|
12208
12565
|
...projectFactSource,
|
|
12209
12566
|
})
|