@papi-ai/server 0.7.60 → 0.7.62
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.
|
@@ -88,6 +88,13 @@ Your output is **not done** if ANY of these is true (these are falsifiable — c
|
|
|
88
88
|
- [ ] Guidance, state, or a next action explained in a sentence of helper text where a visual cue
|
|
89
89
|
(position, size, colour, icon, a signpost, motion-on-change) would carry it — prose used as a
|
|
90
90
|
crutch for missing visual steering.
|
|
91
|
+
- [ ] A label, its value, and its explanation each taking their own line ("The Stacked Caption").
|
|
92
|
+
Anywhere the three exist, they share ONE line.
|
|
93
|
+
- [ ] A new datum added as a sibling row beside a row it could extend ("The Siblinged Row").
|
|
94
|
+
Extend the existing row — inline suffix, extra cell, chip on the same baseline — never stack
|
|
95
|
+
a twin row underneath.
|
|
96
|
+
- [ ] An eyebrow label or an explanatory subtitle above/below a heading ("The Eyebrow Crutch").
|
|
97
|
+
Labels carry themselves; if a heading needs a subtitle to be understood, the heading is wrong.
|
|
91
98
|
- [ ] More than ~3 sections fully visible above the fold.
|
|
92
99
|
- [ ] Identical-sized cards in a uniform row (flat hierarchy — needs a dominant 2×+ cell).
|
|
93
100
|
- [ ] Monospace on labels, status, timestamps, or nav.
|
|
@@ -108,6 +108,12 @@ Run through every named anti-pattern and flag matches. Common universal ones:
|
|
|
108
108
|
- Guidance, state, or a next action carried by a sentence of helper text where a visual cue
|
|
109
109
|
(position, size, colour, an icon, a signpost, motion-on-change) would do — prose as a crutch for
|
|
110
110
|
missing visual steering. Prefer showing over telling.
|
|
111
|
+
- A label, its value, and its explanation each taking their own line ("The Stacked Caption") —
|
|
112
|
+
anywhere the three exist, they share ONE line.
|
|
113
|
+
- A new datum added as a sibling row beside a row it could extend ("The Siblinged Row") — extend
|
|
114
|
+
the existing row (inline suffix, extra cell, chip on the same baseline), never stack a twin row.
|
|
115
|
+
- An eyebrow label or explanatory subtitle above/below a heading ("The Eyebrow Crutch") — labels
|
|
116
|
+
carry themselves; a heading that needs a subtitle to be understood is the wrong heading.
|
|
111
117
|
- Identical-sized cards in a uniform row (flat hierarchy).
|
|
112
118
|
- Monospace on labels / status / timestamps / nav.
|
|
113
119
|
- Header, label, and value at the same size+weight.
|
|
@@ -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
|
|
@@ -5054,7 +5059,7 @@ var init_setup = __esm({
|
|
|
5054
5059
|
|
|
5055
5060
|
// src/index.ts
|
|
5056
5061
|
import { readFileSync as readFileSync16 } from "fs";
|
|
5057
|
-
import { dirname as dirname6, join as join24 } from "path";
|
|
5062
|
+
import { dirname as dirname6, join as join24, basename as basename2 } from "path";
|
|
5058
5063
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5059
5064
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5060
5065
|
import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -12740,6 +12765,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
|
|
|
12740
12765
|
const contextBytes2 = Buffer.byteLength(userMessage2, "utf-8");
|
|
12741
12766
|
console.error(`[plan-perf] contextBytes=${contextBytes2} (handoffs-only)`);
|
|
12742
12767
|
const planSystemPrompt2 = await getPrompt("plan-system");
|
|
12768
|
+
await recordPlanGenerationActive(tracker, incomingCycle);
|
|
12743
12769
|
return {
|
|
12744
12770
|
mode: "full",
|
|
12745
12771
|
// apply phase treats it the same
|
|
@@ -12800,6 +12826,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
|
|
|
12800
12826
|
} catch {
|
|
12801
12827
|
}
|
|
12802
12828
|
const planSystemPrompt = await getPrompt("plan-system");
|
|
12829
|
+
await recordPlanGenerationActive(tracker, incomingCycle);
|
|
12803
12830
|
return {
|
|
12804
12831
|
mode,
|
|
12805
12832
|
cycleNumber,
|
|
@@ -12823,6 +12850,15 @@ async function streamPlanStageSteps(tracker, newCycleNumber) {
|
|
|
12823
12850
|
await tracker.recordStep(step);
|
|
12824
12851
|
}
|
|
12825
12852
|
}
|
|
12853
|
+
async function recordPlanGenerationActive(tracker, incomingCycleNumber) {
|
|
12854
|
+
if (!tracker) return;
|
|
12855
|
+
await tracker.recordStep("recommendation", {
|
|
12856
|
+
cycle: incomingCycleNumber,
|
|
12857
|
+
stage: "plan",
|
|
12858
|
+
status: "active",
|
|
12859
|
+
metadata: { phase: "generating" }
|
|
12860
|
+
});
|
|
12861
|
+
}
|
|
12826
12862
|
async function applyPlan(adapter2, config2, rawLlmOutput, mode, cycleNumber, strategyReviewWarning, contextHashes, planRunMeta, tracker) {
|
|
12827
12863
|
const applyTimer = startTimer();
|
|
12828
12864
|
console.error(`[plan-perf] applyPlan: start (llm_response=${rawLlmOutput.length} chars)`);
|
|
@@ -13053,7 +13089,6 @@ function defaultHint(tool) {
|
|
|
13053
13089
|
function buildSubagentDispatchPrompt(input) {
|
|
13054
13090
|
const {
|
|
13055
13091
|
tool,
|
|
13056
|
-
applyMode,
|
|
13057
13092
|
cycleNumber = 0,
|
|
13058
13093
|
strategyReviewWarning = "",
|
|
13059
13094
|
taskId,
|
|
@@ -13065,7 +13100,7 @@ function buildSubagentDispatchPrompt(input) {
|
|
|
13065
13100
|
const isReview = tool === "review_submit";
|
|
13066
13101
|
let applyNote;
|
|
13067
13102
|
if (tool === "plan") {
|
|
13068
|
-
applyNote = `\`plan\` again with mode="apply", llm_response=<sub-agent output>,
|
|
13103
|
+
applyNote = `\`plan\` again with mode="apply", llm_response=<sub-agent output>, cycle_number=${cycleNumber + 1}, strategy_review_warning=${JSON.stringify(strategyReviewWarning)}`;
|
|
13069
13104
|
} else if (tool === "strategy_review") {
|
|
13070
13105
|
applyNote = `\`strategy_review\` again with mode="apply", llm_response=<sub-agent output>, cycle_number=${cycleNumber}`;
|
|
13071
13106
|
} else if (tool === "zoom_out") {
|
|
@@ -13129,6 +13164,105 @@ Do NOT post-process the sub-agent reply. The apply path expects the full structu
|
|
|
13129
13164
|
step2Note
|
|
13130
13165
|
].join("\n");
|
|
13131
13166
|
}
|
|
13167
|
+
var REVIEW_PRESETS = {
|
|
13168
|
+
// Minimal gate: does it work, and is every write actually read (AD-74)?
|
|
13169
|
+
gate: ["correctness", "wiring"],
|
|
13170
|
+
// Everything.
|
|
13171
|
+
full: ["correctness", "security", "wiring", "test-quality", "scope-drift"],
|
|
13172
|
+
// Security first, plus correctness and a lighter wiring check.
|
|
13173
|
+
"security-focused": ["security", "correctness", "wiring"]
|
|
13174
|
+
};
|
|
13175
|
+
var LENS_INSTRUCTIONS = {
|
|
13176
|
+
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.",
|
|
13177
|
+
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.",
|
|
13178
|
+
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.",
|
|
13179
|
+
"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.",
|
|
13180
|
+
"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."
|
|
13181
|
+
};
|
|
13182
|
+
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>"}]}';
|
|
13183
|
+
var LEG_CONTRACT_SHAPE = '{"lens":"<lens-name>","verdict":"pass|warn|fail","findings":[{"severity":"error|warning|info","file":"<path>","line":<number>,"message":"<specific issue>"}]}';
|
|
13184
|
+
function legId(lens) {
|
|
13185
|
+
return `leg-${lens}`;
|
|
13186
|
+
}
|
|
13187
|
+
function buildFanoutReviewDispatchPrompt(input) {
|
|
13188
|
+
const { taskId, preset, systemPrompt, userMessage, contextBytes } = input;
|
|
13189
|
+
const lenses = REVIEW_PRESETS[preset];
|
|
13190
|
+
const n = lenses.length;
|
|
13191
|
+
const sizeNote = contextBytes !== void 0 ? ` \xB7 ~${Math.round(contextBytes / 1024)} KB context` : "";
|
|
13192
|
+
const sharedContext = `<review_rubric>
|
|
13193
|
+
${systemPrompt}
|
|
13194
|
+
</review_rubric>
|
|
13195
|
+
|
|
13196
|
+
<build_under_review>
|
|
13197
|
+
${userMessage}
|
|
13198
|
+
</build_under_review>`;
|
|
13199
|
+
const legBlocks = lenses.map((lens, i) => {
|
|
13200
|
+
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.
|
|
13201
|
+
|
|
13202
|
+
LENS \u2014 ${LENS_INSTRUCTIONS[lens]}
|
|
13203
|
+
|
|
13204
|
+
CONTRACT:
|
|
13205
|
+
- The build report says what was intended; the diff is what actually changed.
|
|
13206
|
+
- Report ONLY findings your lens (${lens}) is responsible for. Do not stray into other lenses.
|
|
13207
|
+
- Return ONLY a single JSON object \u2014 no preamble, no commentary, no fences \u2014 in exactly this shape:
|
|
13208
|
+
${LEG_CONTRACT_SHAPE.replace("<lens-name>", lens)}
|
|
13209
|
+
- "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.
|
|
13210
|
+
|
|
13211
|
+
${sharedContext}`;
|
|
13212
|
+
return [
|
|
13213
|
+
`#### Lens ${i + 1}/${n} \u2014 ${lens} \`[${legId(lens)}]\``,
|
|
13214
|
+
"",
|
|
13215
|
+
`<<<BEGIN_LENS_PROMPT:${lens}>>>`,
|
|
13216
|
+
legPrompt,
|
|
13217
|
+
`<<<END_LENS_PROMPT:${lens}>>>`
|
|
13218
|
+
].join("\n");
|
|
13219
|
+
});
|
|
13220
|
+
const dependsOn = `[${lenses.map(legId).join(", ")}]`;
|
|
13221
|
+
const synthesisPrompt = `You are the SYNTHESIS step of a PAPI multi-lens build review.
|
|
13222
|
+
|
|
13223
|
+
depends_on = ${dependsOn}
|
|
13224
|
+
|
|
13225
|
+
You receive the JSON outputs of all ${n} specialist lens legs below. Your job:
|
|
13226
|
+
- Merge every leg's findings into ONE consolidated review.
|
|
13227
|
+
- Dedupe: collapse findings that multiple lenses raised about the same file/line/issue into a single entry (keep the highest severity).
|
|
13228
|
+
- Prioritize: order findings most-severe first (error \u2192 warning \u2192 info).
|
|
13229
|
+
- Decide ONE overall verdict: "fail" if any leg found a blocking issue, "warn" if only minor nits remain, "pass" if all legs were clean.
|
|
13230
|
+
- Return ONLY a single JSON object \u2014 no preamble, no commentary, no fences \u2014 in exactly this shape (the review_submit auto_review contract):
|
|
13231
|
+
${SYNTHESIS_CONTRACT_SHAPE}
|
|
13232
|
+
|
|
13233
|
+
<lens_leg_outputs>
|
|
13234
|
+
Paste every lens leg's JSON output here, one per line or concatenated:
|
|
13235
|
+
<<<PASTE_ALL_LEG_OUTPUTS_HERE>>>
|
|
13236
|
+
</lens_leg_outputs>`;
|
|
13237
|
+
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.`;
|
|
13238
|
+
return [
|
|
13239
|
+
`## PAPI review_submit \u2014 Multi-Lens Fan-Out Review (${taskId} \xB7 preset: ${preset} \xB7 ${n} lens${n === 1 ? "" : "es"} + synthesis${sizeNote})`,
|
|
13240
|
+
"",
|
|
13241
|
+
"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.**",
|
|
13242
|
+
"",
|
|
13243
|
+
"---",
|
|
13244
|
+
"",
|
|
13245
|
+
`### Step 1 \u2014 Dispatch ${n} specialist lens${n === 1 ? "" : "es"} IN PARALLEL`,
|
|
13246
|
+
"",
|
|
13247
|
+
`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.`,
|
|
13248
|
+
"",
|
|
13249
|
+
legBlocks.join("\n\n"),
|
|
13250
|
+
"",
|
|
13251
|
+
"### Step 2 \u2014 Synthesis (`depends_on` = ALL lens legs)",
|
|
13252
|
+
"",
|
|
13253
|
+
`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.`,
|
|
13254
|
+
"",
|
|
13255
|
+
"<<<BEGIN_SYNTHESIS_PROMPT>>>",
|
|
13256
|
+
synthesisPrompt,
|
|
13257
|
+
"<<<END_SYNTHESIS_PROMPT>>>",
|
|
13258
|
+
"",
|
|
13259
|
+
"### Step 3 \u2014 Apply the synthesized verdict",
|
|
13260
|
+
"",
|
|
13261
|
+
`When synthesis returns its JSON, surface the consolidated findings, decide your verdict, then call ${applyNote}`,
|
|
13262
|
+
"",
|
|
13263
|
+
"Do NOT post-process the synthesis JSON \u2014 the `auto_review` apply path expects it exactly as emitted."
|
|
13264
|
+
].join("\n");
|
|
13265
|
+
}
|
|
13132
13266
|
|
|
13133
13267
|
// src/lib/resolve-llm-response.ts
|
|
13134
13268
|
import { readFile as readFile3, stat } from "fs/promises";
|
|
@@ -21049,6 +21183,7 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
21049
21183
|
completed: capitalizeCompleted(input.completed),
|
|
21050
21184
|
actualEffort: input.effort,
|
|
21051
21185
|
estimatedEffort: input.estimatedEffort,
|
|
21186
|
+
model: input.model,
|
|
21052
21187
|
surprises: input.surprises,
|
|
21053
21188
|
discoveredIssues: input.discoveredIssues,
|
|
21054
21189
|
architectureNotes: input.architectureNotes,
|
|
@@ -21985,6 +22120,11 @@ var buildExecuteTool = {
|
|
|
21985
22120
|
enum: ["XS", "S", "M", "L", "XL"],
|
|
21986
22121
|
description: "Estimated effort from the BUILD HANDOFF. Required for complete."
|
|
21987
22122
|
},
|
|
22123
|
+
model: {
|
|
22124
|
+
type: "string",
|
|
22125
|
+
maxLength: 120,
|
|
22126
|
+
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."
|
|
22127
|
+
},
|
|
21988
22128
|
surprises: {
|
|
21989
22129
|
type: "string",
|
|
21990
22130
|
maxLength: 800,
|
|
@@ -21993,7 +22133,7 @@ var buildExecuteTool = {
|
|
|
21993
22133
|
discovered_issues: {
|
|
21994
22134
|
type: "string",
|
|
21995
22135
|
maxLength: 800,
|
|
21996
|
-
description: `
|
|
22136
|
+
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
22137
|
},
|
|
21998
22138
|
architecture_notes: {
|
|
21999
22139
|
type: "string",
|
|
@@ -22279,7 +22419,7 @@ async function handleBuildExecute(adapter2, config2, args, clientName) {
|
|
|
22279
22419
|
**PRE-BUILD VERIFICATION:** Before writing any code, read these files and check if the functionality already exists:
|
|
22280
22420
|
${verificationFiles.map((f) => `- ${f}`).join("\n")}
|
|
22281
22421
|
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**:
|
|
22422
|
+
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
22423
|
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
22424
|
let adSection = "";
|
|
22285
22425
|
try {
|
|
@@ -22307,6 +22447,27 @@ ${entries}`;
|
|
|
22307
22447
|
}
|
|
22308
22448
|
} catch {
|
|
22309
22449
|
}
|
|
22450
|
+
let openIssuesSection = "";
|
|
22451
|
+
try {
|
|
22452
|
+
if (adapter2.getCycleLearnings) {
|
|
22453
|
+
const issues = (await adapter2.getCycleLearnings({ category: "issue", limit: 20 })).filter((l) => !l.resolvedAt && l.id);
|
|
22454
|
+
const taskIds = new Set([result.task.id, result.task.displayId].filter(Boolean));
|
|
22455
|
+
const moduleTag = result.task.module?.trim().toLowerCase();
|
|
22456
|
+
const score = (l) => (taskIds.has(l.taskId) ? 2 : 0) + (moduleTag && l.tags.some((t) => t.toLowerCase() === moduleTag) ? 1 : 0);
|
|
22457
|
+
const top = issues.sort((a, b2) => score(b2) - score(a)).slice(0, 8);
|
|
22458
|
+
if (top.length > 0) {
|
|
22459
|
+
const rows = top.map((l) => `- \`${l.id}\` \xB7 ${l.severity ?? "P3"} \xB7 ${l.summary}`).join("\n");
|
|
22460
|
+
openIssuesSection = `
|
|
22461
|
+
|
|
22462
|
+
---
|
|
22463
|
+
|
|
22464
|
+
**OPEN DISCOVERED ISSUES** (${top.length} shown):
|
|
22465
|
+
${rows}
|
|
22466
|
+
If this build fixes any of these, pass their UUIDs in \`fixed_issues\` on complete \u2014 that stamps them FIXED on the caught\u2192fixed ledger. Do not fix out-of-scope issues just to clear the list.`;
|
|
22467
|
+
}
|
|
22468
|
+
}
|
|
22469
|
+
} catch {
|
|
22470
|
+
}
|
|
22310
22471
|
const moduleInstructions = getModuleInstructions(result.task.module);
|
|
22311
22472
|
const moduleContext = await getModuleContext(adapter2, result.task);
|
|
22312
22473
|
const filesToWriteSection = result.filesToWrite ? formatFilesToWriteSection(result.filesToWrite) : "";
|
|
@@ -22315,7 +22476,7 @@ ${entries}`;
|
|
|
22315
22476
|
formatModelRecommendation(result.task.buildHandoff?.effort ?? result.task.complexity)
|
|
22316
22477
|
) ?? "";
|
|
22317
22478
|
const gestaltNote = buildGestaltPreBuildDirective(caps) ?? "";
|
|
22318
|
-
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + verificationNote + buildDisciplineNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
22479
|
+
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + openIssuesSection + verificationNote + buildDisciplineNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
22319
22480
|
} catch (err) {
|
|
22320
22481
|
if (isNoHandoffError(err)) {
|
|
22321
22482
|
const lines = [
|
|
@@ -22355,6 +22516,7 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
22355
22516
|
const completed = args.completed;
|
|
22356
22517
|
const effort = args.effort;
|
|
22357
22518
|
const estimatedEffort = args.estimated_effort;
|
|
22519
|
+
const model = typeof args.model === "string" && args.model.trim() !== "" ? args.model.trim() : void 0;
|
|
22358
22520
|
const surprises = args.surprises;
|
|
22359
22521
|
const discoveredIssues = args.discovered_issues;
|
|
22360
22522
|
const architectureNotes = args.architecture_notes;
|
|
@@ -22410,6 +22572,7 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
22410
22572
|
completed,
|
|
22411
22573
|
effort: parsedEffort,
|
|
22412
22574
|
estimatedEffort: parsedEstimatedEffort,
|
|
22575
|
+
model,
|
|
22413
22576
|
surprises,
|
|
22414
22577
|
discoveredIssues,
|
|
22415
22578
|
architectureNotes,
|
|
@@ -22487,9 +22650,14 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
22487
22650
|
batchRollupNote = "";
|
|
22488
22651
|
}
|
|
22489
22652
|
}
|
|
22490
|
-
|
|
22653
|
+
let fixedNote = "";
|
|
22654
|
+
if (fixedResolvedCount > 0) {
|
|
22655
|
+
fixedNote = `
|
|
22491
22656
|
|
|
22492
|
-
\u2705 Marked ${fixedResolvedCount} discovered issue(s) FIXED \u2014 resolved_at stamped, now counted as fixed on the hub's caught\u2192fixed ledger
|
|
22657
|
+
\u2705 Marked ${fixedResolvedCount} discovered issue(s) FIXED \u2014 resolved_at stamped, now counted as fixed on the hub's caught\u2192fixed ledger.`;
|
|
22658
|
+
} else if (discoveredIssues && discoveredIssues.trim() !== "" && !/^none\b/i.test(discoveredIssues.trim())) {
|
|
22659
|
+
fixedNote = "\n\n\u2139\uFE0F This build filed discovered issues but passed no `fixed_issues`. When a future build fixes one, pass its UUID in `fixed_issues` so it counts as FIXED (not just auto-cleared) on the hub ledger.";
|
|
22660
|
+
}
|
|
22493
22661
|
return textResponse(formatCompleteResult(result) + fixedNote + docsNote + batchRollupNote);
|
|
22494
22662
|
} catch (err) {
|
|
22495
22663
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -22957,7 +23125,11 @@ async function handleBug(adapter2, config2, args) {
|
|
|
22957
23125
|
type,
|
|
22958
23126
|
description: text,
|
|
22959
23127
|
diagnostics,
|
|
22960
|
-
|
|
23128
|
+
// Canonical triage vocabulary (submitted|investigating|fixed|wont_fix).
|
|
23129
|
+
// Was 'open' — a third vocabulary that made the owner triage console
|
|
23130
|
+
// render new upstream reports as un-triaged. Adapters pass status through,
|
|
23131
|
+
// so emitting canonical here keeps hosted + pg writes consistent.
|
|
23132
|
+
status: "submitted",
|
|
22961
23133
|
notifyRequested,
|
|
22962
23134
|
contactOk
|
|
22963
23135
|
});
|
|
@@ -24058,7 +24230,7 @@ var REVIEW_RUBRIC = [
|
|
|
24058
24230
|
"- Quality: tests present and meaningful, no obvious debt or dead code.",
|
|
24059
24231
|
"Be specific and cite file/line where you can. Recommend fail only for blocking issues."
|
|
24060
24232
|
].join("\n");
|
|
24061
|
-
async function
|
|
24233
|
+
async function assembleReviewContext(adapter2, config2, taskId) {
|
|
24062
24234
|
const task = await adapter2.getTask(taskId);
|
|
24063
24235
|
if (!task) {
|
|
24064
24236
|
return { ok: false, error: `Task ${taskId} not found \u2014 cannot assemble review context.` };
|
|
@@ -24091,13 +24263,13 @@ ${report}
|
|
|
24091
24263
|
|
|
24092
24264
|
${diffBlock}`;
|
|
24093
24265
|
const contextBytes = Buffer.byteLength(userMessage, "utf-8");
|
|
24094
|
-
|
|
24095
|
-
|
|
24096
|
-
|
|
24097
|
-
|
|
24098
|
-
|
|
24099
|
-
|
|
24100
|
-
});
|
|
24266
|
+
return { ok: true, userMessage, contextBytes };
|
|
24267
|
+
}
|
|
24268
|
+
async function buildReviewDispatch(adapter2, config2, taskId, preset) {
|
|
24269
|
+
const ctx = await assembleReviewContext(adapter2, config2, taskId);
|
|
24270
|
+
if (!ctx.ok) return ctx;
|
|
24271
|
+
const { userMessage, contextBytes } = ctx;
|
|
24272
|
+
const prompt2 = preset ? buildFanoutReviewDispatchPrompt({ taskId, preset, systemPrompt: REVIEW_RUBRIC, userMessage, contextBytes }) : buildSubagentDispatchPrompt({ tool: "review_submit", taskId, systemPrompt: REVIEW_RUBRIC, userMessage, contextBytes });
|
|
24101
24273
|
return { ok: true, prompt: prompt2, contextBytes };
|
|
24102
24274
|
}
|
|
24103
24275
|
var reviewListTool = {
|
|
@@ -24149,6 +24321,11 @@ var reviewSubmitTool = {
|
|
|
24149
24321
|
enum: ["inline", "subagent"],
|
|
24150
24322
|
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
24323
|
},
|
|
24324
|
+
review_preset: {
|
|
24325
|
+
type: "string",
|
|
24326
|
+
enum: ["gate", "full", "security-focused"],
|
|
24327
|
+
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).'
|
|
24328
|
+
},
|
|
24152
24329
|
reviewer_confirmed: {
|
|
24153
24330
|
type: "boolean",
|
|
24154
24331
|
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 +24540,24 @@ async function handleReviewSubmit(adapter2, config2, args) {
|
|
|
24363
24540
|
caps = {};
|
|
24364
24541
|
}
|
|
24365
24542
|
const explicitDispatch = args.dispatch === "subagent";
|
|
24543
|
+
const rawPreset = args.review_preset;
|
|
24544
|
+
const reviewPreset = typeof rawPreset === "string" && rawPreset in REVIEW_PRESETS ? rawPreset : void 0;
|
|
24545
|
+
if (rawPreset !== void 0 && !explicitDispatch) {
|
|
24546
|
+
return errorResponse('review_preset requires dispatch:"subagent" (multi-lens fan-out is only available on an explicit sub-agent dispatch).');
|
|
24547
|
+
}
|
|
24548
|
+
if (typeof rawPreset === "string" && rawPreset.length > 0 && !reviewPreset) {
|
|
24549
|
+
return errorResponse(`review_preset "${rawPreset}" is not valid. Use "gate", "full", or "security-focused".`);
|
|
24550
|
+
}
|
|
24366
24551
|
const autoDispatchOptIn = args.dispatch !== "inline" && process.env.PAPI_AUTO_DISPATCH !== "false" && isCapabilityEnabled(caps, "prReviewer");
|
|
24367
24552
|
const autoDispatchEligible = !verdict && autoDispatchOptIn;
|
|
24368
24553
|
const capabilityAutoReviewEligible = verdict === "accept" && !autoReview && autoDispatchOptIn;
|
|
24369
24554
|
if ((explicitDispatch || autoDispatchEligible || capabilityAutoReviewEligible) && stage === "build-acceptance" && taskId) {
|
|
24370
|
-
const dispatch = await buildReviewDispatch(
|
|
24555
|
+
const dispatch = await buildReviewDispatch(
|
|
24556
|
+
adapter2,
|
|
24557
|
+
config2,
|
|
24558
|
+
taskId,
|
|
24559
|
+
explicitDispatch ? reviewPreset : void 0
|
|
24560
|
+
);
|
|
24371
24561
|
if (!dispatch.ok) {
|
|
24372
24562
|
if (explicitDispatch) return errorResponse(dispatch.error);
|
|
24373
24563
|
} else if (explicitDispatch || capabilityAutoReviewEligible || dispatch.contextBytes > REVIEW_DISPATCH_THRESHOLD) {
|
|
@@ -30050,4 +30240,10 @@ if (isHttpMode && httpPort !== void 0) {
|
|
|
30050
30240
|
}
|
|
30051
30241
|
const transport = new StdioServerTransport();
|
|
30052
30242
|
await server.connect(transport);
|
|
30243
|
+
const projectName = basename2(config.projectRoot);
|
|
30244
|
+
const projectIdShort = config.projectId ? ` (${config.projectId.slice(0, 8)})` : process.env.PAPI_PROJECT_ID ? ` (${process.env.PAPI_PROJECT_ID.slice(0, 8)})` : "";
|
|
30245
|
+
process.stderr.write(
|
|
30246
|
+
`[papi] Connected \u2014 project: ${projectName}${projectIdShort}, adapter: ${config.adapterType}, v${pkgVersion}
|
|
30247
|
+
`
|
|
30248
|
+
);
|
|
30053
30249
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.62",
|
|
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",
|