@pourkit/cli 0.0.0-next-20260622055621 → 0.0.0-next-20260622235247

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 CHANGED
@@ -3740,10 +3740,6 @@ function shellQuote(value) {
3740
3740
  return `'${value.replace(/'/g, `'"'"'`)}'`;
3741
3741
  }
3742
3742
 
3743
- // commands/issue-run.ts
3744
- import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
3745
- import { isAbsolute as isAbsolute4, join as join15 } from "path";
3746
-
3747
3743
  // pr/templates.ts
3748
3744
  init_common();
3749
3745
  function renderBranchName(template, issue) {
@@ -4960,9 +4956,9 @@ async function assertCanonicalBaseAncestor(options) {
4960
4956
  }
4961
4957
  }
4962
4958
 
4963
- // commands/pr-description-agent.ts
4964
- import { join as join11 } from "path";
4965
- import { readFileSync as readFileSync12 } from "fs";
4959
+ // issues/pr-description-agent.ts
4960
+ import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
4961
+ import { isAbsolute as isAbsolute4, join as join12 } from "path";
4966
4962
 
4967
4963
  // pr/pr-description-context.ts
4968
4964
  init_common();
@@ -5084,204 +5080,323 @@ Write your finalizer output to: ${artifactPathInWorktree}
5084
5080
  Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktree}`;
5085
5081
  }
5086
5082
 
5087
- // commands/pr-description-agent.ts
5088
- import { Effect as Effect7, Layer as Layer3 } from "effect";
5089
- function bridgeExecutionProvider2(ep) {
5090
- return Layer3.succeed(
5091
- ExecutionProvider,
5092
- ExecutionProvider.of({
5093
- execute: (opts) => Effect7.tryPromise({
5094
- try: () => ep.execute(opts),
5095
- catch: (e) => new Error(
5096
- e instanceof Error ? e.message : `Execution failed: ${String(e)}`
5097
- )
5098
- })
5099
- })
5100
- );
5101
- }
5102
- function runFinalizerAgent(options) {
5103
- const { executionProvider } = options;
5104
- const artifactPathInWorktree = join11(
5105
- ".pourkit",
5106
- ".tmp",
5107
- "finalizer",
5108
- "agent-output.md"
5109
- );
5110
- const artifactPath = join11(options.worktreePath, artifactPathInWorktree);
5111
- const program = Effect7.gen(function* () {
5112
- const fs = yield* FileSystem;
5113
- const context = yield* collectFinalizerContextEffect({
5114
- targetBase: options.targetBaseBranch,
5115
- branchName: options.builderBranch,
5116
- worktreePath: options.worktreePath,
5117
- reviewArtifactPath: options.reviewArtifactPath,
5118
- logger: options.logger
5119
- }).pipe(
5120
- Effect7.catchAll(
5121
- (error) => Effect7.fail(
5122
- new FinalizerFailure({
5123
- message: `Failed to collect finalizer context: ${error.message}`
5124
- })
5125
- )
5126
- )
5127
- );
5128
- const strategy = options.target.strategy;
5129
- const finalizer = strategy.finalize.prDescriptionAgent;
5130
- const resolvedPrompt = yield* loadFinalizerPromptEffect(
5131
- options.repoRoot,
5132
- finalizer.promptTemplate,
5133
- fs
5134
- );
5135
- const prompt = buildFinalizerPrompt(context, resolvedPrompt);
5136
- const result = yield* runPrDescriptionFinalizerCore({
5137
- executionProvider: options.executionProvider,
5138
- config: options.config,
5139
- target: options.target,
5140
- finalizer,
5141
- maxAttempts: strategy.finalize.maxAttempts,
5083
+ // issues/issue-final-review.ts
5084
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
5085
+ import { isAbsolute as isAbsolute3, join as join11 } from "path";
5086
+ init_common();
5087
+ var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join11(
5088
+ ".pourkit",
5089
+ ".tmp",
5090
+ "issue-final-review",
5091
+ "agent-output.json"
5092
+ );
5093
+ async function runIssueFinalReview(options) {
5094
+ const {
5095
+ executionProvider,
5096
+ target,
5097
+ issue,
5098
+ parentPrdIssue,
5099
+ builderBranch,
5100
+ worktreePath,
5101
+ repoRoot: repoRoot2,
5102
+ logger
5103
+ } = options;
5104
+ const artifactPathInWorktree = ISSUE_FINAL_REVIEW_ARTIFACT_PATH;
5105
+ const artifactPath = join11(worktreePath, artifactPathInWorktree);
5106
+ const ifrFromState = options.worktreeState?.issueFinalReview;
5107
+ if (ifrFromState?.completed && ifrFromState.verdict === "pass") {
5108
+ if (!ifrFromState.artifactPath) {
5109
+ throw new Error(
5110
+ "Issue Final Review state is incomplete: missing artifactPath"
5111
+ );
5112
+ }
5113
+ const cachedArtifactPath = isAbsolute3(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join11(worktreePath, ifrFromState.artifactPath);
5114
+ const validation2 = validateAgentArtifact({
5115
+ kind: "issue-final-review",
5116
+ artifactPath: cachedArtifactPath,
5117
+ issueNumber: issue.number,
5118
+ branchName: builderBranch
5119
+ });
5120
+ if (!validation2.ok) {
5121
+ throw new Error(
5122
+ `Issue Final Review state artifact is invalid: ${validation2.reason}`
5123
+ );
5124
+ }
5125
+ return {
5126
+ status: "skipped",
5127
+ verdict: "pass",
5128
+ artifactPath: cachedArtifactPath,
5129
+ selfRetouched: ifrFromState.selfRetouched ?? false,
5130
+ changedPaths: ifrFromState.changedPaths ?? [],
5131
+ verificationPassed: ifrFromState.verificationPassed ?? false
5132
+ };
5133
+ }
5134
+ const strategy = target.strategy;
5135
+ const issueFinalReview = strategy.issueFinalReview;
5136
+ const agent = issueFinalReview;
5137
+ const prompt = loadIssueFinalReviewPrompt({
5138
+ repoRoot: repoRoot2,
5139
+ promptTemplate: agent.promptTemplate,
5140
+ validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}`,
5141
+ reviewArtifactPath: options.reviewArtifactPath,
5142
+ reviewVerdict: options.reviewVerdict
5143
+ });
5144
+ const entry = await executeWithMissingOrEmptyArtifactRetry({
5145
+ executionProvider,
5146
+ missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(agent),
5147
+ logger,
5148
+ runningMessage: () => "Running Issue Final Review agent",
5149
+ retryMessage: (attempt, total) => `Retrying Issue Final Review after empty output (${attempt}/${total})`,
5150
+ executionOptions: {
5151
+ stage: "issueFinalReview",
5152
+ agent: agent.agent,
5153
+ model: agent.model,
5154
+ variant: agent.variant,
5155
+ env: agent.env,
5142
5156
  prompt,
5143
- branchName: options.builderBranch,
5144
- repoRoot: options.repoRoot,
5145
- worktreePath: options.worktreePath,
5146
- artifactPathInWorktree,
5147
- commitSummaries: context.commits,
5157
+ target,
5158
+ repoRoot: repoRoot2,
5159
+ branchName: builderBranch,
5160
+ sandbox: options.config.sandbox,
5161
+ autoApprove: true,
5162
+ artifactPath: artifactPathInWorktree,
5163
+ worktreePath,
5148
5164
  artifacts: [
5149
5165
  buildRunContextArtifact({
5150
- issue: options.issue,
5151
- target: options.target,
5152
- branchName: options.builderBranch,
5166
+ issue,
5167
+ parentPrdIssue,
5168
+ target,
5169
+ branchName: builderBranch,
5170
+ repoRoot: repoRoot2,
5153
5171
  reviewerCriteria: strategy.review.reviewer.criteria,
5154
- sections: STAGE_SECTIONS.finalizer
5172
+ sections: STAGE_SECTIONS.issueFinalReview
5155
5173
  })
5156
5174
  ],
5157
- logger: options.logger,
5158
- ...options.memory ? { memory: options.memory } : {}
5159
- });
5160
- const output = readFileSync12(artifactPath, "utf-8");
5161
- yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
5162
- return result;
5175
+ ...options.memory ? { memory: options.memory } : {},
5176
+ logger
5177
+ }
5163
5178
  });
