@pathbit/agentdevkit 0.0.19 → 0.0.21

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.
@@ -49,6 +49,20 @@ export function validateToolConfig(name, value) {
49
49
  }
50
50
  }
51
51
  }
52
+ if (value.optionalExecutables !== undefined) {
53
+ if (download.provider !== 'npm' || !value.optionalExecutables || typeof value.optionalExecutables !== 'object' || Array.isArray(value.optionalExecutables) || !Object.keys(value.optionalExecutables).length) fail(`Mapa de executaveis opcionais invalido: ${name}`);
54
+ for (const [platform, files] of Object.entries(value.optionalExecutables)) {
55
+ if (!value.platforms.includes(platform) || !Array.isArray(files) || !files.length) fail(`Plataforma de executaveis opcionais invalida: ${name}/${platform}`);
56
+ const seen = new Set();
57
+ for (const item of files) {
58
+ if (!item || typeof item !== 'object' || Array.isArray(item) || Object.keys(item).sort().join(',') !== 'file,package'
59
+ || !/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(item.package || '') || seen.has(item.package)
60
+ || typeof item.file !== 'string' || !item.file || item.file.includes('\0') || item.file.includes('\\') || item.file.includes(':')
61
+ || item.file.split('/').some(part => !part || part === '.' || part === '..')) fail(`Executavel opcional deve ser relativo ao pacote: ${name}`);
62
+ seen.add(item.package);
63
+ }
64
+ }
65
+ }
52
66
  if (value.scripts?.entry !== 'SCRIPT.mjs' || Object.keys(value.scripts).length !== 1) fail(`Script invalido; use scripts.entry=SCRIPT.mjs: ${name}`);
53
67
  if (value.nodeMinimum !== undefined) fail(`nodeMinimum foi substituido pelo requisito comum Node >=24: ${name}`);
54
68
  const environmentKeys = new Set();
