oxe-cc 0.3.9 → 0.6.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.
Files changed (77) hide show
  1. package/.cursor/commands/oxe-execute.md +2 -2
  2. package/.cursor/commands/oxe-loop.md +11 -0
  3. package/.cursor/commands/oxe-milestone.md +11 -0
  4. package/.cursor/commands/oxe-obs.md +11 -0
  5. package/.cursor/commands/oxe-project.md +11 -0
  6. package/.cursor/commands/oxe-quick.md +2 -2
  7. package/.cursor/commands/oxe-security.md +11 -0
  8. package/.cursor/commands/oxe-spec.md +2 -2
  9. package/.cursor/commands/oxe-workstream.md +11 -0
  10. package/.cursor/commands/oxe.md +9 -0
  11. package/.github/prompts/oxe-execute.prompt.md +12 -12
  12. package/.github/prompts/oxe-loop.prompt.md +12 -0
  13. package/.github/prompts/oxe-milestone.prompt.md +12 -0
  14. package/.github/prompts/oxe-obs.prompt.md +12 -0
  15. package/.github/prompts/oxe-project.prompt.md +12 -0
  16. package/.github/prompts/oxe-quick.prompt.md +12 -12
  17. package/.github/prompts/oxe-security.prompt.md +12 -0
  18. package/.github/prompts/oxe-spec.prompt.md +2 -2
  19. package/.github/prompts/oxe-workstream.prompt.md +12 -0
  20. package/.github/prompts/oxe.prompt.md +12 -0
  21. package/README.md +287 -544
  22. package/bin/banner.txt +1 -1
  23. package/bin/lib/oxe-plugins.cjs +226 -0
  24. package/bin/lib/oxe-project-health.cjs +97 -1
  25. package/bin/lib/oxe-security.cjs +225 -0
  26. package/bin/oxe-cc.js +30 -1
  27. package/commands/oxe/execute.md +16 -16
  28. package/commands/oxe/loop.md +17 -0
  29. package/commands/oxe/milestone.md +16 -0
  30. package/commands/oxe/obs.md +16 -0
  31. package/commands/oxe/oxe.md +16 -0
  32. package/commands/oxe/project.md +16 -0
  33. package/commands/oxe/quick.md +16 -16
  34. package/commands/oxe/security.md +16 -0
  35. package/commands/oxe/spec.md +2 -2
  36. package/commands/oxe/workstream.md +16 -0
  37. package/lib/sdk/index.cjs +284 -0
  38. package/lib/sdk/index.d.ts +148 -1
  39. package/oxe/personas/README.md +39 -0
  40. package/oxe/personas/architect.md +37 -0
  41. package/oxe/personas/db-specialist.md +36 -0
  42. package/oxe/personas/debugger.md +38 -0
  43. package/oxe/personas/executor.md +38 -0
  44. package/oxe/personas/planner.md +36 -0
  45. package/oxe/personas/researcher.md +35 -0
  46. package/oxe/personas/ui-specialist.md +36 -0
  47. package/oxe/personas/verifier.md +39 -0
  48. package/oxe/templates/CONFIG.md +54 -4
  49. package/oxe/templates/DISCUSS.template.md +44 -0
  50. package/oxe/templates/MEMORY.template.md +49 -0
  51. package/oxe/templates/MILESTONES.template.md +17 -0
  52. package/oxe/templates/OBSERVATIONS.template.md +18 -0
  53. package/oxe/templates/PLUGINS.md +101 -0
  54. package/oxe/templates/ROADMAP.template.md +44 -0
  55. package/oxe/templates/SECURITY.template.md +72 -0
  56. package/oxe/templates/STATE.md +29 -2
  57. package/oxe/templates/config.template.json +5 -0
  58. package/oxe/templates/plan-agents.template.json +3 -2
  59. package/oxe/templates/quick-agents.template.json +24 -0
  60. package/oxe/workflows/discuss.md +48 -5
  61. package/oxe/workflows/execute.md +133 -28
  62. package/oxe/workflows/help.md +105 -24
  63. package/oxe/workflows/loop.md +57 -0
  64. package/oxe/workflows/milestone.md +96 -0
  65. package/oxe/workflows/obs.md +95 -0
  66. package/oxe/workflows/oxe.md +115 -0
  67. package/oxe/workflows/plan-agent.md +50 -3
  68. package/oxe/workflows/plan.md +7 -2
  69. package/oxe/workflows/project.md +50 -0
  70. package/oxe/workflows/quick.md +120 -10
  71. package/oxe/workflows/research.md +16 -0
  72. package/oxe/workflows/scan.md +23 -1
  73. package/oxe/workflows/security.md +61 -0
  74. package/oxe/workflows/spec.md +172 -23
  75. package/oxe/workflows/verify.md +80 -18
  76. package/oxe/workflows/workstream.md +96 -0
  77. package/package.json +3 -2
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: oxe:loop
3
+ description: OXE — Execução iterativa de onda com retries automáticos e diagnóstico inline
4
+ argument-hint: "onda <N> [max:<tentativas>]"
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ - Glob
9
+ - Grep
10
+ - Write
11
+ - Edit
12
+ - Task
13
+ ---
14
+
15
+ **Workflow canônico:** `oxe/workflows/loop.md`
16
+
17
+ Execute integralmente esse ficheiro na raiz do repositório. `$ARGUMENTS` = onda alvo e máximo de tentativas. Pré-requisito: `.oxe/PLAN.md`.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: oxe:milestone
3
+ description: "Marcos de entrega (M-NN) — new, complete (arquiva SPEC/PLAN/VERIFY em .oxe/milestones/M-NN/), status, audit"
4
+ argument-hint: "new <nome> | complete | status | audit"
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ - Glob
9
+ - Grep
10
+ - Write
11
+ - Task
12
+ ---
13
+
14
+ **Workflow canónico:** `oxe/workflows/milestone.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 (ex.: `new sprint-1`, `complete`, `audit`).
@@ -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.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: oxe
3
+ description: OXE — Entrada universal: próximo passo, roteamento de linguagem natural ou help dos 8 comandos essenciais
4
+ argument-hint: "[contexto | 'help' | vazio]"
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ - Glob
9
+ - Grep
10
+ - Write
11
+ - Task
12
+ ---
13
+
14
+ **Workflow canônico:** `oxe/workflows/oxe.md`
15
+
16
+ Execute integralmente esse ficheiro. `$ARGUMENTS`: vazio → próximo passo; texto → roteamento inteligente; "help" → 8 comandos essenciais.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: oxe:project
3
+ description: OXE — Gestão de projeto: milestone (M-NN), workstream (trilhas paralelas), checkpoint (snapshot)
4
+ argument-hint: "milestone new|complete|status|audit | workstream new|switch|list|close <nome> | checkpoint [slug]"
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ - Glob
9
+ - Grep
10
+ - Write
11
+ - Task
12
+ ---
13
+
14
+ **Workflow canônico:** `oxe/workflows/project.md`
15
+
16
+ Execute integralmente esse ficheiro. `$ARGUMENTS` = subcomando. Sem argumento: mostra status atual (milestone ativo, workstreams, último checkpoint).
@@ -1,16 +1,16 @@
1
- ---
2
- name: oxe:quick
3
- description: Modo rápido — .oxe/QUICK.md + STATE (sem SPEC/PLAN longos)
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.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: oxe:security
3
+ description: OXE — Auditoria de segurança OWASP (.oxe/SECURITY.md): P0 crítico / P1 alto / P2 médio
4
+ argument-hint: "[opcional: categoria OWASP, módulo ou arquivo]"
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ - Glob
9
+ - Grep
10
+ - Write
11
+ - Task
12
+ ---
13
+
14
+ **Workflow canônico:** `oxe/workflows/security.md`
15
+
16
+ Execute integralmente esse ficheiro na raiz do repositório. Lê `STACK.md` para determinar categorias OWASP pertinentes. `$ARGUMENTS` = foco opcional.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: oxe:spec
3
- description: Transforma o pedido do usuário em SPEC.md (escopo, aceite, não-objetivos)
4
- argument-hint: "[texto do pedido ou @arquivo.md]"
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
  };