youmd 0.6.19 → 0.6.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/dist/commands/build.d.ts.map +1 -1
  2. package/dist/commands/build.js +0 -1
  3. package/dist/commands/build.js.map +1 -1
  4. package/dist/commands/chat.d.ts.map +1 -1
  5. package/dist/commands/chat.js +604 -199
  6. package/dist/commands/chat.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +21 -1
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/keys.d.ts.map +1 -1
  11. package/dist/commands/keys.js +3 -3
  12. package/dist/commands/keys.js.map +1 -1
  13. package/dist/commands/link.d.ts.map +1 -1
  14. package/dist/commands/link.js +3 -3
  15. package/dist/commands/link.js.map +1 -1
  16. package/dist/commands/login.d.ts +29 -0
  17. package/dist/commands/login.d.ts.map +1 -1
  18. package/dist/commands/login.js +257 -128
  19. package/dist/commands/login.js.map +1 -1
  20. package/dist/commands/logs.js +0 -1
  21. package/dist/commands/logs.js.map +1 -1
  22. package/dist/commands/mcp.d.ts +14 -0
  23. package/dist/commands/mcp.d.ts.map +1 -1
  24. package/dist/commands/mcp.js +284 -28
  25. package/dist/commands/mcp.js.map +1 -1
  26. package/dist/commands/private.d.ts.map +1 -1
  27. package/dist/commands/private.js +6 -5
  28. package/dist/commands/private.js.map +1 -1
  29. package/dist/commands/project.d.ts.map +1 -1
  30. package/dist/commands/project.js +9 -5
  31. package/dist/commands/project.js.map +1 -1
  32. package/dist/commands/publish.d.ts.map +1 -1
  33. package/dist/commands/publish.js +0 -1
  34. package/dist/commands/publish.js.map +1 -1
  35. package/dist/commands/pull.d.ts +24 -1
  36. package/dist/commands/pull.d.ts.map +1 -1
  37. package/dist/commands/pull.js +114 -14
  38. package/dist/commands/pull.js.map +1 -1
  39. package/dist/commands/push.d.ts +1 -0
  40. package/dist/commands/push.d.ts.map +1 -1
  41. package/dist/commands/push.js +33 -13
  42. package/dist/commands/push.js.map +1 -1
  43. package/dist/commands/register.d.ts +6 -0
  44. package/dist/commands/register.d.ts.map +1 -1
  45. package/dist/commands/register.js +179 -79
  46. package/dist/commands/register.js.map +1 -1
  47. package/dist/commands/skill.d.ts.map +1 -1
  48. package/dist/commands/skill.js +123 -52
  49. package/dist/commands/skill.js.map +1 -1
  50. package/dist/commands/stack.d.ts +16 -0
  51. package/dist/commands/stack.d.ts.map +1 -0
  52. package/dist/commands/stack.js +670 -0
  53. package/dist/commands/stack.js.map +1 -0
  54. package/dist/commands/status.d.ts.map +1 -1
  55. package/dist/commands/status.js +25 -5
  56. package/dist/commands/status.js.map +1 -1
  57. package/dist/commands/sync.d.ts +23 -0
  58. package/dist/commands/sync.d.ts.map +1 -1
  59. package/dist/commands/sync.js +193 -12
  60. package/dist/commands/sync.js.map +1 -1
  61. package/dist/commands/whoami.d.ts.map +1 -1
  62. package/dist/commands/whoami.js +21 -16
  63. package/dist/commands/whoami.js.map +1 -1
  64. package/dist/create.js +0 -0
  65. package/dist/index.js +128 -7
  66. package/dist/index.js.map +1 -1
  67. package/dist/lib/api.d.ts +94 -5
  68. package/dist/lib/api.d.ts.map +1 -1
  69. package/dist/lib/api.js +122 -5
  70. package/dist/lib/api.js.map +1 -1
  71. package/dist/lib/ascii.d.ts +5 -0
  72. package/dist/lib/ascii.d.ts.map +1 -1
  73. package/dist/lib/ascii.js +21 -30
  74. package/dist/lib/ascii.js.map +1 -1
  75. package/dist/lib/canonical-json.d.ts +8 -0
  76. package/dist/lib/canonical-json.d.ts.map +1 -0
  77. package/dist/lib/canonical-json.js +29 -0
  78. package/dist/lib/canonical-json.js.map +1 -0
  79. package/dist/lib/capability-router.d.ts +58 -0
  80. package/dist/lib/capability-router.d.ts.map +1 -0
  81. package/dist/lib/capability-router.js +54 -0
  82. package/dist/lib/capability-router.js.map +1 -0
  83. package/dist/lib/compiler.d.ts +32 -0
  84. package/dist/lib/compiler.d.ts.map +1 -1
  85. package/dist/lib/compiler.js +150 -0
  86. package/dist/lib/compiler.js.map +1 -1
  87. package/dist/lib/config.d.ts +71 -35
  88. package/dist/lib/config.d.ts.map +1 -1
  89. package/dist/lib/config.js +334 -124
  90. package/dist/lib/config.js.map +1 -1
  91. package/dist/lib/decompile.d.ts +6 -1
  92. package/dist/lib/decompile.d.ts.map +1 -1
  93. package/dist/lib/decompile.js +120 -11
  94. package/dist/lib/decompile.js.map +1 -1
  95. package/dist/lib/first-run.d.ts +14 -0
  96. package/dist/lib/first-run.d.ts.map +1 -0
  97. package/dist/lib/first-run.js +97 -0
  98. package/dist/lib/first-run.js.map +1 -0
  99. package/dist/lib/hash.d.ts.map +1 -1
  100. package/dist/lib/hash.js +2 -19
  101. package/dist/lib/hash.js.map +1 -1
  102. package/dist/lib/heartbeat.d.ts +38 -0
  103. package/dist/lib/heartbeat.d.ts.map +1 -0
  104. package/dist/lib/heartbeat.js +70 -0
  105. package/dist/lib/heartbeat.js.map +1 -0
  106. package/dist/lib/host-link.d.ts +123 -0
  107. package/dist/lib/host-link.d.ts.map +1 -0
  108. package/dist/lib/host-link.js +350 -0
  109. package/dist/lib/host-link.js.map +1 -0
  110. package/dist/lib/merge.d.ts +50 -0
  111. package/dist/lib/merge.d.ts.map +1 -0
  112. package/dist/lib/merge.js +167 -0
  113. package/dist/lib/merge.js.map +1 -0
  114. package/dist/lib/onboarding.d.ts +17 -0
  115. package/dist/lib/onboarding.d.ts.map +1 -1
  116. package/dist/lib/onboarding.js +225 -208
  117. package/dist/lib/onboarding.js.map +1 -1
  118. package/dist/lib/project.d.ts +5 -0
  119. package/dist/lib/project.d.ts.map +1 -1
  120. package/dist/lib/project.js +8 -0
  121. package/dist/lib/project.js.map +1 -1
  122. package/dist/lib/projectContext.d.ts +201 -0
  123. package/dist/lib/projectContext.d.ts.map +1 -0
  124. package/dist/lib/projectContext.js +597 -0
  125. package/dist/lib/projectContext.js.map +1 -0
  126. package/dist/lib/render.d.ts +38 -1
  127. package/dist/lib/render.d.ts.map +1 -1
  128. package/dist/lib/render.js +83 -18
  129. package/dist/lib/render.js.map +1 -1
  130. package/dist/lib/skill-catalog.d.ts.map +1 -1
  131. package/dist/lib/skill-catalog.js +22 -2
  132. package/dist/lib/skill-catalog.js.map +1 -1
  133. package/dist/lib/skills.d.ts +20 -5
  134. package/dist/lib/skills.d.ts.map +1 -1
  135. package/dist/lib/skills.js +116 -58
  136. package/dist/lib/skills.js.map +1 -1
  137. package/dist/lib/stackEval.d.ts +87 -0
  138. package/dist/lib/stackEval.d.ts.map +1 -0
  139. package/dist/lib/stackEval.js +233 -0
  140. package/dist/lib/stackEval.js.map +1 -0
  141. package/dist/lib/stackImprove.d.ts +49 -0
  142. package/dist/lib/stackImprove.d.ts.map +1 -0
  143. package/dist/lib/stackImprove.js +313 -0
  144. package/dist/lib/stackImprove.js.map +1 -0
  145. package/dist/lib/stackInstall.d.ts +59 -0
  146. package/dist/lib/stackInstall.d.ts.map +1 -0
  147. package/dist/lib/stackInstall.js +162 -0
  148. package/dist/lib/stackInstall.js.map +1 -0
  149. package/dist/lib/stackSafety.d.ts +126 -0
  150. package/dist/lib/stackSafety.d.ts.map +1 -0
  151. package/dist/lib/stackSafety.js +486 -0
  152. package/dist/lib/stackSafety.js.map +1 -0
  153. package/dist/lib/stackUpdate.d.ts +44 -0
  154. package/dist/lib/stackUpdate.d.ts.map +1 -0
  155. package/dist/lib/stackUpdate.js +256 -0
  156. package/dist/lib/stackUpdate.js.map +1 -0
  157. package/dist/lib/stream.d.ts +116 -0
  158. package/dist/lib/stream.d.ts.map +1 -0
  159. package/dist/lib/stream.js +368 -0
  160. package/dist/lib/stream.js.map +1 -0
  161. package/dist/lib/youstack.d.ts +219 -0
  162. package/dist/lib/youstack.d.ts.map +1 -0
  163. package/dist/lib/youstack.js +1049 -0
  164. package/dist/lib/youstack.js.map +1 -0
  165. package/dist/mcp/registry.d.ts +133 -0
  166. package/dist/mcp/registry.d.ts.map +1 -0
  167. package/dist/mcp/registry.js +833 -0
  168. package/dist/mcp/registry.js.map +1 -0
  169. package/dist/mcp/server.d.ts +106 -5
  170. package/dist/mcp/server.d.ts.map +1 -1
  171. package/dist/mcp/server.js +851 -498
  172. package/dist/mcp/server.js.map +1 -1
  173. package/dist/postinstall.js +11 -2
  174. package/dist/postinstall.js.map +1 -1
  175. package/dist/you.d.ts +1 -0
  176. package/dist/you.js +41 -5
  177. package/dist/you.js.map +1 -1
  178. package/examples/youstack-bamfstack-public/docs/quickstart.md +34 -0
  179. package/examples/youstack-bamfstack-public/prompts/creator-draft-starter.md +15 -0
  180. package/examples/youstack-bamfstack-public/skills/bamfstack-start/SKILL.md +36 -0
  181. package/examples/youstack-bamfstack-public/tests/smoke.md +19 -0
  182. package/examples/youstack-bamfstack-public/workflows/creator-growth-workflow.md +22 -0
  183. package/examples/youstack-bamfstack-public/youstack.json +172 -0
  184. package/examples/youstack-personal/docs/quickstart.md +10 -0
  185. package/examples/youstack-personal/skills/youstack-start/SKILL.md +9 -0
  186. package/examples/youstack-personal/tests/smoke.md +9 -0
  187. package/examples/youstack-personal/workflows/startup.md +7 -0
  188. package/examples/youstack-personal/youstack.json +123 -0
  189. package/package.json +3 -3
  190. package/skills/claude-md-generator.md +1 -1
  191. package/skills/meta-improve.md +8 -3
  192. package/skills/proactive-context-fill.md +1 -1
  193. package/skills/youstack-maintainer.md +128 -0
  194. package/skills/youstack-start.md +78 -0
  195. package/dist/__tests__/api.test.d.ts +0 -2
  196. package/dist/__tests__/api.test.d.ts.map +0 -1
  197. package/dist/__tests__/api.test.js +0 -87
  198. package/dist/__tests__/api.test.js.map +0 -1
  199. package/dist/__tests__/compiler.test.d.ts +0 -2
  200. package/dist/__tests__/compiler.test.d.ts.map +0 -1
  201. package/dist/__tests__/compiler.test.js +0 -127
  202. package/dist/__tests__/compiler.test.js.map +0 -1
  203. package/dist/__tests__/config.test.d.ts +0 -2
  204. package/dist/__tests__/config.test.d.ts.map +0 -1
  205. package/dist/__tests__/config.test.js +0 -79
  206. package/dist/__tests__/config.test.js.map +0 -1
  207. package/dist/__tests__/decompile.test.d.ts +0 -2
  208. package/dist/__tests__/decompile.test.d.ts.map +0 -1
  209. package/dist/__tests__/decompile.test.js +0 -102
  210. package/dist/__tests__/decompile.test.js.map +0 -1
  211. package/dist/__tests__/hash.test.d.ts +0 -2
  212. package/dist/__tests__/hash.test.d.ts.map +0 -1
  213. package/dist/__tests__/hash.test.js +0 -44
  214. package/dist/__tests__/hash.test.js.map +0 -1
  215. package/dist/__tests__/integration.test.d.ts +0 -2
  216. package/dist/__tests__/integration.test.d.ts.map +0 -1
  217. package/dist/__tests__/integration.test.js +0 -284
  218. package/dist/__tests__/integration.test.js.map +0 -1
  219. package/dist/__tests__/skill-renderer.test.d.ts +0 -2
  220. package/dist/__tests__/skill-renderer.test.d.ts.map +0 -1
  221. package/dist/__tests__/skill-renderer.test.js +0 -68
  222. package/dist/__tests__/skill-renderer.test.js.map +0 -1
