@papi-ai/server 0.7.47 → 0.7.49
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/backfill-cycle-metrics.js +16 -0
- package/dist/index.js +102 -34
- package/dist/prompts.js +1 -1
- package/package.json +2 -2
|
@@ -30,6 +30,7 @@ __export(git_exports, {
|
|
|
30
30
|
getCurrentBranch: () => getCurrentBranch,
|
|
31
31
|
getDocPathsTouchedOnBranch: () => getDocPathsTouchedOnBranch,
|
|
32
32
|
getFilesChangedBetween: () => getFilesChangedBetween,
|
|
33
|
+
getFilesChangedForTask: () => getFilesChangedForTask,
|
|
33
34
|
getFilesChangedFromBase: () => getFilesChangedFromBase,
|
|
34
35
|
getHeadCommitSha: () => getHeadCommitSha,
|
|
35
36
|
getHeadCommitSubject: () => getHeadCommitSubject,
|
|
@@ -980,6 +981,21 @@ function getFilesChangedBetween(cwd, fromRef, toRef) {
|
|
|
980
981
|
return [];
|
|
981
982
|
}
|
|
982
983
|
}
|
|
984
|
+
function getFilesChangedForTask(cwd, baseBranch, taskId) {
|
|
985
|
+
try {
|
|
986
|
+
const mergeBase = execFileSync("git", ["merge-base", baseBranch, "HEAD"], { cwd, encoding: "utf-8" }).trim();
|
|
987
|
+
const output = execFileSync(
|
|
988
|
+
"git",
|
|
989
|
+
["log", `${mergeBase}..HEAD`, "-E", `--grep=${taskId}($|[^0-9])`, "--pretty=format:", "--name-only"],
|
|
990
|
+
{ cwd, encoding: "utf-8" }
|
|
991
|
+
).trim();
|
|
992
|
+
if (!output) return [];
|
|
993
|
+
const files = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
994
|
+
return Array.from(new Set(files));
|
|
995
|
+
} catch {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
}
|
|
983
999
|
function getLastCommitFiles(cwd) {
|
|
984
1000
|
try {
|
|
985
1001
|
const output = execFileSync(
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ __export(git_exports, {
|
|
|
31
31
|
getCurrentBranch: () => getCurrentBranch,
|
|
32
32
|
getDocPathsTouchedOnBranch: () => getDocPathsTouchedOnBranch,
|
|
33
33
|
getFilesChangedBetween: () => getFilesChangedBetween,
|
|
34
|
+
getFilesChangedForTask: () => getFilesChangedForTask,
|
|
34
35
|
getFilesChangedFromBase: () => getFilesChangedFromBase,
|
|
35
36
|
getHeadCommitSha: () => getHeadCommitSha,
|
|
36
37
|
getHeadCommitSubject: () => getHeadCommitSubject,
|
|
@@ -981,6 +982,21 @@ function getFilesChangedBetween(cwd, fromRef, toRef) {
|
|
|
981
982
|
return [];
|
|
982
983
|
}
|
|
983
984
|
}
|
|
985
|
+
function getFilesChangedForTask(cwd, baseBranch, taskId) {
|
|
986
|
+
try {
|
|
987
|
+
const mergeBase = execFileSync("git", ["merge-base", baseBranch, "HEAD"], { cwd, encoding: "utf-8" }).trim();
|
|
988
|
+
const output = execFileSync(
|
|
989
|
+
"git",
|
|
990
|
+
["log", `${mergeBase}..HEAD`, "-E", `--grep=${taskId}($|[^0-9])`, "--pretty=format:", "--name-only"],
|
|
991
|
+
{ cwd, encoding: "utf-8" }
|
|
992
|
+
).trim();
|
|
993
|
+
if (!output) return [];
|
|
994
|
+
const files = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
995
|
+
return Array.from(new Set(files));
|
|
996
|
+
} catch {
|
|
997
|
+
return [];
|
|
998
|
+
}
|
|
999
|
+
}
|
|
984
1000
|
function getLastCommitFiles(cwd) {
|
|
985
1001
|
try {
|
|
986
1002
|
const output = execFileSync(
|
|
@@ -8714,7 +8730,7 @@ REFERENCE DOCS
|
|
|
8714
8730
|
[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
8731
|
|
|
8716
8732
|
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.]
|
|
8733
|
+
[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
8734
|
|
|
8719
8735
|
FILES LIKELY TOUCHED
|
|
8720
8736
|
[files]
|
|
@@ -10805,7 +10821,8 @@ async function resolveOwnerGate(adapter2, config2) {
|
|
|
10805
10821
|
enforced: true,
|
|
10806
10822
|
callerIsOwner: isProjectOwner(callerUserId, identity.ownerUserId),
|
|
10807
10823
|
callerUserId,
|
|
10808
|
-
ownerUserId: identity.ownerUserId
|
|
10824
|
+
ownerUserId: identity.ownerUserId,
|
|
10825
|
+
transport: "proxy"
|
|
10809
10826
|
};
|
|
10810
10827
|
} catch (err) {
|
|
10811
10828
|
return {
|
|
@@ -10813,7 +10830,8 @@ async function resolveOwnerGate(adapter2, config2) {
|
|
|
10813
10830
|
callerIsOwner: false,
|
|
10814
10831
|
callerUserId: config2.userId ?? null,
|
|
10815
10832
|
ownerUserId: null,
|
|
10816
|
-
resolutionError: err instanceof Error ? err.message : String(err)
|
|
10833
|
+
resolutionError: err instanceof Error ? err.message : String(err),
|
|
10834
|
+
transport: "proxy"
|
|
10817
10835
|
};
|
|
10818
10836
|
}
|
|
10819
10837
|
}
|
|
@@ -10831,14 +10849,16 @@ async function resolveOwnerGate(adapter2, config2) {
|
|
|
10831
10849
|
callerIsOwner: isProjectOwner(callerUserId, ownerUserId),
|
|
10832
10850
|
callerUserId,
|
|
10833
10851
|
ownerUserId,
|
|
10834
|
-
...resolutionError !== void 0 ? { resolutionError } : {}
|
|
10852
|
+
...resolutionError !== void 0 ? { resolutionError } : {},
|
|
10853
|
+
transport: "pg"
|
|
10835
10854
|
};
|
|
10836
10855
|
}
|
|
10837
10856
|
return {
|
|
10838
10857
|
enforced: false,
|
|
10839
10858
|
callerIsOwner: true,
|
|
10840
10859
|
callerUserId: config2.userId ?? null,
|
|
10841
|
-
ownerUserId: null
|
|
10860
|
+
ownerUserId: null,
|
|
10861
|
+
transport: "md"
|
|
10842
10862
|
};
|
|
10843
10863
|
}
|
|
10844
10864
|
|
|
@@ -19414,10 +19434,22 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
19414
19434
|
const resolutionNote = gate.resolutionError ? `
|
|
19415
19435
|
|
|
19416
19436
|
Identity resolution failed (${gate.resolutionError}) \u2014 the gate fails closed. Retry once connectivity is restored.` : "";
|
|
19437
|
+
if (gate.transport === "pg" && gate.callerUserId === null) {
|
|
19438
|
+
return errorResponse(
|
|
19439
|
+
`Release needs to know who you are, and this setup has no PAPI_USER_ID yet.
|
|
19440
|
+
|
|
19441
|
+
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):
|
|
19442
|
+
|
|
19443
|
+
"env": { "PAPI_USER_ID": "<your-account-uuid>", ... }
|
|
19444
|
+
|
|
19445
|
+
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.)`
|
|
19446
|
+
);
|
|
19447
|
+
}
|
|
19417
19448
|
tracker.mark("contributor-role-gate");
|
|
19418
19449
|
const callerRole = adapter2.getContributorRole && gate.callerUserId ? await adapter2.getContributorRole(gate.callerUserId).catch(() => null) : null;
|
|
19419
19450
|
if (callerRole !== "editor") {
|
|
19420
|
-
const
|
|
19451
|
+
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.`;
|
|
19452
|
+
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
19453
|
return errorResponse(
|
|
19422
19454
|
`Release to ${productionBaseBranch} is restricted to the project owner.
|
|
19423
19455
|
|
|
@@ -19794,7 +19826,10 @@ function triggerSurfaceHitsOnBranch(projectRoot, baseRef = "origin/main") {
|
|
|
19794
19826
|
function selectAutostashPaths(modified, untracked) {
|
|
19795
19827
|
const untrackedSet = new Set(untracked);
|
|
19796
19828
|
const isDocsPath = (p) => p === "docs" || p.startsWith("docs/");
|
|
19797
|
-
const
|
|
19829
|
+
const isUntrackedDirEntry = (p) => p.endsWith("/") && untracked.some((u) => u.startsWith(p));
|
|
19830
|
+
const trackedDirty = modified.filter(
|
|
19831
|
+
(p) => !untrackedSet.has(p) && !isUntrackedDirEntry(p)
|
|
19832
|
+
);
|
|
19798
19833
|
const preservedDocs = untracked.filter(isDocsPath);
|
|
19799
19834
|
const stashableUntracked = untracked.filter((p) => !isDocsPath(p));
|
|
19800
19835
|
return { toStash: [...trackedDirty, ...stashableUntracked], preservedDocs };
|
|
@@ -20114,6 +20149,17 @@ async function describeTask(adapter2, taskId) {
|
|
|
20114
20149
|
function shouldBlockDirtyBranchSwitch(opts) {
|
|
20115
20150
|
return opts.trackedDirtyCount > 0 && opts.currentBranch !== opts.baseBranch && opts.currentBranch !== opts.featureBranch;
|
|
20116
20151
|
}
|
|
20152
|
+
async function persistBranchName(adapter2, taskId, branch) {
|
|
20153
|
+
try {
|
|
20154
|
+
await adapter2.updateTask(taskId, { branchName: branch });
|
|
20155
|
+
return true;
|
|
20156
|
+
} catch (err) {
|
|
20157
|
+
console.error(
|
|
20158
|
+
`[build] branch_name persist skipped for ${taskId} (non-fatal): ` + (err instanceof Error ? err.message : String(err))
|
|
20159
|
+
);
|
|
20160
|
+
return false;
|
|
20161
|
+
}
|
|
20162
|
+
}
|
|
20117
20163
|
async function startBuild(adapter2, config2, taskId, options = {}, clientName) {
|
|
20118
20164
|
const task = await adapter2.getTask(taskId);
|
|
20119
20165
|
if (!task) {
|
|
@@ -20330,6 +20376,10 @@ async function startBuild(adapter2, config2, taskId, options = {}, clientName) {
|
|
|
20330
20376
|
if (task.status !== "In Progress") {
|
|
20331
20377
|
await adapter2.updateTaskStatus(taskId, "In Progress");
|
|
20332
20378
|
}
|
|
20379
|
+
const startedBranch = taskBranchMap.get(taskId);
|
|
20380
|
+
if (startedBranch) {
|
|
20381
|
+
await persistBranchName(adapter2, taskId, startedBranch);
|
|
20382
|
+
}
|
|
20333
20383
|
buildStartTimes.set(taskId, (/* @__PURE__ */ new Date()).toISOString());
|
|
20334
20384
|
if (isGitAvailable() && isGitRepo(config2.projectRoot)) {
|
|
20335
20385
|
const startSha = getHeadCommitSha(config2.projectRoot);
|
|
@@ -20737,7 +20787,13 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
20737
20787
|
const changed = getFilesChangedFromBase(config2.projectRoot, baseBranch);
|
|
20738
20788
|
if (changed.length > 0) report.filesChanged = changed;
|
|
20739
20789
|
const startSha = taskStartShaMap.get(taskId);
|
|
20740
|
-
|
|
20790
|
+
let driftChanged;
|
|
20791
|
+
if (startSha) {
|
|
20792
|
+
driftChanged = getFilesChangedBetween(config2.projectRoot, startSha, "HEAD");
|
|
20793
|
+
} else {
|
|
20794
|
+
const taskScoped = getFilesChangedForTask(config2.projectRoot, baseBranch, taskId);
|
|
20795
|
+
driftChanged = taskScoped.length > 0 ? taskScoped : changed;
|
|
20796
|
+
}
|
|
20741
20797
|
const drift = computeScopeDriftSignal(task.buildHandoff?.filesLikelyTouched, driftChanged);
|
|
20742
20798
|
if (drift) report.scopeDriftSignal = drift;
|
|
20743
20799
|
}
|
|
@@ -21438,7 +21494,7 @@ var buildExecuteTool = {
|
|
|
21438
21494
|
},
|
|
21439
21495
|
dead_ends: {
|
|
21440
21496
|
type: "string",
|
|
21441
|
-
description: `
|
|
21497
|
+
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
21498
|
},
|
|
21443
21499
|
brief_implications: {
|
|
21444
21500
|
type: "array",
|
|
@@ -21683,7 +21739,8 @@ async function handleBuildExecute(adapter2, config2, args, clientName) {
|
|
|
21683
21739
|
**PRE-BUILD VERIFICATION:** Before writing any code, read these files and check if the functionality already exists:
|
|
21684
21740
|
${verificationFiles.map((f) => `- ${f}`).join("\n")}
|
|
21685
21741
|
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.';
|
|
21742
|
+
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.';
|
|
21743
|
+
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
21744
|
let adSection = "";
|
|
21688
21745
|
try {
|
|
21689
21746
|
const allADs = await adapter2.getActiveDecisions();
|
|
@@ -21718,7 +21775,7 @@ ${entries}`;
|
|
|
21718
21775
|
formatModelRecommendation(result.task.buildHandoff?.effort ?? result.task.complexity)
|
|
21719
21776
|
) ?? "";
|
|
21720
21777
|
const gestaltNote = buildGestaltPreBuildDirective(caps) ?? "";
|
|
21721
|
-
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + verificationNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
21778
|
+
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + verificationNote + buildDisciplineNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
21722
21779
|
} catch (err) {
|
|
21723
21780
|
if (isNoHandoffError(err)) {
|
|
21724
21781
|
const lines = [
|
|
@@ -21836,9 +21893,11 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
21836
21893
|
tracker.mark("complete_format");
|
|
21837
21894
|
tracker.setStreamScope({ taskId: result.task.displayId ?? result.task.id, cycle: result.cycleNumber });
|
|
21838
21895
|
await tracker.recordStep("report_written");
|
|
21839
|
-
|
|
21840
|
-
|
|
21841
|
-
|
|
21896
|
+
if ((result.autoTriagedCount ?? 0) > 0) {
|
|
21897
|
+
await tracker.recordStep("issues_triaged", {
|
|
21898
|
+
metadata: { autoTriagedCount: result.autoTriagedCount }
|
|
21899
|
+
});
|
|
21900
|
+
}
|
|
21842
21901
|
if (resolvesLearnings && resolvesLearnings.length > 0 && adapter2.updateCycleLearningActionRef) {
|
|
21843
21902
|
for (const learningId of resolvesLearnings) {
|
|
21844
21903
|
try {
|
|
@@ -21847,9 +21906,11 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
21847
21906
|
}
|
|
21848
21907
|
}
|
|
21849
21908
|
}
|
|
21850
|
-
|
|
21851
|
-
|
|
21852
|
-
|
|
21909
|
+
if ((result.learningsLinkedCount ?? 0) > 0) {
|
|
21910
|
+
await tracker.recordStep("learnings", {
|
|
21911
|
+
metadata: { learningsLinkedCount: result.learningsLinkedCount }
|
|
21912
|
+
});
|
|
21913
|
+
}
|
|
21853
21914
|
await tracker.recordStep("moving-to-review", {
|
|
21854
21915
|
metadata: { status: result.task.status }
|
|
21855
21916
|
});
|
|
@@ -23641,19 +23702,19 @@ function mergeAfterAccept(config2, taskId) {
|
|
|
23641
23702
|
return { merged: false, skipped: false, message: `PR merge failed: ${merge.message}`, details };
|
|
23642
23703
|
}
|
|
23643
23704
|
details.push(merge.message);
|
|
23644
|
-
|
|
23645
|
-
|
|
23646
|
-
|
|
23647
|
-
|
|
23648
|
-
|
|
23649
|
-
|
|
23650
|
-
|
|
23651
|
-
|
|
23652
|
-
|
|
23653
|
-
|
|
23654
|
-
|
|
23655
|
-
details
|
|
23656
|
-
}
|
|
23705
|
+
const trackedDirty = getTrackedModifiedFiles(config2.projectRoot);
|
|
23706
|
+
if (trackedDirty.length > 0) {
|
|
23707
|
+
const shown = trackedDirty.slice(0, 5).join(", ");
|
|
23708
|
+
const more = trackedDirty.length > 5 ? ` (+${trackedDirty.length - 5} more)` : "";
|
|
23709
|
+
details.push(
|
|
23710
|
+
`Local branch switch skipped: the working tree has uncommitted tracked changes on '${featureBranch}' (possibly another session's in-progress work) that a checkout to '${baseBranch}' would revert. Commit or stash them, then run \`git checkout ${baseBranch} && git pull\` to finish. Dirty: ${shown}${more}.`
|
|
23711
|
+
);
|
|
23712
|
+
return {
|
|
23713
|
+
merged: true,
|
|
23714
|
+
skipped: false,
|
|
23715
|
+
message: `PR merged: \`${featureBranch}\` \u2192 \`${baseBranch}\`. Local switch skipped \u2014 tracked-dirty tree preserved.`,
|
|
23716
|
+
details
|
|
23717
|
+
};
|
|
23657
23718
|
}
|
|
23658
23719
|
const checkout = checkoutBranch(config2.projectRoot, baseBranch);
|
|
23659
23720
|
if (checkout.success) {
|
|
@@ -24548,7 +24609,12 @@ ${writeNote}
|
|
|
24548
24609
|
const envVars = {
|
|
24549
24610
|
PAPI_PROJECT_DIR: projectRoot,
|
|
24550
24611
|
PAPI_ADAPTER: "pg",
|
|
24551
|
-
DATABASE_URL: process.env.DATABASE_URL || "<YOUR_DATABASE_URL>"
|
|
24612
|
+
DATABASE_URL: process.env.DATABASE_URL || "<YOUR_DATABASE_URL>",
|
|
24613
|
+
// task-2508: pg/direct setups resolve caller identity from this var (the
|
|
24614
|
+
// hosted path uses the bearer token instead). Without it the FIRST
|
|
24615
|
+
// release hits the owner-gate with a null caller — ship it in the
|
|
24616
|
+
// template so fresh pg projects are owner-resolvable out of the box.
|
|
24617
|
+
PAPI_USER_ID: process.env.PAPI_USER_ID || "<YOUR_ACCOUNT_UUID>"
|
|
24552
24618
|
};
|
|
24553
24619
|
if (rootHash) {
|
|
24554
24620
|
envVars.PAPI_RESOLUTION_METHOD = "root_hash";
|
|
@@ -24577,7 +24643,8 @@ ${writeNote}
|
|
|
24577
24643
|
"## Next Steps",
|
|
24578
24644
|
"",
|
|
24579
24645
|
...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. **
|
|
24646
|
+
...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."],
|
|
24647
|
+
`${process.env.PAPI_USER_ID ? "2" : "3"}. **Run \`setup\`** \u2014 this scaffolds your project with a Product Brief, Active Decisions, and CLAUDE.md.`
|
|
24581
24648
|
].join("\n");
|
|
24582
24649
|
return textResponse(output2 + formatFilesToWriteSection(collector));
|
|
24583
24650
|
}
|
|
@@ -26093,7 +26160,7 @@ ${versionDrift}` : "";
|
|
|
26093
26160
|
let enrichmentNote = "";
|
|
26094
26161
|
const enrichmentCollector = new FileWriteCollector();
|
|
26095
26162
|
try {
|
|
26096
|
-
enrichmentNote = enrichClaudeMd(config2.projectRoot, healthResult.cycleNumber, config2.adapterType, enrichmentCollector);
|
|
26163
|
+
enrichmentNote = enrichClaudeMd(config2.projectRoot, healthResult.cycleNumber, config2.adapterType, enrichmentCollector, clientName);
|
|
26097
26164
|
} catch {
|
|
26098
26165
|
}
|
|
26099
26166
|
const enrichmentFilesSection = enrichmentCollector.isEmpty() ? "" : formatFilesToWriteSection(enrichmentCollector);
|
|
@@ -26154,7 +26221,8 @@ ${section}`;
|
|
|
26154
26221
|
}));
|
|
26155
26222
|
}
|
|
26156
26223
|
}
|
|
26157
|
-
function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector) {
|
|
26224
|
+
function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, clientName) {
|
|
26225
|
+
if (!shouldWriteClaudeMd(clientName)) return "";
|
|
26158
26226
|
if (adapterType === "proxy") {
|
|
26159
26227
|
const additions2 = [];
|
|
26160
26228
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "PAPI MCP server
|
|
3
|
+
"version": "0.7.49",
|
|
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",
|
|
7
7
|
"type": "module",
|