fix: disable board audit coverage validation
This commit is contained in:
@@ -654,29 +654,32 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
assertCondition(boardAudit.status === 0, "issue board-audit should succeed as a read-only audit", boardAudit.json ?? { stdout: boardAudit.stdout, stderr: boardAudit.stderr });
|
||||
const boardAuditData = dataOf(boardAudit.json ?? {});
|
||||
assertCondition(boardAuditData.command === "issue board-audit" && boardAuditData.dryRun === true && boardAuditData.readOnly === true, "board-audit should be explicit read-only dry-run", boardAuditData);
|
||||
const boardAuditIssue = boardAuditData.boardIssue as JsonRecord;
|
||||
assertCondition(typeof boardAuditIssue.bodySha === "string" && String(boardAuditIssue.bodySha).length === 64, "board-audit should expose board body sha", boardAuditIssue);
|
||||
const boardAuditSummary = boardAuditData.summary as JsonRecord;
|
||||
assertCondition(boardAuditSummary.openIssues === null && boardAuditSummary.closedIssues === null, "board-audit should not fetch GitHub open/closed issue lists", boardAuditSummary);
|
||||
assertCondition(boardAuditSummary.openRows === 4 && boardAuditSummary.closedRows === 3 && boardAuditSummary.parsedSections === 2, "board-audit should report parsed board structure", boardAuditSummary);
|
||||
const boardAuditValidation = boardAuditData.validation as JsonRecord;
|
||||
const openClosedCoverage = boardAuditValidation.openClosedCoverage as JsonRecord;
|
||||
assertCondition(openClosedCoverage.enabled === false, "board-audit should disable OPEN/CLOSED coverage validation", boardAuditValidation);
|
||||
const missingOpenIssues = boardAuditData.missingOpenIssues as JsonRecord[];
|
||||
assertCondition(Array.isArray(missingOpenIssues) && missingOpenIssues.some((issue) => issue.number === 36), "board-audit should report open issue missing from OPEN table", missingOpenIssues);
|
||||
assertCondition(!missingOpenIssues.some((issue) => issue.number === 45), "board-audit should use the primary markdown issue link as the Issue-column row key", missingOpenIssues);
|
||||
assertCondition(!missingOpenIssues.some((issue) => issue.number === 46), "board-audit should not require daily commander briefs in the OPEN table", missingOpenIssues);
|
||||
assertCondition(Array.isArray(missingOpenIssues) && missingOpenIssues.length === 0, "board-audit should not report missing OPEN rows", missingOpenIssues);
|
||||
const closedInOpenRows = boardAuditData.closedInOpenRows as JsonRecord[];
|
||||
assertCondition(Array.isArray(closedInOpenRows) && closedInOpenRows.some((issue) => issue.number === 40), "board-audit should report closed issue still in OPEN table", closedInOpenRows);
|
||||
assertCondition(Array.isArray(closedInOpenRows) && closedInOpenRows.length === 0, "board-audit should not report closed issues in OPEN rows", closedInOpenRows);
|
||||
const missingClosedRows = boardAuditData.missingClosedRows as JsonRecord[];
|
||||
assertCondition(Array.isArray(missingClosedRows) && missingClosedRows.some((issue) => issue.number === 40) && missingClosedRows.some((issue) => issue.number === 41), "board-audit should report closed issues missing from CLOSED table", missingClosedRows);
|
||||
assertCondition(!missingClosedRows.some((issue) => issue.number === 18), "board-audit should ignore explanatory issue references after the primary Issue-column markdown link", missingClosedRows);
|
||||
assertCondition(Array.isArray(missingClosedRows) && missingClosedRows.length === 0, "board-audit should not report missing CLOSED rows", missingClosedRows);
|
||||
const openInClosedRows = boardAuditData.openInClosedRows as JsonRecord[];
|
||||
assertCondition(Array.isArray(openInClosedRows) && openInClosedRows.some((issue) => issue.number === 36), "board-audit should report open issue placed in CLOSED table", openInClosedRows);
|
||||
assertCondition(Array.isArray(openInClosedRows) && openInClosedRows.length === 0, "board-audit should not report open issues in CLOSED rows", openInClosedRows);
|
||||
const rowValidationWarnings = boardAuditData.rowValidationWarnings as JsonRecord[];
|
||||
assertCondition(Array.isArray(rowValidationWarnings) && rowValidationWarnings.some((warning) => warning.issueNumber === 40 && Array.isArray(warning.missingColumns) && (warning.missingColumns as unknown[]).includes("branch") && (warning.missingColumns as unknown[]).includes("acceptance") && (warning.missingColumns as unknown[]).includes("progress")), "board-audit should report missing required board columns", rowValidationWarnings);
|
||||
assertCondition(!rowValidationWarnings.some((warning) => warning.kind === "multiple-issue-references"), "board-audit should not treat explanatory issue references outside the Issue column as row-key conflicts", rowValidationWarnings);
|
||||
assertCondition(!rowValidationWarnings.some((warning) => warning.kind === "multiple-issue-references" && String(warning.rowPreview ?? "").includes("#20 总看板")), "board-audit should not warn when an Issue-column title mentions #20 after primary #45 link", rowValidationWarnings);
|
||||
assertCondition(!rowValidationWarnings.some((warning) => warning.kind === "multiple-issue-references" && String(warning.rowPreview ?? "").includes("基于 #4")), "board-audit should not warn when an Issue-column title mentions #4 after primary #18 link", rowValidationWarnings);
|
||||
assertCondition(!rowValidationWarnings.some((warning) => Array.isArray(warning.missingColumns) && (warning.missingColumns as unknown[]).includes("relatedTask")), "board-audit should recognize relatedTask from the current header", rowValidationWarnings);
|
||||
assertCondition(Array.isArray(rowValidationWarnings) && rowValidationWarnings.length === 0, "board-audit should not report required-column row warnings", rowValidationWarnings);
|
||||
const parserWarnings = boardAuditData.parserWarnings as JsonRecord[];
|
||||
assertCondition(Array.isArray(parserWarnings), "board-audit should expose parser warnings separately", boardAuditData);
|
||||
const ignoredIssues = boardAuditData.ignoredIssues as JsonRecord[];
|
||||
assertCondition(Array.isArray(ignoredIssues) && ignoredIssues.some((issue) => issue.number === 20 && issue.reason === "known-meta") && ignoredIssues.some((issue) => issue.number === 24 && issue.reason === "known-meta"), "board-audit should ignore configured known meta issues", ignoredIssues);
|
||||
assertCondition(ignoredIssues.some((issue) => issue.number === 46 && issue.reason === "brief-index-managed"), "board-audit should ignore daily commander briefs managed by the #20 brief index", ignoredIssues);
|
||||
assertCondition(Array.isArray(ignoredIssues) && ignoredIssues.length === 0, "board-audit should not produce ignored issue coverage output", ignoredIssues);
|
||||
const recommendedActions = boardAuditData.recommendedActions as JsonRecord[];
|
||||
assertCondition(Array.isArray(recommendedActions) && recommendedActions.some((action) => action.action === "add-open-row" && action.issueNumber === 36) && recommendedActions.some((action) => action.action === "move-open-row-to-closed" && action.issueNumber === 40), "board-audit should emit machine-readable recommended actions", recommendedActions);
|
||||
assertCondition(!recommendedActions.some((action) => action.action === "add-open-row" && action.issueNumber === 46), "board-audit should not recommend adding daily commander briefs to the OPEN table", recommendedActions);
|
||||
assertCondition(Array.isArray(recommendedActions) && recommendedActions.length === 0, "board-audit should not emit OPEN/CLOSED coverage actions", recommendedActions);
|
||||
const boardAuditGetRequests = mock.requests.slice(boardAuditRequestCountBefore).filter((request) => request.method === "GET");
|
||||
assertCondition(boardAuditGetRequests.length === 1 && boardAuditGetRequests[0]?.url === "/repos/pikasTech/unidesk/issues/20", "board-audit should only fetch the board issue body", boardAuditGetRequests);
|
||||
const boardAuditWriteCount = mock.requests.slice(boardAuditRequestCountBefore).filter((request) => request.method === "PATCH" || request.method === "DELETE" || request.method === "POST").length;
|
||||
assertCondition(boardAuditWriteCount === 0, "board-audit must not write GitHub", { requests: mock.requests.slice(boardAuditRequestCountBefore) });
|
||||
|
||||
@@ -684,8 +687,8 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
const legacyBoardAudit = await runCli(["gh", "issue", "board-audit", "--repo", "pikasTech/unidesk", "--board-issue", "60", "--limit", "60", "--dry-run"], env);
|
||||
assertCondition(legacyBoardAudit.status === 0, "legacy board-audit fixture should succeed", legacyBoardAudit.json ?? { stdout: legacyBoardAudit.stdout, stderr: legacyBoardAudit.stderr });
|
||||
const legacyBoardAuditData = dataOf(legacyBoardAudit.json ?? {});
|
||||
const legacyWarnings = legacyBoardAuditData.rowValidationWarnings as JsonRecord[];
|
||||
assertCondition(Array.isArray(legacyWarnings) && legacyWarnings.some((warning) => warning.kind === "multiple-issue-references" && warning.issueNumber === 101), "legacy board-audit fixture should keep whole-row fallback when no Issue column exists", legacyWarnings);
|
||||
const legacyWarnings = legacyBoardAuditData.parserWarnings as JsonRecord[];
|
||||
assertCondition(Array.isArray(legacyWarnings) && legacyWarnings.some((warning) => warning.kind === "multiple-issue-references" && warning.issueNumber === 101), "legacy board-audit fixture should keep parser warnings when no Issue column exists", legacyWarnings);
|
||||
const legacyBoardWriteCount = mock.requests.slice(legacyBoardAuditRequestCountBefore).filter((request) => request.method === "PATCH" || request.method === "DELETE" || request.method === "POST").length;
|
||||
assertCondition(legacyBoardWriteCount === 0, "legacy board-audit must not write GitHub", { requests: mock.requests.slice(legacyBoardAuditRequestCountBefore) });
|
||||
|
||||
@@ -1326,7 +1329,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
|
||||
"issue list default fields include labels and filter pull requests",
|
||||
"issue scan-escape classifies pollution, explanatory mentions, and body risks",
|
||||
"issue cleanup-plan remains dry-run with body/comment cleanup suggestions",
|
||||
"issue board-audit reports missing open rows, closed/open section mismatches, missing closed rows, meta/brief-index ignores, and row validation warnings without writes",
|
||||
"issue board-audit returns read-only board structure, disables OPEN/CLOSED coverage validation, and keeps compatibility fields empty without writes",
|
||||
"issue board-row list/get expose parsed #20 rows without writes",
|
||||
"issue board-row upsert updates existing rows, adds missing rows, reports operation, preserves table trailers, rejects ambiguous rows, blocks stale body SHA writes, and stays dry-run without concurrency guards",
|
||||
"issue board-row add/delete without guard stay on dry-run and do not PATCH",
|
||||
|
||||
+43
-118
@@ -3893,101 +3893,10 @@ async function issueBoardAudit(repo: string, token: string, options: GitHubOptio
|
||||
const commandName = "issue board-audit";
|
||||
const boardIssue = await getIssue(token, repo, options.boardIssue);
|
||||
if (isGitHubError(boardIssue)) return commandError(commandName, repo, boardIssue, { boardIssue: options.boardIssue });
|
||||
const [rawOpenIssues, rawClosedIssues] = await Promise.all([
|
||||
listIssues(token, repo, "open", options.limit),
|
||||
listIssues(token, repo, "closed", options.limit),
|
||||
]);
|
||||
if (isGitHubError(rawOpenIssues)) return commandError(commandName, repo, rawOpenIssues, { phase: "list-open-issues", boardIssue: options.boardIssue, limit: options.limit });
|
||||
if (isGitHubError(rawClosedIssues)) return commandError(commandName, repo, rawClosedIssues, { phase: "list-closed-issues", boardIssue: options.boardIssue, limit: options.limit });
|
||||
|
||||
const openIssues = rawOpenIssues.filter((issue) => issue.pull_request === undefined).slice(0, options.limit).map(boardIssueEntry);
|
||||
const closedIssues = rawClosedIssues.filter((issue) => issue.pull_request === undefined).slice(0, options.limit).map(boardIssueEntry);
|
||||
const allListedIssues = [...openIssues, ...closedIssues];
|
||||
const issueMap = new Map<number, BoardIssueEntry>(allListedIssues.map((issue) => [issue.number, issue]));
|
||||
const ignoreMap = boardIgnoreMap(options, allListedIssues);
|
||||
const parsed = parseBoardTables(boardIssue.body ?? "");
|
||||
const rowValidationWarnings = parsed.warnings.filter((warning) => warning.issueNumber === null || !ignoreMap.has(warning.issueNumber));
|
||||
const openRows = parsed.sections.filter((section) => section.kind === "open").flatMap((section) => section.rows);
|
||||
const closedRows = parsed.sections.filter((section) => section.kind === "closed").flatMap((section) => section.rows);
|
||||
const openRowMap = boardRowsByIssue(openRows);
|
||||
const closedRowMap = boardRowsByIssue(closedRows);
|
||||
const openIssueNumbers = new Set(openIssues.map((issue) => issue.number));
|
||||
const closedIssueNumbers = new Set(closedIssues.map((issue) => issue.number));
|
||||
const boardIssueNumbers = Array.from(new Set([...openRowMap.keys(), ...closedRowMap.keys()]));
|
||||
|
||||
const missingOpenIssues = sortedIssueEntries(openIssues.filter((issue) => !ignoreMap.has(issue.number) && !openRowMap.has(issue.number)));
|
||||
const closedInOpenRows = sortedIssueEntries(closedIssues.filter((issue) => !ignoreMap.has(issue.number) && openRowMap.has(issue.number))).map((issue) => ({
|
||||
...issue,
|
||||
rows: (openRowMap.get(issue.number) ?? []).map((row) => ({ lineNumber: row.lineNumber, rowPreview: preview(row.raw) })),
|
||||
}));
|
||||
const missingClosedRows = sortedIssueEntries(closedIssues.filter((issue) => !ignoreMap.has(issue.number) && !closedRowMap.has(issue.number)));
|
||||
const openInClosedRows = sortedIssueEntries(openIssues.filter((issue) => !ignoreMap.has(issue.number) && closedRowMap.has(issue.number))).map((issue) => ({
|
||||
...issue,
|
||||
rows: (closedRowMap.get(issue.number) ?? []).map((row) => ({ lineNumber: row.lineNumber, rowPreview: preview(row.raw) })),
|
||||
}));
|
||||
const staleOpenRows = Array.from(openRowMap.entries())
|
||||
.filter(([issueNumber]) => !ignoreMap.has(issueNumber) && !openIssueNumbers.has(issueNumber))
|
||||
.map(([issueNumber, rows]) => ({
|
||||
issueNumber,
|
||||
knownState: closedIssueNumbers.has(issueNumber) ? "closed" : "not-in-listed-window",
|
||||
rows: rows.map((row) => ({ lineNumber: row.lineNumber, rowPreview: preview(row.raw) })),
|
||||
}))
|
||||
.sort((a, b) => a.issueNumber - b.issueNumber);
|
||||
const boardOnlyRows = boardIssueNumbers
|
||||
.filter((issueNumber) => !ignoreMap.has(issueNumber) && !issueMap.has(issueNumber))
|
||||
.sort((a, b) => a - b)
|
||||
.map((issueNumber) => ({
|
||||
issueNumber,
|
||||
sections: [
|
||||
...(openRowMap.has(issueNumber) ? ["open"] : []),
|
||||
...(closedRowMap.has(issueNumber) ? ["closed"] : []),
|
||||
],
|
||||
rows: [...(openRowMap.get(issueNumber) ?? []), ...(closedRowMap.get(issueNumber) ?? [])].map((row) => ({
|
||||
section: row.section,
|
||||
lineNumber: row.lineNumber,
|
||||
rowPreview: preview(row.raw),
|
||||
})),
|
||||
}));
|
||||
const ignoredIssues = [
|
||||
...ignoredIssueList(allListedIssues, ignoreMap),
|
||||
...ignoredBoardOnlyIssues(boardIssueNumbers, issueMap, ignoreMap),
|
||||
].sort((a, b) => a.number - b.number);
|
||||
const recommendedActions = [
|
||||
...missingOpenIssues.map((issue) => ({
|
||||
action: "add-open-row",
|
||||
issueNumber: issue.number,
|
||||
title: issue.title,
|
||||
section: "open",
|
||||
reason: "GitHub issue is open but no #20 OPEN table row was found.",
|
||||
})),
|
||||
...closedInOpenRows.map((issue) => ({
|
||||
action: "move-open-row-to-closed",
|
||||
issueNumber: issue.number,
|
||||
title: issue.title,
|
||||
reason: "GitHub issue is closed but still appears in the #20 OPEN table.",
|
||||
})),
|
||||
...missingClosedRows.map((issue) => ({
|
||||
action: "add-closed-row",
|
||||
issueNumber: issue.number,
|
||||
title: issue.title,
|
||||
section: "closed",
|
||||
reason: "GitHub issue is closed but no #20 CLOSED table row was found.",
|
||||
})),
|
||||
...openInClosedRows.map((issue) => ({
|
||||
action: "move-closed-row-to-open",
|
||||
issueNumber: issue.number,
|
||||
title: issue.title,
|
||||
reason: "GitHub issue is open but appears in the #20 CLOSED table.",
|
||||
})),
|
||||
...rowValidationWarnings.map((warning) => ({
|
||||
action: "fill-board-row-fields",
|
||||
issueNumber: warning.issueNumber,
|
||||
section: warning.section,
|
||||
lineNumber: warning.lineNumber,
|
||||
reason: warning.message,
|
||||
missingColumns: warning.missingColumns ?? [],
|
||||
})),
|
||||
];
|
||||
const body = boardIssue.body ?? "";
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
@@ -4001,7 +3910,9 @@ async function issueBoardAudit(repo: string, token: string, options: GitHubOptio
|
||||
title: boardIssue.title,
|
||||
state: boardIssue.state,
|
||||
url: boardIssue.html_url,
|
||||
bodyChars: (boardIssue.body ?? "").length,
|
||||
bodyChars: body.length,
|
||||
bodyLines: body.length === 0 ? 0 : normalizeNewlines(body).split("\n").length,
|
||||
bodySha: bodySha(body),
|
||||
updatedAt: boardIssue.updated_at ?? null,
|
||||
},
|
||||
config: {
|
||||
@@ -4009,21 +3920,36 @@ async function issueBoardAudit(repo: string, token: string, options: GitHubOptio
|
||||
knownMetaIssues: mergedKnownMetaIssues(options),
|
||||
ignoredIssues: options.ignoredIssues,
|
||||
briefIndexManagedIssuePattern: options.boardIssue === CODE_QUEUE_BOARD_TARGET_ISSUE ? "YYYY-MM-DD 指挥简报(北京时间)" : null,
|
||||
requiredColumns: BOARD_AUDIT_REQUIRED_COLUMNS.slice(),
|
||||
openClosedCoverageValidation: false,
|
||||
requiredColumns: [],
|
||||
},
|
||||
summary: {
|
||||
openIssues: openIssues.length,
|
||||
closedIssues: closedIssues.length,
|
||||
openIssues: null,
|
||||
closedIssues: null,
|
||||
openRows: openRows.length,
|
||||
closedRows: closedRows.length,
|
||||
missingOpenIssues: missingOpenIssues.length,
|
||||
closedInOpenRows: closedInOpenRows.length,
|
||||
missingClosedRows: missingClosedRows.length,
|
||||
openInClosedRows: openInClosedRows.length,
|
||||
rowValidationWarnings: rowValidationWarnings.length,
|
||||
ignoredIssues: ignoredIssues.length,
|
||||
boardOnlyRows: boardOnlyRows.length,
|
||||
staleOpenRows: staleOpenRows.length,
|
||||
parsedSections: parsed.sections.length,
|
||||
parsedRows: parsed.sections.reduce((sum, section) => sum + section.rows.length, 0),
|
||||
parserWarnings: parsed.warnings.length,
|
||||
missingOpenIssues: 0,
|
||||
closedInOpenRows: 0,
|
||||
missingClosedRows: 0,
|
||||
openInClosedRows: 0,
|
||||
rowValidationWarnings: 0,
|
||||
ignoredIssues: 0,
|
||||
boardOnlyRows: 0,
|
||||
staleOpenRows: 0,
|
||||
recommendedActions: 0,
|
||||
},
|
||||
validation: {
|
||||
openClosedCoverage: {
|
||||
enabled: false,
|
||||
reason: "OPEN/CLOSED table coverage validation has been disabled; this command no longer compares GitHub issue state against board rows.",
|
||||
},
|
||||
rowRequiredColumns: {
|
||||
enabled: false,
|
||||
reason: "board-audit no longer reports missing required row columns; use board-row commands when intentionally maintaining a legacy OPEN/CLOSED table.",
|
||||
},
|
||||
},
|
||||
sections: parsed.sections.map((section) => ({
|
||||
kind: section.kind,
|
||||
@@ -4034,25 +3960,24 @@ async function issueBoardAudit(repo: string, token: string, options: GitHubOptio
|
||||
rows: section.rows.length,
|
||||
issueNumbers: section.rows.flatMap((row) => row.issueNumbers),
|
||||
})),
|
||||
missingOpenIssues,
|
||||
closedInOpenRows,
|
||||
missingClosedRows,
|
||||
openInClosedRows,
|
||||
staleOpenRows,
|
||||
boardOnlyRows,
|
||||
rowValidationWarnings,
|
||||
ignoredIssues,
|
||||
recommendedActions,
|
||||
parserWarnings: parsed.warnings,
|
||||
missingOpenIssues: [],
|
||||
closedInOpenRows: [],
|
||||
missingClosedRows: [],
|
||||
openInClosedRows: [],
|
||||
staleOpenRows: [],
|
||||
boardOnlyRows: [],
|
||||
rowValidationWarnings: [],
|
||||
ignoredIssues: [],
|
||||
recommendedActions: [],
|
||||
request: {
|
||||
method: "GET",
|
||||
paths: [
|
||||
`/repos/{owner}/{repo}/issues/${options.boardIssue}`,
|
||||
"/repos/{owner}/{repo}/issues?state=open",
|
||||
"/repos/{owner}/{repo}/issues?state=closed",
|
||||
],
|
||||
query: { per_page: options.limit },
|
||||
query: {},
|
||||
},
|
||||
note: "Read-only board audit; no issue body was edited, no issue was closed, and no comments were written.",
|
||||
note: "Read-only board structure audit; OPEN/CLOSED coverage validation is disabled, no issue body was edited, no issue was closed, and no comments were written.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4830,7 +4755,7 @@ export function ghHelp(): unknown {
|
||||
"When staging a body file from a shell, use a quoted heredoc such as cat <<'EOF' > /tmp/body.md so backticks and backslashes are not expanded before --body-file reads the file.",
|
||||
"For JSON request bodies in other CLI namespaces, prefer --body-file or --body-stdin over long inline shell arguments; GitHub Markdown writes intentionally use --body-file only.",
|
||||
"issue scan-escape classifies literal \\n findings as suspected-pollution, explanatory-mention, or risk, and emits cleanupSuggestions with body/comment ids plus diff-like previews. cleanup-plan is an alias that remains dry-run/read-only.",
|
||||
"issue board-audit is read-only and defaults to repo pikasTech/unidesk plus board issue #20. It compares GitHub open/closed issue lists with the board OPEN/CLOSED tables and reports missingOpenIssues, closedInOpenRows, missingClosedRows, rowValidationWarnings, ignoredIssues, and recommendedActions. When an Issue column exists, row.issueNumber is taken from that column; #20 and #24 are known meta issues by default. Daily rolling brief issues titled YYYY-MM-DD 指挥简报(北京时间) are managed by #20's brief index and appear in ignoredIssues with reason=brief-index-managed instead of missingOpenIssues.",
|
||||
"issue board-audit is read-only and defaults to repo pikasTech/unidesk plus board issue #20. It reads only the board issue body, returns body size/SHA and parsed Markdown board sections, and no longer validates GitHub open/closed issue coverage against OPEN/CLOSED tables. The legacy coverage fields remain present as empty arrays/zero counts for compatibility.",
|
||||
"issue board-row list/get reuse the board-audit table parser and are read-only. board-row update changes one table cell by issue number, returns old/new row, body SHA, body guard and request plan, and defaults to dry-run unless --expect-updated-at or --expect-body-sha is supplied for the guarded PATCH. Field aliases map status and validation to the 验收状态 column, tasks to 相关 Code Queue 任务, and focus to 当前关注点.",
|
||||
"issue board-row upsert updates an existing row when the issue is already present, or generates a complete row in --section open|closed when missing. It returns operation=update or operation=add, defaults to dry-run, requires --expect-body-sha or --expect-updated-at before PATCH, and refuses section migration; use board-row move for OPEN/CLOSED migration.",
|
||||
"issue board-row add/move/delete are row-scoped #20 table mutations. add validates a one-line --row-file against the target table column count, Issue column, and GitHub 状态 column; move refuses duplicate/ambiguous rows and can update GitHub 状态 via --status; delete removes only the matched row. All three default to dry-run and require --expect-body-sha or --expect-updated-at before PATCH. add/move/delete return old/new row, body SHA, and line/section plan details for the parsed table mutation.",
|
||||
|
||||
Reference in New Issue
Block a user