@pathbit/agentdevkit 0.0.24 → 0.0.25

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.
@@ -24,6 +24,12 @@ urgente, defina critérios próprios AC-001 e registre o motivo.
24
24
  revisão e `revisar-entrega check <relatorio>` pelo mesmo seletor. Falha,
25
25
  critério sem prova ou revisão pendente bloqueiam o commit/merge.
26
26
 
27
+ Antes de um merge fast-forward, na origem, execute `revisar-entrega check-merge
28
+ --destino refs/heads/<destino> <relatorio> [outros-relatorios...]`. A verificação
29
+ exige cobertura do diff e correspondência com o commit revisado. Ela bloqueia
30
+ históricos divergentes, links, submódulos, arquivos executáveis e operações Git
31
+ acima de 64 MiB. Confirme os hashes retornados antes de integrar.
32
+
27
33
  Saída: resultado por critério, pendências e caminho do único `revisao.json` em
28
34
  `.agent-tests/<referencia>/<execucao>/`. Esta etapa é obrigatória após qualquer
29
35
  implementação; a ausência de spec não elimina a revisão.
@@ -26,7 +26,7 @@ function pathInRoot(root, input) {
26
26
  }
27
27
 
28
28
  function git(root, args, allowFailure = false) {
29
- const result = spawnSync('git', args, { cwd: root, encoding: 'utf8', timeout: 15000, windowsHide: true });
29
+ const result = spawnSync('git', args, { cwd: root, encoding: 'utf8', timeout: 15000, windowsHide: true, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_NO_REPLACE_OBJECTS: '1' } });
30
30
  if ((result.error || result.status !== 0) && !allowFailure) fail(`Git: ${result.error?.message || result.stderr?.trim() || 'operacao falhou'}`);
31
31
  return result;
32
32
  }
@@ -53,7 +53,7 @@ function tracked(root, file) {
53
53
 
54
54
  function ignoredPaths(root, files) {
55
55
  if (!files.length) return new Set();
56
- const result = spawnSync('git', ['check-ignore', '--no-index', '--stdin', '-z'], { cwd: root, input: files.join('\0') + '\0', encoding: 'utf8', timeout: 15000, windowsHide: true });
56
+ const result = spawnSync('git', ['check-ignore', '--no-index', '--stdin', '-z'], { cwd: root, input: files.join('\0') + '\0', encoding: 'utf8', timeout: 15000, windowsHide: true, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_NO_REPLACE_OBJECTS: '1' } });
57
57
  if (result.error || ![0, 1].includes(result.status)) fail(`Git: ${result.error?.message || result.stderr?.trim() || 'falha ao conferir ignores'}`);
58
58
  return new Set(result.stdout.split('\0').filter(Boolean));
59
59
  }
@@ -211,11 +211,79 @@ function validateReview(file, { root = projectRoot, requireSeal = true } = {}) {
211
211
  const recorded = JSON.parse(readFileSync(join(root, sealPath), 'utf8'));
212
212
  if (JSON.stringify(recorded) !== JSON.stringify(seal)) fail('Relatorio, baseline ou evidencia alterados apos selar a revisao.');
213
213
  }
214
- return { root, prefix, seal, result: { status: 'passed', report: reportPath, criteria: ids.size, documents: docs.size } };
214
+ return { root, prefix, seal, snapshot: state, result: { status: 'passed', report: reportPath, criteria: ids.size, documents: docs.size } };
215
215
  }
216
216
 
217
217
  export function checkReview(file, { root = projectRoot } = {}) { return validateReview(file, { root, requireSeal: true }).result; }
218
218
 
