yapout 0.3.0 → 0.4.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 +241 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -245,23 +245,75 @@ def main():
245
245
  )
246
246
  elif action == "enrich":
247
247
  prompt = (
248
- f'A ticket has been approved and needs enrichment before implementation. '
249
- f'Call yapout_get_unenriched_ticket with ticketId for "{ticket_id}" to fetch the ticket details.\\n\\n'
250
- f'Read the codebase to understand the project structure, then produce:\\n'
251
- f'1. An implementation brief (3-5 sentences, specific files/patterns involved)\\n'
252
- f'2. An enriched description with technical context\\n'
253
- f"3. Clarifying questions (0-5) where the answer can't be inferred\\n"
254
- f'4. Duplicate check \u2014 call yapout_get_existing_tickets and compare\\n'
255
- f'5. Scope assessment (is this too large for one PR?)\\n\\n'
256
- f'Call yapout_save_enrichment with your analysis. '
257
- f'If no questions were generated, also call yapout_sync_to_linear to create the Linear ticket.'
248
+ f'Use yapout to enrich a finding.\\n'
249
+ f'\\n'
250
+ f'Start with filter: {{ findingIds: ["{ticket_id}"] }}\\n'
251
+ f'\\n'
252
+ f'Call yapout_start_enrichment, then yapout_enrich_next to claim it.\\n'
253
+ f'Read the codebase, ask me questions if needed, then call yapout_save_enrichment\\n'
254
+ f'with a clean description, acceptance criteria, and implementation brief.'
258
255
  )
256
+ elif action == "enrich-bulk":
257
+ tags = qs.get("tags", [""])[0]
258
+ finding_ids = qs.get("findingIds", [""])[0]
259
+ lines = [
260
+ "Use yapout to enrich findings for this project. Start a bulk enrichment session and work through each finding one by one.",
261
+ "",
262
+ "For each finding:",
263
+ "1. Call yapout_start_enrichment to begin" + (" with the filter below" if (tags or finding_ids) else ""),
264
+ "2. Call yapout_enrich_next to get the next finding",
265
+ "3. Read relevant code in the repository to understand the finding's context",
266
+ "4. Ask me any clarifying questions (only if genuinely needed)",
267
+ "5. Call yapout_save_enrichment with:",
268
+ " - A clean, specific title",
269
+ " - A description a senior engineer would write",
270
+ " - Concrete acceptance criteria",
271
+ " - An implementation brief (which files, approach, edge cases)",
272
+ '6. If I say "skip", call yapout_enrich_next with skip=true',
273
+ "7. After saving, if compactionHint is true or every 5 findings, run /compact",
274
+ "8. Repeat until done",
275
+ "",
276
+ "Keep the pace steady. Don't over-ask \u2014 use your judgment from the code.",
277
+ ]
278
+ if finding_ids:
279
+ ids = [fid.strip() for fid in finding_ids.split(",") if fid.strip()]
280
+ lines.append("")
281
+ lines.append("These findings are bundled \u2014 enrich them as one cohesive problem.")
282
+ lines.append(f'Start with filter: {{ findingIds: {json.dumps(ids)} }}')
283
+ elif tags:
284
+ tag_list = [t.strip() for t in tags.split(",") if t.strip()]
285
+ lines.append("")
286
+ lines.append(f'Start with filter: {{ tags: {json.dumps(tag_list)} }}')
287
+ prompt = "\\n".join(lines)
259
288
  elif action == "yap":
260
289
  prompt = "Let's have a yap session"
261
290
  if topic:
262
291
  prompt += f" about {topic}"
263
292
  if persona and persona != "tech lead":
264
293
  prompt += f" \u2014 be a {persona}"
294
+ elif action == "compact":
295
+ elif action == "enrich-bundle":
296
+ bundle_id = ticket_id
297
+ prompt = (
298
+ f'Use yapout to enrich a bundle as a single cohesive unit.\\n'
299
+ f'\\n'
300
+ f'1. Call yapout_enrich_bundle with bundleId "{bundle_id}" to claim it and get all findings\\n'
301
+ f'2. Read the codebase to understand the full scope of the bundle\\n'
302
+ f'3. Ask me any clarifying questions about the bundle as a whole (not individual findings)\\n'
303
+ f'4. For each finding, formulate:\\n'
304
+ f' - A refined title\\n'
305
+ f' - A specific description\\n'
306
+ f' - Concrete acceptance criteria\\n'
307
+ f' - An implementation brief\\n'
308
+ f'5. Also formulate bundle-level:\\n'
309
+ f' - An overall description of what the bundle delivers\\n'
310
+ f' - Combined acceptance criteria\\n'
311
+ f' - An architecture/approach brief that covers the full scope\\n'
312
+ f'6. Call yapout_save_bundle_enrichment with ALL enrichment data at once\\n'
313
+ f'\\n'
314
+ f'Treat this as ONE problem, not separate findings. Understand how they relate,\\n'
315
+ f'identify dependencies, and write briefs that reference each other.'
316
+ )
265
317
  elif action == "compact":
