@papi-ai/server 0.7.9 → 0.7.11

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 CHANGED
@@ -6724,14 +6724,21 @@ ${newParts.join("\n")}` : newParts.join("\n");
6724
6724
  // Board (Tasks)
6725
6725
  // -------------------------------------------------------------------------
6726
6726
  async queryBoard(options) {
6727
- if (!options) {
6728
- const rows2 = await this.sql`
6729
- SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, state_history, build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source, opportunity, updated_at
6730
- FROM cycle_tasks
6731
- WHERE project_id = ${this.projectId}
6732
- ORDER BY display_id
6733
- LIMIT 2000 -- hard ceiling; single project task count won't approach this
6734
- `;
6727
+ const compact = options?.compact === true;
6728
+ if (!options || Object.keys(options).length === 1 && compact) {
6729
+ const rows2 = compact ? await this.sql`
6730
+ SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, task_type, maturity, stage_id, doc_ref, source, opportunity, updated_at
6731
+ FROM cycle_tasks
6732
+ WHERE project_id = ${this.projectId}
6733
+ ORDER BY display_id
6734
+ LIMIT 2000
6735
+ ` : await this.sql`
6736
+ SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, state_history, build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source, opportunity, updated_at
6737
+ FROM cycle_tasks
6738
+ WHERE project_id = ${this.projectId}
6739
+ ORDER BY display_id
6740
+ LIMIT 2000 -- hard ceiling; single project task count won't approach this
6741
+ `;
6735
6742
  return rows2.map(rowToTask);
6736
6743
  }
6737
6744
  const conditions = [
@@ -6766,11 +6773,15 @@ ${newParts.join("\n")}` : newParts.join("\n");
6766
6773
  for (let i = 1; i < conditions.length; i++) {
6767
6774
  where = this.sql`${where} AND ${conditions[i]}`;
6768
6775
  }
6769
- const rows = await this.sql`
6770
- SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, state_history, build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source, opportunity, updated_at
6771
- FROM cycle_tasks WHERE ${where} ORDER BY display_id
6772
- LIMIT 2000 -- matches no-options path ceiling
6773
- `;
6776
+ const rows = compact ? await this.sql`
6777
+ SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, task_type, maturity, stage_id, doc_ref, source, opportunity, updated_at
6778
+ FROM cycle_tasks WHERE ${where} ORDER BY display_id
6779
+ LIMIT 2000
6780
+ ` : await this.sql`
6781
+ SELECT id, project_id, display_id, title, status, priority, complexity, module, epic, phase, owner, reviewed, cycle, created_cycle, created_at, why, depends_on, notes, closure_reason, state_history, build_handoff, build_report, task_type, maturity, stage_id, doc_ref, source, opportunity, updated_at
6782
+ FROM cycle_tasks WHERE ${where} ORDER BY display_id
6783
+ LIMIT 2000 -- matches no-options path ceiling
6784
+ `;
6774
6785
  return rows.map(rowToTask);
6775
6786
  }
