up-cc 0.2.3 → 0.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/bin/up-tools.cjs CHANGED
@@ -8,10 +8,10 @@
8
8
  * Usage: node up-tools.cjs <command> [args] [--raw] [--cwd <path>]
9
9
  *
10
10
  * Commands:
11
- * init planejar-fase|executar-fase|novo-projeto|rapido|retomar|operacao-fase|progresso|verificar-trabalho
11
+ * init planejar-fase|executar-fase|novo-projeto|rapido|retomar|operacao-fase|progresso|verificar-trabalho|melhorias|ideias
12
12
  * state load|get|update|advance-plan|update-progress|add-decision|record-session|record-metric|snapshot
13
13
  * roadmap get-phase|analyze|update-plan-progress
14
- * phase add|remove|find|complete
14
+ * phase add|remove|find|complete|generate-from-report
15
15
  * config get|set
16
16
  * requirements mark-complete
17
17
  * commit <msg> --files
@@ -200,8 +200,14 @@ function main() {
200
200
  case 'verificar-trabalho':
201
201
  cmdInitVerificarTrabalho(cwd, args[2], raw);
202
202
  break;
203
+ case 'melhorias':
204
+ cmdInitMelhorias(cwd, raw);
205
+ break;
206
+ case 'ideias':
207
+ cmdInitIdeias(cwd, raw);
208
+ break;
203
209
  default:
204
- error(`Unknown init workflow: ${workflow}\nAvailable: planejar-fase, executar-fase, novo-projeto, rapido, retomar, operacao-fase, progresso, verificar-trabalho`);
210
+ error(`Unknown init workflow: ${workflow}\nAvailable: planejar-fase, executar-fase, novo-projeto, rapido, retomar, operacao-fase, progresso, verificar-trabalho, melhorias, ideias`);
205
211
  }
206
212
  break;
207
213
  }
@@ -279,8 +285,10 @@ function main() {
279
285
  cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
280
286
  } else if (sub === 'complete') {
281
287
  cmdPhaseComplete(cwd, args[2], raw);
288
+ } else if (sub === 'generate-from-report') {
289
+ cmdPhaseGenerateFromReport(cwd, args.slice(2), raw);
282
290
  } else {
283
- error('Unknown phase subcommand. Available: find, add, remove, complete');
291
+ error('Unknown phase subcommand. Available: find, add, remove, complete, generate-from-report');
284
292
  }
285
293
  break;
286
294
  }
@@ -693,6 +701,82 @@ function cmdInitVerificarTrabalho(cwd, phase, raw) {
693
701
  output(result, raw);
694
702
  }
695
703
 
704
+ function cmdInitMelhorias(cwd, raw) {
705
+ const config = loadConfig(cwd);
706
+ const now = new Date();
707
+
708
+ // Detectar stack hints do projeto para ajustar auditoria
709
+ const pkgPath = path.join(cwd, 'package.json');
710
+ let stackHints = {};
711
+ try {
712
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
713
+ const allDeps = Object.assign({}, pkg.dependencies || {}, pkg.devDependencies || {});
714
+ stackHints = {
715
+ has_react: !!allDeps.react,
716
+ has_next: !!allDeps.next,
717
+ has_vue: !!allDeps.vue,
718
+ has_nuxt: !!allDeps.nuxt,
719
+ has_svelte: !!allDeps.svelte,
720
+ has_tailwind: !!allDeps.tailwindcss,
721
+ has_prisma: !!(allDeps['@prisma/client'] || allDeps.prisma),
722
+ has_typescript: !!(allDeps.typescript || pathExistsInternal(cwd, 'tsconfig.json')),
723
+ type_module: pkg.type === 'module',
724
+ };
725
+ } catch {}
726
+
727
+ const result = {
728
+ planning_exists: pathExistsInternal(cwd, '.plano'),
729
+ melhorias_dir: '.plano/melhorias',
730
+ melhorias_exists: pathExistsInternal(cwd, '.plano/melhorias'),
731
+ has_claude_md: pathExistsInternal(cwd, 'CLAUDE.md'),
732
+ has_package_json: pathExistsInternal(cwd, 'package.json'),
733
+ date: now.toISOString().split('T')[0],
734
+ timestamp: now.toISOString(),
735
+ commit_docs: config.commit_docs,
736
+ stack_hints: stackHints,
737
+ };
738
+
739
+ output(result, raw);
740
+ }
741
+
742
+ function cmdInitIdeias(cwd, raw) {
743
+ const config = loadConfig(cwd);
744
+ const now = new Date();
745
+
746
+ // Detectar stack hints do projeto para contextualizar analise
747
+ const pkgPath = path.join(cwd, 'package.json');
748
+ let stackHints = {};
749
+ try {
750
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
751
+ const allDeps = Object.assign({}, pkg.dependencies || {}, pkg.devDependencies || {});
752
+ stackHints = {
753
+ has_react: !!allDeps.react,
754
+ has_next: !!allDeps.next,
755
+ has_vue: !!allDeps.vue,
756
+ has_nuxt: !!allDeps.nuxt,
757
+ has_svelte: !!allDeps.svelte,
758
+ has_tailwind: !!allDeps.tailwindcss,
759
+ has_prisma: !!(allDeps['@prisma/client'] || allDeps.prisma),
760
+ has_typescript: !!(allDeps.typescript || pathExistsInternal(cwd, 'tsconfig.json')),
761
+ type_module: pkg.type === 'module',
762
+ };
763
+ } catch {}
764
+
765
+ const result = {
766
+ planning_exists: pathExistsInternal(cwd, '.plano'),
767
+ ideias_dir: '.plano/ideias',
768
+ ideias_exists: pathExistsInternal(cwd, '.plano/ideias'),
769
+ has_claude_md: pathExistsInternal(cwd, 'CLAUDE.md'),
770
+ has_package_json: pathExistsInternal(cwd, 'package.json'),
771
+ date: now.toISOString().split('T')[0],
772
+ timestamp: now.toISOString(),
773
+ commit_docs: config.commit_docs,
774
+ stack_hints: stackHints,
775
+ };
776
+
777
+ output(result, raw);
778
+ }
779
+
696
780
  // =====================================================================
