@mod-computer/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -0
- package/commands/execute.md +156 -0
- package/commands/overview.md +233 -0
- package/commands/review.md +151 -0
- package/commands/spec.md +169 -0
- package/dist/app.js +227 -0
- package/dist/cli.bundle.js +25824 -0
- package/dist/cli.bundle.js.map +7 -0
- package/dist/cli.js +121 -0
- package/dist/commands/agents-run.js +71 -0
- package/dist/commands/auth.js +151 -0
- package/dist/commands/branch.js +1411 -0
- package/dist/commands/claude-sync.js +772 -0
- package/dist/commands/index.js +43 -0
- package/dist/commands/init.js +378 -0
- package/dist/commands/recover.js +207 -0
- package/dist/commands/spec.js +386 -0
- package/dist/commands/status.js +329 -0
- package/dist/commands/sync.js +95 -0
- package/dist/commands/workspace.js +423 -0
- package/dist/components/conflict-resolution-ui.js +120 -0
- package/dist/components/messages.js +5 -0
- package/dist/components/thread.js +8 -0
- package/dist/config/features.js +72 -0
- package/dist/config/release-profiles/development.json +11 -0
- package/dist/config/release-profiles/mvp.json +12 -0
- package/dist/config/release-profiles/v0.1.json +11 -0
- package/dist/config/release-profiles/v0.2.json +11 -0
- package/dist/containers/branches-container.js +140 -0
- package/dist/containers/directory-container.js +92 -0
- package/dist/containers/thread-container.js +214 -0
- package/dist/containers/threads-container.js +27 -0
- package/dist/containers/workspaces-container.js +27 -0
- package/dist/daemon-worker.js +257 -0
- package/dist/lib/auth-server.js +153 -0
- package/dist/lib/browser.js +35 -0
- package/dist/lib/storage.js +203 -0
- package/dist/services/automatic-file-tracker.js +303 -0
- package/dist/services/cli-orchestrator.js +227 -0
- package/dist/services/feature-flags.js +187 -0
- package/dist/services/file-import-service.js +283 -0
- package/dist/services/file-transformation-service.js +218 -0
- package/dist/services/logger.js +44 -0
- package/dist/services/mod-config.js +61 -0
- package/dist/services/modignore-service.js +326 -0
- package/dist/services/sync-daemon.js +244 -0
- package/dist/services/thread-notification-service.js +50 -0
- package/dist/services/thread-service.js +147 -0
- package/dist/stores/use-directory-store.js +96 -0
- package/dist/stores/use-threads-store.js +46 -0
- package/dist/stores/use-workspaces-store.js +32 -0
- package/dist/types/config.js +16 -0
- package/dist/types/index.js +2 -0
- package/dist/types/workspace-connection.js +2 -0
- package/dist/types.js +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// @spec status-command.spec.md/REQ-INT-1.1 Import SpecificationParserService from mod-core
|
|
2
|
+
// @spec status-command.spec.md/REQ-INT-1.3 Reuse RequirementCategory enum and interfaces from mod-core types
|
|
3
|
+
import { SpecificationParserService, RequirementCategory } from '@mod/mod-core';
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { log } from '../services/logger.js';
|
|
7
|
+
// @spec status-command.spec.md/REQ-INT-2.2 Standard command signature
|
|
8
|
+
// @spec status-command.spec.md/REQ-INFRA-2.1 Follow existing CLI command patterns from mod-cli package
|
|
9
|
+
export async function statusCommand(args) {
|
|
10
|
+
try {
|
|
11
|
+
// @spec status-command.spec.md/REQ-BUS-2.1 Parse spec file argument
|
|
12
|
+
if (args.length === 0) {
|
|
13
|
+
console.error('Error: Specification file required');
|
|
14
|
+
console.error('Usage: mod status <spec-file>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
let specName = args[0];
|
|
18
|
+
// @spec status-command.spec.md/REQ-DATA-1.1 Auto-append .spec.md extension
|
|
19
|
+
if (!specName.endsWith('.spec.md')) {
|
|
20
|
+
specName += '.spec.md';
|
|
21
|
+
}
|
|
22
|
+
const workspaceRoot = process.cwd();
|
|
23
|
+
const analysis = await analyzeSpecification(workspaceRoot, specName);
|
|
24
|
+
// @spec status-command.spec.md/REQ-UX-1.1 TypeScript compiler-style output
|
|
25
|
+
printTscStyleOutput(analysis);
|
|
26
|
+
// Exit successfully
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// @spec status-command.spec.md/REQ-BUS-2.1 Handle different error types with appropriate exit codes
|
|
31
|
+
if (error instanceof Error) {
|
|
32
|
+
// @spec status-command.spec.md/REQ-UX-2.1 Clear error messages for file not found
|
|
33
|
+
if (error.message.includes('not found')) {
|
|
34
|
+
console.error(`Error: Specification file "${args[0]}" not found`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
// @spec status-command.spec.md/REQ-UX-2.1 Clear error messages for parse errors
|
|
37
|
+
}
|
|
38
|
+
else if (error.message.includes('parse')) {
|
|
39
|
+
console.error(`Error: Failed to parse specification: ${error.message}`);
|
|
40
|
+
process.exit(2);
|
|
41
|
+
// @spec status-command.spec.md/REQ-UX-2.1 Clear error messages for scan failures
|
|
42
|
+
}
|
|
43
|
+
else if (error.message.includes('scan')) {
|
|
44
|
+
console.error(`Error: Failed to scan workspace: ${error.message}`);
|
|
45
|
+
process.exit(3);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.error(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
|
|
49
|
+
process.exit(3);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// @spec status-command.spec.md/REQ-DATA-1.1 Use SpecificationParserService for parsing and scanning
|
|
53
|
+
async function analyzeSpecification(workspaceRoot, specName) {
|
|
54
|
+
// @spec status-command.spec.md/REQ-INT-1.1 Initialize SpecificationParserService
|
|
55
|
+
// @spec status-command.spec.md/REQ-BUS-2.2 Pure offline operation using local files only, no network or repo connections
|
|
56
|
+
const parser = new SpecificationParserService({
|
|
57
|
+
workspaceRoot,
|
|
58
|
+
specsDirectory: '.mod/specs',
|
|
59
|
+
cacheEnabled: false
|
|
60
|
+
});
|
|
61
|
+
// @spec status-command.spec.md/REQ-DATA-2.1 Scan workspace for @spec traces
|
|
62
|
+
// @spec status-command.spec.md/REQ-INFRA-2.2 Use existing logger service for debug output when MOD_CLI_DEBUG is set
|
|
63
|
+
log('[Status] Scanning workspace for @spec traces');
|
|
64
|
+
const startTime = Date.now();
|
|
65
|
+
const traceMap = await parser.scanWorkspaceForTraces();
|
|
66
|
+
const scanTime = Date.now() - startTime;
|
|
67
|
+
// @spec status-command.spec.md/REQ-INFRA-1.1 Process up to 1000 requirements in under 5 seconds
|
|
68
|
+
log(`[Status] Scan completed in ${scanTime}ms, found ${traceMap.size} requirement IDs`);
|
|
69
|
+
// @spec status-command.spec.md/REQ-DATA-1.1 Find and parse specification file
|
|
70
|
+
const specFilePath = await findSpecificationFile(workspaceRoot, specName);
|
|
71
|
+
const content = await fs.readFile(specFilePath, 'utf-8');
|
|
72
|
+
const parsed = await parser.parseSpecification(content, specFilePath);
|
|
73
|
+
// Calculate frontmatter offset for line number adjustment
|
|
74
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
75
|
+
const frontmatterOffset = frontmatterMatch ? frontmatterMatch[0].split('\n').length - 1 : 0;
|
|
76
|
+
// @spec status-command.spec.md/REQ-DATA-1.2 Extract nested requirements from bullet points
|
|
77
|
+
const allRequirements = extractNestedRequirements(content, parsed.requirements, frontmatterOffset);
|
|
78
|
+
// @spec status-command.spec.md/REQ-DATA-2.3 Filter traces by spec name
|
|
79
|
+
const filteredTraces = filterTracesBySpec(traceMap, specName);
|
|
80
|
+
// @spec status-command.spec.md/REQ-BUS-1.1 Calculate status for each requirement
|
|
81
|
+
const requirements = allRequirements.map(req => {
|
|
82
|
+
const traces = filteredTraces.get(req.id) || [];
|
|
83
|
+
const status = getRequirementStatus(traces.length);
|
|
84
|
+
return {
|
|
85
|
+
requirementId: req.id,
|
|
86
|
+
category: req.category,
|
|
87
|
+
title: req.title,
|
|
88
|
+
status,
|
|
89
|
+
traceCount: traces.length,
|
|
90
|
+
traces: traces.map(trace => ({
|
|
91
|
+
file: path.relative(workspaceRoot, trace.filePath),
|
|
92
|
+
line: trace.lineRange.start,
|
|
93
|
+
description: trace.description
|
|
94
|
+
})),
|
|
95
|
+
hierarchyLevel: calculateHierarchyLevel(req.id),
|
|
96
|
+
lineRange: req.lineRange
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
// @spec status-command.spec.md/REQ-BUS-1.2 Hierarchical aggregation: parent status reflects worst child status
|
|
100
|
+
applyHierarchicalAggregation(requirements);
|
|
101
|
+
// @spec status-command.spec.md/REQ-BUS-1.2 Calculate summary counts
|
|
102
|
+
const summary = {
|
|
103
|
+
traced: requirements.filter(r => r.status === 'traced').length,
|
|
104
|
+
todo: requirements.filter(r => r.status === 'todo').length,
|
|
105
|
+
totalTraces: requirements.reduce((sum, r) => sum + r.traceCount, 0)
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
specFile: path.relative(workspaceRoot, specFilePath),
|
|
109
|
+
title: parsed.metadata.title,
|
|
110
|
+
requirements,
|
|
111
|
+
summary
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// @spec status-command.spec.md/REQ-DATA-1.2 Extract nested requirements from bullet points
|
|
115
|
+
function extractNestedRequirements(content, topLevelRequirements, frontmatterOffset) {
|
|
116
|
+
// Adjust line numbers for top-level requirements to account for frontmatter
|
|
117
|
+
const adjustedTopLevel = topLevelRequirements.map(req => ({
|
|
118
|
+
...req,
|
|
119
|
+
lineRange: req.lineRange ? {
|
|
120
|
+
start: req.lineRange.start + frontmatterOffset,
|
|
121
|
+
end: req.lineRange.end + frontmatterOffset
|
|
122
|
+
} : undefined
|
|
123
|
+
}));
|
|
124
|
+
const allRequirements = [...adjustedTopLevel];
|
|
125
|
+
// @spec status-command.spec.md/REQ-BUS-1.3 Support all @spec comment patterns from spec-parser-utils.ts
|
|
126
|
+
// Regular expression to find nested requirements in bullet points
|
|
127
|
+
// Matches: - **REQ-CAT-N.N**: Description, - **REQ-CAT-N.N.N**: Description, etc.
|
|
128
|
+
const nestedReqRegex = /^\s*-\s*\*\*(?<id>REQ-[A-Z]+-\d+(?:\.\d+)*)\*\*:\s*(?<description>.*)/gm;
|
|
129
|
+
let match;
|
|
130
|
+
while ((match = nestedReqRegex.exec(content)) !== null) {
|
|
131
|
+
const groups = match.groups;
|
|
132
|
+
if (!groups)
|
|
133
|
+
continue;
|
|
134
|
+
const { id, description } = groups;
|
|
135
|
+
// Determine category from requirement ID
|
|
136
|
+
const categoryMatch = id.match(/REQ-([A-Z]+)/);
|
|
137
|
+
if (!categoryMatch)
|
|
138
|
+
continue;
|
|
139
|
+
const categoryStr = categoryMatch[1];
|
|
140
|
+
// @spec status-command.spec.md/REQ-DATA-1.3 Extract requirement categories UX, BUS, DATA, INT, INFRA, QUAL
|
|
141
|
+
const category = Object.values(RequirementCategory).find(cat => cat === categoryStr);
|
|
142
|
+
if (!category)
|
|
143
|
+
continue;
|
|
144
|
+
// Skip if it's already a top-level requirement (parsed by SpecificationParserService)
|
|
145
|
+
if (topLevelRequirements.some(req => req.id === id))
|
|
146
|
+
continue;
|
|
147
|
+
// @spec status-command.spec.md/REQ-DATA-1.2.1 Extract nested requirements from bullet points using regex pattern matching
|
|
148
|
+
// Add nested requirement with line number from match (1-based indexing)
|
|
149
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
150
|
+
allRequirements.push({
|
|
151
|
+
id,
|
|
152
|
+
category,
|
|
153
|
+
title: description.trim(),
|
|
154
|
+
description: description.trim(),
|
|
155
|
+
children: [],
|
|
156
|
+
lineRange: { start: lineNumber, end: lineNumber },
|
|
157
|
+
parent: determineParentRequirement(id)
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return allRequirements;
|
|
161
|
+
}
|
|
162
|
+
// @spec status-command.spec.md/REQ-DATA-1.2 Determine parent requirement for hierarchy
|
|
163
|
+
function determineParentRequirement(requirementId) {
|
|
164
|
+
const parts = requirementId.split('-');
|
|
165
|
+
if (parts.length < 3)
|
|
166
|
+
return undefined;
|
|
167
|
+
const [, category, numberPart] = parts;
|
|
168
|
+
const dotIndex = numberPart.indexOf('.');
|
|
169
|
+
if (dotIndex === -1)
|
|
170
|
+
return undefined; // Top-level requirement
|
|
171
|
+
const parentNumber = numberPart.substring(0, dotIndex);
|
|
172
|
+
return `REQ-${category}-${parentNumber}`;
|
|
173
|
+
}
|
|
174
|
+
// @spec status-command.spec.md/REQ-DATA-1.1 Find specification file in workspace
|
|
175
|
+
async function findSpecificationFile(workspaceRoot, specName) {
|
|
176
|
+
const specsDir = path.join(workspaceRoot, '.mod/specs');
|
|
177
|
+
// @spec status-command.spec.md/REQ-DATA-1.1 Search recursively in .mod/specs
|
|
178
|
+
async function searchDir(dir) {
|
|
179
|
+
try {
|
|
180
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
const fullPath = path.join(dir, entry.name);
|
|
183
|
+
if (entry.isDirectory()) {
|
|
184
|
+
const result = await searchDir(fullPath);
|
|
185
|
+
if (result)
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
else if (entry.name === specName) {
|
|
189
|
+
return fullPath;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Directory doesn't exist or can't be read
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const found = await searchDir(specsDir);
|
|
199
|
+
if (!found) {
|
|
200
|
+
throw new Error(`Specification file "${specName}" not found in ${specsDir}`);
|
|
201
|
+
}
|
|
202
|
+
return found;
|
|
203
|
+
}
|
|
204
|
+
// @spec status-command.spec.md/REQ-DATA-2.3 Filter traces by specification name
|
|
205
|
+
function filterTracesBySpec(traceMap, specName) {
|
|
206
|
+
const filtered = new Map();
|
|
207
|
+
const specBaseName = specName.replace('.spec.md', '');
|
|
208
|
+
for (const [requirementId, traces] of traceMap.entries()) {
|
|
209
|
+
// @spec status-command.spec.md/REQ-DATA-2.2 Support spec-prefixed traces
|
|
210
|
+
const relevantTraces = traces.filter(trace => {
|
|
211
|
+
if (!trace.specPath)
|
|
212
|
+
return true; // Legacy format without spec path
|
|
213
|
+
return trace.specPath.includes(specBaseName);
|
|
214
|
+
});
|
|
215
|
+
if (relevantTraces.length > 0) {
|
|
216
|
+
filtered.set(requirementId, relevantTraces);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return filtered;
|
|
220
|
+
}
|
|
221
|
+
// @spec status-command.spec.md/REQ-BUS-1.1 Status classification logic
|
|
222
|
+
function getRequirementStatus(traceCount) {
|
|
223
|
+
return traceCount === 0 ? 'todo' : 'traced';
|
|
224
|
+
}
|
|
225
|
+
// @spec status-command.spec.md/REQ-DATA-1.2 Calculate hierarchy level from requirement ID
|
|
226
|
+
function calculateHierarchyLevel(requirementId) {
|
|
227
|
+
const parts = requirementId.split('-');
|
|
228
|
+
if (parts.length < 3)
|
|
229
|
+
return 1; // REQ-CAT
|
|
230
|
+
const numberPart = parts[2]; // The part after REQ-CAT-
|
|
231
|
+
const dotCount = (numberPart.match(/\./g) || []).length;
|
|
232
|
+
// @spec status-command.spec.md/REQ-DATA-1.2.2 Calculate hierarchy levels for proper indentation in output display
|
|
233
|
+
return 2 + dotCount; // REQ-CAT-N = 2, REQ-CAT-N.N = 3, etc.
|
|
234
|
+
}
|
|
235
|
+
// @spec status-command.spec.md/REQ-BUS-1.2 Hierarchical aggregation: parent status reflects worst child status
|
|
236
|
+
function applyHierarchicalAggregation(requirements) {
|
|
237
|
+
// Sort by hierarchy level (deepest first) to process children before parents
|
|
238
|
+
const sortedByHierarchy = [...requirements].sort((a, b) => b.hierarchyLevel - a.hierarchyLevel);
|
|
239
|
+
for (const requirement of sortedByHierarchy) {
|
|
240
|
+
// Find all child requirements
|
|
241
|
+
const children = requirements.filter(r => r.requirementId.startsWith(requirement.requirementId + '.') &&
|
|
242
|
+
r.hierarchyLevel === requirement.hierarchyLevel + 1);
|
|
243
|
+
if (children.length > 0) {
|
|
244
|
+
// If parent has no direct traces but all children are traced, mark parent as traced
|
|
245
|
+
if (requirement.traceCount === 0 && children.every(child => child.status === 'traced')) {
|
|
246
|
+
requirement.status = 'traced';
|
|
247
|
+
}
|
|
248
|
+
// If any child is todo, parent remains todo (current behavior is correct)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// @spec status-command.spec.md/REQ-UX-1.1 TypeScript compiler-style output formatting
|
|
253
|
+
function printTscStyleOutput(analysis) {
|
|
254
|
+
// @spec status-command.spec.md/REQ-UX-1.2 Status indicator mapping with colors
|
|
255
|
+
const colors = {
|
|
256
|
+
traced: '\x1b[32m', // Green
|
|
257
|
+
todo: '\x1b[31m', // Red
|
|
258
|
+
reset: '\x1b[0m', // Reset
|
|
259
|
+
dim: '\x1b[90m', // Dim gray
|
|
260
|
+
count: '\x1b[36m' // Cyan for trace counts
|
|
261
|
+
};
|
|
262
|
+
// @spec status-command.spec.md/REQ-UX-1.3 Header with spec name and count
|
|
263
|
+
const totalReqs = analysis.requirements.length;
|
|
264
|
+
console.log(`${analysis.specFile} - ${totalReqs} requirements\n`);
|
|
265
|
+
// @spec status-command.spec.md/REQ-UX-1.1 Print requirements organized by top-level sections
|
|
266
|
+
const requirementsByCategory = groupRequirementsByCategory(analysis.requirements);
|
|
267
|
+
// @spec status-command.spec.md/REQ-BUS-1.2 Hierarchical aggregation: parent status reflects worst child status
|
|
268
|
+
for (const [category, requirements] of requirementsByCategory.entries()) {
|
|
269
|
+
// @spec status-command.spec.md/REQ-UX-1.2.1 Section headers with color-coded category grouping for visual organization
|
|
270
|
+
// Print section header
|
|
271
|
+
console.log(`${colors.dim}## ${category} Requirements${colors.reset}\n`);
|
|
272
|
+
for (const req of requirements) {
|
|
273
|
+
const statusLabel = req.status === 'todo' ? 'todo' : 'traced';
|
|
274
|
+
const color = colors[statusLabel];
|
|
275
|
+
// @spec status-command.spec.md/REQ-UX-1.3 Truncate title to 60 chars
|
|
276
|
+
const truncatedTitle = req.title.length > 60 ?
|
|
277
|
+
req.title.substring(0, 57) + '...' : req.title;
|
|
278
|
+
// @spec status-command.spec.md/REQ-DATA-1.2 Add indentation based on hierarchy level
|
|
279
|
+
const indent = ' '.repeat(Math.max(0, req.hierarchyLevel - 2));
|
|
280
|
+
if (req.traces.length > 0) {
|
|
281
|
+
// @spec status-command.spec.md/REQ-UX-1.1 Show implementation location with trace count
|
|
282
|
+
const trace = req.traces[0]; // Show first trace
|
|
283
|
+
const traceCountDisplay = req.traceCount > 1 ? ` ${colors.count}(${req.traceCount})${colors.reset}` : '';
|
|
284
|
+
console.log(`${indent}${trace.file}:${trace.line}:1 - ${color}${statusLabel}${colors.reset}${traceCountDisplay} ${req.requirementId}: ${truncatedTitle}`);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// @spec status-command.spec.md/REQ-UX-1.1 Show spec location with actual line number
|
|
288
|
+
const lineNumber = req.lineRange?.start || 1;
|
|
289
|
+
console.log(`${indent}${analysis.specFile}:${lineNumber}:1 - ${color}${statusLabel}${colors.reset} ${req.requirementId}: ${truncatedTitle}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
console.log(); // Add spacing between sections
|
|
293
|
+
}
|
|
294
|
+
// @spec status-command.spec.md/REQ-UX-1.1 Color-coded summary line
|
|
295
|
+
// @spec status-command.spec.md/REQ-UX-1.3.1 Color-coded summary line with trace counts and requirement status
|
|
296
|
+
const tracedText = `${colors.traced}${analysis.summary.traced} traced${colors.reset}`;
|
|
297
|
+
const todoText = `${colors.todo}${analysis.summary.todo} todo${colors.reset}`;
|
|
298
|
+
const totalTracesText = `${colors.count}${analysis.summary.totalTraces} traces${colors.reset}`;
|
|
299
|
+
console.log(`Found ${tracedText}, ${todoText} (${totalTracesText} total).`);
|
|
300
|
+
}
|
|
301
|
+
// @spec status-command.spec.md/REQ-DATA-1.2 Group requirements by category to maintain spec order
|
|
302
|
+
function groupRequirementsByCategory(requirements) {
|
|
303
|
+
const categoryOrder = ['UX', 'BUS', 'DATA', 'INT', 'INFRA', 'QUAL'];
|
|
304
|
+
const grouped = new Map();
|
|
305
|
+
// Initialize with empty arrays in correct order
|
|
306
|
+
for (const category of categoryOrder) {
|
|
307
|
+
grouped.set(category, []);
|
|
308
|
+
}
|
|
309
|
+
// Group requirements by category
|
|
310
|
+
for (const req of requirements) {
|
|
311
|
+
const category = req.category;
|
|
312
|
+
if (!grouped.has(category)) {
|
|
313
|
+
grouped.set(category, []);
|
|
314
|
+
}
|
|
315
|
+
grouped.get(category).push(req);
|
|
316
|
+
}
|
|
317
|
+
// Sort requirements within each category by ID
|
|
318
|
+
for (const [, reqs] of grouped.entries()) {
|
|
319
|
+
reqs.sort((a, b) => a.requirementId.localeCompare(b.requirementId));
|
|
320
|
+
}
|
|
321
|
+
// Remove empty categories
|
|
322
|
+
const result = new Map();
|
|
323
|
+
for (const [category, reqs] of grouped.entries()) {
|
|
324
|
+
if (reqs.length > 0) {
|
|
325
|
+
result.set(category, reqs);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { SyncDaemon } from '../services/sync-daemon.js';
|
|
2
|
+
import { FileImportService } from '../services/file-import-service.js';
|
|
3
|
+
export async function syncCommand(args, repo) {
|
|
4
|
+
const command = args[0] || 'start';
|
|
5
|
+
const daemon = new SyncDaemon();
|
|
6
|
+
try {
|
|
7
|
+
switch (command) {
|
|
8
|
+
case 'start':
|
|
9
|
+
await daemon.start({
|
|
10
|
+
force: args.includes('--force'),
|
|
11
|
+
verbose: args.includes('--verbose')
|
|
12
|
+
});
|
|
13
|
+
break;
|
|
14
|
+
case 'stop':
|
|
15
|
+
await daemon.stop({
|
|
16
|
+
force: args.includes('--force')
|
|
17
|
+
});
|
|
18
|
+
break;
|
|
19
|
+
case 'restart':
|
|
20
|
+
await daemon.restart({
|
|
21
|
+
force: args.includes('--force'),
|
|
22
|
+
verbose: args.includes('--verbose')
|
|
23
|
+
});
|
|
24
|
+
break;
|
|
25
|
+
case 'status':
|
|
26
|
+
const status = await daemon.status();
|
|
27
|
+
if (!status) {
|
|
28
|
+
console.log('❌ Daemon is not running');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log('📊 Sync Daemon Status:');
|
|
32
|
+
console.log(` Status: ${status.status}`);
|
|
33
|
+
console.log(` PID: ${status.pid}`);
|
|
34
|
+
console.log(` Started: ${new Date(status.startedAt).toLocaleString()}`);
|
|
35
|
+
console.log(` Last Activity: ${new Date(status.lastActivity).toLocaleString()}`);
|
|
36
|
+
console.log(` Workspace: ${status.workspaceId}${status.workspaceName ? ` (${status.workspaceName})` : ''}`);
|
|
37
|
+
if (status.activeBranchId) {
|
|
38
|
+
console.log(` Active Branch: ${status.activeBranchId}`);
|
|
39
|
+
}
|
|
40
|
+
console.log(` Watched Files: ${status.watchedFiles}`);
|
|
41
|
+
if (status.uptime) {
|
|
42
|
+
const uptimeSeconds = Math.floor(status.uptime / 1000);
|
|
43
|
+
const hours = Math.floor(uptimeSeconds / 3600);
|
|
44
|
+
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
|
45
|
+
const seconds = uptimeSeconds % 60;
|
|
46
|
+
console.log(` Uptime: ${hours}h ${minutes}m ${seconds}s`);
|
|
47
|
+
}
|
|
48
|
+
if (status.crashCount > 0) {
|
|
49
|
+
console.log(` Crashes: ${status.crashCount}`);
|
|
50
|
+
}
|
|
51
|
+
if (status.lastError) {
|
|
52
|
+
console.log(` Last Error: ${status.lastError}`);
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
case 'import':
|
|
56
|
+
const importService = new FileImportService(repo);
|
|
57
|
+
console.log('DEBUG: sync import args:', args);
|
|
58
|
+
const preview = args.includes('--preview');
|
|
59
|
+
console.log('DEBUG: preview flag:', preview);
|
|
60
|
+
const patterns = args.filter(arg => !arg.startsWith('--'));
|
|
61
|
+
if (preview) {
|
|
62
|
+
console.log('DEBUG: Running preview mode only');
|
|
63
|
+
await importService.previewImport({
|
|
64
|
+
patterns: patterns.length > 1 ? patterns.slice(1) : undefined,
|
|
65
|
+
verbose: args.includes('--verbose')
|
|
66
|
+
});
|
|
67
|
+
return; // Exit after preview
|
|
68
|
+
}
|
|
69
|
+
console.log('DEBUG: Running full import');
|
|
70
|
+
await importService.executeImport({
|
|
71
|
+
patterns: patterns.length > 1 ? patterns.slice(1) : undefined,
|
|
72
|
+
verbose: args.includes('--verbose')
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
console.log('Usage: mod sync <command>');
|
|
77
|
+
console.log('Commands:');
|
|
78
|
+
console.log(' start Start the sync daemon in the background');
|
|
79
|
+
console.log(' stop Stop the sync daemon gracefully');
|
|
80
|
+
console.log(' restart Restart the sync daemon');
|
|
81
|
+
console.log(' status Show daemon status and health information');
|
|
82
|
+
console.log(' import Import existing project files to workspace');
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log('Options:');
|
|
85
|
+
console.log(' --force Force start/stop operations');
|
|
86
|
+
console.log(' --verbose Enable verbose output');
|
|
87
|
+
console.log(' --preview Preview files without importing (import only)');
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(`❌ Sync command failed:`, error);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|