266
318
  prompt = "Run yapout_compact to update the project context summary."
267
319
  else:
@@ -2026,7 +2078,7 @@ This tool saves the enrichment, then automatically creates the Linear issue with
2026
2078
  - Clarification Q&A as a branded comment (if any)
2027
2079
  - Implementation brief as attachment metadata
2028
2080
 
2029
- The finding transitions: enriching \u2192 enriched \u2192 synced.`,
2081
+ The finding transitions: enriching \u2192 enriched \u2192 ready.`,
2030
2082
  {
2031
2083
  findingId: z11.string().describe("The finding ID to enrich (from yapout_get_unenriched_finding)"),
2032
2084
  title: z11.string().describe("Refined finding title \u2014 improve it if the original was vague"),
@@ -2086,7 +2138,7 @@ The finding transitions: enriching \u2192 enriched \u2192 synced.`,
2086
2138
  linearIssueId: finding?.linearIssueId ?? null,
2087
2139
  linearIssueUrl: finding?.linearIssueUrl ?? null,
2088
2140
  compactionHint,
2089
- message: finding?.linearIssueUrl ? `Finding enriched and synced to Linear: ${finding.linearIssueUrl}` : "Finding enriched and synced to Linear."
2141
+ message: finding?.linearIssueUrl ? `Finding enriched and ready in Linear: ${finding.linearIssueUrl}` : "Finding enriched and ready in Linear."
2090
2142
  };
2091
2143
  if (args.isOversized && args.suggestedSplit?.length) {
2092
2144
  response.warning = `This finding is oversized. Suggested split: ${args.suggestedSplit.join(", ")}`;
@@ -2967,6 +3019,121 @@ When done=true, all findings have been processed.`,
2967
3019
  );
2968
3020
  }
2969
3021
 
3022
+ // src/mcp/tools/enrich-bundle.ts
3023
+ import { z as z20 } from "zod";
3024
+ function registerEnrichBundleTool(server, ctx) {
3025
+ server.tool(
3026
+ "yapout_enrich_bundle",
3027
+ `Claim an entire bundle for enrichment. Returns ALL findings in the bundle at once
3028
+ with their relationships, source quotes, and project context.
3029
+
3030
+ This is for enriching a bundle as a single cohesive unit \u2014 NOT individual findings.
3031
+ The agent should understand the full scope, ask questions about the bundle as a whole,
3032
+ then call yapout_save_bundle_enrichment with enrichment data for every finding.
3033
+
3034
+ The bundle and all its findings transition to "enriching" status.`,
3035
+ {
3036
+ bundleId: z20.string().describe("The bundle ID to enrich")
3037
+ },
3038
+ async (args) => {
3039
+ try {
3040
+ const result = await ctx.client.mutation(
3041
+ anyApi2.functions.bundles.claimBundleForEnrichment,
3042
+ { bundleId: args.bundleId }
3043
+ );
3044
+ if (!result) {
3045
+ return {
3046
+ content: [{ type: "text", text: "Failed to claim bundle for enrichment." }],
3047
+ isError: true
3048
+ };
3049
+ }
3050
+ return {
3051
+ content: [
3052
+ {
3053
+ type: "text",
3054
+ text: JSON.stringify(result, null, 2)
3055
+ }
3056
+ ]
3057
+ };
3058
+ } catch (err) {
3059
+ return {
3060
+ content: [{ type: "text", text: `Error claiming bundle: ${err.message}` }],
3061
+ isError: true
3062
+ };
3063
+ }
3064
+ }
3065
+ );
3066
+ }
3067
+
3068
+ // src/mcp/tools/save-bundle-enrichment.ts
3069
+ import { z as z21 } from "zod";
3070
+ function registerSaveBundleEnrichmentTool(server, ctx) {
3071
+ server.tool(
3072
+ "yapout_save_bundle_enrichment",
3073
+ `Save enrichment for an entire bundle at once. Call this after you've analyzed all
3074
+ findings in the bundle as a cohesive unit, read the codebase, and asked any questions.
3075
+
3076
+ Provide:
3077
+ - Bundle-level: overall description, combined acceptance criteria, implementation brief
3078
+ - Per-finding: each finding gets its own refined title, description, acceptance criteria, and brief
3079
+
3080
+ The bundle and all findings transition from "enriching" to "enriched".
3081
+ Call yapout_sync_bundle_to_linear afterwards to create the Linear project.`,
3082
+ {
3083
+ bundleId: z21.string().describe("The bundle ID"),
3084
+ title: z21.string().optional().describe("Refined bundle title (optional, keeps existing if omitted)"),
3085
+ enrichedDescription: z21.string().describe("Bundle-level description \u2014 the cohesive story of what this bundle delivers"),
3086
+ acceptanceCriteria: z21.array(z21.string()).describe("Bundle-level acceptance criteria"),
3087
+ implementationBrief: z21.string().describe("Bundle-level implementation brief \u2014 overall approach, architecture decisions, key files"),
3088
+ findings: z21.array(z21.object({
3089
+ findingId: z21.string().describe("Finding ID"),
3090
+ title: z21.string().describe("Refined finding title"),
3091
+ enrichedDescription: z21.string().describe("Finding-specific description"),
3092
+ acceptanceCriteria: z21.array(z21.string()).describe("Finding-specific acceptance criteria"),
3093
+ implementationBrief: z21.string().describe("Finding-specific implementation brief")
3094
+ })).describe("Per-finding enrichment data \u2014 one entry per finding in the bundle")
3095
+ },
3096
+ async (args) => {
3097
+ try {
3098
+ await ctx.client.mutation(
3099
+ anyApi2.functions.bundles.saveBundleEnrichment,
3100
+ {
3101
+ bundleId: args.bundleId,
3102
+ title: args.title,
3103
+ enrichedDescription: args.enrichedDescription,
3104
+ acceptanceCriteria: args.acceptanceCriteria,
3105
+ implementationBrief: args.implementationBrief,
3106
+ findings: args.findings.map((f) => ({
3107
+ findingId: f.findingId,
3108
+ title: f.title,
3109
+ enrichedDescription: f.enrichedDescription,
3110
+ acceptanceCriteria: f.acceptanceCriteria,
3111
+ implementationBrief: f.implementationBrief
3112
+ }))
3113
+ }
3114
+ );
3115
+ return {
3116
+ content: [
3117
+ {
3118
+ type: "text",
3119
+ text: JSON.stringify({
3120
+ bundleId: args.bundleId,
3121
+ findingsEnriched: args.findings.length,
3122
+ message: `Bundle enriched successfully (${args.findings.length} findings). Call yapout_sync_bundle_to_linear to create the Linear project.`
3123
+ }, null, 2)
3124
+ }
3125
+ ]
3126
+ };
3127
+ } catch (err) {
3128
+ return {
3129
+ content: [{ type: "text", text: `Error saving bundle enrichment: ${err.message}` }],
3130
+ isError: true
3131
+ };
3132
+ }
3133
+ }
3134
+ );
3135
+ }
3136
+
2970
3137
  // src/mcp/server.ts
