oxe-cc 1.2.1 → 1.3.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 +2 -2
- package/.cursor/commands/oxe-capabilities.md +2 -2
- package/.cursor/commands/oxe-checkpoint.md +2 -2
- package/.cursor/commands/oxe-compact.md +2 -2
- package/.cursor/commands/oxe-dashboard.md +2 -2
- package/.cursor/commands/oxe-debug.md +2 -2
- package/.cursor/commands/oxe-discuss.md +2 -2
- package/.cursor/commands/oxe-execute.md +5 -2
- package/.cursor/commands/oxe-forensics.md +2 -2
- package/.cursor/commands/oxe-help.md +2 -2
- package/.cursor/commands/oxe-loop.md +2 -2
- package/.cursor/commands/oxe-milestone.md +2 -2
- package/.cursor/commands/oxe-next.md +2 -2
- package/.cursor/commands/oxe-obs.md +2 -2
- package/.cursor/commands/oxe-plan-agent.md +2 -2
- package/.cursor/commands/oxe-plan.md +2 -2
- package/.cursor/commands/oxe-project.md +2 -2
- package/.cursor/commands/oxe-quick.md +2 -2
- package/.cursor/commands/oxe-research.md +2 -2
- package/.cursor/commands/oxe-retro.md +2 -2
- package/.cursor/commands/oxe-review-pr.md +2 -2
- package/.cursor/commands/oxe-route.md +2 -2
- package/.cursor/commands/oxe-scan.md +2 -2
- package/.cursor/commands/oxe-security.md +2 -2
- package/.cursor/commands/oxe-session.md +2 -2
- package/.cursor/commands/oxe-ship.md +2 -2
- package/.cursor/commands/oxe-skill.md +2 -2
- package/.cursor/commands/oxe-spec.md +2 -2
- package/.cursor/commands/oxe-ui-review.md +2 -2
- package/.cursor/commands/oxe-ui-spec.md +2 -2
- package/.cursor/commands/oxe-update.md +2 -2
- package/.cursor/commands/oxe-validate-gaps.md +2 -2
- package/.cursor/commands/oxe-verify.md +5 -2
- package/.cursor/commands/oxe-workstream.md +2 -2
- package/.cursor/commands/oxe.md +2 -2
- package/.github/copilot-instructions.md +13 -13
- package/.github/prompts/oxe-ask.prompt.md +2 -2
- package/.github/prompts/oxe-capabilities.prompt.md +2 -2
- package/.github/prompts/oxe-checkpoint.prompt.md +2 -2
- package/.github/prompts/oxe-compact.prompt.md +2 -2
- package/.github/prompts/oxe-dashboard.prompt.md +2 -2
- package/.github/prompts/oxe-debug.prompt.md +2 -2
- package/.github/prompts/oxe-discuss.prompt.md +2 -2
- package/.github/prompts/oxe-execute.prompt.md +5 -2
- package/.github/prompts/oxe-forensics.prompt.md +2 -2
- package/.github/prompts/oxe-help.prompt.md +2 -2
- package/.github/prompts/oxe-loop.prompt.md +2 -2
- package/.github/prompts/oxe-milestone.prompt.md +2 -2
- package/.github/prompts/oxe-next.prompt.md +2 -2
- package/.github/prompts/oxe-obs.prompt.md +2 -2
- package/.github/prompts/oxe-plan-agent.prompt.md +2 -2
- package/.github/prompts/oxe-plan.prompt.md +2 -2
- package/.github/prompts/oxe-project.prompt.md +2 -2
- package/.github/prompts/oxe-quick.prompt.md +2 -2
- package/.github/prompts/oxe-research.prompt.md +2 -2
- package/.github/prompts/oxe-retro.prompt.md +2 -2
- package/.github/prompts/oxe-review-pr.prompt.md +2 -2
- package/.github/prompts/oxe-route.prompt.md +2 -2
- package/.github/prompts/oxe-scan.prompt.md +2 -2
- package/.github/prompts/oxe-security.prompt.md +2 -2
- package/.github/prompts/oxe-session.prompt.md +2 -2
- package/.github/prompts/oxe-ship.prompt.md +2 -2
- package/.github/prompts/oxe-skill.prompt.md +2 -2
- package/.github/prompts/oxe-spec.prompt.md +2 -2
- package/.github/prompts/oxe-ui-review.prompt.md +2 -2
- package/.github/prompts/oxe-ui-spec.prompt.md +2 -2
- package/.github/prompts/oxe-update.prompt.md +2 -2
- package/.github/prompts/oxe-validate-gaps.prompt.md +2 -2
- package/.github/prompts/oxe-verify.prompt.md +5 -2
- package/.github/prompts/oxe-workstream.prompt.md +2 -2
- package/.github/prompts/oxe.prompt.md +2 -2
- package/CHANGELOG.md +52 -17
- package/README.md +610 -551
- package/bin/banner.txt +1 -1
- package/bin/lib/oxe-agent-install.cjs +69 -69
- package/bin/lib/oxe-azure.cjs +1445 -1445
- package/bin/lib/oxe-context-engine.cjs +867 -867
- package/bin/lib/oxe-dashboard.cjs +76 -28
- package/bin/lib/oxe-operational.cjs +2144 -1340
- package/bin/lib/oxe-project-health.cjs +483 -1
- package/bin/lib/oxe-runtime-semantics.cjs +12 -0
- package/bin/oxe-cc.js +554 -152
- package/commands/oxe/ask.md +2 -2
- package/commands/oxe/capabilities.md +2 -2
- package/commands/oxe/checkpoint.md +2 -2
- package/commands/oxe/compact.md +2 -2
- package/commands/oxe/dashboard.md +2 -2
- package/commands/oxe/debug.md +2 -2
- package/commands/oxe/discuss.md +2 -2
- package/commands/oxe/execute.md +5 -2
- package/commands/oxe/forensics.md +2 -2
- package/commands/oxe/help.md +2 -2
- package/commands/oxe/loop.md +2 -2
- package/commands/oxe/milestone.md +2 -2
- package/commands/oxe/next.md +2 -2
- package/commands/oxe/obs.md +2 -2
- package/commands/oxe/oxe.md +2 -2
- package/commands/oxe/plan-agent.md +2 -2
- package/commands/oxe/plan.md +2 -2
- package/commands/oxe/project.md +2 -2
- package/commands/oxe/quick.md +2 -2
- package/commands/oxe/research.md +2 -2
- package/commands/oxe/retro.md +2 -2
- package/commands/oxe/review-pr.md +2 -2
- package/commands/oxe/route.md +2 -2
- package/commands/oxe/scan.md +2 -2
- package/commands/oxe/security.md +2 -2
- package/commands/oxe/session.md +2 -2
- package/commands/oxe/ship.md +2 -2
- package/commands/oxe/skill.md +2 -2
- package/commands/oxe/spec.md +2 -2
- package/commands/oxe/ui-review.md +2 -2
- package/commands/oxe/ui-spec.md +2 -2
- package/commands/oxe/update.md +2 -2
- package/commands/oxe/validate-gaps.md +2 -2
- package/commands/oxe/verify.md +5 -2
- package/commands/oxe/workstream.md +2 -2
- package/lib/runtime/delivery/branch-manager.d.ts +1 -0
- package/lib/runtime/delivery/branch-manager.js +7 -0
- package/lib/runtime/delivery/ci-checks.js +34 -1
- package/lib/runtime/delivery/delivery-records.d.ts +34 -0
- package/lib/runtime/delivery/delivery-records.js +48 -0
- package/lib/runtime/delivery/index.d.ts +1 -0
- package/lib/runtime/delivery/index.js +1 -0
- package/lib/runtime/delivery/promotion-pipeline.d.ts +26 -2
- package/lib/runtime/delivery/promotion-pipeline.js +111 -14
- package/lib/runtime/gate/gate-manager.d.ts +41 -0
- package/lib/runtime/gate/gate-manager.js +108 -1
- package/lib/runtime/index.d.ts +2 -2
- package/lib/runtime/index.js +3 -1
- package/lib/runtime/models/gate-decision.d.ts +4 -1
- package/lib/runtime/models/workspace.d.ts +3 -0
- package/lib/runtime/plugins/capability-adapter.d.ts +12 -0
- package/lib/runtime/plugins/capability-adapter.js +204 -0
- package/lib/runtime/plugins/capability-matrix.d.ts +5 -0
- package/lib/runtime/plugins/capability-matrix.js +48 -17
- package/lib/runtime/plugins/index.d.ts +1 -0
- package/lib/runtime/plugins/index.js +1 -0
- package/lib/runtime/plugins/plugin-abi.d.ts +2 -0
- package/lib/runtime/plugins/plugin-manifest.d.ts +1 -1
- package/lib/runtime/plugins/plugin-manifest.js +6 -2
- package/lib/runtime/plugins/plugin-registry.d.ts +46 -0
- package/lib/runtime/plugins/plugin-registry.js +79 -2
- package/lib/runtime/policy/policy-engine.d.ts +19 -0
- package/lib/runtime/policy/policy-engine.js +76 -4
- package/lib/runtime/projection/projection-engine.d.ts +9 -1
- package/lib/runtime/projection/projection-engine.js +73 -3
- package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +43 -1
- package/lib/runtime/scheduler/multi-agent-coordinator.js +151 -39
- package/lib/runtime/scheduler/run-journal.d.ts +1 -1
- package/lib/runtime/scheduler/scheduler.d.ts +19 -1
- package/lib/runtime/scheduler/scheduler.js +258 -13
- package/lib/runtime/verification/verification-compiler.d.ts +43 -0
- package/lib/runtime/verification/verification-compiler.js +137 -0
- package/lib/runtime/verification/verification-manifest.d.ts +9 -0
- package/lib/runtime/verification/verification-manifest.js +56 -6
- package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +1 -0
- package/lib/runtime/workspace/strategies/ephemeral-container.js +4 -0
- package/lib/runtime/workspace/strategies/git-worktree.d.ts +1 -0
- package/lib/runtime/workspace/strategies/git-worktree.js +2 -0
- package/lib/runtime/workspace/strategies/inplace.d.ts +1 -0
- package/lib/runtime/workspace/strategies/inplace.js +2 -0
- package/lib/runtime/workspace/workspace-manager.d.ts +2 -1
- package/lib/sdk/README.md +9 -9
- package/lib/sdk/index.cjs +33 -24
- package/lib/sdk/index.d.ts +149 -14
- package/oxe/templates/ACTIVE-RUN.template.json +32 -32
- package/oxe/templates/CAPABILITIES.template.md +7 -7
- package/oxe/templates/CAPABILITY.template.md +45 -45
- package/oxe/templates/CHECKPOINTS.template.md +7 -7
- package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -68
- package/oxe/templates/HYPOTHESES.template.md +33 -33
- package/oxe/templates/LESSONS-METRICS.template.json +13 -13
- package/oxe/templates/NOTES.template.md +16 -16
- package/oxe/templates/PLAN-REVIEW.template.md +31 -31
- package/oxe/templates/SESSION.template.md +34 -34
- package/oxe/templates/SKILL.template.md +26 -26
- package/oxe/templates/STATE.md +55 -55
- package/oxe/templates/WORKFLOW_AUTHORING.md +18 -18
- package/oxe/workflows/ask.md +96 -96
- package/oxe/workflows/capabilities.md +25 -25
- package/oxe/workflows/dashboard.md +33 -33
- package/oxe/workflows/discuss.md +12 -12
- package/oxe/workflows/execute.md +14 -0
- package/oxe/workflows/help.md +352 -352
- package/oxe/workflows/next.md +22 -22
- package/oxe/workflows/oxe.md +6 -6
- package/oxe/workflows/plan-agent.md +9 -9
- package/oxe/workflows/quick.md +10 -10
- package/oxe/workflows/references/reasoning-discovery.md +28 -28
- package/oxe/workflows/references/reasoning-execution.md +29 -29
- package/oxe/workflows/references/reasoning-planning.md +32 -32
- package/oxe/workflows/references/reasoning-review.md +29 -29
- package/oxe/workflows/references/reasoning-status.md +24 -24
- package/oxe/workflows/references/robustness-elevation.md +295 -295
- package/oxe/workflows/references/workflow-runtime-contracts.json +952 -930
- package/oxe/workflows/route.md +16 -16
- package/oxe/workflows/session.md +213 -213
- package/oxe/workflows/ship.md +142 -142
- package/oxe/workflows/skill.md +44 -44
- package/oxe/workflows/ui-review.md +36 -36
- package/oxe/workflows/verify-audit.md +73 -73
- package/oxe/workflows/verify.md +10 -0
- package/package.json +92 -92
- package/packages/runtime/package.json +17 -17
- package/packages/runtime/src/audit/audit-trail.ts +243 -243
- package/packages/runtime/src/audit/index.ts +2 -2
- package/packages/runtime/src/audit/policy-pack.ts +62 -62
- package/packages/runtime/src/compiler/graph-compiler.ts +245 -245
- package/packages/runtime/src/compiler/index.ts +1 -1
- package/packages/runtime/src/context/context-pack-builder.ts +259 -259
- package/packages/runtime/src/context/context-pack-store.ts +197 -197
- package/packages/runtime/src/context/context-profiles.ts +60 -60
- package/packages/runtime/src/context/index.ts +3 -3
- package/packages/runtime/src/decision/decision-engine.ts +174 -174
- package/packages/runtime/src/decision/decision-memo.ts +211 -211
- package/packages/runtime/src/decision/index.ts +2 -2
- package/packages/runtime/src/delivery/branch-manager.ts +91 -84
- package/packages/runtime/src/delivery/ci-checks.ts +285 -252
- package/packages/runtime/src/delivery/delivery-records.ts +75 -0
- package/packages/runtime/src/delivery/index.ts +5 -4
- package/packages/runtime/src/delivery/pr-manager.ts +112 -112
- package/packages/runtime/src/delivery/promotion-pipeline.ts +334 -180
- package/packages/runtime/src/events/bus.ts +92 -92
- package/packages/runtime/src/events/catalog.ts +29 -29
- package/packages/runtime/src/events/envelope.ts +14 -14
- package/packages/runtime/src/events/index.ts +3 -3
- package/packages/runtime/src/evidence/evidence-store.ts +130 -130
- package/packages/runtime/src/evidence/index.ts +1 -1
- package/packages/runtime/src/gate/gate-manager.ts +289 -137
- package/packages/runtime/src/gate/index.ts +1 -1
- package/packages/runtime/src/index.ts +41 -37
- package/packages/runtime/src/models/attempt.ts +19 -19
- package/packages/runtime/src/models/evidence.ts +21 -21
- package/packages/runtime/src/models/gate-decision.ts +25 -21
- package/packages/runtime/src/models/index.ts +8 -8
- package/packages/runtime/src/models/run.ts +24 -24
- package/packages/runtime/src/models/session.ts +11 -11
- package/packages/runtime/src/models/verification-result.ts +10 -10
- package/packages/runtime/src/models/work-item.ts +25 -25
- package/packages/runtime/src/models/workspace.ts +31 -28
- package/packages/runtime/src/plugins/capability-adapter.ts +206 -0
- package/packages/runtime/src/plugins/capability-matrix.ts +126 -83
- package/packages/runtime/src/plugins/index.ts +5 -4
- package/packages/runtime/src/plugins/plugin-abi.ts +97 -95
- package/packages/runtime/src/plugins/plugin-manifest.ts +118 -113
- package/packages/runtime/src/plugins/plugin-registry.ts +232 -124
- package/packages/runtime/src/policy/index.ts +1 -1
- package/packages/runtime/src/policy/policy-engine.ts +330 -244
- package/packages/runtime/src/projection/index.ts +1 -1
- package/packages/runtime/src/projection/projection-engine.ts +328 -249
- package/packages/runtime/src/reducers/debug-reducer.ts +36 -36
- package/packages/runtime/src/reducers/index.ts +2 -2
- package/packages/runtime/src/reducers/run-state-reducer.ts +269 -269
- package/packages/runtime/src/scheduler/agent-registry.ts +132 -132
- package/packages/runtime/src/scheduler/agent-roles.ts +109 -109
- package/packages/runtime/src/scheduler/index.ts +4 -4
- package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +521 -333
- package/packages/runtime/src/scheduler/run-journal.ts +62 -62
- package/packages/runtime/src/scheduler/scheduler.ts +722 -441
- package/packages/runtime/src/verification/index.ts +2 -2
- package/packages/runtime/src/verification/verification-compiler.ts +436 -225
- package/packages/runtime/src/verification/verification-manifest.ts +252 -192
- package/packages/runtime/src/workspace/index.ts +5 -5
- package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +126 -121
- package/packages/runtime/src/workspace/strategies/git-worktree.ts +79 -77
- package/packages/runtime/src/workspace/strategies/inplace.ts +38 -35
- package/packages/runtime/src/workspace/workspace-manager.ts +16 -15
- package/packages/runtime/tsconfig.json +17 -17
- package/vscode-extension/.vscodeignore +7 -7
- package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
- package/vscode-extension/package.json +185 -185
- package/vscode-extension/src/extension.js +310 -310
- package/vscode-extension/src/shared/contextLoader.js +137 -137
- package/vscode-extension/src/shared/contractBuilder.js +159 -159
- package/vscode-extension/src/shared/stateReader.js +101 -101
|
@@ -1,867 +1,867 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const crypto = require('crypto');
|
|
6
|
-
|
|
7
|
-
const health = require('./oxe-project-health.cjs');
|
|
8
|
-
const operational = require('./oxe-operational.cjs');
|
|
9
|
-
const azure = require('./oxe-azure.cjs');
|
|
10
|
-
const runtimeSemantics = require('./oxe-runtime-semantics.cjs');
|
|
11
|
-
|
|
12
|
-
function ensureDir(dirPath) {
|
|
13
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function readTextIfExists(filePath) {
|
|
17
|
-
try {
|
|
18
|
-
return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function writeJson(filePath, value) {
|
|
25
|
-
try {
|
|
26
|
-
ensureDir(path.dirname(filePath));
|
|
27
|
-
fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
28
|
-
} catch (err) {
|
|
29
|
-
throw new Error(`Falha ao escrever ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function sha256Text(text) {
|
|
34
|
-
return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function sha256File(filePath) {
|
|
38
|
-
try {
|
|
39
|
-
return sha256Text(fs.readFileSync(filePath, 'utf8'));
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function toIso(value) {
|
|
46
|
-
if (!value) return null;
|
|
47
|
-
const d = value instanceof Date ? value : new Date(value);
|
|
48
|
-
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function hoursSince(iso) {
|
|
52
|
-
if (!iso) return null;
|
|
53
|
-
const ms = Date.parse(String(iso));
|
|
54
|
-
if (Number.isNaN(ms)) return null;
|
|
55
|
-
return Math.floor((Date.now() - ms) / (1000 * 60 * 60));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function summarizeText(text, maxChars = 640, maxLines = 12) {
|
|
59
|
-
const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
|
|
60
|
-
if (!normalized) return '';
|
|
61
|
-
const lines = normalized
|
|
62
|
-
.split('\n')
|
|
63
|
-
.map((line) => line.trim())
|
|
64
|
-
.filter(Boolean)
|
|
65
|
-
.slice(0, maxLines);
|
|
66
|
-
const joined = lines.join(' ');
|
|
67
|
-
return joined.length > maxChars ? `${joined.slice(0, maxChars - 1)}…` : joined;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const INTENT_SECTION_KEYWORDS = {
|
|
71
|
-
execution_input: ['onda', 'tarefa', 'hipótese', 'hipotese', 'wave', 'task', 'bloqueio', 'checkpoint', 'execu'],
|
|
72
|
-
verification: ['gap', 'critério', 'criterio', 'finding', 'auditoria', 'resultado', 'evidência', 'evidencia', 'falha', 'lacuna'],
|
|
73
|
-
planning_input: ['objetivo', 'autoavaliação', 'autoavaliacao', 'confiança', 'confianca', 'plano', 'requisito', 'risco', 'hipótese', 'hipotese'],
|
|
74
|
-
critical_check: ['bloqueio', 'falha', 'gap', 'erro', 'crítico', 'critico', 'p0', 'p1'],
|
|
75
|
-
status_read: ['fase', 'estado', 'próximo', 'proximo', 'status', 'resumo', 'sessão', 'sessao', 'snapshot'],
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const DEFAULT_PRESERVE_MARKERS = [
|
|
79
|
-
'P0', 'P1', 'bloqueado', 'BLOQUEADO', 'FALHA', 'crítico', 'CRÍTICO',
|
|
80
|
-
'gaps', 'GAPS', 'bloqueante', 'BLOQUEANTE', 'crítica', 'CRÍTICA',
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Extração semântica orientada a intenção — preserva marcadores críticos independentemente
|
|
85
|
-
* de posição, prioriza seções relevantes ao workflow e preenche o orçamento restante
|
|
86
|
-
* com conteúdo adicional.
|
|
87
|
-
*
|
|
88
|
-
* @param {string} text
|
|
89
|
-
* @param {{ intent?: string, maxChars?: number, preserveMarkers?: string[] }} [options]
|
|
90
|
-
* @returns {string}
|
|
91
|
-
*/
|
|
92
|
-
function extractSemanticFragment(text, options = {}) {
|
|
93
|
-
const intent = String(options.intent || 'status_read');
|
|
94
|
-
const maxChars = Math.max(200, Number(options.maxChars) || 1200);
|
|
95
|
-
const preserveMarkers = Array.isArray(options.preserveMarkers)
|
|
96
|
-
? options.preserveMarkers
|
|
97
|
-
: DEFAULT_PRESERVE_MARKERS;
|
|
98
|
-
|
|
99
|
-
const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
|
|
100
|
-
if (!normalized) return '';
|
|
101
|
-
|
|
102
|
-
// Sem headings — fallback para summarizeText com limite maior
|
|
103
|
-
if (!normalized.includes('\n## ') && !normalized.startsWith('## ') &&
|
|
104
|
-
!normalized.includes('\n# ') && !normalized.startsWith('# ')) {
|
|
105
|
-
return summarizeText(normalized, maxChars, 20);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Parsear em seções delimitadas por headings (# ou ##)
|
|
109
|
-
const sections = [];
|
|
110
|
-
let current = { heading: '', lines: [] };
|
|
111
|
-
for (const line of normalized.split('\n')) {
|
|
112
|
-
if (/^#{1,3} /.test(line)) {
|
|
113
|
-
sections.push(current);
|
|
114
|
-
current = { heading: line, lines: [] };
|
|
115
|
-
} else {
|
|
116
|
-
current.lines.push(line);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
sections.push(current);
|
|
120
|
-
|
|
121
|
-
const kws = INTENT_SECTION_KEYWORDS[intent] || INTENT_SECTION_KEYWORDS.status_read;
|
|
122
|
-
|
|
123
|
-
// Pontuar cada seção: 3=crítica, 2=relevante ao intent, 1=primeira seção, 0=resto
|
|
124
|
-
const scored = sections.map((s, i) => {
|
|
125
|
-
const h = s.heading.toLowerCase();
|
|
126
|
-
const hasCritical = [s.heading, ...s.lines].some((l) =>
|
|
127
|
-
preserveMarkers.some((m) => l.includes(m))
|
|
128
|
-
);
|
|
129
|
-
const isRelevant = kws.some((k) => h.includes(k));
|
|
130
|
-
const relevance = hasCritical ? 3 : isRelevant ? 2 : i === 0 ? 1 : 0;
|
|
131
|
-
return { ...s, relevance, index: i };
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Ordenar: críticas primeiro, depois por intent, depois por posição original
|
|
135
|
-
const sorted = [...scored].sort((a, b) => {
|
|
136
|
-
if (b.relevance !== a.relevance) return b.relevance - a.relevance;
|
|
137
|
-
return a.index - b.index;
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Preencher orçamento gulodamente
|
|
141
|
-
const parts = [];
|
|
142
|
-
let budget = maxChars;
|
|
143
|
-
|
|
144
|
-
for (const section of sorted) {
|
|
145
|
-
if (budget <= 0) break;
|
|
146
|
-
const chunk = [section.heading, ...section.lines.filter(Boolean)]
|
|
147
|
-
.filter(Boolean)
|
|
148
|
-
.join('\n')
|
|
149
|
-
.trim();
|
|
150
|
-
if (!chunk) continue;
|
|
151
|
-
if (chunk.length <= budget) {
|
|
152
|
-
parts.push({ index: section.index, text: chunk });
|
|
153
|
-
budget -= chunk.length + 2;
|
|
154
|
-
} else if (budget > 80) {
|
|
155
|
-
parts.push({ index: section.index, text: `${chunk.slice(0, budget - 1)}…` });
|
|
156
|
-
budget = 0;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Reordenar na sequência original do documento
|
|
161
|
-
parts.sort((a, b) => a.index - b.index);
|
|
162
|
-
const result = parts.map((p) => p.text).join('\n\n').trim();
|
|
163
|
-
return result.length > maxChars ? `${result.slice(0, maxChars - 1)}…` : result;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function sanitizeSession(activeSession) {
|
|
167
|
-
return String(activeSession || '')
|
|
168
|
-
.replace(/^sessions\//, '')
|
|
169
|
-
.replace(/[\\/]/g, '__')
|
|
170
|
-
.replace(/[^A-Za-z0-9._-]+/g, '-');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function contextPaths(projectRoot, activeSession) {
|
|
174
|
-
const base = health.oxePaths(projectRoot);
|
|
175
|
-
const stateText = readTextIfExists(base.state) || '';
|
|
176
|
-
const resolvedSession = activeSession === undefined ? health.parseActiveSession(stateText) : activeSession;
|
|
177
|
-
const sessionKey = sanitizeSession(resolvedSession);
|
|
178
|
-
const phase = health.parseStatePhase(stateText) || 'unknown';
|
|
179
|
-
const safePhase = String(phase).replace(/[^A-Za-z0-9._-]+/g, '-');
|
|
180
|
-
const root = path.join(base.oxe, 'context');
|
|
181
|
-
const packsDir = path.join(root, 'packs');
|
|
182
|
-
const summariesDir = path.join(root, 'summaries');
|
|
183
|
-
return {
|
|
184
|
-
root,
|
|
185
|
-
index: path.join(root, 'index.json'),
|
|
186
|
-
packsDir,
|
|
187
|
-
summariesDir,
|
|
188
|
-
projectSummaryJson: path.join(summariesDir, 'project.json'),
|
|
189
|
-
projectSummaryMd: path.join(summariesDir, 'project.md'),
|
|
190
|
-
sessionSummaryJson: resolvedSession ? path.join(summariesDir, `session-${sessionKey}.json`) : null,
|
|
191
|
-
sessionSummaryMd: resolvedSession ? path.join(summariesDir, `session-${sessionKey}.md`) : null,
|
|
192
|
-
phaseSummaryJson: path.join(summariesDir, `phase-${safePhase}.json`),
|
|
193
|
-
phaseSummaryMd: path.join(summariesDir, `phase-${safePhase}.md`),
|
|
194
|
-
defaultPackJson: (workflow) => path.join(packsDir, `${workflow}.json`),
|
|
195
|
-
defaultPackMd: (workflow) => path.join(packsDir, `${workflow}.md`),
|
|
196
|
-
sessionPackJson: (workflow) => resolvedSession ? path.join(packsDir, `session-${sessionKey}-${workflow}.json`) : null,
|
|
197
|
-
sessionPackMd: (workflow) => resolvedSession ? path.join(packsDir, `session-${sessionKey}-${workflow}.md`) : null,
|
|
198
|
-
activeSession: resolvedSession || null,
|
|
199
|
-
phase,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function inferScope(activeSession, preferSession) {
|
|
204
|
-
if (preferSession && activeSession) return 'session';
|
|
205
|
-
return 'project';
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function resolveArtifactCandidates(projectRoot, activeSession) {
|
|
209
|
-
const base = health.oxePaths(projectRoot);
|
|
210
|
-
const scoped = health.scopedOxePaths(projectRoot, activeSession || null);
|
|
211
|
-
const ctx = contextPaths(projectRoot, activeSession);
|
|
212
|
-
const azurePaths = azure.azurePaths(projectRoot);
|
|
213
|
-
const preferSession = Boolean(activeSession);
|
|
214
|
-
const withFallback = (alias, semanticType, primaryPath, fallbackPath = null, scope = inferScope(activeSession, true)) => ({
|
|
215
|
-
alias,
|
|
216
|
-
semantic_type: semanticType,
|
|
217
|
-
scope,
|
|
218
|
-
primary: primaryPath,
|
|
219
|
-
fallback: fallbackPath && fallbackPath !== primaryPath ? fallbackPath : null,
|
|
220
|
-
});
|
|
221
|
-
return {
|
|
222
|
-
state: withFallback('state', 'state', base.state, null, 'project'),
|
|
223
|
-
session_manifest: withFallback('session_manifest', 'session', scoped.sessionManifest || null, null, 'session'),
|
|
224
|
-
sessions_index: withFallback('sessions_index', 'session_index', base.sessionsIndex, null, 'project'),
|
|
225
|
-
execution_state: withFallback('execution_state', 'state', scoped.executionState || null, null, 'session'),
|
|
226
|
-
spec: withFallback('spec', 'spec', scoped.spec, base.spec),
|
|
227
|
-
discuss: withFallback('discuss', 'discuss', scoped.discuss, base.discuss),
|
|
228
|
-
plan: withFallback('plan', 'plan', scoped.plan, base.plan),
|
|
229
|
-
plan_agents: withFallback('plan_agents', 'plan_agents', base.planAgents, null, 'project'),
|
|
230
|
-
quick: withFallback('quick', 'plan', scoped.quick, base.quick),
|
|
231
|
-
runtime: withFallback('runtime', 'runtime', scoped.runtime, base.runtime),
|
|
232
|
-
checkpoints: withFallback('checkpoints', 'checkpoints', scoped.checkpoints, base.checkpoints),
|
|
233
|
-
verify: withFallback('verify', 'verify', scoped.verify, base.verify),
|
|
234
|
-
summary: withFallback('summary', 'summary', scoped.summary, base.summary),
|
|
235
|
-
plan_review: withFallback('plan_review', 'review', scoped.planReview, base.planReview),
|
|
236
|
-
review_comments: withFallback('review_comments', 'review_comments', scoped.planReviewComments, base.planReviewComments),
|
|
237
|
-
active_run: withFallback('active_run', 'runtime', operational.operationalPaths(projectRoot, activeSession || null).activeRun, null, preferSession ? 'session' : 'project'),
|
|
238
|
-
events: withFallback('events', 'trace', operational.operationalPaths(projectRoot, activeSession || null).events, null, preferSession ? 'session' : 'project'),
|
|
239
|
-
capabilities_index: withFallback('capabilities_index', 'capabilities', base.capabilitiesIndex, null, 'project'),
|
|
240
|
-
investigations_index: withFallback('investigations_index', 'investigations', scoped.investigationsIndex, base.investigationsIndex),
|
|
241
|
-
global_lessons: withFallback('global_lessons', 'memory', base.globalLessons, base.lessons, 'project'),
|
|
242
|
-
codebase_overview: withFallback('codebase_overview', 'codebase', path.join(base.codebase, 'OVERVIEW.md'), null, 'project'),
|
|
243
|
-
codebase_stack: withFallback('codebase_stack', 'codebase', path.join(base.codebase, 'STACK.md'), null, 'project'),
|
|
244
|
-
codebase_structure: withFallback('codebase_structure', 'codebase', path.join(base.codebase, 'STRUCTURE.md'), null, 'project'),
|
|
245
|
-
codebase_testing: withFallback('codebase_testing', 'codebase', path.join(base.codebase, 'TESTING.md'), null, 'project'),
|
|
246
|
-
codebase_integrations: withFallback('codebase_integrations', 'codebase', path.join(base.codebase, 'INTEGRATIONS.md'), null, 'project'),
|
|
247
|
-
codebase_concerns: withFallback('codebase_concerns', 'codebase', path.join(base.codebase, 'CONCERNS.md'), null, 'project'),
|
|
248
|
-
azure_inventory: withFallback('azure_inventory', 'provider', azurePaths.inventoryMd, null, 'project'),
|
|
249
|
-
azure_servicebus: withFallback('azure_servicebus', 'provider', azurePaths.serviceBusMd, null, 'project'),
|
|
250
|
-
azure_eventgrid: withFallback('azure_eventgrid', 'provider', azurePaths.eventGridMd, null, 'project'),
|
|
251
|
-
azure_sql: withFallback('azure_sql', 'provider', azurePaths.sqlMd, null, 'project'),
|
|
252
|
-
copilot_manifest: withFallback('copilot_manifest', 'install_manifest', base.copilotManifest, null, 'project'),
|
|
253
|
-
runtime_semantics_manifest: withFallback('runtime_semantics_manifest', 'install_manifest', path.join(base.installDir, 'runtime-semantics.json'), null, 'project'),
|
|
254
|
-
project_summary: withFallback('project_summary', 'summary', ctx.projectSummaryJson, null, 'project'),
|
|
255
|
-
session_summary: withFallback('session_summary', 'summary', ctx.sessionSummaryJson, null, 'session'),
|
|
256
|
-
phase_summary: withFallback('phase_summary', 'summary', ctx.phaseSummaryJson, null, 'project'),
|
|
257
|
-
context_pack_dashboard: withFallback('context_pack_dashboard', 'context_pack', ctx.defaultPackJson('dashboard'), null, 'project'),
|
|
258
|
-
calibration: withFallback('calibration', 'calibration', path.join(base.oxe, 'calibration.json'), null, 'project'),
|
|
259
|
-
lessons_metrics: withFallback('lessons_metrics', 'metrics', path.join(base.oxe, 'lessons-metrics.json'), null, 'project'),
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function buildArtifactRecord(candidate) {
|
|
264
|
-
const primaryExists = Boolean(candidate.primary && fs.existsSync(candidate.primary));
|
|
265
|
-
const fallbackExists = Boolean(candidate.fallback && fs.existsSync(candidate.fallback));
|
|
266
|
-
const chosenPath = primaryExists ? candidate.primary : fallbackExists ? candidate.fallback : candidate.primary || candidate.fallback || null;
|
|
267
|
-
const chosenExists = Boolean(chosenPath && fs.existsSync(chosenPath));
|
|
268
|
-
const chosenText = chosenExists ? readTextIfExists(chosenPath) || '' : '';
|
|
269
|
-
const primaryHash = primaryExists ? sha256File(candidate.primary) : null;
|
|
270
|
-
const fallbackHash = fallbackExists ? sha256File(candidate.fallback) : null;
|
|
271
|
-
const stat = chosenExists ? fs.statSync(chosenPath) : null;
|
|
272
|
-
return {
|
|
273
|
-
alias: candidate.alias,
|
|
274
|
-
semantic_type: candidate.semantic_type,
|
|
275
|
-
scope: candidate.scope,
|
|
276
|
-
primary_path: candidate.primary || null,
|
|
277
|
-
primary_exists: primaryExists,
|
|
278
|
-
fallback_path: candidate.fallback || null,
|
|
279
|
-
fallback_exists: fallbackExists,
|
|
280
|
-
path: chosenPath,
|
|
281
|
-
exists: chosenExists,
|
|
282
|
-
using_fallback: !primaryExists && fallbackExists,
|
|
283
|
-
hash: chosenExists ? sha256Text(chosenText) : null,
|
|
284
|
-
primary_hash: primaryHash,
|
|
285
|
-
fallback_hash: fallbackHash,
|
|
286
|
-
updated_at: stat ? stat.mtime.toISOString() : null,
|
|
287
|
-
age_hours: stat ? hoursSince(stat.mtime.toISOString()) : null,
|
|
288
|
-
size_bytes: stat ? stat.size : 0,
|
|
289
|
-
summary: chosenExists ? summarizeText(chosenText) : '',
|
|
290
|
-
conflict: Boolean(primaryExists && fallbackExists && primaryHash && fallbackHash && primaryHash !== fallbackHash),
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function buildProjectSummary(projectRoot, activeSession, options = {}) {
|
|
295
|
-
const base = health.oxePaths(projectRoot);
|
|
296
|
-
const codebaseDir = base.codebase;
|
|
297
|
-
const overview = summarizeText(readTextIfExists(path.join(codebaseDir, 'OVERVIEW.md')) || '');
|
|
298
|
-
const stack = summarizeText(readTextIfExists(path.join(codebaseDir, 'STACK.md')) || '');
|
|
299
|
-
const concerns = summarizeText(readTextIfExists(path.join(codebaseDir, 'CONCERNS.md')) || '');
|
|
300
|
-
const lessons = summarizeText(readTextIfExists(base.globalLessons) || '');
|
|
301
|
-
const stateText = readTextIfExists(base.state) || '';
|
|
302
|
-
const payload = {
|
|
303
|
-
summary_type: 'project',
|
|
304
|
-
generated_at: new Date().toISOString(),
|
|
305
|
-
project_root: path.resolve(projectRoot),
|
|
306
|
-
phase: health.parseStatePhase(stateText),
|
|
307
|
-
active_session: activeSession || health.parseActiveSession(stateText),
|
|
308
|
-
overview,
|
|
309
|
-
stack,
|
|
310
|
-
concerns,
|
|
311
|
-
lessons,
|
|
312
|
-
};
|
|
313
|
-
const md =
|
|
314
|
-
'# OXE Context Summary — Project\n\n' +
|
|
315
|
-
`- **Gerado em:** ${payload.generated_at}\n` +
|
|
316
|
-
`- **Fase:** ${payload.phase || '—'}\n` +
|
|
317
|
-
`- **Sessão ativa:** ${payload.active_session || 'modo legado'}\n\n` +
|
|
318
|
-
'## Overview\n\n' +
|
|
319
|
-
`${overview || '—'}\n\n` +
|
|
320
|
-
'## Stack\n\n' +
|
|
321
|
-
`${stack || '—'}\n\n` +
|
|
322
|
-
'## Concerns\n\n' +
|
|
323
|
-
`${concerns || '—'}\n\n` +
|
|
324
|
-
'## Lessons\n\n' +
|
|
325
|
-
`${lessons || '—'}\n`;
|
|
326
|
-
if (options.write !== false) {
|
|
327
|
-
const ctx = contextPaths(projectRoot, activeSession);
|
|
328
|
-
try {
|
|
329
|
-
writeJson(ctx.projectSummaryJson, payload);
|
|
330
|
-
ensureDir(path.dirname(ctx.projectSummaryMd));
|
|
331
|
-
fs.writeFileSync(ctx.projectSummaryMd, md, 'utf8');
|
|
332
|
-
} catch (err) {
|
|
333
|
-
throw new Error(`buildProjectSummary: ${err instanceof Error ? err.message : String(err)}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return { json: payload, markdown: md };
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function buildSessionSummary(projectRoot, activeSession, options = {}) {
|
|
340
|
-
if (!activeSession) return null;
|
|
341
|
-
const scoped = health.scopedOxePaths(projectRoot, activeSession);
|
|
342
|
-
const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
|
|
343
|
-
const sessionText = readTextIfExists(scoped.sessionManifest) || '';
|
|
344
|
-
const spec = summarizeText(readTextIfExists(scoped.spec) || '');
|
|
345
|
-
const plan = summarizeText(readTextIfExists(scoped.plan) || '');
|
|
346
|
-
const runtime = summarizeText(readTextIfExists(scoped.runtime) || '');
|
|
347
|
-
const verify = summarizeText(readTextIfExists(scoped.verify) || '');
|
|
348
|
-
const payload = {
|
|
349
|
-
summary_type: 'session',
|
|
350
|
-
generated_at: new Date().toISOString(),
|
|
351
|
-
session: activeSession,
|
|
352
|
-
phase: health.parseStatePhase(stateText),
|
|
353
|
-
session_manifest: summarizeText(sessionText),
|
|
354
|
-
spec,
|
|
355
|
-
plan,
|
|
356
|
-
runtime,
|
|
357
|
-
verify,
|
|
358
|
-
};
|
|
359
|
-
const md =
|
|
360
|
-
'# OXE Context Summary — Session\n\n' +
|
|
361
|
-
`- **Gerado em:** ${payload.generated_at}\n` +
|
|
362
|
-
`- **Sessão:** ${payload.session}\n` +
|
|
363
|
-
`- **Fase:** ${payload.phase || '—'}\n\n` +
|
|
364
|
-
'## Session Manifest\n\n' +
|
|
365
|
-
`${payload.session_manifest || '—'}\n\n` +
|
|
366
|
-
'## SPEC\n\n' +
|
|
367
|
-
`${spec || '—'}\n\n` +
|
|
368
|
-
'## PLAN\n\n' +
|
|
369
|
-
`${plan || '—'}\n\n` +
|
|
370
|
-
'## Runtime\n\n' +
|
|
371
|
-
`${runtime || '—'}\n\n` +
|
|
372
|
-
'## VERIFY\n\n' +
|
|
373
|
-
`${verify || '—'}\n`;
|
|
374
|
-
if (options.write !== false) {
|
|
375
|
-
const ctx = contextPaths(projectRoot, activeSession);
|
|
376
|
-
try {
|
|
377
|
-
if (ctx.sessionSummaryJson) writeJson(ctx.sessionSummaryJson, payload);
|
|
378
|
-
if (ctx.sessionSummaryMd) {
|
|
379
|
-
ensureDir(path.dirname(ctx.sessionSummaryMd));
|
|
380
|
-
fs.writeFileSync(ctx.sessionSummaryMd, md, 'utf8');
|
|
381
|
-
}
|
|
382
|
-
} catch (err) {
|
|
383
|
-
throw new Error(`buildSessionSummary: ${err instanceof Error ? err.message : String(err)}`);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return { json: payload, markdown: md };
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function buildPhaseSummary(projectRoot, activeSession, options = {}) {
|
|
390
|
-
const base = health.oxePaths(projectRoot);
|
|
391
|
-
const stateText = readTextIfExists(base.state) || '';
|
|
392
|
-
const payload = {
|
|
393
|
-
summary_type: 'phase',
|
|
394
|
-
generated_at: new Date().toISOString(),
|
|
395
|
-
phase: health.parseStatePhase(stateText),
|
|
396
|
-
active_session: activeSession || health.parseActiveSession(stateText),
|
|
397
|
-
next_step: firstNonEmpty([
|
|
398
|
-
summarizeText(readTextIfExists(base.state) || '', 220, 8),
|
|
399
|
-
]),
|
|
400
|
-
runtime_status: firstMatch(stateText, /\*\*runtime_status:\*\*\s*([^\n]+)/i),
|
|
401
|
-
plan_review_status: health.parsePlanReviewStatus(stateText),
|
|
402
|
-
};
|
|
403
|
-
const md =
|
|
404
|
-
'# OXE Context Summary — Phase\n\n' +
|
|
405
|
-
`- **Gerado em:** ${payload.generated_at}\n` +
|
|
406
|
-
`- **Fase:** ${payload.phase || '—'}\n` +
|
|
407
|
-
`- **Sessão ativa:** ${payload.active_session || 'modo legado'}\n` +
|
|
408
|
-
`- **runtime_status:** ${payload.runtime_status || '—'}\n` +
|
|
409
|
-
`- **plan_review_status:** ${payload.plan_review_status || '—'}\n\n` +
|
|
410
|
-
'## Snapshot\n\n' +
|
|
411
|
-
`${payload.next_step || '—'}\n`;
|
|
412
|
-
if (options.write !== false) {
|
|
413
|
-
const ctx = contextPaths(projectRoot, activeSession);
|
|
414
|
-
try {
|
|
415
|
-
writeJson(ctx.phaseSummaryJson, payload);
|
|
416
|
-
ensureDir(path.dirname(ctx.phaseSummaryMd));
|
|
417
|
-
fs.writeFileSync(ctx.phaseSummaryMd, md, 'utf8');
|
|
418
|
-
} catch (err) {
|
|
419
|
-
throw new Error(`buildPhaseSummary: ${err instanceof Error ? err.message : String(err)}`);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return { json: payload, markdown: md };
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function firstMatch(text, regex) {
|
|
426
|
-
const match = String(text || '').match(regex);
|
|
427
|
-
return match ? match[1].trim() : null;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function firstNonEmpty(values) {
|
|
431
|
-
for (const value of values || []) {
|
|
432
|
-
if (value && String(value).trim()) return String(value).trim();
|
|
433
|
-
}
|
|
434
|
-
return null;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function buildContextIndex(projectRoot, activeSession, options = {}) {
|
|
438
|
-
const writeOpt = { write: options.write !== false };
|
|
439
|
-
const summaryErrors = [];
|
|
440
|
-
for (const [label, fn, args] of [
|
|
441
|
-
['project', buildProjectSummary, [projectRoot, activeSession, writeOpt]],
|
|
442
|
-
['session', buildSessionSummary, [projectRoot, activeSession, writeOpt]],
|
|
443
|
-
['phase', buildPhaseSummary, [projectRoot, activeSession, writeOpt]],
|
|
444
|
-
]) {
|
|
445
|
-
try {
|
|
446
|
-
fn(...args);
|
|
447
|
-
} catch (err) {
|
|
448
|
-
summaryErrors.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
if (summaryErrors.length) {
|
|
452
|
-
process.stderr.write(`[oxe] WARN buildContextIndex — falha ao escrever summaries: ${summaryErrors.join('; ')}\n`);
|
|
453
|
-
}
|
|
454
|
-
const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
|
|
455
|
-
const resolvedSession = activeSession === undefined ? health.parseActiveSession(stateText) : activeSession;
|
|
456
|
-
const candidates = resolveArtifactCandidates(projectRoot, resolvedSession || null);
|
|
457
|
-
const artifacts = Object.keys(candidates)
|
|
458
|
-
.sort()
|
|
459
|
-
.map((alias) => buildArtifactRecord(candidates[alias]));
|
|
460
|
-
const payload = {
|
|
461
|
-
schema_version: 1,
|
|
462
|
-
generated_at: new Date().toISOString(),
|
|
463
|
-
project_root: path.resolve(projectRoot),
|
|
464
|
-
active_session: resolvedSession || null,
|
|
465
|
-
phase: health.parseStatePhase(stateText),
|
|
466
|
-
artifacts,
|
|
467
|
-
stats: {
|
|
468
|
-
total: artifacts.length,
|
|
469
|
-
existing: artifacts.filter((item) => item.exists).length,
|
|
470
|
-
missing: artifacts.filter((item) => !item.exists).length,
|
|
471
|
-
conflicts: artifacts.filter((item) => item.conflict).length,
|
|
472
|
-
},
|
|
473
|
-
};
|
|
474
|
-
if (options.write !== false) {
|
|
475
|
-
const ctx = contextPaths(projectRoot, resolvedSession || null);
|
|
476
|
-
ensureDir(ctx.root);
|
|
477
|
-
writeJson(ctx.index, payload);
|
|
478
|
-
}
|
|
479
|
-
return payload;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function computePackFreshness(pack, contract) {
|
|
483
|
-
const generatedAt = pack.generated_at || null;
|
|
484
|
-
const sourceTimes = (pack.selected_artifacts || [])
|
|
485
|
-
.map((artifact) => artifact.updated_at)
|
|
486
|
-
.filter(Boolean)
|
|
487
|
-
.map((value) => Date.parse(String(value)))
|
|
488
|
-
.filter((value) => !Number.isNaN(value));
|
|
489
|
-
const latestSource = sourceTimes.length ? new Date(Math.max(...sourceTimes)).toISOString() : null;
|
|
490
|
-
const generatedMs = generatedAt ? Date.parse(String(generatedAt)) : Number.NaN;
|
|
491
|
-
const latestSourceMs = latestSource ? Date.parse(latestSource) : Number.NaN;
|
|
492
|
-
const packAgeHours = hoursSince(generatedAt);
|
|
493
|
-
const maxPackAgeHours = contract && contract.freshness_policy && contract.freshness_policy.pack_max_age_hours != null
|
|
494
|
-
? Number(contract.freshness_policy.pack_max_age_hours)
|
|
495
|
-
: 12;
|
|
496
|
-
const staleByAge = packAgeHours != null && maxPackAgeHours > 0 ? packAgeHours > maxPackAgeHours : false;
|
|
497
|
-
const staleBySource = !Number.isNaN(generatedMs) && !Number.isNaN(latestSourceMs) ? generatedMs < latestSourceMs : false;
|
|
498
|
-
return {
|
|
499
|
-
generated_at: generatedAt,
|
|
500
|
-
latest_source_at: latestSource,
|
|
501
|
-
pack_age_hours: packAgeHours,
|
|
502
|
-
max_pack_age_hours: maxPackAgeHours,
|
|
503
|
-
stale: staleByAge || staleBySource || Boolean(pack.fallback_required),
|
|
504
|
-
reason: staleBySource ? 'source_newer_than_pack' : staleByAge ? 'pack_age_exceeded' : pack.fallback_required ? 'fallback_required' : 'fresh',
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function computeContextQuality(pack) {
|
|
509
|
-
const requiredMissing = (pack.gaps || []).filter((gap) => gap.severity === 'critical').length;
|
|
510
|
-
const optionalMissing = (pack.gaps || []).filter((gap) => gap.severity !== 'critical').length;
|
|
511
|
-
const conflicts = (pack.conflicts || []).length;
|
|
512
|
-
const fallbackCount = (pack.selected_artifacts || []).filter((artifact) => artifact.using_fallback).length;
|
|
513
|
-
let score = 100;
|
|
514
|
-
score -= requiredMissing * 25;
|
|
515
|
-
score -= optionalMissing * 5;
|
|
516
|
-
score -= conflicts * 12;
|
|
517
|
-
score -= fallbackCount * 6;
|
|
518
|
-
if ((pack.selected_artifacts || []).length === 0) score -= 40;
|
|
519
|
-
score = Math.max(0, Math.min(100, score));
|
|
520
|
-
const status = score >= 85 ? 'excellent' : score >= 70 ? 'good' : score >= 50 ? 'fragile' : 'critical';
|
|
521
|
-
return {
|
|
522
|
-
score,
|
|
523
|
-
status,
|
|
524
|
-
requiredMissing,
|
|
525
|
-
optionalMissing,
|
|
526
|
-
conflicts,
|
|
527
|
-
fallbackCount,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function renderPackMarkdown(pack) {
|
|
532
|
-
const selected = (pack.selected_artifacts || [])
|
|
533
|
-
.map((artifact) => `- **${artifact.alias}** (${artifact.scope}) -> ${artifact.exists ? artifact.path : 'ausente'}${artifact.using_fallback ? ' [fallback]' : ''}`)
|
|
534
|
-
.join('\n') || '- Nenhum artefato selecionado';
|
|
535
|
-
const gaps = (pack.gaps || [])
|
|
536
|
-
.map((gap) => `- [${gap.severity}] ${gap.alias}: ${gap.reason}`)
|
|
537
|
-
.join('\n') || '- Nenhuma lacuna';
|
|
538
|
-
const conflicts = (pack.conflicts || [])
|
|
539
|
-
.map((conflict) => `- ${conflict.alias}: ${conflict.reason}`)
|
|
540
|
-
.join('\n') || '- Nenhum conflito';
|
|
541
|
-
const sections = (pack.contract && pack.contract.output_sections || []).join(' · ') || '—';
|
|
542
|
-
return (
|
|
543
|
-
`# OXE Context Pack — ${pack.workflow}\n\n` +
|
|
544
|
-
`- **Gerado em:** ${pack.generated_at}\n` +
|
|
545
|
-
`- **Sessão ativa:** ${pack.active_session || 'modo legado'}\n` +
|
|
546
|
-
`- **Tier:** ${pack.context_tier}\n` +
|
|
547
|
-
`- **Semantics hash:** \`${pack.semantics_hash}\`\n` +
|
|
548
|
-
`- **Quality score:** ${pack.context_quality.score}\n` +
|
|
549
|
-
`- **Fallback required:** ${pack.fallback_required ? 'sim' : 'não'}\n\n` +
|
|
550
|
-
'## Read Order\n\n' +
|
|
551
|
-
`${(pack.read_order || []).map((alias) => `- ${alias}`).join('\n') || '- Nenhuma ordem resolvida'}\n\n` +
|
|
552
|
-
'## Selected Artifacts\n\n' +
|
|
553
|
-
`${selected}\n\n` +
|
|
554
|
-
'## Gaps\n\n' +
|
|
555
|
-
`${gaps}\n\n` +
|
|
556
|
-
'## Conflicts\n\n' +
|
|
557
|
-
`${conflicts}\n\n` +
|
|
558
|
-
'## Output Contract\n\n' +
|
|
559
|
-
`${sections}\n`
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* Extrai o vetor de confiança de um PLAN.md (bloco <confidence_vector>).
|
|
565
|
-
* @param {string} planText
|
|
566
|
-
* @returns {{ cycle: string|null, generated_at: string|null, dimensions: Array<{ name: string, score: number, weight: number, note: string }>, global: { score: number, gate: string } } | null}
|
|
567
|
-
*/
|
|
568
|
-
function parseConfidenceVector(planText) {
|
|
569
|
-
const text = String(planText || '');
|
|
570
|
-
const blockMatch = text.match(/<confidence_vector\s+([^>]*)>([\s\S]*?)<\/confidence_vector>/i);
|
|
571
|
-
if (!blockMatch) return null;
|
|
572
|
-
|
|
573
|
-
const attrs = blockMatch[1];
|
|
574
|
-
const body = blockMatch[2];
|
|
575
|
-
|
|
576
|
-
const cycle = (attrs.match(/\bcycle=["']([^"']+)["']/) || [])[1] || null;
|
|
577
|
-
const generated_at = (attrs.match(/\bgenerated_at=["']([^"']+)["']/) || [])[1] || null;
|
|
578
|
-
|
|
579
|
-
// Extrair dimensões: <dim name="..." score="..." weight="..." note="..." />
|
|
580
|
-
const dimensions = [];
|
|
581
|
-
const dimPattern = /<dim\s+([^/]*)\s*\/>/gi;
|
|
582
|
-
let m;
|
|
583
|
-
while ((m = dimPattern.exec(body)) !== null) {
|
|
584
|
-
const dAttrs = m[1];
|
|
585
|
-
const name = (dAttrs.match(/\bname=["']([^"']+)["']/) || [])[1] || '';
|
|
586
|
-
const score = parseFloat((dAttrs.match(/\bscore=["']([^"']+)["']/) || [])[1] || '0');
|
|
587
|
-
const weight = parseFloat((dAttrs.match(/\bweight=["']([^"']+)["']/) || [])[1] || '0');
|
|
588
|
-
const note = (dAttrs.match(/\bnote=["']([^"']+)["']/) || [])[1] || '';
|
|
589
|
-
if (name) dimensions.push({ name, score: isNaN(score) ? 0 : score, weight: isNaN(weight) ? 0 : weight, note });
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// Extrair global: <global score="..." gate="..." />
|
|
593
|
-
const globalMatch = body.match(/<global\s+([^/]*)\s*\/>/i);
|
|
594
|
-
const globalAttrs = globalMatch ? globalMatch[1] : '';
|
|
595
|
-
const globalScore = parseFloat((globalAttrs.match(/\bscore=["']([^"']+)["']/) || [])[1] || '0');
|
|
596
|
-
const gate = (globalAttrs.match(/\bgate=["']([^"']+)["']/) || [])[1] || 'proceed_with_risk';
|
|
597
|
-
|
|
598
|
-
// Se não há global explícito, calcular como média ponderada
|
|
599
|
-
let computedScore = globalScore;
|
|
600
|
-
if (!globalMatch && dimensions.length > 0) {
|
|
601
|
-
const totalWeight = dimensions.reduce((s, d) => s + d.weight, 0);
|
|
602
|
-
computedScore = totalWeight > 0
|
|
603
|
-
? dimensions.reduce((s, d) => s + d.score * d.weight, 0) / totalWeight
|
|
604
|
-
: 0;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
return {
|
|
608
|
-
cycle,
|
|
609
|
-
generated_at,
|
|
610
|
-
dimensions,
|
|
611
|
-
global: { score: isNaN(computedScore) ? 0 : Math.round(computedScore * 100) / 100, gate },
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/**
|
|
616
|
-
* Extrai hipóteses críticas de um PLAN.md.
|
|
617
|
-
* Suporta tags XML (<hypothesis ...>) e fallback para tabela Markdown.
|
|
618
|
-
* @param {string} planText
|
|
619
|
-
* @returns {Array<{ id: string, condition: string, validation: string, on_failure: string, checkpoint: string|null, status: string }>}
|
|
620
|
-
*/
|
|
621
|
-
function parseHypotheses(planText) {
|
|
622
|
-
const text = String(planText || '');
|
|
623
|
-
const results = [];
|
|
624
|
-
|
|
625
|
-
// Formato 1: tags XML
|
|
626
|
-
const xmlPattern = /<hypothesis\s+([^>]*)>([\s\S]*?)<\/hypothesis>/gi;
|
|
627
|
-
let m;
|
|
628
|
-
while ((m = xmlPattern.exec(text)) !== null) {
|
|
629
|
-
const attrs = m[1];
|
|
630
|
-
const body = m[2];
|
|
631
|
-
const id = (attrs.match(/\bid=["']([^"']+)["']/) || [])[1] || '';
|
|
632
|
-
const checkpoint = (attrs.match(/\bcheckpoint=["']([^"']+)["']/) || [])[1] || null;
|
|
633
|
-
const status = (attrs.match(/\bstatus=["']([^"']+)["']/) || [])[1] || 'pending';
|
|
634
|
-
const condition = (body.match(/<condition>([\s\S]*?)<\/condition>/) || [])[1]?.trim() || '';
|
|
635
|
-
const validation = (body.match(/<validation>([\s\S]*?)<\/validation>/) || [])[1]?.trim() || '';
|
|
636
|
-
const on_failure = (body.match(/<on_failure>([\s\S]*?)<\/on_failure>/) || [])[1]?.trim() || '';
|
|
637
|
-
if (id) results.push({ id, condition, validation, on_failure, checkpoint, status });
|
|
638
|
-
}
|
|
639
|
-
if (results.length > 0) return results;
|
|
640
|
-
|
|
641
|
-
// Formato 2: tabela Markdown (fallback)
|
|
642
|
-
// Encontrar a seção e extrair linhas até o próximo heading
|
|
643
|
-
const sectionIdx = text.search(/##\s*Hip.teses\s*Cr.ticas/im);
|
|
644
|
-
if (sectionIdx !== -1) {
|
|
645
|
-
const afterSection = text.slice(sectionIdx);
|
|
646
|
-
// Parar no próximo heading ## ou # (excluindo o próprio)
|
|
647
|
-
const nextHeadingMatch = afterSection.slice(3).match(/\n#{1,3} /);
|
|
648
|
-
const sectionText = nextHeadingMatch
|
|
649
|
-
? afterSection.slice(0, nextHeadingMatch.index + 3 + 1)
|
|
650
|
-
: afterSection;
|
|
651
|
-
const rows = sectionText.split('\n').filter((l) => l.trimStart().startsWith('|'));
|
|
652
|
-
for (const row of rows) {
|
|
653
|
-
const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
|
|
654
|
-
if (cells.length >= 2 && /^H\d+$/i.test(cells[0])) {
|
|
655
|
-
results.push({
|
|
656
|
-
id: cells[0],
|
|
657
|
-
condition: cells[1] || '',
|
|
658
|
-
validation: cells[2] || '',
|
|
659
|
-
on_failure: cells[3] || '',
|
|
660
|
-
checkpoint: cells[4] || null,
|
|
661
|
-
status: cells[5] || 'pending',
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
return results;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
function buildContextPack(projectRoot, input = {}) {
|
|
670
|
-
const workflow = String(input.workflow || '').trim();
|
|
671
|
-
if (!workflow) {
|
|
672
|
-
throw new Error('workflow é obrigatório para buildContextPack');
|
|
673
|
-
}
|
|
674
|
-
const contract = runtimeSemantics.getWorkflowContract(workflow);
|
|
675
|
-
if (!contract) {
|
|
676
|
-
throw new Error(`Workflow sem contrato canónico: ${workflow}`);
|
|
677
|
-
}
|
|
678
|
-
const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
|
|
679
|
-
const activeSession = input.activeSession === undefined ? health.parseActiveSession(stateText) : input.activeSession;
|
|
680
|
-
const tier = ['minimal', 'standard', 'full'].includes(String(input.tier || 'standard'))
|
|
681
|
-
? String(input.tier || 'standard')
|
|
682
|
-
: 'standard';
|
|
683
|
-
const mode = String(input.mode || 'standard');
|
|
684
|
-
const ctx = contextPaths(projectRoot, activeSession || null);
|
|
685
|
-
const index = buildContextIndex(projectRoot, activeSession || null, { write: input.write !== false });
|
|
686
|
-
const byAlias = new Map((index.artifacts || []).map((artifact) => [artifact.alias, artifact]));
|
|
687
|
-
|
|
688
|
-
// Modo auditor: usa auditor_artifacts do contrato e exclui auditor_excluded
|
|
689
|
-
let selectedAliases;
|
|
690
|
-
if (mode === 'auditor' && contract.auditor_artifacts && contract.auditor_artifacts.length > 0) {
|
|
691
|
-
const excluded = new Set(contract.auditor_excluded || []);
|
|
692
|
-
selectedAliases = Array.from(
|
|
693
|
-
new Set(contract.auditor_artifacts.filter((a) => !excluded.has(a)))
|
|
694
|
-
);
|
|
695
|
-
} else {
|
|
696
|
-
selectedAliases = Array.from(
|
|
697
|
-
new Set(['state', ...(contract.context_tiers[tier] || contract.context_tiers.standard || [])])
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
const intent = String(contract.extraction_intent || 'status_read');
|
|
701
|
-
const selectedArtifacts = selectedAliases.map((alias) => {
|
|
702
|
-
const artifact = byAlias.get(alias);
|
|
703
|
-
if (artifact) {
|
|
704
|
-
const rawText = artifact.exists ? readTextIfExists(artifact.path) || '' : '';
|
|
705
|
-
const semanticSummary = rawText
|
|
706
|
-
? extractSemanticFragment(rawText, { intent })
|
|
707
|
-
: '';
|
|
708
|
-
return {
|
|
709
|
-
...artifact,
|
|
710
|
-
required: contract.required_artifacts.includes(alias),
|
|
711
|
-
selected_because: contract.required_artifacts.includes(alias) ? 'required_artifact' : 'context_tier',
|
|
712
|
-
semantic_summary: semanticSummary,
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
return {
|
|
716
|
-
alias,
|
|
717
|
-
path: null,
|
|
718
|
-
exists: false,
|
|
719
|
-
scope: 'unknown',
|
|
720
|
-
semantic_type: 'unknown',
|
|
721
|
-
using_fallback: false,
|
|
722
|
-
required: contract.required_artifacts.includes(alias),
|
|
723
|
-
selected_because: contract.required_artifacts.includes(alias) ? 'required_artifact' : 'context_tier',
|
|
724
|
-
updated_at: null,
|
|
725
|
-
age_hours: null,
|
|
726
|
-
hash: null,
|
|
727
|
-
summary: '',
|
|
728
|
-
semantic_summary: '',
|
|
729
|
-
};
|
|
730
|
-
});
|
|
731
|
-
const gaps = [];
|
|
732
|
-
for (const artifact of selectedArtifacts) {
|
|
733
|
-
if (!artifact.exists) {
|
|
734
|
-
gaps.push({
|
|
735
|
-
alias: artifact.alias,
|
|
736
|
-
severity: artifact.required ? 'critical' : 'warning',
|
|
737
|
-
reason: artifact.required ? 'required_artifact_missing' : 'selected_artifact_missing',
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
const conflicts = selectedArtifacts
|
|
742
|
-
.filter((artifact) => artifact.conflict)
|
|
743
|
-
.map((artifact) => ({
|
|
744
|
-
alias: artifact.alias,
|
|
745
|
-
reason: 'session_and_root_artifacts_diverge',
|
|
746
|
-
primary_path: artifact.primary_path,
|
|
747
|
-
fallback_path: artifact.fallback_path,
|
|
748
|
-
}));
|
|
749
|
-
const pack = {
|
|
750
|
-
schema_version: 1,
|
|
751
|
-
workflow,
|
|
752
|
-
mode,
|
|
753
|
-
active_session: activeSession || null,
|
|
754
|
-
context_tier: tier,
|
|
755
|
-
generated_at: new Date().toISOString(),
|
|
756
|
-
semantics_hash: runtimeSemantics.computeSemanticsHash(workflow),
|
|
757
|
-
contract,
|
|
758
|
-
read_order: selectedArtifacts.filter((artifact) => artifact.exists).map((artifact) => artifact.alias),
|
|
759
|
-
selected_artifacts: selectedArtifacts,
|
|
760
|
-
gaps,
|
|
761
|
-
conflicts,
|
|
762
|
-
fallback_required: gaps.some((gap) => gap.severity === 'critical') || selectedArtifacts.some((artifact) => artifact.using_fallback),
|
|
763
|
-
summaries: {
|
|
764
|
-
project: ctx.projectSummaryJson,
|
|
765
|
-
session: ctx.sessionSummaryJson,
|
|
766
|
-
phase: ctx.phaseSummaryJson,
|
|
767
|
-
},
|
|
768
|
-
};
|
|
769
|
-
// Extrair hipóteses críticas do PLAN.md se disponível no pack
|
|
770
|
-
const planArtifact = selectedArtifacts.find((a) => a.alias === 'plan' && a.exists);
|
|
771
|
-
const hypotheses = planArtifact ? parseHypotheses(readTextIfExists(planArtifact.path) || '') : [];
|
|
772
|
-
pack.context_quality = computeContextQuality(pack);
|
|
773
|
-
pack.freshness = computePackFreshness(pack, contract);
|
|
774
|
-
pack.hypotheses = hypotheses;
|
|
775
|
-
pack.markdown = renderPackMarkdown(pack);
|
|
776
|
-
if (input.write !== false) {
|
|
777
|
-
try {
|
|
778
|
-
ensureDir(ctx.packsDir);
|
|
779
|
-
const defaultJson = ctx.defaultPackJson(workflow);
|
|
780
|
-
const defaultMd = ctx.defaultPackMd(workflow);
|
|
781
|
-
writeJson(defaultJson, pack);
|
|
782
|
-
fs.writeFileSync(defaultMd, pack.markdown, 'utf8');
|
|
783
|
-
if (ctx.sessionPackJson(workflow)) {
|
|
784
|
-
writeJson(ctx.sessionPackJson(workflow), pack);
|
|
785
|
-
}
|
|
786
|
-
if (ctx.sessionPackMd(workflow)) {
|
|
787
|
-
fs.writeFileSync(ctx.sessionPackMd(workflow), pack.markdown, 'utf8');
|
|
788
|
-
}
|
|
789
|
-
} catch (err) {
|
|
790
|
-
throw new Error(`buildContextPack (${workflow}): falha ao persistir pack — ${err instanceof Error ? err.message : String(err)}`);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
return pack;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
function resolvePackFile(projectRoot, workflow, activeSession) {
|
|
797
|
-
const ctx = contextPaths(projectRoot, activeSession);
|
|
798
|
-
const candidates = [ctx.sessionPackJson(workflow), ctx.defaultPackJson(workflow)].filter(Boolean);
|
|
799
|
-
for (const filePath of candidates) {
|
|
800
|
-
if (fs.existsSync(filePath)) return filePath;
|
|
801
|
-
}
|
|
802
|
-
return ctx.defaultPackJson(workflow);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
function inspectContextPack(projectRoot, input = {}) {
|
|
806
|
-
const workflow = String(input.workflow || '').trim();
|
|
807
|
-
if (!workflow) {
|
|
808
|
-
throw new Error('workflow é obrigatório para inspectContextPack');
|
|
809
|
-
}
|
|
810
|
-
const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
|
|
811
|
-
const activeSession = input.activeSession === undefined ? health.parseActiveSession(stateText) : input.activeSession;
|
|
812
|
-
const filePath = resolvePackFile(projectRoot, workflow, activeSession || null);
|
|
813
|
-
let pack = null;
|
|
814
|
-
if (fs.existsSync(filePath)) {
|
|
815
|
-
try {
|
|
816
|
-
pack = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
817
|
-
} catch {
|
|
818
|
-
pack = null;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
if (!pack) {
|
|
822
|
-
pack = buildContextPack(projectRoot, {
|
|
823
|
-
workflow,
|
|
824
|
-
tier: input.tier || 'standard',
|
|
825
|
-
activeSession: activeSession || null,
|
|
826
|
-
write: false,
|
|
827
|
-
});
|
|
828
|
-
} else {
|
|
829
|
-
const contract = runtimeSemantics.getWorkflowContract(workflow);
|
|
830
|
-
pack.contract = contract;
|
|
831
|
-
pack.freshness = computePackFreshness(pack, contract);
|
|
832
|
-
pack.context_quality = computeContextQuality(pack);
|
|
833
|
-
}
|
|
834
|
-
pack.path = filePath;
|
|
835
|
-
return pack;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
function buildAllContextPacks(projectRoot, input = {}) {
|
|
839
|
-
const workflows = input.workflow
|
|
840
|
-
? [String(input.workflow)]
|
|
841
|
-
: runtimeSemantics.getAllWorkflowContracts().map((contract) => contract.workflow_slug);
|
|
842
|
-
return workflows.map((workflow) => buildContextPack(projectRoot, {
|
|
843
|
-
workflow,
|
|
844
|
-
tier: input.tier || 'standard',
|
|
845
|
-
activeSession: input.activeSession,
|
|
846
|
-
write: input.write !== false,
|
|
847
|
-
}));
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
module.exports = {
|
|
851
|
-
buildAllContextPacks,
|
|
852
|
-
buildContextIndex,
|
|
853
|
-
buildContextPack,
|
|
854
|
-
buildPhaseSummary,
|
|
855
|
-
buildProjectSummary,
|
|
856
|
-
buildSessionSummary,
|
|
857
|
-
computeContextQuality,
|
|
858
|
-
computePackFreshness,
|
|
859
|
-
contextPaths,
|
|
860
|
-
extractSemanticFragment,
|
|
861
|
-
inspectContextPack,
|
|
862
|
-
parseConfidenceVector,
|
|
863
|
-
parseHypotheses,
|
|
864
|
-
resolveArtifactCandidates,
|
|
865
|
-
resolvePackFile,
|
|
866
|
-
summarizeText,
|
|
867
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
const health = require('./oxe-project-health.cjs');
|
|
8
|
+
const operational = require('./oxe-operational.cjs');
|
|
9
|
+
const azure = require('./oxe-azure.cjs');
|
|
10
|
+
const runtimeSemantics = require('./oxe-runtime-semantics.cjs');
|
|
11
|
+
|
|
12
|
+
function ensureDir(dirPath) {
|
|
13
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readTextIfExists(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeJson(filePath, value) {
|
|
25
|
+
try {
|
|
26
|
+
ensureDir(path.dirname(filePath));
|
|
27
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
28
|
+
} catch (err) {
|
|
29
|
+
throw new Error(`Falha ao escrever ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sha256Text(text) {
|
|
34
|
+
return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sha256File(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
return sha256Text(fs.readFileSync(filePath, 'utf8'));
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toIso(value) {
|
|
46
|
+
if (!value) return null;
|
|
47
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
48
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function hoursSince(iso) {
|
|
52
|
+
if (!iso) return null;
|
|
53
|
+
const ms = Date.parse(String(iso));
|
|
54
|
+
if (Number.isNaN(ms)) return null;
|
|
55
|
+
return Math.floor((Date.now() - ms) / (1000 * 60 * 60));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function summarizeText(text, maxChars = 640, maxLines = 12) {
|
|
59
|
+
const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
|
|
60
|
+
if (!normalized) return '';
|
|
61
|
+
const lines = normalized
|
|
62
|
+
.split('\n')
|
|
63
|
+
.map((line) => line.trim())
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.slice(0, maxLines);
|
|
66
|
+
const joined = lines.join(' ');
|
|
67
|
+
return joined.length > maxChars ? `${joined.slice(0, maxChars - 1)}…` : joined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const INTENT_SECTION_KEYWORDS = {
|
|
71
|
+
execution_input: ['onda', 'tarefa', 'hipótese', 'hipotese', 'wave', 'task', 'bloqueio', 'checkpoint', 'execu'],
|
|
72
|
+
verification: ['gap', 'critério', 'criterio', 'finding', 'auditoria', 'resultado', 'evidência', 'evidencia', 'falha', 'lacuna'],
|
|
73
|
+
planning_input: ['objetivo', 'autoavaliação', 'autoavaliacao', 'confiança', 'confianca', 'plano', 'requisito', 'risco', 'hipótese', 'hipotese'],
|
|
74
|
+
critical_check: ['bloqueio', 'falha', 'gap', 'erro', 'crítico', 'critico', 'p0', 'p1'],
|
|
75
|
+
status_read: ['fase', 'estado', 'próximo', 'proximo', 'status', 'resumo', 'sessão', 'sessao', 'snapshot'],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const DEFAULT_PRESERVE_MARKERS = [
|
|
79
|
+
'P0', 'P1', 'bloqueado', 'BLOQUEADO', 'FALHA', 'crítico', 'CRÍTICO',
|
|
80
|
+
'gaps', 'GAPS', 'bloqueante', 'BLOQUEANTE', 'crítica', 'CRÍTICA',
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extração semântica orientada a intenção — preserva marcadores críticos independentemente
|
|
85
|
+
* de posição, prioriza seções relevantes ao workflow e preenche o orçamento restante
|
|
86
|
+
* com conteúdo adicional.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} text
|
|
89
|
+
* @param {{ intent?: string, maxChars?: number, preserveMarkers?: string[] }} [options]
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
function extractSemanticFragment(text, options = {}) {
|
|
93
|
+
const intent = String(options.intent || 'status_read');
|
|
94
|
+
const maxChars = Math.max(200, Number(options.maxChars) || 1200);
|
|
95
|
+
const preserveMarkers = Array.isArray(options.preserveMarkers)
|
|
96
|
+
? options.preserveMarkers
|
|
97
|
+
: DEFAULT_PRESERVE_MARKERS;
|
|
98
|
+
|
|
99
|
+
const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
|
|
100
|
+
if (!normalized) return '';
|
|
101
|
+
|
|
102
|
+
// Sem headings — fallback para summarizeText com limite maior
|
|
103
|
+
if (!normalized.includes('\n## ') && !normalized.startsWith('## ') &&
|
|
104
|
+
!normalized.includes('\n# ') && !normalized.startsWith('# ')) {
|
|
105
|
+
return summarizeText(normalized, maxChars, 20);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Parsear em seções delimitadas por headings (# ou ##)
|
|
109
|
+
const sections = [];
|
|
110
|
+
let current = { heading: '', lines: [] };
|
|
111
|
+
for (const line of normalized.split('\n')) {
|
|
112
|
+
if (/^#{1,3} /.test(line)) {
|
|
113
|
+
sections.push(current);
|
|
114
|
+
current = { heading: line, lines: [] };
|
|
115
|
+
} else {
|
|
116
|
+
current.lines.push(line);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
sections.push(current);
|
|
120
|
+
|
|
121
|
+
const kws = INTENT_SECTION_KEYWORDS[intent] || INTENT_SECTION_KEYWORDS.status_read;
|
|
122
|
+
|
|
123
|
+
// Pontuar cada seção: 3=crítica, 2=relevante ao intent, 1=primeira seção, 0=resto
|
|
124
|
+
const scored = sections.map((s, i) => {
|
|
125
|
+
const h = s.heading.toLowerCase();
|
|
126
|
+
const hasCritical = [s.heading, ...s.lines].some((l) =>
|
|
127
|
+
preserveMarkers.some((m) => l.includes(m))
|
|
128
|
+
);
|
|
129
|
+
const isRelevant = kws.some((k) => h.includes(k));
|
|
130
|
+
const relevance = hasCritical ? 3 : isRelevant ? 2 : i === 0 ? 1 : 0;
|
|
131
|
+
return { ...s, relevance, index: i };
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Ordenar: críticas primeiro, depois por intent, depois por posição original
|
|
135
|
+
const sorted = [...scored].sort((a, b) => {
|
|
136
|
+
if (b.relevance !== a.relevance) return b.relevance - a.relevance;
|
|
137
|
+
return a.index - b.index;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Preencher orçamento gulodamente
|
|
141
|
+
const parts = [];
|
|
142
|
+
let budget = maxChars;
|
|
143
|
+
|
|
144
|
+
for (const section of sorted) {
|
|
145
|
+
if (budget <= 0) break;
|
|
146
|
+
const chunk = [section.heading, ...section.lines.filter(Boolean)]
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.join('\n')
|
|
149
|
+
.trim();
|
|
150
|
+
if (!chunk) continue;
|
|
151
|
+
if (chunk.length <= budget) {
|
|
152
|
+
parts.push({ index: section.index, text: chunk });
|
|
153
|
+
budget -= chunk.length + 2;
|
|
154
|
+
} else if (budget > 80) {
|
|
155
|
+
parts.push({ index: section.index, text: `${chunk.slice(0, budget - 1)}…` });
|
|
156
|
+
budget = 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Reordenar na sequência original do documento
|
|
161
|
+
parts.sort((a, b) => a.index - b.index);
|
|
162
|
+
const result = parts.map((p) => p.text).join('\n\n').trim();
|
|
163
|
+
return result.length > maxChars ? `${result.slice(0, maxChars - 1)}…` : result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function sanitizeSession(activeSession) {
|
|
167
|
+
return String(activeSession || '')
|
|
168
|
+
.replace(/^sessions\//, '')
|
|
169
|
+
.replace(/[\\/]/g, '__')
|
|
170
|
+
.replace(/[^A-Za-z0-9._-]+/g, '-');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function contextPaths(projectRoot, activeSession) {
|
|
174
|
+
const base = health.oxePaths(projectRoot);
|
|
175
|
+
const stateText = readTextIfExists(base.state) || '';
|
|
176
|
+
const resolvedSession = activeSession === undefined ? health.parseActiveSession(stateText) : activeSession;
|
|
177
|
+
const sessionKey = sanitizeSession(resolvedSession);
|
|
178
|
+
const phase = health.parseStatePhase(stateText) || 'unknown';
|
|
179
|
+
const safePhase = String(phase).replace(/[^A-Za-z0-9._-]+/g, '-');
|
|
180
|
+
const root = path.join(base.oxe, 'context');
|
|
181
|
+
const packsDir = path.join(root, 'packs');
|
|
182
|
+
const summariesDir = path.join(root, 'summaries');
|
|
183
|
+
return {
|
|
184
|
+
root,
|
|
185
|
+
index: path.join(root, 'index.json'),
|
|
186
|
+
packsDir,
|
|
187
|
+
summariesDir,
|
|
188
|
+
projectSummaryJson: path.join(summariesDir, 'project.json'),
|
|
189
|
+
projectSummaryMd: path.join(summariesDir, 'project.md'),
|
|
190
|
+
sessionSummaryJson: resolvedSession ? path.join(summariesDir, `session-${sessionKey}.json`) : null,
|
|
191
|
+
sessionSummaryMd: resolvedSession ? path.join(summariesDir, `session-${sessionKey}.md`) : null,
|
|
192
|
+
phaseSummaryJson: path.join(summariesDir, `phase-${safePhase}.json`),
|
|
193
|
+
phaseSummaryMd: path.join(summariesDir, `phase-${safePhase}.md`),
|
|
194
|
+
defaultPackJson: (workflow) => path.join(packsDir, `${workflow}.json`),
|
|
195
|
+
defaultPackMd: (workflow) => path.join(packsDir, `${workflow}.md`),
|
|
196
|
+
sessionPackJson: (workflow) => resolvedSession ? path.join(packsDir, `session-${sessionKey}-${workflow}.json`) : null,
|
|
197
|
+
sessionPackMd: (workflow) => resolvedSession ? path.join(packsDir, `session-${sessionKey}-${workflow}.md`) : null,
|
|
198
|
+
activeSession: resolvedSession || null,
|
|
199
|
+
phase,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function inferScope(activeSession, preferSession) {
|
|
204
|
+
if (preferSession && activeSession) return 'session';
|
|
205
|
+
return 'project';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function resolveArtifactCandidates(projectRoot, activeSession) {
|
|
209
|
+
const base = health.oxePaths(projectRoot);
|
|
210
|
+
const scoped = health.scopedOxePaths(projectRoot, activeSession || null);
|
|
211
|
+
const ctx = contextPaths(projectRoot, activeSession);
|
|
212
|
+
const azurePaths = azure.azurePaths(projectRoot);
|
|
213
|
+
const preferSession = Boolean(activeSession);
|
|
214
|
+
const withFallback = (alias, semanticType, primaryPath, fallbackPath = null, scope = inferScope(activeSession, true)) => ({
|
|
215
|
+
alias,
|
|
216
|
+
semantic_type: semanticType,
|
|
217
|
+
scope,
|
|
218
|
+
primary: primaryPath,
|
|
219
|
+
fallback: fallbackPath && fallbackPath !== primaryPath ? fallbackPath : null,
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
state: withFallback('state', 'state', base.state, null, 'project'),
|
|
223
|
+
session_manifest: withFallback('session_manifest', 'session', scoped.sessionManifest || null, null, 'session'),
|
|
224
|
+
sessions_index: withFallback('sessions_index', 'session_index', base.sessionsIndex, null, 'project'),
|
|
225
|
+
execution_state: withFallback('execution_state', 'state', scoped.executionState || null, null, 'session'),
|
|
226
|
+
spec: withFallback('spec', 'spec', scoped.spec, base.spec),
|
|
227
|
+
discuss: withFallback('discuss', 'discuss', scoped.discuss, base.discuss),
|
|
228
|
+
plan: withFallback('plan', 'plan', scoped.plan, base.plan),
|
|
229
|
+
plan_agents: withFallback('plan_agents', 'plan_agents', base.planAgents, null, 'project'),
|
|
230
|
+
quick: withFallback('quick', 'plan', scoped.quick, base.quick),
|
|
231
|
+
runtime: withFallback('runtime', 'runtime', scoped.runtime, base.runtime),
|
|
232
|
+
checkpoints: withFallback('checkpoints', 'checkpoints', scoped.checkpoints, base.checkpoints),
|
|
233
|
+
verify: withFallback('verify', 'verify', scoped.verify, base.verify),
|
|
234
|
+
summary: withFallback('summary', 'summary', scoped.summary, base.summary),
|
|
235
|
+
plan_review: withFallback('plan_review', 'review', scoped.planReview, base.planReview),
|
|
236
|
+
review_comments: withFallback('review_comments', 'review_comments', scoped.planReviewComments, base.planReviewComments),
|
|
237
|
+
active_run: withFallback('active_run', 'runtime', operational.operationalPaths(projectRoot, activeSession || null).activeRun, null, preferSession ? 'session' : 'project'),
|
|
238
|
+
events: withFallback('events', 'trace', operational.operationalPaths(projectRoot, activeSession || null).events, null, preferSession ? 'session' : 'project'),
|
|
239
|
+
capabilities_index: withFallback('capabilities_index', 'capabilities', base.capabilitiesIndex, null, 'project'),
|
|
240
|
+
investigations_index: withFallback('investigations_index', 'investigations', scoped.investigationsIndex, base.investigationsIndex),
|
|
241
|
+
global_lessons: withFallback('global_lessons', 'memory', base.globalLessons, base.lessons, 'project'),
|
|
242
|
+
codebase_overview: withFallback('codebase_overview', 'codebase', path.join(base.codebase, 'OVERVIEW.md'), null, 'project'),
|
|
243
|
+
codebase_stack: withFallback('codebase_stack', 'codebase', path.join(base.codebase, 'STACK.md'), null, 'project'),
|
|
244
|
+
codebase_structure: withFallback('codebase_structure', 'codebase', path.join(base.codebase, 'STRUCTURE.md'), null, 'project'),
|
|
245
|
+
codebase_testing: withFallback('codebase_testing', 'codebase', path.join(base.codebase, 'TESTING.md'), null, 'project'),
|
|
246
|
+
codebase_integrations: withFallback('codebase_integrations', 'codebase', path.join(base.codebase, 'INTEGRATIONS.md'), null, 'project'),
|
|
247
|
+
codebase_concerns: withFallback('codebase_concerns', 'codebase', path.join(base.codebase, 'CONCERNS.md'), null, 'project'),
|
|
248
|
+
azure_inventory: withFallback('azure_inventory', 'provider', azurePaths.inventoryMd, null, 'project'),
|
|
249
|
+
azure_servicebus: withFallback('azure_servicebus', 'provider', azurePaths.serviceBusMd, null, 'project'),
|
|
250
|
+
azure_eventgrid: withFallback('azure_eventgrid', 'provider', azurePaths.eventGridMd, null, 'project'),
|
|
251
|
+
azure_sql: withFallback('azure_sql', 'provider', azurePaths.sqlMd, null, 'project'),
|
|
252
|
+
copilot_manifest: withFallback('copilot_manifest', 'install_manifest', base.copilotManifest, null, 'project'),
|
|
253
|
+
runtime_semantics_manifest: withFallback('runtime_semantics_manifest', 'install_manifest', path.join(base.installDir, 'runtime-semantics.json'), null, 'project'),
|
|
254
|
+
project_summary: withFallback('project_summary', 'summary', ctx.projectSummaryJson, null, 'project'),
|
|
255
|
+
session_summary: withFallback('session_summary', 'summary', ctx.sessionSummaryJson, null, 'session'),
|
|
256
|
+
phase_summary: withFallback('phase_summary', 'summary', ctx.phaseSummaryJson, null, 'project'),
|
|
257
|
+
context_pack_dashboard: withFallback('context_pack_dashboard', 'context_pack', ctx.defaultPackJson('dashboard'), null, 'project'),
|
|
258
|
+
calibration: withFallback('calibration', 'calibration', path.join(base.oxe, 'calibration.json'), null, 'project'),
|
|
259
|
+
lessons_metrics: withFallback('lessons_metrics', 'metrics', path.join(base.oxe, 'lessons-metrics.json'), null, 'project'),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function buildArtifactRecord(candidate) {
|
|
264
|
+
const primaryExists = Boolean(candidate.primary && fs.existsSync(candidate.primary));
|
|
265
|
+
const fallbackExists = Boolean(candidate.fallback && fs.existsSync(candidate.fallback));
|
|
266
|
+
const chosenPath = primaryExists ? candidate.primary : fallbackExists ? candidate.fallback : candidate.primary || candidate.fallback || null;
|
|
267
|
+
const chosenExists = Boolean(chosenPath && fs.existsSync(chosenPath));
|
|
268
|
+
const chosenText = chosenExists ? readTextIfExists(chosenPath) || '' : '';
|
|
269
|
+
const primaryHash = primaryExists ? sha256File(candidate.primary) : null;
|
|
270
|
+
const fallbackHash = fallbackExists ? sha256File(candidate.fallback) : null;
|
|
271
|
+
const stat = chosenExists ? fs.statSync(chosenPath) : null;
|
|
272
|
+
return {
|
|
273
|
+
alias: candidate.alias,
|
|
274
|
+
semantic_type: candidate.semantic_type,
|
|
275
|
+
scope: candidate.scope,
|
|
276
|
+
primary_path: candidate.primary || null,
|
|
277
|
+
primary_exists: primaryExists,
|
|
278
|
+
fallback_path: candidate.fallback || null,
|
|
279
|
+
fallback_exists: fallbackExists,
|
|
280
|
+
path: chosenPath,
|
|
281
|
+
exists: chosenExists,
|
|
282
|
+
using_fallback: !primaryExists && fallbackExists,
|
|
283
|
+
hash: chosenExists ? sha256Text(chosenText) : null,
|
|
284
|
+
primary_hash: primaryHash,
|
|
285
|
+
fallback_hash: fallbackHash,
|
|
286
|
+
updated_at: stat ? stat.mtime.toISOString() : null,
|
|
287
|
+
age_hours: stat ? hoursSince(stat.mtime.toISOString()) : null,
|
|
288
|
+
size_bytes: stat ? stat.size : 0,
|
|
289
|
+
summary: chosenExists ? summarizeText(chosenText) : '',
|
|
290
|
+
conflict: Boolean(primaryExists && fallbackExists && primaryHash && fallbackHash && primaryHash !== fallbackHash),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function buildProjectSummary(projectRoot, activeSession, options = {}) {
|
|
295
|
+
const base = health.oxePaths(projectRoot);
|
|
296
|
+
const codebaseDir = base.codebase;
|
|
297
|
+
const overview = summarizeText(readTextIfExists(path.join(codebaseDir, 'OVERVIEW.md')) || '');
|
|
298
|
+
const stack = summarizeText(readTextIfExists(path.join(codebaseDir, 'STACK.md')) || '');
|
|
299
|
+
const concerns = summarizeText(readTextIfExists(path.join(codebaseDir, 'CONCERNS.md')) || '');
|
|
300
|
+
const lessons = summarizeText(readTextIfExists(base.globalLessons) || '');
|
|
301
|
+
const stateText = readTextIfExists(base.state) || '';
|
|
302
|
+
const payload = {
|
|
303
|
+
summary_type: 'project',
|
|
304
|
+
generated_at: new Date().toISOString(),
|
|
305
|
+
project_root: path.resolve(projectRoot),
|
|
306
|
+
phase: health.parseStatePhase(stateText),
|
|
307
|
+
active_session: activeSession || health.parseActiveSession(stateText),
|
|
308
|
+
overview,
|
|
309
|
+
stack,
|
|
310
|
+
concerns,
|
|
311
|
+
lessons,
|
|
312
|
+
};
|
|
313
|
+
const md =
|
|
314
|
+
'# OXE Context Summary — Project\n\n' +
|
|
315
|
+
`- **Gerado em:** ${payload.generated_at}\n` +
|
|
316
|
+
`- **Fase:** ${payload.phase || '—'}\n` +
|
|
317
|
+
`- **Sessão ativa:** ${payload.active_session || 'modo legado'}\n\n` +
|
|
318
|
+
'## Overview\n\n' +
|
|
319
|
+
`${overview || '—'}\n\n` +
|
|
320
|
+
'## Stack\n\n' +
|
|
321
|
+
`${stack || '—'}\n\n` +
|
|
322
|
+
'## Concerns\n\n' +
|
|
323
|
+
`${concerns || '—'}\n\n` +
|
|
324
|
+
'## Lessons\n\n' +
|
|
325
|
+
`${lessons || '—'}\n`;
|
|
326
|
+
if (options.write !== false) {
|
|
327
|
+
const ctx = contextPaths(projectRoot, activeSession);
|
|
328
|
+
try {
|
|
329
|
+
writeJson(ctx.projectSummaryJson, payload);
|
|
330
|
+
ensureDir(path.dirname(ctx.projectSummaryMd));
|
|
331
|
+
fs.writeFileSync(ctx.projectSummaryMd, md, 'utf8');
|
|
332
|
+
} catch (err) {
|
|
333
|
+
throw new Error(`buildProjectSummary: ${err instanceof Error ? err.message : String(err)}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return { json: payload, markdown: md };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function buildSessionSummary(projectRoot, activeSession, options = {}) {
|
|
340
|
+
if (!activeSession) return null;
|
|
341
|
+
const scoped = health.scopedOxePaths(projectRoot, activeSession);
|
|
342
|
+
const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
|
|
343
|
+
const sessionText = readTextIfExists(scoped.sessionManifest) || '';
|
|
344
|
+
const spec = summarizeText(readTextIfExists(scoped.spec) || '');
|
|
345
|
+
const plan = summarizeText(readTextIfExists(scoped.plan) || '');
|
|
346
|
+
const runtime = summarizeText(readTextIfExists(scoped.runtime) || '');
|
|
347
|
+
const verify = summarizeText(readTextIfExists(scoped.verify) || '');
|
|
348
|
+
const payload = {
|
|
349
|
+
summary_type: 'session',
|
|
350
|
+
generated_at: new Date().toISOString(),
|
|
351
|
+
session: activeSession,
|
|
352
|
+
phase: health.parseStatePhase(stateText),
|
|
353
|
+
session_manifest: summarizeText(sessionText),
|
|
354
|
+
spec,
|
|
355
|
+
plan,
|
|
356
|
+
runtime,
|
|
357
|
+
verify,
|
|
358
|
+
};
|
|
359
|
+
const md =
|
|
360
|
+
'# OXE Context Summary — Session\n\n' +
|
|
361
|
+
`- **Gerado em:** ${payload.generated_at}\n` +
|
|
362
|
+
`- **Sessão:** ${payload.session}\n` +
|
|
363
|
+
`- **Fase:** ${payload.phase || '—'}\n\n` +
|
|
364
|
+
'## Session Manifest\n\n' +
|
|
365
|
+
`${payload.session_manifest || '—'}\n\n` +
|
|
366
|
+
'## SPEC\n\n' +
|
|
367
|
+
`${spec || '—'}\n\n` +
|
|
368
|
+
'## PLAN\n\n' +
|
|
369
|
+
`${plan || '—'}\n\n` +
|
|
370
|
+
'## Runtime\n\n' +
|
|
371
|
+
`${runtime || '—'}\n\n` +
|
|
372
|
+
'## VERIFY\n\n' +
|
|
373
|
+
`${verify || '—'}\n`;
|
|
374
|
+
if (options.write !== false) {
|
|
375
|
+
const ctx = contextPaths(projectRoot, activeSession);
|
|
376
|
+
try {
|
|
377
|
+
if (ctx.sessionSummaryJson) writeJson(ctx.sessionSummaryJson, payload);
|
|
378
|
+
if (ctx.sessionSummaryMd) {
|
|
379
|
+
ensureDir(path.dirname(ctx.sessionSummaryMd));
|
|
380
|
+
fs.writeFileSync(ctx.sessionSummaryMd, md, 'utf8');
|
|
381
|
+
}
|
|
382
|
+
} catch (err) {
|
|
383
|
+
throw new Error(`buildSessionSummary: ${err instanceof Error ? err.message : String(err)}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return { json: payload, markdown: md };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function buildPhaseSummary(projectRoot, activeSession, options = {}) {
|
|
390
|
+
const base = health.oxePaths(projectRoot);
|
|
391
|
+
const stateText = readTextIfExists(base.state) || '';
|
|
392
|
+
const payload = {
|
|
393
|
+
summary_type: 'phase',
|
|
394
|
+
generated_at: new Date().toISOString(),
|
|
395
|
+
phase: health.parseStatePhase(stateText),
|
|
396
|
+
active_session: activeSession || health.parseActiveSession(stateText),
|
|
397
|
+
next_step: firstNonEmpty([
|
|
398
|
+
summarizeText(readTextIfExists(base.state) || '', 220, 8),
|
|
399
|
+
]),
|
|
400
|
+
runtime_status: firstMatch(stateText, /\*\*runtime_status:\*\*\s*([^\n]+)/i),
|
|
401
|
+
plan_review_status: health.parsePlanReviewStatus(stateText),
|
|
402
|
+
};
|
|
403
|
+
const md =
|
|
404
|
+
'# OXE Context Summary — Phase\n\n' +
|
|
405
|
+
`- **Gerado em:** ${payload.generated_at}\n` +
|
|
406
|
+
`- **Fase:** ${payload.phase || '—'}\n` +
|
|
407
|
+
`- **Sessão ativa:** ${payload.active_session || 'modo legado'}\n` +
|
|
408
|
+
`- **runtime_status:** ${payload.runtime_status || '—'}\n` +
|
|
409
|
+
`- **plan_review_status:** ${payload.plan_review_status || '—'}\n\n` +
|
|
410
|
+
'## Snapshot\n\n' +
|
|
411
|
+
`${payload.next_step || '—'}\n`;
|
|
412
|
+
if (options.write !== false) {
|
|
413
|
+
const ctx = contextPaths(projectRoot, activeSession);
|
|
414
|
+
try {
|
|
415
|
+
writeJson(ctx.phaseSummaryJson, payload);
|
|
416
|
+
ensureDir(path.dirname(ctx.phaseSummaryMd));
|
|
417
|
+
fs.writeFileSync(ctx.phaseSummaryMd, md, 'utf8');
|
|
418
|
+
} catch (err) {
|
|
419
|
+
throw new Error(`buildPhaseSummary: ${err instanceof Error ? err.message : String(err)}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return { json: payload, markdown: md };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function firstMatch(text, regex) {
|
|
426
|
+
const match = String(text || '').match(regex);
|
|
427
|
+
return match ? match[1].trim() : null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function firstNonEmpty(values) {
|
|
431
|
+
for (const value of values || []) {
|
|
432
|
+
if (value && String(value).trim()) return String(value).trim();
|
|
433
|
+
}
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function buildContextIndex(projectRoot, activeSession, options = {}) {
|
|
438
|
+
const writeOpt = { write: options.write !== false };
|
|
439
|
+
const summaryErrors = [];
|
|
440
|
+
for (const [label, fn, args] of [
|
|
441
|
+
['project', buildProjectSummary, [projectRoot, activeSession, writeOpt]],
|
|
442
|
+
['session', buildSessionSummary, [projectRoot, activeSession, writeOpt]],
|
|
443
|
+
['phase', buildPhaseSummary, [projectRoot, activeSession, writeOpt]],
|
|
444
|
+
]) {
|
|
445
|
+
try {
|
|
446
|
+
fn(...args);
|
|
447
|
+
} catch (err) {
|
|
448
|
+
summaryErrors.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (summaryErrors.length) {
|
|
452
|
+
process.stderr.write(`[oxe] WARN buildContextIndex — falha ao escrever summaries: ${summaryErrors.join('; ')}\n`);
|
|
453
|
+
}
|
|
454
|
+
const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
|
|
455
|
+
const resolvedSession = activeSession === undefined ? health.parseActiveSession(stateText) : activeSession;
|
|
456
|
+
const candidates = resolveArtifactCandidates(projectRoot, resolvedSession || null);
|
|
457
|
+
const artifacts = Object.keys(candidates)
|
|
458
|
+
.sort()
|
|
459
|
+
.map((alias) => buildArtifactRecord(candidates[alias]));
|
|
460
|
+
const payload = {
|
|
461
|
+
schema_version: 1,
|
|
462
|
+
generated_at: new Date().toISOString(),
|
|
463
|
+
project_root: path.resolve(projectRoot),
|
|
464
|
+
active_session: resolvedSession || null,
|
|
465
|
+
phase: health.parseStatePhase(stateText),
|
|
466
|
+
artifacts,
|
|
467
|
+
stats: {
|
|
468
|
+
total: artifacts.length,
|
|
469
|
+
existing: artifacts.filter((item) => item.exists).length,
|
|
470
|
+
missing: artifacts.filter((item) => !item.exists).length,
|
|
471
|
+
conflicts: artifacts.filter((item) => item.conflict).length,
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
if (options.write !== false) {
|
|
475
|
+
const ctx = contextPaths(projectRoot, resolvedSession || null);
|
|
476
|
+
ensureDir(ctx.root);
|
|
477
|
+
writeJson(ctx.index, payload);
|
|
478
|
+
}
|
|
479
|
+
return payload;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function computePackFreshness(pack, contract) {
|
|
483
|
+
const generatedAt = pack.generated_at || null;
|
|
484
|
+
const sourceTimes = (pack.selected_artifacts || [])
|
|
485
|
+
.map((artifact) => artifact.updated_at)
|
|
486
|
+
.filter(Boolean)
|
|
487
|
+
.map((value) => Date.parse(String(value)))
|
|
488
|
+
.filter((value) => !Number.isNaN(value));
|
|
489
|
+
const latestSource = sourceTimes.length ? new Date(Math.max(...sourceTimes)).toISOString() : null;
|
|
490
|
+
const generatedMs = generatedAt ? Date.parse(String(generatedAt)) : Number.NaN;
|
|
491
|
+
const latestSourceMs = latestSource ? Date.parse(latestSource) : Number.NaN;
|
|
492
|
+
const packAgeHours = hoursSince(generatedAt);
|
|
493
|
+
const maxPackAgeHours = contract && contract.freshness_policy && contract.freshness_policy.pack_max_age_hours != null
|
|
494
|
+
? Number(contract.freshness_policy.pack_max_age_hours)
|
|
495
|
+
: 12;
|
|
496
|
+
const staleByAge = packAgeHours != null && maxPackAgeHours > 0 ? packAgeHours > maxPackAgeHours : false;
|
|
497
|
+
const staleBySource = !Number.isNaN(generatedMs) && !Number.isNaN(latestSourceMs) ? generatedMs < latestSourceMs : false;
|
|
498
|
+
return {
|
|
499
|
+
generated_at: generatedAt,
|
|
500
|
+
latest_source_at: latestSource,
|
|
501
|
+
pack_age_hours: packAgeHours,
|
|
502
|
+
max_pack_age_hours: maxPackAgeHours,
|
|
503
|
+
stale: staleByAge || staleBySource || Boolean(pack.fallback_required),
|
|
504
|
+
reason: staleBySource ? 'source_newer_than_pack' : staleByAge ? 'pack_age_exceeded' : pack.fallback_required ? 'fallback_required' : 'fresh',
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function computeContextQuality(pack) {
|
|
509
|
+
const requiredMissing = (pack.gaps || []).filter((gap) => gap.severity === 'critical').length;
|
|
510
|
+
const optionalMissing = (pack.gaps || []).filter((gap) => gap.severity !== 'critical').length;
|
|
511
|
+
const conflicts = (pack.conflicts || []).length;
|
|
512
|
+
const fallbackCount = (pack.selected_artifacts || []).filter((artifact) => artifact.using_fallback).length;
|
|
513
|
+
let score = 100;
|
|
514
|
+
score -= requiredMissing * 25;
|
|
515
|
+
score -= optionalMissing * 5;
|
|
516
|
+
score -= conflicts * 12;
|
|
517
|
+
score -= fallbackCount * 6;
|
|
518
|
+
if ((pack.selected_artifacts || []).length === 0) score -= 40;
|
|
519
|
+
score = Math.max(0, Math.min(100, score));
|
|
520
|
+
const status = score >= 85 ? 'excellent' : score >= 70 ? 'good' : score >= 50 ? 'fragile' : 'critical';
|
|
521
|
+
return {
|
|
522
|
+
score,
|
|
523
|
+
status,
|
|
524
|
+
requiredMissing,
|
|
525
|
+
optionalMissing,
|
|
526
|
+
conflicts,
|
|
527
|
+
fallbackCount,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function renderPackMarkdown(pack) {
|
|
532
|
+
const selected = (pack.selected_artifacts || [])
|
|
533
|
+
.map((artifact) => `- **${artifact.alias}** (${artifact.scope}) -> ${artifact.exists ? artifact.path : 'ausente'}${artifact.using_fallback ? ' [fallback]' : ''}`)
|
|
534
|
+
.join('\n') || '- Nenhum artefato selecionado';
|
|
535
|
+
const gaps = (pack.gaps || [])
|
|
536
|
+
.map((gap) => `- [${gap.severity}] ${gap.alias}: ${gap.reason}`)
|
|
537
|
+
.join('\n') || '- Nenhuma lacuna';
|
|
538
|
+
const conflicts = (pack.conflicts || [])
|
|
539
|
+
.map((conflict) => `- ${conflict.alias}: ${conflict.reason}`)
|
|
540
|
+
.join('\n') || '- Nenhum conflito';
|
|
541
|
+
const sections = (pack.contract && pack.contract.output_sections || []).join(' · ') || '—';
|
|
542
|
+
return (
|
|
543
|
+
`# OXE Context Pack — ${pack.workflow}\n\n` +
|
|
544
|
+
`- **Gerado em:** ${pack.generated_at}\n` +
|
|
545
|
+
`- **Sessão ativa:** ${pack.active_session || 'modo legado'}\n` +
|
|
546
|
+
`- **Tier:** ${pack.context_tier}\n` +
|
|
547
|
+
`- **Semantics hash:** \`${pack.semantics_hash}\`\n` +
|
|
548
|
+
`- **Quality score:** ${pack.context_quality.score}\n` +
|
|
549
|
+
`- **Fallback required:** ${pack.fallback_required ? 'sim' : 'não'}\n\n` +
|
|
550
|
+
'## Read Order\n\n' +
|
|
551
|
+
`${(pack.read_order || []).map((alias) => `- ${alias}`).join('\n') || '- Nenhuma ordem resolvida'}\n\n` +
|
|
552
|
+
'## Selected Artifacts\n\n' +
|
|
553
|
+
`${selected}\n\n` +
|
|
554
|
+
'## Gaps\n\n' +
|
|
555
|
+
`${gaps}\n\n` +
|
|
556
|
+
'## Conflicts\n\n' +
|
|
557
|
+
`${conflicts}\n\n` +
|
|
558
|
+
'## Output Contract\n\n' +
|
|
559
|
+
`${sections}\n`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Extrai o vetor de confiança de um PLAN.md (bloco <confidence_vector>).
|
|
565
|
+
* @param {string} planText
|
|
566
|
+
* @returns {{ cycle: string|null, generated_at: string|null, dimensions: Array<{ name: string, score: number, weight: number, note: string }>, global: { score: number, gate: string } } | null}
|
|
567
|
+
*/
|
|
568
|
+
function parseConfidenceVector(planText) {
|
|
569
|
+
const text = String(planText || '');
|
|
570
|
+
const blockMatch = text.match(/<confidence_vector\s+([^>]*)>([\s\S]*?)<\/confidence_vector>/i);
|
|
571
|
+
if (!blockMatch) return null;
|
|
572
|
+
|
|
573
|
+
const attrs = blockMatch[1];
|
|
574
|
+
const body = blockMatch[2];
|
|
575
|
+
|
|
576
|
+
const cycle = (attrs.match(/\bcycle=["']([^"']+)["']/) || [])[1] || null;
|
|
577
|
+
const generated_at = (attrs.match(/\bgenerated_at=["']([^"']+)["']/) || [])[1] || null;
|
|
578
|
+
|
|
579
|
+
// Extrair dimensões: <dim name="..." score="..." weight="..." note="..." />
|
|
580
|
+
const dimensions = [];
|
|
581
|
+
const dimPattern = /<dim\s+([^/]*)\s*\/>/gi;
|
|
582
|
+
let m;
|
|
583
|
+
while ((m = dimPattern.exec(body)) !== null) {
|
|
584
|
+
const dAttrs = m[1];
|
|
585
|
+
const name = (dAttrs.match(/\bname=["']([^"']+)["']/) || [])[1] || '';
|
|
586
|
+
const score = parseFloat((dAttrs.match(/\bscore=["']([^"']+)["']/) || [])[1] || '0');
|
|
587
|
+
const weight = parseFloat((dAttrs.match(/\bweight=["']([^"']+)["']/) || [])[1] || '0');
|
|
588
|
+
const note = (dAttrs.match(/\bnote=["']([^"']+)["']/) || [])[1] || '';
|
|
589
|
+
if (name) dimensions.push({ name, score: isNaN(score) ? 0 : score, weight: isNaN(weight) ? 0 : weight, note });
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Extrair global: <global score="..." gate="..." />
|
|
593
|
+
const globalMatch = body.match(/<global\s+([^/]*)\s*\/>/i);
|
|
594
|
+
const globalAttrs = globalMatch ? globalMatch[1] : '';
|
|
595
|
+
const globalScore = parseFloat((globalAttrs.match(/\bscore=["']([^"']+)["']/) || [])[1] || '0');
|
|
596
|
+
const gate = (globalAttrs.match(/\bgate=["']([^"']+)["']/) || [])[1] || 'proceed_with_risk';
|
|
597
|
+
|
|
598
|
+
// Se não há global explícito, calcular como média ponderada
|
|
599
|
+
let computedScore = globalScore;
|
|
600
|
+
if (!globalMatch && dimensions.length > 0) {
|
|
601
|
+
const totalWeight = dimensions.reduce((s, d) => s + d.weight, 0);
|
|
602
|
+
computedScore = totalWeight > 0
|
|
603
|
+
? dimensions.reduce((s, d) => s + d.score * d.weight, 0) / totalWeight
|
|
604
|
+
: 0;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
cycle,
|
|
609
|
+
generated_at,
|
|
610
|
+
dimensions,
|
|
611
|
+
global: { score: isNaN(computedScore) ? 0 : Math.round(computedScore * 100) / 100, gate },
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Extrai hipóteses críticas de um PLAN.md.
|
|
617
|
+
* Suporta tags XML (<hypothesis ...>) e fallback para tabela Markdown.
|
|
618
|
+
* @param {string} planText
|
|
619
|
+
* @returns {Array<{ id: string, condition: string, validation: string, on_failure: string, checkpoint: string|null, status: string }>}
|
|
620
|
+
*/
|
|
621
|
+
function parseHypotheses(planText) {
|
|
622
|
+
const text = String(planText || '');
|
|
623
|
+
const results = [];
|
|
624
|
+
|
|
625
|
+
// Formato 1: tags XML
|
|
626
|
+
const xmlPattern = /<hypothesis\s+([^>]*)>([\s\S]*?)<\/hypothesis>/gi;
|
|
627
|
+
let m;
|
|
628
|
+
while ((m = xmlPattern.exec(text)) !== null) {
|
|
629
|
+
const attrs = m[1];
|
|
630
|
+
const body = m[2];
|
|
631
|
+
const id = (attrs.match(/\bid=["']([^"']+)["']/) || [])[1] || '';
|
|
632
|
+
const checkpoint = (attrs.match(/\bcheckpoint=["']([^"']+)["']/) || [])[1] || null;
|
|
633
|
+
const status = (attrs.match(/\bstatus=["']([^"']+)["']/) || [])[1] || 'pending';
|
|
634
|
+
const condition = (body.match(/<condition>([\s\S]*?)<\/condition>/) || [])[1]?.trim() || '';
|
|
635
|
+
const validation = (body.match(/<validation>([\s\S]*?)<\/validation>/) || [])[1]?.trim() || '';
|
|
636
|
+
const on_failure = (body.match(/<on_failure>([\s\S]*?)<\/on_failure>/) || [])[1]?.trim() || '';
|
|
637
|
+
if (id) results.push({ id, condition, validation, on_failure, checkpoint, status });
|
|
638
|
+
}
|
|
639
|
+
if (results.length > 0) return results;
|
|
640
|
+
|
|
641
|
+
// Formato 2: tabela Markdown (fallback)
|
|
642
|
+
// Encontrar a seção e extrair linhas até o próximo heading
|
|
643
|
+
const sectionIdx = text.search(/##\s*Hip.teses\s*Cr.ticas/im);
|
|
644
|
+
if (sectionIdx !== -1) {
|
|
645
|
+
const afterSection = text.slice(sectionIdx);
|
|
646
|
+
// Parar no próximo heading ## ou # (excluindo o próprio)
|
|
647
|
+
const nextHeadingMatch = afterSection.slice(3).match(/\n#{1,3} /);
|
|
648
|
+
const sectionText = nextHeadingMatch
|
|
649
|
+
? afterSection.slice(0, nextHeadingMatch.index + 3 + 1)
|
|
650
|
+
: afterSection;
|
|
651
|
+
const rows = sectionText.split('\n').filter((l) => l.trimStart().startsWith('|'));
|
|
652
|
+
for (const row of rows) {
|
|
653
|
+
const cells = row.split('|').map((c) => c.trim()).filter(Boolean);
|
|
654
|
+
if (cells.length >= 2 && /^H\d+$/i.test(cells[0])) {
|
|
655
|
+
results.push({
|
|
656
|
+
id: cells[0],
|
|
657
|
+
condition: cells[1] || '',
|
|
658
|
+
validation: cells[2] || '',
|
|
659
|
+
on_failure: cells[3] || '',
|
|
660
|
+
checkpoint: cells[4] || null,
|
|
661
|
+
status: cells[5] || 'pending',
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return results;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function buildContextPack(projectRoot, input = {}) {
|
|
670
|
+
const workflow = String(input.workflow || '').trim();
|
|
671
|
+
if (!workflow) {
|
|
672
|
+
throw new Error('workflow é obrigatório para buildContextPack');
|
|
673
|
+
}
|
|
674
|
+
const contract = runtimeSemantics.getWorkflowContract(workflow);
|
|
675
|
+
if (!contract) {
|
|
676
|
+
throw new Error(`Workflow sem contrato canónico: ${workflow}`);
|
|
677
|
+
}
|
|
678
|
+
const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
|
|
679
|
+
const activeSession = input.activeSession === undefined ? health.parseActiveSession(stateText) : input.activeSession;
|
|
680
|
+
const tier = ['minimal', 'standard', 'full'].includes(String(input.tier || 'standard'))
|
|
681
|
+
? String(input.tier || 'standard')
|
|
682
|
+
: 'standard';
|
|
683
|
+
const mode = String(input.mode || 'standard');
|
|
684
|
+
const ctx = contextPaths(projectRoot, activeSession || null);
|
|
685
|
+
const index = buildContextIndex(projectRoot, activeSession || null, { write: input.write !== false });
|
|
686
|
+
const byAlias = new Map((index.artifacts || []).map((artifact) => [artifact.alias, artifact]));
|
|
687
|
+
|
|
688
|
+
// Modo auditor: usa auditor_artifacts do contrato e exclui auditor_excluded
|
|
689
|
+
let selectedAliases;
|
|
690
|
+
if (mode === 'auditor' && contract.auditor_artifacts && contract.auditor_artifacts.length > 0) {
|
|
691
|
+
const excluded = new Set(contract.auditor_excluded || []);
|
|
692
|
+
selectedAliases = Array.from(
|
|
693
|
+
new Set(contract.auditor_artifacts.filter((a) => !excluded.has(a)))
|
|
694
|
+
);
|
|
695
|
+
} else {
|
|
696
|
+
selectedAliases = Array.from(
|
|
697
|
+
new Set(['state', ...(contract.context_tiers[tier] || contract.context_tiers.standard || [])])
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
const intent = String(contract.extraction_intent || 'status_read');
|
|
701
|
+
const selectedArtifacts = selectedAliases.map((alias) => {
|
|
702
|
+
const artifact = byAlias.get(alias);
|
|
703
|
+
if (artifact) {
|
|
704
|
+
const rawText = artifact.exists ? readTextIfExists(artifact.path) || '' : '';
|
|
705
|
+
const semanticSummary = rawText
|
|
706
|
+
? extractSemanticFragment(rawText, { intent })
|
|
707
|
+
: '';
|
|
708
|
+
return {
|
|
709
|
+
...artifact,
|
|
710
|
+
required: contract.required_artifacts.includes(alias),
|
|
711
|
+
selected_because: contract.required_artifacts.includes(alias) ? 'required_artifact' : 'context_tier',
|
|
712
|
+
semantic_summary: semanticSummary,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
alias,
|
|
717
|
+
path: null,
|
|
718
|
+
exists: false,
|
|
719
|
+
scope: 'unknown',
|
|
720
|
+
semantic_type: 'unknown',
|
|
721
|
+
using_fallback: false,
|
|
722
|
+
required: contract.required_artifacts.includes(alias),
|
|
723
|
+
selected_because: contract.required_artifacts.includes(alias) ? 'required_artifact' : 'context_tier',
|
|
724
|
+
updated_at: null,
|
|
725
|
+
age_hours: null,
|
|
726
|
+
hash: null,
|
|
727
|
+
summary: '',
|
|
728
|
+
semantic_summary: '',
|
|
729
|
+
};
|
|
730
|
+
});
|
|
731
|
+
const gaps = [];
|
|
732
|
+
for (const artifact of selectedArtifacts) {
|
|
733
|
+
if (!artifact.exists) {
|
|
734
|
+
gaps.push({
|
|
735
|
+
alias: artifact.alias,
|
|
736
|
+
severity: artifact.required ? 'critical' : 'warning',
|
|
737
|
+
reason: artifact.required ? 'required_artifact_missing' : 'selected_artifact_missing',
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const conflicts = selectedArtifacts
|
|
742
|
+
.filter((artifact) => artifact.conflict)
|
|
743
|
+
.map((artifact) => ({
|
|
744
|
+
alias: artifact.alias,
|
|
745
|
+
reason: 'session_and_root_artifacts_diverge',
|
|
746
|
+
primary_path: artifact.primary_path,
|
|
747
|
+
fallback_path: artifact.fallback_path,
|
|
748
|
+
}));
|
|
749
|
+
const pack = {
|
|
750
|
+
schema_version: 1,
|
|
751
|
+
workflow,
|
|
752
|
+
mode,
|
|
753
|
+
active_session: activeSession || null,
|
|
754
|
+
context_tier: tier,
|
|
755
|
+
generated_at: new Date().toISOString(),
|
|
756
|
+
semantics_hash: runtimeSemantics.computeSemanticsHash(workflow),
|
|
757
|
+
contract,
|
|
758
|
+
read_order: selectedArtifacts.filter((artifact) => artifact.exists).map((artifact) => artifact.alias),
|
|
759
|
+
selected_artifacts: selectedArtifacts,
|
|
760
|
+
gaps,
|
|
761
|
+
conflicts,
|
|
762
|
+
fallback_required: gaps.some((gap) => gap.severity === 'critical') || selectedArtifacts.some((artifact) => artifact.using_fallback),
|
|
763
|
+
summaries: {
|
|
764
|
+
project: ctx.projectSummaryJson,
|
|
765
|
+
session: ctx.sessionSummaryJson,
|
|
766
|
+
phase: ctx.phaseSummaryJson,
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
// Extrair hipóteses críticas do PLAN.md se disponível no pack
|
|
770
|
+
const planArtifact = selectedArtifacts.find((a) => a.alias === 'plan' && a.exists);
|
|
771
|
+
const hypotheses = planArtifact ? parseHypotheses(readTextIfExists(planArtifact.path) || '') : [];
|
|
772
|
+
pack.context_quality = computeContextQuality(pack);
|
|
773
|
+
pack.freshness = computePackFreshness(pack, contract);
|
|
774
|
+
pack.hypotheses = hypotheses;
|
|
775
|
+
pack.markdown = renderPackMarkdown(pack);
|
|
776
|
+
if (input.write !== false) {
|
|
777
|
+
try {
|
|
778
|
+
ensureDir(ctx.packsDir);
|
|
779
|
+
const defaultJson = ctx.defaultPackJson(workflow);
|
|
780
|
+
const defaultMd = ctx.defaultPackMd(workflow);
|
|
781
|
+
writeJson(defaultJson, pack);
|
|
782
|
+
fs.writeFileSync(defaultMd, pack.markdown, 'utf8');
|
|
783
|
+
if (ctx.sessionPackJson(workflow)) {
|
|
784
|
+
writeJson(ctx.sessionPackJson(workflow), pack);
|
|
785
|
+
}
|
|
786
|
+
if (ctx.sessionPackMd(workflow)) {
|
|
787
|
+
fs.writeFileSync(ctx.sessionPackMd(workflow), pack.markdown, 'utf8');
|
|
788
|
+
}
|
|
789
|
+
} catch (err) {
|
|
790
|
+
throw new Error(`buildContextPack (${workflow}): falha ao persistir pack — ${err instanceof Error ? err.message : String(err)}`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return pack;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function resolvePackFile(projectRoot, workflow, activeSession) {
|
|
797
|
+
const ctx = contextPaths(projectRoot, activeSession);
|
|
798
|
+
const candidates = [ctx.sessionPackJson(workflow), ctx.defaultPackJson(workflow)].filter(Boolean);
|
|
799
|
+
for (const filePath of candidates) {
|
|
800
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
801
|
+
}
|
|
802
|
+
return ctx.defaultPackJson(workflow);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function inspectContextPack(projectRoot, input = {}) {
|
|
806
|
+
const workflow = String(input.workflow || '').trim();
|
|
807
|
+
if (!workflow) {
|
|
808
|
+
throw new Error('workflow é obrigatório para inspectContextPack');
|
|
809
|
+
}
|
|
810
|
+
const stateText = readTextIfExists(health.oxePaths(projectRoot).state) || '';
|
|
811
|
+
const activeSession = input.activeSession === undefined ? health.parseActiveSession(stateText) : input.activeSession;
|
|
812
|
+
const filePath = resolvePackFile(projectRoot, workflow, activeSession || null);
|
|
813
|
+
let pack = null;
|
|
814
|
+
if (fs.existsSync(filePath)) {
|
|
815
|
+
try {
|
|
816
|
+
pack = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
817
|
+
} catch {
|
|
818
|
+
pack = null;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (!pack) {
|
|
822
|
+
pack = buildContextPack(projectRoot, {
|
|
823
|
+
workflow,
|
|
824
|
+
tier: input.tier || 'standard',
|
|
825
|
+
activeSession: activeSession || null,
|
|
826
|
+
write: false,
|
|
827
|
+
});
|
|
828
|
+
} else {
|
|
829
|
+
const contract = runtimeSemantics.getWorkflowContract(workflow);
|
|
830
|
+
pack.contract = contract;
|
|
831
|
+
pack.freshness = computePackFreshness(pack, contract);
|
|
832
|
+
pack.context_quality = computeContextQuality(pack);
|
|
833
|
+
}
|
|
834
|
+
pack.path = filePath;
|
|
835
|
+
return pack;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function buildAllContextPacks(projectRoot, input = {}) {
|
|
839
|
+
const workflows = input.workflow
|
|
840
|
+
? [String(input.workflow)]
|
|
841
|
+
: runtimeSemantics.getAllWorkflowContracts().map((contract) => contract.workflow_slug);
|
|
842
|
+
return workflows.map((workflow) => buildContextPack(projectRoot, {
|
|
843
|
+
workflow,
|
|
844
|
+
tier: input.tier || 'standard',
|
|
845
|
+
activeSession: input.activeSession,
|
|
846
|
+
write: input.write !== false,
|
|
847
|
+
}));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
module.exports = {
|
|
851
|
+
buildAllContextPacks,
|
|
852
|
+
buildContextIndex,
|
|
853
|
+
buildContextPack,
|
|
854
|
+
buildPhaseSummary,
|
|
855
|
+
buildProjectSummary,
|
|
856
|
+
buildSessionSummary,
|
|
857
|
+
computeContextQuality,
|
|
858
|
+
computePackFreshness,
|
|
859
|
+
contextPaths,
|
|
860
|
+
extractSemanticFragment,
|
|
861
|
+
inspectContextPack,
|
|
862
|
+
parseConfidenceVector,
|
|
863
|
+
parseHypotheses,
|
|
864
|
+
resolveArtifactCandidates,
|
|
865
|
+
resolvePackFile,
|
|
866
|
+
summarizeText,
|
|
867
|
+
};
|