@papi-ai/server 0.7.11 → 0.7.13
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/README.md +8 -0
- package/dist/index.js +908 -103
- package/dist/prompts.js +11 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1453,7 +1453,12 @@ var init_dist2 = __esm({
|
|
|
1453
1453
|
research: 2,
|
|
1454
1454
|
spike: 2,
|
|
1455
1455
|
idea: 3,
|
|
1456
|
-
discovery: 1
|
|
1456
|
+
discovery: 1,
|
|
1457
|
+
// Non-code brief types for non-technical Owners (AD-12)
|
|
1458
|
+
"design-brief": 1,
|
|
1459
|
+
"research-brief": 1,
|
|
1460
|
+
"marketing-brief": 1,
|
|
1461
|
+
"ops-brief": 1
|
|
1457
1462
|
};
|
|
1458
1463
|
VALID_EFFORT_SIZES = /* @__PURE__ */ new Set(["XS", "S", "M", "L", "XL"]);
|
|
1459
1464
|
SECTION_HEADERS = [
|
|
@@ -1575,11 +1580,13 @@ ${TABLE_SEPARATOR}
|
|
|
1575
1580
|
}
|
|
1576
1581
|
/** Prepend a new cycle log entry at the top of the Cycle Log section. */
|
|
1577
1582
|
async writeCycleLogEntry(entry) {
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1583
|
+
const patched = {
|
|
1584
|
+
...entry,
|
|
1585
|
+
uuid: entry.uuid || randomUUID6(),
|
|
1586
|
+
date: entry.date ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1587
|
+
};
|
|
1581
1588
|
const content = await this.read("SPRINT_LOG.md");
|
|
1582
|
-
await this.write("SPRINT_LOG.md", prependCycleLogEntry(
|
|
1589
|
+
await this.write("SPRINT_LOG.md", prependCycleLogEntry(patched, content));
|
|
1583
1590
|
}
|
|
1584
1591
|
/** Write a strategy review — for md adapter, delegates to cycle log. */
|
|
1585
1592
|
async writeStrategyReview(review) {
|
|
@@ -2061,6 +2068,102 @@ ${footer}`);
|
|
|
2061
2068
|
await this.write("STRATEGY_RECOMMENDATIONS.md", updated);
|
|
2062
2069
|
}
|
|
2063
2070
|
// -------------------------------------------------------------------------
|
|
2071
|
+
// Strategy Review Agenda (markdown persistence)
|
|
2072
|
+
// -------------------------------------------------------------------------
|
|
2073
|
+
async addAgendaTopic(input) {
|
|
2074
|
+
const id = randomUUID6();
|
|
2075
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2076
|
+
const full = {
|
|
2077
|
+
id,
|
|
2078
|
+
topic: input.topic,
|
|
2079
|
+
source: input.source,
|
|
2080
|
+
sourceCycle: input.sourceCycle,
|
|
2081
|
+
status: "pending",
|
|
2082
|
+
createdAt
|
|
2083
|
+
};
|
|
2084
|
+
const content = await this.readOptional("STRATEGY_REVIEW_AGENDA.md");
|
|
2085
|
+
const header = "# Strategy Review Agenda\n\n<!-- PAPI-ADAPTER: parse the yaml block below -->\n\n<!-- PAPI-YAML-START -->\ntopics:\n";
|
|
2086
|
+
const footer = "<!-- PAPI-YAML-END -->\n";
|
|
2087
|
+
const entry = [
|
|
2088
|
+
` - id: ${full.id}`,
|
|
2089
|
+
` topic: ${JSON.stringify(full.topic)}`,
|
|
2090
|
+
` source: ${full.source}`,
|
|
2091
|
+
full.sourceCycle != null ? ` source_cycle: ${full.sourceCycle}` : null,
|
|
2092
|
+
` status: ${full.status}`,
|
|
2093
|
+
` created_at: ${full.createdAt}`
|
|
2094
|
+
].filter(Boolean).join("\n");
|
|
2095
|
+
if (!content) {
|
|
2096
|
+
await this.write("STRATEGY_REVIEW_AGENDA.md", `${header}${entry}
|
|
2097
|
+
${footer}`);
|
|
2098
|
+
} else {
|
|
2099
|
+
const insertPoint = content.indexOf("<!-- PAPI-YAML-END -->");
|
|
2100
|
+
if (insertPoint === -1) {
|
|
2101
|
+
await this.write("STRATEGY_REVIEW_AGENDA.md", `${header}${entry}
|
|
2102
|
+
${footer}`);
|
|
2103
|
+
} else {
|
|
2104
|
+
const updated = content.slice(0, insertPoint) + entry + "\n" + content.slice(insertPoint);
|
|
2105
|
+
await this.write("STRATEGY_REVIEW_AGENDA.md", updated);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return full;
|
|
2109
|
+
}
|
|
2110
|
+
async getPendingAgendaTopics() {
|
|
2111
|
+
const content = await this.readOptional("STRATEGY_REVIEW_AGENDA.md");
|
|
2112
|
+
if (!content) return [];
|
|
2113
|
+
const yamlStart = content.indexOf("<!-- PAPI-YAML-START -->");
|
|
2114
|
+
const yamlEnd = content.indexOf("<!-- PAPI-YAML-END -->");
|
|
2115
|
+
if (yamlStart === -1 || yamlEnd === -1) return [];
|
|
2116
|
+
const yamlBlock = content.slice(yamlStart + "<!-- PAPI-YAML-START -->".length, yamlEnd).trim();
|
|
2117
|
+
const entries = yamlBlock.split(/(?=\s+-\s+id:)/);
|
|
2118
|
+
const topics = [];
|
|
2119
|
+
for (const block of entries) {
|
|
2120
|
+
const idMatch = block.match(/id:\s+(.+)/);
|
|
2121
|
+
const topicMatch = block.match(/topic:\s+(.+)/);
|
|
2122
|
+
const sourceMatch = block.match(/source:\s+(\S+)/);
|
|
2123
|
+
const statusMatch = block.match(/status:\s+(\S+)/);
|
|
2124
|
+
const createdMatch = block.match(/created_at:\s+(.+)/);
|
|
2125
|
+
const sourceCycleMatch = block.match(/source_cycle:\s+(\d+)/);
|
|
2126
|
+
if (!idMatch || !topicMatch || !sourceMatch || !statusMatch || !createdMatch) continue;
|
|
2127
|
+
if (statusMatch[1].trim() !== "pending") continue;
|
|
2128
|
+
let parsedTopic = topicMatch[1].trim();
|
|
2129
|
+
if (parsedTopic.startsWith('"') && parsedTopic.endsWith('"')) {
|
|
2130
|
+
try {
|
|
2131
|
+
parsedTopic = JSON.parse(parsedTopic);
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
topics.push({
|
|
2136
|
+
id: idMatch[1].trim(),
|
|
2137
|
+
topic: parsedTopic,
|
|
2138
|
+
source: sourceMatch[1].trim(),
|
|
2139
|
+
sourceCycle: sourceCycleMatch ? parseInt(sourceCycleMatch[1], 10) : void 0,
|
|
2140
|
+
status: "pending",
|
|
2141
|
+
createdAt: createdMatch[1].trim()
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
return topics;
|
|
2145
|
+
}
|
|
2146
|
+
async markAgendaTopicsAddressed(ids, cycleNumber) {
|
|
2147
|
+
if (ids.length === 0) return;
|
|
2148
|
+
const content = await this.readOptional("STRATEGY_REVIEW_AGENDA.md");
|
|
2149
|
+
if (!content) return;
|
|
2150
|
+
let updated = content;
|
|
2151
|
+
const addressedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2152
|
+
for (const id of ids) {
|
|
2153
|
+
const statusPattern = new RegExp(`(\\s+-\\s+id:\\s+${id}\\n(?:.*\\n)*?\\s+status:\\s+)pending`);
|
|
2154
|
+
updated = updated.replace(statusPattern, `$1addressed`);
|
|
2155
|
+
const insertionAnchor = new RegExp(`(\\s+-\\s+id:\\s+${id}\\n(?:.*\\n)*?\\s+created_at:\\s+[^\\n]+)\\n`);
|
|
2156
|
+
const match = updated.match(insertionAnchor);
|
|
2157
|
+
if (match && !match[0].includes("addressed_at:")) {
|
|
2158
|
+
updated = updated.replace(insertionAnchor, `$1
|
|
2159
|
+
addressed_at: ${addressedAt}
|
|
2160
|
+
addressed_in_review: ${cycleNumber}
|
|
2161
|
+
`);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
await this.write("STRATEGY_REVIEW_AGENDA.md", updated);
|
|
2165
|
+
}
|
|
2166
|
+
// -------------------------------------------------------------------------
|
|
2064
2167
|
// Decision Events & Scores (markdown persistence)
|
|
2065
2168
|
// -------------------------------------------------------------------------
|
|
2066
2169
|
async appendDecisionEvent(event) {
|
|
@@ -3708,7 +3811,7 @@ var init_connection = __esm({
|
|
|
3708
3811
|
|
|
3709
3812
|
// ../../node_modules/postgres/src/subscribe.js
|
|
3710
3813
|
function Subscribe(postgres2, options) {
|
|
3711
|
-
const subscribers = /* @__PURE__ */ new Map(), slot = "postgresjs_" + Math.random().toString(36).slice(2),
|
|
3814
|
+
const subscribers = /* @__PURE__ */ new Map(), slot = "postgresjs_" + Math.random().toString(36).slice(2), state2 = {};
|
|
3712
3815
|
let connection2, stream, ended = false;
|
|
3713
3816
|
const sql = subscribe.sql = postgres2({
|
|
3714
3817
|
...options,
|
|
@@ -3725,7 +3828,7 @@ function Subscribe(postgres2, options) {
|
|
|
3725
3828
|
if (ended)
|
|
3726
3829
|
return;
|
|
3727
3830
|
stream = null;
|
|
3728
|
-
|
|
3831
|
+
state2.pid = state2.secret = void 0;
|
|
3729
3832
|
connected(await init(sql, slot, options.publications));
|
|
3730
3833
|
subscribers.forEach((event) => event.forEach(({ onsubscribe }) => onsubscribe()));
|
|
3731
3834
|
},
|
|
@@ -3756,13 +3859,13 @@ function Subscribe(postgres2, options) {
|
|
|
3756
3859
|
connected(x);
|
|
3757
3860
|
onsubscribe();
|
|
3758
3861
|
stream && stream.on("error", onerror);
|
|
3759
|
-
return { unsubscribe, state, sql };
|
|
3862
|
+
return { unsubscribe, state: state2, sql };
|
|
3760
3863
|
});
|
|
3761
3864
|
}
|
|
3762
3865
|
function connected(x) {
|
|
3763
3866
|
stream = x.stream;
|
|
3764
|
-
|
|
3765
|
-
|
|
3867
|
+
state2.pid = x.state.pid;
|
|
3868
|
+
state2.secret = x.state.secret;
|
|
3766
3869
|
}
|
|
3767
3870
|
async function init(sql2, slot2, publications) {
|
|
3768
3871
|
if (!publications)
|
|
@@ -3774,7 +3877,7 @@ function Subscribe(postgres2, options) {
|
|
|
3774
3877
|
const stream2 = await sql2.unsafe(
|
|
3775
3878
|
`START_REPLICATION SLOT ${slot2} LOGICAL ${x.consistent_point} (proto_version '1', publication_names '${publications}')`
|
|
3776
3879
|
).writable();
|
|
3777
|
-
const
|
|
3880
|
+
const state3 = {
|
|
3778
3881
|
lsn: Buffer.concat(x.consistent_point.split("/").map((x2) => Buffer.from(("00000000" + x2).slice(-8), "hex")))
|
|
3779
3882
|
};
|
|
3780
3883
|
stream2.on("data", data);
|
|
@@ -3786,9 +3889,9 @@ function Subscribe(postgres2, options) {
|
|
|
3786
3889
|
}
|
|
3787
3890
|
function data(x2) {
|
|
3788
3891
|
if (x2[0] === 119) {
|
|
3789
|
-
parse(x2.subarray(25),
|
|
3892
|
+
parse(x2.subarray(25), state3, sql2.options.parsers, handle, options.transform);
|
|
3790
3893
|
} else if (x2[0] === 107 && x2[17]) {
|
|
3791
|
-
|
|
3894
|
+
state3.lsn = x2.subarray(1, 9);
|
|
3792
3895
|
pong();
|
|
3793
3896
|
}
|
|
3794
3897
|
}
|
|
@@ -3804,7 +3907,7 @@ function Subscribe(postgres2, options) {
|
|
|
3804
3907
|
function pong() {
|
|
3805
3908
|
const x2 = Buffer.alloc(34);
|
|
3806
3909
|
x2[0] = "r".charCodeAt(0);
|
|
3807
|
-
x2.fill(
|
|
3910
|
+
x2.fill(state3.lsn, 1);
|
|
3808
3911
|
x2.writeBigInt64BE(BigInt(Date.now() - Date.UTC(2e3, 0, 1)) * BigInt(1e3), 25);
|
|
3809
3912
|
stream2.write(x2);
|
|
3810
3913
|
}
|
|
@@ -3816,12 +3919,12 @@ function Subscribe(postgres2, options) {
|
|
|
3816
3919
|
function Time(x) {
|
|
3817
3920
|
return new Date(Date.UTC(2e3, 0, 1) + Number(x / BigInt(1e3)));
|
|
3818
3921
|
}
|
|
3819
|
-
function parse(x,
|
|
3922
|
+
function parse(x, state2, parsers2, handle, transform) {
|
|
3820
3923
|
const char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc);
|
|
3821
3924
|
Object.entries({
|
|
3822
3925
|
R: (x2) => {
|
|
3823
3926
|
let i = 1;
|
|
3824
|
-
const r =
|
|
3927
|
+
const r = state2[x2.readUInt32BE(i)] = {
|
|
3825
3928
|
schema: x2.toString("utf8", i += 4, i = x2.indexOf(0, i)) || "pg_catalog",
|
|
3826
3929
|
table: x2.toString("utf8", i + 1, i = x2.indexOf(0, i + 1)),
|
|
3827
3930
|
columns: Array(x2.readUInt16BE(i += 2)),
|
|
@@ -3848,12 +3951,12 @@ function parse(x, state, parsers2, handle, transform) {
|
|
|
3848
3951
|
},
|
|
3849
3952
|
// Origin
|
|
3850
3953
|
B: (x2) => {
|
|
3851
|
-
|
|
3852
|
-
|
|
3954
|
+
state2.date = Time(x2.readBigInt64BE(9));
|
|
3955
|
+
state2.lsn = x2.subarray(1, 9);
|
|
3853
3956
|
},
|
|
3854
3957
|
I: (x2) => {
|
|
3855
3958
|
let i = 1;
|
|
3856
|
-
const relation =
|
|
3959
|
+
const relation = state2[x2.readUInt32BE(i)];
|
|
3857
3960
|
const { row } = tuples(x2, relation.columns, i += 7, transform);
|
|
3858
3961
|
handle(row, {
|
|
3859
3962
|
command: "insert",
|
|
@@ -3862,7 +3965,7 @@ function parse(x, state, parsers2, handle, transform) {
|
|
|
3862
3965
|
},
|
|
3863
3966
|
D: (x2) => {
|
|
3864
3967
|
let i = 1;
|
|
3865
|
-
const relation =
|
|
3968
|
+
const relation = state2[x2.readUInt32BE(i)];
|
|
3866
3969
|
i += 4;
|
|
3867
3970
|
const key = x2[i] === 75;
|
|
3868
3971
|
handle(
|
|
@@ -3876,7 +3979,7 @@ function parse(x, state, parsers2, handle, transform) {
|
|
|
3876
3979
|
},
|
|
3877
3980
|
U: (x2) => {
|
|
3878
3981
|
let i = 1;
|
|
3879
|
-
const relation =
|
|
3982
|
+
const relation = state2[x2.readUInt32BE(i)];
|
|
3880
3983
|
i += 4;
|
|
3881
3984
|
const key = x2[i] === 75;
|
|
3882
3985
|
const xs = key || x2[i] === 79 ? tuples(x2, relation.columns, i += 3, transform) : null;
|
|
@@ -4612,6 +4715,7 @@ function rowToCycleLogEntry(row) {
|
|
|
4612
4715
|
if (row.notes != null) entry.notes = row.notes;
|
|
4613
4716
|
if (row.task_count != null) entry.taskCount = row.task_count;
|
|
4614
4717
|
if (row.effort_points != null) entry.effortPoints = row.effort_points;
|
|
4718
|
+
if (row.updated_at != null) entry.date = row.updated_at;
|
|
4615
4719
|
return entry;
|
|
4616
4720
|
}
|
|
4617
4721
|
function rowToPhase(row) {
|
|
@@ -6276,7 +6380,7 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
|
6276
6380
|
async getCycleLog(limit) {
|
|
6277
6381
|
if (limit != null) {
|
|
6278
6382
|
const rows2 = await this.sql`
|
|
6279
|
-
SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points
|
|
6383
|
+
SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points, updated_at
|
|
6280
6384
|
FROM planning_log_entries
|
|
6281
6385
|
WHERE project_id = ${this.projectId}
|
|
6282
6386
|
ORDER BY cycle_number DESC
|
|
@@ -6285,7 +6389,7 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
|
6285
6389
|
return rows2.map(rowToCycleLogEntry);
|
|
6286
6390
|
}
|
|
6287
6391
|
const rows = await this.sql`
|
|
6288
|
-
SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points
|
|
6392
|
+
SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points, updated_at
|
|
6289
6393
|
FROM planning_log_entries
|
|
6290
6394
|
WHERE project_id = ${this.projectId}
|
|
6291
6395
|
ORDER BY cycle_number DESC
|
|
@@ -6295,7 +6399,7 @@ EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
|
6295
6399
|
}
|
|
6296
6400
|
async getCycleLogSince(cycleNumber) {
|
|
6297
6401
|
const rows = await this.sql`
|
|
6298
|
-
SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points
|
|
6402
|
+
SELECT id, cycle_number, title, content, carry_forward, notes, task_count, effort_points, updated_at
|
|
6299
6403
|
FROM planning_log_entries
|
|
6300
6404
|
WHERE project_id = ${this.projectId}
|
|
6301
6405
|
AND cycle_number >= ${cycleNumber}
|
|
@@ -6346,7 +6450,8 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
6346
6450
|
carry_forward = EXCLUDED.carry_forward,
|
|
6347
6451
|
notes = EXCLUDED.notes,
|
|
6348
6452
|
task_count = EXCLUDED.task_count,
|
|
6349
|
-
effort_points = EXCLUDED.effort_points
|
|
6453
|
+
effort_points = EXCLUDED.effort_points,
|
|
6454
|
+
updated_at = now()
|
|
6350
6455
|
`;
|
|
6351
6456
|
}
|
|
6352
6457
|
async writeStrategyReview(review) {
|
|
@@ -7560,6 +7665,50 @@ ${newParts.join("\n")}` : newParts.join("\n");
|
|
|
7560
7665
|
UPDATE strategy_recommendations
|
|
7561
7666
|
SET status = 'actioned', dismissal_reason = ${reason}, updated_at = now()
|
|
7562
7667
|
WHERE id = ${id} AND project_id = ${this.projectId}
|
|
7668
|
+
`;
|
|
7669
|
+
}
|
|
7670
|
+
// -------------------------------------------------------------------------
|
|
7671
|
+
// Strategy Review Agenda
|
|
7672
|
+
// -------------------------------------------------------------------------
|
|
7673
|
+
async addAgendaTopic(input) {
|
|
7674
|
+
const [row] = await this.sql`
|
|
7675
|
+
INSERT INTO strategy_review_agenda (project_id, topic, source, source_cycle)
|
|
7676
|
+
VALUES (${this.projectId}, ${input.topic}, ${input.source}, ${input.sourceCycle ?? null})
|
|
7677
|
+
RETURNING id, topic, source, source_cycle, status, created_at
|
|
7678
|
+
`;
|
|
7679
|
+
return {
|
|
7680
|
+
id: row.id,
|
|
7681
|
+
topic: row.topic,
|
|
7682
|
+
source: row.source,
|
|
7683
|
+
sourceCycle: row.source_cycle ?? void 0,
|
|
7684
|
+
status: row.status,
|
|
7685
|
+
createdAt: row.created_at
|
|
7686
|
+
};
|
|
7687
|
+
}
|
|
7688
|
+
async getPendingAgendaTopics() {
|
|
7689
|
+
const rows = await this.sql`
|
|
7690
|
+
SELECT id, topic, source, source_cycle, status, created_at, addressed_at, addressed_in_review
|
|
7691
|
+
FROM strategy_review_agenda
|
|
7692
|
+
WHERE project_id = ${this.projectId} AND status = 'pending'
|
|
7693
|
+
ORDER BY created_at ASC
|
|
7694
|
+
`;
|
|
7695
|
+
return rows.map((r) => ({
|
|
7696
|
+
id: r.id,
|
|
7697
|
+
topic: r.topic,
|
|
7698
|
+
source: r.source,
|
|
7699
|
+
sourceCycle: r.source_cycle ?? void 0,
|
|
7700
|
+
status: "pending",
|
|
7701
|
+
createdAt: r.created_at,
|
|
7702
|
+
addressedAt: r.addressed_at ?? void 0,
|
|
7703
|
+
addressedInReview: r.addressed_in_review ?? void 0
|
|
7704
|
+
}));
|
|
7705
|
+
}
|
|
7706
|
+
async markAgendaTopicsAddressed(ids, cycleNumber) {
|
|
7707
|
+
if (ids.length === 0) return;
|
|
7708
|
+
await this.sql`
|
|
7709
|
+
UPDATE strategy_review_agenda
|
|
7710
|
+
SET status = 'addressed', addressed_at = now(), addressed_in_review = ${cycleNumber}
|
|
7711
|
+
WHERE project_id = ${this.projectId} AND id = ANY(${ids}::uuid[])
|
|
7563
7712
|
`;
|
|
7564
7713
|
}
|
|
7565
7714
|
// -------------------------------------------------------------------------
|
|
@@ -7813,7 +7962,8 @@ ${r.content}` + (r.carry_forward ? `
|
|
|
7813
7962
|
await this.sql`
|
|
7814
7963
|
INSERT INTO plan_runs (
|
|
7815
7964
|
project_id, cycle_number, context_bytes, duration_ms,
|
|
7816
|
-
task_count_in, task_count_out, backlog_depth, notes
|
|
7965
|
+
task_count_in, task_count_out, backlog_depth, notes,
|
|
7966
|
+
token_usage, source
|
|
7817
7967
|
) VALUES (
|
|
7818
7968
|
${this.projectId},
|
|
7819
7969
|
${entry.cycleNumber},
|
|
@@ -7822,7 +7972,9 @@ ${r.content}` + (r.carry_forward ? `
|
|
|
7822
7972
|
${entry.taskCountIn ?? null},
|
|
7823
7973
|
${entry.taskCountOut ?? null},
|
|
7824
7974
|
${entry.backlogDepth ?? null},
|
|
7825
|
-
${entry.notes ?? null}
|
|
7975
|
+
${entry.notes ?? null},
|
|
7976
|
+
${entry.tokenUsage ? this.sql.json(entry.tokenUsage) : null},
|
|
7977
|
+
${entry.source ?? null}
|
|
7826
7978
|
)
|
|
7827
7979
|
`;
|
|
7828
7980
|
}
|
|
@@ -7958,7 +8110,8 @@ ${r.content}` + (r.carry_forward ? `
|
|
|
7958
8110
|
title = EXCLUDED.title,
|
|
7959
8111
|
content = EXCLUDED.content,
|
|
7960
8112
|
carry_forward = EXCLUDED.carry_forward,
|
|
7961
|
-
notes = EXCLUDED.notes
|
|
8113
|
+
notes = EXCLUDED.notes,
|
|
8114
|
+
updated_at = now()
|
|
7962
8115
|
`;
|
|
7963
8116
|
if (payload.healthUpdates.boardHealth != null || payload.healthUpdates.strategicDirection != null) {
|
|
7964
8117
|
const [latest] = await tx`
|
|
@@ -8748,6 +8901,7 @@ __export(git_exports, {
|
|
|
8748
8901
|
getHeadCommitSha: () => getHeadCommitSha,
|
|
8749
8902
|
getLatestTag: () => getLatestTag,
|
|
8750
8903
|
getOriginRepoSlug: () => getOriginRepoSlug,
|
|
8904
|
+
getPullRequestUrl: () => getPullRequestUrl,
|
|
8751
8905
|
getUnmergedBranches: () => getUnmergedBranches,
|
|
8752
8906
|
gitPull: () => gitPull,
|
|
8753
8907
|
gitPush: () => gitPush,
|
|
@@ -8757,9 +8911,11 @@ __export(git_exports, {
|
|
|
8757
8911
|
isGhAvailable: () => isGhAvailable,
|
|
8758
8912
|
isGitAvailable: () => isGitAvailable,
|
|
8759
8913
|
isGitRepo: () => isGitRepo,
|
|
8914
|
+
listGroupedCycleBranches: () => listGroupedCycleBranches,
|
|
8760
8915
|
mergePullRequest: () => mergePullRequest,
|
|
8761
8916
|
resolveBaseBranch: () => resolveBaseBranch,
|
|
8762
8917
|
runAutoCommit: () => runAutoCommit,
|
|
8918
|
+
squashMergePullRequest: () => squashMergePullRequest,
|
|
8763
8919
|
stageAllAndCommit: () => stageAllAndCommit,
|
|
8764
8920
|
stageDirAndCommit: () => stageDirAndCommit,
|
|
8765
8921
|
tagExists: () => tagExists,
|
|
@@ -9213,6 +9369,68 @@ function runAutoCommit(projectRoot, commitFn) {
|
|
|
9213
9369
|
return `Auto-commit failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
9214
9370
|
}
|
|
9215
9371
|
}
|
|
9372
|
+
function getPullRequestUrl(cwd, branch) {
|
|
9373
|
+
try {
|
|
9374
|
+
const output = execFileSync(
|
|
9375
|
+
"gh",
|
|
9376
|
+
["pr", "view", branch, "--json", "url", "--jq", ".url"],
|
|
9377
|
+
{ cwd, encoding: "utf-8" }
|
|
9378
|
+
).trim();
|
|
9379
|
+
return output || null;
|
|
9380
|
+
} catch {
|
|
9381
|
+
return null;
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9384
|
+
function squashMergePullRequest(cwd, branch) {
|
|
9385
|
+
const repo = getOriginRepoSlug(cwd);
|
|
9386
|
+
const baseArgs = ["pr", "merge", branch, "--squash", "--delete-branch"];
|
|
9387
|
+
if (repo) baseArgs.push("--repo", repo);
|
|
9388
|
+
for (let attempt = 1; attempt <= MERGE_MAX_RETRIES; attempt++) {
|
|
9389
|
+
try {
|
|
9390
|
+
execFileSync("gh", baseArgs, { cwd, encoding: "utf-8" });
|
|
9391
|
+
return { success: true, message: `Squash-merged PR for '${branch}' and deleted branch.` };
|
|
9392
|
+
} catch (err) {
|
|
9393
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9394
|
+
if (msg.includes("not mergeable") && attempt < MERGE_MAX_RETRIES) {
|
|
9395
|
+
sleepSync(MERGE_RETRY_DELAY_MS);
|
|
9396
|
+
continue;
|
|
9397
|
+
}
|
|
9398
|
+
return { success: false, message: `PR squash-merge failed: ${msg}` };
|
|
9399
|
+
}
|
|
9400
|
+
}
|
|
9401
|
+
return { success: false, message: "PR squash-merge failed: max retries exceeded" };
|
|
9402
|
+
}
|
|
9403
|
+
function listGroupedCycleBranches(cwd, cycleNum, baseBranch) {
|
|
9404
|
+
const prefix = `feat/cycle-${cycleNum}-`;
|
|
9405
|
+
try {
|
|
9406
|
+
const remoteOutput = execFileSync(
|
|
9407
|
+
"git",
|
|
9408
|
+
["ls-remote", "--heads", "origin", `${prefix}*`],
|
|
9409
|
+
{ cwd, encoding: "utf-8" }
|
|
9410
|
+
).trim();
|
|
9411
|
+
if (!remoteOutput) return [];
|
|
9412
|
+
const remoteBranches = remoteOutput.split("\n").map((line) => line.split(" ")[1]?.replace("refs/heads/", "").trim()).filter((b2) => !!b2 && b2.startsWith(prefix));
|
|
9413
|
+
return remoteBranches.filter((branch) => {
|
|
9414
|
+
try {
|
|
9415
|
+
const branchTip = execFileSync(
|
|
9416
|
+
"git",
|
|
9417
|
+
["rev-parse", `origin/${branch}`],
|
|
9418
|
+
{ cwd, encoding: "utf-8" }
|
|
9419
|
+
).trim();
|
|
9420
|
+
execFileSync(
|
|
9421
|
+
"git",
|
|
9422
|
+
["merge-base", "--is-ancestor", branchTip, baseBranch],
|
|
9423
|
+
{ cwd, stdio: "ignore" }
|
|
9424
|
+
);
|
|
9425
|
+
return false;
|
|
9426
|
+
} catch {
|
|
9427
|
+
return true;
|
|
9428
|
+
}
|
|
9429
|
+
});
|
|
9430
|
+
} catch {
|
|
9431
|
+
return getUnmergedBranches(cwd, baseBranch).filter((b2) => b2.startsWith(prefix));
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9216
9434
|
function getFilesChangedFromBase(cwd, baseBranch) {
|
|
9217
9435
|
try {
|
|
9218
9436
|
const mergeBase = execFileSync("git", ["merge-base", baseBranch, "HEAD"], { cwd, encoding: "utf-8" }).trim();
|
|
@@ -9234,9 +9452,9 @@ var init_git = __esm({
|
|
|
9234
9452
|
});
|
|
9235
9453
|
|
|
9236
9454
|
// src/index.ts
|
|
9237
|
-
import { readFileSync as
|
|
9455
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
9238
9456
|
import { createServer as createHttpServer } from "http";
|
|
9239
|
-
import { dirname as dirname2, join as
|
|
9457
|
+
import { dirname as dirname2, join as join13 } from "path";
|
|
9240
9458
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9241
9459
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9242
9460
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -9264,11 +9482,32 @@ function loadConfig() {
|
|
|
9264
9482
|
const lightMode = process.env.PAPI_LIGHT_MODE === "true";
|
|
9265
9483
|
const projectOwner = process.env.PAPI_OWNER ?? "Cathal";
|
|
9266
9484
|
const skipProjectSpecificRules = process.env.PAPI_SKIP_PROJECT_RULES === "true";
|
|
9485
|
+
const userId = process.env.PAPI_USER_ID || void 0;
|
|
9486
|
+
const telemetryEnabled = process.env.PAPI_TELEMETRY !== "off" && process.env.PAPI_TELEMETRY !== "false";
|
|
9267
9487
|
const papiEndpoint = process.env.PAPI_ENDPOINT;
|
|
9268
9488
|
const dataEndpoint = process.env.PAPI_DATA_ENDPOINT;
|
|
9269
9489
|
const databaseUrl = process.env.DATABASE_URL;
|
|
9270
9490
|
const explicitAdapter = process.env.PAPI_ADAPTER;
|
|
9271
|
-
const
|
|
9491
|
+
const projectId = process.env.PAPI_PROJECT_ID;
|
|
9492
|
+
let adapterType = papiEndpoint ? "pg" : databaseUrl && explicitAdapter === "pg" ? "pg" : dataEndpoint ? "proxy" : explicitAdapter ? explicitAdapter : databaseUrl ? "pg" : "proxy";
|
|
9493
|
+
if (projectId && !databaseUrl && !papiEndpoint && adapterType === "md") {
|
|
9494
|
+
adapterType = "proxy";
|
|
9495
|
+
console.error("[papi] PAPI_PROJECT_ID detected \u2014 switching to proxy adapter (md adapter blocked for external users).");
|
|
9496
|
+
}
|
|
9497
|
+
if (adapterType === "md" && !userId) {
|
|
9498
|
+
throw new Error(
|
|
9499
|
+
`PAPI requires a free account to run in local mode.
|
|
9500
|
+
|
|
9501
|
+
Create your account at https://getpapi.ai/setup \u2014 it takes under a minute.
|
|
9502
|
+
Your project data stays on your machine. The account lets PAPI identify you
|
|
9503
|
+
and unlocks dashboard features when you're ready.
|
|
9504
|
+
|
|
9505
|
+
After signing up, add this to your .mcp.json env config:
|
|
9506
|
+
"PAPI_USER_ID": "your-email@example.com"
|
|
9507
|
+
|
|
9508
|
+
Already have an account? Make sure PAPI_USER_ID is set in your .mcp.json env config.`
|
|
9509
|
+
);
|
|
9510
|
+
}
|
|
9272
9511
|
return {
|
|
9273
9512
|
projectRoot,
|
|
9274
9513
|
papiDir: path.join(projectRoot, ".papi"),
|
|
@@ -9280,7 +9519,9 @@ function loadConfig() {
|
|
|
9280
9519
|
papiEndpoint,
|
|
9281
9520
|
lightMode,
|
|
9282
9521
|
projectOwner,
|
|
9283
|
-
skipProjectSpecificRules
|
|
9522
|
+
skipProjectSpecificRules,
|
|
9523
|
+
userId,
|
|
9524
|
+
telemetryEnabled
|
|
9284
9525
|
};
|
|
9285
9526
|
}
|
|
9286
9527
|
|
|
@@ -9373,7 +9614,25 @@ async function createAdapter(optionsOrType, maybePapiDir) {
|
|
|
9373
9614
|
console.error("[papi] Set PAPI_USER_ID in your .mcp.json env to fix this.");
|
|
9374
9615
|
}
|
|
9375
9616
|
}
|
|
9376
|
-
|
|
9617
|
+
let skipCreate = false;
|
|
9618
|
+
if (userId) {
|
|
9619
|
+
const bySlug = await pgAdapter.listProjects({ slug });
|
|
9620
|
+
const userDup = bySlug.find((p) => p.user_id === userId);
|
|
9621
|
+
if (userDup) {
|
|
9622
|
+
console.error(`[papi] \u26A0 Project '${slug}' already exists for this user (id: ${userDup.id}).`);
|
|
9623
|
+
console.error(`[papi] Update PAPI_PROJECT_ID=${userDup.id} in .mcp.json to avoid a duplicate.`);
|
|
9624
|
+
skipCreate = true;
|
|
9625
|
+
}
|
|
9626
|
+
}
|
|
9627
|
+
if (!skipCreate) {
|
|
9628
|
+
await pgAdapter.createProject({ id: projectId, slug, name: slug, papi_dir: papiDir, user_id: userId });
|
|
9629
|
+
}
|
|
9630
|
+
} else if (existing.user_id) {
|
|
9631
|
+
const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
|
|
9632
|
+
if (configuredUserId && existing.user_id !== configuredUserId) {
|
|
9633
|
+
console.error(`[papi] \u26A0 PAPI_PROJECT_ID=${projectId} belongs to a different user.`);
|
|
9634
|
+
console.error("[papi] Run papi setup or update PAPI_PROJECT_ID in .mcp.json.");
|
|
9635
|
+
}
|
|
9377
9636
|
}
|
|
9378
9637
|
await pgAdapter.close();
|
|
9379
9638
|
} catch {
|
|
@@ -9482,8 +9741,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
9482
9741
|
}
|
|
9483
9742
|
|
|
9484
9743
|
// src/server.ts
|
|
9744
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
9485
9745
|
import { access as access4, readdir as readdir2, readFile as readFile5 } from "fs/promises";
|
|
9486
|
-
import { join as
|
|
9746
|
+
import { join as join12, dirname } from "path";
|
|
9487
9747
|
import { fileURLToPath } from "url";
|
|
9488
9748
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9489
9749
|
import {
|
|
@@ -9689,6 +9949,17 @@ ${formatted}`;
|
|
|
9689
9949
|
}
|
|
9690
9950
|
return sections.join("\n\n");
|
|
9691
9951
|
}
|
|
9952
|
+
function formatCandidateTaskFullNotes(tasks) {
|
|
9953
|
+
const candidates = tasks.filter((t) => !PLAN_EXCLUDED_STATUSES.has(t.status)).filter((t) => (t.notes?.length ?? 0) > PLAN_NOTES_MAX_LENGTH);
|
|
9954
|
+
if (candidates.length === 0) return void 0;
|
|
9955
|
+
const lines = candidates.map((t) => `**${t.id}** \u2014 ${t.title}
|
|
9956
|
+
${t.notes}`);
|
|
9957
|
+
return [
|
|
9958
|
+
`${candidates.length} candidate task(s) have notes longer than ${PLAN_NOTES_MAX_LENGTH} chars. Full untruncated notes below \u2014 reference these when generating BUILD HANDOFFs so submitter context, constraints, and reasoning are preserved. The Board section above uses truncated notes for concise task selection; this section supplies the missing detail for tasks you choose to schedule.`,
|
|
9959
|
+
"",
|
|
9960
|
+
...lines
|
|
9961
|
+
].join("\n\n");
|
|
9962
|
+
}
|
|
9692
9963
|
function formatBoardForReview(tasks) {
|
|
9693
9964
|
if (tasks.length === 0) return "No tasks on the board.";
|
|
9694
9965
|
return tasks.map(
|
|
@@ -10510,6 +10781,7 @@ Standard planning cycle with full board review.
|
|
|
10510
10781
|
**Security section guidance:** Each handoff includes a SECURITY CONSIDERATIONS section. Populate it when the task involves: data exposure risks (PII, secrets in logs/storage), secrets or credentials handling (API keys, tokens, env vars), auth/access control changes, or dependency security risks (new packages, version changes). For pure refactoring, documentation, prompt-text, or UI-only tasks, write "None \u2014 no security-relevant changes".
|
|
10511
10782
|
**Estimation calibration:** Estimate **XS** for: copy/text-only changes, single string replacements, config tweaks, and any task where the scope is "change words in an existing file" with no logic changes. Estimate **S** for: wiring existing adapter methods, adding API routes following established patterns, modifying prompts, or documentation-only changes. Default to S for pattern-following work. Only use M when genuine new architecture, new DB tables, or multi-file architectural changes are needed. Historical data shows systematic over-estimation (198 over vs 8 under out of 528 tasks) \u2014 when in doubt, estimate smaller. If an "Estimation Calibration (Historical)" section is provided in the context below, use its data to adjust your estimates \u2014 it shows how often each estimated size matched the actual effort. Pay special attention to systematic over/under-estimation patterns (e.g. if M\u2192S happens frequently, estimate S instead of M for similar work).
|
|
10512
10783
|
**Reference docs:** If a task's notes include a \`Reference:\` path (e.g. \`Reference: docs/architecture/papi-brain-v1.md\`), include a REFERENCE DOCS section in the BUILD HANDOFF with those paths. This tells the builder to read the referenced doc for background context before implementing. Do NOT omit or summarise the reference \u2014 pass it through so the builder can access the full document. Only tasks with explicit \`Reference:\` paths in their notes should have this section.
|
|
10784
|
+
**Full notes lookup:** Notes in the Board section are truncated to 300 chars for concise task selection. When generating a BUILD HANDOFF for a task, check the "Full Notes for Candidate Tasks" section (if present in context) for that task's complete untruncated notes before writing SCOPE, SCOPE BOUNDARY, and PRE-MORTEM. Submitter context, constraints, and reasoning often live past the 300-char cutoff and must not be dropped from the handoff.
|
|
10513
10785
|
**Pre-build verification:** EVERY handoff MUST include a PRE-BUILD VERIFICATION section listing 2-5 specific file paths the builder should read before implementing. Derive these from FILES LIKELY TOUCHED \u2014 pick the files most likely to already contain the target functionality. This is the #1 prevention mechanism for wasted build slots (C120, C125, C126 all scheduled already-shipped work). If the builder finds >80% of the scope already implemented, they report "already built" instead of re-implementing.
|
|
10514
10786
|
**Pre-mortem:** For projects with 10+ cycles, include a PRE-MORTEM section in every BUILD HANDOFF with 1-3 bullet points: (a) most likely technical blocker based on module history, (b) integration risk with adjacent systems, (c) scope creep signal \u2014 what the builder might be tempted to expand beyond scope. Draw from \`dead_ends\` and \`surprises\` in recent build reports for the same module. Omit this section entirely for projects with fewer than 10 cycles.
|
|
10515
10787
|
**Build order in cycle log:** If any intra-cycle dependencies were detected in this cycle, include a "Build Order" paragraph in \`cycleLogNotes\` showing the recommended build sequence as arrow chains (e.g. "Build order: task-123 \u2192 task-124; task-130 standalone"). Skip this paragraph when no dependencies exist.
|
|
@@ -10740,6 +11012,7 @@ Standard planning cycle with full board review.
|
|
|
10740
11012
|
**Security section guidance:** Each handoff includes a SECURITY CONSIDERATIONS section. Populate it when the task involves: data exposure risks (PII, secrets in logs/storage), secrets or credentials handling (API keys, tokens, env vars), auth/access control changes, or dependency security risks (new packages, version changes). For pure refactoring, documentation, prompt-text, or UI-only tasks, write "None \u2014 no security-relevant changes".
|
|
10741
11013
|
**Estimation calibration:** Estimate **XS** for: copy/text-only changes, single string replacements, config tweaks, and any task where the scope is "change words in an existing file" with no logic changes. Estimate **S** for: wiring existing adapter methods, adding API routes following established patterns, modifying prompts, or documentation-only changes. Default to S for pattern-following work. Only use M when genuine new architecture, new DB tables, or multi-file architectural changes are needed. Historical data shows systematic over-estimation (198 over vs 8 under out of 528 tasks) \u2014 when in doubt, estimate smaller. If an "Estimation Calibration (Historical)" section is provided in the context below, use its data to adjust your estimates \u2014 it shows how often each estimated size matched the actual effort. Pay special attention to systematic over/under-estimation patterns (e.g. if M\u2192S happens frequently, estimate S instead of M for similar work).
|
|
10742
11014
|
**Reference docs:** If a task's notes include a \`Reference:\` path (e.g. \`Reference: docs/architecture/papi-brain-v1.md\`), include a REFERENCE DOCS section in the BUILD HANDOFF with those paths. This tells the builder to read the referenced doc for background context before implementing. Do NOT omit or summarise the reference \u2014 pass it through so the builder can access the full document. Only tasks with explicit \`Reference:\` paths in their notes should have this section.
|
|
11015
|
+
**Full notes lookup:** Notes in the Board section are truncated to 300 chars for concise task selection. When generating a BUILD HANDOFF for a task, check the "Full Notes for Candidate Tasks" section (if present in context) for that task's complete untruncated notes before writing SCOPE, SCOPE BOUNDARY, and PRE-MORTEM. Submitter context, constraints, and reasoning often live past the 300-char cutoff and must not be dropped from the handoff.
|
|
10743
11016
|
**Pre-build verification:** EVERY handoff MUST include a PRE-BUILD VERIFICATION section listing 2-5 specific file paths the builder should read before implementing. Derive these from FILES LIKELY TOUCHED \u2014 pick the files most likely to already contain the target functionality. This is the #1 prevention mechanism for wasted build slots (C120, C125, C126 all scheduled already-shipped work). If the builder finds >80% of the scope already implemented, they report "already built" instead of re-implementing.
|
|
10744
11017
|
**Pre-mortem:** For projects with 10+ cycles, include a PRE-MORTEM section in every BUILD HANDOFF with 1-3 bullet points: (a) most likely technical blocker based on module history, (b) integration risk with adjacent systems, (c) scope creep signal \u2014 what the builder might be tempted to expand beyond scope. Draw from \`dead_ends\` and \`surprises\` in recent build reports for the same module. Omit this section entirely for projects with fewer than 10 cycles.
|
|
10745
11018
|
**Intra-cycle dependency detection:** After selecting cycle tasks, check every pair for build-order dependencies. Two tasks A and B have an intra-cycle dependency when A must be built before B because B consumes an artifact A creates \u2014 e.g. A adds a new adapter method that B calls, A creates a DB migration B depends on, A introduces a new shared type B imports, A refactors a utility B modifies. Signals: same module + adjacent scope (one is "add X", another is "use X"), or notes explicitly reference the other task. For each dependency detected: (a) populate the DEPENDS ON section in the dependent task's BUILD HANDOFF with the upstream task ID(s); (b) add a \`boardCorrections\` entry for the dependent task with \`updates.dependsOn\` set to the comma-separated upstream IDs \u2014 this persists the dependency so the builder's runtime can reuse the upstream branch; (c) keep SCOPE sections independent but note the ordering in "Why now". Do NOT invent dependencies where tasks merely share a module \u2014 only real build-order coupling counts. Linear chains only \u2014 no multi-level graph resolution. When in doubt, omit.
|
|
@@ -10798,6 +11071,9 @@ function buildPlanUserMessage(ctx) {
|
|
|
10798
11071
|
}) : PLAN_FULL_INSTRUCTIONS;
|
|
10799
11072
|
parts.push(instructions);
|
|
10800
11073
|
}
|
|
11074
|
+
if (ctx.foundationalTasksGuidance) {
|
|
11075
|
+
parts.push("", ctx.foundationalTasksGuidance);
|
|
11076
|
+
}
|
|
10801
11077
|
if (ctx.skipHandoffs) {
|
|
10802
11078
|
parts.push(
|
|
10803
11079
|
"",
|
|
@@ -10843,6 +11119,9 @@ function buildPlanUserMessage(ctx) {
|
|
|
10843
11119
|
if (ctx.board) {
|
|
10844
11120
|
parts.push("### Board", "", ctx.board, "");
|
|
10845
11121
|
}
|
|
11122
|
+
if (ctx.candidateTaskFullNotes) {
|
|
11123
|
+
parts.push("### Full Notes for Candidate Tasks", "", ctx.candidateTaskFullNotes, "");
|
|
11124
|
+
}
|
|
10846
11125
|
if (ctx.preAssignedTasks) {
|
|
10847
11126
|
parts.push("### Pre-Assigned Tasks", "", ctx.preAssignedTasks, "");
|
|
10848
11127
|
}
|
|
@@ -11316,6 +11595,9 @@ function buildReviewUserMessage(ctx) {
|
|
|
11316
11595
|
if (ctx.docActionStaleness) {
|
|
11317
11596
|
parts.push("### Doc Action Staleness", "", ctx.docActionStaleness, "");
|
|
11318
11597
|
}
|
|
11598
|
+
if (ctx.pendingAgendaTopics) {
|
|
11599
|
+
parts.push("### Queued Agenda Topics", "", "_Topics queued via `strategy_agenda` since the last review. Address each one in this review \u2014 they will be auto-marked as addressed on apply._", "", ctx.pendingAgendaTopics, "");
|
|
11600
|
+
}
|
|
11319
11601
|
return parts.join("\n");
|
|
11320
11602
|
}
|
|
11321
11603
|
function parseReviewStructuredOutput(raw) {
|
|
@@ -12002,6 +12284,64 @@ function stripTasksForPlan(tasks) {
|
|
|
12002
12284
|
hasHandoff: !!buildHandoff
|
|
12003
12285
|
}));
|
|
12004
12286
|
}
|
|
12287
|
+
var BRIEF_SECTIONS = [
|
|
12288
|
+
{ name: "title", pattern: /^#\s+\S/m },
|
|
12289
|
+
{
|
|
12290
|
+
name: "target audience",
|
|
12291
|
+
pattern: /\b(target users?|audience|for whom|who (it'?s for|uses|it serves|we're building))/i
|
|
12292
|
+
},
|
|
12293
|
+
{
|
|
12294
|
+
name: "problem statement",
|
|
12295
|
+
pattern: /\b(problem|pain point|why it matters|what problem|solves?)/i
|
|
12296
|
+
},
|
|
12297
|
+
{
|
|
12298
|
+
name: "solution / vision",
|
|
12299
|
+
pattern: /\b(solution|approach|vision|what (it|we) do|how it works|value proposition)/i
|
|
12300
|
+
},
|
|
12301
|
+
{
|
|
12302
|
+
name: "GTM / distribution",
|
|
12303
|
+
pattern: /\b(GTM|go[- ]to[- ]market|distribution|channel|pricing|how (users?|people) (discover|find|reach))/i
|
|
12304
|
+
}
|
|
12305
|
+
];
|
|
12306
|
+
function assessBriefThinness(brief) {
|
|
12307
|
+
const TEMPLATE_MARKER = "*Describe your project's core value proposition here.*";
|
|
12308
|
+
const briefWithoutTemplate = brief.replace(TEMPLATE_MARKER, "");
|
|
12309
|
+
const populated = [];
|
|
12310
|
+
const missing = [];
|
|
12311
|
+
for (const section of BRIEF_SECTIONS) {
|
|
12312
|
+
if (section.pattern.test(briefWithoutTemplate)) populated.push(section.name);
|
|
12313
|
+
else missing.push(section.name);
|
|
12314
|
+
}
|
|
12315
|
+
return { populatedSections: populated, missingSections: missing };
|
|
12316
|
+
}
|
|
12317
|
+
function computeFoundationalTasksGuidance(cycleNumber, brief) {
|
|
12318
|
+
if (cycleNumber > 1) return void 0;
|
|
12319
|
+
const { populatedSections, missingSections } = assessBriefThinness(brief);
|
|
12320
|
+
if (populatedSections.length >= 4) return void 0;
|
|
12321
|
+
const populatedList = populatedSections.length > 0 ? populatedSections.join(", ") : "none";
|
|
12322
|
+
const missingList = missingSections.join(", ");
|
|
12323
|
+
return [
|
|
12324
|
+
"## FOUNDATIONAL TASKS GUIDANCE",
|
|
12325
|
+
"",
|
|
12326
|
+
`The project brief is thin \u2014 only ${populatedSections.length} of 5 key sections are clearly populated (${populatedList}). Missing or weak sections: ${missingList}.`,
|
|
12327
|
+
"",
|
|
12328
|
+
"Generate 3-5 foundational research/discovery tasks targeting the MISSING sections. These help the Owner fill in their brief before the product gets built on top of shaky foundations.",
|
|
12329
|
+
"",
|
|
12330
|
+
"Rules for foundational tasks:",
|
|
12331
|
+
"- **ADDITIONS, not replacements.** Include them ALONGSIDE the user's backlog items. If the user submitted 3 ideas, the cycle should have 3 user items + 3-5 foundational items (total 6-8 tasks).",
|
|
12332
|
+
'- **Target specific missing sections.** Do NOT generate "refine audience" if audience is already populated. Each foundational task must close a specific gap above.',
|
|
12333
|
+
"- **Task type: `research` or `discovery`** \u2014 deliverable is a findings doc, not shipped code.",
|
|
12334
|
+
"- **Effort: S or M** \u2014 foundational work should be timeboxed, not open-ended.",
|
|
12335
|
+
'- **WHY in the handoff must tie to the specific gap.** Example: "Brief is thin on GTM \u2014 knowing how users discover the product is a prerequisite for scoping distribution work." Not generic.',
|
|
12336
|
+
"- **Cap at 5 foundational tasks** to avoid drowning the user.",
|
|
12337
|
+
"- **Cycle 2+ will NOT receive this guidance** \u2014 even if the brief stays thin. Foundational work is a one-shot onboarding primitive, not a recurring pattern.",
|
|
12338
|
+
"",
|
|
12339
|
+
"In the structured output:",
|
|
12340
|
+
"- Emit foundational tasks via the `newTasks` array (mark `reviewed: true`, type `research` or `discovery`).",
|
|
12341
|
+
"- Generate full BUILD HANDOFFs for each in `cycleHandoffs` \u2014 use `new-N` IDs to reference them.",
|
|
12342
|
+
"- User-submitted backlog tasks remain in `cycleHandoffs` as usual \u2014 do NOT drop them in favour of foundational tasks."
|
|
12343
|
+
].join("\n");
|
|
12344
|
+
}
|
|
12005
12345
|
function detectBoardFlags(tasks) {
|
|
12006
12346
|
let hasBugTasks = false;
|
|
12007
12347
|
let hasResearchTasks = false;
|
|
@@ -12238,6 +12578,10 @@ ${lines.join("\n")}`;
|
|
|
12238
12578
|
if (reportsForCapsResult.status === "fulfilled") {
|
|
12239
12579
|
recentlyShippedLean = formatRecentlyShippedCapabilities(reportsForCapsResult.value);
|
|
12240
12580
|
}
|
|
12581
|
+
let candidateTaskFullNotesLean;
|
|
12582
|
+
if (preAssignedResult.status === "fulfilled") {
|
|
12583
|
+
candidateTaskFullNotesLean = formatCandidateTaskFullNotes(preAssignedResult.value);
|
|
12584
|
+
}
|
|
12241
12585
|
logDataSourceSummary("plan (lean)", [
|
|
12242
12586
|
{ label: "cycleHealth", hasData: !!health },
|
|
12243
12587
|
{ label: "productBrief", hasData: warnIfEmpty("productBrief", productBrief) },
|
|
@@ -12275,7 +12619,9 @@ ${lines.join("\n")}`;
|
|
|
12275
12619
|
carryForwardStaleness: carryForwardStalenessLean,
|
|
12276
12620
|
preAssignedTasks: preAssignedTextLean,
|
|
12277
12621
|
recentlyShippedCapabilities: recentlyShippedLean,
|
|
12278
|
-
strategyReviewCadence
|
|
12622
|
+
strategyReviewCadence,
|
|
12623
|
+
candidateTaskFullNotes: candidateTaskFullNotesLean,
|
|
12624
|
+
foundationalTasksGuidance: computeFoundationalTasksGuidance(health.totalCycles, productBrief)
|
|
12279
12625
|
};
|
|
12280
12626
|
const { label: leanTierLabel } = applyContextTier(ctx2, health.totalCycles);
|
|
12281
12627
|
ctx2.contextTier = leanTierLabel;
|
|
@@ -12436,7 +12782,9 @@ ${logLines}`);
|
|
|
12436
12782
|
carryForwardStaleness: computeCarryForwardStaleness(log),
|
|
12437
12783
|
preAssignedTasks: preAssignedText,
|
|
12438
12784
|
recentlyShippedCapabilities: formatRecentlyShippedCapabilities(reports),
|
|
12439
|
-
strategyReviewCadence: strategyReviewCadenceFull
|
|
12785
|
+
strategyReviewCadence: strategyReviewCadenceFull,
|
|
12786
|
+
candidateTaskFullNotes: formatCandidateTaskFullNotes(plannerTasks),
|
|
12787
|
+
foundationalTasksGuidance: computeFoundationalTasksGuidance(health.totalCycles, productBrief)
|
|
12440
12788
|
};
|
|
12441
12789
|
const { label: fullTierLabel } = applyContextTier(ctx, health.totalCycles);
|
|
12442
12790
|
ctx.contextTier = fullTierLabel;
|
|
@@ -13817,6 +14165,7 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
|
|
|
13817
14165
|
// Doc registry — docs with pending actions for staleness audit
|
|
13818
14166
|
adapter2.searchDocs?.({ hasPendingActions: true, limit: 20 })?.catch(() => []) ?? Promise.resolve([])
|
|
13819
14167
|
]);
|
|
14168
|
+
const pendingAgendaTopics = await (adapter2.getPendingAgendaTopics?.().catch(() => []) ?? Promise.resolve([]));
|
|
13820
14169
|
const tasks = [...activeTasks, ...recentDoneTasks];
|
|
13821
14170
|
const existingAdIds = new Set(decisions.map((d) => d.id));
|
|
13822
14171
|
const survivingPendingRecs = [];
|
|
@@ -14054,6 +14403,15 @@ ${deferred.join("\n")}`);
|
|
|
14054
14403
|
}
|
|
14055
14404
|
}
|
|
14056
14405
|
} catch {
|
|
14406
|
+
}
|
|
14407
|
+
let pendingAgendaText;
|
|
14408
|
+
if (pendingAgendaTopics.length > 0) {
|
|
14409
|
+
const lines = pendingAgendaTopics.map((t, i) => {
|
|
14410
|
+
const cycleSuffix = t.sourceCycle != null ? ` (queued Cycle ${t.sourceCycle})` : "";
|
|
14411
|
+
return `${i + 1}. ${t.topic} _[${t.source}${cycleSuffix}]_`;
|
|
14412
|
+
});
|
|
14413
|
+
pendingAgendaText = `${pendingAgendaTopics.length} topic(s) queued via strategy_agenda:
|
|
14414
|
+
${lines.join("\n")}`;
|
|
14057
14415
|
}
|
|
14058
14416
|
logDataSourceSummary("strategy_review_audit", [
|
|
14059
14417
|
{ label: "discoveryCanvas", hasData: discoveryCanvasText !== void 0 },
|
|
@@ -14094,7 +14452,8 @@ ${deferred.join("\n")}`);
|
|
|
14094
14452
|
recentPlans: recentPlansText,
|
|
14095
14453
|
unregisteredDocs: unregisteredDocsText,
|
|
14096
14454
|
taskComments: taskCommentsText,
|
|
14097
|
-
docActionStaleness: docActionStalenessText
|
|
14455
|
+
docActionStaleness: docActionStalenessText,
|
|
14456
|
+
pendingAgendaTopics: pendingAgendaText
|
|
14098
14457
|
};
|
|
14099
14458
|
const BUDGET_SOFT2 = 5e4;
|
|
14100
14459
|
const BUDGET_HARD2 = 6e4;
|
|
@@ -14409,6 +14768,15 @@ async function processReviewOutput(adapter2, rawOutput, cycleNumber) {
|
|
|
14409
14768
|
await adapter2.clearPendingReviewResponse?.();
|
|
14410
14769
|
} catch {
|
|
14411
14770
|
}
|
|
14771
|
+
try {
|
|
14772
|
+
if (adapter2.getPendingAgendaTopics && adapter2.markAgendaTopicsAddressed) {
|
|
14773
|
+
const pending = await adapter2.getPendingAgendaTopics();
|
|
14774
|
+
if (pending.length > 0) {
|
|
14775
|
+
await adapter2.markAgendaTopicsAddressed(pending.map((t) => t.id), cycleNumber);
|
|
14776
|
+
}
|
|
14777
|
+
}
|
|
14778
|
+
} catch {
|
|
14779
|
+
}
|
|
14412
14780
|
const webhookUrl = process.env.PAPI_SLACK_WEBHOOK_URL;
|
|
14413
14781
|
slackWarning = await sendSlackWebhook(webhookUrl, buildSlackSummary(data));
|
|
14414
14782
|
}
|
|
@@ -14929,6 +15297,34 @@ var strategyReviewTool = {
|
|
|
14929
15297
|
required: []
|
|
14930
15298
|
}
|
|
14931
15299
|
};
|
|
15300
|
+
var strategyAgendaTool = {
|
|
15301
|
+
name: "strategy_agenda",
|
|
15302
|
+
description: 'Queue topics for the next strategy review. Topics surface as input in the next `strategy_review` prepare phase and are automatically marked as addressed after the review completes. Two modes: "add" to queue a topic, "list" to see pending topics. Use this when you spot a strategic question during a build \u2014 capture the topic now instead of losing it.',
|
|
15303
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
15304
|
+
inputSchema: {
|
|
15305
|
+
type: "object",
|
|
15306
|
+
properties: {
|
|
15307
|
+
mode: {
|
|
15308
|
+
type: "string",
|
|
15309
|
+
enum: ["add", "list"],
|
|
15310
|
+
description: '"add" to queue a topic (requires `topic`). "list" returns all pending topics. Defaults to "list" when omitted.'
|
|
15311
|
+
},
|
|
15312
|
+
topic: {
|
|
15313
|
+
type: "string",
|
|
15314
|
+
description: 'The topic to queue (mode "add" only). One sentence describing what the next strategy review should consider.'
|
|
15315
|
+
},
|
|
15316
|
+
source: {
|
|
15317
|
+
type: "string",
|
|
15318
|
+
description: 'Optional origin label \u2014 e.g. "manual", "carry-forward", "idea". Defaults to "manual".'
|
|
15319
|
+
},
|
|
15320
|
+
source_cycle: {
|
|
15321
|
+
type: "number",
|
|
15322
|
+
description: 'Optional cycle number this topic originated from (mode "add" only).'
|
|
15323
|
+
}
|
|
15324
|
+
},
|
|
15325
|
+
required: []
|
|
15326
|
+
}
|
|
15327
|
+
};
|
|
14932
15328
|
var strategyChangeTool = {
|
|
14933
15329
|
name: "strategy_change",
|
|
14934
15330
|
description: 'Apply a strategic shift to the project. Three modes: "capture" for lightweight mid-conversation decision capture (no LLM round-trip), "prepare" to get a change prompt for full analysis, "apply" to persist analysis output. Use "capture" when you detect a strategic decision in conversation and want to persist it quickly without disrupting the build flow.',
|
|
@@ -15085,6 +15481,49 @@ ${result.userMessage}
|
|
|
15085
15481
|
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
15086
15482
|
}
|
|
15087
15483
|
}
|
|
15484
|
+
async function handleStrategyAgenda(adapter2, _config, args) {
|
|
15485
|
+
const mode = args.mode ?? "list";
|
|
15486
|
+
if (!adapter2.addAgendaTopic || !adapter2.getPendingAgendaTopics) {
|
|
15487
|
+
return errorResponse("strategy_agenda is not supported by the current adapter.");
|
|
15488
|
+
}
|
|
15489
|
+
try {
|
|
15490
|
+
if (mode === "add") {
|
|
15491
|
+
const topic = args.topic;
|
|
15492
|
+
if (!topic || !topic.trim()) {
|
|
15493
|
+
return errorResponse('topic is required for mode "add". Describe what the next strategy review should consider.');
|
|
15494
|
+
}
|
|
15495
|
+
const source = (args.source ?? "manual").trim() || "manual";
|
|
15496
|
+
const sourceCycle = typeof args.source_cycle === "number" ? args.source_cycle : void 0;
|
|
15497
|
+
const entry = await adapter2.addAgendaTopic({ topic: topic.trim(), source, sourceCycle });
|
|
15498
|
+
return textResponse(
|
|
15499
|
+
`**Agenda Topic Queued**
|
|
15500
|
+
|
|
15501
|
+
${entry.topic}
|
|
15502
|
+
|
|
15503
|
+
Source: ${entry.source}${entry.sourceCycle != null ? ` (Cycle ${entry.sourceCycle})` : ""}
|
|
15504
|
+
ID: ${entry.id}
|
|
15505
|
+
|
|
15506
|
+
This topic will surface in the next \`strategy_review\`.`
|
|
15507
|
+
);
|
|
15508
|
+
}
|
|
15509
|
+
const topics = await adapter2.getPendingAgendaTopics();
|
|
15510
|
+
if (topics.length === 0) {
|
|
15511
|
+
return textResponse('No pending agenda topics. Use `strategy_agenda` with `mode: "add"` to queue one.');
|
|
15512
|
+
}
|
|
15513
|
+
const lines = topics.map((t, i) => {
|
|
15514
|
+
const cycleSuffix = t.sourceCycle != null ? ` (Cycle ${t.sourceCycle})` : "";
|
|
15515
|
+
return `${i + 1}. ${t.topic}
|
|
15516
|
+
_source: ${t.source}${cycleSuffix} \xB7 queued ${t.createdAt.slice(0, 10)}_`;
|
|
15517
|
+
});
|
|
15518
|
+
return textResponse(
|
|
15519
|
+
`**Pending Agenda (${topics.length})** \u2014 surfaces at next strategy review
|
|
15520
|
+
|
|
15521
|
+
${lines.join("\n\n")}`
|
|
15522
|
+
);
|
|
15523
|
+
} catch (err) {
|
|
15524
|
+
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
15525
|
+
}
|
|
15526
|
+
}
|
|
15088
15527
|
async function handleStrategyChange(adapter2, _config, args) {
|
|
15089
15528
|
const toolMode = args.mode;
|
|
15090
15529
|
try {
|
|
@@ -15356,7 +15795,7 @@ var boardArchiveTool = {
|
|
|
15356
15795
|
};
|
|
15357
15796
|
var boardEditTool = {
|
|
15358
15797
|
name: "board_edit",
|
|
15359
|
-
description: "Edit fields on an existing task. Supports title, priority, complexity, module, epic, phase, notes, status, and maturity. Pass task_id plus any fields to update. Does not call the Anthropic API.",
|
|
15798
|
+
description: "Edit fields on an existing task. Supports title, priority, complexity, module, epic, phase, notes (with notes_mode for append/replace/clear), status, and maturity. Pass task_id plus any fields to update. Does not call the Anthropic API.",
|
|
15360
15799
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
15361
15800
|
inputSchema: {
|
|
15362
15801
|
type: "object",
|
|
@@ -15393,7 +15832,12 @@ var boardEditTool = {
|
|
|
15393
15832
|
},
|
|
15394
15833
|
notes: {
|
|
15395
15834
|
type: "string",
|
|
15396
|
-
description: "
|
|
15835
|
+
description: "Note content. Default behaviour is append \u2014 see notes_mode to control."
|
|
15836
|
+
},
|
|
15837
|
+
notes_mode: {
|
|
15838
|
+
type: "string",
|
|
15839
|
+
enum: ["append", "replace", "clear"],
|
|
15840
|
+
description: "How to apply the notes value. append (default) = add a new dated entry above existing notes; replace = overwrite all existing notes; clear = empty the notes field (notes value ignored)."
|
|
15397
15841
|
},
|
|
15398
15842
|
status: {
|
|
15399
15843
|
type: "string",
|
|
@@ -15596,6 +16040,10 @@ async function handleBoardEdit(adapter2, args) {
|
|
|
15596
16040
|
changes.push(field);
|
|
15597
16041
|
}
|
|
15598
16042
|
}
|
|
16043
|
+
const notesMode = args.notes_mode;
|
|
16044
|
+
if (notesMode === "clear" && !changes.includes("notes")) {
|
|
16045
|
+
changes.push("notes");
|
|
16046
|
+
}
|
|
15599
16047
|
if (changes.length === 0) {
|
|
15600
16048
|
return errorResponse("No fields to update. Pass at least one field (title, priority, complexity, module, epic, phase, notes, status, maturity).");
|
|
15601
16049
|
}
|
|
@@ -15604,6 +16052,35 @@ async function handleBoardEdit(adapter2, args) {
|
|
|
15604
16052
|
if (!task) {
|
|
15605
16053
|
return errorResponse(`Task ${taskId} not found.`);
|
|
15606
16054
|
}
|
|
16055
|
+
if (changes.includes("notes")) {
|
|
16056
|
+
const incoming = args.notes ?? "";
|
|
16057
|
+
const existing = task.notes ?? "";
|
|
16058
|
+
const mode = notesMode ?? "append";
|
|
16059
|
+
if (mode === "clear") {
|
|
16060
|
+
updates.notes = "";
|
|
16061
|
+
} else if (mode === "replace") {
|
|
16062
|
+
updates.notes = incoming;
|
|
16063
|
+
} else {
|
|
16064
|
+
const trimmed = incoming.trim();
|
|
16065
|
+
if (trimmed.length === 0) {
|
|
16066
|
+
delete updates.notes;
|
|
16067
|
+
const idx = changes.indexOf("notes");
|
|
16068
|
+
if (idx >= 0) changes.splice(idx, 1);
|
|
16069
|
+
} else {
|
|
16070
|
+
const health = await adapter2.getCycleHealth().catch(() => null);
|
|
16071
|
+
const activeCycle = health?.totalCycles ?? null;
|
|
16072
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
16073
|
+
const stamp = activeCycle != null ? `[C${activeCycle} ${date}]` : `[${date}]`;
|
|
16074
|
+
const entry = `${stamp} ${trimmed}`;
|
|
16075
|
+
updates.notes = existing.trim().length > 0 ? `${entry}
|
|
16076
|
+
|
|
16077
|
+
${existing}` : entry;
|
|
16078
|
+
}
|
|
16079
|
+
}
|
|
16080
|
+
if (changes.length === 0) {
|
|
16081
|
+
return errorResponse("No fields to update. Pass at least one field (title, priority, complexity, module, epic, phase, notes, status, maturity).");
|
|
16082
|
+
}
|
|
16083
|
+
}
|
|
15607
16084
|
if (updates.status === "Backlog" && task.cycle != null) {
|
|
15608
16085
|
updates.cycle = void 0;
|
|
15609
16086
|
updates.cycle = null;
|
|
@@ -16625,9 +17102,8 @@ async function prepareSetup(adapter2, config2, input) {
|
|
|
16625
17102
|
);
|
|
16626
17103
|
}
|
|
16627
17104
|
const TEMPLATE_MARKER = "*Describe your project's core value proposition here.*";
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
}
|
|
17105
|
+
const briefHasRealContent = existingBrief.trim().length > 0 && !existingBrief.includes(TEMPLATE_MARKER);
|
|
17106
|
+
const briefAlreadyExists = briefHasRealContent && !input.force;
|
|
16631
17107
|
const detectedCodebaseType = detectCodebaseType(config2.projectRoot);
|
|
16632
17108
|
const autoDetected = input.existingProject === void 0 || input.existingProject === false;
|
|
16633
17109
|
const isExistingProject = input.existingProject === true || autoDetected && detectedCodebaseType === "existing_codebase";
|
|
@@ -16640,7 +17116,7 @@ async function prepareSetup(adapter2, config2, input) {
|
|
|
16640
17116
|
}
|
|
16641
17117
|
codebaseSummary = formatCodebaseSummary(scan, sourceContents);
|
|
16642
17118
|
}
|
|
16643
|
-
const briefPrompt = {
|
|
17119
|
+
const briefPrompt = briefAlreadyExists ? void 0 : {
|
|
16644
17120
|
system: PRODUCT_BRIEF_SYSTEM,
|
|
16645
17121
|
user: buildProductBriefPrompt({
|
|
16646
17122
|
projectName: input.projectName,
|
|
@@ -16694,15 +17170,29 @@ async function prepareSetup(adapter2, config2, input) {
|
|
|
16694
17170
|
initialTasksPrompt,
|
|
16695
17171
|
codebaseSummary,
|
|
16696
17172
|
detectedCodebaseType,
|
|
16697
|
-
autoDetected: autoDetected && detectedCodebaseType !== "new_project"
|
|
17173
|
+
autoDetected: autoDetected && detectedCodebaseType !== "new_project",
|
|
17174
|
+
briefAlreadyExists
|
|
16698
17175
|
};
|
|
16699
17176
|
}
|
|
16700
17177
|
async function applySetup(adapter2, config2, input, briefText, adSeedText, conventionsText, initialTasksText) {
|
|
16701
17178
|
const createdProject = await scaffoldPapiDir(adapter2, config2, input);
|
|
16702
|
-
|
|
16703
|
-
|
|
17179
|
+
const TEMPLATE_MARKER = "*Describe your project's core value proposition here.*";
|
|
17180
|
+
let effectiveBriefText = briefText;
|
|
17181
|
+
if (!effectiveBriefText.trim()) {
|
|
17182
|
+
let existingBrief = "";
|
|
17183
|
+
try {
|
|
17184
|
+
existingBrief = await adapter2.readProductBrief();
|
|
17185
|
+
} catch {
|
|
17186
|
+
existingBrief = "";
|
|
17187
|
+
}
|
|
17188
|
+
const existingIsReal = existingBrief.trim().length > 0 && !existingBrief.includes(TEMPLATE_MARKER);
|
|
17189
|
+
if (existingIsReal && !input.force) {
|
|
17190
|
+
effectiveBriefText = existingBrief;
|
|
17191
|
+
} else {
|
|
17192
|
+
throw new Error("brief_response is required and cannot be empty.");
|
|
17193
|
+
}
|
|
16704
17194
|
}
|
|
16705
|
-
const { seededAds, warnings } = await applySetupOutputs(adapter2, config2, input,
|
|
17195
|
+
const { seededAds, warnings } = await applySetupOutputs(adapter2, config2, input, effectiveBriefText, adSeedText, conventionsText);
|
|
16706
17196
|
let createdTasks = 0;
|
|
16707
17197
|
if (initialTasksText?.trim()) {
|
|
16708
17198
|
try {
|
|
@@ -16928,10 +17418,7 @@ PAPI needs just 3 things: project name, what it does, and who it's for.`
|
|
|
16928
17418
|
const input = extractInput(args);
|
|
16929
17419
|
try {
|
|
16930
17420
|
if (toolMode === "apply") {
|
|
16931
|
-
const briefResponse = args.brief_response;
|
|
16932
|
-
if (!briefResponse || !briefResponse.trim()) {
|
|
16933
|
-
return errorResponse('brief_response is required for mode "apply".');
|
|
16934
|
-
}
|
|
17421
|
+
const briefResponse = args.brief_response ?? "";
|
|
16935
17422
|
const adSeedResponse = args.ad_seed_response;
|
|
16936
17423
|
const conventionsResponse = args.conventions_response;
|
|
16937
17424
|
const initialTasksResponse = args.initial_tasks_response;
|
|
@@ -16964,6 +17451,12 @@ PAPI needs just 3 things: project name, what it does, and who it's for.`
|
|
|
16964
17451
|
""
|
|
16965
17452
|
);
|
|
16966
17453
|
}
|
|
17454
|
+
if (result.briefAlreadyExists) {
|
|
17455
|
+
sections.push(
|
|
17456
|
+
`**Existing brief detected:** this project already has a Product Brief (e.g. from the web wizard). Setup will preserve it and skip brief generation \u2014 no \`brief_response\` needed in the apply step. To overwrite the brief instead, re-run setup with \`force: true\`.`,
|
|
17457
|
+
""
|
|
17458
|
+
);
|
|
17459
|
+
}
|
|
16967
17460
|
if (inferredDefaults.length > 0) {
|
|
16968
17461
|
sections.push(
|
|
16969
17462
|
`**Defaults applied** (override by re-running setup with these fields):`,
|
|
@@ -16974,30 +17467,37 @@ PAPI needs just 3 things: project name, what it does, and who it's for.`
|
|
|
16974
17467
|
sections.push(
|
|
16975
17468
|
`Generate the outputs below, then call \`setup\` again with:`,
|
|
16976
17469
|
`- \`mode\`: "apply"`,
|
|
16977
|
-
`- \`brief_response\`: your Product Brief markdown
|
|
17470
|
+
result.briefPrompt ? `- \`brief_response\`: your Product Brief markdown` : "",
|
|
16978
17471
|
result.adSeedPrompt ? `- \`ad_seed_response\`: your AD seed JSON array` : "",
|
|
16979
17472
|
result.conventionsPrompt ? `- \`conventions_response\`: your conventions markdown` : "",
|
|
16980
17473
|
result.initialTasksPrompt ? `- \`initial_tasks_response\`: your initial tasks JSON array` : "",
|
|
16981
17474
|
`- Plus all the original setup fields (project_name, description, target_users${isExisting ? ", existing_project: true" : ""})`,
|
|
16982
17475
|
"",
|
|
16983
|
-
|
|
16984
|
-
|
|
16985
|
-
|
|
16986
|
-
|
|
16987
|
-
|
|
17476
|
+
`---`
|
|
17477
|
+
);
|
|
17478
|
+
let sectionNum = 0;
|
|
17479
|
+
if (result.briefPrompt) {
|
|
17480
|
+
sectionNum++;
|
|
17481
|
+
sections.push(
|
|
17482
|
+
"",
|
|
17483
|
+
`### ${sectionNum}. Product Brief`,
|
|
17484
|
+
"",
|
|
17485
|
+
`<system_prompt>
|
|
16988
17486
|
${result.briefPrompt.system}
|
|
16989
17487
|
</system_prompt>`,
|
|
16990
|
-
|
|
16991
|
-
|
|
17488
|
+
"",
|
|
17489
|
+
`<context>
|
|
16992
17490
|
${result.briefPrompt.user}
|
|
16993
17491
|
</context>`
|
|
16994
|
-
|
|
17492
|
+
);
|
|
17493
|
+
}
|
|
16995
17494
|
if (result.adSeedPrompt) {
|
|
17495
|
+
sectionNum++;
|
|
16996
17496
|
sections.push(
|
|
16997
17497
|
"",
|
|
16998
17498
|
`---`,
|
|
16999
17499
|
"",
|
|
17000
|
-
`###
|
|
17500
|
+
`### ${sectionNum}. Active Decision Seeds`,
|
|
17001
17501
|
"",
|
|
17002
17502
|
`<system_prompt>
|
|
17003
17503
|
${result.adSeedPrompt.system}
|
|
@@ -17009,11 +17509,12 @@ ${result.adSeedPrompt.user}
|
|
|
17009
17509
|
);
|
|
17010
17510
|
}
|
|
17011
17511
|
if (result.conventionsPrompt) {
|
|
17512
|
+
sectionNum++;
|
|
17012
17513
|
sections.push(
|
|
17013
17514
|
"",
|
|
17014
17515
|
`---`,
|
|
17015
17516
|
"",
|
|
17016
|
-
`###
|
|
17517
|
+
`### ${sectionNum}. Conventions`,
|
|
17017
17518
|
"",
|
|
17018
17519
|
`<system_prompt>
|
|
17019
17520
|
${result.conventionsPrompt.system}
|
|
@@ -17025,11 +17526,12 @@ ${result.conventionsPrompt.user}
|
|
|
17025
17526
|
);
|
|
17026
17527
|
}
|
|
17027
17528
|
if (result.initialTasksPrompt) {
|
|
17529
|
+
sectionNum++;
|
|
17028
17530
|
sections.push(
|
|
17029
17531
|
"",
|
|
17030
17532
|
`---`,
|
|
17031
17533
|
"",
|
|
17032
|
-
`###
|
|
17534
|
+
`### ${sectionNum}. Initial Backlog Tasks`,
|
|
17033
17535
|
"",
|
|
17034
17536
|
`<system_prompt>
|
|
17035
17537
|
${result.initialTasksPrompt.system}
|
|
@@ -17441,18 +17943,37 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
|
17441
17943
|
}
|
|
17442
17944
|
}
|
|
17443
17945
|
let autoTriagedCount = 0;
|
|
17946
|
+
const autoTriagedIds = [];
|
|
17947
|
+
const autoTriagedDupes = [];
|
|
17444
17948
|
if (input.discoveredIssues && input.discoveredIssues !== "None" && typeof adapter2.createTask === "function") {
|
|
17445
17949
|
const issueLines = input.discoveredIssues.split(/\n|;/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
17950
|
+
const backlogTitleMap = /* @__PURE__ */ new Map();
|
|
17951
|
+
try {
|
|
17952
|
+
const backlog = await adapter2.queryBoard({ status: ["Backlog"] });
|
|
17953
|
+
for (const t of backlog) {
|
|
17954
|
+
const normalized = t.title.replace(/^\[Auto-triaged\]\s*/i, "").trim().toLowerCase();
|
|
17955
|
+
if (normalized && !backlogTitleMap.has(normalized)) {
|
|
17956
|
+
backlogTitleMap.set(normalized, t.displayId);
|
|
17957
|
+
}
|
|
17958
|
+
}
|
|
17959
|
+
} catch {
|
|
17960
|
+
}
|
|
17446
17961
|
for (const line of issueLines) {
|
|
17447
17962
|
const sevMatch = line.match(/^(P[0-3])[\s:]+/i);
|
|
17448
17963
|
if (!sevMatch) continue;
|
|
17449
17964
|
const severityLabel = sevMatch[1].toUpperCase();
|
|
17450
|
-
const priority = severityLabel === "P0"
|
|
17965
|
+
const priority = severityLabel === "P0" ? "P0 Critical" : severityLabel === "P1" ? "P1 High" : severityLabel === "P2" ? "P2 Medium" : "P3 Low";
|
|
17451
17966
|
const titleRaw = line.replace(/^P[0-3][\s:]+/i, "").trim();
|
|
17452
17967
|
const title = titleRaw.length > 120 ? titleRaw.slice(0, 120) : titleRaw;
|
|
17453
17968
|
if (!title) continue;
|
|
17969
|
+
const normalized = title.toLowerCase();
|
|
17970
|
+
const dupId = backlogTitleMap.get(normalized);
|
|
17971
|
+
if (dupId) {
|
|
17972
|
+
autoTriagedDupes.push(dupId);
|
|
17973
|
+
continue;
|
|
17974
|
+
}
|
|
17454
17975
|
try {
|
|
17455
|
-
await adapter2.createTask({
|
|
17976
|
+
const created = await adapter2.createTask({
|
|
17456
17977
|
uuid: "",
|
|
17457
17978
|
displayId: "",
|
|
17458
17979
|
title: `[Auto-triaged] ${title}`,
|
|
@@ -17469,6 +17990,10 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
|
17469
17990
|
createdCycle: cycleNumber
|
|
17470
17991
|
});
|
|
17471
17992
|
autoTriagedCount++;
|
|
17993
|
+
if (created?.displayId) {
|
|
17994
|
+
autoTriagedIds.push(created.displayId);
|
|
17995
|
+
backlogTitleMap.set(normalized, created.displayId);
|
|
17996
|
+
}
|
|
17472
17997
|
} catch {
|
|
17473
17998
|
}
|
|
17474
17999
|
}
|
|
@@ -17665,6 +18190,8 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
|
17665
18190
|
dogfoodResolvedCount: dogfoodResolvedCount > 0 ? dogfoodResolvedCount : void 0,
|
|
17666
18191
|
learningsLinkedCount: learningsLinkedCount > 0 ? learningsLinkedCount : void 0,
|
|
17667
18192
|
autoTriagedCount: autoTriagedCount > 0 ? autoTriagedCount : void 0,
|
|
18193
|
+
autoTriagedIds: autoTriagedIds.length > 0 ? autoTriagedIds : void 0,
|
|
18194
|
+
autoTriagedDupes: autoTriagedDupes.length > 0 ? autoTriagedDupes : void 0,
|
|
17668
18195
|
reportWriteVerified
|
|
17669
18196
|
};
|
|
17670
18197
|
}
|
|
@@ -18168,8 +18695,16 @@ function formatCompleteResult(result) {
|
|
|
18168
18695
|
if (result.learningsLinkedCount) {
|
|
18169
18696
|
lines.push("", `Linked ${result.learningsLinkedCount} unactioned learning(s) to this task.`);
|
|
18170
18697
|
}
|
|
18171
|
-
if (result.autoTriagedCount) {
|
|
18172
|
-
|
|
18698
|
+
if (result.autoTriagedCount || result.autoTriagedDupes?.length) {
|
|
18699
|
+
const parts = [];
|
|
18700
|
+
if (result.autoTriagedCount) {
|
|
18701
|
+
const ids = result.autoTriagedIds?.length ? ` (${result.autoTriagedIds.join(", ")})` : "";
|
|
18702
|
+
parts.push(`\u{1F516} Auto-triaged ${result.autoTriagedCount} discovered issue(s) to Backlog${ids}.`);
|
|
18703
|
+
}
|
|
18704
|
+
if (result.autoTriagedDupes?.length) {
|
|
18705
|
+
parts.push(`Skipped ${result.autoTriagedDupes.length} duplicate issue(s) already in Backlog: ${result.autoTriagedDupes.join(", ")}.`);
|
|
18706
|
+
}
|
|
18707
|
+
lines.push("", parts.join(" "));
|
|
18173
18708
|
}
|
|
18174
18709
|
if (result.reportWriteVerified === false) {
|
|
18175
18710
|
lines.push("", "\u26A0\uFE0F Build report write could not be verified \u2014 the report may not have been persisted. Run `build_list` to check, and resubmit if missing.");
|
|
@@ -19784,7 +20319,41 @@ function generateChangelog(version, commits) {
|
|
|
19784
20319
|
${commitList}
|
|
19785
20320
|
`;
|
|
19786
20321
|
}
|
|
19787
|
-
|
|
20322
|
+
function mergeGroupedCycleBranches(config2, cycleNum, baseBranch) {
|
|
20323
|
+
const branches = listGroupedCycleBranches(config2.projectRoot, cycleNum, baseBranch);
|
|
20324
|
+
if (branches.length === 0) return [];
|
|
20325
|
+
if (!isGhAvailable()) {
|
|
20326
|
+
throw new Error(
|
|
20327
|
+
`Release blocked: ${branches.length} unmerged grouped cycle branch(es) detected (${branches.join(", ")}) but \`gh\` CLI is not available. Install gh and re-run release.`
|
|
20328
|
+
);
|
|
20329
|
+
}
|
|
20330
|
+
const results = [];
|
|
20331
|
+
for (const branch of branches) {
|
|
20332
|
+
let prUrl = getPullRequestUrl(config2.projectRoot, branch);
|
|
20333
|
+
if (!prUrl) {
|
|
20334
|
+
const moduleName = branch.replace(`feat/cycle-${cycleNum}-`, "");
|
|
20335
|
+
const prCreate = createPullRequest(
|
|
20336
|
+
config2.projectRoot,
|
|
20337
|
+
branch,
|
|
20338
|
+
baseBranch,
|
|
20339
|
+
`feat(cycle-${cycleNum}): merge shared cycle branch \u2014 ${moduleName}`,
|
|
20340
|
+
`Automated PR created at release time for shared cycle branch \`${branch}\` (Cycle ${cycleNum}).`
|
|
20341
|
+
);
|
|
20342
|
+
if (!prCreate.success) {
|
|
20343
|
+
results.push({ branch, prUrl: null, status: "failed", message: prCreate.message });
|
|
20344
|
+
continue;
|
|
20345
|
+
}
|
|
20346
|
+
prUrl = prCreate.message;
|
|
20347
|
+
}
|
|
20348
|
+
const merge = squashMergePullRequest(config2.projectRoot, branch);
|
|
20349
|
+
if (merge.success) {
|
|
20350
|
+
deleteLocalBranch(config2.projectRoot, branch);
|
|
20351
|
+
}
|
|
20352
|
+
results.push({ branch, prUrl, status: merge.success ? "merged" : "failed", message: merge.message });
|
|
20353
|
+
}
|
|
20354
|
+
return results;
|
|
20355
|
+
}
|
|
20356
|
+
async function createRelease(config2, branch, version, adapter2, cycleNum) {
|
|
19788
20357
|
if (!isGitAvailable()) {
|
|
19789
20358
|
throw new Error("git is not available.");
|
|
19790
20359
|
}
|
|
@@ -19795,10 +20364,13 @@ async function createRelease(config2, branch, version, adapter2) {
|
|
|
19795
20364
|
throw new Error("working directory has uncommitted changes. Commit or stash them before releasing.");
|
|
19796
20365
|
}
|
|
19797
20366
|
const warnings = [];
|
|
20367
|
+
const resolvedCycleNum = cycleNum && cycleNum > 0 ? cycleNum : (() => {
|
|
20368
|
+
const m = version.match(/^v0\.(\d+)\./);
|
|
20369
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
20370
|
+
})();
|
|
19798
20371
|
if (adapter2) {
|
|
19799
20372
|
try {
|
|
19800
|
-
const
|
|
19801
|
-
const currentCycle = versionMatch ? parseInt(versionMatch[1], 10) : 0;
|
|
20373
|
+
const currentCycle = resolvedCycleNum;
|
|
19802
20374
|
if (currentCycle > 0) {
|
|
19803
20375
|
await adapter2.createCycle({
|
|
19804
20376
|
id: `cycle-${currentCycle}`,
|
|
@@ -19827,6 +20399,24 @@ async function createRelease(config2, branch, version, adapter2) {
|
|
|
19827
20399
|
warnings.push(`git pull failed: ${pull.message}. Run manually.`);
|
|
19828
20400
|
}
|
|
19829
20401
|
}
|
|
20402
|
+
let groupedBranchMerges;
|
|
20403
|
+
if (resolvedCycleNum > 0) {
|
|
20404
|
+
const mergeResults = mergeGroupedCycleBranches(config2, resolvedCycleNum, branch);
|
|
20405
|
+
if (mergeResults.length > 0) {
|
|
20406
|
+
groupedBranchMerges = mergeResults;
|
|
20407
|
+
const failures = mergeResults.filter((r) => r.status === "failed");
|
|
20408
|
+
if (failures.length > 0) {
|
|
20409
|
+
const detail = failures.map((r) => `${r.branch}: ${r.message}`).join("; ");
|
|
20410
|
+
throw new Error(`Release blocked: grouped cycle branch merge failed \u2014 ${detail}. Resolve conflicts and retry release.`);
|
|
20411
|
+
}
|
|
20412
|
+
if (hasRemote(config2.projectRoot)) {
|
|
20413
|
+
const repull = gitPull(config2.projectRoot);
|
|
20414
|
+
if (!repull.success) {
|
|
20415
|
+
warnings.push(`Post-merge pull failed: ${repull.message}. Run manually.`);
|
|
20416
|
+
}
|
|
20417
|
+
}
|
|
20418
|
+
}
|
|
20419
|
+
}
|
|
19830
20420
|
if (tagExists(config2.projectRoot, version)) {
|
|
19831
20421
|
throw new Error(`tag "${version}" already exists. Use a different version.`);
|
|
19832
20422
|
}
|
|
@@ -19863,7 +20453,8 @@ async function createRelease(config2, branch, version, adapter2) {
|
|
|
19863
20453
|
commitNote,
|
|
19864
20454
|
tagMessage: tagResult.message,
|
|
19865
20455
|
pushNotes,
|
|
19866
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
20456
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
20457
|
+
...groupedBranchMerges ? { groupedBranchMerges } : {}
|
|
19867
20458
|
};
|
|
19868
20459
|
}
|
|
19869
20460
|
|
|
@@ -19919,7 +20510,9 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
19919
20510
|
return errorResponse(`version must start with "v" (got "${version}"). Example: "v0.1.0-alpha"`);
|
|
19920
20511
|
}
|
|
19921
20512
|
try {
|
|
19922
|
-
const
|
|
20513
|
+
const cycleMatch = version.match(/^v0\.(\d+)\./);
|
|
20514
|
+
const cycleNum = cycleMatch ? parseInt(cycleMatch[1], 10) : void 0;
|
|
20515
|
+
const result = await createRelease(config2, branch, version, adapter2, cycleNum);
|
|
19923
20516
|
const lines = [
|
|
19924
20517
|
`## Release ${result.version}`,
|
|
19925
20518
|
"",
|
|
@@ -19932,13 +20525,17 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
19932
20525
|
lines.push("", "---", "");
|
|
19933
20526
|
lines.push(...result.pushNotes);
|
|
19934
20527
|
}
|
|
20528
|
+
if (result.groupedBranchMerges?.length) {
|
|
20529
|
+
lines.push("", "**Shared cycle branches merged:**");
|
|
20530
|
+
for (const m of result.groupedBranchMerges) {
|
|
20531
|
+
lines.push(`- \`${m.branch}\` \u2014 ${m.message}${m.prUrl ? ` (${m.prUrl})` : ""}`);
|
|
20532
|
+
}
|
|
20533
|
+
}
|
|
19935
20534
|
if (result.warnings?.length) {
|
|
19936
20535
|
lines.push("", "\u26A0\uFE0F Warnings: " + result.warnings.join("; "));
|
|
19937
20536
|
}
|
|
19938
20537
|
try {
|
|
19939
|
-
|
|
19940
|
-
const cycleNum = cycleMatch ? parseInt(cycleMatch[1], 10) : 0;
|
|
19941
|
-
if (cycleNum > 0) {
|
|
20538
|
+
if (cycleNum && cycleNum > 0) {
|
|
19942
20539
|
const reports = await adapter2.getBuildReportsSince(cycleNum);
|
|
19943
20540
|
const EMPTY = /* @__PURE__ */ new Set(["None", "none", "N/A", "", "null"]);
|
|
19944
20541
|
const issues = reports.filter((r) => r.discoveredIssues && !EMPTY.has(r.discoveredIssues.trim())).map((r) => `- **${r.taskId}** (${r.taskName}): ${r.discoveredIssues}`);
|
|
@@ -19951,10 +20548,10 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
19951
20548
|
}
|
|
19952
20549
|
if (rawObservations && rawObservations.length > 0 && adapter2.writeDogfoodEntries) {
|
|
19953
20550
|
try {
|
|
19954
|
-
const
|
|
19955
|
-
const
|
|
20551
|
+
const cycleMatch2 = version.match(/^v0\.(\d+)\./);
|
|
20552
|
+
const cycleNum2 = cycleMatch2 ? parseInt(cycleMatch2[1], 10) : 0;
|
|
19956
20553
|
const entries = rawObservations.map((obs) => ({
|
|
19957
|
-
cycleNumber:
|
|
20554
|
+
cycleNumber: cycleNum2,
|
|
19958
20555
|
category: obs.category,
|
|
19959
20556
|
content: obs.content,
|
|
19960
20557
|
sourceTool: "release",
|
|
@@ -20210,6 +20807,10 @@ function mergeAfterAccept(config2, taskId) {
|
|
|
20210
20807
|
}
|
|
20211
20808
|
const featureBranch = taskBranchName(taskId);
|
|
20212
20809
|
const baseBranch = resolveBaseBranch(config2.projectRoot, config2.baseBranch);
|
|
20810
|
+
if (!branchExists(config2.projectRoot, featureBranch)) {
|
|
20811
|
+
lines.push(`Task is on a shared cycle branch \u2014 will be merged at release time.`);
|
|
20812
|
+
return lines;
|
|
20813
|
+
}
|
|
20213
20814
|
const papiDir = join7(config2.projectRoot, ".papi");
|
|
20214
20815
|
if (existsSync4(papiDir)) {
|
|
20215
20816
|
try {
|
|
@@ -20385,8 +20986,9 @@ ${result.handoffRegenPrompt.userMessage}
|
|
|
20385
20986
|
}
|
|
20386
20987
|
const version = `v0.${result.currentCycle}.0`;
|
|
20387
20988
|
const baseBranch = resolveBaseBranch(config2.projectRoot, config2.baseBranch);
|
|
20388
|
-
const releaseResult = await createRelease(config2, baseBranch, version, adapter2);
|
|
20989
|
+
const releaseResult = await createRelease(config2, baseBranch, version, adapter2, result.currentCycle);
|
|
20389
20990
|
const pushInfo = releaseResult.pushNotes.join(" ");
|
|
20991
|
+
const groupedMergeNote = releaseResult.groupedBranchMerges?.length ? "\n" + releaseResult.groupedBranchMerges.map((r) => `- Merged shared branch \`${r.branch}\` via PR: ${r.prUrl ?? "n/a"}`).join("\n") : "";
|
|
20390
20992
|
autoReleaseNote = `
|
|
20391
20993
|
|
|
20392
20994
|
---
|
|
@@ -20396,7 +20998,7 @@ ${result.handoffRegenPrompt.userMessage}
|
|
|
20396
20998
|
- Version: **${releaseResult.version}**
|
|
20397
20999
|
- ${releaseResult.commitNote}
|
|
20398
21000
|
- ${releaseResult.tagMessage}
|
|
20399
|
-
- ${pushInfo}` + (releaseResult.warnings?.length ? `
|
|
21001
|
+
- ${pushInfo}` + groupedMergeNote + (releaseResult.warnings?.length ? `
|
|
20400
21002
|
- Warnings: ${releaseResult.warnings.join(", ")}` : "") + `
|
|
20401
21003
|
|
|
20402
21004
|
Run \`plan\` to create Cycle ${result.currentCycle + 1}.`;
|
|
@@ -20643,7 +21245,7 @@ function countByStatus(tasks) {
|
|
|
20643
21245
|
async function getHealthSummary(adapter2) {
|
|
20644
21246
|
const health = await adapter2.getCycleHealth();
|
|
20645
21247
|
const activeTasks = await adapter2.queryBoard({
|
|
20646
|
-
status: ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Blocked"]
|
|
21248
|
+
status: ["Backlog", "In Cycle", "Ready", "In Progress", "In Review", "Blocked", "Deferred"]
|
|
20647
21249
|
});
|
|
20648
21250
|
const logEntries = await adapter2.getCycleLog(3);
|
|
20649
21251
|
const cycleNumber = health.totalCycles;
|
|
@@ -21044,21 +21646,94 @@ async function handleDocScan(adapter2, config2, args) {
|
|
|
21044
21646
|
return textResponse(lines.join("\n"));
|
|
21045
21647
|
}
|
|
21046
21648
|
|
|
21649
|
+
// src/services/session-guidance.ts
|
|
21650
|
+
import { existsSync as existsSync6 } from "fs";
|
|
21651
|
+
import { join as join9 } from "path";
|
|
21652
|
+
var state = {
|
|
21653
|
+
toolCallCount: 0,
|
|
21654
|
+
lastOrientAt: null,
|
|
21655
|
+
releaseSinceLastOrient: false,
|
|
21656
|
+
sessionStartedAt: Date.now()
|
|
21657
|
+
};
|
|
21658
|
+
var CONTEXT_BLOAT_CALL_THRESHOLD = 40;
|
|
21659
|
+
var ORIENT_GAP_MS = 3 * 60 * 60 * 1e3;
|
|
21660
|
+
function recordToolCall(name) {
|
|
21661
|
+
state.toolCallCount++;
|
|
21662
|
+
if (name === "release") state.releaseSinceLastOrient = true;
|
|
21663
|
+
}
|
|
21664
|
+
function markOrient() {
|
|
21665
|
+
state.lastOrientAt = Date.now();
|
|
21666
|
+
state.releaseSinceLastOrient = false;
|
|
21667
|
+
}
|
|
21668
|
+
async function buildSessionGuidance(adapter2, projectRoot) {
|
|
21669
|
+
const signals = [];
|
|
21670
|
+
try {
|
|
21671
|
+
if (adapter2.searchDocs) {
|
|
21672
|
+
const researchDir = join9(projectRoot, "docs", "research");
|
|
21673
|
+
if (existsSync6(researchDir)) {
|
|
21674
|
+
const files = scanMdFiles(researchDir, projectRoot);
|
|
21675
|
+
if (files.length > 0) {
|
|
21676
|
+
const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
|
|
21677
|
+
const registeredPaths = new Set(registered.map((d) => d.path));
|
|
21678
|
+
const unregistered = files.filter((f) => !registeredPaths.has(f));
|
|
21679
|
+
if (unregistered.length > 0) {
|
|
21680
|
+
signals.push(
|
|
21681
|
+
`${unregistered.length} research doc(s) in docs/research/ not registered \u2014 run \`doc_register\` so the planner can surface them.`
|
|
21682
|
+
);
|
|
21683
|
+
}
|
|
21684
|
+
}
|
|
21685
|
+
}
|
|
21686
|
+
}
|
|
21687
|
+
} catch {
|
|
21688
|
+
}
|
|
21689
|
+
if (state.toolCallCount > CONTEXT_BLOAT_CALL_THRESHOLD) {
|
|
21690
|
+
signals.push(
|
|
21691
|
+
`${state.toolCallCount} tool calls this session \u2014 context may be bloated. Consider starting a fresh window.`
|
|
21692
|
+
);
|
|
21693
|
+
}
|
|
21694
|
+
if (state.lastOrientAt && Date.now() - state.lastOrientAt > ORIENT_GAP_MS) {
|
|
21695
|
+
const hours = Math.round((Date.now() - state.lastOrientAt) / (60 * 60 * 1e3));
|
|
21696
|
+
signals.push(
|
|
21697
|
+
`${hours}h since last orient \u2014 session may be stale. Consider a fresh window for best results.`
|
|
21698
|
+
);
|
|
21699
|
+
}
|
|
21700
|
+
if (state.releaseSinceLastOrient) {
|
|
21701
|
+
signals.push(
|
|
21702
|
+
"Release just ran \u2014 start a fresh session before the next `plan` to keep planning context clean."
|
|
21703
|
+
);
|
|
21704
|
+
}
|
|
21705
|
+
return signals.slice(0, 3);
|
|
21706
|
+
}
|
|
21707
|
+
|
|
21047
21708
|
// src/tools/orient.ts
|
|
21048
21709
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
21049
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as
|
|
21050
|
-
import { join as
|
|
21710
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync7 } from "fs";
|
|
21711
|
+
import { join as join10 } from "path";
|
|
21712
|
+
var GIT_DEPENDENT_ENVS = /* @__PURE__ */ new Set(["cowork", "api"]);
|
|
21713
|
+
var VALID_ENVS = /* @__PURE__ */ new Set(["claude-code", "cowork", "api", "unknown"]);
|
|
21714
|
+
function normaliseEnvironment(raw) {
|
|
21715
|
+
if (typeof raw === "string" && VALID_ENVS.has(raw)) {
|
|
21716
|
+
return raw;
|
|
21717
|
+
}
|
|
21718
|
+
return "unknown";
|
|
21719
|
+
}
|
|
21051
21720
|
var orientTool = {
|
|
21052
21721
|
name: "orient",
|
|
21053
|
-
description: "Session orientation \u2014 run this FIRST at session start before any other tool. Single call that replaces build_list + health. Returns: cycle number, task counts by status, in-progress/in-review tasks, strategy review cadence, velocity snapshot, recommended next action, and a release reminder when all cycle tasks are Done but release has not run. Read-only, does not modify any files.",
|
|
21722
|
+
description: "Session orientation \u2014 run this FIRST at session start before any other tool. Single call that replaces build_list + health. Returns: cycle number, task counts by status, in-progress/in-review tasks, strategy review cadence, velocity snapshot, recommended next action, and a release reminder when all cycle tasks are Done but release has not run. Read-only, does not modify any files. Pass `environment` to qualify git-dependent recommendations (build_execute, release, review_submit) for non-Claude-Code callers.",
|
|
21054
21723
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
21055
21724
|
inputSchema: {
|
|
21056
21725
|
type: "object",
|
|
21057
|
-
properties: {
|
|
21726
|
+
properties: {
|
|
21727
|
+
environment: {
|
|
21728
|
+
type: "string",
|
|
21729
|
+
enum: ["claude-code", "cowork", "api", "unknown"],
|
|
21730
|
+
description: 'Caller environment. "claude-code" (local CLI with git) sees all recommendations unchanged. "cowork" or "api" (hosted/remote, no local git) suppresses/qualifies build_execute, release, and review_submit recommendations because those require a local Claude Code session to execute. Default "unknown" = show everything (safe default).'
|
|
21731
|
+
}
|
|
21732
|
+
},
|
|
21058
21733
|
required: []
|
|
21059
21734
|
}
|
|
21060
21735
|
};
|
|
21061
|
-
function formatOrientSummary(health, buildInfo, hierarchy, latestTag, projectRoot) {
|
|
21736
|
+
function formatOrientSummary(health, buildInfo, hierarchy, latestTag, projectRoot, environment = "unknown") {
|
|
21062
21737
|
const lines = [];
|
|
21063
21738
|
const cycleIsComplete = health.latestCycleStatus === "complete";
|
|
21064
21739
|
const tagSuffix = latestTag ? ` \u2014 ${latestTag}` : "";
|
|
@@ -21076,7 +21751,13 @@ function formatOrientSummary(health, buildInfo, hierarchy, latestTag, projectRoo
|
|
|
21076
21751
|
}
|
|
21077
21752
|
lines.push("");
|
|
21078
21753
|
}
|
|
21079
|
-
|
|
21754
|
+
const isGitDependentRec = /\*\*(Build|Review)\*\*/.test(health.recommendedMode);
|
|
21755
|
+
if (GIT_DEPENDENT_ENVS.has(environment) && isGitDependentRec) {
|
|
21756
|
+
lines.push(`> **Next action:** ${health.recommendedMode}`);
|
|
21757
|
+
lines.push(`> _Note: \`build_execute\`, \`review_submit\`, and \`release\` require a local Claude Code session with git access. From \`${environment}\` you can view state, log ideas, and edit the board \u2014 but the build/review/release loop must run through Claude Code._`);
|
|
21758
|
+
} else {
|
|
21759
|
+
lines.push(`> **Next action:** ${health.recommendedMode}`);
|
|
21760
|
+
}
|
|
21080
21761
|
lines.push("");
|
|
21081
21762
|
lines.push(`**Strategy Review:** ${health.reviewWarning}`);
|
|
21082
21763
|
lines.push("");
|
|
@@ -21227,7 +21908,7 @@ function getLatestGitTag(projectRoot) {
|
|
|
21227
21908
|
}
|
|
21228
21909
|
function checkNpmVersionDrift() {
|
|
21229
21910
|
try {
|
|
21230
|
-
const pkgPath =
|
|
21911
|
+
const pkgPath = join10(new URL(".", import.meta.url).pathname, "..", "..", "package.json");
|
|
21231
21912
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
21232
21913
|
const localVersion = pkg.version;
|
|
21233
21914
|
const packageName = pkg.name;
|
|
@@ -21244,7 +21925,8 @@ function checkNpmVersionDrift() {
|
|
|
21244
21925
|
return null;
|
|
21245
21926
|
}
|
|
21246
21927
|
}
|
|
21247
|
-
async function handleOrient(adapter2, config2) {
|
|
21928
|
+
async function handleOrient(adapter2, config2, args = {}) {
|
|
21929
|
+
const environment = normaliseEnvironment(args.environment);
|
|
21248
21930
|
try {
|
|
21249
21931
|
try {
|
|
21250
21932
|
await propagatePhaseStatus(adapter2);
|
|
@@ -21353,7 +22035,7 @@ ${versionDrift}` : "";
|
|
|
21353
22035
|
let unregisteredDocsNote = "";
|
|
21354
22036
|
try {
|
|
21355
22037
|
if (adapter2.searchDocs) {
|
|
21356
|
-
const docsDir =
|
|
22038
|
+
const docsDir = join10(config2.projectRoot, "docs");
|
|
21357
22039
|
const docsFiles = scanMdFiles(docsDir, config2.projectRoot);
|
|
21358
22040
|
if (docsFiles.length > 0) {
|
|
21359
22041
|
const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
|
|
@@ -21443,13 +22125,35 @@ ${versionDrift}` : "";
|
|
|
21443
22125
|
}
|
|
21444
22126
|
} catch {
|
|
21445
22127
|
}
|
|
22128
|
+
let alertsNote = "";
|
|
21446
22129
|
let unactionedIssuesNote = "";
|
|
21447
22130
|
try {
|
|
21448
|
-
const learnings = await adapter2.getCycleLearnings?.({ category: "issue", limit:
|
|
22131
|
+
const learnings = await adapter2.getCycleLearnings?.({ category: "issue", limit: 30 });
|
|
21449
22132
|
if (learnings) {
|
|
21450
|
-
const
|
|
21451
|
-
|
|
21452
|
-
|
|
22133
|
+
const byRecency = (a, b2) => (b2.createdAt ?? "").localeCompare(a.createdAt ?? "");
|
|
22134
|
+
const unactionedAll = learnings.filter((l) => !l.actionTaken).map((l) => ({ ...l, severity: l.severity ?? "P3" }));
|
|
22135
|
+
const allAlerts = unactionedAll.filter((l) => l.severity === "P0" || l.severity === "P1").sort(byRecency);
|
|
22136
|
+
const allLowSev = unactionedAll.filter((l) => l.severity === "P2" || l.severity === "P3").sort(byRecency);
|
|
22137
|
+
const totalP2 = allLowSev.filter((l) => l.severity === "P2").length;
|
|
22138
|
+
const totalP3 = allLowSev.filter((l) => l.severity === "P3").length;
|
|
22139
|
+
const ALERT_CAP = 10;
|
|
22140
|
+
const UNACTIONED_CAP = 5;
|
|
22141
|
+
const alerts = allAlerts.slice(0, ALERT_CAP);
|
|
22142
|
+
const unactioned = allLowSev.slice(0, UNACTIONED_CAP);
|
|
22143
|
+
if (allAlerts.length > 0) {
|
|
22144
|
+
const header = allAlerts.length > ALERT_CAP ? `${allAlerts.length} P0/P1 discovered issues awaiting action (showing ${ALERT_CAP} most recent).` : `${allAlerts.length} P0/P1 discovered issue${allAlerts.length !== 1 ? "s" : ""} awaiting action.`;
|
|
22145
|
+
const lines = ["\n\n## \u{1F6A8} Alerts", header];
|
|
22146
|
+
for (const issue of alerts) {
|
|
22147
|
+
const desc = issue.summary.length > 100 ? `${issue.summary.slice(0, 97)}\u2026` : issue.summary;
|
|
22148
|
+
lines.push(`- **${issue.severity}** (C${issue.cycleNumber} / ${issue.taskId}): ${desc}`);
|
|
22149
|
+
}
|
|
22150
|
+
lines.push("_Escalate: run `idea` with P1 priority, or `board_edit` if already handled._");
|
|
22151
|
+
alertsNote = lines.join("\n");
|
|
22152
|
+
}
|
|
22153
|
+
if (allLowSev.length > 0) {
|
|
22154
|
+
const totalLow = totalP2 + totalP3;
|
|
22155
|
+
const header = totalLow > UNACTIONED_CAP ? `${totalP2} P2 \xB7 ${totalP3} P3 (showing ${UNACTIONED_CAP} most recent)` : `${totalP2} P2 \xB7 ${totalP3} P3`;
|
|
22156
|
+
const lines = ["\n\n## Unactioned Issues", header];
|
|
21453
22157
|
for (const issue of unactioned) {
|
|
21454
22158
|
const desc = issue.summary.length > 100 ? `${issue.summary.slice(0, 97)}\u2026` : issue.summary;
|
|
21455
22159
|
lines.push(`- **${issue.severity}** (C${issue.cycleNumber} / ${issue.taskId}): ${desc}`);
|
|
@@ -21460,15 +22164,26 @@ ${versionDrift}` : "";
|
|
|
21460
22164
|
}
|
|
21461
22165
|
} catch {
|
|
21462
22166
|
}
|
|
21463
|
-
|
|
22167
|
+
let sessionGuidanceNote = "";
|
|
22168
|
+
try {
|
|
22169
|
+
const signals = await buildSessionGuidance(adapter2, config2.projectRoot);
|
|
22170
|
+
if (signals.length > 0) {
|
|
22171
|
+
const lines = ["\n\n## Session Guidance"];
|
|
22172
|
+
for (const s of signals) lines.push(`- ${s}`);
|
|
22173
|
+
sessionGuidanceNote = lines.join("\n");
|
|
22174
|
+
}
|
|
22175
|
+
markOrient();
|
|
22176
|
+
} catch {
|
|
22177
|
+
}
|
|
22178
|
+
return textResponse(formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment) + alertsNote + ttfvNote + reconciliationNote + unrecordedNote + unregisteredDocsNote + researchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + sessionGuidanceNote + versionNote + enrichmentNote);
|
|
21464
22179
|
} catch (err) {
|
|
21465
22180
|
const message = err instanceof Error ? err.message : String(err);
|
|
21466
22181
|
return errorResponse(`Orient failed: ${message}`);
|
|
21467
22182
|
}
|
|
21468
22183
|
}
|
|
21469
22184
|
function enrichClaudeMd(projectRoot, cycleNumber) {
|
|
21470
|
-
const claudeMdPath =
|
|
21471
|
-
if (!
|
|
22185
|
+
const claudeMdPath = join10(projectRoot, "CLAUDE.md");
|
|
22186
|
+
if (!existsSync7(claudeMdPath)) return "";
|
|
21472
22187
|
const content = readFileSync3(claudeMdPath, "utf-8");
|
|
21473
22188
|
const additions = [];
|
|
21474
22189
|
if (cycleNumber >= 6 && !content.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1)) {
|
|
@@ -22250,6 +22965,47 @@ ${result.userMessage}
|
|
|
22250
22965
|
}
|
|
22251
22966
|
}
|
|
22252
22967
|
|
|
22968
|
+
// src/lib/install-id.ts
|
|
22969
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
22970
|
+
import { mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2, chmodSync } from "fs";
|
|
22971
|
+
import { homedir as homedir3 } from "os";
|
|
22972
|
+
import { join as join11 } from "path";
|
|
22973
|
+
var PAPI_HOME_DIR = join11(homedir3(), ".papi");
|
|
22974
|
+
var INSTALL_ID_FILE = join11(PAPI_HOME_DIR, "install-id.json");
|
|
22975
|
+
var cachedInstallId = null;
|
|
22976
|
+
function isValidUuid(s) {
|
|
22977
|
+
return typeof s === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
|
|
22978
|
+
}
|
|
22979
|
+
function getInstallId() {
|
|
22980
|
+
if (cachedInstallId) return cachedInstallId;
|
|
22981
|
+
try {
|
|
22982
|
+
const raw = readFileSync4(INSTALL_ID_FILE, "utf-8");
|
|
22983
|
+
const parsed = JSON.parse(raw);
|
|
22984
|
+
if (isValidUuid(parsed.install_id)) {
|
|
22985
|
+
cachedInstallId = parsed.install_id;
|
|
22986
|
+
return cachedInstallId;
|
|
22987
|
+
}
|
|
22988
|
+
} catch {
|
|
22989
|
+
}
|
|
22990
|
+
try {
|
|
22991
|
+
mkdirSync(PAPI_HOME_DIR, { recursive: true, mode: 448 });
|
|
22992
|
+
const id = randomUUID15();
|
|
22993
|
+
const contents = {
|
|
22994
|
+
install_id: id,
|
|
22995
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
22996
|
+
};
|
|
22997
|
+
writeFileSync2(INSTALL_ID_FILE, JSON.stringify(contents, null, 2), { mode: 384 });
|
|
22998
|
+
try {
|
|
22999
|
+
chmodSync(INSTALL_ID_FILE, 384);
|
|
23000
|
+
} catch {
|
|
23001
|
+
}
|
|
23002
|
+
cachedInstallId = id;
|
|
23003
|
+
return cachedInstallId;
|
|
23004
|
+
} catch {
|
|
23005
|
+
return null;
|
|
23006
|
+
}
|
|
23007
|
+
}
|
|
23008
|
+
|
|
22253
23009
|
// src/lib/telemetry.ts
|
|
22254
23010
|
var TELEMETRY_SUPABASE_URL = "https://guewgygcpcmrcoppihzx.supabase.co";
|
|
22255
23011
|
var TELEMETRY_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imd1ZXdneWdjcGNtcmNvcHBpaHp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI2Njk2NTMsImV4cCI6MjA4ODI0NTY1M30.V5Jw7wJgiMpSQPa2mt0ftjyye5ynG1qLlam00yPVNJY";
|
|
@@ -22288,6 +23044,32 @@ function emitToolCall(projectId, toolName, durationMs, extra) {
|
|
|
22288
23044
|
metadata: { duration_ms: durationMs, ...extra }
|
|
22289
23045
|
});
|
|
22290
23046
|
}
|
|
23047
|
+
function emitMdAdapterPing(toolName, extra, userId, projectSlug) {
|
|
23048
|
+
if (!isEnabled()) return;
|
|
23049
|
+
const installId = getInstallId();
|
|
23050
|
+
if (!installId) return;
|
|
23051
|
+
const resolvedUserId = userId ?? process.env["PAPI_USER_ID"] ?? void 0;
|
|
23052
|
+
const body = {
|
|
23053
|
+
install_id: installId,
|
|
23054
|
+
tool_name: toolName,
|
|
23055
|
+
papi_version: process.env["npm_package_version"] ?? null,
|
|
23056
|
+
metadata: extra ?? {}
|
|
23057
|
+
};
|
|
23058
|
+
if (resolvedUserId) body["user_id"] = resolvedUserId;
|
|
23059
|
+
if (projectSlug) body["project_slug"] = projectSlug;
|
|
23060
|
+
fetch(`${TELEMETRY_SUPABASE_URL}/rest/v1/md_adapter_pings`, {
|
|
23061
|
+
method: "POST",
|
|
23062
|
+
headers: {
|
|
23063
|
+
"Content-Type": "application/json",
|
|
23064
|
+
"apikey": TELEMETRY_API_KEY,
|
|
23065
|
+
"Authorization": `Bearer ${TELEMETRY_API_KEY}`,
|
|
23066
|
+
"Prefer": "return=minimal"
|
|
23067
|
+
},
|
|
23068
|
+
body: JSON.stringify(body),
|
|
23069
|
+
signal: AbortSignal.timeout(5e3)
|
|
23070
|
+
}).catch(() => {
|
|
23071
|
+
});
|
|
23072
|
+
}
|
|
22291
23073
|
function emitMilestone(projectId, milestone, extra) {
|
|
22292
23074
|
emitTelemetryEvent({
|
|
22293
23075
|
project_id: projectId,
|
|
@@ -22302,6 +23084,7 @@ var TOOLS_REQUIRING_PAPI = /* @__PURE__ */ new Set([
|
|
|
22302
23084
|
"plan",
|
|
22303
23085
|
"strategy_review",
|
|
22304
23086
|
"strategy_change",
|
|
23087
|
+
"strategy_agenda",
|
|
22305
23088
|
"board_view",
|
|
22306
23089
|
"board_deprioritise",
|
|
22307
23090
|
"board_archive",
|
|
@@ -22322,13 +23105,26 @@ var TOOLS_REQUIRING_PAPI = /* @__PURE__ */ new Set([
|
|
|
22322
23105
|
"handoff_generate"
|
|
22323
23106
|
]);
|
|
22324
23107
|
function createServer(adapter2, config2) {
|
|
23108
|
+
const __pkgFilename = fileURLToPath(import.meta.url);
|
|
23109
|
+
const __pkgDir = dirname(__pkgFilename);
|
|
23110
|
+
let serverVersion = "unknown";
|
|
23111
|
+
try {
|
|
23112
|
+
const pkg = JSON.parse(readFileSync5(join12(__pkgDir, "..", "package.json"), "utf-8"));
|
|
23113
|
+
serverVersion = pkg.version ?? "unknown";
|
|
23114
|
+
} catch {
|
|
23115
|
+
}
|
|
22325
23116
|
const server2 = new Server(
|
|
22326
|
-
{ name: "papi", version:
|
|
23117
|
+
{ name: "papi", version: serverVersion },
|
|
22327
23118
|
{ capabilities: { tools: {}, prompts: {} } }
|
|
22328
23119
|
);
|
|
23120
|
+
if (config2.adapterType === "md") {
|
|
23121
|
+
process.stderr.write(
|
|
23122
|
+
"\n\u26A0 PAPI is running in md mode \u2014 your cycles are not visible on the hosted dashboard.\n Configure DATABASE_URL or sign up at https://getpapi.ai/setup to enable observability.\n\n"
|
|
23123
|
+
);
|
|
23124
|
+
}
|
|
22329
23125
|
const __filename = fileURLToPath(import.meta.url);
|
|
22330
23126
|
const __dirname2 = dirname(__filename);
|
|
22331
|
-
const skillsDir =
|
|
23127
|
+
const skillsDir = join12(__dirname2, "..", "skills");
|
|
22332
23128
|
function parseSkillFrontmatter(content) {
|
|
22333
23129
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
22334
23130
|
if (!match) return null;
|
|
@@ -22346,7 +23142,7 @@ function createServer(adapter2, config2) {
|
|
|
22346
23142
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
22347
23143
|
const prompts = [];
|
|
22348
23144
|
for (const file of mdFiles) {
|
|
22349
|
-
const content = await readFile5(
|
|
23145
|
+
const content = await readFile5(join12(skillsDir, file), "utf-8");
|
|
22350
23146
|
const meta = parseSkillFrontmatter(content);
|
|
22351
23147
|
if (meta) {
|
|
22352
23148
|
prompts.push({ name: meta.name, description: meta.description });
|
|
@@ -22362,7 +23158,7 @@ function createServer(adapter2, config2) {
|
|
|
22362
23158
|
try {
|
|
22363
23159
|
const files = await readdir2(skillsDir);
|
|
22364
23160
|
for (const file of files.filter((f) => f.endsWith(".md"))) {
|
|
22365
|
-
const content = await readFile5(
|
|
23161
|
+
const content = await readFile5(join12(skillsDir, file), "utf-8");
|
|
22366
23162
|
const meta = parseSkillFrontmatter(content);
|
|
22367
23163
|
if (meta?.name === name) {
|
|
22368
23164
|
const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "");
|
|
@@ -22381,6 +23177,7 @@ function createServer(adapter2, config2) {
|
|
|
22381
23177
|
planTool,
|
|
22382
23178
|
strategyReviewTool,
|
|
22383
23179
|
strategyChangeTool,
|
|
23180
|
+
strategyAgendaTool,
|
|
22384
23181
|
boardViewTool,
|
|
22385
23182
|
boardDeprioritiseTool,
|
|
22386
23183
|
boardArchiveTool,
|
|
@@ -22435,6 +23232,7 @@ function createServer(adapter2, config2) {
|
|
|
22435
23232
|
}
|
|
22436
23233
|
}
|
|
22437
23234
|
const timer2 = startTimer();
|
|
23235
|
+
recordToolCall(name);
|
|
22438
23236
|
let result;
|
|
22439
23237
|
switch (name) {
|
|
22440
23238
|
case "plan":
|
|
@@ -22446,6 +23244,9 @@ function createServer(adapter2, config2) {
|
|
|
22446
23244
|
case "strategy_change":
|
|
22447
23245
|
result = await handleStrategyChange(adapter2, config2, safeArgs);
|
|
22448
23246
|
break;
|
|
23247
|
+
case "strategy_agenda":
|
|
23248
|
+
result = await handleStrategyAgenda(adapter2, config2, safeArgs);
|
|
23249
|
+
break;
|
|
22449
23250
|
case "board_view":
|
|
22450
23251
|
result = await handleBoardView(adapter2, safeArgs);
|
|
22451
23252
|
break;
|
|
@@ -22498,7 +23299,7 @@ function createServer(adapter2, config2) {
|
|
|
22498
23299
|
result = await handleInit(config2, safeArgs);
|
|
22499
23300
|
break;
|
|
22500
23301
|
case "orient":
|
|
22501
|
-
result = await handleOrient(adapter2, config2);
|
|
23302
|
+
result = await handleOrient(adapter2, config2, safeArgs);
|
|
22502
23303
|
break;
|
|
22503
23304
|
case "hierarchy_update":
|
|
22504
23305
|
result = await handleHierarchyUpdate(adapter2, safeArgs);
|
|
@@ -22539,6 +23340,10 @@ function createServer(adapter2, config2) {
|
|
|
22539
23340
|
});
|
|
22540
23341
|
} catch {
|
|
22541
23342
|
}
|
|
23343
|
+
if (config2.adapterType === "md") {
|
|
23344
|
+
const mdProjectSlug = config2.projectRoot ? config2.projectRoot.split("/").pop() : void 0;
|
|
23345
|
+
emitMdAdapterPing(name, { duration_ms: elapsed, success: !isError }, config2.userId, mdProjectSlug);
|
|
23346
|
+
}
|
|
22542
23347
|
const telemetryProjectId = process.env["PAPI_PROJECT_ID"];
|
|
22543
23348
|
if (telemetryProjectId) {
|
|
22544
23349
|
emitToolCall(telemetryProjectId, name, elapsed, {
|
|
@@ -22566,7 +23371,7 @@ function createServer(adapter2, config2) {
|
|
|
22566
23371
|
var __dirname = dirname2(fileURLToPath2(import.meta.url));
|
|
22567
23372
|
var pkgVersion = "unknown";
|
|
22568
23373
|
try {
|
|
22569
|
-
const pkg = JSON.parse(
|
|
23374
|
+
const pkg = JSON.parse(readFileSync6(join13(__dirname, "..", "package.json"), "utf-8"));
|
|
22570
23375
|
pkgVersion = pkg.version;
|
|
22571
23376
|
} catch {
|
|
22572
23377
|
}
|