@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.
@@ -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 stem = rule.replace(/^\*\*\//, '').replace(/\[([^\]]+)\]/g, (_match, letters) => letters[0]).replace(/\*/g, 'agentdevkit-private-probe').replace(/\?/g, 'x');
140
- probes.push(`${root}/${stem}${stem.endsWith('/') ? '.agentdevkit-ignore-probe' : ''}`);
141
- if (rule.startsWith('**/')) probes.push(`${root}/.agentdevkit-ignore-probes/${stem}${stem.endsWith('/') ? '.agentdevkit-ignore-probe' : ''}`);
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), next = localBlock(old, first, last, null);
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
- if (!replaced && ((!plan.edits.length && (!match || negative || !local)) || humanOverride)) missing.push(probes[index]);
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.equal(plan.edits.length, 1);
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 seus arquivos rastreados são preservados;
114
- uma exceção humana que conflite com essa proteção é informada como conflito.
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
 
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pathbit/agentdevkit",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "Shared skills, specifications and workflows for AI coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",