@pathbit/agentdevkit 0.0.22 → 0.0.24
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/.pbai4devs/rules/ambiente-local/RULE.md +5 -0
- package/.pbai4devs/rules/git/RULE.md +5 -1
- package/.pbai4devs/scripts/README.md +1 -0
- package/.pbai4devs/scripts/inspecionar-projeto/README.md +6 -0
- package/.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs +218 -18
- package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +59 -11
- package/.pbai4devs/scripts/sincronizar-agentes/tests/CATALOG_OWNERSHIP.mjs +251 -0
- package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +33 -13
- package/.pbai4devs/scripts/testar-maturidade/README.md +12 -3
- package/.pbai4devs/scripts/testar-maturidade/SCRIPT.mjs +65 -25
- package/.pbai4devs/scripts/testar-maturidade/tests/TEST.mjs +150 -5
- package/.pbai4devs/scripts/verificar-configuracao/README.md +22 -0
- package/.pbai4devs/scripts/verificar-configuracao/SCRIPT.mjs +63 -0
- package/.pbai4devs/scripts/verificar-configuracao/tests/TEST.mjs +145 -0
- package/.pbai4devs/skills/desenvolvimento/implementar-backend/SKILL.md +15 -3
- package/.pbai4devs/skills/desenvolvimento/implementar-frontend/SKILL.md +15 -3
- package/.pbai4devs/skills/git/git-diff/SKILL.md +3 -1
- package/.pbai4devs/skills/git/git-status/SKILL.md +2 -1
- package/.pbai4devs/skills/projeto/configurar-projeto/SKILL.md +13 -8
- package/.pbai4devs/skills/projeto/proximos-passos/SKILL.md +4 -2
- package/.pbai4devs/skills/specs/implementar-spec/SKILL.md +14 -2
- package/CHANGELOG.md +17 -0
- package/docs/guides/cli.md +9 -0
- package/docs/guides/troubleshooting.md +12 -6
- package/docs/specs/README.md +3 -0
- package/docs/specs/preimplementation-order/SPEC.md +23 -0
- package/docs/specs/target-paths/SPEC.md +19 -0
- package/package.json +1 -1
- package/src/i18n.mjs +3 -3
- package/src/install.mjs +121 -23
- package/src/next.mjs +26 -9
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { dirname, join } from 'node:path';
|
|
4
5
|
import childProcess, { spawnSync } from 'node:child_process';
|
|
5
6
|
import { syncBuiltinESMExports } from 'node:module';
|
|
6
7
|
import { createHash } from 'node:crypto';
|
|
7
8
|
import test from 'node:test';
|
|
8
9
|
import { projectRoot, requireNode } from '../../lib/runtime.mjs';
|
|
10
|
+
import { adapterManifest } from '../../lib/adapters.mjs';
|
|
9
11
|
import { copyAgentStructure, prepareExamples as prepare, registeredRoots, resourceTargets, SCENARIOS } from '../SCRIPT.mjs';
|
|
10
12
|
|
|
11
13
|
// As fixtures pequenas isolam a cópia; o caso source-only usa o renderizador real.
|
|
@@ -32,6 +34,23 @@ function fixture(t) {
|
|
|
32
34
|
return { root, put, outputRoot: join(root, 'examples/generated') };
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
function controlledSource(root) {
|
|
38
|
+
const source = join(root, 'controlled-source');
|
|
39
|
+
// Only four explicit resources; exclude local state and additional skills.
|
|
40
|
+
for (const path of ['.pbai4devs/skills/projeto/criar-skill/SKILL.md', '.pbai4devs/templates/gitignore/clients.json', '.pbai4devs/templates/gitignore/agents.ignore', '.pbai4devs/templates/gitignore/shared.ignore']) {
|
|
41
|
+
let current = projectRoot;
|
|
42
|
+
for (const part of path.split('/')) {
|
|
43
|
+
current = join(current, part);
|
|
44
|
+
assert.equal(lstatSync(current).isSymbolicLink(), false, `Fonte não pode ser link: ${path}`);
|
|
45
|
+
}
|
|
46
|
+
const destination = join(source, path);
|
|
47
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
48
|
+
copyFileSync(join(projectRoot, path), destination);
|
|
49
|
+
}
|
|
50
|
+
writeFileSync(join(source, '.gitignore'), 'node_modules/\n');
|
|
51
|
+
return source;
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
test('copia recursos e configurações, excluindo payloads, caches e segredos', t => {
|
|
36
55
|
const { root, put } = fixture(t);
|
|
37
56
|
put('.pbai4devs/tools/demo/TOOL.json', '{}'); put('.pbai4devs/tools/demo/SCRIPT.mjs', 'export {};'); put('.pbai4devs/tools/demo/README.md', '# Tool');
|
|
@@ -110,6 +129,34 @@ test('troca de ancestral por symlink antes da criação não escreve no destino
|
|
|
110
129
|
}
|
|
111
130
|
});
|
|
112
131
|
|
|
132
|
+
test('troca de saída durante escrita é detectada, sem alegar atomicidade', t => {
|
|
133
|
+
const { root, outputRoot } = fixture(t);
|
|
134
|
+
const target = join(outputRoot, 'race-output'), outside = mkdtempSync(join(root, 'race-outside-'));
|
|
135
|
+
const sentinel = join(outside, 'sentinel'); writeFileSync(sentinel, 'Não alterar.\n');
|
|
136
|
+
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
137
|
+
const probe = join(root, 'race-link-probe');
|
|
138
|
+
try { symlinkSync(outside, probe, linkType); }
|
|
139
|
+
catch (error) { if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error.code)) { t.skip('Links simbólicos indisponíveis.'); return; } throw error; }
|
|
140
|
+
unlinkSync(probe);
|
|
141
|
+
const original = fs.writeFileSync; let injected = false;
|
|
142
|
+
try {
|
|
143
|
+
fs.writeFileSync = (file, ...args) => {
|
|
144
|
+
if (!injected && String(file) === join(target, '.maturity-suite.json')) {
|
|
145
|
+
renameSync(target, `${target}-moved`); symlinkSync(outside, target, linkType); injected = true;
|
|
146
|
+
}
|
|
147
|
+
return original.call(fs, file, ...args);
|
|
148
|
+
};
|
|
149
|
+
syncBuiltinESMExports();
|
|
150
|
+
assert.throws(() => prepareExamples({ sourceRoot: root, outputRoot: target, scenarios: [SCENARIOS[0]], initializeGit: false }), /link|alterado/i);
|
|
151
|
+
} finally { fs.writeFileSync = original; syncBuiltinESMExports(); }
|
|
152
|
+
assert.equal(injected, true);
|
|
153
|
+
assert.equal(readFileSync(sentinel, 'utf8'), 'Não alterar.\n');
|
|
154
|
+
// O write já em curso pode ter criado o marcador no destino externo: a
|
|
155
|
+
// verificação detecta a troca, mas não é uma operação atômica de diretório.
|
|
156
|
+
assert.equal(existsSync(join(outside, '.maturity-suite.json')), true);
|
|
157
|
+
assert.equal(existsSync(join(outside, 'REPORT.json')), false);
|
|
158
|
+
});
|
|
159
|
+
|
|
113
160
|
test('cada destino padrão é exclusivo, preserva provas anteriores e permanece ignorado', t => {
|
|
114
161
|
const { root, outputRoot } = fixture(t);
|
|
115
162
|
const git = (...args) => {
|
|
@@ -172,16 +219,16 @@ test('preparação interrompida não altera resultado anterior nem declara suces
|
|
|
172
219
|
assert.doesNotMatch(readFileSync(index, 'utf8'), /status: passed|\| passed \|/);
|
|
173
220
|
});
|
|
174
221
|
|
|
175
|
-
test('
|
|
222
|
+
test('sem estado comprovado conserva entradas humanas mesmo com template', t => {
|
|
176
223
|
const { root, put } = fixture(t);
|
|
177
224
|
const template = '---\ndescription: Entrada genérica.\n---\n\n# Agentes do projeto\n';
|
|
178
225
|
put('AGENTS.md', '# Produto do distribuidor\n\n[PRD](docs/prd.md)\n');
|
|
179
226
|
put('.pbai4devs/templates/entrypoints/AGENTS.md', template);
|
|
180
227
|
const destination = join(root, 'copy');
|
|
181
228
|
const copied = copyAgentStructure(root, destination);
|
|
182
|
-
assert.equal(readFileSync(join(destination, 'AGENTS.md'), 'utf8'),
|
|
229
|
+
assert.equal(readFileSync(join(destination, 'AGENTS.md'), 'utf8'), '# Produto do distribuidor\n\n[PRD](docs/prd.md)\n');
|
|
183
230
|
assert.equal(readFileSync(join(destination, 'CLAUDE.md'), 'utf8'), 'fixture\n');
|
|
184
|
-
assert.equal(copied.entrypoints['AGENTS.md'], '
|
|
231
|
+
assert.equal(copied.entrypoints['AGENTS.md'], 'AGENTS.md');
|
|
185
232
|
assert.equal(copied.entrypoints['CLAUDE.md'], 'CLAUDE.md');
|
|
186
233
|
assert.equal(readFileSync(join(root, 'AGENTS.md'), 'utf8'), '# Produto do distribuidor\n\n[PRD](docs/prd.md)\n');
|
|
187
234
|
});
|
|
@@ -315,7 +362,9 @@ test('checkout de fontes sem manifesto deriva clientes em memória', t => {
|
|
|
315
362
|
test('prepare gera apontamentos reais mesmo sem pastas de clientes na origem', t => {
|
|
316
363
|
const { root } = fixture(t);
|
|
317
364
|
const sourceRoot = join(root, 'source-only');
|
|
318
|
-
copyAgentStructure(
|
|
365
|
+
copyAgentStructure(controlledSource(root), sourceRoot);
|
|
366
|
+
// Model a clean source checkout regardless of ignored local developer data.
|
|
367
|
+
for (const path of ['.pbai4devs/espelhos.conf', '.pbai4devs/catalog.json', '.pbai4devs/skills.json', '.pbai4devs/agents.json', '.pbai4devs/rules.json', '.pbai4devs/state/agentdevkit.json', '.pbai4devs/state/derived.json', '.pbai4devs/estado/agentdevkit.json', '.pbai4devs/estado/derived.json']) rmSync(join(sourceRoot, path), { force: true });
|
|
319
368
|
const roots = registeredRoots(sourceRoot);
|
|
320
369
|
for (const client of roots) rmSync(join(sourceRoot, client), { recursive: true, force: true });
|
|
321
370
|
const [example] = prepare({ sourceRoot, scenarios: [SCENARIOS[0]], initializeGit: false });
|
|
@@ -325,6 +374,102 @@ test('prepare gera apontamentos reais mesmo sem pastas de clientes na origem', t
|
|
|
325
374
|
for (const file of ['src/cli.mjs', 'bin/agentdevkit.mjs', 'scripts/release/pipeline.mjs', 'tests/cli.test.mjs']) assert.equal(existsSync(join(example.root, file)), false, file);
|
|
326
375
|
});
|
|
327
376
|
|
|
377
|
+
for (const withState of [false, true]) test(`prepare preserva catálogos humanos na cópia e na origem (estado=${withState})`, t => {
|
|
378
|
+
const { root } = fixture(t), sourceRoot = join(root, `source-catalog-${withState}`);
|
|
379
|
+
copyAgentStructure(controlledSource(root), sourceRoot);
|
|
380
|
+
// Source checkouts do not include generated manifests; provide this fixture
|
|
381
|
+
// explicitly instead of depending on ignored files in the developer's tree.
|
|
382
|
+
writeFileSync(join(sourceRoot, '.pbai4devs/espelhos.conf'), adapterManifest(sourceRoot, { clients: ['codex'] }));
|
|
383
|
+
const state = join(sourceRoot, '.pbai4devs/state/agentdevkit.json');
|
|
384
|
+
const derived = join(sourceRoot, '.pbai4devs/state/derived.json');
|
|
385
|
+
rmSync(state, { force: true }); rmSync(derived, { force: true }); // Só cópias criadas nesta fixture.
|
|
386
|
+
if (withState) {
|
|
387
|
+
mkdirSync(dirname(state), { recursive: true });
|
|
388
|
+
writeFileSync(state, JSON.stringify({ schemaVersion: 1, version: '0.0.24', language: 'pt', clients: ['codex'], files: {}, blocks: {} }) + '\n');
|
|
389
|
+
}
|
|
390
|
+
const originals = {};
|
|
391
|
+
for (const name of ['catalog.json', 'skills.json', 'agents.json', 'rules.json']) {
|
|
392
|
+
const file = join(sourceRoot, '.pbai4devs', name), content = `{ "arquivo humano": ${JSON.stringify(name)} }\n`;
|
|
393
|
+
writeFileSync(file, content); originals[name] = content;
|
|
394
|
+
}
|
|
395
|
+
for (const path of ['.pbai4devs/.gitignore', '.codex/.gitignore']) {
|
|
396
|
+
const file = join(sourceRoot, path);
|
|
397
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
398
|
+
writeFileSync(file, '# BEGIN AGENT IGNORE\n# Alteração humana dentro do bloco\n!/nao-apagar/\n# END AGENT IGNORE\n');
|
|
399
|
+
}
|
|
400
|
+
const skill = '.pbai4devs/skills/projeto/equipe-local/SKILL.md';
|
|
401
|
+
const skillFile = join(sourceRoot, skill);
|
|
402
|
+
mkdirSync(dirname(skillFile), { recursive: true });
|
|
403
|
+
writeFileSync(skillFile, '---\nname: equipe-local\ndescription: Conteúdo próprio.\n---\n\n# Equipe local\n');
|
|
404
|
+
const sourceIgnore = readFileSync(join(sourceRoot, '.gitignore'));
|
|
405
|
+
const sourceConfig = readFileSync(join(sourceRoot, '.pbai4devs/espelhos.conf'));
|
|
406
|
+
const [example] = prepare({ sourceRoot, scenarios: [SCENARIOS[0]], initializeGit: false });
|
|
407
|
+
assert.equal(example.copied.catalogSnapshots.length, 4);
|
|
408
|
+
for (const [name, content] of Object.entries(originals)) {
|
|
409
|
+
assert.equal(readFileSync(join(sourceRoot, '.pbai4devs', name), 'utf8'), content);
|
|
410
|
+
assert.equal(readFileSync(join(example.root, '.agent-tests/testar-maturidade/original-catalogs', name), 'utf8'), content);
|
|
411
|
+
assert.doesNotMatch(readFileSync(join(example.root, '.pbai4devs', name), 'utf8'), /arquivo humano/);
|
|
412
|
+
}
|
|
413
|
+
for (const path of ['.pbai4devs/.gitignore', '.codex/.gitignore']) {
|
|
414
|
+
const original = readFileSync(join(sourceRoot, path));
|
|
415
|
+
const saved = example.copied.overriddenSnapshots.find(item => item.source === path);
|
|
416
|
+
assert.ok(saved, path);
|
|
417
|
+
assert.deepEqual(readFileSync(join(example.root, saved.snapshot)), original);
|
|
418
|
+
assert.match(original.toString('utf8'), /Alteração humana/);
|
|
419
|
+
assert.doesNotMatch(readFileSync(join(example.root, path), 'utf8'), /Alteração humana/);
|
|
420
|
+
}
|
|
421
|
+
assert.equal(readFileSync(join(example.root, skill), 'utf8'), readFileSync(skillFile, 'utf8'));
|
|
422
|
+
assert.deepEqual(readFileSync(join(sourceRoot, '.gitignore')), sourceIgnore);
|
|
423
|
+
assert.deepEqual(readFileSync(join(sourceRoot, '.pbai4devs/espelhos.conf')), sourceConfig);
|
|
424
|
+
const report = JSON.parse(readFileSync(join(dirname(example.root), 'REPORT.json'), 'utf8'));
|
|
425
|
+
assert.deepEqual(report.copies[0].catalogSnapshots, example.copied.catalogSnapshots);
|
|
426
|
+
assert.match(readFileSync(join(example.root, 'README.md'), 'utf8'), /não um teste no projeto original/);
|
|
427
|
+
assert.equal(existsSync(state), withState);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test('arquiva arquivos substituídos mesmo quando o cliente não está selecionado', t => {
|
|
431
|
+
const { root, put } = fixture(t);
|
|
432
|
+
put('.pbai4devs/espelhos.conf', 'agente|.codex|plano\n');
|
|
433
|
+
const files = ['.vscode/settings.json', '.github/workflows/legacy.yml'];
|
|
434
|
+
for (const path of files) put(path, `Conteúdo de equipe: ${path}\r\n`);
|
|
435
|
+
const [example] = prepareExamples({ sourceRoot: root, scenarios: [SCENARIOS.find(item => item.id === 'legado')], initializeGit: false });
|
|
436
|
+
for (const path of files) {
|
|
437
|
+
const saved = example.copied.overriddenSnapshots.filter(item => item.source === path);
|
|
438
|
+
assert.equal(saved.length, 1, path);
|
|
439
|
+
assert.deepEqual(readFileSync(join(example.root, saved[0].snapshot)), readFileSync(join(root, path)));
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('arquivo de cliente não selecionado com ancestral simbólico é recusado', t => {
|
|
444
|
+
const { root, put } = fixture(t);
|
|
445
|
+
put('.pbai4devs/espelhos.conf', 'agente|.codex|plano\n');
|
|
446
|
+
const outside = mkdtempSync(join(root, 'outside-'));
|
|
447
|
+
writeFileSync(join(outside, 'settings.json'), 'Conteúdo externo.\n');
|
|
448
|
+
rmSync(join(root, '.vscode'), { recursive: true });
|
|
449
|
+
try { symlinkSync(outside, join(root, '.vscode'), 'dir'); }
|
|
450
|
+
catch (error) { if (['EPERM', 'EACCES', 'ENOTSUP'].includes(error.code)) { t.skip('Links simbólicos indisponíveis.'); return; } throw error; }
|
|
451
|
+
assert.throws(() => prepareExamples({ sourceRoot: root, scenarios: [SCENARIOS.find(item => item.id === 'legado')], initializeGit: false }), /link/i);
|
|
452
|
+
assert.equal(readFileSync(join(outside, 'settings.json'), 'utf8'), 'Conteúdo externo.\n');
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test('arquivos da fonte substituídos por fixtures têm cópias byte a byte', t => {
|
|
456
|
+
const { root, put } = fixture(t);
|
|
457
|
+
const files = ['README.md', 'Makefile', '.dockerignore', '.env.example', '.pbai4devs/state/README.md', '.pbai4devs/state/configuracao/ESTADO.md', '.vscode/settings.json', '.github/workflows/legacy.yml'];
|
|
458
|
+
const contents = Object.fromEntries(files.map(file => [file, `Conteúdo humano sintético: ${file}\r\n`]));
|
|
459
|
+
for (const [file, content] of Object.entries(contents)) put(file, content);
|
|
460
|
+
const cases = [SCENARIOS.find(item => item.id === 'app-configurada'), SCENARIOS.find(item => item.id === 'legado')];
|
|
461
|
+
const examples = prepareExamples({ sourceRoot: root, scenarios: cases, initializeGit: false });
|
|
462
|
+
for (const example of examples) {
|
|
463
|
+
for (const { source, snapshot } of example.copied.overriddenSnapshots) {
|
|
464
|
+
assert.equal(readFileSync(join(example.root, snapshot), 'utf8'), contents[source], source);
|
|
465
|
+
assert.equal(readFileSync(join(root, source), 'utf8'), contents[source], source);
|
|
466
|
+
}
|
|
467
|
+
for (const path of ['README.md', 'Makefile', '.pbai4devs/state/README.md', '.pbai4devs/state/configuracao/ESTADO.md']) assert.ok(example.copied.overriddenSnapshots.some(item => item.source === path), path);
|
|
468
|
+
const expected = example.id === 'legado' ? ['.vscode/settings.json', '.github/workflows/legacy.yml'] : ['.dockerignore', '.env.example'];
|
|
469
|
+
for (const path of expected) assert.ok(example.copied.overriddenSnapshots.some(item => item.source === path), path);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
328
473
|
test('saída não pode sobrepor recursos de origem nem escapar das áreas de teste', t => {
|
|
329
474
|
const { root } = fixture(t);
|
|
330
475
|
const original = readFileSync(join(root, '.pbai4devs/espelhos.conf'), 'utf8');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Confere os indicadores registrados antes da implementação.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Verificar configuração
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
node .pbai4devs/scripts/executar-script/SCRIPT.mjs verificar-configuracao
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Somente leitura: retorna `1` se o estado estiver ausente, inacessível,
|
|
12
|
+
ambíguo ou com preparação ou configuração pendente. Retorna `0` e
|
|
13
|
+
`verificar-evidencias` quando ambos os indicadores registram `concluido`.
|
|
14
|
+
Para evitar ambiguidades, recusa chaves complexas e tags, âncoras, aliases ou
|
|
15
|
+
mesclagens no topo do cabeçalho. Links simbólicos detectados são recusados;
|
|
16
|
+
a leitura não é uma trava
|
|
17
|
+
atômica contra mudanças simultâneas de caminhos.
|
|
18
|
+
Não valida o YAML inteiro, a infraestrutura ou as provas; confira-os antes de
|
|
19
|
+
escrever código. O comando não bloqueia alterações de quem ignore seu resultado.
|
|
20
|
+
|
|
21
|
+
O estado legado é reconhecido sem migração; estados em conflito são recusados.
|
|
22
|
+
Erros de uso retornam `2`.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { isMain, projectRoot, requireNode } from '../lib/runtime.mjs';
|
|
5
|
+
import { stateDirectory } from '../lib/state-path.mjs';
|
|
6
|
+
|
|
7
|
+
const limit = 'O estado não comprova evidências nem impede um agente de escrever.';
|
|
8
|
+
const blocked = motivo => ({ status: 'bloqueado', motivo, limite: limit });
|
|
9
|
+
|
|
10
|
+
// Read only the two indicators; never print arbitrary state content.
|
|
11
|
+
export function verifyConfiguration(root = projectRoot) {
|
|
12
|
+
requireNode();
|
|
13
|
+
try {
|
|
14
|
+
root = realpathSync(resolve(root));
|
|
15
|
+
const parts = `${stateDirectory(root)}/configuracao/ESTADO.md`.split('/');
|
|
16
|
+
let current = root; let info;
|
|
17
|
+
for (let index = 0; index < parts.length; index++) {
|
|
18
|
+
current = join(current, parts[index]);
|
|
19
|
+
info = lstatSync(current);
|
|
20
|
+
if (info.isSymbolicLink() || (index < parts.length - 1 && !info.isDirectory())) return blocked('estado-inseguro');
|
|
21
|
+
}
|
|
22
|
+
if (!info.isFile() || info.size > 1024 * 1024) return blocked('estado-invalido');
|
|
23
|
+
const text = readFileSync(current, 'utf8').replaceAll('\r\n', '\n');
|
|
24
|
+
const header = text.match(/^---\n([\s\S]*?)\n---(?:\n|$)/)?.[1];
|
|
25
|
+
if (header === undefined) return blocked('estado-invalido');
|
|
26
|
+
const values = {};
|
|
27
|
+
for (const line of header.split('\n')) {
|
|
28
|
+
if (!line.trim() || line.trimStart().startsWith('#') || /^[ \t]/.test(line)) continue;
|
|
29
|
+
// Explicit mapping keys and merge keys may hide duplicate indicators.
|
|
30
|
+
if (/^(?:\?|<<\s*:)/.test(line)) return blocked('estado-invalido');
|
|
31
|
+
const match = line.match(/^("(?:[^"\\]|\\.)*"|'(?:[^']|'')*'|[a-zA-Z_][a-zA-Z0-9_.-]*)[ \t]*:(.*)$/);
|
|
32
|
+
if (!match) return blocked('estado-invalido');
|
|
33
|
+
if (/^[!&*]/.test(match[2].trim())) return blocked('estado-invalido');
|
|
34
|
+
let key = match[1];
|
|
35
|
+
if (key.startsWith('"')) {
|
|
36
|
+
try { key = JSON.parse(key); } catch { return blocked('estado-invalido'); }
|
|
37
|
+
} else if (key.startsWith("'")) key = key.slice(1, -1).replaceAll("''", "'");
|
|
38
|
+
if (!['preparacao', 'configuracao_tecnica'].includes(key)) continue;
|
|
39
|
+
if (Object.hasOwn(values, key)) return blocked('estado-invalido');
|
|
40
|
+
const value = match[2].trim().match(/^(?:concluido|pendente|'(?:concluido|pendente)'|"(?:concluido|pendente)")(?:[ \t]+#.*)?$/)?.[0];
|
|
41
|
+
if (!value) return blocked('estado-invalido');
|
|
42
|
+
values[key] = value.replace(/[ \t]+#.*$/, '').replace(/^['"]|['"]$/g, '');
|
|
43
|
+
}
|
|
44
|
+
if (!Object.hasOwn(values, 'preparacao') || !Object.hasOwn(values, 'configuracao_tecnica')) return blocked('estado-invalido');
|
|
45
|
+
if (values.preparacao !== 'concluido' || values.configuracao_tecnica !== 'concluido') return blocked('configuracao-pendente');
|
|
46
|
+
return { status: 'verificar-evidencias', estado: values, limite: limit };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return blocked('estado-ausente');
|
|
49
|
+
return blocked('estado-inacessivel-ou-inseguro');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function main(args = process.argv.slice(2)) {
|
|
54
|
+
if (args.length) throw new Error('Uso: verificar-configuracao sem argumentos.');
|
|
55
|
+
const report = verifyConfiguration();
|
|
56
|
+
console.log(JSON.stringify(report));
|
|
57
|
+
return report.status === 'bloqueado' ? 1 : 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (isMain(import.meta.url)) {
|
|
61
|
+
try { process.exitCode = main(); }
|
|
62
|
+
catch (error) { console.error(error.message); process.exitCode = 2; }
|
|
63
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmdirSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { projectRoot } from '../../lib/runtime.mjs';
|
|
7
|
+
import { verifyConfiguration } from '../SCRIPT.mjs';
|
|
8
|
+
|
|
9
|
+
function fixture(t) {
|
|
10
|
+
const base = join(projectRoot, '.agent-tests'); mkdirSync(base, { recursive: true });
|
|
11
|
+
const root = mkdtempSync(join(base, 'config-gate-'));
|
|
12
|
+
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
13
|
+
const write = (file, content) => {
|
|
14
|
+
const destination = join(root, file); mkdirSync(dirname(destination), { recursive: true });
|
|
15
|
+
writeFileSync(destination, content);
|
|
16
|
+
};
|
|
17
|
+
for (const file of ['lib/runtime.mjs', 'lib/state-path.mjs', 'lib/script-config.mjs', 'executar-script/SCRIPT.mjs', 'verificar-configuracao/SCRIPT.mjs']) {
|
|
18
|
+
const destination = join(root, '.pbai4devs/scripts', file);
|
|
19
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
20
|
+
copyFileSync(join(projectRoot, '.pbai4devs/scripts', file), destination);
|
|
21
|
+
}
|
|
22
|
+
const state = (body, folder = 'state') => write(`.pbai4devs/${folder}/configuracao/ESTADO.md`, body);
|
|
23
|
+
const run = (...args) => {
|
|
24
|
+
const file = join(root, '.pbai4devs/scripts/verificar-configuracao/SCRIPT.mjs');
|
|
25
|
+
const result = spawnSync(process.execPath, [file, ...args], { encoding: 'utf8' });
|
|
26
|
+
assert.ifError(result.error);
|
|
27
|
+
return { ...result, report: result.stdout ? JSON.parse(result.stdout) : null };
|
|
28
|
+
};
|
|
29
|
+
return { root, state, write, run };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const document = (preparation, configuration) => `---\npreparacao: ${preparation}\nconfiguracao_tecnica: ${configuration}\ndocumentos:\n prd: docs/PRD.md\n---\n\n# Estado\n`;
|
|
33
|
+
|
|
34
|
+
test('bloqueia antes do código quando o estado está ausente ou pendente', t => {
|
|
35
|
+
const { root, state, run } = fixture(t);
|
|
36
|
+
assert.deepEqual(run().report.motivo, 'estado-ausente');
|
|
37
|
+
assert.equal(run().status, 1);
|
|
38
|
+
for (const [preparation, configuration] of [['pendente', 'pendente'], ['concluido', 'pendente'], ['pendente', 'concluido']]) {
|
|
39
|
+
state(document(preparation, configuration));
|
|
40
|
+
const result = run(); assert.equal(result.status, 1);
|
|
41
|
+
assert.equal(result.report.motivo, 'configuracao-pendente');
|
|
42
|
+
assert.equal(existsSync(join(root, 'src')), false);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('aceita o modelo real e mantém provas da aplicação como verificação separada', t => {
|
|
47
|
+
const { root, state, run } = fixture(t);
|
|
48
|
+
const template = readFileSync(join(projectRoot, '.pbai4devs/templates/configuracao/TEMPLATE.md'), 'utf8');
|
|
49
|
+
state(template); assert.equal(run().report.motivo, 'configuracao-pendente');
|
|
50
|
+
state(template.replace('preparacao: pendente', 'preparacao: concluido')
|
|
51
|
+
.replace('configuracao_tecnica: pendente', 'configuracao_tecnica: concluido'));
|
|
52
|
+
assert.equal(run().report.status, 'verificar-evidencias');
|
|
53
|
+
assert.equal(existsSync(join(root, 'src')), false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('indicadores concluídos exigem revisão adicional de evidências e não alteram Git', t => {
|
|
57
|
+
const { root, state, write, run } = fixture(t);
|
|
58
|
+
state(document('"concluido" # aprovado', "'concluido'"));
|
|
59
|
+
write('.gitignore', 'node_modules/\n'); write('AGENTS.md', '# Instruções humanas\n');
|
|
60
|
+
assert.equal(spawnSync('git', ['init', '--quiet', root]).status, 0);
|
|
61
|
+
assert.equal(spawnSync('git', ['-C', root, 'add', '.gitignore', 'AGENTS.md']).status, 0);
|
|
62
|
+
const index = readFileSync(join(root, '.git/index'));
|
|
63
|
+
const statePath = join(root, '.pbai4devs/state/configuracao/ESTADO.md');
|
|
64
|
+
const original = readFileSync(statePath), ignore = readFileSync(join(root, '.gitignore'));
|
|
65
|
+
const result = run(); assert.equal(result.status, 0);
|
|
66
|
+
assert.equal(result.report.status, 'verificar-evidencias');
|
|
67
|
+
assert.match(result.report.limite, /não comprova evidências/);
|
|
68
|
+
assert.deepEqual(readFileSync(statePath), original);
|
|
69
|
+
assert.deepEqual(readFileSync(join(root, '.git/index')), index);
|
|
70
|
+
assert.deepEqual(readFileSync(join(root, '.gitignore')), ignore);
|
|
71
|
+
assert.equal(existsSync(join(root, 'src')), false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('recusa indicadores ausentes, desconhecidos, duplicados e não expõe valores', t => {
|
|
75
|
+
const { state, run } = fixture(t);
|
|
76
|
+
const secret = 'SENSITIVE_VALUE_47849';
|
|
77
|
+
for (const value of [
|
|
78
|
+
'preparacao: concluido',
|
|
79
|
+
'preparacao: concluido\nconfiguracao_tecnica: ausente',
|
|
80
|
+
'preparacao: concluido\npreparacao: pendente\nconfiguracao_tecnica: concluido',
|
|
81
|
+
'preparacao: concluido\nconfiguracao_tecnica: concluido\n"configuracao_tecnica": pendente',
|
|
82
|
+
"'preparacao': concluido\npreparacao: pendente\nconfiguracao_tecnica: concluido",
|
|
83
|
+
'preparacao: concluido\n"prepara\\u0063ao": pendente\nconfiguracao_tecnica: concluido',
|
|
84
|
+
'preparacao: concluido\nconfiguracao_tecnica: concluido\n!!str configuracao_tecnica: pendente',
|
|
85
|
+
'preparacao: concluido\nconfiguracao_tecnica: concluido\npadrao: &base { configuracao_tecnica: pendente }',
|
|
86
|
+
'preparacao: concluido\nconfiguracao_tecnica: concluido\npadrao: *base',
|
|
87
|
+
'preparacao: concluido\nconfiguracao_tecnica: concluido\npadrao: !!str valor',
|
|
88
|
+
'preparacao: concluido\nconfiguracao_tecnica: concluido\n<<: *base',
|
|
89
|
+
'preparacao: concluido\nconfiguracao_tecnica: concluido\n? configuracao_tecnica\n: pendente',
|
|
90
|
+
`preparacao: concluido\nconfiguracao_tecnica: ${secret}`,
|
|
91
|
+
'preparacao: concluido\nconfiguracao_tecnica: true',
|
|
92
|
+
]) {
|
|
93
|
+
state(`---\n${value}\n---\n# Estado\n`);
|
|
94
|
+
const result = run(); assert.equal(result.status, 1);
|
|
95
|
+
assert.equal(result.report.motivo, 'estado-invalido');
|
|
96
|
+
assert.ok(!result.stdout.includes(secret)); assert.ok(!result.stderr.includes(secret));
|
|
97
|
+
}
|
|
98
|
+
state('preparacao: concluido\nconfiguracao_tecnica: concluido\n');
|
|
99
|
+
assert.equal(run().report.motivo, 'estado-invalido');
|
|
100
|
+
state('---\n"preparacao": concluido\n\'configuracao_tecnica\': concluido\n---\n# Estado\n');
|
|
101
|
+
assert.equal(run().report.status, 'verificar-evidencias');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('aceita estado legado e recusa coexistência de árvores de estado', t => {
|
|
105
|
+
const { state, run } = fixture(t);
|
|
106
|
+
state(document('concluido', 'concluido'), 'estado');
|
|
107
|
+
assert.equal(run().report.status, 'verificar-evidencias');
|
|
108
|
+
state(document('concluido', 'concluido'));
|
|
109
|
+
const result = run(); assert.equal(result.status, 1);
|
|
110
|
+
assert.equal(result.report.motivo, 'estado-inacessivel-ou-inseguro');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('não segue link humano em arquivo ou pasta de estado', t => {
|
|
114
|
+
const { root, state, run } = fixture(t);
|
|
115
|
+
const outside = join(root, 'fora'); mkdirSync(outside);
|
|
116
|
+
const target = join(outside, 'ESTADO.md'); writeFileSync(target, document('concluido', 'concluido'));
|
|
117
|
+
const directory = join(root, '.pbai4devs/state/configuracao'); mkdirSync(directory, { recursive: true });
|
|
118
|
+
try { symlinkSync(target, join(directory, 'ESTADO.md')); }
|
|
119
|
+
catch (error) { if (['EPERM', 'EACCES', 'ENOSYS'].includes(error.code)) { t.skip('Sem permissão para links.'); return; } throw error; }
|
|
120
|
+
assert.equal(run().report.motivo, 'estado-inseguro');
|
|
121
|
+
assert.equal(readFileSync(target, 'utf8'), document('concluido', 'concluido'));
|
|
122
|
+
unlinkSync(join(directory, 'ESTADO.md')); rmdirSync(directory);
|
|
123
|
+
symlinkSync(outside, directory, process.platform === 'win32' ? 'junction' : 'dir');
|
|
124
|
+
assert.equal(run().report.motivo, 'estado-inseguro');
|
|
125
|
+
assert.equal(readFileSync(target, 'utf8'), document('concluido', 'concluido'));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('exige Node e rejeita argumentos extras sem ler outro projeto', t => {
|
|
129
|
+
const { root, run } = fixture(t);
|
|
130
|
+
const result = run('--root', root); assert.equal(result.status, 2);
|
|
131
|
+
assert.match(result.stderr, /sem argumentos/);
|
|
132
|
+
assert.equal(verifyConfiguration(root).status, 'bloqueado');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('seletor de scripts propaga bloqueio e exige revisão de evidências', t => {
|
|
136
|
+
const { root, state } = fixture(t);
|
|
137
|
+
const selector = join(root, '.pbai4devs/scripts/executar-script/SCRIPT.mjs');
|
|
138
|
+
const execute = () => spawnSync(process.execPath, [selector, 'verificar-configuracao'], { cwd: root, encoding: 'utf8' });
|
|
139
|
+
state(document('concluido', 'pendente'));
|
|
140
|
+
const pending = execute(); assert.ifError(pending.error); assert.equal(pending.status, 1);
|
|
141
|
+
assert.match(pending.stdout, /"motivo":"configuracao-pendente"/);
|
|
142
|
+
state(document('concluido', 'concluido'));
|
|
143
|
+
const recorded = execute(); assert.ifError(recorded.error); assert.equal(recorded.status, 0);
|
|
144
|
+
assert.match(recorded.stdout, /"status":"verificar-evidencias"/);
|
|
145
|
+
});
|
|
@@ -5,9 +5,21 @@ description: Implementa mudanças de backend com contratos, dados e provas de co
|
|
|
5
5
|
|
|
6
6
|
# Implementar backend
|
|
7
7
|
|
|
8
|
-
Siga
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Siga o escopo aprovado, os contratos e a stack existentes. Ao implementar uma
|
|
9
|
+
spec, confirme spec e plano aprovados e, antes de alterar código, execute
|
|
10
|
+
`node .pbai4devs/scripts/executar-script/SCRIPT.mjs verificar-configuracao`.
|
|
11
|
+
Se o estado estiver ausente, inválido ou pendente, ou faltar prova obrigatória,
|
|
12
|
+
pare e informe o responsável. Saída zero ainda exige conferir as evidências.
|
|
13
|
+
Siga a ordem e as pré-condições já acordadas em
|
|
14
|
+
[implementar-spec](../../specs/implementar-spec/SKILL.md), sem reiniciar o fluxo
|
|
15
|
+
nem pedir novamente aprovações já dadas.
|
|
16
|
+
|
|
17
|
+
Em correção avulsa autorizada de projeto existente, preserve suas decisões e
|
|
18
|
+
use `preparar-dev` quando necessário, sem inventar spec ou estado. Uma tarefa
|
|
19
|
+
que cite uma spec ou esteja coberta por seu escopo é tarefa de spec, mesmo
|
|
20
|
+
sendo correção pontual; não a trate como avulsa. Esclareça dúvidas sobre
|
|
21
|
+
entradas, saídas, persistência, compatibilidade e falhas antes de decidir.
|
|
22
|
+
Altere somente o escopo atribuído.
|
|
11
23
|
|
|
12
24
|
Verifique validação de entrada, autorização por recurso e isolamento de dados.
|
|
13
25
|
Considere limites, concorrência, repetição, timeout, migração e recuperação
|
|
@@ -5,9 +5,21 @@ description: Implementa interfaces web conforme a spec, o design e as provas de
|
|
|
5
5
|
|
|
6
6
|
# Implementar frontend
|
|
7
7
|
|
|
8
|
-
Siga
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Siga o escopo aprovado, o design e os componentes existentes. Ao implementar
|
|
9
|
+
uma spec, confirme spec e plano aprovados e, antes de alterar código, execute
|
|
10
|
+
`node .pbai4devs/scripts/executar-script/SCRIPT.mjs verificar-configuracao`.
|
|
11
|
+
Se o estado estiver ausente, inválido ou pendente, ou faltar prova obrigatória,
|
|
12
|
+
pare e informe o responsável. Saída zero ainda exige conferir as evidências.
|
|
13
|
+
Siga a ordem e as pré-condições já acordadas em
|
|
14
|
+
[implementar-spec](../../specs/implementar-spec/SKILL.md), sem reiniciar o fluxo
|
|
15
|
+
nem pedir novamente aprovações já dadas.
|
|
16
|
+
|
|
17
|
+
Em correção avulsa autorizada de projeto existente, preserve suas decisões e
|
|
18
|
+
use `preparar-dev` quando necessário, sem inventar spec ou estado. Uma tarefa
|
|
19
|
+
que cite uma spec ou esteja coberta por seu escopo é tarefa de spec, mesmo
|
|
20
|
+
sendo correção pontual; não a trate como avulsa. Esclareça comportamento,
|
|
21
|
+
conteúdo e acessibilidade quando faltarem decisões; não escolha framework nem
|
|
22
|
+
crie telas fora do escopo.
|
|
11
23
|
|
|
12
24
|
Considere estados de carregamento, vazio, erro e sucesso; navegação por teclado,
|
|
13
25
|
semântica acessível e diferentes tamanhos de tela. Preserve contratos com o
|
|
@@ -6,4 +6,6 @@ description: Examina e resume alterações Git.
|
|
|
6
6
|
# git-diff
|
|
7
7
|
|
|
8
8
|
Examine os diffs preparados e não preparados conforme o pedido, incluindo
|
|
9
|
-
arquivos não rastreados quando relevantes.
|
|
9
|
+
arquivos não rastreados quando relevantes. Execute consultas com
|
|
10
|
+
`GIT_OPTIONAL_LOCKS=0` no ambiente do subprocesso para evitar atualizar o índice;
|
|
11
|
+
não altere arquivos.
|
|
@@ -6,4 +6,5 @@ description: Inspeciona o estado do repositório e das branches locais e remotas
|
|
|
6
6
|
# git-status
|
|
7
7
|
|
|
8
8
|
Mostre branch, alterações preparadas e não preparadas, arquivos não rastreados e
|
|
9
|
-
divergência do upstream.
|
|
9
|
+
divergência do upstream. Execute a consulta com `GIT_OPTIONAL_LOCKS=0` no
|
|
10
|
+
ambiente do subprocesso para evitar atualizar o índice; não altere arquivos.
|
|
@@ -42,20 +42,25 @@ aprovações já dadas.
|
|
|
42
42
|
2. Prepare as ferramentas definidas em `.pbai4devs/tools/<nome>/TOOL.json` com
|
|
43
43
|
`preparar-ferramentas`; confira versões e compatibilidade com SO/arquitetura.
|
|
44
44
|
Reutilize instalações e lockfiles; não repita init nem troque a stack.
|
|
45
|
-
3. Aplique a [estrutura e o ambiente obrigatórios][estrutura],
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
3. Aplique a [estrutura e o ambiente obrigatórios][estrutura], preenchendo os
|
|
46
|
+
modelos de Makefile, Compose e dockerignore. Configure os serviços de
|
|
47
|
+
infraestrutura necessários, healthchecks, portas, volumes e comandos.
|
|
48
|
+
Antes de concluir a configuração, não implemente código da aplicação;
|
|
49
|
+
`src/` e `tests/` podem existir vazios ou conter apenas configuração.
|
|
48
50
|
4. Configure VS Code e recomende EditorConfig, sem regras conflitantes.
|
|
49
51
|
Termine `.gitignore` com o modelo CUSTOM IGNORE e os padrões da tecnologia;
|
|
50
52
|
confirme que templates, scripts, testes e fixtures continuam versionáveis.
|
|
51
53
|
5. Documente versões, estrutura, setup, comandos, origem do template/init e
|
|
52
54
|
decisões no README/estado. Use `atualizar-documentacao` para contribuição,
|
|
53
55
|
configuração e ADRs aplicáveis. Preserve arquivos e convenções existentes.
|
|
54
|
-
6. Use `verificar-trabalho`: valide
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
6. Use `verificar-trabalho`: valide runtime, configuração Compose e saúde dos
|
|
57
|
+
serviços de infraestrutura necessários; confira a estrutura com
|
|
58
|
+
`inspecionar-projeto --check`. Se Docker ou outra dependência obrigatória
|
|
59
|
+
estiver inacessível, mantenha a configuração pendente e informe a prova
|
|
60
|
+
faltante. Sem aplicação, registre build, saúde e fluxo dela como pendentes;
|
|
61
|
+
não os reporte como aprovados. Com aplicação existente, prove o fluxo real
|
|
62
|
+
conforme seu tipo. Marque `configuracao_tecnica: concluido` somente com
|
|
63
|
+
evidência da configuração prévia e dos serviços aplicáveis.
|
|
59
64
|
|
|
60
65
|
Após alterações técnicas, conclua `revisar-entrega`. Sincronize apontamentos
|
|
61
66
|
somente quando recursos dos agentes forem alterados.
|
|
@@ -17,7 +17,9 @@ Faça apenas as perguntas que impedem avançar.
|
|
|
17
17
|
Continue o trabalho já autorizado; não reinicie fases concluídas nem apresente
|
|
18
18
|
suposições como aprovações.
|
|
19
19
|
|
|
20
|
-
Projeto novo segue preparação → PRD → UX
|
|
21
|
-
|
|
20
|
+
Projeto novo segue preparação → PRD → UX → design (arquitetura e tecnologia) →
|
|
21
|
+
roadmap → spec aprovada → plano aprovado → configuração técnica → implementação
|
|
22
|
+
→ revisão. UX pode ser não aplicável com justificativa aprovada.
|
|
23
|
+
Projeto existente parte de suas evidências atuais;
|
|
22
24
|
instalar o kit não autoriza recriar documentos ou substituir decisões.
|
|
23
25
|
Se não houver pendências no escopo, diga isso claramente.
|
|
@@ -18,8 +18,20 @@ antes de implementar.
|
|
|
18
18
|
seus IDs/revisões. Não invente uma solução já atendida por elas.
|
|
19
19
|
3. Depois da aprovação, crie ou retome `feature/<slug-da-spec>` de `develop`.
|
|
20
20
|
Se faltar `develop`, use `main` quando seguro; sem base/remoto, combine como
|
|
21
|
-
proceder. Preserve alterações. Registre a aprovação no roadmap e
|
|
22
|
-
configuração real
|
|
21
|
+
proceder. Preserve alterações. Registre a aprovação no roadmap e complete a
|
|
22
|
+
configuração real com `configurar-projeto` antes de codificar.
|
|
23
|
+
Antes de qualquer patch de código da spec, execute:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
node .pbai4devs/scripts/executar-script/SCRIPT.mjs verificar-configuracao
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Releia o estado e confira as evidências da infraestrutura obrigatória. Se o
|
|
30
|
+
estado estiver ausente, inválido ou pendente, ou uma prova falhar, pare sem
|
|
31
|
+
escrever código; retome quando o impedimento for resolvido. Código zero do
|
|
32
|
+
script só confirma o estado registrado, não comprova as evidências nem impede
|
|
33
|
+
escrita por um agente que ignore o resultado. Não peça novamente aprovações
|
|
34
|
+
já concedidas.
|
|
23
35
|
4. Use o modelo de tarefas em `<documentos.tarefas>/<slug-da-spec>/TASKS.md`.
|
|
24
36
|
Vincule spec/release e registre tarefas, dependências, critérios e evidências.
|
|
25
37
|
Atualize `[ ]`, `[-]` e `[x]` ao avançar; conclusão exige prova. Ao retomar,
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ description: Alterações verificáveis por versão do AgentDevKit.
|
|
|
4
4
|
|
|
5
5
|
# Changelog
|
|
6
6
|
|
|
7
|
+
## 0.0.24 — 2026-11-14
|
|
8
|
+
|
|
9
|
+
- Preserva catálogos, configuração e blocos existentes sem comprovação de
|
|
10
|
+
propriedade, inclusive ao usar `--force` ou sincronização direta.
|
|
11
|
+
- Mantém o estado coerente quando a sincronização atualiza saídas gerenciadas.
|
|
12
|
+
- Acrescenta testes de colisão, arquivos idênticos e preservação do índice Git.
|
|
13
|
+
|
|
14
|
+
## 0.0.23 — 2026-11-13
|
|
15
|
+
|
|
16
|
+
- Corrige a resolução de destinos inexistentes diretamente sob a raiz.
|
|
17
|
+
- Aceita aliases temporários confiáveis do macOS e recusa destinos que
|
|
18
|
+
atravessam links humanos, inclusive os indicados por variáveis de ambiente.
|
|
19
|
+
- Orienta roadmap e spec antes da configuração técnica e acrescenta uma
|
|
20
|
+
verificação do estado antes da implementação.
|
|
21
|
+
- Aplica a verificação também aos especialistas e orienta consultas Git que
|
|
22
|
+
preservam os bytes do índice.
|
|
23
|
+
|
|
7
24
|
## 0.0.22 — 2026-11-13
|
|
8
25
|
|
|
9
26
|
- Descoberta de skills do Codex sem duplicação, com alternativa segura quando a
|
package/docs/guides/cli.md
CHANGED
|
@@ -125,5 +125,14 @@ do kit. Conteúdo humano não é substituído por `--force`.
|
|
|
125
125
|
|
|
126
126
|
`doctor` verifica o kit, não o produto inteiro. `next` pode terminar com
|
|
127
127
|
sucesso enquanto orienta uma etapa ainda pendente.
|
|
128
|
+
Após produto e design, ele usa os caminhos de roadmap e specs registrados no
|
|
129
|
+
estado. Para orientar uma spec existente, exige uma única referência aberta
|
|
130
|
+
`[ ]` ou em andamento `[-]` no roadmap. Com mais de uma spec, entrega encerrada,
|
|
131
|
+
vínculo ausente ou leitura ambígua, orienta a revisão do roadmap. Um status
|
|
132
|
+
declarado não comprova aprovação: a skill confere decisões e evidências. Com uma
|
|
133
|
+
spec aberta e declarada aprovada, `implementar-spec` verifica o plano antes de
|
|
134
|
+
encaminhar a configuração técnica; `next` não presume aprovação do plano.
|
|
135
|
+
Se o inventário for truncado, `next` orienta inspeção manual, sem escolher uma
|
|
136
|
+
fase ou presumir que um projeto existente está vazio.
|
|
128
137
|
|
|
129
138
|
[Wiki](../README.md) · [Solução de problemas](troubleshooting.md)
|