@harness-engineering/cli 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/agents/skills/claude-code/enforce-architecture/SKILL.md +4 -0
  2. package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +7 -2
  3. package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +10 -1
  4. package/dist/agents/skills/claude-code/harness-execution/SKILL.md +2 -2
  5. package/dist/agents/skills/claude-code/harness-parallel-agents/SKILL.md +105 -20
  6. package/dist/agents/skills/claude-code/harness-pre-commit-review/SKILL.md +37 -0
  7. package/dist/agents/skills/gemini-cli/enforce-architecture/SKILL.md +4 -0
  8. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +7 -2
  9. package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +10 -1
  10. package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +2 -2
  11. package/dist/agents/skills/gemini-cli/harness-parallel-agents/SKILL.md +105 -20
  12. package/dist/agents/skills/gemini-cli/harness-pre-commit-review/SKILL.md +37 -0
  13. package/dist/agents-md-ZFV6RR5J.js +8 -0
  14. package/dist/architecture-EXNUMH5R.js +13 -0
  15. package/dist/bin/harness-mcp.d.ts +1 -0
  16. package/dist/bin/harness-mcp.js +28 -0
  17. package/dist/bin/harness.js +42 -8
  18. package/dist/check-phase-gate-VZFOY2PO.js +12 -0
  19. package/dist/chunk-2NCIKJES.js +470 -0
  20. package/dist/chunk-2YPZKGAG.js +62 -0
  21. package/dist/{chunk-CGSHUJES.js → chunk-2YSQOUHO.js} +4484 -2688
  22. package/dist/chunk-3WGJMBKH.js +45 -0
  23. package/dist/{chunk-ULSRSP53.js → chunk-6N4R6FVX.js} +11 -112
  24. package/dist/{chunk-6JIT7CEM.js → chunk-72GHBOL2.js} +1 -1
  25. package/dist/chunk-BM3PWGXQ.js +14 -0
  26. package/dist/chunk-C2ERUR3L.js +255 -0
  27. package/dist/chunk-EBJQ6N4M.js +39 -0
  28. package/dist/chunk-GNGELAXY.js +293 -0
  29. package/dist/chunk-GSIVNYVJ.js +187 -0
  30. package/dist/chunk-HD4IBGLA.js +80 -0
  31. package/dist/chunk-I6JZYEGT.js +4361 -0
  32. package/dist/chunk-IDZNPTYD.js +16 -0
  33. package/dist/chunk-JSTQ3AWB.js +31 -0
  34. package/dist/chunk-K6XAPGML.js +27 -0
  35. package/dist/chunk-KET4QQZB.js +8 -0
  36. package/dist/chunk-L2KLU56K.js +125 -0
  37. package/dist/chunk-MHBMTPW7.js +29 -0
  38. package/dist/chunk-NC6PXVWT.js +116 -0
  39. package/dist/chunk-NKDM3FMH.js +52 -0
  40. package/dist/chunk-PA2XHK75.js +248 -0
  41. package/dist/chunk-Q6AB7W5Z.js +135 -0
  42. package/dist/chunk-QPEH2QPG.js +347 -0
  43. package/dist/chunk-TEFCFC4H.js +15 -0
  44. package/dist/chunk-TI4TGEX6.js +85 -0
  45. package/dist/chunk-TRAPF4IX.js +185 -0
  46. package/dist/chunk-VRFZWGMS.js +68 -0
  47. package/dist/chunk-VUCPTQ6G.js +67 -0
  48. package/dist/chunk-W6Y7ZW3Y.js +13 -0
  49. package/dist/chunk-WJZDO6OY.js +103 -0
  50. package/dist/chunk-WUJTCNOU.js +122 -0
  51. package/dist/chunk-X3MN5UQJ.js +89 -0
  52. package/dist/chunk-Z75JC6I2.js +189 -0
  53. package/dist/chunk-ZOAWBDWU.js +72 -0
  54. package/dist/{chunk-RTPHUDZS.js → chunk-ZWC3MN5E.js} +1944 -2779
  55. package/dist/ci-workflow-K5RCRNYR.js +8 -0
  56. package/dist/constants-5JGUXPEK.js +6 -0
  57. package/dist/create-skill-WPXHSLX2.js +11 -0
  58. package/dist/dist-D4RYGUZE.js +14 -0
  59. package/dist/{dist-C5PYIQPF.js → dist-JVZ2MKBC.js} +108 -6
  60. package/dist/dist-L7LAAQAS.js +18 -0
  61. package/dist/{dist-I7DB5VKB.js → dist-M6BQODWC.js} +1145 -0
  62. package/dist/docs-PWCUVYWU.js +12 -0
  63. package/dist/engine-6XUP6GAK.js +8 -0
  64. package/dist/entropy-4I6JEYAC.js +12 -0
  65. package/dist/feedback-TNIW534S.js +18 -0
  66. package/dist/generate-agent-definitions-MWKEA5NU.js +15 -0
  67. package/dist/glob-helper-5OHBUQAI.js +52 -0
  68. package/dist/graph-loader-KO4GJ5N2.js +8 -0
  69. package/dist/index.d.ts +328 -12
  70. package/dist/index.js +93 -34
  71. package/dist/loader-4FIPIFII.js +10 -0
  72. package/dist/mcp-MOKLYNZL.js +34 -0
  73. package/dist/performance-BTOJCPXU.js +24 -0
  74. package/dist/review-pipeline-3YTW3463.js +9 -0
  75. package/dist/runner-VMYLHWOC.js +6 -0
  76. package/dist/runtime-GO7K2PJE.js +9 -0
  77. package/dist/security-4P2GGFF6.js +9 -0
  78. package/dist/skill-executor-RG45LUO5.js +8 -0
  79. package/dist/templates/orchestrator/WORKFLOW.md +48 -0
  80. package/dist/templates/orchestrator/template.json +6 -0
  81. package/dist/validate-JN44D2Q7.js +12 -0
  82. package/dist/validate-cross-check-DB7RIFFF.js +8 -0
  83. package/dist/version-KFFPOQAX.js +6 -0
  84. package/package.json +13 -7
  85. package/dist/create-skill-UZOHMXRU.js +0 -8
  86. package/dist/validate-cross-check-VG573VZO.js +0 -7
