@pathbit/agentdevkit 0.0.23 → 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/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/CHANGELOG.md +7 -0
- package/docs/guides/troubleshooting.md +8 -6
- package/package.json +1 -1
- package/src/i18n.mjs +3 -3
- package/src/install.mjs +100 -18
|
@@ -15,6 +15,7 @@ const assets = join(dirname(fileURLToPath(import.meta.url)), 'assets');
|
|
|
15
15
|
const owner = 'testar-maturidade';
|
|
16
16
|
const marker = '.maturity-case.json';
|
|
17
17
|
const suiteMarker = '.maturity-suite.json';
|
|
18
|
+
const generatedCatalogs = new Set(['.pbai4devs/catalog.json', '.pbai4devs/skills.json', '.pbai4devs/agents.json', '.pbai4devs/rules.json']);
|
|
18
19
|
const outputIgnore = '# Exemplos gerados permanecem locais.\n*\n';
|
|
19
20
|
const defaultOutput = root => join(root, 'examples/generated');
|
|
20
21
|
const prepareResources = root => {
|
|
@@ -58,6 +59,7 @@ function createChild(root, parent, name, { allowExisting = false } = {}) {
|
|
|
58
59
|
noLinks(root, join(parent, name));
|
|
59
60
|
const output = stat(join(parent, name));
|
|
60
61
|
if (!output?.isDirectory() || output.isSymbolicLink()) fail('Diretório de saída inválido.');
|
|
62
|
+
return output;
|
|
61
63
|
}
|
|
62
64
|
function ensureDirectories(root, target) {
|
|
63
65
|
let current = root;
|
|
@@ -144,7 +146,7 @@ function copyAllowed(relativePath, directory = false) {
|
|
|
144
146
|
return true;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
export function copyAgentStructure(sourceRoot, destination) {
|
|
149
|
+
export function copyAgentStructure(sourceRoot, destination, { archiveCatalogs = false, archiveOverrides = new Set(), archiveEntrypoints = false } = {}) {
|
|
148
150
|
const roots = ['.pbai4devs', ...registeredRoots(sourceRoot)];
|
|
149
151
|
const stateFile = join(sourceRoot, stateDirectory(sourceRoot), 'agentdevkit.json');
|
|
150
152
|
const stateInfo = stat(stateFile);
|
|
@@ -152,7 +154,7 @@ export function copyAgentStructure(sourceRoot, destination) {
|
|
|
152
154
|
const installation = stateInfo ? json(stateFile) : null;
|
|
153
155
|
if (stateInfo && (!installation || !installation.blocks || typeof installation.blocks !== 'object' || Array.isArray(installation.blocks))) fail('Estado da instalação deve registrar os blocos gerenciados.');
|
|
154
156
|
function managedEntry(name) {
|
|
155
|
-
if (!installation) return
|
|
157
|
+
if (!installation) return false;
|
|
156
158
|
const expected = installation.blocks[name];
|
|
157
159
|
if (typeof expected !== 'string') return false;
|
|
158
160
|
const content = readFileSync(join(sourceRoot, name), 'utf8');
|
|
@@ -161,7 +163,7 @@ export function copyAgentStructure(sourceRoot, destination) {
|
|
|
161
163
|
const start = content.indexOf(begin), finish = content.indexOf(end);
|
|
162
164
|
return finish > start && digest(content.slice(start, finish + end.length)) === expected;
|
|
163
165
|
}
|
|
164
|
-
const files = []; const entrypoints = {}; let skipped = 0;
|
|
166
|
+
const files = []; const entrypoints = {}; const catalogSnapshots = []; const overriddenSnapshots = []; const seenSources = new Set(); let skipped = 0;
|
|
165
167
|
function copy(file, targetFile = file) {
|
|
166
168
|
const source = join(sourceRoot, file);
|
|
167
169
|
const item = stat(source); if (!item) return;
|
|
@@ -171,6 +173,14 @@ export function copyAgentStructure(sourceRoot, destination) {
|
|
|
171
173
|
mkdirSync(join(destination, targetFile), { recursive: true });
|
|
172
174
|
for (const name of readdirSync(source).sort()) copy(join(file, name), join(targetFile, name));
|
|
173
175
|
} else if (item.isFile()) {
|
|
176
|
+
seenSources.add(portable(file));
|
|
177
|
+
if (archiveCatalogs && generatedCatalogs.has(portable(file))) {
|
|
178
|
+
targetFile = `.agent-tests/testar-maturidade/original-catalogs/${basename(file)}`;
|
|
179
|
+
catalogSnapshots.push({ source: portable(file), snapshot: targetFile });
|
|
180
|
+
} else if (archiveOverrides.has(portable(file))) {
|
|
181
|
+
targetFile = `.agent-tests/testar-maturidade/original-files/${portable(file)}`;
|
|
182
|
+
overriddenSnapshots.push({ source: portable(file), snapshot: targetFile });
|
|
183
|
+
}
|
|
174
184
|
const target = join(destination, targetFile); mkdirSync(dirname(target), { recursive: true }); copyFileSync(source, target); files.push(portable(targetFile));
|
|
175
185
|
} else fail(`Arquivo não regular na origem: ${file}`);
|
|
176
186
|
}
|
|
@@ -183,11 +193,29 @@ export function copyAgentStructure(sourceRoot, destination) {
|
|
|
183
193
|
const selected = managedEntry(name) && stat(join(sourceRoot, template)) ? template : name;
|
|
184
194
|
const info = stat(join(sourceRoot, selected));
|
|
185
195
|
if (info && !info.isFile()) fail(`Entrada de agente deve ser arquivo regular: ${selected}`);
|
|
196
|
+
if (archiveEntrypoints && selected !== name) {
|
|
197
|
+
const snapshot = `.agent-tests/testar-maturidade/original-files/${name}`;
|
|
198
|
+
copy(name, snapshot); overriddenSnapshots.push({ source: name, snapshot });
|
|
199
|
+
}
|
|
186
200
|
copy(selected, name);
|
|
187
201
|
if (info) entrypoints[name] = selected;
|
|
188
202
|
}
|
|
203
|
+
// A fixture can create files belonging to an unselected client; archive
|
|
204
|
+
// those source files as well, without traversing symbolic-link ancestors.
|
|
205
|
+
for (const path of archiveOverrides) if (!seenSources.has(path)) {
|
|
206
|
+
noLinks(sourceRoot, join(sourceRoot, path));
|
|
207
|
+
copy(path);
|
|
208
|
+
}
|
|
189
209
|
const sourceDigest = digest(files.map(file => `${file}\0${digest(readFileSync(join(destination, file)))}`).join('\n'));
|
|
190
|
-
return { roots, copiedFiles: files.length, skipped, files, sourceDigest, entrypoints, toolDefinitions: files.filter(file => /^\.pbai4devs\/tools\/[^/]+\/TOOL\.json$/.test(file)).length };
|
|
210
|
+
return { roots, copiedFiles: files.length, skipped, files, sourceDigest, entrypoints, catalogSnapshots, overriddenSnapshots, toolDefinitions: files.filter(file => /^\.pbai4devs\/tools\/[^/]+\/TOOL\.json$/.test(file)).length };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function fixtureOverrides(scenario, sourceRoot) {
|
|
214
|
+
const paths = ['README.md', '.pbai4devs/.gitignore', ...registeredRoots(sourceRoot).map(root => `${root}/.gitignore`), '.pbai4devs/state/configuracao/ESTADO.md', '.pbai4devs/estado/configuracao/ESTADO.md'];
|
|
215
|
+
if (scenario.planning !== 'none' || scenario.app) paths.push('.pbai4devs/state/README.md', '.pbai4devs/estado/README.md');
|
|
216
|
+
if (scenario.configured) paths.push('.dockerignore', '.env.example', 'Makefile');
|
|
217
|
+
if (scenario.layout === 'legacy') paths.push('Makefile', '.github/workflows/legacy.yml', '.vscode/settings.json');
|
|
218
|
+
return new Set(paths);
|
|
191
219
|
}
|
|
192
220
|
|
|
193
221
|
function planning(root, scenario) {
|
|
@@ -299,33 +327,41 @@ export function prepareExamples({ sourceRoot = projectRoot, outputRoot, scenario
|
|
|
299
327
|
const parent = outputRoot === undefined ? base : dirname(base);
|
|
300
328
|
ensureDirectories(sourceRoot, parent);
|
|
301
329
|
const name = outputRoot === undefined ? `maturity-${randomUUID()}` : basename(base);
|
|
302
|
-
createChild(sourceRoot, parent, name);
|
|
330
|
+
const created = createChild(sourceRoot, parent, name);
|
|
303
331
|
outputRoot = join(parent, name);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
332
|
+
const canonicalOutput = realpathSync(outputRoot);
|
|
333
|
+
const verifyOutput = (path = outputRoot) => {
|
|
334
|
+
noLinks(sourceRoot, path);
|
|
335
|
+
const current = stat(outputRoot);
|
|
336
|
+
if (!current?.isDirectory() || current.dev !== created.dev || current.ino !== created.ino || realpathSync(outputRoot) !== canonicalOutput) fail('Diretório de saída alterado durante a execução.');
|
|
337
|
+
};
|
|
338
|
+
const guarded = (path, operation) => { verifyOutput(path); const value = operation(); verifyOutput(path); return value; };
|
|
339
|
+
guarded(outputRoot, () => write(outputRoot, suiteMarker, JSON.stringify({ owner, version: 1 }, null, 2) + '\n'));
|
|
340
|
+
guarded(outputRoot, () => write(outputRoot, '.gitignore', outputIgnore));
|
|
341
|
+
guarded(outputRoot, () => writeIndex(outputRoot, [], 'preparing'));
|
|
307
342
|
const examples = [];
|
|
308
343
|
for (const scenario of scenarios) {
|
|
309
344
|
const destination = join(outputRoot, scenario.id);
|
|
310
|
-
mkdirSync(destination);
|
|
311
|
-
write(destination, marker, JSON.stringify({ owner, version: 1, id: scenario.id }, null, 2) + '\n');
|
|
312
|
-
const copied = copyAgentStructure(sourceRoot, destination);
|
|
313
|
-
planning(destination, scenario);
|
|
314
|
-
const app = application(destination, scenario);
|
|
315
|
-
write(destination, 'README.md', document(scenario.title, `${scenario.note}\n\nEste é um exemplo gerado. Estados de aprovação/publicação são dados de teste.\nRevisão do gate é simulação contratual, não revisão humana.\n\nOs scripts dos agentes foram copiados, sem pacotes ou credenciais conhecidas.\nO filtro não detecta segredos em conteúdo arbitrário.\n${app.tests.length ? `\nPara repetir os testes: \`${scenario.layout === 'legacy' ? 'npm run test:legacy' : 'npm test'}\`.\nO harness executa os testes Node diretamente, sem instalar dependências.\nO teste abre HTTP em porta efêmera e encerra o servidor.\n` : '\nNão há aplicação; a prova real exercita os scripts copiados.\n'}`, `fase: ${scenario.expected}\n`));
|
|
316
|
-
renderResources(destination);
|
|
317
|
-
if (initializeGit) gitInit(destination);
|
|
345
|
+
guarded(outputRoot, () => mkdirSync(destination));
|
|
346
|
+
guarded(destination, () => write(destination, marker, JSON.stringify({ owner, version: 1, id: scenario.id }, null, 2) + '\n'));
|
|
347
|
+
const copied = guarded(destination, () => copyAgentStructure(sourceRoot, destination, { archiveCatalogs: true, archiveOverrides: fixtureOverrides(scenario, sourceRoot), archiveEntrypoints: true }));
|
|
348
|
+
guarded(destination, () => planning(destination, scenario));
|
|
349
|
+
const app = guarded(destination, () => application(destination, scenario));
|
|
350
|
+
guarded(destination, () => write(destination, 'README.md', document(scenario.title, `${scenario.note}\n\nEste é um exemplo gerado, não um teste no projeto original. Estados de aprovação/publicação são dados de teste.\nRevisão do gate é simulação contratual, não revisão humana.\nCatálogos originais e arquivos substituídos pela fixture ficam em \`.agent-tests/testar-maturidade/\`; os catálogos operacionais são regenerados.\n\nOs scripts dos agentes foram copiados, sem pacotes ou credenciais conhecidas.\nO filtro não detecta segredos em conteúdo arbitrário.\n${app.tests.length ? `\nPara repetir os testes: \`${scenario.layout === 'legacy' ? 'npm run test:legacy' : 'npm test'}\`.\nO harness executa os testes Node diretamente, sem instalar dependências.\nO teste abre HTTP em porta efêmera e encerra o servidor.\n` : '\nNão há aplicação; a prova real exercita os scripts copiados.\n'}`, `fase: ${scenario.expected}\n`)));
|
|
351
|
+
guarded(destination, () => renderResources(destination));
|
|
352
|
+
if (initializeGit) guarded(destination, () => gitInit(destination));
|
|
318
353
|
examples.push({ ...scenario, root: destination, copied, application: app });
|
|
319
354
|
}
|
|
320
|
-
write(outputRoot, 'REPORT.json', JSON.stringify({ owner, version: 1, status: 'prepared', scenarios: [...ids], results: [] }, null, 2) + '\n');
|
|
321
|
-
writeIndex(outputRoot, examples, 'prepared');
|
|
355
|
+
guarded(outputRoot, () => write(outputRoot, 'REPORT.json', JSON.stringify({ owner, version: 1, status: 'prepared', scenarios: [...ids], copies: examples.map(example => ({ id: example.id, catalogSnapshots: example.copied.catalogSnapshots, overriddenSnapshots: example.copied.overriddenSnapshots })), results: [] }, null, 2) + '\n'));
|
|
356
|
+
guarded(outputRoot, () => writeIndex(outputRoot, examples, 'prepared'));
|
|
357
|
+
Object.defineProperty(examples, 'verifyOutput', { value: verifyOutput });
|
|
322
358
|
return examples;
|
|
323
359
|
}
|
|
324
360
|
|
|
325
361
|
function writeIndex(outputRoot, examples, status) {
|
|
326
362
|
const rows = examples.map(item => `| [${item.id}](${item.id}/README.md) | ${item.expected} | ${item.status || 'prepared'} |`).join('\n');
|
|
327
363
|
const progress = status === 'preparing' ? '\nPreparação em andamento nesta execução.\n' : '';
|
|
328
|
-
write(outputRoot, 'README.md', document('Exemplos de maturidade', `Projetos sintéticos com cópias locais da estrutura de agentes.\nNenhum resultado certifica produção, revisão humana ou outros sistemas operacionais.\n${progress}\n| Cenário | Nível esperado | Resultado |\n| --- | --- | --- |${rows ? '\n' + rows : ''}\n\nRelatório: \`REPORT.json\`. Logs e evidências por projeto ficam em \`.agent-tests/\`.\nCada execução usa uma pasta nova e preserva as anteriores.\n`, `status: ${status}\n`));
|
|
364
|
+
write(outputRoot, 'README.md', document('Exemplos de maturidade', `Projetos sintéticos com cópias locais da estrutura de agentes; não são testes no projeto original.\nCatálogos operacionais são regenerados e suas cópias originais ficam em cada \`.agent-tests/testar-maturidade/\`.\nNenhum resultado certifica produção, revisão humana ou outros sistemas operacionais.\n${progress}\n| Cenário | Nível esperado | Resultado |\n| --- | --- | --- |${rows ? '\n' + rows : ''}\n\nRelatório: \`REPORT.json\`. Logs e evidências por projeto ficam em \`.agent-tests/\`.\nCada execução usa uma pasta nova e preserva as anteriores.\n`, `status: ${status}\n`));
|
|
329
365
|
}
|
|
330
366
|
|
|
331
367
|
function hashes(root, files) { return Object.fromEntries(files.map(file => [file, digest(readFileSync(join(root, file)))])); }
|
|
@@ -399,13 +435,14 @@ function resourceLifecycle(example, run) {
|
|
|
399
435
|
if (readFileSync(join(root, catalogPath), 'utf8') !== before.catalog || readFileSync(join(root, registryPath), 'utf8') !== before.registry) fail('O ciclo não restaurou os catálogos originais.');
|
|
400
436
|
const corrupted = '{"tampered":true}\n'; write(root, catalogPath, corrupted);
|
|
401
437
|
run(sync, ['--check'], 1, 'catalog-drift-detected');
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
if (
|
|
438
|
+
const blocked = run(sync, ['--force'], 2, 'catalog-drift-preserved');
|
|
439
|
+
if (!blocked.output.includes('Conteudo proprio preservado')) fail('A colisão não informou a preservação do catálogo.');
|
|
440
|
+
if (readFileSync(join(root, catalogPath), 'utf8') !== corrupted) fail('Sincronização alterou o JSON sintético sem ownership.');
|
|
441
|
+
write(root, catalogPath, before.catalog); // Restore only the content this fixture deliberately changed.
|
|
405
442
|
rmSync(join(root, catalogPath)); run(sync, ['--check'], 1, 'catalog-missing-detected');
|
|
406
443
|
run(sync, [], 0, 'catalog-missing-repaired'); run(sync, ['--check'], 0, 'lifecycle-clean');
|
|
407
444
|
if (readFileSync(join(root, catalogPath), 'utf8') !== before.catalog || readFileSync(join(root, registryPath), 'utf8') !== before.registry) fail('Catálogos não foram restaurados após corrupção/remoção.');
|
|
408
|
-
return { status: 'passed', operations: ['create', 'edit-description', 'rename', 'delete', '
|
|
445
|
+
return { status: 'passed', operations: ['create', 'edit-description', 'rename', 'delete', 'protect-modified-json', 'remove-json'], clients: rows.length, sourceRestored: true };
|
|
409
446
|
}
|
|
410
447
|
|
|
411
448
|
function reviewContract(example, run, proof) {
|
|
@@ -444,7 +481,7 @@ function reviewContract(example, run, proof) {
|
|
|
444
481
|
export function testExample(example) {
|
|
445
482
|
const commands = []; const logRoot = '.agent-tests/maturity';
|
|
446
483
|
const run = runner(example.root, logRoot, commands);
|
|
447
|
-
const result = { id: example.id, expected: example.expected, status: 'failed', copied: { files: example.copied.copiedFiles, roots: example.copied.roots, skipped: example.copied.skipped, sourceDigest: example.copied.sourceDigest, entrypoints: example.copied.entrypoints }, commands, limits: ['Executado somente no SO/runtime deste host.', 'Estado de produto e revisão do gate são dados sintéticos.', 'Build Docker, UI e implantação não são exercitados.', 'Cópia exclui credenciais por nomes conhecidos, sem varredura semântica de segredos.'] };
|
|
484
|
+
const result = { id: example.id, expected: example.expected, status: 'failed', copied: { files: example.copied.copiedFiles, roots: example.copied.roots, skipped: example.copied.skipped, sourceDigest: example.copied.sourceDigest, entrypoints: example.copied.entrypoints, catalogSnapshots: example.copied.catalogSnapshots, overriddenSnapshots: example.copied.overriddenSnapshots }, commands, limits: ['Cenário sintético: não testa o projeto original no local.', 'Executado somente no SO/runtime deste host.', 'Estado de produto e revisão do gate são dados sintéticos.', 'Build Docker, UI e implantação não são exercitados.', 'Cópia exclui credenciais por nomes conhecidos, sem varredura semântica de segredos.'] };
|
|
448
485
|
const preserved = hashes(example.root, example.application.protected);
|
|
449
486
|
try {
|
|
450
487
|
const sync = '.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs';
|
|
@@ -493,11 +530,14 @@ export function testExample(example) {
|
|
|
493
530
|
|
|
494
531
|
export function runExamples(options = {}) {
|
|
495
532
|
const examples = prepareExamples(options);
|
|
496
|
-
const results = examples.map(example => { const result = testExample(example); console.log(`${example.id}=${result.status}${result.error ? ': ' + result.error : ''}`); return result; });
|
|
533
|
+
const results = examples.map(example => { examples.verifyOutput(example.root); const result = testExample(example); examples.verifyOutput(example.root); console.log(`${example.id}=${result.status}${result.error ? ': ' + result.error : ''}`); return result; });
|
|
497
534
|
const outputRoot = dirname(examples[0].root);
|
|
498
535
|
const report = { owner, version: 1, status: results.every(item => item.status === 'passed') ? 'passed' : 'failed', platform: process.platform, arch: process.arch, node: process.version, results, limits: ['Provas reais somente das operações descritas e APIs locais.', 'Pareceres do gate são simulações explícitas, sem revisão humana.', 'Não certifica produção nem sistemas operacionais não executados.'] };
|
|
536
|
+
examples.verifyOutput();
|
|
499
537
|
write(outputRoot, 'REPORT.json', JSON.stringify(report, null, 2) + '\n');
|
|
538
|
+
examples.verifyOutput();
|
|
500
539
|
writeIndex(outputRoot, examples.map(example => ({ ...example, status: results.find(result => result.id === example.id).status })), report.status);
|
|
540
|
+
examples.verifyOutput();
|
|
501
541
|
return { outputRoot, report };
|
|
502
542
|
}
|
|
503
543
|
|
|
@@ -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');
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ 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
|
+
|
|
7
14
|
## 0.0.23 — 2026-11-13
|
|
8
15
|
|
|
9
16
|
- Corrige a resolução de destinos inexistentes diretamente sob a raiz.
|
|
@@ -56,9 +56,10 @@ Compare cada `conflicts` com o que deseja manter. Edite a origem em
|
|
|
56
56
|
recurso. Use `sync` para atualizar suas derivações. `update` reaplica os
|
|
57
57
|
recursos do pacote invocado e pode conflitar com essa personalização.
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
`--force` não substitui arquivos próprios nem blocos alterados. Resolva o
|
|
60
|
+
conflito preservando o conteúdo que deseja manter e confira novamente o plano.
|
|
61
|
+
Arquivos gerenciados afetados recebem backup antes da escrita, inclusive em
|
|
62
|
+
operações normais. O campo `backup` informa uma pasta relativa ao projeto:
|
|
62
63
|
|
|
63
64
|
```text
|
|
64
65
|
.agent-tests/agentdevkit/backups/<identificador>/
|
|
@@ -105,9 +106,10 @@ ou caminhos já gerenciados em links.
|
|
|
105
106
|
Destinos dentro de `.git`, `node_modules` ou `.pbai4devs` são recusados,
|
|
106
107
|
inclusive com variações de maiúsculas/minúsculas.
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
No script `sincronizar-agentes`, `--force` repara conflitos gerenciados com
|
|
110
|
+
backup. A CLI bloqueia conflitos de propriedade mesmo com `--force`. Recursos
|
|
111
|
+
nativos humanos e links preservados continuam intactos. Para manter a instalação
|
|
112
|
+
e sua seleção de clientes, prefira a CLI.
|
|
111
113
|
|
|
112
114
|
## Download, plataforma ou ferramenta falhando
|
|
113
115
|
|
package/package.json
CHANGED
package/src/i18n.mjs
CHANGED
|
@@ -12,7 +12,7 @@ const messages = {
|
|
|
12
12
|
preserved: 'Caminhos existentes preservados', preservedDetails: 'Use --json para consultar os caminhos e motivos.',
|
|
13
13
|
help: 'AgentDevKit: init [diretório], update, doctor, sync [--check], lang [pt|en|es], agents [lista], next, tools <ação>.\nOpções: --target <diretório>, --lang <idioma>, --agents <lista>, --yes, --dry-run, --force, --json.\nAliases: --clients e clients.',
|
|
14
14
|
clientsCurrent: 'Agentes selecionados', clientsAvailable: 'Agentes disponíveis', clientsSelect: 'Agentes (IDs separados por vírgula ou all) [all]: ', agents: 'Seleção de agentes atualizada.',
|
|
15
|
-
next: 'Próximo passo', init: 'Estrutura instalada.', update: 'Estrutura atualizada.', sync: 'Apontamentos sincronizados.', lang: 'Idioma configurado.', unchanged: 'Estrutura já atualizada.', plan: 'Plano calculado; nenhum arquivo alterado.', conflict: 'Há arquivos próprios ou alterados em conflito. Revise o plano; --force
|
|
15
|
+
next: 'Próximo passo', init: 'Estrutura instalada.', update: 'Estrutura atualizada.', sync: 'Apontamentos sincronizados.', lang: 'Idioma configurado.', unchanged: 'Estrutura já atualizada.', plan: 'Plano calculado; nenhum arquivo alterado.', conflict: 'Há arquivos próprios ou alterados em conflito. Revise o plano e resolva os conflitos; --force não substitui conteúdo próprio.',
|
|
16
16
|
healthy: 'Estrutura verificada.', unhealthy: 'A estrutura precisa de ajustes.', missing: 'AgentDevKit ainda não foi inicializado neste diretório.', error: 'Não foi possível concluir a operação.', invalid: 'Argumentos inválidos.', language: 'Idioma atual', version: 'Versão', select: 'Idioma / Language / Idioma [pt/en/es] (pt): ',
|
|
17
17
|
project: 'Projeto', readme: 'AgentDevKit instalado. Leia AGENTS.md e siga as etapas de produto, design e implementação.', state: 'Estado local do AgentDevKit e configuração compartilhada do projeto.',
|
|
18
18
|
agentLanguage: 'Responda em português, salvo quando o usuário solicitar outro idioma.', inspect: 'Verifique a instalação', workflow: 'Consulte a próxima etapa', install: 'Inicialize este projeto', repair: 'Confira a sincronização', review: 'Revise os arquivos em conflito', tools: 'Resultado da ferramenta',
|
|
@@ -23,7 +23,7 @@ const messages = {
|
|
|
23
23
|
preserved: 'Existing paths preserved', preservedDetails: 'Use --json to inspect paths and reasons.',
|
|
24
24
|
help: 'AgentDevKit: init [directory], update, doctor, sync [--check], lang [pt|en|es], agents [list], next, tools <action>.\nOptions: --target <directory>, --lang <language>, --agents <list>, --yes, --dry-run, --force, --json.\nAliases: --clients and clients.',
|
|
25
25
|
clientsCurrent: 'Selected agents', clientsAvailable: 'Available agents', clientsSelect: 'Agents (comma-separated IDs or all) [all]: ', agents: 'Agent selection updated.',
|
|
26
|
-
next: 'Next step', init: 'Structure installed.', update: 'Structure updated.', sync: 'Pointers synchronized.', lang: 'Language configured.', unchanged: 'Structure is already current.', plan: 'Plan calculated; no files changed.', conflict: 'User files or edits conflict with this plan. Review
|
|
26
|
+
next: 'Next step', init: 'Structure installed.', update: 'Structure updated.', sync: 'Pointers synchronized.', lang: 'Language configured.', unchanged: 'Structure is already current.', plan: 'Plan calculated; no files changed.', conflict: 'User files or edits conflict with this plan. Review and resolve the conflicts; --force does not replace user content.',
|
|
27
27
|
healthy: 'Structure verified.', unhealthy: 'The structure needs attention.', missing: 'AgentDevKit has not been initialized in this directory.', error: 'The operation could not be completed.', invalid: 'Invalid arguments.', language: 'Current language', version: 'Version', select: 'Idioma / Language / Idioma [pt/en/es] (pt): ',
|
|
28
28
|
project: 'Project', readme: 'AgentDevKit installed. Read AGENTS.md and follow the product, design and implementation stages.', state: 'AgentDevKit state and shared project configuration.',
|
|
29
29
|
agentLanguage: 'Respond in English unless the user requests another language.', inspect: 'Check the installation', workflow: 'Review the next stage', install: 'Initialize this project', repair: 'Check synchronization', review: 'Review conflicting files', tools: 'Tool result',
|
|
@@ -34,7 +34,7 @@ const messages = {
|
|
|
34
34
|
preserved: 'Rutas existentes preservadas', preservedDetails: 'Use --json para consultar las rutas y los motivos.',
|
|
35
35
|
help: 'AgentDevKit: init [directorio], update, doctor, sync [--check], lang [pt|en|es], agents [lista], next, tools <acción>.\nOpciones: --target <directorio>, --lang <idioma>, --agents <lista>, --yes, --dry-run, --force, --json.\nAlias: --clients y clients.',
|
|
36
36
|
clientsCurrent: 'Agentes seleccionados', clientsAvailable: 'Agentes disponibles', clientsSelect: 'Agentes (IDs separados por coma o all) [all]: ', agents: 'Selección de agentes actualizada.',
|
|
37
|
-
next: 'Próximo paso', init: 'Estructura instalada.', update: 'Estructura actualizada.', sync: 'Referencias sincronizadas.', lang: 'Idioma configurado.', unchanged: 'La estructura ya está actualizada.', plan: 'Plan calculado; ningún archivo modificado.', conflict: 'Hay archivos propios o cambios en conflicto. Revise
|
|
37
|
+
next: 'Próximo paso', init: 'Estructura instalada.', update: 'Estructura actualizada.', sync: 'Referencias sincronizadas.', lang: 'Idioma configurado.', unchanged: 'La estructura ya está actualizada.', plan: 'Plan calculado; ningún archivo modificado.', conflict: 'Hay archivos propios o cambios en conflicto. Revise y resuelva los conflictos; --force no reemplaza contenido propio.',
|
|
38
38
|
healthy: 'Estructura verificada.', unhealthy: 'La estructura necesita ajustes.', missing: 'AgentDevKit no se ha inicializado en este directorio.', error: 'No se pudo completar la operación.', invalid: 'Argumentos inválidos.', language: 'Idioma actual', version: 'Versión', select: 'Idioma / Language / Idioma [pt/en/es] (pt): ',
|
|
39
39
|
project: 'Proyecto', readme: 'AgentDevKit instalado. Lea AGENTS.md y siga las etapas de producto, diseño e implementación.', state: 'Estado de AgentDevKit y configuración compartida del proyecto.',
|
|
40
40
|
agentLanguage: 'Responde en español salvo que el usuario solicite otro idioma.', inspect: 'Compruebe la instalación', workflow: 'Consulte la siguiente etapa', install: 'Inicialice este proyecto', repair: 'Compruebe la sincronización', review: 'Revise los archivos en conflicto', tools: 'Resultado de la herramienta',
|