@pourkit/cli 0.0.0-next-20260614002607 → 0.0.0-next-20260614182520
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 +1136 -1030
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +570 -643
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/dist/issues/close-issues-on-merge.js +19 -1
- package/dist/issues/close-issues-on-merge.js.map +1 -1
- package/dist/issues/unblock.js +19 -1
- package/dist/issues/unblock.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
|
}
|
|
@@ -153,6 +153,7 @@ __export(common_exports, {
|
|
|
153
153
|
execJson: () => execJson,
|
|
154
154
|
parseWorktreeListPorcelain: () => parseWorktreeListPorcelain,
|
|
155
155
|
readMaybeEnvInt: () => readMaybeEnvInt,
|
|
156
|
+
redactSensitiveValues: () => redactSensitiveValues,
|
|
156
157
|
repoRelative: () => repoRelative,
|
|
157
158
|
repoRoot: () => repoRoot,
|
|
158
159
|
sleep: () => sleep,
|
|
@@ -265,7 +266,7 @@ async function execJson(command, args, options = {}) {
|
|
|
265
266
|
return JSON.parse(result.stdout);
|
|
266
267
|
}
|
|
267
268
|
function sleep(ms) {
|
|
268
|
-
return new Promise((
|
|
269
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
269
270
|
}
|
|
270
271
|
async function execCaptureWithRetry(command, args, options = {}) {
|
|
271
272
|
const retries = options.retries ?? 3;
|
|
@@ -311,7 +312,14 @@ function parseWorktreeListPorcelain(text2, branch) {
|
|
|
311
312
|
}
|
|
312
313
|
return null;
|
|
313
314
|
}
|
|
314
|
-
|
|
315
|
+
function redactSensitiveValues(input) {
|
|
316
|
+
let redacted = input;
|
|
317
|
+
for (const pattern of TOKEN_LIKE_PATTERNS) {
|
|
318
|
+
redacted = redacted.replace(pattern, "[REDACTED]");
|
|
319
|
+
}
|
|
320
|
+
return redacted;
|
|
321
|
+
}
|
|
322
|
+
var execFileAsync, TRANSIENT_GH_ERROR, TYPE_LABELS, TOKEN_LIKE_PATTERNS;
|
|
315
323
|
var init_common = __esm({
|
|
316
324
|
"shared/common.ts"() {
|
|
317
325
|
"use strict";
|
|
@@ -325,6 +333,16 @@ var init_common = __esm({
|
|
|
325
333
|
"type:polish",
|
|
326
334
|
"type:refactor"
|
|
327
335
|
];
|
|
336
|
+
TOKEN_LIKE_PATTERNS = [
|
|
337
|
+
/gh[ps]_[A-Za-z0-9]{20,}/g,
|
|
338
|
+
/token[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
339
|
+
/secret[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
340
|
+
/credential[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
341
|
+
/key[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
342
|
+
/bearer\s+[A-Za-z0-9_-]{20,}/gi,
|
|
343
|
+
/authorization:\s*Bearer\s+[A-Za-z0-9_-]{20,}/gi,
|
|
344
|
+
/xox[baprs]-[A-Za-z0-9_-]{10,}/g
|
|
345
|
+
];
|
|
328
346
|
}
|
|
329
347
|
});
|
|
330
348
|
|
|
@@ -335,175 +353,37 @@ import { pathToFileURL } from "url";
|
|
|
335
353
|
import { Command, Option, CommanderError } from "commander";
|
|
336
354
|
|
|
337
355
|
// shared/config.ts
|
|
338
|
-
import {
|
|
339
|
-
import {
|
|
340
|
-
|
|
356
|
+
import { readFileSync } from "fs";
|
|
357
|
+
import { fileURLToPath } from "url";
|
|
358
|
+
import { dirname, isAbsolute, join, normalize, sep, resolve } from "path";
|
|
359
|
+
import Ajv from "ajv";
|
|
360
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
361
|
+
var __dirname = dirname(__filename);
|
|
362
|
+
var SCHEMA_PATH = resolve(__dirname, "../schema/pourkit.schema.json");
|
|
363
|
+
var _schema = null;
|
|
364
|
+
var _validate = null;
|
|
365
|
+
var _ajvErrors = null;
|
|
366
|
+
function getValidator() {
|
|
367
|
+
if (!_validate) {
|
|
368
|
+
const schema = _schema ?? JSON.parse(readFileSync(SCHEMA_PATH, "utf-8"));
|
|
369
|
+
_schema = schema;
|
|
370
|
+
const ajv = new Ajv({ strict: true });
|
|
371
|
+
ajv.addKeyword("x-pourkit-schema-version");
|
|
372
|
+
const validate = ajv.compile(schema);
|
|
373
|
+
_validate = (data) => {
|
|
374
|
+
_ajvErrors = null;
|
|
375
|
+
const valid2 = validate(data);
|
|
376
|
+
if (!valid2) _ajvErrors = validate.errors;
|
|
377
|
+
return valid2;
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return _validate;
|
|
381
|
+
}
|
|
341
382
|
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();
|
|
383
|
+
var DEFAULT_BRANCH_TEMPLATE = "pourkit/{{issue.number}}/{{issue.slug}}";
|
|
345
384
|
function resolveMissingOrEmptyOutputRetries(config) {
|
|
346
385
|
return config?.outputRetries?.missingOrEmpty ?? DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES;
|
|
347
386
|
}
|
|
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
387
|
var removedFieldReplacements = {
|
|
508
388
|
"config.implementor": "targets[].strategy.implement.builder",
|
|
509
389
|
"config.reviewer": "targets[].strategy.review.reviewer",
|
|
@@ -567,11 +447,12 @@ function checkRemovedFields(raw) {
|
|
|
567
447
|
}
|
|
568
448
|
}
|
|
569
449
|
}
|
|
570
|
-
function
|
|
571
|
-
if (
|
|
450
|
+
function formatAjvPath(instancePath) {
|
|
451
|
+
if (!instancePath || instancePath === "/") return "";
|
|
452
|
+
const parts = instancePath.split("/").slice(1);
|
|
572
453
|
let result = "";
|
|
573
|
-
for (const segment of
|
|
574
|
-
if (
|
|
454
|
+
for (const segment of parts) {
|
|
455
|
+
if (/^\d+$/.test(segment)) {
|
|
575
456
|
result += `[${segment}]`;
|
|
576
457
|
} else {
|
|
577
458
|
result += result ? `.${segment}` : segment;
|
|
@@ -579,51 +460,172 @@ function formatZodPath(path9) {
|
|
|
579
460
|
}
|
|
580
461
|
return result;
|
|
581
462
|
}
|
|
582
|
-
function
|
|
583
|
-
const
|
|
584
|
-
const path9 =
|
|
585
|
-
if (
|
|
586
|
-
|
|
463
|
+
function formatFirstAjvError(errors) {
|
|
464
|
+
const error = errors[0];
|
|
465
|
+
const path9 = formatAjvPath(error.instancePath);
|
|
466
|
+
if (error.keyword === "required") {
|
|
467
|
+
const missingParam = error.params.missingProperty;
|
|
468
|
+
if (missingParam === "targets") {
|
|
469
|
+
return "Config must have at least one target";
|
|
470
|
+
}
|
|
471
|
+
if (path9 === "" && missingParam === "targets") {
|
|
472
|
+
return "Config must have at least one target";
|
|
473
|
+
}
|
|
474
|
+
if (path9) {
|
|
475
|
+
return `${path9} must have required property '${missingParam}'`;
|
|
476
|
+
}
|
|
477
|
+
return `${missingParam} must be an object`;
|
|
587
478
|
}
|
|
588
|
-
if (
|
|
589
|
-
|
|
479
|
+
if (error.keyword === "additionalProperties") {
|
|
480
|
+
const additionalProp = error.params.additionalProperty;
|
|
481
|
+
const keyPath = path9 ? `${path9}.${additionalProp}` : additionalProp;
|
|
482
|
+
return `${keyPath} is not supported`;
|
|
590
483
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
484
|
+
if (error.keyword === "minLength") {
|
|
485
|
+
if (path9) {
|
|
486
|
+
return `${path9} must be a non-empty string`;
|
|
487
|
+
}
|
|
488
|
+
return "Config must be a non-empty string";
|
|
489
|
+
}
|
|
490
|
+
if (error.keyword === "const") {
|
|
491
|
+
const allowedValue = error.params.allowedValue;
|
|
492
|
+
return `${path9 || "Config"} must be ${JSON.stringify(allowedValue)}`;
|
|
493
|
+
}
|
|
494
|
+
if (error.keyword === "enum") {
|
|
495
|
+
const allowedValues = error.params.allowedValues;
|
|
496
|
+
return `${path9} must be one of: ${allowedValues.map((v) => JSON.stringify(v)).join(", ")}`;
|
|
497
|
+
}
|
|
498
|
+
if (error.keyword === "minimum" || error.keyword === "exclusiveMinimum") {
|
|
499
|
+
const limit = error.params.limit;
|
|
500
|
+
if (limit === 1) {
|
|
501
|
+
return `${path9} must be a positive number`;
|
|
502
|
+
}
|
|
503
|
+
return `${path9} must be at least ${limit}`;
|
|
504
|
+
}
|
|
505
|
+
if (error.keyword === "type") {
|
|
506
|
+
const expected = error.params.type;
|
|
507
|
+
if (path9 === "") {
|
|
508
|
+
return `Config must be an object`;
|
|
509
|
+
}
|
|
510
|
+
if (expected === "object") {
|
|
511
|
+
return `${path9} must be an object`;
|
|
512
|
+
}
|
|
513
|
+
if (expected === "integer") {
|
|
514
|
+
return `${path9} must be an integer`;
|
|
515
|
+
}
|
|
516
|
+
if (expected === "string") {
|
|
517
|
+
return `${path9} must be a string`;
|
|
518
|
+
}
|
|
519
|
+
if (expected === "number") {
|
|
520
|
+
return `${path9} must be a number`;
|
|
521
|
+
}
|
|
522
|
+
return `${path9} must be ${expected === "integer" ? "an integer" : `a ${expected}`}`;
|
|
523
|
+
}
|
|
524
|
+
if (error.keyword === "minItems") {
|
|
525
|
+
return `${path9} must contain at least one item`;
|
|
526
|
+
}
|
|
527
|
+
if (error.keyword === "pattern") {
|
|
528
|
+
return `${path9} has an invalid format`;
|
|
529
|
+
}
|
|
530
|
+
return `${path9 || "Config"} ${error.message || "is invalid"}`;
|
|
531
|
+
}
|
|
532
|
+
function applyOutputRetriesDefaults(retries) {
|
|
533
|
+
if (retries === void 0) return void 0;
|
|
534
|
+
return {
|
|
535
|
+
missingOrEmpty: retries.missingOrEmpty ?? DEFAULT_MISSING_OR_EMPTY_OUTPUT_RETRIES
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function applyStageAgentDefaults(agent) {
|
|
539
|
+
return {
|
|
540
|
+
...agent,
|
|
541
|
+
outputRetries: applyOutputRetriesDefaults(agent.outputRetries)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function assertRepoRelativePath(value, location) {
|
|
545
|
+
const normalized = normalize(value);
|
|
546
|
+
if (isAbsolute(value) || isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${sep}`)) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`${location} must stay within the repository and be repo-relative; got "${value}"`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function assertBaseBranch(value, location) {
|
|
553
|
+
if (value.includes("/")) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`${location} must be a local branch name, not a remote-qualified, tag, ref, or path-like name; got "${value}"`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
if (/^[0-9a-f]{7,40}$/i.test(value)) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`${location} must be a branch name, not a commit SHA; got "${value}"`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function assertStageAgentPath(agent, location) {
|
|
565
|
+
if (!agent) return;
|
|
566
|
+
const promptTemplate = agent.promptTemplate;
|
|
567
|
+
if (typeof promptTemplate === "string") {
|
|
568
|
+
assertRepoRelativePath(promptTemplate, `${location}.promptTemplate`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function validateConfigSemantics(data) {
|
|
572
|
+
const sandbox = data.sandbox;
|
|
573
|
+
const copyToWorktree = sandbox?.copyToWorktree;
|
|
574
|
+
copyToWorktree?.forEach((entry, index) => {
|
|
575
|
+
assertRepoRelativePath(entry, `sandbox.copyToWorktree[${index}]`);
|
|
576
|
+
});
|
|
577
|
+
const targetNames = /* @__PURE__ */ new Set();
|
|
578
|
+
data.targets.forEach((target, targetIndex) => {
|
|
579
|
+
const name = target.name;
|
|
580
|
+
if (targetNames.has(name)) {
|
|
581
|
+
throw new Error(
|
|
582
|
+
`Duplicate target name "${name}"; target names must be unique`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
targetNames.add(name);
|
|
586
|
+
assertBaseBranch(
|
|
587
|
+
target.baseBranch,
|
|
588
|
+
`targets[${targetIndex}].baseBranch`
|
|
589
|
+
);
|
|
590
|
+
const strategy = target.strategy;
|
|
591
|
+
const implement = strategy.implement;
|
|
592
|
+
const review = strategy.review;
|
|
593
|
+
const finalize = strategy.finalize;
|
|
594
|
+
assertStageAgentPath(
|
|
595
|
+
implement.builder,
|
|
596
|
+
`targets[${targetIndex}].strategy.implement.builder`
|
|
597
|
+
);
|
|
598
|
+
assertStageAgentPath(
|
|
599
|
+
strategy.conflictResolution,
|
|
600
|
+
`targets[${targetIndex}].strategy.conflictResolution`
|
|
601
|
+
);
|
|
602
|
+
assertStageAgentPath(
|
|
603
|
+
strategy.failureResolution,
|
|
604
|
+
`targets[${targetIndex}].strategy.failureResolution`
|
|
605
|
+
);
|
|
606
|
+
assertStageAgentPath(
|
|
607
|
+
review.reviewer,
|
|
608
|
+
`targets[${targetIndex}].strategy.review.reviewer`
|
|
609
|
+
);
|
|
610
|
+
assertStageAgentPath(
|
|
611
|
+
review.refactor,
|
|
612
|
+
`targets[${targetIndex}].strategy.review.refactor`
|
|
613
|
+
);
|
|
614
|
+
assertStageAgentPath(
|
|
615
|
+
strategy.issueFinalReview,
|
|
616
|
+
`targets[${targetIndex}].strategy.issueFinalReview`
|
|
617
|
+
);
|
|
618
|
+
assertStageAgentPath(
|
|
619
|
+
finalize.prDescriptionAgent,
|
|
620
|
+
`targets[${targetIndex}].strategy.finalize.prDescriptionAgent`
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
function assertKnownKeys(value, path9, knownKeys) {
|
|
625
|
+
for (const key of Object.keys(value)) {
|
|
626
|
+
if (!knownKeys.includes(key)) {
|
|
627
|
+
throw new Error(`${path9}.${key} is not supported`);
|
|
606
628
|
}
|
|
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
629
|
}
|
|
628
630
|
}
|
|
629
631
|
function parseConfig(raw) {
|
|
@@ -645,6 +647,7 @@ function parseConfig(raw) {
|
|
|
645
647
|
"name",
|
|
646
648
|
"baseBranch",
|
|
647
649
|
"branchTemplate",
|
|
650
|
+
"prdRun",
|
|
648
651
|
"setupCommands",
|
|
649
652
|
"autoMerge",
|
|
650
653
|
"queue",
|
|
@@ -660,9 +663,20 @@ function parseConfig(raw) {
|
|
|
660
663
|
`targets[${i}].strategy.conflictResolution has been removed; use targets[${i}].strategy.failureResolution`
|
|
661
664
|
);
|
|
662
665
|
}
|
|
663
|
-
if (strategy && typeof strategy === "object" && strategy.prdRun && typeof strategy.prdRun === "object"
|
|
666
|
+
if (strategy && typeof strategy === "object" && strategy.prdRun && typeof strategy.prdRun === "object") {
|
|
667
|
+
const prdRun = strategy.prdRun;
|
|
668
|
+
if ("reconciliation" in prdRun) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
`targets[${i}].strategy.prdRun.reconciliation has been removed; PRD Run no longer invokes Architect reconciliation.`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
if ("finalReview" in prdRun) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
`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.`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
664
678
|
throw new Error(
|
|
665
|
-
`targets[${i}].strategy.prdRun
|
|
679
|
+
`targets[${i}].strategy.prdRun is not supported in the new config; use targets[${i}].prdRun instead.`
|
|
666
680
|
);
|
|
667
681
|
}
|
|
668
682
|
}
|
|
@@ -672,67 +686,126 @@ function parseConfig(raw) {
|
|
|
672
686
|
"copyToWorktree",
|
|
673
687
|
"mounts",
|
|
674
688
|
"env",
|
|
675
|
-
"idleTimeoutSeconds"
|
|
689
|
+
"idleTimeoutSeconds",
|
|
690
|
+
"forceRebuild"
|
|
676
691
|
]);
|
|
677
692
|
}
|
|
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
|
|
693
|
+
const validate = getValidator();
|
|
694
|
+
if (!validate(raw)) {
|
|
695
|
+
throw new Error(
|
|
696
|
+
formatFirstAjvError(
|
|
697
|
+
_ajvErrors ?? [
|
|
698
|
+
{
|
|
699
|
+
instancePath: "",
|
|
700
|
+
message: "validation failed",
|
|
701
|
+
keyword: "error",
|
|
702
|
+
params: {}
|
|
727
703
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
704
|
+
]
|
|
705
|
+
)
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
const data = config;
|
|
709
|
+
validateConfigSemantics(data);
|
|
710
|
+
const targets = data.targets.map(
|
|
711
|
+
(t) => {
|
|
712
|
+
const input = t;
|
|
713
|
+
const strategy = input.strategy;
|
|
714
|
+
const implement = strategy.implement;
|
|
715
|
+
const failureResolution = strategy.failureResolution;
|
|
716
|
+
const review = strategy.review;
|
|
717
|
+
const reviewReviewer = review.reviewer;
|
|
718
|
+
const reviewRefactor = review.refactor;
|
|
719
|
+
const finalize = strategy.finalize;
|
|
720
|
+
const issueFinalReview = strategy.issueFinalReview;
|
|
721
|
+
const setupCommands = input.setupCommands?.map((cmd, i) => ({
|
|
722
|
+
command: cmd.command,
|
|
723
|
+
label: cmd.label ?? `check-${i}`
|
|
724
|
+
}));
|
|
725
|
+
const verifyCommands = strategy.verify?.commands ? strategy.verify.commands : void 0;
|
|
726
|
+
const verifyLabeled = verifyCommands?.map((cmd, i) => ({
|
|
727
|
+
command: cmd.command,
|
|
728
|
+
label: cmd.label ?? `check-${i}`
|
|
729
|
+
}));
|
|
730
|
+
return {
|
|
731
|
+
name: input.name,
|
|
732
|
+
baseBranch: input.baseBranch,
|
|
733
|
+
branchTemplate: input.branchTemplate ?? DEFAULT_BRANCH_TEMPLATE,
|
|
734
|
+
prdRun: input.prdRun,
|
|
735
|
+
setupCommands,
|
|
736
|
+
autoMerge: input.autoMerge !== void 0 ? input.autoMerge : true,
|
|
737
|
+
queue: input.queue,
|
|
738
|
+
serena: input.serena,
|
|
739
|
+
strategy: {
|
|
740
|
+
type: "review-refactor-loop",
|
|
741
|
+
implement: {
|
|
742
|
+
builder: applyStageAgentDefaults(
|
|
743
|
+
implement.builder
|
|
744
|
+
)
|
|
745
|
+
},
|
|
746
|
+
failureResolution: {
|
|
747
|
+
agent: failureResolution.agent,
|
|
748
|
+
model: failureResolution.model,
|
|
749
|
+
variant: failureResolution.variant,
|
|
750
|
+
env: failureResolution.env,
|
|
751
|
+
promptTemplate: failureResolution.promptTemplate,
|
|
752
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
753
|
+
failureResolution.outputRetries
|
|
754
|
+
),
|
|
755
|
+
maxAttemptsPerFailure: failureResolution.maxAttemptsPerFailure,
|
|
756
|
+
failureLimits: failureResolution.failureLimits
|
|
757
|
+
},
|
|
758
|
+
review: {
|
|
759
|
+
reviewer: {
|
|
760
|
+
agent: reviewReviewer.agent,
|
|
761
|
+
model: reviewReviewer.model,
|
|
762
|
+
variant: reviewReviewer.variant,
|
|
763
|
+
env: reviewReviewer.env,
|
|
764
|
+
promptTemplate: reviewReviewer.promptTemplate,
|
|
765
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
766
|
+
reviewReviewer.outputRetries
|
|
767
|
+
),
|
|
768
|
+
criteria: reviewReviewer.criteria,
|
|
769
|
+
includeReviewHistory: reviewReviewer.includeReviewHistory,
|
|
770
|
+
passWithNotesRefactorAttempts: reviewReviewer.passWithNotesRefactorAttempts
|
|
771
|
+
},
|
|
772
|
+
refactor: applyStageAgentDefaults(
|
|
773
|
+
reviewRefactor
|
|
774
|
+
),
|
|
775
|
+
maxIterations: review.maxIterations,
|
|
776
|
+
passWithNotesRefactorAttempts: review.passWithNotesRefactorAttempts ?? 2
|
|
777
|
+
},
|
|
778
|
+
...verifyLabeled ? { verify: { commands: verifyLabeled } } : {},
|
|
779
|
+
issueFinalReview: {
|
|
780
|
+
...issueFinalReview,
|
|
781
|
+
maxAttempts: issueFinalReview.maxAttempts,
|
|
782
|
+
outputRetries: applyOutputRetriesDefaults(
|
|
783
|
+
issueFinalReview.outputRetries
|
|
784
|
+
)
|
|
785
|
+
},
|
|
786
|
+
finalize: {
|
|
787
|
+
prDescriptionAgent: applyStageAgentDefaults(
|
|
788
|
+
finalize.prDescriptionAgent
|
|
789
|
+
),
|
|
790
|
+
maxAttempts: finalize.maxAttempts
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
);
|
|
796
|
+
const serenaRaw = data.serena;
|
|
797
|
+
const serenaDefaults = {
|
|
798
|
+
mcpUrl: serenaRaw?.mcpUrl ?? "http://localhost:9121/mcp",
|
|
799
|
+
sandboxMcpUrl: serenaRaw?.sandboxMcpUrl ?? "http://localhost:9121/mcp",
|
|
800
|
+
dataDir: serenaRaw?.dataDir ?? ".pourkit/serena/"
|
|
801
|
+
};
|
|
732
802
|
const serena = {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
803
|
+
enabled: serenaRaw?.enabled ?? false,
|
|
804
|
+
required: serenaRaw?.required ?? false,
|
|
805
|
+
mcpUrl: process.env.POURKIT_SERENA_MCP_URL ?? serenaDefaults.mcpUrl,
|
|
806
|
+
sandboxMcpUrl: process.env.POURKIT_SERENA_SANDBOX_MCP_URL ?? serenaDefaults.sandboxMcpUrl,
|
|
807
|
+
dataDir: serenaDefaults.dataDir,
|
|
808
|
+
autoStart: serenaRaw?.autoStart ?? false
|
|
736
809
|
};
|
|
737
810
|
if (serena.mcpUrl.trim() === "") {
|
|
738
811
|
throw new Error("POURKIT_SERENA_MCP_URL must be a non-empty string");
|
|
@@ -742,39 +815,48 @@ function parseConfig(raw) {
|
|
|
742
815
|
"POURKIT_SERENA_SANDBOX_MCP_URL must be a non-empty string"
|
|
743
816
|
);
|
|
744
817
|
}
|
|
818
|
+
const checksRaw = data.checks;
|
|
819
|
+
const labelsRaw = data.labels;
|
|
820
|
+
const cleanupRaw = data.cleanup;
|
|
821
|
+
const sandboxRaw = data.sandbox;
|
|
745
822
|
return {
|
|
746
823
|
targets,
|
|
747
|
-
labels:
|
|
824
|
+
labels: {
|
|
825
|
+
readyForAgent: labelsRaw?.readyForAgent ?? "ready-for-agent",
|
|
826
|
+
agentInProgress: labelsRaw?.agentInProgress ?? "agent-in-progress",
|
|
827
|
+
blocked: labelsRaw?.blocked ?? "blocked",
|
|
828
|
+
prOpenAwaitingMerge: labelsRaw?.prOpenAwaitingMerge ?? "pr-open-awaiting-merge",
|
|
829
|
+
readyForHuman: labelsRaw?.readyForHuman ?? "ready-for-human",
|
|
830
|
+
needsTriage: labelsRaw?.needsTriage ?? "needs-triage"
|
|
831
|
+
},
|
|
748
832
|
sandbox: {
|
|
749
|
-
provider:
|
|
750
|
-
copyToWorktree:
|
|
751
|
-
mounts:
|
|
752
|
-
|
|
753
|
-
|
|
833
|
+
provider: sandboxRaw?.provider ?? "docker",
|
|
834
|
+
copyToWorktree: sandboxRaw?.copyToWorktree,
|
|
835
|
+
mounts: sandboxRaw?.mounts ? sandboxRaw.mounts.map((m) => ({
|
|
836
|
+
hostPath: m.hostPath,
|
|
837
|
+
sandboxPath: m.sandboxPath,
|
|
838
|
+
readonly: m.readonly ?? false
|
|
839
|
+
})) : void 0,
|
|
840
|
+
env: sandboxRaw?.env,
|
|
841
|
+
idleTimeoutSeconds: sandboxRaw?.idleTimeoutSeconds,
|
|
842
|
+
forceRebuild: sandboxRaw?.forceRebuild
|
|
754
843
|
},
|
|
755
844
|
checks: {
|
|
756
|
-
requiredLabels:
|
|
757
|
-
allowedAuthors:
|
|
758
|
-
checksFoundTimeoutSeconds:
|
|
759
|
-
checksCompletionTimeoutSeconds:
|
|
760
|
-
pollIntervalSeconds:
|
|
761
|
-
issueListLimit:
|
|
845
|
+
requiredLabels: checksRaw?.requiredLabels ?? [],
|
|
846
|
+
allowedAuthors: checksRaw?.allowedAuthors ?? [],
|
|
847
|
+
checksFoundTimeoutSeconds: checksRaw?.checksFoundTimeoutSeconds ?? 60,
|
|
848
|
+
checksCompletionTimeoutSeconds: checksRaw?.checksCompletionTimeoutSeconds ?? 30 * 60,
|
|
849
|
+
pollIntervalSeconds: checksRaw?.pollIntervalSeconds ?? 15,
|
|
850
|
+
issueListLimit: checksRaw?.issueListLimit ?? 50
|
|
762
851
|
},
|
|
763
852
|
serena,
|
|
764
853
|
cleanup: {
|
|
765
|
-
enabled:
|
|
766
|
-
worktreeRetentionDays:
|
|
767
|
-
logRetentionDays:
|
|
854
|
+
enabled: cleanupRaw?.enabled ?? true,
|
|
855
|
+
worktreeRetentionDays: cleanupRaw?.worktreeRetentionDays ?? 14,
|
|
856
|
+
logRetentionDays: cleanupRaw?.logRetentionDays ?? 30
|
|
768
857
|
}
|
|
769
858
|
};
|
|
770
859
|
}
|
|
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
860
|
function getVerificationCommands(target) {
|
|
779
861
|
return target.strategy.verify?.commands ?? [];
|
|
780
862
|
}
|
|
@@ -782,7 +864,7 @@ function resolvePrdRunMode(target, opts) {
|
|
|
782
864
|
if (opts?.localOverride === true) {
|
|
783
865
|
return { mode: "local", source: "cli-override", targetName: target.name };
|
|
784
866
|
}
|
|
785
|
-
const configMode = target.
|
|
867
|
+
const configMode = target.prdRun?.mode;
|
|
786
868
|
if (configMode) {
|
|
787
869
|
return {
|
|
788
870
|
mode: configMode,
|
|
@@ -792,48 +874,41 @@ function resolvePrdRunMode(target, opts) {
|
|
|
792
874
|
}
|
|
793
875
|
return { mode: "github", source: "default", targetName: target.name };
|
|
794
876
|
}
|
|
795
|
-
|
|
877
|
+
var OBSOLETE_CONFIG_PATHS = [
|
|
878
|
+
"pourkit.config.ts",
|
|
879
|
+
"pourkit.config.mjs",
|
|
880
|
+
"pourkit.config.js",
|
|
881
|
+
"pourkit.json"
|
|
882
|
+
];
|
|
883
|
+
var CANONICAL_CONFIG_PATH = ".pourkit/config.json";
|
|
884
|
+
async function loadRepoConfig(repoRoot2, _configFileName) {
|
|
796
885
|
const { existsSync: existsSync19 } = await import("fs");
|
|
797
|
-
const {
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
886
|
+
const { join: pjoin } = await import("path");
|
|
887
|
+
for (const obPath of OBSOLETE_CONFIG_PATHS) {
|
|
888
|
+
const fullPath = pjoin(repoRoot2, obPath);
|
|
889
|
+
if (existsSync19(fullPath)) {
|
|
890
|
+
const isRootJson = obPath === "pourkit.json";
|
|
891
|
+
throw new Error(
|
|
892
|
+
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".`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const configPath = pjoin(repoRoot2, CANONICAL_CONFIG_PATH);
|
|
802
897
|
if (!existsSync19(configPath)) {
|
|
803
898
|
throw new Error(
|
|
804
|
-
`No config
|
|
899
|
+
`No Pourkit config found at ${CANONICAL_CONFIG_PATH}. Run pourkit init or create ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
|
|
805
900
|
);
|
|
806
901
|
}
|
|
807
|
-
const
|
|
808
|
-
await
|
|
809
|
-
|
|
810
|
-
tmpDir,
|
|
811
|
-
`pourkit-config-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.mjs`
|
|
812
|
-
);
|
|
902
|
+
const { readFile: readFile7 } = await import("fs/promises");
|
|
903
|
+
const raw = await readFile7(configPath, "utf-8");
|
|
904
|
+
let parsed;
|
|
813
905
|
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
|
-
}
|
|
906
|
+
parsed = JSON.parse(raw);
|
|
907
|
+
} catch (err) {
|
|
908
|
+
const message = err instanceof SyntaxError ? err.message : String(err);
|
|
909
|
+
throw new Error(`${CANONICAL_CONFIG_PATH}: Invalid JSON \u2014 ${message}`);
|
|
836
910
|
}
|
|
911
|
+
return parseConfig(parsed);
|
|
837
912
|
}
|
|
838
913
|
function resolvePromptTemplatePath(repoRoot2, promptTemplate) {
|
|
839
914
|
if (promptTemplate.includes("/")) {
|
|
@@ -872,8 +947,8 @@ function resolveTarget(config, explicitTarget) {
|
|
|
872
947
|
init_common();
|
|
873
948
|
|
|
874
949
|
// shared/worktree-run-state.ts
|
|
875
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
876
|
-
import { dirname, join as join2 } from "path";
|
|
950
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
951
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
877
952
|
var WORKTREE_RUN_STATE_PATH = ".pourkit/state.json";
|
|
878
953
|
function readWorktreeRunState(worktreePath) {
|
|
879
954
|
const statePath = join2(worktreePath, WORKTREE_RUN_STATE_PATH);
|
|
@@ -881,7 +956,7 @@ function readWorktreeRunState(worktreePath) {
|
|
|
881
956
|
return null;
|
|
882
957
|
}
|
|
883
958
|
try {
|
|
884
|
-
const raw = JSON.parse(
|
|
959
|
+
const raw = JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
885
960
|
if (isValidWorktreeRunState(raw)) {
|
|
886
961
|
return raw;
|
|
887
962
|
}
|
|
@@ -902,7 +977,7 @@ function isValidWorktreeRunState(raw) {
|
|
|
902
977
|
}
|
|
903
978
|
function writeWorktreeRunState(worktreePath, state) {
|
|
904
979
|
const statePath = join2(worktreePath, WORKTREE_RUN_STATE_PATH);
|
|
905
|
-
mkdirSync2(
|
|
980
|
+
mkdirSync2(dirname2(statePath), { recursive: true });
|
|
906
981
|
writeFileSync(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
907
982
|
}
|
|
908
983
|
function updateWorktreeRunState(worktreePath, update) {
|
|
@@ -1049,8 +1124,8 @@ async function cleanupRepository(options) {
|
|
|
1049
1124
|
// commands/artifact-validation.ts
|
|
1050
1125
|
import { createHash } from "crypto";
|
|
1051
1126
|
import { execSync } from "child_process";
|
|
1052
|
-
import { existsSync as
|
|
1053
|
-
import { isAbsolute as
|
|
1127
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
1128
|
+
import { isAbsolute as isAbsolute3, join as join7, resolve as resolve3 } from "path";
|
|
1054
1129
|
|
|
1055
1130
|
// pr/review-verdict.ts
|
|
1056
1131
|
var ReviewVerdictProtocolError = class extends Error {
|
|
@@ -1085,15 +1160,15 @@ function parseReviewVerdict(output) {
|
|
|
1085
1160
|
import {
|
|
1086
1161
|
existsSync as existsSync5,
|
|
1087
1162
|
mkdirSync as mkdirSync5,
|
|
1088
|
-
readFileSync as
|
|
1163
|
+
readFileSync as readFileSync6,
|
|
1089
1164
|
readdirSync as readdirSync2,
|
|
1090
1165
|
writeFileSync as writeFileSync3
|
|
1091
1166
|
} from "fs";
|
|
1092
1167
|
import { join as join6 } from "path";
|
|
1093
1168
|
|
|
1094
1169
|
// execution/agent-output-retry.ts
|
|
1095
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as
|
|
1096
|
-
import { dirname as
|
|
1170
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
1171
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
1097
1172
|
async function executeWithMissingOrEmptyArtifactRetry({
|
|
1098
1173
|
executionProvider,
|
|
1099
1174
|
executionOptions,
|
|
@@ -1153,20 +1228,20 @@ function readArtifactOutput(artifactPath) {
|
|
|
1153
1228
|
if (!existsSync2(artifactPath)) {
|
|
1154
1229
|
return { _tag: "missing", path: artifactPath };
|
|
1155
1230
|
}
|
|
1156
|
-
const output =
|
|
1231
|
+
const output = readFileSync3(artifactPath, "utf-8");
|
|
1157
1232
|
if (!output.trim()) {
|
|
1158
1233
|
return { _tag: "empty", path: artifactPath };
|
|
1159
1234
|
}
|
|
1160
1235
|
return { _tag: "content", value: output, path: artifactPath };
|
|
1161
1236
|
}
|
|
1162
1237
|
function prepareArtifactPath(artifactPath) {
|
|
1163
|
-
mkdirSync3(
|
|
1238
|
+
mkdirSync3(dirname3(artifactPath), { recursive: true });
|
|
1164
1239
|
rmSync(artifactPath, { recursive: true, force: true });
|
|
1165
1240
|
}
|
|
1166
1241
|
|
|
1167
1242
|
// shared/run-context.ts
|
|
1168
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1169
|
-
import { isAbsolute, join as join5, relative, resolve } from "path";
|
|
1243
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
1244
|
+
import { isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve2 } from "path";
|
|
1170
1245
|
|
|
1171
1246
|
// commands/run-verification.ts
|
|
1172
1247
|
init_common();
|
|
@@ -1314,13 +1389,6 @@ var STAGE_SECTIONS = {
|
|
|
1314
1389
|
"branch",
|
|
1315
1390
|
"verification-commands",
|
|
1316
1391
|
"artifacts"
|
|
1317
|
-
],
|
|
1318
|
-
prdReconciliation: [
|
|
1319
|
-
"issue",
|
|
1320
|
-
"comments",
|
|
1321
|
-
"branch",
|
|
1322
|
-
"verification-commands",
|
|
1323
|
-
"artifacts"
|
|
1324
1392
|
]
|
|
1325
1393
|
};
|
|
1326
1394
|
function buildRunContextArtifact(options) {
|
|
@@ -1372,11 +1440,17 @@ function buildRunContextMarkdown(options) {
|
|
|
1372
1440
|
}
|
|
1373
1441
|
}
|
|
1374
1442
|
if (sections.includes("branch")) {
|
|
1443
|
+
const canonicalBaseRef = `origin/${target.baseBranch}`;
|
|
1375
1444
|
parts.push(
|
|
1376
1445
|
"## Branch",
|
|
1377
1446
|
"",
|
|
1378
1447
|
`- Base: ${target.baseBranch}`,
|
|
1448
|
+
`- Canonical Base Ref: ${canonicalBaseRef}`,
|
|
1379
1449
|
`- Working Branch: ${branchName}`,
|
|
1450
|
+
"",
|
|
1451
|
+
"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.",
|
|
1452
|
+
"",
|
|
1453
|
+
"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
1454
|
""
|
|
1381
1455
|
);
|
|
1382
1456
|
}
|
|
@@ -1439,7 +1513,7 @@ function renderPrdContext(issue, parentPrdIssue, repoRoot2) {
|
|
|
1439
1513
|
`### Parent PRD Content: \`${relative(repoRoot2, parentPrdPath)}\``,
|
|
1440
1514
|
"",
|
|
1441
1515
|
"```markdown",
|
|
1442
|
-
|
|
1516
|
+
readFileSync4(parentPrdPath, "utf-8").trimEnd(),
|
|
1443
1517
|
"```",
|
|
1444
1518
|
""
|
|
1445
1519
|
);
|
|
@@ -1466,7 +1540,7 @@ function renderPrdContext(issue, parentPrdIssue, repoRoot2) {
|
|
|
1466
1540
|
`### Document Content: \`${documentPath}\``,
|
|
1467
1541
|
"",
|
|
1468
1542
|
"```markdown",
|
|
1469
|
-
|
|
1543
|
+
readFileSync4(absolutePath, "utf-8").trimEnd(),
|
|
1470
1544
|
"```",
|
|
1471
1545
|
""
|
|
1472
1546
|
);
|
|
@@ -1491,34 +1565,27 @@ function extractRepoPaths(section) {
|
|
|
1491
1565
|
return Array.from(paths);
|
|
1492
1566
|
}
|
|
1493
1567
|
function resolveRepoPath(repoRoot2, path9) {
|
|
1494
|
-
if (
|
|
1495
|
-
const resolved =
|
|
1568
|
+
if (isAbsolute2(path9) || path9.includes("\0")) return null;
|
|
1569
|
+
const resolved = resolve2(repoRoot2, path9);
|
|
1496
1570
|
const repoRelative2 = relative(repoRoot2, resolved);
|
|
1497
|
-
if (repoRelative2.startsWith("..") ||
|
|
1571
|
+
if (repoRelative2.startsWith("..") || isAbsolute2(repoRelative2)) return null;
|
|
1498
1572
|
return resolved;
|
|
1499
1573
|
}
|
|
1500
1574
|
function findParentPrdPath(repoRoot2, parentRef) {
|
|
1501
|
-
const
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
"architecture",
|
|
1505
|
-
parentRef,
|
|
1506
|
-
"PRD.md"
|
|
1507
|
-
);
|
|
1508
|
-
if (existsSync3(directPath)) return directPath;
|
|
1509
|
-
const architectureRoot = join5(repoRoot2, ".pourkit", "architecture");
|
|
1510
|
-
if (!existsSync3(architectureRoot)) return null;
|
|
1511
|
-
return findPrdMirror(architectureRoot, parentRef);
|
|
1575
|
+
const plansRoot = join5(repoRoot2, ".pourkit", "plans");
|
|
1576
|
+
if (!existsSync3(plansRoot)) return null;
|
|
1577
|
+
return findPrdInPlans(plansRoot, parentRef);
|
|
1512
1578
|
}
|
|
1513
|
-
function
|
|
1579
|
+
function findPrdInPlans(directory, parentRef) {
|
|
1514
1580
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
1515
1581
|
const entryPath = join5(directory, entry.name);
|
|
1516
1582
|
if (entry.isDirectory()) {
|
|
1517
|
-
|
|
1583
|
+
const prdNumber = parentRef.match(/^PRD-(\d+)$/)?.[1];
|
|
1584
|
+
if (entry.name.startsWith(parentRef) || prdNumber && entry.name.startsWith(`${prdNumber}-`)) {
|
|
1518
1585
|
const prdPath = join5(entryPath, "PRD.md");
|
|
1519
1586
|
if (existsSync3(prdPath)) return prdPath;
|
|
1520
1587
|
}
|
|
1521
|
-
const nested =
|
|
1588
|
+
const nested = findPrdInPlans(entryPath, parentRef);
|
|
1522
1589
|
if (nested) return nested;
|
|
1523
1590
|
}
|
|
1524
1591
|
}
|
|
@@ -1538,17 +1605,19 @@ function renderCriteria(criteria) {
|
|
|
1538
1605
|
|
|
1539
1606
|
// shared/prompt-guidance.ts
|
|
1540
1607
|
var PROTECTED_WORK_RULE = "Do **not** revert, delete, or substantially strip already-landed protected sibling/base work unless the issue explicitly requires those files.";
|
|
1608
|
+
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
1609
|
function appendProtectedWorkGuidance(promptBody) {
|
|
1542
1610
|
return `${promptBody}
|
|
1543
1611
|
|
|
1544
1612
|
## Hard Rule
|
|
1545
1613
|
|
|
1546
|
-
- ${PROTECTED_WORK_RULE}
|
|
1614
|
+
- ${PROTECTED_WORK_RULE}
|
|
1615
|
+
- ${RUNNER_OWNED_GIT_RULE}`;
|
|
1547
1616
|
}
|
|
1548
1617
|
|
|
1549
1618
|
// shared/effect-services.ts
|
|
1550
1619
|
import { Context, Effect, Layer } from "effect";
|
|
1551
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as
|
|
1620
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
|
|
1552
1621
|
var GitExecutionError = class extends Error {
|
|
1553
1622
|
_tag = "GitExecutionError";
|
|
1554
1623
|
message;
|
|
@@ -1564,7 +1633,7 @@ var FileSystemDefault = Layer.succeed(
|
|
|
1564
1633
|
FileSystem,
|
|
1565
1634
|
FileSystem.of({
|
|
1566
1635
|
readFile: (path9) => Effect.try({
|
|
1567
|
-
try: () =>
|
|
1636
|
+
try: () => readFileSync5(path9, "utf-8"),
|
|
1568
1637
|
catch: (error) => new Error(
|
|
1569
1638
|
`Failed to read file ${path9}: ${error instanceof Error ? error.message : String(error)}`
|
|
1570
1639
|
)
|
|
@@ -1965,7 +2034,7 @@ function validateRefactorArtifact(artifactPath, findingIds) {
|
|
|
1965
2034
|
`Refactor artifact missing at ${artifactPath}`
|
|
1966
2035
|
);
|
|
1967
2036
|
}
|
|
1968
|
-
const content =
|
|
2037
|
+
const content = readFileSync6(artifactPath, "utf-8");
|
|
1969
2038
|
if (!content.trim()) {
|
|
1970
2039
|
throw new RefactorArtifactValidationError("Refactor artifact is empty");
|
|
1971
2040
|
}
|
|
@@ -2290,12 +2359,29 @@ A prior review emitted \`NEEDS_HUMAN\` and stopped the agent loop. The issue has
|
|
|
2290
2359
|
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
2360
|
|
|
2292
2361
|
` : "";
|
|
2293
|
-
return `${renderedTemplate}
|
|
2362
|
+
return appendProtectedWorkGuidance(`${renderedTemplate}
|
|
2294
2363
|
|
|
2295
2364
|
## Shared Run Context
|
|
2296
2365
|
|
|
2297
2366
|
Read the selected issue requirements, PRD context, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
2298
2367
|
|
|
2368
|
+
## Initial Verification Pass
|
|
2369
|
+
|
|
2370
|
+
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
2371
|
+
- Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
|
|
2372
|
+
- Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
|
|
2373
|
+
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
2374
|
+
- 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.
|
|
2375
|
+
- If no verification commands are configured, note that and proceed with normal review.
|
|
2376
|
+
|
|
2377
|
+
## Scope Evidence Rules
|
|
2378
|
+
|
|
2379
|
+
- Use the Run Context's canonical base ref, for example \`origin/<base>\`, for scope diffs and commit ranges.
|
|
2380
|
+
- 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.
|
|
2381
|
+
- Only call a file or commit out of scope when it is part of the working branch's delta from the canonical base ref.
|
|
2382
|
+
- 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.
|
|
2383
|
+
- If scope evidence is ambiguous, use \`NEEDS_HUMAN\` or ask for a runner/base mismatch decision instead of telling Refactor to revert files.
|
|
2384
|
+
|
|
2299
2385
|
${hasCriteriaPlaceholder ? "" : `## Review Criteria
|
|
2300
2386
|
|
|
2301
2387
|
${criteriaBlock}
|
|
@@ -2312,7 +2398,7 @@ End the file with exactly one wrapped verdict token: <verdict>PASS</verdict>, <v
|
|
|
2312
2398
|
|
|
2313
2399
|
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
2400
|
|
|
2315
|
-
When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token
|
|
2401
|
+
When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token.`);
|
|
2316
2402
|
});
|
|
2317
2403
|
}
|
|
2318
2404
|
function renderReviewHistory(reviewHistory) {
|
|
@@ -2437,12 +2523,12 @@ function recoverReviewOutputFromLog(logPath) {
|
|
|
2437
2523
|
if (!existsSync5(logPath)) {
|
|
2438
2524
|
return null;
|
|
2439
2525
|
}
|
|
2440
|
-
const logContent =
|
|
2526
|
+
const logContent = readFileSync6(logPath, "utf-8");
|
|
2441
2527
|
return recoverReviewOutputFromString(logContent);
|
|
2442
2528
|
}
|
|
2443
2529
|
function readReviewArtifact(artifactPath, logPath) {
|
|
2444
2530
|
if (existsSync5(artifactPath)) {
|
|
2445
|
-
const output =
|
|
2531
|
+
const output = readFileSync6(artifactPath, "utf-8");
|
|
2446
2532
|
if (output.trim()) {
|
|
2447
2533
|
return output;
|
|
2448
2534
|
}
|
|
@@ -3091,194 +3177,6 @@ function parseConflictResolutionArtifact(output) {
|
|
|
3091
3177
|
};
|
|
3092
3178
|
}
|
|
3093
3179
|
|
|
3094
|
-
// prd-run/final-review-validation.ts
|
|
3095
|
-
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
3096
|
-
function parseFinalReviewArtifact(artifactPath) {
|
|
3097
|
-
if (!existsSync6(artifactPath)) {
|
|
3098
|
-
return {
|
|
3099
|
-
ok: false,
|
|
3100
|
-
reason: "Final Review artifact not found.",
|
|
3101
|
-
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
3102
|
-
};
|
|
3103
|
-
}
|
|
3104
|
-
let content;
|
|
3105
|
-
try {
|
|
3106
|
-
content = readFileSync6(artifactPath, "utf-8");
|
|
3107
|
-
} catch (error) {
|
|
3108
|
-
return {
|
|
3109
|
-
ok: false,
|
|
3110
|
-
reason: "Final Review artifact could not be read.",
|
|
3111
|
-
diagnostics: [
|
|
3112
|
-
error instanceof Error ? error.message : String(error),
|
|
3113
|
-
`Artifact path: ${artifactPath}`
|
|
3114
|
-
]
|
|
3115
|
-
};
|
|
3116
|
-
}
|
|
3117
|
-
let parsed;
|
|
3118
|
-
try {
|
|
3119
|
-
parsed = JSON.parse(content);
|
|
3120
|
-
} catch {
|
|
3121
|
-
return {
|
|
3122
|
-
ok: false,
|
|
3123
|
-
reason: "Final Review artifact is not valid JSON.",
|
|
3124
|
-
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
3125
|
-
};
|
|
3126
|
-
}
|
|
3127
|
-
const verdict = parsed.verdict;
|
|
3128
|
-
if (!verdict || typeof verdict !== "string") {
|
|
3129
|
-
return {
|
|
3130
|
-
ok: false,
|
|
3131
|
-
reason: "Final Review artifact is missing a verdict field.",
|
|
3132
|
-
diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
|
|
3133
|
-
};
|
|
3134
|
-
}
|
|
3135
|
-
const allowedVerdicts = [
|
|
3136
|
-
"pass_no_changes",
|
|
3137
|
-
"pass_with_retouch",
|
|
3138
|
-
"needs_human_review",
|
|
3139
|
-
"blocked"
|
|
3140
|
-
];
|
|
3141
|
-
if (!allowedVerdicts.includes(verdict)) {
|
|
3142
|
-
return {
|
|
3143
|
-
ok: false,
|
|
3144
|
-
reason: `Final Review artifact has unsupported verdict "${verdict}".`,
|
|
3145
|
-
diagnostics: [`Allowed verdicts: ${allowedVerdicts.join(", ")}`]
|
|
3146
|
-
};
|
|
3147
|
-
}
|
|
3148
|
-
const summary = typeof parsed.summary === "string" ? parsed.summary : typeof parsed.reason === "string" ? parsed.reason : String(verdict);
|
|
3149
|
-
const diagnostics = Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
|
|
3150
|
-
const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
|
|
3151
|
-
const isAutoSummary = !parsed.summary && !parsed.reason;
|
|
3152
|
-
if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
|
|
3153
|
-
return {
|
|
3154
|
-
ok: false,
|
|
3155
|
-
reason: 'Final Review artifact with "pass_with_retouch" verdict requires changed paths.',
|
|
3156
|
-
diagnostics: [
|
|
3157
|
-
"pass_with_retouch verdict must include a changedPaths array in the artifact or provide a summary/reason with enough context for git diff fallback.",
|
|
3158
|
-
"Provide changedPaths or a descriptive summary in the artifact."
|
|
3159
|
-
]
|
|
3160
|
-
};
|
|
3161
|
-
}
|
|
3162
|
-
const prdRef = typeof parsed.prdRef === "string" && parsed.prdRef.trim() ? parsed.prdRef.trim() : void 0;
|
|
3163
|
-
const stage = typeof parsed.stage === "string" && parsed.stage.trim() ? parsed.stage.trim() : void 0;
|
|
3164
|
-
const checkoutBase = typeof parsed.checkoutBase === "string" && parsed.checkoutBase.trim() ? parsed.checkoutBase.trim() : void 0;
|
|
3165
|
-
const reviewBase = typeof parsed.reviewBase === "string" && parsed.reviewBase.trim() ? parsed.reviewBase.trim() : void 0;
|
|
3166
|
-
return {
|
|
3167
|
-
ok: true,
|
|
3168
|
-
verdict,
|
|
3169
|
-
summary,
|
|
3170
|
-
diagnostics,
|
|
3171
|
-
changedPaths,
|
|
3172
|
-
prdRef,
|
|
3173
|
-
stage,
|
|
3174
|
-
checkoutBase,
|
|
3175
|
-
reviewBase
|
|
3176
|
-
};
|
|
3177
|
-
}
|
|
3178
|
-
function validateFinalReviewArtifactSemanticIds(artifact, context) {
|
|
3179
|
-
const errors = [];
|
|
3180
|
-
if (!artifact.prdRef) {
|
|
3181
|
-
errors.push("Final Review artifact is missing prdRef.");
|
|
3182
|
-
} else if (artifact.prdRef !== context.prdRef) {
|
|
3183
|
-
errors.push(
|
|
3184
|
-
`Final Review artifact prdRef "${artifact.prdRef}" does not match active PRD Run "${context.prdRef}".`
|
|
3185
|
-
);
|
|
3186
|
-
}
|
|
3187
|
-
if (!artifact.stage) {
|
|
3188
|
-
errors.push("Final Review artifact is missing stage field.");
|
|
3189
|
-
} else if (artifact.stage !== "prdFinalReview") {
|
|
3190
|
-
errors.push(
|
|
3191
|
-
`Final Review artifact stage "${artifact.stage}" is not "prdFinalReview".`
|
|
3192
|
-
);
|
|
3193
|
-
}
|
|
3194
|
-
if (!artifact.checkoutBase) {
|
|
3195
|
-
errors.push("Final Review artifact is missing checkoutBase.");
|
|
3196
|
-
} else if (artifact.checkoutBase !== context.prdBranch) {
|
|
3197
|
-
errors.push(
|
|
3198
|
-
`Final Review artifact checkoutBase "${artifact.checkoutBase}" does not match active PRD Branch "${context.prdBranch}".`
|
|
3199
|
-
);
|
|
3200
|
-
}
|
|
3201
|
-
if (!artifact.reviewBase) {
|
|
3202
|
-
errors.push("Final Review artifact is missing reviewBase.");
|
|
3203
|
-
} else if (artifact.reviewBase !== context.mergeBase) {
|
|
3204
|
-
errors.push(
|
|
3205
|
-
`Final Review artifact reviewBase "${artifact.reviewBase}" does not match active merge base "${context.mergeBase}".`
|
|
3206
|
-
);
|
|
3207
|
-
}
|
|
3208
|
-
if (errors.length > 0) {
|
|
3209
|
-
return { ok: false, errors, blockedGate: "final-review" };
|
|
3210
|
-
}
|
|
3211
|
-
return { ok: true };
|
|
3212
|
-
}
|
|
3213
|
-
function validateFinalReviewRetouchScope(changedPaths) {
|
|
3214
|
-
if (!changedPaths || changedPaths.length === 0) {
|
|
3215
|
-
return {
|
|
3216
|
-
ok: false,
|
|
3217
|
-
reason: "Final Review retouch scope validation failed. No changed paths available. Provide changed paths or run Final Review with a summary that includes changed paths.",
|
|
3218
|
-
diagnostics: ["Changed paths list is empty or undefined."],
|
|
3219
|
-
offendingPaths: []
|
|
3220
|
-
};
|
|
3221
|
-
}
|
|
3222
|
-
const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
|
|
3223
|
-
const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
|
|
3224
|
-
if (allowedPaths.length === 0) {
|
|
3225
|
-
return {
|
|
3226
|
-
ok: false,
|
|
3227
|
-
reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
|
|
3228
|
-
diagnostics: [
|
|
3229
|
-
"All changed paths are prohibited for retouch.",
|
|
3230
|
-
...changedPaths.map((p) => ` - ${p}`)
|
|
3231
|
-
],
|
|
3232
|
-
offendingPaths
|
|
3233
|
-
};
|
|
3234
|
-
}
|
|
3235
|
-
if (offendingPaths.length > 0) {
|
|
3236
|
-
return {
|
|
3237
|
-
ok: false,
|
|
3238
|
-
reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
|
|
3239
|
-
diagnostics: [
|
|
3240
|
-
"Prohibited paths found:",
|
|
3241
|
-
...offendingPaths.map((p) => ` - ${p}`)
|
|
3242
|
-
],
|
|
3243
|
-
offendingPaths
|
|
3244
|
-
};
|
|
3245
|
-
}
|
|
3246
|
-
return { ok: true, changedPaths };
|
|
3247
|
-
}
|
|
3248
|
-
function validateFinalReviewAgentOutputs(options) {
|
|
3249
|
-
const artifact = parseFinalReviewArtifact(options.artifactPath);
|
|
3250
|
-
if (!artifact.ok) {
|
|
3251
|
-
return { ...artifact, offendingPaths: [] };
|
|
3252
|
-
}
|
|
3253
|
-
const semanticResult = validateFinalReviewArtifactSemanticIds(
|
|
3254
|
-
artifact,
|
|
3255
|
-
options.context
|
|
3256
|
-
);
|
|
3257
|
-
if (!semanticResult.ok) {
|
|
3258
|
-
return {
|
|
3259
|
-
ok: false,
|
|
3260
|
-
reason: "Final Review artifact has mismatched semantic IDs.",
|
|
3261
|
-
diagnostics: semanticResult.errors,
|
|
3262
|
-
offendingPaths: []
|
|
3263
|
-
};
|
|
3264
|
-
}
|
|
3265
|
-
if (artifact.verdict !== "pass_with_retouch") {
|
|
3266
|
-
return { ok: true, artifact };
|
|
3267
|
-
}
|
|
3268
|
-
const changedPaths = options.changedPaths && options.changedPaths.length > 0 ? options.changedPaths : artifact.changedPaths;
|
|
3269
|
-
const retouch = validateFinalReviewRetouchScope(changedPaths ?? []);
|
|
3270
|
-
if (!retouch.ok) {
|
|
3271
|
-
return retouch;
|
|
3272
|
-
}
|
|
3273
|
-
return { ok: true, artifact, retouch };
|
|
3274
|
-
}
|
|
3275
|
-
function isRetouchScopePath(path9) {
|
|
3276
|
-
if (path9.startsWith(".pourkit/plans/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/") || /^\.pourkit\/prd-runs\/[^/]+\.json$/.test(path9)) {
|
|
3277
|
-
return false;
|
|
3278
|
-
}
|
|
3279
|
-
return true;
|
|
3280
|
-
}
|
|
3281
|
-
|
|
3282
3180
|
// commands/artifact-validation.ts
|
|
3283
3181
|
var CONFLICT_MARKER_PATTERN = /<<<<<<<|=======|>>>>>>>/m;
|
|
3284
3182
|
function invalid(options, reason, diagnostics = []) {
|
|
@@ -3299,7 +3197,7 @@ function valid(options, diagnostics = []) {
|
|
|
3299
3197
|
};
|
|
3300
3198
|
}
|
|
3301
3199
|
function readArtifact(options) {
|
|
3302
|
-
if (!
|
|
3200
|
+
if (!existsSync6(options.artifactPath)) {
|
|
3303
3201
|
return {
|
|
3304
3202
|
ok: false,
|
|
3305
3203
|
result: invalid(options, `Artifact missing at ${options.artifactPath}`)
|
|
@@ -3376,7 +3274,7 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3376
3274
|
for (const p of parsed.changedPaths) {
|
|
3377
3275
|
const normalized = p.replace(/\\/g, "/");
|
|
3378
3276
|
const segments = normalized.split("/");
|
|
3379
|
-
if (normalized.trim() === "" || normalized === "." || normalized === ".." ||
|
|
3277
|
+
if (normalized.trim() === "" || normalized === "." || normalized === ".." || isAbsolute3(p) || normalized.startsWith("/") || segments.some((segment) => segment === "..")) {
|
|
3380
3278
|
return {
|
|
3381
3279
|
ok: false,
|
|
3382
3280
|
reason: `changedPaths must not contain absolute paths or path traversal: ${p}`,
|
|
@@ -3500,7 +3398,7 @@ function validateAgentArtifact(options) {
|
|
|
3500
3398
|
if (options.checkConflictMarkers !== false && parsed.status === "resolved") {
|
|
3501
3399
|
const base = options.worktreePath ?? process.cwd();
|
|
3502
3400
|
const filesWithMarkers = parsed.files.filter((file) => {
|
|
3503
|
-
const filePath =
|
|
3401
|
+
const filePath = resolve3(base, file);
|
|
3504
3402
|
try {
|
|
3505
3403
|
return CONFLICT_MARKER_PATTERN.test(
|
|
3506
3404
|
readFileSync7(filePath, "utf-8")
|
|
@@ -3519,29 +3417,6 @@ function validateAgentArtifact(options) {
|
|
|
3519
3417
|
}
|
|
3520
3418
|
return valid(options, [`status: ${parsed.status}`]);
|
|
3521
3419
|
}
|
|
3522
|
-
// QUARANTINED: Retained for Issue Final Review artifact validation via
|
|
3523
|
-
// `runPrdRunValidateFinalReviewCommand`. Remove when Issue Final Review
|
|
3524
|
-
// is migrated to its own dedicated validator kind.
|
|
3525
|
-
case "final-review": {
|
|
3526
|
-
if (!options.prdRef || !options.checkoutBase || !options.reviewBase) {
|
|
3527
|
-
return invalid(
|
|
3528
|
-
options,
|
|
3529
|
-
"Final Review artifact validation requires --prd-ref, --checkout-base, and --review-base."
|
|
3530
|
-
);
|
|
3531
|
-
}
|
|
3532
|
-
const result = validateFinalReviewAgentOutputs({
|
|
3533
|
-
artifactPath: options.artifactPath,
|
|
3534
|
-
context: {
|
|
3535
|
-
prdRef: options.prdRef,
|
|
3536
|
-
prdBranch: options.checkoutBase,
|
|
3537
|
-
mergeBase: options.reviewBase
|
|
3538
|
-
}
|
|
3539
|
-
});
|
|
3540
|
-
if (!result.ok) {
|
|
3541
|
-
return invalid(options, result.reason, result.diagnostics);
|
|
3542
|
-
}
|
|
3543
|
-
return valid(options, [`verdict: ${result.artifact.verdict}`]);
|
|
3544
|
-
}
|
|
3545
3420
|
case "issue-final-review": {
|
|
3546
3421
|
if (options.issueNumber === void 0 || !options.branchName) {
|
|
3547
3422
|
return invalid(
|
|
@@ -3581,12 +3456,6 @@ function validateAgentArtifact(options) {
|
|
|
3581
3456
|
}
|
|
3582
3457
|
return valid(options, [`decision: ${parsed.json.recoveryDecision}`]);
|
|
3583
3458
|
}
|
|
3584
|
-
case "reconciliation":
|
|
3585
|
-
case "planning-manifest":
|
|
3586
|
-
return invalid(
|
|
3587
|
-
options,
|
|
3588
|
-
`Artifact kind "${options.kind}" has been removed from PRD Run validation`
|
|
3589
|
-
);
|
|
3590
3459
|
case "local-prd":
|
|
3591
3460
|
case "local-issue":
|
|
3592
3461
|
case "local-triage":
|
|
@@ -3608,8 +3477,8 @@ function validateAgentArtifact(options) {
|
|
|
3608
3477
|
}
|
|
3609
3478
|
}
|
|
3610
3479
|
function runValidateArtifactCommand(options) {
|
|
3611
|
-
const artifactPath =
|
|
3612
|
-
const latestReviewArtifactPath = options.latestReviewArtifactPath ?
|
|
3480
|
+
const artifactPath = resolve3(options.repoRoot, options.artifactPath);
|
|
3481
|
+
const latestReviewArtifactPath = options.latestReviewArtifactPath ? resolve3(options.repoRoot, options.latestReviewArtifactPath) : void 0;
|
|
3613
3482
|
return validateAgentArtifact({
|
|
3614
3483
|
...options,
|
|
3615
3484
|
artifactPath,
|
|
@@ -3618,9 +3487,9 @@ function runValidateArtifactCommand(options) {
|
|
|
3618
3487
|
});
|
|
3619
3488
|
}
|
|
3620
3489
|
function runLocalValidateArtifactCommand(options) {
|
|
3621
|
-
const resolvedPath =
|
|
3490
|
+
const resolvedPath = resolve3(options.repoRoot, options.artifactPath);
|
|
3622
3491
|
const resolvedExtra = (options.extraArgs ?? []).map(
|
|
3623
|
-
(p) =>
|
|
3492
|
+
(p) => isAbsolute3(p) ? p : resolve3(options.repoRoot, p)
|
|
3624
3493
|
);
|
|
3625
3494
|
let localResult;
|
|
3626
3495
|
switch (options.kind) {
|
|
@@ -4366,8 +4235,8 @@ function validateLocalTriage(prdPath, issuePaths) {
|
|
|
4366
4235
|
}
|
|
4367
4236
|
|
|
4368
4237
|
// commands/issue-run.ts
|
|
4369
|
-
import { existsSync as
|
|
4370
|
-
import { isAbsolute as
|
|
4238
|
+
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
4239
|
+
import { isAbsolute as isAbsolute4, join as join15 } from "path";
|
|
4371
4240
|
|
|
4372
4241
|
// pr/templates.ts
|
|
4373
4242
|
init_common();
|
|
@@ -4502,17 +4371,17 @@ function runEffectAndMapExit(program) {
|
|
|
4502
4371
|
}
|
|
4503
4372
|
|
|
4504
4373
|
// shared/attempt-log.ts
|
|
4505
|
-
import { appendFileSync, existsSync as
|
|
4506
|
-
import { dirname as
|
|
4374
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8 } from "fs";
|
|
4375
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
4507
4376
|
var ATTEMPT_LOG_PATH = ".pourkit/attempt-log.jsonl";
|
|
4508
4377
|
function writeAttemptLog(worktreePath, entry) {
|
|
4509
4378
|
const logPath = join8(worktreePath, ATTEMPT_LOG_PATH);
|
|
4510
|
-
mkdirSync6(
|
|
4379
|
+
mkdirSync6(dirname4(logPath), { recursive: true });
|
|
4511
4380
|
appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
4512
4381
|
}
|
|
4513
4382
|
function readAttemptLog(worktreePath) {
|
|
4514
4383
|
const logPath = join8(worktreePath, ATTEMPT_LOG_PATH);
|
|
4515
|
-
if (!
|
|
4384
|
+
if (!existsSync7(logPath)) {
|
|
4516
4385
|
return [];
|
|
4517
4386
|
}
|
|
4518
4387
|
const raw = readFileSync8(logPath, "utf-8");
|
|
@@ -4634,7 +4503,7 @@ async function runBaseRefreshAttempt(options) {
|
|
|
4634
4503
|
}
|
|
4635
4504
|
|
|
4636
4505
|
// commands/conflict-resolution.ts
|
|
4637
|
-
import { existsSync as
|
|
4506
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
4638
4507
|
import { join as join9 } from "path";
|
|
4639
4508
|
init_common();
|
|
4640
4509
|
var CONFLICT_MARKER_PATTERN2 = /<<<<<<<|=======|>>>>>>>/m;
|
|
@@ -4922,7 +4791,7 @@ async function canReachMcp(url) {
|
|
|
4922
4791
|
return true;
|
|
4923
4792
|
} catch {
|
|
4924
4793
|
if (attempt < 9) {
|
|
4925
|
-
await new Promise((
|
|
4794
|
+
await new Promise((resolve5) => setTimeout(resolve5, 100));
|
|
4926
4795
|
}
|
|
4927
4796
|
}
|
|
4928
4797
|
}
|
|
@@ -5543,7 +5412,7 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
|
5543
5412
|
|
|
5544
5413
|
// prd-run/local-merge-coordinator.ts
|
|
5545
5414
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5546
|
-
import { existsSync as
|
|
5415
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
|
|
5547
5416
|
import { join as join13 } from "path";
|
|
5548
5417
|
|
|
5549
5418
|
// prd-run/local-branches.ts
|
|
@@ -5644,7 +5513,7 @@ function getIssueArtifactPath(repoRoot2, prdId, issueId) {
|
|
|
5644
5513
|
}
|
|
5645
5514
|
function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
5646
5515
|
const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
|
|
5647
|
-
if (!
|
|
5516
|
+
if (!existsSync9(issuePath)) return null;
|
|
5648
5517
|
try {
|
|
5649
5518
|
const content = readFileSync12(issuePath, "utf-8");
|
|
5650
5519
|
const parsed = JSON.parse(content);
|
|
@@ -5656,7 +5525,7 @@ function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
|
5656
5525
|
async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
5657
5526
|
const root = repoRoot2 ?? process.cwd();
|
|
5658
5527
|
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
5659
|
-
if (!
|
|
5528
|
+
if (!existsSync9(receiptPath)) return null;
|
|
5660
5529
|
try {
|
|
5661
5530
|
const content = readFileSync12(receiptPath, "utf-8");
|
|
5662
5531
|
const parsed = JSON.parse(content);
|
|
@@ -5671,7 +5540,7 @@ async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
|
5671
5540
|
async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
|
|
5672
5541
|
const root = repoRoot2 ?? process.cwd();
|
|
5673
5542
|
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
5674
|
-
if (
|
|
5543
|
+
if (existsSync9(receiptPath)) {
|
|
5675
5544
|
try {
|
|
5676
5545
|
const existing = JSON.parse(
|
|
5677
5546
|
readFileSync12(receiptPath, "utf-8")
|
|
@@ -6083,7 +5952,7 @@ function runMergeCoordinator(options) {
|
|
|
6083
5952
|
}
|
|
6084
5953
|
|
|
6085
5954
|
// commands/issue-final-review-agent.ts
|
|
6086
|
-
import { existsSync as
|
|
5955
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
|
|
6087
5956
|
import { join as join14 } from "path";
|
|
6088
5957
|
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join14(
|
|
6089
5958
|
".pourkit",
|
|
@@ -6197,12 +6066,21 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
6197
6066
|
}
|
|
6198
6067
|
function loadIssueFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
6199
6068
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6200
|
-
const promptBody =
|
|
6069
|
+
const promptBody = existsSync10(promptPath) ? readFileSync13(promptPath, "utf-8") : promptTemplate;
|
|
6201
6070
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
6202
6071
|
|
|
6203
6072
|
## Shared Run Context
|
|
6204
6073
|
|
|
6205
|
-
Read the selected issue requirements, PRD context, comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
6074
|
+
Read the selected issue requirements, PRD context, comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
6075
|
+
|
|
6076
|
+
## Initial Verification Pass
|
|
6077
|
+
|
|
6078
|
+
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
6079
|
+
- Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
|
|
6080
|
+
- Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
|
|
6081
|
+
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
6082
|
+
- 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.
|
|
6083
|
+
- If no verification commands are configured, note that and proceed with normal review.`);
|
|
6206
6084
|
}
|
|
6207
6085
|
|
|
6208
6086
|
// issues/issue-transitions.ts
|
|
@@ -6405,7 +6283,7 @@ async function advanceIssueFinalReview(options) {
|
|
|
6405
6283
|
"Issue Final Review state is incomplete: missing artifactPath"
|
|
6406
6284
|
);
|
|
6407
6285
|
}
|
|
6408
|
-
const artifactPath =
|
|
6286
|
+
const artifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join15(worktreePath, ifrFromState.artifactPath);
|
|
6409
6287
|
const validation = validateAgentArtifact({
|
|
6410
6288
|
kind: "issue-final-review",
|
|
6411
6289
|
artifactPath,
|
|
@@ -6437,6 +6315,12 @@ async function advanceIssueFinalReview(options) {
|
|
|
6437
6315
|
logger,
|
|
6438
6316
|
reviewArtifactPath
|
|
6439
6317
|
});
|
|
6318
|
+
await assertCanonicalBaseAncestor({
|
|
6319
|
+
worktreePath,
|
|
6320
|
+
baseRef: `origin/${target.baseBranch}`,
|
|
6321
|
+
stageName: "Issue Final Review",
|
|
6322
|
+
logger
|
|
6323
|
+
});
|
|
6440
6324
|
if (result.verdict === "pass") {
|
|
6441
6325
|
updateWorktreeRunState(worktreePath, {
|
|
6442
6326
|
issueFinalReview: {
|
|
@@ -6661,6 +6545,14 @@ async function startIssueRun(options) {
|
|
|
6661
6545
|
};
|
|
6662
6546
|
}
|
|
6663
6547
|
if (executionResult.worktreePath) {
|
|
6548
|
+
if (shouldRunBuilder) {
|
|
6549
|
+
await assertCanonicalBaseAncestor({
|
|
6550
|
+
worktreePath: executionResult.worktreePath,
|
|
6551
|
+
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6552
|
+
stageName: "Builder",
|
|
6553
|
+
logger
|
|
6554
|
+
});
|
|
6555
|
+
}
|
|
6664
6556
|
if (worktreeState) {
|
|
6665
6557
|
updateWorktreeRunState(executionResult.worktreePath, {
|
|
6666
6558
|
completedStages: { builder: true }
|
|
@@ -6722,6 +6614,12 @@ async function advanceIssueRunReview(options) {
|
|
|
6722
6614
|
}
|
|
6723
6615
|
})
|
|
6724
6616
|
);
|
|
6617
|
+
await assertCanonicalBaseAncestor({
|
|
6618
|
+
worktreePath: options.worktreePath,
|
|
6619
|
+
baseRef: `origin/${options.target.baseBranch}`,
|
|
6620
|
+
stageName: "Review/Refactor",
|
|
6621
|
+
logger: options.logger
|
|
6622
|
+
});
|
|
6725
6623
|
updateWorktreeRunState(options.worktreePath, {
|
|
6726
6624
|
review: {
|
|
6727
6625
|
lifetimeIterations: reviewResult.lifetimeIterations,
|
|
@@ -6785,7 +6683,7 @@ async function completeIssueRun(options) {
|
|
|
6785
6683
|
prTitle = finalizerFromState.title;
|
|
6786
6684
|
prBody = finalizerFromState.body;
|
|
6787
6685
|
} else if (finalizerFromState.artifactPath) {
|
|
6788
|
-
if (!
|
|
6686
|
+
if (!existsSync11(finalizerFromState.artifactPath)) {
|
|
6789
6687
|
throw new FinalizerFailure({
|
|
6790
6688
|
message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
|
|
6791
6689
|
});
|
|
@@ -6848,6 +6746,14 @@ async function completeIssueRun(options) {
|
|
|
6848
6746
|
}
|
|
6849
6747
|
prTitle = finalizerResult.title;
|
|
6850
6748
|
prBody = finalizerResult.body;
|
|
6749
|
+
if (executionResult.worktreePath) {
|
|
6750
|
+
await assertCanonicalBaseAncestor({
|
|
6751
|
+
worktreePath: executionResult.worktreePath,
|
|
6752
|
+
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6753
|
+
stageName: "Finalizer",
|
|
6754
|
+
logger
|
|
6755
|
+
});
|
|
6756
|
+
}
|
|
6851
6757
|
}
|
|
6852
6758
|
prTitle = ensureConventionalPrTitle(
|
|
6853
6759
|
prTitle,
|
|
@@ -7216,18 +7122,12 @@ function getRefactorArtifactDir(artifactPath) {
|
|
|
7216
7122
|
}
|
|
7217
7123
|
async function finalizeWorktreeCommit(options) {
|
|
7218
7124
|
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
|
-
}
|
|
7125
|
+
await assertCanonicalBaseAncestor({
|
|
7126
|
+
worktreePath,
|
|
7127
|
+
baseRef,
|
|
7128
|
+
stageName: "final commit",
|
|
7129
|
+
logger
|
|
7130
|
+
});
|
|
7231
7131
|
await execCapture("git", ["reset", "--soft", baseRef], {
|
|
7232
7132
|
cwd: worktreePath,
|
|
7233
7133
|
logger,
|
|
@@ -7244,6 +7144,21 @@ async function finalizeWorktreeCommit(options) {
|
|
|
7244
7144
|
label: "git commit"
|
|
7245
7145
|
});
|
|
7246
7146
|
}
|
|
7147
|
+
async function assertCanonicalBaseAncestor(options) {
|
|
7148
|
+
const { worktreePath, baseRef, stageName, logger } = options;
|
|
7149
|
+
await syncRemoteBaseRef(worktreePath, baseRef, logger);
|
|
7150
|
+
try {
|
|
7151
|
+
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
7152
|
+
cwd: worktreePath,
|
|
7153
|
+
logger,
|
|
7154
|
+
label: "git merge-base --is-ancestor"
|
|
7155
|
+
});
|
|
7156
|
+
} catch {
|
|
7157
|
+
throw new Error(
|
|
7158
|
+
`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.`
|
|
7159
|
+
);
|
|
7160
|
+
}
|
|
7161
|
+
}
|
|
7247
7162
|
async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
|
|
7248
7163
|
const remoteBase = parseRemoteBaseRef(baseRef);
|
|
7249
7164
|
if (!remoteBase) {
|
|
@@ -7487,7 +7402,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
|
|
|
7487
7402
|
}
|
|
7488
7403
|
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
7489
7404
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
7490
|
-
const promptBody =
|
|
7405
|
+
const promptBody = existsSync11(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
|
|
7491
7406
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
7492
7407
|
|
|
7493
7408
|
## Shared Run Context
|
|
@@ -7978,24 +7893,14 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
7978
7893
|
}
|
|
7979
7894
|
|
|
7980
7895
|
// commands/prd-run.ts
|
|
7981
|
-
import {
|
|
7982
|
-
cpSync,
|
|
7983
|
-
existsSync as existsSync16,
|
|
7984
|
-
lstatSync,
|
|
7985
|
-
mkdirSync as mkdirSync10,
|
|
7986
|
-
mkdtempSync,
|
|
7987
|
-
readFileSync as readFileSync17,
|
|
7988
|
-
realpathSync,
|
|
7989
|
-
rmSync as rmSync3
|
|
7990
|
-
} from "fs";
|
|
7896
|
+
import { existsSync as existsSync15, lstatSync, readFileSync as readFileSync18, realpathSync } from "fs";
|
|
7991
7897
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
7992
|
-
import {
|
|
7993
|
-
import { tmpdir } from "os";
|
|
7898
|
+
import { join as join19, relative as relative2 } from "path";
|
|
7994
7899
|
import { Match, pipe } from "effect";
|
|
7995
7900
|
|
|
7996
7901
|
// prd-run/state.ts
|
|
7997
7902
|
import {
|
|
7998
|
-
existsSync as
|
|
7903
|
+
existsSync as existsSync12,
|
|
7999
7904
|
mkdirSync as mkdirSync8,
|
|
8000
7905
|
readFileSync as readFileSync15,
|
|
8001
7906
|
readdirSync as readdirSync4,
|
|
@@ -8003,14 +7908,14 @@ import {
|
|
|
8003
7908
|
} from "fs";
|
|
8004
7909
|
import { mkdir as mkdir4, readFile as readFile4, writeFile } from "fs/promises";
|
|
8005
7910
|
import { join as join16 } from "path";
|
|
8006
|
-
import { z
|
|
7911
|
+
import { z } from "zod";
|
|
8007
7912
|
var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
|
|
8008
|
-
var PrdRunRecordSchema =
|
|
8009
|
-
prdRef:
|
|
7913
|
+
var PrdRunRecordSchema = z.object({
|
|
7914
|
+
prdRef: z.string().regex(
|
|
8010
7915
|
/^PRD-\d{4}$/,
|
|
8011
7916
|
"PRD ref must use four-digit format (e.g., PRD-0052)"
|
|
8012
7917
|
),
|
|
8013
|
-
status:
|
|
7918
|
+
status: z.enum([
|
|
8014
7919
|
"blocked",
|
|
8015
7920
|
"starting",
|
|
8016
7921
|
"running",
|
|
@@ -8022,61 +7927,61 @@ var PrdRunRecordSchema = z2.object({
|
|
|
8022
7927
|
"complete",
|
|
8023
7928
|
"completed_local_branch"
|
|
8024
7929
|
]),
|
|
8025
|
-
updatedAt:
|
|
8026
|
-
mode:
|
|
8027
|
-
blockedGate:
|
|
8028
|
-
targetName:
|
|
8029
|
-
prdBranch:
|
|
8030
|
-
blockedReason:
|
|
8031
|
-
diagnostics:
|
|
8032
|
-
offendingPaths:
|
|
8033
|
-
start:
|
|
8034
|
-
status:
|
|
8035
|
-
targetName:
|
|
8036
|
-
prdBranch:
|
|
8037
|
-
startBaseBranch:
|
|
8038
|
-
startBaseCommit:
|
|
8039
|
-
branchAction:
|
|
8040
|
-
startedAt:
|
|
8041
|
-
queueStartedAt:
|
|
8042
|
-
queueDrainedAt:
|
|
8043
|
-
queueProcessedCount:
|
|
8044
|
-
queueCommand:
|
|
8045
|
-
refreshReceipts:
|
|
7930
|
+
updatedAt: z.string().min(1),
|
|
7931
|
+
mode: z.enum(["github", "local"]).optional(),
|
|
7932
|
+
blockedGate: z.enum(["receipt-check", "branch-state", "git", "queue", "final-review"]).optional(),
|
|
7933
|
+
targetName: z.string().min(1).optional(),
|
|
7934
|
+
prdBranch: z.string().min(1).optional(),
|
|
7935
|
+
blockedReason: z.string().min(1).optional(),
|
|
7936
|
+
diagnostics: z.array(z.string()).optional(),
|
|
7937
|
+
offendingPaths: z.array(z.string()).optional(),
|
|
7938
|
+
start: z.object({
|
|
7939
|
+
status: z.enum(["started", "succeeded", "adopted", "reused"]),
|
|
7940
|
+
targetName: z.string().min(1),
|
|
7941
|
+
prdBranch: z.string().min(1),
|
|
7942
|
+
startBaseBranch: z.string().min(1),
|
|
7943
|
+
startBaseCommit: z.string().min(1).optional(),
|
|
7944
|
+
branchAction: z.enum(["created", "reused", "adopted"]).optional(),
|
|
7945
|
+
startedAt: z.string().min(1).optional(),
|
|
7946
|
+
queueStartedAt: z.string().min(1).optional(),
|
|
7947
|
+
queueDrainedAt: z.string().min(1).optional(),
|
|
7948
|
+
queueProcessedCount: z.number().int().nonnegative().optional(),
|
|
7949
|
+
queueCommand: z.literal("queue-run").optional(),
|
|
7950
|
+
refreshReceipts: z.tuple([]).optional()
|
|
8046
7951
|
}).strict().optional(),
|
|
8047
|
-
finalReview:
|
|
8048
|
-
status:
|
|
7952
|
+
finalReview: z.object({
|
|
7953
|
+
status: z.enum([
|
|
8049
7954
|
"started",
|
|
8050
7955
|
"succeeded",
|
|
8051
7956
|
"blocked",
|
|
8052
7957
|
"needs_human_review",
|
|
8053
7958
|
"final_reviewed"
|
|
8054
7959
|
]),
|
|
8055
|
-
targetName:
|
|
8056
|
-
prdBranch:
|
|
8057
|
-
mergeBase:
|
|
8058
|
-
verdict:
|
|
7960
|
+
targetName: z.string().min(1),
|
|
7961
|
+
prdBranch: z.string().min(1),
|
|
7962
|
+
mergeBase: z.string().min(1).optional(),
|
|
7963
|
+
verdict: z.enum([
|
|
8059
7964
|
"pass_no_changes",
|
|
8060
7965
|
"pass_with_retouch",
|
|
8061
7966
|
"needs_human_review",
|
|
8062
7967
|
"blocked"
|
|
8063
7968
|
]).optional(),
|
|
8064
|
-
artifactPath:
|
|
8065
|
-
diagnostics:
|
|
8066
|
-
reviewedAt:
|
|
8067
|
-
retouchPrNumber:
|
|
8068
|
-
retouchPrUrl:
|
|
8069
|
-
retouchMergeCommit:
|
|
8070
|
-
autoMerge:
|
|
8071
|
-
changedPaths:
|
|
7969
|
+
artifactPath: z.string().min(1).optional(),
|
|
7970
|
+
diagnostics: z.array(z.string()).optional(),
|
|
7971
|
+
reviewedAt: z.string().min(1).optional(),
|
|
7972
|
+
retouchPrNumber: z.number().int().positive().optional(),
|
|
7973
|
+
retouchPrUrl: z.string().url().optional(),
|
|
7974
|
+
retouchMergeCommit: z.string().min(1).optional(),
|
|
7975
|
+
autoMerge: z.boolean().optional(),
|
|
7976
|
+
changedPaths: z.array(z.string()).optional()
|
|
8072
7977
|
}).strict().optional(),
|
|
8073
|
-
scopeChanges:
|
|
8074
|
-
|
|
8075
|
-
issueNumber:
|
|
8076
|
-
decision:
|
|
8077
|
-
reason:
|
|
8078
|
-
acceptedBy:
|
|
8079
|
-
acceptedAt:
|
|
7978
|
+
scopeChanges: z.array(
|
|
7979
|
+
z.object({
|
|
7980
|
+
issueNumber: z.number().int().positive(),
|
|
7981
|
+
decision: z.literal("accepted_scope_change"),
|
|
7982
|
+
reason: z.string().min(1),
|
|
7983
|
+
acceptedBy: z.string().min(1),
|
|
7984
|
+
acceptedAt: z.string().min(1)
|
|
8080
7985
|
}).strict()
|
|
8081
7986
|
).optional()
|
|
8082
7987
|
}).strict();
|
|
@@ -8093,7 +7998,7 @@ function normalizePrdRunRef(ref) {
|
|
|
8093
7998
|
function readPrdRun(repoRoot2, prdRef) {
|
|
8094
7999
|
const normalized = normalizePrdRunRef(prdRef);
|
|
8095
8000
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
8096
|
-
if (!
|
|
8001
|
+
if (!existsSync12(recordPath)) {
|
|
8097
8002
|
return { record: null, diagnostics: [] };
|
|
8098
8003
|
}
|
|
8099
8004
|
try {
|
|
@@ -8124,7 +8029,7 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
8124
8029
|
}
|
|
8125
8030
|
function listPrdRuns(repoRoot2) {
|
|
8126
8031
|
const stateDir = join16(repoRoot2, PRD_RUN_STATE_DIR);
|
|
8127
|
-
if (!
|
|
8032
|
+
if (!existsSync12(stateDir)) {
|
|
8128
8033
|
return { records: [], diagnostics: [] };
|
|
8129
8034
|
}
|
|
8130
8035
|
const records = [];
|
|
@@ -8168,40 +8073,40 @@ function normalizeLegacyPrdRunRecord(record) {
|
|
|
8168
8073
|
offendingPaths: void 0
|
|
8169
8074
|
};
|
|
8170
8075
|
}
|
|
8171
|
-
var LocalPrdRunRecordSchema =
|
|
8172
|
-
prdId:
|
|
8076
|
+
var LocalPrdRunRecordSchema = z.object({
|
|
8077
|
+
prdId: z.string().regex(
|
|
8173
8078
|
/^PRD-\d{4}$/,
|
|
8174
8079
|
"PRD id must use four-digit format (e.g., PRD-0052)"
|
|
8175
8080
|
),
|
|
8176
|
-
createdAt:
|
|
8177
|
-
receipts:
|
|
8178
|
-
start:
|
|
8179
|
-
startedAt:
|
|
8180
|
-
branch:
|
|
8181
|
-
baseCommit:
|
|
8081
|
+
createdAt: z.string().min(1),
|
|
8082
|
+
receipts: z.object({
|
|
8083
|
+
start: z.object({
|
|
8084
|
+
startedAt: z.string().min(1),
|
|
8085
|
+
branch: z.string().min(1),
|
|
8086
|
+
baseCommit: z.string().min(1)
|
|
8182
8087
|
}).strict().optional(),
|
|
8183
|
-
queue:
|
|
8184
|
-
finalReview:
|
|
8185
|
-
completedAt:
|
|
8186
|
-
targetName:
|
|
8187
|
-
prdBranch:
|
|
8188
|
-
mergeBase:
|
|
8189
|
-
verdict:
|
|
8088
|
+
queue: z.object({ completedAt: z.string().min(1) }).strict().optional(),
|
|
8089
|
+
finalReview: z.object({
|
|
8090
|
+
completedAt: z.string().min(1),
|
|
8091
|
+
targetName: z.string().optional(),
|
|
8092
|
+
prdBranch: z.string().optional(),
|
|
8093
|
+
mergeBase: z.string().optional(),
|
|
8094
|
+
verdict: z.enum([
|
|
8190
8095
|
"pass_no_changes",
|
|
8191
8096
|
"pass_with_retouch",
|
|
8192
8097
|
"needs_human_review",
|
|
8193
8098
|
"blocked"
|
|
8194
8099
|
]).optional(),
|
|
8195
|
-
diagnostics:
|
|
8196
|
-
artifactPath:
|
|
8100
|
+
diagnostics: z.array(z.string()).optional(),
|
|
8101
|
+
artifactPath: z.string().optional()
|
|
8197
8102
|
}).strict().optional(),
|
|
8198
|
-
complete:
|
|
8199
|
-
completedAt:
|
|
8200
|
-
branch:
|
|
8201
|
-
stages:
|
|
8103
|
+
complete: z.object({
|
|
8104
|
+
completedAt: z.string().min(1),
|
|
8105
|
+
branch: z.string().min(1),
|
|
8106
|
+
stages: z.array(z.string())
|
|
8202
8107
|
}).strict().optional()
|
|
8203
8108
|
}).strict(),
|
|
8204
|
-
metadata:
|
|
8109
|
+
metadata: z.record(z.unknown())
|
|
8205
8110
|
}).strict();
|
|
8206
8111
|
function getRecordPath(repoRoot2, prdRef) {
|
|
8207
8112
|
return join16(
|
|
@@ -8211,29 +8116,12 @@ function getRecordPath(repoRoot2, prdRef) {
|
|
|
8211
8116
|
);
|
|
8212
8117
|
}
|
|
8213
8118
|
|
|
8214
|
-
// prd-run/
|
|
8215
|
-
import {
|
|
8216
|
-
import {
|
|
8217
|
-
var PRD_REF_REGEX2 = /^PRD-\d{3,4}$/;
|
|
8218
|
-
var StageSchema = z3.enum(["prdFinalReview", "prdReconciliation"]);
|
|
8219
|
-
var EvidencePacketSchema = z3.object({
|
|
8220
|
-
prdRef: z3.string().regex(PRD_REF_REGEX2),
|
|
8221
|
-
prdBranch: z3.string().min(1),
|
|
8222
|
-
mergeBase: z3.string().min(6),
|
|
8223
|
-
planningManifestPath: z3.string().min(1),
|
|
8224
|
-
planningManifestFacts: z3.object({
|
|
8225
|
-
parentPrdIssueUrl: z3.string().min(1),
|
|
8226
|
-
childIssueCount: z3.number().int().nonnegative()
|
|
8227
|
-
}),
|
|
8228
|
-
stage: StageSchema,
|
|
8229
|
-
stageReceipts: z3.record(z3.string(), z3.unknown())
|
|
8230
|
-
}).strict();
|
|
8231
|
-
|
|
8232
|
-
// commands/prd-run.ts
|
|
8233
|
-
init_common();
|
|
8119
|
+
// prd-run/local-queue-loop.ts
|
|
8120
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
|
|
8121
|
+
import { join as join18 } from "path";
|
|
8234
8122
|
|
|
8235
8123
|
// prd-run/local-artifacts.ts
|
|
8236
|
-
import { existsSync as
|
|
8124
|
+
import { existsSync as existsSync13 } from "fs";
|
|
8237
8125
|
import { join as join17 } from "path";
|
|
8238
8126
|
var REQUIRED_PRD_FIELDS = [
|
|
8239
8127
|
"schemaVersion",
|
|
@@ -8286,7 +8174,7 @@ function hasRequiredFields(data, requiredFields) {
|
|
|
8286
8174
|
async function resolveLocalPrdArtifact(prdId, repoRoot2) {
|
|
8287
8175
|
const root = repoRoot2 ?? process.cwd();
|
|
8288
8176
|
const prdPath = prdArtifactPath(root, prdId);
|
|
8289
|
-
if (!
|
|
8177
|
+
if (!existsSync13(prdPath)) {
|
|
8290
8178
|
return {
|
|
8291
8179
|
ok: false,
|
|
8292
8180
|
failureCode: "missing_prd_artifact",
|
|
@@ -8331,7 +8219,7 @@ async function resolveLocalIssueArtifacts(prdId, repoRoot2) {
|
|
|
8331
8219
|
const issues = [];
|
|
8332
8220
|
for (const childId of childIssueIds) {
|
|
8333
8221
|
const issuePath = issueArtifactPath(root, prdId, childId);
|
|
8334
|
-
if (!
|
|
8222
|
+
if (!existsSync13(issuePath)) {
|
|
8335
8223
|
return {
|
|
8336
8224
|
ok: false,
|
|
8337
8225
|
failureCode: "missing_child_issue",
|
|
@@ -8348,100 +8236,33 @@ async function resolveLocalIssueArtifacts(prdId, repoRoot2) {
|
|
|
8348
8236
|
message: `Failed to read or parse Issue artifact at ${issuePath}: ${result.message}`
|
|
8349
8237
|
};
|
|
8350
8238
|
}
|
|
8351
|
-
const missing = hasRequiredFields(
|
|
8352
|
-
result.data,
|
|
8353
|
-
REQUIRED_ISSUE_FIELDS
|
|
8354
|
-
);
|
|
8355
|
-
if (missing) {
|
|
8356
|
-
return {
|
|
8357
|
-
ok: false,
|
|
8358
|
-
failureCode: "invalid_artifact",
|
|
8359
|
-
missingId: childId,
|
|
8360
|
-
message: `Issue artifact at ${issuePath} is missing required field: ${missing}`
|
|
8361
|
-
};
|
|
8362
|
-
}
|
|
8363
|
-
if (result.data.kind !== "issue") {
|
|
8364
|
-
return {
|
|
8365
|
-
ok: false,
|
|
8366
|
-
failureCode: "invalid_artifact",
|
|
8367
|
-
missingId: childId,
|
|
8368
|
-
message: `Issue artifact at ${issuePath} has kind "${result.data.kind}", expected "issue"`
|
|
8369
|
-
};
|
|
8370
|
-
}
|
|
8371
|
-
issues.push(result.data);
|
|
8372
|
-
}
|
|
8373
|
-
return { ok: true, data: issues };
|
|
8374
|
-
}
|
|
8375
|
-
|
|
8376
|
-
// prd-run/local-issue-adapter.ts
|
|
8377
|
-
var VALID_TRIAGE_LABELS = /* @__PURE__ */ new Set([
|
|
8378
|
-
"needs-triage",
|
|
8379
|
-
"ready-for-agent",
|
|
8380
|
-
"blocked",
|
|
8381
|
-
"ready-for-human",
|
|
8382
|
-
"wontfix"
|
|
8383
|
-
]);
|
|
8384
|
-
function countTypeLabels(labels) {
|
|
8385
|
-
return labels.filter((l) => l.startsWith("type:")).length;
|
|
8386
|
-
}
|
|
8387
|
-
function countTriageLabels(labels) {
|
|
8388
|
-
return labels.filter((l) => VALID_TRIAGE_LABELS.has(l)).length;
|
|
8389
|
-
}
|
|
8390
|
-
function countCategoryLabels(labels) {
|
|
8391
|
-
const triageCount = countTriageLabels(labels);
|
|
8392
|
-
const typeCount = countTypeLabels(labels);
|
|
8393
|
-
return labels.length - triageCount - typeCount;
|
|
8394
|
-
}
|
|
8395
|
-
function hasValidLabels(labels) {
|
|
8396
|
-
if (countTriageLabels(labels) !== 1) return false;
|
|
8397
|
-
if (!labels.includes("ready-for-agent")) return false;
|
|
8398
|
-
if (countTypeLabels(labels) !== 1) return false;
|
|
8399
|
-
if (countCategoryLabels(labels) !== 1) return false;
|
|
8400
|
-
return true;
|
|
8401
|
-
}
|
|
8402
|
-
function isIssueBlockedByDependencies(issue, issues) {
|
|
8403
|
-
const blockers = [];
|
|
8404
|
-
for (const depId of issue.dependencyIssueIds) {
|
|
8405
|
-
const dep = issues.find((i) => i.id === depId);
|
|
8406
|
-
if (!dep || dep.receipts.completedAt === null) {
|
|
8407
|
-
blockers.push(depId);
|
|
8408
|
-
}
|
|
8409
|
-
}
|
|
8410
|
-
return blockers;
|
|
8411
|
-
}
|
|
8412
|
-
async function getRunnableLocalIssues(prdId, repoRoot2) {
|
|
8413
|
-
const issuesResult = await resolveLocalIssueArtifacts(prdId, repoRoot2);
|
|
8414
|
-
if (!issuesResult.ok) return [];
|
|
8415
|
-
const issues = issuesResult.data;
|
|
8416
|
-
const runnable = [];
|
|
8417
|
-
for (const issue of issues) {
|
|
8418
|
-
if (!hasValidLabels(issue.labels)) continue;
|
|
8419
|
-
if (issue.status !== "ready-for-agent") continue;
|
|
8420
|
-
const blockers = isIssueBlockedByDependencies(issue, issues);
|
|
8421
|
-
runnable.push({
|
|
8422
|
-
id: issue.id,
|
|
8423
|
-
title: issue.title,
|
|
8424
|
-
labels: issue.labels,
|
|
8425
|
-
blockedBy: blockers.length > 0 ? blockers : void 0
|
|
8426
|
-
});
|
|
8239
|
+
const missing = hasRequiredFields(
|
|
8240
|
+
result.data,
|
|
8241
|
+
REQUIRED_ISSUE_FIELDS
|
|
8242
|
+
);
|
|
8243
|
+
if (missing) {
|
|
8244
|
+
return {
|
|
8245
|
+
ok: false,
|
|
8246
|
+
failureCode: "invalid_artifact",
|
|
8247
|
+
missingId: childId,
|
|
8248
|
+
message: `Issue artifact at ${issuePath} is missing required field: ${missing}`
|
|
8249
|
+
};
|
|
8250
|
+
}
|
|
8251
|
+
if (result.data.kind !== "issue") {
|
|
8252
|
+
return {
|
|
8253
|
+
ok: false,
|
|
8254
|
+
failureCode: "invalid_artifact",
|
|
8255
|
+
missingId: childId,
|
|
8256
|
+
message: `Issue artifact at ${issuePath} has kind "${result.data.kind}", expected "issue"`
|
|
8257
|
+
};
|
|
8258
|
+
}
|
|
8259
|
+
issues.push(result.data);
|
|
8427
8260
|
}
|
|
8428
|
-
|
|
8429
|
-
(i) => !i.blockedBy || i.blockedBy.length === 0
|
|
8430
|
-
);
|
|
8431
|
-
return filtered;
|
|
8261
|
+
return { ok: true, data: issues };
|
|
8432
8262
|
}
|
|
8433
8263
|
|
|
8434
|
-
// prd-run/local-final-review.ts
|
|
8435
|
-
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
8436
|
-
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
8437
|
-
import { join as join18 } from "path";
|
|
8438
|
-
|
|
8439
|
-
// prd-run/local-queue-loop.ts
|
|
8440
|
-
import { readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
|
|
8441
|
-
import { join as join19 } from "path";
|
|
8442
|
-
|
|
8443
8264
|
// prd-run/local-issue-run.ts
|
|
8444
|
-
import { execFileSync as
|
|
8265
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
8445
8266
|
init_common();
|
|
8446
8267
|
async function createLocalIssueBranch(prdId, issueId, repoRoot2) {
|
|
8447
8268
|
const root = repoRoot2 ?? process.cwd();
|
|
@@ -8465,7 +8286,7 @@ async function createLocalIssueBranch(prdId, issueId, repoRoot2) {
|
|
|
8465
8286
|
}
|
|
8466
8287
|
const localPrdBranch = `local/${prdId}`;
|
|
8467
8288
|
try {
|
|
8468
|
-
|
|
8289
|
+
execFileSync3(
|
|
8469
8290
|
"git",
|
|
8470
8291
|
["show-ref", "--verify", "--quiet", `refs/heads/${localPrdBranch}`],
|
|
8471
8292
|
{
|
|
@@ -8481,7 +8302,7 @@ async function createLocalIssueBranch(prdId, issueId, repoRoot2) {
|
|
|
8481
8302
|
}
|
|
8482
8303
|
let branchExists = false;
|
|
8483
8304
|
try {
|
|
8484
|
-
|
|
8305
|
+
execFileSync3(
|
|
8485
8306
|
"git",
|
|
8486
8307
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
8487
8308
|
{
|
|
@@ -8494,7 +8315,7 @@ async function createLocalIssueBranch(prdId, issueId, repoRoot2) {
|
|
|
8494
8315
|
} catch {
|
|
8495
8316
|
}
|
|
8496
8317
|
if (!branchExists) {
|
|
8497
|
-
|
|
8318
|
+
execFileSync3("git", ["checkout", "-b", branchName, localPrdBranch], {
|
|
8498
8319
|
cwd: root,
|
|
8499
8320
|
encoding: "utf8",
|
|
8500
8321
|
stdio: "pipe"
|
|
@@ -8559,11 +8380,69 @@ async function runLocalIssue(prdId, issueId, builder, repoRoot2) {
|
|
|
8559
8380
|
}
|
|
8560
8381
|
}
|
|
8561
8382
|
|
|
8383
|
+
// prd-run/local-issue-adapter.ts
|
|
8384
|
+
var VALID_TRIAGE_LABELS = /* @__PURE__ */ new Set([
|
|
8385
|
+
"needs-triage",
|
|
8386
|
+
"ready-for-agent",
|
|
8387
|
+
"blocked",
|
|
8388
|
+
"ready-for-human",
|
|
8389
|
+
"wontfix"
|
|
8390
|
+
]);
|
|
8391
|
+
function countTypeLabels(labels) {
|
|
8392
|
+
return labels.filter((l) => l.startsWith("type:")).length;
|
|
8393
|
+
}
|
|
8394
|
+
function countTriageLabels(labels) {
|
|
8395
|
+
return labels.filter((l) => VALID_TRIAGE_LABELS.has(l)).length;
|
|
8396
|
+
}
|
|
8397
|
+
function countCategoryLabels(labels) {
|
|
8398
|
+
const triageCount = countTriageLabels(labels);
|
|
8399
|
+
const typeCount = countTypeLabels(labels);
|
|
8400
|
+
return labels.length - triageCount - typeCount;
|
|
8401
|
+
}
|
|
8402
|
+
function hasValidLabels(labels) {
|
|
8403
|
+
if (countTriageLabels(labels) !== 1) return false;
|
|
8404
|
+
if (!labels.includes("ready-for-agent")) return false;
|
|
8405
|
+
if (countTypeLabels(labels) !== 1) return false;
|
|
8406
|
+
if (countCategoryLabels(labels) !== 1) return false;
|
|
8407
|
+
return true;
|
|
8408
|
+
}
|
|
8409
|
+
function isIssueBlockedByDependencies(issue, issues) {
|
|
8410
|
+
const blockers = [];
|
|
8411
|
+
for (const depId of issue.dependencyIssueIds) {
|
|
8412
|
+
const dep = issues.find((i) => i.id === depId);
|
|
8413
|
+
if (!dep || dep.receipts.completedAt === null) {
|
|
8414
|
+
blockers.push(depId);
|
|
8415
|
+
}
|
|
8416
|
+
}
|
|
8417
|
+
return blockers;
|
|
8418
|
+
}
|
|
8419
|
+
async function getRunnableLocalIssues(prdId, repoRoot2) {
|
|
8420
|
+
const issuesResult = await resolveLocalIssueArtifacts(prdId, repoRoot2);
|
|
8421
|
+
if (!issuesResult.ok) return [];
|
|
8422
|
+
const issues = issuesResult.data;
|
|
8423
|
+
const runnable = [];
|
|
8424
|
+
for (const issue of issues) {
|
|
8425
|
+
if (!hasValidLabels(issue.labels)) continue;
|
|
8426
|
+
if (issue.status !== "ready-for-agent") continue;
|
|
8427
|
+
const blockers = isIssueBlockedByDependencies(issue, issues);
|
|
8428
|
+
runnable.push({
|
|
8429
|
+
id: issue.id,
|
|
8430
|
+
title: issue.title,
|
|
8431
|
+
labels: issue.labels,
|
|
8432
|
+
blockedBy: blockers.length > 0 ? blockers : void 0
|
|
8433
|
+
});
|
|
8434
|
+
}
|
|
8435
|
+
const filtered = runnable.filter(
|
|
8436
|
+
(i) => !i.blockedBy || i.blockedBy.length === 0
|
|
8437
|
+
);
|
|
8438
|
+
return filtered;
|
|
8439
|
+
}
|
|
8440
|
+
|
|
8562
8441
|
// prd-run/local-queue-loop.ts
|
|
8563
8442
|
var CHILD_CLEANUP_LABELS = ["agent-in-progress", "pr-open-awaiting-merge"];
|
|
8564
8443
|
var LOCAL_ISSUE_ID_REGEX = /^I-(\d+)$/i;
|
|
8565
8444
|
function getIssueArtifactPath2(repoRoot2, prdId, issueId) {
|
|
8566
|
-
return
|
|
8445
|
+
return join18(
|
|
8567
8446
|
repoRoot2,
|
|
8568
8447
|
".pourkit",
|
|
8569
8448
|
"local-prd-runs",
|
|
@@ -8584,7 +8463,7 @@ function readIssueArtifact(repoRoot2, prdId, issueId) {
|
|
|
8584
8463
|
}
|
|
8585
8464
|
}
|
|
8586
8465
|
function writeIssueArtifact(repoRoot2, prdId, issue) {
|
|
8587
|
-
|
|
8466
|
+
writeFileSync6(
|
|
8588
8467
|
getIssueArtifactPath2(repoRoot2, prdId, issue.id),
|
|
8589
8468
|
JSON.stringify(issue, null, 2),
|
|
8590
8469
|
"utf-8"
|
|
@@ -9250,6 +9129,9 @@ async function runQueueCommand(options) {
|
|
|
9250
9129
|
return runEffectAndMapExit(runQueueLoop(queueOptions));
|
|
9251
9130
|
}
|
|
9252
9131
|
|
|
9132
|
+
// prd-run/final-review-validation.ts
|
|
9133
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
|
|
9134
|
+
|
|
9253
9135
|
// commands/prd-run.ts
|
|
9254
9136
|
function planLaunchResume(record) {
|
|
9255
9137
|
if (!record) return { attempted: [], skipped: [], resumed: [] };
|
|
@@ -9311,18 +9193,18 @@ function planLaunchResume(record) {
|
|
|
9311
9193
|
);
|
|
9312
9194
|
}
|
|
9313
9195
|
function validateLocalStartStore(repoRoot2, prdRef) {
|
|
9314
|
-
const localStoreDir =
|
|
9196
|
+
const localStoreDir = join19(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
|
|
9315
9197
|
let localStoreReady = false;
|
|
9316
|
-
if (
|
|
9317
|
-
const localStorePath =
|
|
9198
|
+
if (existsSync15(localStoreDir)) {
|
|
9199
|
+
const localStorePath = join19(localStoreDir, "prd.json");
|
|
9318
9200
|
try {
|
|
9319
|
-
const content = JSON.parse(
|
|
9201
|
+
const content = JSON.parse(readFileSync18(localStorePath, "utf8"));
|
|
9320
9202
|
localStoreReady = content?.id === prdRef && content?.kind === "prd";
|
|
9321
9203
|
} catch {
|
|
9322
9204
|
localStoreReady = false;
|
|
9323
9205
|
}
|
|
9324
9206
|
}
|
|
9325
|
-
if (
|
|
9207
|
+
if (existsSync15(localStoreDir) && !localStoreReady) {
|
|
9326
9208
|
return {
|
|
9327
9209
|
ok: false,
|
|
9328
9210
|
gate: "branch-state",
|
|
@@ -9486,13 +9368,13 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
9486
9368
|
attempted.push("queue");
|
|
9487
9369
|
}
|
|
9488
9370
|
const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
9489
|
-
const localStorePath =
|
|
9371
|
+
const localStorePath = join19(
|
|
9490
9372
|
options.repoRoot,
|
|
9491
9373
|
".pourkit",
|
|
9492
9374
|
"local-prd-runs",
|
|
9493
9375
|
prdRef
|
|
9494
9376
|
);
|
|
9495
|
-
if (
|
|
9377
|
+
if (existsSync15(localStorePath)) {
|
|
9496
9378
|
writePrdRunRecord(options.repoRoot, {
|
|
9497
9379
|
prdRef,
|
|
9498
9380
|
status: "completed_local_branch",
|
|
@@ -10003,8 +9885,8 @@ async function processStartResult(startResult, options) {
|
|
|
10003
9885
|
start,
|
|
10004
9886
|
mode: modeForRecord
|
|
10005
9887
|
});
|
|
10006
|
-
const localStorePath =
|
|
10007
|
-
if (
|
|
9888
|
+
const localStorePath = join19(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
|
|
9889
|
+
if (existsSync15(localStorePath)) {
|
|
10008
9890
|
const queueResult = await runLocalQueueLoop(
|
|
10009
9891
|
prdRef,
|
|
10010
9892
|
repoRoot2,
|
|
@@ -10892,7 +10774,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
|
|
|
10892
10774
|
}
|
|
10893
10775
|
|
|
10894
10776
|
// commands/init.ts
|
|
10895
|
-
import { existsSync as
|
|
10777
|
+
import { existsSync as existsSync16, statSync } from "fs";
|
|
10896
10778
|
import {
|
|
10897
10779
|
copyFile,
|
|
10898
10780
|
mkdir as mkdir5,
|
|
@@ -10901,7 +10783,7 @@ import {
|
|
|
10901
10783
|
rename,
|
|
10902
10784
|
writeFile as writeFile2
|
|
10903
10785
|
} from "fs/promises";
|
|
10904
|
-
import { createHash as
|
|
10786
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
10905
10787
|
import path5 from "path";
|
|
10906
10788
|
import { execFile as execFile2 } from "child_process";
|
|
10907
10789
|
import { promisify as promisify2 } from "util";
|
|
@@ -11115,135 +10997,120 @@ function inferVerificationCommands(scripts, pm) {
|
|
|
11115
10997
|
}
|
|
11116
10998
|
function generateConfigTemplate(options) {
|
|
11117
10999
|
const {
|
|
11118
|
-
sourceRoot,
|
|
11119
|
-
targetRoot,
|
|
11120
|
-
packageManager,
|
|
11121
11000
|
baseBranch,
|
|
11001
|
+
packageManager,
|
|
11122
11002
|
verificationCommands,
|
|
11123
11003
|
hasPackageJson = true,
|
|
11124
11004
|
labels: maybeLabels
|
|
11125
11005
|
} = options;
|
|
11126
11006
|
const labels = maybeLabels ?? DEFAULT_RUNNER_LABELS;
|
|
11127
|
-
const relPath = path5.relative(targetRoot, sourceRoot).replace(/\\/g, "/");
|
|
11128
|
-
const importPath = relPath || ".";
|
|
11129
11007
|
const setupCommand = `${packageManager} install`;
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11145
|
-
|
|
11146
|
-
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
" // },"
|
|
11157
|
-
].join("\n");
|
|
11158
|
-
}
|
|
11159
|
-
return `import { definePourkitConfig } from "${importPath}/pourkit/shared/config";
|
|
11160
|
-
import type { PourkitConfig } from "${importPath}/pourkit/shared/config";
|
|
11161
|
-
|
|
11162
|
-
export default definePourkitConfig({
|
|
11163
|
-
targets: [
|
|
11164
|
-
{
|
|
11165
|
-
name: "default",
|
|
11166
|
-
baseBranch: "${baseBranch}",
|
|
11167
|
-
branchTemplate: "pourkit/{{issue.number}}/{{issue.slug}}",
|
|
11168
|
-
autoMerge: false,
|
|
11169
|
-
${setupSection}
|
|
11170
|
-
strategy: {
|
|
11171
|
-
type: "review-refactor-loop",
|
|
11172
|
-
implement: {
|
|
11173
|
-
builder: {
|
|
11174
|
-
agent: "pourkit-builder",
|
|
11175
|
-
model: "opencode-go/deepseek-v4-flash",
|
|
11176
|
-
promptTemplate: ".pourkit/prompts/builder.prompt.md",
|
|
11177
|
-
},
|
|
11178
|
-
},
|
|
11179
|
-
review: {
|
|
11180
|
-
reviewer: {
|
|
11181
|
-
agent: "pourkit-reviewer",
|
|
11182
|
-
model: "opencode-go/deepseek-v4-pro",
|
|
11183
|
-
promptTemplate: ".pourkit/prompts/reviewer.prompt.md",
|
|
11184
|
-
criteria: ["correctness", "scope", "tests", "quality"],
|
|
11185
|
-
},
|
|
11186
|
-
refactor: {
|
|
11187
|
-
agent: "pourkit-refactor",
|
|
11188
|
-
model: "opencode-go/qwen3.6-plus",
|
|
11189
|
-
promptTemplate: ".pourkit/prompts/refactor.prompt.md",
|
|
11190
|
-
},
|
|
11191
|
-
maxIterations: 3,
|
|
11192
|
-
passWithNotesRefactorAttempts: 2,
|
|
11008
|
+
const target = {
|
|
11009
|
+
name: "default",
|
|
11010
|
+
baseBranch,
|
|
11011
|
+
branchTemplate: "pourkit/{{issue.number}}/{{issue.slug}}",
|
|
11012
|
+
autoMerge: false,
|
|
11013
|
+
strategy: {
|
|
11014
|
+
type: "review-refactor-loop",
|
|
11015
|
+
implement: {
|
|
11016
|
+
builder: {
|
|
11017
|
+
agent: "pourkit-builder",
|
|
11018
|
+
model: "opencode-go/deepseek-v4-flash",
|
|
11019
|
+
promptTemplate: ".pourkit/prompts/builder.prompt.md"
|
|
11020
|
+
}
|
|
11021
|
+
},
|
|
11022
|
+
failureResolution: {
|
|
11023
|
+
agent: "pourkit-failure-resolution-agent",
|
|
11024
|
+
model: "opencode-go/mimo-v2.5",
|
|
11025
|
+
promptTemplate: ".pourkit/prompts/failure-resolution.prompt.md",
|
|
11026
|
+
maxAttemptsPerFailure: 1
|
|
11027
|
+
},
|
|
11028
|
+
review: {
|
|
11029
|
+
reviewer: {
|
|
11030
|
+
agent: "pourkit-reviewer",
|
|
11031
|
+
model: "opencode-go/deepseek-v4-pro",
|
|
11032
|
+
promptTemplate: ".pourkit/prompts/reviewer.prompt.md",
|
|
11033
|
+
criteria: ["correctness", "scope", "tests", "quality"]
|
|
11193
11034
|
},
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
model: "opencode-go/deepseek-v4-flash",
|
|
11199
|
-
promptTemplate: ".pourkit/prompts/pr-description.prompt.md",
|
|
11200
|
-
},
|
|
11201
|
-
maxAttempts: 2,
|
|
11035
|
+
refactor: {
|
|
11036
|
+
agent: "pourkit-refactor",
|
|
11037
|
+
model: "opencode-go/qwen3.6-plus",
|
|
11038
|
+
promptTemplate: ".pourkit/prompts/refactor.prompt.md"
|
|
11202
11039
|
},
|
|
11040
|
+
maxIterations: 3,
|
|
11041
|
+
passWithNotesRefactorAttempts: 2
|
|
11203
11042
|
},
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
blocked: ${JSON.stringify(labels.blocked)},
|
|
11210
|
-
prOpenAwaitingMerge: ${JSON.stringify(labels.prOpenAwaitingMerge)},
|
|
11211
|
-
readyForHuman: ${JSON.stringify(labels.readyForHuman)},
|
|
11212
|
-
},
|
|
11213
|
-
sandbox: {
|
|
11214
|
-
provider: "docker",
|
|
11215
|
-
copyToWorktree: ["node_modules"],
|
|
11216
|
-
mounts: [
|
|
11217
|
-
{
|
|
11218
|
-
hostPath: "~/.local/share/opencode",
|
|
11219
|
-
sandboxPath: "/home/agent/.local/share/opencode",
|
|
11220
|
-
readonly: false,
|
|
11043
|
+
issueFinalReview: {
|
|
11044
|
+
agent: "pourkit-reviewer",
|
|
11045
|
+
model: "opencode-go/deepseek-v4-pro",
|
|
11046
|
+
promptTemplate: ".pourkit/prompts/issue-final-review.prompt.md",
|
|
11047
|
+
maxAttempts: 3
|
|
11221
11048
|
},
|
|
11222
|
-
{
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11049
|
+
finalize: {
|
|
11050
|
+
prDescriptionAgent: {
|
|
11051
|
+
agent: "pourkit-pr-description",
|
|
11052
|
+
model: "opencode-go/deepseek-v4-flash",
|
|
11053
|
+
promptTemplate: ".pourkit/prompts/pr-description.prompt.md"
|
|
11054
|
+
},
|
|
11055
|
+
maxAttempts: 2
|
|
11056
|
+
}
|
|
11057
|
+
}
|
|
11058
|
+
};
|
|
11059
|
+
if (hasPackageJson) {
|
|
11060
|
+
target.setupCommands = [
|
|
11061
|
+
{ command: setupCommand, label: "install" }
|
|
11062
|
+
];
|
|
11063
|
+
}
|
|
11064
|
+
if (verificationCommands.length > 0) {
|
|
11065
|
+
target.strategy.verify = {
|
|
11066
|
+
commands: verificationCommands
|
|
11067
|
+
};
|
|
11068
|
+
}
|
|
11069
|
+
const config = {
|
|
11070
|
+
$schema: "./schema/pourkit.schema.json",
|
|
11071
|
+
schemaVersion: 1,
|
|
11072
|
+
targets: [target],
|
|
11073
|
+
labels: {
|
|
11074
|
+
readyForAgent: labels.readyForAgent,
|
|
11075
|
+
agentInProgress: labels.agentInProgress,
|
|
11076
|
+
blocked: labels.blocked,
|
|
11077
|
+
prOpenAwaitingMerge: labels.prOpenAwaitingMerge,
|
|
11078
|
+
readyForHuman: labels.readyForHuman
|
|
11079
|
+
},
|
|
11080
|
+
sandbox: {
|
|
11081
|
+
provider: "docker",
|
|
11082
|
+
copyToWorktree: ["node_modules"],
|
|
11083
|
+
mounts: [
|
|
11084
|
+
{
|
|
11085
|
+
hostPath: "~/.local/share/opencode",
|
|
11086
|
+
sandboxPath: "/home/agent/.local/share/opencode",
|
|
11087
|
+
readonly: false
|
|
11088
|
+
},
|
|
11089
|
+
{
|
|
11090
|
+
hostPath: "~/.config/opencode",
|
|
11091
|
+
sandboxPath: "/home/agent/.config/opencode",
|
|
11092
|
+
readonly: true
|
|
11093
|
+
}
|
|
11094
|
+
],
|
|
11095
|
+
env: {
|
|
11096
|
+
HOME: "/home/agent",
|
|
11097
|
+
XDG_DATA_HOME: "/home/agent/.local/share",
|
|
11098
|
+
XDG_CONFIG_HOME: "/home/agent/.config",
|
|
11099
|
+
XDG_STATE_HOME: "/home/agent/.local/state",
|
|
11100
|
+
XDG_CACHE_HOME: "/home/agent/.cache"
|
|
11226
11101
|
},
|
|
11227
|
-
|
|
11228
|
-
env: {
|
|
11229
|
-
HOME: "/home/agent",
|
|
11230
|
-
XDG_DATA_HOME: "/home/agent/.local/share",
|
|
11231
|
-
XDG_CONFIG_HOME: "/home/agent/.config",
|
|
11232
|
-
XDG_STATE_HOME: "/home/agent/.local/state",
|
|
11233
|
-
XDG_CACHE_HOME: "/home/agent/.cache",
|
|
11102
|
+
idleTimeoutSeconds: 300
|
|
11234
11103
|
},
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
});
|
|
11246
|
-
`;
|
|
11104
|
+
checks: {
|
|
11105
|
+
requiredLabels: [],
|
|
11106
|
+
allowedAuthors: [],
|
|
11107
|
+
checksFoundTimeoutSeconds: 60,
|
|
11108
|
+
checksCompletionTimeoutSeconds: 1800,
|
|
11109
|
+
pollIntervalSeconds: 15,
|
|
11110
|
+
issueListLimit: 50
|
|
11111
|
+
}
|
|
11112
|
+
};
|
|
11113
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
11247
11114
|
}
|
|
11248
11115
|
function generateOpenCodeConfig(existingConfig = {}) {
|
|
11249
11116
|
const { $schema, ...rest } = existingConfig;
|
|
@@ -11392,10 +11259,10 @@ async function walkDir(dir) {
|
|
|
11392
11259
|
}
|
|
11393
11260
|
async function computeFileChecksum(filePath) {
|
|
11394
11261
|
const content = await readFile5(filePath);
|
|
11395
|
-
return
|
|
11262
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
11396
11263
|
}
|
|
11397
11264
|
function lockfileExists(root, name) {
|
|
11398
|
-
return
|
|
11265
|
+
return existsSync16(path5.join(root, name));
|
|
11399
11266
|
}
|
|
11400
11267
|
function detectPackageManager(root) {
|
|
11401
11268
|
if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
|
|
@@ -11439,7 +11306,7 @@ async function discoverLocalSource(sourcePath) {
|
|
|
11439
11306
|
async function discoverReadme(root) {
|
|
11440
11307
|
for (const name of ["README.md", "readme.md"]) {
|
|
11441
11308
|
const p = path5.join(root, name);
|
|
11442
|
-
if (
|
|
11309
|
+
if (existsSync16(p)) {
|
|
11443
11310
|
return p;
|
|
11444
11311
|
}
|
|
11445
11312
|
}
|
|
@@ -11449,7 +11316,7 @@ async function discoverAgentFiles(root) {
|
|
|
11449
11316
|
const files = [];
|
|
11450
11317
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
11451
11318
|
const p = path5.join(root, name);
|
|
11452
|
-
if (
|
|
11319
|
+
if (existsSync16(p)) {
|
|
11453
11320
|
files.push(p);
|
|
11454
11321
|
}
|
|
11455
11322
|
}
|
|
@@ -11457,7 +11324,7 @@ async function discoverAgentFiles(root) {
|
|
|
11457
11324
|
}
|
|
11458
11325
|
async function discoverMerlleState(root) {
|
|
11459
11326
|
const p = path5.join(root, ".pourkit", "state.json");
|
|
11460
|
-
return
|
|
11327
|
+
return existsSync16(p) ? p : null;
|
|
11461
11328
|
}
|
|
11462
11329
|
async function discoverAgentSkills(root) {
|
|
11463
11330
|
const dirs = [
|
|
@@ -11466,7 +11333,7 @@ async function discoverAgentSkills(root) {
|
|
|
11466
11333
|
];
|
|
11467
11334
|
const found = [];
|
|
11468
11335
|
for (const d of dirs) {
|
|
11469
|
-
if (
|
|
11336
|
+
if (existsSync16(d)) {
|
|
11470
11337
|
found.push(d);
|
|
11471
11338
|
}
|
|
11472
11339
|
}
|
|
@@ -11476,12 +11343,12 @@ async function discoverRootDomainDocs(root) {
|
|
|
11476
11343
|
const docs = [];
|
|
11477
11344
|
for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
|
|
11478
11345
|
const p = path5.join(root, name);
|
|
11479
|
-
if (
|
|
11346
|
+
if (existsSync16(p)) {
|
|
11480
11347
|
docs.push(p);
|
|
11481
11348
|
}
|
|
11482
11349
|
}
|
|
11483
11350
|
const adrDir = path5.join(root, "docs", "adr");
|
|
11484
|
-
if (
|
|
11351
|
+
if (existsSync16(adrDir)) {
|
|
11485
11352
|
const entries = await readdir(adrDir, { withFileTypes: true });
|
|
11486
11353
|
for (const entry of entries) {
|
|
11487
11354
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -11624,7 +11491,7 @@ async function planInit(options) {
|
|
|
11624
11491
|
for (const file of skillFiles) {
|
|
11625
11492
|
const relPath = path5.relative(s, file);
|
|
11626
11493
|
const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
|
|
11627
|
-
if (!
|
|
11494
|
+
if (!existsSync16(destPath)) {
|
|
11628
11495
|
operations.push({
|
|
11629
11496
|
kind: "copy",
|
|
11630
11497
|
sourcePath: file,
|
|
@@ -11687,7 +11554,7 @@ async function planInit(options) {
|
|
|
11687
11554
|
});
|
|
11688
11555
|
}
|
|
11689
11556
|
if (sourceRoot) {
|
|
11690
|
-
if (!
|
|
11557
|
+
if (!existsSync16(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
|
|
11691
11558
|
warnings.push(
|
|
11692
11559
|
`--from-local path does not exist or is not a directory: ${sourceRoot}`
|
|
11693
11560
|
);
|
|
@@ -11806,7 +11673,7 @@ async function planInit(options) {
|
|
|
11806
11673
|
requiresConfirmation: false,
|
|
11807
11674
|
destructive: false
|
|
11808
11675
|
});
|
|
11809
|
-
} else if (
|
|
11676
|
+
} else if (existsSync16(destPath)) {
|
|
11810
11677
|
operations.push({
|
|
11811
11678
|
kind: "skip",
|
|
11812
11679
|
path: destPath,
|
|
@@ -11864,7 +11731,7 @@ async function planInit(options) {
|
|
|
11864
11731
|
}
|
|
11865
11732
|
}
|
|
11866
11733
|
const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
|
|
11867
|
-
if (!
|
|
11734
|
+
if (!existsSync16(contextPath) && !merleDestPaths.has(contextPath)) {
|
|
11868
11735
|
operations.push({
|
|
11869
11736
|
kind: "create",
|
|
11870
11737
|
path: contextPath,
|
|
@@ -11882,7 +11749,7 @@ async function planInit(options) {
|
|
|
11882
11749
|
"adr",
|
|
11883
11750
|
".gitkeep"
|
|
11884
11751
|
);
|
|
11885
|
-
if (!
|
|
11752
|
+
if (!existsSync16(adrGitkeep)) {
|
|
11886
11753
|
operations.push({
|
|
11887
11754
|
kind: "create",
|
|
11888
11755
|
path: adrGitkeep,
|
|
@@ -11894,7 +11761,7 @@ async function planInit(options) {
|
|
|
11894
11761
|
}
|
|
11895
11762
|
const srcDocAgents = path5.join(sourceRoot, ".pourkit", "docs", "agents");
|
|
11896
11763
|
const tgtDocAgents = path5.join(targetRoot, ".pourkit", "docs", "agents");
|
|
11897
|
-
if (
|
|
11764
|
+
if (existsSync16(srcDocAgents) && !existsSync16(tgtDocAgents)) {
|
|
11898
11765
|
const docFiles = await walkDir(srcDocAgents);
|
|
11899
11766
|
for (const file of docFiles) {
|
|
11900
11767
|
const relPath = path5.relative(srcDocAgents, file);
|
|
@@ -11927,7 +11794,7 @@ async function planInit(options) {
|
|
|
11927
11794
|
}
|
|
11928
11795
|
const srcPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
|
|
11929
11796
|
const tgtPrompts = path5.join(targetRoot, ".pourkit", "prompts");
|
|
11930
|
-
if (
|
|
11797
|
+
if (existsSync16(srcPrompts) && !existsSync16(tgtPrompts)) {
|
|
11931
11798
|
const promptFiles = await walkDir(srcPrompts);
|
|
11932
11799
|
for (const file of promptFiles) {
|
|
11933
11800
|
const relPath = path5.relative(srcPrompts, file);
|
|
@@ -11954,7 +11821,7 @@ async function planInit(options) {
|
|
|
11954
11821
|
".sandcastle",
|
|
11955
11822
|
"Dockerfile"
|
|
11956
11823
|
);
|
|
11957
|
-
if (
|
|
11824
|
+
if (existsSync16(tgtSandboxDockerfile)) {
|
|
11958
11825
|
operations.push({
|
|
11959
11826
|
kind: "skip",
|
|
11960
11827
|
path: tgtSandboxDockerfile,
|
|
@@ -11963,7 +11830,7 @@ async function planInit(options) {
|
|
|
11963
11830
|
requiresConfirmation: false,
|
|
11964
11831
|
destructive: false
|
|
11965
11832
|
});
|
|
11966
|
-
} else if (
|
|
11833
|
+
} else if (existsSync16(srcSandboxDockerfile)) {
|
|
11967
11834
|
const checksum = await computeFileChecksum(srcSandboxDockerfile);
|
|
11968
11835
|
operations.push({
|
|
11969
11836
|
kind: "copy",
|
|
@@ -11976,15 +11843,13 @@ async function planInit(options) {
|
|
|
11976
11843
|
checksum
|
|
11977
11844
|
});
|
|
11978
11845
|
}
|
|
11979
|
-
const
|
|
11980
|
-
if (!
|
|
11846
|
+
const configJsonPath = path5.join(targetRoot, ".pourkit", "config.json");
|
|
11847
|
+
if (!existsSync16(configJsonPath)) {
|
|
11981
11848
|
const verifyCommands = inferVerificationCommands(
|
|
11982
11849
|
packageScripts,
|
|
11983
11850
|
pm || "npm"
|
|
11984
11851
|
);
|
|
11985
11852
|
const configContent = generateConfigTemplate({
|
|
11986
|
-
targetRoot,
|
|
11987
|
-
sourceRoot,
|
|
11988
11853
|
packageManager: pm || "npm",
|
|
11989
11854
|
baseBranch,
|
|
11990
11855
|
verificationCommands: verifyCommands,
|
|
@@ -11993,9 +11858,9 @@ async function planInit(options) {
|
|
|
11993
11858
|
});
|
|
11994
11859
|
operations.push({
|
|
11995
11860
|
kind: "create",
|
|
11996
|
-
path:
|
|
11861
|
+
path: configJsonPath,
|
|
11997
11862
|
ownership: "managed",
|
|
11998
|
-
reason: "Init pourkit
|
|
11863
|
+
reason: "Init .pourkit/config.json template",
|
|
11999
11864
|
requiresConfirmation: false,
|
|
12000
11865
|
destructive: false,
|
|
12001
11866
|
content: configContent
|
|
@@ -12003,18 +11868,90 @@ async function planInit(options) {
|
|
|
12003
11868
|
} else {
|
|
12004
11869
|
operations.push({
|
|
12005
11870
|
kind: "skip",
|
|
12006
|
-
path:
|
|
11871
|
+
path: configJsonPath,
|
|
12007
11872
|
ownership: "project-owned",
|
|
12008
|
-
reason: "Existing pourkit
|
|
11873
|
+
reason: "Existing .pourkit/config.json (project-owned)",
|
|
12009
11874
|
requiresConfirmation: false,
|
|
12010
11875
|
destructive: false
|
|
12011
11876
|
});
|
|
12012
11877
|
}
|
|
11878
|
+
const schemaJsonPath = path5.join(
|
|
11879
|
+
targetRoot,
|
|
11880
|
+
".pourkit",
|
|
11881
|
+
"schema",
|
|
11882
|
+
"pourkit.schema.json"
|
|
11883
|
+
);
|
|
11884
|
+
const srcSchemaJson = path5.join(
|
|
11885
|
+
sourceRoot,
|
|
11886
|
+
"pourkit",
|
|
11887
|
+
"schema",
|
|
11888
|
+
"pourkit.schema.json"
|
|
11889
|
+
);
|
|
11890
|
+
if (existsSync16(srcSchemaJson)) {
|
|
11891
|
+
const checksum = await computeFileChecksum(srcSchemaJson);
|
|
11892
|
+
if (!existsSync16(schemaJsonPath)) {
|
|
11893
|
+
operations.push({
|
|
11894
|
+
kind: "copy",
|
|
11895
|
+
sourcePath: srcSchemaJson,
|
|
11896
|
+
path: schemaJsonPath,
|
|
11897
|
+
ownership: "managed",
|
|
11898
|
+
reason: "Copy schema: pourkit.schema.json",
|
|
11899
|
+
requiresConfirmation: false,
|
|
11900
|
+
destructive: false,
|
|
11901
|
+
checksum
|
|
11902
|
+
});
|
|
11903
|
+
} else {
|
|
11904
|
+
operations.push({
|
|
11905
|
+
kind: "skip",
|
|
11906
|
+
path: schemaJsonPath,
|
|
11907
|
+
ownership: "project-owned",
|
|
11908
|
+
reason: "Existing .pourkit/schema/pourkit.schema.json (project-owned)",
|
|
11909
|
+
requiresConfirmation: false,
|
|
11910
|
+
destructive: false
|
|
11911
|
+
});
|
|
11912
|
+
}
|
|
11913
|
+
}
|
|
11914
|
+
const schemaHashPath = path5.join(
|
|
11915
|
+
targetRoot,
|
|
11916
|
+
".pourkit",
|
|
11917
|
+
"schema",
|
|
11918
|
+
"pourkit.schema.hash"
|
|
11919
|
+
);
|
|
11920
|
+
const srcSchemaHash = path5.join(
|
|
11921
|
+
sourceRoot,
|
|
11922
|
+
"pourkit",
|
|
11923
|
+
"schema",
|
|
11924
|
+
"pourkit.schema.hash"
|
|
11925
|
+
);
|
|
11926
|
+
if (existsSync16(srcSchemaHash)) {
|
|
11927
|
+
const checksum = await computeFileChecksum(srcSchemaHash);
|
|
11928
|
+
if (!existsSync16(schemaHashPath)) {
|
|
11929
|
+
operations.push({
|
|
11930
|
+
kind: "copy",
|
|
11931
|
+
sourcePath: srcSchemaHash,
|
|
11932
|
+
path: schemaHashPath,
|
|
11933
|
+
ownership: "managed",
|
|
11934
|
+
reason: "Copy schema hash: pourkit.schema.hash",
|
|
11935
|
+
requiresConfirmation: false,
|
|
11936
|
+
destructive: false,
|
|
11937
|
+
checksum
|
|
11938
|
+
});
|
|
11939
|
+
} else {
|
|
11940
|
+
operations.push({
|
|
11941
|
+
kind: "skip",
|
|
11942
|
+
path: schemaHashPath,
|
|
11943
|
+
ownership: "project-owned",
|
|
11944
|
+
reason: "Existing .pourkit/schema/pourkit.schema.hash (project-owned)",
|
|
11945
|
+
requiresConfirmation: false,
|
|
11946
|
+
destructive: false
|
|
11947
|
+
});
|
|
11948
|
+
}
|
|
11949
|
+
}
|
|
12013
11950
|
const agentFileMode = options.conflictPolicy?.agentFile ?? "both";
|
|
12014
11951
|
const hasExistingAgents = operations.some(
|
|
12015
11952
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
|
|
12016
11953
|
);
|
|
12017
|
-
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !
|
|
11954
|
+
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync16(path5.join(targetRoot, "AGENTS.md"))) {
|
|
12018
11955
|
operations.push({
|
|
12019
11956
|
kind: "create",
|
|
12020
11957
|
path: path5.join(targetRoot, "AGENTS.md"),
|
|
@@ -12030,7 +11967,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
12030
11967
|
const hasExistingClaude = operations.some(
|
|
12031
11968
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
|
|
12032
11969
|
);
|
|
12033
|
-
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !
|
|
11970
|
+
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync16(path5.join(targetRoot, "CLAUDE.md"))) {
|
|
12034
11971
|
operations.push({
|
|
12035
11972
|
kind: "create",
|
|
12036
11973
|
path: path5.join(targetRoot, "CLAUDE.md"),
|
|
@@ -12045,7 +11982,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
12045
11982
|
}
|
|
12046
11983
|
const gitignoreTarget = path5.join(targetRoot, ".gitignore");
|
|
12047
11984
|
const gitignoreContent = generateGitignoreBlock();
|
|
12048
|
-
if (!
|
|
11985
|
+
if (!existsSync16(gitignoreTarget)) {
|
|
12049
11986
|
operations.push({
|
|
12050
11987
|
kind: "create",
|
|
12051
11988
|
path: gitignoreTarget,
|
|
@@ -12069,7 +12006,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
12069
12006
|
});
|
|
12070
12007
|
}
|
|
12071
12008
|
const openCodePath = path5.join(targetRoot, "opencode.json");
|
|
12072
|
-
if (!
|
|
12009
|
+
if (!existsSync16(openCodePath)) {
|
|
12073
12010
|
operations.push({
|
|
12074
12011
|
kind: "create",
|
|
12075
12012
|
path: openCodePath,
|
|
@@ -12126,7 +12063,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
12126
12063
|
}
|
|
12127
12064
|
}
|
|
12128
12065
|
const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
|
|
12129
|
-
if (
|
|
12066
|
+
if (existsSync16(manifestPath)) {
|
|
12130
12067
|
operations.push({
|
|
12131
12068
|
kind: "skip",
|
|
12132
12069
|
path: manifestPath,
|
|
@@ -12445,7 +12382,7 @@ async function updateManagedBlock(filePath, content) {
|
|
|
12445
12382
|
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
12446
12383
|
${content}${MANAGED_BLOCK_END}
|
|
12447
12384
|
`;
|
|
12448
|
-
if (!
|
|
12385
|
+
if (!existsSync16(filePath)) {
|
|
12449
12386
|
const dir = path5.dirname(filePath);
|
|
12450
12387
|
await mkdir5(dir, { recursive: true });
|
|
12451
12388
|
await writeFileAtomic(filePath, blockContent);
|
|
@@ -12474,7 +12411,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
|
12474
12411
|
if (op.requiresConfirmation) continue;
|
|
12475
12412
|
const relPath = path5.relative(plan.targetRoot, op.path);
|
|
12476
12413
|
if (relPath === ".pourkit/manifest.json") continue;
|
|
12477
|
-
if (
|
|
12414
|
+
if (existsSync16(op.path)) {
|
|
12478
12415
|
const sha256 = await computeFileChecksum(op.path);
|
|
12479
12416
|
assets[relPath] = {
|
|
12480
12417
|
ownership: op.ownership || "managed",
|
|
@@ -12519,7 +12456,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12519
12456
|
skipped++;
|
|
12520
12457
|
continue;
|
|
12521
12458
|
}
|
|
12522
|
-
if (
|
|
12459
|
+
if (existsSync16(op.path) && !op.destructive) {
|
|
12523
12460
|
skipped++;
|
|
12524
12461
|
continue;
|
|
12525
12462
|
}
|
|
@@ -12534,7 +12471,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12534
12471
|
skipped++;
|
|
12535
12472
|
continue;
|
|
12536
12473
|
}
|
|
12537
|
-
if (
|
|
12474
|
+
if (existsSync16(op.path)) {
|
|
12538
12475
|
skipped++;
|
|
12539
12476
|
continue;
|
|
12540
12477
|
}
|
|
@@ -12563,7 +12500,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12563
12500
|
skipped++;
|
|
12564
12501
|
continue;
|
|
12565
12502
|
}
|
|
12566
|
-
if (
|
|
12503
|
+
if (existsSync16(op.path)) {
|
|
12567
12504
|
skipped++;
|
|
12568
12505
|
continue;
|
|
12569
12506
|
}
|
|
@@ -12703,7 +12640,7 @@ async function applyInitFromSource(options) {
|
|
|
12703
12640
|
if (!manifestSkipped) {
|
|
12704
12641
|
const agentFiles = [];
|
|
12705
12642
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
12706
|
-
if (
|
|
12643
|
+
if (existsSync16(path5.join(targetRoot, name))) {
|
|
12707
12644
|
agentFiles.push(path5.join(targetRoot, name));
|
|
12708
12645
|
}
|
|
12709
12646
|
}
|
|
@@ -12994,6 +12931,165 @@ async function runSerenaStatusCommand(options) {
|
|
|
12994
12931
|
logSerenaSidecarStatus("Serena sidecar status", status);
|
|
12995
12932
|
}
|
|
12996
12933
|
|
|
12934
|
+
// commands/config-schema.ts
|
|
12935
|
+
import { readFileSync as readFileSync19, existsSync as existsSync17 } from "fs";
|
|
12936
|
+
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
|
|
12937
|
+
import { resolve as resolve4, dirname as dirname5 } from "path";
|
|
12938
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12939
|
+
import Ajv2 from "ajv";
|
|
12940
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
12941
|
+
var __dirname2 = dirname5(__filename2);
|
|
12942
|
+
var PACKAGED_SCHEMA_PATH = resolve4(
|
|
12943
|
+
__dirname2,
|
|
12944
|
+
"../schema/pourkit.schema.json"
|
|
12945
|
+
);
|
|
12946
|
+
var PACKAGED_HASH_PATH = resolve4(__dirname2, "../schema/pourkit.schema.hash");
|
|
12947
|
+
var _schemaValidator = null;
|
|
12948
|
+
var _schemaErrors = null;
|
|
12949
|
+
function getSchemaValidator() {
|
|
12950
|
+
if (!_schemaValidator) {
|
|
12951
|
+
const schema = JSON.parse(readFileSync19(PACKAGED_SCHEMA_PATH, "utf-8"));
|
|
12952
|
+
const ajv = new Ajv2({ strict: true });
|
|
12953
|
+
ajv.addKeyword("x-pourkit-schema-version");
|
|
12954
|
+
const validate = ajv.compile(schema);
|
|
12955
|
+
_schemaValidator = (data) => {
|
|
12956
|
+
_schemaErrors = null;
|
|
12957
|
+
const valid2 = validate(data);
|
|
12958
|
+
if (!valid2) _schemaErrors = validate.errors;
|
|
12959
|
+
return valid2;
|
|
12960
|
+
};
|
|
12961
|
+
}
|
|
12962
|
+
return _schemaValidator;
|
|
12963
|
+
}
|
|
12964
|
+
function readPackagedHash() {
|
|
12965
|
+
return readFileSync19(PACKAGED_HASH_PATH, "utf-8");
|
|
12966
|
+
}
|
|
12967
|
+
function localSchemaDir(repoRoot2) {
|
|
12968
|
+
return resolve4(repoRoot2, ".pourkit/schema");
|
|
12969
|
+
}
|
|
12970
|
+
async function runDoctorCommand(options) {
|
|
12971
|
+
const repoRootPath = options.cwd ?? process.cwd();
|
|
12972
|
+
const schemaDir = localSchemaDir(repoRootPath);
|
|
12973
|
+
const localSchemaPath = resolve4(schemaDir, "pourkit.schema.json");
|
|
12974
|
+
const localHashPath = resolve4(schemaDir, "pourkit.schema.hash");
|
|
12975
|
+
const localSchemaExists = existsSync17(localSchemaPath);
|
|
12976
|
+
const localHashExists = existsSync17(localHashPath);
|
|
12977
|
+
let packagedHash = null;
|
|
12978
|
+
try {
|
|
12979
|
+
packagedHash = readPackagedHash();
|
|
12980
|
+
} catch {
|
|
12981
|
+
}
|
|
12982
|
+
let localHashContent = null;
|
|
12983
|
+
if (localHashExists) {
|
|
12984
|
+
try {
|
|
12985
|
+
localHashContent = await readFile6(localHashPath, "utf-8");
|
|
12986
|
+
} catch {
|
|
12987
|
+
}
|
|
12988
|
+
}
|
|
12989
|
+
const schema = !localSchemaExists ? "missing" : !packagedHash || !localHashContent ? "missing" : localHashContent.trim() === packagedHash.trim() ? "fresh" : "stale";
|
|
12990
|
+
const hash = !localHashExists ? "missing" : !packagedHash ? "missing" : localHashContent && localHashContent.trim() === packagedHash.trim() ? "fresh" : "stale";
|
|
12991
|
+
const overall = schema === "fresh" && hash === "fresh" ? "fresh" : schema === "missing" || hash === "missing" ? "missing" : "stale";
|
|
12992
|
+
const configPath = resolve4(repoRootPath, ".pourkit/config.json");
|
|
12993
|
+
let configValidation;
|
|
12994
|
+
if (existsSync17(configPath)) {
|
|
12995
|
+
try {
|
|
12996
|
+
const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
12997
|
+
const validate = getSchemaValidator();
|
|
12998
|
+
const valid2 = validate(raw);
|
|
12999
|
+
if (valid2) {
|
|
13000
|
+
configValidation = { ok: true, errors: [] };
|
|
13001
|
+
} else {
|
|
13002
|
+
const errors = _schemaErrors;
|
|
13003
|
+
configValidation = {
|
|
13004
|
+
ok: false,
|
|
13005
|
+
errors: (errors ?? []).map(
|
|
13006
|
+
(e) => `${e.instancePath || "(root)"}: ${e.message ?? "validation failed"}`
|
|
13007
|
+
)
|
|
13008
|
+
};
|
|
13009
|
+
}
|
|
13010
|
+
} catch (err) {
|
|
13011
|
+
const msg = err instanceof SyntaxError ? err.message : String(err);
|
|
13012
|
+
configValidation = {
|
|
13013
|
+
ok: false,
|
|
13014
|
+
errors: [`.pourkit/config.json: ${msg}`]
|
|
13015
|
+
};
|
|
13016
|
+
}
|
|
13017
|
+
} else {
|
|
13018
|
+
configValidation = {
|
|
13019
|
+
ok: false,
|
|
13020
|
+
errors: [".pourkit/config.json not found"]
|
|
13021
|
+
};
|
|
13022
|
+
}
|
|
13023
|
+
const obsoleteConfigs = [
|
|
13024
|
+
"pourkit.config.ts",
|
|
13025
|
+
"pourkit.config.mjs",
|
|
13026
|
+
"pourkit.config.js",
|
|
13027
|
+
"pourkit.json"
|
|
13028
|
+
].filter((p) => existsSync17(resolve4(repoRootPath, p)));
|
|
13029
|
+
let recommendation = null;
|
|
13030
|
+
if (!configValidation.ok || overall !== "fresh" || obsoleteConfigs.length > 0) {
|
|
13031
|
+
const parts = [];
|
|
13032
|
+
if (obsoleteConfigs.length > 0) {
|
|
13033
|
+
parts.push(
|
|
13034
|
+
`Found obsolete config files: ${obsoleteConfigs.join(", ")}. Move configuration to .pourkit/config.json with "$schema": "./schema/pourkit.schema.json".`
|
|
13035
|
+
);
|
|
13036
|
+
}
|
|
13037
|
+
if (!configValidation.ok) {
|
|
13038
|
+
parts.push(
|
|
13039
|
+
"Config validation failed; fix errors in .pourkit/config.json."
|
|
13040
|
+
);
|
|
13041
|
+
}
|
|
13042
|
+
if (overall !== "fresh") {
|
|
13043
|
+
parts.push(
|
|
13044
|
+
'Run "pourkit config sync-schema" to update local schema assets.'
|
|
13045
|
+
);
|
|
13046
|
+
}
|
|
13047
|
+
recommendation = parts.join(" ");
|
|
13048
|
+
}
|
|
13049
|
+
return {
|
|
13050
|
+
configValidation,
|
|
13051
|
+
obsoleteConfigs,
|
|
13052
|
+
schemaAssets: { schema, hash, overall },
|
|
13053
|
+
recommendation
|
|
13054
|
+
};
|
|
13055
|
+
}
|
|
13056
|
+
async function runConfigSyncSchemaCommand(options) {
|
|
13057
|
+
const repoRootPath = options.cwd ?? process.cwd();
|
|
13058
|
+
const schemaDir = localSchemaDir(repoRootPath);
|
|
13059
|
+
await mkdir6(schemaDir, { recursive: true });
|
|
13060
|
+
const packagedSchema = await readFile6(PACKAGED_SCHEMA_PATH, "utf-8");
|
|
13061
|
+
const packagedHash = await readFile6(PACKAGED_HASH_PATH, "utf-8");
|
|
13062
|
+
const localSchemaPath = resolve4(schemaDir, "pourkit.schema.json");
|
|
13063
|
+
const localHashPath = resolve4(schemaDir, "pourkit.schema.hash");
|
|
13064
|
+
let schemaWritten = false;
|
|
13065
|
+
let hashWritten = false;
|
|
13066
|
+
if (existsSync17(localSchemaPath)) {
|
|
13067
|
+
const existing = await readFile6(localSchemaPath, "utf-8");
|
|
13068
|
+
if (existing !== packagedSchema) {
|
|
13069
|
+
await writeFile3(localSchemaPath, packagedSchema, "utf-8");
|
|
13070
|
+
schemaWritten = true;
|
|
13071
|
+
}
|
|
13072
|
+
} else {
|
|
13073
|
+
await writeFile3(localSchemaPath, packagedSchema, "utf-8");
|
|
13074
|
+
schemaWritten = true;
|
|
13075
|
+
}
|
|
13076
|
+
if (existsSync17(localHashPath)) {
|
|
13077
|
+
const existing = await readFile6(localHashPath, "utf-8");
|
|
13078
|
+
if (existing !== packagedHash) {
|
|
13079
|
+
await writeFile3(localHashPath, packagedHash, "utf-8");
|
|
13080
|
+
hashWritten = true;
|
|
13081
|
+
}
|
|
13082
|
+
} else {
|
|
13083
|
+
await writeFile3(localHashPath, packagedHash, "utf-8");
|
|
13084
|
+
hashWritten = true;
|
|
13085
|
+
}
|
|
13086
|
+
return {
|
|
13087
|
+
changed: schemaWritten || hashWritten,
|
|
13088
|
+
schemaWritten,
|
|
13089
|
+
hashWritten
|
|
13090
|
+
};
|
|
13091
|
+
}
|
|
13092
|
+
|
|
12997
13093
|
// providers/github-provider.ts
|
|
12998
13094
|
var GitHubIssueProvider = class {
|
|
12999
13095
|
client;
|
|
@@ -13523,22 +13619,22 @@ function formatChecks2(checks) {
|
|
|
13523
13619
|
init_common();
|
|
13524
13620
|
|
|
13525
13621
|
// execution/sandcastle-execution.ts
|
|
13526
|
-
import { mkdirSync as
|
|
13527
|
-
import { join as
|
|
13622
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
13623
|
+
import { join as join21 } from "path";
|
|
13528
13624
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
13529
13625
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
13530
13626
|
|
|
13531
13627
|
// execution/execution-provider.ts
|
|
13532
13628
|
init_common();
|
|
13533
|
-
import { mkdtempSync
|
|
13534
|
-
import { writeFile as
|
|
13535
|
-
import { tmpdir
|
|
13536
|
-
import { dirname as
|
|
13629
|
+
import { mkdtempSync } from "fs";
|
|
13630
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
13631
|
+
import { tmpdir } from "os";
|
|
13632
|
+
import { dirname as dirname6, join as join20 } from "path";
|
|
13537
13633
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
13538
13634
|
for (const artifact of artifacts) {
|
|
13539
|
-
const filePath =
|
|
13540
|
-
await ensureDir(
|
|
13541
|
-
await
|
|
13635
|
+
const filePath = join20(worktreePath, artifact.path);
|
|
13636
|
+
await ensureDir(dirname6(filePath));
|
|
13637
|
+
await writeFile4(filePath, artifact.content, "utf-8");
|
|
13542
13638
|
}
|
|
13543
13639
|
}
|
|
13544
13640
|
|
|
@@ -13547,8 +13643,8 @@ init_common();
|
|
|
13547
13643
|
import path7 from "path";
|
|
13548
13644
|
|
|
13549
13645
|
// execution/sandbox-image.ts
|
|
13550
|
-
import { createHash as
|
|
13551
|
-
import { existsSync as existsSync18, readFileSync as
|
|
13646
|
+
import { createHash as createHash3 } from "crypto";
|
|
13647
|
+
import { existsSync as existsSync18, readFileSync as readFileSync20 } from "fs";
|
|
13552
13648
|
import path6 from "path";
|
|
13553
13649
|
function sandboxImageName(repoRoot2) {
|
|
13554
13650
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
@@ -13558,7 +13654,7 @@ function sandboxImageName(repoRoot2) {
|
|
|
13558
13654
|
if (!existsSync18(dockerfilePath)) {
|
|
13559
13655
|
return `sandcastle:${baseName}`;
|
|
13560
13656
|
}
|
|
13561
|
-
const fingerprint =
|
|
13657
|
+
const fingerprint = createHash3("sha256").update(readFileSync20(dockerfilePath)).digest("hex").slice(0, 8);
|
|
13562
13658
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
13563
13659
|
}
|
|
13564
13660
|
|
|
@@ -13854,13 +13950,13 @@ function isPlainObject(value) {
|
|
|
13854
13950
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13855
13951
|
}
|
|
13856
13952
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
13857
|
-
const promptsDir =
|
|
13858
|
-
|
|
13953
|
+
const promptsDir = join21(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
13954
|
+
mkdirSync9(promptsDir, { recursive: true });
|
|
13859
13955
|
const timestamp2 = Date.now();
|
|
13860
13956
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
13861
13957
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
13862
|
-
const filePath =
|
|
13863
|
-
|
|
13958
|
+
const filePath = join21(promptsDir, filename);
|
|
13959
|
+
writeFileSync7(filePath, prompt, "utf-8");
|
|
13864
13960
|
}
|
|
13865
13961
|
|
|
13866
13962
|
// cli.ts
|
|
@@ -13960,7 +14056,7 @@ function createCliProgram(version) {
|
|
|
13960
14056
|
});
|
|
13961
14057
|
program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
|
|
13962
14058
|
"<kind>",
|
|
13963
|
-
"artifact kind: reviewer, refactor, finalizer, conflict-resolution,
|
|
14059
|
+
"artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution, local-prd, local-issue, local-triage, or issue-final-review"
|
|
13964
14060
|
).argument("<artifactPath>", "artifact path to validate").argument("[extraPaths...]", "additional artifact paths (for local-triage)").option("--iteration <number>", "review/refactor iteration", (value) => {
|
|
13965
14061
|
const parsed = Number.parseInt(value, 10);
|
|
13966
14062
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
@@ -13993,12 +14089,6 @@ function createCliProgram(version) {
|
|
|
13993
14089
|
return previous;
|
|
13994
14090
|
},
|
|
13995
14091
|
[]
|
|
13996
|
-
).option("--prd-ref <ref>", "PRD ref for final-review artifacts").option(
|
|
13997
|
-
"--checkout-base <ref>",
|
|
13998
|
-
"checkout base / PRD branch for final-review artifacts"
|
|
13999
|
-
).option(
|
|
14000
|
-
"--review-base <ref>",
|
|
14001
|
-
"review merge base for final-review artifacts"
|
|
14002
14092
|
).option(
|
|
14003
14093
|
"--issue-number <number>",
|
|
14004
14094
|
"issue number for issue-final-review artifacts",
|
|
@@ -14023,7 +14113,6 @@ function createCliProgram(version) {
|
|
|
14023
14113
|
"refactor",
|
|
14024
14114
|
"finalizer",
|
|
14025
14115
|
"conflict-resolution",
|
|
14026
|
-
"final-review",
|
|
14027
14116
|
"failure-resolution",
|
|
14028
14117
|
"local-prd",
|
|
14029
14118
|
"local-issue",
|
|
@@ -14057,9 +14146,6 @@ function createCliProgram(version) {
|
|
|
14057
14146
|
findingIds: options.findingId,
|
|
14058
14147
|
latestReviewArtifactPath: options.latestReviewArtifact,
|
|
14059
14148
|
allowedDecisions: options.allowedDecision,
|
|
14060
|
-
prdRef: options.prdRef,
|
|
14061
|
-
checkoutBase: options.checkoutBase,
|
|
14062
|
-
reviewBase: options.reviewBase,
|
|
14063
14149
|
issueNumber: options.issueNumber,
|
|
14064
14150
|
branchName: options.branchName,
|
|
14065
14151
|
checkConflictMarkers: options.checkConflictMarkers
|
|
@@ -14418,6 +14504,26 @@ function createCliProgram(version) {
|
|
|
14418
14504
|
await runInitCommand(initOptions);
|
|
14419
14505
|
}
|
|
14420
14506
|
);
|
|
14507
|
+
program.command("doctor").description("Report Config Schema drift without mutating files").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
14508
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
14509
|
+
const report = await runDoctorCommand({ cwd: targetRepoRoot });
|
|
14510
|
+
console.log(JSON.stringify(report, null, 2));
|
|
14511
|
+
if (!report.configValidation.ok || report.obsoleteConfigs.length > 0 || report.schemaAssets.overall !== "fresh") {
|
|
14512
|
+
process.exitCode = 1;
|
|
14513
|
+
}
|
|
14514
|
+
});
|
|
14515
|
+
const configCommand = program.command("config").description("Config management commands");
|
|
14516
|
+
configCommand.command("sync-schema").description("Copy packaged Config Schema assets to .pourkit/schema/").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
14517
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
14518
|
+
const result = await runConfigSyncSchemaCommand({ cwd: targetRepoRoot });
|
|
14519
|
+
if (result.changed) {
|
|
14520
|
+
console.log(
|
|
14521
|
+
`Schema assets updated (schema: ${result.schemaWritten ? "written" : "unchanged"}, hash: ${result.hashWritten ? "written" : "unchanged"}).`
|
|
14522
|
+
);
|
|
14523
|
+
} else {
|
|
14524
|
+
console.log("Schema assets are up to date.");
|
|
14525
|
+
}
|
|
14526
|
+
});
|
|
14421
14527
|
const serena = program.command("serena").description("Serena baseline and sidecar commands");
|
|
14422
14528
|
serena.command("init").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
14423
14529
|
await runSerenaInitCommand({
|
|
@@ -14543,11 +14649,11 @@ function createCliProgram(version) {
|
|
|
14543
14649
|
return program;
|
|
14544
14650
|
}
|
|
14545
14651
|
async function resolveCliVersion() {
|
|
14546
|
-
if (isPackageVersion("0.0.0-next-
|
|
14547
|
-
return "0.0.0-next-
|
|
14652
|
+
if (isPackageVersion("0.0.0-next-20260614182520")) {
|
|
14653
|
+
return "0.0.0-next-20260614182520";
|
|
14548
14654
|
}
|
|
14549
|
-
if (isReleaseVersion("0.0.0-next-
|
|
14550
|
-
return "0.0.0-next-
|
|
14655
|
+
if (isReleaseVersion("0.0.0-next-20260614182520")) {
|
|
14656
|
+
return "0.0.0-next-20260614182520";
|
|
14551
14657
|
}
|
|
14552
14658
|
try {
|
|
14553
14659
|
const root = repoRoot();
|