cc-dev-template 0.1.45 → 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,6 +251,71 @@ if (fs.existsSync(mergeSettingsPath)) {
251
251
  });
252
252
  }
253
253
 
254
+ // Remove deprecated files and settings
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
274
+ if (fs.existsSync(settingsFile)) {
275
+ try {
276
+ const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
277
+ let settingsModified = false;
278
+
279
+ // Remove deprecated code-simplifier plugin
280
+ if (settings.enabledPlugins && settings.enabledPlugins['code-simplifier@claude-plugins-official']) {
281
+ delete settings.enabledPlugins['code-simplifier@claude-plugins-official'];
282
+ console.log('✓ Removed deprecated code-simplifier plugin');
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));
309
+ }
310
+ } catch (e) {
311
+ // Ignore errors reading settings
312
+ }
313
+ }
314
+
315
+ if (!cleanupPerformed) {
316
+ console.log(' No deprecated items to remove');
317
+ }
318
+
254
319
  console.log('\n' + '='.repeat(50));
255
320
  console.log('Installation complete!');
256
321
  console.log('='.repeat(50));
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "cc-dev-template",
3
- "version": "0.1.45",
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",
@@ -19,17 +19,11 @@ This requires full conversation context. Handle it yourself rather than delegati
19
19
 
20
20
  ## Steps
21
21
 
22
- **1. Run the code simplifier**
23
-
24
- Stage your changes, then use the code simplifier agent to refine them for clarity and consistency. Run tests afterward to verify nothing broke.
25
-
26
- Skip this step if no code simplifier agent is available.
27
-
28
- **2. Commit your work**
22
+ **1. Commit your work**
29
23
 
30
24
  Write clear commit messages that explain what was accomplished. This IS your record of completed work.
31
25
 
32
- **3. Update `docs/CURRENT_WORK.md`**
26
+ **2. Update `docs/CURRENT_WORK.md`**
33
27
 
34
28
  This file is forward-looking. Review it holistically and ensure it contains ONLY:
35
29
 
@@ -46,7 +40,7 @@ This file is forward-looking. Review it holistically and ensure it contains ONLY
46
40
 
47
41
  The file should be scannable in 30 seconds. If it takes longer, it's too long.
48
42
 
49
- **4. Capture workflow discoveries (rarely)**
43
+ **3. Capture workflow discoveries (rarely)**
50
44
 
51
45
  Add to CLAUDE.md only high-value operational knowledge that can't be found by reading code:
52
46
  - Dev commands, ports, local URLs
@@ -55,11 +49,11 @@ Add to CLAUDE.md only high-value operational knowledge that can't be found by re
55
49
 
56
50
  Most sessions: add nothing.
57
51
 
58
- **5. Push**
52
+ **4. Push**
59
53
 
60
54
  Push your commits to the remote.
61
55
 
62
- **6. Report what was updated**
56
+ **5. Report what was updated**
63
57
 
64
58
  Summarize: commits made, CURRENT_WORK.md changes, any items removed/added.
65
59
 
@@ -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
  */
