@ateam-ai/mcp 0.3.27 → 0.3.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.3.27",
3
+ "version": "0.3.29",
4
4
  "mcpName": "io.github.ariekogan/ateam-mcp",
5
5
  "description": "A-Team MCP Server — build, validate, and deploy multi-agent solutions from any AI environment",
6
6
  "type": "module",
package/src/agentDoc.js CHANGED
@@ -156,20 +156,28 @@ ${AGENT_DOC_SENTINEL}
156
156
  * Merge the newly-rendered header with an existing CLAUDE.md, preserving
157
157
  * anything below the sentinel (solution-specific notes).
158
158
  *
159
+ * Output is deterministic so consecutive merges on the same inputs produce
160
+ * byte-identical output (required for the idempotent-write path in
161
+ * ateam_write_agent_doc).
162
+ *
159
163
  * @param {string} freshHeader - Output of renderAgentDocHeader
160
164
  * @param {string|null} existing - Current CLAUDE.md contents (or null if new file)
161
165
  * @returns {string} merged markdown
162
166
  */
163
167
  export function mergeAgentDoc(freshHeader, existing) {
164
- if (!existing) return freshHeader;
165
- const idx = existing.indexOf(AGENT_DOC_SENTINEL);
166
- if (idx === -1) {
167
- // Existing file has no sentinel — treat the whole file as manual notes,
168
- // prepend the auto-generated header, separate by sentinel.
169
- return freshHeader + "\n" + existing.trimStart();
170
- }
171
- const tail = existing.slice(idx + AGENT_DOC_SENTINEL.length);
172
- return freshHeader + tail;
168
+ // Normalize freshHeader: strip trailing whitespace, end with exactly "\n".
169
+ // This prevents newline accumulation when the output is read back and merged again.
170
+ const headerNormalized = freshHeader.replace(/\s+$/, "") + "\n";
171
+
172
+ const extractNotes = (src) => {
173
+ if (!src) return "";
174
+ const idx = src.indexOf(AGENT_DOC_SENTINEL);
175
+ const afterSentinel = idx === -1 ? src : src.slice(idx + AGENT_DOC_SENTINEL.length);
176
+ return afterSentinel.trim();
177
+ };
178
+
179
+ const notes = extractNotes(existing);
180
+ return notes ? `${headerNormalized}\n${notes}\n` : headerNormalized;
173
181
  }
174
182
 
175
183
  // ──────────────────────────────────────────────────────────────────────────
package/src/tools.js CHANGED
@@ -299,7 +299,8 @@ export const tools = [
299
299
  "5. Array update: { \"tools_update\": [{ name: \"existing_tool\", description: \"updated\" }] }\n" +
300
300
  "6. Replace whole section: { \"role\": { persona: \"...\", goals: [...] } }\n\n" +
301
301
  "EXAMPLES:\n" +
302
- "- Change persona: updates: { \"role.persona\": \"You are a friendly assistant\" }\n" +
302
+ "- Change persona (full replace): updates: { \"role.persona\": \"You are a friendly assistant\" }\n" +
303
+ "- Append to persona (don't replace): updates: { \"persona_append\": \"\\n\\nALWAYS respond in 2 sentences.\" }\n" +
303
304
  "- Add a guardrail: updates: { \"policy.guardrails.never_push\": [\"Never share passwords\"] }\n" +
304
305
  "- Update problem: updates: { \"problem.statement\": \"...\", \"problem.goals\": [\"goal1\"] }\n" +
305
306
  "- Add a tool: updates: { \"tools_push\": [{ name: \"conn.tool\", description: \"...\", inputs: [...], output: {...} }] }\n" +
@@ -1813,7 +1814,17 @@ const handlers = {
1813
1814
  let patched = { ...current };
1814
1815
  try {
1815
1816
  for (const [key, value] of Object.entries(updates || {})) {
1816
- if (key.endsWith("_push") && Array.isArray(value)) {
1817
+ if (key === "persona_append" && typeof value === "string") {
1818
+ // persona_append: shorthand for appending text to role.persona without
1819
+ // rewriting the whole string. Historically agents set this expecting
1820
+ // it to work; the planner only reads role.persona, so this shorthand
1821
+ // now merges into the correct field. A trailing separator is inserted
1822
+ // if the existing persona doesn't already end with whitespace.
1823
+ if (!patched.role || typeof patched.role !== "object") patched.role = {};
1824
+ const existing = typeof patched.role.persona === "string" ? patched.role.persona : "";
1825
+ const sep = (!existing || /\s$/.test(existing)) ? "" : "\n\n";
1826
+ patched.role.persona = existing + sep + value;
1827
+ } else if (key.endsWith("_push") && Array.isArray(value)) {
1817
1828
  // Array push: tools_push, intents_push, etc.
1818
1829
  const field = key.replace(/_push$/, "");
1819
1830
  patched[field] = [...(patched[field] || []), ...value];