yapout 0.16.0 → 0.18.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 +1026 -1006
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command17 } from "commander";
4
+ import { Command as Command18 } from "commander";
5
5
 
6
6
  // src/commands/login.ts
7
7
  import { Command as Command2 } from "commander";
@@ -236,6 +236,20 @@ function readActiveExecution(cwd) {
236
236
  return null;
237
237
  }
238
238
  }
239
+ function writeActiveExecution(cwd, active) {
240
+ const yapoutDir = join(cwd, ".yapout");
241
+ if (!existsSync(yapoutDir)) {
242
+ mkdirSync(yapoutDir, { recursive: true });
243
+ }
244
+ writeFileSync(
245
+ join(yapoutDir, "active-execution.json"),
246
+ JSON.stringify(active, null, 2) + "\n"
247
+ );
248
+ }
249
+ function clearActiveExecution(cwd) {
250
+ const path = join(cwd, ".yapout", "active-execution.json");
251
+ if (existsSync(path)) unlinkSync(path);
252
+ }
239
253
  var WATCH_DEFAULTS = {
240
254
  auto_enrich: true,
241
255
  auto_implement: true,
@@ -358,7 +372,7 @@ function requireAuth() {
358
372
  }
359
373
 
360
374
  // src/commands/serve.ts
361
- var CLI_VERSION = "0.16.0";
375
+ var CLI_VERSION = "0.18.0";
362
376
  var DEFAULT_PORT = 7777;
363
377
  var PORT_RANGE = 10;
364
378
  var HEARTBEAT_MS = 12e4;
@@ -407,7 +421,7 @@ function startServer(payload) {
407
421
  res.writeHead(404, headers);
408
422
  res.end();
409
423
  });
