@seedgrid/ai-toolkit 0.4.0 → 0.4.2
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/dist/adapters/shared.js +1 -0
- package/dist/core/content.js +5 -0
- package/docs/formato.md +1 -0
- package/package.json +1 -1
- package/seedgridKB/SeedGrid/Toolkit/skills/sg-adr/SKILL.md +5 -4
- package/seedgridKB/SeedGrid/Toolkit/skills/sg-adr/scripts/adr-index.mjs +42 -9
- package/seedgridKB/SeedGrid/Toolkit/templates/decisao-arquitetura.md +4 -1
- package/seedgridKB/manifest.json +1 -1
package/dist/adapters/shared.js
CHANGED
|
@@ -132,6 +132,7 @@ export function agentsMdBlock(bundle, targets, mode) {
|
|
|
132
132
|
'',
|
|
133
133
|
`Bloco gerenciado pelo \`seedgrid-ai\` (conteúdo ${bundle.contentVersion}, modo \`${mode}\`). Não edite entre os marcadores; personalize em \`.ai/local/\`.`,
|
|
134
134
|
'',
|
|
135
|
+
...(bundle.agentsNotes.length > 0 ? [...bundle.agentsNotes.map((n) => `> ${n}`), ''] : []),
|
|
135
136
|
'Cada pasta de `.ai/` tem um `index.md` com o que há nela: comece por ele e leia só o que a tarefa pedir.',
|
|
136
137
|
'',
|
|
137
138
|
'- Regras: `.ai/rules/`. Antes de tarefas não triviais, leia `coordenacao.md`.',
|
package/dist/core/content.js
CHANGED
|
@@ -510,6 +510,10 @@ export async function loadContent(root) {
|
|
|
510
510
|
checkBrain(ctx, brain.notes, brain.entrypoints, brain.base);
|
|
511
511
|
shared.push(...brain.notes);
|
|
512
512
|
}
|
|
513
|
+
const rawNotes = manifest.agentsNotes ?? [];
|
|
514
|
+
if (!Array.isArray(rawNotes) || rawNotes.length > 10 || rawNotes.some((n) => typeof n !== 'string' || n.trim() === '' || n.length > 600 || n.includes('\n'))) {
|
|
515
|
+
ctx.errors.push('manifest: agentsNotes deve ser uma lista de até 10 linhas de texto (até 600 caracteres, sem quebra de linha)');
|
|
516
|
+
}
|
|
513
517
|
if (ctx.errors.length > 0) {
|
|
514
518
|
throw new ToolkitError(`Conteúdo inválido em ${absRoot}`, ctx.errors);
|
|
515
519
|
}
|
|
@@ -523,6 +527,7 @@ export async function loadContent(root) {
|
|
|
523
527
|
brain: brain
|
|
524
528
|
? { notes: brain.notes.length, entrypoints: brain.entrypoints.map((e) => `.ai/brain/${brain.base}/${e}`) }
|
|
525
529
|
: null,
|
|
530
|
+
agentsNotes: rawNotes,
|
|
526
531
|
sourceFiles: [...ctx.sources].sort(),
|
|
527
532
|
};
|
|
528
533
|
}
|
package/docs/formato.md
CHANGED
|
@@ -87,6 +87,7 @@ Arquivos e pastas que começam com ponto são ignorados.
|
|
|
87
87
|
| --- | --- |
|
|
88
88
|
| `schemaVersion` | `2` (versão do formato do manifesto) |
|
|
89
89
|
| `contentVersion` | semver; deve ser **igual** à versão do pacote (`package.json`); aumente a cada mudança de conteúdo |
|
|
90
|
+
| `agentsNotes` | (opcional) até 10 linhas curtas que o bloco do `AGENTS.md` mostra no topo, como aviso (ex.: o modo de trabalho que uma camada adicional exige) |
|
|
90
91
|
| `toolkitBase` | pasta do vault onde ficam `agents/`, `rules/`, `templates/`, `references/` e `skills/` (aqui, `SeedGrid/Toolkit`) |
|
|
91
92
|
| `components[].id` | único; para skills, `skill:<name>` |
|
|
92
93
|
| `components[].type` | `agents`, `rules`, `templates`, `references`, `skill` ou `brain` |
|
package/package.json
CHANGED
|
@@ -17,10 +17,11 @@ Crie uma ADR quando a decisão: muda um comportamento observável; cria, remove
|
|
|
17
17
|
|
|
18
18
|
1. **Listar as decisões.** Escreva cada uma em uma frase e **mostre ao usuário** para confirmar: você propõe, quem decide é o usuário. Ao registrar, repita a decisão em uma frase (decisão registrada errada só aparece quando alguém relê).
|
|
19
19
|
2. **Domínio.** Escolha na lista `docs/ai/decisions/dominios.txt` (um domínio por linha). Se o arquivo não existir, proponha 5 a 10 domínios do projeto (por exemplo `seguranca`, `dados`, `api`, `configuracao`, `processo`), confirme e crie. Domínio novo só com confirmação do usuário.
|
|
20
|
-
3. **
|
|
21
|
-
4. **
|
|
22
|
-
5.
|
|
23
|
-
6.
|
|
20
|
+
3. **Número.** `node .ai/skills/sg-adr/scripts/adr-index.mjs --proximo` dá o próximo número livre; ele é sequencial, vai em `numero:` e **nunca muda** (dá para conversar sobre "a ADR #7"). Para ADRs antigas sem número, `--numerar` numera só as que não têm.
|
|
21
|
+
4. **Criar o arquivo** `docs/ai/decisions/AAAA-MM-DD-slug.md` a partir do modelo `.ai/templates/decisao-arquitetura.md`: cabeçalho completo (`titulo`, `numero`, `data`, `status`, `decidido-por`, `consultados`, `dominio`, `modulos`, `origem`) e as seções Contexto, Decisão, Alternativas e Consequências. Comece com `status: proposta` e mude para `aceita` quando o usuário confirmar.
|
|
22
|
+
5. **Substituição.** Se a decisão troca outra, não edite a antiga: crie a nova com `substitui: [arquivo-antigo.md]` e apenas marque a antiga como `status: substituida` e `substituida-por`.
|
|
23
|
+
6. **Índice.** Rode `node .ai/skills/sg-adr/scripts/adr-index.mjs`. Ele valida o cabeçalho e gera `docs/ai/decisions/INDEX.md` em ordem decrescente de data. Nunca edite o `INDEX.md` à mão; use `--check` para conferir se está em dia.
|
|
24
|
+
7. **Entregar** listando as ADRs criadas e o que ficou como `proposta` (o que o dono confirmou e já está no código é `implementada`).
|
|
24
25
|
|
|
25
26
|
## Consultar
|
|
26
27
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Gera docs/ai/decisions/INDEX.md a partir do cabeçalho das ADRs, da mais nova para a mais antiga.
|
|
3
3
|
// Uso: node .ai/skills/sg-adr/scripts/adr-index.mjs [--dir docs/ai/decisions] [--check]
|
|
4
|
-
// --check
|
|
4
|
+
// --check só valida e falha se o INDEX.md estiver desatualizado (sem escrever)
|
|
5
|
+
// --proximo imprime o próximo número livre para uma ADR nova
|
|
6
|
+
// --numerar dá número, em ordem de data e nome, SÓ às ADRs sem "numero"; nunca renumera
|
|
5
7
|
// Domínios: se existir <dir>/dominios.txt (um por linha), só esses são aceitos; senão, qualquer slug em minúsculas com hífen.
|
|
6
8
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
7
9
|
import path from 'node:path';
|
|
@@ -10,7 +12,7 @@ const args = process.argv.slice(2);
|
|
|
10
12
|
const di = args.indexOf('--dir');
|
|
11
13
|
const dir = path.resolve(di >= 0 && args[di + 1] ? args[di + 1] : 'docs/ai/decisions');
|
|
12
14
|
const check = args.includes('--check');
|
|
13
|
-
const STATUS = ['proposta', 'aceita', 'substituida', 'rejeitada'];
|
|
15
|
+
const STATUS = ['proposta', 'aceita', 'implementada', 'substituida', 'rejeitada'];
|
|
14
16
|
|
|
15
17
|
if (!existsSync(dir)) {
|
|
16
18
|
console.error(`Pasta não encontrada: ${dir}`);
|
|
@@ -35,9 +37,34 @@ function parse(text, file) {
|
|
|
35
37
|
return out;
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
const files = readdirSync(dir).filter((n) => n.endsWith('.md') && n !== 'INDEX.md').sort();
|
|
41
|
+
const numeroDe = (f) => {
|
|
42
|
+
try {
|
|
43
|
+
const n = parse(readFileSync(path.join(dir, f), 'utf8'), f).numero;
|
|
44
|
+
return /^\d+$/.test(String(n ?? '')) ? Number(n) : null;
|
|
45
|
+
} catch { return null; }
|
|
46
|
+
};
|
|
47
|
+
const maior = () => Math.max(0, ...files.map(numeroDe).filter((n) => n !== null));
|
|
48
|
+
if (args.includes('--numerar')) {
|
|
49
|
+
let max = maior();
|
|
50
|
+
for (const f of files.filter((x) => numeroDe(x) === null)) {
|
|
51
|
+
const file = path.join(dir, f);
|
|
52
|
+
const raw = readFileSync(file, 'utf8');
|
|
53
|
+
const eol = raw.includes('\r\n') ? '\r\n' : '\n';
|
|
54
|
+
const out = raw.replace(/^(titulo:[^\r\n]*)/m, `$1${eol}numero: ${++max}`);
|
|
55
|
+
if (out === raw) { console.error(`${f}: sem linha "titulo:" para numerar`); process.exit(1); }
|
|
56
|
+
writeFileSync(file, out, 'utf8');
|
|
57
|
+
console.log(`${f} -> #${max}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (args.includes('--proximo')) {
|
|
61
|
+
console.log(maior() + 1);
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
38
65
|
const errors = [];
|
|
39
66
|
const adrs = [];
|
|
40
|
-
for (const f of
|
|
67
|
+
for (const f of files) {
|
|
41
68
|
let h;
|
|
42
69
|
try {
|
|
43
70
|
h = parse(readFileSync(path.join(dir, f), 'utf8'), f);
|
|
@@ -46,7 +73,8 @@ for (const f of readdirSync(dir).filter((n) => n.endsWith('.md') && n !== 'INDEX
|
|
|
46
73
|
continue;
|
|
47
74
|
}
|
|
48
75
|
if (!/^\d{4}-\d{2}-\d{2}-/.test(f)) errors.push(`${f}: o nome deve começar com AAAA-MM-DD-`);
|
|
49
|
-
for (const k of ['titulo', 'data', 'status', 'decidido-por', 'dominio']) if (!h[k] || h[k].length === 0) errors.push(`${f}: falta "${k}"`);
|
|
76
|
+
for (const k of ['titulo', 'numero', 'data', 'status', 'decidido-por', 'dominio']) if (!h[k] || h[k].length === 0) errors.push(`${f}: falta "${k}"`);
|
|
77
|
+
if (h.numero && !/^[1-9]\d*$/.test(h.numero)) errors.push(`${f}: numero inválido (${h.numero}); use inteiro a partir de 1`);
|
|
50
78
|
if (h.data && !/^\d{4}-\d{2}-\d{2}$/.test(h.data)) errors.push(`${f}: data inválida (${h.data})`);
|
|
51
79
|
if (h.status && !STATUS.includes(h.status)) errors.push(`${f}: status inválido (${h.status}); use ${STATUS.join(' | ')}`);
|
|
52
80
|
const doms = Array.isArray(h.dominio) ? h.dominio : [h.dominio].filter(Boolean);
|
|
@@ -57,22 +85,27 @@ for (const f of readdirSync(dir).filter((n) => n.endsWith('.md') && n !== 'INDEX
|
|
|
57
85
|
}
|
|
58
86
|
adrs.push({ file: f, ...h, doms });
|
|
59
87
|
}
|
|
88
|
+
const nums = adrs.map((a) => Number(a.numero)).filter(Number.isInteger).sort((a, b) => a - b);
|
|
89
|
+
for (let i = 0; i < nums.length; i++) {
|
|
90
|
+
if (i > 0 && nums[i] === nums[i - 1]) errors.push(`numero #${nums[i]} repetido`);
|
|
91
|
+
else if (nums[i] !== i + 1) { errors.push(`numeração com lacuna: esperado #${i + 1}, achei #${nums[i]}`); break; }
|
|
92
|
+
}
|
|
60
93
|
if (errors.length) {
|
|
61
94
|
console.error(errors.join('\n'));
|
|
62
95
|
process.exit(1);
|
|
63
96
|
}
|
|
64
|
-
adrs.sort((a, b) => b.data.localeCompare(a.data) || b.
|
|
97
|
+
adrs.sort((a, b) => b.data.localeCompare(a.data) || Number(b.numero) - Number(a.numero));
|
|
65
98
|
|
|
66
99
|
const list = (v) => (Array.isArray(v) ? v.join(', ') : v || '');
|
|
67
|
-
const rows = adrs.map((a) => `| ${a.data} | [${a.titulo}](${a.file}) | ${a.doms.join(', ')} | ${a.status} | ${list(a['decidido-por'])} | ${list(a.consultados)} |`);
|
|
100
|
+
const rows = adrs.map((a) => `| ${a.numero} | ${a.data} | [${a.titulo}](${a.file}) | ${a.doms.join(', ')} | ${a.status} | ${list(a['decidido-por'])} | ${list(a.consultados)} |`);
|
|
68
101
|
const body = `# Decisões (ADR)
|
|
69
102
|
|
|
70
|
-
> Gerado por \`node .ai/skills/sg-adr/scripts/adr-index.mjs\` a partir do cabeçalho de cada ADR. Não edite à mão. Ordem: da mais nova para a mais antiga.
|
|
103
|
+
> Gerado por \`node .ai/skills/sg-adr/scripts/adr-index.mjs\` a partir do cabeçalho de cada ADR. Não edite à mão. Ordem: da mais nova para a mais antiga. O número \`#\` é sequencial, dado na criação e **nunca muda**: use-o para falar de uma ADR ("ADR #7").
|
|
71
104
|
|
|
72
105
|
Para saber **por que** algo foi feito, procure pelo domínio (coluna 3) ou pelo módulo no cabeçalho de cada ADR.${DOMINIOS ? ` Domínios: ${DOMINIOS.join(', ')}.` : ''}
|
|
73
106
|
|
|
74
|
-
| Data | Decisão | Domínio | Status | Decidido por | Consultados |
|
|
75
|
-
|
|
107
|
+
| # | Data | Decisão | Domínio | Status | Decidido por | Consultados |
|
|
108
|
+
|---|---|---|---|---|---|---|
|
|
76
109
|
${rows.join('\n')}
|
|
77
110
|
`;
|
|
78
111
|
const target = path.join(dir, 'INDEX.md');
|
|
@@ -11,8 +11,9 @@ Cabeçalho (obrigatório, no topo do arquivo, entre `---`):
|
|
|
11
11
|
---
|
|
12
12
|
description: Uma linha com a decisão.
|
|
13
13
|
titulo: Título curto da decisão
|
|
14
|
+
numero: 1 # sequencial, dado na criação (--proximo); nunca muda
|
|
14
15
|
data: AAAA-MM-DD
|
|
15
|
-
status: proposta # proposta | aceita | substituida | rejeitada
|
|
16
|
+
status: proposta # proposta | aceita | implementada | substituida | rejeitada
|
|
16
17
|
decidido-por: nome ou papel
|
|
17
18
|
consultados: [nomes, quando houver]
|
|
18
19
|
dominio: [seguranca] # lista do projeto (docs/ai/decisions/dominios.txt)
|
|
@@ -23,6 +24,8 @@ origem: plano, commit ou tarefa que gerou a decisão
|
|
|
23
24
|
---
|
|
24
25
|
```
|
|
25
26
|
|
|
27
|
+
Status: `proposta` (a IA propôs; o dono não confirmou); `aceita` (confirmada, ainda não está no código); `implementada` (confirmada e já no código; o que já foi implementado não fica `proposta`).
|
|
28
|
+
|
|
26
29
|
## Contexto
|
|
27
30
|
Problema, forças em jogo e restrições.
|
|
28
31
|
|