2971
3138
  async function startMcpServer() {
2972
3139
  const cwd = process.cwd();
@@ -3021,6 +3188,8 @@ async function startMcpServer() {
3021
3188
  registerGetLinearProjectsTool(server, ctx);
3022
3189
  registerStartEnrichmentTool(server, ctx);
3023
3190
  registerEnrichNextTool(server, ctx);
3191
+ registerEnrichBundleTool(server, ctx);
3192
+ registerSaveBundleEnrichmentTool(server, ctx);
3024
3193
  const transport = new StdioServerTransport();
3025
3194
  await server.connect(transport);
3026
3195
  }
@@ -4112,7 +4281,7 @@ import { Command as Command15 } from "commander";
4112
4281
  import { spawn as spawn2 } from "child_process";
4113
4282
  import { platform as platform2 } from "os";
4114
4283
  import chalk16 from "chalk";
4115
- var VALID_ACTIONS = ["claim", "enrich", "yap", "compact"];
4284
+ var VALID_ACTIONS = ["claim", "enrich", "enrich-bulk", "enrich-bundle", "yap", "compact"];
4116
4285
  function parseYapoutUri(raw) {
4117
4286
  const url = new URL(raw);
4118
4287
  const action = url.hostname;
@@ -4122,14 +4291,16 @@ function parseYapoutUri(raw) {
4122
4291
  );
4123
4292
  }
4124
4293
  const ticketId = url.pathname.replace(/^\//, "") || void 0;
4125
- if ((action === "claim" || action === "enrich") && !ticketId) {
4126
- throw new Error(`Missing ticket ID in URI: ${raw}`);
4294
+ if ((action === "claim" || action === "enrich" || action === "enrich-bundle") && !ticketId) {
4295
+ throw new Error(`Missing ID in URI: ${raw}`);
4127
4296
  }
4128
4297
  return {
4129
4298
  action,
4130
4299
  ticketId,
4131
4300
  topic: url.searchParams.get("topic") || void 0,
4132
- persona: url.searchParams.get("persona") || void 0
4301
+ persona: url.searchParams.get("persona") || void 0,
4302
+ tags: url.searchParams.get("tags")?.split(",").filter(Boolean) || void 0,
4303
+ findingIds: url.searchParams.get("findingIds")?.split(",").filter(Boolean) || void 0
4133
4304
  };
4134
4305
  }
4135
4306
  function buildPrompt(parsed) {
@@ -4142,18 +4313,62 @@ function buildPrompt(parsed) {
4142
4313
  ].join(" ");
4143
4314
  case "enrich":
4144
4315
  return [
4145
- `A ticket has been approved and needs enrichment before implementation.`,
4146
- `Call yapout_get_unenriched_ticket with ticketId for "${parsed.ticketId}" to fetch the ticket details.`,
4316
+ `Use yapout to enrich a finding.`,
4317
+ ``,
4318
+ `Start with filter: { findingIds: ["${parsed.ticketId}"] }`,
4319
+ ``,
4320
+ `Call yapout_start_enrichment, then yapout_enrich_next to claim it.`,
4321
+ `Read the codebase, ask me questions if needed, then call yapout_save_enrichment`,
4322
+ `with a clean description, acceptance criteria, and implementation brief.`
4323
+ ].join("\n");
4324
+ case "enrich-bulk": {
4325
+ const lines = [
4326
+ "Use yapout to enrich findings for this project. Start a bulk enrichment session and work through each finding one by one.",
4327
+ "",
4328
+ "For each finding:",
4329
+ "1. Call yapout_start_enrichment to begin" + (parsed.tags || parsed.findingIds ? " with the filter below" : ""),
4330
+ "2. Call yapout_enrich_next to get the next finding",
4331
+ "3. Read relevant code in the repository to understand the finding's context",
4332
+ "4. Ask me any clarifying questions (only if genuinely needed)",
4333
+ "5. Call yapout_save_enrichment with:",
4334
+ " - A clean, specific title",
4335
+ " - A description a senior engineer would write",
4336
+ " - Concrete acceptance criteria",
4337
+ " - An implementation brief (which files, approach, edge cases)",
4338
+ '6. If I say "skip", call yapout_enrich_next with skip=true',
4339
+ "7. After saving, if compactionHint is true or every 5 findings, run /compact",
4340
+ "8. Repeat until done",
4341
+ "",
4342
+ "Keep the pace steady. Don't over-ask \u2014 use your judgment from the code."
4343
+ ];
4344
+ if (parsed.findingIds?.length) {
4345
+ lines.push("", `These findings are bundled \u2014 enrich them as one cohesive problem.`);
4346
+ lines.push(`Start with filter: { findingIds: ${JSON.stringify(parsed.findingIds)} }`);
4347
+ } else if (parsed.tags?.length) {
4348
+ lines.push("", `Start with filter: { tags: ${JSON.stringify(parsed.tags)} }`);
4349
+ }
4350
+ return lines.join("\n");
4351
+ }
4352
+ case "enrich-bundle":
4353
+ return [
4354
+ `Use yapout to enrich a bundle as a single cohesive unit.`,
4147
4355
  ``,
4148
- `Read the codebase to understand the project structure, then produce:`,
4149
- `1. An implementation brief (3-5 sentences, specific files/patterns involved)`,
4150
- `2. An enriched description with technical context`,
4151
- `3. Clarifying questions (0-5) where the answer can't be inferred`,
4152
- `4. Duplicate check \u2014 call yapout_get_existing_tickets and compare`,
4153
- `5. Scope assessment (is this too large for one PR?)`,
4356
+ `1. Call yapout_enrich_bundle with bundleId "${parsed.ticketId}" to claim it and get all findings`,
4357
+ `2. Read the codebase to understand the full scope of the bundle`,
4358
+ `3. Ask me any clarifying questions about the bundle as a whole (not individual findings)`,
4359
+ `4. For each finding, formulate:`,
4360
+ ` - A refined title`,
4361
+ ` - A specific description`,
4362
+ ` - Concrete acceptance criteria`,
4363
+ ` - An implementation brief`,
4364
+ `5. Also formulate bundle-level:`,
4365
+ ` - An overall description of what the bundle delivers`,
4366
+ ` - Combined acceptance criteria`,
4367
+ ` - An architecture/approach brief that covers the full scope`,
4368
+ `6. Call yapout_save_bundle_enrichment with ALL enrichment data at once`,
4154
4369
  ``,
4155
- `Call yapout_save_enrichment with your analysis.`,
4156
- `If no questions were generated, also call yapout_sync_to_linear to create the Linear ticket.`
4370
+ `Treat this as ONE problem, not separate findings. Understand how they relate,`,
4371
+ `identify dependencies, and write briefs that reference each other.`
4157
4372
  ].join("\n");
4158
4373
  case "yap": {
4159
4374
  const parts = ["Let's have a yap session"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yapout",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "yapout CLI — link local repos, authenticate, and manage projects",
5
5
  "type": "module",
6
6
  "bin": {