@pathbit/agentdevkit 0.0.16 → 0.0.18

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.
Files changed (44) hide show
  1. package/.pbai4devs/INSTRUCTIONS.md +1 -1
  2. package/.pbai4devs/README.md +1 -1
  3. package/.pbai4devs/commands/sincronizar/COMMAND.md +6 -4
  4. package/.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs +3 -2
  5. package/.pbai4devs/scripts/inspecionar-projeto/tests/TEST.mjs +18 -4
  6. package/.pbai4devs/scripts/lib/README.md +3 -0
  7. package/.pbai4devs/scripts/lib/gitignore.mjs +46 -9
  8. package/.pbai4devs/scripts/lib/state-path.mjs +20 -0
  9. package/.pbai4devs/scripts/sincronizar-agentes/README.md +8 -1
  10. package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +75 -34
  11. package/.pbai4devs/scripts/sincronizar-agentes/tests/COEXISTENCE.mjs +5 -5
  12. package/.pbai4devs/scripts/sincronizar-agentes/tests/HUMAN_OWNERSHIP.mjs +1 -1
  13. package/.pbai4devs/scripts/sincronizar-agentes/tests/MANAGED_CONTENT.mjs +9 -9
  14. package/.pbai4devs/scripts/sincronizar-agentes/tests/PRESERVATION.mjs +306 -0
  15. package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +41 -8
  16. package/.pbai4devs/scripts/testar-maturidade/SCRIPT.mjs +6 -4
  17. package/.pbai4devs/scripts/testar-maturidade/tests/TEST.mjs +4 -3
  18. package/.pbai4devs/scripts/verificar-recursos/SCRIPT.mjs +5 -3
  19. package/.pbai4devs/scripts/verificar-recursos/tests/TEST.mjs +15 -7
  20. package/.pbai4devs/skills/produto/criar-design-doc/SKILL.md +1 -1
  21. package/.pbai4devs/skills/produto/criar-prd/SKILL.md +1 -1
  22. package/.pbai4devs/skills/produto/criar-ux-ui/SKILL.md +1 -1
  23. package/.pbai4devs/skills/projeto/configurar-projeto/SKILL.md +1 -1
  24. package/.pbai4devs/skills/projeto/proximos-passos/SKILL.md +1 -1
  25. package/.pbai4devs/skills/projeto/sincronizar-agentes/SKILL.md +1 -1
  26. package/.pbai4devs/skills/specs/criar-spec/SKILL.md +1 -1
  27. package/.pbai4devs/skills/specs/implementar-spec/SKILL.md +1 -1
  28. package/.pbai4devs/templates/entrypoints/AGENTS.md +1 -1
  29. package/.pbai4devs/templates/gitignore/local.ignore +2 -0
  30. package/.pbai4devs/templates/gitignore/shared.ignore +2 -0
  31. package/CHANGELOG.md +11 -0
  32. package/docs/guides/cli.md +46 -15
  33. package/docs/guides/installation.md +20 -8
  34. package/docs/guides/troubleshooting.md +9 -1
  35. package/docs/guides/workflow.md +1 -1
  36. package/docs/specs/README.md +2 -0
  37. package/docs/specs/safe-synchronization/SPEC.md +21 -0
  38. package/docs/specs/state-directory/SPEC.md +22 -0
  39. package/package.json +1 -1
  40. package/src/cli.mjs +2 -2
  41. package/src/config.mjs +2 -1
  42. package/src/i18n.mjs +25 -0
  43. package/src/install.mjs +373 -27
  44. /package/.pbai4devs/{estado → state}/README.md +0 -0
