claude-cli-advanced-starter-pack 1.1.0 ā 1.8.1
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/OVERVIEW.md +5 -1
- package/README.md +241 -132
- package/bin/gtask.js +53 -0
- package/package.json +1 -1
- package/src/cli/menu.js +27 -0
- package/src/commands/explore-mcp/mcp-registry.js +99 -0
- package/src/commands/init.js +339 -351
- package/src/commands/install-panel-hook.js +108 -0
- package/src/commands/install-scripts.js +232 -0
- package/src/commands/install-skill.js +220 -0
- package/src/commands/panel.js +297 -0
- package/src/commands/setup-wizard.js +4 -3
- package/src/commands/test-setup.js +4 -5
- package/src/data/releases.json +164 -0
- package/src/panel/queue.js +188 -0
- package/templates/commands/ask-claude.template.md +118 -0
- package/templates/commands/ccasp-panel.template.md +72 -0
- package/templates/commands/ccasp-setup.template.md +470 -79
- package/templates/commands/create-smoke-test.template.md +186 -0
- package/templates/commands/project-impl.template.md +9 -113
- package/templates/commands/refactor-check.template.md +112 -0
- package/templates/commands/refactor-cleanup.template.md +144 -0
- package/templates/commands/refactor-prep.template.md +192 -0
- package/templates/docs/AI_ARCHITECTURE_CONSTITUTION.template.md +198 -0
- package/templates/docs/DETAILED_GOTCHAS.template.md +347 -0
- package/templates/docs/PHASE-DEV-CHECKLIST.template.md +241 -0
- package/templates/docs/PROGRESS_JSON_TEMPLATE.json +117 -0
- package/templates/docs/background-agent.template.md +264 -0
- package/templates/hooks/autonomous-decision-logger.template.js +207 -0
- package/templates/hooks/branch-merge-checker.template.js +272 -0
- package/templates/hooks/git-commit-tracker.template.js +267 -0
- package/templates/hooks/issue-completion-detector.template.js +205 -0
- package/templates/hooks/panel-queue-reader.template.js +83 -0
- package/templates/hooks/phase-validation-gates.template.js +307 -0
- package/templates/hooks/session-id-generator.template.js +236 -0
- package/templates/hooks/token-usage-monitor.template.js +193 -0
- package/templates/patterns/README.md +129 -0
- package/templates/patterns/l1-l2-orchestration.md +189 -0
- package/templates/patterns/multi-phase-orchestration.md +258 -0
- package/templates/patterns/two-tier-query-pipeline.md +192 -0
- package/templates/scripts/README.md +109 -0
- package/templates/scripts/analyze-delegation-log.js +299 -0
- package/templates/scripts/autonomous-decision-logger.js +277 -0
- package/templates/scripts/git-history-analyzer.py +269 -0
- package/templates/scripts/phase-validation-gates.js +307 -0
- package/templates/scripts/poll-deployment-status.js +260 -0
- package/templates/scripts/roadmap-scanner.js +263 -0
- package/templates/scripts/validate-deployment.js +293 -0
- package/templates/skills/agent-creator/skill.json +18 -0
- package/templates/skills/agent-creator/skill.md +335 -0
- package/templates/skills/hook-creator/skill.json +18 -0
- package/templates/skills/hook-creator/skill.md +318 -0
- package/templates/skills/panel/skill.json +18 -0
- package/templates/skills/panel/skill.md +90 -0
- package/templates/skills/rag-agent-creator/skill.json +18 -0
- package/templates/skills/rag-agent-creator/skill.md +307 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Poll Deployment Status
|
|
4
|
+
*
|
|
5
|
+
* Polls deployment status until complete or timeout.
|
|
6
|
+
* Supports Railway, Cloudflare, Vercel, and custom endpoints.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node poll-deployment-status.js --platform railway --deployment-id abc123
|
|
10
|
+
* node poll-deployment-status.js --platform cloudflare --project-name my-project
|
|
11
|
+
* node poll-deployment-status.js --url https://api.example.com/status
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const POLL_INTERVAL = 5000; // 5 seconds
|
|
15
|
+
const DEFAULT_TIMEOUT = 300000; // 5 minutes
|
|
16
|
+
|
|
17
|
+
class DeploymentPoller {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.platform = options.platform;
|
|
20
|
+
this.deploymentId = options.deploymentId;
|
|
21
|
+
this.projectName = options.projectName;
|
|
22
|
+
this.projectId = options.projectId;
|
|
23
|
+
this.url = options.url;
|
|
24
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
25
|
+
this.verbose = options.verbose || false;
|
|
26
|
+
this.startTime = Date.now();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async poll() {
|
|
30
|
+
console.log(`\nš Polling ${this.platform || 'custom'} deployment...`);
|
|
31
|
+
console.log(` Timeout: ${this.timeout / 1000}s\n`);
|
|
32
|
+
|
|
33
|
+
let lastStatus = null;
|
|
34
|
+
|
|
35
|
+
while (Date.now() - this.startTime < this.timeout) {
|
|
36
|
+
try {
|
|
37
|
+
const status = await this.checkStatus();
|
|
38
|
+
|
|
39
|
+
if (status.status !== lastStatus) {
|
|
40
|
+
this.logStatus(status);
|
|
41
|
+
lastStatus = status.status;
|
|
42
|
+
} else if (this.verbose) {
|
|
43
|
+
process.stdout.write('.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (status.completed) {
|
|
47
|
+
return this.handleCompletion(status);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await this.sleep(POLL_INTERVAL);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`\nā Error polling: ${error.message}`);
|
|
53
|
+
if (this.verbose) console.error(error);
|
|
54
|
+
await this.sleep(POLL_INTERVAL);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.error(`\nā° Timeout after ${this.timeout / 1000}s`);
|
|
59
|
+
return { success: false, reason: 'timeout' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async checkStatus() {
|
|
63
|
+
switch (this.platform) {
|
|
64
|
+
case 'railway':
|
|
65
|
+
return this.checkRailway();
|
|
66
|
+
case 'cloudflare':
|
|
67
|
+
return this.checkCloudflare();
|
|
68
|
+
case 'vercel':
|
|
69
|
+
return this.checkVercel();
|
|
70
|
+
case 'custom':
|
|
71
|
+
return this.checkCustom();
|
|
72
|
+
default:
|
|
73
|
+
throw new Error(`Unknown platform: ${this.platform}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async checkRailway() {
|
|
78
|
+
const token = process.env.RAILWAY_API_TOKEN;
|
|
79
|
+
if (!token) throw new Error('RAILWAY_API_TOKEN not set');
|
|
80
|
+
|
|
81
|
+
const query = `
|
|
82
|
+
query GetDeployment($id: String!) {
|
|
83
|
+
deployment(id: $id) {
|
|
84
|
+
id
|
|
85
|
+
status
|
|
86
|
+
createdAt
|
|
87
|
+
staticUrl
|
|
88
|
+
meta
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
const response = await fetch('https://backboard.railway.app/graphql/v2', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Authorization': `Bearer ${token}`,
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
query,
|
|
101
|
+
variables: { id: this.deploymentId },
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
if (data.errors) throw new Error(data.errors[0].message);
|
|
107
|
+
|
|
108
|
+
const deployment = data.data.deployment;
|
|
109
|
+
const status = deployment.status;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
status,
|
|
113
|
+
completed: ['SUCCESS', 'FAILED', 'CRASHED', 'REMOVED'].includes(status),
|
|
114
|
+
success: status === 'SUCCESS',
|
|
115
|
+
url: deployment.staticUrl,
|
|
116
|
+
details: deployment,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async checkCloudflare() {
|
|
121
|
+
const token = process.env.CLOUDFLARE_API_TOKEN;
|
|
122
|
+
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
123
|
+
if (!token) throw new Error('CLOUDFLARE_API_TOKEN not set');
|
|
124
|
+
|
|
125
|
+
// Get latest deployment
|
|
126
|
+
const response = await fetch(
|
|
127
|
+
`https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${this.projectName}/deployments`,
|
|
128
|
+
{
|
|
129
|
+
headers: {
|
|
130
|
+
'Authorization': `Bearer ${token}`,
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const data = await response.json();
|
|
137
|
+
if (!data.success) throw new Error(data.errors[0].message);
|
|
138
|
+
|
|
139
|
+
const deployment = data.result[0]; // Latest deployment
|
|
140
|
+
const status = deployment.latest_stage?.status || 'unknown';
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
status,
|
|
144
|
+
completed: ['success', 'failure', 'canceled'].includes(status),
|
|
145
|
+
success: status === 'success',
|
|
146
|
+
url: deployment.url,
|
|
147
|
+
details: deployment,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async checkVercel() {
|
|
152
|
+
const token = process.env.VERCEL_TOKEN;
|
|
153
|
+
if (!token) throw new Error('VERCEL_TOKEN not set');
|
|
154
|
+
|
|
155
|
+
const response = await fetch(
|
|
156
|
+
`https://api.vercel.com/v13/deployments/${this.deploymentId}`,
|
|
157
|
+
{
|
|
158
|
+
headers: {
|
|
159
|
+
'Authorization': `Bearer ${token}`,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const deployment = await response.json();
|
|
165
|
+
const status = deployment.readyState;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
status,
|
|
169
|
+
completed: ['READY', 'ERROR', 'CANCELED'].includes(status),
|
|
170
|
+
success: status === 'READY',
|
|
171
|
+
url: deployment.url,
|
|
172
|
+
details: deployment,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async checkCustom() {
|
|
177
|
+
const response = await fetch(this.url);
|
|
178
|
+
const data = await response.json();
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
status: data.status || 'unknown',
|
|
182
|
+
completed: data.completed || false,
|
|
183
|
+
success: data.success || data.status === 'success',
|
|
184
|
+
url: data.url,
|
|
185
|
+
details: data,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
logStatus(status) {
|
|
190
|
+
const elapsed = Math.round((Date.now() - this.startTime) / 1000);
|
|
191
|
+
const icon = status.success ? 'ā
' :
|
|
192
|
+
status.completed ? 'ā' : 'š';
|
|
193
|
+
console.log(`${icon} [${elapsed}s] Status: ${status.status}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
handleCompletion(status) {
|
|
197
|
+
console.log('\n' + '='.repeat(50));
|
|
198
|
+
if (status.success) {
|
|
199
|
+
console.log('ā
Deployment completed successfully!');
|
|
200
|
+
if (status.url) {
|
|
201
|
+
console.log(`š URL: ${status.url}`);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
console.log('ā Deployment failed');
|
|
205
|
+
if (this.verbose && status.details) {
|
|
206
|
+
console.log('Details:', JSON.stringify(status.details, null, 2));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
console.log('='.repeat(50) + '\n');
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
success: status.success,
|
|
213
|
+
status: status.status,
|
|
214
|
+
url: status.url,
|
|
215
|
+
duration: Date.now() - this.startTime,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
sleep(ms) {
|
|
220
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// CLI entry point
|
|
225
|
+
async function main() {
|
|
226
|
+
const args = process.argv.slice(2);
|
|
227
|
+
|
|
228
|
+
const getArg = (name) => {
|
|
229
|
+
const index = args.indexOf(`--${name}`);
|
|
230
|
+
return index >= 0 ? args[index + 1] : null;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const hasFlag = (name) => args.includes(`--${name}`);
|
|
234
|
+
|
|
235
|
+
const options = {
|
|
236
|
+
platform: getArg('platform') || 'railway',
|
|
237
|
+
deploymentId: getArg('deployment-id'),
|
|
238
|
+
projectName: getArg('project-name'),
|
|
239
|
+
projectId: getArg('project-id'),
|
|
240
|
+
url: getArg('url'),
|
|
241
|
+
timeout: parseInt(getArg('timeout') || '300') * 1000,
|
|
242
|
+
verbose: hasFlag('verbose') || hasFlag('v'),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (options.platform !== 'custom' && !options.deploymentId && !options.projectName) {
|
|
246
|
+
console.error('Usage: node poll-deployment-status.js --platform <railway|cloudflare|vercel|custom>');
|
|
247
|
+
console.error(' --deployment-id <id> Deployment ID (Railway, Vercel)');
|
|
248
|
+
console.error(' --project-name <name> Project name (Cloudflare)');
|
|
249
|
+
console.error(' --timeout <seconds> Timeout in seconds (default: 300)');
|
|
250
|
+
console.error(' --verbose Show detailed output');
|
|
251
|
+
console.error(' --url <url> Custom status endpoint');
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const poller = new DeploymentPoller(options);
|
|
256
|
+
const result = await poller.poll();
|
|
257
|
+
process.exit(result.success ? 0 : 1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Roadmap Scanner
|
|
4
|
+
*
|
|
5
|
+
* Scans for roadmap files and generates a progress dashboard.
|
|
6
|
+
* Detects phase status, completion percentages, and blockers.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node roadmap-scanner.js # Scan current directory
|
|
10
|
+
* node roadmap-scanner.js --path ./docs # Scan specific directory
|
|
11
|
+
* node roadmap-scanner.js --output json # JSON output
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readdirSync, readFileSync, statSync } from 'fs';
|
|
15
|
+
import { join, basename } from 'path';
|
|
16
|
+
|
|
17
|
+
const ROADMAP_PATTERNS = [
|
|
18
|
+
/roadmap/i,
|
|
19
|
+
/phase[-_]?dev/i,
|
|
20
|
+
/implementation[-_]?plan/i,
|
|
21
|
+
/project[-_]?plan/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const STATUS_PATTERNS = {
|
|
25
|
+
complete: [/ā
/, /\[x\]/i, /complete/i, /done/i, /finished/i],
|
|
26
|
+
inProgress: [/šØ/, /\[ \]/, /in.?progress/i, /wip/i, /started/i],
|
|
27
|
+
blocked: [/ā/, /blocked/i, /stuck/i, /waiting/i],
|
|
28
|
+
notStarted: [/ā¬/, /not.?started/i, /pending/i, /todo/i],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
class RoadmapScanner {
|
|
32
|
+
constructor(options = {}) {
|
|
33
|
+
this.path = options.path || process.cwd();
|
|
34
|
+
this.output = options.output || 'text';
|
|
35
|
+
this.recursive = options.recursive !== false;
|
|
36
|
+
this.roadmaps = [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async scan() {
|
|
40
|
+
console.log(`\nš Scanning for roadmaps in: ${this.path}\n`);
|
|
41
|
+
|
|
42
|
+
await this.findRoadmaps(this.path);
|
|
43
|
+
|
|
44
|
+
if (this.roadmaps.length === 0) {
|
|
45
|
+
console.log('No roadmap files found.');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`Found ${this.roadmaps.length} roadmap(s)\n`);
|
|
50
|
+
|
|
51
|
+
for (const roadmap of this.roadmaps) {
|
|
52
|
+
await this.analyzeRoadmap(roadmap);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (this.output === 'json') {
|
|
56
|
+
this.outputJson();
|
|
57
|
+
} else {
|
|
58
|
+
this.outputText();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async findRoadmaps(dir) {
|
|
63
|
+
try {
|
|
64
|
+
const entries = readdirSync(dir);
|
|
65
|
+
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const fullPath = join(dir, entry);
|
|
68
|
+
const stat = statSync(fullPath);
|
|
69
|
+
|
|
70
|
+
if (stat.isDirectory() && this.recursive) {
|
|
71
|
+
if (!entry.startsWith('.') && entry !== 'node_modules') {
|
|
72
|
+
await this.findRoadmaps(fullPath);
|
|
73
|
+
}
|
|
74
|
+
} else if (stat.isFile() && entry.endsWith('.md')) {
|
|
75
|
+
if (ROADMAP_PATTERNS.some(pattern => pattern.test(entry))) {
|
|
76
|
+
this.roadmaps.push({
|
|
77
|
+
path: fullPath,
|
|
78
|
+
name: basename(entry, '.md'),
|
|
79
|
+
phases: [],
|
|
80
|
+
metrics: {},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Ignore access errors
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async analyzeRoadmap(roadmap) {
|
|
91
|
+
const content = readFileSync(roadmap.path, 'utf8');
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
|
|
94
|
+
let currentPhase = null;
|
|
95
|
+
let totalTasks = 0;
|
|
96
|
+
let completedTasks = 0;
|
|
97
|
+
let blockedTasks = 0;
|
|
98
|
+
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
// Detect phase headers
|
|
101
|
+
const phaseMatch = line.match(/^#+\s*phase\s*(\d+)/i);
|
|
102
|
+
if (phaseMatch) {
|
|
103
|
+
if (currentPhase) {
|
|
104
|
+
roadmap.phases.push(currentPhase);
|
|
105
|
+
}
|
|
106
|
+
currentPhase = {
|
|
107
|
+
number: parseInt(phaseMatch[1]),
|
|
108
|
+
name: line.replace(/^#+\s*/, '').trim(),
|
|
109
|
+
status: 'not_started',
|
|
110
|
+
tasks: [],
|
|
111
|
+
notes: [],
|
|
112
|
+
};
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Detect status markers in phase headers or status tables
|
|
117
|
+
if (currentPhase) {
|
|
118
|
+
// Check for task markers
|
|
119
|
+
const taskMatch = line.match(/^\s*[-*]\s*\[([ x])\]/i);
|
|
120
|
+
if (taskMatch) {
|
|
121
|
+
totalTasks++;
|
|
122
|
+
const isComplete = taskMatch[1].toLowerCase() === 'x';
|
|
123
|
+
if (isComplete) completedTasks++;
|
|
124
|
+
currentPhase.tasks.push({
|
|
125
|
+
text: line.replace(/^\s*[-*]\s*\[[ x]\]\s*/i, ''),
|
|
126
|
+
complete: isComplete,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check for status indicators
|
|
131
|
+
for (const [status, patterns] of Object.entries(STATUS_PATTERNS)) {
|
|
132
|
+
if (patterns.some(p => p.test(line))) {
|
|
133
|
+
if (status === 'blocked') {
|
|
134
|
+
blockedTasks++;
|
|
135
|
+
currentPhase.status = 'blocked';
|
|
136
|
+
} else if (status === 'complete' && currentPhase.status !== 'blocked') {
|
|
137
|
+
currentPhase.status = 'complete';
|
|
138
|
+
} else if (status === 'inProgress' && !['complete', 'blocked'].includes(currentPhase.status)) {
|
|
139
|
+
currentPhase.status = 'in_progress';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check for version info
|
|
145
|
+
const versionMatch = line.match(/v?(\d+\.\d+\.\d+)/);
|
|
146
|
+
if (versionMatch) {
|
|
147
|
+
currentPhase.version = versionMatch[1];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Add last phase
|
|
153
|
+
if (currentPhase) {
|
|
154
|
+
roadmap.phases.push(currentPhase);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Calculate metrics
|
|
158
|
+
roadmap.metrics = {
|
|
159
|
+
totalPhases: roadmap.phases.length,
|
|
160
|
+
completedPhases: roadmap.phases.filter(p => p.status === 'complete').length,
|
|
161
|
+
inProgressPhases: roadmap.phases.filter(p => p.status === 'in_progress').length,
|
|
162
|
+
blockedPhases: roadmap.phases.filter(p => p.status === 'blocked').length,
|
|
163
|
+
totalTasks,
|
|
164
|
+
completedTasks,
|
|
165
|
+
blockedTasks,
|
|
166
|
+
progress: totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
outputText() {
|
|
171
|
+
console.log('='.repeat(70));
|
|
172
|
+
console.log(' ROADMAP DASHBOARD');
|
|
173
|
+
console.log('='.repeat(70));
|
|
174
|
+
|
|
175
|
+
for (const roadmap of this.roadmaps) {
|
|
176
|
+
console.log(`\nš ${roadmap.name}`);
|
|
177
|
+
console.log(` Path: ${roadmap.path}`);
|
|
178
|
+
console.log('-'.repeat(70));
|
|
179
|
+
|
|
180
|
+
// Progress bar
|
|
181
|
+
const progress = roadmap.metrics.progress;
|
|
182
|
+
const barLength = 40;
|
|
183
|
+
const filled = Math.round((progress / 100) * barLength);
|
|
184
|
+
const bar = 'ā'.repeat(filled) + 'ā'.repeat(barLength - filled);
|
|
185
|
+
console.log(`\n Progress: [${bar}] ${progress}%`);
|
|
186
|
+
|
|
187
|
+
// Metrics
|
|
188
|
+
const m = roadmap.metrics;
|
|
189
|
+
console.log(`\n Phases: ${m.completedPhases}/${m.totalPhases} complete`);
|
|
190
|
+
console.log(` Tasks: ${m.completedTasks}/${m.totalTasks} complete`);
|
|
191
|
+
|
|
192
|
+
if (m.blockedPhases > 0) {
|
|
193
|
+
console.log(` ā ļø ${m.blockedPhases} phase(s) blocked`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Phase details
|
|
197
|
+
console.log('\n Phase Status:');
|
|
198
|
+
for (const phase of roadmap.phases) {
|
|
199
|
+
const icon = phase.status === 'complete' ? 'ā
' :
|
|
200
|
+
phase.status === 'in_progress' ? 'š' :
|
|
201
|
+
phase.status === 'blocked' ? 'ā' : 'ā¬';
|
|
202
|
+
const version = phase.version ? ` (v${phase.version})` : '';
|
|
203
|
+
console.log(` ${icon} ${phase.name}${version}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Summary
|
|
208
|
+
console.log('\n' + '='.repeat(70));
|
|
209
|
+
console.log('SUMMARY');
|
|
210
|
+
console.log('='.repeat(70));
|
|
211
|
+
|
|
212
|
+
const totals = this.roadmaps.reduce((acc, r) => ({
|
|
213
|
+
phases: acc.phases + r.metrics.totalPhases,
|
|
214
|
+
completed: acc.completed + r.metrics.completedPhases,
|
|
215
|
+
tasks: acc.tasks + r.metrics.totalTasks,
|
|
216
|
+
doneTasks: acc.doneTasks + r.metrics.completedTasks,
|
|
217
|
+
}), { phases: 0, completed: 0, tasks: 0, doneTasks: 0 });
|
|
218
|
+
|
|
219
|
+
console.log(`\n Total Roadmaps: ${this.roadmaps.length}`);
|
|
220
|
+
console.log(` Total Phases: ${totals.completed}/${totals.phases} complete`);
|
|
221
|
+
console.log(` Total Tasks: ${totals.doneTasks}/${totals.tasks} complete`);
|
|
222
|
+
console.log(` Overall: ${totals.tasks > 0 ? Math.round((totals.doneTasks / totals.tasks) * 100) : 0}%`);
|
|
223
|
+
console.log('\n' + '='.repeat(70) + '\n');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
outputJson() {
|
|
227
|
+
const output = {
|
|
228
|
+
scannedAt: new Date().toISOString(),
|
|
229
|
+
path: this.path,
|
|
230
|
+
roadmaps: this.roadmaps,
|
|
231
|
+
summary: {
|
|
232
|
+
totalRoadmaps: this.roadmaps.length,
|
|
233
|
+
totalPhases: this.roadmaps.reduce((acc, r) => acc + r.metrics.totalPhases, 0),
|
|
234
|
+
completedPhases: this.roadmaps.reduce((acc, r) => acc + r.metrics.completedPhases, 0),
|
|
235
|
+
totalTasks: this.roadmaps.reduce((acc, r) => acc + r.metrics.totalTasks, 0),
|
|
236
|
+
completedTasks: this.roadmaps.reduce((acc, r) => acc + r.metrics.completedTasks, 0),
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
console.log(JSON.stringify(output, null, 2));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// CLI entry point
|
|
245
|
+
async function main() {
|
|
246
|
+
const args = process.argv.slice(2);
|
|
247
|
+
|
|
248
|
+
const getArg = (name) => {
|
|
249
|
+
const index = args.indexOf(`--${name}`);
|
|
250
|
+
return index >= 0 ? args[index + 1] : null;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const options = {
|
|
254
|
+
path: getArg('path') || process.cwd(),
|
|
255
|
+
output: getArg('output') || 'text',
|
|
256
|
+
recursive: !args.includes('--no-recursive'),
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const scanner = new RoadmapScanner(options);
|
|
260
|
+
await scanner.scan();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
main().catch(console.error);
|