@papi-ai/server 0.7.61 → 0.7.63
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.
|
@@ -88,6 +88,13 @@ Your output is **not done** if ANY of these is true (these are falsifiable — c
|
|
|
88
88
|
- [ ] Guidance, state, or a next action explained in a sentence of helper text where a visual cue
|
|
89
89
|
(position, size, colour, icon, a signpost, motion-on-change) would carry it — prose used as a
|
|
90
90
|
crutch for missing visual steering.
|
|
91
|
+
- [ ] A label, its value, and its explanation each taking their own line ("The Stacked Caption").
|
|
92
|
+
Anywhere the three exist, they share ONE line.
|
|
93
|
+
- [ ] A new datum added as a sibling row beside a row it could extend ("The Siblinged Row").
|
|
94
|
+
Extend the existing row — inline suffix, extra cell, chip on the same baseline — never stack
|
|
95
|
+
a twin row underneath.
|
|
96
|
+
- [ ] An eyebrow label or an explanatory subtitle above/below a heading ("The Eyebrow Crutch").
|
|
97
|
+
Labels carry themselves; if a heading needs a subtitle to be understood, the heading is wrong.
|
|
91
98
|
- [ ] More than ~3 sections fully visible above the fold.
|
|
92
99
|
- [ ] Identical-sized cards in a uniform row (flat hierarchy — needs a dominant 2×+ cell).
|
|
93
100
|
- [ ] Monospace on labels, status, timestamps, or nav.
|
|
@@ -108,6 +108,12 @@ Run through every named anti-pattern and flag matches. Common universal ones:
|
|
|
108
108
|
- Guidance, state, or a next action carried by a sentence of helper text where a visual cue
|
|
109
109
|
(position, size, colour, an icon, a signpost, motion-on-change) would do — prose as a crutch for
|
|
110
110
|
missing visual steering. Prefer showing over telling.
|
|
111
|
+
- A label, its value, and its explanation each taking their own line ("The Stacked Caption") —
|
|
112
|
+
anywhere the three exist, they share ONE line.
|
|
113
|
+
- A new datum added as a sibling row beside a row it could extend ("The Siblinged Row") — extend
|
|
114
|
+
the existing row (inline suffix, extra cell, chip on the same baseline), never stack a twin row.
|
|
115
|
+
- An eyebrow label or explanatory subtitle above/below a heading ("The Eyebrow Crutch") — labels
|
|
116
|
+
carry themselves; a heading that needs a subtitle to be understood is the wrong heading.
|
|
111
117
|
- Identical-sized cards in a uniform row (flat hierarchy).
|
|
112
118
|
- Monospace on labels / status / timestamps / nav.
|
|
113
119
|
- Header, label, and value at the same size+weight.
|
|
@@ -60,6 +60,7 @@ __export(git_exports, {
|
|
|
60
60
|
isGitAvailable: () => isGitAvailable,
|
|
61
61
|
isGitRepo: () => isGitRepo,
|
|
62
62
|
listGroupedCycleBranches: () => listGroupedCycleBranches,
|
|
63
|
+
listOpenPullRequests: () => listOpenPullRequests,
|
|
63
64
|
listOrphanFeatBranches: () => listOrphanFeatBranches,
|
|
64
65
|
mergePullRequest: () => mergePullRequest,
|
|
65
66
|
normalizeGitUrl: () => normalizeGitUrl,
|
|
@@ -346,6 +347,26 @@ function isGhAvailable() {
|
|
|
346
347
|
return false;
|
|
347
348
|
}
|
|
348
349
|
}
|
|
350
|
+
function listOpenPullRequests(cwd) {
|
|
351
|
+
if (!isGhAvailable()) return null;
|
|
352
|
+
try {
|
|
353
|
+
const out = execFileSync(
|
|
354
|
+
"gh",
|
|
355
|
+
["pr", "list", "--state", "open", "--limit", "100", "--json", "number,title,author,headRefName,createdAt"],
|
|
356
|
+
{ cwd, encoding: "utf-8" }
|
|
357
|
+
);
|
|
358
|
+
const raw = JSON.parse(out);
|
|
359
|
+
return raw.map((p) => ({
|
|
360
|
+
number: p.number,
|
|
361
|
+
title: p.title,
|
|
362
|
+
author: p.author?.login ?? "unknown",
|
|
363
|
+
headRefName: p.headRefName,
|
|
364
|
+
createdAt: p.createdAt
|
|
365
|
+
}));
|
|
366
|
+
} catch {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
349
370
|
function getOriginRepoSlug(cwd) {
|
|
350
371
|
try {
|
|
351
372
|
const url = execFileSync("git", ["remote", "get-url", "origin"], {
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,7 @@ __export(git_exports, {
|
|
|
61
61
|
isGitAvailable: () => isGitAvailable,
|
|
62
62
|
isGitRepo: () => isGitRepo,
|
|
63
63
|
listGroupedCycleBranches: () => listGroupedCycleBranches,
|
|
64
|
+
listOpenPullRequests: () => listOpenPullRequests,
|
|
64
65
|
listOrphanFeatBranches: () => listOrphanFeatBranches,
|
|
65
66
|
mergePullRequest: () => mergePullRequest,
|
|
66
67
|
normalizeGitUrl: () => normalizeGitUrl,
|
|
@@ -347,6 +348,26 @@ function isGhAvailable() {
|
|
|
347
348
|
return false;
|
|
348
349
|
}
|
|
349
350
|
}
|
|
351
|
+
function listOpenPullRequests(cwd) {
|
|
352
|
+
if (!isGhAvailable()) return null;
|
|
353
|
+
try {
|
|
354
|
+
const out = execFileSync(
|
|
355
|
+
"gh",
|
|
356
|
+
["pr", "list", "--state", "open", "--limit", "100", "--json", "number,title,author,headRefName,createdAt"],
|
|
357
|
+
{ cwd, encoding: "utf-8" }
|
|
358
|
+
);
|
|
359
|
+
const raw = JSON.parse(out);
|
|
360
|
+
return raw.map((p) => ({
|
|
361
|
+
number: p.number,
|
|
362
|
+
title: p.title,
|
|
363
|
+
author: p.author?.login ?? "unknown",
|
|
364
|
+
headRefName: p.headRefName,
|
|
365
|
+
createdAt: p.createdAt
|
|
366
|
+
}));
|
|
367
|
+
} catch {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
350
371
|
function getOriginRepoSlug(cwd) {
|
|
351
372
|
try {
|
|
352
373
|
const url = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
@@ -2040,6 +2061,76 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2040
2061
|
}
|
|
2041
2062
|
});
|
|
2042
2063
|
|
|
2064
|
+
// src/lib/reap-orphans.ts
|
|
2065
|
+
var reap_orphans_exports = {};
|
|
2066
|
+
__export(reap_orphans_exports, {
|
|
2067
|
+
formatReapSummary: () => formatReapSummary,
|
|
2068
|
+
isPapiServerCommand: () => isPapiServerCommand,
|
|
2069
|
+
listProcesses: () => listProcesses,
|
|
2070
|
+
reapOrphans: () => reapOrphans,
|
|
2071
|
+
selectReapableOrphans: () => selectReapableOrphans
|
|
2072
|
+
});
|
|
2073
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
2074
|
+
function isPapiServerCommand(command) {
|
|
2075
|
+
return /@papi-ai[/\\]server/.test(command);
|
|
2076
|
+
}
|
|
2077
|
+
function selectReapableOrphans(procs, selfPid) {
|
|
2078
|
+
return procs.filter(
|
|
2079
|
+
(p) => p.pid !== selfPid && p.ppid === 1 && isPapiServerCommand(p.command)
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
function listProcesses() {
|
|
2083
|
+
if (process.platform === "win32") return null;
|
|
2084
|
+
try {
|
|
2085
|
+
const out = execFileSync6("ps", ["-A", "-o", "pid=,ppid=,command="], { encoding: "utf-8" });
|
|
2086
|
+
const procs = [];
|
|
2087
|
+
for (const line of out.split("\n")) {
|
|
2088
|
+
const trimmed = line.trim();
|
|
2089
|
+
if (!trimmed) continue;
|
|
2090
|
+
const m = /^(\d+)\s+(\d+)\s+(.*)$/.exec(trimmed);
|
|
2091
|
+
if (!m) continue;
|
|
2092
|
+
procs.push({ pid: Number(m[1]), ppid: Number(m[2]), command: m[3] });
|
|
2093
|
+
}
|
|
2094
|
+
return procs;
|
|
2095
|
+
} catch {
|
|
2096
|
+
return null;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
function reapOrphans(opts = {}) {
|
|
2100
|
+
const procs = listProcesses();
|
|
2101
|
+
if (procs === null) return { unsupported: true, candidates: [], reaped: [] };
|
|
2102
|
+
const orphans = selectReapableOrphans(procs, process.pid);
|
|
2103
|
+
const candidates = orphans.map((p) => p.pid);
|
|
2104
|
+
const reaped = [];
|
|
2105
|
+
if (!opts.dryRun) {
|
|
2106
|
+
for (const pid of candidates) {
|
|
2107
|
+
try {
|
|
2108
|
+
process.kill(pid, "SIGTERM");
|
|
2109
|
+
reaped.push(pid);
|
|
2110
|
+
} catch {
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
return { unsupported: false, candidates, reaped };
|
|
2115
|
+
}
|
|
2116
|
+
function formatReapSummary(result, dryRun) {
|
|
2117
|
+
if (result.unsupported) {
|
|
2118
|
+
return "Orphan reaper: unsupported on this platform \u2014 skipped (no processes touched).";
|
|
2119
|
+
}
|
|
2120
|
+
if (result.candidates.length === 0) {
|
|
2121
|
+
return "Orphan reaper: no parentless @papi-ai/server processes found.";
|
|
2122
|
+
}
|
|
2123
|
+
if (dryRun) {
|
|
2124
|
+
return `Orphan reaper: ${result.candidates.length} parentless PAPI server process(es) found: ${result.candidates.join(", ")} (run with --reap-orphans to terminate).`;
|
|
2125
|
+
}
|
|
2126
|
+
return `Orphan reaper: terminated ${result.reaped.length} parentless PAPI server process(es): ${result.reaped.join(", ")}.`;
|
|
2127
|
+
}
|
|
2128
|
+
var init_reap_orphans = __esm({
|
|
2129
|
+
"src/lib/reap-orphans.ts"() {
|
|
2130
|
+
"use strict";
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2043
2134
|
// ../../node_modules/postgres/src/query.js
|
|
2044
2135
|
function cachedError(xs) {
|
|
2045
2136
|
if (originCache.has(xs))
|
|
@@ -4506,12 +4597,16 @@ async function runDoctor(cliArgs2 = []) {
|
|
|
4506
4597
|
const fixPool = cliArgs2.includes("--fix-pool") || cliArgs2.includes("--terminate-wedged");
|
|
4507
4598
|
const pool = await diagnosePool({ fix: fixPool });
|
|
4508
4599
|
process.stdout.write("\n" + formatPoolReport(pool) + "\n");
|
|
4600
|
+
const reap = cliArgs2.includes("--reap-orphans");
|
|
4601
|
+
const reapResult = reapOrphans({ dryRun: !reap });
|
|
4602
|
+
process.stdout.write("\n" + formatReapSummary(reapResult, !reap) + "\n");
|
|
4509
4603
|
return 0;
|
|
4510
4604
|
}
|
|
4511
4605
|
var SECRET_VARS, WEDGED_IDLE_TX_SECONDS, WEDGED_ACTIVE_SECONDS, __testing;
|
|
4512
4606
|
var init_doctor = __esm({
|
|
4513
4607
|
"src/cli/doctor.ts"() {
|
|
4514
4608
|
"use strict";
|
|
4609
|
+
init_reap_orphans();
|
|
4515
4610
|
SECRET_VARS = /* @__PURE__ */ new Set(["PAPI_DATA_API_KEY", "DATABASE_URL", "PAPI_ENDPOINT"]);
|
|
4516
4611
|
WEDGED_IDLE_TX_SECONDS = 300;
|
|
4517
4612
|
WEDGED_ACTIVE_SECONDS = 300;
|
|
@@ -5059,7 +5154,7 @@ var init_setup = __esm({
|
|
|
5059
5154
|
|
|
5060
5155
|
// src/index.ts
|
|
5061
5156
|
import { readFileSync as readFileSync16 } from "fs";
|
|
5062
|
-
import { dirname as dirname6, join as join24 } from "path";
|
|
5157
|
+
import { dirname as dirname6, join as join24, basename as basename2 } from "path";
|
|
5063
5158
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5064
5159
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5065
5160
|
import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -12765,6 +12860,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
|
|
|
12765
12860
|
const contextBytes2 = Buffer.byteLength(userMessage2, "utf-8");
|
|
12766
12861
|
console.error(`[plan-perf] contextBytes=${contextBytes2} (handoffs-only)`);
|
|
12767
12862
|
const planSystemPrompt2 = await getPrompt("plan-system");
|
|
12863
|
+
await recordPlanGenerationActive(tracker, incomingCycle);
|
|
12768
12864
|
return {
|
|
12769
12865
|
mode: "full",
|
|
12770
12866
|
// apply phase treats it the same
|
|
@@ -12825,6 +12921,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
|
|
|
12825
12921
|
} catch {
|
|
12826
12922
|
}
|
|
12827
12923
|
const planSystemPrompt = await getPrompt("plan-system");
|
|
12924
|
+
await recordPlanGenerationActive(tracker, incomingCycle);
|
|
12828
12925
|
return {
|
|
12829
12926
|
mode,
|
|
12830
12927
|
cycleNumber,
|
|
@@ -12848,6 +12945,15 @@ async function streamPlanStageSteps(tracker, newCycleNumber) {
|
|
|
12848
12945
|
await tracker.recordStep(step);
|
|
12849
12946
|
}
|
|
12850
12947
|
}
|
|
12948
|
+
async function recordPlanGenerationActive(tracker, incomingCycleNumber) {
|
|
12949
|
+
if (!tracker) return;
|
|
12950
|
+
await tracker.recordStep("recommendation", {
|
|
12951
|
+
cycle: incomingCycleNumber,
|
|
12952
|
+
stage: "plan",
|
|
12953
|
+
status: "active",
|
|
12954
|
+
metadata: { phase: "generating" }
|
|
12955
|
+
});
|
|
12956
|
+
}
|
|
12851
12957
|
async function applyPlan(adapter2, config2, rawLlmOutput, mode, cycleNumber, strategyReviewWarning, contextHashes, planRunMeta, tracker) {
|
|
12852
12958
|
const applyTimer = startTimer();
|
|
12853
12959
|
console.error(`[plan-perf] applyPlan: start (llm_response=${rawLlmOutput.length} chars)`);
|
|
@@ -15929,6 +16035,11 @@ Confidence: ${input.confidence}. Captured mid-conversation via strategy_change c
|
|
|
15929
16035
|
}
|
|
15930
16036
|
|
|
15931
16037
|
// src/tools/strategy.ts
|
|
16038
|
+
function toDateLabel(value) {
|
|
16039
|
+
if (typeof value === "string") return value.slice(0, 10);
|
|
16040
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
16041
|
+
return Number.isNaN(d.getTime()) ? "" : d.toISOString().slice(0, 10);
|
|
16042
|
+
}
|
|
15932
16043
|
var reviewPrepareCache = new PerCallerCache();
|
|
15933
16044
|
var strategyReviewTool = {
|
|
15934
16045
|
name: "strategy_review",
|
|
@@ -16230,7 +16341,7 @@ This topic will surface in the next \`strategy_review\`.`
|
|
|
16230
16341
|
const lines = topics.map((t, i) => {
|
|
16231
16342
|
const cycleSuffix = t.sourceCycle != null ? ` (Cycle ${t.sourceCycle})` : "";
|
|
16232
16343
|
return `${i + 1}. ${t.topic}
|
|
16233
|
-
_source: ${t.source}${cycleSuffix} \xB7 queued ${t.createdAt
|
|
16344
|
+
_source: ${t.source}${cycleSuffix} \xB7 queued ${toDateLabel(t.createdAt)}_`;
|
|
16234
16345
|
});
|
|
16235
16346
|
return textResponse(
|
|
16236
16347
|
`**Pending Agenda (${topics.length})** \u2014 surfaces at next strategy review
|
|
@@ -19716,6 +19827,61 @@ async function completeRelease(tracker, opts) {
|
|
|
19716
19827
|
}
|
|
19717
19828
|
await tracker.recordStep("released", { metadata: { version: opts.version } });
|
|
19718
19829
|
}
|
|
19830
|
+
var OPEN_PR_STALE_DAYS = 21;
|
|
19831
|
+
function taskIdFromBranch(headRefName) {
|
|
19832
|
+
const m = /^feat\/(task-\d+)\b/.exec(headRefName);
|
|
19833
|
+
return m ? m[1] : null;
|
|
19834
|
+
}
|
|
19835
|
+
function classifyOpenPr(pr, cycleNum, inReviewCycleTaskIds, nowMs) {
|
|
19836
|
+
const head = pr.headRefName;
|
|
19837
|
+
const ageDays = (nowMs - Date.parse(pr.createdAt)) / 864e5;
|
|
19838
|
+
if (new RegExp(`^feat/cycle-${cycleNum}-`).test(head)) {
|
|
19839
|
+
return { pr, bucket: "cycle-branch", action: `merge into this release \u2014 \`${head}\` is this cycle's branch and still open` };
|
|
19840
|
+
}
|
|
19841
|
+
const taskId = taskIdFromBranch(head);
|
|
19842
|
+
if (taskId && inReviewCycleTaskIds.has(taskId)) {
|
|
19843
|
+
return { pr, bucket: "held-adhoc-this-cycle", action: `MERGE before closing \u2014 ${taskId} is In Review pinned to Cycle ${cycleNum}` };
|
|
19844
|
+
}
|
|
19845
|
+
if (taskId) {
|
|
19846
|
+
return { pr, bucket: "held-adhoc-other", action: "held adhoc not pinned to this cycle \u2014 defer, or merge if ready" };
|
|
19847
|
+
}
|
|
19848
|
+
if (Number.isFinite(ageDays) && ageDays > OPEN_PR_STALE_DAYS) {
|
|
19849
|
+
return { pr, bucket: "stale", action: `review/close \u2014 open ${Math.round(ageDays)}d with no cycle link` };
|
|
19850
|
+
}
|
|
19851
|
+
return { pr, bucket: "external-other", action: "review manually \u2014 merge, defer, or close" };
|
|
19852
|
+
}
|
|
19853
|
+
var OPEN_PR_BUCKET_ORDER = [
|
|
19854
|
+
"held-adhoc-this-cycle",
|
|
19855
|
+
"cycle-branch",
|
|
19856
|
+
"external-other",
|
|
19857
|
+
"stale",
|
|
19858
|
+
"held-adhoc-other"
|
|
19859
|
+
];
|
|
19860
|
+
function buildOpenPrSweepLines(openPrs, cycleNum, inReviewCycleTaskIds, nowMs) {
|
|
19861
|
+
const lines = [];
|
|
19862
|
+
if (openPrs === null) {
|
|
19863
|
+
lines.push("", "**Open-PR sweep:** skipped \u2014 `gh` unavailable. Run `gh pr list` yourself to check for held/external PRs before considering the cycle closed.");
|
|
19864
|
+
} else if (openPrs.length > 0) {
|
|
19865
|
+
const classified = openPrs.map((pr) => classifyOpenPr(pr, cycleNum, inReviewCycleTaskIds, nowMs)).sort((a, b2) => OPEN_PR_BUCKET_ORDER.indexOf(a.bucket) - OPEN_PR_BUCKET_ORDER.indexOf(b2.bucket));
|
|
19866
|
+
lines.push("", `**Open-PR sweep \u2014 ${openPrs.length} open PR(s). Resolve each before considering Cycle ${cycleNum} closed:**`);
|
|
19867
|
+
for (const c of classified) {
|
|
19868
|
+
lines.push(`- #${c.pr.number} \`${c.pr.headRefName}\` by ${c.pr.author} \u2014 [${c.bucket}] ${c.action}`);
|
|
19869
|
+
}
|
|
19870
|
+
}
|
|
19871
|
+
if (inReviewCycleTaskIds.size > 0) {
|
|
19872
|
+
const prByTask = /* @__PURE__ */ new Map();
|
|
19873
|
+
for (const p of openPrs ?? []) {
|
|
19874
|
+
const id = taskIdFromBranch(p.headRefName);
|
|
19875
|
+
if (id) prByTask.set(id, p.number);
|
|
19876
|
+
}
|
|
19877
|
+
const rows = [...inReviewCycleTaskIds].sort().map((id) => {
|
|
19878
|
+
const prNum = prByTask.get(id);
|
|
19879
|
+
return ` - ${id}${prNum ? ` (PR #${prNum})` : " (no open PR found)"}`;
|
|
19880
|
+
});
|
|
19881
|
+
lines.push("", `\u26A0\uFE0F **${inReviewCycleTaskIds.size} task(s) still In Review, pinned to Cycle ${cycleNum} \u2014 their work is NOT merged. Accept/merge or defer before closing:**`, ...rows);
|
|
19882
|
+
}
|
|
19883
|
+
return lines;
|
|
19884
|
+
}
|
|
19719
19885
|
|
|
19720
19886
|
// src/tools/release.ts
|
|
19721
19887
|
init_git();
|
|
@@ -20206,6 +20372,20 @@ Run \`project_switch <slug>\` to switch the active PAPI project, or verify your
|
|
|
20206
20372
|
if (result.warnings?.length) {
|
|
20207
20373
|
lines.push("", "\u26A0\uFE0F Warnings: " + result.warnings.join("; "));
|
|
20208
20374
|
}
|
|
20375
|
+
try {
|
|
20376
|
+
const openPrs = listOpenPullRequests(config2.projectRoot);
|
|
20377
|
+
const closedCycle = result.cycleClosed ?? 0;
|
|
20378
|
+
let inReviewIds = /* @__PURE__ */ new Set();
|
|
20379
|
+
if (closedCycle > 0) {
|
|
20380
|
+
const board = await adapter2.queryBoard();
|
|
20381
|
+
inReviewIds = new Set(
|
|
20382
|
+
board.filter((t) => t.status === "In Review" && t.cycle === closedCycle).map((t) => t.id)
|
|
20383
|
+
);
|
|
20384
|
+
}
|
|
20385
|
+
const sweep = buildOpenPrSweepLines(openPrs, closedCycle, inReviewIds, Date.now());
|
|
20386
|
+
if (sweep.length > 0) lines.push(...sweep);
|
|
20387
|
+
} catch {
|
|
20388
|
+
}
|
|
20209
20389
|
tracker.mark("surface-discovered-issues");
|
|
20210
20390
|
try {
|
|
20211
20391
|
const closedCycle = result.cycleClosed ?? 0;
|
|
@@ -20947,8 +21127,8 @@ async function startBuild(adapter2, config2, taskId, options = {}, clientName) {
|
|
|
20947
21127
|
} else {
|
|
20948
21128
|
const stashLabel = `papi-autostash/${taskId}-${Math.floor(Date.now() / 1e3)}`;
|
|
20949
21129
|
try {
|
|
20950
|
-
const { execFileSync:
|
|
20951
|
-
|
|
21130
|
+
const { execFileSync: execFileSync7 } = await import("child_process");
|
|
21131
|
+
execFileSync7("git", ["stash", "push", "-u", "-m", stashLabel, "--", ...toStash], {
|
|
20952
21132
|
cwd: config2.projectRoot,
|
|
20953
21133
|
encoding: "utf-8"
|
|
20954
21134
|
});
|
|
@@ -20974,8 +21154,8 @@ async function startBuild(adapter2, config2, taskId, options = {}, clientName) {
|
|
|
20974
21154
|
if (hasRemote(config2.projectRoot) && !featureBranchExistsLocally) {
|
|
20975
21155
|
if (featureBranchOnOrigin) {
|
|
20976
21156
|
try {
|
|
20977
|
-
const { execFileSync:
|
|
20978
|
-
|
|
21157
|
+
const { execFileSync: execFileSync7 } = await import("child_process");
|
|
21158
|
+
execFileSync7("git", ["fetch", "origin", `${featureBranch}:${featureBranch}`], {
|
|
20979
21159
|
cwd: config2.projectRoot,
|
|
20980
21160
|
encoding: "utf-8",
|
|
20981
21161
|
timeout: 6e4
|
|
@@ -22099,6 +22279,10 @@ var buildExecuteTool = {
|
|
|
22099
22279
|
enum: ["yes", "no", "partial"],
|
|
22100
22280
|
description: "Whether the build was completed. Required for complete."
|
|
22101
22281
|
},
|
|
22282
|
+
acceptance_confirmed: {
|
|
22283
|
+
type: "boolean",
|
|
22284
|
+
description: `task-2833: set true to assert every acceptance criterion in the task's BUILD HANDOFF was met. Required to record a completed:"yes" build when the handoff lists acceptance criteria \u2014 without it, build_execute returns the criteria checklist and does NOT mark the task Done (the report is not discarded; re-send with acceptance_confirmed:true). Tasks with no acceptance criteria, and completed:"partial"/"no", are unaffected.`
|
|
22285
|
+
},
|
|
22102
22286
|
effort: {
|
|
22103
22287
|
type: "string",
|
|
22104
22288
|
enum: ["XS", "S", "M", "L", "XL"],
|
|
@@ -22436,6 +22620,27 @@ ${entries}`;
|
|
|
22436
22620
|
}
|
|
22437
22621
|
} catch {
|
|
22438
22622
|
}
|
|
22623
|
+
let openIssuesSection = "";
|
|
22624
|
+
try {
|
|
22625
|
+
if (adapter2.getCycleLearnings) {
|
|
22626
|
+
const issues = (await adapter2.getCycleLearnings({ category: "issue", limit: 20 })).filter((l) => !l.resolvedAt && l.id);
|
|
22627
|
+
const taskIds = new Set([result.task.id, result.task.displayId].filter(Boolean));
|
|
22628
|
+
const moduleTag = result.task.module?.trim().toLowerCase();
|
|
22629
|
+
const score = (l) => (taskIds.has(l.taskId) ? 2 : 0) + (moduleTag && l.tags.some((t) => t.toLowerCase() === moduleTag) ? 1 : 0);
|
|
22630
|
+
const top = issues.sort((a, b2) => score(b2) - score(a)).slice(0, 8);
|
|
22631
|
+
if (top.length > 0) {
|
|
22632
|
+
const rows = top.map((l) => `- \`${l.id}\` \xB7 ${l.severity ?? "P3"} \xB7 ${l.summary}`).join("\n");
|
|
22633
|
+
openIssuesSection = `
|
|
22634
|
+
|
|
22635
|
+
---
|
|
22636
|
+
|
|
22637
|
+
**OPEN DISCOVERED ISSUES** (${top.length} shown):
|
|
22638
|
+
${rows}
|
|
22639
|
+
If this build fixes any of these, pass their UUIDs in \`fixed_issues\` on complete \u2014 that stamps them FIXED on the caught\u2192fixed ledger. Do not fix out-of-scope issues just to clear the list.`;
|
|
22640
|
+
}
|
|
22641
|
+
}
|
|
22642
|
+
} catch {
|
|
22643
|
+
}
|
|
22439
22644
|
const moduleInstructions = getModuleInstructions(result.task.module);
|
|
22440
22645
|
const moduleContext = await getModuleContext(adapter2, result.task);
|
|
22441
22646
|
const filesToWriteSection = result.filesToWrite ? formatFilesToWriteSection(result.filesToWrite) : "";
|
|
@@ -22444,7 +22649,7 @@ ${entries}`;
|
|
|
22444
22649
|
formatModelRecommendation(result.task.buildHandoff?.effort ?? result.task.complexity)
|
|
22445
22650
|
) ?? "";
|
|
22446
22651
|
const gestaltNote = buildGestaltPreBuildDirective(caps) ?? "";
|
|
22447
|
-
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + verificationNote + buildDisciplineNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
22652
|
+
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + openIssuesSection + verificationNote + buildDisciplineNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
22448
22653
|
} catch (err) {
|
|
22449
22654
|
if (isNoHandoffError(err)) {
|
|
22450
22655
|
const lines = [
|
|
@@ -22533,6 +22738,25 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
22533
22738
|
if (!parsedEstimatedEffort) {
|
|
22534
22739
|
return errorResponse(`Invalid estimated_effort value "${estimatedEffort}". Must be one of: XS, S, M, L, XL.`);
|
|
22535
22740
|
}
|
|
22741
|
+
const acceptanceConfirmed = args.acceptance_confirmed === true;
|
|
22742
|
+
if (completed === "yes" && !acceptanceConfirmed) {
|
|
22743
|
+
const gateInfo = adapter2.getProjectInfo ? await adapter2.getProjectInfo().catch(() => null) : null;
|
|
22744
|
+
const gateCaps = gateInfo?.capabilities ?? {};
|
|
22745
|
+
const gateTask = isCapabilityEnabled(gateCaps, "acceptanceGate") ? await adapter2.getTask(taskId).catch(() => null) : null;
|
|
22746
|
+
const criteria = (gateTask?.buildHandoff?.acceptanceCriteria ?? []).filter((c) => c && c.trim());
|
|
22747
|
+
if (criteria.length > 0) {
|
|
22748
|
+
const checklist = criteria.map((c) => ` - [ ] ${c}`).join("\n");
|
|
22749
|
+
return textResponse(
|
|
22750
|
+
`**Acceptance criteria not yet confirmed for ${taskId}.**
|
|
22751
|
+
|
|
22752
|
+
This task's BUILD HANDOFF lists ${criteria.length} acceptance criteri${criteria.length === 1 ? "on" : "a"}. Confirm each was met, then re-call \`build_execute\` complete with the SAME report fields plus \`acceptance_confirmed: true\`:
|
|
22753
|
+
|
|
22754
|
+
${checklist}
|
|
22755
|
+
|
|
22756
|
+
Your report was NOT discarded and the task is NOT yet Done \u2014 re-send with \`acceptance_confirmed: true\` to record completion. If a criterion was NOT met, the task is not complete: finish it, or report \`completed: "partial"\` with what remains in \`surprises\`.`
|
|
22757
|
+
);
|
|
22758
|
+
}
|
|
22759
|
+
}
|
|
22536
22760
|
const tracker = new ProgressTracker("complete_validate").bindStream(adapter2, { stage: "build", taskId });
|
|
22537
22761
|
try {
|
|
22538
22762
|
tracker.mark("complete_build");
|
|
@@ -22618,9 +22842,14 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
|
|
|
22618
22842
|
batchRollupNote = "";
|
|
22619
22843
|
}
|
|
22620
22844
|
}
|
|
22621
|
-
|
|
22845
|
+
let fixedNote = "";
|
|
22846
|
+
if (fixedResolvedCount > 0) {
|
|
22847
|
+
fixedNote = `
|
|
22622
22848
|
|
|
22623
|
-
\u2705 Marked ${fixedResolvedCount} discovered issue(s) FIXED \u2014 resolved_at stamped, now counted as fixed on the hub's caught\u2192fixed ledger
|
|
22849
|
+
\u2705 Marked ${fixedResolvedCount} discovered issue(s) FIXED \u2014 resolved_at stamped, now counted as fixed on the hub's caught\u2192fixed ledger.`;
|
|
22850
|
+
} else if (discoveredIssues && discoveredIssues.trim() !== "" && !/^none\b/i.test(discoveredIssues.trim())) {
|
|
22851
|
+
fixedNote = "\n\n\u2139\uFE0F This build filed discovered issues but passed no `fixed_issues`. When a future build fixes one, pass its UUID in `fixed_issues` so it counts as FIXED (not just auto-cleared) on the hub ledger.";
|
|
22852
|
+
}
|
|
22624
22853
|
return textResponse(formatCompleteResult(result) + fixedNote + docsNote + batchRollupNote);
|
|
22625
22854
|
} catch (err) {
|
|
22626
22855
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -23088,7 +23317,11 @@ async function handleBug(adapter2, config2, args) {
|
|
|
23088
23317
|
type,
|
|
23089
23318
|
description: text,
|
|
23090
23319
|
diagnostics,
|
|
23091
|
-
|
|
23320
|
+
// Canonical triage vocabulary (submitted|investigating|fixed|wont_fix).
|
|
23321
|
+
// Was 'open' — a third vocabulary that made the owner triage console
|
|
23322
|
+
// render new upstream reports as un-triaged. Adapters pass status through,
|
|
23323
|
+
// so emitting canonical here keeps hosted + pg writes consistent.
|
|
23324
|
+
status: "submitted",
|
|
23092
23325
|
notifyRequested,
|
|
23093
23326
|
contactOk
|
|
23094
23327
|
});
|
|
@@ -24181,6 +24414,13 @@ Re-run build_execute complete with a production_verification field, then re-subm
|
|
|
24181
24414
|
|
|
24182
24415
|
// src/tools/review.ts
|
|
24183
24416
|
var REVIEW_DISPATCH_THRESHOLD = 50 * 1024;
|
|
24417
|
+
var REVIEW_DISPATCH_CEILING = 40 * 1024;
|
|
24418
|
+
var REVIEW_ECHO_CAP = 4e3;
|
|
24419
|
+
function trimForEcho(text) {
|
|
24420
|
+
if (text.length <= REVIEW_ECHO_CAP) return text;
|
|
24421
|
+
return `${text.slice(0, REVIEW_ECHO_CAP)}
|
|
24422
|
+
\u2026[trimmed ${text.length - REVIEW_ECHO_CAP} chars]`;
|
|
24423
|
+
}
|
|
24184
24424
|
var REVIEW_RUBRIC = [
|
|
24185
24425
|
"You are reviewing a completed PAPI build for acceptance. Judge:",
|
|
24186
24426
|
"- Correctness: does the change do what the build report claims, without obvious bugs?",
|
|
@@ -24510,6 +24750,7 @@ async function handleReviewSubmit(adapter2, config2, args) {
|
|
|
24510
24750
|
const autoDispatchOptIn = args.dispatch !== "inline" && process.env.PAPI_AUTO_DISPATCH !== "false" && isCapabilityEnabled(caps, "prReviewer");
|
|
24511
24751
|
const autoDispatchEligible = !verdict && autoDispatchOptIn;
|
|
24512
24752
|
const capabilityAutoReviewEligible = verdict === "accept" && !autoReview && autoDispatchOptIn;
|
|
24753
|
+
let capabilityReviewSkippedNote = "";
|
|
24513
24754
|
if ((explicitDispatch || autoDispatchEligible || capabilityAutoReviewEligible) && stage === "build-acceptance" && taskId) {
|
|
24514
24755
|
const dispatch = await buildReviewDispatch(
|
|
24515
24756
|
adapter2,
|
|
@@ -24519,8 +24760,16 @@ async function handleReviewSubmit(adapter2, config2, args) {
|
|
|
24519
24760
|
);
|
|
24520
24761
|
if (!dispatch.ok) {
|
|
24521
24762
|
if (explicitDispatch) return errorResponse(dispatch.error);
|
|
24522
|
-
} else if (explicitDispatch ||
|
|
24763
|
+
} else if (explicitDispatch || autoDispatchEligible && dispatch.contextBytes > REVIEW_DISPATCH_THRESHOLD) {
|
|
24523
24764
|
return textResponse(dispatch.prompt);
|
|
24765
|
+
} else if (capabilityAutoReviewEligible) {
|
|
24766
|
+
if (dispatch.contextBytes <= REVIEW_DISPATCH_CEILING) {
|
|
24767
|
+
return textResponse(dispatch.prompt);
|
|
24768
|
+
}
|
|
24769
|
+
const kb = (dispatch.contextBytes / 1024).toFixed(0);
|
|
24770
|
+
capabilityReviewSkippedNote = `
|
|
24771
|
+
|
|
24772
|
+
> \u26A0\uFE0F pr-reviewer auto-review skipped \u2014 the diff/build-report (~${kb} KB) exceeds the ${REVIEW_DISPATCH_CEILING / 1024} KB inline-dispatch ceiling, which would overflow the response and drop the accept (task-2854). Verdict recorded directly. To review the diff explicitly, run \`review_submit ${taskId} build-acceptance accept dispatch:"subagent"\`.`;
|
|
24524
24773
|
}
|
|
24525
24774
|
}
|
|
24526
24775
|
if (explicitDispatch && stage !== "build-acceptance") {
|
|
@@ -24680,36 +24929,51 @@ ${overlap}`;
|
|
|
24680
24929
|
|
|
24681
24930
|
\u2705 Verdict recorded. All cycle tasks are Done, but **auto-release is owner-only** \u2014 your identity does not match this project's owner, so no release was cut. Push your branch and open a PR for the owner to run \`release\`.${resolutionNote}`;
|
|
24682
24931
|
} else if (cycleTasks.length > 0 && cycleTasks.every((t) => t.status === "Done")) {
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
|
|
24932
|
+
let planRunCount = null;
|
|
24933
|
+
if (typeof adapter2.countPlanRunsForCycle === "function") {
|
|
24934
|
+
try {
|
|
24935
|
+
planRunCount = await adapter2.countPlanRunsForCycle(result.currentCycle);
|
|
24936
|
+
} catch {
|
|
24937
|
+
planRunCount = null;
|
|
24938
|
+
}
|
|
24939
|
+
}
|
|
24940
|
+
if (planRunCount === 0) {
|
|
24686
24941
|
autoReleaseNote = `
|
|
24687
24942
|
|
|
24688
24943
|
---
|
|
24689
24944
|
|
|
24945
|
+
\u26A0\uFE0F **Auto-release skipped** \u2014 Cycle ${result.currentCycle} has **no plan run** (100% injected/adhoc work), so it was never planned. Auto-release only fires for planned cycles, to avoid silently shipping a cycle nobody opened (C340 incident). All tasks are Done \u2014 run \`release\` explicitly to close and ship this cycle.`;
|
|
24946
|
+
} else {
|
|
24947
|
+
const baseBranch = resolveBaseBranch(config2.projectRoot, config2.baseBranch);
|
|
24948
|
+
const unmergedCycleBranches = isGitAvailable() && isGitRepo(config2.projectRoot) ? listGroupedCycleBranches(config2.projectRoot, result.currentCycle, baseBranch) : [];
|
|
24949
|
+
if (unmergedCycleBranches.length > 0) {
|
|
24950
|
+
autoReleaseNote = `
|
|
24951
|
+
|
|
24952
|
+
---
|
|
24953
|
+
|
|
24690
24954
|
\u26A0\uFE0F **Auto-release skipped** \u2014 all tasks are Done but ${unmergedCycleBranches.length} cycle branch(es) not yet merged: \`${unmergedCycleBranches.join("`, `")}\`.
|
|
24691
24955
|
|
|
24692
24956
|
Merge or squash those PRs first, then run \`release\` manually.`;
|
|
24693
|
-
|
|
24694
|
-
|
|
24695
|
-
|
|
24696
|
-
|
|
24697
|
-
|
|
24698
|
-
|
|
24699
|
-
|
|
24700
|
-
|
|
24701
|
-
|
|
24702
|
-
|
|
24703
|
-
|
|
24704
|
-
|
|
24705
|
-
|
|
24706
|
-
|
|
24707
|
-
|
|
24957
|
+
} else {
|
|
24958
|
+
try {
|
|
24959
|
+
const allReviews = await adapter2.getRecentReviews(200);
|
|
24960
|
+
const cycleReviews = allReviews.filter(
|
|
24961
|
+
(r) => r.cycle === result.currentCycle && r.stage === "build-acceptance"
|
|
24962
|
+
);
|
|
24963
|
+
const reviewsWithAutoReview = cycleReviews.filter((r) => r.autoReview);
|
|
24964
|
+
if (reviewsWithAutoReview.length > 0) {
|
|
24965
|
+
const verdictCounts = { pass: 0, warn: 0, fail: 0 };
|
|
24966
|
+
const findingsBySeverity = { error: 0, warning: 0, info: 0 };
|
|
24967
|
+
for (const r of reviewsWithAutoReview) {
|
|
24968
|
+
if (r.autoReview) {
|
|
24969
|
+
verdictCounts[r.autoReview.verdict] = (verdictCounts[r.autoReview.verdict] ?? 0) + 1;
|
|
24970
|
+
for (const f of r.autoReview.findings) {
|
|
24971
|
+
findingsBySeverity[f.severity] = (findingsBySeverity[f.severity] ?? 0) + 1;
|
|
24972
|
+
}
|
|
24708
24973
|
}
|
|
24709
24974
|
}
|
|
24710
|
-
|
|
24711
|
-
|
|
24712
|
-
batchSummaryNote = `
|
|
24975
|
+
const totalFindings = findingsBySeverity.error + findingsBySeverity.warning + findingsBySeverity.info;
|
|
24976
|
+
batchSummaryNote = `
|
|
24713
24977
|
|
|
24714
24978
|
---
|
|
24715
24979
|
|
|
@@ -24717,42 +24981,42 @@ Merge or squash those PRs first, then run \`release\` manually.`;
|
|
|
24717
24981
|
|
|
24718
24982
|
- Verdicts: ${verdictCounts.pass} pass, ${verdictCounts.warn} warn, ${verdictCounts.fail} fail
|
|
24719
24983
|
` + (totalFindings > 0 ? `- Findings: ${findingsBySeverity.error} error${findingsBySeverity.error !== 1 ? "s" : ""}, ${findingsBySeverity.warning} warning${findingsBySeverity.warning !== 1 ? "s" : ""}, ${findingsBySeverity.info} info` : "- No findings logged");
|
|
24984
|
+
}
|
|
24985
|
+
} catch {
|
|
24720
24986
|
}
|
|
24721
|
-
|
|
24722
|
-
|
|
24723
|
-
|
|
24724
|
-
|
|
24725
|
-
if (autoGate.action !== "proceed") {
|
|
24726
|
-
autoReleaseNote = `
|
|
24987
|
+
const version = `v0.${result.currentCycle}.0`;
|
|
24988
|
+
const autoGate = evaluateReleaseGate(caps, config2.gateCommand, void 0);
|
|
24989
|
+
if (autoGate.action !== "proceed") {
|
|
24990
|
+
autoReleaseNote = `
|
|
24727
24991
|
|
|
24728
24992
|
---
|
|
24729
24993
|
|
|
24730
24994
|
\u26A0\uFE0F **Auto-release skipped** \u2014 a release quality gate is configured (\`${config2.gateCommand}\`), and it cannot be run from inside \`review_submit\`.
|
|
24731
24995
|
|
|
24732
24996
|
Run \`release\` manually: PAPI will hand you the gate command, then release once you report it green.`;
|
|
24733
|
-
|
|
24734
|
-
|
|
24735
|
-
|
|
24736
|
-
|
|
24737
|
-
|
|
24738
|
-
|
|
24739
|
-
|
|
24740
|
-
|
|
24741
|
-
|
|
24742
|
-
|
|
24743
|
-
|
|
24744
|
-
|
|
24745
|
-
|
|
24746
|
-
|
|
24747
|
-
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24754
|
-
|
|
24755
|
-
|
|
24997
|
+
} else {
|
|
24998
|
+
const releaseTracker = new ProgressTracker("auto-release").bindStream(adapter2, { stage: "release" });
|
|
24999
|
+
await beginRelease(releaseTracker, result.currentCycle);
|
|
25000
|
+
const releaseResult = await createRelease(config2, baseBranch, version, adapter2, result.currentCycle);
|
|
25001
|
+
await recordReadinessVerified(releaseTracker);
|
|
25002
|
+
await recordQualityGate(releaseTracker, autoGate, caps);
|
|
25003
|
+
await tracker.recordStep("auto_release_triggered", { metadata: { version: releaseResult.version } });
|
|
25004
|
+
const autoChangelogDirective = buildChangelogDirective(
|
|
25005
|
+
caps,
|
|
25006
|
+
buildCycleUpdateCurationDirective(releaseResult.version, releaseResult.cycleClosed ?? 0)
|
|
25007
|
+
);
|
|
25008
|
+
const autoDeployDirective = buildDeployHookDirective(caps, config2.deployCommand);
|
|
25009
|
+
await completeRelease(releaseTracker, {
|
|
25010
|
+
cycleClosed: releaseResult.cycleClosed ?? null,
|
|
25011
|
+
version: releaseResult.version,
|
|
25012
|
+
caps,
|
|
25013
|
+
branchMerges: releaseResult.groupedBranchMerges ?? [],
|
|
25014
|
+
changelogEmitted: Boolean(autoChangelogDirective),
|
|
25015
|
+
deployHookEmitted: Boolean(autoDeployDirective)
|
|
25016
|
+
});
|
|
25017
|
+
const pushInfo = releaseResult.pushNotes.join(" ");
|
|
25018
|
+
const groupedMergeNote = releaseResult.groupedBranchMerges?.length ? "\n" + releaseResult.groupedBranchMerges.map((r) => `- Merged shared branch \`${r.branch}\` via PR: ${r.prUrl ?? "n/a"}`).join("\n") : "";
|
|
25019
|
+
autoReleaseNote = `
|
|
24756
25020
|
|
|
24757
25021
|
---
|
|
24758
25022
|
|
|
@@ -24763,13 +25027,14 @@ Run \`release\` manually: PAPI will hand you the gate command, then release once
|
|
|
24763
25027
|
- ${releaseResult.tagMessage}
|
|
24764
25028
|
- ${pushInfo}` + groupedMergeNote + (releaseResult.warnings?.length ? `
|
|
24765
25029
|
- Warnings: ${releaseResult.warnings.join(", ")}` : "") + // task-2598 (C328): the auto path previously swallowed the curated
|
|
24766
|
-
|
|
24767
|
-
|
|
24768
|
-
|
|
25030
|
+
// cycle-update directive that the manual path emits, so an auto-released
|
|
25031
|
+
// cycle never prompted the Discord post. Same directive, same gate.
|
|
25032
|
+
(autoChangelogDirective ? `
|
|
24769
25033
|
${autoChangelogDirective}` : "") + (autoDeployDirective ? `
|
|
24770
25034
|
${autoDeployDirective}` : "") + `
|
|
24771
25035
|
|
|
24772
25036
|
Run \`plan\` to create Cycle ${result.currentCycle + 1}.`;
|
|
25037
|
+
}
|
|
24773
25038
|
}
|
|
24774
25039
|
}
|
|
24775
25040
|
}
|
|
@@ -24829,9 +25094,9 @@ Next: address the feedback, then run \`build_execute ${taskId}\` to resubmit.`;
|
|
|
24829
25094
|
`**${result.stageLabel}** recorded for ${result.taskId}.
|
|
24830
25095
|
|
|
24831
25096
|
- **Verdict:** ${result.verdict}
|
|
24832
|
-
- **Comments:** ${result.comments}
|
|
25097
|
+
- **Comments:** ${trimForEcho(result.comments)}
|
|
24833
25098
|
|
|
24834
|
-
${statusNote}${autoReviewNote}${securityNote}${unblockNote}${docClosureNote}${regenNote}${mergeNote}${overlapNote}${batchSummaryNote}${autoReleaseNote}${nextStepNote}${phaseNote}`
|
|
25099
|
+
${statusNote}${capabilityReviewSkippedNote}${autoReviewNote}${securityNote}${unblockNote}${docClosureNote}${regenNote}${mergeNote}${overlapNote}${batchSummaryNote}${autoReleaseNote}${nextStepNote}${phaseNote}`
|
|
24835
25100
|
);
|
|
24836
25101
|
} catch (err) {
|
|
24837
25102
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -29697,6 +29962,13 @@ function startHttpTransport(opts) {
|
|
|
29697
29962
|
const ip = clientIp(req);
|
|
29698
29963
|
const origin = req.headers.origin;
|
|
29699
29964
|
const cors = corsHeaders(origin);
|
|
29965
|
+
const pathname = (() => {
|
|
29966
|
+
try {
|
|
29967
|
+
return new URL(req.url ?? "/", "http://internal").pathname;
|
|
29968
|
+
} catch {
|
|
29969
|
+
return req.url ?? "/";
|
|
29970
|
+
}
|
|
29971
|
+
})();
|
|
29700
29972
|
if (req.method === "OPTIONS") {
|
|
29701
29973
|
if (Object.keys(cors).length === 0) {
|
|
29702
29974
|
sendError(res, { status: 403, body: { error: "Origin not allowed" } });
|
|
@@ -29711,12 +29983,12 @@ function startHttpTransport(opts) {
|
|
|
29711
29983
|
sendError(res, { status: 400, body: { error: "HTTPS required" } });
|
|
29712
29984
|
return;
|
|
29713
29985
|
}
|
|
29714
|
-
if (req.method === "GET" &&
|
|
29986
|
+
if (req.method === "GET" && pathname === "/healthz") {
|
|
29715
29987
|
res.writeHead(200, { "Content-Type": "text/plain", ...cors });
|
|
29716
29988
|
res.end("ok");
|
|
29717
29989
|
return;
|
|
29718
29990
|
}
|
|
29719
|
-
if (req.method === "GET" &&
|
|
29991
|
+
if (req.method === "GET" && pathname === "/.well-known/oauth-protected-resource") {
|
|
29720
29992
|
res.writeHead(200, {
|
|
29721
29993
|
"Content-Type": "application/json",
|
|
29722
29994
|
"Cache-Control": "public, max-age=3600",
|
|
@@ -29732,7 +30004,7 @@ function startHttpTransport(opts) {
|
|
|
29732
30004
|
);
|
|
29733
30005
|
return;
|
|
29734
30006
|
}
|
|
29735
|
-
if (req.method === "GET" &&
|
|
30007
|
+
if (req.method === "GET" && pathname === "/.well-known/glama.json") {
|
|
29736
30008
|
res.writeHead(200, {
|
|
29737
30009
|
"Content-Type": "application/json",
|
|
29738
30010
|
"Cache-Control": "public, max-age=3600",
|
|
@@ -29746,7 +30018,7 @@ function startHttpTransport(opts) {
|
|
|
29746
30018
|
);
|
|
29747
30019
|
return;
|
|
29748
30020
|
}
|
|
29749
|
-
if (req.method === "GET" &&
|
|
30021
|
+
if (req.method === "GET" && pathname === "/.well-known/oauth-authorization-server") {
|
|
29750
30022
|
res.writeHead(302, {
|
|
29751
30023
|
Location: `${DASHBOARD_ORIGIN}/.well-known/oauth-authorization-server`,
|
|
29752
30024
|
"Cache-Control": "public, max-age=3600",
|
|
@@ -29755,7 +30027,7 @@ function startHttpTransport(opts) {
|
|
|
29755
30027
|
res.end();
|
|
29756
30028
|
return;
|
|
29757
30029
|
}
|
|
29758
|
-
if (
|
|
30030
|
+
if (pathname !== "/mcp" && pathname !== "/sse") {
|
|
29759
30031
|
sendError(res, { status: 404, body: { error: "Not found" } }, cors);
|
|
29760
30032
|
return;
|
|
29761
30033
|
}
|
|
@@ -30035,6 +30307,7 @@ Options:
|
|
|
30035
30307
|
--yes, -y Skip confirmation prompts (reset only)
|
|
30036
30308
|
--idle-mcp Flag PAPI-idle projects in audit (needs DATABASE_URL; read-only)
|
|
30037
30309
|
--fix-pool Terminate this role's confirmed-wedged DB backends (doctor only; guarded)
|
|
30310
|
+
--reap-orphans Terminate parentless @papi-ai/server processes (doctor only; guarded)
|
|
30038
30311
|
|
|
30039
30312
|
Getting started:
|
|
30040
30313
|
1. Run "npx @papi-ai/server setup" in any project folder
|
|
@@ -30197,6 +30470,21 @@ if (isHttpMode && httpPort !== void 0) {
|
|
|
30197
30470
|
process.stderr.write("[papi] Fatal: stdio mode requires an MCP server instance.\n");
|
|
30198
30471
|
process.exit(1);
|
|
30199
30472
|
}
|
|
30473
|
+
try {
|
|
30474
|
+
const { reapOrphans: reapOrphans2 } = await Promise.resolve().then(() => (init_reap_orphans(), reap_orphans_exports));
|
|
30475
|
+
const swept = reapOrphans2({});
|
|
30476
|
+
if (swept.reaped.length > 0) {
|
|
30477
|
+
process.stderr.write(`[papi] Reaped ${swept.reaped.length} orphaned server process(es): ${swept.reaped.join(", ")}
|
|
30478
|
+
`);
|
|
30479
|
+
}
|
|
30480
|
+
} catch {
|
|
30481
|
+
}
|
|
30200
30482
|
const transport = new StdioServerTransport();
|
|
30201
30483
|
await server.connect(transport);
|
|
30484
|
+
const projectName = basename2(config.projectRoot);
|
|
30485
|
+
const projectIdShort = config.projectId ? ` (${config.projectId.slice(0, 8)})` : process.env.PAPI_PROJECT_ID ? ` (${process.env.PAPI_PROJECT_ID.slice(0, 8)})` : "";
|
|
30486
|
+
process.stderr.write(
|
|
30487
|
+
`[papi] Connected \u2014 project: ${projectName}${projectIdShort}, adapter: ${config.adapterType}, v${pkgVersion}
|
|
30488
|
+
`
|
|
30489
|
+
);
|
|
30202
30490
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.63",
|
|
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",
|
|
@@ -38,6 +38,7 @@ When a conversation starts — fresh window, new session, or after context compr
|
|
|
38
38
|
- **All in-cycle, in-module tasks share `feat/cycle-N-<module>`** regardless of complexity. One branch per module per cycle, merged together. Module-less tasks fall back to a per-task branch.
|
|
39
39
|
- **Dependent tasks (any size):** When a task's BUILD HANDOFF lists a `DEPENDS ON` task from the same cycle, `build_execute` automatically reuses the upstream task's branch so commits stack for a single PR. Do not create a separate branch manually.
|
|
40
40
|
- **Commit per task within grouped branches** — traceable git history.
|
|
41
|
+
- **Integration + gate before release when a cycle fans to more than 4 module branches.** A wide cycle has no single point where all branches are proven together, so a collision between two branches only surfaces at release. When a cycle has more than 4 module branches, before `release`: (1) cut an integration branch off your main branch, (2) merge every cycle module branch into it, (3) run your full local gate/test suite on the integrated result, (4) resolve any cross-branch collisions there, (5) release from the integrated branch. This is a habit, not automation — release does not block on branch count.
|
|
41
42
|
- **Never use `build_execute` with `light=true` on shared branches.** Light mode commits directly to the current branch without creating a PR. When a shared branch is squash-merged, those commits are collapsed — any CLAUDE.md or documentation changes are stripped. Use light mode only on isolated single-task branches where no squash-merge will occur.
|
|
42
43
|
|
|
43
44
|
## Plumbing Is Autonomous
|