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