@@ -0,0 +1,306 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawnSync } from 'node:child_process';
7
+ import { tmpdir } from 'node:os';
8
+ import { synchronize } from '../SCRIPT.mjs';
9
+ import { indexContentMatches } from '../../lib/gitignore.mjs';
10
+
11
+ const repository = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
12
+ const original = '.pbai4devs/skills/projeto/criar-skill/SKILL.md';
13
+ const pointer = '.claude/skills/criar-skill/SKILL.md';
14
+ const file = (root, relative) => join(root, relative);
15
+ const put = (root, relative, content) => { mkdirSync(dirname(file(root, relative)), { recursive: true }); writeFileSync(file(root, relative), content); };
16
+ function git(root, ...args) {
17
+ const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' });
18
+ assert.equal(result.status, 0, `${args.join(' ')}: ${result.stderr}`);
19
+ return result.stdout;
20
+ }
21
+ function fixture(t, withGit = true) {
22
+ mkdirSync(join(repository, '.agent-tests'), { recursive: true });
23
+ const root = mkdtempSync(join(withGit ? join(repository, '.agent-tests') : tmpdir(), 'sync-preservation-'));
24
+ t.after(() => rmSync(root, { recursive: true, force: true }));
25
+ if (withGit) git(root, 'init', '--quiet');
26
+ populate(root);
27
+ return root;
28
+ }
29
+ function populate(root) {
30
+ put(root, '.gitignore', '# Human ignore\nnode_modules/\n');
31
+ put(root, '.pbai4devs/templates/gitignore/agents.ignore', '# Cache\n/*\n!/.gitignore\n{{ALLOWLIST}}\n/cache/\n*.log\n');
32
+ put(root, '.pbai4devs/templates/gitignore/clients.json', '{}\n');
33
+ put(root, '.pbai4devs/templates/gitignore/shared.ignore', '# Shared\n/tools/*/.local/\n');
34
+ put(root, '.pbai4devs/espelhos.conf', 'agente|.claude|plano\n');
35
+ put(root, original, '---\nname: criar-skill\ndescription: Criar recurso.\n---\n\n# Criar skill\n');
36
+ assert.equal(run(root).exitCode, 0);
37
+ assert.ok(existsSync(file(root, pointer)));
38
+ }
39
+ function run(root, options = {}) {
40
+ return synchronize({ root, localIgnores: false, generateEntrypoints: false, output() {}, ...options });
41
+ }
42
+ function withoutGit(t, callback) {
43
+ const emptyPath = mkdtempSync(join(tmpdir(), 'sync-empty-path-'));
44
+ t.after(() => rmSync(emptyPath, { recursive: true, force: true }));
45
+ const keys = Object.keys(process.env).filter(key => key.toLowerCase() === 'path');
46
+ const previous = new Map(keys.map(key => [key, process.env[key]]));
47
+ if (!keys.length) keys.push('PATH');
48
+ try {
49
+ for (const key of keys) process.env[key] = emptyPath;
50
+ return callback();
51
+ } finally {
52
+ for (const key of keys) {
53
+ if (previous.has(key)) process.env[key] = previous.get(key);
54
+ else delete process.env[key];
55
+ }
56
+ }
57
+ }
58
+
59
+ for (const [withGit, force] of [[true, false], [true, true], [false, false], [false, true]]) {
60
+ test(`remoção obsoleta preserva pasta vazia humana (git=${withGit}, force=${force})`, t => {
61
+ const root = fixture(t, withGit), human = '.claude/skills/criar-skill/Equipe-Vazia';
62
+ mkdirSync(file(root, human));
63
+ const state = readFileSync(file(root, '.pbai4devs/state/derived.json'));
64
+ const index = withGit ? git(root, 'ls-files', '--stage', '-z') : null;
65
+ const ignore = readFileSync(file(root, '.gitignore'));
66
+ rmSync(dirname(file(root, original)), { recursive: true });
67
+ const check = run(root, { check: true, force });
68
+ assert.notEqual(check.exitCode, 0);
69
+ assert.ok(existsSync(file(root, pointer)));
70
+ assert.ok(existsSync(file(root, human)));
71
+ assert.deepEqual(readFileSync(file(root, '.pbai4devs/state/derived.json')), state);
72
+ run(root, { force });
73
+ assert.equal(existsSync(file(root, pointer)), false, 'apontamento obsoleto comprovado é removido');
74
+ assert.ok(existsSync(file(root, human)), 'pasta vazia própria permanece');
75
+ assert.deepEqual(readFileSync(file(root, '.gitignore')), ignore);
76
+ if (withGit) assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
77
+ });
78
+ }
79
+
80
+ for (const force of [false, true]) {
81
+ test(`índice com conteúdo humano divergente bloqueia remoção (force=${force})`, t => {
82
+ const root = fixture(t), generated = readFileSync(file(root, pointer));
83
+ put(root, pointer, Buffer.concat([generated, Buffer.from('\nConteúdo humano staged.\n')]));
84
+ git(root, 'add', '-f', '--', pointer);
85
+ const index = git(root, 'ls-files', '--stage', '-z');
86
+ put(root, pointer, generated);
87
+ rmSync(dirname(file(root, original)), { recursive: true });
88
+ const state = readFileSync(file(root, '.pbai4devs/state/derived.json'));
89
+ const check = run(root, { check: true, force });
90
+ assert.notEqual(check.exitCode, 0);
91
+ assert.deepEqual(readFileSync(file(root, pointer)), generated);
92
+ assert.deepEqual(readFileSync(file(root, '.pbai4devs/state/derived.json')), state);
93
+ assert.throws(() => run(root, { force }), /conflitos|indice|índice/i);
94
+ assert.deepEqual(readFileSync(file(root, pointer)), generated);
95
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
96
+ });
97
+ }
98
+
99
+ for (const force of [false, true]) {
100
+ test(`índice divergente impede atualização de apontamento ativo (force=${force})`, t => {
101
+ const root = fixture(t), generated = readFileSync(file(root, pointer));
102
+ put(root, pointer, Buffer.concat([generated, Buffer.from('\nAlteração humana staged.\n')]));
103
+ git(root, 'add', '-f', '--', pointer);
104
+ const index = git(root, 'ls-files', '--stage', '-z');
105
+ put(root, pointer, generated);
106
+ put(root, original, '---\nname: criar-skill\ndescription: Mudança real.\n---\n\n# Criar skill\n');
107
+ const state = readFileSync(file(root, '.pbai4devs/state/derived.json'));
108
+ assert.notEqual(run(root, { check: true, force }).exitCode, 0);
109
+ assert.throws(() => run(root, { force }), /indice/i);
110
+ assert.deepEqual(readFileSync(file(root, pointer)), generated);
111
+ assert.deepEqual(readFileSync(file(root, '.pbai4devs/state/derived.json')), state);
112
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
113
+ });
114
+ }
115
+
116
+ for (const flag of ['--assume-unchanged', '--skip-worktree']) {
117
+ test(`índice divergente continua protegido com ${flag}`, t => {
118
+ const root = fixture(t), generated = readFileSync(file(root, pointer));
119
+ put(root, pointer, Buffer.concat([generated, Buffer.from('\nAlteração humana staged.\n')]));
120
+ git(root, 'add', '-f', '--', pointer);
121
+ put(root, pointer, generated);
122
+ git(root, 'update-index', flag, '--', pointer);
123
+ const index = git(root, 'ls-files', '--stage', '-z');
124
+ const flags = git(root, 'ls-files', '-v', '--', pointer);
125
+ put(root, original, '---\nname: criar-skill\ndescription: Mudança real.\n---\n\n# Criar skill\n');
126
+ assert.notEqual(run(root, { check: true, force: true }).exitCode, 0);
127
+ assert.throws(() => run(root, { force: true }), /indice/i);
128
+ assert.deepEqual(readFileSync(file(root, pointer)), generated);
129
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
130
+ assert.equal(git(root, 'ls-files', '-v', '--', pointer), flags);
131
+ });
132
+ }
133
+
134
+ test('índice com alias de caixa bloqueia alteração conservadoramente em qualquer sistema', t => {
135
+ const root = fixture(t), generated = readFileSync(file(root, pointer));
136
+ const alias = pointer.replace('/skills/', '/SKILLS/');
137
+ put(root, alias, Buffer.concat([generated, Buffer.from('\nAlteração humana staged.\n')]));
138
+ git(root, 'add', '-f', '--', alias);
139
+ const index = git(root, 'ls-files', '--stage', '-z');
140
+ put(root, pointer, generated);
141
+ put(root, original, '---\nname: criar-skill\ndescription: Mudança real.\n---\n\n# Criar skill\n');
142
+ const aliasBytes = readFileSync(file(root, alias));
143
+ const state = readFileSync(file(root, '.pbai4devs/state/derived.json'));
144
+ const ignore = readFileSync(file(root, '.gitignore'));
145
+ const preserved = () => {
146
+ assert.deepEqual(readFileSync(file(root, pointer)), generated);
147
+ assert.deepEqual(readFileSync(file(root, alias)), aliasBytes);
148
+ assert.deepEqual(readFileSync(file(root, '.pbai4devs/state/derived.json')), state);
149
+ assert.deepEqual(readFileSync(file(root, '.gitignore')), ignore);
150
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
151
+ };
152
+ let check, error;
153
+ try { check = run(root, { check: true, force: true }); } catch (failure) { error = failure; }
154
+ if (error) assert.match(error.message, /maiusculas\/minusculas/i);
155
+ else assert.notEqual(check.exitCode, 0);
156
+ preserved();
157
+ assert.throws(() => run(root, { force: true }), /indice|maiusculas\/minusculas/i);
158
+ preserved();
159
+ });
160
+
161
+ test('comparação de índice entende CRLF autorizado e recusa filtros, binários e divergência', t => {
162
+ const root = fixture(t), path = 'arquivo.txt';
163
+ const crlf = Buffer.from('texto\r\noutra linha\r\n'), lf = Buffer.from('texto\noutra linha\n');
164
+ git(root, 'config', 'core.autocrlf', 'false');
165
+ git(root, 'config', 'core.attributesfile', file(root, 'atributos-ausentes'));
166
+ assert.equal(indexContentMatches(root, path, crlf, lf), false);
167
+ git(root, 'config', 'core.autocrlf', 'true');
168
+ assert.equal(indexContentMatches(root, path, crlf, lf), true);
169
+ assert.equal(indexContentMatches(root, path, crlf, Buffer.from('texto humano\noutra linha\n')), false);
170
+ assert.equal(indexContentMatches(root, path, Buffer.from([0, 13, 10]), Buffer.from([0, 10])), false);
171
+ assert.equal(indexContentMatches(root, path, Buffer.from('um\r\ndois\r'), Buffer.from('um\ndois\r')), false);
172
+ assert.equal(indexContentMatches(root, path, Buffer.from('\ufefftexto\r\n'), Buffer.from('texto\n')), false);
173
+ assert.equal(indexContentMatches(root, path, Buffer.from('\ufefftexto\r\n'), Buffer.from('\ufefftexto\n')), true);
174
+ put(root, '.gitattributes', '*.txt -text\n');
175
+ assert.equal(indexContentMatches(root, path, crlf, lf), false);
176
+ put(root, '.gitattributes', '*.txt text filter=nao-executar\n');
177
+ git(root, 'config', 'filter.nao-executar.clean', 'exit 1');
178
+ assert.equal(indexContentMatches(root, path, crlf, lf), false);
179
+ put(root, '.gitattributes', '*.txt text working-tree-encoding=UTF-16\n');
180
+ assert.equal(indexContentMatches(root, path, crlf, lf), false);
181
+ put(root, '.gitattributes', '*.txt text ident\n');
182
+ assert.equal(indexContentMatches(root, path, crlf, lf), false);
183
+ put(root, '.gitattributes', '*.txt text eol=crlf\n');
184
+ git(root, 'config', 'core.autocrlf', 'false');
185
+ assert.equal(indexContentMatches(root, path, crlf, lf), true);
186
+ });
187
+
188
+ test('sync atualiza apontamento staged equivalente em repositório com autocrlf', t => {
189
+ const root = fixture(t), generated = readFileSync(file(root, pointer), 'utf8').replaceAll('\n', '\r\n');
190
+ git(root, 'config', 'core.autocrlf', 'true');
191
+ put(root, pointer, generated);
192
+ git(root, 'add', '-f', '--', pointer);
193
+ const index = git(root, 'ls-files', '--stage', '-z');
194
+ put(root, original, '---\nname: criar-skill\ndescription: Mudança real.\n---\n\n# Criar skill\n');
195
+ assert.equal(run(root, { force: true }).exitCode, 0);
196
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
197
+ });
198
+
199
+ for (const target of ['.pbai4devs/state/derived.json', '.claude/.gitignore']) {
200
+ test(`índice divergente impede atualização de ${target}`, t => {
201
+ const root = fixture(t), generated = readFileSync(file(root, target));
202
+ put(root, target, Buffer.concat([generated, Buffer.from('\nAlteração humana staged.\n')]));
203
+ git(root, 'add', '-f', '--', target);
204
+ const index = git(root, 'ls-files', '--stage', '-z');
205
+ put(root, target, generated);
206
+ if (target.endsWith('derived.json')) {
207
+ put(root, original, '---\nname: criar-skill\ndescription: Mudança real.\n---\n\n# Criar skill\n');
208
+ } else {
209
+ put(root, '.pbai4devs/templates/gitignore/agents.ignore', '# Cache atualizado\n/*\n!/.gitignore\n{{ALLOWLIST}}\n/cache/\n*.log\n');
210
+ }
211
+ assert.notEqual(run(root, { check: true, force: true }).exitCode, 0);
212
+ assert.throws(() => run(root, { force: true }), /indice/i);
213
+ assert.deepEqual(readFileSync(file(root, target)), generated);
214
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
215
+ });
216
+ }
217
+
218
+ for (const options of [{ check: true }, { force: true }]) {
219
+ test(`Git indisponível bloqueia limpeza em repositório (check=${Boolean(options.check)}, force=${Boolean(options.force)})`, t => {
220
+ const root = fixture(t), generated = readFileSync(file(root, pointer));
221
+ put(root, pointer, Buffer.concat([generated, Buffer.from('\nConteúdo humano staged.\n')]));
222
+ git(root, 'add', '-f', '--', pointer);
223
+ const index = git(root, 'ls-files', '--stage', '-z');
224
+ put(root, pointer, generated);
225
+ rmSync(dirname(file(root, original)), { recursive: true });
226
+ const state = readFileSync(file(root, '.pbai4devs/state/derived.json'));
227
+ withoutGit(t, () => assert.throws(() => run(root, options), /consultar o Git.*preservar/i));
228
+ assert.deepEqual(readFileSync(file(root, pointer)), generated);
229
+ assert.deepEqual(readFileSync(file(root, '.pbai4devs/state/derived.json')), state);
230
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
231
+ });
232
+ }
233
+
234
+ test('projeto realmente sem Git pode limpar apontamento próprio sem executável Git', t => {
235
+ const root = fixture(t, false), human = '.claude/skills/criar-skill/equipe-vazia';
236
+ mkdirSync(file(root, human));
237
+ rmSync(dirname(file(root, original)), { recursive: true });
238
+ withoutGit(t, () => assert.equal(run(root, { force: true }).exitCode, 0));
239
+ assert.equal(existsSync(file(root, pointer)), false);
240
+ assert.ok(existsSync(file(root, human)));
241
+ });
242
+
243
+ test('preserva diretório preexistente sem prova de propriedade após remover único apontamento', t => {
244
+ const root = fixture(t), directory = dirname(file(root, pointer));
245
+ rmSync(dirname(file(root, original)), { recursive: true });
246
+ assert.equal(run(root, { force: true }).exitCode, 0);
247
+ assert.equal(existsSync(file(root, pointer)), false);
248
+ assert.ok(existsSync(directory));
249
+ });
250
+
251
+ test('alteração concorrente no índice bloqueia limpeza antes de qualquer exclusão', t => {
252
+ const root = fixture(t), generated = readFileSync(file(root, pointer));
253
+ rmSync(dirname(file(root, original)), { recursive: true });
254
+ let staged;
255
+ assert.throws(() => synchronize({ root, localIgnores: false, generateEntrypoints: false, force: true,
256
+ output() {
257
+ if (staged !== undefined) return;
258
+ put(root, pointer, Buffer.concat([generated, Buffer.from('\nStaged pela equipe.\n')]));
259
+ git(root, 'add', '-f', '--', pointer);
260
+ staged = git(root, 'ls-files', '--stage', '-z');
261
+ put(root, pointer, generated);
262
+ },
263
+ }), /indice git mudou/i);
264
+ assert.notEqual(staged, undefined);
265
+ assert.deepEqual(readFileSync(file(root, pointer)), generated);
266
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), staged);
267
+ });
268
+
269
+ test('índice divergente é protegido quando o projeto fica em subpasta do repositório Git', t => {
270
+ const outer = mkdtempSync(join(tmpdir(), 'sync-parent-git-'));
271
+ t.after(() => rmSync(outer, { recursive: true, force: true }));
272
+ git(outer, 'init', '--quiet');
273
+ const root = join(outer, 'projeto'); mkdirSync(root);
274
+ populate(root);
275
+ const generated = readFileSync(file(root, pointer));
276
+ put(root, pointer, Buffer.concat([generated, Buffer.from('\nStaged pela equipe.\n')]));
277
+ git(root, 'add', '-f', '--', pointer);
278
+ const index = git(outer, 'ls-files', '--stage', '-z');
279
+ put(root, pointer, generated);
280
+ rmSync(dirname(file(root, original)), { recursive: true });
281
+ assert.throws(() => run(root, { force: true }), /indice/i);
282
+ assert.deepEqual(readFileSync(file(root, pointer)), generated);
283
+ assert.equal(git(outer, 'ls-files', '--stage', '-z'), index);
284
+ });
285
+
286
+ test('conteúdo humano na pasta e apontamento gerenciado editado sobrevivem à limpeza forçada', t => {
287
+ const root = fixture(t), human = '.claude/skills/criar-skill/notas-da-equipe.md';
288
+ const notes = '# Referência própria\n', edited = Buffer.concat([readFileSync(file(root, pointer)), Buffer.from('\nNota própria.\n')]);
289
+ put(root, human, notes);
290
+ put(root, pointer, edited);
291
+ rmSync(dirname(file(root, original)), { recursive: true });
292
+ run(root, { force: true });
293
+ assert.equal(readFileSync(file(root, human), 'utf8'), notes);
294
+ assert.deepEqual(readFileSync(file(root, pointer)), edited);
295
+ });
296
+
297
+ test('colisão com arquivo humano ancestral não é removida por --force', t => {
298
+ const root = fixture(t), ancestor = '.claude/skills/equipe-custom';
299
+ const human = 'Arquivo da equipe; não substituir.\n';
300
+ put(root, ancestor, human);
301
+ put(root, '.pbai4devs/skills/projeto/equipe-custom/SKILL.md', '---\nname: equipe-custom\ndescription: Exemplo.\n---\n\n# Equipe\n');
302
+ const index = git(root, 'ls-files', '--stage', '-z');
303
+ try { run(root, { force: true }); } catch (error) { assert.match(error.message, /conflito|gerenciado|arquivo|diretor/i); }
304
+ assert.equal(readFileSync(file(root, ancestor), 'utf8'), human);
305
+ assert.equal(git(root, 'ls-files', '--stage', '-z'), index);
306
+ });
@@ -13,7 +13,9 @@ import './LOCAL_IGNORE.mjs';
13
13
  import './HUMAN_OWNERSHIP.mjs';
