@pathbit/agentdevkit 0.0.21 → 0.0.23
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/COMPATIBILIDADE.md +11 -1
- 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/diagnosticar-ambiente/tests/TEST.mjs +60 -1
- package/.pbai4devs/scripts/inspecionar-projeto/README.md +6 -0
- package/.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs +218 -18
- package/.pbai4devs/scripts/lib/adapters.mjs +5 -2
- package/.pbai4devs/scripts/lib/runtime.mjs +38 -2
- package/.pbai4devs/scripts/sincronizar-agentes/README.md +5 -1
- package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +136 -9
- package/.pbai4devs/scripts/sincronizar-agentes/tests/HUMAN_OWNERSHIP.mjs +30 -0
- package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +22 -0
- package/.pbai4devs/scripts/testar-maturidade/README.md +12 -10
- package/.pbai4devs/scripts/testar-maturidade/SCRIPT.mjs +86 -44
- package/.pbai4devs/scripts/testar-maturidade/tests/TEST.mjs +157 -33
- 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/scripts/verificar-recursos/SCRIPT.mjs +12 -1
- package/.pbai4devs/scripts/verificar-recursos/tests/TEST.mjs +22 -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/.pbai4devs/templates/clients/ADAPTERS.json +3 -2
- package/CHANGELOG.md +20 -0
- package/docs/guides/cli.md +9 -0
- package/docs/guides/troubleshooting.md +4 -0
- package/docs/specs/README.md +5 -0
- package/docs/specs/discovery-preservation/SPEC.md +26 -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/install.mjs +155 -17
- package/src/next.mjs +26 -9
|
@@ -42,20 +42,25 @@ aprovações já dadas.
|
|
|
42
42
|
2. Prepare as ferramentas definidas em `.pbai4devs/tools/<nome>/TOOL.json` com
|
|
43
43
|
`preparar-ferramentas`; confira versões e compatibilidade com SO/arquitetura.
|
|
44
44
|
Reutilize instalações e lockfiles; não repita init nem troque a stack.
|
|
45
|
-
3. Aplique a [estrutura e o ambiente obrigatórios][estrutura],
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
3. Aplique a [estrutura e o ambiente obrigatórios][estrutura], preenchendo os
|
|
46
|
+
modelos de Makefile, Compose e dockerignore. Configure os serviços de
|
|
47
|
+
infraestrutura necessários, healthchecks, portas, volumes e comandos.
|
|
48
|
+
Antes de concluir a configuração, não implemente código da aplicação;
|
|
49
|
+
`src/` e `tests/` podem existir vazios ou conter apenas configuração.
|
|
48
50
|
4. Configure VS Code e recomende EditorConfig, sem regras conflitantes.
|
|
49
51
|
Termine `.gitignore` com o modelo CUSTOM IGNORE e os padrões da tecnologia;
|
|
50
52
|
confirme que templates, scripts, testes e fixtures continuam versionáveis.
|
|
51
53
|
5. Documente versões, estrutura, setup, comandos, origem do template/init e
|
|
52
54
|
decisões no README/estado. Use `atualizar-documentacao` para contribuição,
|
|
53
55
|
configuração e ADRs aplicáveis. Preserve arquivos e convenções existentes.
|
|
54
|
-
6. Use `verificar-trabalho`: valide
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
6. Use `verificar-trabalho`: valide runtime, configuração Compose e saúde dos
|
|
57
|
+
serviços de infraestrutura necessários; confira a estrutura com
|
|
58
|
+
`inspecionar-projeto --check`. Se Docker ou outra dependência obrigatória
|
|
59
|
+
estiver inacessível, mantenha a configuração pendente e informe a prova
|
|
60
|
+
faltante. Sem aplicação, registre build, saúde e fluxo dela como pendentes;
|
|
61
|
+
não os reporte como aprovados. Com aplicação existente, prove o fluxo real
|
|
62
|
+
conforme seu tipo. Marque `configuracao_tecnica: concluido` somente com
|
|
63
|
+
evidência da configuração prévia e dos serviços aplicáveis.
|
|
59
64
|
|
|
60
65
|
Após alterações técnicas, conclua `revisar-entrega`. Sincronize apontamentos
|
|
61
66
|
somente quando recursos dos agentes forem alterados.
|
|
@@ -17,7 +17,9 @@ Faça apenas as perguntas que impedem avançar.
|
|
|
17
17
|
Continue o trabalho já autorizado; não reinicie fases concluídas nem apresente
|
|
18
18
|
suposições como aprovações.
|
|
19
19
|
|
|
20
|
-
Projeto novo segue preparação → PRD → UX
|
|
21
|
-
|
|
20
|
+
Projeto novo segue preparação → PRD → UX → design (arquitetura e tecnologia) →
|
|
21
|
+
roadmap → spec aprovada → plano aprovado → configuração técnica → implementação
|
|
22
|
+
→ revisão. UX pode ser não aplicável com justificativa aprovada.
|
|
23
|
+
Projeto existente parte de suas evidências atuais;
|
|
22
24
|
instalar o kit não autoriza recriar documentos ou substituir decisões.
|
|
23
25
|
Se não houver pendências no escopo, diga isso claramente.
|
|
@@ -18,8 +18,20 @@ antes de implementar.
|
|
|
18
18
|
seus IDs/revisões. Não invente uma solução já atendida por elas.
|
|
19
19
|
3. Depois da aprovação, crie ou retome `feature/<slug-da-spec>` de `develop`.
|
|
20
20
|
Se faltar `develop`, use `main` quando seguro; sem base/remoto, combine como
|
|
21
|
-
proceder. Preserve alterações. Registre a aprovação no roadmap e
|
|
22
|
-
configuração real
|
|
21
|
+
proceder. Preserve alterações. Registre a aprovação no roadmap e complete a
|
|
22
|
+
configuração real com `configurar-projeto` antes de codificar.
|
|
23
|
+
Antes de qualquer patch de código da spec, execute:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
node .pbai4devs/scripts/executar-script/SCRIPT.mjs verificar-configuracao
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Releia o estado e confira as evidências da infraestrutura obrigatória. Se o
|
|
30
|
+
estado estiver ausente, inválido ou pendente, ou uma prova falhar, pare sem
|
|
31
|
+
escrever código; retome quando o impedimento for resolvido. Código zero do
|
|
32
|
+
script só confirma o estado registrado, não comprova as evidências nem impede
|
|
33
|
+
escrita por um agente que ignore o resultado. Não peça novamente aprovações
|
|
34
|
+
já concedidas.
|
|
23
35
|
4. Use o modelo de tarefas em `<documentos.tarefas>/<slug-da-spec>/TASKS.md`.
|
|
24
36
|
Vincule spec/release e registre tarefas, dependências, critérios e evidências.
|
|
25
37
|
Atualize `[ ]`, `[-]` e `[x]` ao avançar; conclusão exige prova. Ao retomar,
|
|
@@ -79,9 +79,10 @@
|
|
|
79
79
|
"name": "Codex",
|
|
80
80
|
"root": ".codex",
|
|
81
81
|
"formats": {
|
|
82
|
-
"skills": "
|
|
82
|
+
"skills": "nenhum"
|
|
83
83
|
},
|
|
84
|
-
"
|
|
84
|
+
"skillFallback": ".agents",
|
|
85
|
+
"reason": "Skills usam apenas .agents/skills para evitar descoberta duplicada; os demais recursos em .codex são referências.",
|
|
85
86
|
"sources": [
|
|
86
87
|
"https://learn.chatgpt.com/docs/build-skills",
|
|
87
88
|
"https://github.com/openai/codex/blob/392ced7d3a9160fd35d40c606359869215f50522/codex-rs/ext/skills/src/loader/host.rs"
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ description: Alterações verificáveis por versão do AgentDevKit.
|
|
|
4
4
|
|
|
5
5
|
# Changelog
|
|
6
6
|
|
|
7
|
+
## 0.0.23 — 2026-11-13
|
|
8
|
+
|
|
9
|
+
- Corrige a resolução de destinos inexistentes diretamente sob a raiz.
|
|
10
|
+
- Aceita aliases temporários confiáveis do macOS e recusa destinos que
|
|
11
|
+
atravessam links humanos, inclusive os indicados por variáveis de ambiente.
|
|
12
|
+
- Orienta roadmap e spec antes da configuração técnica e acrescenta uma
|
|
13
|
+
verificação do estado antes da implementação.
|
|
14
|
+
- Aplica a verificação também aos especialistas e orienta consultas Git que
|
|
15
|
+
preservam os bytes do índice.
|
|
16
|
+
|
|
17
|
+
## 0.0.22 — 2026-11-13
|
|
18
|
+
|
|
19
|
+
- Descoberta de skills do Codex sem duplicação, com alternativa segura quando a
|
|
20
|
+
rota principal está obstruída.
|
|
21
|
+
- Sincronização e atualização reconciliam o estado sem substituir recursos ou
|
|
22
|
+
metadados humanos.
|
|
23
|
+
- Scripts reconhecem caminhos por aliases e exemplos de maturidade usam saídas isoladas.
|
|
24
|
+
- Testes ampliados para combinações de clientes, projetos existentes e
|
|
25
|
+
preservação do Git.
|
|
26
|
+
|
|
7
27
|
## 0.0.21 — 2026-11-13
|
|
8
28
|
|
|
9
29
|
- Criação automática da tag da versão após aprovação da matriz de CI.
|
package/docs/guides/cli.md
CHANGED
|
@@ -125,5 +125,14 @@ do kit. Conteúdo humano não é substituído por `--force`.
|
|
|
125
125
|
|
|
126
126
|
`doctor` verifica o kit, não o produto inteiro. `next` pode terminar com
|
|
127
127
|
sucesso enquanto orienta uma etapa ainda pendente.
|
|
128
|
+
Após produto e design, ele usa os caminhos de roadmap e specs registrados no
|
|
129
|
+
estado. Para orientar uma spec existente, exige uma única referência aberta
|
|
130
|
+
`[ ]` ou em andamento `[-]` no roadmap. Com mais de uma spec, entrega encerrada,
|
|
131
|
+
vínculo ausente ou leitura ambígua, orienta a revisão do roadmap. Um status
|
|
132
|
+
declarado não comprova aprovação: a skill confere decisões e evidências. Com uma
|
|
133
|
+
spec aberta e declarada aprovada, `implementar-spec` verifica o plano antes de
|
|
134
|
+
encaminhar a configuração técnica; `next` não presume aprovação do plano.
|
|
135
|
+
Se o inventário for truncado, `next` orienta inspeção manual, sem escolher uma
|
|
136
|
+
fase ou presumir que um projeto existente está vazio.
|
|
128
137
|
|
|
129
138
|
[Wiki](../README.md) · [Solução de problemas](troubleshooting.md)
|
|
@@ -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:
|
package/docs/specs/README.md
CHANGED
|
@@ -28,6 +28,11 @@ 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.
|
|
34
|
+
- [Descoberta e preservação](discovery-preservation/SPEC.md): rotas de skills,
|
|
35
|
+
sincronização segura e validação em projetos reais.
|
|
31
36
|
|
|
32
37
|
Cada incremento vincula requisitos, critérios, testes e evidências locais.
|
|
33
38
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Descoberta de skills e preservação em projetos existentes.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Descoberta e preservação
|
|
6
|
+
|
|
7
|
+
As integrações devem disponibilizar os recursos sem duplicar skills geradas
|
|
8
|
+
para o Codex e sem substituir configurações ou alterações próprias do projeto.
|
|
9
|
+
|
|
10
|
+
## Critérios de aceite
|
|
11
|
+
|
|
12
|
+
- AC-001: O Codex descobre cada skill na rota principal; rotas alternativas
|
|
13
|
+
são usadas por recurso quando necessário e conflitos são informados.
|
|
14
|
+
- AC-002: `init`, `update` e `sync` preservam conteúdo humano, índice Git e
|
|
15
|
+
`.gitignore` da raiz; limpeza exige propriedade comprovada.
|
|
16
|
+
- AC-003: A sincronização independente e a atualização reconciliam o estado,
|
|
17
|
+
preservando campos adicionais e sinalizando inconsistências.
|
|
18
|
+
- AC-004: Scripts identificam sua execução principal por caminhos reais ou
|
|
19
|
+
aliases, sem executar novamente quando importados.
|
|
20
|
+
- AC-005: Os exemplos de maturidade funcionam também a partir do pacote
|
|
21
|
+
distribuído, preservando arquivos da origem e exemplos anteriores.
|
|
22
|
+
- AC-006: Testes cobrem combinações de clientes e projetos existentes; resultados,
|
|
23
|
+
clones e projetos gerados ficam ignorados ou fora do repositório.
|
|
24
|
+
|
|
25
|
+
Descoberta e validação estrutural não demonstram execução completa de todas as
|
|
26
|
+
skills e perfis em cada cliente. Registre os limites das provas realizadas.
|
|
@@ -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/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);
|
|
@@ -169,6 +185,19 @@ export function safePath(root, path) {
|
|
|
169
185
|
return current;
|
|
170
186
|
}
|
|
171
187
|
|
|
188
|
+
// Inspect the route without following user-owned links. A final directory is
|
|
189
|
+
// also unavailable where the generated skill requires a regular file.
|
|
190
|
+
function nativeOutputBoundary(root, path, ownedPaths) {
|
|
191
|
+
let boundary = nativePathBoundary(root, path, ownedPaths, { preserveRootAliases: true });
|
|
192
|
+
if (!boundary) {
|
|
193
|
+
const info = stat(safePath(root, path));
|
|
194
|
+
if (info && !info.isFile()) boundary = { path, reason: 'non-file' };
|
|
195
|
+
}
|
|
196
|
+
if (!boundary) return { blocked: false };
|
|
197
|
+
const info = lstatSync(join(root, ...boundary.path.split('/')));
|
|
198
|
+
return { blocked: true, ...boundary, identity: [info.dev, info.ino, info.mode, info.size, info.mtimeMs, info.ctimeMs] };
|
|
199
|
+
}
|
|
200
|
+
|
|
172
201
|
function resourceAllowed(path, directory = false) {
|
|
173
202
|
const parts = path.split('/');
|
|
174
203
|
const folders = new Set(['agents', 'commands', 'external-skills', 'rules', 'scripts', 'skills', 'templates', 'tools', 'state']);
|
|
@@ -388,30 +417,85 @@ function copyResources(source, destination) {
|
|
|
388
417
|
return files;
|
|
389
418
|
}
|
|
390
419
|
|
|
420
|
+
// Change only one top-level JSON member. Other fields, escaping and whitespace
|
|
421
|
+
// remain byte-for-byte intact, including user-provided fields in local state.
|
|
422
|
+
function replaceJsonMember(bytes, member, value) {
|
|
423
|
+
const text = bytes.toString('utf8');
|
|
424
|
+
if (!Buffer.from(text, 'utf8').equals(bytes)) fail('E_STATE', 'Invalid UTF-8 in derived state.');
|
|
425
|
+
let i = 0;
|
|
426
|
+
const space = () => { while (/\s/.test(text[i] || '') && i < text.length) i++; };
|
|
427
|
+
const stringEnd = () => {
|
|
428
|
+
if (text[i++] !== '"') fail('E_STATE', 'Invalid JSON member.');
|
|
429
|
+
while (i < text.length) {
|
|
430
|
+
const char = text[i++];
|
|
431
|
+
if (char === '\\') { i++; continue; }
|
|
432
|
+
if (char === '"') return;
|
|
433
|
+
}
|
|
434
|
+
fail('E_STATE', 'Unterminated JSON string.');
|
|
435
|
+
};
|
|
436
|
+
space(); if (text[i++] !== '{') fail('E_STATE', 'Invalid derived state.');
|
|
437
|
+
const properties = [];
|
|
438
|
+
while (true) {
|
|
439
|
+
space(); if (text[i] === '}') break;
|
|
440
|
+
const start = i; stringEnd(); const key = JSON.parse(text.slice(start, i));
|
|
441
|
+
space(); if (text[i++] !== ':') fail('E_STATE', 'Invalid JSON member.');
|
|
442
|
+
space(); const valueStart = i;
|
|
443
|
+
if (text[i] === '"') stringEnd();
|
|
444
|
+
else if (text[i] === '{' || text[i] === '[') {
|
|
445
|
+
const stack = [];
|
|
446
|
+
do {
|
|
447
|
+
const char = text[i];
|
|
448
|
+
if (char === '"') { stringEnd(); continue; }
|
|
449
|
+
i++;
|
|
450
|
+
if (char === '{' || char === '[') stack.push(char);
|
|
451
|
+
else if (char === '}' || char === ']') stack.pop();
|
|
452
|
+
} while (stack.length && i < text.length);
|
|
453
|
+
if (stack.length) fail('E_STATE', 'Invalid derived state.');
|
|
454
|
+
} else { while (i < text.length && text[i] !== ',' && text[i] !== '}') i++; while (i > valueStart && /\s/.test(text[i - 1])) i--; }
|
|
455
|
+
const end = i; properties.push({ key, start, valueStart, end });
|
|
456
|
+
space(); if (text[i] === ',') { i++; continue; } if (text[i] === '}') break;
|
|
457
|
+
fail('E_STATE', 'Invalid JSON member separator.');
|
|
458
|
+
}
|
|
459
|
+
const matches = properties.filter(item => item.key === member);
|
|
460
|
+
if (matches.length !== 1) fail('E_STATE', 'Ambiguous derived state member.');
|
|
461
|
+
const current = matches[0], index = properties.indexOf(current);
|
|
462
|
+
let next;
|
|
463
|
+
if (value !== undefined) next = text.slice(0, current.valueStart) + JSON.stringify(value, null, 2) + text.slice(current.end);
|
|
464
|
+
else if (index > 0) next = text.slice(0, properties[index - 1].end) + text.slice(current.end);
|
|
465
|
+
else if (properties.length > 1) next = text.slice(0, current.start) + text.slice(properties[1].start);
|
|
466
|
+
else next = text.slice(0, current.start) + text.slice(current.end);
|
|
467
|
+
try { JSON.parse(next); } catch { fail('E_STATE', 'Unable to preserve derived state safely.'); }
|
|
468
|
+
return Buffer.from(next);
|
|
469
|
+
}
|
|
470
|
+
|
|
391
471
|
function readDerivedState(target) {
|
|
392
|
-
let entries = {}, outputs = {}, blocks = {}, files = {}, removedBlocks = [];
|
|
472
|
+
let entries = {}, outputs = {}, blocks = {}, files = {}, removedBlocks = [], removedFiles = {};
|
|
393
473
|
const derived = read(target, actualStatePath(target).replace(/agentdevkit\.json$/, 'derived.json'));
|
|
394
474
|
if (derived) {
|
|
395
475
|
try {
|
|
396
476
|
const value = JSON.parse(derived);
|
|
397
477
|
if (value.schemaVersion !== 1 || !value.entries || typeof value.entries !== 'object' || Array.isArray(value.entries)
|
|
398
|
-
|| ['outputs', 'blocks', 'files'].some(key => value[key] !== undefined && (!value[key] || typeof value[key] !== 'object' || Array.isArray(value[key])))) fail('E_STATE', 'Invalid derived entrypoint state.');
|
|
399
|
-
entries = value.entries; outputs = value.outputs ?? {}; blocks = value.blocks ?? {}; files = value.files ?? {};
|
|
478
|
+
|| ['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.');
|
|
479
|
+
entries = value.entries; outputs = value.outputs ?? {}; blocks = value.blocks ?? {}; files = value.files ?? {}; removedFiles = value.removedFiles ?? {};
|
|
400
480
|
removedBlocks = value.removedBlocks ?? [];
|
|
401
481
|
if (!Array.isArray(removedBlocks) || removedBlocks.some(path => typeof path !== 'string' || !nativeManagedText(path))) fail('E_STATE', 'Invalid removed block state.');
|
|
402
482
|
for (const [path, digest] of Object.entries(entries)) if (!canonicalEntrypoint(path) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived entrypoint state.');
|
|
403
483
|
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.');
|
|
404
|
-
for (const [path, digest] of Object.entries(blocks)) if (!nativeManagedText(path) || !/^[a-f0-9]{64}$/.test(digest)) fail('E_STATE', 'Invalid derived block state.');
|
|
484
|
+
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.');
|
|
405
485
|
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.');
|
|
486
|
+
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.');
|
|
487
|
+
const installed = loadState(target);
|
|
488
|
+
if (Object.entries(removedFiles).some(([path, digest]) => installed?.files?.[path] && installed.files[path] !== digest)) fail('E_STATE', 'Removed file state differs from installation.');
|
|
406
489
|
}
|
|
407
490
|
catch { fail('E_STATE', 'Invalid derived entrypoint state.'); }
|
|
408
|
-
for (const path of [...Object.keys(entries), ...Object.keys(outputs), ...Object.keys(blocks), ...Object.keys(files), ...removedBlocks]) safePath(target, path);
|
|
491
|
+
for (const path of [...Object.keys(entries), ...Object.keys(outputs), ...Object.keys(blocks), ...Object.keys(files), ...Object.keys(removedFiles), ...removedBlocks]) safePath(target, path);
|
|
409
492
|
}
|
|
410
|
-
return { entries, outputs, blocks, files, removedBlocks };
|
|
493
|
+
return { entries, outputs, blocks, files, removedBlocks, removedFiles };
|
|
411
494
|
}
|
|
412
495
|
|
|
413
496
|
export function planInstallation({ target, language = 'pt', mode = 'init', force = false, clients }) {
|
|
414
|
-
|
|
497
|
+
let previous = loadState(target);
|
|
498
|
+
const installedState = previous;
|
|
415
499
|
const migration = legacyStateMigration(target);
|
|
416
500
|
// A migration must update the installed scripts and pointers in the same
|
|
417
501
|
// transaction; a plain sync/agents/lang would leave old hard-coded paths.
|
|
@@ -427,7 +511,8 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
427
511
|
if (original === null ? content !== null : !content?.equals(original)) fail('E_CHANGED', `File changed while planning: ${path}`);
|
|
428
512
|
} else nativeInputs.set(path, content);
|
|
429
513
|
};
|
|
430
|
-
const { entries: derivedEntries, outputs: derivedOutputs, blocks: derivedBlocks, files: derivedFiles, removedBlocks } = readDerivedState(target);
|
|
514
|
+
const { entries: derivedEntries, outputs: derivedOutputs, blocks: derivedBlocks, files: derivedFiles, removedBlocks, removedFiles } = readDerivedState(target);
|
|
515
|
+
if (previous) previous = { ...previous, files: Object.fromEntries(Object.entries(previous.files).filter(([path, digest]) => removedFiles[path] !== digest)) };
|
|
431
516
|
const previousBlocks = Object.fromEntries(Object.entries(previous?.blocks || {}).filter(([path]) => !removedBlocks.includes(path)));
|
|
432
517
|
const knownBlocks = { ...previousBlocks, ...derivedBlocks };
|
|
433
518
|
const ownedPaths = [...Object.keys(previous?.files || {}), ...Object.keys(previousBlocks), ...Object.keys(derivedEntries), ...Object.keys(derivedOutputs), ...Object.keys(derivedBlocks), ...Object.keys(derivedFiles)];
|
|
@@ -436,6 +521,14 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
436
521
|
?? (canonicalEntrypoint(path) ? Object.entries(knownBlocks).find(([candidate]) => canonicalEntrypoint(candidate) === canonicalEntrypoint(path))?.[1] : undefined);
|
|
437
522
|
const preserved = new Map();
|
|
438
523
|
const recordPreserved = (path, reason) => { preserved.set(path, { path, reason }); return true; };
|
|
524
|
+
const fallbackChecks = new Map();
|
|
525
|
+
const outputBlocked = path => {
|
|
526
|
+
const result = nativeOutputBoundary(target, path, ownedPaths);
|
|
527
|
+
if (fallbackChecks.has(path) && JSON.stringify(fallbackChecks.get(path)) !== JSON.stringify(result)) fail('E_CHANGED', `Skill route changed while planning: ${path}`);
|
|
528
|
+
fallbackChecks.set(path, result);
|
|
529
|
+
if (result.blocked) recordPreserved(result.path, result.reason);
|
|
530
|
+
return result;
|
|
531
|
+
};
|
|
439
532
|
const stagedHuman = stagedDivergences(target);
|
|
440
533
|
const native = path => canonicalEntrypoint(path) || path === 'README.md' || adapterRoots.some(root => path === root || path.startsWith(`${root}/`));
|
|
441
534
|
const boundary = path => {
|
|
@@ -504,7 +597,7 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
504
597
|
const ownIgnore = read(target, '.pbai4devs/.gitignore');
|
|
505
598
|
if (ownIgnore) write(stage, '.pbai4devs/.gitignore', ownIgnore);
|
|
506
599
|
writeAdapterManifest(stage, { clients: selected });
|
|
507
|
-
const synchronized = synchronize({ root: stage, force: true, localIgnores: false, generateEntrypoints: false, recordDerivedState: false, output() {} });
|
|
600
|
+
const synchronized = synchronize({ root: stage, force: true, localIgnores: false, generateEntrypoints: false, recordDerivedState: false, outputBlocked, output() {} });
|
|
508
601
|
for (const canonical of active.entrypoints) {
|
|
509
602
|
const name = entryPaths.get(canonical);
|
|
510
603
|
if (preserveNative(name)) continue;
|
|
@@ -577,14 +670,45 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
577
670
|
if (block) manifest.blocks[path] = block;
|
|
578
671
|
else manifest.files[path] = hash(content);
|
|
579
672
|
}
|
|
580
|
-
|
|
673
|
+
// A standalone sync records its proven outputs separately. During a later
|
|
674
|
+
// check/sync, compare that overlay before declaring the installation state
|
|
675
|
+
// stale; update still consolidates it into the main state transactionally.
|
|
676
|
+
let stateContent = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`);
|
|
677
|
+
if (mode === 'sync' && installedState && !changingClients) {
|
|
678
|
+
const files = { ...installedState.files }, blocks = { ...installedState.blocks };
|
|
679
|
+
for (const [path, digest] of Object.entries(removedFiles)) {
|
|
680
|
+
if (files[path] === digest && !stat(safePath(target, path))) delete files[path];
|
|
681
|
+
}
|
|
682
|
+
for (const path of removedBlocks) delete blocks[path];
|
|
683
|
+
for (const [path, digest] of Object.entries({ ...derivedFiles, ...derivedOutputs, ...derivedEntries })) { files[path] = digest; delete blocks[path]; }
|
|
684
|
+
for (const [path, digest] of Object.entries(derivedBlocks)) { blocks[path] = digest; delete files[path]; }
|
|
685
|
+
const same = (a, b) => {
|
|
686
|
+
const keys = Object.keys(a).sort();
|
|
687
|
+
return keys.length === Object.keys(b).length && keys.every(key => a[key] === b[key]);
|
|
688
|
+
};
|
|
689
|
+
if (installedState.schemaVersion === manifest.schemaVersion && installedState.version === manifest.version && installedState.language === manifest.language
|
|
690
|
+
&& JSON.stringify(installedState.clients) === JSON.stringify(manifest.clients) && same(files, manifest.files) && same(blocks, manifest.blocks)) stateContent = read(target, statePath);
|
|
691
|
+
}
|
|
692
|
+
desired.set(statePath, stateContent);
|
|
693
|
+
const derivedPath = actualStatePath(target).replace(/agentdevkit\.json$/, 'derived.json');
|
|
694
|
+
const derivedBytes = !migration ? read(target, derivedPath) : null;
|
|
695
|
+
if (derivedBytes && Object.keys(removedFiles).length) {
|
|
696
|
+
const finalState = JSON.parse(stateContent);
|
|
697
|
+
const remaining = Object.fromEntries(Object.entries(removedFiles).filter(([path, digest]) =>
|
|
698
|
+
finalState.files[path] === digest && !stat(safePath(target, path))));
|
|
699
|
+
if (Object.keys(remaining).length !== Object.keys(removedFiles).length) {
|
|
700
|
+
observeNative(derivedPath, derivedBytes);
|
|
701
|
+
desired.set(derivedPath, replaceJsonMember(derivedBytes, 'removedFiles', Object.keys(remaining).length ? remaining : undefined));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
581
704
|
const changes = [], conflicts = [];
|
|
582
705
|
const aliasedEntrypoint = path => canonicalEntrypoint(path) && entryPaths.get(canonicalEntrypoint(path)) !== path;
|
|
583
706
|
for (const [path, content] of desired) {
|
|
584
707
|
const current = read(target, path);
|
|
585
708
|
if (nativeInputs.has(path) && (nativeInputs.get(path) === null ? current !== null : !current?.equals(nativeInputs.get(path)))) fail('E_CHANGED', `File changed while planning: ${path}`);
|
|
586
709
|
if (current?.equals(content)) continue;
|
|
587
|
-
const managed =
|
|
710
|
+
const managed = path === derivedPath && derivedBytes && current?.equals(derivedBytes)
|
|
711
|
+
|| [previous?.files[path], derivedOutputs[path], derivedEntries[path], derivedFiles[path]].some(digest => digest && hash(current || '') === digest)
|
|
588
712
|
|| knownBlocks[path] && blockDigest(path, current || Buffer.alloc(0)) === knownBlocks[path];
|
|
589
713
|
const appendBlock = mixedPaths.has(path) || canonicalEntrypoint(path) || path === '.gitignore' || path.endsWith('/.gitignore');
|
|
590
714
|
const conflict = current && path !== statePath && !managed && !appendBlock;
|
|
@@ -704,7 +828,7 @@ export function planInstallation({ target, language = 'pt', mode = 'init', force
|
|
|
704
828
|
const localIgnore = planLocalIgnore(target, { outputs: ownedOutputs, templateRoot: stage });
|
|
705
829
|
const localIgnoreCheck = checkLocalIgnore(localIgnore, { replacedBlocks: replacedIgnoreBlocks(target, changes, previous) });
|
|
706
830
|
conflicts.push(...(untrack?.conflicts || []), ...localIgnoreCheck.missing);
|
|
707
|
-
return { target, language, mode, changes, conflicts: [...new Set(conflicts)].sort(), canApply: (!conflicts.length || force) && !untrack?.conflicts?.length && !localIgnoreCheck.missing.length, manifest, retiredRoots, ownedOutputs, untrack, localIgnore, localIgnoreCheck, migration, preserved: [...preserved.values()].sort((a, b) => a.path.localeCompare(b.path)) };
|
|
831
|
+
return { target, language, mode, changes, conflicts: [...new Set(conflicts)].sort(), canApply: (!conflicts.length || force) && !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)) };
|
|
708
832
|
} finally { rmSync(stage, { recursive: true, force: true }); }
|
|
709
833
|
}
|
|
710
834
|
|
|
@@ -712,6 +836,15 @@ export function applyPlan(plan) {
|
|
|
712
836
|
if (!plan.canApply) fail('E_CONFLICT', text(plan.language, 'conflict'));
|
|
713
837
|
assertLocalIgnoreUnchanged(plan.localIgnore);
|
|
714
838
|
const { target } = plan;
|
|
839
|
+
const assertFallbackChecks = () => {
|
|
840
|
+
for (const [path, expected] of plan.fallbackChecks || []) {
|
|
841
|
+
let current;
|
|
842
|
+
try { current = nativeOutputBoundary(target, path, plan.fallbackOwnedPaths || []); }
|
|
843
|
+
catch { fail('E_CHANGED', `Skill route changed after planning: ${path}`); }
|
|
844
|
+
if (JSON.stringify(current) !== JSON.stringify(expected)) fail('E_CHANGED', `Skill route changed after planning: ${path}`);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
assertFallbackChecks();
|
|
715
848
|
const stagedHuman = stagedDivergences(target);
|
|
716
849
|
const currentState = loadState(target);
|
|
717
850
|
const stagedChanges = plan.changes.filter(change => {
|
|
@@ -793,6 +926,7 @@ export function applyPlan(plan) {
|
|
|
793
926
|
rmdirSync(file); removedDirectories.push({ path: directory.path, mode: directory.mode });
|
|
794
927
|
}
|
|
795
928
|
}
|
|
929
|
+
assertFallbackChecks();
|
|
796
930
|
pruneRemovedDirectories(plan, directory => removedDirectories.push(directory));
|
|
797
931
|
const changesFallback = plan.changes.some(change => change.path === '.gitignore');
|
|
798
932
|
// Metadata is committed last; its writer rolls back partial writes itself.
|
|
@@ -881,6 +1015,10 @@ export function inspect(target) {
|
|
|
881
1015
|
for (const [path, digest] of Object.entries(state.files)) {
|
|
882
1016
|
if (derived.blocks[path] || derived.removedBlocks.includes(path)) continue;
|
|
883
1017
|
const content = read(target, path);
|
|
1018
|
+
if (derived.removedFiles[path] === digest && !derived.files[path] && !derived.entries[path]) {
|
|
1019
|
+
if (content) issues.push({ code: 'modified', path });
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
884
1022
|
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 });
|
|
885
1023
|
}
|
|
886
1024
|
for (const [path, digest] of Object.entries({ ...state.blocks, ...derived.blocks })) {
|
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],
|