roadmap-kit 1.0.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.
Files changed (39) hide show
  1. package/INSTALL.md +358 -0
  2. package/LICENSE +21 -0
  3. package/README.md +503 -0
  4. package/cli.js +548 -0
  5. package/dashboard/dist/assets/index-BzYzLB7u.css +1 -0
  6. package/dashboard/dist/assets/index-DIonhzlK.js +506 -0
  7. package/dashboard/dist/index.html +18 -0
  8. package/dashboard/dist/roadmap.json +268 -0
  9. package/dashboard/index.html +17 -0
  10. package/dashboard/package-lock.json +4172 -0
  11. package/dashboard/package.json +37 -0
  12. package/dashboard/postcss.config.js +6 -0
  13. package/dashboard/public/roadmap.json +268 -0
  14. package/dashboard/server.js +1366 -0
  15. package/dashboard/src/App.jsx +6979 -0
  16. package/dashboard/src/components/CircularProgress.jsx +55 -0
  17. package/dashboard/src/components/ProgressBar.jsx +33 -0
  18. package/dashboard/src/components/ProjectSettings.jsx +420 -0
  19. package/dashboard/src/components/SharedResources.jsx +239 -0
  20. package/dashboard/src/components/TaskList.jsx +273 -0
  21. package/dashboard/src/components/TechnicalDebt.jsx +170 -0
  22. package/dashboard/src/components/ui/accordion.jsx +46 -0
  23. package/dashboard/src/components/ui/badge.jsx +38 -0
  24. package/dashboard/src/components/ui/card.jsx +60 -0
  25. package/dashboard/src/components/ui/progress.jsx +22 -0
  26. package/dashboard/src/components/ui/tabs.jsx +47 -0
  27. package/dashboard/src/index.css +440 -0
  28. package/dashboard/src/lib/utils.js +6 -0
  29. package/dashboard/src/main.jsx +10 -0
  30. package/dashboard/tailwind.config.js +142 -0
  31. package/dashboard/vite.config.js +18 -0
  32. package/docker/Dockerfile +35 -0
  33. package/docker/docker-compose.yml +30 -0
  34. package/docker/entrypoint.sh +31 -0
  35. package/package.json +68 -0
  36. package/scanner.js +351 -0
  37. package/setup.sh +354 -0
  38. package/templates/clinerules.template +130 -0
  39. package/templates/roadmap.template.json +30 -0