14
14
  import './COEXISTENCE.mjs';
15
15
  import './MANAGED_CONTENT.mjs';
16
+ import './PRESERVATION.mjs';
16
17
  import { validateCatalog as validateExternalCatalog } from '../../gerenciar-skills-externas/SCRIPT.mjs';
18
+ import { stateDirectory } from '../../lib/state-path.mjs';
17
19
 
18
20
  const project = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
19
21
  const cli = resolve(dirname(fileURLToPath(import.meta.url)), '../SCRIPT.mjs');
@@ -24,7 +26,7 @@ const read = (root, path) => readFileSync(join(root, path), 'utf8');
24
26
  const write = (root, path, content) => { mkdirSync(dirname(join(root, path)), { recursive: true }); writeFileSync(join(root, path), content); };
25
27
  const pointer = (origin = 'origem.md', title = 'Referencia') => `---\ndescription: Referencia.\n---\n\n# ${title}\n\n${marker}\nLeia e siga [origem](${origin}).\n`;
26
28
  const recordOwned = (root, paths) => {
27
- const statePath = '.pbai4devs/estado/agentdevkit.json';
29
+ const statePath = '.pbai4devs/state/agentdevkit.json';
28
30
  const previous = existsSync(join(root, statePath)) ? JSON.parse(read(root, statePath)) : {};
29
31
  write(root, statePath, JSON.stringify({ schemaVersion: 1, files: { ...previous.files, ...Object.fromEntries(paths.map(path => [path, createHash('sha256').update(readFileSync(join(root, path))).digest('hex')])) }, blocks: previous.blocks ?? {} }));
30
32
  };