6776
6787
  async getTask(id) {
@@ -7822,7 +7833,7 @@ ${r.content}` + (r.carry_forward ? `
7822
7833
  const [row] = await this.sql`
7823
7834
  INSERT INTO bug_reports (project_id, user_id, description, diagnostics, status)
7824
7835
  VALUES (
7825
- ${report.projectId},
7836
+ ${this.projectId},
7826
7837
  ${report.userId ?? null},
7827
7838
  ${report.description},
7828
7839
  ${JSON.stringify(report.diagnostics)},
@@ -7832,7 +7843,7 @@ ${r.content}` + (r.carry_forward ? `
7832
7843
  `;
7833
7844
  return {
7834
7845
  id: row.id,
7835
- projectId: report.projectId,
7846
+ projectId: this.projectId,
7836
7847
  userId: report.userId,
7837
7848
  description: report.description,
7838
7849
  diagnostics: report.diagnostics,
@@ -10495,6 +10506,7 @@ Standard planning cycle with full board review.
10495
10506
  - Add a \`boardCorrections\` entry for the dependent task with \`updates.dependsOn\` set to the comma-separated upstream IDs \u2014 this persists the dependency so the builder's runtime can reuse the upstream branch.
10496
10507
  - Keep the SCOPE sections independent (each task still has its own deliverable) but note the ordering in "Why now" \u2014 e.g. "depends on task-123 completing the adapter method".
10497
10508
  Do NOT invent dependencies where tasks merely share a module \u2014 only real build-order coupling counts. Linear chains only \u2014 do not attempt to resolve multi-level graphs. When in doubt, omit the dependency and let the builder discover it.
10509
+ **Dependency Chain section (Part 1 markdown):** When intra-cycle dependencies are detected, include a visible **## Dependency Chain** section in Part 1 markdown immediately before the first BUILD HANDOFF block. List each dependency as an arrow chain with a brief reason: \`task-A \u2192 task-B (B calls the adapter method A creates)\`. Then show the full recommended build sequence for all cycle tasks, including standalone tasks: e.g. \`Build order: task-A \u2192 task-B; task-C standalone; task-D standalone\`. Flag circular dependencies with \u26A0\uFE0F and a note. Omit this section entirely when no intra-cycle dependencies exist \u2014 do not include an empty section.
10498
10510
  **Security section guidance:** Each handoff includes a SECURITY CONSIDERATIONS section. Populate it when the task involves: data exposure risks (PII, secrets in logs/storage), secrets or credentials handling (API keys, tokens, env vars), auth/access control changes, or dependency security risks (new packages, version changes). For pure refactoring, documentation, prompt-text, or UI-only tasks, write "None \u2014 no security-relevant changes".
10499
10511
  **Estimation calibration:** Estimate **XS** for: copy/text-only changes, single string replacements, config tweaks, and any task where the scope is "change words in an existing file" with no logic changes. Estimate **S** for: wiring existing adapter methods, adding API routes following established patterns, modifying prompts, or documentation-only changes. Default to S for pattern-following work. Only use M when genuine new architecture, new DB tables, or multi-file architectural changes are needed. Historical data shows systematic over-estimation (198 over vs 8 under out of 528 tasks) \u2014 when in doubt, estimate smaller. If an "Estimation Calibration (Historical)" section is provided in the context below, use its data to adjust your estimates \u2014 it shows how often each estimated size matched the actual effort. Pay special attention to systematic over/under-estimation patterns (e.g. if M\u2192S happens frequently, estimate S instead of M for similar work).
10500
10512
  **Reference docs:** If a task's notes include a \`Reference:\` path (e.g. \`Reference: docs/architecture/papi-brain-v1.md\`), include a REFERENCE DOCS section in the BUILD HANDOFF with those paths. This tells the builder to read the referenced doc for background context before implementing. Do NOT omit or summarise the reference \u2014 pass it through so the builder can access the full document. Only tasks with explicit \`Reference:\` paths in their notes should have this section.
@@ -10589,6 +10601,38 @@ var PLAN_FRAGMENT_SPIKE = `
10589
10601
  - **DONE CONDITION:** "Question answered OR time-box hit, whichever comes first."
10590
10602
  - Keep SCOPE BOUNDARY, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
10591
10603
  - Spikes should be estimated conservatively: XS or S. If a spike needs M+ effort, it's not a spike \u2014 reclassify as a research task.`;
10604
+ var PLAN_FRAGMENT_DESIGN_BRIEF = `
10605
+ **Design brief task detection:** When a task's task type is "design-brief", generate a DESIGN BRIEF handoff. Replace the standard SCOPE (DO THIS) section with these type-specific sections:
10606
+ - AUDIENCE: Who this design is for \u2014 persona and context of use (e.g. "non-technical Owner, first dashboard visit")
10607
+ - BRAND CONSTRAINTS: Palette, typography, tone \u2014 pull from \`.impeccable.md\` and \`docs/design/brand-system.md\` if present. If neither exists, state "No brand doc \u2014 Owner should define constraints before starting."
10608
+ - DELIVERABLE FORMAT: What the output looks like \u2014 Claude Design handoff package / annotated mockup / style spec. Be specific so the person doing the work knows what "done" means.
10609
+ - REVIEW POINTS: What the Owner must approve before the design is considered done (e.g. layout, copy, colour, imagery).
10610
+ Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION sections as normal.
10611
+ Add to ACCEPTANCE CRITERIA: "[ ] Deliverable format confirmed with Owner before starting" and "[ ] Design output is self-contained \u2014 includes enough context for a developer to implement without further clarification."`;
10612
+ var PLAN_FRAGMENT_RESEARCH_BRIEF = `
10613
+ **Research brief task detection:** When a task's task type is "research-brief", generate a RESEARCH BRIEF handoff. Replace the standard SCOPE (DO THIS) section with:
10614
+ - GOAL: The specific question this research answers \u2014 one sentence, phrased as a question (e.g. "What onboarding patterns do our top 3 competitors use?")
10615
+ - TIME-BOX: Maximum effort allowed \u2014 XS or S. Stop when the time-box is hit and report what was found, even if incomplete.
10616
+ - OUTPUT: Where findings land \u2014 a doc at \`docs/research/[topic]-findings.md\` or inline in the build report. State the path.
10617
+ - FOLLOW-UP PROTOCOL: Do NOT submit follow-up backlog tasks until the Owner reviews and confirms the findings are actionable.
10618
+ Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
10619
+ Add to ACCEPTANCE CRITERIA: "[ ] Question answered OR time-box hit \u2014 whichever comes first" and "[ ] Findings doc saved before any follow-up tasks are submitted."`;
10620
+ var PLAN_FRAGMENT_MARKETING_BRIEF = `
10621
+ **Marketing brief task detection:** When a task's task type is "marketing-brief", generate a MARKETING BRIEF handoff. Replace the standard SCOPE (DO THIS) section with:
10622
+ - AUDIENCE: Who this marketing content targets \u2014 persona, awareness level, channel context (e.g. "cold Discord visitor, zero PAPI context")
10623
+ - CHANNEL: Where this content lives \u2014 Discord, landing page, email, social, etc.
10624
+ - MESSAGE FRAME: The core message to land \u2014 one sentence. What does the reader need to believe after seeing this? (e.g. "PAPI makes AI-assisted building systematic, not chaotic.")
10625
+ - SUCCESS SIGNAL: How the Owner knows the content worked \u2014 clicks, signups, replies, saves, DMs. Be specific.
10626
+ Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
10627
+ Add to ACCEPTANCE CRITERIA: "[ ] Message Frame confirmed with Owner before drafting" and "[ ] Final content reviewed by Owner before publishing."`;
10628
+ var PLAN_FRAGMENT_OPS_BRIEF = `
10629
+ **Ops brief task detection:** When a task's task type is "ops-brief", generate an OPS BRIEF handoff. Replace the standard SCOPE (DO THIS) section with:
10630
+ - SYSTEM: Which system or service this ops task touches \u2014 Vercel, Railway, Supabase, GitHub Actions, DNS, etc.
10631
+ - RISK: What could go wrong \u2014 data loss, downtime, broken deployments. Include estimated blast radius (e.g. "affects all authenticated users").
10632
+ - ROLLBACK PLAN: Exact steps to undo the change if something breaks. Must be specific enough to execute under pressure.
10633
+ - DONE CONDITION: The specific observable state that confirms the task is complete \u2014 a health check URL, a metric, a log line, a manual verification step.
10634
+ Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
10635
+ Add to ACCEPTANCE CRITERIA: "[ ] Rollback plan confirmed before executing" and "[ ] Done condition verified after completing."`;
10592
10636
  var PLAN_FRAGMENT_UI = `
10593
10637
  **UI/visual task detection:** Apply these additions ONLY to tasks whose PRIMARY scope is frontend visual work \u2014 the task's main deliverable must be a UI change, new component, visual design, or page. Do NOT apply to backend tasks, DB migrations, or prompt/config changes that merely mention a dashboard or page in passing. Signal: the task would fail if no .tsx/.css files were changed. If uncertain, skip the UI additions.
10594
10638
  When a task IS a UI task (primary scope is visual/frontend):
@@ -10705,6 +10749,10 @@ Standard planning cycle with full board review.
10705
10749
  if (flags.hasIdeaTasks) parts.push(PLAN_FRAGMENT_IDEA);
10706
10750
  if (flags.hasSpikeTasks) parts.push(PLAN_FRAGMENT_SPIKE);
10707
10751
  if (flags.hasTaskTasks) parts.push(PLAN_FRAGMENT_TASK);
10752
+ if (flags.hasDesignBriefTasks) parts.push(PLAN_FRAGMENT_DESIGN_BRIEF);
10753
+ if (flags.hasResearchBriefTasks) parts.push(PLAN_FRAGMENT_RESEARCH_BRIEF);
10754
+ if (flags.hasMarketingBriefTasks) parts.push(PLAN_FRAGMENT_MARKETING_BRIEF);
10755
+ if (flags.hasOpsBriefTasks) parts.push(PLAN_FRAGMENT_OPS_BRIEF);
10708
10756
  if (flags.hasUITasks) parts.push(PLAN_FRAGMENT_UI);
10709
10757
  parts.push(`
10710
10758
  11. **New Tasks (max 3 per cycle)** \u2014 Actively mine the Recent Build Reports for task candidates. For each report, check:
@@ -11961,6 +12009,10 @@ function detectBoardFlags(tasks) {
11961
12009
  let hasSpikeTasks = false;
11962
12010
  let hasTaskTasks = false;
11963
12011
  let hasUITasks = false;
12012
+ let hasDesignBriefTasks = false;
12013
+ let hasResearchBriefTasks = false;
12014
+ let hasMarketingBriefTasks = false;
12015
+ let hasOpsBriefTasks = false;
11964
12016
  const uiKeywords = /\b(visual|design|UI|styling|refresh|frontend|landing page|hero|carousel|theme|layout|cockpit|dashboard|page)\b/i;
11965
12017
  for (const t of tasks) {
11966
12018
  if (t.taskType === "bug" || /^(Bug:|Fix:)/i.test(t.title)) hasBugTasks = true;
@@ -11968,9 +12020,13 @@ function detectBoardFlags(tasks) {
11968
12020
  if (t.taskType === "idea") hasIdeaTasks = true;
11969
12021
  if (t.taskType === "spike" || /^Spike:/i.test(t.title)) hasSpikeTasks = true;
11970
12022
  if (t.taskType === "task") hasTaskTasks = true;
12023
+ if (t.taskType === "design-brief") hasDesignBriefTasks = true;
12024
+ if (t.taskType === "research-brief") hasResearchBriefTasks = true;
12025
+ if (t.taskType === "marketing-brief") hasMarketingBriefTasks = true;
12026
+ if (t.taskType === "ops-brief") hasOpsBriefTasks = true;
11971
12027
  if (uiKeywords.test(t.title) || uiKeywords.test(t.notes ?? "")) hasUITasks = true;
11972
12028
  }
11973
- return { hasBugTasks, hasResearchTasks, hasIdeaTasks, hasSpikeTasks, hasTaskTasks, hasUITasks };
12029
+ return { hasBugTasks, hasResearchTasks, hasIdeaTasks, hasSpikeTasks, hasTaskTasks, hasUITasks, hasDesignBriefTasks, hasResearchBriefTasks, hasMarketingBriefTasks, hasOpsBriefTasks };
11974
12030
  }
11975
12031
  function detectBoardFlagsFromText(boardText) {
11976
12032
  return {
@@ -11979,7 +12035,11 @@ function detectBoardFlagsFromText(boardText) {
11979
12035
  hasIdeaTasks: /\bidea\b/i.test(boardText),
11980
12036
  hasSpikeTasks: /\b(spike|Spike:)\b/i.test(boardText),
11981
12037
  hasTaskTasks: /\btask\b/i.test(boardText),
11982
- hasUITasks: /\b(visual|design|UI|styling|refresh|frontend|landing page|hero|carousel|theme|layout|cockpit|dashboard|page)\b/i.test(boardText)
12038
+ hasUITasks: /\b(visual|design|UI|styling|refresh|frontend|landing page|hero|carousel|theme|layout|cockpit|dashboard|page)\b/i.test(boardText),
12039
+ hasDesignBriefTasks: /\bdesign-brief\b/i.test(boardText),
12040
+ hasResearchBriefTasks: /\bresearch-brief\b/i.test(boardText),
12041
+ hasMarketingBriefTasks: /\bmarketing-brief\b/i.test(boardText),
12042
+ hasOpsBriefTasks: /\bops-brief\b/i.test(boardText)
11983
12043
  };
11984
12044
  }
11985
12045
  function hashSection(content) {
@@ -12125,7 +12185,7 @@ async function assembleContext(adapter2, mode, _config, filters, focus) {
12125
12185
  ]),
12126
12186
  adapter2.searchDocs?.({ status: "active", limit: 5 }),
12127
12187
  adapter2.getCycleLog(5),
12128
- adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready"] }),
12188
+ adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready"], compact: true }),
12129
12189
  Promise.resolve(leanBuildReports.slice(0, 10)),
12130
12190
  adapter2.getContextHashes?.(health.totalCycles) ?? Promise.resolve(null)
12131
12191
  ]);
@@ -12235,7 +12295,7 @@ ${lines.join("\n")}`;
12235
12295
  adapter2.getActiveDecisions(),
12236
12296
  adapter2.getBuildReportsSince(health.totalCycles ?? 0),
12237
12297
  adapter2.getCycleLog(3),
12238
- adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Blocked"], contextTier: 2 }),
12298
+ adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Blocked"], contextTier: 2, compact: true }),
12239
12299
  adapter2.readCycleMetrics(),
12240
12300
  adapter2.getRecentReviews(5),
12241
12301
  adapter2.readPhases(),
@@ -12259,7 +12319,7 @@ ${lines.join("\n")}`;
12259
12319
  adapter2.getPendingRecommendations(),
12260
12320
  assembleDiscoveryCanvasText(adapter2),
12261
12321
  assembleTaskComments(adapter2),
12262
- adapter2.searchDocs?.({ status: "active", limit: 5 }),
12322
+ adapter2.searchDocs?.({ status: "active", limit: 15 }),
12263
12323
  adapter2.getContextHashes?.(health.totalCycles) ?? Promise.resolve(null)
12264
12324
  ]);
12265
12325
  timings["parallelAdvisory"] = t();
@@ -12287,10 +12347,31 @@ ${lines.join("\n")}`;
12287
12347
  const taskCommentsTextFull = taskCommentsResultFull.status === "fulfilled" ? taskCommentsResultFull.value : void 0;
12288
12348
  let registeredDocsTextFull;
12289
12349
  if (docsResultFull.status === "fulfilled" && docsResultFull.value && docsResultFull.value.length > 0) {
12290
- const docs = docsResultFull.value;
12291
- const lines = docs.map((d) => `- **${d.title}** (${d.type}) \u2014 ${d.summary}`);
12292
- registeredDocsTextFull = `${docs.length} active research doc(s):
12350
+ const allDocs = docsResultFull.value;
12351
+ const taskModules = new Set(plannerTasks.map((t2) => t2.module?.toLowerCase()).filter(Boolean));
12352
+ const taskEpics = new Set(plannerTasks.map((t2) => t2.epic?.toLowerCase()).filter(Boolean));
12353
+ const HIGH_VALUE_TYPES = /* @__PURE__ */ new Set(["architecture", "guide", "research"]);
12354
+ const scored = allDocs.map((d) => {
12355
+ let score = 0;
12356
+ const docTags = d.tags.map((tag) => tag.toLowerCase());
12357
+ const docType = d.type?.toLowerCase() ?? "";
12358
+ for (const tag of docTags) {
12359
+ if (taskModules.has(tag) || taskEpics.has(tag)) score += 2;
12360
+ }
12361
+ if (HIGH_VALUE_TYPES.has(docType)) score += 1;
12362
+ if (d.actions?.some((a) => a.status === "pending")) score += 3;
12363
+ const age = health.totalCycles - (d.cycleUpdated ?? d.cycleCreated ?? 0);
12364
+ if (age <= 3) score += 1;
12365
+ return { doc: d, score };
12366
+ });
12367
+ scored.sort((a, b2) => b2.score - a.score);
12368
+ const selected = scored.slice(0, 5);
12369
+ const lines = selected.map(({ doc, score }) => `- **${doc.title}** (${doc.type}) \u2014 ${doc.summary}`);
12370
+ registeredDocsTextFull = `${selected.length} active research doc(s):
12293
12371
  ${lines.join("\n")}`;
12372
+ const logLines = selected.map(({ doc, score }) => ` ${doc.title} [score=${score}]`).join("\n");
12373
+ console.error(`[plan-perf] doc intelligence: selected ${selected.length}/${allDocs.length} docs by relevance:
12374
+ ${logLines}`);
12294
12375
  }
