// Copyright (c) 2026 Lark Technologies Pte. Ltd. // SPDX-License-Identifier: MIT const { describe, it } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { buildSummaryMarkdown, buildChangedLineIndex, checkConclusion, checkName, changedLinesFromPatch, findingKey, inlineCode, inlineCommentBody, loadExistingInlineThreads, loadDecision, parseBlockMode, publish, sanitizeMarkdownBody, selectInlineTarget, } = require("./semantic-review-publish.js"); describe("semantic-review-publish", () => { it("formats author-facing summary groups from decision and facts", () => { const decision = { block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }], warnings: [ { category: "error_hint", severity: "major", review_action: "confirm", evidence: ["facts.errors[0]"], fingerprint: "category:error_hint|errors:file:cmd/root.go:line:77", message: "hint is covered by an exception", suggested_action: "confirm the exception still applies", waiver_id: "err-hint-existing", }, { category: "default_output", severity: "minor", review_action: "observe", evidence: ["facts.outputs[0]"], fingerprint: "category:default_output|outputs:command:drive files list", message: "list output lacks a decision field", suggested_action: "track for a later cleanup", }, ], system_warnings: [{ severity: "minor", message: "review used a degraded model response", suggested_action: "inspect logs", }], }; const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], errors: [{ file: "cmd/root.go", line: 77, command_path: "lark-cli", }], outputs: [{ command: "drive files list", }], }; const markdown = buildSummaryMarkdown(decision, facts, new Map()); assert.match(markdown, /### Must fix/); assert.match(markdown, /skills\/lark-doc\/SKILL\.md:30/); assert.match(markdown, /### Confirm/); assert.match(markdown, /err-hint-existing/); assert.match(markdown, /cmd\/root\.go:77/); assert.doesNotMatch(markdown, /### Non-blocking observations/); assert.doesNotMatch(markdown, /drive files list/); assert.match(markdown, /### System status/); assert.match(markdown, /review used a degraded model response/); }); it("keeps observe findings out of confirm-only PR summaries", () => { const markdown = buildSummaryMarkdown({ block_mode: true, blockers: [], warnings: [ { category: "error_hint", severity: "major", review_action: "confirm", evidence: ["facts.errors[0]"], fingerprint: "confirm-error", message: "hint uses an approved exception", suggested_action: "confirm the exception still applies", }, { category: "default_output", severity: "minor", review_action: "observe", evidence: ["facts.outputs[0]"], fingerprint: "observe-output", message: "list output lacks a decision field", suggested_action: "track for a later cleanup", }, ], }, { errors: [{ file: "cmd/root.go", line: 77 }], outputs: [{ command: "drive files list" }], }); assert.match(markdown, /### Confirm/); assert.match(markdown, /hint uses an approved exception/); assert.doesNotMatch(markdown, /### Non-blocking observations/); assert.doesNotMatch(markdown, /drive files list/); }); it("escapes model-controlled markdown in structured summaries", () => { const markdown = buildSummaryMarkdown({ block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "@team\n# forged [link](https://example.com)", suggested_action: "**do not** trust raw markdown", }], warnings: [], }, { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, }], }); assert(!markdown.includes("@team")); assert(!markdown.includes("\n# forged")); assert(!markdown.includes("https://example.com")); assert(!markdown.includes("")); assert(markdown.includes("@\u200bteam")); assert(markdown.includes("\\# forged")); assert(markdown.includes("\\[link\\]")); assert(markdown.includes("https[:]//example.com")); assert(markdown.includes("<b>")); }); it("parses right-side changed lines from a unified diff patch", () => { const patch = [ "@@ -1,4 +1,5 @@", " unchanged", "-old line", "+new line", " context", "+another new line", ].join("\n"); assert.deepEqual([...changedLinesFromPatch(patch)], [2, 4]); }); it("selects inline target only when evidence maps to a changed diff line", () => { const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], errors: [{ file: "cmd/root.go", line: 77, command_path: "lark-cli", }], }; const changedLineIndex = buildChangedLineIndex([{ filename: "skills/lark-doc/SKILL.md", patch: [ "@@ -29,2 +29,3 @@", " context", "+changed skill line", " another context", ].join("\n"), }]); assert.deepEqual( selectInlineTarget({ evidence: ["facts.skills[0]"] }, facts, changedLineIndex), { path: "skills/lark-doc/SKILL.md", line: 30 }, ); assert.equal(selectInlineTarget({ evidence: ["facts.errors[0]"] }, facts, changedLineIndex), null); }); it("maps public content evidence to changed files but not virtual metadata", () => { const restrictedScope = "pri" + "vate"; const facts = { public_content: [ { rule: "public_content_semantic_candidate", action: "WARNING", file: "docs/public-roadmap.md", line: 4, source: "file", }, { rule: "public_content_semantic_candidate", action: "WARNING", file: "pull_request_metadata", line: 1, source: "metadata", }, { rule: "public_content_automation_branch", action: "WARNING", file: "branch", line: 1, source: "branch", }, { rule: "public_content_change_id_trailer", action: "REJECT", file: "commit:1234abc", line: 3, source: "commit", }, ], }; const changedLineIndex = buildChangedLineIndex([{ filename: "docs/public-roadmap.md", patch: [ "@@ -3,2 +3,3 @@", " context", "+Specific " + restrictedScope + " roadmap detail", ].join("\n"), }]); assert.deepEqual( selectInlineTarget({ evidence: ["facts.public_content[0]"] }, facts, changedLineIndex), { path: "docs/public-roadmap.md", line: 4 }, ); assert.equal(selectInlineTarget({ evidence: ["facts.public_content[1]"] }, facts, changedLineIndex), null); assert.equal(selectInlineTarget({ evidence: ["facts.public_content[2]"] }, facts, changedLineIndex), null); assert.equal(selectInlineTarget({ evidence: ["facts.public_content[3]"] }, facts, changedLineIndex), null); const markdown = buildSummaryMarkdown({ block_mode: true, blockers: [{ category: "public_content_leakage", severity: "major", review_action: "must_fix", evidence: ["facts.public_content[1]"], fingerprint: "public-content-metadata", message: "PR metadata contains " + restrictedScope + " rollout detail", suggested_action: "Move " + restrictedScope + " detail to an internal channel.", }], warnings: [], }, facts); assert.match(markdown, /pull_request_metadata:1/); const virtualMarkdown = buildSummaryMarkdown({ block_mode: true, blockers: [ { category: "public_content_leakage", severity: "major", review_action: "must_fix", evidence: ["facts.public_content[2]"], fingerprint: "public-content-branch", message: "Branch name looks automation-owned.", suggested_action: "Use a maintainer-owned public branch name.", }, { category: "public_content_leakage", severity: "major", review_action: "must_fix", evidence: ["facts.public_content[3]"], fingerprint: "public-content-commit", message: "Commit trailer contains " + restrictedScope + " review metadata.", suggested_action: "Remove " + restrictedScope + " review metadata from commits.", }, ], warnings: [], }, facts); assert.match(virtualMarkdown, /branch:1/); assert.match(virtualMarkdown, /commit:1234abc:3/); }); it("builds finding markers from stable fingerprints and evidence identity", () => { const factsA = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, }], }; const factsB = { skills: [ { source_file: "skills/lark-im/SKILL.md", line: 12, }, { source_file: "skills/lark-doc/SKILL.md", line: 30, }, ], }; const finding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; const sameFindingAfterFactReorder = { ...finding, evidence: ["facts.skills[1]"], }; const differentFindingOnSameEvidence = { ...finding, fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30:other", message: "skill omits required argument documentation", }; const sameFindingWithDifferentWording = { ...finding, message: "invalid command reference in skill", suggested_action: "fix the referenced command", }; assert.equal(findingKey(finding, factsA), findingKey(sameFindingAfterFactReorder, factsB)); assert.equal(findingKey(finding, factsA), findingKey(sameFindingWithDifferentWording, factsA)); assert.notEqual(findingKey(finding, factsA), findingKey(differentFindingOnSameEvidence, factsA)); }); it("uses longer markdown code spans when inline labels contain backticks", () => { const body = inlineCommentBody({ category: "skill_quality", severity: "major", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/`doc`.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }, { skills: [{ source_file: "skills/`doc`.md", line: 30, }], }, { path: "skills/`doc`.md", line: 30, }); assert.match(body, /``skills\/`doc`\.md:30``/); assert(!body.includes("skills/\\`doc\\`.md:30")); }); it("keeps inline code labels on one markdown line", () => { const got = inlineCode("abc\n\n## INJECTED\n\n[x](http://evil)\t@team\u0001"); assert.equal(got, "`abc ## INJECTED [x](http://evil) @team`"); assert(!got.includes("\n")); assert(!got.includes("\t")); assert(!got.includes("\u0001")); }); it("sanitizes fact labels and exception ids before rendering code spans", () => { const markdown = buildSummaryMarkdown({ block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", waiver_id: "waiver\n\n## INJECTED\n\n[x](http://evil)", }], warnings: [], }, { skills: [{ source_file: "skills/lark-doc/SKILL.md\n\n## INJECTED\n\n[x](http://evil)", line: 30, }], }, new Map()); assert(!markdown.includes("\n\n## INJECTED")); assert.match(markdown, /Evidence: `skills\/lark-doc\/SKILL\.md ## INJECTED \[x\]\(http:\/\/evil\):30`/); assert.match(markdown, /Exception: `waiver ## INJECTED \[x\]\(http:\/\/evil\)`/); }); it("parses block mode exactly", () => { assert.equal(parseBlockMode("true"), true); assert.equal(parseBlockMode("false"), false); assert.equal(parseBlockMode("TRUE"), false); assert.equal(parseBlockMode("1"), false); assert.equal(parseBlockMode(""), false); }); it("uses distinct check names for observe and result modes", () => { assert.equal(checkName(false), "semantic-review/observe"); assert.equal(checkName(true), "semantic-review/result"); }); it("keeps missing decision neutral in comment-only mode", () => { const decision = loadDecision(path.join(os.tmpdir(), "missing-semantic-review-decision.json")); assert.equal(decision.infrastructure_failure, true); assert.equal(checkConclusion(decision, false), "neutral"); }); it("fails missing decision in blocking mode", () => { const decision = loadDecision(path.join(os.tmpdir(), "missing-semantic-review-decision.json")); assert.equal(decision.infrastructure_failure, true); assert.equal(checkConclusion(decision, true), "failure"); }); it("keeps invalid decision neutral in comment-only mode", () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const decisionPath = path.join(dir, "decision.json"); fs.writeFileSync(decisionPath, "{", "utf8"); const decision = loadDecision(decisionPath); assert.equal(decision.infrastructure_failure, true); assert.equal(checkConclusion(decision, false), "neutral"); }); it("treats malformed decision shape as infrastructure failure", () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const decisionPath = path.join(dir, "decision.json"); fs.writeFileSync(decisionPath, JSON.stringify({ blockers: [] }), "utf8"); const decision = loadDecision(decisionPath); assert.equal(decision.infrastructure_failure, true); assert.equal(checkConclusion(decision, true), "failure"); assert.equal(checkConclusion(decision, false), "neutral"); }); it("keeps a valid degraded decision neutral in comment-only mode", () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const decisionPath = path.join(dir, "decision.json"); fs.writeFileSync(decisionPath, JSON.stringify({ degraded: true, block_mode: false, blockers: [], warnings: [{ message: "review unavailable" }], }), "utf8"); assert.equal(checkConclusion(loadDecision(decisionPath), false), "neutral"); }); it("fails a valid degraded decision in blocking mode", () => { const decision = { degraded: true, block_mode: true, blockers: [], warnings: [{ message: "review unavailable" }], }; assert.equal(checkConclusion(decision, true), "failure"); }); it("maps skipped decisions by runtime block mode", () => { const decision = { skipped: true, block_mode: false, system_warnings: [{ severity: "minor", message: "reviewer not configured" }], }; assert.equal(checkConclusion(decision, false), "neutral"); assert.equal(checkConclusion({ ...decision, block_mode: true }, true), "failure"); }); it("maps system warnings by runtime block mode", () => { const decision = { block_mode: false, blockers: [], warnings: [], system_warnings: [{ severity: "minor", message: "review used a degraded model response" }], }; assert.equal(checkConclusion(decision, false), "neutral"); assert.equal(checkConclusion({ ...decision, block_mode: true }, true), "failure"); }); it("fails a blocking decision with blockers", () => { const decision = { block_mode: true, blockers: [{ category: "naming", message: "reproducible blocker" }], warnings: [], }; assert.equal(checkConclusion(decision, true), "failure"); }); it("treats runtime decision block mode mismatch as infrastructure failure", () => { const decision = { block_mode: true, blockers: [], warnings: [], }; assert.equal(checkConclusion(decision, false), "neutral"); assert.equal(checkConclusion({ ...decision, block_mode: false }, true), "failure"); }); it("sanitizes mentions, HTML, links, bare URLs, and controls in published markdown", () => { const got = sanitizeMarkdownBody("@team \u0001 [link](https://example.com) ![img](http://example.com/x.png)"); assert(!got.includes("@team")); assert(!got.includes("")); assert(!got.includes("https://example.com")); assert(!got.includes("http://example.com")); assert(got.includes("@\u200bteam")); assert(got.includes("<b>")); assert(got.includes("https[:]//example.com")); assert(got.includes("http[:]//example.com")); }); it("requires verifier-provided publish target", async () => { const env = saveEnv(); try { delete process.env.SEMANTIC_REVIEW_PR_NUMBER; delete process.env.SEMANTIC_REVIEW_HEAD_SHA; delete process.env.SEMANTIC_REVIEW_BASE_SHA; await assert.rejects( () => publish({ github: {}, context: workflowRunContext(), core: silentCore() }), /missing verified semantic review pull request number/, ); } finally { restoreEnv(env); } }); it("publishes check and comment to verifier-provided PR head", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [] }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [{ category: "error_hint", severity: "major", review_action: "must_fix", evidence: ["facts.errors[0]"], fingerprint: "error-hint", message: "error is missing a recovery hint", suggested_action: "add a structured hint", }], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ schema_version: 1, errors: [{ file: "cmd/foo.go", line: 10, changed: true, boundary: true, required_hint: true, hint_action_count: 0 }], }), "utf8"); fs.writeFileSync("semantic-review.md", "## Semantic Review\n\nNo semantic blockers.\n", "utf8"); await publish({ github: fakeGithub(calls), context: workflowRunContext(), core: silentCore(), }); assert.equal(calls.comments.length, 1); assert.equal(calls.comments[0].issue_number, 42); assert.equal(calls.checks.length, 1); assert.equal(calls.checks[0].name, "semantic-review/result"); assert.equal(calls.checks[0].head_sha, "0123456789abcdef0123456789abcdef01234567"); assert.match(calls.comments[0].body, /### Must fix/); assert.deepEqual(calls.order, ["check", "comment"]); } finally { process.chdir(cwd); restoreEnv(env); } }); it("deletes an existing summary and publishes no comment when there are no action items", async () => { await withPublishTempDir(async ({ calls }) => { fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [], warnings: [], }), "utf8"); await publish({ github: fakeGithub(calls, { issueComments: [{ id: 99, user: { type: "Bot" }, body: "", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.checks[0].conclusion, "success"); assert.equal(calls.comments.length, 0); assert.deepEqual(calls.deletedComments.map((c) => c.comment_id), [99]); assert.match(calls.checks[0].output.summary, /No PR Quality Summary was published/); }); }); it("does not publish a summary or inline comment for observe-only findings by default", async () => { await withPublishTempDir(async ({ calls }) => { fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [], warnings: [{ category: "default_output", severity: "minor", review_action: "observe", evidence: ["facts.outputs[0]"], fingerprint: "default-output", message: "list output lacks a decision field", suggested_action: "track for a later cleanup", }], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ outputs: [{ command: "drive files list" }], }), "utf8"); await publish({ github: fakeGithub(calls), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.checks[0].conclusion, "success"); assert.equal(calls.comments.length, 0); assert.equal(calls.reviewComments.length, 0); assert.match(calls.checks[0].output.summary, /Observe: 1/); assert.match(calls.checks[0].output.summary, /No PR Quality Summary was published/); }); }); it("skips publishing when the PR head changed after verification", async () => { await withPublishTempDir(async ({ calls }) => { fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [], warnings: [], }), "utf8"); await publish({ github: fakeGithub(calls, { currentPullRequest: { head: { sha: "9999999999999999999999999999999999999999" }, base: { sha: "fedcba9876543210fedcba9876543210fedcba98", repo: { id: 123 }, }, }, }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 0); assert.equal(calls.comments.length, 0); assert.match(calls.notices[0], /PR head changed before publishing/); }); }); it("skips publishing when the PR base changed after verification", async () => { await withPublishTempDir(async ({ calls }) => { fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [], warnings: [], }), "utf8"); await publish({ github: fakeGithub(calls, { currentPullRequest: { head: { sha: "0123456789abcdef0123456789abcdef01234567" }, base: { sha: "8888888888888888888888888888888888888888", repo: { id: 123 }, }, }, }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 0); assert.equal(calls.comments.length, 0); assert.match(calls.notices[0], /PR base changed before publishing/); }); }); it("skips publishing when the PR closes after verification", async () => { await withPublishTempDir(async ({ calls }) => { fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [], warnings: [], }), "utf8"); await publish({ github: fakeGithub(calls, { currentPullRequest: { state: "closed", head: { sha: "0123456789abcdef0123456789abcdef01234567" }, base: { sha: "fedcba9876543210fedcba9876543210fedcba98", repo: { id: 123 }, }, }, }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 0); assert.equal(calls.comments.length, 0); assert.match(calls.notices[0], /PR is no longer open before publishing/); }); }); it("rejects publishing when the PR base repo changed after verification", async () => { await withPublishTempDir(async ({ calls }) => { fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [], warnings: [], }), "utf8"); await assert.rejects( () => publish({ github: fakeGithub(calls, { currentPullRequest: { head: { sha: "0123456789abcdef0123456789abcdef01234567" }, base: { sha: "fedcba9876543210fedcba9876543210fedcba98", repo: { id: 456 }, }, }, }), context: workflowRunContext(), core: silentCore(calls), }), /PR base repo mismatch before publishing/, ); assert.equal(calls.checks.length, 0); assert.equal(calls.comments.length, 0); }); }); it("skips all PR-visible writes when target is stale before inline publishing", async () => { await withPublishTempDir(async ({ calls }) => { writeInlineCandidateDecisionAndFacts(); await publish({ github: fakeGithub(calls, { files: changedSkillFilePatch(), currentPullRequests: [{ head: { sha: "9".repeat(40) }, base: { sha: process.env.SEMANTIC_REVIEW_BASE_SHA, repo: { id: 123 } }, }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 0); assert.equal(calls.updatedReviewComments?.length || 0, 0); assert.equal(calls.checks.length, 0); assert.equal(calls.comments.length, 0); }); }); it("skips inline update and later writes when target becomes stale before updating an existing discussion", async () => { await withPublishTempDir(async ({ calls }) => { writeInlineCandidateDecisionAndFacts(); await publish({ github: fakeGithub(calls, { files: changedSkillFilePatch(), reviewThreads: [existingUnresolvedFindingThread()], currentPullRequests: [ currentTarget(), { head: { sha: process.env.SEMANTIC_REVIEW_HEAD_SHA }, base: { sha: "8".repeat(40), repo: { id: 123 } } }, ], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.updatedReviewComments?.length || 0, 0); assert.equal(calls.checks.length, 0); assert.equal(calls.comments.length, 0); }); }); it("does not create a check or summary when target becomes stale after inline publishing", async () => { await withPublishTempDir(async ({ calls }) => { writeInlineCandidateDecisionAndFacts(); await publish({ github: fakeGithub(calls, { files: changedSkillFilePatch(), currentPullRequests: [ currentTarget(), currentTarget(), { head: { sha: process.env.SEMANTIC_REVIEW_HEAD_SHA }, base: { sha: "8".repeat(40), repo: { id: 123 } } }, ], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 1); assert.equal(calls.checks.length, 0); assert.equal(calls.comments.length, 0); }); }); it("does not create a summary comment when target becomes stale after check creation", async () => { await withPublishTempDir(async ({ calls }) => { writeDecisionAndFactsWithoutInline(); await publish({ github: fakeGithub(calls, { currentPullRequests: [ currentTarget(), currentTarget(), { head: { sha: process.env.SEMANTIC_REVIEW_HEAD_SHA }, base: { sha: "8".repeat(40), repo: { id: 123 } } }, ], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.comments.length, 0); }); }); it("does not update an existing summary comment when target is stale before summary update", async () => { await withPublishTempDir(async ({ calls }) => { writeDecisionAndFactsWithoutInline(); await publish({ github: fakeGithub(calls, { issueComments: [{ id: 99, user: { type: "Bot" }, body: "" }], currentPullRequests: [ currentTarget(), currentTarget(), { head: { sha: process.env.SEMANTIC_REVIEW_HEAD_SHA }, base: { sha: "8".repeat(40), repo: { id: 123 } } }, ], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.comments.length, 0); }); }); it("updates an existing summary comment on the current target", async () => { await withPublishTempDir(async ({ calls }) => { writeDecisionAndFactsWithoutInline(); await publish({ github: fakeGithub(calls, { issueComments: [{ id: 99, user: { type: "Bot" }, body: "" }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.comments.length, 1); assert.equal(calls.comments[0].comment_id, 99); assert.equal(calls.comments[0].issue_number, undefined); }); }); it("does not update an existing summary comment when target becomes stale after comment listing", async () => { await withPublishTempDir(async ({ calls }) => { writeDecisionAndFactsWithoutInline(); await publish({ github: fakeGithub(calls, { issueComments: [{ id: 99, user: { type: "Bot" }, body: "" }], currentPullRequests: [ currentTarget(), currentTarget(), currentTarget(), { head: { sha: process.env.SEMANTIC_REVIEW_HEAD_SHA }, base: { sha: "8".repeat(40), repo: { id: 123 } } }, ], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.comments.length, 0); }); }); it("does not create a summary comment when target becomes stale after comment listing", async () => { await withPublishTempDir(async ({ calls }) => { writeDecisionAndFactsWithoutInline(); await publish({ github: fakeGithub(calls, { currentPullRequests: [ currentTarget(), currentTarget(), currentTarget(), { head: { sha: process.env.SEMANTIC_REVIEW_HEAD_SHA }, base: { sha: "8".repeat(40), repo: { id: 123 } } }, ], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.comments.length, 0); }); }); it("includes head base and run id in the summary marker", async () => { await withPublishTempDir(async ({ calls }) => { process.env.SEMANTIC_REVIEW_RUN_ID = "123456"; writeDecisionAndFactsWithoutInline(); await publish({ github: fakeGithub(calls), context: workflowRunContext(), core: silentCore(calls) }); assert.match(calls.comments[0].body, //); }); }); it("publishes an infrastructure failure when decision findings omit review_action", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [{ category: "skill_quality", severity: "major", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, }], }), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks[0].conclusion, "failure"); assert.equal(calls.reviewComments.length, 0); assert.match(calls.comments[0].body, /### System status/); assert.match(calls.comments[0].body, /missing review\\_action/); assert.doesNotMatch(calls.comments[0].body, /### Must fix\n\n- \*\*/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("publishes infrastructure failures for invalid finding publication contracts", async () => { const cases = [ { name: "missing fingerprint", decision: { block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], message: "skill references an invalid command", suggested_action: "update the command reference", }], warnings: [], }, want: /missing fingerprint/, }, { name: "blank fingerprint", decision: { block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: " ", message: "skill references an invalid command", suggested_action: "update the command reference", }], warnings: [], }, want: /missing fingerprint/, }, { name: "invalid action", decision: { block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "repair", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }], warnings: [], }, want: /missing review\\_action/, }, { name: "blocker with confirm action", decision: { block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "confirm", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }], warnings: [], }, want: /review\\_action must be must\\_fix/, }, { name: "warning with must_fix action", decision: { block_mode: true, blockers: [], warnings: [{ category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }], }, want: /review\\_action must not be must\\_fix/, }, ]; for (const tc of cases) { await withPublishTempDir(async ({ calls }) => { fs.writeFileSync("decision.json", JSON.stringify(tc.decision), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, }], }), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks[0].conclusion, "failure", tc.name); assert.equal(calls.reviewComments.length, 0, tc.name); assert.match(calls.comments[0].body, /### System status/, tc.name); assert.match(calls.comments[0].body, tc.want, tc.name); }); } }); it("keeps check output status-only and leaves finding details in the PR comment", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }], warnings: [{ category: "default_output", severity: "minor", review_action: "observe", evidence: ["facts.outputs[0]"], fingerprint: "category:default_output|outputs:command:drive files list", message: "list output lacks a decision field", suggested_action: "track for a later cleanup", }], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, }], outputs: [{ command: "drive files list", }], }), "utf8"); await publish({ github: fakeGithub(calls), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks[0].conclusion, "failure"); assert.match(calls.checks[0].output.summary, /Must fix: 1/); assert.match(calls.checks[0].output.summary, /Observe: 1/); assert.doesNotMatch(calls.checks[0].output.summary, /skill references an invalid command/); assert.doesNotMatch(calls.checks[0].output.summary, /### Must fix/); assert.doesNotMatch(calls.checks[0].output.summary, /Evidence:/); assert.match(calls.comments[0].body, /skill references an invalid command/); assert.match(calls.comments[0].body, /### Must fix/); assert.doesNotMatch(calls.comments[0].body, /### Non-blocking observations/); assert.doesNotMatch(calls.comments[0].body, /list output lacks a decision field/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("marks must-fix findings as summary-only when PR files cannot be listed", async () => { await withPublishTempDir(async ({ calls }) => { writeDecisionAndFactsWithoutInline(); await publish({ github: fakeGithub(calls, { failListFiles: true }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks[0].conclusion, "failure"); assert.equal(calls.reviewComments.length, 0); assert.match(calls.comments[0].body, /summary-only; PR files were not listed/); assert.match(calls.warnings[0], /semantic review PR files were not listed/); }); }); it("marks must-fix findings without a changed diff line as summary-only", async () => { await withPublishTempDir(async ({ calls }) => { writeDecisionAndFactsWithoutInline(); await publish({ github: fakeGithub(calls, { files: [{ filename: "cmd/foo.go", patch: "@@ -1,1 +1,1 @@\n unchanged", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks[0].conclusion, "failure"); assert.equal(calls.reviewComments.length, 0); assert.match(calls.comments[0].body, /summary-only; no stable changed diff line/); }); }); it("publishes inline review comments for must-fix findings on changed diff lines", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], errors: [{ file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }; const finding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [finding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(facts), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 1); assert.equal(calls.reviewComments[0].pull_number, 42); assert.equal(calls.reviewComments[0].commit_id, "0123456789abcdef0123456789abcdef01234567"); assert.equal(calls.reviewComments[0].path, "skills/lark-doc/SKILL.md"); assert.equal(calls.reviewComments[0].line, 30); assert.equal(calls.reviewComments[0].side, "RIGHT"); assert.match(calls.reviewComments[0].body, new RegExp(`lark-cli-semantic-finding:${findingKey(finding, facts)}`)); assert.match(calls.reviewComments[0].body, /\*\*Semantic Review: Must fix\*\*/); assert.match(calls.reviewComments[0].body, /Resolving this discussion does not change the failed check/); assert.match(calls.comments[0].body, /Inline: semantic review posted to `skills\/lark-doc\/SKILL\.md:30`/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("publishes separate inline comments for different findings on the same changed line", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], errors: [{ file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }; const firstFinding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30:invalid-command", message: "skill references an invalid command", suggested_action: "update the command reference", }; const secondFinding = { category: "error_hint", severity: "major", review_action: "must_fix", evidence: ["facts.errors[0]"], fingerprint: "category:error_hint|errors:file:skills/lark-doc/SKILL.md:line:30", message: "error hint is not actionable", suggested_action: "add a concrete recovery hint", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [firstFinding, secondFinding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(facts), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 2); assert.notEqual( calls.reviewComments[0].body.match(/lark-cli-semantic-finding:([a-f0-9]+)/)[1], calls.reviewComments[1].body.match(/lark-cli-semantic-finding:([a-f0-9]+)/)[1], ); } finally { process.chdir(cwd); restoreEnv(env); } }); it("publishes a non-blocking summary for confirm warnings without inline comments", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const finding = { category: "skill_quality", severity: "major", review_action: "confirm", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill issue is covered by an exception", suggested_action: "confirm the exception still applies", waiver_id: "skill-doc-waiver", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [], warnings: [finding], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks[0].conclusion, "success"); assert.equal(calls.reviewComments.length, 0); assert.match(calls.comments[0].body, /### Confirm/); assert.match(calls.comments[0].body, /Exception: `skill-doc-waiver`/); assert.doesNotMatch(calls.comments[0].body, /Inline:/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("does not publish duplicate inline comments for repeated findings in the same decision", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const finding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [finding, finding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 1); assert.match(calls.comments[0].body, /Inline: semantic review posted to `skills\/lark-doc\/SKILL\.md:30`/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("does not duplicate unresolved inline comment threads and refreshes stale body", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }; const finding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [finding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(facts), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], reviewThreads: [{ isResolved: false, comments: [{ databaseId: 1001, body: `\nold body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 0); assert.equal(calls.updatedReviewComments.length, 1); assert.equal(calls.updatedReviewComments[0].comment_id, 1001); assert.match(calls.updatedReviewComments[0].body, /Status: Must fix/); assert.match(calls.updatedReviewComments[0].body, /skill references an invalid command/); assert.match(calls.comments[0].body, /Inline: semantic review updated existing unresolved discussion/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("ignores existing inline markers from non-bot review comments", async () => { const calls = { warnings: [], comments: [], checks: [], order: [], reviewComments: [] }; const facts = inlineFacts(); const finding = inlineFinding(); const key = findingKey(finding, facts); const existing = await loadExistingInlineThreads(fakeGithub(calls, { reviewThreads: [{ isResolved: false, comments: [{ author: { __typename: "User", login: "contributor" }, databaseId: 1001, body: `\nold body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }], }), workflowRunContext(), silentCore(calls), 42); assert.equal(existing.has(key), false); }); it("reuses an unchanged unresolved inline discussion without reporting it as updated", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const facts = inlineFacts(); const finding = inlineFinding(); try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [finding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(facts), "utf8"); await publish({ github: fakeGithub(calls, { files: changedSkillFilePatch(), reviewThreads: [{ isResolved: false, comments: [{ databaseId: 1001, body: inlineCommentBody(finding, facts, { path: "skills/lark-doc/SKILL.md", line: 30 }), path: "skills/lark-doc/SKILL.md", line: 30, }], }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 0); assert.equal(calls.updatedReviewComments?.length || 0, 0); assert.match(calls.comments[0].body, /Inline: semantic review reused existing unresolved discussion/); assert.doesNotMatch(calls.comments[0].body, /updated existing unresolved discussion/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("prefers an unresolved existing thread when the same marker also has a resolved thread", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }; const finding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [finding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(facts), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], reviewThreads: [ { isResolved: true, comments: [{ databaseId: 1001, body: `\nold resolved body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }, { isResolved: false, comments: [{ databaseId: 1002, body: `\nnew unresolved body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }, ], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 0); assert.equal(calls.updatedReviewComments.length, 1); assert.equal(calls.updatedReviewComments[0].comment_id, 1002); assert.match(calls.comments[0].body, /Inline: semantic review updated existing unresolved discussion/); assert.doesNotMatch(calls.comments[0].body, /Inline: semantic review existing resolved discussion/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("reads paginated review threads before deciding whether to publish inline comments", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }; const finding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [finding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(facts), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], reviewThreadPages: [ { hasNextPage: true, endCursor: "second-page", nodes: [], }, { hasNextPage: false, endCursor: null, nodes: [{ isResolved: true, comments: [{ databaseId: 1001, body: `\nold body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }], }, ], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 1); assert.match(calls.comments[0].body, /Inline: semantic review posted to `skills\/lark-doc\/SKILL\.md:30`/); assert.doesNotMatch(calls.comments[0].body, /existing resolved discussion/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("falls back to REST review comments and preserves unknown resolution when updating", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }; const finding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [finding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(facts), "utf8"); await publish({ github: fakeGithub(calls, { failGraphql: true, files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], reviewComments: [{ id: 1003, body: `\nold body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 0); assert.equal(calls.updatedReviewComments.length, 1); assert.equal(calls.updatedReviewComments[0].comment_id, 1003); assert.match(calls.comments[0].body, /Inline: semantic review updated existing discussion with unknown resolution/); assert.doesNotMatch(calls.comments[0].body, /updated existing unresolved discussion/); assert.match(calls.warnings[0], /thread state was not read/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("posts a new inline comment when only a resolved discussion has the same finding marker", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const facts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }; const finding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [finding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(facts), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], reviewThreads: [{ isResolved: true, comments: [{ databaseId: 1001, body: `\nold body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 1); assert.equal(calls.checks[0].conclusion, "failure"); assert.match(calls.comments[0].body, /Inline: semantic review posted to `skills\/lark-doc\/SKILL\.md:30`/); assert.doesNotMatch(calls.comments[0].body, /existing resolved discussion/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("does not duplicate inline comments when fact indexes change but evidence location is the same", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; const oldFacts = { skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }; const newFacts = { skills: [ { source_file: "skills/lark-im/SKILL.md", line: 12, command_path: "im +fetch", }, { source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }, ], }; const oldFinding = { category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; const newFinding = { ...oldFinding, evidence: ["facts.skills[1]"], message: "invalid command reference in skill", suggested_action: "fix the referenced command", }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [newFinding], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(newFacts), "utf8"); await publish({ github: fakeGithub(calls, { files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], reviewThreads: [{ isResolved: true, comments: [{ databaseId: 1001, body: `\nold body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.reviewComments.length, 1); assert.match(calls.comments[0].body, /Inline: semantic review posted to `skills\/lark-doc\/SKILL\.md:30`/); assert.doesNotMatch(calls.comments[0].body, /existing resolved discussion/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("updates the semantic check when summary cleanup cannot complete", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [] }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = ""; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: false, }), "utf8"); await publish({ github: fakeGithub(calls, { failComments: true }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.checks[0].name, "semantic-review/observe"); assert.equal(calls.checks[0].conclusion, "success"); assert.equal(calls.checkUpdates.length, 1); assert.equal(calls.checkUpdates[0].conclusion, "failure"); assert.match(calls.checkUpdates[0].output.summary, /PR Quality Summary publication failed/); assert.equal(calls.comments.length, 0); assert.equal(calls.warnings.length, 1); assert.match(calls.warnings[0], /semantic review summary comment was not published or cleaned up/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("updates the semantic check when a required summary cannot be published", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], checkUpdates: [], order: [], warnings: [], reviewComments: [] }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [], warnings: [{ category: "skill_quality", severity: "major", review_action: "confirm", evidence: ["facts.skills[0]"], fingerprint: "confirm-required-summary", message: "exception needs confirmation", suggested_action: "confirm the exception still applies", }], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30 }], }), "utf8"); await publish({ github: fakeGithub(calls, { failComments: true }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.checks[0].conclusion, "success"); assert.equal(calls.checkUpdates.length, 1); assert.equal(calls.checkUpdates[0].conclusion, "failure"); assert.match(calls.checkUpdates[0].output.summary, /PR Quality Summary publication failed/); assert.match(calls.warnings[0], /semantic review summary comment was not published or cleaned up/); } finally { process.chdir(cwd); restoreEnv(env); } }); it("still publishes check and summary when inline comments fail", async () => { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [{ category: "skill_quality", severity: "major", review_action: "must_fix", evidence: ["facts.skills[0]"], fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, command_path: "docs +fetch", }], }), "utf8"); await publish({ github: fakeGithub(calls, { failReviewComments: true, files: [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }], }), context: workflowRunContext(), core: silentCore(calls), }); assert.equal(calls.checks.length, 1); assert.equal(calls.comments.length, 1); assert.equal(calls.reviewComments.length, 0); assert.equal(calls.warnings.length, 1); assert.match(calls.warnings[0], /inline semantic review comment was not published/); } finally { process.chdir(cwd); restoreEnv(env); } }); }); function saveEnv() { return { SEMANTIC_REVIEW_BLOCK: process.env.SEMANTIC_REVIEW_BLOCK, SEMANTIC_REVIEW_PR_NUMBER: process.env.SEMANTIC_REVIEW_PR_NUMBER, SEMANTIC_REVIEW_HEAD_SHA: process.env.SEMANTIC_REVIEW_HEAD_SHA, SEMANTIC_REVIEW_BASE_SHA: process.env.SEMANTIC_REVIEW_BASE_SHA, SEMANTIC_REVIEW_RUN_ID: process.env.SEMANTIC_REVIEW_RUN_ID, }; } async function withPublishTempDir(fn) { const env = saveEnv(); const cwd = process.cwd(); const dir = fs.mkdtempSync(path.join(os.tmpdir(), "semantic-review-publish-")); const calls = { comments: [], checks: [], order: [], warnings: [], reviewComments: [] }; try { process.chdir(dir); process.env.SEMANTIC_REVIEW_BLOCK = "true"; process.env.SEMANTIC_REVIEW_PR_NUMBER = "42"; process.env.SEMANTIC_REVIEW_HEAD_SHA = "0123456789abcdef0123456789abcdef01234567"; process.env.SEMANTIC_REVIEW_BASE_SHA = "fedcba9876543210fedcba9876543210fedcba98"; process.env.SEMANTIC_REVIEW_RUN_ID = "123456"; await fn({ calls, dir }); } finally { process.chdir(cwd); restoreEnv(env); } } function restoreEnv(env) { for (const [key, value] of Object.entries(env)) { if (value === undefined) { delete process.env[key]; } else { process.env[key] = value; } } } function workflowRunContext() { return { repo: { owner: "larksuite", repo: "cli" }, payload: { repository: { id: 123, }, workflow_run: { event: "pull_request", conclusion: "success", head_sha: "ffffffffffffffffffffffffffffffffffffffff", pull_requests: [{ number: 7 }], }, }, }; } function silentCore(calls = { warnings: [] }) { return { notice(message) { calls.notices ||= []; calls.notices.push(message); }, warning(message) { calls.warnings.push(message); }, }; } function inlineFacts() { return { schema_version: 1, skills: [{ source_file: "skills/lark-doc/SKILL.md", line: 30, raw: "lark-cli bad", command_path: "docs +fetch", references_invalid_command: true, }], }; } function inlineFinding() { return { category: "skill_quality", severity: "major", evidence: ["facts.skills[0]"], review_action: "must_fix", fingerprint: "category:skill_quality|skills:source_file:skills/lark-doc/SKILL.md:line:30", message: "skill references an invalid command", suggested_action: "update the command reference", }; } function changedSkillFilePatch() { return [{ filename: "skills/lark-doc/SKILL.md", patch: "@@ -29,0 +30,1 @@\n+bad command reference", }]; } function currentTarget() { return { head: { sha: process.env.SEMANTIC_REVIEW_HEAD_SHA }, base: { sha: process.env.SEMANTIC_REVIEW_BASE_SHA, repo: { id: 123 } }, }; } function writeInlineCandidateDecisionAndFacts() { fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [inlineFinding()], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify(inlineFacts()), "utf8"); } function writeDecisionAndFactsWithoutInline() { fs.writeFileSync("decision.json", JSON.stringify({ block_mode: true, blockers: [{ category: "error_hint", severity: "major", evidence: ["facts.errors[0]"], review_action: "must_fix", fingerprint: "error-hint", message: "error is missing a recovery hint", suggested_action: "add a structured hint", }], warnings: [], }), "utf8"); fs.writeFileSync("facts.json", JSON.stringify({ schema_version: 1, errors: [{ file: "cmd/foo.go", line: 10, changed: true, boundary: true, required_hint: true, hint_action_count: 0 }], }), "utf8"); } function existingUnresolvedFindingThread() { const facts = inlineFacts(); const finding = inlineFinding(); return { isResolved: false, comments: [{ author: { __typename: "Bot", login: "github-actions[bot]" }, databaseId: 1001, body: `\nold body`, path: "skills/lark-doc/SKILL.md", line: 30, }], }; } function fakeGithub(calls, options = {}) { let graphqlPage = 0; let pullGetCount = 0; const api = { paginate: async (endpoint) => { if (options.failComments) { throw new Error("comment API unavailable"); } if (endpoint === api.rest.issues.listComments) { return options.issueComments || []; } if (endpoint === api.rest.pulls.listFiles) { if (options.failListFiles) { throw new Error("list files unavailable"); } return options.files || []; } if (endpoint === api.rest.pulls.listReviewComments) { return (options.reviewComments || []).map((comment) => ({ user: { type: "Bot", login: "github-actions[bot]" }, ...comment, })); } return []; }, graphql: async () => { if (options.failGraphql) { throw new Error("GraphQL unavailable"); } const pages = options.reviewThreadPages || [{ hasNextPage: false, endCursor: null, nodes: options.reviewThreads || [], }]; const page = pages[Math.min(graphqlPage, pages.length - 1)]; graphqlPage++; return { repository: { pullRequest: { reviewThreads: { pageInfo: { hasNextPage: !!page.hasNextPage, endCursor: page.endCursor || null }, nodes: (page.nodes || []).map((thread) => ({ id: thread.id || "thread-id", isResolved: !!thread.isResolved, comments: { nodes: (thread.comments || []).map((comment) => ({ author: { __typename: "Bot", login: "github-actions[bot]" }, ...comment, })), }, })), }, }, }, }; }, rest: { issues: { listComments() {}, createComment: async (args) => { if (options.failComments) { throw new Error("comment API unavailable"); } calls.comments.push(args); calls.order.push("comment"); }, updateComment: async (args) => { if (options.failComments) { throw new Error("comment API unavailable"); } calls.comments.push(args); calls.order.push("comment"); }, deleteComment: async (args) => { calls.deletedComments ||= []; calls.deletedComments.push(args); calls.order.push("comment-delete"); }, }, checks: { create: async (args) => { calls.checks.push(args); calls.order.push("check"); return { data: { id: calls.checks.length } }; }, update: async (args) => { calls.checkUpdates ||= []; calls.checkUpdates.push(args); calls.order.push("check-update"); }, }, pulls: { get: async () => { const pull = Array.isArray(options.currentPullRequests) ? options.currentPullRequests[Math.min(pullGetCount++, options.currentPullRequests.length - 1)] : options.currentPullRequest || { head: { sha: process.env.SEMANTIC_REVIEW_HEAD_SHA }, base: { sha: process.env.SEMANTIC_REVIEW_BASE_SHA, repo: { id: 123 }, }, }; return { data: { state: "open", ...pull } }; }, listFiles() {}, listReviewComments() {}, createReviewComment: async (args) => { if (options.failReviewComments) { throw new Error("review comment API unavailable"); } calls.reviewComments.push(args); calls.order.push("review-comment"); }, updateReviewComment: async (args) => { if (options.failReviewComments) { throw new Error("review comment API unavailable"); } calls.updatedReviewComments ||= []; calls.updatedReviewComments.push(args); calls.order.push("review-comment-update"); }, }, }, }; return api; }