@@ -75,7 +77,7 @@ function fixture(t, minimal = false) {
75
77
  write(root, '.github/prompts/custom.prompt.md', '# Prompt proprio\n');
76
78
  write(root, '.claude/notas.md', '# Nota propria\n');
77
79
  write(root, 'README.md', 'README raiz preservado\n');
78
- for (const kind of ['templates', 'scripts', 'tools', 'external-skills', 'estado', 'tests', 'docs', 'adapters', 'extra']) {
80
+ for (const kind of ['templates', 'scripts', 'tools', 'external-skills', 'state', 'tests', 'docs', 'adapters', 'extra']) {
79
81
  write(root, `.pbai4devs/${kind}/demo/REFERENCE.md`, '---\ndescription: Exclusivo.\n---\n\n# Exclusivo\n');
80
82
  if (kind !== 'extra') {
81
83
  const target = `.claude/${kind}/demo/REFERENCE.md`;
@@ -92,6 +94,30 @@ function fixture(t, minimal = false) {
92
94
  recordOwned(root, ownedLegacy);
93
95
  return root;
94
96
  }
97
+
98
+ test('sincronização direta conserva estado legado e recusa duas pastas de estado', t => {
99
+ const root = fixture(t, true);
100
+ write(root, source, skill);
101
+ write(root, '.pbai4devs/estado/agentdevkit.json', JSON.stringify({
102
+ schemaVersion: 1, clients: ['codex'], files: {}, blocks: {},
103
+ }));
104
+ assert.equal(run(root).exitCode, 0);
105
+ assert.equal(existsSync(join(root, '.pbai4devs/estado/derived.json')), true);
106
+ assert.equal(existsSync(join(root, '.pbai4devs/state')), false);
107
+ write(root, '.pbai4devs/state/configuracao/ESTADO.md', '# Decisões próprias\n');
108
+ const before = snapshot(root);
109
+ assert.throws(() => run(root), /coexistem/);
110
+ assert.deepEqual(snapshot(root), before);
111
+ });
112
+
113
+ test('detecção de estado recusa link simbólico sem abrir o destino', { skip: process.platform === 'win32' }, t => {
114
+ const root = fixture(t, true);
115
+ const external = join(root, 'externo');
116
+ mkdirSync(external);
117
+ symlinkSync(external, join(root, '.pbai4devs/estado'), 'dir');
118
+ assert.throws(() => stateDirectory(root), /Link simbolico/);
119
+ assert.equal(existsSync(join(root, '.pbai4devs/state')), false);
120
+ });
95
121
  function rejected(root, options = {}, pattern) {
96
122
  const before = snapshot(root);
97
123
  assert.throws(() => run(root, options), pattern);
@@ -115,7 +141,12 @@ test('sincroniza somente principais, preserva metadados e usa referências UTF-8
115
141
  assert.match(read(root, '.kimi-code/agents/revisor.md'), /tools: \[Read, Grep, Glob\]/);
116
142
  assert.ok(existsSync(join(root, '.claude/skills/projeto/demo/SKILL.md')));
117
143
  for (const client of ['.claude', '.opencode', '.github', '.codex', '.cursor', '.kimi-code', '.vscode', '.agents', '.agent', '.gemini']) {
118
- for (const kind of ['templates', 'scripts', 'tools', 'external-skills', 'estado', 'tests', 'docs', 'adapters', 'extra', 'README.md', 'INSTRUCTIONS.md']) assert.equal(existsSync(join(root, client, kind)), false, `${client}/${kind}`);
144
+ for (const kind of ['templates', 'scripts', 'tools', 'external-skills', 'state', 'estado', 'tests', 'docs', 'adapters', 'extra', 'README.md', 'INSTRUCTIONS.md']) {
145
+ if (client === '.claude' && ['templates', 'scripts', 'tools', 'external-skills', 'state', 'tests', 'docs', 'adapters'].includes(kind)) {
146
+ assert.equal(existsSync(join(root, client, kind, 'demo/REFERENCE.md')), false);
147
+ assert.ok(existsSync(join(root, client, kind)), 'diretório sem prova de propriedade permanece');
148
+ } else assert.equal(existsSync(join(root, client, kind)), false, `${client}/${kind}`);
149
+ }
119
150
  }
120
151
  assert.deepEqual(JSON.parse(read(root, '.pbai4devs/agents.json')).entries, [{ path: '.pbai4devs/agents/revisor/AGENT.md' }]);
121
152
  assert.ok(JSON.parse(read(root, '.pbai4devs/skills.json')).entries.some(entry => entry.path === '.pbai4devs/skills/grupo com espaço/outro'));
@@ -416,7 +447,8 @@ test('atualiza fontes, adiciona clientes, remove recursos e migra agrupado para
416
447
  rmSync(join(root, '.pbai4devs/rules/example/RULE.md'));
417
448
  write(root, '.pbai4devs/espelhos.conf', read(root, '.pbai4devs/espelhos.conf').replace('agente|.claude\n', 'agente|.claude|plano\n'));
418
449
  run(root);
419
- assert.equal(existsSync(join(root, '.claude/skills/projeto')), false);
450
+ assert.equal(existsSync(join(root, '.claude/skills/projeto/demo/SKILL.md')), false);
451
+ assert.ok(existsSync(join(root, '.claude/skills/projeto')));
420
452
  assert.ok(existsSync(join(root, '.claude/skills/demo/SKILL.md')));
421
453
  assert.equal(existsSync(join(root, '.codex/rules/example.md')), false);
422
454
  assert.deepEqual(JSON.parse(read(root, '.pbai4devs/rules.json')).entries, []);
@@ -589,12 +621,12 @@ test('entradas da CLI mantêm ownership integral e recursos têm proveniência d
589
621
  write(root, source, skill);
590
622
  write(root, '.pbai4devs/templates/entrypoints/AGENTS.md', template);
591
623
  write(root, 'AGENTS.md', entry);
592
- write(root, '.pbai4devs/estado/agentdevkit.json', JSON.stringify({ clients: ['codex'], files: { 'AGENTS.md': createHash('sha256').update(entry).digest('hex') } }));
624
+ write(root, '.pbai4devs/state/agentdevkit.json', JSON.stringify({ clients: ['codex'], files: { 'AGENTS.md': createHash('sha256').update(entry).digest('hex') } }));
593
625
  const result = run(root, { generateEntrypoints: true });
594
626
  assert.ok(result.ownedOutputs.includes('AGENTS.md'));
595
627
  assert.equal(read(root, 'AGENTS.md'), entry);
596
- assert.equal(JSON.parse(read(root, '.pbai4devs/estado/derived.json')).entries['AGENTS.md'], undefined);
597
- assert.ok(JSON.parse(read(root, '.pbai4devs/estado/derived.json')).files['.codex/skills/demo/SKILL.md']);
628
+ assert.equal(JSON.parse(read(root, '.pbai4devs/state/derived.json')).entries['AGENTS.md'], undefined);
629
+ assert.ok(JSON.parse(read(root, '.pbai4devs/state/derived.json')).files['.codex/skills/demo/SKILL.md']);
598
630
  assertCheckReadonly(root, { generateEntrypoints: true }, 0);
599
631
  });
600
632
 
@@ -857,7 +889,8 @@ test('manifestos Antigravity acompanham skills, regras e agentes agrupados', t =
857
889
  rmSync(join(root, '.pbai4devs/skills/projeto/demo'), { recursive: true });
858
890
  run(root, { generateEntrypoints: true });
859
891
  assert.deepEqual(JSON.parse(read(root, '.agents/skills.json')).entries, [{ path: '.agents/skills/projeto/new-skill' }]);
860
- assert.equal(existsSync(join(root, '.agents/skills/projeto/demo')), false);
892
+ assert.equal(existsSync(join(root, '.agents/skills/projeto/demo/SKILL.md')), false);
893
+ assert.ok(existsSync(join(root, '.agents/skills/projeto/demo')));
861
894
  assertCheckReadonly(root, { generateEntrypoints: true }, 0);
862
895
  });
863
896
 
@@ -8,6 +8,7 @@ import { isMain, projectRoot, requireNode } from '../lib/runtime.mjs';
8
8
  import { isGeneratedOutput } from '../lib/gitignore.mjs';
9
9
  import { resourcePath, synchronize } from '../sincronizar-agentes/SCRIPT.mjs';
10
10
  import { adapterManifest } from '../lib/adapters.mjs';
11
+ import { stateDirectory } from '../lib/state-path.mjs';
11
12
 
12
13
  const assets = join(dirname(fileURLToPath(import.meta.url)), 'assets');
13
14
  const owner = 'testar-maturidade';
@@ -47,9 +48,9 @@ export const SCENARIOS = [
47
48
  ];
48
49
 
49
50
  function manifest(root) {
51
+ const state = join(root, stateDirectory(root), 'agentdevkit.json');
50
52
  const file = join(root, '.pbai4devs/espelhos.conf');
51
53
  if (existsSync(file)) return readFileSync(file, 'utf8');
52
- const state = join(root, '.pbai4devs/estado/agentdevkit.json');
53
54
  return adapterManifest(root, { clients: existsSync(state) ? json(state).clients : undefined });
54
55
  }
55
56
 
@@ -94,7 +95,7 @@ function copyAllowed(relativePath, directory = false) {
94
95
 
95
96
  export function copyAgentStructure(sourceRoot, destination) {
96
97
  const roots = ['.pbai4devs', ...registeredRoots(sourceRoot)];
97
- const stateFile = join(sourceRoot, '.pbai4devs/estado/agentdevkit.json');
98
+ const stateFile = join(sourceRoot, stateDirectory(sourceRoot), 'agentdevkit.json');
98
99
  const stateInfo = stat(stateFile);
99
100
  if (stateInfo && (!stateInfo.isFile() || stateInfo.isSymbolicLink())) fail('Estado da instalação deve ser arquivo regular.');
100
101
  const installation = stateInfo ? json(stateFile) : null;
@@ -162,10 +163,11 @@ function ensureOutput(output) {
162
163
  }
163
164
 
164
165
  function planning(root, scenario) {
165
- const state = '.pbai4devs/estado/configuracao/ESTADO.md';
166
+ const stateRoot = stateDirectory(root);
167
+ const state = `${stateRoot}/configuracao/ESTADO.md`;
166
168
  rmSync(join(root, state), { force: true });
167
169
  if (scenario.planning === 'none' && !scenario.app) return;
168
- write(root, '.pbai4devs/estado/README.md', document('Estado dos exemplos', 'Dados sintéticos deste cenário, sem aprovação real de produto.'));
170
+ write(root, `${stateRoot}/README.md`, document('Estado dos exemplos', 'Dados sintéticos deste cenário, sem aprovação real de produto.'));
169
171
  let paths = { prd: null, ux_ui: null, design: null, releases: null, specs: null };
170
172
  if (scenario.planning !== 'none') {
171
173
  for (const folder of ['docs', 'docs/produto', 'docs/design']) write(root, `${folder}/README.md`, document('Documentos do cenário', 'Entradas sintéticas para testar a classificação de maturidade.'));
@@ -27,6 +27,7 @@ function fixture(t) {
27
27
  put('.gitignore', readFileSync(join(projectRoot, '.gitignore')));
28
28
  put('.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs', readFileSync(join(projectRoot, '.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs')));
29
29
  put('.pbai4devs/scripts/lib/runtime.mjs', readFileSync(join(projectRoot, '.pbai4devs/scripts/lib/runtime.mjs')));
30
+ put('.pbai4devs/scripts/lib/state-path.mjs', readFileSync(join(projectRoot, '.pbai4devs/scripts/lib/state-path.mjs')));
30
31
  return { root, put, outputRoot: join(root, 'examples/generated') };
31
32
  }
32
33
 
@@ -112,7 +113,7 @@ test('preparação interrompida não conserva resultado anterior nem declara suc
112
113
  const index = join(outputRoot, 'README.md');
113
114
  writeFileSync(report, JSON.stringify({ owner: 'testar-maturidade', status: 'passed', results: [{}] }));
114
115
  writeFileSync(index, readFileSync(index, 'utf8').replaceAll('prepared', 'passed'));
115
- put('.pbai4devs/estado/agentdevkit.json', JSON.stringify({ blocks: null }));
116
+ put('.pbai4devs/state/agentdevkit.json', JSON.stringify({ blocks: null }));
116
117
  assert.throws(() => prepareExamples(options), /Estado da instalação/);
117
118
  assert.equal(existsSync(report), false);
118
119
  assert.match(readFileSync(index, 'utf8'), /status: preparing/);
@@ -181,7 +182,7 @@ test('instalação parcial preserva entradas próprias e blocos editados', t =>
181
182
  const { root, put } = fixture(t);
182
183
  put('.pbai4devs/espelhos.conf', 'agente|.codex|agrupado\nagente|.agents|agrupado\n');
183
184
  const block = '<!-- BEGIN AGENTDEVKIT -->\nEntrada gerenciada.\n<!-- END AGENTDEVKIT -->';
184
- put('.pbai4devs/estado/agentdevkit.json', JSON.stringify({ schemaVersion: 1, version: '1.0.0', language: 'pt', clients: ['codex'], files: {}, blocks: { 'AGENTS.md': createHash('sha256').update(block).digest('hex') } }));
185
+ put('.pbai4devs/state/agentdevkit.json', JSON.stringify({ schemaVersion: 1, version: '1.0.0', language: 'pt', clients: ['codex'], files: {}, blocks: { 'AGENTS.md': createHash('sha256').update(block).digest('hex') } }));
185
186
  put('AGENTS.md', `# Agentes\n\n${block}\n`);
186
187
  const human = '# Claude pessoal\r\n\r\nPreservar byte a byte: ação.\r\n';
187
188
  put('CLAUDE.md', human);
@@ -222,7 +223,7 @@ test('checkout de fontes sem manifesto deriva clientes em memória', t => {
222
223
  assert.ok(registeredRoots(root).includes('.claude'));
223
224
  const source = '.pbai4devs/skills/projeto/demo/SKILL.md';
224
225
  assert.ok(resourceTargets(root, source).includes('.claude/skills/demo/SKILL.md'));
225
- put('.pbai4devs/estado/agentdevkit.json', JSON.stringify({ clients: ['claude'] }));
226
+ put('.pbai4devs/state/agentdevkit.json', JSON.stringify({ clients: ['claude'] }));
226
227
  assert.deepEqual(registeredRoots(root), ['.claude']);
227
228
  assert.deepEqual(resourceTargets(root, source), ['.claude/skills/demo/SKILL.md']);
228
229
  assert.equal(existsSync(manifest), false);
@@ -8,12 +8,13 @@ import { buildCatalog } from '../lib/catalog.mjs';
8
8
  import { isGeneratedOutput } from '../lib/gitignore.mjs';
9
9
  import { canonicalEntrypoint, extractManagedBlock, managedBlockDigest } from '../lib/managed-content.mjs';
10
10
  import { extractTomlManagedBlock, tomlManagedBlockDigest } from '../lib/managed-toml.mjs';
11
+ import { stateDirectory } from '../lib/state-path.mjs';
11
12
 
12
13
  const fail = message => { throw new Error(message); };
13
14
  const normalize = value => value.split(sep).join('/');
14
15
  const excluded = new Set(['node_modules', '.git', 'opencode-loop', '.agent-tests']);
15
16
  const toolState = new Set(['bin', '.cache', 'npm-cache']);
16
- const auxiliary = ['templates', 'scripts', 'tools', 'external-skills', 'adapters', 'estado', 'tests', 'docs'];
17
+ const auxiliary = ['templates', 'scripts', 'tools', 'external-skills', 'adapters', 'state', 'estado', 'tests', 'docs'];
17
18
 
18
19
  function checkedStat(root, location) {
19
20
  let item;
@@ -31,8 +32,9 @@ function checkedPath(root, path) {
31
32
  }
32
33
 
33
34
  function ownership(root) {
34
- const file = checkedPath(root, '.pbai4devs/estado/agentdevkit.json');
35
- const derivedFile = checkedPath(root, '.pbai4devs/estado/derived.json');
35
+ const stateRoot = stateDirectory(root);
36
+ const file = checkedPath(root, `${stateRoot}/agentdevkit.json`);
37
+ const derivedFile = checkedPath(root, `${stateRoot}/derived.json`);
36
38
  if (!checkedStat(root, file) && !checkedStat(root, derivedFile)) return null;
37
39
  const readState = location => {
38
40
  try { return JSON.parse(readFileSync(location, 'utf8')); }
@@ -1,5 +1,5 @@
1
1
  import assert from 'node:assert/strict';
2
- import { lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
2
+ import { lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { join, relative } from 'node:path';
5
5
  import { spawnSync } from 'node:child_process';
@@ -43,14 +43,22 @@ const verify = root => verifyResources({ root, synchronize() {}, catalogCheck()
43
43
 
44
44
  function installedFixture(t) {
45
45
  const context = resourceFixture(t);
46
- context.file('.pbai4devs/estado/README.md');
46
+ context.file('.pbai4devs/state/README.md');
47
47
  const generated = '.agents/skills/managed/SKILL.md';
48
48
  context.file(generated, document('# Gerado\n'));
49
49
  const files = { [generated]: createHash('sha256').update(readFileSync(join(context.root, generated))).digest('hex') };
50
- context.file('.pbai4devs/estado/agentdevkit.json', JSON.stringify({ schemaVersion: 1, version: '0.0.9', language: 'pt', files, blocks: {} }));
50
+ context.file('.pbai4devs/state/agentdevkit.json', JSON.stringify({ schemaVersion: 1, version: '0.0.9', language: 'pt', files, blocks: {} }));
51
51
  return { ...context, generated };
52
52
  }
53
53
 
54
+ test('verificação consulta propriedade legada e recusa estado ambíguo', t => {
55
+ const { root } = installedFixture(t);
56
+ renameSync(join(root, '.pbai4devs/state'), join(root, '.pbai4devs/estado'));
57
+ assert.doesNotThrow(() => collectMarkdown(root));
58
+ mkdirSync(join(root, '.pbai4devs/state'));
59
+ assert.throws(() => collectMarkdown(root), /coexistem/);
60
+ });
61
+
54
62
  function writeCatalog(root) {
55
63
  for (const [path, content] of buildCatalog(root).outputs) { mkdirSync(join(root, path, '..'), { recursive: true }); writeFileSync(join(root, path), content); }
56
64
  }
@@ -161,10 +169,10 @@ test('instalação confere entradas derivadas sem aplicar lint a blocos humanos'
161
169
  const { root, file } = installedFixture(t);
162
170
  const contents = document('# Entrada gerada\n');
163
171
  file('GEMINI.md', contents);
164
- file('.pbai4devs/estado/derived.json', JSON.stringify({ schemaVersion: 1, entries: { 'GEMINI.md': createHash('sha256').update(contents).digest('hex') } }));
172
+ file('.pbai4devs/state/derived.json', JSON.stringify({ schemaVersion: 1, entries: { 'GEMINI.md': createHash('sha256').update(contents).digest('hex') } }));
165
173
  const block = '<!-- BEGIN AGENTDEVKIT -->\nLeia as fontes.\n<!-- END AGENTDEVKIT -->';
166
174
  file('AGENTS.md', `# Instruções humanas sem frontmatter\n\n${block}\n`);
167
- const statePath = join(root, '.pbai4devs/estado/agentdevkit.json');
175
+ const statePath = join(root, '.pbai4devs/state/agentdevkit.json');
168
176
  const state = JSON.parse(readFileSync(statePath, 'utf8'));
169
177
  state.blocks['AGENTS.md'] = createHash('sha256').update(block).digest('hex');
170
178
  state.files['.pbai4devs/skills/exemplo/SKILL.md'] = createHash('sha256').update('fonte anterior').digest('hex');
@@ -182,7 +190,7 @@ test('instalação também protege manifestos registrados em derived.outputs', t
182
190
  const manifest = '.agents/skills.json';
183
191
  const contents = '{"entries":[]}\n';
184
192
  file(manifest, contents);
185
- file('.pbai4devs/estado/derived.json', JSON.stringify({ schemaVersion: 1, entries: {}, outputs: { [manifest]: createHash('sha256').update(contents).digest('hex') } }));
193
+ file('.pbai4devs/state/derived.json', JSON.stringify({ schemaVersion: 1, entries: {}, outputs: { [manifest]: createHash('sha256').update(contents).digest('hex') } }));
186
194
  assert.ok(!verify(root).files.includes(join(root, manifest)));
187
195
  rmSync(join(root, manifest));
188
196
  assert.throws(() => verify(root), /Arquivo gerenciado ausente: .agents\/skills.json/);
@@ -372,7 +380,7 @@ test('fontes novas permanecem elegíveis e derivados locais ficam fora do índic
372
380
  const outputs = ['.github/skills/generated/SKILL.md', '.vscode/.gitignore', '.github/.gitignore'];
373
381
  applyLocalIgnore(planLocalIgnore(root, { outputs }));
374
382
  const sources = ['src/new.mjs', 'bin/new.mjs', 'tests/new.test.mjs', 'scripts/release/new.mjs', '.github/workflows/own.yml', '.github/ISSUE_TEMPLATE/bug.yml', '.github/skills/human/SKILL.md', '.vscode/settings.json'];
375
- const generated = [...outputs, '.pbai4devs/catalog.json', '.pbai4devs/skills.json', '.pbai4devs/espelhos.conf', '.pbai4devs/estado/agentdevkit.json'];
383
+ const generated = [...outputs, '.pbai4devs/catalog.json', '.pbai4devs/skills.json', '.pbai4devs/espelhos.conf', '.pbai4devs/state/agentdevkit.json'];
376
384
  for (const path of [...sources, ...generated]) file(path, 'fixture\n');
377
385
  assert.deepEqual(ignoredPaths(root, sources), []);
378
386
  assert.deepEqual(new Set(ignoredPaths(root, generated)), new Set(generated));
@@ -9,7 +9,7 @@ Siga `.pbai4devs/rules/qualidade/RULE.md` ao criar arquivos Markdown.
9
9
  Antes de escrever, use `atualizar-documentacao` para ler e reaproveitar documentos
10
10
  existentes; aplique o template somente às lacunas.
11
11
 
12
- Consulte `.pbai4devs/estado/configuracao/ESTADO.md` e confira o PRD aprovado
12
+ Consulte `.pbai4devs/state/configuracao/ESTADO.md` e confira o PRD aprovado
13
13
  e UX/UI aprovado ou não aplicável com justificativa,
14
14
  incluindo a evidência das decisões. Se faltar, estiver pendente ou houver
15
15
  conflito, informe o bloqueio e encaminhe à skill correspondente.
@@ -9,7 +9,7 @@ Siga `.pbai4devs/rules/qualidade/RULE.md` ao criar arquivos Markdown.
9
9
  Antes de escrever, use `atualizar-documentacao` para ler e reaproveitar documentos
10
10
  existentes; aplique o template somente às lacunas.
11
11
 
12
- Confira a preparação em `.pbai4devs/estado/configuracao/ESTADO.md`. Se não estiver
12
+ Confira a preparação em `.pbai4devs/state/configuracao/ESTADO.md`. Se não estiver
13
13
  concluída e comprovada, encaminhe a `configurar-projeto` para a fase
14
14
  inicial antes de criar o PRD.
15
15
 
@@ -9,7 +9,7 @@ Siga `.pbai4devs/rules/qualidade/RULE.md` ao criar arquivos Markdown.
9
9
  Antes de escrever, use `atualizar-documentacao` para ler e reaproveitar documentos
10
10
  existentes; aplique o template somente às lacunas.
11
11
 
12
- Consulte `.pbai4devs/estado/configuracao/ESTADO.md` e confira o PRD
12
+ Consulte `.pbai4devs/state/configuracao/ESTADO.md` e confira o PRD
13
13
  com `status: aprovado` e a
14
14
  aprovação registrada. Se faltar, estiver pendente ou for insuficiente, informe
15
15
  o bloqueio e encaminhe a `criar-prd`.
@@ -20,7 +20,7 @@ as ferramentas e rode o diagnóstico. Isso não escolhe a stack da aplicação.
20
20
  Antes de criar estado ou documentos, faça o inventário de `atualizar-documentacao`.
21
21
  Mapeie os documentos já existentes aos seus papéis, sem reiniciar o produto.
22
22
  Reúna dúvidas no início e aproveite decisões existentes. Quando o projeto
23
- começar, crie ou retome `.pbai4devs/estado/configuracao/ESTADO.md` pelo modelo.
23
+ começar, crie ou retome `.pbai4devs/state/configuracao/ESTADO.md` pelo modelo.
24
24
  Combine os arquivos `prd`, `ux_ui`, `design`, `roadmap` e diretórios `releases`,
25
25
  `specs`, `tarefas` e documentos de apoio aplicáveis. Crie/evolua o README
26
26
  da raiz pelos [modelos da fase](../../../templates/README.md), preservando os
@@ -6,7 +6,7 @@ description: Identifica o estado atual e orienta a próxima ação útil do proj
6
6
  # Próximos passos
7
7
 
8
8
  Leia o pedido, o estado salvo e os documentos existentes. Use o idioma de
9
- `.pbai4devs/estado/agentdevkit.json` ou português quando ainda não houver escolha.
9
+ `.pbai4devs/state/agentdevkit.json` ou português quando ainda não houver escolha.
10
10
  Se a CLI estiver disponível, consulte `agentdevkit next --target . --json`;
11
11
  caso contrário, use os arquivos e a inspeção local do projeto.
12
12