5164
- const executionLayer = bridgeExecutionProvider2(executionProvider);
5165
- return program.pipe(
5166
- Effect7.provide(
5167
- Layer3.mergeAll(executionLayer, FileSystemDefault, GitOperationsDefault)
5168
- )
5179
+ const executionResult = entry.executionResult;
5180
+ if (!executionResult.success) {
5181
+ throw new Error(
5182
+ `Issue Final Review agent execution failed: ${executionResult.error}`
5183
+ );
5184
+ }
5185
+ let content;
5186
+ if (entry.artifact._tag === "content") {
5187
+ content = entry.artifact.value;
5188
+ } else if (entry.artifact._tag === "empty") {
5189
+ throw new Error(
5190
+ `Issue Final Review agent produced empty output at ${artifactPath}`
5191
+ );
5192
+ } else {
5193
+ throw new Error(
5194
+ `Issue Final Review agent did not produce output at ${artifactPath}`
5195
+ );
5196
+ }
5197
+ const validation = validateAgentArtifact({
5198
+ kind: "issue-final-review",
5199
+ artifactPath,
5200
+ issueNumber: issue.number,
5201
+ branchName: builderBranch
5202
+ });
5203
+ if (!validation.ok) {
5204
+ throw new Error(
5205
+ `Issue Final Review artifact validation failed: ${validation.reason}`
5206
+ );
5207
+ }
5208
+ const parsed = JSON.parse(content);
5209
+ const verdict = parsed.verdict;
5210
+ const baseRef = `origin/${target.baseBranch}`;
5211
+ await assertCanonicalBaseAncestor2({
5212
+ worktreePath,
5213
+ baseRef,
5214
+ stageName: "Issue Final Review",
5215
+ logger
5216
+ });
5217
+ if (verdict === "pass") {
5218
+ const changedPaths = await completeIssueFinalReviewChangedPaths({
5219
+ worktreePath,
5220
+ baseRef,
5221
+ declaredChangedPaths: extractChangedPaths(parsed),
5222
+ logger
5223
+ });
5224
+ updateWorktreeRunState(worktreePath, {
5225
+ issueFinalReview: {
5226
+ completed: true,
5227
+ verdict: "pass",
5228
+ artifactPath,
5229
+ selfRetouched: parsed.selfRetouched === true,
5230
+ changedPaths,
5231
+ verificationPassed: parsed.verification?.passed === true
5232
+ }
5233
+ });
5234
+ return {
5235
+ status: "passed",
5236
+ verdict: "pass",
5237
+ artifactPath,
5238
+ selfRetouched: parsed.selfRetouched === true,
5239
+ changedPaths,
5240
+ verificationPassed: parsed.verification?.passed === true
5241
+ };
5242
+ }
5243
+ if (verdict === "needs_human_review") {
5244
+ return {
5245
+ status: "needs_human_review",
5246
+ verdict: "needs_human_review",
5247
+ artifactPath,
5248
+ needsHumanReason: parsed.needsHumanReason,
5249
+ selfRetouched: parsed.selfRetouched === true,
5250
+ changedPaths: extractChangedPaths(parsed)
5251
+ };
5252
+ }
5253
+ throw new Error(
5254
+ `Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
5169
5255
  );
5170
5256
  }
5171
- function runPrDescriptionFinalizerCore(options) {
5172
- const artifactPath = join11(
5173
- options.worktreePath,
5174
- options.artifactPathInWorktree
5175
- );
5176
- return Effect7.gen(function* () {
5177
- let parsed;
5178
- let lastValidationError;
5179
- for (let protocolAttempt = 1; protocolAttempt <= options.maxAttempts; protocolAttempt++) {
5180
- const retryResult = yield* Effect7.tryPromise({
5181
- try: () => executeWithMissingOrEmptyArtifactRetry({
5182
- executionProvider: options.executionProvider,
5183
- missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(
5184
- options.finalizer
5185
- ),
5186
- logger: options.logger,
5187
- runningMessage: () => `Running finalizer agent (${protocolAttempt}/${options.maxAttempts})`,
5188
- retryMessage: (attempt, total) => `Retrying finalizer after empty output (${attempt}/${total})`,
5189
- executionOptions: {
5190
- stage: "finalizer",
5191
- agent: options.finalizer.agent,
5192
- model: options.finalizer.model,
5193
- variant: options.finalizer.variant,
5194
- env: options.finalizer.env,
5195
- prompt: options.prompt,
5196
- target: options.target,
5197
- repoRoot: options.repoRoot,
5198
- branchName: options.branchName,
5199
- sandbox: options.config.sandbox,
5200
- autoApprove: true,
5201
- artifactPath: options.artifactPathInWorktree,
5202
- worktreePath: options.worktreePath,
5203
- artifacts: options.artifacts,
5204
- baseRef: options.baseRef,
5205
- checkoutBase: options.checkoutBase,
5206
- reviewBase: options.reviewBase,
5207
- ...options.memory ? { memory: options.memory } : {},
5208
- logger: options.logger
5209
- }
5210
- }),
5211
- catch: (error) => new FinalizerFailure({
5212
- message: error instanceof Error ? error.message : String(error)
5213
- })
5214
- });
5215
- const executionResult = retryResult.executionResult;
5216
- if (!executionResult.success) {
5217
- return yield* Effect7.fail(
5218
- new FinalizerFailure({
5219
- message: `Finalizer agent execution failed: ${executionResult.error}`
5220
- })
5221
- );
5222
- }
5223
- if (retryResult.artifact._tag === "content") {
5224
- const validation = validateAgentArtifact({
5225
- kind: "finalizer",
5226
- artifactPath
5227
- });
5228
- if (validation.ok) {
5229
- try {
5230
- parsed = parsePrDescription(retryResult.artifact.value);
5231
- lastValidationError = void 0;
5232
- break;
5233
- } catch (error) {
5234
- lastValidationError = error instanceof PrDescriptionProtocolError ? new Error(`Finalizer protocol error: ${error.message}`) : error instanceof Error ? error : new Error(String(error));
5235
- }
5236
- } else {
5237
- lastValidationError = new Error(
5238
- `Finalizer protocol error: ${validation.reason}`
5239
- );
5240
- if (protocolAttempt === options.maxAttempts) break;
5241
- }
5242
- } else if (retryResult.artifact._tag === "empty") {
5243
- lastValidationError = new Error(
5244
- `Finalizer agent produced empty output at ${artifactPath}`
5245
- );
5246
- break;
5247
- } else {
5248
- lastValidationError = new Error(
5249
- `Finalizer agent did not produce output at ${artifactPath}`
5250
- );
5251
- break;
5252
- }
5253
- }
5254
- if (!parsed) {
5255
- return yield* Effect7.fail(
5256
- new FinalizerFailure({
5257
- message: lastValidationError?.message ?? "Finalizer validation failed"
5258
- })
5259
- );
5260
- }
5261
- options.logger.step("info", "Finalizer output generated successfully");
5262
- return {
5263
- title: ensureConventionalPrTitle(parsed.title, options.commitSummaries),
5264
- body: parsed.body,
5265
- artifactPath
5266
- };
5257
+ async function assertCanonicalBaseAncestor2(options) {
5258
+ const { worktreePath, baseRef, stageName, logger } = options;
5259
+ await syncRemoteBaseRef2(worktreePath, baseRef, logger);
5260
+ try {
5261
+ await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
5262
+ cwd: worktreePath,
5263
+ logger,
5264
+ label: "git merge-base --is-ancestor"
5265
+ });
5266
+ } catch {
5267
+ throw new Error(
5268
+ `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.`
5269
+ );
5270
+ }
5271
+ }
5272
+ async function syncRemoteBaseRef2(worktreePath, baseRef, logger) {
5273
+ const remoteBase = parseRemoteBaseRef2(baseRef);
5274
+ if (!remoteBase) {
5275
+ return;
5276
+ }
5277
+ await execCapture("git", ["fetch", remoteBase.remote, remoteBase.branch], {
5278
+ cwd: worktreePath,
5279
+ logger,
5280
+ label: "git fetch target"
5267
5281
  });
5268
5282
  }
5269
- function loadFinalizerPromptEffect(repoRoot2, promptTemplate, fs) {
5270
- return Effect7.gen(function* () {
5271
- const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
5272
- const exists = yield* fs.exists(promptPath).pipe(Effect7.orDie);
5273
- if (exists) {
5274
- return yield* fs.readFile(promptPath).pipe(Effect7.orDie);
5283
+ function parseRemoteBaseRef2(baseRef) {
5284
+ const [remote, ...branchParts] = baseRef.split("/");
5285
+ const branch = branchParts.join("/");
5286
+ if (!remote || !branch) {
5287
+ return null;
5288
+ }
5289
+ return { remote, branch };
5290
+ }
5291
+ async function completeIssueFinalReviewChangedPaths(options) {
5292
+ const actualChangedPaths = await listIssueFinalReviewChangedPaths2(options);
5293
+ const declared = new Set(options.declaredChangedPaths.map(normalizeGitPath2));
5294
+ const missing = actualChangedPaths.filter((path9) => !declared.has(path9));
5295
+ if (missing.length === 0) {
5296
+ return actualChangedPaths;
5297
+ }
5298
+ options.logger.step(
5299
+ "warn",
5300
+ `Issue Final Review pass omitted ${missing.length} changed file(s) from changedFiles; using git inventory for finalization state.`
5301
+ );
5302
+ options.logger.kv("MISSING_CHANGED_FILES", missing.join(", "));
5303
+ return actualChangedPaths;
5304
+ }
5305
+ async function listIssueFinalReviewChangedPaths2(options) {
5306
+ const trackedDiff = await execCapture(
5307
+ "git",
5308
+ ["diff-index", "--name-only", options.baseRef, "--"],
5309
+ {
5310
+ cwd: options.worktreePath,
5311
+ logger: options.logger,
5312
+ label: "git diff-index Issue Final Review paths"
5275
5313
  }
5276
- return promptTemplate;
5277
- });
5314
+ );
5315
+ const untrackedFiles = await execCapture(
5316
+ "git",
5317
+ ["ls-files", "--others", "--exclude-standard"],
5318
+ {
5319
+ cwd: options.worktreePath,
5320
+ logger: options.logger,
5321
+ label: "git ls-files Issue Final Review paths"
5322
+ }
5323
+ );
5324
+ return Array.from(
5325
+ /* @__PURE__ */ new Set([
5326
+ ...parseGitPathList2(trackedDiff.stdout),
5327
+ ...parseGitPathList2(untrackedFiles.stdout)
5328
+ ])
5329
+ ).filter(isIssueFinalReviewAuditedPath2);
5278
5330
  }
5279
- function persistGeneratedArtifactEffect(worktreePath, output, fs) {
5280
- return Effect7.gen(function* () {
5281
- const dir = join11(worktreePath, ".pourkit", ".tmp", "finalizer");
5282
- yield* fs.mkdir(dir).pipe(Effect7.catchAll(() => Effect7.void));
5283
- yield* fs.writeFile(join11(dir, "generated.md"), output).pipe(Effect7.catchAll(() => Effect7.void));
5284
- });
5331
+ function parseGitPathList2(output) {
5332
+ return output.split(/\r?\n/).map(normalizeGitPath2).filter((path9) => path9.length > 0);
5333
+ }
5334
+ function normalizeGitPath2(path9) {
5335
+ return path9.replace(/^"|"$/g, "").replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
5336
+ }
5337
+ function isIssueFinalReviewAuditedPath2(path9) {
5338
+ return path9 !== "" && path9 !== WORKTREE_RUN_STATE_PATH && !path9.startsWith(".pourkit/.tmp/") && !path9.startsWith(".pourkit/logs/");
5339
+ }
5340
+ function extractChangedPaths(parsed) {
5341
+ return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
5342
+ (file) => file && typeof file === "object" ? file.path : void 0
5343
+ ).filter((path9) => typeof path9 === "string") : [];
5344
+ }
5345
+ function loadIssueFinalReviewPrompt(options) {
5346
+ const {
5347
+ repoRoot: repoRoot2,
5348
+ promptTemplate,
5349
+ validationCommand,
5350
+ reviewArtifactPath,
5351
+ reviewVerdict
5352
+ } = options;
5353
+ const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
5354
+ const promptBody = existsSync11(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
5355
+ let prompt = appendProtectedWorkGuidance(`${promptBody}
5356
+
5357
+ ## Shared Run Context
5358
+
5359
+ Read the selected issue requirements, planning context (including plan and PRD content when present), comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
5360
+
5361
+ ## Initial Verification Pass
5362
+
5363
+ - First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
5364
+ - Before reviewing code, diffs, artifacts, or prior findings, run the verification wrapper from the Worktree.
5365
+ - Do not substitute direct underlying commands unless no wrapper path can run; if you must use direct commands, explain why in \`verification.summary\`.
5366
+ - If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
5367
+ - Do not return \`pass\` while any configured verification command is failing.
5368
+ - 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.
5369
+ - If no verification commands are configured, note that and proceed with normal review.
5370
+
5371
+ ## Changed Files Audit
5372
+
5373
+ - Read the Branch section of ${RUN_CONTEXT_PATH_IN_WORKTREE} and use its Canonical Base Ref for the changed-file inventory.
5374
+ - Before writing \`changedFiles\`, run \`git diff-index --name-only <canonical-base-ref> --\` from the Worktree to enumerate tracked files changed by the Issue.
5375
+ - Also run \`git ls-files --others --exclude-standard\` from the Worktree to enumerate untracked files changed by the Issue.
5376
+ - Treat the union of those command outputs, excluding only \`.pourkit/.tmp/**\`, \`.pourkit/logs/**\`, and \`.pourkit/state.json\`, as the required \`changedFiles\` inventory.
5377
+ - Include every file from that inventory in \`changedFiles\`, including generated files, managed operating assets, docs, prompts, schema files, and large batches.
5378
+ - If any file from the inventory is out-of-scope, mark it \`out-of-scope\` and either self-retouch it away or return \`needs_human_review\`; do not omit it.
5379
+ - Do not return \`pass\` until \`changedFiles\` covers the full inventory.
5380
+
5381
+ ## Required Artifact Validation
5382
+
5383
+ - Before handoff, run exactly: \`${validationCommand}\`
5384
+ - This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
5385
+ - If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
5386
+ - Do not finish until the validation command passes.`);
5387
+ if (reviewVerdict) {
5388
+ prompt += `
5389
+
5390
+ ## Prior Review Context
5391
+
5392
+ - Reviewer Artifact Path: ${reviewArtifactPath}
5393
+ - Prior Review Verdict: ${reviewVerdict}`;
5394
+ if (reviewVerdict === "PASS_WITH_NOTES") {
5395
+ prompt += `
5396
+ - This Issue passed the prior Reviewer with notes. Read the Reviewer Artifact and inspect its notes before making your final pass decision.`;
5397
+ }
5398
+ }
5399
+ return prompt;
5285
5400
  }
