@ramarivera/coding-buddy 0.9.4 → 0.9.5
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/cli/install.ts +32 -6
- package/cli/runtime-app.ts +157 -5
- package/package.json +4 -1
- package/runtime.bun.lock +1389 -0
- package/server/index.ts +22 -21
- package/server/react-args.ts +64 -0
- package/skills/buddy/SKILL.md +19 -1
package/server/index.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
+
import { REACT_REASONS, normalizeReactArgs } from "./react-args.ts";
|
|
12
13
|
import { existsSync } from "fs";
|
|
13
14
|
import { join, resolve, dirname } from "path";
|
|
14
15
|
// The version advertised in the MCP handshake. Imported rather than written
|
|
@@ -304,33 +305,33 @@ server.tool(
|
|
|
304
305
|
{
|
|
305
306
|
comment: z
|
|
306
307
|
.string()
|
|
307
|
-
.
|
|
308
|
-
.max(150)
|
|
308
|
+
.optional()
|
|
309
309
|
.describe(
|
|
310
|
-
"The buddy's comment, written in-character (1 short sentence, max 150 chars). Use *asterisks* for actions.",
|
|
310
|
+
"The buddy's comment, written in-character (1 short sentence, max 150 chars; longer text is shortened). Use *asterisks* for actions. Always send it: a call with no comment returns an error.",
|
|
311
311
|
),
|
|
312
|
+
reaction: z
|
|
313
|
+
.string()
|
|
314
|
+
.optional()
|
|
315
|
+
.describe("Deprecated alias for `comment`. Send `comment` instead."),
|
|
312
316
|
reason: z
|
|
313
|
-
.
|
|
314
|
-
"error", "test-fail", "large-diff", "turn",
|
|
315
|
-
"commit", "push", "merge-conflict", "branch", "rebase", "stash", "tag",
|
|
316
|
-
"late-night", "early-morning", "long-session", "marathon", "friday", "weekend", "monday",
|
|
317
|
-
"regex-file", "css-file", "sql-file", "docker-file", "ci-file", "lock-file",
|
|
318
|
-
"env-file", "test-file", "doc-file", "config-file", "binary-file", "gitignore",
|
|
319
|
-
"makefile", "readme", "package-file", "proto-file",
|
|
320
|
-
"lint-fail", "type-error", "build-fail", "security-warning", "deprecation",
|
|
321
|
-
"frustrated", "happy", "stuck", "sarcastic",
|
|
322
|
-
"many-edits", "delete-file", "large-file", "create-file",
|
|
323
|
-
"all-green", "deploy", "release", "coverage",
|
|
324
|
-
"debug-loop", "write-spree", "search-heavy",
|
|
325
|
-
"recovery-from-error", "recovery-from-test-fail",
|
|
326
|
-
"recovery-from-build-fail", "recovery-from-merge-conflict",
|
|
327
|
-
])
|
|
317
|
+
.string()
|
|
328
318
|
.optional()
|
|
329
|
-
.describe(
|
|
319
|
+
.describe(
|
|
320
|
+
`What triggered the reaction. One of: ${REACT_REASONS.join(", ")}. Defaults to "turn"; unknown values are treated as "turn".`,
|
|
321
|
+
),
|
|
330
322
|
},
|
|
331
|
-
async (
|
|
323
|
+
async (raw) => {
|
|
324
|
+
const { comment, reason } = normalizeReactArgs(raw);
|
|
325
|
+
if (comment === null) {
|
|
326
|
+
return {
|
|
327
|
+
isError: true,
|
|
328
|
+
content: [
|
|
329
|
+
{ type: "text", text: 'buddy_react needs a non-empty `comment` string, for example {"comment": "*nods* clean diff", "reason": "turn"}.' },
|
|
330
|
+
],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
332
333
|
const companion = ensureCompanion();
|
|
333
|
-
saveReaction(comment, reason
|
|
334
|
+
saveReaction(comment, reason, "tool");
|
|
334
335
|
incrementEvent("reactions_given", 1, activeSlot());
|
|
335
336
|
|
|
336
337
|
const newAch = checkAndAward(activeSlot());
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument handling for the buddy_react tool.
|
|
3
|
+
*
|
|
4
|
+
* Agents often call buddy_react without having read its schema: the tool is
|
|
5
|
+
* deferred in Claude Code, so the model guesses argument names. A strict
|
|
6
|
+
* schema turned those guesses into MCP -32602 errors and agents then reported
|
|
7
|
+
* the buddy as unavailable. The tool accepts the common guesses instead and
|
|
8
|
+
* normalizes them here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const REACT_REASONS = [
|
|
12
|
+
"error", "test-fail", "large-diff", "turn",
|
|
13
|
+
"commit", "push", "merge-conflict", "branch", "rebase", "stash", "tag",
|
|
14
|
+
"late-night", "early-morning", "long-session", "marathon", "friday", "weekend", "monday",
|
|
15
|
+
"regex-file", "css-file", "sql-file", "docker-file", "ci-file", "lock-file",
|
|
16
|
+
"env-file", "test-file", "doc-file", "config-file", "binary-file", "gitignore",
|
|
17
|
+
"makefile", "readme", "package-file", "proto-file",
|
|
18
|
+
"lint-fail", "type-error", "build-fail", "security-warning", "deprecation",
|
|
19
|
+
"frustrated", "happy", "stuck", "sarcastic",
|
|
20
|
+
"many-edits", "delete-file", "large-file", "create-file",
|
|
21
|
+
"all-green", "deploy", "release", "coverage",
|
|
22
|
+
"debug-loop", "write-spree", "search-heavy",
|
|
23
|
+
"recovery-from-error", "recovery-from-test-fail",
|
|
24
|
+
"recovery-from-build-fail", "recovery-from-merge-conflict",
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export type ReactReason = (typeof REACT_REASONS)[number];
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_REACT_REASON: ReactReason = "turn";
|
|
30
|
+
export const MAX_REACT_COMMENT_LENGTH = 150;
|
|
31
|
+
|
|
32
|
+
export interface RawReactArgs {
|
|
33
|
+
comment?: string;
|
|
34
|
+
/** Alias agents guess when they have not loaded the schema. */
|
|
35
|
+
reaction?: string;
|
|
36
|
+
reason?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ReactArgs {
|
|
40
|
+
/** Null when the caller sent no usable comment under any accepted name. */
|
|
41
|
+
comment: string | null;
|
|
42
|
+
reason: ReactReason;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isReactReason(value: string): value is ReactReason {
|
|
46
|
+
return (REACT_REASONS as readonly string[]).includes(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function clampComment(text: string): string {
|
|
50
|
+
const chars = Array.from(text);
|
|
51
|
+
if (chars.length <= MAX_REACT_COMMENT_LENGTH) return text;
|
|
52
|
+
return `${chars.slice(0, MAX_REACT_COMMENT_LENGTH - 1).join("")}…`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function normalizeReactArgs(raw: RawReactArgs): ReactArgs {
|
|
56
|
+
const text = [raw.comment, raw.reaction]
|
|
57
|
+
.map((value) => (typeof value === "string" ? value.trim() : ""))
|
|
58
|
+
.find((value) => value.length > 0);
|
|
59
|
+
const reason = typeof raw.reason === "string" ? raw.reason.trim() : "";
|
|
60
|
+
return {
|
|
61
|
+
comment: text ? clampComment(text) : null,
|
|
62
|
+
reason: isReactReason(reason) ? reason : DEFAULT_REACT_REASON,
|
|
63
|
+
};
|
|
64
|
+
}
|
package/skills/buddy/SKILL.md
CHANGED
|
@@ -74,7 +74,11 @@ Based on `$ARGUMENTS`:
|
|
|
74
74
|
|
|
75
75
|
## CRITICAL OUTPUT RULES
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
Different buddy tools return different shapes of output. The rules below keep the companion's visual identity intact while making sure plain-text replies actually reach the user.
|
|
78
|
+
|
|
79
|
+
### Visual-card outputs — strict verbatim
|
|
80
|
+
|
|
81
|
+
These tools return pre-formatted ASCII art with ANSI colors, box-drawing characters, stat bars, and species art: `buddy_show`, `buddy_stats`, `buddy_pet`, `buddy_achievements`, `buddy_summon`, `buddy_pick`. This is the companion's visual identity.
|
|
78
82
|
|
|
79
83
|
**You MUST output the tool result text EXACTLY as returned — character for character, line for line.** Do NOT:
|
|
80
84
|
|
|
@@ -86,6 +90,20 @@ The MCP tools return pre-formatted ASCII art with ANSI colors, box-drawing chara
|
|
|
86
90
|
|
|
87
91
|
**Just output the raw text content from the tool result. Nothing else.** The ASCII art IS the response.
|
|
88
92
|
|
|
93
|
+
### Plain-text outputs — verbatim and visible
|
|
94
|
+
|
|
95
|
+
These tools return plain text, not ASCII art: `buddy_help`, `buddy_mute`, `buddy_unmute`, `buddy_frequency`, `buddy_style`, `buddy_statusline`, `buddy_list`, `buddy_rename`, `buddy_save`, `buddy_dismiss`, `buddy_set_personality`.
|
|
96
|
+
|
|
97
|
+
**The visible reply MUST include the tool result text verbatim — every time, on every call, even on repeated calls in the same session.** Treat each invocation as a fresh request from the user, not a "they have seen it before" signal. Tool results are not rendered in the user's transcript; only what you write in your own reply reaches the screen.
|
|
98
|
+
|
|
99
|
+
You MAY add a one-sentence confirmation before or after if it clarifies what changed, but do NOT paraphrase the text itself.
|
|
100
|
+
|
|
101
|
+
**Hard floor for the plain-text commands above:** a reply that contains no visible text of its own is broken. A `buddy_react` call is not visible output — it reaches the user only through the statusline bubble — so a turn whose only content is a tool call renders blank. If you have nothing else to say, the tool result text alone is the correct reply.
|
|
102
|
+
|
|
103
|
+
**Self-check for the plain-text commands above:** does your visible reply contain that command's result text? If not, paste it in. Never paste the result of `buddy_react`. The end-of-turn `buddy_react` call is _additional_, never _instead of_.
|
|
104
|
+
|
|
105
|
+
### Reactions — statusline only
|
|
106
|
+
|
|
89
107
|
If the user mentions the buddy's name in normal conversation, call `buddy_react` with reason "turn". Do NOT echo or quote the tool result — the reaction reaches the user only via the statusline speech bubble.
|
|
90
108
|
|
|
91
109
|
## Uninstall Orchestration
|