vibecodingmachine-core 2026.2.20-438 → 2026.2.26-1739
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 +240 -0
- package/package.json +10 -2
- package/src/agents/Agent.js +300 -0
- package/src/agents/AgentAdditionService.js +311 -0
- package/src/agents/AgentCheckService.js +690 -0
- package/src/agents/AgentInstallationService.js +140 -0
- package/src/agents/AgentSetupService.js +467 -0
- package/src/agents/AgentStatus.js +183 -0
- package/src/agents/AgentVerificationService.js +634 -0
- package/src/agents/ConfigurationSchemaValidator.js +543 -0
- package/src/agents/EnvironmentConfigurationManager.js +602 -0
- package/src/agents/InstallationErrorHandler.js +372 -0
- package/src/agents/InstallationLog.js +363 -0
- package/src/agents/InstallationMethod.js +510 -0
- package/src/agents/InstallationOrchestrator.js +352 -0
- package/src/agents/InstallationProgressReporter.js +372 -0
- package/src/agents/InstallationRetryManager.js +322 -0
- package/src/agents/InstallationType.js +254 -0
- package/src/agents/OperationTypes.js +310 -0
- package/src/agents/PerformanceMetricsCollector.js +493 -0
- package/src/agents/SecurityValidationService.js +534 -0
- package/src/agents/VerificationTest.js +354 -0
- package/src/agents/VerificationType.js +226 -0
- package/src/agents/WindowsPermissionHandler.js +518 -0
- package/src/agents/config/AgentConfigManager.js +393 -0
- package/src/agents/config/AgentDefaultsRegistry.js +373 -0
- package/src/agents/config/ConfigValidator.js +281 -0
- package/src/agents/discovery/AgentDiscoveryService.js +707 -0
- package/src/agents/logging/AgentLogger.js +511 -0
- package/src/agents/status/AgentStatusManager.js +481 -0
- package/src/agents/storage/FileManager.js +454 -0
- package/src/agents/verification/AgentCommunicationTester.js +474 -0
- package/src/agents/verification/BaseVerifier.js +430 -0
- package/src/agents/verification/CommandVerifier.js +480 -0
- package/src/agents/verification/FileOperationVerifier.js +453 -0
- package/src/agents/verification/ResultAnalyzer.js +707 -0
- package/src/agents/verification/TestRequirementManager.js +495 -0
- package/src/agents/verification/VerificationRunner.js +433 -0
- package/src/agents/windows/BaseWindowsInstaller.js +441 -0
- package/src/agents/windows/ChocolateyInstaller.js +509 -0
- package/src/agents/windows/DirectInstaller.js +443 -0
- package/src/agents/windows/InstallerFactory.js +391 -0
- package/src/agents/windows/NpmInstaller.js +505 -0
- package/src/agents/windows/PowerShellInstaller.js +458 -0
- package/src/agents/windows/WinGetInstaller.js +390 -0
- package/src/analysis/analysis-reporter.js +132 -0
- package/src/analysis/boundary-detector.js +712 -0
- package/src/analysis/categorizer.js +340 -0
- package/src/analysis/codebase-scanner.js +384 -0
- package/src/analysis/line-counter.js +513 -0
- package/src/analysis/priority-calculator.js +679 -0
- package/src/analysis/report/analysis-report.js +250 -0
- package/src/analysis/report/package-analyzer.js +278 -0
- package/src/analysis/report/recommendation-generator.js +382 -0
- package/src/analysis/report/statistics-generator.js +515 -0
- package/src/analysis/reports/analysis-report-model.js +101 -0
- package/src/analysis/reports/recommendation-generator.js +283 -0
- package/src/analysis/reports/report-generators.js +191 -0
- package/src/analysis/reports/statistics-calculator.js +231 -0
- package/src/analysis/reports/trend-analyzer.js +219 -0
- package/src/analysis/strategy-generator.js +814 -0
- package/src/auto-mode/AutoModeBusinessLogic.js +836 -0
- package/src/config/refactoring-config.js +307 -0
- package/src/health-tracking/json-storage.js +38 -2
- package/src/ide-integration/applescript-manager-core.js +233 -0
- package/src/ide-integration/applescript-manager.cjs +357 -28
- package/src/ide-integration/applescript-manager.js +89 -3599
- package/src/ide-integration/cdp-manager.js +306 -0
- package/src/ide-integration/claude-code-cli-manager.cjs +1 -1
- package/src/ide-integration/continuation-handler.js +337 -0
- package/src/ide-integration/ide-status-checker.js +292 -0
- package/src/ide-integration/macos-ide-manager.js +627 -0
- package/src/ide-integration/macos-text-sender.js +528 -0
- package/src/ide-integration/response-reader.js +548 -0
- package/src/ide-integration/windows-automation-manager.js +121 -0
- package/src/ide-integration/windows-ide-manager.js +373 -0
- package/src/index.cjs +25 -3
- package/src/index.js +15 -1
- package/src/llm/direct-llm-manager.cjs +90 -2
- package/src/models/compliance-report.js +538 -0
- package/src/models/file-analysis.js +681 -0
- package/src/models/refactoring-plan.js +770 -0
- package/src/monitoring/alert-system.js +834 -0
- package/src/monitoring/compliance-progress-tracker.js +437 -0
- package/src/monitoring/continuous-scan-notifications.js +661 -0
- package/src/monitoring/continuous-scanner.js +279 -0
- package/src/monitoring/file-monitor/file-analyzer.js +262 -0
- package/src/monitoring/file-monitor/file-monitor.js +237 -0
- package/src/monitoring/file-monitor/watcher.js +194 -0
- package/src/monitoring/file-monitor.js +17 -0
- package/src/monitoring/notification-manager.js +437 -0
- package/src/monitoring/scanner-core.js +368 -0
- package/src/monitoring/scanner-events.js +214 -0
- package/src/monitoring/violation-notification-system.js +515 -0
- package/src/refactoring/boundaries/cohesion-analyzer.js +316 -0
- package/src/refactoring/boundaries/extraction-result.js +285 -0
- package/src/refactoring/boundaries/extraction-strategies.js +392 -0
- package/src/refactoring/boundaries/module-boundary.js +209 -0
- package/src/refactoring/boundary/boundary-detector.js +741 -0
- package/src/refactoring/boundary/boundary-types.js +405 -0
- package/src/refactoring/boundary/extraction-strategies.js +554 -0
- package/src/refactoring/boundary-extraction-result.js +77 -0
- package/src/refactoring/boundary-extraction-strategies.js +330 -0
- package/src/refactoring/boundary-extractor.js +384 -0
- package/src/refactoring/boundary-types.js +46 -0
- package/src/refactoring/circular/circular-dependency.js +88 -0
- package/src/refactoring/circular/cycle-detection.js +147 -0
- package/src/refactoring/circular/dependency-node.js +82 -0
- package/src/refactoring/circular/dependency-result.js +107 -0
- package/src/refactoring/circular/dependency-types.js +58 -0
- package/src/refactoring/circular/graph-builder.js +213 -0
- package/src/refactoring/circular/resolution-strategy.js +72 -0
- package/src/refactoring/circular/strategy-generator.js +229 -0
- package/src/refactoring/circular-dependency-resolver-original.js +809 -0
- package/src/refactoring/circular-dependency-resolver.js +200 -0
- package/src/refactoring/code-mover.js +761 -0
- package/src/refactoring/file-splitter.js +696 -0
- package/src/refactoring/functionality-validator.js +816 -0
- package/src/refactoring/import-manager.js +774 -0
- package/src/refactoring/module-boundary.js +107 -0
- package/src/refactoring/refactoring-executor.js +672 -0
- package/src/refactoring/refactoring-rollback.js +614 -0
- package/src/refactoring/test-validator.js +631 -0
- package/src/requirement-management/default-requirement-manager.js +321 -0
- package/src/requirement-management/requirement-file-parser.js +159 -0
- package/src/requirement-management/requirement-sequencer.js +221 -0
- package/src/rui/commands/AgentCommandParser.js +600 -0
- package/src/rui/commands/AgentCommands.js +487 -0
- package/src/rui/commands/AgentResponseFormatter.js +832 -0
- package/src/scripts/verify-full-compliance.js +269 -0
- package/src/sync/sync-engine-core.js +1 -0
- package/src/sync/sync-engine-remote-handlers.js +135 -0
- package/src/task-generation/automated-task-generator.js +351 -0
- package/src/task-generation/prioritizer.js +287 -0
- package/src/task-generation/task-list-updater.js +215 -0
- package/src/task-generation/task-management-integration.js +480 -0
- package/src/task-generation/task-manager-integration.js +270 -0
- package/src/task-generation/violation-task-generator.js +474 -0
- package/src/task-management/continuous-scan-integration.js +342 -0
- package/src/timeout-management/index.js +12 -3
- package/src/timeout-management/response-time-tracker.js +167 -0
- package/src/timeout-management/timeout-calculator.js +159 -0
- package/src/timeout-management/timeout-config-manager.js +172 -0
- package/src/utils/ast-analyzer.js +417 -0
- package/src/utils/current-requirement-manager.js +276 -0
- package/src/utils/current-requirement-operations.js +472 -0
- package/src/utils/dependency-mapper.js +456 -0
- package/src/utils/download-with-progress.js +4 -2
- package/src/utils/electron-update-checker.js +4 -1
- package/src/utils/file-size-analyzer.js +272 -0
- package/src/utils/import-updater.js +280 -0
- package/src/utils/refactoring-tools.js +512 -0
- package/src/utils/report-generator.js +569 -0
- package/src/utils/reports/report-analysis.js +218 -0
- package/src/utils/reports/report-types.js +55 -0
- package/src/utils/reports/summary-generators.js +102 -0
- package/src/utils/requirement-file-management.js +157 -0
- package/src/utils/requirement-helpers/requirement-file-ops.js +392 -0
- package/src/utils/requirement-helpers/requirement-mover.js +414 -0
- package/src/utils/requirement-helpers/requirement-parser.js +326 -0
- package/src/utils/requirement-helpers/requirement-status.js +320 -0
- package/src/utils/requirement-helpers-new.js +55 -0
- package/src/utils/requirement-helpers-refactored.js +367 -0
- package/src/utils/requirement-helpers.js +291 -1191
- package/src/utils/requirement-movement-operations.js +450 -0
- package/src/utils/requirement-movement.js +312 -0
- package/src/utils/requirement-parsing-helpers.js +56 -0
- package/src/utils/requirement-statistics.js +200 -0
- package/src/utils/requirement-text-utils.js +58 -0
- package/src/utils/rollback/rollback-handlers.js +125 -0
- package/src/utils/rollback/rollback-operation.js +63 -0
- package/src/utils/rollback/rollback-recorder.js +166 -0
- package/src/utils/rollback/rollback-state-manager.js +175 -0
- package/src/utils/rollback/rollback-types.js +33 -0
- package/src/utils/rollback/rollback-utils.js +110 -0
- package/src/utils/rollback-manager-original.js +569 -0
- package/src/utils/rollback-manager.js +202 -0
- package/src/utils/smoke-test-cli.js +362 -0
- package/src/utils/smoke-test-gui.js +351 -0
- package/src/utils/smoke-test-orchestrator.js +321 -0
- package/src/utils/smoke-test-runner.js +60 -0
- package/src/utils/smoke-test-web.js +347 -0
- package/src/utils/specification-helpers.js +39 -13
- package/src/utils/specification-migration.js +97 -0
- package/src/utils/test-runner.js +579 -0
- package/src/utils/validation-framework.js +518 -0
- package/src/validation/compliance-analyzer.js +197 -0
- package/src/validation/compliance-report-generator.js +343 -0
- package/src/validation/compliance-reporter.js +711 -0
- package/src/validation/compliance-rules.js +127 -0
- package/src/validation/constitution-validator-new.js +196 -0
- package/src/validation/constitution-validator.js +17 -0
- package/src/validation/file-validators.js +170 -0
- package/src/validation/line-limit/file-analyzer.js +201 -0
- package/src/validation/line-limit/line-limit-validator.js +208 -0
- package/src/validation/line-limit/validation-result.js +144 -0
- package/src/validation/line-limit-core.js +225 -0
- package/src/validation/line-limit-reporter.js +134 -0
- package/src/validation/line-limit-result.js +125 -0
- package/src/validation/line-limit-validator.js +41 -0
- package/src/validation/metrics-calculator.js +660 -0
- package/src/sync/sync-engine-backup.js +0 -559
|
@@ -1,1267 +1,367 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { getRequirementsPath } = require('./repo-helpers.cjs');
|
|
5
|
-
const { logger } = require('./logger.cjs');
|
|
6
|
-
|
|
7
|
-
// Default instruction text
|
|
8
|
-
const DEFAULT_INSTRUCTION_TEXT = 'Follow INSTRUCTIONS.md from .vibecodingmachine directory. CRITICAL: You MUST work through ALL status stages (PREPARE → ACT → CLEAN UP → VERIFY → DONE) and set the status to DONE in the REQUIREMENTS file before stopping. DO NOT stop working until the "🚦 Current Status" section shows "DONE". The VibeCodingMachine app is running in autonomous mode and depends on you completing the requirement fully.';
|
|
9
|
-
|
|
10
1
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
2
|
+
* Requirement Helpers (Refactored)
|
|
3
|
+
* Main entry point for all requirement-related operations
|
|
4
|
+
* Original file was 1,267 lines, now split into focused modules under 555 lines each
|
|
14
5
|
*/
|
|
15
|
-
function getOrdinalSuffix(num) {
|
|
16
|
-
const j = num % 10;
|
|
17
|
-
const k = num % 100;
|
|
18
|
-
if (j === 1 && k !== 11) {
|
|
19
|
-
return 'st';
|
|
20
|
-
}
|
|
21
|
-
if (j === 2 && k !== 12) {
|
|
22
|
-
return 'nd';
|
|
23
|
-
}
|
|
24
|
-
if (j === 3 && k !== 13) {
|
|
25
|
-
return 'rd';
|
|
26
|
-
}
|
|
27
|
-
return 'th';
|
|
28
|
-
}
|
|
29
6
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function addTryAgainPrefix(requirementText) {
|
|
36
|
-
const tryAgainMatch = requirementText.match(/^TRY AGAIN \((\d+)(?:st|nd|rd|th) time\): (.+)$/);
|
|
7
|
+
// Import all sub-modules
|
|
8
|
+
const requirementParser = require('./requirement-helpers/requirement-parser.js');
|
|
9
|
+
const requirementMover = require('./requirement-helpers/requirement-mover.js');
|
|
10
|
+
const requirementStatus = require('./requirement-helpers/requirement-status.js');
|
|
11
|
+
const requirementFileOps = require('./requirement-helpers/requirement-file-ops.js');
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
13
|
+
// Re-export parser utilities
|
|
14
|
+
const {
|
|
15
|
+
DEFAULT_INSTRUCTION_TEXT,
|
|
16
|
+
getOrdinalSuffix,
|
|
17
|
+
addTryAgainPrefix,
|
|
18
|
+
parseRequirementLine,
|
|
19
|
+
extractRequirementTitle,
|
|
20
|
+
isRequirementMatch,
|
|
21
|
+
parseRequirementBlock,
|
|
22
|
+
findRequirementBlock,
|
|
23
|
+
findAllRequirementsInSection,
|
|
24
|
+
validateRequirementFormat,
|
|
25
|
+
formatRequirementForDisplay
|
|
26
|
+
} = requirementParser;
|
|
27
|
+
|
|
28
|
+
// Re-export movement operations
|
|
29
|
+
const {
|
|
30
|
+
promoteToVerified,
|
|
31
|
+
demoteFromVerifiedToTodo,
|
|
32
|
+
promoteTodoToVerify,
|
|
33
|
+
demoteFromVerifyToTodo,
|
|
34
|
+
moveToCurrent,
|
|
35
|
+
getRequirementsInSection,
|
|
36
|
+
getRequirementStatistics
|
|
37
|
+
} = requirementMover;
|
|
38
|
+
|
|
39
|
+
// Re-export status management
|
|
40
|
+
const {
|
|
41
|
+
extractCurrentStatus,
|
|
42
|
+
updateCurrentStatus,
|
|
43
|
+
getCurrentStatus,
|
|
44
|
+
isStatusDone,
|
|
45
|
+
isStatusInProgress,
|
|
46
|
+
getStatusStage,
|
|
47
|
+
validateStatus,
|
|
48
|
+
getNextStatus,
|
|
49
|
+
getPreviousStatus,
|
|
50
|
+
isValidStatusTransition,
|
|
51
|
+
getStatusHistory,
|
|
52
|
+
formatStatusForDisplay,
|
|
53
|
+
getStatusSummary
|
|
54
|
+
} = requirementStatus;
|
|
55
|
+
|
|
56
|
+
// Re-export file operations
|
|
57
|
+
const {
|
|
58
|
+
getRequirementsFilePath,
|
|
59
|
+
requirementsFileExists,
|
|
60
|
+
createRequirementsFile,
|
|
61
|
+
readRequirementsFile,
|
|
62
|
+
writeRequirementsFile,
|
|
63
|
+
backupRequirementsFile,
|
|
64
|
+
getRequirementsFileStats,
|
|
65
|
+
validateRequirementsFile,
|
|
66
|
+
getAllRequirementsFiles,
|
|
67
|
+
cleanupBackupFiles
|
|
68
|
+
} = requirementFileOps;
|
|
46
69
|
|
|
47
70
|
/**
|
|
48
|
-
*
|
|
49
|
-
* @param {string}
|
|
50
|
-
* @returns {
|
|
71
|
+
* Get comprehensive requirement system information
|
|
72
|
+
* @param {string} repoPath - Repository path (optional)
|
|
73
|
+
* @returns {Promise<Object>} System information
|
|
51
74
|
*/
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
async function getRequirementSystemInfo(repoPath = null) {
|
|
76
|
+
try {
|
|
77
|
+
const fileStats = await getRequirementsFileStats(repoPath);
|
|
78
|
+
const validation = await validateRequirementsFile(repoPath);
|
|
79
|
+
const statusSummary = await getStatusSummary(getRequirementsFilePath(repoPath));
|
|
80
|
+
const requirementStats = await getRequirementStatistics(getRequirementsFilePath(repoPath));
|
|
81
|
+
const allFiles = await getAllRequirementsFiles(repoPath || process.cwd());
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
file: fileStats,
|
|
85
|
+
validation,
|
|
86
|
+
status: statusSummary,
|
|
87
|
+
statistics: requirementStats,
|
|
88
|
+
allFiles,
|
|
89
|
+
timestamp: new Date().toISOString()
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
error: error.message,
|
|
94
|
+
timestamp: new Date().toISOString()
|
|
95
|
+
};
|
|
55
96
|
}
|
|
56
|
-
return line;
|
|
57
97
|
}
|
|
58
98
|
|
|
59
99
|
/**
|
|
60
|
-
*
|
|
61
|
-
* @param {string}
|
|
62
|
-
* @
|
|
63
|
-
* @returns {Promise<boolean>} Success status
|
|
100
|
+
* Initialize requirements system
|
|
101
|
+
* @param {string} repoPath - Repository path
|
|
102
|
+
* @returns {Promise<Object>} Initialization result
|
|
64
103
|
*/
|
|
65
|
-
async function
|
|
104
|
+
async function initializeRequirementsSystem(repoPath) {
|
|
66
105
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Find the requirement in TO VERIFY section (new ### format)
|
|
75
|
-
for (let i = 0; i < lines.length; i++) {
|
|
76
|
-
const line = lines[i];
|
|
77
|
-
const trimmed = line.trim();
|
|
78
|
-
|
|
79
|
-
// Check if we're entering TO VERIFY section (multiple variants)
|
|
80
|
-
if (trimmed.startsWith('##') && !trimmed.startsWith('###') &&
|
|
81
|
-
(trimmed.includes('TO VERIFY') || trimmed.includes('Verified by AI screenshot'))) {
|
|
82
|
-
inVerifySection = true;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check if we're leaving TO VERIFY section
|
|
87
|
-
if (inVerifySection && trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
88
|
-
inVerifySection = false;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Look for requirement in TO VERIFY section (### header format)
|
|
92
|
-
if (inVerifySection && trimmed.startsWith('###')) {
|
|
93
|
-
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
94
|
-
if (title === normalizedTitle || title.includes(normalizedTitle) || normalizedTitle.includes(title)) {
|
|
95
|
-
requirementStartIndex = i;
|
|
106
|
+
const result = {
|
|
107
|
+
success: false,
|
|
108
|
+
actions: [],
|
|
109
|
+
warnings: [],
|
|
110
|
+
errors: []
|
|
111
|
+
};
|
|
96
112
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
requirementEndIndex = lines.length;
|
|
107
|
-
}
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
113
|
+
// Check if requirements file exists
|
|
114
|
+
if (!requirementsFileExists(repoPath)) {
|
|
115
|
+
result.actions.push('Creating requirements file...');
|
|
116
|
+
const created = await createRequirementsFile(repoPath);
|
|
117
|
+
if (created) {
|
|
118
|
+
result.actions.push('✅ Requirements file created');
|
|
119
|
+
} else {
|
|
120
|
+
result.errors.push('❌ Failed to create requirements file');
|
|
121
|
+
return result;
|
|
110
122
|
}
|
|
123
|
+
} else {
|
|
124
|
+
result.actions.push('✅ Requirements file already exists');
|
|
111
125
|
}
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
// Validate existing file
|
|
128
|
+
const validation = await validateRequirementsFile(repoPath);
|
|
129
|
+
if (!validation.isValid) {
|
|
130
|
+
result.warnings.push('⚠️ Requirements file has validation issues');
|
|
131
|
+
result.warnings.push(...validation.errors);
|
|
132
|
+
}
|
|
133
|
+
if (validation.warnings.length > 0) {
|
|
134
|
+
result.warnings.push('⚠️ Requirements file has warnings');
|
|
135
|
+
result.warnings.push(...validation.warnings);
|
|
115
136
|
}
|
|
116
137
|
|
|
117
|
-
//
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// Remove requirement from TO VERIFY section
|
|
122
|
-
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
123
|
-
|
|
124
|
-
// Add to CHANGELOG.md
|
|
125
|
-
const allnightDir = path.dirname(reqPath);
|
|
126
|
-
const repoRoot = path.dirname(allnightDir);
|
|
127
|
-
const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
|
|
128
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
129
|
-
const changelogEntry = `- ${extractedTitle} (${timestamp})`;
|
|
130
|
-
|
|
131
|
-
let changelogContent = '';
|
|
132
|
-
if (await fs.pathExists(changelogPath)) {
|
|
133
|
-
changelogContent = await fs.readFile(changelogPath, 'utf-8');
|
|
134
|
-
} else {
|
|
135
|
-
changelogContent = '# Changelog\n\n## Verified Requirements\n\n';
|
|
138
|
+
// Create backup
|
|
139
|
+
const backupPath = await backupRequirementsFile(repoPath);
|
|
140
|
+
if (backupPath) {
|
|
141
|
+
result.actions.push(`✅ Backup created: ${backupPath}`);
|
|
136
142
|
}
|
|
137
143
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
);
|
|
143
|
-
} else {
|
|
144
|
-
changelogContent += `\n## Verified Requirements\n${changelogEntry}\n`;
|
|
144
|
+
// Clean up old backups
|
|
145
|
+
const cleanedCount = await cleanupBackupFiles(repoPath);
|
|
146
|
+
if (cleanedCount > 0) {
|
|
147
|
+
result.actions.push(`🗑️ Cleaned up ${cleanedCount} old backup files`);
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
result.success = true;
|
|
151
|
+
return result;
|
|
152
|
+
|
|
150
153
|
} catch (error) {
|
|
151
|
-
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
actions: [],
|
|
157
|
+
warnings: [],
|
|
158
|
+
errors: [`Initialization failed: ${error.message}`]
|
|
159
|
+
};
|
|
152
160
|
}
|
|
153
161
|
}
|
|
154
162
|
|
|
155
163
|
/**
|
|
156
|
-
*
|
|
157
|
-
* @param {string}
|
|
158
|
-
* @
|
|
159
|
-
* @returns {Promise<boolean>} Success status
|
|
164
|
+
* Get requirement workflow status
|
|
165
|
+
* @param {string} repoPath - Repository path (optional)
|
|
166
|
+
* @returns {Promise<Object>} Workflow status
|
|
160
167
|
*/
|
|
161
|
-
async function
|
|
168
|
+
async function getWorkflowStatus(repoPath = null) {
|
|
162
169
|
try {
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
if (!(await fs.pathExists(changelogPath))) {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
let changelogContent = await fs.readFile(changelogPath, 'utf-8');
|
|
173
|
-
const changelogLines = changelogContent.split('\n');
|
|
174
|
-
const updatedChangelogLines = [];
|
|
175
|
-
let requirementToMove = null;
|
|
176
|
-
|
|
177
|
-
for (const line of changelogLines) {
|
|
178
|
-
if (line.startsWith('- ')) {
|
|
179
|
-
const lineText = parseRequirementLine(line);
|
|
180
|
-
// Extract title part (before timestamp in parentheses)
|
|
181
|
-
const titleMatch = lineText.match(/^(.+?)\s*\([\d-]+\)$/);
|
|
182
|
-
const lineTitle = titleMatch ? titleMatch[1] : lineText;
|
|
183
|
-
|
|
184
|
-
// Check if this line matches the requirement title
|
|
185
|
-
// Handle both cases: requirementTitle might include timestamp or not
|
|
186
|
-
const reqTitleMatch = requirementTitle.match(/^(.+?)\s*\([\d-]+\)$/);
|
|
187
|
-
const reqTitleOnly = reqTitleMatch ? reqTitleMatch[1] : requirementTitle;
|
|
188
|
-
|
|
189
|
-
if (lineTitle === reqTitleOnly || lineTitle.includes(reqTitleOnly) || reqTitleOnly.includes(lineTitle)) {
|
|
190
|
-
requirementToMove = lineTitle;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
updatedChangelogLines.push(line);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (!requirementToMove) {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
await fs.writeFile(changelogPath, updatedChangelogLines.join('\n'));
|
|
202
|
-
|
|
203
|
-
const content = await fs.readFile(reqPath, 'utf-8');
|
|
204
|
-
const lines = content.split('\n');
|
|
205
|
-
const updatedLines = [];
|
|
206
|
-
let foundTodoSection = false;
|
|
170
|
+
const status = await getCurrentStatus(repoPath);
|
|
171
|
+
const stage = getStatusStage(status);
|
|
172
|
+
const isDone = isStatusDone(status);
|
|
173
|
+
const nextStatus = getNextStatus(status);
|
|
174
|
+
const stats = await getRequirementStatistics(getRequirementsFilePath(repoPath));
|
|
207
175
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (!foundTodoSection) {
|
|
223
|
-
updatedLines.push('## ⏳ Requirements not yet completed');
|
|
224
|
-
const requirementText = addTryAgainPrefix(requirementToMove);
|
|
225
|
-
updatedLines.push(`- ${requirementText}`);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
await fs.writeFile(reqPath, updatedLines.join('\n'));
|
|
229
|
-
return true;
|
|
176
|
+
return {
|
|
177
|
+
current: status,
|
|
178
|
+
stage,
|
|
179
|
+
isDone,
|
|
180
|
+
nextStatus,
|
|
181
|
+
canProceed: !isDone && nextStatus !== null,
|
|
182
|
+
requirementsCount: stats.total,
|
|
183
|
+
todoCount: stats.todo,
|
|
184
|
+
verifyCount: stats.verify,
|
|
185
|
+
currentCount: stats.current,
|
|
186
|
+
timestamp: new Date().toISOString()
|
|
187
|
+
};
|
|
230
188
|
} catch (error) {
|
|
231
|
-
|
|
189
|
+
return {
|
|
190
|
+
error: error.message,
|
|
191
|
+
timestamp: new Date().toISOString()
|
|
192
|
+
};
|
|
232
193
|
}
|
|
233
194
|
}
|
|
234
195
|
|
|
235
196
|
/**
|
|
236
|
-
*
|
|
237
|
-
* @param {string}
|
|
238
|
-
* @
|
|
239
|
-
* @returns {Promise<boolean>} Success status
|
|
197
|
+
* Advance requirement workflow to next status
|
|
198
|
+
* @param {string} repoPath - Repository path (optional)
|
|
199
|
+
* @returns {Promise<Object>} Result of advancement
|
|
240
200
|
*/
|
|
241
|
-
async function
|
|
201
|
+
async function advanceWorkflow(repoPath = null) {
|
|
242
202
|
try {
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
// Find the requirement block (### header format)
|
|
247
|
-
let requirementStartIndex = -1;
|
|
248
|
-
let requirementEndIndex = -1;
|
|
249
|
-
let inTodoSection = false;
|
|
250
|
-
|
|
251
|
-
for (let i = 0; i < lines.length; i++) {
|
|
252
|
-
const line = lines[i].trim();
|
|
253
|
-
|
|
254
|
-
if (line.includes('## ⏳ Requirements not yet completed')) {
|
|
255
|
-
inTodoSection = true;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (inTodoSection && line.startsWith('##') && !line.startsWith('###')) {
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (inTodoSection && line.startsWith('###')) {
|
|
264
|
-
const title = line.replace(/^###\s*/, '').trim();
|
|
265
|
-
if (title && (title === requirementTitle || title.includes(requirementTitle) || requirementTitle.includes(title))) {
|
|
266
|
-
requirementStartIndex = i;
|
|
267
|
-
// Find the end of this requirement (next ### or ## header)
|
|
268
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
269
|
-
const nextLine = lines[j].trim();
|
|
270
|
-
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
271
|
-
requirementEndIndex = j;
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (requirementEndIndex === -1) {
|
|
276
|
-
requirementEndIndex = lines.length;
|
|
277
|
-
}
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (requirementStartIndex === -1) {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Extract the requirement block
|
|
288
|
-
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
289
|
-
|
|
290
|
-
// Remove the requirement from its current location
|
|
291
|
-
const updatedLines = [
|
|
292
|
-
...lines.slice(0, requirementStartIndex),
|
|
293
|
-
...lines.slice(requirementEndIndex)
|
|
294
|
-
];
|
|
295
|
-
|
|
296
|
-
// Find or create TO VERIFY section
|
|
297
|
-
const verifySectionVariants = [
|
|
298
|
-
'## 🔍 TO VERIFY BY HUMAN',
|
|
299
|
-
'## 🔍 TO VERIFY',
|
|
300
|
-
'## TO VERIFY',
|
|
301
|
-
'## ✅ TO VERIFY',
|
|
302
|
-
'## ✅ Verified by AI screenshot'
|
|
303
|
-
];
|
|
304
|
-
|
|
305
|
-
let verifyIndex = -1;
|
|
306
|
-
for (let i = 0; i < updatedLines.length; i++) {
|
|
307
|
-
if (verifySectionVariants.some(variant => updatedLines[i].includes(variant))) {
|
|
308
|
-
verifyIndex = i;
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (verifyIndex === -1) {
|
|
314
|
-
// Create TO VERIFY section before CHANGELOG or at end
|
|
315
|
-
const changelogIndex = updatedLines.findIndex(line => line.includes('## CHANGELOG'));
|
|
316
|
-
const insertIndex = changelogIndex > 0 ? changelogIndex : updatedLines.length;
|
|
317
|
-
updatedLines.splice(insertIndex, 0, '', '## 🔍 TO VERIFY BY HUMAN', '');
|
|
318
|
-
verifyIndex = insertIndex + 1;
|
|
319
|
-
}
|
|
203
|
+
const currentStatus = await getCurrentStatus(repoPath);
|
|
204
|
+
const nextStatus = getNextStatus(currentStatus);
|
|
320
205
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
|
|
329
|
-
updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
|
|
206
|
+
if (!nextStatus) {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
message: 'Already at DONE status or invalid current status',
|
|
210
|
+
currentStatus,
|
|
211
|
+
nextStatus: null
|
|
212
|
+
};
|
|
330
213
|
}
|
|
331
214
|
|
|
332
|
-
await
|
|
333
|
-
|
|
215
|
+
const updated = await updateCurrentStatus(repoPath, nextStatus);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
success: updated,
|
|
219
|
+
message: updated ? `Status advanced from ${currentStatus} to ${nextStatus}` : 'Failed to update status',
|
|
220
|
+
previousStatus: currentStatus,
|
|
221
|
+
currentStatus: nextStatus
|
|
222
|
+
};
|
|
334
223
|
} catch (error) {
|
|
335
|
-
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
message: `Error advancing workflow: ${error.message}`,
|
|
227
|
+
error: error.message
|
|
228
|
+
};
|
|
336
229
|
}
|
|
337
230
|
}
|
|
338
231
|
|
|
339
232
|
/**
|
|
340
|
-
*
|
|
341
|
-
* @param {string}
|
|
342
|
-
* @
|
|
343
|
-
* @param {string} explanation - Optional explanation of what went wrong
|
|
344
|
-
* @returns {Promise<boolean>} Success status
|
|
233
|
+
* Get requirement health check
|
|
234
|
+
* @param {string} repoPath - Repository path (optional)
|
|
235
|
+
* @returns {Promise<Object>} Health check results
|
|
345
236
|
*/
|
|
346
|
-
async function
|
|
237
|
+
async function getHealthCheck(repoPath = null) {
|
|
347
238
|
try {
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
'## TO VERIFY',
|
|
360
|
-
'## ✅ TO VERIFY',
|
|
361
|
-
'## ✅ Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG',
|
|
362
|
-
'## ✅ Verified by AI screenshot'
|
|
363
|
-
];
|
|
364
|
-
|
|
365
|
-
// First pass: find all matching requirements in TO VERIFY section
|
|
366
|
-
for (let i = 0; i < lines.length; i++) {
|
|
367
|
-
const line = lines[i];
|
|
368
|
-
const trimmed = line.trim();
|
|
369
|
-
|
|
370
|
-
// Check if this is a TO VERIFY section header
|
|
371
|
-
if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
372
|
-
const isToVerifyHeader = verifySectionVariants.some(variant => {
|
|
373
|
-
return trimmed === variant || trimmed.startsWith(variant) ||
|
|
374
|
-
(trimmed.includes('Verified by AI screenshot') && trimmed.includes('Needs Human to Verify'));
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
if (isToVerifyHeader) {
|
|
378
|
-
// Make sure it's not a VERIFIED section (without TO VERIFY)
|
|
379
|
-
if (!trimmed.includes('## 📝 VERIFIED') && !trimmed.match(/^##\s+VERIFIED$/i) && !trimmed.includes('📝 VERIFIED')) {
|
|
380
|
-
inVerifySection = true;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
} else if (inVerifySection) {
|
|
384
|
-
// Check if we're leaving TO VERIFY section (hit a different section)
|
|
385
|
-
if (trimmed.includes('⏳ Requirements not yet completed') ||
|
|
386
|
-
trimmed.includes('## 📝 VERIFIED') ||
|
|
387
|
-
trimmed.includes('## ♻️ RECYCLED') ||
|
|
388
|
-
trimmed.includes('## 📦 RECYCLED') ||
|
|
389
|
-
trimmed.includes('## ❓ Requirements needing')) {
|
|
390
|
-
// We've left the TO VERIFY section
|
|
391
|
-
inVerifySection = false;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Look for requirement in TO VERIFY section
|
|
397
|
-
if (inVerifySection && trimmed.startsWith('###')) {
|
|
398
|
-
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
399
|
-
// Normalize titles for matching (handle TRY AGAIN prefixes)
|
|
400
|
-
const normalizedTitle = title.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
|
|
401
|
-
const normalizedRequirementTitle = requirementTitle.replace(/^TRY AGAIN \(\d+(st|nd|rd|th) time\):\s*/i, '').trim();
|
|
402
|
-
|
|
403
|
-
if (title && (title === requirementTitle ||
|
|
404
|
-
normalizedTitle === normalizedRequirementTitle ||
|
|
405
|
-
title.includes(requirementTitle) ||
|
|
406
|
-
requirementTitle.includes(title) ||
|
|
407
|
-
normalizedTitle.includes(normalizedRequirementTitle) ||
|
|
408
|
-
normalizedRequirementTitle.includes(normalizedTitle))) {
|
|
409
|
-
// Find the end of this requirement (next ### or ## header)
|
|
410
|
-
let requirementEndIndex = lines.length;
|
|
411
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
412
|
-
const nextLine = lines[j].trim();
|
|
413
|
-
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
414
|
-
requirementEndIndex = j;
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Store this requirement to remove (we'll use the first one for moving to TODO)
|
|
420
|
-
requirementsToRemove.push({
|
|
421
|
-
start: i,
|
|
422
|
-
end: requirementEndIndex,
|
|
423
|
-
block: lines.slice(i, requirementEndIndex)
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
239
|
+
const fileStats = await getRequirementsFileStats(repoPath);
|
|
240
|
+
const validation = await validateRequirementsFile(repoPath);
|
|
241
|
+
const statusSummary = await getStatusSummary(getRequirementsFilePath(repoPath));
|
|
242
|
+
const stats = await getRequirementStatistics(getRequirementsFilePath(repoPath));
|
|
243
|
+
|
|
244
|
+
const health = {
|
|
245
|
+
status: 'healthy',
|
|
246
|
+
issues: [],
|
|
247
|
+
recommendations: [],
|
|
248
|
+
score: 100
|
|
249
|
+
};
|
|
428
250
|
|
|
429
|
-
|
|
430
|
-
|
|
251
|
+
// Check file existence
|
|
252
|
+
if (!fileStats.exists) {
|
|
253
|
+
health.status = 'critical';
|
|
254
|
+
health.issues.push('Requirements file does not exist');
|
|
255
|
+
health.recommendations.push('Create a requirements file');
|
|
256
|
+
health.score -= 50;
|
|
431
257
|
}
|
|
432
258
|
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const titleWithPrefix = addTryAgainPrefix(originalTitle);
|
|
440
|
-
requirementBlock[0] = `### ${titleWithPrefix}`;
|
|
441
|
-
|
|
442
|
-
// Add explanation to the requirement description if provided
|
|
443
|
-
if (explanation && explanation.trim()) {
|
|
444
|
-
// Find where to insert the explanation (after the title, before any existing content)
|
|
445
|
-
// Insert after first line (title) with a blank line and "What went wrong:" section
|
|
446
|
-
const explanationLines = [
|
|
447
|
-
'',
|
|
448
|
-
'**What went wrong (from previous attempt):**',
|
|
449
|
-
explanation.trim(),
|
|
450
|
-
''
|
|
451
|
-
];
|
|
452
|
-
requirementBlock.splice(1, 0, ...explanationLines);
|
|
259
|
+
// Check validation
|
|
260
|
+
if (!validation.isValid) {
|
|
261
|
+
health.status = 'warning';
|
|
262
|
+
health.issues.push('Requirements file has validation errors');
|
|
263
|
+
health.recommendations.push('Fix validation errors');
|
|
264
|
+
health.score -= 20;
|
|
453
265
|
}
|
|
454
266
|
|
|
455
|
-
//
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
267
|
+
// Check status
|
|
268
|
+
if (!statusSummary.current) {
|
|
269
|
+
health.issues.push('No current status set');
|
|
270
|
+
health.recommendations.push('Set a current status');
|
|
271
|
+
health.score -= 10;
|
|
460
272
|
}
|
|
461
273
|
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
break;
|
|
468
|
-
}
|
|
274
|
+
// Check for stuck requirements
|
|
275
|
+
if (stats.verify > 10) {
|
|
276
|
+
health.issues.push('Many requirements waiting for verification');
|
|
277
|
+
health.recommendations.push('Review and verify pending requirements');
|
|
278
|
+
health.score -= 5;
|
|
469
279
|
}
|
|
470
280
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
todoIndex = insertIndex;
|
|
281
|
+
// Check file size
|
|
282
|
+
if (fileStats.size > 50000) { // 50KB
|
|
283
|
+
health.issues.push('Requirements file is large');
|
|
284
|
+
health.recommendations.push('Consider archiving old requirements');
|
|
285
|
+
health.score -= 5;
|
|
477
286
|
}
|
|
478
287
|
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (insertIndex + requirementBlock.length < updatedLines.length && updatedLines[insertIndex + requirementBlock.length].trim() !== '') {
|
|
487
|
-
updatedLines.splice(insertIndex + requirementBlock.length, 0, '');
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
await fs.writeFile(reqPath, updatedLines.join('\n'));
|
|
491
|
-
return true;
|
|
492
|
-
} catch (error) {
|
|
493
|
-
throw new Error(`Failed to demote requirement from TO VERIFY to TODO: ${error.message}`);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Load verified requirements from CHANGELOG.md
|
|
499
|
-
* @param {string} repoPath - Repository root path
|
|
500
|
-
* @returns {Promise<string[]>} List of verified requirement titles
|
|
501
|
-
*/
|
|
502
|
-
async function loadVerifiedFromChangelog(repoPath) {
|
|
503
|
-
try {
|
|
504
|
-
const changelogPath = path.join(repoPath, 'CHANGELOG.md');
|
|
505
|
-
if (!(await fs.pathExists(changelogPath))) {
|
|
506
|
-
return [];
|
|
288
|
+
// Determine final status
|
|
289
|
+
if (health.score >= 90) {
|
|
290
|
+
health.status = 'healthy';
|
|
291
|
+
} else if (health.score >= 70) {
|
|
292
|
+
health.status = 'warning';
|
|
293
|
+
} else {
|
|
294
|
+
health.status = 'critical';
|
|
507
295
|
}
|
|
508
296
|
|
|
509
|
-
|
|
510
|
-
const lines = content.split('\n');
|
|
511
|
-
const requirements = [];
|
|
512
|
-
let inVerifiedSection = false;
|
|
297
|
+
return health;
|
|
513
298
|
|
|
514
|
-
for (const line of lines) {
|
|
515
|
-
const trimmed = line.trim();
|
|
516
|
-
|
|
517
|
-
if (trimmed.includes('## Verified Requirements')) {
|
|
518
|
-
inVerifiedSection = true;
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (inVerifiedSection && trimmed.startsWith('##') && !trimmed.includes('Verified Requirements')) {
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Collect items starting with "- " as verified requirements
|
|
527
|
-
if (inVerifiedSection && trimmed.startsWith('- ') && trimmed.length > 5) {
|
|
528
|
-
let reqText = trimmed.substring(2);
|
|
529
|
-
// Extract title part (before timestamp in parentheses if present)
|
|
530
|
-
const titleMatch = reqText.match(/^(.+?)\s*\([\d-]+\)$/);
|
|
531
|
-
if (titleMatch) {
|
|
532
|
-
reqText = titleMatch[1];
|
|
533
|
-
}
|
|
534
|
-
requirements.push(reqText.trim());
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
return requirements;
|
|
539
299
|
} catch (error) {
|
|
540
|
-
logger.error('❌ Error loading verified requirements from CHANGELOG.md:', error);
|
|
541
|
-
return [];
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Get unified project requirement statistics (TODO, TO VERIFY, VERIFIED)
|
|
547
|
-
* @param {string} repoPath - Repository root path
|
|
548
|
-
* @returns {Promise<Object>} Statistics with counts and formatted percentages
|
|
549
|
-
*/
|
|
550
|
-
async function getProjectRequirementStats(repoPath) {
|
|
551
|
-
try {
|
|
552
|
-
const hostname = os.hostname();
|
|
553
|
-
const { getRequirementsPath } = require('./repo-helpers.cjs');
|
|
554
|
-
const reqPath = await getRequirementsPath(repoPath, hostname);
|
|
555
|
-
|
|
556
|
-
let todoCount = 0;
|
|
557
|
-
let toVerifyCount = 0;
|
|
558
|
-
let verifiedCount = 0;
|
|
559
|
-
let clarificationCount = 0;
|
|
560
|
-
let recycledCount = 0;
|
|
561
|
-
|
|
562
|
-
if (reqPath && await fs.pathExists(reqPath)) {
|
|
563
|
-
const content = await fs.readFile(reqPath, 'utf8');
|
|
564
|
-
const lines = content.split('\n');
|
|
565
|
-
let currentSection = '';
|
|
566
|
-
|
|
567
|
-
const verifySectionVariants = [
|
|
568
|
-
'🔍 TO VERIFY BY HUMAN',
|
|
569
|
-
'TO VERIFY BY HUMAN',
|
|
570
|
-
'🔍 TO VERIFY',
|
|
571
|
-
'TO VERIFY',
|
|
572
|
-
'✅ Verified by AI screenshot',
|
|
573
|
-
'Verified by AI screenshot'
|
|
574
|
-
];
|
|
575
|
-
|
|
576
|
-
const clarificationVariants = [
|
|
577
|
-
'NEEDING CLARIFICATION',
|
|
578
|
-
'Requirements that need information',
|
|
579
|
-
'need information'
|
|
580
|
-
];
|
|
581
|
-
|
|
582
|
-
const recycledVariants = [
|
|
583
|
-
'♻️ Recycled',
|
|
584
|
-
'Recycled'
|
|
585
|
-
];
|
|
586
|
-
|
|
587
|
-
for (const line of lines) {
|
|
588
|
-
const trimmed = line.trim();
|
|
589
|
-
|
|
590
|
-
if (trimmed.startsWith('###')) {
|
|
591
|
-
if (currentSection) {
|
|
592
|
-
// Remove ALL leading ### markers including spaces between them (handles "###", "### ###", "#### ####", etc.)
|
|
593
|
-
const requirementText = trimmed.replace(/^(#{1,}\s*)+/, '').trim();
|
|
594
|
-
// Filter out empty titles and package names
|
|
595
|
-
const packageNames = ['cli', 'core', 'electron-app', 'web', 'mobile', 'vscode-extension', 'sync-server'];
|
|
596
|
-
if (requirementText && requirementText.length > 0 && !packageNames.includes(requirementText.toLowerCase())) {
|
|
597
|
-
if (currentSection === 'todo') todoCount++;
|
|
598
|
-
else if (currentSection === 'toverify') toVerifyCount++;
|
|
599
|
-
else if (currentSection === 'clarification') clarificationCount++;
|
|
600
|
-
else if (currentSection === 'recycled') recycledCount++;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
} else if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
604
|
-
if (trimmed.includes('⏳ Requirements not yet completed') ||
|
|
605
|
-
trimmed.includes('Requirements not yet completed')) {
|
|
606
|
-
currentSection = 'todo';
|
|
607
|
-
} else if (verifySectionVariants.some(v => trimmed.includes(v))) {
|
|
608
|
-
currentSection = 'toverify';
|
|
609
|
-
} else if (clarificationVariants.some(v => trimmed.includes(v))) {
|
|
610
|
-
currentSection = 'clarification';
|
|
611
|
-
} else if (recycledVariants.some(v => trimmed.includes(v))) {
|
|
612
|
-
currentSection = 'recycled';
|
|
613
|
-
} else {
|
|
614
|
-
currentSection = '';
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Load verified counts from CHANGELOG.md
|
|
621
|
-
const verifiedReqs = await loadVerifiedFromChangelog(repoPath);
|
|
622
|
-
verifiedCount = verifiedReqs.length;
|
|
623
|
-
|
|
624
|
-
const total = todoCount + toVerifyCount + verifiedCount + clarificationCount + recycledCount;
|
|
625
|
-
const todoPercent = total > 0 ? Math.round((todoCount / total) * 100) : 0;
|
|
626
|
-
const toVerifyPercent = total > 0 ? Math.round((toVerifyCount / total) * 100) : 0;
|
|
627
|
-
const verifiedPercent = total > 0 ? Math.round((verifiedCount / total) * 100) : 0;
|
|
628
|
-
const clarificationPercent = total > 0 ? Math.round((clarificationCount / total) * 100) : 0;
|
|
629
|
-
const recycledPercent = total > 0 ? Math.round((recycledCount / total) * 100) : 0;
|
|
630
|
-
|
|
631
300
|
return {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
total,
|
|
638
|
-
todoPercent,
|
|
639
|
-
toVerifyPercent,
|
|
640
|
-
verifiedPercent,
|
|
641
|
-
clarificationPercent,
|
|
642
|
-
recycledPercent,
|
|
643
|
-
todoLabel: `TODO (${todoCount} - ${todoPercent}%)`,
|
|
644
|
-
toVerifyLabel: `TO VERIFY (${toVerifyCount} - ${toVerifyPercent}%)`,
|
|
645
|
-
verifiedLabel: `VERIFIED (${verifiedCount} - ${verifiedPercent}%)`,
|
|
646
|
-
clarificationLabel: `CLARIFICATION (${clarificationCount} - ${clarificationPercent}%)`,
|
|
647
|
-
recycledLabel: `RECYCLED (${recycledCount} - ${recycledPercent}%)`
|
|
301
|
+
status: 'error',
|
|
302
|
+
issues: [`Health check failed: ${error.message}`],
|
|
303
|
+
recommendations: ['Check file permissions and paths'],
|
|
304
|
+
score: 0,
|
|
305
|
+
error: error.message
|
|
648
306
|
};
|
|
649
|
-
} catch (error) {
|
|
650
|
-
logger.error('❌ Error getting project requirement stats:', error);
|
|
651
|
-
return null;
|
|
652
307
|
}
|
|
653
308
|
}
|
|
654
309
|
|
|
310
|
+
// Export everything
|
|
655
311
|
module.exports = {
|
|
312
|
+
// Parser utilities
|
|
313
|
+
DEFAULT_INSTRUCTION_TEXT,
|
|
656
314
|
getOrdinalSuffix,
|
|
657
315
|
addTryAgainPrefix,
|
|
658
316
|
parseRequirementLine,
|
|
317
|
+
extractRequirementTitle,
|
|
318
|
+
isRequirementMatch,
|
|
319
|
+
parseRequirementBlock,
|
|
320
|
+
findRequirementBlock,
|
|
321
|
+
findAllRequirementsInSection,
|
|
322
|
+
validateRequirementFormat,
|
|
323
|
+
formatRequirementForDisplay,
|
|
324
|
+
|
|
325
|
+
// Movement operations
|
|
659
326
|
promoteToVerified,
|
|
660
327
|
demoteFromVerifiedToTodo,
|
|
661
328
|
promoteTodoToVerify,
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
329
|
+
demoteFromVerifyToTodo,
|
|
330
|
+
moveToCurrent,
|
|
331
|
+
getRequirementsInSection,
|
|
332
|
+
getRequirementStatistics,
|
|
333
|
+
|
|
334
|
+
// Status management
|
|
335
|
+
extractCurrentStatus,
|
|
336
|
+
updateCurrentStatus,
|
|
337
|
+
getCurrentStatus,
|
|
338
|
+
isStatusDone,
|
|
339
|
+
isStatusInProgress,
|
|
340
|
+
getStatusStage,
|
|
341
|
+
validateStatus,
|
|
342
|
+
getNextStatus,
|
|
343
|
+
getPreviousStatus,
|
|
344
|
+
isValidStatusTransition,
|
|
345
|
+
getStatusHistory,
|
|
346
|
+
formatStatusForDisplay,
|
|
347
|
+
getStatusSummary,
|
|
348
|
+
|
|
349
|
+
// File operations
|
|
350
|
+
getRequirementsFilePath,
|
|
351
|
+
requirementsFileExists,
|
|
352
|
+
createRequirementsFile,
|
|
353
|
+
readRequirementsFile,
|
|
354
|
+
writeRequirementsFile,
|
|
355
|
+
backupRequirementsFile,
|
|
356
|
+
getRequirementsFileStats,
|
|
357
|
+
validateRequirementsFile,
|
|
358
|
+
getAllRequirementsFiles,
|
|
359
|
+
cleanupBackupFiles,
|
|
360
|
+
|
|
361
|
+
// High-level operations
|
|
362
|
+
getRequirementSystemInfo,
|
|
363
|
+
initializeRequirementsSystem,
|
|
364
|
+
getWorkflowStatus,
|
|
365
|
+
advanceWorkflow,
|
|
366
|
+
getHealthCheck
|
|
674
367
|
};
|
|
675
|
-
|
|
676
|
-
// --- Ported Functions from Electron App ---
|
|
677
|
-
|
|
678
|
-
// Function to create a default requirements file
|
|
679
|
-
function createDefaultRequirementsFile(repoPath) {
|
|
680
|
-
try {
|
|
681
|
-
const hostname = os.hostname();
|
|
682
|
-
// Use the core logic to find the .vibecodingmachine directory
|
|
683
|
-
// Note: This logic assumes we want to create it INSIDE the repo if it doesn't exist?
|
|
684
|
-
// Electron app logic was: path.join(repoPath, '.vibecodingmachine')
|
|
685
|
-
// We'll stick to that for creation.
|
|
686
|
-
const vibecodingmachineDir = path.join(repoPath, '.vibecodingmachine');
|
|
687
|
-
|
|
688
|
-
// Ensure .vibecodingmachine directory exists
|
|
689
|
-
if (!fs.existsSync(vibecodingmachineDir)) {
|
|
690
|
-
fs.mkdirSync(vibecodingmachineDir, { recursive: true });
|
|
691
|
-
logger.log('📁 Created .vibecodingmachine directory');
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Create hostname-specific requirements file
|
|
695
|
-
const requirementsFilePath = path.join(vibecodingmachineDir, `REQUIREMENTS-${hostname}.md`);
|
|
696
|
-
|
|
697
|
-
const defaultContent = `# 🌙 VibeCodingMachine – Requirements File
|
|
698
|
-
|
|
699
|
-
This is a Markdown (.md) file.
|
|
700
|
-
|
|
701
|
-
You should not need to modify this file, as VibeCodingMachine will modify it for you, but you can add requirements and move them around either here directly, or via AI.
|
|
702
|
-
|
|
703
|
-
This file contains the requirements to complete the project. Each item in a list is a self contained requirement that should be able to stand on its own, and not require reference to other requirements. Each requirement starts in the section of requirements to complete, then it moved to the section with the current requirement being worked on. VibeCodingMachine will then, after implementing the requirement, move it to the completed requirements list in the CHANGELOG.md file, or to the section in this file for requirements that need attention. These will be available for you to review. They should have options to choose, and you decide which option to take, and VibeCodingMachine will work on that again the next time it is run.
|
|
704
|
-
|
|
705
|
-
## RESPONSE FROM LAST CHAT
|
|
706
|
-
|
|
707
|
-
### ONE LINE STATUS: READY
|
|
708
|
-
|
|
709
|
-
### ONE LINE SUMMARY: New requirements file created
|
|
710
|
-
|
|
711
|
-
### MULTILINE DETAILS: (1-20 lines max)
|
|
712
|
-
|
|
713
|
-
**REQUIREMENT:**
|
|
714
|
-
- Initial setup complete
|
|
715
|
-
|
|
716
|
-
**ALL PHASES COMPLETED:**
|
|
717
|
-
- ✅ Requirements file created
|
|
718
|
-
- ✅ Ready for new requirements
|
|
719
|
-
|
|
720
|
-
## 🔨 Current In Progress Requirement
|
|
721
|
-
|
|
722
|
-
*No current requirement in progress*
|
|
723
|
-
|
|
724
|
-
## 📋 Requirements to Complete
|
|
725
|
-
|
|
726
|
-
*No requirements to complete*
|
|
727
|
-
|
|
728
|
-
## ⚠️ Requirements That Need Attention
|
|
729
|
-
|
|
730
|
-
*No requirements need attention*
|
|
731
|
-
|
|
732
|
-
## ✅ Completed Requirements
|
|
733
|
-
|
|
734
|
-
*No completed requirements yet*
|
|
735
|
-
|
|
736
|
-
---
|
|
737
|
-
|
|
738
|
-
*This file was automatically created by VibeCodingMachine*
|
|
739
|
-
`;
|
|
740
|
-
|
|
741
|
-
fs.writeFileSync(requirementsFilePath, defaultContent, 'utf8');
|
|
742
|
-
logger.log(`📄 Created default requirements file: ${requirementsFilePath}`);
|
|
743
|
-
|
|
744
|
-
return requirementsFilePath;
|
|
745
|
-
} catch (error) {
|
|
746
|
-
logger.error('❌ Error creating default requirements file:', error);
|
|
747
|
-
throw error;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// Function to get or create requirements file path
|
|
752
|
-
async function getOrCreateRequirementsFilePath(repoPath) {
|
|
753
|
-
try {
|
|
754
|
-
const hostname = os.hostname();
|
|
755
|
-
|
|
756
|
-
// Use shared logic to find existing path first
|
|
757
|
-
let requirementsFilePath = await getRequirementsPath(repoPath, hostname);
|
|
758
|
-
|
|
759
|
-
if (requirementsFilePath && await fs.pathExists(requirementsFilePath)) {
|
|
760
|
-
return requirementsFilePath;
|
|
761
|
-
} else {
|
|
762
|
-
// Create a new hostname-specific requirements file
|
|
763
|
-
// We default to creating it inside the repo under .vibecodingmachine
|
|
764
|
-
requirementsFilePath = path.join(repoPath, '.vibecodingmachine', `REQUIREMENTS-${hostname}.md`);
|
|
765
|
-
|
|
766
|
-
const fs = require('fs-extra');
|
|
767
|
-
const defaultRequirementsContent = `# 🌙 VibeCodingMachine – Requirements File
|
|
768
|
-
|
|
769
|
-
This is a Markdown (.md) file.
|
|
770
|
-
|
|
771
|
-
You should not need to modify this file, as VibeCodingMachine will modify it for you, but you can add requirements and move them around either here directly, or via AI.
|
|
772
|
-
|
|
773
|
-
This file contains the requirements to complete the project. Each item in a list is a self contained requirement that should be able to stand on its own, and not require reference to other requirements. Each requirement starts in the section of requirements to complete, then it moved to the section with the current requirement being worked on. VibeCodingMachine will then, after implementing the requirement, move it to the completed requirements list in the CHANGELOG.md file, or to the section in this file for requirements that need attention. These will be available for you to review. They should have options to choose, and you decide which option to take, and VibeCodingMachine will work on that again the next time it is run.
|
|
774
|
-
|
|
775
|
-
## RESPONSE FROM LAST CHAT
|
|
776
|
-
|
|
777
|
-
### ONE LINE STATUS:
|
|
778
|
-
|
|
779
|
-
### ONE LINE SUMMARY:
|
|
780
|
-
|
|
781
|
-
### MULTILINE DETAILS: (1-20 lines max)
|
|
782
|
-
|
|
783
|
-
## 🔨 Current In Progress Requirement
|
|
784
|
-
|
|
785
|
-
-
|
|
786
|
-
|
|
787
|
-
## 🚦 Current Status
|
|
788
|
-
|
|
789
|
-
PREPARE
|
|
790
|
-
|
|
791
|
-
## ⏳ Requirements not yet completed
|
|
792
|
-
|
|
793
|
-
-
|
|
794
|
-
|
|
795
|
-
## ✅ Verified by AI screenshot
|
|
796
|
-
|
|
797
|
-
`;
|
|
798
|
-
fs.ensureDirSync(path.join(repoPath, '.vibecodingmachine'));
|
|
799
|
-
fs.writeFileSync(requirementsFilePath, defaultRequirementsContent, 'utf8');
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
return requirementsFilePath;
|
|
803
|
-
} catch (error) {
|
|
804
|
-
logger.error('❌ Error getting or creating requirements file path:', error);
|
|
805
|
-
throw error;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Function to get current requirement being worked on
|
|
810
|
-
async function getCurrentRequirement(repoPath) {
|
|
811
|
-
try {
|
|
812
|
-
const rPath = repoPath || process.cwd();
|
|
813
|
-
// Get the requirements file path (create if doesn't exist)
|
|
814
|
-
const requirementsFilePath = await getOrCreateRequirementsFilePath(rPath);
|
|
815
|
-
|
|
816
|
-
// Read the requirements file content
|
|
817
|
-
const content = await fs.readFile(requirementsFilePath, 'utf8');
|
|
818
|
-
const lines = content.split('\n');
|
|
819
|
-
|
|
820
|
-
// Look for the current in progress requirement
|
|
821
|
-
for (let i = 0; i < lines.length; i++) {
|
|
822
|
-
if (lines[i].includes('## 🔨 Current In Progress Requirement')) {
|
|
823
|
-
// Look for the next requirement line
|
|
824
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
825
|
-
if (lines[j].trim().startsWith('- ')) {
|
|
826
|
-
let requirementText = lines[j].substring(2).trim();
|
|
827
|
-
|
|
828
|
-
// Extract the requirement title (remove FAILED prefix if present)
|
|
829
|
-
if (requirementText.startsWith('FAILED ')) {
|
|
830
|
-
const failedMatch = requirementText.match(/^FAILED \d+ TIMES?: (.+)$/);
|
|
831
|
-
if (failedMatch) {
|
|
832
|
-
requirementText = failedMatch[1];
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// Extract just the title part (before the colon)
|
|
837
|
-
const colonIndex = requirementText.indexOf(':');
|
|
838
|
-
if (colonIndex !== -1) {
|
|
839
|
-
requirementText = requirementText.substring(0, colonIndex).trim();
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Remove markdown formatting
|
|
843
|
-
requirementText = requirementText.replace(/\*\*/g, '');
|
|
844
|
-
|
|
845
|
-
return `Working on: ${requirementText}`;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
return DEFAULT_INSTRUCTION_TEXT;
|
|
853
|
-
} catch (error) {
|
|
854
|
-
logger.error('❌ Error getting current requirement:', error);
|
|
855
|
-
return DEFAULT_INSTRUCTION_TEXT;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// Function to check if the current requirement status is DONE
|
|
860
|
-
async function isCurrentRequirementDone(repoPath) {
|
|
861
|
-
try {
|
|
862
|
-
const rPath = repoPath || process.cwd();
|
|
863
|
-
const hostname = os.hostname();
|
|
864
|
-
|
|
865
|
-
// Use shared logic to find the file
|
|
866
|
-
const requirementsFilePath = await getRequirementsPath(rPath, hostname);
|
|
867
|
-
|
|
868
|
-
if (!requirementsFilePath || !(await fs.pathExists(requirementsFilePath))) {
|
|
869
|
-
return false;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// Read the requirements file content
|
|
873
|
-
const content = await fs.readFile(requirementsFilePath, 'utf8');
|
|
874
|
-
const lines = content.split('\n');
|
|
875
|
-
|
|
876
|
-
// First pass: check if "Current Status" section contains DONE
|
|
877
|
-
let inStatusSection = false;
|
|
878
|
-
for (let i = 0; i < lines.length; i++) {
|
|
879
|
-
const line = lines[i].trim();
|
|
880
|
-
|
|
881
|
-
if (line.includes('## 🚦 Current Status')) {
|
|
882
|
-
inStatusSection = true;
|
|
883
|
-
continue;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// If we're in the status section
|
|
887
|
-
if (inStatusSection) {
|
|
888
|
-
// Hit another section header, exit
|
|
889
|
-
if (line.startsWith('##')) {
|
|
890
|
-
break;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// Check if this line contains DONE (case-insensitive, exact word match)
|
|
894
|
-
if (line) {
|
|
895
|
-
const upperLine = line.toUpperCase();
|
|
896
|
-
// Match DONE as a standalone word or at the start of the line
|
|
897
|
-
if (upperLine === 'DONE' || upperLine.startsWith('DONE:') || upperLine.startsWith('DONE ')) {
|
|
898
|
-
logger.log('✅ DONE status detected in Current Status section:', line);
|
|
899
|
-
return true;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
// Second pass: check if "Current In Progress Requirement" section is empty
|
|
906
|
-
let inCurrentSection = false;
|
|
907
|
-
let foundRequirement = false;
|
|
908
|
-
for (let i = 0; i < lines.length; i++) {
|
|
909
|
-
const line = lines[i].trim();
|
|
910
|
-
|
|
911
|
-
if (line.includes('## 🔨 Current In Progress Requirement')) {
|
|
912
|
-
inCurrentSection = true;
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// If we're in the current section and hit another section header, exit
|
|
917
|
-
if (inCurrentSection && line.startsWith('##')) {
|
|
918
|
-
// If section was empty (no requirement found), it's done
|
|
919
|
-
if (!foundRequirement) {
|
|
920
|
-
logger.log('✅ Current requirement section is empty - considering as done');
|
|
921
|
-
return true;
|
|
922
|
-
}
|
|
923
|
-
break;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
// Track if we found a requirement in the current section
|
|
927
|
-
if (inCurrentSection && line.startsWith('- ')) {
|
|
928
|
-
foundRequirement = true;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// If we found a requirement but no DONE status, it's not done yet
|
|
933
|
-
if (foundRequirement) {
|
|
934
|
-
logger.log('❌ Found requirement but status is not DONE');
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// Default: not done
|
|
939
|
-
return false;
|
|
940
|
-
} catch (error) {
|
|
941
|
-
logger.error('❌ Error checking requirement status:', error);
|
|
942
|
-
return false;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// Shared function to move to next requirement (used by both IPC and auto mode)
|
|
947
|
-
let isMovingToNextRequirement = false; // Prevent duplicate calls
|
|
948
|
-
let lastMoveTime = 0; // Track when last move happened
|
|
949
|
-
|
|
950
|
-
async function moveToNextRequirement(repoPath) {
|
|
951
|
-
const now = Date.now();
|
|
952
|
-
const rPath = repoPath || process.cwd();
|
|
953
|
-
|
|
954
|
-
// Prevent duplicate calls within 5 seconds
|
|
955
|
-
if (isMovingToNextRequirement || (now - lastMoveTime < 5000)) {
|
|
956
|
-
logger.log('⚠️ moveToNextRequirement already in progress or called too recently, skipping duplicate call');
|
|
957
|
-
return { success: false, error: 'Already moving to next requirement or called too recently' };
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
isMovingToNextRequirement = true;
|
|
961
|
-
lastMoveTime = now;
|
|
962
|
-
|
|
963
|
-
try {
|
|
964
|
-
logger.log('🔄 Moving to next requirement');
|
|
965
|
-
|
|
966
|
-
const hostname = os.hostname();
|
|
967
|
-
// Use shared logic
|
|
968
|
-
const requirementsFilePath = await getRequirementsPath(rPath, hostname);
|
|
969
|
-
|
|
970
|
-
if (!requirementsFilePath || !(await fs.pathExists(requirementsFilePath))) {
|
|
971
|
-
return { success: false, error: 'Requirements file not found' };
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
// Get auto mode config to check skipDisabled setting
|
|
975
|
-
let skipDisabled = false;
|
|
976
|
-
try {
|
|
977
|
-
const { getAutoConfig } = require('../../../cli/src/utils/config');
|
|
978
|
-
const autoConfig = await getAutoConfig();
|
|
979
|
-
skipDisabled = autoConfig.skipDisabled || false;
|
|
980
|
-
} catch (error) {
|
|
981
|
-
// If CLI config is not available, default to false
|
|
982
|
-
logger.log('⚠️ Could not get auto config, using skipDisabled = false');
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// Read current content
|
|
986
|
-
const content = await fs.readFile(requirementsFilePath, 'utf8');
|
|
987
|
-
const lines = content.split('\n');
|
|
988
|
-
|
|
989
|
-
// Extract current requirement and remove from "Current In Progress"
|
|
990
|
-
let currentRequirement = null;
|
|
991
|
-
let inCurrentSection = false;
|
|
992
|
-
for (let i = 0; i < lines.length; i++) {
|
|
993
|
-
if (lines[i].includes('## 🔨 Current In Progress Requirement')) {
|
|
994
|
-
inCurrentSection = true;
|
|
995
|
-
continue;
|
|
996
|
-
}
|
|
997
|
-
if (inCurrentSection && lines[i].trim().startsWith('- ')) {
|
|
998
|
-
currentRequirement = lines[i].substring(2).trim();
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
if (inCurrentSection && lines[i].trim().startsWith('##')) {
|
|
1002
|
-
break;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// Get first non-disabled requirement from "Requirements not yet completed"
|
|
1007
|
-
let nextRequirement = null;
|
|
1008
|
-
let inNotYetCompleted = false;
|
|
1009
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1010
|
-
if (lines[i].includes('## ⏳ Requirements not yet completed')) {
|
|
1011
|
-
inNotYetCompleted = true;
|
|
1012
|
-
continue;
|
|
1013
|
-
}
|
|
1014
|
-
if (inNotYetCompleted && lines[i].trim().startsWith('- ')) {
|
|
1015
|
-
const requirementText = lines[i].substring(2).trim();
|
|
1016
|
-
|
|
1017
|
-
// Check if requirement is disabled (starts with 'DISABLED:')
|
|
1018
|
-
const isDisabled = requirementText.startsWith('DISABLED:');
|
|
1019
|
-
|
|
1020
|
-
// Skip disabled requirements if skipDisabled is enabled
|
|
1021
|
-
if (skipDisabled && isDisabled) {
|
|
1022
|
-
logger.log(`⏭️ Skipping disabled requirement: ${requirementText}`);
|
|
1023
|
-
continue;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
nextRequirement = requirementText;
|
|
1027
|
-
break;
|
|
1028
|
-
}
|
|
1029
|
-
if (inNotYetCompleted && lines[i].trim().startsWith('##')) {
|
|
1030
|
-
break;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
if (!nextRequirement) {
|
|
1035
|
-
return { success: false, error: 'No more requirements to process' };
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// Count requirements in "not yet completed" (before moving)
|
|
1039
|
-
let totalRequirementsNotYetCompleted = 0;
|
|
1040
|
-
inNotYetCompleted = false;
|
|
1041
|
-
for (const line of lines) {
|
|
1042
|
-
if (line.includes('## ⏳ Requirements not yet completed')) {
|
|
1043
|
-
inNotYetCompleted = true;
|
|
1044
|
-
continue;
|
|
1045
|
-
}
|
|
1046
|
-
if (inNotYetCompleted && line.trim().startsWith('##')) {
|
|
1047
|
-
break;
|
|
1048
|
-
}
|
|
1049
|
-
if (inNotYetCompleted && line.trim().startsWith('- ')) {
|
|
1050
|
-
const requirementText = line.substring(2).trim();
|
|
1051
|
-
const isDisabled = requirementText.startsWith('DISABLED:');
|
|
1052
|
-
|
|
1053
|
-
// Don't count disabled requirements if skipDisabled is enabled
|
|
1054
|
-
if (!skipDisabled || !isDisabled) {
|
|
1055
|
-
totalRequirementsNotYetCompleted++;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// Count completed requirements in "Verified by AI" section
|
|
1061
|
-
let completedCount = 0;
|
|
1062
|
-
let inVerifiedSection = false;
|
|
1063
|
-
for (const line of lines) {
|
|
1064
|
-
if (line.includes('## ✅ Verified by AI screenshot')) {
|
|
1065
|
-
inVerifiedSection = true;
|
|
1066
|
-
continue;
|
|
1067
|
-
}
|
|
1068
|
-
if (inVerifiedSection && line.trim().startsWith('##')) {
|
|
1069
|
-
break;
|
|
1070
|
-
}
|
|
1071
|
-
if (inVerifiedSection && line.trim().startsWith('- ')) {
|
|
1072
|
-
completedCount++;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// Update the file: move current to "Verified by AI", move next to "Current In Progress"
|
|
1077
|
-
const updatedLines = [];
|
|
1078
|
-
inCurrentSection = false;
|
|
1079
|
-
inNotYetCompleted = false;
|
|
1080
|
-
inVerifiedSection = false;
|
|
1081
|
-
let addedCurrentToVerified = false;
|
|
1082
|
-
let addedNextToCurrent = false;
|
|
1083
|
-
let removedNextFromNotYetCompleted = false;
|
|
1084
|
-
|
|
1085
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1086
|
-
const line = lines[i];
|
|
1087
|
-
|
|
1088
|
-
// Handle "Current In Progress Requirement" section
|
|
1089
|
-
if (line.includes('## 🔨 Current In Progress Requirement')) {
|
|
1090
|
-
inCurrentSection = true;
|
|
1091
|
-
updatedLines.push(line);
|
|
1092
|
-
updatedLines.push('');
|
|
1093
|
-
updatedLines.push(`- ${nextRequirement}`);
|
|
1094
|
-
updatedLines.push('');
|
|
1095
|
-
addedNextToCurrent = true;
|
|
1096
|
-
continue;
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
// Skip old current requirement and any text in the current section
|
|
1100
|
-
if (inCurrentSection && !line.trim().startsWith('##')) {
|
|
1101
|
-
// Skip lines until we hit the next section header
|
|
1102
|
-
continue;
|
|
1103
|
-
}
|
|
1104
|
-
if (inCurrentSection && line.trim().startsWith('##')) {
|
|
1105
|
-
inCurrentSection = false;
|
|
1106
|
-
// Don't continue - we want to process this section header
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// Handle "Current Status" section
|
|
1110
|
-
if (line.includes('## 🚦 Current Status')) {
|
|
1111
|
-
updatedLines.push(line);
|
|
1112
|
-
updatedLines.push('');
|
|
1113
|
-
updatedLines.push('PREPARE');
|
|
1114
|
-
updatedLines.push('');
|
|
1115
|
-
|
|
1116
|
-
// Skip all old status lines until we hit the next section
|
|
1117
|
-
let j = i + 1;
|
|
1118
|
-
while (j < lines.length && !lines[j].trim().startsWith('##')) {
|
|
1119
|
-
j++;
|
|
1120
|
-
}
|
|
1121
|
-
i = j - 1; // Set i to the line before the next section (loop will increment)
|
|
1122
|
-
continue;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
// Handle "Requirements not yet completed" section
|
|
1126
|
-
if (line.includes('## ⏳ Requirements not yet completed')) {
|
|
1127
|
-
inNotYetCompleted = true;
|
|
1128
|
-
updatedLines.push(line);
|
|
1129
|
-
continue;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
// Remove the next requirement from "not yet completed"
|
|
1133
|
-
if (inNotYetCompleted && line.trim().startsWith('- ') && !removedNextFromNotYetCompleted) {
|
|
1134
|
-
const requirementText = line.substring(2).trim();
|
|
1135
|
-
if (requirementText === nextRequirement) {
|
|
1136
|
-
inNotYetCompleted = false;
|
|
1137
|
-
removedNextFromNotYetCompleted = true;
|
|
1138
|
-
continue; // Skip this line
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// Handle "Verified by AI" section
|
|
1143
|
-
if (line.includes('## ✅ Verified by AI screenshot')) {
|
|
1144
|
-
inNotYetCompleted = false;
|
|
1145
|
-
if (currentRequirement && !addedCurrentToVerified) {
|
|
1146
|
-
// Check if this requirement is already in the verified section
|
|
1147
|
-
const requirementAlreadyExists = updatedLines.some(existingLine =>
|
|
1148
|
-
existingLine.includes(currentRequirement) && existingLine.trim().startsWith('- ')
|
|
1149
|
-
);
|
|
1150
|
-
|
|
1151
|
-
if (!requirementAlreadyExists) {
|
|
1152
|
-
updatedLines.push(line);
|
|
1153
|
-
updatedLines.push('');
|
|
1154
|
-
// Don't add date prefix - just add the requirement as-is to avoid nesting
|
|
1155
|
-
updatedLines.push(`- ${currentRequirement}`);
|
|
1156
|
-
updatedLines.push('');
|
|
1157
|
-
addedCurrentToVerified = true;
|
|
1158
|
-
continue;
|
|
1159
|
-
} else {
|
|
1160
|
-
logger.log(`⚠️ Requirement "${currentRequirement}" already exists in verified section, skipping duplicate`);
|
|
1161
|
-
addedCurrentToVerified = true;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
updatedLines.push(line);
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
updatedLines.push(line);
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
// Write updated content
|
|
1172
|
-
await fs.writeFile(requirementsFilePath, updatedLines.join('\n'), 'utf8');
|
|
1173
|
-
|
|
1174
|
-
// Calculate current number:
|
|
1175
|
-
// - We just completed 1 requirement (moved to "Verified by AI")
|
|
1176
|
-
// - completedCount already includes previously completed requirements
|
|
1177
|
-
// - So current number = completedCount + 1 (the one we just completed) + 1 (the new current one)
|
|
1178
|
-
// - Which simplifies to: completedCount + 2
|
|
1179
|
-
const currentNumber = completedCount + 2; // +1 for the one we just completed, +1 for the new current one we're starting
|
|
1180
|
-
|
|
1181
|
-
// Total requirements = completedCount + 1 (current) + totalRequirementsNotYetCompleted
|
|
1182
|
-
const totalRequirements = completedCount + 1 + totalRequirementsNotYetCompleted;
|
|
1183
|
-
|
|
1184
|
-
logger.log(`✅ Moved to next requirement: ${nextRequirement}`);
|
|
1185
|
-
logger.log(`📊 Progress: ${currentNumber}/${totalRequirements} (completed: ${completedCount + 1}, remaining: ${totalRequirementsNotYetCompleted - 1})`);
|
|
1186
|
-
logger.log(`📝 Moved to requirement: ${nextRequirement}`);
|
|
1187
|
-
|
|
1188
|
-
return {
|
|
1189
|
-
success: true,
|
|
1190
|
-
nextRequirement: nextRequirement,
|
|
1191
|
-
currentNumber: currentNumber,
|
|
1192
|
-
totalRemaining: totalRequirementsNotYetCompleted - 1
|
|
1193
|
-
};
|
|
1194
|
-
} catch (error) {
|
|
1195
|
-
logger.error('❌ Error moving to next requirement:', error);
|
|
1196
|
-
return { success: false, error: error.message };
|
|
1197
|
-
} finally {
|
|
1198
|
-
// Always reset the flag, even if there was an error
|
|
1199
|
-
isMovingToNextRequirement = false;
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
// Function to get current requirement progress numbers
|
|
1204
|
-
async function getCurrentRequirementProgress(repoPath) {
|
|
1205
|
-
try {
|
|
1206
|
-
const stats = await getProjectRequirementStats(repoPath);
|
|
1207
|
-
if (!stats) {
|
|
1208
|
-
return { currentNumber: 1, totalRequirements: 1 };
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// currentNumber = verified + toVerify + 1 (the one we're on)
|
|
1212
|
-
const currentNumber = stats.verifiedCount + stats.toVerifyCount + 1;
|
|
1213
|
-
const totalRequirements = stats.total;
|
|
1214
|
-
|
|
1215
|
-
// Ensure currentNumber doesn't exceed total
|
|
1216
|
-
const sanitizedCurrent = Math.min(currentNumber, totalRequirements || 1);
|
|
1217
|
-
|
|
1218
|
-
return { currentNumber: sanitizedCurrent, totalRequirements: totalRequirements || 1 };
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
logger.error('❌ Error getting current requirement progress:', error);
|
|
1221
|
-
return { currentNumber: 1, totalRequirements: 1 };
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// Function to get current requirement name only (without "Working on:" prefix)
|
|
1226
|
-
async function getCurrentRequirementName(repoPath) {
|
|
1227
|
-
try {
|
|
1228
|
-
const rPath = repoPath || process.cwd();
|
|
1229
|
-
const hostname = os.hostname();
|
|
1230
|
-
|
|
1231
|
-
const requirementsFilePath = await getRequirementsPath(rPath, hostname);
|
|
1232
|
-
|
|
1233
|
-
if (!requirementsFilePath || !(await fs.pathExists(requirementsFilePath))) {
|
|
1234
|
-
return null;
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
// Read the requirements file content
|
|
1238
|
-
const content = await fs.readFile(requirementsFilePath, 'utf8');
|
|
1239
|
-
const lines = content.split('\n');
|
|
1240
|
-
|
|
1241
|
-
// Look for the current in progress requirement
|
|
1242
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1243
|
-
if (lines[i].includes('## 🔨 Current In Progress Requirement')) {
|
|
1244
|
-
// Look for the next requirement line
|
|
1245
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
1246
|
-
if (lines[j].trim().startsWith('- ')) {
|
|
1247
|
-
let requirementText = lines[j].substring(2).trim();
|
|
1248
|
-
|
|
1249
|
-
// Remove markdown formatting (bold **)
|
|
1250
|
-
requirementText = requirementText.replace(/\*\*/g, '');
|
|
1251
|
-
|
|
1252
|
-
// Return the full requirement text without any prefix
|
|
1253
|
-
return requirementText;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
break;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
return null;
|
|
1261
|
-
} catch (error) {
|
|
1262
|
-
logger.error('❌ Error getting current requirement name:', error);
|
|
1263
|
-
return null;
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
|