@josstei/maestro 1.6.4-rc.2 → 1.6.4-rc.4
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/CHANGELOG.md +2 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/src/version.json +1 -1
- package/docs/cicd.md +24 -13
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugins/maestro/.codex-plugin/plugin.json +1 -1
- package/plugins/maestro/.mcp.json +1 -1
- package/plugins/maestro/src/version.json +1 -1
- package/qwen-extension.json +1 -1
- package/scripts/npm-publish-idempotent.js +153 -0
package/CHANGELOG.md
CHANGED
|
@@ -15,10 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
|
|
16
16
|
### Changed
|
|
17
17
|
|
|
18
|
-
- **npm package identity**: renamed the planned npm package to `@josstei/maestro`, added `hello@josstei.dev` to public author metadata, and moved the stable release publish path
|
|
18
|
+
- **npm package identity**: renamed the planned npm package to `@josstei/maestro`, added `hello@josstei.dev` to public author metadata, and moved the stable release publish path into GitHub Actions with npm token authentication.
|
|
19
19
|
|
|
20
20
|
### Fixed
|
|
21
21
|
|
|
22
|
+
- **Stable npm release recovery**: Release now uses `NPM_TOKEN` for stable publishes, uses verified Git tag lookups, supports manual recovery from an existing `vX.Y.Z` tag and target SHA, and enforces a stable-only `latest` dist-tag through the idempotent npm publish helper.
|
|
22
23
|
- **Codex plugin MCP server fails to start**: corrected `npx` args in `plugins/maestro/.mcp.json` — added `-p`/`--package` flag so `maestro-mcp-server` is resolved as the binary name rather than an argument to the package's default binary.
|
|
23
24
|
- **Release metadata drift**: runtime manifests, marketplace entries, detached payload versions, and Codex MCP package specs are now generated from `package.json` so stable and prerelease packages stay self-consistent.
|
|
24
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maestro",
|
|
3
|
-
"version": "1.6.4-rc.
|
|
3
|
+
"version": "1.6.4-rc.4",
|
|
4
4
|
"description": "Multi-agent development orchestration platform — 39 specialists, 4-phase orchestration, native parallel subagents, persistent sessions, and standalone review/debug/security/perf/seo/a11y/compliance commands",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "josstei",
|
package/claude/src/version.json
CHANGED
package/docs/cicd.md
CHANGED
|
@@ -468,13 +468,14 @@ Publishes `@josstei/maestro@X.Y.Z-rc.N` to npm with the `rc` dist-tag, where `N`
|
|
|
468
468
|
|
|
469
469
|
### Purpose
|
|
470
470
|
|
|
471
|
-
The final step of the release pipeline. Triggers on any push to `main`, but only acts when the push is a merged pull request carrying the `release` label. Validates package and archive contents, creates
|
|
471
|
+
The final step of the release pipeline. Triggers on any push to `main`, but only acts when the push is a merged pull request carrying the `release` label. It can also be manually dispatched to recover a failed post-tag stable publish. Validates package and archive contents, creates or reuses the matching Git tag, publishes the stable package to npm, then publishes a GitHub Release with CHANGELOG notes and the self-contained extension archive attached.
|
|
472
472
|
|
|
473
473
|
### Trigger
|
|
474
474
|
|
|
475
475
|
| Event | Details |
|
|
476
476
|
|-------|---------|
|
|
477
477
|
| `push` | Branch: `main` |
|
|
478
|
+
| `workflow_dispatch` | Manual stable recovery with `version` and optional `target_sha`; when `target_sha` is omitted, the workflow checks out `refs/tags/v<version>` |
|
|
478
479
|
| **Condition** | The pushed commit must be the merge commit of a PR labeled `release` that targeted `main` |
|
|
479
480
|
|
|
480
481
|
### Flow
|
|
@@ -482,11 +483,14 @@ The final step of the release pipeline. Triggers on any push to `main`, but only
|
|
|
482
483
|
```mermaid
|
|
483
484
|
graph TD
|
|
484
485
|
A["Push to main"] --> B["Checkout with full history"]
|
|
485
|
-
|
|
486
|
+
AA["Manual recovery<br/>version + target_sha"] --> BB["Checkout target SHA<br/>or refs/tags/vX.Y.Z"]
|
|
487
|
+
B --> C["Resolve release context"]
|
|
488
|
+
BB --> C
|
|
486
489
|
C --> D{"Merged PR with<br/>'release' label found?"}
|
|
487
490
|
D --> |"No"| E["Skip: not a release commit"]
|
|
488
491
|
D --> |"Yes"| F["Setup Node.js 24 with npm registry"]
|
|
489
|
-
F -->
|
|
492
|
+
F --> G["Verify NPM_TOKEN"]
|
|
493
|
+
G --> I["Extract and validate version,<br/>target SHA, and tag"]
|
|
490
494
|
I --> J["Generate runtime adapters"]
|
|
491
495
|
J --> K{"Adapter drift?"}
|
|
492
496
|
K --> |"Yes"| L["Fail: adapters out of sync"]
|
|
@@ -506,18 +510,19 @@ graph TD
|
|
|
506
510
|
|
|
507
511
|
| Step | Description |
|
|
508
512
|
|------|-------------|
|
|
509
|
-
| Checkout | Full history (`fetch-depth: 0`) for tag operations |
|
|
510
|
-
| Resolve release
|
|
511
|
-
| Setup Node.js | Conditional on `is_release=true`; Node.js 24 with npm registry URL
|
|
512
|
-
|
|
|
513
|
+
| Checkout | Full history (`fetch-depth: 0`) for tag operations. Push releases check out the pushed commit; manual recovery checks out `target_sha` or `refs/tags/v<version>`. |
|
|
514
|
+
| Resolve release context | Push releases query the GitHub API for PRs associated with the current commit SHA and filter for merged PRs targeting `main` with the `release` label. Manual recovery sets the release context from the supplied version. If no push release is found, sets `is_release=false` and all subsequent steps are skipped. |
|
|
515
|
+
| Setup Node.js | Conditional on `is_release=true`; Node.js 24 with npm registry URL |
|
|
516
|
+
| Verify npm token | Fails before tag or publish work unless `NPM_TOKEN` is configured |
|
|
517
|
+
| Extract and validate version | Reads version from `package.json` and cross-validates: the version must be stable semver, the CHANGELOG must have a matching section (unconditional), any existing `vX.Y.Z` tag must point at the checked-out target SHA, and manual recovery must match both the requested `version` and `target_sha` when supplied. When the release branch name matches `release/vX.Y.Z` and the PR title matches `release: vX.Y.Z`, their embedded versions must agree with `package.json`. |
|
|
513
518
|
| Generate runtime adapters | Runs `node scripts/generate.js` |
|
|
514
519
|
| Check adapter drift | Final drift check before release; fails with error annotation |
|
|
515
520
|
| Run full test suite | Final test gate before release |
|
|
516
521
|
| Verify npm package contents | Runs `npm run pack:verify` before any tag or publish operation |
|
|
517
522
|
| Package release artifact | Runs `npm run release:artifacts` to create `dist/release/maestro-vX.Y.Z-extension.tar.gz` |
|
|
518
523
|
| Verify release artifact | Runs `npm run release:verify-artifacts` against the generated archive |
|
|
519
|
-
| Create and push tag | Creates Git tag `vX.Y.Z` at the
|
|
520
|
-
| Publish to npm | Publishes stable release through `node scripts/npm-publish-idempotent.js --access public`
|
|
524
|
+
| Create and push tag | Creates Git tag `vX.Y.Z` at the checked-out target SHA; handles idempotency (skips if tag exists at same SHA, fails if tag exists at different SHA) |
|
|
525
|
+
| Publish to npm | Publishes stable release through `node scripts/npm-publish-idempotent.js --access public` with `NODE_AUTH_TOKEN` derived from `NPM_TOKEN`, skipping if the exact version already exists |
|
|
521
526
|
| Extract changelog | Extracts the version-specific section from `CHANGELOG.md` using `awk` |
|
|
522
527
|
| Create GitHub Release | Uses `softprops/action-gh-release` (pinned to SHA `c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda`, v2.2.1) with CHANGELOG excerpt as body and the generic extension archive attached |
|
|
523
528
|
|
|
@@ -526,8 +531,10 @@ graph TD
|
|
|
526
531
|
| Item | Type | Purpose |
|
|
527
532
|
|------|------|---------|
|
|
528
533
|
| `GH_TOKEN` | Env (derived) | Set to `${{ github.token }}` for PR creation and GitHub API calls |
|
|
534
|
+
| `NPM_TOKEN` | Secret | npm registry authentication for stable publish and dist-tag repair |
|
|
535
|
+
| `NODE_AUTH_TOKEN` | Env (derived) | Set to `$NPM_TOKEN` for npm CLI |
|
|
529
536
|
|
|
530
|
-
**Permissions**: `contents: write`, `
|
|
537
|
+
**Permissions**: `contents: write`, `pull-requests: write`
|
|
531
538
|
|
|
532
539
|
### Artifacts
|
|
533
540
|
|
|
@@ -538,8 +545,10 @@ graph TD
|
|
|
538
545
|
### Key Behaviors
|
|
539
546
|
|
|
540
547
|
- The release detection uses the GitHub API to find the PR associated with the merge commit, filtering for the `release` label. Non-release pushes to `main` exit early and cleanly.
|
|
541
|
-
-
|
|
542
|
-
-
|
|
548
|
+
- Manual recovery is only for stable versions and requires the checked-out commit, `package.json`, `CHANGELOG.md`, and existing `vX.Y.Z` tag to agree. This recovers failures after tag creation without creating a new version or publishing from a maintainer laptop.
|
|
549
|
+
- Version validation cross-checks `package.json` against the CHANGELOG (unconditional), the existing tag when present, and, when applicable, against the release branch name (`release/vX.Y.Z`) and the PR title (`release: vX.Y.Z`). A mismatch in any available source fails the workflow.
|
|
550
|
+
- Stable releases require `NPM_TOKEN`; npm Trusted Publishing should only replace it after the package owner explicitly configures and tests that path.
|
|
551
|
+
- npm `latest` is stable-only. The publish helper rejects prereleases published without an explicit prerelease tag or with `latest`, rejects stable publishes with prerelease dist-tags, removes a stale `latest` tag when it points at a prerelease and no stable exists, and moves `latest` to the stable version after a stable publish or idempotent skip.
|
|
543
552
|
- Tag creation is idempotent: if the tag already exists at the same commit, the step is skipped. If it exists at a different commit, the workflow fails to prevent overwriting a release.
|
|
544
553
|
|
|
545
554
|
---
|
|
@@ -633,7 +642,7 @@ graph LR
|
|
|
633
642
|
| Preview Build | `contents: read`, `pull-requests: write` | `NPM_TOKEN` |
|
|
634
643
|
| Prepare Release | `contents: write`, `pull-requests: write` | `RELEASE_TOKEN` |
|
|
635
644
|
| Release Candidate | `contents: read`, `pull-requests: write` | `NPM_TOKEN` |
|
|
636
|
-
| Release | `contents: write`, `
|
|
645
|
+
| Release | `contents: write`, `pull-requests: write` | `NPM_TOKEN` |
|
|
637
646
|
|
|
638
647
|
The `RELEASE_TOKEN` used by Prepare Release is a personal access token with elevated permissions. The default `GITHUB_TOKEN` does not trigger downstream workflow runs, so `RELEASE_TOKEN` is required for branch pushes and PR creation that need to activate Source Of Truth Check and Release Candidate on the newly created PR.
|
|
639
648
|
|
|
@@ -645,3 +654,5 @@ The `RELEASE_TOKEN` used by Prepare Release is a personal access token with elev
|
|
|
645
654
|
| `rc` | Release candidate from release PR | `X.Y.Z-rc.N` | Release Candidate |
|
|
646
655
|
| `preview` | PR preview build | `X.Y.Z-preview.SHORT_SHA` | Preview Build |
|
|
647
656
|
| `nightly` | Daily main snapshot | `X.Y.Z-nightly.YYYYMMDD` | Nightly Build |
|
|
657
|
+
|
|
658
|
+
`latest` must never point at `rc`, `preview`, or `nightly`. If a prerelease publish or idempotent skip sees `latest` pointing to a prerelease, the helper repairs it by moving `latest` back to the highest published stable version or removing it when no stable exists.
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maestro",
|
|
3
|
-
"version": "1.6.4-rc.
|
|
3
|
+
"version": "1.6.4-rc.4",
|
|
4
4
|
"description": "Multi-agent development orchestration platform — 39 specialists, 4-phase orchestration, native parallel subagents, persistent sessions, and standalone review/debug/security/perf/seo/a11y/compliance commands",
|
|
5
5
|
"contextFileName": "GEMINI.md",
|
|
6
6
|
"settings": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josstei/maestro",
|
|
3
|
-
"version": "1.6.4-rc.
|
|
3
|
+
"version": "1.6.4-rc.4",
|
|
4
4
|
"description": "Multi-agent development orchestration platform — 39 specialists, 4-phase workflows, 4 runtime targets (Gemini CLI, Claude Code, OpenAI Codex, Qwen Code)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": {
|
package/qwen-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maestro",
|
|
3
|
-
"version": "1.6.4-rc.
|
|
3
|
+
"version": "1.6.4-rc.4",
|
|
4
4
|
"description": "Multi-agent development orchestration platform — 39 specialists, 4-phase orchestration, native parallel subagents, persistent sessions, and standalone review/debug/security/perf/seo/a11y/compliance commands",
|
|
5
5
|
"contextFileName": "QWEN.md",
|
|
6
6
|
"settings": [
|
|
@@ -6,6 +6,7 @@ const path = require('node:path');
|
|
|
6
6
|
const { execFileSync } = require('node:child_process');
|
|
7
7
|
|
|
8
8
|
const ROOT = path.resolve(__dirname, '..');
|
|
9
|
+
const PRERELEASE_TAGS = new Set(['rc', 'preview', 'nightly']);
|
|
9
10
|
|
|
10
11
|
function printHelp() {
|
|
11
12
|
console.log(`Publish a Maestro npm package if the exact version is absent.
|
|
@@ -79,6 +80,68 @@ function isNpmNotFoundError(error) {
|
|
|
79
80
|
return /\bE404\b|404 Not Found|is not in this registry/i.test(text);
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
function isPrereleaseVersion(version) {
|
|
84
|
+
return /^[0-9]+\.[0-9]+\.[0-9]+-.+/.test(version);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isStableVersion(version) {
|
|
88
|
+
return /^[0-9]+\.[0-9]+\.[0-9]+$/.test(version);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function compareStableVersions(left, right) {
|
|
92
|
+
const leftParts = left.split('.').map((part) => Number.parseInt(part, 10));
|
|
93
|
+
const rightParts = right.split('.').map((part) => Number.parseInt(part, 10));
|
|
94
|
+
|
|
95
|
+
for (let index = 0; index < 3; index += 1) {
|
|
96
|
+
if (leftParts[index] !== rightParts[index]) {
|
|
97
|
+
return leftParts[index] - rightParts[index];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function highestStableVersion(versions) {
|
|
105
|
+
return versions
|
|
106
|
+
.filter(isStableVersion)
|
|
107
|
+
.sort(compareStableVersions)
|
|
108
|
+
.at(-1) || null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseDistTagOutput(output) {
|
|
112
|
+
const tags = {};
|
|
113
|
+
const text = Buffer.isBuffer(output) ? output.toString('utf8') : String(output || '');
|
|
114
|
+
|
|
115
|
+
for (const line of text.split(/\r?\n/)) {
|
|
116
|
+
const match = line.match(/^([^:\s]+):\s*(\S+)\s*$/);
|
|
117
|
+
if (match) {
|
|
118
|
+
tags[match[1]] = match[2];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return tags;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseVersionsOutput(output) {
|
|
126
|
+
const text = Buffer.isBuffer(output) ? output.toString('utf8') : String(output || '');
|
|
127
|
+
const trimmed = text.trim();
|
|
128
|
+
|
|
129
|
+
if (!trimmed) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const parsed = JSON.parse(trimmed);
|
|
134
|
+
if (Array.isArray(parsed)) {
|
|
135
|
+
return parsed;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (typeof parsed === 'string') {
|
|
139
|
+
return [parsed];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
82
145
|
function packageVersionExists(packageSpec, runner) {
|
|
83
146
|
try {
|
|
84
147
|
const stdout = runner('npm', ['view', packageSpec, 'version'], {
|
|
@@ -95,13 +158,92 @@ function packageVersionExists(packageSpec, runner) {
|
|
|
95
158
|
}
|
|
96
159
|
}
|
|
97
160
|
|
|
161
|
+
function getDistTags(packageName, runner) {
|
|
162
|
+
try {
|
|
163
|
+
const stdout = runner('npm', ['dist-tag', 'ls', packageName], {
|
|
164
|
+
encoding: 'utf8',
|
|
165
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
166
|
+
});
|
|
167
|
+
return parseDistTagOutput(stdout);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (isNpmNotFoundError(error)) {
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getPublishedVersions(packageName, runner) {
|
|
178
|
+
try {
|
|
179
|
+
const stdout = runner('npm', ['view', packageName, 'versions', '--json'], {
|
|
180
|
+
encoding: 'utf8',
|
|
181
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
182
|
+
});
|
|
183
|
+
return parseVersionsOutput(stdout);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (isNpmNotFoundError(error)) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function validatePublishTag(pkg, tag) {
|
|
194
|
+
const prerelease = isPrereleaseVersion(pkg.version);
|
|
195
|
+
|
|
196
|
+
if (prerelease && (!tag || tag === 'latest')) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Refusing to publish prerelease ${pkg.name}@${pkg.version} with the latest tag; use rc, preview, or nightly.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!prerelease && PRERELEASE_TAGS.has(tag)) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Refusing to publish stable ${pkg.name}@${pkg.version} with prerelease tag "${tag}".`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function ensureLatestTagPolicy(pkg, runner) {
|
|
210
|
+
const tags = getDistTags(pkg.name, runner);
|
|
211
|
+
const latest = tags.latest;
|
|
212
|
+
|
|
213
|
+
if (isPrereleaseVersion(pkg.version)) {
|
|
214
|
+
if (!latest || !isPrereleaseVersion(latest)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const stableVersion = highestStableVersion(getPublishedVersions(pkg.name, runner));
|
|
219
|
+
if (stableVersion) {
|
|
220
|
+
runner('npm', ['dist-tag', 'add', `${pkg.name}@${stableVersion}`, 'latest'], {
|
|
221
|
+
stdio: 'inherit',
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
runner('npm', ['dist-tag', 'rm', pkg.name, 'latest'], {
|
|
225
|
+
stdio: 'inherit',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (latest !== pkg.version) {
|
|
232
|
+
runner('npm', ['dist-tag', 'add', `${pkg.name}@${pkg.version}`, 'latest'], {
|
|
233
|
+
stdio: 'inherit',
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
98
238
|
function publishIfNeeded(options = {}) {
|
|
99
239
|
const root = options.root || ROOT;
|
|
100
240
|
const runner = options.execFileSync || execFileSync;
|
|
101
241
|
const pkg = readPackage(root);
|
|
102
242
|
const packageSpec = `${pkg.name}@${pkg.version}`;
|
|
243
|
+
validatePublishTag(pkg, options.tag);
|
|
103
244
|
|
|
104
245
|
if (packageVersionExists(packageSpec, runner)) {
|
|
246
|
+
ensureLatestTagPolicy(pkg, runner);
|
|
105
247
|
return {
|
|
106
248
|
packageSpec,
|
|
107
249
|
published: false,
|
|
@@ -119,6 +261,8 @@ function publishIfNeeded(options = {}) {
|
|
|
119
261
|
stdio: 'inherit',
|
|
120
262
|
});
|
|
121
263
|
|
|
264
|
+
ensureLatestTagPolicy(pkg, runner);
|
|
265
|
+
|
|
122
266
|
return {
|
|
123
267
|
packageSpec,
|
|
124
268
|
published: true,
|
|
@@ -142,9 +286,18 @@ if (require.main === module) {
|
|
|
142
286
|
}
|
|
143
287
|
|
|
144
288
|
module.exports = {
|
|
289
|
+
ensureLatestTagPolicy,
|
|
290
|
+
getDistTags,
|
|
291
|
+
getPublishedVersions,
|
|
292
|
+
highestStableVersion,
|
|
145
293
|
isNpmNotFoundError,
|
|
294
|
+
isPrereleaseVersion,
|
|
295
|
+
isStableVersion,
|
|
146
296
|
packageVersionExists,
|
|
297
|
+
parseDistTagOutput,
|
|
147
298
|
parseArgs,
|
|
299
|
+
parseVersionsOutput,
|
|
148
300
|
publishIfNeeded,
|
|
149
301
|
readPackage,
|
|
302
|
+
validatePublishTag,
|
|
150
303
|
};
|