@papi-ai/server 0.7.84 → 0.7.98
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 +184 -49
- package/dist/index.js +3331 -1025
- package/dist/prompts.js +77 -17
- package/dist/statusline.js +168 -0
- package/package.json +7 -8
|
@@ -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" },
|
|
@@ -135,12 +149,26 @@ var init_dist = __esm({
|
|
|
135
149
|
{ key: "autoBranch", label: "Auto branch", description: "Creates a feature branch per task/cycle at build start.", step: "build" },
|
|
136
150
|
{ key: "autoCommit", label: "Auto commit", description: "Commits your work automatically when a build completes.", step: "build" },
|
|
137
151
|
{ key: "autoPush", label: "Auto push & PR", description: "Pushes the branch and opens a pull request on build complete.", step: "build" },
|
|
138
|
-
{ key: "papiMetaFraming", label: "PAPI self-referential framing", description: "Includes PAPI-meta build-discipline framing in handoff output.", step: "build" }
|
|
152
|
+
{ key: "papiMetaFraming", label: "PAPI self-referential framing", description: "Includes PAPI-meta build-discipline framing in handoff output.", step: "build" },
|
|
153
|
+
// Editor-equal release. Two legitimate team shapes need opposite answers, so
|
|
154
|
+
// this is a PROJECT policy rather than a global rule:
|
|
155
|
+
//
|
|
156
|
+
// OFF (default) — an editor's release opens a CONTRIBUTOR PR and PAPI records
|
|
157
|
+
// the release when GitHub merges it. Right for an open//multi-contributor
|
|
158
|
+
// product where the project owner is the last gate before production.
|
|
159
|
+
// ON — an editor releases on exactly the owner's path. Right for a small team
|
|
160
|
+
// of trusted peers who each run whole cycles end to end, where routing every
|
|
161
|
+
// close back through one person is the bottleneck, not the safeguard.
|
|
162
|
+
//
|
|
163
|
+
// Defaults OFF so existing projects are byte-identical, and because widening who
|
|
164
|
+
// can ship to production must be a deliberate choice, never a silent upgrade.
|
|
165
|
+
// A VIEWER is denied either way — this moves the editor line only.
|
|
166
|
+
{ key: "editorRelease", label: "Editors release like the owner", description: "Lets editors run a full release instead of opening a contributor PR.", step: "release", defaultEnabled: false }
|
|
139
167
|
];
|
|
140
168
|
CAPABILITY_KEYS = CAPABILITY_REGISTRY.map((c) => c.key);
|
|
141
169
|
HOSTED_APP_URL = "https://getpapi.ai";
|
|
142
170
|
HOSTED_MCP_URL = "https://mcp.getpapi.ai";
|
|
143
|
-
HOSTED_SUPABASE_URL = "https://
|
|
171
|
+
HOSTED_SUPABASE_URL = "https://api.getpapi.ai";
|
|
144
172
|
WAB_WINDOW_DAYS = 7;
|
|
145
173
|
WAB_WEEK_MS = WAB_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
|
|
146
174
|
EFFORT_SCALE = {
|
|
@@ -171,6 +199,7 @@ __export(git_exports, {
|
|
|
171
199
|
detectUnrecordedCommits: () => detectUnrecordedCommits,
|
|
172
200
|
ensureLatestDevelop: () => ensureLatestDevelop,
|
|
173
201
|
ensureTagAtHead: () => ensureTagAtHead,
|
|
202
|
+
findContributorReleasePullRequests: () => findContributorReleasePullRequests,
|
|
174
203
|
findTaskCommitsOnBase: () => findTaskCommitsOnBase,
|
|
175
204
|
getBranchDiff: () => getBranchDiff,
|
|
176
205
|
getCommitFiles: () => getCommitFiles,
|
|
@@ -188,6 +217,7 @@ __export(git_exports, {
|
|
|
188
217
|
getOriginRepoSlug: () => getOriginRepoSlug,
|
|
189
218
|
getOriginUrl: () => getOriginUrl,
|
|
190
219
|
getPathsDifferingFrom: () => getPathsDifferingFrom,
|
|
220
|
+
getPullRequestState: () => getPullRequestState,
|
|
191
221
|
getPullRequestUrl: () => getPullRequestUrl,
|
|
192
222
|
getRemoteBranchFiles: () => getRemoteBranchFiles,
|
|
193
223
|
getRootCommitHash: () => getRootCommitHash,
|
|
@@ -1151,6 +1181,56 @@ function getPullRequestUrl(cwd, branch) {
|
|
|
1151
1181
|
return null;
|
|
1152
1182
|
}
|
|
1153
1183
|
}
|
|
1184
|
+
function getPullRequestState(cwd, prUrl) {
|
|
1185
|
+
try {
|
|
1186
|
+
const output = execFileSync(
|
|
1187
|
+
"gh",
|
|
1188
|
+
["pr", "view", prUrl, "--json", "url,state,mergedAt"],
|
|
1189
|
+
{ cwd, encoding: "utf-8" }
|
|
1190
|
+
).trim();
|
|
1191
|
+
if (!output) return null;
|
|
1192
|
+
const parsed = JSON.parse(output);
|
|
1193
|
+
if (!parsed.url || !["OPEN", "CLOSED", "MERGED"].includes(parsed.state ?? "")) return null;
|
|
1194
|
+
return {
|
|
1195
|
+
url: parsed.url,
|
|
1196
|
+
state: parsed.state,
|
|
1197
|
+
mergedAt: parsed.mergedAt ?? null
|
|
1198
|
+
};
|
|
1199
|
+
} catch {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function findContributorReleasePullRequests(cwd, cycle) {
|
|
1204
|
+
if (!Number.isInteger(cycle) || cycle <= 0) return [];
|
|
1205
|
+
try {
|
|
1206
|
+
const output = execFileSync(
|
|
1207
|
+
"gh",
|
|
1208
|
+
[
|
|
1209
|
+
"pr",
|
|
1210
|
+
"list",
|
|
1211
|
+
"--state",
|
|
1212
|
+
"all",
|
|
1213
|
+
"--limit",
|
|
1214
|
+
"20",
|
|
1215
|
+
"--search",
|
|
1216
|
+
`"Contributor release PR for cycle ${cycle}" in:body`,
|
|
1217
|
+
"--json",
|
|
1218
|
+
"url,state,mergedAt,headRefName"
|
|
1219
|
+
],
|
|
1220
|
+
{ cwd, encoding: "utf-8" }
|
|
1221
|
+
).trim();
|
|
1222
|
+
if (!output) return [];
|
|
1223
|
+
const parsed = JSON.parse(output);
|
|
1224
|
+
return parsed.flatMap((pr) => pr.url && pr.headRefName && ["OPEN", "CLOSED", "MERGED"].includes(pr.state ?? "") ? [{
|
|
1225
|
+
url: pr.url,
|
|
1226
|
+
state: pr.state,
|
|
1227
|
+
mergedAt: pr.mergedAt ?? null,
|
|
1228
|
+
branch: pr.headRefName
|
|
1229
|
+
}] : []);
|
|
1230
|
+
} catch {
|
|
1231
|
+
return [];
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1154
1234
|
function squashMergePullRequest(cwd, branch) {
|
|
1155
1235
|
const repo = getOriginRepoSlug(cwd);
|
|
1156
1236
|
const baseArgs = ["pr", "merge", branch, "--squash", "--delete-branch"];
|
|
@@ -1405,6 +1485,16 @@ __export(proxy_adapter_exports, {
|
|
|
1405
1485
|
createProxyAdapter: () => createProxyAdapter,
|
|
1406
1486
|
wrapWithForwarding: () => wrapWithForwarding
|
|
1407
1487
|
});
|
|
1488
|
+
function formatRateLimitError(route, response, errorBody, displayMessage = errorBody) {
|
|
1489
|
+
let reason;
|
|
1490
|
+
try {
|
|
1491
|
+
const parsed = JSON.parse(errorBody);
|
|
1492
|
+
if (typeof parsed.reason === "string") reason = parsed.reason;
|
|
1493
|
+
} catch {
|
|
1494
|
+
}
|
|
1495
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
1496
|
+
return `Proxy rate limit on ${route}${reason ? ` (${reason})` : ""}${retryAfter ? `; retry after ${retryAfter}s` : ""}: ${displayMessage}`;
|
|
1497
|
+
}
|
|
1408
1498
|
function snakeToCamel(str) {
|
|
1409
1499
|
return str.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1410
1500
|
}
|
|
@@ -1446,7 +1536,7 @@ function wrapWithForwarding(instance) {
|
|
|
1446
1536
|
return new Proxy(instance, {
|
|
1447
1537
|
get(target, prop, receiver) {
|
|
1448
1538
|
const existing = Reflect.get(target, prop, receiver);
|
|
1449
|
-
if (
|
|
1539
|
+
if (Reflect.has(target, prop)) return existing;
|
|
1450
1540
|
if (typeof prop !== "string" || prop === "then" || prop.startsWith("_") || NO_FORWARD.has(prop)) {
|
|
1451
1541
|
return existing;
|
|
1452
1542
|
}
|
|
@@ -1518,6 +1608,10 @@ var init_proxy_adapter = __esm({
|
|
|
1518
1608
|
"dismissRecommendation",
|
|
1519
1609
|
"findPendingDocActionsForTask",
|
|
1520
1610
|
"getActiveDecisions",
|
|
1611
|
+
"getContributorRole",
|
|
1612
|
+
"listContributorReleasePrs",
|
|
1613
|
+
"recordContributorReleasePr",
|
|
1614
|
+
"setContributorReleasePrStatus",
|
|
1521
1615
|
"getActiveStage",
|
|
1522
1616
|
"getBuildReportCountForTask",
|
|
1523
1617
|
"getBuildReportsSince",
|
|
@@ -1574,8 +1668,14 @@ var init_proxy_adapter = __esm({
|
|
|
1574
1668
|
"linkPhasesToStage",
|
|
1575
1669
|
"listContributors",
|
|
1576
1670
|
"listConventions",
|
|
1671
|
+
"listDecisionPositions",
|
|
1672
|
+
"upsertDecisionPosition",
|
|
1673
|
+
"proposeDecision",
|
|
1674
|
+
"resolveDecisionState",
|
|
1675
|
+
"getUserEmailsByIds",
|
|
1577
1676
|
"listMyBugReports",
|
|
1578
1677
|
"listOwnerActionsForBlockerScan",
|
|
1678
|
+
"listOwnerActionsForOwner",
|
|
1579
1679
|
"logEntityReferences",
|
|
1580
1680
|
"markAgendaTopicsAddressed",
|
|
1581
1681
|
"markCycleLearningResolved",
|
|
@@ -1639,6 +1739,12 @@ var init_proxy_adapter = __esm({
|
|
|
1639
1739
|
// (1) local-only
|
|
1640
1740
|
"close",
|
|
1641
1741
|
"initRls",
|
|
1742
|
+
// task-3667: the exit-criteria evaluators probe adapter.sql to pick their
|
|
1743
|
+
// transport branch (direct SQL vs adapter reads). Forwarding `sql` to the
|
|
1744
|
+
// edge 403'd INSIDE sqlOf — every SQL-backed evaluator then threw
|
|
1745
|
+
// 'evaluator failed' instead of engaging its hosted branch or degrading to
|
|
1746
|
+
// an honest unevaluated reason. `sql` is local-only by definition.
|
|
1747
|
+
"sql",
|
|
1642
1748
|
// task-3207 (C357): commitBuildComplete, commitReviewSubmit, commitRelease have no
|
|
1643
1749
|
// edge case handler and no ALLOWED_METHODS entry — forwarding them 403s at the edge
|
|
1644
1750
|
// with no try/catch at the call site, crashing build_execute/review_submit/release
|
|
@@ -1657,7 +1763,7 @@ var init_proxy_adapter = __esm({
|
|
|
1657
1763
|
"applyActiveDecisionUpdates",
|
|
1658
1764
|
// getToolCallCount + updatePhaseStatus are ABSENT: they now have edge handlers and
|
|
1659
1765
|
// are served through the forwarder (getToolCallCount = SUP-2026-026 handler #1).
|
|
1660
|
-
|
|
1766
|
+
// getContributorRole is edge-wired and binds identity from the bearer.
|
|
1661
1767
|
// task-3018 (C356): storeDocBody + getDocBodyUsage are now WIRED — edge case
|
|
1662
1768
|
// handlers + ALLOWED_METHODS/WRITE_METHODS entries exist, so they forward and
|
|
1663
1769
|
// hosted callers get body storage. Removed from this list, as task-3017 required.
|
|
@@ -1665,9 +1771,6 @@ var init_proxy_adapter = __esm({
|
|
|
1665
1771
|
// of the owner-action queue. Six readers were wired C329 (task-2412) but the
|
|
1666
1772
|
// producer stayed here, so the hosted Owner Action Queue was structurally empty
|
|
1667
1773
|
// (AD-74 inverted). Now forwards; the edge binds user_id to the bearer ([C]).
|
|
1668
|
-
"recordContributorReleasePr",
|
|
1669
|
-
"setContributorReleasePrStatus",
|
|
1670
|
-
"listContributorReleasePrs",
|
|
1671
1774
|
"claimReview",
|
|
1672
1775
|
"getSiblingAds",
|
|
1673
1776
|
"getSiblingRepoTasks",
|
|
@@ -1728,6 +1831,15 @@ var init_proxy_adapter = __esm({
|
|
|
1728
1831
|
this.projectId = config.projectId ?? "";
|
|
1729
1832
|
this.onAuthRejected = config.onAuthRejected;
|
|
1730
1833
|
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Headers shared by every authenticated proxy route.
|
|
1836
|
+
*/
|
|
1837
|
+
authenticatedHeaders() {
|
|
1838
|
+
return {
|
|
1839
|
+
"Content-Type": "application/json",
|
|
1840
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1731
1843
|
/**
|
|
1732
1844
|
* Does the hosted path genuinely serve `name`? (task-3022, C362)
|
|
1733
1845
|
*
|
|
@@ -1766,10 +1878,7 @@ var init_proxy_adapter = __esm({
|
|
|
1766
1878
|
try {
|
|
1767
1879
|
const response = await fetch(`${this.endpoint}/project-list`, {
|
|
1768
1880
|
method: "POST",
|
|
1769
|
-
headers:
|
|
1770
|
-
"Content-Type": "application/json",
|
|
1771
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1772
|
-
},
|
|
1881
|
+
headers: this.authenticatedHeaders(),
|
|
1773
1882
|
body: "{}",
|
|
1774
1883
|
signal: AbortSignal.timeout(5e3)
|
|
1775
1884
|
});
|
|
@@ -1808,10 +1917,7 @@ var init_proxy_adapter = __esm({
|
|
|
1808
1917
|
const url = `${this.endpoint}/ensure-project`;
|
|
1809
1918
|
const response = await fetch(url, {
|
|
1810
1919
|
method: "POST",
|
|
1811
|
-
headers:
|
|
1812
|
-
"Content-Type": "application/json",
|
|
1813
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1814
|
-
},
|
|
1920
|
+
headers: this.authenticatedHeaders(),
|
|
1815
1921
|
body: JSON.stringify({
|
|
1816
1922
|
...projectName ? { projectName } : {},
|
|
1817
1923
|
...repoUrl ? { repoUrl } : {},
|
|
@@ -1831,6 +1937,9 @@ var init_proxy_adapter = __esm({
|
|
|
1831
1937
|
} catch {
|
|
1832
1938
|
message = errorBody;
|
|
1833
1939
|
}
|
|
1940
|
+
if (response.status === 429) {
|
|
1941
|
+
throw new Error(formatRateLimitError("ensureProject", response, errorBody, message));
|
|
1942
|
+
}
|
|
1834
1943
|
throw new Error(`Auto-provision failed (${response.status}): ${message}`);
|
|
1835
1944
|
}
|
|
1836
1945
|
const body = await response.json();
|
|
@@ -1851,10 +1960,7 @@ var init_proxy_adapter = __esm({
|
|
|
1851
1960
|
try {
|
|
1852
1961
|
response = await fetch(url, {
|
|
1853
1962
|
method: "POST",
|
|
1854
|
-
headers:
|
|
1855
|
-
"Content-Type": "application/json",
|
|
1856
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1857
|
-
},
|
|
1963
|
+
headers: this.authenticatedHeaders(),
|
|
1858
1964
|
body: JSON.stringify({
|
|
1859
1965
|
projectId: this.projectId,
|
|
1860
1966
|
method,
|
|
@@ -1897,6 +2003,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1897
2003
|
(${response.status} on ${method}: ${message})`
|
|
1898
2004
|
);
|
|
1899
2005
|
}
|
|
2006
|
+
if (response.status === 429) {
|
|
2007
|
+
throw new Error(formatRateLimitError(method, response, errorBody, message));
|
|
2008
|
+
}
|
|
1900
2009
|
throw new Error(`Proxy error (${response.status}) on ${method}: ${message}`);
|
|
1901
2010
|
}
|
|
1902
2011
|
const body = await response.json();
|
|
@@ -1958,10 +2067,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1958
2067
|
try {
|
|
1959
2068
|
const response = await fetch(`${this.endpoint}/invoke`, {
|
|
1960
2069
|
method: "POST",
|
|
1961
|
-
headers:
|
|
1962
|
-
"Content-Type": "application/json",
|
|
1963
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
1964
|
-
},
|
|
2070
|
+
headers: this.authenticatedHeaders(),
|
|
1965
2071
|
body: JSON.stringify({ projectId, method: "projectExists", args: [] }),
|
|
1966
2072
|
signal: AbortSignal.timeout(5e3)
|
|
1967
2073
|
});
|
|
@@ -1992,8 +2098,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1992
2098
|
writeCycleLogEntry(entry) {
|
|
1993
2099
|
return this.invoke("writeCycleLogEntry", [entry]);
|
|
1994
2100
|
}
|
|
1995
|
-
updateActiveDecision(id, body, cycleNumber) {
|
|
1996
|
-
return this.invoke("updateActiveDecision", [id, body, cycleNumber]);
|
|
2101
|
+
updateActiveDecision(id, body, cycleNumber, action) {
|
|
2102
|
+
return this.invoke("updateActiveDecision", [id, body, cycleNumber, action]);
|
|
1997
2103
|
}
|
|
1998
2104
|
upsertActiveDecision(id, body, title, confidence, cycleNumber) {
|
|
1999
2105
|
return this.invoke("upsertActiveDecision", [id, body, title, confidence, cycleNumber]);
|
|
@@ -2120,7 +2226,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2120
2226
|
* task-2288 (C308): emit a telemetry event via the proxy's /telemetry endpoint
|
|
2121
2227
|
* using THIS adapter's per-request bearer (this.apiKey) — the same credential
|
|
2122
2228
|
* invoke() uses. This is the fix for the hosted-transport blackout: on the
|
|
2123
|
-
* multi-tenant
|
|
2229
|
+
* multi-tenant server has no PAPI_DATA_API_KEY env var, so the
|
|
2124
2230
|
* env-var emitTelemetryEvent path (lib/telemetry.ts) dropped every event since
|
|
2125
2231
|
* 2026-06-16. Fire-and-forget — never throws, never blocks the tool response.
|
|
2126
2232
|
* Not routed through invoke() because /telemetry is a distinct endpoint and a
|
|
@@ -2136,10 +2242,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2136
2242
|
if (event.projectId) payload["projectId"] = event.projectId;
|
|
2137
2243
|
fetch(`${this.endpoint}/telemetry`, {
|
|
2138
2244
|
method: "POST",
|
|
2139
|
-
headers:
|
|
2140
|
-
"Content-Type": "application/json",
|
|
2141
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
2142
|
-
},
|
|
2245
|
+
headers: this.authenticatedHeaders(),
|
|
2143
2246
|
body: JSON.stringify(payload),
|
|
2144
2247
|
signal: AbortSignal.timeout(5e3)
|
|
2145
2248
|
}).then((res) => {
|
|
@@ -2226,6 +2329,19 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2226
2329
|
getLatestDecisionScores() {
|
|
2227
2330
|
return this.invoke("getLatestDecisionScores");
|
|
2228
2331
|
}
|
|
2332
|
+
// --- Decision lifecycle — propose / resolve / positions (task-3408, C366) ---
|
|
2333
|
+
proposeDecision(id, decisionType, proposedByUserId, cycleNumber) {
|
|
2334
|
+
return this.invoke("proposeDecision", [id, decisionType, proposedByUserId, cycleNumber]);
|
|
2335
|
+
}
|
|
2336
|
+
resolveDecisionState(id, state, actorUserId, cycleNumber) {
|
|
2337
|
+
return this.invoke("resolveDecisionState", [id, state, actorUserId, cycleNumber]);
|
|
2338
|
+
}
|
|
2339
|
+
listDecisionPositions(decisionId) {
|
|
2340
|
+
return this.invoke("listDecisionPositions", [decisionId]);
|
|
2341
|
+
}
|
|
2342
|
+
upsertDecisionPosition(decisionId, userId, status, comments) {
|
|
2343
|
+
return this.invoke("upsertDecisionPosition", [decisionId, userId, status, comments ?? null]);
|
|
2344
|
+
}
|
|
2229
2345
|
logEntityReferences(refs) {
|
|
2230
2346
|
return this.invoke("logEntityReferences", [refs]);
|
|
2231
2347
|
}
|
|
@@ -2383,8 +2499,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2383
2499
|
getCycleLearningPatterns() {
|
|
2384
2500
|
return this.invoke("getCycleLearningPatterns", []);
|
|
2385
2501
|
}
|
|
2386
|
-
updateCycleLearningActionRef(learningId, taskDisplayId) {
|
|
2387
|
-
return this.invoke("updateCycleLearningActionRef", [learningId, taskDisplayId]);
|
|
2502
|
+
updateCycleLearningActionRef(learningId, taskDisplayId, opts) {
|
|
2503
|
+
return this.invoke("updateCycleLearningActionRef", [learningId, taskDisplayId, opts ?? {}]);
|
|
2388
2504
|
}
|
|
2389
2505
|
// --- Strategy Review Drafts ---
|
|
2390
2506
|
savePendingReviewResponse(cycleNumber, rawResponse) {
|
|
@@ -2421,7 +2537,18 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2421
2537
|
}
|
|
2422
2538
|
// --- Project metadata (path-identity guardrail) ---
|
|
2423
2539
|
async getProjectInfo() {
|
|
2424
|
-
|
|
2540
|
+
const raw = await this.invoke("getProjectInfo", []);
|
|
2541
|
+
if (!raw) return null;
|
|
2542
|
+
return {
|
|
2543
|
+
name: raw.name,
|
|
2544
|
+
slug: raw.slug,
|
|
2545
|
+
papi_dir: raw.papi_dir ?? raw.papiDir ?? null,
|
|
2546
|
+
repo_url: raw.repo_url ?? raw.repoUrl ?? null,
|
|
2547
|
+
capabilities: raw.capabilities ?? {},
|
|
2548
|
+
discord_channel_id: raw.discord_channel_id ?? raw.discordChannelId ?? null,
|
|
2549
|
+
decision_resolution_mode: raw.decision_resolution_mode ?? raw.decisionResolutionMode ?? "owner",
|
|
2550
|
+
decision_type_resolvers: raw.decision_type_resolvers ?? raw.decisionTypeResolvers ?? {}
|
|
2551
|
+
};
|
|
2425
2552
|
}
|
|
2426
2553
|
/**
|
|
2427
2554
|
* task-2052 (C288): owner-gate identity for the proxy transport. Both sides
|
|
@@ -2461,10 +2588,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2461
2588
|
async postRoute(route, payload) {
|
|
2462
2589
|
const response = await fetch(`${this.endpoint}/${route}`, {
|
|
2463
2590
|
method: "POST",
|
|
2464
|
-
headers:
|
|
2465
|
-
"Content-Type": "application/json",
|
|
2466
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
2467
|
-
},
|
|
2591
|
+
headers: this.authenticatedHeaders(),
|
|
2468
2592
|
body: JSON.stringify(payload),
|
|
2469
2593
|
signal: AbortSignal.timeout(15e3)
|
|
2470
2594
|
});
|
|
@@ -2476,6 +2600,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2476
2600
|
} catch {
|
|
2477
2601
|
message = errorBody;
|
|
2478
2602
|
}
|
|
2603
|
+
if (response.status === 429) {
|
|
2604
|
+
throw new Error(formatRateLimitError(route, response, errorBody, message));
|
|
2605
|
+
}
|
|
2479
2606
|
if (response.status === 401) this.onAuthRejected?.();
|
|
2480
2607
|
throw new Error(`Proxy error (${response.status}) on ${route}: ${message}`);
|
|
2481
2608
|
}
|
|
@@ -2490,7 +2617,11 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2490
2617
|
*/
|
|
2491
2618
|
async getMeteredUsage() {
|
|
2492
2619
|
const body = await this.postRoute("metering", {});
|
|
2493
|
-
return {
|
|
2620
|
+
return {
|
|
2621
|
+
tier: body.tier ?? "free",
|
|
2622
|
+
monthlyToolCalls: body.monthlyToolCalls ?? 0,
|
|
2623
|
+
projectLimitOverride: body.projectLimitOverride ?? null
|
|
2624
|
+
};
|
|
2494
2625
|
}
|
|
2495
2626
|
async listUserProjects() {
|
|
2496
2627
|
const body = await this.postRoute("project-list", {});
|
|
@@ -2621,7 +2752,6 @@ function serverDeploymentProfile() {
|
|
|
2621
2752
|
dataEndpoint: process.env["PAPI_DATA_ENDPOINT"],
|
|
2622
2753
|
hostedSupabaseUrl: process.env["PAPI_HOSTED_SUPABASE_URL"],
|
|
2623
2754
|
vercelEnabled: process.env["PAPI_ENABLE_VERCEL"],
|
|
2624
|
-
railwayEnabled: process.env["PAPI_ENABLE_RAILWAY"],
|
|
2625
2755
|
managedSupabaseEnabled: process.env["PAPI_ENABLE_MANAGED_SUPABASE"]
|
|
2626
2756
|
});
|
|
2627
2757
|
}
|
|
@@ -2792,16 +2922,21 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2792
2922
|
});
|
|
2793
2923
|
}
|
|
2794
2924
|
} else {
|
|
2795
|
-
if (!existing.root_commit_hash && rootHash) {
|
|
2925
|
+
if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
|
|
2796
2926
|
try {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
resolution_method
|
|
2801
|
-
}
|
|
2927
|
+
const update = {};
|
|
2928
|
+
if (!existing.root_commit_hash && rootHash) {
|
|
2929
|
+
update.root_commit_hash = rootHash;
|
|
2930
|
+
update.resolution_method = "root_hash";
|
|
2931
|
+
}
|
|
2932
|
+
if (!existing.repo_url && originUrl) {
|
|
2933
|
+
update.repo_url = originUrl;
|
|
2934
|
+
if (!update.resolution_method && !existing.resolution_method) update.resolution_method = "repo_url";
|
|
2935
|
+
}
|
|
2936
|
+
await pgAdapter.updateProject(projectId, update);
|
|
2802
2937
|
} catch (err) {
|
|
2803
2938
|
console.error(
|
|
2804
|
-
`[papi] \u26A0 Failed to backfill
|
|
2939
|
+
`[papi] \u26A0 Failed to backfill project resolution signals: ${err instanceof Error ? err.message : String(err)}`
|
|
2805
2940
|
);
|
|
2806
2941
|
}
|
|
2807
2942
|
}
|
|
@@ -2842,7 +2977,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2842
2977
|
} catch {
|
|
2843
2978
|
}
|
|
2844
2979
|
}
|
|
2845
|
-
const adapter = new PgPapiAdapter(config, projectId);
|
|
2980
|
+
const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId }, projectId);
|
|
2846
2981
|
try {
|
|
2847
2982
|
await adapter.initRls();
|
|
2848
2983
|
} catch {
|
|
@@ -2871,7 +3006,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2871
3006
|
Get started in 3 steps:
|
|
2872
3007
|
1. Sign up at ${dashboardUrl ? `${dashboardUrl}/login` : "your configured PAPI dashboard"}
|
|
2873
3008
|
2. Complete the onboarding wizard \u2014 it generates your .mcp.json config
|
|
2874
|
-
3. Download the config, place it in your project root, and restart
|
|
3009
|
+
3. Download the config, place it in your project root, and restart your AI client
|
|
2875
3010
|
|
|
2876
3011
|
Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json env config.`
|
|
2877
3012
|
);
|