yapout 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +436 -179
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -947,7 +947,7 @@ Read the following to build your understanding:
|
|
|
947
947
|
- Database schema or type definitions
|
|
948
948
|
- A few representative source files
|
|
949
949
|
|
|
950
|
-
Output a single markdown document. Be concise \u2014 this will be given to other Claude Code sessions as context for implementing
|
|
950
|
+
Output a single markdown document. Be concise \u2014 this will be given to other Claude Code sessions as context for implementing findings.
|
|
951
951
|
|
|
952
952
|
After you produce the summary, call yapout_update_context with the summary text to save it.`;
|
|
953
953
|
function registerCompactTool(server, ctx) {
|
|
@@ -1037,9 +1037,9 @@ import { z as z3 } from "zod";
|
|
|
1037
1037
|
function registerQueueTool(server, ctx) {
|
|
1038
1038
|
server.tool(
|
|
1039
1039
|
"yapout_queue",
|
|
1040
|
-
"List
|
|
1040
|
+
"List findings ready for local implementation. Only returns findings in backlog/unstarted Linear status.",
|
|
1041
1041
|
{
|
|
1042
|
-
includeBlocked: z3.boolean().optional().describe("Show blocked
|
|
1042
|
+
includeBlocked: z3.boolean().optional().describe("Show blocked findings too (default: false)")
|
|
1043
1043
|
},
|
|
1044
1044
|
async (args) => {
|
|
1045
1045
|
if (!ctx.projectId) {
|
|
@@ -1054,7 +1054,7 @@ function registerQueueTool(server, ctx) {
|
|
|
1054
1054
|
};
|
|
1055
1055
|
}
|
|
1056
1056
|
const data = await ctx.client.query(
|
|
1057
|
-
anyApi2.functions.
|
|
1057
|
+
anyApi2.functions.findings.getQueuedWorkItems,
|
|
1058
1058
|
{ projectId: ctx.projectId }
|
|
1059
1059
|
);
|
|
1060
1060
|
if (!data) {
|
|
@@ -1067,7 +1067,7 @@ function registerQueueTool(server, ctx) {
|
|
|
1067
1067
|
}
|
|
1068
1068
|
let ready = data.ready;
|
|
1069
1069
|
ready = ready.filter((t) => t.nature !== "operational");
|
|
1070
|
-
const linearIds = ready.map((t) => t.
|
|
1070
|
+
const linearIds = ready.map((t) => t.linearIssueId).filter((id) => !!id);
|
|
1071
1071
|
if (linearIds.length > 0) {
|
|
1072
1072
|
try {
|
|
1073
1073
|
const statuses = await ctx.client.action(
|
|
@@ -1078,8 +1078,8 @@ function registerQueueTool(server, ctx) {
|
|
|
1078
1078
|
statuses.map((s) => [s.linearIssueId, s.statusType])
|
|
1079
1079
|
);
|
|
1080
1080
|
ready = ready.filter((t) => {
|
|
1081
|
-
if (!t.
|
|
1082
|
-
const type = statusMap.get(t.
|
|
1081
|
+
if (!t.linearIssueId) return true;
|
|
1082
|
+
const type = statusMap.get(t.linearIssueId);
|
|
1083
1083
|
if (!type) return true;
|
|
1084
1084
|
return type === "backlog" || type === "unstarted";
|
|
1085
1085
|
});
|
|
@@ -1114,21 +1114,21 @@ import { z as z4 } from "zod";
|
|
|
1114
1114
|
function registerGetBriefTool(server, ctx) {
|
|
1115
1115
|
server.tool(
|
|
1116
1116
|
"yapout_get_brief",
|
|
1117
|
-
"Fetch the full implementation context for a
|
|
1117
|
+
"Fetch the full implementation context for a finding",
|
|
1118
1118
|
{
|
|
1119
|
-
|
|
1119
|
+
findingId: z4.string().describe("The finding ID to get the brief for")
|
|
1120
1120
|
},
|
|
1121
1121
|
async (args) => {
|
|
1122
1122
|
const data = await ctx.client.query(
|
|
1123
|
-
anyApi2.functions.
|
|
1124
|
-
{
|
|
1123
|
+
anyApi2.functions.findings.getFindingBrief,
|
|
1124
|
+
{ findingId: args.findingId }
|
|
1125
1125
|
);
|
|
1126
1126
|
if (!data) {
|
|
1127
1127
|
return {
|
|
1128
1128
|
content: [
|
|
1129
1129
|
{
|
|
1130
1130
|
type: "text",
|
|
1131
|
-
text: "
|
|
1131
|
+
text: "Finding not found or you don't have access."
|
|
1132
1132
|
}
|
|
1133
1133
|
],
|
|
1134
1134
|
isError: true
|
|
@@ -1159,16 +1159,16 @@ function slugify(text) {
|
|
|
1159
1159
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
1160
1160
|
}
|
|
1161
1161
|
function formatBrief(data) {
|
|
1162
|
-
const
|
|
1162
|
+
const finding = data.finding;
|
|
1163
1163
|
const sections = [
|
|
1164
|
-
`# ${
|
|
1164
|
+
`# ${finding.title}`,
|
|
1165
1165
|
"",
|
|
1166
|
-
`**Priority:** ${
|
|
1166
|
+
`**Priority:** ${finding.priority} | **Type:** ${finding.type}`
|
|
1167
1167
|
];
|
|
1168
|
-
if (
|
|
1169
|
-
sections.push(`**Linear:** ${
|
|
1168
|
+
if (finding.linearIssueUrl) {
|
|
1169
|
+
sections.push(`**Linear:** ${finding.linearIssueUrl}`);
|
|
1170
1170
|
}
|
|
1171
|
-
sections.push("", "## Description", "",
|
|
1171
|
+
sections.push("", "## Description", "", finding.description);
|
|
1172
1172
|
if (data.enrichedDescription) {
|
|
1173
1173
|
sections.push("", "## Enriched Description", "", data.enrichedDescription);
|
|
1174
1174
|
}
|
|
@@ -1202,9 +1202,9 @@ function formatBrief(data) {
|
|
|
1202
1202
|
function registerClaimTool(server, ctx) {
|
|
1203
1203
|
server.tool(
|
|
1204
1204
|
"yapout_claim",
|
|
1205
|
-
"Claim a
|
|
1205
|
+
"Claim a finding for local implementation. Creates a branch (or worktree), writes the brief, and updates status.",
|
|
1206
1206
|
{
|
|
1207
|
-
|
|
1207
|
+
findingId: z5.string().describe("The finding ID to claim"),
|
|
1208
1208
|
worktree: z5.boolean().optional().describe("Create a git worktree for parallel work (default: false)")
|
|
1209
1209
|
},
|
|
1210
1210
|
async (args) => {
|
|
@@ -1220,37 +1220,37 @@ function registerClaimTool(server, ctx) {
|
|
|
1220
1220
|
};
|
|
1221
1221
|
}
|
|
1222
1222
|
const briefData = await ctx.client.query(
|
|
1223
|
-
anyApi2.functions.
|
|
1224
|
-
{
|
|
1223
|
+
anyApi2.functions.findings.getFindingBrief,
|
|
1224
|
+
{ findingId: args.findingId }
|
|
1225
1225
|
);
|
|
1226
1226
|
if (!briefData) {
|
|
1227
1227
|
return {
|
|
1228
1228
|
content: [
|
|
1229
1229
|
{
|
|
1230
1230
|
type: "text",
|
|
1231
|
-
text: "
|
|
1231
|
+
text: "Finding not found or you don't have access."
|
|
1232
1232
|
}
|
|
1233
1233
|
],
|
|
1234
1234
|
isError: true
|
|
1235
1235
|
};
|
|
1236
1236
|
}
|
|
1237
|
-
const
|
|
1238
|
-
const
|
|
1237
|
+
const finding = briefData.finding;
|
|
1238
|
+
const linearIssueId = briefData.linearIssueId;
|
|
1239
1239
|
const defaultBranch = briefData.defaultBranch || "main";
|
|
1240
1240
|
const prefix = readBranchPrefix(ctx.cwd);
|
|
1241
|
-
const slug = slugify(
|
|
1242
|
-
const branchName =
|
|
1241
|
+
const slug = slugify(finding.title);
|
|
1242
|
+
const branchName = linearIssueId ? `${prefix}/${linearIssueId.toLowerCase()}-${slug}` : `${prefix}/${slug}`;
|
|
1243
1243
|
const claim = await ctx.client.mutation(
|
|
1244
|
-
anyApi2.functions.
|
|
1245
|
-
{
|
|
1244
|
+
anyApi2.functions.findings.claimFindingLocal,
|
|
1245
|
+
{ findingId: args.findingId, branchName }
|
|
1246
1246
|
);
|
|
1247
|
-
if (
|
|
1247
|
+
if (linearIssueId && ctx.projectId) {
|
|
1248
1248
|
try {
|
|
1249
1249
|
await ctx.client.action(
|
|
1250
1250
|
anyApi2.functions.linearStatusMutations.moveIssueStatus,
|
|
1251
1251
|
{
|
|
1252
1252
|
projectId: ctx.projectId,
|
|
1253
|
-
linearIssueId
|
|
1253
|
+
linearIssueId,
|
|
1254
1254
|
statusType: "started"
|
|
1255
1255
|
}
|
|
1256
1256
|
);
|
|
@@ -1262,7 +1262,7 @@ function registerClaimTool(server, ctx) {
|
|
|
1262
1262
|
fetchOrigin(ctx.cwd);
|
|
1263
1263
|
worktreePath = createWorktree(
|
|
1264
1264
|
ctx.cwd,
|
|
1265
|
-
args.
|
|
1265
|
+
args.findingId,
|
|
1266
1266
|
branchName,
|
|
1267
1267
|
defaultBranch
|
|
1268
1268
|
);
|
|
@@ -1312,7 +1312,7 @@ function registerClaimTool(server, ctx) {
|
|
|
1312
1312
|
{
|
|
1313
1313
|
pipelineRunId: claim.pipelineRunId,
|
|
1314
1314
|
event: "daemon_claimed",
|
|
1315
|
-
message: `Claimed
|
|
1315
|
+
message: `Claimed finding: ${finding.title}`
|
|
1316
1316
|
}
|
|
1317
1317
|
);
|
|
1318
1318
|
await ctx.client.mutation(
|
|
@@ -1456,19 +1456,19 @@ async function createPullRequest(title, body, branch, base, repoFullName, cwd) {
|
|
|
1456
1456
|
// src/mcp/tools/ship.ts
|
|
1457
1457
|
import { join as join8 } from "path";
|
|
1458
1458
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
1459
|
-
function buildCommitMessage(message, template,
|
|
1459
|
+
function buildCommitMessage(message, template, finding) {
|
|
1460
1460
|
if (message) return message;
|
|
1461
1461
|
if (template) {
|
|
1462
|
-
return template.replace(/\{\{
|
|
1462
|
+
return template.replace(/\{\{finding\.type\}\}/g, finding.type).replace(/\{\{finding\.title\}\}/g, finding.title).replace(/\{\{finding\.linearIssueId\}\}/g, finding.linearIssueId ?? "draft").replace(/\{\{finding\.id\}\}/g, finding.id ?? "").replace(/\{\{finding\.priority\}\}/g, finding.priority ?? "medium");
|
|
1463
1463
|
}
|
|
1464
|
-
const prefix =
|
|
1465
|
-
const ref =
|
|
1466
|
-
return `${prefix}(${
|
|
1464
|
+
const prefix = finding.type === "bug" ? "fix" : "feat";
|
|
1465
|
+
const ref = finding.linearIssueId ? ` (${finding.linearIssueId})` : "";
|
|
1466
|
+
return `${prefix}(${finding.type}): ${finding.title}${ref}`;
|
|
1467
1467
|
}
|
|
1468
1468
|
function registerShipTool(server, ctx) {
|
|
1469
1469
|
server.tool(
|
|
1470
1470
|
"yapout_ship",
|
|
1471
|
-
"Commit, push, open a PR, and mark the
|
|
1471
|
+
"Commit, push, open a PR, and mark the finding as done. Run yapout_check first if post-flight checks are configured.",
|
|
1472
1472
|
{
|
|
1473
1473
|
message: z7.string().optional().describe("Custom commit message (overrides template)"),
|
|
1474
1474
|
skipPr: z7.boolean().optional().describe("Just push, don't open a PR"),
|
|
@@ -1495,26 +1495,26 @@ function registerShipTool(server, ctx) {
|
|
|
1495
1495
|
}
|
|
1496
1496
|
const branch = getCurrentBranch(gitCwd);
|
|
1497
1497
|
const defaultBranch = getDefaultBranch(gitCwd);
|
|
1498
|
-
let
|
|
1499
|
-
let
|
|
1500
|
-
let
|
|
1498
|
+
let findingTitle = branch;
|
|
1499
|
+
let findingType = "feature";
|
|
1500
|
+
let findingLinearId;
|
|
1501
1501
|
try {
|
|
1502
1502
|
const briefPath = join8(gitCwd, ".yapout", "brief.md");
|
|
1503
1503
|
if (existsSync7(briefPath)) {
|
|
1504
1504
|
const brief = readFileSync6(briefPath, "utf-8");
|
|
1505
1505
|
const titleMatch = brief.match(/^# (.+)$/m);
|
|
1506
|
-
if (titleMatch)
|
|
1506
|
+
if (titleMatch) findingTitle = titleMatch[1];
|
|
1507
1507
|
const typeMatch = brief.match(/\*\*Type:\*\* (\w+)/);
|
|
1508
|
-
if (typeMatch)
|
|
1508
|
+
if (typeMatch) findingType = typeMatch[1];
|
|
1509
1509
|
const linearMatch = brief.match(/\*\*Linear:\*\* .+\/([A-Z]+-\d+)\//);
|
|
1510
|
-
if (linearMatch)
|
|
1510
|
+
if (linearMatch) findingLinearId = linearMatch[1];
|
|
1511
1511
|
}
|
|
1512
1512
|
} catch {
|
|
1513
1513
|
}
|
|
1514
1514
|
const commitMsg = buildCommitMessage(args.message, config.commit_template, {
|
|
1515
|
-
title:
|
|
1516
|
-
type:
|
|
1517
|
-
|
|
1515
|
+
title: findingTitle,
|
|
1516
|
+
type: findingType,
|
|
1517
|
+
linearIssueId: findingLinearId
|
|
1518
1518
|
});
|
|
1519
1519
|
stageAll(gitCwd);
|
|
1520
1520
|
const sha = commit(commitMsg, gitCwd);
|
|
@@ -1538,7 +1538,7 @@ function registerShipTool(server, ctx) {
|
|
|
1538
1538
|
const prBody = [
|
|
1539
1539
|
`## Summary`,
|
|
1540
1540
|
"",
|
|
1541
|
-
|
|
1541
|
+
findingTitle,
|
|
1542
1542
|
"",
|
|
1543
1543
|
`## Changes`,
|
|
1544
1544
|
"",
|
|
@@ -1550,7 +1550,7 @@ function registerShipTool(server, ctx) {
|
|
|
1550
1550
|
`Implemented via [yapout](https://yapout.dev) daemon`
|
|
1551
1551
|
].join("\n");
|
|
1552
1552
|
const pr = await createPullRequest(
|
|
1553
|
-
|
|
1553
|
+
findingTitle,
|
|
1554
1554
|
prBody,
|
|
1555
1555
|
branch,
|
|
1556
1556
|
defaultBranch,
|
|
@@ -1598,13 +1598,13 @@ function registerShipTool(server, ctx) {
|
|
|
1598
1598
|
} catch (err) {
|
|
1599
1599
|
result.completionError = err.message;
|
|
1600
1600
|
}
|
|
1601
|
-
if (
|
|
1601
|
+
if (findingLinearId && ctx.projectId) {
|
|
1602
1602
|
try {
|
|
1603
1603
|
await ctx.client.action(
|
|
1604
1604
|
anyApi2.functions.linearStatusMutations.moveIssueStatus,
|
|
1605
1605
|
{
|
|
1606
1606
|
projectId: ctx.projectId,
|
|
1607
|
-
linearIssueId:
|
|
1607
|
+
linearIssueId: findingLinearId,
|
|
1608
1608
|
statusType: "completed"
|
|
1609
1609
|
}
|
|
1610
1610
|
);
|
|
@@ -1616,7 +1616,7 @@ function registerShipTool(server, ctx) {
|
|
|
1616
1616
|
anyApi2.functions.linearStatusMutations.addLinearComment,
|
|
1617
1617
|
{
|
|
1618
1618
|
projectId: ctx.projectId,
|
|
1619
|
-
linearIssueId:
|
|
1619
|
+
linearIssueId: findingLinearId,
|
|
1620
1620
|
body: `PR opened: [#${prNumber}](${prUrl})`
|
|
1621
1621
|
}
|
|
1622
1622
|
);
|
|
@@ -1778,18 +1778,18 @@ import { existsSync as existsSync8, writeFileSync as writeFileSync8, mkdirSync a
|
|
|
1778
1778
|
function registerBundleTool(server, ctx) {
|
|
1779
1779
|
server.tool(
|
|
1780
1780
|
"yapout_bundle",
|
|
1781
|
-
"Add a
|
|
1781
|
+
"Add a finding to the current finding's bundle so they ship as one PR",
|
|
1782
1782
|
{
|
|
1783
|
-
|
|
1784
|
-
|
|
1783
|
+
findingId: z9.string().describe("Finding ID to add to the bundle"),
|
|
1784
|
+
withFinding: z9.string().describe("Lead finding ID (currently being worked on)")
|
|
1785
1785
|
},
|
|
1786
1786
|
async (args) => {
|
|
1787
1787
|
const result = await ctx.client.mutation(
|
|
1788
|
-
anyApi2.functions.
|
|
1789
|
-
{
|
|
1788
|
+
anyApi2.functions.bundles.createBundle,
|
|
1789
|
+
{ leadFindingId: args.withFinding, joiningFindingId: args.findingId }
|
|
1790
1790
|
);
|
|
1791
1791
|
const bundledBrief = await ctx.client.query(
|
|
1792
|
-
anyApi2.functions.
|
|
1792
|
+
anyApi2.functions.bundles.getBundledBrief,
|
|
1793
1793
|
{ bundleId: result.bundleId }
|
|
1794
1794
|
);
|
|
1795
1795
|
if (!bundledBrief) {
|
|
@@ -1806,14 +1806,14 @@ function registerBundleTool(server, ctx) {
|
|
|
1806
1806
|
};
|
|
1807
1807
|
}
|
|
1808
1808
|
const sections = [
|
|
1809
|
-
`# Bundle: ${bundledBrief.
|
|
1809
|
+
`# Bundle: ${bundledBrief.findings.length} findings`,
|
|
1810
1810
|
""
|
|
1811
1811
|
];
|
|
1812
|
-
for (const t of bundledBrief.
|
|
1813
|
-
sections.push(`## ${t.
|
|
1812
|
+
for (const t of bundledBrief.findings) {
|
|
1813
|
+
sections.push(`## ${t.linearIssueId ?? t.findingId}: ${t.title}`);
|
|
1814
1814
|
sections.push("");
|
|
1815
1815
|
sections.push(`**Priority:** ${t.priority} | **Type:** ${t.type}`);
|
|
1816
|
-
if (t.
|
|
1816
|
+
if (t.linearIssueUrl) sections.push(`**Linear:** ${t.linearIssueUrl}`);
|
|
1817
1817
|
sections.push("", t.description);
|
|
1818
1818
|
if (t.enrichedDescription) {
|
|
1819
1819
|
sections.push("", "### Enriched Description", "", t.enrichedDescription);
|
|
@@ -1837,12 +1837,12 @@ function registerBundleTool(server, ctx) {
|
|
|
1837
1837
|
text: JSON.stringify(
|
|
1838
1838
|
{
|
|
1839
1839
|
bundleId: result.bundleId,
|
|
1840
|
-
|
|
1841
|
-
|
|
1840
|
+
findings: bundledBrief.findings.map((t) => ({
|
|
1841
|
+
findingId: t.findingId,
|
|
1842
1842
|
title: t.title
|
|
1843
1843
|
})),
|
|
1844
1844
|
combinedBrief,
|
|
1845
|
-
message: `${bundledBrief.
|
|
1845
|
+
message: `${bundledBrief.findings.length} findings bundled. Brief updated.`
|
|
1846
1846
|
},
|
|
1847
1847
|
null,
|
|
1848
1848
|
2
|
|
@@ -1854,22 +1854,22 @@ function registerBundleTool(server, ctx) {
|
|
|
1854
1854
|
);
|
|
1855
1855
|
}
|
|
1856
1856
|
|
|
1857
|
-
// src/mcp/tools/get-unenriched-
|
|
1857
|
+
// src/mcp/tools/get-unenriched-findings.ts
|
|
1858
1858
|
import { z as z10 } from "zod";
|
|
1859
|
-
function
|
|
1859
|
+
function registerGetUnenrichedFindingsTool(server, ctx) {
|
|
1860
1860
|
server.tool(
|
|
1861
|
-
"
|
|
1862
|
-
`Start enriching a
|
|
1861
|
+
"yapout_get_unenriched_finding",
|
|
1862
|
+
`Start enriching a finding. Returns the next draft finding (or a specific one by ID) with full context including the original capture quote, project context, and existing finding titles for duplicate detection.
|
|
1863
1863
|
|
|
1864
|
-
The
|
|
1864
|
+
The finding is locked to "enriching" status \u2014 no other agent can enrich it concurrently.
|
|
1865
1865
|
|
|
1866
1866
|
After calling this tool, you should:
|
|
1867
1867
|
1. Read the relevant parts of the codebase to understand the area affected
|
|
1868
|
-
2. Check for duplicates against the
|
|
1869
|
-
3. If the
|
|
1868
|
+
2. Check for duplicates against the existingFindings list
|
|
1869
|
+
3. If the finding is ambiguous, ask the developer clarifying questions in conversation
|
|
1870
1870
|
4. When confident, call yapout_save_enrichment with a clean description, acceptance criteria, and implementation brief`,
|
|
1871
1871
|
{
|
|
1872
|
-
|
|
1872
|
+
findingId: z10.string().optional().describe("Specific finding ID to enrich. If omitted, returns the highest priority draft finding.")
|
|
1873
1873
|
},
|
|
1874
1874
|
async (args) => {
|
|
1875
1875
|
const projectId = ctx.projectId ?? process.env.YAPOUT_PROJECT_ID;
|
|
@@ -1889,7 +1889,7 @@ After calling this tool, you should:
|
|
|
1889
1889
|
anyApi2.functions.localPipeline.claimForEnrichment,
|
|
1890
1890
|
{
|
|
1891
1891
|
projectId,
|
|
1892
|
-
...args.
|
|
1892
|
+
...args.findingId ? { findingId: args.findingId } : {}
|
|
1893
1893
|
}
|
|
1894
1894
|
);
|
|
1895
1895
|
if (!result) {
|
|
@@ -1897,7 +1897,7 @@ After calling this tool, you should:
|
|
|
1897
1897
|
content: [
|
|
1898
1898
|
{
|
|
1899
1899
|
type: "text",
|
|
1900
|
-
text: "No
|
|
1900
|
+
text: "No findings need enrichment. All findings are either already enriched or in progress."
|
|
1901
1901
|
}
|
|
1902
1902
|
]
|
|
1903
1903
|
};
|
|
@@ -1915,7 +1915,7 @@ After calling this tool, you should:
|
|
|
1915
1915
|
content: [
|
|
1916
1916
|
{
|
|
1917
1917
|
type: "text",
|
|
1918
|
-
text: `Error claiming
|
|
1918
|
+
text: `Error claiming finding for enrichment: ${err.message}`
|
|
1919
1919
|
}
|
|
1920
1920
|
],
|
|
1921
1921
|
isError: true
|
|
@@ -1925,11 +1925,11 @@ After calling this tool, you should:
|
|
|
1925
1925
|
);
|
|
1926
1926
|
}
|
|
1927
1927
|
|
|
1928
|
-
// src/mcp/tools/get-existing-
|
|
1929
|
-
function
|
|
1928
|
+
// src/mcp/tools/get-existing-findings.ts
|
|
1929
|
+
function registerGetExistingFindingsTool(server, ctx) {
|
|
1930
1930
|
server.tool(
|
|
1931
|
-
"
|
|
1932
|
-
"Fetch all
|
|
1931
|
+
"yapout_get_existing_findings",
|
|
1932
|
+
"Fetch all finding titles in the project for duplicate detection during enrichment",
|
|
1933
1933
|
{},
|
|
1934
1934
|
async () => {
|
|
1935
1935
|
const projectId = ctx.projectId ?? process.env.YAPOUT_PROJECT_ID;
|
|
@@ -1945,16 +1945,16 @@ function registerGetExistingTicketsTool(server, ctx) {
|
|
|
1945
1945
|
};
|
|
1946
1946
|
}
|
|
1947
1947
|
try {
|
|
1948
|
-
const
|
|
1949
|
-
anyApi2.functions.localPipeline.
|
|
1948
|
+
const findings = await ctx.client.query(
|
|
1949
|
+
anyApi2.functions.localPipeline.getExistingFindingTitles,
|
|
1950
1950
|
{ projectId }
|
|
1951
1951
|
);
|
|
1952
|
-
if (!
|
|
1952
|
+
if (!findings || findings.length === 0) {
|
|
1953
1953
|
return {
|
|
1954
1954
|
content: [
|
|
1955
1955
|
{
|
|
1956
1956
|
type: "text",
|
|
1957
|
-
text: "No existing
|
|
1957
|
+
text: "No existing findings in this project."
|
|
1958
1958
|
}
|
|
1959
1959
|
]
|
|
1960
1960
|
};
|
|
@@ -1963,7 +1963,7 @@ function registerGetExistingTicketsTool(server, ctx) {
|
|
|
1963
1963
|
content: [
|
|
1964
1964
|
{
|
|
1965
1965
|
type: "text",
|
|
1966
|
-
text: JSON.stringify(
|
|
1966
|
+
text: JSON.stringify(findings, null, 2)
|
|
1967
1967
|
}
|
|
1968
1968
|
]
|
|
1969
1969
|
};
|
|
@@ -1972,7 +1972,7 @@ function registerGetExistingTicketsTool(server, ctx) {
|
|
|
1972
1972
|
content: [
|
|
1973
1973
|
{
|
|
1974
1974
|
type: "text",
|
|
1975
|
-
text: `Error fetching existing
|
|
1975
|
+
text: `Error fetching existing findings: ${err.message}`
|
|
1976
1976
|
}
|
|
1977
1977
|
],
|
|
1978
1978
|
isError: true
|
|
@@ -1984,23 +1984,53 @@ function registerGetExistingTicketsTool(server, ctx) {
|
|
|
1984
1984
|
|
|
1985
1985
|
// src/mcp/tools/save-enrichment.ts
|
|
1986
1986
|
import { z as z11 } from "zod";
|
|
1987
|
+
|
|
1988
|
+
// src/mcp/tools/enrichment-session.ts
|
|
1989
|
+
var activeSessions = /* @__PURE__ */ new Map();
|
|
1990
|
+
function createSession(projectId, total, filter) {
|
|
1991
|
+
const sessionId = crypto.randomUUID();
|
|
1992
|
+
const session = {
|
|
1993
|
+
sessionId,
|
|
1994
|
+
projectId,
|
|
1995
|
+
filter,
|
|
1996
|
+
startedAt: Date.now(),
|
|
1997
|
+
stats: { enriched: 0, skipped: 0, total },
|
|
1998
|
+
contextDelivered: false
|
|
1999
|
+
};
|
|
2000
|
+
activeSessions.set(sessionId, session);
|
|
2001
|
+
return session;
|
|
2002
|
+
}
|
|
2003
|
+
function getSession(sessionId) {
|
|
2004
|
+
return activeSessions.get(sessionId);
|
|
2005
|
+
}
|
|
2006
|
+
function updateSessionStats(sessionId, update) {
|
|
2007
|
+
const session = activeSessions.get(sessionId);
|
|
2008
|
+
if (!session) return;
|
|
2009
|
+
Object.assign(session.stats, update);
|
|
2010
|
+
}
|
|
2011
|
+
function markContextDelivered(sessionId) {
|
|
2012
|
+
const session = activeSessions.get(sessionId);
|
|
2013
|
+
if (session) session.contextDelivered = true;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// src/mcp/tools/save-enrichment.ts
|
|
1987
2017
|
function registerSaveEnrichmentTool(server, ctx) {
|
|
1988
2018
|
server.tool(
|
|
1989
2019
|
"yapout_save_enrichment",
|
|
1990
2020
|
`Save enrichment and sync to Linear. Call this after you have read the codebase, asked any necessary clarifying questions, and formulated a clean description, acceptance criteria, and implementation brief.
|
|
1991
2021
|
|
|
1992
|
-
The
|
|
2022
|
+
The finding must have been claimed via yapout_get_unenriched_finding first (status must be "enriching").
|
|
1993
2023
|
|
|
1994
2024
|
This tool saves the enrichment, then automatically creates the Linear issue with:
|
|
1995
2025
|
- Clean description + acceptance criteria as the issue body
|
|
1996
2026
|
- Clarification Q&A as a branded comment (if any)
|
|
1997
2027
|
- Implementation brief as attachment metadata
|
|
1998
2028
|
|
|
1999
|
-
The
|
|
2029
|
+
The finding transitions: enriching \u2192 enriched \u2192 synced.`,
|
|
2000
2030
|
{
|
|
2001
|
-
|
|
2002
|
-
title: z11.string().describe("Refined
|
|
2003
|
-
cleanDescription: z11.string().describe("Human-readable summary for the Linear issue body. Write the kind of
|
|
2031
|
+
findingId: z11.string().describe("The finding ID to enrich (from yapout_get_unenriched_finding)"),
|
|
2032
|
+
title: z11.string().describe("Refined finding title \u2014 improve it if the original was vague"),
|
|
2033
|
+
cleanDescription: z11.string().describe("Human-readable summary for the Linear issue body. Write the kind of finding a senior engineer would write."),
|
|
2004
2034
|
acceptanceCriteria: z11.array(z11.string()).describe("List of testable acceptance criteria (each a single clear statement)"),
|
|
2005
2035
|
implementationBrief: z11.string().describe("Deep technical context for the implementing agent: which files, what approach, edge cases to watch for"),
|
|
2006
2036
|
clarifications: z11.array(
|
|
@@ -2009,17 +2039,18 @@ The ticket transitions: enriching \u2192 enriched \u2192 synced.`,
|
|
|
2009
2039
|
answer: z11.string().describe("The developer's answer")
|
|
2010
2040
|
})
|
|
2011
2041
|
).optional().describe("Only meaningful Q&A from the conversation \u2014 deviations from expected scope, scoping decisions, etc. Omit if you had no questions."),
|
|
2012
|
-
isOversized: z11.boolean().optional().describe("Set to true if this
|
|
2013
|
-
suggestedSplit: z11.array(z11.string()).optional().describe("If oversized: suggested sub-
|
|
2014
|
-
level: z11.enum(["project", "issue"]).optional().describe("Override the
|
|
2015
|
-
nature: z11.enum(["implementable", "operational", "spike"]).optional().describe("Override the
|
|
2042
|
+
isOversized: z11.boolean().optional().describe("Set to true if this finding is too large for a single PR"),
|
|
2043
|
+
suggestedSplit: z11.array(z11.string()).optional().describe("If oversized: suggested sub-finding titles for breaking it down"),
|
|
2044
|
+
level: z11.enum(["project", "issue"]).optional().describe("Override the finding's level if enrichment reveals it should be reclassified"),
|
|
2045
|
+
nature: z11.enum(["implementable", "operational", "spike"]).optional().describe("Override the finding's nature if enrichment reveals it should be reclassified"),
|
|
2046
|
+
sessionId: z11.string().optional().describe("Bulk enrichment session ID (from yapout_start_enrichment). Updates session stats.")
|
|
2016
2047
|
},
|
|
2017
2048
|
async (args) => {
|
|
2018
2049
|
try {
|
|
2019
2050
|
await ctx.client.mutation(
|
|
2020
2051
|
anyApi2.functions.localPipeline.saveLocalEnrichment,
|
|
2021
2052
|
{
|
|
2022
|
-
|
|
2053
|
+
findingId: args.findingId,
|
|
2023
2054
|
title: args.title,
|
|
2024
2055
|
cleanDescription: args.cleanDescription,
|
|
2025
2056
|
acceptanceCriteria: args.acceptanceCriteria,
|
|
@@ -2032,21 +2063,33 @@ The ticket transitions: enriching \u2192 enriched \u2192 synced.`,
|
|
|
2032
2063
|
}
|
|
2033
2064
|
);
|
|
2034
2065
|
await ctx.client.action(
|
|
2035
|
-
anyApi2.functions.localPipeline.
|
|
2036
|
-
{
|
|
2066
|
+
anyApi2.functions.localPipeline.syncFindingToLinearLocal,
|
|
2067
|
+
{ findingId: args.findingId }
|
|
2037
2068
|
);
|
|
2038
|
-
const
|
|
2039
|
-
anyApi2.functions.
|
|
2040
|
-
{
|
|
2069
|
+
const finding = await ctx.client.query(
|
|
2070
|
+
anyApi2.functions.findings.getFinding,
|
|
2071
|
+
{ findingId: args.findingId }
|
|
2041
2072
|
);
|
|
2073
|
+
if (args.sessionId) {
|
|
2074
|
+
const session2 = getSession(args.sessionId);
|
|
2075
|
+
if (session2) {
|
|
2076
|
+
updateSessionStats(args.sessionId, {
|
|
2077
|
+
enriched: session2.stats.enriched + 1
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
const session = args.sessionId ? getSession(args.sessionId) : null;
|
|
2082
|
+
const estimatedTokens = session ? session.stats.enriched * 5e3 : 0;
|
|
2083
|
+
const compactionHint = estimatedTokens > 1e5;
|
|
2042
2084
|
const response = {
|
|
2043
|
-
|
|
2044
|
-
linearIssueId:
|
|
2045
|
-
|
|
2046
|
-
|
|
2085
|
+
findingId: args.findingId,
|
|
2086
|
+
linearIssueId: finding?.linearIssueId ?? null,
|
|
2087
|
+
linearIssueUrl: finding?.linearIssueUrl ?? null,
|
|
2088
|
+
compactionHint,
|
|
2089
|
+
message: finding?.linearIssueUrl ? `Finding enriched and synced to Linear: ${finding.linearIssueUrl}` : "Finding enriched and synced to Linear."
|
|
2047
2090
|
};
|
|
2048
2091
|
if (args.isOversized && args.suggestedSplit?.length) {
|
|
2049
|
-
response.warning = `This
|
|
2092
|
+
response.warning = `This finding is oversized. Suggested split: ${args.suggestedSplit.join(", ")}`;
|
|
2050
2093
|
}
|
|
2051
2094
|
return {
|
|
2052
2095
|
content: [
|
|
@@ -2076,15 +2119,15 @@ import { z as z12 } from "zod";
|
|
|
2076
2119
|
function registerSyncToLinearTool(server, ctx) {
|
|
2077
2120
|
server.tool(
|
|
2078
2121
|
"yapout_sync_to_linear",
|
|
2079
|
-
"Trigger Linear
|
|
2122
|
+
"Trigger Linear issue creation for an enriched finding. The sync runs server-side (encrypted Linear token in Convex).",
|
|
2080
2123
|
{
|
|
2081
|
-
|
|
2124
|
+
findingId: z12.string().describe("The finding ID to sync to Linear")
|
|
2082
2125
|
},
|
|
2083
2126
|
async (args) => {
|
|
2084
2127
|
try {
|
|
2085
2128
|
await ctx.client.action(
|
|
2086
|
-
anyApi2.functions.localPipeline.
|
|
2087
|
-
{
|
|
2129
|
+
anyApi2.functions.localPipeline.syncFindingToLinearLocal,
|
|
2130
|
+
{ findingId: args.findingId }
|
|
2088
2131
|
);
|
|
2089
2132
|
return {
|
|
2090
2133
|
content: [
|
|
@@ -2093,8 +2136,8 @@ function registerSyncToLinearTool(server, ctx) {
|
|
|
2093
2136
|
text: JSON.stringify(
|
|
2094
2137
|
{
|
|
2095
2138
|
success: true,
|
|
2096
|
-
|
|
2097
|
-
message: "
|
|
2139
|
+
findingId: args.findingId,
|
|
2140
|
+
message: "Finding synced to Linear successfully. It will now appear in the work queue for implementation."
|
|
2098
2141
|
},
|
|
2099
2142
|
null,
|
|
2100
2143
|
2
|
|
@@ -2122,7 +2165,7 @@ import { z as z13 } from "zod";
|
|
|
2122
2165
|
function registerSubmitYapSessionTool(server, ctx) {
|
|
2123
2166
|
server.tool(
|
|
2124
2167
|
"yapout_submit_yap_session",
|
|
2125
|
-
"Submit a yap session transcript for
|
|
2168
|
+
"Submit a yap session transcript for finding extraction",
|
|
2126
2169
|
{
|
|
2127
2170
|
title: z13.string().describe("Session title (e.g., 'Notification system brainstorm')"),
|
|
2128
2171
|
transcript: z13.string().describe("Cleaned conversation transcript")
|
|
@@ -2140,8 +2183,8 @@ function registerSubmitYapSessionTool(server, ctx) {
|
|
|
2140
2183
|
};
|
|
2141
2184
|
}
|
|
2142
2185
|
try {
|
|
2143
|
-
const
|
|
2144
|
-
anyApi2.functions.
|
|
2186
|
+
const captureId = await ctx.client.mutation(
|
|
2187
|
+
anyApi2.functions.captures.createFromYapSession,
|
|
2145
2188
|
{
|
|
2146
2189
|
projectId: ctx.projectId,
|
|
2147
2190
|
title: args.title,
|
|
@@ -2154,8 +2197,8 @@ function registerSubmitYapSessionTool(server, ctx) {
|
|
|
2154
2197
|
type: "text",
|
|
2155
2198
|
text: JSON.stringify(
|
|
2156
2199
|
{
|
|
2157
|
-
|
|
2158
|
-
message: "Yap session submitted.
|
|
2200
|
+
captureId,
|
|
2201
|
+
message: "Yap session submitted. Findings will appear for review shortly."
|
|
2159
2202
|
},
|
|
2160
2203
|
null,
|
|
2161
2204
|
2
|
|
@@ -2233,46 +2276,46 @@ WHAT YOU DON'T DO:
|
|
|
2233
2276
|
- Don't implement anything. Don't write code. Don't edit files. Just discuss.
|
|
2234
2277
|
- Don't agree with everything. Challenge ideas that seem undercooked.
|
|
2235
2278
|
- Don't ask more than 5 questions without letting the developer steer.
|
|
2236
|
-
- Don't make changes to the codebase, even if the user asks. A yap session produces
|
|
2279
|
+
- Don't make changes to the codebase, even if the user asks. A yap session produces findings \u2014 implementation happens later through the normal finding pipeline. If the user asks you to "just do it" or "make that change now," push back: "That's what the findings are for. Let's capture it properly so it goes through review." This boundary is critical \u2014 implementing during a yap bypasses every approval gate yapout exists to enforce.
|
|
2237
2280
|
|
|
2238
2281
|
${contextSection}
|
|
2239
2282
|
|
|
2240
2283
|
DURING THE SESSION \u2014 COMPLETENESS TRACKING:
|
|
2241
|
-
As the conversation progresses, you are building a mental model of every
|
|
2284
|
+
As the conversation progresses, you are building a mental model of every finding. For each finding, track:
|
|
2242
2285
|
- What has been clearly stated (title, scope, priority)
|
|
2243
|
-
- What has been discussed in enough depth to write a
|
|
2286
|
+
- What has been discussed in enough depth to write a finding (enrichment)
|
|
2244
2287
|
- What is still vague, contradicted, or unanswered
|
|
2245
2288
|
|
|
2246
|
-
The conversation does NOT need to follow a rigid structure. The user may jump between topics, change their mind, go on tangents, or revisit earlier
|
|
2289
|
+
The conversation does NOT need to follow a rigid structure. The user may jump between topics, change their mind, go on tangents, or revisit earlier findings. This is normal \u2014 real conversations are not linear. Your job is to follow the thread and keep track of the state of each finding regardless of conversation order.
|
|
2247
2290
|
|
|
2248
2291
|
If the user contradicts an earlier statement, note it and confirm which version they mean when the time is right. Don't interrupt the flow for minor clarifications \u2014 batch them for later.
|
|
2249
2292
|
|
|
2250
2293
|
BEFORE SUBMITTING \u2014 GAP FILLING:
|
|
2251
|
-
Before you submit, review your mental model of every
|
|
2252
|
-
- Could a developer read this
|
|
2294
|
+
Before you submit, review your mental model of every finding. For each one, ask yourself:
|
|
2295
|
+
- Could a developer read this finding and start working without asking questions?
|
|
2253
2296
|
- Are the acceptance criteria specific enough to verify?
|
|
2254
2297
|
- Are there ambiguities the user didn't resolve?
|
|
2255
2298
|
|
|
2256
2299
|
If there are gaps, ask the user to fill them. Be direct: "Before I submit, I need clarity on a few things..." Group related gaps together rather than asking one at a time.
|
|
2257
2300
|
|
|
2258
|
-
You do NOT need to fill every gap. If the conversation didn't cover something deeply enough, mark that
|
|
2301
|
+
You do NOT need to fill every gap. If the conversation didn't cover something deeply enough, mark that finding as NOT enriched \u2014 it will go through the async enrichment pipeline later. Be honest about what you know vs. what you're guessing.
|
|
2259
2302
|
|
|
2260
2303
|
ENRICHMENT ASSESSMENT:
|
|
2261
|
-
For each
|
|
2304
|
+
For each finding, you must make an honest call: is this enriched or not?
|
|
2262
2305
|
|
|
2263
|
-
Mark a
|
|
2264
|
-
- The conversation covered it thoroughly enough to produce a clear
|
|
2306
|
+
Mark a finding as ENRICHED (isEnriched: true) when:
|
|
2307
|
+
- The conversation covered it thoroughly enough to produce a clear finding
|
|
2265
2308
|
- You can write an enrichedDescription that a senior engineer would recognize as well-scoped
|
|
2266
2309
|
- You can write at least 2-3 testable acceptance criteria
|
|
2267
2310
|
- The user explicitly validated the scope (not just mentioned it in passing)
|
|
2268
2311
|
|
|
2269
|
-
Mark a
|
|
2312
|
+
Mark a finding as NOT ENRICHED (isEnriched: false or omitted) when:
|
|
2270
2313
|
- It came up late in the conversation without much discussion
|
|
2271
2314
|
- The user mentioned it but didn't elaborate on scope or requirements
|
|
2272
2315
|
- You're uncertain about key aspects (what it should do, how it should work)
|
|
2273
2316
|
- It's a spike or needs further scoping
|
|
2274
2317
|
|
|
2275
|
-
This is a quality gate. Do not inflate your assessment \u2014 unenriched
|
|
2318
|
+
This is a quality gate. Do not inflate your assessment \u2014 unenriched findings get enriched properly later. Falsely marking something as enriched skips that process and produces bad findings.
|
|
2276
2319
|
|
|
2277
2320
|
PRESENTING THE FINAL PICTURE:
|
|
2278
2321
|
When the conversation feels complete, present a summary grouped by project:
|
|
@@ -2294,17 +2337,17 @@ Should I submit this to yapout?"
|
|
|
2294
2337
|
SUBMITTING:
|
|
2295
2338
|
On confirmation, call yapout_extract_from_yap with the full data.
|
|
2296
2339
|
|
|
2297
|
-
For each
|
|
2340
|
+
For each finding, provide:
|
|
2298
2341
|
- title, description, sourceQuote, type, priority, confidence, level, nature
|
|
2299
2342
|
- isEnriched: your honest assessment (see above)
|
|
2300
2343
|
|
|
2301
|
-
For ENRICHED
|
|
2302
|
-
- enrichedDescription: clean, final description \u2014 write the kind of
|
|
2344
|
+
For ENRICHED findings (isEnriched: true), also include:
|
|
2345
|
+
- enrichedDescription: clean, final description \u2014 write the kind of finding a senior engineer would write
|
|
2303
2346
|
- acceptanceCriteria: array of specific, testable statements
|
|
2304
2347
|
- implementationBrief: (optional) technical context \u2014 files, approach, edge cases. Only include if you read the codebase during the session. The implementing agent reads the codebase anyway.
|
|
2305
|
-
- clarifications: relevant Q&A from the conversation that shaped the
|
|
2348
|
+
- clarifications: relevant Q&A from the conversation that shaped the finding
|
|
2306
2349
|
|
|
2307
|
-
For PROJECT-level
|
|
2350
|
+
For PROJECT-level findings, also include:
|
|
2308
2351
|
- projectDescription: what this body of work accomplishes
|
|
2309
2352
|
- suggestedOrder: implementation sequencing ("Phase 1: A, then Phase 2: B+C")
|
|
2310
2353
|
- children: array of child issues (each with their own enrichment data)
|
|
@@ -2313,18 +2356,18 @@ For children with DEPENDENCIES, include:
|
|
|
2313
2356
|
- dependsOn: array of sibling indices (0-based) that must complete first
|
|
2314
2357
|
|
|
2315
2358
|
Structure:
|
|
2316
|
-
- Projects include children inline \u2014 never create child issues as separate top-level
|
|
2359
|
+
- Projects include children inline \u2014 never create child issues as separate top-level findings
|
|
2317
2360
|
- Standalone issues go at the top level
|
|
2318
2361
|
- The hierarchy you produce should be the final structure
|
|
2319
2362
|
|
|
2320
2363
|
Call yapout_extract_from_yap with:
|
|
2321
2364
|
- sessionTitle: descriptive title for this session
|
|
2322
2365
|
- sessionTranscript: clean summary of the conversation (meeting-notes style)
|
|
2323
|
-
-
|
|
2366
|
+
- findings: the array (projects with children, standalone issues)
|
|
2324
2367
|
|
|
2325
|
-
This creates
|
|
2368
|
+
This creates findings (enriched or draft based on your assessment) and project decomposition \u2014 all in one call. Enriched findings appear in the work queue ready for the user to sync to Linear.
|
|
2326
2369
|
|
|
2327
|
-
Only fall back to yapout_submit_yap_session if the session was purely exploratory with no clear actionable
|
|
2370
|
+
Only fall back to yapout_submit_yap_session if the session was purely exploratory with no clear actionable findings.
|
|
2328
2371
|
|
|
2329
2372
|
--- BEGIN THE SESSION NOW ---`;
|
|
2330
2373
|
return {
|
|
@@ -2358,39 +2401,39 @@ var childSchema = z15.object({
|
|
|
2358
2401
|
title: z15.string().describe("Child issue title"),
|
|
2359
2402
|
description: z15.string().describe("What needs to be done and why"),
|
|
2360
2403
|
sourceQuote: z15.string().describe("Relevant excerpt from the conversation"),
|
|
2361
|
-
type: z15.enum(["feature", "bug", "chore"]).describe("
|
|
2404
|
+
type: z15.enum(["feature", "bug", "chore"]).describe("Finding category"),
|
|
2362
2405
|
priority: z15.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
|
|
2363
|
-
confidence: z15.number().min(0).max(1).describe("Confidence this is a clear, actionable
|
|
2406
|
+
confidence: z15.number().min(0).max(1).describe("Confidence this is a clear, actionable finding (0-1)"),
|
|
2364
2407
|
nature: z15.enum(["implementable", "operational", "spike"]).describe("What kind of work"),
|
|
2365
2408
|
// Enrichment — set isEnriched: true only if the conversation covered this
|
|
2366
|
-
// issue thoroughly enough to produce a
|
|
2409
|
+
// issue thoroughly enough to produce a finding a developer could pick up.
|
|
2367
2410
|
isEnriched: z15.boolean().optional().describe(
|
|
2368
|
-
"Did the conversation cover this issue deeply enough to produce a complete
|
|
2411
|
+
"Did the conversation cover this issue deeply enough to produce a complete finding? true = enriched (ready for Linear), false/omitted = draft (needs async enrichment)"
|
|
2369
2412
|
),
|
|
2370
2413
|
enrichedDescription: z15.string().optional().describe("Clean, final description for the Linear issue body (required if isEnriched)"),
|
|
2371
2414
|
acceptanceCriteria: z15.array(z15.string()).optional().describe("Testable acceptance criteria (required if isEnriched)"),
|
|
2372
2415
|
implementationBrief: z15.string().optional().describe("Technical context: files, approach, edge cases (optional \u2014 the implementing agent reads the codebase anyway)"),
|
|
2373
|
-
clarifications: z15.array(clarificationSchema).optional().describe("Relevant Q&A from the conversation that shaped this
|
|
2416
|
+
clarifications: z15.array(clarificationSchema).optional().describe("Relevant Q&A from the conversation that shaped this finding"),
|
|
2374
2417
|
dependsOn: z15.array(z15.number()).optional().describe("Indices (0-based) of sibling children that must be completed first")
|
|
2375
2418
|
});
|
|
2376
2419
|
function registerExtractFromYapTool(server, ctx) {
|
|
2377
2420
|
server.tool(
|
|
2378
2421
|
"yapout_extract_from_yap",
|
|
2379
|
-
"Submit
|
|
2422
|
+
"Submit findings from a yap session you conducted. The yap session IS the enrichment \u2014 you participated in the conversation, gathered context, and can assess completeness per-finding. Creates capture, findings (enriched or draft). Enriched findings are ready for the user to sync to Linear from the UI. Draft findings need further enrichment via the async pipeline.",
|
|
2380
2423
|
{
|
|
2381
2424
|
sessionTitle: z15.string().describe("Descriptive title for this yap session"),
|
|
2382
2425
|
sessionTranscript: z15.string().describe("Cleaned transcript of the conversation (meeting-notes style)"),
|
|
2383
|
-
|
|
2426
|
+
findings: z15.array(
|
|
2384
2427
|
z15.object({
|
|
2385
|
-
title: z15.string().describe("
|
|
2428
|
+
title: z15.string().describe("Finding title"),
|
|
2386
2429
|
description: z15.string().describe("What needs to be done and why"),
|
|
2387
2430
|
sourceQuote: z15.string().describe("Relevant excerpt from the conversation"),
|
|
2388
|
-
type: z15.enum(["feature", "bug", "chore"]).describe("
|
|
2431
|
+
type: z15.enum(["feature", "bug", "chore"]).describe("Finding category (do not use spike \u2014 use nature instead)"),
|
|
2389
2432
|
priority: z15.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
|
|
2390
|
-
confidence: z15.number().min(0).max(1).describe("Your confidence this is a clear, actionable
|
|
2433
|
+
confidence: z15.number().min(0).max(1).describe("Your confidence this is a clear, actionable finding (0-1)"),
|
|
2391
2434
|
level: z15.enum(["project", "issue"]).describe("project = body of work with children, issue = single unit"),
|
|
2392
2435
|
nature: z15.enum(["implementable", "operational", "spike"]).describe("implementable = code changes, operational = manual task, spike = needs scoping"),
|
|
2393
|
-
|
|
2436
|
+
sourceFindingId: z15.string().optional().describe("ID of an existing finding this scopes (e.g., scoping a spike)"),
|
|
2394
2437
|
// Issue-level enrichment (when level === "issue")
|
|
2395
2438
|
isEnriched: z15.boolean().optional().describe("For standalone issues: was this thoroughly discussed? true = enriched, false = draft"),
|
|
2396
2439
|
enrichedDescription: z15.string().optional().describe("Clean description for Linear (standalone issues only, required if isEnriched)"),
|
|
@@ -2402,7 +2445,7 @@ function registerExtractFromYapTool(server, ctx) {
|
|
|
2402
2445
|
suggestedOrder: z15.string().optional().describe("Implementation order: 'Phase 1: A, then Phase 2: B+C' (projects only)"),
|
|
2403
2446
|
children: z15.array(childSchema).optional().describe("Child issues for project-level decisions")
|
|
2404
2447
|
})
|
|
2405
|
-
).describe("
|
|
2448
|
+
).describe("Findings from the conversation. Projects include children inline.")
|
|
2406
2449
|
},
|
|
2407
2450
|
async (args) => {
|
|
2408
2451
|
if (!ctx.projectId) {
|
|
@@ -2418,27 +2461,27 @@ function registerExtractFromYapTool(server, ctx) {
|
|
|
2418
2461
|
}
|
|
2419
2462
|
try {
|
|
2420
2463
|
const result = await ctx.client.mutation(
|
|
2421
|
-
anyApi2.functions.
|
|
2464
|
+
anyApi2.functions.captures.extractFromYapSession,
|
|
2422
2465
|
{
|
|
2423
2466
|
projectId: ctx.projectId,
|
|
2424
2467
|
title: args.sessionTitle,
|
|
2425
2468
|
transcript: args.sessionTranscript,
|
|
2426
|
-
|
|
2469
|
+
findings: args.findings
|
|
2427
2470
|
}
|
|
2428
2471
|
);
|
|
2429
2472
|
let enrichedCount = 0;
|
|
2430
2473
|
let draftCount = 0;
|
|
2431
|
-
let
|
|
2474
|
+
let totalFindings = 0;
|
|
2432
2475
|
for (const item of result.items) {
|
|
2433
2476
|
if (item.level === "project") {
|
|
2434
2477
|
for (const child of item.children ?? []) {
|
|
2435
|
-
|
|
2436
|
-
if (child.
|
|
2478
|
+
totalFindings++;
|
|
2479
|
+
if (child.findingStatus === "enriched") enrichedCount++;
|
|
2437
2480
|
else draftCount++;
|
|
2438
2481
|
}
|
|
2439
2482
|
} else {
|
|
2440
|
-
|
|
2441
|
-
if (item.
|
|
2483
|
+
totalFindings++;
|
|
2484
|
+
if (item.findingStatus === "enriched") enrichedCount++;
|
|
2442
2485
|
else draftCount++;
|
|
2443
2486
|
}
|
|
2444
2487
|
}
|
|
@@ -2451,14 +2494,14 @@ function registerExtractFromYapTool(server, ctx) {
|
|
|
2451
2494
|
type: "text",
|
|
2452
2495
|
text: JSON.stringify(
|
|
2453
2496
|
{
|
|
2454
|
-
|
|
2497
|
+
captureId: result.captureId,
|
|
2455
2498
|
items: result.items,
|
|
2456
2499
|
summary: {
|
|
2457
|
-
|
|
2500
|
+
totalFindings,
|
|
2458
2501
|
enriched: enrichedCount,
|
|
2459
2502
|
needsEnrichment: draftCount
|
|
2460
2503
|
},
|
|
2461
|
-
message: `Created ${
|
|
2504
|
+
message: `Created ${totalFindings} finding${totalFindings === 1 ? "" : "s"}: ${parts.join(", ")}. Review in the yapout work queue.`
|
|
2462
2505
|
},
|
|
2463
2506
|
null,
|
|
2464
2507
|
2
|
|
@@ -2486,20 +2529,20 @@ import { z as z16 } from "zod";
|
|
|
2486
2529
|
function registerSaveProjectEnrichmentTool(server, ctx) {
|
|
2487
2530
|
server.tool(
|
|
2488
2531
|
"yapout_save_project_enrichment",
|
|
2489
|
-
`Save the decomposition of a project-level
|
|
2532
|
+
`Save the decomposition of a project-level finding into implementable child issues.
|
|
2490
2533
|
|
|
2491
|
-
Call this after enriching a
|
|
2534
|
+
Call this after enriching a finding with level: "project". The agent should have:
|
|
2492
2535
|
1. Read the codebase extensively
|
|
2493
2536
|
2. Asked the user scoping questions
|
|
2494
2537
|
3. Produced a set of child issues with dependencies
|
|
2495
2538
|
|
|
2496
|
-
This tool creates child
|
|
2497
|
-
|
|
2539
|
+
This tool creates child findings in yapout, transitions the original
|
|
2540
|
+
finding to "decomposed", and returns the child finding IDs for subsequent Linear sync.
|
|
2498
2541
|
|
|
2499
2542
|
Each child issue's implementation brief must be detailed enough to stand alone as a
|
|
2500
2543
|
full spec \u2014 schema changes, files to modify, edge cases, and acceptance criteria.`,
|
|
2501
2544
|
{
|
|
2502
|
-
|
|
2545
|
+
findingId: z16.string().describe("The project-level finding being decomposed (from yapout_get_unenriched_finding)"),
|
|
2503
2546
|
projectDescription: z16.string().describe("Description for the project \u2014 what this body of work accomplishes"),
|
|
2504
2547
|
suggestedOrder: z16.string().describe("Human-readable implementation order (e.g. 'Phase 1: A, then Phase 2: B+C in parallel, then Phase 3: D')"),
|
|
2505
2548
|
linearProjectId: z16.string().optional().describe("Existing Linear project ID to associate child issues with. Omit to skip Linear project association \u2014 issues will be synced without a project."),
|
|
@@ -2531,7 +2574,7 @@ full spec \u2014 schema changes, files to modify, edge cases, and acceptance cri
|
|
|
2531
2574
|
const result = await ctx.client.mutation(
|
|
2532
2575
|
anyApi2.functions.localPipeline.saveProjectEnrichment,
|
|
2533
2576
|
{
|
|
2534
|
-
|
|
2577
|
+
findingId: args.findingId,
|
|
2535
2578
|
projectDescription: args.projectDescription,
|
|
2536
2579
|
suggestedOrder: args.suggestedOrder,
|
|
2537
2580
|
linearProjectId: args.linearProjectId,
|
|
@@ -2544,11 +2587,11 @@ full spec \u2014 schema changes, files to modify, edge cases, and acceptance cri
|
|
|
2544
2587
|
type: "text",
|
|
2545
2588
|
text: JSON.stringify(
|
|
2546
2589
|
{
|
|
2547
|
-
|
|
2590
|
+
findingId: result.findingId,
|
|
2548
2591
|
status: "decomposed",
|
|
2549
|
-
|
|
2592
|
+
childFindingIds: result.childFindingIds,
|
|
2550
2593
|
suggestedOrder: result.suggestedOrder,
|
|
2551
|
-
message: `Project decomposed into ${result.
|
|
2594
|
+
message: `Project decomposed into ${result.childFindingIds.length} child issues. Original finding marked as decomposed. Child findings are in draft status \u2014 enrich and sync each one individually, or approve them for the user to review.`
|
|
2552
2595
|
},
|
|
2553
2596
|
null,
|
|
2554
2597
|
2
|
|
@@ -2576,13 +2619,13 @@ import { z as z17 } from "zod";
|
|
|
2576
2619
|
function registerMarkDuplicateTool(server, ctx) {
|
|
2577
2620
|
server.tool(
|
|
2578
2621
|
"yapout_mark_duplicate",
|
|
2579
|
-
`Mark a
|
|
2622
|
+
`Mark a finding as a duplicate of existing Linear work. Use this during any enrichment
|
|
2580
2623
|
flow when you discover the work is already tracked in Linear.
|
|
2581
2624
|
|
|
2582
|
-
The
|
|
2625
|
+
The finding must be in "enriching" or "enriched" status. It will be transitioned to
|
|
2583
2626
|
"failed" with the duplicate reference stored. Nothing is synced to Linear.`,
|
|
2584
2627
|
{
|
|
2585
|
-
|
|
2628
|
+
findingId: z17.string().describe("The yapout finding to archive as a duplicate"),
|
|
2586
2629
|
duplicateOfLinearId: z17.string().describe("The Linear issue identifier it duplicates (e.g. 'ENG-234')"),
|
|
2587
2630
|
reason: z17.string().describe("Brief explanation of why this is a duplicate")
|
|
2588
2631
|
},
|
|
@@ -2600,9 +2643,9 @@ The ticket must be in "enriching" or "enriched" status. It will be transitioned
|
|
|
2600
2643
|
}
|
|
2601
2644
|
try {
|
|
2602
2645
|
const result = await ctx.client.mutation(
|
|
2603
|
-
anyApi2.functions.
|
|
2646
|
+
anyApi2.functions.findings.markDuplicate,
|
|
2604
2647
|
{
|
|
2605
|
-
|
|
2648
|
+
findingId: args.findingId,
|
|
2606
2649
|
duplicateOfLinearId: args.duplicateOfLinearId,
|
|
2607
2650
|
reason: args.reason
|
|
2608
2651
|
}
|
|
@@ -2613,10 +2656,10 @@ The ticket must be in "enriching" or "enriched" status. It will be transitioned
|
|
|
2613
2656
|
type: "text",
|
|
2614
2657
|
text: JSON.stringify(
|
|
2615
2658
|
{
|
|
2616
|
-
|
|
2659
|
+
findingId: result.findingId,
|
|
2617
2660
|
status: "failed",
|
|
2618
2661
|
duplicateOf: result.duplicateOfLinearId,
|
|
2619
|
-
message: `
|
|
2662
|
+
message: `Finding archived as duplicate of ${args.duplicateOfLinearId}. Nothing synced to Linear.`
|
|
2620
2663
|
},
|
|
2621
2664
|
null,
|
|
2622
2665
|
2
|
|
@@ -2712,6 +2755,218 @@ and issue counts so you can ask the user for confirmation before creating a new
|
|
|
2712
2755
|
);
|
|
2713
2756
|
}
|
|
2714
2757
|
|
|
2758
|
+
// src/mcp/tools/start-enrichment.ts
|
|
2759
|
+
import { z as z18 } from "zod";
|
|
2760
|
+
function registerStartEnrichmentTool(server, ctx) {
|
|
2761
|
+
server.tool(
|
|
2762
|
+
"yapout_start_enrichment",
|
|
2763
|
+
`Start a bulk enrichment session. Returns a session ID and the count of findings to process.
|
|
2764
|
+
|
|
2765
|
+
Use this before calling yapout_enrich_next. The session tracks your progress (enriched/skipped/remaining)
|
|
2766
|
+
and maintains filter criteria so you don't re-pass them on every call.
|
|
2767
|
+
|
|
2768
|
+
Optionally filter by tags, capture, or explicit finding IDs.`,
|
|
2769
|
+
{
|
|
2770
|
+
filter: z18.object({
|
|
2771
|
+
tags: z18.array(z18.string()).optional().describe("Only enrich findings with these tags"),
|
|
2772
|
+
captureId: z18.string().optional().describe("Only enrich findings from this capture"),
|
|
2773
|
+
findingIds: z18.array(z18.string()).optional().describe("Only enrich these specific findings")
|
|
2774
|
+
}).optional().describe("Filter criteria. If omitted, all draft findings in the project are included.")
|
|
2775
|
+
},
|
|
2776
|
+
async (args) => {
|
|
2777
|
+
const projectId = ctx.projectId ?? process.env.YAPOUT_PROJECT_ID;
|
|
2778
|
+
if (!projectId) {
|
|
2779
|
+
return {
|
|
2780
|
+
content: [{ type: "text", text: "No project linked. Run yapout_init or yapout link first." }],
|
|
2781
|
+
isError: true
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
try {
|
|
2785
|
+
const allFindings = await ctx.client.query(
|
|
2786
|
+
anyApi2.functions.findings.getProjectFindings,
|
|
2787
|
+
{ projectId }
|
|
2788
|
+
);
|
|
2789
|
+
let drafts = (allFindings ?? []).filter((f) => f.status === "draft");
|
|
2790
|
+
if (args.filter?.tags?.length) {
|
|
2791
|
+
const filterTags = new Set(args.filter.tags);
|
|
2792
|
+
drafts = drafts.filter(
|
|
2793
|
+
(f) => (f.tags ?? []).some((t) => filterTags.has(t))
|
|
2794
|
+
);
|
|
2795
|
+
}
|
|
2796
|
+
if (args.filter?.captureId) {
|
|
2797
|
+
const cid = args.filter.captureId;
|
|
2798
|
+
drafts = drafts.filter((f) => f.captureId === cid);
|
|
2799
|
+
}
|
|
2800
|
+
if (args.filter?.findingIds?.length) {
|
|
2801
|
+
const ids = new Set(args.filter.findingIds);
|
|
2802
|
+
drafts = drafts.filter((f) => ids.has(f._id));
|
|
2803
|
+
}
|
|
2804
|
+
const session = createSession(projectId, drafts.length, args.filter);
|
|
2805
|
+
return {
|
|
2806
|
+
content: [
|
|
2807
|
+
{
|
|
2808
|
+
type: "text",
|
|
2809
|
+
text: JSON.stringify({
|
|
2810
|
+
sessionId: session.sessionId,
|
|
2811
|
+
totalFindings: drafts.length,
|
|
2812
|
+
filter: args.filter ?? null,
|
|
2813
|
+
message: drafts.length > 0 ? `Starting enrichment session: ${drafts.length} finding${drafts.length !== 1 ? "s" : ""} to process.` : "No draft findings match the filter. Nothing to enrich."
|
|
2814
|
+
}, null, 2)
|
|
2815
|
+
}
|
|
2816
|
+
]
|
|
2817
|
+
};
|
|
2818
|
+
} catch (err) {
|
|
2819
|
+
return {
|
|
2820
|
+
content: [{ type: "text", text: `Error starting enrichment: ${err.message}` }],
|
|
2821
|
+
isError: true
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// src/mcp/tools/enrich-next.ts
|
|
2829
|
+
import { z as z19 } from "zod";
|
|
2830
|
+
function registerEnrichNextTool(server, ctx) {
|
|
2831
|
+
server.tool(
|
|
2832
|
+
"yapout_enrich_next",
|
|
2833
|
+
`Get the next finding to enrich in a bulk enrichment session.
|
|
2834
|
+
|
|
2835
|
+
If skip=true, releases the current finding back to draft and moves on.
|
|
2836
|
+
Returns the next unclaimed draft finding matching the session's filter.
|
|
2837
|
+
|
|
2838
|
+
When done=true, all findings have been processed.`,
|
|
2839
|
+
{
|
|
2840
|
+
sessionId: z19.string().describe("Session ID from yapout_start_enrichment"),
|
|
2841
|
+
skip: z19.boolean().optional().describe("If true, skip the current finding (release back to draft)"),
|
|
2842
|
+
skipFindingId: z19.string().optional().describe("The finding ID to skip (must be in 'enriching' status)")
|
|
2843
|
+
},
|
|
2844
|
+
async (args) => {
|
|
2845
|
+
const session = getSession(args.sessionId);
|
|
2846
|
+
if (!session) {
|
|
2847
|
+
return {
|
|
2848
|
+
content: [{ type: "text", text: "Session not found. Start a new one with yapout_start_enrichment." }],
|
|
2849
|
+
isError: true
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
try {
|
|
2853
|
+
if (args.skip && args.skipFindingId) {
|
|
2854
|
+
try {
|
|
2855
|
+
await ctx.client.mutation(
|
|
2856
|
+
anyApi2.functions.localPipeline.releaseEnrichmentClaim,
|
|
2857
|
+
{ findingId: args.skipFindingId }
|
|
2858
|
+
);
|
|
2859
|
+
updateSessionStats(args.sessionId, {
|
|
2860
|
+
skipped: session.stats.skipped + 1
|
|
2861
|
+
});
|
|
2862
|
+
} catch {
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
const allFindings = await ctx.client.query(
|
|
2866
|
+
anyApi2.functions.findings.getProjectFindings,
|
|
2867
|
+
{ projectId: session.projectId }
|
|
2868
|
+
);
|
|
2869
|
+
let drafts = (allFindings ?? []).filter(
|
|
2870
|
+
(f) => f.status === "draft"
|
|
2871
|
+
);
|
|
2872
|
+
if (session.filter?.tags?.length) {
|
|
2873
|
+
const filterTags = new Set(session.filter.tags);
|
|
2874
|
+
drafts = drafts.filter(
|
|
2875
|
+
(f) => (f.tags ?? []).some((t) => filterTags.has(t))
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2878
|
+
if (session.filter?.captureId) {
|
|
2879
|
+
const cid = session.filter.captureId;
|
|
2880
|
+
drafts = drafts.filter((f) => f.captureId === cid);
|
|
2881
|
+
}
|
|
2882
|
+
if (session.filter?.findingIds?.length) {
|
|
2883
|
+
const ids = new Set(session.filter.findingIds);
|
|
2884
|
+
drafts = drafts.filter((f) => ids.has(f._id));
|
|
2885
|
+
}
|
|
2886
|
+
if (drafts.length === 0) {
|
|
2887
|
+
const total = session.stats.enriched + session.stats.skipped;
|
|
2888
|
+
return {
|
|
2889
|
+
content: [
|
|
2890
|
+
{
|
|
2891
|
+
type: "text",
|
|
2892
|
+
text: JSON.stringify({
|
|
2893
|
+
done: true,
|
|
2894
|
+
position: {
|
|
2895
|
+
current: total,
|
|
2896
|
+
total: session.stats.total,
|
|
2897
|
+
enriched: session.stats.enriched,
|
|
2898
|
+
skipped: session.stats.skipped
|
|
2899
|
+
},
|
|
2900
|
+
message: `All done! ${session.stats.enriched} enriched, ${session.stats.skipped} skipped.`
|
|
2901
|
+
}, null, 2)
|
|
2902
|
+
}
|
|
2903
|
+
]
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
const next = drafts[0];
|
|
2907
|
+
const claimResult = await ctx.client.mutation(
|
|
2908
|
+
anyApi2.functions.localPipeline.claimForEnrichment,
|
|
2909
|
+
{ projectId: session.projectId, findingId: next._id }
|
|
2910
|
+
);
|
|
2911
|
+
if (!claimResult) {
|
|
2912
|
+
return {
|
|
2913
|
+
content: [
|
|
2914
|
+
{
|
|
2915
|
+
type: "text",
|
|
2916
|
+
text: JSON.stringify({
|
|
2917
|
+
done: false,
|
|
2918
|
+
message: "Finding was claimed by another session. Call yapout_enrich_next again to get the next one.",
|
|
2919
|
+
position: {
|
|
2920
|
+
current: session.stats.enriched + session.stats.skipped,
|
|
2921
|
+
total: session.stats.total,
|
|
2922
|
+
enriched: session.stats.enriched,
|
|
2923
|
+
skipped: session.stats.skipped
|
|
2924
|
+
}
|
|
2925
|
+
}, null, 2)
|
|
2926
|
+
}
|
|
2927
|
+
]
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
const current = session.stats.enriched + session.stats.skipped + 1;
|
|
2931
|
+
const includeContext = !session.contextDelivered;
|
|
2932
|
+
if (includeContext) markContextDelivered(args.sessionId);
|
|
2933
|
+
const existingTitles = (allFindings ?? []).filter((f) => f._id !== next._id && f.status !== "archived").map((f) => f.title);
|
|
2934
|
+
const response = {
|
|
2935
|
+
finding: {
|
|
2936
|
+
findingId: claimResult.findingId,
|
|
2937
|
+
title: claimResult.title,
|
|
2938
|
+
description: claimResult.description,
|
|
2939
|
+
sourceQuote: claimResult.sourceQuote,
|
|
2940
|
+
type: claimResult.type,
|
|
2941
|
+
priority: claimResult.priority
|
|
2942
|
+
},
|
|
2943
|
+
position: {
|
|
2944
|
+
current,
|
|
2945
|
+
total: session.stats.total,
|
|
2946
|
+
enriched: session.stats.enriched,
|
|
2947
|
+
skipped: session.stats.skipped
|
|
2948
|
+
},
|
|
2949
|
+
existingFindingTitles: existingTitles,
|
|
2950
|
+
done: false
|
|
2951
|
+
};
|
|
2952
|
+
if (includeContext && claimResult.projectContext) {
|
|
2953
|
+
response.projectContext = claimResult.projectContext;
|
|
2954
|
+
}
|
|
2955
|
+
return {
|
|
2956
|
+
content: [
|
|
2957
|
+
{ type: "text", text: JSON.stringify(response, null, 2) }
|
|
2958
|
+
]
|
|
2959
|
+
};
|
|
2960
|
+
} catch (err) {
|
|
2961
|
+
return {
|
|
2962
|
+
content: [{ type: "text", text: `Error getting next finding: ${err.message}` }],
|
|
2963
|
+
isError: true
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2715
2970
|
// src/mcp/server.ts
|
|
2716
2971
|
async function startMcpServer() {
|
|
2717
2972
|
const cwd = process.cwd();
|
|
@@ -2754,8 +3009,8 @@ async function startMcpServer() {
|
|
|
2754
3009
|
registerShipTool(server, ctx);
|
|
2755
3010
|
registerCheckTool(server, ctx);
|
|
2756
3011
|
registerBundleTool(server, ctx);
|
|
2757
|
-
|
|
2758
|
-
|
|
3012
|
+
registerGetUnenrichedFindingsTool(server, ctx);
|
|
3013
|
+
registerGetExistingFindingsTool(server, ctx);
|
|
2759
3014
|
registerSaveEnrichmentTool(server, ctx);
|
|
2760
3015
|
registerSyncToLinearTool(server, ctx);
|
|
2761
3016
|
registerSubmitYapSessionTool(server, ctx);
|
|
@@ -2764,6 +3019,8 @@ async function startMcpServer() {
|
|
|
2764
3019
|
registerSaveProjectEnrichmentTool(server, ctx);
|
|
2765
3020
|
registerMarkDuplicateTool(server, ctx);
|
|
2766
3021
|
registerGetLinearProjectsTool(server, ctx);
|
|
3022
|
+
registerStartEnrichmentTool(server, ctx);
|
|
3023
|
+
registerEnrichNextTool(server, ctx);
|
|
2767
3024
|
const transport = new StdioServerTransport();
|
|
2768
3025
|
await server.connect(transport);
|
|
2769
3026
|
}
|