697
781
  // STATE COMMANDS
698
782
  // =====================================================================
@@ -1019,7 +1103,7 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
1019
1103
  const content = fs.readFileSync(roadmapPath, 'utf-8');
1020
1104
  const escapedPhase = escapeRegex(phaseNum);
1021
1105
 
1022
- const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
1106
+ const phasePattern = new RegExp(`#{2,4}\\s*(?:Phase|Fase)\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
1023
1107
  const headerMatch = content.match(phasePattern);
1024
1108
 
1025
1109
  if (!headerMatch) {
@@ -1030,11 +1114,11 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
1030
1114
  const phaseName = headerMatch[1].trim();
1031
1115
  const headerIndex = headerMatch.index;
1032
1116
  const restOfContent = content.slice(headerIndex);
1033
- const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
1117
+ const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+(?:Phase|Fase)\s+\d/i);
1034
1118
  const sectionEnd = nextHeaderMatch ? headerIndex + nextHeaderMatch.index : content.length;
1035
1119
  const section = content.slice(headerIndex, sectionEnd).trim();
1036
1120
 
1037
- const goalMatch = section.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
1121
+ const goalMatch = section.match(/\*\*(?:Goal|Objetivo):\*\*\s*([^\n]+)/i);
1038
1122
  const goal = goalMatch ? goalMatch[1].trim() : null;
1039
1123
 
1040
1124
  output({ found: true, phase_number: phaseNum, phase_name: phaseName, goal, section }, raw, section);
@@ -1054,7 +1138,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
1054
1138
  const content = fs.readFileSync(roadmapPath, 'utf-8');
1055
1139
  const phasesDir = path.join(cwd, '.plano', 'fases');
1056
1140
 
1057
- const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
1141
+ const phasePattern = /#{2,4}\s*(?:Phase|Fase)\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
1058
1142
  const phases = [];
1059
1143
  let match;
1060
1144
 
@@ -1064,11 +1148,11 @@ function cmdRoadmapAnalyze(cwd, raw) {
1064
1148
 
1065
1149
  const sectionStart = match.index;
1066
1150
  const restOfContent = content.slice(sectionStart);
1067
- const nextHeader = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
1151
+ const nextHeader = restOfContent.match(/\n#{2,4}\s+(?:Phase|Fase)\s+\d/i);
1068
1152
  const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
1069
1153
  const section = content.slice(sectionStart, sectionEnd);
1070
1154
 
1071
- const goalMatch = section.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
1155
+ const goalMatch = section.match(/\*\*(?:Goal|Objetivo):\*\*\s*([^\n]+)/i);
1072
1156
  const goal = goalMatch ? goalMatch[1].trim() : null;
1073
1157
 
1074
1158
  const normalized = normalizePhaseName(phaseNum);
@@ -1093,7 +1177,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
1093
1177
  }
1094
1178
  } catch {}
1095
1179
 
1096
- const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${escapeRegex(phaseNum)}`, 'i');
1180
+ const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*(?:Phase|Fase)\\s+${escapeRegex(phaseNum)}`, 'i');
1097
1181
  const checkboxMatch = content.match(checkboxPattern);
1098
1182
  const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
1099
1183
 
@@ -1167,7 +1251,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1167
1251
 
1168
1252
  // Update plan count in phase detail section
1169
1253
  const planCountPattern = new RegExp(
1170
- `(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
1254
+ `(#{2,4}\\s*(?:Phase|Fase)\\s+${phaseEscaped}[\\s\\S]*?\\*\\*(?:Plans|Planos):\\*\\*\\s*)[^\\n]+`,
1171
1255
  'i'
1172
1256
  );
