fix: Resolve pre-commit hook failures with version sync, pytest path, ruff version, and broken quality-dco workflow (#334)
* fix: Fixes issues to get clean pre-commit run 1. version-sync hook was failing due to formatting, fixed with block scalar. 2. Python Tests step was failing because it could not locate python or pytest. Fixed by referencing pytest in .venv, [as was shown here in CONTRIBUTING.md](https://github.com/AndyMik90/Auto-Claude/blob/develop/CONTRIBUTING.md?plain=1#L299) At this point pre-commit could run, then there were a few issues it found that had to be fixed: 3. "check yaml" hook failed for the file ".github/workflows/quality-dco.yml". Fixed indenting issue. 4. Various files had whitespace issues that were auto-fixed by the pre-commit commands. After this, "pre-commit run --all-files" passes for all checks. Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * docs: Update CONTRIBUTING.md with cmake dependency cmake is not present by default on macs, can be installed via homebrew Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Addressed PR comments on file consistency and install instructions. Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Ran pre-commit autoupdate, disabled broken quality-dco workflow The version of ruff in pre-commit was on a much older version than what was running as part of the lint github workflow. This caused it to make changes that were rejected by the newer version. As far as disabling quality-dco workflow; according to https://github.com/AndyMik90/Auto-Claude/actions/workflows/quality-dco.yml, it has never actually successfully parsed since it was introduced in https://github.com/AndyMik90/Auto-Claude/pull/266, and so it has not been running on any PRs to date. Given that, plus the fact that I see no mention/discussion of Developer Certificate of Origin in any github issues or the discord, I will run with the assumption this needs more explicit discussion before we turn it on and force all contributors to add these signoffs for every commit. Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Fixed bad sed command in pre-commit version-sync hook It resulted in bad version names being produced for beta versions, such as "Auto-Claude-2.7.2-beta.9-beta.9-arm64.dmg". Also addressed PR comment for needed spacing in markdown code blocks. Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Fixed other sed command for version sync to avoid incorrect names Addresses PR comment to keep this in line with existing sed command fix in the same PR. Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Keep version of ruff in sync between pre-commit and github workflow This will avoid situations where the checks done locally and in CI start to diverge and even conflict Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Enabling DCO workflow Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Fixed windows compatibility issue for running pytest Also committing some more file whitespace changes made by the working pre-commit hook. Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Removed out of date disabled banner on quality dco workflow Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> * Fixed version-sync issue with incorrect version badge image url, fixed dco workflow Updated readme with correct value as well Fixed DCO workflow as it was pointing at a nonexistent step. Improved DCO workflow failure message to warn about accidentally signing others commits. --------- Signed-off-by: Ian <251394470+ianstantiate@users.noreply.github.com> Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
This commit is contained in:
@@ -23,12 +23,12 @@ jobs:
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
# Pin ruff version to match .pre-commit-config.yaml (astral-sh/ruff-pre-commit rev)
|
||||
- name: Install ruff
|
||||
run: pip install ruff
|
||||
run: pip install ruff==0.14.10
|
||||
|
||||
- name: Run ruff check
|
||||
run: ruff check apps/backend/ --output-format=github
|
||||
|
||||
- name: Run ruff format check
|
||||
run: ruff format apps/backend/ --check --diff
|
||||
|
||||
|
||||
@@ -8,16 +8,34 @@ permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
env:
|
||||
# Comma-separated list of emails that should be ignored during DCO checks
|
||||
DCO_EXCLUDE_EMAILS: ''
|
||||
|
||||
# Job is taken from https://github.com/cncf/dcochecker/blob/main/action.yml
|
||||
# Using steps instead of reusable workflow to preserve custom failure message
|
||||
jobs:
|
||||
check:
|
||||
name: DCO Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check DCO Sign-off
|
||||
uses: dcoapp/dco-check@v1
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
require-signoff: true
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Check DCO
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DCO_CHECK_VERBOSE: '1'
|
||||
DCO_CHECK_EXCLUDE_EMAILS: ${{ env.DCO_EXCLUDE_EMAILS }}
|
||||
run: |
|
||||
pip3 install -U dco-check
|
||||
dco-check
|
||||
|
||||
- name: DCO Help on Failure
|
||||
if: failure()
|
||||
@@ -26,45 +44,64 @@ jobs:
|
||||
script: |
|
||||
const helpMsg = `## ❌ DCO Sign-off Required
|
||||
|
||||
This project requires all commits to be signed off with the Developer Certificate of Origin (DCO).
|
||||
This project requires all commits to be signed off with the Developer Certificate of Origin (DCO).
|
||||
|
||||
### How to Fix
|
||||
### How to Fix
|
||||
|
||||
**Option 1: Sign off your last commit**
|
||||
\`\`\`bash
|
||||
git commit --amend --signoff
|
||||
git push --force-with-lease
|
||||
\`\`\`
|
||||
**⚠️ Important:** If you merged \`develop\` or another branch, be careful not to accidentally sign other people's commits. Only sign YOUR commits.
|
||||
|
||||
**Option 2: Sign off all commits in this PR**
|
||||
\`\`\`bash
|
||||
git rebase HEAD~N --signoff # Replace N with number of commits
|
||||
git push --force-with-lease
|
||||
\`\`\`
|
||||
**Step 1: Identify which commits are yours**
|
||||
\`\`\`bash
|
||||
git log --oneline
|
||||
\`\`\`
|
||||
|
||||
**Option 3: Configure git to always sign off**
|
||||
\`\`\`bash
|
||||
git config --global format.signoff true
|
||||
\`\`\`
|
||||
**Step 2: Choose the appropriate method**
|
||||
|
||||
### What is DCO?
|
||||
**If only your last commit needs signing:**
|
||||
\`\`\`bash
|
||||
git commit --amend --signoff
|
||||
git push --force-with-lease
|
||||
\`\`\`
|
||||
|
||||
The [Developer Certificate of Origin](https://developercertificate.org/) is a lightweight way for contributors to certify that they wrote or have the right to submit the code they are contributing.
|
||||
**If you have multiple commits and HAVE NOT merged other branches:**
|
||||
\`\`\`bash
|
||||
git rebase HEAD~N --signoff # Replace N with number of YOUR commits
|
||||
git push --force-with-lease
|
||||
\`\`\`
|
||||
|
||||
By signing off, you agree to the DCO terms:
|
||||
- The contribution was created by you
|
||||
- You have the right to submit it under the project's license
|
||||
- You understand the contribution is public and recorded
|
||||
**If you merged another branch (use interactive rebase):**
|
||||
\`\`\`bash
|
||||
git rebase -i HEAD~N # Replace N with total number of commits
|
||||
# Mark only YOUR commits with 'edit', leave others as 'pick'
|
||||
# For each commit you marked:
|
||||
git commit --amend --signoff --no-edit
|
||||
git rebase --continue
|
||||
git push --force-with-lease
|
||||
\`\`\`
|
||||
|
||||
### Sign-off Format
|
||||
**Tip: Configure git to always sign off future commits**
|
||||
\`\`\`bash
|
||||
git config --global format.signoff true
|
||||
\`\`\`
|
||||
|
||||
Your commit message should end with:
|
||||
\`\`\`
|
||||
Signed-off-by: Your Name <your.email@example.com>
|
||||
\`\`\`
|
||||
### What is DCO?
|
||||
|
||||
This line is automatically added when you use \`git commit -s\` or \`git commit --signoff\`.
|
||||
`;
|
||||
The [Developer Certificate of Origin](https://developercertificate.org/) is a lightweight way for contributors to certify that they wrote or have the right to submit the code they are contributing.
|
||||
|
||||
By signing off, you agree to the DCO terms:
|
||||
- The contribution was created by you
|
||||
- You have the right to submit it under the project's license
|
||||
- You understand the contribution is public and recorded
|
||||
|
||||
### Sign-off Format
|
||||
|
||||
Your commit message should end with:
|
||||
\`\`\`
|
||||
Signed-off-by: Your Name <your.email@example.com>
|
||||
\`\`\`
|
||||
|
||||
This line is automatically added when you use \`git commit -s\` or \`git commit --signoff\`.
|
||||
`;
|
||||
|
||||
core.summary.addRaw(helpMsg);
|
||||
await core.summary.write();
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
with:
|
||||
stale-issue-message: |
|
||||
This issue has been inactive for 60 days. It will be closed in 14 days if there's no activity.
|
||||
|
||||
|
||||
- If this is still relevant, please comment or update the issue
|
||||
- If you're working on this, add the `in-progress` label
|
||||
close-issue-message: 'Closed due to inactivity. Feel free to reopen if still relevant.'
|
||||
|
||||
+6
-6
@@ -111,7 +111,7 @@ if git diff --cached --name-only | grep -q "^apps/backend/.*\.py$"; then
|
||||
exit 1
|
||||
fi
|
||||
cd ../..
|
||||
|
||||
|
||||
echo "Backend checks passed!"
|
||||
fi
|
||||
|
||||
@@ -123,10 +123,10 @@ fi
|
||||
if git diff --cached --name-only | grep -q "^apps/frontend/"; then
|
||||
echo "Frontend changes detected, running frontend checks..."
|
||||
cd apps/frontend
|
||||
|
||||
|
||||
# Run lint-staged (handles staged .ts/.tsx files)
|
||||
npm exec lint-staged
|
||||
|
||||
|
||||
# Run TypeScript type check
|
||||
echo "Running type check..."
|
||||
npm run typecheck
|
||||
@@ -134,7 +134,7 @@ if git diff --cached --name-only | grep -q "^apps/frontend/"; then
|
||||
echo "Type check failed. Please fix TypeScript errors before committing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Run linting
|
||||
echo "Running lint..."
|
||||
npm run lint
|
||||
@@ -142,7 +142,7 @@ if git diff --cached --name-only | grep -q "^apps/frontend/"; then
|
||||
echo "Lint failed. Run 'npm run lint:fix' to auto-fix issues."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Check for vulnerabilities (only high severity)
|
||||
echo "Checking for vulnerabilities..."
|
||||
npm audit --audit-level=high
|
||||
@@ -150,7 +150,7 @@ if git diff --cached --name-only | grep -q "^apps/frontend/"; then
|
||||
echo "High severity vulnerabilities found. Run 'npm audit fix' to resolve."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
cd ../..
|
||||
echo "Frontend checks passed!"
|
||||
fi
|
||||
|
||||
+56
-16
@@ -4,26 +4,44 @@ repos:
|
||||
hooks:
|
||||
- id: version-sync
|
||||
name: Version Sync
|
||||
entry: bash -c '
|
||||
VERSION=$(node -p "require(\"./package.json\").version");
|
||||
if [ -n "$VERSION" ]; then
|
||||
# Sync to apps/frontend/package.json
|
||||
node -e "const fs=require(\"fs\");const p=require(\"./apps/frontend/package.json\");if(p.version!==\"$VERSION\"){p.version=\"$VERSION\";fs.writeFileSync(\"./apps/frontend/package.json\",JSON.stringify(p,null,2)+\"\n\");}";
|
||||
# Sync to apps/backend/__init__.py
|
||||
sed -i.bak "s/__version__ = \"[^\"]*\"/__version__ = \"$VERSION\"/" apps/backend/__init__.py && rm -f apps/backend/__init__.py.bak;
|
||||
# Sync to README.md
|
||||
sed -i.bak "s/version-[0-9]*\.[0-9]*\.[0-9]*-blue/version-$VERSION-blue/g" README.md;
|
||||
sed -i.bak "s/Auto-Claude-[0-9]*\.[0-9]*\.[0-9]*/Auto-Claude-$VERSION/g" README.md && rm -f README.md.bak;
|
||||
git add apps/frontend/package.json apps/backend/__init__.py README.md 2>/dev/null || true;
|
||||
fi
|
||||
'
|
||||
entry: bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
if [ -n "$VERSION" ]; then
|
||||
|
||||
# Sync to apps/frontend/package.json
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const p = require('./apps/frontend/package.json');
|
||||
const v = process.argv[1];
|
||||
if (p.version !== v) {
|
||||
p.version = v;
|
||||
fs.writeFileSync('./apps/frontend/package.json', JSON.stringify(p, null, 2) + '\n');
|
||||
}
|
||||
" "$VERSION"
|
||||
|
||||
# Sync to apps/backend/__init__.py
|
||||
sed -i.bak "s/__version__ = \"[^\"]*\"/__version__ = \"$VERSION\"/" apps/backend/__init__.py && rm -f apps/backend/__init__.py.bak
|
||||
|
||||
# Sync to README.md - shields.io version badge (link requires escaped hyphens for version)
|
||||
ESCAPED_VERSION=$(echo "$VERSION" | sed 's/-/--/g')
|
||||
sed -i.bak "s/version-[0-9]*\.[0-9]*\.[0-9]*\(-\{1,2\}[a-z]*\.[0-9]*\)*-blue/version-$ESCAPED_VERSION-blue/g" README.md
|
||||
|
||||
# Sync to README.md - download filenames (no escaping needed)
|
||||
sed -i.bak "s/Auto-Claude-[0-9]*\.[0-9]*\.[0-9]*\(-[a-z]*\.[0-9]*\)*/Auto-Claude-$VERSION/g" README.md && rm -f README.md.bak
|
||||
|
||||
# Stage changes
|
||||
git add apps/frontend/package.json apps/backend/__init__.py README.md 2>/dev/null || true
|
||||
fi
|
||||
language: system
|
||||
files: ^package\.json$
|
||||
pass_filenames: false
|
||||
|
||||
# Python linting (apps/backend/)
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.3
|
||||
rev: v0.14.10
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
@@ -37,7 +55,29 @@ repos:
|
||||
hooks:
|
||||
- id: pytest
|
||||
name: Python Tests
|
||||
entry: bash -c 'cd apps/backend && PYTHONPATH=. python -m pytest ../../tests/ -v --tb=short -x -m "not slow and not integration" --ignore=../../tests/test_graphiti.py --ignore=../../tests/test_merge_file_tracker.py --ignore=../../tests/test_service_orchestrator.py --ignore=../../tests/test_worktree.py --ignore=../../tests/test_workspace.py'
|
||||
entry: bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
cd apps/backend
|
||||
if [ -f ".venv/bin/pytest" ]; then
|
||||
PYTEST_CMD=".venv/bin/pytest"
|
||||
elif [ -f ".venv/Scripts/pytest.exe" ]; then
|
||||
PYTEST_CMD=".venv/Scripts/pytest.exe"
|
||||
else
|
||||
PYTEST_CMD="python -m pytest"
|
||||
fi
|
||||
PYTHONPATH=. $PYTEST_CMD \
|
||||
../../tests/ \
|
||||
-v \
|
||||
--tb=short \
|
||||
-x \
|
||||
-m "not slow and not integration" \
|
||||
--ignore=../../tests/test_graphiti.py \
|
||||
--ignore=../../tests/test_merge_file_tracker.py \
|
||||
--ignore=../../tests/test_service_orchestrator.py \
|
||||
--ignore=../../tests/test_worktree.py \
|
||||
--ignore=../../tests/test_workspace.py
|
||||
language: system
|
||||
files: ^(apps/backend/.*\.py$|tests/.*\.py$)
|
||||
pass_filenames: false
|
||||
@@ -61,7 +101,7 @@ repos:
|
||||
|
||||
# General checks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
||||
@@ -35,6 +35,7 @@ Before contributing, ensure you have the following installed:
|
||||
- **Node.js 24+** - For the Electron frontend
|
||||
- **npm 10+** - Package manager for the frontend (comes with Node.js)
|
||||
- **uv** (recommended) or **pip** - Python package manager
|
||||
- **CMake** - Required for building native dependencies (e.g., LadybugDB)
|
||||
- **Git** - Version control
|
||||
|
||||
### Installing Python 3.12
|
||||
@@ -54,6 +55,26 @@ brew install python@3.12
|
||||
sudo apt install python3.12 python3.12-venv
|
||||
```
|
||||
|
||||
### Installing CMake
|
||||
|
||||
**Windows:**
|
||||
|
||||
```bash
|
||||
winget install Kitware.CMake
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
|
||||
```bash
|
||||
brew install cmake
|
||||
```
|
||||
|
||||
**Linux (Ubuntu/Debian):**
|
||||
|
||||
```bash
|
||||
sudo apt install cmake
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
The fastest way to get started:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||

|
||||
|
||||
[](https://github.com/AndyMik90/Auto-Claude/releases/latest)
|
||||
[](https://github.com/AndyMik90/Auto-Claude/releases/latest)
|
||||
[](./agpl-3.0.txt)
|
||||
[](https://discord.gg/KCXaPBr4Dj)
|
||||
[](https://github.com/AndyMik90/Auto-Claude/actions)
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('TaskCard', () => {
|
||||
it('renders task title', () => {
|
||||
const task = { id: '1', title: 'Test Task' };
|
||||
render(<TaskCard task={task} onClick={vi.fn()} />);
|
||||
|
||||
|
||||
expect(screen.getByText('Test Task')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+26
-28
@@ -196,7 +196,6 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -581,7 +580,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -625,7 +623,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -665,7 +662,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -1072,6 +1068,7 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cross-dirname": "^0.1.0",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1093,6 +1090,7 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
@@ -4215,7 +4213,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -4402,7 +4401,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -4413,7 +4411,6 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -4505,7 +4502,6 @@
|
||||
"integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.50.1",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
@@ -4918,7 +4914,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4979,7 +4974,6 @@
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -5152,6 +5146,7 @@
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
}
|
||||
@@ -5546,7 +5541,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -6217,7 +6211,8 @@
|
||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
@@ -6567,7 +6562,6 @@
|
||||
"integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "26.0.12",
|
||||
"builder-util": "26.0.11",
|
||||
@@ -6625,7 +6619,8 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
@@ -6701,7 +6696,6 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^22.7.7",
|
||||
@@ -6830,6 +6824,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.1",
|
||||
"debug": "^4.1.1",
|
||||
@@ -6850,6 +6845,7 @@
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -6865,6 +6861,7 @@
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
@@ -6875,6 +6872,7 @@
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
@@ -7244,7 +7242,6 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -8490,7 +8487,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4"
|
||||
},
|
||||
@@ -9281,7 +9277,6 @@
|
||||
"integrity": "sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@acemir/cssom": "^0.9.28",
|
||||
"@asamuzakjp/dom-selector": "^6.7.6",
|
||||
@@ -10213,6 +10208,7 @@
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -12403,7 +12399,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -12501,7 +12496,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -12538,6 +12532,7 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"commander": "^9.4.0"
|
||||
},
|
||||
@@ -12555,6 +12550,7 @@
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
@@ -12575,6 +12571,7 @@
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -12590,6 +12587,7 @@
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -12602,7 +12600,8 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/proc-log": {
|
||||
"version": "2.0.1",
|
||||
@@ -12706,7 +12705,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -12716,7 +12714,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -14036,8 +14033,7 @@
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -14094,6 +14090,7 @@
|
||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "~2.6.2"
|
||||
@@ -14120,6 +14117,7 @@
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
@@ -14141,6 +14139,7 @@
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -14154,6 +14153,7 @@
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
@@ -14168,6 +14168,7 @@
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
@@ -14484,7 +14485,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -14834,7 +14834,6 @@
|
||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -15876,7 +15875,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz",
|
||||
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ interface ProgressData {
|
||||
*/
|
||||
function parseNDJSON(chunk: string, bufferRef: { current: string }): ProgressData[] {
|
||||
const results: ProgressData[] = [];
|
||||
|
||||
|
||||
let stderrBuffer = bufferRef.current + chunk;
|
||||
const lines = stderrBuffer.split('\n');
|
||||
stderrBuffer = lines.pop() || '';
|
||||
|
||||
|
||||
lines.forEach((line) => {
|
||||
if (line.trim()) {
|
||||
try {
|
||||
@@ -48,7 +48,7 @@ function parseNDJSON(chunk: string, bufferRef: { current: string }): ProgressDat
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
bufferRef.current = stderrBuffer;
|
||||
return results;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ describe('NDJSON Parser', () => {
|
||||
it('should parse single JSON object', () => {
|
||||
const chunk = '{"status":"downloading","completed":100,"total":1000}\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].status).toBe('downloading');
|
||||
expect(results[0].completed).toBe(100);
|
||||
@@ -74,7 +74,7 @@ describe('NDJSON Parser', () => {
|
||||
it('should parse multiple JSON objects', () => {
|
||||
const chunk = '{"completed":100}\n{"completed":200}\n{"completed":300}\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(3);
|
||||
expect(results[0].completed).toBe(100);
|
||||
expect(results[1].completed).toBe(200);
|
||||
@@ -86,7 +86,7 @@ describe('NDJSON Parser', () => {
|
||||
it('should preserve incomplete line in buffer', () => {
|
||||
const chunk = '{"completed":100}\n{"incomplete":true';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(bufferRef.current).toBe('{"incomplete":true');
|
||||
});
|
||||
@@ -96,7 +96,7 @@ describe('NDJSON Parser', () => {
|
||||
let results = parseNDJSON(chunk, bufferRef);
|
||||
expect(results).toHaveLength(1);
|
||||
expect(bufferRef.current).toBe('{"status":"down');
|
||||
|
||||
|
||||
chunk = 'loading"}\n';
|
||||
results = parseNDJSON(chunk, bufferRef);
|
||||
expect(results).toHaveLength(1);
|
||||
@@ -109,7 +109,7 @@ describe('NDJSON Parser', () => {
|
||||
it('should skip invalid JSON and continue', () => {
|
||||
const chunk = '{"completed":100}\nINVALID\n{"completed":200}\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results[0].completed).toBe(100);
|
||||
expect(results[1].completed).toBe(200);
|
||||
@@ -118,7 +118,7 @@ describe('NDJSON Parser', () => {
|
||||
it('should skip empty lines', () => {
|
||||
const chunk = '{"completed":100}\n\n{"completed":200}\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -133,7 +133,7 @@ describe('NDJSON Parser', () => {
|
||||
});
|
||||
const chunk = ollamaProgress + '\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].status).toBe('downloading');
|
||||
expect(results[0].completed).toBe(500000000);
|
||||
@@ -148,7 +148,7 @@ describe('NDJSON Parser', () => {
|
||||
];
|
||||
const chunk = updates.map(u => JSON.stringify(u)).join('\n') + '\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(3);
|
||||
expect(results[2].completed).toBe(300000000);
|
||||
});
|
||||
@@ -156,7 +156,7 @@ describe('NDJSON Parser', () => {
|
||||
it('should handle success status', () => {
|
||||
const chunk = '{"status":"success","digest":"sha256:123"}\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].status).toBe('success');
|
||||
});
|
||||
@@ -165,7 +165,7 @@ describe('NDJSON Parser', () => {
|
||||
describe('Streaming Scenarios', () => {
|
||||
it('should accumulate data across multiple chunks', () => {
|
||||
let allResults: ProgressData[] = [];
|
||||
|
||||
|
||||
// Simulate streaming 3 progress updates
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const chunk = JSON.stringify({
|
||||
@@ -175,7 +175,7 @@ describe('NDJSON Parser', () => {
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
allResults = allResults.concat(results);
|
||||
}
|
||||
|
||||
|
||||
expect(allResults).toHaveLength(3);
|
||||
expect(allResults[2].completed).toBe(300000000);
|
||||
});
|
||||
@@ -189,7 +189,7 @@ describe('NDJSON Parser', () => {
|
||||
};
|
||||
const chunk = JSON.stringify(obj) + '\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].completed).toBe(123456789);
|
||||
});
|
||||
@@ -197,7 +197,7 @@ describe('NDJSON Parser', () => {
|
||||
it('should handle very large numbers', () => {
|
||||
const chunk = '{"completed":999999999999,"total":1000000000000}\n';
|
||||
const results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].completed).toBe(999999999999);
|
||||
expect(results[0].total).toBe(1000000000000);
|
||||
@@ -211,7 +211,7 @@ describe('NDJSON Parser', () => {
|
||||
let results = parseNDJSON(chunk, bufferRef);
|
||||
expect(results).toHaveLength(1);
|
||||
expect(bufferRef.current).toBe('{"other');
|
||||
|
||||
|
||||
// Second call completes the incomplete data
|
||||
chunk = '":200}\n';
|
||||
results = parseNDJSON(chunk, bufferRef);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Tests for version-manager.ts
|
||||
*
|
||||
*
|
||||
* Tests the compareVersions function with various version formats
|
||||
* including pre-release versions (alpha, beta, rc).
|
||||
*/
|
||||
|
||||
@@ -318,15 +318,15 @@ export class AgentProcessManager {
|
||||
});
|
||||
|
||||
const isDebug = ['true', '1', 'yes', 'on'].includes(process.env.DEBUG?.toLowerCase() ?? '');
|
||||
|
||||
|
||||
const processLog = (line: string) => {
|
||||
allOutput = (allOutput + line).slice(-10000);
|
||||
|
||||
|
||||
const hasMarker = line.includes('__EXEC_PHASE__');
|
||||
if (isDebug && hasMarker) {
|
||||
console.log(`[PhaseDebug:${taskId}] Found marker in line: "${line.substring(0, 200)}"`);
|
||||
}
|
||||
|
||||
|
||||
const phaseUpdate = this.events.parseExecutionPhase(line, currentPhase, isSpecRunner);
|
||||
|
||||
if (isDebug && hasMarker) {
|
||||
@@ -335,11 +335,11 @@ export class AgentProcessManager {
|
||||
|
||||
if (phaseUpdate) {
|
||||
const phaseChanged = phaseUpdate.phase !== currentPhase;
|
||||
|
||||
|
||||
if (isDebug) {
|
||||
console.log(`[PhaseDebug:${taskId}] Phase update: ${currentPhase} -> ${phaseUpdate.phase} (changed: ${phaseChanged})`);
|
||||
}
|
||||
|
||||
|
||||
currentPhase = phaseUpdate.phase;
|
||||
|
||||
if (phaseUpdate.currentSubtask) {
|
||||
@@ -377,15 +377,15 @@ export class AgentProcessManager {
|
||||
console.log(`[PhaseDebug:${taskId}] Raw chunk with marker (${newData.length} bytes): "${newData.substring(0, 300)}"`);
|
||||
console.log(`[PhaseDebug:${taskId}] Current buffer before append (${buffer.length} bytes): "${buffer.substring(0, 100)}"`);
|
||||
}
|
||||
|
||||
|
||||
buffer += newData;
|
||||
const lines = buffer.split('\n');
|
||||
const remaining = lines.pop() || '';
|
||||
|
||||
|
||||
if (isDebug && newData.includes('__EXEC_PHASE__')) {
|
||||
console.log(`[PhaseDebug:${taskId}] Split into ${lines.length} complete lines, remaining buffer: "${remaining.substring(0, 100)}"`);
|
||||
}
|
||||
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
this.emitter.emit('log', taskId, line + '\n');
|
||||
@@ -395,7 +395,7 @@ export class AgentProcessManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return remaining;
|
||||
};
|
||||
|
||||
@@ -416,7 +416,7 @@ export class AgentProcessManager {
|
||||
this.emitter.emit('log', taskId, stderrBuffer + '\n');
|
||||
processLog(stderrBuffer);
|
||||
}
|
||||
|
||||
|
||||
this.state.deleteProcess(taskId);
|
||||
|
||||
if (this.state.wasSpawnKilled(spawnId)) {
|
||||
|
||||
@@ -200,11 +200,11 @@ export async function checkForUpdates(): Promise<AppUpdateInfo | null> {
|
||||
|
||||
const currentVersion = autoUpdater.currentVersion.version;
|
||||
const latestVersion = result.updateInfo.version;
|
||||
|
||||
|
||||
// Use proper semver comparison to detect if update is actually newer
|
||||
// This prevents offering downgrades (e.g., v2.7.1 when on v2.7.2-beta.6)
|
||||
const isNewer = compareVersions(latestVersion, currentVersion) > 0;
|
||||
|
||||
|
||||
console.warn(`[app-updater] Version comparison: ${latestVersion} vs ${currentVersion} -> ${isNewer ? 'UPDATE' : 'NO UPDATE'}`);
|
||||
|
||||
if (!isNewer) {
|
||||
|
||||
@@ -601,11 +601,11 @@ export function registerMemoryHandlers(): void {
|
||||
if (line.trim()) {
|
||||
try {
|
||||
const progressData = JSON.parse(line);
|
||||
|
||||
|
||||
// Extract progress information
|
||||
if (progressData.completed !== undefined && progressData.total !== undefined) {
|
||||
const percentage = progressData.total > 0
|
||||
? Math.round((progressData.completed / progressData.total) * 100)
|
||||
const percentage = progressData.total > 0
|
||||
? Math.round((progressData.completed / progressData.total) * 100)
|
||||
: 0;
|
||||
|
||||
// Emit progress event to renderer
|
||||
|
||||
@@ -359,7 +359,7 @@ function verifyIsPython(pythonCmd: string): boolean {
|
||||
windowsHide: true,
|
||||
shell: false
|
||||
}).toString().trim();
|
||||
|
||||
|
||||
// Must output "Python X.Y.Z"
|
||||
return /^Python \d+\.\d+/.test(output);
|
||||
} catch {
|
||||
@@ -369,13 +369,13 @@ function verifyIsPython(pythonCmd: string): boolean {
|
||||
|
||||
/**
|
||||
* Validate a Python path for security before use in spawn().
|
||||
*
|
||||
*
|
||||
* Security checks:
|
||||
* 1. No shell metacharacters that could enable command injection
|
||||
* 2. Path must match allowlist of known Python locations OR be a safe command
|
||||
* 3. If a file path, must exist and be executable
|
||||
* 4. Must actually be Python (verified via --version)
|
||||
*
|
||||
*
|
||||
* @param pythonPath - The Python path or command to validate
|
||||
* @returns Validation result with success status and reason
|
||||
*/
|
||||
@@ -385,7 +385,7 @@ export function validatePythonPath(pythonPath: string): PythonPathValidation {
|
||||
}
|
||||
|
||||
const trimmedPath = pythonPath.trim();
|
||||
|
||||
|
||||
// Strip surrounding quotes for validation
|
||||
let cleanPath = trimmedPath;
|
||||
if ((cleanPath.startsWith('"') && cleanPath.endsWith('"')) ||
|
||||
@@ -395,9 +395,9 @@ export function validatePythonPath(pythonPath: string): PythonPathValidation {
|
||||
|
||||
// Security check 1: No shell metacharacters
|
||||
if (DANGEROUS_SHELL_CHARS.test(cleanPath)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Path contains dangerous shell metacharacters'
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Path contains dangerous shell metacharacters'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -407,56 +407,56 @@ export function validatePythonPath(pythonPath: string): PythonPathValidation {
|
||||
if (verifyIsPython(cleanPath)) {
|
||||
return { valid: true, sanitizedPath: cleanPath };
|
||||
}
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Command '${cleanPath}' does not appear to be Python`
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Command '${cleanPath}' does not appear to be Python`
|
||||
};
|
||||
}
|
||||
|
||||
// It's a file path - apply stricter validation
|
||||
const isFilePath = cleanPath.includes('/') || cleanPath.includes('\\');
|
||||
|
||||
|
||||
if (isFilePath) {
|
||||
// Normalize the path to prevent directory traversal tricks
|
||||
const normalizedPath = path.normalize(cleanPath);
|
||||
|
||||
|
||||
// Check for path traversal attempts
|
||||
if (normalizedPath.includes('..')) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Path contains directory traversal sequences'
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Path contains directory traversal sequences'
|
||||
};
|
||||
}
|
||||
|
||||
// Security check 2: Must match allowlist
|
||||
if (!matchesAllowedPattern(normalizedPath)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Path does not match allowed Python locations. Expected: system Python, Homebrew, pyenv, or virtual environment paths'
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Path does not match allowed Python locations. Expected: system Python, Homebrew, pyenv, or virtual environment paths'
|
||||
};
|
||||
}
|
||||
|
||||
// Security check 3: File must exist
|
||||
if (!existsSync(normalizedPath)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Python executable does not exist at specified path'
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Python executable does not exist at specified path'
|
||||
};
|
||||
}
|
||||
|
||||
// Security check 4: Must be executable (Unix) or .exe (Windows)
|
||||
if (process.platform !== 'win32' && !isExecutable(normalizedPath)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'File exists but is not executable'
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'File exists but is not executable'
|
||||
};
|
||||
}
|
||||
|
||||
// Security check 5: Verify it's actually Python
|
||||
if (!verifyIsPython(normalizedPath)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'File exists but does not appear to be a Python interpreter'
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'File exists but does not appear to be a Python interpreter'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -464,9 +464,9 @@ export function validatePythonPath(pythonPath: string): PythonPathValidation {
|
||||
}
|
||||
|
||||
// Unknown format - reject
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Unrecognized Python path format'
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Unrecognized Python path format'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -474,12 +474,12 @@ export function getValidatedPythonPath(providedPath: string | undefined, service
|
||||
if (!providedPath) {
|
||||
return findPythonCommand() || 'python';
|
||||
}
|
||||
|
||||
|
||||
const validation = validatePythonPath(providedPath);
|
||||
if (validation.valid) {
|
||||
return validation.sanitizedPath || providedPath;
|
||||
}
|
||||
|
||||
|
||||
console.error(`[${serviceName}] Invalid Python path rejected: ${validation.reason}`);
|
||||
return findPythonCommand() || 'python';
|
||||
}
|
||||
|
||||
@@ -94,20 +94,20 @@ export function parseVersionFromTag(tag: string): string {
|
||||
/**
|
||||
* Parse a version string into its components
|
||||
* Handles versions like "2.7.2", "2.7.2-beta.6", "2.7.2-alpha.1"
|
||||
*
|
||||
*
|
||||
* @returns { base: number[], prerelease: { type: string, num: number } | null }
|
||||
*/
|
||||
function parseVersion(version: string): {
|
||||
base: number[];
|
||||
prerelease: { type: string; num: number } | null
|
||||
function parseVersion(version: string): {
|
||||
base: number[];
|
||||
prerelease: { type: string; num: number } | null
|
||||
} {
|
||||
// Split into base version and prerelease suffix
|
||||
// e.g., "2.7.2-beta.6" -> ["2.7.2", "beta.6"]
|
||||
const [baseStr, prereleaseStr] = version.split('-');
|
||||
|
||||
|
||||
// Parse base version numbers
|
||||
const base = baseStr.split('.').map(n => parseInt(n, 10) || 0);
|
||||
|
||||
|
||||
// Parse prerelease if present
|
||||
let prerelease: { type: string; num: number } | null = null;
|
||||
if (prereleaseStr) {
|
||||
@@ -120,14 +120,14 @@ function parseVersion(version: string): {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { base, prerelease };
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare semantic versions with proper pre-release support
|
||||
* Returns: 1 if a > b, -1 if a < b, 0 if equal
|
||||
*
|
||||
*
|
||||
* Pre-release ordering:
|
||||
* - alpha < beta < rc < stable (no prerelease)
|
||||
* - 2.7.2-beta.1 < 2.7.2-beta.2 < 2.7.2 (stable)
|
||||
@@ -136,34 +136,34 @@ function parseVersion(version: string): {
|
||||
export function compareVersions(a: string, b: string): number {
|
||||
const parsedA = parseVersion(a);
|
||||
const parsedB = parseVersion(b);
|
||||
|
||||
|
||||
// Compare base versions first
|
||||
const maxLen = Math.max(parsedA.base.length, parsedB.base.length);
|
||||
for (let i = 0; i < maxLen; i++) {
|
||||
const numA = parsedA.base[i] || 0;
|
||||
const numB = parsedB.base[i] || 0;
|
||||
|
||||
|
||||
if (numA > numB) return 1;
|
||||
if (numA < numB) return -1;
|
||||
}
|
||||
|
||||
|
||||
// Base versions are equal, compare prereleases
|
||||
// No prerelease = stable = higher than any prerelease of same base
|
||||
if (!parsedA.prerelease && !parsedB.prerelease) return 0;
|
||||
if (!parsedA.prerelease && parsedB.prerelease) return 1; // a is stable, b is prerelease
|
||||
if (parsedA.prerelease && !parsedB.prerelease) return -1; // a is prerelease, b is stable
|
||||
|
||||
|
||||
// Both have prereleases - compare type then number
|
||||
const prereleaseOrder: Record<string, number> = { alpha: 0, beta: 1, rc: 2 };
|
||||
const typeA = prereleaseOrder[parsedA.prerelease!.type] ?? 1;
|
||||
const typeB = prereleaseOrder[parsedB.prerelease!.type] ?? 1;
|
||||
|
||||
|
||||
if (typeA > typeB) return 1;
|
||||
if (typeA < typeB) return -1;
|
||||
|
||||
|
||||
// Same prerelease type, compare numbers
|
||||
if (parsedA.prerelease!.num > parsedB.prerelease!.num) return 1;
|
||||
if (parsedA.prerelease!.num < parsedB.prerelease!.num) return -1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export function OllamaModelSelector({
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [ollamaAvailable, setOllamaAvailable] = useState(true);
|
||||
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress>({});
|
||||
|
||||
|
||||
// Track previous progress for speed calculation
|
||||
const downloadProgressRef = useRef<{
|
||||
[modelName: string]: {
|
||||
@@ -193,7 +193,7 @@ export function OllamaModelSelector({
|
||||
percentage: number;
|
||||
}) => {
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
// Initialize tracking for this model if needed
|
||||
if (!downloadProgressRef.current[data.modelName]) {
|
||||
downloadProgressRef.current[data.modelName] = {
|
||||
@@ -209,12 +209,12 @@ export function OllamaModelSelector({
|
||||
// Calculate speed only if we have meaningful time delta (> 100ms)
|
||||
let speedStr = '';
|
||||
let timeStr = '';
|
||||
|
||||
|
||||
if (timeDelta > 100 && bytesDelta > 0) {
|
||||
const speed = (bytesDelta / timeDelta) * 1000; // bytes per second
|
||||
const remaining = data.total - data.completed;
|
||||
const timeRemaining = speed > 0 ? Math.ceil(remaining / speed) : 0;
|
||||
|
||||
|
||||
// Format speed (MB/s or KB/s)
|
||||
if (speed > 1024 * 1024) {
|
||||
speedStr = `${(speed / (1024 * 1024)).toFixed(1)} MB/s`;
|
||||
|
||||
@@ -110,7 +110,7 @@ export const infrastructureMock = {
|
||||
}) => void) => {
|
||||
// Store callback for test verification
|
||||
(window as any).__downloadProgressCallback = callback;
|
||||
|
||||
|
||||
// Return cleanup function
|
||||
return () => {
|
||||
delete (window as any).__downloadProgressCallback;
|
||||
|
||||
@@ -52,7 +52,7 @@ export const useTaskStore = create<TaskState>((set, get) => ({
|
||||
|
||||
// Determine execution progress based on status transition
|
||||
let executionProgress = t.executionProgress;
|
||||
|
||||
|
||||
if (status === 'backlog') {
|
||||
// When status goes to backlog, reset execution progress to idle
|
||||
// This ensures the planning/coding animation stops when task is stopped
|
||||
|
||||
Reference in New Issue
Block a user