@papi-ai/server 0.7.60 → 0.7.61

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.
@@ -1189,7 +1189,12 @@ var init_proxy_adapter = __esm({
1189
1189
  "listContributorReleasePrs",
1190
1190
  "claimReview",
1191
1191
  "getSiblingAds",
1192
- "getSiblingRepoTasks"
1192
+ "getSiblingRepoTasks",
1193
+ // task-2828 (C339): attributed-intelligence analytics reader — pg-only this cycle.
1194
+ // Hosted forwarding needs a SECURITY DEFINER RPC + edge handler (like task-2394 did
1195
+ // for getModuleEstimationStats); until then keep it here so hosted degrades to a
1196
+ // safe `undefined` rather than forwarding into a 403. Wire under task-2390.
1197
+ "getModelOutcomeStats"
1193
1198
  // task-2394 (C329) — Batch A wired: findPendingDocActionsForTask,
1194
1199
  // getModuleEstimationStats and getDecisionScorePatterns now have edge case handlers
1195
1200
  // (each backed by a SECURITY DEFINER RPC, migration 20260714140000) plus
package/dist/index.js CHANGED
@@ -1306,7 +1306,12 @@ var init_proxy_adapter = __esm({
1306
1306
  "listContributorReleasePrs",
1307
1307
  "claimReview",
1308
1308
  "getSiblingAds",
1309
- "getSiblingRepoTasks"
1309
+ "getSiblingRepoTasks",
1310
+ // task-2828 (C339): attributed-intelligence analytics reader — pg-only this cycle.
1311
+ // Hosted forwarding needs a SECURITY DEFINER RPC + edge handler (like task-2394 did
1312
+ // for getModuleEstimationStats); until then keep it here so hosted degrades to a
1313
+ // safe `undefined` rather than forwarding into a 403. Wire under task-2390.
1314
+ "getModelOutcomeStats"
1310
1315
  // task-2394 (C329) — Batch A wired: findPendingDocActionsForTask,
1311
1316
  // getModuleEstimationStats and getDecisionScorePatterns now have edge case handlers
1312
1317
  // (each backed by a SECURITY DEFINER RPC, migration 20260714140000) plus
@@ -10946,7 +10951,7 @@ function computeCarryForwardStaleness(log2, doneTaskIds) {
10946
10951
  if (stale.length === 0) return void 0;
10947
10952
  const lines = stale.map(([id, count]) => `- **${id}** \u2014 deferred ${count} consecutive cycle(s)`);
10948
10953
  return [
10949
- `\u26A0\uFE0F ${stale.length} task(s) have been in carry-forward for 3+ consecutive cycles. The planner must resolve each \u2014 either escalate to P1 High or recommend cancellation with a closure reason. Deferring again without justification is not acceptable.`,
10954
+ `\u26A0\uFE0F ${stale.length} task(s) have been in carry-forward for 3+ consecutive cycles. The planner must resolve each \u2014 either escalate it to P1 High, or move it to Deferred with a reason. Do NOT cancel a stale carry-forward task: cancelling removes it from the owner's field of view, and only the owner may cancel. Deferring again without a fresh justification is not acceptable.`,
10950
10955
  "",
10951
10956
  ...lines
10952
10957
  ].join("\n");
@@ -11096,6 +11101,15 @@ async function resolveOwnerGate(adapter2, config2) {
11096
11101
 
11097
11102
  // src/services/plan.ts
11098
11103
  var PLAN_BUILD_REPORT_BUDGET = { maxReports: 12, fieldBudget: 280 };
11104
+ function leadChainWithRecommended(chain, recommendedTaskId) {
11105
+ const rec = recommendedTaskId?.trim();
11106
+ const existing = (chain ?? "").trim();
11107
+ if (!rec) return existing || void 0;
11108
+ const firstMentioned = existing.match(/task-\d+/)?.[0];
11109
+ if (firstMentioned === rec) return existing || void 0;
11110
+ const lead = `Build order: ${rec} first.`;
11111
+ return existing ? `${lead} ${existing}` : lead;
11112
+ }
11099
11113
  async function resolvePlanScope(adapter2, config2) {
11100
11114
  const gate = await resolveOwnerGate(adapter2, config2);
11101
11115
  return { callerUserId: gate.callerUserId, ownerUserId: gate.ownerUserId, callerIsOwner: gate.callerIsOwner };
@@ -12044,7 +12058,12 @@ ${cleanContent}`;
12044
12058
  // project owner when unset, so owner-operated plans stay correct).
12045
12059
  ...options.ownerUserId ? { userId: options.ownerUserId } : {},
12046
12060
  // task-2483 (C319): persist the plan's Dependency Chain build-order markdown.
12047
- ...data.dependencyChain ? { dependencyChain: data.dependencyChain } : {}
12061
+ // task-2823 (C338): seed it to lead with recommendedTaskId when the plan omits a
12062
+ // chain or it doesn't lead with the planner's pick, so the hub next-move agrees.
12063
+ ...(() => {
12064
+ const chain = leadChainWithRecommended(data.dependencyChain, data.recommendedTaskId);
12065
+ return chain ? { dependencyChain: chain } : {};
12066
+ })()
12048
12067
  };
12049
12068
  const earlyCreateWarnings = [];
12050
12069
  try {
@@ -12477,7 +12496,13 @@ ${cleanContent}`;
12477
12496
  ...options.ownerUserId ? { userId: options.ownerUserId } : {},
12478
12497
  // task-2483 (C319): persist the plan's Dependency Chain build-order markdown.
12479
12498
  // This full write patches the early minimal row created in Phase 1.
12480
- ...data.dependencyChain ? { dependencyChain: data.dependencyChain } : {}
12499
+ // task-2823 (C338): seed it to lead with recommendedTaskId (mapped from any
12500
+ // new-task tempId) so the hub next-move matches the planner's explicit pick.
12501
+ ...(() => {
12502
+ const recId = data.recommendedTaskId ? newTaskIdMap.get(data.recommendedTaskId) ?? data.recommendedTaskId : null;
12503
+ const chain = leadChainWithRecommended(data.dependencyChain, recId);
12504
+ return chain ? { dependencyChain: chain } : {};
12505
+ })()
12481
12506
  };
12482
12507
  await adapter2.createCycle(cycle);
12483
12508
  } catch (err) {
@@ -13053,7 +13078,6 @@ function defaultHint(tool) {
13053
13078
  function buildSubagentDispatchPrompt(input) {
13054
13079
  const {
13055
13080
  tool,
13056
- applyMode,
13057
13081
  cycleNumber = 0,
13058
13082
  strategyReviewWarning = "",
13059
13083
  taskId,
@@ -13065,7 +13089,7 @@ function buildSubagentDispatchPrompt(input) {
13065
13089
  const isReview = tool === "review_submit";
13066
13090
  let applyNote;
13067
13091
  if (tool === "plan") {
13068
- applyNote = `\`plan\` again with mode="apply", llm_response=<sub-agent output>, plan_mode="${applyMode ?? "full"}", cycle_number=${cycleNumber + 1}, strategy_review_warning=${JSON.stringify(strategyReviewWarning)}`;
13092
+ applyNote = `\`plan\` again with mode="apply", llm_response=<sub-agent output>, cycle_number=${cycleNumber + 1}, strategy_review_warning=${JSON.stringify(strategyReviewWarning)}`;
13069
13093
  } else if (tool === "strategy_review") {
13070
13094
  applyNote = `\`strategy_review\` again with mode="apply", llm_response=<sub-agent output>, cycle_number=${cycleNumber}`;
13071
13095
  } else if (tool === "zoom_out") {
@@ -13129,6 +13153,105 @@ Do NOT post-process the sub-agent reply. The apply path expects the full structu
13129
13153
  step2Note
13130
13154
  ].join("\n");
13131
13155
  }
13156
+ var REVIEW_PRESETS = {
13157
+ // Minimal gate: does it work, and is every write actually read (AD-74)?
13158
+ gate: ["correctness", "wiring"],
13159
+ // Everything.
13160
+ full: ["correctness", "security", "wiring", "test-quality", "scope-drift"],
13161
+ // Security first, plus correctness and a lighter wiring check.
13162
+ "security-focused": ["security", "correctness", "wiring"]
13163
+ };
13164
+ var LENS_INSTRUCTIONS = {
13165
+ correctness: "CORRECTNESS lens. Does the change do what the build report claims, without bugs? Hunt logic errors, off-by-one/boundary mistakes, unhandled error branches, null/undefined hazards, race conditions, and wrong assumptions about inputs. Ignore style, security, and scope \u2014 other lenses own those.",
13166
+ security: "SECURITY lens. Any auth/data/secret/injection risk introduced? Check for missing project_id scoping on multi-tenant SQL, RLS bypass via service role, unescaped input, leaked credentials/tokens in logs or responses, and newly-public routes (PUBLIC_PATHS). Ignore correctness/style unless it is the vector for a security issue.",
13167
+ wiring: "WIRING lens (AD-74 \u2014 producer\u2192store\u2192consumer). A write with no reader is NOT done. For every new value produced (state, DB column, event, field), confirm something actually CONSUMES it \u2014 grep for the reader. localStorage is a cache, never a store. Flag decorative writes, dead affordances, and green gates that guard a component nothing imports. Ignore correctness/security details \u2014 focus on whether the data path is actually connected end-to-end.",
13168
+ "test-quality": "TEST-QUALITY lens. Are the tests present and meaningful? A test that cannot fail is worse than none. Check that assertions test TRANSITIONS not snapshots, that a probe would actually go RED if the code broke, and that new behaviour is covered. Flag tests that assert on mocks instead of real behaviour, and missing coverage on the changed paths.",
13169
+ "scope-drift": "SCOPE-DRIFT lens. Does the diff match the BUILD HANDOFF scope? Flag unrelated changes, files touched outside the stated scope, new dependencies/abstractions not asked for, and \u2014 conversely \u2014 acceptance criteria from the handoff that were skipped. Ignore whether the in-scope code is correct; only judge whether it is the RIGHT set of changes."
13170
+ };
13171
+ var SYNTHESIS_CONTRACT_SHAPE = '{"verdict":"pass|warn|fail","summary":"<one-line consolidated assessment>","findings":[{"severity":"error|warning|info","file":"<path>","line":<number>,"message":"<specific issue>"}]}';
13172
+ var LEG_CONTRACT_SHAPE = '{"lens":"<lens-name>","verdict":"pass|warn|fail","findings":[{"severity":"error|warning|info","file":"<path>","line":<number>,"message":"<specific issue>"}]}';
13173
+ function legId(lens) {
13174
+ return `leg-${lens}`;
13175
+ }
13176
+ function buildFanoutReviewDispatchPrompt(input) {
13177
+ const { taskId, preset, systemPrompt, userMessage, contextBytes } = input;
13178
+ const lenses = REVIEW_PRESETS[preset];
13179
+ const n = lenses.length;
13180
+ const sizeNote = contextBytes !== void 0 ? ` \xB7 ~${Math.round(contextBytes / 1024)} KB context` : "";
13181
+ const sharedContext = `<review_rubric>
13182
+ ${systemPrompt}
13183
+ </review_rubric>
13184
+
13185
+ <build_under_review>
13186
+ ${userMessage}
13187
+ </build_under_review>`;
13188
+ const legBlocks = lenses.map((lens, i) => {
13189
+ const legPrompt = `You are lens ${i + 1} of ${n} in a PAPI multi-lens build review. Review the completed build below through ONE lens only.
13190
+
13191
+ LENS \u2014 ${LENS_INSTRUCTIONS[lens]}
13192
+
13193
+ CONTRACT:
13194
+ - The build report says what was intended; the diff is what actually changed.
13195
+ - Report ONLY findings your lens (${lens}) is responsible for. Do not stray into other lenses.
13196
+ - Return ONLY a single JSON object \u2014 no preamble, no commentary, no fences \u2014 in exactly this shape:
13197
+ ${LEG_CONTRACT_SHAPE.replace("<lens-name>", lens)}
13198
+ - "pass" = no blocking issues for this lens; "warn" = minor/non-blocking; "fail" = a blocking issue this lens found. \`file\`/\`line\` are optional. Empty \`findings\` is valid when your lens is clean.
13199
+
13200
+ ${sharedContext}`;
13201
+ return [
13202
+ `#### Lens ${i + 1}/${n} \u2014 ${lens} \`[${legId(lens)}]\``,
13203
+ "",
13204
+ `<<<BEGIN_LENS_PROMPT:${lens}>>>`,
13205
+ legPrompt,
13206
+ `<<<END_LENS_PROMPT:${lens}>>>`
13207
+ ].join("\n");
13208
+ });
13209
+ const dependsOn = `[${lenses.map(legId).join(", ")}]`;
13210
+ const synthesisPrompt = `You are the SYNTHESIS step of a PAPI multi-lens build review.
13211
+
13212
+ depends_on = ${dependsOn}
13213
+
13214
+ You receive the JSON outputs of all ${n} specialist lens legs below. Your job:
13215
+ - Merge every leg's findings into ONE consolidated review.
13216
+ - Dedupe: collapse findings that multiple lenses raised about the same file/line/issue into a single entry (keep the highest severity).
13217
+ - Prioritize: order findings most-severe first (error \u2192 warning \u2192 info).
13218
+ - Decide ONE overall verdict: "fail" if any leg found a blocking issue, "warn" if only minor nits remain, "pass" if all legs were clean.
13219
+ - Return ONLY a single JSON object \u2014 no preamble, no commentary, no fences \u2014 in exactly this shape (the review_submit auto_review contract):
13220
+ ${SYNTHESIS_CONTRACT_SHAPE}
13221
+
13222
+ <lens_leg_outputs>
13223
+ Paste every lens leg's JSON output here, one per line or concatenated:
13224
+ <<<PASTE_ALL_LEG_OUTPUTS_HERE>>>
13225
+ </lens_leg_outputs>`;
13226
+ const applyNote = `\`review_submit\` with task_id="${taskId}", stage="build-acceptance", verdict=<your decision: accept / request-changes / reject>, comments=<your reasoning>, and auto_review set to the synthesis JSON object verbatim. The synthesized verdict is a RECOMMENDATION \u2014 you make the final call.`;
13227
+ return [
13228
+ `## PAPI review_submit \u2014 Multi-Lens Fan-Out Review (${taskId} \xB7 preset: ${preset} \xB7 ${n} lens${n === 1 ? "" : "es"} + synthesis${sizeNote})`,
13229
+ "",
13230
+ "This build review fans out across specialist lenses: each lens below reviews the SAME diff in parallel, then a synthesis step dedupes their findings into one prioritized verdict. **PAPI emits this prompt \u2014 YOU (the host) run the Task calls. PAPI never executes the agents itself.**",
13231
+ "",
13232
+ "---",
13233
+ "",
13234
+ `### Step 1 \u2014 Dispatch ${n} specialist lens${n === 1 ? "" : "es"} IN PARALLEL`,
13235
+ "",
13236
+ `Make ${n} \`Task\` call${n === 1 ? "" : "s"} in a SINGLE message so they run concurrently. Each uses \`subagent_type: general-purpose\`. For each lens, pass the prompt block between its BEGIN/END markers (trimmed of the markers). Each leg returns ONLY its own tagged JSON.`,
13237
+ "",
13238
+ legBlocks.join("\n\n"),
13239
+ "",
13240
+ "### Step 2 \u2014 Synthesis (`depends_on` = ALL lens legs)",
13241
+ "",
13242
+ `Wait until ALL ${n} lens legs above have returned. The synthesis step below declares \`depends_on = ${dependsOn}\` \u2014 do NOT run it before every leg is back. Make ONE more \`Task\` call (\`subagent_type: general-purpose\`) with the block below, pasting every leg's JSON output where marked.`,
13243
+ "",
13244
+ "<<<BEGIN_SYNTHESIS_PROMPT>>>",
13245
+ synthesisPrompt,
13246
+ "<<<END_SYNTHESIS_PROMPT>>>",
13247
+ "",
13248
+ "### Step 3 \u2014 Apply the synthesized verdict",
13249
+ "",
13250
+ `When synthesis returns its JSON, surface the consolidated findings, decide your verdict, then call ${applyNote}`,
13251
+ "",
13252
+ "Do NOT post-process the synthesis JSON \u2014 the `auto_review` apply path expects it exactly as emitted."
13253
+ ].join("\n");
13254
+ }
13132
13255
 
13133
13256
  // src/lib/resolve-llm-response.ts
13134
13257
  import { readFile as readFile3, stat } from "fs/promises";
@@ -21049,6 +21172,7 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
21049
21172
  completed: capitalizeCompleted(input.completed),
21050
21173
  actualEffort: input.effort,
21051
21174
  estimatedEffort: input.estimatedEffort,
21175
+ model: input.model,
21052
21176
  surprises: input.surprises,
21053
21177
  discoveredIssues: input.discoveredIssues,
21054
21178
  architectureNotes: input.architectureNotes,
@@ -21985,6 +22109,11 @@ var buildExecuteTool = {
21985
22109
  enum: ["XS", "S", "M", "L", "XL"],
21986
22110
  description: "Estimated effort from the BUILD HANDOFF. Required for complete."
21987
22111
  },
22112
+ model: {
22113
+ type: "string",
22114
+ maxLength: 120,
22115
+ description: "The model/agent that executed this build, self-reported, e.g. 'claude-opus-4' / 'gpt-5' \u2014 optional; powers attributed-intelligence analytics. PAPI never selects a model (AD-58) \u2014 report the model YOU are running as."
22116
+ },
21988
22117
  surprises: {
21989
22118
  type: "string",
21990
22119
  maxLength: 800,
@@ -21993,7 +22122,7 @@ var buildExecuteTool = {
21993
22122
  discovered_issues: {
21994
22123
  type: "string",
21995
22124
  maxLength: 800,
21996
- description: `Problems found DURING this build that are OUTSIDE this task's scope. Include severity (P0-P3). Good: "P2: Auth middleware doesn't validate token expiry \u2014 affects all protected routes." Bad: "Had to install a dependency." Only real bugs or gaps that need their own task. Use "None" if none. Required for complete. TIP: When submitting a follow-up idea for a discovered issue, include "learning:<uuid>" in the idea notes to link it to this cycle learning entry \u2014 use the UUID returned in the build completion output.`
22125
+ description: `A REAL bug or gap OUTSIDE this task's scope that needs its own task. Include severity (P0-P3). INCLUSION TEST \u2014 file it only if BOTH are true: (1) the bug would exist even if this task had never run, and (2) it is outside this task's scope. If either is false, it is NOT a discovered_issue. NEVER file a trivial in-file cleanup you noticed while editing (an em-dash tidy, a rename, a lint nit, a stray console.log) \u2014 those are not bugs, just clean them up or leave them. Good: "P2: Auth middleware doesn't validate token expiry \u2014 affects all protected routes." Bad: "Two em dashes remain in the file" / "Had to install a dependency." Use "None" if none. Required for complete. TIP: When submitting a follow-up idea for a discovered issue, include "learning:<uuid>" in the idea notes to link it to this cycle learning entry \u2014 use the UUID returned in the build completion output.`
21997
22126
  },
21998
22127
  architecture_notes: {
21999
22128
  type: "string",
@@ -22279,7 +22408,7 @@ async function handleBuildExecute(adapter2, config2, args, clientName) {
22279
22408
  **PRE-BUILD VERIFICATION:** Before writing any code, read these files and check if the functionality already exists:
22280
22409
  ${verificationFiles.map((f) => `- ${f}`).join("\n")}
22281
22410
  If >80% of the scope is already implemented, call \`build_execute\` with completed="yes" and note "already built" in surprises instead of re-implementing.` : "";
22282
- const chainInstruction = '\n\n---\n\n**IMPORTANT:** After implementing this task, immediately call `build_execute` again with report fields (`completed`, `effort`, `estimated_effort`, `surprises`, `discovered_issues`, `architecture_notes`) to complete the build. Do not wait for user confirmation.\n\n**Build Report Quality Bar:**\n- **surprises**: What was DIFFERENT from expected \u2014 wrong assumptions, scope changes, missing infrastructure. NOT implementation mechanics ("used X library").\n- **discovered_issues**: Bugs/gaps OUTSIDE this task\'s scope, with severity (P0-P3). NOT "had to install a dependency".\n- **architecture_notes**: Patterns/decisions that AFFECT FUTURE WORK. NOT "used React hooks".\n- If nothing meaningful to report for a field, use "None" \u2014 empty signal is better than noise.\n- **dead_ends**: approaches you tried and RULED OUT (with why). Ruled-out paths are first-class intelligence \u2014 send "None" only if nothing you tried failed.';
22411
+ const chainInstruction = '\n\n---\n\n**IMPORTANT:** After implementing this task, immediately call `build_execute` again with report fields (`completed`, `effort`, `estimated_effort`, `surprises`, `discovered_issues`, `architecture_notes`) to complete the build. Do not wait for user confirmation.\n\n**Build Report Quality Bar:**\n- **surprises**: What was DIFFERENT from expected \u2014 wrong assumptions, scope changes, missing infrastructure. NOT implementation mechanics ("used X library").\n- **discovered_issues**: REAL bugs/gaps OUTSIDE this task\'s scope, with severity (P0-P3). Inclusion test: file it only if the bug would exist even if this task had never run AND it is outside this task\'s scope. NEVER a trivial in-file cleanup you noticed while editing (em-dash tidy, rename, lint nit) \u2014 those are not discovered_issues. NOT "had to install a dependency".\n- **architecture_notes**: Patterns/decisions that AFFECT FUTURE WORK. NOT "used React hooks".\n- If nothing meaningful to report for a field, use "None" \u2014 empty signal is better than noise.\n- **dead_ends**: approaches you tried and RULED OUT (with why). Ruled-out paths are first-class intelligence \u2014 send "None" only if nothing you tried failed.';
22283
22412
  const buildDisciplineNote = "\n\n---\n\n**BUILD DISCIPLINE:**\n- **Read before you claim.** Before asserting how something works \u2014 or that it is already done \u2014 read the actual code/state. Don't rely on memory or earlier context.\n- **Sweep existing material first.** Check the docs (`doc_search` or your docs index) and prior tasks, not just whether a file exists \u2014 the work may already be covered.\n- **Ranged reads.** For large or unfamiliar files, read the relevant range rather than the whole file.";
22284
22413
  let adSection = "";
22285
22414
  try {
@@ -22355,6 +22484,7 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
22355
22484
  const completed = args.completed;
22356
22485
  const effort = args.effort;
22357
22486
  const estimatedEffort = args.estimated_effort;
22487
+ const model = typeof args.model === "string" && args.model.trim() !== "" ? args.model.trim() : void 0;
22358
22488
  const surprises = args.surprises;
22359
22489
  const discoveredIssues = args.discovered_issues;
22360
22490
  const architectureNotes = args.architecture_notes;
@@ -22410,6 +22540,7 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
22410
22540
  completed,
22411
22541
  effort: parsedEffort,
22412
22542
  estimatedEffort: parsedEstimatedEffort,
22543
+ model,
22413
22544
  surprises,
22414
22545
  discoveredIssues,
22415
22546
  architectureNotes,
@@ -24058,7 +24189,7 @@ var REVIEW_RUBRIC = [
24058
24189
  "- Quality: tests present and meaningful, no obvious debt or dead code.",
24059
24190
  "Be specific and cite file/line where you can. Recommend fail only for blocking issues."
24060
24191
  ].join("\n");
24061
- async function buildReviewDispatch(adapter2, config2, taskId) {
24192
+ async function assembleReviewContext(adapter2, config2, taskId) {
24062
24193
  const task = await adapter2.getTask(taskId);
24063
24194
  if (!task) {
24064
24195
  return { ok: false, error: `Task ${taskId} not found \u2014 cannot assemble review context.` };
@@ -24091,13 +24222,13 @@ ${report}
24091
24222
 
24092
24223
  ${diffBlock}`;
24093
24224
  const contextBytes = Buffer.byteLength(userMessage, "utf-8");
24094
- const prompt2 = buildSubagentDispatchPrompt({
24095
- tool: "review_submit",
24096
- taskId,
24097
- systemPrompt: REVIEW_RUBRIC,
24098
- userMessage,
24099
- contextBytes
24100
- });
24225
+ return { ok: true, userMessage, contextBytes };
24226
+ }
24227
+ async function buildReviewDispatch(adapter2, config2, taskId, preset) {
24228
+ const ctx = await assembleReviewContext(adapter2, config2, taskId);
24229
+ if (!ctx.ok) return ctx;
24230
+ const { userMessage, contextBytes } = ctx;
24231
+ const prompt2 = preset ? buildFanoutReviewDispatchPrompt({ taskId, preset, systemPrompt: REVIEW_RUBRIC, userMessage, contextBytes }) : buildSubagentDispatchPrompt({ tool: "review_submit", taskId, systemPrompt: REVIEW_RUBRIC, userMessage, contextBytes });
24101
24232
  return { ok: true, prompt: prompt2, contextBytes };
24102
24233
  }
24103
24234
  var reviewListTool = {
@@ -24149,6 +24280,11 @@ var reviewSubmitTool = {
24149
24280
  enum: ["inline", "subagent"],
24150
24281
  description: `task-1864: set "subagent" (build-acceptance only) to offload code review to a fresh sub-agent. Returns a Task() invocation prompt that feeds the build report + branch diff to the sub-agent, which returns structured auto_review findings. Verdict is NOT required on this call \u2014 you call review_submit again with the human verdict + the sub-agent's auto_review. Default "inline" (record the verdict directly).`
24151
24282
  },
24283
+ review_preset: {
24284
+ type: "string",
24285
+ enum: ["gate", "full", "security-focused"],
24286
+ description: 'task-2824 (opt-in): upgrade the sub-agent dispatch to a MULTI-LENS fan-out. Requires dispatch:"subagent" (build-acceptance only). Emits N specialist lens legs (correctness/security/wiring/test-quality/scope-drift) that review the same diff in parallel, plus a synthesis leg that dedupes their findings into ONE prioritized auto_review verdict. "gate" = correctness+wiring, "full" = all 5, "security-focused" = security+correctness+wiring. Omit for the existing single-lens review (unchanged default).'
24287
+ },
24152
24288
  reviewer_confirmed: {
24153
24289
  type: "boolean",
24154
24290
  description: "Set to true to confirm you have reviewed the build (read the build report or the pending list via review_list) before submitting an accept verdict. Required to accept a build-acceptance review unless review_list was called in the same session within the last 15 minutes. Defense-in-depth against SUP-2026-010 (Codex prematurely accepted a task because review_list was missing from its tool surface)."
@@ -24363,11 +24499,24 @@ async function handleReviewSubmit(adapter2, config2, args) {
24363
24499
  caps = {};
24364
24500
  }
24365
24501
  const explicitDispatch = args.dispatch === "subagent";
24502
+ const rawPreset = args.review_preset;
24503
+ const reviewPreset = typeof rawPreset === "string" && rawPreset in REVIEW_PRESETS ? rawPreset : void 0;
24504
+ if (rawPreset !== void 0 && !explicitDispatch) {
24505
+ return errorResponse('review_preset requires dispatch:"subagent" (multi-lens fan-out is only available on an explicit sub-agent dispatch).');
24506
+ }
24507
+ if (typeof rawPreset === "string" && rawPreset.length > 0 && !reviewPreset) {
24508
+ return errorResponse(`review_preset "${rawPreset}" is not valid. Use "gate", "full", or "security-focused".`);
24509
+ }
24366
24510
  const autoDispatchOptIn = args.dispatch !== "inline" && process.env.PAPI_AUTO_DISPATCH !== "false" && isCapabilityEnabled(caps, "prReviewer");
24367
24511
  const autoDispatchEligible = !verdict && autoDispatchOptIn;
24368
24512
  const capabilityAutoReviewEligible = verdict === "accept" && !autoReview && autoDispatchOptIn;
24369
24513
  if ((explicitDispatch || autoDispatchEligible || capabilityAutoReviewEligible) && stage === "build-acceptance" && taskId) {
24370
- const dispatch = await buildReviewDispatch(adapter2, config2, taskId);
24514
+ const dispatch = await buildReviewDispatch(
24515
+ adapter2,
24516
+ config2,
24517
+ taskId,
24518
+ explicitDispatch ? reviewPreset : void 0
24519
+ );
24371
24520
  if (!dispatch.ok) {
24372
24521
  if (explicitDispatch) return errorResponse(dispatch.error);
24373
24522
  } else if (explicitDispatch || capabilityAutoReviewEligible || dispatch.contextBytes > REVIEW_DISPATCH_THRESHOLD) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.60",
3
+ "version": "0.7.61",
4
4
  "description": "PAPI MCP server — AI-powered sprint planning, build execution, and strategy review for software projects",
5
5
  "license": "Elastic-2.0",
6
6
  "mcpName": "io.github.getpapi/papi",