cc-dev-template 0.1.46 → 0.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/install.js CHANGED
@@ -233,7 +233,7 @@ if (fs.existsSync(mergeSettingsPath)) {
233
233
  { file: 'read-guard-hook.json', name: 'Context guard for large reads' },
234
234
  { file: 'statusline-config.json', name: 'Custom status line' },
235
235
  { file: 'bash-overflow-hook.json', name: 'Bash overflow guard hook' },
236
- { file: 'bash-precheck-hook.json', name: 'Bash precheck hook' }
236
+ { file: 'env-config.json', name: 'Environment variables' }
237
237
  ];
238
238
 
239
239
  configs.forEach(({ file, name }) => {
@@ -251,23 +251,69 @@ if (fs.existsSync(mergeSettingsPath)) {
251
251
  });
252
252
  }
253
253
 
254
- // Remove deprecated plugins
254
+ // Remove deprecated files and settings
255
255
  console.log('\nCleanup:');
256
+ let cleanupPerformed = false;
257
+
258
+ // Remove deprecated bash wrapper files
259
+ const deprecatedFiles = [
260
+ path.join(CLAUDE_DIR, 'hooks', 'bash-precheck.sh'),
261
+ path.join(CLAUDE_DIR, 'hooks', 'bash-wrapper-helper.sh'),
262
+ path.join(CLAUDE_DIR, 'scripts', 'bash-precheck-hook.json')
263
+ ];
264
+
265
+ deprecatedFiles.forEach(file => {
266
+ if (fs.existsSync(file)) {
267
+ fs.unlinkSync(file);
268
+ console.log(`✓ Removed deprecated ${path.basename(file)}`);
269
+ cleanupPerformed = true;
270
+ }
271
+ });
272
+
273
+ // Remove deprecated hooks from settings.json
256
274
  if (fs.existsSync(settingsFile)) {
257
275
  try {
258
276
  const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
277
+ let settingsModified = false;
278
+
279
+ // Remove deprecated code-simplifier plugin
259
280
  if (settings.enabledPlugins && settings.enabledPlugins['code-simplifier@claude-plugins-official']) {
260
281
  delete settings.enabledPlugins['code-simplifier@claude-plugins-official'];
261
- fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
262
282
  console.log('✓ Removed deprecated code-simplifier plugin');
263
- } else {
264
- console.log(' No deprecated plugins to remove');
283
+ settingsModified = true;
284
+ cleanupPerformed = true;
285
+ }
286
+
287
+ // Remove bash-precheck hooks from settings
288
+ if (settings.hooks) {
289
+ const hookTypes = ['PreToolUse', 'PostToolUse'];
290
+ hookTypes.forEach(hookType => {
291
+ if (settings.hooks[hookType] && Array.isArray(settings.hooks[hookType])) {
292
+ const originalLength = settings.hooks[hookType].length;
293
+ settings.hooks[hookType] = settings.hooks[hookType].filter(hook => {
294
+ const command = hook.hooks?.[0]?.command || '';
295
+ return !command.includes('bash-precheck');
296
+ });
297
+ if (settings.hooks[hookType].length < originalLength) {
298
+ console.log(`✓ Removed deprecated bash-precheck hook from ${hookType}`);
299
+ settingsModified = true;
300
+ cleanupPerformed = true;
301
+ }
302
+ }
303
+ });
304
+ }
305
+
306
+
307
+ if (settingsModified) {
308
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
265
309
  }
266
310
  } catch (e) {
267
- console.log(' No deprecated plugins to remove');
311
+ // Ignore errors reading settings
268
312
  }
269
- } else {
270
- console.log(' No deprecated plugins to remove');
313
+ }
314
+
315
+ if (!cleanupPerformed) {
316
+ console.log(' No deprecated items to remove');
271
317
  }
272
318
 
273
319
  console.log('\n' + '='.repeat(50));
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "cc-dev-template",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "Structured AI-assisted development framework for Claude Code",
5
5
  "bin": {
6
6
  "cc-dev-template": "./bin/install.js"
7
7
  },
8
8
  "files": [
9
9
  "bin/",
10
- "src/"
10
+ "src/",
11
+ "!src/mcp-servers/*/node_modules/",
12
+ "!src/mcp-servers/*/dist/"
11
13
  ],
12
14
  "keywords": [
13
15
  "claude",
@@ -0,0 +1,5 @@
1
+ {
2
+ "env": {
3
+ "BASH_MAX_OUTPUT_LENGTH": "1000000"
4
+ }
5
+ }
@@ -161,6 +161,71 @@ function getGitBranch(projectDir) {
161
161
  }
162
162
  }
163
163
 
