claude-cli-advanced-starter-pack 1.0.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/LICENSE +21 -0
- package/OVERVIEW.md +597 -0
- package/README.md +439 -0
- package/bin/gtask.js +282 -0
- package/bin/postinstall.js +53 -0
- package/package.json +69 -0
- package/src/agents/phase-dev-templates.js +1011 -0
- package/src/agents/templates.js +668 -0
- package/src/analysis/checklist-parser.js +414 -0
- package/src/analysis/codebase.js +481 -0
- package/src/cli/menu.js +958 -0
- package/src/commands/claude-audit.js +1482 -0
- package/src/commands/claude-settings.js +2243 -0
- package/src/commands/create-agent.js +681 -0
- package/src/commands/create-command.js +337 -0
- package/src/commands/create-hook.js +262 -0
- package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
- package/src/commands/create-phase-dev/documentation-generator.js +352 -0
- package/src/commands/create-phase-dev/post-completion.js +404 -0
- package/src/commands/create-phase-dev/scale-calculator.js +344 -0
- package/src/commands/create-phase-dev/wizard.js +492 -0
- package/src/commands/create-phase-dev.js +481 -0
- package/src/commands/create-skill.js +313 -0
- package/src/commands/create.js +446 -0
- package/src/commands/decompose.js +392 -0
- package/src/commands/detect-tech-stack.js +768 -0
- package/src/commands/explore-mcp/claude-md-updater.js +252 -0
- package/src/commands/explore-mcp/mcp-installer.js +346 -0
- package/src/commands/explore-mcp/mcp-registry.js +438 -0
- package/src/commands/explore-mcp.js +638 -0
- package/src/commands/gtask-init.js +641 -0
- package/src/commands/help.js +128 -0
- package/src/commands/init.js +1890 -0
- package/src/commands/install.js +250 -0
- package/src/commands/list.js +116 -0
- package/src/commands/roadmap.js +750 -0
- package/src/commands/setup-wizard.js +482 -0
- package/src/commands/setup.js +351 -0
- package/src/commands/sync.js +534 -0
- package/src/commands/test-run.js +456 -0
- package/src/commands/test-setup.js +456 -0
- package/src/commands/validate.js +67 -0
- package/src/config/tech-stack.defaults.json +182 -0
- package/src/config/tech-stack.schema.json +502 -0
- package/src/github/client.js +359 -0
- package/src/index.js +84 -0
- package/src/templates/claude-command.js +244 -0
- package/src/templates/issue-body.js +284 -0
- package/src/testing/config.js +411 -0
- package/src/utils/template-engine.js +398 -0
- package/src/utils/validate-templates.js +223 -0
- package/src/utils.js +396 -0
- package/templates/commands/ccasp-setup.template.md +113 -0
- package/templates/commands/context-audit.template.md +97 -0
- package/templates/commands/create-task-list.template.md +382 -0
- package/templates/commands/deploy-full.template.md +261 -0
- package/templates/commands/github-task-start.template.md +99 -0
- package/templates/commands/github-update.template.md +69 -0
- package/templates/commands/happy-start.template.md +117 -0
- package/templates/commands/phase-track.template.md +142 -0
- package/templates/commands/tunnel-start.template.md +127 -0
- package/templates/commands/tunnel-stop.template.md +106 -0
- package/templates/hooks/context-guardian.template.js +173 -0
- package/templates/hooks/deployment-orchestrator.template.js +219 -0
- package/templates/hooks/github-progress-hook.template.js +197 -0
- package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
- package/templates/hooks/phase-dev-enforcer.template.js +183 -0
|
@@ -0,0 +1,2243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Settings Command
|
|
3
|
+
*
|
|
4
|
+
* Configure Claude Code CLI settings including:
|
|
5
|
+
* - Bypass All Permissions mode
|
|
6
|
+
* - Agent Only mode shortcuts
|
|
7
|
+
* - Permission rules
|
|
8
|
+
* - MCP server configuration
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import inquirer from 'inquirer';
|
|
13
|
+
import ora from 'ora';
|
|
14
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
15
|
+
import { join, dirname } from 'path';
|
|
16
|
+
import { showHeader, showSuccess, showError, showWarning, showInfo } from '../cli/menu.js';
|
|
17
|
+
import { detectTechStack, runDetection } from './detect-tech-stack.js';
|
|
18
|
+
import { processDirectory, validateTechStack, extractPlaceholders } from '../utils/template-engine.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Default GitHub settings schema
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_GITHUB_SETTINGS = {
|
|
24
|
+
repository: {
|
|
25
|
+
owner: '',
|
|
26
|
+
name: '',
|
|
27
|
+
},
|
|
28
|
+
project: {
|
|
29
|
+
number: null,
|
|
30
|
+
id: '',
|
|
31
|
+
},
|
|
32
|
+
defaults: {
|
|
33
|
+
branch: 'main',
|
|
34
|
+
createFeatureBranches: false,
|
|
35
|
+
useWorktreesForPhaseDev: true,
|
|
36
|
+
labels: ['feature'],
|
|
37
|
+
priority: 'P2-Medium',
|
|
38
|
+
qa: 'Not Required',
|
|
39
|
+
agent: 'general-purpose',
|
|
40
|
+
},
|
|
41
|
+
workflow: {
|
|
42
|
+
autoFixBlankFields: true,
|
|
43
|
+
autoReorderOnCreate: false,
|
|
44
|
+
generateTestsByDefault: false,
|
|
45
|
+
taskListsAsPRs: false,
|
|
46
|
+
autoDeployOnComplete: true,
|
|
47
|
+
},
|
|
48
|
+
pr: {
|
|
49
|
+
baseBranch: 'main',
|
|
50
|
+
autoCloseIssues: true,
|
|
51
|
+
squashMerge: true,
|
|
52
|
+
deleteBranchAfterMerge: true,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Permission modes for Claude CLI
|
|
58
|
+
*/
|
|
59
|
+
const PERMISSION_MODES = {
|
|
60
|
+
bypassPermissions: {
|
|
61
|
+
name: 'Bypass All Permissions',
|
|
62
|
+
description: 'Auto-approve all tool calls without prompting',
|
|
63
|
+
recommended: false,
|
|
64
|
+
warning: 'Use only in trusted environments',
|
|
65
|
+
},
|
|
66
|
+
acceptEdits: {
|
|
67
|
+
name: 'Accept Edits',
|
|
68
|
+
description: 'Auto-approve Read/Glob/Grep, prompt for Edit/Write/Bash',
|
|
69
|
+
recommended: true,
|
|
70
|
+
},
|
|
71
|
+
ask: {
|
|
72
|
+
name: 'Ask for Each',
|
|
73
|
+
description: 'Prompt for every tool call (most secure)',
|
|
74
|
+
recommended: false,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run the Claude settings wizard
|
|
80
|
+
*/
|
|
81
|
+
export async function runClaudeSettings(options) {
|
|
82
|
+
showHeader('Claude CLI Settings');
|
|
83
|
+
|
|
84
|
+
const { action } = await inquirer.prompt([
|
|
85
|
+
{
|
|
86
|
+
type: 'list',
|
|
87
|
+
name: 'action',
|
|
88
|
+
message: 'What would you like to configure?',
|
|
89
|
+
choices: [
|
|
90
|
+
{
|
|
91
|
+
name: `${chalk.green('1)')} Configure Permission Mode Set default permission behavior`,
|
|
92
|
+
value: 'permission-mode',
|
|
93
|
+
short: 'Permission Mode',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: `${chalk.cyan('2)')} Create Agent-Only Launcher Scripts for agent-only execution`,
|
|
97
|
+
value: 'agent-only',
|
|
98
|
+
short: 'Agent Only Launcher',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: `${chalk.yellow('3)')} Configure Permissions Set allow/deny rules`,
|
|
102
|
+
value: 'permissions',
|
|
103
|
+
short: 'Permissions',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: `${chalk.blue('4)')} View Current Settings Show .claude/settings.json`,
|
|
107
|
+
value: 'view',
|
|
108
|
+
short: 'View Settings',
|
|
109
|
+
},
|
|
110
|
+
new inquirer.Separator(),
|
|
111
|
+
{
|
|
112
|
+
name: `${chalk.magenta('5)')} GitHub Settings Configure project board, PRs, workflow`,
|
|
113
|
+
value: 'github',
|
|
114
|
+
short: 'GitHub Settings',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: `${chalk.red('6)')} Tech Stack Settings Auto-detect and configure tech stack`,
|
|
118
|
+
value: 'tech-stack',
|
|
119
|
+
short: 'Tech Stack',
|
|
120
|
+
},
|
|
121
|
+
new inquirer.Separator(),
|
|
122
|
+
{
|
|
123
|
+
name: `${chalk.dim('Q)')} Back / Exit`,
|
|
124
|
+
value: 'exit',
|
|
125
|
+
short: 'Exit',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
if (action === 'exit') {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
switch (action) {
|
|
136
|
+
case 'permission-mode':
|
|
137
|
+
return await configurePermissionMode();
|
|
138
|
+
case 'agent-only':
|
|
139
|
+
return await createAgentOnlyLauncher();
|
|
140
|
+
case 'permissions':
|
|
141
|
+
return await configurePermissions();
|
|
142
|
+
case 'view':
|
|
143
|
+
return await viewCurrentSettings();
|
|
144
|
+
case 'github':
|
|
145
|
+
return await configureGitHubSettings();
|
|
146
|
+
case 'tech-stack':
|
|
147
|
+
return await configureTechStackSettings();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Configure the default permission mode
|
|
153
|
+
*/
|
|
154
|
+
async function configurePermissionMode() {
|
|
155
|
+
showHeader('Permission Mode');
|
|
156
|
+
|
|
157
|
+
console.log(chalk.dim('Permission modes control how Claude CLI handles tool approvals.\n'));
|
|
158
|
+
|
|
159
|
+
const { mode } = await inquirer.prompt([
|
|
160
|
+
{
|
|
161
|
+
type: 'list',
|
|
162
|
+
name: 'mode',
|
|
163
|
+
message: 'Select default permission mode:',
|
|
164
|
+
choices: Object.entries(PERMISSION_MODES).map(([key, mode]) => ({
|
|
165
|
+
name: `${mode.name}${mode.recommended ? chalk.green(' (Recommended)') : ''}${mode.warning ? chalk.red(' ⚠') : ''}\n ${chalk.dim(mode.description)}`,
|
|
166
|
+
value: key,
|
|
167
|
+
short: mode.name,
|
|
168
|
+
})),
|
|
169
|
+
default: 'acceptEdits',
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
// Warn for bypass mode
|
|
174
|
+
if (mode === 'bypassPermissions') {
|
|
175
|
+
const { confirm } = await inquirer.prompt([
|
|
176
|
+
{
|
|
177
|
+
type: 'confirm',
|
|
178
|
+
name: 'confirm',
|
|
179
|
+
message: chalk.yellow('Warning: Bypass mode auto-approves ALL operations. Continue?'),
|
|
180
|
+
default: false,
|
|
181
|
+
},
|
|
182
|
+
]);
|
|
183
|
+
if (!confirm) {
|
|
184
|
+
console.log(chalk.dim('Cancelled.'));
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Load or create settings
|
|
190
|
+
const settingsPath = join(process.cwd(), '.claude', 'settings.json');
|
|
191
|
+
let settings = {};
|
|
192
|
+
|
|
193
|
+
if (existsSync(settingsPath)) {
|
|
194
|
+
try {
|
|
195
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
196
|
+
} catch {
|
|
197
|
+
settings = {};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Update permission mode
|
|
202
|
+
if (!settings.permissions) {
|
|
203
|
+
settings.permissions = {};
|
|
204
|
+
}
|
|
205
|
+
settings.permissions.defaultMode = mode;
|
|
206
|
+
|
|
207
|
+
// Ensure directory exists
|
|
208
|
+
const dir = dirname(settingsPath);
|
|
209
|
+
if (!existsSync(dir)) {
|
|
210
|
+
mkdirSync(dir, { recursive: true });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
214
|
+
|
|
215
|
+
showSuccess('Permission Mode Updated', [
|
|
216
|
+
`Mode: ${PERMISSION_MODES[mode].name}`,
|
|
217
|
+
`File: ${settingsPath}`,
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
return { mode, path: settingsPath };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create Agent-Only mode launcher scripts
|
|
225
|
+
*/
|
|
226
|
+
async function createAgentOnlyLauncher() {
|
|
227
|
+
showHeader('Agent-Only Launcher');
|
|
228
|
+
|
|
229
|
+
console.log(chalk.dim('Creates launcher scripts for Agent-Only execution mode.'));
|
|
230
|
+
console.log(chalk.dim('In this mode, Claude delegates all work to agents via Task tool.\n'));
|
|
231
|
+
|
|
232
|
+
const { createPolicy } = await inquirer.prompt([
|
|
233
|
+
{
|
|
234
|
+
type: 'confirm',
|
|
235
|
+
name: 'createPolicy',
|
|
236
|
+
message: 'Create AGENT_ONLY_POLICY.md?',
|
|
237
|
+
default: true,
|
|
238
|
+
},
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
const { createAgents } = await inquirer.prompt([
|
|
242
|
+
{
|
|
243
|
+
type: 'confirm',
|
|
244
|
+
name: 'createAgents',
|
|
245
|
+
message: 'Create agents.json with custom agent definitions?',
|
|
246
|
+
default: true,
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
const { platform } = await inquirer.prompt([
|
|
251
|
+
{
|
|
252
|
+
type: 'checkbox',
|
|
253
|
+
name: 'platform',
|
|
254
|
+
message: 'Create launchers for:',
|
|
255
|
+
choices: [
|
|
256
|
+
{ name: 'Windows (.bat + .ps1)', value: 'windows', checked: process.platform === 'win32' },
|
|
257
|
+
{ name: 'Mac/Linux (.sh)', value: 'unix', checked: process.platform !== 'win32' },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
const spinner = ora('Creating launcher files...').start();
|
|
263
|
+
const files = [];
|
|
264
|
+
|
|
265
|
+
// Create .claude directory if needed
|
|
266
|
+
const claudeDir = join(process.cwd(), '.claude');
|
|
267
|
+
if (!existsSync(claudeDir)) {
|
|
268
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 1. Create AGENT_ONLY_POLICY.md
|
|
272
|
+
if (createPolicy) {
|
|
273
|
+
const policyContent = generateAgentOnlyPolicy();
|
|
274
|
+
const policyPath = join(claudeDir, 'AGENT_ONLY_POLICY.md');
|
|
275
|
+
writeFileSync(policyPath, policyContent, 'utf8');
|
|
276
|
+
files.push(policyPath);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 2. Create agents.json
|
|
280
|
+
if (createAgents) {
|
|
281
|
+
const agentsContent = generateAgentsJson();
|
|
282
|
+
const agentsPath = join(claudeDir, 'agents.json');
|
|
283
|
+
writeFileSync(agentsPath, agentsContent, 'utf8');
|
|
284
|
+
files.push(agentsPath);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 3. Create Windows launchers
|
|
288
|
+
if (platform.includes('windows')) {
|
|
289
|
+
const batContent = generateWindowsBatch();
|
|
290
|
+
const batPath = join(process.cwd(), 'start-agent-only.bat');
|
|
291
|
+
writeFileSync(batPath, batContent, 'utf8');
|
|
292
|
+
files.push(batPath);
|
|
293
|
+
|
|
294
|
+
const ps1Content = generatePowerShellLauncher();
|
|
295
|
+
const ps1Path = join(process.cwd(), 'claude-agent-only.ps1');
|
|
296
|
+
writeFileSync(ps1Path, ps1Content, 'utf8');
|
|
297
|
+
files.push(ps1Path);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 4. Create Unix launcher
|
|
301
|
+
if (platform.includes('unix')) {
|
|
302
|
+
const shContent = generateBashLauncher();
|
|
303
|
+
const shPath = join(process.cwd(), 'claude-agent-only.sh');
|
|
304
|
+
writeFileSync(shPath, shContent, { encoding: 'utf8', mode: 0o755 });
|
|
305
|
+
files.push(shPath);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
spinner.succeed('Launcher files created');
|
|
309
|
+
|
|
310
|
+
showSuccess('Agent-Only Launcher Created', [
|
|
311
|
+
...files.map((f) => f.replace(process.cwd(), '.')),
|
|
312
|
+
'',
|
|
313
|
+
'Usage:',
|
|
314
|
+
platform.includes('windows') ? ' start-agent-only.bat' : '',
|
|
315
|
+
platform.includes('unix') ? ' ./claude-agent-only.sh' : '',
|
|
316
|
+
].filter(Boolean));
|
|
317
|
+
|
|
318
|
+
return { files };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Configure permission allow/deny rules
|
|
323
|
+
*/
|
|
324
|
+
async function configurePermissions() {
|
|
325
|
+
showHeader('Permission Rules');
|
|
326
|
+
|
|
327
|
+
console.log(chalk.dim('Configure which tools and patterns are allowed or denied.\n'));
|
|
328
|
+
|
|
329
|
+
// Load existing settings
|
|
330
|
+
const settingsPath = join(process.cwd(), '.claude', 'settings.json');
|
|
331
|
+
let settings = { permissions: { allow: [], deny: [] } };
|
|
332
|
+
|
|
333
|
+
if (existsSync(settingsPath)) {
|
|
334
|
+
try {
|
|
335
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
336
|
+
if (!settings.permissions) {
|
|
337
|
+
settings.permissions = { allow: [], deny: [] };
|
|
338
|
+
}
|
|
339
|
+
} catch {
|
|
340
|
+
// Use defaults
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Show current rules
|
|
345
|
+
console.log(chalk.cyan('Current Allow Rules:'));
|
|
346
|
+
if (settings.permissions.allow?.length > 0) {
|
|
347
|
+
settings.permissions.allow.forEach((rule) => console.log(chalk.green(` + ${rule}`)));
|
|
348
|
+
} else {
|
|
349
|
+
console.log(chalk.dim(' (none)'));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(chalk.cyan('Current Deny Rules:'));
|
|
354
|
+
if (settings.permissions.deny?.length > 0) {
|
|
355
|
+
settings.permissions.deny.forEach((rule) => console.log(chalk.red(` - ${rule}`)));
|
|
356
|
+
} else {
|
|
357
|
+
console.log(chalk.dim(' (none)'));
|
|
358
|
+
}
|
|
359
|
+
console.log('');
|
|
360
|
+
|
|
361
|
+
const { action } = await inquirer.prompt([
|
|
362
|
+
{
|
|
363
|
+
type: 'list',
|
|
364
|
+
name: 'action',
|
|
365
|
+
message: 'What would you like to do?',
|
|
366
|
+
choices: [
|
|
367
|
+
{ name: 'Add allow rule', value: 'add-allow' },
|
|
368
|
+
{ name: 'Add deny rule', value: 'add-deny' },
|
|
369
|
+
{ name: 'Use preset (recommended)', value: 'preset' },
|
|
370
|
+
{ name: 'Clear all rules', value: 'clear' },
|
|
371
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
372
|
+
],
|
|
373
|
+
},
|
|
374
|
+
]);
|
|
375
|
+
|
|
376
|
+
if (action === 'cancel') {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (action === 'preset') {
|
|
381
|
+
const { preset } = await inquirer.prompt([
|
|
382
|
+
{
|
|
383
|
+
type: 'list',
|
|
384
|
+
name: 'preset',
|
|
385
|
+
message: 'Select preset:',
|
|
386
|
+
choices: [
|
|
387
|
+
{ name: 'Permissive - Allow most, deny dangerous', value: 'permissive' },
|
|
388
|
+
{ name: 'Balanced - Allow common, deny sensitive', value: 'balanced' },
|
|
389
|
+
{ name: 'Strict - Minimal allows, many denies', value: 'strict' },
|
|
390
|
+
],
|
|
391
|
+
},
|
|
392
|
+
]);
|
|
393
|
+
|
|
394
|
+
const presets = {
|
|
395
|
+
permissive: {
|
|
396
|
+
allow: ['Read(*)', 'Write(src/**)', 'Bash(git:*)', 'MCP(*)'],
|
|
397
|
+
deny: ['Read(.env*)', 'Write(secrets/**)', 'Bash(rm -rf:*)'],
|
|
398
|
+
},
|
|
399
|
+
balanced: {
|
|
400
|
+
allow: ['Read(*)', 'Write(src/**)', 'Bash(git:*)', 'Bash(npm:*)'],
|
|
401
|
+
deny: ['Read(.env*)', 'Write(secrets/**)', 'Bash(rm:*)', 'Bash(sudo:*)'],
|
|
402
|
+
},
|
|
403
|
+
strict: {
|
|
404
|
+
allow: ['Read(src/**)', 'Bash(git status)', 'Bash(git log:*)'],
|
|
405
|
+
deny: ['Read(.env*)', 'Write(*)', 'Bash(rm:*)', 'Bash(sudo:*)', 'WebFetch(*)'],
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
settings.permissions.allow = presets[preset].allow;
|
|
410
|
+
settings.permissions.deny = presets[preset].deny;
|
|
411
|
+
} else if (action === 'add-allow' || action === 'add-deny') {
|
|
412
|
+
const { rule } = await inquirer.prompt([
|
|
413
|
+
{
|
|
414
|
+
type: 'input',
|
|
415
|
+
name: 'rule',
|
|
416
|
+
message: `Enter ${action === 'add-allow' ? 'allow' : 'deny'} rule (e.g., "Read(*)", "Bash(git:*)"):`,
|
|
417
|
+
validate: (input) => input.length > 0 || 'Rule cannot be empty',
|
|
418
|
+
},
|
|
419
|
+
]);
|
|
420
|
+
|
|
421
|
+
if (action === 'add-allow') {
|
|
422
|
+
if (!settings.permissions.allow) settings.permissions.allow = [];
|
|
423
|
+
settings.permissions.allow.push(rule);
|
|
424
|
+
} else {
|
|
425
|
+
if (!settings.permissions.deny) settings.permissions.deny = [];
|
|
426
|
+
settings.permissions.deny.push(rule);
|
|
427
|
+
}
|
|
428
|
+
} else if (action === 'clear') {
|
|
429
|
+
settings.permissions.allow = [];
|
|
430
|
+
settings.permissions.deny = [];
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Ensure directory exists
|
|
434
|
+
const dir = dirname(settingsPath);
|
|
435
|
+
if (!existsSync(dir)) {
|
|
436
|
+
mkdirSync(dir, { recursive: true });
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
440
|
+
|
|
441
|
+
showSuccess('Permissions Updated', [`File: ${settingsPath}`]);
|
|
442
|
+
|
|
443
|
+
return { settings, path: settingsPath };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* View current settings
|
|
448
|
+
*/
|
|
449
|
+
async function viewCurrentSettings() {
|
|
450
|
+
showHeader('Current Settings');
|
|
451
|
+
|
|
452
|
+
const settingsPath = join(process.cwd(), '.claude', 'settings.json');
|
|
453
|
+
|
|
454
|
+
if (!existsSync(settingsPath)) {
|
|
455
|
+
showWarning('No .claude/settings.json found.');
|
|
456
|
+
console.log(chalk.dim('Run "gtask claude-settings" to create one.'));
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
462
|
+
console.log(chalk.cyan('File: ') + settingsPath);
|
|
463
|
+
console.log('');
|
|
464
|
+
console.log(JSON.stringify(settings, null, 2));
|
|
465
|
+
return settings;
|
|
466
|
+
} catch (err) {
|
|
467
|
+
showError('Failed to read settings', err.message);
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Configure GitHub settings submenu
|
|
474
|
+
*/
|
|
475
|
+
async function configureGitHubSettings() {
|
|
476
|
+
showHeader('GitHub Settings');
|
|
477
|
+
|
|
478
|
+
const { section } = await inquirer.prompt([
|
|
479
|
+
{
|
|
480
|
+
type: 'list',
|
|
481
|
+
name: 'section',
|
|
482
|
+
message: 'What would you like to configure?',
|
|
483
|
+
choices: [
|
|
484
|
+
{
|
|
485
|
+
name: `${chalk.green('1)')} Project Board Settings Repository, project number, field IDs`,
|
|
486
|
+
value: 'project-board',
|
|
487
|
+
short: 'Project Board',
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
name: `${chalk.cyan('2)')} Branch & Worktree Strategy Feature branches vs worktrees`,
|
|
491
|
+
value: 'branch-strategy',
|
|
492
|
+
short: 'Branch Strategy',
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: `${chalk.yellow('3)')} Task Creation Defaults Labels, priority, QA requirements`,
|
|
496
|
+
value: 'task-defaults',
|
|
497
|
+
short: 'Task Defaults',
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: `${chalk.blue('4)')} PR Workflow Merge strategy, auto-close issues`,
|
|
501
|
+
value: 'pr-workflow',
|
|
502
|
+
short: 'PR Workflow',
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: `${chalk.magenta('5)')} Deployment Settings Auto-deploy on task complete`,
|
|
506
|
+
value: 'deployment',
|
|
507
|
+
short: 'Deployment',
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
name: `${chalk.white('6)')} View Current GitHub Settings Show github section of settings`,
|
|
511
|
+
value: 'view-github',
|
|
512
|
+
short: 'View Settings',
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
name: `${chalk.green('7)')} Apply Settings to .claude Run updater script`,
|
|
516
|
+
value: 'apply',
|
|
517
|
+
short: 'Apply Settings',
|
|
518
|
+
},
|
|
519
|
+
new inquirer.Separator(),
|
|
520
|
+
{
|
|
521
|
+
name: `${chalk.dim('Q)')} Back to Main Menu`,
|
|
522
|
+
value: 'back',
|
|
523
|
+
short: 'Back',
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
},
|
|
527
|
+
]);
|
|
528
|
+
|
|
529
|
+
if (section === 'back') {
|
|
530
|
+
return await runClaudeSettings();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
switch (section) {
|
|
534
|
+
case 'project-board':
|
|
535
|
+
return await configureProjectBoard();
|
|
536
|
+
case 'branch-strategy':
|
|
537
|
+
return await configureBranchStrategy();
|
|
538
|
+
case 'task-defaults':
|
|
539
|
+
return await configureTaskDefaults();
|
|
540
|
+
case 'pr-workflow':
|
|
541
|
+
return await configurePRWorkflow();
|
|
542
|
+
case 'deployment':
|
|
543
|
+
return await configureDeployment();
|
|
544
|
+
case 'view-github':
|
|
545
|
+
return await viewGitHubSettings();
|
|
546
|
+
case 'apply':
|
|
547
|
+
return await applyGitHubSettings();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Load existing GitHub settings or return defaults
|
|
553
|
+
*/
|
|
554
|
+
function loadGitHubSettings() {
|
|
555
|
+
const settingsPath = join(process.cwd(), '.claude', 'settings.json');
|
|
556
|
+
let settings = { github: { ...DEFAULT_GITHUB_SETTINGS } };
|
|
557
|
+
|
|
558
|
+
if (existsSync(settingsPath)) {
|
|
559
|
+
try {
|
|
560
|
+
const fileSettings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
561
|
+
if (fileSettings.github) {
|
|
562
|
+
settings.github = { ...DEFAULT_GITHUB_SETTINGS, ...fileSettings.github };
|
|
563
|
+
}
|
|
564
|
+
settings = { ...fileSettings, github: settings.github };
|
|
565
|
+
} catch {
|
|
566
|
+
// Use defaults
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return settings;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Save GitHub settings
|
|
575
|
+
*/
|
|
576
|
+
function saveGitHubSettings(settings) {
|
|
577
|
+
const settingsPath = join(process.cwd(), '.claude', 'settings.json');
|
|
578
|
+
const dir = dirname(settingsPath);
|
|
579
|
+
|
|
580
|
+
if (!existsSync(dir)) {
|
|
581
|
+
mkdirSync(dir, { recursive: true });
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
585
|
+
return settingsPath;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Configure Project Board settings
|
|
590
|
+
*/
|
|
591
|
+
async function configureProjectBoard() {
|
|
592
|
+
showHeader('Project Board Settings');
|
|
593
|
+
|
|
594
|
+
const settings = loadGitHubSettings();
|
|
595
|
+
const github = settings.github;
|
|
596
|
+
|
|
597
|
+
console.log(chalk.dim('Configure your GitHub repository and project board.\n'));
|
|
598
|
+
|
|
599
|
+
// Repository settings
|
|
600
|
+
const repoAnswers = await inquirer.prompt([
|
|
601
|
+
{
|
|
602
|
+
type: 'input',
|
|
603
|
+
name: 'owner',
|
|
604
|
+
message: 'Repository owner (username or org):',
|
|
605
|
+
default: github.repository?.owner || '',
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
type: 'input',
|
|
609
|
+
name: 'name',
|
|
610
|
+
message: 'Repository name:',
|
|
611
|
+
default: github.repository?.name || '',
|
|
612
|
+
},
|
|
613
|
+
]);
|
|
614
|
+
|
|
615
|
+
// Project board settings
|
|
616
|
+
const projectAnswers = await inquirer.prompt([
|
|
617
|
+
{
|
|
618
|
+
type: 'number',
|
|
619
|
+
name: 'number',
|
|
620
|
+
message: 'Project board number (from URL):',
|
|
621
|
+
default: github.project?.number || null,
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
type: 'input',
|
|
625
|
+
name: 'id',
|
|
626
|
+
message: 'Project ID (PVT_... from GraphQL, leave blank if unknown):',
|
|
627
|
+
default: github.project?.id || '',
|
|
628
|
+
},
|
|
629
|
+
]);
|
|
630
|
+
|
|
631
|
+
// Update settings
|
|
632
|
+
settings.github.repository = {
|
|
633
|
+
owner: repoAnswers.owner,
|
|
634
|
+
name: repoAnswers.name,
|
|
635
|
+
};
|
|
636
|
+
settings.github.project = {
|
|
637
|
+
number: projectAnswers.number,
|
|
638
|
+
id: projectAnswers.id,
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
const path = saveGitHubSettings(settings);
|
|
642
|
+
|
|
643
|
+
showSuccess('Project Board Settings Updated', [
|
|
644
|
+
`Repository: ${repoAnswers.owner}/${repoAnswers.name}`,
|
|
645
|
+
`Project #${projectAnswers.number}`,
|
|
646
|
+
`File: ${path}`,
|
|
647
|
+
]);
|
|
648
|
+
|
|
649
|
+
return await configureGitHubSettings();
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Configure Branch & Worktree Strategy
|
|
654
|
+
*/
|
|
655
|
+
async function configureBranchStrategy() {
|
|
656
|
+
showHeader('Branch & Worktree Strategy');
|
|
657
|
+
|
|
658
|
+
const settings = loadGitHubSettings();
|
|
659
|
+
const github = settings.github;
|
|
660
|
+
|
|
661
|
+
console.log(chalk.dim('Configure how branches and worktrees are used for development.\n'));
|
|
662
|
+
|
|
663
|
+
const answers = await inquirer.prompt([
|
|
664
|
+
{
|
|
665
|
+
type: 'input',
|
|
666
|
+
name: 'branch',
|
|
667
|
+
message: 'Default base branch:',
|
|
668
|
+
default: github.defaults?.branch || 'main',
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
type: 'confirm',
|
|
672
|
+
name: 'createFeatureBranches',
|
|
673
|
+
message: 'Create feature branches for each task?',
|
|
674
|
+
default: github.defaults?.createFeatureBranches || false,
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
type: 'confirm',
|
|
678
|
+
name: 'useWorktreesForPhaseDev',
|
|
679
|
+
message: 'Use git worktrees for phase-dev projects? (recommended for large projects)',
|
|
680
|
+
default: github.defaults?.useWorktreesForPhaseDev !== false,
|
|
681
|
+
},
|
|
682
|
+
]);
|
|
683
|
+
|
|
684
|
+
settings.github.defaults = {
|
|
685
|
+
...settings.github.defaults,
|
|
686
|
+
branch: answers.branch,
|
|
687
|
+
createFeatureBranches: answers.createFeatureBranches,
|
|
688
|
+
useWorktreesForPhaseDev: answers.useWorktreesForPhaseDev,
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const path = saveGitHubSettings(settings);
|
|
692
|
+
|
|
693
|
+
showSuccess('Branch Strategy Updated', [
|
|
694
|
+
`Base branch: ${answers.branch}`,
|
|
695
|
+
`Feature branches: ${answers.createFeatureBranches ? 'Yes' : 'No'}`,
|
|
696
|
+
`Worktrees for phase-dev: ${answers.useWorktreesForPhaseDev ? 'Yes' : 'No'}`,
|
|
697
|
+
`File: ${path}`,
|
|
698
|
+
]);
|
|
699
|
+
|
|
700
|
+
return await configureGitHubSettings();
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Configure Task Creation Defaults
|
|
705
|
+
*/
|
|
706
|
+
async function configureTaskDefaults() {
|
|
707
|
+
showHeader('Task Creation Defaults');
|
|
708
|
+
|
|
709
|
+
const settings = loadGitHubSettings();
|
|
710
|
+
const github = settings.github;
|
|
711
|
+
|
|
712
|
+
console.log(chalk.dim('Configure default values for new GitHub issues/tasks.\n'));
|
|
713
|
+
|
|
714
|
+
const answers = await inquirer.prompt([
|
|
715
|
+
{
|
|
716
|
+
type: 'checkbox',
|
|
717
|
+
name: 'labels',
|
|
718
|
+
message: 'Default labels for new issues:',
|
|
719
|
+
choices: [
|
|
720
|
+
{ name: 'feature', checked: github.defaults?.labels?.includes('feature') },
|
|
721
|
+
{ name: 'bug', checked: github.defaults?.labels?.includes('bug') },
|
|
722
|
+
{ name: 'enhancement', checked: github.defaults?.labels?.includes('enhancement') },
|
|
723
|
+
{ name: 'documentation', checked: github.defaults?.labels?.includes('documentation') },
|
|
724
|
+
{ name: 'frontend', checked: github.defaults?.labels?.includes('frontend') },
|
|
725
|
+
{ name: 'backend', checked: github.defaults?.labels?.includes('backend') },
|
|
726
|
+
],
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
type: 'list',
|
|
730
|
+
name: 'priority',
|
|
731
|
+
message: 'Default priority:',
|
|
732
|
+
choices: ['P0-Critical', 'P1-High', 'P2-Medium', 'P3-Low'],
|
|
733
|
+
default: github.defaults?.priority || 'P2-Medium',
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
type: 'list',
|
|
737
|
+
name: 'qa',
|
|
738
|
+
message: 'Default QA requirement:',
|
|
739
|
+
choices: ['Required', 'Not Required', 'Self-Tested'],
|
|
740
|
+
default: github.defaults?.qa || 'Not Required',
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
type: 'input',
|
|
744
|
+
name: 'agent',
|
|
745
|
+
message: 'Default agent for tasks:',
|
|
746
|
+
default: github.defaults?.agent || 'general-purpose',
|
|
747
|
+
},
|
|
748
|
+
]);
|
|
749
|
+
|
|
750
|
+
settings.github.defaults = {
|
|
751
|
+
...settings.github.defaults,
|
|
752
|
+
labels: answers.labels,
|
|
753
|
+
priority: answers.priority,
|
|
754
|
+
qa: answers.qa,
|
|
755
|
+
agent: answers.agent,
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const path = saveGitHubSettings(settings);
|
|
759
|
+
|
|
760
|
+
showSuccess('Task Defaults Updated', [
|
|
761
|
+
`Labels: ${answers.labels.join(', ') || '(none)'}`,
|
|
762
|
+
`Priority: ${answers.priority}`,
|
|
763
|
+
`QA: ${answers.qa}`,
|
|
764
|
+
`Agent: ${answers.agent}`,
|
|
765
|
+
`File: ${path}`,
|
|
766
|
+
]);
|
|
767
|
+
|
|
768
|
+
return await configureGitHubSettings();
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Configure PR Workflow
|
|
773
|
+
*/
|
|
774
|
+
async function configurePRWorkflow() {
|
|
775
|
+
showHeader('PR Workflow');
|
|
776
|
+
|
|
777
|
+
const settings = loadGitHubSettings();
|
|
778
|
+
const github = settings.github;
|
|
779
|
+
|
|
780
|
+
console.log(chalk.dim('Configure pull request behavior.\n'));
|
|
781
|
+
|
|
782
|
+
const answers = await inquirer.prompt([
|
|
783
|
+
{
|
|
784
|
+
type: 'input',
|
|
785
|
+
name: 'baseBranch',
|
|
786
|
+
message: 'Default PR base branch:',
|
|
787
|
+
default: github.pr?.baseBranch || 'main',
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
type: 'confirm',
|
|
791
|
+
name: 'taskListsAsPRs',
|
|
792
|
+
message: 'Create task lists as PRs instead of issues?',
|
|
793
|
+
default: github.workflow?.taskListsAsPRs || false,
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
type: 'confirm',
|
|
797
|
+
name: 'autoCloseIssues',
|
|
798
|
+
message: 'Auto-close linked issues when PR is merged?',
|
|
799
|
+
default: github.pr?.autoCloseIssues !== false,
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
type: 'confirm',
|
|
803
|
+
name: 'squashMerge',
|
|
804
|
+
message: 'Use squash merge by default?',
|
|
805
|
+
default: github.pr?.squashMerge !== false,
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
type: 'confirm',
|
|
809
|
+
name: 'deleteBranchAfterMerge',
|
|
810
|
+
message: 'Delete branch after merge?',
|
|
811
|
+
default: github.pr?.deleteBranchAfterMerge !== false,
|
|
812
|
+
},
|
|
813
|
+
]);
|
|
814
|
+
|
|
815
|
+
settings.github.pr = {
|
|
816
|
+
baseBranch: answers.baseBranch,
|
|
817
|
+
autoCloseIssues: answers.autoCloseIssues,
|
|
818
|
+
squashMerge: answers.squashMerge,
|
|
819
|
+
deleteBranchAfterMerge: answers.deleteBranchAfterMerge,
|
|
820
|
+
};
|
|
821
|
+
settings.github.workflow = {
|
|
822
|
+
...settings.github.workflow,
|
|
823
|
+
taskListsAsPRs: answers.taskListsAsPRs,
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const path = saveGitHubSettings(settings);
|
|
827
|
+
|
|
828
|
+
showSuccess('PR Workflow Updated', [
|
|
829
|
+
`Base branch: ${answers.baseBranch}`,
|
|
830
|
+
`Task lists as PRs: ${answers.taskListsAsPRs ? 'Yes' : 'No'}`,
|
|
831
|
+
`Auto-close issues: ${answers.autoCloseIssues ? 'Yes' : 'No'}`,
|
|
832
|
+
`Squash merge: ${answers.squashMerge ? 'Yes' : 'No'}`,
|
|
833
|
+
`Delete branch: ${answers.deleteBranchAfterMerge ? 'Yes' : 'No'}`,
|
|
834
|
+
`File: ${path}`,
|
|
835
|
+
]);
|
|
836
|
+
|
|
837
|
+
return await configureGitHubSettings();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Configure Deployment Settings
|
|
842
|
+
*/
|
|
843
|
+
async function configureDeployment() {
|
|
844
|
+
showHeader('Deployment Settings');
|
|
845
|
+
|
|
846
|
+
const settings = loadGitHubSettings();
|
|
847
|
+
const github = settings.github;
|
|
848
|
+
|
|
849
|
+
console.log(chalk.dim('Configure deployment behavior for completed tasks.\n'));
|
|
850
|
+
|
|
851
|
+
const answers = await inquirer.prompt([
|
|
852
|
+
{
|
|
853
|
+
type: 'confirm',
|
|
854
|
+
name: 'autoDeployOnComplete',
|
|
855
|
+
message: 'Auto-deploy when task is marked complete?',
|
|
856
|
+
default: github.workflow?.autoDeployOnComplete !== false,
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
type: 'confirm',
|
|
860
|
+
name: 'autoFixBlankFields',
|
|
861
|
+
message: 'Auto-fix blank fields on GitHub issues?',
|
|
862
|
+
default: github.workflow?.autoFixBlankFields !== false,
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
type: 'confirm',
|
|
866
|
+
name: 'autoReorderOnCreate',
|
|
867
|
+
message: 'Auto-reorder project board when creating issues?',
|
|
868
|
+
default: github.workflow?.autoReorderOnCreate || false,
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
type: 'confirm',
|
|
872
|
+
name: 'generateTestsByDefault',
|
|
873
|
+
message: 'Generate tests by default for new tasks?',
|
|
874
|
+
default: github.workflow?.generateTestsByDefault || false,
|
|
875
|
+
},
|
|
876
|
+
]);
|
|
877
|
+
|
|
878
|
+
settings.github.workflow = {
|
|
879
|
+
...settings.github.workflow,
|
|
880
|
+
autoDeployOnComplete: answers.autoDeployOnComplete,
|
|
881
|
+
autoFixBlankFields: answers.autoFixBlankFields,
|
|
882
|
+
autoReorderOnCreate: answers.autoReorderOnCreate,
|
|
883
|
+
generateTestsByDefault: answers.generateTestsByDefault,
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const path = saveGitHubSettings(settings);
|
|
887
|
+
|
|
888
|
+
showSuccess('Deployment Settings Updated', [
|
|
889
|
+
`Auto-deploy: ${answers.autoDeployOnComplete ? 'Yes' : 'No'}`,
|
|
890
|
+
`Auto-fix fields: ${answers.autoFixBlankFields ? 'Yes' : 'No'}`,
|
|
891
|
+
`Auto-reorder: ${answers.autoReorderOnCreate ? 'Yes' : 'No'}`,
|
|
892
|
+
`Generate tests: ${answers.generateTestsByDefault ? 'Yes' : 'No'}`,
|
|
893
|
+
`File: ${path}`,
|
|
894
|
+
]);
|
|
895
|
+
|
|
896
|
+
return await configureGitHubSettings();
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* View current GitHub settings
|
|
901
|
+
*/
|
|
902
|
+
async function viewGitHubSettings() {
|
|
903
|
+
showHeader('Current GitHub Settings');
|
|
904
|
+
|
|
905
|
+
const settings = loadGitHubSettings();
|
|
906
|
+
|
|
907
|
+
if (!settings.github || Object.keys(settings.github).length === 0) {
|
|
908
|
+
showWarning('No GitHub settings configured.');
|
|
909
|
+
console.log(chalk.dim('Use the GitHub Settings menu to configure them.'));
|
|
910
|
+
} else {
|
|
911
|
+
console.log(chalk.cyan('GitHub Settings:'));
|
|
912
|
+
console.log('');
|
|
913
|
+
console.log(JSON.stringify(settings.github, null, 2));
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
console.log('');
|
|
917
|
+
const { action } = await inquirer.prompt([
|
|
918
|
+
{
|
|
919
|
+
type: 'list',
|
|
920
|
+
name: 'action',
|
|
921
|
+
message: 'What next?',
|
|
922
|
+
choices: [
|
|
923
|
+
{ name: 'Back to GitHub Settings', value: 'back' },
|
|
924
|
+
{ name: 'Exit', value: 'exit' },
|
|
925
|
+
],
|
|
926
|
+
},
|
|
927
|
+
]);
|
|
928
|
+
|
|
929
|
+
if (action === 'back') {
|
|
930
|
+
return await configureGitHubSettings();
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Apply GitHub settings to .claude files using updater script
|
|
938
|
+
*/
|
|
939
|
+
async function applyGitHubSettings() {
|
|
940
|
+
showHeader('Apply GitHub Settings');
|
|
941
|
+
|
|
942
|
+
const settings = loadGitHubSettings();
|
|
943
|
+
|
|
944
|
+
if (!settings.github?.repository?.owner || !settings.github?.repository?.name) {
|
|
945
|
+
showWarning('Please configure Project Board settings first.');
|
|
946
|
+
return await configureGitHubSettings();
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
console.log(chalk.dim('This will update your .claude files to use the configured GitHub settings.\n'));
|
|
950
|
+
|
|
951
|
+
console.log(chalk.cyan('Current settings to apply:'));
|
|
952
|
+
console.log(` Repository: ${settings.github.repository.owner}/${settings.github.repository.name}`);
|
|
953
|
+
console.log(` Project: #${settings.github.project?.number || '(not set)'}`);
|
|
954
|
+
console.log('');
|
|
955
|
+
|
|
956
|
+
const { confirm } = await inquirer.prompt([
|
|
957
|
+
{
|
|
958
|
+
type: 'confirm',
|
|
959
|
+
name: 'confirm',
|
|
960
|
+
message: 'Apply these settings to .claude files?',
|
|
961
|
+
default: true,
|
|
962
|
+
},
|
|
963
|
+
]);
|
|
964
|
+
|
|
965
|
+
if (!confirm) {
|
|
966
|
+
return await configureGitHubSettings();
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Generate and run the updater script
|
|
970
|
+
const updaterPath = join(process.cwd(), '.claude', 'hooks', 'tools', 'github-settings-updater.js');
|
|
971
|
+
const updaterDir = dirname(updaterPath);
|
|
972
|
+
|
|
973
|
+
if (!existsSync(updaterDir)) {
|
|
974
|
+
mkdirSync(updaterDir, { recursive: true });
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Generate the updater script
|
|
978
|
+
const updaterContent = generateGitHubSettingsUpdater();
|
|
979
|
+
writeFileSync(updaterPath, updaterContent, 'utf8');
|
|
980
|
+
|
|
981
|
+
showSuccess('GitHub Settings Updater Created', [
|
|
982
|
+
`Script: ${updaterPath}`,
|
|
983
|
+
'',
|
|
984
|
+
'To apply settings, run:',
|
|
985
|
+
` node ${updaterPath}`,
|
|
986
|
+
'',
|
|
987
|
+
'Or run manually when you want to sync settings.',
|
|
988
|
+
]);
|
|
989
|
+
|
|
990
|
+
return await configureGitHubSettings();
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Configure Tech Stack settings submenu
|
|
995
|
+
*/
|
|
996
|
+
async function configureTechStackSettings() {
|
|
997
|
+
showHeader('Tech Stack Settings');
|
|
998
|
+
|
|
999
|
+
const { section } = await inquirer.prompt([
|
|
1000
|
+
{
|
|
1001
|
+
type: 'list',
|
|
1002
|
+
name: 'section',
|
|
1003
|
+
message: 'What would you like to do?',
|
|
1004
|
+
choices: [
|
|
1005
|
+
{
|
|
1006
|
+
name: `${chalk.green('1)')} Auto-Detect Tech Stack Scan codebase and detect technologies`,
|
|
1007
|
+
value: 'detect',
|
|
1008
|
+
short: 'Auto-Detect',
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
name: `${chalk.cyan('2)')} Edit Frontend Settings Framework, build tool, port`,
|
|
1012
|
+
value: 'frontend',
|
|
1013
|
+
short: 'Frontend',
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
name: `${chalk.yellow('3)')} Edit Backend Settings Language, framework, API style`,
|
|
1017
|
+
value: 'backend',
|
|
1018
|
+
short: 'Backend',
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
name: `${chalk.blue('4)')} Edit Testing Settings E2E, unit tests, selectors`,
|
|
1022
|
+
value: 'testing',
|
|
1023
|
+
short: 'Testing',
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
name: `${chalk.magenta('5)')} Edit Deployment Settings Platforms, URLs, CI/CD`,
|
|
1027
|
+
value: 'deployment',
|
|
1028
|
+
short: 'Deployment',
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
name: `${chalk.red('6)')} Edit Dev Environment Tunnel, container, package manager`,
|
|
1032
|
+
value: 'dev-env',
|
|
1033
|
+
short: 'Dev Environment',
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
name: `${chalk.white('7)')} View Current Tech Stack Show tech-stack.json`,
|
|
1037
|
+
value: 'view',
|
|
1038
|
+
short: 'View',
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
name: `${chalk.green('8)')} Apply Templates Update .claude files with values`,
|
|
1042
|
+
value: 'apply',
|
|
1043
|
+
short: 'Apply',
|
|
1044
|
+
},
|
|
1045
|
+
new inquirer.Separator(),
|
|
1046
|
+
{
|
|
1047
|
+
name: `${chalk.dim('Q)')} Back to Main Menu`,
|
|
1048
|
+
value: 'back',
|
|
1049
|
+
short: 'Back',
|
|
1050
|
+
},
|
|
1051
|
+
],
|
|
1052
|
+
},
|
|
1053
|
+
]);
|
|
1054
|
+
|
|
1055
|
+
if (section === 'back') {
|
|
1056
|
+
return await runClaudeSettings();
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
switch (section) {
|
|
1060
|
+
case 'detect':
|
|
1061
|
+
return await autoDetectTechStack();
|
|
1062
|
+
case 'frontend':
|
|
1063
|
+
return await editFrontendSettings();
|
|
1064
|
+
case 'backend':
|
|
1065
|
+
return await editBackendSettings();
|
|
1066
|
+
case 'testing':
|
|
1067
|
+
return await editTestingSettings();
|
|
1068
|
+
case 'deployment':
|
|
1069
|
+
return await editDeploymentSettings();
|
|
1070
|
+
case 'dev-env':
|
|
1071
|
+
return await editDevEnvironment();
|
|
1072
|
+
case 'view':
|
|
1073
|
+
return await viewTechStack();
|
|
1074
|
+
case 'apply':
|
|
1075
|
+
return await applyTechStackTemplates();
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Load tech-stack.json
|
|
1081
|
+
*/
|
|
1082
|
+
function loadTechStack() {
|
|
1083
|
+
const techStackPath = join(process.cwd(), '.claude', 'config', 'tech-stack.json');
|
|
1084
|
+
if (!existsSync(techStackPath)) {
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
try {
|
|
1088
|
+
return JSON.parse(readFileSync(techStackPath, 'utf8'));
|
|
1089
|
+
} catch {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Save tech-stack.json
|
|
1096
|
+
*/
|
|
1097
|
+
function saveTechStack(techStack) {
|
|
1098
|
+
const configDir = join(process.cwd(), '.claude', 'config');
|
|
1099
|
+
if (!existsSync(configDir)) {
|
|
1100
|
+
mkdirSync(configDir, { recursive: true });
|
|
1101
|
+
}
|
|
1102
|
+
const techStackPath = join(configDir, 'tech-stack.json');
|
|
1103
|
+
writeFileSync(techStackPath, JSON.stringify(techStack, null, 2), 'utf8');
|
|
1104
|
+
return techStackPath;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Auto-detect tech stack
|
|
1109
|
+
*/
|
|
1110
|
+
async function autoDetectTechStack() {
|
|
1111
|
+
showHeader('Auto-Detect Tech Stack');
|
|
1112
|
+
|
|
1113
|
+
console.log(chalk.dim('Scanning your codebase to detect technologies...\n'));
|
|
1114
|
+
|
|
1115
|
+
try {
|
|
1116
|
+
const detected = await detectTechStack(process.cwd());
|
|
1117
|
+
|
|
1118
|
+
// Remove internal tracking
|
|
1119
|
+
delete detected._detected;
|
|
1120
|
+
|
|
1121
|
+
console.log(chalk.green('\n✓ Detection complete!\n'));
|
|
1122
|
+
|
|
1123
|
+
// Show summary
|
|
1124
|
+
const summary = [];
|
|
1125
|
+
if (detected.frontend?.framework) summary.push(`Frontend: ${detected.frontend.framework}`);
|
|
1126
|
+
if (detected.backend?.framework) summary.push(`Backend: ${detected.backend.framework} (${detected.backend.language})`);
|
|
1127
|
+
if (detected.database?.primary) summary.push(`Database: ${detected.database.primary}`);
|
|
1128
|
+
if (detected.testing?.e2e?.framework) summary.push(`E2E: ${detected.testing.e2e.framework}`);
|
|
1129
|
+
if (detected.versionControl?.provider) summary.push(`Git: ${detected.versionControl.owner}/${detected.versionControl.repo}`);
|
|
1130
|
+
|
|
1131
|
+
console.log(chalk.cyan('Detected:'));
|
|
1132
|
+
summary.forEach((s) => console.log(` ${s}`));
|
|
1133
|
+
console.log('');
|
|
1134
|
+
|
|
1135
|
+
const { confirm } = await inquirer.prompt([
|
|
1136
|
+
{
|
|
1137
|
+
type: 'confirm',
|
|
1138
|
+
name: 'confirm',
|
|
1139
|
+
message: 'Save detected tech stack to .claude/config/tech-stack.json?',
|
|
1140
|
+
default: true,
|
|
1141
|
+
},
|
|
1142
|
+
]);
|
|
1143
|
+
|
|
1144
|
+
if (confirm) {
|
|
1145
|
+
const path = saveTechStack(detected);
|
|
1146
|
+
showSuccess('Tech Stack Saved', [`File: ${path}`]);
|
|
1147
|
+
}
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
showError('Detection failed', err.message);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
return await configureTechStackSettings();
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Edit frontend settings
|
|
1157
|
+
*/
|
|
1158
|
+
async function editFrontendSettings() {
|
|
1159
|
+
showHeader('Frontend Settings');
|
|
1160
|
+
|
|
1161
|
+
const techStack = loadTechStack() || { frontend: {} };
|
|
1162
|
+
|
|
1163
|
+
const answers = await inquirer.prompt([
|
|
1164
|
+
{
|
|
1165
|
+
type: 'list',
|
|
1166
|
+
name: 'framework',
|
|
1167
|
+
message: 'Frontend framework:',
|
|
1168
|
+
choices: ['react', 'vue', 'angular', 'svelte', 'nextjs', 'nuxt', 'astro', 'vanilla', 'none', 'other'],
|
|
1169
|
+
default: techStack.frontend?.framework || 'none',
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
type: 'list',
|
|
1173
|
+
name: 'buildTool',
|
|
1174
|
+
message: 'Build tool:',
|
|
1175
|
+
choices: ['vite', 'webpack', 'esbuild', 'parcel', 'turbopack', 'none', 'other'],
|
|
1176
|
+
default: techStack.frontend?.buildTool || 'vite',
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
type: 'list',
|
|
1180
|
+
name: 'stateManager',
|
|
1181
|
+
message: 'State manager:',
|
|
1182
|
+
choices: ['zustand', 'redux', 'mobx', 'jotai', 'pinia', 'vuex', 'none', 'other'],
|
|
1183
|
+
default: techStack.frontend?.stateManager || 'none',
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
type: 'number',
|
|
1187
|
+
name: 'port',
|
|
1188
|
+
message: 'Dev server port:',
|
|
1189
|
+
default: techStack.frontend?.port || 5173,
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
type: 'input',
|
|
1193
|
+
name: 'devCommand',
|
|
1194
|
+
message: 'Dev command:',
|
|
1195
|
+
default: techStack.frontend?.devCommand || 'npm run dev',
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
type: 'input',
|
|
1199
|
+
name: 'buildCommand',
|
|
1200
|
+
message: 'Build command:',
|
|
1201
|
+
default: techStack.frontend?.buildCommand || 'npm run build',
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
type: 'input',
|
|
1205
|
+
name: 'distDir',
|
|
1206
|
+
message: 'Build output directory:',
|
|
1207
|
+
default: techStack.frontend?.distDir || 'dist',
|
|
1208
|
+
},
|
|
1209
|
+
]);
|
|
1210
|
+
|
|
1211
|
+
techStack.frontend = answers;
|
|
1212
|
+
|
|
1213
|
+
// Update local URLs
|
|
1214
|
+
if (!techStack.urls) techStack.urls = { local: {} };
|
|
1215
|
+
techStack.urls.local.frontend = `http://localhost:${answers.port}`;
|
|
1216
|
+
|
|
1217
|
+
const path = saveTechStack(techStack);
|
|
1218
|
+
showSuccess('Frontend Settings Updated', [`File: ${path}`]);
|
|
1219
|
+
|
|
1220
|
+
return await configureTechStackSettings();
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Edit backend settings
|
|
1225
|
+
*/
|
|
1226
|
+
async function editBackendSettings() {
|
|
1227
|
+
showHeader('Backend Settings');
|
|
1228
|
+
|
|
1229
|
+
const techStack = loadTechStack() || { backend: {} };
|
|
1230
|
+
|
|
1231
|
+
const answers = await inquirer.prompt([
|
|
1232
|
+
{
|
|
1233
|
+
type: 'list',
|
|
1234
|
+
name: 'language',
|
|
1235
|
+
message: 'Backend language:',
|
|
1236
|
+
choices: ['python', 'node', 'typescript', 'go', 'rust', 'java', 'ruby', 'none', 'other'],
|
|
1237
|
+
default: techStack.backend?.language || 'none',
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
type: 'list',
|
|
1241
|
+
name: 'framework',
|
|
1242
|
+
message: 'Backend framework:',
|
|
1243
|
+
choices: ['fastapi', 'express', 'nestjs', 'django', 'flask', 'gin', 'rails', 'none', 'other'],
|
|
1244
|
+
default: techStack.backend?.framework || 'none',
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
type: 'list',
|
|
1248
|
+
name: 'apiStyle',
|
|
1249
|
+
message: 'API style:',
|
|
1250
|
+
choices: ['rest', 'graphql', 'grpc', 'trpc', 'other'],
|
|
1251
|
+
default: techStack.backend?.apiStyle || 'rest',
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
type: 'number',
|
|
1255
|
+
name: 'port',
|
|
1256
|
+
message: 'Backend port:',
|
|
1257
|
+
default: techStack.backend?.port || 8000,
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
type: 'input',
|
|
1261
|
+
name: 'devCommand',
|
|
1262
|
+
message: 'Dev command:',
|
|
1263
|
+
default: techStack.backend?.devCommand || '',
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
type: 'input',
|
|
1267
|
+
name: 'healthEndpoint',
|
|
1268
|
+
message: 'Health check endpoint:',
|
|
1269
|
+
default: techStack.backend?.healthEndpoint || '/api/health',
|
|
1270
|
+
},
|
|
1271
|
+
]);
|
|
1272
|
+
|
|
1273
|
+
techStack.backend = answers;
|
|
1274
|
+
|
|
1275
|
+
// Update local URLs
|
|
1276
|
+
if (!techStack.urls) techStack.urls = { local: {} };
|
|
1277
|
+
techStack.urls.local.backend = `http://localhost:${answers.port}`;
|
|
1278
|
+
techStack.urls.local.api = `http://localhost:${answers.port}/api`;
|
|
1279
|
+
|
|
1280
|
+
const path = saveTechStack(techStack);
|
|
1281
|
+
showSuccess('Backend Settings Updated', [`File: ${path}`]);
|
|
1282
|
+
|
|
1283
|
+
return await configureTechStackSettings();
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Edit testing settings
|
|
1288
|
+
*/
|
|
1289
|
+
async function editTestingSettings() {
|
|
1290
|
+
showHeader('Testing Settings');
|
|
1291
|
+
|
|
1292
|
+
const techStack = loadTechStack() || { testing: { e2e: {}, unit: {}, selectors: {}, credentials: {} } };
|
|
1293
|
+
|
|
1294
|
+
const e2eAnswers = await inquirer.prompt([
|
|
1295
|
+
{
|
|
1296
|
+
type: 'list',
|
|
1297
|
+
name: 'framework',
|
|
1298
|
+
message: 'E2E testing framework:',
|
|
1299
|
+
choices: ['playwright', 'cypress', 'puppeteer', 'selenium', 'none', 'other'],
|
|
1300
|
+
default: techStack.testing?.e2e?.framework || 'none',
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
type: 'input',
|
|
1304
|
+
name: 'configFile',
|
|
1305
|
+
message: 'E2E config file:',
|
|
1306
|
+
default: techStack.testing?.e2e?.configFile || 'playwright.config.ts',
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
type: 'input',
|
|
1310
|
+
name: 'testCommand',
|
|
1311
|
+
message: 'E2E test command:',
|
|
1312
|
+
default: techStack.testing?.e2e?.testCommand || 'npx playwright test',
|
|
1313
|
+
},
|
|
1314
|
+
]);
|
|
1315
|
+
|
|
1316
|
+
const unitAnswers = await inquirer.prompt([
|
|
1317
|
+
{
|
|
1318
|
+
type: 'list',
|
|
1319
|
+
name: 'framework',
|
|
1320
|
+
message: 'Unit test framework:',
|
|
1321
|
+
choices: ['vitest', 'jest', 'mocha', 'pytest', 'none', 'other'],
|
|
1322
|
+
default: techStack.testing?.unit?.framework || 'none',
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
type: 'input',
|
|
1326
|
+
name: 'testCommand',
|
|
1327
|
+
message: 'Unit test command:',
|
|
1328
|
+
default: techStack.testing?.unit?.testCommand || 'npm test',
|
|
1329
|
+
},
|
|
1330
|
+
]);
|
|
1331
|
+
|
|
1332
|
+
console.log(chalk.dim('\nLogin form selectors for E2E tests:\n'));
|
|
1333
|
+
|
|
1334
|
+
const selectorAnswers = await inquirer.prompt([
|
|
1335
|
+
{
|
|
1336
|
+
type: 'list',
|
|
1337
|
+
name: 'strategy',
|
|
1338
|
+
message: 'Selector strategy:',
|
|
1339
|
+
choices: ['data-testid', 'name', 'id', 'class', 'xpath'],
|
|
1340
|
+
default: techStack.testing?.selectors?.strategy || 'data-testid',
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
type: 'input',
|
|
1344
|
+
name: 'username',
|
|
1345
|
+
message: 'Username field selector:',
|
|
1346
|
+
default: techStack.testing?.selectors?.username || '[data-testid="username-input"]',
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
type: 'input',
|
|
1350
|
+
name: 'password',
|
|
1351
|
+
message: 'Password field selector:',
|
|
1352
|
+
default: techStack.testing?.selectors?.password || '[data-testid="password-input"]',
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
type: 'input',
|
|
1356
|
+
name: 'loginButton',
|
|
1357
|
+
message: 'Login button selector:',
|
|
1358
|
+
default: techStack.testing?.selectors?.loginButton || '[data-testid="login-submit"]',
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
type: 'input',
|
|
1362
|
+
name: 'loginSuccess',
|
|
1363
|
+
message: 'Login success indicator:',
|
|
1364
|
+
default: techStack.testing?.selectors?.loginSuccess || '[data-testid="dashboard"]',
|
|
1365
|
+
},
|
|
1366
|
+
]);
|
|
1367
|
+
|
|
1368
|
+
const credAnswers = await inquirer.prompt([
|
|
1369
|
+
{
|
|
1370
|
+
type: 'input',
|
|
1371
|
+
name: 'usernameEnvVar',
|
|
1372
|
+
message: 'Username environment variable:',
|
|
1373
|
+
default: techStack.testing?.credentials?.usernameEnvVar || 'TEST_USER_USERNAME',
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
type: 'input',
|
|
1377
|
+
name: 'passwordEnvVar',
|
|
1378
|
+
message: 'Password environment variable:',
|
|
1379
|
+
default: techStack.testing?.credentials?.passwordEnvVar || 'TEST_USER_PASSWORD',
|
|
1380
|
+
},
|
|
1381
|
+
]);
|
|
1382
|
+
|
|
1383
|
+
techStack.testing = {
|
|
1384
|
+
e2e: e2eAnswers,
|
|
1385
|
+
unit: unitAnswers,
|
|
1386
|
+
selectors: selectorAnswers,
|
|
1387
|
+
credentials: credAnswers,
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
const path = saveTechStack(techStack);
|
|
1391
|
+
showSuccess('Testing Settings Updated', [`File: ${path}`]);
|
|
1392
|
+
|
|
1393
|
+
return await configureTechStackSettings();
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* Edit deployment settings
|
|
1398
|
+
*/
|
|
1399
|
+
async function editDeploymentSettings() {
|
|
1400
|
+
showHeader('Deployment Settings');
|
|
1401
|
+
|
|
1402
|
+
const techStack = loadTechStack() || { deployment: { frontend: {}, backend: {} } };
|
|
1403
|
+
|
|
1404
|
+
console.log(chalk.dim('Frontend deployment:\n'));
|
|
1405
|
+
|
|
1406
|
+
const frontendAnswers = await inquirer.prompt([
|
|
1407
|
+
{
|
|
1408
|
+
type: 'list',
|
|
1409
|
+
name: 'platform',
|
|
1410
|
+
message: 'Frontend platform:',
|
|
1411
|
+
choices: ['cloudflare', 'vercel', 'netlify', 'aws-amplify', 'firebase', 'github-pages', 'railway', 'self-hosted', 'none', 'other'],
|
|
1412
|
+
default: techStack.deployment?.frontend?.platform || 'none',
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
type: 'input',
|
|
1416
|
+
name: 'projectName',
|
|
1417
|
+
message: 'Project name (on platform):',
|
|
1418
|
+
default: techStack.deployment?.frontend?.projectName || '',
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
type: 'input',
|
|
1422
|
+
name: 'productionUrl',
|
|
1423
|
+
message: 'Production URL:',
|
|
1424
|
+
default: techStack.deployment?.frontend?.productionUrl || '',
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
type: 'input',
|
|
1428
|
+
name: 'deployCommand',
|
|
1429
|
+
message: 'Deploy command:',
|
|
1430
|
+
default: techStack.deployment?.frontend?.deployCommand || '',
|
|
1431
|
+
},
|
|
1432
|
+
]);
|
|
1433
|
+
|
|
1434
|
+
console.log(chalk.dim('\nBackend deployment:\n'));
|
|
1435
|
+
|
|
1436
|
+
const backendAnswers = await inquirer.prompt([
|
|
1437
|
+
{
|
|
1438
|
+
type: 'list',
|
|
1439
|
+
name: 'platform',
|
|
1440
|
+
message: 'Backend platform:',
|
|
1441
|
+
choices: ['railway', 'heroku', 'aws', 'gcp', 'azure', 'digitalocean', 'render', 'fly', 'self-hosted', 'none', 'other'],
|
|
1442
|
+
default: techStack.deployment?.backend?.platform || 'none',
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
type: 'input',
|
|
1446
|
+
name: 'projectId',
|
|
1447
|
+
message: 'Project ID:',
|
|
1448
|
+
default: techStack.deployment?.backend?.projectId || '',
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
type: 'input',
|
|
1452
|
+
name: 'serviceId',
|
|
1453
|
+
message: 'Service ID:',
|
|
1454
|
+
default: techStack.deployment?.backend?.serviceId || '',
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
type: 'input',
|
|
1458
|
+
name: 'productionUrl',
|
|
1459
|
+
message: 'Production URL:',
|
|
1460
|
+
default: techStack.deployment?.backend?.productionUrl || '',
|
|
1461
|
+
},
|
|
1462
|
+
]);
|
|
1463
|
+
|
|
1464
|
+
const cicdAnswer = await inquirer.prompt([
|
|
1465
|
+
{
|
|
1466
|
+
type: 'list',
|
|
1467
|
+
name: 'cicd',
|
|
1468
|
+
message: 'CI/CD platform:',
|
|
1469
|
+
choices: ['github-actions', 'gitlab-ci', 'circleci', 'jenkins', 'azure-devops', 'none', 'other'],
|
|
1470
|
+
default: techStack.deployment?.cicd || 'none',
|
|
1471
|
+
},
|
|
1472
|
+
]);
|
|
1473
|
+
|
|
1474
|
+
techStack.deployment = {
|
|
1475
|
+
frontend: frontendAnswers,
|
|
1476
|
+
backend: backendAnswers,
|
|
1477
|
+
cicd: cicdAnswer.cicd,
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
// Update production URLs
|
|
1481
|
+
if (!techStack.urls) techStack.urls = { production: {} };
|
|
1482
|
+
techStack.urls.production = {
|
|
1483
|
+
frontend: frontendAnswers.productionUrl,
|
|
1484
|
+
backend: backendAnswers.productionUrl,
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
const path = saveTechStack(techStack);
|
|
1488
|
+
showSuccess('Deployment Settings Updated', [`File: ${path}`]);
|
|
1489
|
+
|
|
1490
|
+
return await configureTechStackSettings();
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Edit dev environment
|
|
1495
|
+
*/
|
|
1496
|
+
async function editDevEnvironment() {
|
|
1497
|
+
showHeader('Dev Environment Settings');
|
|
1498
|
+
|
|
1499
|
+
const techStack = loadTechStack() || { devEnvironment: { tunnel: {} } };
|
|
1500
|
+
|
|
1501
|
+
const tunnelAnswers = await inquirer.prompt([
|
|
1502
|
+
{
|
|
1503
|
+
type: 'list',
|
|
1504
|
+
name: 'service',
|
|
1505
|
+
message: 'Tunnel service:',
|
|
1506
|
+
choices: ['ngrok', 'localtunnel', 'cloudflare-tunnel', 'serveo', 'none', 'other'],
|
|
1507
|
+
default: techStack.devEnvironment?.tunnel?.service || 'none',
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
type: 'input',
|
|
1511
|
+
name: 'url',
|
|
1512
|
+
message: 'Tunnel URL (when active):',
|
|
1513
|
+
default: techStack.devEnvironment?.tunnel?.url || '',
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
type: 'input',
|
|
1517
|
+
name: 'subdomain',
|
|
1518
|
+
message: 'Tunnel subdomain:',
|
|
1519
|
+
default: techStack.devEnvironment?.tunnel?.subdomain || '',
|
|
1520
|
+
},
|
|
1521
|
+
{
|
|
1522
|
+
type: 'input',
|
|
1523
|
+
name: 'startCommand',
|
|
1524
|
+
message: 'Tunnel start command:',
|
|
1525
|
+
default: techStack.devEnvironment?.tunnel?.startCommand || '',
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
type: 'number',
|
|
1529
|
+
name: 'adminPort',
|
|
1530
|
+
message: 'Tunnel admin port (for ngrok: 4040):',
|
|
1531
|
+
default: techStack.devEnvironment?.tunnel?.adminPort || 4040,
|
|
1532
|
+
},
|
|
1533
|
+
]);
|
|
1534
|
+
|
|
1535
|
+
const envAnswers = await inquirer.prompt([
|
|
1536
|
+
{
|
|
1537
|
+
type: 'list',
|
|
1538
|
+
name: 'container',
|
|
1539
|
+
message: 'Container runtime:',
|
|
1540
|
+
choices: ['docker', 'podman', 'colima', 'orbstack', 'none', 'other'],
|
|
1541
|
+
default: techStack.devEnvironment?.container || 'none',
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
type: 'list',
|
|
1545
|
+
name: 'packageManager',
|
|
1546
|
+
message: 'Package manager:',
|
|
1547
|
+
choices: ['npm', 'yarn', 'pnpm', 'bun', 'pip', 'poetry', 'cargo', 'go-mod', 'other'],
|
|
1548
|
+
default: techStack.devEnvironment?.packageManager || 'npm',
|
|
1549
|
+
},
|
|
1550
|
+
]);
|
|
1551
|
+
|
|
1552
|
+
techStack.devEnvironment = {
|
|
1553
|
+
tunnel: tunnelAnswers,
|
|
1554
|
+
container: envAnswers.container,
|
|
1555
|
+
packageManager: envAnswers.packageManager,
|
|
1556
|
+
};
|
|
1557
|
+
|
|
1558
|
+
// Update tunnel URLs
|
|
1559
|
+
if (tunnelAnswers.url) {
|
|
1560
|
+
if (!techStack.urls) techStack.urls = { tunnel: {} };
|
|
1561
|
+
techStack.urls.tunnel = {
|
|
1562
|
+
frontend: tunnelAnswers.url,
|
|
1563
|
+
backend: tunnelAnswers.url,
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const path = saveTechStack(techStack);
|
|
1568
|
+
showSuccess('Dev Environment Updated', [`File: ${path}`]);
|
|
1569
|
+
|
|
1570
|
+
return await configureTechStackSettings();
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
/**
|
|
1574
|
+
* View current tech stack
|
|
1575
|
+
*/
|
|
1576
|
+
async function viewTechStack() {
|
|
1577
|
+
showHeader('Current Tech Stack');
|
|
1578
|
+
|
|
1579
|
+
const techStack = loadTechStack();
|
|
1580
|
+
|
|
1581
|
+
if (!techStack) {
|
|
1582
|
+
showWarning('No tech-stack.json found.');
|
|
1583
|
+
console.log(chalk.dim('Run "Auto-Detect Tech Stack" to create one.'));
|
|
1584
|
+
} else {
|
|
1585
|
+
console.log(JSON.stringify(techStack, null, 2));
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
console.log('');
|
|
1589
|
+
const { action } = await inquirer.prompt([
|
|
1590
|
+
{
|
|
1591
|
+
type: 'list',
|
|
1592
|
+
name: 'action',
|
|
1593
|
+
message: 'What next?',
|
|
1594
|
+
choices: [
|
|
1595
|
+
{ name: 'Back to Tech Stack Settings', value: 'back' },
|
|
1596
|
+
{ name: 'Exit', value: 'exit' },
|
|
1597
|
+
],
|
|
1598
|
+
},
|
|
1599
|
+
]);
|
|
1600
|
+
|
|
1601
|
+
if (action === 'back') {
|
|
1602
|
+
return await configureTechStackSettings();
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
return null;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/**
|
|
1609
|
+
* Apply tech stack templates
|
|
1610
|
+
*/
|
|
1611
|
+
async function applyTechStackTemplates() {
|
|
1612
|
+
showHeader('Apply Tech Stack Templates');
|
|
1613
|
+
|
|
1614
|
+
const techStack = loadTechStack();
|
|
1615
|
+
|
|
1616
|
+
if (!techStack) {
|
|
1617
|
+
showWarning('No tech-stack.json found. Run "Auto-Detect Tech Stack" first.');
|
|
1618
|
+
return await configureTechStackSettings();
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
console.log(chalk.dim('This will update .claude files to use your tech stack configuration.\n'));
|
|
1622
|
+
|
|
1623
|
+
const { dryRun } = await inquirer.prompt([
|
|
1624
|
+
{
|
|
1625
|
+
type: 'confirm',
|
|
1626
|
+
name: 'dryRun',
|
|
1627
|
+
message: 'Run in dry-run mode first? (preview changes)',
|
|
1628
|
+
default: true,
|
|
1629
|
+
},
|
|
1630
|
+
]);
|
|
1631
|
+
|
|
1632
|
+
const claudeDir = join(process.cwd(), '.claude');
|
|
1633
|
+
if (!existsSync(claudeDir)) {
|
|
1634
|
+
showWarning('No .claude directory found.');
|
|
1635
|
+
return await configureTechStackSettings();
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
const spinner = ora('Processing templates...').start();
|
|
1639
|
+
|
|
1640
|
+
try {
|
|
1641
|
+
const results = processDirectory(claudeDir, techStack, {
|
|
1642
|
+
dryRun,
|
|
1643
|
+
extensions: ['.md', '.json'],
|
|
1644
|
+
exclude: ['node_modules', '.git'],
|
|
1645
|
+
});
|
|
1646
|
+
|
|
1647
|
+
spinner.succeed('Template processing complete');
|
|
1648
|
+
|
|
1649
|
+
// Show summary
|
|
1650
|
+
const changed = results.filter((r) => r.changed);
|
|
1651
|
+
const totalPlaceholders = results.reduce((sum, r) => sum + r.placeholders, 0);
|
|
1652
|
+
const totalReplaced = results.reduce((sum, r) => sum + r.replaced, 0);
|
|
1653
|
+
|
|
1654
|
+
console.log('');
|
|
1655
|
+
console.log(chalk.cyan('Summary:'));
|
|
1656
|
+
console.log(` Files scanned: ${results.length}`);
|
|
1657
|
+
console.log(` Files changed: ${changed.length}`);
|
|
1658
|
+
console.log(` Placeholders found: ${totalPlaceholders}`);
|
|
1659
|
+
console.log(` Placeholders replaced: ${totalReplaced}`);
|
|
1660
|
+
|
|
1661
|
+
if (dryRun && changed.length > 0) {
|
|
1662
|
+
console.log('');
|
|
1663
|
+
console.log(chalk.yellow('Files that would be changed:'));
|
|
1664
|
+
changed.forEach((r) => console.log(` ${r.file}`));
|
|
1665
|
+
|
|
1666
|
+
const { apply } = await inquirer.prompt([
|
|
1667
|
+
{
|
|
1668
|
+
type: 'confirm',
|
|
1669
|
+
name: 'apply',
|
|
1670
|
+
message: 'Apply these changes for real?',
|
|
1671
|
+
default: true,
|
|
1672
|
+
},
|
|
1673
|
+
]);
|
|
1674
|
+
|
|
1675
|
+
if (apply) {
|
|
1676
|
+
// Run again without dry-run
|
|
1677
|
+
processDirectory(claudeDir, techStack, {
|
|
1678
|
+
dryRun: false,
|
|
1679
|
+
extensions: ['.md', '.json'],
|
|
1680
|
+
exclude: ['node_modules', '.git'],
|
|
1681
|
+
});
|
|
1682
|
+
showSuccess('Templates Applied', [`${changed.length} files updated`]);
|
|
1683
|
+
}
|
|
1684
|
+
} else if (!dryRun) {
|
|
1685
|
+
showSuccess('Templates Applied', [`${changed.length} files updated`]);
|
|
1686
|
+
}
|
|
1687
|
+
} catch (err) {
|
|
1688
|
+
spinner.fail('Template processing failed');
|
|
1689
|
+
showError('Error', err.message);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
return await configureTechStackSettings();
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* Generate the GitHub settings updater script
|
|
1697
|
+
*/
|
|
1698
|
+
function generateGitHubSettingsUpdater() {
|
|
1699
|
+
return `/**
|
|
1700
|
+
* GitHub Settings Updater
|
|
1701
|
+
*
|
|
1702
|
+
* Reads settings from .claude/settings.json and updates
|
|
1703
|
+
* GitHub-related commands and configurations.
|
|
1704
|
+
*
|
|
1705
|
+
* Usage: node .claude/hooks/tools/github-settings-updater.js
|
|
1706
|
+
*
|
|
1707
|
+
* Generated by: gtask claude-settings
|
|
1708
|
+
*/
|
|
1709
|
+
|
|
1710
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
|
|
1711
|
+
import { join, dirname } from 'path';
|
|
1712
|
+
import { fileURLToPath } from 'url';
|
|
1713
|
+
|
|
1714
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1715
|
+
const __dirname = dirname(__filename);
|
|
1716
|
+
const projectRoot = join(__dirname, '..', '..', '..');
|
|
1717
|
+
|
|
1718
|
+
/**
|
|
1719
|
+
* Load settings from .claude/settings.json
|
|
1720
|
+
*/
|
|
1721
|
+
function loadSettings() {
|
|
1722
|
+
const settingsPath = join(projectRoot, '.claude', 'settings.json');
|
|
1723
|
+
|
|
1724
|
+
if (!existsSync(settingsPath)) {
|
|
1725
|
+
console.error('ERROR: .claude/settings.json not found');
|
|
1726
|
+
console.log('Run "gtask claude-settings" to create it.');
|
|
1727
|
+
process.exit(1);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
try {
|
|
1731
|
+
return JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
1732
|
+
} catch (err) {
|
|
1733
|
+
console.error('ERROR: Failed to parse settings.json:', err.message);
|
|
1734
|
+
process.exit(1);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
/**
|
|
1739
|
+
* Update GitHub commands with settings
|
|
1740
|
+
*/
|
|
1741
|
+
function updateCommands(settings) {
|
|
1742
|
+
const github = settings.github;
|
|
1743
|
+
if (!github) {
|
|
1744
|
+
console.log('No GitHub settings found.');
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
const commandsDir = join(projectRoot, '.claude', 'commands');
|
|
1749
|
+
if (!existsSync(commandsDir)) {
|
|
1750
|
+
console.log('No .claude/commands directory found.');
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// List of GitHub-related commands to check
|
|
1755
|
+
const githubCommands = [
|
|
1756
|
+
'github-create-task.md',
|
|
1757
|
+
'github-task-start.md',
|
|
1758
|
+
'github-update.md',
|
|
1759
|
+
'github-validate.md',
|
|
1760
|
+
'create-task-list.md',
|
|
1761
|
+
];
|
|
1762
|
+
|
|
1763
|
+
let updatedCount = 0;
|
|
1764
|
+
|
|
1765
|
+
for (const cmdFile of githubCommands) {
|
|
1766
|
+
const cmdPath = join(commandsDir, cmdFile);
|
|
1767
|
+
if (!existsSync(cmdPath)) continue;
|
|
1768
|
+
|
|
1769
|
+
let content = readFileSync(cmdPath, 'utf8');
|
|
1770
|
+
let modified = false;
|
|
1771
|
+
|
|
1772
|
+
// Update repository references
|
|
1773
|
+
if (github.repository?.owner && github.repository?.name) {
|
|
1774
|
+
const repoPattern = /--repo [a-zA-Z0-9_-]+\\/[a-zA-Z0-9_-]+/g;
|
|
1775
|
+
const newRepo = \`--repo \${github.repository.owner}/\${github.repository.name}\`;
|
|
1776
|
+
if (content.match(repoPattern)) {
|
|
1777
|
+
content = content.replace(repoPattern, newRepo);
|
|
1778
|
+
modified = true;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// Update project number references
|
|
1783
|
+
if (github.project?.number) {
|
|
1784
|
+
const projectPattern = /project-number:\\s*\\d+/g;
|
|
1785
|
+
const newProject = \`project-number: \${github.project.number}\`;
|
|
1786
|
+
if (content.match(projectPattern)) {
|
|
1787
|
+
content = content.replace(projectPattern, newProject);
|
|
1788
|
+
modified = true;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
if (modified) {
|
|
1793
|
+
writeFileSync(cmdPath, content, 'utf8');
|
|
1794
|
+
console.log(\`Updated: \${cmdFile}\`);
|
|
1795
|
+
updatedCount++;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
console.log(\`\\nUpdated \${updatedCount} command files.\`);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
/**
|
|
1803
|
+
* Update CLAUDE.md with settings
|
|
1804
|
+
*/
|
|
1805
|
+
function updateClaudeMd(settings) {
|
|
1806
|
+
const github = settings.github;
|
|
1807
|
+
if (!github) return;
|
|
1808
|
+
|
|
1809
|
+
const claudeMdPath = join(projectRoot, 'CLAUDE.md');
|
|
1810
|
+
if (!existsSync(claudeMdPath)) {
|
|
1811
|
+
console.log('No CLAUDE.md found.');
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// For now, just log that CLAUDE.md could be updated
|
|
1816
|
+
// More sophisticated updates could be added here
|
|
1817
|
+
console.log('CLAUDE.md exists - manual updates may be needed for complex changes.');
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Main execution
|
|
1822
|
+
*/
|
|
1823
|
+
function main() {
|
|
1824
|
+
console.log('GitHub Settings Updater');
|
|
1825
|
+
console.log('=======================\\n');
|
|
1826
|
+
|
|
1827
|
+
const settings = loadSettings();
|
|
1828
|
+
|
|
1829
|
+
console.log('Loaded settings:');
|
|
1830
|
+
if (settings.github?.repository) {
|
|
1831
|
+
console.log(\` Repository: \${settings.github.repository.owner}/\${settings.github.repository.name}\`);
|
|
1832
|
+
}
|
|
1833
|
+
if (settings.github?.project?.number) {
|
|
1834
|
+
console.log(\` Project: #\${settings.github.project.number}\`);
|
|
1835
|
+
}
|
|
1836
|
+
console.log('');
|
|
1837
|
+
|
|
1838
|
+
updateCommands(settings);
|
|
1839
|
+
updateClaudeMd(settings);
|
|
1840
|
+
|
|
1841
|
+
console.log('\\nDone!');
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
main();
|
|
1845
|
+
`;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* Generate AGENT_ONLY_POLICY.md content
|
|
1850
|
+
*/
|
|
1851
|
+
function generateAgentOnlyPolicy() {
|
|
1852
|
+
return `# Agent Only Execution Policy (STRICT)
|
|
1853
|
+
|
|
1854
|
+
**ENFORCEMENT LEVEL: MANDATORY**
|
|
1855
|
+
|
|
1856
|
+
All substantive work (implementation, analysis, writing) MUST be delegated to agents via the Task tool.
|
|
1857
|
+
|
|
1858
|
+
---
|
|
1859
|
+
|
|
1860
|
+
## DEFAULT BEHAVIOR: Delegate via Task Tool
|
|
1861
|
+
|
|
1862
|
+
**Agents are the DEFAULT. Direct tools are the EXCEPTION.**
|
|
1863
|
+
|
|
1864
|
+
ALL substantive work MUST use Task tool with appropriate agent:
|
|
1865
|
+
- Investigation/Analysis → \`Task(subagent_type="Explore")\`
|
|
1866
|
+
- Implementation → \`Task(subagent_type="general-purpose")\` or custom agent
|
|
1867
|
+
- Multi-step tasks → Task with specialist agent
|
|
1868
|
+
- Code review/debugging → Task with appropriate agent
|
|
1869
|
+
|
|
1870
|
+
---
|
|
1871
|
+
|
|
1872
|
+
## Permitted Direct Tools (STRICTLY LIMITED)
|
|
1873
|
+
|
|
1874
|
+
| Tool | Call Limit | Permitted Purpose |
|
|
1875
|
+
|------|------------|-------------------|
|
|
1876
|
+
| **Read** | 2 calls max | Verify agent output, check single file |
|
|
1877
|
+
| **Glob** | 2 calls max | Quick path existence check |
|
|
1878
|
+
| **Grep** | 2 calls max | Single pattern search |
|
|
1879
|
+
| **TodoWrite** | Unlimited | Task tracking and planning |
|
|
1880
|
+
| **AskUserQuestion** | Unlimited | Clarifying with user |
|
|
1881
|
+
| **Task** | Unlimited | Agent dispatch (REQUIRED) |
|
|
1882
|
+
|
|
1883
|
+
### FORBIDDEN Direct Tools
|
|
1884
|
+
|
|
1885
|
+
| Tool | Status | Alternative |
|
|
1886
|
+
|------|--------|-------------|
|
|
1887
|
+
| **Bash** | FORBIDDEN | Delegate to \`general-purpose\` or \`Explore\` agent |
|
|
1888
|
+
| **Write** | FORBIDDEN | Delegate to appropriate agent |
|
|
1889
|
+
| **Edit** | FORBIDDEN | Delegate to appropriate agent |
|
|
1890
|
+
| **WebFetch** | FORBIDDEN | Delegate to agent |
|
|
1891
|
+
| **WebSearch** | FORBIDDEN | Delegate to agent |
|
|
1892
|
+
|
|
1893
|
+
---
|
|
1894
|
+
|
|
1895
|
+
## Immediate Delegation Triggers
|
|
1896
|
+
|
|
1897
|
+
When user asks to do ANY of the following, spawn an agent IMMEDIATELY as your FIRST action:
|
|
1898
|
+
|
|
1899
|
+
| Trigger Word | Agent to Use |
|
|
1900
|
+
|--------------|--------------|
|
|
1901
|
+
| review | \`Explore\` |
|
|
1902
|
+
| analyze | \`Explore\` |
|
|
1903
|
+
| investigate | \`Explore\` |
|
|
1904
|
+
| fix | \`general-purpose\` |
|
|
1905
|
+
| implement | \`general-purpose\` |
|
|
1906
|
+
| debug | \`general-purpose\` |
|
|
1907
|
+
| research | \`Explore\` |
|
|
1908
|
+
| find | \`Explore\` |
|
|
1909
|
+
|
|
1910
|
+
**Do NOT make direct tool calls first. Delegate IMMEDIATELY.**
|
|
1911
|
+
|
|
1912
|
+
---
|
|
1913
|
+
|
|
1914
|
+
## Violation Detection & Self-Correction
|
|
1915
|
+
|
|
1916
|
+
### You Are Violating This Policy If:
|
|
1917
|
+
|
|
1918
|
+
1. You make 3+ direct Read/Glob/Grep calls without delegating
|
|
1919
|
+
2. You use Bash directly for ANY reason
|
|
1920
|
+
3. You analyze code across multiple files without spawning an agent
|
|
1921
|
+
4. You perform git operations directly instead of via agent
|
|
1922
|
+
5. You investigate/debug without first spawning an agent
|
|
1923
|
+
|
|
1924
|
+
### Self-Correction Protocol:
|
|
1925
|
+
|
|
1926
|
+
If you catch yourself making too many direct calls:
|
|
1927
|
+
|
|
1928
|
+
\`\`\`
|
|
1929
|
+
STOP - Policy violation detected
|
|
1930
|
+
Delegating remaining work to agent...
|
|
1931
|
+
→ Task(subagent_type="Explore", prompt="Continue investigation of...")
|
|
1932
|
+
\`\`\`
|
|
1933
|
+
|
|
1934
|
+
---
|
|
1935
|
+
|
|
1936
|
+
## Agent Dispatch Protocol
|
|
1937
|
+
|
|
1938
|
+
Use Claude Code's Task tool to dispatch work:
|
|
1939
|
+
|
|
1940
|
+
\`\`\`
|
|
1941
|
+
Task(
|
|
1942
|
+
subagent_type="<agent-type>",
|
|
1943
|
+
prompt="<detailed task description>",
|
|
1944
|
+
description="<3-5 word summary>"
|
|
1945
|
+
)
|
|
1946
|
+
\`\`\`
|
|
1947
|
+
|
|
1948
|
+
### Built-in Agent Types (ALWAYS AVAILABLE)
|
|
1949
|
+
|
|
1950
|
+
| Agent | Use For |
|
|
1951
|
+
|-------|---------|
|
|
1952
|
+
| \`general-purpose\` | Implementation, debugging, multi-step tasks |
|
|
1953
|
+
| \`Explore\` | File search, codebase exploration, pattern finding |
|
|
1954
|
+
| \`Plan\` | Architecture planning, implementation strategy |
|
|
1955
|
+
|
|
1956
|
+
---
|
|
1957
|
+
|
|
1958
|
+
*Generated by gtask claude-settings - ${new Date().toISOString()}*
|
|
1959
|
+
`;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/**
|
|
1963
|
+
* Generate agents.json content
|
|
1964
|
+
*/
|
|
1965
|
+
function generateAgentsJson() {
|
|
1966
|
+
const agents = {
|
|
1967
|
+
version: '1.0',
|
|
1968
|
+
agents: [
|
|
1969
|
+
{
|
|
1970
|
+
name: 'frontend_dev',
|
|
1971
|
+
scope: 'React, TypeScript, UI components, styling',
|
|
1972
|
+
when_to_use: 'Frontend implementation, component creation, UI fixes',
|
|
1973
|
+
when_not_to_use: 'Backend work, database operations',
|
|
1974
|
+
output_contract: {
|
|
1975
|
+
required_fields: ['files_modified', 'components_affected'],
|
|
1976
|
+
format: 'Concise summary with code snippets',
|
|
1977
|
+
},
|
|
1978
|
+
},
|
|
1979
|
+
{
|
|
1980
|
+
name: 'backend_dev',
|
|
1981
|
+
scope: 'API endpoints, Python services, database queries',
|
|
1982
|
+
when_to_use: 'Backend implementation, API creation, service logic',
|
|
1983
|
+
when_not_to_use: 'UI work, frontend components',
|
|
1984
|
+
output_contract: {
|
|
1985
|
+
required_fields: ['endpoints_modified', 'services_affected'],
|
|
1986
|
+
format: 'Concise summary with code snippets',
|
|
1987
|
+
},
|
|
1988
|
+
},
|
|
1989
|
+
{
|
|
1990
|
+
name: 'test_review',
|
|
1991
|
+
scope: 'Testing, code review, quality assurance',
|
|
1992
|
+
when_to_use: 'Writing tests, reviewing code, finding bugs',
|
|
1993
|
+
when_not_to_use: 'Feature implementation',
|
|
1994
|
+
output_contract: {
|
|
1995
|
+
required_fields: ['tests_added', 'issues_found'],
|
|
1996
|
+
format: 'Test results and recommendations',
|
|
1997
|
+
},
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
name: 'docs_writer',
|
|
2001
|
+
scope: 'Documentation, README files, API docs',
|
|
2002
|
+
when_to_use: 'Writing documentation, updating README, API docs',
|
|
2003
|
+
when_not_to_use: 'Code implementation',
|
|
2004
|
+
output_contract: {
|
|
2005
|
+
required_fields: ['files_updated'],
|
|
2006
|
+
format: 'Documentation content',
|
|
2007
|
+
},
|
|
2008
|
+
},
|
|
2009
|
+
],
|
|
2010
|
+
};
|
|
2011
|
+
|
|
2012
|
+
return JSON.stringify(agents, null, 2);
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* Generate Windows batch launcher
|
|
2017
|
+
*/
|
|
2018
|
+
function generateWindowsBatch() {
|
|
2019
|
+
return `@echo off
|
|
2020
|
+
REM ============================================================
|
|
2021
|
+
REM Claude Agent-Only Mode Launcher (Windows Batch)
|
|
2022
|
+
REM ============================================================
|
|
2023
|
+
REM
|
|
2024
|
+
REM This batch file launches the PowerShell script that starts
|
|
2025
|
+
REM Claude in Agent-Only execution mode with policy enforcement
|
|
2026
|
+
REM via --append-system-prompt.
|
|
2027
|
+
REM
|
|
2028
|
+
REM Usage:
|
|
2029
|
+
REM - Double-click this file to start Claude in Agent-Only mode
|
|
2030
|
+
REM - Or run from command line: start-agent-only.bat
|
|
2031
|
+
REM - With arguments: start-agent-only.bat --permission-mode acceptEdits
|
|
2032
|
+
REM
|
|
2033
|
+
REM Generated by: gtask claude-settings
|
|
2034
|
+
REM ============================================================
|
|
2035
|
+
|
|
2036
|
+
setlocal
|
|
2037
|
+
|
|
2038
|
+
REM Get the directory where this batch file is located
|
|
2039
|
+
set "SCRIPT_DIR=%~dp0"
|
|
2040
|
+
set "PS_SCRIPT=%SCRIPT_DIR%claude-agent-only.ps1"
|
|
2041
|
+
|
|
2042
|
+
REM Check if PowerShell script exists
|
|
2043
|
+
if not exist "%PS_SCRIPT%" (
|
|
2044
|
+
echo ERROR: PowerShell script not found!
|
|
2045
|
+
echo Expected: %PS_SCRIPT%
|
|
2046
|
+
echo.
|
|
2047
|
+
echo Please ensure claude-agent-only.ps1 exists in the same directory.
|
|
2048
|
+
pause
|
|
2049
|
+
exit /b 1
|
|
2050
|
+
)
|
|
2051
|
+
|
|
2052
|
+
REM Execute PowerShell script with execution policy bypass
|
|
2053
|
+
echo Starting Claude in Agent-Only mode...
|
|
2054
|
+
echo.
|
|
2055
|
+
|
|
2056
|
+
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%PS_SCRIPT%" %*
|
|
2057
|
+
|
|
2058
|
+
REM Capture exit code from PowerShell
|
|
2059
|
+
set EXIT_CODE=%ERRORLEVEL%
|
|
2060
|
+
|
|
2061
|
+
REM Exit with the same code
|
|
2062
|
+
exit /b %EXIT_CODE%
|
|
2063
|
+
`;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
/**
|
|
2067
|
+
* Generate PowerShell launcher
|
|
2068
|
+
*/
|
|
2069
|
+
function generatePowerShellLauncher() {
|
|
2070
|
+
return `<#
|
|
2071
|
+
.SYNOPSIS
|
|
2072
|
+
Launches Claude in Agent-Only Execution Mode
|
|
2073
|
+
|
|
2074
|
+
.DESCRIPTION
|
|
2075
|
+
This script loads the agent definitions and policy file, then launches
|
|
2076
|
+
Claude with the agent-only execution policy appended to the system prompt.
|
|
2077
|
+
|
|
2078
|
+
.PARAMETER Args
|
|
2079
|
+
Additional arguments to pass to the claude CLI
|
|
2080
|
+
|
|
2081
|
+
.EXAMPLE
|
|
2082
|
+
.\\claude-agent-only.ps1
|
|
2083
|
+
.\\claude-agent-only.ps1 --permission-mode acceptEdits
|
|
2084
|
+
|
|
2085
|
+
Generated by: gtask claude-settings
|
|
2086
|
+
#>
|
|
2087
|
+
|
|
2088
|
+
param(
|
|
2089
|
+
[Parameter(ValueFromRemainingArguments = $true)]
|
|
2090
|
+
[string[]]$RemainingArgs
|
|
2091
|
+
)
|
|
2092
|
+
|
|
2093
|
+
$ErrorActionPreference = "Stop"
|
|
2094
|
+
|
|
2095
|
+
# Define paths
|
|
2096
|
+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
2097
|
+
$ClaudeDir = Join-Path $ScriptDir ".claude"
|
|
2098
|
+
$PolicyFile = Join-Path $ClaudeDir "AGENT_ONLY_POLICY.md"
|
|
2099
|
+
$AgentsFile = Join-Path $ClaudeDir "agents.json"
|
|
2100
|
+
|
|
2101
|
+
# Banner
|
|
2102
|
+
Write-Host ""
|
|
2103
|
+
Write-Host "================================================" -ForegroundColor Cyan
|
|
2104
|
+
Write-Host " CLAUDE - AGENT-ONLY EXECUTION MODE (STRICT)" -ForegroundColor Cyan
|
|
2105
|
+
Write-Host "================================================" -ForegroundColor Cyan
|
|
2106
|
+
Write-Host ""
|
|
2107
|
+
|
|
2108
|
+
# Validate required files exist
|
|
2109
|
+
if (-not (Test-Path $PolicyFile)) {
|
|
2110
|
+
Write-Host "ERROR: Policy file not found!" -ForegroundColor Red
|
|
2111
|
+
Write-Host "Expected: $PolicyFile" -ForegroundColor Yellow
|
|
2112
|
+
Write-Host ""
|
|
2113
|
+
Write-Host "Run 'gtask claude-settings' to create it." -ForegroundColor Yellow
|
|
2114
|
+
exit 1
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
if (-not (Test-Path $AgentsFile)) {
|
|
2118
|
+
Write-Host "WARNING: Agents file not found." -ForegroundColor Yellow
|
|
2119
|
+
Write-Host "Using built-in agents only." -ForegroundColor Yellow
|
|
2120
|
+
} else {
|
|
2121
|
+
try {
|
|
2122
|
+
$AgentsContent = Get-Content $AgentsFile -Raw | ConvertFrom-Json
|
|
2123
|
+
$AgentCount = $AgentsContent.agents.Count
|
|
2124
|
+
Write-Host "Loaded $AgentCount custom agents from agents.json" -ForegroundColor Green
|
|
2125
|
+
} catch {
|
|
2126
|
+
Write-Host "WARNING: agents.json is not valid JSON" -ForegroundColor Yellow
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
Write-Host ""
|
|
2131
|
+
Write-Host "Direct tools: Read/Glob/Grep (2 max) | FORBIDDEN: Bash/Write/Edit" -ForegroundColor Yellow
|
|
2132
|
+
Write-Host ""
|
|
2133
|
+
|
|
2134
|
+
# Read policy content
|
|
2135
|
+
$PolicyContent = Get-Content $PolicyFile -Raw
|
|
2136
|
+
|
|
2137
|
+
# Build the append system prompt
|
|
2138
|
+
$AppendPrompt = @"
|
|
2139
|
+
|
|
2140
|
+
======================================================================
|
|
2141
|
+
AGENT-ONLY EXECUTION MODE ACTIVE
|
|
2142
|
+
======================================================================
|
|
2143
|
+
|
|
2144
|
+
$PolicyContent
|
|
2145
|
+
"@
|
|
2146
|
+
|
|
2147
|
+
# Build claude command arguments
|
|
2148
|
+
$ClaudeArgs = @(
|
|
2149
|
+
"--append-system-prompt"
|
|
2150
|
+
$AppendPrompt
|
|
2151
|
+
)
|
|
2152
|
+
|
|
2153
|
+
# Add any remaining arguments passed to this script
|
|
2154
|
+
if ($RemainingArgs) {
|
|
2155
|
+
$ClaudeArgs += $RemainingArgs
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
Write-Host "Starting Claude in Agent-Only mode..." -ForegroundColor Green
|
|
2159
|
+
Write-Host ""
|
|
2160
|
+
Write-Host "================================================" -ForegroundColor Cyan
|
|
2161
|
+
Write-Host ""
|
|
2162
|
+
|
|
2163
|
+
# Execute claude
|
|
2164
|
+
& claude @ClaudeArgs
|
|
2165
|
+
exit $LASTEXITCODE
|
|
2166
|
+
`;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
/**
|
|
2170
|
+
* Generate Bash launcher
|
|
2171
|
+
*/
|
|
2172
|
+
function generateBashLauncher() {
|
|
2173
|
+
return `#!/bin/bash
|
|
2174
|
+
# ============================================================
|
|
2175
|
+
# Claude Agent-Only Mode Launcher (Mac/Linux)
|
|
2176
|
+
# ============================================================
|
|
2177
|
+
#
|
|
2178
|
+
# This script launches Claude in Agent-Only execution mode
|
|
2179
|
+
# with policy enforcement via --append-system-prompt.
|
|
2180
|
+
#
|
|
2181
|
+
# Usage:
|
|
2182
|
+
# ./claude-agent-only.sh
|
|
2183
|
+
# ./claude-agent-only.sh --permission-mode acceptEdits
|
|
2184
|
+
#
|
|
2185
|
+
# Generated by: gtask claude-settings
|
|
2186
|
+
# ============================================================
|
|
2187
|
+
|
|
2188
|
+
set -e
|
|
2189
|
+
|
|
2190
|
+
# Get the directory where this script is located
|
|
2191
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2192
|
+
CLAUDE_DIR="$SCRIPT_DIR/.claude"
|
|
2193
|
+
POLICY_FILE="$CLAUDE_DIR/AGENT_ONLY_POLICY.md"
|
|
2194
|
+
AGENTS_FILE="$CLAUDE_DIR/agents.json"
|
|
2195
|
+
|
|
2196
|
+
# Banner
|
|
2197
|
+
echo ""
|
|
2198
|
+
echo "================================================"
|
|
2199
|
+
echo " CLAUDE - AGENT-ONLY EXECUTION MODE (STRICT)"
|
|
2200
|
+
echo "================================================"
|
|
2201
|
+
echo ""
|
|
2202
|
+
|
|
2203
|
+
# Validate required files exist
|
|
2204
|
+
if [ ! -f "$POLICY_FILE" ]; then
|
|
2205
|
+
echo "ERROR: Policy file not found!"
|
|
2206
|
+
echo "Expected: $POLICY_FILE"
|
|
2207
|
+
echo ""
|
|
2208
|
+
echo "Run 'gtask claude-settings' to create it."
|
|
2209
|
+
exit 1
|
|
2210
|
+
fi
|
|
2211
|
+
|
|
2212
|
+
if [ -f "$AGENTS_FILE" ]; then
|
|
2213
|
+
AGENT_COUNT=$(jq '.agents | length' "$AGENTS_FILE" 2>/dev/null || echo "0")
|
|
2214
|
+
echo "Loaded $AGENT_COUNT custom agents from agents.json"
|
|
2215
|
+
else
|
|
2216
|
+
echo "WARNING: Agents file not found. Using built-in agents only."
|
|
2217
|
+
fi
|
|
2218
|
+
|
|
2219
|
+
echo ""
|
|
2220
|
+
echo "Direct tools: Read/Glob/Grep (2 max) | FORBIDDEN: Bash/Write/Edit"
|
|
2221
|
+
echo ""
|
|
2222
|
+
|
|
2223
|
+
# Read policy content
|
|
2224
|
+
POLICY_CONTENT=$(cat "$POLICY_FILE")
|
|
2225
|
+
|
|
2226
|
+
# Build the append system prompt
|
|
2227
|
+
APPEND_PROMPT="
|
|
2228
|
+
======================================================================
|
|
2229
|
+
AGENT-ONLY EXECUTION MODE ACTIVE
|
|
2230
|
+
======================================================================
|
|
2231
|
+
|
|
2232
|
+
$POLICY_CONTENT
|
|
2233
|
+
"
|
|
2234
|
+
|
|
2235
|
+
echo "Starting Claude in Agent-Only mode..."
|
|
2236
|
+
echo ""
|
|
2237
|
+
echo "================================================"
|
|
2238
|
+
echo ""
|
|
2239
|
+
|
|
2240
|
+
# Execute claude with appended prompt
|
|
2241
|
+
claude --append-system-prompt "$APPEND_PROMPT" "$@"
|
|
2242
|
+
`;
|
|
2243
|
+
}
|