skillgauntlet 1.3.8 → 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 +62 -21
- 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
|
@@ -108,12 +108,42 @@ function cloneWithRetry(exec, url, dir, { tries = 5, waitMs = 3000 } = {}) {
|
|
|
108
108
|
throw last;
|
|
109
109
|
}
|
|
110
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
|
+
|
|
111
140
|
// Fork, branch, commit, push, open. Every GitHub call goes through `exec` so tests can fake them.
|
|
112
|
-
export function openPullRequest({ pkgDir, pkg, gh, exec = defaultExec, say: out = say, cloneWait = 3000 }) {
|
|
141
|
+
export function openPullRequest({ pkgDir, pkg, gh, exec = defaultExec, say: out = say, cloneWait = 3000, sleep = defaultSleep }) {
|
|
113
142
|
const work = fs.mkdtempSync(path.join(os.tmpdir(), 'sg-pr-'));
|
|
143
|
+
const retry = (fn) => withRetry(fn, { sleep });
|
|
114
144
|
try {
|
|
115
145
|
out(t('submitForking', { upstream: UPSTREAM }));
|
|
116
|
-
|
|
146
|
+
ensureFork({ gh, exec, sleep });
|
|
117
147
|
out(t('submitPreparing'));
|
|
118
148
|
const repoDir = path.join(work, 'repo');
|
|
119
149
|
const forkUrl = `https://github.com/${gh.user}/${UPSTREAM.split('/')[1]}.git`;
|
|
@@ -136,16 +166,23 @@ export function openPullRequest({ pkgDir, pkg, gh, exec = defaultExec, say: out
|
|
|
136
166
|
const ident = ['-c', `user.name=${gh.user}`, '-c', `user.email=${gh.id}+${gh.user}@users.noreply.github.com`];
|
|
137
167
|
g(...ident, 'commit', '-q', '-F', msg);
|
|
138
168
|
out(t('submitPushing'));
|
|
139
|
-
|
|
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));
|
|
140
171
|
out(t('submitOpening'));
|
|
141
172
|
const body = path.join(work, 'body.md');
|
|
142
173
|
fs.writeFileSync(body, prBody(pkg));
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
}
|
|
149
186
|
} finally {
|
|
150
187
|
fs.rmSync(work, { recursive: true, force: true });
|
|
151
188
|
}
|
|
@@ -216,18 +253,22 @@ export async function cmdSubmit(args, { exec = defaultExec, verifyRemote = skill
|
|
|
216
253
|
say(t('submitManual', { upstream: UPSTREAM, dir: pkgDir, path: pkg.path.join('/') }));
|
|
217
254
|
return { pkgDir, pkg };
|
|
218
255
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
+
}
|
|
231
272
|
}
|
|
232
273
|
}
|
|
233
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": {
|