@papi-ai/server 0.7.85 → 0.7.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backfill-cycle-metrics.js +172 -41
- package/dist/index.js +3005 -1026
- package/dist/prompts.js +70 -14
- package/dist/statusline.js +168 -0
- package/package.json +5 -6
|
@@ -30,7 +30,6 @@ function resolveDeploymentProfile(env) {
|
|
|
30
30
|
dataEndpoint: cleanUrl(env.dataEndpoint) ?? (hostedSupabaseUrl ? `${hostedSupabaseUrl}/functions/v1/data-proxy` : void 0) ?? (selfHosted ? void 0 : `${HOSTED_SUPABASE_URL}/functions/v1/data-proxy`),
|
|
31
31
|
providers: {
|
|
32
32
|
vercel: optionalBoolean(env.vercelEnabled, !selfHosted),
|
|
33
|
-
railway: optionalBoolean(env.railwayEnabled, !selfHosted),
|
|
34
33
|
managedSupabase: optionalBoolean(env.managedSupabaseEnabled, !selfHosted)
|
|
35
34
|
},
|
|
36
35
|
auth: {
|
|
@@ -100,6 +99,21 @@ var init_dist = __esm({
|
|
|
100
99
|
"../shared/dist/index.js"() {
|
|
101
100
|
"use strict";
|
|
102
101
|
CAPABILITY_REGISTRY = [
|
|
102
|
+
// task-3384: the PLAN step. The landing page has advertised these three since
|
|
103
|
+
// the v5 control beat (components/marketing/landing/ControlC.tsx) with a code
|
|
104
|
+
// comment reading "shipping soon, shown now" — the registry had no plan-step
|
|
105
|
+
// entry at all, so the Planning group rendered empty and was dropped. These
|
|
106
|
+
// are NOT new behaviour: each one gates a section of the planner instructions
|
|
107
|
+
// that already runs on every full-mode plan, so ON reproduces today exactly.
|
|
108
|
+
//
|
|
109
|
+
// All three default ON. Turning one off removes its step from the planner
|
|
110
|
+
// prompt, which is the whole mechanism — the planner does what the prompt
|
|
111
|
+
// tells it to, so an omitted step is genuinely not performed (and the matching
|
|
112
|
+
// plan-prepare progress checkpoint is not recorded, so the hub does not claim
|
|
113
|
+
// a step that never ran).
|
|
114
|
+
{ key: "planHealthCheck", label: "Health check", description: "Flags cycle gaps, stale tasks and AD conflicts before planning.", step: "plan" },
|
|
115
|
+
{ key: "backlogTriage", label: "Backlog triage", description: "Cleans up and prioritises unreviewed backlog items during planning.", step: "plan" },
|
|
116
|
+
{ key: "buildOrder", label: "Build order", description: "Detects intra-cycle dependencies and sequences the build.", step: "plan" },
|
|
103
117
|
{ key: "prReviewer", label: "Auto code review", description: "Runs a code review of the branch diff before accepting.", step: "review" },
|
|
104
118
|
{ key: "securityScan", label: "Security scan", description: "Flags a security pass on risk-tier changes at review time.", step: "review" },
|
|
105
119
|
{ key: "changelog", label: "Changelog & cycle update", description: "Curates a cycle-update post when a release ships.", step: "release" },
|
|
@@ -154,7 +168,7 @@ var init_dist = __esm({
|
|
|
154
168
|
CAPABILITY_KEYS = CAPABILITY_REGISTRY.map((c) => c.key);
|
|
155
169
|
HOSTED_APP_URL = "https://getpapi.ai";
|
|
156
170
|
HOSTED_MCP_URL = "https://mcp.getpapi.ai";
|
|
157
|
-
HOSTED_SUPABASE_URL = "https://
|
|
171
|
+
HOSTED_SUPABASE_URL = "https://api.getpapi.ai";
|
|
158
172
|
WAB_WINDOW_DAYS = 7;
|
|
159
173
|
WAB_WEEK_MS = WAB_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
|
|
160
174
|
EFFORT_SCALE = {
|
|
@@ -175,6 +189,7 @@ __export(git_exports, {
|
|
|
175
189
|
checkoutBranch: () => checkoutBranch,
|
|
176
190
|
commitSinglePath: () => commitSinglePath,
|
|
177
191
|
commitStagedOnly: () => commitStagedOnly,
|
|
192
|
+
computeAheadBehind: () => computeAheadBehind,
|
|
178
193
|
createAndCheckoutBranch: () => createAndCheckoutBranch,
|
|
179
194
|
createPullRequest: () => createPullRequest,
|
|
180
195
|
createTag: () => createTag,
|
|
@@ -185,8 +200,10 @@ __export(git_exports, {
|
|
|
185
200
|
detectUnrecordedCommits: () => detectUnrecordedCommits,
|
|
186
201
|
ensureLatestDevelop: () => ensureLatestDevelop,
|
|
187
202
|
ensureTagAtHead: () => ensureTagAtHead,
|
|
203
|
+
fetchBaseBranch: () => fetchBaseBranch,
|
|
188
204
|
findContributorReleasePullRequests: () => findContributorReleasePullRequests,
|
|
189
205
|
findTaskCommitsOnBase: () => findTaskCommitsOnBase,
|
|
206
|
+
getBaseDivergence: () => getBaseDivergence,
|
|
190
207
|
getBranchDiff: () => getBranchDiff,
|
|
191
208
|
getCommitFiles: () => getCommitFiles,
|
|
192
209
|
getCommitsSinceTag: () => getCommitsSinceTag,
|
|
@@ -221,6 +238,7 @@ __export(git_exports, {
|
|
|
221
238
|
hasUnpushedCommits: () => hasUnpushedCommits,
|
|
222
239
|
isBranchContentAlreadyInBase: () => isBranchContentAlreadyInBase,
|
|
223
240
|
isBranchMergedInto: () => isBranchMergedInto,
|
|
241
|
+
isCommitReachable: () => isCommitReachable,
|
|
224
242
|
isGhAvailable: () => isGhAvailable,
|
|
225
243
|
isGitAvailable: () => isGitAvailable,
|
|
226
244
|
isGitRepo: () => isGitRepo,
|
|
@@ -1283,6 +1301,19 @@ function isBranchMergedInto(cwd, branch, baseBranch) {
|
|
|
1283
1301
|
return false;
|
|
1284
1302
|
}
|
|
1285
1303
|
}
|
|
1304
|
+
function isCommitReachable(cwd, commit, baseBranch) {
|
|
1305
|
+
const recordedCommit = commit.trim();
|
|
1306
|
+
if (!recordedCommit) return false;
|
|
1307
|
+
try {
|
|
1308
|
+
execFileSync("git", ["merge-base", "--is-ancestor", recordedCommit, baseBranch], {
|
|
1309
|
+
cwd,
|
|
1310
|
+
stdio: "ignore"
|
|
1311
|
+
});
|
|
1312
|
+
return true;
|
|
1313
|
+
} catch {
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1286
1317
|
function isBranchContentAlreadyInBase(cwd, branch, baseBranch) {
|
|
1287
1318
|
const resolveCommit = (ref) => {
|
|
1288
1319
|
try {
|
|
@@ -1309,6 +1340,55 @@ function isBranchContentAlreadyInBase(cwd, branch, baseBranch) {
|
|
|
1309
1340
|
return false;
|
|
1310
1341
|
}
|
|
1311
1342
|
}
|
|
1343
|
+
function fetchBaseBranch(cwd, baseBranch, timeoutMs = GIT_FETCH_TIMEOUT_MS) {
|
|
1344
|
+
if (!isGitAvailable() || !isGitRepo(cwd) || !hasRemote(cwd)) {
|
|
1345
|
+
return { fetched: false, compareRef: baseBranch };
|
|
1346
|
+
}
|
|
1347
|
+
const result = spawnSync("git", ["fetch", "--quiet", "origin", baseBranch], {
|
|
1348
|
+
cwd,
|
|
1349
|
+
encoding: "utf-8",
|
|
1350
|
+
timeout: timeoutMs
|
|
1351
|
+
});
|
|
1352
|
+
if (result.error || result.status !== 0) {
|
|
1353
|
+
const raw = result.error?.message ?? ((result.stderr || "").trim() || `git exited ${result.status}`);
|
|
1354
|
+
const isTimeout = result.signal === "SIGTERM" || /ETIMEDOUT/.test(raw);
|
|
1355
|
+
return {
|
|
1356
|
+
fetched: false,
|
|
1357
|
+
compareRef: baseBranch,
|
|
1358
|
+
warning: isTimeout ? `Fetch from origin timed out after ${Math.round(timeoutMs / 1e3)}s \u2014 comparing against the local '${baseBranch}' ref, which may be stale.` : `Fetch from origin failed \u2014 comparing against the local '${baseBranch}' ref, which may be stale. (${raw})`
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
return { fetched: true, compareRef: `origin/${baseBranch}` };
|
|
1362
|
+
}
|
|
1363
|
+
function computeAheadBehind(cwd, compareRef, ref = "HEAD") {
|
|
1364
|
+
try {
|
|
1365
|
+
const out = execFileSync(
|
|
1366
|
+
"git",
|
|
1367
|
+
["rev-list", "--left-right", "--count", `${compareRef}...${ref}`],
|
|
1368
|
+
{ cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
1369
|
+
).trim();
|
|
1370
|
+
const [behindStr, aheadStr] = out.split(/\s+/);
|
|
1371
|
+
const behind = parseInt(behindStr, 10);
|
|
1372
|
+
const ahead = parseInt(aheadStr, 10);
|
|
1373
|
+
if (Number.isNaN(behind) || Number.isNaN(ahead)) return null;
|
|
1374
|
+
return { ahead, behind };
|
|
1375
|
+
} catch {
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
function getBaseDivergence(cwd, preferredBase, opts) {
|
|
1380
|
+
const baseBranch = resolveBaseBranch(cwd, preferredBase);
|
|
1381
|
+
const fetch2 = fetchBaseBranch(cwd, baseBranch, opts?.timeoutMs);
|
|
1382
|
+
const divergence = computeAheadBehind(cwd, fetch2.compareRef, opts?.ref ?? "HEAD");
|
|
1383
|
+
return {
|
|
1384
|
+
baseBranch,
|
|
1385
|
+
compareRef: fetch2.compareRef,
|
|
1386
|
+
fetched: fetch2.fetched,
|
|
1387
|
+
ahead: divergence?.ahead ?? null,
|
|
1388
|
+
behind: divergence?.behind ?? null,
|
|
1389
|
+
warning: fetch2.warning
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1312
1392
|
function pickModuleCycleBranch(candidates, cycleNumber, module, memberSlug) {
|
|
1313
1393
|
if (candidates.length === 0) return void 0;
|
|
1314
1394
|
const expected = cycleBranchName(cycleNumber, module, memberSlug);
|
|
@@ -1413,7 +1493,7 @@ function getRemoteBranchFiles(cwd, branch, baseBranch) {
|
|
|
1413
1493
|
return [];
|
|
1414
1494
|
}
|
|
1415
1495
|
}
|
|
1416
|
-
var AUTO_WRITTEN_PATHS, GIT_NETWORK_TIMEOUT_MS, MERGE_RETRY_DELAY_MS, MERGE_MAX_RETRIES;
|
|
1496
|
+
var AUTO_WRITTEN_PATHS, GIT_NETWORK_TIMEOUT_MS, MERGE_RETRY_DELAY_MS, MERGE_MAX_RETRIES, GIT_FETCH_TIMEOUT_MS;
|
|
1417
1497
|
var init_git = __esm({
|
|
1418
1498
|
"src/lib/git.ts"() {
|
|
1419
1499
|
"use strict";
|
|
@@ -1421,6 +1501,7 @@ var init_git = __esm({
|
|
|
1421
1501
|
GIT_NETWORK_TIMEOUT_MS = 6e4;
|
|
1422
1502
|
MERGE_RETRY_DELAY_MS = 2e3;
|
|
1423
1503
|
MERGE_MAX_RETRIES = 3;
|
|
1504
|
+
GIT_FETCH_TIMEOUT_MS = 5e3;
|
|
1424
1505
|
}
|
|
1425
1506
|
});
|
|
1426
1507
|
|
|
@@ -1471,6 +1552,16 @@ __export(proxy_adapter_exports, {
|
|
|
1471
1552
|
createProxyAdapter: () => createProxyAdapter,
|
|
1472
1553
|
wrapWithForwarding: () => wrapWithForwarding
|
|
1473
1554
|
});
|
|
1555
|
+
function formatRateLimitError(route, response, errorBody, displayMessage = errorBody) {
|
|
1556
|
+
let reason;
|
|
1557
|
+
try {
|
|
1558
|
+
const parsed = JSON.parse(errorBody);
|
|
1559
|
+
if (typeof parsed.reason === "string") reason = parsed.reason;
|
|
1560
|
+
} catch {
|
|
1561
|
+
}
|
|
1562
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
1563
|
+
return `Proxy rate limit on ${route}${reason ? ` (${reason})` : ""}${retryAfter ? `; retry after ${retryAfter}s` : ""}: ${displayMessage}`;
|
|
1564
|
+
}
|
|
1474
1565
|
function snakeToCamel(str) {
|
|
1475
1566
|
return str.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1476
1567
|
}
|
|
@@ -1512,7 +1603,7 @@ function wrapWithForwarding(instance) {
|
|
|
1512
1603
|
return new Proxy(instance, {
|
|
1513
1604
|
get(target, prop, receiver) {
|
|
1514
1605
|
const existing = Reflect.get(target, prop, receiver);
|
|
1515
|
-
if (
|
|
1606
|
+
if (Reflect.has(target, prop)) return existing;
|
|
1516
1607
|
if (typeof prop !== "string" || prop === "then" || prop.startsWith("_") || NO_FORWARD.has(prop)) {
|
|
1517
1608
|
return existing;
|
|
1518
1609
|
}
|
|
@@ -1644,8 +1735,14 @@ var init_proxy_adapter = __esm({
|
|
|
1644
1735
|
"linkPhasesToStage",
|
|
1645
1736
|
"listContributors",
|
|
1646
1737
|
"listConventions",
|
|
1738
|
+
"listDecisionPositions",
|
|
1739
|
+
"upsertDecisionPosition",
|
|
1740
|
+
"proposeDecision",
|
|
1741
|
+
"resolveDecisionState",
|
|
1742
|
+
"getUserEmailsByIds",
|
|
1647
1743
|
"listMyBugReports",
|
|
1648
1744
|
"listOwnerActionsForBlockerScan",
|
|
1745
|
+
"listOwnerActionsForOwner",
|
|
1649
1746
|
"logEntityReferences",
|
|
1650
1747
|
"markAgendaTopicsAddressed",
|
|
1651
1748
|
"markCycleLearningResolved",
|
|
@@ -1709,6 +1806,12 @@ var init_proxy_adapter = __esm({
|
|
|
1709
1806
|
// (1) local-only
|
|
1710
1807
|
"close",
|
|
1711
1808
|
"initRls",
|
|
1809
|
+
// task-3667: the exit-criteria evaluators probe adapter.sql to pick their
|
|
1810
|
+
// transport branch (direct SQL vs adapter reads). Forwarding `sql` to the
|
|
1811
|
+
// edge 403'd INSIDE sqlOf — every SQL-backed evaluator then threw
|
|
1812
|
+
// 'evaluator failed' instead of engaging its hosted branch or degrading to
|
|
1813
|
+
// an honest unevaluated reason. `sql` is local-only by definition.
|
|
1814
|
+
"sql",
|
|
1712
1815
|
// task-3207 (C357): commitBuildComplete, commitReviewSubmit, commitRelease have no
|
|
1713
1816
|
// edge case handler and no ALLOWED_METHODS entry — forwarding them 403s at the edge
|
|
1714
1817
|
// with no try/catch at the call site, crashing build_execute/review_submit/release
|
|
@@ -1795,6 +1898,15 @@ var init_proxy_adapter = __esm({
|
|
|
1795
1898
|
this.projectId = config.projectId ?? "";
|
|
1796
1899
|
this.onAuthRejected = config.onAuthRejected;
|
|
1797
1900
|
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Headers shared by every authenticated proxy route.
|
|
1903
|
+
*/
|
|
1904
|
+
authenticatedHeaders() {
|
|
1905
|
+
return {
|
|
1906
|
+
"Content-Type": "application/json",
|
|
1907
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1798
1910
|
/**
|
|
1799
1911
|
* Does the hosted path genuinely serve `name`? (task-3022, C362)
|
|
1800
1912
|
*
|
|
@@ -1833,10 +1945,7 @@ var init_proxy_adapter = __esm({
|
|
|
1833
1945
|
try {
|
|
1834
1946
|
const response = await fetch(`${this.endpoint}/project-list`, {
|
|
1835
1947
|
method: "POST",
|
|
1836
|
-
headers:
|
|
1837
|
-
"Content-Type": "application/json",
|
|
1838
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1839
|
-
},
|
|
1948
|
+
headers: this.authenticatedHeaders(),
|
|
1840
1949
|
body: "{}",
|
|
1841
1950
|
signal: AbortSignal.timeout(5e3)
|
|
1842
1951
|
});
|
|
@@ -1875,10 +1984,7 @@ var init_proxy_adapter = __esm({
|
|
|
1875
1984
|
const url = `${this.endpoint}/ensure-project`;
|
|
1876
1985
|
const response = await fetch(url, {
|
|
1877
1986
|
method: "POST",
|
|
1878
|
-
headers:
|
|
1879
|
-
"Content-Type": "application/json",
|
|
1880
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1881
|
-
},
|
|
1987
|
+
headers: this.authenticatedHeaders(),
|
|
1882
1988
|
body: JSON.stringify({
|
|
1883
1989
|
...projectName ? { projectName } : {},
|
|
1884
1990
|
...repoUrl ? { repoUrl } : {},
|
|
@@ -1898,6 +2004,9 @@ var init_proxy_adapter = __esm({
|
|
|
1898
2004
|
} catch {
|
|
1899
2005
|
message = errorBody;
|
|
1900
2006
|
}
|
|
2007
|
+
if (response.status === 429) {
|
|
2008
|
+
throw new Error(formatRateLimitError("ensureProject", response, errorBody, message));
|
|
2009
|
+
}
|
|
1901
2010
|
throw new Error(`Auto-provision failed (${response.status}): ${message}`);
|
|
1902
2011
|
}
|
|
1903
2012
|
const body = await response.json();
|
|
@@ -1918,10 +2027,7 @@ var init_proxy_adapter = __esm({
|
|
|
1918
2027
|
try {
|
|
1919
2028
|
response = await fetch(url, {
|
|
1920
2029
|
method: "POST",
|
|
1921
|
-
headers:
|
|
1922
|
-
"Content-Type": "application/json",
|
|
1923
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1924
|
-
},
|
|
2030
|
+
headers: this.authenticatedHeaders(),
|
|
1925
2031
|
body: JSON.stringify({
|
|
1926
2032
|
projectId: this.projectId,
|
|
1927
2033
|
method,
|
|
@@ -1964,6 +2070,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1964
2070
|
(${response.status} on ${method}: ${message})`
|
|
1965
2071
|
);
|
|
1966
2072
|
}
|
|
2073
|
+
if (response.status === 429) {
|
|
2074
|
+
throw new Error(formatRateLimitError(method, response, errorBody, message));
|
|
2075
|
+
}
|
|
1967
2076
|
throw new Error(`Proxy error (${response.status}) on ${method}: ${message}`);
|
|
1968
2077
|
}
|
|
1969
2078
|
const body = await response.json();
|
|
@@ -2025,10 +2134,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2025
2134
|
try {
|
|
2026
2135
|
const response = await fetch(`${this.endpoint}/invoke`, {
|
|
2027
2136
|
method: "POST",
|
|
2028
|
-
headers:
|
|
2029
|
-
"Content-Type": "application/json",
|
|
2030
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
2031
|
-
},
|
|
2137
|
+
headers: this.authenticatedHeaders(),
|
|
2032
2138
|
body: JSON.stringify({ projectId, method: "projectExists", args: [] }),
|
|
2033
2139
|
signal: AbortSignal.timeout(5e3)
|
|
2034
2140
|
});
|
|
@@ -2059,8 +2165,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2059
2165
|
writeCycleLogEntry(entry) {
|
|
2060
2166
|
return this.invoke("writeCycleLogEntry", [entry]);
|
|
2061
2167
|
}
|
|
2062
|
-
updateActiveDecision(id, body, cycleNumber) {
|
|
2063
|
-
return this.invoke("updateActiveDecision", [id, body, cycleNumber]);
|
|
2168
|
+
updateActiveDecision(id, body, cycleNumber, action) {
|
|
2169
|
+
return this.invoke("updateActiveDecision", [id, body, cycleNumber, action]);
|
|
2064
2170
|
}
|
|
2065
2171
|
upsertActiveDecision(id, body, title, confidence, cycleNumber) {
|
|
2066
2172
|
return this.invoke("upsertActiveDecision", [id, body, title, confidence, cycleNumber]);
|
|
@@ -2187,7 +2293,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2187
2293
|
* task-2288 (C308): emit a telemetry event via the proxy's /telemetry endpoint
|
|
2188
2294
|
* using THIS adapter's per-request bearer (this.apiKey) — the same credential
|
|
2189
2295
|
* invoke() uses. This is the fix for the hosted-transport blackout: on the
|
|
2190
|
-
* multi-tenant
|
|
2296
|
+
* multi-tenant server has no PAPI_DATA_API_KEY env var, so the
|
|
2191
2297
|
* env-var emitTelemetryEvent path (lib/telemetry.ts) dropped every event since
|
|
2192
2298
|
* 2026-06-16. Fire-and-forget — never throws, never blocks the tool response.
|
|
2193
2299
|
* Not routed through invoke() because /telemetry is a distinct endpoint and a
|
|
@@ -2203,10 +2309,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2203
2309
|
if (event.projectId) payload["projectId"] = event.projectId;
|
|
2204
2310
|
fetch(`${this.endpoint}/telemetry`, {
|
|
2205
2311
|
method: "POST",
|
|
2206
|
-
headers:
|
|
2207
|
-
"Content-Type": "application/json",
|
|
2208
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
2209
|
-
},
|
|
2312
|
+
headers: this.authenticatedHeaders(),
|
|
2210
2313
|
body: JSON.stringify(payload),
|
|
2211
2314
|
signal: AbortSignal.timeout(5e3)
|
|
2212
2315
|
}).then((res) => {
|
|
@@ -2293,6 +2396,19 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2293
2396
|
getLatestDecisionScores() {
|
|
2294
2397
|
return this.invoke("getLatestDecisionScores");
|
|
2295
2398
|
}
|
|
2399
|
+
// --- Decision lifecycle — propose / resolve / positions (task-3408, C366) ---
|
|
2400
|
+
proposeDecision(id, decisionType, proposedByUserId, cycleNumber) {
|
|
2401
|
+
return this.invoke("proposeDecision", [id, decisionType, proposedByUserId, cycleNumber]);
|
|
2402
|
+
}
|
|
2403
|
+
resolveDecisionState(id, state, actorUserId, cycleNumber) {
|
|
2404
|
+
return this.invoke("resolveDecisionState", [id, state, actorUserId, cycleNumber]);
|
|
2405
|
+
}
|
|
2406
|
+
listDecisionPositions(decisionId) {
|
|
2407
|
+
return this.invoke("listDecisionPositions", [decisionId]);
|
|
2408
|
+
}
|
|
2409
|
+
upsertDecisionPosition(decisionId, userId, status, comments) {
|
|
2410
|
+
return this.invoke("upsertDecisionPosition", [decisionId, userId, status, comments ?? null]);
|
|
2411
|
+
}
|
|
2296
2412
|
logEntityReferences(refs) {
|
|
2297
2413
|
return this.invoke("logEntityReferences", [refs]);
|
|
2298
2414
|
}
|
|
@@ -2488,7 +2604,18 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2488
2604
|
}
|
|
2489
2605
|
// --- Project metadata (path-identity guardrail) ---
|
|
2490
2606
|
async getProjectInfo() {
|
|
2491
|
-
|
|
2607
|
+
const raw = await this.invoke("getProjectInfo", []);
|
|
2608
|
+
if (!raw) return null;
|
|
2609
|
+
return {
|
|
2610
|
+
name: raw.name,
|
|
2611
|
+
slug: raw.slug,
|
|
2612
|
+
papi_dir: raw.papi_dir ?? raw.papiDir ?? null,
|
|
2613
|
+
repo_url: raw.repo_url ?? raw.repoUrl ?? null,
|
|
2614
|
+
capabilities: raw.capabilities ?? {},
|
|
2615
|
+
discord_channel_id: raw.discord_channel_id ?? raw.discordChannelId ?? null,
|
|
2616
|
+
decision_resolution_mode: raw.decision_resolution_mode ?? raw.decisionResolutionMode ?? "owner",
|
|
2617
|
+
decision_type_resolvers: raw.decision_type_resolvers ?? raw.decisionTypeResolvers ?? {}
|
|
2618
|
+
};
|
|
2492
2619
|
}
|
|
2493
2620
|
/**
|
|
2494
2621
|
* task-2052 (C288): owner-gate identity for the proxy transport. Both sides
|
|
@@ -2528,10 +2655,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2528
2655
|
async postRoute(route, payload) {
|
|
2529
2656
|
const response = await fetch(`${this.endpoint}/${route}`, {
|
|
2530
2657
|
method: "POST",
|
|
2531
|
-
headers:
|
|
2532
|
-
"Content-Type": "application/json",
|
|
2533
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
2534
|
-
},
|
|
2658
|
+
headers: this.authenticatedHeaders(),
|
|
2535
2659
|
body: JSON.stringify(payload),
|
|
2536
2660
|
signal: AbortSignal.timeout(15e3)
|
|
2537
2661
|
});
|
|
@@ -2543,6 +2667,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2543
2667
|
} catch {
|
|
2544
2668
|
message = errorBody;
|
|
2545
2669
|
}
|
|
2670
|
+
if (response.status === 429) {
|
|
2671
|
+
throw new Error(formatRateLimitError(route, response, errorBody, message));
|
|
2672
|
+
}
|
|
2546
2673
|
if (response.status === 401) this.onAuthRejected?.();
|
|
2547
2674
|
throw new Error(`Proxy error (${response.status}) on ${route}: ${message}`);
|
|
2548
2675
|
}
|
|
@@ -2692,7 +2819,6 @@ function serverDeploymentProfile() {
|
|
|
2692
2819
|
dataEndpoint: process.env["PAPI_DATA_ENDPOINT"],
|
|
2693
2820
|
hostedSupabaseUrl: process.env["PAPI_HOSTED_SUPABASE_URL"],
|
|
2694
2821
|
vercelEnabled: process.env["PAPI_ENABLE_VERCEL"],
|
|
2695
|
-
railwayEnabled: process.env["PAPI_ENABLE_RAILWAY"],
|
|
2696
2822
|
managedSupabaseEnabled: process.env["PAPI_ENABLE_MANAGED_SUPABASE"]
|
|
2697
2823
|
});
|
|
2698
2824
|
}
|
|
@@ -2863,16 +2989,21 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2863
2989
|
});
|
|
2864
2990
|
}
|
|
2865
2991
|
} else {
|
|
2866
|
-
if (!existing.root_commit_hash && rootHash) {
|
|
2992
|
+
if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
|
|
2867
2993
|
try {
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
resolution_method
|
|
2872
|
-
}
|
|
2994
|
+
const update = {};
|
|
2995
|
+
if (!existing.root_commit_hash && rootHash) {
|
|
2996
|
+
update.root_commit_hash = rootHash;
|
|
2997
|
+
update.resolution_method = "root_hash";
|
|
2998
|
+
}
|
|
2999
|
+
if (!existing.repo_url && originUrl) {
|
|
3000
|
+
update.repo_url = originUrl;
|
|
3001
|
+
if (!update.resolution_method && !existing.resolution_method) update.resolution_method = "repo_url";
|
|
3002
|
+
}
|
|
3003
|
+
await pgAdapter.updateProject(projectId, update);
|
|
2873
3004
|
} catch (err) {
|
|
2874
3005
|
console.error(
|
|
2875
|
-
`[papi] \u26A0 Failed to backfill
|
|
3006
|
+
`[papi] \u26A0 Failed to backfill project resolution signals: ${err instanceof Error ? err.message : String(err)}`
|
|
2876
3007
|
);
|
|
2877
3008
|
}
|
|
2878
3009
|
}
|
|
@@ -2913,7 +3044,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2913
3044
|
} catch {
|
|
2914
3045
|
}
|
|
2915
3046
|
}
|
|
2916
|
-
const adapter = new PgPapiAdapter(config, projectId);
|
|
3047
|
+
const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId }, projectId);
|
|
2917
3048
|
try {
|
|
2918
3049
|
await adapter.initRls();
|
|
2919
3050
|
} catch {
|