specweave 0.24.8 → 0.24.9
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/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +3 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +18 -2
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
- package/dist/src/core/repo-structure/git-error-handler.d.ts +1 -1
- package/dist/src/core/repo-structure/git-error-handler.d.ts.map +1 -1
- package/dist/src/core/repo-structure/git-provider.d.ts +1 -1
- package/dist/src/core/repo-structure/git-provider.d.ts.map +1 -1
- package/dist/src/core/repo-structure/platform-registry.d.ts.map +1 -1
- package/dist/src/core/repo-structure/platform-registry.js +20 -9
- package/dist/src/core/repo-structure/platform-registry.js.map +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts +13 -1
- package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
- package/dist/src/core/repo-structure/prompt-consolidator.js +38 -5
- package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
- package/dist/src/core/repo-structure/providers/azure-devops-provider.d.ts +64 -0
- package/dist/src/core/repo-structure/providers/azure-devops-provider.d.ts.map +1 -0
- package/dist/src/core/repo-structure/providers/azure-devops-provider.js +263 -0
- package/dist/src/core/repo-structure/providers/azure-devops-provider.js.map +1 -0
- package/dist/src/core/repo-structure/providers/bitbucket-provider.d.ts +12 -11
- package/dist/src/core/repo-structure/providers/bitbucket-provider.d.ts.map +1 -1
- package/dist/src/core/repo-structure/providers/bitbucket-provider.js +164 -30
- package/dist/src/core/repo-structure/providers/bitbucket-provider.js.map +1 -1
- package/dist/src/core/repo-structure/providers/gitlab-provider.d.ts +10 -9
- package/dist/src/core/repo-structure/providers/gitlab-provider.d.ts.map +1 -1
- package/dist/src/core/repo-structure/providers/gitlab-provider.js +182 -28
- package/dist/src/core/repo-structure/providers/gitlab-provider.js.map +1 -1
- package/dist/src/core/repo-structure/providers/index.d.ts +3 -1
- package/dist/src/core/repo-structure/providers/index.d.ts.map +1 -1
- package/dist/src/core/repo-structure/providers/index.js +10 -2
- package/dist/src/core/repo-structure/providers/index.js.map +1 -1
- package/dist/src/core/repo-structure/providers/local-provider.d.ts +61 -0
- package/dist/src/core/repo-structure/providers/local-provider.d.ts.map +1 -0
- package/dist/src/core/repo-structure/providers/local-provider.js +148 -0
- package/dist/src/core/repo-structure/providers/local-provider.js.map +1 -0
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts +11 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +268 -84
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/agents/pm/AGENT.md.bak +1893 -0
- package/plugins/specweave/hooks/docs-changed.sh.backup +79 -0
- package/plugins/specweave/hooks/human-input-required.sh.backup +75 -0
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh.bak +245 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh.bak +149 -0
- package/plugins/specweave/hooks/lib/validate-spec-status.sh.bak +163 -0
- package/plugins/specweave/hooks/post-first-increment.sh.backup +61 -0
- package/plugins/specweave/hooks/post-first-increment.sh.bak +61 -0
- package/plugins/specweave/hooks/post-increment-change.sh.backup +98 -0
- package/plugins/specweave/hooks/post-increment-completion.sh.backup +231 -0
- package/plugins/specweave/hooks/post-increment-planning.sh.backup +1048 -0
- package/plugins/specweave/hooks/post-increment-status-change.sh.backup +147 -0
- package/plugins/specweave/hooks/post-spec-update.sh.backup +158 -0
- package/plugins/specweave/hooks/post-spec-update.sh.bak +158 -0
- package/plugins/specweave/hooks/post-task-completion.sh +69 -175
- package/plugins/specweave/hooks/post-user-story-complete.sh.backup +179 -0
- package/plugins/specweave/hooks/post-user-story-complete.sh.bak +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +83 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh.bak +83 -0
- package/plugins/specweave/hooks/pre-implementation.sh.backup +67 -0
- package/plugins/specweave/hooks/pre-task-completion.sh.backup +194 -0
- package/plugins/specweave/hooks/pre-tool-use.sh.backup +133 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh.backup +386 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh.bak +386 -0
- package/plugins/specweave/lib/hooks/auto-transition.js.bak +50 -0
- package/plugins/specweave/lib/hooks/auto-transition.ts.bak +84 -0
- package/plugins/specweave/lib/hooks/consolidated-sync.js +183 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.d.ts.bak +89 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.js.bak +142 -0
- package/plugins/specweave/lib/hooks/git-diff-analyzer.ts.bak +269 -0
- package/plugins/specweave/lib/hooks/invoke-translator-skill.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/invoke-translator-skill.d.ts.bak +60 -0
- package/plugins/specweave/lib/hooks/invoke-translator-skill.js.bak +155 -0
- package/plugins/specweave/lib/hooks/invoke-translator-skill.ts.bak +264 -0
- package/plugins/specweave/lib/hooks/prepare-reflection-context.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/prepare-reflection-context.d.ts.bak +42 -0
- package/plugins/specweave/lib/hooks/prepare-reflection-context.js.bak +110 -0
- package/plugins/specweave/lib/hooks/prepare-reflection-context.ts.bak +178 -0
- package/plugins/specweave/lib/hooks/reflection-config-loader.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/reflection-config-loader.d.ts.bak +45 -0
- package/plugins/specweave/lib/hooks/reflection-config-loader.js.bak +92 -0
- package/plugins/specweave/lib/hooks/reflection-config-loader.ts.bak +156 -0
- package/plugins/specweave/lib/hooks/reflection-parser.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/reflection-parser.d.ts.bak +33 -0
- package/plugins/specweave/lib/hooks/reflection-parser.js.bak +301 -0
- package/plugins/specweave/lib/hooks/reflection-parser.ts.bak +484 -0
- package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.ts.bak +56 -0
- package/plugins/specweave/lib/hooks/reflection-prompt-builder.js.bak +182 -0
- package/plugins/specweave/lib/hooks/reflection-prompt-builder.ts.bak +306 -0
- package/plugins/specweave/lib/hooks/reflection-storage.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/reflection-storage.d.ts.bak +64 -0
- package/plugins/specweave/lib/hooks/reflection-storage.js.bak +231 -0
- package/plugins/specweave/lib/hooks/reflection-storage.ts.bak +369 -0
- package/plugins/specweave/lib/hooks/run-self-reflection.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/run-self-reflection.d.ts.bak +43 -0
- package/plugins/specweave/lib/hooks/run-self-reflection.js.bak +132 -0
- package/plugins/specweave/lib/hooks/run-self-reflection.ts.bak +258 -0
- package/plugins/specweave/lib/hooks/sync-cache.js.bak +294 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.d.js.bak +1 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.d.ts.bak +27 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.js.bak +339 -0
- package/plugins/specweave/lib/hooks/sync-us-tasks.js.bak +476 -0
- package/plugins/specweave/lib/hooks/translate-file.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/translate-file.d.ts.bak +59 -0
- package/plugins/specweave/lib/hooks/translate-file.js.bak +289 -0
- package/plugins/specweave/lib/hooks/translate-file.ts.bak +428 -0
- package/plugins/specweave/lib/hooks/translate-living-docs.d.js.bak +0 -0
- package/plugins/specweave/lib/hooks/translate-living-docs.d.ts.bak +13 -0
- package/plugins/specweave/lib/hooks/translate-living-docs.js.bak +119 -0
- package/plugins/specweave/lib/hooks/translate-living-docs.ts.bak +224 -0
- package/plugins/specweave/lib/hooks/update-ac-status.js.bak +51 -0
- package/plugins/specweave/lib/hooks/update-ac-status.ts.bak +103 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.d.js.bak +1 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.d.ts.bak +29 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.js.bak +296 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.ts.bak +489 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +353 -0
- package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-ado/lib/ado-multi-project-sync.js +1 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +424 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh.backup +258 -0
- package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +172 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +540 -0
- 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 };
|
|
File without changes
|
|
@@ -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
|
+
}
|
|
File without changes
|