@agilemonster/mcp-server 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AgileMonsterProjectConfig, AutonomousStepResponse, Board, BoardAgentConfig, Card, ContextResponse, DiscoveredProject, PulseResponse } from './types.js';
2
- export declare const MCP_SERVER_VERSION = "1.0.9";
2
+ export declare const MCP_SERVER_VERSION = "1.0.11";
3
3
  export declare class AgileMonsterClient {
4
4
  private apiKey;
5
5
  private baseUrl;
package/dist/client.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execSync } from 'node:child_process';
4
- export const MCP_SERVER_VERSION = '1.0.9';
4
+ export const MCP_SERVER_VERSION = '1.0.11';
5
5
  export class AgileMonsterClient {
6
6
  apiKey = '';
7
7
  baseUrl;
package/dist/server.d.ts CHANGED
@@ -36,6 +36,7 @@ export declare function createServer(client?: AgileMonsterClient): Server<{
36
36
  } | undefined;
37
37
  }>;
38
38
  export declare function executeQueuedCardAutonomously(client: AgileMonsterClient, card: Card, projectPath: string): Promise<boolean>;
39
+ export declare function deployApprovedCardIfNeeded(client: AgileMonsterClient, card: Card, projectPath: string): Promise<boolean>;
39
40
  export declare function startBuiltInPullPulse(server: Server | null, client: AgileMonsterClient, intervalMs?: number): NodeJS.Timeout;
40
41
  export declare function runStdioServer(): Promise<void>;
41
42
  export declare function runBackgroundPulseDaemon(intervalMs?: number): Promise<void>;
package/dist/server.js CHANGED
@@ -3,7 +3,8 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
3
  import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
4
  import path from 'node:path';
5
5
  import fs from 'node:fs';
6
- import { spawn } from 'node:child_process';
6
+ import os from 'node:os';
7
+ import { spawn, execSync } from 'node:child_process';
7
8
  import { AgileMonsterClient, MCP_SERVER_VERSION } from './client.js';
8
9
  import { AGENT_GUIDELINES_MARKDOWN } from './guidelines.js';
9
10
  export function createServer(client = new AgileMonsterClient()) {
@@ -862,6 +863,12 @@ function collectProjectSourceFiles(projectPath, keywords) {
862
863
  }
863
864
  catch { }
864
865
  }
866
+ // Bonus score for UI components / screens when task mentions UX or UI
867
+ if (ext === '.tsx' || ext === '.jsx' || ext === '.dart' || ext === '.vue') {
868
+ if (lowerRel.includes('component') || lowerRel.includes('screen') || lowerRel.includes('widget') || lowerRel.includes('modal')) {
869
+ score += 8;
870
+ }
871
+ }
865
872
  results.push({ rel, score });
866
873
  }
867
874
  }
@@ -870,12 +877,22 @@ function collectProjectSourceFiles(projectPath, keywords) {
870
877
  results.sort((a, b) => b.score - a.score || a.rel.localeCompare(b.rel));
871
878
  return results.slice(0, 220).map((r) => r.rel);
872
879
  }
880
+ function getAgileMonsterLocksDir() {
881
+ const dir = path.join(os.homedir(), '.agilemonster', 'locks');
882
+ try {
883
+ if (!fs.existsSync(dir)) {
884
+ fs.mkdirSync(dir, { recursive: true });
885
+ }
886
+ }
887
+ catch { }
888
+ return dir;
889
+ }
873
890
  export async function executeQueuedCardAutonomously(client, card, projectPath) {
874
891
  if (!projectPath || !fs.existsSync(projectPath))
875
892
  return false;
876
893
  if (activeAutonomousCards.has(card.id))
877
894
  return false;
878
- const lockFile = path.join(projectPath, `.agilemonster-task-${card.id.slice(0, 8)}.lock`);
895
+ const lockFile = path.join(getAgileMonsterLocksDir(), `task-${card.id.slice(0, 8)}.lock`);
879
896
  try {
880
897
  if (fs.existsSync(lockFile)) {
881
898
  const stat = fs.statSync(lockFile);
@@ -913,9 +930,9 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
913
930
  action: 'select_files',
914
931
  file_index: fileIndex,
915
932
  });
916
- const selectedFiles = (selectionRes.selected_files || []).slice(0, 4);
933
+ const selectedFiles = (selectionRes.target_files || selectionRes.selected_files || []).slice(0, 4);
917
934
  if (selectedFiles.length === 0) {
918
- selectedFiles.push(...fileIndex.slice(0, 2));
935
+ selectedFiles.push(...fileIndex.slice(0, 3));
919
936
  }
920
937
  const filesPayload = {};
921
938
  for (const rel of selectedFiles) {
@@ -941,30 +958,57 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
941
958
  });
