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:
Ian
2025-12-27 16:31:50 -05:00
committed by GitHub
parent 7881b2d1e9
commit 1fa7a9c769
19 changed files with 269 additions and 173 deletions
+2 -2
View File
@@ -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
+69 -32
View File
@@ -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();
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+21
View File
@@ -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:
+1 -1
View File
@@ -4,7 +4,7 @@
![Auto Claude Kanban Board](.github/assets/Auto-Claude-Kanban.png)
[![Version](https://img.shields.io/badge/version-2.7.2-beta.10-blue?style=flat-square)](https://github.com/AndyMik90/Auto-Claude/releases/latest)
[![Version](https://img.shields.io/badge/version-2.7.2--beta.10-blue?style=flat-square)](https://github.com/AndyMik90/Auto-Claude/releases/latest)
[![License](https://img.shields.io/badge/license-AGPL--3.0-green?style=flat-square)](./agpl-3.0.txt)
[![Discord](https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/KCXaPBr4Dj)
[![CI](https://img.shields.io/github/actions/workflow/status/AndyMik90/Auto-Claude/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/AndyMik90/Auto-Claude/actions)
+1 -1
View File
@@ -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();
});
});
+26 -28
View File
@@ -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).
*/
+10 -10
View File
@@ -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)) {
+2 -2
View File
@@ -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
+32 -32
View File
@@ -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