@papi-ai/server 0.4.0-alpha → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +353 -134
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4396,7 +4396,9 @@ function definedEntries(obj) {
|
|
|
4396
4396
|
async function ensureSchema(config2) {
|
|
4397
4397
|
const sql = src_default(config2.connectionString, {
|
|
4398
4398
|
max: 1,
|
|
4399
|
-
connect_timeout: 10
|
|
4399
|
+
connect_timeout: 10,
|
|
4400
|
+
prepare: false
|
|
4401
|
+
// Supabase PgBouncer transaction mode invalidates prepared statements
|
|
4400
4402
|
});
|
|
4401
4403
|
try {
|
|
4402
4404
|
const [result] = await sql`
|
|
@@ -4702,8 +4704,10 @@ var init_dist3 = __esm({
|
|
|
4702
4704
|
max: config2.maxConnections ?? 10,
|
|
4703
4705
|
idle_timeout: 20,
|
|
4704
4706
|
// Release idle connections after 20s
|
|
4705
|
-
connect_timeout: 10
|
|
4707
|
+
connect_timeout: 10,
|
|
4706
4708
|
// Fail fast if DB unreachable
|
|
4709
|
+
prepare: false
|
|
4710
|
+
// Supabase PgBouncer transaction mode invalidates prepared statements
|
|
4707
4711
|
});
|
|
4708
4712
|
}
|
|
4709
4713
|
// -------------------------------------------------------------------------
|
|
@@ -4711,12 +4715,12 @@ var init_dist3 = __esm({
|
|
|
4711
4715
|
// -------------------------------------------------------------------------
|
|
4712
4716
|
async createProject(input) {
|
|
4713
4717
|
const [row] = input.id ? await this.sql`
|
|
4714
|
-
INSERT INTO projects (id, slug, name, repo_url, papi_dir)
|
|
4715
|
-
VALUES (${input.id}, ${input.slug}, ${input.name}, ${input.repo_url ?? null}, ${input.papi_dir ?? null})
|
|
4718
|
+
INSERT INTO projects (id, slug, name, repo_url, papi_dir, user_id)
|
|
4719
|
+
VALUES (${input.id}, ${input.slug}, ${input.name}, ${input.repo_url ?? null}, ${input.papi_dir ?? null}, ${input.user_id ?? null})
|
|
4716
4720
|
RETURNING *
|
|
4717
4721
|
` : await this.sql`
|
|
4718
|
-
INSERT INTO projects (slug, name, repo_url, papi_dir)
|
|
4719
|
-
VALUES (${input.slug}, ${input.name}, ${input.repo_url ?? null}, ${input.papi_dir ?? null})
|
|
4722
|
+
INSERT INTO projects (slug, name, repo_url, papi_dir, user_id)
|
|
4723
|
+
VALUES (${input.slug}, ${input.name}, ${input.repo_url ?? null}, ${input.papi_dir ?? null}, ${input.user_id ?? null})
|
|
4720
4724
|
RETURNING *
|
|
4721
4725
|
`;
|
|
4722
4726
|
return row;
|
|
@@ -5224,6 +5228,7 @@ CREATE TABLE IF NOT EXISTS projects (
|
|
|
5224
5228
|
name TEXT NOT NULL,
|
|
5225
5229
|
repo_url TEXT,
|
|
5226
5230
|
papi_dir TEXT,
|
|
5231
|
+
user_id UUID,
|
|
5227
5232
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5228
5233
|
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
5229
5234
|
site_url TEXT,
|
|
@@ -5630,6 +5635,7 @@ CREATE TABLE IF NOT EXISTS entity_references (
|
|
|
5630
5635
|
CREATE TABLE IF NOT EXISTS dogfood_log (
|
|
5631
5636
|
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5632
5637
|
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5638
|
+
user_id UUID,
|
|
5633
5639
|
cycle_number INTEGER NOT NULL,
|
|
5634
5640
|
category TEXT NOT NULL,
|
|
5635
5641
|
content TEXT NOT NULL,
|
|
@@ -5654,6 +5660,7 @@ CREATE TABLE IF NOT EXISTS task_comments (
|
|
|
5654
5660
|
CREATE TABLE IF NOT EXISTS task_transitions (
|
|
5655
5661
|
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
5656
5662
|
project_id UUID NOT NULL REFERENCES projects(id),
|
|
5663
|
+
user_id UUID,
|
|
5657
5664
|
task_id VARCHAR(50) NOT NULL,
|
|
5658
5665
|
from_status VARCHAR(50) NOT NULL,
|
|
5659
5666
|
to_status VARCHAR(50) NOT NULL,
|
|
@@ -5962,6 +5969,10 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
|
5962
5969
|
max: 5,
|
|
5963
5970
|
idle_timeout: 20,
|
|
5964
5971
|
connect_timeout: 10,
|
|
5972
|
+
// Supabase uses PgBouncer in transaction mode — prepared statements are
|
|
5973
|
+
// bound to backend connections and become invalid when PgBouncer reassigns.
|
|
5974
|
+
// This caused "prepared statement does not exist" errors on strategy_review.
|
|
5975
|
+
prepare: false,
|
|
5965
5976
|
connection: {
|
|
5966
5977
|
// 30s statement timeout prevents hung queries from blocking the server
|
|
5967
5978
|
statement_timeout: 3e4
|
|
@@ -6077,20 +6088,23 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
|
6077
6088
|
}));
|
|
6078
6089
|
}
|
|
6079
6090
|
async getCycleHealth() {
|
|
6080
|
-
const
|
|
6081
|
-
SELECT
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6091
|
+
const [row] = await this.sql`
|
|
6092
|
+
SELECT
|
|
6093
|
+
COALESCE((SELECT number FROM cycles WHERE project_id = ${this.projectId} ORDER BY number DESC LIMIT 1), 0) AS total_cycles,
|
|
6094
|
+
(SELECT status FROM cycles WHERE project_id = ${this.projectId} ORDER BY number DESC LIMIT 1) AS latest_status,
|
|
6095
|
+
sr.cycle_number AS last_review_cycle,
|
|
6096
|
+
sr.board_health,
|
|
6097
|
+
sr.strategic_direction
|
|
6098
|
+
FROM (SELECT 1) AS _dummy
|
|
6099
|
+
LEFT JOIN LATERAL (
|
|
6100
|
+
SELECT cycle_number, board_health, strategic_direction
|
|
6101
|
+
FROM strategy_reviews WHERE project_id = ${this.projectId}
|
|
6102
|
+
ORDER BY cycle_number DESC LIMIT 1
|
|
6103
|
+
) sr ON true
|
|
6092
6104
|
`;
|
|
6093
|
-
const
|
|
6105
|
+
const totalCycles = row?.total_cycles ?? 0;
|
|
6106
|
+
const latestCycleStatus = row?.latest_status ?? void 0;
|
|
6107
|
+
const lastStrategyReviewCycle = row?.last_review_cycle ?? 0;
|
|
6094
6108
|
const gap = totalCycles - lastStrategyReviewCycle;
|
|
6095
6109
|
const strategyReviewDue = gap >= 5 ? `Cycle ${totalCycles}` : `Cycle ${lastStrategyReviewCycle + 5}`;
|
|
6096
6110
|
return {
|
|
@@ -6098,8 +6112,8 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
|
6098
6112
|
latestCycleStatus,
|
|
6099
6113
|
cyclesSinceLastStrategyReview: gap,
|
|
6100
6114
|
strategyReviewDue,
|
|
6101
|
-
boardHealth:
|
|
6102
|
-
strategicDirection:
|
|
6115
|
+
boardHealth: row?.board_health ?? "",
|
|
6116
|
+
strategicDirection: row?.strategic_direction ?? "",
|
|
6103
6117
|
lastFullMode: 0
|
|
6104
6118
|
};
|
|
6105
6119
|
}
|
|
@@ -6271,20 +6285,16 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
6271
6285
|
}
|
|
6272
6286
|
async writeDogfoodEntries(entries) {
|
|
6273
6287
|
if (entries.length === 0) return;
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
${entry.linkedTaskId ?? null}
|
|
6285
|
-
)
|
|
6286
|
-
`;
|
|
6287
|
-
}
|
|
6288
|
+
const values2 = entries.map((entry) => ({
|
|
6289
|
+
project_id: this.projectId,
|
|
6290
|
+
cycle_number: entry.cycleNumber,
|
|
6291
|
+
category: entry.category,
|
|
6292
|
+
content: entry.content,
|
|
6293
|
+
source_tool: entry.sourceTool ?? "strategy_review",
|
|
6294
|
+
status: entry.status ?? "observed",
|
|
6295
|
+
linked_task_id: entry.linkedTaskId ?? null
|
|
6296
|
+
}));
|
|
6297
|
+
await this.sql`INSERT INTO dogfood_log ${this.sql(values2)}`;
|
|
6288
6298
|
}
|
|
6289
6299
|
async getDogfoodLog(limit = 10) {
|
|
6290
6300
|
const rows = await this.sql`
|
|
@@ -6708,24 +6718,41 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
6708
6718
|
return rows.map(rowToPhase);
|
|
6709
6719
|
}
|
|
6710
6720
|
async writePhases(phases) {
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6721
|
+
if (phases.length === 0) return;
|
|
6722
|
+
const withStage = phases.filter((p) => p.stageId);
|
|
6723
|
+
const withoutStage = phases.filter((p) => !p.stageId);
|
|
6724
|
+
if (withStage.length > 0) {
|
|
6725
|
+
const values2 = withStage.map((p) => ({
|
|
6726
|
+
project_id: this.projectId,
|
|
6727
|
+
slug: p.slug,
|
|
6728
|
+
label: p.label,
|
|
6729
|
+
description: p.description,
|
|
6730
|
+
status: p.status,
|
|
6731
|
+
sort_order: p.order,
|
|
6732
|
+
stage_id: p.stageId
|
|
6733
|
+
}));
|
|
6734
|
+
await this.sql`
|
|
6735
|
+
INSERT INTO phases ${this.sql(values2)}
|
|
6736
|
+
ON CONFLICT (project_id, slug)
|
|
6737
|
+
DO UPDATE SET label = EXCLUDED.label, description = EXCLUDED.description,
|
|
6738
|
+
status = EXCLUDED.status, sort_order = EXCLUDED.sort_order, stage_id = EXCLUDED.stage_id
|
|
6739
|
+
`;
|
|
6740
|
+
}
|
|
6741
|
+
if (withoutStage.length > 0) {
|
|
6742
|
+
const values2 = withoutStage.map((p) => ({
|
|
6743
|
+
project_id: this.projectId,
|
|
6744
|
+
slug: p.slug,
|
|
6745
|
+
label: p.label,
|
|
6746
|
+
description: p.description,
|
|
6747
|
+
status: p.status,
|
|
6748
|
+
sort_order: p.order
|
|
6749
|
+
}));
|
|
6750
|
+
await this.sql`
|
|
6751
|
+
INSERT INTO phases ${this.sql(values2)}
|
|
6752
|
+
ON CONFLICT (project_id, slug)
|
|
6753
|
+
DO UPDATE SET label = EXCLUDED.label, description = EXCLUDED.description,
|
|
6754
|
+
status = EXCLUDED.status, sort_order = EXCLUDED.sort_order
|
|
6755
|
+
`;
|
|
6729
6756
|
}
|
|
6730
6757
|
}
|
|
6731
6758
|
// -------------------------------------------------------------------------
|
|
@@ -7291,17 +7318,15 @@ ${r.content}` + (r.carry_forward ? `
|
|
|
7291
7318
|
// -------------------------------------------------------------------------
|
|
7292
7319
|
async logEntityReferences(refs) {
|
|
7293
7320
|
if (refs.length === 0) return;
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
)
|
|
7303
|
-
`;
|
|
7304
|
-
}
|
|
7321
|
+
const values2 = refs.map((ref) => ({
|
|
7322
|
+
project_id: this.projectId,
|
|
7323
|
+
entity_type: ref.entityType,
|
|
7324
|
+
entity_id: ref.entityId,
|
|
7325
|
+
reference_context: ref.referenceContext,
|
|
7326
|
+
tool_name: ref.toolName,
|
|
7327
|
+
cycle_number: ref.cycleNumber
|
|
7328
|
+
}));
|
|
7329
|
+
await this.sql`INSERT INTO entity_references ${this.sql(values2)}`;
|
|
7305
7330
|
}
|
|
7306
7331
|
async getDecisionUsage(currentSprint) {
|
|
7307
7332
|
const rows = await this.sql`
|
|
@@ -8035,7 +8060,8 @@ async function createAdapter(optionsOrType, maybePapiDir) {
|
|
|
8035
8060
|
if (!existing) {
|
|
8036
8061
|
const projectRoot = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? "";
|
|
8037
8062
|
const slug = path2.basename(projectRoot) || "unnamed";
|
|
8038
|
-
|
|
8063
|
+
const userId = process.env["PAPI_USER_ID"] ?? void 0;
|
|
8064
|
+
await pgAdapter.createProject({ id: projectId, slug, name: slug, papi_dir: papiDir, user_id: userId });
|
|
8039
8065
|
}
|
|
8040
8066
|
await pgAdapter.close();
|
|
8041
8067
|
} catch {
|
|
@@ -8130,15 +8156,25 @@ function formatActiveDecisionsForReview(decisions) {
|
|
|
8130
8156
|
if (decisions.length === 0) return "No active decisions.";
|
|
8131
8157
|
return decisions.map((d) => {
|
|
8132
8158
|
const lifecycle = d.outcome && d.outcome !== "pending" ? ` | Outcome: ${d.outcome}` + (d.revisionCount ? ` | ${d.revisionCount} revision(s)` : "") : "";
|
|
8159
|
+
const summary = extractDecisionSummary(d.body);
|
|
8160
|
+
const summaryLine = summary ? `
|
|
8161
|
+
Summary: ${summary}` : "";
|
|
8133
8162
|
if (d.superseded) {
|
|
8134
|
-
return
|
|
8135
|
-
|
|
8136
|
-
${d.body}`;
|
|
8163
|
+
return `- ${d.id}: ${d.title} [SUPERSEDED by ${d.supersededBy}${lifecycle}]${summaryLine}`;
|
|
8137
8164
|
}
|
|
8138
|
-
return
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8165
|
+
return `- ${d.id}: ${d.title} [${d.confidence}${lifecycle}]${summaryLine}`;
|
|
8166
|
+
}).join("\n");
|
|
8167
|
+
}
|
|
8168
|
+
function extractDecisionSummary(body) {
|
|
8169
|
+
if (!body) return "";
|
|
8170
|
+
const lines = body.split("\n");
|
|
8171
|
+
for (const line of lines) {
|
|
8172
|
+
const trimmed = line.trim();
|
|
8173
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("---")) continue;
|
|
8174
|
+
const clean = trimmed.replace(/\*\*/g, "").replace(/\*/g, "");
|
|
8175
|
+
return clean.length > 150 ? clean.slice(0, 147) + "..." : clean;
|
|
8176
|
+
}
|
|
8177
|
+
return "";
|
|
8142
8178
|
}
|
|
8143
8179
|
function formatDecisionLifecycleSummary(decisions) {
|
|
8144
8180
|
const active = decisions.filter((d) => !d.superseded);
|
|
@@ -9858,7 +9894,8 @@ Start with a level-2 heading for each conventions section. Example sections:
|
|
|
9858
9894
|
- If the project type implies a tech stack (e.g. mobile-app \u2192 React Native/Flutter), suggest conventions for the most likely stack
|
|
9859
9895
|
- Include build & test commands if inferrable (e.g. "npm test", "pytest", "cargo test")
|
|
9860
9896
|
- Do NOT repeat the workflow or documentation maintenance sections \u2014 those are already included
|
|
9861
|
-
- Do NOT include a top-level heading \u2014 the file already has one
|
|
9897
|
+
- Do NOT include a top-level heading \u2014 the file already has one
|
|
9898
|
+
- Do NOT include a dogfood logging section \u2014 that is added separately during setup`;
|
|
9862
9899
|
function buildConventionsPrompt(ctx) {
|
|
9863
9900
|
const parts = [
|
|
9864
9901
|
"Generate coding conventions for this project's CLAUDE.md file.",
|
|
@@ -11202,7 +11239,7 @@ ${result.userMessage}
|
|
|
11202
11239
|
|
|
11203
11240
|
// src/services/strategy.ts
|
|
11204
11241
|
init_dist2();
|
|
11205
|
-
import { randomUUID as randomUUID8 } from "crypto";
|
|
11242
|
+
import { randomUUID as randomUUID8, createHash as createHash2 } from "crypto";
|
|
11206
11243
|
|
|
11207
11244
|
// src/lib/phase-realign.ts
|
|
11208
11245
|
function extractPhaseNumber(phaseField) {
|
|
@@ -11426,16 +11463,33 @@ function formatPreviousReviews(reviews) {
|
|
|
11426
11463
|
if (reviews.length === 0) return void 0;
|
|
11427
11464
|
return reviews.map((r) => {
|
|
11428
11465
|
const range = r.cycleRange ? `Cycles ${r.cycleRange}` : `Cycle ${r.cycleNumber}`;
|
|
11429
|
-
const parts = [
|
|
11466
|
+
const parts = [`- **${range} \u2014 ${r.title}**`];
|
|
11430
11467
|
const meta = [];
|
|
11431
|
-
if (r.
|
|
11432
|
-
if (r.
|
|
11433
|
-
if (r.velocityAssessment) meta.push(
|
|
11434
|
-
if (meta.length > 0) parts.push(
|
|
11435
|
-
const
|
|
11436
|
-
parts.push(
|
|
11468
|
+
if (r.strategicDirection) meta.push(`Direction: ${r.strategicDirection}`);
|
|
11469
|
+
if (r.boardHealth) meta.push(`Board: ${r.boardHealth}`);
|
|
11470
|
+
if (r.velocityAssessment) meta.push(`Velocity: ${r.velocityAssessment}`);
|
|
11471
|
+
if (meta.length > 0) parts.push(` ${meta.join(" | ")}`);
|
|
11472
|
+
const topRec = extractTopRecommendation(r.content);
|
|
11473
|
+
if (topRec) parts.push(` Top rec: ${topRec}`);
|
|
11437
11474
|
return parts.join("\n");
|
|
11438
|
-
}).join("\n
|
|
11475
|
+
}).join("\n");
|
|
11476
|
+
}
|
|
11477
|
+
function extractTopRecommendation(content) {
|
|
11478
|
+
if (!content) return void 0;
|
|
11479
|
+
const lines = content.split("\n");
|
|
11480
|
+
let inRecommendations = false;
|
|
11481
|
+
for (const line of lines) {
|
|
11482
|
+
const trimmed = line.trim();
|
|
11483
|
+
if (/recommend/i.test(trimmed) && (trimmed.startsWith("#") || trimmed.startsWith("**"))) {
|
|
11484
|
+
inRecommendations = true;
|
|
11485
|
+
continue;
|
|
11486
|
+
}
|
|
11487
|
+
if (inRecommendations && (trimmed.startsWith("-") || trimmed.startsWith("1"))) {
|
|
11488
|
+
const clean = trimmed.replace(/^[-\d.)\s]+/, "").replace(/\*\*/g, "");
|
|
11489
|
+
return clean.length > 150 ? clean.slice(0, 147) + "..." : clean;
|
|
11490
|
+
}
|
|
11491
|
+
}
|
|
11492
|
+
return void 0;
|
|
11439
11493
|
}
|
|
11440
11494
|
function capProductBrief2(brief, maxChars = 2e3) {
|
|
11441
11495
|
if (brief.length <= maxChars) return brief;
|
|
@@ -11462,7 +11516,16 @@ function formatBoardForReviewSmart(tasks, lastReviewCycle) {
|
|
|
11462
11516
|
parts.push(`
|
|
11463
11517
|
### Completed Since Last Review \u2014 Cycle ${lastReviewCycle}+ (${recentDone.length})
|
|
11464
11518
|
`);
|
|
11465
|
-
|
|
11519
|
+
const byModule = /* @__PURE__ */ new Map();
|
|
11520
|
+
for (const t of recentDone) {
|
|
11521
|
+
const mod = t.module || "Core";
|
|
11522
|
+
if (!byModule.has(mod)) byModule.set(mod, []);
|
|
11523
|
+
byModule.get(mod).push(t);
|
|
11524
|
+
}
|
|
11525
|
+
for (const [mod, modTasks] of byModule) {
|
|
11526
|
+
const titles = modTasks.map((t) => `${t.id}: ${t.title}`).join(", ");
|
|
11527
|
+
parts.push(`**${mod}** (${modTasks.length}): ${titles}`);
|
|
11528
|
+
}
|
|
11466
11529
|
}
|
|
11467
11530
|
if (olderDone.length > 0) {
|
|
11468
11531
|
const byPhase = /* @__PURE__ */ new Map();
|
|
@@ -11495,22 +11558,41 @@ function formatTaskCompact(t) {
|
|
|
11495
11558
|
}
|
|
11496
11559
|
async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, projectRoot) {
|
|
11497
11560
|
const lastReviewCycleNum = cycleNumber - cyclesSinceLastReview;
|
|
11498
|
-
const [
|
|
11561
|
+
const [
|
|
11562
|
+
productBrief,
|
|
11563
|
+
decisions,
|
|
11564
|
+
reports,
|
|
11565
|
+
log,
|
|
11566
|
+
activeTasks,
|
|
11567
|
+
recentDoneTasks,
|
|
11568
|
+
reviews,
|
|
11569
|
+
dogfoodLog,
|
|
11570
|
+
previousStrategyReviews,
|
|
11571
|
+
currentNorthStar,
|
|
11572
|
+
canvas,
|
|
11573
|
+
decisionUsage,
|
|
11574
|
+
recData
|
|
11575
|
+
] = await Promise.all([
|
|
11499
11576
|
adapter2.readProductBrief(),
|
|
11500
11577
|
adapter2.getActiveDecisions(),
|
|
11501
11578
|
adapter2.getBuildReportsSince(lastReviewCycleNum),
|
|
11502
11579
|
adapter2.getCycleLogSince(lastReviewCycleNum),
|
|
11503
11580
|
adapter2.queryBoard({
|
|
11504
|
-
status: ["Backlog", "In Cycle", "In Progress", "In Review", "Blocked"
|
|
11581
|
+
status: ["Backlog", "In Cycle", "In Progress", "In Review", "Blocked"]
|
|
11582
|
+
// Deferred tasks excluded — they're intentionally deprioritised and add context noise
|
|
11505
11583
|
}),
|
|
11506
11584
|
adapter2.queryBoard({
|
|
11507
11585
|
status: ["Done"],
|
|
11508
11586
|
cycleSince: lastReviewCycleNum
|
|
11509
11587
|
}),
|
|
11510
|
-
adapter2.getRecentReviews(
|
|
11588
|
+
adapter2.getRecentReviews(5),
|
|
11511
11589
|
readDogfoodEntries(projectRoot, 10, adapter2),
|
|
11512
11590
|
adapter2.getStrategyReviews(3),
|
|
11513
|
-
adapter2.getCurrentNorthStar?.() ?? Promise.resolve(null)
|
|
11591
|
+
adapter2.getCurrentNorthStar?.() ?? Promise.resolve(null),
|
|
11592
|
+
// Previously sequential — now parallel
|
|
11593
|
+
adapter2.readDiscoveryCanvas().catch(() => ({})),
|
|
11594
|
+
adapter2.getDecisionUsage(cycleNumber).catch(() => []),
|
|
11595
|
+
adapter2.getRecommendationEffectiveness?.()?.catch(() => []) ?? Promise.resolve([])
|
|
11514
11596
|
]);
|
|
11515
11597
|
const tasks = [...activeTasks, ...recentDoneTasks];
|
|
11516
11598
|
const recentLog = log;
|
|
@@ -11544,8 +11626,17 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
|
|
|
11544
11626
|
]);
|
|
11545
11627
|
let discoveryCanvasText;
|
|
11546
11628
|
try {
|
|
11547
|
-
const
|
|
11548
|
-
|
|
11629
|
+
const fullCanvasText = formatDiscoveryCanvasForReview(canvas);
|
|
11630
|
+
if (fullCanvasText) {
|
|
11631
|
+
const canvasHash = createHash2("md5").update(fullCanvasText).digest("hex");
|
|
11632
|
+
const lastReview = previousStrategyReviews?.[0];
|
|
11633
|
+
const prevHash = lastReview?.structuredData?.canvasHash;
|
|
11634
|
+
if (prevHash && prevHash === canvasHash) {
|
|
11635
|
+
discoveryCanvasText = "Discovery Canvas: No changes since last review.";
|
|
11636
|
+
} else {
|
|
11637
|
+
discoveryCanvasText = fullCanvasText;
|
|
11638
|
+
}
|
|
11639
|
+
}
|
|
11549
11640
|
} catch {
|
|
11550
11641
|
}
|
|
11551
11642
|
const briefImplicationsFromBuilds = reports.filter((r) => Array.isArray(r.briefImplications) && r.briefImplications.length > 0).flatMap((r) => r.briefImplications.map((bi) => ({ ...bi, taskName: r.taskName, cycle: r.cycle })));
|
|
@@ -11555,7 +11646,7 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
|
|
|
11555
11646
|
}
|
|
11556
11647
|
let phasesText;
|
|
11557
11648
|
try {
|
|
11558
|
-
phasesText = await formatHierarchyForReview(adapter2, cycleNumber);
|
|
11649
|
+
phasesText = await formatHierarchyForReview(adapter2, cycleNumber, tasks);
|
|
11559
11650
|
if (!phasesText) {
|
|
11560
11651
|
const phases = await adapter2.readPhases();
|
|
11561
11652
|
phasesText = formatPhasesForReview(phases, cycleNumber);
|
|
@@ -11564,13 +11655,11 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
|
|
|
11564
11655
|
}
|
|
11565
11656
|
let decisionUsageText;
|
|
11566
11657
|
try {
|
|
11567
|
-
|
|
11568
|
-
decisionUsageText = formatDecisionUsageForReview(usage);
|
|
11658
|
+
decisionUsageText = formatDecisionUsageForReview(decisionUsage);
|
|
11569
11659
|
} catch {
|
|
11570
11660
|
}
|
|
11571
11661
|
let recEffectivenessText;
|
|
11572
11662
|
try {
|
|
11573
|
-
const recData = await adapter2.getRecommendationEffectiveness?.();
|
|
11574
11663
|
if (recData && recData.length > 0) {
|
|
11575
11664
|
recEffectivenessText = "| Type | Total | Actioned | Pending | Acceptance Rate | Avg Cycles to Action |\n|------|-------|----------|---------|-----------------|---------------------|\n" + recData.map(
|
|
11576
11665
|
(r) => `| ${r.type} | ${r.total} | ${r.actioned} | ${r.pending} | ${r.acceptanceRate}% | ${r.avgCyclesToAction ?? "N/A"} |`
|
|
@@ -11678,7 +11767,18 @@ ${cleanContent}`;
|
|
|
11678
11767
|
strategicDirection: data.strategicDirection,
|
|
11679
11768
|
fullAnalysis: fullAnalysis ?? void 0,
|
|
11680
11769
|
velocityAssessment: data.velocityAssessment ?? void 0,
|
|
11681
|
-
structuredData:
|
|
11770
|
+
structuredData: await (async () => {
|
|
11771
|
+
const sd = data;
|
|
11772
|
+
try {
|
|
11773
|
+
const currentCanvas = await adapter2.readDiscoveryCanvas();
|
|
11774
|
+
const canvasText = formatDiscoveryCanvasForReview(currentCanvas);
|
|
11775
|
+
if (canvasText) {
|
|
11776
|
+
return { ...sd, canvasHash: createHash2("md5").update(canvasText).digest("hex") };
|
|
11777
|
+
}
|
|
11778
|
+
} catch {
|
|
11779
|
+
}
|
|
11780
|
+
return sd;
|
|
11781
|
+
})()
|
|
11682
11782
|
});
|
|
11683
11783
|
await adapter2.setCycleHealth({
|
|
11684
11784
|
cyclesSinceLastStrategyReview: 0,
|
|
@@ -11702,7 +11802,7 @@ ${cleanContent}`;
|
|
|
11702
11802
|
} catch {
|
|
11703
11803
|
}
|
|
11704
11804
|
if (data.activeDecisionUpdates && data.activeDecisionUpdates.length > 0) {
|
|
11705
|
-
|
|
11805
|
+
await Promise.all(data.activeDecisionUpdates.map(async (ad) => {
|
|
11706
11806
|
if (ad.action === "delete" && adapter2.deleteActiveDecision) {
|
|
11707
11807
|
await adapter2.deleteActiveDecision(ad.id);
|
|
11708
11808
|
} else {
|
|
@@ -11720,10 +11820,10 @@ ${cleanContent}`;
|
|
|
11720
11820
|
});
|
|
11721
11821
|
} catch {
|
|
11722
11822
|
}
|
|
11723
|
-
}
|
|
11823
|
+
}));
|
|
11724
11824
|
}
|
|
11725
11825
|
if (data.decisionScores && data.decisionScores.length > 0) {
|
|
11726
|
-
|
|
11826
|
+
await Promise.all(data.decisionScores.map(async (score) => {
|
|
11727
11827
|
try {
|
|
11728
11828
|
await adapter2.writeDecisionScore({
|
|
11729
11829
|
decisionId: score.id,
|
|
@@ -11745,7 +11845,7 @@ ${cleanContent}`;
|
|
|
11745
11845
|
});
|
|
11746
11846
|
} catch {
|
|
11747
11847
|
}
|
|
11748
|
-
}
|
|
11848
|
+
}));
|
|
11749
11849
|
}
|
|
11750
11850
|
if (data.productBriefUpdates) {
|
|
11751
11851
|
try {
|
|
@@ -11762,8 +11862,8 @@ ${cleanContent}`;
|
|
|
11762
11862
|
}
|
|
11763
11863
|
try {
|
|
11764
11864
|
const recs = extractRecommendations(data, cycleNumber);
|
|
11765
|
-
|
|
11766
|
-
await adapter2.writeRecommendation(rec);
|
|
11865
|
+
if (recs.length > 0) {
|
|
11866
|
+
await Promise.all(recs.map((rec) => adapter2.writeRecommendation(rec)));
|
|
11767
11867
|
}
|
|
11768
11868
|
} catch {
|
|
11769
11869
|
}
|
|
@@ -12012,20 +12112,22 @@ function formatPhasesForReview(phases, currentCycle) {
|
|
|
12012
12112
|
lines.push(`_Current cycle: ${currentCycle}. Use phase status + board task distribution to detect staleness._`);
|
|
12013
12113
|
return lines.join("\n");
|
|
12014
12114
|
}
|
|
12015
|
-
async function formatHierarchyForReview(adapter2, currentCycle) {
|
|
12115
|
+
async function formatHierarchyForReview(adapter2, currentCycle, prefetchedTasks) {
|
|
12016
12116
|
let horizons = [];
|
|
12017
12117
|
let stages = [];
|
|
12018
12118
|
let phases = [];
|
|
12019
12119
|
try {
|
|
12020
|
-
horizons = await
|
|
12021
|
-
|
|
12022
|
-
|
|
12120
|
+
[horizons, stages, phases] = await Promise.all([
|
|
12121
|
+
adapter2.readHorizons(),
|
|
12122
|
+
adapter2.readStages(),
|
|
12123
|
+
adapter2.readPhases()
|
|
12124
|
+
]);
|
|
12023
12125
|
} catch {
|
|
12024
12126
|
}
|
|
12025
12127
|
if (horizons.length === 0 && phases.length === 0) return void 0;
|
|
12026
12128
|
let tasksByPhase = /* @__PURE__ */ new Map();
|
|
12027
12129
|
try {
|
|
12028
|
-
const allTasks = await adapter2.queryBoard();
|
|
12130
|
+
const allTasks = prefetchedTasks ?? await adapter2.queryBoard();
|
|
12029
12131
|
for (const t of allTasks) {
|
|
12030
12132
|
const phase = t.phase || "Unscoped";
|
|
12031
12133
|
const existing = tasksByPhase.get(phase) || { total: 0, done: 0, backlog: 0, inProgress: 0 };
|
|
@@ -13604,6 +13706,41 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
13604
13706
|
} catch {
|
|
13605
13707
|
}
|
|
13606
13708
|
}
|
|
13709
|
+
try {
|
|
13710
|
+
const claudeMdPath = join2(config2.projectRoot, "CLAUDE.md");
|
|
13711
|
+
const existing = await readFile3(claudeMdPath, "utf-8");
|
|
13712
|
+
if (!existing.includes("Dogfood Logging")) {
|
|
13713
|
+
const dogfoodSection = [
|
|
13714
|
+
"",
|
|
13715
|
+
"## Dogfood Logging",
|
|
13716
|
+
"",
|
|
13717
|
+
"After each `release`, append a dogfood entry capturing observations from the cycle.",
|
|
13718
|
+
"Call the adapter method with structured entries for each observation:",
|
|
13719
|
+
"",
|
|
13720
|
+
"- **friction** \u2014 workflow pain points, confusing flows, things that broke or slowed you down",
|
|
13721
|
+
"- **methodology** \u2014 what worked or didn't in the plan/build/review cycle",
|
|
13722
|
+
"- **signal** \u2014 indicators of product-market fit, user value, or growth potential",
|
|
13723
|
+
"- **commercial** \u2014 cost, pricing, or business model observations",
|
|
13724
|
+
"",
|
|
13725
|
+
"This is autonomous plumbing \u2014 log observations after release without asking.",
|
|
13726
|
+
""
|
|
13727
|
+
].join("\n");
|
|
13728
|
+
await writeFile2(claudeMdPath, existing + dogfoodSection, "utf-8");
|
|
13729
|
+
}
|
|
13730
|
+
} catch {
|
|
13731
|
+
}
|
|
13732
|
+
if (adapter2.writeDogfoodEntries) {
|
|
13733
|
+
try {
|
|
13734
|
+
await adapter2.writeDogfoodEntries([{
|
|
13735
|
+
cycleNumber: 0,
|
|
13736
|
+
category: "signal",
|
|
13737
|
+
content: "Project setup completed \u2014 PAPI is configured and ready for first plan.",
|
|
13738
|
+
sourceTool: "setup",
|
|
13739
|
+
status: "observed"
|
|
13740
|
+
}]);
|
|
13741
|
+
} catch {
|
|
13742
|
+
}
|
|
13743
|
+
}
|
|
13607
13744
|
try {
|
|
13608
13745
|
await adapter2.appendToolMetric({
|
|
13609
13746
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15093,13 +15230,9 @@ async function recordAdHoc(adapter2, input) {
|
|
|
15093
15230
|
if (!existing) {
|
|
15094
15231
|
throw new Error(`Task "${input.taskId}" not found on the board. Check the task ID and try again.`);
|
|
15095
15232
|
}
|
|
15096
|
-
if (existing.status === "Done") {
|
|
15097
|
-
throw new Error(`Task "${input.taskId}" (${existing.title}) is already Done.`);
|
|
15098
|
-
}
|
|
15099
15233
|
await adapter2.updateTask(input.taskId, {
|
|
15100
|
-
status: "Done",
|
|
15101
15234
|
notes: existing.notes ? `${existing.notes}
|
|
15102
|
-
[ad-hoc] ${input.notes || "
|
|
15235
|
+
[ad-hoc] ${input.notes || "Work recorded via ad_hoc"}` : `[ad-hoc] ${input.notes || "Work recorded via ad_hoc"}`
|
|
15103
15236
|
});
|
|
15104
15237
|
task = await adapter2.getTask(input.taskId);
|
|
15105
15238
|
} else {
|
|
@@ -15144,7 +15277,7 @@ async function recordAdHoc(adapter2, input) {
|
|
|
15144
15277
|
var VALID_EFFORTS = ["XS", "S", "M", "L", "XL"];
|
|
15145
15278
|
var adHocTool = {
|
|
15146
15279
|
name: "ad_hoc",
|
|
15147
|
-
description: "Record work done outside the normal cycle. Creates a Done task with a lightweight build report, or
|
|
15280
|
+
description: "Record work done outside the normal cycle. Creates a Done task with a lightweight build report, or associates work with an existing task if task_id is provided (without changing task status \u2014 use build_execute for status transitions). Use for quick fixes, bug patches, or ad-hoc changes. Does not call the Anthropic API.",
|
|
15148
15281
|
inputSchema: {
|
|
15149
15282
|
type: "object",
|
|
15150
15283
|
properties: {
|
|
@@ -15154,7 +15287,7 @@ var adHocTool = {
|
|
|
15154
15287
|
},
|
|
15155
15288
|
task_id: {
|
|
15156
15289
|
type: "string",
|
|
15157
|
-
description: 'Existing task ID to
|
|
15290
|
+
description: 'Existing task ID to associate this work with (e.g. "task-42"). When provided, appends notes and attaches a build report to the existing task without changing its status.'
|
|
15158
15291
|
},
|
|
15159
15292
|
notes: {
|
|
15160
15293
|
type: "string",
|
|
@@ -15214,7 +15347,7 @@ async function handleAdHoc(adapter2, config2, args) {
|
|
|
15214
15347
|
}
|
|
15215
15348
|
const truncateWarning = notesTruncated ? ` (notes truncated to ${MAX_NOTES_LENGTH} chars)` : "";
|
|
15216
15349
|
return textResponse(
|
|
15217
|
-
`**${result.task.id}:** "${result.task.title}" \u2014 recorded as ad-hoc (${effortRaw}).
|
|
15350
|
+
`**${result.task.id}:** "${result.task.title}" \u2014 recorded as ad-hoc (${effortRaw}). Build report attached.${truncateWarning}`
|
|
15218
15351
|
);
|
|
15219
15352
|
}
|
|
15220
15353
|
|
|
@@ -15908,8 +16041,8 @@ async function createRelease(config2, branch, version, adapter2) {
|
|
|
15908
16041
|
const warnings = [];
|
|
15909
16042
|
if (adapter2) {
|
|
15910
16043
|
try {
|
|
15911
|
-
const
|
|
15912
|
-
const currentCycle =
|
|
16044
|
+
const versionMatch = version.match(/^v0\.(\d+)\./);
|
|
16045
|
+
const currentCycle = versionMatch ? parseInt(versionMatch[1], 10) : 0;
|
|
15913
16046
|
if (currentCycle > 0) {
|
|
15914
16047
|
await adapter2.createCycle({
|
|
15915
16048
|
id: `cycle-${currentCycle}`,
|
|
@@ -16029,6 +16162,10 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
16029
16162
|
}
|
|
16030
16163
|
}
|
|
16031
16164
|
|
|
16165
|
+
// src/tools/review.ts
|
|
16166
|
+
import { existsSync } from "fs";
|
|
16167
|
+
import { join as join4 } from "path";
|
|
16168
|
+
|
|
16032
16169
|
// src/services/review.ts
|
|
16033
16170
|
init_dist2();
|
|
16034
16171
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
@@ -16075,11 +16212,15 @@ async function submitReview(adapter2, input) {
|
|
|
16075
16212
|
throw new Error(`Task ${input.taskId} not found.`);
|
|
16076
16213
|
}
|
|
16077
16214
|
let cycle;
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16215
|
+
if (task.cycle && task.cycle > 0) {
|
|
16216
|
+
cycle = task.cycle;
|
|
16217
|
+
} else {
|
|
16218
|
+
try {
|
|
16219
|
+
const health = await adapter2.getCycleHealth();
|
|
16220
|
+
cycle = health.totalCycles;
|
|
16221
|
+
} catch {
|
|
16222
|
+
cycle = 0;
|
|
16223
|
+
}
|
|
16083
16224
|
}
|
|
16084
16225
|
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
16085
16226
|
const review = {
|
|
@@ -16261,21 +16402,24 @@ function mergeAfterAccept(config2, taskId) {
|
|
|
16261
16402
|
}
|
|
16262
16403
|
const featureBranch = taskBranchName(taskId);
|
|
16263
16404
|
const baseBranch = resolveBaseBranch(config2.projectRoot, config2.baseBranch);
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
if (
|
|
16273
|
-
|
|
16274
|
-
|
|
16405
|
+
const papiDir = join4(config2.projectRoot, ".papi");
|
|
16406
|
+
if (existsSync(papiDir)) {
|
|
16407
|
+
try {
|
|
16408
|
+
const commitResult = stageDirAndCommit(
|
|
16409
|
+
config2.projectRoot,
|
|
16410
|
+
".papi",
|
|
16411
|
+
`chore(${taskId}): record build-acceptance review`
|
|
16412
|
+
);
|
|
16413
|
+
if (commitResult.committed) {
|
|
16414
|
+
const push = gitPush(config2.projectRoot, featureBranch);
|
|
16415
|
+
if (!push.success) {
|
|
16416
|
+
lines.push(`Push failed: ${push.message}`);
|
|
16417
|
+
return lines;
|
|
16418
|
+
}
|
|
16275
16419
|
}
|
|
16420
|
+
} catch (err) {
|
|
16421
|
+
lines.push(`Pre-merge commit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
16276
16422
|
}
|
|
16277
|
-
} catch (err) {
|
|
16278
|
-
lines.push(`Pre-merge commit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
16279
16423
|
}
|
|
16280
16424
|
const merge = mergePullRequest(config2.projectRoot, featureBranch);
|
|
16281
16425
|
if (!merge.success) {
|
|
@@ -16283,6 +16427,20 @@ function mergeAfterAccept(config2, taskId) {
|
|
|
16283
16427
|
return lines;
|
|
16284
16428
|
}
|
|
16285
16429
|
lines.push(merge.message);
|
|
16430
|
+
if (hasUncommittedChanges(config2.projectRoot, AUTO_WRITTEN_PATHS)) {
|
|
16431
|
+
try {
|
|
16432
|
+
const cleanup = stageDirAndCommit(
|
|
16433
|
+
config2.projectRoot,
|
|
16434
|
+
".",
|
|
16435
|
+
`chore(${taskId}): commit remaining changes before branch switch`
|
|
16436
|
+
);
|
|
16437
|
+
if (cleanup.committed) {
|
|
16438
|
+
lines.push("Committed uncommitted changes before switching branches.");
|
|
16439
|
+
}
|
|
16440
|
+
} catch {
|
|
16441
|
+
lines.push("Warning: uncommitted changes detected \u2014 branch switch may fail.");
|
|
16442
|
+
}
|
|
16443
|
+
}
|
|
16286
16444
|
const checkout = checkoutBranch(config2.projectRoot, baseBranch);
|
|
16287
16445
|
if (checkout.success) {
|
|
16288
16446
|
const pull = gitPull(config2.projectRoot);
|
|
@@ -16292,7 +16450,7 @@ function mergeAfterAccept(config2, taskId) {
|
|
|
16292
16450
|
lines.push(del.message);
|
|
16293
16451
|
}
|
|
16294
16452
|
} else {
|
|
16295
|
-
lines.push(`Could not switch to '${baseBranch}': ${checkout.message}`);
|
|
16453
|
+
lines.push(`Could not switch to '${baseBranch}': ${checkout.message}. You may need to manually run: git checkout ${baseBranch}`);
|
|
16296
16454
|
}
|
|
16297
16455
|
return lines;
|
|
16298
16456
|
}
|
|
@@ -17153,6 +17311,50 @@ ${result.userMessage}
|
|
|
17153
17311
|
}
|
|
17154
17312
|
}
|
|
17155
17313
|
|
|
17314
|
+
// src/lib/telemetry.ts
|
|
17315
|
+
var TELEMETRY_SUPABASE_URL = "https://guewgygcpcmrcoppihzx.supabase.co";
|
|
17316
|
+
var TELEMETRY_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imd1ZXdneWdjcGNtcmNvcHBpaHp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI2Njk2NTMsImV4cCI6MjA4ODI0NTY1M30.V5Jw7wJgiMpSQPa2mt0ftjyye5ynG1qLlam00yPVNJY";
|
|
17317
|
+
function isEnabled() {
|
|
17318
|
+
return process.env["PAPI_TELEMETRY"] !== "false";
|
|
17319
|
+
}
|
|
17320
|
+
function emitTelemetryEvent(event) {
|
|
17321
|
+
if (!isEnabled()) return;
|
|
17322
|
+
const body = {
|
|
17323
|
+
project_id: event.project_id,
|
|
17324
|
+
tool_name: event.tool_name,
|
|
17325
|
+
event_type: event.event_type,
|
|
17326
|
+
metadata: event.metadata ?? {}
|
|
17327
|
+
};
|
|
17328
|
+
fetch(`${TELEMETRY_SUPABASE_URL}/rest/v1/telemetry_events`, {
|
|
17329
|
+
method: "POST",
|
|
17330
|
+
headers: {
|
|
17331
|
+
"Content-Type": "application/json",
|
|
17332
|
+
"apikey": TELEMETRY_API_KEY,
|
|
17333
|
+
"Authorization": `Bearer ${TELEMETRY_API_KEY}`,
|
|
17334
|
+
"Prefer": "return=minimal"
|
|
17335
|
+
},
|
|
17336
|
+
body: JSON.stringify(body),
|
|
17337
|
+
signal: AbortSignal.timeout(5e3)
|
|
17338
|
+
}).catch(() => {
|
|
17339
|
+
});
|
|
17340
|
+
}
|
|
17341
|
+
function emitToolCall(projectId, toolName, durationMs, extra) {
|
|
17342
|
+
emitTelemetryEvent({
|
|
17343
|
+
project_id: projectId,
|
|
17344
|
+
tool_name: toolName,
|
|
17345
|
+
event_type: "tool_call",
|
|
17346
|
+
metadata: { duration_ms: durationMs, ...extra }
|
|
17347
|
+
});
|
|
17348
|
+
}
|
|
17349
|
+
function emitMilestone(projectId, milestone, extra) {
|
|
17350
|
+
emitTelemetryEvent({
|
|
17351
|
+
project_id: projectId,
|
|
17352
|
+
tool_name: milestone,
|
|
17353
|
+
event_type: "milestone",
|
|
17354
|
+
metadata: extra
|
|
17355
|
+
});
|
|
17356
|
+
}
|
|
17357
|
+
|
|
17156
17358
|
// src/server.ts
|
|
17157
17359
|
var TOOLS_REQUIRING_PAPI = /* @__PURE__ */ new Set([
|
|
17158
17360
|
"plan",
|
|
@@ -17323,6 +17525,23 @@ function createServer(adapter2, config2) {
|
|
|
17323
17525
|
} catch {
|
|
17324
17526
|
}
|
|
17325
17527
|
}
|
|
17528
|
+
const telemetryProjectId = process.env["PAPI_PROJECT_ID"];
|
|
17529
|
+
if (telemetryProjectId) {
|
|
17530
|
+
emitToolCall(telemetryProjectId, name, elapsed, {
|
|
17531
|
+
adapter_type: config2.adapterType
|
|
17532
|
+
});
|
|
17533
|
+
const isApplyMode = safeArgs.mode === "apply";
|
|
17534
|
+
const isError = result.content.some((c) => c.text.startsWith("Error:") || c.text.startsWith("\u274C"));
|
|
17535
|
+
if (!isError) {
|
|
17536
|
+
if (name === "setup" && isApplyMode) {
|
|
17537
|
+
emitMilestone(telemetryProjectId, "setup_completed");
|
|
17538
|
+
} else if (name === "plan" && isApplyMode) {
|
|
17539
|
+
emitMilestone(telemetryProjectId, "plan_completed");
|
|
17540
|
+
} else if (name === "release") {
|
|
17541
|
+
emitMilestone(telemetryProjectId, "release_completed");
|
|
17542
|
+
}
|
|
17543
|
+
}
|
|
17544
|
+
}
|
|
17326
17545
|
const footer = formatMetricsFooter(elapsed, usage, contextBytes);
|
|
17327
17546
|
result.content.push({ type: "text", text: footer });
|
|
17328
17547
|
return result;
|
package/package.json
CHANGED