@pourkit/cli 0.0.0-next-20260614002607 → 0.0.0-next-20260614074434
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +993 -631
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +553 -413
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/dist/schema/pourkit.schema.hash +1 -0
- package/dist/schema/pourkit.schema.json +356 -0
- package/package.json +6 -3
package/dist/e2e/run-live-e2e.js
CHANGED
|
@@ -58,20 +58,20 @@ function createLogger(name, filePath) {
|
|
|
58
58
|
);
|
|
59
59
|
},
|
|
60
60
|
async close() {
|
|
61
|
-
await new Promise((
|
|
61
|
+
await new Promise((resolve4) => {
|
|
62
62
|
if (!fileStream) {
|
|
63
|
-
|
|
63
|
+
resolve4();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
const timer = setTimeout(() => {
|
|
67
67
|
if (!fileStream.destroyed) {
|
|
68
68
|
fileStream.destroy();
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
resolve4();
|
|
71
71
|
}, 2e3);
|
|
72
72
|
fileStream.end(() => {
|
|
73
73
|
clearTimeout(timer);
|
|
74
|
-
|
|
74
|
+
resolve4();
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
}
|
|
@@ -265,7 +265,7 @@ async function execJson(command, args, options = {}) {
|
|
|
265
265
|
return JSON.parse(result.stdout);
|
|
266
266
|
}
|
|
267
267
|
function sleep(ms) {
|
|
268
|
-
return new Promise((
|
|
268
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
269
269
|
}
|
|
270
270
|
async function execCaptureWithRetry(command, args, options = {}) {
|
|
271
271
|
const retries = options.retries ?? 3;
|
|
@@ -330,184 +330,45 @@ var init_common = __esm({
|
|
|
330
330
|
|
|
331
331
|
// e2e/run-live-e2e.ts
|
|
332
332
|
import path8 from "path";
|
|
333
|
-
import { existsSync as existsSync14 } from "fs";
|
|
334
333
|
import { pathToFileURL } from "url";
|
|
335
334
|
import { mkdir as mkdir5, readFile as readFile4, readdir as readdir2, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
336
335
|
|
|
337
336
|
// commands/issue-run.ts
|
|
338
|
-
import { existsSync as existsSync12, readFileSync as
|
|
339
|
-
import { isAbsolute as
|
|
337
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
338
|
+
import { isAbsolute as isAbsolute4, join as join14 } from "path";
|
|
340
339
|
|
|
341
340
|
// shared/config.ts
|
|
342
|
-
import {
|
|
343
|
-
import {
|
|
344
|
-
|
|
341
|
+
import { readFileSync } from "fs";
|
|
342
|
+
import { fileURLToPath } from "url";
|
|
343
|
+
import { dirname, isAbsolute, join, normalize, sep, resolve } from "path";
|
|
344
|
+
import Ajv from "ajv";
|
|
345
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
346
|
+
var __dirname = dirname(__filename);
|
|
347
|
+
var SCHEMA_PATH = resolve(__dirname, "../schema/pourkit.schema.json");
|
|
348
|
+
var _schema = null;
|
|
349
|
+
var _validate = null;
|
|
350
|
+
var _ajvErrors = null;
|
|
351
|
+
function getValidator() {
|
|
352
|
+
if (!_validate) {
|
|
353
|
+
const schema = _schema ?? JSON.parse(readFileSync(SCHEMA_PATH, "utf-8"));
|
|
354
|
+
_schema = schema;
|
|
355
|
+
const ajv = new Ajv({ strict: true });
|
|
356
|
+
ajv.addKeyword("x-pourkit-schema-version");
|
|
357
|
+
const validate = ajv.compile(schema);
|
|
358
|
+
_validate = (data) => {
|
|
359
|
+
_ajvErrors = null;
|
|
360
|
+
const valid2 = validate(data);
|
|
361
|
+
if (!valid2) _ajvErrors = validate.errors;
|
|
362
|
+
return valid2;
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return _validate;
|
|
366
|
+
}
|
|
345
367
|
var DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES = 3;
|
|
346
|
-
var
|
|
347
|
-
missingOrEmpty: z.number().int().nonnegative().default(DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES)
|
|
348
|
-
}).strict();
|
|
368
|
+
var DEFAULT_BRANCH_TEMPLATE = "pourkit/{{issue.number}}/{{issue.slug}}";
|
|
349
369
|
function resolveMissingOrEmptyOutputRetries(config) {
|
|
350
370
|
return config?.outputRetries?.missingOrEmpty ?? DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES;
|
|
351
371
|
}
|
|
352
|
-
var StageAgentConfigSchema = z.object({
|
|
353
|
-
agent: NonEmptyString,
|
|
354
|
-
model: NonEmptyString,
|
|
355
|
-
variant: NonEmptyString.optional(),
|
|
356
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
357
|
-
promptTemplate: NonEmptyString,
|
|
358
|
-
outputRetries: OutputRetriesConfigSchema.optional()
|
|
359
|
-
}).strict();
|
|
360
|
-
var ReviewerConfigSchema = z.object({
|
|
361
|
-
agent: NonEmptyString,
|
|
362
|
-
model: NonEmptyString,
|
|
363
|
-
variant: NonEmptyString.optional(),
|
|
364
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
365
|
-
promptTemplate: NonEmptyString,
|
|
366
|
-
outputRetries: OutputRetriesConfigSchema.optional(),
|
|
367
|
-
criteria: z.array(NonEmptyString),
|
|
368
|
-
includeReviewHistory: z.boolean().optional(),
|
|
369
|
-
passWithNotesRefactorAttempts: z.number().int().nonnegative().optional()
|
|
370
|
-
}).strict();
|
|
371
|
-
var VerificationCommandSchema = z.object({
|
|
372
|
-
command: z.string().nullable().optional(),
|
|
373
|
-
label: z.string().optional()
|
|
374
|
-
}).strict().refine((d) => d.command && d.command.trim() !== "", {
|
|
375
|
-
message: "must have a non-empty command"
|
|
376
|
-
}).transform((d) => ({
|
|
377
|
-
command: d.command,
|
|
378
|
-
label: d.label && d.label.trim() !== "" ? d.label : void 0
|
|
379
|
-
}));
|
|
380
|
-
var PrdRunModeSchema = z.enum(["github", "local"]);
|
|
381
|
-
var QueueConfigSchema = z.object({
|
|
382
|
-
loop: z.boolean().optional()
|
|
383
|
-
}).strict();
|
|
384
|
-
var FailureResolutionConfigSchema = z.object({
|
|
385
|
-
agent: NonEmptyString,
|
|
386
|
-
model: NonEmptyString,
|
|
387
|
-
variant: NonEmptyString.optional(),
|
|
388
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
389
|
-
promptTemplate: NonEmptyString,
|
|
390
|
-
outputRetries: OutputRetriesConfigSchema.optional(),
|
|
391
|
-
maxAttemptsPerFailure: z.number().int().positive(),
|
|
392
|
-
failureLimits: z.record(z.string(), z.number().int().positive()).optional()
|
|
393
|
-
}).strict();
|
|
394
|
-
var ReviewRefactorLoopStrategySchema = z.object({
|
|
395
|
-
type: z.literal("review-refactor-loop"),
|
|
396
|
-
implement: z.object({
|
|
397
|
-
builder: StageAgentConfigSchema
|
|
398
|
-
}).strict(),
|
|
399
|
-
conflictResolution: z.object({
|
|
400
|
-
agent: NonEmptyString,
|
|
401
|
-
model: NonEmptyString,
|
|
402
|
-
variant: NonEmptyString.optional(),
|
|
403
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
404
|
-
promptTemplate: NonEmptyString,
|
|
405
|
-
maxAttempts: z.number().int().positive()
|
|
406
|
-
}).strict().optional(),
|
|
407
|
-
failureResolution: FailureResolutionConfigSchema,
|
|
408
|
-
review: z.object({
|
|
409
|
-
reviewer: ReviewerConfigSchema,
|
|
410
|
-
refactor: StageAgentConfigSchema,
|
|
411
|
-
maxIterations: z.number().int().positive(),
|
|
412
|
-
passWithNotesRefactorAttempts: z.number().int().nonnegative().default(2)
|
|
413
|
-
}).strict(),
|
|
414
|
-
verify: z.object({
|
|
415
|
-
commands: z.preprocess(
|
|
416
|
-
(v) => Array.isArray(v) ? v : [],
|
|
417
|
-
z.array(VerificationCommandSchema).refine((arr) => arr.length > 0, {
|
|
418
|
-
message: "must contain at least one command"
|
|
419
|
-
})
|
|
420
|
-
)
|
|
421
|
-
}).strict().optional(),
|
|
422
|
-
issueFinalReview: StageAgentConfigSchema.extend({
|
|
423
|
-
maxAttempts: z.number().int().positive()
|
|
424
|
-
}),
|
|
425
|
-
finalize: z.object({
|
|
426
|
-
prDescriptionAgent: StageAgentConfigSchema,
|
|
427
|
-
maxAttempts: z.number().int().positive()
|
|
428
|
-
}).strict(),
|
|
429
|
-
prdRun: z.object({
|
|
430
|
-
mode: PrdRunModeSchema.optional(),
|
|
431
|
-
// Uses promptTemplate (canonical StageAgentConfig field), not prompt as Issue contract may suggest
|
|
432
|
-
finalReview: StageAgentConfigSchema
|
|
433
|
-
}).strict().optional()
|
|
434
|
-
}).strict();
|
|
435
|
-
var TargetSerenaConfigSchema = z.object({
|
|
436
|
-
enabled: z.boolean().optional(),
|
|
437
|
-
required: z.boolean().optional()
|
|
438
|
-
}).strict();
|
|
439
|
-
var TargetSchema = z.object({
|
|
440
|
-
name: NonEmptyString,
|
|
441
|
-
baseBranch: z.preprocess(
|
|
442
|
-
(v) => typeof v === "string" && v.length > 0 ? v : void 0,
|
|
443
|
-
NonEmptyString.default("main")
|
|
444
|
-
),
|
|
445
|
-
branchTemplate: z.string().default("pourkit/{{issue.number}}/{{issue.slug}}"),
|
|
446
|
-
setupCommands: z.preprocess(
|
|
447
|
-
(v) => Array.isArray(v) ? v : void 0,
|
|
448
|
-
z.array(VerificationCommandSchema).default([])
|
|
449
|
-
),
|
|
450
|
-
autoMerge: z.preprocess(
|
|
451
|
-
(v) => typeof v === "boolean" ? v : void 0,
|
|
452
|
-
z.boolean().default(true)
|
|
453
|
-
),
|
|
454
|
-
queue: QueueConfigSchema.optional(),
|
|
455
|
-
serena: TargetSerenaConfigSchema.optional(),
|
|
456
|
-
strategy: ReviewRefactorLoopStrategySchema
|
|
457
|
-
}).strict();
|
|
458
|
-
var LabelsSchema = z.object({
|
|
459
|
-
readyForAgent: NonEmptyString,
|
|
460
|
-
agentInProgress: NonEmptyString,
|
|
461
|
-
blocked: NonEmptyString,
|
|
462
|
-
prOpenAwaitingMerge: NonEmptyString,
|
|
463
|
-
readyForHuman: NonEmptyString,
|
|
464
|
-
needsTriage: NonEmptyString.optional().default("needs-triage")
|
|
465
|
-
}).strict();
|
|
466
|
-
var SandboxMountSchema = z.object({
|
|
467
|
-
hostPath: NonEmptyString,
|
|
468
|
-
sandboxPath: NonEmptyString,
|
|
469
|
-
readonly: z.boolean().default(false)
|
|
470
|
-
}).strict();
|
|
471
|
-
var SandboxSchema = z.object({
|
|
472
|
-
provider: NonEmptyString,
|
|
473
|
-
copyToWorktree: z.array(NonEmptyString).optional(),
|
|
474
|
-
mounts: z.array(SandboxMountSchema).optional(),
|
|
475
|
-
env: z.record(z.string()).optional(),
|
|
476
|
-
idleTimeoutSeconds: z.preprocess((v) => {
|
|
477
|
-
if (v === void 0) return void 0;
|
|
478
|
-
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
|
|
479
|
-
return v;
|
|
480
|
-
}, z.number().int().positive().optional())
|
|
481
|
-
}).strict();
|
|
482
|
-
var ChecksSchema = z.object({
|
|
483
|
-
requiredLabels: z.array(NonEmptyString),
|
|
484
|
-
allowedAuthors: z.array(NonEmptyString),
|
|
485
|
-
checksFoundTimeoutSeconds: z.number().int().positive().optional(),
|
|
486
|
-
checksCompletionTimeoutSeconds: z.number().int().positive().optional(),
|
|
487
|
-
pollIntervalSeconds: z.number().int().positive().optional(),
|
|
488
|
-
issueListLimit: z.number().int().positive().optional()
|
|
489
|
-
}).strict();
|
|
490
|
-
var CleanupConfigSchema = z.object({
|
|
491
|
-
enabled: z.boolean().default(true),
|
|
492
|
-
worktreeRetentionDays: z.number().int().positive().default(14),
|
|
493
|
-
logRetentionDays: z.number().int().positive().default(30)
|
|
494
|
-
}).strict();
|
|
495
|
-
var SerenaConfigSchema = z.object({
|
|
496
|
-
enabled: z.boolean().default(false),
|
|
497
|
-
required: z.boolean().default(false),
|
|
498
|
-
mcpUrl: NonEmptyString.default("http://localhost:9121/mcp"),
|
|
499
|
-
sandboxMcpUrl: NonEmptyString.default("http://localhost:9121/mcp"),
|
|
500
|
-
dataDir: z.string().default(".pourkit/serena/"),
|
|
501
|
-
autoStart: z.boolean().default(false)
|
|
502
|
-
}).strict();
|
|
503
|
-
var PourkitConfigSchema = z.object({
|
|
504
|
-
targets: z.array(TargetSchema).min(1),
|
|
505
|
-
labels: LabelsSchema,
|
|
506
|
-
sandbox: SandboxSchema,
|
|
507
|
-
checks: ChecksSchema,
|
|
508
|
-
cleanup: CleanupConfigSchema.optional(),
|
|
509
|
-
serena: SerenaConfigSchema.default({})
|
|
510
|
-
}).strict();
|
|
511
372
|
var removedFieldReplacements = {
|
|
512
373
|
"config.implementor": "targets[].strategy.implement.builder",
|
|
513
374
|
"config.reviewer": "targets[].strategy.review.reviewer",
|
|
@@ -571,11 +432,12 @@ function checkRemovedFields(raw) {
|
|
|
571
432
|
}
|
|
572
433
|
}
|
|
573
434
|
}
|
|
574
|
-
function
|
|
575
|
-
if (
|
|
435
|
+
function formatAjvPath(instancePath) {
|
|
436
|
+
if (!instancePath || instancePath === "/") return "";
|
|
437
|
+
const parts = instancePath.split("/").slice(1);
|
|
576
438
|
let result = "";
|
|
577
|
-
for (const segment of
|
|
578
|
-
if (
|
|
439
|
+
for (const segment of parts) {
|
|
440
|
+
if (/^\d+$/.test(segment)) {
|
|
579
441
|
result += `[${segment}]`;
|
|
580
442
|
} else {
|
|
581
443
|
result += result ? `.${segment}` : segment;
|
|
@@ -583,51 +445,172 @@ function formatZodPath(path9) {
|
|
|
583
445
|
}
|
|
584
446
|
return result;
|
|
585
447
|
}
|
|
586
|
-
function
|
|
587
|
-
const
|
|
588
|
-
const path9 =
|
|
589
|
-
if (
|
|
590
|
-
|
|
448
|
+
function formatFirstAjvError(errors) {
|
|
449
|
+
const error = errors[0];
|
|
450
|
+
const path9 = formatAjvPath(error.instancePath);
|
|
451
|
+
if (error.keyword === "required") {
|
|
452
|
+
const missingParam = error.params.missingProperty;
|
|
453
|
+
if (missingParam === "targets") {
|
|
454
|
+
return "Config must have at least one target";
|
|
455
|
+
}
|
|
456
|
+
if (path9 === "" && missingParam === "targets") {
|
|
457
|
+
return "Config must have at least one target";
|
|
458
|
+
}
|
|
459
|
+
if (path9) {
|
|
460
|
+
return `${path9} must have required property '${missingParam}'`;
|
|
461
|
+
}
|
|
462
|
+
return `${missingParam} must be an object`;
|
|
591
463
|
}
|
|
592
|
-
if (
|
|
593
|
-
|
|
464
|
+
if (error.keyword === "additionalProperties") {
|
|
465
|
+
const additionalProp = error.params.additionalProperty;
|
|
466
|
+
const keyPath = path9 ? `${path9}.${additionalProp}` : additionalProp;
|
|
467
|
+
return `${keyPath} is not supported`;
|
|
594
468
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
469
|
+
if (error.keyword === "minLength") {
|
|
470
|
+
if (path9) {
|
|
471
|
+
return `${path9} must be a non-empty string`;
|
|
472
|
+
}
|
|
473
|
+
return "Config must be a non-empty string";
|
|
474
|
+
}
|
|
475
|
+
if (error.keyword === "const") {
|
|
476
|
+
const allowedValue = error.params.allowedValue;
|
|
477
|
+
return `${path9 || "Config"} must be ${JSON.stringify(allowedValue)}`;
|
|
478
|
+
}
|
|
479
|
+
if (error.keyword === "enum") {
|
|
480
|
+
const allowedValues = error.params.allowedValues;
|
|
481
|
+
return `${path9} must be one of: ${allowedValues.map((v) => JSON.stringify(v)).join(", ")}`;
|
|
482
|
+
}
|
|
483
|
+
if (error.keyword === "minimum" || error.keyword === "exclusiveMinimum") {
|
|
484
|
+
const limit = error.params.limit;
|
|
485
|
+
if (limit === 1) {
|
|
486
|
+
return `${path9} must be a positive number`;
|
|
487
|
+
}
|
|
488
|
+
return `${path9} must be at least ${limit}`;
|
|
489
|
+
}
|
|
490
|
+
if (error.keyword === "type") {
|
|
491
|
+
const expected = error.params.type;
|
|
492
|
+
if (path9 === "") {
|
|
493
|
+
return `Config must be an object`;
|
|
494
|
+
}
|
|
495
|
+
if (expected === "object") {
|
|
496
|
+
return `${path9} must be an object`;
|
|
497
|
+
}
|
|
498
|
+
if (expected === "integer") {
|
|
499
|
+
return `${path9} must be an integer`;
|
|
500
|
+
}
|
|
501
|
+
if (expected === "string") {
|
|
502
|
+
return `${path9} must be a string`;
|
|
503
|
+
}
|
|
504
|
+
if (expected === "number") {
|
|
505
|
+
return `${path9} must be a number`;
|
|
506
|
+
}
|
|
507
|
+
return `${path9} must be ${expected === "integer" ? "an integer" : `a ${expected}`}`;
|
|
508
|
+
}
|
|
509
|
+
if (error.keyword === "minItems") {
|
|
510
|
+
return `${path9} must contain at least one item`;
|
|
511
|
+
}
|
|
512
|
+
if (error.keyword === "pattern") {
|
|
513
|
+
return `${path9} has an invalid format`;
|
|
514
|
+
}
|
|
515
|
+
return `${path9 || "Config"} ${error.message || "is invalid"}`;
|
|
516
|
+
}
|
|
517
|
+
function applyOutputRetriesDefaults(retries) {
|
|
518
|
+
if (retries === void 0) return void 0;
|
|
519
|
+
return {
|
|
520
|
+
missingOrEmpty: retries.missingOrEmpty ?? DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function applyStageAgentDefaults(agent) {
|
|
524
|
+
return {
|
|
525
|
+
...agent,
|
|
526
|
+
outputRetries: applyOutputRetriesDefaults(agent.outputRetries)
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function assertRepoRelativePath(value, location) {
|
|
530
|
+
const normalized = normalize(value);
|
|
531
|
+
if (isAbsolute(value) || isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${sep}`)) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
`${location} must stay within the repository and be repo-relative; got "${value}"`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function assertBaseBranch(value, location) {
|
|
538
|
+
if (value.includes("/")) {
|
|
539
|
+
throw new Error(
|
|
540
|
+
`${location} must be a local branch name, not a remote-qualified, tag, ref, or path-like name; got "${value}"`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if (/^[0-9a-f]{7,40}$/i.test(value)) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
`${location} must be a branch name, not a commit SHA; got "${value}"`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function assertStageAgentPath(agent, location) {
|
|
550
|
+
if (!agent) return;
|
|
551
|
+
const promptTemplate = agent.promptTemplate;
|
|
552
|
+
if (typeof promptTemplate === "string") {
|
|
553
|
+
assertRepoRelativePath(promptTemplate, `${location}.promptTemplate`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function validateConfigSemantics(data) {
|
|
557
|
+
const sandbox = data.sandbox;
|
|
558
|
+
const copyToWorktree = sandbox?.copyToWorktree;
|
|
559
|
+
copyToWorktree?.forEach((entry, index) => {
|
|
560
|
+
assertRepoRelativePath(entry, `sandbox.copyToWorktree[${index}]`);
|
|
561
|
+
});
|
|
562
|
+
const targetNames = /* @__PURE__ */ new Set();
|
|
563
|
+
data.targets.forEach((target, targetIndex) => {
|
|
564
|
+
const name = target.name;
|
|
565
|
+
if (targetNames.has(name)) {
|
|
566
|
+
throw new Error(
|
|
567
|
+
`Duplicate target name "${name}"; target names must be unique`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
targetNames.add(name);
|
|
571
|
+
assertBaseBranch(
|
|
572
|
+
target.baseBranch,
|
|
573
|
+
`targets[${targetIndex}].baseBranch`
|
|
574
|
+
);
|
|
575
|
+
const strategy = target.strategy;
|
|
576
|
+
const implement = strategy.implement;
|
|
577
|
+
const review = strategy.review;
|
|
578
|
+
const finalize = strategy.finalize;
|
|
579
|
+
assertStageAgentPath(
|
|
580
|
+
implement.builder,
|
|
581
|
+
`targets[${targetIndex}].strategy.implement.builder`
|
|
582
|
+
);
|
|
583
|
+
assertStageAgentPath(
|
|
584
|
+
strategy.conflictResolution,
|
|
585
|
+
`targets[${targetIndex}].strategy.conflictResolution`
|
|
586
|
+
);
|
|
587
|
+
assertStageAgentPath(
|
|
588
|
+
strategy.failureResolution,
|
|
589
|
+
`targets[${targetIndex}].strategy.failureResolution`
|
|
590
|
+
);
|
|
591
|
+
assertStageAgentPath(
|
|
592
|
+
review.reviewer,
|
|
593
|
+
`targets[${targetIndex}].strategy.review.reviewer`
|
|
594
|
+
);
|
|
595
|
+
assertStageAgentPath(
|
|
596
|
+
review.refactor,
|
|
597
|
+
`targets[${targetIndex}].strategy.review.refactor`
|
|
598
|
+
);
|
|
599
|
+
assertStageAgentPath(
|
|
600
|
+
strategy.issueFinalReview,
|
|
601
|
+
`targets[${targetIndex}].strategy.issueFinalReview`
|
|
602
|
+
);
|
|
603
|
+
assertStageAgentPath(
|
|
604
|
+
finalize.prDescriptionAgent,
|
|
605
|
+
`targets[${targetIndex}].strategy.finalize.prDescriptionAgent`
|
|
606
|
+
);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
function assertKnownKeys(value, path9, knownKeys) {
|
|
610
|
+
for (const key of Object.keys(value)) {
|
|
611
|
+
if (!knownKeys.includes(key)) {
|
|
612
|
+
throw new Error(`${path9}.${key} is not supported`);
|
|
610
613
|
}
|
|
611
|
-
case z.ZodIssueCode.too_small:
|
|
612
|
-
if (issue.type === "string" && issue.minimum === 1) {
|
|
613
|
-
return `${path9} must be a non-empty string`;
|
|
614
|
-
}
|
|
615
|
-
if (issue.type === "array" && issue.minimum === 1) {
|
|
616
|
-
return `${path9} must not be empty`;
|
|
617
|
-
}
|
|
618
|
-
if (issue.type === "number") {
|
|
619
|
-
return `${path9} must be a positive number`;
|
|
620
|
-
}
|
|
621
|
-
return issue.message;
|
|
622
|
-
case z.ZodIssueCode.invalid_literal:
|
|
623
|
-
return `${path9} must be ${issue.expected}`;
|
|
624
|
-
case z.ZodIssueCode.unrecognized_keys:
|
|
625
|
-
const keyPath = path9 ? `${path9}.${issue.keys[0]}` : issue.keys[0];
|
|
626
|
-
return `${keyPath} is not supported`;
|
|
627
|
-
case z.ZodIssueCode.custom:
|
|
628
|
-
return path9 ? `${path9} ${issue.message}` : issue.message;
|
|
629
|
-
default:
|
|
630
|
-
return issue.message;
|
|
631
614
|
}
|
|
632
615
|
}
|
|
633
616
|
function parseConfig(raw) {
|
|
@@ -649,6 +632,7 @@ function parseConfig(raw) {
|
|
|
649
632
|
"name",
|
|
650
633
|
"baseBranch",
|
|
651
634
|
"branchTemplate",
|
|
635
|
+
"prdRun",
|
|
652
636
|
"setupCommands",
|
|
653
637
|
"autoMerge",
|
|
654
638
|
"queue",
|
|
@@ -664,9 +648,20 @@ function parseConfig(raw) {
|
|
|
664
648
|
`targets[${i}].strategy.conflictResolution has been removed; use targets[${i}].strategy.failureResolution`
|
|
665
649
|
);
|
|
666
650
|
}
|
|
667
|
-
if (strategy && typeof strategy === "object" && strategy.prdRun && typeof strategy.prdRun === "object"
|
|
651
|
+
if (strategy && typeof strategy === "object" && strategy.prdRun && typeof strategy.prdRun === "object") {
|
|
652
|
+
const prdRun = strategy.prdRun;
|
|
653
|
+
if ("reconciliation" in prdRun) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`targets[${i}].strategy.prdRun.reconciliation has been removed; PRD Run no longer invokes Architect reconciliation.`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
if ("finalReview" in prdRun) {
|
|
659
|
+
throw new Error(
|
|
660
|
+
`targets[${i}].strategy.prdRun.finalReview has been removed; PRD-wide Final Review is no longer part of the PRD Run lifecycle. Use targets[${i}].strategy.issueFinalReview instead.`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
668
663
|
throw new Error(
|
|
669
|
-
`targets[${i}].strategy.prdRun
|
|
664
|
+
`targets[${i}].strategy.prdRun is not supported in the new config; use targets[${i}].prdRun instead.`
|
|
670
665
|
);
|
|
671
666
|
}
|
|
672
667
|
}
|
|
@@ -676,67 +671,126 @@ function parseConfig(raw) {
|
|
|
676
671
|
"copyToWorktree",
|
|
677
672
|
"mounts",
|
|
678
673
|
"env",
|
|
679
|
-
"idleTimeoutSeconds"
|
|
674
|
+
"idleTimeoutSeconds",
|
|
675
|
+
"forceRebuild"
|
|
680
676
|
]);
|
|
681
677
|
}
|
|
682
|
-
const
|
|
683
|
-
if (!
|
|
684
|
-
throw new Error(
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const verifyCommands = t.strategy.verify?.commands?.map((cmd, i) => ({
|
|
693
|
-
command: cmd.command,
|
|
694
|
-
label: cmd.label ?? `check-${i}`
|
|
695
|
-
}));
|
|
696
|
-
return {
|
|
697
|
-
name: t.name,
|
|
698
|
-
baseBranch: t.baseBranch,
|
|
699
|
-
branchTemplate: t.branchTemplate,
|
|
700
|
-
setupCommands,
|
|
701
|
-
autoMerge: t.autoMerge,
|
|
702
|
-
queue: t.queue,
|
|
703
|
-
serena: t.serena,
|
|
704
|
-
strategy: {
|
|
705
|
-
type: "review-refactor-loop",
|
|
706
|
-
implement: { builder: t.strategy.implement.builder },
|
|
707
|
-
failureResolution: {
|
|
708
|
-
agent: t.strategy.failureResolution.agent,
|
|
709
|
-
model: t.strategy.failureResolution.model,
|
|
710
|
-
promptTemplate: t.strategy.failureResolution.promptTemplate,
|
|
711
|
-
outputRetries: t.strategy.failureResolution.outputRetries,
|
|
712
|
-
maxAttemptsPerFailure: t.strategy.failureResolution.maxAttemptsPerFailure,
|
|
713
|
-
failureLimits: t.strategy.failureResolution.failureLimits
|
|
714
|
-
},
|
|
715
|
-
review: {
|
|
716
|
-
reviewer: t.strategy.review.reviewer,
|
|
717
|
-
refactor: t.strategy.review.refactor,
|
|
718
|
-
maxIterations: t.strategy.review.maxIterations,
|
|
719
|
-
passWithNotesRefactorAttempts: t.strategy.review.passWithNotesRefactorAttempts
|
|
720
|
-
},
|
|
721
|
-
...t.strategy.verify ? { verify: { commands: verifyCommands } } : {},
|
|
722
|
-
issueFinalReview: t.strategy.issueFinalReview,
|
|
723
|
-
finalize: {
|
|
724
|
-
prDescriptionAgent: t.strategy.finalize.prDescriptionAgent,
|
|
725
|
-
maxAttempts: t.strategy.finalize.maxAttempts
|
|
726
|
-
},
|
|
727
|
-
...t.strategy.prdRun ? {
|
|
728
|
-
prdRun: {
|
|
729
|
-
...t.strategy.prdRun.mode ? { mode: t.strategy.prdRun.mode } : {},
|
|
730
|
-
finalReview: t.strategy.prdRun.finalReview
|
|
678
|
+
const validate = getValidator();
|
|
679
|
+
if (!validate(raw)) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
formatFirstAjvError(
|
|
682
|
+
_ajvErrors ?? [
|
|
683
|
+
{
|
|
684
|
+
instancePath: "",
|
|
685
|
+
message: "validation failed",
|
|
686
|
+
keyword: "error",
|
|
687
|
+
params: {}
|
|
731
688
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
689
|
+
]
|
|
690
|
+
)
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
const data = config;
|
|
694
|
+
validateConfigSemantics(data);
|
|
695
|
+
const targets = data.targets.map(
|
|
696
|
+
(t) => {
|
|
697
|
+
const input = t;
|
|
698
|
+
const strategy = input.strategy;
|
|
699
|
+
const implement = strategy.implement;
|
|
700
|
+
const failureResolution = strategy.failureResolution;
|
|
701
|
+
const review = strategy.review;
|
|
702
|
+
const reviewReviewer = review.reviewer;
|
|
703
|
+
const reviewRefactor = review.refactor;
|
|
704
|
+
const finalize = strategy.finalize;
|
|
705
|
+
const issueFinalReview = strategy.issueFinalReview;
|
|
706
|
+
const setupCommands = input.setupCommands?.map((cmd, i) => ({
|
|
707
|
+
command: cmd.command,
|
|
708
|
+
label: cmd.label ?? `check-${i}`
|
|
709
|
+
}));
|
|
710
|
+
const verifyCommands = strategy.verify?.commands ? strategy.verify.commands : void 0;
|
|
711
|
+
const verifyLabeled = verifyCommands?.map((cmd, i) => ({
|
|
712
|
+
command: cmd.command,
|
|
713
|
+
label: cmd.label ?? `check-${i}`
|
|
714
|
+
}));
|
|
715
|
+
return {
|
|
716
|
+
name: input.name,
|
|
717
|
+
baseBranch: input.baseBranch,
|
|
718
|
+
branchTemplate: input.branchTemplate ?? DEFAULT_BRANCH_TEMPLATE,
|
|
719
|
+
prdRun: input.prdRun,
|
|
720
|
+
setupCommands,
|
|
721
|
+
autoMerge: input.autoMerge !== void 0 ? input.autoMerge : true,
|
|
722
|
+
queue: input.queue,
|
|
723
|
+
serena: input.serena,
|
|
724
|
+
strategy: {
|
|
725
|
+
type: "review-refactor-loop",
|
|
726
|
+
implement: {
|
|
727
|
+
builder: applyStageAgentDefaults(
|
|
728
|
+
implement.builder
|
|
729
|
+
)
|
|
730
|
+
},
|
|
731
|
+
failureResolution: {
|
|
732
|
+
agent: failureResolution.agent,
|
|
733
|
+
model: failureResolution.model,
|
|
734
|
+
variant: failureResolution.variant,
|
|
735
|
+
env: failureResolution.env,
|
|
736
|
+
promptTemplate: failureResolution.promptTemplate,
|
|
737
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
738
|
+
failureResolution.outputRetries
|
|
739
|
+
),
|
|
740
|
+
maxAttemptsPerFailure: failureResolution.maxAttemptsPerFailure,
|
|
741
|
+
failureLimits: failureResolution.failureLimits
|
|
742
|
+
},
|
|
743
|
+
review: {
|
|
744
|
+
reviewer: {
|
|
745
|
+
agent: reviewReviewer.agent,
|
|
746
|
+
model: reviewReviewer.model,
|
|
747
|
+
variant: reviewReviewer.variant,
|
|
748
|
+
env: reviewReviewer.env,
|
|
749
|
+
promptTemplate: reviewReviewer.promptTemplate,
|
|
750
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
751
|
+
reviewReviewer.outputRetries
|
|
752
|
+
),
|
|
753
|
+
criteria: reviewReviewer.criteria,
|
|
754
|
+
includeReviewHistory: reviewReviewer.includeReviewHistory,
|
|
755
|
+
passWithNotesRefactorAttempts: reviewReviewer.passWithNotesRefactorAttempts
|
|
756
|
+
},
|
|
757
|
+
refactor: applyStageAgentDefaults(
|
|
758
|
+
reviewRefactor
|
|
759
|
+
),
|
|
760
|
+
maxIterations: review.maxIterations,
|
|
761
|
+
passWithNotesRefactorAttempts: review.passWithNotesRefactorAttempts ?? 2
|
|
762
|
+
},
|
|
763
|
+
...verifyLabeled ? { verify: { commands: verifyLabeled } } : {},
|
|
764
|
+
issueFinalReview: {
|
|
765
|
+
...issueFinalReview,
|
|
766
|
+
maxAttempts: issueFinalReview.maxAttempts,
|
|
767
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
768
|
+
issueFinalReview.outputRetries
|
|
769
|
+
)
|
|
770
|
+
},
|
|
771
|
+
finalize: {
|
|
772
|
+
prDescriptionAgent: applyStageAgentDefaults(
|
|
773
|
+
finalize.prDescriptionAgent
|
|
774
|
+
),
|
|
775
|
+
maxAttempts: finalize.maxAttempts
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
const serenaRaw = data.serena;
|
|
782
|
+
const serenaDefaults = {
|
|
783
|
+
mcpUrl: serenaRaw?.mcpUrl ?? "http://localhost:9121/mcp",
|
|
784
|
+
sandboxMcpUrl: serenaRaw?.sandboxMcpUrl ?? "http://localhost:9121/mcp",
|
|
785
|
+
dataDir: serenaRaw?.dataDir ?? ".pourkit/serena/"
|
|
786
|
+
};
|
|
736
787
|
const serena = {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
788
|
+
enabled: serenaRaw?.enabled ?? false,
|
|
789
|
+
required: serenaRaw?.required ?? false,
|
|
790
|
+
mcpUrl: process.env.POURKIT_SERENA_MCP_URL ?? serenaDefaults.mcpUrl,
|
|
791
|
+
sandboxMcpUrl: process.env.POURKIT_SERENA_SANDBOX_MCP_URL ?? serenaDefaults.sandboxMcpUrl,
|
|
792
|
+
dataDir: serenaDefaults.dataDir,
|
|
793
|
+
autoStart: serenaRaw?.autoStart ?? false
|
|
740
794
|
};
|
|
741
795
|
if (serena.mcpUrl.trim() === "") {
|
|
742
796
|
throw new Error("POURKIT_SERENA_MCP_URL must be a non-empty string");
|
|
@@ -746,84 +800,100 @@ function parseConfig(raw) {
|
|
|
746
800
|
"POURKIT_SERENA_SANDBOX_MCP_URL must be a non-empty string"
|
|
747
801
|
);
|
|
748
802
|
}
|
|
803
|
+
const checksRaw = data.checks;
|
|
804
|
+
const labelsRaw = data.labels;
|
|
805
|
+
const cleanupRaw = data.cleanup;
|
|
806
|
+
const sandboxRaw = data.sandbox;
|
|
749
807
|
return {
|
|
750
808
|
targets,
|
|
751
|
-
labels:
|
|
809
|
+
labels: {
|
|
810
|
+
readyForAgent: labelsRaw?.readyForAgent ?? "ready-for-agent",
|
|
811
|
+
agentInProgress: labelsRaw?.agentInProgress ?? "agent-in-progress",
|
|
812
|
+
blocked: labelsRaw?.blocked ?? "blocked",
|
|
813
|
+
prOpenAwaitingMerge: labelsRaw?.prOpenAwaitingMerge ?? "pr-open-awaiting-merge",
|
|
814
|
+
readyForHuman: labelsRaw?.readyForHuman ?? "ready-for-human",
|
|
815
|
+
needsTriage: labelsRaw?.needsTriage ?? "needs-triage"
|
|
816
|
+
},
|
|
752
817
|
sandbox: {
|
|
753
|
-
provider:
|
|
754
|
-
copyToWorktree:
|
|
755
|
-
mounts:
|
|
756
|
-
|
|
757
|
-
|
|
818
|
+
provider: sandboxRaw?.provider ?? "docker",
|
|
819
|
+
copyToWorktree: sandboxRaw?.copyToWorktree,
|
|
820
|
+
mounts: sandboxRaw?.mounts ? sandboxRaw.mounts.map((m) => ({
|
|
821
|
+
hostPath: m.hostPath,
|
|
822
|
+
sandboxPath: m.sandboxPath,
|
|
823
|
+
readonly: m.readonly ?? false
|
|
824
|
+
})) : void 0,
|
|
825
|
+
env: sandboxRaw?.env,
|
|
826
|
+
idleTimeoutSeconds: sandboxRaw?.idleTimeoutSeconds,
|
|
827
|
+
forceRebuild: sandboxRaw?.forceRebuild
|
|
758
828
|
},
|
|
759
829
|
checks: {
|
|
760
|
-
requiredLabels:
|
|
761
|
-
allowedAuthors:
|
|
762
|
-
checksFoundTimeoutSeconds:
|
|
763
|
-
checksCompletionTimeoutSeconds:
|
|
764
|
-
pollIntervalSeconds:
|
|
765
|
-
issueListLimit:
|
|
830
|
+
requiredLabels: checksRaw?.requiredLabels ?? [],
|
|
831
|
+
allowedAuthors: checksRaw?.allowedAuthors ?? [],
|
|
832
|
+
checksFoundTimeoutSeconds: checksRaw?.checksFoundTimeoutSeconds ?? 60,
|
|
833
|
+
checksCompletionTimeoutSeconds: checksRaw?.checksCompletionTimeoutSeconds ?? 30 * 60,
|
|
834
|
+
pollIntervalSeconds: checksRaw?.pollIntervalSeconds ?? 15,
|
|
835
|
+
issueListLimit: checksRaw?.issueListLimit ?? 50
|
|
766
836
|
},
|
|
767
837
|
serena,
|
|
768
838
|
cleanup: {
|
|
769
|
-
enabled:
|
|
770
|
-
worktreeRetentionDays:
|
|
771
|
-
logRetentionDays:
|
|
839
|
+
enabled: cleanupRaw?.enabled ?? true,
|
|
840
|
+
worktreeRetentionDays: cleanupRaw?.worktreeRetentionDays ?? 14,
|
|
841
|
+
logRetentionDays: cleanupRaw?.logRetentionDays ?? 30
|
|
772
842
|
}
|
|
773
843
|
};
|
|
774
844
|
}
|
|
775
|
-
function assertKnownKeys(value, path9, knownKeys) {
|
|
776
|
-
for (const key of Object.keys(value)) {
|
|
777
|
-
if (!knownKeys.includes(key)) {
|
|
778
|
-
throw new Error(`${path9}.${key} is not supported`);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
845
|
function getVerificationCommands(target) {
|
|
783
846
|
return target.strategy.verify?.commands ?? [];
|
|
784
847
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
848
|
+
var OBSOLETE_CONFIG_PATHS = [
|
|
849
|
+
"pourkit.config.ts",
|
|
850
|
+
"pourkit.config.mjs",
|
|
851
|
+
"pourkit.config.js",
|
|
852
|
+
"pourkit.json"
|
|
853
|
+
];
|
|
854
|
+
var CANONICAL_CONFIG_PATH = ".pourkit/config.json";
|
|
855
|
+
async function loadRepoConfig(repoRoot2, _configFileName) {
|
|
856
|
+
const { existsSync: existsSync14 } = await import("fs");
|
|
857
|
+
const { join: pjoin } = await import("path");
|
|
858
|
+
for (const obPath of OBSOLETE_CONFIG_PATHS) {
|
|
859
|
+
const fullPath = pjoin(repoRoot2, obPath);
|
|
860
|
+
if (existsSync14(fullPath)) {
|
|
861
|
+
const isRootJson = obPath === "pourkit.json";
|
|
862
|
+
throw new Error(
|
|
863
|
+
isRootJson ? `Found root ${obPath}, but Pourkit config now lives at ${CANONICAL_CONFIG_PATH}. Move the file and update "$schema" to "./schema/pourkit.schema.json".` : `Found ${obPath}, but executable config is no longer supported. Move configuration to ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const configPath = pjoin(repoRoot2, CANONICAL_CONFIG_PATH);
|
|
868
|
+
if (!existsSync14(configPath)) {
|
|
793
869
|
throw new Error(
|
|
794
|
-
`No config
|
|
870
|
+
`No Pourkit config found at ${CANONICAL_CONFIG_PATH}. Run pourkit init or create ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
|
|
795
871
|
);
|
|
796
872
|
}
|
|
797
|
-
const
|
|
798
|
-
await
|
|
799
|
-
|
|
800
|
-
tmpDir,
|
|
801
|
-
`pourkit-config-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.mjs`
|
|
802
|
-
);
|
|
873
|
+
const { readFile: readFile5 } = await import("fs/promises");
|
|
874
|
+
const raw = await readFile5(configPath, "utf-8");
|
|
875
|
+
let parsed;
|
|
803
876
|
try {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
try {
|
|
823
|
-
await rm3(tmpFile, { force: true });
|
|
824
|
-
} catch {
|
|
825
|
-
}
|
|
877
|
+
parsed = JSON.parse(raw);
|
|
878
|
+
} catch (err) {
|
|
879
|
+
const message = err instanceof SyntaxError ? err.message : String(err);
|
|
880
|
+
throw new Error(`${CANONICAL_CONFIG_PATH}: Invalid JSON \u2014 ${message}`);
|
|
881
|
+
}
|
|
882
|
+
return parseConfig(parsed);
|
|
883
|
+
}
|
|
884
|
+
async function loadConfig(configPath) {
|
|
885
|
+
const { readFile: readFile5 } = await import("fs/promises");
|
|
886
|
+
const ext = configPath.split(".").pop()?.toLowerCase();
|
|
887
|
+
if (ext === "json") {
|
|
888
|
+
const raw = await readFile5(configPath, "utf-8");
|
|
889
|
+
return parseConfig(JSON.parse(raw));
|
|
890
|
+
}
|
|
891
|
+
if (ext === "mjs" || ext === "js" || ext === "ts") {
|
|
892
|
+
throw new Error(
|
|
893
|
+
`Executable config (${ext}) is no longer supported. Use .pourkit/config.json with "$schema": "./schema/pourkit.schema.json".`
|
|
894
|
+
);
|
|
826
895
|
}
|
|
896
|
+
throw new Error(`Unsupported config format: ${ext}`);
|
|
827
897
|
}
|
|
828
898
|
function resolvePromptTemplatePath(repoRoot2, promptTemplate) {
|
|
829
899
|
if (promptTemplate.includes("/")) {
|
|
@@ -872,8 +942,8 @@ function renderTemplate(template, issue) {
|
|
|
872
942
|
init_common();
|
|
873
943
|
|
|
874
944
|
// shared/run-context.ts
|
|
875
|
-
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
876
|
-
import { isAbsolute, join as join2, relative, resolve } from "path";
|
|
945
|
+
import { existsSync, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
946
|
+
import { isAbsolute as isAbsolute2, join as join2, relative, resolve as resolve2 } from "path";
|
|
877
947
|
|
|
878
948
|
// commands/run-verification.ts
|
|
879
949
|
init_common();
|
|
@@ -1014,11 +1084,17 @@ function buildRunContextMarkdown(options) {
|
|
|
1014
1084
|
}
|
|
1015
1085
|
}
|
|
1016
1086
|
if (sections.includes("branch")) {
|
|
1087
|
+
const canonicalBaseRef = `origin/${target.baseBranch}`;
|
|
1017
1088
|
parts.push(
|
|
1018
1089
|
"## Branch",
|
|
1019
1090
|
"",
|
|
1020
1091
|
`- Base: ${target.baseBranch}`,
|
|
1092
|
+
`- Canonical Base Ref: ${canonicalBaseRef}`,
|
|
1021
1093
|
`- Working Branch: ${branchName}`,
|
|
1094
|
+
"",
|
|
1095
|
+
"Use the canonical base ref for scope checks, commit ranges, and diffs. Do not use a bare PRD/base branch name such as `PRD-0063`; local branches with those names may be stale.",
|
|
1096
|
+
"",
|
|
1097
|
+
"Runner-owned Git operations: do not run destructive history/worktree commands such as `git reset --hard`, `git checkout .`, `git clean`, `git rebase`, `git merge`, or `git branch -f`. Edit files only; the runner owns branch/base movement.",
|
|
1022
1098
|
""
|
|
1023
1099
|
);
|
|
1024
1100
|
}
|
|
@@ -1081,7 +1157,7 @@ function renderPrdContext(issue, parentPrdIssue, repoRoot2) {
|
|
|
1081
1157
|
`### Parent PRD Content: \`${relative(repoRoot2, parentPrdPath)}\``,
|
|
1082
1158
|
"",
|
|
1083
1159
|
"```markdown",
|
|
1084
|
-
|
|
1160
|
+
readFileSync2(parentPrdPath, "utf-8").trimEnd(),
|
|
1085
1161
|
"```",
|
|
1086
1162
|
""
|
|
1087
1163
|
);
|
|
@@ -1108,7 +1184,7 @@ function renderPrdContext(issue, parentPrdIssue, repoRoot2) {
|
|
|
1108
1184
|
`### Document Content: \`${documentPath}\``,
|
|
1109
1185
|
"",
|
|
1110
1186
|
"```markdown",
|
|
1111
|
-
|
|
1187
|
+
readFileSync2(absolutePath, "utf-8").trimEnd(),
|
|
1112
1188
|
"```",
|
|
1113
1189
|
""
|
|
1114
1190
|
);
|
|
@@ -1133,10 +1209,10 @@ function extractRepoPaths(section) {
|
|
|
1133
1209
|
return Array.from(paths);
|
|
1134
1210
|
}
|
|
1135
1211
|
function resolveRepoPath(repoRoot2, path9) {
|
|
1136
|
-
if (
|
|
1137
|
-
const resolved =
|
|
1212
|
+
if (isAbsolute2(path9) || path9.includes("\0")) return null;
|
|
1213
|
+
const resolved = resolve2(repoRoot2, path9);
|
|
1138
1214
|
const repoRelative2 = relative(repoRoot2, resolved);
|
|
1139
|
-
if (repoRelative2.startsWith("..") ||
|
|
1215
|
+
if (repoRelative2.startsWith("..") || isAbsolute2(repoRelative2)) return null;
|
|
1140
1216
|
return resolved;
|
|
1141
1217
|
}
|
|
1142
1218
|
function findParentPrdPath(repoRoot2, parentRef) {
|
|
@@ -1180,17 +1256,19 @@ function renderCriteria(criteria) {
|
|
|
1180
1256
|
|
|
1181
1257
|
// shared/prompt-guidance.ts
|
|
1182
1258
|
var PROTECTED_WORK_RULE = "Do **not** revert, delete, or substantially strip already-landed protected sibling/base work unless the issue explicitly requires those files.";
|
|
1259
|
+
var RUNNER_OWNED_GIT_RULE = "Do **not** move Git history or reset the Worktree. Do not run `git reset --hard`, `git checkout .`, `git clean`, `git rebase`, `git merge`, `git branch -f`, or equivalent destructive history/worktree commands; branch/base changes are runner-owned.";
|
|
1183
1260
|
function appendProtectedWorkGuidance(promptBody) {
|
|
1184
1261
|
return `${promptBody}
|
|
1185
1262
|
|
|
1186
1263
|
## Hard Rule
|
|
1187
1264
|
|
|
1188
|
-
- ${PROTECTED_WORK_RULE}
|
|
1265
|
+
- ${PROTECTED_WORK_RULE}
|
|
1266
|
+
- ${RUNNER_OWNED_GIT_RULE}`;
|
|
1189
1267
|
}
|
|
1190
1268
|
|
|
1191
1269
|
// shared/worktree-run-state.ts
|
|
1192
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as
|
|
1193
|
-
import { dirname, join as join3 } from "path";
|
|
1270
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1271
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
1194
1272
|
var WORKTREE_RUN_STATE_PATH = ".pourkit/state.json";
|
|
1195
1273
|
function readWorktreeRunState(worktreePath) {
|
|
1196
1274
|
const statePath = join3(worktreePath, WORKTREE_RUN_STATE_PATH);
|
|
@@ -1198,7 +1276,7 @@ function readWorktreeRunState(worktreePath) {
|
|
|
1198
1276
|
return null;
|
|
1199
1277
|
}
|
|
1200
1278
|
try {
|
|
1201
|
-
const raw = JSON.parse(
|
|
1279
|
+
const raw = JSON.parse(readFileSync3(statePath, "utf-8"));
|
|
1202
1280
|
if (isValidWorktreeRunState(raw)) {
|
|
1203
1281
|
return raw;
|
|
1204
1282
|
}
|
|
@@ -1219,7 +1297,7 @@ function isValidWorktreeRunState(raw) {
|
|
|
1219
1297
|
}
|
|
1220
1298
|
function writeWorktreeRunState(worktreePath, state) {
|
|
1221
1299
|
const statePath = join3(worktreePath, WORKTREE_RUN_STATE_PATH);
|
|
1222
|
-
mkdirSync2(
|
|
1300
|
+
mkdirSync2(dirname2(statePath), { recursive: true });
|
|
1223
1301
|
writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
1224
1302
|
}
|
|
1225
1303
|
function updateWorktreeRunState(worktreePath, update) {
|
|
@@ -1541,12 +1619,12 @@ function validateRecoveryDecision(artifact, allowedDecisions) {
|
|
|
1541
1619
|
}
|
|
1542
1620
|
|
|
1543
1621
|
// shared/attempt-log.ts
|
|
1544
|
-
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as
|
|
1545
|
-
import { dirname as
|
|
1622
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "fs";
|
|
1623
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
1546
1624
|
var ATTEMPT_LOG_PATH = ".pourkit/attempt-log.jsonl";
|
|
1547
1625
|
function writeAttemptLog(worktreePath, entry) {
|
|
1548
1626
|
const logPath = join4(worktreePath, ATTEMPT_LOG_PATH);
|
|
1549
|
-
mkdirSync3(
|
|
1627
|
+
mkdirSync3(dirname3(logPath), { recursive: true });
|
|
1550
1628
|
appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
1551
1629
|
}
|
|
1552
1630
|
function readAttemptLog(worktreePath) {
|
|
@@ -1554,7 +1632,7 @@ function readAttemptLog(worktreePath) {
|
|
|
1554
1632
|
if (!existsSync3(logPath)) {
|
|
1555
1633
|
return [];
|
|
1556
1634
|
}
|
|
1557
|
-
const raw =
|
|
1635
|
+
const raw = readFileSync4(logPath, "utf-8");
|
|
1558
1636
|
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
1559
1637
|
const entries = [];
|
|
1560
1638
|
for (const line of lines) {
|
|
@@ -1673,7 +1751,7 @@ async function runBaseRefreshAttempt(options) {
|
|
|
1673
1751
|
}
|
|
1674
1752
|
|
|
1675
1753
|
// commands/conflict-resolution.ts
|
|
1676
|
-
import { existsSync as existsSync9, readFileSync as
|
|
1754
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
1677
1755
|
import { join as join8 } from "path";
|
|
1678
1756
|
init_common();
|
|
1679
1757
|
|
|
@@ -1838,8 +1916,8 @@ function parseConflictResolutionArtifact(output) {
|
|
|
1838
1916
|
// commands/artifact-validation.ts
|
|
1839
1917
|
import { createHash } from "crypto";
|
|
1840
1918
|
import { execSync } from "child_process";
|
|
1841
|
-
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as
|
|
1842
|
-
import { isAbsolute as
|
|
1919
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
1920
|
+
import { isAbsolute as isAbsolute3, join as join7, resolve as resolve3 } from "path";
|
|
1843
1921
|
|
|
1844
1922
|
// pr/review-verdict.ts
|
|
1845
1923
|
var ReviewVerdictProtocolError = class extends Error {
|
|
@@ -1874,15 +1952,15 @@ function parseReviewVerdict(output) {
|
|
|
1874
1952
|
import {
|
|
1875
1953
|
existsSync as existsSync6,
|
|
1876
1954
|
mkdirSync as mkdirSync6,
|
|
1877
|
-
readFileSync as
|
|
1955
|
+
readFileSync as readFileSync7,
|
|
1878
1956
|
readdirSync as readdirSync2,
|
|
1879
1957
|
writeFileSync as writeFileSync3
|
|
1880
1958
|
} from "fs";
|
|
1881
1959
|
import { join as join6 } from "path";
|
|
1882
1960
|
|
|
1883
1961
|
// execution/agent-output-retry.ts
|
|
1884
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as
|
|
1885
|
-
import { dirname as
|
|
1962
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync } from "fs";
|
|
1963
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
1886
1964
|
async function executeWithMissingOrEmptyArtifactRetry({
|
|
1887
1965
|
executionProvider,
|
|
1888
1966
|
executionOptions,
|
|
@@ -1942,20 +2020,20 @@ function readArtifactOutput(artifactPath) {
|
|
|
1942
2020
|
if (!existsSync4(artifactPath)) {
|
|
1943
2021
|
return { _tag: "missing", path: artifactPath };
|
|
1944
2022
|
}
|
|
1945
|
-
const output =
|
|
2023
|
+
const output = readFileSync5(artifactPath, "utf-8");
|
|
1946
2024
|
if (!output.trim()) {
|
|
1947
2025
|
return { _tag: "empty", path: artifactPath };
|
|
1948
2026
|
}
|
|
1949
2027
|
return { _tag: "content", value: output, path: artifactPath };
|
|
1950
2028
|
}
|
|
1951
2029
|
function prepareArtifactPath(artifactPath) {
|
|
1952
|
-
mkdirSync4(
|
|
2030
|
+
mkdirSync4(dirname4(artifactPath), { recursive: true });
|
|
1953
2031
|
rmSync(artifactPath, { recursive: true, force: true });
|
|
1954
2032
|
}
|
|
1955
2033
|
|
|
1956
2034
|
// shared/effect-services.ts
|
|
1957
2035
|
import { Context, Effect as Effect3, Layer } from "effect";
|
|
1958
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as
|
|
2036
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
|
|
1959
2037
|
var GitExecutionError = class extends Error {
|
|
1960
2038
|
_tag = "GitExecutionError";
|
|
1961
2039
|
message;
|
|
@@ -1971,7 +2049,7 @@ var FileSystemDefault = Layer.succeed(
|
|
|
1971
2049
|
FileSystem,
|
|
1972
2050
|
FileSystem.of({
|
|
1973
2051
|
readFile: (path9) => Effect3.try({
|
|
1974
|
-
try: () =>
|
|
2052
|
+
try: () => readFileSync6(path9, "utf-8"),
|
|
1975
2053
|
catch: (error) => new Error(
|
|
1976
2054
|
`Failed to read file ${path9}: ${error instanceof Error ? error.message : String(error)}`
|
|
1977
2055
|
)
|
|
@@ -2195,7 +2273,7 @@ function validateRefactorArtifact(artifactPath, findingIds) {
|
|
|
2195
2273
|
`Refactor artifact missing at ${artifactPath}`
|
|
2196
2274
|
);
|
|
2197
2275
|
}
|
|
2198
|
-
const content =
|
|
2276
|
+
const content = readFileSync7(artifactPath, "utf-8");
|
|
2199
2277
|
if (!content.trim()) {
|
|
2200
2278
|
throw new RefactorArtifactValidationError("Refactor artifact is empty");
|
|
2201
2279
|
}
|
|
@@ -2520,12 +2598,29 @@ A prior review emitted \`NEEDS_HUMAN\` and stopped the agent loop. The issue has
|
|
|
2520
2598
|
Before carrying forward old blockers, inspect newer issue comments and the current worktree. Treat prior Reviewer and Refactor Artifacts as historical context, not active findings unless they still apply.
|
|
2521
2599
|
|
|
2522
2600
|
` : "";
|
|
2523
|
-
return `${renderedTemplate}
|
|
2601
|
+
return appendProtectedWorkGuidance(`${renderedTemplate}
|
|
2524
2602
|
|
|
2525
2603
|
## Shared Run Context
|
|
2526
2604
|
|
|
2527
2605
|
Read the selected issue requirements, PRD context, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
2528
2606
|
|
|
2607
|
+
## Initial Verification Pass
|
|
2608
|
+
|
|
2609
|
+
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
2610
|
+
- Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
|
|
2611
|
+
- Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
|
|
2612
|
+
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
2613
|
+
- If a command cannot run because the environment is missing required setup, dependencies, or scripts outside agent control, treat it as a human handoff blocker.
|
|
2614
|
+
- If no verification commands are configured, note that and proceed with normal review.
|
|
2615
|
+
|
|
2616
|
+
## Scope Evidence Rules
|
|
2617
|
+
|
|
2618
|
+
- Use the Run Context's canonical base ref, for example \`origin/<base>\`, for scope diffs and commit ranges.
|
|
2619
|
+
- Do not use bare PRD/base branch names such as \`PRD-0063\`, \`main\`, or \`dev\` for scope decisions; local branches with those names may be stale.
|
|
2620
|
+
- Only call a file or commit out of scope when it is part of the working branch's delta from the canonical base ref.
|
|
2621
|
+
- Do not recommend reverting accepted sibling/base work. If a file is present or changed on the canonical base ref, it is not this Issue's scope overreach.
|
|
2622
|
+
- If scope evidence is ambiguous, use \`NEEDS_HUMAN\` or ask for a runner/base mismatch decision instead of telling Refactor to revert files.
|
|
2623
|
+
|
|
2529
2624
|
${hasCriteriaPlaceholder ? "" : `## Review Criteria
|
|
2530
2625
|
|
|
2531
2626
|
${criteriaBlock}
|
|
@@ -2542,7 +2637,7 @@ End the file with exactly one wrapped verdict token: <verdict>PASS</verdict>, <v
|
|
|
2542
2637
|
|
|
2543
2638
|
Findings must include an ID column with values in the format R${iteration}.F{findingNumber} (e.g., R${iteration}.F1, R${iteration}.F2) and a Supersedes column referencing the finding ID being superseded (or a hyphen for new findings).
|
|
2544
2639
|
|
|
2545
|
-
When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token
|
|
2640
|
+
When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token.`);
|
|
2546
2641
|
});
|
|
2547
2642
|
}
|
|
2548
2643
|
function renderReviewHistory(reviewHistory) {
|
|
@@ -2667,12 +2762,12 @@ function recoverReviewOutputFromLog(logPath) {
|
|
|
2667
2762
|
if (!existsSync6(logPath)) {
|
|
2668
2763
|
return null;
|
|
2669
2764
|
}
|
|
2670
|
-
const logContent =
|
|
2765
|
+
const logContent = readFileSync7(logPath, "utf-8");
|
|
2671
2766
|
return recoverReviewOutputFromString(logContent);
|
|
2672
2767
|
}
|
|
2673
2768
|
function readReviewArtifact(artifactPath, logPath) {
|
|
2674
2769
|
if (existsSync6(artifactPath)) {
|
|
2675
|
-
const output =
|
|
2770
|
+
const output = readFileSync7(artifactPath, "utf-8");
|
|
2676
2771
|
if (output.trim()) {
|
|
2677
2772
|
return output;
|
|
2678
2773
|
}
|
|
@@ -3164,7 +3259,7 @@ function inferConventionalType(commitSummaries) {
|
|
|
3164
3259
|
}
|
|
3165
3260
|
|
|
3166
3261
|
// prd-run/final-review-validation.ts
|
|
3167
|
-
import { existsSync as existsSync7, readFileSync as
|
|
3262
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
3168
3263
|
function parseFinalReviewArtifact(artifactPath) {
|
|
3169
3264
|
if (!existsSync7(artifactPath)) {
|
|
3170
3265
|
return {
|
|
@@ -3175,7 +3270,7 @@ function parseFinalReviewArtifact(artifactPath) {
|
|
|
3175
3270
|
}
|
|
3176
3271
|
let content;
|
|
3177
3272
|
try {
|
|
3178
|
-
content =
|
|
3273
|
+
content = readFileSync8(artifactPath, "utf-8");
|
|
3179
3274
|
} catch (error) {
|
|
3180
3275
|
return {
|
|
3181
3276
|
ok: false,
|
|
@@ -3378,7 +3473,7 @@ function readArtifact(options) {
|
|
|
3378
3473
|
};
|
|
3379
3474
|
}
|
|
3380
3475
|
try {
|
|
3381
|
-
const content =
|
|
3476
|
+
const content = readFileSync9(options.artifactPath, "utf-8");
|
|
3382
3477
|
if (!content.trim()) {
|
|
3383
3478
|
return { ok: false, result: invalid(options, "Artifact is empty") };
|
|
3384
3479
|
}
|
|
@@ -3448,7 +3543,7 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3448
3543
|
for (const p of parsed.changedPaths) {
|
|
3449
3544
|
const normalized = p.replace(/\\/g, "/");
|
|
3450
3545
|
const segments = normalized.split("/");
|
|
3451
|
-
if (normalized.trim() === "" || normalized === "." || normalized === ".." ||
|
|
3546
|
+
if (normalized.trim() === "" || normalized === "." || normalized === ".." || isAbsolute3(p) || normalized.startsWith("/") || segments.some((segment) => segment === "..")) {
|
|
3452
3547
|
return {
|
|
3453
3548
|
ok: false,
|
|
3454
3549
|
reason: `changedPaths must not contain absolute paths or path traversal: ${p}`,
|
|
@@ -3549,7 +3644,7 @@ function validateAgentArtifact(options) {
|
|
|
3549
3644
|
case "refactor": {
|
|
3550
3645
|
let findingIds = options.findingIds ?? [];
|
|
3551
3646
|
if (findingIds.length === 0 && options.latestReviewArtifactPath) {
|
|
3552
|
-
const latestReview =
|
|
3647
|
+
const latestReview = readFileSync9(
|
|
3553
3648
|
options.latestReviewArtifactPath,
|
|
3554
3649
|
"utf-8"
|
|
3555
3650
|
);
|
|
@@ -3572,10 +3667,10 @@ function validateAgentArtifact(options) {
|
|
|
3572
3667
|
if (options.checkConflictMarkers !== false && parsed.status === "resolved") {
|
|
3573
3668
|
const base = options.worktreePath ?? process.cwd();
|
|
3574
3669
|
const filesWithMarkers = parsed.files.filter((file) => {
|
|
3575
|
-
const filePath =
|
|
3670
|
+
const filePath = resolve3(base, file);
|
|
3576
3671
|
try {
|
|
3577
3672
|
return CONFLICT_MARKER_PATTERN.test(
|
|
3578
|
-
|
|
3673
|
+
readFileSync9(filePath, "utf-8")
|
|
3579
3674
|
);
|
|
3580
3675
|
} catch {
|
|
3581
3676
|
return false;
|
|
@@ -3686,7 +3781,7 @@ async function hasUnresolvedConflictMarkers(worktreePath, files) {
|
|
|
3686
3781
|
for (const file of files) {
|
|
3687
3782
|
const filePath = join8(worktreePath, file);
|
|
3688
3783
|
try {
|
|
3689
|
-
const content =
|
|
3784
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
3690
3785
|
if (CONFLICT_MARKER_PATTERN2.test(content)) {
|
|
3691
3786
|
return true;
|
|
3692
3787
|
}
|
|
@@ -3944,7 +4039,7 @@ async function canReachMcp(url) {
|
|
|
3944
4039
|
return true;
|
|
3945
4040
|
} catch {
|
|
3946
4041
|
if (attempt < 9) {
|
|
3947
|
-
await new Promise((
|
|
4042
|
+
await new Promise((resolve4) => setTimeout(resolve4, 100));
|
|
3948
4043
|
}
|
|
3949
4044
|
}
|
|
3950
4045
|
}
|
|
@@ -3995,7 +4090,7 @@ async function prepareSerenaForTarget(options) {
|
|
|
3995
4090
|
}
|
|
3996
4091
|
|
|
3997
4092
|
// failure-resolution/failure-resolution-agent.ts
|
|
3998
|
-
import { readFileSync as
|
|
4093
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
3999
4094
|
import { join as join9 } from "path";
|
|
4000
4095
|
|
|
4001
4096
|
// failure-resolution/recovery-policy.ts
|
|
@@ -4149,7 +4244,7 @@ async function runFailureResolutionAgent(options) {
|
|
|
4149
4244
|
}
|
|
4150
4245
|
let artifact;
|
|
4151
4246
|
try {
|
|
4152
|
-
const md =
|
|
4247
|
+
const md = readFileSync11(fullArtifactPath, "utf-8");
|
|
4153
4248
|
const validation2 = validateAgentArtifact({
|
|
4154
4249
|
kind: "failure-resolution",
|
|
4155
4250
|
artifactPath: fullArtifactPath,
|
|
@@ -4243,7 +4338,7 @@ async function writeRecoveryAttempt(worktreePath, outcome, fingerprint, summary,
|
|
|
4243
4338
|
|
|
4244
4339
|
// commands/pr-description-agent.ts
|
|
4245
4340
|
import { join as join11 } from "path";
|
|
4246
|
-
import { readFileSync as
|
|
4341
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
4247
4342
|
|
|
4248
4343
|
// pr/pr-description-context.ts
|
|
4249
4344
|
init_common();
|
|
@@ -4437,7 +4532,7 @@ function runFinalizerAgent(options) {
|
|
|
4437
4532
|
],
|
|
4438
4533
|
logger: options.logger
|
|
4439
4534
|
});
|
|
4440
|
-
const output =
|
|
4535
|
+
const output = readFileSync12(artifactPath, "utf-8");
|
|
4441
4536
|
yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
|
|
4442
4537
|
return result;
|
|
4443
4538
|
});
|
|
@@ -4565,7 +4660,7 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
|
4565
4660
|
|
|
4566
4661
|
// prd-run/local-merge-coordinator.ts
|
|
4567
4662
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4568
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as
|
|
4663
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
|
|
4569
4664
|
import { join as join12 } from "path";
|
|
4570
4665
|
|
|
4571
4666
|
// prd-run/local-branches.ts
|
|
@@ -4592,7 +4687,7 @@ function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
|
4592
4687
|
const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
|
|
4593
4688
|
if (!existsSync10(issuePath)) return null;
|
|
4594
4689
|
try {
|
|
4595
|
-
const content =
|
|
4690
|
+
const content = readFileSync13(issuePath, "utf-8");
|
|
4596
4691
|
const parsed = JSON.parse(content);
|
|
4597
4692
|
return typeof parsed.branchName === "string" && parsed.branchName ? parsed.branchName : null;
|
|
4598
4693
|
} catch {
|
|
@@ -4604,7 +4699,7 @@ async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
|
4604
4699
|
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
4605
4700
|
if (!existsSync10(receiptPath)) return null;
|
|
4606
4701
|
try {
|
|
4607
|
-
const content =
|
|
4702
|
+
const content = readFileSync13(receiptPath, "utf-8");
|
|
4608
4703
|
const parsed = JSON.parse(content);
|
|
4609
4704
|
if (typeof parsed.prdId === "string" && typeof parsed.issueId === "string" && typeof parsed.stage === "string" && typeof parsed.sourceBranch === "string" && typeof parsed.localPrdBranch === "string" && typeof parsed.mergeCommit === "string" && typeof parsed.completedAt === "string") {
|
|
4610
4705
|
return parsed;
|
|
@@ -4620,7 +4715,7 @@ async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
|
|
|
4620
4715
|
if (existsSync10(receiptPath)) {
|
|
4621
4716
|
try {
|
|
4622
4717
|
const existing = JSON.parse(
|
|
4623
|
-
|
|
4718
|
+
readFileSync13(receiptPath, "utf-8")
|
|
4624
4719
|
);
|
|
4625
4720
|
if (existing.prdId === prdId && existing.issueId === issueId && existing.mergeCommit) {
|
|
4626
4721
|
return {
|
|
@@ -4983,7 +5078,7 @@ function runMergeCoordinator(options) {
|
|
|
4983
5078
|
}
|
|
4984
5079
|
|
|
4985
5080
|
// commands/issue-final-review-agent.ts
|
|
4986
|
-
import { existsSync as existsSync11, readFileSync as
|
|
5081
|
+
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
4987
5082
|
import { join as join13 } from "path";
|
|
4988
5083
|
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join13(
|
|
4989
5084
|
".pourkit",
|
|
@@ -5097,12 +5192,21 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
5097
5192
|
}
|
|
5098
5193
|
function loadIssueFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
5099
5194
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
5100
|
-
const promptBody = existsSync11(promptPath) ?
|
|
5195
|
+
const promptBody = existsSync11(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
|
|
5101
5196
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
5102
5197
|
|
|
5103
5198
|
## Shared Run Context
|
|
5104
5199
|
|
|
5105
|
-
Read the selected issue requirements, PRD context, comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
5200
|
+
Read the selected issue requirements, PRD context, comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
5201
|
+
|
|
5202
|
+
## Initial Verification Pass
|
|
5203
|
+
|
|
5204
|
+
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
5205
|
+
- Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
|
|
5206
|
+
- Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
|
|
5207
|
+
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
5208
|
+
- If a command cannot run because the environment is missing required setup, dependencies, or scripts outside agent control, treat it as a human handoff blocker.
|
|
5209
|
+
- If no verification commands are configured, note that and proceed with normal review.`);
|
|
5106
5210
|
}
|
|
5107
5211
|
|
|
5108
5212
|
// issues/issue-transitions.ts
|
|
@@ -5305,7 +5409,7 @@ async function advanceIssueFinalReview(options) {
|
|
|
5305
5409
|
"Issue Final Review state is incomplete: missing artifactPath"
|
|
5306
5410
|
);
|
|
5307
5411
|
}
|
|
5308
|
-
const artifactPath =
|
|
5412
|
+
const artifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join14(worktreePath, ifrFromState.artifactPath);
|
|
5309
5413
|
const validation = validateAgentArtifact({
|
|
5310
5414
|
kind: "issue-final-review",
|
|
5311
5415
|
artifactPath,
|
|
@@ -5337,6 +5441,12 @@ async function advanceIssueFinalReview(options) {
|
|
|
5337
5441
|
logger,
|
|
5338
5442
|
reviewArtifactPath
|
|
5339
5443
|
});
|
|
5444
|
+
await assertCanonicalBaseAncestor({
|
|
5445
|
+
worktreePath,
|
|
5446
|
+
baseRef: `origin/${target.baseBranch}`,
|
|
5447
|
+
stageName: "Issue Final Review",
|
|
5448
|
+
logger
|
|
5449
|
+
});
|
|
5340
5450
|
if (result.verdict === "pass") {
|
|
5341
5451
|
updateWorktreeRunState(worktreePath, {
|
|
5342
5452
|
issueFinalReview: {
|
|
@@ -5561,6 +5671,14 @@ async function startIssueRun(options) {
|
|
|
5561
5671
|
};
|
|
5562
5672
|
}
|
|
5563
5673
|
if (executionResult.worktreePath) {
|
|
5674
|
+
if (shouldRunBuilder) {
|
|
5675
|
+
await assertCanonicalBaseAncestor({
|
|
5676
|
+
worktreePath: executionResult.worktreePath,
|
|
5677
|
+
baseRef: `origin/${effectiveBaseBranch}`,
|
|
5678
|
+
stageName: "Builder",
|
|
5679
|
+
logger
|
|
5680
|
+
});
|
|
5681
|
+
}
|
|
5564
5682
|
if (worktreeState) {
|
|
5565
5683
|
updateWorktreeRunState(executionResult.worktreePath, {
|
|
5566
5684
|
completedStages: { builder: true }
|
|
@@ -5622,6 +5740,12 @@ async function advanceIssueRunReview(options) {
|
|
|
5622
5740
|
}
|
|
5623
5741
|
})
|
|
5624
5742
|
);
|
|
5743
|
+
await assertCanonicalBaseAncestor({
|
|
5744
|
+
worktreePath: options.worktreePath,
|
|
5745
|
+
baseRef: `origin/${options.target.baseBranch}`,
|
|
5746
|
+
stageName: "Review/Refactor",
|
|
5747
|
+
logger: options.logger
|
|
5748
|
+
});
|
|
5625
5749
|
updateWorktreeRunState(options.worktreePath, {
|
|
5626
5750
|
review: {
|
|
5627
5751
|
lifetimeIterations: reviewResult.lifetimeIterations,
|
|
@@ -5690,7 +5814,7 @@ async function completeIssueRun(options) {
|
|
|
5690
5814
|
message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
|
|
5691
5815
|
});
|
|
5692
5816
|
}
|
|
5693
|
-
const artifactContent =
|
|
5817
|
+
const artifactContent = readFileSync15(
|
|
5694
5818
|
finalizerFromState.artifactPath,
|
|
5695
5819
|
"utf-8"
|
|
5696
5820
|
);
|
|
@@ -5748,6 +5872,14 @@ async function completeIssueRun(options) {
|
|
|
5748
5872
|
}
|
|
5749
5873
|
prTitle = finalizerResult.title;
|
|
5750
5874
|
prBody = finalizerResult.body;
|
|
5875
|
+
if (executionResult.worktreePath) {
|
|
5876
|
+
await assertCanonicalBaseAncestor({
|
|
5877
|
+
worktreePath: executionResult.worktreePath,
|
|
5878
|
+
baseRef: `origin/${effectiveBaseBranch}`,
|
|
5879
|
+
stageName: "Finalizer",
|
|
5880
|
+
logger
|
|
5881
|
+
});
|
|
5882
|
+
}
|
|
5751
5883
|
}
|
|
5752
5884
|
prTitle = ensureConventionalPrTitle(
|
|
5753
5885
|
prTitle,
|
|
@@ -6116,18 +6248,12 @@ function getRefactorArtifactDir(artifactPath) {
|
|
|
6116
6248
|
}
|
|
6117
6249
|
async function finalizeWorktreeCommit(options) {
|
|
6118
6250
|
const { worktreePath, baseRef, title, body, logger } = options;
|
|
6119
|
-
await
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
});
|
|
6126
|
-
} catch {
|
|
6127
|
-
throw new Error(
|
|
6128
|
-
`Cannot finalize stale worktree: ${baseRef} is not an ancestor of HEAD. Refresh the branch onto the latest target base before creating the final commit.`
|
|
6129
|
-
);
|
|
6130
|
-
}
|
|
6251
|
+
await assertCanonicalBaseAncestor({
|
|
6252
|
+
worktreePath,
|
|
6253
|
+
baseRef,
|
|
6254
|
+
stageName: "final commit",
|
|
6255
|
+
logger
|
|
6256
|
+
});
|
|
6131
6257
|
await execCapture("git", ["reset", "--soft", baseRef], {
|
|
6132
6258
|
cwd: worktreePath,
|
|
6133
6259
|
logger,
|
|
@@ -6144,6 +6270,21 @@ async function finalizeWorktreeCommit(options) {
|
|
|
6144
6270
|
label: "git commit"
|
|
6145
6271
|
});
|
|
6146
6272
|
}
|
|
6273
|
+
async function assertCanonicalBaseAncestor(options) {
|
|
6274
|
+
const { worktreePath, baseRef, stageName, logger } = options;
|
|
6275
|
+
await syncRemoteBaseRef(worktreePath, baseRef, logger);
|
|
6276
|
+
try {
|
|
6277
|
+
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
6278
|
+
cwd: worktreePath,
|
|
6279
|
+
logger,
|
|
6280
|
+
label: "git merge-base --is-ancestor"
|
|
6281
|
+
});
|
|
6282
|
+
} catch {
|
|
6283
|
+
throw new Error(
|
|
6284
|
+
`Cannot continue after ${stageName}: ${baseRef} is not an ancestor of HEAD. An agent may have moved the issue branch behind the canonical base; refresh the branch onto the latest target base before continuing.`
|
|
6285
|
+
);
|
|
6286
|
+
}
|
|
6287
|
+
}
|
|
6147
6288
|
async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
|
|
6148
6289
|
const remoteBase = parseRemoteBaseRef(baseRef);
|
|
6149
6290
|
if (!remoteBase) {
|
|
@@ -6387,7 +6528,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
|
|
|
6387
6528
|
}
|
|
6388
6529
|
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
6389
6530
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6390
|
-
const promptBody = existsSync12(promptPath) ?
|
|
6531
|
+
const promptBody = existsSync12(promptPath) ? readFileSync15(promptPath, "utf-8") : promptTemplate;
|
|
6391
6532
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
6392
6533
|
|
|
6393
6534
|
## Shared Run Context
|
|
@@ -7384,7 +7525,7 @@ import path6 from "path";
|
|
|
7384
7525
|
|
|
7385
7526
|
// execution/sandbox-image.ts
|
|
7386
7527
|
import { createHash as createHash2 } from "crypto";
|
|
7387
|
-
import { existsSync as existsSync13, readFileSync as
|
|
7528
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
7388
7529
|
import path5 from "path";
|
|
7389
7530
|
function sandboxImageName(repoRoot2) {
|
|
7390
7531
|
const dirName = path5.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
@@ -7394,7 +7535,7 @@ function sandboxImageName(repoRoot2) {
|
|
|
7394
7535
|
if (!existsSync13(dockerfilePath)) {
|
|
7395
7536
|
return `sandcastle:${baseName}`;
|
|
7396
7537
|
}
|
|
7397
|
-
const fingerprint = createHash2("sha256").update(
|
|
7538
|
+
const fingerprint = createHash2("sha256").update(readFileSync16(dockerfilePath)).digest("hex").slice(0, 8);
|
|
7398
7539
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
7399
7540
|
}
|
|
7400
7541
|
|
|
@@ -7436,11 +7577,11 @@ init_common();
|
|
|
7436
7577
|
import { mkdtempSync } from "fs";
|
|
7437
7578
|
import { writeFile } from "fs/promises";
|
|
7438
7579
|
import { tmpdir } from "os";
|
|
7439
|
-
import { dirname as
|
|
7580
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
7440
7581
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
7441
7582
|
for (const artifact of artifacts) {
|
|
7442
7583
|
const filePath = join15(worktreePath, artifact.path);
|
|
7443
|
-
await ensureDir(
|
|
7584
|
+
await ensureDir(dirname5(filePath));
|
|
7444
7585
|
await writeFile(filePath, artifact.content, "utf-8");
|
|
7445
7586
|
}
|
|
7446
7587
|
}
|
|
@@ -8019,10 +8160,7 @@ function resolveE2EConfigFile(root) {
|
|
|
8019
8160
|
if (explicitConfig) {
|
|
8020
8161
|
return explicitConfig;
|
|
8021
8162
|
}
|
|
8022
|
-
|
|
8023
|
-
return "pourkit.config.ts";
|
|
8024
|
-
}
|
|
8025
|
-
return "pourkit.config.example.ts";
|
|
8163
|
+
return path8.join(".pourkit", "config.json");
|
|
8026
8164
|
}
|
|
8027
8165
|
function generateRunId() {
|
|
8028
8166
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -8390,7 +8528,8 @@ async function runSuccessE2E(options, runId, root, logger, client) {
|
|
|
8390
8528
|
if (!options.keep) {
|
|
8391
8529
|
await persistResources(root, runId, resources);
|
|
8392
8530
|
}
|
|
8393
|
-
const
|
|
8531
|
+
const e2eConfigFile = resolveE2EConfigFile(root);
|
|
8532
|
+
const baseConfig = e2eConfigFile === path8.join(".pourkit", "config.json") ? await loadRepoConfig(root) : await loadConfig(path8.resolve(root, e2eConfigFile));
|
|
8394
8533
|
const profile = resolveProfile(options.fullCheck);
|
|
8395
8534
|
const config = makeE2EConfig(
|
|
8396
8535
|
baseConfig,
|
|
@@ -8507,7 +8646,8 @@ async function runFailureE2E(options, runId, root, logger) {
|
|
|
8507
8646
|
if (!options.keep) {
|
|
8508
8647
|
await persistResources(root, runId, resources);
|
|
8509
8648
|
}
|
|
8510
|
-
const
|
|
8649
|
+
const e2eConfigFile = resolveE2EConfigFile(root);
|
|
8650
|
+
const baseConfig = e2eConfigFile === path8.join(".pourkit", "config.json") ? await loadRepoConfig(root) : await loadConfig(path8.resolve(root, e2eConfigFile));
|
|
8511
8651
|
const profile = resolveProfile(options.fullCheck);
|
|
8512
8652
|
const config = makeFailureE2EConfig(
|
|
8513
8653
|
baseConfig,
|