@the-open-engine/zeroshot 6.7.1 → 6.7.2

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.
@@ -7,7 +7,7 @@ const { execFileSync } = require('child_process');
7
7
  const RELEASE_ORDER = ['patch', 'minor', 'major'];
8
8
  const REQUIRED_PLUGINS = [
9
9
  '@semantic-release/commit-analyzer',
10
- '@semantic-release/release-notes-generator',
10
+ './scripts/semantic-release-notes.js',
11
11
  '@semantic-release/npm',
12
12
  '@semantic-release/github',
13
13
  ];
@@ -50,7 +50,6 @@ function analyzeMessage(message) {
50
50
  if (breaking) return 'major';
51
51
 
52
52
  switch (type) {
53
- case 'release':
54
53
  case 'feat':
55
54
  return 'minor';
56
55
  case 'fix':
@@ -61,6 +60,14 @@ function analyzeMessage(message) {
61
60
  }
62
61
  }
63
62
 
63
+ function releaseTypeForMessages(messages) {
64
+ let releaseType = null;
65
+ for (const message of messages) {
66
+ releaseType = maxReleaseType(releaseType, analyzeMessage(message));
67
+ }
68
+ return releaseType;
69
+ }
70
+
64
71
  function getPluginNames(releaseConfig) {
65
72
  return (releaseConfig.plugins || []).map(normalizePlugin);
66
73
  }
@@ -68,9 +75,7 @@ function getPluginNames(releaseConfig) {
68
75
  function validateReleaseConfig(packageJson) {
69
76
  const releaseConfig = packageJson.release;
70
77
  if (!releaseConfig || typeof releaseConfig !== 'object') {
71
- throw new Error(
72
- 'package.json#release is required so it takes precedence over stale .releaserc files'
73
- );
78
+ throw new Error('package.json#release is required as the single release configuration');
74
79
  }
75
80
 
76
81
  const branches = Array.isArray(releaseConfig.branches)
@@ -103,6 +108,16 @@ function validateReleaseConfig(packageJson) {
103
108
  throw new Error('@semantic-release/npm must publish to npm');
104
109
  }
105
110
 
111
+ const analyzerPlugin = releaseConfig.plugins.find(
112
+ (plugin) => normalizePlugin(plugin) === '@semantic-release/commit-analyzer'
113
+ );
114
+ const analyzerOptions = Array.isArray(analyzerPlugin) ? analyzerPlugin[1] || {} : {};
115
+ if (Array.isArray(analyzerOptions.releaseRules) && analyzerOptions.releaseRules.length > 0) {
116
+ throw new Error(
117
+ '@semantic-release/commit-analyzer must use the standard conventional release rules'
118
+ );
119
+ }
120
+
106
121
  return pluginNames;
107
122
  }
108
123
 
@@ -186,10 +201,7 @@ async function prTitleFromMergeQueue() {
186
201
  async function releaseSignal() {
187
202
  const tag = latestReleaseTag();
188
203
  const messages = commitMessagesSince(tag);
189
- let releaseType = null;
190
- for (const message of messages) {
191
- releaseType = maxReleaseType(releaseType, analyzeMessage(message));
192
- }
204
+ let releaseType = releaseTypeForMessages(messages);
193
205
 
194
206
  let title = prTitleFromEvent();
195
207
  if (!title) title = await prTitleFromMergeQueue();
@@ -214,9 +226,8 @@ async function main() {
214
226
  if (signal.prTitle) console.log(`Release PR title: ${signal.prTitle}`);
215
227
 
216
228
  if (!signal.releaseType) {
217
- throw new Error(
218
- 'Release promotion would publish nothing; use a release-worthy PR title/commit'
219
- );
229
+ console.log('Release preflight passed: no publication expected');
230
+ return;
220
231
  }
221
232
 
222
233
  console.log(`Release preflight passed: ${signal.releaseType}`);
@@ -232,5 +243,6 @@ if (require.main === module) {
232
243
  module.exports = {
233
244
  analyzeMessage,
234
245
  maxReleaseType,
246
+ releaseTypeForMessages,
235
247
  validateReleaseConfig,
236
248
  };
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execFileSync, spawnSync } = require('child_process');
6
+
7
+ function run(command, args, options = {}) {
8
+ return execFileSync(command, args, {
9
+ encoding: 'utf8',
10
+ stdio: options.stdio || ['ignore', 'pipe', 'pipe'],
11
+ ...options,
12
+ }).trim();
13
+ }
14
+
15
+ function parseReleaseTag(tag) {
16
+ const match = String(tag || '').match(/^v(\d+\.\d+\.\d+)$/);
17
+ if (!match) throw new Error('release_tag must match vX.Y.Z');
18
+ return match[1];
19
+ }
20
+
21
+ function validateCommit(commit) {
22
+ if (!/^[0-9a-f]{40}$/.test(String(commit || ''))) {
23
+ throw new Error('release_commit must be a full lowercase commit SHA');
24
+ }
25
+ return commit;
26
+ }
27
+
28
+ function verifyImmutableSource(tag, commit) {
29
+ run('git', ['show-ref', '--verify', `refs/tags/${tag}`]);
30
+ const head = run('git', ['rev-parse', 'HEAD']);
31
+ const tagCommit = run('git', ['rev-list', '-n', '1', `refs/tags/${tag}`]);
32
+ if (head !== commit || tagCommit !== commit) {
33
+ throw new Error('checkout, protected tag, and release_commit must reference the same commit');
34
+ }
35
+ run('git', ['merge-base', '--is-ancestor', commit, 'origin/main']);
36
+ }
37
+
38
+ function npmPackageMetadata(name, version) {
39
+ const result = spawnSync(
40
+ 'npm',
41
+ ['view', `${name}@${version}`, 'version', 'gitHead', 'dist.attestations', '--json'],
42
+ { encoding: 'utf8' }
43
+ );
44
+ if (result.status === 0) return JSON.parse(result.stdout);
45
+ if (/\bE404\b|is not in this registry|No match found/i.test(result.stderr || '')) return null;
46
+ throw new Error(`npm registry lookup failed: ${(result.stderr || result.stdout).trim()}`);
47
+ }
48
+
49
+ function verifyExistingNpmVersion(metadata, version, commit) {
50
+ if (metadata.version !== version) {
51
+ throw new Error(`npm returned ${metadata.version}; expected ${version}`);
52
+ }
53
+ if (metadata.gitHead !== commit) {
54
+ throw new Error(`npm gitHead ${metadata.gitHead || '(missing)'} does not match ${commit}`);
55
+ }
56
+ if (!metadata['dist.attestations']?.provenance) {
57
+ throw new Error('npm package exists without a provenance attestation');
58
+ }
59
+ }
60
+
61
+ function recoverNpm(name, version, commit) {
62
+ const existing = npmPackageMetadata(name, version);
63
+ if (existing) {
64
+ verifyExistingNpmVersion(existing, version, commit);
65
+ console.log(`${name}@${version} already exists with matching provenance; nothing to recover`);
66
+ return;
67
+ }
68
+
69
+ run('npm', ['version', version, '--no-git-tag-version', '--allow-same-version']);
70
+ run('npm', ['publish', '--access', 'public', '--provenance'], { stdio: 'inherit' });
71
+
72
+ const published = npmPackageMetadata(name, version);
73
+ if (!published) throw new Error(`${name}@${version} was not visible after publication`);
74
+ verifyExistingNpmVersion(published, version, commit);
75
+ console.log(`Recovered ${name}@${version}`);
76
+ }
77
+
78
+ function releaseExists(tag) {
79
+ const result = spawnSync('gh', ['release', 'view', tag], { encoding: 'utf8' });
80
+ if (result.status === 0) return true;
81
+ if (/release not found|HTTP 404/i.test(`${result.stdout}\n${result.stderr}`)) return false;
82
+ throw new Error(`GitHub Release lookup failed: ${(result.stderr || result.stdout).trim()}`);
83
+ }
84
+
85
+ function recoverGithubRelease(tag, commit, version) {
86
+ if (releaseExists(tag)) {
87
+ console.log(`GitHub Release ${tag} already exists; nothing to recover`);
88
+ return;
89
+ }
90
+
91
+ const notesPath = path.join(process.cwd(), 'docs', 'releases', `${tag}.md`);
92
+ const args = ['release', 'create', tag, '--verify-tag', '--target', commit, '--title', tag];
93
+ if (fs.existsSync(notesPath)) {
94
+ args.push('--notes-file', notesPath);
95
+ } else {
96
+ args.push('--generate-notes');
97
+ const tags = run('git', [
98
+ 'tag',
99
+ '--list',
100
+ 'v[0-9]*',
101
+ '--merged',
102
+ `${commit}^`,
103
+ '--sort=-version:refname',
104
+ ])
105
+ .split(/\r?\n/)
106
+ .filter(Boolean);
107
+ if (tags[0]) args.push('--notes-start-tag', tags[0]);
108
+ }
109
+
110
+ run('gh', args, { stdio: 'inherit' });
111
+ if (!releaseExists(tag)) throw new Error(`GitHub Release ${tag} was not created`);
112
+ console.log(`Recovered GitHub Release ${tag} for ${version}`);
113
+ }
114
+
115
+ function main() {
116
+ const action = process.env.RECOVERY_ACTION;
117
+ if (!['recover-npm', 'recover-github-release'].includes(action)) {
118
+ throw new Error('RECOVERY_ACTION must be recover-npm or recover-github-release');
119
+ }
120
+
121
+ const tag = process.env.RELEASE_TAG;
122
+ const commit = validateCommit(process.env.RELEASE_COMMIT);
123
+ const version = parseReleaseTag(tag);
124
+ verifyImmutableSource(tag, commit);
125
+
126
+ const packageJson = require('../package.json');
127
+ if (action === 'recover-npm') {
128
+ recoverNpm(packageJson.name, version, commit);
129
+ } else {
130
+ recoverGithubRelease(tag, commit, version);
131
+ }
132
+ }
133
+
134
+ if (require.main === module) {
135
+ try {
136
+ main();
137
+ } catch (error) {
138
+ console.error(`Release recovery failed: ${error.message}`);
139
+ process.exit(1);
140
+ }
141
+ }
142
+
143
+ module.exports = {
144
+ npmPackageMetadata,
145
+ parseReleaseTag,
146
+ releaseExists,
147
+ validateCommit,
148
+ verifyExistingNpmVersion,
149
+ };
@@ -0,0 +1,44 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function curatedNotesPath(cwd, version) {
5
+ return path.join(cwd, 'docs', 'releases', `v${version}.md`);
6
+ }
7
+
8
+ function readCuratedNotes(cwd, version) {
9
+ const notesPath = curatedNotesPath(cwd, version);
10
+ if (!fs.existsSync(notesPath)) return null;
11
+
12
+ const notes = fs.readFileSync(notesPath, 'utf8').trim();
13
+ if (!notes) {
14
+ throw new Error(`Curated release notes are empty: ${notesPath}`);
15
+ }
16
+ return `${notes}\n`;
17
+ }
18
+
19
+ async function conventionalNotes(pluginConfig, context) {
20
+ const generator = await import('@semantic-release/release-notes-generator');
21
+ return generator.generateNotes(pluginConfig.conventional || {}, context);
22
+ }
23
+
24
+ async function generateNotesWithFallback(pluginConfig, context, fallback = conventionalNotes) {
25
+ const version = context.nextRelease?.version;
26
+ if (!version) throw new Error('nextRelease.version is required to generate release notes');
27
+
28
+ const curated = readCuratedNotes(context.cwd || process.cwd(), version);
29
+ if (curated) return curated;
30
+ const generated = await fallback(pluginConfig, context);
31
+ return generated;
32
+ }
33
+
34
+ async function generateNotes(pluginConfig, context) {
35
+ const notes = await generateNotesWithFallback(pluginConfig, context);
36
+ return notes;
37
+ }
38
+
39
+ module.exports = {
40
+ curatedNotesPath,
41
+ generateNotes,
42
+ generateNotesWithFallback,
43
+ readCuratedNotes,
44
+ };
@@ -1,75 +1,79 @@
1
1
  #!/bin/bash
2
- # Setup GitHub merge queue and branch protection for zeroshot
3
- # Run once after creating the repo or to update settings
4
2
 
5
- set -e
3
+ set -euo pipefail
6
4
 
7
5
  REPO="the-open-engine/zeroshot"
6
+ MAIN_RULESET_NAME="Protect main trunk"
7
+ TAG_RULESET_NAME="Make release tags immutable"
8
8
 
9
- echo "╔═══════════════════════════════════════════════════════════════════╗"
10
- echo "║ Setting up merge queue for $REPO ║"
11
- echo "╚═══════════════════════════════════════════════════════════════════╝"
12
- echo ""
13
-
14
- # Check gh is authenticated
15
9
  if ! gh auth status &>/dev/null; then
16
- echo "ERROR: Not authenticated with GitHub CLI"
17
- echo " Run: gh auth login"
10
+ echo "ERROR: Authenticate GitHub CLI with: gh auth login"
18
11
  exit 1
19
12
  fi
20
13
 
21
- # Check we have admin access
22
- if ! gh api "repos/$REPO" --jq '.permissions.admin' | grep -q true; then
23
- echo "❌ ERROR: You need admin access to $REPO"
14
+ if [[ "$(gh api "repos/$REPO" --jq '.permissions.admin')" != "true" ]]; then
15
+ echo "ERROR: Repository admin access is required"
24
16
  exit 1
25
17
  fi
26
18
 
27
- echo "✓ Authenticated with admin access"
28
- echo ""
29
-
30
- # ============================================================================
31
- # Configure 'dev' branch protection (merge target)
32
- # ============================================================================
33
-
34
- echo "→ Configuring 'dev' branch protection..."
35
-
36
- gh api --method PUT "repos/$REPO/branches/dev/protection" \
37
- --input - <<EOF
38
- {
39
- "required_status_checks": {
40
- "strict": true,
41
- "contexts": ["check", "install-matrix (ubuntu-latest, 20)", "install-matrix (macos-latest, 20)"]
42
- },
43
- "enforce_admins": false,
44
- "required_pull_request_reviews": null,
45
- "restrictions": null,
46
- "allow_force_pushes": false,
47
- "allow_deletions": false,
48
- "required_linear_history": true,
49
- "required_conversation_resolution": false
19
+ upsert_ruleset() {
20
+ local name="$1"
21
+ local payload="$2"
22
+ local id
23
+ id="$(gh api "repos/$REPO/rulesets" --jq ".[] | select(.name == \"$name\") | .id" | head -1)"
24
+ if [[ -n "$id" ]]; then
25
+ gh api --method PUT "repos/$REPO/rulesets/$id" --input "$payload" >/dev/null
26
+ else
27
+ gh api --method POST "repos/$REPO/rulesets" --input "$payload" >/dev/null
28
+ fi
50
29
  }
51
- EOF
52
-
53
- echo "✓ 'dev' branch protection configured"
54
30
 
55
- # Enable merge queue for dev branch
56
- echo "→ Enabling merge queue for 'dev' branch..."
31
+ main_payload="$(mktemp)"
32
+ tag_payload="$(mktemp)"
33
+ trap 'rm -f "$main_payload" "$tag_payload"' EXIT
57
34
 
58
- # Note: Merge queue requires GitHub Enterprise or public repos with Actions
59
- # Using the ruleset API which supports merge queue
60
- gh api --method POST "repos/$REPO/rulesets" \
61
- --input - <<EOF 2>/dev/null || echo " (ruleset may already exist)"
35
+ cat > "$main_payload" <<'JSON'
62
36
  {
63
- "name": "dev-merge-queue",
37
+ "name": "Protect main trunk",
64
38
  "target": "branch",
65
39
  "enforcement": "active",
66
40
  "conditions": {
67
41
  "ref_name": {
68
- "include": ["refs/heads/dev"],
42
+ "include": ["refs/heads/main"],
69
43
  "exclude": []
70
44
  }
71
45
  },
72
46
  "rules": [
47
+ {"type": "deletion"},
48
+ {"type": "non_fast_forward"},
49
+ {"type": "required_linear_history"},
50
+ {
51
+ "type": "pull_request",
52
+ "parameters": {
53
+ "required_approving_review_count": 0,
54
+ "dismiss_stale_reviews_on_push": true,
55
+ "required_reviewers": [],
56
+ "require_code_owner_review": false,
57
+ "dismissal_restriction": {
58
+ "enabled": false,
59
+ "allowed_actors": []
60
+ },
61
+ "require_last_push_approval": false,
62
+ "required_review_thread_resolution": true,
63
+ "allowed_merge_methods": ["squash"]
64
+ }
65
+ },
66
+ {
67
+ "type": "required_status_checks",
68
+ "parameters": {
69
+ "strict_required_status_checks_policy": true,
70
+ "do_not_enforce_on_create": false,
71
+ "required_status_checks": [
72
+ {"context": "required", "integration_id": 15368},
73
+ {"context": "semantic", "integration_id": 15368}
74
+ ]
75
+ }
76
+ },
73
77
  {
74
78
  "type": "merge_queue",
75
79
  "parameters": {
@@ -82,49 +86,34 @@ gh api --method POST "repos/$REPO/rulesets" \
82
86
  "min_entries_to_merge_wait_minutes": 1
83
87
  }
84
88
  }
85
- ]
89
+ ],
90
+ "bypass_actors": []
86
91
  }
87
- EOF
88
-
89
- echo "✓ Merge queue enabled for 'dev'"
92
+ JSON
90
93
 
91
- # ============================================================================
92
- # Configure 'main' branch protection (release branch)
93
- # ============================================================================
94
-
95
- echo "→ Configuring 'main' branch protection..."
96
-
97
- gh api --method PUT "repos/$REPO/branches/main/protection" \
98
- --input - <<EOF
94
+ cat > "$tag_payload" <<'JSON'
99
95
  {
100
- "required_status_checks": {
101
- "strict": true,
102
- "contexts": ["check", "install-matrix (ubuntu-latest, 20)", "install-matrix (macos-latest, 20)"]
103
- },
104
- "enforce_admins": false,
105
- "required_pull_request_reviews": {
106
- "required_approving_review_count": 1,
107
- "dismiss_stale_reviews": true,
108
- "require_code_owner_reviews": false
96
+ "name": "Make release tags immutable",
97
+ "target": "tag",
98
+ "enforcement": "active",
99
+ "conditions": {
100
+ "ref_name": {
101
+ "include": ["refs/tags/v*"],
102
+ "exclude": []
103
+ }
109
104
  },
110
- "restrictions": null,
111
- "allow_force_pushes": false,
112
- "allow_deletions": false,
113
- "required_linear_history": true,
114
- "required_conversation_resolution": false
105
+ "rules": [
106
+ {"type": "deletion"},
107
+ {"type": "non_fast_forward"}
108
+ ],
109
+ "bypass_actors": []
115
110
  }
116
- EOF
111
+ JSON
117
112
 
118
- echo " 'main' branch protection configured"
113
+ upsert_ruleset "$MAIN_RULESET_NAME" "$main_payload"
114
+ upsert_ruleset "$TAG_RULESET_NAME" "$tag_payload"
119
115
 
120
- # ============================================================================
121
- # Configure repository settings
122
- # ============================================================================
123
-
124
- echo "→ Configuring repository settings..."
125
-
126
- gh api --method PATCH "repos/$REPO" \
127
- --input - <<EOF
116
+ gh api --method PATCH "repos/$REPO" --input - <<'JSON' >/dev/null
128
117
  {
129
118
  "allow_squash_merge": true,
130
119
  "allow_merge_commit": false,
@@ -134,37 +123,34 @@ gh api --method PATCH "repos/$REPO" \
134
123
  "delete_branch_on_merge": true,
135
124
  "allow_auto_merge": true
136
125
  }
137
- EOF
138
-
139
- echo " Repository settings configured"
140
-
141
- # ============================================================================
142
- # Summary
143
- # ============================================================================
144
-
145
- echo ""
146
- echo "╔═══════════════════════════════════════════════════════════════════╗"
147
- echo "║ ✓ Merge queue setup complete! ║"
148
- echo "╚═══════════════════════════════════════════════════════════════════╝"
149
- echo ""
150
- echo "Workflow:"
151
- echo " feature-branch (local)"
152
- echo " ↓"
153
- echo " pre-push hook → lint + typecheck (~5s)"
154
- echo ""
155
- echo " push to origin/feature-branch"
156
- echo ""
157
- echo " gh pr create --base dev"
158
- echo " ↓"
159
- echo " CI runs tests on PR branch"
160
- echo " ↓"
161
- echo " gh pr merge --auto --squash enters merge queue"
162
- echo " ↓"
163
- echo " Queue rebases PR on latest dev + runs CI again"
164
- echo ""
165
- echo " Merge to dev (only if CI passes on rebased code)"
166
- echo ""
167
- echo "Release workflow:"
168
- echo " gh pr create --base main --head dev --title \"Release\""
169
- echo " → CI passes → merge → semantic-release publishes"
170
- echo ""
126
+ JSON
127
+
128
+ gh api --method PUT "repos/$REPO/environments/release" --input - <<'JSON' >/dev/null
129
+ {
130
+ "deployment_branch_policy": {
131
+ "protected_branches": false,
132
+ "custom_branch_policies": true
133
+ }
134
+ }
135
+ JSON
136
+
137
+ if ! gh api "repos/$REPO/environments/release/deployment-branch-policies" \
138
+ --jq '.branch_policies[] | select(.name == "main") | .id' | grep -q .; then
139
+ gh api --method POST "repos/$REPO/environments/release/deployment-branch-policies" \
140
+ -f name=main -f type=branch >/dev/null
141
+ fi
142
+
143
+ gh variable set RELEASE_AUTOMATION_ENABLED --repo "$REPO" --body false
144
+
145
+ if gh api "repos/$REPO/branches/main/protection" >/dev/null 2>&1; then
146
+ gh api --method DELETE "repos/$REPO/branches/main/protection"
147
+ fi
148
+
149
+ while IFS= read -r ruleset_id; do
150
+ [[ -n "$ruleset_id" ]] && gh api --method DELETE "repos/$REPO/rulesets/$ruleset_id"
151
+ done < <(
152
+ gh api "repos/$REPO/rulesets" \
153
+ --jq '.[] | select(.name == "dev-merge-queue" or .name == "main-merge-queue") | .id'
154
+ )
155
+
156
+ echo "Configured protected main trunk, merge queue, immutable release tags, and disabled release automation."