claudeforge-cli 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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +431 -0
  3. package/bin/cli.js +155 -0
  4. package/package.json +43 -0
  5. package/src/commands/add.js +205 -0
  6. package/src/commands/create.js +218 -0
  7. package/src/commands/github.js +479 -0
  8. package/src/commands/init.js +107 -0
  9. package/src/commands/project.js +123 -0
  10. package/src/commands/status.js +183 -0
  11. package/src/commands/upgrade.js +114 -0
  12. package/src/index.js +6 -0
  13. package/src/logger.js +90 -0
  14. package/src/scaffolder.js +45 -0
  15. package/src/stack-detector.js +62 -0
  16. package/templates/.env.example.tpl +21 -0
  17. package/templates/.gitignore.tpl +40 -0
  18. package/templates/CLAUDE.local.md.tpl +32 -0
  19. package/templates/CLAUDE.md.tpl +112 -0
  20. package/templates/claude/README.md.tpl +94 -0
  21. package/templates/claude/agents/code-reviewer.md.tpl +142 -0
  22. package/templates/claude/commands/commit.md.tpl +34 -0
  23. package/templates/claude/commands/explain-codebase.md.tpl +37 -0
  24. package/templates/claude/commands/fix-issue.md.tpl +43 -0
  25. package/templates/claude/commands/memory-sync.md.tpl +49 -0
  26. package/templates/claude/commands/project-health.md.tpl +70 -0
  27. package/templates/claude/commands/review-pr.md.tpl +43 -0
  28. package/templates/claude/commands/scaffold-structure.md.tpl +308 -0
  29. package/templates/claude/commands/setup-project.md.tpl +253 -0
  30. package/templates/claude/commands/standup.md.tpl +34 -0
  31. package/templates/claude/hooks/post-tool-use.sh.tpl +44 -0
  32. package/templates/claude/hooks/pre-tool-use.sh.tpl +64 -0
  33. package/templates/claude/rules/no-sensitive-files.md.tpl +29 -0
  34. package/templates/claude/settings.json.tpl +50 -0
  35. package/templates/claude/settings.local.json.tpl +4 -0
  36. package/templates/claude/skills/project-conventions/SKILL.md.tpl +39 -0
  37. package/templates/mcp.json.tpl +9 -0
  38. package/templates/memory/MEMORY.md.tpl +37 -0
  39. package/templates/memory/feedback_communication.md.tpl +29 -0
  40. package/templates/memory/project_ai_workflow.md.tpl +43 -0
  41. package/templates/memory/user_profile.md.tpl +30 -0
