@pourkit/cli 0.0.0-next-20260613223714 → 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 +1063 -642
- 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,85 +8125,86 @@ 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",
|
|
8017
8145
|
"drained",
|
|
8146
|
+
"completed_prd_branch",
|
|
8018
8147
|
"waiting_for_integration",
|
|
8019
8148
|
"finalizing",
|
|
8020
8149
|
"final_reviewed",
|
|
8021
8150
|
"complete",
|
|
8022
8151
|
"completed_local_branch"
|
|
8023
8152
|
]),
|
|
8024
|
-
updatedAt:
|
|
8025
|
-
mode:
|
|
8026
|
-
blockedGate:
|
|
8027
|
-
targetName:
|
|
8028
|
-
prdBranch:
|
|
8029
|
-
blockedReason:
|
|
8030
|
-
diagnostics:
|
|
8031
|
-
offendingPaths:
|
|
8032
|
-
start:
|
|
8033
|
-
status:
|
|
8034
|
-
targetName:
|
|
8035
|
-
prdBranch:
|
|
8036
|
-
startBaseBranch:
|
|
8037
|
-
startBaseCommit:
|
|
8038
|
-
branchAction:
|
|
8039
|
-
startedAt:
|
|
8040
|
-
queueStartedAt:
|
|
8041
|
-
queueDrainedAt:
|
|
8042
|
-
queueProcessedCount:
|
|
8043
|
-
queueCommand:
|
|
8044
|
-
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()
|
|
8045
8174
|
}).strict().optional(),
|
|
8046
|
-
finalReview:
|
|
8047
|
-
status:
|
|
8175
|
+
finalReview: z.object({
|
|
8176
|
+
status: z.enum([
|
|
8048
8177
|
"started",
|
|
8049
8178
|
"succeeded",
|
|
8050
8179
|
"blocked",
|
|
8051
8180
|
"needs_human_review",
|
|
8052
8181
|
"final_reviewed"
|
|
8053
8182
|
]),
|
|
8054
|
-
targetName:
|
|
8055
|
-
prdBranch:
|
|
8056
|
-
mergeBase:
|
|
8057
|
-
verdict:
|
|
8183
|
+
targetName: z.string().min(1),
|
|
8184
|
+
prdBranch: z.string().min(1),
|
|
8185
|
+
mergeBase: z.string().min(1).optional(),
|
|
8186
|
+
verdict: z.enum([
|
|
8058
8187
|
"pass_no_changes",
|
|
8059
8188
|
"pass_with_retouch",
|
|
8060
8189
|
"needs_human_review",
|
|
8061
8190
|
"blocked"
|
|
8062
8191
|
]).optional(),
|
|
8063
|
-
artifactPath:
|
|
8064
|
-
diagnostics:
|
|
8065
|
-
reviewedAt:
|
|
8066
|
-
retouchPrNumber:
|
|
8067
|
-
retouchPrUrl:
|
|
8068
|
-
retouchMergeCommit:
|
|
8069
|
-
autoMerge:
|
|
8070
|
-
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()
|
|
8071
8200
|
}).strict().optional(),
|
|
8072
|
-
scopeChanges:
|
|
8073
|
-
|
|
8074
|
-
issueNumber:
|
|
8075
|
-
decision:
|
|
8076
|
-
reason:
|
|
8077
|
-
acceptedBy:
|
|
8078
|
-
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)
|
|
8079
8208
|
}).strict()
|
|
8080
8209
|
).optional()
|
|
8081
8210
|
}).strict();
|
|
@@ -8096,12 +8225,12 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
8096
8225
|
return { record: null, diagnostics: [] };
|
|
8097
8226
|
}
|
|
8098
8227
|
try {
|
|
8099
|
-
const raw = JSON.parse(
|
|
8100
|
-
const parsed = PrdRunRecordSchema.parse(raw);
|
|
8228
|
+
const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
|
|
8229
|
+
const parsed = normalizeLegacyPrdRunRecord(PrdRunRecordSchema.parse(raw));
|
|
8101
8230
|
return { record: parsed, diagnostics: [] };
|
|
8102
8231
|
} catch (error) {
|
|
8103
8232
|
try {
|
|
8104
|
-
const raw = JSON.parse(
|
|
8233
|
+
const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
|
|
8105
8234
|
if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
|
|
8106
8235
|
return {
|
|
8107
8236
|
record: raw,
|
|
@@ -8134,8 +8263,8 @@ function listPrdRuns(repoRoot2) {
|
|
|
8134
8263
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
8135
8264
|
const recordPath = join16(stateDir, entry.name);
|
|
8136
8265
|
try {
|
|
8137
|
-
const record =
|
|
8138
|
-
JSON.parse(
|
|
8266
|
+
const record = normalizeLegacyPrdRunRecord(
|
|
8267
|
+
PrdRunRecordSchema.parse(JSON.parse(readFileSync16(recordPath, "utf-8")))
|
|
8139
8268
|
);
|
|
8140
8269
|
records.push(record);
|
|
8141
8270
|
} catch (error) {
|
|
@@ -8157,40 +8286,50 @@ function writePrdRunRecord(repoRoot2, record) {
|
|
|
8157
8286
|
"utf-8"
|
|
8158
8287
|
);
|
|
8159
8288
|
}
|
|
8160
|
-
|
|
8161
|
-
|
|
8289
|
+
function normalizeLegacyPrdRunRecord(record) {
|
|
8290
|
+
if (record.status !== "waiting_for_integration") return record;
|
|
8291
|
+
return {
|
|
8292
|
+
...record,
|
|
8293
|
+
status: "completed_prd_branch",
|
|
8294
|
+
blockedGate: void 0,
|
|
8295
|
+
blockedReason: void 0,
|
|
8296
|
+
offendingPaths: void 0
|
|
8297
|
+
};
|
|
8298
|
+
}
|
|
8299
|
+
var LocalPrdRunRecordSchema = z.object({
|
|
8300
|
+
prdId: z.string().regex(
|
|
8162
8301
|
/^PRD-\d{4}$/,
|
|
8163
8302
|
"PRD id must use four-digit format (e.g., PRD-0052)"
|
|
8164
8303
|
),
|
|
8165
|
-
createdAt:
|
|
8166
|
-
receipts:
|
|
8167
|
-
start:
|
|
8168
|
-
startedAt:
|
|
8169
|
-
branch:
|
|
8170
|
-
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)
|
|
8171
8310
|
}).strict().optional(),
|
|
8172
|
-
queue:
|
|
8173
|
-
finalReview:
|
|
8174
|
-
completedAt:
|
|
8175
|
-
targetName:
|
|
8176
|
-
prdBranch:
|
|
8177
|
-
mergeBase:
|
|
8178
|
-
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([
|
|
8179
8318
|
"pass_no_changes",
|
|
8180
8319
|
"pass_with_retouch",
|
|
8181
8320
|
"needs_human_review",
|
|
8182
8321
|
"blocked"
|
|
8183
8322
|
]).optional(),
|
|
8184
|
-
diagnostics:
|
|
8185
|
-
artifactPath:
|
|
8323
|
+
diagnostics: z.array(z.string()).optional(),
|
|
8324
|
+
artifactPath: z.string().optional()
|
|
8186
8325
|
}).strict().optional(),
|
|
8187
|
-
complete:
|
|
8188
|
-
completedAt:
|
|
8189
|
-
branch:
|
|
8190
|
-
stages:
|
|
8326
|
+
complete: z.object({
|
|
8327
|
+
completedAt: z.string().min(1),
|
|
8328
|
+
branch: z.string().min(1),
|
|
8329
|
+
stages: z.array(z.string())
|
|
8191
8330
|
}).strict().optional()
|
|
8192
8331
|
}).strict(),
|
|
8193
|
-
metadata:
|
|
8332
|
+
metadata: z.record(z.unknown())
|
|
8194
8333
|
}).strict();
|
|
8195
8334
|
function getRecordPath(repoRoot2, prdRef) {
|
|
8196
8335
|
return join16(
|
|
@@ -8202,20 +8341,20 @@ function getRecordPath(repoRoot2, prdRef) {
|
|
|
8202
8341
|
|
|
8203
8342
|
// prd-run/evidence-packet.ts
|
|
8204
8343
|
import { createHash as createHash2 } from "crypto";
|
|
8205
|
-
import { z as
|
|
8344
|
+
import { z as z2 } from "zod";
|
|
8206
8345
|
var PRD_REF_REGEX2 = /^PRD-\d{3,4}$/;
|
|
8207
|
-
var StageSchema =
|
|
8208
|
-
var EvidencePacketSchema =
|
|
8209
|
-
prdRef:
|
|
8210
|
-
prdBranch:
|
|
8211
|
-
mergeBase:
|
|
8212
|
-
planningManifestPath:
|
|
8213
|
-
planningManifestFacts:
|
|
8214
|
-
parentPrdIssueUrl:
|
|
8215
|
-
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()
|
|
8216
8355
|
}),
|
|
8217
8356
|
stage: StageSchema,
|
|
8218
|
-
stageReceipts:
|
|
8357
|
+
stageReceipts: z2.record(z2.string(), z2.unknown())
|
|
8219
8358
|
}).strict();
|
|
8220
8359
|
|
|
8221
8360
|
// commands/prd-run.ts
|
|
@@ -8426,7 +8565,7 @@ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
|
8426
8565
|
import { join as join18 } from "path";
|
|
8427
8566
|
|
|
8428
8567
|
// prd-run/local-queue-loop.ts
|
|
8429
|
-
import { readFileSync as
|
|
8568
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync7 } from "fs";
|
|
8430
8569
|
import { join as join19 } from "path";
|
|
8431
8570
|
|
|
8432
8571
|
// prd-run/local-issue-run.ts
|
|
@@ -8550,6 +8689,7 @@ async function runLocalIssue(prdId, issueId, builder, repoRoot2) {
|
|
|
8550
8689
|
|
|
8551
8690
|
// prd-run/local-queue-loop.ts
|
|
8552
8691
|
var CHILD_CLEANUP_LABELS = ["agent-in-progress", "pr-open-awaiting-merge"];
|
|
8692
|
+
var LOCAL_ISSUE_ID_REGEX = /^I-(\d+)$/i;
|
|
8553
8693
|
function getIssueArtifactPath2(repoRoot2, prdId, issueId) {
|
|
8554
8694
|
return join19(
|
|
8555
8695
|
repoRoot2,
|
|
@@ -8562,7 +8702,7 @@ function getIssueArtifactPath2(repoRoot2, prdId, issueId) {
|
|
|
8562
8702
|
}
|
|
8563
8703
|
function readIssueArtifact(repoRoot2, prdId, issueId) {
|
|
8564
8704
|
try {
|
|
8565
|
-
const content =
|
|
8705
|
+
const content = readFileSync17(
|
|
8566
8706
|
getIssueArtifactPath2(repoRoot2, prdId, issueId),
|
|
8567
8707
|
"utf-8"
|
|
8568
8708
|
);
|
|
@@ -8578,6 +8718,16 @@ function writeIssueArtifact(repoRoot2, prdId, issue) {
|
|
|
8578
8718
|
"utf-8"
|
|
8579
8719
|
);
|
|
8580
8720
|
}
|
|
8721
|
+
function compareLocalIssueIds(a, b) {
|
|
8722
|
+
const aMatch = a.match(LOCAL_ISSUE_ID_REGEX);
|
|
8723
|
+
const bMatch = b.match(LOCAL_ISSUE_ID_REGEX);
|
|
8724
|
+
if (aMatch && bMatch) {
|
|
8725
|
+
const aNumber = Number.parseInt(aMatch[1], 10);
|
|
8726
|
+
const bNumber = Number.parseInt(bMatch[1], 10);
|
|
8727
|
+
if (aNumber !== bNumber) return aNumber - bNumber;
|
|
8728
|
+
}
|
|
8729
|
+
return a.localeCompare(b);
|
|
8730
|
+
}
|
|
8581
8731
|
async function reconcileLocalBlockedIssues(prdId, repoRoot2) {
|
|
8582
8732
|
const root = repoRoot2 ?? process.cwd();
|
|
8583
8733
|
const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
|
|
@@ -8653,7 +8803,9 @@ async function runLocalQueueLoop(prdId, repoRoot2, issueProvider) {
|
|
|
8653
8803
|
}
|
|
8654
8804
|
break;
|
|
8655
8805
|
}
|
|
8656
|
-
const issue = runnable
|
|
8806
|
+
const issue = [...runnable].sort(
|
|
8807
|
+
(a, b) => compareLocalIssueIds(a.id, b.id)
|
|
8808
|
+
)[0];
|
|
8657
8809
|
const runResult = await runLocalIssue(prdId, issue.id, void 0, root);
|
|
8658
8810
|
if (!runResult.ok) {
|
|
8659
8811
|
return {
|
|
@@ -8781,6 +8933,18 @@ var TYPE_PRIORITY = {
|
|
|
8781
8933
|
"type:polish": 4,
|
|
8782
8934
|
"type:refactor": 5
|
|
8783
8935
|
};
|
|
8936
|
+
var CHILD_ISSUE_TITLE_REGEX = /\bI-(\d+)\b/i;
|
|
8937
|
+
var PRD_TITLE_REGEX = /\bPRD-(\d+)\b/i;
|
|
8938
|
+
function parseChildIssueOrder(value) {
|
|
8939
|
+
const match = value.match(CHILD_ISSUE_TITLE_REGEX);
|
|
8940
|
+
if (!match) return null;
|
|
8941
|
+
return Number.parseInt(match[1], 10);
|
|
8942
|
+
}
|
|
8943
|
+
function parsePrdOrder(value) {
|
|
8944
|
+
const match = value.match(PRD_TITLE_REGEX);
|
|
8945
|
+
if (!match) return null;
|
|
8946
|
+
return Number.parseInt(match[1], 10);
|
|
8947
|
+
}
|
|
8784
8948
|
function selectIssue(candidates, options = {}) {
|
|
8785
8949
|
const blockedLabel = options.blockedLabel ?? "blocked";
|
|
8786
8950
|
const agentInProgressLabel = options.agentInProgressLabel ?? "agent-in-progress";
|
|
@@ -8820,6 +8984,13 @@ function selectIssue(candidates, options = {}) {
|
|
|
8820
8984
|
};
|
|
8821
8985
|
}
|
|
8822
8986
|
valid2.sort((a, b) => {
|
|
8987
|
+
const aChildOrder = parseChildIssueOrder(a.issue.title);
|
|
8988
|
+
const bChildOrder = parseChildIssueOrder(b.issue.title);
|
|
8989
|
+
const aPrdOrder = parsePrdOrder(a.issue.title);
|
|
8990
|
+
const bPrdOrder = parsePrdOrder(b.issue.title);
|
|
8991
|
+
if (aChildOrder !== null && bChildOrder !== null && aPrdOrder !== null && aPrdOrder === bPrdOrder) {
|
|
8992
|
+
if (aChildOrder !== bChildOrder) return aChildOrder - bChildOrder;
|
|
8993
|
+
}
|
|
8823
8994
|
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
8824
8995
|
const ageCmp = a.issue.createdAt.localeCompare(b.issue.createdAt);
|
|
8825
8996
|
if (ageCmp !== 0) return ageCmp;
|
|
@@ -9213,7 +9384,7 @@ function planLaunchResume(record) {
|
|
|
9213
9384
|
return pipe(
|
|
9214
9385
|
record,
|
|
9215
9386
|
Match.value,
|
|
9216
|
-
Match.when({ status: "
|
|
9387
|
+
Match.when({ status: "completed_prd_branch" }, () => ({
|
|
9217
9388
|
attempted: [],
|
|
9218
9389
|
skipped: ["start", "queue"],
|
|
9219
9390
|
resumed: []
|
|
@@ -9273,7 +9444,7 @@ function validateLocalStartStore(repoRoot2, prdRef) {
|
|
|
9273
9444
|
if (existsSync16(localStoreDir)) {
|
|
9274
9445
|
const localStorePath = join20(localStoreDir, "prd.json");
|
|
9275
9446
|
try {
|
|
9276
|
-
const content = JSON.parse(
|
|
9447
|
+
const content = JSON.parse(readFileSync18(localStorePath, "utf8"));
|
|
9277
9448
|
localStoreReady = content?.id === prdRef && content?.kind === "prd";
|
|
9278
9449
|
} catch {
|
|
9279
9450
|
localStoreReady = false;
|
|
@@ -9380,7 +9551,7 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
9380
9551
|
offendingPaths: []
|
|
9381
9552
|
};
|
|
9382
9553
|
}
|
|
9383
|
-
if (existingRecord.record?.status === "
|
|
9554
|
+
if (existingRecord.record?.status === "completed_prd_branch" || existingRecord.record?.status === "complete" || existingRecord.record?.status === "completed_local_branch") {
|
|
9384
9555
|
return {
|
|
9385
9556
|
prdRef,
|
|
9386
9557
|
status: existingRecord.record.status,
|
|
@@ -9388,7 +9559,12 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
9388
9559
|
skipped,
|
|
9389
9560
|
resumed,
|
|
9390
9561
|
diagnostics: [
|
|
9391
|
-
`PRD Run ${prdRef} is already in status "${existingRecord.record.status}"
|
|
9562
|
+
`PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`,
|
|
9563
|
+
...existingRecord.record.status === "completed_prd_branch" ? [
|
|
9564
|
+
buildCompletedPrdBranchHint(
|
|
9565
|
+
existingRecord.record.prdBranch ?? prdRef
|
|
9566
|
+
)
|
|
9567
|
+
] : []
|
|
9392
9568
|
],
|
|
9393
9569
|
prdBranch: existingRecord.record.prdBranch
|
|
9394
9570
|
};
|
|
@@ -9470,7 +9646,7 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
9470
9646
|
}
|
|
9471
9647
|
writePrdRunRecord(options.repoRoot, {
|
|
9472
9648
|
prdRef,
|
|
9473
|
-
status: "
|
|
9649
|
+
status: "completed_prd_branch",
|
|
9474
9650
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9475
9651
|
targetName: currentRecord?.targetName ?? options.targetName,
|
|
9476
9652
|
prdBranch: currentRecord?.prdBranch,
|
|
@@ -9479,14 +9655,15 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
9479
9655
|
});
|
|
9480
9656
|
return {
|
|
9481
9657
|
prdRef,
|
|
9482
|
-
status: "
|
|
9658
|
+
status: "completed_prd_branch",
|
|
9483
9659
|
attempted,
|
|
9484
9660
|
skipped,
|
|
9485
9661
|
resumed,
|
|
9486
9662
|
diagnostics: [
|
|
9487
|
-
|
|
9663
|
+
buildCompletedPrdBranchHint(currentRecord?.prdBranch ?? prdRef)
|
|
9488
9664
|
],
|
|
9489
|
-
start: startResult
|
|
9665
|
+
start: startResult,
|
|
9666
|
+
prdBranch: currentRecord?.prdBranch ?? prdRef
|
|
9490
9667
|
};
|
|
9491
9668
|
}
|
|
9492
9669
|
async function runPrdRunStartCommand(options) {
|
|
@@ -10386,7 +10563,14 @@ async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
|
|
|
10386
10563
|
function runPrdRunStatusCommand(options) {
|
|
10387
10564
|
const prdRef = normalizePrdRunRef(options.prdRef);
|
|
10388
10565
|
const { record, diagnostics } = readPrdRun(options.repoRoot, prdRef);
|
|
10389
|
-
return {
|
|
10566
|
+
return {
|
|
10567
|
+
prdRef,
|
|
10568
|
+
record,
|
|
10569
|
+
diagnostics: record?.status === "completed_prd_branch" ? [
|
|
10570
|
+
...diagnostics,
|
|
10571
|
+
buildCompletedPrdBranchHint(record.prdBranch ?? prdRef)
|
|
10572
|
+
] : diagnostics
|
|
10573
|
+
};
|
|
10390
10574
|
}
|
|
10391
10575
|
function runPrdRunListCommand(options) {
|
|
10392
10576
|
return listPrdRuns(options.repoRoot);
|
|
@@ -10415,6 +10599,9 @@ function coercePrdRunRecordBlockedGate(gate) {
|
|
|
10415
10599
|
return "branch-state";
|
|
10416
10600
|
}
|
|
10417
10601
|
}
|
|
10602
|
+
function buildCompletedPrdBranchHint(prdBranch) {
|
|
10603
|
+
return `PRD Run completed on branch ${prdBranch}. Next: checkout ${prdBranch} for human review, or use release promotion workflow when ready.`;
|
|
10604
|
+
}
|
|
10418
10605
|
function buildBlockedDiagnostics(failure) {
|
|
10419
10606
|
return failure.diagnostics ?? [
|
|
10420
10607
|
failure.reason,
|
|
@@ -11056,135 +11243,120 @@ function inferVerificationCommands(scripts, pm) {
|
|
|
11056
11243
|
}
|
|
11057
11244
|
function generateConfigTemplate(options) {
|
|
11058
11245
|
const {
|
|
11059
|
-
sourceRoot,
|
|
11060
|
-
targetRoot,
|
|
11061
|
-
packageManager,
|
|
11062
11246
|
baseBranch,
|
|
11247
|
+
packageManager,
|
|
11063
11248
|
verificationCommands,
|
|
11064
11249
|
hasPackageJson = true,
|
|
11065
11250
|
labels: maybeLabels
|
|
11066
11251
|
} = options;
|
|
11067
11252
|
const labels = maybeLabels ?? DEFAULT_RUNNER_LABELS;
|
|
11068
|
-
const relPath = path5.relative(targetRoot, sourceRoot).replace(/\\/g, "/");
|
|
11069
|
-
const importPath = relPath || ".";
|
|
11070
11253
|
const setupCommand = `${packageManager} install`;
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
" // },"
|
|
11098
|
-
].join("\n");
|
|
11099
|
-
}
|
|
11100
|
-
return `import { definePourkitConfig } from "${importPath}/pourkit/shared/config";
|
|
11101
|
-
import type { PourkitConfig } from "${importPath}/pourkit/shared/config";
|
|
11102
|
-
|
|
11103
|
-
export default definePourkitConfig({
|
|
11104
|
-
targets: [
|
|
11105
|
-
{
|
|
11106
|
-
name: "default",
|
|
11107
|
-
baseBranch: "${baseBranch}",
|
|
11108
|
-
branchTemplate: "pourkit/{{issue.number}}/{{issue.slug}}",
|
|
11109
|
-
autoMerge: false,
|
|
11110
|
-
${setupSection}
|
|
11111
|
-
strategy: {
|
|
11112
|
-
type: "review-refactor-loop",
|
|
11113
|
-
implement: {
|
|
11114
|
-
builder: {
|
|
11115
|
-
agent: "pourkit-builder",
|
|
11116
|
-
model: "opencode-go/deepseek-v4-flash",
|
|
11117
|
-
promptTemplate: ".pourkit/prompts/builder.prompt.md",
|
|
11118
|
-
},
|
|
11119
|
-
},
|
|
11120
|
-
review: {
|
|
11121
|
-
reviewer: {
|
|
11122
|
-
agent: "pourkit-reviewer",
|
|
11123
|
-
model: "opencode-go/deepseek-v4-pro",
|
|
11124
|
-
promptTemplate: ".pourkit/prompts/reviewer.prompt.md",
|
|
11125
|
-
criteria: ["correctness", "scope", "tests", "quality"],
|
|
11126
|
-
},
|
|
11127
|
-
refactor: {
|
|
11128
|
-
agent: "pourkit-refactor",
|
|
11129
|
-
model: "opencode-go/qwen3.6-plus",
|
|
11130
|
-
promptTemplate: ".pourkit/prompts/refactor.prompt.md",
|
|
11131
|
-
},
|
|
11132
|
-
maxIterations: 3,
|
|
11133
|
-
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"]
|
|
11134
11280
|
},
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
model: "opencode-go/deepseek-v4-flash",
|
|
11140
|
-
promptTemplate: ".pourkit/prompts/pr-description.prompt.md",
|
|
11141
|
-
},
|
|
11142
|
-
maxAttempts: 2,
|
|
11281
|
+
refactor: {
|
|
11282
|
+
agent: "pourkit-refactor",
|
|
11283
|
+
model: "opencode-go/qwen3.6-plus",
|
|
11284
|
+
promptTemplate: ".pourkit/prompts/refactor.prompt.md"
|
|
11143
11285
|
},
|
|
11286
|
+
maxIterations: 3,
|
|
11287
|
+
passWithNotesRefactorAttempts: 2
|
|
11144
11288
|
},
|
|
11145
|
-
|
|
11146
|
-
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
blocked: ${JSON.stringify(labels.blocked)},
|
|
11151
|
-
prOpenAwaitingMerge: ${JSON.stringify(labels.prOpenAwaitingMerge)},
|
|
11152
|
-
readyForHuman: ${JSON.stringify(labels.readyForHuman)},
|
|
11153
|
-
},
|
|
11154
|
-
sandbox: {
|
|
11155
|
-
provider: "docker",
|
|
11156
|
-
copyToWorktree: ["node_modules"],
|
|
11157
|
-
mounts: [
|
|
11158
|
-
{
|
|
11159
|
-
hostPath: "~/.local/share/opencode",
|
|
11160
|
-
sandboxPath: "/home/agent/.local/share/opencode",
|
|
11161
|
-
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
|
|
11162
11294
|
},
|
|
11163
|
-
{
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
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"
|
|
11167
11347
|
},
|
|
11168
|
-
|
|
11169
|
-
env: {
|
|
11170
|
-
HOME: "/home/agent",
|
|
11171
|
-
XDG_DATA_HOME: "/home/agent/.local/share",
|
|
11172
|
-
XDG_CONFIG_HOME: "/home/agent/.config",
|
|
11173
|
-
XDG_STATE_HOME: "/home/agent/.local/state",
|
|
11174
|
-
XDG_CACHE_HOME: "/home/agent/.cache",
|
|
11348
|
+
idleTimeoutSeconds: 300
|
|
11175
11349
|
},
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
});
|
|
11187
|
-
`;
|
|
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";
|
|
11188
11360
|
}
|
|
11189
11361
|
function generateOpenCodeConfig(existingConfig = {}) {
|
|
11190
11362
|
const { $schema, ...rest } = existingConfig;
|
|
@@ -11917,15 +12089,13 @@ async function planInit(options) {
|
|
|
11917
12089
|
checksum
|
|
11918
12090
|
});
|
|
11919
12091
|
}
|
|
11920
|
-
const
|
|
11921
|
-
if (!existsSync17(
|
|
12092
|
+
const configJsonPath = path5.join(targetRoot, ".pourkit", "config.json");
|
|
12093
|
+
if (!existsSync17(configJsonPath)) {
|
|
11922
12094
|
const verifyCommands = inferVerificationCommands(
|
|
11923
12095
|
packageScripts,
|
|
11924
12096
|
pm || "npm"
|
|
11925
12097
|
);
|
|
11926
12098
|
const configContent = generateConfigTemplate({
|
|
11927
|
-
targetRoot,
|
|
11928
|
-
sourceRoot,
|
|
11929
12099
|
packageManager: pm || "npm",
|
|
11930
12100
|
baseBranch,
|
|
11931
12101
|
verificationCommands: verifyCommands,
|
|
@@ -11934,9 +12104,9 @@ async function planInit(options) {
|
|
|
11934
12104
|
});
|
|
11935
12105
|
operations.push({
|
|
11936
12106
|
kind: "create",
|
|
11937
|
-
path:
|
|
12107
|
+
path: configJsonPath,
|
|
11938
12108
|
ownership: "managed",
|
|
11939
|
-
reason: "Init pourkit
|
|
12109
|
+
reason: "Init .pourkit/config.json template",
|
|
11940
12110
|
requiresConfirmation: false,
|
|
11941
12111
|
destructive: false,
|
|
11942
12112
|
content: configContent
|
|
@@ -11944,13 +12114,85 @@ async function planInit(options) {
|
|
|
11944
12114
|
} else {
|
|
11945
12115
|
operations.push({
|
|
11946
12116
|
kind: "skip",
|
|
11947
|
-
path:
|
|
12117
|
+
path: configJsonPath,
|
|
11948
12118
|
ownership: "project-owned",
|
|
11949
|
-
reason: "Existing pourkit
|
|
12119
|
+
reason: "Existing .pourkit/config.json (project-owned)",
|
|
11950
12120
|
requiresConfirmation: false,
|
|
11951
12121
|
destructive: false
|
|
11952
12122
|
});
|
|
11953
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
|
+
}
|
|
11954
12196
|
const agentFileMode = options.conflictPolicy?.agentFile ?? "both";
|
|
11955
12197
|
const hasExistingAgents = operations.some(
|
|
11956
12198
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
|
|
@@ -12935,6 +13177,165 @@ async function runSerenaStatusCommand(options) {
|
|
|
12935
13177
|
logSerenaSidecarStatus("Serena sidecar status", status);
|
|
12936
13178
|
}
|
|
12937
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
|
+
|
|
12938
13339
|
// providers/github-provider.ts
|
|
12939
13340
|
var GitHubIssueProvider = class {
|
|
12940
13341
|
client;
|
|
@@ -13472,14 +13873,14 @@ import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
|
13472
13873
|
// execution/execution-provider.ts
|
|
13473
13874
|
init_common();
|
|
13474
13875
|
import { mkdtempSync as mkdtempSync2 } from "fs";
|
|
13475
|
-
import { writeFile as
|
|
13876
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
13476
13877
|
import { tmpdir as tmpdir2 } from "os";
|
|
13477
|
-
import { dirname as
|
|
13878
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
13478
13879
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
13479
13880
|
for (const artifact of artifacts) {
|
|
13480
13881
|
const filePath = join21(worktreePath, artifact.path);
|
|
13481
|
-
await ensureDir(
|
|
13482
|
-
await
|
|
13882
|
+
await ensureDir(dirname7(filePath));
|
|
13883
|
+
await writeFile4(filePath, artifact.content, "utf-8");
|
|
13483
13884
|
}
|
|
13484
13885
|
}
|
|
13485
13886
|
|
|
@@ -13489,17 +13890,17 @@ import path7 from "path";
|
|
|
13489
13890
|
|
|
13490
13891
|
// execution/sandbox-image.ts
|
|
13491
13892
|
import { createHash as createHash4 } from "crypto";
|
|
13492
|
-
import { existsSync as
|
|
13893
|
+
import { existsSync as existsSync19, readFileSync as readFileSync20 } from "fs";
|
|
13493
13894
|
import path6 from "path";
|
|
13494
13895
|
function sandboxImageName(repoRoot2) {
|
|
13495
13896
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
13496
13897
|
const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
|
|
13497
13898
|
const baseName = sanitized || "local";
|
|
13498
13899
|
const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
|
|
13499
|
-
if (!
|
|
13900
|
+
if (!existsSync19(dockerfilePath)) {
|
|
13500
13901
|
return `sandcastle:${baseName}`;
|
|
13501
13902
|
}
|
|
13502
|
-
const fingerprint = createHash4("sha256").update(
|
|
13903
|
+
const fingerprint = createHash4("sha256").update(readFileSync20(dockerfilePath)).digest("hex").slice(0, 8);
|
|
13503
13904
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
13504
13905
|
}
|
|
13505
13906
|
|
|
@@ -14359,6 +14760,26 @@ function createCliProgram(version) {
|
|
|
14359
14760
|
await runInitCommand(initOptions);
|
|
14360
14761
|
}
|
|
14361
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
|
+
});
|
|
14362
14783
|
const serena = program.command("serena").description("Serena baseline and sidecar commands");
|
|
14363
14784
|
serena.command("init").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
14364
14785
|
await runSerenaInitCommand({
|
|
@@ -14484,11 +14905,11 @@ function createCliProgram(version) {
|
|
|
14484
14905
|
return program;
|
|
14485
14906
|
}
|
|
14486
14907
|
async function resolveCliVersion() {
|
|
14487
|
-
if (isPackageVersion("0.0.0-next-
|
|
14488
|
-
return "0.0.0-next-
|
|
14908
|
+
if (isPackageVersion("0.0.0-next-20260614074434")) {
|
|
14909
|
+
return "0.0.0-next-20260614074434";
|
|
14489
14910
|
}
|
|
14490
|
-
if (isReleaseVersion("0.0.0-next-
|
|
14491
|
-
return "0.0.0-next-
|
|
14911
|
+
if (isReleaseVersion("0.0.0-next-20260614074434")) {
|
|
14912
|
+
return "0.0.0-next-20260614074434";
|
|
14492
14913
|
}
|
|
14493
14914
|
try {
|
|
14494
14915
|
const root = repoRoot();
|