219
+ // This deliberately permits only a fast-forward and regular non-executable
220
+ // files. Existing review snapshots do not record modes or merge results.
221
+ export function checkMerge(reports, destination, { root = projectRoot } = {}) {
222
+ requireNode(); root = repository(root);
223
+ if (!Array.isArray(reports) || !reports.length) fail('Informe um ou mais relatorios selados, sem repeticao.');
224
+ reports = reports.map(file => pathInRoot(root, file));
225
+ if (new Set(reports).size !== reports.length) fail('Informe relatorios selados sem repeticao.');
226
+ if (typeof destination !== 'string' || !destination.startsWith('refs/heads/') || git(root, ['check-ref-format', destination], true).status !== 0) fail('Destino deve ser uma branch local completa: refs/heads/<nome>.');
227
+ const currentBranch = git(root, ['symbolic-ref', '--quiet', 'HEAD'], true);
228
+ if (currentBranch.status !== 0) fail('Confira a branch de origem; HEAD destacado nao e aceito.');
229
+ const sourceRef = currentBranch.stdout.trim();
230
+ if (!sourceRef.startsWith('refs/heads/') || git(root, ['check-ref-format', sourceRef], true).status !== 0) fail('A origem deve ser uma branch local valida.');
231
+ if (sourceRef === destination) fail('Origem e destino devem ser branches distintas.');
232
+ for (const ref of [sourceRef, destination]) if (git(root, ['symbolic-ref', '--quiet', ref], true).status === 0) fail(`Branch simbolica nao e aceita: ${ref}`);
233
+ const resolveCommit = ref => {
234
+ const out = git(root, ['rev-parse', '--verify', `${ref}^{commit}`], true);
235
+ const value = out.stdout?.trim();
236
+ if (out.status !== 0 || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value || '')) fail(`Branch ou commit invalido: ${ref}`);
237
+ return value;
238
+ };
239
+ const source = resolveCommit(sourceRef), target = resolveCommit(destination);
240
+ if (head(root) !== source) fail('HEAD diverge da branch de origem.');
241
+ if (git(root, ['merge-base', '--is-ancestor', target, source], true).status !== 0) fail('Apenas fast-forward comprovado e suportado; revise o resultado integrado para historico divergente.');
242
+ const bytes = args => {
243
+ const result = spawnSync('git', args, { cwd: root, timeout: 15000, windowsHide: true, maxBuffer: 64 * 1024 * 1024, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_NO_REPLACE_OBJECTS: '1' } });
244
+ if (result.error || result.status !== 0) fail(`Git: ${result.error?.message || result.stderr?.toString().trim() || 'operacao falhou'}`);
245
+ return result.stdout;
246
+ };
247
+ const decode = data => { try { return new TextDecoder('utf-8', { fatal: true }).decode(data); } catch { fail('Caminho Git nao e UTF-8 valido.'); } };
248
+ const raw = bytes(['diff', '--raw', '-z', '--no-abbrev', '--no-renames', '--no-ext-diff', '--no-textconv', '--ignore-submodules=none', target, source, '--']);
249
+ const parts = decode(raw).split('\0');
250
+ if (parts.pop() !== '' || parts.length % 2) fail('Diff Git incompleto ou ambiguo.');
251
+ const changes = new Map();
252
+ for (let i = 0; i < parts.length; i += 2) {
253
+ const match = /^:(\d{6}) (\d{6}) ([a-f0-9]+) ([a-f0-9]+) ([AMD])$/.exec(parts[i]);
254
+ if (!match) fail('Tipo de alteracao Git nao suportado.');
255
+ const [, beforeMode, afterMode, , , action] = match, path = pathInRoot(root, parts[i + 1]);
256
+ if (changes.has(path) || !['000000', '100644'].includes(beforeMode) || !['000000', '100644'].includes(afterMode)) fail(`Tipo ou modo nao comprovado: ${path}`);
257
+ changes.set(path, action);
258
+ }
259
+ if (!changes.size) fail('Nao ha alteracoes para integrar.');
260
+ const reviewed = new Set();
261
+ const verified = reports.map(file => validateReview(file, { root, requireSeal: true }));
262
+ for (const review of verified) {
263
+ if (review.snapshot.head !== source) fail('Relatorio nao corresponde ao commit de origem.');
264
+ for (const item of review.snapshot.files) {
265
+ const path = pathInRoot(root, item.path);
266
+ if (item.indexAction !== undefined) fail(`Acao de indice nao pode comprovar merge: ${path}`);
267
+ const tree = decode(bytes(['--literal-pathspecs', 'ls-tree', '-z', '--full-tree', source, '--', path]));
268
+ if (!tree) {
269
+ if (item.sha256 !== null) fail(`Conteudo revisado nao esta no commit de origem: ${path}`);
270
+ } else {
271
+ const m = /^(\d{6}) blob ([a-f0-9]+)\t([^\0]+)\0$/.exec(tree);
272
+ if (!m || m[1] !== '100644' || m[3] !== path) fail(`Arquivo, link ou modo nao comprovado no commit: ${path}`);
273
+ if (item.sha256 !== createHash('sha256').update(bytes(['cat-file', 'blob', m[2]])).digest('hex')) fail(`Conteudo revisado difere do commit de origem: ${path}`);
274
+ }
275
+ if (changes.has(path)) reviewed.add(path);
276
+ }
277
+ }
278
+ const missing = [...changes.keys()].filter(path => !reviewed.has(path));
279
+ if (missing.length) fail(`Alteracoes sem revisao selada: ${missing.join(', ')}`);
280
+ // Detect moved refs or changed worktree/reports during the validation.
281
+ if (git(root, ['symbolic-ref', '--quiet', 'HEAD']).stdout.trim() !== sourceRef || resolveCommit(sourceRef) !== source || resolveCommit(destination) !== target || head(root) !== source) fail('Origem ou destino mudou durante a verificacao.');
282
+ for (const file of reports) validateReview(file, { root, requireSeal: true });
283
+ if (git(root, ['symbolic-ref', '--quiet', 'HEAD']).stdout.trim() !== sourceRef || resolveCommit(sourceRef) !== source || resolveCommit(destination) !== target || head(root) !== source) fail('Origem ou destino mudou durante a verificacao.');
284
+ return { status: 'passed', sourceRef, source, destination, target, changes: changes.size, reports: reports.length, mode: 'fast-forward' };
285
+ }
286
+
219
287
  export function sealReview(file, options) {
220
288
  const checked = validateReview(file, { ...options, requireSeal: false });
221
289
  const path = artifact(checked.root, `${checked.prefix}/seal.json`, checked.prefix, false);
@@ -224,12 +292,13 @@ export function sealReview(file, options) {
224
292
  return checkReview(file, options);
225
293
  }
226
294
 
227
- const usage = 'Uso: revisar-entrega prepare --id <slug> [--execution <slug>] [--spec <arquivo>] [--scope <arquivo>] [--document <arquivo>] [--output <artefato>]; seal|check <revisao.json>';
295
+ const usage = 'Uso: revisar-entrega prepare --id <slug> [--execution <slug>] [--spec <arquivo>] [--scope <arquivo>] [--document <arquivo>] [--output <artefato>]; seal|check <revisao.json>; check-merge --destino refs/heads/<branch> <revisao.json> [...]';
228
296
  export function main(args = process.argv.slice(2)) {
229
297
  requireNode(); const [mode, ...rest] = args;
230
298
  if (!mode || ['--help', '-h'].includes(mode)) { console.log(usage); return 0; }
231
299
  if (mode === 'check' && rest.length === 1) { console.log(JSON.stringify(checkReview(rest[0]))); return 0; }
232
300
  if (mode === 'seal' && rest.length === 1) { console.log(JSON.stringify(sealReview(rest[0]))); return 0; }
301
+ if (mode === 'check-merge' && rest.length >= 3 && rest[0] === '--destino') { console.log(JSON.stringify(checkMerge(rest.slice(2), rest[1]))); return 0; }
233
302
  if (mode !== 'prepare') fail(usage);
234
303
  const options = { scope: [], documents: [] };
235
304
  const fields = { '--id': 'id', '--execution': 'execution', '--spec': 'spec', '--output': 'output' };
@@ -1,9 +1,9 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, join } from 'node:path';
5
5
  import { spawnSync } from 'node:child_process';
6
- import { checkReview, prepareReview, sealReview, specCriteria } from '../SCRIPT.mjs';
6
+ import { checkMerge, checkReview, prepareReview, sealReview, specCriteria } from '../SCRIPT.mjs';
7
7
  import { projectRoot } from '../../lib/runtime.mjs';
8
8
 
9
9
  function fixture(t) {
@@ -247,3 +247,113 @@ test('documentos rastreados permanecem obrigatórios mesmo com regra de ignore',
247
247
  assert.ok(f.report.documentation.some(item => item.path === 'spec.md'));
248
248
  assert.equal(checkReview(f.file, { root: f.root }).status, 'passed');
249
249
  });
250
+
251
+ function mergeFixture(t, { scope = ['src/main.mjs', 'src/old.txt', 'src/new.txt'], dirty = false, seal = true, ignoredSubmodule = false } = {}) {
252
+ const f = fixture(t);
253
+ const commit = message => f.git('-c', 'user.name=Test User', '-c', 'user.email=test@example.invalid', '-c', 'commit.gpgsign=false', '-c', 'core.hooksPath=disabled-hooks', 'commit', '-q', '-m', message);
254
+ f.put('src/old.txt', 'original\n'); f.git('add', '.'); commit('test: base');
255
+ f.git('branch', '-M', 'main'); f.git('checkout', '-q', '-b', 'feature');
256
+ f.put('src/main.mjs', 'export const value=2;\n');
257
+ f.git('mv', 'src/old.txt', 'src/new.txt'); f.git('add', 'src/main.mjs'); commit('test: source');
258
+ if (ignoredSubmodule) {
259
+ const object = f.git('rev-parse', 'HEAD').trim();
260
+ f.git('update-index', '--add', '--cacheinfo', `160000,${object},vendor`);
261
+ commit('test: submodule');
262
+ f.git('config', 'diff.ignoreSubmodules', 'all');
263
+ }
264
+ if (dirty) f.put('src/main.mjs', 'export const value=3; // not committed\n');
265
+ const made = prepareReview({ root: f.root, id: 'merge', execution: 'review', scope });
266
+ for (const item of made.report.documentation) Object.assign(item, { decision: 'no-impact', reason: 'Documento examinado na fixture.' });
267
+ const proof = '.agent-tests/merge/review/proof.txt'; f.put(proof, 'Prova sintetica para testar validacao.\n');
268
+ for (const item of made.report.validation) Object.assign(item, { method: 'fixture', prerequisite: 'fixture local', assertion: 'snapshot comparado', evidence: [proof], status: 'passed' });
269
+ made.report.review = { status: 'approved', reviewer: 'simulacao de teste', findings: [] };
270
+ f.put(made.file, JSON.stringify(made.report));
271
+ if (seal) sealReview(made.file, { root: f.root });
272
+ return { ...f, ...made, commit };
273
+ }
274
+
275
+ test('check-merge prova fast-forward completo sem alterar indice ou arquivos humanos', t => {
276
+ const f = mergeFixture(t);
277
+ f.put('human.txt', 'staged humano\n'); f.git('add', 'human.txt'); f.put('human.txt', 'alteracao humana\n');
278
+ const index = readFileSync(join(f.root, '.git/index')), human = readFileSync(join(f.root, 'human.txt'));
279
+ const result = checkMerge([f.file], 'refs/heads/main', { root: f.root });
280
+ assert.equal(result.status, 'passed'); assert.equal(result.mode, 'fast-forward'); assert.equal(result.changes, 3);
281
+ assert.equal(result.source, f.git('rev-parse', 'HEAD').trim()); assert.equal(result.target, f.git('rev-parse', 'main').trim());
282
+ assert.deepEqual(readFileSync(join(f.root, '.git/index')), index); assert.deepEqual(readFileSync(join(f.root, 'human.txt')), human);
283
+ });
284
+
285
+ test('check-merge funciona na CLI e rejeita relatorio duplicado por alias', t => {
286
+ const f = mergeFixture(t);
287
+ const script = '.pbai4devs/scripts/revisar-entrega/SCRIPT.mjs', runtime = '.pbai4devs/scripts/lib/runtime.mjs';
288
+ for (const file of [script, runtime]) { mkdirSync(dirname(join(f.root, file)), { recursive: true }); cpSync(join(projectRoot, file), join(f.root, file)); }
289
+ const args = [join(f.root, script), 'check-merge', '--destino', 'refs/heads/main', f.file];
290
+ const run = spawnSync(process.execPath, args, { cwd: f.root, encoding: 'utf8' });
291
+ assert.equal(run.status, 0, run.stderr); assert.equal(JSON.parse(run.stdout).status, 'passed');
292
+ assert.throws(() => checkMerge([f.file, `./${f.file}`], 'refs/heads/main', { root: f.root }), /sem repeticao/);
293
+ });
294
+
295
+ test('check-merge bloqueia conteudo nao commitado e escopo parcial', async t => {
296
+ await t.test('working tree diferente do commit', t => {
297
+ const f = mergeFixture(t, { dirty: true });
298
+ assert.equal(checkReview(f.file, { root: f.root }).status, 'passed');
299
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /difere do commit/);
300
+ });
301
+ await t.test('omissao de arquivo removido', t => {
302
+ const f = mergeFixture(t, { scope: ['src/main.mjs', 'src/new.txt'] });
303
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /sem revisao selada: src\/old.txt/);
304
+ });
305
+ await t.test('relatorio sem selo', t => {
306
+ const f = mergeFixture(t, { seal: false });
307
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /nao selada/);
308
+ });
309
+ });
310
+
311
+ test('check-merge aceita cobertura distribuida por relatorios selados', t => {
312
+ const f = mergeFixture(t, { scope: ['src/main.mjs', 'src/new.txt'] });
313
+ const made = prepareReview({ root: f.root, id: 'merge-extra', execution: 'review', scope: ['src/old.txt'] });
314
+ for (const item of made.report.documentation) Object.assign(item, { decision: 'no-impact', reason: 'Documento examinado na fixture.' });
315
+ const proof = '.agent-tests/merge-extra/review/proof.txt'; f.put(proof, 'Prova sintetica complementar.\n');
316
+ for (const item of made.report.validation) Object.assign(item, { method: 'fixture', prerequisite: 'fixture local', assertion: 'delecao revisada', evidence: [proof], status: 'passed' });
317
+ made.report.review = { status: 'approved', reviewer: 'simulacao de teste', findings: [] };
318
+ f.put(made.file, JSON.stringify(made.report)); sealReview(made.file, { root: f.root });
319
+ const result = checkMerge([f.file, made.file], 'refs/heads/main', { root: f.root });
320
+ assert.equal(result.status, 'passed'); assert.equal(result.reports, 2); assert.equal(result.changes, 3);
321
+ });
322
+
323
+ test('check-merge recusa origem e destino invalidos, divergencia e tipos nao comprovados', async t => {
324
+ await t.test('branch invalida ou identica', t => {
325
+ const f = mergeFixture(t);
326
+ for (const name of ['main', 'refs/heads/missing', 'refs/heads/feature', '--help']) assert.throws(() => checkMerge([f.file], name, { root: f.root }));
327
+ });
328
+ await t.test('historico divergente', t => {
329
+ const f = mergeFixture(t); f.git('checkout', '-q', 'main'); f.put('different.txt', 'destino\n'); f.git('add', 'different.txt'); f.commit('test: target'); f.git('checkout', '-q', 'feature');
330
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /fast-forward/);
331
+ });
332
+ await t.test('HEAD destacado', t => {
333
+ const f = mergeFixture(t); f.git('checkout', '-q', '--detach');
334
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /HEAD destacado/);
335
+ });
336
+ await t.test('origem fora das branches locais', t => {
337
+ const f = mergeFixture(t); f.git('symbolic-ref', 'HEAD', 'refs/remotes/origin/feature');
338
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /origem deve ser uma branch local/);
339
+ });
340
+ await t.test('submodulo ignorado pela configuracao Git', t => {
341
+ const f = mergeFixture(t, { ignoredSubmodule: true });
342
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /modo nao comprovado: vendor/);
343
+ const script = '.pbai4devs/scripts/revisar-entrega/SCRIPT.mjs', runtime = '.pbai4devs/scripts/lib/runtime.mjs';
344
+ for (const file of [script, runtime]) { mkdirSync(dirname(join(f.root, file)), { recursive: true }); cpSync(join(projectRoot, file), join(f.root, file)); }
345
+ const run = spawnSync(process.execPath, [join(f.root, script), 'check-merge', '--destino', 'refs/heads/main', f.file], { cwd: f.root, encoding: 'utf8' });
346
+ assert.equal(run.status, 1); assert.match(run.stderr, /modo nao comprovado: vendor/);
347
+ });
348
+ await t.test('link simbolico', t => {
349
+ const f = mergeFixture(t);
350
+ try { symlinkSync('main.mjs', join(f.root, 'src/link')); }
351
+ catch (error) { if (['EPERM', 'EACCES', 'ENOSYS'].includes(error.code)) return t.skip('Sem permissao de symlink.'); throw error; }
352
+ f.git('add', 'src/link'); f.commit('test: link');
353
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /simbolico|modo nao comprovado/);
354
+ });
355
+ await t.test('arquivo executavel', t => {
356
+ const f = mergeFixture(t); f.git('update-index', '--chmod=+x', 'src/main.mjs'); f.commit('test: mode');
357
+ assert.throws(() => checkMerge([f.file], 'refs/heads/main', { root: f.root }), /modo nao comprovado/);
358
+ });
359
+ });
@@ -8,6 +8,29 @@ description: Integra branches com trabalho concluído e verificado.
8
8
  Confira origem, destino, histórico, identidades, mensagens, alterações locais e