942
959
  const edits = editsRes.edits || [];
943
960
  const modifiedFiles = [];
961
+ const originalContents = new Map();
944
962
  for (const edit of edits) {
945
- if (!edit.path || !edit.search || typeof edit.replace !== 'string')
963
+ const relPath = edit.file || edit.path;
964
+ if (!relPath || !edit.search || typeof edit.replace !== 'string')
946
965
  continue;
947
- const fullPath = path.resolve(projectPath, edit.path);
966
+ const fullPath = path.resolve(projectPath, relPath);
948
967
  if (!fullPath.startsWith(path.resolve(projectPath)))
949
968
  continue;
950
969
  if (!fs.existsSync(fullPath))
951
970
  continue;
952
971
  const currentContent = fs.readFileSync(fullPath, 'utf8');
972
+ if (!originalContents.has(fullPath)) {
973
+ originalContents.set(fullPath, currentContent);
974
+ }
953
975
  if (currentContent.includes(edit.search)) {
954
976
  const updatedContent = currentContent.replace(edit.search, edit.replace);
955
977
  if (updatedContent !== currentContent) {
978
+ // Guard against unbalanced braces introduced by partial block replacement
979
+ const countChar = (s, ch) => s.split(ch).length - 1;
980
+ const searchBraceDelta = countChar(edit.search, '{') - countChar(edit.search, '}');
981
+ const replaceBraceDelta = countChar(edit.replace, '{') - countChar(edit.replace, '}');
982
+ if (searchBraceDelta !== replaceBraceDelta) {
983
+ continue;
984
+ }
956
985
  fs.writeFileSync(fullPath, updatedContent, 'utf8');
957
- if (!modifiedFiles.includes(edit.path)) {
958
- modifiedFiles.push(edit.path);
986
+ if (!modifiedFiles.includes(relPath)) {
987
+ modifiedFiles.push(relPath);
959
988
  }
960
989
  }
961
990
  }
962
991
  }
963
992
  if (modifiedFiles.length > 0) {
964
- const summary = editsRes.summary ||
993
+ let diffSnippet = '';
994
+ try {
995
+ const quotedFiles = modifiedFiles.map((f) => `"${f.replace(/"/g, '\\"')}"`).join(' ');
996
+ const rawDiff = execSync(`git diff -- ${quotedFiles}`, {
997
+ cwd: projectPath,
998
+ encoding: 'utf8',
999
+ stdio: ['ignore', 'pipe', 'ignore'],
1000
+ }).trim();
1001
+ if (rawDiff) {
1002
+ const clipped = rawDiff.length > 2500 ? rawDiff.slice(0, 2500) + '\n... (diff truncated)' : rawDiff;
1003
+ diffSnippet = `\n\n### Code Changes (\`git diff\`)\n\`\`\`diff\n${clipped}\n\`\`\``;
1004
+ }
1005
+ }
1006
+ catch { }
1007
+ const baseSummary = editsRes.summary ||
965
1008
  `Implemented "${card.title}" autonomously across ${modifiedFiles.map((f) => `\`${f}\``).join(', ')}.`;
1009
+ const summary = `${baseSummary}${diffSnippet}`;
966
1010
  const verificationSteps = editsRes.verification_steps ||
967
- `1. Open ${projectPath} and inspect changes in ${modifiedFiles.join(', ')}\n2. Run the app and verify "${card.title}".`;
1011
+ `1. Review the code diff above in ${modifiedFiles.join(', ')}\n2. Click **Approve & Move to Production** (or drag to Production) to automatically commit, push, and deploy!`;
968
1012
  await client.submitReview(card.id, provider, summary, verificationSteps);
969
1013
  sendDesktopNotification(`Agile Monster -> Ready for Review`, `Completed "${card.title}" (${modifiedFiles.length} file(s) updated) and moved to Testing / Review!`);
970
1014
  return true;
@@ -987,6 +1031,87 @@ export async function executeQueuedCardAutonomously(client, card, projectPath) {
987
1031
  activeAutonomousCards.delete(card.id);
988
1032
  }
989
1033
  }
1034
+ export async function deployApprovedCardIfNeeded(client, card, projectPath) {
1035
+ if (!projectPath || !fs.existsSync(projectPath))
1036
+ return false;
1037
+ const shortId = card.id.slice(0, 8);
1038
+ const deployLock = path.join(getAgileMonsterLocksDir(), `deployed-${shortId}.json`);
1039
+ if (fs.existsSync(deployLock)) {
1040
+ return false;
1041
+ }
1042
+ const provider = card.agent_provider || 'cursor';
1043
+ const cleanTitle = (card.title || 'update').replace(/["`\r\n]+/g, ' ').trim();
1044
+ try {
1045
+ fs.writeFileSync(deployLock, JSON.stringify({ card_id: card.id, title: card.title, started_at: new Date().toISOString() }), 'utf8');
1046
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
1047
+ cwd: projectPath,
1048
+ encoding: 'utf8',
1049
+ stdio: ['ignore', 'pipe', 'ignore'],
1050
+ }).trim();
1051
+ if (!branch || branch === 'HEAD')
1052
+ return false;
1053
+ const statusOut = execSync('git status --porcelain -uno', {
1054
+ cwd: projectPath,
1055
+ encoding: 'utf8',
1056
+ stdio: ['ignore', 'pipe', 'ignore'],
1057
+ }).trim();
1058
+ const rawText = `${card.title || ''} ${card.description || ''}`;
1059
+ const isContainerRestartCard = /\b(restart|redeploy|rebuild)\b.*\b(container|docker|server|prod)/i.test(rawText);
1060
+ if (statusOut.length > 0) {
1061
+ execSync('git add -u', { cwd: projectPath, stdio: 'ignore' });
1062
+ execSync(`git commit -m "feat: ${cleanTitle} [AM-${shortId}]"`, {
1063
+ cwd: projectPath,
1064
+ stdio: 'ignore',
1065
+ });
1066
+ execSync(`git push origin ${branch}`, {
1067
+ cwd: projectPath,
1068
+ stdio: 'ignore',
1069
+ });
1070
+ const commitHash = execSync('git rev-parse --short HEAD', {
1071
+ cwd: projectPath,
1072
+ encoding: 'utf8',
1073
+ stdio: ['ignore', 'pipe', 'ignore'],
1074
+ }).trim();
1075
+ await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Card approved into **${card.list_name || 'Production'}** — automatically committed and pushed \`${commitHash}\` to \`origin/${branch}\` to trigger the production CI/CD & container deployment pipeline!`);
1076
+ sendDesktopNotification(`Agile Monster -> Production Deploy`, `Pushed "${card.title}" (${commitHash}) to origin/${branch}!`);
1077
+ return true;
1078
+ }
1079
+ else if (isContainerRestartCard) {
1080
+ execSync(`git commit --allow-empty -m "chore: trigger container redeploy [AM-${shortId}]"`, {
1081
+ cwd: projectPath,
1082
+ stdio: 'ignore',
1083
+ });
1084
+ execSync(`git push origin ${branch}`, {
1085
+ cwd: projectPath,
1086
+ stdio: 'ignore',
1087
+ });
1088
+ const commitHash = execSync('git rev-parse --short HEAD', {
1089
+ cwd: projectPath,
1090
+ encoding: 'utf8',
1091
+ stdio: ['ignore', 'pipe', 'ignore'],
1092
+ }).trim();
1093
+ await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Triggered production container rebuild & restart via \`${commitHash}\` on \`origin/${branch}\`.`);
1094
+ sendDesktopNotification(`Agile Monster -> Container Redeploy`, `Triggered container restart (${commitHash}) on origin/${branch}!`);
1095
+ return true;
1096
+ }
1097
+ else {
1098
+ const headHash = execSync('git rev-parse --short HEAD', {
1099
+ cwd: projectPath,
1100
+ encoding: 'utf8',
1101
+ stdio: ['ignore', 'pipe', 'ignore'],
1102
+ }).trim();
1103
+ await client.addProgress(card.id, provider, `🚀 **[Auto-Deployed to Production]** Verified latest commit \`${headHash}\` is pushed to \`origin/${branch}\` and live in the production CI/CD pipeline.`);
1104
+ return true;
1105
+ }
1106
+ }
1107
+ catch (err) {
1108
+ try {
1109
+ await client.addProgress(card.id, provider, `⚠️ **[Auto-Deploy Note]** Attempted to auto-push approved changes in \`${projectPath}\`: ${err?.message || String(err)}`);
1110
+ }
1111
+ catch { }
1112
+ return false;
1113
+ }
1114
+ }
990
1115
  export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
