@papi-ai/server 0.7.43 → 0.7.45
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/index.js
CHANGED
|
@@ -1177,7 +1177,9 @@ var init_telemetry = __esm({
|
|
|
1177
1177
|
// src/proxy-adapter.ts
|
|
1178
1178
|
var proxy_adapter_exports = {};
|
|
1179
1179
|
__export(proxy_adapter_exports, {
|
|
1180
|
-
ProxyPapiAdapter: () => ProxyPapiAdapter
|
|
1180
|
+
ProxyPapiAdapter: () => ProxyPapiAdapter,
|
|
1181
|
+
createProxyAdapter: () => createProxyAdapter,
|
|
1182
|
+
wrapWithForwarding: () => wrapWithForwarding
|
|
1181
1183
|
});
|
|
1182
1184
|
function snakeToCamel(str) {
|
|
1183
1185
|
return str.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
@@ -1216,7 +1218,22 @@ function fixDisplayIdEntities(data) {
|
|
|
1216
1218
|
if (data && typeof data === "object") return fixDisplayIdEntity(data);
|
|
1217
1219
|
return data;
|
|
1218
1220
|
}
|
|
1219
|
-
|
|
1221
|
+
function wrapWithForwarding(instance) {
|
|
1222
|
+
return new Proxy(instance, {
|
|
1223
|
+
get(target, prop, receiver) {
|
|
1224
|
+
const existing = Reflect.get(target, prop, receiver);
|
|
1225
|
+
if (existing !== void 0) return existing;
|
|
1226
|
+
if (typeof prop !== "string" || prop === "then" || prop.startsWith("_") || NO_FORWARD.has(prop)) {
|
|
1227
|
+
return existing;
|
|
1228
|
+
}
|
|
1229
|
+
return (...args) => target.forward(prop, args);
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
function createProxyAdapter(config2) {
|
|
1234
|
+
return wrapWithForwarding(new ProxyPapiAdapter(config2));
|
|
1235
|
+
}
|
|
1236
|
+
var JSONB_PASSTHROUGH_KEYS, DISPLAY_ID_METHODS, NO_FORWARD, ProxyPapiAdapter;
|
|
1220
1237
|
var init_proxy_adapter = __esm({
|
|
1221
1238
|
"src/proxy-adapter.ts"() {
|
|
1222
1239
|
"use strict";
|
|
@@ -1240,6 +1257,30 @@ var init_proxy_adapter = __esm({
|
|
|
1240
1257
|
"getRecentReviews",
|
|
1241
1258
|
"getActiveDecisions"
|
|
1242
1259
|
]);
|
|
1260
|
+
NO_FORWARD = /* @__PURE__ */ new Set([
|
|
1261
|
+
// (1) local-only
|
|
1262
|
+
"close",
|
|
1263
|
+
"initRls",
|
|
1264
|
+
// (2) not-yet-wired hosted gaps — shrink as data-proxy handlers land (task-2390).
|
|
1265
|
+
// getToolCallCount + updatePhaseStatus are ABSENT: they now have edge handlers and
|
|
1266
|
+
// are served through the forwarder (getToolCallCount = SUP-2026-026 handler #1).
|
|
1267
|
+
"createOwnerAction",
|
|
1268
|
+
"findPendingDocActionsForTask",
|
|
1269
|
+
"getContributorRole",
|
|
1270
|
+
"getDecisionScorePatterns",
|
|
1271
|
+
"getModuleEstimationStats",
|
|
1272
|
+
"correctLatestBuildReportEffort",
|
|
1273
|
+
"recordContributorReleasePr",
|
|
1274
|
+
"setContributorReleasePrStatus",
|
|
1275
|
+
"listContributorReleasePrs",
|
|
1276
|
+
"resolveLearningsForDoneTasks",
|
|
1277
|
+
"markCycleLearningResolved",
|
|
1278
|
+
"updateStageExitCriteria",
|
|
1279
|
+
"updateDocAction",
|
|
1280
|
+
"claimReview",
|
|
1281
|
+
"getSiblingAds",
|
|
1282
|
+
"getSiblingRepoTasks"
|
|
1283
|
+
]);
|
|
1243
1284
|
ProxyPapiAdapter = class _ProxyPapiAdapter {
|
|
1244
1285
|
endpoint;
|
|
1245
1286
|
apiKey;
|
|
@@ -1261,11 +1302,11 @@ var init_proxy_adapter = __esm({
|
|
|
1261
1302
|
* data-proxy's validateProjectAccess re-checks server-side regardless.
|
|
1262
1303
|
*/
|
|
1263
1304
|
withProject(projectId) {
|
|
1264
|
-
return new _ProxyPapiAdapter({
|
|
1305
|
+
return wrapWithForwarding(new _ProxyPapiAdapter({
|
|
1265
1306
|
endpoint: this.endpoint,
|
|
1266
1307
|
apiKey: this.apiKey,
|
|
1267
1308
|
projectId
|
|
1268
|
-
});
|
|
1309
|
+
}));
|
|
1269
1310
|
}
|
|
1270
1311
|
/**
|
|
1271
1312
|
* Ensure the authenticated user has a project. If none exists, auto-creates one.
|
|
@@ -1377,6 +1418,17 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1377
1418
|
}
|
|
1378
1419
|
return result;
|
|
1379
1420
|
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Generic forward for the allowlist-driven proxy trap (SUP-2026-026). Any
|
|
1423
|
+
* PapiAdapter method with no explicit wrapper here — and not in NO_FORWARD — is
|
|
1424
|
+
* served through this single path, so a NEW adapter method needs only a data-proxy
|
|
1425
|
+
* case handler, never a hand-written wrapper. Routes through invoke() so the
|
|
1426
|
+
* transformKeys / DISPLAY_ID fixups apply identically to wrapped methods. This is
|
|
1427
|
+
* the structural cure for the pg→proxy drift that caused SUP-2026-026.
|
|
1428
|
+
*/
|
|
1429
|
+
forward(method, args) {
|
|
1430
|
+
return this.invoke(method, args);
|
|
1431
|
+
}
|
|
1380
1432
|
/** Check if the proxy is reachable. */
|
|
1381
1433
|
async probeConnection() {
|
|
1382
1434
|
try {
|
|
@@ -1937,7 +1989,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1937
1989
|
}
|
|
1938
1990
|
});
|
|
1939
1991
|
|
|
1940
|
-
//
|
|
1992
|
+
// ../../node_modules/postgres/src/query.js
|
|
1941
1993
|
function cachedError(xs) {
|
|
1942
1994
|
if (originCache.has(xs))
|
|
1943
1995
|
return originCache.get(xs);
|
|
@@ -1949,7 +2001,7 @@ function cachedError(xs) {
|
|
|
1949
2001
|
}
|
|
1950
2002
|
var originCache, originStackCache, originError, CLOSE, Query;
|
|
1951
2003
|
var init_query = __esm({
|
|
1952
|
-
"
|
|
2004
|
+
"../../node_modules/postgres/src/query.js"() {
|
|
1953
2005
|
"use strict";
|
|
1954
2006
|
originCache = /* @__PURE__ */ new Map();
|
|
1955
2007
|
originStackCache = /* @__PURE__ */ new Map();
|
|
@@ -2080,7 +2132,7 @@ var init_query = __esm({
|
|
|
2080
2132
|
}
|
|
2081
2133
|
});
|
|
2082
2134
|
|
|
2083
|
-
//
|
|
2135
|
+
// ../../node_modules/postgres/src/errors.js
|
|
2084
2136
|
function connection(x, options, socket) {
|
|
2085
2137
|
const { host, port } = socket || options;
|
|
2086
2138
|
const error = Object.assign(
|
|
@@ -2118,7 +2170,7 @@ function notSupported(x) {
|
|
|
2118
2170
|
}
|
|
2119
2171
|
var PostgresError, Errors;
|
|
2120
2172
|
var init_errors = __esm({
|
|
2121
|
-
"
|
|
2173
|
+
"../../node_modules/postgres/src/errors.js"() {
|
|
2122
2174
|
"use strict";
|
|
2123
2175
|
PostgresError = class extends Error {
|
|
2124
2176
|
constructor(x) {
|
|
@@ -2136,7 +2188,7 @@ var init_errors = __esm({
|
|
|
2136
2188
|
}
|
|
2137
2189
|
});
|
|
2138
2190
|
|
|
2139
|
-
//
|
|
2191
|
+
// ../../node_modules/postgres/src/types.js
|
|
2140
2192
|
function handleValue(x, parameters, types2, options) {
|
|
2141
2193
|
let value = x instanceof Parameter ? x.value : x;
|
|
2142
2194
|
if (value === void 0) {
|
|
@@ -2251,7 +2303,7 @@ function createJsonTransform(fn) {
|
|
|
2251
2303
|
}
|
|
2252
2304
|
var types, NotTagged, Identifier, Parameter, Builder, defaultHandlers, builders, serializers, parsers, mergeUserTypes, escapeIdentifier, inferType, escapeBackslash, escapeQuote, arraySerializer, arrayParserState, arrayParser, toCamel, toPascal, toKebab, fromCamel, fromPascal, fromKebab, camel, pascal, kebab;
|
|
2253
2305
|
var init_types = __esm({
|
|
2254
|
-
"
|
|
2306
|
+
"../../node_modules/postgres/src/types.js"() {
|
|
2255
2307
|
"use strict";
|
|
2256
2308
|
init_query();
|
|
2257
2309
|
init_errors();
|
|
@@ -2430,10 +2482,10 @@ var init_types = __esm({
|
|
|
2430
2482
|
}
|
|
2431
2483
|
});
|
|
2432
2484
|
|
|
2433
|
-
//
|
|
2485
|
+
// ../../node_modules/postgres/src/result.js
|
|
2434
2486
|
var Result;
|
|
2435
2487
|
var init_result = __esm({
|
|
2436
|
-
"
|
|
2488
|
+
"../../node_modules/postgres/src/result.js"() {
|
|
2437
2489
|
"use strict";
|
|
2438
2490
|
Result = class extends Array {
|
|
2439
2491
|
constructor() {
|
|
@@ -2453,7 +2505,7 @@ var init_result = __esm({
|
|
|
2453
2505
|
}
|
|
2454
2506
|
});
|
|
2455
2507
|
|
|
2456
|
-
//
|
|
2508
|
+
// ../../node_modules/postgres/src/queue.js
|
|
2457
2509
|
function Queue(initial = []) {
|
|
2458
2510
|
let xs = initial.slice();
|
|
2459
2511
|
let index = 0;
|
|
@@ -2480,13 +2532,13 @@ function Queue(initial = []) {
|
|
|
2480
2532
|
}
|
|
2481
2533
|
var queue_default;
|
|
2482
2534
|
var init_queue = __esm({
|
|
2483
|
-
"
|
|
2535
|
+
"../../node_modules/postgres/src/queue.js"() {
|
|
2484
2536
|
"use strict";
|
|
2485
2537
|
queue_default = Queue;
|
|
2486
2538
|
}
|
|
2487
2539
|
});
|
|
2488
2540
|
|
|
2489
|
-
//
|
|
2541
|
+
// ../../node_modules/postgres/src/bytes.js
|
|
2490
2542
|
function fit(x) {
|
|
2491
2543
|
if (buffer.length - b.i < x) {
|
|
2492
2544
|
const prev = buffer, length = prev.length;
|
|
@@ -2500,7 +2552,7 @@ function reset() {
|
|
|
2500
2552
|
}
|
|
2501
2553
|
var size, buffer, messages, b, bytes_default;
|
|
2502
2554
|
var init_bytes = __esm({
|
|
2503
|
-
"
|
|
2555
|
+
"../../node_modules/postgres/src/bytes.js"() {
|
|
2504
2556
|
"use strict";
|
|
2505
2557
|
size = 256;
|
|
2506
2558
|
buffer = Buffer.allocUnsafe(size);
|
|
@@ -2565,7 +2617,7 @@ var init_bytes = __esm({
|
|
|
2565
2617
|
}
|
|
2566
2618
|
});
|
|
2567
2619
|
|
|
2568
|
-
//
|
|
2620
|
+
// ../../node_modules/postgres/src/connection.js
|
|
2569
2621
|
import net from "net";
|
|
2570
2622
|
import tls from "tls";
|
|
2571
2623
|
import crypto2 from "crypto";
|
|
@@ -3359,7 +3411,7 @@ function timer(fn, seconds) {
|
|
|
3359
3411
|
}
|
|
3360
3412
|
var connection_default, uid, Sync, Flush, SSLRequest, ExecuteUnnamed, DescribeUnnamed, noop, retryRoutines, errorFields;
|
|
3361
3413
|
var init_connection = __esm({
|
|
3362
|
-
"
|
|
3414
|
+
"../../node_modules/postgres/src/connection.js"() {
|
|
3363
3415
|
"use strict";
|
|
3364
3416
|
init_types();
|
|
3365
3417
|
init_errors();
|
|
@@ -3422,7 +3474,7 @@ var init_connection = __esm({
|
|
|
3422
3474
|
}
|
|
3423
3475
|
});
|
|
3424
3476
|
|
|
3425
|
-
//
|
|
3477
|
+
// ../../node_modules/postgres/src/subscribe.js
|
|
3426
3478
|
function Subscribe(postgres2, options) {
|
|
3427
3479
|
const subscribers = /* @__PURE__ */ new Map(), slot = "postgresjs_" + Math.random().toString(36).slice(2), state = {};
|
|
3428
3480
|
let connection2, stream, ended = false;
|
|
@@ -3633,14 +3685,14 @@ function parseEvent(x) {
|
|
|
3633
3685
|
}
|
|
3634
3686
|
var noop2;
|
|
3635
3687
|
var init_subscribe = __esm({
|
|
3636
|
-
"
|
|
3688
|
+
"../../node_modules/postgres/src/subscribe.js"() {
|
|
3637
3689
|
"use strict";
|
|
3638
3690
|
noop2 = () => {
|
|
3639
3691
|
};
|
|
3640
3692
|
}
|
|
3641
3693
|
});
|
|
3642
3694
|
|
|
3643
|
-
//
|
|
3695
|
+
// ../../node_modules/postgres/src/large.js
|
|
3644
3696
|
import Stream2 from "stream";
|
|
3645
3697
|
function largeObject(sql, oid, mode = 131072 | 262144) {
|
|
3646
3698
|
return new Promise(async (resolve3, reject) => {
|
|
@@ -3706,12 +3758,12 @@ function largeObject(sql, oid, mode = 131072 | 262144) {
|
|
|
3706
3758
|
});
|
|
3707
3759
|
}
|
|
3708
3760
|
var init_large = __esm({
|
|
3709
|
-
"
|
|
3761
|
+
"../../node_modules/postgres/src/large.js"() {
|
|
3710
3762
|
"use strict";
|
|
3711
3763
|
}
|
|
3712
3764
|
});
|
|
3713
3765
|
|
|
3714
|
-
//
|
|
3766
|
+
// ../../node_modules/postgres/src/index.js
|
|
3715
3767
|
var src_exports = {};
|
|
3716
3768
|
__export(src_exports, {
|
|
3717
3769
|
default: () => src_default
|
|
@@ -4101,7 +4153,7 @@ function osUsername() {
|
|
|
4101
4153
|
}
|
|
4102
4154
|
var src_default;
|
|
4103
4155
|
var init_src = __esm({
|
|
4104
|
-
"
|
|
4156
|
+
"../../node_modules/postgres/src/index.js"() {
|
|
4105
4157
|
"use strict";
|
|
4106
4158
|
init_types();
|
|
4107
4159
|
init_connection();
|
|
@@ -4991,6 +5043,7 @@ var HELP_FOOTER_MD = `
|
|
|
4991
5043
|
// src/config.ts
|
|
4992
5044
|
var STRATEGY_REVIEW_OFFER_GAP = 5;
|
|
4993
5045
|
var STRATEGY_REVIEW_BLOCK_GAP = 7;
|
|
5046
|
+
var ZOOM_OUT_OFFER_GAP = 25;
|
|
4994
5047
|
function loadConfig() {
|
|
4995
5048
|
const projectArgIdx = process.argv.indexOf("--project");
|
|
4996
5049
|
const configuredRoot = projectArgIdx !== -1 ? process.argv[projectArgIdx + 1] : process.env.PAPI_PROJECT_DIR;
|
|
@@ -5090,12 +5143,12 @@ Already have an account? Make sure PAPI_USER_ID is set in your .mcp.json env con
|
|
|
5090
5143
|
import path3 from "path";
|
|
5091
5144
|
import { execSync } from "child_process";
|
|
5092
5145
|
|
|
5093
|
-
//
|
|
5146
|
+
// ../adapter-md/dist/index.js
|
|
5094
5147
|
import { readFile, writeFile, access } from "fs/promises";
|
|
5095
5148
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
5096
5149
|
import { join } from "path";
|
|
5097
5150
|
|
|
5098
|
-
//
|
|
5151
|
+
// ../shared/dist/index.js
|
|
5099
5152
|
var VALID_TRANSITIONS = {
|
|
5100
5153
|
"Backlog": ["In Cycle", "Ready", "In Progress", "Blocked", "Cancelled", "Deferred", "Done"],
|
|
5101
5154
|
"In Cycle": ["In Progress", "Backlog", "Blocked", "Cancelled"],
|
|
@@ -5114,7 +5167,7 @@ function isLiveDecision(d) {
|
|
|
5114
5167
|
return true;
|
|
5115
5168
|
}
|
|
5116
5169
|
|
|
5117
|
-
//
|
|
5170
|
+
// ../adapter-md/dist/index.js
|
|
5118
5171
|
import { randomUUID } from "crypto";
|
|
5119
5172
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5120
5173
|
import yaml from "js-yaml";
|
|
@@ -6617,14 +6670,27 @@ var MdFileAdapter = class {
|
|
|
6617
6670
|
notes: review.notes
|
|
6618
6671
|
});
|
|
6619
6672
|
}
|
|
6620
|
-
/**
|
|
6621
|
-
|
|
6673
|
+
/**
|
|
6674
|
+
* Get the cycle number of the last strategy review.
|
|
6675
|
+
* task-2416 (C315): `excludeZoomOut` is accepted for interface parity with the pg
|
|
6676
|
+
* adapter. The md adapter already matches strategy-review titles only (zoom-out logs
|
|
6677
|
+
* carry a "Zoom-Out" title, not a strategy-review one), so the flag is a no-op here —
|
|
6678
|
+
* legacy/best-effort path.
|
|
6679
|
+
*/
|
|
6680
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6681
|
+
async getLastStrategyReviewCycle(_opts) {
|
|
6622
6682
|
const log2 = await this.getCycleLog();
|
|
6623
6683
|
const entry = log2.find(
|
|
6624
6684
|
(e) => /strategy.*review|strategic.*shift/i.test(e.title)
|
|
6625
6685
|
);
|
|
6626
6686
|
return entry?.cycleNumber ?? 0;
|
|
6627
6687
|
}
|
|
6688
|
+
/** task-2416 (C315): cycle of the last zoom-out retrospective. md best-effort — matches the log title. */
|
|
6689
|
+
async getLastZoomOutCycle() {
|
|
6690
|
+
const log2 = await this.getCycleLog();
|
|
6691
|
+
const entry = log2.find((e) => /zoom[-\s]?out/i.test(e.title));
|
|
6692
|
+
return entry?.cycleNumber ?? 0;
|
|
6693
|
+
}
|
|
6628
6694
|
/** Get strategy reviews — md adapter returns empty (reviews live in cycle log). */
|
|
6629
6695
|
async getStrategyReviews(_limit, _includeFullAnalysis) {
|
|
6630
6696
|
return [];
|
|
@@ -7841,7 +7907,7 @@ async function createAdapter(optionsOrType, maybePapiDir) {
|
|
|
7841
7907
|
return adapter2;
|
|
7842
7908
|
}
|
|
7843
7909
|
case "proxy": {
|
|
7844
|
-
const { ProxyPapiAdapter: ProxyPapiAdapter2 } = await Promise.resolve().then(() => (init_proxy_adapter(), proxy_adapter_exports));
|
|
7910
|
+
const { ProxyPapiAdapter: ProxyPapiAdapter2, wrapWithForwarding: wrapWithForwarding2 } = await Promise.resolve().then(() => (init_proxy_adapter(), proxy_adapter_exports));
|
|
7845
7911
|
const dashboardUrl = process.env["PAPI_DASHBOARD_URL"] || "https://getpapi.ai";
|
|
7846
7912
|
const projectId = process.env["PAPI_PROJECT_ID"];
|
|
7847
7913
|
const dataApiKey = process.env["PAPI_DATA_API_KEY"];
|
|
@@ -7944,7 +8010,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
7944
8010
|
console.error("[papi] Set PAPI_PROJECT_ID in .mcp.json to connect to an existing project.");
|
|
7945
8011
|
}
|
|
7946
8012
|
}
|
|
7947
|
-
return adapter2;
|
|
8013
|
+
return wrapWithForwarding2(adapter2);
|
|
7948
8014
|
}
|
|
7949
8015
|
default: {
|
|
7950
8016
|
const _exhaustive = adapterType;
|
|
@@ -9060,6 +9126,16 @@ function buildPlanUserMessage(ctx) {
|
|
|
9060
9126
|
if (ctx.board) {
|
|
9061
9127
|
parts.push("### Board", "", ctx.board, "");
|
|
9062
9128
|
}
|
|
9129
|
+
if (ctx.resurfacedBlockers) {
|
|
9130
|
+
parts.push(
|
|
9131
|
+
"### Ready to Unblock (typed blockers now resolved)",
|
|
9132
|
+
"",
|
|
9133
|
+
"These tasks are Blocked but their typed dependency has cleared. Treat them as actionable candidates \u2014 resurface them into the cycle (or confirm they still belong Blocked), do not leave them sitting.",
|
|
9134
|
+
"",
|
|
9135
|
+
ctx.resurfacedBlockers,
|
|
9136
|
+
""
|
|
9137
|
+
);
|
|
9138
|
+
}
|
|
9063
9139
|
if (ctx.candidateTaskFullNotes) {
|
|
9064
9140
|
parts.push("### Full Notes for Candidate Tasks", "", ctx.candidateTaskFullNotes, "");
|
|
9065
9141
|
}
|
|
@@ -9299,6 +9375,7 @@ IMPORTANT: You are running as a non-interactive API call. Do NOT ask the user qu
|
|
|
9299
9375
|
- **Product impact first, process second.** The reader wants to know: what got better for users, what's broken, what opportunities exist. Internal machinery (AD wording, taxonomy labels, hierarchy status) is secondary \u2014 handle it in a compact appendix, not the main review.
|
|
9300
9376
|
- **Lead with insight, not data recitation.** Open each section with the strategic takeaway or pattern, THEN support it with cycle data and task references. Bad: "C131 built task-700, C132 built task-710." Good: "The last 5 cycles show a clear shift from infrastructure to user-facing work \u2014 80% of tasks were dashboard or onboarding, up from 30% in the prior review window."
|
|
9301
9377
|
- **Cycle data first, conversation context second.** Base your review on build reports, cycle logs, board state, and ADs \u2014 not on whatever was discussed earlier in the conversation. If recent conversation context conflicts with the data, flag it but trust the data.
|
|
9378
|
+
- **Verify before claiming \u2014 your context is a snapshot, not live state.** The task bodies, doc summaries, prior strategy reviews, and zoom-out framing you were given were written in the cycle they are dated; the product and codebase have moved since. Treat every present-tense claim about current product or code state \u2014 "X is broken", "Y only works in Z", "feature not built", "metric unmeasured", a stalled task "still needs building" \u2014 as a HYPOTHESIS to check, not a fact. If you have tools to inspect the live code or dashboard (grep, file read, running app), verify the claim before asserting it. If you cannot verify, attribute and hedge it explicitly ("per task-NNNN's C303 framing \u2014 unverified against current code") rather than stating it as current fact. A confident claim the reader can falsify in one grep destroys the review's credibility. **Stalled tasks are the highest-risk case:** a task sitting in Backlog for many cycles is frequently already shipped under another task and never closed \u2014 check the code before recommending you build, cancel, or defer it.
|
|
9302
9379
|
- **Be concise and scannable.** Use short paragraphs, bullet points, and clear headings. Avoid walls of text. The review should be readable in 3 minutes, not 15. Format cycle summaries as compact bullet points, not multi-paragraph narratives.
|
|
9303
9380
|
- **Every conditional section earns its place.** If a conditional section has nothing meaningful to say, skip it entirely. Do not write "No issues found" or "No concerns" \u2014 just omit the section.
|
|
9304
9381
|
- **AD housekeeping is an appendix, not the centerpiece.** Just list changes and make them. Don't score every AD individually. Don't ask for approval on wording tweaks \u2014 small changes (confidence bumps, deleting stale ADs, fixing wording) should just happen. Only flag ADs that represent a genuine strategic question.
|
|
@@ -10584,6 +10661,52 @@ function computeCarryForwardStaleness(log2, doneTaskIds) {
|
|
|
10584
10661
|
].join("\n");
|
|
10585
10662
|
}
|
|
10586
10663
|
|
|
10664
|
+
// src/lib/blocker.ts
|
|
10665
|
+
function idMatches(candidate, ref) {
|
|
10666
|
+
if (!candidate) return false;
|
|
10667
|
+
return candidate.toLowerCase() === ref.toLowerCase();
|
|
10668
|
+
}
|
|
10669
|
+
function isBlockerResolved(blocker, ctx) {
|
|
10670
|
+
if (!blocker || !blocker.ref) return false;
|
|
10671
|
+
switch (blocker.type) {
|
|
10672
|
+
case "depends-on": {
|
|
10673
|
+
const dep = ctx.tasks.find(
|
|
10674
|
+
(t) => idMatches(t.displayId, blocker.ref) || idMatches(t.id, blocker.ref)
|
|
10675
|
+
);
|
|
10676
|
+
return dep?.status === "Done";
|
|
10677
|
+
}
|
|
10678
|
+
case "owner-action": {
|
|
10679
|
+
const action = ctx.ownerActions.find((a) => idMatches(a.id, blocker.ref));
|
|
10680
|
+
return action != null && action.completed_at != null;
|
|
10681
|
+
}
|
|
10682
|
+
case "decision-gate": {
|
|
10683
|
+
const decision = ctx.decisions.find(
|
|
10684
|
+
(d) => idMatches(d.displayId, blocker.ref) || idMatches(d.id, blocker.ref)
|
|
10685
|
+
);
|
|
10686
|
+
const resolvedOutcomes = /* @__PURE__ */ new Set(["resolved", "confirmed", "validated", "superseded"]);
|
|
10687
|
+
if (decision?.outcome && resolvedOutcomes.has(decision.outcome)) return true;
|
|
10688
|
+
const hasRecentEvent = ctx.decisionEvents.some(
|
|
10689
|
+
(e) => idMatches(e.decisionId, blocker.ref) && e.cycle >= blocker.blockedCycle
|
|
10690
|
+
);
|
|
10691
|
+
return hasRecentEvent;
|
|
10692
|
+
}
|
|
10693
|
+
default:
|
|
10694
|
+
return false;
|
|
10695
|
+
}
|
|
10696
|
+
}
|
|
10697
|
+
function formatBlockerWaiting(blocker) {
|
|
10698
|
+
switch (blocker.type) {
|
|
10699
|
+
case "depends-on":
|
|
10700
|
+
return `waiting on ${blocker.ref} \u2014 clears when that task is Done`;
|
|
10701
|
+
case "owner-action":
|
|
10702
|
+
return `waiting on owner action ${blocker.ref} \u2014 clears when you complete it`;
|
|
10703
|
+
case "decision-gate":
|
|
10704
|
+
return `waiting on decision ${blocker.ref} \u2014 clears when that decision is resolved`;
|
|
10705
|
+
default:
|
|
10706
|
+
return `waiting on ${blocker.ref}`;
|
|
10707
|
+
}
|
|
10708
|
+
}
|
|
10709
|
+
|
|
10587
10710
|
// src/lib/visibility-inheritance.ts
|
|
10588
10711
|
var TIER_RANK = {
|
|
10589
10712
|
public: 0,
|
|
@@ -11016,6 +11139,28 @@ function computeFoundationalTasksGuidance(cycleNumber, brief) {
|
|
|
11016
11139
|
"- User-submitted backlog tasks remain in `cycleHandoffs` as usual \u2014 do NOT drop them in favour of foundational tasks."
|
|
11017
11140
|
].join("\n");
|
|
11018
11141
|
}
|
|
11142
|
+
async function computeResurfacedBlockers(adapter2, tasks, decisions, currentCycle) {
|
|
11143
|
+
const blocked = tasks.filter((t) => t.status === "Blocked" && t.blocker);
|
|
11144
|
+
if (blocked.length === 0) return void 0;
|
|
11145
|
+
const minCycle = Math.max(0, currentCycle - 5);
|
|
11146
|
+
const decisionEvents = adapter2.getDecisionEventsSince ? await adapter2.getDecisionEventsSince(minCycle).catch(() => []) : [];
|
|
11147
|
+
let ownerActions = [];
|
|
11148
|
+
if (adapter2.listOwnerActionsForBlockerScan && adapter2.getProjectOwnerUserId) {
|
|
11149
|
+
try {
|
|
11150
|
+
const userId = await adapter2.getProjectOwnerUserId();
|
|
11151
|
+
if (userId) ownerActions = await adapter2.listOwnerActionsForBlockerScan(userId);
|
|
11152
|
+
} catch {
|
|
11153
|
+
}
|
|
11154
|
+
}
|
|
11155
|
+
const ctx = { tasks, decisions, decisionEvents, ownerActions };
|
|
11156
|
+
const resolvedLines = [];
|
|
11157
|
+
for (const t of blocked) {
|
|
11158
|
+
if (t.blocker && isBlockerResolved(t.blocker, ctx)) {
|
|
11159
|
+
resolvedLines.push(`- **${t.displayId}** (${t.title}) \u2014 ${formatBlockerWaiting(t.blocker)} \u2192 now RESOLVED, ready to unblock`);
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
return resolvedLines.length > 0 ? resolvedLines.join("\n") : void 0;
|
|
11163
|
+
}
|
|
11019
11164
|
function detectBoardFlags(tasks) {
|
|
11020
11165
|
let hasBugTasks = false;
|
|
11021
11166
|
let hasResearchTasks = false;
|
|
@@ -11494,6 +11639,7 @@ ${logLines}`);
|
|
|
11494
11639
|
const targetCycle = health.totalCycles + 1;
|
|
11495
11640
|
const preAssigned = plannerTasks.filter((t2) => t2.cycle === targetCycle);
|
|
11496
11641
|
const preAssignedText = formatPreAssignedTasks(preAssigned, targetCycle);
|
|
11642
|
+
const resurfacedBlockersText = await computeResurfacedBlockers(adapter2, tasks, decisions, health.totalCycles).catch(() => void 0);
|
|
11497
11643
|
const gapFull = health.cyclesSinceLastStrategyReview;
|
|
11498
11644
|
const lastReviewCycleFull = health.totalCycles - gapFull;
|
|
11499
11645
|
const strategyReviewCadenceFull = gapFull <= 0 ? `\u2713 Strategy review completed this cycle (C${health.totalCycles}). No carry-forward needed.` : gapFull < STRATEGY_REVIEW_OFFER_GAP ? `\u2713 Strategy review on track \u2014 last review was C${lastReviewCycleFull} (${gapFull} cycle(s) ago). Next due: C${lastReviewCycleFull + STRATEGY_REVIEW_OFFER_GAP}.` : gapFull < STRATEGY_REVIEW_BLOCK_GAP ? `\u25CB Strategy review available \u2014 last review was C${lastReviewCycleFull} (${gapFull} cycles ago). Offered now (optional); planning hard-blocks at ${STRATEGY_REVIEW_BLOCK_GAP} cycles.` : `\u26A0\uFE0F Strategy review overdue \u2014 last review was C${lastReviewCycleFull} (${gapFull} cycles ago). Planning is blocked until it runs (or \`force: true\`).`;
|
|
@@ -11526,6 +11672,7 @@ ${logLines}`);
|
|
|
11526
11672
|
doneTasksResultFull.status === "fulfilled" ? new Set(doneTasksResultFull.value.map((t2) => t2.displayId)) : void 0
|
|
11527
11673
|
),
|
|
11528
11674
|
preAssignedTasks: preAssignedText,
|
|
11675
|
+
resurfacedBlockers: resurfacedBlockersText,
|
|
11529
11676
|
recentlyShippedCapabilities: formatRecentlyShippedCapabilities(reports),
|
|
11530
11677
|
strategyReviewCadence: strategyReviewCadenceFull,
|
|
11531
11678
|
candidateTaskFullNotes: formatCandidateTaskFullNotes(plannerTasks),
|
|
@@ -12519,6 +12666,8 @@ function buildSubagentDispatchPrompt(input) {
|
|
|
12519
12666
|
applyNote = `\`plan\` again with mode="apply", llm_response=<sub-agent output>, plan_mode="${applyMode ?? "full"}", cycle_number=${cycleNumber + 1}, strategy_review_warning=${JSON.stringify(strategyReviewWarning)}`;
|
|
12520
12667
|
} else if (tool === "strategy_review") {
|
|
12521
12668
|
applyNote = `\`strategy_review\` again with mode="apply", llm_response=<sub-agent output>, cycle_number=${cycleNumber}`;
|
|
12669
|
+
} else if (tool === "zoom_out") {
|
|
12670
|
+
applyNote = `\`zoom_out\` again with mode="apply", llm_response=<sub-agent output>, cycle_number=${cycleNumber}`;
|
|
12522
12671
|
} else {
|
|
12523
12672
|
applyNote = `\`review_submit\` with task_id="${taskId ?? "<task-id>"}", stage="build-acceptance", verdict=<your decision: accept / request-changes / reject>, comments=<your reasoning>, and auto_review set to the sub-agent's JSON object verbatim. The sub-agent's verdict is a RECOMMENDATION \u2014 you make the final call`;
|
|
12524
12673
|
}
|
|
@@ -14385,7 +14534,7 @@ function extractDecisionEvidence(ad, eventType, warnings) {
|
|
|
14385
14534
|
async function writeBack2(adapter2, cycleNumber, data, fullAnalysis, warnings) {
|
|
14386
14535
|
const cleanTitle = data.sessionLogTitle.replace(/^(?:Cycle|Session)\s+\d+\s*—\s*/i, "").trim();
|
|
14387
14536
|
const cleanContent = data.sessionLogContent.replace(/^#{1,3}\s+(?:Cycle|Session)\s+\d+\s*—[^\n]*\n*/i, "").trim();
|
|
14388
|
-
const lastReviewCycle = await adapter2.getLastStrategyReviewCycle();
|
|
14537
|
+
const lastReviewCycle = await adapter2.getLastStrategyReviewCycle({ excludeZoomOut: true });
|
|
14389
14538
|
const rangeStart = lastReviewCycle > 0 ? lastReviewCycle + 1 : cycleNumber;
|
|
14390
14539
|
const cycleRange = rangeStart < cycleNumber ? `${rangeStart}-${cycleNumber}` : `${cycleNumber}`;
|
|
14391
14540
|
const logBlock = `### Cycles ${cycleRange} \u2014 ${cleanTitle}
|
|
@@ -15188,7 +15337,7 @@ Confidence: ${input.confidence}. Captured mid-conversation via strategy_change c
|
|
|
15188
15337
|
var reviewPrepareCache = new PerCallerCache();
|
|
15189
15338
|
var strategyReviewTool = {
|
|
15190
15339
|
name: "strategy_review",
|
|
15191
|
-
description: 'Run a Strategy Review \u2014 assesses project direction, velocity, and Active Decisions. Produces recommendations and potential AD updates that feed into the next plan. Offered every 5 cycles; hard-blocked at 7+ overdue cycles.
|
|
15340
|
+
description: 'Run a Strategy Review \u2014 assesses project direction, velocity, and Active Decisions. Produces recommendations and potential AD updates that feed into the next plan. Offered every 5 cycles; hard-blocked at 7+ overdue cycles. Run it in your current conversation \u2014 only start a fresh one if you are genuinely under context pressure (your host just compacted, you are near the context limit, or the session is heavy with build context), not just because a review is next. First call returns a review prompt for you to execute (prepare phase). Then call again with mode "apply" and your output. Pass `force: true` to run before the cadence gate.',
|
|
15192
15341
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
15193
15342
|
inputSchema: {
|
|
15194
15343
|
type: "object",
|
|
@@ -15774,6 +15923,15 @@ var boardDeprioritiseTool = {
|
|
|
15774
15923
|
type: "string",
|
|
15775
15924
|
description: 'Why this task is being removed. Required for "cancel", recommended for "defer".'
|
|
15776
15925
|
},
|
|
15926
|
+
blocker_type: {
|
|
15927
|
+
type: "string",
|
|
15928
|
+
enum: ["depends-on", "decision-gate", "owner-action"],
|
|
15929
|
+
description: 'Optional (action="block" only). Typed blocker so the plan/orient scan can auto-detect when the block clears. "depends-on" = another task must be Done (blocker_ref = task display-id). "decision-gate" = a decision must resolve (blocker_ref = AD/decision id). "owner-action" = an owner action must complete (blocker_ref = owner_action id). Omit for a reason-only block.'
|
|
15930
|
+
},
|
|
15931
|
+
blocker_ref: {
|
|
15932
|
+
type: "string",
|
|
15933
|
+
description: "Required when blocker_type is set. The identifier being waited on: a task display-id (depends-on), an AD/decision id (decision-gate), or an owner_action id (owner-action)."
|
|
15934
|
+
},
|
|
15777
15935
|
defer: {
|
|
15778
15936
|
type: "boolean",
|
|
15779
15937
|
description: 'DEPRECATED \u2014 use action instead. If true, equivalent to action="defer".'
|
|
@@ -16008,19 +16166,50 @@ async function handleBoardDeprioritise(adapter2, args) {
|
|
|
16008
16166
|
if (!reason) {
|
|
16009
16167
|
return errorResponse("reason is required when blocking a task \u2014 explain what external dependency or gate is blocking it.");
|
|
16010
16168
|
}
|
|
16169
|
+
const blockerType = args.blocker_type;
|
|
16170
|
+
const blockerRef = args.blocker_ref;
|
|
16171
|
+
const validTypes = /* @__PURE__ */ new Set(["depends-on", "decision-gate", "owner-action"]);
|
|
16172
|
+
if (blockerType !== void 0 && !validTypes.has(blockerType)) {
|
|
16173
|
+
return errorResponse(`blocker_type must be one of: depends-on, decision-gate, owner-action (got "${blockerType}").`);
|
|
16174
|
+
}
|
|
16175
|
+
if (blockerType !== void 0 && !blockerRef) {
|
|
16176
|
+
return errorResponse("blocker_ref is required when blocker_type is set \u2014 provide the task display-id, decision id, or owner_action id being waited on.");
|
|
16177
|
+
}
|
|
16011
16178
|
try {
|
|
16012
16179
|
const task = await adapter2.getTask(taskId);
|
|
16013
16180
|
if (!task) return errorResponse(`Task ${taskId} not found.`);
|
|
16014
16181
|
const existingNotes = task.notes ? `${task.notes}
|
|
16015
16182
|
|
|
16016
16183
|
` : "";
|
|
16017
|
-
|
|
16184
|
+
const updates = {
|
|
16018
16185
|
status: "Blocked",
|
|
16019
16186
|
notes: `${existingNotes}BLOCKED: ${reason}`
|
|
16020
|
-
}
|
|
16187
|
+
};
|
|
16188
|
+
let typedSuffix = "";
|
|
16189
|
+
if (blockerType !== void 0 && blockerRef) {
|
|
16190
|
+
const health = await adapter2.getCycleHealth().catch(() => null);
|
|
16191
|
+
const blockedCycle = health?.totalCycles ?? 0;
|
|
16192
|
+
updates.blocker = {
|
|
16193
|
+
type: blockerType,
|
|
16194
|
+
ref: blockerRef,
|
|
16195
|
+
reason,
|
|
16196
|
+
blockedCycle
|
|
16197
|
+
};
|
|
16198
|
+
typedSuffix = `
|
|
16199
|
+
|
|
16200
|
+
Blocker: **${blockerType}** \u2192 ${blockerRef} (auto-unblock scanned at plan/orient).`;
|
|
16201
|
+
if (blockerType === "owner-action" && adapter2.linkOwnerActionToTask && adapter2.getProjectOwnerUserId) {
|
|
16202
|
+
try {
|
|
16203
|
+
const ownerUserId = await adapter2.getProjectOwnerUserId();
|
|
16204
|
+
if (ownerUserId) await adapter2.linkOwnerActionToTask(blockerRef, task.uuid, ownerUserId);
|
|
16205
|
+
} catch {
|
|
16206
|
+
}
|
|
16207
|
+
}
|
|
16208
|
+
}
|
|
16209
|
+
await adapter2.updateTask(taskId, updates);
|
|
16021
16210
|
return textResponse(`Blocked **${taskId}** (${task.title}).
|
|
16022
16211
|
|
|
16023
|
-
Reason: ${reason}
|
|
16212
|
+
Reason: ${reason}${typedSuffix}
|
|
16024
16213
|
|
|
16025
16214
|
Task remains visible on the board but will be skipped by the planner and build_list.`);
|
|
16026
16215
|
} catch (err) {
|
|
@@ -16632,7 +16821,7 @@ This is a one-time check at the start of the cycle, not per-task. It catches sco
|
|
|
16632
16821
|
Every 5 cycles, PAPI offers a strategy review \u2014 a deep analysis of velocity, estimation accuracy, active decisions, and project direction.
|
|
16633
16822
|
|
|
16634
16823
|
- **Don't skip them.** They're where compounding value comes from.
|
|
16635
|
-
-
|
|
16824
|
+
- Run the review in your current conversation \u2014 a fresh one is only warranted if you're genuinely under context pressure (your host just compacted, or you're near the limit), never just because a review is next.
|
|
16636
16825
|
- Reviews produce recommendations that feed into the next plan.
|
|
16637
16826
|
- If the review recommends AD changes, use \`strategy_change\` to apply them.
|
|
16638
16827
|
|
|
@@ -16757,7 +16946,7 @@ var DOCS_INDEX_TEMPLATE = `# {{project_name}} \u2014 Document Index
|
|
|
16757
16946
|
Who or what reads this doc: \`planner\`, \`builder\`, \`strategy-review\`, \`user\`, \`onboarding\`.
|
|
16758
16947
|
`;
|
|
16759
16948
|
var CURSOR_RULES_TEMPLATE = `---
|
|
16760
|
-
description: PAPI \u2014
|
|
16949
|
+
description: PAPI cycle workflow for {{project_name}} \u2014 plan, build, review
|
|
16761
16950
|
globs:
|
|
16762
16951
|
alwaysApply: true
|
|
16763
16952
|
---
|
|
@@ -18450,40 +18639,62 @@ To override, pass force=true (emits a telemetry warning).`
|
|
|
18450
18639
|
if (adapter2) {
|
|
18451
18640
|
const currentCycle = resolvedCycleNum;
|
|
18452
18641
|
if (currentCycle > 0) {
|
|
18642
|
+
let existing;
|
|
18453
18643
|
try {
|
|
18454
|
-
|
|
18455
|
-
|
|
18456
|
-
existing = (await adapter2.readCycles()).find((c) => c.number === currentCycle);
|
|
18457
|
-
} catch {
|
|
18458
|
-
}
|
|
18459
|
-
await adapter2.createCycle({
|
|
18460
|
-
id: `cycle-${currentCycle}`,
|
|
18461
|
-
number: currentCycle,
|
|
18462
|
-
status: "complete",
|
|
18463
|
-
startDate: existing?.startDate ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
18464
|
-
endDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18465
|
-
goals: existing?.goals ?? [],
|
|
18466
|
-
boardHealth: existing?.boardHealth ?? "",
|
|
18467
|
-
taskIds: existing?.taskIds ?? []
|
|
18468
|
-
});
|
|
18469
|
-
} catch (err) {
|
|
18470
|
-
const msg = `createCycle (mark complete) failed for cycle ${currentCycle}: ${err instanceof Error ? err.message : String(err)}`;
|
|
18471
|
-
console.error(`[release] ${msg}`);
|
|
18472
|
-
throw new Error(
|
|
18473
|
-
`Release blocked \u2014 could not mark cycle ${currentCycle} complete in the DB. ${err instanceof Error ? err.message : String(err)}. Resolve the DB error and re-run \`release\`.`
|
|
18474
|
-
);
|
|
18644
|
+
existing = (await adapter2.readCycles()).find((c) => c.number === currentCycle);
|
|
18645
|
+
} catch {
|
|
18475
18646
|
}
|
|
18476
|
-
|
|
18647
|
+
const completedCycle = {
|
|
18648
|
+
id: `cycle-${currentCycle}`,
|
|
18649
|
+
number: currentCycle,
|
|
18650
|
+
status: "complete",
|
|
18651
|
+
startDate: existing?.startDate ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
18652
|
+
endDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18653
|
+
goals: existing?.goals ?? [],
|
|
18654
|
+
boardHealth: existing?.boardHealth ?? "",
|
|
18655
|
+
taskIds: existing?.taskIds ?? []
|
|
18656
|
+
};
|
|
18657
|
+
let snapshot;
|
|
18658
|
+
if (adapter2.getBuildReportsSince) {
|
|
18477
18659
|
try {
|
|
18478
18660
|
const cycleReports = (await adapter2.getBuildReportsSince(currentCycle)).filter((r) => r.cycle === currentCycle);
|
|
18479
|
-
|
|
18480
|
-
if (snapshot) await adapter2.appendCycleMetrics(snapshot);
|
|
18661
|
+
[snapshot] = computeSnapshotsFromBuildReports(cycleReports);
|
|
18481
18662
|
} catch (err) {
|
|
18482
18663
|
console.error(
|
|
18483
|
-
`[release] cycle-metrics snapshot
|
|
18664
|
+
`[release] cycle-metrics snapshot compute failed for cycle ${currentCycle} (non-blocking): ${err instanceof Error ? err.message : String(err)}`
|
|
18484
18665
|
);
|
|
18485
18666
|
}
|
|
18486
18667
|
}
|
|
18668
|
+
if (typeof adapter2.commitRelease === "function") {
|
|
18669
|
+
try {
|
|
18670
|
+
await adapter2.commitRelease({ cycle: completedCycle, snapshot: snapshot ?? null });
|
|
18671
|
+
} catch (err) {
|
|
18672
|
+
const msg = `commitRelease (mark complete) failed for cycle ${currentCycle}: ${err instanceof Error ? err.message : String(err)}`;
|
|
18673
|
+
console.error(`[release] ${msg}`);
|
|
18674
|
+
throw new Error(
|
|
18675
|
+
`Release blocked \u2014 could not mark cycle ${currentCycle} complete in the DB. ${err instanceof Error ? err.message : String(err)}. Resolve the DB error and re-run \`release\`.`
|
|
18676
|
+
);
|
|
18677
|
+
}
|
|
18678
|
+
} else {
|
|
18679
|
+
try {
|
|
18680
|
+
await adapter2.createCycle(completedCycle);
|
|
18681
|
+
} catch (err) {
|
|
18682
|
+
const msg = `createCycle (mark complete) failed for cycle ${currentCycle}: ${err instanceof Error ? err.message : String(err)}`;
|
|
18683
|
+
console.error(`[release] ${msg}`);
|
|
18684
|
+
throw new Error(
|
|
18685
|
+
`Release blocked \u2014 could not mark cycle ${currentCycle} complete in the DB. ${err instanceof Error ? err.message : String(err)}. Resolve the DB error and re-run \`release\`.`
|
|
18686
|
+
);
|
|
18687
|
+
}
|
|
18688
|
+
if (adapter2.appendCycleMetrics && snapshot) {
|
|
18689
|
+
try {
|
|
18690
|
+
await adapter2.appendCycleMetrics(snapshot);
|
|
18691
|
+
} catch (err) {
|
|
18692
|
+
console.error(
|
|
18693
|
+
`[release] cycle-metrics snapshot write failed for cycle ${currentCycle} (non-blocking): ${err instanceof Error ? err.message : String(err)}`
|
|
18694
|
+
);
|
|
18695
|
+
}
|
|
18696
|
+
}
|
|
18697
|
+
}
|
|
18487
18698
|
}
|
|
18488
18699
|
}
|
|
18489
18700
|
return { resolvedCycleNum, warnings, force, skipVersion };
|
|
@@ -19137,18 +19348,26 @@ Run \`project_switch <slug>\` to switch the active PAPI project, or verify your
|
|
|
19137
19348
|
}
|
|
19138
19349
|
|
|
19139
19350
|
// src/lib/owns-local-workspace.ts
|
|
19140
|
-
async function
|
|
19141
|
-
|
|
19351
|
+
async function resolveWorkspaceOwners(adapter2, cwd) {
|
|
19352
|
+
const empty = { storedOwner: null, remoteOwner: null, remoteSlug: null };
|
|
19353
|
+
if (!adapter2.getProjectInfo) return empty;
|
|
19142
19354
|
try {
|
|
19143
19355
|
const info = await adapter2.getProjectInfo();
|
|
19144
|
-
const
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19356
|
+
const remoteSlug = getOriginRepoSlug(cwd);
|
|
19357
|
+
return {
|
|
19358
|
+
storedOwner: parseGithubOwner(info?.repo_url ?? null),
|
|
19359
|
+
remoteOwner: parseGithubOwner(remoteSlug),
|
|
19360
|
+
remoteSlug
|
|
19361
|
+
};
|
|
19148
19362
|
} catch {
|
|
19149
|
-
return
|
|
19363
|
+
return empty;
|
|
19150
19364
|
}
|
|
19151
19365
|
}
|
|
19366
|
+
async function ownsLocalWorkspace(adapter2, cwd) {
|
|
19367
|
+
const { storedOwner, remoteOwner } = await resolveWorkspaceOwners(adapter2, cwd);
|
|
19368
|
+
if (!storedOwner || !remoteOwner) return false;
|
|
19369
|
+
return storedOwner === remoteOwner;
|
|
19370
|
+
}
|
|
19152
19371
|
|
|
19153
19372
|
// src/services/build.ts
|
|
19154
19373
|
init_git();
|
|
@@ -19939,7 +20158,24 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
19939
20158
|
} catch {
|
|
19940
20159
|
}
|
|
19941
20160
|
}
|
|
19942
|
-
|
|
20161
|
+
const surpriseNote = input.surprises === "None" ? "" : ` Surprises: ${input.surprises}.`;
|
|
20162
|
+
const issueNote = input.discoveredIssues === "None" ? "" : ` Issues: ${input.discoveredIssues}.`;
|
|
20163
|
+
const iterNote = iterationCount > 1 ? ` Iterations: ${iterationCount} (${iterationCount - 1} pushback${iterationCount > 2 ? "s" : ""}).` : "";
|
|
20164
|
+
const buildReportSummary = `${capitalizeCompleted(input.completed)}. Effort ${input.effort} vs estimated ${input.estimatedEffort}.${iterNote}${surpriseNote}${issueNote}`;
|
|
20165
|
+
const statusChange = input.completed === "yes" ? { from: task.status, to: options.light ? "Done" : "In Review" } : void 0;
|
|
20166
|
+
let atomicCommitDone = false;
|
|
20167
|
+
if (typeof adapter2.commitBuildComplete === "function") {
|
|
20168
|
+
await adapter2.commitBuildComplete({
|
|
20169
|
+
report,
|
|
20170
|
+
taskId,
|
|
20171
|
+
buildReportSummary,
|
|
20172
|
+
statusChange,
|
|
20173
|
+
cycle: cycleNumber
|
|
20174
|
+
});
|
|
20175
|
+
atomicCommitDone = true;
|
|
20176
|
+
} else {
|
|
20177
|
+
await adapter2.appendBuildReport(report);
|
|
20178
|
+
}
|
|
19943
20179
|
let reportWriteVerified;
|
|
19944
20180
|
if (typeof adapter2.getBuildReportCountForTask === "function") {
|
|
19945
20181
|
try {
|
|
@@ -20080,16 +20316,14 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
20080
20316
|
if (task.cycle == null && cycleNumber > 0) {
|
|
20081
20317
|
await adapter2.updateTask(taskId, { cycle: cycleNumber });
|
|
20082
20318
|
}
|
|
20083
|
-
|
|
20084
|
-
|
|
20085
|
-
|
|
20086
|
-
|
|
20087
|
-
|
|
20088
|
-
|
|
20089
|
-
|
|
20090
|
-
|
|
20091
|
-
} else {
|
|
20092
|
-
await adapter2.updateTaskStatus(taskId, "In Review");
|
|
20319
|
+
if (!atomicCommitDone) {
|
|
20320
|
+
await adapter2.updateTask(taskId, { buildReport: buildReportSummary });
|
|
20321
|
+
if (input.completed === "yes") {
|
|
20322
|
+
if (options.light) {
|
|
20323
|
+
await adapter2.updateTaskStatus(taskId, "Done");
|
|
20324
|
+
} else {
|
|
20325
|
+
await adapter2.updateTaskStatus(taskId, "In Review");
|
|
20326
|
+
}
|
|
20093
20327
|
}
|
|
20094
20328
|
}
|
|
20095
20329
|
let dogfoodResolvedCount = 0;
|
|
@@ -20358,7 +20592,7 @@ import { homedir as homedir3 } from "os";
|
|
|
20358
20592
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
20359
20593
|
var docRegisterTool = {
|
|
20360
20594
|
name: "doc_register",
|
|
20361
|
-
description: "Register a document in the doc registry. Called after finalising a research/planning doc, or when build_execute detects unregistered docs. Stores metadata and structured summary \u2014 not full content.",
|
|
20595
|
+
description: "Register or update a document in the doc registry. Called after finalising a research/planning doc, or when build_execute detects unregistered docs. Stores metadata and structured summary \u2014 not full content. Re-registering an existing doc updates its summary, tags, actions, type, and status (upsert). Visibility and owner are not changed on re-register.",
|
|
20362
20596
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
20363
20597
|
inputSchema: {
|
|
20364
20598
|
type: "object",
|
|
@@ -22654,10 +22888,20 @@ Re-run build_execute complete with a production_verification field, then re-subm
|
|
|
22654
22888
|
if (input.autoReview) {
|
|
22655
22889
|
review.autoReview = input.autoReview;
|
|
22656
22890
|
}
|
|
22657
|
-
await adapter2.writeReview(review);
|
|
22658
22891
|
const newStatus = resolveStatus(input.stage, input.verdict);
|
|
22659
|
-
|
|
22660
|
-
|
|
22892
|
+
const statusChange = newStatus ? { from: task.status, to: newStatus } : void 0;
|
|
22893
|
+
if (typeof adapter2.commitReviewSubmit === "function") {
|
|
22894
|
+
await adapter2.commitReviewSubmit({
|
|
22895
|
+
review,
|
|
22896
|
+
taskId: input.taskId,
|
|
22897
|
+
statusChange,
|
|
22898
|
+
cycle
|
|
22899
|
+
});
|
|
22900
|
+
} else {
|
|
22901
|
+
await adapter2.writeReview(review);
|
|
22902
|
+
if (newStatus) {
|
|
22903
|
+
await adapter2.updateTaskStatus(input.taskId, newStatus);
|
|
22904
|
+
}
|
|
22661
22905
|
}
|
|
22662
22906
|
const unblockedTasks = [];
|
|
22663
22907
|
if (newStatus === "Done") {
|
|
@@ -23883,6 +24127,14 @@ ${writeNote}
|
|
|
23883
24127
|
}
|
|
23884
24128
|
|
|
23885
24129
|
// src/services/health.ts
|
|
24130
|
+
function computeZoomOutWarning(cycleNumber, lastZoomOutCycle) {
|
|
24131
|
+
if (cycleNumber <= 0) return "";
|
|
24132
|
+
const baseline = lastZoomOutCycle > 0 ? lastZoomOutCycle : 1;
|
|
24133
|
+
const gap = cycleNumber - baseline;
|
|
24134
|
+
if (gap < ZOOM_OUT_OFFER_GAP) return "";
|
|
24135
|
+
const ran = lastZoomOutCycle > 0 ? `last run Cycle ${lastZoomOutCycle}` : "never run";
|
|
24136
|
+
return `\u25CB Zoom-out available \u2014 ${gap} cycles since last zoom-out (${ran}). QBR/all-hands tier above strategy review; offer-only, no block. Run \`zoom_out\` to read the full project arc.`;
|
|
24137
|
+
}
|
|
23886
24138
|
function computeHealthScore(cycleNumber, snapshots, activeTasks, decisionUsage) {
|
|
23887
24139
|
if (cycleNumber < 3) return null;
|
|
23888
24140
|
const scores = [];
|
|
@@ -23952,6 +24204,12 @@ async function getHealthSummary(adapter2) {
|
|
|
23952
24204
|
const reviewGateBlocking = cyclesSinceReview >= STRATEGY_REVIEW_BLOCK_GAP;
|
|
23953
24205
|
const reviewAvailable = cyclesSinceReview >= STRATEGY_REVIEW_OFFER_GAP && !reviewGateBlocking;
|
|
23954
24206
|
const reviewWarning = reviewGateBlocking ? `\u26A0\uFE0F GATE \u2014 ${cyclesSinceReview} cycles since last Strategy Review. \`plan\` is blocked until \`strategy_review\` runs (or \`force: true\`).` : reviewAvailable ? `\u25CB Strategy review available \u2014 ${cyclesSinceReview} cycles since last review. Offered now (optional); \`plan\` hard-blocks at ${STRATEGY_REVIEW_BLOCK_GAP} cycles.` : `\u2713 On track \u2014 ${cyclesSinceReview} cycle(s) since last review. Next due: ${reviewDue}`;
|
|
24207
|
+
let zoomOutWarning = "";
|
|
24208
|
+
try {
|
|
24209
|
+
const lastZoomOutCycle = adapter2.getLastZoomOutCycle ? await adapter2.getLastZoomOutCycle() : 0;
|
|
24210
|
+
zoomOutWarning = computeZoomOutWarning(cycleNumber, lastZoomOutCycle);
|
|
24211
|
+
} catch {
|
|
24212
|
+
}
|
|
23955
24213
|
const deferredCount = activeTasks.filter((t) => t.status === "Deferred").length;
|
|
23956
24214
|
const nonDeferredTasks = activeTasks.filter((t) => t.status !== "Deferred");
|
|
23957
24215
|
const statusCounts = countByStatus(nonDeferredTasks);
|
|
@@ -24105,6 +24363,7 @@ ${lines.join("\n")}`;
|
|
|
24105
24363
|
latestCycleStatus: health.latestCycleStatus,
|
|
24106
24364
|
connectionStatus: getConnectionStatus(),
|
|
24107
24365
|
reviewWarning,
|
|
24366
|
+
zoomOutWarning,
|
|
24108
24367
|
boardSummary,
|
|
24109
24368
|
staleTasks,
|
|
24110
24369
|
inReviewSummary,
|
|
@@ -24182,6 +24441,30 @@ async function findUnblockCandidates(adapter2, currentCycle) {
|
|
|
24182
24441
|
} catch {
|
|
24183
24442
|
return [];
|
|
24184
24443
|
}
|
|
24444
|
+
const blockedTasks = allTasks.filter((t) => t.status === "Blocked");
|
|
24445
|
+
if (blockedTasks.length === 0) return [];
|
|
24446
|
+
const ctx = await buildResolverContext(adapter2, allTasks, currentCycle).catch(
|
|
24447
|
+
() => ({ tasks: allTasks, decisions: [], decisionEvents: [], ownerActions: [] })
|
|
24448
|
+
);
|
|
24449
|
+
const candidates = [];
|
|
24450
|
+
for (const blocked of blockedTasks) {
|
|
24451
|
+
if (candidates.length >= MAX_CANDIDATES) break;
|
|
24452
|
+
const blocker = blocked.blocker;
|
|
24453
|
+
if (!blocker) continue;
|
|
24454
|
+
const resolved = isBlockerResolved(blocker, ctx);
|
|
24455
|
+
candidates.push({
|
|
24456
|
+
blockedId: blocked.displayId ?? blocked.id,
|
|
24457
|
+
blockedTitle: blocked.title,
|
|
24458
|
+
blockerId: blocker.ref,
|
|
24459
|
+
blockerTitle: "",
|
|
24460
|
+
blockerCycle: blocker.blockedCycle,
|
|
24461
|
+
typed: {
|
|
24462
|
+
kind: blocker.type,
|
|
24463
|
+
waiting: formatBlockerWaiting(blocker),
|
|
24464
|
+
resolved
|
|
24465
|
+
}
|
|
24466
|
+
});
|
|
24467
|
+
}
|
|
24185
24468
|
const minCycle = Math.max(0, currentCycle - RECENT_CYCLE_WINDOW + 1);
|
|
24186
24469
|
const recentDoneById = /* @__PURE__ */ new Map();
|
|
24187
24470
|
for (const t of allTasks) {
|
|
@@ -24190,42 +24473,71 @@ async function findUnblockCandidates(adapter2, currentCycle) {
|
|
|
24190
24473
|
if (cycle < minCycle) continue;
|
|
24191
24474
|
if (t.displayId) recentDoneById.set(t.displayId.toLowerCase(), t);
|
|
24192
24475
|
}
|
|
24193
|
-
if (recentDoneById.size
|
|
24194
|
-
|
|
24195
|
-
|
|
24196
|
-
|
|
24197
|
-
|
|
24198
|
-
|
|
24199
|
-
|
|
24200
|
-
|
|
24201
|
-
|
|
24202
|
-
|
|
24203
|
-
|
|
24204
|
-
|
|
24205
|
-
|
|
24206
|
-
|
|
24207
|
-
|
|
24208
|
-
|
|
24209
|
-
|
|
24210
|
-
|
|
24211
|
-
|
|
24212
|
-
|
|
24213
|
-
|
|
24214
|
-
|
|
24215
|
-
|
|
24216
|
-
|
|
24476
|
+
if (recentDoneById.size > 0) {
|
|
24477
|
+
for (const blocked of blockedTasks) {
|
|
24478
|
+
if (candidates.length >= MAX_CANDIDATES) break;
|
|
24479
|
+
if (blocked.blocker) continue;
|
|
24480
|
+
const notes = blocked.notes ?? "";
|
|
24481
|
+
if (!notes) continue;
|
|
24482
|
+
const matches = notes.match(TASK_REF);
|
|
24483
|
+
if (!matches) continue;
|
|
24484
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24485
|
+
for (const raw of matches) {
|
|
24486
|
+
const key = raw.toLowerCase();
|
|
24487
|
+
if (seen.has(key)) continue;
|
|
24488
|
+
seen.add(key);
|
|
24489
|
+
if (blocked.displayId && key === blocked.displayId.toLowerCase()) continue;
|
|
24490
|
+
const blocker = recentDoneById.get(key);
|
|
24491
|
+
if (!blocker) continue;
|
|
24492
|
+
candidates.push({
|
|
24493
|
+
blockedId: blocked.displayId ?? blocked.id,
|
|
24494
|
+
blockedTitle: blocked.title,
|
|
24495
|
+
blockerId: blocker.displayId ?? blocker.id,
|
|
24496
|
+
blockerTitle: blocker.title,
|
|
24497
|
+
blockerCycle: blocker.cycle ?? 0
|
|
24498
|
+
});
|
|
24499
|
+
break;
|
|
24500
|
+
}
|
|
24217
24501
|
}
|
|
24218
|
-
if (candidates.length >= MAX_CANDIDATES) break;
|
|
24219
24502
|
}
|
|
24220
24503
|
return candidates;
|
|
24221
24504
|
}
|
|
24505
|
+
async function buildResolverContext(adapter2, tasks, currentCycle) {
|
|
24506
|
+
const decisions = adapter2.getActiveDecisions ? await adapter2.getActiveDecisions().catch(() => []) : [];
|
|
24507
|
+
const minCycle = Math.max(0, currentCycle - RECENT_CYCLE_WINDOW);
|
|
24508
|
+
const decisionEvents = adapter2.getDecisionEventsSince ? await adapter2.getDecisionEventsSince(minCycle).catch(() => []) : [];
|
|
24509
|
+
let ownerActions = [];
|
|
24510
|
+
if (adapter2.listOwnerActionsForBlockerScan && adapter2.getProjectOwnerUserId) {
|
|
24511
|
+
try {
|
|
24512
|
+
const userId = await adapter2.getProjectOwnerUserId();
|
|
24513
|
+
if (userId) ownerActions = await adapter2.listOwnerActionsForBlockerScan(userId);
|
|
24514
|
+
} catch {
|
|
24515
|
+
}
|
|
24516
|
+
}
|
|
24517
|
+
return { tasks, decisions, decisionEvents, ownerActions };
|
|
24518
|
+
}
|
|
24222
24519
|
function formatUnblockSection(candidates) {
|
|
24223
24520
|
if (candidates.length === 0) return "";
|
|
24521
|
+
const resolved = candidates.filter((c) => c.typed?.resolved || !c.typed);
|
|
24522
|
+
const stillWaiting = candidates.filter((c) => c.typed && !c.typed.resolved);
|
|
24224
24523
|
const lines = ["## Unblock Candidates"];
|
|
24225
|
-
|
|
24226
|
-
|
|
24227
|
-
|
|
24228
|
-
|
|
24524
|
+
if (resolved.length > 0) {
|
|
24525
|
+
lines.push(`${resolved.length} blocked task(s) ready to unblock \u2014 run \`board_reconcile\` to promote.`);
|
|
24526
|
+
lines.push("");
|
|
24527
|
+
for (const c of resolved) {
|
|
24528
|
+
if (c.typed) {
|
|
24529
|
+
lines.push(`- **${c.blockedId}** (${c.blockedTitle}) \u2014 ${c.typed.kind}: ${c.blockerId} now resolved \u2705`);
|
|
24530
|
+
} else {
|
|
24531
|
+
lines.push(`- **${c.blockedId}** (${c.blockedTitle}) \u2014 blocker **${c.blockerId}** Done in C${c.blockerCycle}`);
|
|
24532
|
+
}
|
|
24533
|
+
}
|
|
24534
|
+
}
|
|
24535
|
+
if (stillWaiting.length > 0) {
|
|
24536
|
+
if (resolved.length > 0) lines.push("");
|
|
24537
|
+
lines.push(`${stillWaiting.length} still blocked:`);
|
|
24538
|
+
for (const c of stillWaiting) {
|
|
24539
|
+
lines.push(`- **${c.blockedId}** (${c.blockedTitle}) \u2014 ${c.typed.waiting}`);
|
|
24540
|
+
}
|
|
24229
24541
|
}
|
|
24230
24542
|
return lines.join("\n");
|
|
24231
24543
|
}
|
|
@@ -24554,6 +24866,10 @@ function formatOrientSummary(health, buildInfo, hierarchy, latestTag, projectRoo
|
|
|
24554
24866
|
lines.push("");
|
|
24555
24867
|
lines.push(`**Strategy Review:** ${health.reviewWarning}`);
|
|
24556
24868
|
lines.push("");
|
|
24869
|
+
if (health.zoomOutWarning) {
|
|
24870
|
+
lines.push(`**Zoom-Out:** ${health.zoomOutWarning}`);
|
|
24871
|
+
lines.push("");
|
|
24872
|
+
}
|
|
24557
24873
|
if (hierarchy) {
|
|
24558
24874
|
lines.push(`**Position:** ${hierarchy.horizon} \u2192 ${hierarchy.stage}`);
|
|
24559
24875
|
if (hierarchy.activePhases.length > 0) {
|
|
@@ -25258,6 +25574,21 @@ async function handleOrient(adapter2, config2, args = {}, clientName) {
|
|
|
25258
25574
|
for (const w of verifyWarnings) buildResult.warnings.push(w);
|
|
25259
25575
|
} catch {
|
|
25260
25576
|
}
|
|
25577
|
+
try {
|
|
25578
|
+
if (hasLocalWorkspace() && adapter2.getProjectInfo) {
|
|
25579
|
+
const { storedOwner, remoteOwner, remoteSlug } = await resolveWorkspaceOwners(adapter2, config2.projectRoot);
|
|
25580
|
+
if (storedOwner && remoteOwner && storedOwner !== remoteOwner) {
|
|
25581
|
+
buildResult.warnings.push(
|
|
25582
|
+
`\u26A0\uFE0F Project mismatch \u2014 this PAPI project is bound to owner '${storedOwner}' but the cwd git remote points to owner '${remoteOwner}'. Run \`project_switch\` to the right project, or update the project's repo_url.`
|
|
25583
|
+
);
|
|
25584
|
+
} else if (!storedOwner && remoteOwner) {
|
|
25585
|
+
buildResult.warnings.push(
|
|
25586
|
+
`\u2139\uFE0F repo_url not set on this project \u2014 cwd git remote is '${remoteSlug}'. Run \`setup\` to capture it so the mismatch guard can fire.`
|
|
25587
|
+
);
|
|
25588
|
+
}
|
|
25589
|
+
}
|
|
25590
|
+
} catch {
|
|
25591
|
+
}
|
|
25261
25592
|
const ttfvNote = ttfvOutcome.status === "fulfilled" ? ttfvOutcome.value : "";
|
|
25262
25593
|
const latestTag = latestTagOutcome.status === "fulfilled" ? latestTagOutcome.value : "";
|
|
25263
25594
|
const versionDrift = versionDriftOutcome.status === "fulfilled" ? versionDriftOutcome.value : void 0;
|
|
@@ -25772,7 +26103,8 @@ async function prepareZoomOut(adapter2, projectRoot) {
|
|
|
25772
26103
|
return {
|
|
25773
26104
|
cycleNumber,
|
|
25774
26105
|
systemPrompt: ZOOM_OUT_SYSTEM_PROMPT,
|
|
25775
|
-
userMessage
|
|
26106
|
+
userMessage,
|
|
26107
|
+
contextBytes: Buffer.byteLength(userMessage, "utf-8")
|
|
25776
26108
|
};
|
|
25777
26109
|
}
|
|
25778
26110
|
async function applyZoomOut(adapter2, llmResponse, cycleNumber) {
|
|
@@ -25780,7 +26112,8 @@ async function applyZoomOut(adapter2, llmResponse, cycleNumber) {
|
|
|
25780
26112
|
cycleNumber,
|
|
25781
26113
|
title: `Zoom-Out Retrospective \u2014 Cycle ${cycleNumber}`,
|
|
25782
26114
|
content: llmResponse.slice(0, 500),
|
|
25783
|
-
fullAnalysis: llmResponse
|
|
26115
|
+
fullAnalysis: llmResponse,
|
|
26116
|
+
reviewType: "zoom_out"
|
|
25784
26117
|
});
|
|
25785
26118
|
return {
|
|
25786
26119
|
cycleNumber,
|
|
@@ -25808,6 +26141,11 @@ var zoomOutTool = {
|
|
|
25808
26141
|
cycle_number: {
|
|
25809
26142
|
type: "number",
|
|
25810
26143
|
description: 'The cycle number from prepare phase (mode "apply" only).'
|
|
26144
|
+
},
|
|
26145
|
+
dispatch: {
|
|
26146
|
+
type: "string",
|
|
26147
|
+
enum: ["inline", "subagent"],
|
|
26148
|
+
description: '"inline" (default) returns the retrospective prompt for the calling LLM to execute directly. "subagent" returns a Task() invocation prompt to dispatch the heavyweight reasoning to a fresh sub-agent. The apply-phase contract is unchanged either way. Auto-routes to "subagent" past the same context threshold plan/strategy_review use (zoom_out runs the largest context of any tool).'
|
|
25811
26149
|
}
|
|
25812
26150
|
},
|
|
25813
26151
|
required: []
|
|
@@ -25835,6 +26173,26 @@ Retrospective saved. Use insights to inform your next \`strategy_review\` or \`p
|
|
|
25835
26173
|
}
|
|
25836
26174
|
{
|
|
25837
26175
|
const result = await prepareZoomOut(adapter2, config2.projectRoot);
|
|
26176
|
+
const autoDispatchEnabled = process.env.PAPI_AUTO_DISPATCH !== "false";
|
|
26177
|
+
const autoDispatchThreshold = 50 * 1024;
|
|
26178
|
+
let dispatch;
|
|
26179
|
+
if (args.dispatch === "inline" || args.dispatch === "subagent") {
|
|
26180
|
+
dispatch = args.dispatch;
|
|
26181
|
+
} else if (autoDispatchEnabled && result.contextBytes > autoDispatchThreshold) {
|
|
26182
|
+
dispatch = "subagent";
|
|
26183
|
+
} else {
|
|
26184
|
+
dispatch = "inline";
|
|
26185
|
+
}
|
|
26186
|
+
if (dispatch === "subagent") {
|
|
26187
|
+
const dispatchPrompt = buildSubagentDispatchPrompt({
|
|
26188
|
+
tool: "zoom_out",
|
|
26189
|
+
cycleNumber: result.cycleNumber,
|
|
26190
|
+
systemPrompt: result.systemPrompt,
|
|
26191
|
+
userMessage: result.userMessage,
|
|
26192
|
+
contextBytes: result.contextBytes
|
|
26193
|
+
});
|
|
26194
|
+
return textResponse(dispatchPrompt);
|
|
26195
|
+
}
|
|
25838
26196
|
return textResponse(
|
|
25839
26197
|
`## PAPI Zoom-Out Retrospective \u2014 Prepare Phase (Cycle ${result.cycleNumber})
|
|
25840
26198
|
|
|
@@ -26630,7 +26988,7 @@ var contributorRemoveTool = {
|
|
|
26630
26988
|
};
|
|
26631
26989
|
var contributorListTool = {
|
|
26632
26990
|
name: "contributor_list",
|
|
26633
|
-
description: "List the current project's contributors (
|
|
26991
|
+
description: "List the current project's contributors (any project member). Shows each member's email, display name, role, and join date. Does not call the Anthropic API.",
|
|
26634
26992
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
26635
26993
|
inputSchema: {
|
|
26636
26994
|
type: "object",
|
|
@@ -26643,10 +27001,15 @@ var contributorListTool = {
|
|
|
26643
27001
|
required: []
|
|
26644
27002
|
}
|
|
26645
27003
|
};
|
|
26646
|
-
|
|
27004
|
+
function denyUnlessCohortCapable(adapter2) {
|
|
26647
27005
|
if (typeof adapter2.listContributors !== "function") {
|
|
26648
27006
|
return "Contributor management is not available on this adapter (requires the pg or proxy adapter \u2014 the local md adapter has no cohort).";
|
|
26649
27007
|
}
|
|
27008
|
+
return null;
|
|
27009
|
+
}
|
|
27010
|
+
async function denyUnlessOwner(adapter2, config2) {
|
|
27011
|
+
const capDenied = denyUnlessCohortCapable(adapter2);
|
|
27012
|
+
if (capDenied) return capDenied;
|
|
26650
27013
|
const gate = await resolveOwnerGate(adapter2, config2);
|
|
26651
27014
|
if (gate.enforced && !gate.callerIsOwner) {
|
|
26652
27015
|
const note = gate.resolutionError ? ` (Identity resolution failed: ${gate.resolutionError} \u2014 the gate fails closed; retry once connectivity is restored.)` : "";
|
|
@@ -26654,6 +27017,17 @@ async function denyUnlessOwner(adapter2, config2) {
|
|
|
26654
27017
|
}
|
|
26655
27018
|
return null;
|
|
26656
27019
|
}
|
|
27020
|
+
async function denyUnlessMember(adapter2, config2) {
|
|
27021
|
+
const capDenied = denyUnlessCohortCapable(adapter2);
|
|
27022
|
+
if (capDenied) return capDenied;
|
|
27023
|
+
const gate = await resolveOwnerGate(adapter2, config2);
|
|
27024
|
+
if (!gate.enforced) return null;
|
|
27025
|
+
if (gate.callerIsOwner) return null;
|
|
27026
|
+
const callerRole = gate.callerUserId && typeof adapter2.getContributorRole === "function" ? await adapter2.getContributorRole(gate.callerUserId).catch(() => null) : null;
|
|
27027
|
+
if (callerRole) return null;
|
|
27028
|
+
const note = gate.resolutionError ? ` (Identity resolution failed: ${gate.resolutionError} \u2014 the gate fails closed; retry once connectivity is restored.)` : "";
|
|
27029
|
+
return `Listing contributors is restricted to project members. Your identity does not match this project's owner or any contributor.${note}`;
|
|
27030
|
+
}
|
|
26657
27031
|
var EMAIL_SHAPE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
26658
27032
|
function requireEmail(args) {
|
|
26659
27033
|
const email = typeof args.email === "string" ? args.email.trim() : "";
|
|
@@ -26693,7 +27067,7 @@ async function handleContributorRemove(adapter2, config2, args) {
|
|
|
26693
27067
|
}
|
|
26694
27068
|
}
|
|
26695
27069
|
async function handleContributorList(adapter2, config2, _args) {
|
|
26696
|
-
const denied = await
|
|
27070
|
+
const denied = await denyUnlessMember(adapter2, config2);
|
|
26697
27071
|
if (denied) return errorResponse(denied);
|
|
26698
27072
|
try {
|
|
26699
27073
|
const entries = await adapter2.listContributors();
|
|
@@ -27308,7 +27682,10 @@ var LONG_RUNNING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
27308
27682
|
"release",
|
|
27309
27683
|
"setup",
|
|
27310
27684
|
"doc_register",
|
|
27311
|
-
"doc_scan"
|
|
27685
|
+
"doc_scan",
|
|
27686
|
+
// task-2416 (C315): zoom_out assembles a 120–150K-char context and, hosted, fans out
|
|
27687
|
+
// ~14 proxied HTTP round-trips — well over the 30s default. See zoom-out-tooling-audit-c313.md #3.
|
|
27688
|
+
"zoom_out"
|
|
27312
27689
|
]);
|
|
27313
27690
|
function toolTimeoutMs(name) {
|
|
27314
27691
|
return LONG_RUNNING_TOOLS.has(name) ? LONG_TOOL_TIMEOUT_MS : DEFAULT_TOOL_TIMEOUT_MS;
|