@@ -0,0 +1,4361 @@
1
+ import {
2
+ detectEntropyDefinition,
3
+ handleDetectEntropy
4
+ } from "./chunk-Z75JC6I2.js";
5
+ import {
6
+ checkPerformanceDefinition,
7
+ getCriticalPathsDefinition,
8
+ getPerfBaselinesDefinition,
9
+ handleCheckPerformance,
10
+ handleGetCriticalPaths,
11
+ handleGetPerfBaselines,
12
+ handleUpdatePerfBaselines,
13
+ updatePerfBaselinesDefinition
14
+ } from "./chunk-GSIVNYVJ.js";
15
+ import {
16
+ analyzeDiffDefinition,
17
+ createSelfReviewDefinition,
18
+ handleAnalyzeDiff,
19
+ handleCreateSelfReview,
20
+ handleRequestPeerReview,
21
+ requestPeerReviewDefinition
22
+ } from "./chunk-PA2XHK75.js";
23
+ import {
24
+ handleRunSecurityScan,
25
+ runSecurityScanDefinition
26
+ } from "./chunk-X3MN5UQJ.js";
27
+ import {
28
+ handleRunCodeReview,
29
+ runCodeReviewDefinition
30
+ } from "./chunk-WUJTCNOU.js";
31
+ import {
32
+ GENERATED_HEADER_CLAUDE,
33
+ GENERATED_HEADER_GEMINI,
34
+ VALID_PLATFORMS,
35
+ applySyncPlan,
36
+ computeSyncPlan
37
+ } from "./chunk-ZOAWBDWU.js";
38
+ import {
39
+ handleValidateProject,
40
+ validateToolDefinition
41
+ } from "./chunk-WJZDO6OY.js";
42
+ import {
43
+ loadGraphStore
44
+ } from "./chunk-2YPZKGAG.js";
45
+ import {
46
+ checkDependenciesDefinition,
47
+ handleCheckDependencies
48
+ } from "./chunk-TI4TGEX6.js";
49
+ import {
50
+ resolveProjectConfig
51
+ } from "./chunk-K6XAPGML.js";
52
+ import {
53
+ checkDocsDefinition,
54
+ handleCheckDocs
55
+ } from "./chunk-NC6PXVWT.js";
56
+ import {
57
+ resultToMcpResponse
58
+ } from "./chunk-IDZNPTYD.js";
59
+ import {
60
+ sanitizePath
61
+ } from "./chunk-W6Y7ZW3Y.js";
62
+ import {
63
+ resolveCommunitySkillsDir,
64
+ resolveGlobalSkillsDir,
65
+ resolvePersonasDir,
66
+ resolveProjectSkillsDir,
67
+ resolveSkillsDir,
68
+ resolveTemplatesDir
69
+ } from "./chunk-HD4IBGLA.js";
70
+ import {
71
+ CLIError,
72
+ ExitCode,
73
+ handleError
74
+ } from "./chunk-3WGJMBKH.js";
75
+ import {
76
+ SkillMetadataSchema
77
+ } from "./chunk-VRFZWGMS.js";
78
+ import {
79
+ Err,
80
+ Ok
81
+ } from "./chunk-MHBMTPW7.js";
82
+
83
+ // src/mcp/server.ts
84
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
85
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
86
+ import {
87
+ CallToolRequestSchema,
88
+ ListToolsRequestSchema,
89
+ ListResourcesRequestSchema,
90
+ ReadResourceRequestSchema
91
+ } from "@modelcontextprotocol/sdk/types.js";
92
+
93
+ // src/mcp/tools/linter.ts
94
+ var generateLinterDefinition = {
95
+ name: "generate_linter",
96
+ description: "Generate an ESLint rule from YAML configuration",
97
+ inputSchema: {
98
+ type: "object",
99
+ properties: {
100
+ configPath: { type: "string", description: "Path to harness-linter.yml" },
101
+ outputDir: { type: "string", description: "Output directory for generated rule" }
102
+ },
103
+ required: ["configPath"]
104
+ }
105
+ };
106
+ async function handleGenerateLinter(input) {
107
+ try {
108
+ const { generate } = await import("./dist-L7LAAQAS.js");
109
+ const result = await generate({
110
+ configPath: sanitizePath(input.configPath),
111
+ ...input.outputDir !== void 0 && { outputDir: sanitizePath(input.outputDir) }
112
+ });
113
+ if ("success" in result && result.success) {
114
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
115
+ }
116
+ return { content: [{ type: "text", text: JSON.stringify(result) }], isError: true };
117
+ } catch (error) {
118
+ return {
119
+ content: [
120
+ {
121
+ type: "text",
122
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
123
+ }
124
+ ],
125
+ isError: true
126
+ };
127
+ }
128
+ }
129
+ var validateLinterConfigDefinition = {
130
+ name: "validate_linter_config",
131
+ description: "Validate a harness-linter.yml configuration file",
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: {
135
+ configPath: { type: "string", description: "Path to harness-linter.yml" }
136
+ },
137
+ required: ["configPath"]
138
+ }
139
+ };
140
+ async function handleValidateLinterConfig(input) {
141
+ try {
142
+ const { validate } = await import("./dist-L7LAAQAS.js");
143
+ const result = await validate({ configPath: sanitizePath(input.configPath) });
144
+ if ("success" in result && result.success) {
145
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
146
+ }
147
+ return { content: [{ type: "text", text: JSON.stringify(result) }], isError: true };
148
+ } catch (error) {
149
+ return {
150
+ content: [
151
+ {
152
+ type: "text",
153
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
154
+ }
155
+ ],
156
+ isError: true
157
+ };
158
+ }
159
+ }
160
+
161
+ // src/mcp/tools/init.ts
162
+ import * as path from "path";
163
+ var initProjectDefinition = {
164
+ name: "init_project",
165
+ description: "Scaffold a new harness engineering project from a template",
166
+ inputSchema: {
167
+ type: "object",
168
+ properties: {
169
+ path: { type: "string", description: "Target directory" },
170
+ name: { type: "string", description: "Project name" },
171
+ level: {
172
+ type: "string",
173
+ enum: ["basic", "intermediate", "advanced"],
174
+ description: "Adoption level"
175
+ },
176
+ framework: { type: "string", description: "Framework overlay (e.g., nextjs)" }
177
+ },
178
+ required: ["path"]
179
+ }
180
+ };
181
+ async function handleInitProject(input) {
182
+ try {
183
+ const { TemplateEngine } = await import("./engine-6XUP6GAK.js");
184
+ const templatesDir = resolveTemplatesDir();
185
+ const engine = new TemplateEngine(templatesDir);
186
+ const level = input.level ?? "basic";
187
+ const resolveResult = engine.resolveTemplate(level, input.framework);
188
+ if (!resolveResult.ok) return resultToMcpResponse(resolveResult);
189
+ const safePath = sanitizePath(input.path);
190
+ const renderResult = engine.render(resolveResult.value, {
191
+ projectName: input.name ?? path.basename(safePath),
192
+ level,
193
+ ...input.framework !== void 0 && { framework: input.framework }
194
+ });
195
+ if (!renderResult.ok) return resultToMcpResponse(renderResult);
196
+ const writeResult = engine.write(renderResult.value, safePath, {
197
+ overwrite: false
198
+ });
199
+ return resultToMcpResponse(writeResult);
200
+ } catch (error) {
201
+ return {
202
+ content: [
203
+ {
204
+ type: "text",
205
+ text: `Init failed: ${error instanceof Error ? error.message : String(error)}`
206
+ }
207
+ ],
208
+ isError: true
209
+ };
210
+ }
211
+ }
212
+
213
+ // src/mcp/tools/persona.ts
214
+ import * as path2 from "path";
215
+ var listPersonasDefinition = {
216
+ name: "list_personas",
217
+ description: "List available agent personas",
218
+ inputSchema: { type: "object", properties: {} }
219
+ };
220
+ async function handleListPersonas() {
221
+ const { listPersonas } = await import("./loader-4FIPIFII.js");
222
+ const result = listPersonas(resolvePersonasDir());
223
+ return resultToMcpResponse(result);
224
+ }
225
+ var generatePersonaArtifactsDefinition = {
226
+ name: "generate_persona_artifacts",
227
+ description: "Generate runtime config, AGENTS.md fragment, and CI workflow from a persona",
228
+ inputSchema: {
229
+ type: "object",
230
+ properties: {
231
+ name: { type: "string", description: "Persona name (e.g., architecture-enforcer)" },
232
+ only: {
233
+ type: "string",
234
+ enum: ["runtime", "agents-md", "ci"],
235
+ description: "Generate only a specific artifact type"
236
+ }
237
+ },
238
+ required: ["name"]
239
+ }
240
+ };
241
+ async function handleGeneratePersonaArtifacts(input) {
242
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.name)) {
243
+ return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.name}`)));
244
+ }
245
+ const { loadPersona } = await import("./loader-4FIPIFII.js");
246
+ const { generateRuntime } = await import("./runtime-GO7K2PJE.js");
247
+ const { generateAgentsMd } = await import("./agents-md-ZFV6RR5J.js");
248
+ const { generateCIWorkflow } = await import("./ci-workflow-K5RCRNYR.js");
249
+ const personasDir = resolvePersonasDir();
250
+ const filePath = path2.join(personasDir, `${input.name}.yaml`);
251
+ if (!filePath.startsWith(personasDir)) {
252
+ return resultToMcpResponse(Err(new Error(`Invalid persona path: ${input.name}`)));
253
+ }
254
+ const personaResult = loadPersona(filePath);
255
+ if (!personaResult.ok) return resultToMcpResponse(personaResult);
256
+ const persona = personaResult.value;
257
+ const artifacts = {};
258
+ if (!input.only || input.only === "runtime") {
259
+ const r = generateRuntime(persona);
260
+ if (r.ok) artifacts.runtime = r.value;
261
+ }
262
+ if (!input.only || input.only === "agents-md") {
263
+ const r = generateAgentsMd(persona);
264
+ if (r.ok) artifacts.agentsMd = r.value;
265
+ }
266
+ if (!input.only || input.only === "ci") {
267
+ const r = generateCIWorkflow(persona, "github");
268
+ if (r.ok) artifacts.ciWorkflow = r.value;
269
+ }
270
+ return resultToMcpResponse(Ok(artifacts));
271
+ }
272
+ var runPersonaDefinition = {
273
+ name: "run_persona",
274
+ description: "Execute all steps defined in a persona and return aggregated results",
275
+ inputSchema: {
276
+ type: "object",
277
+ properties: {
278
+ persona: { type: "string", description: "Persona name (e.g., architecture-enforcer)" },
279
+ path: { type: "string", description: "Path to project root" },
280
+ trigger: {
281
+ type: "string",
282
+ enum: [
283
+ "always",
284
+ "on_pr",
285
+ "on_commit",
286
+ "on_review",
287
+ "scheduled",
288
+ "manual",
289
+ "on_plan_approved",
290
+ "auto"
291
+ ],
292
+ description: "Trigger context for step filtering (default: auto)"
293
+ },
294
+ dryRun: { type: "boolean", description: "Preview without side effects" }
295
+ },
296
+ required: ["persona"]
297
+ }
298
+ };
299
+ async function handleRunPersona(input) {
300
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.persona)) {
301
+ return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.persona}`)));
302
+ }
303
+ const { loadPersona } = await import("./loader-4FIPIFII.js");
304
+ const { runPersona } = await import("./runner-VMYLHWOC.js");
305
+ const { executeSkill } = await import("./skill-executor-RG45LUO5.js");
306
+ const personasDir = resolvePersonasDir();
307
+ const filePath = path2.join(personasDir, `${input.persona}.yaml`);
308
+ if (!filePath.startsWith(personasDir)) {
309
+ return resultToMcpResponse(Err(new Error(`Invalid persona path: ${input.persona}`)));
310
+ }
311
+ const personaResult = loadPersona(filePath);
312
+ if (!personaResult.ok) return resultToMcpResponse(personaResult);
313
+ const projectPath = input.path ? sanitizePath(input.path) : process.cwd();
314
+ const trigger = input.trigger ?? "auto";
315
+ const { ALLOWED_PERSONA_COMMANDS } = await import("./constants-5JGUXPEK.js");
316
+ const commandExecutor = async (command) => {
317
+ if (!ALLOWED_PERSONA_COMMANDS.has(command)) {
318
+ return Err(new Error(`Unknown harness command: ${command}`));
319
+ }
320
+ try {
321
+ const { execFileSync } = await import("child_process");
322
+ const args = ["harness", command];
323
+ if (input.dryRun) args.push("--dry-run");
324
+ const output = execFileSync("npx", args, {
325
+ cwd: projectPath,
326
+ stdio: "pipe",
327
+ timeout: personaResult.value.config.timeout
328
+ });
329
+ return Ok(output.toString());
330
+ } catch (error) {
331
+ return Err(
332
+ new Error(`${command} failed: ${error instanceof Error ? error.message : String(error)}`)
333
+ );
334
+ }
335
+ };
336
+ const report = await runPersona(personaResult.value, {
337
+ trigger,
338
+ commandExecutor,
339
+ skillExecutor: executeSkill,
340
+ projectPath
341
+ });
342
+ return resultToMcpResponse(Ok(report));
343
+ }
344
+
345
+ // src/mcp/tools/agent.ts
346
+ var addComponentDefinition = {
347
+ name: "add_component",
348
+ description: "Add a component (layer, doc, or component type) to the project using the harness CLI",
349
+ inputSchema: {
350
+ type: "object",
351
+ properties: {
352
+ path: { type: "string", description: "Path to project root directory" },
353
+ type: {
354
+ type: "string",
355
+ enum: ["layer", "doc", "component"],
356
+ description: "Type of component to add"
357
+ },
358
+ name: { type: "string", description: "Name of the component to add" }
359
+ },
360
+ required: ["path", "type", "name"]
361
+ }
362
+ };
363
+ var COMPONENT_NAME_REGEX = /^[a-z0-9][a-z0-9._-]{0,64}$/i;
364
+ async function handleAddComponent(input) {
365
+ const projectPath = sanitizePath(input.path);
366
+ const ALLOWED_TYPES = /* @__PURE__ */ new Set(["layer", "doc", "component"]);
367
+ if (!ALLOWED_TYPES.has(input.type)) {
368
+ return {
369
+ content: [
370
+ {
371
+ type: "text",
372
+ text: JSON.stringify({ error: `Invalid component type: ${input.type}` })
373
+ }
374
+ ],
375
+ isError: true
376
+ };
377
+ }
378
+ if (!COMPONENT_NAME_REGEX.test(input.name)) {
379
+ return {
380
+ content: [
381
+ {
382
+ type: "text",
383
+ text: JSON.stringify({
384
+ error: `Invalid component name: must match ${COMPONENT_NAME_REGEX} (lowercase alphanumeric, dots, hyphens, underscores, max 65 chars)`
385
+ })
386
+ }
387
+ ],
388
+ isError: true
389
+ };
390
+ }
391
+ try {
392
+ const { execFileSync } = await import("child_process");
393
+ const output = execFileSync("npx", ["harness", "add", input.type, input.name], {
394
+ cwd: projectPath,
395
+ stdio: "pipe"
396
+ });
397
+ return {
398
+ content: [{ type: "text", text: output.toString() }]
399
+ };
400
+ } catch (error) {
401
+ return {
402
+ content: [
403
+ {
404
+ type: "text",
405
+ text: JSON.stringify({
406
+ error: `add_component failed: ${error instanceof Error ? error.message : String(error)}`
407
+ })
408
+ }
409
+ ],
410
+ isError: true
411
+ };
412
+ }
413
+ }
414
+ var runAgentTaskDefinition = {
415
+ name: "run_agent_task",
416
+ description: "Run an agent task using the harness CLI",
417
+ inputSchema: {
418
+ type: "object",
419
+ properties: {
420
+ task: { type: "string", description: "Task to run" },
421
+ path: { type: "string", description: "Path to project root directory" },
422
+ timeout: { type: "number", description: "Timeout in milliseconds" }
423
+ },
424
+ required: ["task"]
425
+ }
426
+ };
427
+ var ALLOWED_AGENT_TASKS = /* @__PURE__ */ new Set(["review", "doc-review", "test-review"]);
428
+ async function handleRunAgentTask(input) {
429
+ if (!ALLOWED_AGENT_TASKS.has(input.task)) {
430
+ return {
431
+ content: [
432
+ {
433
+ type: "text",
434
+ text: JSON.stringify({
435
+ error: `Invalid task: "${input.task}". Allowed tasks: ${[...ALLOWED_AGENT_TASKS].join(", ")}`
436
+ })
437
+ }
438
+ ],
439
+ isError: true
440
+ };
441
+ }
442
+ const projectPath = input.path ? sanitizePath(input.path) : process.cwd();
443
+ try {
444
+ const { execFileSync } = await import("child_process");
445
+ const output = execFileSync("npx", ["harness", "agent", "run", input.task], {
446
+ cwd: projectPath,
447
+ stdio: "pipe",
448
+ timeout: input.timeout
449
+ });
450
+ return {
451
+ content: [{ type: "text", text: output.toString() }]
452
+ };
453
+ } catch (error) {
454
+ return {
455
+ content: [
456
+ {
457
+ type: "text",
458
+ text: JSON.stringify({
459
+ error: `run_agent_task failed: ${error instanceof Error ? error.message : String(error)}`
460
+ })
461
+ }
462
+ ],
463
+ isError: true
464
+ };
465
+ }
466
+ }
467
+
468
+ // src/mcp/tools/skill.ts
469
+ import * as fs from "fs";
470
+ import * as path3 from "path";
471
+ var runSkillDefinition = {
472
+ name: "run_skill",
473
+ description: "Load and return the content of a skill (SKILL.md), optionally with project state context",
474
+ inputSchema: {
475
+ type: "object",
476
+ properties: {
477
+ skill: { type: "string", description: "Skill name (e.g., harness-tdd)" },
478
+ path: { type: "string", description: "Path to project root for state context injection" },
479
+ complexity: {
480
+ type: "string",
481
+ enum: ["auto", "light", "full"],
482
+ description: "Complexity level for scale-adaptive rigor"
483
+ },
484
+ phase: { type: "string", description: "Start at a specific phase (re-entry)" },
485
+ party: { type: "boolean", description: "Enable multi-perspective evaluation" }
486
+ },
487
+ required: ["skill"]
488
+ }
489
+ };
490
+ async function handleRunSkill(input) {
491
+ const skillsDir = resolveSkillsDir();
492
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.skill)) {
493
+ return resultToMcpResponse(Err(new Error(`Invalid skill name: ${input.skill}`)));
494
+ }
495
+ const skillDir = path3.join(skillsDir, input.skill);
496
+ if (!skillDir.startsWith(skillsDir)) {
497
+ return resultToMcpResponse(Err(new Error(`Invalid skill path: ${input.skill}`)));
498
+ }
499
+ if (!fs.existsSync(skillDir)) {
500
+ return resultToMcpResponse(Err(new Error(`Skill not found: ${input.skill}`)));
501
+ }
502
+ const skillMdPath = path3.join(skillDir, "SKILL.md");
503
+ if (!fs.existsSync(skillMdPath)) {
504
+ return resultToMcpResponse(Err(new Error(`SKILL.md not found for skill: ${input.skill}`)));
505
+ }
506
+ let content = fs.readFileSync(skillMdPath, "utf-8");
507
+ if (input.path) {
508
+ const projectPath = sanitizePath(input.path);
509
+ const stateFile = path3.join(projectPath, ".harness", "state.json");
510
+ if (fs.existsSync(stateFile)) {
511
+ const stateContent = fs.readFileSync(stateFile, "utf-8");
512
+ content += `
513
+
514
+ ---
515
+ ## Project State
516
+ \`\`\`json
517
+ ${stateContent}
518
+ \`\`\`
519
+ `;
520
+ }
521
+ }
522
+ return resultToMcpResponse(Ok(content));
523
+ }
524
+ var createSkillDefinition = {
525
+ name: "create_skill",
526
+ description: "Scaffold a new harness skill with skill.yaml and SKILL.md",
527
+ inputSchema: {
528
+ type: "object",
529
+ properties: {
530
+ path: { type: "string", description: "Path to project root directory" },
531
+ name: { type: "string", description: "Skill name in kebab-case (e.g., my-new-skill)" },
532
+ description: { type: "string", description: "Skill description" },
533
+ cognitiveMode: {
534
+ type: "string",
535
+ enum: [
536
+ "adversarial-reviewer",
537
+ "constructive-architect",
538
+ "meticulous-implementer",
539
+ "diagnostic-investigator",
540
+ "advisory-guide",
541
+ "meticulous-verifier"
542
+ ],
543
+ description: "Cognitive mode (default: constructive-architect)"
544
+ }
545
+ },
546
+ required: ["path", "name", "description"]
547
+ }
548
+ };
549
+ async function handleCreateSkill(input) {
550
+ try {
551
+ const { generateSkillFiles } = await import("./create-skill-WPXHSLX2.js");
552
+ const result = generateSkillFiles({
553
+ name: input.name,
554
+ description: input.description,
555
+ cognitiveMode: input.cognitiveMode ?? "constructive-architect",
556
+ outputDir: sanitizePath(input.path)
557
+ });
558
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
559
+ } catch (error) {
560
+ return {
561
+ content: [
562
+ {
563
+ type: "text",
564
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
565
+ }
566
+ ],
567
+ isError: true
568
+ };
569
+ }
570
+ }
571
+
572
+ // src/mcp/resources/skills.ts
573
+ import * as fs2 from "fs";
574
+ import * as path4 from "path";
575
+ import * as yaml from "yaml";
576
+ async function getSkillsResource(projectRoot) {
577
+ const skillsDir = path4.join(projectRoot, "agents", "skills", "claude-code");
578
+ const skills = [];
579
+ if (!fs2.existsSync(skillsDir)) {
580
+ return JSON.stringify(skills, null, 2);
581
+ }
582
+ const entries = fs2.readdirSync(skillsDir, { withFileTypes: true });
583
+ for (const entry of entries) {
584
+ if (!entry.isDirectory()) continue;
585
+ const skillYamlPath = path4.join(skillsDir, entry.name, "skill.yaml");
586
+ if (!fs2.existsSync(skillYamlPath)) continue;
587
+ try {
588
+ const content = fs2.readFileSync(skillYamlPath, "utf-8");
589
+ const parsed = yaml.parse(content);
590
+ skills.push({
591
+ name: parsed.name,
592
+ description: parsed.description,
593
+ cognitive_mode: parsed.cognitive_mode,
594
+ type: parsed.type,
595
+ triggers: parsed.triggers
596
+ });
597
+ } catch {
598
+ }
599
+ }
600
+ return JSON.stringify(skills, null, 2);
601
+ }
602
+
603
+ // src/mcp/resources/rules.ts
604
+ import * as fs3 from "fs";
605
+ import * as path5 from "path";
606
+ async function getRulesResource(projectRoot) {
607
+ const rules = [];
608
+ const configPath = path5.join(projectRoot, "harness.config.json");
609
+ if (fs3.existsSync(configPath)) {
610
+ try {
611
+ const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
612
+ if (config.layers) {
613
+ rules.push({ type: "layer-enforcement", config: config.layers });
614
+ }
615
+ if (config.phaseGates) {
616
+ rules.push({ type: "phase-gates", config: config.phaseGates });
617
+ }
618
+ if (config.rules) {
619
+ rules.push({ type: "custom-rules", config: config.rules });
620
+ }
621
+ } catch {
622
+ }
623
+ }
624
+ const linterPath = path5.join(projectRoot, ".harness", "linter.json");
625
+ if (fs3.existsSync(linterPath)) {
626
+ try {
627
+ const linterConfig = JSON.parse(fs3.readFileSync(linterPath, "utf-8"));
628
+ rules.push({ type: "linter", config: linterConfig });
629
+ } catch {
630
+ }
631
+ }
632
+ return JSON.stringify(rules, null, 2);
633
+ }
634
+
635
+ // src/mcp/resources/project.ts
636
+ import * as fs4 from "fs";
637
+ import * as path6 from "path";
638
+ async function getProjectResource(projectRoot) {
639
+ const agentsPath = path6.join(projectRoot, "AGENTS.md");
640
+ if (fs4.existsSync(agentsPath)) {
641
+ return fs4.readFileSync(agentsPath, "utf-8");
642
+ }
643
+ return "# No AGENTS.md found";
644
+ }
645
+
646
+ // src/mcp/resources/learnings.ts
647
+ import * as fs5 from "fs";
648
+ import * as path7 from "path";
649
+ async function getLearningsResource(projectRoot) {
650
+ const sections = [];
651
+ const reviewPath = path7.join(projectRoot, ".harness", "review-learnings.md");
652
+ if (fs5.existsSync(reviewPath)) {
653
+ sections.push("## Review Learnings\n\n" + fs5.readFileSync(reviewPath, "utf-8"));
654
+ }
655
+ const antiPath = path7.join(projectRoot, ".harness", "anti-patterns.md");
656
+ if (fs5.existsSync(antiPath)) {
657
+ sections.push("## Anti-Pattern Log\n\n" + fs5.readFileSync(antiPath, "utf-8"));
658
+ }
659
+ return sections.length > 0 ? sections.join("\n\n---\n\n") : "No learnings files found.";
660
+ }
661
+
662
+ // src/mcp/tools/state.ts
663
+ var manageStateDefinition = {
664
+ name: "manage_state",
665
+ description: "Manage harness project state: show current state, record learnings/failures, archive failures, reset state, run mechanical gate checks, or save/load session handoff",
666
+ inputSchema: {
667
+ type: "object",
668
+ properties: {
669
+ path: { type: "string", description: "Path to project root" },
670
+ action: {
671
+ type: "string",
672
+ enum: [
673
+ "show",
674
+ "learn",
675
+ "failure",
676
+ "archive",
677
+ "reset",
678
+ "gate",
679
+ "save-handoff",
680
+ "load-handoff"
681
+ ],
682
+ description: "Action to perform"
683
+ },
684
+ learning: { type: "string", description: "Learning text to record (required for learn)" },
685
+ skillName: { type: "string", description: "Skill name associated with the entry" },
686
+ outcome: { type: "string", description: "Outcome associated with the learning" },
687
+ description: { type: "string", description: "Failure description (required for failure)" },
688
+ failureType: { type: "string", description: "Type of failure (required for failure)" },
689
+ handoff: { type: "object", description: "Handoff data to save (required for save-handoff)" },
690
+ stream: {
691
+ type: "string",
692
+ description: "Stream name to target (auto-resolves from branch if omitted)"
693
+ }
694
+ },
695
+ required: ["path", "action"]
696
+ }
697
+ };
698
+ async function handleManageState(input) {
699
+ try {
700
+ const {
701
+ loadState,
702
+ saveState,
703
+ appendLearning,
704
+ appendFailure,
705
+ archiveFailures,
706
+ runMechanicalGate,
707
+ DEFAULT_STATE
708
+ } = await import("./dist-JVZ2MKBC.js");
709
+ const projectPath = sanitizePath(input.path);
710
+ switch (input.action) {
711
+ case "show": {
712
+ const result = await loadState(projectPath, input.stream);
713
+ return resultToMcpResponse(result);
714
+ }
715
+ case "learn": {
716
+ if (!input.learning) {
717
+ return {
718
+ content: [
719
+ { type: "text", text: "Error: learning is required for learn action" }
720
+ ],
721
+ isError: true
722
+ };
723
+ }
724
+ const result = await appendLearning(
725
+ projectPath,
726
+ input.learning,
727
+ input.skillName,
728
+ input.outcome,
729
+ input.stream
730
+ );
731
+ if (!result.ok) return resultToMcpResponse(result);
732
+ return resultToMcpResponse(Ok({ recorded: true }));
733
+ }
734
+ case "failure": {
735
+ if (!input.description) {
736
+ return {
737
+ content: [
738
+ { type: "text", text: "Error: description is required for failure action" }
739
+ ],
740
+ isError: true
741
+ };
742
+ }
743
+ if (!input.failureType) {
744
+ return {
745
+ content: [
746
+ {
747
+ type: "text",
748
+ text: "Error: failureType is required for failure action"
749
+ }
750
+ ],
751
+ isError: true
752
+ };
753
+ }
754
+ const result = await appendFailure(
755
+ projectPath,
756
+ input.description,
757
+ input.skillName ?? "unknown",
758
+ input.failureType,
759
+ input.stream
760
+ );
761
+ if (!result.ok) return resultToMcpResponse(result);
762
+ return resultToMcpResponse(Ok({ recorded: true }));
763
+ }
764
+ case "archive": {
765
+ const result = await archiveFailures(projectPath, input.stream);
766
+ if (!result.ok) return resultToMcpResponse(result);
767
+ return resultToMcpResponse(Ok({ archived: true }));
768
+ }
769
+ case "reset": {
770
+ const result = await saveState(projectPath, { ...DEFAULT_STATE }, input.stream);
771
+ if (!result.ok) return resultToMcpResponse(result);
772
+ return resultToMcpResponse(Ok({ reset: true }));
773
+ }
774
+ case "gate": {
775
+ const result = await runMechanicalGate(projectPath);
776
+ return resultToMcpResponse(result);
777
+ }
778
+ case "save-handoff": {
779
+ if (!input.handoff) {
780
+ return {
781
+ content: [
782
+ { type: "text", text: "Error: handoff is required for save-handoff action" }
783
+ ],
784
+ isError: true
785
+ };
786
+ }
787
+ const { saveHandoff } = await import("./dist-JVZ2MKBC.js");
788
+ const result = await saveHandoff(
789
+ projectPath,
790
+ input.handoff,
791
+ input.stream
792
+ );
793
+ return resultToMcpResponse(result.ok ? Ok({ saved: true }) : result);
794
+ }
795
+ case "load-handoff": {
796
+ const { loadHandoff } = await import("./dist-JVZ2MKBC.js");
797
+ const result = await loadHandoff(projectPath, input.stream);
798
+ return resultToMcpResponse(result);
799
+ }
800
+ default: {
801
+ return {
802
+ content: [{ type: "text", text: `Error: unknown action` }],
803
+ isError: true
804
+ };
805
+ }
806
+ }
807
+ } catch (error) {
808
+ return {
809
+ content: [
810
+ {
811
+ type: "text",
812
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
813
+ }
814
+ ],
815
+ isError: true
816
+ };
817
+ }
818
+ }
819
+ var listStreamsDefinition = {
820
+ name: "list_streams",
821
+ description: "List known state streams with branch associations and last-active timestamps",
822
+ inputSchema: {
823
+ type: "object",
824
+ properties: {
825
+ path: { type: "string", description: "Path to project root" }
826
+ },
827
+ required: ["path"]
828
+ }
829
+ };
830
+ async function handleListStreams(input) {
831
+ try {
832
+ const { listStreams, loadStreamIndex } = await import("./dist-JVZ2MKBC.js");
833
+ const projectPath = sanitizePath(input.path);
834
+ const indexResult = await loadStreamIndex(projectPath);
835
+ const streamsResult = await listStreams(projectPath);
836
+ if (!streamsResult.ok) return resultToMcpResponse(streamsResult);
837
+ return resultToMcpResponse(
838
+ Ok({
839
+ activeStream: indexResult.ok ? indexResult.value.activeStream : null,
840
+ streams: streamsResult.value
841
+ })
842
+ );
843
+ } catch (error) {
844
+ return {
845
+ content: [
846
+ {
847
+ type: "text",
848
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
849
+ }
850
+ ],
851
+ isError: true
852
+ };
853
+ }
854
+ }
855
+
856
+ // src/mcp/tools/phase-gate.ts
857
+ var checkPhaseGateDefinition = {
858
+ name: "check_phase_gate",
859
+ description: "Verify implementation-to-spec mappings: checks that each implementation file has a corresponding spec document",
860
+ inputSchema: {
861
+ type: "object",
862
+ properties: {
863
+ path: { type: "string", description: "Path to project root directory" }
864
+ },
865
+ required: ["path"]
866
+ }
867
+ };
868
+ async function handleCheckPhaseGate(input) {
869
+ try {
870
+ const { runCheckPhaseGate } = await import("./check-phase-gate-VZFOY2PO.js");
871
+ const result = await runCheckPhaseGate({ cwd: sanitizePath(input.path) });
872
+ if (result.ok) {
873
+ return { content: [{ type: "text", text: JSON.stringify(result.value) }] };
874
+ }
875
+ return { content: [{ type: "text", text: result.error.message }], isError: true };
876
+ } catch (error) {
877
+ return {
878
+ content: [
879
+ {
880
+ type: "text",
881
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
882
+ }
883
+ ],
884
+ isError: true
885
+ };
886
+ }
887
+ }
888
+
889
+ // src/mcp/tools/cross-check.ts
890
+ import * as path8 from "path";
891
+ var validateCrossCheckDefinition = {
892
+ name: "validate_cross_check",
893
+ description: "Validate plan-to-implementation coverage: checks that specs have plans and plans have implementations, detects staleness",
894
+ inputSchema: {
895
+ type: "object",
896
+ properties: {
897
+ path: { type: "string", description: "Path to project root directory" },
898
+ specsDir: {
899
+ type: "string",
900
+ description: "Specs directory relative to project root (default: docs/specs)"
901
+ },
902
+ plansDir: {
903
+ type: "string",
904
+ description: "Plans directory relative to project root (default: docs/plans)"
905
+ }
906
+ },
907
+ required: ["path"]
908
+ }
909
+ };
910
+ async function handleValidateCrossCheck(input) {
911
+ let projectPath;
912
+ try {
913
+ projectPath = sanitizePath(input.path);
914
+ } catch (error) {
915
+ return {
916
+ content: [
917
+ {
918
+ type: "text",
919
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
920
+ }
921
+ ],
922
+ isError: true
923
+ };
924
+ }
925
+ try {
926
+ const { runCrossCheck } = await import("./validate-cross-check-DB7RIFFF.js");
927
+ const specsDir = path8.resolve(projectPath, input.specsDir ?? "docs/specs");
928
+ if (!specsDir.startsWith(projectPath)) {
929
+ return {
930
+ content: [{ type: "text", text: "Error: specsDir escapes project root" }],
931
+ isError: true
932
+ };
933
+ }
934
+ const plansDir = path8.resolve(projectPath, input.plansDir ?? "docs/plans");
935
+ if (!plansDir.startsWith(projectPath)) {
936
+ return {
937
+ content: [{ type: "text", text: "Error: plansDir escapes project root" }],
938
+ isError: true
939
+ };
940
+ }
941
+ const result = await runCrossCheck({
942
+ projectPath,
943
+ specsDir,
944
+ plansDir
945
+ });
946
+ if (result.ok) {
947
+ return { content: [{ type: "text", text: JSON.stringify(result.value) }] };
948
+ }
949
+ return { content: [{ type: "text", text: result.error.message }], isError: true };
950
+ } catch (error) {
951
+ return {
952
+ content: [
953
+ {
954
+ type: "text",
955
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
956
+ }
957
+ ],
958
+ isError: true
959
+ };
960
+ }
961
+ }
962
+
963
+ // src/commands/generate-slash-commands.ts
964
+ import { Command } from "commander";
965
+ import fs7 from "fs";
966
+ import path10 from "path";
967
+ import os from "os";
968
+ import readline from "readline";
969
+
970
+ // src/slash-commands/normalize.ts
971
+ import fs6 from "fs";
972
+ import path9 from "path";
973
+ import { parse as parse2 } from "yaml";
974
+
975
+ // src/slash-commands/normalize-name.ts
976
+ function normalizeName(skillName) {
977
+ let name = skillName;
978
+ if (name.startsWith("harness-")) {
979
+ name = name.slice("harness-".length);
980
+ }
981
+ name = name.replace(/-harness-/g, "-");
982
+ if (name.endsWith("-harness")) {
983
+ name = name.slice(0, -"-harness".length);
984
+ }
985
+ return name;
986
+ }
987
+
988
+ // src/slash-commands/normalize.ts
989
+ function normalizeSkills(skillSources, platforms) {
990
+ const specs = [];
991
+ const nameMap = /* @__PURE__ */ new Map();
992
+ for (const { dir: skillsDir, source } of skillSources) {
993
+ if (!fs6.existsSync(skillsDir)) continue;
994
+ const entries = fs6.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
995
+ for (const entry of entries) {
996
+ const yamlPath = path9.join(skillsDir, entry.name, "skill.yaml");
997
+ if (!fs6.existsSync(yamlPath)) continue;
998
+ let raw;
999
+ try {
1000
+ raw = fs6.readFileSync(yamlPath, "utf-8");
1001
+ } catch {
1002
+ continue;
1003
+ }
1004
+ const parsed = parse2(raw);
1005
+ const result = SkillMetadataSchema.safeParse(parsed);
1006
+ if (!result.success) {
1007
+ console.warn(`Skipping ${entry.name}: invalid skill.yaml`);
1008
+ continue;
1009
+ }
1010
+ const meta = result.data;
1011
+ const matchesPlatform = platforms.some((p) => meta.platforms.includes(p));
1012
+ if (!matchesPlatform) continue;
1013
+ const normalized = normalizeName(meta.name);
1014
+ const existing = nameMap.get(normalized);
1015
+ if (existing) {
1016
+ if (existing.source === source) {
1017
+ throw new Error(
1018
+ `Name collision: skills "${existing.skillName}" and "${meta.name}" both normalize to "${normalized}"`
1019
+ );
1020
+ }
1021
+ continue;
1022
+ }
1023
+ nameMap.set(normalized, { skillName: meta.name, source });
1024
+ const skillMdPath = path9.join(skillsDir, entry.name, "SKILL.md");
1025
+ const skillMdContent = fs6.existsSync(skillMdPath) ? fs6.readFileSync(skillMdPath, "utf-8") : "";
1026
+ const skillMdRelative = path9.relative(
1027
+ process.cwd(),
1028
+ path9.join(skillsDir, entry.name, "SKILL.md")
1029
+ );
1030
+ const skillYamlRelative = path9.relative(
1031
+ process.cwd(),
1032
+ path9.join(skillsDir, entry.name, "skill.yaml")
1033
+ );
1034
+ const args = (meta.cli?.args ?? []).map((a) => ({
1035
+ name: a.name,
1036
+ description: a.description ?? "",
1037
+ required: a.required ?? false
1038
+ }));
1039
+ const tools = [...meta.tools ?? []];
1040
+ if (!tools.includes("Read")) {
1041
+ tools.push("Read");
1042
+ }
1043
+ const contextLines = [];
1044
+ if (meta.cognitive_mode) {
1045
+ contextLines.push(`Cognitive mode: ${meta.cognitive_mode}`);
1046
+ }
1047
+ if (meta.type) {
1048
+ contextLines.push(`Type: ${meta.type}`);
1049
+ }
1050
+ if (meta.state?.persistent) {
1051
+ const files = meta.state.files?.join(", ") ?? "";
1052
+ contextLines.push(`State: persistent${files ? ` (files: ${files})` : ""}`);
1053
+ }
1054
+ const objectiveLines = [meta.description];
1055
+ if (meta.phases && meta.phases.length > 0) {
1056
+ objectiveLines.push("");
1057
+ objectiveLines.push("Phases:");
1058
+ for (const phase of meta.phases) {
1059
+ const req = phase.required !== false ? "" : " (optional)";
1060
+ objectiveLines.push(`- ${phase.name}: ${phase.description}${req}`);
1061
+ }
1062
+ }
1063
+ const executionContextLines = [];
1064
+ if (skillMdContent) {
1065
+ executionContextLines.push(`@${skillMdRelative}`);
1066
+ executionContextLines.push(`@${skillYamlRelative}`);
1067
+ }
1068
+ const processLines = [];
1069
+ if (meta.mcp?.tool) {
1070
+ processLines.push(
1071
+ `1. Try: invoke mcp__harness__${meta.mcp.tool} with skill: "${meta.name}"`
1072
+ );
1073
+ processLines.push(`2. If MCP unavailable: read SKILL.md and follow its workflow directly`);
1074
+ processLines.push(`3. Pass through any arguments provided by the user`);
1075
+ } else {
1076
+ processLines.push(`1. Read SKILL.md and follow its workflow directly`);
1077
+ processLines.push(`2. Pass through any arguments provided by the user`);
1078
+ }
1079
+ specs.push({
1080
+ name: normalized,
1081
+ namespace: "harness",
1082
+ fullName: `harness:${normalized}`,
1083
+ description: meta.description,
1084
+ version: meta.version,
1085
+ ...meta.cognitive_mode ? { cognitiveMode: meta.cognitive_mode } : {},
1086
+ tools,
1087
+ args,
1088
+ skillYamlName: meta.name,
1089
+ sourceDir: entry.name,
1090
+ skillsBaseDir: skillsDir,
1091
+ source,
1092
+ prompt: {
1093
+ context: contextLines.join("\n"),
1094
+ objective: objectiveLines.join("\n"),
1095
+ executionContext: executionContextLines.join("\n"),
1096
+ process: processLines.join("\n")
1097
+ }
1098
+ });
1099
+ }
1100
+ }
1101
+ return specs;
1102
+ }
1103
+
1104
+ // src/slash-commands/argument-hint.ts
1105
+ function buildArgumentHint(args) {
1106
+ return args.map((arg) => arg.required ? `--${arg.name} <${arg.name}>` : `[--${arg.name} <${arg.name}>]`).join(" ");
1107
+ }
1108
+
1109
+ // src/slash-commands/render-claude-code.ts
1110
+ function renderClaudeCode(spec) {
1111
+ const lines = ["---"];
1112
+ lines.push(`name: ${spec.fullName}`);
1113
+ lines.push(`description: ${spec.description}`);
1114
+ const hint = buildArgumentHint(spec.args);
1115
+ if (hint) {
1116
+ lines.push(`argument-hint: "${hint}"`);
1117
+ }
1118
+ if (spec.tools.length > 0) {
1119
+ lines.push("allowed-tools:");
1120
+ for (const tool of spec.tools) {
1121
+ lines.push(` - ${tool}`);
1122
+ }
1123
+ }
1124
+ lines.push("---");
1125
+ lines.push("");
1126
+ lines.push(GENERATED_HEADER_CLAUDE);
1127
+ lines.push("");
1128
+ lines.push("<context>");
1129
+ lines.push(spec.prompt.context);
1130
+ lines.push("</context>");
1131
+ lines.push("");
1132
+ lines.push("<objective>");
1133
+ lines.push(spec.prompt.objective);
1134
+ lines.push("</objective>");
1135
+ lines.push("");
1136
+ if (spec.prompt.executionContext) {
1137
+ lines.push("<execution_context>");
1138
+ lines.push(spec.prompt.executionContext);
1139
+ lines.push("</execution_context>");
1140
+ lines.push("");
1141
+ }
1142
+ lines.push("<process>");
1143
+ lines.push(spec.prompt.process);
1144
+ lines.push("</process>");
1145
+ lines.push("");
1146
+ return lines.join("\n");
1147
+ }
1148
+
1149
+ // src/slash-commands/render-gemini.ts
1150
+ function escapeTomlLiteral(content) {
1151
+ return content.replace(/'''/g, "''\\'''");
1152
+ }
1153
+ function renderGemini(spec, skillMdContent, skillYamlContent) {
1154
+ const lines = [GENERATED_HEADER_GEMINI];
1155
+ const safeDesc = spec.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1156
+ lines.push(`description = "${safeDesc}"`);
1157
+ lines.push("prompt = '''");
1158
+ lines.push("<context>");
1159
+ lines.push(spec.prompt.context);
1160
+ lines.push("</context>");
1161
+ lines.push("");
1162
+ lines.push("<objective>");
1163
+ lines.push(spec.prompt.objective);
1164
+ lines.push("</objective>");
1165
+ lines.push("");
1166
+ if (skillMdContent || skillYamlContent) {
1167
+ lines.push("<execution_context>");
1168
+ if (skillMdContent) {
1169
+ const mdPath = spec.prompt.executionContext.split("\n")[0]?.replace(/^@/, "") ?? "";
1170
+ lines.push(`--- SKILL.md (${mdPath}) ---`);
1171
+ lines.push(escapeTomlLiteral(skillMdContent));
1172
+ lines.push("");
1173
+ }
1174
+ if (skillYamlContent) {
1175
+ const refs = spec.prompt.executionContext.split("\n");
1176
+ const yamlPath = (refs[1] ?? refs[0] ?? "").replace(/^@/, "");
1177
+ lines.push(`--- skill.yaml (${yamlPath}) ---`);
1178
+ lines.push(escapeTomlLiteral(skillYamlContent));
1179
+ }
1180
+ lines.push("</execution_context>");
1181
+ lines.push("");
1182
+ }
1183
+ const geminiProcess = spec.prompt.process.replace(
1184
+ "read SKILL.md and follow its workflow directly",
1185
+ "follow the SKILL.md workflow provided above directly"
1186
+ );
1187
+ lines.push("<process>");
1188
+ lines.push(geminiProcess);
1189
+ lines.push("</process>");
1190
+ lines.push("'''");
1191
+ lines.push("");
1192
+ return lines.join("\n");
1193
+ }
1194
+
1195
+ // src/commands/generate-slash-commands.ts
1196
+ function resolveOutputDir(platform, opts) {
1197
+ if (opts.output) {
1198
+ return path10.join(opts.output, "harness");
1199
+ }
1200
+ if (opts.global) {
1201
+ const home = os.homedir();
1202
+ return platform === "claude-code" ? path10.join(home, ".claude", "commands", "harness") : path10.join(home, ".gemini", "commands", "harness");
1203
+ }
1204
+ return platform === "claude-code" ? path10.join("agents", "commands", "claude-code", "harness") : path10.join("agents", "commands", "gemini-cli", "harness");
1205
+ }
1206
+ function fileExtension(platform) {
1207
+ return platform === "claude-code" ? ".md" : ".toml";
1208
+ }
1209
+ async function confirmDeletion(files) {
1210
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1211
+ return new Promise((resolve2) => {
1212
+ rl.question(`
1213
+ Remove ${files.length} orphaned command(s)? (y/N) `, (answer) => {
1214
+ rl.close();
1215
+ resolve2(answer.toLowerCase() === "y");
1216
+ });
1217
+ });
1218
+ }
1219
+ function generateSlashCommands(opts) {
1220
+ const skillSources = [];
1221
+ if (opts.skillsDir) {
1222
+ skillSources.push({ dir: opts.skillsDir, source: "project" });
1223
+ } else {
1224
+ const projectDir = resolveProjectSkillsDir();
1225
+ if (projectDir) {
1226
+ skillSources.push({ dir: projectDir, source: "project" });
1227
+ }
1228
+ const communityDir = resolveCommunitySkillsDir();
1229
+ if (fs7.existsSync(communityDir)) {
1230
+ skillSources.push({ dir: communityDir, source: "community" });
1231
+ }
1232
+ if (opts.includeGlobal || skillSources.length === 0) {
1233
+ const globalDir = resolveGlobalSkillsDir();
1234
+ if (!projectDir || path10.resolve(globalDir) !== path10.resolve(projectDir)) {
1235
+ skillSources.push({ dir: globalDir, source: "global" });
1236
+ }
1237
+ }
1238
+ }
1239
+ const specs = normalizeSkills(skillSources, opts.platforms);
1240
+ const results = [];
1241
+ for (const platform of opts.platforms) {
1242
+ const outputDir = resolveOutputDir(platform, opts);
1243
+ const ext = fileExtension(platform);
1244
+ const useAbsolutePaths = opts.global;
1245
+ const rendered = /* @__PURE__ */ new Map();
1246
+ for (const spec of specs) {
1247
+ const filename = `${spec.name}${ext}`;
1248
+ if (platform === "claude-code") {
1249
+ const renderSpec = useAbsolutePaths ? {
1250
+ ...spec,
1251
+ prompt: {
1252
+ ...spec.prompt,
1253
+ executionContext: spec.prompt.executionContext.split("\n").map((line) => {
1254
+ if (line.startsWith("@")) {
1255
+ const relPath = line.slice(1);
1256
+ return `@${path10.resolve(relPath)}`;
1257
+ }
1258
+ return line;
1259
+ }).join("\n")
1260
+ }
1261
+ } : spec;
1262
+ rendered.set(filename, renderClaudeCode(renderSpec));
1263
+ } else {
1264
+ const mdPath = path10.join(spec.skillsBaseDir, spec.sourceDir, "SKILL.md");
1265
+ const yamlPath = path10.join(spec.skillsBaseDir, spec.sourceDir, "skill.yaml");
1266
+ const mdContent = fs7.existsSync(mdPath) ? fs7.readFileSync(mdPath, "utf-8") : "";
1267
+ const yamlContent = fs7.existsSync(yamlPath) ? fs7.readFileSync(yamlPath, "utf-8") : "";
1268
+ rendered.set(filename, renderGemini(spec, mdContent, yamlContent));
1269
+ }
1270
+ }
1271
+ const plan = computeSyncPlan(outputDir, rendered);
1272
+ if (!opts.dryRun) {
1273
+ applySyncPlan(outputDir, rendered, plan, false);
1274
+ }
1275
+ results.push({
1276
+ platform,
1277
+ added: plan.added,
1278
+ updated: plan.updated,
1279
+ removed: plan.removed,
1280
+ unchanged: plan.unchanged,
1281
+ outputDir
1282
+ });
1283
+ }
1284
+ return results;
1285
+ }
1286
+ async function handleOrphanDeletion(results, opts) {
1287
+ if (opts.dryRun) return;
1288
+ for (const result of results) {
1289
+ if (result.removed.length === 0) continue;
1290
+ const shouldDelete = opts.yes || await confirmDeletion(result.removed);
1291
+ if (shouldDelete) {
1292
+ for (const filename of result.removed) {
1293
+ const filePath = path10.join(result.outputDir, filename);
1294
+ if (fs7.existsSync(filePath)) {
1295
+ fs7.unlinkSync(filePath);
1296
+ }
1297
+ }
1298
+ }
1299
+ }
1300
+ }
1301
+ function createGenerateSlashCommandsCommand() {
1302
+ return new Command("generate-slash-commands").description(
1303
+ "Generate native slash commands for Claude Code and Gemini CLI from skill metadata"
1304
+ ).option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global config directories", false).option("--include-global", "Include built-in global skills alongside project skills", false).option("--output <dir>", "Custom output directory").option("--skills-dir <path>", "Skills directory to scan").option("--dry-run", "Show what would change without writing", false).option("--yes", "Skip deletion confirmation prompts", false).action(async (opts, cmd) => {
1305
+ const globalOpts = cmd.optsWithGlobals();
1306
+ const platforms = opts.platforms.split(",").map((p) => p.trim());
1307
+ for (const p of platforms) {
1308
+ if (!VALID_PLATFORMS.includes(p)) {
1309
+ throw new CLIError(
1310
+ `Invalid platform "${p}". Valid platforms: ${VALID_PLATFORMS.join(", ")}`,
1311
+ ExitCode.VALIDATION_FAILED
1312
+ );
1313
+ }
1314
+ }
1315
+ const generateOpts = {
1316
+ platforms,
1317
+ global: opts.global,
1318
+ includeGlobal: opts.includeGlobal,
1319
+ output: opts.output,
1320
+ skillsDir: opts.skillsDir ?? "",
1321
+ dryRun: opts.dryRun,
1322
+ yes: opts.yes
1323
+ };
1324
+ try {
1325
+ const results = generateSlashCommands(generateOpts);
1326
+ if (globalOpts.json) {
1327
+ console.log(JSON.stringify(results, null, 2));
1328
+ return;
1329
+ }
1330
+ const totalCommands = results.reduce(
1331
+ (sum, r) => sum + r.added.length + r.updated.length + r.unchanged.length,
1332
+ 0
1333
+ );
1334
+ if (totalCommands === 0) {
1335
+ console.log(
1336
+ "\nNo skills found. Use --include-global to include built-in skills, or create a skill with: harness create-skill"
1337
+ );
1338
+ return;
1339
+ }
1340
+ for (const result of results) {
1341
+ console.log(`
1342
+ ${result.platform} \u2192 ${result.outputDir}`);
1343
+ if (result.added.length > 0) {
1344
+ console.log(` + ${result.added.length} new: ${result.added.join(", ")}`);
1345
+ }
1346
+ if (result.updated.length > 0) {
1347
+ console.log(` ~ ${result.updated.length} updated: ${result.updated.join(", ")}`);
1348
+ }
1349
+ if (result.removed.length > 0) {
1350
+ console.log(` - ${result.removed.length} removed: ${result.removed.join(", ")}`);
1351
+ }
1352
+ if (result.unchanged.length > 0) {
1353
+ console.log(` = ${result.unchanged.length} unchanged`);
1354
+ }
1355
+ if (opts.dryRun) {
1356
+ console.log(" (dry run \u2014 no files written)");
1357
+ }
1358
+ }
1359
+ await handleOrphanDeletion(results, { yes: opts.yes, dryRun: opts.dryRun });
1360
+ } catch (error) {
1361
+ handleError(error);
1362
+ }
1363
+ });
1364
+ }
1365
+
1366
+ // src/mcp/tools/generate-slash-commands.ts
1367
+ var generateSlashCommandsDefinition = {
1368
+ name: "generate_slash_commands",
1369
+ description: "Generate native slash commands for Claude Code and Gemini CLI from harness skill metadata",
1370
+ inputSchema: {
1371
+ type: "object",
1372
+ properties: {
1373
+ platforms: {
1374
+ type: "string",
1375
+ description: "Comma-separated platforms: claude-code,gemini-cli (default: both)"
1376
+ },
1377
+ global: {
1378
+ type: "boolean",
1379
+ description: "Write to global config directories (~/.claude/commands/, ~/.gemini/commands/)"
1380
+ },
1381
+ output: {
1382
+ type: "string",
1383
+ description: "Custom output directory"
1384
+ },
1385
+ skillsDir: {
1386
+ type: "string",
1387
+ description: "Skills directory to scan"
1388
+ },
1389
+ includeGlobal: {
1390
+ type: "boolean",
1391
+ description: "Include built-in global skills alongside project skills"
1392
+ },
1393
+ dryRun: {
1394
+ type: "boolean",
1395
+ description: "Show what would change without writing files"
1396
+ }
1397
+ }
1398
+ }
1399
+ };
1400
+ async function handleGenerateSlashCommands(input) {
1401
+ try {
1402
+ const platforms = (input.platforms ?? "claude-code,gemini-cli").split(",").map((p) => p.trim());
1403
+ const results = generateSlashCommands({
1404
+ platforms,
1405
+ global: input.global ?? false,
1406
+ includeGlobal: input.includeGlobal ?? false,
1407
+ ...input.output !== void 0 && { output: sanitizePath(input.output) },
1408
+ skillsDir: input.skillsDir ? sanitizePath(input.skillsDir) : "",
1409
+ dryRun: input.dryRun ?? false,
1410
+ yes: true
1411
+ });
1412
+ return resultToMcpResponse(Ok(JSON.stringify(results, null, 2)));
1413
+ } catch (error) {
1414
+ return resultToMcpResponse(Err(error instanceof Error ? error : new Error(String(error))));
1415
+ }
1416
+ }
1417
+
1418
+ // src/mcp/resources/state.ts
1419
+ async function getStateResource(projectRoot) {
1420
+ try {
1421
+ const { loadState, migrateToStreams } = await import("./dist-JVZ2MKBC.js");
1422
+ await migrateToStreams(projectRoot);
1423
+ const result = await loadState(projectRoot);
1424
+ if (result.ok) {
1425
+ return JSON.stringify(result.value, null, 2);
1426
+ }
1427
+ return JSON.stringify({
1428
+ schemaVersion: 1,
1429
+ position: {},
1430
+ decisions: [],
1431
+ blockers: [],
1432
+ progress: {}
1433
+ });
1434
+ } catch {
1435
+ return JSON.stringify({
1436
+ schemaVersion: 1,
1437
+ position: {},
1438
+ decisions: [],
1439
+ blockers: [],
1440
+ progress: {}
1441
+ });
1442
+ }
1443
+ }
1444
+
1445
+ // src/mcp/tools/graph.ts
1446
+ import * as path11 from "path";
1447
+ function graphNotFoundError() {
1448
+ return {
1449
+ content: [
1450
+ {
1451
+ type: "text",
1452
+ text: "No graph found. Run `harness scan` or use `ingest_source` tool first."
1453
+ }
1454
+ ],
1455
+ isError: true
1456
+ };
1457
+ }
1458
+ var queryGraphDefinition = {
1459
+ name: "query_graph",
1460
+ description: "Query the project knowledge graph using ContextQL. Traverses from root nodes outward, filtering by node/edge types.",
1461
+ inputSchema: {
1462
+ type: "object",
1463
+ properties: {
1464
+ path: { type: "string", description: "Path to project root" },
1465
+ rootNodeIds: {
1466
+ type: "array",
1467
+ items: { type: "string" },
1468
+ description: "Node IDs to start traversal from"
1469
+ },
1470
+ maxDepth: { type: "number", description: "Maximum traversal depth (default 3)" },
1471
+ includeTypes: {
1472
+ type: "array",
1473
+ items: { type: "string" },
1474
+ description: "Only include nodes of these types"
1475
+ },
1476
+ excludeTypes: {
1477
+ type: "array",
1478
+ items: { type: "string" },
1479
+ description: "Exclude nodes of these types"
1480
+ },
1481
+ includeEdges: {
1482
+ type: "array",
1483
+ items: { type: "string" },
1484
+ description: "Only traverse edges of these types"
1485
+ },
1486
+ bidirectional: {
1487
+ type: "boolean",
1488
+ description: "Traverse edges in both directions (default false)"
1489
+ },
1490
+ pruneObservability: {
1491
+ type: "boolean",
1492
+ description: "Prune observability nodes like spans/metrics/logs (default true)"
1493
+ },
1494
+ mode: {
1495
+ type: "string",
1496
+ enum: ["summary", "detailed"],
1497
+ description: "Response density: summary returns node/edge counts by type + top 10 nodes by connectivity, detailed returns full arrays. Default: detailed"
1498
+ }
1499
+ },
1500
+ required: ["path", "rootNodeIds"]
1501
+ }
1502
+ };
1503
+ async function handleQueryGraph(input) {
1504
+ try {
1505
+ const projectPath = sanitizePath(input.path);
1506
+ const store = await loadGraphStore(projectPath);
1507
+ if (!store) return graphNotFoundError();
1508
+ const { ContextQL } = await import("./dist-M6BQODWC.js");
1509
+ const cql = new ContextQL(store);
1510
+ const result = cql.execute({
1511
+ rootNodeIds: input.rootNodeIds,
1512
+ ...input.maxDepth !== void 0 && { maxDepth: input.maxDepth },
1513
+ ...input.includeTypes !== void 0 && {
1514
+ includeTypes: input.includeTypes
1515
+ },
1516
+ ...input.excludeTypes !== void 0 && {
1517
+ excludeTypes: input.excludeTypes
1518
+ },
1519
+ ...input.includeEdges !== void 0 && {
1520
+ includeEdges: input.includeEdges
1521
+ },
1522
+ ...input.bidirectional !== void 0 && { bidirectional: input.bidirectional },
1523
+ ...input.pruneObservability !== void 0 && {
1524
+ pruneObservability: input.pruneObservability
1525
+ }
1526
+ });
1527
+ if (input.mode === "summary") {
1528
+ const nodesByType = {};
1529
+ for (const node of result.nodes) {
1530
+ nodesByType[node.type] = (nodesByType[node.type] ?? 0) + 1;
1531
+ }
1532
+ const edgesByType = {};
1533
+ for (const edge of result.edges) {
1534
+ edgesByType[edge.type] = (edgesByType[edge.type] ?? 0) + 1;
1535
+ }
1536
+ const edgeCounts = /* @__PURE__ */ new Map();
1537
+ for (const edge of result.edges) {
1538
+ edgeCounts.set(edge.from, (edgeCounts.get(edge.from) ?? 0) + 1);
1539
+ edgeCounts.set(edge.to, (edgeCounts.get(edge.to) ?? 0) + 1);
1540
+ }
1541
+ const topNodes = [...edgeCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([id, connections]) => ({ id, connections }));
1542
+ return {
1543
+ content: [
1544
+ {
1545
+ type: "text",
1546
+ text: JSON.stringify({
1547
+ mode: "summary",
1548
+ totalNodes: result.nodes.length,
1549
+ totalEdges: result.edges.length,
1550
+ nodesByType,
1551
+ edgesByType,
1552
+ topNodes,
1553
+ stats: result.stats
1554
+ })
1555
+ }
1556
+ ]
1557
+ };
1558
+ }
1559
+ return {
1560
+ content: [{ type: "text", text: JSON.stringify(result) }]
1561
+ };
1562
+ } catch (error) {
1563
+ return {
1564
+ content: [
1565
+ {
1566
+ type: "text",
1567
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
1568
+ }
1569
+ ],
1570
+ isError: true
1571
+ };
1572
+ }
1573
+ }
1574
+ var searchSimilarDefinition = {
1575
+ name: "search_similar",
1576
+ description: "Search the knowledge graph for nodes similar to a query string using keyword and semantic fusion.",
1577
+ inputSchema: {
1578
+ type: "object",
1579
+ properties: {
1580
+ path: { type: "string", description: "Path to project root" },
1581
+ query: { type: "string", description: "Search query string" },
1582
+ topK: { type: "number", description: "Maximum number of results to return (default 10)" },
1583
+ mode: {
1584
+ type: "string",
1585
+ enum: ["summary", "detailed"],
1586
+ description: "Response density: summary returns top 5 results with scores only, detailed returns top 10+ with full metadata. Default: detailed"
1587
+ }
1588
+ },
1589
+ required: ["path", "query"]
1590
+ }
1591
+ };
1592
+ async function handleSearchSimilar(input) {
1593
+ try {
1594
+ const projectPath = sanitizePath(input.path);
1595
+ const store = await loadGraphStore(projectPath);
1596
+ if (!store) return graphNotFoundError();
1597
+ const { FusionLayer } = await import("./dist-M6BQODWC.js");
1598
+ const fusion = new FusionLayer(store);
1599
+ const results = fusion.search(input.query, input.topK ?? 10);
1600
+ if (input.mode === "summary") {
1601
+ const summaryResults = results.slice(0, 5).map((r) => ({
1602
+ nodeId: r.nodeId,
1603
+ score: r.score
1604
+ }));
1605
+ return {
1606
+ content: [
1607
+ {
1608
+ type: "text",
1609
+ text: JSON.stringify({ mode: "summary", results: summaryResults })
1610
+ }
1611
+ ]
1612
+ };
1613
+ }
1614
+ return {
1615
+ content: [{ type: "text", text: JSON.stringify(results) }]
1616
+ };
1617
+ } catch (error) {
1618
+ return {
1619
+ content: [
1620
+ {
1621
+ type: "text",
1622
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
1623
+ }
1624
+ ],
1625
+ isError: true
1626
+ };
1627
+ }
1628
+ }
1629
+ var findContextForDefinition = {
1630
+ name: "find_context_for",
1631
+ description: "Find relevant context for a given intent by searching the graph and expanding around top results. Returns assembled context within a token budget.",
1632
+ inputSchema: {
1633
+ type: "object",
1634
+ properties: {
1635
+ path: { type: "string", description: "Path to project root" },
1636
+ intent: { type: "string", description: "Description of what context is needed for" },
1637
+ tokenBudget: {
1638
+ type: "number",
1639
+ description: "Approximate token budget for results (default 4000)"
1640
+ }
1641
+ },
1642
+ required: ["path", "intent"]
1643
+ }
1644
+ };
1645
+ async function handleFindContextFor(input) {
1646
+ try {
1647
+ const projectPath = sanitizePath(input.path);
1648
+ const store = await loadGraphStore(projectPath);
1649
+ if (!store) return graphNotFoundError();
1650
+ const { FusionLayer, ContextQL } = await import("./dist-M6BQODWC.js");
1651
+ const fusion = new FusionLayer(store);
1652
+ const cql = new ContextQL(store);
1653
+ const tokenBudget = input.tokenBudget ?? 4e3;
1654
+ const charBudget = tokenBudget * 4;
1655
+ const searchResults = fusion.search(input.intent, 10);
1656
+ if (searchResults.length === 0) {
1657
+ return {
1658
+ content: [
1659
+ {
1660
+ type: "text",
1661
+ text: JSON.stringify({ context: [], message: "No relevant nodes found." })
1662
+ }
1663
+ ]
1664
+ };
1665
+ }
1666
+ const contextBlocks = [];
1667
+ let totalChars = 0;
1668
+ for (const result of searchResults) {
1669
+ if (totalChars >= charBudget) break;
1670
+ const expanded = cql.execute({
1671
+ rootNodeIds: [result.nodeId],
1672
+ maxDepth: 2
1673
+ });
1674
+ const blockJson = JSON.stringify({
1675
+ rootNode: result.nodeId,
1676
+ score: result.score,
1677
+ nodes: expanded.nodes,
1678
+ edges: expanded.edges
1679
+ });
1680
+ if (totalChars + blockJson.length > charBudget && contextBlocks.length > 0) {
1681
+ break;
1682
+ }
1683
+ contextBlocks.push({
1684
+ rootNode: result.nodeId,
1685
+ score: result.score,
1686
+ nodes: expanded.nodes,
1687
+ edges: expanded.edges
1688
+ });
1689
+ totalChars += blockJson.length;
1690
+ }
1691
+ return {
1692
+ content: [
1693
+ {
1694
+ type: "text",
1695
+ text: JSON.stringify({
1696
+ intent: input.intent,
1697
+ tokenBudget,
1698
+ blocksReturned: contextBlocks.length,
1699
+ context: contextBlocks
1700
+ })
1701
+ }
1702
+ ]
1703
+ };
1704
+ } catch (error) {
1705
+ return {
1706
+ content: [
1707
+ {
1708
+ type: "text",
1709
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
1710
+ }
1711
+ ],
1712
+ isError: true
1713
+ };
1714
+ }
1715
+ }
1716
+ var getRelationshipsDefinition = {
1717
+ name: "get_relationships",
1718
+ description: "Get relationships for a specific node in the knowledge graph, with configurable direction and depth.",
1719
+ inputSchema: {
1720
+ type: "object",
1721
+ properties: {
1722
+ path: { type: "string", description: "Path to project root" },
1723
+ nodeId: { type: "string", description: "ID of the node to get relationships for" },
1724
+ direction: {
1725
+ type: "string",
1726
+ enum: ["outbound", "inbound", "both"],
1727
+ description: "Direction of relationships to include (default both)"
1728
+ },
1729
+ depth: { type: "number", description: "Traversal depth (default 1)" },
1730
+ mode: {
1731
+ type: "string",
1732
+ enum: ["summary", "detailed"],
1733
+ description: "Response density: summary returns neighbor counts by type + direct neighbors only, detailed returns full traversal. Default: detailed"
1734
+ }
1735
+ },
1736
+ required: ["path", "nodeId"]
1737
+ }
1738
+ };
1739
+ async function handleGetRelationships(input) {
1740
+ try {
1741
+ const projectPath = sanitizePath(input.path);
1742
+ const store = await loadGraphStore(projectPath);
1743
+ if (!store) return graphNotFoundError();
1744
+ const { ContextQL } = await import("./dist-M6BQODWC.js");
1745
+ const cql = new ContextQL(store);
1746
+ const direction = input.direction ?? "both";
1747
+ const bidirectional = direction === "both" || direction === "inbound";
1748
+ const result = cql.execute({
1749
+ rootNodeIds: [input.nodeId],
1750
+ maxDepth: input.depth ?? 1,
1751
+ bidirectional
1752
+ });
1753
+ let filteredNodes = result.nodes;
1754
+ let filteredEdges = result.edges;
1755
+ if (direction === "inbound") {
1756
+ filteredEdges = result.edges.filter((e) => e.from !== input.nodeId);
1757
+ const reachableNodeIds = new Set(filteredEdges.map((e) => e.from));
1758
+ reachableNodeIds.add(input.nodeId);
1759
+ filteredNodes = result.nodes.filter((n) => reachableNodeIds.has(n.id));
1760
+ }
1761
+ if (input.mode === "summary") {
1762
+ const neighborsByType = {};
1763
+ for (const node of filteredNodes) {
1764
+ if (node.id === input.nodeId) continue;
1765
+ neighborsByType[node.type] = (neighborsByType[node.type] ?? 0) + 1;
1766
+ }
1767
+ return {
1768
+ content: [
1769
+ {
1770
+ type: "text",
1771
+ text: JSON.stringify({
1772
+ mode: "summary",
1773
+ nodeId: input.nodeId,
1774
+ direction,
1775
+ totalNeighbors: filteredNodes.length - 1,
1776
+ neighborsByType,
1777
+ totalEdges: filteredEdges.length,
1778
+ stats: result.stats
1779
+ })
1780
+ }
1781
+ ]
1782
+ };
1783
+ }
1784
+ return {
1785
+ content: [
1786
+ {
1787
+ type: "text",
1788
+ text: JSON.stringify({
1789
+ nodeId: input.nodeId,
1790
+ direction,
1791
+ depth: input.depth ?? 1,
1792
+ nodes: filteredNodes,
1793
+ edges: filteredEdges,
1794
+ stats: result.stats
1795
+ })
1796
+ }
1797
+ ]
1798
+ };
1799
+ } catch (error) {
1800
+ return {
1801
+ content: [
1802
+ {
1803
+ type: "text",
1804
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
1805
+ }
1806
+ ],
1807
+ isError: true
1808
+ };
1809
+ }
1810
+ }
1811
+ var getImpactDefinition = {
1812
+ name: "get_impact",
1813
+ description: "Analyze the impact of changing a node or file. Returns affected tests, docs, code, and other nodes grouped by type.",
1814
+ inputSchema: {
1815
+ type: "object",
1816
+ properties: {
1817
+ path: { type: "string", description: "Path to project root" },
1818
+ nodeId: { type: "string", description: "ID of the node to analyze impact for" },
1819
+ filePath: {
1820
+ type: "string",
1821
+ description: "File path (relative to project root) to analyze impact for"
1822
+ },
1823
+ mode: {
1824
+ type: "string",
1825
+ enum: ["summary", "detailed"],
1826
+ description: "Response density: summary returns impacted file count by category + highest-risk items, detailed returns full impact tree. Default: detailed"
1827
+ }
1828
+ },
1829
+ required: ["path"]
1830
+ }
1831
+ };
1832
+ async function handleGetImpact(input) {
1833
+ try {
1834
+ if (!input.nodeId && !input.filePath) {
1835
+ return {
1836
+ content: [
1837
+ {
1838
+ type: "text",
1839
+ text: "Error: either nodeId or filePath is required"
1840
+ }
1841
+ ],
1842
+ isError: true
1843
+ };
1844
+ }
1845
+ const projectPath = sanitizePath(input.path);
1846
+ const store = await loadGraphStore(projectPath);
1847
+ if (!store) return graphNotFoundError();
1848
+ const { ContextQL } = await import("./dist-M6BQODWC.js");
1849
+ let targetNodeId = input.nodeId;
1850
+ if (!targetNodeId && input.filePath) {
1851
+ const fileNodes = store.findNodes({ type: "file" });
1852
+ const match = fileNodes.find(
1853
+ (n) => n.path === input.filePath || n.id === `file:${input.filePath}`
1854
+ );
1855
+ if (!match) {
1856
+ return {
1857
+ content: [
1858
+ {
1859
+ type: "text",
1860
+ text: `Error: no file node found matching path "${input.filePath}"`
1861
+ }
1862
+ ],
1863
+ isError: true
1864
+ };
1865
+ }
1866
+ targetNodeId = match.id;
1867
+ }
1868
+ const cql = new ContextQL(store);
1869
+ const result = cql.execute({
1870
+ rootNodeIds: [targetNodeId],
1871
+ bidirectional: true,
1872
+ maxDepth: 3
1873
+ });
1874
+ const groups = {
1875
+ tests: [],
1876
+ docs: [],
1877
+ code: [],
1878
+ other: []
1879
+ };
1880
+ const testTypes = /* @__PURE__ */ new Set(["test_result"]);
1881
+ const docTypes = /* @__PURE__ */ new Set(["adr", "decision", "document", "learning"]);
1882
+ const codeTypes = /* @__PURE__ */ new Set([
1883
+ "file",
1884
+ "module",
1885
+ "class",
1886
+ "interface",
1887
+ "function",
1888
+ "method",
1889
+ "variable"
1890
+ ]);
1891
+ for (const node of result.nodes) {
1892
+ if (node.id === targetNodeId) continue;
1893
+ if (testTypes.has(node.type)) {
1894
+ groups["tests"].push(node);
1895
+ } else if (docTypes.has(node.type)) {
1896
+ groups["docs"].push(node);
1897
+ } else if (codeTypes.has(node.type)) {
1898
+ groups["code"].push(node);
1899
+ } else {
1900
+ groups["other"].push(node);
1901
+ }
1902
+ }
1903
+ if (input.mode === "summary") {
1904
+ const highestRiskItems = [
1905
+ ...groups["tests"].slice(0, 2),
1906
+ ...groups["code"].slice(0, 2),
1907
+ ...groups["docs"].slice(0, 2)
1908
+ ].map((n) => {
1909
+ const node = n;
1910
+ return { id: node.id, type: node.type };
1911
+ });
1912
+ return {
1913
+ content: [
1914
+ {
1915
+ type: "text",
1916
+ text: JSON.stringify({
1917
+ mode: "summary",
1918
+ targetNodeId,
1919
+ impactCounts: {
1920
+ tests: groups["tests"].length,
1921
+ docs: groups["docs"].length,
1922
+ code: groups["code"].length,
1923
+ other: groups["other"].length
1924
+ },
1925
+ highestRiskItems,
1926
+ stats: result.stats
1927
+ })
1928
+ }
1929
+ ]
1930
+ };
1931
+ }
1932
+ return {
1933
+ content: [
1934
+ {
1935
+ type: "text",
1936
+ text: JSON.stringify({
1937
+ targetNodeId,
1938
+ impact: groups,
1939
+ stats: result.stats,
1940
+ edges: result.edges
1941
+ })
1942
+ }
1943
+ ]
1944
+ };
1945
+ } catch (error) {
1946
+ return {
1947
+ content: [
1948
+ {
1949
+ type: "text",
1950
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
1951
+ }
1952
+ ],
1953
+ isError: true
1954
+ };
1955
+ }
1956
+ }
1957
+ var ingestSourceDefinition = {
1958
+ name: "ingest_source",
1959
+ description: "Ingest sources into the project knowledge graph. Supports code analysis, knowledge documents, git history, or all at once.",
1960
+ inputSchema: {
1961
+ type: "object",
1962
+ properties: {
1963
+ path: { type: "string", description: "Path to project root" },
1964
+ source: {
1965
+ type: "string",
1966
+ enum: ["code", "knowledge", "git", "all"],
1967
+ description: "Type of source to ingest"
1968
+ }
1969
+ },
1970
+ required: ["path", "source"]
1971
+ }
1972
+ };
1973
+ async function handleIngestSource(input) {
1974
+ try {
1975
+ const projectPath = sanitizePath(input.path);
1976
+ const graphDir = path11.join(projectPath, ".harness", "graph");
1977
+ const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-M6BQODWC.js");
1978
+ const fs10 = await import("fs/promises");
1979
+ await fs10.mkdir(graphDir, { recursive: true });
1980
+ const store = new GraphStore();
1981
+ await store.load(graphDir);
1982
+ const results = [];
1983
+ if (input.source === "code" || input.source === "all") {
1984
+ const codeIngestor = new CodeIngestor(store);
1985
+ const codeResult = await codeIngestor.ingest(projectPath);
1986
+ results.push(codeResult);
1987
+ const linker = new TopologicalLinker(store);
1988
+ linker.link();
1989
+ }
1990
+ if (input.source === "knowledge" || input.source === "all") {
1991
+ const knowledgeIngestor = new KnowledgeIngestor(store);
1992
+ const knowledgeResult = await knowledgeIngestor.ingestAll(projectPath);
1993
+ results.push(knowledgeResult);
1994
+ }
1995
+ if (input.source === "git" || input.source === "all") {
1996
+ const gitIngestor = new GitIngestor(store);
1997
+ const gitResult = await gitIngestor.ingest(projectPath);
1998
+ results.push(gitResult);
1999
+ }
2000
+ await store.save(graphDir);
2001
+ const combined = {
2002
+ nodesAdded: results.reduce((s, r) => s + r.nodesAdded, 0),
2003
+ nodesUpdated: results.reduce((s, r) => s + r.nodesUpdated, 0),
2004
+ edgesAdded: results.reduce((s, r) => s + r.edgesAdded, 0),
2005
+ edgesUpdated: results.reduce((s, r) => s + r.edgesUpdated, 0),
2006
+ errors: results.flatMap((r) => r.errors),
2007
+ durationMs: results.reduce((s, r) => s + r.durationMs, 0),
2008
+ graphStats: {
2009
+ totalNodes: store.nodeCount,
2010
+ totalEdges: store.edgeCount
2011
+ }
2012
+ };
2013
+ return {
2014
+ content: [{ type: "text", text: JSON.stringify(combined) }]
2015
+ };
2016
+ } catch (error) {
2017
+ return {
2018
+ content: [
2019
+ {
2020
+ type: "text",
2021
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
2022
+ }
2023
+ ],
2024
+ isError: true
2025
+ };
2026
+ }
2027
+ }
2028
+ var detectAnomaliesDefinition = {
2029
+ name: "detect_anomalies",
2030
+ description: "Detect structural anomalies \u2014 statistical outliers across code metrics and topological single points of failure in the import graph",
2031
+ inputSchema: {
2032
+ type: "object",
2033
+ properties: {
2034
+ path: { type: "string", description: "Path to project root" },
2035
+ threshold: { type: "number", description: "Z-score threshold (default 2.0)" },
2036
+ metrics: {
2037
+ type: "array",
2038
+ items: { type: "string" },
2039
+ description: "Metrics to analyze (default: cyclomaticComplexity, fanIn, fanOut, hotspotScore, transitiveDepth)"
2040
+ }
2041
+ },
2042
+ required: ["path"]
2043
+ }
2044
+ };
2045
+ async function handleDetectAnomalies(input) {
2046
+ try {
2047
+ const projectPath = sanitizePath(input.path);
2048
+ const store = await loadGraphStore(projectPath);
2049
+ if (!store) return graphNotFoundError();
2050
+ const { GraphAnomalyAdapter } = await import("./dist-M6BQODWC.js");
2051
+ const adapter = new GraphAnomalyAdapter(store);
2052
+ const report = adapter.detect({
2053
+ ...input.threshold !== void 0 && { threshold: input.threshold },
2054
+ ...input.metrics !== void 0 && { metrics: input.metrics }
2055
+ });
2056
+ return {
2057
+ content: [{ type: "text", text: JSON.stringify(report) }]
2058
+ };
2059
+ } catch (error) {
2060
+ return {
2061
+ content: [
2062
+ {
2063
+ type: "text",
2064
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
2065
+ }
2066
+ ],
2067
+ isError: true
2068
+ };
2069
+ }
2070
+ }
2071
+ var askGraphDefinition = {
2072
+ name: "ask_graph",
2073
+ description: 'Ask a natural language question about the codebase knowledge graph. Supports questions about impact ("what breaks if I change X?"), finding entities ("where is the auth middleware?"), relationships ("what calls UserService?"), explanations ("what is GraphStore?"), and anomalies ("what looks wrong?"). Returns a human-readable summary and raw graph data.',
2074
+ inputSchema: {
2075
+ type: "object",
2076
+ properties: {
2077
+ path: { type: "string", description: "Path to project root" },
2078
+ question: { type: "string", description: "Natural language question about the codebase" }
2079
+ },
2080
+ required: ["path", "question"]
2081
+ }
2082
+ };
2083
+ async function handleAskGraph(input) {
2084
+ try {
2085
+ const projectPath = sanitizePath(input.path);
2086
+ const store = await loadGraphStore(projectPath);
2087
+ if (!store) return graphNotFoundError();
2088
+ const { askGraph } = await import("./dist-M6BQODWC.js");
2089
+ const result = await askGraph(store, input.question);
2090
+ return {
2091
+ content: [{ type: "text", text: JSON.stringify(result) }]
2092
+ };
2093
+ } catch (error) {
2094
+ return {
2095
+ content: [
2096
+ {
2097
+ type: "text",
2098
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
2099
+ }
2100
+ ],
2101
+ isError: true
2102
+ };
2103
+ }
2104
+ }
2105
+
2106
+ // src/mcp/resources/graph.ts
2107
+ import * as fs8 from "fs/promises";
2108
+ import * as path12 from "path";
2109
+ var MAX_ITEMS = 5e3;
2110
+ function formatStaleness(isoTimestamp) {
2111
+ const then = new Date(isoTimestamp).getTime();
2112
+ const now = Date.now();
2113
+ const diffMs = now - then;
2114
+ const seconds = Math.floor(diffMs / 1e3);
2115
+ const minutes = Math.floor(seconds / 60);
2116
+ const hours = Math.floor(minutes / 60);
2117
+ const days = Math.floor(hours / 24);
2118
+ if (days > 0) return `${days} day${days === 1 ? "" : "s"} ago`;
2119
+ if (hours > 0) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
2120
+ if (minutes > 0) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
2121
+ return "just now";
2122
+ }
2123
+ async function getGraphResource(projectRoot) {
2124
+ const store = await loadGraphStore(projectRoot);
2125
+ if (!store) {
2126
+ return JSON.stringify({
2127
+ status: "no_graph",
2128
+ message: "No knowledge graph found. Run harness scan to build one."
2129
+ });
2130
+ }
2131
+ const graphDir = path12.join(projectRoot, ".harness", "graph");
2132
+ const metadataPath = path12.join(graphDir, "metadata.json");
2133
+ let lastScanTimestamp = null;
2134
+ try {
2135
+ const raw = JSON.parse(await fs8.readFile(metadataPath, "utf-8"));
2136
+ lastScanTimestamp = raw.lastScanTimestamp ?? null;
2137
+ } catch {
2138
+ }
2139
+ const allNodes = store.findNodes({});
2140
+ const allEdges = store.getEdges({});
2141
+ const nodesByType = {};
2142
+ for (const node of allNodes) {
2143
+ nodesByType[node.type] = (nodesByType[node.type] ?? 0) + 1;
2144
+ }
2145
+ const edgesByType = {};
2146
+ for (const edge of allEdges) {
2147
+ edgesByType[edge.type] = (edgesByType[edge.type] ?? 0) + 1;
2148
+ }
2149
+ let status = "ok";
2150
+ let staleness = "unknown";
2151
+ if (lastScanTimestamp) {
2152
+ const ageMs = Date.now() - new Date(lastScanTimestamp).getTime();
2153
+ const twentyFourHoursMs = 24 * 60 * 60 * 1e3;
2154
+ if (ageMs > twentyFourHoursMs) {
2155
+ status = "stale";
2156
+ }
2157
+ staleness = formatStaleness(lastScanTimestamp);
2158
+ }
2159
+ return JSON.stringify({
2160
+ status,
2161
+ nodeCount: store.nodeCount,
2162
+ edgeCount: store.edgeCount,
2163
+ nodesByType,
2164
+ edgesByType,
2165
+ lastScanTimestamp,
2166
+ staleness
2167
+ });
2168
+ }
2169
+ async function getEntitiesResource(projectRoot) {
2170
+ const store = await loadGraphStore(projectRoot);
2171
+ if (!store) {
2172
+ return "[]";
2173
+ }
2174
+ const nodes = store.findNodes({});
2175
+ const entities = nodes.slice(0, MAX_ITEMS).map((n) => ({
2176
+ id: n.id,
2177
+ type: n.type,
2178
+ name: n.name,
2179
+ path: n.path,
2180
+ metadata: n.metadata
2181
+ }));
2182
+ if (nodes.length > MAX_ITEMS) {
2183
+ return JSON.stringify({ entities, _truncated: true, _total: nodes.length }, null, 2);
2184
+ }
2185
+ return JSON.stringify(entities);
2186
+ }
2187
+ async function getRelationshipsResource(projectRoot) {
2188
+ const store = await loadGraphStore(projectRoot);
2189
+ if (!store) {
2190
+ return "[]";
2191
+ }
2192
+ const edges = store.getEdges({});
2193
+ const relationships = edges.slice(0, MAX_ITEMS).map((e) => ({
2194
+ from: e.from,
2195
+ to: e.to,
2196
+ type: e.type,
2197
+ confidence: e.confidence,
2198
+ metadata: e.metadata
2199
+ }));
2200
+ if (edges.length > MAX_ITEMS) {
2201
+ return JSON.stringify({ relationships, _truncated: true, _total: edges.length }, null, 2);
2202
+ }
2203
+ return JSON.stringify(relationships);
2204
+ }
2205
+
2206
+ // src/mcp/tools/agent-definitions.ts
2207
+ var generateAgentDefinitionsDefinition = {
2208
+ name: "generate_agent_definitions",
2209
+ description: "Generate agent definition files from personas for Claude Code and Gemini CLI",
2210
+ inputSchema: {
2211
+ type: "object",
2212
+ properties: {
2213
+ global: { type: "boolean", description: "Write to global agent directory" },
2214
+ platform: {
2215
+ type: "string",
2216
+ enum: ["claude-code", "gemini-cli", "all"],
2217
+ description: "Target platform (default: all)"
2218
+ },
2219
+ dryRun: { type: "boolean", description: "Preview without writing" }
2220
+ }
2221
+ }
2222
+ };
2223
+ async function handleGenerateAgentDefinitions(input) {
2224
+ const { generateAgentDefinitions } = await import("./generate-agent-definitions-MWKEA5NU.js");
2225
+ const platforms = input.platform === "all" || !input.platform ? ["claude-code", "gemini-cli"] : [input.platform];
2226
+ const results = generateAgentDefinitions({
2227
+ platforms: [...platforms],
2228
+ global: input.global ?? false,
2229
+ dryRun: input.dryRun ?? false
2230
+ });
2231
+ return resultToMcpResponse(Ok(results));
2232
+ }
2233
+
2234
+ // src/mcp/tools/roadmap.ts
2235
+ import * as fs9 from "fs";
2236
+ import * as path13 from "path";
2237
+ var manageRoadmapDefinition = {
2238
+ name: "manage_roadmap",
2239
+ description: "Manage the project roadmap: show, add, update, remove, sync features, or query by filter. Reads and writes docs/roadmap.md.",
2240
+ inputSchema: {
2241
+ type: "object",
2242
+ properties: {
2243
+ path: { type: "string", description: "Path to project root" },
2244
+ action: {
2245
+ type: "string",
2246
+ enum: ["show", "add", "update", "remove", "query", "sync"],
2247
+ description: "Action to perform"
2248
+ },
2249
+ feature: { type: "string", description: "Feature name (required for add, update, remove)" },
2250
+ milestone: {
2251
+ type: "string",
2252
+ description: "Milestone name (required for add; optional filter for show)"
2253
+ },
2254
+ status: {
2255
+ type: "string",
2256
+ enum: ["backlog", "planned", "in-progress", "done", "blocked"],
2257
+ description: "Feature status (required for add; optional for update; optional filter for show)"
2258
+ },
2259
+ summary: {
2260
+ type: "string",
2261
+ description: "Feature summary (required for add; optional for update)"
2262
+ },
2263
+ spec: { type: "string", description: "Spec file path (optional for add/update)" },
2264
+ plans: {
2265
+ type: "array",
2266
+ items: { type: "string" },
2267
+ description: "Plan file paths (optional for add/update)"
2268
+ },
2269
+ blocked_by: {
2270
+ type: "array",
2271
+ items: { type: "string" },
2272
+ description: "Blocking feature names (optional for add/update)"
2273
+ },
2274
+ filter: {
2275
+ type: "string",
2276
+ description: 'Query filter: "blocked", "in-progress", "done", "planned", "backlog", or "milestone:<name>" (required for query)'
2277
+ },
2278
+ apply: {
2279
+ type: "boolean",
2280
+ description: "For sync action: apply proposed changes (default: false, preview only)"
2281
+ },
2282
+ force_sync: {
2283
+ type: "boolean",
2284
+ description: "For sync action: override human-always-wins rule"
2285
+ }
2286
+ },
2287
+ required: ["path", "action"]
2288
+ }
2289
+ };
2290
+ function roadmapPath(projectRoot) {
2291
+ return path13.join(projectRoot, "docs", "roadmap.md");
2292
+ }
2293
+ function readRoadmapFile(projectRoot) {
2294
+ const filePath = roadmapPath(projectRoot);
2295
+ try {
2296
+ return fs9.readFileSync(filePath, "utf-8");
2297
+ } catch {
2298
+ return null;
2299
+ }
2300
+ }
2301
+ function writeRoadmapFile(projectRoot, content) {
2302
+ const filePath = roadmapPath(projectRoot);
2303
+ const dir = path13.dirname(filePath);
2304
+ fs9.mkdirSync(dir, { recursive: true });
2305
+ fs9.writeFileSync(filePath, content, "utf-8");
2306
+ }
2307
+ async function handleManageRoadmap(input) {
2308
+ try {
2309
+ const { parseRoadmap, serializeRoadmap, syncRoadmap } = await import("./dist-JVZ2MKBC.js");
2310
+ const { Ok: Ok2 } = await import("./dist-D4RYGUZE.js");
2311
+ const projectPath = sanitizePath(input.path);
2312
+ switch (input.action) {
2313
+ case "show": {
2314
+ const raw = readRoadmapFile(projectPath);
2315
+ if (raw === null) {
2316
+ return {
2317
+ content: [
2318
+ {
2319
+ type: "text",
2320
+ text: "Error: docs/roadmap.md not found. Create a roadmap first."
2321
+ }
2322
+ ],
2323
+ isError: true
2324
+ };
2325
+ }
2326
+ const result = parseRoadmap(raw);
2327
+ if (!result.ok) return resultToMcpResponse(result);
2328
+ let roadmap = result.value;
2329
+ if (input.milestone) {
2330
+ const milestoneFilter = input.milestone;
2331
+ roadmap = {
2332
+ ...roadmap,
2333
+ milestones: roadmap.milestones.filter(
2334
+ (m) => m.name.toLowerCase() === milestoneFilter.toLowerCase()
2335
+ )
2336
+ };
2337
+ }
2338
+ if (input.status) {
2339
+ const statusFilter = input.status;
2340
+ roadmap = {
2341
+ ...roadmap,
2342
+ milestones: roadmap.milestones.map((m) => ({
2343
+ ...m,
2344
+ features: m.features.filter((f) => f.status === statusFilter)
2345
+ })).filter((m) => m.features.length > 0)
2346
+ };
2347
+ }
2348
+ return resultToMcpResponse(Ok2(roadmap));
2349
+ }
2350
+ case "add": {
2351
+ if (!input.feature) {
2352
+ return {
2353
+ content: [{ type: "text", text: "Error: feature is required for add action" }],
2354
+ isError: true
2355
+ };
2356
+ }
2357
+ if (!input.milestone) {
2358
+ return {
2359
+ content: [
2360
+ { type: "text", text: "Error: milestone is required for add action" }
2361
+ ],
2362
+ isError: true
2363
+ };
2364
+ }
2365
+ if (!input.status) {
2366
+ return {
2367
+ content: [{ type: "text", text: "Error: status is required for add action" }],
2368
+ isError: true
2369
+ };
2370
+ }
2371
+ if (!input.summary) {
2372
+ return {
2373
+ content: [{ type: "text", text: "Error: summary is required for add action" }],
2374
+ isError: true
2375
+ };
2376
+ }
2377
+ const raw = readRoadmapFile(projectPath);
2378
+ if (raw === null) {
2379
+ return {
2380
+ content: [
2381
+ {
2382
+ type: "text",
2383
+ text: "Error: docs/roadmap.md not found. Create a roadmap first."
2384
+ }
2385
+ ],
2386
+ isError: true
2387
+ };
2388
+ }
2389
+ const result = parseRoadmap(raw);
2390
+ if (!result.ok) return resultToMcpResponse(result);
2391
+ const roadmap = result.value;
2392
+ const milestone = roadmap.milestones.find(
2393
+ (m) => m.name.toLowerCase() === input.milestone.toLowerCase()
2394
+ );
2395
+ if (!milestone) {
2396
+ return {
2397
+ content: [
2398
+ { type: "text", text: `Error: milestone "${input.milestone}" not found` }
2399
+ ],
2400
+ isError: true
2401
+ };
2402
+ }
2403
+ milestone.features.push({
2404
+ name: input.feature,
2405
+ status: input.status,
2406
+ spec: input.spec ?? null,
2407
+ plans: input.plans ?? [],
2408
+ blockedBy: input.blocked_by ?? [],
2409
+ summary: input.summary
2410
+ });
2411
+ roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
2412
+ writeRoadmapFile(projectPath, serializeRoadmap(roadmap));
2413
+ return resultToMcpResponse(Ok2(roadmap));
2414
+ }
2415
+ case "update": {
2416
+ if (!input.feature) {
2417
+ return {
2418
+ content: [
2419
+ { type: "text", text: "Error: feature is required for update action" }
2420
+ ],
2421
+ isError: true
2422
+ };
2423
+ }
2424
+ const raw = readRoadmapFile(projectPath);
2425
+ if (raw === null) {
2426
+ return {
2427
+ content: [
2428
+ {
2429
+ type: "text",
2430
+ text: "Error: docs/roadmap.md not found. Create a roadmap first."
2431
+ }
2432
+ ],
2433
+ isError: true
2434
+ };
2435
+ }
2436
+ const result = parseRoadmap(raw);
2437
+ if (!result.ok) return resultToMcpResponse(result);
2438
+ const roadmap = result.value;
2439
+ let found = false;
2440
+ for (const m of roadmap.milestones) {
2441
+ const feature = m.features.find(
2442
+ (f) => f.name.toLowerCase() === input.feature.toLowerCase()
2443
+ );
2444
+ if (feature) {
2445
+ if (input.status) feature.status = input.status;
2446
+ if (input.summary !== void 0) feature.summary = input.summary;
2447
+ if (input.spec !== void 0) feature.spec = input.spec || null;
2448
+ if (input.plans !== void 0) feature.plans = input.plans;
2449
+ if (input.blocked_by !== void 0) feature.blockedBy = input.blocked_by;
2450
+ found = true;
2451
+ break;
2452
+ }
2453
+ }
2454
+ if (!found) {
2455
+ return {
2456
+ content: [
2457
+ { type: "text", text: `Error: feature "${input.feature}" not found` }
2458
+ ],
2459
+ isError: true
2460
+ };
2461
+ }
2462
+ roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
2463
+ writeRoadmapFile(projectPath, serializeRoadmap(roadmap));
2464
+ return resultToMcpResponse(Ok2(roadmap));
2465
+ }
2466
+ case "remove": {
2467
+ if (!input.feature) {
2468
+ return {
2469
+ content: [
2470
+ { type: "text", text: "Error: feature is required for remove action" }
2471
+ ],
2472
+ isError: true
2473
+ };
2474
+ }
2475
+ const raw = readRoadmapFile(projectPath);
2476
+ if (raw === null) {
2477
+ return {
2478
+ content: [
2479
+ {
2480
+ type: "text",
2481
+ text: "Error: docs/roadmap.md not found. Create a roadmap first."
2482
+ }
2483
+ ],
2484
+ isError: true
2485
+ };
2486
+ }
2487
+ const result = parseRoadmap(raw);
2488
+ if (!result.ok) return resultToMcpResponse(result);
2489
+ const roadmap = result.value;
2490
+ let found = false;
2491
+ for (const m of roadmap.milestones) {
2492
+ const idx = m.features.findIndex(
2493
+ (f) => f.name.toLowerCase() === input.feature.toLowerCase()
2494
+ );
2495
+ if (idx !== -1) {
2496
+ m.features.splice(idx, 1);
2497
+ found = true;
2498
+ break;
2499
+ }
2500
+ }
2501
+ if (!found) {
2502
+ return {
2503
+ content: [
2504
+ { type: "text", text: `Error: feature "${input.feature}" not found` }
2505
+ ],
2506
+ isError: true
2507
+ };
2508
+ }
2509
+ roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
2510
+ writeRoadmapFile(projectPath, serializeRoadmap(roadmap));
2511
+ return resultToMcpResponse(Ok2(roadmap));
2512
+ }
2513
+ case "query": {
2514
+ if (!input.filter) {
2515
+ return {
2516
+ content: [
2517
+ { type: "text", text: "Error: filter is required for query action" }
2518
+ ],
2519
+ isError: true
2520
+ };
2521
+ }
2522
+ const raw = readRoadmapFile(projectPath);
2523
+ if (raw === null) {
2524
+ return {
2525
+ content: [
2526
+ {
2527
+ type: "text",
2528
+ text: "Error: docs/roadmap.md not found. Create a roadmap first."
2529
+ }
2530
+ ],
2531
+ isError: true
2532
+ };
2533
+ }
2534
+ const result = parseRoadmap(raw);
2535
+ if (!result.ok) return resultToMcpResponse(result);
2536
+ const roadmap = result.value;
2537
+ const allFeatures = roadmap.milestones.flatMap(
2538
+ (m) => m.features.map((f) => ({ ...f, milestone: m.name }))
2539
+ );
2540
+ const filter = input.filter.toLowerCase();
2541
+ let filtered;
2542
+ if (filter.startsWith("milestone:")) {
2543
+ const milestoneName = filter.slice("milestone:".length).trim();
2544
+ filtered = allFeatures.filter((f) => f.milestone.toLowerCase().includes(milestoneName));
2545
+ } else {
2546
+ filtered = allFeatures.filter((f) => f.status === filter);
2547
+ }
2548
+ return resultToMcpResponse(Ok2(filtered));
2549
+ }
2550
+ case "sync": {
2551
+ const raw = readRoadmapFile(projectPath);
2552
+ if (raw === null) {
2553
+ return {
2554
+ content: [
2555
+ {
2556
+ type: "text",
2557
+ text: "Error: docs/roadmap.md not found. Create a roadmap first."
2558
+ }
2559
+ ],
2560
+ isError: true
2561
+ };
2562
+ }
2563
+ const result = parseRoadmap(raw);
2564
+ if (!result.ok) return resultToMcpResponse(result);
2565
+ const roadmap = result.value;
2566
+ const syncResult = syncRoadmap({
2567
+ projectPath,
2568
+ roadmap,
2569
+ forceSync: input.force_sync ?? false
2570
+ });
2571
+ if (!syncResult.ok) return resultToMcpResponse(syncResult);
2572
+ const changes = syncResult.value;
2573
+ if (changes.length === 0) {
2574
+ return resultToMcpResponse(Ok2({ changes: [], message: "Roadmap is up to date." }));
2575
+ }
2576
+ if (input.apply) {
2577
+ for (const change of changes) {
2578
+ for (const m of roadmap.milestones) {
2579
+ const feature = m.features.find(
2580
+ (f) => f.name.toLowerCase() === change.feature.toLowerCase()
2581
+ );
2582
+ if (feature) {
2583
+ feature.status = change.to;
2584
+ break;
2585
+ }
2586
+ }
2587
+ }
2588
+ roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
2589
+ writeRoadmapFile(projectPath, serializeRoadmap(roadmap));
2590
+ return resultToMcpResponse(Ok2({ changes, applied: true, roadmap }));
2591
+ }
2592
+ return resultToMcpResponse(Ok2({ changes, applied: false }));
2593
+ }
2594
+ default: {
2595
+ return {
2596
+ content: [{ type: "text", text: `Error: unknown action` }],
2597
+ isError: true
2598
+ };
2599
+ }
2600
+ }
2601
+ } catch (error) {
2602
+ return {
2603
+ content: [
2604
+ {
2605
+ type: "text",
2606
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
2607
+ }
2608
+ ],
2609
+ isError: true
2610
+ };
2611
+ }
2612
+ }
2613
+
2614
+ // src/mcp/tools/interaction.ts
2615
+ import { randomUUID } from "crypto";
2616
+
2617
+ // src/mcp/tools/interaction-schemas.ts
2618
+ import { z } from "zod";
2619
+ var RiskLevel = z.enum(["low", "medium", "high"]);
2620
+ var EffortLevel = z.enum(["low", "medium", "high"]);
2621
+ var ConfidenceLevel = z.enum(["low", "medium", "high"]);
2622
+ var InteractionOptionSchema = z.object({
2623
+ label: z.string().min(1),
2624
+ pros: z.array(z.string().min(1)).min(1),
2625
+ cons: z.array(z.string().min(1)).min(1),
2626
+ risk: RiskLevel.optional(),
2627
+ effort: EffortLevel.optional()
2628
+ });
2629
+ var InteractionQuestionSchema = z.object({
2630
+ text: z.string().min(1),
2631
+ options: z.array(InteractionOptionSchema).min(2).max(10).optional(),
2632
+ recommendation: z.object({
2633
+ optionIndex: z.number().int().min(0),
2634
+ reason: z.string().min(1),
2635
+ confidence: ConfidenceLevel
2636
+ }).optional(),
2637
+ default: z.number().int().min(0).optional()
2638
+ });
2639
+ var InteractionQuestionWithOptionsSchema = InteractionQuestionSchema.refine(
2640
+ (data) => {
2641
+ if (data.options && data.options.length > 0) {
2642
+ return data.recommendation !== void 0;
2643
+ }
2644
+ return true;
2645
+ },
2646
+ { message: "recommendation is required when options are provided" }
2647
+ ).refine(
2648
+ (data) => {
2649
+ if (data.recommendation && data.options) {
2650
+ return data.recommendation.optionIndex < data.options.length;
2651
+ }
2652
+ return true;
2653
+ },
2654
+ { message: "recommendation.optionIndex must reference a valid option" }
2655
+ ).refine(
2656
+ (data) => {
2657
+ if (data.default !== void 0 && data.options) {
2658
+ return data.default < data.options.length;
2659
+ }
2660
+ return true;
2661
+ },
2662
+ { message: "default must reference a valid option index" }
2663
+ );
2664
+ var InteractionConfirmationSchema = z.object({
2665
+ text: z.string().min(1),
2666
+ context: z.string().min(1),
2667
+ impact: z.string().optional(),
2668
+ risk: RiskLevel.optional()
2669
+ });
2670
+ var QualityGateCheckSchema = z.object({
2671
+ name: z.string().min(1),
2672
+ passed: z.boolean(),
2673
+ detail: z.string().optional()
2674
+ });
2675
+ var QualityGateSchema = z.object({
2676
+ checks: z.array(QualityGateCheckSchema).min(1),
2677
+ allPassed: z.boolean()
2678
+ });
2679
+ var InteractionTransitionSchema = z.object({
2680
+ completedPhase: z.string().min(1),
2681
+ suggestedNext: z.string().min(1),
2682
+ reason: z.string().min(1),
2683
+ artifacts: z.array(z.string()),
2684
+ requiresConfirmation: z.boolean(),
2685
+ summary: z.string().min(1),
2686
+ qualityGate: QualityGateSchema.optional()
2687
+ });
2688
+ var BatchDecisionSchema = z.object({
2689
+ label: z.string().min(1),
2690
+ recommendation: z.string().min(1),
2691
+ risk: z.literal("low")
2692
+ });
2693
+ var InteractionBatchSchema = z.object({
2694
+ text: z.string().min(1),
2695
+ decisions: z.array(BatchDecisionSchema).min(1)
2696
+ });
2697
+ var InteractionTypeSchema = z.enum(["question", "confirmation", "transition", "batch"]);
2698
+ var EmitInteractionInputSchema = z.object({
2699
+ path: z.string().min(1),
2700
+ type: InteractionTypeSchema,
2701
+ stream: z.string().optional(),
2702
+ // Uses base schema here; refined validation (recommendation required with options)
2703
+ // is applied in the handler's question branch via InteractionQuestionWithOptionsSchema.
2704
+ // Refined schemas with .refine() cannot be nested inside z.object().optional() reliably.
2705
+ question: InteractionQuestionSchema.optional(),
2706
+ confirmation: InteractionConfirmationSchema.optional(),
2707
+ transition: InteractionTransitionSchema.optional(),
2708
+ batch: InteractionBatchSchema.optional()
2709
+ });
2710
+
2711
+ // src/mcp/tools/interaction-renderer.ts
2712
+ function columnLabel(index) {
2713
+ return String.fromCharCode(65 + index);
2714
+ }
2715
+ function renderQuestion(question) {
2716
+ const { text, options, recommendation } = question;
2717
+ if (!options || options.length === 0) {
2718
+ return text;
2719
+ }
2720
+ const headers = options.map(
2721
+ (opt, i) => `${columnLabel(i)}) ${escapeCell(opt.label)}`
2722
+ );
2723
+ const headerRow = `| | ${headers.join(" | ")} |`;
2724
+ const separatorRow = `|---|${options.map(() => "---").join("|")}|`;
2725
+ const prosRow = `| **Pros** | ${options.map((opt) => opt.pros.map(escapeCell).join("; ")).join(" | ")} |`;
2726
+ const consRow = `| **Cons** | ${options.map((opt) => opt.cons.map(escapeCell).join("; ")).join(" | ")} |`;
2727
+ const rows = [headerRow, separatorRow, prosRow, consRow];
2728
+ if (options.some((opt) => opt.risk)) {
2729
+ const riskRow = `| **Risk** | ${options.map((opt) => opt.risk ? capitalize(opt.risk) : "-").join(" | ")} |`;
2730
+ rows.push(riskRow);
2731
+ }
2732
+ if (options.some((opt) => opt.effort)) {
2733
+ const effortRow = `| **Effort** | ${options.map((opt) => opt.effort ? capitalize(opt.effort) : "-").join(" | ")} |`;
2734
+ rows.push(effortRow);
2735
+ }
2736
+ let prompt = `### Decision needed: ${text}
2737
+
2738
+ ${rows.join("\n")}`;
2739
+ if (recommendation) {
2740
+ const opt = options[recommendation.optionIndex];
2741
+ if (opt) {
2742
+ const recLabel = `${columnLabel(recommendation.optionIndex)}) ${opt.label}`;
2743
+ prompt += `
2744
+
2745
+ **Recommendation:** ${recLabel} (confidence: ${recommendation.confidence})`;
2746
+ prompt += `
2747
+ > ${recommendation.reason}`;
2748
+ }
2749
+ }
2750
+ return prompt;
2751
+ }
2752
+ function renderConfirmation(confirmation) {
2753
+ let prompt = `${confirmation.text}
2754
+
2755
+ Context: ${confirmation.context}`;
2756
+ if (confirmation.impact) {
2757
+ prompt += `
2758
+
2759
+ Impact: ${confirmation.impact}`;
2760
+ }
2761
+ if (confirmation.risk) {
2762
+ prompt += `
2763
+ Risk: ${capitalize(confirmation.risk)}`;
2764
+ }
2765
+ prompt += "\n\nProceed? (yes/no)";
2766
+ return prompt;
2767
+ }
2768
+ function renderTransition(transition) {
2769
+ let prompt = `Phase "${transition.completedPhase}" complete. ${transition.reason}
2770
+
2771
+ ${transition.summary}
2772
+
2773
+ Artifacts produced:
2774
+ ${transition.artifacts.map((a) => ` - ${a}`).join("\n")}`;
2775
+ if (transition.qualityGate) {
2776
+ prompt += "\n\n**Quality Gate:**\n";
2777
+ for (const check of transition.qualityGate.checks) {
2778
+ const icon = check.passed ? "PASS" : "FAIL";
2779
+ prompt += ` - [${icon}] ${check.name}`;
2780
+ if (check.detail) {
2781
+ prompt += ` -- ${check.detail}`;
2782
+ }
2783
+ prompt += "\n";
2784
+ }
2785
+ prompt += transition.qualityGate.allPassed ? " All checks passed." : " **Some checks failed.**";
2786
+ }
2787
+ prompt += "\n\n";
2788
+ prompt += transition.requiresConfirmation ? `Suggested next: "${transition.suggestedNext}". Proceed?` : `Proceeding to ${transition.suggestedNext}...`;
2789
+ return prompt;
2790
+ }
2791
+ function renderBatch(batch) {
2792
+ let prompt = `${batch.text}
2793
+
2794
+ `;
2795
+ batch.decisions.forEach((d, i) => {
2796
+ prompt += `${i + 1}. **${d.label}** -- Recommendation: ${d.recommendation} (risk: low)
2797
+ `;
2798
+ });
2799
+ prompt += "\nApprove all? (yes/no)";
2800
+ return prompt;
2801
+ }
2802
+ function capitalize(s) {
2803
+ return s.charAt(0).toUpperCase() + s.slice(1);
2804
+ }
2805
+ function escapeCell(s) {
2806
+ return s.replace(/\|/g, "\\|");
2807
+ }
2808
+
2809
+ // src/mcp/tools/interaction.ts
2810
+ var emitInteractionDefinition = {
2811
+ name: "emit_interaction",
2812
+ description: "Emit a structured interaction (question, confirmation, phase transition, or batch decision) for round-trip communication with the user",
2813
+ inputSchema: {
2814
+ type: "object",
2815
+ properties: {
2816
+ path: { type: "string", description: "Path to project root" },
2817
+ type: {
2818
+ type: "string",
2819
+ enum: ["question", "confirmation", "transition", "batch"],
2820
+ description: "Type of interaction"
2821
+ },
2822
+ stream: {
2823
+ type: "string",
2824
+ description: "State stream for recording (auto-resolves from branch if omitted)"
2825
+ },
2826
+ question: {
2827
+ type: "object",
2828
+ description: "Question payload (required when type is question)",
2829
+ properties: {
2830
+ text: { type: "string", description: "The question text" },
2831
+ options: {
2832
+ type: "array",
2833
+ items: {
2834
+ type: "object",
2835
+ properties: {
2836
+ label: { type: "string" },
2837
+ pros: { type: "array", items: { type: "string" } },
2838
+ cons: { type: "array", items: { type: "string" } },
2839
+ risk: { type: "string", enum: ["low", "medium", "high"] },
2840
+ effort: { type: "string", enum: ["low", "medium", "high"] }
2841
+ },
2842
+ required: ["label", "pros", "cons"]
2843
+ },
2844
+ description: "Structured options with pros/cons (omit for free-form)"
2845
+ },
2846
+ recommendation: {
2847
+ type: "object",
2848
+ properties: {
2849
+ optionIndex: { type: "number", description: "Index of recommended option" },
2850
+ reason: { type: "string", description: "Why this option is recommended" },
2851
+ confidence: { type: "string", enum: ["low", "medium", "high"] }
2852
+ },
2853
+ required: ["optionIndex", "reason", "confidence"],
2854
+ description: "Required when options are provided"
2855
+ },
2856
+ default: { type: "number", description: "Default option index" }
2857
+ },
2858
+ required: ["text"]
2859
+ },
2860
+ confirmation: {
2861
+ type: "object",
2862
+ description: "Confirmation payload (required when type is confirmation)",
2863
+ properties: {
2864
+ text: { type: "string", description: "What to confirm" },
2865
+ context: {
2866
+ type: "string",
2867
+ description: "Why confirmation is needed"
2868
+ },
2869
+ impact: { type: "string", description: "Impact description" },
2870
+ risk: { type: "string", enum: ["low", "medium", "high"], description: "Risk level" }
2871
+ },
2872
+ required: ["text", "context"]
2873
+ },
2874
+ transition: {
2875
+ type: "object",
2876
+ description: "Transition payload (required when type is transition)",
2877
+ properties: {
2878
+ completedPhase: {
2879
+ type: "string",
2880
+ description: "Phase that was completed"
2881
+ },
2882
+ suggestedNext: {
2883
+ type: "string",
2884
+ description: "Suggested next phase"
2885
+ },
2886
+ reason: {
2887
+ type: "string",
2888
+ description: "Why the transition is happening"
2889
+ },
2890
+ artifacts: {
2891
+ type: "array",
2892
+ items: { type: "string" },
2893
+ description: "File paths produced during the completed phase"
2894
+ },
2895
+ requiresConfirmation: {
2896
+ type: "boolean",
2897
+ description: "true = wait for user confirmation, false = proceed immediately"
2898
+ },
2899
+ summary: {
2900
+ type: "string",
2901
+ description: "1-2 sentence rich summary with key metrics"
2902
+ },
2903
+ qualityGate: {
2904
+ type: "object",
2905
+ properties: {
2906
+ checks: {
2907
+ type: "array",
2908
+ items: {
2909
+ type: "object",
2910
+ properties: {
2911
+ name: { type: "string" },
2912
+ passed: { type: "boolean" },
2913
+ detail: { type: "string" }
2914
+ },
2915
+ required: ["name", "passed"]
2916
+ }
2917
+ },
2918
+ allPassed: { type: "boolean" }
2919
+ },
2920
+ required: ["checks", "allPassed"],
2921
+ description: "Quality gate results for the completed phase"
2922
+ }
2923
+ },
2924
+ required: [
2925
+ "completedPhase",
2926
+ "suggestedNext",
2927
+ "reason",
2928
+ "artifacts",
2929
+ "requiresConfirmation",
2930
+ "summary"
2931
+ ]
2932
+ },
2933
+ batch: {
2934
+ type: "object",
2935
+ description: "Batch decision payload (required when type is batch)",
2936
+ properties: {
2937
+ text: { type: "string", description: "Batch description" },
2938
+ decisions: {
2939
+ type: "array",
2940
+ items: {
2941
+ type: "object",
2942
+ properties: {
2943
+ label: { type: "string" },
2944
+ recommendation: { type: "string" },
2945
+ risk: { type: "string", enum: ["low"] }
2946
+ },
2947
+ required: ["label", "recommendation", "risk"]
2948
+ },
2949
+ description: "Low-risk decisions to approve in batch"
2950
+ }
2951
+ },
2952
+ required: ["text", "decisions"]
2953
+ }
2954
+ },
2955
+ required: ["path", "type"]
2956
+ }
2957
+ };
2958
+ async function handleEmitInteraction(input) {
2959
+ try {
2960
+ const parseResult = EmitInteractionInputSchema.safeParse(input);
2961
+ if (!parseResult.success) {
2962
+ return {
2963
+ content: [
2964
+ {
2965
+ type: "text",
2966
+ text: `Error: ${parseResult.error.issues.map((i) => i.path.length > 0 ? `${i.path.join(".")}: ${i.message}` : i.message).join("; ")}`
2967
+ }
2968
+ ],
2969
+ isError: true
2970
+ };
2971
+ }
2972
+ const validInput = parseResult.data;
2973
+ const projectPath = sanitizePath(validInput.path);
2974
+ const id = randomUUID();
2975
+ switch (validInput.type) {
2976
+ case "question": {
2977
+ if (!validInput.question) {
2978
+ return {
2979
+ content: [
2980
+ {
2981
+ type: "text",
2982
+ text: "Error: question payload is required when type is question"
2983
+ }
2984
+ ],
2985
+ isError: true
2986
+ };
2987
+ }
2988
+ const questionResult = InteractionQuestionWithOptionsSchema.safeParse(validInput.question);
2989
+ if (!questionResult.success) {
2990
+ return {
2991
+ content: [
2992
+ {
2993
+ type: "text",
2994
+ text: `Error: ${questionResult.error.issues.map((i) => i.path.length > 0 ? `${i.path.join(".")}: ${i.message}` : i.message).join("; ")}`
2995
+ }
2996
+ ],
2997
+ isError: true
2998
+ };
2999
+ }
3000
+ const prompt = renderQuestion(questionResult.data);
3001
+ await recordInteraction(
3002
+ projectPath,
3003
+ id,
3004
+ "question",
3005
+ questionResult.data.text,
3006
+ validInput.stream
3007
+ );
3008
+ return {
3009
+ content: [{ type: "text", text: JSON.stringify({ id, prompt }) }]
3010
+ };
3011
+ }
3012
+ case "confirmation": {
3013
+ if (!validInput.confirmation) {
3014
+ return {
3015
+ content: [
3016
+ {
3017
+ type: "text",
3018
+ text: "Error: confirmation payload is required when type is confirmation"
3019
+ }
3020
+ ],
3021
+ isError: true
3022
+ };
3023
+ }
3024
+ const confirmResult = InteractionConfirmationSchema.safeParse(validInput.confirmation);
3025
+ if (!confirmResult.success) {
3026
+ return {
3027
+ content: [
3028
+ {
3029
+ type: "text",
3030
+ text: `Error: ${confirmResult.error.issues.map((i) => i.message).join("; ")}`
3031
+ }
3032
+ ],
3033
+ isError: true
3034
+ };
3035
+ }
3036
+ const prompt = renderConfirmation(confirmResult.data);
3037
+ await recordInteraction(
3038
+ projectPath,
3039
+ id,
3040
+ "confirmation",
3041
+ confirmResult.data.text,
3042
+ validInput.stream
3043
+ );
3044
+ return {
3045
+ content: [{ type: "text", text: JSON.stringify({ id, prompt }) }]
3046
+ };
3047
+ }
3048
+ case "transition": {
3049
+ if (!validInput.transition) {
3050
+ return {
3051
+ content: [
3052
+ {
3053
+ type: "text",
3054
+ text: "Error: transition payload is required when type is transition"
3055
+ }
3056
+ ],
3057
+ isError: true
3058
+ };
3059
+ }
3060
+ const transitionResult = InteractionTransitionSchema.safeParse(validInput.transition);
3061
+ if (!transitionResult.success) {
3062
+ return {
3063
+ content: [
3064
+ {
3065
+ type: "text",
3066
+ text: `Error: ${transitionResult.error.issues.map((i) => i.message).join("; ")}`
3067
+ }
3068
+ ],
3069
+ isError: true
3070
+ };
3071
+ }
3072
+ const transition = transitionResult.data;
3073
+ const prompt = renderTransition(transition);
3074
+ try {
3075
+ const { saveHandoff } = await import("./dist-JVZ2MKBC.js");
3076
+ await saveHandoff(
3077
+ projectPath,
3078
+ {
3079
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3080
+ fromSkill: "emit_interaction",
3081
+ phase: transition.completedPhase,
3082
+ summary: transition.reason,
3083
+ completed: [transition.completedPhase],
3084
+ pending: [transition.suggestedNext],
3085
+ concerns: [],
3086
+ decisions: [],
3087
+ blockers: [],
3088
+ contextKeywords: []
3089
+ },
3090
+ validInput.stream
3091
+ );
3092
+ } catch {
3093
+ }
3094
+ await recordInteraction(
3095
+ projectPath,
3096
+ id,
3097
+ "transition",
3098
+ `${transition.completedPhase} -> ${transition.suggestedNext}`,
3099
+ validInput.stream
3100
+ );
3101
+ const responsePayload = { id, prompt, handoffWritten: true };
3102
+ if (!transition.requiresConfirmation) {
3103
+ responsePayload.autoTransition = true;
3104
+ responsePayload.nextAction = `Invoke harness-${transition.suggestedNext} skill now`;
3105
+ }
3106
+ return {
3107
+ content: [
3108
+ {
3109
+ type: "text",
3110
+ text: JSON.stringify(responsePayload)
3111
+ }
3112
+ ]
3113
+ };
3114
+ }
3115
+ case "batch": {
3116
+ if (!validInput.batch) {
3117
+ return {
3118
+ content: [
3119
+ {
3120
+ type: "text",
3121
+ text: "Error: batch payload is required when type is batch"
3122
+ }
3123
+ ],
3124
+ isError: true
3125
+ };
3126
+ }
3127
+ const batchResult = InteractionBatchSchema.safeParse(validInput.batch);
3128
+ if (!batchResult.success) {
3129
+ return {
3130
+ content: [
3131
+ {
3132
+ type: "text",
3133
+ text: `Error: ${batchResult.error.issues.map((i) => i.message).join("; ")}`
3134
+ }
3135
+ ],
3136
+ isError: true
3137
+ };
3138
+ }
3139
+ const prompt = renderBatch(batchResult.data);
3140
+ await recordInteraction(projectPath, id, "batch", batchResult.data.text, validInput.stream);
3141
+ return {
3142
+ content: [
3143
+ {
3144
+ type: "text",
3145
+ text: JSON.stringify({ id, prompt, batchMode: true })
3146
+ }
3147
+ ]
3148
+ };
3149
+ }
3150
+ default: {
3151
+ return {
3152
+ content: [
3153
+ {
3154
+ type: "text",
3155
+ text: `Error: unknown interaction type: ${String(validInput.type)}`
3156
+ }
3157
+ ],
3158
+ isError: true
3159
+ };
3160
+ }
3161
+ }
3162
+ } catch (error) {
3163
+ return {
3164
+ content: [
3165
+ {
3166
+ type: "text",
3167
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
3168
+ }
3169
+ ],
3170
+ isError: true
3171
+ };
3172
+ }
3173
+ }
3174
+ async function recordInteraction(projectPath, id, type, decision, stream) {
3175
+ try {
3176
+ const { loadState, saveState } = await import("./dist-JVZ2MKBC.js");
3177
+ const stateResult = await loadState(projectPath, stream);
3178
+ if (stateResult.ok) {
3179
+ const state = stateResult.value;
3180
+ state.decisions.push({
3181
+ date: (/* @__PURE__ */ new Date()).toISOString(),
3182
+ decision: `[${type}:${id}] ${decision}`,
3183
+ context: "pending user response"
3184
+ });
3185
+ await saveState(projectPath, state, stream);
3186
+ }
3187
+ } catch {
3188
+ }
3189
+ }
3190
+
3191
+ // src/mcp/tools/gather-context.ts
3192
+ var gatherContextDefinition = {
3193
+ name: "gather_context",
3194
+ description: "Assemble all working context an agent needs in a single call: state, learnings, handoff, graph context, and project validation. Runs constituents in parallel.",
3195
+ inputSchema: {
3196
+ type: "object",
3197
+ properties: {
3198
+ path: { type: "string", description: "Path to project root" },
3199
+ intent: {
3200
+ type: "string",
3201
+ description: "What the agent is about to do (used for graph context search)"
3202
+ },
3203
+ skill: {
3204
+ type: "string",
3205
+ description: "Current skill name (filters learnings by skill)"
3206
+ },
3207
+ tokenBudget: {
3208
+ type: "number",
3209
+ description: "Approximate token budget for graph context (default 4000)"
3210
+ },
3211
+ include: {
3212
+ type: "array",
3213
+ items: {
3214
+ type: "string",
3215
+ enum: ["state", "learnings", "handoff", "graph", "validation"]
3216
+ },
3217
+ description: "Which constituents to include (default: all)"
3218
+ },
3219
+ mode: {
3220
+ type: "string",
3221
+ enum: ["summary", "detailed"],
3222
+ description: "Response density. Default: summary"
3223
+ }
3224
+ },
3225
+ required: ["path", "intent"]
3226
+ }
3227
+ };
3228
+ async function handleGatherContext(input) {
3229
+ const start = Date.now();
3230
+ let projectPath;
3231
+ try {
3232
+ projectPath = sanitizePath(input.path);
3233
+ } catch (error) {
3234
+ return {
3235
+ content: [
3236
+ {
3237
+ type: "text",
3238
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
3239
+ }
3240
+ ],
3241
+ isError: true
3242
+ };
3243
+ }
3244
+ const includeSet = new Set(
3245
+ input.include ?? ["state", "learnings", "handoff", "graph", "validation"]
3246
+ );
3247
+ const errors = [];
3248
+ const statePromise = includeSet.has("state") ? import("./dist-JVZ2MKBC.js").then((core) => core.loadState(projectPath)) : Promise.resolve(null);
3249
+ const learningsPromise = includeSet.has("learnings") ? import("./dist-JVZ2MKBC.js").then(
3250
+ (core) => core.loadRelevantLearnings(projectPath, input.skill)
3251
+ ) : Promise.resolve(null);
3252
+ const handoffPromise = includeSet.has("handoff") ? import("./dist-JVZ2MKBC.js").then((core) => core.loadHandoff(projectPath)) : Promise.resolve(null);
3253
+ const graphPromise = includeSet.has("graph") ? (async () => {
3254
+ const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-KO4GJ5N2.js");
3255
+ const store = await loadGraphStore2(projectPath);
3256
+ if (!store) return null;
3257
+ const { FusionLayer, ContextQL } = await import("./dist-M6BQODWC.js");
3258
+ const fusion = new FusionLayer(store);
3259
+ const cql = new ContextQL(store);
3260
+ const tokenBudget = input.tokenBudget ?? 4e3;
3261
+ const charBudget = tokenBudget * 4;
3262
+ const searchResults = fusion.search(input.intent, 10);
3263
+ if (searchResults.length === 0) return { context: [], tokenBudget };
3264
+ const contextBlocks = [];
3265
+ let totalChars = 0;
3266
+ for (const result of searchResults) {
3267
+ if (totalChars >= charBudget) break;
3268
+ const expanded = cql.execute({
3269
+ rootNodeIds: [result.nodeId],
3270
+ maxDepth: 2
3271
+ });
3272
+ const blockJson = JSON.stringify({
3273
+ rootNode: result.nodeId,
3274
+ score: result.score,
3275
+ nodes: expanded.nodes,
3276
+ edges: expanded.edges
3277
+ });
3278
+ if (totalChars + blockJson.length > charBudget && contextBlocks.length > 0) break;
3279
+ contextBlocks.push({
3280
+ rootNode: result.nodeId,
3281
+ score: result.score,
3282
+ nodes: expanded.nodes,
3283
+ edges: expanded.edges
3284
+ });
3285
+ totalChars += blockJson.length;
3286
+ }
3287
+ return {
3288
+ intent: input.intent,
3289
+ tokenBudget,
3290
+ blocksReturned: contextBlocks.length,
3291
+ context: contextBlocks
3292
+ };
3293
+ })() : Promise.resolve(null);
3294
+ const validationPromise = includeSet.has("validation") ? (async () => {
3295
+ const { handleValidateProject: handleValidateProject2 } = await import("./validate-JN44D2Q7.js");
3296
+ const result = await handleValidateProject2({ path: projectPath });
3297
+ const first = result.content[0];
3298
+ return first ? JSON.parse(first.text) : null;
3299
+ })() : Promise.resolve(null);
3300
+ const [stateResult, learningsResult, handoffResult, graphResult, validationResult] = await Promise.allSettled([
3301
+ statePromise,
3302
+ learningsPromise,
3303
+ handoffPromise,
3304
+ graphPromise,
3305
+ validationPromise
3306
+ ]);
3307
+ function extract(settled, name) {
3308
+ if (settled.status === "rejected") {
3309
+ errors.push(`${name}: ${String(settled.reason)}`);
3310
+ return null;
3311
+ }
3312
+ return settled.value;
3313
+ }
3314
+ const stateRaw = extract(stateResult, "state");
3315
+ const learningsRaw = extract(learningsResult, "learnings");
3316
+ const handoffRaw = extract(handoffResult, "handoff");
3317
+ const graphContextRaw = extract(graphResult, "graph");
3318
+ const validationRaw = extract(validationResult, "validation");
3319
+ const state = stateRaw && typeof stateRaw === "object" && "ok" in stateRaw ? stateRaw.ok ? stateRaw.value : (() => {
3320
+ errors.push(`state: ${stateRaw.error.message}`);
3321
+ return null;
3322
+ })() : stateRaw;
3323
+ const learnings = learningsRaw && typeof learningsRaw === "object" && "ok" in learningsRaw ? learningsRaw.ok ? learningsRaw.value : (() => {
3324
+ errors.push(
3325
+ `learnings: ${learningsRaw.error.message}`
3326
+ );
3327
+ return [];
3328
+ })() : learningsRaw ?? [];
3329
+ const handoff = handoffRaw && typeof handoffRaw === "object" && "ok" in handoffRaw ? handoffRaw.ok ? handoffRaw.value : (() => {
3330
+ errors.push(`handoff: ${handoffRaw.error.message}`);
3331
+ return null;
3332
+ })() : handoffRaw;
3333
+ const graphContext = graphContextRaw;
3334
+ const validation = validationRaw;
3335
+ const assembledIn = Date.now() - start;
3336
+ const mode = input.mode ?? "summary";
3337
+ const outputState = state ?? null;
3338
+ const outputLearnings = learnings ?? [];
3339
+ const outputHandoff = handoff ?? null;
3340
+ const outputGraphContext = graphContext == null ? null : mode === "summary" ? {
3341
+ blocksReturned: graphContext.blocksReturned ?? 0,
3342
+ nodeCount: (graphContext.context ?? []).reduce(
3343
+ (sum, b) => sum + (Array.isArray(b.nodes) ? b.nodes.length : 0),
3344
+ 0
3345
+ ),
3346
+ edgeCount: (graphContext.context ?? []).reduce(
3347
+ (sum, b) => sum + (Array.isArray(b.edges) ? b.edges.length : 0),
3348
+ 0
3349
+ ),
3350
+ intent: graphContext.intent ?? null
3351
+ } : graphContext;
3352
+ const outputValidation = validation ?? null;
3353
+ const output = {
3354
+ state: outputState,
3355
+ learnings: outputLearnings,
3356
+ handoff: outputHandoff,
3357
+ graphContext: outputGraphContext,
3358
+ validation: outputValidation,
3359
+ meta: {
3360
+ assembledIn,
3361
+ graphAvailable: graphContext !== null,
3362
+ tokenEstimate: 0,
3363
+ // set below from final serialization
3364
+ errors
3365
+ }
3366
+ };
3367
+ const outputText = JSON.stringify(output);
3368
+ const tokenEstimate = Math.ceil(outputText.length / 4);
3369
+ output.meta.tokenEstimate = tokenEstimate;
3370
+ return {
3371
+ content: [
3372
+ {
3373
+ type: "text",
3374
+ text: JSON.stringify(output)
3375
+ }
3376
+ ]
3377
+ };
3378
+ }
3379
+
3380
+ // src/mcp/tools/assess-project.ts
3381
+ var assessProjectDefinition = {
3382
+ name: "assess_project",
3383
+ description: "Run all project health checks in parallel and return a unified report. Checks: validate, dependencies, docs, entropy, security, performance, lint.",
3384
+ inputSchema: {
3385
+ type: "object",
3386
+ properties: {
3387
+ path: { type: "string", description: "Path to project root" },
3388
+ checks: {
3389
+ type: "array",
3390
+ items: {
3391
+ type: "string",
3392
+ enum: ["validate", "deps", "docs", "entropy", "security", "perf", "lint"]
3393
+ },
3394
+ description: "Which checks to run (default: all)"
3395
+ },
3396
+ mode: {
3397
+ type: "string",
3398
+ enum: ["summary", "detailed"],
3399
+ description: "Response density. Default: summary"
3400
+ }
3401
+ },
3402
+ required: ["path"]
3403
+ }
3404
+ };
3405
+ async function handleAssessProject(input) {
3406
+ const start = Date.now();
3407
+ let projectPath;
3408
+ try {
3409
+ projectPath = sanitizePath(input.path);
3410
+ } catch (error) {
3411
+ return {
3412
+ content: [
3413
+ {
3414
+ type: "text",
3415
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
3416
+ }
3417
+ ],
3418
+ isError: true
3419
+ };
3420
+ }
3421
+ const checksToRun = new Set(
3422
+ input.checks ?? ["validate", "deps", "docs", "entropy", "security", "perf", "lint"]
3423
+ );
3424
+ const mode = input.mode ?? "summary";
3425
+ let validateResult = null;
3426
+ if (checksToRun.has("validate")) {
3427
+ try {
3428
+ const { handleValidateProject: handleValidateProject2 } = await import("./validate-JN44D2Q7.js");
3429
+ const result = await handleValidateProject2({ path: projectPath });
3430
+ const first = result.content[0];
3431
+ const parsed = first ? JSON.parse(first.text) : {};
3432
+ validateResult = {
3433
+ name: "validate",
3434
+ passed: parsed.valid === true,
3435
+ issueCount: parsed.errors?.length ?? 0,
3436
+ ...parsed.errors?.length > 0 ? { topIssue: parsed.errors[0] } : {},
3437
+ ...mode === "detailed" ? { detailed: parsed } : {}
3438
+ };
3439
+ } catch (error) {
3440
+ validateResult = {
3441
+ name: "validate",
3442
+ passed: false,
3443
+ issueCount: 1,
3444
+ topIssue: error instanceof Error ? error.message : String(error)
3445
+ };
3446
+ }
3447
+ }
3448
+ const parallelChecks = [];
3449
+ if (checksToRun.has("deps")) {
3450
+ parallelChecks.push(
3451
+ (async () => {
3452
+ try {
3453
+ const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-EXNUMH5R.js");
3454
+ const result = await handleCheckDependencies2({ path: projectPath });
3455
+ const first = result.content[0];
3456
+ const parsed = first ? JSON.parse(first.text) : {};
3457
+ const violations = parsed.violations ?? [];
3458
+ return {
3459
+ name: "deps",
3460
+ passed: !result.isError && violations.length === 0,
3461
+ issueCount: violations.length,
3462
+ ...violations.length > 0 ? { topIssue: violations[0]?.message ?? String(violations[0]) } : {},
3463
+ ...mode === "detailed" ? { detailed: parsed } : {}
3464
+ };
3465
+ } catch (error) {
3466
+ return {
3467
+ name: "deps",
3468
+ passed: false,
3469
+ issueCount: 1,
3470
+ topIssue: error instanceof Error ? error.message : String(error)
3471
+ };
3472
+ }
3473
+ })()
3474
+ );
3475
+ }
3476
+ if (checksToRun.has("docs")) {
3477
+ parallelChecks.push(
3478
+ (async () => {
3479
+ try {
3480
+ const { handleCheckDocs: handleCheckDocs2 } = await import("./docs-PWCUVYWU.js");
3481
+ const result = await handleCheckDocs2({ path: projectPath, scope: "coverage" });
3482
+ const first = result.content[0];
3483
+ const parsed = first ? JSON.parse(first.text) : {};
3484
+ const undocumented = parsed.undocumented ?? parsed.files?.undocumented ?? [];
3485
+ return {
3486
+ name: "docs",
3487
+ passed: !result.isError,
3488
+ issueCount: Array.isArray(undocumented) ? undocumented.length : 0,
3489
+ ...Array.isArray(undocumented) && undocumented.length > 0 ? { topIssue: `Undocumented: ${undocumented[0]}` } : {},
3490
+ ...mode === "detailed" ? { detailed: parsed } : {}
3491
+ };
3492
+ } catch (error) {
3493
+ return {
3494
+ name: "docs",
3495
+ passed: false,
3496
+ issueCount: 1,
3497
+ topIssue: error instanceof Error ? error.message : String(error)
3498
+ };
3499
+ }
3500
+ })()
3501
+ );
3502
+ }
3503
+ if (checksToRun.has("entropy")) {
3504
+ parallelChecks.push(
3505
+ (async () => {
3506
+ try {
3507
+ const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-4I6JEYAC.js");
3508
+ const result = await handleDetectEntropy2({ path: projectPath, type: "all" });
3509
+ const first = result.content[0];
3510
+ const parsed = first ? JSON.parse(first.text) : {};
3511
+ const issues = (parsed.drift?.staleReferences?.length ?? 0) + (parsed.drift?.missingTargets?.length ?? 0) + (parsed.deadCode?.unusedImports?.length ?? 0) + (parsed.deadCode?.unusedExports?.length ?? 0) + (parsed.patterns?.violations?.length ?? 0);
3512
+ return {
3513
+ name: "entropy",
3514
+ passed: !result.isError && issues === 0,
3515
+ issueCount: issues,
3516
+ ...issues > 0 ? { topIssue: "Entropy detected -- run detect_entropy for details" } : {},
3517
+ ...mode === "detailed" ? { detailed: parsed } : {}
3518
+ };
3519
+ } catch (error) {
3520
+ return {
3521
+ name: "entropy",
3522
+ passed: false,
3523
+ issueCount: 1,
3524
+ topIssue: error instanceof Error ? error.message : String(error)
3525
+ };
3526
+ }
3527
+ })()
3528
+ );
3529
+ }
3530
+ if (checksToRun.has("security")) {
3531
+ parallelChecks.push(
3532
+ (async () => {
3533
+ try {
3534
+ const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-4P2GGFF6.js");
3535
+ const result = await handleRunSecurityScan2({ path: projectPath });
3536
+ const first = result.content[0];
3537
+ const parsed = first ? JSON.parse(first.text) : {};
3538
+ const findings = parsed.findings ?? [];
3539
+ const errorCount = findings.filter(
3540
+ (f) => f.severity === "error"
3541
+ ).length;
3542
+ return {
3543
+ name: "security",
3544
+ passed: !result.isError && errorCount === 0,
3545
+ issueCount: findings.length,
3546
+ ...findings.length > 0 ? {
3547
+ topIssue: `${findings[0]?.rule ?? findings[0]?.type ?? "finding"}: ${findings[0]?.message ?? ""}`
3548
+ } : {},
3549
+ ...mode === "detailed" ? { detailed: parsed } : {}
3550
+ };
3551
+ } catch (error) {
3552
+ return {
3553
+ name: "security",
3554
+ passed: false,
3555
+ issueCount: 1,
3556
+ topIssue: error instanceof Error ? error.message : String(error)
3557
+ };
3558
+ }
3559
+ })()
3560
+ );
3561
+ }
3562
+ if (checksToRun.has("perf")) {
3563
+ parallelChecks.push(
3564
+ (async () => {
3565
+ try {
3566
+ const { handleCheckPerformance: handleCheckPerformance2 } = await import("./performance-BTOJCPXU.js");
3567
+ const result = await handleCheckPerformance2({ path: projectPath });
3568
+ const first = result.content[0];
3569
+ const parsed = first ? JSON.parse(first.text) : {};
3570
+ const issues = parsed.violations?.length ?? parsed.issues?.length ?? 0;
3571
+ return {
3572
+ name: "perf",
3573
+ passed: !result.isError && issues === 0,
3574
+ issueCount: issues,
3575
+ ...issues > 0 ? { topIssue: "Performance issues detected" } : {},
3576
+ ...mode === "detailed" ? { detailed: parsed } : {}
3577
+ };
3578
+ } catch (error) {
3579
+ return {
3580
+ name: "perf",
3581
+ passed: false,
3582
+ issueCount: 1,
3583
+ topIssue: error instanceof Error ? error.message : String(error)
3584
+ };
3585
+ }
3586
+ })()
3587
+ );
3588
+ }
3589
+ if (checksToRun.has("lint")) {
3590
+ parallelChecks.push(
3591
+ (async () => {
3592
+ try {
3593
+ const { execFileSync } = await import("child_process");
3594
+ const output = execFileSync("npx", ["turbo", "run", "lint", "--force"], {
3595
+ cwd: projectPath,
3596
+ encoding: "utf-8",
3597
+ timeout: 6e4,
3598
+ stdio: ["pipe", "pipe", "pipe"]
3599
+ });
3600
+ return {
3601
+ name: "lint",
3602
+ passed: true,
3603
+ issueCount: 0,
3604
+ ...mode === "detailed" ? { detailed: output } : {}
3605
+ };
3606
+ } catch (error) {
3607
+ const stderr = error && typeof error === "object" && "stderr" in error ? String(error.stderr) : "";
3608
+ const stdout = error && typeof error === "object" && "stdout" in error ? String(error.stdout) : "";
3609
+ const combined = (stderr + "\n" + stdout).trim();
3610
+ const errorMatch = combined.match(/(\d+) error/);
3611
+ const issueCount = errorMatch?.[1] ? parseInt(errorMatch[1], 10) : 1;
3612
+ const firstError = combined.split("\n").find((line) => line.includes("error"));
3613
+ return {
3614
+ name: "lint",
3615
+ passed: false,
3616
+ issueCount,
3617
+ topIssue: firstError?.trim() ?? (error instanceof Error ? error.message : String(error)),
3618
+ ...mode === "detailed" ? { detailed: combined } : {}
3619
+ };
3620
+ }
3621
+ })()
3622
+ );
3623
+ }
3624
+ const parallelResults = await Promise.all(parallelChecks);
3625
+ const allChecks = [];
3626
+ if (validateResult) allChecks.push(validateResult);
3627
+ allChecks.push(...parallelResults);
3628
+ const healthy = allChecks.every((c) => c.passed);
3629
+ const assessedIn = Date.now() - start;
3630
+ if (mode === "summary") {
3631
+ const summaryChecks = allChecks.map(({ detailed: _d, ...rest }) => rest);
3632
+ return {
3633
+ content: [
3634
+ {
3635
+ type: "text",
3636
+ text: JSON.stringify({ healthy, checks: summaryChecks, assessedIn })
3637
+ }
3638
+ ]
3639
+ };
3640
+ }
3641
+ return {
3642
+ content: [
3643
+ {
3644
+ type: "text",
3645
+ text: JSON.stringify({ healthy, checks: allChecks, assessedIn })
3646
+ }
3647
+ ]
3648
+ };
3649
+ }
3650
+
3651
+ // src/mcp/tools/review-changes.ts
3652
+ var SIZE_GATE_LINES = 1e4;
3653
+ var reviewChangesDefinition = {
3654
+ name: "review_changes",
3655
+ description: "Review code changes at configurable depth: quick (diff analysis), standard (+ self-review), deep (full 7-phase pipeline). Auto-downgrades deep to standard for diffs > 10k lines.",
3656
+ inputSchema: {
3657
+ type: "object",
3658
+ properties: {
3659
+ path: { type: "string", description: "Path to project root" },
3660
+ diff: {
3661
+ type: "string",
3662
+ description: "Raw git diff string. If omitted, auto-detects from git."
3663
+ },
3664
+ depth: {
3665
+ type: "string",
3666
+ enum: ["quick", "standard", "deep"],
3667
+ description: "Review depth: quick, standard, or deep"
3668
+ },
3669
+ mode: {
3670
+ type: "string",
3671
+ enum: ["summary", "detailed"],
3672
+ description: "Response density. Default: summary"
3673
+ }
3674
+ },
3675
+ required: ["path", "depth"]
3676
+ }
3677
+ };
3678
+ async function getDiff(projectPath, providedDiff) {
3679
+ if (providedDiff) return providedDiff;
3680
+ const { execFileSync } = await import("child_process");
3681
+ try {
3682
+ const staged = execFileSync("git", ["diff", "--cached"], {
3683
+ cwd: projectPath,
3684
+ encoding: "utf-8",
3685
+ timeout: 1e4
3686
+ });
3687
+ if (staged.trim().length > 0) return staged;
3688
+ const unstaged = execFileSync("git", ["diff"], {
3689
+ cwd: projectPath,
3690
+ encoding: "utf-8",
3691
+ timeout: 1e4
3692
+ });
3693
+ if (unstaged.trim().length > 0) return unstaged;
3694
+ throw new Error("No diff found -- provide a diff string or have uncommitted changes");
3695
+ } catch (error) {
3696
+ if (error instanceof Error && error.message.includes("No diff found")) throw error;
3697
+ throw new Error(
3698
+ `Failed to get diff from git: ${error instanceof Error ? error.message : String(error)}`,
3699
+ { cause: error }
3700
+ );
3701
+ }
3702
+ }
3703
+ async function handleReviewChanges(input) {
3704
+ let projectPath;
3705
+ try {
3706
+ projectPath = sanitizePath(input.path);
3707
+ } catch (error) {
3708
+ return {
3709
+ content: [
3710
+ {
3711
+ type: "text",
3712
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
3713
+ }
3714
+ ],
3715
+ isError: true
3716
+ };
3717
+ }
3718
+ let diff;
3719
+ try {
3720
+ diff = await getDiff(projectPath, input.diff);
3721
+ } catch (error) {
3722
+ return {
3723
+ content: [
3724
+ {
3725
+ type: "text",
3726
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
3727
+ }
3728
+ ],
3729
+ isError: true
3730
+ };
3731
+ }
3732
+ const diffLines = diff.split("\n").length;
3733
+ let effectiveDepth = input.depth;
3734
+ let downgraded = false;
3735
+ if (effectiveDepth === "deep" && diffLines > SIZE_GATE_LINES) {
3736
+ effectiveDepth = "standard";
3737
+ downgraded = true;
3738
+ }
3739
+ try {
3740
+ if (effectiveDepth === "quick") {
3741
+ const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-TNIW534S.js");
3742
+ const result2 = await handleAnalyzeDiff2({ diff, path: projectPath });
3743
+ const firstContent = result2.content[0];
3744
+ if (!firstContent) throw new Error("Empty analyze_diff response");
3745
+ const parsed2 = JSON.parse(firstContent.text);
3746
+ return {
3747
+ content: [
3748
+ {
3749
+ type: "text",
3750
+ text: JSON.stringify({
3751
+ depth: "quick",
3752
+ downgraded,
3753
+ findings: parsed2.findings ?? parsed2.warnings ?? [],
3754
+ fileCount: parsed2.summary?.filesChanged ?? parsed2.files?.length ?? 0,
3755
+ lineCount: diffLines,
3756
+ ...result2.isError ? { error: parsed2 } : {}
3757
+ })
3758
+ }
3759
+ ]
3760
+ };
3761
+ }
3762
+ if (effectiveDepth === "standard") {
3763
+ const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-TNIW534S.js");
3764
+ const [diffResult, reviewResult] = await Promise.all([
3765
+ handleAnalyzeDiff2({ diff, path: projectPath }),
3766
+ handleCreateSelfReview2({ path: projectPath, diff })
3767
+ ]);
3768
+ const diffContent = diffResult.content[0];
3769
+ const reviewContent = reviewResult.content[0];
3770
+ if (!diffContent || !reviewContent) throw new Error("Empty review response");
3771
+ const diffParsed = JSON.parse(diffContent.text);
3772
+ const reviewParsed = JSON.parse(reviewContent.text);
3773
+ const findings = [
3774
+ ...diffParsed.findings ?? diffParsed.warnings ?? [],
3775
+ ...reviewParsed.findings ?? reviewParsed.items ?? []
3776
+ ];
3777
+ return {
3778
+ content: [
3779
+ {
3780
+ type: "text",
3781
+ text: JSON.stringify({
3782
+ depth: "standard",
3783
+ downgraded,
3784
+ findings,
3785
+ diffAnalysis: diffParsed,
3786
+ selfReview: reviewParsed,
3787
+ fileCount: diffParsed.summary?.filesChanged ?? diffParsed.files?.length ?? 0,
3788
+ lineCount: diffLines
3789
+ })
3790
+ }
3791
+ ]
3792
+ };
3793
+ }
3794
+ const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-3YTW3463.js");
3795
+ const result = await handleRunCodeReview2({ path: projectPath, diff });
3796
+ const deepContent = result.content[0];
3797
+ if (!deepContent) throw new Error("Empty code review response");
3798
+ const parsed = JSON.parse(deepContent.text);
3799
+ return {
3800
+ content: [
3801
+ {
3802
+ type: "text",
3803
+ text: JSON.stringify({
3804
+ depth: "deep",
3805
+ downgraded: false,
3806
+ findings: parsed.findings ?? [],
3807
+ assessment: parsed.assessment,
3808
+ findingCount: parsed.findingCount,
3809
+ lineCount: diffLines,
3810
+ pipeline: parsed
3811
+ })
3812
+ }
3813
+ ]
3814
+ };
3815
+ } catch (error) {
3816
+ return {
3817
+ content: [
3818
+ {
3819
+ type: "text",
3820
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
3821
+ }
3822
+ ],
3823
+ isError: true
3824
+ };
3825
+ }
3826
+ }
3827
+
3828
+ // src/mcp/tools/task-independence.ts
3829
+ var checkTaskIndependenceDefinition = {
3830
+ name: "check_task_independence",
3831
+ description: "Check whether N tasks can safely run in parallel by detecting file overlaps and transitive dependency conflicts. Returns pairwise independence matrix and parallel groupings.",
3832
+ inputSchema: {
3833
+ type: "object",
3834
+ properties: {
3835
+ path: { type: "string", description: "Path to project root" },
3836
+ tasks: {
3837
+ type: "array",
3838
+ items: {
3839
+ type: "object",
3840
+ properties: {
3841
+ id: { type: "string" },
3842
+ files: { type: "array", items: { type: "string" } }
3843
+ },
3844
+ required: ["id", "files"]
3845
+ },
3846
+ minItems: 2,
3847
+ description: "Tasks to check. Each task has an id and a list of file paths."
3848
+ },
3849
+ depth: {
3850
+ type: "number",
3851
+ description: "Expansion depth (0=file-only, 1=default, 2-3=thorough)"
3852
+ },
3853
+ edgeTypes: {
3854
+ type: "array",
3855
+ items: { type: "string" },
3856
+ description: "Edge types for graph expansion. Default: imports, calls, references"
3857
+ },
3858
+ mode: {
3859
+ type: "string",
3860
+ enum: ["summary", "detailed"],
3861
+ description: "summary omits overlap details. Default: detailed"
3862
+ }
3863
+ },
3864
+ required: ["path", "tasks"]
3865
+ }
3866
+ };
3867
+ async function handleCheckTaskIndependence(input) {
3868
+ try {
3869
+ const projectPath = sanitizePath(input.path);
3870
+ const store = await loadGraphStore(projectPath);
3871
+ const { TaskIndependenceAnalyzer } = await import("./dist-M6BQODWC.js");
3872
+ const analyzer = new TaskIndependenceAnalyzer(store ?? void 0);
3873
+ const result = analyzer.analyze({
3874
+ tasks: input.tasks,
3875
+ ...input.depth !== void 0 && { depth: input.depth },
3876
+ ...input.edgeTypes !== void 0 && { edgeTypes: input.edgeTypes }
3877
+ });
3878
+ if (input.mode === "summary") {
3879
+ const summaryPairs = result.pairs.map((p) => ({
3880
+ taskA: p.taskA,
3881
+ taskB: p.taskB,
3882
+ independent: p.independent
3883
+ }));
3884
+ return {
3885
+ content: [
3886
+ {
3887
+ type: "text",
3888
+ text: JSON.stringify({
3889
+ mode: "summary",
3890
+ verdict: result.verdict,
3891
+ analysisLevel: result.analysisLevel,
3892
+ depth: result.depth,
3893
+ groups: result.groups,
3894
+ pairs: summaryPairs
3895
+ })
3896
+ }
3897
+ ]
3898
+ };
3899
+ }
3900
+ return {
3901
+ content: [{ type: "text", text: JSON.stringify(result) }]
3902
+ };
3903
+ } catch (error) {
3904
+ return {
3905
+ content: [
3906
+ {
3907
+ type: "text",
3908
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
3909
+ }
3910
+ ],
3911
+ isError: true
3912
+ };
3913
+ }
3914
+ }
3915
+
3916
+ // src/mcp/tools/conflict-prediction.ts
3917
+ var predictConflictsDefinition = {
3918
+ name: "predict_conflicts",
3919
+ description: "Predict conflict severity for task pairs with automatic parallel group recomputation. Returns severity-classified conflicts, revised groups, and human-readable reasoning.",
3920
+ inputSchema: {
3921
+ type: "object",
3922
+ properties: {
3923
+ path: { type: "string", description: "Path to project root" },
3924
+ tasks: {
3925
+ type: "array",
3926
+ items: {
3927
+ type: "object",
3928
+ properties: {
3929
+ id: { type: "string" },
3930
+ files: { type: "array", items: { type: "string" } }
3931
+ },
3932
+ required: ["id", "files"]
3933
+ },
3934
+ minItems: 2,
3935
+ description: "Tasks to check. Each task has an id and a list of file paths."
3936
+ },
3937
+ depth: {
3938
+ type: "number",
3939
+ description: "Expansion depth (0=file-only, 1=default, 2-3=thorough)"
3940
+ },
3941
+ edgeTypes: {
3942
+ type: "array",
3943
+ items: { type: "string" },
3944
+ description: "Edge types for graph expansion. Default: imports, calls, references"
3945
+ },
3946
+ mode: {
3947
+ type: "string",
3948
+ enum: ["summary", "detailed"],
3949
+ description: "summary omits overlap details from conflicts. Default: detailed"
3950
+ }
3951
+ },
3952
+ required: ["path", "tasks"]
3953
+ }
3954
+ };
3955
+ async function handlePredictConflicts(input) {
3956
+ try {
3957
+ const projectPath = sanitizePath(input.path);
3958
+ const store = await loadGraphStore(projectPath);
3959
+ const { ConflictPredictor } = await import("./dist-M6BQODWC.js");
3960
+ const predictor = new ConflictPredictor(store ?? void 0);
3961
+ const result = predictor.predict({
3962
+ tasks: input.tasks,
3963
+ ...input.depth !== void 0 && { depth: input.depth },
3964
+ ...input.edgeTypes !== void 0 && { edgeTypes: input.edgeTypes }
3965
+ });
3966
+ if (input.mode === "summary") {
3967
+ const summaryConflicts = result.conflicts.map((c) => ({
3968
+ taskA: c.taskA,
3969
+ taskB: c.taskB,
3970
+ severity: c.severity,
3971
+ reason: c.reason,
3972
+ mitigation: c.mitigation
3973
+ }));
3974
+ return {
3975
+ content: [
3976
+ {
3977
+ type: "text",
3978
+ text: JSON.stringify({
3979
+ mode: "summary",
3980
+ tasks: result.tasks,
3981
+ analysisLevel: result.analysisLevel,
3982
+ depth: result.depth,
3983
+ conflicts: summaryConflicts,
3984
+ groups: result.groups,
3985
+ summary: result.summary,
3986
+ verdict: result.verdict
3987
+ })
3988
+ }
3989
+ ]
3990
+ };
3991
+ }
3992
+ return {
3993
+ content: [{ type: "text", text: JSON.stringify(result) }]
3994
+ };
3995
+ } catch (error) {
3996
+ return {
3997
+ content: [
3998
+ {
3999
+ type: "text",
4000
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
4001
+ }
4002
+ ],
4003
+ isError: true
4004
+ };
4005
+ }
4006
+ }
4007
+
4008
+ // src/mcp/tools/stale-constraints.ts
4009
+ var detectStaleConstraintsDefinition = {
4010
+ name: "detect_stale_constraints",
4011
+ description: "Detect architectural constraint rules that have not been violated within a configurable time window. Surfaces stale constraints as candidates for removal or relaxation.",
4012
+ inputSchema: {
4013
+ type: "object",
4014
+ properties: {
4015
+ path: { type: "string", description: "Path to project root" },
4016
+ windowDays: {
4017
+ type: "number",
4018
+ description: "Number of days without violation to consider a constraint stale (default: 30)"
4019
+ },
4020
+ category: {
4021
+ type: "string",
4022
+ description: "Optional filter by constraint category",
4023
+ enum: [
4024
+ "circular-deps",
4025
+ "layer-violations",
4026
+ "complexity",
4027
+ "coupling",
4028
+ "forbidden-imports",
4029
+ "module-size",
4030
+ "dependency-depth"
4031
+ ]
4032
+ }
4033
+ },
4034
+ required: ["path"]
4035
+ }
4036
+ };
4037
+ async function handleDetectStaleConstraints(input) {
4038
+ let projectPath;
4039
+ try {
4040
+ projectPath = sanitizePath(input.path);
4041
+ } catch (error) {
4042
+ return {
4043
+ content: [
4044
+ {
4045
+ type: "text",
4046
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
4047
+ }
4048
+ ],
4049
+ isError: true
4050
+ };
4051
+ }
4052
+ try {
4053
+ const windowDays = input.windowDays ?? 30;
4054
+ if (!Number.isFinite(windowDays) || windowDays < 1) {
4055
+ return {
4056
+ content: [
4057
+ {
4058
+ type: "text",
4059
+ text: "Error: windowDays must be a finite number >= 1"
4060
+ }
4061
+ ],
4062
+ isError: true
4063
+ };
4064
+ }
4065
+ const { loadGraphStore: loadGraphStore2 } = await import("./graph-loader-KO4GJ5N2.js");
4066
+ const store = await loadGraphStore2(projectPath);
4067
+ if (!store) {
4068
+ return {
4069
+ content: [
4070
+ {
4071
+ type: "text",
4072
+ text: JSON.stringify(
4073
+ {
4074
+ staleConstraints: [],
4075
+ totalConstraints: 0,
4076
+ windowDays,
4077
+ note: "No graph available. Run ingest_source first to populate the knowledge graph."
4078
+ },
4079
+ null,
4080
+ 2
4081
+ )
4082
+ }
4083
+ ]
4084
+ };
4085
+ }
4086
+ const { detectStaleConstraints } = await import("./dist-JVZ2MKBC.js");
4087
+ const result = detectStaleConstraints(
4088
+ store,
4089
+ windowDays,
4090
+ input.category
4091
+ );
4092
+ return {
4093
+ content: [
4094
+ {
4095
+ type: "text",
4096
+ text: JSON.stringify(result, null, 2)
4097
+ }
4098
+ ]
4099
+ };
4100
+ } catch (error) {
4101
+ return {
4102
+ content: [
4103
+ {
4104
+ type: "text",
4105
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
4106
+ }
4107
+ ],
4108
+ isError: true
4109
+ };
4110
+ }
4111
+ }
4112
+
4113
+ // src/mcp/server.ts
4114
+ var TOOL_DEFINITIONS = [
4115
+ validateToolDefinition,
4116
+ checkDependenciesDefinition,
4117
+ checkDocsDefinition,
4118
+ detectEntropyDefinition,
4119
+ generateLinterDefinition,
4120
+ validateLinterConfigDefinition,
4121
+ initProjectDefinition,
4122
+ listPersonasDefinition,
4123
+ generatePersonaArtifactsDefinition,
4124
+ runPersonaDefinition,
4125
+ addComponentDefinition,
4126
+ runAgentTaskDefinition,
4127
+ runSkillDefinition,
4128
+ manageStateDefinition,
4129
+ createSelfReviewDefinition,
4130
+ analyzeDiffDefinition,
4131
+ requestPeerReviewDefinition,
4132
+ checkPhaseGateDefinition,
4133
+ validateCrossCheckDefinition,
4134
+ createSkillDefinition,
4135
+ generateSlashCommandsDefinition,
4136
+ queryGraphDefinition,
4137
+ searchSimilarDefinition,
4138
+ findContextForDefinition,
4139
+ getRelationshipsDefinition,
4140
+ getImpactDefinition,
4141
+ ingestSourceDefinition,
4142
+ generateAgentDefinitionsDefinition,
4143
+ runSecurityScanDefinition,
4144
+ checkPerformanceDefinition,
4145
+ getPerfBaselinesDefinition,
4146
+ updatePerfBaselinesDefinition,
4147
+ getCriticalPathsDefinition,
4148
+ listStreamsDefinition,
4149
+ manageRoadmapDefinition,
4150
+ emitInteractionDefinition,
4151
+ runCodeReviewDefinition,
4152
+ gatherContextDefinition,
4153
+ assessProjectDefinition,
4154
+ reviewChangesDefinition,
4155
+ detectAnomaliesDefinition,
4156
+ askGraphDefinition,
4157
+ checkTaskIndependenceDefinition,
4158
+ predictConflictsDefinition,
4159
+ detectStaleConstraintsDefinition
4160
+ ];
4161
+ var TOOL_HANDLERS = {
4162
+ validate_project: handleValidateProject,
4163
+ check_dependencies: handleCheckDependencies,
4164
+ check_docs: handleCheckDocs,
4165
+ detect_entropy: handleDetectEntropy,
4166
+ generate_linter: handleGenerateLinter,
4167
+ validate_linter_config: handleValidateLinterConfig,
4168
+ init_project: handleInitProject,
4169
+ list_personas: handleListPersonas,
4170
+ generate_persona_artifacts: handleGeneratePersonaArtifacts,
4171
+ run_persona: handleRunPersona,
4172
+ add_component: handleAddComponent,
4173
+ run_agent_task: handleRunAgentTask,
4174
+ run_skill: handleRunSkill,
4175
+ manage_state: handleManageState,
4176
+ create_self_review: handleCreateSelfReview,
4177
+ analyze_diff: handleAnalyzeDiff,
4178
+ request_peer_review: handleRequestPeerReview,
4179
+ check_phase_gate: handleCheckPhaseGate,
4180
+ validate_cross_check: handleValidateCrossCheck,
4181
+ create_skill: handleCreateSkill,
4182
+ generate_slash_commands: handleGenerateSlashCommands,
4183
+ query_graph: handleQueryGraph,
4184
+ search_similar: handleSearchSimilar,
4185
+ find_context_for: handleFindContextFor,
4186
+ get_relationships: handleGetRelationships,
4187
+ get_impact: handleGetImpact,
4188
+ ingest_source: handleIngestSource,
4189
+ generate_agent_definitions: handleGenerateAgentDefinitions,
4190
+ run_security_scan: handleRunSecurityScan,
4191
+ check_performance: handleCheckPerformance,
4192
+ get_perf_baselines: handleGetPerfBaselines,
4193
+ update_perf_baselines: handleUpdatePerfBaselines,
4194
+ get_critical_paths: handleGetCriticalPaths,
4195
+ list_streams: handleListStreams,
4196
+ manage_roadmap: handleManageRoadmap,
4197
+ emit_interaction: handleEmitInteraction,
4198
+ run_code_review: handleRunCodeReview,
4199
+ gather_context: handleGatherContext,
4200
+ assess_project: handleAssessProject,
4201
+ review_changes: handleReviewChanges,
4202
+ detect_anomalies: handleDetectAnomalies,
4203
+ ask_graph: handleAskGraph,
4204
+ check_task_independence: handleCheckTaskIndependence,
4205
+ predict_conflicts: handlePredictConflicts,
4206
+ detect_stale_constraints: handleDetectStaleConstraints
4207
+ };
4208
+ var RESOURCE_DEFINITIONS = [
4209
+ {
4210
+ uri: "harness://skills",
4211
+ name: "Harness Skills",
4212
+ description: "Available skills with metadata (name, description, cognitive_mode, type, triggers)",
4213
+ mimeType: "application/json"
4214
+ },
4215
+ {
4216
+ uri: "harness://rules",
4217
+ name: "Harness Rules",
4218
+ description: "Active linter rules and constraints from harness config",
4219
+ mimeType: "application/json"
4220
+ },
4221
+ {
4222
+ uri: "harness://project",
4223
+ name: "Project Context",
4224
+ description: "Project structure and agent instructions from AGENTS.md",
4225
+ mimeType: "text/markdown"
4226
+ },
4227
+ {
4228
+ uri: "harness://learnings",
4229
+ name: "Learnings",
4230
+ description: "Review learnings and anti-pattern log from .harness/",
4231
+ mimeType: "text/markdown"
4232
+ },
4233
+ {
4234
+ uri: "harness://state",
4235
+ name: "Project State",
4236
+ description: "Current harness state including position, progress, decisions, and blockers",
4237
+ mimeType: "application/json"
4238
+ },
4239
+ {
4240
+ uri: "harness://graph",
4241
+ name: "Knowledge Graph",
4242
+ description: "Graph statistics, node/edge counts by type, staleness",
4243
+ mimeType: "application/json"
4244
+ },
4245
+ {
4246
+ uri: "harness://entities",
4247
+ name: "Graph Entities",
4248
+ description: "All entity nodes with types and metadata",
4249
+ mimeType: "application/json"
4250
+ },
4251
+ {
4252
+ uri: "harness://relationships",
4253
+ name: "Graph Relationships",
4254
+ description: "All edges with types, confidence scores, and timestamps",
4255
+ mimeType: "application/json"
4256
+ }
4257
+ ];
4258
+ var RESOURCE_HANDLERS = {
4259
+ "harness://skills": getSkillsResource,
4260
+ "harness://rules": getRulesResource,
4261
+ "harness://project": getProjectResource,
4262
+ "harness://learnings": getLearningsResource,
4263
+ "harness://state": getStateResource,
4264
+ "harness://graph": getGraphResource,
4265
+ "harness://entities": getEntitiesResource,
4266
+ "harness://relationships": getRelationshipsResource
4267
+ };
4268
+ function getToolDefinitions() {
4269
+ return TOOL_DEFINITIONS;
4270
+ }
4271
+ function createHarnessServer(projectRoot) {
4272
+ const resolvedRoot = projectRoot ?? process.cwd();
4273
+ let sessionChecked = false;
4274
+ const server = new Server(
4275
+ { name: "harness-engineering", version: "0.1.0" },
4276
+ { capabilities: { tools: {}, resources: {} } }
4277
+ );
4278
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
4279
+ tools: TOOL_DEFINITIONS
4280
+ }));
4281
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
4282
+ const { name, arguments: args } = request.params;
4283
+ const handler = TOOL_HANDLERS[name];
4284
+ if (!handler) {
4285
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
4286
+ }
4287
+ const result = await handler(args ?? {});
4288
+ if (!sessionChecked) {
4289
+ sessionChecked = true;
4290
+ try {
4291
+ const {
4292
+ getUpdateNotification,
4293
+ isUpdateCheckEnabled,
4294
+ shouldRunCheck,
4295
+ readCheckState,
4296
+ spawnBackgroundCheck
4297
+ } = await import("./dist-JVZ2MKBC.js");
4298
+ const { CLI_VERSION: version } = await import("./version-KFFPOQAX.js");
4299
+ let CLI_VERSION = version;
4300
+ let configInterval;
4301
+ try {
4302
+ const configResult = resolveProjectConfig(resolvedRoot);
4303
+ if (configResult.ok) {
4304
+ const raw = configResult.value.updateCheckInterval;
4305
+ if (typeof raw === "number" && Number.isInteger(raw) && raw >= 0) {
4306
+ configInterval = raw;
4307
+ }
4308
+ }
4309
+ } catch {
4310
+ }
4311
+ const DEFAULT_INTERVAL = 864e5;
4312
+ if (isUpdateCheckEnabled(configInterval)) {
4313
+ const state = readCheckState();
4314
+ if (shouldRunCheck(state, configInterval ?? DEFAULT_INTERVAL)) {
4315
+ spawnBackgroundCheck(CLI_VERSION);
4316
+ }
4317
+ const notification = getUpdateNotification(CLI_VERSION);
4318
+ if (notification) {
4319
+ result.content.push({ type: "text", text: `
4320
+ ---
4321
+ ${notification}` });
4322
+ }
4323
+ }
4324
+ } catch {
4325
+ }
4326
+ }
4327
+ return result;
4328
+ });
4329
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
4330
+ resources: RESOURCE_DEFINITIONS
4331
+ }));
4332
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
4333
+ const uri = request.params.uri;
4334
+ const handler = RESOURCE_HANDLERS[uri];
4335
+ if (!handler) {
4336
+ throw new Error(`Unknown resource: ${uri}`);
4337
+ }
4338
+ const content = await handler(resolvedRoot);
4339
+ const resourceDef = RESOURCE_DEFINITIONS.find((r) => r.uri === uri);
4340
+ const mimeType = resourceDef?.mimeType ?? "text/plain";
4341
+ return {
4342
+ contents: [{ uri, text: content, mimeType }]
4343
+ };
4344
+ });
4345
+ return server;
4346
+ }
4347
+ async function startServer() {
4348
+ const server = createHarnessServer();
4349
+ const transport = new StdioServerTransport();
4350
+ await server.connect(transport);
4351
+ }
4352
+
4353
+ export {
4354
+ generateSlashCommands,
4355
+ handleOrphanDeletion,
4356
+ createGenerateSlashCommandsCommand,
4357
+ handleGetImpact,
4358
+ getToolDefinitions,
4359
+ createHarnessServer,
4360
+ startServer
4361
+ };