fix(pipeline): track produced file paths accurately for deploy URL
- RunContext.producedFiles[] accumulates repo-relative paths of extracted code files as juniors write them - implement selfTestReport now includes producedFiles[] - runner extractStageText forwards producedFiles= in priorStages - derivePreviewUrl uses producedFiles list first (exact match), then falls back to rawUrlBase/index.html heuristic - Fixes bug where deployUrl pointed to files/index.html but actual repo path was implement/files/frontend/sprints/SPRINT-AUTO/index.html
This commit is contained in:
@@ -31,6 +31,8 @@ interface RunContext {
|
||||
rails: RailsClient;
|
||||
agentName: string;
|
||||
workspaceDir: string;
|
||||
/** Aggregated code file paths (relative to pipeline repo root) across all juniors */
|
||||
producedFiles: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +51,13 @@ export async function executeInvocation(
|
||||
const workspaceDir = join(WORKSPACE_ROOT, req.pipelineId, req.stage);
|
||||
await mkdir(workspaceDir, { recursive: true });
|
||||
|
||||
const ctx: RunContext = { req, rails, agentName, workspaceDir };
|
||||
const ctx: RunContext = {
|
||||
req,
|
||||
rails,
|
||||
agentName,
|
||||
workspaceDir,
|
||||
producedFiles: [],
|
||||
};
|
||||
|
||||
// 1) Manager itself runs first (it is the single root). Its output is the
|
||||
// strategic decision that feeds into children.
|
||||
@@ -137,7 +145,13 @@ export async function executeInvocation(
|
||||
.join("\n\n---\n\n")
|
||||
.slice(0, 6000);
|
||||
|
||||
return buildSuccessResult(req.stage, req.task, aggregated, gitResult);
|
||||
return buildSuccessResult(
|
||||
req.stage,
|
||||
req.task,
|
||||
aggregated,
|
||||
gitResult,
|
||||
ctx.producedFiles,
|
||||
);
|
||||
} catch (err) {
|
||||
const errorReason = err instanceof Error ? err.message : String(err);
|
||||
await rails.recordEvent(managerId, "failed", { errorReason });
|
||||
@@ -265,6 +279,12 @@ async function maybeExtractFiles(
|
||||
if (blocks.length === 0) return [];
|
||||
|
||||
const saved = await saveExtractedFiles(ctx.workspaceDir, blocks);
|
||||
// Track the repo-relative path (e.g., implement/files/frontend/index.html)
|
||||
// so the deploy stage can build an accurate preview URL.
|
||||
for (const f of saved) {
|
||||
const repoRelPath = `${ctx.req.stage}/files/${f.path}`;
|
||||
ctx.producedFiles.push(repoRelPath);
|
||||
}
|
||||
return saved.map((f) => {
|
||||
const base: { path: string; lang: string; absPath?: string } = {
|
||||
path: f.path,
|
||||
@@ -389,12 +409,19 @@ function derivePreviewUrl(
|
||||
const rawBase = rawBaseMatch?.[1];
|
||||
if (!rawBase) return "";
|
||||
|
||||
// Heuristic: if there's an index.html path referenced anywhere in the
|
||||
// priorStages text, link directly to it. Otherwise link to the repo root.
|
||||
const htmlMatch = impl.text.match(/([a-zA-Z0-9_\-./]+\.html)/);
|
||||
if (htmlMatch?.[1]) {
|
||||
return `${rawBase}/files/${htmlMatch[1]}`;
|
||||
// Prefer the exact producedFiles list emitted by implement stage.
|
||||
const producedMatch = impl.text.match(/producedFiles=([^\n]+)/);
|
||||
if (producedMatch?.[1]) {
|
||||
const files = producedMatch[1]
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
const html = files.find((f) => f.toLowerCase().endsWith(".html"));
|
||||
if (html) return `${rawBase}/${html}`;
|
||||
if (files[0]) return `${rawBase}/${files[0]}`;
|
||||
}
|
||||
|
||||
// Fallback: browse view
|
||||
return rawBase.replace("/raw/branch/main", "");
|
||||
}
|
||||
|
||||
@@ -409,6 +436,7 @@ function buildSuccessResult(
|
||||
commit: string;
|
||||
filesCount: number;
|
||||
} | null,
|
||||
producedFiles: string[] = [],
|
||||
): HandoffMessage {
|
||||
const summary = outputText?.slice(0, 2000) ?? "";
|
||||
switch (stage) {
|
||||
@@ -433,6 +461,7 @@ function buildSuccessResult(
|
||||
workdir: task.workdir || "",
|
||||
selfTestReport: {
|
||||
summary,
|
||||
producedFiles,
|
||||
...(gitResult?.ok && {
|
||||
repoUrl: gitResult.repoUrl,
|
||||
rawUrlBase: gitResult.rawUrlBase,
|
||||
|
||||
@@ -204,6 +204,7 @@ function extractStageText(h: HandoffMessage): string {
|
||||
repoUrl?: string;
|
||||
rawUrlBase?: string;
|
||||
filesCount?: number;
|
||||
producedFiles?: string[];
|
||||
}
|
||||
| undefined;
|
||||
const parts: string[] = [];
|
||||
@@ -213,6 +214,9 @@ function extractStageText(h: HandoffMessage): string {
|
||||
if (typeof report?.filesCount === "number") {
|
||||
parts.push(`[git] filesCount=${report.filesCount}`);
|
||||
}
|
||||
if (report?.producedFiles && report.producedFiles.length > 0) {
|
||||
parts.push(`[git] producedFiles=${report.producedFiles.join(",")}`);
|
||||
}
|
||||
return parts.join("\n");
|
||||
}
|
||||
case "review": {
|
||||
|
||||
Reference in New Issue
Block a user