specweave 0.24.8 → 0.24.11

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 (132) hide show
  1. package/dist/src/cli/commands/init.d.ts.map +1 -1
  2. package/dist/src/cli/commands/init.js +3 -1
  3. package/dist/src/cli/commands/init.js.map +1 -1
  4. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  5. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +18 -2
  6. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  7. package/dist/src/cli/helpers/issue-tracker/index.js +4 -4
  8. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  9. package/dist/src/core/repo-structure/git-error-handler.d.ts +1 -1
  10. package/dist/src/core/repo-structure/git-error-handler.d.ts.map +1 -1
  11. package/dist/src/core/repo-structure/git-provider.d.ts +1 -1
  12. package/dist/src/core/repo-structure/git-provider.d.ts.map +1 -1
  13. package/dist/src/core/repo-structure/platform-registry.d.ts.map +1 -1
  14. package/dist/src/core/repo-structure/platform-registry.js +20 -9
  15. package/dist/src/core/repo-structure/platform-registry.js.map +1 -1
  16. package/dist/src/core/repo-structure/prompt-consolidator.d.ts +13 -1
  17. package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
  18. package/dist/src/core/repo-structure/prompt-consolidator.js +38 -5
  19. package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
  20. package/dist/src/core/repo-structure/providers/azure-devops-provider.d.ts +64 -0
  21. package/dist/src/core/repo-structure/providers/azure-devops-provider.d.ts.map +1 -0
  22. package/dist/src/core/repo-structure/providers/azure-devops-provider.js +263 -0
  23. package/dist/src/core/repo-structure/providers/azure-devops-provider.js.map +1 -0
  24. package/dist/src/core/repo-structure/providers/bitbucket-provider.d.ts +12 -11
  25. package/dist/src/core/repo-structure/providers/bitbucket-provider.d.ts.map +1 -1
  26. package/dist/src/core/repo-structure/providers/bitbucket-provider.js +164 -30
  27. package/dist/src/core/repo-structure/providers/bitbucket-provider.js.map +1 -1
  28. package/dist/src/core/repo-structure/providers/gitlab-provider.d.ts +10 -9
  29. package/dist/src/core/repo-structure/providers/gitlab-provider.d.ts.map +1 -1
  30. package/dist/src/core/repo-structure/providers/gitlab-provider.js +182 -28
  31. package/dist/src/core/repo-structure/providers/gitlab-provider.js.map +1 -1
  32. package/dist/src/core/repo-structure/providers/index.d.ts +3 -1
  33. package/dist/src/core/repo-structure/providers/index.d.ts.map +1 -1
  34. package/dist/src/core/repo-structure/providers/index.js +10 -2
  35. package/dist/src/core/repo-structure/providers/index.js.map +1 -1
  36. package/dist/src/core/repo-structure/providers/local-provider.d.ts +61 -0
  37. package/dist/src/core/repo-structure/providers/local-provider.d.ts.map +1 -0
  38. package/dist/src/core/repo-structure/providers/local-provider.js +148 -0
  39. package/dist/src/core/repo-structure/providers/local-provider.js.map +1 -0
  40. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +11 -1
  41. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  42. package/dist/src/core/repo-structure/repo-structure-manager.js +268 -84
  43. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  44. package/package.json +1 -1
  45. package/plugins/specweave/agents/pm/AGENT.md.bak +1893 -0
  46. package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
  47. package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
  48. package/plugins/specweave/hooks/lib/migrate-increment-work.sh.bak +245 -0
  49. package/plugins/specweave/hooks/lib/sync-spec-content.sh.bak +149 -0
  50. package/plugins/specweave/hooks/lib/validate-spec-status.sh.bak +163 -0
  51. package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
  52. package/plugins/specweave/hooks/post-first-increment.sh.bak +61 -0
  53. package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
  54. package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
  55. package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
  56. package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
  57. package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
  58. package/plugins/specweave/hooks/post-spec-update.sh.bak +158 -0
  59. package/plugins/specweave/hooks/post-task-completion.sh +69 -175
  60. package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
  61. package/plugins/specweave/hooks/post-user-story-complete.sh.bak +179 -0
  62. package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
  63. package/plugins/specweave/hooks/pre-command-deduplication.sh.bak +83 -0
  64. package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
  65. package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
  66. package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
  67. package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
  68. package/plugins/specweave/hooks/user-prompt-submit.sh.bak +386 -0
  69. package/plugins/specweave/lib/hooks/auto-transition.js.bak +50 -0
  70. package/plugins/specweave/lib/hooks/auto-transition.ts.bak +84 -0
  71. package/plugins/specweave/lib/hooks/consolidated-sync.js +183 -0
  72. package/plugins/specweave/lib/hooks/git-diff-analyzer.d.js.bak +0 -0
  73. package/plugins/specweave/lib/hooks/git-diff-analyzer.d.ts.bak +89 -0
  74. package/plugins/specweave/lib/hooks/git-diff-analyzer.js.bak +142 -0
  75. package/plugins/specweave/lib/hooks/git-diff-analyzer.ts.bak +269 -0
  76. package/plugins/specweave/lib/hooks/invoke-translator-skill.d.js.bak +0 -0
  77. package/plugins/specweave/lib/hooks/invoke-translator-skill.d.ts.bak +60 -0
  78. package/plugins/specweave/lib/hooks/invoke-translator-skill.js.bak +155 -0
  79. package/plugins/specweave/lib/hooks/invoke-translator-skill.ts.bak +264 -0
  80. package/plugins/specweave/lib/hooks/prepare-reflection-context.d.js.bak +0 -0
  81. package/plugins/specweave/lib/hooks/prepare-reflection-context.d.ts.bak +42 -0
  82. package/plugins/specweave/lib/hooks/prepare-reflection-context.js.bak +110 -0
  83. package/plugins/specweave/lib/hooks/prepare-reflection-context.ts.bak +178 -0
  84. package/plugins/specweave/lib/hooks/reflection-config-loader.d.js.bak +0 -0
  85. package/plugins/specweave/lib/hooks/reflection-config-loader.d.ts.bak +45 -0
  86. package/plugins/specweave/lib/hooks/reflection-config-loader.js.bak +92 -0
  87. package/plugins/specweave/lib/hooks/reflection-config-loader.ts.bak +156 -0
  88. package/plugins/specweave/lib/hooks/reflection-parser.d.js.bak +0 -0
  89. package/plugins/specweave/lib/hooks/reflection-parser.d.ts.bak +33 -0
  90. package/plugins/specweave/lib/hooks/reflection-parser.js.bak +301 -0
  91. package/plugins/specweave/lib/hooks/reflection-parser.ts.bak +484 -0
  92. package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.js.bak +0 -0
  93. package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.ts.bak +56 -0
  94. package/plugins/specweave/lib/hooks/reflection-prompt-builder.js.bak +182 -0
  95. package/plugins/specweave/lib/hooks/reflection-prompt-builder.ts.bak +306 -0
  96. package/plugins/specweave/lib/hooks/reflection-storage.d.js.bak +0 -0
  97. package/plugins/specweave/lib/hooks/reflection-storage.d.ts.bak +64 -0
  98. package/plugins/specweave/lib/hooks/reflection-storage.js.bak +231 -0
  99. package/plugins/specweave/lib/hooks/reflection-storage.ts.bak +369 -0
  100. package/plugins/specweave/lib/hooks/run-self-reflection.d.js.bak +0 -0
  101. package/plugins/specweave/lib/hooks/run-self-reflection.d.ts.bak +43 -0
  102. package/plugins/specweave/lib/hooks/run-self-reflection.js.bak +132 -0
  103. package/plugins/specweave/lib/hooks/run-self-reflection.ts.bak +258 -0
  104. package/plugins/specweave/lib/hooks/sync-cache.js.bak +294 -0
  105. package/plugins/specweave/lib/hooks/sync-living-docs.d.js.bak +1 -0
  106. package/plugins/specweave/lib/hooks/sync-living-docs.d.ts.bak +27 -0
  107. package/plugins/specweave/lib/hooks/sync-living-docs.js.bak +339 -0
  108. package/plugins/specweave/lib/hooks/sync-us-tasks.js.bak +476 -0
  109. package/plugins/specweave/lib/hooks/translate-file.d.js.bak +0 -0
  110. package/plugins/specweave/lib/hooks/translate-file.d.ts.bak +59 -0
  111. package/plugins/specweave/lib/hooks/translate-file.js.bak +289 -0
  112. package/plugins/specweave/lib/hooks/translate-file.ts.bak +428 -0
  113. package/plugins/specweave/lib/hooks/translate-living-docs.d.js.bak +0 -0
  114. package/plugins/specweave/lib/hooks/translate-living-docs.d.ts.bak +13 -0
  115. package/plugins/specweave/lib/hooks/translate-living-docs.js.bak +119 -0
  116. package/plugins/specweave/lib/hooks/translate-living-docs.ts.bak +224 -0
  117. package/plugins/specweave/lib/hooks/update-ac-status.js.bak +51 -0
  118. package/plugins/specweave/lib/hooks/update-ac-status.ts.bak +103 -0
  119. package/plugins/specweave/lib/hooks/update-tasks-md.d.js.bak +1 -0
  120. package/plugins/specweave/lib/hooks/update-tasks-md.d.ts.bak +29 -0
  121. package/plugins/specweave/lib/hooks/update-tasks-md.js.bak +296 -0
  122. package/plugins/specweave/lib/hooks/update-tasks-md.ts.bak +489 -0
  123. package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
  124. package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
  125. package/plugins/specweave-ado/lib/ado-multi-project-sync.js +1 -0
  126. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
  127. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +424 -0
  128. package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
  129. package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
  130. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
  131. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +540 -0
  132. package/plugins/specweave-release/hooks/post-task-completion.sh.backup +110 -0
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Consolidated Sync Hook
4
+ *
5
+ * PERFORMANCE OPTIMIZATION (v0.24.4):
6
+ * =====================================
7
+ * This script consolidates ALL post-task-completion operations into a SINGLE
8
+ * Node.js process instead of spawning 5-6 separate processes.
9
+ *
10
+ * BEFORE (v0.24.3):
11
+ * - Spawn 1: update-tasks-md.js (10KB)
12
+ * - Spawn 2: sync-living-docs.js (18KB)
13
+ * - Spawn 3: update-ac-status.js (2KB)
14
+ * - Spawn 4: translate-living-docs.js (4.4KB)
15
+ * - Spawn 5: run-self-reflection.js (4.1KB - conditional)
16
+ * Total: 5-6 Node.js processes per TodoWrite
17
+ *
18
+ * AFTER (v0.24.4):
19
+ * - Spawn 1: consolidated-sync.js (runs all operations sequentially)
20
+ * Total: 1 Node.js process per TodoWrite
21
+ *
22
+ * IMPACT:
23
+ * - 83% reduction in process spawning overhead
24
+ * - Faster execution (shared Node.js context)
25
+ * - Better error handling (single error boundary)
26
+ * - Reduced system resource usage
27
+ *
28
+ * ROOT CAUSE ADDRESSED:
29
+ * Multiple rapid TodoWrite calls were spawning 12+ concurrent Node.js processes,
30
+ * causing process exhaustion and Claude Code crashes. This consolidation prevents
31
+ * that by limiting to 1 process per hook fire.
32
+ *
33
+ * See: .specweave/increments/0051-automatic-github-sync/reports/HOOK-CRASH-ANALYSIS.md
34
+ * ADR: (pending - will be created after testing)
35
+ */
36
+
37
+ // Import REAL implementations from existing modules
38
+ import { updateTasksMd } from './update-tasks-md.js';
39
+ import { syncLivingDocs } from './sync-living-docs.js';
40
+ import { translateLivingDocs } from './translate-living-docs.js';
41
+
42
+ // Import for AC status (uses ACStatusManager directly)
43
+ import { ACStatusManager } from '../vendor/core/increment/ac-status-manager.js';
44
+
45
+ // ============================================================================
46
+ // WRAPPER: UPDATE AC STATUS
47
+ // ============================================================================
48
+ // Note: update-ac-status.js doesn't export a function, it only has CLI logic
49
+ // So we replicate the core logic here using ACStatusManager
50
+ async function updateACStatus(incrementId) {
51
+ try {
52
+ console.log(`\n🔄 [3/5] Syncing AC status for increment ${incrementId}...`);
53
+
54
+ const projectRoot = process.cwd();
55
+
56
+ if (process.env.SKIP_AC_SYNC === 'true') {
57
+ console.log('ℹ️ AC sync skipped (SKIP_AC_SYNC=true)');
58
+ return { success: true, message: 'Sync skipped' };
59
+ }
60
+
61
+ const manager = new ACStatusManager(projectRoot);
62
+ const result = await manager.syncACStatus(incrementId);
63
+
64
+ if (result.warnings && result.warnings.length > 0) {
65
+ console.log('\n⚠️ Warnings:');
66
+ result.warnings.forEach((warning) => console.log(` ${warning}`));
67
+ }
68
+
69
+ if (result.conflicts && result.conflicts.length > 0) {
70
+ console.log('\n⚠️ Conflicts detected:');
71
+ result.conflicts.forEach((conflict) => console.log(` ${conflict}`));
72
+ }
73
+
74
+ if (result.updated && result.updated.length > 0) {
75
+ console.log('\n✅ Updated AC checkboxes:');
76
+ result.updated.forEach((acId) => console.log(` ${acId} → [x]`));
77
+ } else if (result.synced) {
78
+ console.log('✅ All ACs already in sync (no changes needed)');
79
+ } else {
80
+ console.log('ℹ️ No AC updates needed');
81
+ }
82
+
83
+ return { success: true, result };
84
+ } catch (error) {
85
+ console.error('❌ Error updating AC status:', error.message);
86
+ return { success: false, error: error.message };
87
+ }
88
+ }
89
+
90
+ // ============================================================================
91
+ // MAIN EXECUTION
92
+ // ============================================================================
93
+ async function runConsolidatedSync(incrementId) {
94
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
95
+ console.log(`🚀 CONSOLIDATED SYNC: ${incrementId}`);
96
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
97
+
98
+ const startTime = Date.now();
99
+ const results = {};
100
+
101
+ try {
102
+ // OPERATION 1: Update tasks.md (uses imported function)
103
+ console.log('\n🔄 [1/4] Updating tasks.md...');
104
+ try {
105
+ await updateTasksMd(incrementId);
106
+ results.updateTasks = { success: true };
107
+ } catch (error) {
108
+ console.error('❌ Error updating tasks.md:', error.message);
109
+ results.updateTasks = { success: false, error: error.message };
110
+ }
111
+
112
+ // OPERATION 2: Sync living docs (uses imported function)
113
+ console.log('\n📚 [2/4] Syncing living docs...');
114
+ try {
115
+ await syncLivingDocs(incrementId);
116
+ results.syncDocs = { success: true };
117
+ } catch (error) {
118
+ console.error('❌ Error syncing living docs:', error.message);
119
+ results.syncDocs = { success: false, error: error.message };
120
+ }
121
+
122
+ // OPERATION 3: Update AC status (uses local wrapper)
123
+ try {
124
+ results.updateAC = await updateACStatus(incrementId);
125
+ } catch (error) {
126
+ console.error('❌ Error updating AC status:', error.message);
127
+ results.updateAC = { success: false, error: error.message };
128
+ }
129
+
130
+ // OPERATION 4: Translate living docs (uses imported function)
131
+ console.log('\n🌐 [4/4] Checking translation needs...');
132
+ try {
133
+ await translateLivingDocs(incrementId);
134
+ results.translate = { success: true };
135
+ } catch (error) {
136
+ console.error('❌ Error translating docs:', error.message);
137
+ results.translate = { success: false, error: error.message };
138
+ }
139
+
140
+ const duration = Date.now() - startTime;
141
+
142
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
143
+ console.log(`✅ CONSOLIDATED SYNC COMPLETED in ${duration}ms`);
144
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
145
+
146
+ // Summary of results
147
+ const successCount = Object.values(results).filter(r => r.success).length;
148
+ const totalCount = Object.keys(results).length;
149
+ console.log(`📊 Results: ${successCount}/${totalCount} operations successful`);
150
+
151
+ // Check for any failures (non-blocking - we tolerate partial failures)
152
+ const failures = Object.entries(results).filter(([_, result]) => !result.success);
153
+ if (failures.length > 0) {
154
+ console.warn('\n⚠️ Some operations had issues (non-blocking):');
155
+ failures.forEach(([op, result]) => {
156
+ console.warn(` ${op}: ${result.error || 'Unknown error'}`);
157
+ });
158
+ }
159
+
160
+ // Always exit 0 to prevent hook failures from blocking Claude Code
161
+ process.exit(0);
162
+ } catch (error) {
163
+ console.error('\n❌ FATAL ERROR in consolidated sync:', error);
164
+ // Even on fatal error, exit 0 to prevent blocking Claude Code
165
+ process.exit(0);
166
+ }
167
+ }
168
+
169
+ // CLI Interface
170
+ const isMainModule = import.meta.url === `file://${process.argv[1]}`;
171
+ if (isMainModule) {
172
+ const incrementId = process.argv[2];
173
+
174
+ if (!incrementId) {
175
+ console.error('Usage: node consolidated-sync.js <increment-id>');
176
+ console.error('Example: node consolidated-sync.js 0051-automatic-github-sync');
177
+ process.exit(1);
178
+ }
179
+
180
+ runConsolidatedSync(incrementId);
181
+ }
182
+
183
+ export { runConsolidatedSync };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Git Diff Analyzer
3
+ *
4
+ * Extracts modified files from git diff for reflection analysis
5
+ * Parses git diff output to get file changes, line counts, and content
6
+ *
7
+ * @module git-diff-analyzer
8
+ */
9
+ import { GitDiffInfo } from './types/reflection-types';
10
+ /**
11
+ * Check if directory is a git repository
12
+ * @param dir Directory to check (defaults to cwd)
13
+ * @returns True if directory is in a git repository
14
+ */
15
+ export declare function isGitRepository(dir?: string): boolean;
16
+ /**
17
+ * Get list of modified files in the working directory
18
+ * Includes both staged and unstaged changes
19
+ *
20
+ * @param cwd Working directory (optional, defaults to process.cwd())
21
+ * @returns Array of file paths relative to git root
22
+ */
23
+ export declare function getModifiedFilesList(cwd?: string): string[];
24
+ /**
25
+ * Parse git diff numstat output to get line counts
26
+ * Format: <added>\t<removed>\t<filename>
27
+ *
28
+ * @param numstatOutput Output from git diff --numstat
29
+ * @returns Map of filename to {added, removed} counts
30
+ */
31
+ export declare function parseNumstat(numstatOutput: string): Map<string, {
32
+ added: number;
33
+ removed: number;
34
+ }>;
35
+ /**
36
+ * Get diff content for a specific file
37
+ * @param file File path relative to git root
38
+ * @param cwd Working directory (optional)
39
+ * @returns Diff content as string
40
+ */
41
+ export declare function getFileDiff(file: string, cwd?: string): string;
42
+ /**
43
+ * Get current file content
44
+ * @param file File path (can be absolute or relative to cwd)
45
+ * @param cwd Working directory (optional)
46
+ * @returns File content as string, or empty string if file doesn't exist
47
+ */
48
+ export declare function getFileContent(file: string, cwd?: string): string;
49
+ /**
50
+ * Get modified files with diff information
51
+ * Main function for reflection analysis
52
+ *
53
+ * @param cwd Working directory (optional, defaults to process.cwd())
54
+ * @param maxFiles Maximum number of files to return (optional, defaults to 100)
55
+ * @returns Array of GitDiffInfo objects with file changes
56
+ */
57
+ export declare function getModifiedFiles(cwd?: string, maxFiles?: number): GitDiffInfo[];
58
+ /**
59
+ * Get summary statistics for modified files
60
+ * Useful for reflection metadata
61
+ *
62
+ * @param modifiedFiles Array of GitDiffInfo objects
63
+ * @returns Summary with file count, total lines added/removed
64
+ */
65
+ export declare function getModifiedFilesSummary(modifiedFiles: GitDiffInfo[]): {
66
+ count: number;
67
+ linesAdded: number;
68
+ linesRemoved: number;
69
+ totalChanges: number;
70
+ };
71
+ /**
72
+ * Filter files by extension
73
+ * Useful for focusing reflection on specific file types
74
+ *
75
+ * @param modifiedFiles Array of GitDiffInfo objects
76
+ * @param extensions Array of file extensions (e.g., ['.ts', '.js'])
77
+ * @returns Filtered array of GitDiffInfo objects
78
+ */
79
+ export declare function filterFilesByExtension(modifiedFiles: GitDiffInfo[], extensions: string[]): GitDiffInfo[];
80
+ /**
81
+ * Exclude files matching patterns
82
+ * Useful for excluding generated files, test files, etc.
83
+ *
84
+ * @param modifiedFiles Array of GitDiffInfo objects
85
+ * @param patterns Array of glob patterns to exclude
86
+ * @returns Filtered array of GitDiffInfo objects
87
+ */
88
+ export declare function excludeFilesByPattern(modifiedFiles: GitDiffInfo[], patterns: string[]): GitDiffInfo[];
89
+ //# sourceMappingURL=git-diff-analyzer.d.ts.map
@@ -0,0 +1,142 @@
1
+ import { execSync } from "child_process";
2
+ import fs from "fs-extra";
3
+ import path from "path";
4
+ function executeGitCommand(command, cwd) {
5
+ try {
6
+ return execSync(command, {
7
+ cwd: cwd || process.cwd(),
8
+ encoding: "utf-8",
9
+ stdio: ["pipe", "pipe", "pipe"]
10
+ });
11
+ } catch (error) {
12
+ throw new Error(`Git command failed: ${command}. ${error.message}`);
13
+ }
14
+ }
15
+ function isGitRepository(dir = process.cwd()) {
16
+ try {
17
+ executeGitCommand("git rev-parse --git-dir", dir);
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+ function getModifiedFilesList(cwd) {
24
+ if (!isGitRepository(cwd)) {
25
+ return [];
26
+ }
27
+ try {
28
+ const output = executeGitCommand("git diff --name-only HEAD", cwd);
29
+ if (!output.trim()) {
30
+ return [];
31
+ }
32
+ return output.trim().split("\n").filter((file) => file.length > 0).filter((file) => !file.startsWith(".git/"));
33
+ } catch {
34
+ return [];
35
+ }
36
+ }
37
+ function parseNumstat(numstatOutput) {
38
+ const stats = /* @__PURE__ */ new Map();
39
+ if (!numstatOutput.trim()) {
40
+ return stats;
41
+ }
42
+ const lines = numstatOutput.trim().split("\n");
43
+ for (const line of lines) {
44
+ const parts = line.split(" ");
45
+ if (parts.length < 3) continue;
46
+ const added = parts[0] === "-" ? 0 : parseInt(parts[0], 10);
47
+ const removed = parts[1] === "-" ? 0 : parseInt(parts[1], 10);
48
+ const filename = parts[2];
49
+ stats.set(filename, { added, removed });
50
+ }
51
+ return stats;
52
+ }
53
+ function getFileDiff(file, cwd) {
54
+ if (!isGitRepository(cwd)) {
55
+ return "";
56
+ }
57
+ try {
58
+ const output = executeGitCommand(`git diff HEAD -- "${file}"`, cwd);
59
+ return output;
60
+ } catch {
61
+ return "";
62
+ }
63
+ }
64
+ function getFileContent(file, cwd) {
65
+ try {
66
+ const workingDir = cwd || process.cwd();
67
+ const absolutePath = path.isAbsolute(file) ? file : path.join(workingDir, file);
68
+ if (!fs.existsSync(absolutePath)) {
69
+ return "";
70
+ }
71
+ return fs.readFileSync(absolutePath, "utf-8");
72
+ } catch {
73
+ return "";
74
+ }
75
+ }
76
+ function getModifiedFiles(cwd, maxFiles = 100) {
77
+ if (!isGitRepository(cwd)) {
78
+ return [];
79
+ }
80
+ const workingDir = cwd || process.cwd();
81
+ const modifiedFiles = getModifiedFilesList(workingDir);
82
+ if (modifiedFiles.length === 0) {
83
+ return [];
84
+ }
85
+ const filesToAnalyze = modifiedFiles.slice(0, maxFiles);
86
+ let numstatOutput = "";
87
+ try {
88
+ numstatOutput = executeGitCommand("git diff --numstat HEAD", workingDir);
89
+ } catch {
90
+ }
91
+ const stats = parseNumstat(numstatOutput);
92
+ const result = [];
93
+ for (const file of filesToAnalyze) {
94
+ const fileStat = stats.get(file) || { added: 0, removed: 0 };
95
+ const diffContent = getFileDiff(file, workingDir);
96
+ if (fileStat.added > 0 || fileStat.removed > 0 || diffContent.length > 0) {
97
+ result.push({
98
+ file,
99
+ linesAdded: fileStat.added,
100
+ linesRemoved: fileStat.removed,
101
+ content: diffContent
102
+ });
103
+ }
104
+ }
105
+ return result;
106
+ }
107
+ function getModifiedFilesSummary(modifiedFiles) {
108
+ return {
109
+ count: modifiedFiles.length,
110
+ linesAdded: modifiedFiles.reduce((sum, file) => sum + file.linesAdded, 0),
111
+ linesRemoved: modifiedFiles.reduce((sum, file) => sum + file.linesRemoved, 0),
112
+ totalChanges: modifiedFiles.reduce(
113
+ (sum, file) => sum + file.linesAdded + file.linesRemoved,
114
+ 0
115
+ )
116
+ };
117
+ }
118
+ function filterFilesByExtension(modifiedFiles, extensions) {
119
+ return modifiedFiles.filter((file) => {
120
+ const ext = path.extname(file.file).toLowerCase();
121
+ return extensions.some((allowedExt) => ext === allowedExt.toLowerCase());
122
+ });
123
+ }
124
+ function excludeFilesByPattern(modifiedFiles, patterns) {
125
+ return modifiedFiles.filter((file) => {
126
+ return !patterns.some((pattern) => {
127
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
128
+ return regex.test(file.file);
129
+ });
130
+ });
131
+ }
132
+ export {
133
+ excludeFilesByPattern,
134
+ filterFilesByExtension,
135
+ getFileContent,
136
+ getFileDiff,
137
+ getModifiedFiles,
138
+ getModifiedFilesList,
139
+ getModifiedFilesSummary,
140
+ isGitRepository,
141
+ parseNumstat
142
+ };
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Git Diff Analyzer
3
+ *
4
+ * Extracts modified files from git diff for reflection analysis
5
+ * Parses git diff output to get file changes, line counts, and content
6
+ *
7
+ * @module git-diff-analyzer
8
+ */
9
+
10
+ import { execSync } from 'child_process';
11
+ import fs from 'fs-extra';
12
+ import path from 'path';
13
+ import { GitDiffInfo } from './types/reflection-types';
14
+
15
+ /**
16
+ * Execute git command safely
17
+ * @param command Git command to execute
18
+ * @param cwd Working directory (optional)
19
+ * @returns Command output as string
20
+ * @throws Error if command fails
21
+ */
22
+ function executeGitCommand(command: string, cwd?: string): string {
23
+ try {
24
+ return execSync(command, {
25
+ cwd: cwd || process.cwd(),
26
+ encoding: 'utf-8',
27
+ stdio: ['pipe', 'pipe', 'pipe']
28
+ });
29
+ } catch (error: any) {
30
+ throw new Error(`Git command failed: ${command}. ${error.message}`);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Check if directory is a git repository
36
+ * @param dir Directory to check (defaults to cwd)
37
+ * @returns True if directory is in a git repository
38
+ */
39
+ export function isGitRepository(dir: string = process.cwd()): boolean {
40
+ try {
41
+ executeGitCommand('git rev-parse --git-dir', dir);
42
+ return true;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Get list of modified files in the working directory
50
+ * Includes both staged and unstaged changes
51
+ *
52
+ * @param cwd Working directory (optional, defaults to process.cwd())
53
+ * @returns Array of file paths relative to git root
54
+ */
55
+ export function getModifiedFilesList(cwd?: string): string[] {
56
+ if (!isGitRepository(cwd)) {
57
+ return [];
58
+ }
59
+
60
+ try {
61
+ // Get both staged and unstaged changes
62
+ const output = executeGitCommand('git diff --name-only HEAD', cwd);
63
+
64
+ if (!output.trim()) {
65
+ return [];
66
+ }
67
+
68
+ return output
69
+ .trim()
70
+ .split('\n')
71
+ .filter(file => file.length > 0)
72
+ .filter(file => !file.startsWith('.git/')); // Exclude .git directory
73
+ } catch {
74
+ return [];
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Parse git diff numstat output to get line counts
80
+ * Format: <added>\t<removed>\t<filename>
81
+ *
82
+ * @param numstatOutput Output from git diff --numstat
83
+ * @returns Map of filename to {added, removed} counts
84
+ */
85
+ export function parseNumstat(numstatOutput: string): Map<string, { added: number; removed: number }> {
86
+ const stats = new Map<string, { added: number; removed: number }>();
87
+
88
+ if (!numstatOutput.trim()) {
89
+ return stats;
90
+ }
91
+
92
+ const lines = numstatOutput.trim().split('\n');
93
+
94
+ for (const line of lines) {
95
+ const parts = line.split('\t');
96
+ if (parts.length < 3) continue;
97
+
98
+ const added = parts[0] === '-' ? 0 : parseInt(parts[0], 10);
99
+ const removed = parts[1] === '-' ? 0 : parseInt(parts[1], 10);
100
+ const filename = parts[2];
101
+
102
+ stats.set(filename, { added, removed });
103
+ }
104
+
105
+ return stats;
106
+ }
107
+
108
+ /**
109
+ * Get diff content for a specific file
110
+ * @param file File path relative to git root
111
+ * @param cwd Working directory (optional)
112
+ * @returns Diff content as string
113
+ */
114
+ export function getFileDiff(file: string, cwd?: string): string {
115
+ if (!isGitRepository(cwd)) {
116
+ return '';
117
+ }
118
+
119
+ try {
120
+ // Get unified diff for the file
121
+ const output = executeGitCommand(`git diff HEAD -- "${file}"`, cwd);
122
+ return output;
123
+ } catch {
124
+ return '';
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Get current file content
130
+ * @param file File path (can be absolute or relative to cwd)
131
+ * @param cwd Working directory (optional)
132
+ * @returns File content as string, or empty string if file doesn't exist
133
+ */
134
+ export function getFileContent(file: string, cwd?: string): string {
135
+ try {
136
+ const workingDir = cwd || process.cwd();
137
+ const absolutePath = path.isAbsolute(file) ? file : path.join(workingDir, file);
138
+
139
+ if (!fs.existsSync(absolutePath)) {
140
+ return '';
141
+ }
142
+
143
+ return fs.readFileSync(absolutePath, 'utf-8');
144
+ } catch {
145
+ return '';
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Get modified files with diff information
151
+ * Main function for reflection analysis
152
+ *
153
+ * @param cwd Working directory (optional, defaults to process.cwd())
154
+ * @param maxFiles Maximum number of files to return (optional, defaults to 100)
155
+ * @returns Array of GitDiffInfo objects with file changes
156
+ */
157
+ export function getModifiedFiles(
158
+ cwd?: string,
159
+ maxFiles: number = 100
160
+ ): GitDiffInfo[] {
161
+ if (!isGitRepository(cwd)) {
162
+ return [];
163
+ }
164
+
165
+ const workingDir = cwd || process.cwd();
166
+
167
+ // Get list of modified files
168
+ const modifiedFiles = getModifiedFilesList(workingDir);
169
+
170
+ if (modifiedFiles.length === 0) {
171
+ return [];
172
+ }
173
+
174
+ // Limit number of files to prevent overwhelming the analysis
175
+ const filesToAnalyze = modifiedFiles.slice(0, maxFiles);
176
+
177
+ // Get line count statistics
178
+ let numstatOutput = '';
179
+ try {
180
+ numstatOutput = executeGitCommand('git diff --numstat HEAD', workingDir);
181
+ } catch {
182
+ // If numstat fails, continue with empty stats
183
+ }
184
+
185
+ const stats = parseNumstat(numstatOutput);
186
+
187
+ // Build GitDiffInfo array
188
+ const result: GitDiffInfo[] = [];
189
+
190
+ for (const file of filesToAnalyze) {
191
+ const fileStat = stats.get(file) || { added: 0, removed: 0 };
192
+ const diffContent = getFileDiff(file, workingDir);
193
+
194
+ // Only include files with actual changes
195
+ if (fileStat.added > 0 || fileStat.removed > 0 || diffContent.length > 0) {
196
+ result.push({
197
+ file,
198
+ linesAdded: fileStat.added,
199
+ linesRemoved: fileStat.removed,
200
+ content: diffContent
201
+ });
202
+ }
203
+ }
204
+
205
+ return result;
206
+ }
207
+
208
+ /**
209
+ * Get summary statistics for modified files
210
+ * Useful for reflection metadata
211
+ *
212
+ * @param modifiedFiles Array of GitDiffInfo objects
213
+ * @returns Summary with file count, total lines added/removed
214
+ */
215
+ export function getModifiedFilesSummary(modifiedFiles: GitDiffInfo[]): {
216
+ count: number;
217
+ linesAdded: number;
218
+ linesRemoved: number;
219
+ totalChanges: number;
220
+ } {
221
+ return {
222
+ count: modifiedFiles.length,
223
+ linesAdded: modifiedFiles.reduce((sum, file) => sum + file.linesAdded, 0),
224
+ linesRemoved: modifiedFiles.reduce((sum, file) => sum + file.linesRemoved, 0),
225
+ totalChanges: modifiedFiles.reduce(
226
+ (sum, file) => sum + file.linesAdded + file.linesRemoved,
227
+ 0
228
+ )
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Filter files by extension
234
+ * Useful for focusing reflection on specific file types
235
+ *
236
+ * @param modifiedFiles Array of GitDiffInfo objects
237
+ * @param extensions Array of file extensions (e.g., ['.ts', '.js'])
238
+ * @returns Filtered array of GitDiffInfo objects
239
+ */
240
+ export function filterFilesByExtension(
241
+ modifiedFiles: GitDiffInfo[],
242
+ extensions: string[]
243
+ ): GitDiffInfo[] {
244
+ return modifiedFiles.filter(file => {
245
+ const ext = path.extname(file.file).toLowerCase();
246
+ return extensions.some(allowedExt => ext === allowedExt.toLowerCase());
247
+ });
248
+ }
249
+
250
+ /**
251
+ * Exclude files matching patterns
252
+ * Useful for excluding generated files, test files, etc.
253
+ *
254
+ * @param modifiedFiles Array of GitDiffInfo objects
255
+ * @param patterns Array of glob patterns to exclude
256
+ * @returns Filtered array of GitDiffInfo objects
257
+ */
258
+ export function excludeFilesByPattern(
259
+ modifiedFiles: GitDiffInfo[],
260
+ patterns: string[]
261
+ ): GitDiffInfo[] {
262
+ return modifiedFiles.filter(file => {
263
+ return !patterns.some(pattern => {
264
+ // Simple pattern matching (supports * wildcard)
265
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
266
+ return regex.test(file.file);
267
+ });
268
+ });
269
+ }