@papi-ai/server 0.7.80 → 0.7.82

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.
@@ -28,6 +28,7 @@ __export(git_exports, {
28
28
  ensureTagAtHead: () => ensureTagAtHead,
29
29
  findTaskCommitsOnBase: () => findTaskCommitsOnBase,
30
30
  getBranchDiff: () => getBranchDiff,
31
+ getCommitFiles: () => getCommitFiles,
31
32
  getCommitsSinceTag: () => getCommitsSinceTag,
32
33
  getCurrentBranch: () => getCurrentBranch,
33
34
  getDocPathsTouchedOnBranch: () => getDocPathsTouchedOnBranch,
@@ -41,11 +42,13 @@ __export(git_exports, {
41
42
  getModifiedFiles: () => getModifiedFiles,
42
43
  getOriginRepoSlug: () => getOriginRepoSlug,
43
44
  getOriginUrl: () => getOriginUrl,
45
+ getPathsDifferingFrom: () => getPathsDifferingFrom,
44
46
  getPullRequestUrl: () => getPullRequestUrl,
45
47
  getRemoteBranchFiles: () => getRemoteBranchFiles,
46
48
  getRootCommitHash: () => getRootCommitHash,
47
49
  getStagedFiles: () => getStagedFiles,
48
50
  getTagTarget: () => getTagTarget,
51
+ getTaskDiff: () => getTaskDiff,
49
52
  getTaskIdsOnBranch: () => getTaskIdsOnBranch,
50
53
  getTrackedModifiedFiles: () => getTrackedModifiedFiles,
51
54
  getUnmergedBranches: () => getUnmergedBranches,
@@ -60,6 +63,7 @@ __export(git_exports, {
60
63
  isGhAvailable: () => isGhAvailable,
61
64
  isGitAvailable: () => isGitAvailable,
62
65
  isGitRepo: () => isGitRepo,
66
+ isPathCommittedAnywhere: () => isPathCommittedAnywhere,
63
67
  isPathIgnored: () => isPathIgnored,
64
68
  isPathTracked: () => isPathTracked,
65
69
  listGroupedCycleBranches: () => listGroupedCycleBranches,
@@ -99,6 +103,22 @@ function isGitRepo(cwd) {
99
103
  return false;
100
104
  }
101
105
  }
106
+ function isPathCommittedAnywhere(cwd, path3) {
107
+ const run = (args) => {
108
+ try {
109
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
110
+ } catch {
111
+ return null;
112
+ }
113
+ };
114
+ const commit = run(["log", "--all", "--format=%H", "-1", "--", path3]);
115
+ if (!commit) return false;
116
+ const committedBlob = run(["rev-parse", `${commit}:${path3}`]);
117
+ if (!committedBlob) return false;
118
+ const onDiskBlob = run(["hash-object", "--", path3]);
119
+ if (!onDiskBlob) return false;
120
+ return committedBlob === onDiskBlob;
121
+ }
102
122
  function isPathTracked(cwd, path3) {
103
123
  try {
104
124
  execFileSync("git", ["ls-files", "--error-unmatch", "--", path3], {
@@ -271,6 +291,50 @@ function getBranchDiff(cwd, base = "origin/main", maxBytes = 2e5) {
271
291
  }
272
292
  return "";
273
293
  }
294
+ function getTaskDiff(cwd, taskId, base = "origin/main", maxBytes = 2e5) {
295
+ const truncate = (out) => out.length > maxBytes ? `${out.slice(0, maxBytes)}
296
+
297
+ ... [diff truncated at ${Math.round(maxBytes / 1024)} KB]` : out;
298
+ try {
299
+ const shas = execFileSync(
300
+ "git",
301
+ ["log", "--all", "--format=%H", `--grep=${taskId})`, "--fixed-strings"],
302
+ { cwd, encoding: "utf-8", maxBuffer: 8 * 1024 * 1024 }
303
+ ).split("\n").map((s) => s.trim()).filter(Boolean);
304
+ if (shas.length > 0) {
305
+ const parts = [];
306
+ for (const sha of [...shas].reverse()) {
307
+ try {
308
+ const one = execFileSync("git", ["diff", `${sha}^..${sha}`], {
309
+ cwd,
310
+ encoding: "utf-8",
311
+ maxBuffer: 32 * 1024 * 1024
312
+ });
313
+ if (one) parts.push(one);
314
+ } catch {
315
+ }
316
+ }
317
+ const out = parts.join("\n");
318
+ if (out) {
319
+ const range = shas.length === 1 ? shas[0].slice(0, 8) : `${shas[shas.length - 1].slice(0, 8)}\u2026${shas[0].slice(0, 8)}`;
320
+ return {
321
+ diff: truncate(out),
322
+ scope: "task-commits",
323
+ detail: `${shas.length} commit(s) for ${taskId} (${range}), each against its own parent`
324
+ };
325
+ }
326
+ }
327
+ } catch {
328
+ }
329
+ const branch = getCurrentBranch(cwd);
330
+ const diff = getBranchDiff(cwd, base, maxBytes);
331
+ if (!diff) return { diff: "", scope: "none", detail: "no diff resolved" };
332
+ return {
333
+ diff,
334
+ scope: "whole-branch",
335
+ detail: `no commit naming ${taskId} was found, so this is the ENTIRE diff of ${branch ?? "the current branch"} vs ${base} \u2014 it may include other tasks' work, and may not include this task's`
336
+ };
337
+ }
274
338
  function getHeadCommitSubject(cwd) {
275
339
  try {
276
340
  const out = execFileSync("git", ["log", "-1", "--format=%s"], {
@@ -318,6 +382,28 @@ function branchExists(cwd, branch) {
318
382
  return false;
319
383
  }
320
384
  }
385
+ function getPathsDifferingFrom(cwd, target) {
386
+ try {
387
+ const out = execFileSync("git", ["diff", "--name-only", "HEAD", target], {
388
+ cwd,
389
+ encoding: "utf-8"
390
+ });
391
+ return out.split("\n").map((l) => l.trim()).filter(Boolean);
392
+ } catch {
393
+ return null;
394
+ }
395
+ }
396
+ function getCommitFiles(cwd, ref = "HEAD") {
397
+ try {
398
+ const out = execFileSync("git", ["show", "--name-only", "--pretty=format:", ref], {
399
+ cwd,
400
+ encoding: "utf-8"
401
+ });
402
+ return out.split("\n").map((l) => l.trim()).filter(Boolean);
403
+ } catch {
404
+ return null;
405
+ }
406
+ }
321
407
  function checkoutBranch(cwd, branch) {
322
408
  try {
323
409
  execFileSync("git", ["checkout", branch], { cwd, encoding: "utf-8" });
@@ -1216,7 +1302,7 @@ function wrapWithForwarding(instance) {
1216
1302
  function createProxyAdapter(config) {
1217
1303
  return wrapWithForwarding(new ProxyPapiAdapter(config));
1218
1304
  }
1219
- var JSONB_PASSTHROUGH_KEYS, DISPLAY_ID_METHODS, NO_FORWARD, ProxyPapiAdapter;
1305
+ var JSONB_PASSTHROUGH_KEYS, DISPLAY_ID_METHODS, HOSTED_SERVED, NO_FORWARD, ProxyPapiAdapter;
1220
1306
  var init_proxy_adapter = __esm({
1221
1307
  "src/proxy-adapter.ts"() {
1222
1308
  "use strict";
@@ -1244,6 +1330,156 @@ var init_proxy_adapter = __esm({
1244
1330
  "getRecentReviews",
1245
1331
  "getActiveDecisions"
1246
1332
  ]);
1333
+ HOSTED_SERVED = /* @__PURE__ */ new Set([
1334
+ "actionRecommendation",
1335
+ "addAgendaTopic",
1336
+ "addContributorByEmail",
1337
+ "appendBuildReport",
1338
+ "appendCycleLearnings",
1339
+ "appendCycleMetrics",
1340
+ "appendDecisionEvent",
1341
+ "appendToolMetric",
1342
+ "archiveTasks",
1343
+ "claimTask",
1344
+ "clearPendingReviewResponse",
1345
+ "compressBuildReports",
1346
+ "compressCycleLog",
1347
+ "confirmPendingActiveDecisions",
1348
+ "correctLatestBuildReportEffort",
1349
+ "countDueOwnerActions",
1350
+ "countNudgedOwnerActions",
1351
+ "countOpenOwnerActions",
1352
+ "countOwnerActionsBlockingTasks",
1353
+ "countPlanRunsForCycle",
1354
+ "createConvention",
1355
+ "createCycle",
1356
+ "createHorizon",
1357
+ "createOwnerAction",
1358
+ "createStage",
1359
+ "createTask",
1360
+ "deleteActiveDecision",
1361
+ "deleteConvention",
1362
+ "deleteDoc",
1363
+ "dismissRecommendation",
1364
+ "findPendingDocActionsForTask",
1365
+ "getActiveDecisions",
1366
+ "getActiveStage",
1367
+ "getBuildReportCountForTask",
1368
+ "getBuildReportsSince",
1369
+ "getContextHashes",
1370
+ "getContextUtilisation",
1371
+ "getCostSnapshots",
1372
+ "getCostSummary",
1373
+ "getCurrentNorthStar",
1374
+ "getCycleHealth",
1375
+ "getCycleLearningPatterns",
1376
+ "getCycleLearnings",
1377
+ "getCycleLog",
1378
+ "getCycleLogSince",
1379
+ "getDecisionEvents",
1380
+ "getDecisionEventsSince",
1381
+ "getDecisionScorePatterns",
1382
+ "getDecisionScores",
1383
+ "getDecisionUsage",
1384
+ "getDoc",
1385
+ "getDocBody",
1386
+ "getDocBodyUsage",
1387
+ "getDogfoodLog",
1388
+ "getEstimationCalibration",
1389
+ "getFailedBuildAttemptsForTask",
1390
+ "getHarnessInventory",
1391
+ "getHarnessState",
1392
+ "getLastStrategyReviewCycle",
1393
+ "getLatestDecisionScores",
1394
+ "getModelOutcomeStats",
1395
+ "getModuleEstimationStats",
1396
+ "getNorthStarSetCycle",
1397
+ "getNorthStarStaleness",
1398
+ "getOwnerIdentity",
1399
+ "getPendingAgendaTopics",
1400
+ "getPendingRecommendations",
1401
+ "getPendingReviewResponse",
1402
+ "getPlanContextSummary",
1403
+ "getProjectInfo",
1404
+ "getProjectOwnerUserId",
1405
+ "getRecentBuildReports",
1406
+ "getRecentReviews",
1407
+ "getRecentTaskComments",
1408
+ "getRecommendationEffectiveness",
1409
+ "getStrategyReviews",
1410
+ "getTask",
1411
+ "getTasks",
1412
+ "getToolCallCount",
1413
+ "getUnactionedDogfoodEntries",
1414
+ "getUnnotifiedResolvedFeedback",
1415
+ "hasToolMilestone",
1416
+ "insertPlanRun",
1417
+ "insertToolRun",
1418
+ "linkOwnerActionToTask",
1419
+ "linkPhasesToStage",
1420
+ "listContributors",
1421
+ "listConventions",
1422
+ "listMyBugReports",
1423
+ "listOwnerActionsForBlockerScan",
1424
+ "logEntityReferences",
1425
+ "markAgendaTopicsAddressed",
1426
+ "markCycleLearningResolved",
1427
+ "markFeedbackNotified",
1428
+ "moveTask",
1429
+ "planWriteBack",
1430
+ "projectExists",
1431
+ "queryBoard",
1432
+ "readCycleMetrics",
1433
+ "readCycles",
1434
+ "readCycles",
1435
+ "readDiscoveryCanvas",
1436
+ "readHorizons",
1437
+ "readPhases",
1438
+ "readPlanningLog",
1439
+ "readProductBrief",
1440
+ "readRegistries",
1441
+ "readStages",
1442
+ "readToolMetrics",
1443
+ "recordProgressStep",
1444
+ "recordTransition",
1445
+ "registerDoc",
1446
+ "removeContributorByEmail",
1447
+ "reorderDocs",
1448
+ "replaceHarnessInventory",
1449
+ "resolveLearningsForDoneTasks",
1450
+ "savePendingReviewResponse",
1451
+ "searchDocs",
1452
+ "setCriterionMet",
1453
+ "setCycleHealth",
1454
+ "setHarnessState",
1455
+ "setProjectPapiDir",
1456
+ "storeDocBody",
1457
+ "submitBugReport",
1458
+ "unclaimTask",
1459
+ "updateActiveDecision",
1460
+ "updateCycleLearningActionRef",
1461
+ "updateDiscoveryCanvas",
1462
+ "updateDocAction",
1463
+ "updateDocStatus",
1464
+ "updateDogfoodEntryStatus",
1465
+ "updateHorizonStatus",
1466
+ "updatePhaseStatus",
1467
+ "updateProductBrief",
1468
+ "updateRegistries",
1469
+ "updateStageExitCriteria",
1470
+ "updateStageStatus",
1471
+ "updateTask",
1472
+ "updateTaskStatus",
1473
+ "upsertActiveDecision",
1474
+ "upsertNorthStar",
1475
+ "writeCycleLogEntry",
1476
+ "writeDecisionScore",
1477
+ "writeDogfoodEntries",
1478
+ "writePhases",
1479
+ "writeRecommendation",
1480
+ "writeReview",
1481
+ "writeStrategyReview"
1482
+ ]);
1247
1483
  NO_FORWARD = /* @__PURE__ */ new Set([
1248
1484
  // (1) local-only
1249
1485
  "close",
@@ -1272,7 +1508,7 @@ var init_proxy_adapter = __esm({
1272
1508
  "listContributorReleasePrs",
1273
1509
  "claimReview",
1274
1510
  "getSiblingAds",
1275
- "getSiblingRepoTasks"
1511
+ "getSiblingRepoTasks",
1276
1512
  // task-2828 (C339): attributed-intelligence analytics reader — pg-only that cycle.
1277
1513
  // task-2864 (C343): WIRED. getModelOutcomeStats now has an edge case handler (raw
1278
1514
  // SQL via postgres.js mirroring the pg query + inlined computeModelOutcomes bucketing)
@@ -1296,6 +1532,28 @@ var init_proxy_adapter = __esm({
1296
1532
  // (case handler + ALLOWED_METHODS/WRITE_METHODS entries), so it forwards for
1297
1533
  // hosted callers and persists a project-scoped cycle_progress_steps row. Removed
1298
1534
  // from NO_FORWARD (was the task-2484 pg-only gap) — hosted parity restored.
1535
+ //
1536
+ // (3) pg-only optimisations probed STRUCTURALLY with a local fallback beside them.
1537
+ // These are the dangerous class: the caller asks `typeof adapter.X === 'function'`
1538
+ // to decide whether the fast path exists, and the get-trap answers "yes" for any
1539
+ // name absent from this set. The probe then passes, the call forwards, and the
1540
+ // edge 403s — while a working fallback sits a few lines below, unreachable.
1541
+ // A method belongs here when BOTH are true: it is probed by `typeof` rather than
1542
+ // called unconditionally, and the probe's else-branch is a real fallback.
1543
+ //
1544
+ // task-3290 (C361): allocateActiveDecision + applyActiveDecisionUpdates are pg-only
1545
+ // (atomic id allocation; transactional batch apply). Both are probed in
1546
+ // services/strategy.ts — asDecisionIdAllocator and asDecisionBatchApplier — and both
1547
+ // have read-then-write / sequential fallbacks. Hosted users hit a 403 minting ANY
1548
+ // Active Decision via setup's AD seed or strategy_change until these were listed.
1549
+ "allocateActiveDecision",
1550
+ "applyActiveDecisionUpdates",
1551
+ // task-3290 (C361), same sweep: getLastZoomOutCycle was documented in the parity
1552
+ // ledger as "proxy NO_FORWARD → safe no-op" while NOT being in this set. It is
1553
+ // optional-chain probed in services/health.ts and wrapped in try/catch, so it
1554
+ // degraded rather than crashed — but every hosted `orient` paid a round-trip to
1555
+ // earn a silent 403. Listing it makes the ledger's claim true and skips the trip.
1556
+ "getLastZoomOutCycle"
1299
1557
  ]);
1300
1558
  ProxyPapiAdapter = class _ProxyPapiAdapter {
1301
1559
  endpoint;
@@ -1308,6 +1566,30 @@ var init_proxy_adapter = __esm({
1308
1566
  this.projectId = config.projectId ?? "";
1309
1567
  this.onAuthRejected = config.onAuthRejected;
1310
1568
  }
1569
+ /**
1570
+ * Does the hosted path genuinely serve `name`? (task-3022, C362)
1571
+ *
1572
+ * This is the honest answer to the question `typeof adapter.name === 'function'`
1573
+ * USED to ask and could not answer: the get-trap below manufactures a function
1574
+ * for any name absent from NO_FORWARD, so a structural probe reads true for
1575
+ * pg-only methods and the caller then earns a 403 from the edge.
1576
+ *
1577
+ * Answered from NO_FORWARD itself, deliberately — the same set the forwarder
1578
+ * consults — so this can never disagree with what an actual call would do. A
1579
+ * name in NO_FORWARD is either local-only or not yet wired hosted; either way
1580
+ * the caller must take its fallback. The proxy-parity test guarantees every
1581
+ * method is an explicit wrapper, in NO_FORWARD, or in the edge allowlist, so
1582
+ * "not in NO_FORWARD" is a sound proxy for "served hosted".
1583
+ *
1584
+ * A REAL method, not a forwarded one: Reflect.get in the trap returns it before
1585
+ * the forwarding branch is reached, so callers get this implementation rather
1586
+ * than a forwarder that would POST "supportsMethod" to the edge.
1587
+ */
1588
+ supportsMethod(name) {
1589
+ if (NO_FORWARD.has(name)) return false;
1590
+ if (name in _ProxyPapiAdapter.prototype) return true;
1591
+ return HOSTED_SERVED.has(name);
1592
+ }
1311
1593
  /**
1312
1594
  * task-1773: bearer-only auth probe. Hits the USER-scoped `project-list` route
1313
1595
  * (no projectId needed), so it answers exactly one question: does the proxy
@@ -1827,6 +2109,21 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1827
2109
  updateDogfoodEntryStatus(id, status, linkedTaskId) {
1828
2110
  return this.invoke("updateDogfoodEntryStatus", [id, status, linkedTaskId]);
1829
2111
  }
2112
+ // --- Project conventions (task-3271) ---
2113
+ //
2114
+ // Wired to the edge handler from the start rather than parked in NO_FORWARD.
2115
+ // The hosted remote connector is the only install path an external user has,
2116
+ // so a local-only conventions store would be a feature nobody in the actual
2117
+ // user base can reach.
2118
+ listConventions() {
2119
+ return this.invoke("listConventions");
2120
+ }
2121
+ createConvention(convention) {
2122
+ return this.invoke("createConvention", [convention]);
2123
+ }
2124
+ deleteConvention(id) {
2125
+ return this.invoke("deleteConvention", [id]);
2126
+ }
1830
2127
  // --- Harness inventory (task-1896) ---
1831
2128
  getHarnessInventory() {
1832
2129
  return this.invoke("getHarnessInventory");