claude-cli-advanced-starter-pack 1.8.4 → 1.8.6
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/package.json +1 -1
- package/src/cli/menu.js +172 -2
- package/src/cli/mobile-menu.js +230 -0
- package/src/commands/explore-mcp/claude-md-updater.js +38 -3
- package/src/commands/init.js +89 -0
- package/src/commands/panel.js +84 -0
- package/src/commands/setup-wizard.js +85 -30
- package/src/data/releases.json +30 -0
- package/src/utils/happy-detect.js +66 -0
- package/src/utils/version-check.js +43 -17
- package/templates/commands/create-task-list-for-issue.template.md +72 -0
- package/templates/commands/create-task-list.template.md +73 -0
- package/templates/commands/detect-tech-stack.template.md +137 -0
- package/templates/commands/menu-for-happy-ui.template.md +109 -0
- package/templates/commands/menu-issues-list.template.md +72 -0
- package/templates/hooks/ccasp-update-check.template.js +36 -1
- package/templates/hooks/github-progress-hook.template.cjs +248 -0
- package/templates/hooks/github-progress-hook.template.js +0 -197
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Re-run tech stack detection and update configuration
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /detect-tech-stack - Tech Stack Analysis
|
|
6
|
+
|
|
7
|
+
Re-analyze the project's tech stack and update `.claude/config/tech-stack.json`.
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Issue #8: Tech stack detection previously only ran during terminal phase (ccasp init).
|
|
12
|
+
This command allows re-running detection from within Claude CLI to:
|
|
13
|
+
- Detect new dependencies added since initial setup
|
|
14
|
+
- Update framework versions
|
|
15
|
+
- Refresh configuration for agents, skills, and hooks
|
|
16
|
+
|
|
17
|
+
## Execution Steps
|
|
18
|
+
|
|
19
|
+
### Step 1: Read Current Tech Stack
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cat .claude/config/tech-stack.json 2>/dev/null || echo "{}"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Save as `previousStack` for comparison.
|
|
26
|
+
|
|
27
|
+
### Step 2: Detect Current Stack
|
|
28
|
+
|
|
29
|
+
Analyze the following files to build the new tech stack:
|
|
30
|
+
|
|
31
|
+
**Package Managers:**
|
|
32
|
+
- `package.json` → Node.js/npm project
|
|
33
|
+
- `pyproject.toml` or `requirements.txt` → Python project
|
|
34
|
+
- `Cargo.toml` → Rust project
|
|
35
|
+
- `go.mod` → Go project
|
|
36
|
+
- `pom.xml` or `build.gradle` → Java project
|
|
37
|
+
|
|
38
|
+
**Frontend Frameworks (from package.json):**
|
|
39
|
+
- `react` → React
|
|
40
|
+
- `vue` → Vue.js
|
|
41
|
+
- `@angular/core` → Angular
|
|
42
|
+
- `svelte` → Svelte
|
|
43
|
+
- `next` → Next.js
|
|
44
|
+
- `nuxt` → Nuxt.js
|
|
45
|
+
- `vite` → Vite bundler
|
|
46
|
+
|
|
47
|
+
**Backend Frameworks:**
|
|
48
|
+
- `express` → Express.js
|
|
49
|
+
- `fastify` → Fastify
|
|
50
|
+
- `@nestjs/core` → NestJS
|
|
51
|
+
- `fastapi` (Python) → FastAPI
|
|
52
|
+
- `django` (Python) → Django
|
|
53
|
+
- `flask` (Python) → Flask
|
|
54
|
+
|
|
55
|
+
**Testing Frameworks:**
|
|
56
|
+
- `jest` → Jest
|
|
57
|
+
- `vitest` → Vitest
|
|
58
|
+
- `playwright` → Playwright
|
|
59
|
+
- `cypress` → Cypress
|
|
60
|
+
- `pytest` (Python) → Pytest
|
|
61
|
+
|
|
62
|
+
**Database:**
|
|
63
|
+
- `prisma` → Prisma ORM
|
|
64
|
+
- `drizzle-orm` → Drizzle
|
|
65
|
+
- `mongoose` → MongoDB
|
|
66
|
+
- `pg` or `postgres` → PostgreSQL
|
|
67
|
+
- `mysql2` → MySQL
|
|
68
|
+
|
|
69
|
+
**Deployment:**
|
|
70
|
+
Check for config files:
|
|
71
|
+
- `wrangler.toml` → Cloudflare
|
|
72
|
+
- `railway.json` or `railway.toml` → Railway
|
|
73
|
+
- `vercel.json` → Vercel
|
|
74
|
+
- `netlify.toml` → Netlify
|
|
75
|
+
- `Dockerfile` → Docker
|
|
76
|
+
|
|
77
|
+
### Step 3: Compare and Report Changes
|
|
78
|
+
|
|
79
|
+
Compare `previousStack` with newly detected stack:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
83
|
+
║ 📊 Tech Stack Analysis ║
|
|
84
|
+
╠═══════════════════════════════════════════════════════════════╣
|
|
85
|
+
║ ║
|
|
86
|
+
║ Language: {{language}} ║
|
|
87
|
+
║ Frontend: {{frontend.framework}} + {{frontend.bundler}} ║
|
|
88
|
+
║ Backend: {{backend.framework}} ║
|
|
89
|
+
║ Database: {{database.type}} ║
|
|
90
|
+
║ Testing: {{testing.frameworks}} ║
|
|
91
|
+
║ Deploy: {{deployment.platform}} ║
|
|
92
|
+
║ ║
|
|
93
|
+
╠═══════════════════════════════════════════════════════════════╣
|
|
94
|
+
║ Changes Detected: ║
|
|
95
|
+
║ [+] Added: (list new dependencies) ║
|
|
96
|
+
║ [-] Removed: (list removed dependencies) ║
|
|
97
|
+
║ [~] Updated: (list version changes) ║
|
|
98
|
+
║ ║
|
|
99
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Step 4: Update tech-stack.json
|
|
103
|
+
|
|
104
|
+
Write the updated configuration:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Backup current config
|
|
108
|
+
cp .claude/config/tech-stack.json .claude/config/tech-stack.json.bak
|
|
109
|
+
|
|
110
|
+
# Write new config (use actual detected values)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Step 5: Suggest Agent/Skill Updates
|
|
114
|
+
|
|
115
|
+
Based on detected changes, recommend:
|
|
116
|
+
|
|
117
|
+
| Change | Recommendation |
|
|
118
|
+
|--------|----------------|
|
|
119
|
+
| Added Playwright | Enable E2E testing skill |
|
|
120
|
+
| Added Prisma | Enable database agent |
|
|
121
|
+
| Added React 19 | Update component patterns |
|
|
122
|
+
| New test framework | Configure test runner |
|
|
123
|
+
|
|
124
|
+
## Output
|
|
125
|
+
|
|
126
|
+
After completion, display:
|
|
127
|
+
|
|
128
|
+
1. **Summary** of detected tech stack
|
|
129
|
+
2. **Diff** showing what changed since last detection
|
|
130
|
+
3. **Recommendations** for updating CCASP configuration
|
|
131
|
+
4. **Restart reminder** if significant changes detected
|
|
132
|
+
|
|
133
|
+
## Related Commands
|
|
134
|
+
|
|
135
|
+
- `/claude-audit` - Audit CLAUDE.md configuration
|
|
136
|
+
- `/update-smart` - Smart update manager
|
|
137
|
+
- `/project-impl` - Project implementation agent
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Mobile-friendly menu optimized for Happy.Engineering CLI (no overflow)
|
|
3
|
+
model: sonnet
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /menu-for-happy-ui - Mobile-Optimized CCASP Menu
|
|
7
|
+
|
|
8
|
+
Display the CCASP menu in a mobile-friendly format that doesn't overflow on small screens.
|
|
9
|
+
|
|
10
|
+
## Purpose
|
|
11
|
+
|
|
12
|
+
This command is a fallback for when automatic Happy CLI detection fails. It forces the mobile-optimized menu layout regardless of environment.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- Running CCASP through Happy Coder mobile app
|
|
17
|
+
- Using a narrow terminal window
|
|
18
|
+
- Automatic detection (`HAPPY_*` env vars) not working
|
|
19
|
+
- Preference for compact menu layout
|
|
20
|
+
|
|
21
|
+
## Mobile Menu Features
|
|
22
|
+
|
|
23
|
+
- **Max 40 character width** - No horizontal scrolling
|
|
24
|
+
- **Single-column layout** - Easy vertical scrolling
|
|
25
|
+
- **Minimal decorations** - Less visual noise
|
|
26
|
+
- **Inline panel** - No new window launch (works with Happy)
|
|
27
|
+
|
|
28
|
+
## Menu Layout
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
╔══════════════════════════════════╗
|
|
32
|
+
║ CCASP v1.x ║
|
|
33
|
+
║ Mobile Menu ║
|
|
34
|
+
╚══════════════════════════════════╝
|
|
35
|
+
✓ Configured
|
|
36
|
+
|
|
37
|
+
1) Create Task
|
|
38
|
+
2) Decompose Issue
|
|
39
|
+
3) Sync Tasks
|
|
40
|
+
──────────────────────────────────
|
|
41
|
+
4) Setup
|
|
42
|
+
5) List Tasks
|
|
43
|
+
6) Install Command
|
|
44
|
+
──────────────────────────────────
|
|
45
|
+
P) Panel (inline)
|
|
46
|
+
T) Test Setup
|
|
47
|
+
A) Agent Creator
|
|
48
|
+
M) MCP Explorer
|
|
49
|
+
──────────────────────────────────
|
|
50
|
+
S) Settings
|
|
51
|
+
?) Help
|
|
52
|
+
Q) Exit
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Instructions for Claude
|
|
56
|
+
|
|
57
|
+
When this command is invoked:
|
|
58
|
+
|
|
59
|
+
1. **Display Mobile Banner**
|
|
60
|
+
- Use 36-character width box
|
|
61
|
+
- Show version number (truncated)
|
|
62
|
+
- Show configuration status
|
|
63
|
+
|
|
64
|
+
2. **Present Menu Options**
|
|
65
|
+
- Single column with numbered shortcuts
|
|
66
|
+
- Separator lines using `─` (34 chars)
|
|
67
|
+
- No descriptions (too wide for mobile)
|
|
68
|
+
|
|
69
|
+
3. **Handle Selection**
|
|
70
|
+
- Route to appropriate handler
|
|
71
|
+
- For panel: display inline (no new window)
|
|
72
|
+
- Return to menu after each action
|
|
73
|
+
|
|
74
|
+
4. **Settings Submenu**
|
|
75
|
+
- Same compact format
|
|
76
|
+
- Quick access to: GitHub, Deployment, Tunnel, Token, Happy
|
|
77
|
+
|
|
78
|
+
## Comparison with Standard Menu
|
|
79
|
+
|
|
80
|
+
| Feature | Standard | Mobile |
|
|
81
|
+
|---------|----------|--------|
|
|
82
|
+
| Width | 76 chars | 36 chars |
|
|
83
|
+
| Columns | Multi | Single |
|
|
84
|
+
| Descriptions | Full | None |
|
|
85
|
+
| Panel launch | New window | Inline |
|
|
86
|
+
| ASCII art | Full banner | Minimal |
|
|
87
|
+
|
|
88
|
+
## Automatic Detection
|
|
89
|
+
|
|
90
|
+
CCASP automatically detects Happy CLI via environment variables:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// Auto-detected when any of these are set:
|
|
94
|
+
process.env.HAPPY_HOME_DIR
|
|
95
|
+
process.env.HAPPY_SERVER_URL
|
|
96
|
+
process.env.HAPPY_WEBAPP_URL
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If these are set, `/menu` automatically uses mobile layout. Use `/menu-for-happy-ui` only when auto-detection fails.
|
|
100
|
+
|
|
101
|
+
## Related Commands
|
|
102
|
+
|
|
103
|
+
- `/menu` - Standard menu (auto-detects Happy)
|
|
104
|
+
- `/happy-start` - Initialize Happy Mode session
|
|
105
|
+
- `/ccasp-panel` - Standard panel (new window)
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
*For Happy.Engineering mobile CLI users*
|
|
@@ -260,6 +260,78 @@ gh issue edit [NUMBER] --body "$(gh issue view [NUMBER] --json body -q .body)
|
|
|
260
260
|
|
|
261
261
|
---
|
|
262
262
|
|
|
263
|
+
### Step 6: After Task Completion - Close Issue Prompt
|
|
264
|
+
|
|
265
|
+
**CRITICAL: After ALL TodoWrite tasks are marked complete AND a commit is created, ALWAYS offer to close the issue.**
|
|
266
|
+
|
|
267
|
+
This step triggers when:
|
|
268
|
+
1. All tasks in TodoWrite are marked `completed`
|
|
269
|
+
2. A git commit has been made with changes
|
|
270
|
+
|
|
271
|
+
Display completion summary:
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
275
|
+
║ ✅ All Tasks Completed ║
|
|
276
|
+
╠═══════════════════════════════════════════════════════════════╣
|
|
277
|
+
║ ║
|
|
278
|
+
║ Issue: #[NUMBER] - [TITLE] ║
|
|
279
|
+
║ Commit: [SHORT_SHA] - [COMMIT_MSG_FIRST_LINE] ║
|
|
280
|
+
║ Tasks: [X] completed ║
|
|
281
|
+
║ ║
|
|
282
|
+
╠═══════════════════════════════════════════════════════════════╣
|
|
283
|
+
║ [C] Close issue with comment ║
|
|
284
|
+
║ [P] Push to origin + close issue ║
|
|
285
|
+
║ [K] Keep issue open ║
|
|
286
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Then ask:
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
header: "Issue"
|
|
293
|
+
question: "All tasks complete. Close issue #[NUMBER]?"
|
|
294
|
+
options:
|
|
295
|
+
- label: "C - Close with comment"
|
|
296
|
+
description: "Add completion summary and close"
|
|
297
|
+
- label: "P - Push + Close"
|
|
298
|
+
description: "Push commit to origin, then close"
|
|
299
|
+
- label: "K - Keep open"
|
|
300
|
+
description: "Leave issue open for follow-up"
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Handle Close Actions:**
|
|
304
|
+
|
|
305
|
+
**C (Close with comment):**
|
|
306
|
+
```bash
|
|
307
|
+
gh issue close [NUMBER] --comment "All tasks completed in commit [SHA].
|
|
308
|
+
|
|
309
|
+
## Completed Tasks
|
|
310
|
+
- ✅ Task 1
|
|
311
|
+
- ✅ Task 2
|
|
312
|
+
...
|
|
313
|
+
|
|
314
|
+
Ready for release."
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**P (Push + Close):**
|
|
318
|
+
```bash
|
|
319
|
+
git push origin HEAD
|
|
320
|
+
gh issue close [NUMBER] --comment "All tasks completed and pushed in commit [SHA].
|
|
321
|
+
|
|
322
|
+
## Completed Tasks
|
|
323
|
+
- ✅ Task 1
|
|
324
|
+
- ✅ Task 2
|
|
325
|
+
...
|
|
326
|
+
|
|
327
|
+
Ready for release."
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**K (Keep open):**
|
|
331
|
+
Display: "Issue #[NUMBER] kept open for follow-up."
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
263
335
|
## MOBILE OPTIMIZATION
|
|
264
336
|
|
|
265
337
|
- Single character inputs (A, B, C, S, V, X)
|
|
@@ -89,15 +89,50 @@ function getCurrentVersion() {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Check npm for latest version
|
|
92
|
+
* Issue #8: Added npm registry API fallback for Windows compatibility
|
|
92
93
|
*/
|
|
93
94
|
function checkLatestVersion() {
|
|
95
|
+
// Try npm CLI first
|
|
94
96
|
try {
|
|
95
97
|
const result = execSync(`npm view ${PACKAGE_NAME} version`, {
|
|
96
98
|
encoding: 'utf8',
|
|
97
99
|
timeout: 10000,
|
|
98
100
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
99
101
|
});
|
|
100
|
-
|
|
102
|
+
const version = result.trim();
|
|
103
|
+
if (version && /^\d+\.\d+\.\d+/.test(version)) {
|
|
104
|
+
return version;
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// npm CLI failed, try fallback
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Fallback: Direct npm registry API call
|
|
111
|
+
try {
|
|
112
|
+
const https = require('https');
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
const req = https.get(
|
|
115
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
116
|
+
{ timeout: 8000 },
|
|
117
|
+
(res) => {
|
|
118
|
+
let data = '';
|
|
119
|
+
res.on('data', (chunk) => (data += chunk));
|
|
120
|
+
res.on('end', () => {
|
|
121
|
+
try {
|
|
122
|
+
const pkg = JSON.parse(data);
|
|
123
|
+
resolve(pkg.version || null);
|
|
124
|
+
} catch {
|
|
125
|
+
resolve(null);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
req.on('error', () => resolve(null));
|
|
131
|
+
req.on('timeout', () => {
|
|
132
|
+
req.destroy();
|
|
133
|
+
resolve(null);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
101
136
|
} catch {
|
|
102
137
|
return null;
|
|
103
138
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Progress Hook
|
|
3
|
+
*
|
|
4
|
+
* Automatically updates GitHub issues as tasks are completed.
|
|
5
|
+
* Monitors TodoWrite calls and syncs progress to linked GitHub issues.
|
|
6
|
+
*
|
|
7
|
+
* Event: PostToolUse
|
|
8
|
+
* Matcher: TodoWrite
|
|
9
|
+
*
|
|
10
|
+
* This hook reads from stdin (Claude Code passes tool info there)
|
|
11
|
+
* and updates the linked GitHub issue with progress comments.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// Configuration - parse from tech-stack.json dynamically
|
|
19
|
+
function loadConfig() {
|
|
20
|
+
try {
|
|
21
|
+
const techStackPath = path.join(process.cwd(), '.claude', 'config', 'tech-stack.json');
|
|
22
|
+
if (fs.existsSync(techStackPath)) {
|
|
23
|
+
const techStack = JSON.parse(fs.readFileSync(techStackPath, 'utf8'));
|
|
24
|
+
const vc = techStack.versionControl || {};
|
|
25
|
+
const repo = vc.repository || '';
|
|
26
|
+
const [owner, repoName] = repo.includes('/') ? repo.split('/') : ['', ''];
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
owner: owner,
|
|
30
|
+
repo: repoName,
|
|
31
|
+
projectNumber: vc.projectBoard?.number || null,
|
|
32
|
+
enabled: !!vc.projectBoard?.type && !!owner && !!repoName,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Silent fail - config not available
|
|
37
|
+
}
|
|
38
|
+
return { owner: '', repo: '', projectNumber: null, enabled: false };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const PROGRESS_FILE = '.claude/hooks/cache/github-progress.json';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load progress tracking data
|
|
45
|
+
*/
|
|
46
|
+
function loadProgress() {
|
|
47
|
+
const progressPath = path.join(process.cwd(), PROGRESS_FILE);
|
|
48
|
+
|
|
49
|
+
if (fs.existsSync(progressPath)) {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(fs.readFileSync(progressPath, 'utf8'));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// Could not parse, return default
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
linkedIssue: null,
|
|
59
|
+
tasks: [],
|
|
60
|
+
completedTasks: [],
|
|
61
|
+
lastUpdate: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Save progress tracking data
|
|
67
|
+
*/
|
|
68
|
+
function saveProgress(progress) {
|
|
69
|
+
const progressPath = path.join(process.cwd(), PROGRESS_FILE);
|
|
70
|
+
const progressDir = path.dirname(progressPath);
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(progressDir)) {
|
|
73
|
+
fs.mkdirSync(progressDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
progress.lastUpdate = new Date().toISOString();
|
|
77
|
+
fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2), 'utf8');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if gh CLI is available
|
|
82
|
+
*/
|
|
83
|
+
function hasGhCli() {
|
|
84
|
+
try {
|
|
85
|
+
execSync('gh --version', { stdio: 'ignore' });
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Update GitHub issue with progress comment
|
|
94
|
+
*/
|
|
95
|
+
function updateGitHubIssue(issueNumber, completedCount, totalCount, latestTask, config) {
|
|
96
|
+
if (!hasGhCli()) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Create progress comment
|
|
102
|
+
const percentage = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
|
|
103
|
+
const progressBar = '█'.repeat(Math.floor(percentage / 10)) + '░'.repeat(10 - Math.floor(percentage / 10));
|
|
104
|
+
|
|
105
|
+
const comment = `### Progress Update
|
|
106
|
+
|
|
107
|
+
${progressBar} ${percentage}% (${completedCount}/${totalCount} tasks)
|
|
108
|
+
|
|
109
|
+
**Latest completed:** ${latestTask || 'N/A'}
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
*Auto-updated by Claude Code github-progress-hook*`;
|
|
113
|
+
|
|
114
|
+
// Add comment to issue - use file to avoid escaping issues
|
|
115
|
+
const cacheDir = path.join(process.cwd(), '.claude', 'hooks', 'cache');
|
|
116
|
+
if (!fs.existsSync(cacheDir)) {
|
|
117
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
const tmpFile = path.join(cacheDir, 'tmp-comment.md');
|
|
120
|
+
fs.writeFileSync(tmpFile, comment, 'utf8');
|
|
121
|
+
|
|
122
|
+
execSync(
|
|
123
|
+
`gh issue comment ${issueNumber} --repo ${config.owner}/${config.repo} --body-file "${tmpFile}"`,
|
|
124
|
+
{ stdio: 'pipe' }
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Clean up temp file
|
|
128
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
129
|
+
|
|
130
|
+
console.log(`[github-progress] Updated issue #${issueNumber}: ${percentage}% complete`);
|
|
131
|
+
return true;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// Silent fail
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Extract linked issue number from todos
|
|
140
|
+
*/
|
|
141
|
+
function findLinkedIssue(todos, progress) {
|
|
142
|
+
// Check if issue is already linked
|
|
143
|
+
if (progress.linkedIssue) {
|
|
144
|
+
return progress.linkedIssue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Look for issue reference in todo content (e.g., "Issue #11" or "#11")
|
|
148
|
+
for (const todo of todos) {
|
|
149
|
+
const content = todo.content || '';
|
|
150
|
+
const issueMatch = content.match(/(?:Issue\s*)?#(\d+)/i);
|
|
151
|
+
if (issueMatch) {
|
|
152
|
+
return parseInt(issueMatch[1], 10);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Main function - reads from stdin and processes TodoWrite
|
|
161
|
+
*/
|
|
162
|
+
async function main() {
|
|
163
|
+
const config = loadConfig();
|
|
164
|
+
|
|
165
|
+
// Skip if not configured
|
|
166
|
+
if (!config.enabled) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Read stdin to get tool information
|
|
171
|
+
let input = '';
|
|
172
|
+
try {
|
|
173
|
+
input = fs.readFileSync(0, 'utf8');
|
|
174
|
+
} catch {
|
|
175
|
+
// No stdin available
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let toolData;
|
|
180
|
+
try {
|
|
181
|
+
toolData = JSON.parse(input);
|
|
182
|
+
} catch {
|
|
183
|
+
// Invalid JSON
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check if this is a TodoWrite tool
|
|
188
|
+
const toolName = toolData.tool_name || toolData.name;
|
|
189
|
+
if (toolName !== 'TodoWrite') {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Get todos from input
|
|
194
|
+
const todos = toolData.tool_input?.todos || toolData.input?.todos || [];
|
|
195
|
+
if (todos.length === 0) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Load progress tracking
|
|
200
|
+
const progress = loadProgress();
|
|
201
|
+
|
|
202
|
+
// Check for linked issue
|
|
203
|
+
const issueNumber = findLinkedIssue(todos, progress);
|
|
204
|
+
|
|
205
|
+
if (issueNumber && !progress.linkedIssue) {
|
|
206
|
+
progress.linkedIssue = issueNumber;
|
|
207
|
+
console.log(`[github-progress] Linked to issue #${issueNumber}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Skip if no linked issue
|
|
211
|
+
if (!progress.linkedIssue) {
|
|
212
|
+
saveProgress(progress);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Count completed vs total tasks (excluding CONTEXT task)
|
|
217
|
+
const actualTodos = todos.filter(t => !t.content?.startsWith('CONTEXT:'));
|
|
218
|
+
const completedTodos = actualTodos.filter(t => t.status === 'completed');
|
|
219
|
+
const totalCount = actualTodos.length;
|
|
220
|
+
const completedCount = completedTodos.length;
|
|
221
|
+
|
|
222
|
+
// Find latest completed task
|
|
223
|
+
const latestCompleted = completedTodos[completedTodos.length - 1];
|
|
224
|
+
const latestTask = latestCompleted?.content || null;
|
|
225
|
+
|
|
226
|
+
// Check if progress changed (more tasks completed than before)
|
|
227
|
+
const previousCompleted = progress.completedTasks?.length || 0;
|
|
228
|
+
|
|
229
|
+
if (completedCount > previousCompleted) {
|
|
230
|
+
// Update GitHub issue
|
|
231
|
+
updateGitHubIssue(progress.linkedIssue, completedCount, totalCount, latestTask, config);
|
|
232
|
+
|
|
233
|
+
// Update progress tracking
|
|
234
|
+
progress.completedTasks = completedTodos.map(t => t.content);
|
|
235
|
+
progress.tasks = actualTodos.map(t => ({ content: t.content, status: t.status }));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Save progress
|
|
239
|
+
saveProgress(progress);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Run the hook
|
|
243
|
+
main().catch(err => {
|
|
244
|
+
// Silently fail - don't break Claude Code
|
|
245
|
+
if (process.env.DEBUG) {
|
|
246
|
+
console.error('github-progress hook error:', err);
|
|
247
|
+
}
|
|
248
|
+
});
|