tlc-claude-code 1.2.28 → 1.2.29
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 +9 -4
- package/package.json +15 -4
- package/scripts/capture-screenshots.js +170 -0
- package/scripts/docs-update.js +253 -0
- package/scripts/generate-screenshots.js +321 -0
- package/scripts/project-docs.js +377 -0
- package/scripts/vps-setup.sh +477 -0
- package/templates/docs-sync.yml +91 -0
package/README.md
CHANGED
|
@@ -58,10 +58,12 @@ No manual testing. No "does this work?" No vibes.
|
|
|
58
58
|
### Then Just Run
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
/tlc
|
|
61
|
+
/tlc:next
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
Shows what's next, asks "Proceed? [Y/n]", then executes. That's it.
|
|
65
|
+
|
|
66
|
+
Or use `/tlc` for the full dashboard.
|
|
65
67
|
|
|
66
68
|
---
|
|
67
69
|
|
|
@@ -70,6 +72,8 @@ TLC knows where you are and what's next.
|
|
|
70
72
|
### For Solo Developers
|
|
71
73
|
|
|
72
74
|
- **Test-first by default** — Claude writes tests before code
|
|
75
|
+
- **Auto-parallelization** — Up to 10 agents run independent tasks simultaneously
|
|
76
|
+
- **`/tlc:next`** — One command to progress. No decisions needed.
|
|
73
77
|
- **Smart dashboard** — See progress, run actions
|
|
74
78
|
- **Coverage gaps** — Find and fix untested code
|
|
75
79
|
- **Auto-fix** — Automatically repair failing tests
|
|
@@ -95,10 +99,11 @@ TLC knows where you are and what's next.
|
|
|
95
99
|
|
|
96
100
|
| Command | What It Does |
|
|
97
101
|
|---------|--------------|
|
|
98
|
-
| `/tlc` | **
|
|
102
|
+
| `/tlc:next` | **Just do it — shows next action, asks once, executes** |
|
|
103
|
+
| `/tlc` | Smart dashboard — full status view |
|
|
99
104
|
| `/tlc:new-project` | Start new project with roadmap |
|
|
100
105
|
| `/tlc:init` | Add TLC to existing codebase |
|
|
101
|
-
| `/tlc:build` | Write tests → implement
|
|
106
|
+
| `/tlc:build` | Write tests → implement (auto-parallelizes up to 10 agents) |
|
|
102
107
|
| `/tlc:coverage` | Find and fix untested code |
|
|
103
108
|
| `/tlc:quality` | Test quality scoring |
|
|
104
109
|
| `/tlc:autofix` | Auto-repair failing tests |
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tlc-claude-code",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.29",
|
|
4
4
|
"description": "TLC - Test Led Coding for Claude Code",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tlc": "./bin/tlc.js",
|
|
7
|
-
"tlc-claude-code": "./bin/install.js"
|
|
7
|
+
"tlc-claude-code": "./bin/install.js",
|
|
8
|
+
"tlc-docs": "./scripts/project-docs.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"bin/",
|
|
@@ -16,6 +17,8 @@
|
|
|
16
17
|
"server/package.json",
|
|
17
18
|
"server/package-lock.json",
|
|
18
19
|
"server/setup.sh",
|
|
20
|
+
"scripts/",
|
|
21
|
+
"templates/",
|
|
19
22
|
"docker-compose.dev.yml",
|
|
20
23
|
"start-dev.ps1",
|
|
21
24
|
"start-dev.sh",
|
|
@@ -26,7 +29,11 @@
|
|
|
26
29
|
],
|
|
27
30
|
"scripts": {
|
|
28
31
|
"build": "cd dashboard && npm run build",
|
|
29
|
-
"prepublishOnly": "npm run build"
|
|
32
|
+
"prepublishOnly": "npm run build",
|
|
33
|
+
"docs": "node scripts/docs-update.js",
|
|
34
|
+
"docs:check": "node scripts/docs-update.js --check",
|
|
35
|
+
"docs:screenshots": "node scripts/generate-screenshots.js",
|
|
36
|
+
"docs:capture": "node scripts/capture-screenshots.js"
|
|
30
37
|
},
|
|
31
38
|
"repository": {
|
|
32
39
|
"type": "git",
|
|
@@ -41,5 +48,9 @@
|
|
|
41
48
|
"testing"
|
|
42
49
|
],
|
|
43
50
|
"author": "Jurgen Calleja",
|
|
44
|
-
"license": "MIT"
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"playwright": "^1.58.1",
|
|
54
|
+
"text-to-image": "^8.0.1"
|
|
55
|
+
}
|
|
45
56
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Capture real screenshots using Playwright
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/capture-screenshots.js # All screenshots
|
|
7
|
+
* node scripts/capture-screenshots.js dashboard # Dashboard only
|
|
8
|
+
* node scripts/capture-screenshots.js terminal # Terminal mockups only
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { chromium } = require('playwright');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { spawn } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const OUTPUT_DIR = path.join(__dirname, '../docs/wiki/images');
|
|
17
|
+
const DASHBOARD_URL = process.env.TLC_DASHBOARD_URL || 'http://localhost:3147';
|
|
18
|
+
|
|
19
|
+
// Ensure output directory exists
|
|
20
|
+
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
21
|
+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if dashboard is running
|
|
26
|
+
*/
|
|
27
|
+
async function isDashboardRunning() {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(DASHBOARD_URL);
|
|
30
|
+
return response.ok;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Start dashboard server
|
|
38
|
+
*/
|
|
39
|
+
async function startDashboard() {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
console.log('Starting TLC dashboard...');
|
|
42
|
+
const proc = spawn('node', ['server/index.js'], {
|
|
43
|
+
cwd: path.join(__dirname, '..'),
|
|
44
|
+
detached: true,
|
|
45
|
+
stdio: 'ignore',
|
|
46
|
+
});
|
|
47
|
+
proc.unref();
|
|
48
|
+
|
|
49
|
+
// Wait for server to be ready
|
|
50
|
+
let attempts = 0;
|
|
51
|
+
const check = setInterval(async () => {
|
|
52
|
+
attempts++;
|
|
53
|
+
if (await isDashboardRunning()) {
|
|
54
|
+
clearInterval(check);
|
|
55
|
+
resolve(proc);
|
|
56
|
+
} else if (attempts > 30) {
|
|
57
|
+
clearInterval(check);
|
|
58
|
+
reject(new Error('Dashboard failed to start'));
|
|
59
|
+
}
|
|
60
|
+
}, 1000);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Capture dashboard screenshots
|
|
66
|
+
*/
|
|
67
|
+
async function captureDashboard(browser) {
|
|
68
|
+
console.log('\nCapturing dashboard screenshots...');
|
|
69
|
+
|
|
70
|
+
const page = await browser.newPage();
|
|
71
|
+
await page.setViewportSize({ width: 1280, height: 800 });
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Main dashboard
|
|
75
|
+
await page.goto(DASHBOARD_URL, { waitUntil: 'networkidle' });
|
|
76
|
+
await page.waitForTimeout(1000);
|
|
77
|
+
await page.screenshot({
|
|
78
|
+
path: path.join(OUTPUT_DIR, 'dashboard-overview.png'),
|
|
79
|
+
fullPage: false,
|
|
80
|
+
});
|
|
81
|
+
console.log(' ✓ dashboard-overview.png');
|
|
82
|
+
|
|
83
|
+
// Try to navigate to different tabs if they exist
|
|
84
|
+
const tabs = ['tasks', 'logs', 'team', 'settings'];
|
|
85
|
+
for (const tab of tabs) {
|
|
86
|
+
try {
|
|
87
|
+
const tabSelector = `[data-tab="${tab}"], [href="#${tab}"], button:has-text("${tab}")`;
|
|
88
|
+
const tabElement = await page.$(tabSelector);
|
|
89
|
+
if (tabElement) {
|
|
90
|
+
await tabElement.click();
|
|
91
|
+
await page.waitForTimeout(500);
|
|
92
|
+
await page.screenshot({
|
|
93
|
+
path: path.join(OUTPUT_DIR, `dashboard-${tab}.png`),
|
|
94
|
+
fullPage: false,
|
|
95
|
+
});
|
|
96
|
+
console.log(` ✓ dashboard-${tab}.png`);
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// Tab might not exist, skip
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.log(` ⚠ Dashboard capture failed: ${error.message}`);
|
|
104
|
+
} finally {
|
|
105
|
+
await page.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Generate terminal-style screenshots using text-to-image
|
|
111
|
+
*/
|
|
112
|
+
async function captureTerminal() {
|
|
113
|
+
console.log('\nGenerating terminal screenshots...');
|
|
114
|
+
|
|
115
|
+
// Import the generator script
|
|
116
|
+
const generateScript = path.join(__dirname, 'generate-screenshots.js');
|
|
117
|
+
if (fs.existsSync(generateScript)) {
|
|
118
|
+
require(generateScript);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(' ⚠ generate-screenshots.js not found, skipping terminal screenshots');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Main function
|
|
126
|
+
*/
|
|
127
|
+
async function main() {
|
|
128
|
+
const mode = process.argv[2] || 'all';
|
|
129
|
+
console.log(`Screenshot capture mode: ${mode}`);
|
|
130
|
+
|
|
131
|
+
let browser;
|
|
132
|
+
let dashboardStarted = false;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
if (mode === 'all' || mode === 'dashboard') {
|
|
136
|
+
// Check if dashboard is running
|
|
137
|
+
const running = await isDashboardRunning();
|
|
138
|
+
|
|
139
|
+
if (!running) {
|
|
140
|
+
console.log('Dashboard not running. Starting...');
|
|
141
|
+
try {
|
|
142
|
+
await startDashboard();
|
|
143
|
+
dashboardStarted = true;
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.log('Could not start dashboard, will use terminal mockups only');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (await isDashboardRunning()) {
|
|
150
|
+
browser = await chromium.launch({ headless: true });
|
|
151
|
+
await captureDashboard(browser);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (mode === 'all' || mode === 'terminal') {
|
|
156
|
+
await captureTerminal();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('\n✓ Screenshot capture complete!');
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('Screenshot capture failed:', error.message);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
} finally {
|
|
164
|
+
if (browser) {
|
|
165
|
+
await browser.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
main();
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Documentation maintenance script
|
|
4
|
+
*
|
|
5
|
+
* Automatically:
|
|
6
|
+
* - Updates version references in docs
|
|
7
|
+
* - Checks for missing command documentation
|
|
8
|
+
* - Validates internal links
|
|
9
|
+
* - Regenerates screenshots if needed
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/docs-update.js # Full update
|
|
13
|
+
* node scripts/docs-update.js --check # Check only, no changes
|
|
14
|
+
* node scripts/docs-update.js --screenshots # Regenerate screenshots
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const ROOT = path.join(__dirname, '..');
|
|
21
|
+
const DOCS_DIR = path.join(ROOT, 'docs/wiki');
|
|
22
|
+
const COMMANDS_DIR = path.join(ROOT, '.claude/commands/tlc');
|
|
23
|
+
const PACKAGE_JSON = path.join(ROOT, 'package.json');
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
const checkOnly = args.includes('--check');
|
|
27
|
+
const screenshotsOnly = args.includes('--screenshots');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get current version from package.json
|
|
31
|
+
*/
|
|
32
|
+
function getVersion() {
|
|
33
|
+
const pkg = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf-8'));
|
|
34
|
+
return pkg.version;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get all command files
|
|
39
|
+
*/
|
|
40
|
+
function getCommands() {
|
|
41
|
+
if (!fs.existsSync(COMMANDS_DIR)) return [];
|
|
42
|
+
|
|
43
|
+
return fs.readdirSync(COMMANDS_DIR)
|
|
44
|
+
.filter(f => f.endsWith('.md'))
|
|
45
|
+
.map(f => {
|
|
46
|
+
const content = fs.readFileSync(path.join(COMMANDS_DIR, f), 'utf-8');
|
|
47
|
+
const name = f.replace('.md', '');
|
|
48
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
49
|
+
const title = titleMatch ? titleMatch[1] : name;
|
|
50
|
+
|
|
51
|
+
return { name, title, file: f };
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check command-reference.md for missing commands
|
|
57
|
+
*/
|
|
58
|
+
function checkCommandDocs() {
|
|
59
|
+
const commandRefPath = path.join(DOCS_DIR, 'command-reference.md');
|
|
60
|
+
if (!fs.existsSync(commandRefPath)) {
|
|
61
|
+
console.log('⚠ command-reference.md not found');
|
|
62
|
+
return { missing: [], outdated: [] };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const content = fs.readFileSync(commandRefPath, 'utf-8');
|
|
66
|
+
const commands = getCommands();
|
|
67
|
+
|
|
68
|
+
const missing = commands.filter(cmd => {
|
|
69
|
+
const pattern = new RegExp(`/tlc:${cmd.name}[^a-z]`, 'i');
|
|
70
|
+
return !pattern.test(content) && cmd.name !== 'tlc';
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return { missing, total: commands.length };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Update version references in docs
|
|
78
|
+
*/
|
|
79
|
+
function updateVersions() {
|
|
80
|
+
const version = getVersion();
|
|
81
|
+
const versionPattern = /v\d+\.\d+\.\d+/g;
|
|
82
|
+
|
|
83
|
+
let updated = 0;
|
|
84
|
+
|
|
85
|
+
const files = fs.readdirSync(DOCS_DIR)
|
|
86
|
+
.filter(f => f.endsWith('.md'));
|
|
87
|
+
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const filepath = path.join(DOCS_DIR, file);
|
|
90
|
+
let content = fs.readFileSync(filepath, 'utf-8');
|
|
91
|
+
|
|
92
|
+
const matches = content.match(versionPattern);
|
|
93
|
+
if (matches) {
|
|
94
|
+
const oldContent = content;
|
|
95
|
+
content = content.replace(versionPattern, `v${version}`);
|
|
96
|
+
|
|
97
|
+
if (content !== oldContent && !checkOnly) {
|
|
98
|
+
fs.writeFileSync(filepath, content);
|
|
99
|
+
updated++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return updated;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check for broken internal links
|
|
109
|
+
*/
|
|
110
|
+
function checkLinks() {
|
|
111
|
+
const broken = [];
|
|
112
|
+
|
|
113
|
+
const files = fs.readdirSync(DOCS_DIR)
|
|
114
|
+
.filter(f => f.endsWith('.md'));
|
|
115
|
+
|
|
116
|
+
const existingPages = files.map(f => f.replace('.md', ''));
|
|
117
|
+
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
const filepath = path.join(DOCS_DIR, file);
|
|
120
|
+
const content = fs.readFileSync(filepath, 'utf-8');
|
|
121
|
+
|
|
122
|
+
// Find markdown links
|
|
123
|
+
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
124
|
+
let match;
|
|
125
|
+
|
|
126
|
+
while ((match = linkPattern.exec(content)) !== null) {
|
|
127
|
+
const [, , link] = match;
|
|
128
|
+
|
|
129
|
+
// Check internal links (no http/https)
|
|
130
|
+
if (!link.startsWith('http') && !link.startsWith('#')) {
|
|
131
|
+
const targetPage = link.replace(/\.md$/, '').replace(/^\//, '');
|
|
132
|
+
|
|
133
|
+
if (!existingPages.includes(targetPage) &&
|
|
134
|
+
!fs.existsSync(path.join(DOCS_DIR, link)) &&
|
|
135
|
+
!link.startsWith('images/')) {
|
|
136
|
+
broken.push({ file, link });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return broken;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check screenshots exist
|
|
147
|
+
*/
|
|
148
|
+
function checkScreenshots() {
|
|
149
|
+
const imagesDir = path.join(DOCS_DIR, 'images');
|
|
150
|
+
const missing = [];
|
|
151
|
+
|
|
152
|
+
if (!fs.existsSync(imagesDir)) {
|
|
153
|
+
return { missing: ['images directory not found'], existing: 0 };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const files = fs.readdirSync(DOCS_DIR)
|
|
157
|
+
.filter(f => f.endsWith('.md'));
|
|
158
|
+
|
|
159
|
+
const existingImages = fs.readdirSync(imagesDir)
|
|
160
|
+
.filter(f => f.endsWith('.png') || f.endsWith('.jpg'));
|
|
161
|
+
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
const filepath = path.join(DOCS_DIR, file);
|
|
164
|
+
const content = fs.readFileSync(filepath, 'utf-8');
|
|
165
|
+
|
|
166
|
+
// Find image references
|
|
167
|
+
const imagePattern = /!\[([^\]]*)\]\(images\/([^)]+)\)/g;
|
|
168
|
+
let match;
|
|
169
|
+
|
|
170
|
+
while ((match = imagePattern.exec(content)) !== null) {
|
|
171
|
+
const [, , imagePath] = match;
|
|
172
|
+
if (!existingImages.includes(imagePath)) {
|
|
173
|
+
missing.push({ file, image: imagePath });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { missing, existing: existingImages.length };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Main function
|
|
183
|
+
*/
|
|
184
|
+
async function main() {
|
|
185
|
+
console.log('TLC Documentation Maintenance\n');
|
|
186
|
+
console.log('═'.repeat(50));
|
|
187
|
+
|
|
188
|
+
const version = getVersion();
|
|
189
|
+
console.log(`\nVersion: ${version}`);
|
|
190
|
+
|
|
191
|
+
// Screenshots only mode
|
|
192
|
+
if (screenshotsOnly) {
|
|
193
|
+
console.log('\nRegenerating screenshots...');
|
|
194
|
+
require('./generate-screenshots.js');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check commands
|
|
199
|
+
console.log('\n📚 Commands');
|
|
200
|
+
const { missing, total } = checkCommandDocs();
|
|
201
|
+
console.log(` ${total} commands found`);
|
|
202
|
+
if (missing.length > 0) {
|
|
203
|
+
console.log(` ⚠ ${missing.length} missing from command-reference.md:`);
|
|
204
|
+
missing.forEach(cmd => console.log(` - /tlc:${cmd.name}`));
|
|
205
|
+
} else {
|
|
206
|
+
console.log(' ✓ All commands documented');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check versions
|
|
210
|
+
console.log('\n🏷️ Versions');
|
|
211
|
+
const updated = updateVersions();
|
|
212
|
+
if (checkOnly) {
|
|
213
|
+
console.log(` ${updated > 0 ? '⚠' : '✓'} ${updated} files need version update`);
|
|
214
|
+
} else {
|
|
215
|
+
console.log(` ✓ ${updated} files updated to v${version}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check links
|
|
219
|
+
console.log('\n🔗 Links');
|
|
220
|
+
const brokenLinks = checkLinks();
|
|
221
|
+
if (brokenLinks.length > 0) {
|
|
222
|
+
console.log(` ⚠ ${brokenLinks.length} broken links:`);
|
|
223
|
+
brokenLinks.forEach(({ file, link }) => console.log(` ${file}: ${link}`));
|
|
224
|
+
} else {
|
|
225
|
+
console.log(' ✓ All internal links valid');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check screenshots
|
|
229
|
+
console.log('\n📸 Screenshots');
|
|
230
|
+
const { missing: missingImages, existing } = checkScreenshots();
|
|
231
|
+
console.log(` ${existing} screenshots found`);
|
|
232
|
+
if (missingImages.length > 0) {
|
|
233
|
+
console.log(` ⚠ ${missingImages.length} missing screenshots:`);
|
|
234
|
+
missingImages.forEach(({ file, image }) => console.log(` ${file}: ${image}`));
|
|
235
|
+
} else {
|
|
236
|
+
console.log(' ✓ All referenced screenshots exist');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Summary
|
|
240
|
+
console.log('\n' + '═'.repeat(50));
|
|
241
|
+
|
|
242
|
+
const issues = missing.length + brokenLinks.length + missingImages.length;
|
|
243
|
+
if (issues > 0) {
|
|
244
|
+
console.log(`\n⚠ ${issues} issue(s) found`);
|
|
245
|
+
if (!checkOnly) {
|
|
246
|
+
console.log('Run with --check to see issues without making changes');
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
console.log('\n✓ Documentation is up to date!');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
main().catch(console.error);
|