claude-cli-advanced-starter-pack 1.0.16 → 1.8.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 (61) hide show
  1. package/OVERVIEW.md +5 -1
  2. package/README.md +241 -132
  3. package/bin/gtask.js +53 -0
  4. package/package.json +1 -1
  5. package/src/cli/menu.js +27 -0
  6. package/src/commands/explore-mcp/mcp-registry.js +99 -0
  7. package/src/commands/init.js +309 -80
  8. package/src/commands/install-panel-hook.js +108 -0
  9. package/src/commands/install-scripts.js +232 -0
  10. package/src/commands/install-skill.js +220 -0
  11. package/src/commands/panel.js +297 -0
  12. package/src/commands/setup-wizard.js +4 -3
  13. package/src/commands/test-setup.js +4 -5
  14. package/src/data/releases.json +209 -0
  15. package/src/panel/queue.js +188 -0
  16. package/templates/commands/ask-claude.template.md +118 -0
  17. package/templates/commands/ccasp-panel.template.md +72 -0
  18. package/templates/commands/ccasp-setup.template.md +470 -79
  19. package/templates/commands/create-smoke-test.template.md +186 -0
  20. package/templates/commands/project-impl.template.md +9 -113
  21. package/templates/commands/refactor-check.template.md +112 -0
  22. package/templates/commands/refactor-cleanup.template.md +144 -0
  23. package/templates/commands/refactor-prep.template.md +192 -0
  24. package/templates/docs/AI_ARCHITECTURE_CONSTITUTION.template.md +198 -0
  25. package/templates/docs/DETAILED_GOTCHAS.template.md +347 -0
  26. package/templates/docs/PHASE-DEV-CHECKLIST.template.md +241 -0
  27. package/templates/docs/PROGRESS_JSON_TEMPLATE.json +117 -0
  28. package/templates/docs/background-agent.template.md +264 -0
  29. package/templates/hooks/autonomous-decision-logger.template.js +207 -0
  30. package/templates/hooks/branch-merge-checker.template.js +272 -0
  31. package/templates/hooks/context-injector.template.js +261 -0
  32. package/templates/hooks/git-commit-tracker.template.js +267 -0
  33. package/templates/hooks/happy-mode-detector.template.js +214 -0
  34. package/templates/hooks/happy-title-generator.template.js +260 -0
  35. package/templates/hooks/issue-completion-detector.template.js +205 -0
  36. package/templates/hooks/panel-queue-reader.template.js +83 -0
  37. package/templates/hooks/phase-validation-gates.template.js +307 -0
  38. package/templates/hooks/session-id-generator.template.js +236 -0
  39. package/templates/hooks/token-budget-loader.template.js +234 -0
  40. package/templates/hooks/token-usage-monitor.template.js +193 -0
  41. package/templates/hooks/tool-output-cacher.template.js +219 -0
  42. package/templates/patterns/README.md +129 -0
  43. package/templates/patterns/l1-l2-orchestration.md +189 -0
  44. package/templates/patterns/multi-phase-orchestration.md +258 -0
  45. package/templates/patterns/two-tier-query-pipeline.md +192 -0
  46. package/templates/scripts/README.md +109 -0
  47. package/templates/scripts/analyze-delegation-log.js +299 -0
  48. package/templates/scripts/autonomous-decision-logger.js +277 -0
  49. package/templates/scripts/git-history-analyzer.py +269 -0
  50. package/templates/scripts/phase-validation-gates.js +307 -0
  51. package/templates/scripts/poll-deployment-status.js +260 -0
  52. package/templates/scripts/roadmap-scanner.js +263 -0
  53. package/templates/scripts/validate-deployment.js +293 -0
  54. package/templates/skills/agent-creator/skill.json +18 -0
  55. package/templates/skills/agent-creator/skill.md +335 -0
  56. package/templates/skills/hook-creator/skill.json +18 -0
  57. package/templates/skills/hook-creator/skill.md +318 -0
  58. package/templates/skills/panel/skill.json +18 -0
  59. package/templates/skills/panel/skill.md +90 -0
  60. package/templates/skills/rag-agent-creator/skill.json +18 -0
  61. package/templates/skills/rag-agent-creator/skill.md +307 -0
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Happy Mode Detector Hook
3
+ *
4
+ * Auto-detects Happy daemon environment and enables mobile-optimized mode.
5
+ * Sets appropriate verbosity and response formatting for mobile clients.
6
+ * Supports multiple detection methods: env var, daemon state, manual config.
7
+ *
8
+ * Event: UserPromptSubmit (runs once per session)
9
+ *
10
+ * Configuration: Reads from .claude/config/hooks-config.json
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ // Default configuration (can be overridden by hooks-config.json)
17
+ const DEFAULT_CONFIG = {
18
+ enabled: false, // Manual override
19
+ auto_detect: true, // Attempt auto-detection
20
+ verbosity: 'condensed', // 'verbose', 'condensed', 'compact'
21
+ show_file_stats: true, // Show file operation statistics
22
+ max_grep_results: 5, // Limit grep results for mobile
23
+ checkpoint_interval_minutes: 10, // Auto-checkpoint frequency
24
+ };
25
+
26
+ // Paths
27
+ const CONFIG_PATH = path.join(process.cwd(), '.claude', 'config', 'hooks-config.json');
28
+ const HAPPY_MODE_PATH = path.join(process.cwd(), '.claude', 'config', 'happy-mode.json');
29
+ const HAPPY_STATE_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.happy', 'daemon.state.json');
30
+ const SESSION_MARKER = path.join(process.cwd(), '.claude', 'config', '.happy-detected');
31
+
32
+ /**
33
+ * Load configuration with defaults
34
+ */
35
+ function loadConfig() {
36
+ try {
37
+ if (fs.existsSync(CONFIG_PATH)) {
38
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
39
+ return { ...DEFAULT_CONFIG, ...(config.happy_mode || {}) };
40
+ }
41
+ } catch (e) {
42
+ // Use defaults on error
43
+ }
44
+ return DEFAULT_CONFIG;
45
+ }
46
+
47
+ /**
48
+ * Check if we've already detected Happy mode this session
49
+ */
50
+ function hasDetectedThisSession() {
51
+ try {
52
+ if (fs.existsSync(SESSION_MARKER)) {
53
+ const content = fs.readFileSync(SESSION_MARKER, 'utf8');
54
+ const data = JSON.parse(content);
55
+ // Session valid for 4 hours
56
+ if (Date.now() - data.timestamp < 4 * 60 * 60 * 1000) {
57
+ return data;
58
+ }
59
+ }
60
+ } catch (e) {
61
+ // Continue with detection
62
+ }
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * Mark session as detected
68
+ */
69
+ function markSessionDetected(result) {
70
+ try {
71
+ const dir = path.dirname(SESSION_MARKER);
72
+ if (!fs.existsSync(dir)) {
73
+ fs.mkdirSync(dir, { recursive: true });
74
+ }
75
+ fs.writeFileSync(SESSION_MARKER, JSON.stringify({
76
+ timestamp: Date.now(),
77
+ ...result,
78
+ }, null, 2), 'utf8');
79
+ } catch (e) {
80
+ // Silent failure
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Detect Happy mode via environment variable
86
+ */
87
+ function detectViaEnvVar() {
88
+ return process.env.HAPPY_SESSION === 'true';
89
+ }
90
+
91
+ /**
92
+ * Detect Happy mode via daemon state file
93
+ */
94
+ function detectViaDaemonState() {
95
+ try {
96
+ if (fs.existsSync(HAPPY_STATE_PATH)) {
97
+ const state = JSON.parse(fs.readFileSync(HAPPY_STATE_PATH, 'utf8'));
98
+ // Check if daemon is running and has active session
99
+ if (state.running && state.current_session) {
100
+ return true;
101
+ }
102
+ }
103
+ } catch (e) {
104
+ // Daemon not running or state invalid
105
+ }
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * Detect Happy mode via manual configuration
111
+ */
112
+ function detectViaManualConfig() {
113
+ try {
114
+ if (fs.existsSync(HAPPY_MODE_PATH)) {
115
+ const config = JSON.parse(fs.readFileSync(HAPPY_MODE_PATH, 'utf8'));
116
+ return config.enabled === true;
117
+ }
118
+ } catch (e) {
119
+ // No manual config
120
+ }
121
+ return false;
122
+ }
123
+
124
+ /**
125
+ * Get detection method description
126
+ */
127
+ function getDetectionMethod(envVar, daemon, manual) {
128
+ if (manual) return 'manual_config';
129
+ if (envVar) return 'env_var';
130
+ if (daemon) return 'daemon_state';
131
+ return 'none';
132
+ }
133
+
134
+ /**
135
+ * Save Happy mode state for other hooks to use
136
+ */
137
+ function saveHappyModeState(result) {
138
+ try {
139
+ const dir = path.dirname(HAPPY_MODE_PATH);
140
+ if (!fs.existsSync(dir)) {
141
+ fs.mkdirSync(dir, { recursive: true });
142
+ }
143
+ fs.writeFileSync(HAPPY_MODE_PATH, JSON.stringify(result, null, 2), 'utf8');
144
+ } catch (e) {
145
+ // Silent failure
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Main hook handler
151
+ */
152
+ module.exports = async function happyModeDetector(context) {
153
+ // Always continue - never block
154
+ const approve = () => ({ continue: true });
155
+
156
+ try {
157
+ // Check if already detected this session
158
+ const cached = hasDetectedThisSession();
159
+ if (cached) {
160
+ return approve();
161
+ }
162
+
163
+ const config = loadConfig();
164
+
165
+ // Skip detection if auto-detect is disabled
166
+ if (!config.auto_detect) {
167
+ const result = {
168
+ happy_mode: config.enabled,
169
+ detection_method: config.enabled ? 'manual_override' : 'disabled',
170
+ config: config,
171
+ };
172
+ markSessionDetected(result);
173
+ return approve();
174
+ }
175
+
176
+ // Try all detection methods
177
+ const viaEnvVar = detectViaEnvVar();
178
+ const viaDaemon = detectViaDaemonState();
179
+ const viaManual = detectViaManualConfig();
180
+
181
+ const isHappyMode = viaEnvVar || viaDaemon || viaManual;
182
+ const detectionMethod = getDetectionMethod(viaEnvVar, viaDaemon, viaManual);
183
+
184
+ // Build result object
185
+ const result = {
186
+ happy_mode: isHappyMode,
187
+ detection_method: detectionMethod,
188
+ detected_at: new Date().toISOString(),
189
+ config: {
190
+ verbosity: config.verbosity,
191
+ show_file_stats: config.show_file_stats,
192
+ max_grep_results: config.max_grep_results,
193
+ checkpoint_interval_minutes: config.checkpoint_interval_minutes,
194
+ },
195
+ };
196
+
197
+ // Save state for other hooks
198
+ saveHappyModeState(result);
199
+ markSessionDetected(result);
200
+
201
+ // Log detection result
202
+ if (isHappyMode) {
203
+ console.log(`[happy-mode-detector] Happy mode ENABLED (via ${detectionMethod})`);
204
+ console.log(`[happy-mode-detector] Verbosity: ${config.verbosity}`);
205
+ } else {
206
+ console.log('[happy-mode-detector] Happy mode not detected');
207
+ }
208
+
209
+ return approve();
210
+ } catch (error) {
211
+ console.error(`[happy-mode-detector] Error: ${error.message}`);
212
+ return approve();
213
+ }
214
+ };
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Happy Title Generator Hook
3
+ *
4
+ * Auto-generates session titles with format: "Issue #XX - Summary"
5
+ * Detects GitHub issue numbers from branch names.
6
+ * Notifies Happy daemon of title updates for mobile display.
7
+ *
8
+ * Event: UserPromptSubmit
9
+ *
10
+ * Configuration: Reads from .claude/config/hooks-config.json
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { execSync } = require('child_process');
16
+
17
+ // Default configuration (can be overridden by hooks-config.json)
18
+ const DEFAULT_CONFIG = {
19
+ rate_limit_ms: 30000, // Throttle title updates to once per 30 sec
20
+ max_summary_words: 4, // Maximum words in title summary
21
+ issue_pattern: 'issue-(\\d+)', // Regex to extract issue number from branch
22
+ common_prefixes: [ // Prefixes to remove from summaries
23
+ 'implement', 'add', 'fix', 'update', 'create',
24
+ 'refactor', 'build', 'remove', 'optimize', 'improve'
25
+ ],
26
+ };
27
+
28
+ // Paths
29
+ const CONFIG_PATH = path.join(process.cwd(), '.claude', 'config', 'hooks-config.json');
30
+ const STATE_PATH = path.join(process.cwd(), '.claude', 'config', 'title-generator-state.json');
31
+ const HAPPY_STATE_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.happy', 'daemon.state.json');
32
+
33
+ /**
34
+ * Load configuration with defaults
35
+ */
36
+ function loadConfig() {
37
+ try {
38
+ if (fs.existsSync(CONFIG_PATH)) {
39
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
40
+ return { ...DEFAULT_CONFIG, ...(config.title_generator || {}) };
41
+ }
42
+ } catch (e) {
43
+ // Use defaults on error
44
+ }
45
+ return DEFAULT_CONFIG;
46
+ }
47
+
48
+ /**
49
+ * Load state
50
+ */
51
+ function loadState() {
52
+ try {
53
+ if (fs.existsSync(STATE_PATH)) {
54
+ return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8'));
55
+ }
56
+ } catch (e) {
57
+ // Return fresh state
58
+ }
59
+ return {
60
+ last_update: 0,
61
+ current_title: null,
62
+ issue_number: null,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Save state
68
+ */
69
+ function saveState(state) {
70
+ try {
71
+ const dir = path.dirname(STATE_PATH);
72
+ if (!fs.existsSync(dir)) {
73
+ fs.mkdirSync(dir, { recursive: true });
74
+ }
75
+ fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), 'utf8');
76
+ } catch (e) {
77
+ // Silent failure
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get current git branch
83
+ */
84
+ function getCurrentBranch() {
85
+ try {
86
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
87
+ encoding: 'utf8',
88
+ stdio: ['pipe', 'pipe', 'pipe'],
89
+ timeout: 5000,
90
+ }).trim();
91
+ return branch;
92
+ } catch (e) {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Extract issue number from branch name
99
+ */
100
+ function extractIssueNumber(branch, config) {
101
+ if (!branch) return null;
102
+
103
+ try {
104
+ const pattern = new RegExp(config.issue_pattern, 'i');
105
+ const match = branch.match(pattern);
106
+ if (match && match[1]) {
107
+ return parseInt(match[1], 10);
108
+ }
109
+ } catch (e) {
110
+ // Invalid regex pattern
111
+ }
112
+
113
+ // Try common patterns as fallback
114
+ const fallbackPatterns = [
115
+ /issue-(\d+)/i,
116
+ /(\d+)-/,
117
+ /#(\d+)/,
118
+ ];
119
+
120
+ for (const pattern of fallbackPatterns) {
121
+ const match = branch.match(pattern);
122
+ if (match && match[1]) {
123
+ return parseInt(match[1], 10);
124
+ }
125
+ }
126
+
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Generate summary from user prompt
132
+ */
133
+ function generateSummary(prompt, config) {
134
+ if (!prompt) return 'New Session';
135
+
136
+ // Clean the prompt
137
+ let summary = prompt.trim();
138
+
139
+ // Remove common prefixes
140
+ const prefixPattern = new RegExp(`^(${config.common_prefixes.join('|')})\\s+`, 'i');
141
+ summary = summary.replace(prefixPattern, '');
142
+
143
+ // Take first N words
144
+ const words = summary.split(/\s+/).slice(0, config.max_summary_words);
145
+ summary = words.join(' ');
146
+
147
+ // Capitalize first letter
148
+ if (summary.length > 0) {
149
+ summary = summary.charAt(0).toUpperCase() + summary.slice(1);
150
+ }
151
+
152
+ // Truncate if too long
153
+ if (summary.length > 50) {
154
+ summary = summary.substring(0, 47) + '...';
155
+ }
156
+
157
+ return summary || 'New Session';
158
+ }
159
+
160
+ /**
161
+ * Format title with issue number
162
+ */
163
+ function formatTitle(issueNumber, summary, branch) {
164
+ let title = '';
165
+
166
+ if (issueNumber) {
167
+ title = `Issue #${issueNumber} - ${summary}`;
168
+ } else {
169
+ title = summary;
170
+ }
171
+
172
+ // Add branch info if available
173
+ if (branch && branch !== 'main' && branch !== 'master') {
174
+ const shortBranch = branch.length > 20 ? branch.substring(0, 17) + '...' : branch;
175
+ title = `${title} | ${shortBranch}`;
176
+ }
177
+
178
+ return title;
179
+ }
180
+
181
+ /**
182
+ * Notify Happy daemon of title update
183
+ */
184
+ function notifyHappyDaemon(title) {
185
+ try {
186
+ if (!fs.existsSync(HAPPY_STATE_PATH)) {
187
+ return false; // Happy daemon not running
188
+ }
189
+
190
+ const happyState = JSON.parse(fs.readFileSync(HAPPY_STATE_PATH, 'utf8'));
191
+
192
+ // Update session title in daemon state
193
+ happyState.current_session = happyState.current_session || {};
194
+ happyState.current_session.title = title;
195
+ happyState.current_session.updated_at = new Date().toISOString();
196
+
197
+ // Write atomically using temp file
198
+ const tempPath = HAPPY_STATE_PATH + '.tmp';
199
+ fs.writeFileSync(tempPath, JSON.stringify(happyState, null, 2), 'utf8');
200
+ fs.renameSync(tempPath, HAPPY_STATE_PATH);
201
+
202
+ return true;
203
+ } catch (e) {
204
+ return false; // Silent failure - daemon may not be running
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Main hook handler
210
+ */
211
+ module.exports = async function happyTitleGenerator(context) {
212
+ // Always continue - never block
213
+ const approve = () => ({ continue: true });
214
+
215
+ try {
216
+ const config = loadConfig();
217
+ const state = loadState();
218
+ const now = Date.now();
219
+
220
+ // Rate limiting - don't update too frequently
221
+ if (state.last_update && (now - state.last_update) < config.rate_limit_ms) {
222
+ return approve();
223
+ }
224
+
225
+ // Parse hook input for user prompt
226
+ let userPrompt = '';
227
+ try {
228
+ const input = JSON.parse(process.env.CLAUDE_HOOK_INPUT || '{}');
229
+ userPrompt = input.prompt || input.message || '';
230
+ } catch (e) {
231
+ // No prompt available
232
+ }
233
+
234
+ // Get git context
235
+ const branch = getCurrentBranch();
236
+ const issueNumber = extractIssueNumber(branch, config);
237
+
238
+ // Generate title components
239
+ const summary = generateSummary(userPrompt, config);
240
+ const title = formatTitle(issueNumber, summary, branch);
241
+
242
+ // Update state
243
+ state.last_update = now;
244
+ state.current_title = title;
245
+ state.issue_number = issueNumber;
246
+ saveState(state);
247
+
248
+ // Notify Happy daemon (if running)
249
+ const notified = notifyHappyDaemon(title);
250
+
251
+ if (notified) {
252
+ console.log(`[happy-title-generator] Title updated: ${title}`);
253
+ }
254
+
255
+ return approve();
256
+ } catch (error) {
257
+ console.error(`[happy-title-generator] Error: ${error.message}`);
258
+ return approve();
259
+ }
260
+ };
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Issue Completion Detector Hook
3
+ *
4
+ * Detects natural language indicators of task/issue completion.
5
+ * Auto-triggers deployment pipeline when completion phrases are detected.
6
+ * Integrates with GitHub to update issue status.
7
+ *
8
+ * Event: UserPromptSubmit
9
+ *
10
+ * Configuration: Reads from .claude/config/hooks-config.json
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { execSync } = require('child_process');
16
+
17
+ // Default configuration
18
+ const DEFAULT_CONFIG = {
19
+ enabled: true,
20
+ auto_deploy: false, // Auto-trigger deployment on completion
21
+ update_github: true, // Update GitHub issue status
22
+ completion_phrases: [
23
+ 'task complete',
24
+ 'task completed',
25
+ 'issue resolved',
26
+ 'issue fixed',
27
+ 'done with this',
28
+ 'finished implementing',
29
+ 'ready for review',
30
+ 'ready to merge',
31
+ 'all tests pass',
32
+ 'implementation complete',
33
+ ],
34
+ deploy_command: '/deploy-full', // Command to trigger on completion
35
+ };
36
+
37
+ // Paths
38
+ const CONFIG_PATH = path.join(process.cwd(), '.claude', 'config', 'hooks-config.json');
39
+ const STATE_PATH = path.join(process.cwd(), '.claude', 'config', 'completion-detector-state.json');
40
+
41
+ /**
42
+ * Load configuration with defaults
43
+ */
44
+ function loadConfig() {
45
+ try {
46
+ if (fs.existsSync(CONFIG_PATH)) {
47
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
48
+ return { ...DEFAULT_CONFIG, ...(config.issue_completion || {}) };
49
+ }
50
+ } catch (e) {
51
+ // Use defaults
52
+ }
53
+ return DEFAULT_CONFIG;
54
+ }
55
+
56
+ /**
57
+ * Load state
58
+ */
59
+ function loadState() {
60
+ try {
61
+ if (fs.existsSync(STATE_PATH)) {
62
+ return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8'));
63
+ }
64
+ } catch (e) {
65
+ // Fresh state
66
+ }
67
+ return {
68
+ last_detection: null,
69
+ completions_detected: 0,
70
+ deployments_triggered: 0,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Save state
76
+ */
77
+ function saveState(state) {
78
+ try {
79
+ const dir = path.dirname(STATE_PATH);
80
+ if (!fs.existsSync(dir)) {
81
+ fs.mkdirSync(dir, { recursive: true });
82
+ }
83
+ fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), 'utf8');
84
+ } catch (e) {
85
+ // Silent
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Check if message contains completion phrase
91
+ */
92
+ function detectCompletion(message, config) {
93
+ if (!message) return null;
94
+
95
+ const lowerMessage = message.toLowerCase();
96
+
97
+ for (const phrase of config.completion_phrases) {
98
+ if (lowerMessage.includes(phrase.toLowerCase())) {
99
+ return phrase;
100
+ }
101
+ }
102
+
103
+ return null;
104
+ }
105
+
106
+ /**
107
+ * Get current issue number from branch
108
+ */
109
+ function getCurrentIssue() {
110
+ try {
111
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
112
+ encoding: 'utf8',
113
+ stdio: ['pipe', 'pipe', 'pipe'],
114
+ }).trim();
115
+
116
+ const match = branch.match(/issue-(\d+)/i) || branch.match(/(\d+)-/);
117
+ if (match) {
118
+ return parseInt(match[1], 10);
119
+ }
120
+ } catch (e) {
121
+ // Not in git repo or error
122
+ }
123
+ return null;
124
+ }
125
+
126
+ /**
127
+ * Update GitHub issue (if gh CLI available)
128
+ */
129
+ function updateGitHubIssue(issueNumber, message) {
130
+ try {
131
+ // Add comment about completion
132
+ execSync(`gh issue comment ${issueNumber} --body "Task marked as complete via Claude CLI"`, {
133
+ encoding: 'utf8',
134
+ stdio: ['pipe', 'pipe', 'pipe'],
135
+ timeout: 10000,
136
+ });
137
+
138
+ console.log(`[issue-completion-detector] Updated GitHub issue #${issueNumber}`);
139
+ return true;
140
+ } catch (e) {
141
+ console.log(`[issue-completion-detector] Could not update GitHub issue: ${e.message}`);
142
+ return false;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Main hook handler
148
+ */
149
+ module.exports = async function issueCompletionDetector(context) {
150
+ const approve = () => ({ continue: true });
151
+
152
+ try {
153
+ const config = loadConfig();
154
+
155
+ if (!config.enabled) {
156
+ return approve();
157
+ }
158
+
159
+ // Parse hook input
160
+ let userMessage = '';
161
+ try {
162
+ const input = JSON.parse(process.env.CLAUDE_HOOK_INPUT || '{}');
163
+ userMessage = input.prompt || input.message || '';
164
+ } catch (e) {
165
+ return approve();
166
+ }
167
+
168
+ // Check for completion phrase
169
+ const detectedPhrase = detectCompletion(userMessage, config);
170
+
171
+ if (!detectedPhrase) {
172
+ return approve();
173
+ }
174
+
175
+ // Completion detected!
176
+ console.log(`[issue-completion-detector] Completion detected: "${detectedPhrase}"`);
177
+
178
+ const state = loadState();
179
+ state.last_detection = new Date().toISOString();
180
+ state.completions_detected++;
181
+
182
+ // Get current issue
183
+ const issueNumber = getCurrentIssue();
184
+
185
+ // Update GitHub if configured
186
+ if (config.update_github && issueNumber) {
187
+ updateGitHubIssue(issueNumber, userMessage);
188
+ }
189
+
190
+ // Log deployment suggestion
191
+ if (config.auto_deploy) {
192
+ console.log(`[issue-completion-detector] Auto-deploy enabled. Suggested: ${config.deploy_command}`);
193
+ state.deployments_triggered++;
194
+ } else {
195
+ console.log(`[issue-completion-detector] Consider running: ${config.deploy_command}`);
196
+ }
197
+
198
+ saveState(state);
199
+
200
+ return approve();
201
+ } catch (error) {
202
+ console.error(`[issue-completion-detector] Error: ${error.message}`);
203
+ return approve();
204
+ }
205
+ };