oxe-cc 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/commands/oxe-help.md +3 -9
- package/.cursor/commands/oxe-next.md +3 -9
- package/.cursor/commands/oxe-plan.md +5 -12
- package/.cursor/commands/oxe-review-pr.md +5 -12
- package/.cursor/commands/oxe-scan.md +5 -12
- package/.cursor/commands/oxe-spec.md +5 -12
- package/.cursor/commands/oxe-update.md +9 -0
- package/.cursor/commands/oxe-verify.md +5 -12
- package/.github/copilot-instructions.md +7 -5
- package/.github/prompts/oxe-help.prompt.md +1 -1
- package/.github/prompts/oxe-update.prompt.md +11 -0
- package/.github/workflows/ci.yml +20 -18
- package/AGENTS.md +5 -5
- package/README.md +167 -32
- package/assets/oxe-ciclo-por-trilha.png +0 -0
- package/bin/banner.txt +1 -1
- package/bin/lib/oxe-agent-install.cjs +87 -36
- package/bin/lib/oxe-npm-version.cjs +64 -0
- package/bin/lib/oxe-project-health.cjs +29 -1
- package/bin/oxe-cc.js +689 -121
- package/lib/sdk/index.cjs +1 -0
- package/oxe/templates/CONFIG.md +12 -0
- package/oxe/templates/DOCS_BROWNFIELD_LAYOUT.md +55 -0
- package/oxe/templates/SPEC.template.md +18 -0
- package/oxe/templates/WORKFLOW_AUTHORING.md +2 -0
- package/oxe/workflows/execute.md +1 -0
- package/oxe/workflows/help.md +13 -7
- package/oxe/workflows/plan.md +4 -0
- package/oxe/workflows/references/legacy-brownfield.md +136 -0
- package/oxe/workflows/scan.md +5 -3
- package/oxe/workflows/spec.md +2 -0
- package/oxe/workflows/update.md +33 -0
- package/oxe/workflows/verify.md +1 -0
- package/package.json +15 -4
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Referências: OpenCode (~/.config/opencode/commands), Gemini CLI (~/.gemini/commands/*.toml),
|
|
6
6
|
* Codex (~/.agents/skills + ~/.codex/prompts), Copilot (~/.copilot/skills), Antigravity (~/.gemini/antigravity/skills),
|
|
7
7
|
* Windsurf (~/.codeium/windsurf/global_workflows).
|
|
8
|
+
* Com `--ide-local`, destinos equivalentes sob a raiz do projeto (ex.: ./.opencode/commands).
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
const fs = require('fs');
|
|
@@ -21,6 +22,50 @@ function expandTilde(p) {
|
|
|
21
22
|
return p;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {{
|
|
27
|
+
* ideGlobal: boolean,
|
|
28
|
+
* opencodeCommandDirs: string[],
|
|
29
|
+
* geminiCommandsBase: string,
|
|
30
|
+
* windsurfWorkflowsDir: string,
|
|
31
|
+
* codexPromptsDir: string,
|
|
32
|
+
* codexAgentsSkillsRoot: string,
|
|
33
|
+
* antigravitySkillsRoot: string,
|
|
34
|
+
* }} AgentInstallPaths
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {boolean} ideGlobal
|
|
39
|
+
* @param {string} projectRoot
|
|
40
|
+
* @returns {AgentInstallPaths}
|
|
41
|
+
*/
|
|
42
|
+
function buildAgentInstallPaths(ideGlobal, projectRoot) {
|
|
43
|
+
const home = os.homedir();
|
|
44
|
+
const root = path.resolve(projectRoot);
|
|
45
|
+
if (ideGlobal) {
|
|
46
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
47
|
+
const codexHome = process.env.CODEX_HOME ? path.resolve(expandTilde(process.env.CODEX_HOME)) : path.join(home, '.codex');
|
|
48
|
+
return {
|
|
49
|
+
ideGlobal: true,
|
|
50
|
+
opencodeCommandDirs: [path.join(xdg, 'opencode', 'commands'), path.join(home, '.opencode', 'commands')],
|
|
51
|
+
geminiCommandsBase: path.join(home, '.gemini', 'commands'),
|
|
52
|
+
windsurfWorkflowsDir: path.join(home, '.codeium', 'windsurf', 'global_workflows'),
|
|
53
|
+
codexPromptsDir: path.join(codexHome, 'prompts'),
|
|
54
|
+
codexAgentsSkillsRoot: path.join(home, '.agents', 'skills'),
|
|
55
|
+
antigravitySkillsRoot: path.join(home, '.gemini', 'antigravity', 'skills'),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ideGlobal: false,
|
|
60
|
+
opencodeCommandDirs: [path.join(root, '.opencode', 'commands')],
|
|
61
|
+
geminiCommandsBase: path.join(root, '.gemini', 'commands'),
|
|
62
|
+
windsurfWorkflowsDir: path.join(root, '.windsurf', 'global_workflows'),
|
|
63
|
+
codexPromptsDir: path.join(root, '.codex', 'prompts'),
|
|
64
|
+
codexAgentsSkillsRoot: path.join(root, '.agents', 'skills'),
|
|
65
|
+
antigravitySkillsRoot: path.join(root, '.gemini', 'antigravity', 'skills'),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
24
69
|
/** @param {string} content */
|
|
25
70
|
function adjustWorkflowPathsForNestedLayout(content) {
|
|
26
71
|
return content
|
|
@@ -76,12 +121,11 @@ function buildAgentSkillMarkdown(skillName, description, body) {
|
|
|
76
121
|
* @returns {string[]}
|
|
77
122
|
*/
|
|
78
123
|
function opencodeCommandDirs() {
|
|
79
|
-
|
|
80
|
-
return [path.join(xdg, 'opencode', 'commands'), path.join(os.homedir(), '.opencode', 'commands')];
|
|
124
|
+
return buildAgentInstallPaths(true, process.cwd()).opencodeCommandDirs;
|
|
81
125
|
}
|
|
82
126
|
|
|
83
127
|
function windsurfGlobalWorkflowsDir() {
|
|
84
|
-
return
|
|
128
|
+
return buildAgentInstallPaths(true, process.cwd()).windsurfWorkflowsDir;
|
|
85
129
|
}
|
|
86
130
|
|
|
87
131
|
function geminiUserDir() {
|
|
@@ -89,16 +133,15 @@ function geminiUserDir() {
|
|
|
89
133
|
}
|
|
90
134
|
|
|
91
135
|
function codexAgentsSkillsRoot() {
|
|
92
|
-
return
|
|
136
|
+
return buildAgentInstallPaths(true, process.cwd()).codexAgentsSkillsRoot;
|
|
93
137
|
}
|
|
94
138
|
|
|
95
139
|
function codexPromptsDir() {
|
|
96
|
-
|
|
97
|
-
return path.join(home, 'prompts');
|
|
140
|
+
return buildAgentInstallPaths(true, process.cwd()).codexPromptsDir;
|
|
98
141
|
}
|
|
99
142
|
|
|
100
143
|
function antigravitySkillsRoot() {
|
|
101
|
-
return
|
|
144
|
+
return buildAgentInstallPaths(true, process.cwd()).antigravitySkillsRoot;
|
|
102
145
|
}
|
|
103
146
|
|
|
104
147
|
/**
|
|
@@ -169,10 +212,11 @@ function installSkillTreeFromCursorCommands(cCmdSrc, skillsRoot, opts, pathRewri
|
|
|
169
212
|
|
|
170
213
|
/**
|
|
171
214
|
* Copia .md dos comandos Cursor para pastas OpenCode (markdown nativo).
|
|
215
|
+
* @param {AgentInstallPaths} paths
|
|
172
216
|
*/
|
|
173
|
-
function installOpenCodeCommands(cCmdSrc, opts, pathRewriteNested, logOmitido, logWrite) {
|
|
217
|
+
function installOpenCodeCommands(cCmdSrc, paths, opts, pathRewriteNested, logOmitido, logWrite) {
|
|
174
218
|
if (!fs.existsSync(cCmdSrc)) return;
|
|
175
|
-
for (const destDir of opencodeCommandDirs
|
|
219
|
+
for (const destDir of paths.opencodeCommandDirs) {
|
|
176
220
|
for (const name of fs.readdirSync(cCmdSrc)) {
|
|
177
221
|
if (!name.startsWith('oxe-') || !name.endsWith('.md')) continue;
|
|
178
222
|
const src = path.join(cCmdSrc, name);
|
|
@@ -195,10 +239,11 @@ function installOpenCodeCommands(cCmdSrc, opts, pathRewriteNested, logOmitido, l
|
|
|
195
239
|
|
|
196
240
|
/**
|
|
197
241
|
* ~/.gemini/commands/oxe.toml → /oxe ; oxe/scan.toml → /oxe:scan
|
|
242
|
+
* @param {AgentInstallPaths} paths
|
|
198
243
|
*/
|
|
199
|
-
function installGeminiTomlCommands(cCmdSrc, opts, pathRewriteNested, logOmitido, logWrite) {
|
|
244
|
+
function installGeminiTomlCommands(cCmdSrc, paths, opts, pathRewriteNested, logOmitido, logWrite) {
|
|
200
245
|
if (!fs.existsSync(cCmdSrc)) return;
|
|
201
|
-
const base =
|
|
246
|
+
const base = paths.geminiCommandsBase;
|
|
202
247
|
|
|
203
248
|
const writeToml = (relPath, srcPath, descSuffix) => {
|
|
204
249
|
let raw = fs.readFileSync(srcPath, 'utf8');
|
|
@@ -233,11 +278,12 @@ function installGeminiTomlCommands(cCmdSrc, opts, pathRewriteNested, logOmitido,
|
|
|
233
278
|
}
|
|
234
279
|
|
|
235
280
|
/**
|
|
236
|
-
* Windsurf Cascade: workflows globais
|
|
281
|
+
* Windsurf Cascade: workflows globais ou ./.windsurf/global_workflows (local).
|
|
282
|
+
* @param {AgentInstallPaths} paths
|
|
237
283
|
*/
|
|
238
|
-
function installWindsurfGlobalWorkflows(cCmdSrc, opts, pathRewriteNested, logOmitido, logWrite) {
|
|
284
|
+
function installWindsurfGlobalWorkflows(cCmdSrc, paths, opts, pathRewriteNested, logOmitido, logWrite) {
|
|
239
285
|
if (!fs.existsSync(cCmdSrc)) return;
|
|
240
|
-
const destDir =
|
|
286
|
+
const destDir = paths.windsurfWorkflowsDir;
|
|
241
287
|
for (const name of fs.readdirSync(cCmdSrc)) {
|
|
242
288
|
if (!name.startsWith('oxe-') || !name.endsWith('.md')) continue;
|
|
243
289
|
const src = path.join(cCmdSrc, name);
|
|
@@ -287,11 +333,12 @@ function installWindsurfGlobalWorkflows(cCmdSrc, opts, pathRewriteNested, logOmi
|
|
|
287
333
|
}
|
|
288
334
|
|
|
289
335
|
/**
|
|
290
|
-
* Codex: ~/.codex/prompts
|
|
336
|
+
* Codex: prompts em ~/.codex/prompts ou ./.codex/prompts (local).
|
|
337
|
+
* @param {AgentInstallPaths} paths
|
|
291
338
|
*/
|
|
292
|
-
function installCodexPrompts(cCmdSrc, opts, pathRewriteNested, logOmitido, logWrite) {
|
|
339
|
+
function installCodexPrompts(cCmdSrc, paths, opts, pathRewriteNested, logOmitido, logWrite) {
|
|
293
340
|
if (!fs.existsSync(cCmdSrc)) return;
|
|
294
|
-
const destDir = codexPromptsDir
|
|
341
|
+
const destDir = paths.codexPromptsDir;
|
|
295
342
|
for (const name of fs.readdirSync(cCmdSrc)) {
|
|
296
343
|
if (!name.startsWith('oxe-') || !name.endsWith('.md')) continue;
|
|
297
344
|
const src = path.join(cCmdSrc, name);
|
|
@@ -322,13 +369,16 @@ function installCodexPrompts(cCmdSrc, opts, pathRewriteNested, logOmitido, logWr
|
|
|
322
369
|
/**
|
|
323
370
|
* Remove apenas ficheiros/pastas criados pelo oxe-cc (marcadores).
|
|
324
371
|
* @param {{ dryRun: boolean }} u
|
|
372
|
+
* @param {AgentInstallPaths} [paths] omissão = instalação global (HOME)
|
|
325
373
|
*/
|
|
326
|
-
function cleanupMarkedUnifiedArtifacts(u) {
|
|
327
|
-
const
|
|
328
|
-
|
|
374
|
+
function cleanupMarkedUnifiedArtifacts(u, paths) {
|
|
375
|
+
const p = paths || buildAgentInstallPaths(true, process.cwd());
|
|
376
|
+
|
|
377
|
+
const unlinkQuiet = (filePath) => {
|
|
378
|
+
if (!fs.existsSync(filePath)) return;
|
|
329
379
|
if (u.dryRun) return;
|
|
330
380
|
try {
|
|
331
|
-
fs.unlinkSync(
|
|
381
|
+
fs.unlinkSync(filePath);
|
|
332
382
|
} catch {
|
|
333
383
|
/* ignore */
|
|
334
384
|
}
|
|
@@ -352,22 +402,22 @@ function cleanupMarkedUnifiedArtifacts(u) {
|
|
|
352
402
|
}
|
|
353
403
|
};
|
|
354
404
|
|
|
355
|
-
for (const dir of opencodeCommandDirs
|
|
405
|
+
for (const dir of p.opencodeCommandDirs) {
|
|
356
406
|
if (!fs.existsSync(dir)) continue;
|
|
357
407
|
for (const name of fs.readdirSync(dir)) {
|
|
358
408
|
if (!name.startsWith('oxe-') || !name.endsWith('.md')) continue;
|
|
359
|
-
const
|
|
409
|
+
const filePath = path.join(dir, name);
|
|
360
410
|
let txt = '';
|
|
361
411
|
try {
|
|
362
|
-
txt = fs.readFileSync(
|
|
412
|
+
txt = fs.readFileSync(filePath, 'utf8');
|
|
363
413
|
} catch {
|
|
364
414
|
continue;
|
|
365
415
|
}
|
|
366
|
-
if (txt.includes(OXE_MANAGED_HTML)) unlinkQuiet(
|
|
416
|
+
if (txt.includes(OXE_MANAGED_HTML)) unlinkQuiet(filePath);
|
|
367
417
|
}
|
|
368
418
|
}
|
|
369
419
|
|
|
370
|
-
const gBase =
|
|
420
|
+
const gBase = p.geminiCommandsBase;
|
|
371
421
|
const oxeToml = path.join(gBase, 'oxe.toml');
|
|
372
422
|
if (fs.existsSync(oxeToml)) {
|
|
373
423
|
try {
|
|
@@ -380,9 +430,9 @@ function cleanupMarkedUnifiedArtifacts(u) {
|
|
|
380
430
|
if (fs.existsSync(oxeSub)) {
|
|
381
431
|
for (const name of fs.readdirSync(oxeSub)) {
|
|
382
432
|
if (!name.endsWith('.toml')) continue;
|
|
383
|
-
const
|
|
433
|
+
const filePath = path.join(oxeSub, name);
|
|
384
434
|
try {
|
|
385
|
-
if (fs.readFileSync(
|
|
435
|
+
if (fs.readFileSync(filePath, 'utf8').includes(OXE_MANAGED_TOML)) unlinkQuiet(filePath);
|
|
386
436
|
} catch {
|
|
387
437
|
/* ignore */
|
|
388
438
|
}
|
|
@@ -394,33 +444,33 @@ function cleanupMarkedUnifiedArtifacts(u) {
|
|
|
394
444
|
}
|
|
395
445
|
}
|
|
396
446
|
|
|
397
|
-
const wfDir =
|
|
447
|
+
const wfDir = p.windsurfWorkflowsDir;
|
|
398
448
|
if (fs.existsSync(wfDir)) {
|
|
399
449
|
for (const name of fs.readdirSync(wfDir)) {
|
|
400
450
|
if (name !== 'oxe.md' && !(name.startsWith('oxe-') && name.endsWith('.md'))) continue;
|
|
401
|
-
const
|
|
451
|
+
const filePath = path.join(wfDir, name);
|
|
402
452
|
try {
|
|
403
|
-
if (fs.readFileSync(
|
|
453
|
+
if (fs.readFileSync(filePath, 'utf8').includes(OXE_MANAGED_HTML)) unlinkQuiet(filePath);
|
|
404
454
|
} catch {
|
|
405
455
|
/* ignore */
|
|
406
456
|
}
|
|
407
457
|
}
|
|
408
458
|
}
|
|
409
459
|
|
|
410
|
-
const cpDir = codexPromptsDir
|
|
460
|
+
const cpDir = p.codexPromptsDir;
|
|
411
461
|
if (fs.existsSync(cpDir)) {
|
|
412
462
|
for (const name of fs.readdirSync(cpDir)) {
|
|
413
463
|
if (!name.startsWith('oxe-') || !name.endsWith('.md')) continue;
|
|
414
|
-
const
|
|
464
|
+
const filePath = path.join(cpDir, name);
|
|
415
465
|
try {
|
|
416
|
-
if (fs.readFileSync(
|
|
466
|
+
if (fs.readFileSync(filePath, 'utf8').includes(OXE_MANAGED_HTML)) unlinkQuiet(filePath);
|
|
417
467
|
} catch {
|
|
418
468
|
/* ignore */
|
|
419
469
|
}
|
|
420
470
|
}
|
|
421
471
|
}
|
|
422
472
|
|
|
423
|
-
const agRoot = antigravitySkillsRoot
|
|
473
|
+
const agRoot = p.antigravitySkillsRoot;
|
|
424
474
|
if (fs.existsSync(agRoot)) {
|
|
425
475
|
for (const name of fs.readdirSync(agRoot, { withFileTypes: true })) {
|
|
426
476
|
if (!name.isDirectory()) continue;
|
|
@@ -429,7 +479,7 @@ function cleanupMarkedUnifiedArtifacts(u) {
|
|
|
429
479
|
}
|
|
430
480
|
}
|
|
431
481
|
|
|
432
|
-
const cxRoot = codexAgentsSkillsRoot
|
|
482
|
+
const cxRoot = p.codexAgentsSkillsRoot;
|
|
433
483
|
if (fs.existsSync(cxRoot)) {
|
|
434
484
|
for (const name of fs.readdirSync(cxRoot, { withFileTypes: true })) {
|
|
435
485
|
if (!name.isDirectory()) continue;
|
|
@@ -442,6 +492,7 @@ function cleanupMarkedUnifiedArtifacts(u) {
|
|
|
442
492
|
module.exports = {
|
|
443
493
|
OXE_MANAGED_HTML,
|
|
444
494
|
OXE_MANAGED_TOML,
|
|
495
|
+
buildAgentInstallPaths,
|
|
445
496
|
adjustWorkflowPathsForNestedLayout,
|
|
446
497
|
parseCursorCommandFrontmatter,
|
|
447
498
|
buildAgentSkillMarkdown,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const semver = require('semver');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extrai versão semver do stdout de `npm view <pkg> version`.
|
|
8
|
+
* @param {string} stdout
|
|
9
|
+
* @returns {string | null}
|
|
10
|
+
*/
|
|
11
|
+
function parseNpmViewVersion(stdout) {
|
|
12
|
+
if (!stdout || typeof stdout !== 'string') return null;
|
|
13
|
+
const line = stdout.trim().split(/\r?\n/)[0].trim();
|
|
14
|
+
if (!line) return null;
|
|
15
|
+
let candidate = line;
|
|
16
|
+
if ((candidate.startsWith('"') && candidate.endsWith('"')) || (candidate.startsWith("'") && candidate.endsWith("'"))) {
|
|
17
|
+
try {
|
|
18
|
+
candidate = JSON.parse(candidate);
|
|
19
|
+
} catch {
|
|
20
|
+
candidate = line.slice(1, -1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
candidate = String(candidate).replace(/^v/i, '').trim();
|
|
24
|
+
const coerced = semver.coerce(candidate);
|
|
25
|
+
return coerced ? coerced.version : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} latest
|
|
30
|
+
* @param {string} current
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
function isNewerThan(latest, current) {
|
|
34
|
+
if (!semver.valid(latest) || !semver.valid(current)) return false;
|
|
35
|
+
return semver.gt(latest, current);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} packageName
|
|
40
|
+
* @param {Record<string, string | undefined>} [spawnOpts] opções extra para spawnSync (ex.: env)
|
|
41
|
+
* @returns {{ ok: true, version: string } | { ok: false, error: string }}
|
|
42
|
+
*/
|
|
43
|
+
function syncNpmViewVersion(packageName, spawnOpts = {}) {
|
|
44
|
+
const r = spawnSync('npm', ['view', packageName, 'version'], {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
env: process.env,
|
|
47
|
+
shell: process.platform === 'win32',
|
|
48
|
+
...spawnOpts,
|
|
49
|
+
});
|
|
50
|
+
if (r.error) return { ok: false, error: r.error.message || String(r.error) };
|
|
51
|
+
if (r.status !== 0 && r.status !== null) {
|
|
52
|
+
const err = (r.stderr || r.stdout || '').trim() || `npm view exited ${r.status}`;
|
|
53
|
+
return { ok: false, error: err };
|
|
54
|
+
}
|
|
55
|
+
const v = parseNpmViewVersion(r.stdout || '');
|
|
56
|
+
if (!v) return { ok: false, error: 'Não foi possível interpretar a versão devolvida pelo npm.' };
|
|
57
|
+
return { ok: true, version: v };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
parseNpmViewVersion,
|
|
62
|
+
isNewerThan,
|
|
63
|
+
syncNpmViewVersion,
|
|
64
|
+
};
|
|
@@ -254,6 +254,30 @@ function specSectionWarnings(specPath, requiredHeadings) {
|
|
|
254
254
|
return out;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Avisos quando uma tarefa `### Tn` em PLAN.md não tem linha **Aceite vinculado:** no seu bloco.
|
|
259
|
+
* @param {string} planPath
|
|
260
|
+
* @returns {string[]}
|
|
261
|
+
*/
|
|
262
|
+
function planTaskAceiteWarnings(planPath) {
|
|
263
|
+
if (!fs.existsSync(planPath)) return [];
|
|
264
|
+
const raw = fs.readFileSync(planPath, 'utf8');
|
|
265
|
+
const parts = raw.split(/^###\s+(T\d+)\s+/m);
|
|
266
|
+
if (parts.length < 3) return [];
|
|
267
|
+
/** @type {string[]} */
|
|
268
|
+
const out = [];
|
|
269
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
270
|
+
const taskId = parts[i];
|
|
271
|
+
const body = parts[i + 1] || '';
|
|
272
|
+
if (!/\*\*Aceite\s+vinculado:\*\*/i.test(body)) {
|
|
273
|
+
out.push(
|
|
274
|
+
`PLAN.md: tarefa ${taskId} sem linha **Aceite vinculado:** — ligue cada Tn aos critérios A* da SPEC (ou declare gap explícito no plano)`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
|
|
257
281
|
function planWaveWarningsFixed(planPath, maxPerWave) {
|
|
258
282
|
if (!maxPerWave || maxPerWave <= 0 || !fs.existsSync(planPath)) return [];
|
|
259
283
|
const raw = fs.readFileSync(planPath, 'utf8');
|
|
@@ -422,7 +446,10 @@ function buildHealthReport(target) {
|
|
|
422
446
|
const sumWarn = verifyGapsWithoutSummaryWarning(p.verify, p.summary);
|
|
423
447
|
const specReq = Array.isArray(config.spec_required_sections) ? config.spec_required_sections : [];
|
|
424
448
|
const specWarn = specSectionWarnings(p.spec, specReq.map(String));
|
|
425
|
-
const planWarn =
|
|
449
|
+
const planWarn = [
|
|
450
|
+
...planWaveWarningsFixed(p.plan, Number(config.plan_max_tasks_per_wave) || 0),
|
|
451
|
+
...planTaskAceiteWarnings(p.plan),
|
|
452
|
+
];
|
|
426
453
|
const next = suggestNextStep(target, { discuss_before_plan: config.discuss_before_plan });
|
|
427
454
|
|
|
428
455
|
return {
|
|
@@ -458,6 +485,7 @@ module.exports = {
|
|
|
458
485
|
verifyGapsWithoutSummaryWarning,
|
|
459
486
|
specSectionWarnings,
|
|
460
487
|
planWaveWarningsFixed,
|
|
488
|
+
planTaskAceiteWarnings,
|
|
461
489
|
suggestNextStep,
|
|
462
490
|
buildHealthReport,
|
|
463
491
|
oxePaths,
|