@pathbit/agentdevkit 0.0.0-stage → 0.0.5
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 +109 -0
- package/.pbai4devs/INSTRUCTIONS.md +87 -0
- package/.pbai4devs/README.md +50 -0
- package/.pbai4devs/agents/README.md +18 -0
- package/.pbai4devs/agents/revisor/AGENT.md +16 -0
- package/.pbai4devs/agents/revisor/antigravity.yaml +8 -0
- package/.pbai4devs/agents/revisor/claude.yaml +5 -0
- package/.pbai4devs/agents/revisor/cursor.yaml +5 -0
- package/.pbai4devs/agents/revisor/gemini.yaml +6 -0
- package/.pbai4devs/agents/revisor/github.yaml +5 -0
- package/.pbai4devs/agents/revisor/kimi-code.yaml +5 -0
- package/.pbai4devs/agents/revisor/mimocode.yaml +10 -0
- package/.pbai4devs/agents/revisor/opencode.yaml +7 -0
- package/.pbai4devs/agents/revisor/zcode.yaml +5 -0
- package/.pbai4devs/commands/README.md +30 -0
- package/.pbai4devs/commands/atualizar-ferramentas/COMMAND.md +17 -0
- package/.pbai4devs/commands/diagnosticar/COMMAND.md +18 -0
- package/.pbai4devs/commands/instalar-ferramentas/COMMAND.md +21 -0
- package/.pbai4devs/commands/preparar-dev/COMMAND.md +14 -0
- package/.pbai4devs/commands/proximos-passos/COMMAND.md +9 -0
- package/.pbai4devs/commands/revisar-entrega/COMMAND.md +29 -0
- package/.pbai4devs/commands/sincronizar/COMMAND.md +20 -0
- package/.pbai4devs/commands/status-roadmap/COMMAND.md +12 -0
- package/.pbai4devs/commands/testar-navegador/COMMAND.md +16 -0
- package/.pbai4devs/commands/verificar-agentes/COMMAND.md +19 -0
- package/.pbai4devs/estado/README.md +12 -0
- package/.pbai4devs/external-skills/CATALOG.json +121 -0
- package/.pbai4devs/external-skills/README.md +48 -0
- package/.pbai4devs/rules/README.md +18 -0
- package/.pbai4devs/rules/ambiente-local/RULE.md +33 -0
- package/.pbai4devs/rules/ambiente-local/cursor.yaml +4 -0
- package/.pbai4devs/rules/documentacao/RULE.md +11 -0
- package/.pbai4devs/rules/documentacao/cursor.yaml +4 -0
- package/.pbai4devs/rules/estrutura/RULE.md +33 -0
- package/.pbai4devs/rules/estrutura/cursor.yaml +4 -0
- package/.pbai4devs/rules/git/RULE.md +18 -0
- package/.pbai4devs/rules/git/cursor.yaml +4 -0
- package/.pbai4devs/rules/proximos-passos/RULE.md +14 -0
- package/.pbai4devs/rules/qualidade/RULE.md +32 -0
- package/.pbai4devs/rules/qualidade/cursor.yaml +4 -0
- package/.pbai4devs/scripts/README.md +69 -0
- package/.pbai4devs/scripts/diagnosticar-ambiente/SCRIPT.mjs +41 -0
- package/.pbai4devs/scripts/diagnosticar-ambiente/tests/TEST.mjs +30 -0
- package/.pbai4devs/scripts/executar-script/README.md +28 -0
- package/.pbai4devs/scripts/executar-script/SCRIPT.mjs +78 -0
- package/.pbai4devs/scripts/executar-script/tests/TEST.mjs +122 -0
- package/.pbai4devs/scripts/gerenciar-skills-externas/README.md +31 -0
- package/.pbai4devs/scripts/gerenciar-skills-externas/SCRIPT.mjs +182 -0
- package/.pbai4devs/scripts/gerenciar-skills-externas/tests/TEST.mjs +134 -0
- package/.pbai4devs/scripts/inspecionar-projeto/SCRIPT.mjs +190 -0
- package/.pbai4devs/scripts/inspecionar-projeto/tests/TEST.mjs +161 -0
- package/.pbai4devs/scripts/lib/README.md +9 -0
- package/.pbai4devs/scripts/lib/adapters.mjs +135 -0
- package/.pbai4devs/scripts/lib/catalog.mjs +170 -0
- package/.pbai4devs/scripts/lib/gitignore.mjs +348 -0
- package/.pbai4devs/scripts/lib/npm.mjs +17 -0
- package/.pbai4devs/scripts/lib/resource-path.mjs +20 -0
- package/.pbai4devs/scripts/lib/runtime.mjs +28 -0
- package/.pbai4devs/scripts/lib/script-config.mjs +8 -0
- package/.pbai4devs/scripts/preparar-ferramentas/BOOTSTRAP.md +34 -0
- package/.pbai4devs/scripts/preparar-ferramentas/BOOTSTRAP.mjs +189 -0
- package/.pbai4devs/scripts/preparar-ferramentas/README.md +71 -0
- package/.pbai4devs/scripts/preparar-ferramentas/SCRIPT.mjs +455 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/FIXTURE.html +20 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_BROWSER.mjs +55 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_DESKTOP.mjs +61 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/SMOKE_VERCEL.mjs +192 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/TEST.mjs +552 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/bootstrap.test.mjs +94 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/just-bash/input.txt +2 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/just-bash-overlay/input.txt +1 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/skills/agentdevkit-smoke/SKILL.md +8 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/webreel/index.html +6 -0
- package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/webreel/webreel.config.json +10 -0
- package/.pbai4devs/scripts/revisar-entrega/README.md +36 -0
- package/.pbai4devs/scripts/revisar-entrega/SCRIPT.mjs +249 -0
- package/.pbai4devs/scripts/revisar-entrega/tests/TEST.mjs +249 -0
- package/.pbai4devs/scripts/sincronizar-agentes/README.md +41 -0
- package/.pbai4devs/scripts/sincronizar-agentes/SCRIPT.mjs +473 -0
- package/.pbai4devs/scripts/sincronizar-agentes/tests/LOCAL_IGNORE.mjs +290 -0
- package/.pbai4devs/scripts/sincronizar-agentes/tests/TEST.mjs +869 -0
- package/.pbai4devs/scripts/testar-infraestrutura/SCRIPT.mjs +39 -0
- package/.pbai4devs/scripts/testar-infraestrutura/tests/TEST.mjs +29 -0
- package/.pbai4devs/scripts/testar-maturidade/README.md +54 -0
- package/.pbai4devs/scripts/testar-maturidade/SCRIPT.mjs +477 -0
- package/.pbai4devs/scripts/testar-maturidade/assets/calculator.mjs +1 -0
- package/.pbai4devs/scripts/testar-maturidade/assets/calculator.test.mjs.template +8 -0
- package/.pbai4devs/scripts/testar-maturidade/assets/http-api.test.mjs.template +28 -0
- package/.pbai4devs/scripts/testar-maturidade/assets/server.mjs +34 -0
- package/.pbai4devs/scripts/testar-maturidade/tests/TEST.mjs +305 -0
- package/.pbai4devs/scripts/testar-navegador/REFERENCE.md +49 -0
- package/.pbai4devs/scripts/testar-navegador/SCRIPT.conf +1 -0
- package/.pbai4devs/scripts/testar-navegador/SCRIPT.js +64 -0
- package/.pbai4devs/scripts/testar-navegador/tests/FAIL.js +8 -0
- package/.pbai4devs/scripts/testar-navegador/tests/SUCCESS.js +13 -0
- package/.pbai4devs/scripts/verificar-recursos/README.md +25 -0
- package/.pbai4devs/scripts/verificar-recursos/SCRIPT.mjs +209 -0
- package/.pbai4devs/scripts/verificar-recursos/tests/TEST.mjs +310 -0
- package/.pbai4devs/scripts/verificar-recursos/tests/ambiente-local.mjs +80 -0
- package/.pbai4devs/skills/README.md +24 -0
- package/.pbai4devs/skills/git/git-branch/SKILL.md +11 -0
- package/.pbai4devs/skills/git/git-commit/SKILL.md +16 -0
- package/.pbai4devs/skills/git/git-diff/SKILL.md +9 -0
- package/.pbai4devs/skills/git/git-fetch/SKILL.md +10 -0
- package/.pbai4devs/skills/git/git-log/SKILL.md +9 -0
- package/.pbai4devs/skills/git/git-merge/SKILL.md +13 -0
- package/.pbai4devs/skills/git/git-pull/SKILL.md +12 -0
- package/.pbai4devs/skills/git/git-push/SKILL.md +12 -0
- package/.pbai4devs/skills/git/git-status/SKILL.md +9 -0
- package/.pbai4devs/skills/git/git-worktree/SKILL.md +10 -0
- package/.pbai4devs/skills/produto/criar-design-doc/SKILL.md +37 -0
- package/.pbai4devs/skills/produto/criar-prd/SKILL.md +35 -0
- package/.pbai4devs/skills/produto/criar-ux-ui/SKILL.md +34 -0
- package/.pbai4devs/skills/projeto/atualizar-documentacao/SKILL.md +26 -0
- package/.pbai4devs/skills/projeto/configurar-projeto/SKILL.md +62 -0
- package/.pbai4devs/skills/projeto/criar-agente/SKILL.md +20 -0
- package/.pbai4devs/skills/projeto/criar-comando/SKILL.md +25 -0
- package/.pbai4devs/skills/projeto/criar-recurso/SKILL.md +41 -0
- package/.pbai4devs/skills/projeto/criar-regra/SKILL.md +19 -0
- package/.pbai4devs/skills/projeto/criar-skill/SKILL.md +22 -0
- package/.pbai4devs/skills/projeto/executar-script/SKILL.md +29 -0
- package/.pbai4devs/skills/projeto/preparar-dev/SKILL.md +45 -0
- package/.pbai4devs/skills/projeto/preparar-ferramentas/SKILL.md +56 -0
- package/.pbai4devs/skills/projeto/proximos-passos/SKILL.md +23 -0
- package/.pbai4devs/skills/projeto/sincronizar-agentes/SKILL.md +42 -0
- package/.pbai4devs/skills/projeto/usar-skills-externas/SKILL.md +28 -0
- package/.pbai4devs/skills/projeto/verificar-trabalho/SKILL.md +45 -0
- package/.pbai4devs/skills/specs/criar-spec/SKILL.md +33 -0
- package/.pbai4devs/skills/specs/gerenciar-roadmap/SKILL.md +26 -0
- package/.pbai4devs/skills/specs/implementar-spec/SKILL.md +48 -0
- package/.pbai4devs/templates/README.md +65 -0
- package/.pbai4devs/templates/adr/TEMPLATE.md +21 -0
- package/.pbai4devs/templates/ambiente-local/Makefile.template +48 -0
- package/.pbai4devs/templates/ambiente-local/TEMPLATE.md +48 -0
- package/.pbai4devs/templates/ambiente-local/compose.local.yml.template +16 -0
- package/.pbai4devs/templates/ambiente-local/dockerignore.template +28 -0
- package/.pbai4devs/templates/changelog/TEMPLATE.md +17 -0
- package/.pbai4devs/templates/claude/TEMPLATE.md +8 -0
- package/.pbai4devs/templates/clients/ADAPTERS.json +306 -0
- package/.pbai4devs/templates/clients/TEMPLATE.md +12 -0
- package/.pbai4devs/templates/comando/TEMPLATE.md +15 -0
- package/.pbai4devs/templates/configuracao/TEMPLATE.md +46 -0
- package/.pbai4devs/templates/configuracao-raiz/TEMPLATE.md +22 -0
- package/.pbai4devs/templates/configuracao-raiz/editorconfig.template +21 -0
- package/.pbai4devs/templates/configuracao-raiz/env.example.template +3 -0
- package/.pbai4devs/templates/configuracao-raiz/gitattributes.template +6 -0
- package/.pbai4devs/templates/contribuicao/TEMPLATE.md +23 -0
- package/.pbai4devs/templates/design/TEMPLATE.md +30 -0
- package/.pbai4devs/templates/entrypoints/AGENTS.md +19 -0
- package/.pbai4devs/templates/entrypoints/CLAUDE.md +7 -0
- package/.pbai4devs/templates/entrypoints/GEMINI.md +7 -0
- package/.pbai4devs/templates/entrypoints/TEMPLATE.md +12 -0
- package/.pbai4devs/templates/gitignore/TEMPLATE.md +42 -0
- package/.pbai4devs/templates/gitignore/agents.ignore +45 -0
- package/.pbai4devs/templates/gitignore/clients.json +20 -0
- package/.pbai4devs/templates/gitignore/local.ignore +10 -0
- package/.pbai4devs/templates/gitignore/shared.ignore +60 -0
- package/.pbai4devs/templates/instrucoes-agentes/TEMPLATE.md +25 -0
- package/.pbai4devs/templates/plano-implementacao/TEMPLATE.md +27 -0
- package/.pbai4devs/templates/prd/TEMPLATE.md +30 -0
- package/.pbai4devs/templates/readme-pasta/TEMPLATE.md +16 -0
- package/.pbai4devs/templates/readme-projeto/TEMPLATE.md +34 -0
- package/.pbai4devs/templates/release/TEMPLATE.md +23 -0
- package/.pbai4devs/templates/revisao-entrega/TEMPLATE.md +41 -0
- package/.pbai4devs/templates/roadmap/TEMPLATE.md +18 -0
- package/.pbai4devs/templates/seguranca/TEMPLATE.md +22 -0
- package/.pbai4devs/templates/spec/TEMPLATE.md +61 -0
- package/.pbai4devs/templates/tasks/TEMPLATE.md +20 -0
- package/.pbai4devs/templates/ux-ui/TEMPLATE.md +26 -0
- package/.pbai4devs/tools/README.md +87 -0
- package/.pbai4devs/tools/agent-browser/README.md +26 -0
- package/.pbai4devs/tools/agent-browser/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/agent-browser/TOOL.json +33 -0
- package/.pbai4devs/tools/agent-desktop/README.md +26 -0
- package/.pbai4devs/tools/agent-desktop/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/agent-desktop/TOOL.json +30 -0
- package/.pbai4devs/tools/just-bash/README.md +23 -0
- package/.pbai4devs/tools/just-bash/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/just-bash/TOOL.json +27 -0
- package/.pbai4devs/tools/markdownlint-cli2/README.md +18 -0
- package/.pbai4devs/tools/markdownlint-cli2/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/markdownlint-cli2/TOOL.json +28 -0
- package/.pbai4devs/tools/opensrc/README.md +25 -0
- package/.pbai4devs/tools/opensrc/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/opensrc/TOOL.json +29 -0
- package/.pbai4devs/tools/portless/README.md +21 -0
- package/.pbai4devs/tools/portless/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/portless/TOOL.json +30 -0
- package/.pbai4devs/tools/skills/README.md +24 -0
- package/.pbai4devs/tools/skills/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/skills/TOOL.json +33 -0
- package/.pbai4devs/tools/webreel/README.md +23 -0
- package/.pbai4devs/tools/webreel/SCRIPT.mjs +5 -0
- package/.pbai4devs/tools/webreel/TOOL.json +25 -0
- package/CHANGELOG.md +58 -0
- package/CONTRIBUTING.md +73 -0
- package/LICENSE +21 -0
- package/README.en.md +78 -0
- package/README.es.md +80 -0
- package/README.md +88 -2
- package/bin/README.md +8 -0
- package/bin/agentdevkit.mjs +35 -0
- package/docs/README.md +81 -0
- package/docs/guides/README.md +14 -0
- package/docs/guides/cli.md +81 -0
- package/docs/guides/installation.md +78 -0
- package/docs/guides/resources.md +121 -0
- package/docs/guides/tools-and-testing.md +85 -0
- package/docs/guides/troubleshooting.md +151 -0
- package/docs/guides/workflow.md +130 -0
- package/docs/maintainers/README.md +12 -0
- package/docs/maintainers/community.md +49 -0
- package/docs/maintainers/development.md +54 -0
- package/docs/maintainers/maintenance.md +104 -0
- package/docs/maintainers/packages.md +76 -0
- package/docs/product/README.md +12 -0
- package/docs/product/design.md +94 -0
- package/docs/product/prd.md +64 -0
- package/docs/product/roadmap.md +36 -0
- package/docs/product/ux-ui.md +42 -0
- package/docs/specs/README.md +20 -0
- package/docs/specs/github-packages/SPEC.md +45 -0
- package/docs/specs/v0.0.1/README.md +10 -0
- package/docs/specs/v0.0.1/SPEC.md +57 -0
- package/docs/specs/v0.0.2/README.md +10 -0
- package/docs/specs/v0.0.2/SPEC.md +67 -0
- package/docs/specs/v0.0.3/README.md +10 -0
- package/docs/specs/v0.0.3/SPEC.md +64 -0
- package/docs/specs/v0.0.4/README.md +10 -0
- package/docs/specs/v0.0.4/SPEC.md +53 -0
- package/docs/specs/v0.0.5/README.md +9 -0
- package/docs/specs/v0.0.5/SPEC.md +42 -0
- package/package.json +52 -4
- package/src/README.md +16 -0
- package/src/adapters/index.mjs +1 -0
- package/src/cli.mjs +162 -0
- package/src/config.mjs +14 -0
- package/src/i18n.mjs +42 -0
- package/src/install.mjs +367 -0
- package/src/next.mjs +79 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { ensureRuntime, main, validateArchive, validateExtracted } from '../BOOTSTRAP.mjs';
|
|
9
|
+
import { findExecutable } from '../../lib/runtime.mjs';
|
|
10
|
+
|
|
11
|
+
const project = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
|
|
12
|
+
const tar = findExecutable('tar');
|
|
13
|
+
function fixture(t) {
|
|
14
|
+
assert.ok(tar, 'Native tar is required for archive integration tests.');
|
|
15
|
+
mkdirSync(join(project, '.agent-tests'), { recursive: true });
|
|
16
|
+
const root = mkdtempSync(join(project, '.agent-tests/bootstrap-node-'));
|
|
17
|
+
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
18
|
+
const platform = process.platform, arch = process.arch;
|
|
19
|
+
const key = platform === 'win32' ? `win-${arch}` : `${platform}-${arch}`;
|
|
20
|
+
const top = `node-v24.1.2-${key}`, extension = platform === 'win32' ? 'zip' : 'tar.gz';
|
|
21
|
+
const bundle = join(root, top); mkdirSync(bundle);
|
|
22
|
+
const write = (path, content) => { mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, content); };
|
|
23
|
+
write(join(bundle, platform === 'win32' ? 'node.exe' : 'bin/node'), 'fixture-node');
|
|
24
|
+
write(join(bundle, platform === 'win32' ? 'node_modules/npm/bin/npm-cli.js' : 'lib/node_modules/npm/bin/npm-cli.js'), 'fixture-npm');
|
|
25
|
+
const archive = join(root, `${top}.${extension}`);
|
|
26
|
+
const packed = spawnSync(tar, [platform === 'win32' ? '-acf' : '-czf', archive, '-C', root, top], { encoding: 'utf8' });
|
|
27
|
+
assert.equal(packed.status, 0, packed.stderr);
|
|
28
|
+
const checksum = createHash('sha256').update(readFileSync(archive)).digest('hex');
|
|
29
|
+
let manifest = `${checksum} ${top}.${extension}\n`, calls = 0;
|
|
30
|
+
const run = (file, args, options) => {
|
|
31
|
+
if (args[0] === '-p') {
|
|
32
|
+
if (!existsSync(file) || readFileSync(file, 'utf8') !== 'fixture-node') return { status: 1, stdout: '' };
|
|
33
|
+
return { status: 0, stdout: JSON.stringify({ version: '24.1.2', node: file, bun: false }) };
|
|
34
|
+
}
|
|
35
|
+
if (args[1] === '--version') return { status: existsSync(args[0]) && readFileSync(args[0], 'utf8') === 'fixture-npm' ? 0 : 1, stdout: '11.0.0\n' };
|
|
36
|
+
return spawnSync(file, args, options);
|
|
37
|
+
};
|
|
38
|
+
const options = { root, node: join(root, 'missing-node'), platform, arch, libc: 'glibc', run,
|
|
39
|
+
download: async (url, destination) => { calls++; if (url.endsWith('SHASUMS256.txt')) writeFileSync(destination, manifest); else copyFileSync(archive, destination); } };
|
|
40
|
+
return { root, options, archive, key, cache: join(root, '.pbai4devs/tools/.cache/runtime/node', key), setManifest: value => { manifest = value; }, calls: () => calls };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test('bootstrap Node extrai arquivo real, verifica npm e reutiliza cache sem rede', async t => {
|
|
44
|
+
const f = fixture(t), runtime = await ensureRuntime(f.options);
|
|
45
|
+
assert.equal(runtime.source, 'download'); assert.equal(runtime.version, '24.1.2'); assert.equal(f.calls(), 2);
|
|
46
|
+
assert.ok(existsSync(runtime.node)); assert.ok(existsSync(runtime.npm));
|
|
47
|
+
const cached = await ensureRuntime({ ...f.options, download: async () => { throw new Error('Network must not run'); } });
|
|
48
|
+
assert.equal(cached.source, 'cache'); assert.equal(cached.node, runtime.node);
|
|
49
|
+
assert.ok(readdirSync(f.cache).every(name => !name.startsWith('.stage.')));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('checksum, manifesto ambíguo e download falho não promovem runtime', async t => {
|
|
53
|
+
for (const kind of ['checksum', 'manifest', 'download']) await t.test(kind, async subtest => {
|
|
54
|
+
const f = fixture(subtest), previous = join(f.cache, 'v24.1.2'); mkdirSync(previous, { recursive: true }); writeFileSync(join(previous, 'preserved'), 'original');
|
|
55
|
+
if (kind === 'checksum') f.setManifest(`${'0'.repeat(64)} node-v24.1.2-${f.key}.${process.platform === 'win32' ? 'zip' : 'tar.gz'}\n`);
|
|
56
|
+
if (kind === 'manifest') f.setManifest('no matching official archive\n');
|
|
57
|
+
const options = kind === 'download' ? { ...f.options, download: async () => { throw new Error('offline'); } } : f.options;
|
|
58
|
+
await assert.rejects(ensureRuntime(options), /SHA-256|indisponivel|offline/);
|
|
59
|
+
assert.equal(readFileSync(join(previous, 'preserved'), 'utf8'), 'original');
|
|
60
|
+
assert.ok(readdirSync(f.cache).every(name => !name.startsWith('.stage.')));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('validação recusa nomes fora da distribuição antes da extração', () => {
|
|
65
|
+
const top = 'node-v24.1.2-linux-x64';
|
|
66
|
+
assert.doesNotThrow(() => validateArchive(`${top}/bin/npm\n`, top));
|
|
67
|
+
for (const name of ['../../outside', `${top}/../outside`, '/absolute/file', `${top}/C:file`]) assert.throws(() => validateArchive(`${name}\n`, top), /fora/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('links internos extraídos são aceitos e links externos são recusados', t => {
|
|
71
|
+
const f = fixture(t), inside = join(f.root, 'inside'), outside = join(f.root, 'outside'); mkdirSync(inside); mkdirSync(outside); writeFileSync(join(inside, 'file'), 'safe');
|
|
72
|
+
try { symlinkSync('file', join(inside, 'link')); } catch (error) { if (['EPERM', 'EACCES'].includes(error.code)) { t.skip('No symlink privilege'); return; } throw error; }
|
|
73
|
+
assert.doesNotThrow(() => validateExtracted(inside));
|
|
74
|
+
symlinkSync(outside, join(inside, 'escape'), 'dir');
|
|
75
|
+
assert.throws(() => validateExtracted(inside), /fora/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('cache simbólico é recusado sem tocar alvo externo', async t => {
|
|
79
|
+
const f = fixture(t), outside = join(f.root, 'outside'); mkdirSync(outside);
|
|
80
|
+
const cacheRoot = join(f.root, '.pbai4devs/tools/.cache'); mkdirSync(dirname(cacheRoot), { recursive: true });
|
|
81
|
+
try { symlinkSync(outside, cacheRoot, 'dir'); } catch (error) { if (['EPERM', 'EACCES'].includes(error.code)) { t.skip('No symlink privilege'); return; } throw error; }
|
|
82
|
+
await assert.rejects(ensureRuntime(f.options), /link simbolico/); assert.deepEqual(readdirSync(outside), []);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('opções inválidas falham antes de rede ou criação de cache', async () => {
|
|
86
|
+
await assert.rejects(main(['--runtime-only', 'all']), /nao aceita/);
|
|
87
|
+
await assert.rejects(main(['--unknown']), /runtime-only/);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('runtime atual saudável não altera PATH nem grava cache', async t => {
|
|
91
|
+
const f = fixture(t), original = process.env.PATH;
|
|
92
|
+
const result = await ensureRuntime({ root: f.root, minimum: Number(process.versions.node.split('.')[0]) });
|
|
93
|
+
assert.equal(result.source, 'current'); assert.equal(process.env.PATH, original); assert.equal(existsSync(f.cache), false);
|
|
94
|
+
});
|
package/.pbai4devs/scripts/preparar-ferramentas/tests/fixtures-vercel/just-bash-overlay/input.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Fixture para escrita somente em memória.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Verificação dos documentos, da revisão e das evidências da entrega.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Revisar entrega
|
|
6
|
+
|
|
7
|
+
Após implementar, atualizar a documentação afetada e testar, gere o registro:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
node .pbai4devs/scripts/revisar-entrega/SCRIPT.mjs prepare --id minha-mudanca
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Use `--spec caminho/SPEC.md` para extrair os critérios `AC-001` da spec.
|
|
14
|
+
`--scope arquivo` limita uma revisão; sem ele, o snapshot reúne alterações Git.
|
|
15
|
+
Use `--document arquivo` para incluir PRD, design e outros documentos.
|
|
16
|
+
README, AGENTS, CLAUDE e GEMINI versionáveis na raiz entram automaticamente na
|
|
17
|
+
revisão, mesmo sem alterações. Saídas locais ignoradas fora do índice não são
|
|
18
|
+
documentos obrigatórios; documentos rastreados continuam cobertos mesmo quando
|
|
19
|
+
uma regra de ignore corresponde ao nome.
|
|
20
|
+
Preencha decisões, resultados e evidências no JSON retornado; peça revisão
|
|
21
|
+
independente, execute `seal <caminho/revisao.json>` e depois
|
|
22
|
+
`check <caminho/revisao.json>` antes de concluir.
|
|
23
|
+
|
|
24
|
+
O check recusa resultados pendentes, evidências ausentes/versionadas e arquivos
|
|
25
|
+
alterados depois da revisão. Relatórios ficam em
|
|
26
|
+
`.agent-tests/<id>/<execução>/`.
|
|
27
|
+
O preparo preserva o escopo em `baseline.json`; o selo registra hashes do
|
|
28
|
+
relatório e das provas em `seal.json`. Preserve esses metadados no fluxo.
|
|
29
|
+
Mudanças posteriores exigem nova execução, preservando as evidências anteriores.
|
|
30
|
+
Uma deleção staged com arquivo ainda presente e ignorado é registrada como
|
|
31
|
+
`indexAction: untrack`. O snapshot preserva seu hash físico; alterações no
|
|
32
|
+
arquivo, no ignore ou nessa ação do índice invalidam a revisão. Diferenças
|
|
33
|
+
entre índice e conteúdo testado continuam bloqueadas, inclusive com `--scope`.
|
|
34
|
+
O script confere o registro; a revisão semântica continua sendo responsabilidade
|
|
35
|
+
do agente revisor. Veja a skill
|
|
36
|
+
[atualizar-documentacao](../../skills/projeto/atualizar-documentacao/SKILL.md).
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { isMain, projectRoot, requireNode } from '../lib/runtime.mjs';
|
|
7
|
+
|
|
8
|
+
const fail = message => { throw new Error(message); };
|
|
9
|
+
const slug = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
10
|
+
const portable = value => value.split(sep).join('/');
|
|
11
|
+
const nonempty = value => typeof value === 'string' && value.trim().length > 0;
|
|
12
|
+
const completed = value => nonempty(value) && !/^(?:a definir|pendente|pending|todo|tbd|\.\.\.)[.!]?$/i.test(value.trim());
|
|
13
|
+
const digest = file => createHash('sha256').update(readFileSync(file)).digest('hex');
|
|
14
|
+
|
|
15
|
+
function pathInRoot(root, input) {
|
|
16
|
+
if (!nonempty(input) || /[\0\r\n]/.test(input)) fail('Caminho invalido.');
|
|
17
|
+
const name = portable(relative(root, resolve(root, input)));
|
|
18
|
+
if (!name || name === '..' || name.startsWith('../') || isAbsolute(name) || name.split('/').some(part => part.toLowerCase() === '.git')) fail(`Caminho fora do escopo: ${input}`);
|
|
19
|
+
let current = root;
|
|
20
|
+
for (const part of name.split('/')) {
|
|
21
|
+
current = join(current, part);
|
|
22
|
+
try { if (lstatSync(current).isSymbolicLink()) fail(`Link simbolico nao permitido: ${name}`); }
|
|
23
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
24
|
+
}
|
|
25
|
+
return name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function git(root, args, allowFailure = false) {
|
|
29
|
+
const result = spawnSync('git', args, { cwd: root, encoding: 'utf8', timeout: 15000, windowsHide: true });
|
|
30
|
+
if ((result.error || result.status !== 0) && !allowFailure) fail(`Git: ${result.error?.message || result.stderr?.trim() || 'operacao falhou'}`);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function repository(root) {
|
|
35
|
+
const canonical = realpathSync(root);
|
|
36
|
+
const top = git(canonical, ['rev-parse', '--show-toplevel']).stdout.trim();
|
|
37
|
+
if (realpathSync(top) !== canonical) fail('Use a raiz do repositorio.');
|
|
38
|
+
return canonical;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function head(root) {
|
|
42
|
+
const result = git(root, ['rev-parse', '--verify', 'HEAD'], true);
|
|
43
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ignored(root, file) {
|
|
47
|
+
return git(root, ['check-ignore', '--no-index', '-q', '--', file], true).status === 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function tracked(root, file) {
|
|
51
|
+
return git(root, ['--literal-pathspecs', 'ls-files', '--error-unmatch', '--', file], true).status === 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ignoredPaths(root, files) {
|
|
55
|
+
if (!files.length) return new Set();
|
|
56
|
+
const result = spawnSync('git', ['check-ignore', '--no-index', '--stdin', '-z'], { cwd: root, input: files.join('\0') + '\0', encoding: 'utf8', timeout: 15000, windowsHide: true });
|
|
57
|
+
if (result.error || ![0, 1].includes(result.status)) fail(`Git: ${result.error?.message || result.stderr?.trim() || 'falha ao conferir ignores'}`);
|
|
58
|
+
return new Set(result.stdout.split('\0').filter(Boolean));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stagedDeletions(root) {
|
|
62
|
+
return new Set(git(root, ['diff', '--cached', '--diff-filter=D', '--no-renames', '--name-only', '-z', '--']).stdout.split('\0').filter(Boolean));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function changedFiles(root) {
|
|
66
|
+
const baseline = head(root);
|
|
67
|
+
const versioned = git(root, baseline ? ['diff', '--name-only', '-z', 'HEAD', '--'] : ['ls-files', '-z']).stdout;
|
|
68
|
+
const untracked = git(root, ['ls-files', '--others', '--exclude-standard', '-z']).stdout;
|
|
69
|
+
return [...new Set((versioned + '\0' + untracked).split('\0').filter(Boolean))].sort();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function snapshot(root, mode, paths, documents = [], spec = null) {
|
|
73
|
+
const names = mode === 'git' ? [...changedFiles(root), ...paths] : paths;
|
|
74
|
+
const selected = [...new Set(names.map(file => pathInRoot(root, file)))].sort();
|
|
75
|
+
const deleted = stagedDeletions(root), local = ignoredPaths(root, selected);
|
|
76
|
+
const files = selected.map(path => {
|
|
77
|
+
if (path.toLowerCase().startsWith('.agent-tests/')) fail('Artefatos nao fazem parte do escopo versionado.');
|
|
78
|
+
const absolute = join(root, path);
|
|
79
|
+
if (!existsSync(absolute)) return { path, sha256: null };
|
|
80
|
+
if (!lstatSync(absolute).isFile()) fail(`Escopo exige arquivos: ${path}`);
|
|
81
|
+
return { path, sha256: createHash('sha256').update(readFileSync(absolute)).digest('hex'), ...(deleted.has(path) && local.has(path) ? { indexAction: 'untrack' } : {}) };
|
|
82
|
+
});
|
|
83
|
+
if (!files.length) fail('Nenhuma alteracao para revisar; indique --scope para uma revisao especifica.');
|
|
84
|
+
return { mode, head: head(root), files, documents, spec };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function specCriteria(text) {
|
|
88
|
+
const section = text.match(/^## (?:Crit[eé]rios de aceite|Acceptance criteria)\s*\r?\n([\s\S]*?)(?=^## |$(?![\s\S]))/im)?.[1];
|
|
89
|
+
const ids = [...new Set((section || '').match(/\bAC-\d{3,}\b/g) || [])];
|
|
90
|
+
if (!ids.length) fail('A spec deve definir criterios AC-001, AC-002... na secao Criterios de aceite.');
|
|
91
|
+
return ids;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function expectedDocuments(root, state, extra = []) {
|
|
95
|
+
const existing = ['README.md', 'AGENTS.md', 'CLAUDE.md', 'GEMINI.md', 'CONTRIBUTING.md', 'CHANGELOG.md', 'SECURITY.md'].filter(file => existsSync(join(root, file)));
|
|
96
|
+
const changed = state.files.filter(file => /\.mdc?$/i.test(file.path)).map(file => file.path);
|
|
97
|
+
const candidates = [...new Set([...existing, ...changed].map(file => pathInRoot(root, file)))];
|
|
98
|
+
const local = ignoredPaths(root, candidates);
|
|
99
|
+
const versioned = new Set(git(root, ['ls-files', '-z']).stdout.split('\0').filter(Boolean));
|
|
100
|
+
return [...new Set([...candidates.filter(file => !local.has(file) || versioned.has(file)), ...extra.map(file => pathInRoot(root, file))])].sort();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function checkIndex(root, state) {
|
|
104
|
+
const staged = new Set(git(root, ['diff', '--cached', '--name-only', '-z', '--']).stdout.split('\0').filter(Boolean));
|
|
105
|
+
const deleted = stagedDeletions(root);
|
|
106
|
+
const indexed = new Set(git(root, ['ls-files', '-z']).stdout.split('\0').filter(Boolean));
|
|
107
|
+
const local = ignoredPaths(root, state.files.filter(file => file.indexAction === 'untrack').map(file => file.path));
|
|
108
|
+
for (const file of state.files) {
|
|
109
|
+
if (file.indexAction === 'untrack') {
|
|
110
|
+
if (!staged.has(file.path) || !deleted.has(file.path) || indexed.has(file.path) || file.sha256 === null || !existsSync(join(root, file.path)) || !local.has(file.path)) fail(`Acao de desindexacao mudou: ${file.path}`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (file.indexAction !== undefined) fail(`Acao de indice invalida: ${file.path}`);
|
|
114
|
+
if (!staged.has(file.path)) continue;
|
|
115
|
+
const deletedButPresent = !indexed.has(file.path) && file.sha256 !== null;
|
|
116
|
+
const differs = git(root, ['--literal-pathspecs', 'diff', '--quiet', '--no-ext-diff', '--', file.path], true);
|
|
117
|
+
if (deletedButPresent || differs.error || differs.status !== 0) fail(`Indice Git difere do arquivo testado: ${file.path}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function artifact(root, file, prefix, mustExist) {
|
|
122
|
+
const path = pathInRoot(root, file);
|
|
123
|
+
if (!path.startsWith(prefix + '/')) fail(`Evidencia deve ficar em ${prefix}/: ${path}`);
|
|
124
|
+
if (!ignored(root, path) || tracked(root, path)) fail(`Artefato deve estar ignorado e fora do indice Git: ${path}`);
|
|
125
|
+
if (mustExist && (!existsSync(join(root, path)) || !lstatSync(join(root, path)).isFile() || lstatSync(join(root, path)).size === 0)) fail(`Evidencia ausente ou vazia: ${path}`);
|
|
126
|
+
return path;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function prepareReview({ root = projectRoot, id, execution = new Date().toISOString().replace(/[^a-z0-9]/gi, '').toLowerCase(), spec = null, scope = [], documents = [], output } = {}) {
|
|
130
|
+
requireNode(); root = repository(root);
|
|
131
|
+
if (!slug.test(id || '') || !slug.test(execution)) fail('Use referencia e execucao como slugs em minusculas.');
|
|
132
|
+
const reference = spec ? pathInRoot(root, spec) : null;
|
|
133
|
+
if (reference && ignored(root, reference) && !tracked(root, reference)) fail('A spec deve ser um documento versionavel, fora dos artefatos ignorados.');
|
|
134
|
+
const criteria = reference ? specCriteria(readFileSync(join(root, reference), 'utf8')) : ['AC-001'];
|
|
135
|
+
let state = snapshot(root, scope.length ? 'scope' : 'git', [...scope, ...(reference ? [reference] : [])]);
|
|
136
|
+
const requiredDocs = expectedDocuments(root, state, documents);
|
|
137
|
+
const localDocuments = ignoredPaths(root, requiredDocs);
|
|
138
|
+
for (const file of requiredDocs) if (localDocuments.has(file) && !tracked(root, file)) fail(`Documento deve ser versionavel: ${file}`);
|
|
139
|
+
state = snapshot(root, scope.length ? 'scope' : 'git', [...state.files.map(file => file.path), ...requiredDocs], requiredDocs, reference);
|
|
140
|
+
const prefix = `.agent-tests/${id}/${execution}`;
|
|
141
|
+
const file = artifact(root, output || `${prefix}/revisao.json`, prefix, false);
|
|
142
|
+
const baselineFile = artifact(root, `${prefix}/baseline.json`, prefix, false);
|
|
143
|
+
if (existsSync(join(root, file)) || existsSync(join(root, baselineFile))) fail(`Relatorio ja existe; preserve-o e use outra execucao: ${file}`);
|
|
144
|
+
const baseline = { version: 1, reference: id, execution, spec: reference, snapshot: state, criteria };
|
|
145
|
+
mkdirSync(dirname(join(root, file)), { recursive: true });
|
|
146
|
+
writeFileSync(join(root, baselineFile), JSON.stringify(baseline, null, 2) + '\n', { flag: 'wx' });
|
|
147
|
+
const report = {
|
|
148
|
+
version: 1, reference: id, execution, spec: reference, snapshot: state,
|
|
149
|
+
baseline: { file: baselineFile, sha256: digest(join(root, baselineFile)) },
|
|
150
|
+
documentation: expectedDocuments(root, state, documents).map(path => ({ path, decision: 'pending', reason: '' })),
|
|
151
|
+
validation: criteria.map(criterion => ({ criterion, method: '', prerequisite: '', assertion: '', evidence: [], status: 'pending' })),
|
|
152
|
+
review: { status: 'pending', reviewer: '', findings: [] },
|
|
153
|
+
};
|
|
154
|
+
mkdirSync(dirname(join(root, file)), { recursive: true });
|
|
155
|
+
writeFileSync(join(root, file), JSON.stringify(report, null, 2) + '\n', { flag: 'wx' });
|
|
156
|
+
return { file, report };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function validateReview(file, { root = projectRoot, requireSeal = true } = {}) {
|
|
160
|
+
requireNode(); root = repository(root);
|
|
161
|
+
const reportPath = pathInRoot(root, file);
|
|
162
|
+
const report = JSON.parse(readFileSync(join(root, reportPath), 'utf8'));
|
|
163
|
+
if (report.version !== 1 || !slug.test(report.reference || '') || !slug.test(report.execution || '')) fail('Cabecalho de revisao invalido.');
|
|
164
|
+
const prefix = `.agent-tests/${report.reference}/${report.execution}`;
|
|
165
|
+
artifact(root, reportPath, prefix, true);
|
|
166
|
+
const baselineFile = artifact(root, `${prefix}/baseline.json`, prefix, true);
|
|
167
|
+
const baseline = JSON.parse(readFileSync(join(root, baselineFile), 'utf8'));
|
|
168
|
+
if (report.baseline?.file !== baselineFile || report.baseline.sha256 !== digest(join(root, baselineFile)) ||
|
|
169
|
+
baseline.reference !== report.reference || baseline.execution !== report.execution || baseline.spec !== report.spec ||
|
|
170
|
+
JSON.stringify(baseline.snapshot) !== JSON.stringify(report.snapshot)) fail('Baseline gerada diverge do relatorio; preserve os metadados e refaca o preparo.');
|
|
171
|
+
const state = report.snapshot;
|
|
172
|
+
if (!state || !['git', 'scope'].includes(state.mode) || !Array.isArray(state.files) || !Array.isArray(state.documents) || report.spec !== state.spec) fail('Snapshot ausente ou referencias alteradas.');
|
|
173
|
+
const requiredDocs = state.documents.map(file => pathInRoot(root, file));
|
|
174
|
+
const currentDocuments = expectedDocuments(root, state, requiredDocs);
|
|
175
|
+
if (currentDocuments.some(path => !requiredDocs.includes(path) || !state.files.some(file => file.path === path))) {
|
|
176
|
+
fail('Documentos obrigatorios mudaram depois do snapshot; refaca a revisao.');
|
|
177
|
+
}
|
|
178
|
+
const current = snapshot(root, state.mode, state.files.map(file => file.path), requiredDocs, state.spec);
|
|
179
|
+
if (JSON.stringify(current) !== JSON.stringify(state)) fail('Arquivos ou HEAD mudaram depois do snapshot; refaca a revisao.');
|
|
180
|
+
checkIndex(root, state);
|
|
181
|
+
if (!Array.isArray(report.documentation) || !Array.isArray(report.validation)) fail('Documentacao e validacoes sao obrigatorias.');
|
|
182
|
+
const docs = new Map();
|
|
183
|
+
for (const item of report.documentation) {
|
|
184
|
+
const path = pathInRoot(root, item.path);
|
|
185
|
+
if (docs.has(path) || !['updated', 'no-impact', 'not-applicable'].includes(item.decision) || !completed(item.reason)) fail(`Decisao de documentacao pendente/invalida: ${path}`);
|
|
186
|
+
if (item.decision !== 'not-applicable' && !existsSync(join(root, path))) fail(`Documento ausente: ${path}`);
|
|
187
|
+
if (item.decision === 'not-applicable' && existsSync(join(root, path))) fail(`Documento existente exige decisao de impacto: ${path}`);
|
|
188
|
+
docs.set(path, item);
|
|
189
|
+
}
|
|
190
|
+
for (const path of currentDocuments) if (!docs.has(path)) fail(`Documento sem revisao: ${path}`);
|
|
191
|
+
const ids = new Set();
|
|
192
|
+
const proofs = new Set();
|
|
193
|
+
for (const item of report.validation) {
|
|
194
|
+
if (!/^AC-\d{3,}$/.test(item.criterion || '') || ids.has(item.criterion)) fail('Criterio invalido ou duplicado.');
|
|
195
|
+
ids.add(item.criterion);
|
|
196
|
+
if (![item.method, item.prerequisite, item.assertion].every(completed)) fail(`Plano de validacao incompleto: ${item.criterion}`);
|
|
197
|
+
if (item.status !== 'passed' || !Array.isArray(item.evidence) || !item.evidence.length) fail(`Criterio sem prova aprovada: ${item.criterion}`);
|
|
198
|
+
for (const proof of item.evidence) proofs.add(artifact(root, proof, prefix, true));
|
|
199
|
+
}
|
|
200
|
+
if (!ids.size) fail('Nenhum criterio de aceite.');
|
|
201
|
+
for (const id of baseline.criteria) if (!ids.has(id)) fail(`Criterio da spec sem resultado: ${id}`);
|
|
202
|
+
if (report.review?.status !== 'approved' || !nonempty(report.review.reviewer) || !Array.isArray(report.review.findings)) fail('Revisao independente pendente.');
|
|
203
|
+
if (report.review.findings.some(item => !nonempty(item.id) || !nonempty(item.description) || item.status !== 'resolved')) fail('Achados de revisao ainda abertos.');
|
|
204
|
+
const seal = {
|
|
205
|
+
reportHash: digest(join(root, reportPath)), baselineHash: digest(join(root, baselineFile)),
|
|
206
|
+
evidence: [...proofs].sort().map(path => ({ path, sha256: digest(join(root, path)) })),
|
|
207
|
+
};
|
|
208
|
+
if (requireSeal) {
|
|
209
|
+
if (!existsSync(join(root, prefix, 'seal.json'))) fail('Revisao nao selada; conclua a revisao independente e execute seal.');
|
|
210
|
+
const sealPath = artifact(root, `${prefix}/seal.json`, prefix, true);
|
|
211
|
+
const recorded = JSON.parse(readFileSync(join(root, sealPath), 'utf8'));
|
|
212
|
+
if (JSON.stringify(recorded) !== JSON.stringify(seal)) fail('Relatorio, baseline ou evidencia alterados apos selar a revisao.');
|
|
213
|
+
}
|
|
214
|
+
return { root, prefix, seal, result: { status: 'passed', report: reportPath, criteria: ids.size, documents: docs.size } };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function checkReview(file, { root = projectRoot } = {}) { return validateReview(file, { root, requireSeal: true }).result; }
|
|
218
|
+
|
|
219
|
+
export function sealReview(file, options) {
|
|
220
|
+
const checked = validateReview(file, { ...options, requireSeal: false });
|
|
221
|
+
const path = artifact(checked.root, `${checked.prefix}/seal.json`, checked.prefix, false);
|
|
222
|
+
if (existsSync(join(checked.root, path))) return checkReview(file, options);
|
|
223
|
+
writeFileSync(join(checked.root, path), JSON.stringify(checked.seal, null, 2) + '\n', { flag: 'wx' });
|
|
224
|
+
return checkReview(file, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const usage = 'Uso: revisar-entrega prepare --id <slug> [--execution <slug>] [--spec <arquivo>] [--scope <arquivo>] [--document <arquivo>] [--output <artefato>]; seal|check <revisao.json>';
|
|
228
|
+
export function main(args = process.argv.slice(2)) {
|
|
229
|
+
requireNode(); const [mode, ...rest] = args;
|
|
230
|
+
if (!mode || ['--help', '-h'].includes(mode)) { console.log(usage); return 0; }
|
|
231
|
+
if (mode === 'check' && rest.length === 1) { console.log(JSON.stringify(checkReview(rest[0]))); return 0; }
|
|
232
|
+
if (mode === 'seal' && rest.length === 1) { console.log(JSON.stringify(sealReview(rest[0]))); return 0; }
|
|
233
|
+
if (mode !== 'prepare') fail(usage);
|
|
234
|
+
const options = { scope: [], documents: [] };
|
|
235
|
+
const fields = { '--id': 'id', '--execution': 'execution', '--spec': 'spec', '--output': 'output' };
|
|
236
|
+
for (let i = 0; i < rest.length; i += 2) {
|
|
237
|
+
const value = rest[i + 1]; if (!value || value.startsWith('--')) fail(usage);
|
|
238
|
+
if (rest[i] === '--scope') options.scope.push(value);
|
|
239
|
+
else if (rest[i] === '--document') options.documents.push(value);
|
|
240
|
+
else if (fields[rest[i]]) { if (options[fields[rest[i]]] !== undefined) fail('Opcao duplicada.'); options[fields[rest[i]]] = value; }
|
|
241
|
+
else fail(usage);
|
|
242
|
+
}
|
|
243
|
+
console.log(JSON.stringify({ report: prepareReview(options).file, status: 'pending' })); return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (isMain(import.meta.url)) {
|
|
247
|
+
try { process.exitCode = main(); }
|
|
248
|
+
catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
249
|
+
}
|