164
+ /**
165
+ * Get submodule paths from .gitmodules
166
+ */
167
+ function getSubmodulePaths(projectDir) {
168
+ try {
169
+ const output = execSync('git config --file .gitmodules --get-regexp path', {
170
+ cwd: projectDir,
171
+ encoding: 'utf-8',
172
+ stdio: ['pipe', 'pipe', 'ignore'],
173
+ });
174
+ // Parse lines like "submodule.packages/core.path packages/core"
175
+ return output
176
+ .trim()
177
+ .split('\n')
178
+ .map((line) => line.split(' ')[1])
179
+ .filter(Boolean);
180
+ } catch {
181
+ return []; // No .gitmodules or no submodules
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Get all modules (main + submodules) that have changes
187
+ * Returns array of { name, path, branch, status }
188
+ */
189
+ function getModulesWithChanges(projectDir) {
190
+ const submodulePaths = getSubmodulePaths(projectDir);
191
+
192
+ // If no submodules, return null to indicate regular repo
193
+ if (submodulePaths.length === 0) {
194
+ return null;
195
+ }
196
+
197
+ const modules = [];
198
+
199
+ // Check main repo
200
+ const mainStatus = getGitStatus(projectDir);
201
+ const mainStatusStr = mainStatus ? formatGitStatus(mainStatus) : '';
202
+ if (mainStatusStr) {
203
+ modules.push({
204
+ name: 'MAIN',
205
+ path: projectDir,
206
+ branch: getGitBranch(projectDir),
207
+ status: mainStatusStr,
208
+ });
209
+ }
210
+
211
+ // Check each submodule
212
+ for (const subPath of submodulePaths) {
213
+ const fullPath = join(projectDir, subPath);
214
+ const subStatus = getGitStatus(fullPath);
215
+ const subStatusStr = subStatus ? formatGitStatus(subStatus) : '';
216
+ if (subStatusStr) {
217
+ modules.push({
218
+ name: basename(subPath).toUpperCase(),
219
+ path: fullPath,
220
+ branch: getGitBranch(fullPath),
221
+ status: subStatusStr,
222
+ });
223
+ }
224
+ }
225
+
226
+ return modules;
227
+ }
228
+
164
229
  /**
165
230
  * Main function
166
231
  */
@@ -237,14 +302,17 @@ function main() {
237
302
  ctxDisplay = `CTX: ${greyColor}[${bar}] ${pct}%${DIM_GREY} | ${usedTokens}/${limitTokens}`;
238
303
  }
239
304
 
240
- // Get git information
241
- const gitBranch = getGitBranch(data.workspace.project_dir);
242
- const gitStatus = getGitStatus(data.workspace.project_dir);
243
- const gitStatusStr = gitStatus ? formatGitStatus(gitStatus) : '';
244
-
245
305
  // Multi-line bordered box format
246
306
  const width = 56;
247
307
 
308
+ // Helper to create a padded box line
309
+ const makeBoxLine = (content) => {
310
+ const plain = content.replace(/\x1b\[[0-9;]*m/g, '');
311
+ const padding = width - plain.length;
312
+ const padded = content + ' '.repeat(Math.max(0, padding));
313
+ return `${DIM_GREY}║ ${padded} ║${RESET}`;
314
+ };
315
+
248
316
  // Top border with model name (add 2 to match content line width: ║ + space + 56 + space + ║ = 60)
249
317
  const modelName = data.model.display_name.toUpperCase();
250
318
  const labelWidth = modelName.length + 2; // +2 for brackets
@@ -255,46 +323,69 @@ function main() {
255
323
  const maxDirLen = width - 5; // "DIR: " = 5 chars
256
324
  const dirName =
257
325
  rawDirName.length > maxDirLen ? rawDirName.substring(0, maxDirLen - 3) + '...' : rawDirName;
258
- const line0Content = `DIR: ${dirName}`;
259
-
260
- const plainLine0 = line0Content.replace(/\x1b\[[0-9;]*m/g, '');
261
- const padding0 = width - plainLine0.length;
262
- const paddedLine0 = line0Content + ' '.repeat(Math.max(0, padding0));
263
- const line0 = `${DIM_GREY}║ ${paddedLine0} ║${RESET}`;
264
-
265
- // Line 1: Branch + git status (padded to width)
266
- let line1Content = '';
267
- if (gitBranch) {
268
- const branchPrefix = 'BRANCH: ';
269
- const branchSuffix = ' ⎇';
270
- const gitStatusSpace = gitStatusStr ? ` ${gitStatusStr}` : '';
271
-
272
- const availableSpace = width - branchPrefix.length - branchSuffix.length - gitStatusSpace.length;
273
- const displayBranch =
274
- gitBranch.length > availableSpace
275
- ? gitBranch.substring(0, availableSpace - 3) + '...'
276
- : gitBranch;
277
-
278
- line1Content = `${branchPrefix}${displayBranch.toUpperCase()}${branchSuffix}${gitStatusSpace}`;
326
+ const line0 = makeBoxLine(`DIR: ${dirName}`);
327
+
328
+ // Get git information - check for submodules
329
+ const modulesWithChanges = getModulesWithChanges(data.workspace.project_dir);
330
+
331
+ // Build branch/module lines
332
+ const branchLines = [];
333
+
334
+ if (modulesWithChanges === null) {
335
+ // No submodules - use original single-line format
336
+ const gitBranch = getGitBranch(data.workspace.project_dir);
337
+ const gitStatus = getGitStatus(data.workspace.project_dir);
338
+ const gitStatusStr = gitStatus ? formatGitStatus(gitStatus) : '';
339
+
340
+ let line1Content = '';
341
+ if (gitBranch) {
342
+ const branchPrefix = 'BRANCH: ';
343
+ const branchSuffix = ' ';
344
+ const gitStatusSpace = gitStatusStr ? ` ${gitStatusStr}` : '';
345
+
346
+ const availableSpace = width - branchPrefix.length - branchSuffix.length - gitStatusSpace.length;
347
+ const displayBranch =
348
+ gitBranch.length > availableSpace
349
+ ? gitBranch.substring(0, availableSpace - 3) + '...'
350
+ : gitBranch;
351
+
352
+ line1Content = `${branchPrefix}${displayBranch.toUpperCase()}${branchSuffix}${gitStatusSpace}`;
353
+ } else {
354
+ line1Content = 'BRANCH: (none)';
355
+ }
356
+ branchLines.push(makeBoxLine(line1Content));
357
+ } else if (modulesWithChanges.length === 0) {
358
+ // Has submodules but all clean
359
+ const gitBranch = getGitBranch(data.workspace.project_dir);
360
+ const branchName = gitBranch ? gitBranch.toUpperCase() : '(none)';
361
+ branchLines.push(makeBoxLine(`BRANCH: ${branchName} ⎇ (all clean)`));
279
362
  } else {
280
- line1Content = 'BRANCH: (none)';
363
+ // Has submodules with changes - one line per module
364
+ for (const mod of modulesWithChanges) {
365
+ const branchName = mod.branch ? mod.branch.toUpperCase() : '(none)';
366
+ const prefix = `[${mod.name}] `;
367
+ const suffix = ' ⎇';
368
+ const statusSpace = ` ${mod.status}`;
369
+
370
+ const availableSpace = width - prefix.length - suffix.length - statusSpace.length;
371
+ const displayBranch =
372
+ branchName.length > availableSpace
373
+ ? branchName.substring(0, availableSpace - 3) + '...'
374
+ : branchName;
375
+
376
+ branchLines.push(makeBoxLine(`${prefix}${displayBranch}${suffix}${statusSpace}`));
377
+ }
281
378
  }
282
379
 
283
- const plainLine1 = line1Content.replace(/\x1b\[[0-9;]*m/g, '');
284
- const padding1 = width - plainLine1.length;
285
- const paddedLine1 = line1Content + ' '.repeat(Math.max(0, padding1));
286
- const line1 = `${DIM_GREY}║ ${paddedLine1} ║${RESET}`;
287
-
288
- // Line 2: Context bar (padded to width)
289
- const plainCtx = ctxDisplay.replace(/\x1b\[[0-9;]*m/g, '');
290
- const padding2 = width - plainCtx.length;
291
- const paddedCtx = ctxDisplay + ' '.repeat(Math.max(0, padding2));
292
- const line2 = `${DIM_GREY}║ ${paddedCtx} ║${RESET}`;
380
+ // Context bar line
381
+ const ctxLine = makeBoxLine(ctxDisplay);
293
382
 
294
383
  // Bottom border (add 2 to match content line width)
295
384
  const bottomBorder = `${DIM_GREY}╚${'═'.repeat(width + 2)}╝${RESET}`;
296
385
 
297
- console.log(`${topBorder}\n${line0}\n${line1}\n${line2}\n${bottomBorder}`);
386
+ // Combine all lines
387
+ const allLines = [topBorder, line0, ...branchLines, ctxLine, bottomBorder];
388
+ console.log(allLines.join('\n'));
298
389
  } catch (error) {
299
390
  // Log error for debugging (goes to stderr, not visible in status line)
300
391
  console.error(`Status line error: ${error.message}`);
@@ -1,15 +0,0 @@
1
- {
2
- "hooks": {
3
- "PreToolUse": [
4
- {
5
- "matcher": "Bash",
6
- "hooks": [
7
- {
8
- "type": "command",
9
- "command": "$HOME/.claude/hooks/bash-precheck.sh"
10
- }
11
- ]
12
- }
13
- ]
14
- }
15
- }
@@ -1,117 +0,0 @@
1
- #!/bin/bash
2
- # bash-precheck.sh - PreToolUse hook to wrap Bash commands with output limiting
3
- #
4
- # Intercepts Bash commands before execution and wraps them to:
5
- # 1. Capture output to temp file
6
- # 2. If output exceeds threshold, save and return truncated version
7
- #
8
- # This ACTUALLY prevents large outputs from consuming context.
9
- # Compatible with bash and zsh on macOS and Linux.
10
-
11
- set -e
12
-
13
- # Configuration
14
- MAX_CHARS=${BASH_OVERFLOW_MAX_CHARS:-20000}
15
- OVERFLOW_DIR="${HOME}/.claude/bash-overflow"
16
-
17
- # Read hook input from stdin
18
- input=$(cat)
19
-
20
- # Parse tool name and command
21
- tool_name=$(echo "$input" | jq -r '.tool_name // empty')
22
-
23
- # Only process Bash tool
24
- if [[ "$tool_name" != "Bash" ]]; then
25
- exit 0
26
- fi
27
-
28
- # Get the original command
29
- original_cmd=$(echo "$input" | jq -r '.tool_input.command // empty')
30
-
31
- # Skip if empty
32
- if [[ -z "$original_cmd" ]]; then
33
- exit 0
34
- fi
35
-
36
- # Skip commands that are already piped to head/tail or are simple commands
37
- if [[ "$original_cmd" =~ \|[[:space:]]*(head|tail|wc|grep.*-c) ]]; then
38
- exit 0
39
- fi
40
-
41
- # Skip very short commands (likely simple operations)
42
- if [[ ${#original_cmd} -lt 10 ]]; then
43
- exit 0
44
- fi
45
-
46
- # Create overflow directory
47
- mkdir -p "$OVERFLOW_DIR"
48
-
49
- # Generate unique filename
50
- timestamp=$(date +%Y%m%d_%H%M%S)
51
- rand_suffix=$RANDOM
52
- overflow_file="$OVERFLOW_DIR/bash_output_${timestamp}_${rand_suffix}.txt"
53
- temp_file="/tmp/claude_bash_${timestamp}_${rand_suffix}.tmp"
54
-
55
- # Escape the original command for embedding in the wrapper
56
- # Use base64 to safely embed arbitrary commands
57
- original_cmd_b64=$(echo "$original_cmd" | base64)
58
-
59
- # Create wrapper that uses temp file approach (more portable)
60
- read -r -d '' wrapped_cmd << 'WRAPPER_EOF' || true
61
- __cmd_b64="BASE64_CMD_HERE"
62
- __cmd=$(echo "$__cmd_b64" | base64 -d)
63
- __tmpfile="TEMP_FILE_HERE"
64
- __overflow="OVERFLOW_FILE_HERE"
65
- __max=MAX_CHARS_HERE
66
-
67
- # Run command and capture to temp file
68
- eval "$__cmd" > "$__tmpfile" 2>&1
69
- __exit_code=$?
70
-
71
- # Check size
72
- __size=$(wc -c < "$__tmpfile" | tr -d ' ')
73
-
74
- if [ "$__size" -gt "$__max" ]; then
75
- # Save full output
76
- cp "$__tmpfile" "$__overflow"
77
- __lines=$(wc -l < "$__tmpfile" | tr -d ' ')
78
- __tokens=$((__size / 4))
79
-
80
- echo "=== OUTPUT TRUNCATED (${__lines} lines, ~${__tokens} tokens) ==="
81
- echo ""
82
- echo "--- First 5 lines ---"
83
- head -n 5 "$__tmpfile"
84
- echo ""
85
- echo "--- Last 5 lines ---"
86
- tail -n 5 "$__tmpfile"
87
- echo ""
88
- echo "=== Full output saved to: $__overflow ==="
89
- echo "Use Grep(pattern, path) or Read(file_path, offset, limit) to explore"
90
- else
91
- cat "$__tmpfile"
92
- fi
93
-
94
- rm -f "$__tmpfile"
95
- exit $__exit_code
96
- WRAPPER_EOF
97
-
98
- # Substitute placeholders
99
- wrapped_cmd="${wrapped_cmd//BASE64_CMD_HERE/$original_cmd_b64}"
100
- wrapped_cmd="${wrapped_cmd//TEMP_FILE_HERE/$temp_file}"
101
- wrapped_cmd="${wrapped_cmd//OVERFLOW_FILE_HERE/$overflow_file}"
102
- wrapped_cmd="${wrapped_cmd//MAX_CHARS_HERE/$MAX_CHARS}"
103
-
104
- # Return the modified command
105
- cat << EOF
106
- {
107
- "hookSpecificOutput": {
108
- "hookEventName": "PreToolUse",
109
- "permissionDecision": "allow",
110
- "updatedInput": {
111
- "command": $(echo "$wrapped_cmd" | jq -Rs .)
112
- }
113
- }
114
- }
115
- EOF
116
-
117
- exit 0