@pathbit/agentdevkit 0.0.12 → 0.0.13
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/scripts/lib/README.md +4 -0
- package/.pbai4devs/scripts/lib/gitignore.mjs +28 -6
- package/.pbai4devs/scripts/sincronizar-agentes/tests/LOCAL_IGNORE.mjs +87 -1
- package/CHANGELOG.md +6 -0
- package/docs/guides/installation.md +4 -2
- package/docs/specs/README.md +2 -0
- package/docs/specs/real-projects/SPEC.md +26 -0
- package/package.json +1 -1
|
@@ -11,3 +11,7 @@ Também procura executáveis opcionais no PATH sem iniciar um shell.
|
|
|
11
11
|
`managed-content.mjs` limita a propriedade do kit aos seus blocos Markdown;
|
|
12
12
|
`managed-toml.mjs` aplica o mesmo contrato ao `prompt` de comandos Gemini.
|
|
13
13
|
Texto externo, nomes existentes e metadados humanos permanecem preservados.
|
|
14
|
+
|
|
15
|
+
`gitignore.mjs` planeja exclusões locais e detecta regras conflitantes. Com
|
|
16
|
+
exceções na raiz, pode acrescentar um bloco limitado ao estado privado dos
|
|
17
|
+
clientes; alterações no bloco gerenciado impedem sua substituição.
|
|
@@ -136,9 +136,11 @@ function privateClientPolicy(templateFile, roots) {
|
|
|
136
136
|
const negative = pattern.startsWith('!'), rule = pattern.replace(/^!/, '').replace(/^\//, '');
|
|
137
137
|
rules.push(`${negative ? '!' : ''}/${escapePattern(root)}/${rule}`);
|
|
138
138
|
if (negative) continue;
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
const witness = lower => rule.replace(/^\*\*\//, '').replace(/\[([^\]]+)\]/g, (_match, letters) => lower ? letters[0].toLowerCase() : letters[0]).replace(/\*/g, 'agentdevkit-private-probe').replace(/\?/g, 'x');
|
|
140
|
+
for (const variant of new Set([witness(false), witness(true)])) {
|
|
141
|
+
probes.push(`${root}/${variant}${variant.endsWith('/') ? '.agentdevkit-ignore-probe' : ''}`);
|
|
142
|
+
if (rule.startsWith('**/')) probes.push(`${root}/.agentdevkit-ignore-probes/${variant}${variant.endsWith('/') ? '.agentdevkit-ignore-probe' : ''}`);
|
|
143
|
+
}
|
|
142
144
|
}
|
|
143
145
|
return { rules, probes };
|
|
144
146
|
}
|
|
@@ -300,6 +302,17 @@ export function planLocalIgnore(root, { outputs = [], templateRoot } = {}) {
|
|
|
300
302
|
const edits = before === after ? [] : [{ path: target, before, after, beforeMode: localMode(target) }];
|
|
301
303
|
const probes = [...paths, ...rootProbes(root, roots)];
|
|
302
304
|
const artifactPaths = [...new Set([...artifactProbes(root, artifacts), ...privateClients.probes.filter(path => safeIgnoreProbe(root, path)), ...(location ? visibleArtifactProbes(root, location, artifacts) : [])])];
|
|
305
|
+
const privateProbes = privateClients.probes.filter(path => safeIgnoreProbe(root, path));
|
|
306
|
+
const rootOverrideProbes = new Set();
|
|
307
|
+
const overrideFirst = `# BEGIN AGENTDEVKIT PRIVATE ${id}`, overrideLast = `# END AGENTDEVKIT PRIVATE ${id}`;
|
|
308
|
+
if (location) {
|
|
309
|
+
const humanIgnore = localRead(checkedLocalPath(join(root, '.gitignore')));
|
|
310
|
+
const withoutFallback = localBlock(humanIgnore, first, last, null);
|
|
311
|
+
const withoutOwned = localBlock(withoutFallback, overrideFirst, overrideLast, null);
|
|
312
|
+
// Any root negation can match an arbitrary spelling or wildcard value.
|
|
313
|
+
// Protect private client paths deterministically instead of sampling it.
|
|
314
|
+
if (withoutOwned.replace(/^\uFEFF/, '').split(/\r?\n/).some(line => line.startsWith('!'))) for (const path of privateProbes) rootOverrideProbes.add(path);
|
|
315
|
+
}
|
|
303
316
|
const wanted = new Set(probes.map(path => location?.prefix ? `${location.prefix}/${path}` : path));
|
|
304
317
|
if (location && !before?.includes(first) && !localRead(join(root, '.gitignore'))?.includes(first)) {
|
|
305
318
|
const ignored = ignoreMatches(location, [...probes, ...artifactPaths]);
|
|
@@ -307,11 +320,19 @@ export function planLocalIgnore(root, { outputs = [], templateRoot } = {}) {
|
|
|
307
320
|
}
|
|
308
321
|
if (location) {
|
|
309
322
|
const fallback = checkedLocalPath(join(root, '.gitignore'));
|
|
310
|
-
const old = localRead(fallback)
|
|
323
|
+
const old = localRead(fallback);
|
|
324
|
+
let next = localBlock(old, first, last, null);
|
|
325
|
+
if (rootOverrideProbes.size || old?.includes(overrideFirst) || old?.includes(overrideLast)) {
|
|
326
|
+
const overrideBody = privateClients.rules.join('\n');
|
|
327
|
+
if ((old?.includes(overrideFirst) || old?.includes(overrideLast)) && localBlock(old, overrideFirst, overrideLast, overrideBody) !== old) throw new Error('Bloco privado AgentDevKit alterado; conteudo preservado.');
|
|
328
|
+
const end = next.indexOf(overrideLast);
|
|
329
|
+
if (end >= 0 && next.slice(end + overrideLast.length).split(/\r?\n/).some(line => line.startsWith('!'))) next = localBlock(next, overrideFirst, overrideLast, null);
|
|
330
|
+
next = localBlock(next, overrideFirst, overrideLast, overrideBody);
|
|
331
|
+
}
|
|
311
332
|
if (old !== null && old !== next) edits.push({ path: fallback, before: old, after: next, beforeMode: localMode(fallback) });
|
|
312
333
|
}
|
|
313
334
|
const tracked = location ? gitCommand(location.top, ['ls-files', '-z']).stdout.split('\0').filter(path => wanted.has(path)).map(path => location.prefix ? path.slice(location.prefix.length + 1) : path) : [];
|
|
314
|
-
return { root, status: location ? 'git' : 'fallback', location, paths, roots, artifactPatterns: artifacts, artifactProbes: artifactPaths, edits, trackedOutputs: tracked, first, last };
|
|
335
|
+
return { root, status: location ? 'git' : 'fallback', location, paths, roots, artifactPatterns: artifacts, artifactProbes: artifactPaths, edits, trackedOutputs: tracked, first, last, rootOverrideProbes: [...rootOverrideProbes] };
|
|
315
336
|
}
|
|
316
337
|
|
|
317
338
|
export function checkLocalIgnore(plan, { replacedBlocks = [] } = {}) {
|
|
@@ -337,7 +358,8 @@ export function checkLocalIgnore(plan, { replacedBlocks = [] } = {}) {
|
|
|
337
358
|
const local = match && localIgnoreSource(plan.location, match.source);
|
|
338
359
|
const humanOverride = negative && local && /(?:^|[\\/])\.gitignore$/.test(match.source);
|
|
339
360
|
const replaced = negative && replacements.some(item => item.path === canonicalExisting(resolve(plan.location.top, match.source)) && match.line >= item.firstLine && match.line <= item.lastLine);
|
|
340
|
-
|
|
361
|
+
const pendingRootOverride = humanOverride && plan.rootOverrideProbes?.includes(probes[index]) && plan.edits.some(edit => edit.path === join(plan.root, '.gitignore'));
|
|
362
|
+
if (!replaced && !pendingRootOverride && ((!plan.edits.length && (!match || negative || !local)) || humanOverride)) missing.push(probes[index]);
|
|
341
363
|
}
|
|
342
364
|
}
|
|
343
365
|
return { ok: plan.edits.length === 0 && missing.length === 0 && plan.trackedOutputs.length === 0, status: plan.status, missing, trackedOutputs: plan.trackedOutputs, changed: plan.edits.length };
|
|
@@ -68,6 +68,92 @@ test('check identifica negação humana e outputs rastreados sem alterar o índi
|
|
|
68
68
|
assert.equal(git(root, ['ls-files', 'generated.md']).trim(), 'generated.md');
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
test('negação genérica de cache preserva regras humanas e protege caches de clientes', t => {
|
|
72
|
+
const root = fixture(t); git(root, ['init', '-q']);
|
|
73
|
+
git(root, ['config', 'core.ignorecase', 'false']);
|
|
74
|
+
const original = '# Regras do projeto\n!*.[Cc]ache/\n';
|
|
75
|
+
put(root, '.gitignore', original); git(root, ['add', '.gitignore']);
|
|
76
|
+
const index = git(root, ['ls-files', '--stage', '-z']);
|
|
77
|
+
const plan = planLocalIgnore(root);
|
|
78
|
+
assert.equal(readFileSync(join(root, '.gitignore'), 'utf8'), original);
|
|
79
|
+
assert.equal(existsSync(join(root, '.cursor')), false);
|
|
80
|
+
applyLocalIgnore(plan);
|
|
81
|
+
const updated = readFileSync(join(root, '.gitignore'), 'utf8');
|
|
82
|
+
assert.ok(updated.startsWith(original));
|
|
83
|
+
assert.equal(git(root, ['ls-files', '--stage', '-z']), index);
|
|
84
|
+
assert.equal(existsSync(join(root, '.cursor')), false);
|
|
85
|
+
assert.equal(checkLocalIgnore(planLocalIgnore(root)).ok, true);
|
|
86
|
+
put(root, '.cursor/.CACHE/private.json', 'local');
|
|
87
|
+
put(root, '.cursor/.cache/private.json', 'local');
|
|
88
|
+
put(root, '.cursor/human.md', 'human');
|
|
89
|
+
assert.ok(git(root, ['check-ignore', '--no-index', '.cursor/.CACHE/private.json']).includes('.cursor/.CACHE/private.json'));
|
|
90
|
+
assert.ok(git(root, ['check-ignore', '--no-index', '.cursor/.cache/private.json']).includes('.cursor/.cache/private.json'));
|
|
91
|
+
const visible = spawnSync('git', ['-C', root, 'check-ignore', '--no-index', '-q', '.cursor/human.md']);
|
|
92
|
+
assert.equal(visible.status, 1);
|
|
93
|
+
applyLocalIgnore(planLocalIgnore(root));
|
|
94
|
+
assert.equal(readFileSync(join(root, '.gitignore'), 'utf8'), updated);
|
|
95
|
+
put(root, '.gitignore', updated + '# edição humana posterior\n');
|
|
96
|
+
assert.equal(checkLocalIgnore(planLocalIgnore(root)).ok, true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('bloco privado modificado é preservado e recusa atualização', t => {
|
|
100
|
+
const root = fixture(t); git(root, ['init', '-q']);
|
|
101
|
+
put(root, '.gitignore', '!*.[Cc]ache/\n');
|
|
102
|
+
applyLocalIgnore(planLocalIgnore(root));
|
|
103
|
+
const path = join(root, '.gitignore');
|
|
104
|
+
const changed = readFileSync(path, 'utf8').replace('# Estado local', '# Estado alterado');
|
|
105
|
+
// Alter a concrete managed rule, even if the template has no heading here.
|
|
106
|
+
const edited = changed.replace('/.cursor/', '/.cursor-alterado/');
|
|
107
|
+
assert.notEqual(edited, readFileSync(path, 'utf8'));
|
|
108
|
+
writeFileSync(path, edited);
|
|
109
|
+
assert.throws(() => planLocalIgnore(root), /Bloco privado AgentDevKit alterado/);
|
|
110
|
+
assert.equal(readFileSync(path, 'utf8'), edited);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('negação com caixa mista não expõe cache em Git sensível a maiúsculas', t => {
|
|
114
|
+
const root = fixture(t); git(root, ['init', '-q']);
|
|
115
|
+
git(root, ['config', 'core.ignorecase', 'false']);
|
|
116
|
+
put(root, '.gitignore', '!*.CaChE/\n');
|
|
117
|
+
put(root, '.cursor/.CaChE/private.json', 'local');
|
|
118
|
+
applyLocalIgnore(planLocalIgnore(root));
|
|
119
|
+
assert.equal(checkLocalIgnore(planLocalIgnore(root)).ok, true);
|
|
120
|
+
assert.ok(git(root, ['check-ignore', '--no-index', '.cursor/.CaChE/private.json']).includes('.cursor/.CaChE/private.json'));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('negações literais e com caixa mista não expõem estado privado', t => {
|
|
124
|
+
const root = fixture(t); git(root, ['init', '-q']);
|
|
125
|
+
git(root, ['config', 'core.ignorecase', 'false']);
|
|
126
|
+
put(root, '.gitignore', '!*.LoCaL/\n!/.cursor/.env.production\n');
|
|
127
|
+
for (const path of ['.cursor/.LoCaL/private.json', '.cursor/.env.production']) put(root, path, 'privado');
|
|
128
|
+
applyLocalIgnore(planLocalIgnore(root));
|
|
129
|
+
for (const path of ['.cursor/.LoCaL/private.json', '.cursor/.env.production']) assert.ok(git(root, ['check-ignore', '--no-index', path]).includes(path));
|
|
130
|
+
assert.equal(checkLocalIgnore(planLocalIgnore(root)).ok, true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('negação na primeira linha com BOM preserva bytes e protege segredos', t => {
|
|
134
|
+
const root = fixture(t); git(root, ['init', '-q']);
|
|
135
|
+
const original = '\uFEFF!/.cursor/.env.production\r\n';
|
|
136
|
+
put(root, '.gitignore', original); put(root, '.cursor/.env.production', 'privado');
|
|
137
|
+
applyLocalIgnore(planLocalIgnore(root));
|
|
138
|
+
assert.ok(readFileSync(join(root, '.gitignore'), 'utf8').startsWith(original));
|
|
139
|
+
assert.ok(git(root, ['check-ignore', '--no-index', '.cursor/.env.production']).includes('.cursor/.env.production'));
|
|
140
|
+
assert.equal(checkLocalIgnore(planLocalIgnore(root)).ok, true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('negação humana acrescentada após o bloco recebe proteção na próxima sincronização', t => {
|
|
144
|
+
const root = fixture(t); git(root, ['init', '-q']);
|
|
145
|
+
put(root, '.gitignore', '!*.cache/\n'); applyLocalIgnore(planLocalIgnore(root));
|
|
146
|
+
const path = join(root, '.gitignore');
|
|
147
|
+
writeFileSync(path, readFileSync(path, 'utf8') + '!/.cursor/.env.production\n');
|
|
148
|
+
put(root, '.cursor/.env.production', 'privado');
|
|
149
|
+
const plan = planLocalIgnore(root);
|
|
150
|
+
assert.ok(plan.edits.some(edit => edit.path === path));
|
|
151
|
+
applyLocalIgnore(plan);
|
|
152
|
+
assert.match(readFileSync(path, 'utf8'), /!\*\.cache\/\n!\/\.cursor\/\.env\.production\n/);
|
|
153
|
+
assert.ok(git(root, ['check-ignore', '--no-index', '.cursor/.env.production']).includes('.cursor/.env.production'));
|
|
154
|
+
assert.equal(checkLocalIgnore(planLocalIgnore(root)).ok, true);
|
|
155
|
+
});
|
|
156
|
+
|
|
71
157
|
test('planejamento é somente leitura e aplicação detecta mudança concorrente', t => {
|
|
72
158
|
const root = fixture(t); put(root, '.gitignore', 'antes\r\n');
|
|
73
159
|
const plan = planLocalIgnore(root, { outputs: ['generated.md'] });
|
|
@@ -250,7 +336,7 @@ test('negação humana é detectada antes de aplicar novas exclusões e permanec
|
|
|
250
336
|
put(root, '.gitignore', '!/AGENTS.md\n');
|
|
251
337
|
const before = readFileSync(join(root, '.git/info/exclude'), 'utf8');
|
|
252
338
|
const plan = planLocalIgnore(root, { outputs: ['AGENTS.md'] });
|
|
253
|
-
assert.
|
|
339
|
+
assert.ok(plan.edits.some(edit => edit.path === join(root, '.git/info/exclude')));
|
|
254
340
|
assert.deepEqual(checkLocalIgnore(plan).missing, ['AGENTS.md']);
|
|
255
341
|
assert.throws(() => applyLocalIgnore(plan), /Regras humanas/);
|
|
256
342
|
assert.equal(readFileSync(join(root, '.git/info/exclude'), 'utf8'), before);
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ description: Alterações verificáveis por versão do AgentDevKit.
|
|
|
4
4
|
|
|
5
5
|
# Changelog
|
|
6
6
|
|
|
7
|
+
## 0.0.13 — 2026-11-12
|
|
8
|
+
|
|
9
|
+
- Proteção de caches de agentes mesmo com negações genéricas no `.gitignore`.
|
|
10
|
+
- Matriz de compatibilidade ampliada para os sete projetos públicos solicitados.
|
|
11
|
+
- Dois exemplos reproduzíveis com produto, design e três specs testadas.
|
|
12
|
+
|
|
7
13
|
## 0.0.12 — 2026-11-12
|
|
8
14
|
|
|
9
15
|
- Entradas e recursos existentes recebem referências em blocos próprios do kit.
|
|
@@ -110,8 +110,10 @@ quando o repositório for iniciado.
|
|
|
110
110
|
|
|
111
111
|
O bloco local também protege `.agent-tests`, `tmp`, `temp`, `.playwright*`
|
|
112
112
|
e relatórios de testes, inclusive com letras maiúsculas e em subpastas.
|
|
113
|
-
O `.gitignore` existente e
|
|
114
|
-
|
|
113
|
+
O conteúdo do `.gitignore` existente e os arquivos rastreados são preservados.
|
|
114
|
+
Quando há exceções no `.gitignore` da raiz, o kit pode acrescentar um bloco
|
|
115
|
+
limitado ao estado privado dos clientes, sem criar pastas não selecionadas.
|
|
116
|
+
Conflitos detectados nas demais proteções são informados para revisão.
|
|
115
117
|
Nomes próprios de artefatos e arquivos existentes também são conferidos;
|
|
116
118
|
a validação fica restrita ao projeto e aos ignores de seus ancestrais.
|
|
117
119
|
|
package/docs/specs/README.md
CHANGED
|
@@ -18,6 +18,7 @@ description: Incrementos verificáveis do AgentDevKit.
|
|
|
18
18
|
- [v0.0.10](v0.0.10/SPEC.md): release com instalação pelos dois registries e pré-requisitos.
|
|
19
19
|
- [Ecossistema de segurança](security-ecosystem/SPEC.md): fluxos e scanners locais.
|
|
20
20
|
- [Integração sem substituição][additive]: acréscimos em arquivos existentes.
|
|
21
|
+
- [Compatibilidade com projetos reais][oss]: projetos públicos e exemplos reproduzíveis.
|
|
21
22
|
|
|
22
23
|
Cada incremento vincula requisitos, critérios, testes e evidências locais.
|
|
23
24
|
|
|
@@ -28,3 +29,4 @@ projeto, siga o [fluxo](../guides/workflow.md) e use os
|
|
|
28
29
|
[Wiki](../README.md) · [Roadmap](../product/roadmap.md)
|
|
29
30
|
|
|
30
31
|
[additive]: additive-integration/SPEC.md
|
|
32
|
+
[oss]: real-projects/SPEC.md
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Compatibilidade com repositórios públicos e exemplos incrementais.
|
|
3
|
+
status: aprovado
|
|
4
|
+
version: 0.0.13
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Projetos reais e exemplos incrementais
|
|
8
|
+
|
|
9
|
+
Validar a instalação e manutenção do pacote em commits fixos de projetos reais,
|
|
10
|
+
preservando suas configurações e seu índice Git. Demonstrar o fluxo de produto,
|
|
11
|
+
design e três specs em dois projetos novos, com resultados locais ignorados.
|
|
12
|
+
|
|
13
|
+
## Critérios de aceite
|
|
14
|
+
|
|
15
|
+
| ID | Resultado observável |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| AC-001 | Testar os sete repositórios, com commits registrados. |
|
|
18
|
+
| AC-002 | Testar init repetido, update, sync e preservação nos clones. |
|
|
19
|
+
| AC-003 | Proteger caches ante exceções Git, preservando texto e índice. |
|
|
20
|
+
| AC-004 | Produto e design antes das três specs de cada projeto novo. |
|
|
21
|
+
| AC-005 | Documentar reprodução, limites e saídas ignoradas. |
|
|
22
|
+
| AC-006 | Testes de regressão, pacote e revisão independente passam. |
|
|
23
|
+
|
|
24
|
+
Os testes de compatibilidade não executam nem compilam as aplicações externas.
|
|
25
|
+
Aprovações de produto e revisão nos exemplos são simuladas. Código de teste é
|
|
26
|
+
versionado; clones, projetos gerados e evidências permanecem fora do Git.
|