oxe-cc 0.9.0 → 0.9.2
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/README.md +1 -1
- package/bin/banner.txt +1 -1
- package/bin/oxe-cc.js +61 -0
- package/package.json +86 -84
- package/vscode-extension/.vscodeignore +7 -0
- package/vscode-extension/oxe-agents-0.9.1.vsix +0 -0
- package/vscode-extension/oxe-agents-0.9.2.vsix +0 -0
- package/vscode-extension/package.json +185 -0
- package/vscode-extension/src/extension.js +310 -0
- package/vscode-extension/src/shared/contextLoader.js +137 -0
- package/vscode-extension/src/shared/contractBuilder.js +159 -0
- package/vscode-extension/src/shared/stateReader.js +101 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/oxe-cc)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
|
-
**Versão:** `0.
|
|
10
|
+
**Versão:** `0.9.2` · [package.json](package.json)
|
|
11
11
|
|
|
12
12
|
**Framework OXE — Orchestrated eXperience Engineering**
|
|
13
13
|
|
package/bin/banner.txt
CHANGED
package/bin/oxe-cc.js
CHANGED
|
@@ -590,6 +590,64 @@ function runtimeSemanticsManifestPath(opts) {
|
|
|
590
590
|
return path.join(path.resolve(opts.dir), '.oxe', 'install', 'runtime-semantics.json');
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
+
/**
|
|
594
|
+
* Instala a extensão VS Code OXE Agents via `code --install-extension`.
|
|
595
|
+
* Não falha a instalação global se o VS Code CLI não estiver disponível.
|
|
596
|
+
* @param {{ dryRun?: boolean, force?: boolean }} opts
|
|
597
|
+
*/
|
|
598
|
+
function installVscodeExtension(opts) {
|
|
599
|
+
const c = useAnsiColors();
|
|
600
|
+
const extDir = path.join(PKG_ROOT, 'vscode-extension');
|
|
601
|
+
|
|
602
|
+
// Encontrar o VSIX mais recente na pasta da extensão
|
|
603
|
+
let vsixPath = null;
|
|
604
|
+
if (fs.existsSync(extDir)) {
|
|
605
|
+
const vsixFiles = fs.readdirSync(extDir)
|
|
606
|
+
.filter((f) => f.startsWith('oxe-agents') && f.endsWith('.vsix'))
|
|
607
|
+
.sort()
|
|
608
|
+
.reverse();
|
|
609
|
+
if (vsixFiles.length > 0) vsixPath = path.join(extDir, vsixFiles[0]);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (!vsixPath) {
|
|
613
|
+
console.log(` ${c ? dim : ''}VS Code extension${c ? reset : ''} VSIX não encontrado — pulando.`);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (opts.dryRun) {
|
|
618
|
+
console.log(`${dim}vscode${reset} (dry-run) code --install-extension "${vsixPath}"`);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Candidatos ao CLI do VS Code (Windows precisa do .cmd)
|
|
623
|
+
const codeCandidates = process.platform === 'win32'
|
|
624
|
+
? ['code.cmd', 'code', 'code-insiders.cmd', 'code-insiders']
|
|
625
|
+
: ['code', 'code-insiders'];
|
|
626
|
+
|
|
627
|
+
const { spawnSync } = require('child_process');
|
|
628
|
+
for (const codeBin of codeCandidates) {
|
|
629
|
+
try {
|
|
630
|
+
const result = spawnSync(
|
|
631
|
+
codeBin,
|
|
632
|
+
['--install-extension', vsixPath, '--force'],
|
|
633
|
+
{ encoding: 'utf8', timeout: 30000, shell: process.platform === 'win32' }
|
|
634
|
+
);
|
|
635
|
+
if (result.status === 0) {
|
|
636
|
+
console.log(` ${c ? green : ''}✓${c ? reset : ''} VS Code extension instalada: ${c ? cyan : ''}OXE Agents${c ? reset : ''} (${path.basename(vsixPath)})`);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
} catch {
|
|
640
|
+
/* tenta próximo candidato */
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// code CLI não encontrado no PATH — instrução manual
|
|
645
|
+
console.log(
|
|
646
|
+
` ${c ? dim : ''}VS Code extension${c ? reset : ''} ${c ? yellow : ''}(instalação manual necessária)${c ? reset : ''}\n` +
|
|
647
|
+
` ${c ? cyan : ''}code --install-extension "${vsixPath}"${c ? reset : ''}`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
|
|
593
651
|
/** Integração legado do Copilot VS Code em ~/.copilot/. */
|
|
594
652
|
function copilotLegacyPromptDir(opts) {
|
|
595
653
|
return path.join(copilotUserDir(opts), 'prompts');
|
|
@@ -2523,6 +2581,9 @@ function runInstall(opts) {
|
|
|
2523
2581
|
oxeManifest.writeFileManifest(home, mergedManifest, readPkgVersion());
|
|
2524
2582
|
}
|
|
2525
2583
|
|
|
2584
|
+
// Instalar extensão VS Code OXE Agents (sempre tenta, falha graciosamente)
|
|
2585
|
+
installVscodeExtension(opts);
|
|
2586
|
+
|
|
2526
2587
|
printSummaryAndNextSteps(c, buildInstallSummary(opts, fullLayout));
|
|
2527
2588
|
if (opts.copilot) {
|
|
2528
2589
|
const copilotReport = oxeHealth.copilotIntegrationReport(target);
|
package/package.json
CHANGED
|
@@ -1,84 +1,86 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "oxe-cc",
|
|
3
|
+
"version": "0.9.2",
|
|
4
|
+
"description": "OXE — spec-driven workflows in .oxe/; integrates with Cursor, GitHub Copilot, Claude, OpenCode, Gemini, Codex, Windsurf, Antigravity (npx)",
|
|
5
|
+
"license": "GPL-3.0",
|
|
6
|
+
"author": "",
|
|
7
|
+
"homepage": "https://www.npmjs.com/package/oxe-cc",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/propagno/oxe-build/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/propagno/oxe-build.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"cursor",
|
|
17
|
+
"github-copilot",
|
|
18
|
+
"copilot",
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"opencode",
|
|
22
|
+
"gemini-cli",
|
|
23
|
+
"codex",
|
|
24
|
+
"windsurf",
|
|
25
|
+
"antigravity",
|
|
26
|
+
"spec-driven",
|
|
27
|
+
"context-engineering",
|
|
28
|
+
"ai-agents",
|
|
29
|
+
"oxe"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"main": "lib/sdk/index.cjs",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": "./lib/sdk/index.cjs",
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"types": "lib/sdk/index.d.ts",
|
|
40
|
+
"bin": {
|
|
41
|
+
"oxe-cc": "bin/oxe-cc.js"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"bin",
|
|
45
|
+
"lib",
|
|
46
|
+
"oxe",
|
|
47
|
+
"assets",
|
|
48
|
+
".cursor",
|
|
49
|
+
".github",
|
|
50
|
+
"commands",
|
|
51
|
+
"vscode-extension",
|
|
52
|
+
"AGENTS.md",
|
|
53
|
+
"README.md",
|
|
54
|
+
"CHANGELOG.md"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"sync:runtime-metadata": "node scripts/sync-runtime-metadata.cjs",
|
|
58
|
+
"sync:cursor": "node scripts/sync-cursor-from-prompts.cjs",
|
|
59
|
+
"test": "node --test tests/install.test.cjs tests/oxe-project-health.test.cjs tests/oxe-dashboard.test.cjs tests/oxe-operational.test.cjs tests/oxe-azure.test.cjs tests/oxe-sdk.test.cjs tests/oxe-manifest.test.cjs tests/oxe-agent-install.test.cjs tests/oxe-install-resolve-full.test.cjs tests/oxe-health-extended.test.cjs tests/oxe-workflows-edge.test.cjs tests/oxe-sdk-edge.test.cjs tests/oxe-cli-edge.test.cjs tests/oxe-npm-version.test.cjs tests/oxe-scripts.test.cjs tests/oxe-retro-health.test.cjs tests/oxe-security-permissions.test.cjs tests/oxe-runtime-semantics.test.cjs",
|
|
60
|
+
"test:coverage": "c8 --check-coverage --lines 82 --functions 85 --branches 58 --statements 82 npm test",
|
|
61
|
+
"scan:assets": "node scripts/oxe-assets-scan.cjs",
|
|
62
|
+
"build:vscode-ext": "cd vscode-extension && npx @vscode/vsce package --no-yarn --allow-missing-repository",
|
|
63
|
+
"prepublishOnly": "npm run build:vscode-ext && node scripts/sync-runtime-metadata.cjs && node scripts/sync-cursor-from-prompts.cjs && npm test && npm run scan:assets && node bin/oxe-cc.js --version"
|
|
64
|
+
},
|
|
65
|
+
"c8": {
|
|
66
|
+
"all": true,
|
|
67
|
+
"include": [
|
|
68
|
+
"bin/oxe-cc.js",
|
|
69
|
+
"bin/lib/**/*.cjs",
|
|
70
|
+
"lib/**/*.cjs",
|
|
71
|
+
"scripts/**/*.cjs"
|
|
72
|
+
],
|
|
73
|
+
"exclude": [
|
|
74
|
+
"**/node_modules/**"
|
|
75
|
+
],
|
|
76
|
+
"reporter": [
|
|
77
|
+
"text-summary"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"c8": "^11.0.0"
|
|
82
|
+
},
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"semver": "^7.7.4"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oxe-agents",
|
|
3
|
+
"displayName": "OXE Agents",
|
|
4
|
+
"description": "Agentes OXE para GitHub Copilot Chat — cada fase do ciclo como um @agente no VS Code",
|
|
5
|
+
"version": "0.9.2",
|
|
6
|
+
"publisher": "oxe-cc",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"vscode": "^1.95.0"
|
|
10
|
+
},
|
|
11
|
+
"categories": [
|
|
12
|
+
"AI",
|
|
13
|
+
"Programming Languages",
|
|
14
|
+
"Other"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"oxe",
|
|
18
|
+
"copilot",
|
|
19
|
+
"chat",
|
|
20
|
+
"agent",
|
|
21
|
+
"workflow",
|
|
22
|
+
"spec-driven"
|
|
23
|
+
],
|
|
24
|
+
"extensionDependencies": [
|
|
25
|
+
"GitHub.copilot-chat"
|
|
26
|
+
],
|
|
27
|
+
"activationEvents": [
|
|
28
|
+
"onStartupFinished"
|
|
29
|
+
],
|
|
30
|
+
"main": "./src/extension.js",
|
|
31
|
+
"contributes": {
|
|
32
|
+
"chatParticipants": [
|
|
33
|
+
{
|
|
34
|
+
"id": "oxe.router",
|
|
35
|
+
"name": "oxe",
|
|
36
|
+
"fullName": "OXE",
|
|
37
|
+
"description": "Router universal — lê STATE.md e sugere o próximo passo do ciclo OXE",
|
|
38
|
+
"isSticky": false,
|
|
39
|
+
"sampleRequest": "Qual é a situação atual do projeto e o próximo passo recomendado?"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "oxe.ask",
|
|
43
|
+
"name": "oxe-ask",
|
|
44
|
+
"fullName": "OXE Ask",
|
|
45
|
+
"description": "Situational awareness — responde perguntas sobre o estado atual do projeto com evidência explícita",
|
|
46
|
+
"isSticky": false,
|
|
47
|
+
"sampleRequest": "Qual é a fase atual e o que foi implementado até agora?"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "oxe.scan",
|
|
51
|
+
"name": "oxe-scan",
|
|
52
|
+
"fullName": "OXE Scan",
|
|
53
|
+
"description": "Mapeamento do codebase — reconstrói .oxe/codebase/ e contextualiza o projeto para um novo ciclo",
|
|
54
|
+
"isSticky": false,
|
|
55
|
+
"sampleRequest": "Mapeie o codebase e atualize o contexto do projeto."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "oxe.spec",
|
|
59
|
+
"name": "oxe-spec",
|
|
60
|
+
"fullName": "OXE Spec",
|
|
61
|
+
"description": "Especificação — gera SPEC.md com critérios de aceite verificáveis a partir de requisitos",
|
|
62
|
+
"isSticky": true,
|
|
63
|
+
"sampleRequest": "Preciso adicionar autenticação JWT ao sistema. Gere a especificação.",
|
|
64
|
+
"commands": [
|
|
65
|
+
{
|
|
66
|
+
"name": "discuss",
|
|
67
|
+
"description": "Abrir discussão estruturada antes de gerar a spec"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "oxe.plan",
|
|
73
|
+
"name": "oxe-plan",
|
|
74
|
+
"fullName": "OXE Plan",
|
|
75
|
+
"description": "Planejamento — gera PLAN.md com ondas, tarefas, hipóteses críticas e confidence vector",
|
|
76
|
+
"isSticky": true,
|
|
77
|
+
"sampleRequest": "Gere o plano de implementação para o SPEC atual.",
|
|
78
|
+
"commands": [
|
|
79
|
+
{
|
|
80
|
+
"name": "replan",
|
|
81
|
+
"description": "Replanejar ciclo atual com motivo e lições aplicadas"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "agents",
|
|
85
|
+
"description": "Gerar blueprint multi-agente para planos com múltiplos domínios"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "oxe.quick",
|
|
91
|
+
"name": "oxe-quick",
|
|
92
|
+
"fullName": "OXE Quick",
|
|
93
|
+
"description": "Plano rápido — para tarefas S/M sem SPEC formal; objetivo → passos → verify",
|
|
94
|
+
"isSticky": true,
|
|
95
|
+
"sampleRequest": "Preciso renomear a função processPayment para handlePayment em todos os arquivos."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "oxe.execute",
|
|
99
|
+
"name": "oxe-execute",
|
|
100
|
+
"fullName": "OXE Execute",
|
|
101
|
+
"description": "Execução — implementa a onda atual do PLAN validando hipóteses antes de mutar",
|
|
102
|
+
"isSticky": true,
|
|
103
|
+
"sampleRequest": "Execute a onda atual do plano.",
|
|
104
|
+
"commands": [
|
|
105
|
+
{
|
|
106
|
+
"name": "wave",
|
|
107
|
+
"description": "Executar uma onda específica (ex: /wave 2)"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "task",
|
|
111
|
+
"description": "Executar uma tarefa específica (ex: /task T3)"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "oxe.debug",
|
|
117
|
+
"name": "oxe-debug",
|
|
118
|
+
"fullName": "OXE Debug",
|
|
119
|
+
"description": "Debug — diagnóstico orientado a hipóteses durante execução travada ou falha inline",
|
|
120
|
+
"isSticky": false,
|
|
121
|
+
"sampleRequest": "A tarefa T3 está falhando com erro de timeout. Ajude a diagnosticar."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"id": "oxe.verify",
|
|
125
|
+
"name": "oxe-verify",
|
|
126
|
+
"fullName": "OXE Verify",
|
|
127
|
+
"description": "Verificação — valida critérios A* do SPEC contra evidências reais produzidas pelo executor",
|
|
128
|
+
"isSticky": true,
|
|
129
|
+
"sampleRequest": "Verifique se a implementação atende todos os critérios do SPEC.",
|
|
130
|
+
"commands": [
|
|
131
|
+
{
|
|
132
|
+
"name": "audit",
|
|
133
|
+
"description": "Auditoria adversarial: sem acesso ao PLAN, apenas SPEC + evidências"
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"id": "oxe.review",
|
|
139
|
+
"name": "oxe-review",
|
|
140
|
+
"fullName": "OXE Review",
|
|
141
|
+
"description": "Revisão de código — auditor adversarial de PR e mudanças, findings por severidade",
|
|
142
|
+
"isSticky": false,
|
|
143
|
+
"sampleRequest": "Revise as mudanças desta branch e liste os findings por severidade."
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"id": "oxe.capabilities",
|
|
147
|
+
"name": "oxe-capabilities",
|
|
148
|
+
"fullName": "OXE Capabilities",
|
|
149
|
+
"description": "Capabilities — lista, instala, remove e atualiza capabilities nativas do projeto em .oxe/",
|
|
150
|
+
"isSticky": false,
|
|
151
|
+
"sampleRequest": "Liste as capabilities disponíveis neste projeto.",
|
|
152
|
+
"commands": [
|
|
153
|
+
{
|
|
154
|
+
"name": "list",
|
|
155
|
+
"description": "Listar capabilities instaladas"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "install",
|
|
159
|
+
"description": "Instalar uma capability"
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"id": "oxe.skill",
|
|
165
|
+
"name": "oxe-skill",
|
|
166
|
+
"fullName": "OXE Skill",
|
|
167
|
+
"description": "Skills — gerencia skills e habilidades registradas no framework OXE do projeto",
|
|
168
|
+
"isSticky": false,
|
|
169
|
+
"sampleRequest": "Quais skills estão disponíveis neste projeto OXE?"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"id": "oxe.dashboard",
|
|
173
|
+
"name": "oxe-dashboard",
|
|
174
|
+
"fullName": "OXE Dashboard",
|
|
175
|
+
"description": "Dashboard — exibe status operacional: runtime, ondas, checkpoints e saúde do ciclo",
|
|
176
|
+
"isSticky": false,
|
|
177
|
+
"sampleRequest": "Mostre o status do dashboard: fase, active run e próximo passo."
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"repository": {
|
|
182
|
+
"type": "git",
|
|
183
|
+
"url": "https://github.com/oxe-cc/oxe-cc"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const vscode = require('vscode');
|
|
4
|
+
const stateReader = require('./shared/stateReader');
|
|
5
|
+
const contextLoader = require('./shared/contextLoader');
|
|
6
|
+
const contractBuilder = require('./shared/contractBuilder');
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Output channel para diagnóstico
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/** @type {vscode.OutputChannel | null} */
|
|
13
|
+
let outputChannel = null;
|
|
14
|
+
|
|
15
|
+
function log(message) {
|
|
16
|
+
if (outputChannel) outputChannel.appendLine(`[OXE Agents] ${message}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Configuração dos 13 agentes
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/** @type {Array<{ id: string, workflow: string, description: string, isSticky: boolean }>} */
|
|
24
|
+
const AGENTS = [
|
|
25
|
+
{ id: 'oxe.router', workflow: 'oxe', isSticky: false },
|
|
26
|
+
{ id: 'oxe.ask', workflow: 'ask', isSticky: false },
|
|
27
|
+
{ id: 'oxe.scan', workflow: 'scan', isSticky: false },
|
|
28
|
+
{ id: 'oxe.spec', workflow: 'spec', isSticky: true },
|
|
29
|
+
{ id: 'oxe.plan', workflow: 'plan', isSticky: true },
|
|
30
|
+
{ id: 'oxe.quick', workflow: 'quick', isSticky: true },
|
|
31
|
+
{ id: 'oxe.execute', workflow: 'execute', isSticky: true },
|
|
32
|
+
{ id: 'oxe.debug', workflow: 'debug', isSticky: false },
|
|
33
|
+
{ id: 'oxe.verify', workflow: 'verify', isSticky: true },
|
|
34
|
+
{ id: 'oxe.review', workflow: 'review-pr', isSticky: false },
|
|
35
|
+
{ id: 'oxe.capabilities', workflow: 'capabilities', isSticky: false },
|
|
36
|
+
{ id: 'oxe.skill', workflow: 'skill', isSticky: false },
|
|
37
|
+
{ id: 'oxe.dashboard', workflow: 'dashboard', isSticky: false },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Seleção de modelo
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Seleciona o melhor modelo disponível — GPT-4o via Copilot com fallbacks.
|
|
46
|
+
* @returns {Promise<import('vscode').LanguageModelChat | null>}
|
|
47
|
+
*/
|
|
48
|
+
async function selectModel() {
|
|
49
|
+
if (!vscode.lm || typeof vscode.lm.selectChatModels !== 'function') {
|
|
50
|
+
log('vscode.lm.selectChatModels não disponível nesta versão do VS Code.');
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const selectors = [
|
|
55
|
+
{ vendor: 'copilot', family: 'gpt-4o' },
|
|
56
|
+
{ vendor: 'copilot', family: 'claude-sonnet' },
|
|
57
|
+
{ vendor: 'copilot' },
|
|
58
|
+
{},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const selector of selectors) {
|
|
62
|
+
try {
|
|
63
|
+
const models = await vscode.lm.selectChatModels(selector);
|
|
64
|
+
if (models && models.length > 0) {
|
|
65
|
+
log(`Modelo selecionado: ${models[0].name || models[0].id || JSON.stringify(selector)}`);
|
|
66
|
+
return models[0];
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
log(`selectChatModels(${JSON.stringify(selector)}) falhou: ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Conversão de histórico — duck typing para evitar quebra em versões antigas
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Converte as últimas N trocas do histórico em mensagens para o LM.
|
|
81
|
+
* Usa duck typing em vez de instanceof para compatibilidade.
|
|
82
|
+
* @param {readonly unknown[]} history
|
|
83
|
+
* @param {number} maxTurns
|
|
84
|
+
* @returns {import('vscode').LanguageModelChatMessage[]}
|
|
85
|
+
*/
|
|
86
|
+
function buildHistoryMessages(history, maxTurns = 3) {
|
|
87
|
+
if (!history || !history.length) return [];
|
|
88
|
+
if (!vscode.LanguageModelChatMessage) return [];
|
|
89
|
+
|
|
90
|
+
const messages = [];
|
|
91
|
+
const recent = history.slice(-maxTurns * 2);
|
|
92
|
+
|
|
93
|
+
for (const turn of recent) {
|
|
94
|
+
if (!turn || typeof turn !== 'object') continue;
|
|
95
|
+
|
|
96
|
+
// ChatRequestTurn: tem propriedade `prompt` (string)
|
|
97
|
+
if (typeof turn.prompt === 'string' && turn.prompt.trim()) {
|
|
98
|
+
try {
|
|
99
|
+
messages.push(vscode.LanguageModelChatMessage.User(turn.prompt));
|
|
100
|
+
} catch { /* API incompatível */ }
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ChatResponseTurn: tem propriedade `response` (array de parts)
|
|
105
|
+
if (Array.isArray(turn.response)) {
|
|
106
|
+
const text = turn.response
|
|
107
|
+
.filter((part) => part && typeof part === 'object' && part.value && typeof part.value.value === 'string')
|
|
108
|
+
.map((part) => part.value.value)
|
|
109
|
+
.join('');
|
|
110
|
+
if (text.trim()) {
|
|
111
|
+
try {
|
|
112
|
+
messages.push(vscode.LanguageModelChatMessage.Assistant(text));
|
|
113
|
+
} catch { /* API incompatível */ }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return messages;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Verificar se o projeto tem OXE inicializado
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Garante que o workspace tem OXE. Retorna o root ou null com mensagem de erro.
|
|
127
|
+
* @param {import('vscode').ChatResponseStream} stream
|
|
128
|
+
* @returns {string | null}
|
|
129
|
+
*/
|
|
130
|
+
function requireOxeProject(stream) {
|
|
131
|
+
const root = stateReader.getProjectRoot(vscode.workspace.workspaceFolders);
|
|
132
|
+
|
|
133
|
+
if (!root) {
|
|
134
|
+
stream.markdown(
|
|
135
|
+
'⚠️ **Nenhum workspace aberto.**\n\nAbra uma pasta de projeto para usar os agentes OXE.\n\n' +
|
|
136
|
+
'```\nFile → Open Folder → selecione a pasta do seu projeto\n```'
|
|
137
|
+
);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!stateReader.hasOxe(root)) {
|
|
142
|
+
stream.markdown(
|
|
143
|
+
`⚠️ **Projeto OXE não inicializado** em \`${root}\`.\n\n` +
|
|
144
|
+
'Execute no terminal para instalar o OXE:\n\n' +
|
|
145
|
+
'```bash\nnpx oxe-cc@latest\n```'
|
|
146
|
+
);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return root;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Handler genérico
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Cria o handler de um agente OXE.
|
|
159
|
+
* @param {{ id: string, workflow: string }} agentDef
|
|
160
|
+
* @returns {import('vscode').ChatRequestHandler}
|
|
161
|
+
*/
|
|
162
|
+
function makeHandler(agentDef) {
|
|
163
|
+
const { workflow } = agentDef;
|
|
164
|
+
|
|
165
|
+
return async (request, context, stream, token) => {
|
|
166
|
+
log(`Handler invocado: ${agentDef.id} | prompt: "${(request.prompt || '').slice(0, 60)}"${request.command ? ' | cmd: /' + request.command : ''}`);
|
|
167
|
+
|
|
168
|
+
// 1. Verificar projeto
|
|
169
|
+
const projectRoot = requireOxeProject(stream);
|
|
170
|
+
if (!projectRoot) return;
|
|
171
|
+
|
|
172
|
+
// 2. Mostrar progresso
|
|
173
|
+
stream.progress(`Carregando contexto OXE — workflow: ${workflow}…`);
|
|
174
|
+
|
|
175
|
+
// 3. Ler state
|
|
176
|
+
const stateInfo = stateReader.getProjectContext(projectRoot);
|
|
177
|
+
log(`Estado: fase=${stateInfo.phase}, sessão=${stateInfo.session}`);
|
|
178
|
+
|
|
179
|
+
// 4. Carregar context pack
|
|
180
|
+
const pack = contextLoader.loadContextPack(projectRoot, workflow);
|
|
181
|
+
log(`Context pack: ${pack ? `carregado (quality=${pack.context_quality?.score})` : 'não encontrado — usando fallback'}`);
|
|
182
|
+
|
|
183
|
+
// 5. Formatar artefatos
|
|
184
|
+
const artifactsText = contextLoader.formatArtifacts(pack);
|
|
185
|
+
const hypothesesText = contextLoader.formatHypotheses(pack);
|
|
186
|
+
const gapsText = contextLoader.formatGaps(pack);
|
|
187
|
+
|
|
188
|
+
// 6. Construir system prompt
|
|
189
|
+
const systemPrompt = contractBuilder.build(
|
|
190
|
+
workflow,
|
|
191
|
+
pack,
|
|
192
|
+
stateInfo.text,
|
|
193
|
+
request.command,
|
|
194
|
+
stateInfo,
|
|
195
|
+
artifactsText,
|
|
196
|
+
hypothesesText,
|
|
197
|
+
gapsText
|
|
198
|
+
);
|
|
199
|
+
log(`System prompt: ${systemPrompt.length} chars`);
|
|
200
|
+
|
|
201
|
+
// 7. Selecionar modelo
|
|
202
|
+
const model = await selectModel();
|
|
203
|
+
if (!model) {
|
|
204
|
+
stream.markdown(
|
|
205
|
+
'⚠️ **Nenhum modelo de linguagem disponível.**\n\n' +
|
|
206
|
+
'Verifique se o **GitHub Copilot Chat** está instalado, habilitado e autenticado no VS Code.\n\n' +
|
|
207
|
+
'> Extensão necessária: `GitHub.copilot-chat`'
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 8. Construir mensagens
|
|
213
|
+
const historyMessages = buildHistoryMessages(context.history);
|
|
214
|
+
const userMessage = request.command
|
|
215
|
+
? `[/${request.command}] ${request.prompt}`
|
|
216
|
+
: request.prompt;
|
|
217
|
+
|
|
218
|
+
const messages = [];
|
|
219
|
+
try {
|
|
220
|
+
messages.push(vscode.LanguageModelChatMessage.User(systemPrompt));
|
|
221
|
+
} catch (err) {
|
|
222
|
+
log(`Erro ao criar LanguageModelChatMessage: ${err.message}`);
|
|
223
|
+
stream.markdown('⚠️ API de mensagens não disponível nesta versão do VS Code. Requer VS Code 1.95+.');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
messages.push(...historyMessages);
|
|
227
|
+
messages.push(vscode.LanguageModelChatMessage.User(userMessage));
|
|
228
|
+
|
|
229
|
+
// 9. Enviar e fazer stream
|
|
230
|
+
try {
|
|
231
|
+
const response = await model.sendRequest(messages, {}, token);
|
|
232
|
+
for await (const chunk of response.text) {
|
|
233
|
+
if (token.isCancellationRequested) break;
|
|
234
|
+
stream.markdown(chunk);
|
|
235
|
+
}
|
|
236
|
+
log(`Resposta concluída para ${agentDef.id}`);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
if (err.name === 'CancellationError' || err.code === 'Cancelled') return;
|
|
239
|
+
|
|
240
|
+
// Tratar erros conhecidos do LM de forma segura (sem depender de statics que podem não existir)
|
|
241
|
+
const errMsg = err.message || String(err);
|
|
242
|
+
if (/blocked/i.test(errMsg)) {
|
|
243
|
+
stream.markdown('⚠️ **Solicitação bloqueada** pelo modelo. Reformule a pergunta e tente novamente.');
|
|
244
|
+
} else if (/not found/i.test(errMsg) || /unavailable/i.test(errMsg)) {
|
|
245
|
+
stream.markdown('⚠️ **Modelo indisponível.** Verifique as configurações do GitHub Copilot.');
|
|
246
|
+
} else if (/permission|quota/i.test(errMsg)) {
|
|
247
|
+
stream.markdown('⚠️ **Sem permissão ou cota esgotada.** Verifique a assinatura do Copilot.');
|
|
248
|
+
} else {
|
|
249
|
+
stream.markdown(`⚠️ **Erro:** ${errMsg}`);
|
|
250
|
+
}
|
|
251
|
+
log(`Erro no handler ${agentDef.id}: ${errMsg}`);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Activate / Deactivate
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @param {import('vscode').ExtensionContext} context
|
|
262
|
+
*/
|
|
263
|
+
function activate(context) {
|
|
264
|
+
// Criar output channel para diagnóstico
|
|
265
|
+
outputChannel = vscode.window.createOutputChannel('OXE Agents');
|
|
266
|
+
context.subscriptions.push(outputChannel);
|
|
267
|
+
log(`Extensão ativando — VS Code ${vscode.version}`);
|
|
268
|
+
|
|
269
|
+
// Guard: verificar se a API de chat está disponível
|
|
270
|
+
if (!vscode.chat || typeof vscode.chat.createChatParticipant !== 'function') {
|
|
271
|
+
const msg = 'OXE Agents requer GitHub Copilot Chat (GitHub.copilot-chat) instalado e habilitado no VS Code.';
|
|
272
|
+
log(`AVISO: ${msg}`);
|
|
273
|
+
vscode.window.showWarningMessage(msg);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Registrar todos os agentes
|
|
278
|
+
let registered = 0;
|
|
279
|
+
for (const agentDef of AGENTS) {
|
|
280
|
+
try {
|
|
281
|
+
const participant = vscode.chat.createChatParticipant(agentDef.id, makeHandler(agentDef));
|
|
282
|
+
participant.iconPath = new vscode.ThemeIcon('sparkle');
|
|
283
|
+
context.subscriptions.push(participant);
|
|
284
|
+
registered++;
|
|
285
|
+
log(`Agente registrado: ${agentDef.id}`);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
log(`Falha ao registrar ${agentDef.id}: ${err.message}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
log(`${registered}/${AGENTS.length} agentes registrados com sucesso.`);
|
|
292
|
+
|
|
293
|
+
// Notificar na primeira ativação
|
|
294
|
+
const key = 'oxe.agents.v2.activated';
|
|
295
|
+
if (!context.globalState.get(key)) {
|
|
296
|
+
context.globalState.update(key, true);
|
|
297
|
+
vscode.window.showInformationMessage(
|
|
298
|
+
`OXE Agents: ${registered} agentes ativos. Use @oxe, @oxe-plan, @oxe-execute e outros no chat do Copilot.`,
|
|
299
|
+
'Ver log'
|
|
300
|
+
).then((choice) => {
|
|
301
|
+
if (choice === 'Ver log') outputChannel?.show();
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function deactivate() {
|
|
307
|
+
log('Extensão desativada.');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = { activate, deactivate };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tenta parsear JSON do stdout do oxe-cc (pula linhas de banner antes do `{`).
|
|
9
|
+
* @param {string} stdout
|
|
10
|
+
* @returns {object | null}
|
|
11
|
+
*/
|
|
12
|
+
function parseJsonFromOutput(stdout) {
|
|
13
|
+
if (!stdout) return null;
|
|
14
|
+
const text = String(stdout).trim();
|
|
15
|
+
const jsonStart = text.indexOf('{');
|
|
16
|
+
if (jsonStart === -1) return null;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(text.slice(jsonStart));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Tenta resolver o context pack via oxe-cc CLI.
|
|
26
|
+
* Primeiro tenta o binário local em node_modules, depois npx global.
|
|
27
|
+
* @param {string} projectRoot
|
|
28
|
+
* @param {string} workflow
|
|
29
|
+
* @returns {object | null}
|
|
30
|
+
*/
|
|
31
|
+
function resolveViaOxeCc(projectRoot, workflow) {
|
|
32
|
+
const localBin = path.join(projectRoot, 'node_modules', 'oxe-cc', 'bin', 'oxe-cc.js');
|
|
33
|
+
const args = ['context', 'inspect', '--workflow', workflow, '--json', '--dir', projectRoot];
|
|
34
|
+
const env = { ...process.env, OXE_NO_BANNER: '1' };
|
|
35
|
+
const opts = { cwd: projectRoot, encoding: 'utf8', timeout: 12000, env };
|
|
36
|
+
|
|
37
|
+
// Tentativa 1 — binário local
|
|
38
|
+
if (fs.existsSync(localBin)) {
|
|
39
|
+
const r = spawnSync(process.execPath, [localBin, ...args], opts);
|
|
40
|
+
if (r.status === 0) {
|
|
41
|
+
const pack = parseJsonFromOutput(r.stdout);
|
|
42
|
+
if (pack) return pack;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Tentativa 2 — npx (global ou cache)
|
|
47
|
+
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
48
|
+
const r = spawnSync(npxCmd, ['oxe-cc', ...args], opts);
|
|
49
|
+
if (r.status === 0) {
|
|
50
|
+
return parseJsonFromOutput(r.stdout);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Carrega o context pack para um workflow. Tenta disco primeiro, depois CLI.
|
|
58
|
+
* @param {string} projectRoot
|
|
59
|
+
* @param {string} workflow
|
|
60
|
+
* @returns {object | null}
|
|
61
|
+
*/
|
|
62
|
+
function loadContextPack(projectRoot, workflow) {
|
|
63
|
+
// Tentativa 1 — ler pack já materializado em disco
|
|
64
|
+
const packPath = path.join(projectRoot, '.oxe', 'context', 'packs', `${workflow}.json`);
|
|
65
|
+
if (fs.existsSync(packPath)) {
|
|
66
|
+
try {
|
|
67
|
+
const data = JSON.parse(fs.readFileSync(packPath, 'utf8'));
|
|
68
|
+
if (data && data.workflow) return data;
|
|
69
|
+
} catch {
|
|
70
|
+
// arquivo corrompido → tenta CLI
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Tentativa 2 — resolver on-demand via oxe-cc
|
|
75
|
+
return resolveViaOxeCc(projectRoot, workflow);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Formata os artefatos selecionados do pack em markdown legível.
|
|
80
|
+
* Usa semantic_summary quando disponível, cai para summary.
|
|
81
|
+
* @param {object | null} pack
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
function formatArtifacts(pack) {
|
|
85
|
+
if (!pack || !Array.isArray(pack.selected_artifacts)) return '';
|
|
86
|
+
|
|
87
|
+
const artifacts = pack.selected_artifacts
|
|
88
|
+
.filter((a) => a.exists && (a.semantic_summary || a.summary))
|
|
89
|
+
.map((a) => {
|
|
90
|
+
const content = (a.semantic_summary || a.summary || '').trim();
|
|
91
|
+
if (!content) return null;
|
|
92
|
+
const label = a.alias.replace(/_/g, ' ');
|
|
93
|
+
const flags = [
|
|
94
|
+
a.using_fallback ? '[fallback]' : '',
|
|
95
|
+
a.required ? '[obrigatório]' : '',
|
|
96
|
+
].filter(Boolean).join(' ');
|
|
97
|
+
return `### ${label}${flags ? ' ' + flags : ''}\n${content}`;
|
|
98
|
+
})
|
|
99
|
+
.filter(Boolean);
|
|
100
|
+
|
|
101
|
+
return artifacts.length > 0 ? artifacts.join('\n\n') : '';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Formata hipóteses pendentes do pack para o system prompt.
|
|
106
|
+
* @param {object | null} pack
|
|
107
|
+
* @returns {string}
|
|
108
|
+
*/
|
|
109
|
+
function formatHypotheses(pack) {
|
|
110
|
+
if (!pack || !Array.isArray(pack.hypotheses)) return '';
|
|
111
|
+
const pending = pack.hypotheses.filter((h) => h.status === 'pending');
|
|
112
|
+
if (pending.length === 0) return '';
|
|
113
|
+
const lines = pending.map((h) =>
|
|
114
|
+
`- **[${h.id}]** ${h.condition}${h.checkpoint ? ` *(checkpoint: ${h.checkpoint})*` : ''}`
|
|
115
|
+
);
|
|
116
|
+
return `## Hipóteses críticas pendentes\n\n${lines.join('\n')}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Formata gaps críticos do pack.
|
|
121
|
+
* @param {object | null} pack
|
|
122
|
+
* @returns {string}
|
|
123
|
+
*/
|
|
124
|
+
function formatGaps(pack) {
|
|
125
|
+
if (!pack || !Array.isArray(pack.gaps)) return '';
|
|
126
|
+
const critical = pack.gaps.filter((g) => g.severity === 'critical');
|
|
127
|
+
if (critical.length === 0) return '';
|
|
128
|
+
const lines = critical.map((g) => `- \`${g.alias}\`: ${g.reason}`);
|
|
129
|
+
return `## Artefatos críticos ausentes\n\n${lines.join('\n')}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
loadContextPack,
|
|
134
|
+
formatArtifacts,
|
|
135
|
+
formatHypotheses,
|
|
136
|
+
formatGaps,
|
|
137
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/** Instruções específicas por reasoning_mode */
|
|
4
|
+
const MODE_INSTRUCTIONS = {
|
|
5
|
+
discovery: [
|
|
6
|
+
'Explore o repositório e os artefatos antes de perguntar.',
|
|
7
|
+
'Separe fatos confirmados, inferências e lacunas.',
|
|
8
|
+
'Pergunte apenas ambiguidades que mudem a decisão ou o artefato final.',
|
|
9
|
+
],
|
|
10
|
+
planning: [
|
|
11
|
+
'Produza plano decision-complete — feche interfaces, validação, riscos, rollback e assumptions.',
|
|
12
|
+
'Não deixe decisões importantes abertas para quem implementar depois.',
|
|
13
|
+
'Explicite confiança e condição objetiva para replanejar.',
|
|
14
|
+
],
|
|
15
|
+
execution: [
|
|
16
|
+
'Faça reconhecimento curto antes de editar ou executar mutações.',
|
|
17
|
+
'Trabalhe no menor write set viável e valide após cada fatia relevante.',
|
|
18
|
+
'Pare e explicite o bloqueio quando houver hipótese crítica não verificada.',
|
|
19
|
+
],
|
|
20
|
+
review: [
|
|
21
|
+
'Apresente findings primeiro, ordenados por severidade e evidência.',
|
|
22
|
+
'Separe bug, risco, regressão e lacuna de teste.',
|
|
23
|
+
'Se não houver findings, declare isso explicitamente e liste riscos residuais.',
|
|
24
|
+
],
|
|
25
|
+
status: [
|
|
26
|
+
'Responda com leitura curta e orientada a decisão.',
|
|
27
|
+
'Dê uma recomendação única e justifique o motivo.',
|
|
28
|
+
'Explicite a confiança quando o estado estiver incompleto ou ambíguo.',
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** Instruções por sub-comando */
|
|
33
|
+
const COMMAND_INSTRUCTIONS = {
|
|
34
|
+
replan: 'Sub-comando **replan**: leia LESSONS.md e lessons-metrics.json antes de replanejar. Registre o motivo do replanejamento e as lições aplicadas.',
|
|
35
|
+
agents: 'Sub-comando **agents**: identifique 2–4 domínios distintos no PLAN.md e gere um blueprint multi-agente em formato plan-agents.json.',
|
|
36
|
+
discuss: 'Sub-comando **discuss**: abra uma discussão estruturada. Pergunte sobre contexto, alternativas e trade-offs antes de gerar a spec.',
|
|
37
|
+
wave: 'Sub-comando **wave**: o usuário indicou uma onda específica. Concentre o reconhecimento e a execução nessa onda apenas.',
|
|
38
|
+
task: 'Sub-comando **task**: o usuário indicou uma tarefa específica (ex: T3). Execute apenas essa tarefa com reconhecimento mínimo.',
|
|
39
|
+
audit: 'Sub-comando **audit**: modo auditor adversarial. Avalie SPEC.md + VERIFY.md sem consultar PLAN.md nem EXECUTION-RUNTIME.md. Objetivo: falsificar, não confirmar.',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Formata o vetor de confiança do pack para o system prompt.
|
|
44
|
+
* @param {object | null} pack
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
function formatConfidenceVector(pack) {
|
|
48
|
+
const artifacts = pack?.selected_artifacts || [];
|
|
49
|
+
const planArtifact = artifacts.find((a) => a.alias === 'plan' && a.exists);
|
|
50
|
+
if (!planArtifact || !planArtifact.semantic_summary) return '';
|
|
51
|
+
|
|
52
|
+
// Verificar se o semantic_summary contém dados de confiança
|
|
53
|
+
if (!planArtifact.semantic_summary.includes('Confiança') &&
|
|
54
|
+
!planArtifact.semantic_summary.includes('confidence')) return '';
|
|
55
|
+
|
|
56
|
+
return ''; // O semantic_summary já inclui isso via extraction_intent=planning_input
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Constrói o system prompt completo para um agente OXE.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} workflow - slug do workflow (ex: 'plan', 'execute')
|
|
63
|
+
* @param {object | null} pack - context pack carregado
|
|
64
|
+
* @param {string} stateText - conteúdo do STATE.md (truncado)
|
|
65
|
+
* @param {string | undefined} command - sub-comando invocado (ex: 'replan')
|
|
66
|
+
* @param {{ phase: string | null, session: string | null, nextStep: string | null }} stateInfo
|
|
67
|
+
* @param {string} artifactsText - artefatos formatados pelo contextLoader
|
|
68
|
+
* @param {string} hypothesesText - hipóteses formatadas
|
|
69
|
+
* @param {string} gapsText - gaps formatados
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
function build(workflow, pack, stateText, command, stateInfo, artifactsText, hypothesesText, gapsText) {
|
|
73
|
+
const contract = pack?.contract || {};
|
|
74
|
+
const mode = contract.reasoning_mode || 'status';
|
|
75
|
+
const guidance = Array.isArray(contract.guidance) && contract.guidance.length > 0
|
|
76
|
+
? contract.guidance
|
|
77
|
+
: (MODE_INSTRUCTIONS[mode] || MODE_INSTRUCTIONS.status);
|
|
78
|
+
|
|
79
|
+
const modeInstructions = MODE_INSTRUCTIONS[mode] || MODE_INSTRUCTIONS.status;
|
|
80
|
+
const commandInstruction = command && COMMAND_INSTRUCTIONS[command]
|
|
81
|
+
? `\n\n**${COMMAND_INSTRUCTIONS[command]}**`
|
|
82
|
+
: '';
|
|
83
|
+
|
|
84
|
+
// Resumo do estado do projeto
|
|
85
|
+
const stateSection = stateText
|
|
86
|
+
? `## Estado atual do projeto\n\n${stateText}`
|
|
87
|
+
: '';
|
|
88
|
+
|
|
89
|
+
// Info de contexto rápido
|
|
90
|
+
const contextInfo = [
|
|
91
|
+
stateInfo.phase ? `- **Fase:** ${stateInfo.phase}` : '',
|
|
92
|
+
stateInfo.session ? `- **Sessão ativa:** ${stateInfo.session}` : '',
|
|
93
|
+
stateInfo.nextStep ? `- **Próximo passo sugerido:** ${stateInfo.nextStep}` : '',
|
|
94
|
+
].filter(Boolean).join('\n');
|
|
95
|
+
|
|
96
|
+
// Qualidade do contexto
|
|
97
|
+
let qualityNote = '';
|
|
98
|
+
if (pack) {
|
|
99
|
+
const quality = pack.context_quality;
|
|
100
|
+
if (quality && quality.status === 'critical') {
|
|
101
|
+
qualityNote = `\n> ⚠️ **Contexto crítico** (score: ${quality.score}/100) — artefatos obrigatórios ausentes. Declare fallback para leitura direta se necessário.`;
|
|
102
|
+
} else if (pack.fallback_required) {
|
|
103
|
+
qualityNote = '\n> ℹ️ **Contexto com fallback** — alguns artefatos foram resolvidos por caminhos alternativos.';
|
|
104
|
+
}
|
|
105
|
+
if (pack.freshness && pack.freshness.stale) {
|
|
106
|
+
qualityNote += `\n> 🕐 **Pack desatualizado** (${pack.freshness.reason}) — considere regenerar com \`oxe-cc context build --workflow ${workflow}\`.`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const parts = [
|
|
111
|
+
`Você é o agente **OXE ${workflow}**, especializado na fase de ${mode} do framework OXE (Orchestrated eXperience Engineering).`,
|
|
112
|
+
'',
|
|
113
|
+
'## Contrato de raciocínio',
|
|
114
|
+
`- **Modo:** ${mode}`,
|
|
115
|
+
`- **Política de perguntas:** ${contract.question_policy || 'none'}`,
|
|
116
|
+
`- **Saída esperada:** ${contract.output_contract || 'routing'}`,
|
|
117
|
+
`- **Perfil de ferramentas:** ${contract.tool_profile || 'read_heavy'}`,
|
|
118
|
+
`- **Política de confiança:** ${contract.confidence_policy || 'explicit'}`,
|
|
119
|
+
'',
|
|
120
|
+
'## Diretrizes de raciocínio',
|
|
121
|
+
...guidance.map((g) => `- ${g}`),
|
|
122
|
+
'',
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
if (contextInfo) {
|
|
126
|
+
parts.push('## Contexto rápido', contextInfo, '');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (stateSection) {
|
|
130
|
+
parts.push(stateSection, '');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (artifactsText) {
|
|
134
|
+
parts.push('## Artefatos do projeto', artifactsText, '');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (hypothesesText) {
|
|
138
|
+
parts.push(hypothesesText, '');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (gapsText) {
|
|
142
|
+
parts.push(gapsText, '');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (qualityNote) {
|
|
146
|
+
parts.push(qualityNote.trim(), '');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
parts.push(
|
|
150
|
+
'## Instrução geral',
|
|
151
|
+
'Responda **em português**. Use evidência explícita dos artefatos acima — não invente estado. ' +
|
|
152
|
+
modeInstructions.join(' ') +
|
|
153
|
+
commandInstruction
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return parts.join('\n').trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = { build };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Retorna o caminho raiz do primeiro workspace folder, ou null.
|
|
8
|
+
* @param {readonly import('vscode').WorkspaceFolder[] | undefined} folders
|
|
9
|
+
* @returns {string | null}
|
|
10
|
+
*/
|
|
11
|
+
function getProjectRoot(folders) {
|
|
12
|
+
if (!folders || folders.length === 0) return null;
|
|
13
|
+
return folders[0].uri.fsPath;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Verifica se o projeto tem estrutura OXE inicializada.
|
|
18
|
+
* @param {string} projectRoot
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
function hasOxe(projectRoot) {
|
|
22
|
+
try {
|
|
23
|
+
return fs.existsSync(path.join(projectRoot, '.oxe', 'STATE.md'));
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Lê o conteúdo de STATE.md, retorna string vazia se ausente.
|
|
31
|
+
* @param {string} projectRoot
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function readState(projectRoot) {
|
|
35
|
+
try {
|
|
36
|
+
const statePath = path.join(projectRoot, '.oxe', 'STATE.md');
|
|
37
|
+
return fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '';
|
|
38
|
+
} catch {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extrai a fase atual do STATE.md (padrão: backtick ou campo `fase_atual:`).
|
|
45
|
+
* @param {string} stateText
|
|
46
|
+
* @returns {string | null}
|
|
47
|
+
*/
|
|
48
|
+
function parsePhase(stateText) {
|
|
49
|
+
// Padrão OXE: ## Fase atual \n\n `scan_complete`
|
|
50
|
+
const backtickMatch = stateText.match(/##\s*Fase\s*atual[\s\S]*?`([^`]+)`/im);
|
|
51
|
+
if (backtickMatch) return backtickMatch[1].trim();
|
|
52
|
+
// Fallback: fase_atual: valor
|
|
53
|
+
const fieldMatch = stateText.match(/fase_atual:\s*([^\n]+)/i);
|
|
54
|
+
return fieldMatch ? fieldMatch[1].trim() : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extrai a sessão ativa do STATE.md.
|
|
59
|
+
* @param {string} stateText
|
|
60
|
+
* @returns {string | null}
|
|
61
|
+
*/
|
|
62
|
+
function parseActiveSession(stateText) {
|
|
63
|
+
const m = stateText.match(/active_session:\s*([^\n]+)/i);
|
|
64
|
+
if (!m) return null;
|
|
65
|
+
const val = m[1].trim().replace(/["'`]/g, '');
|
|
66
|
+
return val === 'null' || val === '' ? null : val;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Extrai próximo passo recomendado do STATE.md.
|
|
71
|
+
* @param {string} stateText
|
|
72
|
+
* @returns {string | null}
|
|
73
|
+
*/
|
|
74
|
+
function parseNextStep(stateText) {
|
|
75
|
+
const m = stateText.match(/próximo[_\s]passo:\s*([^\n]+)/i)
|
|
76
|
+
|| stateText.match(/next[_\s]step:\s*([^\n]+)/i);
|
|
77
|
+
return m ? m[1].trim().replace(/["'`]/g, '') : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Retorna um resumo compacto do estado do projeto para o system prompt.
|
|
82
|
+
* @param {string} projectRoot
|
|
83
|
+
* @returns {{ text: string, phase: string | null, session: string | null, nextStep: string | null }}
|
|
84
|
+
*/
|
|
85
|
+
function getProjectContext(projectRoot) {
|
|
86
|
+
const text = readState(projectRoot);
|
|
87
|
+
const phase = parsePhase(text);
|
|
88
|
+
const session = parseActiveSession(text);
|
|
89
|
+
const nextStep = parseNextStep(text);
|
|
90
|
+
return { text: text.slice(0, 800), phase, session, nextStep };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
getProjectRoot,
|
|
95
|
+
hasOxe,
|
|
96
|
+
readState,
|
|
97
|
+
parsePhase,
|
|
98
|
+
parseActiveSession,
|
|
99
|
+
parseNextStep,
|
|
100
|
+
getProjectContext,
|
|
101
|
+
};
|