Limit legacy disposition preview lookback

Default the legacy execution-disposition preview to recently created issues, keep an explicit --all escape hatch, and cover the lookback behavior with a targeted test.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-04-29 13:54:24 -05:00
1 parent b13c9f5fba
commit 229a4c60a2
3 files changed
+58 -17

No files matched your search

+1
View File
@@ -22,6 +22,7 @@
"db:migrate": "pnpm --filter @paperclipai/db migrate",
"issue-references:backfill": "pnpm run preflight:workspace-links && tsx scripts/backfill-issue-reference-mentions.ts",
"execution-dispositions:preview": "tsx scripts/preview-legacy-invalid-execution-dispositions.ts",
"execution-dispositions:preview:test": "tsx --test scripts/preview-legacy-invalid-execution-dispositions.test.ts",
"secrets:migrate-inline-env": "tsx scripts/migrate-inline-env-secrets.ts",
"db:backup": "./scripts/backup-db.sh",
"paperclipai": "node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts",
@@ -0,0 +1,29 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { filterIssuesByLookback, issueLookbackTimestamp } from "./preview-legacy-invalid-execution-dispositions.ts";
describe("legacy execution-disposition preview lookback", () => {
it("scopes default repair candidates by issue creation time, not recent updates", () => {
const cutoff = new Date("2026-04-27T12:00:00.000Z");
const oldButRecentlyUpdated = {
id: "old",
title: "old blocked issue",
status: "blocked" as const,
createdAt: "2026-04-20T12:00:00.000Z",
updatedAt: "2026-04-29T12:00:00.000Z",
};
const newlyCreated = {
id: "new",
title: "new blocked issue",
status: "blocked" as const,
createdAt: "2026-04-28T12:00:00.000Z",
updatedAt: "2026-04-28T12:00:00.000Z",
};
assert.equal(issueLookbackTimestamp(oldButRecentlyUpdated)?.toISOString(), "2026-04-20T12:00:00.000Z");
assert.deepEqual(filterIssuesByLookback([oldButRecentlyUpdated, newlyCreated], cutoff).map((issue) => issue.id), [
"new",
]);
});
});
@@ -1,3 +1,5 @@
import { pathToFileURL } from "node:url";
type IssueStatus = "backlog" | "todo" | "in_progress" | "in_review" | "done" | "blocked" | "cancelled";
type InvalidDisposition = {
@@ -80,14 +82,14 @@ function dateValue(value: string | Date | null | undefined): Date | null {
return Number.isNaN(parsed.getTime()) ? null : parsed;
}
function issueScopeTimestamp(issue: IssueSummary): Date | null {
return dateValue(issue.updatedAt) ?? dateValue(issue.createdAt);
export function issueLookbackTimestamp(issue: IssueSummary): Date | null {
return dateValue(issue.createdAt);
}
function filterIssuesByLookback(issues: IssueSummary[], cutoff: Date | null): IssueSummary[] {
export function filterIssuesByLookback(issues: IssueSummary[], cutoff: Date | null): IssueSummary[] {
if (!cutoff) return issues;
return issues.filter((issue) => {
const timestamp = issueScopeTimestamp(issue);
const timestamp = issueLookbackTimestamp(issue);
return timestamp ? timestamp >= cutoff : false;
});
}
@@ -183,6 +185,19 @@ function proposedAction(reason: string, issue: IssueSummary): string {
}
}
let apiUrl: string;
let apiKey: string;
let runId: string | undefined;
function configureApiFromEnvironment() {
apiUrl = requireConfig(
"PAPERCLIP_API_URL or --api-url",
readFlag("--api-url") ?? process.env.PAPERCLIP_API_URL,
);
apiKey = requireConfig("PAPERCLIP_API_KEY", process.env.PAPERCLIP_API_KEY);
runId = process.env.PAPERCLIP_RUN_ID?.trim() || undefined;
}
async function requestJson<T>(method: string, path: string, body?: unknown): Promise<T> {
const url = new URL(path, apiUrl);
const response = await fetch(url, {
@@ -284,7 +299,7 @@ function printPreview(
console.log(`# Legacy execution-disposition reconciliation preview`);
console.log("");
console.log(`Company: ${companyId}`);
console.log(`Lookback: ${cutoff ? `updated since ${cutoff.toISOString()}` : "all non-terminal issues"}`);
console.log(`Lookback: ${cutoff ? `created since ${cutoff.toISOString()}` : "all non-terminal issues"}`);
console.log(`Non-terminal issues fetched: ${totalFetched}`);
console.log(`Non-terminal issues scanned: ${totalScanned}`);
console.log(`Invalid issues found: ${invalidCount}`);
@@ -340,14 +355,8 @@ async function applySafeCorrections(groups: PreviewGroup[]): Promise<number> {
return applied;
}
const apiUrl = requireConfig(
"PAPERCLIP_API_URL or --api-url",
readFlag("--api-url") ?? process.env.PAPERCLIP_API_URL,
);
const apiKey = requireConfig("PAPERCLIP_API_KEY", process.env.PAPERCLIP_API_KEY);
const runId = process.env.PAPERCLIP_RUN_ID?.trim();
async function main() {
configureApiFromEnvironment();
const companyId = requireConfig(
"PAPERCLIP_COMPANY_ID or --company",
readFlag("--company") ?? process.env.PAPERCLIP_COMPANY_ID,
@@ -372,8 +381,10 @@ async function main() {
printPreview(companyId, fetchedIssues.length, issues.length, cutoff, groups, appliedCount);
}
void main().catch((error) => {
const message = error instanceof Error ? error.message : String(error);
console.error(`Legacy execution-disposition reconciliation preview failed: ${message}`);
process.exitCode = 1;
});
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
void main().catch((error) => {
const message = error instanceof Error ? error.message : String(error);
console.error(`Legacy execution-disposition reconciliation preview failed: ${message}`);
process.exitCode = 1;
});
}