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.
- 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 +309 -80
- 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 +209 -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/context-injector.template.js +261 -0
- package/templates/hooks/git-commit-tracker.template.js +267 -0
- package/templates/hooks/happy-mode-detector.template.js +214 -0
- package/templates/hooks/happy-title-generator.template.js +260 -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-budget-loader.template.js +234 -0
- package/templates/hooks/token-usage-monitor.template.js +193 -0
- package/templates/hooks/tool-output-cacher.template.js +219 -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,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
|
+
};
|