oxe-cc 0.7.0 → 0.9.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-ask.md +34 -0
- package/.cursor/commands/oxe-capabilities.md +34 -0
- package/.cursor/commands/oxe-checkpoint.md +34 -0
- package/.cursor/commands/oxe-compact.md +33 -0
- package/.cursor/commands/oxe-dashboard.md +34 -0
- package/.cursor/commands/oxe-debug.md +34 -0
- package/.cursor/commands/oxe-discuss.md +34 -0
- package/.cursor/commands/oxe-execute.md +34 -0
- package/.cursor/commands/oxe-forensics.md +34 -0
- package/.cursor/commands/oxe-help.md +33 -0
- package/.cursor/commands/oxe-loop.md +34 -0
- package/.cursor/commands/oxe-milestone.md +34 -0
- package/.cursor/commands/oxe-next.md +33 -0
- package/.cursor/commands/oxe-obs.md +34 -0
- package/.cursor/commands/oxe-plan-agent.md +33 -0
- package/.cursor/commands/oxe-plan.md +34 -0
- package/.cursor/commands/oxe-project.md +34 -0
- package/.cursor/commands/oxe-quick.md +34 -0
- package/.cursor/commands/oxe-research.md +34 -0
- package/.cursor/commands/oxe-retro.md +34 -0
- package/.cursor/commands/oxe-review-pr.md +34 -0
- package/.cursor/commands/oxe-route.md +34 -0
- package/.cursor/commands/oxe-scan.md +34 -0
- package/.cursor/commands/oxe-security.md +34 -0
- package/.cursor/commands/oxe-session.md +34 -0
- package/.cursor/commands/oxe-skill.md +45 -0
- package/.cursor/commands/oxe-spec.md +34 -0
- package/.cursor/commands/oxe-ui-review.md +34 -0
- package/.cursor/commands/oxe-ui-spec.md +34 -0
- package/.cursor/commands/oxe-update.md +33 -0
- package/.cursor/commands/oxe-validate-gaps.md +34 -0
- package/.cursor/commands/oxe-verify.md +34 -0
- package/.cursor/commands/oxe-workstream.md +34 -0
- package/.cursor/commands/oxe.md +38 -2
- package/.github/copilot-instructions.md +20 -5
- package/.github/prompts/oxe-ask.prompt.md +33 -0
- package/.github/prompts/oxe-capabilities.prompt.md +33 -0
- package/.github/prompts/oxe-checkpoint.prompt.md +45 -12
- package/.github/prompts/oxe-compact.prompt.md +44 -11
- package/.github/prompts/oxe-dashboard.prompt.md +33 -0
- package/.github/prompts/oxe-debug.prompt.md +45 -12
- package/.github/prompts/oxe-discuss.prompt.md +33 -0
- package/.github/prompts/oxe-execute.prompt.md +45 -12
- package/.github/prompts/oxe-forensics.prompt.md +45 -12
- package/.github/prompts/oxe-help.prompt.md +42 -9
- package/.github/prompts/oxe-loop.prompt.md +45 -12
- package/.github/prompts/oxe-milestone.prompt.md +45 -12
- package/.github/prompts/oxe-next.prompt.md +42 -9
- package/.github/prompts/oxe-obs.prompt.md +45 -12
- package/.github/prompts/oxe-plan-agent.prompt.md +43 -10
- package/.github/prompts/oxe-plan.prompt.md +45 -12
- package/.github/prompts/oxe-project.prompt.md +45 -12
- package/.github/prompts/oxe-quick.prompt.md +45 -12
- package/.github/prompts/oxe-research.prompt.md +45 -12
- package/.github/prompts/oxe-retro.prompt.md +45 -12
- package/.github/prompts/oxe-review-pr.prompt.md +45 -12
- package/.github/prompts/oxe-route.prompt.md +45 -12
- package/.github/prompts/oxe-scan.prompt.md +45 -12
- package/.github/prompts/oxe-security.prompt.md +45 -12
- package/.github/prompts/oxe-session.prompt.md +33 -0
- package/.github/prompts/oxe-skill.prompt.md +45 -0
- package/.github/prompts/oxe-spec.prompt.md +45 -12
- package/.github/prompts/oxe-ui-review.prompt.md +45 -12
- package/.github/prompts/oxe-ui-spec.prompt.md +45 -12
- package/.github/prompts/oxe-update.prompt.md +44 -11
- package/.github/prompts/oxe-validate-gaps.prompt.md +45 -12
- package/.github/prompts/oxe-verify.prompt.md +45 -12
- package/.github/prompts/oxe-workstream.prompt.md +45 -12
- package/.github/prompts/oxe.prompt.md +45 -12
- package/AGENTS.md +6 -4
- package/CHANGELOG.md +45 -0
- package/README.md +38 -8
- package/bin/banner.txt +1 -1
- package/bin/lib/oxe-agent-install.cjs +69 -55
- package/bin/lib/oxe-context-engine.cjs +866 -0
- package/bin/lib/oxe-dashboard.cjs +605 -588
- package/bin/lib/oxe-operational.cjs +105 -0
- package/bin/lib/oxe-plugins.cjs +115 -0
- package/bin/lib/oxe-project-health.cjs +1139 -666
- package/bin/lib/oxe-runtime-semantics.cjs +459 -0
- package/bin/lib/oxe-security.cjs +64 -0
- package/bin/oxe-cc.js +615 -46
- package/commands/oxe/ask.md +33 -0
- package/commands/oxe/capabilities.md +33 -0
- package/commands/oxe/checkpoint.md +49 -16
- package/commands/oxe/compact.md +43 -10
- package/commands/oxe/dashboard.md +33 -0
- package/commands/oxe/debug.md +49 -16
- package/commands/oxe/discuss.md +33 -0
- package/commands/oxe/execute.md +49 -16
- package/commands/oxe/forensics.md +49 -16
- package/commands/oxe/help.md +44 -11
- package/commands/oxe/loop.md +50 -17
- package/commands/oxe/milestone.md +49 -16
- package/commands/oxe/next.md +45 -12
- package/commands/oxe/obs.md +49 -16
- package/commands/oxe/oxe.md +49 -16
- package/commands/oxe/plan-agent.md +48 -15
- package/commands/oxe/plan.md +48 -15
- package/commands/oxe/project.md +49 -16
- package/commands/oxe/quick.md +49 -16
- package/commands/oxe/research.md +49 -16
- package/commands/oxe/retro.md +49 -16
- package/commands/oxe/review-pr.md +49 -16
- package/commands/oxe/route.md +44 -11
- package/commands/oxe/scan.md +49 -16
- package/commands/oxe/security.md +49 -16
- package/commands/oxe/session.md +33 -0
- package/commands/oxe/skill.md +49 -0
- package/commands/oxe/spec.md +47 -14
- package/commands/oxe/ui-review.md +49 -16
- package/commands/oxe/ui-spec.md +49 -16
- package/commands/oxe/update.md +49 -16
- package/commands/oxe/validate-gaps.md +49 -16
- package/commands/oxe/verify.md +48 -15
- package/commands/oxe/workstream.md +49 -16
- package/lib/sdk/index.cjs +140 -7
- package/lib/sdk/index.d.ts +266 -1
- package/oxe/templates/HYPOTHESES.template.md +33 -0
- package/oxe/templates/PLAN.template.md +53 -22
- package/oxe/templates/SESSION.template.md +2 -0
- package/oxe/templates/SKILL.template.md +26 -0
- package/oxe/templates/WORKFLOW_AUTHORING.md +18 -2
- package/oxe/templates/config.template.json +16 -14
- package/oxe/workflows/ask.md +28 -7
- package/oxe/workflows/capabilities.md +2 -0
- package/oxe/workflows/dashboard.md +12 -2
- package/oxe/workflows/debug.md +9 -4
- package/oxe/workflows/discuss.md +12 -6
- package/oxe/workflows/execute.md +34 -12
- package/oxe/workflows/forensics.md +14 -9
- package/oxe/workflows/help.md +20 -9
- package/oxe/workflows/loop.md +13 -7
- package/oxe/workflows/next.md +6 -4
- package/oxe/workflows/plan-agent.md +3 -2
- package/oxe/workflows/plan.md +26 -3
- package/oxe/workflows/quick.md +10 -3
- package/oxe/workflows/references/reasoning-discovery.md +28 -0
- package/oxe/workflows/references/reasoning-execution.md +29 -0
- package/oxe/workflows/references/reasoning-planning.md +32 -0
- package/oxe/workflows/references/reasoning-review.md +29 -0
- package/oxe/workflows/references/reasoning-status.md +24 -0
- package/oxe/workflows/references/workflow-runtime-contracts.json +879 -0
- package/oxe/workflows/research.md +8 -2
- package/oxe/workflows/retro.md +7 -2
- package/oxe/workflows/review-pr.md +12 -8
- package/oxe/workflows/route.md +16 -13
- package/oxe/workflows/security.md +3 -2
- package/oxe/workflows/session.md +44 -0
- package/oxe/workflows/skill.md +44 -0
- package/oxe/workflows/spec.md +21 -18
- package/oxe/workflows/ui-review.md +13 -7
- package/oxe/workflows/update.md +3 -1
- package/oxe/workflows/validate-gaps.md +12 -6
- package/oxe/workflows/verify-audit.md +73 -0
- package/oxe/workflows/verify.md +40 -16
- package/package.json +4 -3
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const operational = require('./oxe-operational.cjs');
|
|
7
|
+
const azure = require('./oxe-azure.cjs');
|
|
7
8
|
|
|
8
9
|
/** @type {string[]} */
|
|
9
10
|
const ALLOWED_CONFIG_KEYS = [
|
|
@@ -19,17 +20,18 @@ const ALLOWED_CONFIG_KEYS = [
|
|
|
19
20
|
'scan_ignore_globs',
|
|
20
21
|
'spec_required_sections',
|
|
21
22
|
'plan_max_tasks_per_wave',
|
|
22
|
-
'profile',
|
|
23
|
-
'verification_depth',
|
|
24
|
-
'plan_confidence_threshold',
|
|
25
|
-
'security_in_verify',
|
|
23
|
+
'profile',
|
|
24
|
+
'verification_depth',
|
|
25
|
+
'plan_confidence_threshold',
|
|
26
|
+
'security_in_verify',
|
|
26
27
|
'install',
|
|
27
28
|
'plugins',
|
|
28
|
-
'workstreams',
|
|
29
|
-
'milestones',
|
|
30
|
-
'scale_adaptive',
|
|
31
|
-
'
|
|
32
|
-
|
|
29
|
+
'workstreams',
|
|
30
|
+
'milestones',
|
|
31
|
+
'scale_adaptive',
|
|
32
|
+
'permissions',
|
|
33
|
+
'azure',
|
|
34
|
+
];
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Profiles de execução OXE que controlam rigor do workflow.
|
|
@@ -113,6 +115,25 @@ function expandExecutionProfile(profile) {
|
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
/**
|
|
118
|
+
* Lê um JSON config de um caminho; retorna null se não existir ou falhar.
|
|
119
|
+
* @param {string} filePath
|
|
120
|
+
* @returns {Record<string, unknown> | null}
|
|
121
|
+
*/
|
|
122
|
+
function _readJsonConfig(filePath) {
|
|
123
|
+
try {
|
|
124
|
+
if (!fs.existsSync(filePath)) return null;
|
|
125
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
126
|
+
const j = JSON.parse(raw);
|
|
127
|
+
if (!j || typeof j !== 'object' || Array.isArray(j)) return null;
|
|
128
|
+
return j;
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Carrega e mescla config em 3 níveis: system < user < project (project tem maior prioridade).
|
|
136
|
+
* Retorna { config, path, parseError, sources }.
|
|
116
137
|
* @param {string} targetProject
|
|
117
138
|
*/
|
|
118
139
|
function loadOxeConfigMerged(targetProject) {
|
|
@@ -120,37 +141,82 @@ function loadOxeConfigMerged(targetProject) {
|
|
|
120
141
|
discuss_before_plan: false,
|
|
121
142
|
after_verify_suggest_pr: true,
|
|
122
143
|
after_verify_draft_commit: true,
|
|
123
|
-
after_verify_suggest_uat: false,
|
|
124
|
-
verification_depth: 'standard',
|
|
125
|
-
plan_confidence_threshold: 70,
|
|
126
|
-
default_verify_command: '',
|
|
144
|
+
after_verify_suggest_uat: false,
|
|
145
|
+
verification_depth: 'standard',
|
|
146
|
+
plan_confidence_threshold: 70,
|
|
147
|
+
default_verify_command: '',
|
|
127
148
|
scan_max_age_days: 0,
|
|
128
149
|
compact_max_age_days: 0,
|
|
129
150
|
scan_focus_globs: [],
|
|
130
151
|
scan_ignore_globs: [],
|
|
131
|
-
spec_required_sections: [],
|
|
132
|
-
plan_max_tasks_per_wave: 0,
|
|
133
|
-
azure: {
|
|
134
|
-
enabled: false,
|
|
135
|
-
default_resource_group: '',
|
|
136
|
-
preferred_locations: [],
|
|
137
|
-
inventory_max_age_hours: 24,
|
|
138
|
-
resource_graph_auto_install: true,
|
|
139
|
-
vpn_required: false,
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
spec_required_sections: [],
|
|
153
|
+
plan_max_tasks_per_wave: 0,
|
|
154
|
+
azure: {
|
|
155
|
+
enabled: false,
|
|
156
|
+
default_resource_group: '',
|
|
157
|
+
preferred_locations: [],
|
|
158
|
+
inventory_max_age_hours: 24,
|
|
159
|
+
resource_graph_auto_install: true,
|
|
160
|
+
vpn_required: false,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const sources = { system: null, user: null, project: null };
|
|
165
|
+
|
|
166
|
+
// Nível system: OXE_SYSTEM_CONFIG env var → fallback OS-specific
|
|
167
|
+
const systemPath = process.env.OXE_SYSTEM_CONFIG
|
|
168
|
+
|| (process.platform === 'win32'
|
|
169
|
+
? path.join(process.env.PROGRAMDATA || 'C:\\ProgramData', 'oxe', 'config.json')
|
|
170
|
+
: '/etc/oxe/config.json');
|
|
171
|
+
const systemCfg = _readJsonConfig(systemPath);
|
|
172
|
+
if (systemCfg) sources.system = systemPath;
|
|
173
|
+
|
|
174
|
+
// Nível user: ~/.oxe/config.json
|
|
175
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
176
|
+
const userPath = home ? path.join(home, '.oxe', 'config.json') : null;
|
|
177
|
+
const userCfg = userPath ? _readJsonConfig(userPath) : null;
|
|
178
|
+
if (userCfg) sources.user = userPath;
|
|
179
|
+
|
|
180
|
+
// Nível project: .oxe/config.json (comportamento existente)
|
|
181
|
+
const projectPath = path.join(targetProject, '.oxe', 'config.json');
|
|
182
|
+
let projectCfg = null;
|
|
183
|
+
let projectParseError = null;
|
|
184
|
+
if (fs.existsSync(projectPath)) {
|
|
185
|
+
try {
|
|
186
|
+
const raw = fs.readFileSync(projectPath, 'utf8');
|
|
187
|
+
const j = JSON.parse(raw);
|
|
188
|
+
if (!j || typeof j !== 'object' || Array.isArray(j)) {
|
|
189
|
+
projectParseError = 'não é um objeto';
|
|
190
|
+
} else {
|
|
191
|
+
projectCfg = j;
|
|
192
|
+
}
|
|
193
|
+
} catch (e) {
|
|
194
|
+
projectParseError = String(e.message || e);
|
|
195
|
+
}
|
|
153
196
|
}
|
|
197
|
+
if (projectCfg) sources.project = projectPath;
|
|
198
|
+
|
|
199
|
+
// Merge: system < user < project (project vence)
|
|
200
|
+
const merged = { ...defaults };
|
|
201
|
+
for (const layer of [systemCfg, userCfg, projectCfg]) {
|
|
202
|
+
if (!layer) continue;
|
|
203
|
+
// Expandir profile se presente nesta camada
|
|
204
|
+
if (typeof layer.profile === 'string') {
|
|
205
|
+
Object.assign(merged, expandExecutionProfile(layer.profile));
|
|
206
|
+
}
|
|
207
|
+
// Azure: merge aninhado para não sobrescrever campos não especificados
|
|
208
|
+
if (layer.azure && typeof layer.azure === 'object' && !Array.isArray(layer.azure)) {
|
|
209
|
+
merged.azure = { .../** @type {any} */ (merged.azure), ...layer.azure };
|
|
210
|
+
const layerWithoutAzure = { ...layer };
|
|
211
|
+
delete layerWithoutAzure.azure;
|
|
212
|
+
Object.assign(merged, layerWithoutAzure);
|
|
213
|
+
} else {
|
|
214
|
+
Object.assign(merged, layer);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const primaryPath = sources.project || sources.user || sources.system || null;
|
|
219
|
+
return { config: merged, path: primaryPath, parseError: projectParseError, sources };
|
|
154
220
|
}
|
|
155
221
|
|
|
156
222
|
/**
|
|
@@ -224,62 +290,101 @@ function validateConfigShape(cfg) {
|
|
|
224
290
|
typeErrors.push(`profile deve ser um de: ${EXECUTION_PROFILES.join(', ')}`);
|
|
225
291
|
}
|
|
226
292
|
}
|
|
227
|
-
if (cfg.verification_depth != null) {
|
|
228
|
-
if (typeof cfg.verification_depth !== 'string') {
|
|
229
|
-
typeErrors.push('verification_depth deve ser string');
|
|
230
|
-
} else if (!VERIFICATION_DEPTHS.includes(cfg.verification_depth)) {
|
|
231
|
-
typeErrors.push(`verification_depth deve ser um de: ${VERIFICATION_DEPTHS.join(', ')}`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (cfg.plan_confidence_threshold != null && typeof cfg.plan_confidence_threshold !== 'number') {
|
|
235
|
-
typeErrors.push('plan_confidence_threshold deve ser número (percentual de 0 a 100)');
|
|
236
|
-
}
|
|
293
|
+
if (cfg.verification_depth != null) {
|
|
294
|
+
if (typeof cfg.verification_depth !== 'string') {
|
|
295
|
+
typeErrors.push('verification_depth deve ser string');
|
|
296
|
+
} else if (!VERIFICATION_DEPTHS.includes(cfg.verification_depth)) {
|
|
297
|
+
typeErrors.push(`verification_depth deve ser um de: ${VERIFICATION_DEPTHS.join(', ')}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (cfg.plan_confidence_threshold != null && typeof cfg.plan_confidence_threshold !== 'number') {
|
|
301
|
+
typeErrors.push('plan_confidence_threshold deve ser número (percentual de 0 a 100)');
|
|
302
|
+
}
|
|
237
303
|
if (cfg.after_verify_suggest_uat != null && typeof cfg.after_verify_suggest_uat !== 'boolean') {
|
|
238
304
|
typeErrors.push('after_verify_suggest_uat deve ser boolean');
|
|
239
305
|
}
|
|
240
|
-
if (cfg.scale_adaptive != null && typeof cfg.scale_adaptive !== 'boolean') {
|
|
241
|
-
typeErrors.push('scale_adaptive deve ser boolean');
|
|
242
|
-
}
|
|
243
|
-
if (cfg.azure != null) {
|
|
244
|
-
if (typeof cfg.azure !== 'object' || Array.isArray(cfg.azure)) {
|
|
245
|
-
typeErrors.push('azure deve ser um objeto');
|
|
246
|
-
} else {
|
|
247
|
-
const azureCfg = /** @type {Record<string, unknown>} */ (cfg.azure);
|
|
248
|
-
const allowedAzureKeys = [
|
|
249
|
-
'enabled',
|
|
250
|
-
'default_resource_group',
|
|
251
|
-
'preferred_locations',
|
|
252
|
-
'inventory_max_age_hours',
|
|
253
|
-
'resource_graph_auto_install',
|
|
254
|
-
'vpn_required',
|
|
255
|
-
];
|
|
256
|
-
for (const key of Object.keys(azureCfg)) {
|
|
257
|
-
if (!allowedAzureKeys.includes(key)) {
|
|
258
|
-
typeErrors.push(`azure: chave desconhecida "${key}"`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
if (azureCfg.enabled != null && typeof azureCfg.enabled !== 'boolean') {
|
|
262
|
-
typeErrors.push('azure.enabled deve ser boolean');
|
|
263
|
-
}
|
|
264
|
-
if (azureCfg.default_resource_group != null && typeof azureCfg.default_resource_group !== 'string') {
|
|
265
|
-
typeErrors.push('azure.default_resource_group deve ser string');
|
|
266
|
-
}
|
|
267
|
-
if (azureCfg.preferred_locations != null && !Array.isArray(azureCfg.preferred_locations)) {
|
|
268
|
-
typeErrors.push('azure.preferred_locations deve ser array de strings');
|
|
269
|
-
}
|
|
270
|
-
if (azureCfg.inventory_max_age_hours != null && typeof azureCfg.inventory_max_age_hours !== 'number') {
|
|
271
|
-
typeErrors.push('azure.inventory_max_age_hours deve ser número');
|
|
272
|
-
}
|
|
273
|
-
if (azureCfg.resource_graph_auto_install != null && typeof azureCfg.resource_graph_auto_install !== 'boolean') {
|
|
274
|
-
typeErrors.push('azure.resource_graph_auto_install deve ser boolean');
|
|
275
|
-
}
|
|
276
|
-
if (azureCfg.vpn_required != null && typeof azureCfg.vpn_required !== 'boolean') {
|
|
277
|
-
typeErrors.push('azure.vpn_required deve ser boolean');
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
306
|
+
if (cfg.scale_adaptive != null && typeof cfg.scale_adaptive !== 'boolean') {
|
|
307
|
+
typeErrors.push('scale_adaptive deve ser boolean');
|
|
308
|
+
}
|
|
309
|
+
if (cfg.azure != null) {
|
|
310
|
+
if (typeof cfg.azure !== 'object' || Array.isArray(cfg.azure)) {
|
|
311
|
+
typeErrors.push('azure deve ser um objeto');
|
|
312
|
+
} else {
|
|
313
|
+
const azureCfg = /** @type {Record<string, unknown>} */ (cfg.azure);
|
|
314
|
+
const allowedAzureKeys = [
|
|
315
|
+
'enabled',
|
|
316
|
+
'default_resource_group',
|
|
317
|
+
'preferred_locations',
|
|
318
|
+
'inventory_max_age_hours',
|
|
319
|
+
'resource_graph_auto_install',
|
|
320
|
+
'vpn_required',
|
|
321
|
+
];
|
|
322
|
+
for (const key of Object.keys(azureCfg)) {
|
|
323
|
+
if (!allowedAzureKeys.includes(key)) {
|
|
324
|
+
typeErrors.push(`azure: chave desconhecida "${key}"`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (azureCfg.enabled != null && typeof azureCfg.enabled !== 'boolean') {
|
|
328
|
+
typeErrors.push('azure.enabled deve ser boolean');
|
|
329
|
+
}
|
|
330
|
+
if (azureCfg.default_resource_group != null && typeof azureCfg.default_resource_group !== 'string') {
|
|
331
|
+
typeErrors.push('azure.default_resource_group deve ser string');
|
|
332
|
+
}
|
|
333
|
+
if (azureCfg.preferred_locations != null && !Array.isArray(azureCfg.preferred_locations)) {
|
|
334
|
+
typeErrors.push('azure.preferred_locations deve ser array de strings');
|
|
335
|
+
}
|
|
336
|
+
if (azureCfg.inventory_max_age_hours != null && typeof azureCfg.inventory_max_age_hours !== 'number') {
|
|
337
|
+
typeErrors.push('azure.inventory_max_age_hours deve ser número');
|
|
338
|
+
}
|
|
339
|
+
if (azureCfg.resource_graph_auto_install != null && typeof azureCfg.resource_graph_auto_install !== 'boolean') {
|
|
340
|
+
typeErrors.push('azure.resource_graph_auto_install deve ser boolean');
|
|
341
|
+
}
|
|
342
|
+
if (azureCfg.vpn_required != null && typeof azureCfg.vpn_required !== 'boolean') {
|
|
343
|
+
typeErrors.push('azure.vpn_required deve ser boolean');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (cfg.permissions != null) {
|
|
348
|
+
if (!Array.isArray(cfg.permissions)) {
|
|
349
|
+
typeErrors.push('permissions deve ser array de regras { pattern, action, scope? }');
|
|
350
|
+
} else {
|
|
351
|
+
const VALID_PERM_ACTIONS = ['allow', 'deny', 'ask'];
|
|
352
|
+
const VALID_PERM_SCOPES = ['execute', 'apply', 'all'];
|
|
353
|
+
for (let i = 0; i < cfg.permissions.length; i++) {
|
|
354
|
+
const r = cfg.permissions[i];
|
|
355
|
+
if (!r || typeof r !== 'object' || Array.isArray(r)) {
|
|
356
|
+
typeErrors.push(`permissions[${i}] deve ser objeto`);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (typeof r.pattern !== 'string' || r.pattern.length === 0) {
|
|
360
|
+
typeErrors.push(`permissions[${i}].pattern deve ser string não-vazia`);
|
|
361
|
+
}
|
|
362
|
+
if (!VALID_PERM_ACTIONS.includes(r.action)) {
|
|
363
|
+
typeErrors.push(`permissions[${i}].action deve ser um de: ${VALID_PERM_ACTIONS.join(', ')}`);
|
|
364
|
+
}
|
|
365
|
+
if (r.scope != null && !VALID_PERM_SCOPES.includes(r.scope)) {
|
|
366
|
+
typeErrors.push(`permissions[${i}].scope deve ser um de: ${VALID_PERM_SCOPES.join(', ')}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (cfg.plugins != null) {
|
|
372
|
+
if (!Array.isArray(cfg.plugins)) {
|
|
373
|
+
typeErrors.push('plugins deve ser array');
|
|
374
|
+
} else {
|
|
375
|
+
for (let i = 0; i < cfg.plugins.length; i++) {
|
|
376
|
+
const p = cfg.plugins[i];
|
|
377
|
+
if (typeof p === 'string') continue; // aceitar strings legado
|
|
378
|
+
if (!p || typeof p !== 'object' || Array.isArray(p)) {
|
|
379
|
+
typeErrors.push(`plugins[${i}] deve ser string ou objeto { source: string }`);
|
|
380
|
+
} else if (typeof p.source !== 'string' || p.source.length === 0) {
|
|
381
|
+
typeErrors.push(`plugins[${i}].source deve ser string não-vazia`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return { unknownKeys, typeErrors };
|
|
387
|
+
}
|
|
283
388
|
|
|
284
389
|
/**
|
|
285
390
|
* @param {string} stateText
|
|
@@ -340,38 +445,38 @@ function parseLastCompactDate(stateText) {
|
|
|
340
445
|
* @param {string} stateText
|
|
341
446
|
* @returns {Date | null}
|
|
342
447
|
*/
|
|
343
|
-
function parseLastRetroDate(stateText) {
|
|
344
|
-
const m = stateText.match(/\blast_retro\s*:\s*(\d{4}-\d{2}-\d{2})/i);
|
|
345
|
-
if (!m) return null;
|
|
346
|
-
const iso = Date.parse(m[1]);
|
|
347
|
-
return Number.isNaN(iso) ? null : new Date(iso);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* @param {string} stateText
|
|
352
|
-
* @returns {string | null}
|
|
353
|
-
*/
|
|
354
|
-
function parseActiveSession(stateText) {
|
|
355
|
-
if (!stateText) return null;
|
|
356
|
-
const m = stateText.match(/\*\*active_session:\*\*\s*`?([^\n`]+?)`?\s*(?:\n|$)/i);
|
|
357
|
-
if (!m) return null;
|
|
358
|
-
const raw = m[1].trim();
|
|
359
|
-
if (!raw || raw === '—' || /^none$/i.test(raw)) return null;
|
|
360
|
-
return raw.replace(/\\/g, '/');
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* @param {string} stateText
|
|
365
|
-
* @returns {string | null}
|
|
366
|
-
*/
|
|
367
|
-
function parsePlanReviewStatus(stateText) {
|
|
368
|
-
if (!stateText) return null;
|
|
369
|
-
const m = stateText.match(/\*\*plan_review_status:\*\*\s*`?([^\n`]+?)`?\s*(?:\n|$)/i);
|
|
370
|
-
if (!m) return null;
|
|
371
|
-
const raw = m[1].trim();
|
|
372
|
-
if (!raw || raw === '—' || /^none$/i.test(raw)) return null;
|
|
373
|
-
return raw;
|
|
374
|
-
}
|
|
448
|
+
function parseLastRetroDate(stateText) {
|
|
449
|
+
const m = stateText.match(/\blast_retro\s*:\s*(\d{4}-\d{2}-\d{2})/i);
|
|
450
|
+
if (!m) return null;
|
|
451
|
+
const iso = Date.parse(m[1]);
|
|
452
|
+
return Number.isNaN(iso) ? null : new Date(iso);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* @param {string} stateText
|
|
457
|
+
* @returns {string | null}
|
|
458
|
+
*/
|
|
459
|
+
function parseActiveSession(stateText) {
|
|
460
|
+
if (!stateText) return null;
|
|
461
|
+
const m = stateText.match(/\*\*active_session:\*\*\s*`?([^\n`]+?)`?\s*(?:\n|$)/i);
|
|
462
|
+
if (!m) return null;
|
|
463
|
+
const raw = m[1].trim();
|
|
464
|
+
if (!raw || raw === '—' || /^none$/i.test(raw)) return null;
|
|
465
|
+
return raw.replace(/\\/g, '/');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* @param {string} stateText
|
|
470
|
+
* @returns {string | null}
|
|
471
|
+
*/
|
|
472
|
+
function parsePlanReviewStatus(stateText) {
|
|
473
|
+
if (!stateText) return null;
|
|
474
|
+
const m = stateText.match(/\*\*plan_review_status:\*\*\s*`?([^\n`]+?)`?\s*(?:\n|$)/i);
|
|
475
|
+
if (!m) return null;
|
|
476
|
+
const raw = m[1].trim();
|
|
477
|
+
if (!raw || raw === '—' || /^none$/i.test(raw)) return null;
|
|
478
|
+
return raw;
|
|
479
|
+
}
|
|
375
480
|
|
|
376
481
|
/**
|
|
377
482
|
* @param {Date | null} scanDate
|
|
@@ -396,31 +501,38 @@ function isStaleLessons(retroDate, maxAgeDays) {
|
|
|
396
501
|
/**
|
|
397
502
|
* @param {string} target
|
|
398
503
|
*/
|
|
399
|
-
function oxePaths(target) {
|
|
400
|
-
const oxe = path.join(target, '.oxe');
|
|
401
|
-
return {
|
|
402
|
-
oxe,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
504
|
+
function oxePaths(target) {
|
|
505
|
+
const oxe = path.join(target, '.oxe');
|
|
506
|
+
return {
|
|
507
|
+
oxe,
|
|
508
|
+
installDir: path.join(oxe, 'install'),
|
|
509
|
+
contextDir: path.join(oxe, 'context'),
|
|
510
|
+
contextIndex: path.join(oxe, 'context', 'index.json'),
|
|
511
|
+
contextPacksDir: path.join(oxe, 'context', 'packs'),
|
|
512
|
+
contextSummariesDir: path.join(oxe, 'context', 'summaries'),
|
|
513
|
+
state: path.join(oxe, 'STATE.md'),
|
|
514
|
+
runtime: path.join(oxe, 'EXECUTION-RUNTIME.md'),
|
|
515
|
+
checkpoints: path.join(oxe, 'CHECKPOINTS.md'),
|
|
516
|
+
capabilitiesIndex: path.join(oxe, 'CAPABILITIES.md'),
|
|
517
|
+
capabilitiesDir: path.join(oxe, 'capabilities'),
|
|
518
|
+
investigationsIndex: path.join(oxe, 'INVESTIGATIONS.md'),
|
|
519
|
+
investigationsDir: path.join(oxe, 'investigations'),
|
|
520
|
+
dashboardDir: path.join(oxe, 'dashboard'),
|
|
521
|
+
sessionsIndex: path.join(oxe, 'SESSIONS.md'),
|
|
522
|
+
globalDir: path.join(oxe, 'global'),
|
|
523
|
+
globalLessons: path.join(oxe, 'global', 'LESSONS.md'),
|
|
524
|
+
globalMilestones: path.join(oxe, 'global', 'MILESTONES.md'),
|
|
525
|
+
globalMilestonesDir: path.join(oxe, 'global', 'milestones'),
|
|
526
|
+
sessionsDir: path.join(oxe, 'sessions'),
|
|
527
|
+
planReview: path.join(oxe, 'PLAN-REVIEW.md'),
|
|
528
|
+
planReviewComments: path.join(oxe, 'plan-review-comments.json'),
|
|
529
|
+
activeRun: path.join(oxe, 'ACTIVE-RUN.json'),
|
|
530
|
+
runsDir: path.join(oxe, 'runs'),
|
|
531
|
+
events: path.join(oxe, 'OXE-EVENTS.ndjson'),
|
|
532
|
+
copilotManifest: path.join(oxe, 'install', 'copilot-vscode.json'),
|
|
533
|
+
runtimeSemanticsManifest: path.join(oxe, 'install', 'runtime-semantics.json'),
|
|
534
|
+
spec: path.join(oxe, 'SPEC.md'),
|
|
535
|
+
plan: path.join(oxe, 'PLAN.md'),
|
|
424
536
|
quick: path.join(oxe, 'QUICK.md'),
|
|
425
537
|
verify: path.join(oxe, 'VERIFY.md'),
|
|
426
538
|
discuss: path.join(oxe, 'DISCUSS.md'),
|
|
@@ -428,41 +540,247 @@ function oxePaths(target) {
|
|
|
428
540
|
codebase: path.join(oxe, 'codebase'),
|
|
429
541
|
lessons: path.join(oxe, 'LESSONS.md'),
|
|
430
542
|
planAgents: path.join(oxe, 'plan-agents.json'),
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* @param {string} target
|
|
436
|
-
* @param {string | null} activeSession
|
|
437
|
-
*/
|
|
438
|
-
function scopedOxePaths(target, activeSession) {
|
|
439
|
-
const base = oxePaths(target);
|
|
440
|
-
if (!activeSession) return { ...base, activeSession: null, scopedRoot: base.oxe };
|
|
441
|
-
const sessionRoot = path.join(base.oxe, ...activeSession.split('/'));
|
|
442
|
-
return {
|
|
443
|
-
...base,
|
|
444
|
-
activeSession,
|
|
445
|
-
scopedRoot: sessionRoot,
|
|
446
|
-
sessionRoot,
|
|
447
|
-
sessionManifest: path.join(sessionRoot, 'SESSION.md'),
|
|
448
|
-
planReview: path.join(sessionRoot, 'plan', 'PLAN-REVIEW.md'),
|
|
449
|
-
planReviewComments: path.join(sessionRoot, 'plan', 'plan-review-comments.json'),
|
|
450
|
-
runtime: path.join(sessionRoot, 'execution', 'EXECUTION-RUNTIME.md'),
|
|
451
|
-
checkpoints: path.join(sessionRoot, 'execution', 'CHECKPOINTS.md'),
|
|
452
|
-
activeRun: path.join(sessionRoot, 'execution', 'ACTIVE-RUN.json'),
|
|
453
|
-
runsDir: path.join(sessionRoot, 'execution', 'runs'),
|
|
454
|
-
events: path.join(sessionRoot, 'execution', 'OXE-EVENTS.ndjson'),
|
|
455
|
-
investigationsIndex: path.join(sessionRoot, 'research', 'INVESTIGATIONS.md'),
|
|
456
|
-
investigationsDir: path.join(sessionRoot, 'research', 'investigations'),
|
|
457
|
-
spec: path.join(sessionRoot, 'spec', 'SPEC.md'),
|
|
458
|
-
discuss: path.join(sessionRoot, 'spec', 'DISCUSS.md'),
|
|
459
|
-
plan: path.join(sessionRoot, 'plan', 'PLAN.md'),
|
|
460
|
-
quick: path.join(sessionRoot, 'plan', 'QUICK.md'),
|
|
461
|
-
verify: path.join(sessionRoot, 'verification', 'VERIFY.md'),
|
|
462
|
-
summary: path.join(sessionRoot, 'verification', 'SUMMARY.md'),
|
|
463
|
-
executionState: path.join(sessionRoot, 'execution', 'STATE.md'),
|
|
464
|
-
};
|
|
465
|
-
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* @param {string} target
|
|
548
|
+
* @param {string | null} activeSession
|
|
549
|
+
*/
|
|
550
|
+
function scopedOxePaths(target, activeSession) {
|
|
551
|
+
const base = oxePaths(target);
|
|
552
|
+
if (!activeSession) return { ...base, activeSession: null, scopedRoot: base.oxe };
|
|
553
|
+
const sessionRoot = path.join(base.oxe, ...activeSession.split('/'));
|
|
554
|
+
return {
|
|
555
|
+
...base,
|
|
556
|
+
activeSession,
|
|
557
|
+
scopedRoot: sessionRoot,
|
|
558
|
+
sessionRoot,
|
|
559
|
+
sessionManifest: path.join(sessionRoot, 'SESSION.md'),
|
|
560
|
+
planReview: path.join(sessionRoot, 'plan', 'PLAN-REVIEW.md'),
|
|
561
|
+
planReviewComments: path.join(sessionRoot, 'plan', 'plan-review-comments.json'),
|
|
562
|
+
runtime: path.join(sessionRoot, 'execution', 'EXECUTION-RUNTIME.md'),
|
|
563
|
+
checkpoints: path.join(sessionRoot, 'execution', 'CHECKPOINTS.md'),
|
|
564
|
+
activeRun: path.join(sessionRoot, 'execution', 'ACTIVE-RUN.json'),
|
|
565
|
+
runsDir: path.join(sessionRoot, 'execution', 'runs'),
|
|
566
|
+
events: path.join(sessionRoot, 'execution', 'OXE-EVENTS.ndjson'),
|
|
567
|
+
investigationsIndex: path.join(sessionRoot, 'research', 'INVESTIGATIONS.md'),
|
|
568
|
+
investigationsDir: path.join(sessionRoot, 'research', 'investigations'),
|
|
569
|
+
spec: path.join(sessionRoot, 'spec', 'SPEC.md'),
|
|
570
|
+
discuss: path.join(sessionRoot, 'spec', 'DISCUSS.md'),
|
|
571
|
+
plan: path.join(sessionRoot, 'plan', 'PLAN.md'),
|
|
572
|
+
quick: path.join(sessionRoot, 'plan', 'QUICK.md'),
|
|
573
|
+
verify: path.join(sessionRoot, 'verification', 'VERIFY.md'),
|
|
574
|
+
summary: path.join(sessionRoot, 'verification', 'SUMMARY.md'),
|
|
575
|
+
executionState: path.join(sessionRoot, 'execution', 'STATE.md'),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* @param {string} target
|
|
581
|
+
*/
|
|
582
|
+
function copilotWorkspacePaths(target) {
|
|
583
|
+
return {
|
|
584
|
+
root: path.join(target, '.github'),
|
|
585
|
+
promptsDir: path.join(target, '.github', 'prompts'),
|
|
586
|
+
instructions: path.join(target, '.github', 'copilot-instructions.md'),
|
|
587
|
+
manifest: path.join(target, '.oxe', 'install', 'copilot-vscode.json'),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function copilotLegacyHome() {
|
|
592
|
+
if (process.env.COPILOT_CONFIG_DIR) return path.resolve(process.env.COPILOT_CONFIG_DIR);
|
|
593
|
+
if (process.env.COPILOT_HOME) return path.resolve(process.env.COPILOT_HOME);
|
|
594
|
+
return path.join(os.homedir(), '.copilot');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function copilotLegacyPaths() {
|
|
598
|
+
const root = copilotLegacyHome();
|
|
599
|
+
return {
|
|
600
|
+
root,
|
|
601
|
+
promptsDir: path.join(root, 'prompts'),
|
|
602
|
+
instructions: path.join(root, 'copilot-instructions.md'),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* @param {string} filePath
|
|
608
|
+
*/
|
|
609
|
+
function readJsonFileSafe(filePath) {
|
|
610
|
+
if (!fs.existsSync(filePath)) return { ok: false, data: null, error: null };
|
|
611
|
+
try {
|
|
612
|
+
return { ok: true, data: JSON.parse(fs.readFileSync(filePath, 'utf8')), error: null };
|
|
613
|
+
} catch (error) {
|
|
614
|
+
return { ok: false, data: null, error: error instanceof Error ? error.message : String(error) };
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* @param {string} dir
|
|
620
|
+
* @returns {string[]}
|
|
621
|
+
*/
|
|
622
|
+
function listOxePromptFiles(dir) {
|
|
623
|
+
if (!fs.existsSync(dir)) return [];
|
|
624
|
+
return fs
|
|
625
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
626
|
+
.filter((entry) => entry.isFile() && /^oxe-.*\.prompt\.md$/i.test(entry.name))
|
|
627
|
+
.map((entry) => path.join(dir, entry.name))
|
|
628
|
+
.sort();
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* @param {string} filePath
|
|
633
|
+
* @returns {boolean}
|
|
634
|
+
*/
|
|
635
|
+
function hasOxeInstructionBlock(filePath) {
|
|
636
|
+
if (!fs.existsSync(filePath)) return false;
|
|
637
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
638
|
+
return text.includes('<!-- oxe-cc:install-begin -->') && text.includes('<!-- oxe-cc:install-end -->');
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* @param {string} filePath
|
|
643
|
+
* @returns {boolean}
|
|
644
|
+
*/
|
|
645
|
+
function hasOtherManagedInstructionBlocks(filePath) {
|
|
646
|
+
if (!fs.existsSync(filePath)) return false;
|
|
647
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
648
|
+
const withoutOxe = text
|
|
649
|
+
.replace(/<!-- oxe-cc:install-begin -->[\s\S]*?<!-- oxe-cc:install-end -->/g, '')
|
|
650
|
+
.trim();
|
|
651
|
+
if (!withoutOxe) return false;
|
|
652
|
+
return /<!--[^>]*(managed|configuration|install-begin|install-end|get-shit-done|gsd)[^>]*-->/i.test(withoutOxe);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* @param {string} filePath
|
|
657
|
+
* @param {string} target
|
|
658
|
+
* @returns {string[]}
|
|
659
|
+
*/
|
|
660
|
+
function promptWorkflowPathWarnings(filePath, target) {
|
|
661
|
+
if (!fs.existsSync(filePath)) return [];
|
|
662
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
663
|
+
const hasClassicRef = /(^|[`"'(\s])oxe\/workflows\//i.test(text);
|
|
664
|
+
const hasNestedRef = /(^|[`"'(\s])\.oxe\/workflows\//i.test(text);
|
|
665
|
+
const classicExists = fs.existsSync(path.join(target, 'oxe', 'workflows'));
|
|
666
|
+
const nestedExists = fs.existsSync(path.join(target, '.oxe', 'workflows'));
|
|
667
|
+
/** @type {string[]} */
|
|
668
|
+
const warnings = [];
|
|
669
|
+
if (nestedExists && !classicExists && hasClassicRef && !hasNestedRef) {
|
|
670
|
+
warnings.push(`${path.basename(filePath)} aponta para oxe/workflows/, mas este projeto usa .oxe/workflows/`);
|
|
671
|
+
}
|
|
672
|
+
if (classicExists && !nestedExists && hasNestedRef && !hasClassicRef) {
|
|
673
|
+
warnings.push(`${path.basename(filePath)} aponta para .oxe/workflows/, mas este projeto usa oxe/workflows/`);
|
|
674
|
+
}
|
|
675
|
+
return warnings;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @param {string} target
|
|
680
|
+
*/
|
|
681
|
+
function copilotIntegrationReport(target) {
|
|
682
|
+
const workspace = copilotWorkspacePaths(target);
|
|
683
|
+
const legacy = copilotLegacyPaths();
|
|
684
|
+
const workspacePromptFiles = listOxePromptFiles(workspace.promptsDir);
|
|
685
|
+
const legacyPromptFiles = listOxePromptFiles(legacy.promptsDir);
|
|
686
|
+
const workspaceHasInstructions = fs.existsSync(workspace.instructions);
|
|
687
|
+
const workspaceHasOxeBlock = hasOxeInstructionBlock(workspace.instructions);
|
|
688
|
+
const legacyHasInstructions = fs.existsSync(legacy.instructions);
|
|
689
|
+
const legacyHasOxeBlock = hasOxeInstructionBlock(legacy.instructions);
|
|
690
|
+
const legacyHasOtherManagedBlocks = hasOtherManagedInstructionBlocks(legacy.instructions);
|
|
691
|
+
const manifestRaw = readJsonFileSafe(workspace.manifest);
|
|
692
|
+
const manifest = manifestRaw.ok ? manifestRaw.data : null;
|
|
693
|
+
const promptPathWarnings = [];
|
|
694
|
+
for (const filePath of workspacePromptFiles) {
|
|
695
|
+
for (const warning of promptWorkflowPathWarnings(filePath, target)) promptPathWarnings.push(warning);
|
|
696
|
+
}
|
|
697
|
+
for (const warning of promptWorkflowPathWarnings(workspace.instructions, target)) promptPathWarnings.push(warning);
|
|
698
|
+
|
|
699
|
+
/** @type {string[]} */
|
|
700
|
+
const warnings = [];
|
|
701
|
+
const workspaceDetected =
|
|
702
|
+
workspacePromptFiles.length > 0 || workspaceHasInstructions || fs.existsSync(workspace.manifest);
|
|
703
|
+
const legacyDetected = legacyPromptFiles.length > 0 || legacyHasOxeBlock;
|
|
704
|
+
|
|
705
|
+
if (workspacePromptFiles.length > 0 && !workspaceHasInstructions) {
|
|
706
|
+
warnings.push('Prompts OXE do Copilot VS Code existem no workspace, mas .github/copilot-instructions.md está ausente');
|
|
707
|
+
} else if (workspaceHasInstructions && !workspaceHasOxeBlock) {
|
|
708
|
+
warnings.push('.github/copilot-instructions.md existe, mas não contém o bloco OXE');
|
|
709
|
+
}
|
|
710
|
+
if (workspaceHasInstructions && workspacePromptFiles.length === 0 && !legacyDetected) {
|
|
711
|
+
warnings.push('.github/copilot-instructions.md existe, mas .github/prompts/ não contém prompt files OXE');
|
|
712
|
+
}
|
|
713
|
+
if (!workspaceDetected && legacyDetected) {
|
|
714
|
+
warnings.push('Prompts OXE do Copilot VS Code foram encontrados apenas no legado global ~/.copilot/; sincronize .github/ no workspace');
|
|
715
|
+
}
|
|
716
|
+
if (legacyPromptFiles.length > 0) {
|
|
717
|
+
warnings.push('Instalação legado do Copilot VS Code detectada em ~/.copilot/prompts/; trate como resíduo ou execute uninstall --copilot-legacy-clean');
|
|
718
|
+
}
|
|
719
|
+
if (legacyHasOxeBlock) {
|
|
720
|
+
warnings.push('Bloco OXE legado detectado em ~/.copilot/copilot-instructions.md');
|
|
721
|
+
}
|
|
722
|
+
if (legacyHasOtherManagedBlocks) {
|
|
723
|
+
warnings.push('copilot-instructions global contém blocos geridos por outro framework; isso pode contaminar respostas do Copilot');
|
|
724
|
+
}
|
|
725
|
+
if (!manifestRaw.ok && fs.existsSync(workspace.manifest)) {
|
|
726
|
+
warnings.push(`Manifesto Copilot VS Code inválido: ${manifestRaw.error}`);
|
|
727
|
+
} else if (!fs.existsSync(workspace.manifest) && workspacePromptFiles.length > 0) {
|
|
728
|
+
warnings.push('Manifesto .oxe/install/copilot-vscode.json ausente para a integração Copilot VS Code');
|
|
729
|
+
} else if (manifest && Array.isArray(manifest.prompt_files)) {
|
|
730
|
+
const actualPromptNames = workspacePromptFiles.map((filePath) => path.basename(filePath)).sort();
|
|
731
|
+
const expectedPromptNames = manifest.prompt_files.map((value) => String(value)).sort();
|
|
732
|
+
for (const name of expectedPromptNames) {
|
|
733
|
+
if (!actualPromptNames.includes(name)) {
|
|
734
|
+
warnings.push(`Manifesto Copilot VS Code referencia ${name}, mas o arquivo não existe em .github/prompts/`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
for (const warning of promptPathWarnings) warnings.push(warning);
|
|
739
|
+
|
|
740
|
+
let status = 'not_installed';
|
|
741
|
+
if (
|
|
742
|
+
workspacePromptFiles.length > 0 &&
|
|
743
|
+
workspaceHasInstructions &&
|
|
744
|
+
workspaceHasOxeBlock &&
|
|
745
|
+
promptPathWarnings.length === 0
|
|
746
|
+
) {
|
|
747
|
+
status = warnings.length ? 'warning' : 'healthy';
|
|
748
|
+
} else if (workspaceDetected || legacyDetected) {
|
|
749
|
+
status =
|
|
750
|
+
promptPathWarnings.length > 0 || (workspacePromptFiles.length > 0 && (!workspaceHasInstructions || !workspaceHasOxeBlock))
|
|
751
|
+
? 'broken'
|
|
752
|
+
: 'warning';
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return {
|
|
756
|
+
status,
|
|
757
|
+
detected: workspaceDetected || legacyDetected,
|
|
758
|
+
target: 'workspace',
|
|
759
|
+
promptSource:
|
|
760
|
+
workspacePromptFiles.length > 0 ? 'workspace' : legacyDetected ? 'legacy_global' : 'missing',
|
|
761
|
+
workspace: {
|
|
762
|
+
root: workspace.root,
|
|
763
|
+
promptsDir: workspace.promptsDir,
|
|
764
|
+
instructions: workspace.instructions,
|
|
765
|
+
manifest: workspace.manifest,
|
|
766
|
+
promptFiles: workspacePromptFiles,
|
|
767
|
+
hasInstructions: workspaceHasInstructions,
|
|
768
|
+
hasOxeBlock: workspaceHasOxeBlock,
|
|
769
|
+
},
|
|
770
|
+
legacy: {
|
|
771
|
+
root: legacy.root,
|
|
772
|
+
promptsDir: legacy.promptsDir,
|
|
773
|
+
instructions: legacy.instructions,
|
|
774
|
+
promptFiles: legacyPromptFiles,
|
|
775
|
+
hasInstructions: legacyHasInstructions,
|
|
776
|
+
hasOxeBlock: legacyHasOxeBlock,
|
|
777
|
+
hasOtherManagedBlocks: legacyHasOtherManagedBlocks,
|
|
778
|
+
detected: legacyDetected,
|
|
779
|
+
},
|
|
780
|
+
manifest,
|
|
781
|
+
warnings,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
466
784
|
|
|
467
785
|
/**
|
|
468
786
|
* Valida o arquivo plan-agents.json (se existir) e retorna avisos.
|
|
@@ -590,7 +908,7 @@ function planTaskAceiteWarnings(planPath) {
|
|
|
590
908
|
return out;
|
|
591
909
|
}
|
|
592
910
|
|
|
593
|
-
function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
911
|
+
function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
594
912
|
if (!maxPerWave || maxPerWave <= 0 || !fs.existsSync(planPath)) return [];
|
|
595
913
|
const raw = fs.readFileSync(planPath, 'utf8');
|
|
596
914
|
const lines = raw.split('\n');
|
|
@@ -611,241 +929,241 @@ function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
|
611
929
|
w.push(`PLAN.md: onda ${wN} tem ${count} tarefas (máximo configurado: ${maxPerWave} — considere dividir ondas)`);
|
|
612
930
|
}
|
|
613
931
|
}
|
|
614
|
-
return w;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* @param {string} planPath
|
|
619
|
-
* @returns {{
|
|
620
|
-
* hasSection: boolean,
|
|
621
|
-
* bestPlan: string | null,
|
|
622
|
-
* confidence: number | null,
|
|
623
|
-
* warnings: string[],
|
|
624
|
-
* }}
|
|
625
|
-
*/
|
|
626
|
-
function parsePlanSelfEvaluation(planPath) {
|
|
627
|
-
const empty = { hasSection: false, bestPlan: null, confidence: null, warnings: [] };
|
|
628
|
-
if (!fs.existsSync(planPath)) return empty;
|
|
629
|
-
const raw = fs.readFileSync(planPath, 'utf8');
|
|
630
|
-
const m = raw.match(/##\s*Autoavaliação do Plano\s*([\s\S]*?)(?=\n## |\n#[^\#]|$)/i);
|
|
631
|
-
if (!m) {
|
|
632
|
-
return {
|
|
633
|
-
...empty,
|
|
634
|
-
warnings: ['PLAN.md sem a seção obrigatória "## Autoavaliação do Plano"'],
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
const body = m[1];
|
|
638
|
-
const best = body.match(/\*\*Melhor plano atual:\*\*\s*(sim|não|nao)/i);
|
|
639
|
-
const confidence = body.match(/\*\*Confiança:\*\*\s*(\d{1,3})\s*%/i);
|
|
640
|
-
/** @type {string[]} */
|
|
641
|
-
const warnings = [];
|
|
642
|
-
const rubricLabels = [
|
|
643
|
-
'Completude dos requisitos',
|
|
644
|
-
'Dependências conhecidas',
|
|
645
|
-
'Risco técnico',
|
|
646
|
-
'Impacto no código existente',
|
|
647
|
-
'Clareza da validação / testes',
|
|
648
|
-
'Lacunas externas / decisões pendentes',
|
|
649
|
-
];
|
|
650
|
-
if (!best) warnings.push('PLAN.md: autoavaliação sem "Melhor plano atual: sim|não"');
|
|
651
|
-
if (!confidence) warnings.push('PLAN.md: autoavaliação sem "Confiança: NN%"');
|
|
652
|
-
if (!/\*\*Principais incertezas:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Principais incertezas"');
|
|
653
|
-
if (!/\*\*Alternativas descartadas:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Alternativas descartadas"');
|
|
654
|
-
if (!/\*\*Condição para replanejar:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Condição para replanejar"');
|
|
655
|
-
for (const label of rubricLabels) {
|
|
656
|
-
if (!body.includes(label)) warnings.push(`PLAN.md: rubrica sem "${label}"`);
|
|
657
|
-
}
|
|
658
|
-
const parsedConfidence = confidence ? Number(confidence[1]) : null;
|
|
659
|
-
if (parsedConfidence != null && (parsedConfidence < 0 || parsedConfidence > 100)) {
|
|
660
|
-
warnings.push('PLAN.md: confiança fora do intervalo 0–100%');
|
|
661
|
-
}
|
|
662
|
-
return {
|
|
663
|
-
hasSection: true,
|
|
664
|
-
bestPlan: best ? best[1].toLowerCase().replace('nao', 'não') : null,
|
|
665
|
-
confidence: parsedConfidence,
|
|
666
|
-
warnings,
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* @param {string} planPath
|
|
672
|
-
* @param {number} threshold
|
|
673
|
-
* @returns {string[]}
|
|
674
|
-
*/
|
|
675
|
-
function planSelfEvaluationWarnings(planPath, threshold) {
|
|
676
|
-
const info = parsePlanSelfEvaluation(planPath);
|
|
677
|
-
const warns = [...info.warnings];
|
|
678
|
-
if (!fs.existsSync(planPath)) return warns;
|
|
679
|
-
if (info.bestPlan === 'não') warns.push('PLAN.md: autoavaliação declara que este não é o melhor plano atual');
|
|
680
|
-
if (info.confidence != null && info.confidence < threshold) {
|
|
681
|
-
warns.push(`PLAN.md: confiança ${info.confidence}% abaixo do limiar executável (${threshold}%)`);
|
|
682
|
-
}
|
|
683
|
-
return warns;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* @param {string} target
|
|
688
|
-
* @param {string | null} activeSession
|
|
689
|
-
* @returns {string[]}
|
|
690
|
-
*/
|
|
691
|
-
function sessionWarnings(target, activeSession) {
|
|
692
|
-
if (!activeSession) return [];
|
|
693
|
-
const base = oxePaths(target);
|
|
694
|
-
const scoped = scopedOxePaths(target, activeSession);
|
|
695
|
-
/** @type {string[]} */
|
|
696
|
-
const warns = [];
|
|
697
|
-
if (!/^sessions\/s\d{3}-/.test(activeSession)) {
|
|
698
|
-
warns.push(`active_session "${activeSession}" não segue o formato sessions/sNNN-slug`);
|
|
699
|
-
}
|
|
700
|
-
if (!fs.existsSync(scoped.sessionRoot)) {
|
|
701
|
-
warns.push(`active_session aponta para ${activeSession}, mas a pasta da sessão não existe em .oxe/`);
|
|
702
|
-
return warns;
|
|
703
|
-
}
|
|
704
|
-
if (!fs.existsSync(scoped.sessionManifest)) warns.push(`Sessão ativa ${activeSession} sem SESSION.md`);
|
|
705
|
-
if (!fs.existsSync(base.sessionsIndex)) warns.push('Sessão ativa definida, mas .oxe/SESSIONS.md não existe');
|
|
706
|
-
return warns;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* @param {string} target
|
|
711
|
-
* @returns {string[]}
|
|
712
|
-
*/
|
|
713
|
-
function installationCompletenessWarnings(target) {
|
|
714
|
-
const p = oxePaths(target);
|
|
715
|
-
/** @type {string[]} */
|
|
716
|
-
const warns = [];
|
|
717
|
-
if (!fs.existsSync(p.oxe)) return warns;
|
|
718
|
-
if (!fs.existsSync(p.globalDir)) warns.push('.oxe/global/ ausente');
|
|
719
|
-
if (!fs.existsSync(p.globalLessons)) warns.push('.oxe/global/LESSONS.md ausente');
|
|
720
|
-
if (!fs.existsSync(p.globalMilestones)) warns.push('.oxe/global/MILESTONES.md ausente');
|
|
721
|
-
if (!fs.existsSync(p.globalMilestonesDir)) warns.push('.oxe/global/milestones/ ausente');
|
|
722
|
-
if (!fs.existsSync(p.sessionsDir)) warns.push('.oxe/sessions/ ausente');
|
|
723
|
-
if (!fs.existsSync(p.capabilitiesDir)) warns.push('.oxe/capabilities/ ausente');
|
|
724
|
-
if (!fs.existsSync(p.capabilitiesIndex)) warns.push('.oxe/CAPABILITIES.md ausente');
|
|
725
|
-
if (!fs.existsSync(p.investigationsDir)) warns.push('.oxe/investigations/ ausente');
|
|
726
|
-
if (!fs.existsSync(p.investigationsIndex)) warns.push('.oxe/INVESTIGATIONS.md ausente');
|
|
727
|
-
if (!fs.existsSync(p.runtime)) warns.push('.oxe/EXECUTION-RUNTIME.md ausente');
|
|
728
|
-
if (!fs.existsSync(p.checkpoints)) warns.push('.oxe/CHECKPOINTS.md ausente');
|
|
729
|
-
if (!fs.existsSync(p.activeRun)) warns.push('.oxe/ACTIVE-RUN.json ausente');
|
|
730
|
-
if (!fs.existsSync(p.runsDir)) warns.push('.oxe/runs/ ausente');
|
|
731
|
-
if (!fs.existsSync(p.events)) warns.push('.oxe/OXE-EVENTS.ndjson ausente');
|
|
732
|
-
if (azure.isAzureContextEnabled(target)) {
|
|
733
|
-
const azurePaths = azure.azurePaths(target);
|
|
734
|
-
if (!fs.existsSync(azurePaths.root)) warns.push('.oxe/cloud/azure/ ausente');
|
|
735
|
-
if (!fs.existsSync(azurePaths.operationsDir)) warns.push('.oxe/cloud/azure/operations/ ausente');
|
|
736
|
-
if (!fs.existsSync(azurePaths.profile)) warns.push('.oxe/cloud/azure/profile.json ausente');
|
|
737
|
-
if (!fs.existsSync(azurePaths.authStatus)) warns.push('.oxe/cloud/azure/auth-status.json ausente');
|
|
738
|
-
}
|
|
739
|
-
return warns;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
/**
|
|
743
|
-
* @param {string} stateText
|
|
744
|
-
* @param {ReturnType<typeof scopedOxePaths>} p
|
|
745
|
-
* @returns {string[]}
|
|
746
|
-
*/
|
|
747
|
-
function runtimeWarnings(stateText, p) {
|
|
748
|
-
/** @type {string[]} */
|
|
749
|
-
const warns = [];
|
|
750
|
-
const checkpointPending = /\*\*checkpoint_status:\*\*\s*`?pending_approval`?/i.test(stateText);
|
|
751
|
-
const runtimeBlocked = /\*\*runtime_status:\*\*\s*`?(blocked|waiting_approval|failed)`?/i.test(stateText);
|
|
752
|
-
if (checkpointPending && !fs.existsSync(p.checkpoints)) {
|
|
753
|
-
warns.push('STATE.md indica checkpoint pendente, mas o índice de checkpoints não existe');
|
|
754
|
-
}
|
|
755
|
-
if (runtimeBlocked && !fs.existsSync(p.runtime)) {
|
|
756
|
-
warns.push('STATE.md indica runtime bloqueado, mas EXECUTION-RUNTIME.md não existe');
|
|
757
|
-
}
|
|
758
|
-
if (fs.existsSync(p.runtime)) {
|
|
759
|
-
const raw = fs.readFileSync(p.runtime, 'utf8');
|
|
760
|
-
if (!/##\s*Checkpoints/i.test(raw)) warns.push('EXECUTION-RUNTIME.md sem seção "Checkpoints"');
|
|
761
|
-
if (!/##\s*Agentes ativos/i.test(raw)) warns.push('EXECUTION-RUNTIME.md sem seção "Agentes ativos"');
|
|
762
|
-
if (!/Run ID/i.test(raw)) warns.push('EXECUTION-RUNTIME.md sem referência explícita de Run ID');
|
|
763
|
-
if (!/Tracing operacional/i.test(raw)) warns.push('EXECUTION-RUNTIME.md sem seção "Tracing operacional"');
|
|
764
|
-
}
|
|
765
|
-
if (!fs.existsSync(p.activeRun)) {
|
|
766
|
-
warns.push('ACTIVE-RUN.json não existe para o escopo atual');
|
|
767
|
-
}
|
|
768
|
-
if (!fs.existsSync(p.events)) {
|
|
769
|
-
warns.push('OXE-EVENTS.ndjson não existe para o escopo atual');
|
|
770
|
-
}
|
|
771
|
-
const runState = operational.readRunState(path.dirname(p.oxe), p.activeSession || null);
|
|
772
|
-
const checkpointRows = [];
|
|
773
|
-
if (fs.existsSync(p.checkpoints)) {
|
|
774
|
-
const checkpointText = fs.readFileSync(p.checkpoints, 'utf8');
|
|
775
|
-
for (const line of checkpointText.split('\n')) {
|
|
776
|
-
const match = line.match(/^\|\s*(CP-[^|]+)\|\s*[^|]*\|\s*[^|]*\|\s*[^|]*\|\s*([^|]+)\|/i);
|
|
777
|
-
if (!match) continue;
|
|
778
|
-
checkpointRows.push({ id: match[1].trim(), status: match[2].trim() });
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
for (const warn of operational.runtimeStateWarnings(runState, checkpointRows)) warns.push(warn);
|
|
782
|
-
return warns;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* @param {ReturnType<typeof scopedOxePaths>} p
|
|
787
|
-
* @returns {string[]}
|
|
788
|
-
*/
|
|
789
|
-
function capabilityWarnings(p) {
|
|
790
|
-
/** @type {string[]} */
|
|
791
|
-
const warns = [];
|
|
792
|
-
if (fs.existsSync(p.capabilitiesDir) && !fs.existsSync(p.capabilitiesIndex)) {
|
|
793
|
-
warns.push('Existem capabilities em .oxe/capabilities/, mas .oxe/CAPABILITIES.md não existe');
|
|
794
|
-
}
|
|
795
|
-
for (const warn of operational.capabilityCatalogWarnings(path.dirname(p.oxe))) warns.push(warn);
|
|
796
|
-
return warns;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* @param {ReturnType<typeof scopedOxePaths>} p
|
|
801
|
-
* @returns {string[]}
|
|
802
|
-
*/
|
|
803
|
-
function investigationWarnings(p) {
|
|
804
|
-
/** @type {string[]} */
|
|
805
|
-
const warns = [];
|
|
806
|
-
if (fs.existsSync(p.investigationsDir) && !fs.existsSync(p.investigationsIndex)) {
|
|
807
|
-
warns.push('Existe pasta de investigações, mas falta o índice INVESTIGATIONS.md');
|
|
808
|
-
}
|
|
809
|
-
return warns;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
/**
|
|
813
|
-
* @param {string} stateText
|
|
814
|
-
* @param {ReturnType<typeof scopedOxePaths>} p
|
|
815
|
-
* @returns {string[]}
|
|
816
|
-
*/
|
|
817
|
-
function planReviewWarnings(stateText, p) {
|
|
818
|
-
/** @type {string[]} */
|
|
819
|
-
const warns = [];
|
|
820
|
-
const reviewStatus = parsePlanReviewStatus(stateText);
|
|
821
|
-
if (fs.existsSync(p.plan) && !reviewStatus) {
|
|
822
|
-
warns.push('PLAN.md existe, mas STATE.md não declara plan_review_status');
|
|
823
|
-
}
|
|
824
|
-
if (reviewStatus && !fs.existsSync(p.planReview)) {
|
|
825
|
-
warns.push('STATE.md declara revisão do plano, mas PLAN-REVIEW.md não existe');
|
|
826
|
-
}
|
|
827
|
-
if (reviewStatus === 'needs_revision' || reviewStatus === 'rejected') {
|
|
828
|
-
warns.push(`Plano em estado ${reviewStatus} — revisão adicional necessária antes de executar`);
|
|
829
|
-
}
|
|
830
|
-
return warns;
|
|
831
|
-
}
|
|
932
|
+
return w;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* @param {string} planPath
|
|
937
|
+
* @returns {{
|
|
938
|
+
* hasSection: boolean,
|
|
939
|
+
* bestPlan: string | null,
|
|
940
|
+
* confidence: number | null,
|
|
941
|
+
* warnings: string[],
|
|
942
|
+
* }}
|
|
943
|
+
*/
|
|
944
|
+
function parsePlanSelfEvaluation(planPath) {
|
|
945
|
+
const empty = { hasSection: false, bestPlan: null, confidence: null, warnings: [] };
|
|
946
|
+
if (!fs.existsSync(planPath)) return empty;
|
|
947
|
+
const raw = fs.readFileSync(planPath, 'utf8');
|
|
948
|
+
const m = raw.match(/##\s*Autoavaliação do Plano\s*([\s\S]*?)(?=\n## |\n#[^\#]|$)/i);
|
|
949
|
+
if (!m) {
|
|
950
|
+
return {
|
|
951
|
+
...empty,
|
|
952
|
+
warnings: ['PLAN.md sem a seção obrigatória "## Autoavaliação do Plano"'],
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
const body = m[1];
|
|
956
|
+
const best = body.match(/\*\*Melhor plano atual:\*\*\s*(sim|não|nao)/i);
|
|
957
|
+
const confidence = body.match(/\*\*Confiança:\*\*\s*(\d{1,3})\s*%/i);
|
|
958
|
+
/** @type {string[]} */
|
|
959
|
+
const warnings = [];
|
|
960
|
+
const rubricLabels = [
|
|
961
|
+
'Completude dos requisitos',
|
|
962
|
+
'Dependências conhecidas',
|
|
963
|
+
'Risco técnico',
|
|
964
|
+
'Impacto no código existente',
|
|
965
|
+
'Clareza da validação / testes',
|
|
966
|
+
'Lacunas externas / decisões pendentes',
|
|
967
|
+
];
|
|
968
|
+
if (!best) warnings.push('PLAN.md: autoavaliação sem "Melhor plano atual: sim|não"');
|
|
969
|
+
if (!confidence) warnings.push('PLAN.md: autoavaliação sem "Confiança: NN%"');
|
|
970
|
+
if (!/\*\*Principais incertezas:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Principais incertezas"');
|
|
971
|
+
if (!/\*\*Alternativas descartadas:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Alternativas descartadas"');
|
|
972
|
+
if (!/\*\*Condição para replanejar:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Condição para replanejar"');
|
|
973
|
+
for (const label of rubricLabels) {
|
|
974
|
+
if (!body.includes(label)) warnings.push(`PLAN.md: rubrica sem "${label}"`);
|
|
975
|
+
}
|
|
976
|
+
const parsedConfidence = confidence ? Number(confidence[1]) : null;
|
|
977
|
+
if (parsedConfidence != null && (parsedConfidence < 0 || parsedConfidence > 100)) {
|
|
978
|
+
warnings.push('PLAN.md: confiança fora do intervalo 0–100%');
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
hasSection: true,
|
|
982
|
+
bestPlan: best ? best[1].toLowerCase().replace('nao', 'não') : null,
|
|
983
|
+
confidence: parsedConfidence,
|
|
984
|
+
warnings,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* @param {string} planPath
|
|
990
|
+
* @param {number} threshold
|
|
991
|
+
* @returns {string[]}
|
|
992
|
+
*/
|
|
993
|
+
function planSelfEvaluationWarnings(planPath, threshold) {
|
|
994
|
+
const info = parsePlanSelfEvaluation(planPath);
|
|
995
|
+
const warns = [...info.warnings];
|
|
996
|
+
if (!fs.existsSync(planPath)) return warns;
|
|
997
|
+
if (info.bestPlan === 'não') warns.push('PLAN.md: autoavaliação declara que este não é o melhor plano atual');
|
|
998
|
+
if (info.confidence != null && info.confidence < threshold) {
|
|
999
|
+
warns.push(`PLAN.md: confiança ${info.confidence}% abaixo do limiar executável (${threshold}%)`);
|
|
1000
|
+
}
|
|
1001
|
+
return warns;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* @param {string} target
|
|
1006
|
+
* @param {string | null} activeSession
|
|
1007
|
+
* @returns {string[]}
|
|
1008
|
+
*/
|
|
1009
|
+
function sessionWarnings(target, activeSession) {
|
|
1010
|
+
if (!activeSession) return [];
|
|
1011
|
+
const base = oxePaths(target);
|
|
1012
|
+
const scoped = scopedOxePaths(target, activeSession);
|
|
1013
|
+
/** @type {string[]} */
|
|
1014
|
+
const warns = [];
|
|
1015
|
+
if (!/^sessions\/s\d{3}-/.test(activeSession)) {
|
|
1016
|
+
warns.push(`active_session "${activeSession}" não segue o formato sessions/sNNN-slug`);
|
|
1017
|
+
}
|
|
1018
|
+
if (!fs.existsSync(scoped.sessionRoot)) {
|
|
1019
|
+
warns.push(`active_session aponta para ${activeSession}, mas a pasta da sessão não existe em .oxe/`);
|
|
1020
|
+
return warns;
|
|
1021
|
+
}
|
|
1022
|
+
if (!fs.existsSync(scoped.sessionManifest)) warns.push(`Sessão ativa ${activeSession} sem SESSION.md`);
|
|
1023
|
+
if (!fs.existsSync(base.sessionsIndex)) warns.push('Sessão ativa definida, mas .oxe/SESSIONS.md não existe');
|
|
1024
|
+
return warns;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* @param {string} target
|
|
1029
|
+
* @returns {string[]}
|
|
1030
|
+
*/
|
|
1031
|
+
function installationCompletenessWarnings(target) {
|
|
1032
|
+
const p = oxePaths(target);
|
|
1033
|
+
/** @type {string[]} */
|
|
1034
|
+
const warns = [];
|
|
1035
|
+
if (!fs.existsSync(p.oxe)) return warns;
|
|
1036
|
+
if (!fs.existsSync(p.globalDir)) warns.push('.oxe/global/ ausente');
|
|
1037
|
+
if (!fs.existsSync(p.globalLessons)) warns.push('.oxe/global/LESSONS.md ausente');
|
|
1038
|
+
if (!fs.existsSync(p.globalMilestones)) warns.push('.oxe/global/MILESTONES.md ausente');
|
|
1039
|
+
if (!fs.existsSync(p.globalMilestonesDir)) warns.push('.oxe/global/milestones/ ausente');
|
|
1040
|
+
if (!fs.existsSync(p.sessionsDir)) warns.push('.oxe/sessions/ ausente');
|
|
1041
|
+
if (!fs.existsSync(p.capabilitiesDir)) warns.push('.oxe/capabilities/ ausente');
|
|
1042
|
+
if (!fs.existsSync(p.capabilitiesIndex)) warns.push('.oxe/CAPABILITIES.md ausente');
|
|
1043
|
+
if (!fs.existsSync(p.investigationsDir)) warns.push('.oxe/investigations/ ausente');
|
|
1044
|
+
if (!fs.existsSync(p.investigationsIndex)) warns.push('.oxe/INVESTIGATIONS.md ausente');
|
|
1045
|
+
if (!fs.existsSync(p.runtime)) warns.push('.oxe/EXECUTION-RUNTIME.md ausente');
|
|
1046
|
+
if (!fs.existsSync(p.checkpoints)) warns.push('.oxe/CHECKPOINTS.md ausente');
|
|
1047
|
+
if (!fs.existsSync(p.activeRun)) warns.push('.oxe/ACTIVE-RUN.json ausente');
|
|
1048
|
+
if (!fs.existsSync(p.runsDir)) warns.push('.oxe/runs/ ausente');
|
|
1049
|
+
if (!fs.existsSync(p.events)) warns.push('.oxe/OXE-EVENTS.ndjson ausente');
|
|
1050
|
+
if (azure.isAzureContextEnabled(target)) {
|
|
1051
|
+
const azurePaths = azure.azurePaths(target);
|
|
1052
|
+
if (!fs.existsSync(azurePaths.root)) warns.push('.oxe/cloud/azure/ ausente');
|
|
1053
|
+
if (!fs.existsSync(azurePaths.operationsDir)) warns.push('.oxe/cloud/azure/operations/ ausente');
|
|
1054
|
+
if (!fs.existsSync(azurePaths.profile)) warns.push('.oxe/cloud/azure/profile.json ausente');
|
|
1055
|
+
if (!fs.existsSync(azurePaths.authStatus)) warns.push('.oxe/cloud/azure/auth-status.json ausente');
|
|
1056
|
+
}
|
|
1057
|
+
return warns;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* @param {string} stateText
|
|
1062
|
+
* @param {ReturnType<typeof scopedOxePaths>} p
|
|
1063
|
+
* @returns {string[]}
|
|
1064
|
+
*/
|
|
1065
|
+
function runtimeWarnings(stateText, p) {
|
|
1066
|
+
/** @type {string[]} */
|
|
1067
|
+
const warns = [];
|
|
1068
|
+
const checkpointPending = /\*\*checkpoint_status:\*\*\s*`?pending_approval`?/i.test(stateText);
|
|
1069
|
+
const runtimeBlocked = /\*\*runtime_status:\*\*\s*`?(blocked|waiting_approval|failed)`?/i.test(stateText);
|
|
1070
|
+
if (checkpointPending && !fs.existsSync(p.checkpoints)) {
|
|
1071
|
+
warns.push('STATE.md indica checkpoint pendente, mas o índice de checkpoints não existe');
|
|
1072
|
+
}
|
|
1073
|
+
if (runtimeBlocked && !fs.existsSync(p.runtime)) {
|
|
1074
|
+
warns.push('STATE.md indica runtime bloqueado, mas EXECUTION-RUNTIME.md não existe');
|
|
1075
|
+
}
|
|
1076
|
+
if (fs.existsSync(p.runtime)) {
|
|
1077
|
+
const raw = fs.readFileSync(p.runtime, 'utf8');
|
|
1078
|
+
if (!/##\s*Checkpoints/i.test(raw)) warns.push('EXECUTION-RUNTIME.md sem seção "Checkpoints"');
|
|
1079
|
+
if (!/##\s*Agentes ativos/i.test(raw)) warns.push('EXECUTION-RUNTIME.md sem seção "Agentes ativos"');
|
|
1080
|
+
if (!/Run ID/i.test(raw)) warns.push('EXECUTION-RUNTIME.md sem referência explícita de Run ID');
|
|
1081
|
+
if (!/Tracing operacional/i.test(raw)) warns.push('EXECUTION-RUNTIME.md sem seção "Tracing operacional"');
|
|
1082
|
+
}
|
|
1083
|
+
if (!fs.existsSync(p.activeRun)) {
|
|
1084
|
+
warns.push('ACTIVE-RUN.json não existe para o escopo atual');
|
|
1085
|
+
}
|
|
1086
|
+
if (!fs.existsSync(p.events)) {
|
|
1087
|
+
warns.push('OXE-EVENTS.ndjson não existe para o escopo atual');
|
|
1088
|
+
}
|
|
1089
|
+
const runState = operational.readRunState(path.dirname(p.oxe), p.activeSession || null);
|
|
1090
|
+
const checkpointRows = [];
|
|
1091
|
+
if (fs.existsSync(p.checkpoints)) {
|
|
1092
|
+
const checkpointText = fs.readFileSync(p.checkpoints, 'utf8');
|
|
1093
|
+
for (const line of checkpointText.split('\n')) {
|
|
1094
|
+
const match = line.match(/^\|\s*(CP-[^|]+)\|\s*[^|]*\|\s*[^|]*\|\s*[^|]*\|\s*([^|]+)\|/i);
|
|
1095
|
+
if (!match) continue;
|
|
1096
|
+
checkpointRows.push({ id: match[1].trim(), status: match[2].trim() });
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
for (const warn of operational.runtimeStateWarnings(runState, checkpointRows)) warns.push(warn);
|
|
1100
|
+
return warns;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* @param {ReturnType<typeof scopedOxePaths>} p
|
|
1105
|
+
* @returns {string[]}
|
|
1106
|
+
*/
|
|
1107
|
+
function capabilityWarnings(p) {
|
|
1108
|
+
/** @type {string[]} */
|
|
1109
|
+
const warns = [];
|
|
1110
|
+
if (fs.existsSync(p.capabilitiesDir) && !fs.existsSync(p.capabilitiesIndex)) {
|
|
1111
|
+
warns.push('Existem capabilities em .oxe/capabilities/, mas .oxe/CAPABILITIES.md não existe');
|
|
1112
|
+
}
|
|
1113
|
+
for (const warn of operational.capabilityCatalogWarnings(path.dirname(p.oxe))) warns.push(warn);
|
|
1114
|
+
return warns;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* @param {ReturnType<typeof scopedOxePaths>} p
|
|
1119
|
+
* @returns {string[]}
|
|
1120
|
+
*/
|
|
1121
|
+
function investigationWarnings(p) {
|
|
1122
|
+
/** @type {string[]} */
|
|
1123
|
+
const warns = [];
|
|
1124
|
+
if (fs.existsSync(p.investigationsDir) && !fs.existsSync(p.investigationsIndex)) {
|
|
1125
|
+
warns.push('Existe pasta de investigações, mas falta o índice INVESTIGATIONS.md');
|
|
1126
|
+
}
|
|
1127
|
+
return warns;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* @param {string} stateText
|
|
1132
|
+
* @param {ReturnType<typeof scopedOxePaths>} p
|
|
1133
|
+
* @returns {string[]}
|
|
1134
|
+
*/
|
|
1135
|
+
function planReviewWarnings(stateText, p) {
|
|
1136
|
+
/** @type {string[]} */
|
|
1137
|
+
const warns = [];
|
|
1138
|
+
const reviewStatus = parsePlanReviewStatus(stateText);
|
|
1139
|
+
if (fs.existsSync(p.plan) && !reviewStatus) {
|
|
1140
|
+
warns.push('PLAN.md existe, mas STATE.md não declara plan_review_status');
|
|
1141
|
+
}
|
|
1142
|
+
if (reviewStatus && !fs.existsSync(p.planReview)) {
|
|
1143
|
+
warns.push('STATE.md declara revisão do plano, mas PLAN-REVIEW.md não existe');
|
|
1144
|
+
}
|
|
1145
|
+
if (reviewStatus === 'needs_revision' || reviewStatus === 'rejected') {
|
|
1146
|
+
warns.push(`Plano em estado ${reviewStatus} — revisão adicional necessária antes de executar`);
|
|
1147
|
+
}
|
|
1148
|
+
return warns;
|
|
1149
|
+
}
|
|
832
1150
|
|
|
833
1151
|
/**
|
|
834
1152
|
* Próximo passo único (espelha o workflow next.md).
|
|
835
1153
|
* @param {string} target
|
|
836
1154
|
* @param {{ discuss_before_plan?: boolean }} cfg
|
|
837
1155
|
*/
|
|
838
|
-
function suggestNextStep(target, cfg = {}) {
|
|
839
|
-
const base = oxePaths(target);
|
|
840
|
-
const stateText = fs.existsSync(base.state) ? fs.readFileSync(base.state, 'utf8') : '';
|
|
841
|
-
const p = scopedOxePaths(target, parseActiveSession(stateText));
|
|
842
|
-
const discussBefore = Boolean(cfg.discuss_before_plan);
|
|
843
|
-
const threshold = Number(cfg.plan_confidence_threshold) || 70;
|
|
844
|
-
const has = (/** @type {string} */ f) => fs.existsSync(f);
|
|
845
|
-
const mapsComplete = EXPECTED_CODEBASE_MAPS.every((f) => has(path.join(p.codebase, f)));
|
|
846
|
-
const azureActive = azure.isAzureContextEnabled(target, cfg);
|
|
847
|
-
|
|
848
|
-
if (!has(p.oxe) || !has(p.state)) {
|
|
1156
|
+
function suggestNextStep(target, cfg = {}) {
|
|
1157
|
+
const base = oxePaths(target);
|
|
1158
|
+
const stateText = fs.existsSync(base.state) ? fs.readFileSync(base.state, 'utf8') : '';
|
|
1159
|
+
const p = scopedOxePaths(target, parseActiveSession(stateText));
|
|
1160
|
+
const discussBefore = Boolean(cfg.discuss_before_plan);
|
|
1161
|
+
const threshold = Number(cfg.plan_confidence_threshold) || 70;
|
|
1162
|
+
const has = (/** @type {string} */ f) => fs.existsSync(f);
|
|
1163
|
+
const mapsComplete = EXPECTED_CODEBASE_MAPS.every((f) => has(path.join(p.codebase, f)));
|
|
1164
|
+
const azureActive = azure.isAzureContextEnabled(target, cfg);
|
|
1165
|
+
|
|
1166
|
+
if (!has(p.oxe) || !has(p.state)) {
|
|
849
1167
|
return {
|
|
850
1168
|
step: 'scan',
|
|
851
1169
|
cursorCmd: '/oxe-scan',
|
|
@@ -854,56 +1172,56 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
854
1172
|
};
|
|
855
1173
|
}
|
|
856
1174
|
|
|
857
|
-
const phase = parseStatePhase(stateText);
|
|
1175
|
+
const phase = parseStatePhase(stateText);
|
|
858
1176
|
|
|
859
|
-
if (!mapsComplete && !has(p.quick)) {
|
|
1177
|
+
if (!mapsComplete && !has(p.quick)) {
|
|
860
1178
|
return {
|
|
861
1179
|
step: 'scan',
|
|
862
1180
|
cursorCmd: '/oxe-scan',
|
|
863
1181
|
reason: 'Mapas do codebase incompletos e sem QUICK.md — atualize o contexto com scan',
|
|
864
1182
|
artifacts: ['.oxe/codebase/'],
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
if (azureActive) {
|
|
869
|
-
const azureHealth = azure.azureDoctor(target, cfg, {
|
|
870
|
-
autoInstall: false,
|
|
871
|
-
write: false,
|
|
872
|
-
});
|
|
873
|
-
if (!azureHealth.authStatus || !azureHealth.authStatus.login_active) {
|
|
874
|
-
return {
|
|
875
|
-
step: 'azure-auth',
|
|
876
|
-
cursorCmd: 'npx oxe-cc azure auth login',
|
|
877
|
-
reason: 'Contexto Azure ativo, mas sem sessão Azure CLI autenticada',
|
|
878
|
-
artifacts: ['.oxe/cloud/azure/profile.json', '.oxe/cloud/azure/auth-status.json'],
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
if (!azureHealth.profile || !azureHealth.profile.subscription_id) {
|
|
882
|
-
return {
|
|
883
|
-
step: 'azure-auth',
|
|
884
|
-
cursorCmd: 'npx oxe-cc azure auth set-subscription --subscription <id-ou-nome>',
|
|
885
|
-
reason: 'Contexto Azure ativo, mas a subscription operacional ainda não está definida',
|
|
886
|
-
artifacts: ['.oxe/cloud/azure/profile.json', '.oxe/cloud/azure/auth-status.json'],
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
const maxAgeHours = cfg.azure && cfg.azure.inventory_max_age_hours != null
|
|
890
|
-
? Number(cfg.azure.inventory_max_age_hours)
|
|
891
|
-
: 24;
|
|
892
|
-
const syncedAt = Date.parse(String(azureHealth.inventory && azureHealth.inventory.synced_at || ''));
|
|
893
|
-
const staleInventory =
|
|
894
|
-
!azureHealth.inventory ||
|
|
895
|
-
(maxAgeHours > 0 && !Number.isNaN(syncedAt) && ((Date.now() - syncedAt) / (1000 * 60 * 60)) > maxAgeHours);
|
|
896
|
-
if (staleInventory) {
|
|
897
|
-
return {
|
|
898
|
-
step: 'azure-sync',
|
|
899
|
-
cursorCmd: 'npx oxe-cc azure sync',
|
|
900
|
-
reason: 'Contexto Azure ativo, mas o inventário está ausente ou stale',
|
|
901
|
-
artifacts: ['.oxe/cloud/azure/inventory.json', '.oxe/cloud/azure/INVENTORY.md'],
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
if (phase === 'quick_active' || (has(p.quick) && !has(p.plan))) {
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
if (azureActive) {
|
|
1187
|
+
const azureHealth = azure.azureDoctor(target, cfg, {
|
|
1188
|
+
autoInstall: false,
|
|
1189
|
+
write: false,
|
|
1190
|
+
});
|
|
1191
|
+
if (!azureHealth.authStatus || !azureHealth.authStatus.login_active) {
|
|
1192
|
+
return {
|
|
1193
|
+
step: 'azure-auth',
|
|
1194
|
+
cursorCmd: 'npx oxe-cc azure auth login',
|
|
1195
|
+
reason: 'Contexto Azure ativo, mas sem sessão Azure CLI autenticada',
|
|
1196
|
+
artifacts: ['.oxe/cloud/azure/profile.json', '.oxe/cloud/azure/auth-status.json'],
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
if (!azureHealth.profile || !azureHealth.profile.subscription_id) {
|
|
1200
|
+
return {
|
|
1201
|
+
step: 'azure-auth',
|
|
1202
|
+
cursorCmd: 'npx oxe-cc azure auth set-subscription --subscription <id-ou-nome>',
|
|
1203
|
+
reason: 'Contexto Azure ativo, mas a subscription operacional ainda não está definida',
|
|
1204
|
+
artifacts: ['.oxe/cloud/azure/profile.json', '.oxe/cloud/azure/auth-status.json'],
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
const maxAgeHours = cfg.azure && cfg.azure.inventory_max_age_hours != null
|
|
1208
|
+
? Number(cfg.azure.inventory_max_age_hours)
|
|
1209
|
+
: 24;
|
|
1210
|
+
const syncedAt = Date.parse(String(azureHealth.inventory && azureHealth.inventory.synced_at || ''));
|
|
1211
|
+
const staleInventory =
|
|
1212
|
+
!azureHealth.inventory ||
|
|
1213
|
+
(maxAgeHours > 0 && !Number.isNaN(syncedAt) && ((Date.now() - syncedAt) / (1000 * 60 * 60)) > maxAgeHours);
|
|
1214
|
+
if (staleInventory) {
|
|
1215
|
+
return {
|
|
1216
|
+
step: 'azure-sync',
|
|
1217
|
+
cursorCmd: 'npx oxe-cc azure sync',
|
|
1218
|
+
reason: 'Contexto Azure ativo, mas o inventário está ausente ou stale',
|
|
1219
|
+
artifacts: ['.oxe/cloud/azure/inventory.json', '.oxe/cloud/azure/INVENTORY.md'],
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (phase === 'quick_active' || (has(p.quick) && !has(p.plan))) {
|
|
907
1225
|
return {
|
|
908
1226
|
step: 'execute',
|
|
909
1227
|
cursorCmd: '/oxe-execute',
|
|
@@ -930,71 +1248,71 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
930
1248
|
};
|
|
931
1249
|
}
|
|
932
1250
|
|
|
933
|
-
if (!has(p.plan)) {
|
|
934
|
-
return {
|
|
935
|
-
step: 'plan',
|
|
936
|
-
cursorCmd: '/oxe-plan',
|
|
937
|
-
reason: 'SPEC existe mas PLAN.md não — gere o plano com verificação por tarefa',
|
|
938
|
-
artifacts: ['.oxe/PLAN.md'],
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
const selfEval = parsePlanSelfEvaluation(p.plan);
|
|
943
|
-
if (selfEval.bestPlan === 'não' || (selfEval.confidence != null && selfEval.confidence < threshold)) {
|
|
944
|
-
return {
|
|
945
|
-
step: 'plan',
|
|
946
|
-
cursorCmd: '/oxe-plan --replan',
|
|
947
|
-
reason: `O plano atual ainda não atingiu confiança executável (limiar ${threshold}%)`,
|
|
948
|
-
artifacts: ['.oxe/PLAN.md', '.oxe/STATE.md'],
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
const reviewStatus = parsePlanReviewStatus(stateText);
|
|
953
|
-
if (phase === 'plan_ready' && (reviewStatus === 'needs_revision' || reviewStatus === 'rejected')) {
|
|
954
|
-
return {
|
|
955
|
-
step: 'plan',
|
|
956
|
-
cursorCmd: '/oxe-plan --replan',
|
|
957
|
-
reason: `Revisão do plano em estado ${reviewStatus} — ajuste o plano antes de executar`,
|
|
958
|
-
artifacts: ['.oxe/PLAN.md', '.oxe/PLAN-REVIEW.md', '.oxe/STATE.md'],
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
if (phase === 'plan_ready' && (!reviewStatus || reviewStatus === 'draft' || reviewStatus === 'in_review')) {
|
|
962
|
-
return {
|
|
963
|
-
step: 'dashboard',
|
|
964
|
-
cursorCmd: '/oxe-dashboard',
|
|
965
|
-
reason: 'Plano pronto, mas ainda não passou por revisão/aprovação visual',
|
|
966
|
-
artifacts: ['.oxe/PLAN.md', '.oxe/PLAN-REVIEW.md', '.oxe/STATE.md'],
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
const activeRun = operational.readRunState(target, parseActiveSession(stateText));
|
|
971
|
-
if (activeRun && activeRun.status === 'waiting_approval') {
|
|
972
|
-
return {
|
|
973
|
-
step: 'dashboard',
|
|
974
|
-
cursorCmd: '/oxe-dashboard',
|
|
975
|
-
reason: 'ACTIVE-RUN está aguardando aprovação formal antes de continuar',
|
|
976
|
-
artifacts: ['.oxe/ACTIVE-RUN.json', '.oxe/CHECKPOINTS.md', '.oxe/OXE-EVENTS.ndjson'],
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
if (activeRun && activeRun.status === 'paused') {
|
|
980
|
-
return {
|
|
981
|
-
step: 'execute',
|
|
982
|
-
cursorCmd: '/oxe-execute',
|
|
983
|
-
reason: 'ACTIVE-RUN está pausado — retome a execução a partir do cursor atual',
|
|
984
|
-
artifacts: ['.oxe/ACTIVE-RUN.json', '.oxe/EXECUTION-RUNTIME.md'],
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
if (/\*\*checkpoint_status:\*\*\s*`?pending_approval`?/i.test(stateText)) {
|
|
989
|
-
return {
|
|
990
|
-
step: 'execute',
|
|
991
|
-
cursorCmd: '/oxe-execute',
|
|
992
|
-
reason: 'Há checkpoint pendente de aprovação — resolva a aprovação antes de avançar a execução',
|
|
993
|
-
artifacts: ['.oxe/CHECKPOINTS.md', '.oxe/STATE.md'],
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
if (!has(p.verify)) {
|
|
1251
|
+
if (!has(p.plan)) {
|
|
1252
|
+
return {
|
|
1253
|
+
step: 'plan',
|
|
1254
|
+
cursorCmd: '/oxe-plan',
|
|
1255
|
+
reason: 'SPEC existe mas PLAN.md não — gere o plano com verificação por tarefa',
|
|
1256
|
+
artifacts: ['.oxe/PLAN.md'],
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const selfEval = parsePlanSelfEvaluation(p.plan);
|
|
1261
|
+
if (selfEval.bestPlan === 'não' || (selfEval.confidence != null && selfEval.confidence < threshold)) {
|
|
1262
|
+
return {
|
|
1263
|
+
step: 'plan',
|
|
1264
|
+
cursorCmd: '/oxe-plan --replan',
|
|
1265
|
+
reason: `O plano atual ainda não atingiu confiança executável (limiar ${threshold}%)`,
|
|
1266
|
+
artifacts: ['.oxe/PLAN.md', '.oxe/STATE.md'],
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const reviewStatus = parsePlanReviewStatus(stateText);
|
|
1271
|
+
if (phase === 'plan_ready' && (reviewStatus === 'needs_revision' || reviewStatus === 'rejected')) {
|
|
1272
|
+
return {
|
|
1273
|
+
step: 'plan',
|
|
1274
|
+
cursorCmd: '/oxe-plan --replan',
|
|
1275
|
+
reason: `Revisão do plano em estado ${reviewStatus} — ajuste o plano antes de executar`,
|
|
1276
|
+
artifacts: ['.oxe/PLAN.md', '.oxe/PLAN-REVIEW.md', '.oxe/STATE.md'],
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
if (phase === 'plan_ready' && (!reviewStatus || reviewStatus === 'draft' || reviewStatus === 'in_review')) {
|
|
1280
|
+
return {
|
|
1281
|
+
step: 'dashboard',
|
|
1282
|
+
cursorCmd: '/oxe-dashboard',
|
|
1283
|
+
reason: 'Plano pronto, mas ainda não passou por revisão/aprovação visual',
|
|
1284
|
+
artifacts: ['.oxe/PLAN.md', '.oxe/PLAN-REVIEW.md', '.oxe/STATE.md'],
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const activeRun = operational.readRunState(target, parseActiveSession(stateText));
|
|
1289
|
+
if (activeRun && activeRun.status === 'waiting_approval') {
|
|
1290
|
+
return {
|
|
1291
|
+
step: 'dashboard',
|
|
1292
|
+
cursorCmd: '/oxe-dashboard',
|
|
1293
|
+
reason: 'ACTIVE-RUN está aguardando aprovação formal antes de continuar',
|
|
1294
|
+
artifacts: ['.oxe/ACTIVE-RUN.json', '.oxe/CHECKPOINTS.md', '.oxe/OXE-EVENTS.ndjson'],
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
if (activeRun && activeRun.status === 'paused') {
|
|
1298
|
+
return {
|
|
1299
|
+
step: 'execute',
|
|
1300
|
+
cursorCmd: '/oxe-execute',
|
|
1301
|
+
reason: 'ACTIVE-RUN está pausado — retome a execução a partir do cursor atual',
|
|
1302
|
+
artifacts: ['.oxe/ACTIVE-RUN.json', '.oxe/EXECUTION-RUNTIME.md'],
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
if (/\*\*checkpoint_status:\*\*\s*`?pending_approval`?/i.test(stateText)) {
|
|
1307
|
+
return {
|
|
1308
|
+
step: 'execute',
|
|
1309
|
+
cursorCmd: '/oxe-execute',
|
|
1310
|
+
reason: 'Há checkpoint pendente de aprovação — resolva a aprovação antes de avançar a execução',
|
|
1311
|
+
artifacts: ['.oxe/CHECKPOINTS.md', '.oxe/STATE.md'],
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
if (!has(p.verify)) {
|
|
998
1316
|
return {
|
|
999
1317
|
step: 'execute',
|
|
1000
1318
|
cursorCmd: '/oxe-execute',
|
|
@@ -1065,140 +1383,292 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
1065
1383
|
/**
|
|
1066
1384
|
* @param {string} target
|
|
1067
1385
|
*/
|
|
1068
|
-
function buildHealthReport(target) {
|
|
1069
|
-
const
|
|
1070
|
-
const
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
stateText = '';
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1386
|
+
function buildHealthReport(target) {
|
|
1387
|
+
const contextEngine = require('./oxe-context-engine.cjs');
|
|
1388
|
+
const runtimeSemantics = require('./oxe-runtime-semantics.cjs');
|
|
1389
|
+
const { config, path: cfgPath, parseError } = loadOxeConfigMerged(target);
|
|
1390
|
+
const shape = validateConfigShape(config);
|
|
1391
|
+
const base = oxePaths(target);
|
|
1392
|
+
let stateText = '';
|
|
1393
|
+
if (fs.existsSync(base.state)) {
|
|
1394
|
+
try {
|
|
1395
|
+
stateText = fs.readFileSync(base.state, 'utf8');
|
|
1396
|
+
} catch {
|
|
1397
|
+
stateText = '';
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
const activeSession = parseActiveSession(stateText);
|
|
1401
|
+
const p = scopedOxePaths(target, activeSession);
|
|
1402
|
+
const phase = parseStatePhase(stateText);
|
|
1083
1403
|
const scanDate = parseLastScanDate(stateText);
|
|
1084
1404
|
const stale = isStaleScan(scanDate, Number(config.scan_max_age_days) || 0);
|
|
1085
1405
|
const compactDate = parseLastCompactDate(stateText);
|
|
1086
1406
|
const staleCompact = isStaleScan(compactDate, Number(config.compact_max_age_days) || 0);
|
|
1087
1407
|
const retroDate = parseLastRetroDate(stateText);
|
|
1088
|
-
const staleLessons = isStaleLessons(retroDate, Number(config.lessons_max_age_days) || 0);
|
|
1089
|
-
const phaseWarn = phase ? phaseCoherenceWarnings(phase, p) : [];
|
|
1090
|
-
const runtimeWarn = runtimeWarnings(stateText, p);
|
|
1091
|
-
const sumWarn = verifyGapsWithoutSummaryWarning(p.verify, p.summary);
|
|
1092
|
-
const specReq = Array.isArray(config.spec_required_sections) ? config.spec_required_sections : [];
|
|
1093
|
-
const specWarn = specSectionWarnings(p.spec, specReq.map(String));
|
|
1094
|
-
const threshold = Number(config.plan_confidence_threshold) || 70;
|
|
1095
|
-
const capabilityWarn = capabilityWarnings(p);
|
|
1096
|
-
const investigationWarn = investigationWarnings(p);
|
|
1097
|
-
const planWarn = [
|
|
1098
|
-
...planWaveWarningsFixed(p.plan, Number(config.plan_max_tasks_per_wave) || 0),
|
|
1099
|
-
...planTaskAceiteWarnings(p.plan),
|
|
1100
|
-
...planSelfEvaluationWarnings(p.plan, threshold),
|
|
1101
|
-
...planAgentsWarnings(target),
|
|
1102
|
-
];
|
|
1103
|
-
const sessionWarn = sessionWarnings(target, activeSession);
|
|
1104
|
-
const installWarn = installationCompletenessWarnings(target);
|
|
1105
|
-
const
|
|
1106
|
-
const
|
|
1107
|
-
const
|
|
1108
|
-
const
|
|
1109
|
-
const
|
|
1110
|
-
const
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1408
|
+
const staleLessons = isStaleLessons(retroDate, Number(config.lessons_max_age_days) || 0);
|
|
1409
|
+
const phaseWarn = phase ? phaseCoherenceWarnings(phase, p) : [];
|
|
1410
|
+
const runtimeWarn = runtimeWarnings(stateText, p);
|
|
1411
|
+
const sumWarn = verifyGapsWithoutSummaryWarning(p.verify, p.summary);
|
|
1412
|
+
const specReq = Array.isArray(config.spec_required_sections) ? config.spec_required_sections : [];
|
|
1413
|
+
const specWarn = specSectionWarnings(p.spec, specReq.map(String));
|
|
1414
|
+
const threshold = Number(config.plan_confidence_threshold) || 70;
|
|
1415
|
+
const capabilityWarn = capabilityWarnings(p);
|
|
1416
|
+
const investigationWarn = investigationWarnings(p);
|
|
1417
|
+
const planWarn = [
|
|
1418
|
+
...planWaveWarningsFixed(p.plan, Number(config.plan_max_tasks_per_wave) || 0),
|
|
1419
|
+
...planTaskAceiteWarnings(p.plan),
|
|
1420
|
+
...planSelfEvaluationWarnings(p.plan, threshold),
|
|
1421
|
+
...planAgentsWarnings(target),
|
|
1422
|
+
];
|
|
1423
|
+
const sessionWarn = sessionWarnings(target, activeSession);
|
|
1424
|
+
const installWarn = installationCompletenessWarnings(target);
|
|
1425
|
+
const copilot = copilotIntegrationReport(target);
|
|
1426
|
+
const copilotWarn = copilot.warnings;
|
|
1427
|
+
const reviewWarn = planReviewWarnings(stateText, p);
|
|
1428
|
+
const planSelfEvaluation = parsePlanSelfEvaluation(p.plan);
|
|
1429
|
+
const activeRun = operational.readRunState(target, activeSession);
|
|
1430
|
+
const eventsSummary = operational.summarizeEvents(operational.readEvents(target, activeSession));
|
|
1431
|
+
const memoryLayers = operational.buildMemoryLayers(target, activeSession);
|
|
1432
|
+
const azureActive = azure.isAzureContextEnabled(target, config);
|
|
1433
|
+
const azureReport = azureActive
|
|
1434
|
+
? azure.azureDoctor(target, config, {
|
|
1435
|
+
autoInstall: false,
|
|
1436
|
+
write: false,
|
|
1437
|
+
})
|
|
1438
|
+
: null;
|
|
1439
|
+
const azureInventorySyncedAt = azureReport && azureReport.inventory ? azureReport.inventory.synced_at || null : null;
|
|
1440
|
+
const azureInventoryMaxAgeHours = config.azure && config.azure.inventory_max_age_hours != null
|
|
1441
|
+
? Number(config.azure.inventory_max_age_hours)
|
|
1442
|
+
: 24;
|
|
1443
|
+
let azureInventoryStale = { stale: false, hours: null };
|
|
1444
|
+
if (azureInventorySyncedAt) {
|
|
1445
|
+
const syncedAt = Date.parse(String(azureInventorySyncedAt));
|
|
1446
|
+
if (!Number.isNaN(syncedAt)) {
|
|
1447
|
+
const ageHours = Math.floor((Date.now() - syncedAt) / (1000 * 60 * 60));
|
|
1448
|
+
azureInventoryStale = {
|
|
1449
|
+
stale: azureInventoryMaxAgeHours > 0 ? ageHours > azureInventoryMaxAgeHours : false,
|
|
1450
|
+
hours: ageHours,
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
} else if (azureActive) {
|
|
1454
|
+
azureInventoryStale = { stale: true, hours: null };
|
|
1455
|
+
}
|
|
1456
|
+
const next = suggestNextStep(target, {
|
|
1457
|
+
discuss_before_plan: config.discuss_before_plan,
|
|
1458
|
+
plan_confidence_threshold: threshold,
|
|
1459
|
+
azure: config.azure,
|
|
1460
|
+
});
|
|
1461
|
+
/** @type {string[]} */
|
|
1462
|
+
const contextWarn = [];
|
|
1463
|
+
/** @type {Record<string, unknown>} */
|
|
1464
|
+
const contextPacks = {};
|
|
1465
|
+
/** @type {Record<string, unknown>} */
|
|
1466
|
+
const packFreshness = {};
|
|
1467
|
+
/** @type {{ project: string | null, session: string | null, phase: string | null }} */
|
|
1468
|
+
let activeSummaryRefs = { project: null, session: null, phase: null };
|
|
1469
|
+
/** @type {{ primaryWorkflow: string | null, primaryScore: number | null, primaryStatus: string | null, byWorkflow: Record<string, unknown> }} */
|
|
1470
|
+
let contextQuality = {
|
|
1471
|
+
primaryWorkflow: null,
|
|
1472
|
+
primaryScore: null,
|
|
1473
|
+
primaryStatus: null,
|
|
1474
|
+
byWorkflow: {},
|
|
1475
|
+
};
|
|
1476
|
+
// Bloco A — resolução de paths e refs de summaries
|
|
1477
|
+
try {
|
|
1478
|
+
const ctxPaths = contextEngine.contextPaths(target, activeSession);
|
|
1479
|
+
activeSummaryRefs = {
|
|
1480
|
+
project: ctxPaths.projectSummaryJson,
|
|
1481
|
+
session: ctxPaths.sessionSummaryJson,
|
|
1482
|
+
phase: ctxPaths.phaseSummaryJson,
|
|
1483
|
+
};
|
|
1484
|
+
} catch (err) {
|
|
1485
|
+
contextWarn.push(`Contexto — falha ao resolver paths (contextPaths): ${err instanceof Error ? err.message : String(err)}`);
|
|
1486
|
+
}
|
|
1487
|
+
// Bloco B — inspeção de context packs por workflow
|
|
1488
|
+
try {
|
|
1489
|
+
const contextMaterialized = fs.existsSync(base.contextIndex);
|
|
1490
|
+
const candidateWorkflows = Array.from(
|
|
1491
|
+
new Set(
|
|
1492
|
+
['dashboard', next.step, phase === 'planning' ? 'plan' : null, phase === 'executing' ? 'execute' : null, phase === 'verifying' ? 'verify' : null]
|
|
1493
|
+
.filter(Boolean)
|
|
1494
|
+
.map(String)
|
|
1495
|
+
)
|
|
1496
|
+
).filter((workflow) => Boolean(runtimeSemantics.getWorkflowContract(workflow)));
|
|
1497
|
+
for (const workflow of candidateWorkflows) {
|
|
1498
|
+
let pack;
|
|
1499
|
+
try {
|
|
1500
|
+
pack = contextEngine.inspectContextPack(target, { workflow, activeSession });
|
|
1501
|
+
} catch (err) {
|
|
1502
|
+
contextWarn.push(`Context pack ${workflow} — falha na inspeção: ${err instanceof Error ? err.message : String(err)}`);
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
contextPacks[workflow] = {
|
|
1506
|
+
path: pack.path || contextEngine.resolvePackFile(target, workflow, activeSession),
|
|
1507
|
+
context_tier: pack.context_tier,
|
|
1508
|
+
semantics_hash: pack.semantics_hash,
|
|
1509
|
+
read_order: pack.read_order,
|
|
1510
|
+
selected_artifacts: (pack.selected_artifacts || []).map((artifact) => ({
|
|
1511
|
+
alias: artifact.alias,
|
|
1512
|
+
path: artifact.path,
|
|
1513
|
+
exists: artifact.exists,
|
|
1514
|
+
required: artifact.required,
|
|
1515
|
+
using_fallback: artifact.using_fallback,
|
|
1516
|
+
scope: artifact.scope,
|
|
1517
|
+
summary: artifact.summary,
|
|
1518
|
+
})),
|
|
1519
|
+
gaps: pack.gaps,
|
|
1520
|
+
conflicts: pack.conflicts,
|
|
1521
|
+
fallback_required: pack.fallback_required,
|
|
1522
|
+
freshness: pack.freshness,
|
|
1523
|
+
context_quality: pack.context_quality,
|
|
1524
|
+
};
|
|
1525
|
+
packFreshness[workflow] = pack.freshness;
|
|
1526
|
+
contextQuality.byWorkflow[workflow] = pack.context_quality;
|
|
1527
|
+
if (contextMaterialized && pack.fallback_required) {
|
|
1528
|
+
contextWarn.push(`Context pack ${workflow} exige fallback explícito para leitura direta.`);
|
|
1529
|
+
}
|
|
1530
|
+
if (contextMaterialized && pack.freshness && pack.freshness.stale) {
|
|
1531
|
+
contextWarn.push(`Context pack ${workflow} stale (${pack.freshness.reason}).`);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
if (contextPacks.dashboard) {
|
|
1535
|
+
contextQuality.primaryWorkflow = 'dashboard';
|
|
1536
|
+
contextQuality.primaryScore = contextPacks.dashboard.context_quality.score;
|
|
1537
|
+
contextQuality.primaryStatus = contextPacks.dashboard.context_quality.status;
|
|
1538
|
+
} else {
|
|
1539
|
+
const firstWorkflow = Object.keys(contextPacks)[0] || null;
|
|
1540
|
+
if (firstWorkflow) {
|
|
1541
|
+
contextQuality.primaryWorkflow = firstWorkflow;
|
|
1542
|
+
contextQuality.primaryScore = contextPacks[firstWorkflow].context_quality.score;
|
|
1543
|
+
contextQuality.primaryStatus = contextPacks[firstWorkflow].context_quality.status;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
contextWarn.push(`Contexto — falha ao inspecionar context packs: ${err instanceof Error ? err.message : String(err)}`);
|
|
1548
|
+
}
|
|
1549
|
+
const semanticsManifest = readJsonFileSafe(base.runtimeSemanticsManifest);
|
|
1550
|
+
const semanticsAudit = runtimeSemantics.auditRuntimeTargets(target);
|
|
1551
|
+
/** @type {string[]} */
|
|
1552
|
+
const semanticsWarn = [];
|
|
1553
|
+
const semanticTargetsPresent = [
|
|
1554
|
+
path.join(target, '.github', 'prompts'),
|
|
1555
|
+
path.join(target, 'commands', 'oxe'),
|
|
1556
|
+
path.join(target, '.cursor', 'commands'),
|
|
1557
|
+
].some((dirPath) => fs.existsSync(dirPath));
|
|
1558
|
+
if (semanticTargetsPresent && !semanticsManifest.ok) {
|
|
1559
|
+
semanticsWarn.push('runtime-semantics.json ausente ou inválido — rode `npx oxe-cc update` para sincronizar o manifest semântico.');
|
|
1560
|
+
}
|
|
1561
|
+
if (semanticsManifest.error) {
|
|
1562
|
+
semanticsWarn.push(`runtime-semantics.json inválido: ${semanticsManifest.error}`);
|
|
1563
|
+
}
|
|
1564
|
+
if (semanticsManifest.data && semanticsManifest.data.contract_version && semanticsManifest.data.contract_version !== runtimeSemantics.CONTRACT_VERSION) {
|
|
1565
|
+
semanticsWarn.push(
|
|
1566
|
+
`Manifest semântico em versão ${semanticsManifest.data.contract_version}; esperado ${runtimeSemantics.CONTRACT_VERSION}.`
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
if (semanticsAudit.registryIssues.length) {
|
|
1570
|
+
semanticsWarn.push(...semanticsAudit.registryIssues);
|
|
1571
|
+
}
|
|
1572
|
+
if (semanticsAudit.mismatches.length) {
|
|
1573
|
+
semanticsWarn.push(`${semanticsAudit.mismatches.length} wrapper(s) com drift semântico detectado.`);
|
|
1574
|
+
}
|
|
1575
|
+
const semanticsDrift = {
|
|
1576
|
+
ok: semanticsWarn.length === 0 && semanticsAudit.ok,
|
|
1577
|
+
contractVersion: runtimeSemantics.CONTRACT_VERSION,
|
|
1578
|
+
manifestPath: base.runtimeSemanticsManifest,
|
|
1579
|
+
manifest: semanticsManifest.data,
|
|
1580
|
+
audit: {
|
|
1581
|
+
ok: semanticsAudit.ok,
|
|
1582
|
+
warnings: semanticsAudit.warnings,
|
|
1583
|
+
mismatchCount: semanticsAudit.mismatches.length,
|
|
1584
|
+
mismatches: semanticsAudit.mismatches,
|
|
1585
|
+
targets: Object.fromEntries(
|
|
1586
|
+
Object.entries(semanticsAudit.targets || {}).map(([name, value]) => [
|
|
1587
|
+
name,
|
|
1588
|
+
{
|
|
1589
|
+
path: value.path,
|
|
1590
|
+
checked: value.checked,
|
|
1591
|
+
missing: value.missing,
|
|
1592
|
+
},
|
|
1593
|
+
])
|
|
1594
|
+
),
|
|
1595
|
+
},
|
|
1596
|
+
};
|
|
1597
|
+
const hardFailure = Boolean(parseError) || sessionWarn.some((w) => /não existe|sem SESSION\.md/i.test(w));
|
|
1598
|
+
const warningCount =
|
|
1599
|
+
phaseWarn.length +
|
|
1600
|
+
runtimeWarn.length +
|
|
1601
|
+
reviewWarn.length +
|
|
1602
|
+
specWarn.length +
|
|
1603
|
+
planWarn.length +
|
|
1604
|
+
capabilityWarn.length +
|
|
1605
|
+
investigationWarn.length +
|
|
1606
|
+
sessionWarn.length +
|
|
1607
|
+
installWarn.length +
|
|
1608
|
+
copilotWarn.length +
|
|
1609
|
+
contextWarn.length +
|
|
1610
|
+
semanticsWarn.length +
|
|
1611
|
+
(azureReport ? azureReport.warnings.length : 0) +
|
|
1612
|
+
(sumWarn ? 1 : 0);
|
|
1613
|
+
const healthStatus = hardFailure ? 'broken' : warningCount > 0 ? 'warning' : 'healthy';
|
|
1614
|
+
|
|
1615
|
+
return {
|
|
1155
1616
|
configPath: cfgPath,
|
|
1156
1617
|
configParseError: parseError,
|
|
1157
1618
|
unknownConfigKeys: shape.unknownKeys,
|
|
1158
|
-
typeErrors: shape.typeErrors,
|
|
1159
|
-
phase,
|
|
1160
|
-
activeSession,
|
|
1161
|
-
scanDate,
|
|
1619
|
+
typeErrors: shape.typeErrors,
|
|
1620
|
+
phase,
|
|
1621
|
+
activeSession,
|
|
1622
|
+
scanDate,
|
|
1162
1623
|
stale,
|
|
1163
1624
|
compactDate,
|
|
1164
1625
|
staleCompact,
|
|
1165
1626
|
retroDate,
|
|
1166
|
-
staleLessons,
|
|
1167
|
-
phaseWarn,
|
|
1168
|
-
runtimeWarn,
|
|
1169
|
-
reviewWarn,
|
|
1170
|
-
capabilityWarn,
|
|
1171
|
-
investigationWarn,
|
|
1172
|
-
sessionWarn,
|
|
1173
|
-
installWarn,
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1627
|
+
staleLessons,
|
|
1628
|
+
phaseWarn,
|
|
1629
|
+
runtimeWarn,
|
|
1630
|
+
reviewWarn,
|
|
1631
|
+
capabilityWarn,
|
|
1632
|
+
investigationWarn,
|
|
1633
|
+
sessionWarn,
|
|
1634
|
+
installWarn,
|
|
1635
|
+
copilotWarn,
|
|
1636
|
+
contextWarn,
|
|
1637
|
+
semanticsWarn,
|
|
1638
|
+
copilot,
|
|
1639
|
+
summaryGapWarn: sumWarn,
|
|
1640
|
+
specWarn,
|
|
1641
|
+
planWarn,
|
|
1642
|
+
planSelfEvaluation,
|
|
1643
|
+
planReviewStatus: parsePlanReviewStatus(stateText),
|
|
1644
|
+
activeRun,
|
|
1645
|
+
eventsSummary,
|
|
1646
|
+
memoryLayers,
|
|
1647
|
+
azureActive,
|
|
1648
|
+
azure: azureReport
|
|
1649
|
+
? {
|
|
1650
|
+
profile: azureReport.profile,
|
|
1651
|
+
authStatus: azureReport.authStatus,
|
|
1652
|
+
inventorySummary: azureReport.inventorySummary,
|
|
1653
|
+
inventoryPath: azureReport.paths.inventory,
|
|
1654
|
+
operationsPath: azureReport.paths.operationsDir,
|
|
1655
|
+
inventorySyncedAt: azureInventorySyncedAt,
|
|
1656
|
+
inventoryStale: azureInventoryStale,
|
|
1657
|
+
pendingOperations: azure.listAzureOperations(target).filter((operation) => operation.phase === 'waiting_approval').length,
|
|
1658
|
+
lastOperation: azure.listAzureOperations(target)[0] || null,
|
|
1659
|
+
warnings: azureReport.warnings,
|
|
1660
|
+
}
|
|
1661
|
+
: null,
|
|
1662
|
+
contextPacks,
|
|
1663
|
+
contextQuality,
|
|
1664
|
+
semanticsDrift,
|
|
1665
|
+
packFreshness,
|
|
1666
|
+
activeSummaryRefs,
|
|
1667
|
+
healthStatus,
|
|
1668
|
+
next,
|
|
1669
|
+
scanFocusGlobs: config.scan_focus_globs,
|
|
1670
|
+
scanIgnoreGlobs: config.scan_ignore_globs,
|
|
1671
|
+
};
|
|
1202
1672
|
}
|
|
1203
1673
|
|
|
1204
1674
|
module.exports = {
|
|
@@ -1213,28 +1683,31 @@ module.exports = {
|
|
|
1213
1683
|
loadOxeConfigMerged,
|
|
1214
1684
|
validateConfigShape,
|
|
1215
1685
|
parseStatePhase,
|
|
1216
|
-
parseLastScanDate,
|
|
1217
|
-
parseLastCompactDate,
|
|
1218
|
-
parseLastRetroDate,
|
|
1219
|
-
parseActiveSession,
|
|
1220
|
-
parsePlanReviewStatus,
|
|
1221
|
-
isStaleScan,
|
|
1222
|
-
isStaleLessons,
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1686
|
+
parseLastScanDate,
|
|
1687
|
+
parseLastCompactDate,
|
|
1688
|
+
parseLastRetroDate,
|
|
1689
|
+
parseActiveSession,
|
|
1690
|
+
parsePlanReviewStatus,
|
|
1691
|
+
isStaleScan,
|
|
1692
|
+
isStaleLessons,
|
|
1693
|
+
copilotWorkspacePaths,
|
|
1694
|
+
copilotLegacyPaths,
|
|
1695
|
+
copilotIntegrationReport,
|
|
1696
|
+
planAgentsWarnings,
|
|
1697
|
+
installationCompletenessWarnings,
|
|
1698
|
+
parsePlanSelfEvaluation,
|
|
1699
|
+
planSelfEvaluationWarnings,
|
|
1700
|
+
runtimeWarnings,
|
|
1701
|
+
planReviewWarnings,
|
|
1702
|
+
capabilityWarnings,
|
|
1703
|
+
investigationWarnings,
|
|
1704
|
+
phaseCoherenceWarnings,
|
|
1232
1705
|
verifyGapsWithoutSummaryWarning,
|
|
1233
1706
|
specSectionWarnings,
|
|
1234
1707
|
planWaveWarningsFixed,
|
|
1235
1708
|
planTaskAceiteWarnings,
|
|
1236
|
-
suggestNextStep,
|
|
1237
|
-
buildHealthReport,
|
|
1238
|
-
oxePaths,
|
|
1239
|
-
scopedOxePaths,
|
|
1240
|
-
};
|
|
1709
|
+
suggestNextStep,
|
|
1710
|
+
buildHealthReport,
|
|
1711
|
+
oxePaths,
|
|
1712
|
+
scopedOxePaths,
|
|
1713
|
+
};
|