@josstei/maestro 1.6.4-rc.2 → 1.6.4-rc.3

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 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 toward GitHub Actions trusted publishing.
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, 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.2",
3
+ "version": "1.6.4-rc.3",
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",
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.6.4-rc.2"
2
+ "version": "1.6.4-rc.3"
3
3
  }
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 a Git tag, publishes the stable package to npm, then publishes a GitHub Release with CHANGELOG notes and the self-contained extension archive attached.
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
- B --> C["Resolve release PR from commit"]
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 --> I["Extract and validate version"]
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 PR from commit | Queries the GitHub API for PRs associated with the current commit SHA; filters for merged PRs targeting `main` with the `release` label. If none found, sets `is_release=false` and all subsequent steps are skipped. |
511
- | Setup Node.js | Conditional on `is_release=true`; Node.js 24 with npm registry URL for npm Trusted Publishing |
512
- | Extract and validate version | Reads version from `package.json` and cross-validates: the CHANGELOG must have a matching section (unconditional). 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
+ | 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 merge commit SHA; handles idempotency (skips if tag exists at same SHA, fails if tag exists at different SHA) |
520
- | Publish to npm | Publishes stable release through `node scripts/npm-publish-idempotent.js --access public` and GitHub Actions OIDC trusted publishing, skipping if the exact version already exists |
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`, `id-token: write`, `pull-requests: 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
- - Version validation cross-checks `package.json` against the CHANGELOG (unconditional) 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.
542
- - Stable releases require npm Trusted Publishing to be configured for `@josstei/maestro` with GitHub Actions workflow `release.yml`; the workflow does not use a long-lived npm token.
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`, `id-token: write`, `pull-requests: write` | None |
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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maestro",
3
- "version": "1.6.4-rc.2",
3
+ "version": "1.6.4-rc.3",
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.2",
3
+ "version": "1.6.4-rc.3",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maestro",
3
- "version": "1.6.4-rc.2",
3
+ "version": "1.6.4-rc.3",
4
4
  "description": "Generated Codex runtime for Maestro's multi-agent design, planning, execution, and review workflows.",
5
5
  "author": {
6
6
  "name": "josstei",
@@ -5,7 +5,7 @@
5
5
  "args": [
6
6
  "-y",
7
7
  "-p",
8
- "@josstei/maestro@1.6.4-rc.2",
8
+ "@josstei/maestro@1.6.4-rc.3",
9
9
  "maestro-mcp-server"
10
10
  ],
11
11
  "env": {
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.6.4-rc.2"
2
+ "version": "1.6.4-rc.3"
3
3
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maestro",
3
- "version": "1.6.4-rc.2",
3
+ "version": "1.6.4-rc.3",
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
  };