@@ -0,0 +1,479 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const chalk = require('chalk');
6
+ const { detect } = require('../stack-detector');
7
+ const logger = require('../logger');
8
+
9
+ async function github(options) {
10
+ const targetDir = path.resolve(options.dir || process.cwd());
11
+ const dryRun = options.dryRun || false;
12
+ const withDevcontainer = options.devcontainer !== false;
13
+ const withPrTemplate = options.prTemplate !== false;
14
+ const withIssueTemplates = options.issueTemplates !== false;
15
+
16
+ // Auto-detect stack unless overridden
17
+ let stack = options.stack;
18
+ if (!stack) {
19
+ const { labels } = await detect(targetDir);
20
+ stack = detectPrimaryStack(labels);
21
+ }
22
+
23
+ console.log('');
24
+ console.log(chalk.bold.cyan(' claudeforge') + chalk.dim(' — GitHub Setup'));
25
+ console.log(chalk.dim(' ─────────────────────────────────────────'));
26
+ if (dryRun) console.log(chalk.bold.yellow(' [DRY RUN] No files will be written'));
27
+ console.log('');
28
+ console.log(` ${chalk.dim('Detected stack:')} ${chalk.white(stack)}`);
29
+ console.log('');
30
+
31
+ const writes = [];
32
+
33
+ // ── GitHub Actions CI ─────────────────────────────────────────────────────
34
+ writes.push({
35
+ dest: '.github/workflows/ci.yml',
36
+ content: buildCI(stack),
37
+ });
38
+
39
+ // ── PR Template ───────────────────────────────────────────────────────────
40
+ if (withPrTemplate) {
41
+ writes.push({
42
+ dest: '.github/pull_request_template.md',
43
+ content: PR_TEMPLATE,
44
+ });
45
+ }
46
+
47
+ // ── Issue Templates ───────────────────────────────────────────────────────
48
+ if (withIssueTemplates) {
49
+ writes.push({
50
+ dest: '.github/ISSUE_TEMPLATE/bug_report.md',
51
+ content: BUG_TEMPLATE,
52
+ });
53
+ writes.push({
54
+ dest: '.github/ISSUE_TEMPLATE/feature_request.md',
55
+ content: FEATURE_TEMPLATE,
56
+ });
57
+ writes.push({
58
+ dest: '.github/ISSUE_TEMPLATE/config.yml',
59
+ content: ISSUE_CONFIG,
60
+ });
61
+ }
62
+
63
+ // ── CODEOWNERS ────────────────────────────────────────────────────────────
64
+ writes.push({
65
+ dest: '.github/CODEOWNERS',
66
+ content: CODEOWNERS,
67
+ });
68
+
69
+ // ── Devcontainer ──────────────────────────────────────────────────────────
70
+ if (withDevcontainer) {
71
+ writes.push({
72
+ dest: '.devcontainer/devcontainer.json',
73
+ content: buildDevcontainer(stack),
74
+ });
75
+ }
76
+
77
+ // ── Write files ───────────────────────────────────────────────────────────
78
+ let created = 0;
79
+ let skipped = 0;
80
+
81
+ for (const w of writes) {
82
+ const destAbs = path.join(targetDir, w.dest);
83
+
84
+ if (dryRun) {
85
+ console.log(` ${chalk.cyan('~')} ${chalk.dim(w.dest)} ${chalk.cyan('would create')}`);
86
+ created++;
87
+ continue;
88
+ }
89
+
90
+ const exists = await fs.pathExists(destAbs);
91
+ if (exists) {
92
+ console.log(` ${chalk.gray('○')} ${chalk.dim(w.dest)} ${chalk.gray('skipped (already exists)')}`);
93
+ skipped++;
94
+ } else {
95
+ await fs.ensureDir(path.dirname(destAbs));
96
+ await fs.writeFile(destAbs, w.content, 'utf8');
97
+ console.log(` ${chalk.green('✓')} ${chalk.dim(w.dest)}`);
98
+ created++;
99
+ }
100
+ }
101
+
102
+ console.log('');
103
+ console.log(chalk.dim(' ─────────────────────────────────────────'));
104
+
105
+ if (dryRun) {
106
+ console.log(chalk.cyan(` Dry run: ${created} file(s) would be created.`));
107
+ console.log('');
108
+ return;
109
+ }
110
+
111
+ console.log(chalk.green(` ✓ ${created} file(s) created, ${skipped} skipped.`));
112
+ console.log('');
113
+
114
+ logger.hints('What to do next:', [
115
+ { cmd: 'git add .github/ .devcontainer/', note: 'stage the new files' },
116
+ { cmd: '/setup-project "your description"', note: 'run in Claude Code chat — Claude can refine the CI workflow for your stack' },
117
+ { cmd: 'claudeforge status', note: 'confirm full project setup' },
118
+ ]);
119
+ }
120
+
121
+ // ── Stack detection ───────────────────────────────────────────────────────────
122
+
123
+ function detectPrimaryStack(labels) {
124
+ const l = labels.join(' ').toLowerCase();
125
+ if (l.includes('rust')) return 'rust';
126
+ if (l.includes('go')) return 'go';
127
+ if (l.includes('python') || l.includes('fastapi') || l.includes('django')) return 'python';
128
+ if (l.includes('node') || l.includes('typescript') || l.includes('react') || l.includes('next.js')) return 'node';
129
+ return 'generic';
130
+ }
131
+
132
+ // ── CI workflow builders ──────────────────────────────────────────────────────
133
+
134
+ function buildCI(stack) {
135
+ if (stack === 'node') return NODE_CI;
136
+ if (stack === 'python') return PYTHON_CI;
137
+ if (stack === 'go') return GO_CI;
138
+ if (stack === 'rust') return RUST_CI;
139
+ return GENERIC_CI;
140
+ }
141
+
142
+ const NODE_CI = `name: CI
143
+
144
+ on:
145
+ push:
146
+ branches: [main, master]
147
+ pull_request:
148
+ branches: [main, master]
149
+
150
+ jobs:
151
+ test:
152
+ name: Lint & Test
153
+ runs-on: ubuntu-latest
154
+
155
+ strategy:
156
+ matrix:
157
+ node-version: [18, 20, 22]
158
+
159
+ steps:
160
+ - uses: actions/checkout@v4
161
+
162
+ - name: Set up Node.js \${{ matrix.node-version }}
163
+ uses: actions/setup-node@v4
164
+ with:
165
+ node-version: \${{ matrix.node-version }}
166
+ cache: npm
167
+
168
+ - name: Install dependencies
169
+ run: npm ci
170
+
171
+ - name: Lint
172
+ run: npm run lint --if-present
173
+
174
+ - name: Type check
175
+ run: npm run typecheck --if-present
176
+
177
+ - name: Test
178
+ run: npm test
179
+
180
+ - name: Build
181
+ run: npm run build --if-present
182
+ `;
183
+
184
+ const PYTHON_CI = `name: CI
185
+
186
+ on:
187
+ push:
188
+ branches: [main, master]
189
+ pull_request:
190
+ branches: [main, master]
191
+
192
+ jobs:
193
+ test:
194
+ name: Lint & Test
195
+ runs-on: ubuntu-latest
196
+
197
+ strategy:
198
+ matrix:
199
+ python-version: ["3.10", "3.11", "3.12"]
200
+
201
+ steps:
202
+ - uses: actions/checkout@v4
203
+
204
+ - name: Set up Python \${{ matrix.python-version }}
205
+ uses: actions/setup-python@v5
206
+ with:
207
+ python-version: \${{ matrix.python-version }}
208
+ cache: pip
209
+
210
+ - name: Install dependencies
211
+ run: |
212
+ pip install --upgrade pip
213
+ pip install -r requirements.txt
214
+ pip install -r requirements-dev.txt 2>/dev/null || true
215
+
216
+ - name: Lint (ruff)
217
+ run: ruff check . --if-present 2>/dev/null || true
218
+
219
+ - name: Type check (mypy)
220
+ run: mypy . --if-present 2>/dev/null || true
221
+
222
+ - name: Test
223
+ run: pytest -v
224
+ `;
225
+
226
+ const GO_CI = `name: CI
227
+
228
+ on:
229
+ push:
230
+ branches: [main, master]
231
+ pull_request:
232
+ branches: [main, master]
233
+
234
+ jobs:
235
+ test:
236
+ name: Lint, Vet & Test
237
+ runs-on: ubuntu-latest
238
+
239
+ steps:
240
+ - uses: actions/checkout@v4
241
+
242
+ - name: Set up Go
243
+ uses: actions/setup-go@v5
244
+ with:
245
+ go-version: stable
246
+
247
+ - name: Download dependencies
248
+ run: go mod download
249
+
250
+ - name: Vet
251
+ run: go vet ./...
252
+
253
+ - name: golangci-lint
254
+ uses: golangci/golangci-lint-action@v6
255
+ with:
256
+ version: latest
257
+
258
+ - name: Test
259
+ run: go test -race -coverprofile=coverage.txt ./...
260
+
261
+ - name: Build
262
+ run: go build ./...
263
+ `;
264
+
265
+ const RUST_CI = `name: CI
266
+
267
+ on:
268
+ push:
269
+ branches: [main, master]
270
+ pull_request:
271
+ branches: [main, master]
272
+
273
+ jobs:
274
+ test:
275
+ name: Lint, Clippy & Test
276
+ runs-on: ubuntu-latest
277
+
278
+ steps:
279
+ - uses: actions/checkout@v4
280
+
281
+ - name: Install Rust toolchain
282
+ uses: dtolnay/rust-toolchain@stable
283
+ with:
284
+ components: rustfmt, clippy
285
+
286
+ - name: Cache cargo
287
+ uses: Swatinem/rust-cache@v2
288
+
289
+ - name: Format check
290
+ run: cargo fmt --check
291
+
292
+ - name: Clippy
293
+ run: cargo clippy --all-targets --all-features -- -D warnings
294
+
295
+ - name: Test
296
+ run: cargo test --all-features
297
+
298
+ - name: Build (release)
299
+ run: cargo build --release
300
+ `;
301
+
302
+ const GENERIC_CI = `name: CI
303
+
304
+ on:
305
+ push:
306
+ branches: [main, master]
307
+ pull_request:
308
+ branches: [main, master]
309
+
310
+ jobs:
311
+ build:
312
+ name: Build & Test
313
+ runs-on: ubuntu-latest
314
+
315
+ steps:
316
+ - uses: actions/checkout@v4
317
+
318
+ - name: Run tests
319
+ run: |
320
+ echo "Add your test command here"
321
+ # e.g. make test, ./scripts/test.sh, etc.
322
+ `;
323
+
324
+ // ── Static templates ──────────────────────────────────────────────────────────
325
+
326
+ const PR_TEMPLATE = `## Summary
327
+
328
+ <!-- What does this PR do? 1-3 bullet points. -->
329
+
330
+ -
331
+ -
332
+
333
+ ## Type of change
334
+
335
+ - [ ] Bug fix
336
+ - [ ] New feature
337
+ - [ ] Refactor / cleanup
338
+ - [ ] Documentation
339
+ - [ ] CI / infrastructure
340
+
341
+ ## Test plan
342
+
343
+ <!-- How was this tested? -->
344
+
345
+ - [ ] Unit tests pass (\`npm test\` / \`pytest\` / \`go test\` / \`cargo test\`)
346
+ - [ ] Manually tested locally
347
+ - [ ] Added new tests for new behaviour
348
+
349
+ ## Checklist
350
+
351
+ - [ ] Code follows the style guide in CLAUDE.md
352
+ - [ ] No hardcoded secrets or credentials
353
+ - [ ] CLAUDE.md / docs updated if needed
354
+ - [ ] PR title follows conventional commits (\`feat:\`, \`fix:\`, \`chore:\`, etc.)
355
+
356
+ ---
357
+ > Reviewed by [code-reviewer agent](.claude/agents/code-reviewer.md) before merging.
358
+ `;
359
+
360
+ const BUG_TEMPLATE = `---
361
+ name: Bug report
362
+ about: Something is broken
363
+ title: '[Bug] '
364
+ labels: bug
365
+ assignees: ''
366
+ ---
367
+
368
+ ## Describe the bug
369
+
370
+ <!-- Clear description of what went wrong. -->
371
+
372
+ ## To reproduce
373
+
374
+ 1.
375
+ 2.
376
+ 3.
377
+
378
+ ## Expected behaviour
379
+
380
+ ## Actual behaviour
381
+
382
+ ## Environment
383
+
384
+ - OS:
385
+ - Node / Python / Go / Rust version:
386
+ - Package version:
387
+
388
+ ## Additional context
389
+
390
+ <!-- Logs, screenshots, etc. -->
391
+ `;
392
+
393
+ const FEATURE_TEMPLATE = `---
394
+ name: Feature request
395
+ about: Suggest an improvement or new capability
396
+ title: '[Feature] '
397
+ labels: enhancement
398
+ assignees: ''
399
+ ---
400
+
401
+ ## Problem
402
+
403
+ <!-- What problem would this solve? -->
404
+
405
+ ## Proposed solution
406
+
407
+ ## Alternatives considered
408
+
409
+ ## Additional context
410
+ `;
411
+
412
+ const ISSUE_CONFIG = `blank_issues_enabled: false
413
+ contact_links:
414
+ - name: Documentation
415
+ url: https://github.com/your-org/your-repo#readme
416
+ about: Read the docs first
417
+ `;
418
+
419
+ const CODEOWNERS = `# CODEOWNERS
420
+ # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
421
+
422
+ # Default owners for everything
423
+ * @your-org/your-team
424
+
425
+ # Infrastructure
426
+ .github/ @your-org/platform
427
+ .devcontainer/ @your-org/platform
428
+ `;
429
+
430
+ // ── Devcontainer builder ──────────────────────────────────────────────────────
431
+
432
+ function buildDevcontainer(stack) {
433
+ const image = {
434
+ node: 'mcr.microsoft.com/devcontainers/javascript-node:22',
435
+ python: 'mcr.microsoft.com/devcontainers/python:3.12',
436
+ go: 'mcr.microsoft.com/devcontainers/go:1.22',
437
+ rust: 'mcr.microsoft.com/devcontainers/rust:latest',
438
+ generic: 'mcr.microsoft.com/devcontainers/base:ubuntu',
439
+ }[stack] || 'mcr.microsoft.com/devcontainers/base:ubuntu';
440
+
441
+ const features = [];
442
+ if (stack !== 'node') features.push('"ghcr.io/devcontainers/features/node:1": {}');
443
+ if (stack === 'python') features.push('"ghcr.io/devcontainers/features/python:1": {}');
444
+
445
+ const postCreate = {
446
+ node: 'npm install',
447
+ python: 'pip install -r requirements.txt 2>/dev/null || true',
448
+ go: 'go mod download',
449
+ rust: 'rustup component add rustfmt clippy',
450
+ generic: 'echo "Add your setup commands here"',
451
+ }[stack] || 'echo done';
452
+
453
+ return JSON.stringify({
454
+ name: 'claudeforge Dev Container',
455
+ image,
456
+ features: features.length > 0 ? Object.fromEntries(features.map(f => {
457
+ const [key, val] = f.split(': ');
458
+ return [key.replace(/"/g, ''), JSON.parse(val)];
459
+ })) : undefined,
460
+ customizations: {
461
+ vscode: {
462
+ extensions: [
463
+ 'anthropics.claude-code',
464
+ 'dbaeumer.vscode-eslint',
465
+ 'esbenp.prettier-vscode',
466
+ 'eamodio.gitlens',
467
+ ],
468
+ settings: {
469
+ 'editor.formatOnSave': true,
470
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
471
+ },
472
+ },
473
+ },
474
+ postCreateCommand: postCreate,
475
+ remoteUser: 'vscode',
476
+ }, null, 2) + '\n';
477
+ }
478
+
479
+ module.exports = github;
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const scaffolder = require('../scaffolder');
5
+ const logger = require('../logger');
6
+
7
+ /**
8
+ * Static manifest — defines every file and directory to scaffold.
9
+ * Ordered so parent directories appear before their children.
10
+ *
11
+ * type: 'dir' — create directory (no template src needed)
12
+ * type: 'file' — copy template src → dest
13
+ */
14
+ const MANIFEST = [
15
+ // ── Directories ──────────────────────────────────────────────────────────
16
+ { type: 'dir', dest: '.claude' },
17
+ { type: 'dir', dest: '.claude/agents' },
18
+ { type: 'dir', dest: '.claude/commands' },
19
+ { type: 'dir', dest: '.claude/hooks' },
20
+ { type: 'dir', dest: '.claude/rules' },
21
+ { type: 'dir', dest: '.claude/skills' },
22
+ { type: 'dir', dest: '.claude/skills/project-conventions' },
23
+ { type: 'dir', dest: 'memory' },
24
+
25
+ // ── .claude/ root files ───────────────────────────────────────────────────
26
+ { type: 'file', src: 'claude/README.md.tpl', dest: '.claude/README.md' },
27
+ { type: 'file', src: 'claude/settings.json.tpl', dest: '.claude/settings.json' },
28
+ { type: 'file', src: 'claude/settings.local.json.tpl', dest: '.claude/settings.local.json' },
29
+
30
+ // ── .claude/agents/ ───────────────────────────────────────────────────────
31
+ { type: 'file', src: 'claude/agents/code-reviewer.md.tpl', dest: '.claude/agents/code-reviewer.md' },
32
+
33
+ // ── .claude/commands/ ─────────────────────────────────────────────────────
34
+ // Core workflow commands
35
+ { type: 'file', src: 'claude/commands/commit.md.tpl', dest: '.claude/commands/commit.md' },
36
+ { type: 'file', src: 'claude/commands/review-pr.md.tpl', dest: '.claude/commands/review-pr.md' },
37
+ // AI setup & maintenance commands
38
+ { type: 'file', src: 'claude/commands/setup-project.md.tpl', dest: '.claude/commands/setup-project.md' },
39
+ { type: 'file', src: 'claude/commands/memory-sync.md.tpl', dest: '.claude/commands/memory-sync.md' },
40
+ { type: 'file', src: 'claude/commands/project-health.md.tpl', dest: '.claude/commands/project-health.md' },
41
+ // Developer productivity commands
42
+ { type: 'file', src: 'claude/commands/standup.md.tpl', dest: '.claude/commands/standup.md' },
43
+ { type: 'file', src: 'claude/commands/explain-codebase.md.tpl',dest: '.claude/commands/explain-codebase.md' },
44
+ { type: 'file', src: 'claude/commands/fix-issue.md.tpl', dest: '.claude/commands/fix-issue.md' },
45
+ { type: 'file', src: 'claude/commands/scaffold-structure.md.tpl', dest: '.claude/commands/scaffold-structure.md' },
46
+
47
+ // ── .claude/hooks/ ────────────────────────────────────────────────────────
48
+ { type: 'file', src: 'claude/hooks/pre-tool-use.sh.tpl', dest: '.claude/hooks/pre-tool-use.sh' },
49
+ { type: 'file', src: 'claude/hooks/post-tool-use.sh.tpl', dest: '.claude/hooks/post-tool-use.sh' },
50
+
51
+ // ── .claude/rules/ ────────────────────────────────────────────────────────
52
+ { type: 'file', src: 'claude/rules/no-sensitive-files.md.tpl', dest: '.claude/rules/no-sensitive-files.md' },
53
+
54
+ // ── .claude/skills/ ───────────────────────────────────────────────────────
55
+ { type: 'file', src: 'claude/skills/project-conventions/SKILL.md.tpl', dest: '.claude/skills/project-conventions/SKILL.md' },
56
+
57
+ // ── memory/ ───────────────────────────────────────────────────────────────
58
+ { type: 'file', src: 'memory/MEMORY.md.tpl', dest: 'memory/MEMORY.md' },
59
+ { type: 'file', src: 'memory/user_profile.md.tpl', dest: 'memory/user_profile.md' },
60
+ { type: 'file', src: 'memory/feedback_communication.md.tpl', dest: 'memory/feedback_communication.md' },
61
+ { type: 'file', src: 'memory/project_ai_workflow.md.tpl', dest: 'memory/project_ai_workflow.md' },
62
+
63
+ // ── Project root files ────────────────────────────────────────────────────
64
+ { type: 'file', src: 'CLAUDE.md.tpl', dest: 'CLAUDE.md' },
65
+ { type: 'file', src: 'CLAUDE.local.md.tpl', dest: 'CLAUDE.local.md' },
66
+ { type: 'file', src: '.env.example.tpl', dest: '.env.example' },
67
+ { type: 'file', src: 'mcp.json.tpl', dest: '.mcp.json' },
68
+ { type: 'file', src: '.gitignore.tpl', dest: '.gitignore' },
69
+ ];
70
+
71
+ async function init(options) {
72
+ const targetDir = path.resolve(options.dir || process.cwd());
73
+ const { force, dryRun } = options;
74
+
75
+ logger.banner(dryRun);
76
+ logger.info(`Target: ${targetDir}`);
77
+ console.log('');
78
+
79
+ const stats = { created: 0, skipped: 0, overwritten: 0 };
80
+ const templatesDir = path.join(__dirname, '../../templates');
81
+
82
+ for (const entry of MANIFEST) {
83
+ const destAbs = path.join(targetDir, entry.dest);
84
+
85
+ if (entry.type === 'dir') {
86
+ await scaffolder.ensureDir(destAbs, dryRun);
87
+ logger.dirResult(entry.dest, dryRun);
88
+ continue;
89
+ }
90
+
91
+ // type === 'file'
92
+ const srcAbs = path.join(templatesDir, entry.src);
93
+ const result = await scaffolder.writeFile(srcAbs, destAbs, { force, dryRun });
94
+ stats[result]++;
95
+ logger.fileResult(result, entry.dest, dryRun);
96
+ }
97
+
98
+ // Make hook scripts executable on Unix systems
99
+ if (!dryRun) {
100
+ await scaffolder.chmod(path.join(targetDir, '.claude/hooks/pre-tool-use.sh'), 0o755);
101
+ await scaffolder.chmod(path.join(targetDir, '.claude/hooks/post-tool-use.sh'), 0o755);
102
+ }
103
+
104
+ logger.summary(stats, dryRun);
105
+ }
106
+
107
+ module.exports = init;