yapout 0.15.2 → 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 +351 -616
- 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,
|
|
@@ -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;
|
|
@@ -345,7 +407,7 @@ function startServer(payload) {
|
|
|
345
407
|
res.writeHead(404, headers);
|
|
346
408
|
res.end();
|
|
347
409
|
});
|
|
348
|
-
return new Promise((
|
|
410
|
+
return new Promise((resolve13, reject) => {
|
|
349
411
|
let attempt = 0;
|
|
350
412
|
const tryListen = () => {
|
|
351
413
|
const port = DEFAULT_PORT + attempt;
|
|
@@ -361,7 +423,7 @@ function startServer(payload) {
|
|
|
361
423
|
server.once("error", onError);
|
|
362
424
|
server.listen(port, "127.0.0.1", () => {
|
|
363
425
|
server.removeListener("error", onError);
|
|
364
|
-
|
|
426
|
+
resolve13({
|
|
365
427
|
port,
|
|
366
428
|
close: () => server.close()
|
|
367
429
|
});
|
|
@@ -518,7 +580,7 @@ var serveCommand = new Command("serve").description(
|
|
|
518
580
|
});
|
|
519
581
|
|
|
520
582
|
// src/commands/login.ts
|
|
521
|
-
var CLI_VERSION2 = "0.
|
|
583
|
+
var CLI_VERSION2 = "0.16.0";
|
|
522
584
|
function safeReturnTo(raw) {
|
|
523
585
|
if (!raw) return null;
|
|
524
586
|
try {
|
|
@@ -530,7 +592,7 @@ function safeReturnTo(raw) {
|
|
|
530
592
|
}
|
|
531
593
|
}
|
|
532
594
|
function startCallbackServer() {
|
|
533
|
-
return new Promise((
|
|
595
|
+
return new Promise((resolve13) => {
|
|
534
596
|
let resolveData;
|
|
535
597
|
let rejectData;
|
|
536
598
|
const dataPromise = new Promise((res, rej) => {
|
|
@@ -579,7 +641,7 @@ function startCallbackServer() {
|
|
|
579
641
|
server.listen(0, () => {
|
|
580
642
|
const address = server.address();
|
|
581
643
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
582
|
-
|
|
644
|
+
resolve13({ port, data: dataPromise });
|
|
583
645
|
});
|
|
584
646
|
setTimeout(() => {
|
|
585
647
|
server.close();
|
|
@@ -681,15 +743,6 @@ async function pickProject(projects) {
|
|
|
681
743
|
})
|
|
682
744
|
});
|
|
683
745
|
}
|
|
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
746
|
|
|
694
747
|
// src/commands/link.ts
|
|
695
748
|
var CONFIG_YAML_CONTENT = `# yapout local configuration
|
|
@@ -716,7 +769,50 @@ branch_prefix: feat
|
|
|
716
769
|
# {{ticket.linearTicketId}}, {{ticket.id}}
|
|
717
770
|
# commit_template: "{{ticket.type}}({{ticket.linearTicketId}}): {{ticket.title}}"
|
|
718
771
|
`;
|
|
719
|
-
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() {
|
|
720
816
|
const creds = requireAuth();
|
|
721
817
|
const cwd = resolveRepoRoot(resolve2(process.cwd()));
|
|
722
818
|
const client = createConvexClient(creds.token);
|
|
@@ -776,10 +872,7 @@ var linkCommand = new Command4("link").description("Link the current directory t
|
|
|
776
872
|
if (existsSync3(gitignorePath)) {
|
|
777
873
|
const content = readFileSync3(gitignorePath, "utf-8");
|
|
778
874
|
if (!content.includes(".yapout/")) {
|
|
779
|
-
appendFileSync(
|
|
780
|
-
gitignorePath,
|
|
781
|
-
"\n# yapout local config\n.yapout/\n"
|
|
782
|
-
);
|
|
875
|
+
appendFileSync(gitignorePath, "\n# yapout local config\n.yapout/\n");
|
|
783
876
|
}
|
|
784
877
|
} else {
|
|
785
878
|
writeFileSync3(gitignorePath, "# yapout local config\n.yapout/\n");
|
|
@@ -803,7 +896,7 @@ var linkCommand = new Command4("link").description("Link the current directory t
|
|
|
803
896
|
console.log(
|
|
804
897
|
chalk5.green(`Linked to ${label}${orgSuffix}.`) + " Claude Code will discover yapout tools automatically."
|
|
805
898
|
);
|
|
806
|
-
}
|
|
899
|
+
}
|
|
807
900
|
function getCliVersion() {
|
|
808
901
|
try {
|
|
809
902
|
const pkg = JSON.parse(
|
|
@@ -916,7 +1009,6 @@ import {
|
|
|
916
1009
|
readFileSync as readFileSync5,
|
|
917
1010
|
appendFileSync as appendFileSync2
|
|
918
1011
|
} from "fs";
|
|
919
|
-
import { hostname as hostname4 } from "os";
|
|
920
1012
|
import chalk8 from "chalk";
|
|
921
1013
|
var CONFIG_YAML_CONTENT2 = `# yapout local configuration
|
|
922
1014
|
# See: https://docs.yapout.dev/cli/config
|
|
@@ -927,7 +1019,6 @@ var CONFIG_YAML_CONTENT2 = `# yapout local configuration
|
|
|
927
1019
|
# - npm run lint
|
|
928
1020
|
# - npm run typecheck
|
|
929
1021
|
# - npm run test
|
|
930
|
-
# - sf project deploy --dry-run --target-org scratch
|
|
931
1022
|
post_flight: []
|
|
932
1023
|
|
|
933
1024
|
# Require post_flight checks to pass before yapout_ship succeeds.
|
|
@@ -936,170 +1027,89 @@ ship_requires_checks: false
|
|
|
936
1027
|
|
|
937
1028
|
# Git branch prefix (used by yapout_claim)
|
|
938
1029
|
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
1030
|
`;
|
|
945
|
-
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) => {
|
|
946
1037
|
const creds = requireAuth();
|
|
947
|
-
const cwd =
|
|
948
|
-
|
|
949
|
-
let defaultBranch;
|
|
950
|
-
try {
|
|
951
|
-
repoFullName = getRepoFullName(cwd);
|
|
952
|
-
defaultBranch = getDefaultBranch(cwd);
|
|
953
|
-
} catch (err) {
|
|
1038
|
+
const cwd = resolve5(process.cwd());
|
|
1039
|
+
if (!isInGitRepo(cwd)) {
|
|
954
1040
|
console.error(
|
|
955
|
-
chalk8.red("
|
|
956
|
-
err.message
|
|
1041
|
+
chalk8.red("`yapout init` must be run from inside a git repo.") + "\n No .git/ directory found from " + chalk8.dim(cwd) + "."
|
|
957
1042
|
);
|
|
958
1043
|
process.exit(1);
|
|
959
1044
|
}
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
let orgs;
|
|
1045
|
+
const repoRoot = resolveRepoRoot(cwd);
|
|
1046
|
+
let rawRemote;
|
|
963
1047
|
try {
|
|
964
|
-
|
|
965
|
-
anyApi.functions.orgMembers.getMyOrgs,
|
|
966
|
-
{}
|
|
967
|
-
);
|
|
1048
|
+
rawRemote = getOriginRemoteUrl(repoRoot);
|
|
968
1049
|
} catch (err) {
|
|
969
1050
|
console.error(
|
|
970
|
-
chalk8.red("Failed to
|
|
971
|
-
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`."
|
|
972
1052
|
);
|
|
973
1053
|
process.exit(1);
|
|
974
1054
|
}
|
|
975
|
-
|
|
1055
|
+
const canonicalId = normalizeGitRemote(rawRemote);
|
|
1056
|
+
if (!canonicalId) {
|
|
976
1057
|
console.error(
|
|
977
1058
|
chalk8.red(
|
|
978
|
-
|
|
979
|
-
)
|
|
1059
|
+
`Could not derive a canonical id from the origin remote URL: ${rawRemote}`
|
|
1060
|
+
) + "\n yapout currently supports github.com remotes only."
|
|
980
1061
|
);
|
|
981
1062
|
process.exit(1);
|
|
982
1063
|
}
|
|
983
|
-
let
|
|
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());
|
|
1064
|
+
let defaultBranch;
|
|
1019
1065
|
try {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
-
);
|
|
1066
|
+
defaultBranch = getDefaultBranch(repoRoot);
|
|
1067
|
+
} catch {
|
|
1068
|
+
defaultBranch = void 0;
|
|
1031
1069
|
}
|
|
1070
|
+
const client = createConvexClient(creds.token);
|
|
1032
1071
|
let result;
|
|
1033
1072
|
try {
|
|
1034
1073
|
result = await client.mutation(
|
|
1035
|
-
anyApi.functions.
|
|
1074
|
+
anyApi.functions.resourcesCli.findOrCreateRepoResource,
|
|
1036
1075
|
{
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1076
|
+
canonicalId,
|
|
1077
|
+
remoteUrl: canonicalId,
|
|
1078
|
+
defaultBranch,
|
|
1079
|
+
...options?.group ? { groupId: options.group } : {}
|
|
1041
1080
|
}
|
|
1042
1081
|
);
|
|
1043
1082
|
} catch (err) {
|
|
1044
1083
|
console.error(
|
|
1045
|
-
chalk8.red("Failed to
|
|
1084
|
+
chalk8.red("Failed to register Resource."),
|
|
1046
1085
|
err.message
|
|
1047
1086
|
);
|
|
1048
1087
|
process.exit(1);
|
|
1049
1088
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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,
|
|
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,
|
|
1065
1097
|
linkedAt: Date.now()
|
|
1066
1098
|
});
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
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);
|
|
1099
|
+
const configYamlPath = join5(yapoutDir, "config.yml");
|
|
1100
|
+
if (!existsSync5(configYamlPath)) {
|
|
1101
|
+
writeFileSync5(configYamlPath, CONFIG_YAML_CONTENT2);
|
|
1091
1102
|
}
|
|
1092
|
-
const gitignorePath = join5(
|
|
1103
|
+
const gitignorePath = join5(repoRoot, ".gitignore");
|
|
1093
1104
|
if (existsSync5(gitignorePath)) {
|
|
1094
1105
|
const content = readFileSync5(gitignorePath, "utf-8");
|
|
1095
1106
|
if (!content.includes(".yapout/")) {
|
|
1096
|
-
appendFileSync2(
|
|
1097
|
-
gitignorePath,
|
|
1098
|
-
"\n# yapout local config\n.yapout/\n"
|
|
1099
|
-
);
|
|
1107
|
+
appendFileSync2(gitignorePath, "\n# yapout local config\n.yapout/\n");
|
|
1100
1108
|
}
|
|
1109
|
+
} else {
|
|
1110
|
+
writeFileSync5(gitignorePath, "# yapout local config\n.yapout/\n");
|
|
1101
1111
|
}
|
|
1102
|
-
const mcpPath = join5(
|
|
1112
|
+
const mcpPath = join5(repoRoot, ".mcp.json");
|
|
1103
1113
|
let mcpConfig = {};
|
|
1104
1114
|
if (existsSync5(mcpPath)) {
|
|
1105
1115
|
try {
|
|
@@ -1110,40 +1120,25 @@ var initCommand = new Command7("init").description("Create a yapout project from
|
|
|
1110
1120
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
1111
1121
|
const existingYapoutServer = mcpConfig.mcpServers.yapout ?? {};
|
|
1112
1122
|
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
1123
|
mcpConfig.mcpServers.yapout = {
|
|
1118
1124
|
command: "yapout",
|
|
1119
1125
|
args: ["mcp-server"],
|
|
1120
|
-
...Object.keys(
|
|
1126
|
+
...Object.keys(existingEnv).length > 0 ? { env: existingEnv } : {}
|
|
1121
1127
|
};
|
|
1122
1128
|
writeFileSync5(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
1129
|
+
const verb = result.created ? "Registered" : "Linked";
|
|
1123
1130
|
console.log(
|
|
1124
|
-
chalk8.green(`
|
|
1125
|
-
` in ${chosenOrgName} (${repoFullName}, branch: ${defaultBranch})`
|
|
1126
|
-
)
|
|
1131
|
+
chalk8.green(`${verb} Resource `) + chalk8.cyan(canonicalId) + chalk8.dim(` (id: ${result.resourceId}).`)
|
|
1127
1132
|
);
|
|
1128
|
-
if (
|
|
1129
|
-
console.log(
|
|
1130
|
-
chalk8.dim("Provisioned default agent \u2014 token stored in ") + chalk8.cyan(".mcp.json")
|
|
1131
|
-
);
|
|
1133
|
+
if (defaultBranch) {
|
|
1134
|
+
console.log(chalk8.dim(` default branch: ${defaultBranch}`));
|
|
1132
1135
|
}
|
|
1133
1136
|
console.log(
|
|
1134
|
-
chalk8.dim(
|
|
1137
|
+
chalk8.dim(
|
|
1138
|
+
"Claude Code will discover yapout's MCP tools via .mcp.json on next launch."
|
|
1139
|
+
)
|
|
1135
1140
|
);
|
|
1136
1141
|
});
|
|
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
1142
|
|
|
1148
1143
|
// src/commands/mcp-server.ts
|
|
1149
1144
|
import { Command as Command8 } from "commander";
|
|
@@ -2581,7 +2576,7 @@ function truncate(text) {
|
|
|
2581
2576
|
` + lines.slice(-MAX_OUTPUT_LINES).join("\n");
|
|
2582
2577
|
}
|
|
2583
2578
|
function runCommand(command, cwd) {
|
|
2584
|
-
return new Promise((
|
|
2579
|
+
return new Promise((resolve13) => {
|
|
2585
2580
|
const start = Date.now();
|
|
2586
2581
|
const child = exec(command, {
|
|
2587
2582
|
cwd,
|
|
@@ -2589,7 +2584,7 @@ function runCommand(command, cwd) {
|
|
|
2589
2584
|
maxBuffer: 10 * 1024 * 1024
|
|
2590
2585
|
// 10MB
|
|
2591
2586
|
}, (error, stdout, stderr) => {
|
|
2592
|
-
|
|
2587
|
+
resolve13({
|
|
2593
2588
|
exitCode: error?.code ?? (error ? 1 : 0),
|
|
2594
2589
|
stdout: truncate(stdout),
|
|
2595
2590
|
stderr: truncate(stderr),
|
|
@@ -2597,7 +2592,7 @@ function runCommand(command, cwd) {
|
|
|
2597
2592
|
});
|
|
2598
2593
|
});
|
|
2599
2594
|
child.on("error", () => {
|
|
2600
|
-
|
|
2595
|
+
resolve13({
|
|
2601
2596
|
exitCode: 1,
|
|
2602
2597
|
stdout: "",
|
|
2603
2598
|
stderr: `Command timed out after ${COMMAND_TIMEOUT_MS / 1e3}s`,
|
|
@@ -2683,91 +2678,8 @@ function registerCheckTool(server, ctx) {
|
|
|
2683
2678
|
);
|
|
2684
2679
|
}
|
|
2685
2680
|
|
|
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) {
|
|
2691
|
-
server.tool(
|
|
2692
|
-
"yapout_bundle",
|
|
2693
|
-
"Add a finding to the current finding's bundle so they ship as one PR",
|
|
2694
|
-
{
|
|
2695
|
-
findingId: z10.string().describe("Finding ID to add to the bundle"),
|
|
2696
|
-
withFinding: z10.string().describe("Lead finding ID (currently being worked on)")
|
|
2697
|
-
},
|
|
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
2681
|
// src/mcp/tools/get-unenriched-findings.ts
|
|
2770
|
-
import { z as
|
|
2682
|
+
import { z as z10 } from "zod";
|
|
2771
2683
|
function registerGetUnenrichedFindingsTool(server, ctx) {
|
|
2772
2684
|
server.tool(
|
|
2773
2685
|
"yapout_get_unenriched_finding",
|
|
@@ -2781,7 +2693,7 @@ After calling this tool, you should:
|
|
|
2781
2693
|
3. If the finding is ambiguous, ask the developer clarifying questions in conversation
|
|
2782
2694
|
4. When confident, call yapout_save_enrichment with a clean description, acceptance criteria, and implementation brief`,
|
|
2783
2695
|
{
|
|
2784
|
-
findingId:
|
|
2696
|
+
findingId: z10.string().optional().describe("Specific finding ID to enrich. If omitted, returns the highest priority draft finding.")
|
|
2785
2697
|
},
|
|
2786
2698
|
withScopeCheck(ctx, "yapout_get_unenriched_finding", async (args) => {
|
|
2787
2699
|
const projectId = ctx.projectId ?? process.env.YAPOUT_PROJECT_ID;
|
|
@@ -2895,7 +2807,7 @@ function registerGetExistingFindingsTool(server, ctx) {
|
|
|
2895
2807
|
}
|
|
2896
2808
|
|
|
2897
2809
|
// src/mcp/tools/save-enrichment.ts
|
|
2898
|
-
import { z as
|
|
2810
|
+
import { z as z11 } from "zod";
|
|
2899
2811
|
|
|
2900
2812
|
// src/mcp/tools/enrichment-session.ts
|
|
2901
2813
|
var activeSessions = /* @__PURE__ */ new Map();
|
|
@@ -2940,22 +2852,22 @@ This tool saves the enrichment, then automatically creates the Linear issue with
|
|
|
2940
2852
|
|
|
2941
2853
|
The finding transitions: enriching \u2192 enriched \u2192 ready.`,
|
|
2942
2854
|
{
|
|
2943
|
-
findingId:
|
|
2944
|
-
title:
|
|
2945
|
-
cleanDescription:
|
|
2946
|
-
acceptanceCriteria:
|
|
2947
|
-
implementationBrief:
|
|
2948
|
-
clarifications:
|
|
2949
|
-
|
|
2950
|
-
question:
|
|
2951
|
-
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")
|
|
2952
2864
|
})
|
|
2953
2865
|
).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:
|
|
2955
|
-
suggestedSplit:
|
|
2956
|
-
nature:
|
|
2957
|
-
cloudSafe:
|
|
2958
|
-
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.")
|
|
2959
2871
|
},
|
|
2960
2872
|
withScopeCheck(ctx, "yapout_save_enrichment", async (args) => {
|
|
2961
2873
|
try {
|
|
@@ -3027,13 +2939,13 @@ The finding transitions: enriching \u2192 enriched \u2192 ready.`,
|
|
|
3027
2939
|
}
|
|
3028
2940
|
|
|
3029
2941
|
// src/mcp/tools/sync-to-linear.ts
|
|
3030
|
-
import { z as
|
|
2942
|
+
import { z as z12 } from "zod";
|
|
3031
2943
|
function registerSyncToLinearTool(server, ctx) {
|
|
3032
2944
|
server.tool(
|
|
3033
2945
|
"yapout_sync_to_linear",
|
|
3034
2946
|
"Trigger Linear issue creation for an enriched finding. The sync runs server-side (encrypted Linear token in Convex).",
|
|
3035
2947
|
{
|
|
3036
|
-
findingId:
|
|
2948
|
+
findingId: z12.string().describe("The finding ID to sync to Linear")
|
|
3037
2949
|
},
|
|
3038
2950
|
withScopeCheck(ctx, "yapout_sync_to_linear", async (args) => {
|
|
3039
2951
|
try {
|
|
@@ -3073,14 +2985,14 @@ function registerSyncToLinearTool(server, ctx) {
|
|
|
3073
2985
|
}
|
|
3074
2986
|
|
|
3075
2987
|
// src/mcp/tools/submit-yap-session.ts
|
|
3076
|
-
import { z as
|
|
2988
|
+
import { z as z13 } from "zod";
|
|
3077
2989
|
function registerSubmitYapSessionTool(server, ctx) {
|
|
3078
2990
|
server.tool(
|
|
3079
2991
|
"yapout_submit_yap_session",
|
|
3080
2992
|
"Submit a yap session transcript for finding extraction",
|
|
3081
2993
|
{
|
|
3082
|
-
title:
|
|
3083
|
-
transcript:
|
|
2994
|
+
title: z13.string().describe("Session title (e.g., 'Notification system brainstorm')"),
|
|
2995
|
+
transcript: z13.string().describe("Cleaned conversation transcript")
|
|
3084
2996
|
},
|
|
3085
2997
|
withScopeCheck(ctx, "yapout_submit_yap_session", async (args) => {
|
|
3086
2998
|
if (!ctx.projectId) {
|
|
@@ -3134,7 +3046,7 @@ function registerSubmitYapSessionTool(server, ctx) {
|
|
|
3134
3046
|
}
|
|
3135
3047
|
|
|
3136
3048
|
// src/mcp/tools/start-yap.ts
|
|
3137
|
-
import { z as
|
|
3049
|
+
import { z as z14 } from "zod";
|
|
3138
3050
|
var PERSONA_PRESETS = {
|
|
3139
3051
|
"tech lead": "You are an experienced tech lead. You care about maintainability, simplicity, and shipping. Challenge over-engineering and vague scope.",
|
|
3140
3052
|
"qa engineer": "You are a skeptical QA engineer. Focus on error states, edge cases, missing validation, accessibility, and user-facing failure modes.",
|
|
@@ -3150,10 +3062,10 @@ function registerStartYapTool(server, ctx) {
|
|
|
3150
3062
|
"yapout_start_yap",
|
|
3151
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.",
|
|
3152
3064
|
{
|
|
3153
|
-
persona:
|
|
3065
|
+
persona: z14.string().optional().describe(
|
|
3154
3066
|
'Interviewer persona: "tech lead", "qa engineer", "product owner", "end user", or a custom description'
|
|
3155
3067
|
),
|
|
3156
|
-
context:
|
|
3068
|
+
context: z14.string().optional().describe("What the developer wants to discuss")
|
|
3157
3069
|
},
|
|
3158
3070
|
withScopeCheck(ctx, "yapout_start_yap", async (args) => {
|
|
3159
3071
|
if (!ctx.projectId) {
|
|
@@ -3304,57 +3216,57 @@ Only fall back to yapout_submit_yap_session if the session was purely explorator
|
|
|
3304
3216
|
}
|
|
3305
3217
|
|
|
3306
3218
|
// src/mcp/tools/extract-from-yap.ts
|
|
3307
|
-
import { z as
|
|
3308
|
-
var clarificationSchema =
|
|
3309
|
-
question:
|
|
3310
|
-
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")
|
|
3311
3223
|
});
|
|
3312
|
-
var childSchema =
|
|
3313
|
-
title:
|
|
3314
|
-
description:
|
|
3315
|
-
sourceQuote:
|
|
3316
|
-
type:
|
|
3317
|
-
priority:
|
|
3318
|
-
confidence:
|
|
3319
|
-
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"),
|
|
3320
3232
|
// Enrichment — set isEnriched: true only if the conversation covered this
|
|
3321
3233
|
// issue thoroughly enough to produce a finding a developer could pick up.
|
|
3322
|
-
isEnriched:
|
|
3234
|
+
isEnriched: z15.boolean().optional().describe(
|
|
3323
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)"
|
|
3324
3236
|
),
|
|
3325
|
-
enrichedDescription:
|
|
3326
|
-
acceptanceCriteria:
|
|
3327
|
-
implementationBrief:
|
|
3328
|
-
clarifications:
|
|
3329
|
-
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")
|
|
3330
3242
|
});
|
|
3331
3243
|
function registerExtractFromYapTool(server, ctx) {
|
|
3332
3244
|
server.tool(
|
|
3333
3245
|
"yapout_extract_from_yap",
|
|
3334
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.",
|
|
3335
3247
|
{
|
|
3336
|
-
sessionTitle:
|
|
3337
|
-
sessionTranscript:
|
|
3338
|
-
findings:
|
|
3339
|
-
|
|
3340
|
-
title:
|
|
3341
|
-
description:
|
|
3342
|
-
sourceQuote:
|
|
3343
|
-
type:
|
|
3344
|
-
priority:
|
|
3345
|
-
confidence:
|
|
3346
|
-
nature:
|
|
3347
|
-
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)"),
|
|
3348
3260
|
// Enrichment data for standalone findings
|
|
3349
|
-
isEnriched:
|
|
3350
|
-
enrichedDescription:
|
|
3351
|
-
acceptanceCriteria:
|
|
3352
|
-
implementationBrief:
|
|
3353
|
-
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"),
|
|
3354
3266
|
// Bundle fields — when a finding has children, a bundle is created
|
|
3355
|
-
bundleDescription:
|
|
3356
|
-
suggestedOrder:
|
|
3357
|
-
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")
|
|
3358
3270
|
})
|
|
3359
3271
|
).describe("Findings from the conversation. Projects include children inline.")
|
|
3360
3272
|
},
|
|
@@ -3436,7 +3348,7 @@ function registerExtractFromYapTool(server, ctx) {
|
|
|
3436
3348
|
}
|
|
3437
3349
|
|
|
3438
3350
|
// src/mcp/tools/decompose-finding.ts
|
|
3439
|
-
import { z as
|
|
3351
|
+
import { z as z16 } from "zod";
|
|
3440
3352
|
function registerDecomposeFindingTool(server, ctx) {
|
|
3441
3353
|
server.tool(
|
|
3442
3354
|
"yapout_decompose_finding",
|
|
@@ -3454,19 +3366,19 @@ archives the original finding. Returns the bundle ID and child finding IDs.
|
|
|
3454
3366
|
Each child issue's implementation brief must be detailed enough to stand alone as a
|
|
3455
3367
|
full spec \u2014 schema changes, files to modify, edge cases, and acceptance criteria.`,
|
|
3456
3368
|
{
|
|
3457
|
-
findingId:
|
|
3458
|
-
bundleDescription:
|
|
3459
|
-
suggestedOrder:
|
|
3460
|
-
linearProjectId:
|
|
3461
|
-
issues:
|
|
3462
|
-
|
|
3463
|
-
title:
|
|
3464
|
-
description:
|
|
3465
|
-
acceptanceCriteria:
|
|
3466
|
-
implementationBrief:
|
|
3467
|
-
type:
|
|
3468
|
-
priority:
|
|
3469
|
-
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")
|
|
3470
3382
|
})
|
|
3471
3383
|
).describe("The child issues produced by decomposition")
|
|
3472
3384
|
},
|
|
@@ -3529,7 +3441,7 @@ full spec \u2014 schema changes, files to modify, edge cases, and acceptance cri
|
|
|
3529
3441
|
}
|
|
3530
3442
|
|
|
3531
3443
|
// src/mcp/tools/mark-duplicate.ts
|
|
3532
|
-
import { z as
|
|
3444
|
+
import { z as z17 } from "zod";
|
|
3533
3445
|
function registerMarkDuplicateTool(server, ctx) {
|
|
3534
3446
|
server.tool(
|
|
3535
3447
|
"yapout_mark_duplicate",
|
|
@@ -3539,9 +3451,9 @@ flow when you discover the work is already tracked in Linear.
|
|
|
3539
3451
|
The finding must be in "enriching" or "enriched" status. It will be transitioned to
|
|
3540
3452
|
"failed" with the duplicate reference stored. Nothing is synced to Linear.`,
|
|
3541
3453
|
{
|
|
3542
|
-
findingId:
|
|
3543
|
-
duplicateOfLinearId:
|
|
3544
|
-
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")
|
|
3545
3457
|
},
|
|
3546
3458
|
withScopeCheck(ctx, "yapout_mark_duplicate", async (args) => {
|
|
3547
3459
|
if (!ctx.projectId) {
|
|
@@ -3670,7 +3582,7 @@ and issue counts so you can ask the user for confirmation before creating a new
|
|
|
3670
3582
|
}
|
|
3671
3583
|
|
|
3672
3584
|
// src/mcp/tools/start-enrichment.ts
|
|
3673
|
-
import { z as
|
|
3585
|
+
import { z as z18 } from "zod";
|
|
3674
3586
|
function registerStartEnrichmentTool(server, ctx) {
|
|
3675
3587
|
server.tool(
|
|
3676
3588
|
"yapout_start_enrichment",
|
|
@@ -3681,10 +3593,10 @@ and maintains filter criteria so you don't re-pass them on every call.
|
|
|
3681
3593
|
|
|
3682
3594
|
Optionally filter by tags, capture, or explicit finding IDs.`,
|
|
3683
3595
|
{
|
|
3684
|
-
filter:
|
|
3685
|
-
tags:
|
|
3686
|
-
captureId:
|
|
3687
|
-
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")
|
|
3688
3600
|
}).optional().describe("Filter criteria. If omitted, all draft findings in the project are included.")
|
|
3689
3601
|
},
|
|
3690
3602
|
withScopeCheck(ctx, "yapout_start_enrichment", async (args) => {
|
|
@@ -3740,7 +3652,7 @@ Optionally filter by tags, capture, or explicit finding IDs.`,
|
|
|
3740
3652
|
}
|
|
3741
3653
|
|
|
3742
3654
|
// src/mcp/tools/enrich-next.ts
|
|
3743
|
-
import { z as
|
|
3655
|
+
import { z as z19 } from "zod";
|
|
3744
3656
|
function registerEnrichNextTool(server, ctx) {
|
|
3745
3657
|
server.tool(
|
|
3746
3658
|
"yapout_enrich_next",
|
|
@@ -3751,9 +3663,9 @@ Returns the next unclaimed draft finding matching the session's filter.
|
|
|
3751
3663
|
|
|
3752
3664
|
When done=true, all findings have been processed.`,
|
|
3753
3665
|
{
|
|
3754
|
-
sessionId:
|
|
3755
|
-
skip:
|
|
3756
|
-
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)")
|
|
3757
3669
|
},
|
|
3758
3670
|
withScopeCheck(ctx, "yapout_enrich_next", async (args) => {
|
|
3759
3671
|
const session = getSession(args.sessionId);
|
|
@@ -3881,125 +3793,8 @@ When done=true, all findings have been processed.`,
|
|
|
3881
3793
|
);
|
|
3882
3794
|
}
|
|
3883
3795
|
|
|
3884
|
-
// src/mcp/tools/enrich-bundle.ts
|
|
3885
|
-
import { z as z21 } from "zod";
|
|
3886
|
-
function registerEnrichBundleTool(server, ctx) {
|
|
3887
|
-
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.
|
|
3891
|
-
|
|
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.
|
|
3895
|
-
|
|
3896
|
-
The bundle and all its findings transition to "enriching" status.`,
|
|
3897
|
-
{
|
|
3898
|
-
bundleId: z21.string().describe("The bundle ID to enrich")
|
|
3899
|
-
},
|
|
3900
|
-
withScopeCheck(ctx, "yapout_enrich_bundle", async (args) => {
|
|
3901
|
-
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
3796
|
// src/mcp/tools/block-enrichment.ts
|
|
4002
|
-
import { z as
|
|
3797
|
+
import { z as z20 } from "zod";
|
|
4003
3798
|
function registerBlockEnrichmentTool(server, ctx) {
|
|
4004
3799
|
server.tool(
|
|
4005
3800
|
"yapout_block_enrichment",
|
|
@@ -4011,9 +3806,9 @@ The finding must currently be in "enriching" status (claimed via yapout_get_unen
|
|
|
4011
3806
|
|
|
4012
3807
|
Transitions: enriching \u2192 needs_input. The user sees the blockerReason and questions in the UI, adds context, and resubmits.`,
|
|
4013
3808
|
{
|
|
4014
|
-
findingId:
|
|
4015
|
-
blockerReason:
|
|
4016
|
-
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.")
|
|
4017
3812
|
},
|
|
4018
3813
|
withScopeCheck(ctx, "yapout_block_enrichment", async (args) => {
|
|
4019
3814
|
try {
|
|
@@ -4060,9 +3855,9 @@ Transitions: enriching \u2192 needs_input. The user sees the blockerReason and q
|
|
|
4060
3855
|
|
|
4061
3856
|
Same semantics as yapout_block_enrichment but for an entire bundle. Bundle status transitions enriching \u2192 needs_input; child findings revert to draft.`,
|
|
4062
3857
|
{
|
|
4063
|
-
bundleId:
|
|
4064
|
-
blockerReason:
|
|
4065
|
-
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.")
|
|
4066
3861
|
},
|
|
4067
3862
|
async (args) => {
|
|
4068
3863
|
try {
|
|
@@ -5163,143 +4958,6 @@ function registerProjectTools(server, ctx) {
|
|
|
5163
4958
|
registerTool(server, ctx, listProjectsTool);
|
|
5164
4959
|
}
|
|
5165
4960
|
|
|
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
4961
|
// src/mcp/tools/insights.ts
|
|
5304
4962
|
var listInsightsTool = defineTool({
|
|
5305
4963
|
name: "yapout_list_insights",
|
|
@@ -5471,7 +5129,7 @@ async function startMcpServer() {
|
|
|
5471
5129
|
};
|
|
5472
5130
|
const server = new McpServer({
|
|
5473
5131
|
name: "yapout",
|
|
5474
|
-
version: "0.
|
|
5132
|
+
version: "0.16.0"
|
|
5475
5133
|
});
|
|
5476
5134
|
registerInitTool(server, ctx);
|
|
5477
5135
|
registerCompactTool(server, ctx);
|
|
@@ -5482,7 +5140,6 @@ async function startMcpServer() {
|
|
|
5482
5140
|
registerEventTool(server, ctx);
|
|
5483
5141
|
registerShipTool(server, ctx);
|
|
5484
5142
|
registerCheckTool(server, ctx);
|
|
5485
|
-
registerBundleTool(server, ctx);
|
|
5486
5143
|
registerGetUnenrichedFindingsTool(server, ctx);
|
|
5487
5144
|
registerGetExistingFindingsTool(server, ctx);
|
|
5488
5145
|
registerSaveEnrichmentTool(server, ctx);
|
|
@@ -5495,8 +5152,6 @@ async function startMcpServer() {
|
|
|
5495
5152
|
registerGetLinearProjectsTool(server, ctx);
|
|
5496
5153
|
registerStartEnrichmentTool(server, ctx);
|
|
5497
5154
|
registerEnrichNextTool(server, ctx);
|
|
5498
|
-
registerEnrichBundleTool(server, ctx);
|
|
5499
|
-
registerSaveBundleEnrichmentTool(server, ctx);
|
|
5500
5155
|
registerBlockEnrichmentTool(server, ctx);
|
|
5501
5156
|
registerSessionTools(server, ctx);
|
|
5502
5157
|
registerCodebaseTools(server, ctx);
|
|
@@ -5506,7 +5161,6 @@ async function startMcpServer() {
|
|
|
5506
5161
|
registerFindingReadTools(server, ctx);
|
|
5507
5162
|
registerFindingWriteTools(server, ctx);
|
|
5508
5163
|
registerProjectTools(server, ctx);
|
|
5509
|
-
registerBundleCrudTools(server, ctx);
|
|
5510
5164
|
registerInsightTools(server, ctx);
|
|
5511
5165
|
registerPipelineRunTools(server, ctx);
|
|
5512
5166
|
const transport = new StdioServerTransport();
|
|
@@ -6191,9 +5845,89 @@ var pillarTest = new Command15("test").description("Send a test delivery to a pi
|
|
|
6191
5845
|
});
|
|
6192
5846
|
var pillarCommand = new Command15("pillar").description("Manage per-project pillar overrides (extraction / enrichment / intelligence / implementation)").addCommand(pillarList).addCommand(pillarSet).addCommand(pillarTest);
|
|
6193
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
|
+
|
|
6194
5928
|
// src/index.ts
|
|
6195
|
-
var program = new
|
|
6196
|
-
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");
|
|
6197
5931
|
program.addCommand(loginCommand);
|
|
6198
5932
|
program.addCommand(logoutCommand);
|
|
6199
5933
|
program.addCommand(initCommand);
|
|
@@ -6209,4 +5943,5 @@ program.addCommand(agentCommand);
|
|
|
6209
5943
|
program.addCommand(webhookCommand);
|
|
6210
5944
|
program.addCommand(inboundCommand);
|
|
6211
5945
|
program.addCommand(pillarCommand);
|
|
5946
|
+
program.addCommand(observeCommand);
|
|
6212
5947
|
program.parse();
|