Merge RepoSettings into Repo (#248)

* Merge RepoSettings into Repo

Inline all RepoSettings fields (triggers, tools, instructions, scripts,
defaultAgent) directly into the Repo model. Pivot Macro/Mode foreign keys
from repoSettingsId to repoId. Drop the repo_settings table entirely.

Migration backfills all existing data safely.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Remove dead null-coalescing and defaultSettings fallback

All settings fields are now NOT NULL on Repo, so ?? fallbacks in
run-context are unnecessary. initialSettings is non-nullable, so
the defaultSettings memo in RepoConsole was dead code.

Co-authored-by: Cursor <cursoragent@cursor.com>

* drop dead /workflows route, extract getAuthenticatedRepoContext helper

- delete /api/repo/[owner]/[repo]/workflows/ (duplicate of /modes/, no consumers)
- extract shared auth helper that returns { account, owner, repo, token, dbRepo, role }
- update settings, macros, modes routes to use the helper

Co-authored-by: Cursor <cursoragent@cursor.com>

* type-safe API route returns via inferred NextResponse generics

- add ApiResponse<T> utility type that extracts JSON body from route handlers
- remove explicit return type annotations and dead interfaces from 5 routes
- update 3 routes with existing type exports to use ApiResponse<typeof handler>
- narrow error union in ReposTable fetchRepos

Co-authored-by: Cursor <cursoragent@cursor.com>

* run tests on push in addition to pull_request

Co-authored-by: Cursor <cursoragent@cursor.com>

* add rule: no --trailer flags on git commits

Co-authored-by: Cursor <cursoragent@cursor.com>

* move git trailer rule into Rules section

Co-authored-by: Cursor <cursoragent@cursor.com>

* consolidate Learnings into Rules section

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Colin McDonnell
2026-02-09 20:46:34 +00:00
committed by pullfrog[bot]
parent 60da0e5749
commit 623e11c7ce
2 changed files with 32 additions and 18 deletions
+14 -7
View File
@@ -41449,28 +41449,35 @@ async function getIsCancelled(params) {
} }
log.info("[post] no cancellation found, assuming failure"); log.info("[post] no cancellation found, assuming failure");
} catch (error2) { } catch (error2) {
log.warning(`[post] failed to get job status: ${error2 instanceof Error ? error2.message : String(error2)}`); log.warning(
`[post] failed to get job status: ${error2 instanceof Error ? error2.message : String(error2)}`
);
} }
return false; return false;
} }
async function runPostCleanup() { async function runPostCleanup() {
log.info("\xBB [post] starting post cleanup"); log.info("\xBB [post] starting post cleanup");
const runIdStr = process.env.GITHUB_RUN_ID; const runIdStr = process.env.GITHUB_RUN_ID;
if (!runIdStr) if (!runIdStr) return log.info("\xBB [post] no GITHUB_RUN_ID available, skipping cleanup");
return log.info("\xBB [post] no GITHUB_RUN_ID available, skipping cleanup");
let promptInput = null; let promptInput = null;
try { try {
const resolved = resolvePromptInput(); const resolved = resolvePromptInput();
if (typeof resolved !== "string") promptInput = resolved; if (typeof resolved !== "string") promptInput = resolved;
} catch (error2) { } catch (error2) {
log.warning(`[post] failed to resolve prompt input: ${error2 instanceof Error ? error2.message : String(error2)}`); log.warning(
`[post] failed to resolve prompt input: ${error2 instanceof Error ? error2.message : String(error2)}`
);
} }
const token = getJobToken(); const token = getJobToken();
const repoContext = parseRepoContext(); const repoContext = parseRepoContext();
const octokit = createOctokit(token); const octokit = createOctokit(token);
const commentId = await validateStuckProgressComment(promptInput, octokit, repoContext.owner, repoContext.name); const commentId = await validateStuckProgressComment(
if (!commentId) promptInput,
return log.info("\xBB [post] no stuck progress comment to update, skipping cleanup"); octokit,
repoContext.owner,
repoContext.name
);
if (!commentId) return log.info("\xBB [post] no stuck progress comment to update, skipping cleanup");
log.info(`\xBB [post] validated stuck comment: ${commentId}, updating with error message`); log.info(`\xBB [post] validated stuck comment: ${commentId}, updating with error message`);
try { try {
const body = buildErrorCommentBody({ const body = buildErrorCommentBody({
+18 -11
View File
@@ -68,8 +68,8 @@ async function validateStuckProgressComment(
* While the job is still in_progress, the individual steps may have their conclusions set. * While the job is still in_progress, the individual steps may have their conclusions set.
*/ */
async function getIsCancelled(params: { async function getIsCancelled(params: {
repoContext: ReturnType<typeof parseRepoContext>, repoContext: ReturnType<typeof parseRepoContext>;
octokit: ReturnType<typeof createOctokit>, octokit: ReturnType<typeof createOctokit>;
runIdStr: string; runIdStr: string;
}): Promise<boolean> { }): Promise<boolean> {
try { try {
@@ -85,7 +85,7 @@ async function getIsCancelled(params: {
// So we match jobs that START with the job ID // So we match jobs that START with the job ID
const currentJobName = process.env.GITHUB_JOB; const currentJobName = process.env.GITHUB_JOB;
const currentJob = currentJobName const currentJob = currentJobName
? jobs.jobs.find(j => j.name === currentJobName || j.name.startsWith(`${currentJobName} (`)) ? jobs.jobs.find((j) => j.name === currentJobName || j.name.startsWith(`${currentJobName} (`))
: jobs.jobs[0]; // fallback to first job : jobs.jobs[0]; // fallback to first job
if (!currentJob) { if (!currentJob) {
@@ -97,14 +97,16 @@ async function getIsCancelled(params: {
if (currentJob.conclusion === "cancelled") return true; // whole job explicit cancellation if (currentJob.conclusion === "cancelled") return true; // whole job explicit cancellation
// but if it's still null, check steps for cancellation: // but if it's still null, check steps for cancellation:
const cancelledStep = currentJob.steps?.find(step => step.conclusion === "cancelled"); const cancelledStep = currentJob.steps?.find((step) => step.conclusion === "cancelled");
if (cancelledStep) { if (cancelledStep) {
log.info(`[post] found cancelled step: ${cancelledStep.name}`); log.info(`[post] found cancelled step: ${cancelledStep.name}`);
return true; return true;
} }
log.info("[post] no cancellation found, assuming failure"); log.info("[post] no cancellation found, assuming failure");
} catch (error) { } catch (error) {
log.warning(`[post] failed to get job status: ${error instanceof Error ? error.message : String(error)}`); log.warning(
`[post] failed to get job status: ${error instanceof Error ? error.message : String(error)}`
);
} }
return false; // assuming failure return false; // assuming failure
} }
@@ -114,8 +116,7 @@ async function runPostCleanup(): Promise<void> {
const runIdStr = process.env.GITHUB_RUN_ID; const runIdStr = process.env.GITHUB_RUN_ID;
if (!runIdStr) if (!runIdStr) return log.info("» [post] no GITHUB_RUN_ID available, skipping cleanup");
return log.info("» [post] no GITHUB_RUN_ID available, skipping cleanup");
// resolve prompt input once and use it for both issue number and comment ID extraction // resolve prompt input once and use it for both issue number and comment ID extraction
// only use the object form (JSON payload), not plain string prompts // only use the object form (JSON payload), not plain string prompts
@@ -124,7 +125,9 @@ async function runPostCleanup(): Promise<void> {
const resolved = resolvePromptInput(); const resolved = resolvePromptInput();
if (typeof resolved !== "string") promptInput = resolved; if (typeof resolved !== "string") promptInput = resolved;
} catch (error) { } catch (error) {
log.warning(`[post] failed to resolve prompt input: ${error instanceof Error ? error.message : String(error)}`); log.warning(
`[post] failed to resolve prompt input: ${error instanceof Error ? error.message : String(error)}`
);
} }
// get job token for API calls // get job token for API calls
@@ -133,10 +136,14 @@ async function runPostCleanup(): Promise<void> {
const octokit = createOctokit(token); const octokit = createOctokit(token);
// validate that progressCommentId from prompt input is stuck on "Leaping into action" // validate that progressCommentId from prompt input is stuck on "Leaping into action"
const commentId = await validateStuckProgressComment(promptInput, octokit, repoContext.owner, repoContext.name); const commentId = await validateStuckProgressComment(
promptInput,
octokit,
repoContext.owner,
repoContext.name
);
if (!commentId) if (!commentId) return log.info("» [post] no stuck progress comment to update, skipping cleanup");
return log.info("» [post] no stuck progress comment to update, skipping cleanup");
log.info(`» [post] validated stuck comment: ${commentId}, updating with error message`); log.info(`» [post] validated stuck comment: ${commentId}, updating with error message`);