@@ -6,11 +6,12 @@
6
6
  * An MCP where the context is you. Every agent that connects gets structured,
7
7
  * portable identity context: who you are, how you work, what you sound like.
8
8
  *
9
- * Resources: identity, profile sections, preferences, voice, directives,
10
- * memories, projects, skills
11
- * Tools: add_memory, update_section, search_memories, use_skill,
12
- * compile_bundle, push_bundle, get_project_context,
13
- * add_source, create_context_link, list_projects, get_remote_status
9
+ * Resources: identity, agent brief, profile sections, preferences, voice,
10
+ * directives, memories, projects, skills
11
+ * Tools: whoami, get_agent_brief, add_memory, update_section,
12
+ * search_memories, use_skill, compile_bundle, push_bundle, get_project_context,
13
+ * add_source, create_context_link, list_projects, get_remote_status,
14
+ * get_stack_manifest, get_stack_capabilities, route_stack_request, smoke_stack
14
15
  *
15
16
  * Transport: stdio (for Claude Code, Cursor, any MCP client)
16
17
  *
@@ -52,14 +53,32 @@ var __importStar = (this && this.__importStar) || (function () {
52
53
  };
53
54
  })();
54
55
  Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.MEMORY_CATEGORIES = exports.DURABLE_MEMORY_CATEGORIES = exports.AGENT_BRIEF_MEMORY_CAP = void 0;
57
+ exports.activeBundleExists = activeBundleExists;
58
+ exports.getInstalledSkills = getInstalledSkills;
59
+ exports.orderBriefMemories = orderBriefMemories;
60
+ exports.briefOneLine = briefOneLine;
61
+ exports.fetchMemoriesEnvelope = fetchMemoriesEnvelope;
62
+ exports.fetchPrivateContextEnvelope = fetchPrivateContextEnvelope;
63
+ exports.apiRequest = apiRequest;
64
+ exports.setMcpClientName = setMcpClientName;
65
+ exports.resolveAgentName = resolveAgentName;
66
+ exports.buildWhoamiSummaryFromYouJson = buildWhoamiSummaryFromYouJson;
67
+ exports.buildAgentBrief = buildAgentBrief;
68
+ exports.formatAgentBriefMarkdown = formatAgentBriefMarkdown;
55
69
  exports.startMcpServer = startMcpServer;
56
70
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
57
71
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
58
72
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
73
+ // T14: CLI tool registry — single source of truth for future tool additions.
74
+ const registry_js_1 = require("./registry.js");
59
75
  const fs = __importStar(require("fs"));
60
76
  const path = __importStar(require("path"));
61
77
  const config_1 = require("../lib/config");
62
78
  const project_1 = require("../lib/project");
79
+ const projectContext_1 = require("../lib/projectContext");
80
+ const youstack_1 = require("../lib/youstack");
81
+ const MCP_SERVER_VERSION = "0.6.23";
63
82
  function getCurrentProject() {
64
83
  const root = (0, project_1.findProjectsRoot)();
65
84
  if (!root)
@@ -89,13 +108,27 @@ function readJsonOr(filePath, fallback) {
89
108
  }
90
109
  }
91
110
  function getBundleDir() {
92
- return (0, config_1.getLocalBundleDir)();
111
+ const localDir = (0, config_1.getLocalBundleDir)();
112
+ if ((0, config_1.bundleLooksInitialized)(localDir))
113
+ return localDir;
114
+ const homeDir = (0, config_1.getHomeBundleDir)();
115
+ if ((0, config_1.bundleLooksInitialized)(homeDir))
116
+ return homeDir;
117
+ return localDir;
118
+ }
119
+ function activeBundleExists() {
120
+ return (0, config_1.bundleLooksInitialized)(getBundleDir());
93
121
  }
94
122
  function getYouJson() {
95
123
  const bundleDir = getBundleDir();
96
124
  const youJsonPath = path.join(bundleDir, "you.json");
97
125
  return readJsonOr(youJsonPath, {});
98
126
  }
127
+ function asRecord(value) {
128
+ return typeof value === "object" && value !== null && !Array.isArray(value)
129
+ ? value
130
+ : {};
131
+ }
99
132
  function getYouMd() {
100
133
  const bundleDir = getBundleDir();
101
134
  return readFileOr(path.join(bundleDir, "you.md"), "");
@@ -145,9 +178,61 @@ function getInstalledSkills() {
145
178
  }
146
179
  return skills;
147
180
  }
