@papi-ai/server 0.7.76 → 0.7.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backfill-cycle-metrics.js +65 -3
- package/dist/index.js +115 -18
- package/dist/prompts.js +5 -5
- package/package.json +1 -1
|
@@ -1236,6 +1236,14 @@ var init_proxy_adapter = __esm({
|
|
|
1236
1236
|
// (1) local-only
|
|
1237
1237
|
"close",
|
|
1238
1238
|
"initRls",
|
|
1239
|
+
// task-3207 (C357): commitBuildComplete, commitReviewSubmit, commitRelease have no
|
|
1240
|
+
// edge case handler and no ALLOWED_METHODS entry — forwarding them 403s at the edge
|
|
1241
|
+
// with no try/catch at the call site, crashing build_execute/review_submit/release
|
|
1242
|
+
// completion for every hosted user. Restores the intended graceful degradation
|
|
1243
|
+
// (separate appendBuildReport + updateTaskStatus calls) until they're atomically wired.
|
|
1244
|
+
"commitBuildComplete",
|
|
1245
|
+
"commitReviewSubmit",
|
|
1246
|
+
"commitRelease",
|
|
1239
1247
|
// (2) not-yet-wired hosted gaps — shrink as data-proxy handlers land (task-2390).
|
|
1240
1248
|
// getToolCallCount + updatePhaseStatus are ABSENT: they now have edge handlers and
|
|
1241
1249
|
// are served through the forwarder (getToolCallCount = SUP-2026-026 handler #1).
|
|
@@ -2943,6 +2951,59 @@ var ACCURACY_HEADER = "| Cycle | Reports | Match Rate | MAE | Bias |";
|
|
|
2943
2951
|
var ACCURACY_SEPARATOR = "|--------|---------|------------|-----|------|";
|
|
2944
2952
|
var VELOCITY_HEADER = "| Cycle | Completed | Partial | Failed | Effort Points |";
|
|
2945
2953
|
var VELOCITY_SEPARATOR = "|--------|-----------|---------|--------|---------------|";
|
|
2954
|
+
var EFFORT_SCALE = {
|
|
2955
|
+
XS: 1,
|
|
2956
|
+
S: 2,
|
|
2957
|
+
M: 3,
|
|
2958
|
+
L: 4,
|
|
2959
|
+
XL: 5
|
|
2960
|
+
};
|
|
2961
|
+
function effortOrdinal(effort) {
|
|
2962
|
+
const normalized = effort.trim().toUpperCase();
|
|
2963
|
+
return EFFORT_SCALE[normalized];
|
|
2964
|
+
}
|
|
2965
|
+
function calculateCycleMetrics(reports, currentCycle, window = 5) {
|
|
2966
|
+
const recentReports = reports.filter(
|
|
2967
|
+
(r) => r.cycle > currentCycle - window && r.cycle <= currentCycle
|
|
2968
|
+
);
|
|
2969
|
+
const perCycle = /* @__PURE__ */ new Map();
|
|
2970
|
+
for (const r of recentReports) {
|
|
2971
|
+
const group = perCycle.get(r.cycle) ?? [];
|
|
2972
|
+
group.push(r);
|
|
2973
|
+
perCycle.set(r.cycle, group);
|
|
2974
|
+
}
|
|
2975
|
+
const accuracy = [];
|
|
2976
|
+
const velocity = [];
|
|
2977
|
+
const sortedCycles = [...perCycle.keys()].sort((a, b) => a - b);
|
|
2978
|
+
for (const cycle of sortedCycles) {
|
|
2979
|
+
const reps = perCycle.get(cycle);
|
|
2980
|
+
const deltas = [];
|
|
2981
|
+
for (const r of reps) {
|
|
2982
|
+
const actual = effortOrdinal(r.actualEffort);
|
|
2983
|
+
const estimated = effortOrdinal(r.estimatedEffort);
|
|
2984
|
+
if (actual !== void 0 && estimated !== void 0) {
|
|
2985
|
+
deltas.push(actual - estimated);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
if (deltas.length > 0) {
|
|
2989
|
+
accuracy.push({
|
|
2990
|
+
cycle,
|
|
2991
|
+
reports: deltas.length,
|
|
2992
|
+
matchRate: Math.round(deltas.filter((d) => d === 0).length / deltas.length * 100),
|
|
2993
|
+
mae: Math.round(deltas.reduce((s, d) => s + Math.abs(d), 0) / deltas.length * 10) / 10,
|
|
2994
|
+
bias: Math.round(deltas.reduce((s, d) => s + d, 0) / deltas.length * 10) / 10
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
velocity.push({
|
|
2998
|
+
cycle,
|
|
2999
|
+
completed: reps.filter((r) => r.completed === "Yes").length,
|
|
3000
|
+
partial: reps.filter((r) => r.completed === "Partial").length,
|
|
3001
|
+
failed: reps.filter((r) => r.completed === "No").length,
|
|
3002
|
+
effortPoints: reps.reduce((s, r) => s + (effortOrdinal(r.actualEffort) ?? 0), 0)
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
return { accuracy, velocity };
|
|
3006
|
+
}
|
|
2946
3007
|
function serializeAccuracyRow(a) {
|
|
2947
3008
|
return `| ${a.cycle} | ${a.reports} | ${a.matchRate}% | ${a.mae} | ${a.bias >= 0 ? "+" : ""}${a.bias} |`;
|
|
2948
3009
|
}
|
|
@@ -4726,13 +4787,14 @@ function computeSnapshotsFromBuildReports(reports, tasks) {
|
|
|
4726
4787
|
const cycleReports = reportsByCycle.get(sn) ?? [];
|
|
4727
4788
|
const cycleTaskRows = tasksByCycle.get(sn);
|
|
4728
4789
|
const withEffort = cycleReports.filter((r) => r.estimatedEffort && r.actualEffort);
|
|
4729
|
-
const
|
|
4730
|
-
const matchRate = withEffort.length > 0 ? Math.round(accurate / withEffort.length * 100) : 0;
|
|
4790
|
+
const [computedAccuracy] = calculateCycleMetrics(withEffort, sn, 1).accuracy;
|
|
4731
4791
|
const { completed, total, plannedPoints, deliveredPoints } = computeCycleEffort(cycleTaskRows, cycleReports);
|
|
4732
4792
|
snapshots.push({
|
|
4733
4793
|
cycle: sn,
|
|
4734
4794
|
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4735
|
-
|
|
4795
|
+
// No report in this cycle carried BOTH an estimate and an actual, so there
|
|
4796
|
+
// is genuinely nothing to measure. Zeros here mean "no data", not "no bias".
|
|
4797
|
+
accuracy: [computedAccuracy ?? { cycle: sn, reports: 0, matchRate: 0, mae: 0, bias: 0 }],
|
|
4736
4798
|
velocity: [{
|
|
4737
4799
|
cycle: sn,
|
|
4738
4800
|
completed,
|
package/dist/index.js
CHANGED
|
@@ -1353,6 +1353,14 @@ var init_proxy_adapter = __esm({
|
|
|
1353
1353
|
// (1) local-only
|
|
1354
1354
|
"close",
|
|
1355
1355
|
"initRls",
|
|
1356
|
+
// task-3207 (C357): commitBuildComplete, commitReviewSubmit, commitRelease have no
|
|
1357
|
+
// edge case handler and no ALLOWED_METHODS entry — forwarding them 403s at the edge
|
|
1358
|
+
// with no try/catch at the call site, crashing build_execute/review_submit/release
|
|
1359
|
+
// completion for every hosted user. Restores the intended graceful degradation
|
|
1360
|
+
// (separate appendBuildReport + updateTaskStatus calls) until they're atomically wired.
|
|
1361
|
+
"commitBuildComplete",
|
|
1362
|
+
"commitReviewSubmit",
|
|
1363
|
+
"commitRelease",
|
|
1356
1364
|
// (2) not-yet-wired hosted gaps — shrink as data-proxy handlers land (task-2390).
|
|
1357
1365
|
// getToolCallCount + updatePhaseStatus are ABSENT: they now have edge handlers and
|
|
1358
1366
|
// are served through the forwarder (getToolCallCount = SUP-2026-026 handler #1).
|
|
@@ -5259,6 +5267,15 @@ var HELP_FOOTER_MD = `
|
|
|
5259
5267
|
var STRATEGY_REVIEW_OFFER_GAP = 5;
|
|
5260
5268
|
var STRATEGY_REVIEW_BLOCK_GAP = 7;
|
|
5261
5269
|
var ZOOM_OUT_OFFER_GAP = 25;
|
|
5270
|
+
function isDatabaseBackedAdapter(known) {
|
|
5271
|
+
if (known) return known === "pg" || known === "proxy";
|
|
5272
|
+
try {
|
|
5273
|
+
const { adapterType } = loadConfig();
|
|
5274
|
+
return adapterType === "pg" || adapterType === "proxy";
|
|
5275
|
+
} catch {
|
|
5276
|
+
return process.env.PAPI_ADAPTER !== "md";
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5262
5279
|
function loadConfig() {
|
|
5263
5280
|
const projectArgIdx = process.argv.indexOf("--project");
|
|
5264
5281
|
const configuredRoot = projectArgIdx !== -1 ? process.argv[projectArgIdx + 1] : process.env.PAPI_PROJECT_DIR;
|
|
@@ -6324,6 +6341,48 @@ function effortOrdinal(effort) {
|
|
|
6324
6341
|
const normalized = effort.trim().toUpperCase();
|
|
6325
6342
|
return EFFORT_SCALE[normalized];
|
|
6326
6343
|
}
|
|
6344
|
+
function calculateCycleMetrics(reports, currentCycle, window = 5) {
|
|
6345
|
+
const recentReports = reports.filter(
|
|
6346
|
+
(r) => r.cycle > currentCycle - window && r.cycle <= currentCycle
|
|
6347
|
+
);
|
|
6348
|
+
const perCycle = /* @__PURE__ */ new Map();
|
|
6349
|
+
for (const r of recentReports) {
|
|
6350
|
+
const group = perCycle.get(r.cycle) ?? [];
|
|
6351
|
+
group.push(r);
|
|
6352
|
+
perCycle.set(r.cycle, group);
|
|
6353
|
+
}
|
|
6354
|
+
const accuracy = [];
|
|
6355
|
+
const velocity = [];
|
|
6356
|
+
const sortedCycles = [...perCycle.keys()].sort((a, b2) => a - b2);
|
|
6357
|
+
for (const cycle of sortedCycles) {
|
|
6358
|
+
const reps = perCycle.get(cycle);
|
|
6359
|
+
const deltas = [];
|
|
6360
|
+
for (const r of reps) {
|
|
6361
|
+
const actual = effortOrdinal(r.actualEffort);
|
|
6362
|
+
const estimated = effortOrdinal(r.estimatedEffort);
|
|
6363
|
+
if (actual !== void 0 && estimated !== void 0) {
|
|
6364
|
+
deltas.push(actual - estimated);
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
if (deltas.length > 0) {
|
|
6368
|
+
accuracy.push({
|
|
6369
|
+
cycle,
|
|
6370
|
+
reports: deltas.length,
|
|
6371
|
+
matchRate: Math.round(deltas.filter((d) => d === 0).length / deltas.length * 100),
|
|
6372
|
+
mae: Math.round(deltas.reduce((s, d) => s + Math.abs(d), 0) / deltas.length * 10) / 10,
|
|
6373
|
+
bias: Math.round(deltas.reduce((s, d) => s + d, 0) / deltas.length * 10) / 10
|
|
6374
|
+
});
|
|
6375
|
+
}
|
|
6376
|
+
velocity.push({
|
|
6377
|
+
cycle,
|
|
6378
|
+
completed: reps.filter((r) => r.completed === "Yes").length,
|
|
6379
|
+
partial: reps.filter((r) => r.completed === "Partial").length,
|
|
6380
|
+
failed: reps.filter((r) => r.completed === "No").length,
|
|
6381
|
+
effortPoints: reps.reduce((s, r) => s + (effortOrdinal(r.actualEffort) ?? 0), 0)
|
|
6382
|
+
});
|
|
6383
|
+
}
|
|
6384
|
+
return { accuracy, velocity };
|
|
6385
|
+
}
|
|
6327
6386
|
function serializeAccuracyRow(a) {
|
|
6328
6387
|
return `| ${a.cycle} | ${a.reports} | ${a.matchRate}% | ${a.mae} | ${a.bias >= 0 ? "+" : ""}${a.bias} |`;
|
|
6329
6388
|
}
|
|
@@ -8402,6 +8461,16 @@ function formatBuildReports(reports, opts) {
|
|
|
8402
8461
|
_\u2026and ${reports.length - capped.length} older build report(s) omitted to bound context size._` : "";
|
|
8403
8462
|
return body + omitted;
|
|
8404
8463
|
}
|
|
8464
|
+
function extractReferencedTaskIds(report) {
|
|
8465
|
+
const prose = [report.surprises, report.architectureNotes, report.deadEnds, report.discoveredIssues].filter((s) => typeof s === "string" && s.length > 0).join("\n");
|
|
8466
|
+
const own = report.taskId?.toLowerCase();
|
|
8467
|
+
const found = /* @__PURE__ */ new Set();
|
|
8468
|
+
for (const m of prose.matchAll(/\btask-\d+\b/gi)) {
|
|
8469
|
+
const id = m[0].toLowerCase();
|
|
8470
|
+
if (id !== own) found.add(id);
|
|
8471
|
+
}
|
|
8472
|
+
return [...found].sort();
|
|
8473
|
+
}
|
|
8405
8474
|
function formatRecentlyShippedCapabilities(reports) {
|
|
8406
8475
|
const completed = reports.filter((r) => r.completed === "Yes" || r.completed === "Partial");
|
|
8407
8476
|
if (completed.length === 0) return void 0;
|
|
@@ -8416,13 +8485,39 @@ function formatRecentlyShippedCapabilities(reports) {
|
|
|
8416
8485
|
}
|
|
8417
8486
|
return parts.join("\n");
|
|
8418
8487
|
});
|
|
8419
|
-
|
|
8488
|
+
const namedBy = /* @__PURE__ */ new Map();
|
|
8489
|
+
const completedIds = new Set(completed.map((r) => r.taskId?.toLowerCase()).filter(Boolean));
|
|
8490
|
+
for (const r of completed) {
|
|
8491
|
+
for (const ref of extractReferencedTaskIds(r)) {
|
|
8492
|
+
if (completedIds.has(ref)) continue;
|
|
8493
|
+
const namers = namedBy.get(ref) ?? [];
|
|
8494
|
+
namers.push(r.taskId);
|
|
8495
|
+
namedBy.set(ref, namers);
|
|
8496
|
+
}
|
|
8497
|
+
}
|
|
8498
|
+
const out = [
|
|
8420
8499
|
`${completed.length} task(s) completed in recent cycles:`,
|
|
8421
8500
|
"",
|
|
8422
8501
|
...lines,
|
|
8423
8502
|
"",
|
|
8424
8503
|
"Cross-reference candidate tasks against this list. If >80% of a candidate task's scope appears here, recommend cancellation or scope reduction instead of scheduling."
|
|
8425
|
-
]
|
|
8504
|
+
];
|
|
8505
|
+
if (namedBy.size > 0) {
|
|
8506
|
+
out.push(
|
|
8507
|
+
"",
|
|
8508
|
+
"### \u26A0 Named by a shipped task \u2014 VERIFY BEFORE SCHEDULING",
|
|
8509
|
+
"",
|
|
8510
|
+
"These task IDs are referenced in the build reports above but are NOT themselves",
|
|
8511
|
+
"completed. A discovery is often fixed as a side effect of a sibling task's diff and",
|
|
8512
|
+
"never marked done, so it survives into this plan carrying notes that are no longer",
|
|
8513
|
+
"true (C357 gave task-3043 a P1 slot this way \u2014 task-2998 had already fixed it).",
|
|
8514
|
+
"For each one below: check the naming report and the live code BEFORE scheduling it.",
|
|
8515
|
+
"If it is already fixed, close it with a boardCorrection instead of spending a slot.",
|
|
8516
|
+
"",
|
|
8517
|
+
...[...namedBy.entries()].sort(([a], [b2]) => a.localeCompare(b2)).map(([ref, namers]) => `- **${ref}** \u2014 named by ${namers.join(", ")}`)
|
|
8518
|
+
);
|
|
8519
|
+
}
|
|
8520
|
+
return out.join("\n");
|
|
8426
8521
|
}
|
|
8427
8522
|
function formatCycleLog(entries) {
|
|
8428
8523
|
if (entries.length === 0) return "No cycle log entries yet.";
|
|
@@ -8669,13 +8764,14 @@ function computeSnapshotsFromBuildReports(reports, tasks) {
|
|
|
8669
8764
|
const cycleReports = reportsByCycle.get(sn) ?? [];
|
|
8670
8765
|
const cycleTaskRows = tasksByCycle.get(sn);
|
|
8671
8766
|
const withEffort = cycleReports.filter((r) => r.estimatedEffort && r.actualEffort);
|
|
8672
|
-
const
|
|
8673
|
-
const matchRate = withEffort.length > 0 ? Math.round(accurate / withEffort.length * 100) : 0;
|
|
8767
|
+
const [computedAccuracy] = calculateCycleMetrics(withEffort, sn, 1).accuracy;
|
|
8674
8768
|
const { completed, total, plannedPoints, deliveredPoints } = computeCycleEffort(cycleTaskRows, cycleReports);
|
|
8675
8769
|
snapshots.push({
|
|
8676
8770
|
cycle: sn,
|
|
8677
8771
|
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8678
|
-
|
|
8772
|
+
// No report in this cycle carried BOTH an estimate and an actual, so there
|
|
8773
|
+
// is genuinely nothing to measure. Zeros here mean "no data", not "no bias".
|
|
8774
|
+
accuracy: [computedAccuracy ?? { cycle: sn, reports: 0, matchRate: 0, mae: 0, bias: 0 }],
|
|
8679
8775
|
velocity: [{
|
|
8680
8776
|
cycle: sn,
|
|
8681
8777
|
completed,
|
|
@@ -9196,7 +9292,7 @@ var PLAN_FRAGMENT_RESEARCH = `
|
|
|
9196
9292
|
var PLAN_FRAGMENT_BUG = `
|
|
9197
9293
|
**Bug task detection:** When a task's task type is "bug" or the title starts with "Bug:" or "Fix:", apply these rules:
|
|
9198
9294
|
- **Auto-P1:** If the task's current priority is P2 or lower, upgrade it to "P1 High" via a boardCorrections entry in Part 2. Note the upgrade in Part 1 analysis.
|
|
9199
|
-
-
|
|
9295
|
+
- Inside SCOPE (DO THIS), use these bug-specific subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
9200
9296
|
- **REPRODUCE:** Exact steps to reproduce the bug before touching any code. If the task notes describe the symptoms, include them. If not, the first build step is "confirm the bug reproduces."
|
|
9201
9297
|
- **ROOT CAUSE:** One-sentence hypothesis for the root cause (what is wrong, not what the user sees). The builder must confirm or correct this before implementing a fix.
|
|
9202
9298
|
- **MINIMAL FIX:** The smallest code change that resolves the root cause. "Bug fix \u2014 minimal blast radius. Change only what is necessary. Do not refactor surrounding code or expand scope."
|
|
@@ -9221,7 +9317,7 @@ var PLAN_FRAGMENT_SPIKE = `
|
|
|
9221
9317
|
- Keep SCOPE BOUNDARY, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
|
|
9222
9318
|
- 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.`;
|
|
9223
9319
|
var PLAN_FRAGMENT_DESIGN_BRIEF = `
|
|
9224
|
-
**Design brief task detection:** When a task's task type is "design-brief", generate a DESIGN BRIEF handoff.
|
|
9320
|
+
**Design brief task detection:** When a task's task type is "design-brief", generate a DESIGN BRIEF handoff. Inside SCOPE (DO THIS), use these type-specific subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
9225
9321
|
- AUDIENCE: Who this design is for \u2014 persona and context of use (e.g. "non-technical Owner, first dashboard visit")
|
|
9226
9322
|
- BRAND CONSTRAINTS: Palette, typography, tone \u2014 pull from \`.impeccable.md\` (dev patterns, anti-patterns, component rules) AND \`docs/branding/brand-book.html\` (brand identity, positioning, voice canon) if present. If neither exists, state "No brand doc \u2014 Owner should define constraints before starting."
|
|
9227
9323
|
- DELIVERABLE FORMAT: What the output looks like \u2014 design handoff package / annotated mockup / style spec. Be specific so the person doing the work knows what "done" means.
|
|
@@ -9229,7 +9325,7 @@ var PLAN_FRAGMENT_DESIGN_BRIEF = `
|
|
|
9229
9325
|
Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION sections as normal.
|
|
9230
9326
|
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."`;
|
|
9231
9327
|
var PLAN_FRAGMENT_RESEARCH_BRIEF = `
|
|
9232
|
-
**Research brief task detection:** When a task's task type is "research-brief", generate a RESEARCH BRIEF handoff.
|
|
9328
|
+
**Research brief task detection:** When a task's task type is "research-brief", generate a RESEARCH BRIEF handoff. Inside SCOPE (DO THIS), use these subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
9233
9329
|
- 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?")
|
|
9234
9330
|
- TIME-BOX: Maximum effort allowed \u2014 XS or S. Stop when the time-box is hit and report what was found, even if incomplete.
|
|
9235
9331
|
- OUTPUT: Where findings land \u2014 a doc at \`docs/research/[topic]-findings.md\` or inline in the build report. State the path.
|
|
@@ -9237,7 +9333,7 @@ var PLAN_FRAGMENT_RESEARCH_BRIEF = `
|
|
|
9237
9333
|
Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
|
|
9238
9334
|
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."`;
|
|
9239
9335
|
var PLAN_FRAGMENT_MARKETING_BRIEF = `
|
|
9240
|
-
**Marketing brief task detection:** When a task's task type is "marketing-brief", generate a MARKETING BRIEF handoff.
|
|
9336
|
+
**Marketing brief task detection:** When a task's task type is "marketing-brief", generate a MARKETING BRIEF handoff. Inside SCOPE (DO THIS), use these subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
9241
9337
|
- AUDIENCE: Who this marketing content targets \u2014 persona, awareness level, channel context (e.g. "cold Discord visitor, zero PAPI context")
|
|
9242
9338
|
- CHANNEL: Where this content lives \u2014 Discord, landing page, email, social, etc.
|
|
9243
9339
|
- 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.")
|
|
@@ -9245,7 +9341,7 @@ var PLAN_FRAGMENT_MARKETING_BRIEF = `
|
|
|
9245
9341
|
Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
|
|
9246
9342
|
Add to ACCEPTANCE CRITERIA: "[ ] Message Frame confirmed with Owner before drafting" and "[ ] Final content reviewed by Owner before publishing."`;
|
|
9247
9343
|
var PLAN_FRAGMENT_OPS_BRIEF = `
|
|
9248
|
-
**Ops brief task detection:** When a task's task type is "ops-brief", generate an OPS BRIEF handoff.
|
|
9344
|
+
**Ops brief task detection:** When a task's task type is "ops-brief", generate an OPS BRIEF handoff. Inside SCOPE (DO THIS), use these subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
9249
9345
|
- SYSTEM: Which system or service this ops task touches \u2014 Vercel, Railway, Supabase, GitHub Actions, DNS, etc.
|
|
9250
9346
|
- RISK: What could go wrong \u2014 data loss, downtime, broken deployments. Include estimated blast radius (e.g. "affects all authenticated users").
|
|
9251
9347
|
- ROLLBACK PLAN: Exact steps to undo the change if something breaks. Must be specific enough to execute under pressure.
|
|
@@ -10888,8 +10984,9 @@ async function applyHandoffs(adapter2, rawLlmOutput, cycleNumber, force = false)
|
|
|
10888
10984
|
}
|
|
10889
10985
|
const invalidFields = validateHandoffScope(parsed);
|
|
10890
10986
|
if (invalidFields.length > 0) {
|
|
10987
|
+
const scopeMissing = invalidFields.includes("scope");
|
|
10891
10988
|
warnings.push(
|
|
10892
|
-
`Rejected handoff for ${handoff.taskId}: missing or empty ${invalidFields.join(", ")}. Handoffs without explicit scope produce ambiguous builds.`
|
|
10989
|
+
`Rejected handoff for ${handoff.taskId}: missing or empty ${invalidFields.join(", ")}. Handoffs without explicit scope produce ambiguous builds.` + (scopeMissing ? ` If this is a bug/design-brief/research-brief/marketing-brief/ops-brief task, KEEP the "SCOPE (DO THIS)" header and nest the type-specific sections (REPRODUCE / ROOT CAUSE / MINIMAL FIX / \u2026) inside it \u2014 the parser only recognises the standard headers, so replacing SCOPE outright drops it entirely.` : "")
|
|
10893
10990
|
);
|
|
10894
10991
|
continue;
|
|
10895
10992
|
}
|
|
@@ -12894,7 +12991,7 @@ async function assertSingleActiveCycle(adapter2, opts = {}) {
|
|
|
12894
12991
|
}
|
|
12895
12992
|
return notes;
|
|
12896
12993
|
}
|
|
12897
|
-
async function validateAndPrepare(adapter2, force, callerUserId) {
|
|
12994
|
+
async function validateAndPrepare(adapter2, force, callerUserId, adapterType) {
|
|
12898
12995
|
let mode;
|
|
12899
12996
|
let cycleNumber;
|
|
12900
12997
|
let strategyReviewWarning = "";
|
|
@@ -12956,7 +13053,7 @@ Run \`strategy_review\` first, or pass \`force: true\` to bypass this gate.`
|
|
|
12956
13053
|
if (err instanceof Error && (err.message.startsWith("Strategy Review") || err.message.startsWith("Cycle ") || err.message.startsWith("Stale reviews"))) {
|
|
12957
13054
|
throw err;
|
|
12958
13055
|
}
|
|
12959
|
-
const isPg =
|
|
13056
|
+
const isPg = isDatabaseBackedAdapter(adapterType);
|
|
12960
13057
|
throw new Error(
|
|
12961
13058
|
isPg ? "Could not read cycle health from the database. Check your DATABASE_URL and verify the project exists." : "Could not read cycle health from PLANNING_LOG.md. Run setup first to initialise your PAPI project."
|
|
12962
13059
|
);
|
|
@@ -13074,7 +13171,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
|
|
|
13074
13171
|
tracker?.mark("validate_and_prepare");
|
|
13075
13172
|
let t = startTimer();
|
|
13076
13173
|
const prepareScope = await resolvePlanScope(adapter2, config2);
|
|
13077
|
-
const { mode, cycleNumber, strategyReviewWarning } = await validateAndPrepare(adapter2, force, prepareScope.callerUserId);
|
|
13174
|
+
const { mode, cycleNumber, strategyReviewWarning } = await validateAndPrepare(adapter2, force, prepareScope.callerUserId, config2.adapterType);
|
|
13078
13175
|
const validateMs = t();
|
|
13079
13176
|
const incomingCycle = cycleNumber + 1;
|
|
13080
13177
|
tracker?.setStreamScope({ cycle: incomingCycle });
|
|
@@ -16007,7 +16104,7 @@ async function prepareStrategyReview(adapter2, force, projectRoot, adapterType,
|
|
|
16007
16104
|
};
|
|
16008
16105
|
}
|
|
16009
16106
|
} catch {
|
|
16010
|
-
const isPg =
|
|
16107
|
+
const isPg = isDatabaseBackedAdapter(adapterType);
|
|
16011
16108
|
throw new Error(
|
|
16012
16109
|
isPg ? "Could not read cycle health from the database. Check your DATABASE_URL and verify the project exists." : "Could not read cycle health from PLANNING_LOG.md. Run setup first to initialise your PAPI project."
|
|
16013
16110
|
);
|
|
@@ -16451,13 +16548,13 @@ ${cleanContent}`;
|
|
|
16451
16548
|
${evidenceWarnings.map((w) => `- ${w}`).join("\n")}` : displayText;
|
|
16452
16549
|
return { cycleNumber, displayText: fullText, writeBackFailed };
|
|
16453
16550
|
}
|
|
16454
|
-
async function prepareStrategyChange(adapter2, text) {
|
|
16551
|
+
async function prepareStrategyChange(adapter2, text, adapterType) {
|
|
16455
16552
|
let cycleNumber;
|
|
16456
16553
|
try {
|
|
16457
16554
|
const health = await adapter2.getCycleHealth();
|
|
16458
16555
|
cycleNumber = health.totalCycles;
|
|
16459
16556
|
} catch {
|
|
16460
|
-
const isPg =
|
|
16557
|
+
const isPg = isDatabaseBackedAdapter(adapterType);
|
|
16461
16558
|
throw new Error(
|
|
16462
16559
|
isPg ? "Could not read cycle health from the database. Check your DATABASE_URL and verify the project exists." : "Could not read cycle health from PLANNING_LOG.md. Run setup first to initialise your PAPI project."
|
|
16463
16560
|
);
|
|
@@ -16976,7 +17073,7 @@ Decision event logged.`
|
|
|
16976
17073
|
return errorResponse("text is required for strategy_change. Describe the strategic shift to apply.");
|
|
16977
17074
|
}
|
|
16978
17075
|
{
|
|
16979
|
-
const result = await prepareStrategyChange(adapter2, text);
|
|
17076
|
+
const result = await prepareStrategyChange(adapter2, text, _config.adapterType);
|
|
16980
17077
|
return textResponse(
|
|
16981
17078
|
`## PAPI Strategy Change \u2014 Prepare Phase (Cycle ${result.cycleNumber})
|
|
16982
17079
|
|
package/dist/prompts.js
CHANGED
|
@@ -216,7 +216,7 @@ var PLAN_FRAGMENT_RESEARCH = `
|
|
|
216
216
|
var PLAN_FRAGMENT_BUG = `
|
|
217
217
|
**Bug task detection:** When a task's task type is "bug" or the title starts with "Bug:" or "Fix:", apply these rules:
|
|
218
218
|
- **Auto-P1:** If the task's current priority is P2 or lower, upgrade it to "P1 High" via a boardCorrections entry in Part 2. Note the upgrade in Part 1 analysis.
|
|
219
|
-
-
|
|
219
|
+
- Inside SCOPE (DO THIS), use these bug-specific subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
220
220
|
- **REPRODUCE:** Exact steps to reproduce the bug before touching any code. If the task notes describe the symptoms, include them. If not, the first build step is "confirm the bug reproduces."
|
|
221
221
|
- **ROOT CAUSE:** One-sentence hypothesis for the root cause (what is wrong, not what the user sees). The builder must confirm or correct this before implementing a fix.
|
|
222
222
|
- **MINIMAL FIX:** The smallest code change that resolves the root cause. "Bug fix \u2014 minimal blast radius. Change only what is necessary. Do not refactor surrounding code or expand scope."
|
|
@@ -241,7 +241,7 @@ var PLAN_FRAGMENT_SPIKE = `
|
|
|
241
241
|
- Keep SCOPE BOUNDARY, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
|
|
242
242
|
- 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.`;
|
|
243
243
|
var PLAN_FRAGMENT_DESIGN_BRIEF = `
|
|
244
|
-
**Design brief task detection:** When a task's task type is "design-brief", generate a DESIGN BRIEF handoff.
|
|
244
|
+
**Design brief task detection:** When a task's task type is "design-brief", generate a DESIGN BRIEF handoff. Inside SCOPE (DO THIS), use these type-specific subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
245
245
|
- AUDIENCE: Who this design is for \u2014 persona and context of use (e.g. "non-technical Owner, first dashboard visit")
|
|
246
246
|
- BRAND CONSTRAINTS: Palette, typography, tone \u2014 pull from \`.impeccable.md\` (dev patterns, anti-patterns, component rules) AND \`docs/branding/brand-book.html\` (brand identity, positioning, voice canon) if present. If neither exists, state "No brand doc \u2014 Owner should define constraints before starting."
|
|
247
247
|
- DELIVERABLE FORMAT: What the output looks like \u2014 design handoff package / annotated mockup / style spec. Be specific so the person doing the work knows what "done" means.
|
|
@@ -249,7 +249,7 @@ var PLAN_FRAGMENT_DESIGN_BRIEF = `
|
|
|
249
249
|
Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION sections as normal.
|
|
250
250
|
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."`;
|
|
251
251
|
var PLAN_FRAGMENT_RESEARCH_BRIEF = `
|
|
252
|
-
**Research brief task detection:** When a task's task type is "research-brief", generate a RESEARCH BRIEF handoff.
|
|
252
|
+
**Research brief task detection:** When a task's task type is "research-brief", generate a RESEARCH BRIEF handoff. Inside SCOPE (DO THIS), use these subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
253
253
|
- 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?")
|
|
254
254
|
- TIME-BOX: Maximum effort allowed \u2014 XS or S. Stop when the time-box is hit and report what was found, even if incomplete.
|
|
255
255
|
- OUTPUT: Where findings land \u2014 a doc at \`docs/research/[topic]-findings.md\` or inline in the build report. State the path.
|
|
@@ -257,7 +257,7 @@ var PLAN_FRAGMENT_RESEARCH_BRIEF = `
|
|
|
257
257
|
Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
|
|
258
258
|
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."`;
|
|
259
259
|
var PLAN_FRAGMENT_MARKETING_BRIEF = `
|
|
260
|
-
**Marketing brief task detection:** When a task's task type is "marketing-brief", generate a MARKETING BRIEF handoff.
|
|
260
|
+
**Marketing brief task detection:** When a task's task type is "marketing-brief", generate a MARKETING BRIEF handoff. Inside SCOPE (DO THIS), use these subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
261
261
|
- AUDIENCE: Who this marketing content targets \u2014 persona, awareness level, channel context (e.g. "cold Discord visitor, zero PAPI context")
|
|
262
262
|
- CHANNEL: Where this content lives \u2014 Discord, landing page, email, social, etc.
|
|
263
263
|
- 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.")
|
|
@@ -265,7 +265,7 @@ var PLAN_FRAGMENT_MARKETING_BRIEF = `
|
|
|
265
265
|
Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION as normal.
|
|
266
266
|
Add to ACCEPTANCE CRITERIA: "[ ] Message Frame confirmed with Owner before drafting" and "[ ] Final content reviewed by Owner before publishing."`;
|
|
267
267
|
var PLAN_FRAGMENT_OPS_BRIEF = `
|
|
268
|
-
**Ops brief task detection:** When a task's task type is "ops-brief", generate an OPS BRIEF handoff.
|
|
268
|
+
**Ops brief task detection:** When a task's task type is "ops-brief", generate an OPS BRIEF handoff. Inside SCOPE (DO THIS), use these subsections. KEEP the "SCOPE (DO THIS)" header and put these subsections INSIDE it \u2014 the handoff parser only recognises the standard headers (packages/adapter-md build-handoff.ts SECTION_HEADERS) and a handoff with no SCOPE section is rejected outright (task-3050, C357):
|
|
269
269
|
- SYSTEM: Which system or service this ops task touches \u2014 Vercel, Railway, Supabase, GitHub Actions, DNS, etc.
|
|
270
270
|
- RISK: What could go wrong \u2014 data loss, downtime, broken deployments. Include estimated blast radius (e.g. "affects all authenticated users").
|
|
271
271
|
- ROLLBACK PLAN: Exact steps to undo the change if something breaks. Must be specific enough to execute under pressure.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.77",
|
|
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
|
"mcpName": "io.github.getpapi/papi",
|