@papi-ai/server 0.7.44 → 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.
|
@@ -1879,12 +1879,12 @@ import { pathToFileURL } from "url";
|
|
|
1879
1879
|
import path2 from "path";
|
|
1880
1880
|
import { execSync } from "child_process";
|
|
1881
1881
|
|
|
1882
|
-
//
|
|
1882
|
+
// ../adapter-md/dist/index.js
|
|
1883
1883
|
import { readFile, writeFile, access } from "fs/promises";
|
|
1884
1884
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
1885
1885
|
import { join } from "path";
|
|
1886
1886
|
|
|
1887
|
-
//
|
|
1887
|
+
// ../shared/dist/index.js
|
|
1888
1888
|
var VALID_TRANSITIONS = {
|
|
1889
1889
|
"Backlog": ["In Cycle", "Ready", "In Progress", "Blocked", "Cancelled", "Deferred", "Done"],
|
|
1890
1890
|
"In Cycle": ["In Progress", "Backlog", "Blocked", "Cancelled"],
|
|
@@ -1903,7 +1903,7 @@ function isLiveDecision(d) {
|
|
|
1903
1903
|
return true;
|
|
1904
1904
|
}
|
|
1905
1905
|
|
|
1906
|
-
//
|
|
1906
|
+
// ../adapter-md/dist/index.js
|
|
1907
1907
|
import { randomUUID } from "crypto";
|
|
1908
1908
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1909
1909
|
import yaml from "js-yaml";
|
|
@@ -3298,14 +3298,27 @@ var MdFileAdapter = class {
|
|
|
3298
3298
|
notes: review.notes
|
|
3299
3299
|
});
|
|
3300
3300
|
}
|
|
3301
|
-
/**
|
|
3302
|
-
|
|
3301
|
+
/**
|
|
3302
|
+
* Get the cycle number of the last strategy review.
|
|
3303
|
+
* task-2416 (C315): `excludeZoomOut` is accepted for interface parity with the pg
|
|
3304
|
+
* adapter. The md adapter already matches strategy-review titles only (zoom-out logs
|
|
3305
|
+
* carry a "Zoom-Out" title, not a strategy-review one), so the flag is a no-op here —
|
|
3306
|
+
* legacy/best-effort path.
|
|
3307
|
+
*/
|
|
3308
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3309
|
+
async getLastStrategyReviewCycle(_opts) {
|
|
3303
3310
|
const log = await this.getCycleLog();
|
|
3304
3311
|
const entry = log.find(
|
|
3305
3312
|
(e) => /strategy.*review|strategic.*shift/i.test(e.title)
|
|
3306
3313
|
);
|
|
3307
3314
|
return entry?.cycleNumber ?? 0;
|
|
3308
3315
|
}
|
|
3316
|
+
/** task-2416 (C315): cycle of the last zoom-out retrospective. md best-effort — matches the log title. */
|
|
3317
|
+
async getLastZoomOutCycle() {
|
|
3318
|
+
const log = await this.getCycleLog();
|
|
3319
|
+
const entry = log.find((e) => /zoom[-\s]?out/i.test(e.title));
|
|
3320
|
+
return entry?.cycleNumber ?? 0;
|
|
3321
|
+
}
|
|
3309
3322
|
/** Get strategy reviews — md adapter returns empty (reviews live in cycle log). */
|
|
3310
3323
|
async getStrategyReviews(_limit, _includeFullAnalysis) {
|
|
3311
3324
|
return [];
|
package/dist/index.js
CHANGED
|
@@ -1989,7 +1989,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1989
1989
|
}
|
|
1990
1990
|
});
|
|
1991
1991
|
|
|
1992
|
-
//
|
|
1992
|
+
// ../../node_modules/postgres/src/query.js
|
|
1993
1993
|
function cachedError(xs) {
|
|
1994
1994
|
if (originCache.has(xs))
|
|
1995
1995
|
return originCache.get(xs);
|
|
@@ -2001,7 +2001,7 @@ function cachedError(xs) {
|
|
|
2001
2001
|
}
|
|
2002
2002
|
var originCache, originStackCache, originError, CLOSE, Query;
|
|
2003
2003
|
var init_query = __esm({
|
|
2004
|
-
"
|
|
2004
|
+
"../../node_modules/postgres/src/query.js"() {
|
|
2005
2005
|
"use strict";
|
|
2006
2006
|
originCache = /* @__PURE__ */ new Map();
|
|
2007
2007
|
originStackCache = /* @__PURE__ */ new Map();
|
|
@@ -2132,7 +2132,7 @@ var init_query = __esm({
|
|
|
2132
2132
|
}
|
|
2133
2133
|
});
|
|
2134
2134
|
|
|
2135
|
-
//
|
|
2135
|
+
// ../../node_modules/postgres/src/errors.js
|
|
2136
2136
|
function connection(x, options, socket) {
|
|
2137
2137
|
const { host, port } = socket || options;
|
|
2138
2138
|
const error = Object.assign(
|
|
@@ -2170,7 +2170,7 @@ function notSupported(x) {
|
|
|
2170
2170
|
}
|
|
2171
2171
|
var PostgresError, Errors;
|
|
2172
2172
|
var init_errors = __esm({
|
|
2173
|
-
"
|
|
2173
|
+
"../../node_modules/postgres/src/errors.js"() {
|
|
2174
2174
|
"use strict";
|
|
2175
2175
|
PostgresError = class extends Error {
|
|
2176
2176
|
constructor(x) {
|
|
@@ -2188,7 +2188,7 @@ var init_errors = __esm({
|
|
|
2188
2188
|
}
|
|
2189
2189
|
});
|
|
2190
2190
|
|
|
2191
|
-
//
|
|
2191
|
+
// ../../node_modules/postgres/src/types.js
|
|
2192
2192
|
function handleValue(x, parameters, types2, options) {
|
|
2193
2193
|
let value = x instanceof Parameter ? x.value : x;
|
|
2194
2194
|
if (value === void 0) {
|
|
@@ -2303,7 +2303,7 @@ function createJsonTransform(fn) {
|
|
|
2303
2303
|
}
|
|
2304
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;
|
|
2305
2305
|
var init_types = __esm({
|
|
2306
|
-
"
|
|
2306
|
+
"../../node_modules/postgres/src/types.js"() {
|
|
2307
2307
|
"use strict";
|
|
2308
2308
|
init_query();
|
|
2309
2309
|
init_errors();
|
|
@@ -2482,10 +2482,10 @@ var init_types = __esm({
|
|
|
2482
2482
|
}
|
|
2483
2483
|
});
|
|
2484
2484
|
|
|
2485
|
-
//
|
|
2485
|
+
// ../../node_modules/postgres/src/result.js
|
|
2486
2486
|
var Result;
|
|
2487
2487
|
var init_result = __esm({
|
|
2488
|
-
"
|
|
2488
|
+
"../../node_modules/postgres/src/result.js"() {
|
|
2489
2489
|
"use strict";
|
|
2490
2490
|
Result = class extends Array {
|
|
2491
2491
|
constructor() {
|
|
@@ -2505,7 +2505,7 @@ var init_result = __esm({
|
|
|
2505
2505
|
}
|
|
2506
2506
|
});
|
|
2507
2507
|
|
|
2508
|
-
//
|
|
2508
|
+
// ../../node_modules/postgres/src/queue.js
|
|
2509
2509
|
function Queue(initial = []) {
|
|
2510
2510
|
let xs = initial.slice();
|
|
2511
2511
|
let index = 0;
|
|
@@ -2532,13 +2532,13 @@ function Queue(initial = []) {
|
|
|
2532
2532
|
}
|
|
2533
2533
|
var queue_default;
|
|
2534
2534
|
var init_queue = __esm({
|
|
2535
|
-
"
|
|
2535
|
+
"../../node_modules/postgres/src/queue.js"() {
|
|
2536
2536
|
"use strict";
|
|
2537
2537
|
queue_default = Queue;
|
|
2538
2538
|
}
|
|
2539
2539
|
});
|
|
2540
2540
|
|
|
2541
|
-
//
|
|
2541
|
+
// ../../node_modules/postgres/src/bytes.js
|
|
2542
2542
|
function fit(x) {
|
|
2543
2543
|
if (buffer.length - b.i < x) {
|
|
2544
2544
|
const prev = buffer, length = prev.length;
|
|
@@ -2552,7 +2552,7 @@ function reset() {
|
|
|
2552
2552
|
}
|
|
2553
2553
|
var size, buffer, messages, b, bytes_default;
|
|
2554
2554
|
var init_bytes = __esm({
|
|
2555
|
-
"
|
|
2555
|
+
"../../node_modules/postgres/src/bytes.js"() {
|
|
2556
2556
|
"use strict";
|
|
2557
2557
|
size = 256;
|
|
2558
2558
|
buffer = Buffer.allocUnsafe(size);
|
|
@@ -2617,7 +2617,7 @@ var init_bytes = __esm({
|
|
|
2617
2617
|
}
|
|
2618
2618
|
});
|
|
2619
2619
|
|
|
2620
|
-
//
|
|
2620
|
+
// ../../node_modules/postgres/src/connection.js
|
|
2621
2621
|
import net from "net";
|
|
2622
2622
|
import tls from "tls";
|
|
2623
2623
|
import crypto2 from "crypto";
|
|
@@ -3411,7 +3411,7 @@ function timer(fn, seconds) {
|
|
|
3411
3411
|
}
|
|
3412
3412
|
var connection_default, uid, Sync, Flush, SSLRequest, ExecuteUnnamed, DescribeUnnamed, noop, retryRoutines, errorFields;
|
|
3413
3413
|
var init_connection = __esm({
|
|
3414
|
-
"
|
|
3414
|
+
"../../node_modules/postgres/src/connection.js"() {
|
|
3415
3415
|
"use strict";
|
|
3416
3416
|
init_types();
|
|
3417
3417
|
init_errors();
|
|
@@ -3474,7 +3474,7 @@ var init_connection = __esm({
|
|
|
3474
3474
|
}
|
|
3475
3475
|
});
|
|
3476
3476
|
|
|
3477
|
-
//
|
|
3477
|
+
// ../../node_modules/postgres/src/subscribe.js
|
|
3478
3478
|
function Subscribe(postgres2, options) {
|
|
3479
3479
|
const subscribers = /* @__PURE__ */ new Map(), slot = "postgresjs_" + Math.random().toString(36).slice(2), state = {};
|
|
3480
3480
|
let connection2, stream, ended = false;
|
|
@@ -3685,14 +3685,14 @@ function parseEvent(x) {
|
|
|
3685
3685
|
}
|
|
3686
3686
|
var noop2;
|
|
3687
3687
|
var init_subscribe = __esm({
|
|
3688
|
-
"
|
|
3688
|
+
"../../node_modules/postgres/src/subscribe.js"() {
|
|
3689
3689
|
"use strict";
|
|
3690
3690
|
noop2 = () => {
|
|
3691
3691
|
};
|
|
3692
3692
|
}
|
|
3693
3693
|
});
|
|
3694
3694
|
|
|
3695
|
-
//
|
|
3695
|
+
// ../../node_modules/postgres/src/large.js
|
|
3696
3696
|
import Stream2 from "stream";
|
|
3697
3697
|
function largeObject(sql, oid, mode = 131072 | 262144) {
|
|
3698
3698
|
return new Promise(async (resolve3, reject) => {
|
|
@@ -3758,12 +3758,12 @@ function largeObject(sql, oid, mode = 131072 | 262144) {
|
|
|
3758
3758
|
});
|
|
3759
3759
|
}
|
|
3760
3760
|
var init_large = __esm({
|
|
3761
|
-
"
|
|
3761
|
+
"../../node_modules/postgres/src/large.js"() {
|
|
3762
3762
|
"use strict";
|
|
3763
3763
|
}
|
|
3764
3764
|
});
|
|
3765
3765
|
|
|
3766
|
-
//
|
|
3766
|
+
// ../../node_modules/postgres/src/index.js
|
|
3767
3767
|
var src_exports = {};
|
|
3768
3768
|
__export(src_exports, {
|
|
3769
3769
|
default: () => src_default
|
|
@@ -4153,7 +4153,7 @@ function osUsername() {
|
|
|
4153
4153
|
}
|
|
4154
4154
|
var src_default;
|
|
4155
4155
|
var init_src = __esm({
|
|
4156
|
-
"
|
|
4156
|
+
"../../node_modules/postgres/src/index.js"() {
|
|
4157
4157
|
"use strict";
|
|
4158
4158
|
init_types();
|
|
4159
4159
|
init_connection();
|
|
@@ -5043,6 +5043,7 @@ var HELP_FOOTER_MD = `
|
|
|
5043
5043
|
// src/config.ts
|
|
5044
5044
|
var STRATEGY_REVIEW_OFFER_GAP = 5;
|
|
5045
5045
|
var STRATEGY_REVIEW_BLOCK_GAP = 7;
|
|
5046
|
+
var ZOOM_OUT_OFFER_GAP = 25;
|
|
5046
5047
|
function loadConfig() {
|
|
5047
5048
|
const projectArgIdx = process.argv.indexOf("--project");
|
|
5048
5049
|
const configuredRoot = projectArgIdx !== -1 ? process.argv[projectArgIdx + 1] : process.env.PAPI_PROJECT_DIR;
|
|
@@ -5142,12 +5143,12 @@ Already have an account? Make sure PAPI_USER_ID is set in your .mcp.json env con
|
|
|
5142
5143
|
import path3 from "path";
|
|
5143
5144
|
import { execSync } from "child_process";
|
|
5144
5145
|
|
|
5145
|
-
//
|
|
5146
|
+
// ../adapter-md/dist/index.js
|
|
5146
5147
|
import { readFile, writeFile, access } from "fs/promises";
|
|
5147
5148
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
5148
5149
|
import { join } from "path";
|
|
5149
5150
|
|
|
5150
|
-
//
|
|
5151
|
+
// ../shared/dist/index.js
|
|
5151
5152
|
var VALID_TRANSITIONS = {
|
|
5152
5153
|
"Backlog": ["In Cycle", "Ready", "In Progress", "Blocked", "Cancelled", "Deferred", "Done"],
|
|
5153
5154
|
"In Cycle": ["In Progress", "Backlog", "Blocked", "Cancelled"],
|
|
@@ -5166,7 +5167,7 @@ function isLiveDecision(d) {
|
|
|
5166
5167
|
return true;
|
|
5167
5168
|
}
|
|
5168
5169
|
|
|
5169
|
-
//
|
|
5170
|
+
// ../adapter-md/dist/index.js
|
|
5170
5171
|
import { randomUUID } from "crypto";
|
|
5171
5172
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5172
5173
|
import yaml from "js-yaml";
|
|
@@ -6669,14 +6670,27 @@ var MdFileAdapter = class {
|
|
|
6669
6670
|
notes: review.notes
|
|
6670
6671
|
});
|
|
6671
6672
|
}
|
|
6672
|
-
/**
|
|
6673
|
-
|
|
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) {
|
|
6674
6682
|
const log2 = await this.getCycleLog();
|
|
6675
6683
|
const entry = log2.find(
|
|
6676
6684
|
(e) => /strategy.*review|strategic.*shift/i.test(e.title)
|
|
6677
6685
|
);
|
|
6678
6686
|
return entry?.cycleNumber ?? 0;
|
|
6679
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
|
+
}
|
|
6680
6694
|
/** Get strategy reviews — md adapter returns empty (reviews live in cycle log). */
|
|
6681
6695
|
async getStrategyReviews(_limit, _includeFullAnalysis) {
|
|
6682
6696
|
return [];
|
|
@@ -9112,6 +9126,16 @@ function buildPlanUserMessage(ctx) {
|
|
|
9112
9126
|
if (ctx.board) {
|
|
9113
9127
|
parts.push("### Board", "", ctx.board, "");
|
|
9114
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
|
+
}
|
|
9115
9139
|
if (ctx.candidateTaskFullNotes) {
|
|
9116
9140
|
parts.push("### Full Notes for Candidate Tasks", "", ctx.candidateTaskFullNotes, "");
|
|
9117
9141
|
}
|
|
@@ -9351,6 +9375,7 @@ IMPORTANT: You are running as a non-interactive API call. Do NOT ask the user qu
|
|
|
9351
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.
|
|
9352
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."
|
|
9353
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.
|
|
9354
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.
|
|
9355
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.
|
|
9356
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.
|
|
@@ -10636,6 +10661,52 @@ function computeCarryForwardStaleness(log2, doneTaskIds) {
|
|
|
10636
10661
|
].join("\n");
|
|
10637
10662
|
}
|
|
10638
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
|
+
|
|
10639
10710
|
// src/lib/visibility-inheritance.ts
|
|
10640
10711
|
var TIER_RANK = {
|
|
10641
10712
|
public: 0,
|
|
@@ -11068,6 +11139,28 @@ function computeFoundationalTasksGuidance(cycleNumber, brief) {
|
|
|
11068
11139
|
"- User-submitted backlog tasks remain in `cycleHandoffs` as usual \u2014 do NOT drop them in favour of foundational tasks."
|
|
11069
11140
|
].join("\n");
|
|
11070
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
|
+
}
|
|
11071
11164
|
function detectBoardFlags(tasks) {
|
|
11072
11165
|
let hasBugTasks = false;
|
|
11073
11166
|
let hasResearchTasks = false;
|
|
@@ -11546,6 +11639,7 @@ ${logLines}`);
|
|
|
11546
11639
|
const targetCycle = health.totalCycles + 1;
|
|
11547
11640
|
const preAssigned = plannerTasks.filter((t2) => t2.cycle === targetCycle);
|
|
11548
11641
|
const preAssignedText = formatPreAssignedTasks(preAssigned, targetCycle);
|
|
11642
|
+
const resurfacedBlockersText = await computeResurfacedBlockers(adapter2, tasks, decisions, health.totalCycles).catch(() => void 0);
|
|
11549
11643
|
const gapFull = health.cyclesSinceLastStrategyReview;
|
|
11550
11644
|
const lastReviewCycleFull = health.totalCycles - gapFull;
|
|
11551
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\`).`;
|
|
@@ -11578,6 +11672,7 @@ ${logLines}`);
|
|
|
11578
11672
|
doneTasksResultFull.status === "fulfilled" ? new Set(doneTasksResultFull.value.map((t2) => t2.displayId)) : void 0
|
|
11579
11673
|
),
|
|
11580
11674
|
preAssignedTasks: preAssignedText,
|
|
11675
|
+
resurfacedBlockers: resurfacedBlockersText,
|
|
11581
11676
|
recentlyShippedCapabilities: formatRecentlyShippedCapabilities(reports),
|
|
11582
11677
|
strategyReviewCadence: strategyReviewCadenceFull,
|
|
11583
11678
|
candidateTaskFullNotes: formatCandidateTaskFullNotes(plannerTasks),
|
|
@@ -12571,6 +12666,8 @@ function buildSubagentDispatchPrompt(input) {
|
|
|
12571
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)}`;
|
|
12572
12667
|
} else if (tool === "strategy_review") {
|
|
12573
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}`;
|
|
12574
12671
|
} else {
|
|
12575
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`;
|
|
12576
12673
|
}
|
|
@@ -14437,7 +14534,7 @@ function extractDecisionEvidence(ad, eventType, warnings) {
|
|
|
14437
14534
|
async function writeBack2(adapter2, cycleNumber, data, fullAnalysis, warnings) {
|
|
14438
14535
|
const cleanTitle = data.sessionLogTitle.replace(/^(?:Cycle|Session)\s+\d+\s*—\s*/i, "").trim();
|
|
14439
14536
|
const cleanContent = data.sessionLogContent.replace(/^#{1,3}\s+(?:Cycle|Session)\s+\d+\s*—[^\n]*\n*/i, "").trim();
|
|
14440
|
-
const lastReviewCycle = await adapter2.getLastStrategyReviewCycle();
|
|
14537
|
+
const lastReviewCycle = await adapter2.getLastStrategyReviewCycle({ excludeZoomOut: true });
|
|
14441
14538
|
const rangeStart = lastReviewCycle > 0 ? lastReviewCycle + 1 : cycleNumber;
|
|
14442
14539
|
const cycleRange = rangeStart < cycleNumber ? `${rangeStart}-${cycleNumber}` : `${cycleNumber}`;
|
|
14443
14540
|
const logBlock = `### Cycles ${cycleRange} \u2014 ${cleanTitle}
|
|
@@ -15240,7 +15337,7 @@ Confidence: ${input.confidence}. Captured mid-conversation via strategy_change c
|
|
|
15240
15337
|
var reviewPrepareCache = new PerCallerCache();
|
|
15241
15338
|
var strategyReviewTool = {
|
|
15242
15339
|
name: "strategy_review",
|
|
15243
|
-
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.',
|
|
15244
15341
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
15245
15342
|
inputSchema: {
|
|
15246
15343
|
type: "object",
|
|
@@ -15826,6 +15923,15 @@ var boardDeprioritiseTool = {
|
|
|
15826
15923
|
type: "string",
|
|
15827
15924
|
description: 'Why this task is being removed. Required for "cancel", recommended for "defer".'
|
|
15828
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
|
+
},
|
|
15829
15935
|
defer: {
|
|
15830
15936
|
type: "boolean",
|
|
15831
15937
|
description: 'DEPRECATED \u2014 use action instead. If true, equivalent to action="defer".'
|
|
@@ -16060,19 +16166,50 @@ async function handleBoardDeprioritise(adapter2, args) {
|
|
|
16060
16166
|
if (!reason) {
|
|
16061
16167
|
return errorResponse("reason is required when blocking a task \u2014 explain what external dependency or gate is blocking it.");
|
|
16062
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
|
+
}
|
|
16063
16178
|
try {
|
|
16064
16179
|
const task = await adapter2.getTask(taskId);
|
|
16065
16180
|
if (!task) return errorResponse(`Task ${taskId} not found.`);
|
|
16066
16181
|
const existingNotes = task.notes ? `${task.notes}
|
|
16067
16182
|
|
|
16068
16183
|
` : "";
|
|
16069
|
-
|
|
16184
|
+
const updates = {
|
|
16070
16185
|
status: "Blocked",
|
|
16071
16186
|
notes: `${existingNotes}BLOCKED: ${reason}`
|
|
16072
|
-
}
|
|
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);
|
|
16073
16210
|
return textResponse(`Blocked **${taskId}** (${task.title}).
|
|
16074
16211
|
|
|
16075
|
-
Reason: ${reason}
|
|
16212
|
+
Reason: ${reason}${typedSuffix}
|
|
16076
16213
|
|
|
16077
16214
|
Task remains visible on the board but will be skipped by the planner and build_list.`);
|
|
16078
16215
|
} catch (err) {
|
|
@@ -16684,7 +16821,7 @@ This is a one-time check at the start of the cycle, not per-task. It catches sco
|
|
|
16684
16821
|
Every 5 cycles, PAPI offers a strategy review \u2014 a deep analysis of velocity, estimation accuracy, active decisions, and project direction.
|
|
16685
16822
|
|
|
16686
16823
|
- **Don't skip them.** They're where compounding value comes from.
|
|
16687
|
-
-
|
|
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.
|
|
16688
16825
|
- Reviews produce recommendations that feed into the next plan.
|
|
16689
16826
|
- If the review recommends AD changes, use \`strategy_change\` to apply them.
|
|
16690
16827
|
|
|
@@ -16809,7 +16946,7 @@ var DOCS_INDEX_TEMPLATE = `# {{project_name}} \u2014 Document Index
|
|
|
16809
16946
|
Who or what reads this doc: \`planner\`, \`builder\`, \`strategy-review\`, \`user\`, \`onboarding\`.
|
|
16810
16947
|
`;
|
|
16811
16948
|
var CURSOR_RULES_TEMPLATE = `---
|
|
16812
|
-
description: PAPI \u2014
|
|
16949
|
+
description: PAPI cycle workflow for {{project_name}} \u2014 plan, build, review
|
|
16813
16950
|
globs:
|
|
16814
16951
|
alwaysApply: true
|
|
16815
16952
|
---
|
|
@@ -18502,40 +18639,62 @@ To override, pass force=true (emits a telemetry warning).`
|
|
|
18502
18639
|
if (adapter2) {
|
|
18503
18640
|
const currentCycle = resolvedCycleNum;
|
|
18504
18641
|
if (currentCycle > 0) {
|
|
18642
|
+
let existing;
|
|
18505
18643
|
try {
|
|
18506
|
-
|
|
18507
|
-
|
|
18508
|
-
existing = (await adapter2.readCycles()).find((c) => c.number === currentCycle);
|
|
18509
|
-
} catch {
|
|
18510
|
-
}
|
|
18511
|
-
await adapter2.createCycle({
|
|
18512
|
-
id: `cycle-${currentCycle}`,
|
|
18513
|
-
number: currentCycle,
|
|
18514
|
-
status: "complete",
|
|
18515
|
-
startDate: existing?.startDate ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
18516
|
-
endDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18517
|
-
goals: existing?.goals ?? [],
|
|
18518
|
-
boardHealth: existing?.boardHealth ?? "",
|
|
18519
|
-
taskIds: existing?.taskIds ?? []
|
|
18520
|
-
});
|
|
18521
|
-
} catch (err) {
|
|
18522
|
-
const msg = `createCycle (mark complete) failed for cycle ${currentCycle}: ${err instanceof Error ? err.message : String(err)}`;
|
|
18523
|
-
console.error(`[release] ${msg}`);
|
|
18524
|
-
throw new Error(
|
|
18525
|
-
`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\`.`
|
|
18526
|
-
);
|
|
18644
|
+
existing = (await adapter2.readCycles()).find((c) => c.number === currentCycle);
|
|
18645
|
+
} catch {
|
|
18527
18646
|
}
|
|
18528
|
-
|
|
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) {
|
|
18529
18659
|
try {
|
|
18530
18660
|
const cycleReports = (await adapter2.getBuildReportsSince(currentCycle)).filter((r) => r.cycle === currentCycle);
|
|
18531
|
-
|
|
18532
|
-
if (snapshot) await adapter2.appendCycleMetrics(snapshot);
|
|
18661
|
+
[snapshot] = computeSnapshotsFromBuildReports(cycleReports);
|
|
18533
18662
|
} catch (err) {
|
|
18534
18663
|
console.error(
|
|
18535
|
-
`[release] cycle-metrics snapshot
|
|
18664
|
+
`[release] cycle-metrics snapshot compute failed for cycle ${currentCycle} (non-blocking): ${err instanceof Error ? err.message : String(err)}`
|
|
18536
18665
|
);
|
|
18537
18666
|
}
|
|
18538
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
|
+
}
|
|
18539
18698
|
}
|
|
18540
18699
|
}
|
|
18541
18700
|
return { resolvedCycleNum, warnings, force, skipVersion };
|
|
@@ -19189,18 +19348,26 @@ Run \`project_switch <slug>\` to switch the active PAPI project, or verify your
|
|
|
19189
19348
|
}
|
|
19190
19349
|
|
|
19191
19350
|
// src/lib/owns-local-workspace.ts
|
|
19192
|
-
async function
|
|
19193
|
-
|
|
19351
|
+
async function resolveWorkspaceOwners(adapter2, cwd) {
|
|
19352
|
+
const empty = { storedOwner: null, remoteOwner: null, remoteSlug: null };
|
|
19353
|
+
if (!adapter2.getProjectInfo) return empty;
|
|
19194
19354
|
try {
|
|
19195
19355
|
const info = await adapter2.getProjectInfo();
|
|
19196
|
-
const
|
|
19197
|
-
|
|
19198
|
-
|
|
19199
|
-
|
|
19356
|
+
const remoteSlug = getOriginRepoSlug(cwd);
|
|
19357
|
+
return {
|
|
19358
|
+
storedOwner: parseGithubOwner(info?.repo_url ?? null),
|
|
19359
|
+
remoteOwner: parseGithubOwner(remoteSlug),
|
|
19360
|
+
remoteSlug
|
|
19361
|
+
};
|
|
19200
19362
|
} catch {
|
|
19201
|
-
return
|
|
19363
|
+
return empty;
|
|
19202
19364
|
}
|
|
19203
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
|
+
}
|
|
19204
19371
|
|
|
19205
19372
|
// src/services/build.ts
|
|
19206
19373
|
init_git();
|
|
@@ -19991,7 +20158,24 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
19991
20158
|
} catch {
|
|
19992
20159
|
}
|
|
19993
20160
|
}
|
|
19994
|
-
|
|
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
|
+
}
|
|
19995
20179
|
let reportWriteVerified;
|
|
19996
20180
|
if (typeof adapter2.getBuildReportCountForTask === "function") {
|
|
19997
20181
|
try {
|
|
@@ -20132,16 +20316,14 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
20132
20316
|
if (task.cycle == null && cycleNumber > 0) {
|
|
20133
20317
|
await adapter2.updateTask(taskId, { cycle: cycleNumber });
|
|
20134
20318
|
}
|
|
20135
|
-
|
|
20136
|
-
|
|
20137
|
-
|
|
20138
|
-
|
|
20139
|
-
|
|
20140
|
-
|
|
20141
|
-
|
|
20142
|
-
|
|
20143
|
-
} else {
|
|
20144
|
-
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
|
+
}
|
|
20145
20327
|
}
|
|
20146
20328
|
}
|
|
20147
20329
|
let dogfoodResolvedCount = 0;
|
|
@@ -20410,7 +20592,7 @@ import { homedir as homedir3 } from "os";
|
|
|
20410
20592
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
20411
20593
|
var docRegisterTool = {
|
|
20412
20594
|
name: "doc_register",
|
|
20413
|
-
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.",
|
|
20414
20596
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
20415
20597
|
inputSchema: {
|
|
20416
20598
|
type: "object",
|
|
@@ -22706,10 +22888,20 @@ Re-run build_execute complete with a production_verification field, then re-subm
|
|
|
22706
22888
|
if (input.autoReview) {
|
|
22707
22889
|
review.autoReview = input.autoReview;
|
|
22708
22890
|
}
|
|
22709
|
-
await adapter2.writeReview(review);
|
|
22710
22891
|
const newStatus = resolveStatus(input.stage, input.verdict);
|
|
22711
|
-
|
|
22712
|
-
|
|
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
|
+
}
|
|
22713
22905
|
}
|
|
22714
22906
|
const unblockedTasks = [];
|
|
22715
22907
|
if (newStatus === "Done") {
|
|
@@ -23935,6 +24127,14 @@ ${writeNote}
|
|
|
23935
24127
|
}
|
|
23936
24128
|
|
|
23937
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
|
+
}
|
|
23938
24138
|
function computeHealthScore(cycleNumber, snapshots, activeTasks, decisionUsage) {
|
|
23939
24139
|
if (cycleNumber < 3) return null;
|
|
23940
24140
|
const scores = [];
|
|
@@ -24004,6 +24204,12 @@ async function getHealthSummary(adapter2) {
|
|
|
24004
24204
|
const reviewGateBlocking = cyclesSinceReview >= STRATEGY_REVIEW_BLOCK_GAP;
|
|
24005
24205
|
const reviewAvailable = cyclesSinceReview >= STRATEGY_REVIEW_OFFER_GAP && !reviewGateBlocking;
|
|
24006
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
|
+
}
|
|
24007
24213
|
const deferredCount = activeTasks.filter((t) => t.status === "Deferred").length;
|
|
24008
24214
|
const nonDeferredTasks = activeTasks.filter((t) => t.status !== "Deferred");
|
|
24009
24215
|
const statusCounts = countByStatus(nonDeferredTasks);
|
|
@@ -24157,6 +24363,7 @@ ${lines.join("\n")}`;
|
|
|
24157
24363
|
latestCycleStatus: health.latestCycleStatus,
|
|
24158
24364
|
connectionStatus: getConnectionStatus(),
|
|
24159
24365
|
reviewWarning,
|
|
24366
|
+
zoomOutWarning,
|
|
24160
24367
|
boardSummary,
|
|
24161
24368
|
staleTasks,
|
|
24162
24369
|
inReviewSummary,
|
|
@@ -24234,6 +24441,30 @@ async function findUnblockCandidates(adapter2, currentCycle) {
|
|
|
24234
24441
|
} catch {
|
|
24235
24442
|
return [];
|
|
24236
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
|
+
}
|
|
24237
24468
|
const minCycle = Math.max(0, currentCycle - RECENT_CYCLE_WINDOW + 1);
|
|
24238
24469
|
const recentDoneById = /* @__PURE__ */ new Map();
|
|
24239
24470
|
for (const t of allTasks) {
|
|
@@ -24242,42 +24473,71 @@ async function findUnblockCandidates(adapter2, currentCycle) {
|
|
|
24242
24473
|
if (cycle < minCycle) continue;
|
|
24243
24474
|
if (t.displayId) recentDoneById.set(t.displayId.toLowerCase(), t);
|
|
24244
24475
|
}
|
|
24245
|
-
if (recentDoneById.size
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
|
|
24253
|
-
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
24261
|
-
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
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
|
+
}
|
|
24269
24501
|
}
|
|
24270
|
-
if (candidates.length >= MAX_CANDIDATES) break;
|
|
24271
24502
|
}
|
|
24272
24503
|
return candidates;
|
|
24273
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
|
+
}
|
|
24274
24519
|
function formatUnblockSection(candidates) {
|
|
24275
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);
|
|
24276
24523
|
const lines = ["## Unblock Candidates"];
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
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
|
+
}
|
|
24281
24541
|
}
|
|
24282
24542
|
return lines.join("\n");
|
|
24283
24543
|
}
|
|
@@ -24606,6 +24866,10 @@ function formatOrientSummary(health, buildInfo, hierarchy, latestTag, projectRoo
|
|
|
24606
24866
|
lines.push("");
|
|
24607
24867
|
lines.push(`**Strategy Review:** ${health.reviewWarning}`);
|
|
24608
24868
|
lines.push("");
|
|
24869
|
+
if (health.zoomOutWarning) {
|
|
24870
|
+
lines.push(`**Zoom-Out:** ${health.zoomOutWarning}`);
|
|
24871
|
+
lines.push("");
|
|
24872
|
+
}
|
|
24609
24873
|
if (hierarchy) {
|
|
24610
24874
|
lines.push(`**Position:** ${hierarchy.horizon} \u2192 ${hierarchy.stage}`);
|
|
24611
24875
|
if (hierarchy.activePhases.length > 0) {
|
|
@@ -25310,6 +25574,21 @@ async function handleOrient(adapter2, config2, args = {}, clientName) {
|
|
|
25310
25574
|
for (const w of verifyWarnings) buildResult.warnings.push(w);
|
|
25311
25575
|
} catch {
|
|
25312
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
|
+
}
|
|
25313
25592
|
const ttfvNote = ttfvOutcome.status === "fulfilled" ? ttfvOutcome.value : "";
|
|
25314
25593
|
const latestTag = latestTagOutcome.status === "fulfilled" ? latestTagOutcome.value : "";
|
|
25315
25594
|
const versionDrift = versionDriftOutcome.status === "fulfilled" ? versionDriftOutcome.value : void 0;
|
|
@@ -25824,7 +26103,8 @@ async function prepareZoomOut(adapter2, projectRoot) {
|
|
|
25824
26103
|
return {
|
|
25825
26104
|
cycleNumber,
|
|
25826
26105
|
systemPrompt: ZOOM_OUT_SYSTEM_PROMPT,
|
|
25827
|
-
userMessage
|
|
26106
|
+
userMessage,
|
|
26107
|
+
contextBytes: Buffer.byteLength(userMessage, "utf-8")
|
|
25828
26108
|
};
|
|
25829
26109
|
}
|
|
25830
26110
|
async function applyZoomOut(adapter2, llmResponse, cycleNumber) {
|
|
@@ -25832,7 +26112,8 @@ async function applyZoomOut(adapter2, llmResponse, cycleNumber) {
|
|
|
25832
26112
|
cycleNumber,
|
|
25833
26113
|
title: `Zoom-Out Retrospective \u2014 Cycle ${cycleNumber}`,
|
|
25834
26114
|
content: llmResponse.slice(0, 500),
|
|
25835
|
-
fullAnalysis: llmResponse
|
|
26115
|
+
fullAnalysis: llmResponse,
|
|
26116
|
+
reviewType: "zoom_out"
|
|
25836
26117
|
});
|
|
25837
26118
|
return {
|
|
25838
26119
|
cycleNumber,
|
|
@@ -25860,6 +26141,11 @@ var zoomOutTool = {
|
|
|
25860
26141
|
cycle_number: {
|
|
25861
26142
|
type: "number",
|
|
25862
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).'
|
|
25863
26149
|
}
|
|
25864
26150
|
},
|
|
25865
26151
|
required: []
|
|
@@ -25887,6 +26173,26 @@ Retrospective saved. Use insights to inform your next \`strategy_review\` or \`p
|
|
|
25887
26173
|
}
|
|
25888
26174
|
{
|
|
25889
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
|
+
}
|
|
25890
26196
|
return textResponse(
|
|
25891
26197
|
`## PAPI Zoom-Out Retrospective \u2014 Prepare Phase (Cycle ${result.cycleNumber})
|
|
25892
26198
|
|
|
@@ -26682,7 +26988,7 @@ var contributorRemoveTool = {
|
|
|
26682
26988
|
};
|
|
26683
26989
|
var contributorListTool = {
|
|
26684
26990
|
name: "contributor_list",
|
|
26685
|
-
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.",
|
|
26686
26992
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
26687
26993
|
inputSchema: {
|
|
26688
26994
|
type: "object",
|
|
@@ -26695,10 +27001,15 @@ var contributorListTool = {
|
|
|
26695
27001
|
required: []
|
|
26696
27002
|
}
|
|
26697
27003
|
};
|
|
26698
|
-
|
|
27004
|
+
function denyUnlessCohortCapable(adapter2) {
|
|
26699
27005
|
if (typeof adapter2.listContributors !== "function") {
|
|
26700
27006
|
return "Contributor management is not available on this adapter (requires the pg or proxy adapter \u2014 the local md adapter has no cohort).";
|
|
26701
27007
|
}
|
|
27008
|
+
return null;
|
|
27009
|
+
}
|
|
27010
|
+
async function denyUnlessOwner(adapter2, config2) {
|
|
27011
|
+
const capDenied = denyUnlessCohortCapable(adapter2);
|
|
27012
|
+
if (capDenied) return capDenied;
|
|
26702
27013
|
const gate = await resolveOwnerGate(adapter2, config2);
|
|
26703
27014
|
if (gate.enforced && !gate.callerIsOwner) {
|
|
26704
27015
|
const note = gate.resolutionError ? ` (Identity resolution failed: ${gate.resolutionError} \u2014 the gate fails closed; retry once connectivity is restored.)` : "";
|
|
@@ -26706,6 +27017,17 @@ async function denyUnlessOwner(adapter2, config2) {
|
|
|
26706
27017
|
}
|
|
26707
27018
|
return null;
|
|
26708
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
|
+
}
|
|
26709
27031
|
var EMAIL_SHAPE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
26710
27032
|
function requireEmail(args) {
|
|
26711
27033
|
const email = typeof args.email === "string" ? args.email.trim() : "";
|
|
@@ -26745,7 +27067,7 @@ async function handleContributorRemove(adapter2, config2, args) {
|
|
|
26745
27067
|
}
|
|
26746
27068
|
}
|
|
26747
27069
|
async function handleContributorList(adapter2, config2, _args) {
|
|
26748
|
-
const denied = await
|
|
27070
|
+
const denied = await denyUnlessMember(adapter2, config2);
|
|
26749
27071
|
if (denied) return errorResponse(denied);
|
|
26750
27072
|
try {
|
|
26751
27073
|
const entries = await adapter2.listContributors();
|
|
@@ -27360,7 +27682,10 @@ var LONG_RUNNING_TOOLS = /* @__PURE__ */ new Set([
|
|
|
27360
27682
|
"release",
|
|
27361
27683
|
"setup",
|
|
27362
27684
|
"doc_register",
|
|
27363
|
-
"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"
|
|
27364
27689
|
]);
|
|
27365
27690
|
function toolTimeoutMs(name) {
|
|
27366
27691
|
return LONG_RUNNING_TOOLS.has(name) ? LONG_TOOL_TIMEOUT_MS : DEFAULT_TOOL_TIMEOUT_MS;
|
package/dist/prompts.js
CHANGED
|
@@ -512,6 +512,16 @@ function buildPlanUserMessage(ctx) {
|
|
|
512
512
|
if (ctx.board) {
|
|
513
513
|
parts.push("### Board", "", ctx.board, "");
|
|
514
514
|
}
|
|
515
|
+
if (ctx.resurfacedBlockers) {
|
|
516
|
+
parts.push(
|
|
517
|
+
"### Ready to Unblock (typed blockers now resolved)",
|
|
518
|
+
"",
|
|
519
|
+
"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.",
|
|
520
|
+
"",
|
|
521
|
+
ctx.resurfacedBlockers,
|
|
522
|
+
""
|
|
523
|
+
);
|
|
524
|
+
}
|
|
515
525
|
if (ctx.candidateTaskFullNotes) {
|
|
516
526
|
parts.push("### Full Notes for Candidate Tasks", "", ctx.candidateTaskFullNotes, "");
|
|
517
527
|
}
|
|
@@ -751,6 +761,7 @@ IMPORTANT: You are running as a non-interactive API call. Do NOT ask the user qu
|
|
|
751
761
|
- **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.
|
|
752
762
|
- **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."
|
|
753
763
|
- **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.
|
|
764
|
+
- **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.
|
|
754
765
|
- **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.
|
|
755
766
|
- **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.
|
|
756
767
|
- **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.
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.45",
|
|
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
|
-
"mcpName": "io.github.
|
|
6
|
+
"mcpName": "io.github.getpapi/papi",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"exports": {
|
|
@@ -7,13 +7,13 @@ description: Invoke for cross-project patterns, dogfood-derived workflows, or ad
|
|
|
7
7
|
|
|
8
8
|
## When to Start a New Conversation
|
|
9
9
|
|
|
10
|
-
Your AI host manages its own context — it compacts automatically as a conversation grows, so you
|
|
10
|
+
Your AI host manages its own context — it compacts automatically as a conversation grows, so you almost never need to restart. **Stay in the same conversation.** Keep building, planning, and reviewing in one place; `orient` re-grounds you whenever you need it.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
The **only** trigger for suggesting a fresh conversation is a real context signal:
|
|
13
|
+
- Your host tells you it just compacted, or you notice earlier messages have dropped.
|
|
14
|
+
- You're near the context/window limit, or a verified context-usage reading is high.
|
|
15
15
|
|
|
16
|
-
Do NOT
|
|
16
|
+
Below that, say nothing about restarting. Do NOT suggest a fresh conversation because of the task type (a design session, a strategy review, "starting the next cycle"), the number of tasks built, elapsed time, or a sense that "we've done a lot" — none of those are context signals. A fresh conversation costs a full re-orient; only pay it when context pressure is actually there.
|
|
17
17
|
|
|
18
18
|
## Advanced Patterns
|
|
19
19
|
|
|
@@ -10,7 +10,7 @@ description: Invoke when running strategy_review or making Active Decision chang
|
|
|
10
10
|
Every 5 cycles, PAPI offers a strategy review — a deep analysis of velocity, estimation accuracy, active decisions, and project direction.
|
|
11
11
|
|
|
12
12
|
- **Don't skip them.** They're where compounding value comes from.
|
|
13
|
-
-
|
|
13
|
+
- Run the review in your current conversation — 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.
|
|
14
14
|
- Reviews produce recommendations that feed into the next plan.
|
|
15
15
|
- If the review recommends AD changes, use `strategy_change` to apply them.
|
|
16
16
|
|