yapout 0.15.2 → 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 +1138 -1383
  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 Command16 } 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";
@@ -45,6 +45,31 @@ function getRepoFullName(cwd) {
45
45
  if (httpsMatch) return httpsMatch[1];
46
46
  throw new Error(`Could not parse GitHub repo from remote URL: ${url}`);
47
47
  }
48
+ function getOriginRemoteUrl(cwd) {
49
+ return git("remote get-url origin", cwd);
50
+ }
51
+ function normalizeGitRemote(raw) {
52
+ if (!raw) return null;
53
+ let s = raw.trim();
54
+ if (s.endsWith(".git")) s = s.slice(0, -4);
55
+ s = s.replace(/^git@([^:]+):/, "$1/");
56
+ s = s.replace(/^ssh:\/\/(?:git@)?/, "");
57
+ s = s.replace(/^https?:\/\//, "");
58
+ s = s.replace(/^github\.com\//, "");
59
+ const parts = s.split("/").filter(Boolean);
60
+ if (parts.length < 2) return null;
61
+ const owner = parts[0];
62
+ const repo = parts[1];
63
+ return `https://github.com/${owner}/${repo}`;
64
+ }
65
+ function isInGitRepo(cwd) {
66
+ try {
67
+ git("rev-parse --git-dir", cwd);
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
48
73
  function getDefaultBranch(cwd) {
49
74
  try {
50
75
  const ref = git("rev-parse --abbrev-ref origin/HEAD", cwd);
@@ -174,6 +199,57 @@ function getOrCreateDeviceIdentity(defaultName) {
174
199
  writeDeviceIdentity(identity);
175
200
  return identity;
176
201
  }
202
+ function readResourceConfig(cwd) {
203
+ const path = join(resolveRepoRoot(cwd), ".yapout", "config.json");
204
+ if (!existsSync(path)) return null;
205
+ try {
206
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
207
+ if (!raw || typeof raw !== "object") return null;
208
+ if (!raw.resourceId || !raw.canonicalId) return null;
209
+ return raw;
210
+ } catch {
211
+ return null;
212
+ }
213
+ }
214
+ function writeResourceConfig(cwd, cfg) {
215
+ const yapoutDir = join(resolveRepoRoot(cwd), ".yapout");
216
+ if (!existsSync(yapoutDir)) {
217
+ mkdirSync(yapoutDir, { recursive: true });
218
+ }
219
+ const path = join(yapoutDir, "config.json");
220
+ writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n");
221
+ }
222
+ function readActiveExecution(cwd) {
223
+ const path = join(cwd, ".yapout", "active-execution.json");
224
+ if (!existsSync(path)) {
225
+ const fallback = join(resolveRepoRoot(cwd), ".yapout", "active-execution.json");
226
+ if (!existsSync(fallback)) return null;
227
+ try {
228
+ return JSON.parse(readFileSync(fallback, "utf-8"));
229
+ } catch {
230
+ return null;
231
+ }
232
+ }
233
+ try {
234
+ return JSON.parse(readFileSync(path, "utf-8"));
235
+ } catch {
236
+ return null;
237
+ }
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
+ }
177
253
  var WATCH_DEFAULTS = {
178
254
  auto_enrich: true,
179
255
  auto_implement: true,
@@ -296,7 +372,7 @@ function requireAuth() {
296
372
  }
297
373
 
298
374
  // src/commands/serve.ts
299
- var CLI_VERSION = "0.15.2";
375
+ var CLI_VERSION = "0.18.0";
300
376
  var DEFAULT_PORT = 7777;
301
377
  var PORT_RANGE = 10;
302
378
  var HEARTBEAT_MS = 12e4;
@@ -345,7 +421,7 @@ function startServer(payload) {
345
421
  res.writeHead(404, headers);
346
422
  res.end();
347
423
  });
348
- return new Promise((resolve12, reject) => {
424
+ return new Promise((resolve16, reject) => {
349
425
  let attempt = 0;
350
426
  const tryListen = () => {
351
427
  const port = DEFAULT_PORT + attempt;
@@ -361,7 +437,7 @@ function startServer(payload) {
361
437
  server.once("error", onError);
362
438
  server.listen(port, "127.0.0.1", () => {
363
439
  server.removeListener("error", onError);
364
- resolve12({
440
+ resolve16({
365
441
  port,
366
442
  close: () => server.close()
367
443
  });
@@ -518,7 +594,7 @@ var serveCommand = new Command("serve").description(
518
594
  });
519
595
 
520
596
  // src/commands/login.ts
521
- var CLI_VERSION2 = "0.15.2";
597
+ var CLI_VERSION2 = "0.18.0";
522
598
  function safeReturnTo(raw) {
523
599
  if (!raw) return null;
524
600
  try {
@@ -530,7 +606,7 @@ function safeReturnTo(raw) {
530
606
  }
531
607
  }
532
608
  function startCallbackServer() {
533
- return new Promise((resolve12) => {
609
+ return new Promise((resolve16) => {
534
610
  let resolveData;
535
611
  let rejectData;
536
612
  const dataPromise = new Promise((res, rej) => {
@@ -579,7 +655,7 @@ function startCallbackServer() {
579
655
  server.listen(0, () => {
580
656
  const address = server.address();
581
657
  const port = typeof address === "object" && address ? address.port : 0;
582
- resolve12({ port, data: dataPromise });
658
+ resolve16({ port, data: dataPromise });
583
659
  });
584
660
  setTimeout(() => {
585
661
  server.close();
@@ -681,15 +757,6 @@ async function pickProject(projects) {
681
757
  })
682
758
  });
683
759
  }
684
- async function pickOrg(orgs, message = "Which org does this project belong to?") {
685
- return await select({
686
- message,
687
- choices: orgs.map((o) => ({
688
- name: `${o.name} (${o.slug}, ${o.role})`,
689
- value: o
690
- }))
691
- });
692
- }
693
760
 
694
761
  // src/commands/link.ts
695
762
  var CONFIG_YAML_CONTENT = `# yapout local configuration
@@ -716,7 +783,50 @@ branch_prefix: feat
716
783
  # {{ticket.linearTicketId}}, {{ticket.id}}
717
784
  # commit_template: "{{ticket.type}}({{ticket.linearTicketId}}): {{ticket.title}}"
718
785
  `;
719
- var linkCommand = new Command4("link").description("Link the current directory to a yapout project").action(async () => {
786
+ var linkCommand = new Command4("link").description(
787
+ "Link the current Resource into a Group (--group), or link the current directory to a project (legacy)."
788
+ ).option(
789
+ "--group <groupId>",
790
+ "Attach the active Resource into a Group (requires `yapout init` to have run in this repo)"
791
+ ).action(async (options) => {
792
+ if (options.group) {
793
+ await linkResourceToGroup(options.group);
794
+ return;
795
+ }
796
+ await legacyLinkProject();
797
+ });
798
+ async function linkResourceToGroup(groupId) {
799
+ const creds = requireAuth();
800
+ const cwd = resolve2(process.cwd());
801
+ const resourceCfg = readResourceConfig(cwd);
802
+ if (!resourceCfg) {
803
+ console.error(
804
+ chalk5.red("No `.yapout/config.json` found.") + " Run " + chalk5.cyan("yapout init") + " here first."
805
+ );
806
+ process.exit(1);
807
+ }
808
+ const client = createConvexClient(creds.token);
809
+ try {
810
+ await client.mutation(anyApi.functions.resourcesCli.linkResourceToGroup, {
811
+ resourceId: resourceCfg.resourceId,
812
+ groupId
813
+ });
814
+ } catch (err) {
815
+ console.error(
816
+ chalk5.red("Failed to link Resource to Group."),
817
+ err.message
818
+ );
819
+ process.exit(1);
820
+ }
821
+ writeResourceConfig(resolve2(process.cwd()), {
822
+ ...resourceCfg,
823
+ groupId
824
+ });
825
+ console.log(
826
+ chalk5.green("Linked Resource ") + chalk5.cyan(resourceCfg.canonicalId) + chalk5.green(" to Group ") + chalk5.cyan(groupId) + "."
827
+ );
828
+ }
829
+ async function legacyLinkProject() {
720
830
  const creds = requireAuth();
721
831
  const cwd = resolveRepoRoot(resolve2(process.cwd()));
722
832
  const client = createConvexClient(creds.token);
@@ -776,10 +886,7 @@ var linkCommand = new Command4("link").description("Link the current directory t
776
886
  if (existsSync3(gitignorePath)) {
777
887
  const content = readFileSync3(gitignorePath, "utf-8");
778
888
  if (!content.includes(".yapout/")) {
779
- appendFileSync(
780
- gitignorePath,
781
- "\n# yapout local config\n.yapout/\n"
782
- );
889
+ appendFileSync(gitignorePath, "\n# yapout local config\n.yapout/\n");
783
890
  }
784
891
  } else {
785
892
  writeFileSync3(gitignorePath, "# yapout local config\n.yapout/\n");
@@ -803,7 +910,7 @@ var linkCommand = new Command4("link").description("Link the current directory t
803
910
  console.log(
804
911
  chalk5.green(`Linked to ${label}${orgSuffix}.`) + " Claude Code will discover yapout tools automatically."
805
912
  );
806
- });
913
+ }
807
914
  function getCliVersion() {
808
915
  try {
809
916
  const pkg = JSON.parse(
@@ -916,7 +1023,6 @@ import {
916
1023
  readFileSync as readFileSync5,
917
1024
  appendFileSync as appendFileSync2
918
1025
  } from "fs";
919
- import { hostname as hostname4 } from "os";
920
1026
  import chalk8 from "chalk";
921
1027
  var CONFIG_YAML_CONTENT2 = `# yapout local configuration
922
1028
  # See: https://docs.yapout.dev/cli/config
@@ -927,7 +1033,6 @@ var CONFIG_YAML_CONTENT2 = `# yapout local configuration
927
1033
  # - npm run lint
928
1034
  # - npm run typecheck
929
1035
  # - npm run test
930
- # - sf project deploy --dry-run --target-org scratch
931
1036
  post_flight: []
932
1037
 
933
1038
  # Require post_flight checks to pass before yapout_ship succeeds.
@@ -936,170 +1041,89 @@ ship_requires_checks: false
936
1041
 
937
1042
  # Git branch prefix (used by yapout_claim)
938
1043
  branch_prefix: feat
939
-
940
- # Commit message template (optional \u2014 uses sensible default if omitted)
941
- # Available variables: {{ticket.title}}, {{ticket.type}}, {{ticket.priority}},
942
- # {{ticket.linearTicketId}}, {{ticket.id}}
943
- # commit_template: "{{ticket.type}}({{ticket.linearTicketId}}): {{ticket.title}}"
944
1044
  `;
945
- var initCommand = new Command7("init").description("Create a yapout project from the current repo and link it").argument("[name]", "Project name (defaults to repo name)").option("--org <slug>", "Org slug to create the project in (skips picker)").action(async (name, options) => {
1045
+ var initCommand = new Command7("init").description(
1046
+ "Register the current repo as a Resource and start the MCP server scoped to it"
1047
+ ).option(
1048
+ "--group <groupId>",
1049
+ "Optional Group id to attach the Resource to (defaults to the org's migration-bridge group)"
1050
+ ).action(async (options) => {
946
1051
  const creds = requireAuth();
947
- const cwd = resolveRepoRoot(resolve5(process.cwd()));
948
- let repoFullName;
949
- let defaultBranch;
950
- try {
951
- repoFullName = getRepoFullName(cwd);
952
- defaultBranch = getDefaultBranch(cwd);
953
- } catch (err) {
1052
+ const cwd = resolve5(process.cwd());
1053
+ if (!isInGitRepo(cwd)) {
954
1054
  console.error(
955
- chalk8.red("Not a git repo with a GitHub remote."),
956
- err.message
1055
+ chalk8.red("`yapout init` must be run from inside a git repo.") + "\n No .git/ directory found from " + chalk8.dim(cwd) + "."
957
1056
  );
958
1057
  process.exit(1);
959
1058
  }
960
- const projectName = name || repoFullName.split("/")[1] || "unnamed";
961
- const client = createConvexClient(creds.token);
962
- let orgs;
1059
+ const repoRoot = resolveRepoRoot(cwd);
1060
+ let rawRemote;
963
1061
  try {
964
- orgs = await client.query(
965
- anyApi.functions.orgMembers.getMyOrgs,
966
- {}
967
- );
1062
+ rawRemote = getOriginRemoteUrl(repoRoot);
968
1063
  } catch (err) {
969
1064
  console.error(
970
- chalk8.red("Failed to load your orgs."),
971
- err.message
1065
+ chalk8.red("Failed to read origin remote URL.") + "\n " + err.message + "\n Set a GitHub remote (`git remote add origin ...`) before running `yapout init`."
972
1066
  );
973
1067
  process.exit(1);
974
1068
  }
975
- if (!orgs || orgs.length === 0) {
1069
+ const canonicalId = normalizeGitRemote(rawRemote);
1070
+ if (!canonicalId) {
976
1071
  console.error(
977
1072
  chalk8.red(
978
- "You aren't a member of any org. Sign in to the web app once to create your personal org, then re-run."
979
- )
1073
+ `Could not derive a canonical id from the origin remote URL: ${rawRemote}`
1074
+ ) + "\n yapout currently supports github.com remotes only."
980
1075
  );
981
1076
  process.exit(1);
982
1077
  }
983
- let chosenOrgId;
984
- let chosenOrgName;
985
- if (options?.org) {
986
- const match = orgs.find((o) => o.org.slug === options.org);
987
- if (!match) {
988
- console.error(
989
- chalk8.red(`Org "${options.org}" not found among your memberships.`)
990
- );
991
- console.error(
992
- chalk8.dim(
993
- "Available: " + orgs.map((o) => o.org.slug).join(", ")
994
- )
995
- );
996
- process.exit(1);
997
- }
998
- chosenOrgId = match.org._id;
999
- chosenOrgName = match.org.name;
1000
- } else if (orgs.length === 1) {
1001
- chosenOrgId = orgs[0].org._id;
1002
- chosenOrgName = orgs[0].org.name;
1003
- console.log(
1004
- chalk8.dim(`Creating in `) + chalk8.cyan(chosenOrgName)
1005
- );
1006
- } else {
1007
- const picked = await pickOrg(
1008
- orgs.map((o) => ({
1009
- id: o.org._id,
1010
- name: o.org.name,
1011
- slug: o.org.slug,
1012
- role: o.role
1013
- }))
1014
- );
1015
- chosenOrgId = picked.id;
1016
- chosenOrgName = picked.name;
1017
- }
1018
- const device = getOrCreateDeviceIdentity(hostname4());
1078
+ let defaultBranch;
1019
1079
  try {
1020
- await client.mutation(anyApi.functions.devices.registerDevice, {
1021
- deviceId: device.deviceId,
1022
- name: device.name,
1023
- cliVersion: getCliVersion2(),
1024
- machineHostname: hostname4()
1025
- });
1026
- } catch (err) {
1027
- console.warn(
1028
- chalk8.yellow("Warning: device registration failed \u2014 "),
1029
- err.message
1030
- );
1080
+ defaultBranch = getDefaultBranch(repoRoot);
1081
+ } catch {
1082
+ defaultBranch = void 0;
1031
1083
  }
1084
+ const client = createConvexClient(creds.token);
1032
1085
  let result;
1033
1086
  try {
1034
1087
  result = await client.mutation(
1035
- anyApi.functions.projects.createProjectFromCli,
1088
+ anyApi.functions.resourcesCli.findOrCreateRepoResource,
1036
1089
  {
1037
- orgId: chosenOrgId,
1038
- name: projectName,
1039
- githubRepoFullName: repoFullName,
1040
- githubDefaultBranch: defaultBranch
1090
+ canonicalId,
1091
+ remoteUrl: canonicalId,
1092
+ defaultBranch,
1093
+ ...options?.group ? { groupId: options.group } : {}
1041
1094
  }
1042
1095
  );
1043
1096
  } catch (err) {
1044
1097
  console.error(
1045
- chalk8.red("Failed to create project."),
1098
+ chalk8.red("Failed to register Resource."),
1046
1099
  err.message
1047
1100
  );
1048
1101
  process.exit(1);
1049
1102
  }
1050
- try {
1051
- await client.mutation(anyApi.functions.projectCheckouts.linkCheckout, {
1052
- projectId: result.projectId,
1053
- deviceId: device.deviceId,
1054
- localPath: cwd
1055
- });
1056
- } catch (err) {
1057
- console.warn(
1058
- chalk8.yellow("Warning: failed to record project checkout \u2014 "),
1059
- err.message
1060
- );
1061
- }
1062
- setProjectMapping(cwd, {
1063
- projectId: result.projectId,
1064
- projectName: result.projectName,
1103
+ const yapoutDir = join5(repoRoot, ".yapout");
1104
+ if (!existsSync5(yapoutDir)) mkdirSync3(yapoutDir, { recursive: true });
1105
+ writeResourceConfig(repoRoot, {
1106
+ resourceId: result.resourceId,
1107
+ canonicalId: result.canonicalId,
1108
+ groupId: result.groupId,
1109
+ remoteUrl: canonicalId,
1110
+ defaultBranch,
1065
1111
  linkedAt: Date.now()
1066
1112
  });
1067
- let agentToken = null;
1068
- try {
1069
- const agentRes = await client.mutation(
1070
- anyApi.functions.agents.createAgent,
1071
- {
1072
- projectId: result.projectId,
1073
- displayName: `${creds.email}'s default agent (${result.projectName})`,
1074
- roleDescription: "Default agent created at yapout init. Has full tool access.",
1075
- scopes: ["*"],
1076
- label: "yapout init default"
1077
- }
1078
- );
1079
- agentToken = agentRes.rawToken;
1080
- } catch (err) {
1081
- console.warn(
1082
- chalk8.yellow("Warning: failed to provision default agent \u2014 "),
1083
- err.message
1084
- );
1085
- }
1086
- const yapoutDir = join5(cwd, ".yapout");
1087
- if (!existsSync5(yapoutDir)) mkdirSync3(yapoutDir, { recursive: true });
1088
- const configPath = join5(yapoutDir, "config.yml");
1089
- if (!existsSync5(configPath)) {
1090
- writeFileSync5(configPath, CONFIG_YAML_CONTENT2);
1113
+ const configYamlPath = join5(yapoutDir, "config.yml");
1114
+ if (!existsSync5(configYamlPath)) {
1115
+ writeFileSync5(configYamlPath, CONFIG_YAML_CONTENT2);
1091
1116
  }
1092
- const gitignorePath = join5(cwd, ".gitignore");
1117
+ const gitignorePath = join5(repoRoot, ".gitignore");
1093
1118
  if (existsSync5(gitignorePath)) {
1094
1119
  const content = readFileSync5(gitignorePath, "utf-8");
1095
1120
  if (!content.includes(".yapout/")) {
1096
- appendFileSync2(
1097
- gitignorePath,
1098
- "\n# yapout local config\n.yapout/\n"
1099
- );
1121
+ appendFileSync2(gitignorePath, "\n# yapout local config\n.yapout/\n");
1100
1122
  }
1123
+ } else {
1124
+ writeFileSync5(gitignorePath, "# yapout local config\n.yapout/\n");
1101
1125
  }
1102
- const mcpPath = join5(cwd, ".mcp.json");
1126
+ const mcpPath = join5(repoRoot, ".mcp.json");
1103
1127
  let mcpConfig = {};
1104
1128
  if (existsSync5(mcpPath)) {
1105
1129
  try {
@@ -1110,40 +1134,25 @@ var initCommand = new Command7("init").description("Create a yapout project from
1110
1134
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
1111
1135
  const existingYapoutServer = mcpConfig.mcpServers.yapout ?? {};
1112
1136
  const existingEnv = existingYapoutServer.env && typeof existingYapoutServer.env === "object" ? existingYapoutServer.env : {};
1113
- const newEnv = { ...existingEnv };
1114
- if (agentToken) {
1115
- newEnv.YAPOUT_AGENT_TOKEN = agentToken;
1116
- }
1117
1137
  mcpConfig.mcpServers.yapout = {
1118
1138
  command: "yapout",
1119
1139
  args: ["mcp-server"],
1120
- ...Object.keys(newEnv).length > 0 ? { env: newEnv } : {}
1140
+ ...Object.keys(existingEnv).length > 0 ? { env: existingEnv } : {}
1121
1141
  };
1122
1142
  writeFileSync5(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
1143
+ const verb = result.created ? "Registered" : "Linked";
1123
1144
  console.log(
1124
- chalk8.green(`Created project "${result.projectName}"`) + chalk8.dim(
1125
- ` in ${chosenOrgName} (${repoFullName}, branch: ${defaultBranch})`
1126
- )
1145
+ chalk8.green(`${verb} Resource `) + chalk8.cyan(canonicalId) + chalk8.dim(` (id: ${result.resourceId}).`)
1127
1146
  );
1128
- if (agentToken) {
1129
- console.log(
1130
- chalk8.dim("Provisioned default agent \u2014 token stored in ") + chalk8.cyan(".mcp.json")
1131
- );
1147
+ if (defaultBranch) {
1148
+ console.log(chalk8.dim(` default branch: ${defaultBranch}`));
1132
1149
  }
1133
1150
  console.log(
1134
- chalk8.dim("Run ") + chalk8.cyan("yapout_compact") + chalk8.dim(" in Claude Code to generate project context.")
1151
+ chalk8.dim(
1152
+ "Claude Code will discover yapout's MCP tools via .mcp.json on next launch."
1153
+ )
1135
1154
  );
1136
1155
  });
1137
- function getCliVersion2() {
1138
- try {
1139
- const pkg = JSON.parse(
1140
- readFileSync5(join5(import.meta.dirname, "..", "package.json"), "utf-8")
1141
- );
1142
- return pkg.version ?? "unknown";
1143
- } catch {
1144
- return "unknown";
1145
- }
1146
- }
1147
1156
 
1148
1157
  // src/commands/mcp-server.ts
1149
1158
  import { Command as Command8 } from "commander";
@@ -1516,151 +1525,457 @@ function registerUpdateContextTool(server, ctx) {
1516
1525
  );
1517
1526
  }
1518
1527
 
1519
- // src/mcp/tools/queue.ts
1520
- import { z as z4 } from "zod";
1521
- function formatFinding(f, indent, done) {
1522
- const ref = f.linearIssueId ? ` ${f.linearIssueId}` : "";
1523
- const prefix = done ? "\u2713 " : "";
1524
- return `${indent}${prefix}${f.title} ${f.type}\xB7${f.priority}${ref}`;
1525
- }
1526
- function formatWorkItem(item, done) {
1527
- const lines = [];
1528
- if (item.kind === "bundle") {
1529
- const count = item.findings.length;
1530
- const priority = item.findings.length > 0 ? item.findings.map((f) => f.priority).sort((a, b) => {
1531
- const order = { urgent: 0, high: 1, medium: 2, low: 3 };
1532
- return (order[a] ?? 3) - (order[b] ?? 3);
1533
- })[0] : "medium";
1534
- const prefix = done ? "\u2713 " : "";
1535
- const prRef = done && item.pr?.githubPrNumber ? ` PR #${item.pr.githubPrNumber}` : "";
1536
- lines.push(` ${prefix}\u{1F4E6} ${item.title} (${count} findings) \u2014 ${priority}${prRef}`);
1537
- for (const f of item.findings) {
1538
- lines.push(formatFinding(f, " ", done));
1528
+ // src/mcp/tools/dev-loop.ts
1529
+ import { resolve as resolve6 } from "path";
1530
+
1531
+ // src/lib/worktree.ts
1532
+ import { execSync as execSync2 } from "child_process";
1533
+ import { join as join7 } from "path";
1534
+ import { existsSync as existsSync7, symlinkSync, copyFileSync, mkdirSync as mkdirSync5 } from "fs";
1535
+ var WORKTREES_DIR = ".yapout/worktrees";
1536
+ function getWorktreesDir(cwd) {
1537
+ return join7(cwd, WORKTREES_DIR);
1538
+ }
1539
+ function getWorktreePath(cwd, ticketId) {
1540
+ return join7(cwd, WORKTREES_DIR, ticketId);
1541
+ }
1542
+ function createWorktree(cwd, ticketId, branchName, baseBranch) {
1543
+ const wtPath = getWorktreePath(cwd, ticketId);
1544
+ const wtDir = getWorktreesDir(cwd);
1545
+ if (!existsSync7(wtDir)) mkdirSync5(wtDir, { recursive: true });
1546
+ execSync2(
1547
+ `git worktree add "${wtPath}" -b ${branchName} origin/${baseBranch}`,
1548
+ { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
1549
+ );
1550
+ const nodeModulesSrc = join7(cwd, "node_modules");
1551
+ const nodeModulesDst = join7(wtPath, "node_modules");
1552
+ if (existsSync7(nodeModulesSrc) && !existsSync7(nodeModulesDst)) {
1553
+ try {
1554
+ symlinkSync(nodeModulesSrc, nodeModulesDst, "junction");
1555
+ } catch {
1556
+ try {
1557
+ execSync2("npm install --prefer-offline", {
1558
+ cwd: wtPath,
1559
+ encoding: "utf-8",
1560
+ stdio: ["pipe", "pipe", "pipe"],
1561
+ timeout: 12e4
1562
+ });
1563
+ } catch {
1564
+ }
1539
1565
  }
1540
- } else {
1541
- const f = item.findings[0];
1542
- if (f) {
1543
- const prRef = done && item.pr?.githubPrNumber ? ` PR #${item.pr.githubPrNumber}` : "";
1544
- const prefix = done ? "\u2713 " : "";
1545
- const ref = f.linearIssueId ? ` ${f.linearIssueId}` : "";
1546
- lines.push(` ${prefix}${item.title} ${f.type}\xB7${f.priority}${ref}${prRef}`);
1566
+ }
1567
+ for (const envFile of [".env", ".env.local"]) {
1568
+ const src = join7(cwd, envFile);
1569
+ if (existsSync7(src)) {
1570
+ copyFileSync(src, join7(wtPath, envFile));
1547
1571
  }
1548
1572
  }
1549
- return lines.join("\n");
1573
+ return wtPath;
1550
1574
  }
1551
- function registerQueueTool(server, ctx) {
1552
- server.tool(
1553
- "yapout_queue",
1554
- "List work items (bundles and standalone findings) ready for local implementation, plus active and done items.",
1555
- {
1556
- includeIds: z4.boolean().optional().describe("Include work item IDs in output (default: false)")
1557
- },
1558
- withScopeCheck(ctx, "yapout_queue", async (args) => {
1559
- if (!ctx.projectId) {
1560
- return {
1561
- content: [
1562
- {
1563
- type: "text",
1564
- text: "No project linked. Run yapout_init or yapout link first."
1565
- }
1566
- ],
1567
- isError: true
1568
- };
1569
- }
1570
- const data = await ctx.client.query(
1571
- anyApi5.functions.workQueue.getWorkQueue,
1572
- { projectId: ctx.projectId }
1573
- );
1574
- if (!data) {
1575
- return {
1576
- content: [
1577
- { type: "text", text: "Could not fetch work queue." }
1578
- ],
1579
- isError: true
1580
- };
1581
- }
1582
- const sections = [];
1583
- sections.push(`Ready (${data.ready.length}):`);
1584
- if (data.ready.length === 0) {
1585
- sections.push(" (none)");
1586
- } else {
1587
- for (const item of data.ready) {
1588
- const line = formatWorkItem(item);
1589
- if (args.includeIds) {
1590
- sections.push(`${line} [${item.id}]`);
1591
- } else {
1592
- sections.push(line);
1575
+ function removeWorktree(cwd, wtPath) {
1576
+ try {
1577
+ execSync2(`git worktree remove "${wtPath}" --force`, {
1578
+ cwd,
1579
+ encoding: "utf-8",
1580
+ stdio: ["pipe", "pipe", "pipe"]
1581
+ });
1582
+ } catch {
1583
+ try {
1584
+ execSync2(`git worktree prune`, {
1585
+ cwd,
1586
+ encoding: "utf-8",
1587
+ stdio: ["pipe", "pipe", "pipe"]
1588
+ });
1589
+ } catch {
1590
+ }
1591
+ }
1592
+ }
1593
+
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."
1593
1613
  }
1594
- }
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
1595
1623
  }
1596
- sections.push("");
1597
- sections.push(`Active (${data.active.length}):`);
1598
- if (data.active.length === 0) {
1599
- sections.push(" (none)");
1600
- } else {
1601
- for (const item of data.active) {
1602
- const statusTag = item.status === "failed" ? " \u274C FAILED" : item.status === "review" ? " \u{1F50D} review" : "";
1603
- const line = formatWorkItem(item);
1604
- if (args.includeIds) {
1605
- sections.push(`${line}${statusTag} [${item.id}]`);
1606
- } else {
1607
- sections.push(`${line}${statusTag}`);
1624
+ );
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."
1608
1631
  }
1632
+ ],
1633
+ isError: true
1634
+ };
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
+ );
1609
1669
  }
1610
1670
  }
1611
- sections.push("");
1612
- sections.push(`Done (${data.done.length}):`);
1613
- if (data.done.length === 0) {
1614
- sections.push(" (none)");
1615
- } else {
1616
- for (const item of data.done) {
1617
- const line = formatWorkItem(item, true);
1618
- if (args.includeIds) {
1619
- sections.push(`${line} [${item.id}]`);
1620
- } else {
1621
- sections.push(line);
1622
- }
1623
- }
1624
- }
1625
- if (data.agentStatus.isActive) {
1626
- sections.push("");
1627
- sections.push(`Agent: ${data.agentStatus.agentCount} active, ${data.agentStatus.worktreeCount} worktrees`);
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
+ };
1699
+ }
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";
1628
1731
  }
1629
- const structured = {
1630
- ready: data.ready.map((item) => ({
1631
- workItemId: item.id,
1632
- kind: item.kind,
1633
- title: item.title,
1634
- findings: item.findings.map((f) => ({
1635
- id: f.id,
1636
- title: f.title,
1637
- type: f.type,
1638
- priority: f.priority,
1639
- linearIssueId: f.linearIssueId
1640
- }))
1641
- })),
1642
- activeCount: data.active.length,
1643
- doneCount: data.done.length
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
1644
1756
  };
1757
+ }
1758
+ try {
1759
+ createWorktree(
1760
+ repoRoot,
1761
+ `directive-${shortId}`,
1762
+ branchName,
1763
+ baseBranch
1764
+ );
1765
+ } catch (err) {
1645
1766
  return {
1646
1767
  content: [
1647
- { type: "text", text: sections.join("\n") },
1648
- { type: "text", text: "\n---\nStructured data:\n" + JSON.stringify(structured, null, 2) }
1649
- ]
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
1650
1776
  };
1651
- })
1652
- );
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
+ }
1653
1968
  }
1654
1969
 
1655
1970
  // src/mcp/tools/get-brief.ts
1656
- import { z as z5 } from "zod";
1971
+ import { z as z4 } from "zod";
1657
1972
  function registerGetBriefTool(server, ctx) {
1658
1973
  server.tool(
1659
1974
  "yapout_get_brief",
1660
1975
  "Fetch the full implementation context for a work item (finding or bundle)",
1661
1976
  {
1662
- findingId: z5.string().optional().describe("The finding ID to get the brief for (deprecated: use workItemId)"),
1663
- workItemId: z5.string().optional().describe("The work item ID (finding or bundle) to get the brief for")
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")
1664
1979
  },
1665
1980
  withScopeCheck(ctx, "yapout_get_brief", async (args) => {
1666
1981
  const itemId = args.workItemId || args.findingId;
@@ -1741,72 +2056,7 @@ function registerGetBriefTool(server, ctx) {
1741
2056
  }
1742
2057
 
1743
2058
  // src/mcp/tools/claim.ts
1744
- import { z as z6 } from "zod";
1745
-
1746
- // src/lib/worktree.ts
1747
- import { execSync as execSync2 } from "child_process";
1748
- import { join as join7 } from "path";
1749
- import { existsSync as existsSync7, symlinkSync, copyFileSync, mkdirSync as mkdirSync5 } from "fs";
1750
- var WORKTREES_DIR = ".yapout/worktrees";
1751
- function getWorktreesDir(cwd) {
1752
- return join7(cwd, WORKTREES_DIR);
1753
- }
1754
- function getWorktreePath(cwd, ticketId) {
1755
- return join7(cwd, WORKTREES_DIR, ticketId);
1756
- }
1757
- function createWorktree(cwd, ticketId, branchName, baseBranch) {
1758
- const wtPath = getWorktreePath(cwd, ticketId);
1759
- const wtDir = getWorktreesDir(cwd);
1760
- if (!existsSync7(wtDir)) mkdirSync5(wtDir, { recursive: true });
1761
- execSync2(
1762
- `git worktree add "${wtPath}" -b ${branchName} origin/${baseBranch}`,
1763
- { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
1764
- );
1765
- const nodeModulesSrc = join7(cwd, "node_modules");
1766
- const nodeModulesDst = join7(wtPath, "node_modules");
1767
- if (existsSync7(nodeModulesSrc) && !existsSync7(nodeModulesDst)) {
1768
- try {
1769
- symlinkSync(nodeModulesSrc, nodeModulesDst, "junction");
1770
- } catch {
1771
- try {
1772
- execSync2("npm install --prefer-offline", {
1773
- cwd: wtPath,
1774
- encoding: "utf-8",
1775
- stdio: ["pipe", "pipe", "pipe"],
1776
- timeout: 12e4
1777
- });
1778
- } catch {
1779
- }
1780
- }
1781
- }
1782
- for (const envFile of [".env", ".env.local"]) {
1783
- const src = join7(cwd, envFile);
1784
- if (existsSync7(src)) {
1785
- copyFileSync(src, join7(wtPath, envFile));
1786
- }
1787
- }
1788
- return wtPath;
1789
- }
1790
- function removeWorktree(cwd, wtPath) {
1791
- try {
1792
- execSync2(`git worktree remove "${wtPath}" --force`, {
1793
- cwd,
1794
- encoding: "utf-8",
1795
- stdio: ["pipe", "pipe", "pipe"]
1796
- });
1797
- } catch {
1798
- try {
1799
- execSync2(`git worktree prune`, {
1800
- cwd,
1801
- encoding: "utf-8",
1802
- stdio: ["pipe", "pipe", "pipe"]
1803
- });
1804
- } catch {
1805
- }
1806
- }
1807
- }
1808
-
1809
- // src/mcp/tools/claim.ts
2059
+ import { z as z5 } from "zod";
1810
2060
  import { join as join8 } from "path";
1811
2061
  import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
1812
2062
 
@@ -1964,9 +2214,9 @@ function registerClaimTool(server, ctx) {
1964
2214
  "yapout_claim",
1965
2215
  "Claim a work item (bundle or standalone finding) for local implementation. Creates a branch (or worktree), writes the brief, and updates status.",
1966
2216
  {
1967
- workItemId: z6.string().describe("The work item ID to claim (bundle ID or finding ID from yapout_queue)"),
1968
- findingId: z6.string().optional().describe("Deprecated: use workItemId instead. If provided, treated as a standalone finding."),
1969
- 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)")
1970
2220
  },
1971
2221
  withScopeCheck(ctx, "yapout_claim", async (args) => {
1972
2222
  if (!ctx.projectId) {
@@ -2222,17 +2472,17 @@ async function reportClaimEvents(ctx, pipelineRunId, title, branchName) {
2222
2472
  }
2223
2473
 
2224
2474
  // src/mcp/tools/event.ts
2225
- import { z as z7 } from "zod";
2475
+ import { z as z6 } from "zod";
2226
2476
  function registerEventTool(server, ctx) {
2227
2477
  server.tool(
2228
2478
  "yapout_event",
2229
2479
  "Report a status event back to yapout for the activity feed",
2230
2480
  {
2231
- pipelineRunId: z7.string().describe("The pipeline run ID"),
2232
- event: z7.string().describe(
2481
+ pipelineRunId: z6.string().describe("The pipeline run ID"),
2482
+ event: z6.string().describe(
2233
2483
  'Event type (e.g., "reading_codebase", "writing_code", "running_tests")'
2234
2484
  ),
2235
- message: z7.string().describe("Human-readable description")
2485
+ message: z6.string().describe("Human-readable description")
2236
2486
  },
2237
2487
  withScopeCheck(ctx, "yapout_event", async (args) => {
2238
2488
  try {
@@ -2268,7 +2518,7 @@ function registerEventTool(server, ctx) {
2268
2518
  }
2269
2519
 
2270
2520
  // src/mcp/tools/ship.ts
2271
- import { z as z8 } from "zod";
2521
+ import { z as z7 } from "zod";
2272
2522
 
2273
2523
  // src/lib/github-cli.ts
2274
2524
  import { execSync as execSync3 } from "child_process";
@@ -2347,10 +2597,10 @@ function registerShipTool(server, ctx) {
2347
2597
  "yapout_ship",
2348
2598
  "Commit, push, open a PR, and mark the work item as done. Run yapout_check first if post-flight checks are configured.",
2349
2599
  {
2350
- message: z8.string().optional().describe("Custom commit message (overrides template)"),
2351
- skipPr: z8.boolean().optional().describe("Just push, don't open a PR"),
2352
- pipelineRunId: z8.string().describe("The pipeline run ID from yapout_claim"),
2353
- 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)")
2354
2604
  },
2355
2605
  withScopeCheck(ctx, "yapout_ship", async (args) => {
2356
2606
  const gitCwd = args.worktreePath || ctx.cwd;
@@ -2570,7 +2820,7 @@ function registerShipTool(server, ctx) {
2570
2820
  }
2571
2821
 
2572
2822
  // src/mcp/tools/check.ts
2573
- import { z as z9 } from "zod";
2823
+ import { z as z8 } from "zod";
2574
2824
  import { exec } from "child_process";
2575
2825
  var COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
2576
2826
  var MAX_OUTPUT_LINES = 100;
@@ -2581,7 +2831,7 @@ function truncate(text) {
2581
2831
  ` + lines.slice(-MAX_OUTPUT_LINES).join("\n");
2582
2832
  }
2583
2833
  function runCommand(command, cwd) {
2584
- return new Promise((resolve12) => {
2834
+ return new Promise((resolve16) => {
2585
2835
  const start = Date.now();
2586
2836
  const child = exec(command, {
2587
2837
  cwd,
@@ -2589,7 +2839,7 @@ function runCommand(command, cwd) {
2589
2839
  maxBuffer: 10 * 1024 * 1024
2590
2840
  // 10MB
2591
2841
  }, (error, stdout, stderr) => {
2592
- resolve12({
2842
+ resolve16({
2593
2843
  exitCode: error?.code ?? (error ? 1 : 0),
2594
2844
  stdout: truncate(stdout),
2595
2845
  stderr: truncate(stderr),
@@ -2597,7 +2847,7 @@ function runCommand(command, cwd) {
2597
2847
  });
2598
2848
  });
2599
2849
  child.on("error", () => {
2600
- resolve12({
2850
+ resolve16({
2601
2851
  exitCode: 1,
2602
2852
  stdout: "",
2603
2853
  stderr: `Command timed out after ${COMMAND_TIMEOUT_MS / 1e3}s`,
@@ -2611,9 +2861,9 @@ function registerCheckTool(server, ctx) {
2611
2861
  "yapout_check",
2612
2862
  "Run post-flight checks (lint, test, typecheck) before shipping. Configure commands in .yapout/config.yml",
2613
2863
  {
2614
- commands: z9.array(z9.string()).optional().describe("Override: run these commands instead of post_flight config"),
2615
- pipelineRunId: z9.string().optional().describe("Pipeline run ID for event reporting"),
2616
- 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")
2617
2867
  },
2618
2868
  withScopeCheck(ctx, "yapout_check", async (args) => {
2619
2869
  const checkCwd = args.worktreePath || ctx.cwd;
@@ -2683,109 +2933,26 @@ function registerCheckTool(server, ctx) {
2683
2933
  );
2684
2934
  }
2685
2935
 
2686
- // src/mcp/tools/bundle.ts
2687
- import { z as z10 } from "zod";
2688
- import { join as join10 } from "path";
2689
- import { existsSync as existsSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
2690
- function registerBundleTool(server, ctx) {
2936
+ // src/mcp/tools/get-unenriched-findings.ts
2937
+ import { z as z9 } from "zod";
2938
+ function registerGetUnenrichedFindingsTool(server, ctx) {
2691
2939
  server.tool(
2692
- "yapout_bundle",
2693
- "Add a finding to the current finding's bundle so they ship as one PR",
2940
+ "yapout_get_unenriched_finding",
2941
+ `Start enriching a finding. Returns the next draft finding (or a specific one by ID) with full context including the original capture quote, project context, and existing finding titles for duplicate detection.
2942
+
2943
+ The finding is locked to "enriching" status \u2014 no other agent can enrich it concurrently.
2944
+
2945
+ After calling this tool, you should:
2946
+ 1. Read the relevant parts of the codebase to understand the area affected
2947
+ 2. Check for duplicates against the existingFindings list
2948
+ 3. If the finding is ambiguous, ask the developer clarifying questions in conversation
2949
+ 4. When confident, call yapout_save_enrichment with a clean description, acceptance criteria, and implementation brief`,
2694
2950
  {
2695
- findingId: z10.string().describe("Finding ID to add to the bundle"),
2696
- withFinding: z10.string().describe("Lead finding ID (currently being worked on)")
2951
+ findingId: z9.string().optional().describe("Specific finding ID to enrich. If omitted, returns the highest priority draft finding.")
2697
2952
  },
2698
- withScopeCheck(ctx, "yapout_bundle", async (args) => {
2699
- const result = await ctx.client.mutation(
2700
- anyApi5.functions.bundles.createBundle,
2701
- { leadFindingId: args.withFinding, joiningFindingId: args.findingId }
2702
- );
2703
- const bundledBrief = await ctx.client.query(
2704
- anyApi5.functions.bundles.getBundledBrief,
2705
- { bundleId: result.bundleId }
2706
- );
2707
- if (!bundledBrief) {
2708
- return {
2709
- content: [
2710
- {
2711
- type: "text",
2712
- text: JSON.stringify({
2713
- bundleId: result.bundleId,
2714
- message: "Bundled successfully but could not fetch combined brief."
2715
- })
2716
- }
2717
- ]
2718
- };
2719
- }
2720
- const sections = [
2721
- `# Bundle: ${bundledBrief.findings.length} findings`,
2722
- ""
2723
- ];
2724
- for (const t of bundledBrief.findings) {
2725
- sections.push(`## ${t.linearIssueId ?? t.findingId}: ${t.title}`);
2726
- sections.push("");
2727
- sections.push(`**Priority:** ${t.priority} | **Type:** ${t.type}`);
2728
- if (t.linearIssueUrl) sections.push(`**Linear:** ${t.linearIssueUrl}`);
2729
- sections.push("", t.description);
2730
- if (t.enrichedDescription) {
2731
- sections.push("", "### Enriched Description", "", t.enrichedDescription);
2732
- }
2733
- if (t.implementationBrief) {
2734
- sections.push("", "### Implementation Brief", "", t.implementationBrief);
2735
- }
2736
- sections.push("", "---", "");
2737
- }
2738
- if (bundledBrief.projectContext) {
2739
- sections.push("## Project Context", "", bundledBrief.projectContext);
2740
- }
2741
- const combinedBrief = sections.join("\n");
2742
- const yapoutDir = join10(ctx.cwd, ".yapout");
2743
- if (!existsSync10(yapoutDir)) mkdirSync7(yapoutDir, { recursive: true });
2744
- writeFileSync8(join10(yapoutDir, "brief.md"), combinedBrief);
2745
- return {
2746
- content: [
2747
- {
2748
- type: "text",
2749
- text: JSON.stringify(
2750
- {
2751
- bundleId: result.bundleId,
2752
- findings: bundledBrief.findings.map((t) => ({
2753
- findingId: t.findingId,
2754
- title: t.title
2755
- })),
2756
- combinedBrief,
2757
- message: `${bundledBrief.findings.length} findings bundled. Brief updated.`
2758
- },
2759
- null,
2760
- 2
2761
- )
2762
- }
2763
- ]
2764
- };
2765
- })
2766
- );
2767
- }
2768
-
2769
- // src/mcp/tools/get-unenriched-findings.ts
2770
- import { z as z11 } from "zod";
2771
- function registerGetUnenrichedFindingsTool(server, ctx) {
2772
- server.tool(
2773
- "yapout_get_unenriched_finding",
2774
- `Start enriching a finding. Returns the next draft finding (or a specific one by ID) with full context including the original capture quote, project context, and existing finding titles for duplicate detection.
2775
-
2776
- The finding is locked to "enriching" status \u2014 no other agent can enrich it concurrently.
2777
-
2778
- After calling this tool, you should:
2779
- 1. Read the relevant parts of the codebase to understand the area affected
2780
- 2. Check for duplicates against the existingFindings list
2781
- 3. If the finding is ambiguous, ask the developer clarifying questions in conversation
2782
- 4. When confident, call yapout_save_enrichment with a clean description, acceptance criteria, and implementation brief`,
2783
- {
2784
- findingId: z11.string().optional().describe("Specific finding ID to enrich. If omitted, returns the highest priority draft finding.")
2785
- },
2786
- withScopeCheck(ctx, "yapout_get_unenriched_finding", async (args) => {
2787
- const projectId = ctx.projectId ?? process.env.YAPOUT_PROJECT_ID;
2788
- if (!projectId) {
2953
+ withScopeCheck(ctx, "yapout_get_unenriched_finding", async (args) => {
2954
+ const projectId = ctx.projectId ?? process.env.YAPOUT_PROJECT_ID;
2955
+ if (!projectId) {
2789
2956
  return {
2790
2957
  content: [
2791
2958
  {
@@ -2895,7 +3062,7 @@ function registerGetExistingFindingsTool(server, ctx) {
2895
3062
  }
2896
3063
 
2897
3064
  // src/mcp/tools/save-enrichment.ts
2898
- import { z as z12 } from "zod";
3065
+ import { z as z10 } from "zod";
2899
3066
 
2900
3067
  // src/mcp/tools/enrichment-session.ts
2901
3068
  var activeSessions = /* @__PURE__ */ new Map();
@@ -2940,22 +3107,22 @@ This tool saves the enrichment, then automatically creates the Linear issue with
2940
3107
 
2941
3108
  The finding transitions: enriching \u2192 enriched \u2192 ready.`,
2942
3109
  {
2943
- findingId: z12.string().describe("The finding ID to enrich (from yapout_get_unenriched_finding)"),
2944
- title: z12.string().describe("Refined finding title \u2014 improve it if the original was vague"),
2945
- cleanDescription: z12.string().describe("Human-readable summary for the Linear issue body. Write the kind of finding a senior engineer would write."),
2946
- acceptanceCriteria: z12.array(z12.string()).describe("List of testable acceptance criteria (each a single clear statement)"),
2947
- implementationBrief: z12.string().describe("Deep technical context for the implementing agent: which files, what approach, edge cases to watch for"),
2948
- clarifications: z12.array(
2949
- z12.object({
2950
- question: z12.string().describe("The question you asked the developer"),
2951
- answer: z12.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")
2952
3119
  })
2953
3120
  ).optional().describe("Only meaningful Q&A from the conversation \u2014 deviations from expected scope, scoping decisions, etc. Omit if you had no questions."),
2954
- isOversized: z12.boolean().optional().describe("Set to true if this finding is too large for a single PR"),
2955
- suggestedSplit: z12.array(z12.string()).optional().describe("If oversized: suggested sub-finding titles for breaking it down"),
2956
- nature: z12.enum(["implementable", "operational", "spike"]).optional().describe("Override the finding's nature if enrichment reveals it should be reclassified"),
2957
- cloudSafe: z12.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."),
2958
- sessionId: z12.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.")
2959
3126
  },
2960
3127
  withScopeCheck(ctx, "yapout_save_enrichment", async (args) => {
2961
3128
  try {
@@ -3027,13 +3194,13 @@ The finding transitions: enriching \u2192 enriched \u2192 ready.`,
3027
3194
  }
3028
3195
 
3029
3196
  // src/mcp/tools/sync-to-linear.ts
3030
- import { z as z13 } from "zod";
3197
+ import { z as z11 } from "zod";
3031
3198
  function registerSyncToLinearTool(server, ctx) {
3032
3199
  server.tool(
3033
3200
  "yapout_sync_to_linear",
3034
3201
  "Trigger Linear issue creation for an enriched finding. The sync runs server-side (encrypted Linear token in Convex).",
3035
3202
  {
3036
- findingId: z13.string().describe("The finding ID to sync to Linear")
3203
+ findingId: z11.string().describe("The finding ID to sync to Linear")
3037
3204
  },
3038
3205
  withScopeCheck(ctx, "yapout_sync_to_linear", async (args) => {
3039
3206
  try {
@@ -3072,371 +3239,8 @@ function registerSyncToLinearTool(server, ctx) {
3072
3239
  );
3073
3240
  }
3074
3241
 
3075
- // src/mcp/tools/submit-yap-session.ts
3076
- import { z as z14 } from "zod";
3077
- function registerSubmitYapSessionTool(server, ctx) {
3078
- server.tool(
3079
- "yapout_submit_yap_session",
3080
- "Submit a yap session transcript for finding extraction",
3081
- {
3082
- title: z14.string().describe("Session title (e.g., 'Notification system brainstorm')"),
3083
- transcript: z14.string().describe("Cleaned conversation transcript")
3084
- },
3085
- withScopeCheck(ctx, "yapout_submit_yap_session", async (args) => {
3086
- if (!ctx.projectId) {
3087
- return {
3088
- content: [
3089
- {
3090
- type: "text",
3091
- text: "No project linked. Run yapout_init or yapout link first."
3092
- }
3093
- ],
3094
- isError: true
3095
- };
3096
- }
3097
- try {
3098
- const captureId = await ctx.client.mutation(
3099
- anyApi5.functions.captures.createFromYapSession,
3100
- {
3101
- projectId: ctx.projectId,
3102
- title: args.title,
3103
- transcript: args.transcript
3104
- }
3105
- );
3106
- return {
3107
- content: [
3108
- {
3109
- type: "text",
3110
- text: JSON.stringify(
3111
- {
3112
- captureId,
3113
- message: "Yap session submitted. Findings will appear for review shortly."
3114
- },
3115
- null,
3116
- 2
3117
- )
3118
- }
3119
- ]
3120
- };
3121
- } catch (err) {
3122
- return {
3123
- content: [
3124
- {
3125
- type: "text",
3126
- text: `Failed to submit yap session: ${err.message}`
3127
- }
3128
- ],
3129
- isError: true
3130
- };
3131
- }
3132
- })
3133
- );
3134
- }
3135
-
3136
- // src/mcp/tools/start-yap.ts
3137
- import { z as z15 } from "zod";
3138
- var PERSONA_PRESETS = {
3139
- "tech lead": "You are an experienced tech lead. You care about maintainability, simplicity, and shipping. Challenge over-engineering and vague scope.",
3140
- "qa engineer": "You are a skeptical QA engineer. Focus on error states, edge cases, missing validation, accessibility, and user-facing failure modes.",
3141
- "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.",
3142
- "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."
3143
- };
3144
- function resolvePersona(input5) {
3145
- const lower = input5.toLowerCase().trim();
3146
- return PERSONA_PRESETS[lower] ?? input5;
3147
- }
3148
- function registerStartYapTool(server, ctx) {
3149
- server.tool(
3150
- "yapout_start_yap",
3151
- "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.",
3152
- {
3153
- persona: z15.string().optional().describe(
3154
- 'Interviewer persona: "tech lead", "qa engineer", "product owner", "end user", or a custom description'
3155
- ),
3156
- context: z15.string().optional().describe("What the developer wants to discuss")
3157
- },
3158
- withScopeCheck(ctx, "yapout_start_yap", async (args) => {
3159
- if (!ctx.projectId) {
3160
- return {
3161
- content: [
3162
- {
3163
- type: "text",
3164
- text: "No project linked. Run yapout_init or yapout link first."
3165
- }
3166
- ],
3167
- isError: true
3168
- };
3169
- }
3170
- const persona = args.persona ?? "tech lead";
3171
- const personaBlock = resolvePersona(persona);
3172
- 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.";
3173
- const instructions = `You are now in a yapout yap session \u2014 a structured brainstorming conversation about this codebase.
3174
-
3175
- YOUR ROLE: ${personaBlock}
3176
-
3177
- WHAT YOU DO:
3178
- - Have a natural, focused conversation about the developer's idea
3179
- - Read files from the codebase to inform your questions (package.json, schema, relevant source files)
3180
- - Ask ONE question at a time \u2014 don't overwhelm
3181
- - Push back on vague statements: "What do you mean by 'better'? Better for whom?"
3182
- - Surface edge cases: "What happens when the user has no internet?"
3183
- - Reference actual code: "I see you have a stateMachines.ts \u2014 should this new status go there?"
3184
- - When the developer makes a clear decision, acknowledge it and move to the next topic
3185
- - Keep the conversation productive \u2014 steer away from tangents
3186
-
3187
- WHAT YOU DON'T DO:
3188
- - Don't implement anything. Don't write code. Don't edit files. Just discuss.
3189
- - Don't agree with everything. Challenge ideas that seem undercooked.
3190
- - Don't ask more than 5 questions without letting the developer steer.
3191
- - 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.
3192
-
3193
- ${contextSection}
3194
-
3195
- DURING THE SESSION \u2014 COMPLETENESS TRACKING:
3196
- As the conversation progresses, you are building a mental model of every finding. For each finding, track:
3197
- - What has been clearly stated (title, scope, priority)
3198
- - What has been discussed in enough depth to write a finding (enrichment)
3199
- - What is still vague, contradicted, or unanswered
3200
-
3201
- 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.
3202
-
3203
- 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.
3204
-
3205
- BEFORE SUBMITTING \u2014 GAP FILLING:
3206
- Before you submit, review your mental model of every finding. For each one, ask yourself:
3207
- - Could a developer read this finding and start working without asking questions?
3208
- - Are the acceptance criteria specific enough to verify?
3209
- - Are there ambiguities the user didn't resolve?
3210
-
3211
- 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.
3212
-
3213
- 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.
3214
-
3215
- ENRICHMENT ASSESSMENT:
3216
- For each finding, you must make an honest call: is this enriched or not?
3217
-
3218
- Mark a finding as ENRICHED (isEnriched: true) when:
3219
- - The conversation covered it thoroughly enough to produce a clear finding
3220
- - You can write an enrichedDescription that a senior engineer would recognize as well-scoped
3221
- - You can write at least 2-3 testable acceptance criteria
3222
- - The user explicitly validated the scope (not just mentioned it in passing)
3223
-
3224
- Mark a finding as NOT ENRICHED (isEnriched: false or omitted) when:
3225
- - It came up late in the conversation without much discussion
3226
- - The user mentioned it but didn't elaborate on scope or requirements
3227
- - You're uncertain about key aspects (what it should do, how it should work)
3228
- - It's a spike or needs further scoping
3229
-
3230
- 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.
3231
-
3232
- PRESENTING THE FINAL PICTURE:
3233
- When the conversation feels complete, present a summary grouped by bundles and standalone issues:
3234
-
3235
- "Here's what I've gathered from our conversation:
3236
-
3237
- **[Bundle Name]** \u2014 [one-line description]
3238
- 1. [Child issue] \u2014 enriched \u2713
3239
- 2. [Child issue] \u2014 enriched \u2713
3240
- 3. [Child issue] \u2014 needs enrichment (we didn't discuss scope)
3241
-
3242
- **Standalone issues:**
3243
- 4. [Issue] \u2014 enriched \u2713
3244
-
3245
- [Any open questions you still need answered]
3246
-
3247
- Should I submit this to yapout?"
3248
-
3249
- SUBMITTING:
3250
- On confirmation, call yapout_extract_from_yap with the full data.
3251
-
3252
- For each finding, provide:
3253
- - title, description, sourceQuote, type, priority, confidence, nature
3254
- - isEnriched: your honest assessment (see above)
3255
-
3256
- For ENRICHED findings (isEnriched: true), also include:
3257
- - enrichedDescription: clean, final description \u2014 write the kind of finding a senior engineer would write
3258
- - acceptanceCriteria: array of specific, testable statements
3259
- - 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.
3260
- - clarifications: relevant Q&A from the conversation that shaped the finding
3261
-
3262
- For findings with CHILDREN (bundles), also include:
3263
- - bundleDescription: what this body of work accomplishes
3264
- - suggestedOrder: implementation sequencing ("Phase 1: A, then Phase 2: B+C")
3265
- - children: array of child issues (each with their own enrichment data)
3266
-
3267
- For children with DEPENDENCIES, include:
3268
- - dependsOn: array of sibling indices (0-based) that must complete first
3269
-
3270
- Structure:
3271
- - Findings with children become bundles \u2014 never create child issues as separate top-level findings
3272
- - Standalone issues go at the top level
3273
- - The hierarchy you produce should be the final structure
3274
-
3275
- Call yapout_extract_from_yap with:
3276
- - sessionTitle: descriptive title for this session
3277
- - sessionTranscript: clean summary of the conversation (meeting-notes style)
3278
- - findings: the array (bundles with children, standalone issues)
3279
-
3280
- 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.
3281
-
3282
- Only fall back to yapout_submit_yap_session if the session was purely exploratory with no clear actionable findings.
3283
-
3284
- --- BEGIN THE SESSION NOW ---`;
3285
- return {
3286
- content: [
3287
- {
3288
- type: "text",
3289
- text: JSON.stringify(
3290
- {
3291
- projectId: ctx.projectId,
3292
- projectName: ctx.projectName,
3293
- persona,
3294
- instructions
3295
- },
3296
- null,
3297
- 2
3298
- )
3299
- }
3300
- ]
3301
- };
3302
- })
3303
- );
3304
- }
3305
-
3306
- // src/mcp/tools/extract-from-yap.ts
3307
- import { z as z16 } from "zod";
3308
- var clarificationSchema = z16.object({
3309
- question: z16.string().describe("The question asked during the conversation"),
3310
- answer: z16.string().describe("The answer given")
3311
- });
3312
- var childSchema = z16.object({
3313
- title: z16.string().describe("Child issue title"),
3314
- description: z16.string().describe("What needs to be done and why"),
3315
- sourceQuote: z16.string().describe("Relevant excerpt from the conversation"),
3316
- type: z16.enum(["feature", "bug", "chore", "spike"]).describe("Finding category"),
3317
- priority: z16.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
3318
- confidence: z16.number().min(0).max(1).describe("Confidence this is a clear, actionable finding (0-1)"),
3319
- nature: z16.enum(["implementable", "operational", "spike"]).describe("What kind of work"),
3320
- // Enrichment — set isEnriched: true only if the conversation covered this
3321
- // issue thoroughly enough to produce a finding a developer could pick up.
3322
- isEnriched: z16.boolean().optional().describe(
3323
- "Did the conversation cover this issue deeply enough to produce a complete finding? true = enriched (ready for Linear), false/omitted = draft (needs async enrichment)"
3324
- ),
3325
- enrichedDescription: z16.string().optional().describe("Clean, final description for the Linear issue body (required if isEnriched)"),
3326
- acceptanceCriteria: z16.array(z16.string()).optional().describe("Testable acceptance criteria (required if isEnriched)"),
3327
- implementationBrief: z16.string().optional().describe("Technical context: files, approach, edge cases (optional \u2014 the implementing agent reads the codebase anyway)"),
3328
- clarifications: z16.array(clarificationSchema).optional().describe("Relevant Q&A from the conversation that shaped this finding"),
3329
- dependsOn: z16.array(z16.number()).optional().describe("Indices (0-based) of sibling children that must be completed first")
3330
- });
3331
- function registerExtractFromYapTool(server, ctx) {
3332
- server.tool(
3333
- "yapout_extract_from_yap",
3334
- "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.",
3335
- {
3336
- sessionTitle: z16.string().describe("Descriptive title for this yap session"),
3337
- sessionTranscript: z16.string().describe("Cleaned transcript of the conversation (meeting-notes style)"),
3338
- findings: z16.array(
3339
- z16.object({
3340
- title: z16.string().describe("Finding title"),
3341
- description: z16.string().describe("What needs to be done and why"),
3342
- sourceQuote: z16.string().describe("Relevant excerpt from the conversation"),
3343
- type: z16.enum(["feature", "bug", "chore", "spike"]).describe("Finding category (do not use spike \u2014 use nature instead)"),
3344
- priority: z16.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
3345
- confidence: z16.number().min(0).max(1).describe("Your confidence this is a clear, actionable finding (0-1)"),
3346
- nature: z16.enum(["implementable", "operational", "spike"]).describe("implementable = code changes, operational = manual task, spike = needs scoping"),
3347
- sourceFindingId: z16.string().optional().describe("ID of an existing finding this scopes (e.g., scoping a spike)"),
3348
- // Enrichment data for standalone findings
3349
- isEnriched: z16.boolean().optional().describe("For standalone issues: was this thoroughly discussed? true = enriched, false = draft"),
3350
- enrichedDescription: z16.string().optional().describe("Clean description for Linear (required if isEnriched)"),
3351
- acceptanceCriteria: z16.array(z16.string()).optional().describe("Testable acceptance criteria (required if isEnriched)"),
3352
- implementationBrief: z16.string().optional().describe("Technical context (optional)"),
3353
- clarifications: z16.array(clarificationSchema).optional().describe("Relevant Q&A from conversation"),
3354
- // Bundle fields — when a finding has children, a bundle is created
3355
- bundleDescription: z16.string().optional().describe("What this body of work accomplishes (only for findings with children)"),
3356
- suggestedOrder: z16.string().optional().describe("Implementation order: 'Phase 1: A, then Phase 2: B+C' (only for findings with children)"),
3357
- children: z16.array(childSchema).optional().describe("Child issues \u2014 when present, a bundle is created from this finding")
3358
- })
3359
- ).describe("Findings from the conversation. Projects include children inline.")
3360
- },
3361
- withScopeCheck(ctx, "yapout_extract_from_yap", async (args) => {
3362
- if (!ctx.projectId) {
3363
- return {
3364
- content: [
3365
- {
3366
- type: "text",
3367
- text: "No project linked. Run yapout_init or yapout link first."
3368
- }
3369
- ],
3370
- isError: true
3371
- };
3372
- }
3373
- try {
3374
- const result = await ctx.client.mutation(
3375
- anyApi5.functions.captures.extractFromYapSession,
3376
- {
3377
- projectId: ctx.projectId,
3378
- title: args.sessionTitle,
3379
- transcript: args.sessionTranscript,
3380
- findings: args.findings
3381
- }
3382
- );
3383
- let enrichedCount = 0;
3384
- let draftCount = 0;
3385
- let totalFindings = 0;
3386
- for (const item of result.items) {
3387
- if (item.children && item.children.length > 0) {
3388
- for (const child of item.children) {
3389
- totalFindings++;
3390
- if (child.findingStatus === "enriched") enrichedCount++;
3391
- else draftCount++;
3392
- }
3393
- } else {
3394
- totalFindings++;
3395
- if (item.findingStatus === "enriched") enrichedCount++;
3396
- else draftCount++;
3397
- }
3398
- }
3399
- const parts = [];
3400
- if (enrichedCount > 0) parts.push(`${enrichedCount} enriched (ready for Linear)`);
3401
- if (draftCount > 0) parts.push(`${draftCount} need enrichment`);
3402
- return {
3403
- content: [
3404
- {
3405
- type: "text",
3406
- text: JSON.stringify(
3407
- {
3408
- captureId: result.captureId,
3409
- items: result.items,
3410
- summary: {
3411
- totalFindings,
3412
- enriched: enrichedCount,
3413
- needsEnrichment: draftCount
3414
- },
3415
- message: `Created ${totalFindings} finding${totalFindings === 1 ? "" : "s"}: ${parts.join(", ")}. Review in the yapout work queue.`
3416
- },
3417
- null,
3418
- 2
3419
- )
3420
- }
3421
- ]
3422
- };
3423
- } catch (err) {
3424
- return {
3425
- content: [
3426
- {
3427
- type: "text",
3428
- text: `Failed to extract from yap session: ${err.message}`
3429
- }
3430
- ],
3431
- isError: true
3432
- };
3433
- }
3434
- })
3435
- );
3436
- }
3437
-
3438
3242
  // src/mcp/tools/decompose-finding.ts
3439
- import { z as z17 } from "zod";
3243
+ import { z as z12 } from "zod";
3440
3244
  function registerDecomposeFindingTool(server, ctx) {
3441
3245
  server.tool(
3442
3246
  "yapout_decompose_finding",
@@ -3454,19 +3258,19 @@ archives the original finding. Returns the bundle ID and child finding IDs.
3454
3258
  Each child issue's implementation brief must be detailed enough to stand alone as a
3455
3259
  full spec \u2014 schema changes, files to modify, edge cases, and acceptance criteria.`,
3456
3260
  {
3457
- findingId: z17.string().describe("The finding being decomposed (from yapout_get_unenriched_finding)"),
3458
- bundleDescription: z17.string().describe("Description for the bundle \u2014 what this body of work accomplishes"),
3459
- suggestedOrder: z17.string().describe("Human-readable implementation order (e.g. 'Phase 1: A, then Phase 2: B+C in parallel, then Phase 3: D')"),
3460
- linearProjectId: z17.string().optional().describe("Existing Linear project ID to associate with. Omit to skip Linear project association."),
3461
- issues: z17.array(
3462
- z17.object({
3463
- title: z17.string().describe("Child issue title"),
3464
- description: z17.string().describe("What needs to be done and why"),
3465
- acceptanceCriteria: z17.array(z17.string()).describe("Testable acceptance criteria"),
3466
- implementationBrief: z17.string().describe("Full spec: files to modify, schema changes, edge cases, approach"),
3467
- type: z17.enum(["feature", "bug", "chore", "spike"]).describe("Category of work"),
3468
- priority: z17.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
3469
- dependsOn: z17.array(z17.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")
3470
3274
  })
3471
3275
  ).describe("The child issues produced by decomposition")
3472
3276
  },
@@ -3529,7 +3333,7 @@ full spec \u2014 schema changes, files to modify, edge cases, and acceptance cri
3529
3333
  }
3530
3334
 
3531
3335
  // src/mcp/tools/mark-duplicate.ts
3532
- import { z as z18 } from "zod";
3336
+ import { z as z13 } from "zod";
3533
3337
  function registerMarkDuplicateTool(server, ctx) {
3534
3338
  server.tool(
3535
3339
  "yapout_mark_duplicate",
@@ -3539,9 +3343,9 @@ flow when you discover the work is already tracked in Linear.
3539
3343
  The finding must be in "enriching" or "enriched" status. It will be transitioned to
3540
3344
  "failed" with the duplicate reference stored. Nothing is synced to Linear.`,
3541
3345
  {
3542
- findingId: z18.string().describe("The yapout finding to archive as a duplicate"),
3543
- duplicateOfLinearId: z18.string().describe("The Linear issue identifier it duplicates (e.g. 'ENG-234')"),
3544
- reason: z18.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")
3545
3349
  },
3546
3350
  withScopeCheck(ctx, "yapout_mark_duplicate", async (args) => {
3547
3351
  if (!ctx.projectId) {
@@ -3670,7 +3474,7 @@ and issue counts so you can ask the user for confirmation before creating a new
3670
3474
  }
3671
3475
 
3672
3476
  // src/mcp/tools/start-enrichment.ts
3673
- import { z as z19 } from "zod";
3477
+ import { z as z14 } from "zod";
3674
3478
  function registerStartEnrichmentTool(server, ctx) {
3675
3479
  server.tool(
3676
3480
  "yapout_start_enrichment",
@@ -3681,10 +3485,10 @@ and maintains filter criteria so you don't re-pass them on every call.
3681
3485
 
3682
3486
  Optionally filter by tags, capture, or explicit finding IDs.`,
3683
3487
  {
3684
- filter: z19.object({
3685
- tags: z19.array(z19.string()).optional().describe("Only enrich findings with these tags"),
3686
- captureId: z19.string().optional().describe("Only enrich findings from this capture"),
3687
- findingIds: z19.array(z19.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")
3688
3492
  }).optional().describe("Filter criteria. If omitted, all draft findings in the project are included.")
3689
3493
  },
3690
3494
  withScopeCheck(ctx, "yapout_start_enrichment", async (args) => {
@@ -3740,7 +3544,7 @@ Optionally filter by tags, capture, or explicit finding IDs.`,
3740
3544
  }
3741
3545
 
3742
3546
  // src/mcp/tools/enrich-next.ts
3743
- import { z as z20 } from "zod";
3547
+ import { z as z15 } from "zod";
3744
3548
  function registerEnrichNextTool(server, ctx) {
3745
3549
  server.tool(
3746
3550
  "yapout_enrich_next",
@@ -3751,9 +3555,9 @@ Returns the next unclaimed draft finding matching the session's filter.
3751
3555
 
3752
3556
  When done=true, all findings have been processed.`,
3753
3557
  {
3754
- sessionId: z20.string().describe("Session ID from yapout_start_enrichment"),
3755
- skip: z20.boolean().optional().describe("If true, skip the current finding (release back to draft)"),
3756
- skipFindingId: z20.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)")
3757
3561
  },
3758
3562
  withScopeCheck(ctx, "yapout_enrich_next", async (args) => {
3759
3563
  const session = getSession(args.sessionId);
@@ -3881,149 +3685,32 @@ When done=true, all findings have been processed.`,
3881
3685
  );
3882
3686
  }
3883
3687
 
3884
- // src/mcp/tools/enrich-bundle.ts
3885
- import { z as z21 } from "zod";
3886
- function registerEnrichBundleTool(server, ctx) {
3688
+ // src/mcp/tools/block-enrichment.ts
3689
+ import { z as z16 } from "zod";
3690
+ function registerBlockEnrichmentTool(server, ctx) {
3887
3691
  server.tool(
3888
- "yapout_enrich_bundle",
3889
- `Claim an entire bundle for enrichment. Returns ALL findings in the bundle at once
3890
- with their relationships, source quotes, and project context.
3692
+ "yapout_block_enrichment",
3693
+ `Refuse to enrich a finding because the user has not provided enough specifics for an autonomous agent to complete the work end-to-end.
3694
+
3695
+ Use this when the finding lacks any of: target file/component, intended behavior, scope boundaries, or success criteria. Do NOT produce a half-baked enrichment "for review" \u2014 block instead. The bar for "enriched" is "another agent can ship this with zero further input."
3891
3696
 
3892
- This is for enriching a bundle as a single cohesive unit \u2014 NOT individual findings.
3893
- The agent should understand the full scope, ask questions about the bundle as a whole,
3894
- then call yapout_save_bundle_enrichment with enrichment data for every finding.
3697
+ The finding must currently be in "enriching" status (claimed via yapout_get_unenriched_finding).
3895
3698
 
3896
- The bundle and all its findings transition to "enriching" status.`,
3699
+ Transitions: enriching \u2192 needs_input. The user sees the blockerReason and questions in the UI, adds context, and resubmits.`,
3897
3700
  {
3898
- bundleId: z21.string().describe("The bundle ID to enrich")
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.")
3899
3704
  },
3900
- withScopeCheck(ctx, "yapout_enrich_bundle", async (args) => {
3705
+ withScopeCheck(ctx, "yapout_block_enrichment", async (args) => {
3901
3706
  try {
3902
- const result = await ctx.client.mutation(
3903
- anyApi5.functions.bundles.claimBundleForEnrichment,
3904
- { bundleId: args.bundleId }
3905
- );
3906
- if (!result) {
3907
- return {
3908
- content: [{ type: "text", text: "Failed to claim bundle for enrichment." }],
3909
- isError: true
3910
- };
3911
- }
3912
- return {
3913
- content: [
3914
- {
3915
- type: "text",
3916
- text: JSON.stringify(result, null, 2)
3917
- }
3918
- ]
3919
- };
3920
- } catch (err) {
3921
- return {
3922
- content: [{ type: "text", text: `Error claiming bundle: ${err.message}` }],
3923
- isError: true
3924
- };
3925
- }
3926
- })
3927
- );
3928
- }
3929
-
3930
- // src/mcp/tools/save-bundle-enrichment.ts
3931
- import { z as z22 } from "zod";
3932
- function registerSaveBundleEnrichmentTool(server, ctx) {
3933
- server.tool(
3934
- "yapout_save_bundle_enrichment",
3935
- `Save enrichment for an entire bundle at once. Call this after you've analyzed all
3936
- findings in the bundle as a cohesive unit, read the codebase, and asked any questions.
3937
-
3938
- Provide:
3939
- - Bundle-level: overall description, combined acceptance criteria, implementation brief
3940
- - Per-finding: each finding gets its own refined title, description, acceptance criteria, and brief
3941
-
3942
- The bundle and all findings transition from "enriching" to "enriched".
3943
- Call yapout_sync_bundle_to_linear afterwards to create the Linear project.`,
3944
- {
3945
- bundleId: z22.string().describe("The bundle ID"),
3946
- title: z22.string().optional().describe("Refined bundle title (optional, keeps existing if omitted)"),
3947
- enrichedDescription: z22.string().describe("Bundle-level description \u2014 the cohesive story of what this bundle delivers"),
3948
- acceptanceCriteria: z22.array(z22.string()).describe("Bundle-level acceptance criteria"),
3949
- implementationBrief: z22.string().describe("Bundle-level implementation brief \u2014 overall approach, architecture decisions, key files"),
3950
- cloudSafe: z22.boolean().optional().describe("Set true ONLY if the entire bundle is small mechanical work (\u226430 lines total, single-file ideally, text/className/constant edits). Default false. If unsure, false."),
3951
- findings: z22.array(z22.object({
3952
- findingId: z22.string().describe("Finding ID"),
3953
- title: z22.string().describe("Refined finding title"),
3954
- enrichedDescription: z22.string().describe("Finding-specific description"),
3955
- acceptanceCriteria: z22.array(z22.string()).describe("Finding-specific acceptance criteria"),
3956
- implementationBrief: z22.string().describe("Finding-specific implementation brief")
3957
- })).describe("Per-finding enrichment data \u2014 one entry per finding in the bundle")
3958
- },
3959
- withScopeCheck(ctx, "yapout_save_bundle_enrichment", async (args) => {
3960
- try {
3961
- await ctx.client.mutation(
3962
- anyApi5.functions.bundles.saveBundleEnrichment,
3963
- {
3964
- bundleId: args.bundleId,
3965
- title: args.title,
3966
- enrichedDescription: args.enrichedDescription,
3967
- acceptanceCriteria: args.acceptanceCriteria,
3968
- implementationBrief: args.implementationBrief,
3969
- cloudSafe: args.cloudSafe,
3970
- findings: args.findings.map((f) => ({
3971
- findingId: f.findingId,
3972
- title: f.title,
3973
- enrichedDescription: f.enrichedDescription,
3974
- acceptanceCriteria: f.acceptanceCriteria,
3975
- implementationBrief: f.implementationBrief
3976
- }))
3977
- }
3978
- );
3979
- return {
3980
- content: [
3981
- {
3982
- type: "text",
3983
- text: JSON.stringify({
3984
- bundleId: args.bundleId,
3985
- findingsEnriched: args.findings.length,
3986
- message: `Bundle enriched successfully (${args.findings.length} findings). Call yapout_sync_bundle_to_linear to create the Linear project.`
3987
- }, null, 2)
3988
- }
3989
- ]
3990
- };
3991
- } catch (err) {
3992
- return {
3993
- content: [{ type: "text", text: `Error saving bundle enrichment: ${err.message}` }],
3994
- isError: true
3995
- };
3996
- }
3997
- })
3998
- );
3999
- }
4000
-
4001
- // src/mcp/tools/block-enrichment.ts
4002
- import { z as z23 } from "zod";
4003
- function registerBlockEnrichmentTool(server, ctx) {
4004
- server.tool(
4005
- "yapout_block_enrichment",
4006
- `Refuse to enrich a finding because the user has not provided enough specifics for an autonomous agent to complete the work end-to-end.
4007
-
4008
- Use this when the finding lacks any of: target file/component, intended behavior, scope boundaries, or success criteria. Do NOT produce a half-baked enrichment "for review" \u2014 block instead. The bar for "enriched" is "another agent can ship this with zero further input."
4009
-
4010
- The finding must currently be in "enriching" status (claimed via yapout_get_unenriched_finding).
4011
-
4012
- Transitions: enriching \u2192 needs_input. The user sees the blockerReason and questions in the UI, adds context, and resubmits.`,
4013
- {
4014
- findingId: z23.string().describe("The finding ID to block (currently in 'enriching')"),
4015
- blockerReason: z23.string().describe("One sentence: why this finding cannot be enriched as written. State the missing piece concretely."),
4016
- blockerQuestions: z23.array(z23.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.")
4017
- },
4018
- withScopeCheck(ctx, "yapout_block_enrichment", async (args) => {
4019
- try {
4020
- await ctx.client.mutation(
4021
- anyApi5.functions.localPipeline.blockLocalEnrichment,
4022
- {
4023
- findingId: args.findingId,
4024
- blockerReason: args.blockerReason,
4025
- blockerQuestions: args.blockerQuestions
4026
- }
3707
+ await ctx.client.mutation(
3708
+ anyApi5.functions.localPipeline.blockLocalEnrichment,
3709
+ {
3710
+ findingId: args.findingId,
3711
+ blockerReason: args.blockerReason,
3712
+ blockerQuestions: args.blockerQuestions
3713
+ }
4027
3714
  );
4028
3715
  return {
4029
3716
  content: [
@@ -4060,9 +3747,9 @@ Transitions: enriching \u2192 needs_input. The user sees the blockerReason and q
4060
3747
 
4061
3748
  Same semantics as yapout_block_enrichment but for an entire bundle. Bundle status transitions enriching \u2192 needs_input; child findings revert to draft.`,
4062
3749
  {
4063
- bundleId: z23.string().describe("The bundle ID to block (currently in 'enriching')"),
4064
- blockerReason: z23.string().describe("One sentence: why this bundle cannot be enriched."),
4065
- blockerQuestions: z23.array(z23.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.")
4066
3753
  },
4067
3754
  async (args) => {
4068
3755
  try {
@@ -5163,143 +4850,6 @@ function registerProjectTools(server, ctx) {
5163
4850
  registerTool(server, ctx, listProjectsTool);
5164
4851
  }
5165
4852
 
5166
- // src/mcp/tools/bundles-crud.ts
5167
- var getBundleTool = defineTool({
5168
- name: "yapout_get_bundle",
5169
- description: `Fetch a bundle by id, including its child findings array. Use this when you need full bundle context \u2014 title, description, enrichment data, claim state, child findings \u2014 not just the work-queue summary.`,
5170
- inputSchema: { bundleId: z.string() },
5171
- handler: async (ctx, args) => {
5172
- const bundle = await ctx.client.query(
5173
- anyApi5.functions.bundles.getBundle,
5174
- args
5175
- );
5176
- if (!bundle) {
5177
- return {
5178
- content: [{ type: "text", text: "Bundle not found." }],
5179
- isError: true
5180
- };
5181
- }
5182
- return {
5183
- content: [
5184
- { type: "text", text: JSON.stringify(bundle, null, 2) }
5185
- ]
5186
- };
5187
- }
5188
- });
5189
- var listBundlesTool = defineTool({
5190
- name: "yapout_list_bundles",
5191
- description: `List every bundle in the current project. Returns the bundle records (title, status, claim, archive metadata). For per-bundle child findings, follow up with yapout_get_bundle.`,
5192
- inputSchema: {},
5193
- handler: async (ctx) => {
5194
- if (!ctx.projectId) {
5195
- return {
5196
- content: [{ type: "text", text: "No project linked." }],
5197
- isError: true
5198
- };
5199
- }
5200
- const bundles = await ctx.client.query(
5201
- anyApi5.functions.bundles.getProjectBundles,
5202
- { projectId: ctx.projectId }
5203
- );
5204
- return {
5205
- content: [
5206
- { type: "text", text: JSON.stringify(bundles, null, 2) }
5207
- ]
5208
- };
5209
- }
5210
- });
5211
- var updateBundleTool = defineTool({
5212
- name: "yapout_update_bundle",
5213
- description: `Patch a bundle's editable content fields: title, description. Pass only the fields you want to change. Status / claim / archive go through dedicated tools.`,
5214
- inputSchema: {
5215
- bundleId: z.string(),
5216
- title: z.string().optional(),
5217
- description: z.string().optional()
5218
- },
5219
- handler: async (ctx, args) => {
5220
- await ctx.client.mutation(
5221
- anyApi5.functions.bundles.updateBundle,
5222
- args
5223
- );
5224
- return {
5225
- content: [{ type: "text", text: "Bundle updated." }]
5226
- };
5227
- }
5228
- });
5229
- var archiveBundleTool = defineTool({
5230
- name: "yapout_archive_bundle",
5231
- description: `Archive a bundle. Children are detached (their bundleId cleared) so they can be re-bundled or shipped solo. Use yapout_restore_bundle to bring it back.`,
5232
- inputSchema: { bundleId: z.string() },
5233
- handler: async (ctx, args) => {
5234
- await ctx.client.mutation(
5235
- anyApi5.functions.bundles.archiveBundle,
5236
- args
5237
- );
5238
- return {
5239
- content: [{ type: "text", text: "Bundle archived." }]
5240
- };
5241
- }
5242
- });
5243
- var restoreBundleTool = defineTool({
5244
- name: "yapout_restore_bundle",
5245
- description: `Restore an archived bundle. Defaults to draft; pass to: "ready" to bring it straight back to the open queue.`,
5246
- inputSchema: {
5247
- bundleId: z.string(),
5248
- to: z.enum(["draft", "ready"]).optional()
5249
- },
5250
- handler: async (ctx, args) => {
5251
- await ctx.client.mutation(
5252
- anyApi5.functions.bundles.restoreBundle,
5253
- args
5254
- );
5255
- return {
5256
- content: [{ type: "text", text: "Bundle restored." }]
5257
- };
5258
- }
5259
- });
5260
- var addFindingToBundleTool = defineTool({
5261
- name: "yapout_add_finding_to_bundle",
5262
- description: `Attach an existing finding to a bundle. The finding's bundleId is set; its status is left alone (use yapout_set_finding_status separately if you want to align statuses).`,
5263
- inputSchema: {
5264
- findingId: z.string(),
5265
- bundleId: z.string()
5266
- },
5267
- handler: async (ctx, args) => {
5268
- await ctx.client.mutation(
5269
- anyApi5.functions.findings.addToBundle,
5270
- args
5271
- );
5272
- return {
5273
- content: [{ type: "text", text: "Finding added to bundle." }]
5274
- };
5275
- }
5276
- });
5277
- var removeFindingFromBundleTool = defineTool({
5278
- name: "yapout_remove_finding_from_bundle",
5279
- description: `Detach a finding from its current bundle. The finding stays in the project; only its bundleId is cleared.`,
5280
- inputSchema: { findingId: z.string() },
5281
- handler: async (ctx, args) => {
5282
- await ctx.client.mutation(
5283
- anyApi5.functions.findings.removeFromBundle,
5284
- args
5285
- );
5286
- return {
5287
- content: [
5288
- { type: "text", text: "Finding removed from bundle." }
5289
- ]
5290
- };
5291
- }
5292
- });
5293
- function registerBundleCrudTools(server, ctx) {
5294
- registerTool(server, ctx, getBundleTool);
5295
- registerTool(server, ctx, listBundlesTool);
5296
- registerTool(server, ctx, updateBundleTool);
5297
- registerTool(server, ctx, archiveBundleTool);
5298
- registerTool(server, ctx, restoreBundleTool);
5299
- registerTool(server, ctx, addFindingToBundleTool);
5300
- registerTool(server, ctx, removeFindingFromBundleTool);
5301
- }
5302
-
5303
4853
  // src/mcp/tools/insights.ts
5304
4854
  var listInsightsTool = defineTool({
5305
4855
  name: "yapout_list_insights",
@@ -5471,32 +5021,26 @@ async function startMcpServer() {
5471
5021
  };
5472
5022
  const server = new McpServer({
5473
5023
  name: "yapout",
5474
- version: "0.15.2"
5024
+ version: "0.18.0"
5475
5025
  });
5476
5026
  registerInitTool(server, ctx);
5477
5027
  registerCompactTool(server, ctx);
5478
5028
  registerUpdateContextTool(server, ctx);
5479
- registerQueueTool(server, ctx);
5029
+ registerDevLoopTools(server, ctx);
5480
5030
  registerGetBriefTool(server, ctx);
5481
5031
  registerClaimTool(server, ctx);
5482
5032
  registerEventTool(server, ctx);
5483
5033
  registerShipTool(server, ctx);
5484
5034
  registerCheckTool(server, ctx);
5485
- registerBundleTool(server, ctx);
5486
5035
  registerGetUnenrichedFindingsTool(server, ctx);
5487
5036
  registerGetExistingFindingsTool(server, ctx);
5488
5037
  registerSaveEnrichmentTool(server, ctx);
5489
5038
  registerSyncToLinearTool(server, ctx);
5490
- registerSubmitYapSessionTool(server, ctx);
5491
- registerStartYapTool(server, ctx);
5492
- registerExtractFromYapTool(server, ctx);
5493
5039
  registerDecomposeFindingTool(server, ctx);
5494
5040
  registerMarkDuplicateTool(server, ctx);
5495
5041
  registerGetLinearProjectsTool(server, ctx);
5496
5042
  registerStartEnrichmentTool(server, ctx);
5497
5043
  registerEnrichNextTool(server, ctx);
5498
- registerEnrichBundleTool(server, ctx);
5499
- registerSaveBundleEnrichmentTool(server, ctx);
5500
5044
  registerBlockEnrichmentTool(server, ctx);
5501
5045
  registerSessionTools(server, ctx);
5502
5046
  registerCodebaseTools(server, ctx);
@@ -5506,7 +5050,6 @@ async function startMcpServer() {
5506
5050
  registerFindingReadTools(server, ctx);
5507
5051
  registerFindingWriteTools(server, ctx);
5508
5052
  registerProjectTools(server, ctx);
5509
- registerBundleCrudTools(server, ctx);
5510
5053
  registerInsightTools(server, ctx);
5511
5054
  registerPipelineRunTools(server, ctx);
5512
5055
  const transport = new StdioServerTransport();
@@ -5520,108 +5063,133 @@ var mcpServerCommand = new Command8("mcp-server").description("Start the MCP ser
5520
5063
 
5521
5064
  // src/commands/queue.ts
5522
5065
  import { Command as Command9 } from "commander";
5523
- import { resolve as resolve6 } from "path";
5066
+ import { resolve as resolve7 } from "path";
5524
5067
  import chalk9 from "chalk";
5525
- 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) => {
5526
5069
  const creds = requireAuth();
5527
- const cwd = resolve6(process.cwd());
5528
- const mapping = getProjectMapping(cwd);
5529
- if (!mapping) {
5070
+ const cwd = resolve7(process.cwd());
5071
+ const repoRoot = resolveRepoRoot(cwd);
5072
+ const resourceCfg = readResourceConfig(repoRoot);
5073
+ if (!resourceCfg) {
5530
5074
  console.error(
5531
- 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."
5532
5076
  );
5533
5077
  process.exit(1);
5534
5078
  }
5079
+ const limit = Math.max(1, parseInt(options.limit, 10) || 100);
5535
5080
  const client = createConvexClient(creds.token);
5536
- const { anyApi: anyApi6 } = await import("convex/server");
5537
- const [queueData, unenriched, pending] = await Promise.all([
5538
- client.query(anyApi6.functions.tickets.getLocalQueuedTickets, {
5539
- projectId: mapping.projectId
5540
- }),
5541
- client.query(anyApi6.functions.localPipeline.getUnenrichedTickets, {
5542
- projectId: mapping.projectId
5543
- }),
5544
- client.query(anyApi6.functions.localPipeline.getPendingSources, {
5545
- projectId: mapping.projectId
5546
- })
5547
- ]);
5548
- console.log();
5549
- if (queueData?.ready && queueData.ready.length > 0) {
5550
- console.log(chalk9.bold("Ready to implement:"));
5551
- for (const t of queueData.ready) {
5552
- const ref = t.linearTicketId ?? t.ticketId;
5553
- const prio = colorPriority(t.priority);
5554
- console.log(
5555
- ` ${chalk9.bold(ref)} ${t.title.slice(0, 45).padEnd(45)} ${prio} ${chalk9.dim(t.type)}`
5556
- );
5557
- }
5558
- 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);
5559
5096
  }
5560
- if (queueData?.blocked && queueData.blocked.length > 0) {
5561
- console.log(chalk9.bold("Blocked:"));
5562
- for (const t of queueData.blocked) {
5563
- console.log(
5564
- ` ${chalk9.bold(t.ticketId.slice(-6))} ${t.title.slice(0, 45)} ${chalk9.red("blocked by " + t.blockedBy.map((id) => id.slice(-6)).join(", "))}`
5565
- );
5566
- }
5567
- 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);
5568
5102
  }
5569
- if (unenriched && unenriched.length > 0) {
5570
- console.log(chalk9.bold("Needs enrichment:"));
5571
- for (const t of unenriched) {
5572
- const ref = t.ticketId;
5573
- console.log(
5574
- ` ${chalk9.bold(ref.slice(-6))} ${t.title.slice(0, 45).padEnd(45)} ${chalk9.dim(t.priority)}`
5575
- );
5576
- }
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}`));
5577
5113
  console.log();
5114
+ return;
5578
5115
  }
5579
- if (pending && pending.length > 0) {
5580
- console.log(chalk9.bold("Pending extraction:"));
5581
- for (const t of pending) {
5582
- 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
+ ) : "";
5583
5144
  console.log(
5584
- ` "${t.meetingTitle ?? "Untitled"}" ${chalk9.dim(`(uploaded ${ago})`)}`
5145
+ ` ${chalk9.bold(idTail)} ${title} ${verb} ${exec2}`.trimEnd()
5585
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
+ }
5586
5155
  }
5587
- console.log();
5588
- }
5589
- if ((!queueData?.ready || queueData.ready.length === 0) && (!unenriched || unenriched.length === 0) && (!pending || pending.length === 0)) {
5590
- console.log(chalk9.dim("Queue is empty. Upload a transcript to get started."));
5591
- console.log();
5592
5156
  }
5157
+ console.log();
5593
5158
  });
5594
- function colorPriority(p) {
5595
- switch (p) {
5596
- case "urgent":
5597
- return chalk9.red(p);
5598
- case "high":
5599
- return chalk9.yellow(p);
5600
- case "medium":
5601
- return chalk9.white(p);
5602
- case "low":
5603
- 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";
5604
5177
  default:
5605
- return p;
5178
+ return status;
5606
5179
  }
5607
5180
  }
5608
- function formatAgo(ms) {
5609
- const s = Math.floor(ms / 1e3);
5610
- if (s < 60) return `${s}s ago`;
5611
- const m = Math.floor(s / 60);
5612
- if (m < 60) return `${m}m ago`;
5613
- const h = Math.floor(m / 60);
5614
- if (h < 24) return `${h}h ago`;
5615
- 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";
5616
5184
  }
5617
5185
 
5618
5186
  // src/commands/recap.ts
5619
5187
  import { Command as Command10 } from "commander";
5620
- import { resolve as resolve7 } from "path";
5188
+ import { resolve as resolve8 } from "path";
5621
5189
  import chalk10 from "chalk";
5622
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) => {
5623
5191
  const creds = requireAuth();
5624
- const cwd = resolve7(process.cwd());
5192
+ const cwd = resolve8(process.cwd());
5625
5193
  const mapping = getProjectMapping(cwd);
5626
5194
  if (!mapping) {
5627
5195
  console.error(
@@ -5675,44 +5243,13 @@ var recapCommand = new Command10("recap").description("Show a summary of recent
5675
5243
  console.log();
5676
5244
  });
5677
5245
 
5678
- // src/commands/yap.ts
5246
+ // src/commands/agent.ts
5679
5247
  import { Command as Command11 } from "commander";
5680
5248
  import chalk11 from "chalk";
5681
- var yapCommand = new Command11("yap").description(
5682
- "Start a yap session \u2014 brainstorm with an AI that knows your codebase"
5683
- ).action(() => {
5684
- console.log(chalk11.bold("yapout yap sessions"));
5685
- console.log();
5686
- console.log(
5687
- "Yap sessions run inside Claude \u2014 Desktop, CLI, or web \u2014 anywhere the"
5688
- );
5689
- console.log("yapout MCP server is connected.");
5690
- console.log();
5691
- console.log("Just tell Claude:");
5692
- console.log(
5693
- chalk11.green(` "Let's have a yap session about [topic]"`)
5694
- );
5695
- console.log(
5696
- chalk11.green(
5697
- ' "I want to brainstorm [idea] \u2014 be a skeptical QA engineer"'
5698
- )
5699
- );
5700
- console.log();
5701
- console.log(chalk11.dim("Personas: tech lead, qa engineer, product owner, end user, or custom"));
5702
- console.log(
5703
- chalk11.dim(
5704
- "Claude will call yapout_start_yap to get instructions and yapout_submit_yap_session when done."
5705
- )
5706
- );
5707
- });
5708
-
5709
- // src/commands/agent.ts
5710
- import { Command as Command12 } from "commander";
5711
- import chalk12 from "chalk";
5712
5249
  import { input, confirm } from "@inquirer/prompts";
5713
- import { resolve as resolve8 } from "path";
5250
+ import { resolve as resolve9 } from "path";
5714
5251
  async function resolveProjectId() {
5715
- const cwd = resolve8(process.cwd());
5252
+ const cwd = resolve9(process.cwd());
5716
5253
  const mapping = getProjectMapping(cwd);
5717
5254
  if (!mapping) {
5718
5255
  throw new Error(
@@ -5724,7 +5261,7 @@ async function resolveProjectId() {
5724
5261
  projectName: mapping.projectName
5725
5262
  };
5726
5263
  }
5727
- 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(
5728
5265
  "--scopes <scopes>",
5729
5266
  "Comma-separated list of MCP tool names, or '*' for full access",
5730
5267
  "*"
@@ -5745,23 +5282,23 @@ var agentCreate = new Command12("create").description("Create a new agent identi
5745
5282
  label: opts.label
5746
5283
  }
5747
5284
  );
5748
- console.log(chalk12.green(`Agent "${name}" created in ${projectName}.`));
5285
+ console.log(chalk11.green(`Agent "${name}" created in ${projectName}.`));
5749
5286
  console.log("");
5750
- console.log(chalk12.dim("agentUserId:"), result.agentUserId);
5751
- console.log(chalk12.dim("tokenId: "), result.tokenId);
5752
- 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));
5753
5290
  console.log("");
5754
5291
  console.log(
5755
- chalk12.bold("Store this token now."),
5292
+ chalk11.bold("Store this token now."),
5756
5293
  "Yapout keeps only the SHA-256 hash; this is the only time it's shown."
5757
5294
  );
5758
5295
  console.log(
5759
5296
  "Pass to the harness via the",
5760
- chalk12.cyan("YAPOUT_AGENT_TOKEN"),
5297
+ chalk11.cyan("YAPOUT_AGENT_TOKEN"),
5761
5298
  "env var."
5762
5299
  );
5763
5300
  });
5764
- 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 () => {
5765
5302
  const creds = requireAuth();
5766
5303
  const { projectId, projectName } = await resolveProjectId();
5767
5304
  const client = createConvexClient(creds.token);
@@ -5770,26 +5307,26 @@ var agentList = new Command12("list").description("List agents in the current pr
5770
5307
  { projectId }
5771
5308
  );
5772
5309
  if (rows.length === 0) {
5773
- console.log(chalk12.dim(`No agents in ${projectName}.`));
5310
+ console.log(chalk11.dim(`No agents in ${projectName}.`));
5774
5311
  return;
5775
5312
  }
5776
- console.log(chalk12.bold(`Agents in ${projectName}:`));
5313
+ console.log(chalk11.bold(`Agents in ${projectName}:`));
5777
5314
  for (const a of rows) {
5778
- const role = a.roleDescription ? chalk12.dim(` \u2014 ${a.roleDescription}`) : "";
5779
- console.log(` ${chalk12.cyan(a.displayName)}${role}`);
5780
- 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}`));
5781
5318
  console.log(
5782
- chalk12.dim(` created: ${new Date(a.createdAt).toLocaleString()}`)
5319
+ chalk11.dim(` created: ${new Date(a.createdAt).toLocaleString()}`)
5783
5320
  );
5784
5321
  }
5785
5322
  });
5786
- 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) => {
5787
5324
  const creds = requireAuth();
5788
5325
  const client = createConvexClient(creds.token);
5789
5326
  await client.mutation(anyApi.functions.agents.revokeToken, { tokenId });
5790
- console.log(chalk12.green("Token revoked."));
5327
+ console.log(chalk11.green("Token revoked."));
5791
5328
  });
5792
- 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(
5793
5330
  "--scopes <scopes>",
5794
5331
  "Comma-separated MCP tool names or '*'. Defaults to the old token's scopes."
5795
5332
  ).option("--label <label>", "New token label").action(async (oldTokenId, opts) => {
@@ -5800,7 +5337,7 @@ var agentRotate = new Command12("rotate").description("Issue a new token and rev
5800
5337
  { tokenId: oldTokenId }
5801
5338
  );
5802
5339
  if (!oldToken) {
5803
- 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."));
5804
5341
  process.exit(1);
5805
5342
  }
5806
5343
  const scopes = opts.scopes ? opts.scopes === "*" ? ["*"] : opts.scopes.split(",").map((s) => s.trim()).filter(Boolean) : oldToken.scopes;
@@ -5826,21 +5363,21 @@ var agentRotate = new Command12("rotate").description("Issue a new token and rev
5826
5363
  tokenId: oldTokenId
5827
5364
  });
5828
5365
  console.log(
5829
- chalk12.green(`Token rotated for "${oldToken.agentDisplayName ?? oldToken.agentUserId}".`)
5366
+ chalk11.green(`Token rotated for "${oldToken.agentDisplayName ?? oldToken.agentUserId}".`)
5830
5367
  );
5831
- console.log(chalk12.dim("new tokenId:"), created.tokenId);
5832
- 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));
5833
5370
  console.log("");
5834
- console.log(chalk12.bold("Store this token now."), "Old token revoked.");
5371
+ console.log(chalk11.bold("Store this token now."), "Old token revoked.");
5835
5372
  });
5836
- 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);
5837
5374
 
5838
5375
  // src/commands/webhook.ts
5839
- import { Command as Command13 } from "commander";
5840
- import chalk13 from "chalk";
5376
+ import { Command as Command12 } from "commander";
5377
+ import chalk12 from "chalk";
5841
5378
  import { input as input2, checkbox } from "@inquirer/prompts";
5842
5379
  import { randomBytes } from "crypto";
5843
- import { resolve as resolve9 } from "path";
5380
+ import { resolve as resolve10 } from "path";
5844
5381
  var SUPPORTED_EVENTS = [
5845
5382
  "finding.status_changed",
5846
5383
  "finding.created",
@@ -5852,7 +5389,7 @@ var SUPPORTED_EVENTS = [
5852
5389
  "settings.changed"
5853
5390
  ];
5854
5391
  async function resolveProjectId2() {
5855
- const cwd = resolve9(process.cwd());
5392
+ const cwd = resolve10(process.cwd());
5856
5393
  const mapping = getProjectMapping(cwd);
5857
5394
  if (!mapping) {
5858
5395
  throw new Error(
@@ -5861,7 +5398,7 @@ async function resolveProjectId2() {
5861
5398
  }
5862
5399
  return { projectId: mapping.projectId, projectName: mapping.projectName };
5863
5400
  }
5864
- 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 () => {
5865
5402
  const creds = requireAuth();
5866
5403
  const { projectId, projectName } = await resolveProjectId2();
5867
5404
  const client = createConvexClient(creds.token);
@@ -5870,19 +5407,19 @@ var webhookList = new Command13("list").description("List outbound webhooks for
5870
5407
  { projectId }
5871
5408
  );
5872
5409
  if (rows.length === 0) {
5873
- console.log(chalk13.dim(`No webhooks for ${projectName}.`));
5410
+ console.log(chalk12.dim(`No webhooks for ${projectName}.`));
5874
5411
  return;
5875
5412
  }
5876
- console.log(chalk13.bold(`Webhooks for ${projectName}:`));
5413
+ console.log(chalk12.bold(`Webhooks for ${projectName}:`));
5877
5414
  for (const w of rows) {
5878
- const status = w.active ? chalk13.green("active") : chalk13.red(`disabled (${w.consecutiveFailures} failures)`);
5879
- console.log(` ${chalk13.cyan(w.label ?? w.url)} \u2014 ${status}`);
5880
- console.log(chalk13.dim(` id: ${w._id}`));
5881
- console.log(chalk13.dim(` url: ${w.url}`));
5882
- 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(", ")}`));
5883
5420
  }
5884
5421
  });
5885
- 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(
5886
5423
  "--events <list>",
5887
5424
  `Comma-separated event names. Available: ${SUPPORTED_EVENTS.join(", ")}`
5888
5425
  ).option(
@@ -5909,23 +5446,23 @@ var webhookCreate = new Command13("create").description("Create an outbound webh
5909
5446
  anyApi.functions.webhooks.createWebhook,
5910
5447
  { projectId, url, label, events, secret }
5911
5448
  );
5912
- console.log(chalk13.green(`Webhook created in ${projectName}.`));
5913
- console.log(chalk13.dim("webhookId:"), result.webhookId);
5449
+ console.log(chalk12.green(`Webhook created in ${projectName}.`));
5450
+ console.log(chalk12.dim("webhookId:"), result.webhookId);
5914
5451
  if (!opts.secret) {
5915
- console.log(chalk13.dim("secret: "), chalk13.yellow(secret));
5452
+ console.log(chalk12.dim("secret: "), chalk12.yellow(secret));
5916
5453
  console.log(
5917
- chalk13.bold("Store this secret now."),
5454
+ chalk12.bold("Store this secret now."),
5918
5455
  "Yapout keeps only the encrypted form; the receiver needs the plaintext to verify HMAC signatures."
5919
5456
  );
5920
5457
  }
5921
5458
  });
5922
- 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) => {
5923
5460
  const creds = requireAuth();
5924
5461
  const client = createConvexClient(creds.token);
5925
5462
  await client.mutation(anyApi.functions.webhooks.deleteWebhook, { webhookId });
5926
- console.log(chalk13.green("Webhook deleted."));
5463
+ console.log(chalk12.green("Webhook deleted."));
5927
5464
  });
5928
- 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) => {
5929
5466
  const creds = requireAuth();
5930
5467
  const client = createConvexClient(creds.token);
5931
5468
  const limit = parseInt(opts.limit, 10);
@@ -5934,28 +5471,28 @@ var webhookTest = new Command13("test").description("Inspect recent delivery att
5934
5471
  { webhookId, limit }
5935
5472
  );
5936
5473
  if (rows.length === 0) {
5937
- console.log(chalk13.dim("No deliveries yet."));
5474
+ console.log(chalk12.dim("No deliveries yet."));
5938
5475
  return;
5939
5476
  }
5940
5477
  for (const d of rows) {
5941
- 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);
5942
5479
  const time = new Date(d.createdAt).toLocaleString();
5943
5480
  const http3 = d.httpStatus !== void 0 ? ` HTTP ${d.httpStatus}` : "";
5944
- const err = d.error ? chalk13.dim(` (${d.error})`) : "";
5481
+ const err = d.error ? chalk12.dim(` (${d.error})`) : "";
5945
5482
  console.log(
5946
- ` ${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}`
5947
5484
  );
5948
5485
  }
5949
5486
  });
5950
- 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);
5951
5488
 
5952
5489
  // src/commands/inbound.ts
5953
- import { Command as Command14 } from "commander";
5954
- import chalk14 from "chalk";
5490
+ import { Command as Command13 } from "commander";
5491
+ import chalk13 from "chalk";
5955
5492
  import { input as input3 } from "@inquirer/prompts";
5956
- import { resolve as resolve10 } from "path";
5493
+ import { resolve as resolve11 } from "path";
5957
5494
  async function resolveProjectId3() {
5958
- const cwd = resolve10(process.cwd());
5495
+ const cwd = resolve11(process.cwd());
5959
5496
  const mapping = getProjectMapping(cwd);
5960
5497
  if (!mapping) {
5961
5498
  throw new Error(
@@ -5964,7 +5501,7 @@ async function resolveProjectId3() {
5964
5501
  }
5965
5502
  return { projectId: mapping.projectId, projectName: mapping.projectName };
5966
5503
  }
5967
- 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 () => {
5968
5505
  const creds = requireAuth();
5969
5506
  const { projectId, projectName } = await resolveProjectId3();
5970
5507
  const client = createConvexClient(creds.token);
@@ -5973,20 +5510,20 @@ var inboundList = new Command14("list").description("List inbound webhook tokens
5973
5510
  { projectId }
5974
5511
  );
5975
5512
  if (rows.length === 0) {
5976
- console.log(chalk14.dim(`No inbound webhook tokens for ${projectName}.`));
5513
+ console.log(chalk13.dim(`No inbound webhook tokens for ${projectName}.`));
5977
5514
  return;
5978
5515
  }
5979
- console.log(chalk14.bold(`Inbound tokens for ${projectName}:`));
5516
+ console.log(chalk13.bold(`Inbound tokens for ${projectName}:`));
5980
5517
  for (const t of rows) {
5981
- const status = t.revokedAt ? chalk14.red("revoked") : t.lastUsedAt ? chalk14.green(`last used ${new Date(t.lastUsedAt).toLocaleString()}`) : chalk14.dim("unused");
5982
- console.log(` ${chalk14.cyan(t.label)} \u2014 ${status}`);
5983
- 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}`));
5984
5521
  console.log(
5985
- chalk14.dim(` created: ${new Date(t.createdAt).toLocaleString()}`)
5522
+ chalk13.dim(` created: ${new Date(t.createdAt).toLocaleString()}`)
5986
5523
  );
5987
5524
  }
5988
5525
  });
5989
- 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) => {
5990
5527
  const creds = requireAuth();
5991
5528
  const { projectId, projectName } = await resolveProjectId3();
5992
5529
  const client = createConvexClient(creds.token);
@@ -5999,15 +5536,15 @@ var inboundCreate = new Command14("create").description("Issue a new inbound web
5999
5536
  const cloudUrl = getConvexUrl();
6000
5537
  return cloudUrl.replace(/\.convex\.cloud(\/.*)?$/, ".convex.site");
6001
5538
  })();
6002
- console.log(chalk14.green(`Inbound token created in ${projectName}.`));
6003
- console.log(chalk14.dim("tokenId:"), result.tokenId);
6004
- 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));
6005
5542
  console.log("");
6006
- 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.");
6007
5544
  console.log("");
6008
5545
  console.log("Usage:");
6009
5546
  console.log(
6010
- ` ${chalk14.cyan("curl")} -X POST ${convexSiteUrl}/api/inbound/${projectId} \\`
5547
+ ` ${chalk13.cyan("curl")} -X POST ${convexSiteUrl}/api/inbound/${projectId} \\`
6011
5548
  );
6012
5549
  console.log(` -H "Authorization: Bearer ${result.rawToken}" \\`);
6013
5550
  console.log(` -H "Content-Type: application/json" \\`);
@@ -6015,22 +5552,22 @@ var inboundCreate = new Command14("create").description("Issue a new inbound web
6015
5552
  ` -d '{ "content": "<raw text>", "sourceLabel": "${label}" }'`
6016
5553
  );
6017
5554
  });
6018
- 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) => {
6019
5556
  const creds = requireAuth();
6020
5557
  const client = createConvexClient(creds.token);
6021
5558
  await client.mutation(anyApi.functions.inboundTokens.revokeInboundToken, {
6022
5559
  tokenId
6023
5560
  });
6024
- console.log(chalk14.green("Inbound token revoked."));
5561
+ console.log(chalk13.green("Inbound token revoked."));
6025
5562
  });
6026
- 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);
6027
5564
 
6028
5565
  // src/commands/pillar.ts
6029
- import { Command as Command15 } from "commander";
6030
- import chalk15 from "chalk";
5566
+ import { Command as Command14 } from "commander";
5567
+ import chalk14 from "chalk";
6031
5568
  import { input as input4, select as select2 } from "@inquirer/prompts";
6032
5569
  import { randomBytes as randomBytes2 } from "crypto";
6033
- import { resolve as resolve11 } from "path";
5570
+ import { resolve as resolve12 } from "path";
6034
5571
  var PILLARS = [
6035
5572
  "extraction",
6036
5573
  "enrichment",
@@ -6039,7 +5576,7 @@ var PILLARS = [
6039
5576
  ];
6040
5577
  var MODES = ["cloud-default", "disabled", "external-webhook"];
6041
5578
  async function resolveProjectId4() {
6042
- const cwd = resolve11(process.cwd());
5579
+ const cwd = resolve12(process.cwd());
6043
5580
  const mapping = getProjectMapping(cwd);
6044
5581
  if (!mapping) {
6045
5582
  throw new Error(
@@ -6051,14 +5588,14 @@ async function resolveProjectId4() {
6051
5588
  function modeBadge(mode) {
6052
5589
  switch (mode) {
6053
5590
  case "cloud-default":
6054
- return chalk15.dim("cloud");
5591
+ return chalk14.dim("cloud");
6055
5592
  case "disabled":
6056
- return chalk15.gray("disabled");
5593
+ return chalk14.gray("disabled");
6057
5594
  case "external-webhook":
6058
- return chalk15.green("webhook");
5595
+ return chalk14.green("webhook");
6059
5596
  }
6060
5597
  }
6061
- 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 () => {
6062
5599
  const creds = requireAuth();
6063
5600
  const { projectId, projectName } = await resolveProjectId4();
6064
5601
  const client = createConvexClient(creds.token);
@@ -6066,17 +5603,17 @@ var pillarList = new Command15("list").description("Show current modes for all f
6066
5603
  anyApi.functions.projectConfig.getProjectConfig,
6067
5604
  { projectId }
6068
5605
  );
6069
- console.log(chalk15.bold(`Pillars for ${projectName}:`));
5606
+ console.log(chalk14.bold(`Pillars for ${projectName}:`));
6070
5607
  for (const p of PILLARS) {
6071
5608
  const setting = config?.pillars?.[p] ?? { mode: "cloud-default" };
6072
- 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}`) : "";
6073
5610
  console.log(` ${p.padEnd(15)} ${modeBadge(setting.mode)}${url}`);
6074
5611
  }
6075
5612
  });
6076
- 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(
6077
5614
  async (pillar, mode, opts) => {
6078
5615
  if (!PILLARS.includes(pillar)) {
6079
- console.error(chalk15.red(`Unknown pillar "${pillar}".`));
5616
+ console.error(chalk14.red(`Unknown pillar "${pillar}".`));
6080
5617
  console.error(` Choices: ${PILLARS.join(", ")}`);
6081
5618
  process.exit(1);
6082
5619
  }
@@ -6091,7 +5628,7 @@ var pillarSet = new Command15("set").description("Set a pillar's mode (interacti
6091
5628
  });
6092
5629
  }
6093
5630
  if (!MODES.includes(chosenMode)) {
6094
- console.error(chalk15.red(`Invalid mode "${chosenMode}".`));
5631
+ console.error(chalk14.red(`Invalid mode "${chosenMode}".`));
6095
5632
  console.error(` Choices: ${MODES.join(", ")}`);
6096
5633
  process.exit(1);
6097
5634
  }
@@ -6099,7 +5636,7 @@ var pillarSet = new Command15("set").description("Set a pillar's mode (interacti
6099
5636
  if (chosenMode === "external-webhook") {
6100
5637
  const url = opts.url ?? await input4({ message: "Webhook URL:" });
6101
5638
  if (!url.trim()) {
6102
- console.error(chalk15.red("Webhook URL required."));
5639
+ console.error(chalk14.red("Webhook URL required."));
6103
5640
  process.exit(1);
6104
5641
  }
6105
5642
  const secret = randomBytes2(32).toString("hex");
@@ -6115,13 +5652,13 @@ var pillarSet = new Command15("set").description("Set a pillar's mode (interacti
6115
5652
  }
6116
5653
  );
6117
5654
  console.log(
6118
- chalk15.green(`Set ${pillar} \u2192 external-webhook in ${projectName}.`)
5655
+ chalk14.green(`Set ${pillar} \u2192 external-webhook in ${projectName}.`)
6119
5656
  );
6120
- console.log(chalk15.dim("URL: "), setting.webhookUrl);
6121
- 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));
6122
5659
  console.log("");
6123
5660
  console.log(
6124
- chalk15.bold("Store this secret now."),
5661
+ chalk14.bold("Store this secret now."),
6125
5662
  "Yapout encrypts it at rest; this is the only time it's shown. The receiver needs the plaintext to verify HMAC signatures."
6126
5663
  );
6127
5664
  return;
@@ -6136,13 +5673,13 @@ var pillarSet = new Command15("set").description("Set a pillar's mode (interacti
6136
5673
  }
6137
5674
  );
6138
5675
  console.log(
6139
- chalk15.green(`Set ${pillar} \u2192 ${chosenMode} in ${projectName}.`)
5676
+ chalk14.green(`Set ${pillar} \u2192 ${chosenMode} in ${projectName}.`)
6140
5677
  );
6141
5678
  }
6142
5679
  );
6143
- 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) => {
6144
5681
  if (!PILLARS.includes(pillar)) {
6145
- console.error(chalk15.red(`Unknown pillar "${pillar}".`));
5682
+ console.error(chalk14.red(`Unknown pillar "${pillar}".`));
6146
5683
  process.exit(1);
6147
5684
  }
6148
5685
  const creds = requireAuth();
@@ -6155,7 +5692,7 @@ var pillarTest = new Command15("test").description("Send a test delivery to a pi
6155
5692
  const setting = config?.pillars?.[pillar];
6156
5693
  if (!setting || setting.mode !== "external-webhook") {
6157
5694
  console.error(
6158
- chalk15.red(
5695
+ chalk14.red(
6159
5696
  `${pillar} is in "${setting?.mode ?? "cloud-default"}" mode \u2014 set it to external-webhook first via \`yapout pillar set ${pillar} external-webhook\`.`
6160
5697
  )
6161
5698
  );
@@ -6163,11 +5700,11 @@ var pillarTest = new Command15("test").description("Send a test delivery to a pi
6163
5700
  }
6164
5701
  if (!setting.webhookUrl || !setting.webhookSecret) {
6165
5702
  console.error(
6166
- chalk15.red(`${pillar} is missing webhookUrl or webhookSecret.`)
5703
+ chalk14.red(`${pillar} is missing webhookUrl or webhookSecret.`)
6167
5704
  );
6168
5705
  process.exit(1);
6169
5706
  }
6170
- console.log(chalk15.dim(`POSTing test payload to ${setting.webhookUrl}...`));
5707
+ console.log(chalk14.dim(`POSTing test payload to ${setting.webhookUrl}...`));
6171
5708
  const result = await client.action(
6172
5709
  anyApi.functions.pillarHooks.testDelivery,
6173
5710
  {
@@ -6177,23 +5714,239 @@ var pillarTest = new Command15("test").description("Send a test delivery to a pi
6177
5714
  }
6178
5715
  );
6179
5716
  if (result.ok) {
6180
- 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`));
6181
5718
  } else {
6182
- console.log(chalk15.red(`\u26A0 HTTP ${result.httpStatus ?? "n/a"}`));
6183
- 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}`));
6184
5721
  }
6185
5722
  if (result.body) {
6186
- console.log(chalk15.dim(" body:"));
5723
+ console.log(chalk14.dim(" body:"));
6187
5724
  console.log(
6188
5725
  result.body.split("\n").map((l) => ` ${l}`).join("\n")
6189
5726
  );
6190
5727
  }
6191
5728
  });
6192
- 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);
5730
+
5731
+ // src/commands/observe.ts
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(
5736
+ "--severity <level>",
5737
+ "Severity hint: low | normal | high (default: normal)"
5738
+ ).option(
5739
+ "--area <hint>",
5740
+ "Optional scoping hint for the extraction chain (e.g., 'auth', 'billing')"
5741
+ ).action(
5742
+ async (words, options) => {
5743
+ const creds = requireAuth();
5744
+ const cwd = resolve13(process.cwd());
5745
+ const resourceCfg = readResourceConfig(cwd);
5746
+ if (!resourceCfg) {
5747
+ console.error(
5748
+ chalk15.red("No `.yapout/config.json` found.") + " Run " + chalk15.cyan("yapout init") + " in this repo first."
5749
+ );
5750
+ process.exit(1);
5751
+ }
5752
+ const active = readActiveExecution(cwd);
5753
+ if (!active) {
5754
+ console.error(
5755
+ chalk15.red("No active Execution.") + " `yapout observe` must run from a worktree where " + chalk15.cyan("yapout pick") + " has been called."
5756
+ );
5757
+ process.exit(1);
5758
+ }
5759
+ const body = words.join(" ").trim();
5760
+ if (!body) {
5761
+ console.error(chalk15.red("Observation body cannot be empty."));
5762
+ process.exit(1);
5763
+ }
5764
+ const severityHint = parseSeverity(options.severity);
5765
+ if (options.severity && !severityHint) {
5766
+ console.error(
5767
+ chalk15.red(
5768
+ `Invalid --severity: ${options.severity}. Allowed: low, normal, high.`
5769
+ )
5770
+ );
5771
+ process.exit(1);
5772
+ }
5773
+ const client = createConvexClient(creds.token);
5774
+ let result;
5775
+ try {
5776
+ result = await client.mutation(
5777
+ anyApi.functions.resourcesCli.createAgentObservationRaw,
5778
+ {
5779
+ executionId: active.executionId,
5780
+ body,
5781
+ ...severityHint ? { severityHint } : {},
5782
+ ...options.area ? { area: options.area } : {}
5783
+ }
5784
+ );
5785
+ } catch (err) {
5786
+ console.error(
5787
+ chalk15.red("Failed to file observation."),
5788
+ err.message
5789
+ );
5790
+ process.exit(1);
5791
+ }
5792
+ console.log(
5793
+ chalk15.green("Filed agent observation. ") + chalk15.dim(
5794
+ `(raw: ${result.rawId}, execution: ${result.executionId})`
5795
+ )
5796
+ );
5797
+ console.log(
5798
+ chalk15.dim(
5799
+ "Extraction will turn this into a Request (or link it to an existing one)."
5800
+ )
5801
+ );
5802
+ }
5803
+ );
5804
+ function parseSeverity(raw) {
5805
+ if (!raw) return void 0;
5806
+ const v = raw.toLowerCase();
5807
+ if (v === "low" || v === "normal" || v === "high") return v;
5808
+ return void 0;
5809
+ }
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
+ });
6193
5946
 
6194
5947
  // src/index.ts
6195
- var program = new Command16();
6196
- program.name("yapout").description("yapout \u2014 the PM tool for agents").version("0.15.2");
5948
+ var program = new Command18();
5949
+ program.name("yapout").description("yapout \u2014 the PM tool for agents").version("0.18.0");
6197
5950
  program.addCommand(loginCommand);
6198
5951
  program.addCommand(logoutCommand);
6199
5952
  program.addCommand(initCommand);
@@ -6203,10 +5956,12 @@ program.addCommand(statusCommand);
6203
5956
  program.addCommand(mcpServerCommand);
6204
5957
  program.addCommand(queueCommand);
6205
5958
  program.addCommand(recapCommand);
6206
- program.addCommand(yapCommand);
6207
5959
  program.addCommand(serveCommand);
6208
5960
  program.addCommand(agentCommand);
6209
5961
  program.addCommand(webhookCommand);
6210
5962
  program.addCommand(inboundCommand);
6211
5963
  program.addCommand(pillarCommand);
5964
+ program.addCommand(observeCommand);
5965
+ program.addCommand(pickCommand);
5966
+ program.addCommand(submitCommand);
6212
5967
  program.parse();