@@ -162,7 +176,7 @@ export function binaryInvocation(prefix, name, args = [], binaryName = name.spli
162
176
  if (typeof bin !== 'string' || !bin || bin.includes('\0')) fail(`Executavel ausente ou invalido: ${name}`);
163
177
  const binary = resolve(base, bin); const traversal = relative(base, binary);
164
178
  if (isAbsolute(traversal) || traversal === '..' || traversal.startsWith('..' + (process.platform === 'win32' ? '\\' : '/'))) fail('Executavel fora do pacote.');
165
- const resolved = relative(prefix, realpathSync(binary));
179
+ const resolved = relative(realpathSync(prefix), realpathSync(binary));
166
180
  if (isAbsolute(resolved) || resolved === '..' || resolved.startsWith('..' + (process.platform === 'win32' ? '\\' : '/'))) fail('Executavel fora da instalacao local.');
167
181
  const header = Buffer.alloc(128); const handle = openSync(binary, 'r');
168
182
  try { readSync(handle, header, 0, header.length, 0); } finally { closeSync(handle); }
@@ -203,25 +217,57 @@ function execute(prefix, entry, args, capture = false, input, environment, runti
203
217
  return result.status ?? 1;
204
218
  }
205
219
 
206
- function prepareExecutables(prefix, entry, platform) {
207
- const files = entry.executables?.[platform];
208
- if (!files || process.platform === 'win32') return;
220
+ async function prepareExecutables(prefix, entry, platform, pkg, lock, fetchOptional) {
221
+ const files = entry.executables?.[platform] || [];
222
+ const optional = entry.optionalExecutables?.[platform] || [];
223
+ if (!files.length && !optional.length) return;
209
224
  const within = (base, file) => { const child = relative(base, file); return !isAbsolute(child) && child !== '..' && !child.startsWith(`..${sep}`); };
210
225
  const stage = realpathSync(prefix);
211
226
  const packageRoot = realpathSync(join(prefix, 'node_modules', entry.download.package));
212
227
  if (!within(stage, packageRoot)) fail(`Pacote fora do staging: ${entry.name}`);
213
- const targets = files.map(file => {
214
- const target = realpathSync(join(packageRoot, file));
215
- if (!within(packageRoot, target)) fail(`Executavel fora do pacote: ${entry.name}/${file}`);
228
+ const declared = optional.filter(item => Object.hasOwn(pkg.optionalDependencies || {}, item.package));
229
+ const targets = [];
230
+ const collect = (base, file, label) => {
231
+ const target = realpathSync(join(base, file));
232
+ if (!within(base, target)) fail(`Executavel fora do pacote: ${label}`);
216
233
  const info = lstatSync(target);
217
- if (!info.isFile()) fail(`Executavel nao e arquivo regular: ${entry.name}/${file}`);
218
- return { target, mode: (info.mode & 0o777) | 0o111 };
219
- });
220
- for (const { target, mode } of targets) chmodSync(target, mode);
234
+ if (!info.isFile()) fail(`Executavel nao e arquivo regular: ${label}`);
235
+ targets.push({ target, mode: (info.mode & 0o777) | 0o111 });
236
+ };
237
+ // Older releases contain all of their binaries in the main package. Newer
238
+ // releases declare a host package; inspect every legacy file that exists so
239
+ // the upstream launcher cannot prefer an unchecked file over that package.
240
+ for (const file of files) {
241
+ let exists = false;
242
+ try { lstatSync(join(packageRoot, file)); exists = true; } catch (error) { if (error.code !== 'ENOENT') throw error; }
243
+ if (exists || !declared.length) collect(packageRoot, file, `${entry.name}/${file}`);
244
+ }
245
+ if (declared.length) {
246
+ let installed = 0;
247
+ for (const item of declared) {
248
+ const version = pkg.optionalDependencies[item.package];
249
+ if (!versionPattern.test(version || '')) fail(`Versao de executavel opcional invalida: ${item.package}`);
250
+ const directory = sourcePath(prefix, join('node_modules', item.package));
251
+ try { lstatSync(directory); } catch (error) { if (error.code === 'ENOENT') continue; throw error; }
252
+ const optionalRoot = realpathSync(directory);
253
+ if (!within(realpathSync(join(prefix, 'node_modules')), optionalRoot)) fail(`Pacote opcional fora de node_modules: ${item.package}`);
254
+ const manifest = json(join(optionalRoot, 'package.json'));
255
+ const record = lock.packages?.[`node_modules/${item.package}`];
256
+ const official = await fetchOptional(item.package, version);
257
+ const tarball = new URL(official.dist?.tarball);
258
+ if (official.name !== item.package || official.version !== version || tarball.origin !== registry.slice(0, -1)
259
+ || !decodeURIComponent(tarball.pathname).startsWith(`/${item.package}/-/`) || tarball.search || tarball.hash
260
+ || !official.dist?.integrity?.startsWith('sha512-') || manifest.name !== item.package || manifest.version !== version
261
+ || record?.version !== version || record.integrity !== official.dist.integrity || record.resolved !== official.dist.tarball) fail(`Pacote opcional instalado nao corresponde ao registro oficial: ${item.package}`);
262
+ collect(optionalRoot, item.file, `${item.package}/${item.file}`); installed++;
263
+ }
264
+ if (!installed) fail(`Executavel opcional ausente para ${entry.name}/${platform}; instale as dependencias opcionais.`);
265
+ }
266
+ if (process.platform !== 'win32') for (const { target, mode } of targets) chmodSync(target, mode);
221
267
  }
222
268
 
223
269
  export class ToolManager {
224
- constructor({ root = projectRoot, catalog, fetchRelease, install, npm = npmCli, nodeVersion = process.versions.node, platform = process.platform, arch = process.arch } = {}) {
270
+ constructor({ root = projectRoot, catalog, fetchRelease, fetchOptional, install, npm = npmCli, nodeVersion = process.versions.node, platform = process.platform, arch = process.arch } = {}) {
225
271
  this.root = resolve(root); this.home = join(this.root, '.pbai4devs/tools'); this.target = `${platform}-${arch}`;
226
272
  this.nodeVersion = nodeVersion; this.npm = npm;
227
273
  this.catalog = catalog ?? loadCatalog(this.root);
@@ -232,11 +278,19 @@ export class ToolManager {
232
278
  if (!response.ok) fail(`Registro indisponivel: HTTP ${response.status}`);
233
279
  return response.json();
234
280
  });
281
+ this.fetchOptional = fetchOptional || (async (name, version) => {
282
+ const response = await fetch(`${registry}${encodeURIComponent(name)}/${version}`, { signal: AbortSignal.timeout(20000) });
283
+ if (!response.ok) fail(`Registro indisponivel para ${name}: HTTP ${response.status}`);
284
+ return response.json();
285
+ });
235
286
  this.installPackage = install || (async (prefix, packageName, version, entry, runtime) => {
236
287
  if (entry.download.provider === 'github-release') return installNative(prefix, entry, this.target);
237
288
  const environment = toolEnvironment(entry, this.local(entry.name), runtime);
238
- const result = spawnSync(runtime.file, [this.npm(runtime, environment), 'install', '--prefix', prefix, '--cache', join(this.home, '.cache/npm'), '--registry', entry.download.registry, '--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund', '--save-exact', `${packageName}@${version}`], {
239
- cwd: prefix, encoding: 'utf8', timeout: 180000,
289
+ // npm resolves cwd but not --prefix, yielding invalid lock paths when
290
+ // ancestors are aliases such as macOS /var -> /private/var.
291
+ const npmPrefix = realpathSync(storagePath(this.root, prefix, 'directory'));
292
+ const result = spawnSync(runtime.file, [this.npm(runtime, environment), 'install', '--prefix', npmPrefix, '--cache', join(this.home, '.cache/npm'), '--registry', entry.download.registry, '--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund', '--save-exact', `${packageName}@${version}`], {
293
+ cwd: npmPrefix, encoding: 'utf8', timeout: 180000,
240
294
  env: { ...environment, npm_config_engine_strict: 'true', npm_config_update_notifier: 'false', PUPPETEER_SKIP_DOWNLOAD: 'true', PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' }
241
295
  });
242
296
  if (result.error || result.status !== 0) fail(`npm: ${result.error?.message || result.stderr?.trim() || result.status}`, result.status ?? 1);
@@ -438,7 +492,7 @@ export class ToolManager {
438
492
  else {
439
493
  const pkg = json(join(stage, 'node_modules', entry.download.package, 'package.json')); const lock = json(join(stage, 'package-lock.json'));
440
494
  if (pkg.name !== entry.download.package || pkg.version !== release.version || lock.packages?.[`node_modules/${entry.download.package}`]?.integrity !== release.dist.integrity) fail('Pacote instalado nao corresponde ao registro.');
441
- prepareExecutables(stage, entry, this.target);
495
+ await prepareExecutables(stage, entry, this.target, pkg, lock, this.fetchOptional);
442
496
  }
443
497
  execute(stage, entry, entry.probe, true, entry.probeInput, toolEnvironment(entry, local, runtime), runtime);
444
498
  this.storage(name); storagePath(this.root, target, 'directory');
@@ -1,6 +1,6 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, readdirSync, existsSync, copyFileSync, symlinkSync, unlinkSync, chmodSync, statSync, renameSync } from 'node:fs';
3
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, readdirSync, existsSync, copyFileSync, symlinkSync, unlinkSync, chmodSync, statSync, renameSync, realpathSync } from 'node:fs';
4
4
  import { resolve, join, dirname, delimiter } from 'node:path';
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
6
  import { spawnSync } from 'node:child_process';
@@ -519,6 +519,26 @@ writeFileSync(join(prefix, 'npm-proof.json'), JSON.stringify({args,path:process.
519
519
  assert.equal(JSON.parse(readFileSync(join(local, 'CURRENT.json'), 'utf8')).nodePath, process.execPath);
520
520
  });
521
521
 
522
+ test('npm recebe staging canonico quando a raiz usa alias simbolico', async t => {
523
+ const config = tool(); const { root } = fixture(t, { 'test-tool': config });
524
+ const alias = join(root, 'project-alias');
525
+ if (!createSymlink(t, root, alias, 'dir')) return;
526
+ const npm = join(root, 'fake-npm.cjs');
527
+ writeFileSync(npm, `const { mkdirSync, writeFileSync, realpathSync } = require('node:fs');
528
+ const { join } = require('node:path');
529
+ ${packageFiles.toString()}
530
+ const prefix = process.argv[process.argv.indexOf('--prefix') + 1];
531
+ if (prefix !== realpathSync(prefix) || process.cwd() !== prefix) process.exit(91);
532
+ packageFiles(prefix, 'test-tool', '1.0.0');
533
+ writeFileSync(join(prefix, 'canonical-proof.json'), JSON.stringify({prefix,cwd:process.cwd()}));
534
+ `);
535
+ const manager = new ToolManager({ root: alias, catalog: { 'test-tool': config }, npm: () => npm, fetchRelease: async () => release('1.0.0') });
536
+ const installed = await manager.install('test-tool');
537
+ const proof = JSON.parse(readFileSync(join(installed.prefix, 'canonical-proof.json'), 'utf8'));
538
+ assert.equal(proof.cwd, proof.prefix); assert.ok(proof.prefix.startsWith(realpathSync(root)));
539
+ assert.equal(manager.check('test-tool').version, '1.0.0');
540
+ });
541
+
522
542
  test('setup padrao prepara todas as ferramentas compativeis e confirma cada instalacao', async t => {
523
543
  const unsupported = { ...tool('unsupported-tool'), platforms: [process.platform === 'darwin' ? 'linux-x64' : 'darwin-arm64'] };
524
544
  const { root, manager, state, local } = fixture(t, { 'test-tool': tool(), 'unsupported-tool': unsupported });
@@ -599,6 +619,62 @@ test('executables valida plataforma e caminhos relativos sem autorizar comandos
599
619
  }
600
620
  });
601
621
 
622
+ test('executaveis opcionais validam pacotes e caminhos sem permitir escapes', () => {
623
+ const target = platformKey(); const base = tool();
624
+ const good = { [target]: [{ package: '@owner/cli-host', file: 'bin/probe' }] };
625
+ assert.deepEqual(validateToolConfig('test-tool', { ...base, optionalExecutables: good }).optionalExecutables, good);
626
+ for (const item of [
627
+ { package: '../outside', file: 'bin/probe' }, { package: '@owner/cli-host', file: '../outside' },
628
+ { package: '@owner/cli-host', file: '/absolute' }, { package: '@owner/cli-host', file: 'C:\\outside' },
629
+ { package: '@owner/cli-host', file: 'bin/probe', extra: true }
630
+ ]) assert.throws(() => validateToolConfig('test-tool', { ...base, optionalExecutables: { [target]: [item] } }), /opcional|relativo/);
631
+ assert.throws(() => validateToolConfig('test-tool', { ...base, optionalExecutables: { [target]: [good[target][0], good[target][0]] } }), /relativo/);
632
+ });
633
+
634
+ test('atualizacao aceita layout opcional e preserva versao anterior se dependencia falhar', async t => {
635
+ const targets = process.platform === 'win32' ? [platformKey()] : [platformKey(), 'win32-x64'];
636
+ for (const target of targets) await t.test(target, async subtest => {
637
+ const name = '@owner/cli-host'; const isWindowsTarget = target.startsWith('win32-');
638
+ const config = { ...tool(), platforms: [target], ...(!isWindowsTarget ? { executables: { [target]: ['bin/legacy'] } } : {}), optionalExecutables: { [target]: [{ package: name, file: 'bin/probe' }] } };
639
+ const [platform, arch] = target.split('-');
640
+ const { manager, state, local, root } = fixture(subtest, { 'test-tool': config }, { platform, arch });
641
+ let scenario = 'legacy'; const outside = join(root, 'outside-native'); const probe = join(root, 'probe-executado'); writeFileSync(outside, 'untouched'); chmodSync(outside, 0o644);
642
+ const officialTarball = version => `https://registry.npmjs.org/${name}/-/cli-host-${version}.tgz`;
643
+ manager.fetchOptional = async (pkgName, version) => ({ name: pkgName, version, dist: { tarball: officialTarball(version), integrity: 'sha512-fixture' } });
644
+ manager.installPackage = (prefix, pkgName, version) => {
645
+ packageFiles(prefix, pkgName, version);
646
+ const base = join(prefix, 'node_modules', pkgName); mkdirSync(join(base, 'bin'));
647
+ if (scenario === 'legacy') { writeFileSync(join(base, 'bin/legacy'), 'legacy'); return; }
648
+ const manifestFile = join(base, 'package.json'); const manifest = JSON.parse(readFileSync(manifestFile));
649
+ manifest.optionalDependencies = { [name]: version }; writeFileSync(manifestFile, JSON.stringify(manifest));
650
+ writeFileSync(join(base, 'cli.cjs'), `require('node:fs').writeFileSync(${JSON.stringify(probe)}, 'executado'); console.log(${JSON.stringify(version)});`);
651
+ if (scenario === 'missing') return;
652
+ const optional = join(prefix, 'node_modules', name);
653
+ if (scenario === 'package-link') { mkdirSync(dirname(optional), { recursive: true }); symlinkSync(root, optional, 'dir'); return; }
654
+ mkdirSync(join(optional, 'bin'), { recursive: true });
655
+ writeFileSync(join(optional, 'package.json'), JSON.stringify({ name, version: scenario === 'version' ? '9.9.9' : version }));
656
+ const lockFile = join(prefix, 'package-lock.json'); const lock = JSON.parse(readFileSync(lockFile));
657
+ lock.packages[`node_modules/${name}`] = { version, integrity: scenario === 'integrity' ? 'sha512-other-registry' : 'sha512-fixture', resolved: scenario === 'registry' ? `https://example.invalid/${name}/-/cli-host-${version}.tgz` : officialTarball(version) };
658
+ writeFileSync(lockFile, JSON.stringify(lock));
659
+ if (scenario === 'escape') symlinkSync(outside, join(optional, 'bin/probe'));
660
+ else { writeFileSync(join(optional, 'bin/probe'), 'probe'); chmodSync(join(optional, 'bin/probe'), 0o644); }
661
+ if (scenario === 'legacy-escape') symlinkSync(outside, join(base, 'bin/legacy'));
662
+ };
663
+ await manager.install('test-tool'); const before = readFileSync(join(local, 'CURRENT.json'), 'utf8'); state.version = '2.0.0';
664
+ for (scenario of ['missing', 'version', 'integrity', 'registry', ...(process.platform === 'win32' ? [] : ['escape', 'package-link', ...(!isWindowsTarget ? ['legacy-escape'] : [])])]) {
665
+ await assert.rejects(manager.update('test-tool'), /ausente|corresponde|fora do pacote|nao pode ser um link/);
666
+ assert.equal(readFileSync(join(local, 'CURRENT.json'), 'utf8'), before);
667
+ assert.equal(manager.check('test-tool').version, '1.0.0');
668
+ assert.equal(existsSync(probe), false);
669
+ assert.ok(readdirSync(local).every(entry => !entry.startsWith('.stage-')));
670
+ }
671
+ if (process.platform !== 'win32') assert.equal(statSync(outside).mode & 0o777, 0o644);
672
+ scenario = 'optional'; const updated = await manager.update('test-tool');
673
+ assert.equal(updated.version, '2.0.0'); assert.equal(manager.check('test-tool').version, '2.0.0');
674
+ if (process.platform !== 'win32') assert.equal(statSync(join(updated.prefix, 'node_modules', name, 'bin/probe')).mode & 0o777, 0o755);
675
+ });
676
+ });
677
+
602
678
  test('executables prepara somente binarios da plataforma no staging antes de executar probe real', { skip: process.platform === 'win32' }, async t => {
603
679
  const other = process.platform === 'darwin' ? 'linux-x64' : 'darwin-arm64';
604
680
  const config = { ...tool(), platforms: [platformKey(), other], executables: { [platformKey()]: ['native/probe with space'], [other]: ['native/unused'] } };
@@ -24,6 +24,14 @@
24
24
  "linux-arm64": ["bin/agent-browser-linux-arm64", "bin/agent-browser-linux-musl-arm64"],
25
25
  "linux-x64": ["bin/agent-browser-linux-x64", "bin/agent-browser-linux-musl-x64"]
26
26
  },
27
+ "optionalExecutables": {
28
+ "darwin-arm64": [{"package": "@agent-browser/cli-darwin-arm64", "file": "bin/agent-browser"}],
29
+ "darwin-x64": [{"package": "@agent-browser/cli-darwin-x64", "file": "bin/agent-browser"}],
30
+ "linux-arm64": [{"package": "@agent-browser/cli-linux-arm64-gnu", "file": "bin/agent-browser"}, {"package": "@agent-browser/cli-linux-arm64-musl", "file": "bin/agent-browser"}],
31
+ "linux-x64": [{"package": "@agent-browser/cli-linux-x64-gnu", "file": "bin/agent-browser"}, {"package": "@agent-browser/cli-linux-x64-musl", "file": "bin/agent-browser"}],
32
+ "win32-arm64": [{"package": "@agent-browser/cli-win32-x64-msvc", "file": "bin/agent-browser.exe"}],
33
+ "win32-x64": [{"package": "@agent-browser/cli-win32-x64-msvc", "file": "bin/agent-browser.exe"}]
34
+ },
27
35
  "scripts": {
28
36
  "entry": "SCRIPT.mjs"
29
37
  },
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ description: Alterações verificáveis por versão do AgentDevKit.
4
4
 
5
5
  # Changelog
6
6
 
7
+ ## 0.0.21 — 2026-11-13
8
+
9
+ - Criação automática da tag da versão após aprovação da matriz de CI.
10
+ - Publicação acionada pela automação com proteção contra colisões de tags.
11
+
12
+ ## 0.0.20 — 2026-11-13
13
+
14
+ - Atualização da ferramenta agent-browser compatível com sua distribuição atual.
15
+ - Orientações para consultar e recuperar versões pelas tags publicadas.
16
+
7
17
  ## 0.0.19 — 2026-11-13
8
18
 
9
19
  - Pastas dos clientes são ignoradas localmente por padrão, com exceções do projeto.
@@ -75,8 +75,15 @@ nada. Nunca versione o tarball ou seus relatórios.
75
75
 
76
76
  1. Atualize versão e lockfile, changelog, exemplos e escopo da entrega.
77
77
  2. Conclua testes, documentação e revisão independente.
78
- 3. Envie o commit à branch principal e aguarde o CI aprovado.
79
- 4. Crie e envie a tag `v<versão>` para esse mesmo commit.
78
+ 3. Envie o commit à branch principal e acompanhe o CI e a release.
79
+
80
+ Após toda a matriz de CI passar, a automação cria a tag `v<versão>` no
81
+ commit verificado e inicia o workflow de release. Um push sem mudança de versão
82
+ publicada não gera outra release. Uma tag existente não é movida: se a versão
83
+ mudou e a tag aponta para outro commit, a automação falha para revisão.
84
+ Versões inferiores às já registradas também não são publicadas automaticamente.
85
+ Se um push cancelar o CI anterior e a versão ainda não tiver tag, o próximo
86
+ CI aprovado da branch principal poderá publicar a versão no seu próprio commit.
80
87
 
81
88
  O [workflow de release][release] localiza o CI aprovado de um push na branch
82
89
  principal, confere o commit e baixa seu artifact por ID. Verifica procedência,
@@ -85,6 +92,29 @@ Também envia o mesmo pacote ao [npm público e GitHub Packages](packages.md),
85
92
  confere a integridade nos dois destinos e testa versão, instalação, diagnóstico
86
93
  e sincronização. O consumo pelo npm é anônimo; pelo GitHub, autenticado.
87
94
 
95
+ A tag é a origem da publicação: `v0.0.21`, a release `v0.0.21` e os pacotes
96
+ `0.0.21` apontam para a mesma entrega. A execução manual também exige a tag;
97
+ não crie outra tag para uma versão nem mova uma tag já publicada. Publicações
98
+ feitas fora desse fluxo não criam tags automaticamente.
99
+
100
+ Para consultar o código de uma versão sem trocar os arquivos atuais:
101
+
102
+ ```console
103
+ git fetch --tags
104
+ git worktree add --detach ../AgentDevKit-v0.0.19 v0.0.19
105
+ ```
106
+
107
+ Em um projeto atendido, fixe a versão desejada do pacote e revise o plano antes
108
+ de aplicá-la:
109
+
110
+ ```console
111
+ npx @pathbit/agentdevkit@0.0.19 update --dry-run --json
112
+ ```
113
+
114
+ O comando usa aquela versão do kit; não reverte automaticamente o histórico
115
+ Git nem arquivos próprios do projeto. Confira os conflitos e faça backup antes
116
+ de aplicar uma versão anterior.
117
+
88
118
  Se o artifact expirou ou um job do CI falhou, use **Re-run all jobs** no CI
89
119
  original do push e aguarde todos os jobs. **Re-run failed jobs** pode manter o
90
120
  pacote de uma tentativa anterior, que a release não aceita. Depois reexecute
@@ -102,6 +132,9 @@ Se a release ainda não existe, use **Re-run failed jobs** ou
102
132
  `gh workflow run release.yml --ref vX.Y.Z` com a tag real. A publicação prioriza
103
133
  o secret `NPMJS_TOKEN` do repositório; sem ele, usa OIDC, conforme
104
134
  [publicação](packages.md).
135
+ Se a criação da tag ou o acionamento falhar, consulte a execução **Automatic
136
+ release tag**. Uma tag no commit correto pode ser retomada sem movê-la; se ela
137
+ apontar para outro commit, investigue antes de qualquer publicação.
105
138
 
106
139
  Se a release já existe e falta somente o pacote em um registro, use o modo
107
140
  `package_only` descrito em [pacote npm](packages.md#publicar-pelo-actions).
@@ -26,6 +26,8 @@ description: Incrementos verificáveis do AgentDevKit.
26
26
  - [Compatibilidade com projetos reais][oss]: projetos públicos e exemplos reproduzíveis.
27
27
  - [Ignore do projeto](project-ignore/SPEC.md): preservar o arquivo da raiz.
28
28
  - [Ignore de clientes](client-ignore/SPEC.md): padrão local e exceções do projeto.
29
+ - [Ferramenta e versões](tool-release/SPEC.md): atualização segura e tags de release.
30
+ - [Tags automáticas](automatic-release-tags/SPEC.md): publicação após CI aprovada.
29
31
 
30
32
  Cada incremento vincula requisitos, critérios, testes e evidências locais.
31
33
 
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: Criação e publicação seguras de tags de versões verificadas.
3
+ ---
4
+
5
+ # Tags automáticas de release
6
+
7
+ ## Critérios de aceite
8
+
9
+ - AC-001: A CI cria `v<versão>` no commit da branch principal somente após
10
+ toda a matriz passar e inicia a publicação do mesmo commit.
11
+ - AC-002: Alterações sem nova versão não duplicam publicações; tags existentes
12
+ nunca são movidas e colisões ou regressões de versão impedem a publicação.
13
+ - AC-003: A release e os dois pacotes usam o mesmo artefato e a mesma versão
14
+ da tag; a publicação exige o CI completo aprovado.
15
+ - AC-004: Testes verificam eventos indevidos, falhas, colisões, retomada e
16
+ corridas sem criar tags ou publicar pacotes reais.
@@ -0,0 +1,19 @@
1
+ ---
2
+ description: Atualização segura do agent-browser e recuperação por versão.
3
+ ---
4
+
5
+ # Atualização de ferramenta e versões
6
+
7
+ O agent-browser passou a distribuir executáveis em pacotes opcionais por
8
+ plataforma. A atualização deve funcionar sem perder instalações existentes.
9
+
10
+ ## Critérios de aceite
11
+
12
+ - AC-001: O instalador aceita os layouts anterior e atual do agent-browser,
13
+ valida executáveis adequados à plataforma e comprova a versão instalada.
14
+ - AC-002: Caminhos inválidos ou incompletos são recusados antes da troca;
15
+ falhas preservam a instalação anterior e não atravessam links inseguros.
16
+ - AC-003: Cada release e pacote do fluxo aponta para a tag da mesma versão;
17
+ a documentação explica como consultar uma versão e simular seu uso.
18
+ - AC-004: Testes automatizados cobrem layouts e falhas, e a CI verifica Linux,
19
+ Windows e macOS na matriz configurada antes da publicação.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pathbit/agentdevkit",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Shared skills, specifications and workflows for AI coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",