@papi-ai/server 0.7.12 → 0.7.13
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 +618 -53
- package/dist/prompts.js +6 -0
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -2068,6 +2068,102 @@ ${footer}`);
|
|
|
2068
2068
|
await this.write("STRATEGY_RECOMMENDATIONS.md", updated);
|
|
2069
2069
|
}
|
|
2070
2070
|
// -------------------------------------------------------------------------
|
|
2071
|
+
// Strategy Review Agenda (markdown persistence)
|
|
2072
|
+
// -------------------------------------------------------------------------
|
|
2073
|
+
async addAgendaTopic(input) {
|
|
2074
|
+
const id = randomUUID6();
|
|
2075
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2076
|
+
const full = {
|
|
2077
|
+
id,
|
|
2078
|
+
topic: input.topic,
|
|
2079
|
+
source: input.source,
|
|
2080
|
+
sourceCycle: input.sourceCycle,
|
|
2081
|
+
status: "pending",
|
|
2082
|
+
createdAt
|
|
2083
|
+
};
|
|
2084
|
+
const content = await this.readOptional("STRATEGY_REVIEW_AGENDA.md");
|
|
2085
|
+
const header = "# Strategy Review Agenda\n\n<!-- PAPI-ADAPTER: parse the yaml block below -->\n\n<!-- PAPI-YAML-START -->\ntopics:\n";
|
|
2086
|
+
const footer = "<!-- PAPI-YAML-END -->\n";
|
|
2087
|
+
const entry = [
|
|
2088
|
+
` - id: ${full.id}`,
|
|
2089
|
+
` topic: ${JSON.stringify(full.topic)}`,
|
|
2090
|
+
` source: ${full.source}`,
|
|
2091
|
+
full.sourceCycle != null ? ` source_cycle: ${full.sourceCycle}` : null,
|
|
2092
|
+
` status: ${full.status}`,
|
|
2093
|
+
` created_at: ${full.createdAt}`
|
|
2094
|
+
].filter(Boolean).join("\n");
|
|
2095
|
+
if (!content) {
|
|
2096
|
+
await this.write("STRATEGY_REVIEW_AGENDA.md", `${header}${entry}
|
|
2097
|
+
${footer}`);
|
|
2098
|
+
} else {
|
|
2099
|
+
const insertPoint = content.indexOf("<!-- PAPI-YAML-END -->");
|
|
2100
|
+
if (insertPoint === -1) {
|
|
2101
|
+
await this.write("STRATEGY_REVIEW_AGENDA.md", `${header}${entry}
|
|
2102
|
+
${footer}`);
|
|
2103
|
+
} else {
|
|
2104
|
+
const updated = content.slice(0, insertPoint) + entry + "\n" + content.slice(insertPoint);
|
|
2105
|
+
await this.write("STRATEGY_REVIEW_AGENDA.md", updated);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return full;
|
|
2109
|
+
}
|
|
2110
|
+
async getPendingAgendaTopics() {
|
|
2111
|
+
const content = await this.readOptional("STRATEGY_REVIEW_AGENDA.md");
|
|
2112
|
+
if (!content) return [];
|
|
2113
|
+
const yamlStart = content.indexOf("<!-- PAPI-YAML-START -->");
|
|
2114
|
+
const yamlEnd = content.indexOf("<!-- PAPI-YAML-END -->");
|
|
2115
|
+
if (yamlStart === -1 || yamlEnd === -1) return [];
|
|
2116
|
+
const yamlBlock = content.slice(yamlStart + "<!-- PAPI-YAML-START -->".length, yamlEnd).trim();
|
|
2117
|
+
const entries = yamlBlock.split(/(?=\s+-\s+id:)/);
|
|
2118
|
+
const topics = [];
|
|
2119
|
+
for (const block of entries) {
|
|
2120
|
+
const idMatch = block.match(/id:\s+(.+)/);
|
|
2121
|
+
const topicMatch = block.match(/topic:\s+(.+)/);
|
|
2122
|
+
const sourceMatch = block.match(/source:\s+(\S+)/);
|
|
2123
|
+
const statusMatch = block.match(/status:\s+(\S+)/);
|
|
2124
|
+
const createdMatch = block.match(/created_at:\s+(.+)/);
|
|
2125
|
+
const sourceCycleMatch = block.match(/source_cycle:\s+(\d+)/);
|
|
2126
|
+
if (!idMatch || !topicMatch || !sourceMatch || !statusMatch || !createdMatch) continue;
|
|
2127
|
+
if (statusMatch[1].trim() !== "pending") continue;
|
|
2128
|
+
let parsedTopic = topicMatch[1].trim();
|
|
2129
|
+
if (parsedTopic.startsWith('"') && parsedTopic.endsWith('"')) {
|
|
2130
|
+
try {
|
|
2131
|
+
parsedTopic = JSON.parse(parsedTopic);
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
topics.push({
|
|
2136
|
+
id: idMatch[1].trim(),
|
|
2137
|
+
topic: parsedTopic,
|
|
2138
|
+
source: sourceMatch[1].trim(),
|
|
2139
|
+
sourceCycle: sourceCycleMatch ? parseInt(sourceCycleMatch[1], 10) : void 0,
|
|
2140
|
+
status: "pending",
|
|
2141
|
+
createdAt: createdMatch[1].trim()
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
return topics;
|
|
2145
|
+
}
|
|
2146
|
+
async markAgendaTopicsAddressed(ids, cycleNumber) {
|
|
2147
|
+
if (ids.length === 0) return;
|
|
2148
|
+
const content = await this.readOptional("STRATEGY_REVIEW_AGENDA.md");
|
|
2149
|
+
if (!content) return;
|
|
2150
|
+
let updated = content;
|
|
2151
|
+
const addressedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2152
|
+
for (const id of ids) {
|
|
2153
|
+
const statusPattern = new RegExp(`(\\s+-\\s+id:\\s+${id}\\n(?:.*\\n)*?\\s+status:\\s+)pending`);
|
|
2154
|
+
updated = updated.replace(statusPattern, `$1addressed`);
|
|
2155
|
+
const insertionAnchor = new RegExp(`(\\s+-\\s+id:\\s+${id}\\n(?:.*\\n)*?\\s+created_at:\\s+[^\\n]+)\\n`);
|
|
2156
|
+
const match = updated.match(insertionAnchor);
|
|
2157
|
+
if (match && !match[0].includes("addressed_at:")) {
|
|
2158
|
+
updated = updated.replace(insertionAnchor, `$1
|
|
2159
|
+
addressed_at: ${addressedAt}
|
|
2160
|
+
addressed_in_review: ${cycleNumber}
|
|
2161
|
+
`);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
await this.write("STRATEGY_REVIEW_AGENDA.md", updated);
|
|
2165
|
+
}
|
|
2166
|
+
// -------------------------------------------------------------------------
|
|
2071
2167
|
// Decision Events & Scores (markdown persistence)
|
|
2072
2168
|
// -------------------------------------------------------------------------
|
|
2073
2169
|
async appendDecisionEvent(event) {
|
|
@@ -7569,6 +7665,50 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
7569
7665
|
UPDATE strategy_recommendations
|
|
7570
7666
|
SET status = 'actioned', dismissal_reason = ${reason}, updated_at = now()
|
|
7571
7667
|
WHERE id = ${id} AND project_id = ${this.projectId}
|
|
7668
|
+
`;
|
|
7669
|
+
}
|
|
7670
|
+
// -------------------------------------------------------------------------
|
|
7671
|
+
// Strategy Review Agenda
|
|
7672
|
+
// -------------------------------------------------------------------------
|
|
7673
|
+
async addAgendaTopic(input) {
|
|
7674
|
+
const [row] = await this.sql`
|
|
7675
|
+
INSERT INTO strategy_review_agenda (project_id, topic, source, source_cycle)
|
|
7676
|
+
VALUES (${this.projectId}, ${input.topic}, ${input.source}, ${input.sourceCycle ?? null})
|
|
7677
|
+
RETURNING id, topic, source, source_cycle, status, created_at
|
|
7678
|
+
`;
|
|
7679
|
+
return {
|
|
7680
|
+
id: row.id,
|
|
7681
|
+
topic: row.topic,
|
|
7682
|
+
source: row.source,
|
|
7683
|
+
sourceCycle: row.source_cycle ?? void 0,
|
|
7684
|
+
status: row.status,
|
|
7685
|
+
createdAt: row.created_at
|
|
7686
|
+
};
|
|
7687
|
+
}
|
|
7688
|
+
async getPendingAgendaTopics() {
|
|
7689
|
+
const rows = await this.sql`
|
|
7690
|
+
SELECT id, topic, source, source_cycle, status, created_at, addressed_at, addressed_in_review
|
|
7691
|
+
FROM strategy_review_agenda
|
|
7692
|
+
WHERE project_id = ${this.projectId} AND status = 'pending'
|
|
7693
|
+
ORDER BY created_at ASC
|
|
7694
|
+
`;
|
|
7695
|
+
return rows.map((r) => ({
|
|
7696
|
+
id: r.id,
|
|
7697
|
+
topic: r.topic,
|
|
7698
|
+
source: r.source,
|
|
7699
|
+
sourceCycle: r.source_cycle ?? void 0,
|
|
7700
|
+
status: "pending",
|
|
7701
|
+
createdAt: r.created_at,
|
|
7702
|
+
addressedAt: r.addressed_at ?? void 0,
|
|
7703
|
+
addressedInReview: r.addressed_in_review ?? void 0
|
|
7704
|
+
}));
|
|
7705
|
+
}
|
|
7706
|
+
async markAgendaTopicsAddressed(ids, cycleNumber) {
|
|
7707
|
+
if (ids.length === 0) return;
|
|
7708
|
+
await this.sql`
|
|
7709
|
+
UPDATE strategy_review_agenda
|
|
7710
|
+
SET status = 'addressed', addressed_at = now(), addressed_in_review = ${cycleNumber}
|
|
7711
|
+
WHERE project_id = ${this.projectId} AND id = ANY(${ids}::uuid[])
|
|
7572
7712
|
`;
|
|
7573
7713
|
}
|
|
7574
7714
|
// -------------------------------------------------------------------------
|
|
@@ -8761,6 +8901,7 @@ __export(git_exports, {
|
|
|
8761
8901
|
getHeadCommitSha: () => getHeadCommitSha,
|
|
8762
8902
|
getLatestTag: () => getLatestTag,
|
|
8763
8903
|
getOriginRepoSlug: () => getOriginRepoSlug,
|
|
8904
|
+
getPullRequestUrl: () => getPullRequestUrl,
|
|
8764
8905
|
getUnmergedBranches: () => getUnmergedBranches,
|
|
8765
8906
|
gitPull: () => gitPull,
|
|
8766
8907
|
gitPush: () => gitPush,
|
|
@@ -8770,9 +8911,11 @@ __export(git_exports, {
|
|
|
8770
8911
|
isGhAvailable: () => isGhAvailable,
|
|
8771
8912
|
isGitAvailable: () => isGitAvailable,
|
|
8772
8913
|
isGitRepo: () => isGitRepo,
|
|
8914
|
+
listGroupedCycleBranches: () => listGroupedCycleBranches,
|
|
8773
8915
|
mergePullRequest: () => mergePullRequest,
|
|
8774
8916
|
resolveBaseBranch: () => resolveBaseBranch,
|
|
8775
8917
|
runAutoCommit: () => runAutoCommit,
|
|
8918
|
+
squashMergePullRequest: () => squashMergePullRequest,
|
|
8776
8919
|
stageAllAndCommit: () => stageAllAndCommit,
|
|
8777
8920
|
stageDirAndCommit: () => stageDirAndCommit,
|
|
8778
8921
|
tagExists: () => tagExists,
|
|
@@ -9226,6 +9369,68 @@ function runAutoCommit(projectRoot, commitFn) {
|
|
|
9226
9369
|
return `Auto-commit failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
9227
9370
|
}
|
|
9228
9371
|
}
|
|
9372
|
+
function getPullRequestUrl(cwd, branch) {
|
|
9373
|
+
try {
|
|
9374
|
+
const output = execFileSync(
|
|
9375
|
+
"gh",
|
|
9376
|
+
["pr", "view", branch, "--json", "url", "--jq", ".url"],
|
|
9377
|
+
{ cwd, encoding: "utf-8" }
|
|
9378
|
+
).trim();
|
|
9379
|
+
return output || null;
|
|
9380
|
+
} catch {
|
|
9381
|
+
return null;
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9384
|
+
function squashMergePullRequest(cwd, branch) {
|
|
9385
|
+
const repo = getOriginRepoSlug(cwd);
|
|
9386
|
+
const baseArgs = ["pr", "merge", branch, "--squash", "--delete-branch"];
|
|
9387
|
+
if (repo) baseArgs.push("--repo", repo);
|
|
9388
|
+
for (let attempt = 1; attempt <= MERGE_MAX_RETRIES; attempt++) {
|
|
9389
|
+
try {
|
|
9390
|
+
execFileSync("gh", baseArgs, { cwd, encoding: "utf-8" });
|
|
9391
|
+
return { success: true, message: `Squash-merged PR for '${branch}' and deleted branch.` };
|
|
9392
|
+
} catch (err) {
|
|
9393
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9394
|
+
if (msg.includes("not mergeable") && attempt < MERGE_MAX_RETRIES) {
|
|
9395
|
+
sleepSync(MERGE_RETRY_DELAY_MS);
|
|
9396
|
+
continue;
|
|
9397
|
+
}
|
|
9398
|
+
return { success: false, message: `PR squash-merge failed: ${msg}` };
|
|
9399
|
+
}
|
|
9400
|
+
}
|
|
9401
|
+
return { success: false, message: "PR squash-merge failed: max retries exceeded" };
|
|
9402
|
+
}
|
|
9403
|
+
function listGroupedCycleBranches(cwd, cycleNum, baseBranch) {
|
|
9404
|
+
const prefix = `feat/cycle-${cycleNum}-`;
|
|
9405
|
+
try {
|
|
9406
|
+
const remoteOutput = execFileSync(
|
|
9407
|
+
"git",
|
|
9408
|
+
["ls-remote", "--heads", "origin", `${prefix}*`],
|
|
9409
|
+
{ cwd, encoding: "utf-8" }
|
|
9410
|
+
).trim();
|
|
9411
|
+
if (!remoteOutput) return [];
|
|
9412
|
+
const remoteBranches = remoteOutput.split("\n").map((line) => line.split(" ")[1]?.replace("refs/heads/", "").trim()).filter((b2) => !!b2 && b2.startsWith(prefix));
|
|
9413
|
+
return remoteBranches.filter((branch) => {
|
|
9414
|
+
try {
|
|
9415
|
+
const branchTip = execFileSync(
|
|
9416
|
+
"git",
|
|
9417
|
+
["rev-parse", `origin/${branch}`],
|
|
9418
|
+
{ cwd, encoding: "utf-8" }
|
|
9419
|
+
).trim();
|
|
9420
|
+
execFileSync(
|
|
9421
|
+
"git",
|
|
9422
|
+
["merge-base", "--is-ancestor", branchTip, baseBranch],
|
|
9423
|
+
{ cwd, stdio: "ignore" }
|
|
9424
|
+
);
|
|
9425
|
+
return false;
|
|
9426
|
+
} catch {
|
|
9427
|
+
return true;
|
|
9428
|
+
}
|
|
9429
|
+
});
|
|
9430
|
+
} catch {
|
|
9431
|
+
return getUnmergedBranches(cwd, baseBranch).filter((b2) => b2.startsWith(prefix));
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9229
9434
|
function getFilesChangedFromBase(cwd, baseBranch) {
|
|
9230
9435
|
try {
|
|
9231
9436
|
const mergeBase = execFileSync("git", ["merge-base", baseBranch, "HEAD"], { cwd, encoding: "utf-8" }).trim();
|
|
@@ -9277,6 +9482,8 @@ function loadConfig() {
|
|
|
9277
9482
|
const lightMode = process.env.PAPI_LIGHT_MODE === "true";
|
|
9278
9483
|
const projectOwner = process.env.PAPI_OWNER ?? "Cathal";
|
|
9279
9484
|
const skipProjectSpecificRules = process.env.PAPI_SKIP_PROJECT_RULES === "true";
|
|
9485
|
+
const userId = process.env.PAPI_USER_ID || void 0;
|
|
9486
|
+
const telemetryEnabled = process.env.PAPI_TELEMETRY !== "off" && process.env.PAPI_TELEMETRY !== "false";
|
|
9280
9487
|
const papiEndpoint = process.env.PAPI_ENDPOINT;
|
|
9281
9488
|
const dataEndpoint = process.env.PAPI_DATA_ENDPOINT;
|
|
9282
9489
|
const databaseUrl = process.env.DATABASE_URL;
|
|
@@ -9287,9 +9494,18 @@ function loadConfig() {
|
|
|
9287
9494
|
adapterType = "proxy";
|
|
9288
9495
|
console.error("[papi] PAPI_PROJECT_ID detected \u2014 switching to proxy adapter (md adapter blocked for external users).");
|
|
9289
9496
|
}
|
|
9290
|
-
if (
|
|
9497
|
+
if (adapterType === "md" && !userId) {
|
|
9291
9498
|
throw new Error(
|
|
9292
|
-
|
|
9499
|
+
`PAPI requires a free account to run in local mode.
|
|
9500
|
+
|
|
9501
|
+
Create your account at https://getpapi.ai/setup \u2014 it takes under a minute.
|
|
9502
|
+
Your project data stays on your machine. The account lets PAPI identify you
|
|
9503
|
+
and unlocks dashboard features when you're ready.
|
|
9504
|
+
|
|
9505
|
+
After signing up, add this to your .mcp.json env config:
|
|
9506
|
+
"PAPI_USER_ID": "your-email@example.com"
|
|
9507
|
+
|
|
9508
|
+
Already have an account? Make sure PAPI_USER_ID is set in your .mcp.json env config.`
|
|
9293
9509
|
);
|
|
9294
9510
|
}
|
|
9295
9511
|
return {
|
|
@@ -9303,7 +9519,9 @@ function loadConfig() {
|
|
|
9303
9519
|
papiEndpoint,
|
|
9304
9520
|
lightMode,
|
|
9305
9521
|
projectOwner,
|
|
9306
|
-
skipProjectSpecificRules
|
|
9522
|
+
skipProjectSpecificRules,
|
|
9523
|
+
userId,
|
|
9524
|
+
telemetryEnabled
|
|
9307
9525
|
};
|
|
9308
9526
|
}
|
|
9309
9527
|
|
|
@@ -9396,7 +9614,25 @@ async function createAdapter(optionsOrType, maybePapiDir) {
|
|
|
9396
9614
|
console.error("[papi] Set PAPI_USER_ID in your .mcp.json env to fix this.");
|
|
9397
9615
|
}
|
|
9398
9616
|
}
|
|
9399
|
-
|
|
9617
|
+
let skipCreate = false;
|
|
9618
|
+
if (userId) {
|
|
9619
|
+
const bySlug = await pgAdapter.listProjects({ slug });
|
|
9620
|
+
const userDup = bySlug.find((p) => p.user_id === userId);
|
|
9621
|
+
if (userDup) {
|
|
9622
|
+
console.error(`[papi] \u26A0 Project '${slug}' already exists for this user (id: ${userDup.id}).`);
|
|
9623
|
+
console.error(`[papi] Update PAPI_PROJECT_ID=${userDup.id} in .mcp.json to avoid a duplicate.`);
|
|
9624
|
+
skipCreate = true;
|
|
9625
|
+
}
|
|
9626
|
+
}
|
|
9627
|
+
if (!skipCreate) {
|
|
9628
|
+
await pgAdapter.createProject({ id: projectId, slug, name: slug, papi_dir: papiDir, user_id: userId });
|
|
9629
|
+
}
|
|
9630
|
+
} else if (existing.user_id) {
|
|
9631
|
+
const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
|
|
9632
|
+
if (configuredUserId && existing.user_id !== configuredUserId) {
|
|
9633
|
+
console.error(`[papi] \u26A0 PAPI_PROJECT_ID=${projectId} belongs to a different user.`);
|
|
9634
|
+
console.error("[papi] Run papi setup or update PAPI_PROJECT_ID in .mcp.json.");
|
|
9635
|
+
}
|
|
9400
9636
|
}
|
|
9401
9637
|
await pgAdapter.close();
|
|
9402
9638
|
} catch {
|
|
@@ -10835,6 +11071,9 @@ function buildPlanUserMessage(ctx) {
|
|
|
10835
11071
|
}) : PLAN_FULL_INSTRUCTIONS;
|
|
10836
11072
|
parts.push(instructions);
|
|
10837
11073
|
}
|
|
11074
|
+
if (ctx.foundationalTasksGuidance) {
|
|
11075
|
+
parts.push("", ctx.foundationalTasksGuidance);
|
|
11076
|
+
}
|
|
10838
11077
|
if (ctx.skipHandoffs) {
|
|
10839
11078
|
parts.push(
|
|
10840
11079
|
"",
|
|
@@ -11356,6 +11595,9 @@ function buildReviewUserMessage(ctx) {
|
|
|
11356
11595
|
if (ctx.docActionStaleness) {
|
|
11357
11596
|
parts.push("### Doc Action Staleness", "", ctx.docActionStaleness, "");
|
|
11358
11597
|
}
|
|
11598
|
+
if (ctx.pendingAgendaTopics) {
|
|
11599
|
+
parts.push("### Queued Agenda Topics", "", "_Topics queued via `strategy_agenda` since the last review. Address each one in this review \u2014 they will be auto-marked as addressed on apply._", "", ctx.pendingAgendaTopics, "");
|
|
11600
|
+
}
|
|
11359
11601
|
return parts.join("\n");
|
|
11360
11602
|
}
|
|
11361
11603
|
function parseReviewStructuredOutput(raw) {
|
|
@@ -12042,6 +12284,64 @@ function stripTasksForPlan(tasks) {
|
|
|
12042
12284
|
hasHandoff: !!buildHandoff
|
|
12043
12285
|
}));
|
|
12044
12286
|
}
|
|
12287
|
+
var BRIEF_SECTIONS = [
|
|
12288
|
+
{ name: "title", pattern: /^#\s+\S/m },
|
|
12289
|
+
{
|
|
12290
|
+
name: "target audience",
|
|
12291
|
+
pattern: /\b(target users?|audience|for whom|who (it'?s for|uses|it serves|we're building))/i
|
|
12292
|
+
},
|
|
12293
|
+
{
|
|
12294
|
+
name: "problem statement",
|
|
12295
|
+
pattern: /\b(problem|pain point|why it matters|what problem|solves?)/i
|
|
12296
|
+
},
|
|
12297
|
+
{
|
|
12298
|
+
name: "solution / vision",
|
|
12299
|
+
pattern: /\b(solution|approach|vision|what (it|we) do|how it works|value proposition)/i
|
|
12300
|
+
},
|
|
12301
|
+
{
|
|
12302
|
+
name: "GTM / distribution",
|
|
12303
|
+
pattern: /\b(GTM|go[- ]to[- ]market|distribution|channel|pricing|how (users?|people) (discover|find|reach))/i
|
|
12304
|
+
}
|
|
12305
|
+
];
|
|
12306
|
+
function assessBriefThinness(brief) {
|
|
12307
|
+
const TEMPLATE_MARKER = "*Describe your project's core value proposition here.*";
|
|
12308
|
+
const briefWithoutTemplate = brief.replace(TEMPLATE_MARKER, "");
|
|
12309
|
+
const populated = [];
|
|
12310
|
+
const missing = [];
|
|
12311
|
+
for (const section of BRIEF_SECTIONS) {
|
|
12312
|
+
if (section.pattern.test(briefWithoutTemplate)) populated.push(section.name);
|
|
12313
|
+
else missing.push(section.name);
|
|
12314
|
+
}
|
|
12315
|
+
return { populatedSections: populated, missingSections: missing };
|
|
12316
|
+
}
|
|
12317
|
+
function computeFoundationalTasksGuidance(cycleNumber, brief) {
|
|
12318
|
+
if (cycleNumber > 1) return void 0;
|
|
12319
|
+
const { populatedSections, missingSections } = assessBriefThinness(brief);
|
|
12320
|
+
if (populatedSections.length >= 4) return void 0;
|
|
12321
|
+
const populatedList = populatedSections.length > 0 ? populatedSections.join(", ") : "none";
|
|
12322
|
+
const missingList = missingSections.join(", ");
|
|
12323
|
+
return [
|
|
12324
|
+
"## FOUNDATIONAL TASKS GUIDANCE",
|
|
12325
|
+
"",
|
|
12326
|
+
`The project brief is thin \u2014 only ${populatedSections.length} of 5 key sections are clearly populated (${populatedList}). Missing or weak sections: ${missingList}.`,
|
|
12327
|
+
"",
|
|
12328
|
+
"Generate 3-5 foundational research/discovery tasks targeting the MISSING sections. These help the Owner fill in their brief before the product gets built on top of shaky foundations.",
|
|
12329
|
+
"",
|
|
12330
|
+
"Rules for foundational tasks:",
|
|
12331
|
+
"- **ADDITIONS, not replacements.** Include them ALONGSIDE the user's backlog items. If the user submitted 3 ideas, the cycle should have 3 user items + 3-5 foundational items (total 6-8 tasks).",
|
|
12332
|
+
'- **Target specific missing sections.** Do NOT generate "refine audience" if audience is already populated. Each foundational task must close a specific gap above.',
|
|
12333
|
+
"- **Task type: `research` or `discovery`** \u2014 deliverable is a findings doc, not shipped code.",
|
|
12334
|
+
"- **Effort: S or M** \u2014 foundational work should be timeboxed, not open-ended.",
|
|
12335
|
+
'- **WHY in the handoff must tie to the specific gap.** Example: "Brief is thin on GTM \u2014 knowing how users discover the product is a prerequisite for scoping distribution work." Not generic.',
|
|
12336
|
+
"- **Cap at 5 foundational tasks** to avoid drowning the user.",
|
|
12337
|
+
"- **Cycle 2+ will NOT receive this guidance** \u2014 even if the brief stays thin. Foundational work is a one-shot onboarding primitive, not a recurring pattern.",
|
|
12338
|
+
"",
|
|
12339
|
+
"In the structured output:",
|
|
12340
|
+
"- Emit foundational tasks via the `newTasks` array (mark `reviewed: true`, type `research` or `discovery`).",
|
|
12341
|
+
"- Generate full BUILD HANDOFFs for each in `cycleHandoffs` \u2014 use `new-N` IDs to reference them.",
|
|
12342
|
+
"- User-submitted backlog tasks remain in `cycleHandoffs` as usual \u2014 do NOT drop them in favour of foundational tasks."
|
|
12343
|
+
].join("\n");
|
|
12344
|
+
}
|
|
12045
12345
|
function detectBoardFlags(tasks) {
|
|
12046
12346
|
let hasBugTasks = false;
|
|
12047
12347
|
let hasResearchTasks = false;
|
|
@@ -12320,7 +12620,8 @@ ${lines.join("\n")}`;
|
|
|
12320
12620
|
preAssignedTasks: preAssignedTextLean,
|
|
12321
12621
|
recentlyShippedCapabilities: recentlyShippedLean,
|
|
12322
12622
|
strategyReviewCadence,
|
|
12323
|
-
candidateTaskFullNotes: candidateTaskFullNotesLean
|
|
12623
|
+
candidateTaskFullNotes: candidateTaskFullNotesLean,
|
|
12624
|
+
foundationalTasksGuidance: computeFoundationalTasksGuidance(health.totalCycles, productBrief)
|
|
12324
12625
|
};
|
|
12325
12626
|
const { label: leanTierLabel } = applyContextTier(ctx2, health.totalCycles);
|
|
12326
12627
|
ctx2.contextTier = leanTierLabel;
|
|
@@ -12482,7 +12783,8 @@ ${logLines}`);
|
|
|
12482
12783
|
preAssignedTasks: preAssignedText,
|
|
12483
12784
|
recentlyShippedCapabilities: formatRecentlyShippedCapabilities(reports),
|
|
12484
12785
|
strategyReviewCadence: strategyReviewCadenceFull,
|
|
12485
|
-
candidateTaskFullNotes: formatCandidateTaskFullNotes(plannerTasks)
|
|
12786
|
+
candidateTaskFullNotes: formatCandidateTaskFullNotes(plannerTasks),
|
|
12787
|
+
foundationalTasksGuidance: computeFoundationalTasksGuidance(health.totalCycles, productBrief)
|
|
12486
12788
|
};
|
|
12487
12789
|
const { label: fullTierLabel } = applyContextTier(ctx, health.totalCycles);
|
|
12488
12790
|
ctx.contextTier = fullTierLabel;
|
|
@@ -13863,6 +14165,7 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
|
|
|
13863
14165
|
// Doc registry — docs with pending actions for staleness audit
|
|
13864
14166
|
adapter2.searchDocs?.({ hasPendingActions: true, limit: 20 })?.catch(() => []) ?? Promise.resolve([])
|
|
13865
14167
|
]);
|
|
14168
|
+
const pendingAgendaTopics = await (adapter2.getPendingAgendaTopics?.().catch(() => []) ?? Promise.resolve([]));
|
|
13866
14169
|
const tasks = [...activeTasks, ...recentDoneTasks];
|
|
13867
14170
|
const existingAdIds = new Set(decisions.map((d) => d.id));
|
|
13868
14171
|
const survivingPendingRecs = [];
|
|
@@ -14100,6 +14403,15 @@ ${deferred.join("\n")}`);
|
|
|
14100
14403
|
}
|
|
14101
14404
|
}
|
|
14102
14405
|
} catch {
|
|
14406
|
+
}
|
|
14407
|
+
let pendingAgendaText;
|
|
14408
|
+
if (pendingAgendaTopics.length > 0) {
|
|
14409
|
+
const lines = pendingAgendaTopics.map((t, i) => {
|
|
14410
|
+
const cycleSuffix = t.sourceCycle != null ? ` (queued Cycle ${t.sourceCycle})` : "";
|
|
14411
|
+
return `${i + 1}. ${t.topic} _[${t.source}${cycleSuffix}]_`;
|
|
14412
|
+
});
|
|
14413
|
+
pendingAgendaText = `${pendingAgendaTopics.length} topic(s) queued via strategy_agenda:
|
|
14414
|
+
${lines.join("\n")}`;
|
|
14103
14415
|
}
|
|
14104
14416
|
logDataSourceSummary("strategy_review_audit", [
|
|
14105
14417
|
{ label: "discoveryCanvas", hasData: discoveryCanvasText !== void 0 },
|
|
@@ -14140,7 +14452,8 @@ ${deferred.join("\n")}`);
|
|
|
14140
14452
|
recentPlans: recentPlansText,
|
|
14141
14453
|
unregisteredDocs: unregisteredDocsText,
|
|
14142
14454
|
taskComments: taskCommentsText,
|
|
14143
|
-
docActionStaleness: docActionStalenessText
|
|
14455
|
+
docActionStaleness: docActionStalenessText,
|
|
14456
|
+
pendingAgendaTopics: pendingAgendaText
|
|
14144
14457
|
};
|
|
14145
14458
|
const BUDGET_SOFT2 = 5e4;
|
|
14146
14459
|
const BUDGET_HARD2 = 6e4;
|
|
@@ -14455,6 +14768,15 @@ async function processReviewOutput(adapter2, rawOutput, cycleNumber) {
|
|
|
14455
14768
|
await adapter2.clearPendingReviewResponse?.();
|
|
14456
14769
|
} catch {
|
|
14457
14770
|
}
|
|
14771
|
+
try {
|
|
14772
|
+
if (adapter2.getPendingAgendaTopics && adapter2.markAgendaTopicsAddressed) {
|
|
14773
|
+
const pending = await adapter2.getPendingAgendaTopics();
|
|
14774
|
+
if (pending.length > 0) {
|
|
14775
|
+
await adapter2.markAgendaTopicsAddressed(pending.map((t) => t.id), cycleNumber);
|
|
14776
|
+
}
|
|
14777
|
+
}
|
|
14778
|
+
} catch {
|
|
14779
|
+
}
|
|
14458
14780
|
const webhookUrl = process.env.PAPI_SLACK_WEBHOOK_URL;
|
|
14459
14781
|
slackWarning = await sendSlackWebhook(webhookUrl, buildSlackSummary(data));
|
|
14460
14782
|
}
|
|
@@ -14975,6 +15297,34 @@ var strategyReviewTool = {
|
|
|
14975
15297
|
required: []
|
|
14976
15298
|
}
|
|
14977
15299
|
};
|
|
15300
|
+
var strategyAgendaTool = {
|
|
15301
|
+
name: "strategy_agenda",
|
|
15302
|
+
description: 'Queue topics for the next strategy review. Topics surface as input in the next `strategy_review` prepare phase and are automatically marked as addressed after the review completes. Two modes: "add" to queue a topic, "list" to see pending topics. Use this when you spot a strategic question during a build \u2014 capture the topic now instead of losing it.',
|
|
15303
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
15304
|
+
inputSchema: {
|
|
15305
|
+
type: "object",
|
|
15306
|
+
properties: {
|
|
15307
|
+
mode: {
|
|
15308
|
+
type: "string",
|
|
15309
|
+
enum: ["add", "list"],
|
|
15310
|
+
description: '"add" to queue a topic (requires `topic`). "list" returns all pending topics. Defaults to "list" when omitted.'
|
|
15311
|
+
},
|
|
15312
|
+
topic: {
|
|
15313
|
+
type: "string",
|
|
15314
|
+
description: 'The topic to queue (mode "add" only). One sentence describing what the next strategy review should consider.'
|
|
15315
|
+
},
|
|
15316
|
+
source: {
|
|
15317
|
+
type: "string",
|
|
15318
|
+
description: 'Optional origin label \u2014 e.g. "manual", "carry-forward", "idea". Defaults to "manual".'
|
|
15319
|
+
},
|
|
15320
|
+
source_cycle: {
|
|
15321
|
+
type: "number",
|
|
15322
|
+
description: 'Optional cycle number this topic originated from (mode "add" only).'
|
|
15323
|
+
}
|
|
15324
|
+
},
|
|
15325
|
+
required: []
|
|
15326
|
+
}
|
|
15327
|
+
};
|
|
14978
15328
|
var strategyChangeTool = {
|
|
14979
15329
|
name: "strategy_change",
|
|
14980
15330
|
description: 'Apply a strategic shift to the project. Three modes: "capture" for lightweight mid-conversation decision capture (no LLM round-trip), "prepare" to get a change prompt for full analysis, "apply" to persist analysis output. Use "capture" when you detect a strategic decision in conversation and want to persist it quickly without disrupting the build flow.',
|
|
@@ -15131,6 +15481,49 @@ ${result.userMessage}
|
|
|
15131
15481
|
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
15132
15482
|
}
|
|
15133
15483
|
}
|
|
15484
|
+
async function handleStrategyAgenda(adapter2, _config, args) {
|
|
15485
|
+
const mode = args.mode ?? "list";
|
|
15486
|
+
if (!adapter2.addAgendaTopic || !adapter2.getPendingAgendaTopics) {
|
|
15487
|
+
return errorResponse("strategy_agenda is not supported by the current adapter.");
|
|
15488
|
+
}
|
|
15489
|
+
try {
|
|
15490
|
+
if (mode === "add") {
|
|
15491
|
+
const topic = args.topic;
|
|
15492
|
+
if (!topic || !topic.trim()) {
|
|
15493
|
+
return errorResponse('topic is required for mode "add". Describe what the next strategy review should consider.');
|
|
15494
|
+
}
|
|
15495
|
+
const source = (args.source ?? "manual").trim() || "manual";
|
|
15496
|
+
const sourceCycle = typeof args.source_cycle === "number" ? args.source_cycle : void 0;
|
|
15497
|
+
const entry = await adapter2.addAgendaTopic({ topic: topic.trim(), source, sourceCycle });
|
|
15498
|
+
return textResponse(
|
|
15499
|
+
`**Agenda Topic Queued**
|
|
15500
|
+
|
|
15501
|
+
${entry.topic}
|
|
15502
|
+
|
|
15503
|
+
Source: ${entry.source}${entry.sourceCycle != null ? ` (Cycle ${entry.sourceCycle})` : ""}
|
|
15504
|
+
ID: ${entry.id}
|
|
15505
|
+
|
|
15506
|
+
This topic will surface in the next \`strategy_review\`.`
|
|
15507
|
+
);
|
|
15508
|
+
}
|
|
15509
|
+
const topics = await adapter2.getPendingAgendaTopics();
|
|
15510
|
+
if (topics.length === 0) {
|
|
15511
|
+
return textResponse('No pending agenda topics. Use `strategy_agenda` with `mode: "add"` to queue one.');
|
|
15512
|
+
}
|
|
15513
|
+
const lines = topics.map((t, i) => {
|
|
15514
|
+
const cycleSuffix = t.sourceCycle != null ? ` (Cycle ${t.sourceCycle})` : "";
|
|
15515
|
+
return `${i + 1}. ${t.topic}
|
|
15516
|
+
_source: ${t.source}${cycleSuffix} \xB7 queued ${t.createdAt.slice(0, 10)}_`;
|
|
15517
|
+
});
|
|
15518
|
+
return textResponse(
|
|
15519
|
+
`**Pending Agenda (${topics.length})** \u2014 surfaces at next strategy review
|
|
15520
|
+
|
|
15521
|
+
${lines.join("\n\n")}`
|
|
15522
|
+
);
|
|
15523
|
+
} catch (err) {
|
|
15524
|
+
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
15525
|
+
}
|
|
15526
|
+
}
|
|
15134
15527
|
async function handleStrategyChange(adapter2, _config, args) {
|
|
15135
15528
|
const toolMode = args.mode;
|
|
15136
15529
|
try {
|
|
@@ -15402,7 +15795,7 @@ var boardArchiveTool = {
|
|
|
15402
15795
|
};
|
|
15403
15796
|
var boardEditTool = {
|
|
15404
15797
|
name: "board_edit",
|
|
15405
|
-
description: "Edit fields on an existing task. Supports title, priority, complexity, module, epic, phase, notes, status, and maturity. Pass task_id plus any fields to update. Does not call the Anthropic API.",
|
|
15798
|
+
description: "Edit fields on an existing task. Supports title, priority, complexity, module, epic, phase, notes (with notes_mode for append/replace/clear), status, and maturity. Pass task_id plus any fields to update. Does not call the Anthropic API.",
|
|
15406
15799
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
15407
15800
|
inputSchema: {
|
|
15408
15801
|
type: "object",
|
|
@@ -15439,7 +15832,12 @@ var boardEditTool = {
|
|
|
15439
15832
|
},
|
|
15440
15833
|
notes: {
|
|
15441
15834
|
type: "string",
|
|
15442
|
-
description: "
|
|
15835
|
+
description: "Note content. Default behaviour is append \u2014 see notes_mode to control."
|
|
15836
|
+
},
|
|
15837
|
+
notes_mode: {
|
|
15838
|
+
type: "string",
|
|
15839
|
+
enum: ["append", "replace", "clear"],
|
|
15840
|
+
description: "How to apply the notes value. append (default) = add a new dated entry above existing notes; replace = overwrite all existing notes; clear = empty the notes field (notes value ignored)."
|
|
15443
15841
|
},
|
|
15444
15842
|
status: {
|
|
15445
15843
|
type: "string",
|
|
@@ -15642,6 +16040,10 @@ async function handleBoardEdit(adapter2, args) {
|
|
|
15642
16040
|
changes.push(field);
|
|
15643
16041
|
}
|
|
15644
16042
|
}
|
|
16043
|
+
const notesMode = args.notes_mode;
|
|
16044
|
+
if (notesMode === "clear" && !changes.includes("notes")) {
|
|
16045
|
+
changes.push("notes");
|
|
16046
|
+
}
|
|
15645
16047
|
if (changes.length === 0) {
|
|
15646
16048
|
return errorResponse("No fields to update. Pass at least one field (title, priority, complexity, module, epic, phase, notes, status, maturity).");
|
|
15647
16049
|
}
|
|
@@ -15650,6 +16052,35 @@ async function handleBoardEdit(adapter2, args) {
|
|
|
15650
16052
|
if (!task) {
|
|
15651
16053
|
return errorResponse(`Task ${taskId} not found.`);
|
|
15652
16054
|
}
|
|
16055
|
+
if (changes.includes("notes")) {
|
|
16056
|
+
const incoming = args.notes ?? "";
|
|
16057
|
+
const existing = task.notes ?? "";
|
|
16058
|
+
const mode = notesMode ?? "append";
|
|
16059
|
+
if (mode === "clear") {
|
|
16060
|
+
updates.notes = "";
|
|
16061
|
+
} else if (mode === "replace") {
|
|
16062
|
+
updates.notes = incoming;
|
|
16063
|
+
} else {
|
|
16064
|
+
const trimmed = incoming.trim();
|
|
16065
|
+
if (trimmed.length === 0) {
|
|
16066
|
+
delete updates.notes;
|
|
16067
|
+
const idx = changes.indexOf("notes");
|
|
16068
|
+
if (idx >= 0) changes.splice(idx, 1);
|
|
16069
|
+
} else {
|
|
16070
|
+
const health = await adapter2.getCycleHealth().catch(() => null);
|
|
16071
|
+
const activeCycle = health?.totalCycles ?? null;
|
|
16072
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
16073
|
+
const stamp = activeCycle != null ? `[C${activeCycle} ${date}]` : `[${date}]`;
|
|
16074
|
+
const entry = `${stamp} ${trimmed}`;
|
|
16075
|
+
updates.notes = existing.trim().length > 0 ? `${entry}
|
|
16076
|
+
|
|
16077
|
+
${existing}` : entry;
|
|
16078
|
+
}
|
|
16079
|
+
}
|
|
16080
|
+
if (changes.length === 0) {
|
|
16081
|
+
return errorResponse("No fields to update. Pass at least one field (title, priority, complexity, module, epic, phase, notes, status, maturity).");
|
|
16082
|
+
}
|
|
16083
|
+
}
|
|
15653
16084
|
if (updates.status === "Backlog" && task.cycle != null) {
|
|
15654
16085
|
updates.cycle = void 0;
|
|
15655
16086
|
updates.cycle = null;
|
|
@@ -16671,9 +17102,8 @@ async function prepareSetup(adapter2, config2, input) {
|
|
|
16671
17102
|
);
|
|
16672
17103
|
}
|
|
16673
17104
|
const TEMPLATE_MARKER = "*Describe your project's core value proposition here.*";
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
}
|
|
17105
|
+
const briefHasRealContent = existingBrief.trim().length > 0 && !existingBrief.includes(TEMPLATE_MARKER);
|
|
17106
|
+
const briefAlreadyExists = briefHasRealContent && !input.force;
|
|
16677
17107
|
const detectedCodebaseType = detectCodebaseType(config2.projectRoot);
|
|
16678
17108
|
const autoDetected = input.existingProject === void 0 || input.existingProject === false;
|
|
16679
17109
|
const isExistingProject = input.existingProject === true || autoDetected && detectedCodebaseType === "existing_codebase";
|
|
@@ -16686,7 +17116,7 @@ async function prepareSetup(adapter2, config2, input) {
|
|
|
16686
17116
|
}
|
|
16687
17117
|
codebaseSummary = formatCodebaseSummary(scan, sourceContents);
|
|
16688
17118
|
}
|
|
16689
|
-
const briefPrompt = {
|
|
17119
|
+
const briefPrompt = briefAlreadyExists ? void 0 : {
|
|
16690
17120
|
system: PRODUCT_BRIEF_SYSTEM,
|
|
16691
17121
|
user: buildProductBriefPrompt({
|
|
16692
17122
|
projectName: input.projectName,
|
|
@@ -16740,15 +17170,29 @@ async function prepareSetup(adapter2, config2, input) {
|
|
|
16740
17170
|
initialTasksPrompt,
|
|
16741
17171
|
codebaseSummary,
|
|
16742
17172
|
detectedCodebaseType,
|
|
16743
|
-
autoDetected: autoDetected && detectedCodebaseType !== "new_project"
|
|
17173
|
+
autoDetected: autoDetected && detectedCodebaseType !== "new_project",
|
|
17174
|
+
briefAlreadyExists
|
|
16744
17175
|
};
|
|
16745
17176
|
}
|
|
16746
17177
|
async function applySetup(adapter2, config2, input, briefText, adSeedText, conventionsText, initialTasksText) {
|
|
16747
17178
|
const createdProject = await scaffoldPapiDir(adapter2, config2, input);
|
|
16748
|
-
|
|
16749
|
-
|
|
17179
|
+
const TEMPLATE_MARKER = "*Describe your project's core value proposition here.*";
|
|
17180
|
+
let effectiveBriefText = briefText;
|
|
17181
|
+
if (!effectiveBriefText.trim()) {
|
|
17182
|
+
let existingBrief = "";
|
|
17183
|
+
try {
|
|
17184
|
+
existingBrief = await adapter2.readProductBrief();
|
|
17185
|
+
} catch {
|
|
17186
|
+
existingBrief = "";
|
|
17187
|
+
}
|
|
17188
|
+
const existingIsReal = existingBrief.trim().length > 0 && !existingBrief.includes(TEMPLATE_MARKER);
|
|
17189
|
+
if (existingIsReal && !input.force) {
|
|
17190
|
+
effectiveBriefText = existingBrief;
|
|
17191
|
+
} else {
|
|
17192
|
+
throw new Error("brief_response is required and cannot be empty.");
|
|
17193
|
+
}
|
|
16750
17194
|
}
|
|
16751
|
-
const { seededAds, warnings } = await applySetupOutputs(adapter2, config2, input,
|
|
17195
|
+
const { seededAds, warnings } = await applySetupOutputs(adapter2, config2, input, effectiveBriefText, adSeedText, conventionsText);
|
|
16752
17196
|
let createdTasks = 0;
|
|
16753
17197
|
if (initialTasksText?.trim()) {
|
|
16754
17198
|
try {
|
|
@@ -16974,10 +17418,7 @@ PAPI needs just 3 things: project name, what it does, and who it's for.`
|
|
|
16974
17418
|
const input = extractInput(args);
|
|
16975
17419
|
try {
|
|
16976
17420
|
if (toolMode === "apply") {
|
|
16977
|
-
const briefResponse = args.brief_response;
|
|
16978
|
-
if (!briefResponse || !briefResponse.trim()) {
|
|
16979
|
-
return errorResponse('brief_response is required for mode "apply".');
|
|
16980
|
-
}
|
|
17421
|
+
const briefResponse = args.brief_response ?? "";
|
|
16981
17422
|
const adSeedResponse = args.ad_seed_response;
|
|
16982
17423
|
const conventionsResponse = args.conventions_response;
|
|
16983
17424
|
const initialTasksResponse = args.initial_tasks_response;
|
|
@@ -17010,6 +17451,12 @@ PAPI needs just 3 things: project name, what it does, and who it's for.`
|
|
|
17010
17451
|
""
|
|
17011
17452
|
);
|
|
17012
17453
|
}
|
|
17454
|
+
if (result.briefAlreadyExists) {
|
|
17455
|
+
sections.push(
|
|
17456
|
+
`**Existing brief detected:** this project already has a Product Brief (e.g. from the web wizard). Setup will preserve it and skip brief generation \u2014 no \`brief_response\` needed in the apply step. To overwrite the brief instead, re-run setup with \`force: true\`.`,
|
|
17457
|
+
""
|
|
17458
|
+
);
|
|
17459
|
+
}
|
|
17013
17460
|
if (inferredDefaults.length > 0) {
|
|
17014
17461
|
sections.push(
|
|
17015
17462
|
`**Defaults applied** (override by re-running setup with these fields):`,
|
|
@@ -17020,30 +17467,37 @@ PAPI needs just 3 things: project name, what it does, and who it's for.`
|
|
|
17020
17467
|
sections.push(
|
|
17021
17468
|
`Generate the outputs below, then call \`setup\` again with:`,
|
|
17022
17469
|
`- \`mode\`: "apply"`,
|
|
17023
|
-
`- \`brief_response\`: your Product Brief markdown
|
|
17470
|
+
result.briefPrompt ? `- \`brief_response\`: your Product Brief markdown` : "",
|
|
17024
17471
|
result.adSeedPrompt ? `- \`ad_seed_response\`: your AD seed JSON array` : "",
|
|
17025
17472
|
result.conventionsPrompt ? `- \`conventions_response\`: your conventions markdown` : "",
|
|
17026
17473
|
result.initialTasksPrompt ? `- \`initial_tasks_response\`: your initial tasks JSON array` : "",
|
|
17027
17474
|
`- Plus all the original setup fields (project_name, description, target_users${isExisting ? ", existing_project: true" : ""})`,
|
|
17028
17475
|
"",
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17476
|
+
`---`
|
|
17477
|
+
);
|
|
17478
|
+
let sectionNum = 0;
|
|
17479
|
+
if (result.briefPrompt) {
|
|
17480
|
+
sectionNum++;
|
|
17481
|
+
sections.push(
|
|
17482
|
+
"",
|
|
17483
|
+
`### ${sectionNum}. Product Brief`,
|
|
17484
|
+
"",
|
|
17485
|
+
`<system_prompt>
|
|
17034
17486
|
${result.briefPrompt.system}
|
|
17035
17487
|
</system_prompt>`,
|
|
17036
|
-
|
|
17037
|
-
|
|
17488
|
+
"",
|
|
17489
|
+
`<context>
|
|
17038
17490
|
${result.briefPrompt.user}
|
|
17039
17491
|
</context>`
|
|
17040
|
-
|
|
17492
|
+
);
|
|
17493
|
+
}
|
|
17041
17494
|
if (result.adSeedPrompt) {
|
|
17495
|
+
sectionNum++;
|
|
17042
17496
|
sections.push(
|
|
17043
17497
|
"",
|
|
17044
17498
|
`---`,
|
|
17045
17499
|
"",
|
|
17046
|
-
`###
|
|
17500
|
+
`### ${sectionNum}. Active Decision Seeds`,
|
|
17047
17501
|
"",
|
|
17048
17502
|
`<system_prompt>
|
|
17049
17503
|
${result.adSeedPrompt.system}
|
|
@@ -17055,11 +17509,12 @@ ${result.adSeedPrompt.user}
|
|
|
17055
17509
|
);
|
|
17056
17510
|
}
|
|
17057
17511
|
if (result.conventionsPrompt) {
|
|
17512
|
+
sectionNum++;
|
|
17058
17513
|
sections.push(
|
|
17059
17514
|
"",
|
|
17060
17515
|
`---`,
|
|
17061
17516
|
"",
|
|
17062
|
-
`###
|
|
17517
|
+
`### ${sectionNum}. Conventions`,
|
|
17063
17518
|
"",
|
|
17064
17519
|
`<system_prompt>
|
|
17065
17520
|
${result.conventionsPrompt.system}
|
|
@@ -17071,11 +17526,12 @@ ${result.conventionsPrompt.user}
|
|
|
17071
17526
|
);
|
|
17072
17527
|
}
|
|
17073
17528
|
if (result.initialTasksPrompt) {
|
|
17529
|
+
sectionNum++;
|
|
17074
17530
|
sections.push(
|
|
17075
17531
|
"",
|
|
17076
17532
|
`---`,
|
|
17077
17533
|
"",
|
|
17078
|
-
`###
|
|
17534
|
+
`### ${sectionNum}. Initial Backlog Tasks`,
|
|
17079
17535
|
"",
|
|
17080
17536
|
`<system_prompt>
|
|
17081
17537
|
${result.initialTasksPrompt.system}
|
|
@@ -17487,18 +17943,37 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
|
17487
17943
|
}
|
|
17488
17944
|
}
|
|
17489
17945
|
let autoTriagedCount = 0;
|
|
17946
|
+
const autoTriagedIds = [];
|
|
17947
|
+
const autoTriagedDupes = [];
|
|
17490
17948
|
if (input.discoveredIssues && input.discoveredIssues !== "None" && typeof adapter2.createTask === "function") {
|
|
17491
17949
|
const issueLines = input.discoveredIssues.split(/\n|;/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
17950
|
+
const backlogTitleMap = /* @__PURE__ */ new Map();
|
|
17951
|
+
try {
|
|
17952
|
+
const backlog = await adapter2.queryBoard({ status: ["Backlog"] });
|
|
17953
|
+
for (const t of backlog) {
|
|
17954
|
+
const normalized = t.title.replace(/^\[Auto-triaged\]\s*/i, "").trim().toLowerCase();
|
|
17955
|
+
if (normalized && !backlogTitleMap.has(normalized)) {
|
|
17956
|
+
backlogTitleMap.set(normalized, t.displayId);
|
|
17957
|
+
}
|
|
17958
|
+
}
|
|
17959
|
+
} catch {
|
|
17960
|
+
}
|
|
17492
17961
|
for (const line of issueLines) {
|
|
17493
17962
|
const sevMatch = line.match(/^(P[0-3])[\s:]+/i);
|
|
17494
17963
|
if (!sevMatch) continue;
|
|
17495
17964
|
const severityLabel = sevMatch[1].toUpperCase();
|
|
17496
|
-
const priority = severityLabel === "P0"
|
|
17965
|
+
const priority = severityLabel === "P0" ? "P0 Critical" : severityLabel === "P1" ? "P1 High" : severityLabel === "P2" ? "P2 Medium" : "P3 Low";
|
|
17497
17966
|
const titleRaw = line.replace(/^P[0-3][\s:]+/i, "").trim();
|
|
17498
17967
|
const title = titleRaw.length > 120 ? titleRaw.slice(0, 120) : titleRaw;
|
|
17499
17968
|
if (!title) continue;
|
|
17969
|
+
const normalized = title.toLowerCase();
|
|
17970
|
+
const dupId = backlogTitleMap.get(normalized);
|
|
17971
|
+
if (dupId) {
|
|
17972
|
+
autoTriagedDupes.push(dupId);
|
|
17973
|
+
continue;
|
|
17974
|
+
}
|
|
17500
17975
|
try {
|
|
17501
|
-
await adapter2.createTask({
|
|
17976
|
+
const created = await adapter2.createTask({
|
|
17502
17977
|
uuid: "",
|
|
17503
17978
|
displayId: "",
|
|
17504
17979
|
title: `[Auto-triaged] ${title}`,
|
|
@@ -17515,6 +17990,10 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
|
17515
17990
|
createdCycle: cycleNumber
|
|
17516
17991
|
});
|
|
17517
17992
|
autoTriagedCount++;
|
|
17993
|
+
if (created?.displayId) {
|
|
17994
|
+
autoTriagedIds.push(created.displayId);
|
|
17995
|
+
backlogTitleMap.set(normalized, created.displayId);
|
|
17996
|
+
}
|
|
17518
17997
|
} catch {
|
|
17519
17998
|
}
|
|
17520
17999
|
}
|
|
@@ -17711,6 +18190,8 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
|
17711
18190
|
dogfoodResolvedCount: dogfoodResolvedCount > 0 ? dogfoodResolvedCount : void 0,
|
|
17712
18191
|
learningsLinkedCount: learningsLinkedCount > 0 ? learningsLinkedCount : void 0,
|
|
17713
18192
|
autoTriagedCount: autoTriagedCount > 0 ? autoTriagedCount : void 0,
|
|
18193
|
+
autoTriagedIds: autoTriagedIds.length > 0 ? autoTriagedIds : void 0,
|
|
18194
|
+
autoTriagedDupes: autoTriagedDupes.length > 0 ? autoTriagedDupes : void 0,
|
|
17714
18195
|
reportWriteVerified
|
|
17715
18196
|
};
|
|
17716
18197
|
}
|
|
@@ -18214,8 +18695,16 @@ function formatCompleteResult(result) {
|
|
|
18214
18695
|
if (result.learningsLinkedCount) {
|
|
18215
18696
|
lines.push("", `Linked ${result.learningsLinkedCount} unactioned learning(s) to this task.`);
|
|
18216
18697
|
}
|
|
18217
|
-
if (result.autoTriagedCount) {
|
|
18218
|
-
|
|
18698
|
+
if (result.autoTriagedCount || result.autoTriagedDupes?.length) {
|
|
18699
|
+
const parts = [];
|
|
18700
|
+
if (result.autoTriagedCount) {
|
|
18701
|
+
const ids = result.autoTriagedIds?.length ? ` (${result.autoTriagedIds.join(", ")})` : "";
|
|
18702
|
+
parts.push(`\u{1F516} Auto-triaged ${result.autoTriagedCount} discovered issue(s) to Backlog${ids}.`);
|
|
18703
|
+
}
|
|
18704
|
+
if (result.autoTriagedDupes?.length) {
|
|
18705
|
+
parts.push(`Skipped ${result.autoTriagedDupes.length} duplicate issue(s) already in Backlog: ${result.autoTriagedDupes.join(", ")}.`);
|
|
18706
|
+
}
|
|
18707
|
+
lines.push("", parts.join(" "));
|
|
18219
18708
|
}
|
|
18220
18709
|
if (result.reportWriteVerified === false) {
|
|
18221
18710
|
lines.push("", "\u26A0\uFE0F Build report write could not be verified \u2014 the report may not have been persisted. Run `build_list` to check, and resubmit if missing.");
|
|
@@ -19830,7 +20319,41 @@ function generateChangelog(version, commits) {
|
|
|
19830
20319
|
${commitList}
|
|
19831
20320
|
`;
|
|
19832
20321
|
}
|
|
19833
|
-
|
|
20322
|
+
function mergeGroupedCycleBranches(config2, cycleNum, baseBranch) {
|
|
20323
|
+
const branches = listGroupedCycleBranches(config2.projectRoot, cycleNum, baseBranch);
|
|
20324
|
+
if (branches.length === 0) return [];
|
|
20325
|
+
if (!isGhAvailable()) {
|
|
20326
|
+
throw new Error(
|
|
20327
|
+
`Release blocked: ${branches.length} unmerged grouped cycle branch(es) detected (${branches.join(", ")}) but \`gh\` CLI is not available. Install gh and re-run release.`
|
|
20328
|
+
);
|
|
20329
|
+
}
|
|
20330
|
+
const results = [];
|
|
20331
|
+
for (const branch of branches) {
|
|
20332
|
+
let prUrl = getPullRequestUrl(config2.projectRoot, branch);
|
|
20333
|
+
if (!prUrl) {
|
|
20334
|
+
const moduleName = branch.replace(`feat/cycle-${cycleNum}-`, "");
|
|
20335
|
+
const prCreate = createPullRequest(
|
|
20336
|
+
config2.projectRoot,
|
|
20337
|
+
branch,
|
|
20338
|
+
baseBranch,
|
|
20339
|
+
`feat(cycle-${cycleNum}): merge shared cycle branch \u2014 ${moduleName}`,
|
|
20340
|
+
`Automated PR created at release time for shared cycle branch \`${branch}\` (Cycle ${cycleNum}).`
|
|
20341
|
+
);
|
|
20342
|
+
if (!prCreate.success) {
|
|
20343
|
+
results.push({ branch, prUrl: null, status: "failed", message: prCreate.message });
|
|
20344
|
+
continue;
|
|
20345
|
+
}
|
|
20346
|
+
prUrl = prCreate.message;
|
|
20347
|
+
}
|
|
20348
|
+
const merge = squashMergePullRequest(config2.projectRoot, branch);
|
|
20349
|
+
if (merge.success) {
|
|
20350
|
+
deleteLocalBranch(config2.projectRoot, branch);
|
|
20351
|
+
}
|
|
20352
|
+
results.push({ branch, prUrl, status: merge.success ? "merged" : "failed", message: merge.message });
|
|
20353
|
+
}
|
|
20354
|
+
return results;
|
|
20355
|
+
}
|
|
20356
|
+
async function createRelease(config2, branch, version, adapter2, cycleNum) {
|
|
19834
20357
|
if (!isGitAvailable()) {
|
|
19835
20358
|
throw new Error("git is not available.");
|
|
19836
20359
|
}
|
|
@@ -19841,10 +20364,13 @@ async function createRelease(config2, branch, version, adapter2) {
|
|
|
19841
20364
|
throw new Error("working directory has uncommitted changes. Commit or stash them before releasing.");
|
|
19842
20365
|
}
|
|
19843
20366
|
const warnings = [];
|
|
20367
|
+
const resolvedCycleNum = cycleNum && cycleNum > 0 ? cycleNum : (() => {
|
|
20368
|
+
const m = version.match(/^v0\.(\d+)\./);
|
|
20369
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
20370
|
+
})();
|
|
19844
20371
|
if (adapter2) {
|
|
19845
20372
|
try {
|
|
19846
|
-
const
|
|
19847
|
-
const currentCycle = versionMatch ? parseInt(versionMatch[1], 10) : 0;
|
|
20373
|
+
const currentCycle = resolvedCycleNum;
|
|
19848
20374
|
if (currentCycle > 0) {
|
|
19849
20375
|
await adapter2.createCycle({
|
|
19850
20376
|
id: `cycle-${currentCycle}`,
|
|
@@ -19873,6 +20399,24 @@ async function createRelease(config2, branch, version, adapter2) {
|
|
|
19873
20399
|
warnings.push(`git pull failed: ${pull.message}. Run manually.`);
|
|
19874
20400
|
}
|
|
19875
20401
|
}
|
|
20402
|
+
let groupedBranchMerges;
|
|
20403
|
+
if (resolvedCycleNum > 0) {
|
|
20404
|
+
const mergeResults = mergeGroupedCycleBranches(config2, resolvedCycleNum, branch);
|
|
20405
|
+
if (mergeResults.length > 0) {
|
|
20406
|
+
groupedBranchMerges = mergeResults;
|
|
20407
|
+
const failures = mergeResults.filter((r) => r.status === "failed");
|
|
20408
|
+
if (failures.length > 0) {
|
|
20409
|
+
const detail = failures.map((r) => `${r.branch}: ${r.message}`).join("; ");
|
|
20410
|
+
throw new Error(`Release blocked: grouped cycle branch merge failed \u2014 ${detail}. Resolve conflicts and retry release.`);
|
|
20411
|
+
}
|
|
20412
|
+
if (hasRemote(config2.projectRoot)) {
|
|
20413
|
+
const repull = gitPull(config2.projectRoot);
|
|
20414
|
+
if (!repull.success) {
|
|
20415
|
+
warnings.push(`Post-merge pull failed: ${repull.message}. Run manually.`);
|
|
20416
|
+
}
|
|
20417
|
+
}
|
|
20418
|
+
}
|
|
20419
|
+
}
|
|
19876
20420
|
if (tagExists(config2.projectRoot, version)) {
|
|
19877
20421
|
throw new Error(`tag "${version}" already exists. Use a different version.`);
|
|
19878
20422
|
}
|
|
@@ -19909,7 +20453,8 @@ async function createRelease(config2, branch, version, adapter2) {
|
|
|
19909
20453
|
commitNote,
|
|
19910
20454
|
tagMessage: tagResult.message,
|
|
19911
20455
|
pushNotes,
|
|
19912
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
20456
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
20457
|
+
...groupedBranchMerges ? { groupedBranchMerges } : {}
|
|
19913
20458
|
};
|
|
19914
20459
|
}
|
|
19915
20460
|
|
|
@@ -19965,7 +20510,9 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
19965
20510
|
return errorResponse(`version must start with "v" (got "${version}"). Example: "v0.1.0-alpha"`);
|
|
19966
20511
|
}
|
|
19967
20512
|
try {
|
|
19968
|
-
const
|
|
20513
|
+
const cycleMatch = version.match(/^v0\.(\d+)\./);
|
|
20514
|
+
const cycleNum = cycleMatch ? parseInt(cycleMatch[1], 10) : void 0;
|
|
20515
|
+
const result = await createRelease(config2, branch, version, adapter2, cycleNum);
|
|
19969
20516
|
const lines = [
|
|
19970
20517
|
`## Release ${result.version}`,
|
|
19971
20518
|
"",
|
|
@@ -19978,13 +20525,17 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
19978
20525
|
lines.push("", "---", "");
|
|
19979
20526
|
lines.push(...result.pushNotes);
|
|
19980
20527
|
}
|
|
20528
|
+
if (result.groupedBranchMerges?.length) {
|
|
20529
|
+
lines.push("", "**Shared cycle branches merged:**");
|
|
20530
|
+
for (const m of result.groupedBranchMerges) {
|
|
20531
|
+
lines.push(`- \`${m.branch}\` \u2014 ${m.message}${m.prUrl ? ` (${m.prUrl})` : ""}`);
|
|
20532
|
+
}
|
|
20533
|
+
}
|
|
19981
20534
|
if (result.warnings?.length) {
|
|
19982
20535
|
lines.push("", "\u26A0\uFE0F Warnings: " + result.warnings.join("; "));
|
|
19983
20536
|
}
|
|
19984
20537
|
try {
|
|
19985
|
-
|
|
19986
|
-
const cycleNum = cycleMatch ? parseInt(cycleMatch[1], 10) : 0;
|
|
19987
|
-
if (cycleNum > 0) {
|
|
20538
|
+
if (cycleNum && cycleNum > 0) {
|
|
19988
20539
|
const reports = await adapter2.getBuildReportsSince(cycleNum);
|
|
19989
20540
|
const EMPTY = /* @__PURE__ */ new Set(["None", "none", "N/A", "", "null"]);
|
|
19990
20541
|
const issues = reports.filter((r) => r.discoveredIssues && !EMPTY.has(r.discoveredIssues.trim())).map((r) => `- **${r.taskId}** (${r.taskName}): ${r.discoveredIssues}`);
|
|
@@ -19997,10 +20548,10 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
19997
20548
|
}
|
|
19998
20549
|
if (rawObservations && rawObservations.length > 0 && adapter2.writeDogfoodEntries) {
|
|
19999
20550
|
try {
|
|
20000
|
-
const
|
|
20001
|
-
const
|
|
20551
|
+
const cycleMatch2 = version.match(/^v0\.(\d+)\./);
|
|
20552
|
+
const cycleNum2 = cycleMatch2 ? parseInt(cycleMatch2[1], 10) : 0;
|
|
20002
20553
|
const entries = rawObservations.map((obs) => ({
|
|
20003
|
-
cycleNumber:
|
|
20554
|
+
cycleNumber: cycleNum2,
|
|
20004
20555
|
category: obs.category,
|
|
20005
20556
|
content: obs.content,
|
|
20006
20557
|
sourceTool: "release",
|
|
@@ -20256,6 +20807,10 @@ function mergeAfterAccept(config2, taskId) {
|
|
|
20256
20807
|
}
|
|
20257
20808
|
const featureBranch = taskBranchName(taskId);
|
|
20258
20809
|
const baseBranch = resolveBaseBranch(config2.projectRoot, config2.baseBranch);
|
|
20810
|
+
if (!branchExists(config2.projectRoot, featureBranch)) {
|
|
20811
|
+
lines.push(`Task is on a shared cycle branch \u2014 will be merged at release time.`);
|
|
20812
|
+
return lines;
|
|
20813
|
+
}
|
|
20259
20814
|
const papiDir = join7(config2.projectRoot, ".papi");
|
|
20260
20815
|
if (existsSync4(papiDir)) {
|
|
20261
20816
|
try {
|
|
@@ -20431,8 +20986,9 @@ ${result.handoffRegenPrompt.userMessage}
|
|
|
20431
20986
|
}
|
|
20432
20987
|
const version = `v0.${result.currentCycle}.0`;
|
|
20433
20988
|
const baseBranch = resolveBaseBranch(config2.projectRoot, config2.baseBranch);
|
|
20434
|
-
const releaseResult = await createRelease(config2, baseBranch, version, adapter2);
|
|
20989
|
+
const releaseResult = await createRelease(config2, baseBranch, version, adapter2, result.currentCycle);
|
|
20435
20990
|
const pushInfo = releaseResult.pushNotes.join(" ");
|
|
20991
|
+
const groupedMergeNote = releaseResult.groupedBranchMerges?.length ? "\n" + releaseResult.groupedBranchMerges.map((r) => `- Merged shared branch \`${r.branch}\` via PR: ${r.prUrl ?? "n/a"}`).join("\n") : "";
|
|
20436
20992
|
autoReleaseNote = `
|
|
20437
20993
|
|
|
20438
20994
|
---
|
|
@@ -20442,7 +20998,7 @@ ${result.handoffRegenPrompt.userMessage}
|
|
|
20442
20998
|
- Version: **${releaseResult.version}**
|
|
20443
20999
|
- ${releaseResult.commitNote}
|
|
20444
21000
|
- ${releaseResult.tagMessage}
|
|
20445
|
-
- ${pushInfo}` + (releaseResult.warnings?.length ? `
|
|
21001
|
+
- ${pushInfo}` + groupedMergeNote + (releaseResult.warnings?.length ? `
|
|
20446
21002
|
- Warnings: ${releaseResult.warnings.join(", ")}` : "") + `
|
|
20447
21003
|
|
|
20448
21004
|
Run \`plan\` to create Cycle ${result.currentCycle + 1}.`;
|
|
@@ -20689,7 +21245,7 @@ function countByStatus(tasks) {
|
|
|
20689
21245
|
async function getHealthSummary(adapter2) {
|
|
20690
21246
|
const health = await adapter2.getCycleHealth();
|
|
20691
21247
|
const activeTasks = await adapter2.queryBoard({
|
|
20692
|
-
status: ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Blocked"]
|
|
21248
|
+
status: ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Blocked", "Deferred"]
|
|
20693
21249
|
});
|
|
20694
21250
|
const logEntries = await adapter2.getCycleLog(3);
|
|
20695
21251
|
const cycleNumber = health.totalCycles;
|
|
@@ -22488,16 +23044,19 @@ function emitToolCall(projectId, toolName, durationMs, extra) {
|
|
|
22488
23044
|
metadata: { duration_ms: durationMs, ...extra }
|
|
22489
23045
|
});
|
|
22490
23046
|
}
|
|
22491
|
-
function emitMdAdapterPing(toolName, extra) {
|
|
23047
|
+
function emitMdAdapterPing(toolName, extra, userId, projectSlug) {
|
|
22492
23048
|
if (!isEnabled()) return;
|
|
22493
23049
|
const installId = getInstallId();
|
|
22494
23050
|
if (!installId) return;
|
|
23051
|
+
const resolvedUserId = userId ?? process.env["PAPI_USER_ID"] ?? void 0;
|
|
22495
23052
|
const body = {
|
|
22496
23053
|
install_id: installId,
|
|
22497
23054
|
tool_name: toolName,
|
|
22498
23055
|
papi_version: process.env["npm_package_version"] ?? null,
|
|
22499
23056
|
metadata: extra ?? {}
|
|
22500
23057
|
};
|
|
23058
|
+
if (resolvedUserId) body["user_id"] = resolvedUserId;
|
|
23059
|
+
if (projectSlug) body["project_slug"] = projectSlug;
|
|
22501
23060
|
fetch(`${TELEMETRY_SUPABASE_URL}/rest/v1/md_adapter_pings`, {
|
|
22502
23061
|
method: "POST",
|
|
22503
23062
|
headers: {
|
|
@@ -22525,6 +23084,7 @@ var TOOLS_REQUIRING_PAPI = /* @__PURE__ */ new Set([
|
|
|
22525
23084
|
"plan",
|
|
22526
23085
|
"strategy_review",
|
|
22527
23086
|
"strategy_change",
|
|
23087
|
+
"strategy_agenda",
|
|
22528
23088
|
"board_view",
|
|
22529
23089
|
"board_deprioritise",
|
|
22530
23090
|
"board_archive",
|
|
@@ -22617,6 +23177,7 @@ function createServer(adapter2, config2) {
|
|
|
22617
23177
|
planTool,
|
|
22618
23178
|
strategyReviewTool,
|
|
22619
23179
|
strategyChangeTool,
|
|
23180
|
+
strategyAgendaTool,
|
|
22620
23181
|
boardViewTool,
|
|
22621
23182
|
boardDeprioritiseTool,
|
|
22622
23183
|
boardArchiveTool,
|
|
@@ -22683,6 +23244,9 @@ function createServer(adapter2, config2) {
|
|
|
22683
23244
|
case "strategy_change":
|
|
22684
23245
|
result = await handleStrategyChange(adapter2, config2, safeArgs);
|
|
22685
23246
|
break;
|
|
23247
|
+
case "strategy_agenda":
|
|
23248
|
+
result = await handleStrategyAgenda(adapter2, config2, safeArgs);
|
|
23249
|
+
break;
|
|
22686
23250
|
case "board_view":
|
|
22687
23251
|
result = await handleBoardView(adapter2, safeArgs);
|
|
22688
23252
|
break;
|
|
@@ -22777,7 +23341,8 @@ function createServer(adapter2, config2) {
|
|
|
22777
23341
|
} catch {
|
|
22778
23342
|
}
|
|
22779
23343
|
if (config2.adapterType === "md") {
|
|
22780
|
-
|
|
23344
|
+
const mdProjectSlug = config2.projectRoot ? config2.projectRoot.split("/").pop() : void 0;
|
|
23345
|
+
emitMdAdapterPing(name, { duration_ms: elapsed, success: !isError }, config2.userId, mdProjectSlug);
|
|
22781
23346
|
}
|
|
22782
23347
|
const telemetryProjectId = process.env["PAPI_PROJECT_ID"];
|
|
22783
23348
|
if (telemetryProjectId) {
|
package/dist/prompts.js
CHANGED
|
@@ -535,6 +535,9 @@ function buildPlanUserMessage(ctx) {
|
|
|
535
535
|
}) : PLAN_FULL_INSTRUCTIONS;
|
|
536
536
|
parts.push(instructions);
|
|
537
537
|
}
|
|
538
|
+
if (ctx.foundationalTasksGuidance) {
|
|
539
|
+
parts.push("", ctx.foundationalTasksGuidance);
|
|
540
|
+
}
|
|
538
541
|
if (ctx.skipHandoffs) {
|
|
539
542
|
parts.push(
|
|
540
543
|
"",
|
|
@@ -1056,6 +1059,9 @@ function buildReviewUserMessage(ctx) {
|
|
|
1056
1059
|
if (ctx.docActionStaleness) {
|
|
1057
1060
|
parts.push("### Doc Action Staleness", "", ctx.docActionStaleness, "");
|
|
1058
1061
|
}
|
|
1062
|
+
if (ctx.pendingAgendaTopics) {
|
|
1063
|
+
parts.push("### Queued Agenda Topics", "", "_Topics queued via `strategy_agenda` since the last review. Address each one in this review \u2014 they will be auto-marked as addressed on apply._", "", ctx.pendingAgendaTopics, "");
|
|
1064
|
+
}
|
|
1059
1065
|
return parts.join("\n");
|
|
1060
1066
|
}
|
|
1061
1067
|
function parseReviewStructuredOutput(raw) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"mcpName": "io.github.cathalos92/papi",
|
|
3
|
+
"version": "0.7.13",
|
|
5
4
|
"description": "PAPI MCP server — AI-powered sprint planning, build execution, and strategy review for software projects",
|
|
6
5
|
"license": "Elastic-2.0",
|
|
7
6
|
"type": "module",
|