@northbridge-security/secureai 0.1.13

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 (50) hide show
  1. package/.claude/README.md +122 -0
  2. package/.claude/commands/architect/clean.md +978 -0
  3. package/.claude/commands/architect/kiss.md +762 -0
  4. package/.claude/commands/architect/review.md +704 -0
  5. package/.claude/commands/catchup.md +90 -0
  6. package/.claude/commands/code.md +115 -0
  7. package/.claude/commands/commit.md +1218 -0
  8. package/.claude/commands/cover.md +1298 -0
  9. package/.claude/commands/fmea.md +275 -0
  10. package/.claude/commands/kaizen.md +312 -0
  11. package/.claude/commands/pr.md +503 -0
  12. package/.claude/commands/todo.md +99 -0
  13. package/.claude/commands/worktree.md +738 -0
  14. package/.claude/commands/wrapup.md +103 -0
  15. package/LICENSE +183 -0
  16. package/README.md +108 -0
  17. package/dist/cli.js +75634 -0
  18. package/docs/agents/devops-reviewer.md +889 -0
  19. package/docs/agents/kiss-simplifier.md +1088 -0
  20. package/docs/agents/typescript.md +8 -0
  21. package/docs/guides/README.md +109 -0
  22. package/docs/guides/agents.clean.arch.md +244 -0
  23. package/docs/guides/agents.clean.arch.ts.md +1314 -0
  24. package/docs/guides/agents.gotask.md +1037 -0
  25. package/docs/guides/agents.markdown.md +1209 -0
  26. package/docs/guides/agents.onepassword.md +285 -0
  27. package/docs/guides/agents.sonar.md +857 -0
  28. package/docs/guides/agents.tdd.md +838 -0
  29. package/docs/guides/agents.tdd.ts.md +1062 -0
  30. package/docs/guides/agents.typesript.md +1389 -0
  31. package/docs/guides/github-mcp.md +1075 -0
  32. package/package.json +130 -0
  33. package/packages/secureai-cli/src/cli.ts +21 -0
  34. package/tasks/README.md +880 -0
  35. package/tasks/aws.yml +64 -0
  36. package/tasks/bash.yml +118 -0
  37. package/tasks/bun.yml +738 -0
  38. package/tasks/claude.yml +183 -0
  39. package/tasks/docker.yml +420 -0
  40. package/tasks/docs.yml +127 -0
  41. package/tasks/git.yml +1336 -0
  42. package/tasks/gotask.yml +132 -0
  43. package/tasks/json.yml +77 -0
  44. package/tasks/markdown.yml +95 -0
  45. package/tasks/onepassword.yml +350 -0
  46. package/tasks/security.yml +102 -0
  47. package/tasks/sonar.yml +437 -0
  48. package/tasks/template.yml +74 -0
  49. package/tasks/vscode.yml +103 -0
  50. package/tasks/yaml.yml +121 -0
