aios-core 4.2.13 → 4.2.15
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/pro/license/degradation.js +220 -220
- package/pro/license/errors.js +450 -450
- package/pro/license/feature-gate.js +354 -354
- package/pro/license/index.js +181 -181
- package/pro/license/license-cache.js +523 -523
- package/pro/license/license-crypto.js +303 -303
- 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/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,462 +1,462 @@
|
|
|
1
|
-
const { exec } = require('child_process');
|
|
2
|
-
const { promisify } = require('util');
|
|
3
|
-
const execAsync = promisify(exec);
|
|
4
|
-
const _path = require('path');
|
|
5
|
-
const _fs = require('fs').promises;
|
|
6
|
-
const chalk = require('chalk');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Git operations wrapper for AIOS framework modifications
|
|
10
|
-
*/
|
|
11
|
-
class GitWrapper {
|
|
12
|
-
constructor(options = {}) {
|
|
13
|
-
this.rootPath = options.rootPath || process.cwd();
|
|
14
|
-
this.gitPath = options.gitPath || 'git';
|
|
15
|
-
this.defaultBranch = options.defaultBranch || 'main';
|
|
16
|
-
this.metaAgentPrefix = options.metaAgentPrefix || 'meta-agent/';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Execute a git command
|
|
21
|
-
* @private
|
|
22
|
-
*/
|
|
23
|
-
async execGit(command, options = {}) {
|
|
24
|
-
try {
|
|
25
|
-
const { stdout, stderr } = await execAsync(`${this.gitPath} ${command}`, {
|
|
26
|
-
cwd: this.rootPath,
|
|
27
|
-
...options
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (stderr && !options.ignoreStderr) {
|
|
31
|
-
console.warn(chalk.yellow(`Git warning: ${stderr}`));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return stdout.trim();
|
|
35
|
-
} catch (_error) {
|
|
36
|
-
throw new Error(`Git command failed: ${error.message}`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Check if git is initialized
|
|
42
|
-
* @returns {Promise<boolean>}
|
|
43
|
-
*/
|
|
44
|
-
async isGitInitialized() {
|
|
45
|
-
try {
|
|
46
|
-
await this.execGit('status');
|
|
47
|
-
return true;
|
|
48
|
-
} catch (_error) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Initialize git repository if not already initialized
|
|
55
|
-
* @returns {Promise<void>}
|
|
56
|
-
*/
|
|
57
|
-
async initializeRepository() {
|
|
58
|
-
const initialized = await this.isGitInitialized();
|
|
59
|
-
if (!initialized) {
|
|
60
|
-
await this.execGit('init');
|
|
61
|
-
console.log(chalk.green('✅ Git repository initialized'));
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get current branch name
|
|
67
|
-
* @returns {Promise<string>}
|
|
68
|
-
*/
|
|
69
|
-
async getCurrentBranch() {
|
|
70
|
-
return await this.execGit('rev-parse --abbrev-ref HEAD');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Create a new branch
|
|
75
|
-
* @param {string} branchName - Name of the branch to create
|
|
76
|
-
* @param {boolean} checkout - Whether to checkout the branch
|
|
77
|
-
* @returns {Promise<void>}
|
|
78
|
-
*/
|
|
79
|
-
async createBranch(branchName, checkout = true) {
|
|
80
|
-
try {
|
|
81
|
-
if (checkout) {
|
|
82
|
-
await this.execGit(`checkout -b ${branchName}`);
|
|
83
|
-
} else {
|
|
84
|
-
await this.execGit(`branch ${branchName}`);
|
|
85
|
-
}
|
|
86
|
-
console.log(chalk.green(`✅ Created branch: ${branchName}`));
|
|
87
|
-
} catch (_error) {
|
|
88
|
-
// Branch might already exist
|
|
89
|
-
if (error.message.includes('already exists')) {
|
|
90
|
-
console.log(chalk.yellow(`Branch already exists: ${branchName}`));
|
|
91
|
-
if (checkout) {
|
|
92
|
-
await this.checkoutBranch(branchName);
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Checkout an existing branch
|
|
102
|
-
* @param {string} branchName - Name of the branch to checkout
|
|
103
|
-
* @returns {Promise<void>}
|
|
104
|
-
*/
|
|
105
|
-
async checkoutBranch(branchName) {
|
|
106
|
-
await this.execGit(`checkout ${branchName}`);
|
|
107
|
-
console.log(chalk.green(`✅ Checked out branch: ${branchName}`));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Create a branch for meta-agent modifications
|
|
112
|
-
* @param {string} modificationName - Name of the modification
|
|
113
|
-
* @returns {Promise<string>} Branch name
|
|
114
|
-
*/
|
|
115
|
-
async createModificationBranch(modificationName) {
|
|
116
|
-
const timestamp = new Date().toISOString().substring(0, 10);
|
|
117
|
-
const branchName = `${this.metaAgentPrefix}${modificationName}-${timestamp}`;
|
|
118
|
-
await this.createBranch(branchName);
|
|
119
|
-
return branchName;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Stage files for commit
|
|
124
|
-
* @param {Array<string>} files - Files to stage
|
|
125
|
-
* @returns {Promise<void>}
|
|
126
|
-
*/
|
|
127
|
-
async stageFiles(files) {
|
|
128
|
-
if (!Array.isArray(files) || files.length === 0) {
|
|
129
|
-
throw new Error('No files provided to stage');
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for (const file of files) {
|
|
133
|
-
await this.execGit(`add "${file}"`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
console.log(chalk.green(`✅ Staged ${files.length} files`));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Commit changes with message
|
|
141
|
-
* @param {string} message - Commit message
|
|
142
|
-
* @param {Object} options - Commit options
|
|
143
|
-
* @returns {Promise<string>} Commit hash
|
|
144
|
-
*/
|
|
145
|
-
async commit(message, options = {}) {
|
|
146
|
-
const {
|
|
147
|
-
author = 'aios-developer <aios-developer@aios-fullstack.local>',
|
|
148
|
-
signoff = true
|
|
149
|
-
} = options;
|
|
150
|
-
|
|
151
|
-
let command = `commit -m "${message.replace(/"/g, '\\"')}"`;
|
|
152
|
-
|
|
153
|
-
if (author) {
|
|
154
|
-
command += ` --author="${author}"`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (signoff) {
|
|
158
|
-
command += ' --signoff';
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const output = await this.execGit(command);
|
|
162
|
-
const hashMatch = output.match(/\[[\w-]+ ([\w]+)\]/);
|
|
163
|
-
const commitHash = hashMatch ? hashMatch[1] : 'unknown';
|
|
164
|
-
|
|
165
|
-
console.log(chalk.green(`✅ Committed: ${commitHash}`));
|
|
166
|
-
return commitHash;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Create a commit for component modifications
|
|
171
|
-
* @param {Array<string>} files - Files to commit
|
|
172
|
-
* @param {string} message - Commit message
|
|
173
|
-
* @param {Object} metadata - Additional metadata
|
|
174
|
-
* @returns {Promise<string>} Commit hash
|
|
175
|
-
*/
|
|
176
|
-
async commitModification(files, message, metadata = {}) {
|
|
177
|
-
await this.stageFiles(files);
|
|
178
|
-
|
|
179
|
-
// Add metadata to commit message
|
|
180
|
-
let fullMessage = message;
|
|
181
|
-
if (metadata.componentType && metadata.componentName) {
|
|
182
|
-
fullMessage = `${metadata.componentType}(${metadata.componentName}): ${message}`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (metadata.breakingChange) {
|
|
186
|
-
fullMessage += '\n\nBREAKING CHANGE: ' + metadata.breakingChange;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (metadata.approvedBy) {
|
|
190
|
-
fullMessage += `\n\nApproved-by: ${metadata.approvedBy}`;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
fullMessage += '\n\nGenerated by: aios-developer meta-agent';
|
|
194
|
-
|
|
195
|
-
return await this.commit(fullMessage);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Get git status
|
|
200
|
-
* @returns {Promise<Object>} Status information
|
|
201
|
-
*/
|
|
202
|
-
async getStatus() {
|
|
203
|
-
const porcelainStatus = await this.execGit('status --porcelain');
|
|
204
|
-
const branch = await this.getCurrentBranch();
|
|
205
|
-
|
|
206
|
-
const files = {
|
|
207
|
-
modified: [],
|
|
208
|
-
added: [],
|
|
209
|
-
deleted: [],
|
|
210
|
-
untracked: []
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
if (porcelainStatus) {
|
|
214
|
-
const lines = porcelainStatus.split('\n');
|
|
215
|
-
for (const line of lines) {
|
|
216
|
-
if (!line) continue;
|
|
217
|
-
|
|
218
|
-
const status = line.substring(0, 2);
|
|
219
|
-
const filename = line.substring(3);
|
|
220
|
-
|
|
221
|
-
if (status.includes('M')) files.modified.push(filename);
|
|
222
|
-
else if (status.includes('A')) files.added.push(filename);
|
|
223
|
-
else if (status.includes('D')) files.deleted.push(filename);
|
|
224
|
-
else if (status === '??') files.untracked.push(filename);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
branch,
|
|
230
|
-
clean: porcelainStatus === '',
|
|
231
|
-
files
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Get commit history
|
|
237
|
-
* @param {number} limit - Number of commits to retrieve
|
|
238
|
-
* @returns {Promise<Array>} Commit history
|
|
239
|
-
*/
|
|
240
|
-
async getHistory(limit = 10) {
|
|
241
|
-
const format = '%H|%an|%ae|%at|%s';
|
|
242
|
-
const output = await this.execGit(`log -${limit} --format="${format}"`);
|
|
243
|
-
|
|
244
|
-
if (!output) return [];
|
|
245
|
-
|
|
246
|
-
return output.split('\n').map(line => {
|
|
247
|
-
const [hash, author, email, timestamp, subject] = line.split('|');
|
|
248
|
-
return {
|
|
249
|
-
hash,
|
|
250
|
-
author,
|
|
251
|
-
email,
|
|
252
|
-
date: new Date(parseInt(timestamp) * 1000),
|
|
253
|
-
subject
|
|
254
|
-
};
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Check for conflicts
|
|
260
|
-
* @returns {Promise<Array>} List of conflicted files
|
|
261
|
-
*/
|
|
262
|
-
async getConflicts() {
|
|
263
|
-
try {
|
|
264
|
-
const output = await this.execGit('diff --name-only --diff-filter=U');
|
|
265
|
-
return output ? output.split('\n').filter(Boolean) : [];
|
|
266
|
-
} catch (_error) {
|
|
267
|
-
return [];
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Merge a branch
|
|
273
|
-
* @param {string} branchName - Branch to merge
|
|
274
|
-
* @param {Object} options - Merge options
|
|
275
|
-
* @returns {Promise<Object>} Merge result
|
|
276
|
-
*/
|
|
277
|
-
async mergeBranch(branchName, options = {}) {
|
|
278
|
-
const {
|
|
279
|
-
strategy = 'recursive',
|
|
280
|
-
message = null,
|
|
281
|
-
noFastForward = true
|
|
282
|
-
} = options;
|
|
283
|
-
|
|
284
|
-
let command = `merge ${branchName}`;
|
|
285
|
-
|
|
286
|
-
if (strategy) {
|
|
287
|
-
command += ` --strategy=${strategy}`;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (noFastForward) {
|
|
291
|
-
command += ' --no-ff';
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (message) {
|
|
295
|
-
command += ` -m "${message.replace(/"/g, '\\"')}"`;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
try {
|
|
299
|
-
const output = await this.execGit(command);
|
|
300
|
-
return {
|
|
301
|
-
success: true,
|
|
302
|
-
message: output
|
|
303
|
-
};
|
|
304
|
-
} catch (_error) {
|
|
305
|
-
// Check for conflicts
|
|
306
|
-
const conflicts = await this.getConflicts();
|
|
307
|
-
if (conflicts.length > 0) {
|
|
308
|
-
return {
|
|
309
|
-
success: false,
|
|
310
|
-
conflicts,
|
|
311
|
-
error: 'Merge conflicts detected'
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
throw error;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Create a tag
|
|
320
|
-
* @param {string} tagName - Name of the tag
|
|
321
|
-
* @param {string} message - Tag message
|
|
322
|
-
* @returns {Promise<void>}
|
|
323
|
-
*/
|
|
324
|
-
async createTag(tagName, message) {
|
|
325
|
-
await this.execGit(`tag -a ${tagName} -m "${message.replace(/"/g, '\\"')}"`);
|
|
326
|
-
console.log(chalk.green(`✅ Created tag: ${tagName}`));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Push changes to remote
|
|
331
|
-
* @param {string} remote - Remote name
|
|
332
|
-
* @param {string} branch - Branch name
|
|
333
|
-
* @param {Object} options - Push options
|
|
334
|
-
* @returns {Promise<void>}
|
|
335
|
-
*/
|
|
336
|
-
async push(remote = 'origin', branch = null, options = {}) {
|
|
337
|
-
const currentBranch = branch || await this.getCurrentBranch();
|
|
338
|
-
let command = `push ${remote} ${currentBranch}`;
|
|
339
|
-
|
|
340
|
-
if (options.tags) {
|
|
341
|
-
command += ' --tags';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (options.force) {
|
|
345
|
-
command += ' --force';
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (options.setUpstream) {
|
|
349
|
-
command = `push -u ${remote} ${currentBranch}`;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
await this.execGit(command);
|
|
353
|
-
console.log(chalk.green(`✅ Pushed to ${remote}/${currentBranch}`));
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Get diff between commits or working tree
|
|
358
|
-
* @param {Object} options - Diff options
|
|
359
|
-
* @returns {Promise<string>} Diff output
|
|
360
|
-
*/
|
|
361
|
-
async getDiff(options = {}) {
|
|
362
|
-
const {
|
|
363
|
-
from = 'HEAD',
|
|
364
|
-
to = null,
|
|
365
|
-
files = [],
|
|
366
|
-
nameOnly = false
|
|
367
|
-
} = options;
|
|
368
|
-
|
|
369
|
-
let command = 'diff';
|
|
370
|
-
|
|
371
|
-
if (nameOnly) {
|
|
372
|
-
command += ' --name-only';
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (to) {
|
|
376
|
-
command += ` ${from} ${to}`;
|
|
377
|
-
} else {
|
|
378
|
-
command += ` ${from}`;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (files.length > 0) {
|
|
382
|
-
command += ` -- ${files.join(' ')}`;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return await this.execGit(command);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Stash changes
|
|
390
|
-
* @param {string} message - Stash message
|
|
391
|
-
* @returns {Promise<void>}
|
|
392
|
-
*/
|
|
393
|
-
async stash(message = 'Meta-agent modifications') {
|
|
394
|
-
await this.execGit(`stash push -m "${message}"`);
|
|
395
|
-
console.log(chalk.green('✅ Changes stashed'));
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Apply stash
|
|
400
|
-
* @param {string} stashRef - Stash reference
|
|
401
|
-
* @returns {Promise<void>}
|
|
402
|
-
*/
|
|
403
|
-
async stashApply(stashRef = 'stash@{0}') {
|
|
404
|
-
await this.execGit(`stash apply ${stashRef}`);
|
|
405
|
-
console.log(chalk.green('✅ Stash applied'));
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Get remote information
|
|
410
|
-
* @returns {Promise<Array>} Remote information
|
|
411
|
-
*/
|
|
412
|
-
async getRemotes() {
|
|
413
|
-
const output = await this.execGit('remote -v');
|
|
414
|
-
if (!output) return [];
|
|
415
|
-
|
|
416
|
-
const remotes = {};
|
|
417
|
-
output.split('\n').forEach(line => {
|
|
418
|
-
const [name, url, type] = line.split(/\s+/);
|
|
419
|
-
if (!remotes[name]) {
|
|
420
|
-
remotes[name] = {};
|
|
421
|
-
}
|
|
422
|
-
remotes[name][type.replace(/[()]/g, '')] = url;
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
return Object.entries(remotes).map(([name, urls]) => ({
|
|
426
|
-
name,
|
|
427
|
-
fetchUrl: urls.fetch,
|
|
428
|
-
pushUrl: urls.push
|
|
429
|
-
}));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Generate commit message for modifications
|
|
434
|
-
* @param {Object} modification - Modification details
|
|
435
|
-
* @returns {string} Generated commit message
|
|
436
|
-
*/
|
|
437
|
-
generateCommitMessage(modification) {
|
|
438
|
-
const {
|
|
439
|
-
action,
|
|
440
|
-
componentType,
|
|
441
|
-
_componentName,
|
|
442
|
-
summary,
|
|
443
|
-
details = [],
|
|
444
|
-
breakingChanges = []
|
|
445
|
-
} = modification;
|
|
446
|
-
|
|
447
|
-
let message = `${action}(${componentType}): ${summary}`;
|
|
448
|
-
|
|
449
|
-
if (details.length > 0) {
|
|
450
|
-
message += '\n\n' + details.map(d => `- ${d}`).join('\n');
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (breakingChanges.length > 0) {
|
|
454
|
-
message += '\n\nBREAKING CHANGES:\n' +
|
|
455
|
-
breakingChanges.map(bc => `- ${bc}`).join('\n');
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return message;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
1
|
+
const { exec } = require('child_process');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
const execAsync = promisify(exec);
|
|
4
|
+
const _path = require('path');
|
|
5
|
+
const _fs = require('fs').promises;
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Git operations wrapper for AIOS framework modifications
|
|
10
|
+
*/
|
|
11
|
+
class GitWrapper {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.rootPath = options.rootPath || process.cwd();
|
|
14
|
+
this.gitPath = options.gitPath || 'git';
|
|
15
|
+
this.defaultBranch = options.defaultBranch || 'main';
|
|
16
|
+
this.metaAgentPrefix = options.metaAgentPrefix || 'meta-agent/';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Execute a git command
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
async execGit(command, options = {}) {
|
|
24
|
+
try {
|
|
25
|
+
const { stdout, stderr } = await execAsync(`${this.gitPath} ${command}`, {
|
|
26
|
+
cwd: this.rootPath,
|
|
27
|
+
...options
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (stderr && !options.ignoreStderr) {
|
|
31
|
+
console.warn(chalk.yellow(`Git warning: ${stderr}`));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return stdout.trim();
|
|
35
|
+
} catch (_error) {
|
|
36
|
+
throw new Error(`Git command failed: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if git is initialized
|
|
42
|
+
* @returns {Promise<boolean>}
|
|
43
|
+
*/
|
|
44
|
+
async isGitInitialized() {
|
|
45
|
+
try {
|
|
46
|
+
await this.execGit('status');
|
|
47
|
+
return true;
|
|
48
|
+
} catch (_error) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Initialize git repository if not already initialized
|
|
55
|
+
* @returns {Promise<void>}
|
|
56
|
+
*/
|
|
57
|
+
async initializeRepository() {
|
|
58
|
+
const initialized = await this.isGitInitialized();
|
|
59
|
+
if (!initialized) {
|
|
60
|
+
await this.execGit('init');
|
|
61
|
+
console.log(chalk.green('✅ Git repository initialized'));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get current branch name
|
|
67
|
+
* @returns {Promise<string>}
|
|
68
|
+
*/
|
|
69
|
+
async getCurrentBranch() {
|
|
70
|
+
return await this.execGit('rev-parse --abbrev-ref HEAD');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create a new branch
|
|
75
|
+
* @param {string} branchName - Name of the branch to create
|
|
76
|
+
* @param {boolean} checkout - Whether to checkout the branch
|
|
77
|
+
* @returns {Promise<void>}
|
|
78
|
+
*/
|
|
79
|
+
async createBranch(branchName, checkout = true) {
|
|
80
|
+
try {
|
|
81
|
+
if (checkout) {
|
|
82
|
+
await this.execGit(`checkout -b ${branchName}`);
|
|
83
|
+
} else {
|
|
84
|
+
await this.execGit(`branch ${branchName}`);
|
|
85
|
+
}
|
|
86
|
+
console.log(chalk.green(`✅ Created branch: ${branchName}`));
|
|
87
|
+
} catch (_error) {
|
|
88
|
+
// Branch might already exist
|
|
89
|
+
if (error.message.includes('already exists')) {
|
|
90
|
+
console.log(chalk.yellow(`Branch already exists: ${branchName}`));
|
|
91
|
+
if (checkout) {
|
|
92
|
+
await this.checkoutBranch(branchName);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Checkout an existing branch
|
|
102
|
+
* @param {string} branchName - Name of the branch to checkout
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
*/
|
|
105
|
+
async checkoutBranch(branchName) {
|
|
106
|
+
await this.execGit(`checkout ${branchName}`);
|
|
107
|
+
console.log(chalk.green(`✅ Checked out branch: ${branchName}`));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create a branch for meta-agent modifications
|
|
112
|
+
* @param {string} modificationName - Name of the modification
|
|
113
|
+
* @returns {Promise<string>} Branch name
|
|
114
|
+
*/
|
|
115
|
+
async createModificationBranch(modificationName) {
|
|
116
|
+
const timestamp = new Date().toISOString().substring(0, 10);
|
|
117
|
+
const branchName = `${this.metaAgentPrefix}${modificationName}-${timestamp}`;
|
|
118
|
+
await this.createBranch(branchName);
|
|
119
|
+
return branchName;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Stage files for commit
|
|
124
|
+
* @param {Array<string>} files - Files to stage
|
|
125
|
+
* @returns {Promise<void>}
|
|
126
|
+
*/
|
|
127
|
+
async stageFiles(files) {
|
|
128
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
129
|
+
throw new Error('No files provided to stage');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
await this.execGit(`add "${file}"`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(chalk.green(`✅ Staged ${files.length} files`));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Commit changes with message
|
|
141
|
+
* @param {string} message - Commit message
|
|
142
|
+
* @param {Object} options - Commit options
|
|
143
|
+
* @returns {Promise<string>} Commit hash
|
|
144
|
+
*/
|
|
145
|
+
async commit(message, options = {}) {
|
|
146
|
+
const {
|
|
147
|
+
author = 'aios-developer <aios-developer@aios-fullstack.local>',
|
|
148
|
+
signoff = true
|
|
149
|
+
} = options;
|
|
150
|
+
|
|
151
|
+
let command = `commit -m "${message.replace(/"/g, '\\"')}"`;
|
|
152
|
+
|
|
153
|
+
if (author) {
|
|
154
|
+
command += ` --author="${author}"`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (signoff) {
|
|
158
|
+
command += ' --signoff';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const output = await this.execGit(command);
|
|
162
|
+
const hashMatch = output.match(/\[[\w-]+ ([\w]+)\]/);
|
|
163
|
+
const commitHash = hashMatch ? hashMatch[1] : 'unknown';
|
|
164
|
+
|
|
165
|
+
console.log(chalk.green(`✅ Committed: ${commitHash}`));
|
|
166
|
+
return commitHash;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Create a commit for component modifications
|
|
171
|
+
* @param {Array<string>} files - Files to commit
|
|
172
|
+
* @param {string} message - Commit message
|
|
173
|
+
* @param {Object} metadata - Additional metadata
|
|
174
|
+
* @returns {Promise<string>} Commit hash
|
|
175
|
+
*/
|
|
176
|
+
async commitModification(files, message, metadata = {}) {
|
|
177
|
+
await this.stageFiles(files);
|
|
178
|
+
|
|
179
|
+
// Add metadata to commit message
|
|
180
|
+
let fullMessage = message;
|
|
181
|
+
if (metadata.componentType && metadata.componentName) {
|
|
182
|
+
fullMessage = `${metadata.componentType}(${metadata.componentName}): ${message}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (metadata.breakingChange) {
|
|
186
|
+
fullMessage += '\n\nBREAKING CHANGE: ' + metadata.breakingChange;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (metadata.approvedBy) {
|
|
190
|
+
fullMessage += `\n\nApproved-by: ${metadata.approvedBy}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fullMessage += '\n\nGenerated by: aios-developer meta-agent';
|
|
194
|
+
|
|
195
|
+
return await this.commit(fullMessage);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get git status
|
|
200
|
+
* @returns {Promise<Object>} Status information
|
|
201
|
+
*/
|
|
202
|
+
async getStatus() {
|
|
203
|
+
const porcelainStatus = await this.execGit('status --porcelain');
|
|
204
|
+
const branch = await this.getCurrentBranch();
|
|
205
|
+
|
|
206
|
+
const files = {
|
|
207
|
+
modified: [],
|
|
208
|
+
added: [],
|
|
209
|
+
deleted: [],
|
|
210
|
+
untracked: []
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
if (porcelainStatus) {
|
|
214
|
+
const lines = porcelainStatus.split('\n');
|
|
215
|
+
for (const line of lines) {
|
|
216
|
+
if (!line) continue;
|
|
217
|
+
|
|
218
|
+
const status = line.substring(0, 2);
|
|
219
|
+
const filename = line.substring(3);
|
|
220
|
+
|
|
221
|
+
if (status.includes('M')) files.modified.push(filename);
|
|
222
|
+
else if (status.includes('A')) files.added.push(filename);
|
|
223
|
+
else if (status.includes('D')) files.deleted.push(filename);
|
|
224
|
+
else if (status === '??') files.untracked.push(filename);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
branch,
|
|
230
|
+
clean: porcelainStatus === '',
|
|
231
|
+
files
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get commit history
|
|
237
|
+
* @param {number} limit - Number of commits to retrieve
|
|
238
|
+
* @returns {Promise<Array>} Commit history
|
|
239
|
+
*/
|
|
240
|
+
async getHistory(limit = 10) {
|
|
241
|
+
const format = '%H|%an|%ae|%at|%s';
|
|
242
|
+
const output = await this.execGit(`log -${limit} --format="${format}"`);
|
|
243
|
+
|
|
244
|
+
if (!output) return [];
|
|
245
|
+
|
|
246
|
+
return output.split('\n').map(line => {
|
|
247
|
+
const [hash, author, email, timestamp, subject] = line.split('|');
|
|
248
|
+
return {
|
|
249
|
+
hash,
|
|
250
|
+
author,
|
|
251
|
+
email,
|
|
252
|
+
date: new Date(parseInt(timestamp) * 1000),
|
|
253
|
+
subject
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check for conflicts
|
|
260
|
+
* @returns {Promise<Array>} List of conflicted files
|
|
261
|
+
*/
|
|
262
|
+
async getConflicts() {
|
|
263
|
+
try {
|
|
264
|
+
const output = await this.execGit('diff --name-only --diff-filter=U');
|
|
265
|
+
return output ? output.split('\n').filter(Boolean) : [];
|
|
266
|
+
} catch (_error) {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Merge a branch
|
|
273
|
+
* @param {string} branchName - Branch to merge
|
|
274
|
+
* @param {Object} options - Merge options
|
|
275
|
+
* @returns {Promise<Object>} Merge result
|
|
276
|
+
*/
|
|
277
|
+
async mergeBranch(branchName, options = {}) {
|
|
278
|
+
const {
|
|
279
|
+
strategy = 'recursive',
|
|
280
|
+
message = null,
|
|
281
|
+
noFastForward = true
|
|
282
|
+
} = options;
|
|
283
|
+
|
|
284
|
+
let command = `merge ${branchName}`;
|
|
285
|
+
|
|
286
|
+
if (strategy) {
|
|
287
|
+
command += ` --strategy=${strategy}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (noFastForward) {
|
|
291
|
+
command += ' --no-ff';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (message) {
|
|
295
|
+
command += ` -m "${message.replace(/"/g, '\\"')}"`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const output = await this.execGit(command);
|
|
300
|
+
return {
|
|
301
|
+
success: true,
|
|
302
|
+
message: output
|
|
303
|
+
};
|
|
304
|
+
} catch (_error) {
|
|
305
|
+
// Check for conflicts
|
|
306
|
+
const conflicts = await this.getConflicts();
|
|
307
|
+
if (conflicts.length > 0) {
|
|
308
|
+
return {
|
|
309
|
+
success: false,
|
|
310
|
+
conflicts,
|
|
311
|
+
error: 'Merge conflicts detected'
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Create a tag
|
|
320
|
+
* @param {string} tagName - Name of the tag
|
|
321
|
+
* @param {string} message - Tag message
|
|
322
|
+
* @returns {Promise<void>}
|
|
323
|
+
*/
|
|
324
|
+
async createTag(tagName, message) {
|
|
325
|
+
await this.execGit(`tag -a ${tagName} -m "${message.replace(/"/g, '\\"')}"`);
|
|
326
|
+
console.log(chalk.green(`✅ Created tag: ${tagName}`));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Push changes to remote
|
|
331
|
+
* @param {string} remote - Remote name
|
|
332
|
+
* @param {string} branch - Branch name
|
|
333
|
+
* @param {Object} options - Push options
|
|
334
|
+
* @returns {Promise<void>}
|
|
335
|
+
*/
|
|
336
|
+
async push(remote = 'origin', branch = null, options = {}) {
|
|
337
|
+
const currentBranch = branch || await this.getCurrentBranch();
|
|
338
|
+
let command = `push ${remote} ${currentBranch}`;
|
|
339
|
+
|
|
340
|
+
if (options.tags) {
|
|
341
|
+
command += ' --tags';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (options.force) {
|
|
345
|
+
command += ' --force';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (options.setUpstream) {
|
|
349
|
+
command = `push -u ${remote} ${currentBranch}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
await this.execGit(command);
|
|
353
|
+
console.log(chalk.green(`✅ Pushed to ${remote}/${currentBranch}`));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get diff between commits or working tree
|
|
358
|
+
* @param {Object} options - Diff options
|
|
359
|
+
* @returns {Promise<string>} Diff output
|
|
360
|
+
*/
|
|
361
|
+
async getDiff(options = {}) {
|
|
362
|
+
const {
|
|
363
|
+
from = 'HEAD',
|
|
364
|
+
to = null,
|
|
365
|
+
files = [],
|
|
366
|
+
nameOnly = false
|
|
367
|
+
} = options;
|
|
368
|
+
|
|
369
|
+
let command = 'diff';
|
|
370
|
+
|
|
371
|
+
if (nameOnly) {
|
|
372
|
+
command += ' --name-only';
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (to) {
|
|
376
|
+
command += ` ${from} ${to}`;
|
|
377
|
+
} else {
|
|
378
|
+
command += ` ${from}`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (files.length > 0) {
|
|
382
|
+
command += ` -- ${files.join(' ')}`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return await this.execGit(command);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Stash changes
|
|
390
|
+
* @param {string} message - Stash message
|
|
391
|
+
* @returns {Promise<void>}
|
|
392
|
+
*/
|
|
393
|
+
async stash(message = 'Meta-agent modifications') {
|
|
394
|
+
await this.execGit(`stash push -m "${message}"`);
|
|
395
|
+
console.log(chalk.green('✅ Changes stashed'));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Apply stash
|
|
400
|
+
* @param {string} stashRef - Stash reference
|
|
401
|
+
* @returns {Promise<void>}
|
|
402
|
+
*/
|
|
403
|
+
async stashApply(stashRef = 'stash@{0}') {
|
|
404
|
+
await this.execGit(`stash apply ${stashRef}`);
|
|
405
|
+
console.log(chalk.green('✅ Stash applied'));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Get remote information
|
|
410
|
+
* @returns {Promise<Array>} Remote information
|
|
411
|
+
*/
|
|
412
|
+
async getRemotes() {
|
|
413
|
+
const output = await this.execGit('remote -v');
|
|
414
|
+
if (!output) return [];
|
|
415
|
+
|
|
416
|
+
const remotes = {};
|
|
417
|
+
output.split('\n').forEach(line => {
|
|
418
|
+
const [name, url, type] = line.split(/\s+/);
|
|
419
|
+
if (!remotes[name]) {
|
|
420
|
+
remotes[name] = {};
|
|
421
|
+
}
|
|
422
|
+
remotes[name][type.replace(/[()]/g, '')] = url;
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
return Object.entries(remotes).map(([name, urls]) => ({
|
|
426
|
+
name,
|
|
427
|
+
fetchUrl: urls.fetch,
|
|
428
|
+
pushUrl: urls.push
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Generate commit message for modifications
|
|
434
|
+
* @param {Object} modification - Modification details
|
|
435
|
+
* @returns {string} Generated commit message
|
|
436
|
+
*/
|
|
437
|
+
generateCommitMessage(modification) {
|
|
438
|
+
const {
|
|
439
|
+
action,
|
|
440
|
+
componentType,
|
|
441
|
+
_componentName,
|
|
442
|
+
summary,
|
|
443
|
+
details = [],
|
|
444
|
+
breakingChanges = []
|
|
445
|
+
} = modification;
|
|
446
|
+
|
|
447
|
+
let message = `${action}(${componentType}): ${summary}`;
|
|
448
|
+
|
|
449
|
+
if (details.length > 0) {
|
|
450
|
+
message += '\n\n' + details.map(d => `- ${d}`).join('\n');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (breakingChanges.length > 0) {
|
|
454
|
+
message += '\n\nBREAKING CHANGES:\n' +
|
|
455
|
+
breakingChanges.map(bc => `- ${bc}`).join('\n');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return message;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
462
|
module.exports = GitWrapper;
|