@papi-ai/server 0.7.105 → 0.7.108
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 +43 -25
- package/dist/index.js +226 -86
- package/package.json +1 -1
|
@@ -12,6 +12,10 @@ var __export = (target, all) => {
|
|
|
12
12
|
function isSelfHostedDeployment(value) {
|
|
13
13
|
return value === "1" || value?.toLowerCase() === "true";
|
|
14
14
|
}
|
|
15
|
+
function isFossilSupabaseReference(value) {
|
|
16
|
+
if (!value) return false;
|
|
17
|
+
return value.includes(".pooler.supabase.com") || value.includes(DECOMMISSIONED_SUPABASE_PROJECT_REF);
|
|
18
|
+
}
|
|
15
19
|
function optionalBoolean(value, fallback) {
|
|
16
20
|
if (value == null || value.trim() === "") return fallback;
|
|
17
21
|
return isSelfHostedDeployment(value);
|
|
@@ -94,10 +98,11 @@ function calculateCycleMetrics(reports, currentCycle, window = 5) {
|
|
|
94
98
|
}
|
|
95
99
|
return { accuracy, velocity, unparsedEffortCount };
|
|
96
100
|
}
|
|
97
|
-
var CAPABILITY_REGISTRY, CAPABILITY_KEYS, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, WAB_WINDOW_DAYS, WAB_WEEK_MS, EFFORT_SCALE;
|
|
101
|
+
var DECOMMISSIONED_SUPABASE_PROJECT_REF, CAPABILITY_REGISTRY, CAPABILITY_KEYS, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, WAB_WINDOW_DAYS, WAB_WEEK_MS, EFFORT_SCALE;
|
|
98
102
|
var init_dist = __esm({
|
|
99
103
|
"../shared/dist/index.js"() {
|
|
100
104
|
"use strict";
|
|
105
|
+
DECOMMISSIONED_SUPABASE_PROJECT_REF = "guewgygcpcmrcoppihzx";
|
|
101
106
|
CAPABILITY_REGISTRY = [
|
|
102
107
|
// task-3384: the PLAN step. The landing page has advertised these three since
|
|
103
108
|
// the v5 control beat (components/marketing/landing/ControlC.tsx) with a code
|
|
@@ -1908,11 +1913,13 @@ var init_proxy_adapter = __esm({
|
|
|
1908
1913
|
endpoint;
|
|
1909
1914
|
apiKey;
|
|
1910
1915
|
projectId;
|
|
1916
|
+
papiDir;
|
|
1911
1917
|
onAuthRejected;
|
|
1912
1918
|
constructor(config) {
|
|
1913
1919
|
this.endpoint = config.endpoint.replace(/\/$/, "");
|
|
1914
1920
|
this.apiKey = config.apiKey;
|
|
1915
1921
|
this.projectId = config.projectId ?? "";
|
|
1922
|
+
this.papiDir = config.papiDir ?? null;
|
|
1916
1923
|
this.onAuthRejected = config.onAuthRejected;
|
|
1917
1924
|
}
|
|
1918
1925
|
/**
|
|
@@ -1988,6 +1995,7 @@ var init_proxy_adapter = __esm({
|
|
|
1988
1995
|
endpoint: this.endpoint,
|
|
1989
1996
|
apiKey: this.apiKey,
|
|
1990
1997
|
projectId,
|
|
1998
|
+
papiDir: this.papiDir,
|
|
1991
1999
|
onAuthRejected: this.onAuthRejected
|
|
1992
2000
|
}));
|
|
1993
2001
|
}
|
|
@@ -2620,7 +2628,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2620
2628
|
}
|
|
2621
2629
|
// --- Project metadata (path-identity guardrail) ---
|
|
2622
2630
|
async getProjectInfo() {
|
|
2623
|
-
const raw = await this.invoke("getProjectInfo", []);
|
|
2631
|
+
const raw = await this.invoke("getProjectInfo", [this.papiDir]);
|
|
2624
2632
|
if (!raw) return null;
|
|
2625
2633
|
return {
|
|
2626
2634
|
name: raw.name,
|
|
@@ -2707,7 +2715,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2707
2715
|
};
|
|
2708
2716
|
}
|
|
2709
2717
|
async listUserProjects() {
|
|
2710
|
-
const body = await this.postRoute("project-list", {
|
|
2718
|
+
const body = await this.postRoute("project-list", {
|
|
2719
|
+
...this.papiDir ? { papiDir: this.papiDir } : {}
|
|
2720
|
+
});
|
|
2711
2721
|
return body.projects ?? [];
|
|
2712
2722
|
}
|
|
2713
2723
|
async createUserProject(input) {
|
|
@@ -2849,6 +2859,11 @@ var PLACEHOLDER_PATTERNS = [
|
|
|
2849
2859
|
];
|
|
2850
2860
|
function validateDatabaseUrl(connectionString) {
|
|
2851
2861
|
const lower = connectionString.toLowerCase().trim();
|
|
2862
|
+
if (isFossilSupabaseReference(connectionString)) {
|
|
2863
|
+
throw new Error(
|
|
2864
|
+
"This config points at PAPI's old hosted database, which was migrated.\nRegenerate your config at https://getpapi.ai \u2014 your projects and data are intact."
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2852
2867
|
if (PLACEHOLDER_PATTERNS.some((p) => lower.includes(p.toLowerCase()))) {
|
|
2853
2868
|
throw new Error(
|
|
2854
2869
|
"DATABASE_URL contains a placeholder value and is not configured.\nReplace it with your actual Supabase connection string in .mcp.json.\nIf you don't have one yet, contact the PAPI admin for access."
|
|
@@ -2863,10 +2878,12 @@ Check your .mcp.json configuration.`
|
|
|
2863
2878
|
}
|
|
2864
2879
|
}
|
|
2865
2880
|
var _connectionStatus = "offline";
|
|
2881
|
+
var _lastAdapterType = null;
|
|
2866
2882
|
var _pathIdentityChecked = false;
|
|
2867
2883
|
async function createAdapter(optionsOrType, maybePapiDir) {
|
|
2868
2884
|
const options = typeof optionsOrType === "string" ? { adapterType: optionsOrType, papiDir: maybePapiDir } : optionsOrType;
|
|
2869
2885
|
const { adapterType, papiDir, papiEndpoint } = options;
|
|
2886
|
+
_lastAdapterType = adapterType;
|
|
2870
2887
|
switch (adapterType) {
|
|
2871
2888
|
case "pg": {
|
|
2872
2889
|
const { PgAdapter, PgPapiAdapter, configFromEnv } = await import("@papi-ai/adapter-pg");
|
|
@@ -2966,6 +2983,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2966
2983
|
);
|
|
2967
2984
|
}
|
|
2968
2985
|
const pgAdapter = new PgAdapter(config);
|
|
2986
|
+
let projectCreated = false;
|
|
2969
2987
|
try {
|
|
2970
2988
|
const existing = await pgAdapter.getProject(projectId);
|
|
2971
2989
|
if (!existing) {
|
|
@@ -2997,12 +3015,12 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2997
3015
|
id: projectId,
|
|
2998
3016
|
slug,
|
|
2999
3017
|
name: slug,
|
|
3000
|
-
papi_dir: papiDir,
|
|
3001
3018
|
user_id: userId,
|
|
3002
3019
|
root_commit_hash: rootHash ?? void 0,
|
|
3003
3020
|
repo_url: originUrl ?? void 0,
|
|
3004
3021
|
resolution_method: rootHash ? "root_hash" : "manual"
|
|
3005
3022
|
});
|
|
3023
|
+
projectCreated = true;
|
|
3006
3024
|
}
|
|
3007
3025
|
} else {
|
|
3008
3026
|
if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
|
|
@@ -3023,25 +3041,6 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
3023
3041
|
);
|
|
3024
3042
|
}
|
|
3025
3043
|
}
|
|
3026
|
-
if (!_pathIdentityChecked) {
|
|
3027
|
-
_pathIdentityChecked = true;
|
|
3028
|
-
const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
|
|
3029
|
-
const projectName = existing.name ?? existing.slug ?? projectId;
|
|
3030
|
-
const result = assertWorkspaceMatch({
|
|
3031
|
-
storedPapiDir: existing.papi_dir,
|
|
3032
|
-
projectName,
|
|
3033
|
-
workspacePath: cwd
|
|
3034
|
-
});
|
|
3035
|
-
if (result && (result.action === "backfill" || result.action === "migrate")) {
|
|
3036
|
-
try {
|
|
3037
|
-
await pgAdapter.updateProject(projectId, { papi_dir: result.newPapiDir });
|
|
3038
|
-
} catch (err) {
|
|
3039
|
-
console.error(
|
|
3040
|
-
`[papi] Failed to persist papi_dir update: ${err instanceof Error ? err.message : String(err)}`
|
|
3041
|
-
);
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
3044
|
if (existing.user_id) {
|
|
3046
3045
|
const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
|
|
3047
3046
|
if (configuredUserId && existing.user_id !== configuredUserId) {
|
|
@@ -3060,11 +3059,29 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
3060
3059
|
} catch {
|
|
3061
3060
|
}
|
|
3062
3061
|
}
|
|
3063
|
-
const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId }, projectId);
|
|
3062
|
+
const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId, papiDir }, projectId);
|
|
3064
3063
|
try {
|
|
3065
3064
|
await adapter.initRls();
|
|
3066
3065
|
} catch {
|
|
3067
3066
|
}
|
|
3067
|
+
if (projectCreated && papiDir && adapter.setProjectPapiDir) {
|
|
3068
|
+
await adapter.setProjectPapiDir(papiDir);
|
|
3069
|
+
}
|
|
3070
|
+
if (!_pathIdentityChecked && adapter.getProjectInfo && adapter.setProjectPapiDir) {
|
|
3071
|
+
_pathIdentityChecked = true;
|
|
3072
|
+
const info = await adapter.getProjectInfo();
|
|
3073
|
+
if (info) {
|
|
3074
|
+
const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
|
|
3075
|
+
const result = assertWorkspaceMatch({
|
|
3076
|
+
storedPapiDir: info.papi_dir,
|
|
3077
|
+
projectName: info.name || info.slug || projectId,
|
|
3078
|
+
workspacePath: cwd
|
|
3079
|
+
});
|
|
3080
|
+
if (result && (result.action === "backfill" || result.action === "migrate")) {
|
|
3081
|
+
await adapter.setProjectPapiDir(result.newPapiDir);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3068
3085
|
const connected = await adapter.probeConnection();
|
|
3069
3086
|
if (connected) {
|
|
3070
3087
|
_connectionStatus = "connected";
|
|
@@ -3103,7 +3120,8 @@ Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json en
|
|
|
3103
3120
|
const adapter = new ProxyPapiAdapter2({
|
|
3104
3121
|
endpoint: dataEndpoint,
|
|
3105
3122
|
apiKey: dataApiKey,
|
|
3106
|
-
projectId: projectId || void 0
|
|
3123
|
+
projectId: projectId || void 0,
|
|
3124
|
+
papiDir
|
|
3107
3125
|
});
|
|
3108
3126
|
const connected = await adapter.probeConnection();
|
|
3109
3127
|
if (!connected) {
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,10 @@ import { randomUUID } from "crypto";
|
|
|
14
14
|
function isSelfHostedDeployment(value) {
|
|
15
15
|
return value === "1" || value?.toLowerCase() === "true";
|
|
16
16
|
}
|
|
17
|
+
function isFossilSupabaseReference(value) {
|
|
18
|
+
if (!value) return false;
|
|
19
|
+
return value.includes(".pooler.supabase.com") || value.includes(DECOMMISSIONED_SUPABASE_PROJECT_REF);
|
|
20
|
+
}
|
|
17
21
|
function isCapabilityEnabled(caps, key) {
|
|
18
22
|
const stored = caps?.[key];
|
|
19
23
|
if (stored != null) return stored !== false;
|
|
@@ -788,10 +792,11 @@ function parsePhaseBlock(block) {
|
|
|
788
792
|
if (isNaN(order)) return null;
|
|
789
793
|
return { id, slug, label, description, status, order };
|
|
790
794
|
}
|
|
791
|
-
var CAPABILITY_REGISTRY, CAPABILITY_KEYS, ASSIGNABLE_CONTRIBUTOR_ROLES, SENSITIVE_CHANGELOG_PATTERNS, CONTRIBUTOR_PRICING_URL, PROPOSAL_EVENT_TYPES, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, SEVERITY_ORDER, NONE_RE, AD_HOC_PLACEHOLDER_RE, NONE_LEAD_RE, CONTRAST_LEAD_RE, MIN_REASON_LENGTH, LOW_SEVERITIES, WAB_WINDOW_DAYS, WAB_DEFAULT_WEEKS, WAB_WEEK_MS, PRODUCT_MARKER, MECHANICS_MARKER, CHECK_VALUE_MAX, VALID_EFFORT_SIZES, SECTION_HEADERS, VALID_EFFORT_SIZES2, EFFORT_SCALE, NONE_PATTERN, NONE_PATTERN2, VALID_STATUSES, PHASES_START, PHASES_END;
|
|
795
|
+
var DECOMMISSIONED_SUPABASE_PROJECT_REF, CAPABILITY_REGISTRY, CAPABILITY_KEYS, ASSIGNABLE_CONTRIBUTOR_ROLES, SENSITIVE_CHANGELOG_PATTERNS, CONTRIBUTOR_PRICING_URL, PROPOSAL_EVENT_TYPES, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, SEVERITY_ORDER, NONE_RE, AD_HOC_PLACEHOLDER_RE, NONE_LEAD_RE, CONTRAST_LEAD_RE, MIN_REASON_LENGTH, LOW_SEVERITIES, WAB_WINDOW_DAYS, WAB_DEFAULT_WEEKS, WAB_WEEK_MS, PRODUCT_MARKER, MECHANICS_MARKER, CHECK_VALUE_MAX, VALID_EFFORT_SIZES, SECTION_HEADERS, VALID_EFFORT_SIZES2, EFFORT_SCALE, NONE_PATTERN, NONE_PATTERN2, VALID_STATUSES, PHASES_START, PHASES_END;
|
|
792
796
|
var init_dist = __esm({
|
|
793
797
|
"../shared/dist/index.js"() {
|
|
794
798
|
"use strict";
|
|
799
|
+
DECOMMISSIONED_SUPABASE_PROJECT_REF = "guewgygcpcmrcoppihzx";
|
|
795
800
|
CAPABILITY_REGISTRY = [
|
|
796
801
|
// task-3384: the PLAN step. The landing page has advertised these three since
|
|
797
802
|
// the v5 control beat (components/marketing/landing/ControlC.tsx) with a code
|
|
@@ -2744,11 +2749,13 @@ var init_proxy_adapter = __esm({
|
|
|
2744
2749
|
endpoint;
|
|
2745
2750
|
apiKey;
|
|
2746
2751
|
projectId;
|
|
2752
|
+
papiDir;
|
|
2747
2753
|
onAuthRejected;
|
|
2748
2754
|
constructor(config2) {
|
|
2749
2755
|
this.endpoint = config2.endpoint.replace(/\/$/, "");
|
|
2750
2756
|
this.apiKey = config2.apiKey;
|
|
2751
2757
|
this.projectId = config2.projectId ?? "";
|
|
2758
|
+
this.papiDir = config2.papiDir ?? null;
|
|
2752
2759
|
this.onAuthRejected = config2.onAuthRejected;
|
|
2753
2760
|
}
|
|
2754
2761
|
/**
|
|
@@ -2824,6 +2831,7 @@ var init_proxy_adapter = __esm({
|
|
|
2824
2831
|
endpoint: this.endpoint,
|
|
2825
2832
|
apiKey: this.apiKey,
|
|
2826
2833
|
projectId,
|
|
2834
|
+
papiDir: this.papiDir,
|
|
2827
2835
|
onAuthRejected: this.onAuthRejected
|
|
2828
2836
|
}));
|
|
2829
2837
|
}
|
|
@@ -3456,7 +3464,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
3456
3464
|
}
|
|
3457
3465
|
// --- Project metadata (path-identity guardrail) ---
|
|
3458
3466
|
async getProjectInfo() {
|
|
3459
|
-
const raw = await this.invoke("getProjectInfo", []);
|
|
3467
|
+
const raw = await this.invoke("getProjectInfo", [this.papiDir]);
|
|
3460
3468
|
if (!raw) return null;
|
|
3461
3469
|
return {
|
|
3462
3470
|
name: raw.name,
|
|
@@ -3543,7 +3551,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
3543
3551
|
};
|
|
3544
3552
|
}
|
|
3545
3553
|
async listUserProjects() {
|
|
3546
|
-
const body = await this.postRoute("project-list", {
|
|
3554
|
+
const body = await this.postRoute("project-list", {
|
|
3555
|
+
...this.papiDir ? { papiDir: this.papiDir } : {}
|
|
3556
|
+
});
|
|
3547
3557
|
return body.projects ?? [];
|
|
3548
3558
|
}
|
|
3549
3559
|
async createUserProject(input) {
|
|
@@ -7170,6 +7180,11 @@ var PLACEHOLDER_PATTERNS = [
|
|
|
7170
7180
|
];
|
|
7171
7181
|
function validateDatabaseUrl(connectionString) {
|
|
7172
7182
|
const lower = connectionString.toLowerCase().trim();
|
|
7183
|
+
if (isFossilSupabaseReference(connectionString)) {
|
|
7184
|
+
throw new Error(
|
|
7185
|
+
"This config points at PAPI's old hosted database, which was migrated.\nRegenerate your config at https://getpapi.ai \u2014 your projects and data are intact."
|
|
7186
|
+
);
|
|
7187
|
+
}
|
|
7173
7188
|
if (PLACEHOLDER_PATTERNS.some((p) => lower.includes(p.toLowerCase()))) {
|
|
7174
7189
|
throw new Error(
|
|
7175
7190
|
"DATABASE_URL contains a placeholder value and is not configured.\nReplace it with your actual Supabase connection string in .mcp.json.\nIf you don't have one yet, contact the PAPI admin for access."
|
|
@@ -7184,6 +7199,10 @@ Check your .mcp.json configuration.`
|
|
|
7184
7199
|
}
|
|
7185
7200
|
}
|
|
7186
7201
|
var _connectionStatus = "offline";
|
|
7202
|
+
var _lastAdapterType = null;
|
|
7203
|
+
function getLastAdapterType() {
|
|
7204
|
+
return _lastAdapterType;
|
|
7205
|
+
}
|
|
7187
7206
|
var _pathIdentityChecked = false;
|
|
7188
7207
|
function getConnectionStatus() {
|
|
7189
7208
|
return _connectionStatus;
|
|
@@ -7191,6 +7210,7 @@ function getConnectionStatus() {
|
|
|
7191
7210
|
async function createAdapter(optionsOrType, maybePapiDir) {
|
|
7192
7211
|
const options = typeof optionsOrType === "string" ? { adapterType: optionsOrType, papiDir: maybePapiDir } : optionsOrType;
|
|
7193
7212
|
const { adapterType, papiDir, papiEndpoint } = options;
|
|
7213
|
+
_lastAdapterType = adapterType;
|
|
7194
7214
|
switch (adapterType) {
|
|
7195
7215
|
case "pg": {
|
|
7196
7216
|
const { PgAdapter, PgPapiAdapter, configFromEnv } = await import("@papi-ai/adapter-pg");
|
|
@@ -7290,6 +7310,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7290
7310
|
);
|
|
7291
7311
|
}
|
|
7292
7312
|
const pgAdapter = new PgAdapter(config2);
|
|
7313
|
+
let projectCreated = false;
|
|
7293
7314
|
try {
|
|
7294
7315
|
const existing = await pgAdapter.getProject(projectId);
|
|
7295
7316
|
if (!existing) {
|
|
@@ -7321,12 +7342,12 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7321
7342
|
id: projectId,
|
|
7322
7343
|
slug,
|
|
7323
7344
|
name: slug,
|
|
7324
|
-
papi_dir: papiDir,
|
|
7325
7345
|
user_id: userId,
|
|
7326
7346
|
root_commit_hash: rootHash ?? void 0,
|
|
7327
7347
|
repo_url: originUrl ?? void 0,
|
|
7328
7348
|
resolution_method: rootHash ? "root_hash" : "manual"
|
|
7329
7349
|
});
|
|
7350
|
+
projectCreated = true;
|
|
7330
7351
|
}
|
|
7331
7352
|
} else {
|
|
7332
7353
|
if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
|
|
@@ -7347,25 +7368,6 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7347
7368
|
);
|
|
7348
7369
|
}
|
|
7349
7370
|
}
|
|
7350
|
-
if (!_pathIdentityChecked) {
|
|
7351
|
-
_pathIdentityChecked = true;
|
|
7352
|
-
const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
|
|
7353
|
-
const projectName = existing.name ?? existing.slug ?? projectId;
|
|
7354
|
-
const result = assertWorkspaceMatch({
|
|
7355
|
-
storedPapiDir: existing.papi_dir,
|
|
7356
|
-
projectName,
|
|
7357
|
-
workspacePath: cwd
|
|
7358
|
-
});
|
|
7359
|
-
if (result && (result.action === "backfill" || result.action === "migrate")) {
|
|
7360
|
-
try {
|
|
7361
|
-
await pgAdapter.updateProject(projectId, { papi_dir: result.newPapiDir });
|
|
7362
|
-
} catch (err) {
|
|
7363
|
-
console.error(
|
|
7364
|
-
`[papi] Failed to persist papi_dir update: ${err instanceof Error ? err.message : String(err)}`
|
|
7365
|
-
);
|
|
7366
|
-
}
|
|
7367
|
-
}
|
|
7368
|
-
}
|
|
7369
7371
|
if (existing.user_id) {
|
|
7370
7372
|
const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
|
|
7371
7373
|
if (configuredUserId && existing.user_id !== configuredUserId) {
|
|
@@ -7384,11 +7386,29 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7384
7386
|
} catch {
|
|
7385
7387
|
}
|
|
7386
7388
|
}
|
|
7387
|
-
const adapter2 = new PgPapiAdapter({ ...config2, userId: resolveUserId }, projectId);
|
|
7389
|
+
const adapter2 = new PgPapiAdapter({ ...config2, userId: resolveUserId, papiDir }, projectId);
|
|
7388
7390
|
try {
|
|
7389
7391
|
await adapter2.initRls();
|
|
7390
7392
|
} catch {
|
|
7391
7393
|
}
|
|
7394
|
+
if (projectCreated && papiDir && adapter2.setProjectPapiDir) {
|
|
7395
|
+
await adapter2.setProjectPapiDir(papiDir);
|
|
7396
|
+
}
|
|
7397
|
+
if (!_pathIdentityChecked && adapter2.getProjectInfo && adapter2.setProjectPapiDir) {
|
|
7398
|
+
_pathIdentityChecked = true;
|
|
7399
|
+
const info = await adapter2.getProjectInfo();
|
|
7400
|
+
if (info) {
|
|
7401
|
+
const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
|
|
7402
|
+
const result = assertWorkspaceMatch({
|
|
7403
|
+
storedPapiDir: info.papi_dir,
|
|
7404
|
+
projectName: info.name || info.slug || projectId,
|
|
7405
|
+
workspacePath: cwd
|
|
7406
|
+
});
|
|
7407
|
+
if (result && (result.action === "backfill" || result.action === "migrate")) {
|
|
7408
|
+
await adapter2.setProjectPapiDir(result.newPapiDir);
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7392
7412
|
const connected = await adapter2.probeConnection();
|
|
7393
7413
|
if (connected) {
|
|
7394
7414
|
_connectionStatus = "connected";
|
|
@@ -7427,7 +7447,8 @@ Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json en
|
|
|
7427
7447
|
const adapter2 = new ProxyPapiAdapter2({
|
|
7428
7448
|
endpoint: dataEndpoint,
|
|
7429
7449
|
apiKey: dataApiKey,
|
|
7430
|
-
projectId: projectId || void 0
|
|
7450
|
+
projectId: projectId || void 0,
|
|
7451
|
+
papiDir
|
|
7431
7452
|
});
|
|
7432
7453
|
const connected = await adapter2.probeConnection();
|
|
7433
7454
|
if (!connected) {
|
|
@@ -7556,21 +7577,27 @@ import {
|
|
|
7556
7577
|
// src/universal-frame.ts
|
|
7557
7578
|
var UNIVERSAL_FRAME = `PAPI gives this project a structured plan \u2192 build \u2192 review cycle, persisted across sessions. Follow it:
|
|
7558
7579
|
|
|
7559
|
-
|
|
7580
|
+
0a. MCHAT REALITY. Most hosts load MCP tools at CONVERSATION START \u2014 tools added or authenticated mid-chat are invisible in that chat. If the user says PAPI "is not working" or "failed to connect" but your tool list has no papi tools, do NOT retry the install or re-run OAuth: the link almost certainly succeeded already. Tell them plainly: "PAPI connected fine \u2014 tools only load in a NEW conversation. Start a fresh chat and run \`papi\` there." Never claim the connection failed when you simply cannot see the tools.
|
|
7581
|
+
|
|
7582
|
+
0b. ONE CHAT, ONE PROJECT. Each conversation binds to ONE PAPI project at a time. If the user has several projects, orient names which one is connected \u2014 confirm it matches the repo the user is working in before writing anything. Wrong project? Run \`project_switch\` (or pass \`project="<slug>"\` per call), and say a fresh conversation is the cleanest switch.
|
|
7583
|
+
|
|
7584
|
+
1. NEW HERE? If PAPI has never been set up for this project (no cycles yet, or \`orient\` says the board is empty), call \`setup\` FIRST \u2014 it generates the Product Brief and scaffolds the workflow. Then run \`plan\` to create the first cycle. This is the required first step for a brand-new project; everything below assumes setup has already run.
|
|
7560
7585
|
|
|
7561
|
-
|
|
7586
|
+
2. ORIENT FIRST (once set up). At the start of every session, call \`orient\` (or \`papi\`) before anything else \u2014 it returns the current cycle, what's in flight, and the recommended next action. Re-run it after any context compression.
|
|
7562
7587
|
|
|
7563
|
-
|
|
7588
|
+
3. THE CYCLE, IN ORDER: \`plan\` (once per cycle) \u2192 \`build_list\` (pick a task) \u2192 \`build_execute <task>\` to start \u2192 implement the task from its BUILD HANDOFF \u2192 \`build_execute\` again to complete with a build report \u2192 \`review_submit\` \u2192 \`release\` when every cycle task is done.
|
|
7564
7589
|
|
|
7565
|
-
|
|
7590
|
+
4. STATUS DISCIPLINE. Check a task's status before acting. "In Review" = already built \u2014 never re-build it; submit a review instead. "In Progress" = a build started \u2014 check the existing branch before writing new code. "Backlog" = not started, but check for an existing feature branch first.
|
|
7566
7591
|
|
|
7567
|
-
|
|
7592
|
+
5. STAY IN SCOPE. Build exactly what the BUILD HANDOFF specifies. Mechanical steps (branch creation, commits, status updates) are automatic \u2014 only "what to build" needs the user's confirmation.
|
|
7568
7593
|
|
|
7569
|
-
|
|
7594
|
+
6. VERIFY BEFORE DONE. Test the change and confirm it works before reporting a task complete. Report failures honestly, with the output.
|
|
7570
7595
|
|
|
7571
|
-
|
|
7596
|
+
7. NAME THE PROJECT WHEN YOU KNOW IT. If you know which repo this session is working in, pass \`project="<slug>"\` on the call rather than relying on whatever the connection defaults to. An account with more than one project cannot be resolved from a connection with no project bound \u2014 PAPI will stop and ask which one rather than guess, and answering costs a round trip. If PAPI asks, put the list to the user, then re-call with their choice; run \`project_switch\` to make it stick.
|
|
7572
7597
|
|
|
7573
|
-
PAPI reads and writes all project state through these tools \u2014 they are the source of truth, not local files
|
|
7598
|
+
PAPI reads and writes all project state through these tools \u2014 they are the source of truth, not local files.
|
|
7599
|
+
|
|
7600
|
+
DATA HANDLING: PAPI stores structured project metadata only (never your source code), runs no AI calls from its servers \u2014 https://getpapi.ai/trust states exactly who can read what.`;
|
|
7574
7601
|
|
|
7575
7602
|
// src/lib/response.ts
|
|
7576
7603
|
function textResponse(text, usage) {
|
|
@@ -12398,6 +12425,19 @@ ${cleanContent}`;
|
|
|
12398
12425
|
`task-3670 self-heal: ${repairedCycles.length} task(s) had lost cycle membership and were re-assigned (${repairedCycles.join(", ")})`
|
|
12399
12426
|
);
|
|
12400
12427
|
}
|
|
12428
|
+
let health;
|
|
12429
|
+
try {
|
|
12430
|
+
health = await adapter2.getCycleHealth?.();
|
|
12431
|
+
} catch (err) {
|
|
12432
|
+
verifyWarnings.push(
|
|
12433
|
+
`task-3733: could not re-verify the cycle row after write (${err instanceof Error ? err.message : String(err)}) \u2014 cycle membership was written and repaired, but this final check could not confirm it.`
|
|
12434
|
+
);
|
|
12435
|
+
}
|
|
12436
|
+
if (health && health.totalCycles !== newCycleNumber) {
|
|
12437
|
+
throw new Error(
|
|
12438
|
+
`Plan apply committed, but the cycle row for Cycle ${newCycleNumber} did not take: getCycleHealth() reports the caller's current cycle as ${health.totalCycles}. Task assignments were written and repaired, but the cycle entity itself is stale \u2014 investigate the planWriteBack cycle upsert before trusting this apply.`
|
|
12439
|
+
);
|
|
12440
|
+
}
|
|
12401
12441
|
const allWarnings = [...result.warnings, ...verifyWarnings, ...earlyCreateWarnings];
|
|
12402
12442
|
const handoffCount = data.cycleHandoffs?.length ?? 0;
|
|
12403
12443
|
const correctionCount = data.boardCorrections?.length ?? 0;
|
|
@@ -12805,6 +12845,13 @@ async function resolveCallerLatestCycle(adapter2, callerUserId) {
|
|
|
12805
12845
|
}
|
|
12806
12846
|
return newest;
|
|
12807
12847
|
}
|
|
12848
|
+
function unownedCycleNumbers(cycles) {
|
|
12849
|
+
const unowned = /* @__PURE__ */ new Set();
|
|
12850
|
+
for (const c of cycles) {
|
|
12851
|
+
if (c.userId == null) unowned.add(c.number);
|
|
12852
|
+
}
|
|
12853
|
+
return unowned;
|
|
12854
|
+
}
|
|
12808
12855
|
async function validateAndPrepare(adapter2, force, callerUserId, adapterType) {
|
|
12809
12856
|
let mode;
|
|
12810
12857
|
let cycleNumber;
|
|
@@ -12829,23 +12876,37 @@ Run \`release\` first, or pass \`force: true\` to bypass this block.`
|
|
|
12829
12876
|
);
|
|
12830
12877
|
}
|
|
12831
12878
|
if (!force) {
|
|
12879
|
+
const callerCycleNumbers = callerUserId ? (await adapter2.readCycles()).filter((c) => c.userId === callerUserId).map((c) => c.number) : null;
|
|
12832
12880
|
const inReviewTasks = await adapter2.queryBoard({
|
|
12833
12881
|
status: ["In Review"],
|
|
12834
12882
|
compact: true
|
|
12835
12883
|
});
|
|
12836
|
-
const
|
|
12837
|
-
(t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2
|
|
12884
|
+
const staleMine = inReviewTasks.filter(
|
|
12885
|
+
(t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2 && (callerCycleNumbers === null || callerCycleNumbers.includes(t.cycle))
|
|
12838
12886
|
);
|
|
12839
|
-
if (
|
|
12840
|
-
const taskList =
|
|
12887
|
+
if (staleMine.length > 0) {
|
|
12888
|
+
const taskList = staleMine.map((t) => `- ${t.id} (Cycle ${t.cycle}): ${t.title}`).join("\n");
|
|
12841
12889
|
throw new Error(
|
|
12842
|
-
`Stale reviews detected \u2014 ${
|
|
12890
|
+
`Stale reviews detected \u2014 ${staleMine.length} task(s) have been In Review for 2+ cycles:
|
|
12843
12891
|
|
|
12844
12892
|
${taskList}
|
|
12845
12893
|
|
|
12846
12894
|
Run \`review_submit\` to clear them, or pass \`force: true\` to bypass this block.`
|
|
12847
12895
|
);
|
|
12848
12896
|
}
|
|
12897
|
+
if (callerCycleNumbers !== null) {
|
|
12898
|
+
const unowned = unownedCycleNumbers(await adapter2.readCycles());
|
|
12899
|
+
const staleTheirs = inReviewTasks.filter(
|
|
12900
|
+
(t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2 && !callerCycleNumbers.includes(t.cycle) && // A task in a cycle with no known owner is unowned legacy data —
|
|
12901
|
+
// do not attribute it to a teammate either.
|
|
12902
|
+
!unowned.has(t.cycle)
|
|
12903
|
+
);
|
|
12904
|
+
if (staleTheirs.length > 0) {
|
|
12905
|
+
strategyReviewWarning += `> \u2139\uFE0F ${staleTheirs.length} task(s) are In Review from other members' cycles (2+ cycles stale). They do not block your planning \u2014 but the review queue may need attention.
|
|
12906
|
+
|
|
12907
|
+
`;
|
|
12908
|
+
}
|
|
12909
|
+
}
|
|
12849
12910
|
}
|
|
12850
12911
|
const autoCompleted = await assertSingleActiveCycle(adapter2, { autoComplete: force === true, userId: callerUserId ?? void 0 });
|
|
12851
12912
|
for (const note of autoCompleted) {
|
|
@@ -22064,60 +22125,87 @@ Releasing to a non-base branch (e.g. dev) is unaffected.` + resolutionNote
|
|
|
22064
22125
|
}
|
|
22065
22126
|
const releaseRole = callerRole === "release_manager" ? "release manager" : "editor";
|
|
22066
22127
|
tracker.mark("contributor-pr-reconciliation");
|
|
22128
|
+
const closeContributorCycle = (cycleOverride) => closeCycleState(
|
|
22129
|
+
config2,
|
|
22130
|
+
adapter2,
|
|
22131
|
+
version,
|
|
22132
|
+
cycleOverride ?? cycleToClose ?? void 0,
|
|
22133
|
+
{
|
|
22134
|
+
force: force ?? false,
|
|
22135
|
+
skipVersion: skipVersion ?? false,
|
|
22136
|
+
callerUserId: gate.callerUserId
|
|
22137
|
+
}
|
|
22138
|
+
);
|
|
22139
|
+
let reconciled = null;
|
|
22067
22140
|
try {
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
|
|
22076
|
-
|
|
22077
|
-
}
|
|
22078
|
-
|
|
22079
|
-
|
|
22080
|
-
|
|
22081
|
-
|
|
22082
|
-
|
|
22083
|
-
|
|
22084
|
-
|
|
22085
|
-
|
|
22086
|
-
|
|
22087
|
-
|
|
22141
|
+
reconciled = await reconcileContributorReleasePrs(config2, adapter2, gate.callerUserId, cycleToClose);
|
|
22142
|
+
} catch (err) {
|
|
22143
|
+
console.error(`[release] contributor PR reconciliation failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
22144
|
+
}
|
|
22145
|
+
if (reconciled?.merged.length) {
|
|
22146
|
+
const latest = reconciled.merged[0];
|
|
22147
|
+
let caps2 = {};
|
|
22148
|
+
try {
|
|
22149
|
+
const info = adapter2.getProjectInfo ? await adapter2.getProjectInfo() : null;
|
|
22150
|
+
caps2 = info?.capabilities ?? {};
|
|
22151
|
+
} catch {
|
|
22152
|
+
caps2 = {};
|
|
22153
|
+
}
|
|
22154
|
+
let closed;
|
|
22155
|
+
try {
|
|
22156
|
+
closed = await closeContributorCycle(latest.cycle);
|
|
22157
|
+
} catch (err) {
|
|
22158
|
+
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
22159
|
+
}
|
|
22160
|
+
await recordReadinessVerified(tracker);
|
|
22161
|
+
await recordQualityGate(tracker, evaluateReleaseGate(caps2, config2.gateCommand, gateResult), caps2);
|
|
22162
|
+
await completeRelease(tracker, {
|
|
22163
|
+
cycleClosed: closed.resolvedCycleNum > 0 ? closed.resolvedCycleNum : latest.cycle,
|
|
22164
|
+
version,
|
|
22165
|
+
caps: caps2,
|
|
22166
|
+
branchMerges: latest.branch ? [{ branch: latest.branch, prUrl: latest.prUrl }] : [],
|
|
22167
|
+
changelogEmitted: false
|
|
22168
|
+
});
|
|
22169
|
+
const localHandoff = isHostedTransport() ? `
|
|
22088
22170
|
${buildHostedGitDirective({
|
|
22089
|
-
|
|
22090
|
-
|
|
22091
|
-
|
|
22092
|
-
|
|
22093
|
-
|
|
22094
|
-
|
|
22095
|
-
|
|
22096
|
-
|
|
22097
|
-
|
|
22098
|
-
|
|
22171
|
+
version,
|
|
22172
|
+
branch,
|
|
22173
|
+
skipVersion: skipVersion ?? false,
|
|
22174
|
+
harnessCanBuild: detectHarness(clientName).build,
|
|
22175
|
+
harnessKnown: detectHarness(clientName).known,
|
|
22176
|
+
harnessLabel: detectHarness(clientName).label,
|
|
22177
|
+
changelogSection: null
|
|
22178
|
+
})}` : "";
|
|
22179
|
+
const cycleLabel = closed.resolvedCycleNum > 0 ? `Cycle ${closed.resolvedCycleNum}` : latest.cycle ? `Cycle ${latest.cycle}` : "The contributor cycle";
|
|
22180
|
+
return textResponse(
|
|
22181
|
+
`## Release ${version} \u2014 contributor PR merged
|
|
22099
22182
|
|
|
22100
22183
|
PAPI detected that ${latest.prUrl} was merged on GitHub and reconciled the contributor release. GitHub granted the merge permission; your PAPI **${releaseRole}** role granted the release workflow.
|
|
22101
22184
|
|
|
22102
|
-
${
|
|
22185
|
+
${cycleLabel} remains **complete**, and the release is now recorded as **released** in PAPI.` + localHandoff + `
|
|
22103
22186
|
|
|
22104
22187
|
Next: run \`plan\` to start your next cycle.`
|
|
22105
|
-
|
|
22188
|
+
);
|
|
22189
|
+
}
|
|
22190
|
+
if (reconciled?.open.length) {
|
|
22191
|
+
const openCycle = cycleToClose !== null ? reconciled.open.find((pr) => pr.cycle === cycleToClose)?.cycle ?? null : reconciled.open[0].cycle;
|
|
22192
|
+
if (openCycle !== null && openCycle !== void 0) {
|
|
22193
|
+
try {
|
|
22194
|
+
await closeContributorCycle(openCycle);
|
|
22195
|
+
} catch (err) {
|
|
22196
|
+
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
22197
|
+
}
|
|
22106
22198
|
}
|
|
22107
|
-
|
|
22108
|
-
|
|
22109
|
-
|
|
22110
|
-
`## Release ${version} \u2014 contributor PR awaiting merge
|
|
22199
|
+
const prLines = reconciled.open.map((p) => `- ${p.prUrl}`).join("\n");
|
|
22200
|
+
return textResponse(
|
|
22201
|
+
`## Release ${version} \u2014 contributor PR awaiting merge
|
|
22111
22202
|
|
|
22112
22203
|
${prLines}
|
|
22113
22204
|
|
|
22114
22205
|
Your PAPI **${releaseRole}** role allowed this release PR. Merge permission is controlled separately by GitHub: if GitHub lets you merge, review and merge it there; otherwise a repository maintainer must merge it.
|
|
22115
22206
|
|
|
22116
22207
|
After it is merged, run \`release\` again so PAPI can detect the merge and finish the release record.`
|
|
22117
|
-
|
|
22118
|
-
}
|
|
22119
|
-
} catch (err) {
|
|
22120
|
-
console.error(`[release] contributor PR reconciliation failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
22208
|
+
);
|
|
22121
22209
|
}
|
|
22122
22210
|
if (isHostedTransport()) {
|
|
22123
22211
|
return errorResponse(
|
|
@@ -23056,7 +23144,7 @@ function getUnresolvedDeps(task, allTasks) {
|
|
|
23056
23144
|
const taskStatusMap = new Map(allTasks.map((t) => [t.id, t.status]));
|
|
23057
23145
|
return deps.filter((depId) => {
|
|
23058
23146
|
const status = taskStatusMap.get(depId);
|
|
23059
|
-
return status !== "Done";
|
|
23147
|
+
return status !== "Done" && status !== "Cancelled" && status !== "Archived";
|
|
23060
23148
|
});
|
|
23061
23149
|
}
|
|
23062
23150
|
function resolveDepCycleBranch(cycleBranches, cycleNumber, upstreamModule, dependentModule) {
|
|
@@ -29879,6 +29967,30 @@ async function handleReviewClaim(adapter2, config2, args) {
|
|
|
29879
29967
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
29880
29968
|
import { access as access2, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
29881
29969
|
import path6 from "path";
|
|
29970
|
+
init_dist();
|
|
29971
|
+
var FOSSIL_CONFIG_MESSAGE = [
|
|
29972
|
+
"This config points at PAPI's old hosted database, which was migrated.",
|
|
29973
|
+
"Regenerate your config at https://getpapi.ai \u2014 your projects and data are intact."
|
|
29974
|
+
].join("\n");
|
|
29975
|
+
function extractPapiOwnedConfigText(existingConfig, agent) {
|
|
29976
|
+
if (agent === "codex") {
|
|
29977
|
+
const match = existingConfig.match(/\[mcp_servers\.papi\][\s\S]*?(?=\n\[|$)/);
|
|
29978
|
+
return match ? match[0] : existingConfig;
|
|
29979
|
+
}
|
|
29980
|
+
if (agent === "hermes") {
|
|
29981
|
+
const match = existingConfig.match(/^[ \t]*papi[ \t]*:[\s\S]*?(?=\n[ \t]{0,2}\S[^\n]*:[ \t]*$|$)/m);
|
|
29982
|
+
return match ? match[0] : existingConfig;
|
|
29983
|
+
}
|
|
29984
|
+
try {
|
|
29985
|
+
const parsed = JSON.parse(existingConfig);
|
|
29986
|
+
const container = parsed.mcpServers ?? parsed.servers ?? parsed.mcp;
|
|
29987
|
+
const papiEntry = container?.papi;
|
|
29988
|
+
const env = papiEntry?.env ?? papiEntry?.environment;
|
|
29989
|
+
if (env) return JSON.stringify(env);
|
|
29990
|
+
} catch {
|
|
29991
|
+
}
|
|
29992
|
+
return existingConfig;
|
|
29993
|
+
}
|
|
29882
29994
|
var initTool = {
|
|
29883
29995
|
name: "init",
|
|
29884
29996
|
description: "Write the MCP config file that connects this project to PAPI. Generates .mcp.json, or the equivalent for whichever MCP client you use \u2014 Claude Code, Cursor, VS Code, Windsurf, OpenCode, Amazon Q, Kilo Code, Gemini CLI, Codex CLI, or Hermes Agent. Config-only \u2014 does not create any project data. Run this first, then run `setup` to create your PAPI project.",
|
|
@@ -30155,6 +30267,11 @@ async function handleInit(config2, args) {
|
|
|
30155
30267
|
await access2(mcpJsonPath);
|
|
30156
30268
|
existingConfig = await readFile5(mcpJsonPath, "utf-8");
|
|
30157
30269
|
} catch {
|
|
30270
|
+
}
|
|
30271
|
+
if (existingConfig && isFossilSupabaseReference(extractPapiOwnedConfigText(existingConfig, agent))) {
|
|
30272
|
+
return errorResponse(`${FOSSIL_CONFIG_MESSAGE}
|
|
30273
|
+
|
|
30274
|
+
Path: ${mcpJsonPath}`);
|
|
30158
30275
|
}
|
|
30159
30276
|
if (existingConfig && !force) {
|
|
30160
30277
|
const hasPapiServer = (() => {
|
|
@@ -30308,6 +30425,9 @@ ${writeNote}
|
|
|
30308
30425
|
` + formatFilesToWriteSection(collector)
|
|
30309
30426
|
);
|
|
30310
30427
|
}
|
|
30428
|
+
if (isDatabaseUser && isFossilSupabaseReference(process.env.DATABASE_URL)) {
|
|
30429
|
+
return errorResponse(FOSSIL_CONFIG_MESSAGE);
|
|
30430
|
+
}
|
|
30311
30431
|
if (isDatabaseUser) {
|
|
30312
30432
|
const projectId = randomUUID12();
|
|
30313
30433
|
const envVars = {
|
|
@@ -30346,7 +30466,7 @@ ${writeNote}
|
|
|
30346
30466
|
"",
|
|
30347
30467
|
"## Next Steps",
|
|
30348
30468
|
"",
|
|
30349
|
-
...process.env.DATABASE_URL ? ["1. **Restart your MCP client** to pick up the new config."] : [`1. **Set your DATABASE_URL** \u2014 replace \`<YOUR_DATABASE_URL>\` in \`${target.displayPath}\` with
|
|
30469
|
+
...process.env.DATABASE_URL ? ["1. **Restart your MCP client** to pick up the new config."] : [`1. **Set your DATABASE_URL** \u2014 replace \`<YOUR_DATABASE_URL>\` in \`${target.displayPath}\` with the connection string for the self-hosted Postgres/Auth stack you are running (production resolves this over the internal-only VPS tunnel \u2014 see \`infra/vps/self-host-runbook.md\`). This is not a managed Supabase pooler string.`],
|
|
30350
30470
|
...process.env.PAPI_USER_ID ? [] : ["2. **Set your PAPI_USER_ID** \u2014 replace `<YOUR_ACCOUNT_UUID>` with your account UUID (getpapi.ai \u2192 Settings \u2192 Account). Release and reviews use it to recognise you as the owner."],
|
|
30351
30471
|
`${process.env.PAPI_USER_ID ? "2" : "3"}. **Run \`setup\`** \u2014 this scaffolds your project with a Product Brief, Active Decisions, and CLAUDE.md.`
|
|
30352
30472
|
].join("\n");
|
|
@@ -30394,6 +30514,9 @@ function formatWorkspaceModeLine(result) {
|
|
|
30394
30514
|
}
|
|
30395
30515
|
|
|
30396
30516
|
// src/services/health.ts
|
|
30517
|
+
function formatDegradedConnectionLabel() {
|
|
30518
|
+
return getLastAdapterType() === "proxy" ? "connection degraded \u2014 data may be stale. Regenerate your config at https://getpapi.ai if this persists." : "connection degraded \u2014 data may be stale. Check DATABASE_URL in .mcp.json";
|
|
30519
|
+
}
|
|
30397
30520
|
function computeZoomOutWarning(cycleNumber, lastZoomOutCycle) {
|
|
30398
30521
|
if (cycleNumber <= 0) return "";
|
|
30399
30522
|
const baseline = lastZoomOutCycle > 0 ? lastZoomOutCycle : 1;
|
|
@@ -31501,7 +31624,7 @@ function formatOrientSummary(health, buildInfo, hierarchy, latestTag, projectRoo
|
|
|
31501
31624
|
lines.push("");
|
|
31502
31625
|
if (health.connectionStatus !== "offline") {
|
|
31503
31626
|
const statusIcon = health.connectionStatus === "connected" ? "\u2713" : "\u26A0\uFE0F";
|
|
31504
|
-
const statusLabel = health.connectionStatus === "connected" ? "
|
|
31627
|
+
const statusLabel = health.connectionStatus === "connected" ? "connected" : formatDegradedConnectionLabel();
|
|
31505
31628
|
lines.push(`**Connection:** ${statusIcon} ${statusLabel}`);
|
|
31506
31629
|
lines.push("");
|
|
31507
31630
|
}
|
|
@@ -32105,10 +32228,18 @@ async function handleOrient(rawAdapter, config2, args = {}, clientName, serverVe
|
|
|
32105
32228
|
const handshake = buildHandshakeResult(proxyVersion);
|
|
32106
32229
|
return handshake.mismatch && handshake.warning ? handshake.warning : void 0;
|
|
32107
32230
|
}),
|
|
32108
|
-
// P1 stall warning: P1 High tasks in Backlog that haven't moved in 3+ cycles
|
|
32231
|
+
// P1 stall warning: P1 High tasks in Backlog that haven't moved in 3+ cycles.
|
|
32232
|
+
// task-3736: the read is scoped to the CALLER (unassigned-or-mine, the same
|
|
32233
|
+
// predicate task-3397 pinned). Unscoped, a teammate's Backlog P1s were aged
|
|
32234
|
+
// against the CALLER's cycle count — a member at cycle 372 reading a
|
|
32235
|
+
// teammate's cycle-5 task as "367+ cycles" stalled. Cycle identity is
|
|
32236
|
+
// (project_id, user_id, number) since task-3335; cross-member cycle-age
|
|
32237
|
+
// arithmetic is meaningless. The caller's own unclaimed pool P1s still
|
|
32238
|
+
// count — unclaimed work is everyone's to claim, and its createdCycle is
|
|
32239
|
+
// the cycle that planned it.
|
|
32109
32240
|
tracked("p1-stall", async () => {
|
|
32110
32241
|
const p1BacklogTasks = await adapter2.queryBoard({ status: ["Backlog"], priority: ["P1 High"] });
|
|
32111
|
-
const stalledP1 = p1BacklogTasks.filter(
|
|
32242
|
+
const stalledP1 = scopeTasksToCaller(p1BacklogTasks, callerUserId).filter(
|
|
32112
32243
|
(t) => t.createdCycle != null && currentCycle2 - t.createdCycle >= 3
|
|
32113
32244
|
);
|
|
32114
32245
|
if (stalledP1.length === 0) return void 0;
|
|
@@ -36285,8 +36416,11 @@ function startHttpTransport(opts) {
|
|
|
36285
36416
|
const installClient = typeof clientHeader === "string" && KNOWN_INSTALL_CLIENTS.has(clientHeader) ? clientHeader : "direct";
|
|
36286
36417
|
const projectIdHeader = req.headers["x-papi-project-id"];
|
|
36287
36418
|
const projectId = typeof projectIdHeader === "string" && projectIdHeader.length > 0 ? projectIdHeader : void 0;
|
|
36288
|
-
if (req.method !== "POST"
|
|
36289
|
-
sendError(res, {
|
|
36419
|
+
if (req.method !== "POST") {
|
|
36420
|
+
sendError(res, {
|
|
36421
|
+
status: 405,
|
|
36422
|
+
body: { error: "method_not_allowed", reason: "server does not support server-initiated SSE; use POST" }
|
|
36423
|
+
});
|
|
36290
36424
|
return;
|
|
36291
36425
|
}
|
|
36292
36426
|
const chunks = [];
|
|
@@ -36567,6 +36701,12 @@ async function dispatchRequest(args) {
|
|
|
36567
36701
|
}
|
|
36568
36702
|
}
|
|
36569
36703
|
|
|
36704
|
+
// src/lib/version-check.ts
|
|
36705
|
+
init_dist();
|
|
36706
|
+
function shouldCheckForUpdate(version, selfHostFlag) {
|
|
36707
|
+
return version !== "unknown" && !isSelfHostedDeployment(selfHostFlag);
|
|
36708
|
+
}
|
|
36709
|
+
|
|
36570
36710
|
// src/index.ts
|
|
36571
36711
|
init_dist();
|
|
36572
36712
|
var __dirname = dirname7(fileURLToPath5(import.meta.url));
|
|
@@ -36711,7 +36851,7 @@ If you already have an account, check that both **PAPI_PROJECT_ID** and **PAPI_D
|
|
|
36711
36851
|
}));
|
|
36712
36852
|
}
|
|
36713
36853
|
}
|
|
36714
|
-
if (pkgVersion
|
|
36854
|
+
if (shouldCheckForUpdate(pkgVersion, process.env["PAPI_SELF_HOST"])) {
|
|
36715
36855
|
(async () => {
|
|
36716
36856
|
try {
|
|
36717
36857
|
const controller = new AbortController();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.108",
|
|
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",
|