410
- return new Promise((resolve13, reject) => {
424
+ return new Promise((resolve16, reject) => {
411
425
  let attempt = 0;
412
426
  const tryListen = () => {
413
427
  const port = DEFAULT_PORT + attempt;
@@ -423,7 +437,7 @@ function startServer(payload) {
423
437
  server.once("error", onError);
424
438
  server.listen(port, "127.0.0.1", () => {
425
439
  server.removeListener("error", onError);
426
- resolve13({
440
+ resolve16({
427
441
  port,
428
442
  close: () => server.close()
429
443
  });
@@ -580,7 +594,7 @@ var serveCommand = new Command("serve").description(
580
594
  });
581
595
 
582
596
  // src/commands/login.ts
583
- var CLI_VERSION2 = "0.16.0";
597
+ var CLI_VERSION2 = "0.18.0";
584
598
  function safeReturnTo(raw) {
585
599
  if (!raw) return null;
586
600
  try {
@@ -592,7 +606,7 @@ function safeReturnTo(raw) {
592
606
  }
593
607
  }
594
608
  function startCallbackServer() {
595
- return new Promise((resolve13) => {
609
+ return new Promise((resolve16) => {
596
610
  let resolveData;
597
611
  let rejectData;
598
612
  const dataPromise = new Promise((res, rej) => {
@@ -641,7 +655,7 @@ function startCallbackServer() {
641
655
  server.listen(0, () => {
642
656
  const address = server.address();
643
657
  const port = typeof address === "object" && address ? address.port : 0;
644
- resolve13({ port, data: dataPromise });
658
+ resolve16({ port, data: dataPromise });
645
659
  });
646
660
  setTimeout(() => {
647
661
  server.close();
@@ -1511,232 +1525,8 @@ function registerUpdateContextTool(server, ctx) {
1511
1525
  );
1512
1526
  }
1513
1527
 
1514
- // src/mcp/tools/queue.ts
1515
- import { z as z4 } from "zod";
1516
- function formatFinding(f, indent, done) {
1517
- const ref = f.linearIssueId ? ` ${f.linearIssueId}` : "";
1518
- const prefix = done ? "\u2713 " : "";
1519
- return `${indent}${prefix}${f.title} ${f.type}\xB7${f.priority}${ref}`;
1520
- }
1521
- function formatWorkItem(item, done) {
1522
- const lines = [];
1523
- if (item.kind === "bundle") {
1524
- const count = item.findings.length;
1525
- const priority = item.findings.length > 0 ? item.findings.map((f) => f.priority).sort((a, b) => {
1526
- const order = { urgent: 0, high: 1, medium: 2, low: 3 };
1527
- return (order[a] ?? 3) - (order[b] ?? 3);
1528
- })[0] : "medium";
1529
- const prefix = done ? "\u2713 " : "";
1530
- const prRef = done && item.pr?.githubPrNumber ? ` PR #${item.pr.githubPrNumber}` : "";
1531
- lines.push(` ${prefix}\u{1F4E6} ${item.title} (${count} findings) \u2014 ${priority}${prRef}`);
1532
- for (const f of item.findings) {
1533
- lines.push(formatFinding(f, " ", done));
1534
- }
1535
- } else {
1536
- const f = item.findings[0];
1537
- if (f) {
1538
- const prRef = done && item.pr?.githubPrNumber ? ` PR #${item.pr.githubPrNumber}` : "";
1539
- const prefix = done ? "\u2713 " : "";
1540
- const ref = f.linearIssueId ? ` ${f.linearIssueId}` : "";
1541
- lines.push(` ${prefix}${item.title} ${f.type}\xB7${f.priority}${ref}${prRef}`);
1542
- }
1543
- }
1544
- return lines.join("\n");
1545
- }
1546
- function registerQueueTool(server, ctx) {
1547
- server.tool(
1548
- "yapout_queue",
1549
- "List work items (bundles and standalone findings) ready for local implementation, plus active and done items.",
1550
- {
1551
- includeIds: z4.boolean().optional().describe("Include work item IDs in output (default: false)")
1552
- },
1553
- withScopeCheck(ctx, "yapout_queue", async (args) => {
1554
- if (!ctx.projectId) {
1555
- return {
1556
- content: [
1557
- {
1558
- type: "text",
1559
- text: "No project linked. Run yapout_init or yapout link first."
1560
- }
1561
- ],
1562
- isError: true
1563
- };
1564
- }
1565
- const data = await ctx.client.query(
1566
- anyApi5.functions.workQueue.getWorkQueue,
1567
- { projectId: ctx.projectId }
1568
- );
1569
- if (!data) {
1570
- return {
1571
- content: [
1572
- { type: "text", text: "Could not fetch work queue." }
1573
- ],
1574
- isError: true
1575
- };
1576
- }
1577
- const sections = [];
1578
- sections.push(`Ready (${data.ready.length}):`);
1579
- if (data.ready.length === 0) {
1580
- sections.push(" (none)");
1581
- } else {
1582
- for (const item of data.ready) {
1583
- const line = formatWorkItem(item);
1584
- if (args.includeIds) {
1585
- sections.push(`${line} [${item.id}]`);
1586
- } else {
1587
- sections.push(line);
1588
- }
1589
- }
1590
- }
1591
- sections.push("");
1592
- sections.push(`Active (${data.active.length}):`);
1593
- if (data.active.length === 0) {
1594
- sections.push(" (none)");
1595
- } else {
1596
- for (const item of data.active) {
1597
- const statusTag = item.status === "failed" ? " \u274C FAILED" : item.status === "review" ? " \u{1F50D} review" : "";
1598
- const line = formatWorkItem(item);
1599
- if (args.includeIds) {
1600
- sections.push(`${line}${statusTag} [${item.id}]`);
1601
- } else {
1602
- sections.push(`${line}${statusTag}`);
1603
- }
1604
- }
1605
- }
1606
- sections.push("");
1607
- sections.push(`Done (${data.done.length}):`);
1608
- if (data.done.length === 0) {
1609
- sections.push(" (none)");
1610
- } else {
1611
- for (const item of data.done) {
1612
- const line = formatWorkItem(item, true);
1613
- if (args.includeIds) {
1614
- sections.push(`${line} [${item.id}]`);
1615
- } else {
1616
- sections.push(line);
1617
- }
1618
- }
1619
- }
1620
- if (data.agentStatus.isActive) {
1621
- sections.push("");
1622
- sections.push(`Agent: ${data.agentStatus.agentCount} active, ${data.agentStatus.worktreeCount} worktrees`);
1623
- }
1624
- const structured = {
1625
- ready: data.ready.map((item) => ({
1626
- workItemId: item.id,
1627
- kind: item.kind,
1628
- title: item.title,
1629
- findings: item.findings.map((f) => ({
1630
- id: f.id,
1631
- title: f.title,
1632
- type: f.type,
1633
- priority: f.priority,
1634
- linearIssueId: f.linearIssueId
1635
- }))
1636
- })),
1637
- activeCount: data.active.length,
1638
- doneCount: data.done.length
1639
- };
1640
- return {
1641
- content: [
1642
- { type: "text", text: sections.join("\n") },
1643
- { type: "text", text: "\n---\nStructured data:\n" + JSON.stringify(structured, null, 2) }
1644
- ]
1645
- };
1646
- })
1647
- );
1648
- }
1649
-
1650
- // src/mcp/tools/get-brief.ts
1651
- import { z as z5 } from "zod";
1652
- function registerGetBriefTool(server, ctx) {
1653
- server.tool(
1654
- "yapout_get_brief",
1655
- "Fetch the full implementation context for a work item (finding or bundle)",
1656
- {
1657
- findingId: z5.string().optional().describe("The finding ID to get the brief for (deprecated: use workItemId)"),
1658
- workItemId: z5.string().optional().describe("The work item ID (finding or bundle) to get the brief for")
1659
- },
1660
- withScopeCheck(ctx, "yapout_get_brief", async (args) => {
1661
- const itemId = args.workItemId || args.findingId;
1662
- if (!itemId) {
1663
- return {
1664
- content: [
1665
- {
1666
- type: "text",
1667
- text: "Must provide workItemId or findingId."
1668
- }
1669
- ],
1670
- isError: true
1671
- };
1672
- }
1673
- try {
1674
- const data = await ctx.client.query(
1675
- anyApi5.functions.findings.getFindingBrief,
1676
- { findingId: itemId }
1677
- );
1678
- if (data) {
1679
- return {
1680
- content: [
1681
- { type: "text", text: JSON.stringify(data, null, 2) }
1682
- ]
1683
- };
1684
- }
1685
- } catch {
1686
- }
1687
- try {
1688
- const bundle = await ctx.client.query(
1689
- anyApi5.functions.bundles.getBundle,
1690
- { bundleId: itemId }
1691
- );
1692
- if (bundle) {
1693
- const result = {
1694
- kind: "bundle",
1695
- bundle: {
1696
- id: bundle._id,
1697
- title: bundle.title,
1698
- description: bundle.description,
1699
- enrichedDescription: bundle.enrichedDescription,
1700
- acceptanceCriteria: bundle.acceptanceCriteria,
1701
- implementationBrief: bundle.implementationBrief
1702
- },
1703
- findings: bundle.findings.map((f) => ({
1704
- id: f._id,
1705
- title: f.title,
1706
- description: f.description,
1707
- priority: f.priority,
1708
- type: f.type,
1709
- linearIssueId: f.linearIssueId,
1710
- linearIssueUrl: f.linearIssueUrl,
1711
- enrichedDescription: f.enrichedDescription,
1712
- acceptanceCriteria: f.acceptanceCriteria,
1713
- implementationBrief: f.implementationBrief,
1714
- dependsOn: f.dependsOn
1715
- }))
1716
- };
1717
- return {
1718
- content: [
1719
- { type: "text", text: JSON.stringify(result, null, 2) }
1720
- ]
1721
- };
1722
- }
1723
- } catch {
1724
- }
1725
- return {
1726
- content: [
1727
- {
1728
- type: "text",
1729
- text: "Work item not found or you don't have access. Provide a valid finding ID or bundle ID."
1730
- }
1731
- ],
1732
- isError: true
1733
- };
1734
- })
1735
- );
1736
- }
1737
-
1738
- // src/mcp/tools/claim.ts
1739
- import { z as z6 } from "zod";
1528
+ // src/mcp/tools/dev-loop.ts
1529
+ import { resolve as resolve6 } from "path";
1740
1530
 
1741
1531
  // src/lib/worktree.ts
1742
1532
  import { execSync as execSync2 } from "child_process";
@@ -1801,75 +1591,540 @@ function removeWorktree(cwd, wtPath) {
1801
1591
  }
1802
1592
  }
1803
1593
 
1804
- // src/mcp/tools/claim.ts
1805
- import { join as join8 } from "path";
1806
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
1807
-
1808
- // src/mcp/tools/session-progress.ts
1809
- async function reportSessionProgress(ctx, pipelineRunId, message, details) {
1810
- try {
1811
- const run = await ctx.client.query(
1812
- anyApi5.functions.pipelineRuns.getPipelineRun,
1813
- { pipelineRunId }
1594
+ // src/mcp/tools/dev-loop.ts
1595
+ var queueTool = defineTool({
1596
+ name: "yapout_queue",
1597
+ description: "List Directives for the repo's bound Resource (data-model-redesign-2026-05-01). Buckets queued / in_progress / review by default; pass includeAll to get drafted / vetted / done / cancelled too.",
1598
+ inputSchema: {
1599
+ includeAll: z.boolean().optional().describe(
1600
+ "Include drafted/vetted/done/cancelled directives (default: false \u2014 only queued/in_progress/review)"
1601
+ ),
1602
+ limit: z.number().optional().describe("Max directives to fetch (default: 100)")
1603
+ },
1604
+ handler: async (ctx, args) => {
1605
+ const repoRoot = resolveRepoRoot(ctx.cwd);
1606
+ const cfg = readResourceConfig(repoRoot);
1607
+ if (!cfg) {
1608
+ return {
1609
+ content: [
1610
+ {
1611
+ type: "text",
1612
+ text: "No `.yapout/config.json` found. Run `yapout init` in this repo first."
1613
+ }
1614
+ ],
1615
+ isError: true
1616
+ };
1617
+ }
1618
+ const result = await ctx.client.query(
1619
+ anyApi5.functions.dashboardNewModel.listDirectivesForResource,
1620
+ {
1621
+ resourceId: cfg.resourceId,
1622
+ limit: args.limit ?? 100
1623
+ }
1814
1624
  );
1815
- if (!run?.sourceId) return;
1816
- await ctx.client.mutation(anyApi5.functions.comments.createComment, {
1817
- findingId: run.sourceId,
1818
- body: message,
1819
- isSystem: true,
1820
- systemEvent: "session_progress",
1821
- systemEventDetails: details,
1822
- sessionId: ctx.session.id ?? void 0
1823
- });
1824
- } catch {
1825
- }
1826
- }
1827
-
1828
- // src/mcp/tools/claim.ts
1829
- function readBranchPrefix(cwd) {
1830
- try {
1831
- const config = readYapoutConfig(cwd);
1832
- return config.branch_prefix || "feat";
1833
- } catch {
1834
- return "feat";
1835
- }
1836
- }
1837
- function slugify(text) {
1838
- return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
1839
- }
1840
- function formatFindingBrief(data) {
1841
- const finding = data.finding;
1842
- const sections = [
1843
- `# ${finding.title}`,
1844
- "",
1845
- `**Priority:** ${finding.priority} | **Type:** ${finding.type}`
1846
- ];
1847
- if (finding.linearIssueUrl) {
1848
- sections.push(`**Linear:** ${finding.linearIssueUrl}`);
1849
- }
1850
- sections.push("", "## Description", "", finding.description);
1851
- if (data.enrichedDescription) {
1852
- sections.push("", "## Enriched Description", "", data.enrichedDescription);
1853
- }
1854
- if (data.implementationBrief) {
1855
- sections.push("", "## Implementation Brief", "", data.implementationBrief);
1856
- }
1857
- const qa = data.enrichmentQA;
1858
- if (qa && qa.length > 0) {
1859
- sections.push("", "## Q&A");
1860
- for (const item of qa) {
1861
- sections.push("", `**Q:** ${item.question}`);
1862
- sections.push(`**A:** ${item.answer || "(no answer yet)"}`);
1625
+ if (!result) {
1626
+ return {
1627
+ content: [
1628
+ {
1629
+ type: "text",
1630
+ text: "No access to the bound Resource. Check `yapout status` and re-run `yapout init` if needed."
1631
+ }
1632
+ ],
1633
+ isError: true
1634
+ };
1863
1635
  }
1636
+ const visible = args.includeAll ? result.directives : result.directives.filter(
1637
+ (d) => ["queued", "in_progress", "review"].includes(d.status)
1638
+ );
1639
+ const buckets = {};
1640
+ for (const d of visible) (buckets[d.status] ??= []).push(d);
1641
+ const lines = [];
1642
+ lines.push(
1643
+ `Resource: ${result.resource.remoteUrl ?? result.resource.canonicalId} (${result.resource._id})`
1644
+ );
1645
+ lines.push("");
1646
+ const order = args.includeAll ? [
1647
+ "queued",
1648
+ "in_progress",
1649
+ "review",
1650
+ "vetted",
1651
+ "drafted",
1652
+ "deployed",
1653
+ "done",
1654
+ "cancelled"
1655
+ ] : ["queued", "in_progress", "review"];
1656
+ let total = 0;
1657
+ for (const status of order) {
1658
+ const rows = buckets[status];
1659
+ if (!rows || rows.length === 0) continue;
1660
+ total += rows.length;
1661
+ lines.push(`${labelForStatus(status)} (${rows.length}):`);
1662
+ for (const d of rows) {
1663
+ const exec2 = d.latestExecution ? ` [exec ${d.latestExecution._id.slice(-6)} ${d.latestExecution.devStatus}]` : "";
1664
+ lines.push(` ${d._id} ${d.title}${exec2}`);
1665
+ if (d.parentRequestTitles.length > 0) {
1666
+ lines.push(
1667
+ ` \u21B3 ${d.parentRequestTitles.join(" | ")}` + (d.parentRequestCount > d.parentRequestTitles.length ? ` (+${d.parentRequestCount - d.parentRequestTitles.length} more)` : "")
1668
+ );
1669
+ }
1670
+ }
1671
+ lines.push("");
1672
+ }
1673
+ if (total === 0) {
1674
+ const filter = args.includeAll ? "" : " (use includeAll=true to include drafted/done)";
1675
+ lines.push(`No directives.${filter}`);
1676
+ }
1677
+ const structured = {
1678
+ resource: result.resource,
1679
+ directives: visible.map((d) => ({
1680
+ directiveId: d._id,
1681
+ title: d.title,
1682
+ status: d.status,
1683
+ actionVerb: d.actionVerb,
1684
+ branchName: d.branchName,
1685
+ parentRequestTitles: d.parentRequestTitles,
1686
+ parentRequestCount: d.parentRequestCount,
1687
+ latestExecution: d.latestExecution
1688
+ }))
1689
+ };
1690
+ return {
1691
+ content: [
1692
+ { type: "text", text: lines.join("\n") },
1693
+ {
1694
+ type: "text",
1695
+ text: "\n---\nStructured data:\n" + JSON.stringify(structured, null, 2)
1696
+ }
1697
+ ]
1698
+ };
1864
1699
  }
1865
- if (data.userContext) {
1866
- sections.push("", "## User Context", "", data.userContext);
1867
- }
1868
- if (data.projectContext) {
1869
- sections.push("", "## Project Context", "", data.projectContext);
1870
- }
1871
- const warnings = data.warnings;
1872
- if (warnings?.duplicateWarning || warnings?.scopeWarning) {
1700
+ });
1701
+ var pickTool = defineTool({
1702
+ name: "yapout_pick",
1703
+ description: "Claim a queued Directive for the active worktree (data-model-redesign-2026-05-01). Locks the Directive (queued \u2192 in_progress), inserts an Execution row at devStatus=running, creates a `.yapout/worktrees/directive-<short>` git worktree, and writes `.yapout/active-execution.json` inside it.",
1704
+ inputSchema: {
1705
+ directiveId: z.string().describe("Convex `directives._id` to pick"),
1706
+ branch: z.string().optional().describe(
1707
+ "Override branch name (default: `<branch_prefix>/directive-<short-id>` from .yapout/config.yml)"
1708
+ ),
1709
+ agent: z.string().optional().describe("Implementer identity tag (default: 'claude')")
1710
+ },
1711
+ handler: async (ctx, args) => {
1712
+ const repoRoot = resolveRepoRoot(ctx.cwd);
1713
+ const cfg = readResourceConfig(repoRoot);
1714
+ if (!cfg) {
1715
+ return {
1716
+ content: [
1717
+ {
1718
+ type: "text",
1719
+ text: "No `.yapout/config.json` found. Run `yapout init` in this repo first."
1720
+ }
1721
+ ],
1722
+ isError: true
1723
+ };
1724
+ }
1725
+ const yapoutCfg = readYapoutConfig(repoRoot);
1726
+ const baseBranch = cfg.defaultBranch ?? (() => {
1727
+ try {
1728
+ return getDefaultBranch(repoRoot);
1729
+ } catch {
1730
+ return "main";
1731
+ }
1732
+ })();
1733
+ const shortId = args.directiveId.slice(-6);
1734
+ const branchName = args.branch ?? `${yapoutCfg.branch_prefix}/directive-${shortId}`;
1735
+ const worktreePath = getWorktreePath(repoRoot, `directive-${shortId}`);
1736
+ let result;
1737
+ try {
1738
+ result = await ctx.client.mutation(
1739
+ anyApi5.functions.resourcesCli.createExecutionForDirective,
1740
+ {
1741
+ directiveId: args.directiveId,
1742
+ worktreePath,
1743
+ branch: branchName,
1744
+ agent: args.agent ?? "claude"
1745
+ }
1746
+ );
1747
+ } catch (err) {
1748
+ return {
1749
+ content: [
1750
+ {
1751
+ type: "text",
1752
+ text: `Failed to pick Directive: ${err.message}`
1753
+ }
1754
+ ],
1755
+ isError: true
1756
+ };
1757
+ }
1758
+ try {
1759
+ createWorktree(
1760
+ repoRoot,
1761
+ `directive-${shortId}`,
1762
+ branchName,
1763
+ baseBranch
1764
+ );
1765
+ } catch (err) {
1766
+ return {
1767
+ content: [
1768
+ {
1769
+ type: "text",
1770
+ text: `Server-side pick succeeded but worktree creation failed: ${err.message}
1771
+
1772
+ Recovery: the Directive is now \`in_progress\` and an Execution row exists (executionId: ${result.executionId}). Resolve the worktree issue and re-run, or flip the Directive back to \`queued\` from the dashboard.`
1773
+ }
1774
+ ],
1775
+ isError: true
1776
+ };
1777
+ }
1778
+ writeActiveExecution(worktreePath, {
1779
+ executionId: result.executionId,
1780
+ directiveId: result.directiveId,
1781
+ branch: branchName,
1782
+ worktreePath,
1783
+ startedAt: Date.now()
1784
+ });
1785
+ return {
1786
+ content: [
1787
+ {
1788
+ type: "text",
1789
+ text: `Picked: ${result.directiveTitle}
1790
+ directive: ${result.directiveId}
1791
+ execution: ${result.executionId}
1792
+ worktree: ${worktreePath}
1793
+ branch: ${branchName}
1794
+
1795
+ Next: cd into the worktree to implement. Use yapout_observe for tangential observations and yapout_submit when ready for review.`
1796
+ }
1797
+ ]
1798
+ };
1799
+ }
1800
+ });
1801
+ var submitTool = defineTool({
1802
+ name: "yapout_submit",
1803
+ description: "Mark the active Execution as submitted and move the parent Directive to review (data-model-redesign-2026-05-01). Reads `.yapout/active-execution.json` from cwd; clears it on success. Pushing/PR opening is out of scope (the harness handles publication).",
1804
+ inputSchema: {},
1805
+ handler: async (ctx) => {
1806
+ const repoRoot = resolveRepoRoot(ctx.cwd);
1807
+ const cfg = readResourceConfig(repoRoot);
1808
+ if (!cfg) {
1809
+ return {
1810
+ content: [
1811
+ {
1812
+ type: "text",
1813
+ text: "No `.yapout/config.json` found. Run `yapout init` in this repo first."
1814
+ }
1815
+ ],
1816
+ isError: true
1817
+ };
1818
+ }
1819
+ const active = readActiveExecution(ctx.cwd);
1820
+ if (!active) {
1821
+ return {
1822
+ content: [
1823
+ {
1824
+ type: "text",
1825
+ text: "No active Execution. yapout_submit must run from a worktree where yapout_pick (or `yapout pick`) was called."
1826
+ }
1827
+ ],
1828
+ isError: true
1829
+ };
1830
+ }
1831
+ let result;
1832
+ try {
1833
+ result = await ctx.client.mutation(
1834
+ anyApi5.functions.resourcesCli.submitExecution,
1835
+ { executionId: active.executionId }
1836
+ );
1837
+ } catch (err) {
1838
+ return {
1839
+ content: [
1840
+ {
1841
+ type: "text",
1842
+ text: `Failed to submit Execution: ${err.message}`
1843
+ }
1844
+ ],
1845
+ isError: true
1846
+ };
1847
+ }
1848
+ clearActiveExecution(ctx.cwd);
1849
+ return {
1850
+ content: [
1851
+ {
1852
+ type: "text",
1853
+ text: `Submitted Execution ${result.executionId}.
1854
+ Directive ${result.directiveId} is now in \`review\`.`
1855
+ }
1856
+ ]
1857
+ };
1858
+ }
1859
+ });
1860
+ var observeTool = defineTool({
1861
+ name: "yapout_observe",
1862
+ description: "File an `agent_observation` Raw against the active Execution (data-model-redesign-2026-05-01). Bypasses intakeRoutes; lineage anchored via `spawnedByExecutionId`. Use when the implementing agent notices something tangential to the Directive \u2014 extraction will turn it into a Request (or link it to an existing one).",
1863
+ inputSchema: {
1864
+ body: z.string().describe(
1865
+ "Freeform observation text. The extraction pipeline parses this into a Request."
1866
+ ),
1867
+ severity: z.enum(["low", "normal", "high"]).optional().describe("Severity hint (default: normal)"),
1868
+ area: z.string().optional().describe(
1869
+ "Optional scoping hint for the extraction chain (e.g., 'auth', 'billing')"
1870
+ )
1871
+ },
1872
+ handler: async (ctx, args) => {
1873
+ const cwd = resolve6(ctx.cwd);
1874
+ const repoRoot = resolveRepoRoot(cwd);
1875
+ const cfg = readResourceConfig(repoRoot);
1876
+ if (!cfg) {
1877
+ return {
1878
+ content: [
1879
+ {
1880
+ type: "text",
1881
+ text: "No `.yapout/config.json` found. Run `yapout init` in this repo first."
1882
+ }
1883
+ ],
1884
+ isError: true
1885
+ };
1886
+ }
1887
+ const active = readActiveExecution(cwd);
1888
+ if (!active) {
1889
+ return {
1890
+ content: [
1891
+ {
1892
+ type: "text",
1893
+ text: "No active Execution. yapout_observe must run from a worktree where yapout_pick (or `yapout pick`) was called."
1894
+ }
1895
+ ],
1896
+ isError: true
1897
+ };
1898
+ }
1899
+ const body = args.body.trim();
1900
+ if (!body) {
1901
+ return {
1902
+ content: [
1903
+ { type: "text", text: "Observation body cannot be empty." }
1904
+ ],
1905
+ isError: true
1906
+ };
1907
+ }
1908
+ let result;
1909
+ try {
1910
+ result = await ctx.client.mutation(
1911
+ anyApi5.functions.resourcesCli.createAgentObservationRaw,
1912
+ {
1913
+ executionId: active.executionId,
1914
+ body,
1915
+ ...args.severity ? { severityHint: args.severity } : {},
1916
+ ...args.area ? { area: args.area } : {}
1917
+ }
1918
+ );
1919
+ } catch (err) {
1920
+ return {
1921
+ content: [
1922
+ {
1923
+ type: "text",
1924
+ text: `Failed to file observation: ${err.message}`
1925
+ }
1926
+ ],
1927
+ isError: true
1928
+ };
1929
+ }
1930
+ return {
1931
+ content: [
1932
+ {
1933
+ type: "text",
1934
+ text: `Filed agent observation (raw: ${result.rawId}, execution: ${result.executionId}).
1935
+ Extraction will turn this into a Request (or link it to an existing one).`
1936
+ }
1937
+ ]
1938
+ };
1939
+ }
1940
+ });
1941
+ function registerDevLoopTools(server, ctx) {
1942
+ registerTool(server, ctx, queueTool);
1943
+ registerTool(server, ctx, pickTool);
1944
+ registerTool(server, ctx, submitTool);
1945
+ registerTool(server, ctx, observeTool);
1946
+ }
1947
+ function labelForStatus(status) {
1948
+ switch (status) {
1949
+ case "queued":
1950
+ return "Ready to pick";
1951
+ case "in_progress":
1952
+ return "In progress";
1953
+ case "review":
1954
+ return "In review";
1955
+ case "vetted":
1956
+ return "Vetted (PM-side)";
1957
+ case "drafted":
1958
+ return "Drafted (PM-side)";
1959
+ case "deployed":
1960
+ return "Deployed";
1961
+ case "done":
1962
+ return "Done";
1963
+ case "cancelled":
1964
+ return "Cancelled";
1965
+ default:
1966
+ return status;
1967
+ }
1968
+ }
1969
+
1970
+ // src/mcp/tools/get-brief.ts
1971
+ import { z as z4 } from "zod";
1972
+ function registerGetBriefTool(server, ctx) {
1973
+ server.tool(
1974
+ "yapout_get_brief",
1975
+ "Fetch the full implementation context for a work item (finding or bundle)",
1976
+ {
1977
+ findingId: z4.string().optional().describe("The finding ID to get the brief for (deprecated: use workItemId)"),
1978
+ workItemId: z4.string().optional().describe("The work item ID (finding or bundle) to get the brief for")
1979
+ },
1980
+ withScopeCheck(ctx, "yapout_get_brief", async (args) => {
1981
+ const itemId = args.workItemId || args.findingId;
1982
+ if (!itemId) {
1983
+ return {
1984
+ content: [
1985
+ {
1986
+ type: "text",
1987
+ text: "Must provide workItemId or findingId."
1988
+ }
1989
+ ],
1990
+ isError: true
1991
+ };
1992
+ }
1993
+ try {
1994
+ const data = await ctx.client.query(
1995
+ anyApi5.functions.findings.getFindingBrief,
1996
+ { findingId: itemId }
1997
+ );
1998
+ if (data) {
1999
+ return {
2000
+ content: [
2001
+ { type: "text", text: JSON.stringify(data, null, 2) }
2002
+ ]
2003
+ };
2004
+ }
2005
+ } catch {
2006
+ }
2007
+ try {
2008
+ const bundle = await ctx.client.query(
2009
+ anyApi5.functions.bundles.getBundle,
2010
+ { bundleId: itemId }
2011
+ );
2012
+ if (bundle) {
2013
+ const result = {
2014
+ kind: "bundle",
2015
+ bundle: {
2016
+ id: bundle._id,
2017
+ title: bundle.title,
2018
+ description: bundle.description,
2019
+ enrichedDescription: bundle.enrichedDescription,
2020
+ acceptanceCriteria: bundle.acceptanceCriteria,
2021
+ implementationBrief: bundle.implementationBrief
2022
+ },
2023
+ findings: bundle.findings.map((f) => ({
2024
+ id: f._id,
2025
+ title: f.title,
2026
+ description: f.description,
2027
+ priority: f.priority,
2028
+ type: f.type,
2029
+ linearIssueId: f.linearIssueId,
2030
+ linearIssueUrl: f.linearIssueUrl,
2031
+ enrichedDescription: f.enrichedDescription,
2032
+ acceptanceCriteria: f.acceptanceCriteria,
2033
+ implementationBrief: f.implementationBrief,
2034
+ dependsOn: f.dependsOn
2035
+ }))
2036
+ };
2037
+ return {
2038
+ content: [
2039
+ { type: "text", text: JSON.stringify(result, null, 2) }
2040
+ ]
2041
+ };
2042
+ }
2043
+ } catch {
2044
+ }
2045
+ return {
2046
+ content: [
2047
+ {
2048
+ type: "text",
2049
+ text: "Work item not found or you don't have access. Provide a valid finding ID or bundle ID."
2050
+ }
2051
+ ],
2052
+ isError: true
2053
+ };
2054
+ })
2055
+ );
2056
+ }
2057
+
2058
+ // src/mcp/tools/claim.ts
2059
+ import { z as z5 } from "zod";
2060
+ import { join as join8 } from "path";
2061
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
2062
+
2063
+ // src/mcp/tools/session-progress.ts
2064
+ async function reportSessionProgress(ctx, pipelineRunId, message, details) {
2065
+ try {
2066
+ const run = await ctx.client.query(
2067
+ anyApi5.functions.pipelineRuns.getPipelineRun,
2068
+ { pipelineRunId }
2069
+ );
2070
+ if (!run?.sourceId) return;
2071
+ await ctx.client.mutation(anyApi5.functions.comments.createComment, {
2072
+ findingId: run.sourceId,
2073
+ body: message,
2074
+ isSystem: true,
2075
+ systemEvent: "session_progress",
2076
+ systemEventDetails: details,
2077
+ sessionId: ctx.session.id ?? void 0
2078
+ });
2079
+ } catch {
2080
+ }
2081
+ }
2082
+
2083
+ // src/mcp/tools/claim.ts
2084
+ function readBranchPrefix(cwd) {
2085
+ try {
2086
+ const config = readYapoutConfig(cwd);
2087
+ return config.branch_prefix || "feat";
2088
+ } catch {
2089
+ return "feat";
2090
+ }
2091
+ }
2092
+ function slugify(text) {
2093
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
2094
+ }
2095
+ function formatFindingBrief(data) {
2096
+ const finding = data.finding;
2097
+ const sections = [
2098
+ `# ${finding.title}`,
2099
+ "",
2100
+ `**Priority:** ${finding.priority} | **Type:** ${finding.type}`
2101
+ ];
2102
+ if (finding.linearIssueUrl) {
2103
+ sections.push(`**Linear:** ${finding.linearIssueUrl}`);
2104
+ }
2105
+ sections.push("", "## Description", "", finding.description);
2106
+ if (data.enrichedDescription) {
2107
+ sections.push("", "## Enriched Description", "", data.enrichedDescription);
2108
+ }
2109
+ if (data.implementationBrief) {
2110
+ sections.push("", "## Implementation Brief", "", data.implementationBrief);
2111
+ }
2112
+ const qa = data.enrichmentQA;
2113
+ if (qa && qa.length > 0) {
2114
+ sections.push("", "## Q&A");
2115
+ for (const item of qa) {
2116
+ sections.push("", `**Q:** ${item.question}`);
2117
+ sections.push(`**A:** ${item.answer || "(no answer yet)"}`);
2118
+ }
2119
+ }
2120
+ if (data.userContext) {
2121
+ sections.push("", "## User Context", "", data.userContext);
2122
+ }
2123
+ if (data.projectContext) {
2124
+ sections.push("", "## Project Context", "", data.projectContext);
2125
+ }
2126
+ const warnings = data.warnings;
2127
+ if (warnings?.duplicateWarning || warnings?.scopeWarning) {
1873
2128
  sections.push("", "## Warnings");
1874
2129
  if (warnings.duplicateWarning)
1875
2130
  sections.push("", `**Duplicate:** ${warnings.duplicateWarning}`);
@@ -1959,9 +2214,9 @@ function registerClaimTool(server, ctx) {
1959
2214
  "yapout_claim",
1960
2215
  "Claim a work item (bundle or standalone finding) for local implementation. Creates a branch (or worktree), writes the brief, and updates status.",
1961
2216
  {
1962
- workItemId: z6.string().describe("The work item ID to claim (bundle ID or finding ID from yapout_queue)"),
1963
- findingId: z6.string().optional().describe("Deprecated: use workItemId instead. If provided, treated as a standalone finding."),
1964
- worktree: z6.boolean().optional().describe("Create a git worktree for parallel work (default: false)")
2217
+ workItemId: z5.string().describe("The work item ID to claim (bundle ID or finding ID from yapout_queue)"),
2218
+ findingId: z5.string().optional().describe("Deprecated: use workItemId instead. If provided, treated as a standalone finding."),
2219
+ worktree: z5.boolean().optional().describe("Create a git worktree for parallel work (default: false)")
1965
2220
  },
1966
2221
  withScopeCheck(ctx, "yapout_claim", async (args) => {
1967
2222
  if (!ctx.projectId) {
@@ -2217,17 +2472,17 @@ async function reportClaimEvents(ctx, pipelineRunId, title, branchName) {
2217
2472
  }
2218
2473
 
2219
2474
  // src/mcp/tools/event.ts
2220
- import { z as z7 } from "zod";
2475
+ import { z as z6 } from "zod";
2221
2476
  function registerEventTool(server, ctx) {
2222
2477
  server.tool(
2223
2478
  "yapout_event",
2224
2479
  "Report a status event back to yapout for the activity feed",
2225
2480
  {
2226
- pipelineRunId: z7.string().describe("The pipeline run ID"),
2227
- event: z7.string().describe(
2481
+ pipelineRunId: z6.string().describe("The pipeline run ID"),
2482
+ event: z6.string().describe(
2228
2483
  'Event type (e.g., "reading_codebase", "writing_code", "running_tests")'
2229
2484
  ),
2230
- message: z7.string().describe("Human-readable description")
2485
+ message: z6.string().describe("Human-readable description")
2231
2486
  },
2232
2487
  withScopeCheck(ctx, "yapout_event", async (args) => {
2233
2488
  try {
@@ -2263,7 +2518,7 @@ function registerEventTool(server, ctx) {
2263
2518
  }
2264
2519
 
2265
2520
  // src/mcp/tools/ship.ts
2266
- import { z as z8 } from "zod";
2521
+ import { z as z7 } from "zod";
2267
2522
 
2268
2523
  // src/lib/github-cli.ts
2269
2524
  import { execSync as execSync3 } from "child_process";
@@ -2342,10 +2597,10 @@ function registerShipTool(server, ctx) {
2342
2597
  "yapout_ship",
2343
2598
  "Commit, push, open a PR, and mark the work item as done. Run yapout_check first if post-flight checks are configured.",
2344
2599
  {
2345
- message: z8.string().optional().describe("Custom commit message (overrides template)"),
2346
- skipPr: z8.boolean().optional().describe("Just push, don't open a PR"),
2347
- pipelineRunId: z8.string().describe("The pipeline run ID from yapout_claim"),
2348
- worktreePath: z8.string().optional().describe("Worktree path (if claiming was done with worktree: true)")
2600
+ message: z7.string().optional().describe("Custom commit message (overrides template)"),
2601
+ skipPr: z7.boolean().optional().describe("Just push, don't open a PR"),
2602
+ pipelineRunId: z7.string().describe("The pipeline run ID from yapout_claim"),
2603
+ worktreePath: z7.string().optional().describe("Worktree path (if claiming was done with worktree: true)")
2349
2604
  },
2350
2605
  withScopeCheck(ctx, "yapout_ship", async (args) => {
2351
2606
  const gitCwd = args.worktreePath || ctx.cwd;
@@ -2565,7 +2820,7 @@ function registerShipTool(server, ctx) {
2565
2820
  }
2566
2821
 
2567
2822
  // src/mcp/tools/check.ts
2568
- import { z as z9 } from "zod";
2823
+ import { z as z8 } from "zod";
2569
2824
  import { exec } from "child_process";
2570
2825
  var COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
2571
2826
  var MAX_OUTPUT_LINES = 100;
@@ -2576,7 +2831,7 @@ function truncate(text) {
2576
2831
  ` + lines.slice(-MAX_OUTPUT_LINES).join("\n");
2577
2832
  }
2578
2833
  function runCommand(command, cwd) {
2579
- return new Promise((resolve13) => {
2834
+ return new Promise((resolve16) => {
2580
2835
  const start = Date.now();
2581
2836
  const child = exec(command, {
2582
2837
  cwd,
@@ -2584,7 +2839,7 @@ function runCommand(command, cwd) {
2584
2839
  maxBuffer: 10 * 1024 * 1024
2585
2840
  // 10MB
2586
2841
  }, (error, stdout, stderr) => {
2587
- resolve13({
2842
+ resolve16({
2588
2843
  exitCode: error?.code ?? (error ? 1 : 0),
2589
2844
  stdout: truncate(stdout),
2590
2845
  stderr: truncate(stderr),
@@ -2592,7 +2847,7 @@ function runCommand(command, cwd) {
2592
2847
  });
2593
2848
  });
2594
2849
  child.on("error", () => {
2595
- resolve13({
2850
+ resolve16({
2596
2851
  exitCode: 1,
2597
2852
  stdout: "",
2598
2853
  stderr: `Command timed out after ${COMMAND_TIMEOUT_MS / 1e3}s`,
@@ -2606,9 +2861,9 @@ function registerCheckTool(server, ctx) {
2606
2861
  "yapout_check",
2607
2862
  "Run post-flight checks (lint, test, typecheck) before shipping. Configure commands in .yapout/config.yml",
2608
2863
  {
2609
- commands: z9.array(z9.string()).optional().describe("Override: run these commands instead of post_flight config"),
2610
- pipelineRunId: z9.string().optional().describe("Pipeline run ID for event reporting"),
2611
- worktreePath: z9.string().optional().describe("Run checks in this worktree directory instead of the repo root")
2864
+ commands: z8.array(z8.string()).optional().describe("Override: run these commands instead of post_flight config"),
2865
+ pipelineRunId: z8.string().optional().describe("Pipeline run ID for event reporting"),
2866
+ worktreePath: z8.string().optional().describe("Run checks in this worktree directory instead of the repo root")
2612
2867
  },
2613
2868
  withScopeCheck(ctx, "yapout_check", async (args) => {
2614
2869
  const checkCwd = args.worktreePath || ctx.cwd;
@@ -2679,7 +2934,7 @@ function registerCheckTool(server, ctx) {
2679
2934
  }
2680
2935
 
2681
2936
  // src/mcp/tools/get-unenriched-findings.ts
2682
- import { z as z10 } from "zod";
2937
+ import { z as z9 } from "zod";
2683
2938
  function registerGetUnenrichedFindingsTool(server, ctx) {
2684
2939
  server.tool(
2685
2940
  "yapout_get_unenriched_finding",
@@ -2693,7 +2948,7 @@ After calling this tool, you should:
2693
2948
  3. If the finding is ambiguous, ask the developer clarifying questions in conversation
2694
2949
  4. When confident, call yapout_save_enrichment with a clean description, acceptance criteria, and implementation brief`,
2695
2950
  {
2696
- findingId: z10.string().optional().describe("Specific finding ID to enrich. If omitted, returns the highest priority draft finding.")
2951
+ findingId: z9.string().optional().describe("Specific finding ID to enrich. If omitted, returns the highest priority draft finding.")
2697
2952
  },
2698
2953
  withScopeCheck(ctx, "yapout_get_unenriched_finding", async (args) => {
2699
2954
  const projectId = ctx.projectId ?? process.env.YAPOUT_PROJECT_ID;
@@ -2807,7 +3062,7 @@ function registerGetExistingFindingsTool(server, ctx) {
2807
3062
  }
2808
3063
 
2809
3064
  // src/mcp/tools/save-enrichment.ts
2810
- import { z as z11 } from "zod";
3065
+ import { z as z10 } from "zod";
2811
3066
 
2812
3067
  // src/mcp/tools/enrichment-session.ts
2813
3068
  var activeSessions = /* @__PURE__ */ new Map();
@@ -2852,22 +3107,22 @@ This tool saves the enrichment, then automatically creates the Linear issue with
2852
3107
 
2853
3108
  The finding transitions: enriching \u2192 enriched \u2192 ready.`,
2854
3109
  {
2855
- findingId: z11.string().describe("The finding ID to enrich (from yapout_get_unenriched_finding)"),
2856
- title: z11.string().describe("Refined finding title \u2014 improve it if the original was vague"),
2857
- cleanDescription: z11.string().describe("Human-readable summary for the Linear issue body. Write the kind of finding a senior engineer would write."),
2858
- acceptanceCriteria: z11.array(z11.string()).describe("List of testable acceptance criteria (each a single clear statement)"),
2859
- implementationBrief: z11.string().describe("Deep technical context for the implementing agent: which files, what approach, edge cases to watch for"),
2860
- clarifications: z11.array(
2861
- z11.object({
2862
- question: z11.string().describe("The question you asked the developer"),
2863
- answer: z11.string().describe("The developer's answer")
3110
+ findingId: z10.string().describe("The finding ID to enrich (from yapout_get_unenriched_finding)"),
3111
+ title: z10.string().describe("Refined finding title \u2014 improve it if the original was vague"),
3112
+ cleanDescription: z10.string().describe("Human-readable summary for the Linear issue body. Write the kind of finding a senior engineer would write."),
3113
+ acceptanceCriteria: z10.array(z10.string()).describe("List of testable acceptance criteria (each a single clear statement)"),
3114
+ implementationBrief: z10.string().describe("Deep technical context for the implementing agent: which files, what approach, edge cases to watch for"),
3115
+ clarifications: z10.array(
3116
+ z10.object({
3117
+ question: z10.string().describe("The question you asked the developer"),
3118
+ answer: z10.string().describe("The developer's answer")
2864
3119
  })
2865
3120
  ).optional().describe("Only meaningful Q&A from the conversation \u2014 deviations from expected scope, scoping decisions, etc. Omit if you had no questions."),
2866
- isOversized: z11.boolean().optional().describe("Set to true if this finding is too large for a single PR"),
2867
- suggestedSplit: z11.array(z11.string()).optional().describe("If oversized: suggested sub-finding titles for breaking it down"),
2868
- nature: z11.enum(["implementable", "operational", "spike"]).optional().describe("Override the finding's nature if enrichment reveals it should be reclassified"),
2869
- cloudSafe: z11.boolean().optional().describe("Set to true ONLY if this is a small mechanical change a cloud agent can ship without sandbox testing \u2014 text/copy edits, classname tweaks, single-named-constant changes, \u226430 lines, single file, no logic/type/dependency changes. Default false."),
2870
- sessionId: z11.string().optional().describe("Bulk enrichment session ID (from yapout_start_enrichment). Updates session stats.")
3121
+ isOversized: z10.boolean().optional().describe("Set to true if this finding is too large for a single PR"),
3122
+ suggestedSplit: z10.array(z10.string()).optional().describe("If oversized: suggested sub-finding titles for breaking it down"),
3123
+ nature: z10.enum(["implementable", "operational", "spike"]).optional().describe("Override the finding's nature if enrichment reveals it should be reclassified"),
3124
+ cloudSafe: z10.boolean().optional().describe("Set to true ONLY if this is a small mechanical change a cloud agent can ship without sandbox testing \u2014 text/copy edits, classname tweaks, single-named-constant changes, \u226430 lines, single file, no logic/type/dependency changes. Default false."),
3125
+ sessionId: z10.string().optional().describe("Bulk enrichment session ID (from yapout_start_enrichment). Updates session stats.")
2871
3126
  },
2872
3127
  withScopeCheck(ctx, "yapout_save_enrichment", async (args) => {
2873
3128
  try {
@@ -2898,433 +3153,70 @@ The finding transitions: enriching \u2192 enriched \u2192 ready.`,
2898
3153
  const session2 = getSession(args.sessionId);
2899
3154
  if (session2) {
2900
3155
  updateSessionStats(args.sessionId, {
2901
- enriched: session2.stats.enriched + 1
2902
- });
2903
- }
2904
- }
2905
- const session = args.sessionId ? getSession(args.sessionId) : null;
2906
- const estimatedTokens = session ? session.stats.enriched * 5e3 : 0;
2907
- const compactionHint = estimatedTokens > 1e5;
2908
- const response = {
2909
- findingId: args.findingId,
2910
- linearIssueId: finding?.linearIssueId ?? null,
2911
- linearIssueUrl: finding?.linearIssueUrl ?? null,
2912
- compactionHint,
2913
- message: finding?.linearIssueUrl ? `Finding enriched and ready in Linear: ${finding.linearIssueUrl}` : "Finding enriched and ready in Linear."
2914
- };
2915
- if (args.isOversized && args.suggestedSplit?.length) {
2916
- response.warning = `This finding is oversized. Suggested split: ${args.suggestedSplit.join(", ")}`;
2917
- }
2918
- return {
2919
- content: [
2920
- {
2921
- type: "text",
2922
- text: JSON.stringify(response, null, 2)
2923
- }
2924
- ]
2925
- };
2926
- } catch (err) {
2927
- return {
2928
- content: [
2929
- {
2930
- type: "text",
2931
- text: `Error saving enrichment: ${err.message}`
2932
- }
2933
- ],
2934
- isError: true
2935
- };
2936
- }
2937
- })
2938
- );
2939
- }
2940
-
2941
- // src/mcp/tools/sync-to-linear.ts
2942
- import { z as z12 } from "zod";
2943
- function registerSyncToLinearTool(server, ctx) {
2944
- server.tool(
2945
- "yapout_sync_to_linear",
2946
- "Trigger Linear issue creation for an enriched finding. The sync runs server-side (encrypted Linear token in Convex).",
2947
- {
2948
- findingId: z12.string().describe("The finding ID to sync to Linear")
2949
- },
2950
- withScopeCheck(ctx, "yapout_sync_to_linear", async (args) => {
2951
- try {
2952
- await ctx.client.action(
2953
- anyApi5.functions.localPipeline.syncFindingToLinearLocal,
2954
- { findingId: args.findingId }
2955
- );
2956
- return {
2957
- content: [
2958
- {
2959
- type: "text",
2960
- text: JSON.stringify(
2961
- {
2962
- success: true,
2963
- findingId: args.findingId,
2964
- message: "Finding synced to Linear successfully. It will now appear in the work queue for implementation."
2965
- },
2966
- null,
2967
- 2
2968
- )
2969
- }
2970
- ]
2971
- };
2972
- } catch (err) {
2973
- return {
2974
- content: [
2975
- {
2976
- type: "text",
2977
- text: `Error syncing to Linear: ${err.message}`
2978
- }
2979
- ],
2980
- isError: true
2981
- };
2982
- }
2983
- })
2984
- );
2985
- }
2986
-
2987
- // src/mcp/tools/submit-yap-session.ts
2988
- import { z as z13 } from "zod";
2989
- function registerSubmitYapSessionTool(server, ctx) {
2990
- server.tool(
2991
- "yapout_submit_yap_session",
2992
- "Submit a yap session transcript for finding extraction",
2993
- {
2994
- title: z13.string().describe("Session title (e.g., 'Notification system brainstorm')"),
2995
- transcript: z13.string().describe("Cleaned conversation transcript")
2996
- },
2997
- withScopeCheck(ctx, "yapout_submit_yap_session", async (args) => {
2998
- if (!ctx.projectId) {
2999
- return {
3000
- content: [
3001
- {
3002
- type: "text",
3003
- text: "No project linked. Run yapout_init or yapout link first."
3004
- }
3005
- ],
3006
- isError: true
3007
- };
3008
- }
3009
- try {
3010
- const captureId = await ctx.client.mutation(
3011
- anyApi5.functions.captures.createFromYapSession,
3012
- {
3013
- projectId: ctx.projectId,
3014
- title: args.title,
3015
- transcript: args.transcript
3016
- }
3017
- );
3018
- return {
3019
- content: [
3020
- {
3021
- type: "text",
3022
- text: JSON.stringify(
3023
- {
3024
- captureId,
3025
- message: "Yap session submitted. Findings will appear for review shortly."
3026
- },
3027
- null,
3028
- 2
3029
- )
3030
- }
3031
- ]
3032
- };
3033
- } catch (err) {
3034
- return {
3035
- content: [
3036
- {
3037
- type: "text",
3038
- text: `Failed to submit yap session: ${err.message}`
3039
- }
3040
- ],
3041
- isError: true
3042
- };
3043
- }
3044
- })
3045
- );
3046
- }
3047
-
3048
- // src/mcp/tools/start-yap.ts
3049
- import { z as z14 } from "zod";
3050
- var PERSONA_PRESETS = {
3051
- "tech lead": "You are an experienced tech lead. You care about maintainability, simplicity, and shipping. Challenge over-engineering and vague scope.",
3052
- "qa engineer": "You are a skeptical QA engineer. Focus on error states, edge cases, missing validation, accessibility, and user-facing failure modes.",
3053
- "product owner": "You are a product owner. Focus on user value, scope, prioritization, and whether features solve real problems. Push back on technical gold-plating.",
3054
- "end user": "You are an end user of this application. You're not technical. Focus on usability, clarity, frustration points, and what you'd expect to happen."
3055
- };
3056
- function resolvePersona(input5) {
3057
- const lower = input5.toLowerCase().trim();
3058
- return PERSONA_PRESETS[lower] ?? input5;
3059
- }
3060
- function registerStartYapTool(server, ctx) {
3061
- server.tool(
3062
- "yapout_start_yap",
3063
- "Start a yap session \u2014 a structured AI-guided brainstorming conversation. Call this when the user wants to brainstorm, discuss ideas, or have a yap session. Returns instructions for how to conduct the session. Available personas: tech lead (default), qa engineer, product owner, end user, or any custom description.",
3064
- {
3065
- persona: z14.string().optional().describe(
3066
- 'Interviewer persona: "tech lead", "qa engineer", "product owner", "end user", or a custom description'
3067
- ),
3068
- context: z14.string().optional().describe("What the developer wants to discuss")
3069
- },
3070
- withScopeCheck(ctx, "yapout_start_yap", async (args) => {
3071
- if (!ctx.projectId) {
3072
- return {
3073
- content: [
3074
- {
3075
- type: "text",
3076
- text: "No project linked. Run yapout_init or yapout link first."
3077
- }
3078
- ],
3079
- isError: true
3080
- };
3081
- }
3082
- const persona = args.persona ?? "tech lead";
3083
- const personaBlock = resolvePersona(persona);
3084
- const contextSection = args.context ? `The developer wants to discuss: ${args.context}. Start by reading relevant parts of the codebase, then open with a specific question about their idea.` : "Start by asking the developer what they'd like to discuss. Read the codebase first to understand the project.";
3085
- const instructions = `You are now in a yapout yap session \u2014 a structured brainstorming conversation about this codebase.
3086
-
3087
- YOUR ROLE: ${personaBlock}
3088
-
3089
- WHAT YOU DO:
3090
- - Have a natural, focused conversation about the developer's idea
3091
- - Read files from the codebase to inform your questions (package.json, schema, relevant source files)
3092
- - Ask ONE question at a time \u2014 don't overwhelm
3093
- - Push back on vague statements: "What do you mean by 'better'? Better for whom?"
3094
- - Surface edge cases: "What happens when the user has no internet?"
3095
- - Reference actual code: "I see you have a stateMachines.ts \u2014 should this new status go there?"
3096
- - When the developer makes a clear decision, acknowledge it and move to the next topic
3097
- - Keep the conversation productive \u2014 steer away from tangents
3098
-
3099
- WHAT YOU DON'T DO:
3100
- - Don't implement anything. Don't write code. Don't edit files. Just discuss.
3101
- - Don't agree with everything. Challenge ideas that seem undercooked.
3102
- - Don't ask more than 5 questions without letting the developer steer.
3103
- - 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.
3104
-
3105
- ${contextSection}
3106
-
3107
- DURING THE SESSION \u2014 COMPLETENESS TRACKING:
3108
- As the conversation progresses, you are building a mental model of every finding. For each finding, track:
3109
- - What has been clearly stated (title, scope, priority)
3110
- - What has been discussed in enough depth to write a finding (enrichment)
3111
- - What is still vague, contradicted, or unanswered
3112
-
3113
- 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.
3114
-
3115
- 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.
3116
-
3117
- BEFORE SUBMITTING \u2014 GAP FILLING:
3118
- Before you submit, review your mental model of every finding. For each one, ask yourself:
3119
- - Could a developer read this finding and start working without asking questions?
3120
- - Are the acceptance criteria specific enough to verify?
3121
- - Are there ambiguities the user didn't resolve?
3122
-
3123
- 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.
3124
-
3125
- 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.
3126
-
3127
- ENRICHMENT ASSESSMENT:
3128
- For each finding, you must make an honest call: is this enriched or not?
3129
-
3130
- Mark a finding as ENRICHED (isEnriched: true) when:
3131
- - The conversation covered it thoroughly enough to produce a clear finding
3132
- - You can write an enrichedDescription that a senior engineer would recognize as well-scoped
3133
- - You can write at least 2-3 testable acceptance criteria
3134
- - The user explicitly validated the scope (not just mentioned it in passing)
3135
-
3136
- Mark a finding as NOT ENRICHED (isEnriched: false or omitted) when:
3137
- - It came up late in the conversation without much discussion
3138
- - The user mentioned it but didn't elaborate on scope or requirements
3139
- - You're uncertain about key aspects (what it should do, how it should work)
3140
- - It's a spike or needs further scoping
3141
-
3142
- 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.
3143
-
3144
- PRESENTING THE FINAL PICTURE:
3145
- When the conversation feels complete, present a summary grouped by bundles and standalone issues:
3146
-
3147
- "Here's what I've gathered from our conversation:
3148
-
3149
- **[Bundle Name]** \u2014 [one-line description]
3150
- 1. [Child issue] \u2014 enriched \u2713
3151
- 2. [Child issue] \u2014 enriched \u2713
3152
- 3. [Child issue] \u2014 needs enrichment (we didn't discuss scope)
3153
-
3154
- **Standalone issues:**
3155
- 4. [Issue] \u2014 enriched \u2713
3156
-
3157
- [Any open questions you still need answered]
3158
-
3159
- Should I submit this to yapout?"
3160
-
3161
- SUBMITTING:
3162
- On confirmation, call yapout_extract_from_yap with the full data.
3163
-
3164
- For each finding, provide:
3165
- - title, description, sourceQuote, type, priority, confidence, nature
3166
- - isEnriched: your honest assessment (see above)
3167
-
3168
- For ENRICHED findings (isEnriched: true), also include:
3169
- - enrichedDescription: clean, final description \u2014 write the kind of finding a senior engineer would write
3170
- - acceptanceCriteria: array of specific, testable statements
3171
- - 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.
3172
- - clarifications: relevant Q&A from the conversation that shaped the finding
3173
-
3174
- For findings with CHILDREN (bundles), also include:
3175
- - bundleDescription: what this body of work accomplishes
3176
- - suggestedOrder: implementation sequencing ("Phase 1: A, then Phase 2: B+C")
3177
- - children: array of child issues (each with their own enrichment data)
3178
-
3179
- For children with DEPENDENCIES, include:
3180
- - dependsOn: array of sibling indices (0-based) that must complete first
3181
-
3182
- Structure:
3183
- - Findings with children become bundles \u2014 never create child issues as separate top-level findings
3184
- - Standalone issues go at the top level
3185
- - The hierarchy you produce should be the final structure
3186
-
3187
- Call yapout_extract_from_yap with:
3188
- - sessionTitle: descriptive title for this session
3189
- - sessionTranscript: clean summary of the conversation (meeting-notes style)
3190
- - findings: the array (bundles with children, standalone issues)
3191
-
3192
- This creates findings (enriched or draft based on your assessment) and bundles \u2014 all in one call. Enriched findings appear in the work queue ready for the user to sync to Linear.
3193
-
3194
- Only fall back to yapout_submit_yap_session if the session was purely exploratory with no clear actionable findings.
3195
-
3196
- --- BEGIN THE SESSION NOW ---`;
3197
- return {
3198
- content: [
3199
- {
3200
- type: "text",
3201
- text: JSON.stringify(
3202
- {
3203
- projectId: ctx.projectId,
3204
- projectName: ctx.projectName,
3205
- persona,
3206
- instructions
3207
- },
3208
- null,
3209
- 2
3210
- )
3156
+ enriched: session2.stats.enriched + 1
3157
+ });
3211
3158
  }
3212
- ]
3213
- };
3214
- })
3215
- );
3216
- }
3217
-
3218
- // src/mcp/tools/extract-from-yap.ts
3219
- import { z as z15 } from "zod";
3220
- var clarificationSchema = z15.object({
3221
- question: z15.string().describe("The question asked during the conversation"),
3222
- answer: z15.string().describe("The answer given")
3223
- });
3224
- var childSchema = z15.object({
3225
- title: z15.string().describe("Child issue title"),
3226
- description: z15.string().describe("What needs to be done and why"),
3227
- sourceQuote: z15.string().describe("Relevant excerpt from the conversation"),
3228
- type: z15.enum(["feature", "bug", "chore", "spike"]).describe("Finding category"),
3229
- priority: z15.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
3230
- confidence: z15.number().min(0).max(1).describe("Confidence this is a clear, actionable finding (0-1)"),
3231
- nature: z15.enum(["implementable", "operational", "spike"]).describe("What kind of work"),
3232
- // Enrichment — set isEnriched: true only if the conversation covered this
3233
- // issue thoroughly enough to produce a finding a developer could pick up.
3234
- isEnriched: z15.boolean().optional().describe(
3235
- "Did the conversation cover this issue deeply enough to produce a complete finding? true = enriched (ready for Linear), false/omitted = draft (needs async enrichment)"
3236
- ),
3237
- enrichedDescription: z15.string().optional().describe("Clean, final description for the Linear issue body (required if isEnriched)"),
3238
- acceptanceCriteria: z15.array(z15.string()).optional().describe("Testable acceptance criteria (required if isEnriched)"),
3239
- implementationBrief: z15.string().optional().describe("Technical context: files, approach, edge cases (optional \u2014 the implementing agent reads the codebase anyway)"),
3240
- clarifications: z15.array(clarificationSchema).optional().describe("Relevant Q&A from the conversation that shaped this finding"),
3241
- dependsOn: z15.array(z15.number()).optional().describe("Indices (0-based) of sibling children that must be completed first")
3242
- });
3243
- function registerExtractFromYapTool(server, ctx) {
3244
- server.tool(
3245
- "yapout_extract_from_yap",
3246
- "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.",
3247
- {
3248
- sessionTitle: z15.string().describe("Descriptive title for this yap session"),
3249
- sessionTranscript: z15.string().describe("Cleaned transcript of the conversation (meeting-notes style)"),
3250
- findings: z15.array(
3251
- z15.object({
3252
- title: z15.string().describe("Finding title"),
3253
- description: z15.string().describe("What needs to be done and why"),
3254
- sourceQuote: z15.string().describe("Relevant excerpt from the conversation"),
3255
- type: z15.enum(["feature", "bug", "chore", "spike"]).describe("Finding category (do not use spike \u2014 use nature instead)"),
3256
- priority: z15.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
3257
- confidence: z15.number().min(0).max(1).describe("Your confidence this is a clear, actionable finding (0-1)"),
3258
- nature: z15.enum(["implementable", "operational", "spike"]).describe("implementable = code changes, operational = manual task, spike = needs scoping"),
3259
- sourceFindingId: z15.string().optional().describe("ID of an existing finding this scopes (e.g., scoping a spike)"),
3260
- // Enrichment data for standalone findings
3261
- isEnriched: z15.boolean().optional().describe("For standalone issues: was this thoroughly discussed? true = enriched, false = draft"),
3262
- enrichedDescription: z15.string().optional().describe("Clean description for Linear (required if isEnriched)"),
3263
- acceptanceCriteria: z15.array(z15.string()).optional().describe("Testable acceptance criteria (required if isEnriched)"),
3264
- implementationBrief: z15.string().optional().describe("Technical context (optional)"),
3265
- clarifications: z15.array(clarificationSchema).optional().describe("Relevant Q&A from conversation"),
3266
- // Bundle fields — when a finding has children, a bundle is created
3267
- bundleDescription: z15.string().optional().describe("What this body of work accomplishes (only for findings with children)"),
3268
- suggestedOrder: z15.string().optional().describe("Implementation order: 'Phase 1: A, then Phase 2: B+C' (only for findings with children)"),
3269
- children: z15.array(childSchema).optional().describe("Child issues \u2014 when present, a bundle is created from this finding")
3270
- })
3271
- ).describe("Findings from the conversation. Projects include children inline.")
3272
- },
3273
- withScopeCheck(ctx, "yapout_extract_from_yap", async (args) => {
3274
- if (!ctx.projectId) {
3159
+ }
3160
+ const session = args.sessionId ? getSession(args.sessionId) : null;
3161
+ const estimatedTokens = session ? session.stats.enriched * 5e3 : 0;
3162
+ const compactionHint = estimatedTokens > 1e5;
3163
+ const response = {
3164
+ findingId: args.findingId,
3165
+ linearIssueId: finding?.linearIssueId ?? null,
3166
+ linearIssueUrl: finding?.linearIssueUrl ?? null,
3167
+ compactionHint,
3168
+ message: finding?.linearIssueUrl ? `Finding enriched and ready in Linear: ${finding.linearIssueUrl}` : "Finding enriched and ready in Linear."
3169
+ };
3170
+ if (args.isOversized && args.suggestedSplit?.length) {
3171
+ response.warning = `This finding is oversized. Suggested split: ${args.suggestedSplit.join(", ")}`;
3172
+ }
3275
3173
  return {
3276
3174
  content: [
3277
3175
  {
3278
3176
  type: "text",
3279
- text: "No project linked. Run yapout_init or yapout link first."
3177
+ text: JSON.stringify(response, null, 2)
3178
+ }
3179
+ ]
3180
+ };
3181
+ } catch (err) {
3182
+ return {
3183
+ content: [
3184
+ {
3185
+ type: "text",
3186
+ text: `Error saving enrichment: ${err.message}`
3280
3187
  }
3281
3188
  ],
3282
3189
  isError: true
3283
3190
  };
3284
3191
  }
3192
+ })
3193
+ );
3194
+ }
3195
+
3196
+ // src/mcp/tools/sync-to-linear.ts
3197
+ import { z as z11 } from "zod";
3198
+ function registerSyncToLinearTool(server, ctx) {
3199
+ server.tool(
3200
+ "yapout_sync_to_linear",
3201
+ "Trigger Linear issue creation for an enriched finding. The sync runs server-side (encrypted Linear token in Convex).",
3202
+ {
3203
+ findingId: z11.string().describe("The finding ID to sync to Linear")
3204
+ },
3205
+ withScopeCheck(ctx, "yapout_sync_to_linear", async (args) => {
3285
3206
  try {
3286
- const result = await ctx.client.mutation(
3287
- anyApi5.functions.captures.extractFromYapSession,
3288
- {
3289
- projectId: ctx.projectId,
3290
- title: args.sessionTitle,
3291
- transcript: args.sessionTranscript,
3292
- findings: args.findings
3293
- }
3207
+ await ctx.client.action(
3208
+ anyApi5.functions.localPipeline.syncFindingToLinearLocal,
3209
+ { findingId: args.findingId }
3294
3210
  );
3295
- let enrichedCount = 0;
3296
- let draftCount = 0;
3297
- let totalFindings = 0;
3298
- for (const item of result.items) {
3299
- if (item.children && item.children.length > 0) {
3300
- for (const child of item.children) {
3301
- totalFindings++;
3302
- if (child.findingStatus === "enriched") enrichedCount++;
3303
- else draftCount++;
3304
- }
3305
- } else {
3306
- totalFindings++;
3307
- if (item.findingStatus === "enriched") enrichedCount++;
3308
- else draftCount++;
3309
- }
3310
- }
3311
- const parts = [];
3312
- if (enrichedCount > 0) parts.push(`${enrichedCount} enriched (ready for Linear)`);
3313
- if (draftCount > 0) parts.push(`${draftCount} need enrichment`);
3314
3211
  return {
3315
3212
  content: [
3316
3213
  {
3317
3214
  type: "text",
3318
3215
  text: JSON.stringify(
3319
3216
  {
3320
- captureId: result.captureId,
3321
- items: result.items,
3322
- summary: {
3323
- totalFindings,
3324
- enriched: enrichedCount,
3325
- needsEnrichment: draftCount
3326
- },
3327
- message: `Created ${totalFindings} finding${totalFindings === 1 ? "" : "s"}: ${parts.join(", ")}. Review in the yapout work queue.`
3217
+ success: true,
3218
+ findingId: args.findingId,
3219
+ message: "Finding synced to Linear successfully. It will now appear in the work queue for implementation."
3328
3220
  },
3329
3221
  null,
3330
3222
  2
@@ -3337,7 +3229,7 @@ function registerExtractFromYapTool(server, ctx) {
3337
3229
  content: [
3338
3230
  {
3339
3231
  type: "text",
3340
- text: `Failed to extract from yap session: ${err.message}`
3232
+ text: `Error syncing to Linear: ${err.message}`
3341
3233
  }
3342
3234
  ],
3343
3235
  isError: true
@@ -3348,7 +3240,7 @@ function registerExtractFromYapTool(server, ctx) {
3348
3240
  }
3349
3241
 
3350
3242
  // src/mcp/tools/decompose-finding.ts
3351
- import { z as z16 } from "zod";
3243
+ import { z as z12 } from "zod";
3352
3244
  function registerDecomposeFindingTool(server, ctx) {
3353
3245
  server.tool(
3354
3246
  "yapout_decompose_finding",
@@ -3366,19 +3258,19 @@ archives the original finding. Returns the bundle ID and child finding IDs.
3366
3258
  Each child issue's implementation brief must be detailed enough to stand alone as a
3367
3259
  full spec \u2014 schema changes, files to modify, edge cases, and acceptance criteria.`,
3368
3260
  {
3369
- findingId: z16.string().describe("The finding being decomposed (from yapout_get_unenriched_finding)"),
3370
- bundleDescription: z16.string().describe("Description for the bundle \u2014 what this body of work accomplishes"),
3371
- suggestedOrder: z16.string().describe("Human-readable implementation order (e.g. 'Phase 1: A, then Phase 2: B+C in parallel, then Phase 3: D')"),
3372
- linearProjectId: z16.string().optional().describe("Existing Linear project ID to associate with. Omit to skip Linear project association."),
3373
- issues: z16.array(
3374
- z16.object({
3375
- title: z16.string().describe("Child issue title"),
3376
- description: z16.string().describe("What needs to be done and why"),
3377
- acceptanceCriteria: z16.array(z16.string()).describe("Testable acceptance criteria"),
3378
- implementationBrief: z16.string().describe("Full spec: files to modify, schema changes, edge cases, approach"),
3379
- type: z16.enum(["feature", "bug", "chore", "spike"]).describe("Category of work"),
3380
- priority: z16.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
3381
- dependsOn: z16.array(z16.number()).describe("Indices (0-based) of issues in this array that must be completed first")
3261
+ findingId: z12.string().describe("The finding being decomposed (from yapout_get_unenriched_finding)"),
3262
+ bundleDescription: z12.string().describe("Description for the bundle \u2014 what this body of work accomplishes"),
3263
+ suggestedOrder: z12.string().describe("Human-readable implementation order (e.g. 'Phase 1: A, then Phase 2: B+C in parallel, then Phase 3: D')"),
3264
+ linearProjectId: z12.string().optional().describe("Existing Linear project ID to associate with. Omit to skip Linear project association."),
3265
+ issues: z12.array(
3266
+ z12.object({
3267
+ title: z12.string().describe("Child issue title"),
3268
+ description: z12.string().describe("What needs to be done and why"),
3269
+ acceptanceCriteria: z12.array(z12.string()).describe("Testable acceptance criteria"),
3270
+ implementationBrief: z12.string().describe("Full spec: files to modify, schema changes, edge cases, approach"),
3271
+ type: z12.enum(["feature", "bug", "chore", "spike"]).describe("Category of work"),
3272
+ priority: z12.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
3273
+ dependsOn: z12.array(z12.number()).describe("Indices (0-based) of issues in this array that must be completed first")
3382
3274
  })
3383
3275
  ).describe("The child issues produced by decomposition")
3384
3276
  },
@@ -3441,7 +3333,7 @@ full spec \u2014 schema changes, files to modify, edge cases, and acceptance cri
3441
3333
  }
3442
3334
 
3443
3335
  // src/mcp/tools/mark-duplicate.ts
3444
- import { z as z17 } from "zod";
3336
+ import { z as z13 } from "zod";
3445
3337
  function registerMarkDuplicateTool(server, ctx) {
3446
3338
  server.tool(
3447
3339
  "yapout_mark_duplicate",
@@ -3451,9 +3343,9 @@ flow when you discover the work is already tracked in Linear.
3451
3343
  The finding must be in "enriching" or "enriched" status. It will be transitioned to
3452
3344
  "failed" with the duplicate reference stored. Nothing is synced to Linear.`,
3453
3345
  {
3454
- findingId: z17.string().describe("The yapout finding to archive as a duplicate"),
3455
- duplicateOfLinearId: z17.string().describe("The Linear issue identifier it duplicates (e.g. 'ENG-234')"),
3456
- reason: z17.string().describe("Brief explanation of why this is a duplicate")
3346
+ findingId: z13.string().describe("The yapout finding to archive as a duplicate"),
3347
+ duplicateOfLinearId: z13.string().describe("The Linear issue identifier it duplicates (e.g. 'ENG-234')"),
3348
+ reason: z13.string().describe("Brief explanation of why this is a duplicate")
3457
3349
  },
3458
3350
  withScopeCheck(ctx, "yapout_mark_duplicate", async (args) => {
3459
3351
  if (!ctx.projectId) {
@@ -3582,7 +3474,7 @@ and issue counts so you can ask the user for confirmation before creating a new
3582
3474
  }
3583
3475
 
3584
3476
  // src/mcp/tools/start-enrichment.ts
3585
- import { z as z18 } from "zod";
3477
+ import { z as z14 } from "zod";
3586
3478
  function registerStartEnrichmentTool(server, ctx) {
3587
3479
  server.tool(
3588
3480
  "yapout_start_enrichment",
@@ -3593,10 +3485,10 @@ and maintains filter criteria so you don't re-pass them on every call.
3593
3485
 
3594
3486
  Optionally filter by tags, capture, or explicit finding IDs.`,
3595
3487
  {
3596
- filter: z18.object({
3597
- tags: z18.array(z18.string()).optional().describe("Only enrich findings with these tags"),
3598
- captureId: z18.string().optional().describe("Only enrich findings from this capture"),
3599
- findingIds: z18.array(z18.string()).optional().describe("Only enrich these specific findings")
3488
+ filter: z14.object({
3489
+ tags: z14.array(z14.string()).optional().describe("Only enrich findings with these tags"),
3490
+ captureId: z14.string().optional().describe("Only enrich findings from this capture"),
3491
+ findingIds: z14.array(z14.string()).optional().describe("Only enrich these specific findings")
3600
3492
  }).optional().describe("Filter criteria. If omitted, all draft findings in the project are included.")
3601
3493
  },
3602
3494
  withScopeCheck(ctx, "yapout_start_enrichment", async (args) => {
@@ -3652,7 +3544,7 @@ Optionally filter by tags, capture, or explicit finding IDs.`,
3652
3544
  }
3653
3545
 
3654
3546
  // src/mcp/tools/enrich-next.ts
3655
- import { z as z19 } from "zod";
3547
+ import { z as z15 } from "zod";
3656
3548
  function registerEnrichNextTool(server, ctx) {
3657
3549
  server.tool(
3658
3550
  "yapout_enrich_next",
@@ -3663,9 +3555,9 @@ Returns the next unclaimed draft finding matching the session's filter.
3663
3555
 
3664
3556
  When done=true, all findings have been processed.`,
3665
3557
  {
3666
- sessionId: z19.string().describe("Session ID from yapout_start_enrichment"),
3667
- skip: z19.boolean().optional().describe("If true, skip the current finding (release back to draft)"),
3668
- skipFindingId: z19.string().optional().describe("The finding ID to skip (must be in 'enriching' status)")
3558
+ sessionId: z15.string().describe("Session ID from yapout_start_enrichment"),
3559
+ skip: z15.boolean().optional().describe("If true, skip the current finding (release back to draft)"),
3560
+ skipFindingId: z15.string().optional().describe("The finding ID to skip (must be in 'enriching' status)")
3669
3561
  },
3670
3562
  withScopeCheck(ctx, "yapout_enrich_next", async (args) => {
3671
3563
  const session = getSession(args.sessionId);
@@ -3794,7 +3686,7 @@ When done=true, all findings have been processed.`,
3794
3686
  }
3795
3687
 
3796
3688
  // src/mcp/tools/block-enrichment.ts
3797
- import { z as z20 } from "zod";
3689
+ import { z as z16 } from "zod";
3798
3690
  function registerBlockEnrichmentTool(server, ctx) {
3799
3691
  server.tool(
3800
3692
  "yapout_block_enrichment",
@@ -3806,9 +3698,9 @@ The finding must currently be in "enriching" status (claimed via yapout_get_unen
3806
3698
 
3807
3699
  Transitions: enriching \u2192 needs_input. The user sees the blockerReason and questions in the UI, adds context, and resubmits.`,
3808
3700
  {
3809
- findingId: z20.string().describe("The finding ID to block (currently in 'enriching')"),
3810
- blockerReason: z20.string().describe("One sentence: why this finding cannot be enriched as written. State the missing piece concretely."),
3811
- blockerQuestions: z20.array(z20.string()).min(1).describe("2-5 specific questions the user must answer before enrichment can succeed. Each must be answerable in a sentence or two.")
3701
+ findingId: z16.string().describe("The finding ID to block (currently in 'enriching')"),
3702
+ blockerReason: z16.string().describe("One sentence: why this finding cannot be enriched as written. State the missing piece concretely."),
3703
+ blockerQuestions: z16.array(z16.string()).min(1).describe("2-5 specific questions the user must answer before enrichment can succeed. Each must be answerable in a sentence or two.")
3812
3704
  },
3813
3705
  withScopeCheck(ctx, "yapout_block_enrichment", async (args) => {
3814
3706
  try {
@@ -3855,9 +3747,9 @@ Transitions: enriching \u2192 needs_input. The user sees the blockerReason and q
3855
3747
 
3856
3748
  Same semantics as yapout_block_enrichment but for an entire bundle. Bundle status transitions enriching \u2192 needs_input; child findings revert to draft.`,
3857
3749
  {
3858
- bundleId: z20.string().describe("The bundle ID to block (currently in 'enriching')"),
3859
- blockerReason: z20.string().describe("One sentence: why this bundle cannot be enriched."),
3860
- blockerQuestions: z20.array(z20.string()).min(1).describe("2-5 specific questions the user must answer.")
3750
+ bundleId: z16.string().describe("The bundle ID to block (currently in 'enriching')"),
3751
+ blockerReason: z16.string().describe("One sentence: why this bundle cannot be enriched."),
3752
+ blockerQuestions: z16.array(z16.string()).min(1).describe("2-5 specific questions the user must answer.")
3861
3753
  },
3862
3754
  async (args) => {
3863
3755
  try {
@@ -5129,12 +5021,12 @@ async function startMcpServer() {
5129
5021
  };
5130
5022
  const server = new McpServer({
5131
5023
  name: "yapout",
5132
- version: "0.16.0"
5024
+ version: "0.18.0"
5133
5025
  });
5134
5026
  registerInitTool(server, ctx);
5135
5027
  registerCompactTool(server, ctx);
5136
5028
  registerUpdateContextTool(server, ctx);
5137
- registerQueueTool(server, ctx);
5029
+ registerDevLoopTools(server, ctx);
5138
5030
  registerGetBriefTool(server, ctx);
5139
5031
  registerClaimTool(server, ctx);
5140
5032
  registerEventTool(server, ctx);
@@ -5144,9 +5036,6 @@ async function startMcpServer() {
5144
5036
  registerGetExistingFindingsTool(server, ctx);
5145
5037
  registerSaveEnrichmentTool(server, ctx);
5146
5038
  registerSyncToLinearTool(server, ctx);
5147
- registerSubmitYapSessionTool(server, ctx);
5148
- registerStartYapTool(server, ctx);
5149
- registerExtractFromYapTool(server, ctx);
5150
5039
  registerDecomposeFindingTool(server, ctx);
5151
5040
  registerMarkDuplicateTool(server, ctx);
5152
5041
  registerGetLinearProjectsTool(server, ctx);
@@ -5174,108 +5063,133 @@ var mcpServerCommand = new Command8("mcp-server").description("Start the MCP ser
5174
5063
 
5175
5064
  // src/commands/queue.ts
5176
5065
  import { Command as Command9 } from "commander";
5177
- import { resolve as resolve6 } from "path";
5066
+ import { resolve as resolve7 } from "path";
5178
5067
  import chalk9 from "chalk";
5179
- var queueCommand = new Command9("queue").description("Show pipeline state \u2014 what's ready, blocked, and pending").action(async () => {
5068
+ var queueCommand = new Command9("queue").description("Show Directives for this repo's Resource").option("--all", "Include drafted/vetted/done/cancelled directives", false).option("--limit <n>", "Max directives to fetch (default 100)", "100").action(async (options) => {
5180
5069
  const creds = requireAuth();
5181
- const cwd = resolve6(process.cwd());
5182
- const mapping = getProjectMapping(cwd);
5183
- if (!mapping) {
5070
+ const cwd = resolve7(process.cwd());
5071
+ const repoRoot = resolveRepoRoot(cwd);
5072
+ const resourceCfg = readResourceConfig(repoRoot);
5073
+ if (!resourceCfg) {
5184
5074
  console.error(
5185
- chalk9.red("No project linked.") + " Run " + chalk9.cyan("yapout link") + " in a repo."
5075
+ chalk9.red("No `.yapout/config.json` found.") + " Run " + chalk9.cyan("yapout init") + " in this repo first."
5186
5076
  );
5187
5077
  process.exit(1);
5188
5078
  }
5079
+ const limit = Math.max(1, parseInt(options.limit, 10) || 100);
5189
5080
  const client = createConvexClient(creds.token);
5190
- const { anyApi: anyApi6 } = await import("convex/server");
5191
- const [queueData, unenriched, pending] = await Promise.all([
5192
- client.query(anyApi6.functions.tickets.getLocalQueuedTickets, {
5193
- projectId: mapping.projectId
5194
- }),
5195
- client.query(anyApi6.functions.localPipeline.getUnenrichedTickets, {
5196
- projectId: mapping.projectId
5197
- }),
5198
- client.query(anyApi6.functions.localPipeline.getPendingSources, {
5199
- projectId: mapping.projectId
5200
- })
5201
- ]);
5202
- console.log();
5203
- if (queueData?.ready && queueData.ready.length > 0) {
5204
- console.log(chalk9.bold("Ready to implement:"));
5205
- for (const t of queueData.ready) {
5206
- const ref = t.linearTicketId ?? t.ticketId;
5207
- const prio = colorPriority(t.priority);
5208
- console.log(
5209
- ` ${chalk9.bold(ref)} ${t.title.slice(0, 45).padEnd(45)} ${prio} ${chalk9.dim(t.type)}`
5210
- );
5211
- }
5212
- console.log();
5081
+ let result;
5082
+ try {
5083
+ result = await client.query(
5084
+ anyApi.functions.dashboardNewModel.listDirectivesForResource,
5085
+ {
5086
+ resourceId: resourceCfg.resourceId,
5087
+ limit
5088
+ }
5089
+ );
5090
+ } catch (err) {
5091
+ console.error(
5092
+ chalk9.red("Failed to load directive queue."),
5093
+ err.message
5094
+ );
5095
+ process.exit(1);
5213
5096
  }
5214
- if (queueData?.blocked && queueData.blocked.length > 0) {
5215
- console.log(chalk9.bold("Blocked:"));
5216
- for (const t of queueData.blocked) {
5217
- console.log(
5218
- ` ${chalk9.bold(t.ticketId.slice(-6))} ${t.title.slice(0, 45)} ${chalk9.red("blocked by " + t.blockedBy.map((id) => id.slice(-6)).join(", "))}`
5219
- );
5220
- }
5221
- console.log();
5097
+ if (!result) {
5098
+ console.error(
5099
+ chalk9.red("No access.") + " The Resource bound in `.yapout/config.json` is unreachable. Check `yapout status` and re-run `yapout init` if needed."
5100
+ );
5101
+ process.exit(1);
5222
5102
  }
5223
- if (unenriched && unenriched.length > 0) {
5224
- console.log(chalk9.bold("Needs enrichment:"));
5225
- for (const t of unenriched) {
5226
- const ref = t.ticketId;
5227
- console.log(
5228
- ` ${chalk9.bold(ref.slice(-6))} ${t.title.slice(0, 45).padEnd(45)} ${chalk9.dim(t.priority)}`
5229
- );
5230
- }
5103
+ console.log();
5104
+ console.log(
5105
+ chalk9.bold(result.resource.remoteUrl ?? result.resource.canonicalId) + chalk9.dim(` (resource ${result.resource._id})`)
5106
+ );
5107
+ const interesting = options.all ? result.directives : result.directives.filter(
5108
+ (d) => ["queued", "in_progress", "review"].includes(d.status)
5109
+ );
5110
+ if (interesting.length === 0) {
5111
+ const filter = options.all ? "" : " (use --all to include drafted/done)";
5112
+ console.log(chalk9.dim(`No directives.${filter}`));
5231
5113
  console.log();
5114
+ return;
5232
5115
  }
5233
- if (pending && pending.length > 0) {
5234
- console.log(chalk9.bold("Pending extraction:"));
5235
- for (const t of pending) {
5236
- const ago = formatAgo(Date.now() - t.createdAt);
5116
+ const buckets = {};
5117
+ for (const d of interesting) {
5118
+ (buckets[d.status] ??= []).push(d);
5119
+ }
5120
+ const order = options.all ? [
5121
+ "queued",
5122
+ "in_progress",
5123
+ "review",
5124
+ "vetted",
5125
+ "drafted",
5126
+ "deployed",
5127
+ "done",
5128
+ "cancelled"
5129
+ ] : ["queued", "in_progress", "review"];
5130
+ for (const status of order) {
5131
+ const rows = buckets[status];
5132
+ if (!rows || rows.length === 0) continue;
5133
+ console.log();
5134
+ console.log(
5135
+ chalk9.bold(labelForStatus2(status)) + chalk9.dim(` (${rows.length})`)
5136
+ );
5137
+ for (const d of rows) {
5138
+ const idTail = d._id.slice(-6);
5139
+ const title = truncate2(d.title, 55).padEnd(55);
5140
+ const verb = d.actionVerb ? chalk9.dim(d.actionVerb) : "";
5141
+ const exec2 = d.latestExecution ? chalk9.dim(
5142
+ `[exec ${d.latestExecution._id.slice(-6)} ${d.latestExecution.devStatus}]`
5143
+ ) : "";
5237
5144
  console.log(
5238
- ` "${t.meetingTitle ?? "Untitled"}" ${chalk9.dim(`(uploaded ${ago})`)}`
5145
+ ` ${chalk9.bold(idTail)} ${title} ${verb} ${exec2}`.trimEnd()
5239
5146
  );
5147
+ if (d.parentRequestTitles.length > 0) {
5148
+ const parentSummary = d.parentRequestTitles.map((t) => truncate2(t, 40)).join(" | ");
5149
+ console.log(
5150
+ chalk9.dim(
5151
+ ` \u21B3 ${parentSummary}` + (d.parentRequestCount > d.parentRequestTitles.length ? ` (+${d.parentRequestCount - d.parentRequestTitles.length} more)` : "")
5152
+ )
5153
+ );
5154
+ }
5240
5155
  }
5241
- console.log();
5242
- }
5243
- if ((!queueData?.ready || queueData.ready.length === 0) && (!unenriched || unenriched.length === 0) && (!pending || pending.length === 0)) {
5244
- console.log(chalk9.dim("Queue is empty. Upload a transcript to get started."));
5245
- console.log();
5246
5156
  }
5157
+ console.log();
5247
5158
  });
5248
- function colorPriority(p) {
5249
- switch (p) {
5250
- case "urgent":
5251
- return chalk9.red(p);
5252
- case "high":
5253
- return chalk9.yellow(p);
5254
- case "medium":
5255
- return chalk9.white(p);
5256
- case "low":
5257
- return chalk9.dim(p);
5159
+ function labelForStatus2(status) {
5160
+ switch (status) {
5161
+ case "queued":
5162
+ return "Ready to pick";
5163
+ case "in_progress":
5164
+ return "In progress";
5165
+ case "review":
5166
+ return "In review";
5167
+ case "vetted":
5168
+ return "Vetted (PM-side)";
5169
+ case "drafted":
5170
+ return "Drafted (PM-side)";
5171
+ case "deployed":
5172
+ return "Deployed";
5173
+ case "done":
5174
+ return "Done";
5175
+ case "cancelled":
5176
+ return "Cancelled";
5258
5177
  default:
5259
- return p;
5178
+ return status;
5260
5179
  }
5261
5180
  }
5262
- function formatAgo(ms) {
5263
- const s = Math.floor(ms / 1e3);
5264
- if (s < 60) return `${s}s ago`;
5265
- const m = Math.floor(s / 60);
5266
- if (m < 60) return `${m}m ago`;
5267
- const h = Math.floor(m / 60);
5268
- if (h < 24) return `${h}h ago`;
5269
- return `${Math.floor(h / 24)}d ago`;
5181
+ function truncate2(s, max) {
5182
+ if (s.length <= max) return s;
5183
+ return s.slice(0, max - 1) + "\u2026";
5270
5184
  }
5271
5185
 
5272
5186
  // src/commands/recap.ts
5273
5187
  import { Command as Command10 } from "commander";
5274
- import { resolve as resolve7 } from "path";
5188
+ import { resolve as resolve8 } from "path";
5275
5189
  import chalk10 from "chalk";
5276
5190
  var recapCommand = new Command10("recap").description("Show a summary of recent yapout activity").option("--week", "Show full week summary (default: today)").action(async (opts) => {
5277
5191
  const creds = requireAuth();
5278
- const cwd = resolve7(process.cwd());
5192
+ const cwd = resolve8(process.cwd());
5279
5193
  const mapping = getProjectMapping(cwd);
5280
5194
  if (!mapping) {
5281
5195
  console.error(
@@ -5329,44 +5243,13 @@ var recapCommand = new Command10("recap").description("Show a summary of recent
5329
5243
  console.log();
5330
5244
  });
5331
5245
 
5332
- // src/commands/yap.ts
5246
+ // src/commands/agent.ts
5333
5247
  import { Command as Command11 } from "commander";
5334
5248
  import chalk11 from "chalk";
5335
- var yapCommand = new Command11("yap").description(
5336
- "Start a yap session \u2014 brainstorm with an AI that knows your codebase"
5337
- ).action(() => {
5338
- console.log(chalk11.bold("yapout yap sessions"));
5339
- console.log();
5340
- console.log(
5341
- "Yap sessions run inside Claude \u2014 Desktop, CLI, or web \u2014 anywhere the"
5342
- );
5343
- console.log("yapout MCP server is connected.");
5344
- console.log();
5345
- console.log("Just tell Claude:");
5346
- console.log(
5347
- chalk11.green(` "Let's have a yap session about [topic]"`)
5348
- );
5349
- console.log(
5350
- chalk11.green(
5351
- ' "I want to brainstorm [idea] \u2014 be a skeptical QA engineer"'
5352
- )
5353
- );
5354
- console.log();
5355
- console.log(chalk11.dim("Personas: tech lead, qa engineer, product owner, end user, or custom"));
5356
- console.log(
5357
- chalk11.dim(
5358
- "Claude will call yapout_start_yap to get instructions and yapout_submit_yap_session when done."
5359
- )
5360
- );
5361
- });
5362
-
5363
- // src/commands/agent.ts
5364
- import { Command as Command12 } from "commander";
5365
- import chalk12 from "chalk";
5366
5249
  import { input, confirm } from "@inquirer/prompts";
5367
- import { resolve as resolve8 } from "path";
5250
+ import { resolve as resolve9 } from "path";
5368
5251
  async function resolveProjectId() {
5369
- const cwd = resolve8(process.cwd());
5252
+ const cwd = resolve9(process.cwd());
5370
5253
  const mapping = getProjectMapping(cwd);
5371
5254
  if (!mapping) {
5372
5255
  throw new Error(
@@ -5378,7 +5261,7 @@ async function resolveProjectId() {
5378
5261
  projectName: mapping.projectName
5379
5262
  };
5380
5263
  }
5381
- var agentCreate = new Command12("create").description("Create a new agent identity with a fresh token").option("--name <name>", "Display name (e.g. 'frontend-implementer')").option("--role <description>", "One-line role description").option(
5264
+ var agentCreate = new Command11("create").description("Create a new agent identity with a fresh token").option("--name <name>", "Display name (e.g. 'frontend-implementer')").option("--role <description>", "One-line role description").option(
5382
5265
  "--scopes <scopes>",
5383
5266
  "Comma-separated list of MCP tool names, or '*' for full access",
5384
5267
  "*"
@@ -5399,23 +5282,23 @@ var agentCreate = new Command12("create").description("Create a new agent identi
5399
5282
  label: opts.label
5400
5283
  }
5401
5284
  );
5402
- console.log(chalk12.green(`Agent "${name}" created in ${projectName}.`));
5285
+ console.log(chalk11.green(`Agent "${name}" created in ${projectName}.`));
5403
5286
  console.log("");
5404
- console.log(chalk12.dim("agentUserId:"), result.agentUserId);
5405
- console.log(chalk12.dim("tokenId: "), result.tokenId);
5406
- console.log(chalk12.dim("token: "), chalk12.yellow(result.rawToken));
5287
+ console.log(chalk11.dim("agentUserId:"), result.agentUserId);
5288
+ console.log(chalk11.dim("tokenId: "), result.tokenId);
5289
+ console.log(chalk11.dim("token: "), chalk11.yellow(result.rawToken));
5407
5290
  console.log("");
5408
5291
  console.log(
5409
- chalk12.bold("Store this token now."),
5292
+ chalk11.bold("Store this token now."),
5410
5293
  "Yapout keeps only the SHA-256 hash; this is the only time it's shown."
5411
5294
  );
5412
5295
  console.log(
5413
5296
  "Pass to the harness via the",
5414
- chalk12.cyan("YAPOUT_AGENT_TOKEN"),
5297
+ chalk11.cyan("YAPOUT_AGENT_TOKEN"),
5415
5298
  "env var."
5416
5299
  );
5417
5300
  });
5418
- var agentList = new Command12("list").description("List agents in the current project").action(async () => {
5301
+ var agentList = new Command11("list").description("List agents in the current project").action(async () => {
5419
5302
  const creds = requireAuth();
5420
5303
  const { projectId, projectName } = await resolveProjectId();
5421
5304
  const client = createConvexClient(creds.token);
@@ -5424,26 +5307,26 @@ var agentList = new Command12("list").description("List agents in the current pr
5424
5307
  { projectId }
5425
5308
  );
5426
5309
  if (rows.length === 0) {
5427
- console.log(chalk12.dim(`No agents in ${projectName}.`));
5310
+ console.log(chalk11.dim(`No agents in ${projectName}.`));
5428
5311
  return;
5429
5312
  }
5430
- console.log(chalk12.bold(`Agents in ${projectName}:`));
5313
+ console.log(chalk11.bold(`Agents in ${projectName}:`));
5431
5314
  for (const a of rows) {
5432
- const role = a.roleDescription ? chalk12.dim(` \u2014 ${a.roleDescription}`) : "";
5433
- console.log(` ${chalk12.cyan(a.displayName)}${role}`);
5434
- console.log(chalk12.dim(` id: ${a._id}`));
5315
+ const role = a.roleDescription ? chalk11.dim(` \u2014 ${a.roleDescription}`) : "";
5316
+ console.log(` ${chalk11.cyan(a.displayName)}${role}`);
5317
+ console.log(chalk11.dim(` id: ${a._id}`));
5435
5318
  console.log(
5436
- chalk12.dim(` created: ${new Date(a.createdAt).toLocaleString()}`)
5319
+ chalk11.dim(` created: ${new Date(a.createdAt).toLocaleString()}`)
5437
5320
  );
5438
5321
  }
5439
5322
  });
5440
- var agentRevoke = new Command12("revoke").description("Revoke an agent token by tokenId").argument("<tokenId>", "Token id (from `yapout agent list` or creation output)").action(async (tokenId) => {
5323
+ var agentRevoke = new Command11("revoke").description("Revoke an agent token by tokenId").argument("<tokenId>", "Token id (from `yapout agent list` or creation output)").action(async (tokenId) => {
5441
5324
  const creds = requireAuth();
5442
5325
  const client = createConvexClient(creds.token);
5443
5326
  await client.mutation(anyApi.functions.agents.revokeToken, { tokenId });
5444
- console.log(chalk12.green("Token revoked."));
5327
+ console.log(chalk11.green("Token revoked."));
5445
5328
  });
5446
- var agentRotate = new Command12("rotate").description("Issue a new token and revoke the old one (for an existing agent)").argument("<tokenId>", "Old token id to rotate").option(
5329
+ var agentRotate = new Command11("rotate").description("Issue a new token and revoke the old one (for an existing agent)").argument("<tokenId>", "Old token id to rotate").option(
5447
5330
  "--scopes <scopes>",
5448
5331
  "Comma-separated MCP tool names or '*'. Defaults to the old token's scopes."
5449
5332
  ).option("--label <label>", "New token label").action(async (oldTokenId, opts) => {
@@ -5454,7 +5337,7 @@ var agentRotate = new Command12("rotate").description("Issue a new token and rev
5454
5337
  { tokenId: oldTokenId }
5455
5338
  );
5456
5339
  if (!oldToken) {
5457
- console.error(chalk12.red("Old token not found or you don't have access."));
5340
+ console.error(chalk11.red("Old token not found or you don't have access."));
5458
5341
  process.exit(1);
5459
5342
  }
5460
5343
  const scopes = opts.scopes ? opts.scopes === "*" ? ["*"] : opts.scopes.split(",").map((s) => s.trim()).filter(Boolean) : oldToken.scopes;
@@ -5480,21 +5363,21 @@ var agentRotate = new Command12("rotate").description("Issue a new token and rev
5480
5363
  tokenId: oldTokenId
5481
5364
  });
5482
5365
  console.log(
5483
- chalk12.green(`Token rotated for "${oldToken.agentDisplayName ?? oldToken.agentUserId}".`)
5366
+ chalk11.green(`Token rotated for "${oldToken.agentDisplayName ?? oldToken.agentUserId}".`)
5484
5367
  );
5485
- console.log(chalk12.dim("new tokenId:"), created.tokenId);
5486
- console.log(chalk12.dim("token: "), chalk12.yellow(created.rawToken));
5368
+ console.log(chalk11.dim("new tokenId:"), created.tokenId);
5369
+ console.log(chalk11.dim("token: "), chalk11.yellow(created.rawToken));
5487
5370
  console.log("");
5488
- console.log(chalk12.bold("Store this token now."), "Old token revoked.");
5371
+ console.log(chalk11.bold("Store this token now."), "Old token revoked.");
5489
5372
  });
5490
- var agentCommand = new Command12("agent").description("Manage agent identities and tokens").addCommand(agentCreate).addCommand(agentList).addCommand(agentRevoke).addCommand(agentRotate);
5373
+ var agentCommand = new Command11("agent").description("Manage agent identities and tokens").addCommand(agentCreate).addCommand(agentList).addCommand(agentRevoke).addCommand(agentRotate);
5491
5374
 
5492
5375
  // src/commands/webhook.ts
5493
- import { Command as Command13 } from "commander";
5494
- import chalk13 from "chalk";
5376
+ import { Command as Command12 } from "commander";
5377
+ import chalk12 from "chalk";
5495
5378
  import { input as input2, checkbox } from "@inquirer/prompts";
5496
5379
  import { randomBytes } from "crypto";
5497
- import { resolve as resolve9 } from "path";
5380
+ import { resolve as resolve10 } from "path";
5498
5381
  var SUPPORTED_EVENTS = [
5499
5382
  "finding.status_changed",
5500
5383
  "finding.created",
@@ -5506,7 +5389,7 @@ var SUPPORTED_EVENTS = [
5506
5389
  "settings.changed"
5507
5390
  ];
5508
5391
  async function resolveProjectId2() {
5509
- const cwd = resolve9(process.cwd());
5392
+ const cwd = resolve10(process.cwd());
5510
5393
  const mapping = getProjectMapping(cwd);
5511
5394
  if (!mapping) {
5512
5395
  throw new Error(
@@ -5515,7 +5398,7 @@ async function resolveProjectId2() {
5515
5398
  }
5516
5399
  return { projectId: mapping.projectId, projectName: mapping.projectName };
5517
5400
  }
5518
- var webhookList = new Command13("list").description("List outbound webhooks for the current project").action(async () => {
5401
+ var webhookList = new Command12("list").description("List outbound webhooks for the current project").action(async () => {
5519
5402
  const creds = requireAuth();
5520
5403
  const { projectId, projectName } = await resolveProjectId2();
5521
5404
  const client = createConvexClient(creds.token);
@@ -5524,19 +5407,19 @@ var webhookList = new Command13("list").description("List outbound webhooks for
5524
5407
  { projectId }
5525
5408
  );
5526
5409
  if (rows.length === 0) {
5527
- console.log(chalk13.dim(`No webhooks for ${projectName}.`));
5410
+ console.log(chalk12.dim(`No webhooks for ${projectName}.`));
5528
5411
  return;
5529
5412
  }
5530
- console.log(chalk13.bold(`Webhooks for ${projectName}:`));
5413
+ console.log(chalk12.bold(`Webhooks for ${projectName}:`));
5531
5414
  for (const w of rows) {
5532
- const status = w.active ? chalk13.green("active") : chalk13.red(`disabled (${w.consecutiveFailures} failures)`);
5533
- console.log(` ${chalk13.cyan(w.label ?? w.url)} \u2014 ${status}`);
5534
- console.log(chalk13.dim(` id: ${w._id}`));
5535
- console.log(chalk13.dim(` url: ${w.url}`));
5536
- console.log(chalk13.dim(` events: ${w.events.join(", ")}`));
5415
+ const status = w.active ? chalk12.green("active") : chalk12.red(`disabled (${w.consecutiveFailures} failures)`);
5416
+ console.log(` ${chalk12.cyan(w.label ?? w.url)} \u2014 ${status}`);
5417
+ console.log(chalk12.dim(` id: ${w._id}`));
5418
+ console.log(chalk12.dim(` url: ${w.url}`));
5419
+ console.log(chalk12.dim(` events: ${w.events.join(", ")}`));
5537
5420
  }
5538
5421
  });
5539
- var webhookCreate = new Command13("create").description("Create an outbound webhook subscription").option("--url <url>", "Webhook target URL").option("--label <label>", "Human-readable label").option(
5422
+ var webhookCreate = new Command12("create").description("Create an outbound webhook subscription").option("--url <url>", "Webhook target URL").option("--label <label>", "Human-readable label").option(
5540
5423
  "--events <list>",
5541
5424
  `Comma-separated event names. Available: ${SUPPORTED_EVENTS.join(", ")}`
5542
5425
  ).option(
@@ -5563,23 +5446,23 @@ var webhookCreate = new Command13("create").description("Create an outbound webh
5563
5446
  anyApi.functions.webhooks.createWebhook,
5564
5447
  { projectId, url, label, events, secret }
5565
5448
  );
5566
- console.log(chalk13.green(`Webhook created in ${projectName}.`));
5567
- console.log(chalk13.dim("webhookId:"), result.webhookId);
5449
+ console.log(chalk12.green(`Webhook created in ${projectName}.`));
5450
+ console.log(chalk12.dim("webhookId:"), result.webhookId);
5568
5451
  if (!opts.secret) {
5569
- console.log(chalk13.dim("secret: "), chalk13.yellow(secret));
5452
+ console.log(chalk12.dim("secret: "), chalk12.yellow(secret));
5570
5453
  console.log(
5571
- chalk13.bold("Store this secret now."),
5454
+ chalk12.bold("Store this secret now."),
5572
5455
  "Yapout keeps only the encrypted form; the receiver needs the plaintext to verify HMAC signatures."
5573
5456
  );
5574
5457
  }
5575
5458
  });
5576
- var webhookDelete = new Command13("delete").description("Delete a webhook subscription").argument("<webhookId>").action(async (webhookId) => {
5459
+ var webhookDelete = new Command12("delete").description("Delete a webhook subscription").argument("<webhookId>").action(async (webhookId) => {
5577
5460
  const creds = requireAuth();
5578
5461
  const client = createConvexClient(creds.token);
5579
5462
  await client.mutation(anyApi.functions.webhooks.deleteWebhook, { webhookId });
5580
- console.log(chalk13.green("Webhook deleted."));
5463
+ console.log(chalk12.green("Webhook deleted."));
5581
5464
  });
5582
- var webhookTest = new Command13("test").description("Inspect recent delivery attempts for a webhook").argument("<webhookId>").option("-n, --limit <n>", "Number of deliveries to show", "20").action(async (webhookId, opts) => {
5465
+ var webhookTest = new Command12("test").description("Inspect recent delivery attempts for a webhook").argument("<webhookId>").option("-n, --limit <n>", "Number of deliveries to show", "20").action(async (webhookId, opts) => {
5583
5466
  const creds = requireAuth();
5584
5467
  const client = createConvexClient(creds.token);
5585
5468
  const limit = parseInt(opts.limit, 10);
@@ -5588,28 +5471,28 @@ var webhookTest = new Command13("test").description("Inspect recent delivery att
5588
5471
  { webhookId, limit }
5589
5472
  );
5590
5473
  if (rows.length === 0) {
5591
- console.log(chalk13.dim("No deliveries yet."));
5474
+ console.log(chalk12.dim("No deliveries yet."));
5592
5475
  return;
5593
5476
  }
5594
5477
  for (const d of rows) {
5595
- const statusLabel = d.status === "success" ? chalk13.green(d.status) : d.status === "failed" ? chalk13.red(d.status) : chalk13.yellow(d.status);
5478
+ const statusLabel = d.status === "success" ? chalk12.green(d.status) : d.status === "failed" ? chalk12.red(d.status) : chalk12.yellow(d.status);
5596
5479
  const time = new Date(d.createdAt).toLocaleString();
5597
5480
  const http3 = d.httpStatus !== void 0 ? ` HTTP ${d.httpStatus}` : "";
5598
- const err = d.error ? chalk13.dim(` (${d.error})`) : "";
5481
+ const err = d.error ? chalk12.dim(` (${d.error})`) : "";
5599
5482
  console.log(
5600
- ` ${time} \u2014 ${statusLabel} attempt ${d.attempt}${http3} \u2014 ${chalk13.cyan(d.event)}${err}`
5483
+ ` ${time} \u2014 ${statusLabel} attempt ${d.attempt}${http3} \u2014 ${chalk12.cyan(d.event)}${err}`
5601
5484
  );
5602
5485
  }
5603
5486
  });
5604
- var webhookCommand = new Command13("webhook").description("Manage outbound webhook subscriptions").addCommand(webhookList).addCommand(webhookCreate).addCommand(webhookDelete).addCommand(webhookTest);
5487
+ var webhookCommand = new Command12("webhook").description("Manage outbound webhook subscriptions").addCommand(webhookList).addCommand(webhookCreate).addCommand(webhookDelete).addCommand(webhookTest);
5605
5488
 
5606
5489
  // src/commands/inbound.ts
5607
- import { Command as Command14 } from "commander";
5608
- import chalk14 from "chalk";
5490
+ import { Command as Command13 } from "commander";
5491
+ import chalk13 from "chalk";
5609
5492
  import { input as input3 } from "@inquirer/prompts";
5610
- import { resolve as resolve10 } from "path";
5493
+ import { resolve as resolve11 } from "path";
5611
5494
  async function resolveProjectId3() {
5612
- const cwd = resolve10(process.cwd());
5495
+ const cwd = resolve11(process.cwd());
5613
5496
  const mapping = getProjectMapping(cwd);
5614
5497
  if (!mapping) {
5615
5498
  throw new Error(
@@ -5618,7 +5501,7 @@ async function resolveProjectId3() {
5618
5501
  }
5619
5502
  return { projectId: mapping.projectId, projectName: mapping.projectName };
5620
5503
  }
5621
- var inboundList = new Command14("list").description("List inbound webhook tokens for the current project").action(async () => {
5504
+ var inboundList = new Command13("list").description("List inbound webhook tokens for the current project").action(async () => {
5622
5505
  const creds = requireAuth();
5623
5506
  const { projectId, projectName } = await resolveProjectId3();
5624
5507
  const client = createConvexClient(creds.token);
@@ -5627,20 +5510,20 @@ var inboundList = new Command14("list").description("List inbound webhook tokens
5627
5510
  { projectId }
5628
5511
  );
5629
5512
  if (rows.length === 0) {
5630
- console.log(chalk14.dim(`No inbound webhook tokens for ${projectName}.`));
5513
+ console.log(chalk13.dim(`No inbound webhook tokens for ${projectName}.`));
5631
5514
  return;
5632
5515
  }
5633
- console.log(chalk14.bold(`Inbound tokens for ${projectName}:`));
5516
+ console.log(chalk13.bold(`Inbound tokens for ${projectName}:`));
5634
5517
  for (const t of rows) {
5635
- const status = t.revokedAt ? chalk14.red("revoked") : t.lastUsedAt ? chalk14.green(`last used ${new Date(t.lastUsedAt).toLocaleString()}`) : chalk14.dim("unused");
5636
- console.log(` ${chalk14.cyan(t.label)} \u2014 ${status}`);
5637
- console.log(chalk14.dim(` id: ${t._id}`));
5518
+ const status = t.revokedAt ? chalk13.red("revoked") : t.lastUsedAt ? chalk13.green(`last used ${new Date(t.lastUsedAt).toLocaleString()}`) : chalk13.dim("unused");
5519
+ console.log(` ${chalk13.cyan(t.label)} \u2014 ${status}`);
5520
+ console.log(chalk13.dim(` id: ${t._id}`));
5638
5521
  console.log(
5639
- chalk14.dim(` created: ${new Date(t.createdAt).toLocaleString()}`)
5522
+ chalk13.dim(` created: ${new Date(t.createdAt).toLocaleString()}`)
5640
5523
  );
5641
5524
  }
5642
5525
  });
5643
- var inboundCreate = new Command14("create").description("Issue a new inbound webhook token").option("--label <label>", "Human-readable label").action(async (opts) => {
5526
+ var inboundCreate = new Command13("create").description("Issue a new inbound webhook token").option("--label <label>", "Human-readable label").action(async (opts) => {
5644
5527
  const creds = requireAuth();
5645
5528
  const { projectId, projectName } = await resolveProjectId3();
5646
5529
  const client = createConvexClient(creds.token);
@@ -5653,15 +5536,15 @@ var inboundCreate = new Command14("create").description("Issue a new inbound web
5653
5536
  const cloudUrl = getConvexUrl();
5654
5537
  return cloudUrl.replace(/\.convex\.cloud(\/.*)?$/, ".convex.site");
5655
5538
  })();
5656
- console.log(chalk14.green(`Inbound token created in ${projectName}.`));
5657
- console.log(chalk14.dim("tokenId:"), result.tokenId);
5658
- console.log(chalk14.dim("token: "), chalk14.yellow(result.rawToken));
5539
+ console.log(chalk13.green(`Inbound token created in ${projectName}.`));
5540
+ console.log(chalk13.dim("tokenId:"), result.tokenId);
5541
+ console.log(chalk13.dim("token: "), chalk13.yellow(result.rawToken));
5659
5542
  console.log("");
5660
- console.log(chalk14.bold("Store this token now."), "Yapout keeps only the SHA-256 hash.");
5543
+ console.log(chalk13.bold("Store this token now."), "Yapout keeps only the SHA-256 hash.");
5661
5544
  console.log("");
5662
5545
  console.log("Usage:");
5663
5546
  console.log(
5664
- ` ${chalk14.cyan("curl")} -X POST ${convexSiteUrl}/api/inbound/${projectId} \\`
5547
+ ` ${chalk13.cyan("curl")} -X POST ${convexSiteUrl}/api/inbound/${projectId} \\`
5665
5548
  );
5666
5549
  console.log(` -H "Authorization: Bearer ${result.rawToken}" \\`);
5667
5550
  console.log(` -H "Content-Type: application/json" \\`);
@@ -5669,22 +5552,22 @@ var inboundCreate = new Command14("create").description("Issue a new inbound web
5669
5552
  ` -d '{ "content": "<raw text>", "sourceLabel": "${label}" }'`
5670
5553
  );
5671
5554
  });
5672
- var inboundRevoke = new Command14("revoke").description("Revoke an inbound webhook token").argument("<tokenId>").action(async (tokenId) => {
5555
+ var inboundRevoke = new Command13("revoke").description("Revoke an inbound webhook token").argument("<tokenId>").action(async (tokenId) => {
5673
5556
  const creds = requireAuth();
5674
5557
  const client = createConvexClient(creds.token);
5675
5558
  await client.mutation(anyApi.functions.inboundTokens.revokeInboundToken, {
5676
5559
  tokenId
5677
5560
  });
5678
- console.log(chalk14.green("Inbound token revoked."));
5561
+ console.log(chalk13.green("Inbound token revoked."));
5679
5562
  });
5680
- var inboundCommand = new Command14("inbound").description("Manage inbound webhook tokens (long-tail capture sources)").addCommand(inboundList).addCommand(inboundCreate).addCommand(inboundRevoke);
5563
+ var inboundCommand = new Command13("inbound").description("Manage inbound webhook tokens (long-tail capture sources)").addCommand(inboundList).addCommand(inboundCreate).addCommand(inboundRevoke);
5681
5564
 
5682
5565
  // src/commands/pillar.ts
5683
- import { Command as Command15 } from "commander";
5684
- import chalk15 from "chalk";
5566
+ import { Command as Command14 } from "commander";
5567
+ import chalk14 from "chalk";
5685
5568
  import { input as input4, select as select2 } from "@inquirer/prompts";
5686
5569
  import { randomBytes as randomBytes2 } from "crypto";
5687
- import { resolve as resolve11 } from "path";
5570
+ import { resolve as resolve12 } from "path";
5688
5571
  var PILLARS = [
5689
5572
  "extraction",
5690
5573
  "enrichment",
@@ -5693,7 +5576,7 @@ var PILLARS = [
5693
5576
  ];
5694
5577
  var MODES = ["cloud-default", "disabled", "external-webhook"];
5695
5578
  async function resolveProjectId4() {
5696
- const cwd = resolve11(process.cwd());
5579
+ const cwd = resolve12(process.cwd());
5697
5580
  const mapping = getProjectMapping(cwd);
5698
5581
  if (!mapping) {
5699
5582
  throw new Error(
@@ -5705,14 +5588,14 @@ async function resolveProjectId4() {
5705
5588
  function modeBadge(mode) {
5706
5589
  switch (mode) {
5707
5590
  case "cloud-default":
5708
- return chalk15.dim("cloud");
5591
+ return chalk14.dim("cloud");
5709
5592
  case "disabled":
5710
- return chalk15.gray("disabled");
5593
+ return chalk14.gray("disabled");
5711
5594
  case "external-webhook":
5712
- return chalk15.green("webhook");
5595
+ return chalk14.green("webhook");
5713
5596
  }
5714
5597
  }
5715
- var pillarList = new Command15("list").description("Show current modes for all four pillars").action(async () => {
5598
+ var pillarList = new Command14("list").description("Show current modes for all four pillars").action(async () => {
5716
5599
  const creds = requireAuth();
5717
5600
  const { projectId, projectName } = await resolveProjectId4();
5718
5601
  const client = createConvexClient(creds.token);
@@ -5720,17 +5603,17 @@ var pillarList = new Command15("list").description("Show current modes for all f
5720
5603
  anyApi.functions.projectConfig.getProjectConfig,
5721
5604
  { projectId }
5722
5605
  );
5723
- console.log(chalk15.bold(`Pillars for ${projectName}:`));
5606
+ console.log(chalk14.bold(`Pillars for ${projectName}:`));
5724
5607
  for (const p of PILLARS) {
5725
5608
  const setting = config?.pillars?.[p] ?? { mode: "cloud-default" };
5726
- const url = setting.mode === "external-webhook" && setting.webhookUrl ? chalk15.dim(` \u2192 ${setting.webhookUrl}`) : "";
5609
+ const url = setting.mode === "external-webhook" && setting.webhookUrl ? chalk14.dim(` \u2192 ${setting.webhookUrl}`) : "";
5727
5610
  console.log(` ${p.padEnd(15)} ${modeBadge(setting.mode)}${url}`);
5728
5611
  }
5729
5612
  });
5730
- var pillarSet = new Command15("set").description("Set a pillar's mode (interactive when setting external-webhook)").argument("<pillar>", "extraction | enrichment | intelligence | implementation").argument("[mode]", "cloud-default | disabled | external-webhook").option("--url <url>", "Webhook URL (for external-webhook mode)").option("--reason <reason>", "Optional reason captured in the audit log").action(
5613
+ var pillarSet = new Command14("set").description("Set a pillar's mode (interactive when setting external-webhook)").argument("<pillar>", "extraction | enrichment | intelligence | implementation").argument("[mode]", "cloud-default | disabled | external-webhook").option("--url <url>", "Webhook URL (for external-webhook mode)").option("--reason <reason>", "Optional reason captured in the audit log").action(
5731
5614
  async (pillar, mode, opts) => {
5732
5615
  if (!PILLARS.includes(pillar)) {
5733
- console.error(chalk15.red(`Unknown pillar "${pillar}".`));
5616
+ console.error(chalk14.red(`Unknown pillar "${pillar}".`));
5734
5617
  console.error(` Choices: ${PILLARS.join(", ")}`);
5735
5618
  process.exit(1);
5736
5619
  }
@@ -5745,7 +5628,7 @@ var pillarSet = new Command15("set").description("Set a pillar's mode (interacti
5745
5628
  });
5746
5629
  }
5747
5630
  if (!MODES.includes(chosenMode)) {
5748
- console.error(chalk15.red(`Invalid mode "${chosenMode}".`));
5631
+ console.error(chalk14.red(`Invalid mode "${chosenMode}".`));
5749
5632
  console.error(` Choices: ${MODES.join(", ")}`);
5750
5633
  process.exit(1);
5751
5634
  }
@@ -5753,7 +5636,7 @@ var pillarSet = new Command15("set").description("Set a pillar's mode (interacti
5753
5636
  if (chosenMode === "external-webhook") {
5754
5637
  const url = opts.url ?? await input4({ message: "Webhook URL:" });
5755
5638
  if (!url.trim()) {
5756
- console.error(chalk15.red("Webhook URL required."));
5639
+ console.error(chalk14.red("Webhook URL required."));
5757
5640
  process.exit(1);
5758
5641
  }
5759
5642
  const secret = randomBytes2(32).toString("hex");
@@ -5769,13 +5652,13 @@ var pillarSet = new Command15("set").description("Set a pillar's mode (interacti
5769
5652
  }
5770
5653
  );
5771
5654
  console.log(
5772
- chalk15.green(`Set ${pillar} \u2192 external-webhook in ${projectName}.`)
5655
+ chalk14.green(`Set ${pillar} \u2192 external-webhook in ${projectName}.`)
5773
5656
  );
5774
- console.log(chalk15.dim("URL: "), setting.webhookUrl);
5775
- console.log(chalk15.dim("secret:"), chalk15.yellow(secret));
5657
+ console.log(chalk14.dim("URL: "), setting.webhookUrl);
5658
+ console.log(chalk14.dim("secret:"), chalk14.yellow(secret));
5776
5659
  console.log("");
5777
5660
  console.log(
5778
- chalk15.bold("Store this secret now."),
5661
+ chalk14.bold("Store this secret now."),
5779
5662
  "Yapout encrypts it at rest; this is the only time it's shown. The receiver needs the plaintext to verify HMAC signatures."
5780
5663
  );
5781
5664
  return;
@@ -5790,13 +5673,13 @@ var pillarSet = new Command15("set").description("Set a pillar's mode (interacti
5790
5673
  }
5791
5674
  );
5792
5675
  console.log(
5793
- chalk15.green(`Set ${pillar} \u2192 ${chosenMode} in ${projectName}.`)
5676
+ chalk14.green(`Set ${pillar} \u2192 ${chosenMode} in ${projectName}.`)
5794
5677
  );
5795
5678
  }
5796
5679
  );
5797
- var pillarTest = new Command15("test").description("Send a test delivery to a pillar's external webhook").argument("<pillar>", "extraction | enrichment | intelligence | implementation").action(async (pillar) => {
5680
+ var pillarTest = new Command14("test").description("Send a test delivery to a pillar's external webhook").argument("<pillar>", "extraction | enrichment | intelligence | implementation").action(async (pillar) => {
5798
5681
  if (!PILLARS.includes(pillar)) {
5799
- console.error(chalk15.red(`Unknown pillar "${pillar}".`));
5682
+ console.error(chalk14.red(`Unknown pillar "${pillar}".`));
5800
5683
  process.exit(1);
5801
5684
  }
5802
5685
  const creds = requireAuth();
@@ -5809,7 +5692,7 @@ var pillarTest = new Command15("test").description("Send a test delivery to a pi
5809
5692
  const setting = config?.pillars?.[pillar];
5810
5693
  if (!setting || setting.mode !== "external-webhook") {
5811
5694
  console.error(
5812
- chalk15.red(
5695
+ chalk14.red(
5813
5696
  `${pillar} is in "${setting?.mode ?? "cloud-default"}" mode \u2014 set it to external-webhook first via \`yapout pillar set ${pillar} external-webhook\`.`
5814
5697
  )
5815
5698
  );
@@ -5817,11 +5700,11 @@ var pillarTest = new Command15("test").description("Send a test delivery to a pi
5817
5700
  }
5818
5701
  if (!setting.webhookUrl || !setting.webhookSecret) {
5819
5702
  console.error(
5820
- chalk15.red(`${pillar} is missing webhookUrl or webhookSecret.`)
5703
+ chalk14.red(`${pillar} is missing webhookUrl or webhookSecret.`)
5821
5704
  );
5822
5705
  process.exit(1);
5823
5706
  }
5824
- console.log(chalk15.dim(`POSTing test payload to ${setting.webhookUrl}...`));
5707
+ console.log(chalk14.dim(`POSTing test payload to ${setting.webhookUrl}...`));
5825
5708
  const result = await client.action(
5826
5709
  anyApi.functions.pillarHooks.testDelivery,
5827
5710
  {
@@ -5831,25 +5714,25 @@ var pillarTest = new Command15("test").description("Send a test delivery to a pi
5831
5714
  }
5832
5715
  );
5833
5716
  if (result.ok) {
5834
- console.log(chalk15.green(`\u2713 HTTP ${result.httpStatus} \u2014 receiver returned 2xx`));
5717
+ console.log(chalk14.green(`\u2713 HTTP ${result.httpStatus} \u2014 receiver returned 2xx`));
5835
5718
  } else {
5836
- console.log(chalk15.red(`\u26A0 HTTP ${result.httpStatus ?? "n/a"}`));
5837
- if (result.error) console.log(chalk15.dim(` error: ${result.error}`));
5719
+ console.log(chalk14.red(`\u26A0 HTTP ${result.httpStatus ?? "n/a"}`));
5720
+ if (result.error) console.log(chalk14.dim(` error: ${result.error}`));
5838
5721
  }
5839
5722
  if (result.body) {
5840
- console.log(chalk15.dim(" body:"));
5723
+ console.log(chalk14.dim(" body:"));
5841
5724
  console.log(
5842
5725
  result.body.split("\n").map((l) => ` ${l}`).join("\n")
5843
5726
  );
5844
5727
  }
5845
5728
  });
5846
- var pillarCommand = new Command15("pillar").description("Manage per-project pillar overrides (extraction / enrichment / intelligence / implementation)").addCommand(pillarList).addCommand(pillarSet).addCommand(pillarTest);
5729
+ var pillarCommand = new Command14("pillar").description("Manage per-project pillar overrides (extraction / enrichment / intelligence / implementation)").addCommand(pillarList).addCommand(pillarSet).addCommand(pillarTest);
5847
5730
 
5848
5731
  // src/commands/observe.ts
5849
- import { Command as Command16 } from "commander";
5850
- import { resolve as resolve12 } from "path";
5851
- import chalk16 from "chalk";
5852
- var observeCommand = new Command16("observe").description("File an observation Raw against the active Execution").argument("<observation...>", "Freeform observation text").option(
5732
+ import { Command as Command15 } from "commander";
5733
+ import { resolve as resolve13 } from "path";
5734
+ import chalk15 from "chalk";
5735
+ var observeCommand = new Command15("observe").description("File an observation Raw against the active Execution").argument("<observation...>", "Freeform observation text").option(
5853
5736
  "--severity <level>",
5854
5737
  "Severity hint: low | normal | high (default: normal)"
5855
5738
  ).option(
@@ -5858,30 +5741,30 @@ var observeCommand = new Command16("observe").description("File an observation R
5858
5741
  ).action(
5859
5742
  async (words, options) => {
5860
5743
  const creds = requireAuth();
5861
- const cwd = resolve12(process.cwd());
5744
+ const cwd = resolve13(process.cwd());
5862
5745
  const resourceCfg = readResourceConfig(cwd);
5863
5746
  if (!resourceCfg) {
5864
5747
  console.error(
5865
- chalk16.red("No `.yapout/config.json` found.") + " Run " + chalk16.cyan("yapout init") + " in this repo first."
5748
+ chalk15.red("No `.yapout/config.json` found.") + " Run " + chalk15.cyan("yapout init") + " in this repo first."
5866
5749
  );
5867
5750
  process.exit(1);
5868
5751
  }
5869
5752
  const active = readActiveExecution(cwd);
5870
5753
  if (!active) {
5871
5754
  console.error(
5872
- chalk16.red("No active Execution.") + " `yapout observe` must run from a worktree where " + chalk16.cyan("yapout pick") + " has been called."
5755
+ chalk15.red("No active Execution.") + " `yapout observe` must run from a worktree where " + chalk15.cyan("yapout pick") + " has been called."
5873
5756
  );
5874
5757
  process.exit(1);
5875
5758
  }
5876
5759
  const body = words.join(" ").trim();
5877
5760
  if (!body) {
5878
- console.error(chalk16.red("Observation body cannot be empty."));
5761
+ console.error(chalk15.red("Observation body cannot be empty."));
5879
5762
  process.exit(1);
5880
5763
  }
5881
5764
  const severityHint = parseSeverity(options.severity);
5882
5765
  if (options.severity && !severityHint) {
5883
5766
  console.error(
5884
- chalk16.red(
5767
+ chalk15.red(
5885
5768
  `Invalid --severity: ${options.severity}. Allowed: low, normal, high.`
5886
5769
  )
5887
5770
  );
@@ -5901,18 +5784,18 @@ var observeCommand = new Command16("observe").description("File an observation R
5901
5784
  );
5902
5785
  } catch (err) {
5903
5786
  console.error(
5904
- chalk16.red("Failed to file observation."),
5787
+ chalk15.red("Failed to file observation."),
5905
5788
  err.message
5906
5789
  );
5907
5790
  process.exit(1);
5908
5791
  }
5909
5792
  console.log(
5910
- chalk16.green("Filed agent observation. ") + chalk16.dim(
5793
+ chalk15.green("Filed agent observation. ") + chalk15.dim(
5911
5794
  `(raw: ${result.rawId}, execution: ${result.executionId})`
5912
5795
  )
5913
5796
  );
5914
5797
  console.log(
5915
- chalk16.dim(
5798
+ chalk15.dim(
5916
5799
  "Extraction will turn this into a Request (or link it to an existing one)."
5917
5800
  )
5918
5801
  );
@@ -5925,9 +5808,145 @@ function parseSeverity(raw) {
5925
5808
  return void 0;
5926
5809
  }
5927
5810
 
5811
+ // src/commands/pick.ts
5812
+ import { Command as Command16 } from "commander";
5813
+ import { resolve as resolve14 } from "path";
5814
+ import chalk16 from "chalk";
5815
+ var pickCommand = new Command16("pick").description(
5816
+ "Claim a queued Directive: create worktree + start an Execution"
5817
+ ).argument("<directive-id>", "Convex `directives._id` to pick").option(
5818
+ "--branch <name>",
5819
+ "Override the branch name (default: directive.branchName or `directive/<short-id>`)"
5820
+ ).option(
5821
+ "--agent <name>",
5822
+ "Implementer identity tag (default: 'claude')"
5823
+ ).action(
5824
+ async (directiveId, options) => {
5825
+ const creds = requireAuth();
5826
+ const cwd = resolve14(process.cwd());
5827
+ const repoRoot = resolveRepoRoot(cwd);
5828
+ const resourceCfg = readResourceConfig(repoRoot);
5829
+ if (!resourceCfg) {
5830
+ console.error(
5831
+ chalk16.red("No `.yapout/config.json` found.") + " Run " + chalk16.cyan("yapout init") + " in this repo first."
5832
+ );
5833
+ process.exit(1);
5834
+ }
5835
+ const yapoutCfg = readYapoutConfig(repoRoot);
5836
+ const baseBranch = resourceCfg.defaultBranch ?? (() => {
5837
+ try {
5838
+ return getDefaultBranch(repoRoot);
5839
+ } catch {
5840
+ return "main";
5841
+ }
5842
+ })();
5843
+ const client = createConvexClient(creds.token);
5844
+ const shortId = directiveId.slice(-6);
5845
+ const branchName = options.branch ?? `${yapoutCfg.branch_prefix}/directive-${shortId}`;
5846
+ const worktreePath = getWorktreePath(repoRoot, `directive-${shortId}`);
5847
+ let result;
5848
+ try {
5849
+ result = await client.mutation(
5850
+ anyApi.functions.resourcesCli.createExecutionForDirective,
5851
+ {
5852
+ directiveId,
5853
+ worktreePath,
5854
+ branch: branchName,
5855
+ agent: options.agent ?? "claude"
5856
+ }
5857
+ );
5858
+ } catch (err) {
5859
+ console.error(
5860
+ chalk16.red("Failed to pick Directive."),
5861
+ err.message
5862
+ );
5863
+ process.exit(1);
5864
+ }
5865
+ try {
5866
+ createWorktree(repoRoot, `directive-${shortId}`, branchName, baseBranch);
5867
+ } catch (err) {
5868
+ console.error(
5869
+ chalk16.red("Server-side pick succeeded but worktree creation failed.")
5870
+ );
5871
+ console.error(chalk16.dim(` ${err.message}`));
5872
+ console.error(
5873
+ chalk16.yellow("Recovery: ") + "the Directive is now `in_progress` and an Execution row exists. Either resolve the worktree issue and re-run with the same id, or flip the Directive back to `queued` from the dashboard."
5874
+ );
5875
+ console.error(chalk16.dim(` executionId: ${result.executionId}`));
5876
+ process.exit(1);
5877
+ }
5878
+ writeActiveExecution(worktreePath, {
5879
+ executionId: result.executionId,
5880
+ directiveId: result.directiveId,
5881
+ branch: branchName,
5882
+ worktreePath,
5883
+ startedAt: Date.now()
5884
+ });
5885
+ console.log(
5886
+ chalk16.green("Picked ") + chalk16.bold(result.directiveTitle) + chalk16.dim(` (id: ${result.directiveId}).`)
5887
+ );
5888
+ console.log(
5889
+ chalk16.dim(" worktree: ") + chalk16.cyan(worktreePath)
5890
+ );
5891
+ console.log(chalk16.dim(" branch: ") + chalk16.cyan(branchName));
5892
+ console.log(chalk16.dim(" execution: ") + chalk16.cyan(result.executionId));
5893
+ console.log();
5894
+ console.log(
5895
+ chalk16.dim("Next: ") + "cd into the worktree and start work. Use " + chalk16.cyan("yapout observe") + " to file tangential observations and " + chalk16.cyan("yapout submit") + " when ready for review."
5896
+ );
5897
+ }
5898
+ );
5899
+
5900
+ // src/commands/submit.ts
5901
+ import { Command as Command17 } from "commander";
5902
+ import { resolve as resolve15 } from "path";
5903
+ import chalk17 from "chalk";
5904
+ var submitCommand = new Command17("submit").description(
5905
+ "Mark the active Execution as submitted and move the Directive to review"
5906
+ ).action(async () => {
5907
+ const creds = requireAuth();
5908
+ const cwd = resolve15(process.cwd());
5909
+ const repoRoot = resolveRepoRoot(cwd);
5910
+ const resourceCfg = readResourceConfig(repoRoot);
5911
+ if (!resourceCfg) {
5912
+ console.error(
5913
+ chalk17.red("No `.yapout/config.json` found.") + " Run " + chalk17.cyan("yapout init") + " in this repo first."
5914
+ );
5915
+ process.exit(1);
5916
+ }
5917
+ const active = readActiveExecution(cwd);
5918
+ if (!active) {
5919
+ console.error(
5920
+ chalk17.red("No active Execution.") + " `yapout submit` must run from a worktree where " + chalk17.cyan("yapout pick") + " has been called."
5921
+ );
5922
+ process.exit(1);
5923
+ }
5924
+ const client = createConvexClient(creds.token);
5925
+ let result;
5926
+ try {
5927
+ result = await client.mutation(
5928
+ anyApi.functions.resourcesCli.submitExecution,
5929
+ { executionId: active.executionId }
5930
+ );
5931
+ } catch (err) {
5932
+ console.error(
5933
+ chalk17.red("Failed to submit Execution."),
5934
+ err.message
5935
+ );
5936
+ process.exit(1);
5937
+ }
5938
+ clearActiveExecution(cwd);
5939
+ console.log(
5940
+ chalk17.green("Submitted Execution. ") + chalk17.dim(`(execution: ${result.executionId})`)
5941
+ );
5942
+ console.log(
5943
+ chalk17.dim("Directive ") + chalk17.cyan(result.directiveId) + chalk17.dim(" is now in `review`.")
5944
+ );
5945
+ });
5946
+
5928
5947
  // src/index.ts
5929
- var program = new Command17();
5930
- program.name("yapout").description("yapout \u2014 the PM tool for agents").version("0.16.0");
5948
+ var program = new Command18();
5949
+ program.name("yapout").description("yapout \u2014 the PM tool for agents").version("0.18.0");
5931
5950
  program.addCommand(loginCommand);
5932
5951
  program.addCommand(logoutCommand);
5933
5952
  program.addCommand(initCommand);
@@ -5937,11 +5956,12 @@ program.addCommand(statusCommand);
5937
5956
  program.addCommand(mcpServerCommand);
5938
5957
  program.addCommand(queueCommand);
5939
5958
  program.addCommand(recapCommand);
5940
- program.addCommand(yapCommand);
5941
5959
  program.addCommand(serveCommand);
5942
5960
  program.addCommand(agentCommand);
5943
5961
  program.addCommand(webhookCommand);
5944
5962
  program.addCommand(inboundCommand);
5945
5963
  program.addCommand(pillarCommand);
5946
5964
  program.addCommand(observeCommand);
5965
+ program.addCommand(pickCommand);
5966
+ program.addCommand(submitCommand);
5947
5967
  program.parse();