oxe-cc 0.6.5 → 0.7.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 +11 -0
- package/.cursor/commands/oxe-capabilities.md +11 -0
- package/.cursor/commands/oxe-dashboard.md +11 -0
- package/.github/prompts/oxe-ask.prompt.md +12 -0
- package/.github/prompts/oxe-capabilities.prompt.md +12 -0
- package/.github/prompts/oxe-dashboard.prompt.md +12 -0
- package/CHANGELOG.md +33 -0
- package/README.md +189 -34
- package/assets/oxe-framework-artifacts-paper.png +0 -0
- package/bin/banner.txt +1 -1
- package/bin/lib/oxe-azure.cjs +1445 -0
- package/bin/lib/oxe-dashboard.cjs +588 -0
- package/bin/lib/oxe-install-resolve.cjs +4 -1
- package/bin/lib/oxe-operational.cjs +670 -0
- package/bin/lib/oxe-project-health.cjs +655 -118
- package/bin/oxe-cc.js +1404 -17
- package/commands/oxe/ask.md +14 -0
- package/commands/oxe/capabilities.md +13 -0
- package/commands/oxe/dashboard.md +14 -0
- package/lib/sdk/README.md +9 -7
- package/lib/sdk/index.cjs +56 -0
- package/lib/sdk/index.d.ts +73 -0
- package/oxe/templates/ACTIVE-RUN.template.json +32 -0
- package/oxe/templates/CAPABILITIES.template.md +7 -0
- package/oxe/templates/CAPABILITY.template.md +45 -0
- package/oxe/templates/CHECKPOINTS.template.md +7 -0
- package/oxe/templates/CONFIG.md +3 -2
- package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -0
- package/oxe/templates/INVESTIGATION.template.md +38 -0
- package/oxe/templates/NOTES.template.md +16 -0
- package/oxe/templates/PLAN-REVIEW.template.md +31 -0
- package/oxe/templates/PLAN.template.md +22 -7
- package/oxe/templates/RESEARCH.template.md +11 -4
- package/oxe/templates/SPEC.template.md +6 -4
- package/oxe/templates/STATE.md +45 -7
- package/oxe/templates/config.template.json +14 -5
- package/oxe/workflows/ask.md +71 -0
- package/oxe/workflows/capabilities.md +23 -0
- package/oxe/workflows/dashboard.md +23 -0
- package/oxe/workflows/discuss.md +11 -9
- package/oxe/workflows/execute.md +46 -17
- package/oxe/workflows/help.md +273 -239
- package/oxe/workflows/next.md +10 -8
- package/oxe/workflows/obs.md +70 -20
- package/oxe/workflows/plan-agent.md +2 -1
- package/oxe/workflows/plan.md +70 -21
- package/oxe/workflows/quick.md +14 -6
- package/oxe/workflows/references/adaptive-discovery.md +27 -0
- package/oxe/workflows/references/flow-robustness-contract.md +80 -0
- package/oxe/workflows/research.md +12 -8
- package/oxe/workflows/retro.md +30 -5
- package/oxe/workflows/scan.md +1 -0
- package/oxe/workflows/spec.md +58 -33
- package/oxe/workflows/verify.md +40 -10
- package/package.json +2 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const operational = require('./oxe-operational.cjs');
|
|
6
|
+
const azure = require('./oxe-azure.cjs');
|
|
5
7
|
|
|
6
8
|
/** @type {string[]} */
|
|
7
9
|
const ALLOWED_CONFIG_KEYS = [
|
|
@@ -17,15 +19,17 @@ const ALLOWED_CONFIG_KEYS = [
|
|
|
17
19
|
'scan_ignore_globs',
|
|
18
20
|
'spec_required_sections',
|
|
19
21
|
'plan_max_tasks_per_wave',
|
|
20
|
-
'profile',
|
|
21
|
-
'verification_depth',
|
|
22
|
-
'
|
|
22
|
+
'profile',
|
|
23
|
+
'verification_depth',
|
|
24
|
+
'plan_confidence_threshold',
|
|
25
|
+
'security_in_verify',
|
|
23
26
|
'install',
|
|
24
27
|
'plugins',
|
|
25
|
-
'workstreams',
|
|
26
|
-
'milestones',
|
|
27
|
-
'scale_adaptive',
|
|
28
|
-
|
|
28
|
+
'workstreams',
|
|
29
|
+
'milestones',
|
|
30
|
+
'scale_adaptive',
|
|
31
|
+
'azure',
|
|
32
|
+
];
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
35
|
* Profiles de execução OXE que controlam rigor do workflow.
|
|
@@ -116,16 +120,25 @@ function loadOxeConfigMerged(targetProject) {
|
|
|
116
120
|
discuss_before_plan: false,
|
|
117
121
|
after_verify_suggest_pr: true,
|
|
118
122
|
after_verify_draft_commit: true,
|
|
119
|
-
after_verify_suggest_uat: false,
|
|
120
|
-
verification_depth: 'standard',
|
|
121
|
-
|
|
123
|
+
after_verify_suggest_uat: false,
|
|
124
|
+
verification_depth: 'standard',
|
|
125
|
+
plan_confidence_threshold: 70,
|
|
126
|
+
default_verify_command: '',
|
|
122
127
|
scan_max_age_days: 0,
|
|
123
128
|
compact_max_age_days: 0,
|
|
124
129
|
scan_focus_globs: [],
|
|
125
130
|
scan_ignore_globs: [],
|
|
126
|
-
spec_required_sections: [],
|
|
127
|
-
plan_max_tasks_per_wave: 0,
|
|
128
|
-
|
|
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
|
+
};
|
|
129
142
|
const p = path.join(targetProject, '.oxe', 'config.json');
|
|
130
143
|
if (!fs.existsSync(p)) return { config: defaults, path: null, parseError: null };
|
|
131
144
|
try {
|
|
@@ -211,21 +224,62 @@ function validateConfigShape(cfg) {
|
|
|
211
224
|
typeErrors.push(`profile deve ser um de: ${EXECUTION_PROFILES.join(', ')}`);
|
|
212
225
|
}
|
|
213
226
|
}
|
|
214
|
-
if (cfg.verification_depth != null) {
|
|
215
|
-
if (typeof cfg.verification_depth !== 'string') {
|
|
216
|
-
typeErrors.push('verification_depth deve ser string');
|
|
217
|
-
} else if (!VERIFICATION_DEPTHS.includes(cfg.verification_depth)) {
|
|
218
|
-
typeErrors.push(`verification_depth deve ser um de: ${VERIFICATION_DEPTHS.join(', ')}`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
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
|
+
}
|
|
221
237
|
if (cfg.after_verify_suggest_uat != null && typeof cfg.after_verify_suggest_uat !== 'boolean') {
|
|
222
238
|
typeErrors.push('after_verify_suggest_uat deve ser boolean');
|
|
223
239
|
}
|
|
224
|
-
if (cfg.scale_adaptive != null && typeof cfg.scale_adaptive !== 'boolean') {
|
|
225
|
-
typeErrors.push('scale_adaptive deve ser boolean');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
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
|
+
return { unknownKeys, typeErrors };
|
|
282
|
+
}
|
|
229
283
|
|
|
230
284
|
/**
|
|
231
285
|
* @param {string} stateText
|
|
@@ -286,12 +340,38 @@ function parseLastCompactDate(stateText) {
|
|
|
286
340
|
* @param {string} stateText
|
|
287
341
|
* @returns {Date | null}
|
|
288
342
|
*/
|
|
289
|
-
function parseLastRetroDate(stateText) {
|
|
290
|
-
const m = stateText.match(/\blast_retro\s*:\s*(\d{4}-\d{2}-\d{2})/i);
|
|
291
|
-
if (!m) return null;
|
|
292
|
-
const iso = Date.parse(m[1]);
|
|
293
|
-
return Number.isNaN(iso) ? null : new Date(iso);
|
|
294
|
-
}
|
|
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
|
+
}
|
|
295
375
|
|
|
296
376
|
/**
|
|
297
377
|
* @param {Date | null} scanDate
|
|
@@ -316,13 +396,31 @@ function isStaleLessons(retroDate, maxAgeDays) {
|
|
|
316
396
|
/**
|
|
317
397
|
* @param {string} target
|
|
318
398
|
*/
|
|
319
|
-
function oxePaths(target) {
|
|
320
|
-
const oxe = path.join(target, '.oxe');
|
|
321
|
-
return {
|
|
322
|
-
oxe,
|
|
323
|
-
state: path.join(oxe, 'STATE.md'),
|
|
324
|
-
|
|
325
|
-
|
|
399
|
+
function oxePaths(target) {
|
|
400
|
+
const oxe = path.join(target, '.oxe');
|
|
401
|
+
return {
|
|
402
|
+
oxe,
|
|
403
|
+
state: path.join(oxe, 'STATE.md'),
|
|
404
|
+
runtime: path.join(oxe, 'EXECUTION-RUNTIME.md'),
|
|
405
|
+
checkpoints: path.join(oxe, 'CHECKPOINTS.md'),
|
|
406
|
+
capabilitiesIndex: path.join(oxe, 'CAPABILITIES.md'),
|
|
407
|
+
capabilitiesDir: path.join(oxe, 'capabilities'),
|
|
408
|
+
investigationsIndex: path.join(oxe, 'INVESTIGATIONS.md'),
|
|
409
|
+
investigationsDir: path.join(oxe, 'investigations'),
|
|
410
|
+
dashboardDir: path.join(oxe, 'dashboard'),
|
|
411
|
+
sessionsIndex: path.join(oxe, 'SESSIONS.md'),
|
|
412
|
+
globalDir: path.join(oxe, 'global'),
|
|
413
|
+
globalLessons: path.join(oxe, 'global', 'LESSONS.md'),
|
|
414
|
+
globalMilestones: path.join(oxe, 'global', 'MILESTONES.md'),
|
|
415
|
+
globalMilestonesDir: path.join(oxe, 'global', 'milestones'),
|
|
416
|
+
sessionsDir: path.join(oxe, 'sessions'),
|
|
417
|
+
planReview: path.join(oxe, 'PLAN-REVIEW.md'),
|
|
418
|
+
planReviewComments: path.join(oxe, 'plan-review-comments.json'),
|
|
419
|
+
activeRun: path.join(oxe, 'ACTIVE-RUN.json'),
|
|
420
|
+
runsDir: path.join(oxe, 'runs'),
|
|
421
|
+
events: path.join(oxe, 'OXE-EVENTS.ndjson'),
|
|
422
|
+
spec: path.join(oxe, 'SPEC.md'),
|
|
423
|
+
plan: path.join(oxe, 'PLAN.md'),
|
|
326
424
|
quick: path.join(oxe, 'QUICK.md'),
|
|
327
425
|
verify: path.join(oxe, 'VERIFY.md'),
|
|
328
426
|
discuss: path.join(oxe, 'DISCUSS.md'),
|
|
@@ -330,8 +428,41 @@ function oxePaths(target) {
|
|
|
330
428
|
codebase: path.join(oxe, 'codebase'),
|
|
331
429
|
lessons: path.join(oxe, 'LESSONS.md'),
|
|
332
430
|
planAgents: path.join(oxe, 'plan-agents.json'),
|
|
333
|
-
};
|
|
334
|
-
}
|
|
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
|
+
}
|
|
335
466
|
|
|
336
467
|
/**
|
|
337
468
|
* Valida o arquivo plan-agents.json (se existir) e retorna avisos.
|
|
@@ -459,7 +590,7 @@ function planTaskAceiteWarnings(planPath) {
|
|
|
459
590
|
return out;
|
|
460
591
|
}
|
|
461
592
|
|
|
462
|
-
function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
593
|
+
function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
463
594
|
if (!maxPerWave || maxPerWave <= 0 || !fs.existsSync(planPath)) return [];
|
|
464
595
|
const raw = fs.readFileSync(planPath, 'utf8');
|
|
465
596
|
const lines = raw.split('\n');
|
|
@@ -480,21 +611,241 @@ function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
|
480
611
|
w.push(`PLAN.md: onda ${wN} tem ${count} tarefas (máximo configurado: ${maxPerWave} — considere dividir ondas)`);
|
|
481
612
|
}
|
|
482
613
|
}
|
|
483
|
-
return w;
|
|
484
|
-
}
|
|
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
|
+
}
|
|
485
832
|
|
|
486
833
|
/**
|
|
487
834
|
* Próximo passo único (espelha o workflow next.md).
|
|
488
835
|
* @param {string} target
|
|
489
836
|
* @param {{ discuss_before_plan?: boolean }} cfg
|
|
490
837
|
*/
|
|
491
|
-
function suggestNextStep(target, cfg = {}) {
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
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)) {
|
|
498
849
|
return {
|
|
499
850
|
step: 'scan',
|
|
500
851
|
cursorCmd: '/oxe-scan',
|
|
@@ -503,19 +854,56 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
503
854
|
};
|
|
504
855
|
}
|
|
505
856
|
|
|
506
|
-
const
|
|
507
|
-
const phase = parseStatePhase(stateText);
|
|
857
|
+
const phase = parseStatePhase(stateText);
|
|
508
858
|
|
|
509
|
-
if (!mapsComplete && !has(p.quick)) {
|
|
859
|
+
if (!mapsComplete && !has(p.quick)) {
|
|
510
860
|
return {
|
|
511
861
|
step: 'scan',
|
|
512
862
|
cursorCmd: '/oxe-scan',
|
|
513
863
|
reason: 'Mapas do codebase incompletos e sem QUICK.md — atualize o contexto com scan',
|
|
514
864
|
artifacts: ['.oxe/codebase/'],
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (
|
|
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))) {
|
|
519
907
|
return {
|
|
520
908
|
step: 'execute',
|
|
521
909
|
cursorCmd: '/oxe-execute',
|
|
@@ -542,16 +930,71 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
542
930
|
};
|
|
543
931
|
}
|
|
544
932
|
|
|
545
|
-
if (!has(p.plan)) {
|
|
546
|
-
return {
|
|
547
|
-
step: 'plan',
|
|
548
|
-
cursorCmd: '/oxe-plan',
|
|
549
|
-
reason: 'SPEC existe mas PLAN.md não — gere o plano com verificação por tarefa',
|
|
550
|
-
artifacts: ['.oxe/PLAN.md'],
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
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)) {
|
|
555
998
|
return {
|
|
556
999
|
step: 'execute',
|
|
557
1000
|
cursorCmd: '/oxe-execute',
|
|
@@ -622,56 +1065,140 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
622
1065
|
/**
|
|
623
1066
|
* @param {string} target
|
|
624
1067
|
*/
|
|
625
|
-
function buildHealthReport(target) {
|
|
626
|
-
const { config, path: cfgPath, parseError } = loadOxeConfigMerged(target);
|
|
627
|
-
const shape = validateConfigShape(config);
|
|
628
|
-
const
|
|
629
|
-
let stateText = '';
|
|
630
|
-
if (fs.existsSync(
|
|
631
|
-
try {
|
|
632
|
-
stateText = fs.readFileSync(
|
|
633
|
-
} catch {
|
|
634
|
-
stateText = '';
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
const
|
|
1068
|
+
function buildHealthReport(target) {
|
|
1069
|
+
const { config, path: cfgPath, parseError } = loadOxeConfigMerged(target);
|
|
1070
|
+
const shape = validateConfigShape(config);
|
|
1071
|
+
const base = oxePaths(target);
|
|
1072
|
+
let stateText = '';
|
|
1073
|
+
if (fs.existsSync(base.state)) {
|
|
1074
|
+
try {
|
|
1075
|
+
stateText = fs.readFileSync(base.state, 'utf8');
|
|
1076
|
+
} catch {
|
|
1077
|
+
stateText = '';
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const activeSession = parseActiveSession(stateText);
|
|
1081
|
+
const p = scopedOxePaths(target, activeSession);
|
|
1082
|
+
const phase = parseStatePhase(stateText);
|
|
638
1083
|
const scanDate = parseLastScanDate(stateText);
|
|
639
1084
|
const stale = isStaleScan(scanDate, Number(config.scan_max_age_days) || 0);
|
|
640
1085
|
const compactDate = parseLastCompactDate(stateText);
|
|
641
1086
|
const staleCompact = isStaleScan(compactDate, Number(config.compact_max_age_days) || 0);
|
|
642
1087
|
const retroDate = parseLastRetroDate(stateText);
|
|
643
|
-
const staleLessons = isStaleLessons(retroDate, Number(config.lessons_max_age_days) || 0);
|
|
644
|
-
const phaseWarn = phase ? phaseCoherenceWarnings(phase, p) : [];
|
|
645
|
-
const
|
|
646
|
-
const
|
|
647
|
-
const
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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 reviewWarn = planReviewWarnings(stateText, p);
|
|
1106
|
+
const planSelfEvaluation = parsePlanSelfEvaluation(p.plan);
|
|
1107
|
+
const activeRun = operational.readRunState(target, activeSession);
|
|
1108
|
+
const eventsSummary = operational.summarizeEvents(operational.readEvents(target, activeSession));
|
|
1109
|
+
const memoryLayers = operational.buildMemoryLayers(target, activeSession);
|
|
1110
|
+
const azureActive = azure.isAzureContextEnabled(target, config);
|
|
1111
|
+
const azureReport = azureActive
|
|
1112
|
+
? azure.azureDoctor(target, config, {
|
|
1113
|
+
autoInstall: false,
|
|
1114
|
+
write: false,
|
|
1115
|
+
})
|
|
1116
|
+
: null;
|
|
1117
|
+
const azureInventorySyncedAt = azureReport && azureReport.inventory ? azureReport.inventory.synced_at || null : null;
|
|
1118
|
+
const azureInventoryMaxAgeHours = config.azure && config.azure.inventory_max_age_hours != null
|
|
1119
|
+
? Number(config.azure.inventory_max_age_hours)
|
|
1120
|
+
: 24;
|
|
1121
|
+
let azureInventoryStale = { stale: false, hours: null };
|
|
1122
|
+
if (azureInventorySyncedAt) {
|
|
1123
|
+
const syncedAt = Date.parse(String(azureInventorySyncedAt));
|
|
1124
|
+
if (!Number.isNaN(syncedAt)) {
|
|
1125
|
+
const ageHours = Math.floor((Date.now() - syncedAt) / (1000 * 60 * 60));
|
|
1126
|
+
azureInventoryStale = {
|
|
1127
|
+
stale: azureInventoryMaxAgeHours > 0 ? ageHours > azureInventoryMaxAgeHours : false,
|
|
1128
|
+
hours: ageHours,
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
} else if (azureActive) {
|
|
1132
|
+
azureInventoryStale = { stale: true, hours: null };
|
|
1133
|
+
}
|
|
1134
|
+
const next = suggestNextStep(target, {
|
|
1135
|
+
discuss_before_plan: config.discuss_before_plan,
|
|
1136
|
+
plan_confidence_threshold: threshold,
|
|
1137
|
+
azure: config.azure,
|
|
1138
|
+
});
|
|
1139
|
+
const hardFailure = Boolean(parseError) || sessionWarn.some((w) => /não existe|sem SESSION\.md/i.test(w));
|
|
1140
|
+
const warningCount =
|
|
1141
|
+
phaseWarn.length +
|
|
1142
|
+
runtimeWarn.length +
|
|
1143
|
+
reviewWarn.length +
|
|
1144
|
+
specWarn.length +
|
|
1145
|
+
planWarn.length +
|
|
1146
|
+
capabilityWarn.length +
|
|
1147
|
+
investigationWarn.length +
|
|
1148
|
+
sessionWarn.length +
|
|
1149
|
+
installWarn.length +
|
|
1150
|
+
(azureReport ? azureReport.warnings.length : 0) +
|
|
1151
|
+
(sumWarn ? 1 : 0);
|
|
1152
|
+
const healthStatus = hardFailure ? 'broken' : warningCount > 0 ? 'warning' : 'healthy';
|
|
1153
|
+
|
|
1154
|
+
return {
|
|
656
1155
|
configPath: cfgPath,
|
|
657
1156
|
configParseError: parseError,
|
|
658
1157
|
unknownConfigKeys: shape.unknownKeys,
|
|
659
|
-
typeErrors: shape.typeErrors,
|
|
660
|
-
phase,
|
|
661
|
-
|
|
1158
|
+
typeErrors: shape.typeErrors,
|
|
1159
|
+
phase,
|
|
1160
|
+
activeSession,
|
|
1161
|
+
scanDate,
|
|
662
1162
|
stale,
|
|
663
1163
|
compactDate,
|
|
664
1164
|
staleCompact,
|
|
665
1165
|
retroDate,
|
|
666
|
-
staleLessons,
|
|
667
|
-
phaseWarn,
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1166
|
+
staleLessons,
|
|
1167
|
+
phaseWarn,
|
|
1168
|
+
runtimeWarn,
|
|
1169
|
+
reviewWarn,
|
|
1170
|
+
capabilityWarn,
|
|
1171
|
+
investigationWarn,
|
|
1172
|
+
sessionWarn,
|
|
1173
|
+
installWarn,
|
|
1174
|
+
summaryGapWarn: sumWarn,
|
|
1175
|
+
specWarn,
|
|
1176
|
+
planWarn,
|
|
1177
|
+
planSelfEvaluation,
|
|
1178
|
+
planReviewStatus: parsePlanReviewStatus(stateText),
|
|
1179
|
+
activeRun,
|
|
1180
|
+
eventsSummary,
|
|
1181
|
+
memoryLayers,
|
|
1182
|
+
azureActive,
|
|
1183
|
+
azure: azureReport
|
|
1184
|
+
? {
|
|
1185
|
+
profile: azureReport.profile,
|
|
1186
|
+
authStatus: azureReport.authStatus,
|
|
1187
|
+
inventorySummary: azureReport.inventorySummary,
|
|
1188
|
+
inventoryPath: azureReport.paths.inventory,
|
|
1189
|
+
operationsPath: azureReport.paths.operationsDir,
|
|
1190
|
+
inventorySyncedAt: azureInventorySyncedAt,
|
|
1191
|
+
inventoryStale: azureInventoryStale,
|
|
1192
|
+
pendingOperations: azure.listAzureOperations(target).filter((operation) => operation.phase === 'waiting_approval').length,
|
|
1193
|
+
lastOperation: azure.listAzureOperations(target)[0] || null,
|
|
1194
|
+
warnings: azureReport.warnings,
|
|
1195
|
+
}
|
|
1196
|
+
: null,
|
|
1197
|
+
healthStatus,
|
|
1198
|
+
next,
|
|
1199
|
+
scanFocusGlobs: config.scan_focus_globs,
|
|
1200
|
+
scanIgnoreGlobs: config.scan_ignore_globs,
|
|
1201
|
+
};
|
|
675
1202
|
}
|
|
676
1203
|
|
|
677
1204
|
module.exports = {
|
|
@@ -686,18 +1213,28 @@ module.exports = {
|
|
|
686
1213
|
loadOxeConfigMerged,
|
|
687
1214
|
validateConfigShape,
|
|
688
1215
|
parseStatePhase,
|
|
689
|
-
parseLastScanDate,
|
|
690
|
-
parseLastCompactDate,
|
|
691
|
-
parseLastRetroDate,
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1216
|
+
parseLastScanDate,
|
|
1217
|
+
parseLastCompactDate,
|
|
1218
|
+
parseLastRetroDate,
|
|
1219
|
+
parseActiveSession,
|
|
1220
|
+
parsePlanReviewStatus,
|
|
1221
|
+
isStaleScan,
|
|
1222
|
+
isStaleLessons,
|
|
1223
|
+
planAgentsWarnings,
|
|
1224
|
+
installationCompletenessWarnings,
|
|
1225
|
+
parsePlanSelfEvaluation,
|
|
1226
|
+
planSelfEvaluationWarnings,
|
|
1227
|
+
runtimeWarnings,
|
|
1228
|
+
planReviewWarnings,
|
|
1229
|
+
capabilityWarnings,
|
|
1230
|
+
investigationWarnings,
|
|
1231
|
+
phaseCoherenceWarnings,
|
|
696
1232
|
verifyGapsWithoutSummaryWarning,
|
|
697
1233
|
specSectionWarnings,
|
|
698
1234
|
planWaveWarningsFixed,
|
|
699
1235
|
planTaskAceiteWarnings,
|
|
700
|
-
suggestNextStep,
|
|
701
|
-
buildHealthReport,
|
|
702
|
-
oxePaths,
|
|
703
|
-
|
|
1236
|
+
suggestNextStep,
|
|
1237
|
+
buildHealthReport,
|
|
1238
|
+
oxePaths,
|
|
1239
|
+
scopedOxePaths,
|
|
1240
|
+
};
|