@@ -0,0 +1,142 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ darkMode: 'class',
8
+ theme: {
9
+ extend: {
10
+ fontFamily: {
11
+ display: ['Orbitron', 'system-ui', 'sans-serif'],
12
+ sans: ['Space Grotesk', 'system-ui', 'sans-serif'],
13
+ mono: ['IBM Plex Mono', 'monospace'],
14
+ },
15
+ colors: {
16
+ // Pure black base
17
+ void: {
18
+ DEFAULT: '#000000',
19
+ 50: '#0a0a0a',
20
+ 100: '#0d0d0d',
21
+ 200: '#121212',
22
+ 300: '#1a1a1a',
23
+ 400: '#222222',
24
+ },
25
+ // Electric terminal green
26
+ matrix: {
27
+ DEFAULT: '#00ff88',
28
+ dim: '#00cc6a',
29
+ bright: '#33ffaa',
30
+ glow: 'rgba(0, 255, 136, 0.5)',
31
+ },
32
+ // Amber warning/active
33
+ signal: {
34
+ DEFAULT: '#ff9500',
35
+ dim: '#cc7700',
36
+ bright: '#ffaa33',
37
+ glow: 'rgba(255, 149, 0, 0.5)',
38
+ },
39
+ // Cyan interactive
40
+ cyber: {
41
+ DEFAULT: '#00d4ff',
42
+ dim: '#00a8cc',
43
+ bright: '#33ddff',
44
+ glow: 'rgba(0, 212, 255, 0.5)',
45
+ },
46
+ // Rose danger
47
+ alert: {
48
+ DEFAULT: '#ff3366',
49
+ dim: '#cc2952',
50
+ bright: '#ff5580',
51
+ glow: 'rgba(255, 51, 102, 0.5)',
52
+ },
53
+ // Status semantic colors
54
+ status: {
55
+ completed: '#00ff88',
56
+ progress: '#ff9500',
57
+ pending: '#555555'
58
+ },
59
+ severity: {
60
+ high: '#ff3366',
61
+ medium: '#ff9500',
62
+ low: '#00d4ff'
63
+ }
64
+ },
65
+ boxShadow: {
66
+ 'matrix': '0 0 20px rgba(0, 255, 136, 0.3), 0 0 40px rgba(0, 255, 136, 0.1)',
67
+ 'signal': '0 0 20px rgba(255, 149, 0, 0.3), 0 0 40px rgba(255, 149, 0, 0.1)',
68
+ 'cyber': '0 0 20px rgba(0, 212, 255, 0.3), 0 0 40px rgba(0, 212, 255, 0.1)',
69
+ 'alert': '0 0 20px rgba(255, 51, 102, 0.3), 0 0 40px rgba(255, 51, 102, 0.1)',
70
+ 'inner-glow': 'inset 0 0 30px rgba(0, 255, 136, 0.05)',
71
+ },
72
+ keyframes: {
73
+ "scan": {
74
+ "0%": { transform: "translateY(-100%)" },
75
+ "100%": { transform: "translateY(100vh)" },
76
+ },
77
+ "pulse-glow": {
78
+ "0%, 100%": { opacity: "0.5" },
79
+ "50%": { opacity: "1" },
80
+ },
81
+ "flicker": {
82
+ "0%, 100%": { opacity: "1" },
83
+ "50%": { opacity: "0.95" },
84
+ "52%": { opacity: "1" },
85
+ "54%": { opacity: "0.9" },
86
+ "56%": { opacity: "1" },
87
+ },
88
+ "typing": {
89
+ "from": { width: "0" },
90
+ "to": { width: "100%" },
91
+ },
92
+ "blink": {
93
+ "0%, 100%": { opacity: "1" },
94
+ "50%": { opacity: "0" },
95
+ },
96
+ "slide-up": {
97
+ "from": { opacity: "0", transform: "translateY(20px)" },
98
+ "to": { opacity: "1", transform: "translateY(0)" },
99
+ },
100
+ "slide-in-left": {
101
+ "from": { opacity: "0", transform: "translateX(-20px)" },
102
+ "to": { opacity: "1", transform: "translateX(0)" },
103
+ },
104
+ "slide-left": {
105
+ "from": { opacity: "0", transform: "translateX(100%)" },
106
+ "to": { opacity: "1", transform: "translateX(0)" },
107
+ },
108
+ "fade-in": {
109
+ "from": { opacity: "0" },
110
+ "to": { opacity: "1" },
111
+ },
112
+ "progress-fill": {
113
+ "from": { width: "0" },
114
+ "to": { width: "var(--progress-width)" },
115
+ },
116
+ },
117
+ animation: {
118
+ "scan": "scan 8s linear infinite",
119
+ "pulse-glow": "pulse-glow 2s ease-in-out infinite",
120
+ "flicker": "flicker 0.15s ease-in-out",
121
+ "typing": "typing 2s steps(30) forwards",
122
+ "blink": "blink 1s step-end infinite",
123
+ "slide-up": "slide-up 0.4s ease-out forwards",
124
+ "slide-in-left": "slide-in-left 0.3s ease-out forwards",
125
+ "slide-left": "slide-left 0.3s ease-out forwards",
126
+ "fade-in": "fade-in 0.3s ease-out forwards",
127
+ "progress-fill": "progress-fill 1s ease-out forwards",
128
+ },
129
+ backgroundImage: {
130
+ 'grid-pattern': `
131
+ linear-gradient(rgba(0, 255, 136, 0.03) 1px, transparent 1px),
132
+ linear-gradient(90deg, rgba(0, 255, 136, 0.03) 1px, transparent 1px)
133
+ `,
134
+ 'scanline': 'repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 0, 0, 0.1) 2px, rgba(0, 0, 0, 0.1) 4px)',
135
+ },
136
+ backgroundSize: {
137
+ 'grid': '40px 40px',
138
+ },
139
+ },
140
+ },
141
+ plugins: [],
142
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vitejs.dev/config/
5
+ //
6
+ // Environment variables:
7
+ // PORT - Server port (default: 6969)
8
+ // BASE_PATH - Base path for subpath deployment (default: '/')
9
+ // Example: BASE_PATH=/roadmap npm run build
10
+ //
11
+ export default defineConfig({
12
+ plugins: [react()],
13
+ base: process.env.BASE_PATH || '/',
14
+ server: {
15
+ port: parseInt(process.env.PORT) || 6969,
16
+ open: true
17
+ }
18
+ })
@@ -0,0 +1,35 @@
1
+ # ROADMAP-KIT Dockerfile
2
+ # Lightweight Node.js image for running the dashboard
3
+
4
+ FROM node:20-alpine
5
+
6
+ # Install git (required for scanner)
7
+ RUN apk add --no-cache git
8
+
9
+ # Set working directory
10
+ WORKDIR /app
11
+
12
+ # Copy roadmap-kit files
13
+ COPY package.json ./
14
+ COPY scanner.js ./
15
+ COPY cli.js ./
16
+ COPY dashboard ./dashboard
17
+
18
+ # Install dependencies
19
+ RUN npm install
20
+
21
+ # Install dashboard dependencies
22
+ WORKDIR /app/dashboard
23
+ RUN npm install
24
+
25
+ # Back to root
26
+ WORKDIR /app
27
+
28
+ # Expose port
29
+ EXPOSE 3001
30
+
31
+ # Set entrypoint
32
+ COPY docker/entrypoint.sh /entrypoint.sh
33
+ RUN chmod +x /entrypoint.sh
34
+
35
+ ENTRYPOINT ["/entrypoint.sh"]
@@ -0,0 +1,30 @@
1
+ # ROADMAP-KIT Docker Compose Configuration
2
+ # Add this to your existing docker-compose.yml or use standalone
3
+
4
+ version: '3.8'
5
+
6
+ services:
7
+ roadmap-dashboard:
8
+ build:
9
+ context: ../
10
+ dockerfile: docker/Dockerfile
11
+ container_name: roadmap-dashboard
12
+ ports:
13
+ - "${ROADMAP_PORT:-6969}:6969"
14
+ environment:
15
+ - PORT=6969
16
+ volumes:
17
+ # Mount .git directory (read-only) for scanner
18
+ - ../.git:/app/.git:ro
19
+ # Mount roadmap.json for sync
20
+ - ../roadmap.json:/app/roadmap.json
21
+ # Mount project root for file access
22
+ - ..:/project:ro
23
+ restart: unless-stopped
24
+ - NODE_ENV=development
25
+ networks:
26
+ - roadmap-network
27
+
28
+ networks:
29
+ roadmap-network:
30
+ driver: bridge
@@ -0,0 +1,31 @@
1
+ #!/bin/sh
2
+
3
+ # ROADMAP-KIT Entrypoint
4
+ # Runs scanner and starts Vite dashboard
5
+
6
+ set -e
7
+
8
+ echo "šŸ—ŗļø ROADMAP-KIT Docker Container"
9
+ echo "================================="
10
+
11
+ # Check if roadmap.json exists
12
+ if [ ! -f /app/roadmap.json ]; then
13
+ echo "āš ļø roadmap.json not found"
14
+ echo "Creating from template..."
15
+ cp /app/templates/roadmap.template.json /app/roadmap.json
16
+ fi
17
+
18
+ # Run scanner to update roadmap
19
+ echo "šŸ”„ Scanning Git history..."
20
+ cd /app
21
+ node scanner.js || echo "āš ļø Scanner failed (might not be a git repo)"
22
+
23
+ # Copy roadmap.json to dashboard public folder
24
+ echo "šŸ“‹ Copying roadmap to dashboard..."
25
+ mkdir -p /app/dashboard/public
26
+ cp /app/roadmap.json /app/dashboard/public/roadmap.json
27
+
28
+ # Start Vite dev server
29
+ echo "šŸš€ Starting dashboard on http://localhost:3001"
30
+ cd /app/dashboard
31
+ exec npm run dev -- --host 0.0.0.0
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "roadmap-kit",
3
+ "version": "1.0.0",
4
+ "description": "AI-powered project management for Vibe Coding. Track features, tasks, and technical debt with a visual dashboard.",
5
+ "main": "cli.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "roadmap-kit": "./cli.js"
9
+ },
10
+ "files": [
11
+ "cli.js",
12
+ "scanner.js",
13
+ "setup.sh",
14
+ "dashboard/",
15
+ "templates/",
16
+ "docker/",
17
+ "!dashboard/node_modules",
18
+ "README.md",
19
+ "INSTALL.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "scan": "node scanner.js",
24
+ "dev": "cd dashboard && npm run dev",
25
+ "dev:scan": "node scanner.js && cd dashboard && npm run dev",
26
+ "build": "cd dashboard && npm run build",
27
+ "preview": "cd dashboard && npm run preview",
28
+ "postinstall": "echo '\\nšŸ—ŗļø ROADMAP-KIT installed! Run: npx roadmap-kit init\\n'"
29
+ },
30
+ "keywords": [
31
+ "ai",
32
+ "vibe-coding",
33
+ "roadmap",
34
+ "project-management",
35
+ "claude",
36
+ "cursor",
37
+ "cline",
38
+ "windsurf",
39
+ "copilot",
40
+ "ai-coding",
41
+ "developer-tools",
42
+ "task-tracking",
43
+ "technical-debt"
44
+ ],
45
+ "author": {
46
+ "name": "HACKLET",
47
+ "url": "https://hacklet.es"
48
+ },
49
+ "license": "MIT",
50
+ "homepage": "https://roadmap.ink",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/hacklet1101/roadmap-kit.git"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/hacklet1101/roadmap-kit/issues"
57
+ },
58
+ "dependencies": {
59
+ "simple-git": "^3.22.0",
60
+ "chalk": "^5.3.0",
61
+ "commander": "^12.0.0",
62
+ "ora": "^8.0.1"
63
+ },
64
+ "devDependencies": {},
65
+ "engines": {
66
+ "node": ">=18.0.0"
67
+ }
68
+ }
package/scanner.js ADDED
@@ -0,0 +1,351 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ROADMAP-KIT SCANNER
5
+ * Reads Git commits and updates roadmap.json automatically
6
+ * Parses tags: [task:id], [status:value], [debt:description]
7
+ */
8
+
9
+ import { simpleGit } from 'simple-git';
10
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
11
+ import { join, dirname } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ import chalk from 'chalk';
14
+ import ora from 'ora';
15
+
16
+ // Get current directory
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+
20
+ // Configuration
21
+ const CONFIG = {
22
+ maxCommits: 50,
23
+ roadmapPath: null, // Will be set based on execution context
24
+ };
25
+
26
+ /**
27
+ * Parse commit message for tags
28
+ * Tags format: [task:id] [status:value] [debt:description]
29
+ */
30
+ function parseCommitTags(message) {
31
+ const tags = {
32
+ taskId: null,
33
+ status: null,
34
+ debts: []
35
+ };
36
+
37
+ // Extract [task:id]
38
+ const taskMatch = message.match(/\[task:([^\]]+)\]/);
39
+ if (taskMatch) {
40
+ tags.taskId = taskMatch[1];
41
+ }
42
+
43
+ // Extract [status:value]
44
+ const statusMatch = message.match(/\[status:([^\]]+)\]/);
45
+ if (statusMatch) {
46
+ const status = statusMatch[1];
47
+ if (['pending', 'in_progress', 'completed'].includes(status)) {
48
+ tags.status = status;
49
+ }
50
+ }
51
+
52
+ // Extract all [debt:description]
53
+ const debtMatches = message.matchAll(/\[debt:([^\]]+)\]/g);
54
+ for (const match of debtMatches) {
55
+ tags.debts.push(match[1]);
56
+ }
57
+
58
+ return tags;
59
+ }
60
+
61
+ /**
62
+ * Get file statistics from commit
63
+ */
64
+ async function getCommitStats(git, commitHash) {
65
+ try {
66
+ const diffSummary = await git.diffSummary([`${commitHash}^`, commitHash]);
67
+ return {
68
+ lines_added: diffSummary.insertions || 0,
69
+ lines_removed: diffSummary.deletions || 0,
70
+ files_created: diffSummary.files.filter(f => f.binary === false && f.deletions === 0).length,
71
+ files_modified: diffSummary.files.filter(f => f.binary === false && f.deletions > 0).length,
72
+ files: diffSummary.files.map(f => f.file)
73
+ };
74
+ } catch (error) {
75
+ // If it's the first commit, there's no parent to diff against
76
+ return {
77
+ lines_added: 0,
78
+ lines_removed: 0,
79
+ files_created: 0,
80
+ files_modified: 0,
81
+ files: []
82
+ };
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Calculate complexity score based on lines changed
88
+ */
89
+ function calculateComplexity(linesAdded, linesRemoved) {
90
+ const totalLines = linesAdded + linesRemoved;
91
+ if (totalLines < 50) return 1;
92
+ if (totalLines < 100) return 2;
93
+ if (totalLines < 200) return 3;
94
+ if (totalLines < 500) return 5;
95
+ if (totalLines < 1000) return 7;
96
+ return 10;
97
+ }
98
+
99
+ /**
100
+ * Find task in roadmap by ID
101
+ */
102
+ function findTask(roadmap, taskId) {
103
+ for (const feature of roadmap.features) {
104
+ for (const task of feature.tasks) {
105
+ if (task.id === taskId) {
106
+ return { feature, task };
107
+ }
108
+ }
109
+ }
110
+ return null;
111
+ }
112
+
113
+ /**
114
+ * Update task with commit information
115
+ */
116
+ function updateTask(task, commit, tags, stats) {
117
+ // Update status
118
+ if (tags.status) {
119
+ task.status = tags.status;
120
+
121
+ // Set timestamps
122
+ if (tags.status === 'in_progress' && !task.started_at) {
123
+ task.started_at = commit.date;
124
+ }
125
+ if (tags.status === 'completed' && !task.completed_at) {
126
+ task.completed_at = commit.date;
127
+ }
128
+ }
129
+
130
+ // Update git information
131
+ if (!task.git) {
132
+ task.git = {
133
+ branch: null,
134
+ pr_number: null,
135
+ pr_url: null,
136
+ last_commit: null,
137
+ commits: []
138
+ };
139
+ }
140
+ task.git.last_commit = commit.hash;
141
+ if (!task.git.commits.includes(commit.hash)) {
142
+ task.git.commits.push(commit.hash);
143
+ }
144
+
145
+ // Update affected files
146
+ if (stats.files && stats.files.length > 0) {
147
+ task.affected_files = task.affected_files || [];
148
+ stats.files.forEach(file => {
149
+ if (!task.affected_files.includes(file)) {
150
+ task.affected_files.push(file);
151
+ }
152
+ });
153
+ }
154
+
155
+ // Update metrics
156
+ if (!task.metrics) {
157
+ task.metrics = {
158
+ lines_added: 0,
159
+ lines_removed: 0,
160
+ files_created: 0,
161
+ files_modified: 0,
162
+ complexity_score: 0
163
+ };
164
+ }
165
+ task.metrics.lines_added += stats.lines_added;
166
+ task.metrics.lines_removed += stats.lines_removed;
167
+ task.metrics.files_created += stats.files_created;
168
+ task.metrics.files_modified += stats.files_modified;
169
+ task.metrics.complexity_score = calculateComplexity(
170
+ task.metrics.lines_added,
171
+ task.metrics.lines_removed
172
+ );
173
+
174
+ // Add technical debt
175
+ if (tags.debts.length > 0) {
176
+ task.technical_debt = task.technical_debt || [];
177
+ tags.debts.forEach(debtDesc => {
178
+ task.technical_debt.push({
179
+ description: debtDesc,
180
+ severity: 'medium', // Default severity
181
+ estimated_effort: 'TBD'
182
+ });
183
+ });
184
+ }
185
+
186
+ return task;
187
+ }
188
+
189
+ /**
190
+ * Calculate feature progress
191
+ */
192
+ function calculateFeatureProgress(feature) {
193
+ if (!feature.tasks || feature.tasks.length === 0) return 0;
194
+
195
+ const completedTasks = feature.tasks.filter(t => t.status === 'completed').length;
196
+ return Math.round((completedTasks / feature.tasks.length) * 100);
197
+ }
198
+
199
+ /**
200
+ * Calculate total project progress
201
+ */
202
+ function calculateTotalProgress(roadmap) {
203
+ if (!roadmap.features || roadmap.features.length === 0) return 0;
204
+
205
+ const totalProgress = roadmap.features.reduce((sum, feature) => {
206
+ return sum + (feature.progress || 0);
207
+ }, 0);
208
+
209
+ return Math.round(totalProgress / roadmap.features.length);
210
+ }
211
+
212
+ /**
213
+ * Load roadmap.json
214
+ */
215
+ function loadRoadmap(roadmapPath) {
216
+ if (!existsSync(roadmapPath)) {
217
+ console.error(chalk.red('āœ— roadmap.json not found'));
218
+ console.log(chalk.yellow(' Run "roadmap-kit init" to create one'));
219
+ process.exit(1);
220
+ }
221
+
222
+ try {
223
+ const content = readFileSync(roadmapPath, 'utf-8');
224
+ return JSON.parse(content);
225
+ } catch (error) {
226
+ console.error(chalk.red('āœ— Error reading roadmap.json:'), error.message);
227
+ process.exit(1);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Save roadmap.json
233
+ */
234
+ function saveRoadmap(roadmapPath, roadmap) {
235
+ try {
236
+ writeFileSync(roadmapPath, JSON.stringify(roadmap, null, 2), 'utf-8');
237
+ } catch (error) {
238
+ console.error(chalk.red('āœ— Error saving roadmap.json:'), error.message);
239
+ process.exit(1);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Main scanner function
245
+ */
246
+ export async function scanGitHistory(projectRoot = process.cwd()) {
247
+ const roadmapPath = join(projectRoot, 'roadmap.json');
248
+ const spinner = ora('Scanning Git history...').start();
249
+
250
+ try {
251
+ // Initialize git
252
+ const git = simpleGit(projectRoot);
253
+
254
+ // Check if it's a git repository
255
+ const isRepo = await git.checkIsRepo();
256
+ if (!isRepo) {
257
+ spinner.fail('Not a Git repository');
258
+ console.log(chalk.yellow(' Initialize Git first: git init'));
259
+ process.exit(1);
260
+ }
261
+
262
+ // Load roadmap
263
+ const roadmap = loadRoadmap(roadmapPath);
264
+
265
+ // Get last sync date
266
+ const lastSync = roadmap.project_info.last_sync
267
+ ? new Date(roadmap.project_info.last_sync)
268
+ : null;
269
+
270
+ // Get commits
271
+ const log = await git.log({ maxCount: CONFIG.maxCommits });
272
+
273
+ let updatedTasks = 0;
274
+ let newDebts = 0;
275
+ let processedCommits = 0;
276
+
277
+ // Process commits from newest to oldest
278
+ for (const commit of log.all) {
279
+ const commitDate = new Date(commit.date);
280
+
281
+ // Skip commits before last sync (if any)
282
+ if (lastSync && commitDate <= lastSync) {
283
+ continue;
284
+ }
285
+
286
+ processedCommits++;
287
+ const tags = parseCommitTags(commit.message);
288
+
289
+ // If no task tag, skip
290
+ if (!tags.taskId) {
291
+ continue;
292
+ }
293
+
294
+ // Find task in roadmap
295
+ const result = findTask(roadmap, tags.taskId);
296
+ if (!result) {
297
+ console.log(chalk.yellow(`\n ⚠ Task "${tags.taskId}" not found in roadmap`));
298
+ continue;
299
+ }
300
+
301
+ // Get commit stats
302
+ const stats = await getCommitStats(git, commit.hash);
303
+
304
+ // Update task
305
+ updateTask(result.task, commit, tags, stats);
306
+ updatedTasks++;
307
+
308
+ if (tags.debts.length > 0) {
309
+ newDebts += tags.debts.length;
310
+ }
311
+
312
+ spinner.text = `Processing commits... (${processedCommits} commits, ${updatedTasks} tasks updated)`;
313
+ }
314
+
315
+ // Recalculate progress
316
+ roadmap.features.forEach(feature => {
317
+ feature.progress = calculateFeatureProgress(feature);
318
+ });
319
+ roadmap.project_info.total_progress = calculateTotalProgress(roadmap);
320
+ roadmap.project_info.last_sync = new Date().toISOString();
321
+
322
+ // Save updated roadmap
323
+ saveRoadmap(roadmapPath, roadmap);
324
+
325
+ spinner.succeed('Roadmap updated successfully');
326
+
327
+ // Print summary
328
+ console.log(chalk.cyan('\nšŸ“Š Summary:'));
329
+ console.log(chalk.white(` • Processed commits: ${processedCommits}`));
330
+ console.log(chalk.white(` • Updated tasks: ${updatedTasks}`));
331
+ console.log(chalk.white(` • New technical debts: ${newDebts}`));
332
+ console.log(chalk.green(` • Total progress: ${roadmap.project_info.total_progress}%`));
333
+
334
+ if (updatedTasks === 0 && processedCommits > 0) {
335
+ console.log(chalk.yellow('\nšŸ’” Tip: Use commit tags like [task:id] [status:completed] to track tasks'));
336
+ }
337
+
338
+ } catch (error) {
339
+ spinner.fail('Error scanning Git history');
340
+ console.error(chalk.red(error.message));
341
+ if (error.stack) {
342
+ console.error(chalk.gray(error.stack));
343
+ }
344
+ process.exit(1);
345
+ }
346
+ }
347
+
348
+ // Run scanner if executed directly
349
+ if (import.meta.url === `file://${process.argv[1]}`) {
350
+ scanGitHistory();
351
+ }