@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.
@@ -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 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). If you ARE the owner on a local (pg) setup, set PAPI_USER_ID to your account UUID in .mcp.json; on the hosted/proxy setup your identity comes from your API key \u2014 check you are using YOUR key for YOUR project.`;
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 trackedDirty = modified.filter((p) => !untrackedSet.has(p));
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
- const driftChanged = startSha ? getFilesChangedBetween(config2.projectRoot, startSha, "HEAD") : changed;
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: `Approaches tried and rejected during the build, with WHY they failed. Example: "Tried using Supabase realtime subscriptions but Edge Functions can't hold persistent connections \u2014 switched to polling." Include whenever you abandoned an approach. Future builds and plans reference this to avoid repeating blind alleys.`
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
- await tracker.recordStep("issues_triaged", {
21840
- metadata: { autoTriagedCount: result.autoTriagedCount ?? 0 }
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
- await tracker.recordStep("learnings", {
21851
- metadata: { learningsLinkedCount: result.learningsLinkedCount ?? 0 }
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
- if (hasUncommittedChanges(config2.projectRoot, AUTO_WRITTEN_PATHS)) {
23645
- try {
23646
- const cleanup = stageDirAndCommit(
23647
- config2.projectRoot,
23648
- ".",
23649
- `chore(${taskId}): commit remaining changes before branch switch`
23650
- );
23651
- if (cleanup.committed) {
23652
- details.push("Committed remaining changes before branch switch.");
23653
- }
23654
- } catch {
23655
- details.push("Warning: uncommitted changes detected \u2014 branch switch may fail.");
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. **Run `setup`** \u2014 this scaffolds your project with a Product Brief, Active Decisions, and CLAUDE.md."
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.47",
4
- "description": "PAPI MCP server \u2014 AI-powered sprint planning, build execution, and strategy review for software projects",
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",