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.
- package/.cursor/commands/oxe-execute.md +2 -2
- package/.cursor/commands/oxe-loop.md +11 -0
- package/.cursor/commands/oxe-milestone.md +11 -0
- package/.cursor/commands/oxe-obs.md +11 -0
- package/.cursor/commands/oxe-project.md +11 -0
- package/.cursor/commands/oxe-quick.md +2 -2
- package/.cursor/commands/oxe-security.md +11 -0
- package/.cursor/commands/oxe-spec.md +2 -2
- package/.cursor/commands/oxe-workstream.md +11 -0
- package/.cursor/commands/oxe.md +9 -0
- package/.github/prompts/oxe-execute.prompt.md +12 -12
- package/.github/prompts/oxe-loop.prompt.md +12 -0
- package/.github/prompts/oxe-milestone.prompt.md +12 -0
- package/.github/prompts/oxe-obs.prompt.md +12 -0
- package/.github/prompts/oxe-project.prompt.md +12 -0
- package/.github/prompts/oxe-quick.prompt.md +12 -12
- package/.github/prompts/oxe-security.prompt.md +12 -0
- package/.github/prompts/oxe-spec.prompt.md +2 -2
- package/.github/prompts/oxe-workstream.prompt.md +12 -0
- package/.github/prompts/oxe.prompt.md +12 -0
- package/README.md +287 -544
- 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 +30 -1
- package/commands/oxe/execute.md +16 -16
- package/commands/oxe/loop.md +17 -0
- package/commands/oxe/milestone.md +16 -0
- package/commands/oxe/obs.md +16 -0
- package/commands/oxe/oxe.md +16 -0
- package/commands/oxe/project.md +16 -0
- package/commands/oxe/quick.md +16 -16
- package/commands/oxe/security.md +16 -0
- 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 +54 -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/SECURITY.template.md +72 -0
- package/oxe/templates/STATE.md +29 -2
- package/oxe/templates/config.template.json +5 -0
- package/oxe/templates/plan-agents.template.json +3 -2
- package/oxe/templates/quick-agents.template.json +24 -0
- package/oxe/workflows/discuss.md +48 -5
- package/oxe/workflows/execute.md +133 -28
- package/oxe/workflows/help.md +105 -24
- package/oxe/workflows/loop.md +57 -0
- package/oxe/workflows/milestone.md +96 -0
- package/oxe/workflows/obs.md +95 -0
- package/oxe/workflows/oxe.md +115 -0
- package/oxe/workflows/plan-agent.md +50 -3
- package/oxe/workflows/plan.md +7 -2
- package/oxe/workflows/project.md +50 -0
- package/oxe/workflows/quick.md +120 -10
- package/oxe/workflows/research.md +16 -0
- package/oxe/workflows/scan.md +23 -1
- package/oxe/workflows/security.md +61 -0
- package/oxe/workflows/spec.md +172 -23
- package/oxe/workflows/verify.md +80 -18
- package/oxe/workflows/workstream.md +96 -0
- 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).
|
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.
|
|
@@ -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.
|
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
|
};
|