@zoebuildsai/trace 1.5.0
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/.gitignore +115 -0
- package/.trace/progress.json +22 -0
- package/README.md +466 -0
- package/RELEASE-NOTES-1.5.0.md +410 -0
- package/STATUS.md +245 -0
- package/dist/auto-commit.d.ts +66 -0
- package/dist/auto-commit.d.ts.map +1 -0
- package/dist/auto-commit.js +180 -0
- package/dist/auto-commit.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +246 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +46 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +256 -0
- package/dist/commands.js.map +1 -0
- package/dist/diff.d.ts +23 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +106 -0
- package/dist/diff.js.map +1 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js.map +1 -0
- package/dist/index-cache.d.ts +35 -0
- package/dist/index-cache.d.ts.map +1 -0
- package/dist/index-cache.js +114 -0
- package/dist/index-cache.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +45 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +151 -0
- package/dist/storage.js.map +1 -0
- package/dist/sync.d.ts +60 -0
- package/dist/sync.js +184 -0
- package/dist/tags.d.ts +85 -0
- package/dist/tags.d.ts.map +1 -0
- package/dist/tags.js +219 -0
- package/dist/tags.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +73 -0
- package/docs/_config.yml +2 -0
- package/docs/index.html +960 -0
- package/docs-website/package.json +20 -0
- package/jest.config.js +21 -0
- package/package.json +50 -0
- package/scripts/init.ts +290 -0
- package/src/agent-audit.ts +270 -0
- package/src/agent-checkout.ts +227 -0
- package/src/agent-coordination.ts +318 -0
- package/src/async-queue.ts +203 -0
- package/src/auto-branching.ts +279 -0
- package/src/auto-commit.ts +166 -0
- package/src/cherry-pick.ts +252 -0
- package/src/chunked-upload.ts +224 -0
- package/src/cli-v2.ts +335 -0
- package/src/cli.ts +318 -0
- package/src/cliff-detection.ts +232 -0
- package/src/commands.ts +267 -0
- package/src/commit-hash-system.ts +351 -0
- package/src/compression.ts +176 -0
- package/src/conflict-resolution-ui.ts +277 -0
- package/src/conflict-visualization.ts +238 -0
- package/src/diff-formatter.ts +184 -0
- package/src/diff.ts +124 -0
- package/src/distributed-coordination.ts +273 -0
- package/src/git-interop.ts +316 -0
- package/src/index-cache.ts +88 -0
- package/src/index.ts +38 -0
- package/src/merge-engine.ts +143 -0
- package/src/message-search.ts +370 -0
- package/src/performance-monitoring.ts +236 -0
- package/src/rebase.ts +327 -0
- package/src/rollback.ts +215 -0
- package/src/semantic-grouping.ts +245 -0
- package/src/stage-area.ts +324 -0
- package/src/stash.ts +278 -0
- package/src/storage.ts +131 -0
- package/src/sync.ts +205 -0
- package/src/tags.ts +244 -0
- package/src/types.ts +119 -0
- package/src/webhooks.ts +119 -0
- package/src/workspace-isolation.ts +298 -0
- package/tests/auto-commit.test.ts +308 -0
- package/tests/checkout.test.ts +136 -0
- package/tests/commit.test.ts +118 -0
- package/tests/diff.test.ts +191 -0
- package/tests/github.test.ts +94 -0
- package/tests/integration.test.ts +267 -0
- package/tests/log.test.ts +125 -0
- package/tests/phase2-integration.test.ts +370 -0
- package/tests/storage.test.ts +167 -0
- package/tests/tags.test.ts +477 -0
- package/tests/types.test.ts +75 -0
- package/tests/v1.1/agent-audit.test.ts +472 -0
- package/tests/v1.1/agent-coordination.test.ts +308 -0
- package/tests/v1.1/async-queue.test.ts +253 -0
- package/tests/v1.1/comprehensive.test.ts +521 -0
- package/tests/v1.1/diff-formatter.test.ts +238 -0
- package/tests/v1.1/integration.test.ts +389 -0
- package/tests/v1.1/onboarding.test.ts +365 -0
- package/tests/v1.1/rollback.test.ts +370 -0
- package/tests/v1.1/semantic-grouping.test.ts +230 -0
- package/tests/v1.2/chunked-upload.test.ts +301 -0
- package/tests/v1.2/cliff-detection.test.ts +272 -0
- package/tests/v1.2/commit-hash-system.test.ts +288 -0
- package/tests/v1.2/compression.test.ts +220 -0
- package/tests/v1.2/conflict-visualization.test.ts +263 -0
- package/tests/v1.2/distributed.test.ts +261 -0
- package/tests/v1.2/performance-monitoring.test.ts +328 -0
- package/tests/v1.3/auto-branching.test.ts +270 -0
- package/tests/v1.3/message-search.test.ts +264 -0
- package/tests/v1.3/stage-area.test.ts +330 -0
- package/tests/v1.3/stash-rebase-cherry-pick.test.ts +361 -0
- package/tests/v1.4/cli.test.ts +171 -0
- package/tests/v1.4/conflict-resolution-advanced.test.ts +429 -0
- package/tests/v1.4/conflict-resolution-ui.test.ts +286 -0
- package/tests/v1.4/workspace-isolation-advanced.test.ts +382 -0
- package/tests/v1.4/workspace-isolation.test.ts +268 -0
- package/tests/v1.5/agent-coordination.real.test.ts +401 -0
- package/tests/v1.5/cli-v2.test.ts +354 -0
- package/tests/v1.5/git-interop.real.test.ts +358 -0
- package/tests/v1.5/integration-testing.real.test.ts +440 -0
- package/tsconfig.json +26 -0
package/src/rollback.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollback Engine for Trace
|
|
3
|
+
* Reset to previous commit state (equivalent to git reset)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface RollbackPoint {
|
|
7
|
+
commitId: string;
|
|
8
|
+
agentId: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
message: string;
|
|
11
|
+
fileStates: Map<string, string>; // file -> content hash
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RollbackResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
fromCommit: string;
|
|
17
|
+
toCommit: string;
|
|
18
|
+
filesRestored: string[];
|
|
19
|
+
filesDiscarded: string[];
|
|
20
|
+
timestamp: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class RollbackEngine {
|
|
24
|
+
private history: RollbackPoint[] = [];
|
|
25
|
+
private fileSnapshots: Map<string, Map<string, string>> = new Map(); // commitId -> fileStates
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create rollback point (snapshot)
|
|
29
|
+
*/
|
|
30
|
+
createSnapshot(
|
|
31
|
+
commitId: string,
|
|
32
|
+
agentId: string,
|
|
33
|
+
message: string,
|
|
34
|
+
files: Map<string, string>
|
|
35
|
+
): void {
|
|
36
|
+
const snapshot: RollbackPoint = {
|
|
37
|
+
commitId,
|
|
38
|
+
agentId,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
message,
|
|
41
|
+
fileStates: new Map(files),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.history.push(snapshot);
|
|
45
|
+
this.fileSnapshots.set(commitId, new Map(files));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Rollback to specific commit
|
|
50
|
+
*/
|
|
51
|
+
rollbackToCommit(targetCommitId: string): RollbackResult {
|
|
52
|
+
const targetIndex = this.history.findIndex(h => h.commitId === targetCommitId);
|
|
53
|
+
|
|
54
|
+
if (targetIndex === -1) {
|
|
55
|
+
throw new Error(`Commit ${targetCommitId} not found`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const currentCommit = this.history[this.history.length - 1];
|
|
59
|
+
const targetCommit = this.history[targetIndex];
|
|
60
|
+
|
|
61
|
+
const currentFiles = this.fileSnapshots.get(currentCommit.commitId) || new Map();
|
|
62
|
+
const targetFiles = this.fileSnapshots.get(targetCommitId) || new Map();
|
|
63
|
+
|
|
64
|
+
// Find what needs to be restored/discarded
|
|
65
|
+
const filesRestored: string[] = [];
|
|
66
|
+
const filesDiscarded: string[] = [];
|
|
67
|
+
|
|
68
|
+
// Files that exist in target but not in current (restore)
|
|
69
|
+
for (const file of targetFiles.keys()) {
|
|
70
|
+
if (!currentFiles.has(file)) {
|
|
71
|
+
filesRestored.push(file);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Files that exist in current but not in target (discard)
|
|
76
|
+
for (const file of currentFiles.keys()) {
|
|
77
|
+
if (!targetFiles.has(file)) {
|
|
78
|
+
filesDiscarded.push(file);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Truncate history to target
|
|
83
|
+
this.history = this.history.slice(0, targetIndex + 1);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
fromCommit: currentCommit.commitId,
|
|
88
|
+
toCommit: targetCommitId,
|
|
89
|
+
filesRestored,
|
|
90
|
+
filesDiscarded,
|
|
91
|
+
timestamp: Date.now(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Rollback N commits back
|
|
97
|
+
*/
|
|
98
|
+
rollbackN(n: number): RollbackResult {
|
|
99
|
+
if (n < 1) throw new Error('N must be >= 1');
|
|
100
|
+
if (n > this.history.length) throw new Error('Cannot rollback more commits than exist');
|
|
101
|
+
|
|
102
|
+
const targetIndex = this.history.length - n - 1;
|
|
103
|
+
const targetCommit = this.history[targetIndex];
|
|
104
|
+
|
|
105
|
+
return this.rollbackToCommit(targetCommit.commitId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Rollback to commit before agent's changes
|
|
110
|
+
*/
|
|
111
|
+
rollbackBeforeAgent(agentId: string): RollbackResult {
|
|
112
|
+
const targetIndex = this.history.findLastIndex(h => h.agentId !== agentId);
|
|
113
|
+
|
|
114
|
+
if (targetIndex === -1) {
|
|
115
|
+
throw new Error(`No commits found before agent ${agentId}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const targetCommit = this.history[targetIndex];
|
|
119
|
+
return this.rollbackToCommit(targetCommit.commitId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get available rollback points
|
|
124
|
+
*/
|
|
125
|
+
getRollbackPoints(limit: number = 10): RollbackPoint[] {
|
|
126
|
+
return this.history.slice(-limit).reverse();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if rollback is safe (no conflicts)
|
|
131
|
+
*/
|
|
132
|
+
isRollbackSafe(targetCommitId: string): boolean {
|
|
133
|
+
const targetIndex = this.history.findIndex(h => h.commitId === targetCommitId);
|
|
134
|
+
|
|
135
|
+
if (targetIndex === -1) return false;
|
|
136
|
+
|
|
137
|
+
// Safe if no uncommitted changes would be lost
|
|
138
|
+
// (in real implementation, would check working directory)
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get diff between two commits
|
|
144
|
+
*/
|
|
145
|
+
getDiffBetween(fromCommitId: string, toCommitId: string): Map<string, string> {
|
|
146
|
+
const fromFiles = this.fileSnapshots.get(fromCommitId) || new Map();
|
|
147
|
+
const toFiles = this.fileSnapshots.get(toCommitId) || new Map();
|
|
148
|
+
|
|
149
|
+
const diff = new Map<string, string>();
|
|
150
|
+
|
|
151
|
+
for (const [file, toHash] of toFiles) {
|
|
152
|
+
const fromHash = fromFiles.get(file);
|
|
153
|
+
if (fromHash !== toHash) {
|
|
154
|
+
diff.set(file, `${fromHash || 'NEW'} -> ${toHash}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const [file] of fromFiles) {
|
|
159
|
+
if (!toFiles.has(file)) {
|
|
160
|
+
diff.set(file, `DELETED`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return diff;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get history
|
|
169
|
+
*/
|
|
170
|
+
getHistory(): RollbackPoint[] {
|
|
171
|
+
return [...this.history];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get commit details
|
|
176
|
+
*/
|
|
177
|
+
getCommit(commitId: string): RollbackPoint | undefined {
|
|
178
|
+
return this.history.find(h => h.commitId === commitId);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* List all commits by agent
|
|
183
|
+
*/
|
|
184
|
+
getCommitsByAgent(agentId: string): RollbackPoint[] {
|
|
185
|
+
return this.history.filter(h => h.agentId === agentId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Verify rollback integrity
|
|
190
|
+
*/
|
|
191
|
+
verifyIntegrity(): { valid: boolean; errors: string[] } {
|
|
192
|
+
const errors: string[] = [];
|
|
193
|
+
|
|
194
|
+
// Check history is chronological
|
|
195
|
+
for (let i = 0; i < this.history.length - 1; i++) {
|
|
196
|
+
if (this.history[i].timestamp > this.history[i + 1].timestamp) {
|
|
197
|
+
errors.push(`Timeline violation at commit ${i}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check snapshots match history
|
|
202
|
+
for (const commit of this.history) {
|
|
203
|
+
if (!this.fileSnapshots.has(commit.commitId)) {
|
|
204
|
+
errors.push(`Missing snapshot for commit ${commit.commitId}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
valid: errors.length === 0,
|
|
210
|
+
errors,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export default RollbackEngine;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Grouping for Trace
|
|
3
|
+
* Auto-categorizes changes by type (feature, fix, docs, test, refactor, perf, security)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type ChangeType = 'feature' | 'fix' | 'docs' | 'test' | 'refactor' | 'perf' | 'security' | 'chore' | 'unknown';
|
|
7
|
+
|
|
8
|
+
export interface GroupedChange {
|
|
9
|
+
type: ChangeType;
|
|
10
|
+
files: string[];
|
|
11
|
+
count: number;
|
|
12
|
+
summary: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SemanticGroup {
|
|
16
|
+
feature: GroupedChange | null;
|
|
17
|
+
fix: GroupedChange | null;
|
|
18
|
+
docs: GroupedChange | null;
|
|
19
|
+
test: GroupedChange | null;
|
|
20
|
+
refactor: GroupedChange | null;
|
|
21
|
+
perf: GroupedChange | null;
|
|
22
|
+
security: GroupedChange | null;
|
|
23
|
+
chore: GroupedChange | null;
|
|
24
|
+
unknown: GroupedChange | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class SemanticGrouping {
|
|
28
|
+
/**
|
|
29
|
+
* Detect change type from file path and content
|
|
30
|
+
*/
|
|
31
|
+
static detectType(filePath: string, commitMessage?: string): ChangeType {
|
|
32
|
+
// Pattern matching for file paths
|
|
33
|
+
if (this.matchesPattern(filePath, /\.(test|spec)\.ts$|__tests__|\.test\./)) {
|
|
34
|
+
return 'test';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (this.matchesPattern(filePath, /README|\.md$|docs\//)) {
|
|
38
|
+
return 'docs';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (this.matchesPattern(filePath, /security|auth|crypt|encrypt|hash|sign/)) {
|
|
42
|
+
return 'security';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (this.matchesPattern(filePath, /perf|bench|cache|optim|compress/)) {
|
|
46
|
+
return 'perf';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (this.matchesPattern(filePath, /refactor|cleanup|reorganize|rename/)) {
|
|
50
|
+
return 'refactor';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Commit message patterns
|
|
54
|
+
if (commitMessage) {
|
|
55
|
+
const msg = commitMessage.toLowerCase();
|
|
56
|
+
|
|
57
|
+
if (msg.startsWith('fix:') || msg.startsWith('bugfix')) {
|
|
58
|
+
return 'fix';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (msg.startsWith('feat:') || msg.startsWith('feature')) {
|
|
62
|
+
return 'feature';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (msg.startsWith('docs:')) {
|
|
66
|
+
return 'docs';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (msg.startsWith('test:')) {
|
|
70
|
+
return 'test';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (msg.startsWith('perf:')) {
|
|
74
|
+
return 'perf';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (msg.startsWith('security:') || msg.includes('security')) {
|
|
78
|
+
return 'security';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (msg.startsWith('refactor:')) {
|
|
82
|
+
return 'refactor';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (msg.startsWith('chore:')) {
|
|
86
|
+
return 'chore';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Default heuristics
|
|
91
|
+
if (this.matchesPattern(filePath, /src\/.*\.ts$/)) {
|
|
92
|
+
return 'feature'; // src/ files are usually features
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return 'unknown';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Group files by change type
|
|
100
|
+
*/
|
|
101
|
+
static group(files: string[], commitMessage?: string): SemanticGroup {
|
|
102
|
+
const groups: Record<ChangeType, string[]> = {
|
|
103
|
+
feature: [],
|
|
104
|
+
fix: [],
|
|
105
|
+
docs: [],
|
|
106
|
+
test: [],
|
|
107
|
+
refactor: [],
|
|
108
|
+
perf: [],
|
|
109
|
+
security: [],
|
|
110
|
+
chore: [],
|
|
111
|
+
unknown: [],
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
const type = this.detectType(file, commitMessage);
|
|
116
|
+
groups[type].push(file);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Build result
|
|
120
|
+
const result: SemanticGroup = {
|
|
121
|
+
feature: groups.feature.length > 0 ? {
|
|
122
|
+
type: 'feature',
|
|
123
|
+
files: groups.feature,
|
|
124
|
+
count: groups.feature.length,
|
|
125
|
+
summary: `Added ${groups.feature.length} new feature(s)`,
|
|
126
|
+
} : null,
|
|
127
|
+
fix: groups.fix.length > 0 ? {
|
|
128
|
+
type: 'fix',
|
|
129
|
+
files: groups.fix,
|
|
130
|
+
count: groups.fix.length,
|
|
131
|
+
summary: `Fixed ${groups.fix.length} issue(s)`,
|
|
132
|
+
} : null,
|
|
133
|
+
docs: groups.docs.length > 0 ? {
|
|
134
|
+
type: 'docs',
|
|
135
|
+
files: groups.docs,
|
|
136
|
+
count: groups.docs.length,
|
|
137
|
+
summary: `Updated ${groups.docs.length} doc(s)`,
|
|
138
|
+
} : null,
|
|
139
|
+
test: groups.test.length > 0 ? {
|
|
140
|
+
type: 'test',
|
|
141
|
+
files: groups.test,
|
|
142
|
+
count: groups.test.length,
|
|
143
|
+
summary: `Added ${groups.test.length} test(s)`,
|
|
144
|
+
} : null,
|
|
145
|
+
refactor: groups.refactor.length > 0 ? {
|
|
146
|
+
type: 'refactor',
|
|
147
|
+
files: groups.refactor,
|
|
148
|
+
count: groups.refactor.length,
|
|
149
|
+
summary: `Refactored ${groups.refactor.length} file(s)`,
|
|
150
|
+
} : null,
|
|
151
|
+
perf: groups.perf.length > 0 ? {
|
|
152
|
+
type: 'perf',
|
|
153
|
+
files: groups.perf,
|
|
154
|
+
count: groups.perf.length,
|
|
155
|
+
summary: `Optimized ${groups.perf.length} file(s)`,
|
|
156
|
+
} : null,
|
|
157
|
+
security: groups.security.length > 0 ? {
|
|
158
|
+
type: 'security',
|
|
159
|
+
files: groups.security,
|
|
160
|
+
count: groups.security.length,
|
|
161
|
+
summary: `Enhanced security in ${groups.security.length} file(s)`,
|
|
162
|
+
} : null,
|
|
163
|
+
chore: groups.chore.length > 0 ? {
|
|
164
|
+
type: 'chore',
|
|
165
|
+
files: groups.chore,
|
|
166
|
+
count: groups.chore.length,
|
|
167
|
+
summary: `Maintenance: ${groups.chore.length} file(s)`,
|
|
168
|
+
} : null,
|
|
169
|
+
unknown: groups.unknown.length > 0 ? {
|
|
170
|
+
type: 'unknown',
|
|
171
|
+
files: groups.unknown,
|
|
172
|
+
count: groups.unknown.length,
|
|
173
|
+
summary: `Uncategorized: ${groups.unknown.length} file(s)`,
|
|
174
|
+
} : null,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Generate semantic commit message from grouped changes
|
|
182
|
+
*/
|
|
183
|
+
static generateMessage(group: SemanticGroup): string {
|
|
184
|
+
const parts: string[] = [];
|
|
185
|
+
|
|
186
|
+
if (group.feature) {
|
|
187
|
+
parts.push(`โจ ${group.feature.summary}`);
|
|
188
|
+
}
|
|
189
|
+
if (group.fix) {
|
|
190
|
+
parts.push(`๐ ${group.fix.summary}`);
|
|
191
|
+
}
|
|
192
|
+
if (group.security) {
|
|
193
|
+
parts.push(`๐ ${group.security.summary}`);
|
|
194
|
+
}
|
|
195
|
+
if (group.perf) {
|
|
196
|
+
parts.push(`โก ${group.perf.summary}`);
|
|
197
|
+
}
|
|
198
|
+
if (group.test) {
|
|
199
|
+
parts.push(`โ
${group.test.summary}`);
|
|
200
|
+
}
|
|
201
|
+
if (group.docs) {
|
|
202
|
+
parts.push(`๐ ${group.docs.summary}`);
|
|
203
|
+
}
|
|
204
|
+
if (group.refactor) {
|
|
205
|
+
parts.push(`โป๏ธ ${group.refactor.summary}`);
|
|
206
|
+
}
|
|
207
|
+
if (group.chore) {
|
|
208
|
+
parts.push(`๐งน ${group.chore.summary}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return parts.join('\n');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Helper: match pattern against path
|
|
216
|
+
*/
|
|
217
|
+
private static matchesPattern(path: string, pattern: RegExp): boolean {
|
|
218
|
+
return pattern.test(path);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Check if files represent a "micro" commit (minimal changes)
|
|
223
|
+
*/
|
|
224
|
+
static isMicroCommit(files: string[], lineChanges?: number): boolean {
|
|
225
|
+
// Micro = few files, few lines
|
|
226
|
+
const fileCount = files.length;
|
|
227
|
+
const lines = lineChanges || 50;
|
|
228
|
+
|
|
229
|
+
return fileCount <= 5 && lines <= 100;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get dominant change type (most files)
|
|
234
|
+
*/
|
|
235
|
+
static getDominantType(group: SemanticGroup): ChangeType {
|
|
236
|
+
const types: (ChangeType | null)[] = Object.values(group);
|
|
237
|
+
const withCounts = types
|
|
238
|
+
.filter((t): t is ChangeType => t !== null)
|
|
239
|
+
.sort((a, b) => (group[b]?.count || 0) - (group[a]?.count || 0));
|
|
240
|
+
|
|
241
|
+
return withCounts[0] || 'unknown';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default SemanticGrouping;
|