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.
Files changed (2) hide show
  1. package/dist/index.js +436 -179
  2. 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 tickets.
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 tickets ready for local implementation. Only returns tickets in backlog/unstarted Linear status.",
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 tickets too (default: false)")
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.tickets.getLocalQueuedTickets,
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.linearTicketId).filter((id) => !!id);
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.linearTicketId) return true;
1082
- const type = statusMap.get(t.linearTicketId);
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 ticket",
1117
+ "Fetch the full implementation context for a finding",
1118
1118
  {
1119
- ticketId: z4.string().describe("The ticket ID to get the brief for")
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.tickets.getTicketBrief,
1124
- { ticketId: args.ticketId }
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: "Ticket not found or you don't have access."
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 ticket = data.ticket;
1162
+ const finding = data.finding;
1163
1163
  const sections = [
1164
- `# ${ticket.title}`,
1164
+ `# ${finding.title}`,
1165
1165
  "",
1166
- `**Priority:** ${ticket.priority} | **Type:** ${ticket.type}`
1166
+ `**Priority:** ${finding.priority} | **Type:** ${finding.type}`
1167
1167
  ];
1168
- if (ticket.linearUrl) {
1169
- sections.push(`**Linear:** ${ticket.linearUrl}`);
1168
+ if (finding.linearIssueUrl) {
1169
+ sections.push(`**Linear:** ${finding.linearIssueUrl}`);
1170
1170
  }
1171
- sections.push("", "## Description", "", ticket.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 ticket for local implementation. Creates a branch (or worktree), writes the brief, and updates status.",
1205
+ "Claim a finding for local implementation. Creates a branch (or worktree), writes the brief, and updates status.",
1206
1206
  {
1207
- ticketId: z5.string().describe("The ticket ID to claim"),
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.tickets.getTicketBrief,
1224
- { ticketId: args.ticketId }
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: "Ticket not found or you don't have access."
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 ticket = briefData.ticket;
1238
- const linearTicketId = briefData.linearTicketId;
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(ticket.title);
1242
- const branchName = linearTicketId ? `${prefix}/${linearTicketId.toLowerCase()}-${slug}` : `${prefix}/${slug}`;
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.tickets.claimTicketLocal,
1245
- { ticketId: args.ticketId, branchName }
1244
+ anyApi2.functions.findings.claimFindingLocal,
1245
+ { findingId: args.findingId, branchName }
1246
1246
  );
1247
- if (linearTicketId && ctx.projectId) {
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: linearTicketId,
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.ticketId,
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 ticket: ${ticket.title}`
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, ticket) {
1459
+ function buildCommitMessage(message, template, finding) {
1460
1460
  if (message) return message;
1461
1461
  if (template) {
1462
- return template.replace(/\{\{ticket\.type\}\}/g, ticket.type).replace(/\{\{ticket\.title\}\}/g, ticket.title).replace(/\{\{ticket\.linearTicketId\}\}/g, ticket.linearTicketId ?? "draft").replace(/\{\{ticket\.id\}\}/g, ticket.id ?? "").replace(/\{\{ticket\.priority\}\}/g, ticket.priority ?? "medium");
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 = ticket.type === "bug" ? "fix" : "feat";
1465
- const ref = ticket.linearTicketId ? ` (${ticket.linearTicketId})` : "";
1466
- return `${prefix}(${ticket.type}): ${ticket.title}${ref}`;
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 ticket as done. Run yapout_check first if post-flight checks are configured.",
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 ticketTitle = branch;
1499
- let ticketType = "feature";
1500
- let ticketLinearId;
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) ticketTitle = titleMatch[1];
1506
+ if (titleMatch) findingTitle = titleMatch[1];
1507
1507
  const typeMatch = brief.match(/\*\*Type:\*\* (\w+)/);
1508
- if (typeMatch) ticketType = typeMatch[1];
1508
+ if (typeMatch) findingType = typeMatch[1];
1509
1509
  const linearMatch = brief.match(/\*\*Linear:\*\* .+\/([A-Z]+-\d+)\//);
1510
- if (linearMatch) ticketLinearId = linearMatch[1];
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: ticketTitle,
1516
- type: ticketType,
1517
- linearTicketId: ticketLinearId
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
- ticketTitle,
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
- ticketTitle,
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 (ticketLinearId && ctx.projectId) {
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: ticketLinearId,
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: ticketLinearId,
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 ticket to the current ticket's bundle so they ship as one PR",
1781
+ "Add a finding to the current finding's bundle so they ship as one PR",
1782
1782
  {
1783
- ticketId: z9.string().describe("Ticket ID to add to the bundle"),
1784
- withTicket: z9.string().describe("Lead ticket ID (currently being worked on)")
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.tickets.bundleTickets,
1789
- { leadTicketId: args.withTicket, joiningTicketId: args.ticketId }
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.tickets.getBundledBrief,
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.tickets.length} tickets`,
1809
+ `# Bundle: ${bundledBrief.findings.length} findings`,
1810
1810
  ""
1811
1811
  ];
1812
- for (const t of bundledBrief.tickets) {
1813
- sections.push(`## ${t.linearTicketId ?? t.ticketId}: ${t.title}`);
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.linearUrl) sections.push(`**Linear:** ${t.linearUrl}`);
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
- tickets: bundledBrief.tickets.map((t) => ({
1841
- ticketId: t.ticketId,
1840
+ findings: bundledBrief.findings.map((t) => ({
1841
+ findingId: t.findingId,
1842
1842
  title: t.title
1843
1843
  })),
1844
1844
  combinedBrief,
1845
- message: `${bundledBrief.tickets.length} tickets bundled. Brief updated.`
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-tickets.ts
1857
+ // src/mcp/tools/get-unenriched-findings.ts
1858
1858
  import { z as z10 } from "zod";
1859
- function registerGetUnenrichedTicketsTool(server, ctx) {
1859
+ function registerGetUnenrichedFindingsTool(server, ctx) {
1860
1860
  server.tool(
1861
- "yapout_get_unenriched_ticket",
1862
- `Start enriching a ticket. Returns the next draft ticket (or a specific one by ID) with full context including the original transcript quote, project context, and existing ticket titles for duplicate detection.
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 ticket is locked to "enriching" status \u2014 no other agent can enrich it concurrently.
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 existingTickets list
1869
- 3. If the decision is ambiguous, ask the developer clarifying questions in conversation
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
- ticketId: z10.string().optional().describe("Specific ticket ID to enrich. If omitted, returns the highest priority draft ticket.")
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.ticketId ? { ticketId: args.ticketId } : {}
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 tickets need enrichment. All tickets are either already enriched or in progress."
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 ticket for enrichment: ${err.message}`
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-tickets.ts
1929
- function registerGetExistingTicketsTool(server, ctx) {
1928
+ // src/mcp/tools/get-existing-findings.ts
1929
+ function registerGetExistingFindingsTool(server, ctx) {
1930
1930
  server.tool(
1931
- "yapout_get_existing_tickets",
1932
- "Fetch all ticket titles in the project for duplicate detection during enrichment",
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 tickets = await ctx.client.query(
1949
- anyApi2.functions.localPipeline.getExistingTicketTitles,
1948
+ const findings = await ctx.client.query(
1949
+ anyApi2.functions.localPipeline.getExistingFindingTitles,
1950
1950
  { projectId }
1951
1951
  );
1952
- if (!tickets || tickets.length === 0) {
1952
+ if (!findings || findings.length === 0) {
1953
1953
  return {
1954
1954
  content: [
1955
1955
  {
1956
1956
  type: "text",
1957
- text: "No existing tickets in this project."
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(tickets, null, 2)
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 tickets: ${err.message}`
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 ticket must have been claimed via yapout_get_unenriched_ticket first (status must be "enriching").
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 ticket transitions: enriching \u2192 enriched \u2192 synced.`,
2029
+ The finding transitions: enriching \u2192 enriched \u2192 synced.`,
2000
2030
  {
2001
- ticketId: z11.string().describe("The ticket ID to enrich (from yapout_get_unenriched_ticket)"),
2002
- title: z11.string().describe("Refined ticket title \u2014 improve it if the original was vague"),
2003
- cleanDescription: z11.string().describe("Human-readable summary for the Linear issue body. Write the kind of ticket a senior engineer would write."),
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 ticket is too large for a single PR"),
2013
- suggestedSplit: z11.array(z11.string()).optional().describe("If oversized: suggested sub-ticket titles for breaking it down"),
2014
- level: z11.enum(["project", "issue"]).optional().describe("Override the decision's level if enrichment reveals it should be reclassified"),
2015
- nature: z11.enum(["implementable", "operational", "spike"]).optional().describe("Override the decision's nature if enrichment reveals it should be reclassified")
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
- ticketId: args.ticketId,
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.syncTicketToLinearLocal,
2036
- { ticketId: args.ticketId }
2066
+ anyApi2.functions.localPipeline.syncFindingToLinearLocal,
2067
+ { findingId: args.findingId }
2037
2068
  );
2038
- const ticket = await ctx.client.query(
2039
- anyApi2.functions.tickets.getTicket,
2040
- { ticketId: args.ticketId }
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
- ticketId: args.ticketId,
2044
- linearIssueId: ticket?.linearTicketId ?? null,
2045
- linearUrl: ticket?.linearUrl ?? null,
2046
- message: ticket?.linearUrl ? `Ticket enriched and synced to Linear: ${ticket.linearUrl}` : "Ticket enriched and synced to Linear."
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 ticket is oversized. Suggested split: ${args.suggestedSplit.join(", ")}`;
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 ticket creation for an enriched ticket. The sync runs server-side (encrypted Linear token in Convex).",
2122
+ "Trigger Linear issue creation for an enriched finding. The sync runs server-side (encrypted Linear token in Convex).",
2080
2123
  {
2081
- ticketId: z12.string().describe("The ticket ID to sync to Linear")
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.syncTicketToLinearLocal,
2087
- { ticketId: args.ticketId }
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
- ticketId: args.ticketId,
2097
- message: "Ticket synced to Linear successfully. It will now appear in the work queue for implementation."
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 decision extraction",
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 transcriptId = await ctx.client.mutation(
2144
- anyApi2.functions.transcripts.createFromYapSession,
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
- transcriptId,
2158
- message: "Yap session submitted. Decisions will appear for review shortly."
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 decisions and tickets \u2014 implementation happens later through the normal ticket pipeline. If the user asks you to "just do it" or "make that change now," push back: "That's what the tickets 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.
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 decision. For each decision, track:
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 ticket (enrichment)
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 decisions. This is normal \u2014 real conversations are not linear. Your job is to follow the thread and keep track of the state of each decision regardless of conversation order.
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 decision. For each one, ask yourself:
2252
- - Could a developer read this ticket and start working without asking questions?
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 decision as NOT enriched \u2014 it will go through the async enrichment pipeline later. Be honest about what you know vs. what you're guessing.
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 decision, you must make an honest call: is this enriched or not?
2304
+ For each finding, you must make an honest call: is this enriched or not?
2262
2305
 
2263
- Mark a decision as ENRICHED (isEnriched: true) when:
2264
- - The conversation covered it thoroughly enough to produce a clear ticket
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 decision as NOT ENRICHED (isEnriched: false or omitted) when:
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 tickets get enriched properly later. Falsely marking something as enriched skips that process and produces bad tickets.
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 decision, provide:
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 decisions (isEnriched: true), also include:
2302
- - enrichedDescription: clean, final description \u2014 write the kind of ticket a senior engineer would write
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 decision
2348
+ - clarifications: relevant Q&A from the conversation that shaped the finding
2306
2349
 
2307
- For PROJECT-level decisions, also include:
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 decisions
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
- - decisions: the array (projects with children, standalone issues)
2366
+ - findings: the array (projects with children, standalone issues)
2324
2367
 
2325
- This creates decisions (as converted \u2014 your participation was the approval), tickets (enriched or draft based on your assessment), and project decomposition \u2014 all in one call. Enriched tickets appear in the work queue ready for the user to sync to Linear.
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 decisions.
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("Decision category"),
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 decision (0-1)"),
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 ticket a developer could pick up.
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 ticket? true = enriched (ready for Linear), false/omitted = draft (needs async enrichment)"
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 decision"),
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 decisions 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-decision. Creates transcript, decisions (as converted), and tickets (enriched or draft). Enriched tickets are ready for the user to sync to Linear from the UI. Draft tickets need further enrichment via the async pipeline.",
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
- decisions: z15.array(
2426
+ findings: z15.array(
2384
2427
  z15.object({
2385
- title: z15.string().describe("Decision title"),
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("Decision category (do not use spike \u2014 use nature instead)"),
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 decision (0-1)"),
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
- sourceDecisionId: z15.string().optional().describe("ID of an existing decision this scopes (e.g., scoping a spike)"),
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("Decisions from the conversation. Projects include children inline.")
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.transcripts.extractFromYapSession,
2464
+ anyApi2.functions.captures.extractFromYapSession,
2422
2465
  {
2423
2466
  projectId: ctx.projectId,
2424
2467
  title: args.sessionTitle,
2425
2468
  transcript: args.sessionTranscript,
2426
- decisions: args.decisions
2469
+ findings: args.findings
2427
2470
  }
2428
2471
  );
2429
2472
  let enrichedCount = 0;
2430
2473
  let draftCount = 0;
2431
- let totalTickets = 0;
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
- totalTickets++;
2436
- if (child.ticketStatus === "enriched") enrichedCount++;
2478
+ totalFindings++;
2479
+ if (child.findingStatus === "enriched") enrichedCount++;
2437
2480
  else draftCount++;
2438
2481
  }
2439
2482
  } else {
2440
- totalTickets++;
2441
- if (item.ticketStatus === "enriched") enrichedCount++;
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
- transcriptId: result.transcriptId,
2497
+ captureId: result.captureId,
2455
2498
  items: result.items,
2456
2499
  summary: {
2457
- totalTickets,
2500
+ totalFindings,
2458
2501
  enriched: enrichedCount,
2459
2502
  needsEnrichment: draftCount
2460
2503
  },
2461
- message: `Created ${totalTickets} ticket${totalTickets === 1 ? "" : "s"}: ${parts.join(", ")}. Review in the yapout work queue.`
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 decision into implementable child issues.
2532
+ `Save the decomposition of a project-level finding into implementable child issues.
2490
2533
 
2491
- Call this after enriching a ticket with level: "project". The agent should have:
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 decisions and tickets in yapout, transitions the original
2497
- ticket to "decomposed", and returns the child ticket IDs for subsequent Linear sync.
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
- ticketId: z16.string().describe("The project-level ticket being decomposed (from yapout_get_unenriched_ticket)"),
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
- ticketId: args.ticketId,
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
- ticketId: result.ticketId,
2590
+ findingId: result.findingId,
2548
2591
  status: "decomposed",
2549
- childTicketIds: result.childTicketIds,
2592
+ childFindingIds: result.childFindingIds,
2550
2593
  suggestedOrder: result.suggestedOrder,
2551
- message: `Project decomposed into ${result.childTicketIds.length} child issues. Original ticket marked as decomposed. Child tickets are in draft status \u2014 enrich and sync each one individually, or approve them for the user to review.`
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 ticket as a duplicate of existing Linear work. Use this during any enrichment
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 ticket must be in "enriching" or "enriched" status. It will be transitioned to
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
- ticketId: z17.string().describe("The yapout ticket to archive as a duplicate"),
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.tickets.markDuplicate,
2646
+ anyApi2.functions.findings.markDuplicate,
2604
2647
  {
2605
- ticketId: args.ticketId,
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
- ticketId: result.ticketId,
2659
+ findingId: result.findingId,
2617
2660
  status: "failed",
2618
2661
  duplicateOf: result.duplicateOfLinearId,
2619
- message: `Ticket archived as duplicate of ${args.duplicateOfLinearId}. Nothing synced to Linear.`
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
- registerGetUnenrichedTicketsTool(server, ctx);
2758
- registerGetExistingTicketsTool(server, ctx);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yapout",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "yapout CLI — link local repos, authenticate, and manage projects",
5
5
  "type": "module",
6
6
  "bin": {