skillgauntlet 1.3.7 → 1.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/i18n.js +3 -0
- package/lib/submit.js +84 -23
- package/package.json +1 -1
package/lib/i18n.js
CHANGED
|
@@ -275,6 +275,7 @@ const MESSAGES = {
|
|
|
275
275
|
submitPrOpened: 'PR 已开:{url}',
|
|
276
276
|
submitNext: '接下来:机器人要求时签 CLA,然后等 PR 里的 CI 留言。留言会写通过,或者精确写出缺什么。合并由维护者操作。',
|
|
277
277
|
submitGhFail: 'GitHub 步骤失败:{msg}',
|
|
278
|
+
submitRetry: '这看起来是 GitHub 的临时故障,已经打包好的内容不会丢。现在重试吗?',
|
|
278
279
|
modelRemap: '后端 {host} 会静默改写 Anthropic 模型名,"{model}" 测的不是你指定的模型;请传该后端自己的模型名',
|
|
279
280
|
hostMissing: '没有找到宿主 {host}(PATH 里没有 {bin})。{how};也可以重新运行并选择其他宿主(Claude Code、CodeBuddy、Codewhale、Codex)。',
|
|
280
281
|
installCodebuddy: '安装方法:npm install -g @tencent-ai/codebuddy-code',
|
|
@@ -709,6 +710,7 @@ Layer 0 always runs first: it is free, and it disqualifies most problems before
|
|
|
709
710
|
submitPrOpened: 'Pull request opened: {url}',
|
|
710
711
|
submitNext: 'Next: sign the CLA if the bot asks, then wait for the CI comment on the pull request. It says pass, or exactly what is missing. A maintainer does the merging.',
|
|
711
712
|
submitGhFail: 'GitHub step failed: {msg}',
|
|
713
|
+
submitRetry: 'This looks like a temporary GitHub problem, and nothing already packaged is lost. Retry now?',
|
|
712
714
|
modelRemap: 'backend {host} remaps Anthropic model names, so "{model}" would test a different model; pass that backend\'s own model id',
|
|
713
715
|
hostMissing: 'Host {host} was not found ({bin} is not on PATH). {how}, or run again and pick another host (Claude Code, CodeBuddy, Codewhale, Codex).',
|
|
714
716
|
installCodebuddy: 'To install: npm install -g @tencent-ai/codebuddy-code',
|
|
@@ -1019,6 +1021,7 @@ Layer 0 always runs first: it is free, and it disqualifies most problems before
|
|
|
1019
1021
|
submitPrOpened: 'プルリクエストを開きました:{url}',
|
|
1020
1022
|
submitNext: '次に、ボットに求められたら CLA に署名し、プルリクエストへの CI コメントを待ちます。コメントには合格か、何が足りないかが正確に書かれます。マージはメンテナが行います。',
|
|
1021
1023
|
submitGhFail: 'GitHub の手順で失敗しました:{msg}',
|
|
1024
|
+
submitRetry: 'GitHub の一時的な問題のようです。パッケージ済みの内容は失われません。今すぐ再試行しますか?',
|
|
1022
1025
|
modelRemap: 'バックエンド {host} は Anthropic のモデル名を黙って書き換えるため、「{model}」では指定したモデルをテストできません。そのバックエンド自身のモデル名を指定してください',
|
|
1023
1026
|
hostMissing: 'ホスト {host} が見つかりません(PATH に {bin} がありません)。{how}。または、もう一度実行して別のホスト(Claude Code、CodeBuddy、Codewhale、Codex)を選んでください。',
|
|
1024
1027
|
installCodebuddy: 'インストール方法:npm install -g @tencent-ai/codebuddy-code',
|
package/lib/submit.js
CHANGED
|
@@ -92,15 +92,66 @@ function prBody(pkg) {
|
|
|
92
92
|
].join('\n');
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// A fork that was just created is not always cloneable at once; try a few times, a few seconds apart.
|
|
96
|
+
function cloneWithRetry(exec, url, dir, { tries = 5, waitMs = 3000 } = {}) {
|
|
97
|
+
let last;
|
|
98
|
+
for (let i = 0; i < tries; i += 1) {
|
|
99
|
+
try {
|
|
100
|
+
exec('git', ['clone', '--depth', '1', '-c', 'credential.helper=', '-c', 'credential.https://github.com.helper=!gh auth git-credential', url, dir]);
|
|
101
|
+
return;
|
|
102
|
+
} catch (err) {
|
|
103
|
+
last = err;
|
|
104
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
105
|
+
if (i < tries - 1) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, waitMs);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw last;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const defaultSleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
112
|
+
|
|
113
|
+
// Server-side hiccups and network drops are worth another try; auth or validation errors are not.
|
|
114
|
+
export function isTransient(err) {
|
|
115
|
+
const s = `${(err && err.stderr) || ''} ${(err && err.message) || ''}`;
|
|
116
|
+
return /HTTP 5\d\d|\b50[0-4]\b|timed? ?out|ETIMEDOUT|ECONNRESET|EAI_AGAIN|ENOTFOUND|unable to access|early EOF|RPC failed|Could not resolve host|rate limit/i.test(s);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Run one GitHub step, retrying transient failures a few times with a growing pause.
|
|
120
|
+
export function withRetry(fn, { attempts = 3, sleep = defaultSleep } = {}) {
|
|
121
|
+
for (let i = 1; ; i += 1) {
|
|
122
|
+
try { return fn(); } catch (err) {
|
|
123
|
+
if (i >= attempts || !isTransient(err)) throw err;
|
|
124
|
+
sleep(2000 * i);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// GitHub sometimes answers a fork request with a 5xx and creates the fork anyway, so look before giving up.
|
|
130
|
+
function ensureFork({ gh, exec, sleep }) {
|
|
131
|
+
const exists = () => { try { exec('gh', ['repo', 'view', `${gh.user}/${UPSTREAM.split('/')[1]}`, '--json', 'name']); return true; } catch { return false; } };
|
|
132
|
+
try {
|
|
133
|
+
withRetry(() => exec('gh', ['repo', 'fork', UPSTREAM, '--clone=false']), { sleep });
|
|
134
|
+
} catch (err) {
|
|
135
|
+
for (let i = 0; i < 4 && !exists(); i += 1) sleep(3000);
|
|
136
|
+
if (!exists()) throw err;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
95
140
|
// Fork, branch, commit, push, open. Every GitHub call goes through `exec` so tests can fake them.
|
|
96
|
-
export function openPullRequest({ pkgDir, pkg, gh, exec = defaultExec, say: out = say }) {
|
|
141
|
+
export function openPullRequest({ pkgDir, pkg, gh, exec = defaultExec, say: out = say, cloneWait = 3000, sleep = defaultSleep }) {
|
|
97
142
|
const work = fs.mkdtempSync(path.join(os.tmpdir(), 'sg-pr-'));
|
|
143
|
+
const retry = (fn) => withRetry(fn, { sleep });
|
|
98
144
|
try {
|
|
99
145
|
out(t('submitForking', { upstream: UPSTREAM }));
|
|
100
|
-
|
|
146
|
+
ensureFork({ gh, exec, sleep });
|
|
101
147
|
out(t('submitPreparing'));
|
|
102
148
|
const repoDir = path.join(work, 'repo');
|
|
103
|
-
|
|
149
|
+
const forkUrl = `https://github.com/${gh.user}/${UPSTREAM.split('/')[1]}.git`;
|
|
150
|
+
// The branch is made from the contributor's own fork, not from upstream's main. A fork made before upstream gained
|
|
151
|
+
// workflow files lacks them, and pushing a branch built on upstream's main would then "create" those workflows in
|
|
152
|
+
// the fork, which GitHub refuses for a token without the `workflow` scope (the default gh sign-in has none). A branch
|
|
153
|
+
// made on the fork adds only the submission, and the pull request's diff against upstream is still just that.
|
|
154
|
+
cloneWithRetry(exec, forkUrl, repoDir, { waitMs: cloneWait });
|
|
104
155
|
const g = (...a) => exec('git', ['-C', repoDir, ...a]);
|
|
105
156
|
const branch = `submit/${slug(pkg.files.meta.skill)}-${pkg.files.meta.submission.host.id}-${pkg.path[2].split('-')[0].toLowerCase()}`;
|
|
106
157
|
g('checkout', '-b', branch);
|
|
@@ -115,17 +166,23 @@ export function openPullRequest({ pkgDir, pkg, gh, exec = defaultExec, say: out
|
|
|
115
166
|
const ident = ['-c', `user.name=${gh.user}`, '-c', `user.email=${gh.id}+${gh.user}@users.noreply.github.com`];
|
|
116
167
|
g(...ident, 'commit', '-q', '-F', msg);
|
|
117
168
|
out(t('submitPushing'));
|
|
118
|
-
|
|
119
|
-
g('-c', 'credential.helper=', '-c', 'credential.https://github.com.helper=!gh auth git-credential', 'push', '-q', '
|
|
169
|
+
// a branch name left by an earlier attempt that died after the push is simply replaced (it is the contributor's own fork)
|
|
170
|
+
retry(() => g('-c', 'credential.helper=', '-c', 'credential.https://github.com.helper=!gh auth git-credential', 'push', '-q', '--force', 'origin', branch));
|
|
120
171
|
out(t('submitOpening'));
|
|
121
172
|
const body = path.join(work, 'body.md');
|
|
122
173
|
fs.writeFileSync(body, prBody(pkg));
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
174
|
+
try {
|
|
175
|
+
return retry(() => exec('gh', [
|
|
176
|
+
'pr', 'create', '--repo', UPSTREAM, '--head', `${gh.user}:${branch}`, '--base', 'main',
|
|
177
|
+
'--title', `Submission: ${m.skill} on ${m.submission.host.id} / ${m.submission.model.resolved || m.submission.model.requested}`,
|
|
178
|
+
'--body-file', body,
|
|
179
|
+
])).trim().split(/\s+/).pop();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
// an earlier attempt may have opened it before the connection dropped: reuse that pull request
|
|
182
|
+
const dup = /already exists[\s\S]*?(https:\/\/github\.com\/\S+\/pull\/\d+)/i.exec(String(err.stderr || err.message));
|
|
183
|
+
if (dup) return dup[1];
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
129
186
|
} finally {
|
|
130
187
|
fs.rmSync(work, { recursive: true, force: true });
|
|
131
188
|
}
|
|
@@ -196,18 +253,22 @@ export async function cmdSubmit(args, { exec = defaultExec, verifyRemote = skill
|
|
|
196
253
|
say(t('submitManual', { upstream: UPSTREAM, dir: pkgDir, path: pkg.path.join('/') }));
|
|
197
254
|
return { pkgDir, pkg };
|
|
198
255
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
256
|
+
for (;;) {
|
|
257
|
+
try {
|
|
258
|
+
const url = openPullRequest({ pkgDir, pkg, gh, exec });
|
|
259
|
+
say(`\n${t('submitPrOpened', { url })}`);
|
|
260
|
+
say(t('submitNext'));
|
|
261
|
+
// the CLA link and the wait for the merge, with enter to continue (under test fakes there is nothing to wait for)
|
|
262
|
+
if (hasTerminal() && exec === defaultExec) await watchPullRequest(url, { pick: choose, out: say });
|
|
263
|
+
return { pkgDir, pkg, url };
|
|
264
|
+
} catch (err) {
|
|
265
|
+
say(`\n${t('submitGhFail', { msg: String(err.stderr || err.message).trim().split(/\r?\n/).slice(-3).join(' ') })}`);
|
|
266
|
+
// Enter retries: a non-technical contributor should not have to start over after a GitHub hiccup.
|
|
267
|
+
if (!opts.yes && isTransient(err) && hasTerminal() && await confirm(t('submitRetry'), true)) continue;
|
|
268
|
+
say(t('submitManual', { upstream: UPSTREAM, dir: pkgDir, path: pkg.path.join('/') }));
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
return { pkgDir, pkg };
|
|
271
|
+
}
|
|
211
272
|
}
|
|
212
273
|
}
|
|
213
274
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillgauntlet",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
4
4
|
"description": "Check an agent skill before you install it: context cost, permission surface, code risk, instruction hijacking, description quality, trigger rate and gain.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|