@cyanheads/mcp-ts-core 0.9.17 → 0.9.19
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 +3 -3
- package/CLAUDE.md +3 -3
- package/README.md +1 -1
- package/changelog/0.9.x/0.9.18.md +12 -0
- package/changelog/0.9.x/0.9.19.md +22 -0
- package/dist/core/context.d.ts +11 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +17 -4
- package/dist/core/context.js.map +1 -1
- package/dist/logs/combined.log +8 -8
- package/dist/logs/error.log +4 -4
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/dist/types-global/errors.d.ts +30 -5
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/requestContext.d.ts.map +1 -1
- package/dist/utils/internal/requestContext.js.map +1 -1
- package/dist/utils/network/retry.d.ts +15 -2
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/network/retry.js +20 -0
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/pagination/pagination.d.ts.map +1 -1
- package/dist/utils/pagination/pagination.js.map +1 -1
- package/package.json +4 -1
- package/scripts/check-skill-versions.ts +137 -0
- package/scripts/devcheck.ts +39 -0
- package/scripts/release-github.ts +187 -0
- package/skills/add-service/SKILL.md +2 -2
- package/skills/design-mcp-server/SKILL.md +2 -1
- package/skills/orchestrations/SKILL.md +2 -2
- package/skills/orchestrations/workflows/field-test-fix.md +9 -9
- package/skills/orchestrations/workflows/fix-wrapup-release.md +7 -7
- package/skills/orchestrations/workflows/greenfield-build.md +20 -20
- package/skills/orchestrations/workflows/maintenance-release.md +6 -6
- package/skills/release-and-publish/SKILL.md +21 -14
- package/templates/package.json +1 -0
|
@@ -34,9 +34,22 @@ function computeDelay(attempt, baseDelayMs, maxDelayMs, jitter) {
|
|
|
34
34
|
/**
|
|
35
35
|
* Default transient check: `McpError` with a transient code, or any non-McpError
|
|
36
36
|
* (network failures, unexpected throws) which are assumed transient.
|
|
37
|
+
*
|
|
38
|
+
* **Per-error opt-out.** When a thrown `McpError` carries `data.retryable === false`,
|
|
39
|
+
* the error is treated as non-transient and fails fast — even if its code is in
|
|
40
|
+
* `TRANSIENT_CODES`. This is the in-band escape hatch for deterministic upstream
|
|
41
|
+
* failures (e.g. a query too expensive to ever succeed surfaced as HTTP 200 +
|
|
42
|
+
* error body) that arrive with a transient code but should never be retried.
|
|
43
|
+
*
|
|
44
|
+
* Absent the flag (or when it is `true`), behavior is unchanged — code-based
|
|
45
|
+
* classification applies. Non-`McpError` throws (raw network errors, unexpected
|
|
46
|
+
* throws) are always assumed transient regardless of the flag.
|
|
37
47
|
*/
|
|
38
48
|
function defaultIsTransient(error) {
|
|
39
49
|
if (error instanceof McpError) {
|
|
50
|
+
// Explicit opt-out wins over code-based classification.
|
|
51
|
+
if (error.data?.retryable === false)
|
|
52
|
+
return false;
|
|
40
53
|
return TRANSIENT_CODES.has(error.code);
|
|
41
54
|
}
|
|
42
55
|
// Non-McpError (raw network errors, unexpected throws) — assume transient
|
|
@@ -72,6 +85,13 @@ function enrichExhaustedError(error, totalAttempts, operation) {
|
|
|
72
85
|
* parsing, and validation — not just the network call. This ensures that
|
|
73
86
|
* transient upstream errors (e.g., HTTP 200 with an error body) are retried.
|
|
74
87
|
*
|
|
88
|
+
* **Deterministic-failure opt-out.** When the thrown `McpError` carries
|
|
89
|
+
* `data.retryable === false`, the default predicate treats it as non-transient
|
|
90
|
+
* and fails immediately — even for `Timeout`/`ServiceUnavailable`/`RateLimited`
|
|
91
|
+
* codes. Use this at the throw site (or let the error contract auto-populate it
|
|
92
|
+
* via `ctx.fail`) for failures that can never succeed on retry (oversized query,
|
|
93
|
+
* malformed request surfaced as HTTP 200 + error body, etc.).
|
|
94
|
+
*
|
|
75
95
|
* When retries exhaust, the final error is enriched with attempt count in both
|
|
76
96
|
* the message and structured data, so callers know retries were already attempted.
|
|
77
97
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/utils/network/retry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAGpD;;;GAGG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAmB;IAChD,gBAAgB,CAAC,kBAAkB;IACnC,gBAAgB,CAAC,OAAO;IACxB,gBAAgB,CAAC,WAAW;CAC7B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/utils/network/retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../../src/utils/pagination/pagination.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../../src/utils/pagination/pagination.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IAEjF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,iCAAiC;IACjC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAc3D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,eAAe,CA6BrF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B,GAAG,MAAM,GAAG,SAAS,CAErB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EAAE,EACV,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,cAAc,GACtB,eAAe,CAAC,CAAC,CAAC,CAmCpB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB;IACpC,uCAAuC;;IAEvC,qCAAqC;;IAErC,qCAAqC;;CAE7B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../../src/utils/pagination/pagination.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../../../src/utils/pagination/pagination.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.19",
|
|
4
4
|
"mcpName": "io.github.cyanheads/mcp-ts-core",
|
|
5
5
|
"description": "Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.",
|
|
6
6
|
"main": "dist/core/index.js",
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
"scripts/build.ts",
|
|
13
13
|
"scripts/check-docs-sync.ts",
|
|
14
14
|
"scripts/check-framework-antipatterns.ts",
|
|
15
|
+
"scripts/check-skill-versions.ts",
|
|
15
16
|
"scripts/check-skills-sync.ts",
|
|
16
17
|
"scripts/clean.ts",
|
|
17
18
|
"scripts/devcheck.ts",
|
|
18
19
|
"scripts/lint-mcp.ts",
|
|
19
20
|
"scripts/lint-packaging.ts",
|
|
20
21
|
"scripts/list-skills.ts",
|
|
22
|
+
"scripts/release-github.ts",
|
|
21
23
|
"scripts/tree.ts",
|
|
22
24
|
"skills/",
|
|
23
25
|
"templates/",
|
|
@@ -160,6 +162,7 @@
|
|
|
160
162
|
"audit:refresh": "rm -f bun.lock && bun install && bun audit",
|
|
161
163
|
"changelog:build": "bun run scripts/build-changelog.ts",
|
|
162
164
|
"changelog:check": "bun run scripts/build-changelog.ts --check",
|
|
165
|
+
"release:github": "bun run scripts/release-github.ts",
|
|
163
166
|
"publish-mcp": "mcp-publisher login github -token \"$(security find-generic-password -a \"$USER\" -s mcp-publisher-github-pat -w)\" && mcp-publisher publish"
|
|
164
167
|
},
|
|
165
168
|
"resolutions": {
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Enforces the skill-versioning policy (#98 → #99): a change to a
|
|
4
|
+
* `skills/<name>/SKILL.md` body must bump `metadata.version` in the same edit.
|
|
5
|
+
* Documenting the policy made the expectation visible; this check makes it stick.
|
|
6
|
+
* The triggering incident was 7 missed bumps across 2 consecutive releases — the
|
|
7
|
+
* kind of low-salience checklist item that needs tooling, not vigilance.
|
|
8
|
+
*
|
|
9
|
+
* For each `skills/<name>/SKILL.md` that differs from `HEAD` (working tree, staged
|
|
10
|
+
* or not), it compares the frontmatter `metadata.version` and the body across
|
|
11
|
+
* `HEAD` → working tree. A changed body with an unchanged version is a violation.
|
|
12
|
+
* Whitespace-only body edits never trigger it (the policy's typo/whitespace
|
|
13
|
+
* carve-out); a genuine typo fix opts out via `devcheck.config.json`:
|
|
14
|
+
*
|
|
15
|
+
* {
|
|
16
|
+
* "skillVersions": {
|
|
17
|
+
* "ignore": ["add-tool", "api-linter/SKILL.md"]
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* A bare name (`add-tool`) and the file path (`add-tool/SKILL.md`) both match.
|
|
22
|
+
*
|
|
23
|
+
* Diffing against `HEAD` keeps the per-release-cycle carve-out automatic: once the
|
|
24
|
+
* version line is bumped, later commits in the same cycle no longer re-trigger.
|
|
25
|
+
* Severity mirrors `check-skills-sync.ts` — exits 1, demoted to a warning by
|
|
26
|
+
* devcheck. New skills (no `HEAD` version) and non-git trees are skipped.
|
|
27
|
+
*
|
|
28
|
+
* Runs standalone (`bun run scripts/check-skill-versions.ts`) and as a devcheck step.
|
|
29
|
+
*
|
|
30
|
+
* @module scripts/check-skill-versions
|
|
31
|
+
*/
|
|
32
|
+
import { spawnSync } from 'node:child_process';
|
|
33
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
34
|
+
import { resolve } from 'node:path';
|
|
35
|
+
import process from 'node:process';
|
|
36
|
+
|
|
37
|
+
const ROOT = resolve('.');
|
|
38
|
+
const SKILL_MD_RE = /^skills\/[^/]+\/SKILL\.md$/;
|
|
39
|
+
|
|
40
|
+
interface DevcheckConfig {
|
|
41
|
+
skillVersions?: { ignore?: string[] };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadIgnorePatterns(): string[] {
|
|
45
|
+
try {
|
|
46
|
+
const cfg = JSON.parse(
|
|
47
|
+
readFileSync(resolve(ROOT, 'devcheck.config.json'), 'utf-8'),
|
|
48
|
+
) as DevcheckConfig;
|
|
49
|
+
return cfg.skillVersions?.ignore ?? [];
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Match check-skills-sync semantics: full `<name>/SKILL.md` path or the bare `<name>`. */
|
|
56
|
+
function isIgnored(relPath: string, patterns: string[]): boolean {
|
|
57
|
+
const name = relPath.split('/')[1]; // skills/<name>/SKILL.md → <name>
|
|
58
|
+
return patterns.some(
|
|
59
|
+
(p) => p === relPath || p === name || (name !== undefined && p === `${name}/SKILL.md`),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Skill `SKILL.md` files that differ from `HEAD` (staged + unstaged). */
|
|
64
|
+
function changedSkillFiles(): string[] {
|
|
65
|
+
const result = spawnSync('git', ['diff', '--name-only', 'HEAD', '--'], { encoding: 'utf-8' });
|
|
66
|
+
if (result.status !== 0) return []; // not a git repo / no HEAD commit
|
|
67
|
+
return result.stdout
|
|
68
|
+
.trim()
|
|
69
|
+
.split('\n')
|
|
70
|
+
.filter((p) => SKILL_MD_RE.test(p));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Content of a path at `HEAD`, or null when it didn't exist there (new file). */
|
|
74
|
+
function headContent(relPath: string): string | null {
|
|
75
|
+
const result = spawnSync('git', ['show', `HEAD:${relPath}`], { encoding: 'utf-8' });
|
|
76
|
+
return result.status === 0 ? result.stdout : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** `metadata.version` from skill frontmatter, or null when absent/unparseable. */
|
|
80
|
+
function extractVersion(content: string): string | null {
|
|
81
|
+
const block = content.match(/^---\n([\s\S]*?)\n---/)?.[1];
|
|
82
|
+
if (!block) return null;
|
|
83
|
+
const version = block.match(/^\s*version:\s*["']?([^"'\n]+?)["']?\s*$/m)?.[1];
|
|
84
|
+
return version?.trim() ?? null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Body = everything after the frontmatter block. */
|
|
88
|
+
function extractBody(content: string): string {
|
|
89
|
+
const fm = content.match(/^---\n[\s\S]*?\n---/)?.[0];
|
|
90
|
+
return fm ? content.slice(fm.length) : content;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Whitespace-insensitive comparison (`git diff -w` style). */
|
|
94
|
+
function bodiesDiffer(a: string, b: string): boolean {
|
|
95
|
+
return a.replace(/\s+/g, '') !== b.replace(/\s+/g, '');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!existsSync(resolve(ROOT, 'skills'))) {
|
|
99
|
+
console.log('Skipped: no skills/ directory.');
|
|
100
|
+
process.exit(0);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const ignore = loadIgnorePatterns();
|
|
104
|
+
const changed = changedSkillFiles().filter((f) => !isIgnored(f, ignore));
|
|
105
|
+
|
|
106
|
+
const violations: { file: string; version: string }[] = [];
|
|
107
|
+
for (const file of changed) {
|
|
108
|
+
const oldContent = headContent(file);
|
|
109
|
+
if (oldContent === null) continue; // new skill — no prior version to compare
|
|
110
|
+
const newContent = readFileSync(resolve(ROOT, file), 'utf-8');
|
|
111
|
+
|
|
112
|
+
if (!bodiesDiffer(extractBody(oldContent), extractBody(newContent))) continue; // whitespace-only
|
|
113
|
+
|
|
114
|
+
const oldVersion = extractVersion(oldContent);
|
|
115
|
+
const newVersion = extractVersion(newContent);
|
|
116
|
+
if (oldVersion !== null && oldVersion === newVersion) {
|
|
117
|
+
violations.push({ file, version: oldVersion });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (violations.length === 0) {
|
|
122
|
+
console.log('Skill versions are in step with body changes.');
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const lines = [
|
|
127
|
+
`${violations.length} skill${violations.length === 1 ? '' : 's'} changed body without a metadata.version bump:`,
|
|
128
|
+
'',
|
|
129
|
+
];
|
|
130
|
+
for (const v of violations) {
|
|
131
|
+
lines.push(` - ${v.file} body changed but metadata.version is still "${v.version}"`);
|
|
132
|
+
}
|
|
133
|
+
lines.push('');
|
|
134
|
+
lines.push('Fix: bump metadata.version in the SKILL.md frontmatter, or add the skill to');
|
|
135
|
+
lines.push(' devcheck.config.json `skillVersions.ignore` for the typo/whitespace carve-out.');
|
|
136
|
+
console.log(lines.join('\n'));
|
|
137
|
+
process.exit(1);
|
package/scripts/devcheck.ts
CHANGED
|
@@ -242,6 +242,9 @@ interface DevcheckConfig {
|
|
|
242
242
|
skillsSync?: {
|
|
243
243
|
ignore?: string[];
|
|
244
244
|
};
|
|
245
|
+
skillVersions?: {
|
|
246
|
+
ignore?: string[];
|
|
247
|
+
};
|
|
245
248
|
}
|
|
246
249
|
|
|
247
250
|
function loadDevcheckConfig(rootDir: string): DevcheckConfig {
|
|
@@ -438,6 +441,21 @@ const ALL_CHECKS: Check[] = [
|
|
|
438
441
|
tip: (c) =>
|
|
439
442
|
`Remove the flagged SDK-coupling shortcut. See ${c.bold('scripts/check-framework-antipatterns.ts')} for rule rationale.`,
|
|
440
443
|
},
|
|
444
|
+
{
|
|
445
|
+
name: 'Open-Indexed Interfaces',
|
|
446
|
+
flag: '--no-open-index',
|
|
447
|
+
canFix: false,
|
|
448
|
+
// Framework-only AST check (#123): flags interfaces mixing named members with an
|
|
449
|
+
// open `[key: string]: unknown|any` index signature that lack an opt-out comment.
|
|
450
|
+
// Not shipped in package.json `files:`, so the existence guard skips it cleanly in
|
|
451
|
+
// consumer projects — the pattern is common and legitimate in consumer code.
|
|
452
|
+
getCommand: () => {
|
|
453
|
+
if (!existsSync(path.join(ROOT_DIR, 'scripts/audit-open-index-signatures.ts'))) return null;
|
|
454
|
+
return ['bun', 'run', 'scripts/audit-open-index-signatures.ts'];
|
|
455
|
+
},
|
|
456
|
+
tip: (c) =>
|
|
457
|
+
`Add ${c.bold('// allow open-indexed-named: <rationale>')} above the index signature, or use explicit fields. See ${c.bold('scripts/audit-open-index-signatures.ts')}.`,
|
|
458
|
+
},
|
|
441
459
|
{
|
|
442
460
|
name: 'Docs Sync',
|
|
443
461
|
flag: '--no-docs-sync',
|
|
@@ -470,6 +488,27 @@ const ALL_CHECKS: Check[] = [
|
|
|
470
488
|
tip: (c) =>
|
|
471
489
|
`Propagate ${c.bold('skills/')} to ${c.bold('.agents/skills/')} and ${c.bold('.claude/skills/')}, or add entries to ${c.bold('devcheck.config.json')} ${c.bold('skillsSync.ignore')}.`,
|
|
472
490
|
},
|
|
491
|
+
{
|
|
492
|
+
name: 'Skill Versions',
|
|
493
|
+
flag: '--no-skill-versions',
|
|
494
|
+
canFix: false,
|
|
495
|
+
// Flags skills/<name>/SKILL.md body changes (vs HEAD) that lack a metadata.version
|
|
496
|
+
// bump (#99). Skipped when skills/ is absent. Drift is demoted to a warning via
|
|
497
|
+
// isSuccess — the typo/whitespace carve-out lives in devcheck.config.json
|
|
498
|
+
// `skillVersions.ignore`.
|
|
499
|
+
getCommand: () => {
|
|
500
|
+
if (!existsSync(path.join(ROOT_DIR, 'skills'))) return null;
|
|
501
|
+
return ['bun', 'run', 'scripts/check-skill-versions.ts'];
|
|
502
|
+
},
|
|
503
|
+
isSuccess: (result) => {
|
|
504
|
+
if (result.exitCode === 0) return true;
|
|
505
|
+
const firstLine =
|
|
506
|
+
result.stdout.split('\n')[0]?.trim() || 'Skill bodies changed without a version bump.';
|
|
507
|
+
return { success: true, warning: firstLine };
|
|
508
|
+
},
|
|
509
|
+
tip: (c) =>
|
|
510
|
+
`Bump ${c.bold('metadata.version')} in the changed ${c.bold('SKILL.md')}, or add it to ${c.bold('devcheck.config.json')} ${c.bold('skillVersions.ignore')}.`,
|
|
511
|
+
},
|
|
473
512
|
{
|
|
474
513
|
name: 'Changelog Sync',
|
|
475
514
|
flag: '--no-changelog-sync',
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Create (or repair) a GitHub Release on the current package version's
|
|
4
|
+
* annotated tag, enforcing the `v<VERSION>: <tag subject>` title format that
|
|
5
|
+
* `--notes-from-tag` alone cannot set.
|
|
6
|
+
*
|
|
7
|
+
* What it does:
|
|
8
|
+
* 1. Reads `version` from `package.json`.
|
|
9
|
+
* 2. Derives the tag subject via `git for-each-ref refs/tags/v<version>`.
|
|
10
|
+
* 3. Runs `gh release create v<version> --verify-tag --notes-from-tag
|
|
11
|
+
* --title "v<version>: <subject>"` plus `dist/*.mcpb` when
|
|
12
|
+
* `manifest.json` exists (MCPB bundle attach).
|
|
13
|
+
* 4. On "release already exists" (re-invocation after partial run):
|
|
14
|
+
* - If `manifest.json` exists: `gh release upload v<version> --clobber dist/*.mcpb`
|
|
15
|
+
* to attach/replace the asset, then `gh release edit` to set the title.
|
|
16
|
+
* - Otherwise: `gh release edit` to set the title on the existing release.
|
|
17
|
+
*
|
|
18
|
+
* The framework itself has no `manifest.json`/`.mcpb`, so the attach path is
|
|
19
|
+
* skipped here but scaffolded servers that do have a manifest get the full flow.
|
|
20
|
+
*
|
|
21
|
+
* @module scripts/release-github
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Create a GitHub Release for the current package version:
|
|
25
|
+
* // bun run release:github
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Dry-run — print the command that would be executed without running it:
|
|
29
|
+
* // bun run release:github -- --dry-run
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { spawnSync } from 'node:child_process';
|
|
33
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
34
|
+
import { resolve } from 'node:path';
|
|
35
|
+
import process from 'node:process';
|
|
36
|
+
|
|
37
|
+
const DRY_RUN = process.argv.includes('--dry-run');
|
|
38
|
+
|
|
39
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run a command synchronously and return its trimmed stdout.
|
|
43
|
+
* Exits the process on non-zero exit when `required` is true.
|
|
44
|
+
*/
|
|
45
|
+
function run(
|
|
46
|
+
cmd: string,
|
|
47
|
+
args: string[],
|
|
48
|
+
options: { required?: boolean; capture?: boolean } = {},
|
|
49
|
+
): string {
|
|
50
|
+
const { required = true, capture = true } = options;
|
|
51
|
+
const result = spawnSync(cmd, args, {
|
|
52
|
+
encoding: 'utf-8',
|
|
53
|
+
stdio: capture ? ['ignore', 'pipe', 'pipe'] : ['inherit', 'inherit', 'pipe'],
|
|
54
|
+
});
|
|
55
|
+
const stdout = (result.stdout ?? '').trim();
|
|
56
|
+
const stderr = (result.stderr ?? '').trim();
|
|
57
|
+
|
|
58
|
+
if (result.error) {
|
|
59
|
+
console.error(`Failed to spawn '${cmd}': ${result.error.message}`);
|
|
60
|
+
if (required) process.exit(1);
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ((result.status ?? 1) !== 0) {
|
|
65
|
+
if (required) {
|
|
66
|
+
console.error(`Command failed: ${cmd} ${args.join(' ')}`);
|
|
67
|
+
if (stderr) console.error(stderr);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
// Return stderr concatenated so callers can inspect the failure reason.
|
|
71
|
+
return `__ERROR__:${stderr}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return stdout;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Run a `gh` CLI command.
|
|
79
|
+
* On "release already exists" the return value starts with `__ERROR__:`.
|
|
80
|
+
*/
|
|
81
|
+
function gh(args: string[], options: { required?: boolean } = {}): string {
|
|
82
|
+
return run('gh', args, { required: options.required ?? true, capture: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
function main(): void {
|
|
88
|
+
// 1. Read version from package.json
|
|
89
|
+
const pkgPath = resolve('package.json');
|
|
90
|
+
if (!existsSync(pkgPath)) {
|
|
91
|
+
console.error('package.json not found in current directory. Run from the project root.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version?: string };
|
|
96
|
+
const version = pkg.version?.trim();
|
|
97
|
+
if (!version) {
|
|
98
|
+
console.error('package.json has no version field.');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const tag = `v${version}`;
|
|
103
|
+
|
|
104
|
+
// 2. Derive tag subject via git for-each-ref
|
|
105
|
+
const subject = run('git', ['for-each-ref', `refs/tags/${tag}`, '--format=%(contents:subject)']);
|
|
106
|
+
|
|
107
|
+
if (!subject) {
|
|
108
|
+
console.error(
|
|
109
|
+
`Tag ${tag} not found locally or has no subject line. ` +
|
|
110
|
+
`Create the annotated tag first: git tag -a ${tag} -m "..."`,
|
|
111
|
+
);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const title = `${tag}: ${subject}`;
|
|
116
|
+
const hasMcpb = existsSync('manifest.json');
|
|
117
|
+
|
|
118
|
+
// 3. Build the gh release create command
|
|
119
|
+
const createArgs = [
|
|
120
|
+
'release',
|
|
121
|
+
'create',
|
|
122
|
+
tag,
|
|
123
|
+
'--verify-tag',
|
|
124
|
+
'--notes-from-tag',
|
|
125
|
+
'--title',
|
|
126
|
+
title,
|
|
127
|
+
];
|
|
128
|
+
if (hasMcpb) {
|
|
129
|
+
createArgs.push('dist/*.mcpb');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (DRY_RUN) {
|
|
133
|
+
console.log(`[dry-run] gh ${createArgs.join(' ')}`);
|
|
134
|
+
if (hasMcpb) {
|
|
135
|
+
console.log(
|
|
136
|
+
`[dry-run] fallback (if release exists): gh release upload ${tag} dist/*.mcpb --clobber`,
|
|
137
|
+
);
|
|
138
|
+
console.log(
|
|
139
|
+
`[dry-run] fallback (if release exists): gh release edit ${tag} --title "${title}"`,
|
|
140
|
+
);
|
|
141
|
+
} else {
|
|
142
|
+
console.log(
|
|
143
|
+
`[dry-run] fallback (if release exists): gh release edit ${tag} --title "${title}"`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(`Creating GitHub Release ${tag}…`);
|
|
150
|
+
console.log(` title: ${title}`);
|
|
151
|
+
if (hasMcpb) {
|
|
152
|
+
console.log(' asset: dist/*.mcpb');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 4. Try to create the release
|
|
156
|
+
const createResult = gh(createArgs, { required: false });
|
|
157
|
+
|
|
158
|
+
if (!createResult.startsWith('__ERROR__:')) {
|
|
159
|
+
// Success — print the release URL returned by gh
|
|
160
|
+
if (createResult) console.log(createResult);
|
|
161
|
+
console.log(`Release ${tag} created.`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const errText = createResult.slice('__ERROR__:'.length);
|
|
166
|
+
const alreadyExists = /release already exists/i.test(errText);
|
|
167
|
+
|
|
168
|
+
if (!alreadyExists) {
|
|
169
|
+
console.error(`gh release create failed:\n${errText}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 5. Release already exists — repair: upload asset (if applicable) and set title.
|
|
174
|
+
console.log(`Release ${tag} already exists. Repairing…`);
|
|
175
|
+
|
|
176
|
+
if (hasMcpb) {
|
|
177
|
+
console.log(' uploading dist/*.mcpb (--clobber)…');
|
|
178
|
+
gh(['release', 'upload', tag, 'dist/*.mcpb', '--clobber']);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log(` setting title: ${title}`);
|
|
182
|
+
gh(['release', 'edit', tag, '--title', title]);
|
|
183
|
+
|
|
184
|
+
console.log(`Release ${tag} repaired.`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main();
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Scaffold a new service integration. Use when the user asks to add a service, integrate an external API, or create a reusable domain module with its own initialization and state.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.7"
|
|
8
8
|
audience: external
|
|
9
9
|
type: reference
|
|
10
10
|
---
|
|
@@ -131,7 +131,7 @@ async fetchItem(id: string, ctx: Context): Promise<Item> {
|
|
|
131
131
|
|
|
132
132
|
1. **Calibrate backoff to the upstream.** 200–500ms for ephemeral failures, 1–2s for rate-limited APIs, 2–5s for service degradation. The default `baseDelayMs: 1000` suits most APIs.
|
|
133
133
|
2. **Check HTTP status before parsing.** `fetchWithTimeout` already throws on non-OK responses with granular status mapping (401→`Unauthorized`, 403→`Forbidden`, 404→`NotFound`, 408/425→`Timeout`, 422→`ValidationError`, 429→`RateLimited`, 5xx→`ServiceUnavailable`/`InternalError`) — this prevents feeding HTML error pages into XML/JSON parsers.
|
|
134
|
-
3. **Classify parse failures by content.** If the upstream returns HTTP 200 with an HTML error page, detect it and throw `ServiceUnavailable` (transient) instead of `SerializationError` (non-transient).
|
|
134
|
+
3. **Classify parse failures by content.** If the upstream returns HTTP 200 with an HTML error page, detect it and throw `ServiceUnavailable` (transient) instead of `SerializationError` (non-transient). **Exception — deterministic HTTP 200 errors fail fast, not transient.** Some upstreams return HTTP 200 with a structured error body for failures that will *never* succeed regardless of how many times you retry: a query too expensive for the server's budget, an oversized result set, or a malformed request the server rejects. Retrying these wastes upstream capacity and delays the client. Declare them in the contract with `retryable: false` (or pass `{ retryable: false }` in `data` at the throw site) — `withRetry`'s default predicate reads `error.data.retryable === false` and fails immediately, even for `Timeout`/`ServiceUnavailable` codes. `ctx.fail` auto-populates `data.retryable` from the contract entry, so declaring it once in `errors[]` is enough.
|
|
135
135
|
4. **Exhausted retries say so.** `withRetry` automatically enriches the final error with attempt count — callers know retries were already attempted.
|
|
136
136
|
|
|
137
137
|
### When you need finer-grained HTTP error classification
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Design the tool surface, resources, and service layer for a new MCP server. Use when starting a new server, planning a major feature expansion, or when the user describes a domain/API they want to expose via MCP. Produces a design doc at docs/design.md that drives implementation.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "2.
|
|
7
|
+
version: "2.15"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -350,6 +350,7 @@ output: z.object({
|
|
|
350
350
|
|
|
351
351
|
- **Truncate large output with counts.** When a list exceeds a reasonable display size, show the top N and append "...and X more". Don't silently drop results.
|
|
352
352
|
- **Spill big tabular results to a queryable surface.** When a tool's row set can exceed any reasonable context budget — paginated APIs, streamed exports, big query results — pair an inline preview with a `DataCanvas` table holding the full set, returned as a token the agent can SQL. Compute distributions or refinement hints across the full result, not the preview, so aggregate signal stays honest. See `api-canvas` for the `spillover()` helper.
|
|
353
|
+
- **Mirror a bulk upstream instead of paginating it live.** When the server wraps a large or slow API whose corpus is queried far more than it changes, sync it once into a persistent local index and query that as the primary data path — not the live API per request. Match the backend to corpus size: ≲ tens of thousands of rows → an in-memory index (server-level, no primitive); ~10⁴–10⁷ → the `MirrorService` (embedded SQLite + FTS5; declare a schema + a `sync` ingester via `defineMirror`/`sqliteMirrorStore`, then `runSync`/`query`, see `api-mirror`); ≳ 10⁸ → an external store. Distinct lifecycle from DataCanvas: a mirror is long-lived and cross-session, refreshed on a schedule; canvas is ephemeral and per-session.
|
|
353
354
|
- **`format()` is the markdown twin of `structuredContent` — make both content-complete.** Different MCP clients forward different surfaces to the model: some (e.g., Claude Code) read `structuredContent` from `output`, others (e.g., Claude Desktop) read `content[]` from `format()`. Both must carry the same data so every client sees the same picture — `format()` just dresses it up with markdown. A thin `format()` that returns only a count or title leaves `content[]`-only clients blind to data that `structuredContent` clients can see. Render all fields the LLM needs, with structured markdown (headers, bold labels, lists) for readability.
|
|
354
355
|
- **Agent-facing context must reach both client surfaces — put it in `enrichment`.** `structuredContent` (from `output`) and `content[]` (from `format()`) are read by different clients. Empty-result notices, the query/filter as the server parsed it, and pagination totals — the context the agent *reasons with*, distinct from the domain payload — reach only `content[]` if hand-authored into `format()` text alone, leaving `structuredContent`-only clients (Claude Code) blind. (The reverse can't happen: `format-parity` drags every `output` field into `format()`, so `output`-authored context already reaches both.) An `enrichment` block — the success-path counterpart to `errors[]`, populated via `ctx.enrich(...)` — reaches both automatically: merged into `structuredContent`, advertised as `output.extend(enrichment)`, mirrored into a `content[]` trailer, no `format()` entry needed. How each field renders in that trailer is a per-tool call — a kind-tag (`notice`/`total`/`echo`/`delta`) when a canonical form fits, a domain key like `totalFound` otherwise, and an `enrichmentTrailer.render` for any structured (object/array) field so it doesn't ship as a JSON blob. See `add-tool`'s **Tool Response Design**.
|
|
355
356
|
|
|
@@ -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.2"
|
|
8
8
|
audience: internal
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -157,7 +157,7 @@ For N targets in a phase:
|
|
|
157
157
|
3. Collect their reports
|
|
158
158
|
4. Verify with a read-only orchestrator check before advancing to the next phase
|
|
159
159
|
|
|
160
|
-
**Barriers only where gates sit.** Step 4's "advance to the next phase" implies a barrier — collect every target's phase-N result before any target starts phase N+1. That barrier is only required when a gate sits between the phases: a human decision (authorization, version-bump intent) or cross-target synthesis (the roll-up). Where no gate intervenes, a target may flow through consecutive phases independently — tier-3 platforms pipeline this for wall-clock, and even hand-spawned runs can let one sub-agent carry a target across adjacent gate-free phases. Keep the barrier at gate boundaries; drop it elsewhere.
|
|
160
|
+
**Barriers only where gates sit.** Step 4's "advance to the next phase" implies a barrier — collect every target's phase-N result before any target starts phase N+1. That barrier is only required when a gate sits between the phases: a human decision (authorization, version-bump intent) or cross-target synthesis (the roll-up). Where no gate intervenes, a target may flow through consecutive phases independently — tier-3 platforms pipeline this for wall-clock, and even hand-spawned runs can let one sub-agent carry a target across adjacent gate-free phases. Keep the barrier at gate boundaries; drop it elsewhere. Each workflow's phases table encodes this directly: the `Gate after` column marks every boundary as `barrier` (with a terse reason) or `gate-free` so the spawn/round structure is derivable without re-derivation.
|
|
161
161
|
|
|
162
162
|
### Editor / wrap-up separation
|
|
163
163
|
|
|
@@ -49,15 +49,15 @@ Per target:
|
|
|
49
49
|
|
|
50
50
|
Each phase's Objective column is the goal state per target — the verifiable end state the phase must produce.
|
|
51
51
|
|
|
52
|
-
| # | Phase | Objective | Sub-agent mode |
|
|
53
|
-
|
|
54
|
-
| 1 | Field-test | Per target: live tool/resource/prompt surface exercised across happy/error/edge paths; valid findings filed as GH issues against the server's own repo; noise filtered | parallel fanout per target; within a target, 1 or 3 sub-agents (see below) |
|
|
55
|
-
| 2 | Issue triage | Per-target GH issue count + severity breakdown reconciled against actual GH state | orchestrator (serial) |
|
|
56
|
-
| 3 | Fix | Per target: priority issues fixed in source, tests updated, `devcheck` + `test` green, each issue commented with fix details, working tree dirty for review | parallel fanout (one sub-agent per target — hard constraint) |
|
|
57
|
-
| 4 | Verify | Per target: full diff cold-reviewed; simplified if warranted; each fix re-exercised against the running server with actual tool output in the summary | parallel fanout |
|
|
58
|
-
| 5 | Loop decision | Orchestrator decision recorded — proceed to release, loop another field-test cycle, or pause/surface to user. Evidence-based | orchestrator (serial) |
|
|
59
|
-
| 6 | Wrap-up + release | (Optional) Per target: fixes split into per-file commits with a release commit on top; annotated tag; published per repo visibility; tag annotation is structured markdown with issue backlinks | parallel fanout (Bash git only) |
|
|
60
|
-
| 7 | Issue cleanup | Every GH issue that shipped a fix closed with "Fixed in v\<version\>" comment; skipped issues remain open | orchestrator (serial) |
|
|
52
|
+
| # | Phase | Objective | Sub-agent mode | Gate after |
|
|
53
|
+
|:--|:---|:---|:---|:---|
|
|
54
|
+
| 1 | Field-test | Per target: live tool/resource/prompt surface exercised across happy/error/edge paths; valid findings filed as GH issues against the server's own repo; noise filtered | parallel fanout per target; within a target, 1 or 3 sub-agents (see below) | **barrier** — cross-target synthesis: orchestrator reconciles all findings before filing triage |
|
|
55
|
+
| 2 | Issue triage | Per-target GH issue count + severity breakdown reconciled against actual GH state | orchestrator (serial) | gate-free |
|
|
56
|
+
| 3 | Fix | Per target: priority issues fixed in source, tests updated, `devcheck` + `test` green, each issue commented with fix details, working tree dirty for review | parallel fanout (one sub-agent per target — hard constraint) | gate-free |
|
|
57
|
+
| 4 | Verify | Per target: full diff cold-reviewed; simplified if warranted; each fix re-exercised against the running server with actual tool output in the summary | parallel fanout | **barrier** — orchestrator loop decision (human/evidence-based: proceed, loop, or surface to user) |
|
|
58
|
+
| 5 | Loop decision | Orchestrator decision recorded — proceed to release, loop another field-test cycle, or pause/surface to user. Evidence-based | orchestrator (serial) | **barrier** — release authorization required before advancing |
|
|
59
|
+
| 6 | Wrap-up + release | (Optional) Per target: fixes split into per-file commits with a release commit on top; annotated tag; published per repo visibility; tag annotation is structured markdown with issue backlinks | parallel fanout (Bash git only) | gate-free |
|
|
60
|
+
| 7 | Issue cleanup | Every GH issue that shipped a fix closed with "Fixed in v\<version\>" comment; skipped issues remain open | orchestrator (serial) | — |
|
|
61
61
|
|
|
62
62
|
Phase 6 is optional — stop earlier if release isn't authorized. Phase 7 only runs if Phase 6 ran.
|
|
63
63
|
|
|
@@ -57,13 +57,13 @@ Per target:
|
|
|
57
57
|
|
|
58
58
|
Each phase's Objective column is the goal state per target — the verifiable end state the phase must produce.
|
|
59
59
|
|
|
60
|
-
| # | Phase | Objective | Sub-agent mode |
|
|
61
|
-
|
|
62
|
-
| 1a | Validate (conditional) | Each handoff finding field-tested live; valid ones filed as GH issues; invalidated ones reported back with reason. If zero validate, workflow stops | one sub-agent per target |
|
|
63
|
-
| 1b | Fix | Per target: targeted issues fixed in source, tests updated/added, `devcheck` + `rebuild` + `test` green, each fixed issue commented with fix details, working tree dirty for review | parallel fanout (one sub-agent per target — hard constraint) |
|
|
64
|
-
| 2 | Verify | Per target: full diff cold-reviewed; simplified if warranted; each fix re-exercised against the running server with actual tool output in the summary | parallel fanout |
|
|
65
|
-
| 3 | Wrap-up + release | Per target: fixes split into per-file commits with a release commit on top; annotated tag; published per repo visibility; tag annotation is structured markdown with issue backlinks | parallel fanout (Bash git only) |
|
|
66
|
-
| 4 | Issue cleanup | Every GH issue that shipped a fix closed with "Fixed in v\<version\>" comment | orchestrator (serial) |
|
|
60
|
+
| # | Phase | Objective | Sub-agent mode | Gate after |
|
|
61
|
+
|:--|:---|:---|:---|:---|
|
|
62
|
+
| 1a | Validate (conditional) | Each handoff finding field-tested live; valid ones filed as GH issues; invalidated ones reported back with reason. If zero validate, workflow stops | one sub-agent per target | **barrier** — cross-target synthesis: orchestrator confirms validated findings before fix proceeds (or stops workflow if zero validate) |
|
|
63
|
+
| 1b | Fix | Per target: targeted issues fixed in source, tests updated/added, `devcheck` + `rebuild` + `test` green, each fixed issue commented with fix details, working tree dirty for review | parallel fanout (one sub-agent per target — hard constraint) | **barrier** — orchestrator reviews diffs before verify (explicit gate in checklist) |
|
|
64
|
+
| 2 | Verify | Per target: full diff cold-reviewed; simplified if warranted; each fix re-exercised against the running server with actual tool output in the summary | parallel fanout | **barrier** — orchestrator reviews simplified diff and verified outputs; release authorization required |
|
|
65
|
+
| 3 | Wrap-up + release | Per target: fixes split into per-file commits with a release commit on top; annotated tag; published per repo visibility; tag annotation is structured markdown with issue backlinks | parallel fanout (Bash git only) | gate-free |
|
|
66
|
+
| 4 | Issue cleanup | Every GH issue that shipped a fix closed with "Fixed in v\<version\>" comment | orchestrator (serial) | — |
|
|
67
67
|
|
|
68
68
|
Phase 1a is conditional — only runs when the input is a handoff document or otherwise unvalidated. When the input is already tracked GH issues, skip directly to Phase 1b. The release portion of Phase 3 is conditional on user authorization to ship.
|
|
69
69
|
|