991
1116
  const notifiedCardIds = new Set();
992
1117
  let updateTriggered = false;
@@ -1091,6 +1216,15 @@ export function startBuiltInPullPulse(server, client, intervalMs = 20000) {
1091
1216
  await executeQueuedCardAutonomously(client, qCard, targetPath);
1092
1217
  }
1093
1218
  }
1219
+ // Zero-touch auto-commit & push (deploy to production) for cards approved into Production / Done!
1220
+ const boundDone = res.all_bound_done_cards || [];
1221
+ for (const dCard of boundDone) {
1222
+ const targetPath = (dCard.bound_project_path || '').trim() ||
1223
+ (dCard.board_id === config.board_id ? config.project_path || process.cwd() : '');
1224
+ if (targetPath && fs.existsSync(targetPath)) {
1225
+ await deployApprovedCardIfNeeded(client, dCard, targetPath);
1226
+ }
1227
+ }
1094
1228
  if ((res.version?.force_update_requested || res.version?.update_available) &&
1095
1229
  !updateTriggered) {
1096
1230
  updateTriggered = true;
package/dist/types.d.ts CHANGED
@@ -88,6 +88,7 @@ export interface PulseResponse {
88
88
  queued_cards: Card[];
89
89
  in_progress_cards: Card[];
90
90
  all_bound_queued_cards?: Card[];
91
+ all_bound_done_cards?: Card[];
91
92
  version: PulseVersionInfo;
92
93
  saved_bindings?: Array<{
93
94
  board_id: string;
@@ -99,14 +100,17 @@ export interface PulseResponse {
99
100
  }>;
100
101
  }
101
102
  export interface AutonomousEdit {
102
- path: string;
103
+ path?: string;
104
+ file?: string;
103
105
  search: string;
104
106
  replace: string;
107
+ explanation?: string;
105
108
  }
106
109
  export interface AutonomousStepResponse {
107
110
  card_id: string;
108
- action: 'select_files' | 'generate_edits';
111
+ action?: 'select_files' | 'generate_edits';
109
112
  selected_files?: string[];
113
+ target_files?: string[];
110
114
  edits?: AutonomousEdit[];
111
115
  summary?: string;
112
116
  verification_steps?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agilemonster/mcp-server",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "type": "module",
5
5
  "description": "Official Model Context Protocol (MCP) server & Orchestrator for Agile Monster (Cursor, Claude Code, Antigravity, Codex, Hermes, Windsurf)",
6
6
  "main": "dist/server.js",