oxe-cc 0.6.4 → 0.6.6
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-execute.md +1 -1
- package/.cursor/commands/oxe-session.md +11 -0
- package/.github/prompts/oxe-ask.prompt.md +12 -0
- package/.github/prompts/oxe-execute.prompt.md +1 -1
- package/.github/prompts/oxe-session.prompt.md +12 -0
- package/README.md +363 -323
- package/bin/banner.txt +1 -1
- package/bin/lib/oxe-project-health.cjs +284 -91
- package/bin/oxe-cc.js +305 -123
- package/commands/oxe/ask.md +14 -0
- package/commands/oxe/session.md +16 -0
- package/oxe/templates/CONFIG.md +3 -2
- package/oxe/templates/PLAN.template.md +22 -7
- package/oxe/templates/SESSION.template.md +32 -0
- package/oxe/templates/STATE.md +10 -5
- package/oxe/templates/config.template.json +3 -2
- package/oxe/workflows/ask.md +62 -0
- package/oxe/workflows/checkpoint.md +10 -9
- package/oxe/workflows/debug.md +6 -5
- package/oxe/workflows/discuss.md +8 -7
- package/oxe/workflows/execute.md +37 -28
- package/oxe/workflows/forensics.md +6 -6
- package/oxe/workflows/help.md +39 -19
- package/oxe/workflows/milestone.md +12 -13
- package/oxe/workflows/next.md +16 -13
- package/oxe/workflows/obs.md +9 -8
- package/oxe/workflows/plan-agent.md +6 -4
- package/oxe/workflows/plan.md +73 -32
- package/oxe/workflows/project.md +1 -1
- package/oxe/workflows/quick.md +11 -7
- package/oxe/workflows/references/flow-robustness-contract.md +80 -0
- package/oxe/workflows/references/session-path-resolution.md +71 -0
- package/oxe/workflows/research.md +9 -8
- package/oxe/workflows/security.md +7 -6
- package/oxe/workflows/session.md +153 -0
- package/oxe/workflows/spec.md +41 -27
- package/oxe/workflows/ui-review.md +3 -3
- package/oxe/workflows/ui-spec.md +3 -3
- package/oxe/workflows/validate-gaps.md +5 -4
- package/oxe/workflows/verify.md +37 -21
- package/oxe/workflows/workstream.md +16 -15
- package/package.json +1 -1
package/bin/banner.txt
CHANGED
|
@@ -17,9 +17,10 @@ const ALLOWED_CONFIG_KEYS = [
|
|
|
17
17
|
'scan_ignore_globs',
|
|
18
18
|
'spec_required_sections',
|
|
19
19
|
'plan_max_tasks_per_wave',
|
|
20
|
-
'profile',
|
|
21
|
-
'verification_depth',
|
|
22
|
-
'
|
|
20
|
+
'profile',
|
|
21
|
+
'verification_depth',
|
|
22
|
+
'plan_confidence_threshold',
|
|
23
|
+
'security_in_verify',
|
|
23
24
|
'install',
|
|
24
25
|
'plugins',
|
|
25
26
|
'workstreams',
|
|
@@ -116,9 +117,10 @@ function loadOxeConfigMerged(targetProject) {
|
|
|
116
117
|
discuss_before_plan: false,
|
|
117
118
|
after_verify_suggest_pr: true,
|
|
118
119
|
after_verify_draft_commit: true,
|
|
119
|
-
after_verify_suggest_uat: false,
|
|
120
|
-
verification_depth: 'standard',
|
|
121
|
-
|
|
120
|
+
after_verify_suggest_uat: false,
|
|
121
|
+
verification_depth: 'standard',
|
|
122
|
+
plan_confidence_threshold: 70,
|
|
123
|
+
default_verify_command: '',
|
|
122
124
|
scan_max_age_days: 0,
|
|
123
125
|
compact_max_age_days: 0,
|
|
124
126
|
scan_focus_globs: [],
|
|
@@ -211,13 +213,16 @@ function validateConfigShape(cfg) {
|
|
|
211
213
|
typeErrors.push(`profile deve ser um de: ${EXECUTION_PROFILES.join(', ')}`);
|
|
212
214
|
}
|
|
213
215
|
}
|
|
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
|
-
}
|
|
216
|
+
if (cfg.verification_depth != null) {
|
|
217
|
+
if (typeof cfg.verification_depth !== 'string') {
|
|
218
|
+
typeErrors.push('verification_depth deve ser string');
|
|
219
|
+
} else if (!VERIFICATION_DEPTHS.includes(cfg.verification_depth)) {
|
|
220
|
+
typeErrors.push(`verification_depth deve ser um de: ${VERIFICATION_DEPTHS.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (cfg.plan_confidence_threshold != null && typeof cfg.plan_confidence_threshold !== 'number') {
|
|
224
|
+
typeErrors.push('plan_confidence_threshold deve ser número (percentual de 0 a 100)');
|
|
225
|
+
}
|
|
221
226
|
if (cfg.after_verify_suggest_uat != null && typeof cfg.after_verify_suggest_uat !== 'boolean') {
|
|
222
227
|
typeErrors.push('after_verify_suggest_uat deve ser boolean');
|
|
223
228
|
}
|
|
@@ -286,12 +291,25 @@ function parseLastCompactDate(stateText) {
|
|
|
286
291
|
* @param {string} stateText
|
|
287
292
|
* @returns {Date | null}
|
|
288
293
|
*/
|
|
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
|
-
}
|
|
294
|
+
function parseLastRetroDate(stateText) {
|
|
295
|
+
const m = stateText.match(/\blast_retro\s*:\s*(\d{4}-\d{2}-\d{2})/i);
|
|
296
|
+
if (!m) return null;
|
|
297
|
+
const iso = Date.parse(m[1]);
|
|
298
|
+
return Number.isNaN(iso) ? null : new Date(iso);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @param {string} stateText
|
|
303
|
+
* @returns {string | null}
|
|
304
|
+
*/
|
|
305
|
+
function parseActiveSession(stateText) {
|
|
306
|
+
if (!stateText) return null;
|
|
307
|
+
const m = stateText.match(/\*\*active_session:\*\*\s*`?([^\n`]+?)`?\s*(?:\n|$)/i);
|
|
308
|
+
if (!m) return null;
|
|
309
|
+
const raw = m[1].trim();
|
|
310
|
+
if (!raw || raw === '—' || /^none$/i.test(raw)) return null;
|
|
311
|
+
return raw.replace(/\\/g, '/');
|
|
312
|
+
}
|
|
295
313
|
|
|
296
314
|
/**
|
|
297
315
|
* @param {Date | null} scanDate
|
|
@@ -316,12 +334,18 @@ function isStaleLessons(retroDate, maxAgeDays) {
|
|
|
316
334
|
/**
|
|
317
335
|
* @param {string} target
|
|
318
336
|
*/
|
|
319
|
-
function oxePaths(target) {
|
|
320
|
-
const oxe = path.join(target, '.oxe');
|
|
321
|
-
return {
|
|
322
|
-
oxe,
|
|
323
|
-
state: path.join(oxe, 'STATE.md'),
|
|
324
|
-
|
|
337
|
+
function oxePaths(target) {
|
|
338
|
+
const oxe = path.join(target, '.oxe');
|
|
339
|
+
return {
|
|
340
|
+
oxe,
|
|
341
|
+
state: path.join(oxe, 'STATE.md'),
|
|
342
|
+
sessionsIndex: path.join(oxe, 'SESSIONS.md'),
|
|
343
|
+
globalDir: path.join(oxe, 'global'),
|
|
344
|
+
globalLessons: path.join(oxe, 'global', 'LESSONS.md'),
|
|
345
|
+
globalMilestones: path.join(oxe, 'global', 'MILESTONES.md'),
|
|
346
|
+
globalMilestonesDir: path.join(oxe, 'global', 'milestones'),
|
|
347
|
+
sessionsDir: path.join(oxe, 'sessions'),
|
|
348
|
+
spec: path.join(oxe, 'SPEC.md'),
|
|
325
349
|
plan: path.join(oxe, 'PLAN.md'),
|
|
326
350
|
quick: path.join(oxe, 'QUICK.md'),
|
|
327
351
|
verify: path.join(oxe, 'VERIFY.md'),
|
|
@@ -330,8 +354,32 @@ function oxePaths(target) {
|
|
|
330
354
|
codebase: path.join(oxe, 'codebase'),
|
|
331
355
|
lessons: path.join(oxe, 'LESSONS.md'),
|
|
332
356
|
planAgents: path.join(oxe, 'plan-agents.json'),
|
|
333
|
-
};
|
|
334
|
-
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* @param {string} target
|
|
362
|
+
* @param {string | null} activeSession
|
|
363
|
+
*/
|
|
364
|
+
function scopedOxePaths(target, activeSession) {
|
|
365
|
+
const base = oxePaths(target);
|
|
366
|
+
if (!activeSession) return { ...base, activeSession: null, scopedRoot: base.oxe };
|
|
367
|
+
const sessionRoot = path.join(base.oxe, ...activeSession.split('/'));
|
|
368
|
+
return {
|
|
369
|
+
...base,
|
|
370
|
+
activeSession,
|
|
371
|
+
scopedRoot: sessionRoot,
|
|
372
|
+
sessionRoot,
|
|
373
|
+
sessionManifest: path.join(sessionRoot, 'SESSION.md'),
|
|
374
|
+
spec: path.join(sessionRoot, 'spec', 'SPEC.md'),
|
|
375
|
+
discuss: path.join(sessionRoot, 'spec', 'DISCUSS.md'),
|
|
376
|
+
plan: path.join(sessionRoot, 'plan', 'PLAN.md'),
|
|
377
|
+
quick: path.join(sessionRoot, 'plan', 'QUICK.md'),
|
|
378
|
+
verify: path.join(sessionRoot, 'verification', 'VERIFY.md'),
|
|
379
|
+
summary: path.join(sessionRoot, 'verification', 'SUMMARY.md'),
|
|
380
|
+
executionState: path.join(sessionRoot, 'execution', 'STATE.md'),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
335
383
|
|
|
336
384
|
/**
|
|
337
385
|
* Valida o arquivo plan-agents.json (se existir) e retorna avisos.
|
|
@@ -459,7 +507,7 @@ function planTaskAceiteWarnings(planPath) {
|
|
|
459
507
|
return out;
|
|
460
508
|
}
|
|
461
509
|
|
|
462
|
-
function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
510
|
+
function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
463
511
|
if (!maxPerWave || maxPerWave <= 0 || !fs.existsSync(planPath)) return [];
|
|
464
512
|
const raw = fs.readFileSync(planPath, 'utf8');
|
|
465
513
|
const lines = raw.split('\n');
|
|
@@ -480,19 +528,131 @@ function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
|
480
528
|
w.push(`PLAN.md: onda ${wN} tem ${count} tarefas (máximo configurado: ${maxPerWave} — considere dividir ondas)`);
|
|
481
529
|
}
|
|
482
530
|
}
|
|
483
|
-
return w;
|
|
484
|
-
}
|
|
531
|
+
return w;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* @param {string} planPath
|
|
536
|
+
* @returns {{
|
|
537
|
+
* hasSection: boolean,
|
|
538
|
+
* bestPlan: string | null,
|
|
539
|
+
* confidence: number | null,
|
|
540
|
+
* warnings: string[],
|
|
541
|
+
* }}
|
|
542
|
+
*/
|
|
543
|
+
function parsePlanSelfEvaluation(planPath) {
|
|
544
|
+
const empty = { hasSection: false, bestPlan: null, confidence: null, warnings: [] };
|
|
545
|
+
if (!fs.existsSync(planPath)) return empty;
|
|
546
|
+
const raw = fs.readFileSync(planPath, 'utf8');
|
|
547
|
+
const m = raw.match(/##\s*Autoavaliação do Plano\s*([\s\S]*?)(?=\n## |\n#[^\#]|$)/i);
|
|
548
|
+
if (!m) {
|
|
549
|
+
return {
|
|
550
|
+
...empty,
|
|
551
|
+
warnings: ['PLAN.md sem a seção obrigatória "## Autoavaliação do Plano"'],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const body = m[1];
|
|
555
|
+
const best = body.match(/\*\*Melhor plano atual:\*\*\s*(sim|não|nao)/i);
|
|
556
|
+
const confidence = body.match(/\*\*Confiança:\*\*\s*(\d{1,3})\s*%/i);
|
|
557
|
+
/** @type {string[]} */
|
|
558
|
+
const warnings = [];
|
|
559
|
+
const rubricLabels = [
|
|
560
|
+
'Completude dos requisitos',
|
|
561
|
+
'Dependências conhecidas',
|
|
562
|
+
'Risco técnico',
|
|
563
|
+
'Impacto no código existente',
|
|
564
|
+
'Clareza da validação / testes',
|
|
565
|
+
'Lacunas externas / decisões pendentes',
|
|
566
|
+
];
|
|
567
|
+
if (!best) warnings.push('PLAN.md: autoavaliação sem "Melhor plano atual: sim|não"');
|
|
568
|
+
if (!confidence) warnings.push('PLAN.md: autoavaliação sem "Confiança: NN%"');
|
|
569
|
+
if (!/\*\*Principais incertezas:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Principais incertezas"');
|
|
570
|
+
if (!/\*\*Alternativas descartadas:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Alternativas descartadas"');
|
|
571
|
+
if (!/\*\*Condição para replanejar:\*\*/i.test(body)) warnings.push('PLAN.md: autoavaliação sem "Condição para replanejar"');
|
|
572
|
+
for (const label of rubricLabels) {
|
|
573
|
+
if (!body.includes(label)) warnings.push(`PLAN.md: rubrica sem "${label}"`);
|
|
574
|
+
}
|
|
575
|
+
const parsedConfidence = confidence ? Number(confidence[1]) : null;
|
|
576
|
+
if (parsedConfidence != null && (parsedConfidence < 0 || parsedConfidence > 100)) {
|
|
577
|
+
warnings.push('PLAN.md: confiança fora do intervalo 0–100%');
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
hasSection: true,
|
|
581
|
+
bestPlan: best ? best[1].toLowerCase().replace('nao', 'não') : null,
|
|
582
|
+
confidence: parsedConfidence,
|
|
583
|
+
warnings,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* @param {string} planPath
|
|
589
|
+
* @param {number} threshold
|
|
590
|
+
* @returns {string[]}
|
|
591
|
+
*/
|
|
592
|
+
function planSelfEvaluationWarnings(planPath, threshold) {
|
|
593
|
+
const info = parsePlanSelfEvaluation(planPath);
|
|
594
|
+
const warns = [...info.warnings];
|
|
595
|
+
if (!fs.existsSync(planPath)) return warns;
|
|
596
|
+
if (info.bestPlan === 'não') warns.push('PLAN.md: autoavaliação declara que este não é o melhor plano atual');
|
|
597
|
+
if (info.confidence != null && info.confidence < threshold) {
|
|
598
|
+
warns.push(`PLAN.md: confiança ${info.confidence}% abaixo do limiar executável (${threshold}%)`);
|
|
599
|
+
}
|
|
600
|
+
return warns;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* @param {string} target
|
|
605
|
+
* @param {string | null} activeSession
|
|
606
|
+
* @returns {string[]}
|
|
607
|
+
*/
|
|
608
|
+
function sessionWarnings(target, activeSession) {
|
|
609
|
+
if (!activeSession) return [];
|
|
610
|
+
const base = oxePaths(target);
|
|
611
|
+
const scoped = scopedOxePaths(target, activeSession);
|
|
612
|
+
/** @type {string[]} */
|
|
613
|
+
const warns = [];
|
|
614
|
+
if (!/^sessions\/s\d{3}-/.test(activeSession)) {
|
|
615
|
+
warns.push(`active_session "${activeSession}" não segue o formato sessions/sNNN-slug`);
|
|
616
|
+
}
|
|
617
|
+
if (!fs.existsSync(scoped.sessionRoot)) {
|
|
618
|
+
warns.push(`active_session aponta para ${activeSession}, mas a pasta da sessão não existe em .oxe/`);
|
|
619
|
+
return warns;
|
|
620
|
+
}
|
|
621
|
+
if (!fs.existsSync(scoped.sessionManifest)) warns.push(`Sessão ativa ${activeSession} sem SESSION.md`);
|
|
622
|
+
if (!fs.existsSync(base.sessionsIndex)) warns.push('Sessão ativa definida, mas .oxe/SESSIONS.md não existe');
|
|
623
|
+
return warns;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* @param {string} target
|
|
628
|
+
* @returns {string[]}
|
|
629
|
+
*/
|
|
630
|
+
function installationCompletenessWarnings(target) {
|
|
631
|
+
const p = oxePaths(target);
|
|
632
|
+
/** @type {string[]} */
|
|
633
|
+
const warns = [];
|
|
634
|
+
if (!fs.existsSync(p.oxe)) return warns;
|
|
635
|
+
if (!fs.existsSync(p.globalDir)) warns.push('.oxe/global/ ausente');
|
|
636
|
+
if (!fs.existsSync(p.globalLessons)) warns.push('.oxe/global/LESSONS.md ausente');
|
|
637
|
+
if (!fs.existsSync(p.globalMilestones)) warns.push('.oxe/global/MILESTONES.md ausente');
|
|
638
|
+
if (!fs.existsSync(p.globalMilestonesDir)) warns.push('.oxe/global/milestones/ ausente');
|
|
639
|
+
if (!fs.existsSync(p.sessionsDir)) warns.push('.oxe/sessions/ ausente');
|
|
640
|
+
return warns;
|
|
641
|
+
}
|
|
485
642
|
|
|
486
643
|
/**
|
|
487
644
|
* Próximo passo único (espelha o workflow next.md).
|
|
488
645
|
* @param {string} target
|
|
489
646
|
* @param {{ discuss_before_plan?: boolean }} cfg
|
|
490
647
|
*/
|
|
491
|
-
function suggestNextStep(target, cfg = {}) {
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
const
|
|
495
|
-
const
|
|
648
|
+
function suggestNextStep(target, cfg = {}) {
|
|
649
|
+
const base = oxePaths(target);
|
|
650
|
+
const stateText = fs.existsSync(base.state) ? fs.readFileSync(base.state, 'utf8') : '';
|
|
651
|
+
const p = scopedOxePaths(target, parseActiveSession(stateText));
|
|
652
|
+
const discussBefore = Boolean(cfg.discuss_before_plan);
|
|
653
|
+
const threshold = Number(cfg.plan_confidence_threshold) || 70;
|
|
654
|
+
const has = (/** @type {string} */ f) => fs.existsSync(f);
|
|
655
|
+
const mapsComplete = EXPECTED_CODEBASE_MAPS.every((f) => has(path.join(p.codebase, f)));
|
|
496
656
|
|
|
497
657
|
if (!has(p.oxe) || !has(p.state)) {
|
|
498
658
|
return {
|
|
@@ -503,8 +663,7 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
503
663
|
};
|
|
504
664
|
}
|
|
505
665
|
|
|
506
|
-
const
|
|
507
|
-
const phase = parseStatePhase(stateText);
|
|
666
|
+
const phase = parseStatePhase(stateText);
|
|
508
667
|
|
|
509
668
|
if (!mapsComplete && !has(p.quick)) {
|
|
510
669
|
return {
|
|
@@ -542,14 +701,24 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
542
701
|
};
|
|
543
702
|
}
|
|
544
703
|
|
|
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
|
-
}
|
|
704
|
+
if (!has(p.plan)) {
|
|
705
|
+
return {
|
|
706
|
+
step: 'plan',
|
|
707
|
+
cursorCmd: '/oxe-plan',
|
|
708
|
+
reason: 'SPEC existe mas PLAN.md não — gere o plano com verificação por tarefa',
|
|
709
|
+
artifacts: ['.oxe/PLAN.md'],
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const selfEval = parsePlanSelfEvaluation(p.plan);
|
|
714
|
+
if (selfEval.bestPlan === 'não' || (selfEval.confidence != null && selfEval.confidence < threshold)) {
|
|
715
|
+
return {
|
|
716
|
+
step: 'plan',
|
|
717
|
+
cursorCmd: '/oxe-plan --replan',
|
|
718
|
+
reason: `O plano atual ainda não atingiu confiança executável (limiar ${threshold}%)`,
|
|
719
|
+
artifacts: ['.oxe/PLAN.md', '.oxe/STATE.md'],
|
|
720
|
+
};
|
|
721
|
+
}
|
|
553
722
|
|
|
554
723
|
if (!has(p.verify)) {
|
|
555
724
|
return {
|
|
@@ -622,53 +791,72 @@ function suggestNextStep(target, cfg = {}) {
|
|
|
622
791
|
/**
|
|
623
792
|
* @param {string} target
|
|
624
793
|
*/
|
|
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
|
|
794
|
+
function buildHealthReport(target) {
|
|
795
|
+
const { config, path: cfgPath, parseError } = loadOxeConfigMerged(target);
|
|
796
|
+
const shape = validateConfigShape(config);
|
|
797
|
+
const base = oxePaths(target);
|
|
798
|
+
let stateText = '';
|
|
799
|
+
if (fs.existsSync(base.state)) {
|
|
800
|
+
try {
|
|
801
|
+
stateText = fs.readFileSync(base.state, 'utf8');
|
|
802
|
+
} catch {
|
|
803
|
+
stateText = '';
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const activeSession = parseActiveSession(stateText);
|
|
807
|
+
const p = scopedOxePaths(target, activeSession);
|
|
808
|
+
const phase = parseStatePhase(stateText);
|
|
638
809
|
const scanDate = parseLastScanDate(stateText);
|
|
639
810
|
const stale = isStaleScan(scanDate, Number(config.scan_max_age_days) || 0);
|
|
640
811
|
const compactDate = parseLastCompactDate(stateText);
|
|
641
812
|
const staleCompact = isStaleScan(compactDate, Number(config.compact_max_age_days) || 0);
|
|
642
813
|
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 sumWarn = verifyGapsWithoutSummaryWarning(p.verify, p.summary);
|
|
646
|
-
const specReq = Array.isArray(config.spec_required_sections) ? config.spec_required_sections : [];
|
|
647
|
-
const specWarn = specSectionWarnings(p.spec, specReq.map(String));
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
...
|
|
651
|
-
...
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
814
|
+
const staleLessons = isStaleLessons(retroDate, Number(config.lessons_max_age_days) || 0);
|
|
815
|
+
const phaseWarn = phase ? phaseCoherenceWarnings(phase, p) : [];
|
|
816
|
+
const sumWarn = verifyGapsWithoutSummaryWarning(p.verify, p.summary);
|
|
817
|
+
const specReq = Array.isArray(config.spec_required_sections) ? config.spec_required_sections : [];
|
|
818
|
+
const specWarn = specSectionWarnings(p.spec, specReq.map(String));
|
|
819
|
+
const threshold = Number(config.plan_confidence_threshold) || 70;
|
|
820
|
+
const planWarn = [
|
|
821
|
+
...planWaveWarningsFixed(p.plan, Number(config.plan_max_tasks_per_wave) || 0),
|
|
822
|
+
...planTaskAceiteWarnings(p.plan),
|
|
823
|
+
...planSelfEvaluationWarnings(p.plan, threshold),
|
|
824
|
+
...planAgentsWarnings(target),
|
|
825
|
+
];
|
|
826
|
+
const sessionWarn = sessionWarnings(target, activeSession);
|
|
827
|
+
const installWarn = installationCompletenessWarnings(target);
|
|
828
|
+
const planSelfEvaluation = parsePlanSelfEvaluation(p.plan);
|
|
829
|
+
const next = suggestNextStep(target, {
|
|
830
|
+
discuss_before_plan: config.discuss_before_plan,
|
|
831
|
+
plan_confidence_threshold: threshold,
|
|
832
|
+
});
|
|
833
|
+
const hardFailure = Boolean(parseError) || sessionWarn.some((w) => /não existe|sem SESSION\.md/i.test(w));
|
|
834
|
+
const warningCount =
|
|
835
|
+
phaseWarn.length + specWarn.length + planWarn.length + sessionWarn.length + installWarn.length + (sumWarn ? 1 : 0);
|
|
836
|
+
const healthStatus = hardFailure ? 'broken' : warningCount > 0 ? 'warning' : 'healthy';
|
|
837
|
+
|
|
838
|
+
return {
|
|
656
839
|
configPath: cfgPath,
|
|
657
840
|
configParseError: parseError,
|
|
658
841
|
unknownConfigKeys: shape.unknownKeys,
|
|
659
|
-
typeErrors: shape.typeErrors,
|
|
660
|
-
phase,
|
|
661
|
-
|
|
842
|
+
typeErrors: shape.typeErrors,
|
|
843
|
+
phase,
|
|
844
|
+
activeSession,
|
|
845
|
+
scanDate,
|
|
662
846
|
stale,
|
|
663
847
|
compactDate,
|
|
664
848
|
staleCompact,
|
|
665
849
|
retroDate,
|
|
666
|
-
staleLessons,
|
|
667
|
-
phaseWarn,
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
850
|
+
staleLessons,
|
|
851
|
+
phaseWarn,
|
|
852
|
+
sessionWarn,
|
|
853
|
+
installWarn,
|
|
854
|
+
summaryGapWarn: sumWarn,
|
|
855
|
+
specWarn,
|
|
856
|
+
planWarn,
|
|
857
|
+
planSelfEvaluation,
|
|
858
|
+
healthStatus,
|
|
859
|
+
next,
|
|
672
860
|
scanFocusGlobs: config.scan_focus_globs,
|
|
673
861
|
scanIgnoreGlobs: config.scan_ignore_globs,
|
|
674
862
|
};
|
|
@@ -686,18 +874,23 @@ module.exports = {
|
|
|
686
874
|
loadOxeConfigMerged,
|
|
687
875
|
validateConfigShape,
|
|
688
876
|
parseStatePhase,
|
|
689
|
-
parseLastScanDate,
|
|
690
|
-
parseLastCompactDate,
|
|
691
|
-
parseLastRetroDate,
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
877
|
+
parseLastScanDate,
|
|
878
|
+
parseLastCompactDate,
|
|
879
|
+
parseLastRetroDate,
|
|
880
|
+
parseActiveSession,
|
|
881
|
+
isStaleScan,
|
|
882
|
+
isStaleLessons,
|
|
883
|
+
planAgentsWarnings,
|
|
884
|
+
installationCompletenessWarnings,
|
|
885
|
+
parsePlanSelfEvaluation,
|
|
886
|
+
planSelfEvaluationWarnings,
|
|
887
|
+
phaseCoherenceWarnings,
|
|
696
888
|
verifyGapsWithoutSummaryWarning,
|
|
697
889
|
specSectionWarnings,
|
|
698
890
|
planWaveWarningsFixed,
|
|
699
891
|
planTaskAceiteWarnings,
|
|
700
|
-
suggestNextStep,
|
|
701
|
-
buildHealthReport,
|
|
702
|
-
oxePaths,
|
|
703
|
-
|
|
892
|
+
suggestNextStep,
|
|
893
|
+
buildHealthReport,
|
|
894
|
+
oxePaths,
|
|
895
|
+
scopedOxePaths,
|
|
896
|
+
};
|