@papi-ai/server 0.7.105 → 0.7.106
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 +30 -24
- package/dist/index.js +80 -39
- package/package.json +1 -1
|
@@ -1908,11 +1908,13 @@ var init_proxy_adapter = __esm({
|
|
|
1908
1908
|
endpoint;
|
|
1909
1909
|
apiKey;
|
|
1910
1910
|
projectId;
|
|
1911
|
+
papiDir;
|
|
1911
1912
|
onAuthRejected;
|
|
1912
1913
|
constructor(config) {
|
|
1913
1914
|
this.endpoint = config.endpoint.replace(/\/$/, "");
|
|
1914
1915
|
this.apiKey = config.apiKey;
|
|
1915
1916
|
this.projectId = config.projectId ?? "";
|
|
1917
|
+
this.papiDir = config.papiDir ?? null;
|
|
1916
1918
|
this.onAuthRejected = config.onAuthRejected;
|
|
1917
1919
|
}
|
|
1918
1920
|
/**
|
|
@@ -1988,6 +1990,7 @@ var init_proxy_adapter = __esm({
|
|
|
1988
1990
|
endpoint: this.endpoint,
|
|
1989
1991
|
apiKey: this.apiKey,
|
|
1990
1992
|
projectId,
|
|
1993
|
+
papiDir: this.papiDir,
|
|
1991
1994
|
onAuthRejected: this.onAuthRejected
|
|
1992
1995
|
}));
|
|
1993
1996
|
}
|
|
@@ -2620,7 +2623,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2620
2623
|
}
|
|
2621
2624
|
// --- Project metadata (path-identity guardrail) ---
|
|
2622
2625
|
async getProjectInfo() {
|
|
2623
|
-
const raw = await this.invoke("getProjectInfo", []);
|
|
2626
|
+
const raw = await this.invoke("getProjectInfo", [this.papiDir]);
|
|
2624
2627
|
if (!raw) return null;
|
|
2625
2628
|
return {
|
|
2626
2629
|
name: raw.name,
|
|
@@ -2707,7 +2710,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2707
2710
|
};
|
|
2708
2711
|
}
|
|
2709
2712
|
async listUserProjects() {
|
|
2710
|
-
const body = await this.postRoute("project-list", {
|
|
2713
|
+
const body = await this.postRoute("project-list", {
|
|
2714
|
+
...this.papiDir ? { papiDir: this.papiDir } : {}
|
|
2715
|
+
});
|
|
2711
2716
|
return body.projects ?? [];
|
|
2712
2717
|
}
|
|
2713
2718
|
async createUserProject(input) {
|
|
@@ -2966,6 +2971,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2966
2971
|
);
|
|
2967
2972
|
}
|
|
2968
2973
|
const pgAdapter = new PgAdapter(config);
|
|
2974
|
+
let projectCreated = false;
|
|
2969
2975
|
try {
|
|
2970
2976
|
const existing = await pgAdapter.getProject(projectId);
|
|
2971
2977
|
if (!existing) {
|
|
@@ -2997,12 +3003,12 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2997
3003
|
id: projectId,
|
|
2998
3004
|
slug,
|
|
2999
3005
|
name: slug,
|
|
3000
|
-
papi_dir: papiDir,
|
|
3001
3006
|
user_id: userId,
|
|
3002
3007
|
root_commit_hash: rootHash ?? void 0,
|
|
3003
3008
|
repo_url: originUrl ?? void 0,
|
|
3004
3009
|
resolution_method: rootHash ? "root_hash" : "manual"
|
|
3005
3010
|
});
|
|
3011
|
+
projectCreated = true;
|
|
3006
3012
|
}
|
|
3007
3013
|
} else {
|
|
3008
3014
|
if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
|
|
@@ -3023,25 +3029,6 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
3023
3029
|
);
|
|
3024
3030
|
}
|
|
3025
3031
|
}
|
|
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
3032
|
if (existing.user_id) {
|
|
3046
3033
|
const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
|
|
3047
3034
|
if (configuredUserId && existing.user_id !== configuredUserId) {
|
|
@@ -3060,11 +3047,29 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
3060
3047
|
} catch {
|
|
3061
3048
|
}
|
|
3062
3049
|
}
|
|
3063
|
-
const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId }, projectId);
|
|
3050
|
+
const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId, papiDir }, projectId);
|
|
3064
3051
|
try {
|
|
3065
3052
|
await adapter.initRls();
|
|
3066
3053
|
} catch {
|
|
3067
3054
|
}
|
|
3055
|
+
if (projectCreated && papiDir && adapter.setProjectPapiDir) {
|
|
3056
|
+
await adapter.setProjectPapiDir(papiDir);
|
|
3057
|
+
}
|
|
3058
|
+
if (!_pathIdentityChecked && adapter.getProjectInfo && adapter.setProjectPapiDir) {
|
|
3059
|
+
_pathIdentityChecked = true;
|
|
3060
|
+
const info = await adapter.getProjectInfo();
|
|
3061
|
+
if (info) {
|
|
3062
|
+
const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
|
|
3063
|
+
const result = assertWorkspaceMatch({
|
|
3064
|
+
storedPapiDir: info.papi_dir,
|
|
3065
|
+
projectName: info.name || info.slug || projectId,
|
|
3066
|
+
workspacePath: cwd
|
|
3067
|
+
});
|
|
3068
|
+
if (result && (result.action === "backfill" || result.action === "migrate")) {
|
|
3069
|
+
await adapter.setProjectPapiDir(result.newPapiDir);
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3068
3073
|
const connected = await adapter.probeConnection();
|
|
3069
3074
|
if (connected) {
|
|
3070
3075
|
_connectionStatus = "connected";
|
|
@@ -3103,7 +3108,8 @@ Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json en
|
|
|
3103
3108
|
const adapter = new ProxyPapiAdapter2({
|
|
3104
3109
|
endpoint: dataEndpoint,
|
|
3105
3110
|
apiKey: dataApiKey,
|
|
3106
|
-
projectId: projectId || void 0
|
|
3111
|
+
projectId: projectId || void 0,
|
|
3112
|
+
papiDir
|
|
3107
3113
|
});
|
|
3108
3114
|
const connected = await adapter.probeConnection();
|
|
3109
3115
|
if (!connected) {
|
package/dist/index.js
CHANGED
|
@@ -2744,11 +2744,13 @@ var init_proxy_adapter = __esm({
|
|
|
2744
2744
|
endpoint;
|
|
2745
2745
|
apiKey;
|
|
2746
2746
|
projectId;
|
|
2747
|
+
papiDir;
|
|
2747
2748
|
onAuthRejected;
|
|
2748
2749
|
constructor(config2) {
|
|
2749
2750
|
this.endpoint = config2.endpoint.replace(/\/$/, "");
|
|
2750
2751
|
this.apiKey = config2.apiKey;
|
|
2751
2752
|
this.projectId = config2.projectId ?? "";
|
|
2753
|
+
this.papiDir = config2.papiDir ?? null;
|
|
2752
2754
|
this.onAuthRejected = config2.onAuthRejected;
|
|
2753
2755
|
}
|
|
2754
2756
|
/**
|
|
@@ -2824,6 +2826,7 @@ var init_proxy_adapter = __esm({
|
|
|
2824
2826
|
endpoint: this.endpoint,
|
|
2825
2827
|
apiKey: this.apiKey,
|
|
2826
2828
|
projectId,
|
|
2829
|
+
papiDir: this.papiDir,
|
|
2827
2830
|
onAuthRejected: this.onAuthRejected
|
|
2828
2831
|
}));
|
|
2829
2832
|
}
|
|
@@ -3456,7 +3459,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
3456
3459
|
}
|
|
3457
3460
|
// --- Project metadata (path-identity guardrail) ---
|
|
3458
3461
|
async getProjectInfo() {
|
|
3459
|
-
const raw = await this.invoke("getProjectInfo", []);
|
|
3462
|
+
const raw = await this.invoke("getProjectInfo", [this.papiDir]);
|
|
3460
3463
|
if (!raw) return null;
|
|
3461
3464
|
return {
|
|
3462
3465
|
name: raw.name,
|
|
@@ -3543,7 +3546,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
3543
3546
|
};
|
|
3544
3547
|
}
|
|
3545
3548
|
async listUserProjects() {
|
|
3546
|
-
const body = await this.postRoute("project-list", {
|
|
3549
|
+
const body = await this.postRoute("project-list", {
|
|
3550
|
+
...this.papiDir ? { papiDir: this.papiDir } : {}
|
|
3551
|
+
});
|
|
3547
3552
|
return body.projects ?? [];
|
|
3548
3553
|
}
|
|
3549
3554
|
async createUserProject(input) {
|
|
@@ -7290,6 +7295,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7290
7295
|
);
|
|
7291
7296
|
}
|
|
7292
7297
|
const pgAdapter = new PgAdapter(config2);
|
|
7298
|
+
let projectCreated = false;
|
|
7293
7299
|
try {
|
|
7294
7300
|
const existing = await pgAdapter.getProject(projectId);
|
|
7295
7301
|
if (!existing) {
|
|
@@ -7321,12 +7327,12 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7321
7327
|
id: projectId,
|
|
7322
7328
|
slug,
|
|
7323
7329
|
name: slug,
|
|
7324
|
-
papi_dir: papiDir,
|
|
7325
7330
|
user_id: userId,
|
|
7326
7331
|
root_commit_hash: rootHash ?? void 0,
|
|
7327
7332
|
repo_url: originUrl ?? void 0,
|
|
7328
7333
|
resolution_method: rootHash ? "root_hash" : "manual"
|
|
7329
7334
|
});
|
|
7335
|
+
projectCreated = true;
|
|
7330
7336
|
}
|
|
7331
7337
|
} else {
|
|
7332
7338
|
if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
|
|
@@ -7347,25 +7353,6 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7347
7353
|
);
|
|
7348
7354
|
}
|
|
7349
7355
|
}
|
|
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
7356
|
if (existing.user_id) {
|
|
7370
7357
|
const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
|
|
7371
7358
|
if (configuredUserId && existing.user_id !== configuredUserId) {
|
|
@@ -7384,11 +7371,29 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7384
7371
|
} catch {
|
|
7385
7372
|
}
|
|
7386
7373
|
}
|
|
7387
|
-
const adapter2 = new PgPapiAdapter({ ...config2, userId: resolveUserId }, projectId);
|
|
7374
|
+
const adapter2 = new PgPapiAdapter({ ...config2, userId: resolveUserId, papiDir }, projectId);
|
|
7388
7375
|
try {
|
|
7389
7376
|
await adapter2.initRls();
|
|
7390
7377
|
} catch {
|
|
7391
7378
|
}
|
|
7379
|
+
if (projectCreated && papiDir && adapter2.setProjectPapiDir) {
|
|
7380
|
+
await adapter2.setProjectPapiDir(papiDir);
|
|
7381
|
+
}
|
|
7382
|
+
if (!_pathIdentityChecked && adapter2.getProjectInfo && adapter2.setProjectPapiDir) {
|
|
7383
|
+
_pathIdentityChecked = true;
|
|
7384
|
+
const info = await adapter2.getProjectInfo();
|
|
7385
|
+
if (info) {
|
|
7386
|
+
const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
|
|
7387
|
+
const result = assertWorkspaceMatch({
|
|
7388
|
+
storedPapiDir: info.papi_dir,
|
|
7389
|
+
projectName: info.name || info.slug || projectId,
|
|
7390
|
+
workspacePath: cwd
|
|
7391
|
+
});
|
|
7392
|
+
if (result && (result.action === "backfill" || result.action === "migrate")) {
|
|
7393
|
+
await adapter2.setProjectPapiDir(result.newPapiDir);
|
|
7394
|
+
}
|
|
7395
|
+
}
|
|
7396
|
+
}
|
|
7392
7397
|
const connected = await adapter2.probeConnection();
|
|
7393
7398
|
if (connected) {
|
|
7394
7399
|
_connectionStatus = "connected";
|
|
@@ -7427,7 +7432,8 @@ Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json en
|
|
|
7427
7432
|
const adapter2 = new ProxyPapiAdapter2({
|
|
7428
7433
|
endpoint: dataEndpoint,
|
|
7429
7434
|
apiKey: dataApiKey,
|
|
7430
|
-
projectId: projectId || void 0
|
|
7435
|
+
projectId: projectId || void 0,
|
|
7436
|
+
papiDir
|
|
7431
7437
|
});
|
|
7432
7438
|
const connected = await adapter2.probeConnection();
|
|
7433
7439
|
if (!connected) {
|
|
@@ -7556,21 +7562,27 @@ import {
|
|
|
7556
7562
|
// src/universal-frame.ts
|
|
7557
7563
|
var UNIVERSAL_FRAME = `PAPI gives this project a structured plan \u2192 build \u2192 review cycle, persisted across sessions. Follow it:
|
|
7558
7564
|
|
|
7559
|
-
|
|
7565
|
+
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.
|
|
7566
|
+
|
|
7567
|
+
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.
|
|
7568
|
+
|
|
7569
|
+
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
7570
|
|
|
7561
|
-
|
|
7571
|
+
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
7572
|
|
|
7563
|
-
|
|
7573
|
+
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
7574
|
|
|
7565
|
-
|
|
7575
|
+
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
7576
|
|
|
7567
|
-
|
|
7577
|
+
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
7578
|
|
|
7569
|
-
|
|
7579
|
+
6. VERIFY BEFORE DONE. Test the change and confirm it works before reporting a task complete. Report failures honestly, with the output.
|
|
7570
7580
|
|
|
7571
|
-
|
|
7581
|
+
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
7582
|
|
|
7573
|
-
PAPI reads and writes all project state through these tools \u2014 they are the source of truth, not local files
|
|
7583
|
+
PAPI reads and writes all project state through these tools \u2014 they are the source of truth, not local files.
|
|
7584
|
+
|
|
7585
|
+
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
7586
|
|
|
7575
7587
|
// src/lib/response.ts
|
|
7576
7588
|
function textResponse(text, usage) {
|
|
@@ -12805,6 +12817,13 @@ async function resolveCallerLatestCycle(adapter2, callerUserId) {
|
|
|
12805
12817
|
}
|
|
12806
12818
|
return newest;
|
|
12807
12819
|
}
|
|
12820
|
+
function unownedCycleNumbers(cycles) {
|
|
12821
|
+
const unowned = /* @__PURE__ */ new Set();
|
|
12822
|
+
for (const c of cycles) {
|
|
12823
|
+
if (c.userId == null) unowned.add(c.number);
|
|
12824
|
+
}
|
|
12825
|
+
return unowned;
|
|
12826
|
+
}
|
|
12808
12827
|
async function validateAndPrepare(adapter2, force, callerUserId, adapterType) {
|
|
12809
12828
|
let mode;
|
|
12810
12829
|
let cycleNumber;
|
|
@@ -12829,23 +12848,37 @@ Run \`release\` first, or pass \`force: true\` to bypass this block.`
|
|
|
12829
12848
|
);
|
|
12830
12849
|
}
|
|
12831
12850
|
if (!force) {
|
|
12851
|
+
const callerCycleNumbers = callerUserId ? (await adapter2.readCycles()).filter((c) => c.userId === callerUserId).map((c) => c.number) : null;
|
|
12832
12852
|
const inReviewTasks = await adapter2.queryBoard({
|
|
12833
12853
|
status: ["In Review"],
|
|
12834
12854
|
compact: true
|
|
12835
12855
|
});
|
|
12836
|
-
const
|
|
12837
|
-
(t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2
|
|
12856
|
+
const staleMine = inReviewTasks.filter(
|
|
12857
|
+
(t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2 && (callerCycleNumbers === null || callerCycleNumbers.includes(t.cycle))
|
|
12838
12858
|
);
|
|
12839
|
-
if (
|
|
12840
|
-
const taskList =
|
|
12859
|
+
if (staleMine.length > 0) {
|
|
12860
|
+
const taskList = staleMine.map((t) => `- ${t.id} (Cycle ${t.cycle}): ${t.title}`).join("\n");
|
|
12841
12861
|
throw new Error(
|
|
12842
|
-
`Stale reviews detected \u2014 ${
|
|
12862
|
+
`Stale reviews detected \u2014 ${staleMine.length} task(s) have been In Review for 2+ cycles:
|
|
12843
12863
|
|
|
12844
12864
|
${taskList}
|
|
12845
12865
|
|
|
12846
12866
|
Run \`review_submit\` to clear them, or pass \`force: true\` to bypass this block.`
|
|
12847
12867
|
);
|
|
12848
12868
|
}
|
|
12869
|
+
if (callerCycleNumbers !== null) {
|
|
12870
|
+
const unowned = unownedCycleNumbers(await adapter2.readCycles());
|
|
12871
|
+
const staleTheirs = inReviewTasks.filter(
|
|
12872
|
+
(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 —
|
|
12873
|
+
// do not attribute it to a teammate either.
|
|
12874
|
+
!unowned.has(t.cycle)
|
|
12875
|
+
);
|
|
12876
|
+
if (staleTheirs.length > 0) {
|
|
12877
|
+
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.
|
|
12878
|
+
|
|
12879
|
+
`;
|
|
12880
|
+
}
|
|
12881
|
+
}
|
|
12849
12882
|
}
|
|
12850
12883
|
const autoCompleted = await assertSingleActiveCycle(adapter2, { autoComplete: force === true, userId: callerUserId ?? void 0 });
|
|
12851
12884
|
for (const note of autoCompleted) {
|
|
@@ -32105,10 +32138,18 @@ async function handleOrient(rawAdapter, config2, args = {}, clientName, serverVe
|
|
|
32105
32138
|
const handshake = buildHandshakeResult(proxyVersion);
|
|
32106
32139
|
return handshake.mismatch && handshake.warning ? handshake.warning : void 0;
|
|
32107
32140
|
}),
|
|
32108
|
-
// P1 stall warning: P1 High tasks in Backlog that haven't moved in 3+ cycles
|
|
32141
|
+
// P1 stall warning: P1 High tasks in Backlog that haven't moved in 3+ cycles.
|
|
32142
|
+
// task-3736: the read is scoped to the CALLER (unassigned-or-mine, the same
|
|
32143
|
+
// predicate task-3397 pinned). Unscoped, a teammate's Backlog P1s were aged
|
|
32144
|
+
// against the CALLER's cycle count — a member at cycle 372 reading a
|
|
32145
|
+
// teammate's cycle-5 task as "367+ cycles" stalled. Cycle identity is
|
|
32146
|
+
// (project_id, user_id, number) since task-3335; cross-member cycle-age
|
|
32147
|
+
// arithmetic is meaningless. The caller's own unclaimed pool P1s still
|
|
32148
|
+
// count — unclaimed work is everyone's to claim, and its createdCycle is
|
|
32149
|
+
// the cycle that planned it.
|
|
32109
32150
|
tracked("p1-stall", async () => {
|
|
32110
32151
|
const p1BacklogTasks = await adapter2.queryBoard({ status: ["Backlog"], priority: ["P1 High"] });
|
|
32111
|
-
const stalledP1 = p1BacklogTasks.filter(
|
|
32152
|
+
const stalledP1 = scopeTasksToCaller(p1BacklogTasks, callerUserId).filter(
|
|
32112
32153
|
(t) => t.createdCycle != null && currentCycle2 - t.createdCycle >= 3
|
|
32113
32154
|
);
|
|
32114
32155
|
if (stalledP1.length === 0) return void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.106",
|
|
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",
|