9
9
  verificações; integre somente tarefas completas conforme AGENTS.md. Se os
10
10
  commits a integrar identificarem agentes como autores ou coautores, informe e
11
- não os integre. Se gerar commit de merge, confira as identidades efetivas e siga
11
+ não os integre.
12
+
13
+ Quando houver implementação ou gate exigido, na origem execute isoladamente:
14
+
15
+ ```sh
16
+ node .pbai4devs/scripts/revisar-entrega/SCRIPT.mjs check-merge \
17
+ --destino refs/heads/<branch> <revisao.json> [outros...]
18
+ ```
19
+
20
+ O check exige fast-forward, cobertura de todo o diff e snapshots correspondentes
21
+ ao commit de origem; qualquer falha bloqueia a integração. Registre os hashes
22
+ de origem e destino retornados e confirme ambos imediatamente antes do merge,
23
+ com comandos que interrompam a execução em qualquer falha; não mascare o status.
24
+ Confirme também que `git symbolic-ref HEAD` é a ref de destino e que o HEAD
25
+ corresponde ao hash de destino validado. Se alguma conferência falhar,
26
+ interrompa; se os hashes mudarem, refaça a revisão e o check. Integre com
27
+ `git merge --ff-only <hash-da-origem-validada>`. Essas verificações não são
28
+ atômicas. O check recusa links, submódulos, arquivos executáveis e saídas Git
29
+ maiores que 64 MiB; informe bloqueios sem contorná-los. Preserve trabalho fora
30
+ do escopo.
31
+ Se o resultado integrado divergir do conteúdo revisado, faça nova revisão e
32
+ verificações antes de concluir. Sem implementação, siga os critérios aplicáveis.
33
+
34
+ Se gerar commit de merge, confira as identidades efetivas e siga
12
35
  `.pbai4devs/rules/git/RULE.md` para identidade, mensagem e assinatura. Preserve