5286
5401
 
5287
5402
  // pr/pr-body.ts
@@ -5352,10 +5467,281 @@ function extractClosingRefs(body) {
5352
5467
  return refs;
5353
5468
  }
5354
5469
 
5470
+ // issues/pr-description-agent.ts
5471
+ import { Effect as Effect7, Layer as Layer3 } from "effect";
5472
+ function bridgeExecutionProvider2(ep) {
5473
+ return Layer3.succeed(
5474
+ ExecutionProvider,
5475
+ ExecutionProvider.of({
5476
+ execute: (opts) => Effect7.tryPromise({
5477
+ try: () => ep.execute(opts),
5478
+ catch: (e) => new Error(
5479
+ e instanceof Error ? e.message : `Execution failed: ${String(e)}`
5480
+ )
5481
+ })
5482
+ })
5483
+ );
5484
+ }
5485
+ function loadFinalizerPromptEffect(repoRoot2, promptTemplate, fs) {
5486
+ return Effect7.gen(function* () {
5487
+ const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
5488
+ const exists = yield* fs.exists(promptPath).pipe(Effect7.orDie);
5489
+ if (exists) {
5490
+ return yield* fs.readFile(promptPath).pipe(Effect7.orDie);
5491
+ }
5492
+ return promptTemplate;
5493
+ });
5494
+ }
5495
+ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
5496
+ return Effect7.gen(function* () {
5497
+ const dir = join12(worktreePath, ".pourkit", ".tmp", "finalizer");
5498
+ yield* fs.mkdir(dir).pipe(Effect7.catchAll(() => Effect7.void));
5499
+ yield* fs.writeFile(join12(dir, "generated.md"), output).pipe(Effect7.catchAll(() => Effect7.void));
5500
+ });
5501
+ }
5502
+ function runPrDescriptionAgent(options) {
5503
+ const { executionProvider } = options;
5504
+ const artifactPathInWorktree = join12(
5505
+ ".pourkit",
5506
+ ".tmp",
5507
+ "finalizer",
5508
+ "agent-output.md"
5509
+ );
5510
+ const artifactPath = join12(options.worktreePath, artifactPathInWorktree);
5511
+ const finalizerFromState = options.worktreeState?.finalizer?.completed ? options.worktreeState.finalizer : null;
5512
+ if (finalizerFromState) {
5513
+ return Effect7.gen(function* () {
5514
+ if (!finalizerFromState.artifactPath) {
5515
+ return yield* Effect7.fail(
5516
+ new FinalizerFailure({
5517
+ message: "Finalizer artifact missing: artifactPath not set in completed finalizer state"
5518
+ })
5519
+ );
5520
+ }
5521
+ const resolvedArtifactPath = isAbsolute4(finalizerFromState.artifactPath) ? finalizerFromState.artifactPath : join12(options.worktreePath, finalizerFromState.artifactPath);
5522
+ if (!existsSync12(resolvedArtifactPath)) {
5523
+ return yield* Effect7.fail(
5524
+ new FinalizerFailure({
5525
+ message: `Finalizer artifact missing: the artifact does not exist at \`${resolvedArtifactPath}\``
5526
+ })
5527
+ );
5528
+ }
5529
+ const validation = validateAgentArtifact({
5530
+ kind: "finalizer",
5531
+ artifactPath: resolvedArtifactPath
5532
+ });
5533
+ if (!validation.ok) {
5534
+ return yield* Effect7.fail(
5535
+ new FinalizerFailure({
5536
+ message: `Finalizer state artifact is invalid: ${validation.reason}`
5537
+ })
5538
+ );
5539
+ }
5540
+ if (!finalizerFromState.title || !finalizerFromState.body) {
5541
+ return yield* Effect7.fail(
5542
+ new FinalizerFailure({
5543
+ message: "Finalizer state is incomplete: missing stored title or body"
5544
+ })
5545
+ );
5546
+ }
5547
+ return {
5548
+ status: "skipped",
5549
+ title: finalizerFromState.title,
5550
+ body: finalizerFromState.body,
5551
+ artifactPath: resolvedArtifactPath
5552
+ };
5553
+ });
5554
+ }
5555
+ const program = Effect7.gen(function* () {
5556
+ const fs = yield* FileSystem;
5557
+ const context = yield* collectFinalizerContextEffect({
5558
+ targetBase: options.targetBaseBranch,
5559
+ branchName: options.builderBranch,
5560
+ worktreePath: options.worktreePath,
5561
+ reviewArtifactPath: options.reviewArtifactPath,
5562
+ logger: options.logger
5563
+ }).pipe(
5564
+ Effect7.catchAll(
5565
+ (error) => Effect7.fail(
5566
+ new FinalizerFailure({
5567
+ message: `Failed to collect finalizer context: ${error.message}`
5568
+ })
5569
+ )
5570
+ )
5571
+ );
5572
+ const strategy = options.target.strategy;
5573
+ const finalizer = strategy.finalize.prDescriptionAgent;
5574
+ const resolvedPrompt = yield* loadFinalizerPromptEffect(
5575
+ options.repoRoot,
5576
+ finalizer.promptTemplate,
5577
+ fs
5578
+ );
5579
+ const prompt = buildFinalizerPrompt(context, resolvedPrompt);
5580
+ const result = yield* runPrDescriptionFinalizerCore({
5581
+ executionProvider: options.executionProvider,
5582
+ config: options.config,
5583
+ target: options.target,
5584
+ finalizer,
5585
+ maxAttempts: strategy.finalize.maxAttempts,
5586
+ prompt,
5587
+ branchName: options.builderBranch,
5588
+ repoRoot: options.repoRoot,
5589
+ worktreePath: options.worktreePath,
5590
+ artifactPathInWorktree,
5591
+ commitSummaries: context.commits,
5592
+ artifacts: [
5593
+ buildRunContextArtifact({
5594
+ issue: options.issue,
5595
+ target: options.target,
5596
+ branchName: options.builderBranch,
5597
+ reviewerCriteria: strategy.review.reviewer.criteria,
5598
+ sections: STAGE_SECTIONS.finalizer
5599
+ })
5600
+ ],
5601
+ logger: options.logger,
5602
+ ...options.memory ? { memory: options.memory } : {}
5603
+ });
5604
+ const output = readFileSync13(artifactPath, "utf-8");
5605
+ yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
5606
+ const body = ensureClosingRefs(result.body, options.issue.number);
5607
+ yield* Effect7.tryPromise({
5608
+ try: () => assertCanonicalBaseAncestor2({
5609
+ worktreePath: options.worktreePath,
5610
+ baseRef: `origin/${options.targetBaseBranch}`,
5611
+ stageName: "Finalizer",
5612
+ logger: options.logger
5613
+ }),
5614
+ catch: (error) => new FinalizerFailure({
5615
+ message: error instanceof Error ? error.message : String(error)
5616
+ })
5617
+ });
5618
+ yield* Effect7.sync(() => {
5619
+ updateWorktreeRunState(options.worktreePath, {
5620
+ finalizer: {
5621
+ completed: true,
5622
+ artifactPath: result.artifactPath,
5623
+ title: result.title,
5624
+ body
5625
+ }
5626
+ });
5627
+ });
5628
+ return {
5629
+ status: "completed",
5630
+ title: result.title,
5631
+ body,
5632
+ artifactPath: result.artifactPath
5633
+ };
5634
+ });
5635
+ const executionLayer = bridgeExecutionProvider2(executionProvider);
5636
+ return program.pipe(
5637
+ Effect7.provide(
5638
+ Layer3.mergeAll(executionLayer, FileSystemDefault, GitOperationsDefault)
5639
+ )
5640
+ );
5641
+ }
5642
+ function runPrDescriptionFinalizerCore(options) {
5643
+ const artifactPath = join12(
5644
+ options.worktreePath,
5645
+ options.artifactPathInWorktree
5646
+ );
5647
+ return Effect7.gen(function* () {
5648
+ let parsed;
5649
+ let lastValidationError;
5650
+ for (let protocolAttempt = 1; protocolAttempt <= options.maxAttempts; protocolAttempt++) {
5651
+ const retryResult = yield* Effect7.tryPromise({
5652
+ try: () => executeWithMissingOrEmptyArtifactRetry({
5653
+ executionProvider: options.executionProvider,
5654
+ missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(
5655
+ options.finalizer
5656
+ ),
5657
+ logger: options.logger,
5658
+ runningMessage: () => `Running finalizer agent (${protocolAttempt}/${options.maxAttempts})`,
5659
+ retryMessage: (attempt, total) => `Retrying finalizer after empty output (${attempt}/${total})`,
5660
+ executionOptions: {
5661
+ stage: "finalizer",
5662
+ agent: options.finalizer.agent,
5663
+ model: options.finalizer.model,
5664
+ variant: options.finalizer.variant,
5665
+ env: options.finalizer.env,
5666
+ prompt: options.prompt,
5667
+ target: options.target,
5668
+ repoRoot: options.repoRoot,
5669
+ branchName: options.branchName,
5670
+ sandbox: options.config.sandbox,
5671
+ autoApprove: true,
5672
+ artifactPath: options.artifactPathInWorktree,
5673
+ worktreePath: options.worktreePath,
5674
+ artifacts: options.artifacts,
5675
+ baseRef: options.baseRef,
5676
+ checkoutBase: options.checkoutBase,
5677
+ reviewBase: options.reviewBase,
5678
+ ...options.memory ? { memory: options.memory } : {},
5679
+ logger: options.logger
5680
+ }
5681
+ }),
5682
+ catch: (error) => new FinalizerFailure({
5683
+ message: error instanceof Error ? error.message : String(error)
5684
+ })
5685
+ });
5686
+ const executionResult = retryResult.executionResult;
5687
+ if (!executionResult.success) {
5688
+ return yield* Effect7.fail(
5689
+ new FinalizerFailure({
5690
+ message: `Finalizer agent execution failed: ${executionResult.error}`
5691
+ })
5692
+ );
5693
+ }
5694
+ if (retryResult.artifact._tag === "content") {
5695
+ const validation = validateAgentArtifact({
5696
+ kind: "finalizer",
5697
+ artifactPath
5698
+ });
5699
+ if (validation.ok) {
5700
+ try {
5701
+ parsed = parsePrDescription(retryResult.artifact.value);
5702
+ lastValidationError = void 0;
5703
+ break;
5704
+ } catch (error) {
5705
+ lastValidationError = error instanceof PrDescriptionProtocolError ? new Error(`Finalizer protocol error: ${error.message}`) : error instanceof Error ? error : new Error(String(error));
5706
+ }
5707
+ } else {
5708
+ lastValidationError = new Error(
5709
+ `Finalizer protocol error: ${validation.reason}`
5710
+ );
5711
+ if (protocolAttempt === options.maxAttempts) break;
5712
+ }
5713
+ } else if (retryResult.artifact._tag === "empty") {
5714
+ lastValidationError = new Error(
5715
+ `Finalizer agent produced empty output at ${artifactPath}`
5716
+ );
5717
+ break;
5718
+ } else {
5719
+ lastValidationError = new Error(
5720
+ `Finalizer agent did not produce output at ${artifactPath}`
5721
+ );
5722
+ break;
5723
+ }
5724
+ }
5725
+ if (!parsed) {
5726
+ return yield* Effect7.fail(
5727
+ new FinalizerFailure({
5728
+ message: lastValidationError?.message ?? "Finalizer validation failed"
5729
+ })
5730
+ );
5731
+ }
5732
+ options.logger.step("info", "Finalizer output generated successfully");
5733
+ return {
5734
+ title: ensureConventionalPrTitle(parsed.title, options.commitSummaries),
5735
+ body: parsed.body,
5736
+ artifactPath
5737
+ };
5738
+ });
5739
+ }
5740
+
5355
5741
  // issues/github-issue-publication.ts
