@papi-ai/server 0.7.82 → 0.7.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backfill-cycle-metrics.js +195 -119
- package/dist/index.js +6899 -6172
- package/package.json +3 -3
|
@@ -8,6 +8,151 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// ../shared/dist/index.js
|
|
12
|
+
function isSelfHostedDeployment(value) {
|
|
13
|
+
return value === "1" || value?.toLowerCase() === "true";
|
|
14
|
+
}
|
|
15
|
+
function optionalBoolean(value, fallback) {
|
|
16
|
+
if (value == null || value.trim() === "") return fallback;
|
|
17
|
+
return isSelfHostedDeployment(value);
|
|
18
|
+
}
|
|
19
|
+
function cleanUrl(value) {
|
|
20
|
+
const cleaned = value?.trim().replace(/\/+$/, "");
|
|
21
|
+
return cleaned || void 0;
|
|
22
|
+
}
|
|
23
|
+
function resolveDeploymentProfile(env) {
|
|
24
|
+
const selfHosted = isSelfHostedDeployment(env.selfHosted);
|
|
25
|
+
const hostedSupabaseUrl = cleanUrl(env.hostedSupabaseUrl);
|
|
26
|
+
return {
|
|
27
|
+
kind: selfHosted ? "self-hosted" : "hosted",
|
|
28
|
+
appUrl: cleanUrl(env.appUrl) ?? (selfHosted ? void 0 : HOSTED_APP_URL),
|
|
29
|
+
mcpUrl: cleanUrl(env.mcpUrl) ?? (selfHosted ? void 0 : HOSTED_MCP_URL),
|
|
30
|
+
dataEndpoint: cleanUrl(env.dataEndpoint) ?? (hostedSupabaseUrl ? `${hostedSupabaseUrl}/functions/v1/data-proxy` : void 0) ?? (selfHosted ? void 0 : `${HOSTED_SUPABASE_URL}/functions/v1/data-proxy`),
|
|
31
|
+
providers: {
|
|
32
|
+
vercel: optionalBoolean(env.vercelEnabled, !selfHosted),
|
|
33
|
+
railway: optionalBoolean(env.railwayEnabled, !selfHosted),
|
|
34
|
+
managedSupabase: optionalBoolean(env.managedSupabaseEnabled, !selfHosted)
|
|
35
|
+
},
|
|
36
|
+
auth: {
|
|
37
|
+
emailPassword: optionalBoolean(env.emailPasswordEnabled, true),
|
|
38
|
+
emailSignup: optionalBoolean(env.emailSignupEnabled, false),
|
|
39
|
+
google: optionalBoolean(env.googleAuthEnabled, !selfHosted),
|
|
40
|
+
github: optionalBoolean(env.githubAuthEnabled, !selfHosted)
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function effortOrdinal(effort) {
|
|
45
|
+
if (typeof effort !== "string") return void 0;
|
|
46
|
+
const normalized = effort.trim().toUpperCase();
|
|
47
|
+
return EFFORT_SCALE[normalized];
|
|
48
|
+
}
|
|
49
|
+
function isUnparsedEffort(effort) {
|
|
50
|
+
if (typeof effort !== "string" || effort.trim().length === 0) return false;
|
|
51
|
+
return effortOrdinal(effort) === void 0;
|
|
52
|
+
}
|
|
53
|
+
function calculateCycleMetrics(reports, currentCycle, window = 5) {
|
|
54
|
+
const recentReports = reports.filter(
|
|
55
|
+
(r) => r.cycle > currentCycle - window && r.cycle <= currentCycle
|
|
56
|
+
);
|
|
57
|
+
const unparsedEffortCount = recentReports.filter(
|
|
58
|
+
(r) => isUnparsedEffort(r.actualEffort) || isUnparsedEffort(r.estimatedEffort)
|
|
59
|
+
).length;
|
|
60
|
+
const perCycle = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const r of recentReports) {
|
|
62
|
+
const group = perCycle.get(r.cycle) ?? [];
|
|
63
|
+
group.push(r);
|
|
64
|
+
perCycle.set(r.cycle, group);
|
|
65
|
+
}
|
|
66
|
+
const accuracy = [];
|
|
67
|
+
const velocity = [];
|
|
68
|
+
const sortedCycles = [...perCycle.keys()].sort((a, b) => a - b);
|
|
69
|
+
for (const cycle of sortedCycles) {
|
|
70
|
+
const reps = perCycle.get(cycle);
|
|
71
|
+
const deltas = [];
|
|
72
|
+
for (const r of reps) {
|
|
73
|
+
const actual = effortOrdinal(r.actualEffort);
|
|
74
|
+
const estimated = effortOrdinal(r.estimatedEffort);
|
|
75
|
+
if (actual !== void 0 && estimated !== void 0) {
|
|
76
|
+
deltas.push(actual - estimated);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (deltas.length > 0) {
|
|
80
|
+
accuracy.push({
|
|
81
|
+
cycle,
|
|
82
|
+
reports: deltas.length,
|
|
83
|
+
matchRate: Math.round(deltas.filter((d) => d === 0).length / deltas.length * 100),
|
|
84
|
+
mae: Math.round(deltas.reduce((s, d) => s + Math.abs(d), 0) / deltas.length * 10) / 10,
|
|
85
|
+
bias: Math.round(deltas.reduce((s, d) => s + d, 0) / deltas.length * 10) / 10
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
velocity.push({
|
|
89
|
+
cycle,
|
|
90
|
+
completed: reps.filter((r) => r.completed === "Yes").length,
|
|
91
|
+
partial: reps.filter((r) => r.completed === "Partial").length,
|
|
92
|
+
failed: reps.filter((r) => r.completed === "No").length,
|
|
93
|
+
effortPoints: reps.reduce((s, r) => s + (effortOrdinal(r.actualEffort) ?? 0), 0)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return { accuracy, velocity, unparsedEffortCount };
|
|
97
|
+
}
|
|
98
|
+
var CAPABILITY_REGISTRY, CAPABILITY_KEYS, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, WAB_WINDOW_DAYS, WAB_WEEK_MS, EFFORT_SCALE;
|
|
99
|
+
var init_dist = __esm({
|
|
100
|
+
"../shared/dist/index.js"() {
|
|
101
|
+
"use strict";
|
|
102
|
+
CAPABILITY_REGISTRY = [
|
|
103
|
+
{ key: "prReviewer", label: "Auto code review", description: "Runs a code review of the branch diff before accepting.", step: "review" },
|
|
104
|
+
{ key: "securityScan", label: "Security scan", description: "Flags a security pass on risk-tier changes at review time.", step: "review" },
|
|
105
|
+
{ key: "changelog", label: "Changelog & cycle update", description: "Curates a cycle-update post when a release ships.", step: "release" },
|
|
106
|
+
{ key: "verifyHealthCheck", label: "Release health check", description: "Reminds you to verify cycle state before release.", step: "release" },
|
|
107
|
+
{ key: "gestaltPreBuild", label: "Gestalt pre-build check", description: "Reads the whole cycle before building the first task.", step: "build" },
|
|
108
|
+
{ key: "batchBuildRollup", label: "Batch build rollup", description: "Emits a cross-task summary after a batch build.", step: "build" },
|
|
109
|
+
{ key: "modelRecommendation", label: "Model recommendation", description: "Suggests a model tier for each task.", step: "build" },
|
|
110
|
+
{ key: "discoveredIssues", label: "Discovered issues surfacing", description: "Surfaces issues logged during builds at release.", step: "release" },
|
|
111
|
+
{ key: "publishDirective", label: "Publish & registry updates", description: "Announces the release and updates MCP registry listings.", step: "release" },
|
|
112
|
+
// task-2482 (C319): the release quality gate. When on AND a gate command is
|
|
113
|
+
// configured (PAPI_GATE), release makes the host LLM run that command and
|
|
114
|
+
// BLOCKS the tag/merge on failure (fail-closed, AD-58 — PAPI never runs it).
|
|
115
|
+
{ key: "releaseGate", label: "Release quality gate", description: "Runs your test/build command before release and blocks on failure.", step: "release" },
|
|
116
|
+
// task-2491 (C320): post-release deploy hook — the deploy half of the ship→verify
|
|
117
|
+
// loop. UNLIKE every other capability this one defaults OFF (defaultEnabled:false):
|
|
118
|
+
// running a deploy command is destructive, so it is strictly opt-in. When ON AND
|
|
119
|
+
// papi.deploy (PAPI_DEPLOY) is set, release emits a directive telling the HOST to
|
|
120
|
+
// run the deploy command AFTER the merge/tag (AD-58 — PAPI never runs it) and
|
|
121
|
+
// records a deploy_hook progress step so the reactive hub shows the deploy step.
|
|
122
|
+
{ key: "deployHook", label: "Post-release deploy", description: "Runs your deploy command after a release merges.", step: "release", defaultEnabled: false },
|
|
123
|
+
// task-2833 (C341): enforce the handoff's acceptanceCriteria[] at build-complete.
|
|
124
|
+
// When ON, a completed:"yes" build whose handoff lists acceptance criteria must
|
|
125
|
+
// pass acceptance_confirmed:true or build_execute returns the checklist and does
|
|
126
|
+
// NOT mark the task Done (non-destructive — the report is not discarded). Defaults
|
|
127
|
+
// OFF: it changes the completion contract, so it is strictly opt-in until a project
|
|
128
|
+
// chooses to hold builds to their own acceptance criteria.
|
|
129
|
+
{ key: "acceptanceGate", label: "Acceptance-criteria gate", description: "Requires confirming the handoff acceptance criteria before a build completes.", step: "build", defaultEnabled: false },
|
|
130
|
+
// task-2325 (C344): configurable workflow guardrails — let a user opt out of the
|
|
131
|
+
// git/branch/commit/PR ceremony and PAPI-meta framing so PAPI adapts to their
|
|
132
|
+
// workflow instead of imposing one. Every toggle DEFAULTS ON (no defaultEnabled)
|
|
133
|
+
// so current behaviour is byte-identical until the user flips it off. The no-git
|
|
134
|
+
// fallback that these ride on top of is a separate task (task-2353).
|
|
135
|
+
{ key: "autoBranch", label: "Auto branch", description: "Creates a feature branch per task/cycle at build start.", step: "build" },
|
|
136
|
+
{ key: "autoCommit", label: "Auto commit", description: "Commits your work automatically when a build completes.", step: "build" },
|
|
137
|
+
{ key: "autoPush", label: "Auto push & PR", description: "Pushes the branch and opens a pull request on build complete.", step: "build" },
|
|
138
|
+
{ key: "papiMetaFraming", label: "PAPI self-referential framing", description: "Includes PAPI-meta build-discipline framing in handoff output.", step: "build" }
|
|
139
|
+
];
|
|
140
|
+
CAPABILITY_KEYS = CAPABILITY_REGISTRY.map((c) => c.key);
|
|
141
|
+
HOSTED_APP_URL = "https://getpapi.ai";
|
|
142
|
+
HOSTED_MCP_URL = "https://mcp.getpapi.ai";
|
|
143
|
+
HOSTED_SUPABASE_URL = "https://guewgygcpcmrcoppihzx.supabase.co";
|
|
144
|
+
WAB_WINDOW_DAYS = 7;
|
|
145
|
+
WAB_WEEK_MS = WAB_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
|
|
146
|
+
EFFORT_SCALE = {
|
|
147
|
+
XS: 1,
|
|
148
|
+
S: 2,
|
|
149
|
+
M: 3,
|
|
150
|
+
L: 4,
|
|
151
|
+
XL: 5
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
11
156
|
// src/lib/git.ts
|
|
12
157
|
var git_exports = {};
|
|
13
158
|
__export(git_exports, {
|
|
@@ -69,6 +214,7 @@ __export(git_exports, {
|
|
|
69
214
|
listGroupedCycleBranches: () => listGroupedCycleBranches,
|
|
70
215
|
listOpenPullRequests: () => listOpenPullRequests,
|
|
71
216
|
listOrphanFeatBranches: () => listOrphanFeatBranches,
|
|
217
|
+
memberBranchSlug: () => memberBranchSlug,
|
|
72
218
|
mergePullRequest: () => mergePullRequest,
|
|
73
219
|
normalizeGitUrl: () => normalizeGitUrl,
|
|
74
220
|
pickModuleCycleBranch: () => pickModuleCycleBranch,
|
|
@@ -961,9 +1107,20 @@ function detectUnrecordedCommits(cwd, baseBranch) {
|
|
|
961
1107
|
function taskBranchName(taskId) {
|
|
962
1108
|
return `feat/${taskId}`;
|
|
963
1109
|
}
|
|
964
|
-
function cycleBranchName(cycleNumber, module) {
|
|
1110
|
+
function cycleBranchName(cycleNumber, module, memberSlug) {
|
|
965
1111
|
const slug = module.toLowerCase().replace(/&/g, "and").replace(/&/g, "and").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
966
|
-
|
|
1112
|
+
const member = memberSlug ? `-${memberSlug}` : "";
|
|
1113
|
+
return `feat/cycle-${cycleNumber}-${slug}${member}`;
|
|
1114
|
+
}
|
|
1115
|
+
function memberBranchSlug(member) {
|
|
1116
|
+
const sanitise = (raw) => raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 20).replace(/-+$/g, "");
|
|
1117
|
+
const fromName = member.displayName ? sanitise(member.displayName) : "";
|
|
1118
|
+
if (fromName) return fromName;
|
|
1119
|
+
const localPart = member.email ? member.email.split("@")[0] : "";
|
|
1120
|
+
const fromEmail = localPart ? sanitise(localPart) : "";
|
|
1121
|
+
if (fromEmail) return fromEmail;
|
|
1122
|
+
const fromId = member.userId ? sanitise(member.userId).slice(0, 8) : "";
|
|
1123
|
+
return fromId || void 0;
|
|
967
1124
|
}
|
|
968
1125
|
function getHeadCommitSha(cwd) {
|
|
969
1126
|
try {
|
|
@@ -1086,9 +1243,9 @@ function isBranchContentAlreadyInBase(cwd, branch, baseBranch) {
|
|
|
1086
1243
|
return false;
|
|
1087
1244
|
}
|
|
1088
1245
|
}
|
|
1089
|
-
function pickModuleCycleBranch(candidates, cycleNumber, module) {
|
|
1246
|
+
function pickModuleCycleBranch(candidates, cycleNumber, module, memberSlug) {
|
|
1090
1247
|
if (candidates.length === 0) return void 0;
|
|
1091
|
-
const expected = cycleBranchName(cycleNumber, module);
|
|
1248
|
+
const expected = cycleBranchName(cycleNumber, module, memberSlug);
|
|
1092
1249
|
return candidates.find((b) => b === expected);
|
|
1093
1250
|
}
|
|
1094
1251
|
function listOrphanFeatBranches(cwd, baseBranch) {
|
|
@@ -1230,14 +1387,12 @@ function reportTelemetryEmitFailure(reason, ctx) {
|
|
|
1230
1387
|
function noteTelemetryEmitSuccess() {
|
|
1231
1388
|
consecutiveTelemetryFailures = 0;
|
|
1232
1389
|
}
|
|
1233
|
-
var
|
|
1390
|
+
var consecutiveTelemetryFailures, TELEMETRY_BLACKOUT_THRESHOLD;
|
|
1234
1391
|
var init_telemetry = __esm({
|
|
1235
1392
|
"src/lib/telemetry.ts"() {
|
|
1236
1393
|
"use strict";
|
|
1237
1394
|
init_install_id();
|
|
1238
|
-
|
|
1239
|
-
DEFAULT_TELEMETRY_ENDPOINT = `${HOSTED_SUPABASE_URL}/functions/v1/data-proxy`;
|
|
1240
|
-
MD_PINGS_SUPABASE_URL = process.env["PAPI_MD_PINGS_URL"] ?? HOSTED_SUPABASE_URL;
|
|
1395
|
+
init_dist();
|
|
1241
1396
|
consecutiveTelemetryFailures = 0;
|
|
1242
1397
|
TELEMETRY_BLACKOUT_THRESHOLD = 3;
|
|
1243
1398
|
}
|
|
@@ -1493,6 +1648,13 @@ var init_proxy_adapter = __esm({
|
|
|
1493
1648
|
"commitReviewSubmit",
|
|
1494
1649
|
"commitRelease",
|
|
1495
1650
|
// (2) not-yet-wired hosted gaps — shrink as data-proxy handlers land (task-2390).
|
|
1651
|
+
// These Active Decision transaction helpers exist only on the direct-pg adapter.
|
|
1652
|
+
// The strategy service probes for them structurally and deliberately falls back to
|
|
1653
|
+
// the hosted-safe read/upsert paths when they are absent. Exposing them through the
|
|
1654
|
+
// generic trap sends an unwired method to data-proxy and turns that fallback into a
|
|
1655
|
+
// 403, blocking strategy_change and strategy_review on the canonical remote MCP.
|
|
1656
|
+
"allocateActiveDecision",
|
|
1657
|
+
"applyActiveDecisionUpdates",
|
|
1496
1658
|
// getToolCallCount + updatePhaseStatus are ABSENT: they now have edge handlers and
|
|
1497
1659
|
// are served through the forwarder (getToolCallCount = SUP-2026-026 handler #1).
|
|
1498
1660
|
"getContributorRole",
|
|
@@ -2281,8 +2443,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2281
2443
|
async listContributors() {
|
|
2282
2444
|
return this.invoke("listContributors", []);
|
|
2283
2445
|
}
|
|
2284
|
-
async addContributorByEmail(email) {
|
|
2285
|
-
return this.invoke("addContributorByEmail", [email]);
|
|
2446
|
+
async addContributorByEmail(email, role) {
|
|
2447
|
+
return this.invoke("addContributorByEmail", [email, role]);
|
|
2286
2448
|
}
|
|
2287
2449
|
async removeContributorByEmail(email) {
|
|
2288
2450
|
return this.invoke("removeContributorByEmail", [email]);
|
|
@@ -2366,6 +2528,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2366
2528
|
import { pathToFileURL } from "url";
|
|
2367
2529
|
|
|
2368
2530
|
// src/adapter-factory.ts
|
|
2531
|
+
init_dist();
|
|
2369
2532
|
import path2 from "path";
|
|
2370
2533
|
import { execSync } from "child_process";
|
|
2371
2534
|
|
|
@@ -2450,8 +2613,18 @@ function detectUserId() {
|
|
|
2450
2613
|
}
|
|
2451
2614
|
return void 0;
|
|
2452
2615
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2616
|
+
function serverDeploymentProfile() {
|
|
2617
|
+
return resolveDeploymentProfile({
|
|
2618
|
+
selfHosted: process.env["PAPI_SELF_HOST"],
|
|
2619
|
+
appUrl: process.env["PAPI_DASHBOARD_URL"],
|
|
2620
|
+
mcpUrl: process.env["NEXT_PUBLIC_MCP_URL"],
|
|
2621
|
+
dataEndpoint: process.env["PAPI_DATA_ENDPOINT"],
|
|
2622
|
+
hostedSupabaseUrl: process.env["PAPI_HOSTED_SUPABASE_URL"],
|
|
2623
|
+
vercelEnabled: process.env["PAPI_ENABLE_VERCEL"],
|
|
2624
|
+
railwayEnabled: process.env["PAPI_ENABLE_RAILWAY"],
|
|
2625
|
+
managedSupabaseEnabled: process.env["PAPI_ENABLE_MANAGED_SUPABASE"]
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2455
2628
|
var PLACEHOLDER_PATTERNS = [
|
|
2456
2629
|
"<YOUR_DATABASE_URL>",
|
|
2457
2630
|
"your-database-url",
|
|
@@ -2687,7 +2860,8 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2687
2860
|
}
|
|
2688
2861
|
case "proxy": {
|
|
2689
2862
|
const { ProxyPapiAdapter: ProxyPapiAdapter2, wrapWithForwarding: wrapWithForwarding2 } = await Promise.resolve().then(() => (init_proxy_adapter(), proxy_adapter_exports));
|
|
2690
|
-
const
|
|
2863
|
+
const deployment = serverDeploymentProfile();
|
|
2864
|
+
const dashboardUrl = deployment.appUrl;
|
|
2691
2865
|
const projectId = process.env["PAPI_PROJECT_ID"];
|
|
2692
2866
|
const dataApiKey = process.env["PAPI_DATA_API_KEY"];
|
|
2693
2867
|
if (!dataApiKey) {
|
|
@@ -2695,14 +2869,19 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
|
|
|
2695
2869
|
`PAPI needs an account to store your project data.
|
|
2696
2870
|
|
|
2697
2871
|
Get started in 3 steps:
|
|
2698
|
-
1. Sign up at ${dashboardUrl}/login
|
|
2872
|
+
1. Sign up at ${dashboardUrl ? `${dashboardUrl}/login` : "your configured PAPI dashboard"}
|
|
2699
2873
|
2. Complete the onboarding wizard \u2014 it generates your .mcp.json config
|
|
2700
2874
|
3. Download the config, place it in your project root, and restart Claude Code
|
|
2701
2875
|
|
|
2702
2876
|
Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json env config.`
|
|
2703
2877
|
);
|
|
2704
2878
|
}
|
|
2705
|
-
const dataEndpoint =
|
|
2879
|
+
const dataEndpoint = deployment.dataEndpoint;
|
|
2880
|
+
if (!dataEndpoint) {
|
|
2881
|
+
throw new Error(
|
|
2882
|
+
"PAPI_DATA_ENDPOINT is required for the proxy adapter in a self-hosted deployment. Set it to the deployment-owned data-proxy URL; hosted production defaults are intentionally disabled."
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2706
2885
|
const adapter = new ProxyPapiAdapter2({
|
|
2707
2886
|
endpoint: dataEndpoint,
|
|
2708
2887
|
apiKey: dataApiKey,
|
|
@@ -2800,111 +2979,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
2800
2979
|
}
|
|
2801
2980
|
}
|
|
2802
2981
|
|
|
2803
|
-
// ../shared/dist/index.js
|
|
2804
|
-
var CAPABILITY_REGISTRY = [
|
|
2805
|
-
{ key: "prReviewer", label: "Auto code review", description: "Runs a code review of the branch diff before accepting.", step: "review" },
|
|
2806
|
-
{ key: "securityScan", label: "Security scan", description: "Flags a security pass on risk-tier changes at review time.", step: "review" },
|
|
2807
|
-
{ key: "changelog", label: "Changelog & cycle update", description: "Curates a cycle-update post when a release ships.", step: "release" },
|
|
2808
|
-
{ key: "verifyHealthCheck", label: "Release health check", description: "Reminds you to verify cycle state before release.", step: "release" },
|
|
2809
|
-
{ key: "gestaltPreBuild", label: "Gestalt pre-build check", description: "Reads the whole cycle before building the first task.", step: "build" },
|
|
2810
|
-
{ key: "batchBuildRollup", label: "Batch build rollup", description: "Emits a cross-task summary after a batch build.", step: "build" },
|
|
2811
|
-
{ key: "modelRecommendation", label: "Model recommendation", description: "Suggests a model tier for each task.", step: "build" },
|
|
2812
|
-
{ key: "discoveredIssues", label: "Discovered issues surfacing", description: "Surfaces issues logged during builds at release.", step: "release" },
|
|
2813
|
-
{ key: "publishDirective", label: "Publish & registry updates", description: "Announces the release and updates MCP registry listings.", step: "release" },
|
|
2814
|
-
// task-2482 (C319): the release quality gate. When on AND a gate command is
|
|
2815
|
-
// configured (PAPI_GATE), release makes the host LLM run that command and
|
|
2816
|
-
// BLOCKS the tag/merge on failure (fail-closed, AD-58 — PAPI never runs it).
|
|
2817
|
-
{ key: "releaseGate", label: "Release quality gate", description: "Runs your test/build command before release and blocks on failure.", step: "release" },
|
|
2818
|
-
// task-2491 (C320): post-release deploy hook — the deploy half of the ship→verify
|
|
2819
|
-
// loop. UNLIKE every other capability this one defaults OFF (defaultEnabled:false):
|
|
2820
|
-
// running a deploy command is destructive, so it is strictly opt-in. When ON AND
|
|
2821
|
-
// papi.deploy (PAPI_DEPLOY) is set, release emits a directive telling the HOST to
|
|
2822
|
-
// run the deploy command AFTER the merge/tag (AD-58 — PAPI never runs it) and
|
|
2823
|
-
// records a deploy_hook progress step so the reactive hub shows the deploy step.
|
|
2824
|
-
{ key: "deployHook", label: "Post-release deploy", description: "Runs your deploy command after a release merges.", step: "release", defaultEnabled: false },
|
|
2825
|
-
// task-2833 (C341): enforce the handoff's acceptanceCriteria[] at build-complete.
|
|
2826
|
-
// When ON, a completed:"yes" build whose handoff lists acceptance criteria must
|
|
2827
|
-
// pass acceptance_confirmed:true or build_execute returns the checklist and does
|
|
2828
|
-
// NOT mark the task Done (non-destructive — the report is not discarded). Defaults
|
|
2829
|
-
// OFF: it changes the completion contract, so it is strictly opt-in until a project
|
|
2830
|
-
// chooses to hold builds to their own acceptance criteria.
|
|
2831
|
-
{ key: "acceptanceGate", label: "Acceptance-criteria gate", description: "Requires confirming the handoff acceptance criteria before a build completes.", step: "build", defaultEnabled: false },
|
|
2832
|
-
// task-2325 (C344): configurable workflow guardrails — let a user opt out of the
|
|
2833
|
-
// git/branch/commit/PR ceremony and PAPI-meta framing so PAPI adapts to their
|
|
2834
|
-
// workflow instead of imposing one. Every toggle DEFAULTS ON (no defaultEnabled)
|
|
2835
|
-
// so current behaviour is byte-identical until the user flips it off. The no-git
|
|
2836
|
-
// fallback that these ride on top of is a separate task (task-2353).
|
|
2837
|
-
{ key: "autoBranch", label: "Auto branch", description: "Creates a feature branch per task/cycle at build start.", step: "build" },
|
|
2838
|
-
{ key: "autoCommit", label: "Auto commit", description: "Commits your work automatically when a build completes.", step: "build" },
|
|
2839
|
-
{ key: "autoPush", label: "Auto push & PR", description: "Pushes the branch and opens a pull request on build complete.", step: "build" },
|
|
2840
|
-
{ key: "papiMetaFraming", label: "PAPI self-referential framing", description: "Includes PAPI-meta build-discipline framing in handoff output.", step: "build" }
|
|
2841
|
-
];
|
|
2842
|
-
var CAPABILITY_KEYS = CAPABILITY_REGISTRY.map((c) => c.key);
|
|
2843
|
-
var WAB_WINDOW_DAYS = 7;
|
|
2844
|
-
var WAB_WEEK_MS = WAB_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
|
|
2845
|
-
var EFFORT_SCALE = {
|
|
2846
|
-
XS: 1,
|
|
2847
|
-
S: 2,
|
|
2848
|
-
M: 3,
|
|
2849
|
-
L: 4,
|
|
2850
|
-
XL: 5
|
|
2851
|
-
};
|
|
2852
|
-
function effortOrdinal(effort) {
|
|
2853
|
-
if (typeof effort !== "string") return void 0;
|
|
2854
|
-
const normalized = effort.trim().toUpperCase();
|
|
2855
|
-
return EFFORT_SCALE[normalized];
|
|
2856
|
-
}
|
|
2857
|
-
function isUnparsedEffort(effort) {
|
|
2858
|
-
if (typeof effort !== "string" || effort.trim().length === 0) return false;
|
|
2859
|
-
return effortOrdinal(effort) === void 0;
|
|
2860
|
-
}
|
|
2861
|
-
function calculateCycleMetrics(reports, currentCycle, window = 5) {
|
|
2862
|
-
const recentReports = reports.filter(
|
|
2863
|
-
(r) => r.cycle > currentCycle - window && r.cycle <= currentCycle
|
|
2864
|
-
);
|
|
2865
|
-
const unparsedEffortCount = recentReports.filter(
|
|
2866
|
-
(r) => isUnparsedEffort(r.actualEffort) || isUnparsedEffort(r.estimatedEffort)
|
|
2867
|
-
).length;
|
|
2868
|
-
const perCycle = /* @__PURE__ */ new Map();
|
|
2869
|
-
for (const r of recentReports) {
|
|
2870
|
-
const group = perCycle.get(r.cycle) ?? [];
|
|
2871
|
-
group.push(r);
|
|
2872
|
-
perCycle.set(r.cycle, group);
|
|
2873
|
-
}
|
|
2874
|
-
const accuracy = [];
|
|
2875
|
-
const velocity = [];
|
|
2876
|
-
const sortedCycles = [...perCycle.keys()].sort((a, b) => a - b);
|
|
2877
|
-
for (const cycle of sortedCycles) {
|
|
2878
|
-
const reps = perCycle.get(cycle);
|
|
2879
|
-
const deltas = [];
|
|
2880
|
-
for (const r of reps) {
|
|
2881
|
-
const actual = effortOrdinal(r.actualEffort);
|
|
2882
|
-
const estimated = effortOrdinal(r.estimatedEffort);
|
|
2883
|
-
if (actual !== void 0 && estimated !== void 0) {
|
|
2884
|
-
deltas.push(actual - estimated);
|
|
2885
|
-
}
|
|
2886
|
-
}
|
|
2887
|
-
if (deltas.length > 0) {
|
|
2888
|
-
accuracy.push({
|
|
2889
|
-
cycle,
|
|
2890
|
-
reports: deltas.length,
|
|
2891
|
-
matchRate: Math.round(deltas.filter((d) => d === 0).length / deltas.length * 100),
|
|
2892
|
-
mae: Math.round(deltas.reduce((s, d) => s + Math.abs(d), 0) / deltas.length * 10) / 10,
|
|
2893
|
-
bias: Math.round(deltas.reduce((s, d) => s + d, 0) / deltas.length * 10) / 10
|
|
2894
|
-
});
|
|
2895
|
-
}
|
|
2896
|
-
velocity.push({
|
|
2897
|
-
cycle,
|
|
2898
|
-
completed: reps.filter((r) => r.completed === "Yes").length,
|
|
2899
|
-
partial: reps.filter((r) => r.completed === "Partial").length,
|
|
2900
|
-
failed: reps.filter((r) => r.completed === "No").length,
|
|
2901
|
-
effortPoints: reps.reduce((s, r) => s + (effortOrdinal(r.actualEffort) ?? 0), 0)
|
|
2902
|
-
});
|
|
2903
|
-
}
|
|
2904
|
-
return { accuracy, velocity, unparsedEffortCount };
|
|
2905
|
-
}
|
|
2906
|
-
|
|
2907
2982
|
// src/lib/formatters.ts
|
|
2983
|
+
init_dist();
|
|
2908
2984
|
var PLAN_FULL_NOTES_BUDGET_BYTES = Number(process.env.PAPI_PLAN_NOTES_BUDGET) || 6e4;
|
|
2909
2985
|
function effortWeight(size) {
|
|
2910
2986
|
switch ((size || "").toUpperCase()) {
|