@papi-ai/server 0.7.104 → 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 +50 -28
- package/dist/index.js +223 -52
- package/package.json +1 -1
|
@@ -201,6 +201,7 @@ __export(git_exports, {
|
|
|
201
201
|
ensureLatestDevelop: () => ensureLatestDevelop,
|
|
202
202
|
ensureTagAtHead: () => ensureTagAtHead,
|
|
203
203
|
fetchBaseBranch: () => fetchBaseBranch,
|
|
204
|
+
findCollidingWork: () => findCollidingWork,
|
|
204
205
|
findContributorReleasePullRequests: () => findContributorReleasePullRequests,
|
|
205
206
|
findTaskCommitsOnBase: () => findTaskCommitsOnBase,
|
|
206
207
|
getBaseDivergence: () => getBaseDivergence,
|
|
@@ -1493,6 +1494,23 @@ function getRemoteBranchFiles(cwd, branch, baseBranch) {
|
|
|
1493
1494
|
return [];
|
|
1494
1495
|
}
|
|
1495
1496
|
}
|
|
1497
|
+
function findCollidingWork(cwd, baseBranch, filesLikelyTouched) {
|
|
1498
|
+
if (filesLikelyTouched.length === 0) return null;
|
|
1499
|
+
const prs = listOpenPullRequests(cwd);
|
|
1500
|
+
if (!prs || prs.length === 0) return null;
|
|
1501
|
+
const wanted = new Set(filesLikelyTouched);
|
|
1502
|
+
for (const pr of prs) {
|
|
1503
|
+
const branchFiles = getRemoteBranchFiles(cwd, pr.headRefName, baseBranch);
|
|
1504
|
+
const overlap = branchFiles.filter((f) => wanted.has(f));
|
|
1505
|
+
if (overlap.length > 0) {
|
|
1506
|
+
return {
|
|
1507
|
+
label: `PR #${pr.number} "${pr.title}" (branch \`${pr.headRefName}\`, opened by ${pr.author})`,
|
|
1508
|
+
files: overlap
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1496
1514
|
var AUTO_WRITTEN_PATHS, GIT_NETWORK_TIMEOUT_MS, MERGE_RETRY_DELAY_MS, MERGE_MAX_RETRIES, GIT_FETCH_TIMEOUT_MS;
|
|
1497
1515
|
var init_git = __esm({
|
|
1498
1516
|
"src/lib/git.ts"() {
|
|
@@ -1684,7 +1702,6 @@ var init_proxy_adapter = __esm({
|
|
|
1684
1702
|
"getBuildReportsSince",
|
|
1685
1703
|
"getContextHashes",
|
|
1686
1704
|
"getContextUtilisation",
|
|
1687
|
-
"getCostSnapshots",
|
|
1688
1705
|
"getCostSummary",
|
|
1689
1706
|
"getCurrentNorthStar",
|
|
1690
1707
|
"getCycleHealth",
|
|
@@ -1891,11 +1908,13 @@ var init_proxy_adapter = __esm({
|
|
|
1891
1908
|
endpoint;
|
|
1892
1909
|
apiKey;
|
|
1893
1910
|
projectId;
|
|
1911
|
+
papiDir;
|
|
1894
1912
|
onAuthRejected;
|
|
1895
1913
|
constructor(config) {
|
|
1896
1914
|
this.endpoint = config.endpoint.replace(/\/$/, "");
|
|
1897
1915
|
this.apiKey = config.apiKey;
|
|
1898
1916
|
this.projectId = config.projectId ?? "";
|
|
1917
|
+
this.papiDir = config.papiDir ?? null;
|
|
1899
1918
|
this.onAuthRejected = config.onAuthRejected;
|
|
1900
1919
|
}
|
|
1901
1920
|
/**
|
|
@@ -1971,6 +1990,7 @@ var init_proxy_adapter = __esm({
|
|
|
1971
1990
|
endpoint: this.endpoint,
|
|
1972
1991
|
apiKey: this.apiKey,
|
|
1973
1992
|
projectId,
|
|
1993
|
+
papiDir: this.papiDir,
|
|
1974
1994
|
onAuthRejected: this.onAuthRejected
|
|
1975
1995
|
}));
|
|
1976
1996
|
}
|
|
@@ -2338,9 +2358,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2338
2358
|
getCostSummary(cycleNumber) {
|
|
2339
2359
|
return this.invoke("getCostSummary", [cycleNumber]);
|
|
2340
2360
|
}
|
|
2341
|
-
getCostSnapshots
|
|
2342
|
-
|
|
2343
|
-
}
|
|
2361
|
+
// task-3332: getCostSnapshots retired — see the removal note on CostSnapshot
|
|
2362
|
+
// in packages/shared/src/entities.ts.
|
|
2344
2363
|
appendCycleMetrics(snapshot) {
|
|
2345
2364
|
return this.invoke("appendCycleMetrics", [snapshot]);
|
|
2346
2365
|
}
|
|
@@ -2604,7 +2623,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2604
2623
|
}
|
|
2605
2624
|
// --- Project metadata (path-identity guardrail) ---
|
|
2606
2625
|
async getProjectInfo() {
|
|
2607
|
-
const raw = await this.invoke("getProjectInfo", []);
|
|
2626
|
+
const raw = await this.invoke("getProjectInfo", [this.papiDir]);
|
|
2608
2627
|
if (!raw) return null;
|
|
2609
2628
|
return {
|
|
2610
2629
|
name: raw.name,
|
|
@@ -2691,7 +2710,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2691
2710
|
};
|
|
2692
2711
|
}
|
|
2693
2712
|
async listUserProjects() {
|
|
2694
|
-
const body = await this.postRoute("project-list", {
|
|
2713
|
+
const body = await this.postRoute("project-list", {
|
|
2714
|
+
...this.papiDir ? { papiDir: this.papiDir } : {}
|
|
2715
|
+
});
|
|
2695
2716
|
return body.projects ?? [];
|
|
2696
2717
|
}
|
|
2697
2718
|
async createUserProject(input) {
|
|
@@ -2950,6 +2971,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2950
2971
|
);
|
|
2951
2972
|
}
|
|
2952
2973
|
const pgAdapter = new PgAdapter(config);
|
|
2974
|
+
let projectCreated = false;
|
|
2953
2975
|
try {
|
|
2954
2976
|
const existing = await pgAdapter.getProject(projectId);
|
|
2955
2977
|
if (!existing) {
|
|
@@ -2981,12 +3003,12 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2981
3003
|
id: projectId,
|
|
2982
3004
|
slug,
|
|
2983
3005
|
name: slug,
|
|
2984
|
-
papi_dir: papiDir,
|
|
2985
3006
|
user_id: userId,
|
|
2986
3007
|
root_commit_hash: rootHash ?? void 0,
|
|
2987
3008
|
repo_url: originUrl ?? void 0,
|
|
2988
3009
|
resolution_method: rootHash ? "root_hash" : "manual"
|
|
2989
3010
|
});
|
|
3011
|
+
projectCreated = true;
|
|
2990
3012
|
}
|
|
2991
3013
|
} else {
|
|
2992
3014
|
if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
|
|
@@ -3007,25 +3029,6 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
3007
3029
|
);
|
|
3008
3030
|
}
|
|
3009
3031
|
}
|
|
3010
|
-
if (!_pathIdentityChecked) {
|
|
3011
|
-
_pathIdentityChecked = true;
|
|
3012
|
-
const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
|
|
3013
|
-
const projectName = existing.name ?? existing.slug ?? projectId;
|
|
3014
|
-
const result = assertWorkspaceMatch({
|
|
3015
|
-
storedPapiDir: existing.papi_dir,
|
|
3016
|
-
projectName,
|
|
3017
|
-
workspacePath: cwd
|
|
3018
|
-
});
|
|
3019
|
-
if (result && (result.action === "backfill" || result.action === "migrate")) {
|
|
3020
|
-
try {
|
|
3021
|
-
await pgAdapter.updateProject(projectId, { papi_dir: result.newPapiDir });
|
|
3022
|
-
} catch (err) {
|
|
3023
|
-
console.error(
|
|
3024
|
-
`[papi] Failed to persist papi_dir update: ${err instanceof Error ? err.message : String(err)}`
|
|
3025
|
-
);
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
3032
|
if (existing.user_id) {
|
|
3030
3033
|
const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
|
|
3031
3034
|
if (configuredUserId && existing.user_id !== configuredUserId) {
|
|
@@ -3044,11 +3047,29 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
3044
3047
|
} catch {
|
|
3045
3048
|
}
|
|
3046
3049
|
}
|
|
3047
|
-
const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId }, projectId);
|
|
3050
|
+
const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId, papiDir }, projectId);
|
|
3048
3051
|
try {
|
|
3049
3052
|
await adapter.initRls();
|
|
3050
3053
|
} catch {
|
|
3051
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
|
+
}
|
|
3052
3073
|
const connected = await adapter.probeConnection();
|
|
3053
3074
|
if (connected) {
|
|
3054
3075
|
_connectionStatus = "connected";
|
|
@@ -3087,7 +3108,8 @@ Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json en
|
|
|
3087
3108
|
const adapter = new ProxyPapiAdapter2({
|
|
3088
3109
|
endpoint: dataEndpoint,
|
|
3089
3110
|
apiKey: dataApiKey,
|
|
3090
|
-
projectId: projectId || void 0
|
|
3111
|
+
projectId: projectId || void 0,
|
|
3112
|
+
papiDir
|
|
3091
3113
|
});
|
|
3092
3114
|
const connected = await adapter.probeConnection();
|
|
3093
3115
|
if (!connected) {
|
package/dist/index.js
CHANGED
|
@@ -971,6 +971,7 @@ __export(git_exports, {
|
|
|
971
971
|
ensureLatestDevelop: () => ensureLatestDevelop,
|
|
972
972
|
ensureTagAtHead: () => ensureTagAtHead,
|
|
973
973
|
fetchBaseBranch: () => fetchBaseBranch,
|
|
974
|
+
findCollidingWork: () => findCollidingWork,
|
|
974
975
|
findContributorReleasePullRequests: () => findContributorReleasePullRequests,
|
|
975
976
|
findTaskCommitsOnBase: () => findTaskCommitsOnBase,
|
|
976
977
|
getBaseDivergence: () => getBaseDivergence,
|
|
@@ -2263,6 +2264,23 @@ function getRemoteBranchFiles(cwd, branch, baseBranch) {
|
|
|
2263
2264
|
return [];
|
|
2264
2265
|
}
|
|
2265
2266
|
}
|
|
2267
|
+
function findCollidingWork(cwd, baseBranch, filesLikelyTouched) {
|
|
2268
|
+
if (filesLikelyTouched.length === 0) return null;
|
|
2269
|
+
const prs = listOpenPullRequests(cwd);
|
|
2270
|
+
if (!prs || prs.length === 0) return null;
|
|
2271
|
+
const wanted = new Set(filesLikelyTouched);
|
|
2272
|
+
for (const pr of prs) {
|
|
2273
|
+
const branchFiles = getRemoteBranchFiles(cwd, pr.headRefName, baseBranch);
|
|
2274
|
+
const overlap = branchFiles.filter((f) => wanted.has(f));
|
|
2275
|
+
if (overlap.length > 0) {
|
|
2276
|
+
return {
|
|
2277
|
+
label: `PR #${pr.number} "${pr.title}" (branch \`${pr.headRefName}\`, opened by ${pr.author})`,
|
|
2278
|
+
files: overlap
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
return null;
|
|
2283
|
+
}
|
|
2266
2284
|
var AUTO_WRITTEN_PATHS, GIT_NETWORK_TIMEOUT_MS, MERGE_RETRY_DELAY_MS, MERGE_MAX_RETRIES, GIT_FETCH_TIMEOUT_MS;
|
|
2267
2285
|
var init_git = __esm({
|
|
2268
2286
|
"src/lib/git.ts"() {
|
|
@@ -2520,7 +2538,6 @@ var init_proxy_adapter = __esm({
|
|
|
2520
2538
|
"getBuildReportsSince",
|
|
2521
2539
|
"getContextHashes",
|
|
2522
2540
|
"getContextUtilisation",
|
|
2523
|
-
"getCostSnapshots",
|
|
2524
2541
|
"getCostSummary",
|
|
2525
2542
|
"getCurrentNorthStar",
|
|
2526
2543
|
"getCycleHealth",
|
|
@@ -2727,11 +2744,13 @@ var init_proxy_adapter = __esm({
|
|
|
2727
2744
|
endpoint;
|
|
2728
2745
|
apiKey;
|
|
2729
2746
|
projectId;
|
|
2747
|
+
papiDir;
|
|
2730
2748
|
onAuthRejected;
|
|
2731
2749
|
constructor(config2) {
|
|
2732
2750
|
this.endpoint = config2.endpoint.replace(/\/$/, "");
|
|
2733
2751
|
this.apiKey = config2.apiKey;
|
|
2734
2752
|
this.projectId = config2.projectId ?? "";
|
|
2753
|
+
this.papiDir = config2.papiDir ?? null;
|
|
2735
2754
|
this.onAuthRejected = config2.onAuthRejected;
|
|
2736
2755
|
}
|
|
2737
2756
|
/**
|
|
@@ -2807,6 +2826,7 @@ var init_proxy_adapter = __esm({
|
|
|
2807
2826
|
endpoint: this.endpoint,
|
|
2808
2827
|
apiKey: this.apiKey,
|
|
2809
2828
|
projectId,
|
|
2829
|
+
papiDir: this.papiDir,
|
|
2810
2830
|
onAuthRejected: this.onAuthRejected
|
|
2811
2831
|
}));
|
|
2812
2832
|
}
|
|
@@ -3174,9 +3194,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
3174
3194
|
getCostSummary(cycleNumber) {
|
|
3175
3195
|
return this.invoke("getCostSummary", [cycleNumber]);
|
|
3176
3196
|
}
|
|
3177
|
-
getCostSnapshots
|
|
3178
|
-
|
|
3179
|
-
}
|
|
3197
|
+
// task-3332: getCostSnapshots retired — see the removal note on CostSnapshot
|
|
3198
|
+
// in packages/shared/src/entities.ts.
|
|
3180
3199
|
appendCycleMetrics(snapshot) {
|
|
3181
3200
|
return this.invoke("appendCycleMetrics", [snapshot]);
|
|
3182
3201
|
}
|
|
@@ -3440,7 +3459,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
3440
3459
|
}
|
|
3441
3460
|
// --- Project metadata (path-identity guardrail) ---
|
|
3442
3461
|
async getProjectInfo() {
|
|
3443
|
-
const raw = await this.invoke("getProjectInfo", []);
|
|
3462
|
+
const raw = await this.invoke("getProjectInfo", [this.papiDir]);
|
|
3444
3463
|
if (!raw) return null;
|
|
3445
3464
|
return {
|
|
3446
3465
|
name: raw.name,
|
|
@@ -3527,7 +3546,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
3527
3546
|
};
|
|
3528
3547
|
}
|
|
3529
3548
|
async listUserProjects() {
|
|
3530
|
-
const body = await this.postRoute("project-list", {
|
|
3549
|
+
const body = await this.postRoute("project-list", {
|
|
3550
|
+
...this.papiDir ? { papiDir: this.papiDir } : {}
|
|
3551
|
+
});
|
|
3531
3552
|
return body.projects ?? [];
|
|
3532
3553
|
}
|
|
3533
3554
|
async createUserProject(input) {
|
|
@@ -7274,6 +7295,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7274
7295
|
);
|
|
7275
7296
|
}
|
|
7276
7297
|
const pgAdapter = new PgAdapter(config2);
|
|
7298
|
+
let projectCreated = false;
|
|
7277
7299
|
try {
|
|
7278
7300
|
const existing = await pgAdapter.getProject(projectId);
|
|
7279
7301
|
if (!existing) {
|
|
@@ -7305,12 +7327,12 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7305
7327
|
id: projectId,
|
|
7306
7328
|
slug,
|
|
7307
7329
|
name: slug,
|
|
7308
|
-
papi_dir: papiDir,
|
|
7309
7330
|
user_id: userId,
|
|
7310
7331
|
root_commit_hash: rootHash ?? void 0,
|
|
7311
7332
|
repo_url: originUrl ?? void 0,
|
|
7312
7333
|
resolution_method: rootHash ? "root_hash" : "manual"
|
|
7313
7334
|
});
|
|
7335
|
+
projectCreated = true;
|
|
7314
7336
|
}
|
|
7315
7337
|
} else {
|
|
7316
7338
|
if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
|
|
@@ -7331,25 +7353,6 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7331
7353
|
);
|
|
7332
7354
|
}
|
|
7333
7355
|
}
|
|
7334
|
-
if (!_pathIdentityChecked) {
|
|
7335
|
-
_pathIdentityChecked = true;
|
|
7336
|
-
const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
|
|
7337
|
-
const projectName = existing.name ?? existing.slug ?? projectId;
|
|
7338
|
-
const result = assertWorkspaceMatch({
|
|
7339
|
-
storedPapiDir: existing.papi_dir,
|
|
7340
|
-
projectName,
|
|
7341
|
-
workspacePath: cwd
|
|
7342
|
-
});
|
|
7343
|
-
if (result && (result.action === "backfill" || result.action === "migrate")) {
|
|
7344
|
-
try {
|
|
7345
|
-
await pgAdapter.updateProject(projectId, { papi_dir: result.newPapiDir });
|
|
7346
|
-
} catch (err) {
|
|
7347
|
-
console.error(
|
|
7348
|
-
`[papi] Failed to persist papi_dir update: ${err instanceof Error ? err.message : String(err)}`
|
|
7349
|
-
);
|
|
7350
|
-
}
|
|
7351
|
-
}
|
|
7352
|
-
}
|
|
7353
7356
|
if (existing.user_id) {
|
|
7354
7357
|
const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
|
|
7355
7358
|
if (configuredUserId && existing.user_id !== configuredUserId) {
|
|
@@ -7368,11 +7371,29 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
7368
7371
|
} catch {
|
|
7369
7372
|
}
|
|
7370
7373
|
}
|
|
7371
|
-
const adapter2 = new PgPapiAdapter({ ...config2, userId: resolveUserId }, projectId);
|
|
7374
|
+
const adapter2 = new PgPapiAdapter({ ...config2, userId: resolveUserId, papiDir }, projectId);
|
|
7372
7375
|
try {
|
|
7373
7376
|
await adapter2.initRls();
|
|
7374
7377
|
} catch {
|
|
7375
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
|
+
}
|
|
7376
7397
|
const connected = await adapter2.probeConnection();
|
|
7377
7398
|
if (connected) {
|
|
7378
7399
|
_connectionStatus = "connected";
|
|
@@ -7411,7 +7432,8 @@ Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json en
|
|
|
7411
7432
|
const adapter2 = new ProxyPapiAdapter2({
|
|
7412
7433
|
endpoint: dataEndpoint,
|
|
7413
7434
|
apiKey: dataApiKey,
|
|
7414
|
-
projectId: projectId || void 0
|
|
7435
|
+
projectId: projectId || void 0,
|
|
7436
|
+
papiDir
|
|
7415
7437
|
});
|
|
7416
7438
|
const connected = await adapter2.probeConnection();
|
|
7417
7439
|
if (!connected) {
|
|
@@ -7540,21 +7562,27 @@ import {
|
|
|
7540
7562
|
// src/universal-frame.ts
|
|
7541
7563
|
var UNIVERSAL_FRAME = `PAPI gives this project a structured plan \u2192 build \u2192 review cycle, persisted across sessions. Follow it:
|
|
7542
7564
|
|
|
7543
|
-
|
|
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.
|
|
7544
7570
|
|
|
7545
|
-
|
|
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.
|
|
7546
7572
|
|
|
7547
|
-
|
|
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.
|
|
7548
7574
|
|
|
7549
|
-
|
|
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.
|
|
7550
7576
|
|
|
7551
|
-
|
|
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.
|
|
7552
7578
|
|
|
7553
|
-
|
|
7579
|
+
6. VERIFY BEFORE DONE. Test the change and confirm it works before reporting a task complete. Report failures honestly, with the output.
|
|
7554
7580
|
|
|
7555
|
-
|
|
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.
|
|
7556
7582
|
|
|
7557
|
-
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.`;
|
|
7558
7586
|
|
|
7559
7587
|
// src/lib/response.ts
|
|
7560
7588
|
function textResponse(text, usage) {
|
|
@@ -12789,6 +12817,13 @@ async function resolveCallerLatestCycle(adapter2, callerUserId) {
|
|
|
12789
12817
|
}
|
|
12790
12818
|
return newest;
|
|
12791
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
|
+
}
|
|
12792
12827
|
async function validateAndPrepare(adapter2, force, callerUserId, adapterType) {
|
|
12793
12828
|
let mode;
|
|
12794
12829
|
let cycleNumber;
|
|
@@ -12813,23 +12848,37 @@ Run \`release\` first, or pass \`force: true\` to bypass this block.`
|
|
|
12813
12848
|
);
|
|
12814
12849
|
}
|
|
12815
12850
|
if (!force) {
|
|
12851
|
+
const callerCycleNumbers = callerUserId ? (await adapter2.readCycles()).filter((c) => c.userId === callerUserId).map((c) => c.number) : null;
|
|
12816
12852
|
const inReviewTasks = await adapter2.queryBoard({
|
|
12817
12853
|
status: ["In Review"],
|
|
12818
12854
|
compact: true
|
|
12819
12855
|
});
|
|
12820
|
-
const
|
|
12821
|
-
(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))
|
|
12822
12858
|
);
|
|
12823
|
-
if (
|
|
12824
|
-
const taskList =
|
|
12859
|
+
if (staleMine.length > 0) {
|
|
12860
|
+
const taskList = staleMine.map((t) => `- ${t.id} (Cycle ${t.cycle}): ${t.title}`).join("\n");
|
|
12825
12861
|
throw new Error(
|
|
12826
|
-
`Stale reviews detected \u2014 ${
|
|
12862
|
+
`Stale reviews detected \u2014 ${staleMine.length} task(s) have been In Review for 2+ cycles:
|
|
12827
12863
|
|
|
12828
12864
|
${taskList}
|
|
12829
12865
|
|
|
12830
12866
|
Run \`review_submit\` to clear them, or pass \`force: true\` to bypass this block.`
|
|
12831
12867
|
);
|
|
12832
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
|
+
}
|
|
12833
12882
|
}
|
|
12834
12883
|
const autoCompleted = await assertSingleActiveCycle(adapter2, { autoComplete: force === true, userId: callerUserId ?? void 0 });
|
|
12835
12884
|
for (const note of autoCompleted) {
|
|
@@ -27762,7 +27811,7 @@ function resolveAdHocCycle(cycle, latest, latestComplete) {
|
|
|
27762
27811
|
if (cycle === "current") return latest;
|
|
27763
27812
|
return latestComplete ? latest + 1 : latest;
|
|
27764
27813
|
}
|
|
27765
|
-
async function recordAdHoc(adapter2, input) {
|
|
27814
|
+
async function recordAdHoc(adapter2, config2, input) {
|
|
27766
27815
|
const [health, phases] = await Promise.all([
|
|
27767
27816
|
adapter2.getCycleHealth(),
|
|
27768
27817
|
adapter2.readPhases()
|
|
@@ -27784,9 +27833,16 @@ async function recordAdHoc(adapter2, input) {
|
|
|
27784
27833
|
if (!existing) {
|
|
27785
27834
|
throw new Error(`Task "${input.taskId}" not found on the board. Check the task ID and try again.`);
|
|
27786
27835
|
}
|
|
27787
|
-
if (
|
|
27836
|
+
if (existing.assigneeId) {
|
|
27837
|
+
const gate = await resolveOwnerGate(adapter2, config2);
|
|
27838
|
+
if (gate.enforced && (!gate.callerUserId || gate.callerUserId !== existing.assigneeId)) {
|
|
27839
|
+
throw new Error(
|
|
27840
|
+
`Task "${input.taskId}" (${existing.title}) is claimed by another member \u2014 ad-hoc work cannot be recorded against it. Have the claimer record it, or run \`task_unclaim\` to release it first.`
|
|
27841
|
+
);
|
|
27842
|
+
}
|
|
27843
|
+
} else {
|
|
27788
27844
|
warnings.push(
|
|
27789
|
-
`\u2139\uFE0F ${input.taskId} has no assignee \u2014 recording ad-hoc work against an unclaimed pool task. If someone else might be building it too, check for an open PR/branch on the same files before merging.`
|
|
27845
|
+
`\u2139\uFE0F ${input.taskId} has no assignee \u2014 recording ad-hoc work against an unclaimed pool task. ` + (input.collidingWorkNote ?? `If someone else might be building it too, check for an open PR/branch on the same files before merging.`)
|
|
27790
27846
|
);
|
|
27791
27847
|
}
|
|
27792
27848
|
const updatePayload = {
|
|
@@ -28043,7 +28099,22 @@ async function handleAdHoc(adapter2, config2, args) {
|
|
|
28043
28099
|
const gitUsable = !overrideNote && isGitAvailable() && isGitRepo(config2.projectRoot);
|
|
28044
28100
|
const currentBranch = gitUsable ? getCurrentBranch(config2.projectRoot) : null;
|
|
28045
28101
|
const baseBranch = gitUsable ? resolveBaseBranch(config2.projectRoot, config2.baseBranch) : null;
|
|
28046
|
-
|
|
28102
|
+
let collidingWorkNote;
|
|
28103
|
+
if (taskId && gitUsable && baseBranch) {
|
|
28104
|
+
try {
|
|
28105
|
+
const existingTask = await target.getTask(taskId);
|
|
28106
|
+
const filesLikelyTouched = existingTask?.buildHandoff?.filesLikelyTouched ?? [];
|
|
28107
|
+
if (existingTask && !existingTask.assigneeId && filesLikelyTouched.length > 0) {
|
|
28108
|
+
const collision = findCollidingWork(config2.projectRoot, baseBranch, filesLikelyTouched);
|
|
28109
|
+
if (collision) {
|
|
28110
|
+
const fileList = collision.files.slice(0, 3).join(", ") + (collision.files.length > 3 ? ", \u2026" : "");
|
|
28111
|
+
collidingWorkNote = `${collision.label} already touches ${collision.files.length === 1 ? "the same file" : `${collision.files.length} of the same files`} (${fileList}) \u2014 check it before merging.`;
|
|
28112
|
+
}
|
|
28113
|
+
}
|
|
28114
|
+
} catch {
|
|
28115
|
+
}
|
|
28116
|
+
}
|
|
28117
|
+
const result = await recordAdHoc(target, config2, {
|
|
28047
28118
|
title: title || "",
|
|
28048
28119
|
taskId,
|
|
28049
28120
|
notes: rawNotes,
|
|
@@ -28058,7 +28129,8 @@ async function handleAdHoc(adapter2, config2, args) {
|
|
|
28058
28129
|
stage: stageArg,
|
|
28059
28130
|
hold: holdArg,
|
|
28060
28131
|
currentBranch,
|
|
28061
|
-
baseBranch
|
|
28132
|
+
baseBranch,
|
|
28133
|
+
collidingWorkNote
|
|
28062
28134
|
});
|
|
28063
28135
|
if (!holdArg && gitUsable) {
|
|
28064
28136
|
try {
|
|
@@ -29090,6 +29162,27 @@ var reviewSubmitTool = {
|
|
|
29090
29162
|
}
|
|
29091
29163
|
},
|
|
29092
29164
|
required: ["verdict", "summary", "findings"]
|
|
29165
|
+
},
|
|
29166
|
+
proposal: {
|
|
29167
|
+
type: "object",
|
|
29168
|
+
description: 'task-3388, OPTIONAL (build-acceptance + verdict:"accept" only): propose a Decision or a Convention the build or the review itself settled. Same mechanism as build_execute\u2019s `proposal` (task-3273) \u2014 PAPI never mints one for you. A proposal that passes all four admission tests is QUEUED for the owner behind a decision gate, and nothing is written as an Active Decision. YOU apply the four tests (they are stated in the planning prompts); the server routes on your answers and makes no judgement about the content. Fails "arguable today" only and it is recorded as a Convention instead, riding every future build. Fails "constrains future work" and it is returned to you with the reason, unrecorded.',
|
|
29169
|
+
properties: {
|
|
29170
|
+
title: { type: "string", description: "One line stating the stance or rule." },
|
|
29171
|
+
body: { type: "string", description: "What was decided or settled, and why." },
|
|
29172
|
+
module: { type: "string", description: "Optional module scope. Only used when this lands as a Convention." },
|
|
29173
|
+
tests: {
|
|
29174
|
+
type: "object",
|
|
29175
|
+
description: "The four admission tests, each answered explicitly. An omitted test is NOT a failed test \u2014 the proposal is refused rather than routed.",
|
|
29176
|
+
properties: {
|
|
29177
|
+
alternativesWereReal: { type: "boolean", description: "(a) Something else could genuinely have been chosen." },
|
|
29178
|
+
constrainsFutureWork: { type: "boolean", description: "(b) It changes what a task nobody has written yet will do." },
|
|
29179
|
+
arguableToday: { type: "boolean", description: "(c) A competent person could argue the other side right now." },
|
|
29180
|
+
reversalCostsMore: { type: "boolean", description: "(d) Reversing it costs more than making it did." }
|
|
29181
|
+
},
|
|
29182
|
+
required: ["alternativesWereReal", "constrainsFutureWork", "arguableToday", "reversalCostsMore"]
|
|
29183
|
+
}
|
|
29184
|
+
},
|
|
29185
|
+
required: ["title", "body", "tests"]
|
|
29093
29186
|
}
|
|
29094
29187
|
},
|
|
29095
29188
|
required: ["task_id", "stage", "verdict", "comments"],
|
|
@@ -29705,6 +29798,30 @@ Next: address the feedback, then run \`build_execute ${taskId}\` to resubmit.`;
|
|
|
29705
29798
|
}
|
|
29706
29799
|
} catch {
|
|
29707
29800
|
}
|
|
29801
|
+
let proposalNote = "";
|
|
29802
|
+
if (args.proposal !== void 0) {
|
|
29803
|
+
if (!(stage === "build-acceptance" && verdict === "accept")) {
|
|
29804
|
+
proposalNote = "\n\n---\n\n**Proposal not recorded.** `proposal` is only accepted on a build-acceptance `accept` \u2014 nothing has settled on a handoff-review or a non-accept verdict.\n\nThe review itself is unaffected \u2014 only the proposal was skipped.";
|
|
29805
|
+
} else {
|
|
29806
|
+
const validated = validateProposal(args.proposal);
|
|
29807
|
+
if ("error" in validated) {
|
|
29808
|
+
proposalNote = `
|
|
29809
|
+
|
|
29810
|
+
---
|
|
29811
|
+
|
|
29812
|
+
**Proposal not recorded.** ${validated.error}
|
|
29813
|
+
|
|
29814
|
+
The review itself is unaffected \u2014 only the proposal failed.`;
|
|
29815
|
+
} else {
|
|
29816
|
+
const routing = routeProposal(validated.proposal);
|
|
29817
|
+
const applied = await applyProposal(adapter2, routing, validated.proposal, {
|
|
29818
|
+
cycleNumber: result.currentCycle,
|
|
29819
|
+
sourceTaskId: result.taskId
|
|
29820
|
+
});
|
|
29821
|
+
proposalNote = formatProposalOutcome(routing, validated.proposal, applied);
|
|
29822
|
+
}
|
|
29823
|
+
}
|
|
29824
|
+
}
|
|
29708
29825
|
tracker.mark("format-response");
|
|
29709
29826
|
return textResponse(
|
|
29710
29827
|
`**${result.stageLabel}** recorded for ${result.taskId}.
|
|
@@ -29712,7 +29829,7 @@ Next: address the feedback, then run \`build_execute ${taskId}\` to resubmit.`;
|
|
|
29712
29829
|
- **Verdict:** ${result.verdict}
|
|
29713
29830
|
- **Comments:** ${trimForEcho(result.comments)}
|
|
29714
29831
|
|
|
29715
|
-
${statusNote}${capabilityReviewSkippedNote}${autoReviewNote}${securityNote}${adConflictNote}${unblockNote}${docClosureNote}${regenNote}${mergeNote}${overlapNote}${batchSummaryNote}${autoReleaseNote}${nextStepNote}${phaseNote}`
|
|
29832
|
+
${statusNote}${capabilityReviewSkippedNote}${autoReviewNote}${securityNote}${adConflictNote}${unblockNote}${docClosureNote}${regenNote}${mergeNote}${overlapNote}${batchSummaryNote}${autoReleaseNote}${nextStepNote}${phaseNote}${proposalNote}`
|
|
29716
29833
|
);
|
|
29717
29834
|
} catch (err) {
|
|
29718
29835
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -30746,6 +30863,41 @@ function formatUnblockSection(candidates) {
|
|
|
30746
30863
|
return lines.join("\n");
|
|
30747
30864
|
}
|
|
30748
30865
|
|
|
30866
|
+
// src/lib/decision-drafts.ts
|
|
30867
|
+
async function findPendingDecisionDrafts(adapter2) {
|
|
30868
|
+
let blockedProbe;
|
|
30869
|
+
try {
|
|
30870
|
+
blockedProbe = await adapter2.queryBoard({ status: ["Blocked"], compact: true });
|
|
30871
|
+
} catch {
|
|
30872
|
+
return [];
|
|
30873
|
+
}
|
|
30874
|
+
if (blockedProbe.length === 0) return [];
|
|
30875
|
+
const drafts = [];
|
|
30876
|
+
for (const task of blockedProbe) {
|
|
30877
|
+
const blocker = task.blocker;
|
|
30878
|
+
if (!blocker || blocker.type !== "decision-gate" || blocker.ref !== PROPOSED_DECISION_REF) continue;
|
|
30879
|
+
const evidence = (task.notes ?? "").split("\n")[0]?.trim() ?? "";
|
|
30880
|
+
drafts.push({
|
|
30881
|
+
taskId: task.displayId ?? task.id,
|
|
30882
|
+
taskTitle: task.title,
|
|
30883
|
+
evidence
|
|
30884
|
+
});
|
|
30885
|
+
}
|
|
30886
|
+
return drafts;
|
|
30887
|
+
}
|
|
30888
|
+
function formatPendingDecisionDrafts(drafts) {
|
|
30889
|
+
if (drafts.length === 0) return "";
|
|
30890
|
+
const lines = ["## Decisions Awaiting Your Call"];
|
|
30891
|
+
lines.push(
|
|
30892
|
+
`${drafts.length} decision${drafts.length === 1 ? "" : "s"} proposed during a build, waiting on you \u2014 mint with \`strategy_change\` to make it a real Active Decision, or \`board_edit <task> status:Cancelled\` to dismiss it. Neither is automatic.`
|
|
30893
|
+
);
|
|
30894
|
+
lines.push("");
|
|
30895
|
+
for (const d of drafts) {
|
|
30896
|
+
lines.push(`- **${formatRef(d.taskId, d.taskTitle)}**${d.evidence ? ` \u2014 ${d.evidence}` : ""}`);
|
|
30897
|
+
}
|
|
30898
|
+
return lines.join("\n");
|
|
30899
|
+
}
|
|
30900
|
+
|
|
30749
30901
|
// src/lib/deferred-gate.ts
|
|
30750
30902
|
var GATE_PHRASES = [
|
|
30751
30903
|
"depends on",
|
|
@@ -31986,10 +32138,18 @@ async function handleOrient(rawAdapter, config2, args = {}, clientName, serverVe
|
|
|
31986
32138
|
const handshake = buildHandshakeResult(proxyVersion);
|
|
31987
32139
|
return handshake.mismatch && handshake.warning ? handshake.warning : void 0;
|
|
31988
32140
|
}),
|
|
31989
|
-
// 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.
|
|
31990
32150
|
tracked("p1-stall", async () => {
|
|
31991
32151
|
const p1BacklogTasks = await adapter2.queryBoard({ status: ["Backlog"], priority: ["P1 High"] });
|
|
31992
|
-
const stalledP1 = p1BacklogTasks.filter(
|
|
32152
|
+
const stalledP1 = scopeTasksToCaller(p1BacklogTasks, callerUserId).filter(
|
|
31993
32153
|
(t) => t.createdCycle != null && currentCycle2 - t.createdCycle >= 3
|
|
31994
32154
|
);
|
|
31995
32155
|
if (stalledP1.length === 0) return void 0;
|
|
@@ -32288,7 +32448,8 @@ async function handleOrient(rawAdapter, config2, args = {}, clientName, serverVe
|
|
|
32288
32448
|
}
|
|
32289
32449
|
let unrecordedNote2 = "";
|
|
32290
32450
|
try {
|
|
32291
|
-
const
|
|
32451
|
+
const { compareRef } = getBaseDivergence(config2.projectRoot, config2.baseBranch, { timeoutMs: ORIGIN_FETCH_TIMEOUT_MS });
|
|
32452
|
+
const unrecorded = detectUnrecordedCommits(config2.projectRoot, compareRef);
|
|
32292
32453
|
if (unrecorded.length > 0) {
|
|
32293
32454
|
const doneTasks = await adapter2.queryBoard({ status: ["Done"] });
|
|
32294
32455
|
const adHocDoneTasks = doneTasks.filter((t) => t.cycle == null);
|
|
@@ -32417,8 +32578,14 @@ ${versionDrift}` : "";
|
|
|
32417
32578
|
}
|
|
32418
32579
|
tracker.mark("parallel-tail");
|
|
32419
32580
|
const sharedContributorsPromise = adapterSupports(adapter2, "listContributors") ? adapter2.listContributors().catch(() => []) : Promise.resolve([]);
|
|
32420
|
-
const [unblockCandidates, subAgents, teamSummaryLine, releaseHistoryLine, cohortVisibilityLine, carryForwardRefs] = await Promise.all([
|
|
32581
|
+
const [unblockCandidates, pendingDecisionDrafts, subAgents, teamSummaryLine, releaseHistoryLine, cohortVisibilityLine, carryForwardRefs] = await Promise.all([
|
|
32421
32582
|
tracked("unblock-candidates", () => findUnblockCandidates(adapter2, currentCycle2))().catch(() => []),
|
|
32583
|
+
// task-3388: pending decision-proposal drafts (build_execute/review_submit's
|
|
32584
|
+
// `proposal` param, task-3273/3388) — mandatory reader, deliberately
|
|
32585
|
+
// uncapped and separate from the housekeeping-suggestion unblock list above
|
|
32586
|
+
// (see lib/decision-drafts.ts for why). Runs on every orient call, not
|
|
32587
|
+
// gated behind deep_housekeeping — same posture as unblock-candidates.
|
|
32588
|
+
tracked("pending-decision-drafts", () => findPendingDecisionDrafts(adapter2))().catch(() => []),
|
|
32422
32589
|
// task-1866: discover project sub-agents for the orient surface (read-only, never throws).
|
|
32423
32590
|
listAgents(config2.projectRoot),
|
|
32424
32591
|
// task-2071 (MU-3) + task-2072 (MU-5): team summary + release-history — both
|
|
@@ -32437,6 +32604,10 @@ ${versionDrift}` : "";
|
|
|
32437
32604
|
const unblockNote = unblockSection ? `
|
|
32438
32605
|
|
|
32439
32606
|
${unblockSection}` : "";
|
|
32607
|
+
const decisionDraftsSection = formatPendingDecisionDrafts(pendingDecisionDrafts);
|
|
32608
|
+
const decisionDraftsNote = decisionDraftsSection ? `
|
|
32609
|
+
|
|
32610
|
+
${decisionDraftsSection}` : "";
|
|
32440
32611
|
const teamSummary = [teamSummaryLine, releaseHistoryLine, cohortVisibilityLine].filter(Boolean).join("\n") || void 0;
|
|
32441
32612
|
let deferredGateNote = "";
|
|
32442
32613
|
if (deepHousekeeping) {
|
|
@@ -32472,7 +32643,7 @@ ${formatRuntimeIdentity(getRuntimeIdentity(config2, serverVersion))}`;
|
|
|
32472
32643
|
let effStaleSkillsNote = staleSkillsNote;
|
|
32473
32644
|
let effResearchSignalsNote = researchSignalsNote;
|
|
32474
32645
|
const droppedNotes = [];
|
|
32475
|
-
const assembleOrientOutput = () => projectOverrideLine + projectBannerNote + formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment, subAgents, projectName, teamSummary, clientName, carryForwardRefs) + runtimeIdentityNote + unblockNote + deferredGateNote + feedbackResolvedNote + alertsNote + ttfvNote + reconciliationNote + mergedInProgressNote + unrecordedNote + unregisteredDocsNote + effStaleSkillsNote + effResearchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + skillProposalsNote + sessionGuidanceNote + effOnboardingCoachingNote + versionNote + enrichmentNote + deliveryShapeNote + preBuildCheckNote + effDeepHint + enrichmentFilesSection;
|
|
32646
|
+
const assembleOrientOutput = () => projectOverrideLine + projectBannerNote + formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment, subAgents, projectName, teamSummary, clientName, carryForwardRefs) + runtimeIdentityNote + unblockNote + decisionDraftsNote + deferredGateNote + feedbackResolvedNote + alertsNote + ttfvNote + reconciliationNote + mergedInProgressNote + unrecordedNote + unregisteredDocsNote + effStaleSkillsNote + effResearchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + skillProposalsNote + sessionGuidanceNote + effOnboardingCoachingNote + versionNote + enrichmentNote + deliveryShapeNote + preBuildCheckNote + effDeepHint + enrichmentFilesSection;
|
|
32476
32647
|
const orientBudgetSoft = Number(process.env.PAPI_ORIENT_CONTEXT_BUDGET) || 6e4;
|
|
32477
32648
|
let assembled = assembleOrientOutput();
|
|
32478
32649
|
if (Buffer.byteLength(assembled, "utf-8") > orientBudgetSoft && effDeepHint) {
|
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",
|