Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 49 additions & 37 deletions .aios-core/infrastructure/scripts/git-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const chalk = require('chalk');
* GitWrapper - Centralized git operations for AIOS
*
* Refactored to use execa for cross-platform compatibility
* All git operations route through execGit() method
* All git operations route through execGitArgs() with array-based arguments
* Security: No string splitting to prevent injection via spaces in arguments
*/
class GitWrapper {
constructor(rootPath) {
Expand All @@ -16,16 +17,14 @@ class GitWrapper {
}

/**
* Execute git command using execa
* Execute git command using execa with array arguments (secure)
*
* @param {string} command - Git command with arguments as string
* @param {string[]} args - Git arguments as array
* @param {object} options - Execution options
* @returns {string} Command stdout output
*/
async execGit(command, options = {}) {
async execGitArgs(args, options = {}) {
try {
// Split command string into array for execa
const args = command.split(' ');
const { stdout, stderr } = await execa(this.gitPath, args, {
cwd: this.rootPath,
...options,
Expand All @@ -41,12 +40,25 @@ class GitWrapper {
}
}

/**
* Execute git command using string (deprecated — use execGitArgs instead)
* Maintained for backwards compatibility with branch-manager, conflict-resolver, worktree-manager.
* @deprecated Use execGitArgs() with array arguments for security
* @param {string} command - Git command with arguments as string
* @param {object} options - Execution options
* @returns {string} Command stdout output
*/
async execGit(command, options = {}) {
const args = command.split(' ');
return this.execGitArgs(args, options);
}

/**
* Check if current directory is a git repository
*/
async isGitInitialized() {
try {
await this.execGit('rev-parse --git-dir');
await this.execGitArgs(['rev-parse', '--git-dir']);
return true;
} catch (error) {
return false;
Expand All @@ -58,7 +70,7 @@ class GitWrapper {
*/
async initializeRepository() {
try {
await this.execGit('init');
await this.execGitArgs(['init']);
return true;
} catch (error) {
throw new Error(`Failed to initialize git repository: ${error.message}`);
Expand All @@ -70,7 +82,7 @@ class GitWrapper {
*/
async getCurrentBranch() {
try {
return await this.execGit('rev-parse --abbrev-ref HEAD');
return await this.execGitArgs(['rev-parse', '--abbrev-ref', 'HEAD']);
} catch (error) {
throw new Error(`Failed to get current branch: ${error.message}`);
}
Expand All @@ -82,9 +94,9 @@ class GitWrapper {
async createBranch(branchName, baseBranch = null) {
try {
if (baseBranch) {
await this.execGit(`checkout -b ${branchName} ${baseBranch}`);
await this.execGitArgs(['checkout', '-b', branchName, baseBranch]);
} else {
await this.execGit(`checkout -b ${branchName}`);
await this.execGitArgs(['checkout', '-b', branchName]);
}
return true;
} catch (error) {
Expand All @@ -97,7 +109,7 @@ class GitWrapper {
*/
async checkoutBranch(branchName) {
try {
await this.execGit(`checkout ${branchName}`);
await this.execGitArgs(['checkout', branchName]);
return true;
} catch (error) {
throw new Error(`Failed to checkout branch ${branchName}: ${error.message}`);
Expand All @@ -121,10 +133,10 @@ class GitWrapper {
try {
if (Array.isArray(files)) {
for (const file of files) {
await this.execGit(`add ${file}`);
await this.execGitArgs(['add', file]);
}
} else {
await this.execGit(`add ${files}`);
await this.execGitArgs(['add', files]);
}
return true;
} catch (error) {
Expand Down Expand Up @@ -171,7 +183,7 @@ ${JSON.stringify(metadata, null, 2)}`;
*/
async getStatus() {
try {
const output = await this.execGit('status --porcelain');
const output = await this.execGitArgs(['status', '--porcelain']);

const status = {
clean: !output,
Expand Down Expand Up @@ -208,7 +220,7 @@ ${JSON.stringify(metadata, null, 2)}`;
*/
async getHistory(limit = 10) {
try {
const output = await this.execGit(`log --oneline -n ${limit}`);
const output = await this.execGitArgs(['log', '--oneline', '-n', String(limit)]);
return output.split('\n').map(line => {
const [hash, ...messageParts] = line.split(' ');
return {
Expand All @@ -226,7 +238,7 @@ ${JSON.stringify(metadata, null, 2)}`;
*/
async getConflicts() {
try {
const output = await this.execGit('diff --name-only --diff-filter=U');
const output = await this.execGitArgs(['diff', '--name-only', '--diff-filter=U']);
return output ? output.split('\n').filter(f => f.trim()) : [];
} catch (error) {
return [];
Expand Down Expand Up @@ -271,10 +283,10 @@ ${JSON.stringify(metadata, null, 2)}`;
*/
async createTag(tagName, message = null) {
const tagArgs = message
? `tag -a ${tagName} -m "${message}"`
: `tag ${tagName}`;
? ['tag', '-a', tagName, '-m', message]
: ['tag', tagName];

return await this.execGit(tagArgs);
return await this.execGitArgs(tagArgs);
}

/**
Expand Down Expand Up @@ -311,25 +323,25 @@ ${JSON.stringify(metadata, null, 2)}`;
*/
async getDiff(files = null, options = {}) {
try {
let diffCommand = 'diff';
const diffArgs = ['diff'];

if (options.staged) {
diffCommand += ' --staged';
diffArgs.push('--staged');
}

if (options.nameOnly) {
diffCommand += ' --name-only';
diffArgs.push('--name-only');
}

if (files) {
if (Array.isArray(files)) {
diffCommand += ' ' + files.join(' ');
diffArgs.push(...files);
} else {
diffCommand += ' ' + files;
diffArgs.push(files);
}
}

return await this.execGit(diffCommand);
return await this.execGitArgs(diffArgs);
} catch (error) {
return '';
}
Expand All @@ -339,30 +351,30 @@ ${JSON.stringify(metadata, null, 2)}`;
* Stash changes
*/
async stash(message = null) {
const stashCommand = message
? `stash save "${message}"`
: 'stash';
const stashArgs = message
? ['stash', 'save', message]
: ['stash'];

return await this.execGit(stashCommand);
return await this.execGitArgs(stashArgs);
}

/**
* Apply stashed changes
*/
async stashApply(stashRef = null) {
const applyCommand = stashRef
? `stash apply ${stashRef}`
: 'stash apply';
const applyArgs = stashRef
? ['stash', 'apply', stashRef]
: ['stash', 'apply'];

return await this.execGit(applyCommand);
return await this.execGitArgs(applyArgs);
}

/**
* Get remote repositories
*/
async getRemotes() {
try {
const output = await this.execGit('remote -v');
const output = await this.execGitArgs(['remote', '-v']);
const remotes = {};

if (!output) return remotes;
Expand All @@ -387,14 +399,14 @@ ${JSON.stringify(metadata, null, 2)}`;
* Add remote repository
*/
async addRemote(name, url) {
return await this.execGit(`remote add ${name} ${url}`);
return await this.execGitArgs(['remote', 'add', name, url]);
}

/**
* Remove remote repository
*/
async removeRemote(name) {
return await this.execGit(`remote remove ${name}`);
return await this.execGitArgs(['remote', 'remove', name]);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions .aios-core/install-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# - File types for categorization
#
version: 4.2.13
generated_at: "2026-02-20T22:30:07.846Z"
generated_at: "2026-02-21T01:48:52.552Z"
generator: scripts/generate-install-manifest.js
file_count: 1048
files:
Expand Down Expand Up @@ -1045,7 +1045,7 @@ files:
type: data
size: 34251
- path: data/entity-registry.yaml
hash: sha256:5b6223eb4b7fdc532ed707ddb573ea614ec2d824f6c6ac436c9965ac0d9bea9c
hash: sha256:8cb1291ecb42f9f72881b3552639d9dd5b85cf6a4c9cc7dce5e6baad8ce14118
type: data
size: 292792
- path: data/learned-patterns.yaml
Expand Down Expand Up @@ -2857,9 +2857,9 @@ files:
type: script
size: 1953
- path: infrastructure/scripts/git-wrapper.js
hash: sha256:e4354cbceb1d3fe64f0a32b3b69e3f12e55f4a5770412b7cd31f92fe2cf3278c
hash: sha256:7cb6157df998542e8ab5657939148e926a540786f8e66a342144d7cf22ed62cb
type: script
size: 9735
size: 10419
- path: infrastructure/scripts/gotchas-documenter.js
hash: sha256:8fc0003beff9149ce8f6667b154466442652cccc7a98f41166a6f1aad4a8efd3
type: script
Expand Down
Loading