aios-core 4.2.13 → 4.2.14
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/.aios-core/core/code-intel/helpers/dev-helper.js +206 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
- package/.aios-core/data/entity-registry.yaml +27 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/build-autonomous.md +10 -4
- package/.aios-core/development/tasks/create-service.md +23 -0
- package/.aios-core/development/tasks/dev-develop-story.md +12 -6
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
- package/.aios-core/development/tasks/publish-npm.md +3 -3
- package/.aios-core/hooks/unified/README.md +1 -1
- package/.aios-core/install-manifest.yaml +65 -61
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/README.en.md +747 -0
- package/README.md +4 -2
- package/bin/aios.js +7 -4
- package/package.json +1 -1
- package/packages/aios-pro-cli/src/recover.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +6 -6
- package/packages/installer/src/wizard/pro-setup.js +3 -3
- package/scripts/package-synapse.js +5 -5
- package/scripts/validate-package-completeness.js +3 -3
- package/.aios-core/.session/current-session.json +0 -14
- package/.aios-core/data/registry-update-log.jsonl +0 -191
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
- package/.aios-core/docs/component-creation-guide.md +0 -458
- package/.aios-core/docs/session-update-pattern.md +0 -307
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
- package/.aios-core/docs/template-syntax.md +0 -267
- package/.aios-core/docs/troubleshooting-guide.md +0 -625
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
- package/.aios-core/manifests/agents.csv +0 -29
- package/.aios-core/manifests/tasks.csv +0 -198
- package/.aios-core/manifests/workers.csv +0 -204
- package/.claude/rules/agent-authority.md +0 -105
- package/.claude/rules/coderabbit-integration.md +0 -93
- package/.claude/rules/ids-principles.md +0 -112
- package/.claude/rules/story-lifecycle.md +0 -139
- package/.claude/rules/workflow-execution.md +0 -150
- package/pro/README.md +0 -66
- package/pro/license/degradation.js +0 -220
- package/pro/license/errors.js +0 -450
- package/pro/license/feature-gate.js +0 -354
- package/pro/license/index.js +0 -181
- package/pro/license/license-api.js +0 -651
- package/pro/license/license-cache.js +0 -523
- package/pro/license/license-crypto.js +0 -303
- package/scripts/glue/README.md +0 -355
- package/scripts/glue/compose-agent-prompt.cjs +0 -362
- /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
- /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
|
@@ -1,390 +1,390 @@
|
|
|
1
|
-
const GitWrapper = require('./git-wrapper');
|
|
2
|
-
const chalk = require('chalk');
|
|
3
|
-
const inquirer = require('inquirer');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Manages git branches for meta-agent modifications
|
|
8
|
-
*/
|
|
9
|
-
class BranchManager {
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
this.git = new GitWrapper(options);
|
|
12
|
-
this.branchPrefix = options.branchPrefix || 'meta-agent/';
|
|
13
|
-
this.maxBranches = options.maxBranches || 10;
|
|
14
|
-
this.autoCleanup = options.autoCleanup !== false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Create a modification branch with proper naming
|
|
19
|
-
* @param {Object} modification - Modification details
|
|
20
|
-
* @returns {Promise<Object>} Branch creation result
|
|
21
|
-
*/
|
|
22
|
-
async createModificationBranch(modification) {
|
|
23
|
-
const {
|
|
24
|
-
type,
|
|
25
|
-
target,
|
|
26
|
-
action,
|
|
27
|
-
ticketId
|
|
28
|
-
} = modification;
|
|
29
|
-
|
|
30
|
-
// Generate branch name
|
|
31
|
-
const timestamp = Date.now();
|
|
32
|
-
const sanitizedTarget = target.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
|
33
|
-
const sanitizedAction = action.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
|
34
|
-
|
|
35
|
-
let branchName = `${this.branchPrefix}${type}/${sanitizedTarget}-${sanitizedAction}-${timestamp}`;
|
|
36
|
-
|
|
37
|
-
if (ticketId) {
|
|
38
|
-
branchName = `${this.branchPrefix}${ticketId}/${type}-${sanitizedTarget}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
// Ensure we're on the default branch first
|
|
43
|
-
const currentBranch = await this.git.getCurrentBranch();
|
|
44
|
-
if (currentBranch !== this.git.defaultBranch) {
|
|
45
|
-
console.log(chalk.yellow(`Switching to ${this.git.defaultBranch} before creating new branch`));
|
|
46
|
-
await this.git.checkoutBranch(this.git.defaultBranch);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Create and checkout the new branch
|
|
50
|
-
await this.git.createBranch(branchName, true);
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
success: true,
|
|
54
|
-
branchName,
|
|
55
|
-
baseBranch: this.git.defaultBranch,
|
|
56
|
-
timestamp: new Date(timestamp).toISOString()
|
|
57
|
-
};
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.error(chalk.red(`Failed to create branch: ${error.message}`));
|
|
60
|
-
return {
|
|
61
|
-
success: false,
|
|
62
|
-
error: error.message
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get all modification branches
|
|
69
|
-
* @returns {Promise<Array>} List of modification branches
|
|
70
|
-
*/
|
|
71
|
-
async getModificationBranches() {
|
|
72
|
-
try {
|
|
73
|
-
const output = await this.git.execGit('branch -a');
|
|
74
|
-
const branches = output.split('\n')
|
|
75
|
-
.map(line => line.trim().replace('* ', ''))
|
|
76
|
-
.filter(branch => branch.startsWith(this.branchPrefix));
|
|
77
|
-
|
|
78
|
-
const branchDetails = [];
|
|
79
|
-
for (const branch of branches) {
|
|
80
|
-
const lastCommit = await this.git.execGit(`log -1 --format="%H|%at|%s" ${branch}`);
|
|
81
|
-
const [hash, timestamp, subject] = lastCommit.split('|');
|
|
82
|
-
|
|
83
|
-
branchDetails.push({
|
|
84
|
-
name: branch,
|
|
85
|
-
lastCommitHash: hash,
|
|
86
|
-
lastCommitDate: new Date(parseInt(timestamp) * 1000),
|
|
87
|
-
lastCommitMessage: subject,
|
|
88
|
-
age: this.calculateAge(parseInt(timestamp) * 1000)
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return branchDetails.sort((a, b) => b.lastCommitDate - a.lastCommitDate);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error(chalk.red(`Failed to get modification branches: ${error.message}`));
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Switch to a modification branch
|
|
101
|
-
* @param {string} branchName - Branch to switch to
|
|
102
|
-
* @returns {Promise<Object>} Switch result
|
|
103
|
-
*/
|
|
104
|
-
async switchToBranch(branchName) {
|
|
105
|
-
try {
|
|
106
|
-
// Check for uncommitted changes
|
|
107
|
-
const status = await this.git.getStatus();
|
|
108
|
-
if (!status.clean) {
|
|
109
|
-
const { action } = await inquirer.prompt([{
|
|
110
|
-
type: 'list',
|
|
111
|
-
name: 'action',
|
|
112
|
-
message: 'You have uncommitted changes. What would you like to do?',
|
|
113
|
-
choices: [
|
|
114
|
-
{ name: 'Stash changes and switch', value: 'stash' },
|
|
115
|
-
{ name: 'Commit changes first', value: 'commit' },
|
|
116
|
-
{ name: 'Cancel', value: 'cancel' }
|
|
117
|
-
]
|
|
118
|
-
}]);
|
|
119
|
-
|
|
120
|
-
if (action === 'cancel') {
|
|
121
|
-
return { success: false, reason: 'User cancelled' };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (action === 'stash') {
|
|
125
|
-
await this.git.stash(`Auto-stash before switching to ${branchName}`);
|
|
126
|
-
} else if (action === 'commit') {
|
|
127
|
-
await this.git.stageFiles(['.']);
|
|
128
|
-
await this.git.commit('WIP: Auto-commit before branch switch');
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
await this.git.checkoutBranch(branchName);
|
|
133
|
-
return { success: true, branchName };
|
|
134
|
-
} catch (error) {
|
|
135
|
-
return { success: false, error: error.message };
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Merge modification branch back to main
|
|
141
|
-
* @param {string} branchName - Branch to merge
|
|
142
|
-
* @param {Object} options - Merge options
|
|
143
|
-
* @returns {Promise<Object>} Merge result
|
|
144
|
-
*/
|
|
145
|
-
async mergeModificationBranch(branchName, options = {}) {
|
|
146
|
-
try {
|
|
147
|
-
// Switch to target branch
|
|
148
|
-
const targetBranch = options.targetBranch || this.git.defaultBranch;
|
|
149
|
-
await this.git.checkoutBranch(targetBranch);
|
|
150
|
-
|
|
151
|
-
// Attempt merge
|
|
152
|
-
const mergeResult = await this.git.mergeBranch(branchName, {
|
|
153
|
-
message: options.message || `Merge modification branch '${branchName}'`,
|
|
154
|
-
noFastForward: true
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
if (mergeResult.success) {
|
|
158
|
-
// Optionally delete the branch after successful merge
|
|
159
|
-
if (options.deleteBranch) {
|
|
160
|
-
await this.deleteBranch(branchName);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
success: true,
|
|
165
|
-
message: 'Branch merged successfully',
|
|
166
|
-
targetBranch
|
|
167
|
-
};
|
|
168
|
-
} else {
|
|
169
|
-
return {
|
|
170
|
-
success: false,
|
|
171
|
-
conflicts: mergeResult.conflicts,
|
|
172
|
-
message: 'Merge conflicts detected'
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
} catch (error) {
|
|
176
|
-
return {
|
|
177
|
-
success: false,
|
|
178
|
-
error: error.message
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Delete a modification branch
|
|
185
|
-
* @param {string} branchName - Branch to delete
|
|
186
|
-
* @param {boolean} force - Force delete even if not merged
|
|
187
|
-
* @returns {Promise<Object>} Deletion result
|
|
188
|
-
*/
|
|
189
|
-
async deleteBranch(branchName, force = false) {
|
|
190
|
-
try {
|
|
191
|
-
const flag = force ? '-D' : '-d';
|
|
192
|
-
await this.git.execGit(`branch ${flag} ${branchName}`);
|
|
193
|
-
|
|
194
|
-
console.log(chalk.green(`✅ Deleted branch: ${branchName}`));
|
|
195
|
-
return { success: true };
|
|
196
|
-
} catch (error) {
|
|
197
|
-
if (error.message.includes('not fully merged')) {
|
|
198
|
-
console.error(chalk.red('Branch not fully merged. Use force=true to delete anyway.'));
|
|
199
|
-
}
|
|
200
|
-
return { success: false, error: error.message };
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Clean up old modification branches
|
|
206
|
-
* @param {number} daysOld - Delete branches older than this many days
|
|
207
|
-
* @returns {Promise<Object>} Cleanup result
|
|
208
|
-
*/
|
|
209
|
-
async cleanupOldBranches(daysOld = 30) {
|
|
210
|
-
const branches = await this.getModificationBranches();
|
|
211
|
-
const cutoffDate = new Date();
|
|
212
|
-
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
|
|
213
|
-
|
|
214
|
-
const toDelete = branches.filter(branch =>
|
|
215
|
-
branch.lastCommitDate < cutoffDate &&
|
|
216
|
-
branch.name !== await this.git.getCurrentBranch()
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
if (toDelete.length === 0) {
|
|
220
|
-
console.log(chalk.yellow('No old branches to clean up'));
|
|
221
|
-
return { deleted: 0 };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
console.log(chalk.blue(`Found ${toDelete.length} branches older than ${daysOld} days:`));
|
|
225
|
-
toDelete.forEach(branch => {
|
|
226
|
-
console.log(chalk.gray(` - ${branch.name} (${branch.age})`));
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const { confirm } = await inquirer.prompt([{
|
|
230
|
-
type: 'confirm',
|
|
231
|
-
name: 'confirm',
|
|
232
|
-
message: `Delete ${toDelete.length} old branches?`,
|
|
233
|
-
default: false
|
|
234
|
-
}]);
|
|
235
|
-
|
|
236
|
-
if (!confirm) {
|
|
237
|
-
return { deleted: 0, cancelled: true };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
let deleted = 0;
|
|
241
|
-
for (const branch of toDelete) {
|
|
242
|
-
const result = await this.deleteBranch(branch.name, true);
|
|
243
|
-
if (result.success) deleted++;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return { deleted, total: toDelete.length };
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Create a branch protection strategy
|
|
251
|
-
* @param {string} branchName - Branch to protect
|
|
252
|
-
* @returns {Promise<Object>} Protection result
|
|
253
|
-
*/
|
|
254
|
-
async protectBranch(branchName) {
|
|
255
|
-
// This would integrate with GitHub/GitLab API for real protection
|
|
256
|
-
// For now, we'll track it locally
|
|
257
|
-
const protectionFile = path.join(
|
|
258
|
-
this.git.rootPath,
|
|
259
|
-
'.git',
|
|
260
|
-
'aios-branch-protection.json'
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
let protections = {};
|
|
265
|
-
try {
|
|
266
|
-
const content = await require('fs').promises.readFile(protectionFile, 'utf-8');
|
|
267
|
-
protections = JSON.parse(content);
|
|
268
|
-
} catch (_e) {
|
|
269
|
-
// File doesn't exist yet
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
protections[branchName] = {
|
|
273
|
-
protected: true,
|
|
274
|
-
requiredReviews: 1,
|
|
275
|
-
dismissStaleReviews: true,
|
|
276
|
-
requireUpToDate: true,
|
|
277
|
-
protectedAt: new Date().toISOString()
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
await require('fs').promises.writeFile(
|
|
281
|
-
protectionFile,
|
|
282
|
-
JSON.stringify(protections, null, 2)
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
console.log(chalk.green(`✅ Branch protection enabled for: ${branchName}`));
|
|
286
|
-
return { success: true, protection: protections[branchName] };
|
|
287
|
-
} catch (error) {
|
|
288
|
-
return { success: false, error: error.message };
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Get branch comparison
|
|
294
|
-
* @param {string} branch1 - First branch
|
|
295
|
-
* @param {string} branch2 - Second branch (default: main)
|
|
296
|
-
* @returns {Promise<Object>} Comparison result
|
|
297
|
-
*/
|
|
298
|
-
async compareBranches(branch1, branch2 = null) {
|
|
299
|
-
const targetBranch = branch2 || this.git.defaultBranch;
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
// Get commits ahead/behind
|
|
303
|
-
const ahead = await this.git.execGit(
|
|
304
|
-
`rev-list --count ${targetBranch}..${branch1}`
|
|
305
|
-
);
|
|
306
|
-
const behind = await this.git.execGit(
|
|
307
|
-
`rev-list --count ${branch1}..${targetBranch}`
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
// Get changed files
|
|
311
|
-
const changedFiles = await this.git.getDiff({
|
|
312
|
-
from: targetBranch,
|
|
313
|
-
to: branch1,
|
|
314
|
-
nameOnly: true
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
branch: branch1,
|
|
319
|
-
compareTo: targetBranch,
|
|
320
|
-
ahead: parseInt(ahead),
|
|
321
|
-
behind: parseInt(behind),
|
|
322
|
-
changedFiles: changedFiles.split('\n').filter(Boolean),
|
|
323
|
-
canFastForward: parseInt(behind) === 0
|
|
324
|
-
};
|
|
325
|
-
} catch (error) {
|
|
326
|
-
return {
|
|
327
|
-
success: false,
|
|
328
|
-
error: error.message
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Calculate age string from timestamp
|
|
335
|
-
* @private
|
|
336
|
-
*/
|
|
337
|
-
calculateAge(timestamp) {
|
|
338
|
-
const now = Date.now();
|
|
339
|
-
const diff = now - timestamp;
|
|
340
|
-
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
341
|
-
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
342
|
-
|
|
343
|
-
if (days > 0) {
|
|
344
|
-
return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
345
|
-
} else if (hours > 0) {
|
|
346
|
-
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
347
|
-
} else {
|
|
348
|
-
return 'recently';
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Create branch strategy for different modification types
|
|
354
|
-
* @param {string} modificationType - Type of modification
|
|
355
|
-
* @returns {Object} Branch strategy
|
|
356
|
-
*/
|
|
357
|
-
getBranchStrategy(modificationType) {
|
|
358
|
-
const strategies = {
|
|
359
|
-
enhancement: {
|
|
360
|
-
prefix: 'feature/',
|
|
361
|
-
baseFrom: 'main',
|
|
362
|
-
protectByDefault: false,
|
|
363
|
-
autoMerge: false
|
|
364
|
-
},
|
|
365
|
-
bugfix: {
|
|
366
|
-
prefix: 'fix/',
|
|
367
|
-
baseFrom: 'main',
|
|
368
|
-
protectByDefault: false,
|
|
369
|
-
autoMerge: true
|
|
370
|
-
},
|
|
371
|
-
experiment: {
|
|
372
|
-
prefix: 'experiment/',
|
|
373
|
-
baseFrom: 'develop',
|
|
374
|
-
protectByDefault: false,
|
|
375
|
-
autoMerge: false
|
|
376
|
-
},
|
|
377
|
-
'self-modification': {
|
|
378
|
-
prefix: 'self-mod/',
|
|
379
|
-
baseFrom: 'main',
|
|
380
|
-
protectByDefault: true,
|
|
381
|
-
autoMerge: false,
|
|
382
|
-
requireApproval: true
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
return strategies[modificationType] || strategies.enhancement;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
1
|
+
const GitWrapper = require('./git-wrapper');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Manages git branches for meta-agent modifications
|
|
8
|
+
*/
|
|
9
|
+
class BranchManager {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.git = new GitWrapper(options);
|
|
12
|
+
this.branchPrefix = options.branchPrefix || 'meta-agent/';
|
|
13
|
+
this.maxBranches = options.maxBranches || 10;
|
|
14
|
+
this.autoCleanup = options.autoCleanup !== false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a modification branch with proper naming
|
|
19
|
+
* @param {Object} modification - Modification details
|
|
20
|
+
* @returns {Promise<Object>} Branch creation result
|
|
21
|
+
*/
|
|
22
|
+
async createModificationBranch(modification) {
|
|
23
|
+
const {
|
|
24
|
+
type,
|
|
25
|
+
target,
|
|
26
|
+
action,
|
|
27
|
+
ticketId
|
|
28
|
+
} = modification;
|
|
29
|
+
|
|
30
|
+
// Generate branch name
|
|
31
|
+
const timestamp = Date.now();
|
|
32
|
+
const sanitizedTarget = target.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
|
33
|
+
const sanitizedAction = action.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
|
34
|
+
|
|
35
|
+
let branchName = `${this.branchPrefix}${type}/${sanitizedTarget}-${sanitizedAction}-${timestamp}`;
|
|
36
|
+
|
|
37
|
+
if (ticketId) {
|
|
38
|
+
branchName = `${this.branchPrefix}${ticketId}/${type}-${sanitizedTarget}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Ensure we're on the default branch first
|
|
43
|
+
const currentBranch = await this.git.getCurrentBranch();
|
|
44
|
+
if (currentBranch !== this.git.defaultBranch) {
|
|
45
|
+
console.log(chalk.yellow(`Switching to ${this.git.defaultBranch} before creating new branch`));
|
|
46
|
+
await this.git.checkoutBranch(this.git.defaultBranch);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Create and checkout the new branch
|
|
50
|
+
await this.git.createBranch(branchName, true);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
branchName,
|
|
55
|
+
baseBranch: this.git.defaultBranch,
|
|
56
|
+
timestamp: new Date(timestamp).toISOString()
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(chalk.red(`Failed to create branch: ${error.message}`));
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: error.message
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get all modification branches
|
|
69
|
+
* @returns {Promise<Array>} List of modification branches
|
|
70
|
+
*/
|
|
71
|
+
async getModificationBranches() {
|
|
72
|
+
try {
|
|
73
|
+
const output = await this.git.execGit('branch -a');
|
|
74
|
+
const branches = output.split('\n')
|
|
75
|
+
.map(line => line.trim().replace('* ', ''))
|
|
76
|
+
.filter(branch => branch.startsWith(this.branchPrefix));
|
|
77
|
+
|
|
78
|
+
const branchDetails = [];
|
|
79
|
+
for (const branch of branches) {
|
|
80
|
+
const lastCommit = await this.git.execGit(`log -1 --format="%H|%at|%s" ${branch}`);
|
|
81
|
+
const [hash, timestamp, subject] = lastCommit.split('|');
|
|
82
|
+
|
|
83
|
+
branchDetails.push({
|
|
84
|
+
name: branch,
|
|
85
|
+
lastCommitHash: hash,
|
|
86
|
+
lastCommitDate: new Date(parseInt(timestamp) * 1000),
|
|
87
|
+
lastCommitMessage: subject,
|
|
88
|
+
age: this.calculateAge(parseInt(timestamp) * 1000)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return branchDetails.sort((a, b) => b.lastCommitDate - a.lastCommitDate);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(chalk.red(`Failed to get modification branches: ${error.message}`));
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Switch to a modification branch
|
|
101
|
+
* @param {string} branchName - Branch to switch to
|
|
102
|
+
* @returns {Promise<Object>} Switch result
|
|
103
|
+
*/
|
|
104
|
+
async switchToBranch(branchName) {
|
|
105
|
+
try {
|
|
106
|
+
// Check for uncommitted changes
|
|
107
|
+
const status = await this.git.getStatus();
|
|
108
|
+
if (!status.clean) {
|
|
109
|
+
const { action } = await inquirer.prompt([{
|
|
110
|
+
type: 'list',
|
|
111
|
+
name: 'action',
|
|
112
|
+
message: 'You have uncommitted changes. What would you like to do?',
|
|
113
|
+
choices: [
|
|
114
|
+
{ name: 'Stash changes and switch', value: 'stash' },
|
|
115
|
+
{ name: 'Commit changes first', value: 'commit' },
|
|
116
|
+
{ name: 'Cancel', value: 'cancel' }
|
|
117
|
+
]
|
|
118
|
+
}]);
|
|
119
|
+
|
|
120
|
+
if (action === 'cancel') {
|
|
121
|
+
return { success: false, reason: 'User cancelled' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (action === 'stash') {
|
|
125
|
+
await this.git.stash(`Auto-stash before switching to ${branchName}`);
|
|
126
|
+
} else if (action === 'commit') {
|
|
127
|
+
await this.git.stageFiles(['.']);
|
|
128
|
+
await this.git.commit('WIP: Auto-commit before branch switch');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await this.git.checkoutBranch(branchName);
|
|
133
|
+
return { success: true, branchName };
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return { success: false, error: error.message };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Merge modification branch back to main
|
|
141
|
+
* @param {string} branchName - Branch to merge
|
|
142
|
+
* @param {Object} options - Merge options
|
|
143
|
+
* @returns {Promise<Object>} Merge result
|
|
144
|
+
*/
|
|
145
|
+
async mergeModificationBranch(branchName, options = {}) {
|
|
146
|
+
try {
|
|
147
|
+
// Switch to target branch
|
|
148
|
+
const targetBranch = options.targetBranch || this.git.defaultBranch;
|
|
149
|
+
await this.git.checkoutBranch(targetBranch);
|
|
150
|
+
|
|
151
|
+
// Attempt merge
|
|
152
|
+
const mergeResult = await this.git.mergeBranch(branchName, {
|
|
153
|
+
message: options.message || `Merge modification branch '${branchName}'`,
|
|
154
|
+
noFastForward: true
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (mergeResult.success) {
|
|
158
|
+
// Optionally delete the branch after successful merge
|
|
159
|
+
if (options.deleteBranch) {
|
|
160
|
+
await this.deleteBranch(branchName);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
success: true,
|
|
165
|
+
message: 'Branch merged successfully',
|
|
166
|
+
targetBranch
|
|
167
|
+
};
|
|
168
|
+
} else {
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
conflicts: mergeResult.conflicts,
|
|
172
|
+
message: 'Merge conflicts detected'
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
error: error.message
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Delete a modification branch
|
|
185
|
+
* @param {string} branchName - Branch to delete
|
|
186
|
+
* @param {boolean} force - Force delete even if not merged
|
|
187
|
+
* @returns {Promise<Object>} Deletion result
|
|
188
|
+
*/
|
|
189
|
+
async deleteBranch(branchName, force = false) {
|
|
190
|
+
try {
|
|
191
|
+
const flag = force ? '-D' : '-d';
|
|
192
|
+
await this.git.execGit(`branch ${flag} ${branchName}`);
|
|
193
|
+
|
|
194
|
+
console.log(chalk.green(`✅ Deleted branch: ${branchName}`));
|
|
195
|
+
return { success: true };
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (error.message.includes('not fully merged')) {
|
|
198
|
+
console.error(chalk.red('Branch not fully merged. Use force=true to delete anyway.'));
|
|
199
|
+
}
|
|
200
|
+
return { success: false, error: error.message };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Clean up old modification branches
|
|
206
|
+
* @param {number} daysOld - Delete branches older than this many days
|
|
207
|
+
* @returns {Promise<Object>} Cleanup result
|
|
208
|
+
*/
|
|
209
|
+
async cleanupOldBranches(daysOld = 30) {
|
|
210
|
+
const branches = await this.getModificationBranches();
|
|
211
|
+
const cutoffDate = new Date();
|
|
212
|
+
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
|
|
213
|
+
|
|
214
|
+
const toDelete = branches.filter(branch =>
|
|
215
|
+
branch.lastCommitDate < cutoffDate &&
|
|
216
|
+
branch.name !== await this.git.getCurrentBranch()
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (toDelete.length === 0) {
|
|
220
|
+
console.log(chalk.yellow('No old branches to clean up'));
|
|
221
|
+
return { deleted: 0 };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(chalk.blue(`Found ${toDelete.length} branches older than ${daysOld} days:`));
|
|
225
|
+
toDelete.forEach(branch => {
|
|
226
|
+
console.log(chalk.gray(` - ${branch.name} (${branch.age})`));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const { confirm } = await inquirer.prompt([{
|
|
230
|
+
type: 'confirm',
|
|
231
|
+
name: 'confirm',
|
|
232
|
+
message: `Delete ${toDelete.length} old branches?`,
|
|
233
|
+
default: false
|
|
234
|
+
}]);
|
|
235
|
+
|
|
236
|
+
if (!confirm) {
|
|
237
|
+
return { deleted: 0, cancelled: true };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let deleted = 0;
|
|
241
|
+
for (const branch of toDelete) {
|
|
242
|
+
const result = await this.deleteBranch(branch.name, true);
|
|
243
|
+
if (result.success) deleted++;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { deleted, total: toDelete.length };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Create a branch protection strategy
|
|
251
|
+
* @param {string} branchName - Branch to protect
|
|
252
|
+
* @returns {Promise<Object>} Protection result
|
|
253
|
+
*/
|
|
254
|
+
async protectBranch(branchName) {
|
|
255
|
+
// This would integrate with GitHub/GitLab API for real protection
|
|
256
|
+
// For now, we'll track it locally
|
|
257
|
+
const protectionFile = path.join(
|
|
258
|
+
this.git.rootPath,
|
|
259
|
+
'.git',
|
|
260
|
+
'aios-branch-protection.json'
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
let protections = {};
|
|
265
|
+
try {
|
|
266
|
+
const content = await require('fs').promises.readFile(protectionFile, 'utf-8');
|
|
267
|
+
protections = JSON.parse(content);
|
|
268
|
+
} catch (_e) {
|
|
269
|
+
// File doesn't exist yet
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
protections[branchName] = {
|
|
273
|
+
protected: true,
|
|
274
|
+
requiredReviews: 1,
|
|
275
|
+
dismissStaleReviews: true,
|
|
276
|
+
requireUpToDate: true,
|
|
277
|
+
protectedAt: new Date().toISOString()
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
await require('fs').promises.writeFile(
|
|
281
|
+
protectionFile,
|
|
282
|
+
JSON.stringify(protections, null, 2)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
console.log(chalk.green(`✅ Branch protection enabled for: ${branchName}`));
|
|
286
|
+
return { success: true, protection: protections[branchName] };
|
|
287
|
+
} catch (error) {
|
|
288
|
+
return { success: false, error: error.message };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get branch comparison
|
|
294
|
+
* @param {string} branch1 - First branch
|
|
295
|
+
* @param {string} branch2 - Second branch (default: main)
|
|
296
|
+
* @returns {Promise<Object>} Comparison result
|
|
297
|
+
*/
|
|
298
|
+
async compareBranches(branch1, branch2 = null) {
|
|
299
|
+
const targetBranch = branch2 || this.git.defaultBranch;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
// Get commits ahead/behind
|
|
303
|
+
const ahead = await this.git.execGit(
|
|
304
|
+
`rev-list --count ${targetBranch}..${branch1}`
|
|
305
|
+
);
|
|
306
|
+
const behind = await this.git.execGit(
|
|
307
|
+
`rev-list --count ${branch1}..${targetBranch}`
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Get changed files
|
|
311
|
+
const changedFiles = await this.git.getDiff({
|
|
312
|
+
from: targetBranch,
|
|
313
|
+
to: branch1,
|
|
314
|
+
nameOnly: true
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
branch: branch1,
|
|
319
|
+
compareTo: targetBranch,
|
|
320
|
+
ahead: parseInt(ahead),
|
|
321
|
+
behind: parseInt(behind),
|
|
322
|
+
changedFiles: changedFiles.split('\n').filter(Boolean),
|
|
323
|
+
canFastForward: parseInt(behind) === 0
|
|
324
|
+
};
|
|
325
|
+
} catch (error) {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: error.message
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Calculate age string from timestamp
|
|
335
|
+
* @private
|
|
336
|
+
*/
|
|
337
|
+
calculateAge(timestamp) {
|
|
338
|
+
const now = Date.now();
|
|
339
|
+
const diff = now - timestamp;
|
|
340
|
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
341
|
+
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
342
|
+
|
|
343
|
+
if (days > 0) {
|
|
344
|
+
return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
345
|
+
} else if (hours > 0) {
|
|
346
|
+
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
347
|
+
} else {
|
|
348
|
+
return 'recently';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Create branch strategy for different modification types
|
|
354
|
+
* @param {string} modificationType - Type of modification
|
|
355
|
+
* @returns {Object} Branch strategy
|
|
356
|
+
*/
|
|
357
|
+
getBranchStrategy(modificationType) {
|
|
358
|
+
const strategies = {
|
|
359
|
+
enhancement: {
|
|
360
|
+
prefix: 'feature/',
|
|
361
|
+
baseFrom: 'main',
|
|
362
|
+
protectByDefault: false,
|
|
363
|
+
autoMerge: false
|
|
364
|
+
},
|
|
365
|
+
bugfix: {
|
|
366
|
+
prefix: 'fix/',
|
|
367
|
+
baseFrom: 'main',
|
|
368
|
+
protectByDefault: false,
|
|
369
|
+
autoMerge: true
|
|
370
|
+
},
|
|
371
|
+
experiment: {
|
|
372
|
+
prefix: 'experiment/',
|
|
373
|
+
baseFrom: 'develop',
|
|
374
|
+
protectByDefault: false,
|
|
375
|
+
autoMerge: false
|
|
376
|
+
},
|
|
377
|
+
'self-modification': {
|
|
378
|
+
prefix: 'self-mod/',
|
|
379
|
+
baseFrom: 'main',
|
|
380
|
+
protectByDefault: true,
|
|
381
|
+
autoMerge: false,
|
|
382
|
+
requireApproval: true
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return strategies[modificationType] || strategies.enhancement;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
390
|
module.exports = BranchManager;
|