ai-dev-workflow-cli 0.0.0-stage → 0.5.1
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/.continue/prompts/analisar-projeto.md +7 -0
- package/.continue/prompts/documentar-task.md +7 -0
- package/.continue/prompts/planejar-task.md +9 -0
- package/.continue/prompts/revisar-codigo.md +7 -0
- package/.continue/rules/workflow.md +12 -0
- package/.github/pull_request_template.md +10 -0
- package/.gitignore +28 -0
- package/AGENTS.md +38 -0
- package/CLAUDE.md +7 -0
- package/GEMINI.md +7 -0
- package/LICENSE +21 -0
- package/README.md +93 -2
- package/agents/architect.md +5 -0
- package/agents/developer.md +5 -0
- package/agents/discovery.md +5 -0
- package/agents/documentation.md +5 -0
- package/agents/environment.md +3 -0
- package/agents/reviewer.md +5 -0
- package/bin/ai-dev-workflow.js +10 -0
- package/catalog/central-v0.2.json +197 -0
- package/catalog/legacy-v1.json +158 -0
- package/catalog/template.json +207 -0
- package/docs/CLI.md +224 -0
- package/docs/DISCOVERY_CLI.md +83 -0
- package/docs/LAYOUT.md +13 -0
- package/docs/MULTI_AI.md +36 -0
- package/docs/PERSISTENCE.md +80 -0
- package/docs/QUICKSTART.md +219 -0
- package/docs/WORKFLOW.md +58 -0
- package/integrations/codex/README.md +14 -0
- package/integrations/continue/README.md +19 -0
- package/package.json +45 -4
- package/payload/central/.ai-dev-workflow/agents/architect.md +5 -0
- package/payload/central/.ai-dev-workflow/agents/developer.md +5 -0
- package/payload/central/.ai-dev-workflow/agents/discovery.md +5 -0
- package/payload/central/.ai-dev-workflow/agents/documentation.md +5 -0
- package/payload/central/.ai-dev-workflow/agents/environment.md +3 -0
- package/payload/central/.ai-dev-workflow/agents/reviewer.md +5 -0
- package/payload/central/.ai-dev-workflow/docs/WORKFLOW.md +58 -0
- package/payload/central/.ai-dev-workflow/integrations/codex/README.md +14 -0
- package/payload/central/.ai-dev-workflow/integrations/continue/README.md +19 -0
- package/payload/central/.ai-dev-workflow/rules/cost-control.md +6 -0
- package/payload/central/.ai-dev-workflow/rules/database.md +46 -0
- package/payload/central/.ai-dev-workflow/rules/general.md +7 -0
- package/payload/central/.ai-dev-workflow/rules/git-workflow.md +16 -0
- package/payload/central/.ai-dev-workflow/rules/security.md +11 -0
- package/payload/central/.ai-dev-workflow/rules/testing.md +6 -0
- package/payload/central/.ai-dev-workflow/stacks/nodejs.md +3 -0
- package/payload/central/.ai-dev-workflow/stacks/php-laravel.md +6 -0
- package/payload/central/.ai-dev-workflow/stacks/php-native.md +3 -0
- package/payload/central/.ai-dev-workflow/stacks/wordpress.md +4 -0
- package/payload/central/.ai-dev-workflow/templates/ARCHITECTURE.md +8 -0
- package/payload/central/.ai-dev-workflow/templates/BASELINE.md +16 -0
- package/payload/central/.ai-dev-workflow/templates/CURRENT_STATE.md +15 -0
- package/payload/central/.ai-dev-workflow/templates/DATABASE_CHANGE.md +31 -0
- package/payload/central/.ai-dev-workflow/templates/DECISIONS.md +9 -0
- package/payload/central/.ai-dev-workflow/templates/DISCOVERY.md +13 -0
- package/payload/central/.ai-dev-workflow/templates/ENVIRONMENT.md +20 -0
- package/payload/central/.ai-dev-workflow/templates/PR_CHECKLIST.md +9 -0
- package/payload/central/.ai-dev-workflow/templates/REQUIREMENTS.md +9 -0
- package/payload/central/.ai-dev-workflow/templates/TASK.md +26 -0
- package/payload/central/.ai-dev-workflow/templates/TASK_REPORT.md +20 -0
- package/payload/central/.continue/prompts/analisar-projeto.md +7 -0
- package/payload/central/.continue/prompts/documentar-task.md +7 -0
- package/payload/central/.continue/prompts/planejar-task.md +9 -0
- package/payload/central/.continue/prompts/revisar-codigo.md +7 -0
- package/payload/central/.continue/rules/workflow.md +12 -0
- package/payload/central/.github/pull_request_template.md +10 -0
- package/payload/central/.gitignore.txt +28 -0
- package/payload/central/AGENTS.md +38 -0
- package/payload/central/CLAUDE.md +7 -0
- package/payload/central/GEMINI.md +7 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/architect.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/developer.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/discovery.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/documentation.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/environment.md +3 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/reviewer.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/docs/WORKFLOW.md +58 -0
- package/payload/central-v0.2/.ai-dev-workflow/integrations/codex/README.md +14 -0
- package/payload/central-v0.2/.ai-dev-workflow/integrations/continue/README.md +19 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/cost-control.md +6 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/database.md +46 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/general.md +7 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/git-workflow.md +16 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/security.md +11 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/testing.md +6 -0
- package/payload/central-v0.2/.ai-dev-workflow/stacks/nodejs.md +3 -0
- package/payload/central-v0.2/.ai-dev-workflow/stacks/php-laravel.md +6 -0
- package/payload/central-v0.2/.ai-dev-workflow/stacks/php-native.md +3 -0
- package/payload/central-v0.2/.ai-dev-workflow/stacks/wordpress.md +4 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/ARCHITECTURE.md +8 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/BASELINE.md +16 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/CURRENT_STATE.md +15 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/DATABASE_CHANGE.md +31 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/DECISIONS.md +9 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/DISCOVERY.md +13 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/ENVIRONMENT.md +20 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/PR_CHECKLIST.md +9 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/REQUIREMENTS.md +9 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/TASK.md +26 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/TASK_REPORT.md +20 -0
- package/payload/central-v0.2/.continue/prompts/analisar-projeto.md +7 -0
- package/payload/central-v0.2/.continue/prompts/documentar-task.md +7 -0
- package/payload/central-v0.2/.continue/prompts/planejar-task.md +9 -0
- package/payload/central-v0.2/.continue/prompts/revisar-codigo.md +7 -0
- package/payload/central-v0.2/.continue/rules/workflow.md +12 -0
- package/payload/central-v0.2/.github/pull_request_template.md +10 -0
- package/payload/central-v0.2/.gitignore.txt +28 -0
- package/payload/central-v0.2/AGENTS.md +38 -0
- package/payload/legacy-v1/.continue/prompts/analisar-projeto.md +7 -0
- package/payload/legacy-v1/.continue/prompts/documentar-task.md +7 -0
- package/payload/legacy-v1/.continue/prompts/planejar-task.md +9 -0
- package/payload/legacy-v1/.continue/prompts/revisar-codigo.md +7 -0
- package/payload/legacy-v1/.continue/rules/workflow.md +12 -0
- package/payload/legacy-v1/.github/pull_request_template.md +10 -0
- package/payload/legacy-v1/.gitignore.txt +28 -0
- package/payload/legacy-v1/AGENTS.md +38 -0
- package/payload/legacy-v1/agents/architect.md +5 -0
- package/payload/legacy-v1/agents/developer.md +5 -0
- package/payload/legacy-v1/agents/discovery.md +5 -0
- package/payload/legacy-v1/agents/documentation.md +5 -0
- package/payload/legacy-v1/agents/environment.md +3 -0
- package/payload/legacy-v1/agents/reviewer.md +5 -0
- package/payload/legacy-v1/docs/WORKFLOW.md +58 -0
- package/payload/legacy-v1/integrations/codex/README.md +14 -0
- package/payload/legacy-v1/integrations/continue/README.md +19 -0
- package/payload/legacy-v1/rules/cost-control.md +6 -0
- package/payload/legacy-v1/rules/database.md +46 -0
- package/payload/legacy-v1/rules/general.md +7 -0
- package/payload/legacy-v1/rules/git-workflow.md +16 -0
- package/payload/legacy-v1/rules/security.md +11 -0
- package/payload/legacy-v1/rules/testing.md +6 -0
- package/payload/legacy-v1/stacks/nodejs.md +3 -0
- package/payload/legacy-v1/stacks/php-laravel.md +6 -0
- package/payload/legacy-v1/stacks/php-native.md +3 -0
- package/payload/legacy-v1/stacks/wordpress.md +4 -0
- package/payload/legacy-v1/templates/ARCHITECTURE.md +8 -0
- package/payload/legacy-v1/templates/BASELINE.md +16 -0
- package/payload/legacy-v1/templates/CURRENT_STATE.md +15 -0
- package/payload/legacy-v1/templates/DATABASE_CHANGE.md +31 -0
- package/payload/legacy-v1/templates/DECISIONS.md +9 -0
- package/payload/legacy-v1/templates/DISCOVERY.md +13 -0
- package/payload/legacy-v1/templates/ENVIRONMENT.md +20 -0
- package/payload/legacy-v1/templates/PR_CHECKLIST.md +9 -0
- package/payload/legacy-v1/templates/REQUIREMENTS.md +9 -0
- package/payload/legacy-v1/templates/TASK.md +26 -0
- package/payload/legacy-v1/templates/TASK_REPORT.md +20 -0
- package/rules/cost-control.md +6 -0
- package/rules/database.md +46 -0
- package/rules/general.md +7 -0
- package/rules/git-workflow.md +16 -0
- package/rules/security.md +11 -0
- package/rules/testing.md +6 -0
- package/scripts/check-catalog.js +11 -0
- package/src/cli.js +88 -0
- package/src/commands/discover.js +2 -0
- package/src/commands/doctor.js +1 -0
- package/src/commands/init.js +2 -0
- package/src/commands/status.js +1 -0
- package/src/commands/validate.js +1 -0
- package/src/core/adapters.js +9 -0
- package/src/core/catalog.js +52 -0
- package/src/core/diagnostics.js +82 -0
- package/src/core/errors.js +10 -0
- package/src/core/install-plan.js +49 -0
- package/src/core/installer.js +72 -0
- package/src/core/journal.js +28 -0
- package/src/core/manifest.js +55 -0
- package/src/discovery/detectors.js +156 -0
- package/src/discovery/evidence.js +12 -0
- package/src/discovery/reader.js +47 -0
- package/src/discovery/reconcile.js +18 -0
- package/src/discovery/report.js +24 -0
- package/src/discovery/scanner.js +60 -0
- package/src/interaction/init.js +52 -0
- package/src/output/json.js +1 -0
- package/src/output/text.js +18 -0
- package/src/persistence/history.js +23 -0
- package/src/persistence/markdown.js +25 -0
- package/src/persistence/record.js +90 -0
- package/src/persistence/storage.js +47 -0
- package/src/platform/filesystem.js +71 -0
- package/src/platform/git.js +123 -0
- package/stacks/nodejs.md +3 -0
- package/stacks/php-laravel.md +6 -0
- package/stacks/php-native.md +3 -0
- package/stacks/wordpress.md +4 -0
- package/templates/ARCHITECTURE.md +8 -0
- package/templates/BASELINE.md +16 -0
- package/templates/CURRENT_STATE.md +15 -0
- package/templates/DATABASE_CHANGE.md +31 -0
- package/templates/DECISIONS.md +9 -0
- package/templates/DISCOVERY.md +13 -0
- package/templates/ENVIRONMENT.md +20 -0
- package/templates/PR_CHECKLIST.md +9 -0
- package/templates/REQUIREMENTS.md +9 -0
- package/templates/TASK.md +26 -0
- package/templates/TASK_REPORT.md +20 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { checkedPath, stat, hash } from '../platform/filesystem.js';
|
|
5
|
+
import { inspectGit, inspectDiscoveryGit } from '../platform/git.js';
|
|
6
|
+
import { assertReconciled } from '../core/journal.js';
|
|
7
|
+
import { packageVersion } from '../core/catalog.js';
|
|
8
|
+
import { fail } from '../core/errors.js';
|
|
9
|
+
import { paths, limits, read, absent, parent, exclusive, publish, syncParent } from './storage.js';
|
|
10
|
+
import { parseHistory } from './history.js';
|
|
11
|
+
import { discoveryMarkdown } from './markdown.js';
|
|
12
|
+
async function safety(root,command,allowNoGit){
|
|
13
|
+
const noGit=command==='discover'&&allowNoGit&&!await stat(path.join(root,'.git'));
|
|
14
|
+
const g=noGit?await inspectDiscoveryGit(root,true):await inspectGit(root);
|
|
15
|
+
if(!g.safe)fail(g.code,'Persistência exige alvo Git seguro e branch de task, ou Discovery sem Git explicitamente permitido.',g.exitCode);
|
|
16
|
+
await assertReconciled(root);
|
|
17
|
+
await absent(root,paths.journal);
|
|
18
|
+
}
|
|
19
|
+
export async function preflight(root,command,allowNoGit){
|
|
20
|
+
await safety(root,command,allowNoGit);await absent(root,paths.lock);
|
|
21
|
+
parseHistory(await read(root,paths.history,limits.history));
|
|
22
|
+
if(command==='discover')await read(root,paths.current,limits.report);
|
|
23
|
+
}
|
|
24
|
+
export async function persist(root,command,result,allowNoGit=false){
|
|
25
|
+
await safety(root,command,allowNoGit);
|
|
26
|
+
const id=randomUUID();let lockInfo;let locked=false;let journalCreated=false;let preserveLock=false;
|
|
27
|
+
try{
|
|
28
|
+
await parent(root,paths.lock);
|
|
29
|
+
// exclusive cria antes de escrever; guardar propriedade mesmo se a escrita falhar.
|
|
30
|
+
const file=await checkedPath(root,paths.lock);let h;
|
|
31
|
+
try{h=await fs.open(file,'wx',0o600);}catch(e){if(e.code==='EEXIST')fail('PERSISTENCE_LOCKED','Outro gravador ou lock antigo está presente.');throw e;}
|
|
32
|
+
locked=true;
|
|
33
|
+
try{lockInfo=await h.stat();await h.writeFile(id+'\n');await h.sync();}finally{await h.close();}
|
|
34
|
+
await safety(root,command,allowNoGit);
|
|
35
|
+
const history=await read(root,paths.history,limits.history);const events=parseHistory(history);
|
|
36
|
+
const operationExitCode=result.exitCode??(result.ok?0:2);
|
|
37
|
+
const cliVersion=await packageVersion();
|
|
38
|
+
// Sob lock: instante de preparação do registro, não do comando. O relógio pode regredir.
|
|
39
|
+
const timestamp=new Date().toISOString();
|
|
40
|
+
const event={historyVersion:'1.0.0',id,timestamp,cliVersion,command,
|
|
41
|
+
result:operationExitCode===0?'completed':operationExitCode===1?'error':'blocked',operationExitCode,
|
|
42
|
+
summaryCode:['COMPLETED','OPERATIONAL_ERROR','BLOCKED','INVALID_ARGUMENTS'][operationExitCode]};
|
|
43
|
+
const entries=[];let canonical='not-applicable';
|
|
44
|
+
if(command==='discover'&&operationExitCode===0){
|
|
45
|
+
const report=discoveryMarkdown(result,id,timestamp);
|
|
46
|
+
const current=await read(root,paths.current,limits.report);
|
|
47
|
+
const previous=events.filter(e=>e.discovery).at(-1)?.discovery;
|
|
48
|
+
let owned=false;
|
|
49
|
+
if(previous){
|
|
50
|
+
const last=await read(root,paths.versions+'/'+previous.snapshot,limits.report);
|
|
51
|
+
if(!last||hash(last)!==previous.sha256)fail('HISTORY_INVALID','Última versão histórica ausente/adulterada; reconciliação humana necessária.');
|
|
52
|
+
owned=!!current&&hash(current)===previous.sha256;
|
|
53
|
+
}
|
|
54
|
+
canonical=!current?'created':owned?'updated':'preserved-custom';
|
|
55
|
+
event.discovery={snapshot:id+'.md',sha256:hash(report),current:canonical};
|
|
56
|
+
entries.push({path:paths.versions+'/'+id+'.md',data:report,beforeHash:null,maximum:limits.report});
|
|
57
|
+
if(!current||owned)entries.push({path:paths.current,data:report,beforeHash:current?hash(current):null,maximum:limits.report});
|
|
58
|
+
}
|
|
59
|
+
const line=Buffer.from(JSON.stringify(event)+'\n');if(line.length>limits.event)fail('PERSISTENCE_LIMIT','Evento excede limite.');
|
|
60
|
+
const next=Buffer.concat([history??Buffer.alloc(0),line]);if(next.length>limits.history)fail('PERSISTENCE_LIMIT','Histórico excede limite; nenhuma rotação automática.');
|
|
61
|
+
parseHistory(next); // Conferir schema gerado antes de qualquer publicação.
|
|
62
|
+
entries.push({path:paths.history,data:next,beforeHash:history?hash(history):null,maximum:limits.history});
|
|
63
|
+
entries.forEach((e,i)=>{e.temp=path.posix.dirname(e.path)+'/.record-'+id+'-'+i+'.tmp';});
|
|
64
|
+
for(const e of entries){await parent(root,e.path);await absent(root,e.temp);}
|
|
65
|
+
const plan={persistenceVersion:'1.0.0',id,event,files:entries.map(e=>({path:e.path,temp:e.temp,beforeHash:e.beforeHash,plannedHash:hash(e.data)}))};
|
|
66
|
+
// Até um journal vazio/truncado é evidência bloqueante; nunca removê-lo no erro.
|
|
67
|
+
journalCreated=true;await exclusive(root,paths.journal,Buffer.from(JSON.stringify(plan)+'\n'));await syncParent(root,paths.journal);
|
|
68
|
+
for(const entry of entries)await publish(root,entry);
|
|
69
|
+
for(const entry of entries){const value=await read(root,entry.path,entry.maximum);if(!value||hash(value)!==hash(entry.data))fail('PERSISTENCE_INCOMPLETE','Publicação divergente; preservar evidências.');}
|
|
70
|
+
await fs.unlink(await checkedPath(root,paths.journal));
|
|
71
|
+
try { await syncParent(root,paths.journal);journalCreated=false; }
|
|
72
|
+
catch(e) {
|
|
73
|
+
// Resultado do cleanup não comprovado: recolocar marcador ou manter lock.
|
|
74
|
+
try { await exclusive(root,paths.journal,Buffer.from(JSON.stringify(plan)+'\n')); }
|
|
75
|
+
catch { preserveLock=true; }
|
|
76
|
+
throw e;
|
|
77
|
+
}
|
|
78
|
+
return {ok:true,wrote:true,completed:true,id,history:paths.history,...(event.discovery?{snapshot:paths.versions+'/'+event.discovery.snapshot,current:canonical}:{}),message:'Registros publicados; não representam auditoria completa nem aprovação humana.'};
|
|
79
|
+
}catch(e){
|
|
80
|
+
// Mensagens constantes: não retornar conteúdo de arquivos, argv ou erro bruto.
|
|
81
|
+
if(journalCreated)e.persistenceIncomplete=true;
|
|
82
|
+
throw e;
|
|
83
|
+
}finally{
|
|
84
|
+
if(locked&&!preserveLock){
|
|
85
|
+
const file=await checkedPath(root,paths.lock);const now=await stat(file);
|
|
86
|
+
if(!lockInfo||!now||now.ino!==lockInfo.ino||now.dev!==lockInfo.dev||now.nlink!==1)fail('PERSISTENCE_LOCK_CLEANUP','Lock mudou; não remover automaticamente.',1);
|
|
87
|
+
await fs.unlink(file);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { checkedPath, hash } from '../platform/filesystem.js';
|
|
5
|
+
import { fail } from '../core/errors.js';
|
|
6
|
+
export const paths = { history:'docs/project/COMMAND_HISTORY.jsonl', current:'docs/project/DISCOVERY.md', versions:'docs/project/discovery-history', lock:'docs/project/.command-history.lock', journal:'docs/project/.command-history-journal.json' };
|
|
7
|
+
export const limits = { report:2*1024*1024, event:16*1024, history:8*1024*1024 };
|
|
8
|
+
export async function read(root, relative, maximum) {
|
|
9
|
+
const file=await checkedPath(root,relative);let info;
|
|
10
|
+
try{info=await fs.lstat(file);}catch(e){if(e.code==='ENOENT')return null;throw e;}
|
|
11
|
+
if(info.nlink!==1)fail('PERSISTENCE_UNSAFE','Hardlink em arquivo de persistência.');
|
|
12
|
+
if(info.size>maximum)fail('PERSISTENCE_LIMIT','Limite de persistência excedido.');
|
|
13
|
+
const handle=await fs.open(file,constants.O_RDONLY|(constants.O_NOFOLLOW??0));
|
|
14
|
+
try{
|
|
15
|
+
const opened=await handle.stat();
|
|
16
|
+
if(opened.ino!==info.ino||opened.dev!==info.dev||opened.size!==info.size||opened.nlink!==1)fail('PERSISTENCE_UNSAFE','Arquivo alterado durante validação.');
|
|
17
|
+
const data=Buffer.alloc(info.size+1);let offset=0;
|
|
18
|
+
while(offset<data.length){const r=await handle.read(data,offset,data.length-offset,null);if(!r.bytesRead)break;offset+=r.bytesRead;}
|
|
19
|
+
const after=await handle.stat();
|
|
20
|
+
if(offset!==info.size||after.size!==info.size||after.mtimeMs!==opened.mtimeMs||after.nlink!==1)fail('PERSISTENCE_UNSAFE','Arquivo alterado durante leitura.');
|
|
21
|
+
return data.subarray(0,offset);
|
|
22
|
+
}finally{await handle.close();}
|
|
23
|
+
}
|
|
24
|
+
export async function absent(root,relative){
|
|
25
|
+
if(await fs.lstat(await checkedPath(root,relative)).catch(e=>{if(e.code==='ENOENT')return null;throw e;}))fail('PERSISTENCE_INCOMPLETE','Persistência ocupada/incompleta; preservar evidências e reconciliar manualmente.');
|
|
26
|
+
}
|
|
27
|
+
export async function parent(root,relative){
|
|
28
|
+
const file=await checkedPath(root,relative);await fs.mkdir(path.dirname(file),{recursive:true});await checkedPath(root,relative);return file;
|
|
29
|
+
}
|
|
30
|
+
export async function exclusive(root,relative,data){
|
|
31
|
+
const file=await parent(root,relative);const h=await fs.open(file,'wx',0o600);
|
|
32
|
+
try{await h.writeFile(data);await h.sync();return await h.stat();}finally{await h.close();}
|
|
33
|
+
}
|
|
34
|
+
export async function syncParent(root,relative){
|
|
35
|
+
const dir=path.dirname(await checkedPath(root,relative));const h=await fs.open(dir,'r');try{await h.sync();}finally{await h.close();}
|
|
36
|
+
}
|
|
37
|
+
export async function publish(root,entry){
|
|
38
|
+
await exclusive(root,entry.temp,entry.data);
|
|
39
|
+
const before=await read(root,entry.path,entry.maximum);
|
|
40
|
+
if((before?hash(before):null)!==entry.beforeHash)fail('PERSISTENCE_CONFLICT','Arquivo mudou; publicação bloqueada.');
|
|
41
|
+
const dest=await checkedPath(root,entry.path);const temp=await checkedPath(root,entry.temp);
|
|
42
|
+
if(before){await fs.rename(temp,dest);}else{
|
|
43
|
+
// Publicar sem substituir arquivo preexistente, inclusive UUID colidente.
|
|
44
|
+
await fs.link(temp,dest);await fs.unlink(temp);
|
|
45
|
+
}
|
|
46
|
+
await syncParent(root,entry.path);
|
|
47
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { fail } from '../core/errors.js';
|
|
5
|
+
export const hash = data => createHash('sha256').update(data).digest('hex');
|
|
6
|
+
export async function stat(file) {
|
|
7
|
+
try { return await fs.lstat(file); }
|
|
8
|
+
catch (e) { if (e.code === 'ENOENT') return null; throw e; }
|
|
9
|
+
}
|
|
10
|
+
export function safeRelative(name) {
|
|
11
|
+
return typeof name === 'string' && name.length > 0 && !name.includes('\\') &&
|
|
12
|
+
!/[<>:"|?*\u0000-\u001F]/.test(name) && !path.posix.isAbsolute(name) &&
|
|
13
|
+
name.split('/').every(p => p && p !== '.' && p !== '..' && !/[. ]$/.test(p) &&
|
|
14
|
+
!/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(p));
|
|
15
|
+
}
|
|
16
|
+
export async function resolveTarget(input) {
|
|
17
|
+
const absolute = path.resolve(input);
|
|
18
|
+
const parsed = path.parse(absolute);
|
|
19
|
+
let current = parsed.root;
|
|
20
|
+
const parts = absolute.slice(parsed.root.length).split(path.sep).filter(Boolean);
|
|
21
|
+
for (let i = 0; i < parts.length; i++) {
|
|
22
|
+
current = path.join(current, parts[i]);
|
|
23
|
+
const info = await stat(current);
|
|
24
|
+
if (!info) fail('TARGET_MISSING', 'Crie o diretório e prepare Git manualmente antes de init.');
|
|
25
|
+
if (info.isSymbolicLink()) {
|
|
26
|
+
const aliases = { '/tmp': '/private/tmp', '/var': '/private/var', '/etc': '/private/etc' };
|
|
27
|
+
const allowed = process.platform === 'darwin' && i < parts.length - 1 && aliases[current];
|
|
28
|
+
if (!allowed || await fs.realpath(current) !== aliases[current])
|
|
29
|
+
fail('UNSAFE_TARGET', 'Link/junction no destino ou ancestral não autorizado; use o destino real após revisão.');
|
|
30
|
+
} else if (!info.isDirectory()) fail('UNSAFE_TARGET', 'Destino e ancestrais devem ser diretórios.');
|
|
31
|
+
}
|
|
32
|
+
return fs.realpath(absolute);
|
|
33
|
+
}
|
|
34
|
+
export async function checkedPath(root, relative) {
|
|
35
|
+
if (!safeRelative(relative)) fail('UNSAFE_PATH', 'Caminho fora do contrato do template.');
|
|
36
|
+
const rootInfo = await stat(root);
|
|
37
|
+
if (rootInfo && (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()))
|
|
38
|
+
fail('UNSAFE_TARGET', 'Raiz alterada ou insegura.');
|
|
39
|
+
let current = root;
|
|
40
|
+
const parts = relative.split('/');
|
|
41
|
+
for (let i = 0; i < parts.length; i++) {
|
|
42
|
+
current = path.join(current, parts[i]);
|
|
43
|
+
const info = await stat(current);
|
|
44
|
+
if (info?.isSymbolicLink()) fail('UNSAFE_PATH', `Link recusado: ${relative}`);
|
|
45
|
+
if (info && i < parts.length - 1 && !info.isDirectory())
|
|
46
|
+
fail('UNSAFE_PATH', `Diretório esperado: ${relative}`);
|
|
47
|
+
if (info && i === parts.length - 1 && !info.isFile())
|
|
48
|
+
fail('UNSAFE_PATH', `Arquivo regular esperado: ${relative}`);
|
|
49
|
+
}
|
|
50
|
+
return current;
|
|
51
|
+
}
|
|
52
|
+
export async function readKnown(root, relative) {
|
|
53
|
+
const file = await checkedPath(root, relative);
|
|
54
|
+
const info = await stat(file);
|
|
55
|
+
if (!info) return null;
|
|
56
|
+
if (info.size > 2 * 1024 * 1024) fail('FILE_TOO_LARGE', `Arquivo excede limite de 2 MiB: ${relative}`);
|
|
57
|
+
return fs.readFile(file);
|
|
58
|
+
}
|
|
59
|
+
export async function createExclusive(root, relative, content) {
|
|
60
|
+
const file = await checkedPath(root, relative);
|
|
61
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
62
|
+
// Revalidar depois da criação dos diretórios; wx nunca substitui um arquivo.
|
|
63
|
+
await checkedPath(root, relative);
|
|
64
|
+
const handle = await fs.open(file, 'wx', 0o644);
|
|
65
|
+
try { await handle.writeFile(content); await handle.sync(); }
|
|
66
|
+
catch (e) { e.createdPath = relative; throw e; }
|
|
67
|
+
finally {
|
|
68
|
+
try { await handle.close(); }
|
|
69
|
+
catch (e) { e.createdPath = relative; throw e; }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { stat, readKnown, checkedPath } from './filesystem.js';
|
|
5
|
+
import { fail } from '../core/errors.js';
|
|
6
|
+
|
|
7
|
+
const samePath = (a, b) => process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
8
|
+
const blocked = (code, message) => ({ safe: false, code, message, exitCode: 2 });
|
|
9
|
+
function runGit(root, args) {
|
|
10
|
+
// Sem shell, rede, hooks ou configurações globais; somente comandos fixos de inspeção.
|
|
11
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !k.startsWith('GIT_')));
|
|
12
|
+
// Usar o caminho reconhecido pelo Git, inclusive Git for Windows, não os.devNull.
|
|
13
|
+
Object.assign(env, { GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null',
|
|
14
|
+
GIT_OPTIONAL_LOCKS: '0', GIT_TERMINAL_PROMPT: '0', GIT_NO_REPLACE_OBJECTS: '1', LC_ALL: 'C' });
|
|
15
|
+
const r = spawnSync('git', ['--no-optional-locks', '-c', 'core.fsmonitor=false', ...args],
|
|
16
|
+
{ cwd: root, env, encoding: 'utf8', shell: false, timeout: 10000, maxBuffer: 1024 * 1024, windowsHide: true });
|
|
17
|
+
if (r.error || r.signal) fail(r.error?.code === 'ENOENT' ? 'GIT_REQUIRED' : 'GIT_IO_ERROR',
|
|
18
|
+
'Git executável disponível e permissões de leitura são necessários para a ferramenta.', 1);
|
|
19
|
+
if (/permission denied|input\/output error|cannot allocate memory/i.test(r.stderr))
|
|
20
|
+
fail('GIT_IO_ERROR', 'Falha operacional ao consultar Git; confira permissões.', 1);
|
|
21
|
+
return { status: r.status, text: r.stdout.trim() };
|
|
22
|
+
}
|
|
23
|
+
// Inspeção somente por filesystem, compartilhada pelos quatro comandos.
|
|
24
|
+
// Não seguir links nem abrir arquivos antes de validar a árvore completa.
|
|
25
|
+
export async function assertGitMetadataIsolation(root) {
|
|
26
|
+
const marker = await stat(path.join(root, '.git'));
|
|
27
|
+
if (!marker) fail('GIT_MISSING', 'Metadados Git locais ausentes; preparação humana necessária.');
|
|
28
|
+
if (!marker.isDirectory() || marker.isSymbolicLink())
|
|
29
|
+
fail('GIT_EXTERNAL_METADATA', 'Metadados Git indiretos não são suportados.');
|
|
30
|
+
let count = 0;
|
|
31
|
+
const walk = async (relative, depth = 0) => {
|
|
32
|
+
if (++count > 100000 || depth > 64)
|
|
33
|
+
fail('GIT_METADATA_LIMIT', 'Metadados excedem o limite verificável (100000 entradas/64 níveis).');
|
|
34
|
+
const file = path.join(root, relative);
|
|
35
|
+
const info = await fs.lstat(file);
|
|
36
|
+
if (info.isSymbolicLink() || (!info.isDirectory() && !info.isFile()) || (info.isFile() && info.nlink !== 1))
|
|
37
|
+
fail('GIT_EXTERNAL_METADATA', 'Link, hardlink ou arquivo especial em metadados Git; isolamento não comprovado.');
|
|
38
|
+
if (!samePath(await fs.realpath(file), file))
|
|
39
|
+
fail('GIT_EXTERNAL_METADATA', 'Caminho Git redirecionado; isolamento não comprovado.');
|
|
40
|
+
const key = relative.toLowerCase();
|
|
41
|
+
// Bloquear mecanismos indiretos mesmo se o arquivo estiver vazio.
|
|
42
|
+
if (['.git/commondir', '.git/gitdir', '.git/config.worktree', '.git/objects/info/alternates',
|
|
43
|
+
'.git/objects/info/http-alternates', '.git/info/grafts', '.git/refs/replace', '.git/reftable'].includes(key) ||
|
|
44
|
+
(key.startsWith('.git/objects/') && key.endsWith('.promisor')))
|
|
45
|
+
fail('GIT_EXTERNAL_METADATA', 'Armazenamento Git compartilhado, promisor, grafts ou reftable não suportado.');
|
|
46
|
+
if (info.isDirectory()) {
|
|
47
|
+
for (const name of await fs.readdir(file)) await walk(relative + '/' + name, depth + 1);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
await walk('.git');
|
|
51
|
+
for (const name of ['HEAD', 'config', 'objects', 'refs']) {
|
|
52
|
+
const info = await stat(path.join(root, '.git', name));
|
|
53
|
+
if (!info || (['objects', 'refs'].includes(name) ? !info.isDirectory() : !info.isFile()))
|
|
54
|
+
fail('GIT_INVALID', 'Metadados Git incompletos ou inseguros.');
|
|
55
|
+
}
|
|
56
|
+
const config = await readKnown(root, '.git/config');
|
|
57
|
+
if (/^\s*\[\s*include(?:if)?(?:\s|[.\]])/im.test(config.toString('utf8')) ||
|
|
58
|
+
/(?:^|\])\s*(?:worktree|worktreeconfig|promisor|partialclone|refstorage)\s*(?:=|[#;]|$)/im.test(config.toString('utf8')))
|
|
59
|
+
fail('GIT_EXTERNAL_METADATA', 'Configuração indireta, promisor ou armazenamento de refs alternativo não suportado.');
|
|
60
|
+
}
|
|
61
|
+
export async function inspectGit(root) {
|
|
62
|
+
return inspectRepository(root, false, false);
|
|
63
|
+
}
|
|
64
|
+
export async function inspectDiscoveryGit(root, allowNoGit = false) {
|
|
65
|
+
return inspectRepository(root, true, allowNoGit);
|
|
66
|
+
}
|
|
67
|
+
async function inspectRepository(root, discovery, allowNoGit) {
|
|
68
|
+
if (Object.keys(process.env).some(k => /^GIT_(DIR|WORK_TREE|COMMON_DIR|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES|CONFIG.*)$/.test(k)))
|
|
69
|
+
return blocked('GIT_ENV_OVERRIDE', 'Remova overrides Git antes de usar a ferramenta.');
|
|
70
|
+
const marker = await stat(path.join(root, '.git'));
|
|
71
|
+
let parent = path.dirname(root);
|
|
72
|
+
while (true) {
|
|
73
|
+
if (await stat(path.join(parent, '.git')))
|
|
74
|
+
return blocked(marker ? 'GIT_NESTED' : 'GIT_PARENT', 'Repositório pai/aninhado detectado; revisão manual necessária.');
|
|
75
|
+
const next = path.dirname(parent);
|
|
76
|
+
if (next === parent) break;
|
|
77
|
+
parent = next;
|
|
78
|
+
}
|
|
79
|
+
if (!marker && discovery && allowNoGit) return { safe: true, code: 'NO_GIT_LIMITED', mode: 'no-git', exitCode: 0 };
|
|
80
|
+
if (!marker) return blocked('GIT_MISSING', 'Inicialize Git e crie branch de task manualmente, com autorização; a CLI não executa git init.');
|
|
81
|
+
if (!marker.isDirectory() || marker.isSymbolicLink())
|
|
82
|
+
return blocked('GIT_EXTERNAL_METADATA', 'Worktree/submódulo ou metadados indiretos exigem revisão manual.');
|
|
83
|
+
try { await assertGitMetadataIsolation(root); }
|
|
84
|
+
catch (e) {
|
|
85
|
+
if (e.exitCode === 2) return blocked(e.code, e.message);
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
await checkedPath(root, '.git/packed-refs');
|
|
89
|
+
const headText = (await readKnown(root, '.git/HEAD')).toString('utf8');
|
|
90
|
+
if (headText.startsWith('ref:')) {
|
|
91
|
+
const candidate = /^ref: (refs\/heads\/[^\r\n]+)\r?\n?$/.exec(headText);
|
|
92
|
+
if (!candidate) return blocked('GIT_INVALID', 'Referência HEAD incompleta ou inválida.');
|
|
93
|
+
await checkedPath(root, `.git/${candidate[1]}`);
|
|
94
|
+
}
|
|
95
|
+
const top = runGit(root, ['rev-parse', '--show-toplevel']);
|
|
96
|
+
const inside = runGit(root, ['rev-parse', '--is-inside-work-tree']);
|
|
97
|
+
const bare = runGit(root, ['rev-parse', '--is-bare-repository']);
|
|
98
|
+
if (top.status || inside.status || bare.status || inside.text !== 'true' || bare.text !== 'false')
|
|
99
|
+
return blocked('GIT_INVALID', 'Repositório Git não reconhecido como árvore de trabalho válida.');
|
|
100
|
+
const gitDir = runGit(root, ['rev-parse', '--absolute-git-dir']);
|
|
101
|
+
const common = runGit(root, ['rev-parse', '--git-common-dir']);
|
|
102
|
+
if (gitDir.status || common.status || !samePath(await fs.realpath(top.text), root) ||
|
|
103
|
+
!samePath(await fs.realpath(gitDir.text), path.join(root, '.git')) ||
|
|
104
|
+
!samePath(await fs.realpath(path.resolve(root, common.text)), path.join(root, '.git')))
|
|
105
|
+
return blocked('GIT_ROOT_MISMATCH', 'Raiz ou metadados Git não correspondem ao alvo aprovado.');
|
|
106
|
+
const ref = runGit(root, ['symbolic-ref', '--quiet', 'HEAD']);
|
|
107
|
+
if (ref.status === 1 && discovery) {
|
|
108
|
+
if (runGit(root, ['rev-parse', '--verify', 'HEAD^{commit}']).status) return blocked('GIT_INVALID', 'HEAD destacado inválido.');
|
|
109
|
+
return { safe: true, code: 'GIT_OK', mode: 'git', head: 'detached', exitCode: 0 };
|
|
110
|
+
}
|
|
111
|
+
if (ref.status === 1) return blocked('GIT_DETACHED', 'HEAD destacado; crie branch de task manualmente.');
|
|
112
|
+
if (ref.status || !ref.text.startsWith('refs/heads/') || runGit(root, ['check-ref-format', ref.text]).status)
|
|
113
|
+
return blocked('GIT_INVALID', 'Referência HEAD/branch inválida.');
|
|
114
|
+
await checkedPath(root, `.git/${ref.text}`);
|
|
115
|
+
const branch = ref.text.slice('refs/heads/'.length);
|
|
116
|
+
if (!discovery && ['main', 'master'].includes(branch.toLowerCase()))
|
|
117
|
+
return blocked('GIT_PROTECTED', 'Branch protegida; crie branch de task antes da instalação.');
|
|
118
|
+
const commit = runGit(root, ['rev-parse', '--verify', 'HEAD^{commit}']);
|
|
119
|
+
// Um repositório novo pode ter branch válida ainda sem commit (unborn).
|
|
120
|
+
if (commit.status && runGit(root, ['show-ref', '--verify', '--quiet', ref.text]).status !== 1)
|
|
121
|
+
return blocked('GIT_INVALID', 'HEAD aponta para objeto ausente ou inválido.');
|
|
122
|
+
return { safe: true, code: 'GIT_OK', ...(discovery ? { mode: 'git', head: commit.status ? 'unborn' : 'attached' } : {}), message: 'Raiz, metadados e branch Git real verificados.', exitCode: 0 };
|
|
123
|
+
}
|
package/stacks/nodejs.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# PHP / Laravel — checklist
|
|
2
|
+
- Identificar versões por `composer.json` e `composer.lock`, `php -v` se ambiente autorizado.
|
|
3
|
+
- Mapear `artisan`, `routes/`, `app/`, `config/`, `database/migrations`, `tests/`.
|
|
4
|
+
- Verificar versão do PHP, extensões, Composer, banco, Node e compatibilidade de pacotes.
|
|
5
|
+
- Evitar atualizar Laravel/PHP durante mudanças urgentes; preparar plano em separado.
|
|
6
|
+
- Comandos que criem/alterem banco, seeds e migrations seguem `rules/database.md`: MANUAL_SQL por padrão; G2 não autoriza execução. Exigir modo permitido, DB_CHANGE e DB_EXEC específicos. Não executar escrita em MANUAL_SQL.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Architecture — [projeto]
|
|
2
|
+
## Contexto e limites
|
|
3
|
+
## Componentes, responsabilidades e integrações
|
|
4
|
+
## Diagrama (texto ou Mermaid)
|
|
5
|
+
## Fluxos críticos e segurança
|
|
6
|
+
## Modelo de dados (sem dados reais)
|
|
7
|
+
## Decisões arquiteturais e trade-offs
|
|
8
|
+
## Dívida técnica e plano futuro
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Baseline — [projeto]
|
|
2
|
+
Status G2: [...] | Status G3: pendente
|
|
3
|
+
## Comportamento esperado antes da alteração
|
|
4
|
+
## Fluxos testados
|
|
5
|
+
| Cenário | Ambiente | Comando/procedimento | Resultado | Evidência |
|
|
6
|
+
|---|---|---|---|---|
|
|
7
|
+
## Falhas já existentes
|
|
8
|
+
## Lacunas de cobertura
|
|
9
|
+
## Riscos de regressão
|
|
10
|
+
## Decisão G3
|
|
11
|
+
|
|
12
|
+
## Projeto novo sem aplicação
|
|
13
|
+
Tipo: [pré-implementação / executável] | Referência preservada da baseline anterior: [...]
|
|
14
|
+
Inventário e ferramentas realmente verificadas: [...]
|
|
15
|
+
Testes indisponíveis sem bootstrap e motivo: [...]
|
|
16
|
+
G3 aprova ponto de partida e lacunas; não aprova funcionalidades inexistentes. Bootstrap só após G4; baseline executável registrada em P06.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Estado atual — [projeto]
|
|
2
|
+
Atualizado em: [AAAA-MM-DD]
|
|
3
|
+
## Trilha e fase atual
|
|
4
|
+
## Stack e ambiente confirmado
|
|
5
|
+
## Última task concluída / ativa
|
|
6
|
+
## Funcionalidades verificadas
|
|
7
|
+
## Mudanças locais não commitadas
|
|
8
|
+
## Bloqueios e riscos
|
|
9
|
+
## Aprovações pendentes (G0–G6)
|
|
10
|
+
## Próximos passos em ordem
|
|
11
|
+
## Referências de documentos / PRs
|
|
12
|
+
|
|
13
|
+
## Modo de banco e aprovações canônicas
|
|
14
|
+
Modo do projeto: MANUAL_SQL | Task e override humano (se houver): [...]
|
|
15
|
+
IDs/revisões vigentes em DECISIONS, rejeições/revogações e revalidações pendentes: [...]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# TASK-XXX — Plano e controle de banco
|
|
2
|
+
## Modo e evidências
|
|
3
|
+
Modo do projeto: [MANUAL_SQL por padrão; herdar escolha humana vigente] | Modo da task: [...]
|
|
4
|
+
DB_MODE (escolha/override humano): [...] | Executor: [...]
|
|
5
|
+
Motor e versão real: [...] | Evidência sanitizada: [...]
|
|
6
|
+
Ambiente/alvo: [...] | Schema, engine, charset/collation e SQL mode: [...]
|
|
7
|
+
Se desconhecido, bloquear SQL executável dependente dessas informações. Para outro motor/interface, acordar dialeto e procedimento sem alterar o modo implicitamente.
|
|
8
|
+
## Aprovações canônicas
|
|
9
|
+
G2: [...] | G4: [...] | DB_CHANGE: [...] | DB_EXEC: [...] | G6 se produção: [...]
|
|
10
|
+
Revisão/hash dos scripts: [...] | Janela/validade/repetições autorizadas: [...]
|
|
11
|
+
G4 e escolha de modo não autorizam escrita. MANUAL_SQL permite apenas preparação pelo agente.
|
|
12
|
+
## Artefatos por task
|
|
13
|
+
Pasta: `docs/tasks/TASK-XXX/sql/` (sem dumps, credenciais ou dados reais).
|
|
14
|
+
- `01-precheck.sql`: versão, schema, pré-condições e resultados esperados.
|
|
15
|
+
- `02-change.sql`: mudança compatível com o motor/versão confirmados.
|
|
16
|
+
- `03-postcheck.sql`: invariantes e resultados esperados.
|
|
17
|
+
- `04-rollback.sql`: reversão segura quando possível; ausência justificada abaixo.
|
|
18
|
+
- `README.md`: roteiro manual de phpMyAdmin e referências das aprovações.
|
|
19
|
+
## Riscos e backup
|
|
20
|
+
Locks, indisponibilidade, perda de dados, commits implícitos, repetição e dependências: [...]
|
|
21
|
+
Backup necessário, responsável, referência segura e evidência de restauração validada: [...]
|
|
22
|
+
## Execução manual no phpMyAdmin
|
|
23
|
+
Humano autorizado confirma servidor/banco/versão, backup e aprovações; abre a aba SQL do banco correto, executa precheck e confere resultados; executa a mudança aprovada por etapas; executa postcheck e registra evidências sanitizadas. Não prosseguir se pré-condições falharem. Não repetir nem executar reversão sem conferir autorização específica.
|
|
24
|
+
Adaptar a interface e etapas à instalação real. O agente não executa nem simula resultados.
|
|
25
|
+
## Validação e condições de parada
|
|
26
|
+
Critérios de sucesso/falha, observações e responsável: [...]
|
|
27
|
+
## Reversão ou compensação
|
|
28
|
+
Possibilidade técnica, limitações transacionais/DDL, perda irrecuperável e restauração: [...]
|
|
29
|
+
Comandos de reversão exigem autorização própria de execução; nunca rollback automático.
|
|
30
|
+
## Resultado real
|
|
31
|
+
Não executado / executor / data / evidências / desvios / IDs de autorizações: [...]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Registro canônico de decisões e aprovações
|
|
2
|
+
Única fonte de aprovações; tasks e estado referenciam IDs. Não registrar segredos. Preservar histórico por novos registros vinculados ao anterior.
|
|
3
|
+
|
|
4
|
+
| ID | Data | Tipo/portão | Estado | Responsável humano / evidência da manifestação | Ação, escopo e ambiente/alvo | Executor | Artefato e revisão/hash | Restrições / validade | ID anterior / motivo |
|
|
5
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
6
|
+
|
|
7
|
+
Tipos: G0–G6, DB_MODE, DB_CHANGE, DB_EXEC ou ADR.
|
|
8
|
+
Estados: pendente, aprovado, rejeitado, revogado, revalidação pendente.
|
|
9
|
+
Rejeição/revogação bloqueia a ação; mudança relevante exige revalidação humana. Silêncio não é aprovação. A task não substitui DB_EXEC. Usar revisão documental identificável quando não houver commit. Para DB_EXEC registrar comandos/scripts, executor, alvo, janela e limite de repetições. O agente apenas transcreve manifestações humanas explícitas com evidência sanitizada.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Discovery — [projeto]
|
|
2
|
+
Data: [AAAA-MM-DD] | Trilha: existente | Responsável: [...] | Status G1: pendente
|
|
3
|
+
## Escopo/limitações
|
|
4
|
+
## Evidências (caminho:linha ou comando seguro)
|
|
5
|
+
## Stack (declarada / confirmada)
|
|
6
|
+
| Camada | Tecnologia | Versão declarada | Versão confirmada | Evidência |
|
|
7
|
+
|---|---|---|---|---|
|
|
8
|
+
## Mapa de módulos e fluxos
|
|
9
|
+
## Banco de dados (sem dados reais)
|
|
10
|
+
## Dependências e pontos críticos
|
|
11
|
+
## Riscos e lacunas
|
|
12
|
+
## Próximas ações propostas
|
|
13
|
+
## Aprovação G1 (quem, quando, escopo)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Environment — [projeto]
|
|
2
|
+
Status G1: [...] | Status G2: pendente
|
|
3
|
+
## Pré-requisitos e versões compatíveis
|
|
4
|
+
## SO / arquitetura / Docker
|
|
5
|
+
## Serviços, portas e volumes
|
|
6
|
+
## Variáveis de ambiente (somente nomes, sem valores secretos)
|
|
7
|
+
## Comandos propostos (alvo e riscos)
|
|
8
|
+
## Isolamento do banco de teste
|
|
9
|
+
## Reprodução / troubleshooting
|
|
10
|
+
## Testes de inicialização e evidências
|
|
11
|
+
## Aprovação G2 da configuração (ID em DECISIONS)
|
|
12
|
+
G2 não autoriza inicialização, migrations ou seeders.
|
|
13
|
+
## Modo de operação de banco do projeto
|
|
14
|
+
Modo: MANUAL_SQL (padrão até escolha humana explícita)
|
|
15
|
+
Escolha DB_MODE (ID): [...] | Motor/versão real e evidência: [...]
|
|
16
|
+
Alvos autorizados sem credenciais: [...]
|
|
17
|
+
DB_CHANGE e DB_EXEC são separados e específicos por task/execução; ver `rules/database.md`.
|
|
18
|
+
## Entrada e bootstrap
|
|
19
|
+
Discovery (existente) ou REQUIREMENTS/ARCHITECTURE (novo), G1: [...]
|
|
20
|
+
Projeto novo: não criar aplicação antes da task de bootstrap com G4.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Checklist de PR
|
|
2
|
+
- [ ] Task e G4 documentados
|
|
3
|
+
- [ ] Branch isolada; diff revisado
|
|
4
|
+
- [ ] Sem credenciais, dumps nem dados reais
|
|
5
|
+
- [ ] Testes relevantes executados e evidências anexadas
|
|
6
|
+
- [ ] Compatibilidade validada; migrations/rollback planejados
|
|
7
|
+
- [ ] Docs/CURRENT_STATE atualizados
|
|
8
|
+
- [ ] Revisão G5 realizada
|
|
9
|
+
- [ ] Deploy separado sujeito a G6
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Requirements — projeto novo
|
|
2
|
+
## Problema / usuários / objetivo
|
|
3
|
+
## Funcionalidades do MVP e fora do escopo
|
|
4
|
+
## Requisitos funcionais e não funcionais
|
|
5
|
+
## Dados e privacidade
|
|
6
|
+
## Alternativas de stack e trade-offs
|
|
7
|
+
## Critérios de aceitação
|
|
8
|
+
## Riscos / perguntas abertas
|
|
9
|
+
## Aprovação G1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# TASK-XXX — [título]
|
|
2
|
+
Status: proposta | Branch: [feature/TASK-XXX-...] | Responsável: [...] | Aprovação G4: pendente
|
|
3
|
+
## Contexto e objetivo
|
|
4
|
+
## Fora do escopo
|
|
5
|
+
## Requisitos e critérios de aceitação (verificáveis)
|
|
6
|
+
## Arquivos/áreas possivelmente impactados
|
|
7
|
+
## Dados, segurança e riscos
|
|
8
|
+
## Plano de implementação (curto)
|
|
9
|
+
## Plano de testes e ambiente
|
|
10
|
+
## Rollback / reversão
|
|
11
|
+
## Dependências e decisões abertas
|
|
12
|
+
## Aprovação explícita G4
|
|
13
|
+
|
|
14
|
+
## Isolamento e aprovações
|
|
15
|
+
Raiz Git confirmada: [...] | Git-dir/common-dir: [...] | Branch efetiva: [...]
|
|
16
|
+
IDs em `docs/project/DECISIONS.md`: [...] | Revisão dos artefatos: [...]
|
|
17
|
+
Estados: pendente/aprovado/rejeitado/revogado/revalidação pendente. Escopo alterado exige revalidação.
|
|
18
|
+
|
|
19
|
+
## Controle de banco (obrigatório, inclusive quando não aplicável)
|
|
20
|
+
Banco envolvido: [sim/não/desconhecido; evidência]
|
|
21
|
+
Modo do projeto: [MANUAL_SQL por padrão; herdar escolha humana vigente] | Modo efetivo da task: [...]
|
|
22
|
+
Escolha humana/override DB_MODE (ID): [...] | Motor e versão real/evidência: [...]
|
|
23
|
+
Alvo/ambiente sem credenciais: [...] | Executor: [...]
|
|
24
|
+
G2 ambiente (ID): [...] | DB_CHANGE plano (ID): [...] | DB_EXEC execução concreta (ID): [...] | G6 produção, se aplicável: [...]
|
|
25
|
+
**G4 não autoriza modificar banco. Sem DB_EXEC específico, não executar migrations, seeders, SQL de escrita ou scripts/testes que alterem banco. MANUAL_SQL proíbe execução pelo agente.**
|
|
26
|
+
Entrega SQL/roteiro: `docs/tasks/TASK-XXX/sql/` conforme `templates/DATABASE_CHANGE.md`, ou não aplicável com justificativa.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# TASK-XXX — Relatório de execução
|
|
2
|
+
Data: [...] | Branch / commit: [...] | Status: [...]
|
|
3
|
+
## Resumo da alteração
|
|
4
|
+
## Arquivos criados/alterados
|
|
5
|
+
## Testes executados
|
|
6
|
+
| Comando | Ambiente | Resultado | Evidência/observação |
|
|
7
|
+
|---|---|---|---|
|
|
8
|
+
## Testes não executados e motivo
|
|
9
|
+
## Segurança/compatibilidade e riscos restantes
|
|
10
|
+
## Como reverter
|
|
11
|
+
## Checklist para revisão G5
|
|
12
|
+
## Próximas tarefas / CURRENT_STATE atualizado?
|
|
13
|
+
|
|
14
|
+
## Controles de execução
|
|
15
|
+
Raiz Git e branch verificadas: [...]
|
|
16
|
+
IDs/revisões em DECISIONS e revalidações necessárias: [...]
|
|
17
|
+
Modo de banco do projeto/task: MANUAL_SQL / [...]
|
|
18
|
+
DB_CHANGE/DB_EXEC/G2/G6 aplicáveis: [...] | Executor e alvo: [...]
|
|
19
|
+
SQL/roteiro entregues: [...] | Execução real e evidências: [...]
|
|
20
|
+
Em MANUAL_SQL marcar: nenhuma escrita de banco executada pelo agente.
|