fix: guard hwlab product routing from issue 20
This commit is contained in:
@@ -737,7 +737,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
assertCondition(Array.isArray(boardRowGetRow.cells) && boardRowGetRow.cells[1] === "OPEN", "board-row get should expose the GitHub status column", boardRowGetRow);
|
||||
assertCondition(boardRowGetFields.branch === "master" && boardRowGetFields.status === "pass" && boardRowGetFields.validation === "pass" && boardRowGetFields.tasks === "cq-35" && String(boardRowGetFields.focus ?? "").includes("当前关注点"), "board-row get should expose canonical field aliases", boardRowGetFields);
|
||||
const boardRowGetHint = boardRowGetData.codeQueueBoardHint as JsonRecord;
|
||||
assertCondition(boardRowGetHint.detected === false && String(boardRowGetHint.warning ?? "").includes("#20 is the long-term board only"), "board-row get should remind callers not to put daily briefs into #20", boardRowGetHint);
|
||||
assertCondition(boardRowGetHint.detected === false && String(boardRowGetHint.warning ?? "").includes("governance board only"), "board-row get should remind callers that #20 is governance-only", boardRowGetHint);
|
||||
|
||||
const boardRowUpsertUpdateRequestCountBefore = mock.requests.length;
|
||||
const boardRowUpsertUpdate = await runCli([
|
||||
@@ -968,7 +968,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const boardRowAddNoGuardData = dataOf(boardRowAddNoGuard.json ?? {});
|
||||
assertCondition(boardRowAddNoGuardData.dryRun === true && boardRowAddNoGuardData.planned === true, "board-row add without guard should not PATCH GitHub", boardRowAddNoGuardData);
|
||||
const boardRowAddNoGuardPlan = boardRowAddNoGuardData.add as JsonRecord;
|
||||
assertCondition(boardRowAddNoGuardPlan.section === "open" && boardRowAddNoGuardPlan.insertAfterLine > 0, "board-row add without guard should still return an insertion plan", boardRowAddNoGuardPlan);
|
||||
assertCondition(boardRowAddNoGuardPlan.section === "open" && Number(boardRowAddNoGuardPlan.insertAfterLine ?? 0) > 0, "board-row add without guard should still return an insertion plan", boardRowAddNoGuardPlan);
|
||||
const boardRowAddNoGuardWriteCount = mock.requests.slice(boardRowAddNoGuardRequestCountBefore).filter((request) => request.method === "PATCH").length;
|
||||
assertCondition(boardRowAddNoGuardWriteCount === 0, "board-row add without guard must not PATCH GitHub", { requests: mock.requests.slice(boardRowAddNoGuardRequestCountBefore) });
|
||||
|
||||
@@ -978,7 +978,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const boardRowAddDryRunData = dataOf(boardRowAddDryRun.json ?? {});
|
||||
assertCondition(boardRowAddDryRunData.command === "issue board-row add" && boardRowAddDryRunData.dryRun === true && boardRowAddDryRunData.planned === true, "board-row add should default to dry-run", boardRowAddDryRunData);
|
||||
const boardRowAddDryRunPlan = boardRowAddDryRunData.add as JsonRecord;
|
||||
assertCondition(boardRowAddDryRunPlan.section === "open" && boardRowAddDryRunPlan.validation.actualStatus === "OPEN", "board-row add dry-run should validate the target section and GitHub status", boardRowAddDryRunPlan);
|
||||
const boardRowAddDryRunValidation = boardRowAddDryRunPlan.validation as JsonRecord;
|
||||
assertCondition(boardRowAddDryRunPlan.section === "open" && boardRowAddDryRunValidation.actualStatus === "OPEN", "board-row add dry-run should validate the target section and GitHub status", boardRowAddDryRunPlan);
|
||||
const boardRowAddDryRunPatchCount = mock.requests.slice(boardRowAddDryRunRequestCountBefore).filter((request) => request.method === "PATCH").length;
|
||||
assertCondition(boardRowAddDryRunPatchCount === 0, "board-row add dry-run must not PATCH GitHub", { requests: mock.requests.slice(boardRowAddDryRunRequestCountBefore) });
|
||||
|
||||
@@ -992,7 +993,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const boardRowAddData = dataOf(boardRowAdd.json ?? {});
|
||||
assertCondition(boardRowAddData.dryRun === false && boardRowAddData.rest === true, "board-row add should report a real REST update", boardRowAddData);
|
||||
const boardRowAddPlan = boardRowAddData.add as JsonRecord;
|
||||
assertCondition(boardRowAddPlan.section === "open" && boardRowAddPlan.validation.expectedStatus === "OPEN" && boardRowAddPlan.validation.actualStatus === "OPEN", "board-row add should validate section/status alignment", boardRowAddPlan);
|
||||
const boardRowAddValidation = boardRowAddPlan.validation as JsonRecord;
|
||||
assertCondition(boardRowAddPlan.section === "open" && boardRowAddValidation.expectedStatus === "OPEN" && boardRowAddValidation.actualStatus === "OPEN", "board-row add should validate section/status alignment", boardRowAddPlan);
|
||||
const boardRowAddRequests = mock.requests.slice(boardRowAddRequestCountBefore).filter((request) => request.method === "PATCH" && request.url === "/repos/pikasTech/unidesk/issues/20");
|
||||
assertCondition(boardRowAddRequests.length === 1, "board-row add should send exactly one PATCH", { requests: mock.requests.slice(boardRowAddRequestCountBefore) });
|
||||
const boardRowAddPayload = JSON.parse(boardRowAddRequests[0]?.body ?? "{}") as JsonRecord;
|
||||
@@ -1066,7 +1068,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const boardRowMoveDryRunData = dataOf(boardRowMoveDryRun.json ?? {});
|
||||
assertCondition(boardRowMoveDryRunData.command === "issue board-row move" && boardRowMoveDryRunData.dryRun === true && boardRowMoveDryRunData.planned === true, "board-row move should default to dry-run", boardRowMoveDryRunData);
|
||||
const boardRowMoveDryRunPlan = boardRowMoveDryRunData.move as JsonRecord;
|
||||
assertCondition(boardRowMoveDryRunPlan.from === "open" && boardRowMoveDryRunPlan.to === "closed" && boardRowMoveDryRunPlan.status.requested === "CLOSED", "board-row move dry-run should plan the cross-section migration", boardRowMoveDryRunPlan);
|
||||
const boardRowMoveDryRunStatus = boardRowMoveDryRunPlan.status as JsonRecord;
|
||||
assertCondition(boardRowMoveDryRunPlan.from === "open" && boardRowMoveDryRunPlan.to === "closed" && boardRowMoveDryRunStatus.requested === "CLOSED", "board-row move dry-run should plan the cross-section migration", boardRowMoveDryRunPlan);
|
||||
|
||||
const boardRowMoveConflict = await runCli(["gh", "issue", "board-row", "move", "35", "--repo", "pikasTech/unidesk", "--board-issue", "20", "--to", "closed", "--status", "OPEN", "--dry-run"], env);
|
||||
assertCondition(boardRowMoveConflict.status !== 0, "board-row move status conflict should fail structurally", boardRowMoveConflict.json ?? { stdout: boardRowMoveConflict.stdout, stderr: boardRowMoveConflict.stderr });
|
||||
@@ -1082,7 +1085,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const boardRowMoveData = dataOf(boardRowMove.json ?? {});
|
||||
assertCondition(boardRowMoveData.dryRun === false && boardRowMoveData.rest === true, "board-row move should report a real REST update", boardRowMoveData);
|
||||
const boardRowMovePlan = boardRowMoveData.move as JsonRecord;
|
||||
assertCondition(boardRowMovePlan.from === "open" && boardRowMovePlan.to === "closed" && boardRowMovePlan.status.new === "CLOSED", "board-row move should update the GitHub status column", boardRowMovePlan);
|
||||
const boardRowMoveStatus = boardRowMovePlan.status as JsonRecord;
|
||||
assertCondition(boardRowMovePlan.from === "open" && boardRowMovePlan.to === "closed" && boardRowMoveStatus.new === "CLOSED", "board-row move should update the GitHub status column", boardRowMovePlan);
|
||||
const boardRowMoveLinePlan = boardRowMovePlan.linePlan as JsonRecord;
|
||||
const boardRowMoveSectionPlan = boardRowMovePlan.sectionPlan as JsonRecord;
|
||||
assertCondition(boardRowMoveLinePlan.action === "move" && Number(boardRowMoveLinePlan.sourceLineNumber ?? 0) > 0 && Number(boardRowMoveLinePlan.newLineNumber ?? 0) > 0, "board-row move should expose a line plan", boardRowMoveLinePlan);
|
||||
@@ -1123,7 +1127,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const readIssue = readBodyData.issue as JsonRecord;
|
||||
assertCondition(typeof readIssue.body === "string" && readIssue.body.includes("## 看板(OPEN)"), ".data.issue.body should remain readable", readBodyData);
|
||||
const readBodyHint = readBodyData.codeQueueBoardHint as JsonRecord;
|
||||
assertCondition(readBodyHint.detected === false && String(readBodyHint.warning ?? "").includes("#20 is the long-term board only"), "issue read #20 should remind callers not to put daily briefs into #20", readBodyHint);
|
||||
assertCondition(readBodyHint.detected === false && String(readBodyHint.warning ?? "").includes("governance board only"), "issue read #20 should remind callers that #20 is governance-only", readBodyHint);
|
||||
const selectedJson = readBodyData.json as JsonRecord;
|
||||
assertCondition(typeof selectedJson.body === "string" && selectedJson.body === readIssue.body, "selected json body should match issue body", readBodyData);
|
||||
assertCondition(!("comments" in selectedJson), "--json body should not imply comments field", selectedJson);
|
||||
@@ -1216,6 +1220,88 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const pollutedBoardHint = pollutedBoardGuard.codeQueueBoardHint as JsonRecord;
|
||||
assertCondition(pollutedBoardHint.detected === true && String(pollutedBoardHint.route ?? "").includes("daily rolling commander brief"), "#20 guard should hint to move updates to the daily brief issue", pollutedBoardHint);
|
||||
|
||||
const hwlabProductBoardFile = join(tmp, "hwlab-product-board.md");
|
||||
writeFileSync(hwlabProductBoardFile, [
|
||||
"# Code Queue",
|
||||
"",
|
||||
"## 看板(OPEN)",
|
||||
"",
|
||||
"| Issue | GitHub 状态 | Branch | 验收状态 | 相关 Code Queue 任务 | 当前关注点 | 进度 |",
|
||||
"| --- | --- | --- | --- | --- | --- | --- |",
|
||||
"| [pikasTech/HWLAB#108](https://github.com/pikasTech/HWLAB/issues/108) HWLAB user feedback | OPEN | main | product | cq-hwlab | Cloud Workbench 用户反馈 | doing |",
|
||||
"",
|
||||
].join("\n"), "utf8");
|
||||
const hwlabProductBoard = await runCli(["gh", "issue", "update", "20", "--repo", "pikasTech/unidesk", "--mode", "replace", "--body-file", hwlabProductBoardFile, "--dry-run"], env);
|
||||
assertCondition(hwlabProductBoard.status !== 0, "#20 body guard should reject HWLAB product issue rows", hwlabProductBoard.json ?? { stdout: hwlabProductBoard.stdout });
|
||||
const hwlabProductBoardData = failedDataOf(hwlabProductBoard.json ?? {});
|
||||
const hwlabProductBoardGuard = hwlabProductBoardData.guard as JsonRecord;
|
||||
assertCondition(Array.isArray(hwlabProductBoardGuard.failures) && hwlabProductBoardGuard.failures.includes("code-queue-board-contains-hwlab-product-work"), "#20 guard should report HWLAB product routing pollution", hwlabProductBoardGuard);
|
||||
const hwlabProductHint = hwlabProductBoardGuard.codeQueueBoardHint as JsonRecord;
|
||||
const hwlabProductRouting = hwlabProductHint.hwlabProductRouting as JsonRecord;
|
||||
assertCondition(hwlabProductRouting.detected === true && String(hwlabProductRouting.route ?? "").includes("pikasTech/HWLAB"), "#20 guard should route HWLAB product issues to the HWLAB repo", hwlabProductRouting);
|
||||
|
||||
const hwlabProductUpsert = await runCli([
|
||||
"gh",
|
||||
"issue",
|
||||
"board-row",
|
||||
"upsert",
|
||||
"108",
|
||||
"--repo",
|
||||
"pikasTech/unidesk",
|
||||
"--board-issue",
|
||||
"20",
|
||||
"--section",
|
||||
"open",
|
||||
"--branch",
|
||||
"main",
|
||||
"--tasks",
|
||||
"cq-hwlab",
|
||||
"--summary",
|
||||
"pikasTech/HWLAB#108 HWLAB user feedback",
|
||||
"--focus",
|
||||
"Cloud Workbench 用户反馈",
|
||||
"--validation",
|
||||
"product",
|
||||
"--progress",
|
||||
"doing",
|
||||
"--dry-run",
|
||||
], env);
|
||||
assertCondition(hwlabProductUpsert.status !== 0, "#20 board-row upsert should reject HWLAB product issue rows", hwlabProductUpsert.json ?? { stdout: hwlabProductUpsert.stdout });
|
||||
const hwlabProductUpsertData = failedDataOf(hwlabProductUpsert.json ?? {});
|
||||
const hwlabProductUpsertGuard = hwlabProductUpsertData.guard as JsonRecord;
|
||||
assertCondition(Array.isArray(hwlabProductUpsertGuard.warnings) && hwlabProductUpsertGuard.warnings.includes("code-queue-board-contains-hwlab-product-work"), "board-row upsert guard should report HWLAB product routing pollution", hwlabProductUpsertGuard);
|
||||
|
||||
const hwlabGovernanceGuard = await runCli([
|
||||
"gh",
|
||||
"issue",
|
||||
"board-row",
|
||||
"upsert",
|
||||
"109",
|
||||
"--repo",
|
||||
"pikasTech/unidesk",
|
||||
"--board-issue",
|
||||
"20",
|
||||
"--section",
|
||||
"open",
|
||||
"--branch",
|
||||
"master",
|
||||
"--tasks",
|
||||
"cq-guard",
|
||||
"--summary",
|
||||
"UniDesk CLI guard for HWLAB#108 routing",
|
||||
"--focus",
|
||||
"commander governance guard prevents HWLAB product misfile",
|
||||
"--validation",
|
||||
"dry-run guard",
|
||||
"--progress",
|
||||
"ready",
|
||||
"--dry-run",
|
||||
], env);
|
||||
assertCondition(hwlabGovernanceGuard.status === 0, "#20 board-row upsert should allow UniDesk governance rows that mention HWLAB as routing context", hwlabGovernanceGuard.json ?? { stdout: hwlabGovernanceGuard.stdout });
|
||||
const hwlabGovernanceGuardData = dataOf(hwlabGovernanceGuard.json ?? {});
|
||||
const hwlabGovernanceGuardSummary = hwlabGovernanceGuardData.guard as JsonRecord;
|
||||
assertCondition(hwlabGovernanceGuardSummary.ok === true && !(hwlabGovernanceGuardSummary.warnings as unknown[]).includes("code-queue-board-contains-hwlab-product-work"), "governance rows mentioning HWLAB should not be classified as HWLAB product work", hwlabGovernanceGuardSummary);
|
||||
|
||||
const commanderBriefBlocked = await runCli(["gh", "issue", "edit", "24", "--repo", "pikasTech/unidesk", "--body-file", missingHeadingFile, "--dry-run"], env);
|
||||
assertCondition(commanderBriefBlocked.status !== 0, "#24 missing heading should fail", commanderBriefBlocked.json ?? { stdout: commanderBriefBlocked.stdout });
|
||||
const commanderBriefData = failedDataOf(commanderBriefBlocked.json ?? {});
|
||||
@@ -1397,6 +1483,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
"unsupported --json fields fail structurally",
|
||||
"issue edit --body-file rejects literal null",
|
||||
"#20/#24 body profile guards reject missing headings or wrong profile",
|
||||
"#20 body and board-row guards reject HWLAB product issue routing and point to pikasTech/HWLAB",
|
||||
"#20 board-row guard allows UniDesk governance rows that mention HWLAB only as routing context",
|
||||
"#24 commander-brief profile remains compatible",
|
||||
"daily commander brief issues match commander-brief profile by title",
|
||||
"non-brief issues fail commander-brief profile without printing token",
|
||||
|
||||
Reference in New Issue
Block a user