yapout 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +352 -618
- 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
|
|
4
|
+
import { Command as Command17 } 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,43 @@ 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
|
+
}
|
|
177
239
|
var WATCH_DEFAULTS = {
|
|
178
240
|
auto_enrich: true,
|
|
179
241
|
auto_implement: true,
|
|
@@ -246,7 +308,7 @@ function getConvexUrl() {
|
|
|
246
308
|
);
|
|
247
309
|
}
|
|
248
310
|
function getAppUrl() {
|
|
249
|
-
return process.env.YAPOUT_APP_URL || "https://yapout.
|
|
311
|
+
return process.env.YAPOUT_APP_URL || "https://yapout.com";
|
|
250
312
|
}
|
|
251
313
|
|
|
252
314
|
// src/lib/convex.ts
|
|
@@ -296,7 +358,7 @@ function requireAuth() {
|
|
|
296
358
|
}
|
|
297
359
|
|
|
298
360
|
// src/commands/serve.ts
|
|
299
|
-
var CLI_VERSION = "0.
|
|
361
|
+
var CLI_VERSION = "0.16.0";
|
|
300
362
|
var DEFAULT_PORT = 7777;
|
|
301
363
|
var PORT_RANGE = 10;
|
|
302
364
|
var HEARTBEAT_MS = 12e4;
|
|
@@ -306,7 +368,6 @@ var LOG_FILE = join2(getYapoutDir(), "serve.log");
|
|
|
306
368
|
var ALLOWED_ORIGINS = [
|
|
307
369
|
"https://yapout.com",
|
|
308
370
|
"https://www.yapout.com",
|
|
309
|
-
"https://yapout.vercel.app",
|
|
310
371
|
// Local dev / sandbox ports — wide range so any worktree's auto-allocated
|
|
311
372
|
// Next.js port works without configuration.
|
|
312
373
|
...Array.from({ length: 20 }, (_, i) => `http://localhost:${3e3 + i}`),
|
|
@@ -346,7 +407,7 @@ function startServer(payload) {
|
|
|
346
407
|
res.writeHead(404, headers);
|
|
347
408
|
res.end();
|
|
348
409
|
});
|
|
349
|
-
return new Promise((
|
|
410
|
+
return new Promise((resolve13, reject) => {
|
|
350
411
|
let attempt = 0;
|
|
351
412
|
const tryListen = () => {
|
|
352
413
|
const port = DEFAULT_PORT + attempt;
|
|
@@ -362,7 +423,7 @@ function startServer(payload) {
|
|
|
362
423
|
server.once("error", onError);
|
|
363
424
|
server.listen(port, "127.0.0.1", () => {
|
|
364
425
|
server.removeListener("error", onError);
|
|
365
|
-
|
|
426
|
+
resolve13({
|
|
366
427
|
port,
|
|
367
428
|
close: () => server.close()
|
|
368
429
|
});
|
|
@@ -519,7 +580,7 @@ var serveCommand = new Command("serve").description(
|
|
|
519
580
|
});
|
|
520
581
|
|
|
521
582
|
// src/commands/login.ts
|
|
522
|
-
var CLI_VERSION2 = "0.
|
|
583
|
+
var CLI_VERSION2 = "0.16.0";
|
|
523
584
|
function safeReturnTo(raw) {
|
|
524
585
|
if (!raw) return null;
|
|
525
586
|
try {
|
|
@@ -531,7 +592,7 @@ function safeReturnTo(raw) {
|
|
|
531
592
|
}
|
|
532
593
|
}
|
|
533
594
|
function startCallbackServer() {
|
|
534
|
-
return new Promise((
|
|
595
|
+
return new Promise((resolve13) => {
|
|
535
596
|
let resolveData;
|
|
536
597
|
let rejectData;
|
|
537
598
|
const dataPromise = new Promise((res, rej) => {
|
|
@@ -580,7 +641,7 @@ function startCallbackServer() {
|
|
|
580
641
|
server.listen(0, () => {
|
|
581
642
|
const address = server.address();
|
|
582
643
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
583
|
-
|
|
644
|
+
resolve13({ port, data: dataPromise });
|
|
584
645
|
});
|
|
585
646
|
setTimeout(() => {
|
|
586
647
|
server.close();
|
|
@@ -682,15 +743,6 @@ async function pickProject(projects) {
|
|
|
682
743
|
})
|
|
683
744
|
});
|
|
684
745
|
}
|
|
685
|
-
async function pickOrg(orgs, message = "Which org does this project belong to?") {
|
|
686
|
-
return await select({
|
|
687
|
-
message,
|
|
688
|
-
choices: orgs.map((o) => ({
|
|
689
|
-
name: `${o.name} (${o.slug}, ${o.role})`,
|
|
690
|
-
value: o
|
|
691
|
-
}))
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
746
|
|
|
695
747
|
// src/commands/link.ts
|
|
696
748
|
var CONFIG_YAML_CONTENT = `# yapout local configuration
|
|
@@ -717,7 +769,50 @@ branch_prefix: feat
|
|
|
717
769
|
# {{ticket.linearTicketId}}, {{ticket.id}}
|
|
718
770
|
# commit_template: "{{ticket.type}}({{ticket.linearTicketId}}): {{ticket.title}}"
|
|
719
771
|
`;
|
|
720
|
-
var linkCommand = new Command4("link").description(
|
|
772
|
+
var linkCommand = new Command4("link").description(
|
|
773
|
+
"Link the current Resource into a Group (--group), or link the current directory to a project (legacy)."
|
|
774
|
+
).option(
|
|
775
|
+
"--group <groupId>",
|
|
776
|
+
"Attach the active Resource into a Group (requires `yapout init` to have run in this repo)"
|
|
777
|
+
).action(async (options) => {
|
|
778
|
+
if (options.group) {
|
|
779
|
+
await linkResourceToGroup(options.group);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
await legacyLinkProject();
|
|
783
|
+
});
|
|
784
|
+
async function linkResourceToGroup(groupId) {
|
|
785
|
+
const creds = requireAuth();
|
|
786
|
+
const cwd = resolve2(process.cwd());
|
|
787
|
+
const resourceCfg = readResourceConfig(cwd);
|
|
788
|
+
if (!resourceCfg) {
|
|
789
|
+
console.error(
|
|
790
|
+
chalk5.red("No `.yapout/config.json` found.") + " Run " + chalk5.cyan("yapout init") + " here first."
|
|
791
|
+
);
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
const client = createConvexClient(creds.token);
|
|
795
|
+
try {
|
|
796
|
+
await client.mutation(anyApi.functions.resourcesCli.linkResourceToGroup, {
|
|
797
|
+
resourceId: resourceCfg.resourceId,
|
|
798
|
+
groupId
|
|
799
|
+
});
|
|
800
|
+
} catch (err) {
|
|
801
|
+
console.error(
|
|
802
|
+
chalk5.red("Failed to link Resource to Group."),
|
|
803
|
+
err.message
|
|
804
|
+
);
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
writeResourceConfig(resolve2(process.cwd()), {
|
|
808
|
+
...resourceCfg,
|
|
809
|
+
groupId
|
|
810
|
+
});
|
|
811
|
+
console.log(
|
|
812
|
+
chalk5.green("Linked Resource ") + chalk5.cyan(resourceCfg.canonicalId) + chalk5.green(" to Group ") + chalk5.cyan(groupId) + "."
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
async function legacyLinkProject() {
|
|
721
816
|
const creds = requireAuth();
|
|
722
817
|
const cwd = resolveRepoRoot(resolve2(process.cwd()));
|
|
723
818
|
const client = createConvexClient(creds.token);
|
|
@@ -777,10 +872,7 @@ var linkCommand = new Command4("link").description("Link the current directory t
|
|
|
777
872
|
if (existsSync3(gitignorePath)) {
|
|
778
873
|
const content = readFileSync3(gitignorePath, "utf-8");
|
|
779
874
|
if (!content.includes(".yapout/")) {
|
|
780
|
-
appendFileSync(
|
|
781
|
-
gitignorePath,
|
|
782
|
-
"\n# yapout local config\n.yapout/\n"
|
|
783
|
-
);
|
|
875
|
+
appendFileSync(gitignorePath, "\n# yapout local config\n.yapout/\n");
|
|
784
876
|
}
|
|
785
877
|
} else {
|
|
786
878
|
writeFileSync3(gitignorePath, "# yapout local config\n.yapout/\n");
|
|
@@ -804,7 +896,7 @@ var linkCommand = new Command4("link").description("Link the current directory t
|
|
|
804
896
|
console.log(
|
|
805
897
|
chalk5.green(`Linked to ${label}${orgSuffix}.`) + " Claude Code will discover yapout tools automatically."
|
|
806
898
|
);
|
|
807
|
-
}
|
|
899
|
+
}
|
|
808
900
|
function getCliVersion() {
|
|
809
901
|
try {
|
|
810
902
|
const pkg = JSON.parse(
|
|
@@ -917,7 +1009,6 @@ import {
|
|
|
917
1009
|
readFileSync as readFileSync5,
|
|
918
1010
|
appendFileSync as appendFileSync2
|
|
919
1011
|
} from "fs";
|
|
920
|
-
import { hostname as hostname4 } from "os";
|
|
921
1012
|
import chalk8 from "chalk";
|
|
922
1013
|
var CONFIG_YAML_CONTENT2 = `# yapout local configuration
|
|
923
1014
|
# See: https://docs.yapout.dev/cli/config
|
|
@@ -928,7 +1019,6 @@ var CONFIG_YAML_CONTENT2 = `# yapout local configuration
|
|
|
928
1019
|
# - npm run lint
|
|
929
1020
|
# - npm run typecheck
|
|
930
1021
|
# - npm run test
|
|
931
|
-
# - sf project deploy --dry-run --target-org scratch
|
|
932
1022
|
post_flight: []
|
|
933
1023
|
|
|
934
1024
|
# Require post_flight checks to pass before yapout_ship succeeds.
|
|
@@ -937,170 +1027,89 @@ ship_requires_checks: false
|
|
|
937
1027
|
|
|
938
1028
|
# Git branch prefix (used by yapout_claim)
|
|
939
1029
|
branch_prefix: feat
|
|
940
|
-
|
|
941
|
-
# Commit message template (optional \u2014 uses sensible default if omitted)
|
|
942
|
-
# Available variables: {{ticket.title}}, {{ticket.type}}, {{ticket.priority}},
|
|
943
|
-
# {{ticket.linearTicketId}}, {{ticket.id}}
|
|
944
|
-
# commit_template: "{{ticket.type}}({{ticket.linearTicketId}}): {{ticket.title}}"
|
|
945
1030
|
`;
|
|
946
|
-
var initCommand = new Command7("init").description(
|
|
1031
|
+
var initCommand = new Command7("init").description(
|
|
1032
|
+
"Register the current repo as a Resource and start the MCP server scoped to it"
|
|
1033
|
+
).option(
|
|
1034
|
+
"--group <groupId>",
|
|
1035
|
+
"Optional Group id to attach the Resource to (defaults to the org's migration-bridge group)"
|
|
1036
|
+
).action(async (options) => {
|
|
947
1037
|
const creds = requireAuth();
|
|
948
|
-
const cwd =
|
|
949
|
-
|
|
950
|
-
let defaultBranch;
|
|
951
|
-
try {
|
|
952
|
-
repoFullName = getRepoFullName(cwd);
|
|
953
|
-
defaultBranch = getDefaultBranch(cwd);
|
|
954
|
-
} catch (err) {
|
|
1038
|
+
const cwd = resolve5(process.cwd());
|
|
1039
|
+
if (!isInGitRepo(cwd)) {
|
|
955
1040
|
console.error(
|
|
956
|
-
chalk8.red("
|
|
957
|
-
err.message
|
|
1041
|
+
chalk8.red("`yapout init` must be run from inside a git repo.") + "\n No .git/ directory found from " + chalk8.dim(cwd) + "."
|
|
958
1042
|
);
|
|
959
1043
|
process.exit(1);
|
|
960
1044
|
}
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
let orgs;
|
|
1045
|
+
const repoRoot = resolveRepoRoot(cwd);
|
|
1046
|
+
let rawRemote;
|
|
964
1047
|
try {
|
|
965
|
-
|
|
966
|
-
anyApi.functions.orgMembers.getMyOrgs,
|
|
967
|
-
{}
|
|
968
|
-
);
|
|
1048
|
+
rawRemote = getOriginRemoteUrl(repoRoot);
|
|
969
1049
|
} catch (err) {
|
|
970
1050
|
console.error(
|
|
971
|
-
chalk8.red("Failed to
|
|
972
|
-
err.message
|
|
1051
|
+
chalk8.red("Failed to read origin remote URL.") + "\n " + err.message + "\n Set a GitHub remote (`git remote add origin ...`) before running `yapout init`."
|
|
973
1052
|
);
|
|
974
1053
|
process.exit(1);
|
|
975
1054
|
}
|
|
976
|
-
|
|
1055
|
+
const canonicalId = normalizeGitRemote(rawRemote);
|
|
1056
|
+
if (!canonicalId) {
|
|
977
1057
|
console.error(
|
|
978
1058
|
chalk8.red(
|
|
979
|
-
|
|
980
|
-
)
|
|
1059
|
+
`Could not derive a canonical id from the origin remote URL: ${rawRemote}`
|
|
1060
|
+
) + "\n yapout currently supports github.com remotes only."
|
|
981
1061
|
);
|
|
982
1062
|
process.exit(1);
|
|
983
1063
|
}
|
|
984
|
-
let
|
|
985
|
-
let chosenOrgName;
|
|
986
|
-
if (options?.org) {
|
|
987
|
-
const match = orgs.find((o) => o.org.slug === options.org);
|
|
988
|
-
if (!match) {
|
|
989
|
-
console.error(
|
|
990
|
-
chalk8.red(`Org "${options.org}" not found among your memberships.`)
|
|
991
|
-
);
|
|
992
|
-
console.error(
|
|
993
|
-
chalk8.dim(
|
|
994
|
-
"Available: " + orgs.map((o) => o.org.slug).join(", ")
|
|
995
|
-
)
|
|
996
|
-
);
|
|
997
|
-
process.exit(1);
|
|
998
|
-
}
|
|
999
|
-
chosenOrgId = match.org._id;
|
|
1000
|
-
chosenOrgName = match.org.name;
|
|
1001
|
-
} else if (orgs.length === 1) {
|
|
1002
|
-
chosenOrgId = orgs[0].org._id;
|
|
1003
|
-
chosenOrgName = orgs[0].org.name;
|
|
1004
|
-
console.log(
|
|
1005
|
-
chalk8.dim(`Creating in `) + chalk8.cyan(chosenOrgName)
|
|
1006
|
-
);
|
|
1007
|
-
} else {
|
|
1008
|
-
const picked = await pickOrg(
|
|
1009
|
-
orgs.map((o) => ({
|
|
1010
|
-
id: o.org._id,
|
|
1011
|
-
name: o.org.name,
|
|
1012
|
-
slug: o.org.slug,
|
|
1013
|
-
role: o.role
|
|
1014
|
-
}))
|
|
1015
|
-
);
|
|
1016
|
-
chosenOrgId = picked.id;
|
|
1017
|
-
chosenOrgName = picked.name;
|
|
1018
|
-
}
|
|
1019
|
-
const device = getOrCreateDeviceIdentity(hostname4());
|
|
1064
|
+
let defaultBranch;
|
|
1020
1065
|
try {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
cliVersion: getCliVersion2(),
|
|
1025
|
-
machineHostname: hostname4()
|
|
1026
|
-
});
|
|
1027
|
-
} catch (err) {
|
|
1028
|
-
console.warn(
|
|
1029
|
-
chalk8.yellow("Warning: device registration failed \u2014 "),
|
|
1030
|
-
err.message
|
|
1031
|
-
);
|
|
1066
|
+
defaultBranch = getDefaultBranch(repoRoot);
|
|
1067
|
+
} catch {
|
|
1068
|
+
defaultBranch = void 0;
|
|
1032
1069
|
}
|
|
1070
|
+
const client = createConvexClient(creds.token);
|
|
1033
1071
|
let result;
|
|
1034
1072
|
try {
|
|
1035
1073
|
result = await client.mutation(
|
|
1036
|
-
anyApi.functions.
|
|
1074
|
+
anyApi.functions.resourcesCli.findOrCreateRepoResource,
|
|
1037
1075
|
{
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1076
|
+
canonicalId,
|
|
1077
|
+
remoteUrl: canonicalId,
|
|
1078
|
+
defaultBranch,
|
|
1079
|
+
...options?.group ? { groupId: options.group } : {}
|
|
1042
1080
|
}
|
|
1043
1081
|
);
|
|
1044
1082
|
} catch (err) {
|
|
1045
1083
|
console.error(
|
|
1046
|
-
chalk8.red("Failed to
|
|
1084
|
+
chalk8.red("Failed to register Resource."),
|
|
1047
1085
|
err.message
|
|
1048
1086
|
);
|
|
1049
1087
|
process.exit(1);
|
|
1050
1088
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
chalk8.yellow("Warning: failed to record project checkout \u2014 "),
|
|
1060
|
-
err.message
|
|
1061
|
-
);
|
|
1062
|
-
}
|
|
1063
|
-
setProjectMapping(cwd, {
|
|
1064
|
-
projectId: result.projectId,
|
|
1065
|
-
projectName: result.projectName,
|
|
1089
|
+
const yapoutDir = join5(repoRoot, ".yapout");
|
|
1090
|
+
if (!existsSync5(yapoutDir)) mkdirSync3(yapoutDir, { recursive: true });
|
|
1091
|
+
writeResourceConfig(repoRoot, {
|
|
1092
|
+
resourceId: result.resourceId,
|
|
1093
|
+
canonicalId: result.canonicalId,
|
|
1094
|
+
groupId: result.groupId,
|
|
1095
|
+
remoteUrl: canonicalId,
|
|
1096
|
+
defaultBranch,
|
|
1066
1097
|
linkedAt: Date.now()
|
|
1067
1098
|
});
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
anyApi.functions.agents.createAgent,
|
|
1072
|
-
{
|
|
1073
|
-
projectId: result.projectId,
|
|
1074
|
-
displayName: `${creds.email}'s default agent (${result.projectName})`,
|
|
1075
|
-
roleDescription: "Default agent created at yapout init. Has full tool access.",
|
|
1076
|
-
scopes: ["*"],
|
|
1077
|
-
label: "yapout init default"
|
|
1078
|
-
}
|
|
1079
|
-
);
|
|
1080
|
-
agentToken = agentRes.rawToken;
|
|
1081
|
-
} catch (err) {
|
|
1082
|
-
console.warn(
|
|
1083
|
-
chalk8.yellow("Warning: failed to provision default agent \u2014 "),
|
|
1084
|
-
err.message
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
const yapoutDir = join5(cwd, ".yapout");
|
|
1088
|
-
if (!existsSync5(yapoutDir)) mkdirSync3(yapoutDir, { recursive: true });
|
|
1089
|
-
const configPath = join5(yapoutDir, "config.yml");
|
|
1090
|
-
if (!existsSync5(configPath)) {
|
|
1091
|
-
writeFileSync5(configPath, CONFIG_YAML_CONTENT2);
|
|
1099
|
+
const configYamlPath = join5(yapoutDir, "config.yml");
|
|
1100
|
+
if (!existsSync5(configYamlPath)) {
|
|
1101
|
+
writeFileSync5(configYamlPath, CONFIG_YAML_CONTENT2);
|
|
1092
1102
|
}
|
|
1093
|
-
const gitignorePath = join5(
|
|
1103
|
+
const gitignorePath = join5(repoRoot, ".gitignore");
|
|
1094
1104
|
if (existsSync5(gitignorePath)) {
|
|
1095
1105
|
const content = readFileSync5(gitignorePath, "utf-8");
|
|
1096
1106
|
if (!content.includes(".yapout/")) {
|
|
1097
|
-
appendFileSync2(
|
|
1098
|
-
gitignorePath,
|
|
1099
|
-
"\n# yapout local config\n.yapout/\n"
|
|
1100
|
-
);
|
|
1107
|
+
appendFileSync2(gitignorePath, "\n# yapout local config\n.yapout/\n");
|
|
1101
1108
|
}
|
|
1109
|
+
} else {
|
|
1110
|
+
writeFileSync5(gitignorePath, "# yapout local config\n.yapout/\n");
|
|
1102
1111
|
}
|
|
1103
|
-
const mcpPath = join5(
|
|
1112
|
+
const mcpPath = join5(repoRoot, ".mcp.json");
|
|
1104
1113
|
let mcpConfig = {};
|
|
1105
1114
|
if (existsSync5(mcpPath)) {
|
|
1106
1115
|
try {
|
|
@@ -1111,40 +1120,25 @@ var initCommand = new Command7("init").description("Create a yapout project from
|
|
|
1111
1120
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
1112
1121
|
const existingYapoutServer = mcpConfig.mcpServers.yapout ?? {};
|
|
1113
1122
|
const existingEnv = existingYapoutServer.env && typeof existingYapoutServer.env === "object" ? existingYapoutServer.env : {};
|
|
1114
|
-
const newEnv = { ...existingEnv };
|
|
1115
|
-
if (agentToken) {
|
|
1116
|
-
newEnv.YAPOUT_AGENT_TOKEN = agentToken;
|
|
1117
|
-
}
|
|
1118
1123
|
mcpConfig.mcpServers.yapout = {
|
|
1119
1124
|
command: "yapout",
|
|
1120
1125
|
args: ["mcp-server"],
|
|
1121
|
-
...Object.keys(
|
|
1126
|
+
...Object.keys(existingEnv).length > 0 ? { env: existingEnv } : {}
|
|
1122
1127
|
};
|
|
1123
1128
|
writeFileSync5(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
1129
|
+
const verb = result.created ? "Registered" : "Linked";
|
|
1124
1130
|
console.log(
|
|
1125
|
-
chalk8.green(`
|
|
1126
|
-
` in ${chosenOrgName} (${repoFullName}, branch: ${defaultBranch})`
|
|
1127
|
-
)
|
|
1131
|
+
chalk8.green(`${verb} Resource `) + chalk8.cyan(canonicalId) + chalk8.dim(` (id: ${result.resourceId}).`)
|
|
1128
1132
|
);
|
|
1129
|
-
if (
|
|
1130
|
-
console.log(
|
|
1131
|
-
chalk8.dim("Provisioned default agent \u2014 token stored in ") + chalk8.cyan(".mcp.json")
|
|
1132
|
-
);
|
|
1133
|
+
if (defaultBranch) {
|
|
1134
|
+
console.log(chalk8.dim(` default branch: ${defaultBranch}`));
|
|
1133
1135
|
}
|
|
1134
1136
|
console.log(
|
|
1135
|
-
chalk8.dim(
|
|
1137
|
+
chalk8.dim(
|
|
1138
|
+
"Claude Code will discover yapout's MCP tools via .mcp.json on next launch."
|
|
1139
|
+
)
|
|
1136
1140
|
);
|
|
1137
1141
|
});
|
|
1138
|
-
function getCliVersion2() {
|
|
1139
|
-
try {
|
|
1140
|
-
const pkg = JSON.parse(
|
|
1141
|
-
readFileSync5(join5(import.meta.dirname, "..", "package.json"), "utf-8")
|
|
1142
|
-
);
|
|
1143
|
-
return pkg.version ?? "unknown";
|
|
1144
|
-
} catch {
|
|
1145
|
-
return "unknown";
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
1142
|
|
|
1149
1143
|
// src/commands/mcp-server.ts
|
|
1150
1144
|
import { Command as Command8 } from "commander";
|
|
@@ -2582,7 +2576,7 @@ function truncate(text) {
|
|
|
2582
2576
|
` + lines.slice(-MAX_OUTPUT_LINES).join("\n");
|
|
2583
2577
|
}
|
|
2584
2578
|
function runCommand(command, cwd) {
|
|
2585
|
-
return new Promise((
|
|
2579
|
+
return new Promise((resolve13) => {
|
|
2586
2580
|
const start = Date.now();
|
|
2587
2581
|
const child = exec(command, {
|
|
2588
2582
|
cwd,
|
|
@@ -2590,7 +2584,7 @@ function runCommand(command, cwd) {
|
|
|
2590
2584
|
maxBuffer: 10 * 1024 * 1024
|
|
2591
2585
|
// 10MB
|
|
2592
2586
|
}, (error, stdout, stderr) => {
|
|
2593
|
-
|
|
2587
|
+
resolve13({
|
|
2594
2588
|
exitCode: error?.code ?? (error ? 1 : 0),
|
|
2595
2589
|
stdout: truncate(stdout),
|
|
2596
2590
|
stderr: truncate(stderr),
|
|
@@ -2598,7 +2592,7 @@ function runCommand(command, cwd) {
|
|
|
2598
2592
|
});
|
|
2599
2593
|
});
|
|
2600
2594
|
child.on("error", () => {
|
|
2601
|
-
|
|
2595
|
+
resolve13({
|
|
2602
2596
|
exitCode: 1,
|
|
2603
2597
|
stdout: "",
|
|
2604
2598
|
stderr: `Command timed out after ${COMMAND_TIMEOUT_MS / 1e3}s`,
|
|
@@ -2684,91 +2678,8 @@ function registerCheckTool(server, ctx) {
|
|
|
2684
2678
|
);
|
|
2685
2679
|
}
|
|
2686
2680
|
|
|
2687
|
-
// src/mcp/tools/bundle.ts
|
|
2688
|
-
import { z as z10 } from "zod";
|
|
2689
|
-
import { join as join10 } from "path";
|
|
2690
|
-
import { existsSync as existsSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
|
|
2691
|
-
function registerBundleTool(server, ctx) {
|
|
2692
|
-
server.tool(
|
|
2693
|
-
"yapout_bundle",
|
|
2694
|
-
"Add a finding to the current finding's bundle so they ship as one PR",
|
|
2695
|
-
{
|
|
2696
|
-
findingId: z10.string().describe("Finding ID to add to the bundle"),
|
|
2697
|
-
withFinding: z10.string().describe("Lead finding ID (currently being worked on)")
|
|
2698
|
-
},
|
|
2699
|
-
withScopeCheck(ctx, "yapout_bundle", async (args) => {
|
|
2700
|
-
const result = await ctx.client.mutation(
|
|
2701
|
-
anyApi5.functions.bundles.createBundle,
|
|
2702
|
-
{ leadFindingId: args.withFinding, joiningFindingId: args.findingId }
|
|
2703
|
-
);
|
|
2704
|
-
const bundledBrief = await ctx.client.query(
|
|
2705
|
-
anyApi5.functions.bundles.getBundledBrief,
|
|
2706
|
-
{ bundleId: result.bundleId }
|
|
2707
|
-
);
|
|
2708
|
-
if (!bundledBrief) {
|
|
2709
|
-
return {
|
|
2710
|
-
content: [
|
|
2711
|
-
{
|
|
2712
|
-
type: "text",
|
|
2713
|
-
text: JSON.stringify({
|
|
2714
|
-
bundleId: result.bundleId,
|
|
2715
|
-
message: "Bundled successfully but could not fetch combined brief."
|
|
2716
|
-
})
|
|
2717
|
-
}
|
|
2718
|
-
]
|
|
2719
|
-
};
|
|
2720
|
-
}
|
|
2721
|
-
const sections = [
|
|
2722
|
-
`# Bundle: ${bundledBrief.findings.length} findings`,
|
|
2723
|
-
""
|
|
2724
|
-
];
|
|
2725
|
-
for (const t of bundledBrief.findings) {
|
|
2726
|
-
sections.push(`## ${t.linearIssueId ?? t.findingId}: ${t.title}`);
|
|
2727
|
-
sections.push("");
|
|
2728
|
-
sections.push(`**Priority:** ${t.priority} | **Type:** ${t.type}`);
|
|
2729
|
-
if (t.linearIssueUrl) sections.push(`**Linear:** ${t.linearIssueUrl}`);
|
|
2730
|
-
sections.push("", t.description);
|
|
2731
|
-
if (t.enrichedDescription) {
|
|
2732
|
-
sections.push("", "### Enriched Description", "", t.enrichedDescription);
|
|
2733
|
-
}
|
|
2734
|
-
if (t.implementationBrief) {
|
|
2735
|
-
sections.push("", "### Implementation Brief", "", t.implementationBrief);
|
|
2736
|
-
}
|
|
2737
|
-
sections.push("", "---", "");
|
|
2738
|
-
}
|
|
2739
|
-
if (bundledBrief.projectContext) {
|
|
2740
|
-
sections.push("## Project Context", "", bundledBrief.projectContext);
|
|
2741
|
-
}
|
|
2742
|
-
const combinedBrief = sections.join("\n");
|
|
2743
|
-
const yapoutDir = join10(ctx.cwd, ".yapout");
|
|
2744
|
-
if (!existsSync10(yapoutDir)) mkdirSync7(yapoutDir, { recursive: true });
|
|
2745
|
-
writeFileSync8(join10(yapoutDir, "brief.md"), combinedBrief);
|
|
2746
|
-
return {
|
|
2747
|
-
content: [
|
|
2748
|
-
{
|
|
2749
|
-
type: "text",
|
|
2750
|
-
text: JSON.stringify(
|
|
2751
|
-
{
|
|
2752
|
-
bundleId: result.bundleId,
|
|
2753
|
-
findings: bundledBrief.findings.map((t) => ({
|
|
2754
|
-
findingId: t.findingId,
|
|
2755
|
-
title: t.title
|
|
2756
|
-
})),
|
|
2757
|
-
combinedBrief,
|
|
2758
|
-
message: `${bundledBrief.findings.length} findings bundled. Brief updated.`
|
|
2759
|
-
},
|
|
2760
|
-
null,
|
|
2761
|
-
2
|
|
2762
|
-
)
|
|
2763
|
-
}
|
|
2764
|
-
]
|
|
2765
|
-
};
|
|
2766
|
-
})
|
|
2767
|
-
);
|
|
2768
|
-
}
|
|
2769
|
-
|
|
2770
2681
|
// src/mcp/tools/get-unenriched-findings.ts
|
|
2771
|
-
import { z as
|
|
2682
|
+
import { z as z10 } from "zod";
|
|
2772
2683
|
function registerGetUnenrichedFindingsTool(server, ctx) {
|
|
2773
2684
|
server.tool(
|
|
2774
2685
|
"yapout_get_unenriched_finding",
|
|
@@ -2782,7 +2693,7 @@ After calling this tool, you should:
|
|
|
2782
2693
|
3. If the finding is ambiguous, ask the developer clarifying questions in conversation
|
|
2783
2694
|
4. When confident, call yapout_save_enrichment with a clean description, acceptance criteria, and implementation brief`,
|
|
2784
2695
|
{
|
|
2785
|
-
findingId:
|
|
2696
|
+
findingId: z10.string().optional().describe("Specific finding ID to enrich. If omitted, returns the highest priority draft finding.")
|
|
2786
2697
|
},
|
|
2787
2698
|
withScopeCheck(ctx, "yapout_get_unenriched_finding", async (args) => {
|
|
2788
2699
|
const projectId = ctx.projectId ?? process.env.YAPOUT_PROJECT_ID;
|
|
@@ -2896,7 +2807,7 @@ function registerGetExistingFindingsTool(server, ctx) {
|
|
|
2896
2807
|
}
|
|
2897
2808
|
|
|
2898
2809
|
// src/mcp/tools/save-enrichment.ts
|
|
2899
|
-
import { z as
|
|
2810
|
+
import { z as z11 } from "zod";
|
|
2900
2811
|
|
|
2901
2812
|
// src/mcp/tools/enrichment-session.ts
|
|
2902
2813
|
var activeSessions = /* @__PURE__ */ new Map();
|
|
@@ -2941,22 +2852,22 @@ This tool saves the enrichment, then automatically creates the Linear issue with
|
|
|
2941
2852
|
|
|
2942
2853
|
The finding transitions: enriching \u2192 enriched \u2192 ready.`,
|
|
2943
2854
|
{
|
|
2944
|
-
findingId:
|
|
2945
|
-
title:
|
|
2946
|
-
cleanDescription:
|
|
2947
|
-
acceptanceCriteria:
|
|
2948
|
-
implementationBrief:
|
|
2949
|
-
clarifications:
|
|
2950
|
-
|
|
2951
|
-
question:
|
|
2952
|
-
answer:
|
|
2855
|
+
findingId: z11.string().describe("The finding ID to enrich (from yapout_get_unenriched_finding)"),
|
|
2856
|
+
title: z11.string().describe("Refined finding title \u2014 improve it if the original was vague"),
|
|
2857
|
+
cleanDescription: z11.string().describe("Human-readable summary for the Linear issue body. Write the kind of finding a senior engineer would write."),
|
|
2858
|
+
acceptanceCriteria: z11.array(z11.string()).describe("List of testable acceptance criteria (each a single clear statement)"),
|
|
2859
|
+
implementationBrief: z11.string().describe("Deep technical context for the implementing agent: which files, what approach, edge cases to watch for"),
|
|
2860
|
+
clarifications: z11.array(
|
|
2861
|
+
z11.object({
|
|
2862
|
+
question: z11.string().describe("The question you asked the developer"),
|
|
2863
|
+
answer: z11.string().describe("The developer's answer")
|
|
2953
2864
|
})
|
|
2954
2865
|
).optional().describe("Only meaningful Q&A from the conversation \u2014 deviations from expected scope, scoping decisions, etc. Omit if you had no questions."),
|
|
2955
|
-
isOversized:
|
|
2956
|
-
suggestedSplit:
|
|
2957
|
-
nature:
|
|
2958
|
-
cloudSafe:
|
|
2959
|
-
sessionId:
|
|
2866
|
+
isOversized: z11.boolean().optional().describe("Set to true if this finding is too large for a single PR"),
|
|
2867
|
+
suggestedSplit: z11.array(z11.string()).optional().describe("If oversized: suggested sub-finding titles for breaking it down"),
|
|
2868
|
+
nature: z11.enum(["implementable", "operational", "spike"]).optional().describe("Override the finding's nature if enrichment reveals it should be reclassified"),
|
|
2869
|
+
cloudSafe: z11.boolean().optional().describe("Set to true ONLY if this is a small mechanical change a cloud agent can ship without sandbox testing \u2014 text/copy edits, classname tweaks, single-named-constant changes, \u226430 lines, single file, no logic/type/dependency changes. Default false."),
|
|
2870
|
+
sessionId: z11.string().optional().describe("Bulk enrichment session ID (from yapout_start_enrichment). Updates session stats.")
|
|
2960
2871
|
},
|
|
2961
2872
|
withScopeCheck(ctx, "yapout_save_enrichment", async (args) => {
|
|
2962
2873
|
try {
|
|
@@ -3028,13 +2939,13 @@ The finding transitions: enriching \u2192 enriched \u2192 ready.`,
|
|
|
3028
2939
|
}
|
|
3029
2940
|
|
|
3030
2941
|
// src/mcp/tools/sync-to-linear.ts
|
|
3031
|
-
import { z as
|
|
2942
|
+
import { z as z12 } from "zod";
|
|
3032
2943
|
function registerSyncToLinearTool(server, ctx) {
|
|
3033
2944
|
server.tool(
|
|
3034
2945
|
"yapout_sync_to_linear",
|
|
3035
2946
|
"Trigger Linear issue creation for an enriched finding. The sync runs server-side (encrypted Linear token in Convex).",
|
|
3036
2947
|
{
|
|
3037
|
-
findingId:
|
|
2948
|
+
findingId: z12.string().describe("The finding ID to sync to Linear")
|
|
3038
2949
|
},
|
|
3039
2950
|
withScopeCheck(ctx, "yapout_sync_to_linear", async (args) => {
|
|
3040
2951
|
try {
|
|
@@ -3074,14 +2985,14 @@ function registerSyncToLinearTool(server, ctx) {
|
|
|
3074
2985
|
}
|
|
3075
2986
|
|
|
3076
2987
|
// src/mcp/tools/submit-yap-session.ts
|
|
3077
|
-
import { z as
|
|
2988
|
+
import { z as z13 } from "zod";
|
|
3078
2989
|
function registerSubmitYapSessionTool(server, ctx) {
|
|
3079
2990
|
server.tool(
|
|
3080
2991
|
"yapout_submit_yap_session",
|
|
3081
2992
|
"Submit a yap session transcript for finding extraction",
|
|
3082
2993
|
{
|
|
3083
|
-
title:
|
|
3084
|
-
transcript:
|
|
2994
|
+
title: z13.string().describe("Session title (e.g., 'Notification system brainstorm')"),
|
|
2995
|
+
transcript: z13.string().describe("Cleaned conversation transcript")
|
|
3085
2996
|
},
|
|
3086
2997
|
withScopeCheck(ctx, "yapout_submit_yap_session", async (args) => {
|
|
3087
2998
|
if (!ctx.projectId) {
|
|
@@ -3135,7 +3046,7 @@ function registerSubmitYapSessionTool(server, ctx) {
|
|
|
3135
3046
|
}
|
|
3136
3047
|
|
|
3137
3048
|
// src/mcp/tools/start-yap.ts
|
|
3138
|
-
import { z as
|
|
3049
|
+
import { z as z14 } from "zod";
|
|
3139
3050
|
var PERSONA_PRESETS = {
|
|
3140
3051
|
"tech lead": "You are an experienced tech lead. You care about maintainability, simplicity, and shipping. Challenge over-engineering and vague scope.",
|
|
3141
3052
|
"qa engineer": "You are a skeptical QA engineer. Focus on error states, edge cases, missing validation, accessibility, and user-facing failure modes.",
|
|
@@ -3151,10 +3062,10 @@ function registerStartYapTool(server, ctx) {
|
|
|
3151
3062
|
"yapout_start_yap",
|
|
3152
3063
|
"Start a yap session \u2014 a structured AI-guided brainstorming conversation. Call this when the user wants to brainstorm, discuss ideas, or have a yap session. Returns instructions for how to conduct the session. Available personas: tech lead (default), qa engineer, product owner, end user, or any custom description.",
|
|
3153
3064
|
{
|
|
3154
|
-
persona:
|
|
3065
|
+
persona: z14.string().optional().describe(
|
|
3155
3066
|
'Interviewer persona: "tech lead", "qa engineer", "product owner", "end user", or a custom description'
|
|
3156
3067
|
),
|
|
3157
|
-
context:
|
|
3068
|
+
context: z14.string().optional().describe("What the developer wants to discuss")
|
|
3158
3069
|
},
|
|
3159
3070
|
withScopeCheck(ctx, "yapout_start_yap", async (args) => {
|
|
3160
3071
|
if (!ctx.projectId) {
|
|
@@ -3305,57 +3216,57 @@ Only fall back to yapout_submit_yap_session if the session was purely explorator
|
|
|
3305
3216
|
}
|
|
3306
3217
|
|
|
3307
3218
|
// src/mcp/tools/extract-from-yap.ts
|
|
3308
|
-
import { z as
|
|
3309
|
-
var clarificationSchema =
|
|
3310
|
-
question:
|
|
3311
|
-
answer:
|
|
3219
|
+
import { z as z15 } from "zod";
|
|
3220
|
+
var clarificationSchema = z15.object({
|
|
3221
|
+
question: z15.string().describe("The question asked during the conversation"),
|
|
3222
|
+
answer: z15.string().describe("The answer given")
|
|
3312
3223
|
});
|
|
3313
|
-
var childSchema =
|
|
3314
|
-
title:
|
|
3315
|
-
description:
|
|
3316
|
-
sourceQuote:
|
|
3317
|
-
type:
|
|
3318
|
-
priority:
|
|
3319
|
-
confidence:
|
|
3320
|
-
nature:
|
|
3224
|
+
var childSchema = z15.object({
|
|
3225
|
+
title: z15.string().describe("Child issue title"),
|
|
3226
|
+
description: z15.string().describe("What needs to be done and why"),
|
|
3227
|
+
sourceQuote: z15.string().describe("Relevant excerpt from the conversation"),
|
|
3228
|
+
type: z15.enum(["feature", "bug", "chore", "spike"]).describe("Finding category"),
|
|
3229
|
+
priority: z15.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
|
|
3230
|
+
confidence: z15.number().min(0).max(1).describe("Confidence this is a clear, actionable finding (0-1)"),
|
|
3231
|
+
nature: z15.enum(["implementable", "operational", "spike"]).describe("What kind of work"),
|
|
3321
3232
|
// Enrichment — set isEnriched: true only if the conversation covered this
|
|
3322
3233
|
// issue thoroughly enough to produce a finding a developer could pick up.
|
|
3323
|
-
isEnriched:
|
|
3234
|
+
isEnriched: z15.boolean().optional().describe(
|
|
3324
3235
|
"Did the conversation cover this issue deeply enough to produce a complete finding? true = enriched (ready for Linear), false/omitted = draft (needs async enrichment)"
|
|
3325
3236
|
),
|
|
3326
|
-
enrichedDescription:
|
|
3327
|
-
acceptanceCriteria:
|
|
3328
|
-
implementationBrief:
|
|
3329
|
-
clarifications:
|
|
3330
|
-
dependsOn:
|
|
3237
|
+
enrichedDescription: z15.string().optional().describe("Clean, final description for the Linear issue body (required if isEnriched)"),
|
|
3238
|
+
acceptanceCriteria: z15.array(z15.string()).optional().describe("Testable acceptance criteria (required if isEnriched)"),
|
|
3239
|
+
implementationBrief: z15.string().optional().describe("Technical context: files, approach, edge cases (optional \u2014 the implementing agent reads the codebase anyway)"),
|
|
3240
|
+
clarifications: z15.array(clarificationSchema).optional().describe("Relevant Q&A from the conversation that shaped this finding"),
|
|
3241
|
+
dependsOn: z15.array(z15.number()).optional().describe("Indices (0-based) of sibling children that must be completed first")
|
|
3331
3242
|
});
|
|
3332
3243
|
function registerExtractFromYapTool(server, ctx) {
|
|
3333
3244
|
server.tool(
|
|
3334
3245
|
"yapout_extract_from_yap",
|
|
3335
3246
|
"Submit findings from a yap session you conducted. The yap session IS the enrichment \u2014 you participated in the conversation, gathered context, and can assess completeness per-finding. Creates capture, findings (enriched or draft). Enriched findings are ready for the user to sync to Linear from the UI. Draft findings need further enrichment via the async pipeline.",
|
|
3336
3247
|
{
|
|
3337
|
-
sessionTitle:
|
|
3338
|
-
sessionTranscript:
|
|
3339
|
-
findings:
|
|
3340
|
-
|
|
3341
|
-
title:
|
|
3342
|
-
description:
|
|
3343
|
-
sourceQuote:
|
|
3344
|
-
type:
|
|
3345
|
-
priority:
|
|
3346
|
-
confidence:
|
|
3347
|
-
nature:
|
|
3348
|
-
sourceFindingId:
|
|
3248
|
+
sessionTitle: z15.string().describe("Descriptive title for this yap session"),
|
|
3249
|
+
sessionTranscript: z15.string().describe("Cleaned transcript of the conversation (meeting-notes style)"),
|
|
3250
|
+
findings: z15.array(
|
|
3251
|
+
z15.object({
|
|
3252
|
+
title: z15.string().describe("Finding title"),
|
|
3253
|
+
description: z15.string().describe("What needs to be done and why"),
|
|
3254
|
+
sourceQuote: z15.string().describe("Relevant excerpt from the conversation"),
|
|
3255
|
+
type: z15.enum(["feature", "bug", "chore", "spike"]).describe("Finding category (do not use spike \u2014 use nature instead)"),
|
|
3256
|
+
priority: z15.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
|
|
3257
|
+
confidence: z15.number().min(0).max(1).describe("Your confidence this is a clear, actionable finding (0-1)"),
|
|
3258
|
+
nature: z15.enum(["implementable", "operational", "spike"]).describe("implementable = code changes, operational = manual task, spike = needs scoping"),
|
|
3259
|
+
sourceFindingId: z15.string().optional().describe("ID of an existing finding this scopes (e.g., scoping a spike)"),
|
|
3349
3260
|
// Enrichment data for standalone findings
|
|
3350
|
-
isEnriched:
|
|
3351
|
-
enrichedDescription:
|
|
3352
|
-
acceptanceCriteria:
|
|
3353
|
-
implementationBrief:
|
|
3354
|
-
clarifications:
|
|
3261
|
+
isEnriched: z15.boolean().optional().describe("For standalone issues: was this thoroughly discussed? true = enriched, false = draft"),
|
|
3262
|
+
enrichedDescription: z15.string().optional().describe("Clean description for Linear (required if isEnriched)"),
|
|
3263
|
+
acceptanceCriteria: z15.array(z15.string()).optional().describe("Testable acceptance criteria (required if isEnriched)"),
|
|
3264
|
+
implementationBrief: z15.string().optional().describe("Technical context (optional)"),
|
|
3265
|
+
clarifications: z15.array(clarificationSchema).optional().describe("Relevant Q&A from conversation"),
|
|
3355
3266
|
// Bundle fields — when a finding has children, a bundle is created
|
|
3356
|
-
bundleDescription:
|
|
3357
|
-
suggestedOrder:
|
|
3358
|
-
children:
|
|
3267
|
+
bundleDescription: z15.string().optional().describe("What this body of work accomplishes (only for findings with children)"),
|
|
3268
|
+
suggestedOrder: z15.string().optional().describe("Implementation order: 'Phase 1: A, then Phase 2: B+C' (only for findings with children)"),
|
|
3269
|
+
children: z15.array(childSchema).optional().describe("Child issues \u2014 when present, a bundle is created from this finding")
|
|
3359
3270
|
})
|
|
3360
3271
|
).describe("Findings from the conversation. Projects include children inline.")
|
|
3361
3272
|
},
|
|
@@ -3437,7 +3348,7 @@ function registerExtractFromYapTool(server, ctx) {
|
|
|
3437
3348
|
}
|
|
3438
3349
|
|
|
3439
3350
|
// src/mcp/tools/decompose-finding.ts
|
|
3440
|
-
import { z as
|
|
3351
|
+
import { z as z16 } from "zod";
|
|
3441
3352
|
function registerDecomposeFindingTool(server, ctx) {
|
|
3442
3353
|
server.tool(
|
|
3443
3354
|
"yapout_decompose_finding",
|
|
@@ -3455,19 +3366,19 @@ archives the original finding. Returns the bundle ID and child finding IDs.
|
|
|
3455
3366
|
Each child issue's implementation brief must be detailed enough to stand alone as a
|
|
3456
3367
|
full spec \u2014 schema changes, files to modify, edge cases, and acceptance criteria.`,
|
|
3457
3368
|
{
|
|
3458
|
-
findingId:
|
|
3459
|
-
bundleDescription:
|
|
3460
|
-
suggestedOrder:
|
|
3461
|
-
linearProjectId:
|
|
3462
|
-
issues:
|
|
3463
|
-
|
|
3464
|
-
title:
|
|
3465
|
-
description:
|
|
3466
|
-
acceptanceCriteria:
|
|
3467
|
-
implementationBrief:
|
|
3468
|
-
type:
|
|
3469
|
-
priority:
|
|
3470
|
-
dependsOn:
|
|
3369
|
+
findingId: z16.string().describe("The finding being decomposed (from yapout_get_unenriched_finding)"),
|
|
3370
|
+
bundleDescription: z16.string().describe("Description for the bundle \u2014 what this body of work accomplishes"),
|
|
3371
|
+
suggestedOrder: z16.string().describe("Human-readable implementation order (e.g. 'Phase 1: A, then Phase 2: B+C in parallel, then Phase 3: D')"),
|
|
3372
|
+
linearProjectId: z16.string().optional().describe("Existing Linear project ID to associate with. Omit to skip Linear project association."),
|
|
3373
|
+
issues: z16.array(
|
|
3374
|
+
z16.object({
|
|
3375
|
+
title: z16.string().describe("Child issue title"),
|
|
3376
|
+
description: z16.string().describe("What needs to be done and why"),
|
|
3377
|
+
acceptanceCriteria: z16.array(z16.string()).describe("Testable acceptance criteria"),
|
|
3378
|
+
implementationBrief: z16.string().describe("Full spec: files to modify, schema changes, edge cases, approach"),
|
|
3379
|
+
type: z16.enum(["feature", "bug", "chore", "spike"]).describe("Category of work"),
|
|
3380
|
+
priority: z16.enum(["urgent", "high", "medium", "low"]).describe("Priority level"),
|
|
3381
|
+
dependsOn: z16.array(z16.number()).describe("Indices (0-based) of issues in this array that must be completed first")
|
|
3471
3382
|
})
|
|
3472
3383
|
).describe("The child issues produced by decomposition")
|
|
3473
3384
|
},
|
|
@@ -3530,7 +3441,7 @@ full spec \u2014 schema changes, files to modify, edge cases, and acceptance cri
|
|
|
3530
3441
|
}
|
|
3531
3442
|
|
|
3532
3443
|
// src/mcp/tools/mark-duplicate.ts
|
|
3533
|
-
import { z as
|
|
3444
|
+
import { z as z17 } from "zod";
|
|
3534
3445
|
function registerMarkDuplicateTool(server, ctx) {
|
|
3535
3446
|
server.tool(
|
|
3536
3447
|
"yapout_mark_duplicate",
|
|
@@ -3540,9 +3451,9 @@ flow when you discover the work is already tracked in Linear.
|
|
|
3540
3451
|
The finding must be in "enriching" or "enriched" status. It will be transitioned to
|
|
3541
3452
|
"failed" with the duplicate reference stored. Nothing is synced to Linear.`,
|
|
3542
3453
|
{
|
|
3543
|
-
findingId:
|
|
3544
|
-
duplicateOfLinearId:
|
|
3545
|
-
reason:
|
|
3454
|
+
findingId: z17.string().describe("The yapout finding to archive as a duplicate"),
|
|
3455
|
+
duplicateOfLinearId: z17.string().describe("The Linear issue identifier it duplicates (e.g. 'ENG-234')"),
|
|
3456
|
+
reason: z17.string().describe("Brief explanation of why this is a duplicate")
|
|
3546
3457
|
},
|
|
3547
3458
|
withScopeCheck(ctx, "yapout_mark_duplicate", async (args) => {
|
|
3548
3459
|
if (!ctx.projectId) {
|
|
@@ -3671,7 +3582,7 @@ and issue counts so you can ask the user for confirmation before creating a new
|
|
|
3671
3582
|
}
|
|
3672
3583
|
|
|
3673
3584
|
// src/mcp/tools/start-enrichment.ts
|
|
3674
|
-
import { z as
|
|
3585
|
+
import { z as z18 } from "zod";
|
|
3675
3586
|
function registerStartEnrichmentTool(server, ctx) {
|
|
3676
3587
|
server.tool(
|
|
3677
3588
|
"yapout_start_enrichment",
|
|
@@ -3682,10 +3593,10 @@ and maintains filter criteria so you don't re-pass them on every call.
|
|
|
3682
3593
|
|
|
3683
3594
|
Optionally filter by tags, capture, or explicit finding IDs.`,
|
|
3684
3595
|
{
|
|
3685
|
-
filter:
|
|
3686
|
-
tags:
|
|
3687
|
-
captureId:
|
|
3688
|
-
findingIds:
|
|
3596
|
+
filter: z18.object({
|
|
3597
|
+
tags: z18.array(z18.string()).optional().describe("Only enrich findings with these tags"),
|
|
3598
|
+
captureId: z18.string().optional().describe("Only enrich findings from this capture"),
|
|
3599
|
+
findingIds: z18.array(z18.string()).optional().describe("Only enrich these specific findings")
|
|
3689
3600
|
}).optional().describe("Filter criteria. If omitted, all draft findings in the project are included.")
|
|
3690
3601
|
},
|
|
3691
3602
|
withScopeCheck(ctx, "yapout_start_enrichment", async (args) => {
|
|
@@ -3741,7 +3652,7 @@ Optionally filter by tags, capture, or explicit finding IDs.`,
|
|
|
3741
3652
|
}
|
|
3742
3653
|
|
|
3743
3654
|
// src/mcp/tools/enrich-next.ts
|
|
3744
|
-
import { z as
|
|
3655
|
+
import { z as z19 } from "zod";
|
|
3745
3656
|
function registerEnrichNextTool(server, ctx) {
|
|
3746
3657
|
server.tool(
|
|
3747
3658
|
"yapout_enrich_next",
|
|
@@ -3752,9 +3663,9 @@ Returns the next unclaimed draft finding matching the session's filter.
|
|
|
3752
3663
|
|
|
3753
3664
|
When done=true, all findings have been processed.`,
|
|
3754
3665
|
{
|
|
3755
|
-
sessionId:
|
|
3756
|
-
skip:
|
|
3757
|
-
skipFindingId:
|
|
3666
|
+
sessionId: z19.string().describe("Session ID from yapout_start_enrichment"),
|
|
3667
|
+
skip: z19.boolean().optional().describe("If true, skip the current finding (release back to draft)"),
|
|
3668
|
+
skipFindingId: z19.string().optional().describe("The finding ID to skip (must be in 'enriching' status)")
|
|
3758
3669
|
},
|
|
3759
3670
|
withScopeCheck(ctx, "yapout_enrich_next", async (args) => {
|
|
3760
3671
|
const session = getSession(args.sessionId);
|
|
@@ -3882,125 +3793,8 @@ When done=true, all findings have been processed.`,
|
|
|
3882
3793
|
);
|
|
3883
3794
|
}
|
|
3884
3795
|
|
|
3885
|
-
// src/mcp/tools/enrich-bundle.ts
|
|
3886
|
-
import { z as z21 } from "zod";
|
|
3887
|
-
function registerEnrichBundleTool(server, ctx) {
|
|
3888
|
-
server.tool(
|
|
3889
|
-
"yapout_enrich_bundle",
|
|
3890
|
-
`Claim an entire bundle for enrichment. Returns ALL findings in the bundle at once
|
|
3891
|
-
with their relationships, source quotes, and project context.
|
|
3892
|
-
|
|
3893
|
-
This is for enriching a bundle as a single cohesive unit \u2014 NOT individual findings.
|
|
3894
|
-
The agent should understand the full scope, ask questions about the bundle as a whole,
|
|
3895
|
-
then call yapout_save_bundle_enrichment with enrichment data for every finding.
|
|
3896
|
-
|
|
3897
|
-
The bundle and all its findings transition to "enriching" status.`,
|
|
3898
|
-
{
|
|
3899
|
-
bundleId: z21.string().describe("The bundle ID to enrich")
|
|
3900
|
-
},
|
|
3901
|
-
withScopeCheck(ctx, "yapout_enrich_bundle", async (args) => {
|
|
3902
|
-
try {
|
|
3903
|
-
const result = await ctx.client.mutation(
|
|
3904
|
-
anyApi5.functions.bundles.claimBundleForEnrichment,
|
|
3905
|
-
{ bundleId: args.bundleId }
|
|
3906
|
-
);
|
|
3907
|
-
if (!result) {
|
|
3908
|
-
return {
|
|
3909
|
-
content: [{ type: "text", text: "Failed to claim bundle for enrichment." }],
|
|
3910
|
-
isError: true
|
|
3911
|
-
};
|
|
3912
|
-
}
|
|
3913
|
-
return {
|
|
3914
|
-
content: [
|
|
3915
|
-
{
|
|
3916
|
-
type: "text",
|
|
3917
|
-
text: JSON.stringify(result, null, 2)
|
|
3918
|
-
}
|
|
3919
|
-
]
|
|
3920
|
-
};
|
|
3921
|
-
} catch (err) {
|
|
3922
|
-
return {
|
|
3923
|
-
content: [{ type: "text", text: `Error claiming bundle: ${err.message}` }],
|
|
3924
|
-
isError: true
|
|
3925
|
-
};
|
|
3926
|
-
}
|
|
3927
|
-
})
|
|
3928
|
-
);
|
|
3929
|
-
}
|
|
3930
|
-
|
|
3931
|
-
// src/mcp/tools/save-bundle-enrichment.ts
|
|
3932
|
-
import { z as z22 } from "zod";
|
|
3933
|
-
function registerSaveBundleEnrichmentTool(server, ctx) {
|
|
3934
|
-
server.tool(
|
|
3935
|
-
"yapout_save_bundle_enrichment",
|
|
3936
|
-
`Save enrichment for an entire bundle at once. Call this after you've analyzed all
|
|
3937
|
-
findings in the bundle as a cohesive unit, read the codebase, and asked any questions.
|
|
3938
|
-
|
|
3939
|
-
Provide:
|
|
3940
|
-
- Bundle-level: overall description, combined acceptance criteria, implementation brief
|
|
3941
|
-
- Per-finding: each finding gets its own refined title, description, acceptance criteria, and brief
|
|
3942
|
-
|
|
3943
|
-
The bundle and all findings transition from "enriching" to "enriched".
|
|
3944
|
-
Call yapout_sync_bundle_to_linear afterwards to create the Linear project.`,
|
|
3945
|
-
{
|
|
3946
|
-
bundleId: z22.string().describe("The bundle ID"),
|
|
3947
|
-
title: z22.string().optional().describe("Refined bundle title (optional, keeps existing if omitted)"),
|
|
3948
|
-
enrichedDescription: z22.string().describe("Bundle-level description \u2014 the cohesive story of what this bundle delivers"),
|
|
3949
|
-
acceptanceCriteria: z22.array(z22.string()).describe("Bundle-level acceptance criteria"),
|
|
3950
|
-
implementationBrief: z22.string().describe("Bundle-level implementation brief \u2014 overall approach, architecture decisions, key files"),
|
|
3951
|
-
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."),
|
|
3952
|
-
findings: z22.array(z22.object({
|
|
3953
|
-
findingId: z22.string().describe("Finding ID"),
|
|
3954
|
-
title: z22.string().describe("Refined finding title"),
|
|
3955
|
-
enrichedDescription: z22.string().describe("Finding-specific description"),
|
|
3956
|
-
acceptanceCriteria: z22.array(z22.string()).describe("Finding-specific acceptance criteria"),
|
|
3957
|
-
implementationBrief: z22.string().describe("Finding-specific implementation brief")
|
|
3958
|
-
})).describe("Per-finding enrichment data \u2014 one entry per finding in the bundle")
|
|
3959
|
-
},
|
|
3960
|
-
withScopeCheck(ctx, "yapout_save_bundle_enrichment", async (args) => {
|
|
3961
|
-
try {
|
|
3962
|
-
await ctx.client.mutation(
|
|
3963
|
-
anyApi5.functions.bundles.saveBundleEnrichment,
|
|
3964
|
-
{
|
|
3965
|
-
bundleId: args.bundleId,
|
|
3966
|
-
title: args.title,
|
|
3967
|
-
enrichedDescription: args.enrichedDescription,
|
|
3968
|
-
acceptanceCriteria: args.acceptanceCriteria,
|
|
3969
|
-
implementationBrief: args.implementationBrief,
|
|
3970
|
-
cloudSafe: args.cloudSafe,
|
|
3971
|
-
findings: args.findings.map((f) => ({
|
|
3972
|
-
findingId: f.findingId,
|
|
3973
|
-
title: f.title,
|
|
3974
|
-
enrichedDescription: f.enrichedDescription,
|
|
3975
|
-
acceptanceCriteria: f.acceptanceCriteria,
|
|
3976
|
-
implementationBrief: f.implementationBrief
|
|
3977
|
-
}))
|
|
3978
|
-
}
|
|
3979
|
-
);
|
|
3980
|
-
return {
|
|
3981
|
-
content: [
|
|
3982
|
-
{
|
|
3983
|
-
type: "text",
|
|
3984
|
-
text: JSON.stringify({
|
|
3985
|
-
bundleId: args.bundleId,
|
|
3986
|
-
findingsEnriched: args.findings.length,
|
|
3987
|
-
message: `Bundle enriched successfully (${args.findings.length} findings). Call yapout_sync_bundle_to_linear to create the Linear project.`
|
|
3988
|
-
}, null, 2)
|
|
3989
|
-
}
|
|
3990
|
-
]
|
|
3991
|
-
};
|
|
3992
|
-
} catch (err) {
|
|
3993
|
-
return {
|
|
3994
|
-
content: [{ type: "text", text: `Error saving bundle enrichment: ${err.message}` }],
|
|
3995
|
-
isError: true
|
|
3996
|
-
};
|
|
3997
|
-
}
|
|
3998
|
-
})
|
|
3999
|
-
);
|
|
4000
|
-
}
|
|
4001
|
-
|
|
4002
3796
|
// src/mcp/tools/block-enrichment.ts
|
|
4003
|
-
import { z as
|
|
3797
|
+
import { z as z20 } from "zod";
|
|
4004
3798
|
function registerBlockEnrichmentTool(server, ctx) {
|
|
4005
3799
|
server.tool(
|
|
4006
3800
|
"yapout_block_enrichment",
|
|
@@ -4012,9 +3806,9 @@ The finding must currently be in "enriching" status (claimed via yapout_get_unen
|
|
|
4012
3806
|
|
|
4013
3807
|
Transitions: enriching \u2192 needs_input. The user sees the blockerReason and questions in the UI, adds context, and resubmits.`,
|
|
4014
3808
|
{
|
|
4015
|
-
findingId:
|
|
4016
|
-
blockerReason:
|
|
4017
|
-
blockerQuestions:
|
|
3809
|
+
findingId: z20.string().describe("The finding ID to block (currently in 'enriching')"),
|
|
3810
|
+
blockerReason: z20.string().describe("One sentence: why this finding cannot be enriched as written. State the missing piece concretely."),
|
|
3811
|
+
blockerQuestions: z20.array(z20.string()).min(1).describe("2-5 specific questions the user must answer before enrichment can succeed. Each must be answerable in a sentence or two.")
|
|
4018
3812
|
},
|
|
4019
3813
|
withScopeCheck(ctx, "yapout_block_enrichment", async (args) => {
|
|
4020
3814
|
try {
|
|
@@ -4061,9 +3855,9 @@ Transitions: enriching \u2192 needs_input. The user sees the blockerReason and q
|
|
|
4061
3855
|
|
|
4062
3856
|
Same semantics as yapout_block_enrichment but for an entire bundle. Bundle status transitions enriching \u2192 needs_input; child findings revert to draft.`,
|
|
4063
3857
|
{
|
|
4064
|
-
bundleId:
|
|
4065
|
-
blockerReason:
|
|
4066
|
-
blockerQuestions:
|
|
3858
|
+
bundleId: z20.string().describe("The bundle ID to block (currently in 'enriching')"),
|
|
3859
|
+
blockerReason: z20.string().describe("One sentence: why this bundle cannot be enriched."),
|
|
3860
|
+
blockerQuestions: z20.array(z20.string()).min(1).describe("2-5 specific questions the user must answer.")
|
|
4067
3861
|
},
|
|
4068
3862
|
async (args) => {
|
|
4069
3863
|
try {
|
|
@@ -5164,143 +4958,6 @@ function registerProjectTools(server, ctx) {
|
|
|
5164
4958
|
registerTool(server, ctx, listProjectsTool);
|
|
5165
4959
|
}
|
|
5166
4960
|
|
|
5167
|
-
// src/mcp/tools/bundles-crud.ts
|
|
5168
|
-
var getBundleTool = defineTool({
|
|
5169
|
-
name: "yapout_get_bundle",
|
|
5170
|
-
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.`,
|
|
5171
|
-
inputSchema: { bundleId: z.string() },
|
|
5172
|
-
handler: async (ctx, args) => {
|
|
5173
|
-
const bundle = await ctx.client.query(
|
|
5174
|
-
anyApi5.functions.bundles.getBundle,
|
|
5175
|
-
args
|
|
5176
|
-
);
|
|
5177
|
-
if (!bundle) {
|
|
5178
|
-
return {
|
|
5179
|
-
content: [{ type: "text", text: "Bundle not found." }],
|
|
5180
|
-
isError: true
|
|
5181
|
-
};
|
|
5182
|
-
}
|
|
5183
|
-
return {
|
|
5184
|
-
content: [
|
|
5185
|
-
{ type: "text", text: JSON.stringify(bundle, null, 2) }
|
|
5186
|
-
]
|
|
5187
|
-
};
|
|
5188
|
-
}
|
|
5189
|
-
});
|
|
5190
|
-
var listBundlesTool = defineTool({
|
|
5191
|
-
name: "yapout_list_bundles",
|
|
5192
|
-
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.`,
|
|
5193
|
-
inputSchema: {},
|
|
5194
|
-
handler: async (ctx) => {
|
|
5195
|
-
if (!ctx.projectId) {
|
|
5196
|
-
return {
|
|
5197
|
-
content: [{ type: "text", text: "No project linked." }],
|
|
5198
|
-
isError: true
|
|
5199
|
-
};
|
|
5200
|
-
}
|
|
5201
|
-
const bundles = await ctx.client.query(
|
|
5202
|
-
anyApi5.functions.bundles.getProjectBundles,
|
|
5203
|
-
{ projectId: ctx.projectId }
|
|
5204
|
-
);
|
|
5205
|
-
return {
|
|
5206
|
-
content: [
|
|
5207
|
-
{ type: "text", text: JSON.stringify(bundles, null, 2) }
|
|
5208
|
-
]
|
|
5209
|
-
};
|
|
5210
|
-
}
|
|
5211
|
-
});
|
|
5212
|
-
var updateBundleTool = defineTool({
|
|
5213
|
-
name: "yapout_update_bundle",
|
|
5214
|
-
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.`,
|
|
5215
|
-
inputSchema: {
|
|
5216
|
-
bundleId: z.string(),
|
|
5217
|
-
title: z.string().optional(),
|
|
5218
|
-
description: z.string().optional()
|
|
5219
|
-
},
|
|
5220
|
-
handler: async (ctx, args) => {
|
|
5221
|
-
await ctx.client.mutation(
|
|
5222
|
-
anyApi5.functions.bundles.updateBundle,
|
|
5223
|
-
args
|
|
5224
|
-
);
|
|
5225
|
-
return {
|
|
5226
|
-
content: [{ type: "text", text: "Bundle updated." }]
|
|
5227
|
-
};
|
|
5228
|
-
}
|
|
5229
|
-
});
|
|
5230
|
-
var archiveBundleTool = defineTool({
|
|
5231
|
-
name: "yapout_archive_bundle",
|
|
5232
|
-
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.`,
|
|
5233
|
-
inputSchema: { bundleId: z.string() },
|
|
5234
|
-
handler: async (ctx, args) => {
|
|
5235
|
-
await ctx.client.mutation(
|
|
5236
|
-
anyApi5.functions.bundles.archiveBundle,
|
|
5237
|
-
args
|
|
5238
|
-
);
|
|
5239
|
-
return {
|
|
5240
|
-
content: [{ type: "text", text: "Bundle archived." }]
|
|
5241
|
-
};
|
|
5242
|
-
}
|
|
5243
|
-
});
|
|
5244
|
-
var restoreBundleTool = defineTool({
|
|
5245
|
-
name: "yapout_restore_bundle",
|
|
5246
|
-
description: `Restore an archived bundle. Defaults to draft; pass to: "ready" to bring it straight back to the open queue.`,
|
|
5247
|
-
inputSchema: {
|
|
5248
|
-
bundleId: z.string(),
|
|
5249
|
-
to: z.enum(["draft", "ready"]).optional()
|
|
5250
|
-
},
|
|
5251
|
-
handler: async (ctx, args) => {
|
|
5252
|
-
await ctx.client.mutation(
|
|
5253
|
-
anyApi5.functions.bundles.restoreBundle,
|
|
5254
|
-
args
|
|
5255
|
-
);
|
|
5256
|
-
return {
|
|
5257
|
-
content: [{ type: "text", text: "Bundle restored." }]
|
|
5258
|
-
};
|
|
5259
|
-
}
|
|
5260
|
-
});
|
|
5261
|
-
var addFindingToBundleTool = defineTool({
|
|
5262
|
-
name: "yapout_add_finding_to_bundle",
|
|
5263
|
-
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).`,
|
|
5264
|
-
inputSchema: {
|
|
5265
|
-
findingId: z.string(),
|
|
5266
|
-
bundleId: z.string()
|
|
5267
|
-
},
|
|
5268
|
-
handler: async (ctx, args) => {
|
|
5269
|
-
await ctx.client.mutation(
|
|
5270
|
-
anyApi5.functions.findings.addToBundle,
|
|
5271
|
-
args
|
|
5272
|
-
);
|
|
5273
|
-
return {
|
|
5274
|
-
content: [{ type: "text", text: "Finding added to bundle." }]
|
|
5275
|
-
};
|
|
5276
|
-
}
|
|
5277
|
-
});
|
|
5278
|
-
var removeFindingFromBundleTool = defineTool({
|
|
5279
|
-
name: "yapout_remove_finding_from_bundle",
|
|
5280
|
-
description: `Detach a finding from its current bundle. The finding stays in the project; only its bundleId is cleared.`,
|
|
5281
|
-
inputSchema: { findingId: z.string() },
|
|
5282
|
-
handler: async (ctx, args) => {
|
|
5283
|
-
await ctx.client.mutation(
|
|
5284
|
-
anyApi5.functions.findings.removeFromBundle,
|
|
5285
|
-
args
|
|
5286
|
-
);
|
|
5287
|
-
return {
|
|
5288
|
-
content: [
|
|
5289
|
-
{ type: "text", text: "Finding removed from bundle." }
|
|
5290
|
-
]
|
|
5291
|
-
};
|
|
5292
|
-
}
|
|
5293
|
-
});
|
|
5294
|
-
function registerBundleCrudTools(server, ctx) {
|
|
5295
|
-
registerTool(server, ctx, getBundleTool);
|
|
5296
|
-
registerTool(server, ctx, listBundlesTool);
|
|
5297
|
-
registerTool(server, ctx, updateBundleTool);
|
|
5298
|
-
registerTool(server, ctx, archiveBundleTool);
|
|
5299
|
-
registerTool(server, ctx, restoreBundleTool);
|
|
5300
|
-
registerTool(server, ctx, addFindingToBundleTool);
|
|
5301
|
-
registerTool(server, ctx, removeFindingFromBundleTool);
|
|
5302
|
-
}
|
|
5303
|
-
|
|
5304
4961
|
// src/mcp/tools/insights.ts
|
|
5305
4962
|
var listInsightsTool = defineTool({
|
|
5306
4963
|
name: "yapout_list_insights",
|
|
@@ -5472,7 +5129,7 @@ async function startMcpServer() {
|
|
|
5472
5129
|
};
|
|
5473
5130
|
const server = new McpServer({
|
|
5474
5131
|
name: "yapout",
|
|
5475
|
-
version: "0.
|
|
5132
|
+
version: "0.16.0"
|
|
5476
5133
|
});
|
|
5477
5134
|
registerInitTool(server, ctx);
|
|
5478
5135
|
registerCompactTool(server, ctx);
|
|
@@ -5483,7 +5140,6 @@ async function startMcpServer() {
|
|
|
5483
5140
|
registerEventTool(server, ctx);
|
|
5484
5141
|
registerShipTool(server, ctx);
|
|
5485
5142
|
registerCheckTool(server, ctx);
|
|
5486
|
-
registerBundleTool(server, ctx);
|
|
5487
5143
|
registerGetUnenrichedFindingsTool(server, ctx);
|
|
5488
5144
|
registerGetExistingFindingsTool(server, ctx);
|
|
5489
5145
|
registerSaveEnrichmentTool(server, ctx);
|
|
@@ -5496,8 +5152,6 @@ async function startMcpServer() {
|
|
|
5496
5152
|
registerGetLinearProjectsTool(server, ctx);
|
|
5497
5153
|
registerStartEnrichmentTool(server, ctx);
|
|
5498
5154
|
registerEnrichNextTool(server, ctx);
|
|
5499
|
-
registerEnrichBundleTool(server, ctx);
|
|
5500
|
-
registerSaveBundleEnrichmentTool(server, ctx);
|
|
5501
5155
|
registerBlockEnrichmentTool(server, ctx);
|
|
5502
5156
|
registerSessionTools(server, ctx);
|
|
5503
5157
|
registerCodebaseTools(server, ctx);
|
|
@@ -5507,7 +5161,6 @@ async function startMcpServer() {
|
|
|
5507
5161
|
registerFindingReadTools(server, ctx);
|
|
5508
5162
|
registerFindingWriteTools(server, ctx);
|
|
5509
5163
|
registerProjectTools(server, ctx);
|
|
5510
|
-
registerBundleCrudTools(server, ctx);
|
|
5511
5164
|
registerInsightTools(server, ctx);
|
|
5512
5165
|
registerPipelineRunTools(server, ctx);
|
|
5513
5166
|
const transport = new StdioServerTransport();
|
|
@@ -6192,9 +5845,89 @@ var pillarTest = new Command15("test").description("Send a test delivery to a pi
|
|
|
6192
5845
|
});
|
|
6193
5846
|
var pillarCommand = new Command15("pillar").description("Manage per-project pillar overrides (extraction / enrichment / intelligence / implementation)").addCommand(pillarList).addCommand(pillarSet).addCommand(pillarTest);
|
|
6194
5847
|
|
|
5848
|
+
// src/commands/observe.ts
|
|
5849
|
+
import { Command as Command16 } from "commander";
|
|
5850
|
+
import { resolve as resolve12 } from "path";
|
|
5851
|
+
import chalk16 from "chalk";
|
|
5852
|
+
var observeCommand = new Command16("observe").description("File an observation Raw against the active Execution").argument("<observation...>", "Freeform observation text").option(
|
|
5853
|
+
"--severity <level>",
|
|
5854
|
+
"Severity hint: low | normal | high (default: normal)"
|
|
5855
|
+
).option(
|
|
5856
|
+
"--area <hint>",
|
|
5857
|
+
"Optional scoping hint for the extraction chain (e.g., 'auth', 'billing')"
|
|
5858
|
+
).action(
|
|
5859
|
+
async (words, options) => {
|
|
5860
|
+
const creds = requireAuth();
|
|
5861
|
+
const cwd = resolve12(process.cwd());
|
|
5862
|
+
const resourceCfg = readResourceConfig(cwd);
|
|
5863
|
+
if (!resourceCfg) {
|
|
5864
|
+
console.error(
|
|
5865
|
+
chalk16.red("No `.yapout/config.json` found.") + " Run " + chalk16.cyan("yapout init") + " in this repo first."
|
|
5866
|
+
);
|
|
5867
|
+
process.exit(1);
|
|
5868
|
+
}
|
|
5869
|
+
const active = readActiveExecution(cwd);
|
|
5870
|
+
if (!active) {
|
|
5871
|
+
console.error(
|
|
5872
|
+
chalk16.red("No active Execution.") + " `yapout observe` must run from a worktree where " + chalk16.cyan("yapout pick") + " has been called."
|
|
5873
|
+
);
|
|
5874
|
+
process.exit(1);
|
|
5875
|
+
}
|
|
5876
|
+
const body = words.join(" ").trim();
|
|
5877
|
+
if (!body) {
|
|
5878
|
+
console.error(chalk16.red("Observation body cannot be empty."));
|
|
5879
|
+
process.exit(1);
|
|
5880
|
+
}
|
|
5881
|
+
const severityHint = parseSeverity(options.severity);
|
|
5882
|
+
if (options.severity && !severityHint) {
|
|
5883
|
+
console.error(
|
|
5884
|
+
chalk16.red(
|
|
5885
|
+
`Invalid --severity: ${options.severity}. Allowed: low, normal, high.`
|
|
5886
|
+
)
|
|
5887
|
+
);
|
|
5888
|
+
process.exit(1);
|
|
5889
|
+
}
|
|
5890
|
+
const client = createConvexClient(creds.token);
|
|
5891
|
+
let result;
|
|
5892
|
+
try {
|
|
5893
|
+
result = await client.mutation(
|
|
5894
|
+
anyApi.functions.resourcesCli.createAgentObservationRaw,
|
|
5895
|
+
{
|
|
5896
|
+
executionId: active.executionId,
|
|
5897
|
+
body,
|
|
5898
|
+
...severityHint ? { severityHint } : {},
|
|
5899
|
+
...options.area ? { area: options.area } : {}
|
|
5900
|
+
}
|
|
5901
|
+
);
|
|
5902
|
+
} catch (err) {
|
|
5903
|
+
console.error(
|
|
5904
|
+
chalk16.red("Failed to file observation."),
|
|
5905
|
+
err.message
|
|
5906
|
+
);
|
|
5907
|
+
process.exit(1);
|
|
5908
|
+
}
|
|
5909
|
+
console.log(
|
|
5910
|
+
chalk16.green("Filed agent observation. ") + chalk16.dim(
|
|
5911
|
+
`(raw: ${result.rawId}, execution: ${result.executionId})`
|
|
5912
|
+
)
|
|
5913
|
+
);
|
|
5914
|
+
console.log(
|
|
5915
|
+
chalk16.dim(
|
|
5916
|
+
"Extraction will turn this into a Request (or link it to an existing one)."
|
|
5917
|
+
)
|
|
5918
|
+
);
|
|
5919
|
+
}
|
|
5920
|
+
);
|
|
5921
|
+
function parseSeverity(raw) {
|
|
5922
|
+
if (!raw) return void 0;
|
|
5923
|
+
const v = raw.toLowerCase();
|
|
5924
|
+
if (v === "low" || v === "normal" || v === "high") return v;
|
|
5925
|
+
return void 0;
|
|
5926
|
+
}
|
|
5927
|
+
|
|
6195
5928
|
// src/index.ts
|
|
6196
|
-
var program = new
|
|
6197
|
-
program.name("yapout").description("yapout \u2014 the PM tool for agents").version("0.
|
|
5929
|
+
var program = new Command17();
|
|
5930
|
+
program.name("yapout").description("yapout \u2014 the PM tool for agents").version("0.16.0");
|
|
6198
5931
|
program.addCommand(loginCommand);
|
|
6199
5932
|
program.addCommand(logoutCommand);
|
|
6200
5933
|
program.addCommand(initCommand);
|
|
@@ -6210,4 +5943,5 @@ program.addCommand(agentCommand);
|
|
|
6210
5943
|
program.addCommand(webhookCommand);
|
|
6211
5944
|
program.addCommand(inboundCommand);
|
|
6212
5945
|
program.addCommand(pillarCommand);
|
|
5946
|
+
program.addCommand(observeCommand);
|
|
6213
5947
|
program.parse();
|