@@ -0,0 +1,1218 @@
1
+ ---
2
+ description: Analyze changes, generate commit messages, run QA checks, and create grouped commits
3
+ argument-hint: [files...] [--amend] [--all] [--message="text"] [--no-verify] [--dry-run] [--compare] [--quick]
4
+ allowed-tools: Bash, Read, Edit, Write, Grep, Bash(git *), Bash(task *), mcp__task_master_ai__*
5
+ ---
6
+
7
+ # Commit Command: $ARGUMENTS
8
+
9
+ Intelligently groups related files and creates multiple focused commits with generated conventional commit messages, comprehensive validation, and strict hook compliance.
10
+
11
+ **IMPORTANT - Report Location Pattern**: This command generates a commit report. See [REPORT-LOCATIONS.md](./REPORT-LOCATIONS.md) for the required pattern:
12
+
13
+ - **Save to temporary directory**: `${TMPDIR:-${TEMP:-/tmp}}/commit-report-$(date +%Y%m%d-%H%M%S).md`
14
+ - **Auto-open in VSCode**: `code "$REPORT_FILE"` after generation
15
+ - **Never save to project root**: No `.commit.report.local.md` files
16
+
17
+ **Key Features:**
18
+
19
+ - **Smart grouping** - Groups files by Task Master context, bug tracking, and logical relationships
20
+ - **Multiple commits** - Creates separate commits for unrelated changes
21
+ - **Task context** - Uses Task Master to understand what work files belong to (task IDs stay internal)
22
+ - **Strict validation** - Respects all commit hooks, validates file readiness
23
+ - **Comprehensive reporting** - Detailed report in `.commit.report.local.md`
24
+
25
+ ## Quick Examples
26
+
27
+ ```bash
28
+ # Commit all staged files (auto-grouped into multiple commits)
29
+ /commit
30
+
31
+ # Commit specific files
32
+ /commit src/cli/*.ts tests/cli/*.test.ts
33
+
34
+ # Stage and commit everything (auto-grouped)
35
+ /commit --all
36
+
37
+ # Preview what would be committed
38
+ /commit --dry-run --all
39
+
40
+ # Use custom message for single commit (bypasses grouping)
41
+ /commit --message="chore: update dependencies" package.json bun.lock
42
+
43
+ # Amend previous commit
44
+ /commit --amend src/fix.ts
45
+
46
+ # Compare with previous attempt
47
+ /commit --all --compare
48
+
49
+ # Quick mode - commit files from current conversation
50
+ /commit --quick
51
+ ```
52
+
53
+ ## Phase 0: Parse Arguments
54
+
55
+ Extract from `$ARGUMENTS`:
56
+
57
+ - **Files**: Optional file list (e.g., `src/cli/*.ts Taskfile.yml`)
58
+ - **--all**: Stage all modified files before committing
59
+ - **--amend**: Amend the previous commit instead of creating new
60
+ - **--message="text"**: Use provided message (creates single commit, bypasses grouping)
61
+ - **--no-verify**: Skip git hooks (dangerous, requires explicit confirmation)
62
+ - **--dry-run**: Show what would be committed without actually committing
63
+ - **--compare**: Compare with previous commit attempt (if `.commit.report.local.md` exists)
64
+ - **--quick**: Quick mode - commit files discussed in current conversation with minimal ceremony
65
+
66
+ **If `--quick` flag is present, skip to [Quick Mode](#quick-mode) section.**
67
+
68
+ ## Quick Mode
69
+
70
+ Quick mode is designed for committing files discussed in the current conversation with minimal ceremony. This is useful for smaller tool changes or work done outside the main task context.
71
+
72
+ **When to use `--quick`:**
73
+
74
+ - Small configuration or tool changes
75
+ - Files edited during a side conversation
76
+ - Quick fixes unrelated to current Task Master tasks
77
+ - Documentation updates from chat discussion
78
+
79
+ **What Quick Mode does:**
80
+
81
+ 1. Extracts files discussed/edited in current conversation
82
+ 2. Creates a single focused commit
83
+ 3. Generates conventional commit message from conversation context
84
+ 4. Respects all git hooks (no `--no-verify`)
85
+ 5. Skips complex grouping logic
86
+
87
+ ### Quick Mode Workflow
88
+
89
+ ```javascript
90
+ // Step 1: Extract files from conversation context
91
+ // Look for files that were:
92
+ // - Read with Read tool
93
+ // - Edited with Edit tool
94
+ // - Written with Write tool
95
+ // - Discussed explicitly by path
96
+
97
+ const conversationFiles = extractFilesFromConversation();
98
+ // Example result: ['docs/commands/commit.md', '.claude/settings.json']
99
+
100
+ // Step 2: Filter to only modified files
101
+ const modifiedFiles = conversationFiles.filter((file) => {
102
+ // Check git status - only include files with actual changes
103
+ const status = execSync(`git status --porcelain "${file}"`).toString().trim();
104
+ return status.length > 0;
105
+ });
106
+
107
+ // Step 3: Validate files exist and are trackable
108
+ const validFiles = modifiedFiles.filter((file) => {
109
+ return existsSync(file) && !isIgnored(file);
110
+ });
111
+ ```
112
+
113
+ ### Quick Mode: Stage and Validate
114
+
115
+ ```bash
116
+ # Stage only the conversation files
117
+ for file in "${QUICK_FILES[@]}"; do
118
+ if [ -f "$file" ]; then
119
+ git add "$file"
120
+ fi
121
+ done
122
+
123
+ # Verify we have files to commit
124
+ STAGED=$(git diff --cached --name-only)
125
+ if [ -z "$STAGED" ]; then
126
+ echo "❌ No modified files from conversation to commit"
127
+ echo " Files checked: ${QUICK_FILES[*]}"
128
+ exit 1
129
+ fi
130
+
131
+ echo "📝 Quick commit: $(echo "$STAGED" | wc -l | tr -d ' ') files"
132
+ echo "$STAGED" | while read -r file; do
133
+ echo " - $file"
134
+ done
135
+ ```
136
+
137
+ ### Quick Mode: Generate Commit Message
138
+
139
+ ```javascript
140
+ // Analyze conversation context to determine commit type and scope
141
+
142
+ // Extract the main topic from conversation
143
+ const topic = analyzeConversationTopic();
144
+ // Example: "updating commit command with quick flag"
145
+
146
+ // Determine commit type from changes
147
+ let commitType = "chore";
148
+ const fileTypes = validFiles.map((f) => getFileType(f));
149
+
150
+ if (fileTypes.some((t) => t === "feature-code")) {
151
+ commitType = "feat";
152
+ } else if (fileTypes.some((t) => t === "test")) {
153
+ commitType = "test";
154
+ } else if (fileTypes.every((t) => t === "docs")) {
155
+ commitType = "docs";
156
+ } else if (fileTypes.some((t) => t === "config")) {
157
+ commitType = "chore";
158
+ }
159
+
160
+ // Determine scope from file paths
161
+ const scope = inferScopeFromPaths(validFiles);
162
+ // Example: 'commands' from 'docs/commands/commit.md'
163
+
164
+ // Generate description from conversation context
165
+ const description = generateDescriptionFromContext(topic);
166
+ // Example: 'add quick flag for conversation-based commits'
167
+
168
+ // Compose message
169
+ const message = scope ? `${commitType}(${scope}): ${description}` : `${commitType}: ${description}`;
170
+
171
+ // Example: "docs(commands): add quick flag for conversation-based commits"
172
+ ```
173
+
174
+ ### Quick Mode: Execute Commit
175
+
176
+ ```bash
177
+ # Run pre-commit hooks (NEVER skip in quick mode)
178
+ echo "🔍 Running pre-commit checks..."
179
+
180
+ # Create commit with generated message
181
+ if git commit -m "$(cat <<'EOF'
182
+ $COMMIT_MESSAGE
183
+ EOF
184
+ )"; then
185
+ COMMIT_SHA=$(git rev-parse HEAD)
186
+ echo ""
187
+ echo "✅ Quick commit successful"
188
+ echo " SHA: ${COMMIT_SHA:0:7}"
189
+ echo " Message: $COMMIT_MESSAGE"
190
+ echo ""
191
+
192
+ # Show files committed
193
+ echo "📁 Files committed:"
194
+ git diff-tree --no-commit-id --name-only -r HEAD | while read -r file; do
195
+ echo " - $file"
196
+ done
197
+ else
198
+ echo "❌ Commit failed"
199
+ echo ""
200
+ echo "Possible causes:"
201
+ echo " - Pre-commit hooks failed (fix lint/type errors)"
202
+ echo " - Commit message format rejected"
203
+ echo ""
204
+ echo "Fix issues and retry with: /commit --quick"
205
+ exit 1
206
+ fi
207
+ ```
208
+
209
+ ### Quick Mode: File Extraction Patterns
210
+
211
+ **Files to include from conversation:**
212
+
213
+ ```javascript
214
+ // Pattern 1: Files read with Read tool
215
+ // Look for: Read tool calls in conversation
216
+ const readFiles = conversation.toolCalls
217
+ .filter((call) => call.tool === "Read")
218
+ .map((call) => call.params.file_path);
219
+
220
+ // Pattern 2: Files edited with Edit tool
221
+ // Look for: Edit tool calls in conversation
222
+ const editedFiles = conversation.toolCalls
223
+ .filter((call) => call.tool === "Edit")
224
+ .map((call) => call.params.file_path);
225
+
226
+ // Pattern 3: Files written with Write tool
227
+ const writtenFiles = conversation.toolCalls
228
+ .filter((call) => call.tool === "Write")
229
+ .map((call) => call.params.file_path);
230
+
231
+ // Pattern 4: Files explicitly mentioned by path
232
+ // Look for: file paths in user messages
233
+ const mentionedFiles = extractFilePathsFromMessages(conversation.messages);
234
+
235
+ // Combine and deduplicate
236
+ const allFiles = [...new Set([...readFiles, ...editedFiles, ...writtenFiles, ...mentionedFiles])];
237
+
238
+ // Filter to only include files with modifications
239
+ return allFiles.filter(hasUncommittedChanges);
240
+ ```
241
+
242
+ **Files to exclude:**
243
+
244
+ - Files in `.gitignore`
245
+ - Files with no actual changes (read-only access)
246
+ - Temporary files (`.tmp/*`, `*.log`)
247
+ - Files outside repository root
248
+
249
+ ### Quick Mode vs Standard Mode
250
+
251
+ | Aspect | Quick Mode | Standard Mode |
252
+ | -------------- | ----------------- | ------------------------ |
253
+ | File selection | From conversation | All staged or specified |
254
+ | Grouping | Single commit | Multiple grouped commits |
255
+ | Task Master | Not used | Used for context |
256
+ | Complexity | Minimal | Full analysis |
257
+ | Use case | Small changes | Large changesets |
258
+ | Hooks | Always run | Always run |
259
+
260
+ **Important**: Quick mode still respects all git hooks and conventional commit format. The only simplification is in file selection and grouping logic.
261
+
262
+ ## Phase 1: Analyze Git State
263
+
264
+ ### 1.1 Verify Git Repository
265
+
266
+ ```bash
267
+ # Check if we're in a git repository
268
+ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
269
+ echo "❌ Error: Not a git repository"
270
+ echo " Initialize with: git init"
271
+ exit 1
272
+ }
273
+
274
+ # Get current branch
275
+ BRANCH=$(git branch --show-current)
276
+
277
+ # Warn if on protected branch
278
+ if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
279
+ echo "⚠️ Warning: Committing to protected branch '$BRANCH'"
280
+ echo " Consider using a feature branch instead"
281
+ fi
282
+
283
+ # Check for merge in progress
284
+ if [ -f .git/MERGE_HEAD ]; then
285
+ echo "❌ Error: Merge in progress"
286
+ echo " Resolve conflicts first with: git status"
287
+ exit 1
288
+ fi
289
+
290
+ # Check for detached HEAD
291
+ if ! git symbolic-ref -q HEAD >/dev/null; then
292
+ echo "⚠️ Warning: Detached HEAD state"
293
+ echo " Commits will not be on a branch"
294
+ echo " Consider: git checkout -b <branch-name>"
295
+ fi
296
+ ```
297
+
298
+ ### 1.2 Collect Changed Files
299
+
300
+ ```bash
301
+ # Determine which files to process
302
+ if [ -n "$FILES_ARG" ]; then
303
+ # Specific files provided
304
+ CHANGED_FILES=("$FILES_ARG")
305
+
306
+ # Validate files exist
307
+ for file in "${CHANGED_FILES[@]}"; do
308
+ [ -f "$file" ] || {
309
+ echo "❌ Error: File not found: $file"
310
+ exit 1
311
+ }
312
+ done
313
+
314
+ # Stage specified files
315
+ git add "${CHANGED_FILES[@]}"
316
+ elif [ "$ALL_FLAG" = "true" ]; then
317
+ # Stage all modified and untracked files
318
+ git add -A
319
+ CHANGED_FILES=($(git diff --cached --name-only))
320
+ else
321
+ # Use currently staged files
322
+ CHANGED_FILES=($(git diff --cached --name-only))
323
+
324
+ # Warn if nothing is staged
325
+ if [ ${#CHANGED_FILES[@]} -eq 0 ]; then
326
+ echo "❌ Error: No files to commit"
327
+ echo " Options:"
328
+ echo " - Stage files: git add <file>"
329
+ echo " - Use --all: /commit --all"
330
+ exit 1
331
+ fi
332
+ fi
333
+
334
+ TOTAL_FILES=${#CHANGED_FILES[@]}
335
+ echo "📊 Analyzing $TOTAL_FILES changed files..."
336
+ ```
337
+
338
+ ## Phase 2: Validate Files
339
+
340
+ For each changed file, validate readiness:
341
+
342
+ ### 2.1 Syntax and Completeness Validation
343
+
344
+ ```bash
345
+ READY_FILES=()
346
+ SKIPPED_FILES=()
347
+
348
+ for file in "${CHANGED_FILES[@]}"; do
349
+ SKIP_REASON=""
350
+
351
+ # Empty files
352
+ if [ ! -s "$file" ] || ! grep -q '[^[:space:]]' "$file" 2>/dev/null; then
353
+ SKIP_REASON="Empty file or whitespace only"
354
+
355
+ # JSON syntax
356
+ elif [[ "$file" == *.json ]] && ! jq empty "$file" 2>/dev/null; then
357
+ SKIP_REASON="Invalid JSON syntax"
358
+
359
+ # YAML syntax
360
+ elif [[ "$file" =~ \.(yaml|yml)$ ]] && command -v yamllint &>/dev/null; then
361
+ if ! yamllint -d relaxed "$file" 2>/dev/null; then
362
+ SKIP_REASON="Invalid YAML syntax"
363
+ fi
364
+
365
+ # TypeScript/JavaScript syntax
366
+ elif [[ "$file" =~ \.(ts|tsx|js|jsx)$ ]] && command -v node &>/dev/null; then
367
+ if ! node -c "$file" 2>/dev/null; then
368
+ SKIP_REASON="Syntax errors detected"
369
+ fi
370
+ fi
371
+
372
+ # WIP markers
373
+ if [ -z "$SKIP_REASON" ] && grep -qiE '\bWIP\b|TODO:.*finish|FIXME:.*broken|HACK:' "$file"; then
374
+ MARKERS=$(grep -niE '\bWIP\b|TODO:.*finish|FIXME:.*broken|HACK:' "$file" | head -3)
375
+ SKIP_REASON="Contains WIP markers: $MARKERS"
376
+ fi
377
+
378
+ # Debug code
379
+ if [ -z "$SKIP_REASON" ] && grep -qE 'console\.log.*["\x27]test["\x27]|debugger;|console\.debug' "$file"; then
380
+ DEBUG_LINES=$(grep -nE 'console\.log.*["\x27]test["\x27]|debugger;|console\.debug' "$file" | head -3)
381
+ SKIP_REASON="Contains debug code: $DEBUG_LINES"
382
+ fi
383
+
384
+ # Add to appropriate list
385
+ if [ -n "$SKIP_REASON" ]; then
386
+ SKIPPED_FILES+=("$file:$SKIP_REASON")
387
+ git reset "$file" 2>/dev/null || true
388
+ else
389
+ READY_FILES+=("$file")
390
+ fi
391
+ done
392
+
393
+ echo "✅ Validated: ${#READY_FILES[@]} files ready, ${#SKIPPED_FILES[@]} skipped"
394
+ ```
395
+
396
+ ### 2.2 Sensitive Data Detection
397
+
398
+ ```bash
399
+ SENSITIVE_FILES=()
400
+
401
+ for file in "${READY_FILES[@]}"; do
402
+ # API keys
403
+ if grep -qE 'api[_-]?key.*["\x27][a-zA-Z0-9]{32,}["\x27]' "$file"; then
404
+ SENSITIVE_FILES+=("$file:Potential API key detected")
405
+
406
+ # AWS credentials
407
+ elif grep -qE 'AKIA[0-9A-Z]{16}' "$file"; then
408
+ SENSITIVE_FILES+=("$file:AWS access key detected")
409
+
410
+ # Private keys
411
+ elif grep -q '-----BEGIN.*PRIVATE KEY-----' "$file"; then
412
+ SENSITIVE_FILES+=("$file:Private key detected")
413
+
414
+ # JWT tokens
415
+ elif grep -qE 'eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*' "$file"; then
416
+ SENSITIVE_FILES+=("$file:JWT token detected")
417
+
418
+ # Generic secrets
419
+ elif grep -qiE 'secret.*=.*["\x27][a-zA-Z0-9]{16,}["\x27]' "$file"; then
420
+ SENSITIVE_FILES+=("$file:Potential secret detected")
421
+ fi
422
+ done
423
+
424
+ # Block commit if sensitive data found
425
+ if [ ${#SENSITIVE_FILES[@]} -gt 0 ]; then
426
+ echo "❌ BLOCKED: Sensitive data detected:"
427
+ for item in "${SENSITIVE_FILES[@]}"; do
428
+ echo " - $item"
429
+ done
430
+ echo ""
431
+ echo "Remove sensitive data before committing"
432
+ exit 1
433
+ fi
434
+ ```
435
+
436
+ ### 2.3 Conflict and Size Detection
437
+
438
+ ```bash
439
+ # Check for unresolved conflicts
440
+ CONFLICT_FILES=$(git diff --name-only --diff-filter=U)
441
+ if [ -n "$CONFLICT_FILES" ]; then
442
+ echo "❌ BLOCKED: Unresolved merge conflicts in:"
443
+ echo "$CONFLICT_FILES" | while read -r file; do
444
+ echo " - $file"
445
+ grep -n '^<<<<<<< \|^=======$\|^>>>>>>> ' "$file" 2>/dev/null | head -3 || true
446
+ done
447
+ echo ""
448
+ echo "Resolve conflicts before committing"
449
+ exit 1
450
+ fi
451
+
452
+ # Warn about large files
453
+ LARGE_FILES=()
454
+ for file in "${READY_FILES[@]}"; do
455
+ if [ -f "$file" ]; then
456
+ SIZE=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
457
+ if [ "$SIZE" -gt 1048576 ]; then # 1MB
458
+ SIZE_MB=$(echo "scale=2; $SIZE / 1048576" | bc)
459
+ LARGE_FILES+=("$file:${SIZE_MB}MB")
460
+ fi
461
+ fi
462
+ done
463
+
464
+ if [ ${#LARGE_FILES[@]} -gt 0 ]; then
465
+ echo "⚠️ Large files detected (>1MB):"
466
+ for item in "${LARGE_FILES[@]}"; do
467
+ echo " - $item"
468
+ done
469
+ echo " Consider using Git LFS for large binary files"
470
+ echo ""
471
+ fi
472
+
473
+ # Detect partial staging
474
+ PARTIAL_STAGED=$(comm -12 \
475
+ <(git diff --name-only | sort) \
476
+ <(git diff --cached --name-only | sort))
477
+
478
+ if [ -n "$PARTIAL_STAGED" ]; then
479
+ echo "⚠️ Files with partial staging (staged + unstaged changes):"
480
+ echo "$PARTIAL_STAGED" | while read -r file; do
481
+ echo " - $file"
482
+ done
483
+ echo " Only staged changes will be committed"
484
+ echo ""
485
+ fi
486
+ ```
487
+
488
+ ## Phase 3: Group Files by Context
489
+
490
+ **CRITICAL**: Never commit 40+ files in one commit. Group changes by function and create multiple focused commits.
491
+
492
+ ### 3.1 Load Task Master Context
493
+
494
+ **IMPORTANT**: Task IDs are internal to the operator and MUST NOT appear in commit messages. Use Task Master only to understand what work is being done and group files accordingly.
495
+
496
+ ```bash
497
+ # Check if Task Master is available
498
+ TASKMASTER_ENABLED=false
499
+ if [ -d .taskmaster ]; then
500
+ TASKMASTER_ENABLED=true
501
+ echo "🔍 Loading Task Master context..."
502
+ fi
503
+
504
+ # Initialize arrays for grouping
505
+ declare -A TASK_CONTEXTS
506
+ declare -A FILE_TO_GROUP
507
+
508
+ if [ "$TASKMASTER_ENABLED" = "true" ]; then
509
+ # Get recent and in-progress tasks using MCP
510
+ # Focus on: status in (in-progress, done) AND updated within last 7 days
511
+
512
+ # For each task, collect:
513
+ # - Title (for commit description)
514
+ # - Description (for context matching)
515
+ # - Details (may contain file paths, function names)
516
+ # - Affected files (from git branch or task notes)
517
+
518
+ # Example pseudo-code for MCP call:
519
+ # TASKS=$(call mcp__task_master_ai__get_tasks with status filter)
520
+ # For each TASK:
521
+ # TASK_CONTEXTS[task_key]="title|description|scope"
522
+ # Store task context WITHOUT storing task ID in commit messages
523
+ fi
524
+ ```
525
+
526
+ ### 3.2 Analyze Files and Match to Contexts
527
+
528
+ ```bash
529
+ # For each ready file, determine which group it belongs to
530
+ for file in "${READY_FILES[@]}"; do
531
+ FILE_DIR=$(dirname "$file")
532
+ FILE_NAME=$(basename "$file")
533
+ FILE_EXT="${FILE_NAME##*.}"
534
+
535
+ # Extract file diff to understand changes
536
+ FILE_DIFF=$(git diff --cached --unified=3 "$file" | grep -E '^\+' | head -20)
537
+
538
+ # Match to Task Master context (if available)
539
+ MATCHED_GROUP=""
540
+ if [ "$TASKMASTER_ENABLED" = "true" ]; then
541
+ # For each task context:
542
+ for task_key in "${!TASK_CONTEXTS[@]}"; do
543
+ CONTEXT="${TASK_CONTEXTS[$task_key]}"
544
+ TITLE=$(echo "$CONTEXT" | cut -d'|' -f1)
545
+ DESCRIPTION=$(echo "$CONTEXT" | cut -d'|' -f2)
546
+ SCOPE=$(echo "$CONTEXT" | cut -d'|' -f3)
547
+
548
+ # Match file to task by:
549
+ # 1. File path mentioned in task details
550
+ # 2. Directory matches task scope (e.g., task about "cli" → src/cli/*.ts)
551
+ # 3. Keywords from task title match file path or diff
552
+ # 4. Related files (if test changed, implementation likely related to same task)
553
+
554
+ # Fuzzy matching logic:
555
+ MATCH_SCORE=0
556
+
557
+ # Check if file path mentioned in description
558
+ if echo "$DESCRIPTION" | grep -q "$file"; then
559
+ MATCH_SCORE=$((MATCH_SCORE + 10))
560
+ fi
561
+
562
+ # Check if directory matches scope
563
+ if [[ "$FILE_DIR" =~ $SCOPE ]]; then
564
+ MATCH_SCORE=$((MATCH_SCORE + 5))
565
+ fi
566
+
567
+ # Check keyword overlap
568
+ TITLE_KEYWORDS=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr -s '[:space:]' '\n')
569
+ for keyword in $TITLE_KEYWORDS; do
570
+ if [[ "$file" =~ $keyword ]] || echo "$FILE_DIFF" | grep -qi "$keyword"; then
571
+ MATCH_SCORE=$((MATCH_SCORE + 2))
572
+ fi
573
+ done
574
+
575
+ # Assign to best matching task context
576
+ if [ $MATCH_SCORE -gt 5 ]; then
577
+ MATCHED_GROUP="task:$task_key"
578
+ break
579
+ fi
580
+ done
581
+ fi
582
+
583
+ # If no Task Master match, use logical grouping
584
+ if [ -z "$MATCHED_GROUP" ]; then
585
+ case "$file" in
586
+ tests/*test.ts)
587
+ # Find matching implementation
588
+ IMPL_FILE=$(echo "$file" | sed 's|tests/||; s|\.test\.ts$|.ts|')
589
+ if echo "${READY_FILES[@]}" | grep -q "$IMPL_FILE"; then
590
+ MATCHED_GROUP="logical:test-impl:$(dirname $IMPL_FILE)"
591
+ else
592
+ MATCHED_GROUP="logical:test:$(dirname $file)"
593
+ fi
594
+ ;;
595
+
596
+ src/*)
597
+ # Find matching test
598
+ TEST_FILE=$(echo "$file" | sed 's|src/|tests/|; s|\.ts$|.test.ts|')
599
+ if echo "${READY_FILES[@]}" | grep -q "$TEST_FILE"; then
600
+ MATCHED_GROUP="logical:test-impl:$(dirname $file)"
601
+ else
602
+ MATCHED_GROUP="logical:feature:$(dirname $file | sed 's|src/||')"
603
+ fi
604
+ ;;
605
+
606
+ package.json|bun.lock|Taskfile.yml|tsconfig.json|*.config.js|*.config.ts)
607
+ MATCHED_GROUP="logical:build"
608
+ ;;
609
+
610
+ .github/workflows/*)
611
+ MATCHED_GROUP="logical:ci"
612
+ ;;
613
+
614
+ docs/*.md)
615
+ # Try to link to related feature
616
+ DOC_NAME=$(basename "$file" .md)
617
+ FEATURE_FILE="src/${DOC_NAME}.ts"
618
+ if echo "${READY_FILES[@]}" | grep -q "$FEATURE_FILE"; then
619
+ MATCHED_GROUP="logical:feature-docs:${DOC_NAME}"
620
+ else
621
+ MATCHED_GROUP="logical:docs"
622
+ fi
623
+ ;;
624
+
625
+ *)
626
+ MATCHED_GROUP="logical:misc"
627
+ ;;
628
+ esac
629
+ fi
630
+
631
+ FILE_TO_GROUP["$file"]="$MATCHED_GROUP"
632
+ done
633
+ ```
634
+
635
+ ### 3.3 Create Commit Groups
636
+
637
+ ```bash
638
+ # Group files by their assigned group
639
+ declare -A GROUP_FILES
640
+
641
+ for file in "${READY_FILES[@]}"; do
642
+ GROUP="${FILE_TO_GROUP[$file]}"
643
+
644
+ if [ -z "${GROUP_FILES[$GROUP]}" ]; then
645
+ GROUP_FILES[$GROUP]="$file"
646
+ else
647
+ GROUP_FILES[$GROUP]="${GROUP_FILES[$GROUP]} $file"
648
+ fi
649
+ done
650
+
651
+ # Sort groups by priority:
652
+ # 1. Task-based groups first
653
+ # 2. Feature+test+docs groups
654
+ # 3. Build/CI groups last
655
+ SORTED_GROUPS=($(for group in "${!GROUP_FILES[@]}"; do echo "$group"; done | \
656
+ sort -t: -k1,1r -k2,2))
657
+
658
+ echo "✅ Grouped ${#READY_FILES[@]} files into ${#SORTED_GROUPS[@]} commits"
659
+
660
+ # Validate grouping
661
+ ASSIGNED_COUNT=0
662
+ for group in "${SORTED_GROUPS[@]}"; do
663
+ FILES="${GROUP_FILES[$group]}"
664
+ FILE_COUNT=$(echo "$FILES" | wc -w | tr -d ' ')
665
+ ASSIGNED_COUNT=$((ASSIGNED_COUNT + FILE_COUNT))
666
+ done
667
+
668
+ if [ $ASSIGNED_COUNT -ne ${#READY_FILES[@]} ]; then
669
+ echo "⚠️ Warning: Grouping mismatch (${ASSIGNED_COUNT} assigned vs ${#READY_FILES[@]} ready)"
670
+ fi
671
+ ```
672
+
673
+ ## Phase 4: Generate Commit Messages
674
+
675
+ **CRITICAL**: NEVER include Task Master task IDs in commit messages. Task IDs are internal to the operator.
676
+
677
+ ### 4.1 Generate Message Per Group
678
+
679
+ ```bash
680
+ declare -A COMMIT_MESSAGES
681
+
682
+ for group in "${SORTED_GROUPS[@]}"; do
683
+ GROUP_TYPE=$(echo "$group" | cut -d: -f1)
684
+ GROUP_DETAIL=$(echo "$group" | cut -d: -f2-)
685
+ FILES="${GROUP_FILES[$group]}"
686
+
687
+ # Analyze files to determine commit type and scope
688
+ FILE_ARRAY=($FILES)
689
+
690
+ # Get diff stats
691
+ DIFF_STATS=$(git diff --cached --stat -- "${FILE_ARRAY[@]}")
692
+ DIFF_CONTENT=$(git diff --cached --unified=3 -- "${FILE_ARRAY[@]}" | grep -E '^\+' | head -30)
693
+
694
+ # Determine commit type based on changes
695
+ COMMIT_TYPE="chore"
696
+
697
+ # Check for new functionality
698
+ if echo "$DIFF_CONTENT" | grep -qE '^\+.*function |^\+.*class |^\+.*export '; then
699
+ if echo "$DIFF_CONTENT" | grep -qE 'test.*it\(|describe\('; then
700
+ COMMIT_TYPE="test"
701
+ else
702
+ COMMIT_TYPE="feat"
703
+ fi
704
+ # Check for bug fixes
705
+ elif echo "$DIFF_CONTENT" | grep -qiE '^\+.*(fix|resolve|correct|patch)'; then
706
+ COMMIT_TYPE="fix"
707
+ # Check for documentation
708
+ elif echo "${FILE_ARRAY[*]}" | grep -qE '\.md$'; then
709
+ COMMIT_TYPE="docs"
710
+ # Check for tests
711
+ elif echo "${FILE_ARRAY[*]}" | grep -qE 'test\.ts$'; then
712
+ COMMIT_TYPE="test"
713
+ # Check for build files
714
+ elif echo "${FILE_ARRAY[*]}" | grep -qE 'package\.json|Taskfile\.yml|\.config\.(js|ts)'; then
715
+ COMMIT_TYPE="build"
716
+ # Check for CI files
717
+ elif echo "${FILE_ARRAY[*]}" | grep -qE '\.github/workflows'; then
718
+ COMMIT_TYPE="ci"
719
+ # Check for refactoring
720
+ elif echo "$DIFF_CONTENT" | grep -qE '^\+' && echo "$DIFF_CONTENT" | grep -qE '^\-'; then
721
+ # Roughly equal additions and deletions suggests refactoring
722
+ PLUS_COUNT=$(echo "$DIFF_CONTENT" | grep -cE '^\+')
723
+ MINUS_COUNT=$(git diff --cached --unified=3 -- "${FILE_ARRAY[@]}" | grep -cE '^\-')
724
+ if [ $((PLUS_COUNT - MINUS_COUNT)) -lt 10 ]; then
725
+ COMMIT_TYPE="refactor"
726
+ fi
727
+ fi
728
+
729
+ # Determine scope from file paths
730
+ SCOPE=""
731
+ if [ "$GROUP_TYPE" = "task" ]; then
732
+ # Extract scope from task context (WITHOUT exposing task ID)
733
+ TASK_CONTEXT="${TASK_CONTEXTS[$GROUP_DETAIL]}"
734
+ if [ -n "$TASK_CONTEXT" ]; then
735
+ SCOPE=$(echo "$TASK_CONTEXT" | cut -d'|' -f3)
736
+ fi
737
+ elif [ "$GROUP_TYPE" = "logical" ]; then
738
+ # Extract scope from group detail
739
+ case "$GROUP_DETAIL" in
740
+ test-impl:*)
741
+ SCOPE=$(echo "$GROUP_DETAIL" | sed 's|test-impl:||; s|src/||; s|/|:|g')
742
+ ;;
743
+ feature:*)
744
+ SCOPE=$(echo "$GROUP_DETAIL" | sed 's|feature:||')
745
+ ;;
746
+ feature-docs:*)
747
+ SCOPE=$(echo "$GROUP_DETAIL" | sed 's|feature-docs:||')
748
+ ;;
749
+ build|ci|docs|test|misc)
750
+ SCOPE="$GROUP_DETAIL"
751
+ ;;
752
+ *)
753
+ # Infer from file paths
754
+ COMMON_PATH=$(echo "${FILE_ARRAY[@]}" | tr ' ' '\n' | \
755
+ sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head -1 | \
756
+ awk '{print $2}' | sed 's|src/||; s|tests/||')
757
+ SCOPE="${COMMON_PATH/\//:}"
758
+ ;;
759
+ esac
760
+ fi
761
+
762
+ # Generate description
763
+ DESCRIPTION=""
764
+ if [ "$GROUP_TYPE" = "task" ]; then
765
+ # Use task title as basis for description (WITHOUT task ID)
766
+ TASK_CONTEXT="${TASK_CONTEXTS[$GROUP_DETAIL]}"
767
+ if [ -n "$TASK_CONTEXT" ]; then
768
+ TASK_TITLE=$(echo "$TASK_CONTEXT" | cut -d'|' -f1)
769
+ # Convert title to commit description format
770
+ DESCRIPTION=$(echo "$TASK_TITLE" | tr '[:upper:]' '[:lower:]' | \
771
+ sed 's/^implement /add /; s/^create /add /; s/^fix /resolve /')
772
+ fi
773
+ else
774
+ # Generate description from file changes
775
+ if [ ${#FILE_ARRAY[@]} -eq 1 ]; then
776
+ # Single file - describe the change
777
+ FILE_NAME=$(basename "${FILE_ARRAY[0]}")
778
+ DESCRIPTION="update $FILE_NAME"
779
+ else
780
+ # Multiple files - describe the scope
781
+ case "$COMMIT_TYPE" in
782
+ feat)
783
+ DESCRIPTION="add ${SCOPE} functionality"
784
+ ;;
785
+ fix)
786
+ DESCRIPTION="resolve ${SCOPE} issues"
787
+ ;;
788
+ docs)
789
+ DESCRIPTION="update ${SCOPE} documentation"
790
+ ;;
791
+ test)
792
+ DESCRIPTION="add ${SCOPE} tests"
793
+ ;;
794
+ build)
795
+ DESCRIPTION="update build configuration"
796
+ ;;
797
+ ci)
798
+ DESCRIPTION="update CI workflow"
799
+ ;;
800
+ refactor)
801
+ DESCRIPTION="refactor ${SCOPE} implementation"
802
+ ;;
803
+ *)
804
+ DESCRIPTION="update ${SCOPE}"
805
+ ;;
806
+ esac
807
+ fi
808
+ fi
809
+
810
+ # Generate body with bullet points
811
+ BODY=""
812
+
813
+ # Analyze changes per file for bullet points
814
+ for file in "${FILE_ARRAY[@]}"; do
815
+ FILE_CHANGES=$(git diff --cached --unified=0 "$file" | \
816
+ grep -E '^\+[^+]' | head -5 | sed 's/^\+//')
817
+
818
+ if [ -n "$FILE_CHANGES" ]; then
819
+ FILE_NAME=$(basename "$file")
820
+ # Extract key changes
821
+ if echo "$FILE_CHANGES" | grep -qE 'function |class |export '; then
822
+ FUNCTIONS=$(echo "$FILE_CHANGES" | grep -oE 'function [a-zA-Z]+|class [a-zA-Z]+|export [a-zA-Z]+' | \
823
+ head -3 | sed 's/function /Add function /; s/class /Add class /; s/export /Export /')
824
+ BODY="$BODY\n- $FILE_NAME: $FUNCTIONS"
825
+ elif echo "$FILE_CHANGES" | grep -qE 'test\(|it\(|describe\('; then
826
+ BODY="$BODY\n- Add tests in $FILE_NAME"
827
+ else
828
+ BODY="$BODY\n- Update $FILE_NAME"
829
+ fi
830
+ fi
831
+ done
832
+
833
+ # Compose full message (NO TASK IDS)
834
+ if [ -n "$SCOPE" ]; then
835
+ MESSAGE="${COMMIT_TYPE}(${SCOPE}): ${DESCRIPTION}${BODY}"
836
+ else
837
+ MESSAGE="${COMMIT_TYPE}: ${DESCRIPTION}${BODY}"
838
+ fi
839
+
840
+ COMMIT_MESSAGES["$group"]="$MESSAGE"
841
+ done
842
+ ```
843
+
844
+ ### 4.2 Validate Message Format
845
+
846
+ ```bash
847
+ # Validate each generated message
848
+ for group in "${SORTED_GROUPS[@]}"; do
849
+ MESSAGE="${COMMIT_MESSAGES[$group]}"
850
+ FIRST_LINE=$(echo -e "$MESSAGE" | head -1)
851
+
852
+ # Ensure conventional commit format
853
+ if ! echo "$FIRST_LINE" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\(.+\))?: .+'; then
854
+ echo "⚠️ Generated message doesn't follow conventional format"
855
+ echo "Group: $group"
856
+ echo "Message: $FIRST_LINE"
857
+ echo "Adjusting format..."
858
+ fi
859
+
860
+ # Check first line length (≤72 characters)
861
+ if [ ${#FIRST_LINE} -gt 72 ]; then
862
+ echo "⚠️ Message too long (${#FIRST_LINE} chars), truncating to 72"
863
+ FIRST_LINE="${FIRST_LINE:0:69}..."
864
+ # Update message
865
+ BODY=$(echo -e "$MESSAGE" | tail -n +2)
866
+ COMMIT_MESSAGES["$group"]="${FIRST_LINE}${BODY}"
867
+ fi
868
+
869
+ # CRITICAL: Ensure no task IDs in message
870
+ if echo "$MESSAGE" | grep -qE 'Task: #|task #[0-9]'; then
871
+ echo "❌ CRITICAL: Task ID found in commit message!"
872
+ echo "Group: $group"
873
+ echo "Task IDs are internal to the operator and must not be in commits"
874
+ exit 1
875
+ fi
876
+ done
877
+
878
+ echo "✅ All ${#SORTED_GROUPS[@]} commit messages validated"
879
+ ```
880
+
881
+ ## Phase 5: Pre-Commit QA Checks
882
+
883
+ ### 5.1 Run Validation Tasks
884
+
885
+ ```bash
886
+ # Discover available tasks
887
+ AVAILABLE_TASKS=()
888
+ if [ -f Taskfile.yml ]; then
889
+ AVAILABLE_TASKS=($(task --list 2>/dev/null | grep -E 'lint|typecheck|test' | awk '{print $2}'))
890
+ fi
891
+
892
+ # Run linting
893
+ LINT_STATUS="⊘ Skipped"
894
+ LINT_FAILED=false
895
+ if echo "${AVAILABLE_TASKS[@]}" | grep -q '^lint$'; then
896
+ echo "🔍 Running lint checks..."
897
+ if task lint; then
898
+ LINT_STATUS="✅ Passed"
899
+ else
900
+ LINT_STATUS="❌ Failed"
901
+ LINT_FAILED=true
902
+ fi
903
+ fi
904
+
905
+ # Run type checking
906
+ TYPECHECK_STATUS="⊘ Skipped"
907
+ TYPECHECK_FAILED=false
908
+ if echo "${AVAILABLE_TASKS[@]}" | grep -q '^typecheck$'; then
909
+ echo "🔍 Running type checks..."
910
+ if task typecheck; then
911
+ TYPECHECK_STATUS="✅ Passed"
912
+ else
913
+ TYPECHECK_STATUS="❌ Failed"
914
+ TYPECHECK_FAILED=true
915
+ fi
916
+ elif command -v tsc &>/dev/null; then
917
+ echo "🔍 Running TypeScript type checks..."
918
+ if tsc --noEmit; then
919
+ TYPECHECK_STATUS="✅ Passed"
920
+ else
921
+ TYPECHECK_STATUS="❌ Failed"
922
+ TYPECHECK_FAILED=true
923
+ fi
924
+ fi
925
+
926
+ # Run tests (skip in dry-run)
927
+ TEST_STATUS="⊘ Skipped"
928
+ TEST_FAILED=false
929
+ if [ "$DRY_RUN" != "true" ] && echo "${AVAILABLE_TASKS[@]}" | grep -q '^test$'; then
930
+ echo "🧪 Running tests..."
931
+ if task test; then
932
+ TEST_STATUS="✅ Passed"
933
+ else
934
+ TEST_STATUS="❌ Failed"
935
+ TEST_FAILED=true
936
+ fi
937
+ fi
938
+
939
+ # Handle QA failures
940
+ if [ "$LINT_FAILED" = "true" ] || [ "$TYPECHECK_FAILED" = "true" ] || [ "$TEST_FAILED" = "true" ]; then
941
+ echo ""
942
+ echo "❌ Pre-commit QA checks failed"
943
+ echo ""
944
+ [ "$LINT_FAILED" = "true" ] && echo " - Linting: Fix with 'task lint:fix'"
945
+ [ "$TYPECHECK_FAILED" = "true" ] && echo " - Type errors: Review tsc output"
946
+ [ "$TEST_FAILED" = "true" ] && echo " - Tests: Fix failing tests"
947
+ echo ""
948
+ echo "Fix issues and retry commit"
949
+ exit 1
950
+ fi
951
+ ```
952
+
953
+ ## Phase 6: Execute Commits
954
+
955
+ ### 6.1 Dry-Run Mode
956
+
957
+ ```bash
958
+ if [ "$DRY_RUN" = "true" ]; then
959
+ echo ""
960
+ echo "🔍 DRY RUN - No commits will be made"
961
+ echo ""
962
+ echo "📊 Would create ${#SORTED_GROUPS[@]} commits for ${#READY_FILES[@]} files:"
963
+ echo ""
964
+
965
+ COMMIT_NUM=1
966
+ for group in "${SORTED_GROUPS[@]}"; do
967
+ FILES="${GROUP_FILES[$group]}"
968
+ FILE_COUNT=$(echo "$FILES" | wc -w | tr -d ' ')
969
+ MESSAGE="${COMMIT_MESSAGES[$group]}"
970
+ FIRST_LINE=$(echo -e "$MESSAGE" | head -1)
971
+
972
+ echo "Commit $COMMIT_NUM of ${#SORTED_GROUPS[@]}: $FIRST_LINE"
973
+ echo " Files ($FILE_COUNT):"
974
+ for file in $FILES; do
975
+ STATUS=$(git status --porcelain "$file" | awk '{print $1}')
976
+ echo " - $file ($STATUS)"
977
+ done
978
+ echo ""
979
+ ((COMMIT_NUM++))
980
+ done
981
+
982
+ echo "💡 Run without --dry-run to commit"
983
+ exit 0
984
+ fi
985
+ ```
986
+
987
+ ### 6.2 Create Commits Sequentially
988
+
989
+ ```bash
990
+ COMMIT_NUM=1
991
+ CREATED_COMMITS=()
992
+ FAILED_COMMITS=()
993
+
994
+ for group in "${SORTED_GROUPS[@]}"; do
995
+ FILES="${GROUP_FILES[$group]}"
996
+ MESSAGE="${COMMIT_MESSAGES[$group]}"
997
+ FIRST_LINE=$(echo -e "$MESSAGE" | head -1)
998
+
999
+ echo ""
1000
+ echo "📝 Creating commit $COMMIT_NUM of ${#SORTED_GROUPS[@]}: $FIRST_LINE"
1001
+
1002
+ # Reset staging area
1003
+ git reset >/dev/null 2>&1 || true
1004
+
1005
+ # Stage only files for this group
1006
+ for file in $FILES; do
1007
+ git add "$file" || {
1008
+ echo "❌ Failed to stage $file"
1009
+ FAILED_COMMITS+=("$group:Failed to stage files")
1010
+ break 2
1011
+ }
1012
+ done
1013
+
1014
+ # Create commit
1015
+ NO_VERIFY_FLAG=""
1016
+ [ "$NO_VERIFY" = "true" ] && NO_VERIFY_FLAG="--no-verify"
1017
+
1018
+ if git commit $NO_VERIFY_FLAG -m "$(echo -e "$MESSAGE")"; then
1019
+ COMMIT_SHA=$(git rev-parse HEAD)
1020
+ CREATED_COMMITS+=("$group:$COMMIT_SHA")
1021
+ echo "✅ Committed: $COMMIT_SHA"
1022
+ else
1023
+ FAILED_COMMITS+=("$group:Commit failed (likely hooks)")
1024
+ echo "❌ Commit failed for group: $group"
1025
+ echo " Stopping at commit $COMMIT_NUM of ${#SORTED_GROUPS[@]}"
1026
+ break
1027
+ fi
1028
+
1029
+ ((COMMIT_NUM++))
1030
+ done
1031
+
1032
+ # Handle commit failures
1033
+ if [ ${#FAILED_COMMITS[@]} -gt 0 ]; then
1034
+ echo ""
1035
+ echo "❌ Commit sequence failed"
1036
+ echo ""
1037
+ echo "Created: ${#CREATED_COMMITS[@]} commits"
1038
+ echo "Failed: ${#FAILED_COMMITS[@]} commits"
1039
+ echo ""
1040
+ echo "Options:"
1041
+ echo " 1. Fix issues and run /commit again"
1042
+ echo " 2. Use 'git reset HEAD~${#CREATED_COMMITS[@]}' to undo all commits"
1043
+ echo ""
1044
+ exit 1
1045
+ fi
1046
+
1047
+ echo ""
1048
+ echo "✅ All commits successful (${#CREATED_COMMITS[@]} commits created)"
1049
+ ```
1050
+
1051
+ ## Phase 7: Generate Report
1052
+
1053
+ ```bash
1054
+ REPORT_FILE=".commit.report.local.md"
1055
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1056
+
1057
+ cat > "$REPORT_FILE" <<EOF
1058
+ # Commit Report
1059
+
1060
+ **Generated:** $TIMESTAMP
1061
+ **Branch:** $(git branch --show-current)
1062
+ **Operator:** $(git config user.name) <$(git config user.email)>
1063
+
1064
+ ## Summary
1065
+
1066
+ **Files Analyzed:** $TOTAL_FILES
1067
+ **Files Committed:** ${#READY_FILES[@]}
1068
+ **Files Skipped:** ${#SKIPPED_FILES[@]}
1069
+ **Commits Created:** ${#CREATED_COMMITS[@]}
1070
+
1071
+ ## Commits Created
1072
+
1073
+ $(for item in "${CREATED_COMMITS[@]}"; do
1074
+ GROUP=$(echo "$item" | cut -d: -f1)
1075
+ SHA=$(echo "$item" | cut -d: -f2)
1076
+ MESSAGE="${COMMIT_MESSAGES[$GROUP]}"
1077
+ FIRST_LINE=$(echo -e "$MESSAGE" | head -1)
1078
+ FILES="${GROUP_FILES[$GROUP]}"
1079
+ FILE_COUNT=$(echo "$FILES" | wc -w | tr -d ' ')
1080
+ echo "### Commit: \`$SHA\`"
1081
+ echo ""
1082
+ echo "\`\`\`"
1083
+ echo -e "$MESSAGE"
1084
+ echo "\`\`\`"
1085
+ echo ""
1086
+ echo "**Files ($FILE_COUNT):**"
1087
+ echo ""
1088
+ for file in $FILES; do
1089
+ STAT=\$(git diff HEAD~1 --numstat -- "$file" 2>/dev/null | awk '{print "+"$1", -"$2}')
1090
+ echo "- \`$file\` ($STAT)"
1091
+ done
1092
+ echo ""
1093
+ done)
1094
+
1095
+ ## Skipped Files
1096
+
1097
+ $(if [ ${#SKIPPED_FILES[@]} -eq 0 ]; then
1098
+ echo "No files skipped ✅"
1099
+ else
1100
+ echo "**Total Skipped:** ${#SKIPPED_FILES[@]}"
1101
+ echo ""
1102
+ for item in "${SKIPPED_FILES[@]}"; do
1103
+ file=$(echo "$item" | cut -d: -f1)
1104
+ reason=$(echo "$item" | cut -d: -f2-)
1105
+ echo "- \`$file\`: $reason"
1106
+ done
1107
+ fi)
1108
+
1109
+ ## Pre-Commit Checks
1110
+
1111
+ - **Linting:** $LINT_STATUS
1112
+ - **Type Checking:** $TYPECHECK_STATUS
1113
+ - **Tests:** $TEST_STATUS
1114
+
1115
+ ## Next Steps
1116
+
1117
+ $(AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo "0")
1118
+ if [ "$AHEAD" -gt 0 ]; then
1119
+ echo "**Branch Status:** Branch is ahead by $AHEAD commit(s)"
1120
+ echo "**Action Required:** Use \`git push\` to publish changes"
1121
+ else
1122
+ echo "Branch is up to date with remote"
1123
+ fi)
1124
+
1125
+ ---
1126
+
1127
+ **Report Location:** \`.commit.report.local.md\`
1128
+ EOF
1129
+
1130
+ # Add report to gitignore
1131
+ if ! grep -q "^\.commit\.report\.local\.md$" .gitignore 2>/dev/null; then
1132
+ echo ".commit.report.local.md" >> .gitignore
1133
+ fi
1134
+
1135
+ echo ""
1136
+ echo "📄 Report saved: .commit.report.local.md"
1137
+ ```
1138
+
1139
+ ## Phase 8: Present Results
1140
+
1141
+ ```bash
1142
+ echo ""
1143
+ echo "✅ Commit process complete"
1144
+ echo ""
1145
+ echo "📊 Summary:"
1146
+ echo " - Files committed: ${#READY_FILES[@]}"
1147
+ echo " - Commits created: ${#CREATED_COMMITS[@]}"
1148
+ echo " - Branch: $(git branch --show-current)"
1149
+ echo ""
1150
+ echo "📝 Commits:"
1151
+ for item in "${CREATED_COMMITS[@]}"; do
1152
+ GROUP=$(echo "$item" | cut -d: -f1)
1153
+ SHA=$(echo "$item" | cut -d: -f2)
1154
+ MESSAGE="${COMMIT_MESSAGES[$GROUP]}"
1155
+ FIRST_LINE=$(echo -e "$MESSAGE" | head -1)
1156
+ echo " - ${SHA:0:7}: $FIRST_LINE"
1157
+ done
1158
+ echo ""
1159
+ ```
1160
+
1161
+ ## Important Notes
1162
+
1163
+ ### Task Master Integration
1164
+
1165
+ **CRITICAL**: Task IDs are internal to the operator and MUST NOT appear in commit messages.
1166
+
1167
+ **Correct usage:**
1168
+
1169
+ - Use Task Master to understand what work is being done
1170
+ - Match files to task contexts based on title, description, scope
1171
+ - Generate commit messages from task context WITHOUT task IDs
1172
+ - Example: "feat(cli): add selective tool installation" (no task ID)
1173
+
1174
+ **Incorrect usage:**
1175
+
1176
+ - ❌ "feat(cli): implement task 1.2 - selective install"
1177
+ - ❌ "fix(paths): resolve issue in task 5"
1178
+ - ❌ "Task: #1.2" in commit footer
1179
+
1180
+ ### Grouping Philosophy
1181
+
1182
+ **Files that belong together:**
1183
+
1184
+ - Implementation + corresponding tests
1185
+ - Feature + documentation
1186
+ - Related bug fixes
1187
+ - Build configuration changes
1188
+
1189
+ **Files that should be separate commits:**
1190
+
1191
+ - Unrelated features
1192
+ - Different bug fixes
1193
+ - Independent refactorings
1194
+ - Feature + unrelated dependency updates
1195
+
1196
+ ### Hook Compliance
1197
+
1198
+ **Always respect git hooks:**
1199
+
1200
+ - Husky pre-commit hooks validate code quality
1201
+ - Commit-msg hooks enforce conventional commits
1202
+ - Never use `--no-verify` unless explicitly approved by user
1203
+ - Fix issues rather than bypassing hooks
1204
+
1205
+ ### Multi-Commit Benefits
1206
+
1207
+ **Why create multiple commits:**
1208
+
1209
+ - Clear history - each commit has single purpose
1210
+ - Easy review - reviewers understand changes per commit
1211
+ - Better rollback - revert specific changes without affecting others
1212
+ - Semantic grouping - related changes stay together
1213
+
1214
+ **When to use single commit:**
1215
+
1216
+ - User provides `--message` flag (bypasses grouping)
1217
+ - Small changesets (< 5 files, all related)
1218
+ - Trivial changes (formatting, typos, dependency updates)