oxe-cc 0.3.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/commands/oxe-checkpoint.md +2 -0
- package/.cursor/commands/oxe-compact.md +2 -0
- package/.cursor/commands/oxe-debug.md +2 -0
- package/.cursor/commands/oxe-discuss.md +2 -0
- package/.cursor/commands/oxe-execute.md +3 -1
- package/.cursor/commands/oxe-forensics.md +2 -0
- package/.cursor/commands/oxe-help.md +2 -0
- package/.cursor/commands/oxe-milestone.md +11 -0
- package/.cursor/commands/oxe-next.md +2 -0
- package/.cursor/commands/oxe-obs.md +11 -0
- package/.cursor/commands/oxe-plan-agent.md +2 -0
- package/.cursor/commands/oxe-plan.md +2 -0
- package/.cursor/commands/oxe-quick.md +3 -1
- package/.cursor/commands/oxe-research.md +2 -0
- package/.cursor/commands/oxe-review-pr.md +2 -0
- package/.cursor/commands/oxe-route.md +2 -0
- package/.cursor/commands/oxe-scan.md +2 -0
- package/.cursor/commands/oxe-spec.md +3 -1
- package/.cursor/commands/oxe-ui-review.md +2 -0
- package/.cursor/commands/oxe-ui-spec.md +2 -0
- package/.cursor/commands/oxe-update.md +2 -0
- package/.cursor/commands/oxe-validate-gaps.md +2 -0
- package/.cursor/commands/oxe-verify.md +2 -0
- package/.cursor/commands/oxe-workstream.md +11 -0
- package/.github/prompts/oxe-execute.prompt.md +12 -12
- package/.github/prompts/oxe-milestone.prompt.md +12 -0
- package/.github/prompts/oxe-obs.prompt.md +12 -0
- package/.github/prompts/oxe-quick.prompt.md +12 -12
- package/.github/prompts/oxe-spec.prompt.md +2 -2
- package/.github/prompts/oxe-workstream.prompt.md +12 -0
- package/README.md +205 -442
- package/bin/banner.txt +1 -1
- package/bin/lib/oxe-plugins.cjs +226 -0
- package/bin/lib/oxe-project-health.cjs +97 -1
- package/bin/lib/oxe-security.cjs +225 -0
- package/bin/oxe-cc.js +29 -0
- package/commands/oxe/execute.md +16 -16
- package/commands/oxe/milestone.md +16 -0
- package/commands/oxe/obs.md +16 -0
- package/commands/oxe/quick.md +16 -16
- package/commands/oxe/spec.md +2 -2
- package/commands/oxe/workstream.md +16 -0
- package/lib/sdk/index.cjs +284 -0
- package/lib/sdk/index.d.ts +148 -1
- package/oxe/personas/README.md +39 -0
- package/oxe/personas/architect.md +37 -0
- package/oxe/personas/db-specialist.md +36 -0
- package/oxe/personas/debugger.md +38 -0
- package/oxe/personas/executor.md +38 -0
- package/oxe/personas/planner.md +36 -0
- package/oxe/personas/researcher.md +35 -0
- package/oxe/personas/ui-specialist.md +36 -0
- package/oxe/personas/verifier.md +39 -0
- package/oxe/templates/CONFIG.md +58 -4
- package/oxe/templates/DISCUSS.template.md +44 -0
- package/oxe/templates/MEMORY.template.md +49 -0
- package/oxe/templates/MILESTONES.template.md +17 -0
- package/oxe/templates/OBSERVATIONS.template.md +18 -0
- package/oxe/templates/PLUGINS.md +101 -0
- package/oxe/templates/ROADMAP.template.md +44 -0
- package/oxe/templates/STATE.md +29 -2
- package/oxe/templates/config.template.json +5 -0
- package/oxe/templates/plan-agent-messages-README.template.md +1 -1
- package/oxe/templates/quick-agents.template.json +24 -0
- package/oxe/workflows/discuss.md +48 -5
- package/oxe/workflows/execute.md +111 -28
- package/oxe/workflows/help.md +80 -15
- package/oxe/workflows/milestone.md +96 -0
- package/oxe/workflows/obs.md +95 -0
- package/oxe/workflows/plan-agent.md +31 -1
- package/oxe/workflows/plan.md +5 -1
- package/oxe/workflows/quick.md +120 -10
- package/oxe/workflows/references/plan-agent-chat-protocol.md +14 -0
- package/oxe/workflows/scan.md +9 -1
- package/oxe/workflows/spec.md +172 -23
- package/oxe/workflows/verify.md +77 -17
- package/oxe/workflows/workstream.md +96 -0
- package/package.json +3 -2
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oxe:obs
|
|
3
|
+
description: "Observação contextual — registra em .oxe/OBSERVATIONS.md, incorporada automaticamente no próximo spec/plan/execute sem re-explicar"
|
|
4
|
+
argument-hint: "[observação: restrição, descoberta, preferência, risco ou decisão]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Bash
|
|
8
|
+
- Glob
|
|
9
|
+
- Grep
|
|
10
|
+
- Write
|
|
11
|
+
- Task
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
**Workflow canónico:** `oxe/workflows/obs.md`
|
|
15
|
+
|
|
16
|
+
Execute integralmente esse ficheiro na raiz do repositório em que estás a trabalhar. Usa o texto em `$ARGUMENTS` como a observação a registrar.
|
package/commands/oxe/quick.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: oxe:quick
|
|
3
|
-
description: Modo rápido —
|
|
4
|
-
argument-hint: "[objetivo]"
|
|
5
|
-
allowed-tools:
|
|
6
|
-
- Read
|
|
7
|
-
- Bash
|
|
8
|
-
- Glob
|
|
9
|
-
- Grep
|
|
10
|
-
- Write
|
|
11
|
-
- Task
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
**Workflow canónico:** `oxe/workflows/quick.md`
|
|
15
|
-
|
|
16
|
-
Execute integralmente esse ficheiro na raiz do repositório em que estás a trabalhar. Usa o texto em `$ARGUMENTS` como objetivo e contexto.
|
|
1
|
+
---
|
|
2
|
+
name: oxe:quick
|
|
3
|
+
description: "Modo rápido com Plan-Driven Dynamic Agents lean — minispec (objetivo) + mini-plano (passos) + agentes dinâmicos por domínio (opcional) + verificar"
|
|
4
|
+
argument-hint: "[objetivo] [--agents]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Bash
|
|
8
|
+
- Glob
|
|
9
|
+
- Grep
|
|
10
|
+
- Write
|
|
11
|
+
- Task
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
**Workflow canónico:** `oxe/workflows/quick.md`
|
|
15
|
+
|
|
16
|
+
Execute integralmente esse ficheiro na raiz do repositório em que estás a trabalhar. Usa o texto em `$ARGUMENTS` como objetivo e contexto.
|
package/commands/oxe/spec.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oxe:spec
|
|
3
|
-
description:
|
|
4
|
-
argument-hint: "[
|
|
3
|
+
description: "Spec em 5 fases: perguntas → pesquisa → requisitos R-ID (v1/v2/fora) → roteiro ROADMAP.md → aprovação → plan"
|
|
4
|
+
argument-hint: "[descrição da feature, ideia ou @arquivo.md com PRD]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oxe:workstream
|
|
3
|
+
description: "Trilhas paralelas — list, new <nome>, switch <nome>, status, close <nome> — artefatos independentes em .oxe/workstreams/<nome>/"
|
|
4
|
+
argument-hint: "list | new <nome> | switch <nome> | status | close <nome>"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Bash
|
|
8
|
+
- Glob
|
|
9
|
+
- Grep
|
|
10
|
+
- Write
|
|
11
|
+
- Task
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
**Workflow canónico:** `oxe/workflows/workstream.md`
|
|
15
|
+
|
|
16
|
+
Execute integralmente esse ficheiro na raiz do repositório em que estás a trabalhar. Usa o texto em `$ARGUMENTS` como subcomando e contexto.
|
package/lib/sdk/index.cjs
CHANGED
|
@@ -12,6 +12,8 @@ const manifest = require('../../bin/lib/oxe-manifest.cjs');
|
|
|
12
12
|
const workflows = require('../../bin/lib/oxe-workflows.cjs');
|
|
13
13
|
const installResolve = require('../../bin/lib/oxe-install-resolve.cjs');
|
|
14
14
|
const agentInstall = require('../../bin/lib/oxe-agent-install.cjs');
|
|
15
|
+
const security = require('../../bin/lib/oxe-security.cjs');
|
|
16
|
+
const plugins = require('../../bin/lib/oxe-plugins.cjs');
|
|
15
17
|
|
|
16
18
|
const PACKAGE_ROOT = path.join(__dirname, '..', '..');
|
|
17
19
|
|
|
@@ -38,6 +40,224 @@ function readMinNode(packageRoot) {
|
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Faz parse de um PLAN.md e extrai tarefas estruturadas.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} planMd - Conteúdo do PLAN.md (string).
|
|
47
|
+
* @returns {{
|
|
48
|
+
* tasks: Array<{
|
|
49
|
+
* id: string,
|
|
50
|
+
* title: string,
|
|
51
|
+
* wave: number | null,
|
|
52
|
+
* dependsOn: string[],
|
|
53
|
+
* files: string[],
|
|
54
|
+
* verifyCommand: string | null,
|
|
55
|
+
* aceite: string[],
|
|
56
|
+
* decisions: string[],
|
|
57
|
+
* done: boolean,
|
|
58
|
+
* meta: Record<string, unknown> | null,
|
|
59
|
+
* }>,
|
|
60
|
+
* waves: Record<number, string[]>,
|
|
61
|
+
* totalTasks: number,
|
|
62
|
+
* }}
|
|
63
|
+
*/
|
|
64
|
+
function parsePlan(planMd) {
|
|
65
|
+
const parts = planMd.split(/^###\s+(T\d+)\s*[—-]\s*/m);
|
|
66
|
+
/** @type {ReturnType<typeof parsePlan>['tasks']} */
|
|
67
|
+
const tasks = [];
|
|
68
|
+
/** @type {Record<number, string[]>} */
|
|
69
|
+
const waves = {};
|
|
70
|
+
|
|
71
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
72
|
+
const id = parts[i].trim();
|
|
73
|
+
const rest = (parts[i + 1] || '').split(/^###\s+T\d+/m)[0];
|
|
74
|
+
const titleMatch = rest.match(/^([^\n]+)/);
|
|
75
|
+
const title = titleMatch ? titleMatch[1].trim() : '';
|
|
76
|
+
|
|
77
|
+
const waveMatch = rest.match(/\*\*Onda:\*\*\s*(\d+)/i);
|
|
78
|
+
const wave = waveMatch ? parseInt(waveMatch[1], 10) : null;
|
|
79
|
+
|
|
80
|
+
const depsMatch = rest.match(/\*\*Depende\s+de:\*\*\s*([^\n]+)/i);
|
|
81
|
+
const dependsOn = depsMatch
|
|
82
|
+
? depsMatch[1].split(/[,\s]+/).filter((s) => /^T\d+$/.test(s.trim()))
|
|
83
|
+
: [];
|
|
84
|
+
|
|
85
|
+
const filesMatch = rest.match(/\*\*Arquivos\s+prováveis:\*\*\s*([^\n]+)/i);
|
|
86
|
+
const files = filesMatch
|
|
87
|
+
? filesMatch[1].match(/`([^`]+)`/g)?.map((s) => s.replace(/`/g, '')) || []
|
|
88
|
+
: [];
|
|
89
|
+
|
|
90
|
+
const verifyCmdMatch = rest.match(/Comando:\s*`([^`]+)`/i);
|
|
91
|
+
const verifyCommand = verifyCmdMatch ? verifyCmdMatch[1] : null;
|
|
92
|
+
|
|
93
|
+
const aceiteMatch = rest.match(/\*\*Aceite\s+vinculado:\*\*\s*([^\n]+)/i);
|
|
94
|
+
const aceite = aceiteMatch
|
|
95
|
+
? aceiteMatch[1].match(/A\d+/g) || []
|
|
96
|
+
: [];
|
|
97
|
+
|
|
98
|
+
const decisionsMatch = rest.match(/\*\*Decisão\s+vinculada:\*\*\s*([^\n]+)/i);
|
|
99
|
+
const decisions = decisionsMatch
|
|
100
|
+
? decisionsMatch[1].match(/D-\d+/g) || []
|
|
101
|
+
: [];
|
|
102
|
+
|
|
103
|
+
// Parse do metadado JSON em comentário HTML <!-- oxe-task: {...} -->
|
|
104
|
+
const metaMatch = rest.match(/<!--\s*oxe-task:\s*(\{[\s\S]*?\})\s*-->/);
|
|
105
|
+
let meta = null;
|
|
106
|
+
if (metaMatch) {
|
|
107
|
+
try {
|
|
108
|
+
meta = JSON.parse(metaMatch[1]);
|
|
109
|
+
} catch {
|
|
110
|
+
meta = null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const done = meta?.done === true;
|
|
115
|
+
|
|
116
|
+
const task = { id, title, wave, dependsOn, files, verifyCommand, aceite, decisions, done, meta };
|
|
117
|
+
tasks.push(task);
|
|
118
|
+
|
|
119
|
+
if (wave != null) {
|
|
120
|
+
if (!waves[wave]) waves[wave] = [];
|
|
121
|
+
waves[wave].push(id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { tasks, waves, totalTasks: tasks.length };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Faz parse de um SPEC.md e extrai critérios de aceite.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} specMd - Conteúdo do SPEC.md.
|
|
132
|
+
* @returns {{
|
|
133
|
+
* objective: string | null,
|
|
134
|
+
* criteria: Array<{ id: string, criterion: string, howToVerify: string }>,
|
|
135
|
+
* requiredSections: string[],
|
|
136
|
+
* }}
|
|
137
|
+
*/
|
|
138
|
+
function parseSpec(specMd) {
|
|
139
|
+
// Objetivo
|
|
140
|
+
const objMatch = specMd.match(/##\s*Objetivo\s*\n+([\s\S]*?)(?=\n##\s|\n#[^\#]|$)/im);
|
|
141
|
+
const objective = objMatch ? objMatch[1].trim().split('\n')[0].trim() : null;
|
|
142
|
+
|
|
143
|
+
// Tabela de critérios
|
|
144
|
+
/** @type {Array<{ id: string, criterion: string, howToVerify: string }>} */
|
|
145
|
+
const criteria = [];
|
|
146
|
+
const tableMatch = specMd.match(/##\s*Critérios.*?aceite[\s\S]*?(\|[\s\S]*?)(?=\n##\s|\n#[^\#]|$)/im);
|
|
147
|
+
if (tableMatch) {
|
|
148
|
+
const rows = tableMatch[1].split('\n').filter((l) => l.startsWith('|'));
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
|
|
151
|
+
if (cells.length >= 2 && /^A\d+$/i.test(cells[0])) {
|
|
152
|
+
criteria.push({
|
|
153
|
+
id: cells[0],
|
|
154
|
+
criterion: cells[1] || '',
|
|
155
|
+
howToVerify: cells[2] || '',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Seções presentes (para validação spec_required_sections)
|
|
162
|
+
const headings = [];
|
|
163
|
+
for (const m of specMd.matchAll(/^##\s+(.+)/gm)) {
|
|
164
|
+
headings.push(`## ${m[1].trim()}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { objective, criteria, requiredSections: headings };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Faz parse do STATE.md e extrai fase, data do scan e próximo passo.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} stateMd - Conteúdo do STATE.md.
|
|
174
|
+
* @returns {{
|
|
175
|
+
* phase: string | null,
|
|
176
|
+
* lastScanDate: string | null,
|
|
177
|
+
* nextStep: string | null,
|
|
178
|
+
* decisions: string[],
|
|
179
|
+
* activeWorkstreams: string[],
|
|
180
|
+
* activeMilestone: string | null,
|
|
181
|
+
* }}
|
|
182
|
+
*/
|
|
183
|
+
function parseState(stateMd) {
|
|
184
|
+
const phase = health.parseStatePhase(stateMd);
|
|
185
|
+
|
|
186
|
+
const scanDate = health.parseLastScanDate(stateMd);
|
|
187
|
+
const lastScanDate = scanDate ? scanDate.toISOString().split('T')[0] : null;
|
|
188
|
+
|
|
189
|
+
const nextStepMatch = stateMd.match(/##\s*Próximo passo sugerido\s*\n+([\s\S]*?)(?=\n##\s|\n#[^\#]|$)/im);
|
|
190
|
+
const nextStep = nextStepMatch ? nextStepMatch[1].trim().split('\n')[0].replace(/^[-*]\s*/, '').trim() : null;
|
|
191
|
+
|
|
192
|
+
// Decisões persistentes (seção opcional)
|
|
193
|
+
const decisionsMatch = stateMd.match(/##\s*Decisões persistentes\s*\n+([\s\S]*?)(?=\n##\s|\n#[^\#]|$)/im);
|
|
194
|
+
const decisions = decisionsMatch
|
|
195
|
+
? decisionsMatch[1].match(/D-\d+[^)]*\)/g) || decisionsMatch[1].match(/D-\d+[^\n]*/g) || []
|
|
196
|
+
: [];
|
|
197
|
+
|
|
198
|
+
// Workstreams ativos (seção opcional)
|
|
199
|
+
const wsMatch = stateMd.match(/##\s*Workstreams ativos\s*\n+([\s\S]*?)(?=\n##\s|\n#[^\#]|$)/im);
|
|
200
|
+
const activeWorkstreams = wsMatch
|
|
201
|
+
? wsMatch[1].match(/`([^`]+)`/g)?.map((s) => s.replace(/`/g, '')) || []
|
|
202
|
+
: [];
|
|
203
|
+
|
|
204
|
+
// Milestone ativo
|
|
205
|
+
const msMatch = stateMd.match(/##\s*Milestone ativo\s*\n+[^`]*`([^`]+)`/im);
|
|
206
|
+
const activeMilestone = msMatch ? msMatch[1] : null;
|
|
207
|
+
|
|
208
|
+
return { phase, lastScanDate, nextStep, decisions, activeWorkstreams, activeMilestone };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Valida fidelidade entre decisões do DISCUSS.md e tarefas do PLAN.md.
|
|
213
|
+
* Retorna gaps onde decisões D-NN não têm tarefa vinculada.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} discussMd - Conteúdo do DISCUSS.md.
|
|
216
|
+
* @param {string} planMd - Conteúdo do PLAN.md.
|
|
217
|
+
* @returns {{
|
|
218
|
+
* ok: boolean,
|
|
219
|
+
* gaps: Array<{ decisionId: string, decision: string }>,
|
|
220
|
+
* covered: Array<{ decisionId: string, taskIds: string[] }>,
|
|
221
|
+
* }}
|
|
222
|
+
*/
|
|
223
|
+
function validateDecisionFidelity(discussMd, planMd) {
|
|
224
|
+
// Extrair decisões da tabela D-NN
|
|
225
|
+
const decisionRows = [];
|
|
226
|
+
const tableMatch = discussMd.match(/##\s*Decisões[\s\S]*?(\|[\s\S]*?)(?=\n##\s|\n#[^\#]|$)/im);
|
|
227
|
+
if (tableMatch) {
|
|
228
|
+
const rows = tableMatch[1].split('\n').filter((l) => l.startsWith('|'));
|
|
229
|
+
for (const row of rows) {
|
|
230
|
+
const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
|
|
231
|
+
if (cells.length >= 2 && /^D-\d+$/i.test(cells[0])) {
|
|
232
|
+
decisionRows.push({ id: cells[0], text: cells[1] || '' });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!decisionRows.length) return { ok: true, gaps: [], covered: [] };
|
|
238
|
+
|
|
239
|
+
const { tasks } = parsePlan(planMd);
|
|
240
|
+
|
|
241
|
+
/** @type {ReturnType<typeof validateDecisionFidelity>['gaps']} */
|
|
242
|
+
const gaps = [];
|
|
243
|
+
/** @type {ReturnType<typeof validateDecisionFidelity>['covered']} */
|
|
244
|
+
const covered = [];
|
|
245
|
+
|
|
246
|
+
for (const dec of decisionRows) {
|
|
247
|
+
// Ignorar decisões revertidas
|
|
248
|
+
if (/revertida/i.test(dec.text)) continue;
|
|
249
|
+
|
|
250
|
+
const tasksCovering = tasks.filter((t) => t.decisions.includes(dec.id));
|
|
251
|
+
if (tasksCovering.length === 0) {
|
|
252
|
+
gaps.push({ decisionId: dec.id, decision: dec.text });
|
|
253
|
+
} else {
|
|
254
|
+
covered.push({ decisionId: dec.id, taskIds: tasksCovering.map((t) => t.id) });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { ok: gaps.length === 0, gaps, covered };
|
|
259
|
+
}
|
|
260
|
+
|
|
41
261
|
/**
|
|
42
262
|
* Verificações alinhadas ao `oxe-cc doctor`, com resultado estruturado (CI / gateways).
|
|
43
263
|
*
|
|
@@ -47,6 +267,7 @@ function readMinNode(packageRoot) {
|
|
|
47
267
|
* nodeMajor?: number,
|
|
48
268
|
* includeWorkflowLint?: boolean,
|
|
49
269
|
* workflowLintOptions?: { maxBytesSoft?: number },
|
|
270
|
+
* includeSecurity?: boolean,
|
|
50
271
|
* }} args
|
|
51
272
|
* @returns {{
|
|
52
273
|
* ok: boolean,
|
|
@@ -60,6 +281,7 @@ function readMinNode(packageRoot) {
|
|
|
60
281
|
* validation: ReturnType<typeof health.validateConfigShape>,
|
|
61
282
|
* healthReport: ReturnType<typeof health.buildHealthReport>,
|
|
62
283
|
* workflowShape: ReturnType<typeof workflows.validateWorkflowShapes> | null,
|
|
284
|
+
* securityReport: { secretFiles: string[], pluginsValid: boolean } | null,
|
|
63
285
|
* }}
|
|
64
286
|
*/
|
|
65
287
|
function runDoctorChecks(args) {
|
|
@@ -157,6 +379,39 @@ function runDoctorChecks(args) {
|
|
|
157
379
|
}
|
|
158
380
|
}
|
|
159
381
|
|
|
382
|
+
// Verificações de segurança (opcional, ativado por padrão)
|
|
383
|
+
let securityReport = null;
|
|
384
|
+
if (args.includeSecurity !== false) {
|
|
385
|
+
const secretFiles = security.scanDirForSecretFiles(projectRoot, { maxDepth: 3 });
|
|
386
|
+
if (secretFiles.length > 0) {
|
|
387
|
+
warnings.push({
|
|
388
|
+
code: 'SECURITY_SECRET_FILES',
|
|
389
|
+
message: `Arquivos com nomes sensíveis detectados: ${secretFiles.join(', ')}`,
|
|
390
|
+
detail: { files: secretFiles },
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Verificar plugins se existirem
|
|
395
|
+
const pluginsDir = path.join(projectRoot, '.oxe', 'plugins');
|
|
396
|
+
let pluginsValid = true;
|
|
397
|
+
if (fs.existsSync(pluginsDir)) {
|
|
398
|
+
const pluginFiles = fs.readdirSync(pluginsDir).filter((f) => f.endsWith('.cjs'));
|
|
399
|
+
for (const pf of pluginFiles) {
|
|
400
|
+
const pluginPath = path.join(pluginsDir, pf);
|
|
401
|
+
const safetyCheck = security.checkPathSafety(pluginPath, projectRoot);
|
|
402
|
+
if (!safetyCheck.safe) {
|
|
403
|
+
pluginsValid = false;
|
|
404
|
+
warnings.push({
|
|
405
|
+
code: 'SECURITY_PLUGIN_PATH',
|
|
406
|
+
message: `Plugin com caminho inseguro: ${pf} — ${safetyCheck.reason}`,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
securityReport = { secretFiles, pluginsValid };
|
|
413
|
+
}
|
|
414
|
+
|
|
160
415
|
return {
|
|
161
416
|
ok: errors.length === 0,
|
|
162
417
|
errors,
|
|
@@ -169,6 +424,7 @@ function runDoctorChecks(args) {
|
|
|
169
424
|
validation,
|
|
170
425
|
healthReport,
|
|
171
426
|
workflowShape,
|
|
427
|
+
securityReport,
|
|
172
428
|
};
|
|
173
429
|
}
|
|
174
430
|
|
|
@@ -184,6 +440,12 @@ module.exports = {
|
|
|
184
440
|
readPackageMeta,
|
|
185
441
|
readMinNode,
|
|
186
442
|
|
|
443
|
+
/** Parsing de artefatos OXE (PLAN, SPEC, STATE). */
|
|
444
|
+
parsePlan,
|
|
445
|
+
parseSpec,
|
|
446
|
+
parseState,
|
|
447
|
+
validateDecisionFidelity,
|
|
448
|
+
|
|
187
449
|
/** Estado do projeto, SPEC/PLAN, fase, config. */
|
|
188
450
|
health: {
|
|
189
451
|
loadOxeConfigMerged: health.loadOxeConfigMerged,
|
|
@@ -200,7 +462,10 @@ module.exports = {
|
|
|
200
462
|
planWaveWarningsFixed: health.planWaveWarningsFixed,
|
|
201
463
|
planTaskAceiteWarnings: health.planTaskAceiteWarnings,
|
|
202
464
|
verifyGapsWithoutSummaryWarning: health.verifyGapsWithoutSummaryWarning,
|
|
465
|
+
expandExecutionProfile: health.expandExecutionProfile,
|
|
203
466
|
ALLOWED_CONFIG_KEYS: health.ALLOWED_CONFIG_KEYS,
|
|
467
|
+
EXECUTION_PROFILES: health.EXECUTION_PROFILES,
|
|
468
|
+
VERIFICATION_DEPTHS: health.VERIFICATION_DEPTHS,
|
|
204
469
|
INSTALL_PROFILES: health.INSTALL_PROFILES,
|
|
205
470
|
INSTALL_REPO_LAYOUTS: health.INSTALL_REPO_LAYOUTS,
|
|
206
471
|
INSTALL_OBJECT_KEYS: health.INSTALL_OBJECT_KEYS,
|
|
@@ -238,6 +503,25 @@ module.exports = {
|
|
|
238
503
|
parseCursorCommandFrontmatter: agentInstall.parseCursorCommandFrontmatter,
|
|
239
504
|
},
|
|
240
505
|
|
|
506
|
+
/** Segurança: validação de caminhos e detecção de segredos. */
|
|
507
|
+
security: {
|
|
508
|
+
checkPathSafety: security.checkPathSafety,
|
|
509
|
+
scanFileForSecrets: security.scanFileForSecrets,
|
|
510
|
+
scanDirForSecretFiles: security.scanDirForSecretFiles,
|
|
511
|
+
validatePlanPaths: security.validatePlanPaths,
|
|
512
|
+
DEFAULT_SECRET_PATTERNS: security.DEFAULT_SECRET_PATTERNS,
|
|
513
|
+
DEFAULT_SECRET_CONTENT_PATTERNS: security.DEFAULT_SECRET_CONTENT_PATTERNS,
|
|
514
|
+
DEFAULT_DENIED_PATH_PATTERNS: security.DEFAULT_DENIED_PATH_PATTERNS,
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
/** Plugin system — hooks de ciclo de vida em `.oxe/plugins/*.cjs`. */
|
|
518
|
+
plugins: {
|
|
519
|
+
loadPlugins: plugins.loadPlugins,
|
|
520
|
+
runHook: plugins.runHook,
|
|
521
|
+
validatePlugins: plugins.validatePlugins,
|
|
522
|
+
initPluginsDir: plugins.initPluginsDir,
|
|
523
|
+
},
|
|
524
|
+
|
|
241
525
|
/** Um único objeto com verificações tipo `doctor` + relatório de saúde. */
|
|
242
526
|
runDoctorChecks,
|
|
243
527
|
};
|
package/lib/sdk/index.d.ts
CHANGED
|
@@ -65,6 +65,11 @@ export interface OxeHealthReport {
|
|
|
65
65
|
scanIgnoreGlobs?: unknown;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export interface SecurityReport {
|
|
69
|
+
secretFiles: string[];
|
|
70
|
+
pluginsValid: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
export interface DoctorChecksResult {
|
|
69
74
|
ok: boolean;
|
|
70
75
|
errors: DoctorIssue[];
|
|
@@ -81,6 +86,91 @@ export interface DoctorChecksResult {
|
|
|
81
86
|
validation: { unknownKeys: string[]; typeErrors: string[] };
|
|
82
87
|
healthReport: OxeHealthReport;
|
|
83
88
|
workflowShape: WorkflowShapeResult | null;
|
|
89
|
+
securityReport: SecurityReport | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Tarefa parseada de PLAN.md. */
|
|
93
|
+
export interface ParsedTask {
|
|
94
|
+
id: string;
|
|
95
|
+
title: string;
|
|
96
|
+
wave: number | null;
|
|
97
|
+
dependsOn: string[];
|
|
98
|
+
files: string[];
|
|
99
|
+
verifyCommand: string | null;
|
|
100
|
+
aceite: string[];
|
|
101
|
+
decisions: string[];
|
|
102
|
+
done: boolean;
|
|
103
|
+
meta: Record<string, unknown> | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ParsedPlan {
|
|
107
|
+
tasks: ParsedTask[];
|
|
108
|
+
waves: Record<number, string[]>;
|
|
109
|
+
totalTasks: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ParsedCriterion {
|
|
113
|
+
id: string;
|
|
114
|
+
criterion: string;
|
|
115
|
+
howToVerify: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ParsedSpec {
|
|
119
|
+
objective: string | null;
|
|
120
|
+
criteria: ParsedCriterion[];
|
|
121
|
+
requiredSections: string[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface ParsedState {
|
|
125
|
+
phase: string | null;
|
|
126
|
+
lastScanDate: string | null;
|
|
127
|
+
nextStep: string | null;
|
|
128
|
+
decisions: string[];
|
|
129
|
+
activeWorkstreams: string[];
|
|
130
|
+
activeMilestone: string | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface DecisionFidelityResult {
|
|
134
|
+
ok: boolean;
|
|
135
|
+
gaps: Array<{ decisionId: string; decision: string }>;
|
|
136
|
+
covered: Array<{ decisionId: string; taskIds: string[] }>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface PathSafetyResult {
|
|
140
|
+
safe: boolean;
|
|
141
|
+
reason: string | null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface SecretMatch {
|
|
145
|
+
line: number;
|
|
146
|
+
pattern: string;
|
|
147
|
+
preview: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface SecretScanResult {
|
|
151
|
+
hasSecrets: boolean;
|
|
152
|
+
matches: SecretMatch[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface PlanPathsResult {
|
|
156
|
+
ok: boolean;
|
|
157
|
+
issues: Array<{ path: string; reason: string }>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface OxePlugin {
|
|
161
|
+
name: string;
|
|
162
|
+
version?: string;
|
|
163
|
+
hooks: Record<string, (ctx: Record<string, unknown>) => Promise<void> | void>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface PluginLoadResult {
|
|
167
|
+
plugins: OxePlugin[];
|
|
168
|
+
errors: Array<{ file: string; error: string }>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface PluginValidationResult {
|
|
172
|
+
valid: boolean;
|
|
173
|
+
issues: Array<{ file: string; issue: string }>;
|
|
84
174
|
}
|
|
85
175
|
|
|
86
176
|
export interface OxeSdk {
|
|
@@ -89,7 +179,38 @@ export interface OxeSdk {
|
|
|
89
179
|
PACKAGE_ROOT: string;
|
|
90
180
|
readPackageMeta: (root?: string) => PackageMeta;
|
|
91
181
|
readMinNode: (packageRoot: string) => number;
|
|
92
|
-
|
|
182
|
+
|
|
183
|
+
/** Parsing de artefatos OXE. */
|
|
184
|
+
parsePlan: (planMd: string) => ParsedPlan;
|
|
185
|
+
parseSpec: (specMd: string) => ParsedSpec;
|
|
186
|
+
parseState: (stateMd: string) => ParsedState;
|
|
187
|
+
validateDecisionFidelity: (discussMd: string, planMd: string) => DecisionFidelityResult;
|
|
188
|
+
|
|
189
|
+
health: {
|
|
190
|
+
loadOxeConfigMerged: (targetProject: string) => { config: Record<string, unknown>; path: string | null; parseError: string | null };
|
|
191
|
+
validateConfigShape: (cfg: Record<string, unknown>) => { unknownKeys: string[]; typeErrors: string[] };
|
|
192
|
+
buildHealthReport: (target: string) => OxeHealthReport;
|
|
193
|
+
suggestNextStep: (target: string, cfg?: { discuss_before_plan?: boolean }) => OxeNextSuggestion;
|
|
194
|
+
oxePaths: (target: string) => Record<string, string>;
|
|
195
|
+
parseStatePhase: (stateText: string) => string | null;
|
|
196
|
+
parseLastScanDate: (stateText: string) => Date | null;
|
|
197
|
+
parseLastCompactDate: (stateText: string) => Date | null;
|
|
198
|
+
isStaleScan: (scanDate: Date | null, maxAgeDays: number) => HealthStaleInfo;
|
|
199
|
+
phaseCoherenceWarnings: (phase: string, paths: Record<string, string>) => string[];
|
|
200
|
+
specSectionWarnings: (specPath: string, requiredHeadings: string[]) => string[];
|
|
201
|
+
planWaveWarningsFixed: (planPath: string, maxPerWave: number) => string[];
|
|
202
|
+
planTaskAceiteWarnings: (planPath: string) => string[];
|
|
203
|
+
verifyGapsWithoutSummaryWarning: (verifyPath: string, summaryPath: string) => string | null;
|
|
204
|
+
expandExecutionProfile: (profile: string) => Record<string, unknown>;
|
|
205
|
+
ALLOWED_CONFIG_KEYS: string[];
|
|
206
|
+
EXECUTION_PROFILES: string[];
|
|
207
|
+
VERIFICATION_DEPTHS: string[];
|
|
208
|
+
INSTALL_PROFILES: string[];
|
|
209
|
+
INSTALL_REPO_LAYOUTS: string[];
|
|
210
|
+
INSTALL_OBJECT_KEYS: string[];
|
|
211
|
+
EXPECTED_CODEBASE_MAPS: string[];
|
|
212
|
+
};
|
|
213
|
+
|
|
93
214
|
workflows: {
|
|
94
215
|
resolveWorkflowsDir: (targetProject: string) => string | null;
|
|
95
216
|
listWorkflowMdFiles: (workflowsDir: string) => string[];
|
|
@@ -101,20 +222,46 @@ export interface OxeSdk {
|
|
|
101
222
|
DEFAULT_MAX_BYTES_SOFT: number;
|
|
102
223
|
SUCCESS_CRITERIA_EXCEPTIONS: Set<string>;
|
|
103
224
|
};
|
|
225
|
+
|
|
104
226
|
install: {
|
|
105
227
|
resolveOptionsFromConfig: (
|
|
106
228
|
projectRoot: string,
|
|
107
229
|
optsIn: Record<string, unknown>
|
|
108
230
|
) => { options: Record<string, unknown>; warnings: string[] };
|
|
109
231
|
};
|
|
232
|
+
|
|
110
233
|
manifest: Record<string, unknown>;
|
|
111
234
|
agents: Record<string, unknown>;
|
|
235
|
+
|
|
236
|
+
security: {
|
|
237
|
+
checkPathSafety: (filePath: string, projectRoot: string, options?: {
|
|
238
|
+
allowedRoots?: string[];
|
|
239
|
+
deniedPatterns?: RegExp[];
|
|
240
|
+
secretPatterns?: RegExp[];
|
|
241
|
+
}) => PathSafetyResult;
|
|
242
|
+
scanFileForSecrets: (filePath: string, options?: { contentPatterns?: RegExp[] }) => SecretScanResult;
|
|
243
|
+
scanDirForSecretFiles: (dir: string, options?: { secretPatterns?: RegExp[]; maxDepth?: number }) => string[];
|
|
244
|
+
validatePlanPaths: (filePaths: string[], projectRoot: string) => PlanPathsResult;
|
|
245
|
+
DEFAULT_SECRET_PATTERNS: RegExp[];
|
|
246
|
+
DEFAULT_SECRET_CONTENT_PATTERNS: RegExp[];
|
|
247
|
+
DEFAULT_DENIED_PATH_PATTERNS: RegExp[];
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/** Plugin system — hooks de ciclo de vida em `.oxe/plugins/*.cjs`. */
|
|
251
|
+
plugins: {
|
|
252
|
+
loadPlugins: (projectRoot: string) => PluginLoadResult;
|
|
253
|
+
runHook: (plugins: OxePlugin[], hookName: string, ctx: Record<string, unknown>) => Promise<Array<{ plugin: string; error: string }>>;
|
|
254
|
+
validatePlugins: (projectRoot: string) => PluginValidationResult;
|
|
255
|
+
initPluginsDir: (projectRoot: string) => void;
|
|
256
|
+
};
|
|
257
|
+
|
|
112
258
|
runDoctorChecks: (args: {
|
|
113
259
|
projectRoot: string;
|
|
114
260
|
packageRoot?: string;
|
|
115
261
|
nodeMajor?: number;
|
|
116
262
|
includeWorkflowLint?: boolean;
|
|
117
263
|
workflowLintOptions?: { maxBytesSoft?: number };
|
|
264
|
+
includeSecurity?: boolean;
|
|
118
265
|
}) => DoctorChecksResult;
|
|
119
266
|
}
|
|
120
267
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# OXE — Personas de Agentes
|
|
2
|
+
|
|
3
|
+
Esta pasta contém **definições de personas** para uso nos workflows `/oxe-plan-agent` e `/oxe-execute`.
|
|
4
|
+
|
|
5
|
+
## O que é uma persona OXE?
|
|
6
|
+
|
|
7
|
+
Uma persona define o **comportamento esperado** de um contexto de agente focado. Não é um binário externo nem um serviço — é um conjunto de instruções que qualquer LLM (Claude, GPT, Gemini, etc.) pode seguir ao executar tarefas de um blueprint OXE.
|
|
8
|
+
|
|
9
|
+
## Como usar
|
|
10
|
+
|
|
11
|
+
No `/oxe-plan-agent`, referencie personas por ID no campo `persona` de cada agente:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"id": "agent-backend",
|
|
16
|
+
"role": "Backend Specialist",
|
|
17
|
+
"persona": "executor",
|
|
18
|
+
"scope": ["Implementar endpoints REST", "Escrever testes unitários"]
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
O workflow `/oxe-execute` carrega a persona correspondente e instrui o LLM a agir conforme as diretrizes definidas.
|
|
23
|
+
|
|
24
|
+
## Personas disponíveis
|
|
25
|
+
|
|
26
|
+
| ID | Papel | Foco |
|
|
27
|
+
|----|-------|------|
|
|
28
|
+
| `executor` | Implementador | Código funcional, commits atômicos, checklist do PLAN |
|
|
29
|
+
| `planner` | Planejador | Decomposição de tarefas, ondas, dependências |
|
|
30
|
+
| `verifier` | Verificador | Testes, critérios SPEC, evidências, UAT |
|
|
31
|
+
| `researcher` | Pesquisador | Investigação técnica, benchmarks, POCs |
|
|
32
|
+
| `debugger` | Depurador | Diagnóstico de falhas, root cause, hotfix |
|
|
33
|
+
| `architect` | Arquiteto | Estrutura, padrões, dívida técnica, escalabilidade |
|
|
34
|
+
| `ui-specialist` | Especialista UI | Componentes, acessibilidade, contratos de design |
|
|
35
|
+
| `db-specialist` | Especialista DB | Esquemas, migrações, queries, performance |
|
|
36
|
+
|
|
37
|
+
## Criando personas personalizadas
|
|
38
|
+
|
|
39
|
+
Copie qualquer arquivo `.md` desta pasta, altere o frontmatter e o conteúdo, e salve em `.oxe/personas/` no seu projeto. O OXE prioriza personas locais sobre as do pacote.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
oxe_persona: architect
|
|
3
|
+
name: Arquiteto
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: Define estrutura, padrões de design, trata dívida técnica e decisões de escalabilidade.
|
|
6
|
+
tools: [Read, Grep, Glob]
|
|
7
|
+
scope: architecture
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Persona: Arquiteto
|
|
11
|
+
|
|
12
|
+
## Identidade
|
|
13
|
+
|
|
14
|
+
Você é um guardião da qualidade estrutural. Seu trabalho é garantir que o sistema seja mantível, escalável e coerente — agora e nas próximas 10 entregas.
|
|
15
|
+
|
|
16
|
+
## Princípios
|
|
17
|
+
|
|
18
|
+
1. **Simplicidade primeiro.** A solução mais simples que satisfaz os requisitos é a melhor. Complexidade acidental é dívida técnica imediata.
|
|
19
|
+
2. **Coerência de padrões.** Novas estruturas devem seguir os padrões em `CONVENTIONS.md`. Se um padrão novo for necessário, documente-o antes de implementar.
|
|
20
|
+
3. **Dívida explícita.** Toda decisão de design com trade-offs vai para `CONCERNS.md`. Dívida não documentada é dívida invisível.
|
|
21
|
+
4. **Sem over-engineering.** Não projete para requisitos hipotéticos. Projete para o que o usuário pediu, com extensibilidade onde há sinal claro de crescimento.
|
|
22
|
+
5. **SPEC antes de estrutura.** A arquitetura serve a SPEC, não ao contrário. Se a estrutura proposta não entrega os critérios A*, ela está errada.
|
|
23
|
+
|
|
24
|
+
## Ao ser ativado
|
|
25
|
+
|
|
26
|
+
1. Ler `.oxe/SPEC.md` (requisitos a satisfazer).
|
|
27
|
+
2. Ler `.oxe/codebase/STRUCTURE.md`, `STACK.md`, `CONCERNS.md`, `CONVENTIONS.md`.
|
|
28
|
+
3. Identificar decisões arquiteturais necessárias (ex.: padrão de módulos, contrato de APIs, estrutura de dados).
|
|
29
|
+
4. Propor estrutura com justificativas e trade-offs.
|
|
30
|
+
5. Se houver DISCUSS.md em aberto: contribuir com perspectiva técnica para decisões D-NN relacionadas à arquitetura.
|
|
31
|
+
6. Atualizar `CONCERNS.md` se novas dívidas forem identificadas.
|
|
32
|
+
|
|
33
|
+
## Saída esperada
|
|
34
|
+
|
|
35
|
+
- Decisões arquiteturais documentadas (em DISCUSS.md ou diretamente no PLAN).
|
|
36
|
+
- `CONCERNS.md` atualizado com novos riscos se aplicável.
|
|
37
|
+
- Orientação clara para o planejador sobre estrutura de arquivos e padrões.
|