@@ -191,18 +256,35 @@ function main() {
191
256
 
192
257
  // Get token usage from context_window (available in Claude Code v2.0.70+)
193
258
  let tokenData = null;
194
- if (data.context_window?.current_usage) {
195
- const usage = data.context_window.current_usage;
196
- const used =
197
- (usage.input_tokens || 0) +
198
- (usage.cache_read_input_tokens || 0) +
199
- (usage.cache_creation_input_tokens || 0);
259
+ if (data.context_window) {
200
260
  const limit = data.context_window.context_window_size || 200000;
201
- tokenData = {
202
- used,
203
- limit,
204
- percentage: Math.min(100, Math.round((used / limit) * 100)),
205
- };
261
+
262
+ // Use new percentage fields if available (v2.1.6+)
263
+ if (typeof data.context_window.used_percentage === 'number') {
264
+ const usage = data.context_window.current_usage;
265
+ const used = usage
266
+ ? (usage.input_tokens || 0) +
267
+ (usage.cache_read_input_tokens || 0) +
268
+ (usage.cache_creation_input_tokens || 0)
269
+ : 0;
270
+ tokenData = {
271
+ used,
272
+ limit,
273
+ percentage: Math.round(data.context_window.used_percentage),
274
+ };
275
+ } else if (data.context_window.current_usage) {
276
+ // Fallback to manual calculation for older versions
277
+ const usage = data.context_window.current_usage;
278
+ const used =
279
+ (usage.input_tokens || 0) +
280
+ (usage.cache_read_input_tokens || 0) +
281
+ (usage.cache_creation_input_tokens || 0);
282
+ tokenData = {
283
+ used,
284
+ limit,
285
+ percentage: Math.min(100, Math.round((used / limit) * 100)),
286
+ };
287
+ }
206
288
  }
207
289
 
208
290
  // Generate context display
@@ -220,14 +302,17 @@ function main() {
220
302
  ctxDisplay = `CTX: ${greyColor}[${bar}] ${pct}%${DIM_GREY} | ${usedTokens}/${limitTokens}`;
221
303
  }
222
304
 
223
- // Get git information
224
- const gitBranch = getGitBranch(data.workspace.project_dir);
225
- const gitStatus = getGitStatus(data.workspace.project_dir);
226
- const gitStatusStr = gitStatus ? formatGitStatus(gitStatus) : '';
227
-
228
305
  // Multi-line bordered box format
229
306
  const width = 56;
230
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
+
231
316
  // Top border with model name (add 2 to match content line width: ║ + space + 56 + space + ║ = 60)
232
317
  const modelName = data.model.display_name.toUpperCase();
233
318
  const labelWidth = modelName.length + 2; // +2 for brackets
@@ -238,46 +323,69 @@ function main() {
238
323
  const maxDirLen = width - 5; // "DIR: " = 5 chars
239
324
  const dirName =
240
325
  rawDirName.length > maxDirLen ? rawDirName.substring(0, maxDirLen - 3) + '...' : rawDirName;
241
- const line0Content = `DIR: ${dirName}`;
242
-
243
- const plainLine0 = line0Content.replace(/\x1b\[[0-9;]*m/g, '');
244
- const padding0 = width - plainLine0.length;
245
- const paddedLine0 = line0Content + ' '.repeat(Math.max(0, padding0));
246
- const line0 = `${DIM_GREY}║ ${paddedLine0} ║${RESET}`;
247
-
248
- // Line 1: Branch + git status (padded to width)
249
- let line1Content = '';
250
- if (gitBranch) {
251
- const branchPrefix = 'BRANCH: ';
252
- const branchSuffix = ' ⎇';
253
- const gitStatusSpace = gitStatusStr ? ` ${gitStatusStr}` : '';
254
-
255
- const availableSpace = width - branchPrefix.length - branchSuffix.length - gitStatusSpace.length;
256
- const displayBranch =
257
- gitBranch.length > availableSpace
258
- ? gitBranch.substring(0, availableSpace - 3) + '...'
259
- : gitBranch;
260
-
261
- 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)`));
262
362
  } else {
263
- 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
+ }
264
378
  }
265
379
 
266
- const plainLine1 = line1Content.replace(/\x1b\[[0-9;]*m/g, '');
267
- const padding1 = width - plainLine1.length;
268
- const paddedLine1 = line1Content + ' '.repeat(Math.max(0, padding1));
269
- const line1 = `${DIM_GREY}║ ${paddedLine1} ║${RESET}`;
270
-
271
- // Line 2: Context bar (padded to width)
272
- const plainCtx = ctxDisplay.replace(/\x1b\[[0-9;]*m/g, '');
273
- const padding2 = width - plainCtx.length;
274
- const paddedCtx = ctxDisplay + ' '.repeat(Math.max(0, padding2));
275
- const line2 = `${DIM_GREY}║ ${paddedCtx} ║${RESET}`;
380
+ // Context bar line
381
+ const ctxLine = makeBoxLine(ctxDisplay);
276
382
 
277
383
  // Bottom border (add 2 to match content line width)
278
384
  const bottomBorder = `${DIM_GREY}╚${'═'.repeat(width + 2)}╝${RESET}`;
279
385
 
280
- 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'));
281
389
  } catch (error) {
282
390
  // Log error for debugging (goes to stderr, not visible in status line)
283
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