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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/OVERVIEW.md +597 -0
  3. package/README.md +439 -0
  4. package/bin/gtask.js +282 -0
  5. package/bin/postinstall.js +53 -0
  6. package/package.json +69 -0
  7. package/src/agents/phase-dev-templates.js +1011 -0
  8. package/src/agents/templates.js +668 -0
  9. package/src/analysis/checklist-parser.js +414 -0
  10. package/src/analysis/codebase.js +481 -0
  11. package/src/cli/menu.js +958 -0
  12. package/src/commands/claude-audit.js +1482 -0
  13. package/src/commands/claude-settings.js +2243 -0
  14. package/src/commands/create-agent.js +681 -0
  15. package/src/commands/create-command.js +337 -0
  16. package/src/commands/create-hook.js +262 -0
  17. package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
  18. package/src/commands/create-phase-dev/documentation-generator.js +352 -0
  19. package/src/commands/create-phase-dev/post-completion.js +404 -0
  20. package/src/commands/create-phase-dev/scale-calculator.js +344 -0
  21. package/src/commands/create-phase-dev/wizard.js +492 -0
  22. package/src/commands/create-phase-dev.js +481 -0
  23. package/src/commands/create-skill.js +313 -0
  24. package/src/commands/create.js +446 -0
  25. package/src/commands/decompose.js +392 -0
  26. package/src/commands/detect-tech-stack.js +768 -0
  27. package/src/commands/explore-mcp/claude-md-updater.js +252 -0
  28. package/src/commands/explore-mcp/mcp-installer.js +346 -0
  29. package/src/commands/explore-mcp/mcp-registry.js +438 -0
  30. package/src/commands/explore-mcp.js +638 -0
  31. package/src/commands/gtask-init.js +641 -0
  32. package/src/commands/help.js +128 -0
  33. package/src/commands/init.js +1890 -0
  34. package/src/commands/install.js +250 -0
  35. package/src/commands/list.js +116 -0
  36. package/src/commands/roadmap.js +750 -0
  37. package/src/commands/setup-wizard.js +482 -0
  38. package/src/commands/setup.js +351 -0
  39. package/src/commands/sync.js +534 -0
  40. package/src/commands/test-run.js +456 -0
  41. package/src/commands/test-setup.js +456 -0
  42. package/src/commands/validate.js +67 -0
  43. package/src/config/tech-stack.defaults.json +182 -0
  44. package/src/config/tech-stack.schema.json +502 -0
  45. package/src/github/client.js +359 -0
  46. package/src/index.js +84 -0
  47. package/src/templates/claude-command.js +244 -0
  48. package/src/templates/issue-body.js +284 -0
  49. package/src/testing/config.js +411 -0
  50. package/src/utils/template-engine.js +398 -0
  51. package/src/utils/validate-templates.js +223 -0
  52. package/src/utils.js +396 -0
  53. package/templates/commands/ccasp-setup.template.md +113 -0
  54. package/templates/commands/context-audit.template.md +97 -0
  55. package/templates/commands/create-task-list.template.md +382 -0
  56. package/templates/commands/deploy-full.template.md +261 -0
  57. package/templates/commands/github-task-start.template.md +99 -0
  58. package/templates/commands/github-update.template.md +69 -0
  59. package/templates/commands/happy-start.template.md +117 -0
  60. package/templates/commands/phase-track.template.md +142 -0
  61. package/templates/commands/tunnel-start.template.md +127 -0
  62. package/templates/commands/tunnel-stop.template.md +106 -0
  63. package/templates/hooks/context-guardian.template.js +173 -0
  64. package/templates/hooks/deployment-orchestrator.template.js +219 -0
  65. package/templates/hooks/github-progress-hook.template.js +197 -0
  66. package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
  67. 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
+ }