@papi-ai/server 0.7.47 → 0.7.48
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/index.js +66 -20
- package/dist/prompts.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8714,7 +8714,7 @@ REFERENCE DOCS
|
|
|
8714
8714
|
[Optional \u2014 paths to docs/ files that provide background context for this task. Include only when the task originated from research or scoping work and the doc contains context the builder will need beyond what is in this handoff. Omit this section entirely for tasks that don't need supplementary context.]
|
|
8715
8715
|
|
|
8716
8716
|
PRE-BUILD VERIFICATION
|
|
8717
|
-
[List 2-5 specific file paths the builder should read BEFORE implementing to check if the functionality already exists. Derive these from FILES LIKELY TOUCHED \u2014 pick the files most likely to already contain the target functionality. If >80% of the scope is already implemented, the builder should report "already built" instead of re-implementing. Include this section for EVERY task \u2014 it prevents wasted build slots on already-shipped code.]
|
|
8717
|
+
[List 2-5 specific file paths the builder should read BEFORE implementing to check if the functionality already exists. Derive these from FILES LIKELY TOUCHED \u2014 pick the files most likely to already contain the target functionality. ALSO mandate a docs sweep, not just file-existence (task-2161): name any docs the builder should check via doc_search or the docs index \u2014 a design/research/status:final doc or a prior task may already cover the work. If >80% of the scope is already implemented, the builder should report "already built" instead of re-implementing. Include this section for EVERY task \u2014 it prevents wasted build slots on already-shipped code.]
|
|
8718
8718
|
|
|
8719
8719
|
FILES LIKELY TOUCHED
|
|
8720
8720
|
[files]
|
|
@@ -10805,7 +10805,8 @@ async function resolveOwnerGate(adapter2, config2) {
|
|
|
10805
10805
|
enforced: true,
|
|
10806
10806
|
callerIsOwner: isProjectOwner(callerUserId, identity.ownerUserId),
|
|
10807
10807
|
callerUserId,
|
|
10808
|
-
ownerUserId: identity.ownerUserId
|
|
10808
|
+
ownerUserId: identity.ownerUserId,
|
|
10809
|
+
transport: "proxy"
|
|
10809
10810
|
};
|
|
10810
10811
|
} catch (err) {
|
|
10811
10812
|
return {
|
|
@@ -10813,7 +10814,8 @@ async function resolveOwnerGate(adapter2, config2) {
|
|
|
10813
10814
|
callerIsOwner: false,
|
|
10814
10815
|
callerUserId: config2.userId ?? null,
|
|
10815
10816
|
ownerUserId: null,
|
|
10816
|
-
resolutionError: err instanceof Error ? err.message : String(err)
|
|
10817
|
+
resolutionError: err instanceof Error ? err.message : String(err),
|
|
10818
|
+
transport: "proxy"
|
|
10817
10819
|
};
|
|
10818
10820
|
}
|
|
10819
10821
|
}
|
|
@@ -10831,14 +10833,16 @@ async function resolveOwnerGate(adapter2, config2) {
|
|
|
10831
10833
|
callerIsOwner: isProjectOwner(callerUserId, ownerUserId),
|
|
10832
10834
|
callerUserId,
|
|
10833
10835
|
ownerUserId,
|
|
10834
|
-
...resolutionError !== void 0 ? { resolutionError } : {}
|
|
10836
|
+
...resolutionError !== void 0 ? { resolutionError } : {},
|
|
10837
|
+
transport: "pg"
|
|
10835
10838
|
};
|
|
10836
10839
|
}
|
|
10837
10840
|
return {
|
|
10838
10841
|
enforced: false,
|
|
10839
10842
|
callerIsOwner: true,
|
|
10840
10843
|
callerUserId: config2.userId ?? null,
|
|
10841
|
-
ownerUserId: null
|
|
10844
|
+
ownerUserId: null,
|
|
10845
|
+
transport: "md"
|
|
10842
10846
|
};
|
|
10843
10847
|
}
|
|
10844
10848
|
|
|
@@ -19414,10 +19418,22 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
19414
19418
|
const resolutionNote = gate.resolutionError ? `
|
|
19415
19419
|
|
|
19416
19420
|
Identity resolution failed (${gate.resolutionError}) \u2014 the gate fails closed. Retry once connectivity is restored.` : "";
|
|
19421
|
+
if (gate.transport === "pg" && gate.callerUserId === null) {
|
|
19422
|
+
return errorResponse(
|
|
19423
|
+
`Release needs to know who you are, and this setup has no PAPI_USER_ID yet.
|
|
19424
|
+
|
|
19425
|
+
Add it to the papi server's env block in .mcp.json \u2014 the value is your account UUID (getpapi.ai \u2192 Settings \u2192 Account):
|
|
19426
|
+
|
|
19427
|
+
"env": { "PAPI_USER_ID": "<your-account-uuid>", ... }
|
|
19428
|
+
|
|
19429
|
+
Then reconnect your AI tool and run release again. (Direct/pg setups read identity from this variable; hosted setups resolve it from the API key automatically.)`
|
|
19430
|
+
);
|
|
19431
|
+
}
|
|
19417
19432
|
tracker.mark("contributor-role-gate");
|
|
19418
19433
|
const callerRole = adapter2.getContributorRole && gate.callerUserId ? await adapter2.getContributorRole(gate.callerUserId).catch(() => null) : null;
|
|
19419
19434
|
if (callerRole !== "editor") {
|
|
19420
|
-
const
|
|
19435
|
+
const ownerHint = gate.transport === "pg" ? `If you ARE the owner, set PAPI_USER_ID to your account UUID in .mcp.json (getpapi.ai \u2192 Settings \u2192 Account).` : `If you ARE the owner, your identity comes from your API key \u2014 check you are using YOUR key for YOUR project.`;
|
|
19436
|
+
const roleNote = callerRole === "viewer" ? `Your role on this project is "viewer", which cannot release. Ask the owner (or an editor) to release, or ask for editor access.` : `Your identity does not match this project's owner, and you do not have an editor role to open a release PR. If you are a contributor, ask the owner for editor access (or push your branch and open a PR manually). ` + ownerHint;
|
|
19421
19437
|
return errorResponse(
|
|
19422
19438
|
`Release to ${productionBaseBranch} is restricted to the project owner.
|
|
19423
19439
|
|
|
@@ -19794,7 +19810,10 @@ function triggerSurfaceHitsOnBranch(projectRoot, baseRef = "origin/main") {
|
|
|
19794
19810
|
function selectAutostashPaths(modified, untracked) {
|
|
19795
19811
|
const untrackedSet = new Set(untracked);
|
|
19796
19812
|
const isDocsPath = (p) => p === "docs" || p.startsWith("docs/");
|
|
19797
|
-
const
|
|
19813
|
+
const isUntrackedDirEntry = (p) => p.endsWith("/") && untracked.some((u) => u.startsWith(p));
|
|
19814
|
+
const trackedDirty = modified.filter(
|
|
19815
|
+
(p) => !untrackedSet.has(p) && !isUntrackedDirEntry(p)
|
|
19816
|
+
);
|
|
19798
19817
|
const preservedDocs = untracked.filter(isDocsPath);
|
|
19799
19818
|
const stashableUntracked = untracked.filter((p) => !isDocsPath(p));
|
|
19800
19819
|
return { toStash: [...trackedDirty, ...stashableUntracked], preservedDocs };
|
|
@@ -20114,6 +20133,17 @@ async function describeTask(adapter2, taskId) {
|
|
|
20114
20133
|
function shouldBlockDirtyBranchSwitch(opts) {
|
|
20115
20134
|
return opts.trackedDirtyCount > 0 && opts.currentBranch !== opts.baseBranch && opts.currentBranch !== opts.featureBranch;
|
|
20116
20135
|
}
|
|
20136
|
+
async function persistBranchName(adapter2, taskId, branch) {
|
|
20137
|
+
try {
|
|
20138
|
+
await adapter2.updateTask(taskId, { branchName: branch });
|
|
20139
|
+
return true;
|
|
20140
|
+
} catch (err) {
|
|
20141
|
+
console.error(
|
|
20142
|
+
`[build] branch_name persist skipped for ${taskId} (non-fatal): ` + (err instanceof Error ? err.message : String(err))
|
|
20143
|
+
);
|
|
20144
|
+
return false;
|
|
20145
|
+
}
|
|
20146
|
+
}
|
|
20117
20147
|
async function startBuild(adapter2, config2, taskId, options = {}, clientName) {
|
|
20118
20148
|
const task = await adapter2.getTask(taskId);
|
|
20119
20149
|
if (!task) {
|
|
@@ -20330,6 +20360,10 @@ async function startBuild(adapter2, config2, taskId, options = {}, clientName) {
|
|
|
20330
20360
|
if (task.status !== "In Progress") {
|
|
20331
20361
|
await adapter2.updateTaskStatus(taskId, "In Progress");
|
|
20332
20362
|
}
|
|
20363
|
+
const startedBranch = taskBranchMap.get(taskId);
|
|
20364
|
+
if (startedBranch) {
|
|
20365
|
+
await persistBranchName(adapter2, taskId, startedBranch);
|
|
20366
|
+
}
|
|
20333
20367
|
buildStartTimes.set(taskId, (/* @__PURE__ */ new Date()).toISOString());
|
|
20334
20368
|
if (isGitAvailable() && isGitRepo(config2.projectRoot)) {
|
|
20335
20369
|
const startSha = getHeadCommitSha(config2.projectRoot);
|
|
@@ -21438,7 +21472,7 @@ var buildExecuteTool = {
|
|
|
21438
21472
|
},
|
|
21439
21473
|
dead_ends: {
|
|
21440
21474
|
type: "string",
|
|
21441
|
-
description: `
|
|
21475
|
+
description: `CONTRACT: send this on every complete. List approaches you tried and RULED OUT during this build, with why each failed. Example: "Tried Supabase realtime but Edge Functions can't hold persistent connections \u2014 switched to polling." Send "None" ONLY if you genuinely tried nothing that failed. Ruled-out paths are first-class intelligence \u2014 future builds and plans read them to avoid repeating blind alleys.`
|
|
21442
21476
|
},
|
|
21443
21477
|
brief_implications: {
|
|
21444
21478
|
type: "array",
|
|
@@ -21683,7 +21717,8 @@ async function handleBuildExecute(adapter2, config2, args, clientName) {
|
|
|
21683
21717
|
**PRE-BUILD VERIFICATION:** Before writing any code, read these files and check if the functionality already exists:
|
|
21684
21718
|
${verificationFiles.map((f) => `- ${f}`).join("\n")}
|
|
21685
21719
|
If >80% of the scope is already implemented, call \`build_execute\` with completed="yes" and note "already built" in surprises instead of re-implementing.` : "";
|
|
21686
|
-
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.';
|
|
21720
|
+
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.';
|
|
21721
|
+
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.";
|
|
21687
21722
|
let adSection = "";
|
|
21688
21723
|
try {
|
|
21689
21724
|
const allADs = await adapter2.getActiveDecisions();
|
|
@@ -21718,7 +21753,7 @@ ${entries}`;
|
|
|
21718
21753
|
formatModelRecommendation(result.task.buildHandoff?.effort ?? result.task.complexity)
|
|
21719
21754
|
) ?? "";
|
|
21720
21755
|
const gestaltNote = buildGestaltPreBuildDirective(caps) ?? "";
|
|
21721
|
-
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + verificationNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
21756
|
+
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + verificationNote + buildDisciplineNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
21722
21757
|
} catch (err) {
|
|
21723
21758
|
if (isNoHandoffError(err)) {
|
|
21724
21759
|
const lines = [
|
|
@@ -21836,9 +21871,11 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
21836
21871
|
tracker.mark("complete_format");
|
|
21837
21872
|
tracker.setStreamScope({ taskId: result.task.displayId ?? result.task.id, cycle: result.cycleNumber });
|
|
21838
21873
|
await tracker.recordStep("report_written");
|
|
21839
|
-
|
|
21840
|
-
|
|
21841
|
-
|
|
21874
|
+
if ((result.autoTriagedCount ?? 0) > 0) {
|
|
21875
|
+
await tracker.recordStep("issues_triaged", {
|
|
21876
|
+
metadata: { autoTriagedCount: result.autoTriagedCount }
|
|
21877
|
+
});
|
|
21878
|
+
}
|
|
21842
21879
|
if (resolvesLearnings && resolvesLearnings.length > 0 && adapter2.updateCycleLearningActionRef) {
|
|
21843
21880
|
for (const learningId of resolvesLearnings) {
|
|
21844
21881
|
try {
|
|
@@ -21847,9 +21884,11 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
21847
21884
|
}
|
|
21848
21885
|
}
|
|
21849
21886
|
}
|
|
21850
|
-
|
|
21851
|
-
|
|
21852
|
-
|
|
21887
|
+
if ((result.learningsLinkedCount ?? 0) > 0) {
|
|
21888
|
+
await tracker.recordStep("learnings", {
|
|
21889
|
+
metadata: { learningsLinkedCount: result.learningsLinkedCount }
|
|
21890
|
+
});
|
|
21891
|
+
}
|
|
21853
21892
|
await tracker.recordStep("moving-to-review", {
|
|
21854
21893
|
metadata: { status: result.task.status }
|
|
21855
21894
|
});
|
|
@@ -24548,7 +24587,12 @@ ${writeNote}
|
|
|
24548
24587
|
const envVars = {
|
|
24549
24588
|
PAPI_PROJECT_DIR: projectRoot,
|
|
24550
24589
|
PAPI_ADAPTER: "pg",
|
|
24551
|
-
DATABASE_URL: process.env.DATABASE_URL || "<YOUR_DATABASE_URL>"
|
|
24590
|
+
DATABASE_URL: process.env.DATABASE_URL || "<YOUR_DATABASE_URL>",
|
|
24591
|
+
// task-2508: pg/direct setups resolve caller identity from this var (the
|
|
24592
|
+
// hosted path uses the bearer token instead). Without it the FIRST
|
|
24593
|
+
// release hits the owner-gate with a null caller — ship it in the
|
|
24594
|
+
// template so fresh pg projects are owner-resolvable out of the box.
|
|
24595
|
+
PAPI_USER_ID: process.env.PAPI_USER_ID || "<YOUR_ACCOUNT_UUID>"
|
|
24552
24596
|
};
|
|
24553
24597
|
if (rootHash) {
|
|
24554
24598
|
envVars.PAPI_RESOLUTION_METHOD = "root_hash";
|
|
@@ -24577,7 +24621,8 @@ ${writeNote}
|
|
|
24577
24621
|
"## Next Steps",
|
|
24578
24622
|
"",
|
|
24579
24623
|
...process.env.DATABASE_URL ? ["1. **Restart your MCP client** to pick up the new config."] : [`1. **Set your DATABASE_URL** \u2014 replace \`<YOUR_DATABASE_URL>\` in \`${target.displayPath}\` with your Supabase **session pooler** connection string (port **5432**, not 6543 \u2014 the transaction pooler wedges on interrupted calls).`],
|
|
24580
|
-
"2. **
|
|
24624
|
+
...process.env.PAPI_USER_ID ? [] : ["2. **Set your PAPI_USER_ID** \u2014 replace `<YOUR_ACCOUNT_UUID>` with your account UUID (getpapi.ai \u2192 Settings \u2192 Account). Release and reviews use it to recognise you as the owner."],
|
|
24625
|
+
`${process.env.PAPI_USER_ID ? "2" : "3"}. **Run \`setup\`** \u2014 this scaffolds your project with a Product Brief, Active Decisions, and CLAUDE.md.`
|
|
24581
24626
|
].join("\n");
|
|
24582
24627
|
return textResponse(output2 + formatFilesToWriteSection(collector));
|
|
24583
24628
|
}
|
|
@@ -26093,7 +26138,7 @@ ${versionDrift}` : "";
|
|
|
26093
26138
|
let enrichmentNote = "";
|
|
26094
26139
|
const enrichmentCollector = new FileWriteCollector();
|
|
26095
26140
|
try {
|
|
26096
|
-
enrichmentNote = enrichClaudeMd(config2.projectRoot, healthResult.cycleNumber, config2.adapterType, enrichmentCollector);
|
|
26141
|
+
enrichmentNote = enrichClaudeMd(config2.projectRoot, healthResult.cycleNumber, config2.adapterType, enrichmentCollector, clientName);
|
|
26097
26142
|
} catch {
|
|
26098
26143
|
}
|
|
26099
26144
|
const enrichmentFilesSection = enrichmentCollector.isEmpty() ? "" : formatFilesToWriteSection(enrichmentCollector);
|
|
@@ -26154,7 +26199,8 @@ ${section}`;
|
|
|
26154
26199
|
}));
|
|
26155
26200
|
}
|
|
26156
26201
|
}
|
|
26157
|
-
function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector) {
|
|
26202
|
+
function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, clientName) {
|
|
26203
|
+
if (!shouldWriteClaudeMd(clientName)) return "";
|
|
26158
26204
|
if (adapterType === "proxy") {
|
|
26159
26205
|
const additions2 = [];
|
|
26160
26206
|
if (cycleNumber >= 6) additions2.push(CLAUDE_MD_TIER_1);
|
package/dist/prompts.js
CHANGED
|
@@ -66,7 +66,7 @@ REFERENCE DOCS
|
|
|
66
66
|
[Optional \u2014 paths to docs/ files that provide background context for this task. Include only when the task originated from research or scoping work and the doc contains context the builder will need beyond what is in this handoff. Omit this section entirely for tasks that don't need supplementary context.]
|
|
67
67
|
|
|
68
68
|
PRE-BUILD VERIFICATION
|
|
69
|
-
[List 2-5 specific file paths the builder should read BEFORE implementing to check if the functionality already exists. Derive these from FILES LIKELY TOUCHED \u2014 pick the files most likely to already contain the target functionality. If >80% of the scope is already implemented, the builder should report "already built" instead of re-implementing. Include this section for EVERY task \u2014 it prevents wasted build slots on already-shipped code.]
|
|
69
|
+
[List 2-5 specific file paths the builder should read BEFORE implementing to check if the functionality already exists. Derive these from FILES LIKELY TOUCHED \u2014 pick the files most likely to already contain the target functionality. ALSO mandate a docs sweep, not just file-existence (task-2161): name any docs the builder should check via doc_search or the docs index \u2014 a design/research/status:final doc or a prior task may already cover the work. If >80% of the scope is already implemented, the builder should report "already built" instead of re-implementing. Include this section for EVERY task \u2014 it prevents wasted build slots on already-shipped code.]
|
|
70
70
|
|
|
71
71
|
FILES LIKELY TOUCHED
|
|
72
72
|
[files]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.48",
|
|
4
4
|
"description": "PAPI MCP server \u2014 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",
|