5356
5742
  init_common();
5357
- import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
5358
- import { isAbsolute as isAbsolute3, join as join12 } from "path";
5743
+ import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
5744
+ import { isAbsolute as isAbsolute5, join as join13 } from "path";
5359
5745
 
5360
5746
  // issues/issue-transitions.ts
5361
5747
  function createIssueTransitions(deps, labels) {
@@ -5830,11 +6216,11 @@ function readIssueFinalReviewArtifact(worktreeState, worktreePath) {
5830
6216
  if (!configuredPath) {
5831
6217
  return null;
5832
6218
  }
5833
- const artifactPath = isAbsolute3(configuredPath) ? configuredPath : join12(worktreePath, configuredPath);
5834
- if (!existsSync11(artifactPath)) return null;
6219
+ const artifactPath = isAbsolute5(configuredPath) ? configuredPath : join13(worktreePath, configuredPath);
6220
+ if (!existsSync13(artifactPath)) return null;
5835
6221
  let parsed;
5836
6222
  try {
5837
- parsed = JSON.parse(readFileSync13(artifactPath, "utf-8"));
6223
+ parsed = JSON.parse(readFileSync14(artifactPath, "utf-8"));
5838
6224
  } catch {
5839
6225
  return null;
5840
6226
  }
@@ -6034,243 +6420,61 @@ function assertNoSecretLikeContent(inputs) {
6034
6420
  }
6035
6421
  const summary = findings.slice(0, 5).map((finding) => `${finding.source}: ${finding.kind}`).join("; ");
6036
6422
  throw new Error(
6037
- `Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
6038
- );
6039
- }
6040
- function findSecretLikeContent(source, content) {
6041
- const findings = [];
6042
- const patterns = [
6043
- {
6044
- kind: "private key block",
6045
- regex: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/gi
6046
- },
6047
- {
6048
- kind: "GitHub token",
6049
- regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g
6050
- },
6051
- {
6052
- kind: "GitHub fine-grained token",
6053
- regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g
6054
- },
6055
- { kind: "npm token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g },
6056
- { kind: "OpenAI key", regex: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g },
6057
- { kind: "AWS access key", regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
6058
- {
6059
- kind: "authorization bearer token",
6060
- regex: /\bAuthorization\s*:\s*Bearer\s+[^\s`'\"]{12,}/gi
6061
- },
6062
- {
6063
- kind: "secret assignment",
6064
- regex: /\b[A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CLIENT[_-]?SECRET|DATABASE[_-]?URL)[A-Z0-9_]*\s*[:=]\s*["']?[^\s"'`]{8,}/gi
6065
- }
6066
- ];
6067
- for (const { kind, regex } of patterns) {
6068
- for (const match of content.matchAll(regex)) {
6069
- if (isAllowedSecretPlaceholder(match[0]) || isAllowedDocumentationTokenReference(match[0])) {
6070
- continue;
6071
- }
6072
- findings.push({ source, kind });
6073
- break;
6074
- }
6075
- }
6076
- return findings;
6077
- }
6078
- function isAllowedSecretPlaceholder(value) {
6079
- const normalized = value.toLowerCase();
6080
- return [
6081
- "<redacted>",
6082
- "<example",
6083
- "example-",
6084
- "dummy-",
6085
- "test-",
6086
- "placeholder",
6087
- "xxxxx"
6088
- ].some((allowed) => normalized.includes(allowed));
6089
- }
6090
- function isAllowedDocumentationTokenReference(value) {
6091
- return /^token\s*:\s*["']?<verdict>/i.test(value);
6092
- }
6093
-
6094
- // commands/issue-final-review-agent.ts
6095
- import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
6096
- import { join as join13 } from "path";
6097
- var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join13(
6098
- ".pourkit",
6099
- ".tmp",
6100
- "issue-final-review",
6101
- "agent-output.json"
6102
- );
6103
- async function runIssueFinalReviewAgent(options) {
6104
- const {
6105
- executionProvider,
6106
- target,
6107
- issue,
6108
- parentPrdIssue,
6109
- builderBranch,
6110
- worktreePath,
6111
- repoRoot: repoRoot2,
6112
- logger
6113
- } = options;
6114
- const artifactPathInWorktree = ISSUE_FINAL_REVIEW_ARTIFACT_PATH;
6115
- const artifactPath = join13(worktreePath, artifactPathInWorktree);
6116
- const strategy = target.strategy;
6117
- const issueFinalReview = strategy.issueFinalReview;
6118
- const agent = issueFinalReview;
6119
- const prompt = loadIssueFinalReviewPrompt({
6120
- repoRoot: repoRoot2,
6121
- promptTemplate: agent.promptTemplate,
6122
- validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}`,
6123
- reviewArtifactPath: options.reviewArtifactPath,
6124
- reviewVerdict: options.reviewVerdict
6125
- });
6126
- const entry = await executeWithMissingOrEmptyArtifactRetry({
6127
- executionProvider,
6128
- missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(agent),
6129
- logger,
6130
- runningMessage: () => "Running Issue Final Review agent",
6131
- retryMessage: (attempt, total) => `Retrying Issue Final Review after empty output (${attempt}/${total})`,
6132
- executionOptions: {
6133
- stage: "issueFinalReview",
6134
- agent: agent.agent,
6135
- model: agent.model,
6136
- variant: agent.variant,
6137
- env: agent.env,
6138
- prompt,
6139
- target,
6140
- repoRoot: repoRoot2,
6141
- branchName: builderBranch,
6142
- sandbox: options.config.sandbox,
6143
- autoApprove: true,
6144
- artifactPath: artifactPathInWorktree,
6145
- worktreePath,
6146
- artifacts: [
6147
- buildRunContextArtifact({
6148
- issue,
6149
- parentPrdIssue,
6150
- target,
6151
- branchName: builderBranch,
6152
- repoRoot: repoRoot2,
6153
- reviewerCriteria: strategy.review.reviewer.criteria,
6154
- sections: STAGE_SECTIONS.issueFinalReview
6155
- })
6156
- ],
6157
- ...options.memory ? { memory: options.memory } : {},
6158
- logger
6159
- }
6160
- });
6161
- const executionResult = entry.executionResult;
6162
- if (!executionResult.success) {
6163
- throw new Error(
6164
- `Issue Final Review agent execution failed: ${executionResult.error}`
6165
- );
6166
- }
6167
- let content;
6168
- if (entry.artifact._tag === "content") {
6169
- content = entry.artifact.value;
6170
- } else if (entry.artifact._tag === "empty") {
6171
- throw new Error(
6172
- `Issue Final Review agent produced empty output at ${artifactPath}`
6173
- );
6174
- } else {
6175
- throw new Error(
6176
- `Issue Final Review agent did not produce output at ${artifactPath}`
6177
- );
6178
- }
6179
- const validation = validateAgentArtifact({
6180
- kind: "issue-final-review",
6181
- artifactPath,
6182
- issueNumber: issue.number,
6183
- branchName: builderBranch
6184
- });
6185
- if (!validation.ok) {
6186
- throw new Error(
6187
- `Issue Final Review artifact validation failed: ${validation.reason}`
6188
- );
6189
- }
6190
- const parsed = JSON.parse(content);
6191
- const verdict = parsed.verdict;
6192
- if (verdict === "pass") {
6193
- return {
6194
- verdict: "pass",
6195
- artifactPath,
6196
- selfRetouched: parsed.selfRetouched === true,
6197
- changedPaths: extractChangedPaths(parsed),
6198
- verificationPassed: parsed.verification?.passed === true
6199
- };
6200
- }
6201
- if (verdict === "needs_human_review") {
6202
- return {
6203
- verdict: "needs_human_review",
6204
- artifactPath,
6205
- needsHumanReason: parsed.needsHumanReason,
6206
- selfRetouched: parsed.selfRetouched === true,
6207
- changedPaths: extractChangedPaths(parsed)
6208
- };
6209
- }
6210
- throw new Error(
6211
- `Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
6212
- );
6213
- }
6214
- function extractChangedPaths(parsed) {
6215
- return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
6216
- (file) => file && typeof file === "object" ? file.path : void 0
6217
- ).filter((path9) => typeof path9 === "string") : [];
6218
- }
6219
- function loadIssueFinalReviewPrompt(options) {
6220
- const {
6221
- repoRoot: repoRoot2,
6222
- promptTemplate,
6223
- validationCommand,
6224
- reviewArtifactPath,
6225
- reviewVerdict
6226
- } = options;
6227
- const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
6228
- const promptBody = existsSync12(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
6229
- let prompt = appendProtectedWorkGuidance(`${promptBody}
6230
-
6231
- ## Shared Run Context
6232
-
6233
- Read the selected issue requirements, planning context (including plan and PRD content when present), comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
6234
-
6235
- ## Initial Verification Pass
6236
-
6237
- - First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
6238
- - Before reviewing code, diffs, artifacts, or prior findings, run the verification wrapper from the Worktree.
6239
- - Do not substitute direct underlying commands unless no wrapper path can run; if you must use direct commands, explain why in \`verification.summary\`.
6240
- - If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
6241
- - Do not return \`pass\` while any configured verification command is failing.
6242
- - 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.
6243
- - If no verification commands are configured, note that and proceed with normal review.
6244
-
6245
- ## Changed Files Audit
6246
-
6247
- - Read the Branch section of ${RUN_CONTEXT_PATH_IN_WORKTREE} and use its Canonical Base Ref for the changed-file inventory.
6248
- - Before writing \`changedFiles\`, run \`git diff-index --name-only <canonical-base-ref> --\` from the Worktree to enumerate tracked files changed by the Issue.
6249
- - Also run \`git ls-files --others --exclude-standard\` from the Worktree to enumerate untracked files changed by the Issue.
6250
- - Treat the union of those command outputs, excluding only \`.pourkit/.tmp/**\`, \`.pourkit/logs/**\`, and \`.pourkit/state.json\`, as the required \`changedFiles\` inventory.
6251
- - Include every file from that inventory in \`changedFiles\`, including generated files, managed operating assets, docs, prompts, schema files, and large batches.
6252
- - If any file from the inventory is out-of-scope, mark it \`out-of-scope\` and either self-retouch it away or return \`needs_human_review\`; do not omit it.
6253
- - Do not return \`pass\` until \`changedFiles\` covers the full inventory.
6254
-
6255
- ## Required Artifact Validation
6256
-
6257
- - Before handoff, run exactly: \`${validationCommand}\`
6258
- - This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
6259
- - If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
6260
- - Do not finish until the validation command passes.`);
6261
- if (reviewVerdict) {
6262
- prompt += `
6263
-
6264
- ## Prior Review Context
6265
-
6266
- - Reviewer Artifact Path: ${reviewArtifactPath}
6267
- - Prior Review Verdict: ${reviewVerdict}`;
6268
- if (reviewVerdict === "PASS_WITH_NOTES") {
6269
- prompt += `
6270
- - This Issue passed the prior Reviewer with notes. Read the Reviewer Artifact and inspect its notes before making your final pass decision.`;
6423
+ `Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
6424
+ );
6425
+ }
6426
+ function findSecretLikeContent(source, content) {
6427
+ const findings = [];
6428
+ const patterns = [
6429
+ {
6430
+ kind: "private key block",
6431
+ regex: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/gi
6432
+ },
6433
+ {
6434
+ kind: "GitHub token",
6435
+ regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g
6436
+ },
6437
+ {
6438
+ kind: "GitHub fine-grained token",
6439
+ regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g
6440
+ },
6441
+ { kind: "npm token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g },
6442
+ { kind: "OpenAI key", regex: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g },
6443
+ { kind: "AWS access key", regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
6444
+ {
6445
+ kind: "authorization bearer token",
6446
+ regex: /\bAuthorization\s*:\s*Bearer\s+[^\s`'\"]{12,}/gi
6447
+ },
6448
+ {
6449
+ kind: "secret assignment",
6450
+ regex: /\b[A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CLIENT[_-]?SECRET|DATABASE[_-]?URL)[A-Z0-9_]*\s*[:=]\s*["']?[^\s"'`]{8,}/gi
6451
+ }
6452
+ ];
6453
+ for (const { kind, regex } of patterns) {
6454
+ for (const match of content.matchAll(regex)) {
6455
+ if (isAllowedSecretPlaceholder(match[0]) || isAllowedDocumentationTokenReference(match[0])) {
6456
+ continue;
6457
+ }
6458
+ findings.push({ source, kind });
6459
+ break;
6271
6460
  }
6272
6461
  }
6273
- return prompt;
6462
+ return findings;
6463
+ }
6464
+ function isAllowedSecretPlaceholder(value) {
6465
+ const normalized = value.toLowerCase();
6466
+ return [
6467
+ "<redacted>",
6468
+ "<example",
6469
+ "example-",
6470
+ "dummy-",
6471
+ "test-",
6472
+ "placeholder",
6473
+ "xxxxx"
6474
+ ].some((allowed) => normalized.includes(allowed));
6475
+ }
6476
+ function isAllowedDocumentationTokenReference(value) {
6477
+ return /^token\s*:\s*["']?<verdict>/i.test(value);
6274
6478
  }
6275
6479
 
6276
6480
  // issues/issue-worktree-resolution.ts
@@ -6449,90 +6653,7 @@ function assertIssueFinalReviewPassed(worktreeState) {
6449
6653
  }
6450
6654
  }
6451
6655
  async function advanceIssueFinalReview(options) {
6452
- const {
6453
- executionProvider,
6454
- config,
6455
- target,
6456
- issue,
6457
- parentPrdIssue,
6458
- builderBranch,
6459
- worktreePath,
6460
- repoRoot: repoRoot2,
6461
- logger,
6462
- reviewArtifactPath,
6463
- reviewVerdict,
6464
- worktreeState
6465
- } = options;
6466
- const ifrFromState = worktreeState?.issueFinalReview;
6467
- if (ifrFromState?.completed && ifrFromState.verdict === "pass") {
6468
- if (!ifrFromState.artifactPath) {
6469
- throw new Error(
6470
- "Issue Final Review state is incomplete: missing artifactPath"
6471
- );
6472
- }
6473
- const artifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join15(worktreePath, ifrFromState.artifactPath);
6474
- const validation = validateAgentArtifact({
6475
- kind: "issue-final-review",
6476
- artifactPath,
6477
- issueNumber: issue.number,
6478
- branchName: builderBranch
6479
- });
6480
- if (!validation.ok) {
6481
- throw new Error(
6482
- `Issue Final Review state artifact is invalid: ${validation.reason}`
6483
- );
6484
- }
6485
- return {
6486
- verdict: "pass",
6487
- artifactPath,
6488
- selfRetouched: ifrFromState.selfRetouched ?? false,
6489
- changedPaths: ifrFromState.changedPaths ?? [],
6490
- verificationPassed: ifrFromState.verificationPassed ?? false
6491
- };
6492
- }
6493
- const result = await runIssueFinalReviewAgent({
6494
- executionProvider,
6495
- config,
6496
- target,
6497
- issue,
6498
- parentPrdIssue,
6499
- builderBranch,
6500
- worktreePath,
6501
- repoRoot: repoRoot2,
6502
- logger,
6503
- reviewArtifactPath,
6504
- reviewVerdict,
6505
- ...options.memory ? { memory: options.memory } : {}
6506
- });
6507
- await assertCanonicalBaseAncestor2({
6508
- worktreePath,
6509
- baseRef: `origin/${target.baseBranch}`,
6510
- stageName: "Issue Final Review",
6511
- logger
6512
- });
6513
- if (result.verdict === "pass") {
6514
- const changedPaths = await completeIssueFinalReviewChangedPaths({
6515
- worktreePath,
6516
- baseRef: `origin/${target.baseBranch}`,
6517
- declaredChangedPaths: result.changedPaths,
6518
- logger
6519
- });
6520
- updateWorktreeRunState(worktreePath, {
6521
- issueFinalReview: {
6522
- completed: true,
6523
- verdict: "pass",
6524
- artifactPath: result.artifactPath,
6525
- selfRetouched: result.selfRetouched,
6526
- changedPaths,
6527
- verificationPassed: result.verificationPassed
6528
- }
6529
- });
6530
- return {
6531
- ...result,
6532
- changedPaths
6533
- };
6534
- }
6535
- return result;
6656
+ return runIssueFinalReview(options);
6536
6657
  }
6537
6658
  async function startIssueRun(options) {
6538
6659
  const {
@@ -6734,9 +6855,11 @@ async function completeIssueRun(options) {
6734
6855
  checksCompletionTimeoutMs: config.checks.checksCompletionTimeoutSeconds * 1e3,
6735
6856
  pollIntervalMs: config.checks.pollIntervalSeconds * 1e3
6736
6857
  };
6737
- const ifrState = executionResult.worktreePath ? readWorktreeRunState(executionResult.worktreePath) ?? worktreeState : worktreeState;
6858
+ const readCurrentWorktreeState = () => executionResult.worktreePath ? readWorktreeRunState(executionResult.worktreePath) ?? worktreeState : worktreeState;
6859
+ const ifrState = readCurrentWorktreeState();
6738
6860
  assertIssueFinalReviewPassed(ifrState);
6739
- if (executionResult.worktreePath && !worktreeState?.finalCommit?.completed && !worktreeState?.pr?.created && !await hasWorktreeChanges(
6861
+ const stateBeforeCompletion = readCurrentWorktreeState();
6862
+ if (executionResult.worktreePath && !stateBeforeCompletion?.finalCommit?.completed && !stateBeforeCompletion?.pr?.created && !await hasWorktreeChanges(
6740
6863
  executionResult.worktreePath,
6741
6864
  `origin/${effectiveBaseBranch}`,
6742
6865
  logger
@@ -6753,92 +6876,33 @@ async function completeIssueRun(options) {
6753
6876
  noOp: true
6754
6877
  };
6755
6878
  }
6756
- let prTitle = issue.title;
6757
- let prBody;
6758
- let finalizerResult;
6759
- const finalizerFromState = worktreeState?.finalizer?.completed ? worktreeState.finalizer : null;
6760
- if (finalizerFromState) {
6761
- try {
6762
- if (finalizerFromState.title && finalizerFromState.body) {
6763
- prTitle = finalizerFromState.title;
6764
- prBody = finalizerFromState.body;
6765
- } else if (finalizerFromState.artifactPath) {
6766
- if (!existsSync13(finalizerFromState.artifactPath)) {
6767
- throw new FinalizerFailure({
6768
- message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
6769
- });
6770
- }
6771
- const artifactContent = readFileSync15(
6772
- finalizerFromState.artifactPath,
6773
- "utf-8"
6774
- );
6775
- const parsed = parsePrDescription(artifactContent);
6776
- prTitle = parsed.title;
6777
- prBody = parsed.body;
6778
- } else {
6779
- throw new FinalizerFailure({
6780
- message: "Finalizer state is incomplete: missing title, body, and artifactPath"
6781
- });
6782
- }
6783
- } catch (error) {
6784
- throw error;
6785
- }
6786
- } else {
6787
- try {
6788
- finalizerResult = await runEffectAndMapExit(
6789
- runFinalizerAgent({
6790
- executionProvider,
6791
- config,
6792
- target,
6793
- issue,
6794
- builderBranch: branchName,
6795
- targetBaseBranch: effectiveBaseBranch,
6796
- worktreePath: executionResult.worktreePath,
6797
- reviewArtifactPath,
6798
- repoRoot: ROOT,
6799
- logger,
6800
- ...startResult.memory ? { memory: startResult.memory } : {}
6801
- })
6802
- );
6803
- } catch (error) {
6804
- throw error;
6805
- }
6806
- prTitle = finalizerResult.title;
6807
- prBody = finalizerResult.body;
6808
- if (executionResult.worktreePath) {
6809
- await assertCanonicalBaseAncestor2({
6810
- worktreePath: executionResult.worktreePath,
6811
- baseRef: `origin/${effectiveBaseBranch}`,
6812
- stageName: "Finalizer",
6813
- logger
6814
- });
6815
- }
6816
- }
6817
- prTitle = ensureConventionalPrTitle(
6818
- prTitle,
6819
- executionResult.commits.join("\n")
6820
- );
6821
- const finalBody = ensureClosingRefs(
6822
- prBody ?? `Closes #${issue.number}`,
6823
- issue.number
6879
+ const currentWorktreeState = readCurrentWorktreeState();
6880
+ const prDescriptionAgentResult = await runEffectAndMapExit(
6881
+ runPrDescriptionAgent({
6882
+ executionProvider,
6883
+ config,
6884
+ target,
6885
+ issue,
6886
+ builderBranch: branchName,
6887
+ targetBaseBranch: effectiveBaseBranch,
6888
+ worktreePath: executionResult.worktreePath,
6889
+ reviewArtifactPath,
6890
+ repoRoot: ROOT,
6891
+ logger,
6892
+ worktreeState: currentWorktreeState,
6893
+ ...startResult.memory ? { memory: startResult.memory } : {}
6894
+ })
6824
6895
  );
6825
- if (!finalizerFromState && executionResult.worktreePath) {
6826
- updateWorktreeRunState(executionResult.worktreePath, {
6827
- finalizer: {
6828
- completed: true,
6829
- artifactPath: finalizerResult.artifactPath,
6830
- title: prTitle,
6831
- body: prBody
6832
- }
6833
- });
6834
- }
6835
- const finalCommitFromState = worktreeState?.finalCommit?.completed;
6896
+ const prTitle = prDescriptionAgentResult.title;
6897
+ const prBody = prDescriptionAgentResult.body;
6898
+ const stateBeforeFinalCommit = readCurrentWorktreeState();
6899
+ const finalCommitFromState = stateBeforeFinalCommit?.finalCommit?.completed;
6836
6900
  if (!finalCommitFromState) {
6837
6901
  await finalizeWorktreeCommit({
6838
6902
  worktreePath: executionResult.worktreePath,
6839
6903
  baseRef: `origin/${effectiveBaseBranch}`,
6840
6904
  title: prTitle,
6841
- body: finalBody,
6905
+ body: prBody,
6842
6906
  logger
6843
6907
  });
6844
6908
  if (executionResult.worktreePath) {
@@ -6866,9 +6930,9 @@ async function completeIssueRun(options) {
6866
6930
  baseBranch: effectiveBaseBranch,
6867
6931
  autoMerge: target.autoMerge !== false,
6868
6932
  worktreePath: executionResult.worktreePath,
6869
- worktreeState,
6933
+ worktreeState: readCurrentWorktreeState(),
6870
6934
  prTitle,
6871
- prBody: finalBody,
6935
+ prBody,
6872
6936
  issueProvider,
6873
6937
  prProvider,
6874
6938
  logger,
@@ -6968,89 +7032,6 @@ async function finalizeWorktreeCommit(options) {
6968
7032
  label: "git commit"
6969
7033
  });
6970
7034
  }
6971
- async function assertCanonicalBaseAncestor2(options) {
6972
- const { worktreePath, baseRef, stageName, logger } = options;
6973
- await syncRemoteBaseRef2(worktreePath, baseRef, logger);
6974
- try {
6975
- await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
6976
- cwd: worktreePath,
6977
- logger,
6978
- label: "git merge-base --is-ancestor"
6979
- });
6980
- } catch {
6981
- throw new Error(
6982
- `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.`
6983
- );
6984
- }
6985
- }
6986
- async function completeIssueFinalReviewChangedPaths(options) {
6987
- const actualChangedPaths = await listIssueFinalReviewChangedPaths2(options);
6988
- const declared = new Set(options.declaredChangedPaths.map(normalizeGitPath2));
6989
- const missing = actualChangedPaths.filter((path9) => !declared.has(path9));
6990
- if (missing.length === 0) {
6991
- return actualChangedPaths;
6992
- }
6993
- options.logger.step(
6994
- "warn",
6995
- `Issue Final Review pass omitted ${missing.length} changed file(s) from changedFiles; using git inventory for finalization state.`
6996
- );
6997
- options.logger.kv("MISSING_CHANGED_FILES", missing.join(", "));
6998
- return actualChangedPaths;
6999
- }
7000
- async function listIssueFinalReviewChangedPaths2(options) {
7001
- const trackedDiff = await execCapture(
7002
- "git",
7003
- ["diff-index", "--name-only", options.baseRef, "--"],
7004
- {
7005
- cwd: options.worktreePath,
7006
- logger: options.logger,
7007
- label: "git diff-index Issue Final Review paths"
7008
- }
7009
- );
7010
- const untrackedFiles = await execCapture(
7011
- "git",
7012
- ["ls-files", "--others", "--exclude-standard"],
7013
- {
7014
- cwd: options.worktreePath,
7015
- logger: options.logger,
7016
- label: "git ls-files Issue Final Review paths"
7017
- }
7018
- );
7019
- return Array.from(
7020
- /* @__PURE__ */ new Set([
7021
- ...parseGitPathList2(trackedDiff.stdout),
7022
- ...parseGitPathList2(untrackedFiles.stdout)
7023
- ])
7024
- ).filter(isIssueFinalReviewAuditedPath2);
7025
- }
7026
- function parseGitPathList2(output) {
7027
- return output.split(/\r?\n/).map(normalizeGitPath2).filter((path9) => path9.length > 0);
7028
- }
7029
- function normalizeGitPath2(path9) {
7030
- return path9.replace(/^"|"$/g, "").replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
7031
- }
7032
- function isIssueFinalReviewAuditedPath2(path9) {
7033
- return path9 !== "" && path9 !== WORKTREE_RUN_STATE_PATH && !path9.startsWith(".pourkit/.tmp/") && !path9.startsWith(".pourkit/logs/");
7034
- }
7035
- async function syncRemoteBaseRef2(worktreePath, baseRef, logger) {
7036
- const remoteBase = parseRemoteBaseRef2(baseRef);
7037
- if (!remoteBase) {
7038
- return;
7039
- }
7040
- await execCapture("git", ["fetch", remoteBase.remote, remoteBase.branch], {
7041
- cwd: worktreePath,
7042
- logger,
7043
- label: "git fetch target"
7044
- });
7045
- }
7046
- function parseRemoteBaseRef2(baseRef) {
7047
- const [remote, ...branchParts] = baseRef.split("/");
7048
- const branch = branchParts.join("/");
7049
- if (!remote || !branch) {
7050
- return null;
7051
- }
7052
- return { remote, branch };
7053
- }
7054
7035
  function makeIssueTransitions2(provider, config) {
7055
7036
  return createIssueTransitions(
7056
7037
  {
@@ -7199,7 +7180,7 @@ async function runIssueLifecycle(options) {
7199
7180
  worktreeState: startResult.worktreeState,
7200
7181
  memory: startResult.memory
7201
7182
  });
7202
- if (finalReviewResult.verdict === "needs_human_review") {
7183
+ if (finalReviewResult.status === "needs_human_review") {
7203
7184
  await options.deps.transitionIssueFinalReviewNeedsHumanToHandoff({
7204
7185
  issueNumber: options.issueNumber,
7205
7186
  finalReviewResult
@@ -7393,17 +7374,17 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
7393
7374
 
7394
7375
  // commands/prd-run.ts
7395
7376
  import { lstatSync, realpathSync } from "fs";
7396
- import { join as join17 } from "path";
7377
+ import { join as join16 } from "path";
7397
7378
 
7398
7379
  // prd-run/state.ts
7399
7380
  import {
7400
7381
  existsSync as existsSync14,
7401
7382
  mkdirSync as mkdirSync7,
7402
- readFileSync as readFileSync16,
7383
+ readFileSync as readFileSync15,
7403
7384
  readdirSync as readdirSync3,
7404
7385
  writeFileSync as writeFileSync4
7405
7386
  } from "fs";
7406
- import { join as join16 } from "path";
7387
+ import { join as join15 } from "path";
7407
7388
  import { z } from "zod";
7408
7389
  var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
7409
7390
  var PrdRunRecordSchema = z.object({
@@ -7492,12 +7473,12 @@ function readPrdRun(repoRoot2, prdRef) {
7492
7473
  return { record: null, diagnostics: [] };
7493
7474
  }
7494
7475
  try {
7495
- const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
7476
+ const raw = JSON.parse(readFileSync15(recordPath, "utf-8"));
7496
7477
  const parsed = PrdRunRecordSchema.parse(raw);
7497
7478
  return { record: parsed, diagnostics: [] };
7498
7479
  } catch (error) {
7499
7480
  try {
7500
- const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
7481
+ const raw = JSON.parse(readFileSync15(recordPath, "utf-8"));
7501
7482
  if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
7502
7483
  return {
7503
7484
  record: raw,
@@ -7518,7 +7499,7 @@ function readPrdRun(repoRoot2, prdRef) {
7518
7499
  }
7519
7500
  }
7520
7501
  function listPrdRuns(repoRoot2) {
7521
- const stateDir = join16(repoRoot2, PRD_RUN_STATE_DIR);
7502
+ const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
7522
7503
  if (!existsSync14(stateDir)) {
7523
7504
  return { records: [], diagnostics: [] };
7524
7505
  }
@@ -7528,10 +7509,10 @@ function listPrdRuns(repoRoot2) {
7528
7509
  (left, right) => left.name.localeCompare(right.name)
7529
7510
  )) {
7530
7511
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
7531
- const recordPath = join16(stateDir, entry.name);
7512
+ const recordPath = join15(stateDir, entry.name);
7532
7513
  try {
7533
7514
  const record = PrdRunRecordSchema.parse(
7534
- JSON.parse(readFileSync16(recordPath, "utf-8"))
7515
+ JSON.parse(readFileSync15(recordPath, "utf-8"))
7535
7516
  );
7536
7517
  records.push(record);
7537
7518
  } catch (error) {
@@ -7544,7 +7525,7 @@ function listPrdRuns(repoRoot2) {
7544
7525
  }
7545
7526
  function writePrdRunRecord(repoRoot2, record) {
7546
7527
  const normalized = normalizePrdRunRef(record.prdRef);
7547
- const stateDir = join16(repoRoot2, PRD_RUN_STATE_DIR);
7528
+ const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
7548
7529
  const recordPath = getRecordPath(repoRoot2, normalized);
7549
7530
  mkdirSync7(stateDir, { recursive: true });
7550
7531
  writeFileSync4(
@@ -7554,7 +7535,7 @@ function writePrdRunRecord(repoRoot2, record) {
7554
7535
  );
7555
7536
  }
7556
7537
  function getRecordPath(repoRoot2, prdRef) {
7557
- return join16(
7538
+ return join15(
7558
7539
  repoRoot2,
7559
7540
  PRD_RUN_STATE_DIR,
7560
7541
  `${normalizePrdRunRef(prdRef)}.json`
@@ -9314,7 +9295,7 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
9314
9295
  }
9315
9296
 
9316
9297
  // prd-run/final-review-validation.ts
9317
- import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
9298
+ import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
9318
9299
 
9319
9300
  // commands/prd-run.ts
9320
9301
  async function runPrdRunLaunchCommand(options) {
@@ -12324,18 +12305,18 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
12324
12305
  }
12325
12306
 
12326
12307
  // commands/memory-init.ts
12327
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync5 } from "fs";
12328
- import { join as join18 } from "path";
12308
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
12309
+ import { join as join17 } from "path";
12329
12310
  async function runMemoryInitCommand(options) {
12330
12311
  const cwd = options.cwd ?? process.cwd();
12331
- const configPath = join18(cwd, ".pourkit", "config.json");
12312
+ const configPath = join17(cwd, ".pourkit", "config.json");
12332
12313
  if (!existsSync17(configPath)) {
12333
12314
  return {
12334
12315
  ok: false,
12335
12316
  message: "No .pourkit/config.json found. Run `pourkit init` first to initialize Pourkit, then run `pourkit memory init`."
12336
12317
  };
12337
12318
  }
12338
- const raw = JSON.parse(readFileSync18(configPath, "utf8"));
12319
+ const raw = JSON.parse(readFileSync17(configPath, "utf8"));
12339
12320
  let alreadyEnabled = false;
12340
12321
  if (Object.prototype.hasOwnProperty.call(raw, "memory")) {
12341
12322
  const m = raw.memory;
@@ -12360,8 +12341,8 @@ async function runMemoryInitCommand(options) {
12360
12341
  const next = { ...raw, memory };
12361
12342
  writeFileSync5(configPath, JSON.stringify(next, null, 2) + "\n");
12362
12343
  }
12363
- mkdirSync8(join18(cwd, ".pourkit", "icm"), { recursive: true });
12364
- const gitignorePath = join18(cwd, ".gitignore");
12344
+ mkdirSync8(join17(cwd, ".pourkit", "icm"), { recursive: true });
12345
+ const gitignorePath = join17(cwd, ".gitignore");
12365
12346
  updateManagedBlockSync(gitignorePath, generateGitignoreBlock());
12366
12347
  const managed = await generateManagedAgentInstructions({
12367
12348
  sourceRoot: cwd,
@@ -12369,14 +12350,14 @@ async function runMemoryInitCommand(options) {
12369
12350
  });
12370
12351
  let updatedAgentFile = false;
12371
12352
  for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
12372
- const agentPath = join18(cwd, fileName);
12353
+ const agentPath = join17(cwd, fileName);
12373
12354
  if (existsSync17(agentPath)) {
12374
12355
  updateManagedBlockSync(agentPath, managed);
12375
12356
  updatedAgentFile = true;
12376
12357
  }
12377
12358
  }
12378
12359
  if (!updatedAgentFile) {
12379
- updateManagedBlockSync(join18(cwd, "AGENTS.md"), managed);
12360
+ updateManagedBlockSync(join17(cwd, "AGENTS.md"), managed);
12380
12361
  }
12381
12362
  return {
12382
12363
  ok: true,
@@ -12392,7 +12373,7 @@ ${content}${MANAGED_BLOCK_END}
12392
12373
  writeFileSync5(filePath, blockContent);
12393
12374
  return;
12394
12375
  }
12395
- const existing = readFileSync18(filePath, "utf8");
12376
+ const existing = readFileSync17(filePath, "utf8");
12396
12377
  const beginIdx = existing.indexOf(MANAGED_BLOCK_BEGIN);
12397
12378
  const endIdx = existing.indexOf(MANAGED_BLOCK_END);
12398
12379
  if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
@@ -12521,7 +12502,7 @@ async function runSerenaStatusCommand(options) {
12521
12502
  }
12522
12503
 
12523
12504
  // commands/config-schema.ts
12524
- import { readFileSync as readFileSync19, existsSync as existsSync18 } from "fs";
12505
+ import { readFileSync as readFileSync18, existsSync as existsSync18 } from "fs";
12525
12506
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile2, readdir as readdir2 } from "fs/promises";
12526
12507
  import { resolve as resolve3, dirname as dirname5, relative as relative2 } from "path";
12527
12508
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -12542,7 +12523,7 @@ var _schemaValidator = null;
12542
12523
  var _schemaErrors = null;
12543
12524
  function getSchemaValidator() {
12544
12525
  if (!_schemaValidator) {
12545
- const schema = JSON.parse(readFileSync19(PACKAGED_SCHEMA_PATH, "utf-8"));
12526
+ const schema = JSON.parse(readFileSync18(PACKAGED_SCHEMA_PATH, "utf-8"));
12546
12527
  const ajv = new Ajv2({ strict: true });
12547
12528
  ajv.addKeyword("x-pourkit-schema-version");
12548
12529
  const validate = ajv.compile(schema);
@@ -12556,7 +12537,7 @@ function getSchemaValidator() {
12556
12537
  return _schemaValidator;
12557
12538
  }
12558
12539
  function readPackagedHash() {
12559
- return readFileSync19(PACKAGED_HASH_PATH, "utf-8");
12540
+ return readFileSync18(PACKAGED_HASH_PATH, "utf-8");
12560
12541
  }
12561
12542
  function localSchemaDir(repoRoot2) {
12562
12543
  return resolve3(repoRoot2, ".pourkit/schema");
@@ -13112,7 +13093,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
13112
13093
  detail: icmPath !== null ? icmPath : "icm not found on PATH"
13113
13094
  });
13114
13095
  const gitignorePath = resolve3(cwd, ".gitignore");
13115
- const gitignoreContent = existsSync18(gitignorePath) ? readFileSync19(gitignorePath, "utf-8") : "";
13096
+ const gitignoreContent = existsSync18(gitignorePath) ? readFileSync18(gitignorePath, "utf-8") : "";
13116
13097
  const hasGitignoreEntry = gitignoreContent.includes(".pourkit/icm/");
13117
13098
  checks.push({
13118
13099
  name: "icm-gitignore",
@@ -13161,7 +13142,7 @@ async function runDoctorCommand(options) {
13161
13142
  let rawMemoryConfig;
13162
13143
  if (existsSync18(configPath)) {
13163
13144
  try {
13164
- const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
13145
+ const raw = JSON.parse(readFileSync18(configPath, "utf-8"));
13165
13146
  rawMemoryConfig = raw.memory;
13166
13147
  const validate = getSchemaValidator();
13167
13148
  const valid2 = validate(raw);
@@ -14373,7 +14354,7 @@ init_common();
14373
14354
 
14374
14355
  // execution/sandcastle-execution.ts
14375
14356
  import { existsSync as existsSync21, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
14376
- import { join as join21 } from "path";
14357
+ import { join as join20 } from "path";
14377
14358
  import { createWorktree, opencode } from "@ai-hero/sandcastle";
14378
14359
  import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
14379
14360
 
@@ -14382,10 +14363,10 @@ init_common();
14382
14363
  import { mkdtempSync } from "fs";
14383
14364
  import { writeFile as writeFile4 } from "fs/promises";
14384
14365
  import { tmpdir } from "os";
14385
- import { dirname as dirname7, join as join19 } from "path";
14366
+ import { dirname as dirname7, join as join18 } from "path";
14386
14367
  async function writeExecutionArtifacts(worktreePath, artifacts) {
14387
14368
  for (const artifact of artifacts) {
14388
- const filePath = join19(worktreePath, artifact.path);
14369
+ const filePath = join18(worktreePath, artifact.path);
14389
14370
  await ensureDir(dirname7(filePath));
14390
14371
  await writeFile4(filePath, artifact.content, "utf-8");
14391
14372
  }
@@ -14400,7 +14381,7 @@ import path7 from "path";
14400
14381
 
14401
14382
  // execution/sandbox-image.ts
14402
14383
  import { createHash as createHash2 } from "crypto";
14403
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
14384
+ import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
14404
14385
  import path6 from "path";
14405
14386
  function sandboxImageName(repoRoot2, installIcm) {
14406
14387
  const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
@@ -14411,7 +14392,7 @@ function sandboxImageName(repoRoot2, installIcm) {
14411
14392
  const base2 = `sandcastle:${baseName}`;
14412
14393
  return installIcm ? `${base2}-icm` : base2;
14413
14394
  }
14414
- const fingerprint = createHash2("sha256").update(readFileSync20(dockerfilePath)).digest("hex").slice(0, 8);
14395
+ const fingerprint = createHash2("sha256").update(readFileSync19(dockerfilePath)).digest("hex").slice(0, 8);
14415
14396
  const base = `sandcastle:${baseName}-${fingerprint}`;
14416
14397
  return installIcm ? `${base}-icm` : base;
14417
14398
  }
@@ -14447,7 +14428,7 @@ async function createSandboxFromExistingWorktree(options) {
14447
14428
  }
14448
14429
 
14449
14430
  // execution/sandbox-options.ts
14450
- import { join as join20 } from "path";
14431
+ import { join as join19 } from "path";
14451
14432
  var POURKIT_ICM_CONTAINER_DIR = "/home/agent/.pourkit/icm";
14452
14433
  function buildSandboxOptions(repoRoot2, sandbox, memory) {
14453
14434
  const mounts = [];
@@ -14456,12 +14437,12 @@ function buildSandboxOptions(repoRoot2, sandbox, memory) {
14456
14437
  }
14457
14438
  if (memory?.available) {
14458
14439
  mounts.push({
14459
- hostPath: join20(repoRoot2, ".pourkit", "icm"),
14440
+ hostPath: join19(repoRoot2, ".pourkit", "icm"),
14460
14441
  sandboxPath: POURKIT_ICM_CONTAINER_DIR,
14461
14442
  readonly: false
14462
14443
  });
14463
14444
  mounts.push({
14464
- hostPath: join20(repoRoot2, ".pourkit", "icm", "cache"),
14445
+ hostPath: join19(repoRoot2, ".pourkit", "icm", "cache"),
14465
14446
  sandboxPath: "/home/agent/.cache/icm",
14466
14447
  readonly: false
14467
14448
  });
@@ -14595,13 +14576,13 @@ var SandcastleExecutionSession = class {
14595
14576
  "Memory enabled but memory context is invalid. Run `pourkit memory init` or install ICM for host planning use."
14596
14577
  );
14597
14578
  }
14598
- const hostMemoryDir = join21(repoRoot2, ".pourkit", "icm");
14579
+ const hostMemoryDir = join20(repoRoot2, ".pourkit", "icm");
14599
14580
  if (!existsSync21(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
14600
14581
  throw new MissingHostMemoryDirError(
14601
14582
  "Memory enabled but host .pourkit/icm/ directory is missing. Run `pourkit memory init` to create the repo-scoped memory store."
14602
14583
  );
14603
14584
  }
14604
- mkdirSync9(join21(hostMemoryDir, "cache"), { recursive: true });
14585
+ mkdirSync9(join20(hostMemoryDir, "cache"), { recursive: true });
14605
14586
  }
14606
14587
  const sandboxOptions = buildSandboxOptions(
14607
14588
  repoRoot2,
@@ -14760,7 +14741,7 @@ function sanitizeBranch(branchName) {
14760
14741
  }
14761
14742
  function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
14762
14743
  return paths.filter((relativePath) => {
14763
- const source = join21(repoRoot2, relativePath);
14744
+ const source = join20(repoRoot2, relativePath);
14764
14745
  if (!existsSync21(source)) {
14765
14746
  return true;
14766
14747
  }
@@ -14771,7 +14752,7 @@ function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
14771
14752
  } catch {
14772
14753
  return true;
14773
14754
  }
14774
- const destination = join21(worktreePath, relativePath);
14755
+ const destination = join20(worktreePath, relativePath);
14775
14756
  return !existsSync21(destination);
14776
14757
  });
14777
14758
  }
@@ -14836,12 +14817,12 @@ function isPlainObject(value) {
14836
14817
  return typeof value === "object" && value !== null && !Array.isArray(value);
14837
14818
  }
14838
14819
  function savePromptToFile(repoRoot2, stage, iteration, prompt) {
14839
- const promptsDir = join21(repoRoot2, ".pourkit", ".tmp", "prompts");
14820
+ const promptsDir = join20(repoRoot2, ".pourkit", ".tmp", "prompts");
14840
14821
  mkdirSync9(promptsDir, { recursive: true });
14841
14822
  const timestamp2 = Date.now();
14842
14823
  const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
14843
14824
  const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
14844
- const filePath = join21(promptsDir, filename);
14825
+ const filePath = join20(promptsDir, filename);
14845
14826
  writeFileSync6(filePath, prompt, "utf-8");
14846
14827
  }
14847
14828
 
@@ -15562,11 +15543,11 @@ function createCliProgram(version) {
15562
15543
  return program;
15563
15544
  }
15564
15545
  async function resolveCliVersion() {
15565
- if (isPackageVersion("0.0.0-next-20260622055621")) {
15566
- return "0.0.0-next-20260622055621";
15546
+ if (isPackageVersion("0.0.0-next-20260622235247")) {
15547
+ return "0.0.0-next-20260622235247";
15567
15548
  }
15568
- if (isReleaseVersion("0.0.0-next-20260622055621")) {
15569
- return "0.0.0-next-20260622055621";
15549
+ if (isReleaseVersion("0.0.0-next-20260622235247")) {
15550
+ return "0.0.0-next-20260622235247";
15570
15551
  }
15571
15552
  try {
15572
15553
  const root = repoRoot();