13
36
  alterações, resolva conflitos no escopo autorizado e respeite proteções da branch.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ description: Alterações verificáveis por versão do AgentDevKit.
4
4
 
5
5
  # Changelog
6
6
 
7
+ ## 0.0.25 — 2026-11-14
8
+
9
+ - Verifica a cobertura e o conteúdo revisado antes de um merge fast-forward;
10
+ bloqueia situações que o gate não consegue comprovar.
11
+ - Esclarece a validação na origem e a interrupção do merge quando o gate falha.
12
+ - Amplia as provas de preservação em projetos existentes e explica os limites
13
+ do campo `preserved`. Os projetos de exemplo gerados continuam locais.
14
+
7
15
  ## 0.0.24 — 2026-11-14
8
16
 
9
17
  - Preserva catálogos, configuração e blocos existentes sem comprovação de
@@ -183,9 +183,11 @@ Arquivos mistos não passam a pertencer ao kit. Sua visibilidade para o Git
183
183
  também depende da regra aplicada à pasta do cliente.
184
184
 
185
185
  Links simbólicos preexistentes são preservados sem atravessá-los. O campo
186
- `preserved` informa rotas que continuam sob controle do projeto. Um formato
187
- não suportado ou um bloco alterado é informado; `--force` não permite apagar
188
- instruções humanas para reconstruir um apontamento.
186
+ `preserved` lista caminhos próprios encontrados no planejamento ou em colisões,
187
+ sem inventariar rotas não examinadas. A ausência de um caminho não prova que ele
188
+ foi verificado ou preservado. Um formato não suportado ou um bloco alterado é
189
+ informado; `--force` não permite apagar instruções humanas para reconstruir um
190
+ apontamento.
189
191
 
190
192
  Essa preservação não autoriza trocar arquivos gerenciados por links. Links
191
193
  em caminhos já pertencentes ao kit, no hub, no estado ou em ferramentas
@@ -158,7 +158,10 @@ Não apague pastas inteiras para contornar um conflito.
158
158
 
159
159
  Recursos nativos humanos são preservados, inclusive com `--force` e nomes
160
160
  iguais aos do kit. Links humanos são tratados como caminhos opacos; o gerador
161
- não os atravessa. A CLI informa essas rotas em `preserved` no JSON.
161
+ não os atravessa. No JSON, `preserved` lista caminhos humanos encontrados no
162
+ planejamento ou em colisões; não inventaria rotas não examinadas, como um
163
+ fallback não utilizado. A ausência de um caminho não prova que ele foi
164
+ verificado ou preservado.
162
165
 
163
166
  Não edite ou versione `catalog.json`, `skills.json`, `rules.json` ou
164
167
  `agents.json`: são derivados locais. Configurações nativas e documentos
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pathbit/agentdevkit",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "description": "Shared skills, specifications and workflows for AI coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",