@cyanheads/mcp-ts-core 0.12.7 → 0.12.9
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/AGENTS.md +10 -5
- package/CLAUDE.md +10 -5
- package/README.md +12 -5
- package/changelog/0.12.x/0.12.8.md +55 -0
- package/changelog/0.12.x/0.12.9.md +36 -0
- package/dist/config/index.d.ts +3 -34
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +4 -26
- package/dist/config/index.js.map +1 -1
- package/dist/core/app.d.ts +0 -8
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +0 -7
- package/dist/core/app.js.map +1 -1
- package/dist/core/serverManifest.d.ts +0 -7
- package/dist/core/serverManifest.d.ts.map +1 -1
- package/dist/core/serverManifest.js +1 -13
- package/dist/core/serverManifest.js.map +1 -1
- package/dist/linter/rules/enrichment-rules.js +2 -2
- package/dist/linter/rules/enrichment-rules.js.map +1 -1
- package/dist/linter/rules/format-parity-rules.d.ts.map +1 -1
- package/dist/linter/rules/format-parity-rules.js +14 -36
- package/dist/linter/rules/format-parity-rules.js.map +1 -1
- package/dist/linter/rules/prompt-rules.d.ts +1 -1
- package/dist/linter/rules/prompt-rules.d.ts.map +1 -1
- package/dist/linter/rules/prompt-rules.js +2 -19
- package/dist/linter/rules/prompt-rules.js.map +1 -1
- package/dist/linter/rules/resource-rules.d.ts +1 -1
- package/dist/linter/rules/resource-rules.d.ts.map +1 -1
- package/dist/linter/rules/resource-rules.js +9 -39
- package/dist/linter/rules/resource-rules.js.map +1 -1
- package/dist/linter/rules/schema-rules.d.ts +22 -2
- package/dist/linter/rules/schema-rules.d.ts.map +1 -1
- package/dist/linter/rules/schema-rules.js +28 -5
- package/dist/linter/rules/schema-rules.js.map +1 -1
- package/dist/linter/rules/tool-rules.d.ts +1 -1
- package/dist/linter/rules/tool-rules.d.ts.map +1 -1
- package/dist/linter/rules/tool-rules.js +13 -41
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/dist/linter/validate.d.ts.map +1 -1
- package/dist/linter/validate.js +22 -42
- package/dist/linter/validate.js.map +1 -1
- package/dist/mcp-server/apps/appBuilders.d.ts.map +1 -1
- package/dist/mcp-server/apps/appBuilders.js +2 -16
- package/dist/mcp-server/apps/appBuilders.js.map +1 -1
- package/dist/mcp-server/handlerContext.d.ts +66 -0
- package/dist/mcp-server/handlerContext.d.ts.map +1 -0
- package/dist/mcp-server/handlerContext.js +71 -0
- package/dist/mcp-server/handlerContext.js.map +1 -0
- package/dist/mcp-server/inputRequired.d.ts +7 -1
- package/dist/mcp-server/inputRequired.d.ts.map +1 -1
- package/dist/mcp-server/inputRequired.js +10 -3
- package/dist/mcp-server/inputRequired.js.map +1 -1
- package/dist/mcp-server/resources/resource-registration.d.ts +2 -2
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +14 -43
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +11 -50
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/tools/tool-registration.d.ts +5 -9
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +9 -11
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/schemaShape.d.ts +21 -0
- package/dist/mcp-server/tools/utils/schemaShape.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/schemaShape.js +8 -6
- package/dist/mcp-server/tools/utils/schemaShape.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +15 -43
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +31 -72
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.js +2 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -1
- package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpTransport.js +70 -2
- package/dist/mcp-server/transports/http/httpTransport.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/handler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/handler.js +2 -1
- package/dist/mcp-server/transports/http/landing-page/handler.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/connect.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/connect.js +9 -2
- package/dist/mcp-server/transports/http/landing-page/sections/connect.js.map +1 -1
- package/dist/mcp-server/transports/http/protectedResourceMetadata.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/protectedResourceMetadata.js +2 -1
- package/dist/mcp-server/transports/http/protectedResourceMetadata.js.map +1 -1
- package/dist/mcp-server/transports/http/publicOrigin.d.ts +11 -0
- package/dist/mcp-server/transports/http/publicOrigin.d.ts.map +1 -0
- package/dist/mcp-server/transports/http/publicOrigin.js +13 -0
- package/dist/mcp-server/transports/http/publicOrigin.js.map +1 -0
- package/dist/mcp-server/transports/http/serverCard.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/serverCard.js +2 -1
- package/dist/mcp-server/transports/http/serverCard.js.map +1 -1
- package/dist/mcp-server/transports/http/sessionIdUtils.d.ts +4 -0
- package/dist/mcp-server/transports/http/sessionIdUtils.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/sessionIdUtils.js +3 -13
- package/dist/mcp-server/transports/http/sessionIdUtils.js.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.d.ts +10 -2
- package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
- package/dist/mcp-server/transports/manager.d.ts +0 -3
- package/dist/mcp-server/transports/manager.d.ts.map +1 -1
- package/dist/mcp-server/transports/manager.js +0 -7
- package/dist/mcp-server/transports/manager.js.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts +14 -0
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js +3 -2
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +16 -0
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +78 -103
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/graph/core/GraphService.d.ts +3 -3
- package/dist/services/graph/core/GraphService.js +3 -3
- package/dist/services/graph/types.d.ts +2 -79
- package/dist/services/graph/types.d.ts.map +1 -1
- package/dist/services/graph/types.js +2 -2
- package/dist/services/index.d.ts +1 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +0 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/mirror/sqlite/handle.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/handle.js +27 -39
- package/dist/services/mirror/sqlite/handle.js.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js +8 -9
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js.map +1 -1
- package/dist/services/mirror/types.d.ts +5 -1
- package/dist/services/mirror/types.d.ts.map +1 -1
- package/dist/services/speech/core/ISpeechProvider.d.ts +0 -24
- package/dist/services/speech/core/ISpeechProvider.d.ts.map +1 -1
- package/dist/services/speech/core/ISpeechProvider.js +1 -28
- package/dist/services/speech/core/ISpeechProvider.js.map +1 -1
- package/dist/services/speech/core/SpeechService.d.ts.map +1 -1
- package/dist/services/speech/core/SpeechService.js +5 -8
- package/dist/services/speech/core/SpeechService.js.map +1 -1
- package/dist/services/speech/providers/elevenlabs.provider.d.ts.map +1 -1
- package/dist/services/speech/providers/elevenlabs.provider.js +1 -0
- package/dist/services/speech/providers/elevenlabs.provider.js.map +1 -1
- package/dist/services/speech/types.d.ts +2 -19
- package/dist/services/speech/types.d.ts.map +1 -1
- package/dist/storage/core/providerHelpers.d.ts +52 -0
- package/dist/storage/core/providerHelpers.d.ts.map +1 -0
- package/dist/storage/core/providerHelpers.js +96 -0
- package/dist/storage/core/providerHelpers.js.map +1 -0
- package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/d1Provider.js +1 -4
- package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.js +4 -31
- package/dist/storage/providers/cloudflare/kvProvider.js.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.d.ts +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.js +8 -48
- package/dist/storage/providers/cloudflare/r2Provider.js.map +1 -1
- package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts +1 -1
- package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts.map +1 -1
- package/dist/storage/providers/fileSystem/fileSystemProvider.js +17 -86
- package/dist/storage/providers/fileSystem/fileSystemProvider.js.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.js +5 -38
- package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -1
- package/dist/storage/providers/supabase/supabaseProvider.d.ts.map +1 -1
- package/dist/storage/providers/supabase/supabaseProvider.js +1 -4
- package/dist/storage/providers/supabase/supabaseProvider.js.map +1 -1
- package/dist/testing/fuzz.d.ts.map +1 -1
- package/dist/testing/fuzz.js +17 -31
- package/dist/testing/fuzz.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +4 -26
- package/dist/testing/index.js.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts +0 -4
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/logger.d.ts.map +1 -1
- package/dist/utils/internal/logger.js +2 -16
- package/dist/utils/internal/logger.js.map +1 -1
- package/dist/utils/internal/performance.d.ts +9 -32
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +175 -297
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +1 -1
- package/dist/utils/network/retry.js +1 -1
- package/dist/utils/security/idGenerator.d.ts +3 -1
- package/dist/utils/security/idGenerator.d.ts.map +1 -1
- package/dist/utils/security/idGenerator.js +35 -43
- package/dist/utils/security/idGenerator.js.map +1 -1
- package/dist/utils/security/sanitization.d.ts +0 -7
- package/dist/utils/security/sanitization.d.ts.map +1 -1
- package/dist/utils/security/sanitization.js +4 -31
- package/dist/utils/security/sanitization.js.map +1 -1
- package/dist/utils/security/sensitiveFields.d.ts +14 -0
- package/dist/utils/security/sensitiveFields.d.ts.map +1 -0
- package/dist/utils/security/sensitiveFields.js +31 -0
- package/dist/utils/security/sensitiveFields.js.map +1 -0
- package/dist/utils/telemetry/trace.d.ts +8 -10
- package/dist/utils/telemetry/trace.d.ts.map +1 -1
- package/dist/utils/telemetry/trace.js +19 -18
- package/dist/utils/telemetry/trace.js.map +1 -1
- package/dist/utils/types/guards.d.ts +0 -102
- package/dist/utils/types/guards.d.ts.map +1 -1
- package/dist/utils/types/guards.js +0 -114
- package/dist/utils/types/guards.js.map +1 -1
- package/package.json +11 -11
- package/scripts/devcheck.ts +21 -14
- package/skills/add-provider/SKILL.md +18 -4
- package/skills/add-tool/SKILL.md +4 -4
- package/skills/api-config/SKILL.md +4 -18
- package/skills/api-errors/SKILL.md +2 -1
- package/skills/api-mirror/SKILL.md +3 -1
- package/skills/api-services/SKILL.md +1 -1
- package/skills/api-services/references/speech.md +1 -2
- package/skills/api-telemetry/SKILL.md +2 -2
- package/skills/api-utils/SKILL.md +2 -2
- package/skills/code-simplifier/SKILL.md +47 -20
- package/skills/design-mcp-server/SKILL.md +59 -101
- package/skills/field-test/SKILL.md +101 -17
- package/skills/git-wrapup/SKILL.md +68 -29
- package/skills/orchestrations/SKILL.md +17 -6
- package/skills/orchestrations/workflows/field-test-fix.md +6 -4
- package/skills/orchestrations/workflows/fix-wrapup-release.md +6 -4
- package/skills/orchestrations/workflows/greenfield-build.md +2 -2
- package/skills/orchestrations/workflows/maintenance-release.md +4 -2
- package/skills/polish-docs-meta/SKILL.md +1 -1
- package/skills/polish-docs-meta/references/package-meta.md +1 -1
- package/skills/polish-docs-meta/references/readme.md +2 -2
- package/skills/release-and-publish/SKILL.md +104 -23
- package/skills/release-pr-review/SKILL.md +147 -0
- package/skills/security-pass/SKILL.md +2 -2
- package/templates/AGENTS.md +5 -3
- package/templates/CLAUDE.md +5 -3
- package/templates/package.json +1 -1
- package/dist/mcp-server/transports/ITransport.d.ts +0 -15
- package/dist/mcp-server/transports/ITransport.d.ts.map +0 -1
- package/dist/mcp-server/transports/ITransport.js +0 -2
- package/dist/mcp-server/transports/ITransport.js.map +0 -1
- package/dist/services/llm/types.d.ts +0 -16
- package/dist/services/llm/types.d.ts.map +0 -1
- package/dist/services/llm/types.js +0 -9
- package/dist/services/llm/types.js.map +0 -1
- package/dist/utils/internal/health.d.ts +0 -60
- package/dist/utils/internal/health.d.ts.map +0 -1
- package/dist/utils/internal/health.js +0 -46
- package/dist/utils/internal/health.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: field-test
|
|
3
3
|
description: >
|
|
4
|
-
Exercise tools, resources, and prompts against a live HTTP server via MCP JSON-RPC over curl. Starts the server, surfaces the catalog, runs real and adversarial inputs, and produces a tight report with concrete findings and numbered follow-up options. Use after adding or modifying definitions, or when the user asks to test, try out, or verify their MCP surface.
|
|
4
|
+
Exercise tools, resources, and prompts against a live HTTP server via MCP JSON-RPC over curl. Starts the server, surfaces the catalog, runs real and adversarial inputs, measures every call (bytes, token estimate, wall-clock) and weighs the catalog, and produces a tight report with concrete findings and numbered follow-up options. Use after adding or modifying definitions, or when the user asks to test, try out, or verify their MCP surface.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "2.
|
|
7
|
+
version: "2.14"
|
|
8
8
|
audience: external
|
|
9
9
|
type: debug
|
|
10
10
|
---
|
|
@@ -19,7 +19,7 @@ Unit tests (`add-test` skill) verify handler logic with mocked context. Field te
|
|
|
19
19
|
|
|
20
20
|
This skill drives an HTTP server because curl + JSON-RPC is the most reliable harness for shell-based agents. The same handlers run on both transports — only the framing differs — so HTTP exercises the full functional surface. Both HTTP session modes are covered: a durable `Mcp-Session-Id` session, and the sessionless initialization a `MCP_SESSION_MODE=stateless` server performs.
|
|
21
21
|
|
|
22
|
-
**Stdio coverage is a boot check only — run this before Step 1.** Run `bun run rebuild && bun run start:stdio`, confirm the startup logs look clean (banner, expected tool/resource counts, no errors/warnings, no missing-config gripes), then
|
|
22
|
+
**Stdio coverage is a boot check only — run this before Step 1.** Run `bun run rebuild && bun run start:stdio < /dev/null`, and confirm the startup logs look clean (banner, expected tool/resource counts, no errors/warnings, no missing-config gripes). Redirecting stdin is what ends the run: the server treats EOF as a shutdown signal, boots fully, then exits on its own, so the log also shows the graceful-shutdown path. Do not background it and reach for `pkill` — a pattern like `pkill -f dist/index.js` matches every other stdio MCP server on the machine, including the ones the calling agent's own session is connected to. Pino logs go to stderr in stdio mode (stdout is reserved for JSON-RPC), so they print straight to the terminal when you run interactively. No need to call tools over stdio — the HTTP pass already covered handler behavior.
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -45,7 +45,8 @@ cat > /tmp/<project-name>-field-test-9DJ73-K103L.sh <<'HELPER_EOF'
|
|
|
45
45
|
#
|
|
46
46
|
# Surfaces failures aggressively — field test is for finding things that fail,
|
|
47
47
|
# so the helper auto-tails logs and prints HTTP status/body on errors instead
|
|
48
|
-
# of swallowing them.
|
|
48
|
+
# of swallowing them. It also measures: every mcp_call prints a one-line
|
|
49
|
+
# size/latency reading on stderr, and mcp_catalog_size weighs tools/list.
|
|
49
50
|
|
|
50
51
|
# Usage: mcp_start /path/to/server [startup-timeout-seconds] (default: 30)
|
|
51
52
|
# Builds, starts the HTTP server in the background, waits for the listen line,
|
|
@@ -107,7 +108,10 @@ _mcp_init_fail() {
|
|
|
107
108
|
|
|
108
109
|
# Usage: mcp_init <url>
|
|
109
110
|
# Runs `initialize`, sends `notifications/initialized`, prints:
|
|
110
|
-
# ready sid=<id-or-empty> protocol=<negotiated-version> requested=<want> (HTTP <code>)
|
|
111
|
+
# ready sid=<id-or-empty> protocol=<negotiated-version> requested=<want> instructions=<bytes>B (HTTP <code>)
|
|
112
|
+
# `instructions=` is the byte size of the server's `instructions` string — it
|
|
113
|
+
# loads into every client session alongside tools/list, so it is the other half
|
|
114
|
+
# of the per-session context tax mcp_catalog_size weighs.
|
|
111
115
|
# The initialize *result* is what decides success — a session ID is optional.
|
|
112
116
|
# A server started with MCP_SESSION_MODE=stateless mints none, and the session
|
|
113
117
|
# header is then omitted from every later request. Capture BOTH `sid` and
|
|
@@ -136,7 +140,10 @@ mcp_init() {
|
|
|
136
140
|
# Unwrap SSE framing when present; a plain JSON body is used as-is.
|
|
137
141
|
local payload; payload=$(sed -n 's/^data: //p' "$body_file")
|
|
138
142
|
[ -z "$payload" ] && payload=$(cat "$body_file")
|
|
139
|
-
|
|
143
|
+
# Pick the reply frame by structure, not by substring: a server that logs to the
|
|
144
|
+
# client emits `notifications/message` frames first, and a `"level":"error"` or a
|
|
145
|
+
# log string containing `result` matches a text grep and gets read as the reply.
|
|
146
|
+
local reply; reply=$(printf '%s\n' "$payload" | jq -c 'select(type=="object" and (has("result") or has("error")))' 2>/dev/null | tail -1)
|
|
140
147
|
[ -z "$reply" ] && reply="$payload"
|
|
141
148
|
if printf '%s' "$reply" | grep -q '"error"'; then
|
|
142
149
|
_mcp_init_fail "server returned a JSON-RPC error" "$body_file" "$hdr"
|
|
@@ -151,13 +158,44 @@ mcp_init() {
|
|
|
151
158
|
_mcp_init_fail "initialize result declares no protocolVersion" "$body_file" "$hdr"
|
|
152
159
|
return 1
|
|
153
160
|
fi
|
|
161
|
+
local instr; instr=$(printf '%s' "$reply" | jq -r '.result.instructions // "" | utf8bytelength' 2>/dev/null || echo 0)
|
|
154
162
|
local sid; sid=$(grep -i '^mcp-session-id:' "$hdr" | awk '{print $2}' | tr -d '\r\n')
|
|
155
163
|
local init_headers=(-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "MCP-Protocol-Version: $got")
|
|
156
164
|
[ -n "$sid" ] && init_headers+=(-H "Mcp-Session-Id: $sid")
|
|
157
165
|
curl -sS -X POST "$url" "${init_headers[@]}" \
|
|
158
166
|
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
|
|
159
167
|
rm -f "$hdr" "$body_file"
|
|
160
|
-
echo "ready sid=$sid protocol=$got requested=$want (HTTP $code)"
|
|
168
|
+
echo "ready sid=$sid protocol=$got requested=$want instructions=${instr}B (HTTP $code)"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Internal: one stderr line per call — reply bytes, the content/structured
|
|
172
|
+
# split, a token estimate, wall-clock. Bytes are the reply as delivered (SSE
|
|
173
|
+
# framing stripped). `content` is every text block's bytes, `structured` is
|
|
174
|
+
# structuredContent serialized. The token figure is bytes/4 — an estimate, not
|
|
175
|
+
# a tokenizer. Wall-clock is curl's time_total for the whole exchange.
|
|
176
|
+
_mcp_measure() {
|
|
177
|
+
local method="$1"; local params="$2"; local reply="$3"; local code="$4"; local secs="$5"
|
|
178
|
+
local total; total=$(printf '%s' "$reply" | wc -c | tr -d ' ')
|
|
179
|
+
local ms; ms=$(awk -v s="$secs" 'BEGIN { printf "%d", s * 1000 }')
|
|
180
|
+
local label="$method"
|
|
181
|
+
local split=""
|
|
182
|
+
case "$method" in
|
|
183
|
+
tools/call)
|
|
184
|
+
local name; name=$(printf '%s' "$params" | jq -r '.name // empty' 2>/dev/null)
|
|
185
|
+
[ -n "$name" ] && label="$method $name"
|
|
186
|
+
split=$(printf '%s' "$reply" | jq -r '
|
|
187
|
+
(.result // {}) as $r
|
|
188
|
+
| ([$r.content[]? | select(.type == "text") | .text] | join("") | utf8bytelength) as $c
|
|
189
|
+
| (if $r.structuredContent == null then "none" else ($r.structuredContent | tojson | utf8bytelength | tostring) end) as $s
|
|
190
|
+
| "content \($c) · structured \($s)"' 2>/dev/null)
|
|
191
|
+
;;
|
|
192
|
+
resources/read)
|
|
193
|
+
split=$(printf '%s' "$reply" | jq -r '
|
|
194
|
+
"text \([.result.contents[]? | .text // ""] | join("") | utf8bytelength)"' 2>/dev/null)
|
|
195
|
+
;;
|
|
196
|
+
esac
|
|
197
|
+
local tok; tok=$(awk -v b="$total" 'BEGIN { if (b >= 1000) printf "~%.1fk", b / 4000; else printf "~%d", b / 4 }')
|
|
198
|
+
echo "⏱ $label · HTTP $code · ${total} B${split:+ ($split)} · $tok tok · ${ms} ms" >&2
|
|
161
199
|
}
|
|
162
200
|
|
|
163
201
|
# Usage: mcp_call <url> <sid> <method> [JSON_PARAMS] [protocol]
|
|
@@ -166,6 +204,9 @@ mcp_init() {
|
|
|
166
204
|
# emitting every event would break `| jq .result`). A transport failure or an
|
|
167
205
|
# HTTP >= 400 prints the details and returns non-zero — it never returns 0 with
|
|
168
206
|
# empty output. Pipe to `jq`.
|
|
207
|
+
# Every call also prints one measurement line on stderr, e.g.
|
|
208
|
+
# ⏱ tools/call gbif_search_species · HTTP 200 · 18412 B (content 9100 · structured 8900) · ~4.6k tok · 812 ms
|
|
209
|
+
# Read it on every call — it is the size/latency evidence the report cites.
|
|
169
210
|
# `sid` may be empty ('') for a stateless server; the session header is then
|
|
170
211
|
# omitted. Pass the `protocol` mcp_init printed as the 5th arg — with no
|
|
171
212
|
# session carrying the negotiation, MCP-Protocol-Version is what tells the
|
|
@@ -180,12 +221,13 @@ mcp_call() {
|
|
|
180
221
|
body=$(printf '{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}' "$RANDOM" "$method" "$params")
|
|
181
222
|
fi
|
|
182
223
|
local resp_file; resp_file=$(mktemp)
|
|
183
|
-
local code curl_rc
|
|
224
|
+
local stats code secs curl_rc
|
|
184
225
|
local headers=(-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream")
|
|
185
226
|
[ -n "$sid" ] && headers+=(-H "Mcp-Session-Id: $sid")
|
|
186
227
|
[ -n "$protocol" ] && headers+=(-H "MCP-Protocol-Version: $protocol")
|
|
187
|
-
|
|
228
|
+
stats=$(curl -sS -o "$resp_file" -w '%{http_code} %{time_total}' -X POST "$url" "${headers[@]}" -d "$body")
|
|
188
229
|
curl_rc=$?
|
|
230
|
+
read -r code secs <<< "$stats"
|
|
189
231
|
if [ "$curl_rc" -ne 0 ] || [ -z "$code" ] || [ "$code" = "000" ]; then
|
|
190
232
|
echo "TRANSPORT FAILURE calling $method — curl exit $curl_rc, http_code '${code:-none}'." >&2
|
|
191
233
|
echo "Server not reachable at $url (check it is still running: mcp_log <log>)." >&2
|
|
@@ -198,14 +240,46 @@ mcp_call() {
|
|
|
198
240
|
rm -f "$resp_file"
|
|
199
241
|
return 1
|
|
200
242
|
fi
|
|
243
|
+
local reply
|
|
201
244
|
local sse; sse=$(sed -n 's/^data: //p' "$resp_file")
|
|
202
245
|
if [ -n "$sse" ]; then
|
|
203
|
-
|
|
204
|
-
|
|
246
|
+
# Structural pick, same reason as in mcp_init: log-notification frames precede
|
|
247
|
+
# the reply and can carry the literal tokens a text grep keys on.
|
|
248
|
+
reply=$(printf '%s\n' "$sse" | jq -c 'select(type=="object" and (has("result") or has("error")))' 2>/dev/null | tail -1)
|
|
249
|
+
reply="${reply:-$sse}"
|
|
205
250
|
else
|
|
206
|
-
cat "$resp_file"
|
|
251
|
+
reply=$(cat "$resp_file")
|
|
207
252
|
fi
|
|
208
253
|
rm -f "$resp_file"
|
|
254
|
+
_mcp_measure "$method" "$params" "$reply" "$code" "$secs"
|
|
255
|
+
printf '%s\n' "$reply"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# Usage: mcp_catalog_size <url> <sid> [protocol]
|
|
259
|
+
# Weighs the catalog: the bytes of the tools/list reply — what every client
|
|
260
|
+
# loads into context per session before a single call — then each tool's
|
|
261
|
+
# serialized entry, largest first, split into description / inputSchema /
|
|
262
|
+
# outputSchema so the row says WHERE the weight is. A fat outputSchema costs as
|
|
263
|
+
# much as a fat description and is the usual surprise. Prints:
|
|
264
|
+
# catalog: 12 tools · 48210 B · ~12.1k tok
|
|
265
|
+
# <bytes> <~tok> <name> desc <b> · input <b> · output <b|none> (one row per tool)
|
|
266
|
+
mcp_catalog_size() {
|
|
267
|
+
local url="$1"; local sid="$2"; local protocol="${3:-}"
|
|
268
|
+
[ -z "$url" ] && { echo "usage: mcp_catalog_size <url> <sid> [protocol]" >&2; return 1; }
|
|
269
|
+
local reply; reply=$(mcp_call "$url" "$sid" tools/list '' "$protocol") || return 1
|
|
270
|
+
printf '%s' "$reply" | jq -r '
|
|
271
|
+
def tok: if . >= 1000 then "~\(. / 4000 * 10 | round / 10)k" else "~\(. / 4 | floor)" end;
|
|
272
|
+
def bytes_or_none: if . == null then "none" else (tojson | utf8bytelength | tostring) end;
|
|
273
|
+
(.result.tools // []) as $t
|
|
274
|
+
| (. | tojson | utf8bytelength) as $total
|
|
275
|
+
| "catalog: \($t | length) tools · \($total) B · \($total | tok) tok",
|
|
276
|
+
($t
|
|
277
|
+
| map({name, b: (tojson | utf8bytelength),
|
|
278
|
+
d: ((.description // "") | utf8bytelength),
|
|
279
|
+
i: (.inputSchema | bytes_or_none),
|
|
280
|
+
o: (.outputSchema | bytes_or_none)})
|
|
281
|
+
| sort_by(-.b) | .[]
|
|
282
|
+
| "\(.b)\t\(.b | tok)\t\(.name)\tdesc \(.d) · input \(.i) · output \(.o)")'
|
|
209
283
|
}
|
|
210
284
|
|
|
211
285
|
# Usage: mcp_log <server-log-path> [N] (default: 50 lines)
|
|
@@ -273,7 +347,7 @@ Capture `pid`, `url`, `port`, `log` from the `mcp_start` output — every later
|
|
|
273
347
|
mcp_init <url-from-mcp_start>
|
|
274
348
|
```
|
|
275
349
|
|
|
276
|
-
Runs `initialize`, sends `notifications/initialized`, prints the `sid` and `protocol` to capture for `mcp_call
|
|
350
|
+
Runs `initialize`, sends `notifications/initialized`, prints the `sid` and `protocol` to capture for `mcp_call`, plus `instructions=` — the byte size of the server's `instructions` string, which every client loads per session alongside the catalog (record it with the catalog total in Step 3). Success is decided by the initialize *result*, so a transport failure, a non-2xx status, a JSON-RPC error, a malformed body, or a result with no `protocolVersion` all fail loudly with the raw exchange.
|
|
277
351
|
|
|
278
352
|
The helper requests the newest `initialize`-negotiated revision the SDK supports (`2025-11-25`). **If `protocol=` comes back older than `requested=`, the server capped it** — every call after that exercises an older protocol than a current client would negotiate. Note it as a `bug` finding and check the pinned `@modelcontextprotocol/server` version; don't quietly test the downgraded surface. To deliberately test an older version, set `MCP_FIELD_TEST_PROTOCOL`.
|
|
279
353
|
|
|
@@ -294,8 +368,11 @@ Both modes exercise the **2025-era arm**: `initialize` negotiates the revision,
|
|
|
294
368
|
mcp_call <url> <sid> tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema}'
|
|
295
369
|
mcp_call <url> <sid> resources/list | jq '.result.resources[] | {uri, name, mimeType}'
|
|
296
370
|
mcp_call <url> <sid> prompts/list | jq '.result.prompts[] | {name, description, arguments}'
|
|
371
|
+
mcp_catalog_size <url> <sid> <protocol>
|
|
297
372
|
```
|
|
298
373
|
|
|
374
|
+
**Weigh the catalog.** `mcp_catalog_size` prints the `tools/list` bytes — the context every client loads per session before a single call — and each tool's entry, largest first, split into description / `inputSchema` / `outputSchema`. Record the total alongside the `instructions=` bytes from Step 2; together they are the per-session tax. The split says where a heavy tool's weight lives: an `outputSchema` narrating every field of a 60-field record is the common surprise, an over-long description the obvious one. Hand the outliers to `tool-defs-analysis` (its length-outliers pass) rather than trimming blind.
|
|
375
|
+
|
|
299
376
|
Present a compact catalog to the user: each definition's name + 1-line description. Flag vague or missing descriptions as you go — those feed into the report. Use this to build the test plan.
|
|
300
377
|
|
|
301
378
|
**Audit every description for leaks** — tool description, every parameter `.describe()` in `inputSchema`, and every field `.describe()` in `outputSchema` (the `outputSchema` projection above is what surfaces these; don't skim past it). Three categories:
|
|
@@ -317,6 +394,7 @@ Treat any hit as a `ux` finding in the report. The authoring rule lives under *T
|
|
|
317
394
|
| Happy path | One realistic input. Output shape matches schema. `content[]` text reads clearly to a human. |
|
|
318
395
|
| `structuredContent` ↔ `content[]` parity | Dump the whole array (`jq '.result.content'`) and check every `structuredContent` field is surfaced *somewhere* in it — enrichment lands in its own trailing block, not in `content[0]`. Parity gap = client-specific blindness. |
|
|
319
396
|
| Input error | One invalid input (wrong type or missing required). Error text says *what*, *why*, *how to fix*. |
|
|
397
|
+
| Size & latency | Read the `⏱` line `mcp_call` prints on every call. A happy-path response over **24,000 B** (the framework's `DEFAULT_OUTLINE_BUDGET_BYTES` — the line at which it would outline a document itself) with no truncation disclosure and no retrieval path (cursor, offset, `sections`, canvas handle) is a `ux` finding: the agent pays the whole payload with no way to ask for less. `content` ≈ `structured` with the text starting `{` means the JSON is on the wire twice — a missing `format()`. A call over ~5 s on a happy-path input is worth a `mcp_log` look before calling it upstream latency. |
|
|
320
398
|
|
|
321
399
|
**Situational — add only when triggered**
|
|
322
400
|
|
|
@@ -349,7 +427,7 @@ Treat any hit as a `ux` finding in the report. The authoring rule lives under *T
|
|
|
349
427
|
|
|
350
428
|
Use `TaskCreate` — one task per definition. Mark complete as you go. Don't batch.
|
|
351
429
|
|
|
352
|
-
For each call, capture: input sent, response (trim huge payloads to files), whether `isError: true` appeared, anything surprising (slow response, parity drift, unhelpful text, crash).
|
|
430
|
+
For each call, capture: input sent, the `⏱` line (bytes, split, ms), response (trim huge payloads to files), whether `isError: true` appeared, anything surprising (slow response, parity drift, unhelpful text, crash).
|
|
353
431
|
|
|
354
432
|
When a call surprises you — slow, hangs, returns terse output, surfaces an unhelpful error — run `. /tmp/<project-name>-field-test-<ID>.sh && mcp_log <log>` to tail the server log. The pino startup banner, request handler errors, upstream API call traces, and rate-limit warnings all land in the per-server log (read via `mcp_log`) rather than coming back through `mcp_call`. Don't guess at runtime behavior from response text alone.
|
|
355
433
|
|
|
@@ -374,12 +452,16 @@ Kills the background server and its port-holding child, removes the server log,
|
|
|
374
452
|
|
|
375
453
|
### 7. Report
|
|
376
454
|
|
|
377
|
-
|
|
455
|
+
Four sections. Tight. The user should be able to skim the summary, scan the numbers, read details only for what matters, and act on numbered options.
|
|
378
456
|
|
|
379
457
|
#### Summary (1 paragraph)
|
|
380
458
|
|
|
381
459
|
One paragraph. How many definitions exercised, how many passed clean, how many have issues, and the single most important finding. No tables, no lists.
|
|
382
460
|
|
|
461
|
+
#### Size & latency
|
|
462
|
+
|
|
463
|
+
Per-session tax on its own line (`instructions` bytes + catalog bytes, with the heaviest tool named), then one row per tool exercised, sorted by happy-path bytes descending: tool · bytes · ~tok · ms. Over 15 tools, keep every row over 24,000 B plus the three slowest and fold the rest into one line ("N more under budget, median X B"). Numbers only — what they mean goes in Findings.
|
|
464
|
+
|
|
383
465
|
#### Findings
|
|
384
466
|
|
|
385
467
|
Only include definitions with issues. Group by severity. Each finding is 2–4 lines unless it genuinely needs more. A parity finding cites the full `content[]` dump as its evidence — a quote from one index doesn't establish drift.
|
|
@@ -417,10 +499,12 @@ End with:
|
|
|
417
499
|
|
|
418
500
|
## Checklist
|
|
419
501
|
|
|
420
|
-
- [ ] Stdio boot check completed — `bun run rebuild && bun run start:stdio` shows clean startup (banner, expected counts, no errors)
|
|
502
|
+
- [ ] Stdio boot check completed — `bun run rebuild && bun run start:stdio < /dev/null` shows clean startup (banner, expected counts, no errors) and a graceful shutdown on EOF
|
|
421
503
|
- [ ] HTTP server built and started; real port parsed from log
|
|
422
504
|
- [ ] Session initialized (a stateless server returns an empty `sid` — still a pass); `notifications/initialized` sent; negotiated protocol version matches the requested one (a downgrade is a finding)
|
|
423
505
|
- [ ] Catalog surfaced and presented; descriptions audited for leaks (implementation details, meta-coaching, consumer-aware phrasing)
|
|
506
|
+
- [ ] Catalog weighed (`mcp_catalog_size`); total + `instructions=` bytes recorded for the report
|
|
507
|
+
- [ ] Every call's `⏱` line read; any happy-path response over 24,000 B with no disclosure + retrieval path filed as `ux`
|
|
424
508
|
- [ ] Universal battery run on every definition (happy path, parity against the full `content[]` array, input error)
|
|
425
509
|
- [ ] Situational categories applied only when triggered
|
|
426
510
|
- [ ] **If >15 tools:** sampled 30–40% for situational testing; skipped definitions listed in report
|
|
@@ -429,4 +513,4 @@ End with:
|
|
|
429
513
|
- [ ] **If any tool truncates, caps, or spills its output:** truncation forced; disclosure + a retrieval path (cursor, offset, selector, canvas handle) verified
|
|
430
514
|
- [ ] External-state / auth-gated tools handled explicitly (run, skip, or confirm)
|
|
431
515
|
- [ ] Server stopped (port confirmed free); server log and helper script removed
|
|
432
|
-
- [ ] Report: summary paragraph → grouped findings → numbered options
|
|
516
|
+
- [ ] Report: summary paragraph → size & latency table → grouped findings → numbered options
|
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: git-wrapup
|
|
3
3
|
description: >
|
|
4
|
-
Land working-tree changes as logical commits — the work grouped by concern, topped by a release commit (version bump, changelog, regenerated artifacts)
|
|
4
|
+
Land working-tree changes as logical commits — the work grouped by concern, topped by a release commit (version bump, changelog, regenerated artifacts). Verify, commit. Stops at "committed locally on main" — or, when the project releases through a release PR, at "release branch pushed, PR open". No tag, no push to main, no publish: the release-and-publish skill merges, tags, and ships from here. Distilled from the git_wrapup_instructions protocol.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.16"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## When to use
|
|
13
13
|
|
|
14
|
-
Working-tree or staged changes are ready to ship as a new version. This skill lands them as a stack of logical commits — the work grouped by concern, topped by a release commit (version + changelog + tree)
|
|
14
|
+
Working-tree or staged changes are ready to ship as a new version. This skill lands them as a stack of logical commits — the work grouped by concern, topped by a release commit (version + changelog + tree). It does NOT tag, push to main, or publish — `release-and-publish` does all three.
|
|
15
15
|
|
|
16
16
|
Common triggers:
|
|
17
17
|
- Feature work, bug fixes, or dependency updates are done and tested
|
|
18
18
|
- A maintenance or polish pass left changes in the working tree
|
|
19
19
|
- An orchestrator says "wrapup this project"
|
|
20
20
|
|
|
21
|
+
## Release PR mode
|
|
22
|
+
|
|
23
|
+
A project can route every release through a pull request — one PR per version, for the audit trail and a stable review target. The mode is declared in the project's `CLAUDE.md`/`AGENTS.md` or in the caller's brief; when neither says anything, there is no release PR and the stack lands on `main` directly.
|
|
24
|
+
|
|
25
|
+
| Mode | Wrapup ends at | Then |
|
|
26
|
+
|:--|:--|:--|
|
|
27
|
+
| *(none — default)* | commit stack on `main`, tree clean | `release-and-publish` tags HEAD and ships |
|
|
28
|
+
| **gated** | commit stack on `release/<version>`, branch pushed, PR open | a review pass on the PR (`release-pr-review` skill), then a separate `release-and-publish` run fast-forwards `main`, tags, and ships |
|
|
29
|
+
| **straight-through** | same as gated | the same agent continues straight into `release-and-publish` |
|
|
30
|
+
|
|
31
|
+
The branch is created at wrapup time, never before: work happens on `main` until the version is known, then the uncommitted tree moves to `release/<version>` in one step (step 7). The commit stack, the release commit, and the tag format are identical in every mode — the PR adds an artifact around them, it does not change them.
|
|
32
|
+
|
|
21
33
|
## Pre-wrapup gate checklist
|
|
22
34
|
|
|
23
35
|
Every item must be true before starting wrapup. Committing means releasing — a commit only happens when the work is ready to ship, not just "the edits are done." Each item is a goal to verify.
|
|
@@ -104,6 +116,8 @@ security: false # true ONLY for a security fix in this server's own source
|
|
|
104
116
|
|
|
105
117
|
**Tone:** Terse, fact-dense. Bullet = **symbol** + what changed + at most one consumer-facing caveat; one sentence by default, two max — a bullet past ~40 words or three sentences is wrong. The linked issue carries the why and the commit diff the how; the changelog names what changed and what a consumer does about it. Cut: history/justification narration, design-rationale defense, "X unchanged" clauses (short parenthetical only where a misread is likely), edge-case inventories. **Verified ≠ included** — the diff-is-source-of-truth rule bounds the truth of what you write, never the amount. Model length on `changelog/template.md`'s authoring guide, never on the previous entry (entries modeled on entries compound). `agent-notes` carries adoption steps only, never a second rendering of the body; a consequence shared by many bullets is stated once, not per bullet. Full conventions: the authoring guide in `changelog/template.md`.
|
|
106
118
|
|
|
119
|
+
**Re-read the entry file after writing it, then sweep for harness markup:** `grep -rlF -e '</invoke>' -e '</content>' changelog/` must print nothing. A stray closing tag at EOF is the authoring tool's own syntax bleeding into the file; `changelog/` is in `package.json` `files`, so it ships inside the npm tarball, and `changelog:check` cannot catch it — the rollup drops the trailing line, so a clean `CHANGELOG.md` proves nothing about the entry.
|
|
120
|
+
|
|
107
121
|
### 5. Regenerate derived artifacts
|
|
108
122
|
|
|
109
123
|
```bash
|
|
@@ -127,6 +141,15 @@ bun run test:package # only if the script exists — NOT part of test:all
|
|
|
127
141
|
|
|
128
142
|
### 7. Commit — group by concern, release artifacts on top
|
|
129
143
|
|
|
144
|
+
**Release PR mode only — move to the release branch first, before the first commit:**
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
git branch --show-current # must be main
|
|
148
|
+
git switch -c release/<version> # uncommitted work rides along
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Commits never land on `main` in this mode. If a `release/*` branch already exists locally, a prior release PR was never merged — halt and report it rather than stacking a second release on top.
|
|
152
|
+
|
|
130
153
|
Do NOT `git add -A` into one commit. Group the working tree into a handful of logical commits — never one blob:
|
|
131
154
|
|
|
132
155
|
1. **The work — one commit per concern.** A feature spanning multiple layers splits by layer: runtime/logic, linter/tooling, docs/skills. Unrelated changes (two separate fixes, an incidental doc tweak) are their own commits. Work commits do not carry the version.
|
|
@@ -140,7 +163,7 @@ git commit -m "<subject>"
|
|
|
140
163
|
# repeat per concern; version + changelog + tree are the final commit
|
|
141
164
|
```
|
|
142
165
|
|
|
143
|
-
**The file is the atomic boundary:** NEVER split a single file's changes across commits. When one file serves two concerns, it ships whole in the commit of its dominant concern.
|
|
166
|
+
**The file is the atomic boundary:** NEVER split a single file's working-tree changes across commits, regardless of mechanism — not `git add -p`, not an index-only patch (`git apply --cached`), not editing the file between commits to remove-then-re-add a hunk. When one file serves two concerns, it ships whole in the commit of its dominant concern; a later commit may touch the file again only for changes made AFTER the first commit (a version badge bumped after the fix landed).
|
|
144
167
|
|
|
145
168
|
**Subject format:** Conventional Commits.
|
|
146
169
|
- Work commits (no version): `feat: hosted server endpoint`, `fix: handle empty SPARQL result sets`, `feat(linter): enrichment contract rules`, `docs: document the enrichment block`
|
|
@@ -172,66 +195,81 @@ The changelog carries the depth, the tag carries the headline, the commit carrie
|
|
|
172
195
|
|
|
173
196
|
**Right-size it.** "Group by concern" is not "always split." A genuinely single-concern change — one fix, a dependency bump, a small doc edit — is one work commit plus the release commit; when the change and its version bump are inseparable for a tiny patch, a single commit whose subject leads with the version is fine. The failure mode to prevent is the inverse: a large, multi-layer feature crammed into one commit alongside the release artifacts.
|
|
174
197
|
|
|
175
|
-
### 8.
|
|
198
|
+
### 8. Open the release PR (release PR mode only)
|
|
199
|
+
|
|
200
|
+
Skip this step entirely when the project has no release PR mode — go to step 9.
|
|
176
201
|
|
|
177
202
|
```bash
|
|
178
|
-
git
|
|
203
|
+
git push -u origin release/<version>
|
|
204
|
+
gh pr create --base main --head release/<version> --title "<release commit subject>" --body-file <path-to-body.md>
|
|
179
205
|
```
|
|
180
206
|
|
|
181
|
-
|
|
207
|
+
**Title:** the release commit's subject, verbatim — `chore(release): <version> — <theme>`.
|
|
182
208
|
|
|
183
|
-
|
|
209
|
+
**Body — always via `--body-file`, never an inline `--body` string** (backticks inside a double-quoted argument are command substitution and silently vanish). Write the file to a scratch location, not into the repo.
|
|
184
210
|
|
|
185
|
-
|
|
211
|
+
The body is the release digest — the same headline digest the annotated tag will carry, plus a gates record. It is written here, reviewed on the PR, and copied into the tag at release time, so it is the one place the release notes get reviewed before they become permanent. Format:
|
|
186
212
|
|
|
187
213
|
```
|
|
188
|
-
<theme —
|
|
214
|
+
<theme — the changelog entry's summary: line, plain prose, one line>
|
|
215
|
+
|
|
216
|
+
## Changes
|
|
189
217
|
|
|
190
218
|
- <notable user-facing change> (#N)
|
|
191
219
|
- <notable user-facing change> (#N)
|
|
192
220
|
- <ONE compact grouped line for the minor/internal changes — build config, repo hygiene, metadata>
|
|
193
221
|
- deps: `@cyanheads/mcp-ts-core` ^0.10.6 → ^0.10.14 (+ dev-dep bumps)
|
|
194
222
|
|
|
223
|
+
## Gates
|
|
224
|
+
|
|
225
|
+
- `bun run devcheck` — clean
|
|
226
|
+
- `bun run rebuild` — ok
|
|
227
|
+
- `bun run test:all` — <N> passed
|
|
228
|
+
- `bun run test:package` — <N> passed (only where the project defines it)
|
|
229
|
+
|
|
195
230
|
[CHANGELOG v<version>](https://github.com/<OWNER>/<REPO>/blob/main/changelog/<major.minor>.x/<version>.md)
|
|
196
231
|
```
|
|
197
232
|
|
|
198
233
|
**Rules:**
|
|
199
|
-
-
|
|
200
|
-
- **
|
|
201
|
-
-
|
|
202
|
-
- **
|
|
203
|
-
- **
|
|
204
|
-
-
|
|
205
|
-
|
|
206
|
-
-
|
|
207
|
-
|
|
208
|
-
|
|
234
|
+
- **`## Changes` follows the tag rules exactly** (`release-and-publish` step 4): flat bullets, never Keep-a-Changelog section headers; complete at headline granularity — notable changes get their own bullet, minor/internal items share ONE grouped bullet; deps one line max, naming only what earns it; no narrative, no marketing adjectives. Depth lives in the changelog entry, which is in this PR's diff and linked on the last line.
|
|
235
|
+
- **Every claim traces to the diff and to the changelog entry.** The body is derived from the entry you authored in step 4, never written independently of it.
|
|
236
|
+
- **`## Gates` is the one release surface that carries gate results** — the exact commands from step 6 with their outcomes. It never enters the tag.
|
|
237
|
+
- **Issue references are bare `(#N)` backlinks — never a closing keyword** (`Closes #N`, `Fixes #N`); the merge would close the issue before its close-out comment lands.
|
|
238
|
+
- **Changelog link is the final line**, same form as the tag, blank line above it.
|
|
239
|
+
- Length is earned — a theme, two bullets, gates, and the link is a complete body for a small patch.
|
|
240
|
+
|
|
241
|
+
If the review pass changes what ships, `release-pr-review` updates `## Changes` and `## Gates` to match; `release-and-publish` then lifts `## Changes` plus the final link into the tag verbatim.
|
|
242
|
+
|
|
243
|
+
**Gated mode: halt here.** Report the PR URL, the branch, and the commit stack. Do not tag, do not merge, do not touch `main`. The review pass and `release-and-publish` run as separate steps after this one.
|
|
244
|
+
|
|
245
|
+
**Straight-through mode:** continue directly into `release-and-publish`.
|
|
209
246
|
|
|
210
247
|
### 9. Verify end state
|
|
211
248
|
|
|
212
249
|
```bash
|
|
213
250
|
git log --oneline -8 # confirm the commit stack: work commits + release commit on top
|
|
214
|
-
git show v<version> --stat | head -20 # confirm tag points at HEAD (the release commit)
|
|
215
251
|
git status # must be clean
|
|
216
|
-
git tag -
|
|
252
|
+
git tag --points-at HEAD # must print nothing — tagging is release-and-publish's job
|
|
253
|
+
git branch --show-current # main, or release/<version> in release PR mode
|
|
254
|
+
gh pr view --json number,url,state # release PR mode: OPEN, head = the branch above
|
|
217
255
|
```
|
|
218
256
|
|
|
219
|
-
If the working tree isn't clean or the
|
|
257
|
+
If the working tree isn't clean or the release commit isn't at HEAD, something went wrong — investigate before proceeding.
|
|
220
258
|
|
|
221
|
-
**Do NOT push.** This skill stops here.
|
|
259
|
+
**Do NOT tag, push `main`, or publish.** This skill stops here. `release-and-publish` merges the release branch when there is one, creates the tag, pushes, and publishes.
|
|
222
260
|
|
|
223
261
|
## Constraints
|
|
224
262
|
|
|
225
|
-
- **
|
|
263
|
+
- **No push to `main`, no tag, no publish.** The only remote writes this skill makes are the release-branch push and the PR create in release PR mode
|
|
226
264
|
- **Never stash.** Not for quick checks, not for testing, not for any reason
|
|
227
|
-
- **Never destructive.** No `git reset --hard`, `git restore .`, `git clean -f`, `git checkout --
|
|
265
|
+
- **Never destructive.** No `git reset --hard`, `git restore .`, `git clean -f`, `git checkout -- .`, no force-push
|
|
228
266
|
- **Bash git only.** Drive every git operation through the shell
|
|
229
267
|
- If `v<version>` already exists as a tag, **halt and report the conflict** — include the version string, existing tag SHA, and current HEAD SHA so the caller can resolve it. Do not delete or move tags without explicit authorization
|
|
230
268
|
|
|
231
269
|
## Checklist
|
|
232
270
|
|
|
233
271
|
- [ ] Diff reviewed end-to-end before version bump
|
|
234
|
-
- [ ] Version bumped in every declaring file (`package.json`, `server.json`, `manifest.json`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, README badge, `CLAUDE.md`/`AGENTS.md` if they pin a version)
|
|
272
|
+
- [ ] Version bumped in every declaring file (`package.json`, `server.json`, `manifest.json`, `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, README badge, `CLAUDE.md`/`AGENTS.md` if they pin a version) — verify by command, not by eye: `v=$(jq -r .version package.json); grep -rl "$v" package.json server.json manifest.json .claude-plugin/plugin.json .codex-plugin/plugin.json README.md | wc -l` must equal the count of files that exist, and `grep -c "Version-$v-" README.md` must print `1`. The README badge is the one no lint reads, so it is the one that ships stale
|
|
235
273
|
- [ ] GH issues addressed by this work commented with what landed (if working from GH issues)
|
|
236
274
|
- [ ] Docs updated for any new or changed features
|
|
237
275
|
- [ ] Changelog authored at `changelog/<major.minor>.x/<version>.md`
|
|
@@ -240,8 +278,9 @@ If the working tree isn't clean or the tag doesn't point at HEAD, something went
|
|
|
240
278
|
- [ ] `bun run devcheck` passes
|
|
241
279
|
- [ ] `bun run test:all` (or `test`) passes
|
|
242
280
|
- [ ] `bun run test:package` passes, when the project defines it — it guards the public-export manifest and `test:all` does not run it
|
|
281
|
+
- [ ] Release PR mode: stack committed on `release/<version>`, never on `main`
|
|
243
282
|
- [ ] Work grouped into logical commits (large features split by layer); release artifacts (version + changelog + tree) committed separately on top, subject leading with the version
|
|
244
283
|
- [ ] Every commit carries a body, and every body is one or two lines — none subject-only, none a paragraph
|
|
245
|
-
- [ ]
|
|
284
|
+
- [ ] Release PR mode: branch pushed, PR open — title = release commit subject; body = theme line, `## Changes` in tag rules, `## Gates`, changelog link last (via `--body-file`, no closing keywords)
|
|
246
285
|
- [ ] Working tree clean
|
|
247
|
-
- [ ]
|
|
286
|
+
- [ ] No tag at HEAD, nothing pushed to `main` — `release-and-publish` owns both
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Pick and run a multi-phase workflow that chains foundational task skills (`git-wrapup`, `release-and-publish`, `maintenance`, `field-test`, `setup`, etc.) end-to-end. Routes user intent to a workflow file under `workflows/` — greenfield builds, maintenance + release, field-test + fix, or known-work + release. Single source for the universal rules (no commits without authorization, no destructive git, no marketing language), the orchestrator posture (own the goal, ground sub-agents in primary sources, verify against the goal), and the sub-agent strategy (orient block, parallel fanout, isolation, normalization) that apply across every workflow. Sub-agents are an optional capability — workflows run linearly when fanout isn't available.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.8"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -51,14 +51,14 @@ A workflow file is the orchestrator's playbook for one run. Read it end-to-end b
|
|
|
51
51
|
|
|
52
52
|
These apply to every workflow. Workflow files don't restate them; the orchestrator carries them forward and restates them in sub-agent prompts where applicable.
|
|
53
53
|
|
|
54
|
-
1. **No commits, pushes, tags, branch creation, or destructive ops without explicit user authorization.** Work phases leave the working tree dirty for orchestrator review. Wrap-up and release phases run only after the user authorizes — though once authorized, the authorization is durable through the workflow's end (no re-asking at each phase boundary).
|
|
54
|
+
1. **No commits, pushes, tags, branch creation, or destructive ops without explicit user authorization.** Work phases leave the working tree dirty for orchestrator review. Wrap-up and release phases run only after the user authorizes — though once authorized, the authorization is durable through the workflow's end (no re-asking at each phase boundary). The `release/<version>` branch and PR that `git-wrapup` creates in release PR mode are part of the authorized release, not a separate ask.
|
|
55
55
|
2. **No `git stash`, no `git reset --hard`, no `git restore .`, no `git clean -f`, no `git checkout -- .`.** These bypass safety and risk silent data loss. Read-only git (`status`, `diff`, `log`, `show`, `blame`) is always safe.
|
|
56
56
|
3. **No `--no-verify`, no `--no-gpg-sign`, no bypassing commit hooks.** If a hook fails, investigate the underlying issue.
|
|
57
57
|
4. **`bun run devcheck` is the handoff gate between phases.** Work phases must hand back a green devcheck. If a phase can't reach green, halt and report the failing step verbatim rather than carrying broken state forward.
|
|
58
58
|
5. **No marketing adjectives** in commits, tags, READMEs, or changelog entries — no "comprehensive", "robust", "enhanced", "seamless", "improved". State the change, not its quality.
|
|
59
59
|
6. **One workflow per orchestration run.** Don't interleave two workflows in the same session. If a target needs both (e.g., maintenance surfaces a bug fix that needs field-testing first), sequence them as two workflow runs with a clean handoff in between.
|
|
60
60
|
7. **`gh release create --notes-from-tag` is incompatible with `--repo`.** Always `cd` into the target repo directory for `gh release` commands.
|
|
61
|
-
8. **Annotated tags only** (`git tag -a`), never lightweight, created with `--cleanup=whitespace` — the default (`strip`) deletes `#`-leading lines as comments; `--cleanup=verbatim` glues the SSH signature into the message (unparseable-as-signed tag, signature block leaks into the release body). Tag annotation subject omits the version number — GitHub prepends `v<VERSION>:` to release titles when using `--notes-from-tag`, so including the version in the subject creates stutter. The tag body's final line is a Markdown link to this version's changelog file — `[CHANGELOG v<VERSION>](https://github.com/<OWNER>/<REPO>/blob/main/changelog/<major.minor>.x/<VERSION>.md)`,
|
|
61
|
+
8. **Annotated tags only** (`git tag -a`), never lightweight, created with `--cleanup=whitespace` — the default (`strip`) deletes `#`-leading lines as comments; `--cleanup=verbatim` glues the SSH signature into the message (unparseable-as-signed tag, signature block leaks into the release body). Tag annotation subject omits the version number — GitHub prepends `v<VERSION>:` to release titles when using `--notes-from-tag`, so including the version in the subject creates stutter. The tag body's final line is a Markdown link to this version's changelog file — `[CHANGELOG v<VERSION>](https://github.com/<OWNER>/<REPO>/blob/main/changelog/<major.minor>.x/<VERSION>.md)`, on its own paragraph — giving the release a one-click jump to the full entry; in release PR mode that line continues with ` · release PR #<N>` so the release also points at its audit trail.
|
|
62
62
|
9. **Conventional Commits subjects** (`feat|fix|refactor|chore|docs|test|build(scope): message`). One logical concern per commit. The release commit (version bump + changelog + regenerated artifacts) lands on top of a stack of feature/fix commits, never collapsed alongside them.
|
|
63
63
|
10. **Email on any artifact is the user's domain email**, never a personal address that might appear in git config.
|
|
64
64
|
|
|
@@ -161,7 +161,17 @@ For N targets in a phase:
|
|
|
161
161
|
|
|
162
162
|
### Editor / wrap-up separation
|
|
163
163
|
|
|
164
|
-
Editing phases and wrap-up phases never go in the same sub-agent. Editing sub-agents make file changes and run devcheck — they do not commit, tag, or push. Wrap-up sub-agents read the working tree, commit,
|
|
164
|
+
Editing phases and wrap-up phases never go in the same sub-agent. Editing sub-agents make file changes and run devcheck — they do not commit, tag, or push. Wrap-up sub-agents read the working tree, commit, and (when releasing) tag, push and publish — they do not edit source. This separation lets the orchestrator review diffs before they become permanent and keeps the commit graph clean.
|
|
165
|
+
|
|
166
|
+
### Release PR mode
|
|
167
|
+
|
|
168
|
+
A target can declare that every release goes through a pull request (in its `CLAUDE.md`/`AGENTS.md`, or in the run's brief — mechanics in `git-wrapup`'s "Release PR mode"). The wrap-up + release phase then runs as **three sub-agents in sequence**, with an orchestrator check between each:
|
|
169
|
+
|
|
170
|
+
1. **Wrap-up** — `git-wrapup`; halts with the stack committed on `release/<version>`, pushed, PR open.
|
|
171
|
+
2. **Review** — `release-pr-review`; reads the PR range through `code-simplifier` plus a correctness review, lands fixes as fixup commits autosquashed into the stack, force-with-lease pushes the release branch, syncs the PR body, leaves one summary comment. This is the one role that both edits and commits — scoped to the release branch, never `main`, never a tag.
|
|
172
|
+
3. **Release** — `release-and-publish`; `git merge --ff-only` onto `main` locally, tags `main`'s tip, pushes, publishes. Its brief must state that the review pass is finished — the skill halts without that line, and the orchestrator writes it only after confirming the review agent's report against the PR (`gh pr view --json state,headRefOid`, `git log --oneline main..HEAD`).
|
|
173
|
+
|
|
174
|
+
Straight-through mode drops the review agent: one sub-agent runs wrap-up and release back to back, opening and merging the PR in the same session. Without a declaration there is no PR, and the stack lands on `main` directly.
|
|
165
175
|
|
|
166
176
|
### Normalization
|
|
167
177
|
|
|
@@ -186,10 +196,11 @@ Sub-agent self-reports describe intent, not always reality. After every phase th
|
|
|
186
196
|
- **Files** — `ls`, `git status`, `git diff --stat`
|
|
187
197
|
- **Commits** — `git log --oneline -5`
|
|
188
198
|
- **Tags** — `git tag --points-at HEAD`, `git ls-remote --tags origin`
|
|
199
|
+
- **Release PR** — `gh pr view <N> --json state,headRefOid` (`OPEN` with head == local HEAD between phases; `MERGED` after release), `git ls-remote --heads origin release/<VERSION>` empty after release
|
|
189
200
|
- **GitHub** — `gh repo view --json visibility`, `gh release view v<VERSION>`, `gh issue list`, `gh issue view <N> --comments` to confirm the fix comment landed
|
|
190
201
|
- **npm / registries** — `npm view <pkg>@<version>`, registry-specific checks
|
|
191
202
|
- **Build state** — re-run `bun run devcheck` if the previous phase was supposed to land green
|
|
192
|
-
- **Quality** — tag annotation is a headline digest covering every change (flat bullets — notable ones named, minor ones in one grouped bullet; no changelog section headers, deps ≤1 line, no gates line), subject omits the version number, no marketing adjectives, issue backlinks where applicable, changelog link as final line
|
|
203
|
+
- **Quality** — tag annotation is a headline digest covering every change (flat bullets — notable ones named, minor ones in one grouped bullet; no changelog section headers, deps ≤1 line, no gates line), subject omits the version number, no marketing adjectives, issue backlinks where applicable, changelog link as final line (plus ` · release PR #<N>` in release PR mode)
|
|
193
204
|
|
|
194
205
|
If verification disagrees with the sub-agent's report, that's the signal to re-spawn with the actual state and the unmet goal in the prompt — not to trust the report. The goal hasn't changed; only the path needs to.
|
|
195
206
|
|
|
@@ -200,7 +211,7 @@ If verification disagrees with the sub-agent's report, that's the signal to re-s
|
|
|
200
211
|
| Reads, analysis, file edits (working tree only) | Implicit — initial workflow approval covers these |
|
|
201
212
|
| Local commits, annotated tags | Explicit at workflow start; durable through workflow end |
|
|
202
213
|
| Push to remote, npm / registry publish, GH release create, Docker push | Explicit at workflow start; durable through workflow end |
|
|
203
|
-
| Destructive ops (force push, tag delete, remote branch delete, etc.) | Always re-confirm, never assume |
|
|
214
|
+
| Destructive ops (force push, tag delete, remote branch delete, etc.) | Always re-confirm, never assume — two exceptions ride the release authorization: `release-pr-review`'s `--force-with-lease` on the run's own unmerged `release/<version>` branch, and `release-and-publish` deleting that branch once the PR reports `MERGED` |
|
|
204
215
|
|
|
205
216
|
Pipeline authorization is durable through to completion. Once the user authorizes a workflow run, don't re-ask at each phase boundary — proceed automatically through gates that pass. Conditions that always require a fresh check-in: destructive ops on shared resources, external actions without sign-off, errors that need human judgment.
|
|
206
217
|
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Workflow: field-test one or more existing MCP server projects against the live upstream API, file GH issues for valid findings, deploy fix sub-agents per server, optionally loop until clean, then wrap up and release. Chains the `field-test`, `report-issue-local`, `tool-defs-analysis`, `code-simplifier`, `git-wrapup`, and `release-and-publish` skills. Read `../SKILL.md` first for the universal rules and sub-agent strategy.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.1"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -130,14 +130,16 @@ If looping: respawn Phase 1 + Phase 3 for targets that had fixes applied; skip t
|
|
|
130
130
|
### Phase 6: Wrap-up + release (optional)
|
|
131
131
|
Each sub-agent reads both `skills/git-wrapup/SKILL.md` and `skills/release-and-publish/SKILL.md`.
|
|
132
132
|
|
|
133
|
+
**Release PR mode.** When the target declares it (see "Release PR mode" in `../SKILL.md`), Phase 6 runs as three serial sub-agents — wrap-up (halts at the open PR) → `release-pr-review` → release — with an orchestrator check of the PR between each. Everything below is unchanged; the PR wraps it.
|
|
134
|
+
|
|
133
135
|
**Commit structure.** Fixes are NOT collapsed into a single commit. Per the universal git rules:
|
|
134
136
|
1. Analyze the diff (`git diff --stat`, then spot-check actual changes)
|
|
135
137
|
2. Group by file boundaries — fixes sharing a file ship in the same commit
|
|
136
138
|
3. Commit each group: `fix(scope): description` (Conventional Commits)
|
|
137
|
-
4. Release commit on top — version bump + changelog + regenerated artifacts as `chore(release):
|
|
138
|
-
5. Tag the release commit
|
|
139
|
+
4. Release commit on top — version bump + changelog + regenerated artifacts as `chore(release): <version> — <theme>`
|
|
140
|
+
5. Tag the release commit (`release-and-publish` step 4 — the tag is created at release time, not at wrap-up)
|
|
139
141
|
|
|
140
|
-
The changelog carries the depth; the tag annotation covers every change at headline granularity — notable ones named, minor ones in one grouped bullet (per
|
|
142
|
+
The changelog carries the depth; the tag annotation covers every change at headline granularity — notable ones named, minor ones in one grouped bullet (per `release-and-publish` step 4). The commit split is about git history, not release notes.
|
|
141
143
|
|
|
142
144
|
**Version bump.** Default **patch** for field-test fix releases. **Minor** when enhancements are bundled in.
|
|
143
145
|
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Workflow for landing known work (handoff document findings, tracked GH issues, observed gaps) and shipping it: fix → optional simplify and field-test verification → wrap-up → release across one or more MCP server projects. Generalizes "I have known issues to fix and ship" regardless of how the issues were surfaced. Chains the `field-test`, `report-issue-local`, `code-simplifier`, `git-wrapup`, and `release-and-publish` skills. Read `../SKILL.md` first for the universal rules and sub-agent strategy.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.1"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -114,16 +114,18 @@ Exit gate: `bun run devcheck && bun run rebuild && bun run test`.
|
|
|
114
114
|
### Phase 3: Wrap-up + release
|
|
115
115
|
Each sub-agent reads BOTH `skills/git-wrapup/SKILL.md` AND `skills/release-and-publish/SKILL.md`.
|
|
116
116
|
|
|
117
|
+
**Release PR mode.** When the target declares it (see "Release PR mode" in `../SKILL.md`), Phase 3 runs as three serial sub-agents — wrap-up (halts at the open PR) → `release-pr-review` → release — with an orchestrator check of the PR between each. The commit structure, version bump, and tag rules below are unchanged; the PR wraps them.
|
|
118
|
+
|
|
117
119
|
**Orchestrator responsibility:** before spawning Phase 3 sub-agents, collect all open GH issue numbers per target (`gh issue list -R <owner>/<repo> --state open --json number,title`) and include them in each sub-agent's prompt. Phase 3 sub-agents have no context from prior phases — they need the explicit issue list to know what to close.
|
|
118
120
|
|
|
119
121
|
**Commit structure.** Fixes are NOT collapsed into a single commit:
|
|
120
122
|
1. Analyze the diff — understand which fixes touch which files
|
|
121
123
|
2. Group by file boundaries — fixes sharing a file ship in the same commit
|
|
122
124
|
3. Commit each group: `fix(scope): description` (Conventional Commits)
|
|
123
|
-
4. Release commit on top: `chore(release):
|
|
124
|
-
5. Tag the release commit
|
|
125
|
+
4. Release commit on top: `chore(release): <version> — <theme>` — version bump + changelog + regenerated artifacts
|
|
126
|
+
5. Tag the release commit (`release-and-publish` step 4 — the tag is created at release time, not at wrap-up)
|
|
125
127
|
|
|
126
|
-
The changelog carries the depth; the tag annotation covers every change at headline granularity — notable ones named, minor ones in one grouped bullet (per
|
|
128
|
+
The changelog carries the depth; the tag annotation covers every change at headline granularity — notable ones named, minor ones in one grouped bullet (per `release-and-publish` step 4). The commit split is about git history, not release notes.
|
|
127
129
|
|
|
128
130
|
**Version bump.** Default **patch** for bug-fix releases. **Minor** when enhancements are included.
|
|
129
131
|
|