claude-cli-advanced-starter-pack 1.8.2 → 1.8.4

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.
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Global Project Registry
3
+ * Tracks all projects configured with CCASP across the system
4
+ * Location: ~/.claude/ccasp-registry.json
5
+ */
6
+
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
8
+ import { join } from 'path';
9
+ import {
10
+ globalClaudePath,
11
+ getHomeDir,
12
+ toForwardSlashes,
13
+ pathsEqual,
14
+ getPathSegment
15
+ } from './paths.js';
16
+
17
+ /**
18
+ * Get the path to the global registry file
19
+ * @returns {string} Path to ~/.claude/ccasp-registry.json
20
+ */
21
+ export function getRegistryPath() {
22
+ return globalClaudePath('ccasp-registry.json');
23
+ }
24
+
25
+ /**
26
+ * Get the global .claude directory path
27
+ * @returns {string} Path to ~/.claude/
28
+ */
29
+ export function getGlobalClaudeDir() {
30
+ return join(getHomeDir(), '.claude');
31
+ }
32
+
33
+ /**
34
+ * Ensure the global .claude directory exists
35
+ */
36
+ function ensureGlobalDir() {
37
+ const globalDir = getGlobalClaudeDir();
38
+ if (!existsSync(globalDir)) {
39
+ mkdirSync(globalDir, { recursive: true });
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Load the global registry
45
+ * @returns {Object} Registry object with projects array and metadata
46
+ */
47
+ export function loadRegistry() {
48
+ const registryPath = getRegistryPath();
49
+
50
+ if (!existsSync(registryPath)) {
51
+ return {
52
+ version: '1.0.0',
53
+ projects: [],
54
+ lastModified: null
55
+ };
56
+ }
57
+
58
+ try {
59
+ const content = readFileSync(registryPath, 'utf-8');
60
+ return JSON.parse(content);
61
+ } catch (err) {
62
+ console.error(`Warning: Could not parse registry file: ${err.message}`);
63
+ return {
64
+ version: '1.0.0',
65
+ projects: [],
66
+ lastModified: null
67
+ };
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Save the global registry
73
+ * @param {Object} registry - Registry object to save
74
+ */
75
+ export function saveRegistry(registry) {
76
+ ensureGlobalDir();
77
+ const registryPath = getRegistryPath();
78
+
79
+ registry.lastModified = new Date().toISOString();
80
+
81
+ writeFileSync(registryPath, JSON.stringify(registry, null, 2), 'utf-8');
82
+ }
83
+
84
+ /**
85
+ * Register a project in the global registry
86
+ * @param {string} projectPath - Absolute path to the project
87
+ * @param {Object} metadata - Optional metadata (name, version, features)
88
+ * @returns {boolean} True if newly registered, false if already existed
89
+ */
90
+ export function registerProject(projectPath, metadata = {}) {
91
+ const registry = loadRegistry();
92
+
93
+ // Check if already registered (using cross-platform path comparison)
94
+ const existingIndex = registry.projects.findIndex(
95
+ p => pathsEqual(p.path, projectPath)
96
+ );
97
+
98
+ const projectEntry = {
99
+ path: projectPath,
100
+ name: metadata.name || getPathSegment(projectPath),
101
+ registeredAt: new Date().toISOString(),
102
+ lastInitAt: new Date().toISOString(),
103
+ ccaspVersion: metadata.version || 'unknown',
104
+ features: metadata.features || []
105
+ };
106
+
107
+ if (existingIndex >= 0) {
108
+ // Update existing entry
109
+ registry.projects[existingIndex] = {
110
+ ...registry.projects[existingIndex],
111
+ ...projectEntry,
112
+ registeredAt: registry.projects[existingIndex].registeredAt // Keep original registration date
113
+ };
114
+ saveRegistry(registry);
115
+ return false;
116
+ }
117
+
118
+ // Add new entry
119
+ registry.projects.push(projectEntry);
120
+ saveRegistry(registry);
121
+ return true;
122
+ }
123
+
124
+ /**
125
+ * Unregister a project from the global registry
126
+ * @param {string} projectPath - Absolute path to the project
127
+ * @returns {boolean} True if removed, false if not found
128
+ */
129
+ export function unregisterProject(projectPath) {
130
+ const registry = loadRegistry();
131
+
132
+ const initialLength = registry.projects.length;
133
+ registry.projects = registry.projects.filter(
134
+ p => !pathsEqual(p.path, projectPath)
135
+ );
136
+
137
+ if (registry.projects.length < initialLength) {
138
+ saveRegistry(registry);
139
+ return true;
140
+ }
141
+
142
+ return false;
143
+ }
144
+
145
+ /**
146
+ * Get all registered projects
147
+ * @param {Object} options - Filter options
148
+ * @param {boolean} options.existsOnly - Only return projects that still exist on disk
149
+ * @returns {Array} Array of project entries
150
+ */
151
+ export function getRegisteredProjects(options = {}) {
152
+ const registry = loadRegistry();
153
+ let projects = registry.projects;
154
+
155
+ if (options.existsOnly) {
156
+ projects = projects.filter(p => existsSync(p.path));
157
+ }
158
+
159
+ return projects;
160
+ }
161
+
162
+ /**
163
+ * Check if a project is registered
164
+ * @param {string} projectPath - Absolute path to the project
165
+ * @returns {boolean} True if registered
166
+ */
167
+ export function isProjectRegistered(projectPath) {
168
+ const registry = loadRegistry();
169
+
170
+ return registry.projects.some(
171
+ p => pathsEqual(p.path, projectPath)
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Get registry statistics
177
+ * @returns {Object} Stats about registered projects
178
+ */
179
+ export function getRegistryStats() {
180
+ const registry = loadRegistry();
181
+ const projects = registry.projects;
182
+
183
+ let existingCount = 0;
184
+ let missingCount = 0;
185
+
186
+ for (const project of projects) {
187
+ if (existsSync(project.path)) {
188
+ existingCount++;
189
+ } else {
190
+ missingCount++;
191
+ }
192
+ }
193
+
194
+ return {
195
+ total: projects.length,
196
+ existing: existingCount,
197
+ missing: missingCount,
198
+ lastModified: registry.lastModified
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Clean up registry by removing projects that no longer exist
204
+ * @returns {number} Number of projects removed
205
+ */
206
+ export function cleanupRegistry() {
207
+ const registry = loadRegistry();
208
+ const initialLength = registry.projects.length;
209
+
210
+ registry.projects = registry.projects.filter(p => existsSync(p.path));
211
+
212
+ const removed = initialLength - registry.projects.length;
213
+ if (removed > 0) {
214
+ saveRegistry(registry);
215
+ }
216
+
217
+ return removed;
218
+ }
219
+
220
+ /**
221
+ * Clear the entire registry
222
+ */
223
+ export function clearRegistry() {
224
+ const registry = {
225
+ version: '1.0.0',
226
+ projects: [],
227
+ lastModified: new Date().toISOString()
228
+ };
229
+ saveRegistry(registry);
230
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Cross-Platform Path Utilities
3
+ *
4
+ * Centralized path handling for Windows/macOS/Linux compatibility.
5
+ * All path operations should use these utilities to ensure consistency.
6
+ */
7
+
8
+ import { join, normalize, sep } from 'path';
9
+ import { homedir } from 'os';
10
+
11
+ /**
12
+ * Normalize a path to use forward slashes (for comparison and storage)
13
+ * @param {string} filePath - Path to normalize
14
+ * @returns {string} Path with forward slashes
15
+ */
16
+ export function toForwardSlashes(filePath) {
17
+ if (!filePath) return filePath;
18
+ return filePath.replace(/\\/g, '/');
19
+ }
20
+
21
+ /**
22
+ * Normalize a path to use the platform's native separator
23
+ * @param {string} filePath - Path to normalize
24
+ * @returns {string} Path with native separators
25
+ */
26
+ export function toNativePath(filePath) {
27
+ if (!filePath) return filePath;
28
+ return filePath.replace(/[/\\]/g, sep);
29
+ }
30
+
31
+ /**
32
+ * Compare two paths for equality (cross-platform safe)
33
+ * @param {string} path1 - First path
34
+ * @param {string} path2 - Second path
35
+ * @returns {boolean} True if paths are equivalent
36
+ */
37
+ export function pathsEqual(path1, path2) {
38
+ if (!path1 || !path2) return path1 === path2;
39
+ const norm1 = toForwardSlashes(path1).replace(/\/$/, '').toLowerCase();
40
+ const norm2 = toForwardSlashes(path2).replace(/\/$/, '').toLowerCase();
41
+ return norm1 === norm2;
42
+ }
43
+
44
+ /**
45
+ * Get the last segment of a path (cross-platform safe)
46
+ * @param {string} filePath - Path to extract from
47
+ * @returns {string} Last segment of the path
48
+ */
49
+ export function getPathSegment(filePath) {
50
+ if (!filePath) return '';
51
+ return filePath.split(/[/\\]/).pop() || '';
52
+ }
53
+
54
+ /**
55
+ * Build a .claude-relative path string for use in config/templates
56
+ * Always uses forward slashes for consistency in JSON/templates
57
+ * @param {...string} segments - Path segments after .claude/
58
+ * @returns {string} Path string like ".claude/hooks/file.js"
59
+ */
60
+ export function claudeRelativePath(...segments) {
61
+ return '.claude/' + segments.join('/');
62
+ }
63
+
64
+ /**
65
+ * Build an absolute path to a .claude directory item
66
+ * @param {string} baseDir - Project root directory
67
+ * @param {...string} segments - Path segments after .claude/
68
+ * @returns {string} Absolute path using native separators
69
+ */
70
+ export function claudeAbsolutePath(baseDir, ...segments) {
71
+ return join(baseDir, '.claude', ...segments);
72
+ }
73
+
74
+ /**
75
+ * Get the user's home directory (cross-platform)
76
+ * @returns {string} Home directory path
77
+ */
78
+ export function getHomeDir() {
79
+ return process.env.HOME || process.env.USERPROFILE || homedir();
80
+ }
81
+
82
+ /**
83
+ * Build a path relative to user's home directory
84
+ * @param {...string} segments - Path segments after home
85
+ * @returns {string} Absolute path using native separators
86
+ */
87
+ export function homeRelativePath(...segments) {
88
+ return join(getHomeDir(), ...segments);
89
+ }
90
+
91
+ /**
92
+ * Build a path to global .claude directory
93
+ * @param {...string} segments - Path segments after ~/.claude/
94
+ * @returns {string} Absolute path using native separators
95
+ */
96
+ export function globalClaudePath(...segments) {
97
+ return join(getHomeDir(), '.claude', ...segments);
98
+ }
99
+
100
+ /**
101
+ * Check if running on Windows
102
+ * @returns {boolean} True if Windows
103
+ */
104
+ export function isWindows() {
105
+ return process.platform === 'win32';
106
+ }
107
+
108
+ /**
109
+ * Get the appropriate command existence checker
110
+ * @returns {string} 'where' on Windows, 'which' on Unix
111
+ */
112
+ export function getWhichCommand() {
113
+ return isWindows() ? 'where' : 'which';
114
+ }
115
+
116
+ /**
117
+ * Normalize line endings to Unix style (LF)
118
+ * @param {string} content - Content to normalize
119
+ * @returns {string} Content with Unix line endings
120
+ */
121
+ export function normalizeLineEndings(content) {
122
+ if (!content) return content;
123
+ return content.replace(/\r\n/g, '\n');
124
+ }
125
+
126
+ /**
127
+ * Build a Node.js command that works cross-platform
128
+ * Uses forward slashes which Node.js handles on all platforms
129
+ * @param {string} scriptPath - Path to the script (relative to project root)
130
+ * @returns {string} Command string like "node .claude/hooks/script.js"
131
+ */
132
+ export function nodeCommand(scriptPath) {
133
+ // Node.js handles forward slashes on all platforms
134
+ const normalizedPath = toForwardSlashes(scriptPath);
135
+ return `node ${normalizedPath}`;
136
+ }
137
+
138
+ /**
139
+ * Ensure a path uses consistent separators for storage/comparison
140
+ * Stores with forward slashes, converts to native when needed for fs operations
141
+ * @param {string} filePath - Path to store
142
+ * @returns {string} Normalized path for storage
143
+ */
144
+ export function storagePath(filePath) {
145
+ return toForwardSlashes(filePath);
146
+ }
@@ -0,0 +1,216 @@
1
+ ---
2
+ description: Start working on a GitHub issue by number with full workflow
3
+ type: utility
4
+ complexity: simple
5
+ model: haiku
6
+ argument-hint: <issue-number>
7
+ allowed-tools:
8
+ - Bash
9
+ - AskUserQuestion
10
+ - Task
11
+ ---
12
+
13
+ # /create-task-list-for-issue - Quick Issue Start
14
+
15
+ **Start working on a GitHub issue by number. Confirms details, then runs full `/create-task-list` workflow.**
16
+
17
+ ---
18
+
19
+ ## USAGE
20
+
21
+ ```bash
22
+ /create-task-list-for-issue 123
23
+ /create-task-list-for-issue #45
24
+ /create-task-list-for-issue 7
25
+ ```
26
+
27
+ ---
28
+
29
+ ## EXECUTION
30
+
31
+ ### Step 1: Parse Issue Number
32
+
33
+ Extract issue number from `$ARGUMENTS`:
34
+ - Strip `#` prefix if present
35
+ - Validate it's a positive integer
36
+
37
+ ```
38
+ If no issue number provided:
39
+ → Use AskUserQuestion: "Which issue number would you like to work on?"
40
+ ```
41
+
42
+ ---
43
+
44
+ ### Step 2: Fetch Issue Details
45
+
46
+ ```bash
47
+ gh issue view [NUMBER] --json number,title,body,createdAt,labels,url
48
+ ```
49
+
50
+ ---
51
+
52
+ ### Step 3: Detect Issue Format
53
+
54
+ Check if the issue body contains BOTH of these indicators of `/github-task` format:
55
+ - `## Acceptance Criteria` OR `## Task Checklist` section
56
+ - `## Suggested Implementation` OR `## Implementation Approach` section
57
+
58
+ **Set `IS_TASK_READY`** = true if both indicators found, false otherwise.
59
+
60
+ ---
61
+
62
+ ### Step 4: Display Issue Confirmation
63
+
64
+ **If IS_TASK_READY = true:**
65
+
66
+ ```
67
+ ╔═══════════════════════════════════════════════════════════════╗
68
+ ║ Issue #[NUMBER] ✓ Task-Ready ║
69
+ ╠═══════════════════════════════════════════════════════════════╣
70
+ ║ ║
71
+ ║ [Issue Title - max 50 chars] ║
72
+ ║ ║
73
+ ║ Created: [MM/DD/YYYY] ║
74
+ ║ Labels: [label1, label2, ...] ║
75
+ ║ URL: [github url] ║
76
+ ║ ║
77
+ ║ Format: Has task checklist - will use existing tasks ║
78
+ ║ ║
79
+ ║ Tasks found: ║
80
+ ║ • [Task 1 from checklist] ║
81
+ ║ • [Task 2 from checklist] ║
82
+ ║ • ... ([N] total) ║
83
+ ║ ║
84
+ ╚═══════════════════════════════════════════════════════════════╝
85
+ ```
86
+
87
+ **If IS_TASK_READY = false:**
88
+
89
+ ```
90
+ ╔═══════════════════════════════════════════════════════════════╗
91
+ ║ Issue #[NUMBER] ⚠ Needs Analysis ║
92
+ ╠═══════════════════════════════════════════════════════════════╣
93
+ ║ ║
94
+ ║ [Issue Title - max 50 chars] ║
95
+ ║ ║
96
+ ║ Created: [MM/DD/YYYY] ║
97
+ ║ Labels: [label1, label2, ...] ║
98
+ ║ URL: [github url] ║
99
+ ║ ║
100
+ ║ Format: No task checklist - will generate via exploration ║
101
+ ║ ║
102
+ ║ Description preview: ║
103
+ ║ [First 100 chars of issue body...] ║
104
+ ║ ║
105
+ ╚═══════════════════════════════════════════════════════════════╝
106
+ ```
107
+
108
+ ---
109
+
110
+ ### Step 5: Confirm and Proceed
111
+
112
+ ```
113
+ header: "Start"
114
+ question: "Start working on this issue?"
115
+ options:
116
+ - label: "Y - Yes, proceed"
117
+ description: "Run full /create-task-list workflow"
118
+ - label: "V - View full details"
119
+ description: "Show complete issue body first"
120
+ - label: "N - Cancel"
121
+ description: "Return without starting"
122
+ ```
123
+
124
+ **If user selects V (View):**
125
+ - Run `gh issue view [NUMBER]` and display full body
126
+ - Then re-ask the confirmation question (Y/N only)
127
+
128
+ **If user selects N (Cancel):**
129
+ - Exit command
130
+
131
+ **If user selects Y (Yes):**
132
+ - Proceed to Step 6
133
+
134
+ ---
135
+
136
+ ### Step 6: Run Full /create-task-list Workflow
137
+
138
+ **If IS_TASK_READY = true:**
139
+
140
+ Display:
141
+ ```
142
+ ╔═══════════════════════════════════════════════════════════════╗
143
+ ║ 📋 Starting Issue #[NUMBER] (Task-Ready) ║
144
+ ╠═══════════════════════════════════════════════════════════════╣
145
+ ║ Using existing task checklist from issue ║
146
+ ║ Running full /create-task-list workflow for: ║
147
+ ║ • Codebase exploration & context ║
148
+ ║ • Testing configuration (Ralph loop, E2E) ║
149
+ ║ • Workflow setup (branch, board sync) ║
150
+ ╚═══════════════════════════════════════════════════════════════╝
151
+ ```
152
+
153
+ Run: `/create-task-list for issue #[NUMBER] --use-existing-tasks`
154
+
155
+ ---
156
+
157
+ **If IS_TASK_READY = false:**
158
+
159
+ Display:
160
+ ```
161
+ ╔═══════════════════════════════════════════════════════════════╗
162
+ ║ 📋 Starting Issue #[NUMBER] (Needs Analysis) ║
163
+ ╠═══════════════════════════════════════════════════════════════╣
164
+ ║ Running full /create-task-list workflow for: ║
165
+ ║ • Task generation via codebase exploration ║
166
+ ║ • Testing configuration (Ralph loop, E2E) ║
167
+ ║ • Workflow setup (branch, board sync) ║
168
+ ║ • Option to update issue with generated tasks ║
169
+ ╚═══════════════════════════════════════════════════════════════╝
170
+ ```
171
+
172
+ Run: `/create-task-list for issue #[NUMBER]`
173
+
174
+ After `/create-task-list` completes and generates tasks, ask:
175
+
176
+ ```
177
+ header: "Update"
178
+ question: "Update GitHub issue with generated task list?"
179
+ options:
180
+ - label: "Y - Yes, update issue"
181
+ description: "Add task checklist to issue body"
182
+ - label: "N - No, just implement"
183
+ description: "Keep issue as-is, start work"
184
+ ```
185
+
186
+ If user selects Y:
187
+ ```bash
188
+ # Append task checklist to issue body
189
+ gh issue edit [NUMBER] --body "$(gh issue view [NUMBER] --json body -q .body)
190
+
191
+ ## Task Checklist (Auto-generated)
192
+
193
+ - [ ] Task 1
194
+ - [ ] Task 2
195
+ ..."
196
+ ```
197
+
198
+ ---
199
+
200
+ ## ERROR HANDLING
201
+
202
+ | Error | Action |
203
+ |-------|--------|
204
+ | Issue not found | Display "Issue #[N] not found" and exit |
205
+ | gh not authenticated | Show `gh auth login` instructions |
206
+ | Invalid issue number | Ask user to provide valid number |
207
+ | Network error | Show retry option |
208
+
209
+ ---
210
+
211
+ ## RELATED COMMANDS
212
+
213
+ - `/menu-issues-list` - Browse all open issues with menu
214
+ - `/create-task-list` - Create task list from any prompt
215
+ - `/github-task-start` - Start working on issue (simpler flow)
216
+ - `/github-update` - Sync with project board