148
- async function fetchMemories(category, limit) {
149
- if (!(0, config_1.isAuthenticated)())
150
- return [];
181
+ function tryLoadCurrentYouStack(inputPath) {
182
+ try {
183
+ return (0, youstack_1.loadYouStackManifest)(inputPath);
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ // Mirror of the canonical convex/lib/agentContext.ts assembly. The CLI is a
190
+ // separately published npm package and cannot import the convex module at
191
+ // runtime; cli/src/__tests__/agent-context-parity.test.ts asserts the two
192
+ // implementations never drift (cap, durable set, ordering, one-line collapse,
193
+ // identity summary). Exported for that parity test.
194
+ /** Max memories rendered into the agent brief (parity with hosted MCP). */
195
+ exports.AGENT_BRIEF_MEMORY_CAP = 20;
196
+ /** Categories considered durable — surfaced ahead of newer ephemeral notes. */
197
+ exports.DURABLE_MEMORY_CATEGORIES = new Set([
198
+ "preference",
199
+ "decision",
200
+ "goal",
201
+ "fact",
202
+ ]);
203
+ function asBriefMemories(memories) {
204
+ return memories.filter((m) => !!m &&
205
+ typeof m === "object" &&
206
+ typeof m.category === "string" &&
207
+ typeof m.content === "string");
208
+ }
209
+ /** Durable categories first, preserving server order (newest-first) within each group. */
210
+ function orderBriefMemories(memories) {
211
+ const durable = [];
212
+ const rest = [];
213
+ for (const memory of memories) {
214
+ (exports.DURABLE_MEMORY_CATEGORIES.has(memory.category) ? durable : rest).push(memory);
215
+ }
216
+ return [...durable, ...rest];
217
+ }
218
+ /** Collapse memory content to a single line capped at `max` chars. */
219
+ function briefOneLine(content, max = 200) {
220
+ const collapsed = content.replace(/\s+/g, " ").trim();
221
+ return collapsed.length > max ? `${collapsed.slice(0, max - 1).trimEnd()}…` : collapsed;
222
+ }
223
+ async function fetchMemoriesEnvelope(category, limit) {
224
+ if (!(0, config_1.isAuthenticated)()) {
225
+ return {
226
+ readiness: {
227
+ status: "auth_required",
228
+ ready: false,
229
+ reason: "Memory search needs an authenticated You.md session or API key.",
230
+ fallback: "Use public identity, project-context files, or run `youmd login` before retrying protected memory search.",
231
+ },
232
+ memories: [],
233
+ count: 0,
234
+ };
235
+ }
151
236
  const config = (0, config_1.readGlobalConfig)();
152
237
  const siteUrl = (0, config_1.getConvexSiteUrl)();
153
238
  const params = new URLSearchParams();
@@ -160,15 +245,241 @@ async function fetchMemories(category, limit) {
160
245
  headers: { Authorization: `Bearer ${config.token}` },
161
246
  signal: AbortSignal.timeout(10000),
162
247
  });
163
- if (!res.ok)
164
- return [];
248
+ if (!res.ok) {
249
+ return {
250
+ readiness: {
251
+ status: "unavailable",
252
+ ready: false,
253
+ reason: `Memory search failed with HTTP ${res.status}.`,
254
+ fallback: "Use project-context files or local stack files first, then retry protected memory search when auth/server access is healthy.",
255
+ },
256
+ memories: [],
257
+ count: 0,
258
+ };
259
+ }
165
260
  const data = await res.json();
166
- return (data.memories || data) || [];
261
+ const memories = (data.memories || data) || [];
262
+ return {
263
+ readiness: {
264
+ status: "ready",
265
+ ready: true,
266
+ reason: memories.length > 0 ? "Protected memory search succeeded." : "Protected memory search succeeded but found no matching memories.",
267
+ fallback: memories.length > 0 ? "None needed." : "If nothing matched, fall back to public identity, project-context files, or a narrower keyword query.",
268
+ },
269
+ memories,
270
+ count: memories.length,
271
+ };
167
272
  }
168
273
  catch {
169
- return [];
274
+ return {
275
+ readiness: {
276
+ status: "unavailable",
277
+ ready: false,
278
+ reason: "Memory search could not reach the protected You.md memories endpoint.",
279
+ fallback: "Use public identity, project-context files, or local stack context first, then retry protected memory search later.",
280
+ },
281
+ memories: [],
282
+ count: 0,
283
+ };
170
284
  }
171
285
  }
286
+ async function fetchPrivateContextEnvelope() {
287
+ if (!(0, config_1.isAuthenticated)()) {
288
+ return {
289
+ readiness: {
290
+ status: "auth_required",
291
+ ready: false,
292
+ reason: "Private context needs an authenticated You.md session or API key.",
293
+ fallback: "Use local stack files, project-context files, public identity, or run `youmd login` before retrying protected private-context access.",
294
+ },
295
+ privateContext: null,
296
+ summary: {
297
+ hasNotes: false,
298
+ linkCount: 0,
299
+ projectCount: 0,
300
+ },
301
+ };
302
+ }
303
+ const config = (0, config_1.readGlobalConfig)();
304
+ const siteUrl = (0, config_1.getConvexSiteUrl)();
305
+ try {
306
+ const res = await fetch(`${siteUrl}/api/v1/me/private`, {
307
+ headers: { Authorization: `Bearer ${config.token}` },
308
+ signal: AbortSignal.timeout(10000),
309
+ });
310
+ if (!res.ok) {
311
+ return {
312
+ readiness: {
313
+ status: "unavailable",
314
+ ready: false,
315
+ reason: `Private context fetch failed with HTTP ${res.status}.`,
316
+ fallback: "Use local stack files, project-context files, or public identity first, then retry protected private-context access when auth/server access is healthy.",
317
+ },
318
+ privateContext: null,
319
+ summary: {
320
+ hasNotes: false,
321
+ linkCount: 0,
322
+ projectCount: 0,
323
+ },
324
+ };
325
+ }
326
+ const privateContext = await res.json();
327
+ const links = privateContext && typeof privateContext === "object" && privateContext.internalLinks && typeof privateContext.internalLinks === "object"
328
+ ? Object.keys(privateContext.internalLinks).length
329
+ : 0;
330
+ const projects = Array.isArray(privateContext?.privateProjects) ? privateContext.privateProjects.length : 0;
331
+ const hasNotes = typeof privateContext?.privateNotes === "string" && privateContext.privateNotes.trim().length > 0;
332
+ return {
333
+ readiness: {
334
+ status: "ready",
335
+ ready: true,
336
+ reason: privateContext
337
+ ? "Protected private context fetch succeeded."
338
+ : "Protected private context fetch succeeded but no private context is stored yet.",
339
+ fallback: privateContext
340
+ ? "None needed."
341
+ : "If private context is empty, fall back to local stack files, project-context files, or public identity.",
342
+ },
343
+ privateContext,
344
+ summary: {
345
+ hasNotes,
346
+ linkCount: links,
347
+ projectCount: projects,
348
+ },
349
+ };
350
+ }
351
+ catch {
352
+ return {
353
+ readiness: {
354
+ status: "unavailable",
355
+ ready: false,
356
+ reason: "Private context fetch could not reach the protected You.md private-context endpoint.",
357
+ fallback: "Use local stack files, project-context files, or public identity first, then retry protected private-context access later.",
358
+ },
359
+ privateContext: null,
360
+ summary: {
361
+ hasNotes: false,
362
+ linkCount: 0,
363
+ projectCount: 0,
364
+ },
365
+ };
366
+ }
367
+ }
368
+ function fetchProjectContextEnvelope(projectName) {
369
+ // Reads route through the single project-context engine: the repo
370
+ // project-context/ directory is overlaid on the global store, repo wins
371
+ // on file conflicts, and readers see the union (PRODUCT-AUDIT #14).
372
+ if (projectName) {
373
+ const ctx = (0, projectContext_1.readMergedProjectContext)({ projectName });
374
+ if (!ctx) {
375
+ const hasAnyRoot = !!(0, project_1.findProjectsRoot)() || !!(0, projectContext_1.findRepoContextRoot)();
376
+ if (!hasAnyRoot) {
377
+ return {
378
+ readiness: {
379
+ status: "not_found",
380
+ ready: false,
381
+ reason: "No projects directory was found for the requested named project.",
382
+ fallback: "Use local project-context files from the current repo, public identity, or a current project detection flow before retrying a named project lookup.",
383
+ },
384
+ project: {
385
+ name: projectName,
386
+ source: "named",
387
+ },
388
+ projectContext: null,
389
+ };
390
+ }
391
+ return {
392
+ readiness: {
393
+ status: "not_found",
394
+ ready: false,
395
+ reason: `Project context for ${projectName} was not found or is incomplete.`,
396
+ fallback: "Use local repo instructions, project-context files, or confirm the named project exists before retrying.",
397
+ },
398
+ project: {
399
+ name: projectName,
400
+ source: "named",
401
+ },
402
+ projectContext: null,
403
+ };
404
+ }
405
+ return {
406
+ readiness: {
407
+ status: "ready",
408
+ ready: true,
409
+ reason: `Project context for ${projectName} is available.`,
410
+ fallback: "None needed.",
411
+ },
412
+ project: {
413
+ name: projectName,
414
+ source: "named",
415
+ },
416
+ projectContext: ctx,
417
+ };
418
+ }
419
+ const current = getCurrentProject();
420
+ if (!current) {
421
+ // No managed project matches the cwd — the repo overlay alone may
422
+ // still provide the context union.
423
+ const repoCtx = (0, projectContext_1.readMergedProjectContext)({});
424
+ if (repoCtx) {
425
+ return {
426
+ readiness: {
427
+ status: "ready",
428
+ ready: true,
429
+ reason: `Project context for ${repoCtx.meta.name} is available.`,
430
+ fallback: "None needed.",
431
+ },
432
+ project: {
433
+ name: repoCtx.meta.name,
434
+ source: "current",
435
+ },
436
+ projectContext: repoCtx,
437
+ };
438
+ }
439
+ return {
440
+ readiness: {
441
+ status: "not_found",
442
+ ready: false,
443
+ reason: "No current project was detected from the working directory.",
444
+ fallback: "Use local project-context files in the current repo, pass an explicit project name, or fall back to public identity and stack files.",
445
+ },
446
+ project: {
447
+ name: null,
448
+ source: "current",
449
+ },
450
+ projectContext: null,
451
+ };
452
+ }
453
+ const ctx = (0, projectContext_1.readMergedProjectContext)({ projectName: current.name }) || (0, project_1.readProjectContext)(current.dir);
454
+ if (!ctx) {
455
+ return {
456
+ readiness: {
457
+ status: "not_found",
458
+ ready: false,
459
+ reason: `Project context for ${current.name} was detected but could not be read.`,
460
+ fallback: "Use repo-local instructions and project-context markdown files directly, then repair the named project bundle before retrying.",
461
+ },
462
+ project: {
463
+ name: current.name,
464
+ source: "current",
465
+ },
466
+ projectContext: null,
467
+ };
468
+ }
469
+ return {
470
+ readiness: {
471
+ status: "ready",
472
+ ready: true,
473
+ reason: `Project context for ${current.name} is available.`,
474
+ fallback: "None needed.",
475
+ },
476
+ project: {
477
+ name: current.name,
478
+ source: "current",
479
+ },
480
+ projectContext: ctx,
481
+ };
482
+ }
172
483
  async function apiRequest(path, opts) {
173
484
  const config = (0, config_1.readGlobalConfig)();
174
485
  const siteUrl = (0, config_1.getConvexSiteUrl)();
@@ -183,6 +494,36 @@ async function apiRequest(path, opts) {
183
494
  });
184
495
  return res.json();
185
496
  }
497
+ /**
498
+ * Agent attribution. Resolution order:
499
+ * 1. YOUMD_AGENT_NAME env (set by generated MCP configs per host)
500
+ * 2. MCP clientInfo.name from the initialize handshake
501
+ * 3. "MCP Agent" — honest unknown instead of misattributing to one host
502
+ */
503
+ let mcpClientName = null;
504
+ function setMcpClientName(name) {
505
+ mcpClientName = typeof name === "string" && name.trim() ? name.trim() : null;
506
+ }
507
+ /** Friendly display names for well-known MCP clientInfo.name values. */
508
+ const CLIENT_NAME_MAP = {
509
+ "claude-code": "Claude Code",
510
+ "claude": "Claude Code",
511
+ "claude-desktop": "Claude Desktop",
512
+ "cursor": "Cursor",
513
+ "cursor-vscode": "Cursor",
514
+ "codex": "Codex",
515
+ "codex-cli": "Codex",
516
+ "windsurf": "Windsurf",
517
+ };
518
+ function resolveAgentName() {
519
+ const envName = process.env.YOUMD_AGENT_NAME?.trim();
520
+ if (envName)
521
+ return envName;
522
+ if (mcpClientName) {
523
+ return CLIENT_NAME_MAP[mcpClientName.toLowerCase()] || mcpClientName;
524
+ }
525
+ return "MCP Agent";
526
+ }
186
527
  /**
187
528
  * Fire-and-forget activity logger. Logs every MCP tool call so the user can
188
529
  * see which agents are using their you.md identity via `youmd logs` or the
@@ -199,10 +540,10 @@ async function logMcpActivity(action, resource, details) {
199
540
  headers: {
200
541
  Authorization: `Bearer ${config.token}`,
201
542
  "Content-Type": "application/json",
202
- "User-Agent": "youmd-mcp/0.6.19",
543
+ "User-Agent": "youmd-mcp/0.6.23",
203
544
  },
204
545
  body: JSON.stringify({
205
- agentName: process.env.YOUMD_AGENT_NAME || "Claude Code",
546
+ agentName: resolveAgentName(),
206
547
  agentSource: "mcp",
207
548
  action,
208
549
  resource,
@@ -217,7 +558,7 @@ async function logMcpActivity(action, resource, details) {
217
558
  }
218
559
  }
219
560
  // Valid memory categories enforced by add_memory
220
- const MEMORY_CATEGORIES = [
561
+ exports.MEMORY_CATEGORIES = [
221
562
  "fact",
222
563
  "preference",
223
564
  "decision",
@@ -233,24 +574,43 @@ const MEMORY_CATEGORIES = [
233
574
  * first call an agent makes so it can orient on the user in <1 tool round.
234
575
  */
