kiro-spec-engine 1.4.4 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -1
- package/README.md +16 -0
- package/README.zh.md +380 -0
- package/bin/kiro-spec-engine.js +102 -44
- package/docs/adoption-guide.md +53 -0
- package/docs/document-governance.md +864 -0
- package/docs/spec-numbering-guide.md +348 -0
- package/docs/spec-workflow.md +65 -0
- package/docs/troubleshooting.md +339 -0
- package/docs/zh/spec-numbering-guide.md +348 -0
- package/lib/adoption/adoption-strategy.js +22 -6
- package/lib/adoption/conflict-resolver.js +239 -0
- package/lib/adoption/diff-viewer.js +226 -0
- package/lib/backup/selective-backup.js +207 -0
- package/lib/commands/adopt.js +95 -10
- package/lib/commands/docs.js +717 -0
- package/lib/commands/doctor.js +141 -3
- package/lib/commands/status.js +77 -5
- package/lib/governance/archive-tool.js +231 -0
- package/lib/governance/cleanup-tool.js +237 -0
- package/lib/governance/config-manager.js +186 -0
- package/lib/governance/diagnostic-engine.js +271 -0
- package/lib/governance/execution-logger.js +243 -0
- package/lib/governance/file-scanner.js +285 -0
- package/lib/governance/hooks-manager.js +333 -0
- package/lib/governance/reporter.js +337 -0
- package/lib/governance/validation-engine.js +181 -0
- package/package.json +1 -1
package/lib/commands/doctor.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const pythonChecker = require('../python-checker');
|
|
3
3
|
const { getI18n } = require('../i18n');
|
|
4
|
+
const DiagnosticEngine = require('../governance/diagnostic-engine');
|
|
5
|
+
const ConfigManager = require('../governance/config-manager');
|
|
6
|
+
const path = require('path');
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* CLI Doctor Command Component
|
|
7
10
|
*
|
|
8
11
|
* Verifies system requirements and provides diagnostics.
|
|
9
|
-
* Checks Node.js version and
|
|
12
|
+
* Checks Node.js version, Python availability, and document compliance.
|
|
10
13
|
*
|
|
11
|
-
* Requirements: 7.5
|
|
14
|
+
* Requirements: 7.5, 10.2
|
|
12
15
|
*/
|
|
13
|
-
function doctorCommand() {
|
|
16
|
+
async function doctorCommand(options = {}) {
|
|
14
17
|
const i18n = getI18n();
|
|
15
18
|
|
|
16
19
|
console.log(chalk.red('🔥') + ' ' + i18n.t('cli.commands.doctor.title'));
|
|
@@ -43,6 +46,13 @@ function doctorCommand() {
|
|
|
43
46
|
console.log(chalk.blue('─'.repeat(60)));
|
|
44
47
|
console.log();
|
|
45
48
|
|
|
49
|
+
// Check document compliance
|
|
50
|
+
await checkDocumentCompliance(options);
|
|
51
|
+
|
|
52
|
+
console.log();
|
|
53
|
+
console.log(chalk.blue('─'.repeat(60)));
|
|
54
|
+
console.log();
|
|
55
|
+
|
|
46
56
|
// Summary
|
|
47
57
|
if (pythonStatus.available) {
|
|
48
58
|
console.log(chalk.green('✅ ' + i18n.t('cli.commands.doctor.all_good')));
|
|
@@ -56,4 +66,132 @@ function doctorCommand() {
|
|
|
56
66
|
console.log();
|
|
57
67
|
}
|
|
58
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Check document compliance
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} options - Command options
|
|
73
|
+
* @param {boolean} options.docs - Whether to show detailed diagnostics
|
|
74
|
+
*/
|
|
75
|
+
async function checkDocumentCompliance(options = {}) {
|
|
76
|
+
try {
|
|
77
|
+
const projectPath = process.cwd();
|
|
78
|
+
|
|
79
|
+
// Load configuration
|
|
80
|
+
const configManager = new ConfigManager(projectPath);
|
|
81
|
+
const config = await configManager.load();
|
|
82
|
+
|
|
83
|
+
// Run diagnostic scan
|
|
84
|
+
const diagnosticEngine = new DiagnosticEngine(projectPath, config);
|
|
85
|
+
const report = await diagnosticEngine.scan();
|
|
86
|
+
|
|
87
|
+
// Display results based on options
|
|
88
|
+
if (options.docs) {
|
|
89
|
+
// Detailed diagnostics mode
|
|
90
|
+
displayDetailedDiagnostics(report);
|
|
91
|
+
} else {
|
|
92
|
+
// Brief compliance status
|
|
93
|
+
displayBriefCompliance(report);
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.log(chalk.yellow('⚠️ Document compliance check failed:'), error.message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Display brief document compliance status
|
|
102
|
+
*
|
|
103
|
+
* @param {Object} report - Diagnostic report
|
|
104
|
+
*/
|
|
105
|
+
function displayBriefCompliance(report) {
|
|
106
|
+
console.log(chalk.bold('Document Compliance:'));
|
|
107
|
+
|
|
108
|
+
if (report.compliant) {
|
|
109
|
+
console.log(chalk.green(' ✓ All documents follow lifecycle management rules'));
|
|
110
|
+
} else {
|
|
111
|
+
const errorCount = report.summary.bySeverity.error || 0;
|
|
112
|
+
const warningCount = report.summary.bySeverity.warning || 0;
|
|
113
|
+
|
|
114
|
+
if (errorCount > 0) {
|
|
115
|
+
console.log(chalk.red(` ✗ ${errorCount} error(s) found`));
|
|
116
|
+
}
|
|
117
|
+
if (warningCount > 0) {
|
|
118
|
+
console.log(chalk.yellow(` ⚠ ${warningCount} warning(s) found`));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(chalk.cyan(` → Run 'kse doctor --docs' for detailed diagnostics`));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Display detailed document diagnostics
|
|
127
|
+
*
|
|
128
|
+
* @param {Object} report - Diagnostic report
|
|
129
|
+
*/
|
|
130
|
+
function displayDetailedDiagnostics(report) {
|
|
131
|
+
console.log(chalk.bold.cyan('Document Governance Diagnostic:'));
|
|
132
|
+
console.log();
|
|
133
|
+
|
|
134
|
+
if (report.compliant) {
|
|
135
|
+
console.log(chalk.green(' ✅ Project is compliant'));
|
|
136
|
+
console.log(' All documents follow the lifecycle management rules.');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(chalk.yellow(` ⚠️ Found ${report.violations.length} violation(s)`));
|
|
141
|
+
console.log();
|
|
142
|
+
|
|
143
|
+
// Group violations by type
|
|
144
|
+
const byType = groupBy(report.violations, 'type');
|
|
145
|
+
|
|
146
|
+
for (const [type, violations] of Object.entries(byType)) {
|
|
147
|
+
console.log(chalk.blue(` ${formatType(type)} (${violations.length})`));
|
|
148
|
+
|
|
149
|
+
for (const violation of violations) {
|
|
150
|
+
const icon = violation.severity === 'error' ? '❌' : '⚠️';
|
|
151
|
+
const relativePath = path.relative(process.cwd(), violation.path);
|
|
152
|
+
console.log(` ${icon} ${chalk.gray(relativePath)}`);
|
|
153
|
+
console.log(` ${violation.description}`);
|
|
154
|
+
console.log(` ${chalk.cyan('→ ' + violation.recommendation)}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Display recommendations
|
|
161
|
+
if (report.recommendations && report.recommendations.length > 0) {
|
|
162
|
+
console.log(chalk.blue(' 💡 Recommended Actions:'));
|
|
163
|
+
report.recommendations.forEach(rec => {
|
|
164
|
+
console.log(` • ${rec}`);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Helper: Group array by property
|
|
171
|
+
*
|
|
172
|
+
* @param {Array} array - Array to group
|
|
173
|
+
* @param {string} property - Property to group by
|
|
174
|
+
* @returns {Object} - Grouped object
|
|
175
|
+
*/
|
|
176
|
+
function groupBy(array, property) {
|
|
177
|
+
return array.reduce((acc, item) => {
|
|
178
|
+
const key = item[property];
|
|
179
|
+
if (!acc[key]) acc[key] = [];
|
|
180
|
+
acc[key].push(item);
|
|
181
|
+
return acc;
|
|
182
|
+
}, {});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Helper: Format violation type
|
|
187
|
+
*
|
|
188
|
+
* @param {string} type - Violation type
|
|
189
|
+
* @returns {string} - Formatted type
|
|
190
|
+
*/
|
|
191
|
+
function formatType(type) {
|
|
192
|
+
return type.split('_').map(word =>
|
|
193
|
+
word.charAt(0).toUpperCase() + word.slice(1)
|
|
194
|
+
).join(' ');
|
|
195
|
+
}
|
|
196
|
+
|
|
59
197
|
module.exports = doctorCommand;
|
package/lib/commands/status.js
CHANGED
|
@@ -9,6 +9,8 @@ const path = require('path');
|
|
|
9
9
|
const fs = require('fs-extra');
|
|
10
10
|
const TaskClaimer = require('../task/task-claimer');
|
|
11
11
|
const WorkspaceManager = require('../workspace/workspace-manager');
|
|
12
|
+
const DiagnosticEngine = require('../governance/diagnostic-engine');
|
|
13
|
+
const ConfigManager = require('../governance/config-manager');
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Executes the status command
|
|
@@ -57,7 +59,10 @@ async function statusCommand(options = {}) {
|
|
|
57
59
|
|
|
58
60
|
console.log();
|
|
59
61
|
|
|
60
|
-
// 3.
|
|
62
|
+
// 3. Document compliance status
|
|
63
|
+
await displayDocumentCompliance(projectPath);
|
|
64
|
+
|
|
65
|
+
// 4. List specs
|
|
61
66
|
const specsPath = path.join(projectPath, '.kiro/specs');
|
|
62
67
|
const specsExist = await fs.pathExists(specsPath);
|
|
63
68
|
|
|
@@ -83,7 +88,7 @@ async function statusCommand(options = {}) {
|
|
|
83
88
|
console.log(chalk.blue(`📁 Specs (${specDirs.length})`));
|
|
84
89
|
console.log();
|
|
85
90
|
|
|
86
|
-
//
|
|
91
|
+
// 5. Analyze each spec
|
|
87
92
|
const taskClaimer = new TaskClaimer();
|
|
88
93
|
const allClaimedTasks = [];
|
|
89
94
|
|
|
@@ -145,7 +150,7 @@ async function statusCommand(options = {}) {
|
|
|
145
150
|
console.log();
|
|
146
151
|
}
|
|
147
152
|
|
|
148
|
-
//
|
|
153
|
+
// 6. Team activity view
|
|
149
154
|
if (team && allClaimedTasks.length > 0) {
|
|
150
155
|
console.log(chalk.blue('👥 Team Activity'));
|
|
151
156
|
console.log();
|
|
@@ -186,7 +191,7 @@ async function statusCommand(options = {}) {
|
|
|
186
191
|
});
|
|
187
192
|
}
|
|
188
193
|
|
|
189
|
-
//
|
|
194
|
+
// 7. Summary
|
|
190
195
|
if (allClaimedTasks.length > 0) {
|
|
191
196
|
const staleClaims = allClaimedTasks.filter(t => t.isStale);
|
|
192
197
|
|
|
@@ -202,7 +207,7 @@ async function statusCommand(options = {}) {
|
|
|
202
207
|
}
|
|
203
208
|
}
|
|
204
209
|
|
|
205
|
-
//
|
|
210
|
+
// 8. Next steps
|
|
206
211
|
console.log(chalk.blue('💡 Commands'));
|
|
207
212
|
console.log(' View team activity: ' + chalk.cyan('kse status --team'));
|
|
208
213
|
console.log(' Detailed view: ' + chalk.cyan('kse status --verbose'));
|
|
@@ -222,4 +227,71 @@ async function statusCommand(options = {}) {
|
|
|
222
227
|
}
|
|
223
228
|
}
|
|
224
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Display document compliance status
|
|
232
|
+
*
|
|
233
|
+
* @param {string} projectPath - Project root path
|
|
234
|
+
* @returns {Promise<void>}
|
|
235
|
+
*/
|
|
236
|
+
async function displayDocumentCompliance(projectPath) {
|
|
237
|
+
try {
|
|
238
|
+
// Load configuration
|
|
239
|
+
const configManager = new ConfigManager(projectPath);
|
|
240
|
+
await configManager.load();
|
|
241
|
+
|
|
242
|
+
// Run diagnostic scan
|
|
243
|
+
const diagnosticEngine = new DiagnosticEngine(projectPath, configManager.config);
|
|
244
|
+
const report = await diagnosticEngine.scan();
|
|
245
|
+
|
|
246
|
+
// Display compliance status
|
|
247
|
+
console.log(chalk.blue('📄 Document Compliance'));
|
|
248
|
+
|
|
249
|
+
if (report.compliant) {
|
|
250
|
+
console.log(` Status: ${chalk.green('✅ Compliant')}`);
|
|
251
|
+
console.log(` ${chalk.gray('All documents follow lifecycle management rules')}`);
|
|
252
|
+
} else {
|
|
253
|
+
const errorCount = report.violations.filter(v => v.severity === 'error').length;
|
|
254
|
+
const warningCount = report.violations.filter(v => v.severity === 'warning').length;
|
|
255
|
+
|
|
256
|
+
console.log(` Status: ${chalk.red('❌ Non-Compliant')}`);
|
|
257
|
+
console.log(` Violations: ${chalk.red(errorCount)} error(s), ${chalk.yellow(warningCount)} warning(s)`);
|
|
258
|
+
|
|
259
|
+
// Show violation breakdown by type
|
|
260
|
+
const byType = {};
|
|
261
|
+
report.violations.forEach(v => {
|
|
262
|
+
byType[v.type] = (byType[v.type] || 0) + 1;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const typeNames = {
|
|
266
|
+
'root_violation': 'Root directory',
|
|
267
|
+
'spec_violation': 'Spec structure',
|
|
268
|
+
'missing_file': 'Missing files',
|
|
269
|
+
'misplaced_artifact': 'Misplaced artifacts',
|
|
270
|
+
'temporary_document': 'Temporary documents'
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
Object.entries(byType).forEach(([type, count]) => {
|
|
274
|
+
const name = typeNames[type] || type;
|
|
275
|
+
console.log(` • ${name}: ${count}`);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
console.log();
|
|
279
|
+
console.log(chalk.cyan(' Quick Fix Commands:'));
|
|
280
|
+
console.log(` ${chalk.gray('•')} Run diagnostics: ${chalk.cyan('kse doctor --docs')}`);
|
|
281
|
+
console.log(` ${chalk.gray('•')} Clean temporary files: ${chalk.cyan('kse cleanup')}`);
|
|
282
|
+
console.log(` ${chalk.gray('•')} Validate structure: ${chalk.cyan('kse validate --all')}`);
|
|
283
|
+
console.log(` ${chalk.gray('•')} Archive artifacts: ${chalk.cyan('kse archive --spec <name>')}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
// Silently skip if governance components not available
|
|
289
|
+
// This allows status to work even if governance is not fully set up
|
|
290
|
+
if (error.code !== 'MODULE_NOT_FOUND') {
|
|
291
|
+
console.log(chalk.gray(' (Document compliance check skipped)'));
|
|
292
|
+
console.log();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
225
297
|
module.exports = statusCommand;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive Tool
|
|
3
|
+
*
|
|
4
|
+
* Organizes Spec artifacts into proper subdirectories
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const FileScanner = require('./file-scanner');
|
|
10
|
+
|
|
11
|
+
class ArchiveTool {
|
|
12
|
+
constructor(projectPath, config) {
|
|
13
|
+
this.projectPath = projectPath;
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.scanner = new FileScanner(projectPath);
|
|
16
|
+
this.movedFiles = [];
|
|
17
|
+
this.errors = [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Archive artifacts in a Spec directory
|
|
22
|
+
*
|
|
23
|
+
* @param {string} specName - Spec name
|
|
24
|
+
* @param {Object} options - Archive options
|
|
25
|
+
* @param {boolean} options.dryRun - Preview without moving
|
|
26
|
+
* @returns {Promise<ArchiveReport>}
|
|
27
|
+
*/
|
|
28
|
+
async archive(specName, options = {}) {
|
|
29
|
+
const specPath = this.scanner.getSpecDirectory(specName);
|
|
30
|
+
|
|
31
|
+
// Check if Spec directory exists
|
|
32
|
+
if (!await this.scanner.exists(specPath)) {
|
|
33
|
+
this.errors.push({
|
|
34
|
+
path: specPath,
|
|
35
|
+
error: `Spec directory does not exist: ${specName}`
|
|
36
|
+
});
|
|
37
|
+
return this.generateReport();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const artifacts = await this.identifyArtifacts(specPath);
|
|
41
|
+
|
|
42
|
+
if (options.dryRun) {
|
|
43
|
+
return this.generateDryRunReport(artifacts);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const artifact of artifacts) {
|
|
47
|
+
await this.moveArtifact(artifact);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.generateReport();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Identify artifacts to archive
|
|
55
|
+
*
|
|
56
|
+
* @param {string} specPath - Path to Spec directory
|
|
57
|
+
* @returns {Promise<Artifact[]>}
|
|
58
|
+
*/
|
|
59
|
+
async identifyArtifacts(specPath) {
|
|
60
|
+
const artifacts = [];
|
|
61
|
+
const files = await this.scanner.getFiles(specPath);
|
|
62
|
+
const requiredFiles = ['requirements.md', 'design.md', 'tasks.md'];
|
|
63
|
+
|
|
64
|
+
for (const filePath of files) {
|
|
65
|
+
const basename = path.basename(filePath);
|
|
66
|
+
|
|
67
|
+
// Skip required files
|
|
68
|
+
if (requiredFiles.includes(basename)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const targetSubdir = this.determineTargetSubdir(basename);
|
|
73
|
+
artifacts.push({
|
|
74
|
+
sourcePath: filePath,
|
|
75
|
+
targetSubdir: targetSubdir,
|
|
76
|
+
filename: basename
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return artifacts;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Determine target subdirectory for a file
|
|
85
|
+
*
|
|
86
|
+
* @param {string} filename - File name
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
determineTargetSubdir(filename) {
|
|
90
|
+
const lower = filename.toLowerCase();
|
|
91
|
+
|
|
92
|
+
// Results - check for result keywords FIRST (before test patterns)
|
|
93
|
+
// This ensures "test-results.json" goes to results, not tests
|
|
94
|
+
if (lower.includes('result') || lower.includes('output')) {
|
|
95
|
+
return 'results';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Tests - check for test patterns (test files, not result files)
|
|
99
|
+
if (lower.endsWith('.test.js') || lower.endsWith('.spec.js') ||
|
|
100
|
+
lower.includes('.test.') || lower.includes('.spec.') ||
|
|
101
|
+
(lower.includes('test') && (lower.endsWith('.js') || lower.endsWith('.py')))) {
|
|
102
|
+
return 'tests';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Scripts - check for script extensions and keywords
|
|
106
|
+
if (lower.endsWith('.js') || lower.endsWith('.py') ||
|
|
107
|
+
lower.endsWith('.sh') || lower.endsWith('.bat') ||
|
|
108
|
+
lower.includes('script')) {
|
|
109
|
+
return 'scripts';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Reports - check for report keywords
|
|
113
|
+
if (lower.includes('report') || lower.includes('analysis') ||
|
|
114
|
+
lower.includes('summary') || lower.includes('diagnostic')) {
|
|
115
|
+
return 'reports';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Data files
|
|
119
|
+
if (lower.includes('data')) {
|
|
120
|
+
return 'results';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Default to docs for markdown and other documentation files
|
|
124
|
+
return 'docs';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Move an artifact to its target subdirectory
|
|
129
|
+
*
|
|
130
|
+
* @param {Artifact} artifact - Artifact to move
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
async moveArtifact(artifact) {
|
|
134
|
+
try {
|
|
135
|
+
const targetDir = path.join(
|
|
136
|
+
path.dirname(artifact.sourcePath),
|
|
137
|
+
artifact.targetSubdir
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Ensure target directory exists
|
|
141
|
+
await fs.ensureDir(targetDir);
|
|
142
|
+
|
|
143
|
+
const targetPath = path.join(targetDir, artifact.filename);
|
|
144
|
+
|
|
145
|
+
// Check if target file already exists
|
|
146
|
+
if (await fs.pathExists(targetPath)) {
|
|
147
|
+
this.errors.push({
|
|
148
|
+
path: artifact.sourcePath,
|
|
149
|
+
error: `Target file already exists: ${targetPath}`
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await fs.move(artifact.sourcePath, targetPath);
|
|
155
|
+
|
|
156
|
+
this.movedFiles.push({
|
|
157
|
+
from: artifact.sourcePath,
|
|
158
|
+
to: targetPath
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.errors.push({
|
|
162
|
+
path: artifact.sourcePath,
|
|
163
|
+
error: error.message
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Generate dry run report
|
|
170
|
+
*
|
|
171
|
+
* @param {Artifact[]} artifacts - Artifacts that would be moved
|
|
172
|
+
* @returns {ArchiveReport}
|
|
173
|
+
*/
|
|
174
|
+
generateDryRunReport(artifacts) {
|
|
175
|
+
const movedFiles = artifacts.map(artifact => ({
|
|
176
|
+
from: artifact.sourcePath,
|
|
177
|
+
to: path.join(
|
|
178
|
+
path.dirname(artifact.sourcePath),
|
|
179
|
+
artifact.targetSubdir,
|
|
180
|
+
artifact.filename
|
|
181
|
+
)
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
success: true,
|
|
186
|
+
movedFiles: movedFiles,
|
|
187
|
+
errors: [],
|
|
188
|
+
summary: {
|
|
189
|
+
totalMoved: movedFiles.length,
|
|
190
|
+
totalErrors: 0
|
|
191
|
+
},
|
|
192
|
+
dryRun: true
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Generate archive report
|
|
198
|
+
*
|
|
199
|
+
* @returns {ArchiveReport}
|
|
200
|
+
*/
|
|
201
|
+
generateReport() {
|
|
202
|
+
return {
|
|
203
|
+
success: this.errors.length === 0,
|
|
204
|
+
movedFiles: this.movedFiles,
|
|
205
|
+
errors: this.errors,
|
|
206
|
+
summary: {
|
|
207
|
+
totalMoved: this.movedFiles.length,
|
|
208
|
+
totalErrors: this.errors.length
|
|
209
|
+
},
|
|
210
|
+
dryRun: false
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @typedef {Object} Artifact
|
|
217
|
+
* @property {string} sourcePath - Current file path
|
|
218
|
+
* @property {string} targetSubdir - Target subdirectory name
|
|
219
|
+
* @property {string} filename - File name
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @typedef {Object} ArchiveReport
|
|
224
|
+
* @property {boolean} success - Whether archiving succeeded
|
|
225
|
+
* @property {Object[]} movedFiles - Files that were moved
|
|
226
|
+
* @property {Object[]} errors - Errors encountered
|
|
227
|
+
* @property {Object} summary - Summary statistics
|
|
228
|
+
* @property {boolean} dryRun - Whether this was a dry run
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
module.exports = ArchiveTool;
|