12295
12376
  logDataSourceSummary("plan (full)", [
12296
12377
  { label: "cycleHealth", hasData: !!health },
@@ -12315,8 +12396,18 @@ ${lines.join("\n")}`;
12315
12396
  const strippedTasks = stripTasksForPlan(tasks);
12316
12397
  const boardFlagsFull = detectBoardFlags(tasks);
12317
12398
  const horizonCtx = buildHorizonContext(phases, tasks) ?? void 0;
12399
+ const ACTIVE_STATUSES2 = /* @__PURE__ */ new Set(["In Progress", "In Review", "Blocked"]);
12400
+ const p3Excluded = strippedTasks.filter(
12401
+ (t2) => t2.priority === "P3 Low" && !ACTIVE_STATUSES2.has(t2.status)
12402
+ );
12403
+ const plannerTasks = strippedTasks.filter(
12404
+ (t2) => t2.priority !== "P3 Low" || ACTIVE_STATUSES2.has(t2.status)
12405
+ );
12406
+ if (p3Excluded.length > 0) {
12407
+ console.error(`[plan-perf] board tiering: excluded ${p3Excluded.length} P3 Low tasks from planner context`);
12408
+ }
12318
12409
  const targetCycle = health.totalCycles + 1;
12319
- const preAssigned = strippedTasks.filter((t2) => t2.cycle === targetCycle);
12410
+ const preAssigned = plannerTasks.filter((t2) => t2.cycle === targetCycle);
12320
12411
  const preAssignedText = formatPreAssignedTasks(preAssigned, targetCycle);
12321
12412
  const gapFull = health.cyclesSinceLastStrategyReview;
12322
12413
  const lastReviewCycleFull = health.totalCycles - gapFull;
@@ -12328,7 +12419,7 @@ ${lines.join("\n")}`;
12328
12419
  activeDecisions: formatActiveDecisionsForPlan(decisions),
12329
12420
  recentBuildReports: formatBuildReports(cappedReports),
12330
12421
  cycleLog: formatCycleLog(log),
12331
- board: formatBoardForPlan(strippedTasks, filters, health.totalCycles),
12422
+ board: formatBoardForPlan(plannerTasks, filters, health.totalCycles),
12332
12423
  northStar,
12333
12424
  methodologyMetrics: formatCycleMetrics(metricsSnapshots),
12334
12425
  recentReviews: formatReviews(reviews),
@@ -12356,6 +12447,9 @@ ${lines.join("\n")}`;
12356
12447
  if (savedBytes > 0) {
12357
12448
  console.error(`[plan-perf] context diff saved ${savedBytes} bytes`);
12358
12449
  }
12450
+ const boardChars = ctx.board?.length ?? 0;
12451
+ const totalChars = Object.values(ctx).reduce((sum, v) => sum + (typeof v === "string" ? v.length : 0), 0);
12452
+ console.error(`[plan-perf] context budget: board=${boardChars} chars, total fields=${totalChars} chars (excl. system prompt)`);
12359
12453
  return { context: ctx, contextHashes: newHashes };
12360
12454
  }
12361
12455
  async function transactionalWriteBack(adapter2, cycleNumber, data, contextHashes) {
@@ -12393,7 +12487,7 @@ ${cleanContent}`;
12393
12487
  };
12394
12488
  let dedupedNewTasks = data.newTasks ?? [];
12395
12489
  if (dedupedNewTasks.length > 0) {
12396
- const existingTasks = await adapter2.queryBoard();
12490
+ const existingTasks = await adapter2.queryBoard({ compact: true });
12397
12491
  const normalise = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
12398
12492
  const existingTitles = existingTasks.map((t) => normalise(t.title));
12399
12493
  const MIN_SUBSTRING_LEN = 20;
@@ -12475,7 +12569,7 @@ ${cleanContent}`;
12475
12569
  try {
12476
12570
  const [cycles, boardTasks] = await Promise.all([
12477
12571
  adapter2.readCycles(),
12478
- adapter2.queryBoard({ status: ["In Cycle", "Backlog", "In Progress", "In Review"] })
12572
+ adapter2.queryBoard({ status: ["In Cycle", "Backlog", "In Progress", "In Review"], compact: true })
12479
12573
  ]);
12480
12574
  const newCycle = cycles.find((s) => s.number === newCycleNumber);
12481
12575
  if (!newCycle) {
@@ -12551,7 +12645,7 @@ ${cleanContent}`;
12551
12645
  const newTaskIdMap = /* @__PURE__ */ new Map();
12552
12646
  const createTasksPromise = (async () => {
12553
12647
  if (!data.newTasks || data.newTasks.length === 0) return;
12554
- const existingTasks = await adapter2.queryBoard();
12648
+ const existingTasks = await adapter2.queryBoard({ compact: true });
12555
12649
  const normalise = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
12556
12650
  const existingTitles = existingTasks.map((t) => normalise(t.title));
12557
12651
  for (let i = 0; i < data.newTasks.length; i++) {
@@ -12786,7 +12880,8 @@ async function validateAndPrepare(adapter2, force) {
12786
12880
  mode = determineMode(health.totalCycles);
12787
12881
  if (health.latestCycleStatus && health.latestCycleStatus !== "complete" && health.latestCycleStatus !== "released" && !force) {
12788
12882
  const activeTasks = await adapter2.queryBoard({
12789
- status: ["In Progress", "In Review"]
12883
+ status: ["In Progress", "In Review"],
12884
+ compact: true
12790
12885
  });
12791
12886
  const taskNote = activeTasks.length > 0 ? ` ${activeTasks.length} task(s) still active: ${activeTasks.map((t) => t.id).join(", ")}.` : "";
12792
12887
  throw new Error(
@@ -12797,7 +12892,8 @@ Run \`release\` first, or pass \`force: true\` to bypass this block.`
12797
12892
  }
12798
12893
  if (!force) {
12799
12894
  const inReviewTasks = await adapter2.queryBoard({
12800
- status: ["In Review"]
12895
+ status: ["In Review"],
12896
+ compact: true
12801
12897
  });
12802
12898
  const staleTasks = inReviewTasks.filter(
12803
12899
  (t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2
@@ -12871,7 +12967,9 @@ async function processLlmOutput(adapter2, config2, rawOutput, mode, cycleNumber,
12871
12967
  durationMs,
12872
12968
  taskCountIn: planRunMeta?.taskCountIn,
12873
12969
  taskCountOut: (writeSummary?.handoffs ?? 0) + (writeSummary?.newTasks ?? 0),
12874
- backlogDepth: planRunMeta?.backlogDepth
12970
+ backlogDepth: planRunMeta?.backlogDepth,
12971
+ tokenUsage: planRunMeta?.tokenUsage,
12972
+ source: planRunMeta?.source ?? "mcp-server"
12875
12973
  }).catch(() => {
12876
12974
  });
12877
12975
  }
@@ -12968,7 +13066,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12968
13066
  if (skipHandoffs) context.skipHandoffs = true;
12969
13067
  t = startTimer();
12970
13068
  try {
12971
- const scanTasks = await adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready"] });
13069
+ const scanTasks = await adapter2.queryBoard({ status: ["Backlog", "In Cycle", "Ready"], compact: true });
12972
13070
  const candidates = scanTasks.filter((task) => task.priority !== "P3 Low").slice(0, 15).map((task) => ({ id: task.id, title: task.title, notes: task.notes }));
12973
13071
  const scanResult = scanCodebaseForTasks(config2.projectRoot, candidates);
12974
13072
  if (scanResult) context.codebaseScan = scanResult;
@@ -13122,7 +13220,7 @@ function formatPhaseChanges(changes) {
13122
13220
  async function propagatePhaseStatus(adapter2) {
13123
13221
  const [phases, tasks] = await Promise.all([
13124
13222
  adapter2.readPhases(),
13125
- adapter2.queryBoard()
13223
+ adapter2.queryBoard({ compact: true })
13126
13224
  ]);
13127
13225
  if (phases.length === 0) return [];
13128
13226
  const horizons = await adapter2.readHorizons?.() ?? [];
@@ -13306,7 +13404,6 @@ async function handlePlan(adapter2, config2, args) {
13306
13404
  lastPrepareContextBytes = void 0;
13307
13405
  lastPrepareCycleNumber = void 0;
13308
13406
  lastPrepareSkipHandoffs = void 0;
13309
- const result = await applyPlan(adapter2, config2, llmResponse, planMode, cycleNumber, strategyReviewWarning, contextHashes, { contextBytes: contextBytes ?? void 0, skipHandoffs: skipHandoffs || void 0 });
13310
13407
  let utilisation;
13311
13408
  if (inputContext) {
13312
13409
  try {
@@ -13314,6 +13411,12 @@ async function handlePlan(adapter2, config2, args) {
13314
13411
  } catch {
13315
13412
  }
13316
13413
  }
13414
+ const result = await applyPlan(adapter2, config2, llmResponse, planMode, cycleNumber, strategyReviewWarning, contextHashes, {
13415
+ contextBytes: contextBytes ?? void 0,
13416
+ skipHandoffs: skipHandoffs || void 0,
13417
+ tokenUsage: utilisation !== void 0 ? { utilisation } : void 0,
13418
+ source: "mcp-server"
13419
+ });
13317
13420
  const response = formatPlanResult({ ...result, contextUtilisation: utilisation, contextBytes, skipHandoffs });
13318
13421
  return {
13319
13422
  ...response,
@@ -21264,6 +21367,49 @@ ${versionDrift}` : "";
21264
21367
  }
21265
21368
  } catch {
21266
21369
  }
21370
+ let researchSignalsNote = "";
21371
+ try {
21372
+ if (adapter2.searchDocs) {
21373
+ const cycleHealth = await adapter2.getCycleHealth();
21374
+ const lastReviewCycle = Math.max(0, cycleHealth.totalCycles - cycleHealth.cyclesSinceLastStrategyReview);
21375
+ const researchDocs = await adapter2.searchDocs({
21376
+ type: "research",
21377
+ hasPendingActions: true,
21378
+ sinceCycle: lastReviewCycle,
21379
+ limit: 10
21380
+ });
21381
+ if (researchDocs.length > 0) {
21382
+ let activeAds = [];
21383
+ try {
21384
+ activeAds = (await adapter2.getActiveDecisions()).filter((a) => !a.superseded);
21385
+ } catch {
21386
+ }
21387
+ const stopWords = /* @__PURE__ */ new Set(["about", "above", "after", "again", "being", "could", "doing", "during", "every", "first", "front", "going", "great", "helps", "large", "later", "level", "local", "makes", "model", "needs", "never", "other", "place", "right", "since", "small", "still", "there", "these", "thing", "those", "three", "under", "until", "using", "where", "which", "while", "would"]);
21388
+ const lines = ["\n\n## Research Signals"];
21389
+ for (const doc of researchDocs) {
21390
+ const docText = [doc.title, doc.summary, ...doc.tags].join(" ").toLowerCase();
21391
+ const relatedAds = activeAds.filter((ad) => {
21392
+ const adWords = ad.title.toLowerCase().split(/\W+/).filter((w) => w.length >= 5 && !stopWords.has(w));
21393
+ return adWords.some((w) => docText.includes(w));
21394
+ });
21395
+ const cycleLabel = `C${doc.cycleUpdated ?? doc.cycleCreated}`;
21396
+ const adRef = relatedAds.length > 0 ? ` \u2014 may relate to ${relatedAds.map((a) => a.displayId).join(", ")}` : "";
21397
+ const pendingActions = doc.actions?.filter((a) => a.status === "pending") ?? [];
21398
+ lines.push(`- **${doc.title}** [${cycleLabel}${adRef}]`);
21399
+ for (const action of pendingActions.slice(0, 2)) {
21400
+ const desc = action.description.length > 100 ? `${action.description.slice(0, 97)}\u2026` : action.description;
21401
+ lines.push(` \u2192 ${desc}`);
21402
+ }
21403
+ if (pendingActions.length > 2) {
21404
+ lines.push(` \u2192 \u2026and ${pendingActions.length - 2} more`);
21405
+ }
21406
+ }
21407
+ lines.push("_Factor into next `strategy_review` or run `doc_search` for details._");
21408
+ researchSignalsNote = lines.join("\n");
21409
+ }
21410
+ }
21411
+ } catch {
21412
+ }
21267
21413
  let recsNote = "";
21268
21414
  try {
21269
21415
  const pendingRecs = await adapter2.getPendingRecommendations();
@@ -21314,7 +21460,7 @@ ${versionDrift}` : "";
21314
21460
  }
21315
21461
  } catch {
21316
21462
  }
21317
- return textResponse(formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot) + ttfvNote + reconciliationNote + unrecordedNote + unregisteredDocsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + versionNote + enrichmentNote);
21463
+ return textResponse(formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot) + ttfvNote + reconciliationNote + unrecordedNote + unregisteredDocsNote + researchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + versionNote + enrichmentNote);
21318
21464
  } catch (err) {
21319
21465
  const message = err instanceof Error ? err.message : String(err);
21320
21466
  return errorResponse(`Orient failed: ${message}`);
@@ -22536,41 +22682,7 @@ if (httpPort) {
22536
22682
  if (!httpToken) {
22537
22683
  process.stderr.write("[papi] WARNING: PAPI_HTTP_TOKEN is not set. HTTP transport is unauthenticated \u2014 anyone with the URL can call your PAPI tools. Set PAPI_HTTP_TOKEN to a secret string.\n");
22538
22684
  }
22539
- const createServerForRequest = () => {
22540
- if (adapter && !setupError) {
22541
- return createServer(adapter, config);
22542
- }
22543
- const errorServer = new Server2(
22544
- { name: "papi", version: pkgVersion },
22545
- { capabilities: { tools: {} } }
22546
- );
22547
- const errorMessage = setupError || "Unknown startup error";
22548
- errorServer.setRequestHandler(ListToolsRequestSchema2, async () => ({
22549
- tools: [{
22550
- name: "setup",
22551
- description: "PAPI is not connected \u2014 run this tool for setup instructions.",
22552
- inputSchema: { type: "object", properties: {}, required: [] }
22553
- }]
22554
- }));
22555
- errorServer.setRequestHandler(CallToolRequestSchema2, async () => ({
22556
- content: [{
22557
- type: "text",
22558
- text: `# PAPI Connection Error
22559
-
22560
- ${errorMessage}
22561
-
22562
- ## Quick Fix
22563
-
22564
- If you haven't set up PAPI yet:
22565
- 1. Go to https://getpapi.ai/login and sign up
22566
- 2. Complete the onboarding wizard \u2014 it generates your config
22567
- 3. Copy the config to your project and restart your AI tool
22568
-
22569
- If you already have an account, check that both **PAPI_PROJECT_ID** and **PAPI_DATA_API_KEY** are set in your .mcp.json env config.`
22570
- }]
22571
- }));
22572
- return errorServer;
22573
- };
22685
+ const httpTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
22574
22686
  const httpServer = createHttpServer((req, res) => {
22575
22687
  if (req.method === "GET" && req.url === "/healthz") {
22576
22688
  res.writeHead(200, { "Content-Type": "text/plain" });
@@ -22587,13 +22699,6 @@ If you already have an account, check that both **PAPI_PROJECT_ID** and **PAPI_D
22587
22699
  }
22588
22700
  }
22589
22701
  if (req.url === "/mcp" || req.url === "/sse") {
22590
- req.headers["accept"] = "application/json, text/event-stream";
22591
- const acceptIdx = req.rawHeaders.findIndex((h) => h.toLowerCase() === "accept");
22592
- if (acceptIdx >= 0) {
22593
- req.rawHeaders[acceptIdx + 1] = "application/json, text/event-stream";
22594
- } else {
22595
- req.rawHeaders.push("Accept", "application/json, text/event-stream");
22596
- }
22597
22702
  if (req.method === "POST") {
22598
22703
  const chunks = [];
22599
22704
  req.on("data", (chunk) => chunks.push(chunk));
@@ -22606,42 +22711,23 @@ If you already have an account, check that both **PAPI_PROJECT_ID** and **PAPI_D
22606
22711
  res.end(JSON.stringify({ error: "Invalid JSON body" }));
22607
22712
  return;
22608
22713
  }
22609
- (async () => {
22610
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
22611
- const reqServer = createServerForRequest();
22612
- await reqServer.connect(transport);
22613
- await transport.handleRequest(req, res, parsedBody);
22614
- await reqServer.close();
22615
- })().catch((err) => {
22714
+ httpTransport.handleRequest(req, res, parsedBody).catch((err) => {
22616
22715
  process.stderr.write(`[papi] HTTP transport error: ${err instanceof Error ? err.message : String(err)}
22617
22716
  `);
22618
- if (!res.headersSent) {
22619
- res.writeHead(500, { "Content-Type": "application/json" });
22620
- res.end(JSON.stringify({ error: "Internal server error" }));
22621
- }
22622
22717
  });
22623
22718
  });
22624
22719
  return;
22625
22720
  }
22626
- (async () => {
22627
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
22628
- const reqServer = createServerForRequest();
22629
- await reqServer.connect(transport);
22630
- await transport.handleRequest(req, res);
22631
- await reqServer.close();
22632
- })().catch((err) => {
22721
+ httpTransport.handleRequest(req, res).catch((err) => {
22633
22722
  process.stderr.write(`[papi] HTTP transport error: ${err instanceof Error ? err.message : String(err)}
22634
22723
  `);
22635
- if (!res.headersSent) {
22636
- res.writeHead(500, { "Content-Type": "application/json" });
22637
- res.end(JSON.stringify({ error: "Internal server error" }));
22638
- }
22639
22724
  });
22640
22725
  return;
22641
22726
  }
22642
22727
  res.writeHead(404, { "Content-Type": "text/plain" });
22643
22728
  res.end("Not found");
22644
22729
  });
22730
+ await server.connect(httpTransport);
22645
22731
  httpServer.listen(httpPort, httpHost, () => {
22646
22732
  process.stderr.write(`[papi] HTTP transport listening on http://${httpHost}:${httpPort}/mcp
22647
22733
  `);
package/dist/prompts.js CHANGED
@@ -241,6 +241,7 @@ Standard planning cycle with full board review.
241
241
  - Add a \`boardCorrections\` entry for the dependent task with \`updates.dependsOn\` set to the comma-separated upstream IDs \u2014 this persists the dependency so the builder's runtime can reuse the upstream branch.
242
242
  - Keep the SCOPE sections independent (each task still has its own deliverable) but note the ordering in "Why now" \u2014 e.g. "depends on task-123 completing the adapter method".
243
243
  Do NOT invent dependencies where tasks merely share a module \u2014 only real build-order coupling counts. Linear chains only \u2014 do not attempt to resolve multi-level graphs. When in doubt, omit the dependency and let the builder discover it.
244
+ **Dependency Chain section (Part 1 markdown):** When intra-cycle dependencies are detected, include a visible **## Dependency Chain** section in Part 1 markdown immediately before the first BUILD HANDOFF block. List each dependency as an arrow chain with a brief reason: \`task-A \u2192 task-B (B calls the adapter method A creates)\`. Then show the full recommended build sequence for all cycle tasks, including standalone tasks: e.g. \`Build order: task-A \u2192 task-B; task-C standalone; task-D standalone\`. Flag circular dependencies with \u26A0\uFE0F and a note. Omit this section entirely when no intra-cycle dependencies exist \u2014 do not include an empty section.
244
245
  **Security section guidance:** Each handoff includes a SECURITY CONSIDERATIONS section. Populate it when the task involves: data exposure risks (PII, secrets in logs/storage), secrets or credentials handling (API keys, tokens, env vars), auth/access control changes, or dependency security risks (new packages, version changes). For pure refactoring, documentation, prompt-text, or UI-only tasks, write "None \u2014 no security-relevant changes".
245
246
  **Estimation calibration:** Estimate **XS** for: copy/text-only changes, single string replacements, config tweaks, and any task where the scope is "change words in an existing file" with no logic changes. Estimate **S** for: wiring existing adapter methods, adding API routes following established patterns, modifying prompts, or documentation-only changes. Default to S for pattern-following work. Only use M when genuine new architecture, new DB tables, or multi-file architectural changes are needed. Historical data shows systematic over-estimation (198 over vs 8 under out of 528 tasks) \u2014 when in doubt, estimate smaller. If an "Estimation Calibration (Historical)" section is provided in the context below, use its data to adjust your estimates \u2014 it shows how often each estimated size matched the actual effort. Pay special attention to systematic over/under-estimation patterns (e.g. if M\u2192S happens frequently, estimate S instead of M for similar work).
246
247
  **Reference docs:** If a task's notes include a \`Reference:\` path (e.g. \`Reference: docs/architecture/papi-brain-v1.md\`), include a REFERENCE DOCS section in the BUILD HANDOFF with those paths. This tells the builder to read the referenced doc for background context before implementing. Do NOT omit or summarise the reference \u2014 pass it through so the builder can access the full document. Only tasks with explicit \`Reference:\` paths in their notes should have this section.
@@ -335,6 +336,38 @@ var PLAN_FRAGMENT_SPIKE = `
335
336
  - **DONE CONDITION:** "Question answered OR time-box hit, whichever comes first."
336
337
  - Keep SCOPE BOUNDARY, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
337
338
  - Spikes should be estimated conservatively: XS or S. If a spike needs M+ effort, it's not a spike \u2014 reclassify as a research task.`;
339
+ var PLAN_FRAGMENT_DESIGN_BRIEF = `
340
+ **Design brief task detection:** When a task's task type is "design-brief", generate a DESIGN BRIEF handoff. Replace the standard SCOPE (DO THIS) section with these type-specific sections:
341
+ - AUDIENCE: Who this design is for \u2014 persona and context of use (e.g. "non-technical Owner, first dashboard visit")
342
+ - BRAND CONSTRAINTS: Palette, typography, tone \u2014 pull from \`.impeccable.md\` and \`docs/design/brand-system.md\` if present. If neither exists, state "No brand doc \u2014 Owner should define constraints before starting."
343
+ - DELIVERABLE FORMAT: What the output looks like \u2014 Claude Design handoff package / annotated mockup / style spec. Be specific so the person doing the work knows what "done" means.
344
+ - REVIEW POINTS: What the Owner must approve before the design is considered done (e.g. layout, copy, colour, imagery).
345
+ Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION sections as normal.
346
+ Add to ACCEPTANCE CRITERIA: "[ ] Deliverable format confirmed with Owner before starting" and "[ ] Design output is self-contained \u2014 includes enough context for a developer to implement without further clarification."`;
347
+ var PLAN_FRAGMENT_RESEARCH_BRIEF = `
348
+ **Research brief task detection:** When a task's task type is "research-brief", generate a RESEARCH BRIEF handoff. Replace the standard SCOPE (DO THIS) section with:
349
+ - GOAL: The specific question this research answers \u2014 one sentence, phrased as a question (e.g. "What onboarding patterns do our top 3 competitors use?")
350
+ - TIME-BOX: Maximum effort allowed \u2014 XS or S. Stop when the time-box is hit and report what was found, even if incomplete.
351
+ - OUTPUT: Where findings land \u2014 a doc at \`docs/research/[topic]-findings.md\` or inline in the build report. State the path.
352
+ - FOLLOW-UP PROTOCOL: Do NOT submit follow-up backlog tasks until the Owner reviews and confirms the findings are actionable.
353
+ Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
354
+ Add to ACCEPTANCE CRITERIA: "[ ] Question answered OR time-box hit \u2014 whichever comes first" and "[ ] Findings doc saved before any follow-up tasks are submitted."`;
355
+ var PLAN_FRAGMENT_MARKETING_BRIEF = `
356
+ **Marketing brief task detection:** When a task's task type is "marketing-brief", generate a MARKETING BRIEF handoff. Replace the standard SCOPE (DO THIS) section with:
357
+ - AUDIENCE: Who this marketing content targets \u2014 persona, awareness level, channel context (e.g. "cold Discord visitor, zero PAPI context")
358
+ - CHANNEL: Where this content lives \u2014 Discord, landing page, email, social, etc.
359
+ - MESSAGE FRAME: The core message to land \u2014 one sentence. What does the reader need to believe after seeing this? (e.g. "PAPI makes AI-assisted building systematic, not chaotic.")
360
+ - SUCCESS SIGNAL: How the Owner knows the content worked \u2014 clicks, signups, replies, saves, DMs. Be specific.
361
+ Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
362
+ Add to ACCEPTANCE CRITERIA: "[ ] Message Frame confirmed with Owner before drafting" and "[ ] Final content reviewed by Owner before publishing."`;
363
+ var PLAN_FRAGMENT_OPS_BRIEF = `
364
+ **Ops brief task detection:** When a task's task type is "ops-brief", generate an OPS BRIEF handoff. Replace the standard SCOPE (DO THIS) section with:
365
+ - SYSTEM: Which system or service this ops task touches \u2014 Vercel, Railway, Supabase, GitHub Actions, DNS, etc.
366
+ - RISK: What could go wrong \u2014 data loss, downtime, broken deployments. Include estimated blast radius (e.g. "affects all authenticated users").
367
+ - ROLLBACK PLAN: Exact steps to undo the change if something breaks. Must be specific enough to execute under pressure.
368
+ - DONE CONDITION: The specific observable state that confirms the task is complete \u2014 a health check URL, a metric, a log line, a manual verification step.
369
+ Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
370
+ Add to ACCEPTANCE CRITERIA: "[ ] Rollback plan confirmed before executing" and "[ ] Done condition verified after completing."`;
338
371
  var PLAN_FRAGMENT_UI = `
339
372
  **UI/visual task detection:** Apply these additions ONLY to tasks whose PRIMARY scope is frontend visual work \u2014 the task's main deliverable must be a UI change, new component, visual design, or page. Do NOT apply to backend tasks, DB migrations, or prompt/config changes that merely mention a dashboard or page in passing. Signal: the task would fail if no .tsx/.css files were changed. If uncertain, skip the UI additions.
340
373
  When a task IS a UI task (primary scope is visual/frontend):
@@ -451,6 +484,10 @@ Standard planning cycle with full board review.
451
484
  if (flags.hasIdeaTasks) parts.push(PLAN_FRAGMENT_IDEA);
452
485
  if (flags.hasSpikeTasks) parts.push(PLAN_FRAGMENT_SPIKE);
453
486
  if (flags.hasTaskTasks) parts.push(PLAN_FRAGMENT_TASK);
487
+ if (flags.hasDesignBriefTasks) parts.push(PLAN_FRAGMENT_DESIGN_BRIEF);
488
+ if (flags.hasResearchBriefTasks) parts.push(PLAN_FRAGMENT_RESEARCH_BRIEF);
489
+ if (flags.hasMarketingBriefTasks) parts.push(PLAN_FRAGMENT_MARKETING_BRIEF);
490
+ if (flags.hasOpsBriefTasks) parts.push(PLAN_FRAGMENT_OPS_BRIEF);
454
491
  if (flags.hasUITasks) parts.push(PLAN_FRAGMENT_UI);
455
492
  parts.push(`
456
493
  11. **New Tasks (max 3 per cycle)** \u2014 Actively mine the Recent Build Reports for task candidates. For each report, check:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
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
  "type": "module",
@@ -44,7 +44,7 @@
44
44
  "node": ">=18.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@anthropic-ai/sdk": "^0.79.0",
47
+ "@anthropic-ai/sdk": "^0.82.0",
48
48
  "@modelcontextprotocol/sdk": "^1.27.1",
49
49
  "js-yaml": "^4.1.0"
50
50
  },