@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
|
@@ -39,6 +39,10 @@ agente; arquivos próprios também podem manter uma pasta após a desmarcação.
|
|
|
39
39
|
| `E_MANAGED_BLOCK` | Revise marcadores ou formato; preserve suas instruções. |
|
|
40
40
|
| `E_CHANGED` | Um arquivo mudou durante a operação; gere outro plano. |
|
|
41
41
|
|
|
42
|
+
No macOS, `/tmp` e `/var/folders` são aceitos quando forem os aliases de
|
|
43
|
+
sistema esperados. Destinos que atravessam links humanos continuam recusados,
|
|
44
|
+
inclusive quando apontados por `TMPDIR`, `TEMP` ou `TMP`.
|
|
45
|
+
|
|
42
46
|
## Conflitos e backups
|
|
43
47
|
|
|
44
48
|
Simule antes de substituir uma alteração própria:
|
|
@@ -52,9 +56,10 @@ Compare cada `conflicts` com o que deseja manter. Edite a origem em
|
|
|
52
56
|
recurso. Use `sync` para atualizar suas derivações. `update` reaplica os
|
|
53
57
|
recursos do pacote invocado e pode conflitar com essa personalização.
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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:
|
|
58
63
|
|
|
59
64
|
```text
|
|
60
65
|
.agent-tests/agentdevkit/backups/<identificador>/
|
|
@@ -101,9 +106,10 @@ ou caminhos já gerenciados em links.
|
|
|
101
106
|
Destinos dentro de `.git`, `node_modules` ou `.pbai4devs` são recusados,
|
|
102
107
|
inclusive com variações de maiúsculas/minúsculas.
|
|
103
108
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
107
113
|
|
|
108
114
|
## Download, plataforma ou ferramenta falhando
|
|
109
115
|
|
package/docs/specs/README.md
CHANGED
|
@@ -28,6 +28,9 @@ description: Incrementos verificáveis do AgentDevKit.
|
|
|
28
28
|
- [Ignore de clientes](client-ignore/SPEC.md): padrão local e exceções do projeto.
|
|
29
29
|
- [Ferramenta e versões](tool-release/SPEC.md): atualização segura e tags de release.
|
|
30
30
|
- [Tags automáticas](automatic-release-tags/SPEC.md): publicação após CI aprovada.
|
|
31
|
+
- [Destinos temporários](target-paths/SPEC.md): caminhos corretos e links seguros.
|
|
32
|
+
- [Ordem antes da implementação](preimplementation-order/SPEC.md): produto,
|
|
33
|
+
spec e configuração antes do código.
|
|
31
34
|
- [Descoberta e preservação](discovery-preservation/SPEC.md): rotas de skills,
|
|
32
35
|
sincronização segura e validação em projetos reais.
|
|
33
36
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Orientação e verificação da ordem anterior à implementação.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Ordem antes da implementação
|
|
6
|
+
|
|
7
|
+
O projeto deve partir de decisões de produto e design, passar por roadmap e
|
|
8
|
+
spec e concluir a configuração técnica antes do código da aplicação.
|
|
9
|
+
|
|
10
|
+
## Critérios de aceite
|
|
11
|
+
|
|
12
|
+
- AC-001: `next` orienta roadmap e spec antes da configuração técnica e não
|
|
13
|
+
presume aprovação ou escolhe uma spec ambígua.
|
|
14
|
+
- AC-002: A verificação prévia informa bloqueio se o estado de preparação ou
|
|
15
|
+
configuração estiver ausente, inválido ou pendente; ela não modifica Git.
|
|
16
|
+
- AC-003: As skills exigem provas da infraestrutura antes de implementar e
|
|
17
|
+
registram como pendentes as provas de uma aplicação que ainda não existe.
|
|
18
|
+
- AC-004: Projetos existentes, arquivos humanos, links e índice são
|
|
19
|
+
preservados; testes e revisão cobrem decisões, lacunas e retomada.
|
|
20
|
+
|
|
21
|
+
O status registrado não comprova a evidência por si só. Instruções e scripts
|
|
22
|
+
não impedem escrita arbitrária por um agente que os ignore; essa proteção
|
|
23
|
+
depende das permissões ou hooks do cliente usado.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Resolução segura dos destinos de instalação.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Destinos de instalação
|
|
6
|
+
|
|
7
|
+
O instalador deve manter o caminho solicitado e preservar projetos existentes,
|
|
8
|
+
inclusive quando trabalha com diretórios temporários no macOS.
|
|
9
|
+
|
|
10
|
+
## Critérios de aceite
|
|
11
|
+
|
|
12
|
+
- AC-001: Destinos inexistentes preservam todos os nomes e sua ordem,
|
|
13
|
+
inclusive quando o primeiro componente está diretamente sob a raiz.
|
|
14
|
+
- AC-002: Os aliases temporários de sistema esperados no macOS funcionam para
|
|
15
|
+
instalação e consulta sem modificar o índice ou o `.gitignore` do projeto.
|
|
16
|
+
- AC-003: Destinos que atravessam links humanos são recusados, inclusive os
|
|
17
|
+
apontados por variáveis de ambiente; caminhos reservados continuam protegidos.
|
|
18
|
+
- AC-004: Testes de regressão, revisão independente e CI cobrem os cenários
|
|
19
|
+
pertinentes em Linux, Windows e macOS.
|
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',
|
package/src/install.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { chmodSync, closeSync, existsSync, fstatSync, lstatSync, openSync, readFileSync, readSync, writeFileSync, readdirSync, mkdirSync, mkdtempSync, realpathSync, renameSync, rmSync, rmdirSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
6
6
|
import { synchronize, isReference } from '../.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs';
|
|
7
7
|
import { isGeneratedOutput, planLocalIgnore, checkLocalIgnore, assertLocalIgnoreUnchanged, applyLocalIgnore, indexContentMatches } from '../.pbai4devs/scripts/lib/gitignore.mjs';
|
|
8
8
|
import { nativePathBoundary } from '../.pbai4devs/scripts/lib/native-paths.mjs';
|
|
@@ -138,12 +138,28 @@ const checkTarget = path => {
|
|
|
138
138
|
if (path.split(sep).some(part => reservedTargets.has(part.toLowerCase()))) fail('E_TARGET', 'Unsafe target directory.');
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
+
// Only normalize macOS aliases owned by the system. Environment-controlled
|
|
142
|
+
// TMPDIR is not evidence that a symbolic link is safe to follow.
|
|
143
|
+
function normalizeSystemTemporaryAlias(target) {
|
|
144
|
+
if (process.platform !== 'darwin') return target;
|
|
145
|
+
for (const { alias, allowed, physical } of [
|
|
146
|
+
{ alias: '/tmp', allowed: '/tmp', physical: '/private/tmp' },
|
|
147
|
+
{ alias: '/var', allowed: '/var/folders', physical: '/private/var' },
|
|
148
|
+
]) {
|
|
149
|
+
if (target !== allowed && !target.startsWith(`${allowed}${sep}`)) continue;
|
|
150
|
+
const link = stat(alias), directory = stat(physical);
|
|
151
|
+
if (!link?.isSymbolicLink() || link.uid !== 0 || !directory?.isDirectory() || directory.uid !== 0) return target;
|
|
152
|
+
try { if (realpathSync(alias) !== physical) return target; }
|
|
153
|
+
catch (error) { if (error.code === 'ENOENT' || error.code === 'ELOOP') return target; throw error; }
|
|
154
|
+
return join(physical, relative(alias, target));
|
|
155
|
+
}
|
|
156
|
+
return target;
|
|
157
|
+
}
|
|
158
|
+
|
|
141
159
|
export function resolveTarget(input, cwd = process.cwd()) {
|
|
142
160
|
let target = resolve(cwd, input || '.');
|
|
143
161
|
checkTarget(target);
|
|
144
|
-
|
|
145
|
-
const temporary = resolve(tmpdir()), insideTemporary = relative(temporary, target);
|
|
146
|
-
if (insideTemporary === '' || (!insideTemporary.startsWith(`..${sep}`) && insideTemporary !== '..' && !isAbsolute(insideTemporary))) target = join(realpathSync(temporary), insideTemporary);
|
|
162
|
+
target = normalizeSystemTemporaryAlias(target);
|
|
147
163
|
let ancestor = target;
|
|
148
164
|
while (true) {
|
|
149
165
|
const info = stat(ancestor);
|
|
@@ -151,7 +167,7 @@ export function resolveTarget(input, cwd = process.cwd()) {
|
|
|
151
167
|
const parent = dirname(ancestor); if (parent === ancestor) break; ancestor = parent;
|
|
152
168
|
}
|
|
153
169
|
const missing = []; let parent = target;
|
|
154
|
-
while (!stat(parent)) { missing.unshift(
|
|
170
|
+
while (!stat(parent)) { missing.unshift(basename(parent)); parent = dirname(parent); }
|
|
155
171
|
const info = stat(parent);
|
|
156
172
|
if (info.isSymbolicLink() || !info.isDirectory()) fail('E_TARGET', 'Target must be a real directory, without a symbolic link.');
|
|
157
173
|
const canonical = join(realpathSync(parent), ...missing);
|
|
@@ -441,7 +457,14 @@ function replaceJsonMember(bytes, member, value) {
|
|
|
441
457
|
fail('E_STATE', 'Invalid JSON member separator.');
|
|
442
458
|
}
|
|
443
459
|
const matches = properties.filter(item => item.key === member);
|
|
444
|
-
if (matches.length
|
|
460
|
+
if (matches.length > 1) fail('E_STATE', 'Ambiguous state member.');
|
|
461
|
+
if (!matches.length) {
|
|
462
|
+
if (value === undefined) return bytes;
|
|
463
|
+
const insertion = `${properties.length ? ',' : ''}\n ${JSON.stringify(member)}: ${JSON.stringify(value, null, 2)}\n`;
|
|
464
|
+
const next = text.slice(0, i) + insertion + text.slice(i);
|
|
465
|
+
try { JSON.parse(next); } catch { fail('E_STATE', 'Unable to preserve state safely.'); }
|
|
466
|
+
return Buffer.from(next);
|
|
467
|
+
}
|
|
445
468
|
const current = matches[0], index = properties.indexOf(current);
|
|
446
469
|
let next;
|
|
447
470
|
if (value !== undefined) next = text.slice(0, current.valueStart) + JSON.stringify(value, null, 2) + text.slice(current.end);
|
|
@@ -452,29 +475,44 @@ function replaceJsonMember(bytes, member, value) {
|
|
|
452
475
|
return Buffer.from(next);
|
|
453
476
|
}
|
|
454
477
|
|
|
478
|
+
function reconcileStateBytes(original, previous, next) {
|
|
479
|
+
if (!original) return Buffer.from(`${JSON.stringify(next, null, 2)}\n`);
|
|
480
|
+
const canonical = value => value && typeof value === 'object' && !Array.isArray(value)
|
|
481
|
+
? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])]))
|
|
482
|
+
: Array.isArray(value) ? value.map(canonical) : value;
|
|
483
|
+
let result = original;
|
|
484
|
+
for (const key of ['schemaVersion', 'version', 'language', 'clients', 'files', 'blocks']) {
|
|
485
|
+
if (JSON.stringify(canonical(previous?.[key])) !== JSON.stringify(canonical(next[key]))) result = replaceJsonMember(result, key, next[key]);
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
|
|
455
490
|
function readDerivedState(target) {
|
|
456
|
-
let entries = {}, outputs = {}, blocks = {}, files = {}, removedBlocks = [], removedFiles = {};
|
|
491
|
+
let entries = {}, outputs = {}, blocks = {}, files = {}, catalogs = {}, config = null, removedBlocks = [], removedFiles = {};
|
|
457
492
|
const derived = read(target, actualStatePath(target).replace(/agentdevkit\.json$/, 'derived.json'));
|
|
458
493
|
if (derived) {
|
|
459
494
|
try {
|
|
460
495
|
const value = JSON.parse(derived);
|
|
461
496
|
if (value.schemaVersion !== 1 || !value.entries || typeof value.entries !== 'object' || Array.isArray(value.entries)
|
|
462
|
-
|| ['outputs', 'blocks', 'files', 'removedFiles'].some(key => value[key] !== undefined && (!value[key] || typeof value[key] !== 'object' || Array.isArray(value[key])))) fail('E_STATE', 'Invalid derived entrypoint state.');
|
|
463
|
-
entries = value.entries; outputs = value.outputs ?? {}; blocks = value.blocks ?? {}; files = value.files ?? {}; removedFiles = value.removedFiles ?? {};
|
|
497
|
+
|| ['outputs', 'blocks', 'files', 'catalogs', 'removedFiles'].some(key => value[key] !== undefined && (!value[key] || typeof value[key] !== 'object' || Array.isArray(value[key])))) fail('E_STATE', 'Invalid derived entrypoint state.');
|
|
498
|
+
entries = value.entries; outputs = value.outputs ?? {}; blocks = value.blocks ?? {}; files = value.files ?? {}; catalogs = value.catalogs ?? {}; removedFiles = value.removedFiles ?? {};
|
|
499
|
+
if (value.config !== undefined && (typeof value.config !== 'string' || !/^[a-f0-9]{64}$/.test(value.config))) fail('E_STATE', 'Invalid derived configuration state.');
|
|
500
|
+
config = value.config ?? null;
|
|
464
501
|
removedBlocks = value.removedBlocks ?? [];
|
|
465
502
|
if (!Array.isArray(removedBlocks) || removedBlocks.some(path => typeof path !== 'string' || !nativeManagedText(path))) fail('E_STATE', 'Invalid removed block state.');
|
|
466
503
|
for (const [path, digest] of Object.entries(entries)) if (!canonicalEntrypoint(path) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived entrypoint state.');
|
|
467
504
|
for (const [path, digest] of Object.entries(outputs)) if (!allowedManaged(path) || !/^\.[^/]+\/(?:skills|agents|rules)\.json$/.test(path) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived output state.');
|
|
468
|
-
for (const [path, digest] of Object.entries(blocks)) if (!(nativeManagedText(path) || adapterRoots.some(root => path === `${root}/.gitignore`)) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived block state.');
|
|
505
|
+
for (const [path, digest] of Object.entries(blocks)) if (!(nativeManagedText(path) || path === '.pbai4devs/.gitignore' || adapterRoots.some(root => path === `${root}/.gitignore`)) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived block state.');
|
|
469
506
|
for (const [path, digest] of Object.entries(files)) if (!allowedManaged(path) || !/\.(?:md|mdc|toml)$/.test(path) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived file state.');
|
|
507
|
+
for (const [path, digest] of Object.entries(catalogs)) if (!/^\.pbai4devs\/(?:catalog|skills|agents|rules)\.json$/.test(path) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived catalogue state.');
|
|
470
508
|
for (const [path, digest] of Object.entries(removedFiles)) if (canonicalEntrypoint(path) || !(nativeMarkdown(path) || nativeToml(path)) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid removed file state.');
|
|
471
509
|
const installed = loadState(target);
|
|
472
510
|
if (Object.entries(removedFiles).some(([path, digest]) => installed?.files?.[path] && installed.files[path] !== digest)) fail('E_STATE', 'Removed file state differs from installation.');
|
|
473
511
|
}
|
|
474
512
|
catch { fail('E_STATE', 'Invalid derived entrypoint state.'); }
|
|
475
|
-
for (const path of [...Object.keys(entries), ...Object.keys(outputs), ...Object.keys(blocks), ...Object.keys(files), ...Object.keys(removedFiles), ...removedBlocks]) safePath(target, path);
|
|
513
|
+
for (const path of [...Object.keys(entries), ...Object.keys(outputs), ...Object.keys(blocks), ...Object.keys(files), ...Object.keys(catalogs), ...Object.keys(removedFiles), ...removedBlocks]) safePath(target, path);
|
|
476
514
|
}
|
|
477
|
-
return { entries, outputs, blocks, files, removedBlocks, removedFiles };
|
|
515
|
+
return { entries, outputs, blocks, files, catalogs, config, removedBlocks, removedFiles };
|
|
478
516
|
}
|
|
479
517
|
|
|
480
518
|
export function planInstallation({ target, language = 'pt', mode = 'init', force = false, clients }) {
|
|
@@ -495,11 +533,12 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
495
533
|
if (original === null ? content !== null : !content?.equals(original)) fail('E_CHANGED', `File changed while planning: ${path}`);
|
|
496
534
|
} else nativeInputs.set(path, content);
|
|
497
535
|
};
|
|
498
|
-
const { entries: derivedEntries, outputs: derivedOutputs, blocks: derivedBlocks, files: derivedFiles, removedBlocks, removedFiles } = readDerivedState(target);
|
|
536
|
+
const { entries: derivedEntries, outputs: derivedOutputs, blocks: derivedBlocks, files: derivedFiles, catalogs: derivedCatalogs, config: derivedConfig, removedBlocks, removedFiles } = readDerivedState(target);
|
|
537
|
+
const derivedConfigFiles = derivedConfig ? { '.pbai4devs/espelhos.conf': derivedConfig } : {};
|
|
499
538
|
if (previous) previous = { ...previous, files: Object.fromEntries(Object.entries(previous.files).filter(([path, digest]) => removedFiles[path] !== digest)) };
|
|
500
539
|
const previousBlocks = Object.fromEntries(Object.entries(previous?.blocks || {}).filter(([path]) => !removedBlocks.includes(path)));
|
|
501
540
|
const knownBlocks = { ...previousBlocks, ...derivedBlocks };
|
|
502
|
-
const ownedPaths = [...Object.keys(previous?.files || {}), ...Object.keys(previousBlocks), ...Object.keys(derivedEntries), ...Object.keys(derivedOutputs), ...Object.keys(derivedBlocks), ...Object.keys(derivedFiles)];
|
|
541
|
+
const ownedPaths = [...Object.keys(previous?.files || {}), ...Object.keys(previousBlocks), ...Object.keys(derivedEntries), ...Object.keys(derivedOutputs), ...Object.keys(derivedBlocks), ...Object.keys(derivedFiles), ...Object.keys(derivedCatalogs), ...Object.keys(derivedConfigFiles)];
|
|
503
542
|
const entryPaths = new Map(entryNames.map(name => [name, resolveEntrypoint(target, name)]));
|
|
504
543
|
const blockHash = path => knownBlocks[path]
|
|
505
544
|
?? (canonicalEntrypoint(path) ? Object.entries(knownBlocks).find(([candidate]) => canonicalEntrypoint(candidate) === canonicalEntrypoint(path))?.[1] : undefined);
|
|
@@ -581,7 +620,17 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
581
620
|
const ownIgnore = read(target, '.pbai4devs/.gitignore');
|
|
582
621
|
if (ownIgnore) write(stage, '.pbai4devs/.gitignore', ownIgnore);
|
|
583
622
|
writeAdapterManifest(stage, { clients: selected });
|
|
584
|
-
|
|
623
|
+
// Supply the already-validated ownership records to the private stage so
|
|
624
|
+
// the synchronizer can distinguish an owned ignore block from a user's.
|
|
625
|
+
// These temporary records never replace or bypass checks on the target.
|
|
626
|
+
const stagingState = '.pbai4devs/state/agentdevkit.json', stagingDerived = '.pbai4devs/state/derived.json';
|
|
627
|
+
if (previous) write(stage, stagingState, JSON.stringify(previous));
|
|
628
|
+
if (Object.keys(derivedEntries).length || Object.keys(derivedOutputs).length || Object.keys(derivedBlocks).length || Object.keys(derivedFiles).length || Object.keys(derivedCatalogs).length || derivedConfig || removedBlocks.length || Object.keys(removedFiles).length) {
|
|
629
|
+
write(stage, stagingDerived, JSON.stringify({ schemaVersion: 1, entries: derivedEntries, outputs: derivedOutputs, blocks: derivedBlocks, files: derivedFiles, catalogs: derivedCatalogs, ...(derivedConfig ? { config: derivedConfig } : {}), removedBlocks, removedFiles }));
|
|
630
|
+
}
|
|
631
|
+
let synchronized;
|
|
632
|
+
try { synchronized = synchronize({ root: stage, force: true, localIgnores: false, generateEntrypoints: false, recordDerivedState: false, outputBlocked, output() {} }); }
|
|
633
|
+
finally { rmSync(join(stage, stagingState), { force: true }); rmSync(join(stage, stagingDerived), { force: true }); }
|
|
585
634
|
for (const canonical of active.entrypoints) {
|
|
586
635
|
const name = entryPaths.get(canonical);
|
|
587
636
|
if (preserveNative(name)) continue;
|
|
@@ -657,14 +706,14 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
657
706
|
// A standalone sync records its proven outputs separately. During a later
|
|
658
707
|
// check/sync, compare that overlay before declaring the installation state
|
|
659
708
|
// stale; update still consolidates it into the main state transactionally.
|
|
660
|
-
let stateContent =
|
|
709
|
+
let stateContent = reconcileStateBytes(installedState ? read(target, actualStatePath(target)) : null, installedState, manifest);
|
|
661
710
|
if (mode === 'sync' && installedState && !changingClients) {
|
|
662
711
|
const files = { ...installedState.files }, blocks = { ...installedState.blocks };
|
|
663
712
|
for (const [path, digest] of Object.entries(removedFiles)) {
|
|
664
713
|
if (files[path] === digest && !stat(safePath(target, path))) delete files[path];
|
|
665
714
|
}
|
|
666
715
|
for (const path of removedBlocks) delete blocks[path];
|
|
667
|
-
for (const [path, digest] of Object.entries({ ...derivedFiles, ...derivedOutputs, ...derivedEntries })) { files[path] = digest; delete blocks[path]; }
|
|
716
|
+
for (const [path, digest] of Object.entries({ ...derivedFiles, ...derivedOutputs, ...derivedEntries, ...derivedCatalogs, ...derivedConfigFiles })) { files[path] = digest; delete blocks[path]; }
|
|
668
717
|
for (const [path, digest] of Object.entries(derivedBlocks)) { blocks[path] = digest; delete files[path]; }
|
|
669
718
|
const same = (a, b) => {
|
|
670
719
|
const keys = Object.keys(a).sort();
|
|
@@ -676,23 +725,70 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
676
725
|
desired.set(statePath, stateContent);
|
|
677
726
|
const derivedPath = actualStatePath(target).replace(/agentdevkit\.json$/, 'derived.json');
|
|
678
727
|
const derivedBytes = !migration ? read(target, derivedPath) : null;
|
|
679
|
-
|
|
728
|
+
let nextDerivedBytes = derivedBytes;
|
|
729
|
+
if (nextDerivedBytes && Object.keys(removedFiles).length) {
|
|
680
730
|
const finalState = JSON.parse(stateContent);
|
|
681
731
|
const remaining = Object.fromEntries(Object.entries(removedFiles).filter(([path, digest]) =>
|
|
682
732
|
finalState.files[path] === digest && !stat(safePath(target, path))));
|
|
683
733
|
if (Object.keys(remaining).length !== Object.keys(removedFiles).length) {
|
|
684
734
|
observeNative(derivedPath, derivedBytes);
|
|
685
|
-
|
|
735
|
+
nextDerivedBytes = replaceJsonMember(nextDerivedBytes, 'removedFiles', Object.keys(remaining).length ? remaining : undefined);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (nextDerivedBytes && Object.keys(derivedCatalogs).length) {
|
|
739
|
+
const finalState = JSON.parse(stateContent);
|
|
740
|
+
const remaining = Object.fromEntries(Object.entries(derivedCatalogs).filter(([path]) => {
|
|
741
|
+
const content = desired.get(path);
|
|
742
|
+
return !content || finalState.files[path] !== hash(content);
|
|
743
|
+
}));
|
|
744
|
+
if (Object.keys(remaining).length !== Object.keys(derivedCatalogs).length) {
|
|
745
|
+
observeNative(derivedPath, derivedBytes);
|
|
746
|
+
nextDerivedBytes = replaceJsonMember(nextDerivedBytes, 'catalogs', Object.keys(remaining).length ? remaining : undefined);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (nextDerivedBytes && Object.keys(derivedBlocks).length) {
|
|
750
|
+
const finalState = JSON.parse(stateContent);
|
|
751
|
+
const remaining = Object.fromEntries(Object.entries(derivedBlocks).filter(([path, digest]) => {
|
|
752
|
+
const content = desired.get(path);
|
|
753
|
+
// A deselected generated block is removed later in this transaction.
|
|
754
|
+
// Drop its overlay only if its current bytes are still proven; edited
|
|
755
|
+
// blocks remain conflicts and no metadata is committed on failure.
|
|
756
|
+
if (!content && !canonicalEntrypoint(path) && !finalState.blocks[path] && !finalState.files[path]) {
|
|
757
|
+
const current = read(target, path);
|
|
758
|
+
return Boolean(current && blockDigest(path, current) !== digest);
|
|
759
|
+
}
|
|
760
|
+
return !content || finalState.blocks[path] !== blockDigest(path, content);
|
|
761
|
+
}));
|
|
762
|
+
if (Object.keys(remaining).length !== Object.keys(derivedBlocks).length) {
|
|
763
|
+
observeNative(derivedPath, derivedBytes);
|
|
764
|
+
nextDerivedBytes = replaceJsonMember(nextDerivedBytes, 'blocks', Object.keys(remaining).length ? remaining : undefined);
|
|
686
765
|
}
|
|
687
766
|
}
|
|
767
|
+
if (nextDerivedBytes && derivedConfig) {
|
|
768
|
+
const finalState = JSON.parse(stateContent), path = '.pbai4devs/espelhos.conf', content = desired.get(path);
|
|
769
|
+
if (content && finalState.files[path] === hash(content)) {
|
|
770
|
+
observeNative(derivedPath, derivedBytes);
|
|
771
|
+
nextDerivedBytes = replaceJsonMember(nextDerivedBytes, 'config', undefined);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (nextDerivedBytes && !nextDerivedBytes.equals(derivedBytes)) desired.set(derivedPath, nextDerivedBytes);
|
|
688
775
|
const changes = [], conflicts = [];
|
|
689
776
|
const aliasedEntrypoint = path => canonicalEntrypoint(path) && entryPaths.get(canonicalEntrypoint(path)) !== path;
|
|
777
|
+
const generatedInternal = new Set(['.pbai4devs/catalog.json', '.pbai4devs/skills.json', '.pbai4devs/agents.json', '.pbai4devs/rules.json', '.pbai4devs/espelhos.conf']);
|
|
690
778
|
for (const [path, content] of desired) {
|
|
691
779
|
const current = read(target, path);
|
|
692
780
|
if (nativeInputs.has(path) && (nativeInputs.get(path) === null ? current !== null : !current?.equals(nativeInputs.get(path)))) fail('E_CHANGED', `File changed while planning: ${path}`);
|
|
693
|
-
|
|
781
|
+
const wholeFileOwned = current && [previous?.files[path], derivedOutputs[path], derivedEntries[path], derivedFiles[path], derivedCatalogs[path], derivedConfigFiles[path]].some(digest => digest && digest === hash(current));
|
|
782
|
+
const existingBlock = current && (nativeManagedText(path) || canonicalEntrypoint(path) || path.endsWith('/.gitignore')) ? blockDigest(path, current) : null;
|
|
783
|
+
if (existingBlock && !wholeFileOwned && knownBlocks[path] !== existingBlock) conflicts.push(path);
|
|
784
|
+
if (current?.equals(content)) {
|
|
785
|
+
// Identical bytes do not prove ownership of a pre-existing catalogue.
|
|
786
|
+
// Refuse adoption: a later sync could otherwise overwrite user data.
|
|
787
|
+
if (!wholeFileOwned && (generatedInternal.has(path) || native(path) && !existingBlock && !mixedPaths.has(path) && !preserved.has(path))) conflicts.push(path);
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
694
790
|
const managed = path === derivedPath && derivedBytes && current?.equals(derivedBytes)
|
|
695
|
-
|| [previous?.files[path], derivedOutputs[path], derivedEntries[path], derivedFiles[path]].some(digest => digest && hash(current || '') === digest)
|
|
791
|
+
|| [previous?.files[path], derivedOutputs[path], derivedEntries[path], derivedFiles[path], derivedCatalogs[path], derivedConfigFiles[path]].some(digest => digest && hash(current || '') === digest)
|
|
696
792
|
|| knownBlocks[path] && blockDigest(path, current || Buffer.alloc(0)) === knownBlocks[path];
|
|
697
793
|
const appendBlock = mixedPaths.has(path) || canonicalEntrypoint(path) || path === '.gitignore' || path.endsWith('/.gitignore');
|
|
698
794
|
const conflict = current && path !== statePath && !managed && !appendBlock;
|
|
@@ -812,7 +908,9 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
812
908
|
const localIgnore = planLocalIgnore(target, { outputs: ownedOutputs, templateRoot: stage });
|
|
813
909
|
const localIgnoreCheck = checkLocalIgnore(localIgnore, { replacedBlocks: replacedIgnoreBlocks(target, changes, previous) });
|
|
814
910
|
conflicts.push(...(untrack?.conflicts || []), ...localIgnoreCheck.missing);
|
|
815
|
-
|
|
911
|
+
// A conflict means ownership or the managed bytes could not be proven.
|
|
912
|
+
// Backups do not authorize replacing user content, even with --force.
|
|
913
|
+
return { target, language, mode, changes, conflicts: [...new Set(conflicts)].sort(), canApply: !conflicts.length && !untrack?.conflicts?.length && !localIgnoreCheck.missing.length, manifest, retiredRoots, ownedOutputs, untrack, localIgnore, localIgnoreCheck, migration, fallbackChecks: [...fallbackChecks], fallbackOwnedPaths: ownedPaths, preserved: [...preserved.values()].sort((a, b) => a.path.localeCompare(b.path)) };
|
|
816
914
|
} finally { rmSync(stage, { recursive: true, force: true }); }
|
|
817
915
|
}
|
|
818
916
|
|
|
@@ -1003,7 +1101,7 @@ export function inspect(target) {
|
|
|
1003
1101
|
if (content) issues.push({ code: 'modified', path });
|
|
1004
1102
|
continue;
|
|
1005
1103
|
}
|
|
1006
|
-
if (!content || hash(content) !== digest && hash(content) !== derived.outputs[path] && hash(content) !== derived.files[path] && hash(content) !== derived.entries[path]) issues.push({ code: content ? 'modified' : 'missing', path });
|
|
1104
|
+
if (!content || hash(content) !== digest && hash(content) !== derived.outputs[path] && hash(content) !== derived.files[path] && hash(content) !== derived.entries[path] && hash(content) !== derived.catalogs[path] && !(path === '.pbai4devs/espelhos.conf' && hash(content) === derived.config)) issues.push({ code: content ? 'modified' : 'missing', path });
|
|
1007
1105
|
}
|
|
1008
1106
|
for (const [path, digest] of Object.entries({ ...state.blocks, ...derived.blocks })) {
|
|
1009
1107
|
if (derived.removedBlocks.includes(path) && !Object.hasOwn(derived.blocks, path)) continue;
|
package/src/next.mjs
CHANGED
|
@@ -8,9 +8,11 @@ const messages = {
|
|
|
8
8
|
install: 'Instale o AgentDevKit neste projeto.', verify: 'Confira a instalação.',
|
|
9
9
|
configure: 'Conclua a preparação inicial.', prepare: 'Prepare o ambiente existente.',
|
|
10
10
|
prd: 'Defina ou conclua o PRD.', ux: 'Defina UX/UI ou justifique sua não aplicação.',
|
|
11
|
-
design: 'Conclua o design técnico.',
|
|
12
|
-
spec: 'Defina a próxima spec.', roadmap: 'Revise o roadmap e escolha o próximo incremento.',
|
|
11
|
+
design: 'Conclua o design técnico.',
|
|
12
|
+
spec: 'Defina a próxima spec.', implement: 'Planeje a implementação da spec.', roadmap: 'Revise o roadmap e escolha o próximo incremento.',
|
|
13
13
|
inspect: 'Confira o estado do projeto antes de avançar.',
|
|
14
|
+
incomplete: 'O inventário do projeto está incompleto.',
|
|
15
|
+
incompleteInstruction: 'Inspecione manualmente a estrutura e os documentos antes de escolher uma etapa; não presuma que o projeto está vazio.',
|
|
14
16
|
ask: 'Use a skill indicada no agente; confirme as decisões ainda pendentes.',
|
|
15
17
|
existing: 'Preserve código e decisões existentes; não reinicie a definição do produto.',
|
|
16
18
|
},
|
|
@@ -18,9 +20,11 @@ const messages = {
|
|
|
18
20
|
install: 'Install AgentDevKit in this project.', verify: 'Check the installation.',
|
|
19
21
|
configure: 'Complete the initial preparation.', prepare: 'Prepare the existing environment.',
|
|
20
22
|
prd: 'Define or complete the PRD.', ux: 'Define UX/UI or document why it does not apply.',
|
|
21
|
-
design: 'Complete the technical design.',
|
|
22
|
-
spec: 'Define the next spec.', roadmap: 'Review the roadmap and select the next increment.',
|
|
23
|
+
design: 'Complete the technical design.',
|
|
24
|
+
spec: 'Define the next spec.', implement: 'Plan the spec implementation.', roadmap: 'Review the roadmap and select the next increment.',
|
|
23
25
|
inspect: 'Inspect the project before proceeding.',
|
|
26
|
+
incomplete: 'The project inventory is incomplete.',
|
|
27
|
+
incompleteInstruction: 'Inspect the structure and documents manually before choosing a step; do not assume the project is empty.',
|
|
24
28
|
ask: 'Use the indicated skill in your agent; confirm outstanding decisions.',
|
|
25
29
|
existing: 'Preserve existing code and decisions; do not restart product discovery.',
|
|
26
30
|
},
|
|
@@ -28,9 +32,11 @@ const messages = {
|
|
|
28
32
|
install: 'Instale AgentDevKit en este proyecto.', verify: 'Compruebe la instalación.',
|
|
29
33
|
configure: 'Complete la preparación inicial.', prepare: 'Prepare el entorno existente.',
|
|
30
34
|
prd: 'Defina o complete el PRD.', ux: 'Defina UX/UI o justifique que no se aplica.',
|
|
31
|
-
design: 'Complete el diseño técnico.',
|
|
32
|
-
spec: 'Defina la próxima spec.', roadmap: 'Revise el roadmap y elija el próximo incremento.',
|
|
35
|
+
design: 'Complete el diseño técnico.',
|
|
36
|
+
spec: 'Defina la próxima spec.', implement: 'Planifique la implementación de la spec.', roadmap: 'Revise el roadmap y elija el próximo incremento.',
|
|
33
37
|
inspect: 'Inspeccione el proyecto antes de continuar.',
|
|
38
|
+
incomplete: 'El inventario del proyecto está incompleto.',
|
|
39
|
+
incompleteInstruction: 'Inspeccione manualmente la estructura y los documentos antes de elegir una etapa; no suponga que el proyecto está vacío.',
|
|
34
40
|
ask: 'Use la skill indicada en su agente y confirme las decisiones pendientes.',
|
|
35
41
|
existing: 'Preserve el código y las decisiones existentes; no reinicie el producto.',
|
|
36
42
|
},
|
|
@@ -48,14 +54,21 @@ function commandLine(args, platform) {
|
|
|
48
54
|
function workflow(report) {
|
|
49
55
|
const state = report.maturity;
|
|
50
56
|
const app = state.signals.application;
|
|
57
|
+
if (report.scan.truncated) return ['incomplete', null];
|
|
51
58
|
if (app && (!state.configuration.present || !state.planning.ready)) return ['prepare', 'preparar-dev'];
|
|
52
59
|
if (state.configuration.preparacao !== 'concluido') return app ? ['prepare', 'preparar-dev'] : ['configure', 'configurar-projeto'];
|
|
53
60
|
if (state.planning.prd.status !== 'aprovado') return ['prd', 'criar-prd'];
|
|
54
61
|
if (!['aprovado', 'nao-aplicavel'].includes(state.planning.ux_ui.status)) return ['ux', 'criar-ux-ui'];
|
|
55
62
|
if (state.planning.design.status !== 'aprovado') return ['design', 'criar-design-doc'];
|
|
56
|
-
if (state.
|
|
57
|
-
if (app && !report.contract.complete) return ['prepare', 'preparar-dev'];
|
|
58
|
-
|
|
63
|
+
if (state.level === 'manutencao' || !state.releasesComplete) return ['roadmap', 'gerenciar-roadmap'];
|
|
64
|
+
if (app && state.configuration.configuracao_tecnica === 'concluido' && !report.contract.complete) return ['prepare', 'preparar-dev'];
|
|
65
|
+
const { roadmap, specs } = state.planning;
|
|
66
|
+
if (!roadmap?.present || !['planejado', 'aprovado'].includes(roadmap.status) || !specs?.path || !specs.complete || specs.items.length > 1) return ['roadmap', 'gerenciar-roadmap'];
|
|
67
|
+
if (!specs.items.length) return ['spec', 'criar-spec'];
|
|
68
|
+
const [spec] = specs.items;
|
|
69
|
+
if (!spec.linked || !['open', 'in-progress'].includes(spec.roadmapState) || !spec.status) return ['roadmap', 'gerenciar-roadmap'];
|
|
70
|
+
if (spec.status !== 'aprovado') return ['spec', 'criar-spec'];
|
|
71
|
+
return ['implement', 'implementar-spec'];
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
export function getNextSteps({ command, target, language = 'pt', installed, platform = process.platform }) {
|
|
@@ -68,6 +81,10 @@ export function getNextSteps({ command, target, language = 'pt', installed, plat
|
|
|
68
81
|
try { report = inspectProject(target); }
|
|
69
82
|
catch { return [verification]; }
|
|
70
83
|
const [key, skill] = workflow(report);
|
|
84
|
+
if (key === 'incomplete') {
|
|
85
|
+
const step = { label: text.incomplete, instruction: text.incompleteInstruction };
|
|
86
|
+
return ['init', 'update', 'sync', 'lang', 'agents', 'tools'].includes(command) ? [verification, step] : [step];
|
|
87
|
+
}
|
|
71
88
|
const inspector = join(target, '.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs');
|
|
72
89
|
const step = {
|
|
73
90
|
label: text[key],
|