@papi-ai/server 0.7.10 → 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 +187 -106
- package/dist/prompts.js +37 -0
- package/package.json +2 -2
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
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
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
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
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
|
-
${
|
|
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:
|
|
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:
|
|
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
|
|
12291
|
-
const
|
|
12292
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
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" });
|
|
@@ -22579,26 +22691,14 @@ If you already have an account, check that both **PAPI_PROJECT_ID** and **PAPI_D
|
|
|
22579
22691
|
}
|
|
22580
22692
|
if (httpToken) {
|
|
22581
22693
|
const authHeader = req.headers["authorization"] ?? "";
|
|
22582
|
-
const
|
|
22583
|
-
|
|
22584
|
-
const urlToken = urlMatch?.[2];
|
|
22585
|
-
if (bearerMatch === httpToken) {
|
|
22586
|
-
} else if (urlToken === httpToken) {
|
|
22587
|
-
req.url = `/${urlMatch[1]}`;
|
|
22588
|
-
} else {
|
|
22694
|
+
const provided = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
22695
|
+
if (provided !== httpToken) {
|
|
22589
22696
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
22590
22697
|
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
22591
22698
|
return;
|
|
22592
22699
|
}
|
|
22593
22700
|
}
|
|
22594
22701
|
if (req.url === "/mcp" || req.url === "/sse") {
|
|
22595
|
-
req.headers["accept"] = "application/json, text/event-stream";
|
|
22596
|
-
const acceptIdx = req.rawHeaders.findIndex((h) => h.toLowerCase() === "accept");
|
|
22597
|
-
if (acceptIdx >= 0) {
|
|
22598
|
-
req.rawHeaders[acceptIdx + 1] = "application/json, text/event-stream";
|
|
22599
|
-
} else {
|
|
22600
|
-
req.rawHeaders.push("Accept", "application/json, text/event-stream");
|
|
22601
|
-
}
|
|
22602
22702
|
if (req.method === "POST") {
|
|
22603
22703
|
const chunks = [];
|
|
22604
22704
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -22611,42 +22711,23 @@ If you already have an account, check that both **PAPI_PROJECT_ID** and **PAPI_D
|
|
|
22611
22711
|
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
22612
22712
|
return;
|
|
22613
22713
|
}
|
|
22614
|
-
(
|
|
22615
|
-
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
22616
|
-
const reqServer = createServerForRequest();
|
|
22617
|
-
await reqServer.connect(transport);
|
|
22618
|
-
await transport.handleRequest(req, res, parsedBody);
|
|
22619
|
-
await reqServer.close();
|
|
22620
|
-
})().catch((err) => {
|
|
22714
|
+
httpTransport.handleRequest(req, res, parsedBody).catch((err) => {
|
|
22621
22715
|
process.stderr.write(`[papi] HTTP transport error: ${err instanceof Error ? err.message : String(err)}
|
|
22622
22716
|
`);
|
|
22623
|
-
if (!res.headersSent) {
|
|
22624
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
22625
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
22626
|
-
}
|
|
22627
22717
|
});
|
|
22628
22718
|
});
|
|
22629
22719
|
return;
|
|
22630
22720
|
}
|
|
22631
|
-
(
|
|
22632
|
-
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
22633
|
-
const reqServer = createServerForRequest();
|
|
22634
|
-
await reqServer.connect(transport);
|
|
22635
|
-
await transport.handleRequest(req, res);
|
|
22636
|
-
await reqServer.close();
|
|
22637
|
-
})().catch((err) => {
|
|
22721
|
+
httpTransport.handleRequest(req, res).catch((err) => {
|
|
22638
22722
|
process.stderr.write(`[papi] HTTP transport error: ${err instanceof Error ? err.message : String(err)}
|
|
22639
22723
|
`);
|
|
22640
|
-
if (!res.headersSent) {
|
|
22641
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
22642
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
22643
|
-
}
|
|
22644
22724
|
});
|
|
22645
22725
|
return;
|
|
22646
22726
|
}
|
|
22647
22727
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
22648
22728
|
res.end("Not found");
|
|
22649
22729
|
});
|
|
22730
|
+
await server.connect(httpTransport);
|
|
22650
22731
|
httpServer.listen(httpPort, httpHost, () => {
|
|
22651
22732
|
process.stderr.write(`[papi] HTTP transport listening on http://${httpHost}:${httpPort}/mcp
|
|
22652
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.
|
|
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.
|
|
47
|
+
"@anthropic-ai/sdk": "^0.82.0",
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
49
49
|
"js-yaml": "^4.1.0"
|
|
50
50
|
},
|