@seedgrid/ai-toolkit 0.0.0-stage → 0.3.0
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/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/adapters/claude.js +114 -0
- package/dist/adapters/codex.js +108 -0
- package/dist/adapters/index.js +17 -0
- package/dist/adapters/shared.js +142 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli.js +203 -0
- package/dist/commands/common.js +98 -0
- package/dist/commands/doctor.js +290 -0
- package/dist/commands/init.js +9 -0
- package/dist/commands/install.js +107 -0
- package/dist/commands/remove.js +54 -0
- package/dist/commands/review.js +127 -0
- package/dist/commands/status.js +73 -0
- package/dist/commands/update.js +8 -0
- package/dist/commands/validate.js +27 -0
- package/dist/core/apply.js +181 -0
- package/dist/core/blocks.js +64 -0
- package/dist/core/content.js +528 -0
- package/dist/core/env.js +40 -0
- package/dist/core/frontmatter.js +29 -0
- package/dist/core/hash.js +14 -0
- package/dist/core/inspect.js +51 -0
- package/dist/core/lock.js +124 -0
- package/dist/core/paths.js +65 -0
- package/dist/core/plan.js +242 -0
- package/dist/core/tools.js +62 -0
- package/dist/core/types.js +11 -0
- package/dist/core/updates.js +121 -0
- package/docs/arquitetura.md +80 -0
- package/docs/compatibilidade.md +50 -0
- package/docs/formato.md +155 -0
- package/docs/instalacao.md +105 -0
- package/docs/manutencao.md +115 -0
- package/package.json +43 -4
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-enums.md +161 -0
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-propriedades.md +971 -0
- package/seedgridKB/SeedGrid/Extensions/Indice.md +63 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Configuracao.md +124 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Enums.md +40 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Exemplos.md +97 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao-Rust.md +69 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao.md +88 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Enums.md +33 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Exemplos.md +121 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Rust.md +126 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Visao-geral.md +111 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao-Rust.md +93 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao.md +127 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Enums.md +66 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Exemplos.md +151 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Rust.md +120 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Configuracao-Rust.md +136 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Configuracao.md +319 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Enums.md +131 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Exemplos.md +210 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Rust.md +135 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Visao-geral.md +150 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Configuracao-Rust.md +86 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Configuracao.md +243 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Enums.md +165 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Exemplos.md +205 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Rust.md +109 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Visao-geral.md +181 -0
- package/seedgridKB/SeedGrid/Extensions/broker/Configuracao-Rust.md +40 -0
- package/seedgridKB/SeedGrid/Extensions/broker/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Configuracao-Rust.md +73 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Configuracao.md +112 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Enums.md +36 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Exemplos.md +149 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Rust.md +81 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Visao-geral.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Configuracao.md +107 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Enums.md +105 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Exemplos.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Visao-geral.md +99 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Configuracao.md +122 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Enums.md +29 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Exemplos.md +82 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Visao-geral.md +118 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao-Rust.md +110 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao.md +157 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Exemplos.md +157 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Rust.md +70 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Visao-geral.md +88 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao-Rust.md +124 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao.md +195 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Enums.md +71 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Exemplos.md +149 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Rust.md +139 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Visao-geral.md +130 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Configuracao.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Enums.md +52 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Exemplos.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Visao-geral.md +113 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao-Rust.md +67 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao.md +94 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Enums.md +79 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Exemplos.md +172 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Rust.md +107 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Visao-geral.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao-Rust.md +72 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao.md +104 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Exemplos.md +104 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Rust.md +69 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Visao-geral.md +105 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao-Rust.md +68 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao.md +186 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Enums.md +96 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Exemplos.md +229 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Rust.md +80 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Visao-geral.md +112 -0
- package/seedgridKB/SeedGrid/Extensions/runtime/Configuracao-Rust.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/runtime/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/security/Configuracao-Rust.md +327 -0
- package/seedgridKB/SeedGrid/Extensions/security/Configuracao.md +510 -0
- package/seedgridKB/SeedGrid/Extensions/security/Enums.md +95 -0
- package/seedgridKB/SeedGrid/Extensions/security/Exemplos.md +218 -0
- package/seedgridKB/SeedGrid/Extensions/security/Rust.md +203 -0
- package/seedgridKB/SeedGrid/Extensions/security/Visao-geral.md +131 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust.md +188 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao.md +345 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Enums.md +117 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Exemplos.md +205 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Rust.md +161 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Visao-geral.md +142 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao-Rust.md +75 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao.md +152 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Enums.md +198 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Exemplos.md +173 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Rust.md +83 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Visao-geral.md +99 -0
- package/seedgridKB/SeedGrid/Mapa-dos-repositorios.md +112 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/Indice.md +323 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/API.md +257 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Exemplos.md +307 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Visao-geral.md +51 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/API.md +174 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Exemplos.md +236 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Visao-geral.md +76 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/API.md +339 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/Exemplos.md +311 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/Visao-geral.md +57 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/API.md +125 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Exemplos.md +116 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Visao-geral.md +86 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/API.md +490 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Exemplos.md +544 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Visao-geral.md +69 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/API.md +113 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Exemplos.md +159 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Visao-geral.md +92 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/API.md +516 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Exemplos.md +629 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Visao-geral.md +80 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/API.md +615 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Exemplos.md +384 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Visao-geral.md +111 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/API.md +661 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Exemplos.md +312 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Visao-geral.md +123 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/API.md +1090 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/Exemplos.md +1172 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/Visao-geral.md +98 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/Visao-geral.md +98 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-apikeys.md +118 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-audit.md +88 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-backup.md +131 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-platform.md +132 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-user.md +120 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-login-tenancy.md +119 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-security.md +427 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-self-enter.md +104 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-subdomain-tenancy.md +110 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/API.md +490 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Exemplos.md +561 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Visao-geral.md +82 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/Visao-geral.md +68 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-commons.md +61 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-core.md +140 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-playground.md +112 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-theme.md +97 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/API.md +177 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/Exemplos.md +332 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/Visao-geral.md +106 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/showcase.md +70 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/arquiteto.md +53 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/backend-java.md +45 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/backend-rust.md +43 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/frontend-react.md +41 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/qa.md +44 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/revisor.md +40 -0
- package/seedgridKB/SeedGrid/Toolkit/references/fluxo-de-trabalho.md +78 -0
- package/seedgridKB/SeedGrid/Toolkit/references/glossario.md +15 -0
- package/seedgridKB/SeedGrid/Toolkit/references/modos-shared-native.md +27 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/backend-java-seedgrid.md +54 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/coordenacao.md +39 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/formato-entrega.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/frontend-seedgrid.md +47 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/personalizacao-local.md +22 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/planejar-mudanca/SKILL.md +38 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/SKILL.md +33 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/references/checklist.md +25 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-backend-crud/SKILL.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-bump/SKILL.md +26 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/SKILL.md +221 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/agents/openai.yaml +4 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/checklist.md +31 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/patterns.md +185 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/routing.md +66 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/SKILL.md +56 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/agents/openai.yaml +4 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/checklist.md +32 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/steps.md +114 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-review/SKILL.md +61 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/validar-mudanca/SKILL.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/decisao-arquitetura.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/especificacao-tarefa.md +32 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/relatorio-validacao.md +30 -0
- package/seedgridKB/SeedGrid/Versoes.md +64 -0
- package/seedgridKB/SeedGrid/Visao-geral.md +35 -0
- package/seedgridKB/manifest.json +142 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ADAPTERS, buildDesiredState } from '../adapters/index.js';
|
|
3
|
+
import { ApplyError, applyPlan, newOperationId } from '../core/apply.js';
|
|
4
|
+
import { loadContent } from '../core/content.js';
|
|
5
|
+
import { bundledContentDir, cliVersion, resolveProject } from '../core/env.js';
|
|
6
|
+
import { readLock } from '../core/lock.js';
|
|
7
|
+
import { planInstall } from '../core/plan.js';
|
|
8
|
+
import { detectTool } from '../core/tools.js';
|
|
9
|
+
import { ToolkitError } from '../core/types.js';
|
|
10
|
+
import { conflictHelp, EXIT_CONFLICT, EXIT_ERROR, EXIT_OK, parseMode, parseTargets, planOperations, planSummary, renderPlan, sourceRelPath, } from './common.js';
|
|
11
|
+
/** Fluxo comum de init e update: carregar conteúdo, planejar, checar conflitos e aplicar. */
|
|
12
|
+
export async function runInstall(kind, opts) {
|
|
13
|
+
const projectRoot = resolveProject(opts.project);
|
|
14
|
+
const lockState = await readLock(projectRoot);
|
|
15
|
+
if (kind === 'update' && lockState.status === 'absent') {
|
|
16
|
+
throw new ToolkitError('Nenhuma instalação encontrada neste projeto. Use "seedgrid-ai init".');
|
|
17
|
+
}
|
|
18
|
+
const prev = lockState.status === 'ok' ? lockState.lock : null;
|
|
19
|
+
const targets = parseTargets(opts.target) ?? prev?.targets ?? ['claude', 'codex'];
|
|
20
|
+
const mode = parseMode(opts.mode) ?? prev?.mode ?? 'shared';
|
|
21
|
+
const sourceDir = opts.source ? path.resolve(opts.source) : bundledContentDir();
|
|
22
|
+
const bundle = await loadContent(sourceDir);
|
|
23
|
+
const source = opts.source
|
|
24
|
+
? { type: 'local', ...(sourceRelPath(projectRoot, sourceDir) ? { path: sourceRelPath(projectRoot, sourceDir) } : {}) }
|
|
25
|
+
: { type: 'bundled' };
|
|
26
|
+
const operationId = newOperationId();
|
|
27
|
+
const plan = await planInstall(projectRoot, buildDesiredState(bundle, targets, mode), lockState, {
|
|
28
|
+
kind,
|
|
29
|
+
cliVersion: cliVersion(),
|
|
30
|
+
contentVersion: bundle.contentVersion,
|
|
31
|
+
contentSchemaVersion: bundle.schemaVersion,
|
|
32
|
+
targets,
|
|
33
|
+
mode,
|
|
34
|
+
source,
|
|
35
|
+
operationId,
|
|
36
|
+
now: new Date().toISOString(),
|
|
37
|
+
});
|
|
38
|
+
if (kind === 'update' && prev?.source.type === 'local' && !opts.source) {
|
|
39
|
+
plan.warnings.push('A instalação anterior usou uma fonte local; este update usa o conteúdo empacotado no CLI.');
|
|
40
|
+
}
|
|
41
|
+
if (mode === 'native') {
|
|
42
|
+
for (const t of targets) {
|
|
43
|
+
const info = await detectTool(ADAPTERS[t]);
|
|
44
|
+
if (!info.installed) {
|
|
45
|
+
plan.warnings.push(`${info.displayName} não encontrado: arquivos native gerados, mas a descoberta real não foi testada.`);
|
|
46
|
+
}
|
|
47
|
+
else if (!info.validated) {
|
|
48
|
+
plan.warnings.push(`${info.displayName} ${info.version ?? '?'} não está entre as versões validadas (${info.validatedVersions.join(', ')}); confirme a descoberta dos agentes.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const report = {
|
|
53
|
+
command: kind,
|
|
54
|
+
dryRun: Boolean(opts.dryRun),
|
|
55
|
+
project: projectRoot,
|
|
56
|
+
targets,
|
|
57
|
+
mode,
|
|
58
|
+
contentVersion: bundle.contentVersion,
|
|
59
|
+
source,
|
|
60
|
+
summary: planSummary(plan),
|
|
61
|
+
operations: planOperations(plan),
|
|
62
|
+
conflicts: plan.conflicts,
|
|
63
|
+
warnings: plan.warnings,
|
|
64
|
+
};
|
|
65
|
+
const header = `${kind} (${opts.dryRun ? 'simulação' : 'execução'}) em ${projectRoot}`;
|
|
66
|
+
const lines = [header, `targets: ${targets.join(', ')} | modo: ${mode} | conteúdo: ${bundle.contentVersion}`, '', ...renderPlan(plan)];
|
|
67
|
+
if (plan.conflicts.length > 0) {
|
|
68
|
+
return { exitCode: EXIT_CONFLICT, report: { ...report, applied: false }, lines: [...lines, ...conflictHelp()] };
|
|
69
|
+
}
|
|
70
|
+
if (opts.dryRun) {
|
|
71
|
+
return { exitCode: EXIT_OK, report: { ...report, applied: false }, lines: [...lines, '', 'Simulação: nenhum arquivo foi escrito.'] };
|
|
72
|
+
}
|
|
73
|
+
if (plan.changes === 0) {
|
|
74
|
+
return { exitCode: EXIT_OK, report: { ...report, applied: false }, lines: [...lines, '', 'Nada a fazer: a instalação já está atualizada.'] };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const result = await applyPlan(plan, operationId, opts.io);
|
|
78
|
+
return {
|
|
79
|
+
exitCode: EXIT_OK,
|
|
80
|
+
report: { ...report, applied: true, operationId, result },
|
|
81
|
+
lines: [
|
|
82
|
+
...lines,
|
|
83
|
+
'',
|
|
84
|
+
`Concluído (operação ${operationId}).`,
|
|
85
|
+
...(result.backupDir ? [`Backup dos arquivos alterados: ${result.backupDir}/`] : []),
|
|
86
|
+
'Abra uma nova sessão do Claude Code ou do Codex para carregar as mudanças.',
|
|
87
|
+
'Verifique com: seedgrid-ai doctor',
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof ApplyError) {
|
|
93
|
+
return {
|
|
94
|
+
exitCode: EXIT_ERROR,
|
|
95
|
+
report: { ...report, applied: false, error: err.message, rollback: err.rollback, backupDir: err.backupDir },
|
|
96
|
+
lines: [
|
|
97
|
+
...lines,
|
|
98
|
+
'',
|
|
99
|
+
`ERRO: ${err.message}`,
|
|
100
|
+
...err.rollback.errors.map((e) => ` rollback falhou: ${e}`),
|
|
101
|
+
...(err.backupDir ? [`Backup: ${err.backupDir}/ (veja docs/instalacao.md, "Recuperação")`] : []),
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ApplyError, applyPlan, newOperationId } from '../core/apply.js';
|
|
2
|
+
import { resolveProject } from '../core/env.js';
|
|
3
|
+
import { readLock } from '../core/lock.js';
|
|
4
|
+
import { planRemove } from '../core/plan.js';
|
|
5
|
+
import { conflictHelp, EXIT_CONFLICT, EXIT_ERROR, EXIT_OK, planOperations, planSummary, renderPlan, } from './common.js';
|
|
6
|
+
/**
|
|
7
|
+
* Remove apenas arquivos gerenciados intactos e blocos intactos. Havendo
|
|
8
|
+
* customizações, aborta sem alterar nada. Pastas com conteúdo de terceiros
|
|
9
|
+
* permanecem; .ai/local/ e .ai/backups/ nunca são apagados.
|
|
10
|
+
*/
|
|
11
|
+
export async function removeCommand(opts) {
|
|
12
|
+
const projectRoot = resolveProject(opts.project);
|
|
13
|
+
const plan = await planRemove(projectRoot, await readLock(projectRoot));
|
|
14
|
+
const report = {
|
|
15
|
+
command: 'remove',
|
|
16
|
+
dryRun: Boolean(opts.dryRun),
|
|
17
|
+
project: projectRoot,
|
|
18
|
+
summary: planSummary(plan),
|
|
19
|
+
operations: planOperations(plan),
|
|
20
|
+
conflicts: plan.conflicts,
|
|
21
|
+
warnings: plan.warnings,
|
|
22
|
+
};
|
|
23
|
+
const lines = [`remove (${opts.dryRun ? 'simulação' : 'execução'}) em ${projectRoot}`, '', ...renderPlan(plan)];
|
|
24
|
+
if (plan.conflicts.length > 0) {
|
|
25
|
+
return { exitCode: EXIT_CONFLICT, report: { ...report, applied: false }, lines: [...lines, ...conflictHelp()] };
|
|
26
|
+
}
|
|
27
|
+
if (opts.dryRun) {
|
|
28
|
+
return { exitCode: EXIT_OK, report: { ...report, applied: false }, lines: [...lines, '', 'Simulação: nenhum arquivo foi removido.'] };
|
|
29
|
+
}
|
|
30
|
+
const operationId = newOperationId();
|
|
31
|
+
try {
|
|
32
|
+
const result = await applyPlan(plan, operationId, opts.io);
|
|
33
|
+
return {
|
|
34
|
+
exitCode: EXIT_OK,
|
|
35
|
+
report: { ...report, applied: true, operationId, result },
|
|
36
|
+
lines: [
|
|
37
|
+
...lines,
|
|
38
|
+
'',
|
|
39
|
+
`Removido (operação ${operationId}).`,
|
|
40
|
+
...(result.backupDir ? [`Backup: ${result.backupDir}/ (apague .ai/backups/ quando não precisar mais)`] : []),
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (err instanceof ApplyError) {
|
|
46
|
+
return {
|
|
47
|
+
exitCode: EXIT_ERROR,
|
|
48
|
+
report: { ...report, applied: false, error: err.message, rollback: err.rollback },
|
|
49
|
+
lines: [...lines, '', `ERRO: ${err.message}`, ...err.rollback.errors.map((e) => ` rollback falhou: ${e}`)],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ADAPTERS } from '../adapters/index.js';
|
|
4
|
+
import { resolveProject } from '../core/env.js';
|
|
5
|
+
import { readLock } from '../core/lock.js';
|
|
6
|
+
import { detectTool, runReviewCommand } from '../core/tools.js';
|
|
7
|
+
import { ALL_TARGETS, ToolkitError } from '../core/types.js';
|
|
8
|
+
import { EXIT_ERROR, EXIT_OK, EXIT_VIOLATIONS } from './common.js';
|
|
9
|
+
export const REVIEW_SKILL = 'seedgrid-review';
|
|
10
|
+
export const REVIEW_SCOPES = ['front', 'back', 'both'];
|
|
11
|
+
const SCOPE_TEXT = { front: 'front', back: 'back', both: 'front e back' };
|
|
12
|
+
export function parseScope(value) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return 'both';
|
|
15
|
+
if (REVIEW_SCOPES.includes(value))
|
|
16
|
+
return value;
|
|
17
|
+
throw new ToolkitError(`Escopo inválido: ${value} (use front, back ou nenhum para os dois)`);
|
|
18
|
+
}
|
|
19
|
+
/** Prompt fixo: só enums e texto do toolkit, sem entrada livre do usuário (vai para um comando de shell). */
|
|
20
|
+
export function reviewPrompt(scope, full) {
|
|
21
|
+
const target = full ? 'o projeto inteiro' : 'os arquivos alterados (diff)';
|
|
22
|
+
return `Use a skill ${REVIEW_SKILL} para revisar ${target} no escopo ${SCOPE_TEXT[scope]}. Siga a skill e termine com o bloco seedgrid-review-json.`;
|
|
23
|
+
}
|
|
24
|
+
/** Extrai o último bloco seedgrid-review-json da saída do modelo; null se ausente ou inválido. */
|
|
25
|
+
export function parseReviewSummary(output) {
|
|
26
|
+
const blocks = [...output.matchAll(/```seedgrid-review-json\s*\n([\s\S]*?)```/g)];
|
|
27
|
+
const last = blocks.at(-1)?.[1];
|
|
28
|
+
if (last === undefined)
|
|
29
|
+
return null;
|
|
30
|
+
let data;
|
|
31
|
+
try {
|
|
32
|
+
data = JSON.parse(last);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (data === null || typeof data !== 'object')
|
|
38
|
+
return null;
|
|
39
|
+
const d = data;
|
|
40
|
+
const num = (v) => (typeof v === 'number' && Number.isInteger(v) && v >= 0 ? v : null);
|
|
41
|
+
const violations = num(d.violations);
|
|
42
|
+
const blocking = num(d.blocking);
|
|
43
|
+
if (violations === null || blocking === null || blocking > violations)
|
|
44
|
+
return null;
|
|
45
|
+
return {
|
|
46
|
+
scope: typeof d.scope === 'string' ? d.scope : 'both',
|
|
47
|
+
violations,
|
|
48
|
+
blocking,
|
|
49
|
+
notVerified: num(d.notVerified) ?? 0,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function skillInstalled(projectRoot, target) {
|
|
53
|
+
const dir = ADAPTERS[target].skillsDir;
|
|
54
|
+
try {
|
|
55
|
+
return (await stat(path.join(projectRoot, ...dir.split('/'), REVIEW_SKILL, 'SKILL.md'))).isFile();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Revisa o código do projeto contra os padrões SeedGrid chamando, em modo somente leitura,
|
|
63
|
+
* a skill seedgrid-review na ferramenta de IA instalada (Claude Code ou Codex).
|
|
64
|
+
* Usa o login do usuário na ferramenta e consome tokens; nenhum outro comando chama o modelo.
|
|
65
|
+
*/
|
|
66
|
+
export async function reviewCommand(opts) {
|
|
67
|
+
const projectRoot = resolveProject(opts.project);
|
|
68
|
+
const scope = parseScope(opts.scope);
|
|
69
|
+
const lockState = await readLock(projectRoot);
|
|
70
|
+
if (lockState.status === 'absent') {
|
|
71
|
+
throw new ToolkitError('O SeedGrid AI Toolkit não está instalado neste projeto.', ['Instale com: seedgrid-ai init']);
|
|
72
|
+
}
|
|
73
|
+
if (lockState.status === 'invalid') {
|
|
74
|
+
throw new ToolkitError('O lock .ai/toolkit-lock.json é inválido.', lockState.errors);
|
|
75
|
+
}
|
|
76
|
+
const wanted = opts.tool;
|
|
77
|
+
if (wanted !== undefined && !ALL_TARGETS.includes(wanted)) {
|
|
78
|
+
throw new ToolkitError(`--tool inválido: ${wanted} (use claude ou codex)`);
|
|
79
|
+
}
|
|
80
|
+
const order = wanted ? [wanted] : ['claude', 'codex'];
|
|
81
|
+
const candidates = order.filter((t) => lockState.lock.targets.includes(t));
|
|
82
|
+
if (candidates.length === 0) {
|
|
83
|
+
throw new ToolkitError(`A ferramenta pedida não está entre os targets instalados (${lockState.lock.targets.join(', ')}).`);
|
|
84
|
+
}
|
|
85
|
+
let chosen = null;
|
|
86
|
+
const skipped = [];
|
|
87
|
+
for (const t of candidates) {
|
|
88
|
+
if (!(await skillInstalled(projectRoot, t))) {
|
|
89
|
+
skipped.push(`${ADAPTERS[t].displayName}: skill ${REVIEW_SKILL} não instalada (rode seedgrid-ai update)`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!opts.runner && !(await detectTool(ADAPTERS[t])).installed) {
|
|
93
|
+
skipped.push(`${ADAPTERS[t].displayName}: ferramenta não encontrada no PATH`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
chosen = t;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
if (!chosen)
|
|
100
|
+
throw new ToolkitError('Nenhuma ferramenta disponível para a revisão.', skipped);
|
|
101
|
+
const adapter = ADAPTERS[chosen];
|
|
102
|
+
const command = adapter.reviewCommand(reviewPrompt(scope, opts.full ?? false));
|
|
103
|
+
const run = await (opts.runner ?? runReviewCommand)(command, projectRoot);
|
|
104
|
+
const summary = run.timedOut || run.code !== 0 ? null : parseReviewSummary(run.stdout);
|
|
105
|
+
const base = { command: 'review', project: projectRoot, tool: chosen, scope, full: opts.full ?? false };
|
|
106
|
+
if (run.timedOut || run.code !== 0 || !summary) {
|
|
107
|
+
const reason = run.timedOut
|
|
108
|
+
? 'a ferramenta não terminou no tempo limite'
|
|
109
|
+
: run.code !== 0
|
|
110
|
+
? `a ferramenta terminou com erro (código ${run.code ?? 'desconhecido'})`
|
|
111
|
+
: 'a resposta não trouxe o bloco seedgrid-review-json válido; o resultado abaixo não pôde ser contado';
|
|
112
|
+
return {
|
|
113
|
+
exitCode: EXIT_ERROR,
|
|
114
|
+
report: { ...base, ok: false, reason, output: run.stdout, stderr: run.stderr },
|
|
115
|
+
lines: [`Revisão não concluída: ${reason}.`, '', run.stdout.trim() || run.stderr.trim() || '(sem saída)'],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
exitCode: summary.violations > 0 ? EXIT_VIOLATIONS : EXIT_OK,
|
|
120
|
+
report: { ...base, ok: true, ...summary, output: run.stdout },
|
|
121
|
+
lines: [
|
|
122
|
+
run.stdout.trim(),
|
|
123
|
+
'',
|
|
124
|
+
`Revisão por ${adapter.displayName} (${SCOPE_TEXT[scope]}): ${summary.violations} violação(ões), ${summary.blocking} bloqueante(s), ${summary.notVerified} item(ns) não verificado(s).`,
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { loadContent } from '../core/content.js';
|
|
2
|
+
import { bundledContentDir, cliVersion, resolveProject } from '../core/env.js';
|
|
3
|
+
import { inspectInstallation } from '../core/inspect.js';
|
|
4
|
+
import { readLock } from '../core/lock.js';
|
|
5
|
+
import { EXIT_ERROR, EXIT_OK } from './common.js';
|
|
6
|
+
/** Resumo da instalação: versões, opções e divergências em relação ao lock. Somente leitura. */
|
|
7
|
+
export async function statusCommand(opts) {
|
|
8
|
+
const projectRoot = resolveProject(opts.project);
|
|
9
|
+
const lockState = await readLock(projectRoot);
|
|
10
|
+
let bundledVersion = null;
|
|
11
|
+
try {
|
|
12
|
+
bundledVersion = (await loadContent(bundledContentDir())).contentVersion;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
bundledVersion = null;
|
|
16
|
+
}
|
|
17
|
+
const base = { command: 'status', project: projectRoot, cliVersion: cliVersion(), bundledContentVersion: bundledVersion };
|
|
18
|
+
if (lockState.status === 'absent') {
|
|
19
|
+
return {
|
|
20
|
+
exitCode: EXIT_OK,
|
|
21
|
+
report: { ...base, installed: false },
|
|
22
|
+
lines: [`SeedGrid AI Toolkit não instalado em ${projectRoot}.`, 'Instale com: seedgrid-ai init --target both --mode shared'],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (lockState.status === 'invalid') {
|
|
26
|
+
return {
|
|
27
|
+
exitCode: EXIT_ERROR,
|
|
28
|
+
report: { ...base, installed: true, lockValid: false, lockErrors: lockState.errors },
|
|
29
|
+
lines: ['O lock .ai/toolkit-lock.json é inválido:', ...lockState.errors.map((e) => ` - ${e}`)],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const lock = lockState.lock;
|
|
33
|
+
const insp = await inspectInstallation(projectRoot, lock);
|
|
34
|
+
const count = (s) => insp.files.filter((f) => f.state === s).length;
|
|
35
|
+
const divergent = [...insp.files.filter((f) => f.state !== 'ok'), ...insp.blocks.filter((b) => b.state !== 'ok')];
|
|
36
|
+
const updateAvailable = bundledVersion !== null && bundledVersion !== lock.contentVersion;
|
|
37
|
+
const lines = [
|
|
38
|
+
`SeedGrid AI Toolkit em ${projectRoot}`,
|
|
39
|
+
` conteúdo instalado: ${lock.contentVersion} (CLI ${lock.cliVersion}, fonte ${lock.source.type}${lock.source.path ? `: ${lock.source.path}` : ''})`,
|
|
40
|
+
` targets: ${lock.targets.join(', ')} | modo: ${lock.mode}`,
|
|
41
|
+
` última operação: ${lock.lastOperation.kind} ${lock.lastOperation.id}`,
|
|
42
|
+
` arquivos gerenciados: ${insp.files.length} (ok ${count('ok')}, modificados ${count('modificado')}, ausentes ${count('ausente')}, inseguros ${count('inseguro')})`,
|
|
43
|
+
` blocos: ${insp.blocks.map((b) => `${b.file} ${b.state}`).join(', ')}`,
|
|
44
|
+
];
|
|
45
|
+
if (divergent.length > 0) {
|
|
46
|
+
lines.push('', 'Divergências:');
|
|
47
|
+
for (const f of insp.files.filter((x) => x.state !== 'ok'))
|
|
48
|
+
lines.push(` ${f.state.padEnd(10)} ${f.path}${f.detail ? ` (${f.detail})` : ''}`);
|
|
49
|
+
for (const b of insp.blocks.filter((x) => x.state !== 'ok'))
|
|
50
|
+
lines.push(` bloco ${b.state.padEnd(10)} ${b.file}`);
|
|
51
|
+
}
|
|
52
|
+
if (updateAvailable) {
|
|
53
|
+
lines.push('', `Conteúdo ${bundledVersion} disponível no CLI ${cliVersion()}. Simule com: seedgrid-ai update --dry-run`);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
exitCode: EXIT_OK,
|
|
57
|
+
report: {
|
|
58
|
+
...base,
|
|
59
|
+
installed: true,
|
|
60
|
+
lockValid: true,
|
|
61
|
+
contentVersion: lock.contentVersion,
|
|
62
|
+
installedWithCli: lock.cliVersion,
|
|
63
|
+
targets: lock.targets,
|
|
64
|
+
mode: lock.mode,
|
|
65
|
+
source: lock.source,
|
|
66
|
+
lastOperation: lock.lastOperation,
|
|
67
|
+
updateAvailable,
|
|
68
|
+
files: insp.files,
|
|
69
|
+
blocks: insp.blocks,
|
|
70
|
+
},
|
|
71
|
+
lines,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { loadContent } from '../core/content.js';
|
|
3
|
+
import { bundledContentDir } from '../core/env.js';
|
|
4
|
+
import { EXIT_OK } from './common.js';
|
|
5
|
+
/** Valida uma fonte de conteúdo (manifesto, schema, nomes e referências) sem tocar em projetos. */
|
|
6
|
+
export async function validateCommand(opts) {
|
|
7
|
+
const dir = opts.source ? path.resolve(opts.source) : bundledContentDir();
|
|
8
|
+
const bundle = await loadContent(dir); // lança ToolkitError com todos os problemas
|
|
9
|
+
const files = bundle.shared.length + bundle.skills.reduce((n, s) => n + s.files.length, 0);
|
|
10
|
+
return {
|
|
11
|
+
exitCode: EXIT_OK,
|
|
12
|
+
report: {
|
|
13
|
+
command: 'validate',
|
|
14
|
+
source: dir,
|
|
15
|
+
contentVersion: bundle.contentVersion,
|
|
16
|
+
agents: bundle.agents.map((a) => a.id),
|
|
17
|
+
brainNotes: bundle.brain?.notes ?? 0,
|
|
18
|
+
skills: bundle.skills.map((s) => s.name),
|
|
19
|
+
files,
|
|
20
|
+
valid: true,
|
|
21
|
+
},
|
|
22
|
+
lines: [
|
|
23
|
+
`Conteúdo válido: ${dir}`,
|
|
24
|
+
` versão ${bundle.contentVersion}; ${bundle.agents.length} papéis, ${bundle.skills.length} skills, ${bundle.brain?.notes ?? 0} notas da KB, ${files} arquivos`,
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import * as fsp from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { sha256 } from './hash.js';
|
|
5
|
+
import { LOCK_PATH } from './lock.js';
|
|
6
|
+
import { resolveInProject } from './paths.js';
|
|
7
|
+
import { ToolkitError } from './types.js';
|
|
8
|
+
export const BACKUP_ROOT = '.ai/backups';
|
|
9
|
+
export const nodeIo = {
|
|
10
|
+
readFile: (p) => fsp.readFile(p),
|
|
11
|
+
writeFile: (p, d) => fsp.writeFile(p, d),
|
|
12
|
+
rename: (a, b) => fsp.rename(a, b),
|
|
13
|
+
rm: (p) => fsp.rm(p, { force: true }),
|
|
14
|
+
mkdir: async (p) => {
|
|
15
|
+
await fsp.mkdir(p, { recursive: true });
|
|
16
|
+
},
|
|
17
|
+
rmdirIfEmpty: async (p) => {
|
|
18
|
+
try {
|
|
19
|
+
if ((await fsp.readdir(p)).length > 0)
|
|
20
|
+
return false;
|
|
21
|
+
await fsp.rmdir(p);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
exists: async (p) => {
|
|
29
|
+
try {
|
|
30
|
+
await fsp.lstat(p);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export function newOperationId(now = new Date()) {
|
|
39
|
+
const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
|
|
40
|
+
return `${stamp}-${randomBytes(3).toString('hex')}`;
|
|
41
|
+
}
|
|
42
|
+
export class ApplyError extends ToolkitError {
|
|
43
|
+
rollback;
|
|
44
|
+
backupDir;
|
|
45
|
+
constructor(message, rollback, backupDir) {
|
|
46
|
+
super(message, rollback.errors);
|
|
47
|
+
this.rollback = rollback;
|
|
48
|
+
this.backupDir = backupDir;
|
|
49
|
+
this.name = 'ApplyError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function stepsOf(plan) {
|
|
53
|
+
const steps = [];
|
|
54
|
+
for (const op of plan.fileOps) {
|
|
55
|
+
if (op.action === 'create' || op.action === 'update') {
|
|
56
|
+
steps.push({ rel: op.path, kind: 'write', content: op.content, expectedHash: op.expectedHash });
|
|
57
|
+
}
|
|
58
|
+
else if (op.action === 'delete') {
|
|
59
|
+
steps.push({ rel: op.path, kind: 'delete', expectedHash: op.expectedHash });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const op of plan.blockOps) {
|
|
63
|
+
if (op.action === 'delete-file')
|
|
64
|
+
steps.push({ rel: op.file, kind: 'delete', expectedHash: op.expectedHash });
|
|
65
|
+
else if (op.newText !== undefined && op.action !== 'unchanged' && op.action !== 'forget') {
|
|
66
|
+
steps.push({ rel: op.file, kind: 'write', content: op.newText, expectedHash: op.expectedHash });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// O lock é sempre o último passo: se algo falhar antes, o lock antigo continua válido.
|
|
70
|
+
if (plan.lockOp.action === 'write') {
|
|
71
|
+
steps.push({ rel: LOCK_PATH, kind: 'write', content: plan.lockOp.content, expectedHash: plan.lockOp.expectedHash });
|
|
72
|
+
}
|
|
73
|
+
else if (plan.lockOp.action === 'delete') {
|
|
74
|
+
steps.push({ rel: LOCK_PATH, kind: 'delete', expectedHash: plan.lockOp.expectedHash });
|
|
75
|
+
}
|
|
76
|
+
return steps;
|
|
77
|
+
}
|
|
78
|
+
async function currentHash(io, abs) {
|
|
79
|
+
if (!(await io.exists(abs)))
|
|
80
|
+
return null;
|
|
81
|
+
return sha256((await io.readFile(abs)).toString('utf8'));
|
|
82
|
+
}
|
|
83
|
+
async function writeSafely(io, abs, content, opId) {
|
|
84
|
+
await io.mkdir(path.dirname(abs));
|
|
85
|
+
const tmp = `${abs}.seedgrid-tmp-${opId}`;
|
|
86
|
+
try {
|
|
87
|
+
await io.writeFile(tmp, content);
|
|
88
|
+
await io.rename(tmp, abs); // substituição por renomeação: atômica por arquivo onde o sistema suporta
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
await io.rm(tmp).catch(() => undefined);
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Aplica um plano sem conflitos. Faz backup do que será alterado, escreve via
|
|
97
|
+
* arquivo temporário + renomeação e tenta desfazer tudo se algum passo falhar.
|
|
98
|
+
* Não há atomicidade global entre vários arquivos.
|
|
99
|
+
*/
|
|
100
|
+
export async function applyPlan(plan, operationId, io = nodeIo) {
|
|
101
|
+
if (plan.conflicts.length > 0)
|
|
102
|
+
throw new ToolkitError('O plano tem conflitos; nada foi alterado.');
|
|
103
|
+
const root = plan.projectRoot;
|
|
104
|
+
const steps = stepsOf(plan);
|
|
105
|
+
const result = { operationId, backupDir: null, written: [], deleted: [], prunedDirs: [] };
|
|
106
|
+
if (steps.length === 0)
|
|
107
|
+
return result;
|
|
108
|
+
// 1. Revalida: o disco não pode ter mudado entre o planejamento e a escrita.
|
|
109
|
+
for (const s of steps) {
|
|
110
|
+
const h = await currentHash(io, resolveInProject(root, s.rel));
|
|
111
|
+
if (h !== s.expectedHash) {
|
|
112
|
+
throw new ToolkitError(`${s.rel} mudou depois do planejamento; nada foi alterado. Execute o comando novamente.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// 2. Backup dos arquivos existentes que serão modificados ou apagados.
|
|
116
|
+
const existing = steps.filter((s) => s.expectedHash !== null);
|
|
117
|
+
const backupRel = `${BACKUP_ROOT}/${operationId}`;
|
|
118
|
+
const backupAbs = resolveInProject(root, backupRel);
|
|
119
|
+
if (existing.length > 0) {
|
|
120
|
+
const ignoreFile = resolveInProject(root, `${BACKUP_ROOT}/.gitignore`);
|
|
121
|
+
await io.mkdir(backupAbs);
|
|
122
|
+
if (!(await io.exists(ignoreFile)))
|
|
123
|
+
await io.writeFile(ignoreFile, '# Backups locais do seedgrid-ai\n*\n');
|
|
124
|
+
for (const s of existing) {
|
|
125
|
+
const dest = path.join(backupAbs, ...s.rel.split('/'));
|
|
126
|
+
await io.mkdir(path.dirname(dest));
|
|
127
|
+
await io.writeFile(dest, await io.readFile(resolveInProject(root, s.rel)));
|
|
128
|
+
}
|
|
129
|
+
await io.writeFile(path.join(backupAbs, 'operation.json'), `${JSON.stringify({ operationId, kind: plan.kind, files: existing.map((s) => s.rel) }, null, 2)}\n`);
|
|
130
|
+
result.backupDir = backupRel;
|
|
131
|
+
}
|
|
132
|
+
// 3. Executa, registrando como desfazer cada passo.
|
|
133
|
+
const undo = [];
|
|
134
|
+
try {
|
|
135
|
+
for (const s of steps) {
|
|
136
|
+
const abs = resolveInProject(root, s.rel);
|
|
137
|
+
undo.push({ rel: s.rel, hadFile: s.expectedHash !== null });
|
|
138
|
+
if (s.kind === 'write') {
|
|
139
|
+
await writeSafely(io, abs, s.content, operationId);
|
|
140
|
+
result.written.push(s.rel);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
await io.rm(abs);
|
|
144
|
+
result.deleted.push(s.rel);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
const rollback = { complete: true, errors: [], restored: [] };
|
|
150
|
+
for (const u of undo.reverse()) {
|
|
151
|
+
const abs = resolveInProject(root, u.rel);
|
|
152
|
+
try {
|
|
153
|
+
if (u.hadFile) {
|
|
154
|
+
const data = await io.readFile(path.join(backupAbs, ...u.rel.split('/')));
|
|
155
|
+
await writeSafely(io, abs, data.toString('utf8'), `${operationId}-rb`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
await io.rm(abs);
|
|
159
|
+
}
|
|
160
|
+
result.written = result.written.filter((w) => w !== u.rel);
|
|
161
|
+
rollback.restored.push(u.rel);
|
|
162
|
+
}
|
|
163
|
+
catch (rbErr) {
|
|
164
|
+
rollback.complete = false;
|
|
165
|
+
rollback.errors.push(`${u.rel}: ${rbErr.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
throw new ApplyError(`Falha ao escrever: ${err.message}. ${rollback.complete ? 'Rollback concluído.' : 'Rollback INCOMPLETO; restaure manualmente a partir do backup.'}`, rollback, result.backupDir);
|
|
169
|
+
}
|
|
170
|
+
// 4. Remove pastas que ficaram vazias, sem subir até a raiz do projeto.
|
|
171
|
+
for (const rel of result.deleted) {
|
|
172
|
+
let dir = path.posix.dirname(rel);
|
|
173
|
+
while (dir !== '.' && dir !== BACKUP_ROOT && dir !== '.ai/backups') {
|
|
174
|
+
if (!(await io.rmdirIfEmpty(resolveInProject(root, dir))))
|
|
175
|
+
break;
|
|
176
|
+
result.prunedDirs.push(dir);
|
|
177
|
+
dir = path.posix.dirname(dir);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { normalizeText } from './hash.js';
|
|
2
|
+
export const BLOCK_BEGIN = '<!-- BEGIN SEEDGRID AI TOOLKIT -->';
|
|
3
|
+
export const BLOCK_END = '<!-- END SEEDGRID AI TOOLKIT -->';
|
|
4
|
+
function count(text, needle) {
|
|
5
|
+
let n = 0;
|
|
6
|
+
for (let i = text.indexOf(needle); i >= 0; i = text.indexOf(needle, i + needle.length))
|
|
7
|
+
n++;
|
|
8
|
+
return n;
|
|
9
|
+
}
|
|
10
|
+
/** Localiza o bloco gerenciado. start/end delimitam o bloco, marcadores inclusive. */
|
|
11
|
+
export function scanBlock(text) {
|
|
12
|
+
const begins = count(text, BLOCK_BEGIN);
|
|
13
|
+
const ends = count(text, BLOCK_END);
|
|
14
|
+
if (begins === 0 && ends === 0)
|
|
15
|
+
return { status: 'none' };
|
|
16
|
+
if (begins !== 1 || ends !== 1) {
|
|
17
|
+
return { status: 'invalid', reason: `marcadores duplicados ou incompletos (BEGIN=${begins}, END=${ends})` };
|
|
18
|
+
}
|
|
19
|
+
const start = text.indexOf(BLOCK_BEGIN);
|
|
20
|
+
const endMarker = text.indexOf(BLOCK_END);
|
|
21
|
+
if (endMarker < start)
|
|
22
|
+
return { status: 'invalid', reason: 'o marcador END aparece antes de BEGIN' };
|
|
23
|
+
const inner = text.slice(start + BLOCK_BEGIN.length, endMarker);
|
|
24
|
+
return { status: 'one', start, end: endMarker + BLOCK_END.length, inner: canonicalInner(inner) };
|
|
25
|
+
}
|
|
26
|
+
/** Forma canônica do conteúdo interno, usada no hash e nas comparações. */
|
|
27
|
+
export function canonicalInner(inner) {
|
|
28
|
+
return normalizeText(inner).replace(/^\n+/, '').replace(/\n+$/, '');
|
|
29
|
+
}
|
|
30
|
+
function detectEol(text) {
|
|
31
|
+
return text.includes('\r\n') ? '\r\n' : '\n';
|
|
32
|
+
}
|
|
33
|
+
export function renderBlock(inner, eol = '\n') {
|
|
34
|
+
const body = canonicalInner(inner).split('\n').join(eol);
|
|
35
|
+
return `${BLOCK_BEGIN}${eol}${body}${eol}${BLOCK_END}`;
|
|
36
|
+
}
|
|
37
|
+
/** Insere ou substitui o bloco, preservando todo o texto fora dos marcadores. */
|
|
38
|
+
export function upsertBlock(text, inner) {
|
|
39
|
+
if (text === null || text.length === 0)
|
|
40
|
+
return `${renderBlock(inner)}\n`;
|
|
41
|
+
const eol = detectEol(text);
|
|
42
|
+
const scan = scanBlock(text);
|
|
43
|
+
if (scan.status === 'invalid')
|
|
44
|
+
throw new Error(scan.reason);
|
|
45
|
+
if (scan.status === 'one') {
|
|
46
|
+
return text.slice(0, scan.start) + renderBlock(inner, eol) + text.slice(scan.end);
|
|
47
|
+
}
|
|
48
|
+
const sep = text.endsWith(eol) ? eol : eol + eol;
|
|
49
|
+
return `${text}${sep}${renderBlock(inner, eol)}${eol}`;
|
|
50
|
+
}
|
|
51
|
+
/** Remove o bloco e a quebra de linha que o acompanhava, preservando o restante. */
|
|
52
|
+
export function removeBlock(text) {
|
|
53
|
+
const scan = scanBlock(text);
|
|
54
|
+
if (scan.status !== 'one')
|
|
55
|
+
return text;
|
|
56
|
+
const eol = detectEol(text);
|
|
57
|
+
let before = text.slice(0, scan.start);
|
|
58
|
+
let after = text.slice(scan.end);
|
|
59
|
+
if (after.startsWith(eol))
|
|
60
|
+
after = after.slice(eol.length);
|
|
61
|
+
if ((after.length === 0 || after.startsWith(eol)) && before.endsWith(eol + eol))
|
|
62
|
+
before = before.slice(0, -eol.length);
|
|
63
|
+
return before + after;
|
|
64
|
+
}
|