@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/cli.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((resolve5) => {
|
|
62
62
|
if (!fileStream) {
|
|
63
|
-
|
|
63
|
+
resolve5();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
const timer = setTimeout(() => {
|
|
67
67
|
if (!fileStream.destroyed) {
|
|
68
68
|
fileStream.destroy();
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
resolve5();
|
|
71
71
|
}, 2e3);
|
|
72
72
|
fileStream.end(() => {
|
|
73
73
|
clearTimeout(timer);
|
|
74
|
-
|
|
74
|
+
resolve5();
|
|
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((resolve5) => setTimeout(resolve5, ms));
|
|
269
269
|
}
|
|
270
270
|
async function execCaptureWithRetry(command, args, options = {}) {
|
|
271
271
|
const retries = options.retries ?? 3;
|
|
@@ -335,175 +335,37 @@ import { pathToFileURL } from "url";
|
|
|
335
335
|
import { Command, Option, CommanderError } from "commander";
|
|
336
336
|
|
|
337
337
|
// shared/config.ts
|
|
338
|
-
import {
|
|
339
|
-
import {
|
|
340
|
-
|
|
338
|
+
import { readFileSync } from "fs";
|
|
339
|
+
import { fileURLToPath } from "url";
|
|
340
|
+
import { dirname, isAbsolute, join, normalize, sep, resolve } from "path";
|
|
341
|
+
import Ajv from "ajv";
|
|
342
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
343
|
+
var __dirname = dirname(__filename);
|
|
344
|
+
var SCHEMA_PATH = resolve(__dirname, "../schema/pourkit.schema.json");
|
|
345
|
+
var _schema = null;
|
|
346
|
+
var _validate = null;
|
|
347
|
+
var _ajvErrors = null;
|
|
348
|
+
function getValidator() {
|
|
349
|
+
if (!_validate) {
|
|
350
|
+
const schema = _schema ?? JSON.parse(readFileSync(SCHEMA_PATH, "utf-8"));
|
|
351
|
+
_schema = schema;
|
|
352
|
+
const ajv = new Ajv({ strict: true });
|
|
353
|
+
ajv.addKeyword("x-pourkit-schema-version");
|
|
354
|
+
const validate = ajv.compile(schema);
|
|
355
|
+
_validate = (data) => {
|
|
356
|
+
_ajvErrors = null;
|
|
357
|
+
const valid2 = validate(data);
|
|
358
|
+
if (!valid2) _ajvErrors = validate.errors;
|
|
359
|
+
return valid2;
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return _validate;
|
|
363
|
+
}
|
|
341
364
|
var DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES = 3;
|
|
342
|
-
var
|
|
343
|
-
missingOrEmpty: z.number().int().nonnegative().default(DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES)
|
|
344
|
-
}).strict();
|
|
365
|
+
var DEFAULT_BRANCH_TEMPLATE = "pourkit/{{issue.number}}/{{issue.slug}}";
|
|
345
366
|
function resolveMissingOrEmptyOutputRetries(config) {
|
|
346
367
|
return config?.outputRetries?.missingOrEmpty ?? DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES;
|
|
347
368
|
}
|
|
348
|
-
var StageAgentConfigSchema = z.object({
|
|
349
|
-
agent: NonEmptyString,
|
|
350
|
-
model: NonEmptyString,
|
|
351
|
-
variant: NonEmptyString.optional(),
|
|
352
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
353
|
-
promptTemplate: NonEmptyString,
|
|
354
|
-
outputRetries: OutputRetriesConfigSchema.optional()
|
|
355
|
-
}).strict();
|
|
356
|
-
var ReviewerConfigSchema = z.object({
|
|
357
|
-
agent: NonEmptyString,
|
|
358
|
-
model: NonEmptyString,
|
|
359
|
-
variant: NonEmptyString.optional(),
|
|
360
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
361
|
-
promptTemplate: NonEmptyString,
|
|
362
|
-
outputRetries: OutputRetriesConfigSchema.optional(),
|
|
363
|
-
criteria: z.array(NonEmptyString),
|
|
364
|
-
includeReviewHistory: z.boolean().optional(),
|
|
365
|
-
passWithNotesRefactorAttempts: z.number().int().nonnegative().optional()
|
|
366
|
-
}).strict();
|
|
367
|
-
var VerificationCommandSchema = z.object({
|
|
368
|
-
command: z.string().nullable().optional(),
|
|
369
|
-
label: z.string().optional()
|
|
370
|
-
}).strict().refine((d) => d.command && d.command.trim() !== "", {
|
|
371
|
-
message: "must have a non-empty command"
|
|
372
|
-
}).transform((d) => ({
|
|
373
|
-
command: d.command,
|
|
374
|
-
label: d.label && d.label.trim() !== "" ? d.label : void 0
|
|
375
|
-
}));
|
|
376
|
-
var PrdRunModeSchema = z.enum(["github", "local"]);
|
|
377
|
-
var QueueConfigSchema = z.object({
|
|
378
|
-
loop: z.boolean().optional()
|
|
379
|
-
}).strict();
|
|
380
|
-
var FailureResolutionConfigSchema = z.object({
|
|
381
|
-
agent: NonEmptyString,
|
|
382
|
-
model: NonEmptyString,
|
|
383
|
-
variant: NonEmptyString.optional(),
|
|
384
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
385
|
-
promptTemplate: NonEmptyString,
|
|
386
|
-
outputRetries: OutputRetriesConfigSchema.optional(),
|
|
387
|
-
maxAttemptsPerFailure: z.number().int().positive(),
|
|
388
|
-
failureLimits: z.record(z.string(), z.number().int().positive()).optional()
|
|
389
|
-
}).strict();
|
|
390
|
-
var ReviewRefactorLoopStrategySchema = z.object({
|
|
391
|
-
type: z.literal("review-refactor-loop"),
|
|
392
|
-
implement: z.object({
|
|
393
|
-
builder: StageAgentConfigSchema
|
|
394
|
-
}).strict(),
|
|
395
|
-
conflictResolution: z.object({
|
|
396
|
-
agent: NonEmptyString,
|
|
397
|
-
model: NonEmptyString,
|
|
398
|
-
variant: NonEmptyString.optional(),
|
|
399
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
400
|
-
promptTemplate: NonEmptyString,
|
|
401
|
-
maxAttempts: z.number().int().positive()
|
|
402
|
-
}).strict().optional(),
|
|
403
|
-
failureResolution: FailureResolutionConfigSchema,
|
|
404
|
-
review: z.object({
|
|
405
|
-
reviewer: ReviewerConfigSchema,
|
|
406
|
-
refactor: StageAgentConfigSchema,
|
|
407
|
-
maxIterations: z.number().int().positive(),
|
|
408
|
-
passWithNotesRefactorAttempts: z.number().int().nonnegative().default(2)
|
|
409
|
-
}).strict(),
|
|
410
|
-
verify: z.object({
|
|
411
|
-
commands: z.preprocess(
|
|
412
|
-
(v) => Array.isArray(v) ? v : [],
|
|
413
|
-
z.array(VerificationCommandSchema).refine((arr) => arr.length > 0, {
|
|
414
|
-
message: "must contain at least one command"
|
|
415
|
-
})
|
|
416
|
-
)
|
|
417
|
-
}).strict().optional(),
|
|
418
|
-
issueFinalReview: StageAgentConfigSchema.extend({
|
|
419
|
-
maxAttempts: z.number().int().positive()
|
|
420
|
-
}),
|
|
421
|
-
finalize: z.object({
|
|
422
|
-
prDescriptionAgent: StageAgentConfigSchema,
|
|
423
|
-
maxAttempts: z.number().int().positive()
|
|
424
|
-
}).strict(),
|
|
425
|
-
prdRun: z.object({
|
|
426
|
-
mode: PrdRunModeSchema.optional(),
|
|
427
|
-
// Uses promptTemplate (canonical StageAgentConfig field), not prompt as Issue contract may suggest
|
|
428
|
-
finalReview: StageAgentConfigSchema
|
|
429
|
-
}).strict().optional()
|
|
430
|
-
}).strict();
|
|
431
|
-
var TargetSerenaConfigSchema = z.object({
|
|
432
|
-
enabled: z.boolean().optional(),
|
|
433
|
-
required: z.boolean().optional()
|
|
434
|
-
}).strict();
|
|
435
|
-
var TargetSchema = z.object({
|
|
436
|
-
name: NonEmptyString,
|
|
437
|
-
baseBranch: z.preprocess(
|
|
438
|
-
(v) => typeof v === "string" && v.length > 0 ? v : void 0,
|
|
439
|
-
NonEmptyString.default("main")
|
|
440
|
-
),
|
|
441
|
-
branchTemplate: z.string().default("pourkit/{{issue.number}}/{{issue.slug}}"),
|
|
442
|
-
setupCommands: z.preprocess(
|
|
443
|
-
(v) => Array.isArray(v) ? v : void 0,
|
|
444
|
-
z.array(VerificationCommandSchema).default([])
|
|
445
|
-
),
|
|
446
|
-
autoMerge: z.preprocess(
|
|
447
|
-
(v) => typeof v === "boolean" ? v : void 0,
|
|
448
|
-
z.boolean().default(true)
|
|
449
|
-
),
|
|
450
|
-
queue: QueueConfigSchema.optional(),
|
|
451
|
-
serena: TargetSerenaConfigSchema.optional(),
|
|
452
|
-
strategy: ReviewRefactorLoopStrategySchema
|
|
453
|
-
}).strict();
|
|
454
|
-
var LabelsSchema = z.object({
|
|
455
|
-
readyForAgent: NonEmptyString,
|
|
456
|
-
agentInProgress: NonEmptyString,
|
|
457
|
-
blocked: NonEmptyString,
|
|
458
|
-
prOpenAwaitingMerge: NonEmptyString,
|
|
459
|
-
readyForHuman: NonEmptyString,
|
|
460
|
-
needsTriage: NonEmptyString.optional().default("needs-triage")
|
|
461
|
-
}).strict();
|
|
462
|
-
var SandboxMountSchema = z.object({
|
|
463
|
-
hostPath: NonEmptyString,
|
|
464
|
-
sandboxPath: NonEmptyString,
|
|
465
|
-
readonly: z.boolean().default(false)
|
|
466
|
-
}).strict();
|
|
467
|
-
var SandboxSchema = z.object({
|
|
468
|
-
provider: NonEmptyString,
|
|
469
|
-
copyToWorktree: z.array(NonEmptyString).optional(),
|
|
470
|
-
mounts: z.array(SandboxMountSchema).optional(),
|
|
471
|
-
env: z.record(z.string()).optional(),
|
|
472
|
-
idleTimeoutSeconds: z.preprocess((v) => {
|
|
473
|
-
if (v === void 0) return void 0;
|
|
474
|
-
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
|
|
475
|
-
return v;
|
|
476
|
-
}, z.number().int().positive().optional())
|
|
477
|
-
}).strict();
|
|
478
|
-
var ChecksSchema = z.object({
|
|
479
|
-
requiredLabels: z.array(NonEmptyString),
|
|
480
|
-
allowedAuthors: z.array(NonEmptyString),
|
|
481
|
-
checksFoundTimeoutSeconds: z.number().int().positive().optional(),
|
|
482
|
-
checksCompletionTimeoutSeconds: z.number().int().positive().optional(),
|
|
483
|
-
pollIntervalSeconds: z.number().int().positive().optional(),
|
|
484
|
-
issueListLimit: z.number().int().positive().optional()
|
|
485
|
-
}).strict();
|
|
486
|
-
var CleanupConfigSchema = z.object({
|
|
487
|
-
enabled: z.boolean().default(true),
|
|
488
|
-
worktreeRetentionDays: z.number().int().positive().default(14),
|
|
489
|
-
logRetentionDays: z.number().int().positive().default(30)
|
|
490
|
-
}).strict();
|
|
491
|
-
var SerenaConfigSchema = z.object({
|
|
492
|
-
enabled: z.boolean().default(false),
|
|
493
|
-
required: z.boolean().default(false),
|
|
494
|
-
mcpUrl: NonEmptyString.default("http://localhost:9121/mcp"),
|
|
495
|
-
sandboxMcpUrl: NonEmptyString.default("http://localhost:9121/mcp"),
|
|
496
|
-
dataDir: z.string().default(".pourkit/serena/"),
|
|
497
|
-
autoStart: z.boolean().default(false)
|
|
498
|
-
}).strict();
|
|
499
|
-
var PourkitConfigSchema = z.object({
|
|
500
|
-
targets: z.array(TargetSchema).min(1),
|
|
501
|
-
labels: LabelsSchema,
|
|
502
|
-
sandbox: SandboxSchema,
|
|
503
|
-
checks: ChecksSchema,
|
|
504
|
-
cleanup: CleanupConfigSchema.optional(),
|
|
505
|
-
serena: SerenaConfigSchema.default({})
|
|
506
|
-
}).strict();
|
|
507
369
|
var removedFieldReplacements = {
|
|
508
370
|
"config.implementor": "targets[].strategy.implement.builder",
|
|
509
371
|
"config.reviewer": "targets[].strategy.review.reviewer",
|
|
@@ -567,11 +429,12 @@ function checkRemovedFields(raw) {
|
|
|
567
429
|
}
|
|
568
430
|
}
|
|
569
431
|
}
|
|
570
|
-
function
|
|
571
|
-
if (
|
|
432
|
+
function formatAjvPath(instancePath) {
|
|
433
|
+
if (!instancePath || instancePath === "/") return "";
|
|
434
|
+
const parts = instancePath.split("/").slice(1);
|
|
572
435
|
let result = "";
|
|
573
|
-
for (const segment of
|
|
574
|
-
if (
|
|
436
|
+
for (const segment of parts) {
|
|
437
|
+
if (/^\d+$/.test(segment)) {
|
|
575
438
|
result += `[${segment}]`;
|
|
576
439
|
} else {
|
|
577
440
|
result += result ? `.${segment}` : segment;
|
|
@@ -579,51 +442,172 @@ function formatZodPath(path9) {
|
|
|
579
442
|
}
|
|
580
443
|
return result;
|
|
581
444
|
}
|
|
582
|
-
function
|
|
583
|
-
const
|
|
584
|
-
const path9 =
|
|
585
|
-
if (
|
|
586
|
-
|
|
445
|
+
function formatFirstAjvError(errors) {
|
|
446
|
+
const error = errors[0];
|
|
447
|
+
const path9 = formatAjvPath(error.instancePath);
|
|
448
|
+
if (error.keyword === "required") {
|
|
449
|
+
const missingParam = error.params.missingProperty;
|
|
450
|
+
if (missingParam === "targets") {
|
|
451
|
+
return "Config must have at least one target";
|
|
452
|
+
}
|
|
453
|
+
if (path9 === "" && missingParam === "targets") {
|
|
454
|
+
return "Config must have at least one target";
|
|
455
|
+
}
|
|
456
|
+
if (path9) {
|
|
457
|
+
return `${path9} must have required property '${missingParam}'`;
|
|
458
|
+
}
|
|
459
|
+
return `${missingParam} must be an object`;
|
|
587
460
|
}
|
|
588
|
-
if (
|
|
589
|
-
|
|
461
|
+
if (error.keyword === "additionalProperties") {
|
|
462
|
+
const additionalProp = error.params.additionalProperty;
|
|
463
|
+
const keyPath = path9 ? `${path9}.${additionalProp}` : additionalProp;
|
|
464
|
+
return `${keyPath} is not supported`;
|
|
590
465
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
466
|
+
if (error.keyword === "minLength") {
|
|
467
|
+
if (path9) {
|
|
468
|
+
return `${path9} must be a non-empty string`;
|
|
469
|
+
}
|
|
470
|
+
return "Config must be a non-empty string";
|
|
471
|
+
}
|
|
472
|
+
if (error.keyword === "const") {
|
|
473
|
+
const allowedValue = error.params.allowedValue;
|
|
474
|
+
return `${path9 || "Config"} must be ${JSON.stringify(allowedValue)}`;
|
|
475
|
+
}
|
|
476
|
+
if (error.keyword === "enum") {
|
|
477
|
+
const allowedValues = error.params.allowedValues;
|
|
478
|
+
return `${path9} must be one of: ${allowedValues.map((v) => JSON.stringify(v)).join(", ")}`;
|
|
479
|
+
}
|
|
480
|
+
if (error.keyword === "minimum" || error.keyword === "exclusiveMinimum") {
|
|
481
|
+
const limit = error.params.limit;
|
|
482
|
+
if (limit === 1) {
|
|
483
|
+
return `${path9} must be a positive number`;
|
|
484
|
+
}
|
|
485
|
+
return `${path9} must be at least ${limit}`;
|
|
486
|
+
}
|
|
487
|
+
if (error.keyword === "type") {
|
|
488
|
+
const expected = error.params.type;
|
|
489
|
+
if (path9 === "") {
|
|
490
|
+
return `Config must be an object`;
|
|
491
|
+
}
|
|
492
|
+
if (expected === "object") {
|
|
493
|
+
return `${path9} must be an object`;
|
|
494
|
+
}
|
|
495
|
+
if (expected === "integer") {
|
|
496
|
+
return `${path9} must be an integer`;
|
|
497
|
+
}
|
|
498
|
+
if (expected === "string") {
|
|
499
|
+
return `${path9} must be a string`;
|
|
500
|
+
}
|
|
501
|
+
if (expected === "number") {
|
|
502
|
+
return `${path9} must be a number`;
|
|
503
|
+
}
|
|
504
|
+
return `${path9} must be ${expected === "integer" ? "an integer" : `a ${expected}`}`;
|
|
505
|
+
}
|
|
506
|
+
if (error.keyword === "minItems") {
|
|
507
|
+
return `${path9} must contain at least one item`;
|
|
508
|
+
}
|
|
509
|
+
if (error.keyword === "pattern") {
|
|
510
|
+
return `${path9} has an invalid format`;
|
|
511
|
+
}
|
|
512
|
+
return `${path9 || "Config"} ${error.message || "is invalid"}`;
|
|
513
|
+
}
|
|
514
|
+
function applyOutputRetriesDefaults(retries) {
|
|
515
|
+
if (retries === void 0) return void 0;
|
|
516
|
+
return {
|
|
517
|
+
missingOrEmpty: retries.missingOrEmpty ?? DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function applyStageAgentDefaults(agent) {
|
|
521
|
+
return {
|
|
522
|
+
...agent,
|
|
523
|
+
outputRetries: applyOutputRetriesDefaults(agent.outputRetries)
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function assertRepoRelativePath(value, location) {
|
|
527
|
+
const normalized = normalize(value);
|
|
528
|
+
if (isAbsolute(value) || isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${sep}`)) {
|
|
529
|
+
throw new Error(
|
|
530
|
+
`${location} must stay within the repository and be repo-relative; got "${value}"`
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function assertBaseBranch(value, location) {
|
|
535
|
+
if (value.includes("/")) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
`${location} must be a local branch name, not a remote-qualified, tag, ref, or path-like name; got "${value}"`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
if (/^[0-9a-f]{7,40}$/i.test(value)) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`${location} must be a branch name, not a commit SHA; got "${value}"`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function assertStageAgentPath(agent, location) {
|
|
547
|
+
if (!agent) return;
|
|
548
|
+
const promptTemplate = agent.promptTemplate;
|
|
549
|
+
if (typeof promptTemplate === "string") {
|
|
550
|
+
assertRepoRelativePath(promptTemplate, `${location}.promptTemplate`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function validateConfigSemantics(data) {
|
|
554
|
+
const sandbox = data.sandbox;
|
|
555
|
+
const copyToWorktree = sandbox?.copyToWorktree;
|
|
556
|
+
copyToWorktree?.forEach((entry, index) => {
|
|
557
|
+
assertRepoRelativePath(entry, `sandbox.copyToWorktree[${index}]`);
|
|
558
|
+
});
|
|
559
|
+
const targetNames = /* @__PURE__ */ new Set();
|
|
560
|
+
data.targets.forEach((target, targetIndex) => {
|
|
561
|
+
const name = target.name;
|
|
562
|
+
if (targetNames.has(name)) {
|
|
563
|
+
throw new Error(
|
|
564
|
+
`Duplicate target name "${name}"; target names must be unique`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
targetNames.add(name);
|
|
568
|
+
assertBaseBranch(
|
|
569
|
+
target.baseBranch,
|
|
570
|
+
`targets[${targetIndex}].baseBranch`
|
|
571
|
+
);
|
|
572
|
+
const strategy = target.strategy;
|
|
573
|
+
const implement = strategy.implement;
|
|
574
|
+
const review = strategy.review;
|
|
575
|
+
const finalize = strategy.finalize;
|
|
576
|
+
assertStageAgentPath(
|
|
577
|
+
implement.builder,
|
|
578
|
+
`targets[${targetIndex}].strategy.implement.builder`
|
|
579
|
+
);
|
|
580
|
+
assertStageAgentPath(
|
|
581
|
+
strategy.conflictResolution,
|
|
582
|
+
`targets[${targetIndex}].strategy.conflictResolution`
|
|
583
|
+
);
|
|
584
|
+
assertStageAgentPath(
|
|
585
|
+
strategy.failureResolution,
|
|
586
|
+
`targets[${targetIndex}].strategy.failureResolution`
|
|
587
|
+
);
|
|
588
|
+
assertStageAgentPath(
|
|
589
|
+
review.reviewer,
|
|
590
|
+
`targets[${targetIndex}].strategy.review.reviewer`
|
|
591
|
+
);
|
|
592
|
+
assertStageAgentPath(
|
|
593
|
+
review.refactor,
|
|
594
|
+
`targets[${targetIndex}].strategy.review.refactor`
|
|
595
|
+
);
|
|
596
|
+
assertStageAgentPath(
|
|
597
|
+
strategy.issueFinalReview,
|
|
598
|
+
`targets[${targetIndex}].strategy.issueFinalReview`
|
|
599
|
+
);
|
|
600
|
+
assertStageAgentPath(
|
|
601
|
+
finalize.prDescriptionAgent,
|
|
602
|
+
`targets[${targetIndex}].strategy.finalize.prDescriptionAgent`
|
|
603
|
+
);
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
function assertKnownKeys(value, path9, knownKeys) {
|
|
607
|
+
for (const key of Object.keys(value)) {
|
|
608
|
+
if (!knownKeys.includes(key)) {
|
|
609
|
+
throw new Error(`${path9}.${key} is not supported`);
|
|
606
610
|
}
|
|
607
|
-
case z.ZodIssueCode.too_small:
|
|
608
|
-
if (issue.type === "string" && issue.minimum === 1) {
|
|
609
|
-
return `${path9} must be a non-empty string`;
|
|
610
|
-
}
|
|
611
|
-
if (issue.type === "array" && issue.minimum === 1) {
|
|
612
|
-
return `${path9} must not be empty`;
|
|
613
|
-
}
|
|
614
|
-
if (issue.type === "number") {
|
|
615
|
-
return `${path9} must be a positive number`;
|
|
616
|
-
}
|
|
617
|
-
return issue.message;
|
|
618
|
-
case z.ZodIssueCode.invalid_literal:
|
|
619
|
-
return `${path9} must be ${issue.expected}`;
|
|
620
|
-
case z.ZodIssueCode.unrecognized_keys:
|
|
621
|
-
const keyPath = path9 ? `${path9}.${issue.keys[0]}` : issue.keys[0];
|
|
622
|
-
return `${keyPath} is not supported`;
|
|
623
|
-
case z.ZodIssueCode.custom:
|
|
624
|
-
return path9 ? `${path9} ${issue.message}` : issue.message;
|
|
625
|
-
default:
|
|
626
|
-
return issue.message;
|
|
627
611
|
}
|
|
628
612
|
}
|
|
629
613
|
function parseConfig(raw) {
|
|
@@ -645,6 +629,7 @@ function parseConfig(raw) {
|
|
|
645
629
|
"name",
|
|
646
630
|
"baseBranch",
|
|
647
631
|
"branchTemplate",
|
|
632
|
+
"prdRun",
|
|
648
633
|
"setupCommands",
|
|
649
634
|
"autoMerge",
|
|
650
635
|
"queue",
|
|
@@ -660,9 +645,20 @@ function parseConfig(raw) {
|
|
|
660
645
|
`targets[${i}].strategy.conflictResolution has been removed; use targets[${i}].strategy.failureResolution`
|
|
661
646
|
);
|
|
662
647
|
}
|
|
663
|
-
if (strategy && typeof strategy === "object" && strategy.prdRun && typeof strategy.prdRun === "object"
|
|
648
|
+
if (strategy && typeof strategy === "object" && strategy.prdRun && typeof strategy.prdRun === "object") {
|
|
649
|
+
const prdRun = strategy.prdRun;
|
|
650
|
+
if ("reconciliation" in prdRun) {
|
|
651
|
+
throw new Error(
|
|
652
|
+
`targets[${i}].strategy.prdRun.reconciliation has been removed; PRD Run no longer invokes Architect reconciliation.`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
if ("finalReview" in prdRun) {
|
|
656
|
+
throw new Error(
|
|
657
|
+
`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.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
664
660
|
throw new Error(
|
|
665
|
-
`targets[${i}].strategy.prdRun
|
|
661
|
+
`targets[${i}].strategy.prdRun is not supported in the new config; use targets[${i}].prdRun instead.`
|
|
666
662
|
);
|
|
667
663
|
}
|
|
668
664
|
}
|
|
@@ -672,67 +668,126 @@ function parseConfig(raw) {
|
|
|
672
668
|
"copyToWorktree",
|
|
673
669
|
"mounts",
|
|
674
670
|
"env",
|
|
675
|
-
"idleTimeoutSeconds"
|
|
671
|
+
"idleTimeoutSeconds",
|
|
672
|
+
"forceRebuild"
|
|
676
673
|
]);
|
|
677
674
|
}
|
|
678
|
-
const
|
|
679
|
-
if (!
|
|
680
|
-
throw new Error(
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const verifyCommands = t.strategy.verify?.commands?.map((cmd, i) => ({
|
|
689
|
-
command: cmd.command,
|
|
690
|
-
label: cmd.label ?? `check-${i}`
|
|
691
|
-
}));
|
|
692
|
-
return {
|
|
693
|
-
name: t.name,
|
|
694
|
-
baseBranch: t.baseBranch,
|
|
695
|
-
branchTemplate: t.branchTemplate,
|
|
696
|
-
setupCommands,
|
|
697
|
-
autoMerge: t.autoMerge,
|
|
698
|
-
queue: t.queue,
|
|
699
|
-
serena: t.serena,
|
|
700
|
-
strategy: {
|
|
701
|
-
type: "review-refactor-loop",
|
|
702
|
-
implement: { builder: t.strategy.implement.builder },
|
|
703
|
-
failureResolution: {
|
|
704
|
-
agent: t.strategy.failureResolution.agent,
|
|
705
|
-
model: t.strategy.failureResolution.model,
|
|
706
|
-
promptTemplate: t.strategy.failureResolution.promptTemplate,
|
|
707
|
-
outputRetries: t.strategy.failureResolution.outputRetries,
|
|
708
|
-
maxAttemptsPerFailure: t.strategy.failureResolution.maxAttemptsPerFailure,
|
|
709
|
-
failureLimits: t.strategy.failureResolution.failureLimits
|
|
710
|
-
},
|
|
711
|
-
review: {
|
|
712
|
-
reviewer: t.strategy.review.reviewer,
|
|
713
|
-
refactor: t.strategy.review.refactor,
|
|
714
|
-
maxIterations: t.strategy.review.maxIterations,
|
|
715
|
-
passWithNotesRefactorAttempts: t.strategy.review.passWithNotesRefactorAttempts
|
|
716
|
-
},
|
|
717
|
-
...t.strategy.verify ? { verify: { commands: verifyCommands } } : {},
|
|
718
|
-
issueFinalReview: t.strategy.issueFinalReview,
|
|
719
|
-
finalize: {
|
|
720
|
-
prDescriptionAgent: t.strategy.finalize.prDescriptionAgent,
|
|
721
|
-
maxAttempts: t.strategy.finalize.maxAttempts
|
|
722
|
-
},
|
|
723
|
-
...t.strategy.prdRun ? {
|
|
724
|
-
prdRun: {
|
|
725
|
-
...t.strategy.prdRun.mode ? { mode: t.strategy.prdRun.mode } : {},
|
|
726
|
-
finalReview: t.strategy.prdRun.finalReview
|
|
675
|
+
const validate = getValidator();
|
|
676
|
+
if (!validate(raw)) {
|
|
677
|
+
throw new Error(
|
|
678
|
+
formatFirstAjvError(
|
|
679
|
+
_ajvErrors ?? [
|
|
680
|
+
{
|
|
681
|
+
instancePath: "",
|
|
682
|
+
message: "validation failed",
|
|
683
|
+
keyword: "error",
|
|
684
|
+
params: {}
|
|
727
685
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
686
|
+
]
|
|
687
|
+
)
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const data = config;
|
|
691
|
+
validateConfigSemantics(data);
|
|
692
|
+
const targets = data.targets.map(
|
|
693
|
+
(t) => {
|
|
694
|
+
const input = t;
|
|
695
|
+
const strategy = input.strategy;
|
|
696
|
+
const implement = strategy.implement;
|
|
697
|
+
const failureResolution = strategy.failureResolution;
|
|
698
|
+
const review = strategy.review;
|
|
699
|
+
const reviewReviewer = review.reviewer;
|
|
700
|
+
const reviewRefactor = review.refactor;
|
|
701
|
+
const finalize = strategy.finalize;
|
|
702
|
+
const issueFinalReview = strategy.issueFinalReview;
|
|
703
|
+
const setupCommands = input.setupCommands?.map((cmd, i) => ({
|
|
704
|
+
command: cmd.command,
|
|
705
|
+
label: cmd.label ?? `check-${i}`
|
|
706
|
+
}));
|
|
707
|
+
const verifyCommands = strategy.verify?.commands ? strategy.verify.commands : void 0;
|
|
708
|
+
const verifyLabeled = verifyCommands?.map((cmd, i) => ({
|
|
709
|
+
command: cmd.command,
|
|
710
|
+
label: cmd.label ?? `check-${i}`
|
|
711
|
+
}));
|
|
712
|
+
return {
|
|
713
|
+
name: input.name,
|
|
714
|
+
baseBranch: input.baseBranch,
|
|
715
|
+
branchTemplate: input.branchTemplate ?? DEFAULT_BRANCH_TEMPLATE,
|
|
716
|
+
prdRun: input.prdRun,
|
|
717
|
+
setupCommands,
|
|
718
|
+
autoMerge: input.autoMerge !== void 0 ? input.autoMerge : true,
|
|
719
|
+
queue: input.queue,
|
|
720
|
+
serena: input.serena,
|
|
721
|
+
strategy: {
|
|
722
|
+
type: "review-refactor-loop",
|
|
723
|
+
implement: {
|
|
724
|
+
builder: applyStageAgentDefaults(
|
|
725
|
+
implement.builder
|
|
726
|
+
)
|
|
727
|
+
},
|
|
728
|
+
failureResolution: {
|
|
729
|
+
agent: failureResolution.agent,
|
|
730
|
+
model: failureResolution.model,
|
|
731
|
+
variant: failureResolution.variant,
|
|
732
|
+
env: failureResolution.env,
|
|
733
|
+
promptTemplate: failureResolution.promptTemplate,
|
|
734
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
735
|
+
failureResolution.outputRetries
|
|
736
|
+
),
|
|
737
|
+
maxAttemptsPerFailure: failureResolution.maxAttemptsPerFailure,
|
|
738
|
+
failureLimits: failureResolution.failureLimits
|
|
739
|
+
},
|
|
740
|
+
review: {
|
|
741
|
+
reviewer: {
|
|
742
|
+
agent: reviewReviewer.agent,
|
|
743
|
+
model: reviewReviewer.model,
|
|
744
|
+
variant: reviewReviewer.variant,
|
|
745
|
+
env: reviewReviewer.env,
|
|
746
|
+
promptTemplate: reviewReviewer.promptTemplate,
|
|
747
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
748
|
+
reviewReviewer.outputRetries
|
|
749
|
+
),
|
|
750
|
+
criteria: reviewReviewer.criteria,
|
|
751
|
+
includeReviewHistory: reviewReviewer.includeReviewHistory,
|
|
752
|
+
passWithNotesRefactorAttempts: reviewReviewer.passWithNotesRefactorAttempts
|
|
753
|
+
},
|
|
754
|
+
refactor: applyStageAgentDefaults(
|
|
755
|
+
reviewRefactor
|
|
756
|
+
),
|
|
757
|
+
maxIterations: review.maxIterations,
|
|
758
|
+
passWithNotesRefactorAttempts: review.passWithNotesRefactorAttempts ?? 2
|
|
759
|
+
},
|
|
760
|
+
...verifyLabeled ? { verify: { commands: verifyLabeled } } : {},
|
|
761
|
+
issueFinalReview: {
|
|
762
|
+
...issueFinalReview,
|
|
763
|
+
maxAttempts: issueFinalReview.maxAttempts,
|
|
764
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
765
|
+
issueFinalReview.outputRetries
|
|
766
|
+
)
|
|
767
|
+
},
|
|
768
|
+
finalize: {
|
|
769
|
+
prDescriptionAgent: applyStageAgentDefaults(
|
|
770
|
+
finalize.prDescriptionAgent
|
|
771
|
+
),
|
|
772
|
+
maxAttempts: finalize.maxAttempts
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
);
|
|
778
|
+
const serenaRaw = data.serena;
|
|
779
|
+
const serenaDefaults = {
|
|
780
|
+
mcpUrl: serenaRaw?.mcpUrl ?? "http://localhost:9121/mcp",
|
|
781
|
+
sandboxMcpUrl: serenaRaw?.sandboxMcpUrl ?? "http://localhost:9121/mcp",
|
|
782
|
+
dataDir: serenaRaw?.dataDir ?? ".pourkit/serena/"
|
|
783
|
+
};
|
|
732
784
|
const serena = {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
785
|
+
enabled: serenaRaw?.enabled ?? false,
|
|
786
|
+
required: serenaRaw?.required ?? false,
|
|
787
|
+
mcpUrl: process.env.POURKIT_SERENA_MCP_URL ?? serenaDefaults.mcpUrl,
|
|
788
|
+
sandboxMcpUrl: process.env.POURKIT_SERENA_SANDBOX_MCP_URL ?? serenaDefaults.sandboxMcpUrl,
|
|
789
|
+
dataDir: serenaDefaults.dataDir,
|
|
790
|
+
autoStart: serenaRaw?.autoStart ?? false
|
|
736
791
|
};
|
|
737
792
|
if (serena.mcpUrl.trim() === "") {
|
|
738
793
|
throw new Error("POURKIT_SERENA_MCP_URL must be a non-empty string");
|
|
@@ -742,39 +797,48 @@ function parseConfig(raw) {
|
|
|
742
797
|
"POURKIT_SERENA_SANDBOX_MCP_URL must be a non-empty string"
|
|
743
798
|
);
|
|
744
799
|
}
|
|
800
|
+
const checksRaw = data.checks;
|
|
801
|
+
const labelsRaw = data.labels;
|
|
802
|
+
const cleanupRaw = data.cleanup;
|
|
803
|
+
const sandboxRaw = data.sandbox;
|
|
745
804
|
return {
|
|
746
805
|
targets,
|
|
747
|
-
labels:
|
|
806
|
+
labels: {
|
|
807
|
+
readyForAgent: labelsRaw?.readyForAgent ?? "ready-for-agent",
|
|
808
|
+
agentInProgress: labelsRaw?.agentInProgress ?? "agent-in-progress",
|
|
809
|
+
blocked: labelsRaw?.blocked ?? "blocked",
|
|
810
|
+
prOpenAwaitingMerge: labelsRaw?.prOpenAwaitingMerge ?? "pr-open-awaiting-merge",
|
|
811
|
+
readyForHuman: labelsRaw?.readyForHuman ?? "ready-for-human",
|
|
812
|
+
needsTriage: labelsRaw?.needsTriage ?? "needs-triage"
|
|
813
|
+
},
|
|
748
814
|
sandbox: {
|
|
749
|
-
provider:
|
|
750
|
-
copyToWorktree:
|
|
751
|
-
mounts:
|
|
752
|
-
|
|
753
|
-
|
|
815
|
+
provider: sandboxRaw?.provider ?? "docker",
|
|
816
|
+
copyToWorktree: sandboxRaw?.copyToWorktree,
|
|
817
|
+
mounts: sandboxRaw?.mounts ? sandboxRaw.mounts.map((m) => ({
|
|
818
|
+
hostPath: m.hostPath,
|
|
819
|
+
sandboxPath: m.sandboxPath,
|
|
820
|
+
readonly: m.readonly ?? false
|
|
821
|
+
})) : void 0,
|
|
822
|
+
env: sandboxRaw?.env,
|
|
823
|
+
idleTimeoutSeconds: sandboxRaw?.idleTimeoutSeconds,
|
|
824
|
+
forceRebuild: sandboxRaw?.forceRebuild
|
|
754
825
|
},
|
|
755
826
|
checks: {
|
|
756
|
-
requiredLabels:
|
|
757
|
-
allowedAuthors:
|
|
758
|
-
checksFoundTimeoutSeconds:
|
|
759
|
-
checksCompletionTimeoutSeconds:
|
|
760
|
-
pollIntervalSeconds:
|
|
761
|
-
issueListLimit:
|
|
827
|
+
requiredLabels: checksRaw?.requiredLabels ?? [],
|
|
828
|
+
allowedAuthors: checksRaw?.allowedAuthors ?? [],
|
|
829
|
+
checksFoundTimeoutSeconds: checksRaw?.checksFoundTimeoutSeconds ?? 60,
|
|
830
|
+
checksCompletionTimeoutSeconds: checksRaw?.checksCompletionTimeoutSeconds ?? 30 * 60,
|
|
831
|
+
pollIntervalSeconds: checksRaw?.pollIntervalSeconds ?? 15,
|
|
832
|
+
issueListLimit: checksRaw?.issueListLimit ?? 50
|
|
762
833
|
},
|
|
763
834
|
serena,
|
|
764
835
|
cleanup: {
|
|
765
|
-
enabled:
|
|
766
|
-
worktreeRetentionDays:
|
|
767
|
-
logRetentionDays:
|
|
836
|
+
enabled: cleanupRaw?.enabled ?? true,
|
|
837
|
+
worktreeRetentionDays: cleanupRaw?.worktreeRetentionDays ?? 14,
|
|
838
|
+
logRetentionDays: cleanupRaw?.logRetentionDays ?? 30
|
|
768
839
|
}
|
|
769
840
|
};
|
|
770
841
|
}
|
|
771
|
-
function assertKnownKeys(value, path9, knownKeys) {
|
|
772
|
-
for (const key of Object.keys(value)) {
|
|
773
|
-
if (!knownKeys.includes(key)) {
|
|
774
|
-
throw new Error(`${path9}.${key} is not supported`);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
842
|
function getVerificationCommands(target) {
|
|
779
843
|
return target.strategy.verify?.commands ?? [];
|
|
780
844
|
}
|
|
@@ -782,7 +846,7 @@ function resolvePrdRunMode(target, opts) {
|
|
|
782
846
|
if (opts?.localOverride === true) {
|
|
783
847
|
return { mode: "local", source: "cli-override", targetName: target.name };
|
|
784
848
|
}
|
|
785
|
-
const configMode = target.
|
|
849
|
+
const configMode = target.prdRun?.mode;
|
|
786
850
|
if (configMode) {
|
|
787
851
|
return {
|
|
788
852
|
mode: configMode,
|
|
@@ -792,48 +856,41 @@ function resolvePrdRunMode(target, opts) {
|
|
|
792
856
|
}
|
|
793
857
|
return { mode: "github", source: "default", targetName: target.name };
|
|
794
858
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
859
|
+
var OBSOLETE_CONFIG_PATHS = [
|
|
860
|
+
"pourkit.config.ts",
|
|
861
|
+
"pourkit.config.mjs",
|
|
862
|
+
"pourkit.config.js",
|
|
863
|
+
"pourkit.json"
|
|
864
|
+
];
|
|
865
|
+
var CANONICAL_CONFIG_PATH = ".pourkit/config.json";
|
|
866
|
+
async function loadRepoConfig(repoRoot2, _configFileName) {
|
|
867
|
+
const { existsSync: existsSync20 } = await import("fs");
|
|
868
|
+
const { join: pjoin } = await import("path");
|
|
869
|
+
for (const obPath of OBSOLETE_CONFIG_PATHS) {
|
|
870
|
+
const fullPath = pjoin(repoRoot2, obPath);
|
|
871
|
+
if (existsSync20(fullPath)) {
|
|
872
|
+
const isRootJson = obPath === "pourkit.json";
|
|
873
|
+
throw new Error(
|
|
874
|
+
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".`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const configPath = pjoin(repoRoot2, CANONICAL_CONFIG_PATH);
|
|
879
|
+
if (!existsSync20(configPath)) {
|
|
803
880
|
throw new Error(
|
|
804
|
-
`No config
|
|
881
|
+
`No Pourkit config found at ${CANONICAL_CONFIG_PATH}. Run pourkit init or create ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
|
|
805
882
|
);
|
|
806
883
|
}
|
|
807
|
-
const
|
|
808
|
-
await
|
|
809
|
-
|
|
810
|
-
tmpDir,
|
|
811
|
-
`pourkit-config-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.mjs`
|
|
812
|
-
);
|
|
884
|
+
const { readFile: readFile7 } = await import("fs/promises");
|
|
885
|
+
const raw = await readFile7(configPath, "utf-8");
|
|
886
|
+
let parsed;
|
|
813
887
|
try {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
platform: "node",
|
|
819
|
-
format: "esm",
|
|
820
|
-
external: ["node:*"]
|
|
821
|
-
}).then(async (result) => {
|
|
822
|
-
const output = result.outputFiles[0].text;
|
|
823
|
-
await writeFile4(tmpFile, output, "utf-8");
|
|
824
|
-
});
|
|
825
|
-
const imported = await import(pathToFileURL2(tmpFile).href);
|
|
826
|
-
const raw = imported.default;
|
|
827
|
-
if (raw === void 0) {
|
|
828
|
-
throw new Error("pourkit.config.ts must have a default export");
|
|
829
|
-
}
|
|
830
|
-
return parseConfig(raw);
|
|
831
|
-
} finally {
|
|
832
|
-
try {
|
|
833
|
-
await rm(tmpFile, { force: true });
|
|
834
|
-
} catch {
|
|
835
|
-
}
|
|
888
|
+
parsed = JSON.parse(raw);
|
|
889
|
+
} catch (err) {
|
|
890
|
+
const message = err instanceof SyntaxError ? err.message : String(err);
|
|
891
|
+
throw new Error(`${CANONICAL_CONFIG_PATH}: Invalid JSON \u2014 ${message}`);
|
|
836
892
|
}
|
|
893
|
+
return parseConfig(parsed);
|
|
837
894
|
}
|
|
838
895
|
function resolvePromptTemplatePath(repoRoot2, promptTemplate) {
|
|
839
896
|
if (promptTemplate.includes("/")) {
|
|
@@ -872,8 +929,8 @@ function resolveTarget(config, explicitTarget) {
|
|
|
872
929
|
init_common();
|
|
873
930
|
|
|
874
931
|
// shared/worktree-run-state.ts
|
|
875
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
876
|
-
import { dirname, join as join2 } from "path";
|
|
932
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
933
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
877
934
|
var WORKTREE_RUN_STATE_PATH = ".pourkit/state.json";
|
|
878
935
|
function readWorktreeRunState(worktreePath) {
|
|
879
936
|
const statePath = join2(worktreePath, WORKTREE_RUN_STATE_PATH);
|
|
@@ -881,7 +938,7 @@ function readWorktreeRunState(worktreePath) {
|
|
|
881
938
|
return null;
|
|
882
939
|
}
|
|
883
940
|
try {
|
|
884
|
-
const raw = JSON.parse(
|
|
941
|
+
const raw = JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
885
942
|
if (isValidWorktreeRunState(raw)) {
|
|
886
943
|
return raw;
|
|
887
944
|
}
|
|
@@ -902,7 +959,7 @@ function isValidWorktreeRunState(raw) {
|
|
|
902
959
|
}
|
|
903
960
|
function writeWorktreeRunState(worktreePath, state) {
|
|
904
961
|
const statePath = join2(worktreePath, WORKTREE_RUN_STATE_PATH);
|
|
905
|
-
mkdirSync2(
|
|
962
|
+
mkdirSync2(dirname2(statePath), { recursive: true });
|
|
906
963
|
writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
907
964
|
}
|
|
908
965
|
function updateWorktreeRunState(worktreePath, update) {
|
|
@@ -1049,8 +1106,8 @@ async function cleanupRepository(options) {
|
|
|
1049
1106
|
// commands/artifact-validation.ts
|
|
1050
1107
|
import { createHash } from "crypto";
|
|
1051
1108
|
import { execSync } from "child_process";
|
|
1052
|
-
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as
|
|
1053
|
-
import { isAbsolute as
|
|
1109
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
|
|
1110
|
+
import { isAbsolute as isAbsolute3, join as join7, resolve as resolve3 } from "path";
|
|
1054
1111
|
|
|
1055
1112
|
// pr/review-verdict.ts
|
|
1056
1113
|
var ReviewVerdictProtocolError = class extends Error {
|
|
@@ -1085,15 +1142,15 @@ function parseReviewVerdict(output) {
|
|
|
1085
1142
|
import {
|
|
1086
1143
|
existsSync as existsSync5,
|
|
1087
1144
|
mkdirSync as mkdirSync5,
|
|
1088
|
-
readFileSync as
|
|
1145
|
+
readFileSync as readFileSync6,
|
|
1089
1146
|
readdirSync as readdirSync2,
|
|
1090
1147
|
writeFileSync as writeFileSync3
|
|
1091
1148
|
} from "fs";
|
|
1092
1149
|
import { join as join6 } from "path";
|
|
1093
1150
|
|
|
1094
1151
|
// execution/agent-output-retry.ts
|
|
1095
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as
|
|
1096
|
-
import { dirname as
|
|
1152
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
1153
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
1097
1154
|
async function executeWithMissingOrEmptyArtifactRetry({
|
|
1098
1155
|
executionProvider,
|
|
1099
1156
|
executionOptions,
|
|
@@ -1153,20 +1210,20 @@ function readArtifactOutput(artifactPath) {
|
|
|
1153
1210
|
if (!existsSync2(artifactPath)) {
|
|
1154
1211
|
return { _tag: "missing", path: artifactPath };
|
|
1155
1212
|
}
|
|
1156
|
-
const output =
|
|
1213
|
+
const output = readFileSync3(artifactPath, "utf-8");
|
|
1157
1214
|
if (!output.trim()) {
|
|
1158
1215
|
return { _tag: "empty", path: artifactPath };
|
|
1159
1216
|
}
|
|
1160
1217
|
return { _tag: "content", value: output, path: artifactPath };
|
|
1161
1218
|
}
|
|
1162
1219
|
function prepareArtifactPath(artifactPath) {
|
|
1163
|
-
mkdirSync3(
|
|
1220
|
+
mkdirSync3(dirname3(artifactPath), { recursive: true });
|
|
1164
1221
|
rmSync(artifactPath, { recursive: true, force: true });
|
|
1165
1222
|
}
|
|
1166
1223
|
|
|
1167
1224
|
// shared/run-context.ts
|
|
1168
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1169
|
-
import { isAbsolute, join as join5, relative, resolve } from "path";
|
|
1225
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
1226
|
+
import { isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve2 } from "path";
|
|
1170
1227
|
|
|
1171
1228
|
// commands/run-verification.ts
|
|
1172
1229
|
init_common();
|
|
@@ -1372,11 +1429,17 @@ function buildRunContextMarkdown(options) {
|
|
|
1372
1429
|
}
|
|
1373
1430
|
}
|
|
1374
1431
|
if (sections.includes("branch")) {
|
|
1432
|
+
const canonicalBaseRef = `origin/${target.baseBranch}`;
|
|
1375
1433
|
parts.push(
|
|
1376
1434
|
"## Branch",
|
|
1377
1435
|
"",
|
|
1378
1436
|
`- Base: ${target.baseBranch}`,
|
|
1437
|
+
`- Canonical Base Ref: ${canonicalBaseRef}`,
|
|
1379
1438
|
`- Working Branch: ${branchName}`,
|
|
1439
|
+
"",
|
|
1440
|
+
"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.",
|
|
1441
|
+
"",
|
|
1442
|
+
"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.",
|
|
1380
1443
|
""
|
|
1381
1444
|
);
|
|
1382
1445
|
}
|
|
@@ -1439,7 +1502,7 @@ function renderPrdContext(issue, parentPrdIssue, repoRoot2) {
|
|
|
1439
1502
|
`### Parent PRD Content: \`${relative(repoRoot2, parentPrdPath)}\``,
|
|
1440
1503
|
"",
|
|
1441
1504
|
"```markdown",
|
|
1442
|
-
|
|
1505
|
+
readFileSync4(parentPrdPath, "utf-8").trimEnd(),
|
|
1443
1506
|
"```",
|
|
1444
1507
|
""
|
|
1445
1508
|
);
|
|
@@ -1466,7 +1529,7 @@ function renderPrdContext(issue, parentPrdIssue, repoRoot2) {
|
|
|
1466
1529
|
`### Document Content: \`${documentPath}\``,
|
|
1467
1530
|
"",
|
|
1468
1531
|
"```markdown",
|
|
1469
|
-
|
|
1532
|
+
readFileSync4(absolutePath, "utf-8").trimEnd(),
|
|
1470
1533
|
"```",
|
|
1471
1534
|
""
|
|
1472
1535
|
);
|
|
@@ -1491,10 +1554,10 @@ function extractRepoPaths(section) {
|
|
|
1491
1554
|
return Array.from(paths);
|
|
1492
1555
|
}
|
|
1493
1556
|
function resolveRepoPath(repoRoot2, path9) {
|
|
1494
|
-
if (
|
|
1495
|
-
const resolved =
|
|
1557
|
+
if (isAbsolute2(path9) || path9.includes("\0")) return null;
|
|
1558
|
+
const resolved = resolve2(repoRoot2, path9);
|
|
1496
1559
|
const repoRelative2 = relative(repoRoot2, resolved);
|
|
1497
|
-
if (repoRelative2.startsWith("..") ||
|
|
1560
|
+
if (repoRelative2.startsWith("..") || isAbsolute2(repoRelative2)) return null;
|
|
1498
1561
|
return resolved;
|
|
1499
1562
|
}
|
|
1500
1563
|
function findParentPrdPath(repoRoot2, parentRef) {
|
|
@@ -1538,17 +1601,19 @@ function renderCriteria(criteria) {
|
|
|
1538
1601
|
|
|
1539
1602
|
// shared/prompt-guidance.ts
|
|
1540
1603
|
var PROTECTED_WORK_RULE = "Do **not** revert, delete, or substantially strip already-landed protected sibling/base work unless the issue explicitly requires those files.";
|
|
1604
|
+
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.";
|
|
1541
1605
|
function appendProtectedWorkGuidance(promptBody) {
|
|
1542
1606
|
return `${promptBody}
|
|
1543
1607
|
|
|
1544
1608
|
## Hard Rule
|
|
1545
1609
|
|
|
1546
|
-
- ${PROTECTED_WORK_RULE}
|
|
1610
|
+
- ${PROTECTED_WORK_RULE}
|
|
1611
|
+
- ${RUNNER_OWNED_GIT_RULE}`;
|
|
1547
1612
|
}
|
|
1548
1613
|
|
|
1549
1614
|
// shared/effect-services.ts
|
|
1550
1615
|
import { Context, Effect, Layer } from "effect";
|
|
1551
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as
|
|
1616
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
|
|
1552
1617
|
var GitExecutionError = class extends Error {
|
|
1553
1618
|
_tag = "GitExecutionError";
|
|
1554
1619
|
message;
|
|
@@ -1564,7 +1629,7 @@ var FileSystemDefault = Layer.succeed(
|
|
|
1564
1629
|
FileSystem,
|
|
1565
1630
|
FileSystem.of({
|
|
1566
1631
|
readFile: (path9) => Effect.try({
|
|
1567
|
-
try: () =>
|
|
1632
|
+
try: () => readFileSync5(path9, "utf-8"),
|
|
1568
1633
|
catch: (error) => new Error(
|
|
1569
1634
|
`Failed to read file ${path9}: ${error instanceof Error ? error.message : String(error)}`
|
|
1570
1635
|
)
|
|
@@ -1965,7 +2030,7 @@ function validateRefactorArtifact(artifactPath, findingIds) {
|
|
|
1965
2030
|
`Refactor artifact missing at ${artifactPath}`
|
|
1966
2031
|
);
|
|
1967
2032
|
}
|
|
1968
|
-
const content =
|
|
2033
|
+
const content = readFileSync6(artifactPath, "utf-8");
|
|
1969
2034
|
if (!content.trim()) {
|
|
1970
2035
|
throw new RefactorArtifactValidationError("Refactor artifact is empty");
|
|
1971
2036
|
}
|
|
@@ -2290,12 +2355,29 @@ A prior review emitted \`NEEDS_HUMAN\` and stopped the agent loop. The issue has
|
|
|
2290
2355
|
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.
|
|
2291
2356
|
|
|
2292
2357
|
` : "";
|
|
2293
|
-
return `${renderedTemplate}
|
|
2358
|
+
return appendProtectedWorkGuidance(`${renderedTemplate}
|
|
2294
2359
|
|
|
2295
2360
|
## Shared Run Context
|
|
2296
2361
|
|
|
2297
2362
|
Read the selected issue requirements, PRD context, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
2298
2363
|
|
|
2364
|
+
## Initial Verification Pass
|
|
2365
|
+
|
|
2366
|
+
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
2367
|
+
- Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
|
|
2368
|
+
- Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
|
|
2369
|
+
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
2370
|
+
- 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.
|
|
2371
|
+
- If no verification commands are configured, note that and proceed with normal review.
|
|
2372
|
+
|
|
2373
|
+
## Scope Evidence Rules
|
|
2374
|
+
|
|
2375
|
+
- Use the Run Context's canonical base ref, for example \`origin/<base>\`, for scope diffs and commit ranges.
|
|
2376
|
+
- 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.
|
|
2377
|
+
- Only call a file or commit out of scope when it is part of the working branch's delta from the canonical base ref.
|
|
2378
|
+
- 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.
|
|
2379
|
+
- If scope evidence is ambiguous, use \`NEEDS_HUMAN\` or ask for a runner/base mismatch decision instead of telling Refactor to revert files.
|
|
2380
|
+
|
|
2299
2381
|
${hasCriteriaPlaceholder ? "" : `## Review Criteria
|
|
2300
2382
|
|
|
2301
2383
|
${criteriaBlock}
|
|
@@ -2312,7 +2394,7 @@ End the file with exactly one wrapped verdict token: <verdict>PASS</verdict>, <v
|
|
|
2312
2394
|
|
|
2313
2395
|
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).
|
|
2314
2396
|
|
|
2315
|
-
When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token
|
|
2397
|
+
When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token.`);
|
|
2316
2398
|
});
|
|
2317
2399
|
}
|
|
2318
2400
|
function renderReviewHistory(reviewHistory) {
|
|
@@ -2437,12 +2519,12 @@ function recoverReviewOutputFromLog(logPath) {
|
|
|
2437
2519
|
if (!existsSync5(logPath)) {
|
|
2438
2520
|
return null;
|
|
2439
2521
|
}
|
|
2440
|
-
const logContent =
|
|
2522
|
+
const logContent = readFileSync6(logPath, "utf-8");
|
|
2441
2523
|
return recoverReviewOutputFromString(logContent);
|
|
2442
2524
|
}
|
|
2443
2525
|
function readReviewArtifact(artifactPath, logPath) {
|
|
2444
2526
|
if (existsSync5(artifactPath)) {
|
|
2445
|
-
const output =
|
|
2527
|
+
const output = readFileSync6(artifactPath, "utf-8");
|
|
2446
2528
|
if (output.trim()) {
|
|
2447
2529
|
return output;
|
|
2448
2530
|
}
|
|
@@ -3092,7 +3174,7 @@ function parseConflictResolutionArtifact(output) {
|
|
|
3092
3174
|
}
|
|
3093
3175
|
|
|
3094
3176
|
// prd-run/final-review-validation.ts
|
|
3095
|
-
import { existsSync as existsSync6, readFileSync as
|
|
3177
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
3096
3178
|
function parseFinalReviewArtifact(artifactPath) {
|
|
3097
3179
|
if (!existsSync6(artifactPath)) {
|
|
3098
3180
|
return {
|
|
@@ -3103,7 +3185,7 @@ function parseFinalReviewArtifact(artifactPath) {
|
|
|
3103
3185
|
}
|
|
3104
3186
|
let content;
|
|
3105
3187
|
try {
|
|
3106
|
-
content =
|
|
3188
|
+
content = readFileSync7(artifactPath, "utf-8");
|
|
3107
3189
|
} catch (error) {
|
|
3108
3190
|
return {
|
|
3109
3191
|
ok: false,
|
|
@@ -3306,7 +3388,7 @@ function readArtifact(options) {
|
|
|
3306
3388
|
};
|
|
3307
3389
|
}
|
|
3308
3390
|
try {
|
|
3309
|
-
const content =
|
|
3391
|
+
const content = readFileSync8(options.artifactPath, "utf-8");
|
|
3310
3392
|
if (!content.trim()) {
|
|
3311
3393
|
return { ok: false, result: invalid(options, "Artifact is empty") };
|
|
3312
3394
|
}
|
|
@@ -3376,7 +3458,7 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3376
3458
|
for (const p of parsed.changedPaths) {
|
|
3377
3459
|
const normalized = p.replace(/\\/g, "/");
|
|
3378
3460
|
const segments = normalized.split("/");
|
|
3379
|
-
if (normalized.trim() === "" || normalized === "." || normalized === ".." ||
|
|
3461
|
+
if (normalized.trim() === "" || normalized === "." || normalized === ".." || isAbsolute3(p) || normalized.startsWith("/") || segments.some((segment) => segment === "..")) {
|
|
3380
3462
|
return {
|
|
3381
3463
|
ok: false,
|
|
3382
3464
|
reason: `changedPaths must not contain absolute paths or path traversal: ${p}`,
|
|
@@ -3477,7 +3559,7 @@ function validateAgentArtifact(options) {
|
|
|
3477
3559
|
case "refactor": {
|
|
3478
3560
|
let findingIds = options.findingIds ?? [];
|
|
3479
3561
|
if (findingIds.length === 0 && options.latestReviewArtifactPath) {
|
|
3480
|
-
const latestReview =
|
|
3562
|
+
const latestReview = readFileSync8(
|
|
3481
3563
|
options.latestReviewArtifactPath,
|
|
3482
3564
|
"utf-8"
|
|
3483
3565
|
);
|
|
@@ -3500,10 +3582,10 @@ function validateAgentArtifact(options) {
|
|
|
3500
3582
|
if (options.checkConflictMarkers !== false && parsed.status === "resolved") {
|
|
3501
3583
|
const base = options.worktreePath ?? process.cwd();
|
|
3502
3584
|
const filesWithMarkers = parsed.files.filter((file) => {
|
|
3503
|
-
const filePath =
|
|
3585
|
+
const filePath = resolve3(base, file);
|
|
3504
3586
|
try {
|
|
3505
3587
|
return CONFLICT_MARKER_PATTERN.test(
|
|
3506
|
-
|
|
3588
|
+
readFileSync8(filePath, "utf-8")
|
|
3507
3589
|
);
|
|
3508
3590
|
} catch {
|
|
3509
3591
|
return false;
|
|
@@ -3608,8 +3690,8 @@ function validateAgentArtifact(options) {
|
|
|
3608
3690
|
}
|
|
3609
3691
|
}
|
|
3610
3692
|
function runValidateArtifactCommand(options) {
|
|
3611
|
-
const artifactPath =
|
|
3612
|
-
const latestReviewArtifactPath = options.latestReviewArtifactPath ?
|
|
3693
|
+
const artifactPath = resolve3(options.repoRoot, options.artifactPath);
|
|
3694
|
+
const latestReviewArtifactPath = options.latestReviewArtifactPath ? resolve3(options.repoRoot, options.latestReviewArtifactPath) : void 0;
|
|
3613
3695
|
return validateAgentArtifact({
|
|
3614
3696
|
...options,
|
|
3615
3697
|
artifactPath,
|
|
@@ -3618,9 +3700,9 @@ function runValidateArtifactCommand(options) {
|
|
|
3618
3700
|
});
|
|
3619
3701
|
}
|
|
3620
3702
|
function runLocalValidateArtifactCommand(options) {
|
|
3621
|
-
const resolvedPath =
|
|
3703
|
+
const resolvedPath = resolve3(options.repoRoot, options.artifactPath);
|
|
3622
3704
|
const resolvedExtra = (options.extraArgs ?? []).map(
|
|
3623
|
-
(p) =>
|
|
3705
|
+
(p) => isAbsolute3(p) ? p : resolve3(options.repoRoot, p)
|
|
3624
3706
|
);
|
|
3625
3707
|
let localResult;
|
|
3626
3708
|
switch (options.kind) {
|
|
@@ -3667,7 +3749,7 @@ function runLocalValidateArtifactCommand(options) {
|
|
|
3667
3749
|
}
|
|
3668
3750
|
function readLocalArtifact(path9, failureCode) {
|
|
3669
3751
|
try {
|
|
3670
|
-
const raw =
|
|
3752
|
+
const raw = readFileSync8(path9, "utf-8");
|
|
3671
3753
|
const parsed = JSON.parse(raw);
|
|
3672
3754
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3673
3755
|
return {
|
|
@@ -4366,8 +4448,8 @@ function validateLocalTriage(prdPath, issuePaths) {
|
|
|
4366
4448
|
}
|
|
4367
4449
|
|
|
4368
4450
|
// commands/issue-run.ts
|
|
4369
|
-
import { existsSync as existsSync12, readFileSync as
|
|
4370
|
-
import { isAbsolute as
|
|
4451
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
4452
|
+
import { isAbsolute as isAbsolute4, join as join15 } from "path";
|
|
4371
4453
|
|
|
4372
4454
|
// pr/templates.ts
|
|
4373
4455
|
init_common();
|
|
@@ -4502,12 +4584,12 @@ function runEffectAndMapExit(program) {
|
|
|
4502
4584
|
}
|
|
4503
4585
|
|
|
4504
4586
|
// shared/attempt-log.ts
|
|
4505
|
-
import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as
|
|
4506
|
-
import { dirname as
|
|
4587
|
+
import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync9 } from "fs";
|
|
4588
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
4507
4589
|
var ATTEMPT_LOG_PATH = ".pourkit/attempt-log.jsonl";
|
|
4508
4590
|
function writeAttemptLog(worktreePath, entry) {
|
|
4509
4591
|
const logPath = join8(worktreePath, ATTEMPT_LOG_PATH);
|
|
4510
|
-
mkdirSync6(
|
|
4592
|
+
mkdirSync6(dirname4(logPath), { recursive: true });
|
|
4511
4593
|
appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
4512
4594
|
}
|
|
4513
4595
|
function readAttemptLog(worktreePath) {
|
|
@@ -4515,7 +4597,7 @@ function readAttemptLog(worktreePath) {
|
|
|
4515
4597
|
if (!existsSync8(logPath)) {
|
|
4516
4598
|
return [];
|
|
4517
4599
|
}
|
|
4518
|
-
const raw =
|
|
4600
|
+
const raw = readFileSync9(logPath, "utf-8");
|
|
4519
4601
|
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
4520
4602
|
const entries = [];
|
|
4521
4603
|
for (const line of lines) {
|
|
@@ -4634,7 +4716,7 @@ async function runBaseRefreshAttempt(options) {
|
|
|
4634
4716
|
}
|
|
4635
4717
|
|
|
4636
4718
|
// commands/conflict-resolution.ts
|
|
4637
|
-
import { existsSync as existsSync9, readFileSync as
|
|
4719
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
4638
4720
|
import { join as join9 } from "path";
|
|
4639
4721
|
init_common();
|
|
4640
4722
|
var CONFLICT_MARKER_PATTERN2 = /<<<<<<<|=======|>>>>>>>/m;
|
|
@@ -4642,7 +4724,7 @@ async function hasUnresolvedConflictMarkers(worktreePath, files) {
|
|
|
4642
4724
|
for (const file of files) {
|
|
4643
4725
|
const filePath = join9(worktreePath, file);
|
|
4644
4726
|
try {
|
|
4645
|
-
const content =
|
|
4727
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4646
4728
|
if (CONFLICT_MARKER_PATTERN2.test(content)) {
|
|
4647
4729
|
return true;
|
|
4648
4730
|
}
|
|
@@ -4922,7 +5004,7 @@ async function canReachMcp(url) {
|
|
|
4922
5004
|
return true;
|
|
4923
5005
|
} catch {
|
|
4924
5006
|
if (attempt < 9) {
|
|
4925
|
-
await new Promise((
|
|
5007
|
+
await new Promise((resolve5) => setTimeout(resolve5, 100));
|
|
4926
5008
|
}
|
|
4927
5009
|
}
|
|
4928
5010
|
}
|
|
@@ -4973,7 +5055,7 @@ async function prepareSerenaForTarget(options) {
|
|
|
4973
5055
|
}
|
|
4974
5056
|
|
|
4975
5057
|
// failure-resolution/failure-resolution-agent.ts
|
|
4976
|
-
import { readFileSync as
|
|
5058
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4977
5059
|
import { join as join10 } from "path";
|
|
4978
5060
|
|
|
4979
5061
|
// failure-resolution/recovery-policy.ts
|
|
@@ -5127,7 +5209,7 @@ async function runFailureResolutionAgent(options) {
|
|
|
5127
5209
|
}
|
|
5128
5210
|
let artifact;
|
|
5129
5211
|
try {
|
|
5130
|
-
const md =
|
|
5212
|
+
const md = readFileSync11(fullArtifactPath, "utf-8");
|
|
5131
5213
|
const validation2 = validateAgentArtifact({
|
|
5132
5214
|
kind: "failure-resolution",
|
|
5133
5215
|
artifactPath: fullArtifactPath,
|
|
@@ -5221,7 +5303,7 @@ async function writeRecoveryAttempt(worktreePath, outcome, fingerprint, summary,
|
|
|
5221
5303
|
|
|
5222
5304
|
// commands/pr-description-agent.ts
|
|
5223
5305
|
import { join as join12 } from "path";
|
|
5224
|
-
import { readFileSync as
|
|
5306
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
5225
5307
|
|
|
5226
5308
|
// pr/pr-description-context.ts
|
|
5227
5309
|
init_common();
|
|
@@ -5415,7 +5497,7 @@ function runFinalizerAgent(options) {
|
|
|
5415
5497
|
],
|
|
5416
5498
|
logger: options.logger
|
|
5417
5499
|
});
|
|
5418
|
-
const output =
|
|
5500
|
+
const output = readFileSync12(artifactPath, "utf-8");
|
|
5419
5501
|
yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
|
|
5420
5502
|
return result;
|
|
5421
5503
|
});
|
|
@@ -5543,7 +5625,7 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
|
5543
5625
|
|
|
5544
5626
|
// prd-run/local-merge-coordinator.ts
|
|
5545
5627
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5546
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as
|
|
5628
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
|
|
5547
5629
|
import { join as join13 } from "path";
|
|
5548
5630
|
|
|
5549
5631
|
// prd-run/local-branches.ts
|
|
@@ -5646,7 +5728,7 @@ function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
|
5646
5728
|
const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
|
|
5647
5729
|
if (!existsSync10(issuePath)) return null;
|
|
5648
5730
|
try {
|
|
5649
|
-
const content =
|
|
5731
|
+
const content = readFileSync13(issuePath, "utf-8");
|
|
5650
5732
|
const parsed = JSON.parse(content);
|
|
5651
5733
|
return typeof parsed.branchName === "string" && parsed.branchName ? parsed.branchName : null;
|
|
5652
5734
|
} catch {
|
|
@@ -5658,7 +5740,7 @@ async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
|
5658
5740
|
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
5659
5741
|
if (!existsSync10(receiptPath)) return null;
|
|
5660
5742
|
try {
|
|
5661
|
-
const content =
|
|
5743
|
+
const content = readFileSync13(receiptPath, "utf-8");
|
|
5662
5744
|
const parsed = JSON.parse(content);
|
|
5663
5745
|
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") {
|
|
5664
5746
|
return parsed;
|
|
@@ -5674,7 +5756,7 @@ async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
|
|
|
5674
5756
|
if (existsSync10(receiptPath)) {
|
|
5675
5757
|
try {
|
|
5676
5758
|
const existing = JSON.parse(
|
|
5677
|
-
|
|
5759
|
+
readFileSync13(receiptPath, "utf-8")
|
|
5678
5760
|
);
|
|
5679
5761
|
if (existing.prdId === prdId && existing.issueId === issueId && existing.mergeCommit) {
|
|
5680
5762
|
return {
|
|
@@ -6083,7 +6165,7 @@ function runMergeCoordinator(options) {
|
|
|
6083
6165
|
}
|
|
6084
6166
|
|
|
6085
6167
|
// commands/issue-final-review-agent.ts
|
|
6086
|
-
import { existsSync as existsSync11, readFileSync as
|
|
6168
|
+
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
6087
6169
|
import { join as join14 } from "path";
|
|
6088
6170
|
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join14(
|
|
6089
6171
|
".pourkit",
|
|
@@ -6197,12 +6279,21 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
6197
6279
|
}
|
|
6198
6280
|
function loadIssueFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
6199
6281
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6200
|
-
const promptBody = existsSync11(promptPath) ?
|
|
6282
|
+
const promptBody = existsSync11(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
|
|
6201
6283
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
6202
6284
|
|
|
6203
6285
|
## Shared Run Context
|
|
6204
6286
|
|
|
6205
|
-
Read the selected issue requirements, PRD context, comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
6287
|
+
Read the selected issue requirements, PRD context, comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
6288
|
+
|
|
6289
|
+
## Initial Verification Pass
|
|
6290
|
+
|
|
6291
|
+
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
6292
|
+
- Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
|
|
6293
|
+
- Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
|
|
6294
|
+
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
6295
|
+
- 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.
|
|
6296
|
+
- If no verification commands are configured, note that and proceed with normal review.`);
|
|
6206
6297
|
}
|
|
6207
6298
|
|
|
6208
6299
|
// issues/issue-transitions.ts
|
|
@@ -6405,7 +6496,7 @@ async function advanceIssueFinalReview(options) {
|
|
|
6405
6496
|
"Issue Final Review state is incomplete: missing artifactPath"
|
|
6406
6497
|
);
|
|
6407
6498
|
}
|
|
6408
|
-
const artifactPath =
|
|
6499
|
+
const artifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join15(worktreePath, ifrFromState.artifactPath);
|
|
6409
6500
|
const validation = validateAgentArtifact({
|
|
6410
6501
|
kind: "issue-final-review",
|
|
6411
6502
|
artifactPath,
|
|
@@ -6437,6 +6528,12 @@ async function advanceIssueFinalReview(options) {
|
|
|
6437
6528
|
logger,
|
|
6438
6529
|
reviewArtifactPath
|
|
6439
6530
|
});
|
|
6531
|
+
await assertCanonicalBaseAncestor({
|
|
6532
|
+
worktreePath,
|
|
6533
|
+
baseRef: `origin/${target.baseBranch}`,
|
|
6534
|
+
stageName: "Issue Final Review",
|
|
6535
|
+
logger
|
|
6536
|
+
});
|
|
6440
6537
|
if (result.verdict === "pass") {
|
|
6441
6538
|
updateWorktreeRunState(worktreePath, {
|
|
6442
6539
|
issueFinalReview: {
|
|
@@ -6661,6 +6758,14 @@ async function startIssueRun(options) {
|
|
|
6661
6758
|
};
|
|
6662
6759
|
}
|
|
6663
6760
|
if (executionResult.worktreePath) {
|
|
6761
|
+
if (shouldRunBuilder) {
|
|
6762
|
+
await assertCanonicalBaseAncestor({
|
|
6763
|
+
worktreePath: executionResult.worktreePath,
|
|
6764
|
+
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6765
|
+
stageName: "Builder",
|
|
6766
|
+
logger
|
|
6767
|
+
});
|
|
6768
|
+
}
|
|
6664
6769
|
if (worktreeState) {
|
|
6665
6770
|
updateWorktreeRunState(executionResult.worktreePath, {
|
|
6666
6771
|
completedStages: { builder: true }
|
|
@@ -6722,6 +6827,12 @@ async function advanceIssueRunReview(options) {
|
|
|
6722
6827
|
}
|
|
6723
6828
|
})
|
|
6724
6829
|
);
|
|
6830
|
+
await assertCanonicalBaseAncestor({
|
|
6831
|
+
worktreePath: options.worktreePath,
|
|
6832
|
+
baseRef: `origin/${options.target.baseBranch}`,
|
|
6833
|
+
stageName: "Review/Refactor",
|
|
6834
|
+
logger: options.logger
|
|
6835
|
+
});
|
|
6725
6836
|
updateWorktreeRunState(options.worktreePath, {
|
|
6726
6837
|
review: {
|
|
6727
6838
|
lifetimeIterations: reviewResult.lifetimeIterations,
|
|
@@ -6790,7 +6901,7 @@ async function completeIssueRun(options) {
|
|
|
6790
6901
|
message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
|
|
6791
6902
|
});
|
|
6792
6903
|
}
|
|
6793
|
-
const artifactContent =
|
|
6904
|
+
const artifactContent = readFileSync15(
|
|
6794
6905
|
finalizerFromState.artifactPath,
|
|
6795
6906
|
"utf-8"
|
|
6796
6907
|
);
|
|
@@ -6848,6 +6959,14 @@ async function completeIssueRun(options) {
|
|
|
6848
6959
|
}
|
|
6849
6960
|
prTitle = finalizerResult.title;
|
|
6850
6961
|
prBody = finalizerResult.body;
|
|
6962
|
+
if (executionResult.worktreePath) {
|
|
6963
|
+
await assertCanonicalBaseAncestor({
|
|
6964
|
+
worktreePath: executionResult.worktreePath,
|
|
6965
|
+
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6966
|
+
stageName: "Finalizer",
|
|
6967
|
+
logger
|
|
6968
|
+
});
|
|
6969
|
+
}
|
|
6851
6970
|
}
|
|
6852
6971
|
prTitle = ensureConventionalPrTitle(
|
|
6853
6972
|
prTitle,
|
|
@@ -7216,18 +7335,12 @@ function getRefactorArtifactDir(artifactPath) {
|
|
|
7216
7335
|
}
|
|
7217
7336
|
async function finalizeWorktreeCommit(options) {
|
|
7218
7337
|
const { worktreePath, baseRef, title, body, logger } = options;
|
|
7219
|
-
await
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
});
|
|
7226
|
-
} catch {
|
|
7227
|
-
throw new Error(
|
|
7228
|
-
`Cannot finalize stale worktree: ${baseRef} is not an ancestor of HEAD. Refresh the branch onto the latest target base before creating the final commit.`
|
|
7229
|
-
);
|
|
7230
|
-
}
|
|
7338
|
+
await assertCanonicalBaseAncestor({
|
|
7339
|
+
worktreePath,
|
|
7340
|
+
baseRef,
|
|
7341
|
+
stageName: "final commit",
|
|
7342
|
+
logger
|
|
7343
|
+
});
|
|
7231
7344
|
await execCapture("git", ["reset", "--soft", baseRef], {
|
|
7232
7345
|
cwd: worktreePath,
|
|
7233
7346
|
logger,
|
|
@@ -7244,6 +7357,21 @@ async function finalizeWorktreeCommit(options) {
|
|
|
7244
7357
|
label: "git commit"
|
|
7245
7358
|
});
|
|
7246
7359
|
}
|
|
7360
|
+
async function assertCanonicalBaseAncestor(options) {
|
|
7361
|
+
const { worktreePath, baseRef, stageName, logger } = options;
|
|
7362
|
+
await syncRemoteBaseRef(worktreePath, baseRef, logger);
|
|
7363
|
+
try {
|
|
7364
|
+
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
7365
|
+
cwd: worktreePath,
|
|
7366
|
+
logger,
|
|
7367
|
+
label: "git merge-base --is-ancestor"
|
|
7368
|
+
});
|
|
7369
|
+
} catch {
|
|
7370
|
+
throw new Error(
|
|
7371
|
+
`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.`
|
|
7372
|
+
);
|
|
7373
|
+
}
|
|
7374
|
+
}
|
|
7247
7375
|
async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
|
|
7248
7376
|
const remoteBase = parseRemoteBaseRef(baseRef);
|
|
7249
7377
|
if (!remoteBase) {
|
|
@@ -7487,7 +7615,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
|
|
|
7487
7615
|
}
|
|
7488
7616
|
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
7489
7617
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
7490
|
-
const promptBody = existsSync12(promptPath) ?
|
|
7618
|
+
const promptBody = existsSync12(promptPath) ? readFileSync15(promptPath, "utf-8") : promptTemplate;
|
|
7491
7619
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
7492
7620
|
|
|
7493
7621
|
## Shared Run Context
|
|
@@ -7984,12 +8112,12 @@ import {
|
|
|
7984
8112
|
lstatSync,
|
|
7985
8113
|
mkdirSync as mkdirSync10,
|
|
7986
8114
|
mkdtempSync,
|
|
7987
|
-
readFileSync as
|
|
8115
|
+
readFileSync as readFileSync18,
|
|
7988
8116
|
realpathSync,
|
|
7989
8117
|
rmSync as rmSync3
|
|
7990
8118
|
} from "fs";
|
|
7991
8119
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
7992
|
-
import { dirname as
|
|
8120
|
+
import { dirname as dirname5, join as join20, relative as relative2 } from "path";
|
|
7993
8121
|
import { tmpdir } from "os";
|
|
7994
8122
|
import { Match, pipe } from "effect";
|
|
7995
8123
|
|
|
@@ -7997,20 +8125,20 @@ import { Match, pipe } from "effect";
|
|
|
7997
8125
|
import {
|
|
7998
8126
|
existsSync as existsSync13,
|
|
7999
8127
|
mkdirSync as mkdirSync8,
|
|
8000
|
-
readFileSync as
|
|
8128
|
+
readFileSync as readFileSync16,
|
|
8001
8129
|
readdirSync as readdirSync4,
|
|
8002
8130
|
writeFileSync as writeFileSync5
|
|
8003
8131
|
} from "fs";
|
|
8004
8132
|
import { mkdir as mkdir4, readFile as readFile4, writeFile } from "fs/promises";
|
|
8005
8133
|
import { join as join16 } from "path";
|
|
8006
|
-
import { z
|
|
8134
|
+
import { z } from "zod";
|
|
8007
8135
|
var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
|
|
8008
|
-
var PrdRunRecordSchema =
|
|
8009
|
-
prdRef:
|
|
8136
|
+
var PrdRunRecordSchema = z.object({
|
|
8137
|
+
prdRef: z.string().regex(
|
|
8010
8138
|
/^PRD-\d{4}$/,
|
|
8011
8139
|
"PRD ref must use four-digit format (e.g., PRD-0052)"
|
|
8012
8140
|
),
|
|
8013
|
-
status:
|
|
8141
|
+
status: z.enum([
|
|
8014
8142
|
"blocked",
|
|
8015
8143
|
"starting",
|
|
8016
8144
|
"running",
|
|
@@ -8022,61 +8150,61 @@ var PrdRunRecordSchema = z2.object({
|
|
|
8022
8150
|
"complete",
|
|
8023
8151
|
"completed_local_branch"
|
|
8024
8152
|
]),
|
|
8025
|
-
updatedAt:
|
|
8026
|
-
mode:
|
|
8027
|
-
blockedGate:
|
|
8028
|
-
targetName:
|
|
8029
|
-
prdBranch:
|
|
8030
|
-
blockedReason:
|
|
8031
|
-
diagnostics:
|
|
8032
|
-
offendingPaths:
|
|
8033
|
-
start:
|
|
8034
|
-
status:
|
|
8035
|
-
targetName:
|
|
8036
|
-
prdBranch:
|
|
8037
|
-
startBaseBranch:
|
|
8038
|
-
startBaseCommit:
|
|
8039
|
-
branchAction:
|
|
8040
|
-
startedAt:
|
|
8041
|
-
queueStartedAt:
|
|
8042
|
-
queueDrainedAt:
|
|
8043
|
-
queueProcessedCount:
|
|
8044
|
-
queueCommand:
|
|
8045
|
-
refreshReceipts:
|
|
8153
|
+
updatedAt: z.string().min(1),
|
|
8154
|
+
mode: z.enum(["github", "local"]).optional(),
|
|
8155
|
+
blockedGate: z.enum(["receipt-check", "branch-state", "git", "queue", "final-review"]).optional(),
|
|
8156
|
+
targetName: z.string().min(1).optional(),
|
|
8157
|
+
prdBranch: z.string().min(1).optional(),
|
|
8158
|
+
blockedReason: z.string().min(1).optional(),
|
|
8159
|
+
diagnostics: z.array(z.string()).optional(),
|
|
8160
|
+
offendingPaths: z.array(z.string()).optional(),
|
|
8161
|
+
start: z.object({
|
|
8162
|
+
status: z.enum(["started", "succeeded", "adopted", "reused"]),
|
|
8163
|
+
targetName: z.string().min(1),
|
|
8164
|
+
prdBranch: z.string().min(1),
|
|
8165
|
+
startBaseBranch: z.string().min(1),
|
|
8166
|
+
startBaseCommit: z.string().min(1).optional(),
|
|
8167
|
+
branchAction: z.enum(["created", "reused", "adopted"]).optional(),
|
|
8168
|
+
startedAt: z.string().min(1).optional(),
|
|
8169
|
+
queueStartedAt: z.string().min(1).optional(),
|
|
8170
|
+
queueDrainedAt: z.string().min(1).optional(),
|
|
8171
|
+
queueProcessedCount: z.number().int().nonnegative().optional(),
|
|
8172
|
+
queueCommand: z.literal("queue-run").optional(),
|
|
8173
|
+
refreshReceipts: z.tuple([]).optional()
|
|
8046
8174
|
}).strict().optional(),
|
|
8047
|
-
finalReview:
|
|
8048
|
-
status:
|
|
8175
|
+
finalReview: z.object({
|
|
8176
|
+
status: z.enum([
|
|
8049
8177
|
"started",
|
|
8050
8178
|
"succeeded",
|
|
8051
8179
|
"blocked",
|
|
8052
8180
|
"needs_human_review",
|
|
8053
8181
|
"final_reviewed"
|
|
8054
8182
|
]),
|
|
8055
|
-
targetName:
|
|
8056
|
-
prdBranch:
|
|
8057
|
-
mergeBase:
|
|
8058
|
-
verdict:
|
|
8183
|
+
targetName: z.string().min(1),
|
|
8184
|
+
prdBranch: z.string().min(1),
|
|
8185
|
+
mergeBase: z.string().min(1).optional(),
|
|
8186
|
+
verdict: z.enum([
|
|
8059
8187
|
"pass_no_changes",
|
|
8060
8188
|
"pass_with_retouch",
|
|
8061
8189
|
"needs_human_review",
|
|
8062
8190
|
"blocked"
|
|
8063
8191
|
]).optional(),
|
|
8064
|
-
artifactPath:
|
|
8065
|
-
diagnostics:
|
|
8066
|
-
reviewedAt:
|
|
8067
|
-
retouchPrNumber:
|
|
8068
|
-
retouchPrUrl:
|
|
8069
|
-
retouchMergeCommit:
|
|
8070
|
-
autoMerge:
|
|
8071
|
-
changedPaths:
|
|
8192
|
+
artifactPath: z.string().min(1).optional(),
|
|
8193
|
+
diagnostics: z.array(z.string()).optional(),
|
|
8194
|
+
reviewedAt: z.string().min(1).optional(),
|
|
8195
|
+
retouchPrNumber: z.number().int().positive().optional(),
|
|
8196
|
+
retouchPrUrl: z.string().url().optional(),
|
|
8197
|
+
retouchMergeCommit: z.string().min(1).optional(),
|
|
8198
|
+
autoMerge: z.boolean().optional(),
|
|
8199
|
+
changedPaths: z.array(z.string()).optional()
|
|
8072
8200
|
}).strict().optional(),
|
|
8073
|
-
scopeChanges:
|
|
8074
|
-
|
|
8075
|
-
issueNumber:
|
|
8076
|
-
decision:
|
|
8077
|
-
reason:
|
|
8078
|
-
acceptedBy:
|
|
8079
|
-
acceptedAt:
|
|
8201
|
+
scopeChanges: z.array(
|
|
8202
|
+
z.object({
|
|
8203
|
+
issueNumber: z.number().int().positive(),
|
|
8204
|
+
decision: z.literal("accepted_scope_change"),
|
|
8205
|
+
reason: z.string().min(1),
|
|
8206
|
+
acceptedBy: z.string().min(1),
|
|
8207
|
+
acceptedAt: z.string().min(1)
|
|
8080
8208
|
}).strict()
|
|
8081
8209
|
).optional()
|
|
8082
8210
|
}).strict();
|
|
@@ -8097,12 +8225,12 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
8097
8225
|
return { record: null, diagnostics: [] };
|
|
8098
8226
|
}
|
|
8099
8227
|
try {
|
|
8100
|
-
const raw = JSON.parse(
|
|
8228
|
+
const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
|
|
8101
8229
|
const parsed = normalizeLegacyPrdRunRecord(PrdRunRecordSchema.parse(raw));
|
|
8102
8230
|
return { record: parsed, diagnostics: [] };
|
|
8103
8231
|
} catch (error) {
|
|
8104
8232
|
try {
|
|
8105
|
-
const raw = JSON.parse(
|
|
8233
|
+
const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
|
|
8106
8234
|
if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
|
|
8107
8235
|
return {
|
|
8108
8236
|
record: raw,
|
|
@@ -8136,7 +8264,7 @@ function listPrdRuns(repoRoot2) {
|
|
|
8136
8264
|
const recordPath = join16(stateDir, entry.name);
|
|
8137
8265
|
try {
|
|
8138
8266
|
const record = normalizeLegacyPrdRunRecord(
|
|
8139
|
-
PrdRunRecordSchema.parse(JSON.parse(
|
|
8267
|
+
PrdRunRecordSchema.parse(JSON.parse(readFileSync16(recordPath, "utf-8")))
|
|
8140
8268
|
);
|
|
8141
8269
|
records.push(record);
|
|
8142
8270
|
} catch (error) {
|
|
@@ -8168,40 +8296,40 @@ function normalizeLegacyPrdRunRecord(record) {
|
|
|
8168
8296
|
offendingPaths: void 0
|
|
8169
8297
|
};
|
|
8170
8298
|
}
|
|
8171
|
-
var LocalPrdRunRecordSchema =
|
|
8172
|
-
prdId:
|
|
8299
|
+
var LocalPrdRunRecordSchema = z.object({
|
|
8300
|
+
prdId: z.string().regex(
|
|
8173
8301
|
/^PRD-\d{4}$/,
|
|
8174
8302
|
"PRD id must use four-digit format (e.g., PRD-0052)"
|
|
8175
8303
|
),
|
|
8176
|
-
createdAt:
|
|
8177
|
-
receipts:
|
|
8178
|
-
start:
|
|
8179
|
-
startedAt:
|
|
8180
|
-
branch:
|
|
8181
|
-
baseCommit:
|
|
8304
|
+
createdAt: z.string().min(1),
|
|
8305
|
+
receipts: z.object({
|
|
8306
|
+
start: z.object({
|
|
8307
|
+
startedAt: z.string().min(1),
|
|
8308
|
+
branch: z.string().min(1),
|
|
8309
|
+
baseCommit: z.string().min(1)
|
|
8182
8310
|
}).strict().optional(),
|
|
8183
|
-
queue:
|
|
8184
|
-
finalReview:
|
|
8185
|
-
completedAt:
|
|
8186
|
-
targetName:
|
|
8187
|
-
prdBranch:
|
|
8188
|
-
mergeBase:
|
|
8189
|
-
verdict:
|
|
8311
|
+
queue: z.object({ completedAt: z.string().min(1) }).strict().optional(),
|
|
8312
|
+
finalReview: z.object({
|
|
8313
|
+
completedAt: z.string().min(1),
|
|
8314
|
+
targetName: z.string().optional(),
|
|
8315
|
+
prdBranch: z.string().optional(),
|
|
8316
|
+
mergeBase: z.string().optional(),
|
|
8317
|
+
verdict: z.enum([
|
|
8190
8318
|
"pass_no_changes",
|
|
8191
8319
|
"pass_with_retouch",
|
|
8192
8320
|
"needs_human_review",
|
|
8193
8321
|
"blocked"
|
|
8194
8322
|
]).optional(),
|
|
8195
|
-
diagnostics:
|
|
8196
|
-
artifactPath:
|
|
8323
|
+
diagnostics: z.array(z.string()).optional(),
|
|
8324
|
+
artifactPath: z.string().optional()
|
|
8197
8325
|
}).strict().optional(),
|
|
8198
|
-
complete:
|
|
8199
|
-
completedAt:
|
|
8200
|
-
branch:
|
|
8201
|
-
stages:
|
|
8326
|
+
complete: z.object({
|
|
8327
|
+
completedAt: z.string().min(1),
|
|
8328
|
+
branch: z.string().min(1),
|
|
8329
|
+
stages: z.array(z.string())
|
|
8202
8330
|
}).strict().optional()
|
|
8203
8331
|
}).strict(),
|
|
8204
|
-
metadata:
|
|
8332
|
+
metadata: z.record(z.unknown())
|
|
8205
8333
|
}).strict();
|
|
8206
8334
|
function getRecordPath(repoRoot2, prdRef) {
|
|
8207
8335
|
return join16(
|
|
@@ -8213,20 +8341,20 @@ function getRecordPath(repoRoot2, prdRef) {
|
|
|
8213
8341
|
|
|
8214
8342
|
// prd-run/evidence-packet.ts
|
|
8215
8343
|
import { createHash as createHash2 } from "crypto";
|
|
8216
|
-
import { z as
|
|
8344
|
+
import { z as z2 } from "zod";
|
|
8217
8345
|
var PRD_REF_REGEX2 = /^PRD-\d{3,4}$/;
|
|
8218
|
-
var StageSchema =
|
|
8219
|
-
var EvidencePacketSchema =
|
|
8220
|
-
prdRef:
|
|
8221
|
-
prdBranch:
|
|
8222
|
-
mergeBase:
|
|
8223
|
-
planningManifestPath:
|
|
8224
|
-
planningManifestFacts:
|
|
8225
|
-
parentPrdIssueUrl:
|
|
8226
|
-
childIssueCount:
|
|
8346
|
+
var StageSchema = z2.enum(["prdFinalReview", "prdReconciliation"]);
|
|
8347
|
+
var EvidencePacketSchema = z2.object({
|
|
8348
|
+
prdRef: z2.string().regex(PRD_REF_REGEX2),
|
|
8349
|
+
prdBranch: z2.string().min(1),
|
|
8350
|
+
mergeBase: z2.string().min(6),
|
|
8351
|
+
planningManifestPath: z2.string().min(1),
|
|
8352
|
+
planningManifestFacts: z2.object({
|
|
8353
|
+
parentPrdIssueUrl: z2.string().min(1),
|
|
8354
|
+
childIssueCount: z2.number().int().nonnegative()
|
|
8227
8355
|
}),
|
|
8228
8356
|
stage: StageSchema,
|
|
8229
|
-
stageReceipts:
|
|
8357
|
+
stageReceipts: z2.record(z2.string(), z2.unknown())
|
|
8230
8358
|
}).strict();
|
|
8231
8359
|
|
|
8232
8360
|
// commands/prd-run.ts
|
|
@@ -8437,7 +8565,7 @@ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
|
8437
8565
|
import { join as join18 } from "path";
|
|
8438
8566
|
|
|
8439
8567
|
// prd-run/local-queue-loop.ts
|
|
8440
|
-
import { readFileSync as
|
|
8568
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync7 } from "fs";
|
|
8441
8569
|
import { join as join19 } from "path";
|
|
8442
8570
|
|
|
8443
8571
|
// prd-run/local-issue-run.ts
|
|
@@ -8574,7 +8702,7 @@ function getIssueArtifactPath2(repoRoot2, prdId, issueId) {
|
|
|
8574
8702
|
}
|
|
8575
8703
|
function readIssueArtifact(repoRoot2, prdId, issueId) {
|
|
8576
8704
|
try {
|
|
8577
|
-
const content =
|
|
8705
|
+
const content = readFileSync17(
|
|
8578
8706
|
getIssueArtifactPath2(repoRoot2, prdId, issueId),
|
|
8579
8707
|
"utf-8"
|
|
8580
8708
|
);
|
|
@@ -9316,7 +9444,7 @@ function validateLocalStartStore(repoRoot2, prdRef) {
|
|
|
9316
9444
|
if (existsSync16(localStoreDir)) {
|
|
9317
9445
|
const localStorePath = join20(localStoreDir, "prd.json");
|
|
9318
9446
|
try {
|
|
9319
|
-
const content = JSON.parse(
|
|
9447
|
+
const content = JSON.parse(readFileSync18(localStorePath, "utf8"));
|
|
9320
9448
|
localStoreReady = content?.id === prdRef && content?.kind === "prd";
|
|
9321
9449
|
} catch {
|
|
9322
9450
|
localStoreReady = false;
|
|
@@ -11115,135 +11243,120 @@ function inferVerificationCommands(scripts, pm) {
|
|
|
11115
11243
|
}
|
|
11116
11244
|
function generateConfigTemplate(options) {
|
|
11117
11245
|
const {
|
|
11118
|
-
sourceRoot,
|
|
11119
|
-
targetRoot,
|
|
11120
|
-
packageManager,
|
|
11121
11246
|
baseBranch,
|
|
11247
|
+
packageManager,
|
|
11122
11248
|
verificationCommands,
|
|
11123
11249
|
hasPackageJson = true,
|
|
11124
11250
|
labels: maybeLabels
|
|
11125
11251
|
} = options;
|
|
11126
11252
|
const labels = maybeLabels ?? DEFAULT_RUNNER_LABELS;
|
|
11127
|
-
const relPath = path5.relative(targetRoot, sourceRoot).replace(/\\/g, "/");
|
|
11128
|
-
const importPath = relPath || ".";
|
|
11129
11253
|
const setupCommand = `${packageManager} install`;
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11145
|
-
|
|
11146
|
-
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
" // },"
|
|
11157
|
-
].join("\n");
|
|
11158
|
-
}
|
|
11159
|
-
return `import { definePourkitConfig } from "${importPath}/pourkit/shared/config";
|
|
11160
|
-
import type { PourkitConfig } from "${importPath}/pourkit/shared/config";
|
|
11161
|
-
|
|
11162
|
-
export default definePourkitConfig({
|
|
11163
|
-
targets: [
|
|
11164
|
-
{
|
|
11165
|
-
name: "default",
|
|
11166
|
-
baseBranch: "${baseBranch}",
|
|
11167
|
-
branchTemplate: "pourkit/{{issue.number}}/{{issue.slug}}",
|
|
11168
|
-
autoMerge: false,
|
|
11169
|
-
${setupSection}
|
|
11170
|
-
strategy: {
|
|
11171
|
-
type: "review-refactor-loop",
|
|
11172
|
-
implement: {
|
|
11173
|
-
builder: {
|
|
11174
|
-
agent: "pourkit-builder",
|
|
11175
|
-
model: "opencode-go/deepseek-v4-flash",
|
|
11176
|
-
promptTemplate: ".pourkit/prompts/builder.prompt.md",
|
|
11177
|
-
},
|
|
11178
|
-
},
|
|
11179
|
-
review: {
|
|
11180
|
-
reviewer: {
|
|
11181
|
-
agent: "pourkit-reviewer",
|
|
11182
|
-
model: "opencode-go/deepseek-v4-pro",
|
|
11183
|
-
promptTemplate: ".pourkit/prompts/reviewer.prompt.md",
|
|
11184
|
-
criteria: ["correctness", "scope", "tests", "quality"],
|
|
11185
|
-
},
|
|
11186
|
-
refactor: {
|
|
11187
|
-
agent: "pourkit-refactor",
|
|
11188
|
-
model: "opencode-go/qwen3.6-plus",
|
|
11189
|
-
promptTemplate: ".pourkit/prompts/refactor.prompt.md",
|
|
11190
|
-
},
|
|
11191
|
-
maxIterations: 3,
|
|
11192
|
-
passWithNotesRefactorAttempts: 2,
|
|
11254
|
+
const target = {
|
|
11255
|
+
name: "default",
|
|
11256
|
+
baseBranch,
|
|
11257
|
+
branchTemplate: "pourkit/{{issue.number}}/{{issue.slug}}",
|
|
11258
|
+
autoMerge: false,
|
|
11259
|
+
strategy: {
|
|
11260
|
+
type: "review-refactor-loop",
|
|
11261
|
+
implement: {
|
|
11262
|
+
builder: {
|
|
11263
|
+
agent: "pourkit-builder",
|
|
11264
|
+
model: "opencode-go/deepseek-v4-flash",
|
|
11265
|
+
promptTemplate: ".pourkit/prompts/builder.prompt.md"
|
|
11266
|
+
}
|
|
11267
|
+
},
|
|
11268
|
+
failureResolution: {
|
|
11269
|
+
agent: "pourkit-failure-resolution-agent",
|
|
11270
|
+
model: "opencode-go/mimo-v2.5",
|
|
11271
|
+
promptTemplate: ".pourkit/prompts/failure-resolution.prompt.md",
|
|
11272
|
+
maxAttemptsPerFailure: 1
|
|
11273
|
+
},
|
|
11274
|
+
review: {
|
|
11275
|
+
reviewer: {
|
|
11276
|
+
agent: "pourkit-reviewer",
|
|
11277
|
+
model: "opencode-go/deepseek-v4-pro",
|
|
11278
|
+
promptTemplate: ".pourkit/prompts/reviewer.prompt.md",
|
|
11279
|
+
criteria: ["correctness", "scope", "tests", "quality"]
|
|
11193
11280
|
},
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
model: "opencode-go/deepseek-v4-flash",
|
|
11199
|
-
promptTemplate: ".pourkit/prompts/pr-description.prompt.md",
|
|
11200
|
-
},
|
|
11201
|
-
maxAttempts: 2,
|
|
11281
|
+
refactor: {
|
|
11282
|
+
agent: "pourkit-refactor",
|
|
11283
|
+
model: "opencode-go/qwen3.6-plus",
|
|
11284
|
+
promptTemplate: ".pourkit/prompts/refactor.prompt.md"
|
|
11202
11285
|
},
|
|
11286
|
+
maxIterations: 3,
|
|
11287
|
+
passWithNotesRefactorAttempts: 2
|
|
11203
11288
|
},
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
blocked: ${JSON.stringify(labels.blocked)},
|
|
11210
|
-
prOpenAwaitingMerge: ${JSON.stringify(labels.prOpenAwaitingMerge)},
|
|
11211
|
-
readyForHuman: ${JSON.stringify(labels.readyForHuman)},
|
|
11212
|
-
},
|
|
11213
|
-
sandbox: {
|
|
11214
|
-
provider: "docker",
|
|
11215
|
-
copyToWorktree: ["node_modules"],
|
|
11216
|
-
mounts: [
|
|
11217
|
-
{
|
|
11218
|
-
hostPath: "~/.local/share/opencode",
|
|
11219
|
-
sandboxPath: "/home/agent/.local/share/opencode",
|
|
11220
|
-
readonly: false,
|
|
11289
|
+
issueFinalReview: {
|
|
11290
|
+
agent: "pourkit-reviewer",
|
|
11291
|
+
model: "opencode-go/deepseek-v4-pro",
|
|
11292
|
+
promptTemplate: ".pourkit/prompts/issue-final-review.prompt.md",
|
|
11293
|
+
maxAttempts: 3
|
|
11221
11294
|
},
|
|
11222
|
-
{
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11295
|
+
finalize: {
|
|
11296
|
+
prDescriptionAgent: {
|
|
11297
|
+
agent: "pourkit-pr-description",
|
|
11298
|
+
model: "opencode-go/deepseek-v4-flash",
|
|
11299
|
+
promptTemplate: ".pourkit/prompts/pr-description.prompt.md"
|
|
11300
|
+
},
|
|
11301
|
+
maxAttempts: 2
|
|
11302
|
+
}
|
|
11303
|
+
}
|
|
11304
|
+
};
|
|
11305
|
+
if (hasPackageJson) {
|
|
11306
|
+
target.setupCommands = [
|
|
11307
|
+
{ command: setupCommand, label: "install" }
|
|
11308
|
+
];
|
|
11309
|
+
}
|
|
11310
|
+
if (verificationCommands.length > 0) {
|
|
11311
|
+
target.strategy.verify = {
|
|
11312
|
+
commands: verificationCommands
|
|
11313
|
+
};
|
|
11314
|
+
}
|
|
11315
|
+
const config = {
|
|
11316
|
+
$schema: "./schema/pourkit.schema.json",
|
|
11317
|
+
schemaVersion: 1,
|
|
11318
|
+
targets: [target],
|
|
11319
|
+
labels: {
|
|
11320
|
+
readyForAgent: labels.readyForAgent,
|
|
11321
|
+
agentInProgress: labels.agentInProgress,
|
|
11322
|
+
blocked: labels.blocked,
|
|
11323
|
+
prOpenAwaitingMerge: labels.prOpenAwaitingMerge,
|
|
11324
|
+
readyForHuman: labels.readyForHuman
|
|
11325
|
+
},
|
|
11326
|
+
sandbox: {
|
|
11327
|
+
provider: "docker",
|
|
11328
|
+
copyToWorktree: ["node_modules"],
|
|
11329
|
+
mounts: [
|
|
11330
|
+
{
|
|
11331
|
+
hostPath: "~/.local/share/opencode",
|
|
11332
|
+
sandboxPath: "/home/agent/.local/share/opencode",
|
|
11333
|
+
readonly: false
|
|
11334
|
+
},
|
|
11335
|
+
{
|
|
11336
|
+
hostPath: "~/.config/opencode",
|
|
11337
|
+
sandboxPath: "/home/agent/.config/opencode",
|
|
11338
|
+
readonly: true
|
|
11339
|
+
}
|
|
11340
|
+
],
|
|
11341
|
+
env: {
|
|
11342
|
+
HOME: "/home/agent",
|
|
11343
|
+
XDG_DATA_HOME: "/home/agent/.local/share",
|
|
11344
|
+
XDG_CONFIG_HOME: "/home/agent/.config",
|
|
11345
|
+
XDG_STATE_HOME: "/home/agent/.local/state",
|
|
11346
|
+
XDG_CACHE_HOME: "/home/agent/.cache"
|
|
11226
11347
|
},
|
|
11227
|
-
|
|
11228
|
-
env: {
|
|
11229
|
-
HOME: "/home/agent",
|
|
11230
|
-
XDG_DATA_HOME: "/home/agent/.local/share",
|
|
11231
|
-
XDG_CONFIG_HOME: "/home/agent/.config",
|
|
11232
|
-
XDG_STATE_HOME: "/home/agent/.local/state",
|
|
11233
|
-
XDG_CACHE_HOME: "/home/agent/.cache",
|
|
11348
|
+
idleTimeoutSeconds: 300
|
|
11234
11349
|
},
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
});
|
|
11246
|
-
`;
|
|
11350
|
+
checks: {
|
|
11351
|
+
requiredLabels: [],
|
|
11352
|
+
allowedAuthors: [],
|
|
11353
|
+
checksFoundTimeoutSeconds: 60,
|
|
11354
|
+
checksCompletionTimeoutSeconds: 1800,
|
|
11355
|
+
pollIntervalSeconds: 15,
|
|
11356
|
+
issueListLimit: 50
|
|
11357
|
+
}
|
|
11358
|
+
};
|
|
11359
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
11247
11360
|
}
|
|
11248
11361
|
function generateOpenCodeConfig(existingConfig = {}) {
|
|
11249
11362
|
const { $schema, ...rest } = existingConfig;
|
|
@@ -11976,15 +12089,13 @@ async function planInit(options) {
|
|
|
11976
12089
|
checksum
|
|
11977
12090
|
});
|
|
11978
12091
|
}
|
|
11979
|
-
const
|
|
11980
|
-
if (!existsSync17(
|
|
12092
|
+
const configJsonPath = path5.join(targetRoot, ".pourkit", "config.json");
|
|
12093
|
+
if (!existsSync17(configJsonPath)) {
|
|
11981
12094
|
const verifyCommands = inferVerificationCommands(
|
|
11982
12095
|
packageScripts,
|
|
11983
12096
|
pm || "npm"
|
|
11984
12097
|
);
|
|
11985
12098
|
const configContent = generateConfigTemplate({
|
|
11986
|
-
targetRoot,
|
|
11987
|
-
sourceRoot,
|
|
11988
12099
|
packageManager: pm || "npm",
|
|
11989
12100
|
baseBranch,
|
|
11990
12101
|
verificationCommands: verifyCommands,
|
|
@@ -11993,9 +12104,9 @@ async function planInit(options) {
|
|
|
11993
12104
|
});
|
|
11994
12105
|
operations.push({
|
|
11995
12106
|
kind: "create",
|
|
11996
|
-
path:
|
|
12107
|
+
path: configJsonPath,
|
|
11997
12108
|
ownership: "managed",
|
|
11998
|
-
reason: "Init pourkit
|
|
12109
|
+
reason: "Init .pourkit/config.json template",
|
|
11999
12110
|
requiresConfirmation: false,
|
|
12000
12111
|
destructive: false,
|
|
12001
12112
|
content: configContent
|
|
@@ -12003,13 +12114,85 @@ async function planInit(options) {
|
|
|
12003
12114
|
} else {
|
|
12004
12115
|
operations.push({
|
|
12005
12116
|
kind: "skip",
|
|
12006
|
-
path:
|
|
12117
|
+
path: configJsonPath,
|
|
12007
12118
|
ownership: "project-owned",
|
|
12008
|
-
reason: "Existing pourkit
|
|
12119
|
+
reason: "Existing .pourkit/config.json (project-owned)",
|
|
12009
12120
|
requiresConfirmation: false,
|
|
12010
12121
|
destructive: false
|
|
12011
12122
|
});
|
|
12012
12123
|
}
|
|
12124
|
+
const schemaJsonPath = path5.join(
|
|
12125
|
+
targetRoot,
|
|
12126
|
+
".pourkit",
|
|
12127
|
+
"schema",
|
|
12128
|
+
"pourkit.schema.json"
|
|
12129
|
+
);
|
|
12130
|
+
const srcSchemaJson = path5.join(
|
|
12131
|
+
sourceRoot,
|
|
12132
|
+
"pourkit",
|
|
12133
|
+
"schema",
|
|
12134
|
+
"pourkit.schema.json"
|
|
12135
|
+
);
|
|
12136
|
+
if (existsSync17(srcSchemaJson)) {
|
|
12137
|
+
const checksum = await computeFileChecksum(srcSchemaJson);
|
|
12138
|
+
if (!existsSync17(schemaJsonPath)) {
|
|
12139
|
+
operations.push({
|
|
12140
|
+
kind: "copy",
|
|
12141
|
+
sourcePath: srcSchemaJson,
|
|
12142
|
+
path: schemaJsonPath,
|
|
12143
|
+
ownership: "managed",
|
|
12144
|
+
reason: "Copy schema: pourkit.schema.json",
|
|
12145
|
+
requiresConfirmation: false,
|
|
12146
|
+
destructive: false,
|
|
12147
|
+
checksum
|
|
12148
|
+
});
|
|
12149
|
+
} else {
|
|
12150
|
+
operations.push({
|
|
12151
|
+
kind: "skip",
|
|
12152
|
+
path: schemaJsonPath,
|
|
12153
|
+
ownership: "project-owned",
|
|
12154
|
+
reason: "Existing .pourkit/schema/pourkit.schema.json (project-owned)",
|
|
12155
|
+
requiresConfirmation: false,
|
|
12156
|
+
destructive: false
|
|
12157
|
+
});
|
|
12158
|
+
}
|
|
12159
|
+
}
|
|
12160
|
+
const schemaHashPath = path5.join(
|
|
12161
|
+
targetRoot,
|
|
12162
|
+
".pourkit",
|
|
12163
|
+
"schema",
|
|
12164
|
+
"pourkit.schema.hash"
|
|
12165
|
+
);
|
|
12166
|
+
const srcSchemaHash = path5.join(
|
|
12167
|
+
sourceRoot,
|
|
12168
|
+
"pourkit",
|
|
12169
|
+
"schema",
|
|
12170
|
+
"pourkit.schema.hash"
|
|
12171
|
+
);
|
|
12172
|
+
if (existsSync17(srcSchemaHash)) {
|
|
12173
|
+
const checksum = await computeFileChecksum(srcSchemaHash);
|
|
12174
|
+
if (!existsSync17(schemaHashPath)) {
|
|
12175
|
+
operations.push({
|
|
12176
|
+
kind: "copy",
|
|
12177
|
+
sourcePath: srcSchemaHash,
|
|
12178
|
+
path: schemaHashPath,
|
|
12179
|
+
ownership: "managed",
|
|
12180
|
+
reason: "Copy schema hash: pourkit.schema.hash",
|
|
12181
|
+
requiresConfirmation: false,
|
|
12182
|
+
destructive: false,
|
|
12183
|
+
checksum
|
|
12184
|
+
});
|
|
12185
|
+
} else {
|
|
12186
|
+
operations.push({
|
|
12187
|
+
kind: "skip",
|
|
12188
|
+
path: schemaHashPath,
|
|
12189
|
+
ownership: "project-owned",
|
|
12190
|
+
reason: "Existing .pourkit/schema/pourkit.schema.hash (project-owned)",
|
|
12191
|
+
requiresConfirmation: false,
|
|
12192
|
+
destructive: false
|
|
12193
|
+
});
|
|
12194
|
+
}
|
|
12195
|
+
}
|
|
12013
12196
|
const agentFileMode = options.conflictPolicy?.agentFile ?? "both";
|
|
12014
12197
|
const hasExistingAgents = operations.some(
|
|
12015
12198
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
|
|
@@ -12994,6 +13177,165 @@ async function runSerenaStatusCommand(options) {
|
|
|
12994
13177
|
logSerenaSidecarStatus("Serena sidecar status", status);
|
|
12995
13178
|
}
|
|
12996
13179
|
|
|
13180
|
+
// commands/config-schema.ts
|
|
13181
|
+
import { readFileSync as readFileSync19, existsSync as existsSync18 } from "fs";
|
|
13182
|
+
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
|
|
13183
|
+
import { resolve as resolve4, dirname as dirname6 } from "path";
|
|
13184
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13185
|
+
import Ajv2 from "ajv";
|
|
13186
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
13187
|
+
var __dirname2 = dirname6(__filename2);
|
|
13188
|
+
var PACKAGED_SCHEMA_PATH = resolve4(
|
|
13189
|
+
__dirname2,
|
|
13190
|
+
"../schema/pourkit.schema.json"
|
|
13191
|
+
);
|
|
13192
|
+
var PACKAGED_HASH_PATH = resolve4(__dirname2, "../schema/pourkit.schema.hash");
|
|
13193
|
+
var _schemaValidator = null;
|
|
13194
|
+
var _schemaErrors = null;
|
|
13195
|
+
function getSchemaValidator() {
|
|
13196
|
+
if (!_schemaValidator) {
|
|
13197
|
+
const schema = JSON.parse(readFileSync19(PACKAGED_SCHEMA_PATH, "utf-8"));
|
|
13198
|
+
const ajv = new Ajv2({ strict: true });
|
|
13199
|
+
ajv.addKeyword("x-pourkit-schema-version");
|
|
13200
|
+
const validate = ajv.compile(schema);
|
|
13201
|
+
_schemaValidator = (data) => {
|
|
13202
|
+
_schemaErrors = null;
|
|
13203
|
+
const valid2 = validate(data);
|
|
13204
|
+
if (!valid2) _schemaErrors = validate.errors;
|
|
13205
|
+
return valid2;
|
|
13206
|
+
};
|
|
13207
|
+
}
|
|
13208
|
+
return _schemaValidator;
|
|
13209
|
+
}
|
|
13210
|
+
function readPackagedHash() {
|
|
13211
|
+
return readFileSync19(PACKAGED_HASH_PATH, "utf-8");
|
|
13212
|
+
}
|
|
13213
|
+
function localSchemaDir(repoRoot2) {
|
|
13214
|
+
return resolve4(repoRoot2, ".pourkit/schema");
|
|
13215
|
+
}
|
|
13216
|
+
async function runDoctorCommand(options) {
|
|
13217
|
+
const repoRootPath = options.cwd ?? process.cwd();
|
|
13218
|
+
const schemaDir = localSchemaDir(repoRootPath);
|
|
13219
|
+
const localSchemaPath = resolve4(schemaDir, "pourkit.schema.json");
|
|
13220
|
+
const localHashPath = resolve4(schemaDir, "pourkit.schema.hash");
|
|
13221
|
+
const localSchemaExists = existsSync18(localSchemaPath);
|
|
13222
|
+
const localHashExists = existsSync18(localHashPath);
|
|
13223
|
+
let packagedHash = null;
|
|
13224
|
+
try {
|
|
13225
|
+
packagedHash = readPackagedHash();
|
|
13226
|
+
} catch {
|
|
13227
|
+
}
|
|
13228
|
+
let localHashContent = null;
|
|
13229
|
+
if (localHashExists) {
|
|
13230
|
+
try {
|
|
13231
|
+
localHashContent = await readFile6(localHashPath, "utf-8");
|
|
13232
|
+
} catch {
|
|
13233
|
+
}
|
|
13234
|
+
}
|
|
13235
|
+
const schema = !localSchemaExists ? "missing" : !packagedHash || !localHashContent ? "missing" : localHashContent.trim() === packagedHash.trim() ? "fresh" : "stale";
|
|
13236
|
+
const hash = !localHashExists ? "missing" : !packagedHash ? "missing" : localHashContent && localHashContent.trim() === packagedHash.trim() ? "fresh" : "stale";
|
|
13237
|
+
const overall = schema === "fresh" && hash === "fresh" ? "fresh" : schema === "missing" || hash === "missing" ? "missing" : "stale";
|
|
13238
|
+
const configPath = resolve4(repoRootPath, ".pourkit/config.json");
|
|
13239
|
+
let configValidation;
|
|
13240
|
+
if (existsSync18(configPath)) {
|
|
13241
|
+
try {
|
|
13242
|
+
const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
13243
|
+
const validate = getSchemaValidator();
|
|
13244
|
+
const valid2 = validate(raw);
|
|
13245
|
+
if (valid2) {
|
|
13246
|
+
configValidation = { ok: true, errors: [] };
|
|
13247
|
+
} else {
|
|
13248
|
+
const errors = _schemaErrors;
|
|
13249
|
+
configValidation = {
|
|
13250
|
+
ok: false,
|
|
13251
|
+
errors: (errors ?? []).map(
|
|
13252
|
+
(e) => `${e.instancePath || "(root)"}: ${e.message ?? "validation failed"}`
|
|
13253
|
+
)
|
|
13254
|
+
};
|
|
13255
|
+
}
|
|
13256
|
+
} catch (err) {
|
|
13257
|
+
const msg = err instanceof SyntaxError ? err.message : String(err);
|
|
13258
|
+
configValidation = {
|
|
13259
|
+
ok: false,
|
|
13260
|
+
errors: [`.pourkit/config.json: ${msg}`]
|
|
13261
|
+
};
|
|
13262
|
+
}
|
|
13263
|
+
} else {
|
|
13264
|
+
configValidation = {
|
|
13265
|
+
ok: false,
|
|
13266
|
+
errors: [".pourkit/config.json not found"]
|
|
13267
|
+
};
|
|
13268
|
+
}
|
|
13269
|
+
const obsoleteConfigs = [
|
|
13270
|
+
"pourkit.config.ts",
|
|
13271
|
+
"pourkit.config.mjs",
|
|
13272
|
+
"pourkit.config.js",
|
|
13273
|
+
"pourkit.json"
|
|
13274
|
+
].filter((p) => existsSync18(resolve4(repoRootPath, p)));
|
|
13275
|
+
let recommendation = null;
|
|
13276
|
+
if (!configValidation.ok || overall !== "fresh" || obsoleteConfigs.length > 0) {
|
|
13277
|
+
const parts = [];
|
|
13278
|
+
if (obsoleteConfigs.length > 0) {
|
|
13279
|
+
parts.push(
|
|
13280
|
+
`Found obsolete config files: ${obsoleteConfigs.join(", ")}. Move configuration to .pourkit/config.json with "$schema": "./schema/pourkit.schema.json".`
|
|
13281
|
+
);
|
|
13282
|
+
}
|
|
13283
|
+
if (!configValidation.ok) {
|
|
13284
|
+
parts.push(
|
|
13285
|
+
"Config validation failed; fix errors in .pourkit/config.json."
|
|
13286
|
+
);
|
|
13287
|
+
}
|
|
13288
|
+
if (overall !== "fresh") {
|
|
13289
|
+
parts.push(
|
|
13290
|
+
'Run "pourkit config sync-schema" to update local schema assets.'
|
|
13291
|
+
);
|
|
13292
|
+
}
|
|
13293
|
+
recommendation = parts.join(" ");
|
|
13294
|
+
}
|
|
13295
|
+
return {
|
|
13296
|
+
configValidation,
|
|
13297
|
+
obsoleteConfigs,
|
|
13298
|
+
schemaAssets: { schema, hash, overall },
|
|
13299
|
+
recommendation
|
|
13300
|
+
};
|
|
13301
|
+
}
|
|
13302
|
+
async function runConfigSyncSchemaCommand(options) {
|
|
13303
|
+
const repoRootPath = options.cwd ?? process.cwd();
|
|
13304
|
+
const schemaDir = localSchemaDir(repoRootPath);
|
|
13305
|
+
await mkdir6(schemaDir, { recursive: true });
|
|
13306
|
+
const packagedSchema = await readFile6(PACKAGED_SCHEMA_PATH, "utf-8");
|
|
13307
|
+
const packagedHash = await readFile6(PACKAGED_HASH_PATH, "utf-8");
|
|
13308
|
+
const localSchemaPath = resolve4(schemaDir, "pourkit.schema.json");
|
|
13309
|
+
const localHashPath = resolve4(schemaDir, "pourkit.schema.hash");
|
|
13310
|
+
let schemaWritten = false;
|
|
13311
|
+
let hashWritten = false;
|
|
13312
|
+
if (existsSync18(localSchemaPath)) {
|
|
13313
|
+
const existing = await readFile6(localSchemaPath, "utf-8");
|
|
13314
|
+
if (existing !== packagedSchema) {
|
|
13315
|
+
await writeFile3(localSchemaPath, packagedSchema, "utf-8");
|
|
13316
|
+
schemaWritten = true;
|
|
13317
|
+
}
|
|
13318
|
+
} else {
|
|
13319
|
+
await writeFile3(localSchemaPath, packagedSchema, "utf-8");
|
|
13320
|
+
schemaWritten = true;
|
|
13321
|
+
}
|
|
13322
|
+
if (existsSync18(localHashPath)) {
|
|
13323
|
+
const existing = await readFile6(localHashPath, "utf-8");
|
|
13324
|
+
if (existing !== packagedHash) {
|
|
13325
|
+
await writeFile3(localHashPath, packagedHash, "utf-8");
|
|
13326
|
+
hashWritten = true;
|
|
13327
|
+
}
|
|
13328
|
+
} else {
|
|
13329
|
+
await writeFile3(localHashPath, packagedHash, "utf-8");
|
|
13330
|
+
hashWritten = true;
|
|
13331
|
+
}
|
|
13332
|
+
return {
|
|
13333
|
+
changed: schemaWritten || hashWritten,
|
|
13334
|
+
schemaWritten,
|
|
13335
|
+
hashWritten
|
|
13336
|
+
};
|
|
13337
|
+
}
|
|
13338
|
+
|
|
12997
13339
|
// providers/github-provider.ts
|
|
12998
13340
|
var GitHubIssueProvider = class {
|
|
12999
13341
|
client;
|
|
@@ -13531,14 +13873,14 @@ import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
|
13531
13873
|
// execution/execution-provider.ts
|
|
13532
13874
|
init_common();
|
|
13533
13875
|
import { mkdtempSync as mkdtempSync2 } from "fs";
|
|
13534
|
-
import { writeFile as
|
|
13876
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
13535
13877
|
import { tmpdir as tmpdir2 } from "os";
|
|
13536
|
-
import { dirname as
|
|
13878
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
13537
13879
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
13538
13880
|
for (const artifact of artifacts) {
|
|
13539
13881
|
const filePath = join21(worktreePath, artifact.path);
|
|
13540
|
-
await ensureDir(
|
|
13541
|
-
await
|
|
13882
|
+
await ensureDir(dirname7(filePath));
|
|
13883
|
+
await writeFile4(filePath, artifact.content, "utf-8");
|
|
13542
13884
|
}
|
|
13543
13885
|
}
|
|
13544
13886
|
|
|
@@ -13548,17 +13890,17 @@ import path7 from "path";
|
|
|
13548
13890
|
|
|
13549
13891
|
// execution/sandbox-image.ts
|
|
13550
13892
|
import { createHash as createHash4 } from "crypto";
|
|
13551
|
-
import { existsSync as
|
|
13893
|
+
import { existsSync as existsSync19, readFileSync as readFileSync20 } from "fs";
|
|
13552
13894
|
import path6 from "path";
|
|
13553
13895
|
function sandboxImageName(repoRoot2) {
|
|
13554
13896
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
13555
13897
|
const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
|
|
13556
13898
|
const baseName = sanitized || "local";
|
|
13557
13899
|
const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
|
|
13558
|
-
if (!
|
|
13900
|
+
if (!existsSync19(dockerfilePath)) {
|
|
13559
13901
|
return `sandcastle:${baseName}`;
|
|
13560
13902
|
}
|
|
13561
|
-
const fingerprint = createHash4("sha256").update(
|
|
13903
|
+
const fingerprint = createHash4("sha256").update(readFileSync20(dockerfilePath)).digest("hex").slice(0, 8);
|
|
13562
13904
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
13563
13905
|
}
|
|
13564
13906
|
|
|
@@ -14418,6 +14760,26 @@ function createCliProgram(version) {
|
|
|
14418
14760
|
await runInitCommand(initOptions);
|
|
14419
14761
|
}
|
|
14420
14762
|
);
|
|
14763
|
+
program.command("doctor").description("Report Config Schema drift without mutating files").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
14764
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
14765
|
+
const report = await runDoctorCommand({ cwd: targetRepoRoot });
|
|
14766
|
+
console.log(JSON.stringify(report, null, 2));
|
|
14767
|
+
if (!report.configValidation.ok || report.obsoleteConfigs.length > 0 || report.schemaAssets.overall !== "fresh") {
|
|
14768
|
+
process.exitCode = 1;
|
|
14769
|
+
}
|
|
14770
|
+
});
|
|
14771
|
+
const configCommand = program.command("config").description("Config management commands");
|
|
14772
|
+
configCommand.command("sync-schema").description("Copy packaged Config Schema assets to .pourkit/schema/").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
14773
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
14774
|
+
const result = await runConfigSyncSchemaCommand({ cwd: targetRepoRoot });
|
|
14775
|
+
if (result.changed) {
|
|
14776
|
+
console.log(
|
|
14777
|
+
`Schema assets updated (schema: ${result.schemaWritten ? "written" : "unchanged"}, hash: ${result.hashWritten ? "written" : "unchanged"}).`
|
|
14778
|
+
);
|
|
14779
|
+
} else {
|
|
14780
|
+
console.log("Schema assets are up to date.");
|
|
14781
|
+
}
|
|
14782
|
+
});
|
|
14421
14783
|
const serena = program.command("serena").description("Serena baseline and sidecar commands");
|
|
14422
14784
|
serena.command("init").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
14423
14785
|
await runSerenaInitCommand({
|
|
@@ -14543,11 +14905,11 @@ function createCliProgram(version) {
|
|
|
14543
14905
|
return program;
|
|
14544
14906
|
}
|
|
14545
14907
|
async function resolveCliVersion() {
|
|
14546
|
-
if (isPackageVersion("0.0.0-next-
|
|
14547
|
-
return "0.0.0-next-
|
|
14908
|
+
if (isPackageVersion("0.0.0-next-20260614074434")) {
|
|
14909
|
+
return "0.0.0-next-20260614074434";
|
|
14548
14910
|
}
|
|
14549
|
-
if (isReleaseVersion("0.0.0-next-
|
|
14550
|
-
return "0.0.0-next-
|
|
14911
|
+
if (isReleaseVersion("0.0.0-next-20260614074434")) {
|
|
14912
|
+
return "0.0.0-next-20260614074434";
|
|
14551
14913
|
}
|
|
14552
14914
|
try {
|
|
14553
14915
|
const root = repoRoot();
|