235
576
  function buildWhoamiSummary() {
236
- const youJson = getYouJson();
237
- const identity = youJson.identity || {};
238
- const preferences = youJson.preferences || {};
239
- const agentPrefs = preferences.agent || {};
240
- const directives = youJson.agent_directives || {};
577
+ return buildWhoamiSummaryFromYouJson(getYouJson());
578
+ }
579
+ /**
580
+ * Pure whoami summary from a youJson bundle. Mirror of the canonical
581
+ * extractIdentityCore + renderIdentitySummary in convex/lib/agentContext.ts;
582
+ * exported for the agent-context parity test.
583
+ */
584
+ function buildWhoamiSummaryFromYouJson(youJson) {
585
+ const identity = asRecord(youJson.identity);
586
+ const preferences = asRecord(youJson.preferences);
587
+ const agentPrefs = asRecord(preferences.agent);
588
+ const directives = asRecord(youJson.agent_directives);
241
589
  const projects = Array.isArray(youJson.projects) ? youJson.projects : [];
242
- const name = identity.name || youJson.username || "(unknown)";
243
- const role = identity.tagline || identity.bio?.short || "";
244
- const stack = directives.default_stack || "";
245
- const tone = agentPrefs.tone || "";
246
- const avoidList = Array.isArray(agentPrefs.avoid) ? agentPrefs.avoid : [];
590
+ const bio = asRecord(identity.bio);
591
+ const name = typeof identity.name === "string"
592
+ ? identity.name
593
+ : typeof youJson.username === "string" ? youJson.username : "(unknown)";
594
+ const role = typeof identity.tagline === "string"
595
+ ? identity.tagline
596
+ : typeof bio.short === "string" ? bio.short : "";
597
+ const stack = typeof directives.default_stack === "string" ? directives.default_stack : "";
598
+ const tone = typeof agentPrefs.tone === "string" ? agentPrefs.tone : "";
599
+ const avoidList = Array.isArray(agentPrefs.avoid)
600
+ ? agentPrefs.avoid.filter((item) => typeof item === "string")
601
+ : [];
247
602
  const avoid = avoidList.join(", ");
248
603
  const topProjects = projects
249
604
  .slice(0, 3)
250
- .map((p) => (typeof p === "string" ? p : p?.name || ""))
605
+ .map((project) => {
606
+ if (typeof project === "string")
607
+ return project;
608
+ const record = asRecord(project);
609
+ return typeof record.name === "string" ? record.name : "";
610
+ })
251
611
  .filter(Boolean)
252
612
  .join(", ");
253
- const goal = directives.current_goal || "";
613
+ const goal = typeof directives.current_goal === "string" ? directives.current_goal : "";
254
614
  const lines = [];
255
615
  lines.push(`Name: ${name}`);
256
616
  if (role)
@@ -282,67 +642,317 @@ function buildIdentityMarkdown() {
282
642
  return md;
283
643
  // Fallback: synthesize from youJson if you.md is missing
284
644
  const youJson = getYouJson();
285
- const identity = youJson.identity || {};
645
+ const identity = asRecord(youJson.identity);
646
+ const bio = asRecord(identity.bio);
286
647
  const parts = [];
287
- parts.push(`# ${identity.name || youJson.username || "Identity"}`);
288
- if (identity.tagline)
648
+ const title = typeof identity.name === "string"
649
+ ? identity.name
650
+ : typeof youJson.username === "string" ? youJson.username : "Identity";
651
+ parts.push(`# ${title}`);
652
+ if (typeof identity.tagline === "string")
289
653
  parts.push(identity.tagline);
290
- if (identity.bio?.long || identity.bio?.medium || identity.bio?.short) {
291
- parts.push(`\n## About\n\n${identity.bio.long || identity.bio.medium || identity.bio.short}`);
654
+ const about = typeof bio.long === "string"
655
+ ? bio.long
656
+ : typeof bio.medium === "string"
657
+ ? bio.medium
658
+ : typeof bio.short === "string" ? bio.short : "";
659
+ if (about) {
660
+ parts.push(`\n## About\n\n${about}`);
292
661
  }
293
662
  return parts.join("\n") + "\n";
294
663
  }
295
- /**
296
- * Find the nearest project-context/ directory by walking up from cwd.
297
- * Also treats a `.youmd-project` marker file as a valid project root.
298
- * Returns the directory that contains project-context/ or the marker.
299
- */
300
- function findLocalProjectContextRoot(startDir = process.cwd()) {
301
- let dir = startDir;
302
- // Walk up at most 8 levels to avoid runaway searches
303
- for (let i = 0; i < 8; i++) {
304
- const contextDir = path.join(dir, "project-context");
305
- const marker = path.join(dir, ".youmd-project");
306
- if (fs.existsSync(contextDir) && fs.statSync(contextDir).isDirectory()) {
307
- return dir;
308
- }
309
- if (fs.existsSync(marker)) {
310
- return dir;
311
- }
312
- const parent = path.dirname(dir);
313
- if (parent === dir)
314
- break;
315
- dir = parent;
316
- }
317
- return null;
318
- }
319
664
  /**
320
665
  * Returns true if the current cwd has local project context available
321
666
  * (either via project-context/ directory or a .youmd-project marker).
667
+ * Detection and reads route through the single project-context engine
668
+ * (lib/projectContext.ts) — repo files overlay the global store.
322
669
  */
323
670
  function hasLocalProjectContext() {
324
- return findLocalProjectContextRoot() !== null;
671
+ return (0, projectContext_1.findRepoContextRoot)() !== null;
325
672
  }
326
673
  /**
327
- * Read a single project-context markdown file. Tries project-context/<name>.md
328
- * in the detected project root. Returns empty string if not found.
674
+ * Read a single project-context markdown file via the engine's overlay
675
+ * union (repo project-context/ wins over the global overlay; legacy
676
+ * top-level repo files still resolve). Returns empty string if not found.
329
677
  */
330
678
  function readLocalProjectContextFile(name) {
331
- const root = findLocalProjectContextRoot();
332
- if (!root)
333
- return "";
679
+ const resolved = (0, projectContext_1.resolveProjectContext)();
680
+ return (0, projectContext_1.readContextFile)(resolved, name)?.content || "";
681
+ }
682
+ function firstContentLines(content, limit = 3) {
683
+ const lines = content
684
+ .split("\n")
685
+ .map((line) => line.trim())
686
+ .filter((line) => line && !line.startsWith("---") && !line.startsWith("#") && !line.startsWith("<!--"));
687
+ return lines.slice(0, limit).join(" ").slice(0, 280);
688
+ }
689
+ function readBriefFile(root, relativePath) {
690
+ const filePath = path.join(root, relativePath);
691
+ if (!fs.existsSync(filePath)) {
692
+ return { path: relativePath, exists: false };
693
+ }
694
+ try {
695
+ const stat = fs.statSync(filePath);
696
+ if (stat.isDirectory()) {
697
+ return { path: relativePath, exists: true, summary: "directory" };
698
+ }
699
+ const content = fs.readFileSync(filePath, "utf-8");
700
+ return {
701
+ path: relativePath,
702
+ exists: true,
703
+ chars: content.length,
704
+ summary: firstContentLines(content),
705
+ };
706
+ }
707
+ catch {
708
+ return { path: relativePath, exists: true, summary: "unreadable" };
709
+ }
710
+ }
711
+ function readProjectFile(root, relativePath) {
712
+ const filePath = path.join(root, relativePath);
713
+ return readFileOr(filePath, "");
714
+ }
715
+ function collectInstructionFiles(root) {
334
716
  const candidates = [
335
- path.join(root, "project-context", `${name}.md`),
336
- path.join(root, "project-context", `${name.toUpperCase()}.md`),
337
- path.join(root, `${name}.md`),
338
- path.join(root, `${name.toUpperCase()}.md`),
717
+ "AGENTS.md",
718
+ "CLAUDE.md",
719
+ ".you/AGENT.md",
720
+ ".you/STACK-MAP.md",
721
+ ".cursor/rules/youmd.mdc",
722
+ ".cursor/rules/youmd.md",
723
+ ".claude/skills/youmd",
724
+ ".codex/skills/youmd",
339
725
  ];
340
- for (const candidate of candidates) {
341
- if (fs.existsSync(candidate)) {
342
- return readFileOr(candidate, "");
726
+ return candidates
727
+ .map((candidate) => readBriefFile(root, candidate))
728
+ .filter((file) => file.exists);
729
+ }
730
+ function collectContextFiles(root) {
731
+ const candidates = [
732
+ "project-context/CURRENT_STATE.md",
733
+ "project-context/feature-requests-active.md",
734
+ "project-context/TODO.md",
735
+ "project-context/FEATURES.md",
736
+ "project-context/CHANGELOG.md",
737
+ "project-context/PRD.md",
738
+ "project-context/ARCHITECTURE.md",
739
+ "project-context/STYLE_GUIDE.md",
740
+ "project-context/BRANDING.md",
741
+ ];
742
+ return candidates
743
+ .map((candidate) => readBriefFile(root, candidate))
744
+ .filter((file) => file.exists);
745
+ }
746
+ function extractOpenTodos(content, limit = 8) {
747
+ const todos = [];
748
+ for (const line of content.split("\n")) {
749
+ const match = line.match(/^\s*-\s+\[\s\]\s+(.+)$/);
750
+ if (match)
751
+ todos.push(match[1].trim());
752
+ if (todos.length >= limit)
753
+ break;
754
+ }
755
+ return todos;
756
+ }
757
+ function extractActiveRequests(content, limit = 6) {
758
+ const requests = [];
759
+ const chunks = content.split(/\n(?=###\s+)/g);
760
+ for (const chunk of chunks) {
761
+ const titleMatch = chunk.match(/^###\s+(.+)$/m);
762
+ if (!titleMatch)
763
+ continue;
764
+ const statusMatch = chunk.match(/\*\*Status:\*\*\s*([^\n]+)/);
765
+ const status = statusMatch?.[1]?.trim() || "UNKNOWN";
766
+ if (/^(DONE|VERIFIED BY USER|DECIDED|N\/A)/i.test(status))
767
+ continue;
768
+ requests.push(`${titleMatch[1].trim()} [${status}]`);
769
+ if (requests.length >= limit)
770
+ break;
771
+ }
772
+ return requests;
773
+ }
774
+ function extractSectionBullets(content, heading, limit = 6) {
775
+ const lines = content.split("\n");
776
+ const start = lines.findIndex((line) => line.trim().toLowerCase() === `## ${heading.toLowerCase()}`);
777
+ if (start < 0)
778
+ return [];
779
+ const bullets = [];
780
+ for (const line of lines.slice(start + 1)) {
781
+ if (/^##\s+/.test(line))
782
+ break;
783
+ const match = line.match(/^\s*-\s+(.+)$/);
784
+ if (match)
785
+ bullets.push(match[1].trim());
786
+ if (bullets.length >= limit)
787
+ break;
788
+ }
789
+ return bullets;
790
+ }
791
+ function buildProjectBrief() {
792
+ const detected = (0, config_1.detectProjectContext)();
793
+ const fallbackRoot = (0, projectContext_1.findRepoContextRoot)();
794
+ const root = detected?.root || fallbackRoot;
795
+ if (!root)
796
+ return null;
797
+ const activeRequests = extractActiveRequests(readProjectFile(root, "project-context/feature-requests-active.md"));
798
+ const openTodos = extractOpenTodos(readProjectFile(root, "project-context/TODO.md"));
799
+ const knownIssues = extractSectionBullets(readProjectFile(root, "project-context/CURRENT_STATE.md"), "Known Issues");
800
+ return {
801
+ name: detected?.name || path.basename(root),
802
+ root,
803
+ marker: detected?.marker || "project-context",
804
+ instructionFiles: collectInstructionFiles(root),
805
+ contextFiles: collectContextFiles(root),
806
+ activeRequests,
807
+ openTodos,
808
+ knownIssues,
809
+ };
810
+ }
811
+ async function buildAgentBrief(options = {}) {
812
+ const project = buildProjectBrief();
813
+ const installedSkills = getInstalledSkills().map((skill) => skill.name).sort();
814
+ const nextMoves = [];
815
+ if (project?.activeRequests.length) {
816
+ nextMoves.push(`Address the top active request: ${project.activeRequests[0]}`);
817
+ }
818
+ if (project?.openTodos.length) {
819
+ nextMoves.push(`Use the first open TODO as backlog context: ${project.openTodos[0]}`);
820
+ }
821
+ if (project && project.contextFiles.length === 0) {
822
+ nextMoves.push("Bootstrap project-context with: youmd skill init-project --mode additive");
823
+ }
824
+ if (!installedSkills.includes("youstack-start")) {
825
+ nextMoves.push("Install the YouStack starter skill with: youmd skill install youstack-start");
826
+ }
827
+ if (nextMoves.length === 0) {
828
+ nextMoves.push("Read the project instructions, pick the highest-signal task, and act end-to-end.");
829
+ }
830
+ const brief = {
831
+ generatedAt: new Date().toISOString(),
832
+ user: {
833
+ summary: buildWhoamiSummary(),
834
+ authenticated: (0, config_1.isAuthenticated)(),
835
+ bundleDir: getBundleDir(),
836
+ bundleInitialized: activeBundleExists(),
837
+ },
838
+ environment: {
839
+ cwd: process.cwd(),
840
+ mcpVersion: MCP_SERVER_VERSION,
841
+ },
842
+ project,
843
+ skills: {
844
+ installed: installedSkills,
845
+ recommended: [
846
+ "youstack-start",
847
+ "project-context-init",
848
+ "proactive-context-fill",
849
+ "voice-sync",
850
+ "you-logs",
851
+ ],
852
+ },
853
+ nextMoves: nextMoves.slice(0, 5),
854
+ reminders: [
855
+ "Read the whole user request before acting; split multi-part asks into tracked items.",
856
+ "Prefer local project instructions and project-context files before asking for context.",
857
+ "When making changes, update TODO, FEATURES, CHANGELOG, active requests, and PROMPTS before closing.",
858
+ "Do not claim files were written or pushed unless the tool call actually succeeded.",
859
+ ],
860
+ };
861
+ // Memories are included by default (parity with hosted get_agent_brief:
862
+ // the brief must surface memory CONTENT, not a count — opt out explicitly).
863
+ if (options.includeMemories !== false) {
864
+ const memoryResult = await fetchMemoriesEnvelope(undefined, exports.AGENT_BRIEF_MEMORY_CAP * 3);
865
+ brief.memoriesReadiness = memoryResult.readiness;
866
+ brief.memories = orderBriefMemories(asBriefMemories(memoryResult.memories))
867
+ .slice(0, exports.AGENT_BRIEF_MEMORY_CAP);
868
+ }
869
+ return brief;
870
+ }
871
+ function renderFileList(files) {
872
+ if (files.length === 0)
873
+ return "- none detected";
874
+ return files.map((file) => {
875
+ const detail = file.summary ? ` - ${file.summary}` : "";
876
+ const chars = typeof file.chars === "number" ? ` (${file.chars} chars)` : "";
877
+ return `- ${file.path}${chars}${detail}`;
878
+ }).join("\n");
879
+ }
880
+ function renderList(items, empty) {
881
+ if (items.length === 0)
882
+ return `- ${empty}`;
883
+ return items.map((item) => `- ${item}`).join("\n");
884
+ }
885
+ function formatAgentBriefMarkdown(brief, maxChars = 6000) {
886
+ const project = brief.project;
887
+ const lines = [];
888
+ lines.push("# YouStack Agent Brief");
889
+ lines.push("");
890
+ lines.push("## User");
891
+ lines.push(brief.user.summary || "(no identity summary available)");
892
+ lines.push("");
893
+ lines.push(`- authenticated: ${brief.user.authenticated ? "yes" : "no"}`);
894
+ lines.push(`- bundle: ${brief.user.bundleInitialized ? brief.user.bundleDir : "not initialized"}`);
895
+ lines.push(`- cwd: ${brief.environment.cwd}`);
896
+ lines.push("");
897
+ lines.push("## Project");
898
+ if (project) {
899
+ lines.push(`- name: ${project.name}`);
900
+ lines.push(`- root: ${project.root}`);
901
+ lines.push(`- marker: ${project.marker}`);
902
+ lines.push("");
903
+ lines.push("### Instruction Files");
904
+ lines.push(renderFileList(project.instructionFiles));
905
+ lines.push("");
906
+ lines.push("### Project Context");
907
+ lines.push(renderFileList(project.contextFiles));
908
+ lines.push("");
909
+ lines.push("### Active Requests");
910
+ lines.push(renderList(project.activeRequests, "none detected"));
911
+ lines.push("");
912
+ lines.push("### Open TODOs");
913
+ lines.push(renderList(project.openTodos, "none detected"));
914
+ if (project.knownIssues.length > 0) {
915
+ lines.push("");
916
+ lines.push("### Known Issues");
917
+ lines.push(renderList(project.knownIssues, "none detected"));
343
918
  }
344
919
  }
345
- return "";
920
+ else {
921
+ lines.push("- no local project detected");
922
+ }
923
+ lines.push("");
924
+ lines.push("## Skills");
925
+ lines.push(`- installed: ${brief.skills.installed.length > 0 ? brief.skills.installed.join(", ") : "none"}`);
926
+ lines.push(`- recommended: ${brief.skills.recommended.join(", ")}`);
927
+ lines.push("");
928
+ if (brief.memories) {
929
+ // Render actual memory content lines, not a bare count (parity with the
930
+ // hosted get_agent_brief).
931
+ lines.push(`## Memories (${brief.memories.length})`);
932
+ if (brief.memories.length === 0) {
933
+ lines.push("- none recorded yet");
934
+ }
935
+ else {
936
+ for (const memory of brief.memories) {
937
+ lines.push(`- [${memory.category}] ${briefOneLine(memory.content)}`);
938
+ }
939
+ }
940
+ if (brief.memoriesReadiness && !brief.memoriesReadiness.ready) {
941
+ lines.push(`- readiness: ${brief.memoriesReadiness.status} (${brief.memoriesReadiness.reason})`);
942
+ lines.push(`- fallback: ${brief.memoriesReadiness.fallback}`);
943
+ }
944
+ lines.push("");
945
+ }
946
+ lines.push("## Next Moves");
947
+ lines.push(renderList(brief.nextMoves, "read instructions, then act"));
948
+ lines.push("");
949
+ lines.push("## Reminders");
950
+ lines.push(renderList(brief.reminders, "none"));
951
+ let markdown = lines.join("\n");
952
+ if (markdown.length > maxChars) {
953
+ markdown = markdown.slice(0, Math.max(0, maxChars - 32)).trimEnd() + "\n\n[truncated]";
954
+ }
955
+ return markdown;
346
956
  }
347
957
  // ---------------------------------------------------------------------------
348
958
  // MCP Server
@@ -350,13 +960,18 @@ function readLocalProjectContextFile(name) {
350
960
  async function startMcpServer() {
351
961
  const server = new index_js_1.Server({
352
962
  name: "youmd",
353
- version: "0.5.0",
963
+ version: MCP_SERVER_VERSION,
354
964
  }, {
355
965
  capabilities: {
356
966
  resources: {},
357
967
  tools: {},
358
968
  },
359
969
  });
970
+ // Capture clientInfo.name from the initialize handshake for activity
971
+ // attribution (fallback when YOUMD_AGENT_NAME isn't set by the host config).
972
+ server.oninitialized = () => {
973
+ setMcpClientName(server.getClientVersion()?.name);
974
+ };
360
975
  // ── LIST RESOURCES ─────────────────────────────────────────────────
361
976
  server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
362
977
  const resources = [];
@@ -373,6 +988,27 @@ async function startMcpServer() {
373
988
  description: "Human-readable you.md identity (you.md format)",
374
989
  mimeType: "text/markdown",
375
990
  });
991
+ resources.push({
992
+ uri: "youmd://agent/brief",
993
+ name: "agent-brief",
994
+ description: "YouStack startup brief for local agents: compact identity, project instructions, active requests, open TODOs, installed skills, and next moves",
995
+ mimeType: "text/markdown",
996
+ });
997
+ const currentStack = tryLoadCurrentYouStack();
998
+ if (currentStack) {
999
+ resources.push({
1000
+ uri: "youmd://stacks/current/manifest",
1001
+ name: "stacks/current/manifest",
1002
+ description: "Current local YouStack manifest discovered from cwd",
1003
+ mimeType: "application/json",
1004
+ });
1005
+ resources.push({
1006
+ uri: "youmd://stacks/current/capabilities",
1007
+ name: "stacks/current/capabilities",
1008
+ description: "Current local YouStack capability map",
1009
+ mimeType: "application/json",
1010
+ });
1011
+ }
376
1012
  // Profile sections
377
1013
  const sections = getSectionFiles();
378
1014
  for (const section of sections) {
@@ -385,6 +1021,12 @@ async function startMcpServer() {
385
1021
  }
386
1022
  // Memories (if authenticated)
387
1023
  if ((0, config_1.isAuthenticated)()) {
1024
+ resources.push({
1025
+ uri: "youmd://private-context",
1026
+ name: "private-context",
1027
+ description: "Protected private context — notes, internal links, and private projects with readiness/fallback guidance",
1028
+ mimeType: "application/json",
1029
+ });
388
1030
  resources.push({
389
1031
  uri: "youmd://memories",
390
1032
  name: "memories",
@@ -505,9 +1147,55 @@ async function startMcpServer() {
505
1147
  }],
506
1148
  };
507
1149
  }
1150
+ // youmd://agent/brief
1151
+ if (uri === "youmd://agent/brief") {
1152
+ const brief = await buildAgentBrief();
1153
+ void logMcpActivity("read", "agent/brief");
1154
+ return {
1155
+ contents: [{
1156
+ uri,
1157
+ mimeType: "text/markdown",
1158
+ text: formatAgentBriefMarkdown(brief),
1159
+ }],
1160
+ };
1161
+ }
1162
+ if (uri === "youmd://stacks/current/manifest") {
1163
+ const loaded = tryLoadCurrentYouStack();
1164
+ if (!loaded)
1165
+ throw new Error("no local YouStack manifest found from current directory");
1166
+ void logMcpActivity("read", "stacks/current/manifest");
1167
+ return {
1168
+ contents: [{
1169
+ uri,
1170
+ mimeType: "application/json",
1171
+ text: JSON.stringify({
1172
+ manifestPath: loaded.manifestPath,
1173
+ rootDir: loaded.rootDir,
1174
+ manifest: loaded.manifest,
1175
+ validation: loaded.validation,
1176
+ }, null, 2),
1177
+ }],
1178
+ };
1179
+ }
1180
+ if (uri === "youmd://stacks/current/capabilities") {
1181
+ const loaded = tryLoadCurrentYouStack();
1182
+ if (!loaded)
1183
+ throw new Error("no local YouStack manifest found from current directory");
1184
+ void logMcpActivity("read", "stacks/current/capabilities");
1185
+ return {
1186
+ contents: [{
1187
+ uri,
1188
+ mimeType: "application/json",
1189
+ text: JSON.stringify({
1190
+ stack: loaded.manifest.slug,
1191
+ capabilities: (0, youstack_1.getYouStackCapabilities)(loaded.manifest),
1192
+ }, null, 2),
1193
+ }],
1194
+ };
1195
+ }
508
1196
  // youmd://memories
509
1197
  if (uri === "youmd://memories" || uri === "youmd://memories/all") {
510
- const memories = await fetchMemories(undefined, 50);
1198
+ const memories = await fetchMemoriesEnvelope(undefined, 50);
511
1199
  return {
512
1200
  contents: [{
513
1201
  uri,
@@ -519,7 +1207,7 @@ async function startMcpServer() {
519
1207
  // youmd://memories/{category}
520
1208
  const memCatMatch = uri.match(/^youmd:\/\/memories\/(.+)$/);
521
1209
  if (memCatMatch) {
522
- const memories = await fetchMemories(memCatMatch[1], 50);
1210
+ const memories = await fetchMemoriesEnvelope(memCatMatch[1], 50);
523
1211
  return {
524
1212
  contents: [{
525
1213
  uri,
@@ -528,6 +1216,16 @@ async function startMcpServer() {
528
1216
  }],
529
1217
  };
530
1218
  }
1219
+ if (uri === "youmd://private-context") {
1220
+ const privateContext = await fetchPrivateContextEnvelope();
1221
+ return {
1222
+ contents: [{
1223
+ uri,
1224
+ mimeType: "application/json",
1225
+ text: JSON.stringify(privateContext, null, 2),
1226
+ }],
1227
+ };
1228
+ }
531
1229
  // youmd://project/current/{file} — local project-context files from cwd
532
1230
  const localProjMatch = uri.match(/^youmd:\/\/project\/current\/(prd|todo|features|decisions|changelog)$/);
533
1231
  if (localProjMatch) {
@@ -545,10 +1243,7 @@ async function startMcpServer() {
545
1243
  }
546
1244
  // youmd://projects/current
547
1245
  if (uri === "youmd://projects/current") {
548
- const current = getCurrentProject();
549
- if (!current)
550
- throw new Error("no project detected in current directory");
551
- const ctx = (0, project_1.readProjectContext)(current.dir);
1246
+ const ctx = fetchProjectContextEnvelope();
552
1247
  return {
553
1248
  contents: [{
554
1249
  uri,
@@ -560,11 +1255,7 @@ async function startMcpServer() {
560
1255
  // youmd://projects/{name}
561
1256
  const projMatch = uri.match(/^youmd:\/\/projects\/(.+)$/);
562
1257
  if (projMatch) {
563
- const root = (0, project_1.findProjectsRoot)();
564
- if (!root)
565
- throw new Error("no projects directory found");
566
- const projDir = (0, project_1.getProjectDir)(root, projMatch[1]);
567
- const ctx = (0, project_1.readProjectContext)(projDir);
1258
+ const ctx = fetchProjectContextEnvelope(projMatch[1]);
568
1259
  return {
569
1260
  contents: [{
570
1261
  uri,
@@ -610,42 +1301,33 @@ async function startMcpServer() {
610
1301
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
611
1302
  return {
612
1303
  tools: [
1304
+ // whoami, get_identity, list_skills, list_projects, get_section,
1305
+ // get_project_context, get_stack_manifest, get_stack_capabilities,
1306
+ // route_stack_request, smoke_stack — migrated to registry.ts (T14).
613
1307
  {
614
- name: "whoami",
615
- description: "Return a compact ~500-char identity summary: name, role, stack, tone, things to avoid, top projects, and current goal. This is the FIRST tool you should call when starting a new conversation it gives you just enough context to orient on the user before deciding whether to pull the full identity bundle. Returns plain text, not JSON.",
616
- inputSchema: {
617
- type: "object",
618
- properties: {},
619
- },
620
- },
621
- {
622
- name: "get_identity",
623
- description: "Get the user's complete you.md identity bundle. Returns compact (default), full JSON, or human-readable markdown. For the fastest orient, call whoami first; call get_identity when you need full detail (preferences, voice, directives, projects).",
1308
+ name: "get_agent_brief",
1309
+ description: "Return a YouStack startup brief for local agents. Use immediately after whoami when starting Claude Code, Codex, Cursor, or another MCP-backed session. It combines compact identity, the user's recent durable memories (rendered inline by default), current repo instructions, project-context active requests, open TODOs, installed skills, and recommended next moves so the agent can act without asking the user to re-explain the project.",
624
1310
  inputSchema: {
625
1311
  type: "object",
626
1312
  properties: {
627
1313
  format: {
628
1314
  type: "string",
629
- enum: ["compact", "full", "json", "markdown"],
630
- description: "Output format: compact (500-char summary, default same as whoami), full/json (complete identity bundle as JSON), markdown (human-readable markdown).",
1315
+ enum: ["markdown", "json"],
1316
+ description: "Output format. markdown is default and best for agent context; json is best for automation.",
631
1317
  },
632
- },
633
- },
634
- },
635
- {
636
- name: "get_section",
637
- description: "Read a specific identity section by path. Use when you need just ONE section rather than the full bundle. Returns markdown content for the requested section. Available paths: profile/about, profile/projects, profile/now, profile/values, profile/links, preferences/agent, preferences/writing, voice/voice, directives/agent.",
638
- inputSchema: {
639
- type: "object",
640
- properties: {
641
- section: {
642
- type: "string",
643
- description: "Section path: profile/about, profile/projects, profile/now, profile/values, profile/links, preferences/agent, preferences/writing, voice/voice, directives/agent",
1318
+ includeMemories: {
1319
+ type: "boolean",
1320
+ description: "Include up to 20 memories (category + content, durable categories first, then newest) plus retrieval readiness/fallback guidance. Default true.",
1321
+ },
1322
+ maxChars: {
1323
+ type: "number",
1324
+ description: "Maximum markdown characters when format=markdown. Default 6000.",
644
1325
  },
645
1326
  },
646
- required: ["section"],
647
1327
  },
648
1328
  },
1329
+ // get_stack_manifest, get_stack_capabilities, route_stack_request,
1330
+ // smoke_stack, get_identity, get_section — migrated to registry.ts (T14).
649
1331
  {
650
1332
  name: "update_section",
651
1333
  description: "Update a section of the user's identity. Writes markdown content to the local .youmd/ directory. Use after the user explicitly asks to change their profile, preferences, voice, or directives. Always confirm changes with the user before calling. Does NOT auto-push — call push_bundle afterward if the user wants to publish.",
@@ -664,60 +1346,8 @@ async function startMcpServer() {
664
1346
  required: ["section", "content"],
665
1347
  },
666
1348
  },
667
- {
668
- name: "add_memory",
669
- description: "Save a memory about the user — facts, preferences, decisions, or context learned during this conversation. Memories persist across sessions and inform ALL future agent interactions. Use proactively when you learn something important about the user (a preference, a decision, a project detail). Requires authentication.",
670
- inputSchema: {
671
- type: "object",
672
- properties: {
673
- category: {
674
- type: "string",
675
- enum: [...MEMORY_CATEGORIES],
676
- description: "Memory category. Must be one of: fact, preference, decision, project, goal, insight, context, relationship.",
677
- },
678
- content: {
679
- type: "string",
680
- description: "The memory content — be specific and actionable",
681
- },
682
- tags: {
683
- type: "array",
684
- items: { type: "string" },
685
- description: "Optional tags for searchability",
686
- },
687
- },
688
- required: ["category", "content"],
689
- },
690
- },
691
- {
692
- name: "search_memories",
693
- description: "Search the user's memories by category or list all active memories. Returns an array of memory objects with category, content, tags, and timestamps. Use to check what you already know before asking the user a question they may have answered before.",
694
- inputSchema: {
695
- type: "object",
696
- properties: {
697
- category: {
698
- type: "string",
699
- description: "Filter by category (optional): fact, preference, decision, project, goal, insight, context",
700
- },
701
- limit: {
702
- type: "number",
703
- description: "Max results (default 30)",
704
- },
705
- },
706
- },
707
- },
708
- {
709
- name: "get_project_context",
710
- description: "Get the full project context for the current or named project — PRD, TODO, features, decisions, changelog, and project memories. Returns a JSON object with all project-context/ files. Use when starting work on a project to understand what has been built, what is planned, and what decisions have been made.",
711
- inputSchema: {
712
- type: "object",
713
- properties: {
714
- project: {
715
- type: "string",
716
- description: "Project name. Omit to auto-detect from current directory.",
717
- },
718
- },
719
- },
720
- },
1349
+ // add_memory, search_memories, get_private_context — migrated to registry.ts (T14).
1350
+ // get_project_context — migrated to registry.ts (T14).
721
1351
  {
722
1352
  name: "add_project_memory",
723
1353
  description: "Save a memory scoped to a specific project. Unlike add_memory (which is global), project memories are stored locally in the project-context/ directory and only surface when working on that project. Use for architecture decisions, bug context, and feature-specific learnings.",
@@ -740,20 +1370,7 @@ async function startMcpServer() {
740
1370
  required: ["category", "content"],
741
1371
  },
742
1372
  },
743
- {
744
- name: "use_skill",
745
- description: "Render an identity-aware skill — returns the skill content with the user's identity interpolated into {{var}} placeholders. Returns rendered markdown with instructions the agent should follow. Use when the user asks to generate a CLAUDE.md, sync voice, scaffold a project, or run a self-improvement review.",
746
- inputSchema: {
747
- type: "object",
748
- properties: {
749
- name: {
750
- type: "string",
751
- description: "Skill name: voice-sync, claude-md-generator, project-context-init, meta-improve, proactive-context-fill, you-logs",
752
- },
753
- },
754
- required: ["name"],
755
- },
756
- },
1373
+ // use_skill — migrated to registry.ts (T14).
757
1374
  {
758
1375
  name: "compile_bundle",
759
1376
  description: "Recompile the local identity bundle from profile/, preferences/, voice/, directives/ files into you.json + you.md. Call after update_section to regenerate the compiled bundle. Returns compilation stats including version, section count, and files read.",
@@ -783,132 +1400,80 @@ async function startMcpServer() {
783
1400
  properties: {},
784
1401
  },
785
1402
  },
786
- {
787
- name: "list_skills",
788
- description: "List all installed identity-aware skills with their names. Use to discover what skills are available before calling use_skill. Returns a simple list of installed skill names.",
789
- inputSchema: {
790
- type: "object",
791
- properties: {},
792
- },
793
- },
794
- {
795
- name: "add_source",
796
- description: "Register an identity data source (LinkedIn, GitHub, X, website, blog, YouTube). Links an external profile to the user's identity so it can be scraped and indexed. Requires authentication. Use when the user wants to connect a new social profile or website to their identity.",
797
- inputSchema: {
798
- type: "object",
799
- properties: {
800
- sourceType: {
801
- type: "string",
802
- enum: ["website", "linkedin", "x", "github", "blog", "youtube"],
803
- description: "Type of source to register",
804
- },
805
- sourceUrl: {
806
- type: "string",
807
- description: "Full URL to the source (e.g., https://github.com/username)",
808
- },
809
- },
810
- required: ["sourceType", "sourceUrl"],
811
- },
812
- },
813
- {
814
- name: "create_context_link",
815
- description: "Generate a shareable context link for agents. The link gives any agent temporary or permanent read access to the user's identity context. Use when the user wants to share their identity with a third-party agent or service. Returns a URL that can be passed to any agent.",
816
- inputSchema: {
817
- type: "object",
818
- properties: {
819
- scope: {
820
- type: "string",
821
- enum: ["public", "full"],
822
- description: "Access scope: public (profile only) or full (includes memories, preferences, directives). Default: public",
823
- },
824
- ttl: {
825
- type: "string",
826
- enum: ["1h", "24h", "7d", "30d", "never"],
827
- description: "Time-to-live for the link. Default: 24h",
828
- },
829
- },
830
- },
831
- },
832
- {
833
- name: "list_projects",
834
- description: "List all detected projects with their metadata. Returns project names found in the projects root directory. Use to discover available projects before calling get_project_context with a specific project name.",
835
- inputSchema: {
836
- type: "object",
837
- properties: {},
838
- },
839
- },
840
- {
841
- name: "get_remote_status",
842
- description: "Check sync status between local identity bundle and the remote you.md server. Returns whether the user is authenticated, whether the local bundle exists, and the current version info. Use to diagnose sync issues or confirm a push was successful.",
843
- inputSchema: {
844
- type: "object",
845
- properties: {},
846
- },
847
- },
848
- {
849
- name: "get_activity_log",
850
- description: "Get the user's recent agent activity log. Use this to see which agents have connected to their you.md identity and what they did. Returns an array of activity events with agent name, action, resource, timestamp. Useful for: showing the user proof their identity context is being used by other agents, debugging integration issues, auditing access.",
851
- inputSchema: {
852
- type: "object",
853
- properties: {
854
- limit: { type: "number", description: "Max events (default 30)" },
855
- agentName: { type: "string", description: "Filter by agent name (e.g. 'Claude Code')" },
856
- action: { type: "string", description: "Filter by action (read|write|push|memory_add)" },
857
- },
858
- },
859
- },
1403
+ // list_skills — migrated to registry.ts (T14).
1404
+ // add_source, create_context_link — migrated to registry.ts (T14).
1405
+ // list_projects migrated to registry.ts (T14).
1406
+ // get_remote_status, get_activity_log — migrated to registry.ts (T14).
860
1407
  ],
1408
+ // T14: registry tools appended; inline tools above remain until migration.
1409
+ ...registry_js_1.CLI_MCP_TOOLS.map(({ name, description, inputSchema }) => ({
1410
+ name,
1411
+ description,
1412
+ inputSchema,
1413
+ })),
861
1414
  };
862
1415
  });
863
1416
  // ── CALL TOOL ──────────────────────────────────────────────────────
864
1417
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
865
1418
  const { name, arguments: args } = request.params;
1419
+ // T14: Try registry dispatch first (grows as tools are migrated out of the switch).
1420
+ const registrySpec = registry_js_1.CLI_MCP_TOOLS.find((t) => t.name === name);
1421
+ if (registrySpec) {
1422
+ const ctx = {
1423
+ logActivity: (action, resource, details) => {
1424
+ void logMcpActivity(action, resource, details);
1425
+ },
1426
+ authenticated: (0, config_1.isAuthenticated)(),
1427
+ // Injected helpers — avoids circular import (registry cannot import server).
1428
+ fetchMemoriesEnvelope,
1429
+ fetchPrivateContextEnvelope,
1430
+ apiRequest,
1431
+ memoryCategories: exports.MEMORY_CATEGORIES,
1432
+ resolveAgentName,
1433
+ getInstalledSkills,
1434
+ activeBundleExists,
1435
+ getYouJson,
1436
+ fetchActivityLog: (params) => {
1437
+ const config = (0, config_1.readGlobalConfig)();
1438
+ return fetch(`${(0, config_1.getConvexSiteUrl)()}/api/v1/me/activity?${params}`, {
1439
+ headers: { Authorization: `Bearer ${config.token}` },
1440
+ signal: AbortSignal.timeout(10000),
1441
+ });
1442
+ },
1443
+ };
1444
+ const result = await registrySpec.handler((args || {}), ctx);
1445
+ return { content: result.content, isError: result.isError };
1446
+ }
1447
+ // whoami, get_identity, get_section, list_skills, list_projects,
1448
+ // get_project_context, get_stack_manifest, get_stack_capabilities,
1449
+ // route_stack_request, smoke_stack — dispatched via registry above (T14).
866
1450
  switch (name) {
867
- case "whoami": {
868
- void logMcpActivity("read", "identity:compact");
869
- return {
870
- content: [{
871
- type: "text",
872
- text: buildWhoamiSummary(),
873
- }],
874
- };
875
- }
876
- case "get_identity": {
877
- const format = args?.format || "compact";
878
- void logMcpActivity("read", "identity:" + format);
879
- if (format === "markdown") {
880
- return { content: [{ type: "text", text: buildIdentityMarkdown() }] };
881
- }
882
- if (format === "full" || format === "json") {
1451
+ case "get_agent_brief": {
1452
+ const briefArgs = (args || {});
1453
+ const includeMemories = briefArgs.includeMemories !== false;
1454
+ const brief = await buildAgentBrief({ includeMemories });
1455
+ void logMcpActivity("read", "agent/brief", {
1456
+ format: briefArgs.format || "markdown",
1457
+ includeMemories,
1458
+ });
1459
+ if (briefArgs.format === "json") {
883
1460
  return {
884
1461
  content: [{
885
1462
  type: "text",
886
- text: JSON.stringify(getYouJson(), null, 2),
1463
+ text: JSON.stringify(brief, null, 2),
887
1464
  }],
888
1465
  };
889
1466
  }
890
- // compact (default) matches whoami output
1467
+ const maxChars = typeof briefArgs.maxChars === "number" && briefArgs.maxChars > 500
1468
+ ? Math.min(briefArgs.maxChars, 20000)
1469
+ : 6000;
891
1470
  return {
892
1471
  content: [{
893
1472
  type: "text",
894
- text: buildWhoamiSummary(),
1473
+ text: formatAgentBriefMarkdown(brief, maxChars),
895
1474
  }],
896
1475
  };
897
1476
  }
898
- case "get_section": {
899
- const section = args.section;
900
- const parts = section.split("/");
901
- if (parts.length !== 2) {
902
- return { content: [{ type: "text", text: "invalid section path — use format: dir/slug (e.g., profile/about)" }], isError: true };
903
- }
904
- const filePath = path.join(getBundleDir(), parts[0], `${parts[1]}.md`);
905
- const content = readFileOr(filePath, "");
906
- if (!content) {
907
- return { content: [{ type: "text", text: `section not found: ${section}` }], isError: true };
908
- }
909
- void logMcpActivity("read_section", section);
910
- return { content: [{ type: "text", text: content }] };
911
- }
912
1477
  case "update_section": {
913
1478
  const section = args.section;
914
1479
  const content = args.content;
@@ -926,78 +1491,7 @@ async function startMcpServer() {
926
1491
  void logMcpActivity("write", section);
927
1492
  return { content: [{ type: "text", text: `updated ${section}` }] };
928
1493
  }
929
- case "add_memory": {
930
- const { category, content: memContent, tags } = args;
931
- if (!(0, config_1.isAuthenticated)()) {
932
- return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
933
- }
934
- // Validate category against known enum. Reject unknown categories so
935
- // we don't pollute the memory store with one-off tags masquerading as
936
- // categories.
937
- if (!category || !MEMORY_CATEGORIES.includes(category)) {
938
- return {
939
- content: [{
940
- type: "text",
941
- text: `invalid category: ${category || "(missing)"}. valid categories: ${MEMORY_CATEGORIES.join(", ")}`,
942
- }],
943
- isError: true,
944
- };
945
- }
946
- try {
947
- const result = await apiRequest("/api/v1/me/memories", {
948
- method: "POST",
949
- body: {
950
- memories: [{
951
- category,
952
- content: memContent,
953
- source: "mcp",
954
- sourceAgent: "youmd-mcp",
955
- tags,
956
- }],
957
- },
958
- });
959
- void logMcpActivity("memory_add", category);
960
- return { content: [{ type: "text", text: `memory saved: [${category}] ${memContent.slice(0, 80)}${memContent.length > 80 ? "..." : ""}` }] };
961
- }
962
- catch (err) {
963
- return { content: [{ type: "text", text: `failed to save memory: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
964
- }
965
- }
966
- case "search_memories": {
967
- const { category, limit } = (args || {});
968
- const memories = await fetchMemories(category, limit || 30);
969
- void logMcpActivity("read", "memories");
970
- return {
971
- content: [{
972
- type: "text",
973
- text: JSON.stringify(memories, null, 2),
974
- }],
975
- };
976
- }
977
- case "get_project_context": {
978
- const projectName = args?.project;
979
- try {
980
- if (projectName) {
981
- const root = (0, project_1.findProjectsRoot)();
982
- if (!root)
983
- throw new Error("no projects directory found");
984
- const projDir = (0, project_1.getProjectDir)(root, projectName);
985
- const ctx = (0, project_1.readProjectContext)(projDir);
986
- void logMcpActivity("read", "project/" + projectName);
987
- return { content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }] };
988
- }
989
- const current = getCurrentProject();
990
- if (!current) {
991
- return { content: [{ type: "text", text: "no project detected in current directory" }], isError: true };
992
- }
993
- const ctx = (0, project_1.readProjectContext)(current.dir);
994
- void logMcpActivity("read", "project/current");
995
- return { content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }] };
996
- }
997
- catch (err) {
998
- return { content: [{ type: "text", text: `project error: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
999
- }
1000
- }
1494
+ // add_memory, search_memories, get_private_context — dispatched via registry (T14).
1001
1495
  case "add_project_memory": {
1002
1496
  const { project: projName, category, content: memContent } = args;
1003
1497
  try {
@@ -1022,17 +1516,7 @@ async function startMcpServer() {
1022
1516
  return { content: [{ type: "text", text: `error: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
1023
1517
  }
1024
1518
  }
1025
- case "use_skill": {
1026
- const skillName = args.name;
1027
- const skills = getInstalledSkills();
1028
- const skill = skills.find((s) => s.name === skillName);
1029
- if (!skill) {
1030
- const available = skills.map((s) => s.name).join(", ") || "none installed";
1031
- return { content: [{ type: "text", text: `skill not found: ${skillName}. available: ${available}` }], isError: true };
1032
- }
1033
- void logMcpActivity("skill_use", "skill/" + skillName);
1034
- return { content: [{ type: "text", text: skill.rendered || skill.raw }] };
1035
- }
1519
+ // use_skill — dispatched via registry (T14).
1036
1520
  case "compile_bundle": {
1037
1521
  try {
1038
1522
  const { compileBundle, writeBundle } = await Promise.resolve().then(() => __importStar(require("../lib/compiler")));
@@ -1138,138 +1622,7 @@ async function startMcpServer() {
1138
1622
  };
1139
1623
  }
1140
1624
  }
1141
- case "list_skills": {
1142
- const skills = getInstalledSkills();
1143
- if (skills.length === 0) {
1144
- return { content: [{ type: "text", text: "no skills installed. run: youmd skill install voice-sync" }] };
1145
- }
1146
- const list = skills.map((s) => `- ${s.name}`).join("\n");
1147
- void logMcpActivity("read", "skills");
1148
- return { content: [{ type: "text", text: `installed skills:\n${list}` }] };
1149
- }
1150
- case "add_source": {
1151
- if (!(0, config_1.isAuthenticated)()) {
1152
- return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1153
- }
1154
- const { sourceType, sourceUrl } = args;
1155
- try {
1156
- const result = await apiRequest("/api/v1/me/sources", {
1157
- method: "POST",
1158
- body: { sourceType, sourceUrl },
1159
- });
1160
- void logMcpActivity("write", "source/" + sourceType);
1161
- return { content: [{ type: "text", text: `source registered: [${sourceType}] ${sourceUrl}` }] };
1162
- }
1163
- catch (err) {
1164
- return { content: [{ type: "text", text: `failed to add source: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
1165
- }
1166
- }
1167
- case "create_context_link": {
1168
- if (!(0, config_1.isAuthenticated)()) {
1169
- return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1170
- }
1171
- const { scope, ttl } = (args || {});
1172
- try {
1173
- const result = await apiRequest("/api/v1/me/context-links", {
1174
- method: "POST",
1175
- body: { scope: scope || "public", ttl: ttl || "24h" },
1176
- });
1177
- const link = result.url || result.link || JSON.stringify(result);
1178
- void logMcpActivity("write", "context-link", { scope: scope || "public" });
1179
- return { content: [{ type: "text", text: `context link created: ${link}\nscope: ${scope || "public"}, ttl: ${ttl || "24h"}` }] };
1180
- }
1181
- catch (err) {
1182
- return { content: [{ type: "text", text: `failed to create context link: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
1183
- }
1184
- }
1185
- case "list_projects": {
1186
- try {
1187
- const root = (0, project_1.findProjectsRoot)();
1188
- if (!root) {
1189
- return { content: [{ type: "text", text: "no projects directory found" }], isError: true };
1190
- }
1191
- const projects = (0, project_1.listProjects)(root);
1192
- if (projects.length === 0) {
1193
- return { content: [{ type: "text", text: "no projects detected. create one with: youmd project init <name>" }] };
1194
- }
1195
- const current = getCurrentProject();
1196
- const list = projects.map((p) => {
1197
- const marker = current && current.name === p ? " (current)" : "";
1198
- return `- ${p}${marker}`;
1199
- }).join("\n");
1200
- void logMcpActivity("read", "projects");
1201
- return { content: [{ type: "text", text: `projects:\n${list}` }] };
1202
- }
1203
- catch (err) {
1204
- return { content: [{ type: "text", text: `error listing projects: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
1205
- }
1206
- }
1207
- case "get_remote_status": {
1208
- const authenticated = (0, config_1.isAuthenticated)();
1209
- const bundleExists = (0, config_1.localBundleExists)();
1210
- const youJson = getYouJson();
1211
- const version = youJson?.version || "unknown";
1212
- const status = {
1213
- authenticated,
1214
- localBundleExists: bundleExists,
1215
- localVersion: version,
1216
- };
1217
- if (authenticated) {
1218
- try {
1219
- const remote = await apiRequest("/api/v1/me/status");
1220
- status.remote = remote;
1221
- }
1222
- catch {
1223
- status.remote = "unreachable";
1224
- }
1225
- }
1226
- void logMcpActivity("read", "status");
1227
- return {
1228
- content: [{
1229
- type: "text",
1230
- text: JSON.stringify(status, null, 2),
1231
- }],
1232
- };
1233
- }
1234
- case "get_activity_log": {
1235
- if (!(0, config_1.isAuthenticated)()) {
1236
- return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1237
- }
1238
- const config = (0, config_1.readGlobalConfig)();
1239
- const params = new URLSearchParams();
1240
- const activityArgs = (args || {});
1241
- if (activityArgs.limit)
1242
- params.set("limit", String(activityArgs.limit));
1243
- if (activityArgs.agentName)
1244
- params.set("agent", String(activityArgs.agentName));
1245
- if (activityArgs.action)
1246
- params.set("action", String(activityArgs.action));
1247
- const res = await fetch(`${(0, config_1.getConvexSiteUrl)()}/api/v1/me/activity?${params}`, {
1248
- headers: { Authorization: `Bearer ${config.token}` },
1249
- signal: AbortSignal.timeout(10000),
1250
- });
1251
- if (!res.ok) {
1252
- return { content: [{ type: "text", text: `failed to fetch activity log: ${res.status}` }], isError: true };
1253
- }
1254
- const data = await res.json();
1255
- const events = data.activity || [];
1256
- if (events.length === 0) {
1257
- return { content: [{ type: "text", text: "No activity yet. Agents will appear here when they connect to your you.md identity." }] };
1258
- }
1259
- const formatted = events.slice(0, 30).reverse().map((e) => {
1260
- const time = new Date(e.createdAt).toTimeString().slice(0, 5);
1261
- const versions = e.bundleVersionBefore && e.bundleVersionAfter
1262
- ? ` v${e.bundleVersionBefore}→v${e.bundleVersionAfter}`
1263
- : '';
1264
- return `${time} ${e.agentName.padEnd(16)} ${e.action.padEnd(12)} ${e.resource || ''}${versions}`;
1265
- }).join('\n');
1266
- return {
1267
- content: [{
1268
- type: "text",
1269
- text: `── recent agent activity (${events.length} events) ──\n\n${formatted}`,
1270
- }],
1271
- };
1272
- }
1625
+ // add_source, create_context_link, get_remote_status, get_activity_log — dispatched via registry (T14).
1273
1626
  default:
1274
1627
  throw new Error(`unknown tool: ${name}`);
1275
1628
  }