1173
1257
  const planCountText = isComplete
@@ -1177,7 +1261,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1177
1261
 
1178
1262
  if (isComplete) {
1179
1263
  const checkboxPattern = new RegExp(
1180
- `(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
1264
+ `(-\\s*\\[)[ ](\\]\\s*.*(?:Phase|Fase)\\s+${phaseEscaped}[:\\s][^\\n]*)`,
1181
1265
  'i'
1182
1266
  );
1183
1267
  roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
@@ -1243,7 +1327,7 @@ function cmdPhaseAdd(cwd, description, raw) {
1243
1327
  const content = fs.readFileSync(roadmapPath, 'utf-8');
1244
1328
  const slug = generateSlugInternal(description);
1245
1329
 
1246
- const phasePattern = /#{2,4}\s*Phase\s+(\d+)[A-Z]?(?:\.\d+)*:/gi;
1330
+ const phasePattern = /#{2,4}\s*(?:Phase|Fase)\s+(\d+)[A-Z]?(?:\.\d+)*:/gi;
1247
1331
  let maxPhase = 0;
1248
1332
  let m;
1249
1333
  while ((m = phasePattern.exec(content)) !== null) {
@@ -1259,7 +1343,11 @@ function cmdPhaseAdd(cwd, description, raw) {
1259
1343
  fs.mkdirSync(dirPath, { recursive: true });
1260
1344
  fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
1261
1345
 
1262
- const phaseEntry = `\n### Phase ${newPhaseNum}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${maxPhase}\n**Plans:** 0 plans\n`;
1346
+ // Detect ROADMAP language: if it contains '### Fase ' use Portuguese, otherwise English
1347
+ const usePt = /###\s*Fase\s+\d/.test(content);
1348
+ const phaseEntry = usePt
1349
+ ? `\n### Fase ${newPhaseNum}: ${description}\n\n**Objetivo:** [A ser planejado]\n**Requisitos**: TBD\n**Depende de:** Fase ${maxPhase}\n**Planos:** 0 planos\n`
1350
+ : `\n### Phase ${newPhaseNum}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${maxPhase}\n**Plans:** 0 plans\n`;
1263
1351
 
1264
1352
  let updatedContent;
1265
1353
  const lastSeparator = content.lastIndexOf('\n---');
@@ -1357,12 +1445,12 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
1357
1445
 
1358
1446
  const targetEscaped = escapeRegex(targetPhase);
1359
1447
  const sectionPattern = new RegExp(
1360
- `\\n?#{2,4}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`,
1448
+ `\\n?#{2,4}\\s*(?:Phase|Fase)\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+(?:Phase|Fase)\\s+\\d|$)`,
1361
1449
  'i'
1362
1450
  );
1363
1451
  roadmapContent = roadmapContent.replace(sectionPattern, '');
1364
1452
 
1365
- const checkboxPattern = new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${targetEscaped}[:\\s][^\\n]*`, 'gi');
1453
+ const checkboxPattern = new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*(?:Phase|Fase)\\s+${targetEscaped}[:\\s][^\\n]*`, 'gi');
1366
1454
  roadmapContent = roadmapContent.replace(checkboxPattern, '');
1367
1455
 
1368
1456
  if (!isDecimal) {
@@ -1373,11 +1461,11 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
1373
1461
  const newStr = String(newNum);
1374
1462
 
1375
1463
  roadmapContent = roadmapContent.replace(
1376
- new RegExp(`(#{2,4}\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'),
1464
+ new RegExp(`(#{2,4}\\s*(?:Phase|Fase)\\s+)${oldStr}(\\s*:)`, 'gi'),
1377
1465
  `$1${newStr}$2`
1378
1466
  );
1379
1467
  roadmapContent = roadmapContent.replace(
1380
- new RegExp(`(Phase\\s+)${oldStr}([:\\s])`, 'g'),
1468
+ new RegExp(`((?:Phase|Fase)\\s+)${oldStr}([:\\s])`, 'g'),
1381
1469
  `$1${newStr}$2`
1382
1470
  );
1383
1471
  }
@@ -1426,13 +1514,13 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1426
1514
  const phaseEscaped = escapeRegex(phaseNum);
1427
1515
 
1428
1516
  const checkboxPattern = new RegExp(
1429
- `(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
1517
+ `(-\\s*\\[)[ ](\\]\\s*.*(?:Phase|Fase)\\s+${phaseEscaped}[:\\s][^\\n]*)`,
1430
1518
  'i'
1431
1519
  );
1432
1520
  roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
1433
1521
 
1434
1522
  const planCountPattern = new RegExp(
1435
- `(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
1523
+ `(#{2,4}\\s*(?:Phase|Fase)\\s+${phaseEscaped}[\\s\\S]*?\\*\\*(?:Plans|Planos):\\*\\*\\s*)[^\\n]+`,
1436
1524
  'i'
1437
1525
  );
1438
1526
  roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
@@ -1443,7 +1531,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1443
1531
  const reqPath = path.join(cwd, '.plano', 'REQUIREMENTS.md');
1444
1532
  if (fs.existsSync(reqPath)) {
1445
1533
  const reqMatch = roadmapContent.match(
1446
- new RegExp(`Phase\\s+${escapeRegex(phaseNum)}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, 'i')
1534
+ new RegExp(`(?:Phase|Fase)\\s+${escapeRegex(phaseNum)}[\\s\\S]*?\\*\\*(?:Requirements|Requisitos):\\*\\*\\s*([^\\n]+)`, 'i')
1447
1535
  );
1448
1536
  if (reqMatch) {
1449
1537
  const reqIds = reqMatch[1].replace(/[\[\]]/g, '').split(/[,\s]+/).map(r => r.trim()).filter(Boolean);
@@ -1485,7 +1573,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1485
1573
  if (isLastPhase && fs.existsSync(roadmapPath)) {
1486
1574
  try {
1487
1575
  const roadmapForPhases = fs.readFileSync(roadmapPath, 'utf-8');
1488
- const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
1576
+ const phasePattern = /#{2,4}\s*(?:Phase|Fase)\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
1489
1577
  let pm;
1490
1578
  while ((pm = phasePattern.exec(roadmapForPhases)) !== null) {
1491
1579
  if (comparePhaseNum(pm[1], phaseNum) > 0) {
@@ -1526,6 +1614,385 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
1526
1614
  }, raw);
1527
1615
  }
1528
1616
 
1617
+ // --- Phase Generate From Report ---
1618
+
1619
+ function cmdPhaseGenerateFromReport(cwd, args, raw) {
1620
+ // Parse input: try stdin JSON first, fall back to args
1621
+ let source = null;
1622
+ let reportPath = null;
1623
+ let approvedIds = [];
1624
+ let grouping = 'auto';
1625
+
1626
+ let stdinData = null;
1627
+ try {
1628
+ const stdinRaw = fs.readFileSync('/dev/stdin', 'utf-8').trim();
1629
+ if (stdinRaw) {
1630
+ stdinData = JSON.parse(stdinRaw);
1631
+ }
1632
+ } catch {
1633
+ // stdin not available or not JSON -- use args
1634
+ }
1635
+
1636
+ if (stdinData) {
1637
+ source = stdinData.source || null;
1638
+ reportPath = stdinData.report_path || null;
1639
+ approvedIds = stdinData.approved_ids || [];
1640
+ grouping = stdinData.grouping || 'auto';
1641
+ } else {
1642
+ source = args[0] || null;
1643
+ reportPath = args[1] || null;
1644
+ const idsArg = args.slice(2).join(',');
1645
+ approvedIds = idsArg ? idsArg.split(',').map(s => s.trim()).filter(Boolean) : [];
1646
+ // Check for --grouping flag
1647
+ const groupIdx = args.indexOf('--grouping');
1648
+ if (groupIdx !== -1 && args[groupIdx + 1]) {
1649
+ grouping = args[groupIdx + 1];
1650
+ }
1651
+ }
1652
+
1653
+ if (!source) error('source required (melhorias or ideias)');
1654
+ if (!reportPath) error('report_path required');
1655
+ if (approvedIds.length === 0) error('approved_ids required (at least one ID)');
1656
+
1657
+ // Read the report file
1658
+ const fullReportPath = path.join(cwd, reportPath);
1659
+ if (!fs.existsSync(fullReportPath)) {
1660
+ error(`Report file not found: ${reportPath}`);
1661
+ }
1662
+ const reportContent = fs.readFileSync(fullReportPath, 'utf-8');
1663
+
1664
+ // Parse suggestions from report
1665
+ const suggestions = parseSuggestionsFromReport(reportContent, approvedIds);
1666
+
1667
+ if (suggestions.length === 0) {
1668
+ error(`No approved suggestions found in report. IDs requested: ${approvedIds.join(', ')}`);
1669
+ }
1670
+
1671
+ // Group suggestions into phases
1672
+ const groups = grouping === 'single'
1673
+ ? [{ name: buildGroupName(source, suggestions), suggestions }]
1674
+ : groupSuggestionsByDimension(suggestions, source);
1675
+
1676
+ // Read ROADMAP to detect language and max phase
1677
+ const roadmapPath = path.join(cwd, '.plano', 'ROADMAP.md');
1678
+ if (!fs.existsSync(roadmapPath)) error('ROADMAP.md not found');
1679
+ let roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
1680
+
1681
+ const usePt = /###\s*Fase\s+\d/.test(roadmapContent);
1682
+
1683
+ // Find max phase number
1684
+ const phaseNumPattern = /#{2,4}\s*(?:Phase|Fase)\s+(\d+)[A-Z]?(?:\.\d+)*:/gi;
1685
+ let maxPhase = 0;
1686
+ let pm;
1687
+ while ((pm = phaseNumPattern.exec(roadmapContent)) !== null) {
1688
+ const num = parseInt(pm[1], 10);
1689
+ if (num > maxPhase) maxPhase = num;
1690
+ }
1691
+
1692
+ const phasesCreated = [];
1693
+
1694
+ for (const group of groups) {
1695
+ const newPhaseNum = maxPhase + 1;
1696
+ maxPhase = newPhaseNum;
1697
+ const paddedNum = String(newPhaseNum).padStart(2, '0');
1698
+ const slug = generateSlugInternal(group.name).substring(0, 50);
1699
+ const dirName = `${paddedNum}-${slug}`;
1700
+ const dirPath = path.join(cwd, '.plano', 'fases', dirName);
1701
+
1702
+ // Create phase directory
1703
+ fs.mkdirSync(dirPath, { recursive: true });
1704
+ fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
1705
+
1706
+ // Build criteria
1707
+ const criteria = buildCriteria(group.suggestions, source, usePt);
1708
+
1709
+ // Build suggestion list
1710
+ const suggestionList = group.suggestions.map(s => {
1711
+ const effortLabel = usePt ? 'Esforco' : 'Effort';
1712
+ const impactLabel = usePt ? 'Impacto' : 'Impact';
1713
+ return `- ${s.id}: ${s.title} (${effortLabel}: ${s.effort}, ${impactLabel}: ${s.impact})`;
1714
+ }).join('\n');
1715
+
1716
+ // Build the phase entry
1717
+ let phaseEntry;
1718
+ if (usePt) {
1719
+ const criteriaText = criteria.map((c, i) => ` ${i + 1}. ${c}`).join('\n');
1720
+ phaseEntry = `\n### Fase ${newPhaseNum}: ${group.name}\n` +
1721
+ `**Objetivo**: Implementar ${group.suggestions.length} ${source === 'ideias' ? 'ideias' : 'melhorias'} de ${group.dimension || 'multiplas dimensoes'} identificadas pela auditoria\n` +
1722
+ `**Depende de**: Fase ${newPhaseNum - 1}\n` +
1723
+ `**Criterios de Sucesso** (o que deve ser VERDADE):\n${criteriaText}\n` +
1724
+ `**Planos**: TBD\n\n` +
1725
+ `Sugestoes incluidas:\n${suggestionList}\n`;
1726
+ } else {
1727
+ const criteriaText = criteria.map((c, i) => ` ${i + 1}. ${c}`).join('\n');
1728
+ phaseEntry = `\n### Phase ${newPhaseNum}: ${group.name}\n` +
1729
+ `**Goal**: Implement ${group.suggestions.length} ${source === 'ideias' ? 'ideas' : 'improvements'} for ${group.dimension || 'multiple dimensions'} identified by audit\n` +
1730
+ `**Depends on**: Phase ${newPhaseNum - 1}\n` +
1731
+ `**Success Criteria** (what must be TRUE):\n${criteriaText}\n` +
1732
+ `**Plans**: TBD\n\n` +
1733
+ `Included suggestions:\n${suggestionList}\n`;
1734
+ }
1735
+
1736
+ // Insert phase entry before progress table or at end
1737
+ const tableHeaderPattern = /\n##\s*(?:Tabela de Progresso|Progress Table)/i;
1738
+ const tableMatch = roadmapContent.match(tableHeaderPattern);
1739
+ if (tableMatch) {
1740
+ roadmapContent = roadmapContent.slice(0, tableMatch.index) + phaseEntry + roadmapContent.slice(tableMatch.index);
1741
+ } else {
1742
+ roadmapContent += phaseEntry;
1743
+ }
1744
+
1745
+ // Add checkbox in Fases/Phases section
1746
+ const checkboxSectionPattern = /\n(##\s*(?:Fases|Phases)\s*\n)/i;
1747
+ const checkboxSection = roadmapContent.match(checkboxSectionPattern);
1748
+ if (checkboxSection) {
1749
+ // Find the last checkbox line in the section
1750
+ const sectionStart = checkboxSection.index + checkboxSection[0].length;
1751
+ const sectionRest = roadmapContent.slice(sectionStart);
1752
+ const lastCheckboxEnd = findLastCheckboxEnd(sectionRest);
1753
+ const insertPos = sectionStart + lastCheckboxEnd;
1754
+ const label = usePt ? 'Fase' : 'Phase';
1755
+ const shortDesc = group.suggestions.length + (usePt ? ' sugestoes de ' : ' suggestions for ') + (group.dimension || source);
1756
+ const checkboxLine = `\n- [ ] **${label} ${newPhaseNum}: ${group.name}** - ${shortDesc}`;
1757
+ roadmapContent = roadmapContent.slice(0, insertPos) + checkboxLine + roadmapContent.slice(insertPos);
1758
+ }
1759
+
1760
+ // Add row in progress table
1761
+ const progressTablePattern = /(\|\s*(?:Fase|Phase)\s*\|[^\n]*\n\|[-|\s]+\n)([\s\S]*?)(?=\n##|\n$|$)/i;
1762
+ const progressMatch = roadmapContent.match(progressTablePattern);
1763
+ if (progressMatch) {
1764
+ const tableBody = progressMatch[2].trimEnd();
1765
+ const statusLabel = usePt ? 'Nao iniciado' : 'Not started';
1766
+ const newRow = `| ${newPhaseNum}. ${group.name} | 0/? | ${statusLabel} | - |`;
1767
+ const newTableBody = tableBody + '\n' + newRow;
1768
+ roadmapContent = roadmapContent.replace(progressTablePattern,
1769
+ (_match, header) => `${header}${newTableBody}\n`
1770
+ );
1771
+ }
1772
+
1773
+ phasesCreated.push({
1774
+ phase_number: newPhaseNum,
1775
+ name: group.name,
1776
+ suggestion_count: group.suggestions.length,
1777
+ suggestion_ids: group.suggestions.map(s => s.id),
1778
+ directory: `.plano/fases/${dirName}/`,
1779
+ });
1780
+ }
1781
+
1782
+ // Write updated ROADMAP
1783
+ fs.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
1784
+
1785
+ output({
1786
+ phases_created: phasesCreated,
1787
+ total_phases: phasesCreated.length,
1788
+ total_suggestions: suggestions.length,
1789
+ roadmap_updated: true,
1790
+ }, raw);
1791
+ }
1792
+
1793
+ // --- Helper: Parse suggestions from RELATORIO.md ---
1794
+
1795
+ function parseSuggestionsFromReport(content, approvedIds) {
1796
+ const suggestions = [];
1797
+ const idSet = new Set(approvedIds.map(id => id.toUpperCase()));
1798
+
1799
+ // Match suggestion blocks: ### ID: title
1800
+ const suggestionPattern = /###\s+([\w-]+):\s*([^\n]+)/g;
1801
+ let match;
1802
+
1803
+ while ((match = suggestionPattern.exec(content)) !== null) {
1804
+ const id = match[1].trim().toUpperCase();
1805
+ if (!idSet.has(id)) continue;
1806
+
1807
+ const title = match[2].trim();
1808
+ const blockStart = match.index;
1809
+
1810
+ // Find the end of this suggestion block (next ### or end)
1811
+ const restContent = content.slice(blockStart + match[0].length);
1812
+ const nextSuggestion = restContent.match(/\n###\s+[\w-]+:/);
1813
+ const blockEnd = nextSuggestion
1814
+ ? blockStart + match[0].length + nextSuggestion.index
1815
+ : content.length;
1816
+ const block = content.slice(blockStart, blockEnd);
1817
+
1818
+ // Parse table fields
1819
+ const arquivo = extractTableField(block, 'Arquivo');
1820
+ const dimensao = extractTableField(block, 'Dimensao') || extractTableField(block, 'Dimension');
1821
+ const esforco = extractTableField(block, 'Esforco') || extractTableField(block, 'Effort');
1822
+ const impacto = extractTableField(block, 'Impacto') || extractTableField(block, 'Impact');
1823
+
1824
+ // Parse Problema/Sugestao
1825
+ const problemaMatch = block.match(/\*\*(?:Problema|Problem):\*\*\s*([\s\S]*?)(?=\*\*(?:Sugestao|Suggestion|Referencia|Reference):\*\*|$)/i);
1826
+ const sugestaoMatch = block.match(/\*\*(?:Sugestao|Suggestion):\*\*\s*([\s\S]*?)(?=\*\*(?:Referencia|Reference):\*\*|$)/i);
1827
+
1828
+ suggestions.push({
1829
+ id: match[1].trim(), // preserve original case
1830
+ title,
1831
+ file: arquivo ? arquivo.replace(/`/g, '') : null,
1832
+ dimension: dimensao ? dimensao.split(/\s*\(/)[0].trim() : null,
1833
+ effort: esforco || '?',
1834
+ impact: impacto || '?',
1835
+ problem: problemaMatch ? problemaMatch[1].trim() : null,
1836
+ suggestion: sugestaoMatch ? sugestaoMatch[1].trim() : null,
1837
+ });
1838
+ }
1839
+
1840
+ return suggestions;
1841
+ }
1842
+
1843
+ function extractTableField(block, fieldName) {
1844
+ const pattern = new RegExp(`\\|\\s*${fieldName}\\s*\\|\\s*([^|]+)\\|`, 'i');
1845
+ const match = block.match(pattern);
1846
+ return match ? match[1].trim() : null;
1847
+ }
1848
+
1849
+ // --- Helper: Group suggestions by dimension ---
1850
+
1851
+ function groupSuggestionsByDimension(suggestions, source) {
1852
+ // Group by primary dimension
1853
+ const dimensionMap = {};
1854
+ for (const s of suggestions) {
1855
+ const dim = s.dimension || 'Geral';
1856
+ if (!dimensionMap[dim]) dimensionMap[dim] = [];
1857
+ dimensionMap[dim].push(s);
1858
+ }
1859
+
1860
+ const groups = [];
1861
+
1862
+ for (const [dim, items] of Object.entries(dimensionMap)) {
1863
+ // If 5+ suggestions in a dimension, try to subdivide by directory
1864
+ if (items.length >= 5) {
1865
+ const dirMap = {};
1866
+ for (const item of items) {
1867
+ const dir = item.file ? path.dirname(item.file) : '_root';
1868
+ if (!dirMap[dir]) dirMap[dir] = [];
1869
+ dirMap[dir].push(item);
1870
+ }
1871
+
1872
+ const dirKeys = Object.keys(dirMap);
1873
+ if (dirKeys.length > 1) {
1874
+ for (const [dir, dirItems] of Object.entries(dirMap)) {
1875
+ const dirLabel = dir === '_root' ? 'raiz' : dir.replace(/\//g, '/');
1876
+ groups.push({
1877
+ name: `${dim}: ${buildSubgroupName(dirItems, source)} (${dirLabel})`,
1878
+ dimension: dim,
1879
+ suggestions: dirItems,
1880
+ });
1881
+ }
1882
+ continue;
1883
+ }
1884
+ }
1885
+
1886
+ groups.push({
1887
+ name: `${dim}: ${buildSubgroupName(items, source)}`,
1888
+ dimension: dim,
1889
+ suggestions: items,
1890
+ });
1891
+ }
1892
+
1893
+ // Merge small groups: if a group has only 1 suggestion with small effort, try to merge
1894
+ const mergedGroups = [];
1895
+ const pendingMerge = [];
1896
+
1897
+ for (const group of groups) {
1898
+ if (group.suggestions.length === 1 && group.suggestions[0].effort === 'P') {
1899
+ pendingMerge.push(group);
1900
+ } else {
1901
+ mergedGroups.push(group);
1902
+ }
1903
+ }
1904
+
1905
+ // Try to merge pending into adjacent groups of same dimension
1906
+ for (const pending of pendingMerge) {
1907
+ const target = mergedGroups.find(g => g.dimension === pending.dimension);
1908
+ if (target) {
1909
+ target.suggestions.push(...pending.suggestions);
1910
+ // Update name if needed
1911
+ target.name = `${target.dimension}: ${buildSubgroupName(target.suggestions, source)}`;
1912
+ } else {
1913
+ mergedGroups.push(pending);
1914
+ }
1915
+ }
1916
+
1917
+ return mergedGroups;
1918
+ }
1919
+
1920
+ function buildSubgroupName(suggestions, source) {
1921
+ if (suggestions.length === 1) {
1922
+ return suggestions[0].title;
1923
+ }
1924
+ // Synthesize a short name from titles
1925
+ const uniqueWords = new Set();
1926
+ for (const s of suggestions) {
1927
+ const words = s.title.split(/\s+/).slice(0, 3);
1928
+ for (const w of words) {
1929
+ if (w.length > 3) uniqueWords.add(w.toLowerCase());
1930
+ }
1931
+ }
1932
+ const wordList = [...uniqueWords].slice(0, 4).join(', ');
1933
+ const prefix = source === 'ideias' ? 'Ideias sobre' : 'Melhorias em';
1934
+ return `${prefix} ${wordList}`;
1935
+ }
1936
+
1937
+ function buildGroupName(source, suggestions) {
1938
+ const prefix = source === 'ideias' ? 'Ideias' : 'Melhorias';
1939
+ return `${prefix}: ${suggestions.length} sugestoes aprovadas`;
1940
+ }
1941
+
1942
+ // --- Helper: Build success criteria ---
1943
+
1944
+ function buildCriteria(suggestions, source, usePt) {
1945
+ if (suggestions.length <= 5) {
1946
+ return suggestions.map(s => {
1947
+ if (usePt) {
1948
+ const prefix = source === 'ideias' ? 'Feature' : 'Sugestao';
1949
+ return `${prefix} ${s.id} implementada: ${s.title}`;
1950
+ } else {
1951
+ const prefix = source === 'ideias' ? 'Feature' : 'Suggestion';
1952
+ return `${prefix} ${s.id} implemented: ${s.title}`;
1953
+ }
1954
+ });
1955
+ }
1956
+
1957
+ // More than 5 suggestions: summarize
1958
+ const dimCounts = {};
1959
+ for (const s of suggestions) {
1960
+ const dim = s.dimension || 'Geral';
1961
+ dimCounts[dim] = (dimCounts[dim] || 0) + 1;
1962
+ }
1963
+
1964
+ const criteria = [];
1965
+ for (const [dim, count] of Object.entries(dimCounts)) {
1966
+ if (usePt) {
1967
+ criteria.push(`${count} sugestoes de ${dim} implementadas conforme RELATORIO.md`);
1968
+ } else {
1969
+ criteria.push(`${count} ${dim} suggestions implemented per RELATORIO.md`);
1970
+ }
1971
+ }
1972
+
1973
+ if (criteria.length > 5) {
1974
+ if (usePt) {
1975
+ return [`${suggestions.length} sugestoes implementadas conforme RELATORIO.md`];
1976
+ } else {
1977
+ return [`${suggestions.length} suggestions implemented per RELATORIO.md`];
1978
+ }
1979
+ }
1980
+
1981
+ return criteria;
1982
+ }
1983
+
1984
+ // --- Helper: Find end of last checkbox line ---
1985
+
1986
+ function findLastCheckboxEnd(text) {
1987
+ let lastEnd = 0;
1988
+ const checkboxRegex = /^-\s*\[[ x]\]\s*\*\*.*\n/gm;
1989
+ let m;
1990
+ while ((m = checkboxRegex.exec(text)) !== null) {
1991
+ lastEnd = m.index + m[0].length;
1992
+ }
1993
+ return lastEnd;
1994
+ }
1995
+
1529
1996
  function cmdPhasePlanIndex(cwd, phase, raw) {
1530
1997
  if (!phase) error('phase required for phase-plan-index');
1531
1998
 
package/commands/ajuda.md CHANGED
@@ -50,6 +50,13 @@ Sistema de desenvolvimento orientado a fases para projetos de software.
50
50
  | `/up:remover-fase` | Remover fase futura e renumerar | `/up:remover-fase 5` |
51
51
  | `/up:resetar` | Resetar projeto (limpar .plano/) | `/up:resetar` |
52
52
 
53
+ ### Auditoria
54
+
55
+ | Comando | Descricao | Uso |
56
+ |---------|-----------|-----|
57
+ | `/up:melhorias` | Auditoria completa do codebase (UX, performance, modernidade) | `/up:melhorias` |
58
+ | `/up:ideias` | Sugestoes de features novas com pesquisa de mercado | `/up:ideias` |
59
+
53
60
  ### Utilitarios
54
61
 
55
62
  | Comando | Descricao | Uso |
@@ -116,6 +123,18 @@ O mapeamento do codebase alimenta todo o pipeline automaticamente.
116
123
  /up:progresso
117
124
  ```
118
125
 
126
+ ### Auditoria de Codebase
127
+ ```
128
+ /up:melhorias # Auditoria completa (standalone, nao requer /up:novo-projeto)
129
+ ```
130
+ Resultado em .plano/melhorias/RELATORIO.md com sugestoes priorizadas.
131
+
132
+ ### Ideacao de Features
133
+ ```
134
+ /up:ideias # Sugestoes de features novas (standalone, nao requer /up:novo-projeto)
135
+ ```
136
+ Resultado em .plano/ideias/RELATORIO.md com ranking ICE e anti-features.
137
+
119
138
  ### Correcao Rapida
120
139
  ```
121
140
  /up:rapido "Descricao da tarefa"