feat(core): rename .auto-claude data folder to .aperant
Renames the project data directory from .auto-claude to .aperant across the entire codebase (113 files) to align with the Aperant rebrand. - All path constants, configs, worktree paths, branch prefixes updated - MCP server ID changed from 'auto-claude' to 'aperant' - Migration dialog added: detects .auto-claude folder and offers rename - .gitignore includes both .auto-claude/ and .aperant/ for compatibility - i18n strings updated (en + fr) - All 207 test files pass (4603 tests) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -80,7 +80,7 @@ Output: Raw JSON status data
|
||||
|
||||
## Status File
|
||||
|
||||
Auto-build writes status to `.auto-claude-status` in your project root:
|
||||
Auto-build writes status to `.aperant-status` in your project root:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -121,7 +121,7 @@ When active, you'll see these indicators:
|
||||
## Troubleshooting
|
||||
|
||||
### Status not showing?
|
||||
1. Check if `.auto-claude-status` exists in your project root
|
||||
1. Check if `.aperant-status` exists in your project root
|
||||
2. Verify the path to `statusline.py` is correct
|
||||
3. Try running the command manually: `python auto-claude/statusline.py --format compact`
|
||||
|
||||
|
||||
+4
-1
@@ -54,13 +54,16 @@ lerna-debug.log*
|
||||
.worktrees/
|
||||
|
||||
# ===========================
|
||||
# Auto Claude Generated
|
||||
# Aperant Generated
|
||||
# ===========================
|
||||
.aperant/
|
||||
.auto-claude/
|
||||
.planning/
|
||||
.planning-archive/
|
||||
.auto-build-security.json
|
||||
.aperant-security.json
|
||||
.auto-claude-security.json
|
||||
.aperant-status
|
||||
.auto-claude-status
|
||||
.claude_settings.json
|
||||
.update-metadata.json
|
||||
|
||||
@@ -82,10 +82,10 @@ Your context window will be automatically compacted as it approaches its limit,
|
||||
|
||||
### Resetting PR Review State
|
||||
|
||||
To fully clear all PR review data so reviews run fresh, delete/reset these three things in `.auto-claude/github/`:
|
||||
To fully clear all PR review data so reviews run fresh, delete/reset these three things in `.aperant/github/`:
|
||||
|
||||
1. `rm .auto-claude/github/pr/logs_*.json` — review log files
|
||||
2. `rm .auto-claude/github/pr/review_*.json` — review result files
|
||||
1. `rm .aperant/github/pr/logs_*.json` — review log files
|
||||
2. `rm .aperant/github/pr/review_*.json` — review result files
|
||||
3. Reset `pr/index.json` to `{"reviews": [], "last_updated": null}`
|
||||
4. Reset `bot_detection_state.json` to `{"reviewed_commits": {}}` — this is the gatekeeper; without clearing it, the bot detector skips already-seen commits
|
||||
|
||||
@@ -217,7 +217,7 @@ const readTool = tool({
|
||||
|
||||
### Spec Directory Structure
|
||||
|
||||
Each spec in `.auto-claude/specs/XXX-name/` contains: `spec.md`, `requirements.json`, `context.json`, `implementation_plan.json`, `qa_report.md`, `QA_FIX_REQUEST.md`
|
||||
Each spec in `.aperant/specs/XXX-name/` contains: `spec.md`, `requirements.json`, `context.json`, `implementation_plan.json`, `qa_report.md`, `QA_FIX_REQUEST.md`
|
||||
|
||||
### Memory System (Graphiti)
|
||||
|
||||
@@ -355,5 +355,5 @@ npm run dev # Development mode with HMR
|
||||
npm run dev:debug # Debug mode with verbose output
|
||||
npm run dev:mcp # Electron MCP server for AI debugging
|
||||
|
||||
# Project data: .auto-claude/specs/ (gitignored)
|
||||
# Project data: .aperant/specs/ (gitignored)
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ You are continuing work on an autonomous development task. This is a **FRESH con
|
||||
environment at the start of each prompt in the "YOUR ENVIRONMENT" section. Pay close attention to:
|
||||
|
||||
- **Working Directory**: This is your root - all paths are relative to here
|
||||
- **Spec Location**: Where your spec files live (usually `./auto-claude/specs/{spec-name}/`)
|
||||
- **Spec Location**: Where your spec files live (usually `./.aperant/specs/{spec-name}/`)
|
||||
- **Isolation Mode**: If present, you are in an isolated worktree (see below)
|
||||
|
||||
**RULES:**
|
||||
@@ -57,9 +57,9 @@ You may see absolute paths like `/e/projects/myapp/prod/src/file.ts` in:
|
||||
```bash
|
||||
# Verify you're still in the worktree
|
||||
pwd
|
||||
# Should show: .../.auto-claude/worktrees/tasks/{spec-name}/
|
||||
# Should show: .../.aperant/worktrees/tasks/{spec-name}/
|
||||
# Or (legacy): .../.worktrees/{spec-name}/
|
||||
# Or (PR review): .../.auto-claude/github/pr/worktrees/{pr-number}/
|
||||
# Or (PR review): .../.aperant/github/pr/worktrees/{pr-number}/
|
||||
# NOT: /path/to/main/project
|
||||
```
|
||||
|
||||
@@ -140,7 +140,7 @@ pwd && ls -la
|
||||
find . -name "implementation_plan.json" -type f 2>/dev/null | head -5
|
||||
|
||||
# 3. Set SPEC_DIR based on what you find (example - adjust path as needed)
|
||||
SPEC_DIR="./auto-claude/specs/YOUR-SPEC-NAME" # Replace with actual path from step 2
|
||||
SPEC_DIR="./.aperant/specs/YOUR-SPEC-NAME" # Replace with actual path from step 2
|
||||
|
||||
# 4. Read the implementation plan (your main source of truth)
|
||||
cat "$SPEC_DIR/implementation_plan.json"
|
||||
@@ -791,7 +791,7 @@ The system **automatically scans for secrets** before every commit. If secrets a
|
||||
api_key = os.environ.get("API_KEY")
|
||||
```
|
||||
3. **Update .env.example** - Add placeholder for the new variable
|
||||
4. **Re-stage and retry** - `git add . ':!.auto-claude' && git commit ...`
|
||||
4. **Re-stage and retry** - `git add . ':!.aperant' && git commit ...`
|
||||
|
||||
**If it's a false positive:**
|
||||
- Add the file pattern to `.secretsignore` in the project root
|
||||
@@ -803,22 +803,22 @@ The system **automatically scans for secrets** before every commit. If secrets a
|
||||
# FIRST: Make sure you're in the working directory root (check YOUR ENVIRONMENT section at top)
|
||||
pwd # Should match your working directory
|
||||
|
||||
# Add all files EXCEPT .auto-claude directory (spec files should never be committed)
|
||||
git add . ':!.auto-claude'
|
||||
# Add all files EXCEPT .aperant directory (spec files should never be committed)
|
||||
git add . ':!.aperant'
|
||||
|
||||
# If git add fails with "pathspec did not match", you have a path problem:
|
||||
# 1. Run pwd to see where you are
|
||||
# 2. Run git status to see what git sees
|
||||
# 3. Adjust your paths accordingly
|
||||
|
||||
git commit -m "auto-claude: Complete [subtask-id] - [subtask description]
|
||||
git commit -m "aperant: Complete [subtask-id] - [subtask description]
|
||||
|
||||
- Files modified: [list]
|
||||
- Verification: [type] - passed
|
||||
- Phase progress: [X]/[Y] subtasks complete"
|
||||
```
|
||||
|
||||
**CRITICAL**: The `:!.auto-claude` pathspec exclusion ensures spec files are NEVER committed.
|
||||
**CRITICAL**: The `:!.aperant` pathspec exclusion ensures spec files are NEVER committed.
|
||||
These are internal tracking files that must stay local.
|
||||
|
||||
### DO NOT Push to Remote
|
||||
@@ -851,7 +851,7 @@ Next phase (if applicable): [phase-name]
|
||||
=== END SESSION N ===
|
||||
```
|
||||
|
||||
**Note:** The `build-progress.txt` file is in `.auto-claude/specs/` which is gitignored.
|
||||
**Note:** The `build-progress.txt` file is in `.aperant/specs/` which is gitignored.
|
||||
Do NOT try to commit it - the framework tracks progress automatically.
|
||||
|
||||
---
|
||||
@@ -881,7 +881,7 @@ All subtasks completed!
|
||||
Workflow type: [type]
|
||||
Total phases: [N]
|
||||
Total subtasks: [N]
|
||||
Branch: auto-claude/[feature-name]
|
||||
Branch: aperant/[feature-name]
|
||||
|
||||
Ready for human review and merge.
|
||||
```
|
||||
|
||||
@@ -741,7 +741,7 @@ The following files are gitignored and should NOT be committed:
|
||||
- `init.sh` - tracked locally only
|
||||
- `build-progress.txt` - tracked locally only
|
||||
|
||||
These files live in `.auto-claude/specs/` which is gitignored. The orchestrator handles syncing them between worktrees and the main project.
|
||||
These files live in `.aperant/specs/` which is gitignored. The orchestrator handles syncing them between worktrees and the main project.
|
||||
|
||||
**Only code changes should be committed** - spec metadata stays local.
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ You are the **QA Fix Agent** in an autonomous development process. The QA Review
|
||||
### NEVER edit qa_report.md
|
||||
The `qa_report.md` file belongs to the QA Reviewer. You must NEVER modify it. The reviewer writes the verdict; you implement fixes. If you change the report status (e.g., to "FIXES_APPLIED"), the orchestrator won't recognize it as a valid verdict and your fixes will be wasted.
|
||||
|
||||
### Fix in the PROJECT SOURCE, not in .auto-claude/specs/
|
||||
All your code changes, documentation additions, and new files must go into the **project source tree** (the actual codebase). Never create deliverable files inside `.auto-claude/specs/` — that directory contains gitignored metadata (spec, plan, QA report). The QA reviewer evaluates the project source, not spec artifacts.
|
||||
### Fix in the PROJECT SOURCE, not in .aperant/specs/
|
||||
All your code changes, documentation additions, and new files must go into the **project source tree** (the actual codebase). Never create deliverable files inside `.aperant/specs/` — that directory contains gitignored metadata (spec, plan, QA report). The QA reviewer evaluates the project source, not spec artifacts.
|
||||
|
||||
**Example:** If QA says "missing route inventory document", create it in the project root (e.g., `docs/route-policy.md` or `ROUTE_POLICY.md`), NOT in `.auto-claude/specs/route_access_policy.md`.
|
||||
**Example:** If QA says "missing route inventory document", create it in the project root (e.g., `docs/route-policy.md` or `ROUTE_POLICY.md`), NOT in `.aperant/specs/route_access_policy.md`.
|
||||
|
||||
### Fix CODE issues with CODE, not documentation
|
||||
If QA reports a missing test, write the test. If QA reports a code bug, fix the code. Don't write a markdown document explaining why the code is fine — write the code that makes it fine.
|
||||
@@ -204,7 +204,7 @@ Escaping the worktree causes:
|
||||
pwd
|
||||
|
||||
# 2. Verify the target is within your worktree
|
||||
# If pwd shows: /path/to/.auto-claude/worktrees/tasks/spec-name/
|
||||
# If pwd shows: /path/to/.aperant/worktrees/tasks/spec-name/
|
||||
# Then: cd ./apps/desktop ✅ SAFE
|
||||
# But: cd /path/to/parent/project ❌ FORBIDDEN - ESCAPES ISOLATION
|
||||
|
||||
@@ -338,8 +338,8 @@ ls -la [path-to-files] # Make sure the path is correct from your current locati
|
||||
# FIRST: Make sure you're in the working directory root
|
||||
pwd # Should match your working directory
|
||||
|
||||
# Add all files EXCEPT .auto-claude directory (spec files should never be committed)
|
||||
git add . ':!.auto-claude'
|
||||
# Add all files EXCEPT .aperant directory (spec files should never be committed)
|
||||
git add . ':!.aperant'
|
||||
|
||||
# If git add fails with "pathspec did not match", you have a path problem:
|
||||
# 1. Run pwd to see where you are
|
||||
@@ -360,7 +360,7 @@ Verified:
|
||||
QA Fix Session: [N]"
|
||||
```
|
||||
|
||||
**CRITICAL**: The `:!.auto-claude` pathspec exclusion ensures spec files are NEVER committed.
|
||||
**CRITICAL**: The `:!.aperant` pathspec exclusion ensures spec files are NEVER committed.
|
||||
|
||||
**NOTE**: Do NOT push to remote. All work stays local until user reviews and approves.
|
||||
|
||||
@@ -490,7 +490,7 @@ npx prisma migrate dev --name [name]
|
||||
|
||||
### Write Deliverables to the Project, Not Spec Artifacts
|
||||
- All new files (docs, tests, code) go in the project source tree
|
||||
- NEVER create deliverable files in `.auto-claude/specs/` — that directory is gitignored metadata
|
||||
- NEVER create deliverable files in `.aperant/specs/` — that directory is gitignored metadata
|
||||
|
||||
### Git Configuration - NEVER MODIFY
|
||||
**CRITICAL**: You MUST NOT modify git user configuration. Never run:
|
||||
|
||||
@@ -480,7 +480,7 @@ cat > qa_report.md << 'EOF'
|
||||
[QA Report content]
|
||||
EOF
|
||||
|
||||
# Note: qa_report.md and implementation_plan.json are in .auto-claude/specs/ (gitignored)
|
||||
# Note: qa_report.md and implementation_plan.json are in .aperant/specs/ (gitignored)
|
||||
# Do NOT commit them - the framework tracks QA status automatically
|
||||
# Only commit actual code changes to the project
|
||||
```
|
||||
@@ -517,7 +517,7 @@ Once fixes are complete:
|
||||
|
||||
EOF
|
||||
|
||||
# Note: QA_FIX_REQUEST.md and implementation_plan.json are in .auto-claude/specs/ (gitignored)
|
||||
# Note: QA_FIX_REQUEST.md and implementation_plan.json are in .aperant/specs/ (gitignored)
|
||||
# Do NOT commit them - the framework tracks QA status automatically
|
||||
# Only commit actual code fixes to the project
|
||||
```
|
||||
|
||||
@@ -127,7 +127,7 @@ function setupTestDirs(): void {
|
||||
TEST_PROJECT_PATH = path.join(TEST_DIR, 'test-project');
|
||||
mkdirSync(TEST_PROJECT_PATH, { recursive: true });
|
||||
// Create a minimal project structure
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, '.auto-claude'), { recursive: true });
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, '.aperant'), { recursive: true });
|
||||
}
|
||||
|
||||
// Cleanup test directories
|
||||
|
||||
@@ -23,7 +23,7 @@ let PLAN_PATH: string;
|
||||
// Setup test directories
|
||||
function setupTestDirs(): void {
|
||||
TEST_DIR = mkdtempSync(path.join(tmpdir(), 'rate-limit-recovery-test-'));
|
||||
TEST_SPEC_DIR = path.join(TEST_DIR, '.auto-claude/specs/001-test-feature');
|
||||
TEST_SPEC_DIR = path.join(TEST_DIR, '.aperant/specs/001-test-feature');
|
||||
PLAN_PATH = path.join(TEST_SPEC_DIR, 'implementation_plan.json');
|
||||
mkdirSync(TEST_SPEC_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ function setupTestDirs(): void {
|
||||
// Create secure temp directory with random suffix
|
||||
TEST_DIR = mkdtempSync(path.join(tmpdir(), 'task-lifecycle-test-'));
|
||||
TEST_PROJECT_PATH = path.join(TEST_DIR, 'test-project');
|
||||
TEST_SPEC_DIR = path.join(TEST_PROJECT_PATH, '.auto-claude/specs/001-test-feature');
|
||||
TEST_SPEC_DIR = path.join(TEST_PROJECT_PATH, '.aperant/specs/001-test-feature');
|
||||
mkdirSync(TEST_SPEC_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ describe('FileWatcher concurrency', () => {
|
||||
// -------------------------------------------------------------------------
|
||||
describe('deduplication: second watch() with same specDir is a no-op', () => {
|
||||
it('should only create one FSWatcher when watch() is called twice with the same specDir while the first is still in-flight', async () => {
|
||||
const specDir = '/project/.auto-claude/specs/001-task';
|
||||
const specDir = '/project/.aperant/specs/001-task';
|
||||
const taskId = 'task-1';
|
||||
|
||||
// To create a real async gap we need an existing watcher whose close() is slow.
|
||||
@@ -111,8 +111,8 @@ describe('FileWatcher concurrency', () => {
|
||||
describe('supersession: watch() with different specDir replaces the in-flight call', () => {
|
||||
it('should let the second call win when the first is awaiting close()', async () => {
|
||||
const taskId = 'task-2';
|
||||
const specDir1 = path.join('/project', '.auto-claude', 'specs', '001-first');
|
||||
const specDir2 = path.join('/project', '.auto-claude', 'specs', '002-second');
|
||||
const specDir1 = path.join('/project', '.aperant', 'specs', '001-first');
|
||||
const specDir2 = path.join('/project', '.aperant', 'specs', '002-second');
|
||||
|
||||
// First call installs an existing watcher (simulate: the watcher for
|
||||
// specDir1 is already set up so the second watch() needs to close it).
|
||||
@@ -148,8 +148,8 @@ describe('FileWatcher concurrency', () => {
|
||||
|
||||
it('first watch() bails when pendingWatches changes to a different specDir', async () => {
|
||||
const taskId = 'task-super';
|
||||
const specDir1 = path.join('/project', '.auto-claude', 'specs', 'super-first');
|
||||
const specDir2 = path.join('/project', '.auto-claude', 'specs', 'super-second');
|
||||
const specDir1 = path.join('/project', '.aperant', 'specs', 'super-first');
|
||||
const specDir2 = path.join('/project', '.aperant', 'specs', 'super-second');
|
||||
|
||||
// Make the first watcher's close() slow so we can interleave.
|
||||
let resolveFirstClose!: () => void;
|
||||
@@ -187,7 +187,7 @@ describe('FileWatcher concurrency', () => {
|
||||
describe('cancellation: unwatch() during in-flight watch() prevents watcher creation', () => {
|
||||
it('should not create a watcher when unwatch() is called before the async gap resolves', async () => {
|
||||
const taskId = 'task-3';
|
||||
const specDir = '/project/.auto-claude/specs/003-cancel';
|
||||
const specDir = '/project/.aperant/specs/003-cancel';
|
||||
|
||||
// There's no pre-existing watcher, so watch() won't call close(). But it
|
||||
// does go async (chokidar.watch is sync but we can test the cancellation
|
||||
@@ -207,7 +207,7 @@ describe('FileWatcher concurrency', () => {
|
||||
);
|
||||
|
||||
// Start a second watch() — it will await the slow close().
|
||||
const specDir2 = '/project/.auto-claude/specs/003-cancel-v2';
|
||||
const specDir2 = '/project/.aperant/specs/003-cancel-v2';
|
||||
const watchPromise = fw.watch(taskId, specDir2);
|
||||
|
||||
// While watch() is in-flight, call unwatch().
|
||||
@@ -231,8 +231,8 @@ describe('FileWatcher concurrency', () => {
|
||||
it('should cancel pending watch() calls and clear pendingWatches', async () => {
|
||||
const taskId1 = 'task-4a';
|
||||
const taskId2 = 'task-4b';
|
||||
const specDir1 = '/project/.auto-claude/specs/004a';
|
||||
const specDir2 = '/project/.auto-claude/specs/004b';
|
||||
const specDir1 = '/project/.aperant/specs/004a';
|
||||
const specDir2 = '/project/.aperant/specs/004b';
|
||||
|
||||
// Set up slow-close scenario for taskId1 (so watch() is in-flight).
|
||||
await fw.watch(taskId1, specDir1);
|
||||
@@ -243,7 +243,7 @@ describe('FileWatcher concurrency', () => {
|
||||
);
|
||||
|
||||
// Start a new watch for taskId1 with a different specDir — this is now in-flight.
|
||||
const newSpecDir1 = '/project/.auto-claude/specs/004a-v2';
|
||||
const newSpecDir1 = '/project/.aperant/specs/004a-v2';
|
||||
const watchPromise1 = fw.watch(taskId1, newSpecDir1);
|
||||
|
||||
// Start a fresh watch for taskId2.
|
||||
@@ -262,7 +262,7 @@ describe('FileWatcher concurrency', () => {
|
||||
|
||||
// pendingWatches should be cleared (we verify indirectly: a fresh
|
||||
// watch() call for taskId1 must succeed without treating it as a duplicate).
|
||||
const specDirFresh = path.join('/project', '.auto-claude', 'specs', '004a-fresh');
|
||||
const specDirFresh = path.join('/project', '.aperant', 'specs', '004a-fresh');
|
||||
await fw.watch(taskId1, specDirFresh);
|
||||
expect(fw.isWatching(taskId1)).toBe(true);
|
||||
expect(fw.getWatchedSpecDir(taskId1)).toBe(specDirFresh);
|
||||
@@ -275,7 +275,7 @@ describe('FileWatcher concurrency', () => {
|
||||
describe('getWatchedSpecDir()', () => {
|
||||
it('returns the specDir that was passed to watch()', async () => {
|
||||
const taskId = 'task-5';
|
||||
const specDir = path.join('/project', '.auto-claude', 'specs', '005-specdir');
|
||||
const specDir = path.join('/project', '.aperant', 'specs', '005-specdir');
|
||||
|
||||
await fw.watch(taskId, specDir);
|
||||
|
||||
@@ -288,8 +288,8 @@ describe('FileWatcher concurrency', () => {
|
||||
|
||||
it('returns updated specDir after re-watch with different specDir', async () => {
|
||||
const taskId = 'task-5b';
|
||||
const specDir1 = path.join('/project', '.auto-claude', 'specs', '005b-first');
|
||||
const specDir2 = path.join('/project', '.auto-claude', 'specs', '005b-second');
|
||||
const specDir1 = path.join('/project', '.aperant', 'specs', '005b-first');
|
||||
const specDir2 = path.join('/project', '.aperant', 'specs', '005b-second');
|
||||
|
||||
await fw.watch(taskId, specDir1);
|
||||
expect(fw.getWatchedSpecDir(taskId)).toBe(specDir1);
|
||||
|
||||
@@ -143,7 +143,7 @@ vi.mock("electron", () => {
|
||||
// Setup test project structure
|
||||
function setupTestProject(): void {
|
||||
mkdirSync(TEST_PROJECT_PATH, { recursive: true });
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, "auto-claude", "specs"), { recursive: true });
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, "aperant", "specs"), { recursive: true });
|
||||
}
|
||||
|
||||
// Cleanup test directories
|
||||
@@ -419,15 +419,15 @@ describe("IPC Handlers", { timeout: 30000 }, () => {
|
||||
() => mockMainWindow as never
|
||||
);
|
||||
|
||||
// Create .auto-claude directory first (before adding project so it gets detected)
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, ".auto-claude", "specs"), { recursive: true });
|
||||
// Create .aperant directory first (before adding project so it gets detected)
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, ".aperant", "specs"), { recursive: true });
|
||||
|
||||
// Add a project - it will detect .auto-claude
|
||||
// Add a project - it will detect .aperant
|
||||
const addResult = await ipcMain.invokeHandler("project:add", {}, TEST_PROJECT_PATH);
|
||||
const projectId = (addResult as { data: { id: string } }).data.id;
|
||||
|
||||
// Create a spec directory with implementation plan in .auto-claude/specs
|
||||
const specDir = path.join(TEST_PROJECT_PATH, ".auto-claude", "specs", "001-test-feature");
|
||||
// Create a spec directory with implementation plan in .aperant/specs
|
||||
const specDir = path.join(TEST_PROJECT_PATH, ".aperant", "specs", "001-test-feature");
|
||||
mkdirSync(specDir, { recursive: true });
|
||||
writeFileSync(
|
||||
path.join(specDir, "implementation_plan.json"),
|
||||
@@ -489,8 +489,8 @@ describe("IPC Handlers", { timeout: 30000 }, () => {
|
||||
() => mockMainWindow as never
|
||||
);
|
||||
|
||||
// Create .auto-claude directory first (before adding project so it gets detected)
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, ".auto-claude", "specs"), { recursive: true });
|
||||
// Create .aperant directory first (before adding project so it gets detected)
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, ".aperant", "specs"), { recursive: true });
|
||||
|
||||
// Add a project first
|
||||
const addResult = await ipcMain.invokeHandler("project:add", {}, TEST_PROJECT_PATH);
|
||||
@@ -630,7 +630,7 @@ describe("IPC Handlers", { timeout: 30000 }, () => {
|
||||
await ipcMain.invokeHandler("project:add", {}, TEST_PROJECT_PATH);
|
||||
|
||||
// Create a spec/task directory with implementation_plan.json
|
||||
const specDir = path.join(TEST_PROJECT_PATH, ".auto-claude", "specs", "task-1");
|
||||
const specDir = path.join(TEST_PROJECT_PATH, ".aperant", "specs", "task-1");
|
||||
mkdirSync(specDir, { recursive: true });
|
||||
writeFileSync(
|
||||
path.join(specDir, "implementation_plan.json"),
|
||||
|
||||
@@ -88,16 +88,16 @@ describe('ProjectStore', () => {
|
||||
expect(project1.id).toBe(project2.id);
|
||||
});
|
||||
|
||||
it('should detect auto-claude directory if present', async () => {
|
||||
// Create .auto-claude directory (the data directory, not source code)
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, '.auto-claude'), { recursive: true });
|
||||
it('should detect aperant directory if present', async () => {
|
||||
// Create .aperant directory (the data directory, not source code)
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, '.aperant'), { recursive: true });
|
||||
|
||||
const { ProjectStore } = await import('../project-store');
|
||||
const store = new ProjectStore();
|
||||
|
||||
const project = store.addProject(TEST_PROJECT_PATH);
|
||||
|
||||
expect(project.autoBuildPath).toBe('.auto-claude');
|
||||
expect(project.autoBuildPath).toBe('.aperant');
|
||||
});
|
||||
|
||||
it('should set empty autoBuildPath if not present', async () => {
|
||||
@@ -284,8 +284,8 @@ describe('ProjectStore', () => {
|
||||
});
|
||||
|
||||
it('should read tasks from filesystem correctly', async () => {
|
||||
// Create spec directory structure in .auto-claude (the data directory)
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '001-test-feature');
|
||||
// Create spec directory structure in .aperant (the data directory)
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '001-test-feature');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -332,7 +332,7 @@ describe('ProjectStore', () => {
|
||||
});
|
||||
|
||||
it('should determine status as backlog when no subtasks completed', async () => {
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '002-pending');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '002-pending');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -372,7 +372,7 @@ describe('ProjectStore', () => {
|
||||
});
|
||||
|
||||
it('should determine status as ai_review when all subtasks completed', async () => {
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '003-complete');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '003-complete');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -412,7 +412,7 @@ describe('ProjectStore', () => {
|
||||
});
|
||||
|
||||
it('should determine status as human_review when plan status is human_review', async () => {
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '004-rejected');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '004-rejected');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -452,7 +452,7 @@ describe('ProjectStore', () => {
|
||||
});
|
||||
|
||||
it('should determine reviewReason from plan when status is human_review', async () => {
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '005-approved');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '005-approved');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -494,7 +494,7 @@ describe('ProjectStore', () => {
|
||||
|
||||
it('should determine status as done when plan status is explicitly done', async () => {
|
||||
// User explicitly marking task as done via drag-and-drop sets status to done
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '006-done');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '006-done');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -533,7 +533,7 @@ describe('ProjectStore', () => {
|
||||
});
|
||||
|
||||
it('should prefer original task description from requirements.json over plan description', async () => {
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '007-description-priority');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '007-description-priority');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const aiDescription = 'AI-generated implementation plan description';
|
||||
@@ -633,7 +633,7 @@ describe('ProjectStore', () => {
|
||||
describe('archiveTasks - multi-location handling', () => {
|
||||
it('should archive task from main specs directory only', async () => {
|
||||
// Create spec directory in main location only
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '001-test-task');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '001-test-task');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -666,18 +666,18 @@ describe('ProjectStore', () => {
|
||||
|
||||
it('should archive task from BOTH main and worktree locations', async () => {
|
||||
// Create spec directory in main location
|
||||
const mainSpecsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '002-multi-location');
|
||||
const mainSpecsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '002-multi-location');
|
||||
mkdirSync(mainSpecsDir, { recursive: true });
|
||||
|
||||
// Create spec directory in worktree location
|
||||
// Worktree path: .auto-claude/worktrees/tasks/<worktreeName>/.auto-claude/specs/<taskId>
|
||||
// Worktree path: .aperant/worktrees/tasks/<worktreeName>/.aperant/specs/<taskId>
|
||||
const worktreeDir = path.join(
|
||||
TEST_PROJECT_PATH,
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'worktrees',
|
||||
'tasks',
|
||||
'my-worktree',
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'specs',
|
||||
'002-multi-location'
|
||||
);
|
||||
@@ -724,11 +724,11 @@ describe('ProjectStore', () => {
|
||||
// Create spec directory ONLY in worktree location (not in main)
|
||||
const worktreeDir = path.join(
|
||||
TEST_PROJECT_PATH,
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'worktrees',
|
||||
'tasks',
|
||||
'only-worktree',
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'specs',
|
||||
'003-worktree-only'
|
||||
);
|
||||
@@ -765,8 +765,8 @@ describe('ProjectStore', () => {
|
||||
const { ProjectStore } = await import('../project-store');
|
||||
const store = new ProjectStore();
|
||||
|
||||
// Create .auto-claude directory so project is recognized
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, '.auto-claude'), { recursive: true });
|
||||
// Create .aperant directory so project is recognized
|
||||
mkdirSync(path.join(TEST_PROJECT_PATH, '.aperant'), { recursive: true });
|
||||
|
||||
const project = store.addProject(TEST_PROJECT_PATH);
|
||||
// Task doesn't exist anywhere
|
||||
@@ -778,7 +778,7 @@ describe('ProjectStore', () => {
|
||||
|
||||
it('should reject path traversal attempts in taskId', async () => {
|
||||
// Create a valid spec dir
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', 'valid-task');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', 'valid-task');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = { feature: 'Test', phases: [] };
|
||||
@@ -820,16 +820,16 @@ describe('ProjectStore', () => {
|
||||
describe('unarchiveTasks - multi-location handling', () => {
|
||||
it('should unarchive task from BOTH main and worktree locations', async () => {
|
||||
// Create archived task in both locations
|
||||
const mainSpecsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '004-unarchive-test');
|
||||
const mainSpecsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '004-unarchive-test');
|
||||
mkdirSync(mainSpecsDir, { recursive: true });
|
||||
|
||||
const worktreeDir = path.join(
|
||||
TEST_PROJECT_PATH,
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'worktrees',
|
||||
'tasks',
|
||||
'unarchive-worktree',
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'specs',
|
||||
'004-unarchive-test'
|
||||
);
|
||||
@@ -874,7 +874,7 @@ describe('ProjectStore', () => {
|
||||
|
||||
describe('cache invalidation', () => {
|
||||
it('should invalidate cache after archiveTasks', async () => {
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '005-cache-test');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '005-cache-test');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -915,7 +915,7 @@ describe('ProjectStore', () => {
|
||||
});
|
||||
|
||||
it('should return fresh data after invalidateTasksCache is called', async () => {
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '006-invalidate-test');
|
||||
const specsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '006-invalidate-test');
|
||||
mkdirSync(specsDir, { recursive: true });
|
||||
|
||||
const plan = {
|
||||
@@ -959,16 +959,16 @@ describe('ProjectStore', () => {
|
||||
describe('getTasks - worktree deduplication', () => {
|
||||
it('should not duplicate tasks that exist in both main and worktree', async () => {
|
||||
// Create same task in both main and worktree
|
||||
const mainSpecsDir = path.join(TEST_PROJECT_PATH, '.auto-claude', 'specs', '007-dedupe-test');
|
||||
const mainSpecsDir = path.join(TEST_PROJECT_PATH, '.aperant', 'specs', '007-dedupe-test');
|
||||
mkdirSync(mainSpecsDir, { recursive: true });
|
||||
|
||||
const worktreeDir = path.join(
|
||||
TEST_PROJECT_PATH,
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'worktrees',
|
||||
'tasks',
|
||||
'dedupe-worktree',
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'specs',
|
||||
'007-dedupe-test'
|
||||
);
|
||||
|
||||
@@ -375,7 +375,7 @@ export class AgentManager extends EventEmitter {
|
||||
const resolved = await this.resolveAuthFromProviderQueue(specModelId, preferredProvider);
|
||||
|
||||
// Build the serializable session config for the worker
|
||||
const resolvedSpecDir = specDir ?? path.join(projectPath, '.auto-claude', 'specs', taskId);
|
||||
const resolvedSpecDir = specDir ?? path.join(projectPath, '.aperant', 'specs', taskId);
|
||||
const sessionConfig: SerializableSessionConfig = {
|
||||
agentType: 'spec_orchestrator' as const,
|
||||
systemPrompt,
|
||||
|
||||
@@ -493,7 +493,7 @@ export class AgentProcessManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment variables from project's .auto-claude/.env file
|
||||
* Load environment variables from project's .aperant/.env file
|
||||
* This contains frontend-configured settings like memory configuration
|
||||
*/
|
||||
private loadProjectEnv(projectPath: string): Record<string, string> {
|
||||
@@ -1036,7 +1036,7 @@ export class AgentProcessManager {
|
||||
* Priority (later sources override earlier):
|
||||
* 1. App-wide memory settings from settings.json (NEW - enables memory from onboarding)
|
||||
* 2. Auto-build source .env (prompts directory) - default values
|
||||
* 3. Project's .auto-claude/.env - Frontend-configured settings (memory, integrations)
|
||||
* 3. Project's .aperant/.env - Frontend-configured settings (memory, integrations)
|
||||
* 4. Project settings (useClaudeMd) - Runtime overrides
|
||||
*/
|
||||
getCombinedEnv(projectPath: string): Record<string, string> {
|
||||
|
||||
@@ -225,7 +225,7 @@ export class AgentQueueManager {
|
||||
// which handles both dev (apps/desktop/prompts/) and production (resourcesPath/prompts/)
|
||||
const promptsDir = resolvePromptsDir();
|
||||
|
||||
const outputDir = path.join(projectPath, '.auto-claude', 'ideation');
|
||||
const outputDir = path.join(projectPath, '.aperant', 'ideation');
|
||||
|
||||
// Emit initial progress
|
||||
this.emitter.emit('ideation-progress', projectId, {
|
||||
@@ -469,7 +469,7 @@ export class AgentQueueManager {
|
||||
this.clearRoadmapProgress(projectPath);
|
||||
|
||||
// Load and emit the complete roadmap
|
||||
const roadmapFilePath = path.join(projectPath, '.auto-claude', 'roadmap', 'roadmap.json');
|
||||
const roadmapFilePath = path.join(projectPath, '.aperant', 'roadmap', 'roadmap.json');
|
||||
if (existsSync(roadmapFilePath)) {
|
||||
try {
|
||||
const content = await fsPromises.readFile(roadmapFilePath, 'utf-8');
|
||||
|
||||
@@ -82,7 +82,7 @@ const FAKE_MODEL = { type: 'language-model', modelId: 'mock-model-id' };
|
||||
const baseToolContext = {
|
||||
cwd: '/project',
|
||||
projectDir: '/project',
|
||||
specDir: '/project/.auto-claude/specs/001',
|
||||
specDir: '/project/.aperant/specs/001',
|
||||
securityProfile: 'standard' as const,
|
||||
} as unknown as ToolContext;
|
||||
|
||||
|
||||
@@ -87,11 +87,11 @@ describe('AGENT_CONFIGS', () => {
|
||||
expect(config.thinkingDefault).toBe('low');
|
||||
});
|
||||
|
||||
it('should configure planner with memory and auto-claude MCP', () => {
|
||||
it('should configure planner with memory and aperant MCP', () => {
|
||||
const config = AGENT_CONFIGS.planner;
|
||||
expect(config.mcpServers).toContain('context7');
|
||||
expect(config.mcpServers).toContain('memory');
|
||||
expect(config.mcpServers).toContain('auto-claude');
|
||||
expect(config.mcpServers).toContain('aperant');
|
||||
expect(config.mcpServersOptional).toContain('linear');
|
||||
expect(config.thinkingDefault).toBe('high');
|
||||
});
|
||||
@@ -197,7 +197,7 @@ describe('mapMcpServerName', () => {
|
||||
expect(mapMcpServerName('graphiti')).toBe('memory');
|
||||
expect(mapMcpServerName('graphiti-memory')).toBe('memory');
|
||||
expect(mapMcpServerName('linear')).toBe('linear');
|
||||
expect(mapMcpServerName('auto-claude')).toBe('auto-claude');
|
||||
expect(mapMcpServerName('aperant')).toBe('aperant');
|
||||
});
|
||||
|
||||
it('should return null for unknown names', () => {
|
||||
@@ -291,12 +291,12 @@ describe('getRequiredMcpServers', () => {
|
||||
expect(servers).toContain('context7');
|
||||
});
|
||||
|
||||
it('should support per-agent MCP removals but never remove auto-claude', () => {
|
||||
it('should support per-agent MCP removals but never remove aperant', () => {
|
||||
const servers = getRequiredMcpServers('coder', {
|
||||
memoryEnabled: true,
|
||||
agentMcpRemove: 'auto-claude,memory',
|
||||
agentMcpRemove: 'aperant,memory',
|
||||
});
|
||||
expect(servers).toContain('auto-claude');
|
||||
expect(servers).toContain('aperant');
|
||||
expect(servers).not.toContain('memory');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,15 +36,15 @@ const ALL_BUILTIN_TOOLS = [...BASE_READ_TOOLS, ...BASE_WRITE_TOOLS, ...WEB_TOOLS
|
||||
const SPEC_TOOLS = [...BASE_READ_TOOLS, 'Write', ...WEB_TOOLS] as const;
|
||||
|
||||
// =============================================================================
|
||||
// Auto-Claude MCP Tools (Custom build management)
|
||||
// Aperant MCP Tools (Custom build management)
|
||||
// =============================================================================
|
||||
|
||||
const TOOL_UPDATE_SUBTASK_STATUS = 'mcp__auto-claude__update_subtask_status';
|
||||
const TOOL_GET_BUILD_PROGRESS = 'mcp__auto-claude__get_build_progress';
|
||||
const TOOL_RECORD_DISCOVERY = 'mcp__auto-claude__record_discovery';
|
||||
const TOOL_RECORD_GOTCHA = 'mcp__auto-claude__record_gotcha';
|
||||
const TOOL_GET_SESSION_CONTEXT = 'mcp__auto-claude__get_session_context';
|
||||
const TOOL_UPDATE_QA_STATUS = 'mcp__auto-claude__update_qa_status';
|
||||
const TOOL_UPDATE_SUBTASK_STATUS = 'mcp__aperant__update_subtask_status';
|
||||
const TOOL_GET_BUILD_PROGRESS = 'mcp__aperant__get_build_progress';
|
||||
const TOOL_RECORD_DISCOVERY = 'mcp__aperant__record_discovery';
|
||||
const TOOL_RECORD_GOTCHA = 'mcp__aperant__record_gotcha';
|
||||
const TOOL_GET_SESSION_CONTEXT = 'mcp__aperant__get_session_context';
|
||||
const TOOL_UPDATE_QA_STATUS = 'mcp__aperant__update_qa_status';
|
||||
|
||||
// =============================================================================
|
||||
// External MCP Tools
|
||||
@@ -246,7 +246,7 @@ export const AGENT_CONFIGS: Record<AgentType, AgentConfig> = {
|
||||
*/
|
||||
build_orchestrator: {
|
||||
tools: [...ALL_BUILTIN_TOOLS, 'SpawnSubagent'],
|
||||
mcpServers: ['context7', 'memory', 'auto-claude'],
|
||||
mcpServers: ['context7', 'memory', 'aperant'],
|
||||
mcpServersOptional: ['linear'],
|
||||
autoClaudeTools: [
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
@@ -263,7 +263,7 @@ export const AGENT_CONFIGS: Record<AgentType, AgentConfig> = {
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
planner: {
|
||||
tools: [...ALL_BUILTIN_TOOLS],
|
||||
mcpServers: ['context7', 'memory', 'auto-claude'],
|
||||
mcpServers: ['context7', 'memory', 'aperant'],
|
||||
mcpServersOptional: ['linear'],
|
||||
autoClaudeTools: [
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
@@ -274,7 +274,7 @@ export const AGENT_CONFIGS: Record<AgentType, AgentConfig> = {
|
||||
},
|
||||
coder: {
|
||||
tools: [...ALL_BUILTIN_TOOLS],
|
||||
mcpServers: ['context7', 'memory', 'auto-claude'],
|
||||
mcpServers: ['context7', 'memory', 'aperant'],
|
||||
mcpServersOptional: ['linear'],
|
||||
autoClaudeTools: [
|
||||
TOOL_UPDATE_SUBTASK_STATUS,
|
||||
@@ -291,7 +291,7 @@ export const AGENT_CONFIGS: Record<AgentType, AgentConfig> = {
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
qa_reviewer: {
|
||||
tools: [...ALL_BUILTIN_TOOLS],
|
||||
mcpServers: ['context7', 'memory', 'auto-claude', 'browser'],
|
||||
mcpServers: ['context7', 'memory', 'aperant', 'browser'],
|
||||
mcpServersOptional: ['linear'],
|
||||
autoClaudeTools: [
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
@@ -302,7 +302,7 @@ export const AGENT_CONFIGS: Record<AgentType, AgentConfig> = {
|
||||
},
|
||||
qa_fixer: {
|
||||
tools: [...ALL_BUILTIN_TOOLS],
|
||||
mcpServers: ['context7', 'memory', 'auto-claude', 'browser'],
|
||||
mcpServers: ['context7', 'memory', 'aperant', 'browser'],
|
||||
mcpServersOptional: ['linear'],
|
||||
autoClaudeTools: [
|
||||
TOOL_UPDATE_SUBTASK_STATUS,
|
||||
@@ -482,7 +482,7 @@ const MCP_SERVER_NAME_MAP: Record<string, string> = {
|
||||
linear: 'linear',
|
||||
electron: 'electron',
|
||||
puppeteer: 'puppeteer',
|
||||
'auto-claude': 'auto-claude',
|
||||
'aperant': 'aperant',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -593,11 +593,11 @@ export function getRequiredMcpServers(
|
||||
}
|
||||
}
|
||||
|
||||
// Apply per-agent MCP removals (never remove auto-claude)
|
||||
// Apply per-agent MCP removals (never remove aperant)
|
||||
if (options.agentMcpRemove) {
|
||||
for (const name of options.agentMcpRemove.split(',')) {
|
||||
const mapped = mapMcpServerName(name.trim(), options.customServerIds);
|
||||
if (mapped && mapped !== 'auto-claude') {
|
||||
if (mapped && mapped !== 'aperant') {
|
||||
const idx = servers.indexOf(mapped);
|
||||
if (idx !== -1) servers.splice(idx, 1);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import type {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function loadProjectIndex(projectDir: string): ProjectIndex {
|
||||
const indexFile = path.join(projectDir, '.auto-claude', 'project_index.json');
|
||||
const indexFile = path.join(projectDir, '.aperant', 'project_index.json');
|
||||
if (fs.existsSync(indexFile)) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(indexFile, 'utf8')) as ProjectIndex;
|
||||
|
||||
@@ -14,8 +14,8 @@ import type { FileMatch } from './types.js';
|
||||
/** Directories that should never be searched. */
|
||||
const SKIP_DIRS = new Set([
|
||||
'node_modules', '.git', '__pycache__', '.venv', 'venv', 'dist', 'build',
|
||||
'.next', '.nuxt', 'target', 'vendor', '.idea', '.vscode', 'auto-claude',
|
||||
'.auto-claude', '.pytest_cache', '.mypy_cache', 'coverage', '.turbo', '.cache',
|
||||
'.next', '.nuxt', 'target', 'vendor', '.idea', '.vscode',
|
||||
'.aperant', '.pytest_cache', '.mypy_cache', 'coverage', '.turbo', '.cache',
|
||||
'out',
|
||||
]);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export interface ServiceInfo {
|
||||
key_directories?: Record<string, string>;
|
||||
}
|
||||
|
||||
/** Shape of .auto-claude/project_index.json */
|
||||
/** Shape of .aperant/project_index.json */
|
||||
export interface ProjectIndex {
|
||||
services?: Record<string, ServiceInfo>;
|
||||
[key: string]: unknown;
|
||||
|
||||
@@ -195,10 +195,10 @@ describe('createMcpClientsForAgent', () => {
|
||||
});
|
||||
|
||||
it('creates clients for each resolved server config', async () => {
|
||||
mockGetRequiredMcpServers.mockReturnValueOnce(['context7', 'auto-claude']);
|
||||
mockGetRequiredMcpServers.mockReturnValueOnce(['context7', 'aperant']);
|
||||
mockResolveMcpServers.mockReturnValueOnce([
|
||||
{ ...stdioConfig, id: 'context7' },
|
||||
{ ...stdioConfig, id: 'auto-claude' },
|
||||
{ ...stdioConfig, id: 'aperant' },
|
||||
]);
|
||||
// Two separate mock instances for the two servers
|
||||
mockCreateMCPClient
|
||||
@@ -209,7 +209,7 @@ describe('createMcpClientsForAgent', () => {
|
||||
|
||||
expect(clients).toHaveLength(2);
|
||||
expect(clients[0].serverId).toBe('context7');
|
||||
expect(clients[1].serverId).toBe('auto-claude');
|
||||
expect(clients[1].serverId).toBe('aperant');
|
||||
});
|
||||
|
||||
it('skips failed connections without throwing', async () => {
|
||||
|
||||
@@ -104,23 +104,23 @@ describe('getMcpServerConfig', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('auto-claude', () => {
|
||||
it('returns auto-claude config with empty specDir as default', () => {
|
||||
const config = getMcpServerConfig('auto-claude', {});
|
||||
describe('aperant', () => {
|
||||
it('returns aperant config with empty specDir as default', () => {
|
||||
const config = getMcpServerConfig('aperant', {});
|
||||
expect(config).not.toBeNull();
|
||||
expect(config?.id).toBe('auto-claude');
|
||||
expect(config?.id).toBe('aperant');
|
||||
});
|
||||
|
||||
it('injects SPEC_DIR into transport env', () => {
|
||||
const config = getMcpServerConfig('auto-claude', { specDir: '/project/.auto-claude/specs/001-feature' });
|
||||
const config = getMcpServerConfig('aperant', { specDir: '/project/.aperant/specs/001-feature' });
|
||||
expect(config?.transport.type).toBe('stdio');
|
||||
if (config?.transport.type === 'stdio') {
|
||||
expect(config.transport.env?.SPEC_DIR).toBe('/project/.auto-claude/specs/001-feature');
|
||||
expect(config.transport.env?.SPEC_DIR).toBe('/project/.aperant/specs/001-feature');
|
||||
}
|
||||
});
|
||||
|
||||
it('uses node command', () => {
|
||||
const config = getMcpServerConfig('auto-claude', {});
|
||||
const config = getMcpServerConfig('aperant', {});
|
||||
if (config?.transport.type === 'stdio') {
|
||||
expect(config.transport.command).toBe('node');
|
||||
}
|
||||
@@ -174,9 +174,9 @@ describe('resolveMcpServers', () => {
|
||||
expect(configs[0].id).toBe('memory');
|
||||
});
|
||||
|
||||
it('passes specDir through to auto-claude config', () => {
|
||||
const specDir = '/my-project/.auto-claude/specs/042-auth';
|
||||
const configs = resolveMcpServers(['auto-claude'], { specDir });
|
||||
it('passes specDir through to aperant config', () => {
|
||||
const specDir = '/my-project/.aperant/specs/042-auth';
|
||||
const configs = resolveMcpServers(['aperant'], { specDir });
|
||||
expect(configs).toHaveLength(1);
|
||||
if (configs[0].transport.type === 'stdio') {
|
||||
expect(configs[0].transport.env?.SPEC_DIR).toBe(specDir);
|
||||
|
||||
@@ -100,12 +100,12 @@ const PUPPETEER_SERVER: McpServerConfig = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Auto-Claude MCP server - custom build management tools.
|
||||
* Aperant MCP server - custom build management tools.
|
||||
* Used by planner, coder, and QA agents for build progress tracking.
|
||||
*/
|
||||
function createAutoClaudeServer(specDir: string): McpServerConfig {
|
||||
return {
|
||||
id: 'auto-claude',
|
||||
id: 'aperant',
|
||||
name: 'Aperant',
|
||||
description: 'Build management tools (progress tracking, session context)',
|
||||
enabledByDefault: true,
|
||||
@@ -124,7 +124,7 @@ function createAutoClaudeServer(specDir: string): McpServerConfig {
|
||||
|
||||
/** Options for resolving MCP server configurations */
|
||||
export interface McpRegistryOptions {
|
||||
/** Spec directory for auto-claude MCP server */
|
||||
/** Spec directory for aperant MCP server */
|
||||
specDir?: string;
|
||||
/** Memory MCP server URL (if enabled) */
|
||||
memoryMcpUrl?: string;
|
||||
@@ -175,7 +175,7 @@ export function getMcpServerConfig(
|
||||
case 'puppeteer':
|
||||
return PUPPETEER_SERVER;
|
||||
|
||||
case 'auto-claude': {
|
||||
case 'aperant': {
|
||||
const specDir = options.specDir ?? '';
|
||||
return createAutoClaudeServer(specDir);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export type McpServerId =
|
||||
| 'memory'
|
||||
| 'electron'
|
||||
| 'puppeteer'
|
||||
| 'auto-claude';
|
||||
| 'aperant';
|
||||
|
||||
/** Configuration for a single MCP server */
|
||||
export interface McpServerConfig {
|
||||
|
||||
@@ -46,7 +46,7 @@ export class IncrementalIndexer {
|
||||
ignored: [
|
||||
'**/node_modules/**',
|
||||
'**/.git/**',
|
||||
'**/.auto-claude/**',
|
||||
'**/.aperant/**',
|
||||
'**/dist/**',
|
||||
'**/build/**',
|
||||
'**/.next/**',
|
||||
@@ -313,7 +313,7 @@ export class IncrementalIndexer {
|
||||
private collectSupportedFiles(dir: string, extensions: string[]): string[] {
|
||||
const files: string[] = [];
|
||||
const IGNORED_DIRS = new Set([
|
||||
'node_modules', '.git', '.auto-claude', 'dist', 'build',
|
||||
'node_modules', '.git', '.aperant', 'dist', 'build',
|
||||
'.next', '__pycache__', 'target', '.venv',
|
||||
]);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* Manages:
|
||||
* - Baseline capture when worktrees are created
|
||||
* - File content snapshots in .auto-claude/baselines/
|
||||
* - File content snapshots in .aperant/baselines/
|
||||
* - Task modification tracking with semantic analysis
|
||||
* - Persistence of evolution data
|
||||
*/
|
||||
@@ -198,7 +198,7 @@ export class FileEvolutionTracker {
|
||||
storageDir?: string,
|
||||
semanticAnalyzer?: SemanticAnalyzer,
|
||||
) {
|
||||
const resolvedStorageDir = storageDir ?? path.join(projectDir, '.auto-claude');
|
||||
const resolvedStorageDir = storageDir ?? path.join(projectDir, '.aperant');
|
||||
this.storage = new EvolutionStorage(projectDir, resolvedStorageDir);
|
||||
this.analyzer = semanticAnalyzer ?? new SemanticAnalyzer();
|
||||
this.evolutions = this.storage.loadEvolutions();
|
||||
|
||||
@@ -110,8 +110,8 @@ function getFileFromBranch(
|
||||
function findWorktree(projectDir: string, taskId: string): string | undefined {
|
||||
// Common worktree locations
|
||||
const candidates = [
|
||||
path.join(projectDir, '.auto-claude', 'worktrees', taskId),
|
||||
path.join(projectDir, '.auto-claude', 'worktrees', 'tasks', taskId),
|
||||
path.join(projectDir, '.aperant', 'worktrees', taskId),
|
||||
path.join(projectDir, '.aperant', 'worktrees', 'tasks', taskId),
|
||||
];
|
||||
for (const c of candidates) {
|
||||
if (fs.existsSync(c)) return c;
|
||||
@@ -254,7 +254,7 @@ export class MergeOrchestrator {
|
||||
dryRun?: boolean;
|
||||
}) {
|
||||
this.projectDir = path.resolve(options.projectDir);
|
||||
this.storageDir = options.storageDir ?? path.join(this.projectDir, '.auto-claude');
|
||||
this.storageDir = options.storageDir ?? path.join(this.projectDir, '.aperant');
|
||||
this.enableAi = options.enableAi ?? true;
|
||||
this.dryRun = options.dryRun ?? false;
|
||||
this.aiResolver = options.aiResolver;
|
||||
|
||||
@@ -313,7 +313,7 @@ function getCommitInfo(commitHash: string, cwd: string): Record<string, string>
|
||||
|
||||
function getWorktreeFileContent(taskId: string, filePath: string, projectDir: string): string {
|
||||
// Try common worktree locations
|
||||
const worktreePath = path.join(projectDir, '.auto-claude', 'worktrees', taskId, filePath);
|
||||
const worktreePath = path.join(projectDir, '.aperant', 'worktrees', taskId, filePath);
|
||||
if (fs.existsSync(worktreePath)) {
|
||||
try {
|
||||
return fs.readFileSync(worktreePath, 'utf8');
|
||||
@@ -369,7 +369,7 @@ export class FileTimelineTracker {
|
||||
|
||||
constructor(projectPath: string, storagePath?: string) {
|
||||
this.projectPath = path.resolve(projectPath);
|
||||
const resolvedStoragePath = storagePath ?? path.join(this.projectPath, '.auto-claude');
|
||||
const resolvedStoragePath = storagePath ?? path.join(this.projectPath, '.aperant');
|
||||
this.persistence = new TimelinePersistence(resolvedStoragePath);
|
||||
this.timelines = this.persistence.loadAllTimelines();
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ import type { SessionResult } from '../../session/types';
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SPEC_DIR = '/project/.auto-claude/specs/001-feature';
|
||||
const SPEC_DIR = '/project/.aperant/specs/001-feature';
|
||||
const PROJECT_DIR = '/project';
|
||||
|
||||
function completedPlan(qaStatus?: 'approved' | 'rejected' | 'unknown') {
|
||||
|
||||
@@ -207,7 +207,7 @@ describe('generateEscalationReport', () => {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('generateManualTestPlan', () => {
|
||||
const SPEC_DIR = '/project/.auto-claude/specs/001-feature';
|
||||
const SPEC_DIR = '/project/.aperant/specs/001-feature';
|
||||
const PROJECT_DIR = '/project';
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -33,7 +33,7 @@ import type { BuildCheckpoint, FailureType } from '../recovery-manager';
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const PROJECT_DIR = path.join(path.sep, 'project');
|
||||
const SPEC_DIR = path.join(PROJECT_DIR, '.auto-claude', 'specs', '001-feature');
|
||||
const SPEC_DIR = path.join(PROJECT_DIR, '.aperant', 'specs', '001-feature');
|
||||
const MEMORY_DIR = path.join(SPEC_DIR, 'memory');
|
||||
const ATTEMPT_HISTORY_PATH = path.join(MEMORY_DIR, 'attempt_history.json');
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ const PHASE_CONFIG_MAP: Record<BuildPhase, Phase> = {
|
||||
|
||||
/** Configuration for the build orchestrator */
|
||||
export interface BuildOrchestratorConfig {
|
||||
/** Spec directory path (e.g., .auto-claude/specs/001-feature/) */
|
||||
/** Spec directory path (e.g., .aperant/specs/001-feature/) */
|
||||
specDir: string;
|
||||
/** Project root directory */
|
||||
projectDir: string;
|
||||
|
||||
@@ -40,8 +40,8 @@ import type {
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const PROFILE_FILENAME = '.auto-claude-security.json';
|
||||
const CUSTOM_ALLOWLIST_FILENAME = '.auto-claude-allowlist';
|
||||
const PROFILE_FILENAME = '.aperant-security.json';
|
||||
const CUSTOM_ALLOWLIST_FILENAME = '.aperant-allowlist';
|
||||
|
||||
const HASH_FILES = [
|
||||
'package.json',
|
||||
|
||||
@@ -35,7 +35,7 @@ const SKIP_DIRS = new Set([
|
||||
'.nuxt',
|
||||
'target',
|
||||
'vendor',
|
||||
'.auto-claude',
|
||||
'.aperant',
|
||||
'coverage',
|
||||
'.nyc_output',
|
||||
]);
|
||||
|
||||
@@ -402,10 +402,10 @@ function validateBranchName(branch: string | null | undefined): string | null {
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Load project_index.json from the project's .auto-claude directory.
|
||||
* Load project_index.json from the project's .aperant directory.
|
||||
*/
|
||||
export function loadProjectIndex(projectDir: string): Record<string, unknown> {
|
||||
const indexPath = join(projectDir, '.auto-claude', 'project_index.json');
|
||||
const indexPath = join(projectDir, '.aperant', 'project_index.json');
|
||||
if (!existsSync(indexPath)) return {};
|
||||
try {
|
||||
return JSON.parse(readFileSync(indexPath, 'utf-8')) as Record<string, unknown>;
|
||||
|
||||
@@ -28,8 +28,8 @@ import type {
|
||||
|
||||
/** Patterns to detect worktree isolation */
|
||||
const WORKTREE_PATH_PATTERNS = [
|
||||
/[/\\]\.auto-claude[/\\]worktrees[/\\]tasks[/\\]/,
|
||||
/[/\\]\.auto-claude[/\\]github[/\\]pr[/\\]worktrees[/\\]/,
|
||||
/[/\\]\.aperant[/\\]worktrees[/\\]tasks[/\\]/,
|
||||
/[/\\]\.aperant[/\\]github[/\\]pr[/\\]worktrees[/\\]/,
|
||||
/[/\\]\.worktrees[/\\]/,
|
||||
];
|
||||
|
||||
@@ -106,7 +106,7 @@ function getRelativeSpecPath(specDir: string, projectDir: string): string {
|
||||
|
||||
// Fallback: just use the spec dir name
|
||||
const parts = resolvedSpec.split(/[/\\]/);
|
||||
return `./auto-claude/specs/${parts[parts.length - 1]}`;
|
||||
return `./aperant/specs/${parts[parts.length - 1]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -161,7 +161,7 @@ describe('generateCommitMessage', () => {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
it('reads spec.md for title when spec directory exists', async () => {
|
||||
// Spec directory at .auto-claude/specs/001-add-feature
|
||||
// Spec directory at .aperant/specs/001-add-feature
|
||||
mockExistsSync.mockImplementation((p: string) => {
|
||||
const normalized = p.replace(/\\/g, '/');
|
||||
if (normalized.includes('specs/001-add-feature')) return true;
|
||||
|
||||
@@ -71,7 +71,7 @@ function makeStream(parts: Array<Record<string, unknown>>) {
|
||||
function baseConfig(overrides: Partial<IdeationConfig> = {}): IdeationConfig {
|
||||
return {
|
||||
projectDir: '/project',
|
||||
outputDir: '/project/.auto-claude/ideation',
|
||||
outputDir: '/project/.aperant/ideation',
|
||||
promptsDir: '/app/prompts',
|
||||
ideationType: 'code_improvements',
|
||||
...overrides,
|
||||
@@ -285,7 +285,7 @@ describe('runIdeation', () => {
|
||||
mockStreamText.mockReturnValue(makeStream([]));
|
||||
|
||||
await runIdeation(
|
||||
baseConfig({ projectDir: '/my/project', outputDir: '/my/project/.auto-claude/ideation' }),
|
||||
baseConfig({ projectDir: '/my/project', outputDir: '/my/project/.aperant/ideation' }),
|
||||
);
|
||||
|
||||
// The system prompt passed to streamText should contain the project dir
|
||||
|
||||
@@ -124,7 +124,7 @@ const VALID_ROADMAP_JSON = JSON.stringify({
|
||||
function baseConfig(overrides: Partial<RoadmapConfig> = {}): RoadmapConfig {
|
||||
return {
|
||||
projectDir: '/project',
|
||||
outputDir: '/project/.auto-claude/roadmap',
|
||||
outputDir: '/project/.aperant/roadmap',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -277,7 +277,7 @@ describe('runRoadmapGeneration', () => {
|
||||
});
|
||||
mockStreamText.mockReturnValue(makeStream([]));
|
||||
|
||||
await runRoadmapGeneration(baseConfig({ outputDir: '/project/.auto-claude/roadmap' }));
|
||||
await runRoadmapGeneration(baseConfig({ outputDir: '/project/.aperant/roadmap' }));
|
||||
|
||||
expect(mockMkdirSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('roadmap'),
|
||||
@@ -388,7 +388,7 @@ describe('runRoadmapGeneration', () => {
|
||||
|
||||
// mkdirSync should have been called with the default path
|
||||
expect(mockMkdirSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('.auto-claude'),
|
||||
expect.stringContaining('.aperant'),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -236,10 +236,7 @@ export async function generateCommitMessage(
|
||||
} = config;
|
||||
|
||||
// Find spec directory
|
||||
let specDir = join(projectDir, '.auto-claude', 'specs', specName);
|
||||
if (!existsSync(specDir)) {
|
||||
specDir = join(projectDir, 'auto-claude', 'specs', specName);
|
||||
}
|
||||
const specDir = join(projectDir, '.aperant', 'specs', specName);
|
||||
|
||||
// Get context from spec files
|
||||
const specContext = existsSync(specDir) ? getSpecContext(specDir) : {
|
||||
|
||||
@@ -141,7 +141,7 @@ function gatherPRContext(
|
||||
* Extract a brief summary from the spec file for fallback PR body.
|
||||
*/
|
||||
function extractSpecSummary(projectDir: string, specId: string): string {
|
||||
const specFile = join(projectDir, '.auto-claude', 'specs', specId, 'spec.md');
|
||||
const specFile = join(projectDir, '.aperant', 'specs', specId, 'spec.md');
|
||||
if (!existsSync(specFile)) {
|
||||
return `Implements ${specId}`;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ export async function runIdeation(
|
||||
const toolContext: ToolContext = {
|
||||
cwd: projectDir,
|
||||
projectDir,
|
||||
specDir: join(projectDir, '.auto-claude', 'specs'),
|
||||
specDir: join(projectDir, '.aperant', 'specs'),
|
||||
securityProfile: null as unknown as SecurityProfile,
|
||||
abortSignal,
|
||||
};
|
||||
|
||||
@@ -99,7 +99,7 @@ function loadProjectContext(projectDir: string): string {
|
||||
const contextParts: string[] = [];
|
||||
|
||||
// Load project index if available
|
||||
const indexPath = join(projectDir, '.auto-claude', 'project_index.json');
|
||||
const indexPath = join(projectDir, '.aperant', 'project_index.json');
|
||||
if (existsSync(indexPath)) {
|
||||
const index = safeParseJson<Record<string, unknown>>(readFileSync(indexPath, 'utf-8'));
|
||||
if (index) {
|
||||
@@ -116,7 +116,7 @@ function loadProjectContext(projectDir: string): string {
|
||||
}
|
||||
|
||||
// Load roadmap if available
|
||||
const roadmapPath = join(projectDir, '.auto-claude', 'roadmap', 'roadmap.json');
|
||||
const roadmapPath = join(projectDir, '.aperant', 'roadmap', 'roadmap.json');
|
||||
if (existsSync(roadmapPath)) {
|
||||
const roadmap = safeParseJson<Record<string, unknown>>(readFileSync(roadmapPath, 'utf-8'));
|
||||
if (roadmap) {
|
||||
@@ -132,7 +132,7 @@ function loadProjectContext(projectDir: string): string {
|
||||
}
|
||||
|
||||
// Load existing tasks
|
||||
const tasksPath = join(projectDir, '.auto-claude', 'specs');
|
||||
const tasksPath = join(projectDir, '.aperant', 'specs');
|
||||
if (existsSync(tasksPath)) {
|
||||
try {
|
||||
const taskDirs = readdirSync(tasksPath, { withFileTypes: true })
|
||||
@@ -246,7 +246,7 @@ export async function runInsightsQuery(
|
||||
const toolContext: ToolContext = {
|
||||
cwd: projectDir,
|
||||
projectDir,
|
||||
specDir: join(projectDir, '.auto-claude', 'specs'),
|
||||
specDir: join(projectDir, '.aperant', 'specs'),
|
||||
securityProfile: null as unknown as SecurityProfile,
|
||||
abortSignal,
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ const MAX_STEPS_PER_PHASE = 30;
|
||||
export interface RoadmapConfig {
|
||||
/** Project directory path */
|
||||
projectDir: string;
|
||||
/** Output directory for roadmap files (defaults to .auto-claude/roadmap/) */
|
||||
/** Output directory for roadmap files (defaults to .aperant/roadmap/) */
|
||||
outputDir?: string;
|
||||
/** Model shorthand (defaults to 'sonnet') */
|
||||
modelShorthand?: ModelShorthand;
|
||||
@@ -443,7 +443,7 @@ export async function runRoadmapGeneration(
|
||||
abortSignal,
|
||||
} = config;
|
||||
|
||||
const outputDir = config.outputDir ?? join(projectDir, '.auto-claude', 'roadmap');
|
||||
const outputDir = config.outputDir ?? join(projectDir, '.aperant', 'roadmap');
|
||||
|
||||
// Ensure output directory exists
|
||||
if (!existsSync(outputDir)) {
|
||||
@@ -454,7 +454,7 @@ export async function runRoadmapGeneration(
|
||||
const toolContext: ToolContext = {
|
||||
cwd: projectDir,
|
||||
projectDir,
|
||||
specDir: join(projectDir, '.auto-claude', 'specs'),
|
||||
specDir: join(projectDir, '.aperant', 'specs'),
|
||||
securityProfile: null as unknown as SecurityProfile,
|
||||
abortSignal,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Security Profile Management
|
||||
* ============================
|
||||
*
|
||||
* Loads and caches project security profiles from .auto-claude/ config.
|
||||
* Loads and caches project security profiles from .aperant/ config.
|
||||
* Provides SecurityProfile instances consumed by bash-validator.ts.
|
||||
*
|
||||
* NOTE: With the denylist security model, SecurityProfile command sets are no
|
||||
@@ -23,8 +23,8 @@ import type { SecurityProfile } from './bash-validator';
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const PROFILE_FILENAME = '.auto-claude-security.json';
|
||||
const ALLOWLIST_FILENAME = '.auto-claude-allowlist';
|
||||
const PROFILE_FILENAME = '.aperant-security.json';
|
||||
const ALLOWLIST_FILENAME = '.aperant-allowlist';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cache state
|
||||
|
||||
@@ -86,7 +86,7 @@ const BLOCKED_PROCESS_NAMES = new Set([
|
||||
// -- Self-protection (don't let the agent kill its own host) --
|
||||
'electron',
|
||||
'Electron',
|
||||
'auto-claude',
|
||||
'aperant',
|
||||
'Aperant',
|
||||
]);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('ProgressTracker', () => {
|
||||
type: 'tool-call',
|
||||
toolName: 'Write',
|
||||
toolCallId: 'c1',
|
||||
args: { file_path: '/project/.auto-claude/specs/001/implementation_plan.json' },
|
||||
args: { file_path: '/project/.aperant/specs/001/implementation_plan.json' },
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
@@ -251,12 +251,12 @@ describe('getRequiredMcpServers (registry)', () => {
|
||||
expect(servers).toContain('context7');
|
||||
});
|
||||
|
||||
it('should support per-agent MCP REMOVE overrides but protect auto-claude', () => {
|
||||
it('should support per-agent MCP REMOVE overrides but protect aperant', () => {
|
||||
const servers = getRequiredMcpServers('coder', {
|
||||
memoryEnabled: true,
|
||||
mcpConfig: { AGENT_MCP_coder_REMOVE: 'auto-claude,memory' },
|
||||
mcpConfig: { AGENT_MCP_coder_REMOVE: 'aperant,memory' },
|
||||
});
|
||||
expect(servers).toContain('auto-claude');
|
||||
expect(servers).toContain('aperant');
|
||||
expect(servers).not.toContain('memory');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -221,7 +221,7 @@ export function getRequiredMcpServers(
|
||||
const removals = removeValue.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
for (const server of removals) {
|
||||
const mapped = mapMcpServerName(server, customServerIds);
|
||||
if (mapped && mapped !== 'auto-claude') {
|
||||
if (mapped && mapped !== 'aperant') {
|
||||
servers = servers.filter((s) => s !== mapped);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface TruncationResult {
|
||||
*
|
||||
* @param output - The raw tool output string
|
||||
* @param toolName - Name of the tool (for spillover filename)
|
||||
* @param projectDir - Project directory (spillover written to .auto-claude/tool-output/)
|
||||
* @param projectDir - Project directory (spillover written to .aperant/tool-output/)
|
||||
* @param maxBytes - Override max bytes limit (default: MAX_BYTES)
|
||||
* @returns TruncationResult with potentially truncated content
|
||||
*/
|
||||
@@ -67,7 +67,7 @@ export function truncateToolOutput(
|
||||
}
|
||||
|
||||
// Exceeds limits — spill to disk
|
||||
const spilloverDir = path.join(projectDir, '.auto-claude', 'tool-output');
|
||||
const spilloverDir = path.join(projectDir, '.aperant', 'tool-output');
|
||||
try {
|
||||
fs.mkdirSync(spilloverDir, { recursive: true });
|
||||
} catch {
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface ToolContext {
|
||||
cwd: string;
|
||||
/** Root directory of the project being worked on */
|
||||
projectDir: string;
|
||||
/** Spec directory for the current task (e.g., .auto-claude/specs/001-feature/) */
|
||||
/** Spec directory for the current task (e.g., .aperant/specs/001-feature/) */
|
||||
specDir: string;
|
||||
/** Security profile governing command allowlists */
|
||||
securityProfile: SecurityProfile;
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
*
|
||||
* Creates and manages git worktrees for autonomous task execution.
|
||||
* Each task runs in an isolated worktree at:
|
||||
* {projectPath}/.auto-claude/worktrees/tasks/{specId}/
|
||||
* {projectPath}/.aperant/worktrees/tasks/{specId}/
|
||||
* on branch:
|
||||
* auto-claude/{specId}
|
||||
* aperant/{specId}
|
||||
*
|
||||
* The function is idempotent — calling it repeatedly with the same specId
|
||||
* returns the existing worktree without error.
|
||||
@@ -78,7 +78,7 @@ export interface WorktreeResult {
|
||||
* the remote ref (preserves gitignored files)
|
||||
* @param pushNewBranches If true, push the branch to origin and set upstream
|
||||
* tracking after worktree creation. Defaults to true.
|
||||
* @param autoBuildPath Optional custom data directory (e.g. ".auto-claude").
|
||||
* @param autoBuildPath Optional custom data directory (e.g. ".aperant").
|
||||
* Passed to getSpecsDir() for spec-copy logic.
|
||||
*/
|
||||
export async function createOrGetWorktree(
|
||||
@@ -89,8 +89,8 @@ export async function createOrGetWorktree(
|
||||
pushNewBranches = true,
|
||||
autoBuildPath?: string,
|
||||
): Promise<WorktreeResult> {
|
||||
const worktreePath = join(projectPath, '.auto-claude/worktrees/tasks', specId);
|
||||
const branchName = `auto-claude/${specId}`;
|
||||
const worktreePath = join(projectPath, '.aperant/worktrees/tasks', specId);
|
||||
const branchName = `aperant/${specId}`;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Step 1: Prune stale worktree references from git's internal records
|
||||
@@ -239,11 +239,11 @@ export async function createOrGetWorktree(
|
||||
// ------------------------------------------------------------------
|
||||
// Step 7: Copy spec directory into the worktree
|
||||
//
|
||||
// .auto-claude/specs/ is gitignored, so it is NOT present in the
|
||||
// .aperant/specs/ is gitignored, so it is NOT present in the
|
||||
// newly-created worktree checkout. Copy it from the main project so
|
||||
// that agents can read spec.md, implementation_plan.json, etc.
|
||||
// ------------------------------------------------------------------
|
||||
const specsRelDir = getSpecsDir(autoBuildPath); // e.g. ".auto-claude/specs"
|
||||
const specsRelDir = getSpecsDir(autoBuildPath); // e.g. ".aperant/specs"
|
||||
const sourceSpecDir = join(projectPath, specsRelDir, specId);
|
||||
const destSpecDir = join(worktreePath, specsRelDir, specId);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('ChangelogService - Task Filtering Integration', () => {
|
||||
// Create temporary test directory
|
||||
testDir = mkdtempSync(path.join(tmpdir(), 'changelog-test-'));
|
||||
projectPath = path.join(testDir, 'test-project');
|
||||
specsDir = path.join(projectPath, '.auto-claude', 'specs');
|
||||
specsDir = path.join(projectPath, '.aperant', 'specs');
|
||||
|
||||
// Create project structure
|
||||
mkdirSync(projectPath, { recursive: true });
|
||||
|
||||
@@ -54,7 +54,7 @@ export class ChangelogService extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Check if debug mode is enabled
|
||||
* Checks DEBUG from auto-claude/.env and DEBUG from process.env
|
||||
* Checks DEBUG from aperant/.env and DEBUG from process.env
|
||||
*/
|
||||
private isDebugEnabled(): boolean {
|
||||
// Cache the result after first check
|
||||
@@ -78,7 +78,7 @@ export class ChangelogService extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug logging - only logs when DEBUG=true in auto-claude/.env or DEBUG is set
|
||||
* Debug logging - only logs when DEBUG=true in aperant/.env or DEBUG is set
|
||||
*/
|
||||
private debug(...args: unknown[]): void {
|
||||
if (this.isDebugEnabled()) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { isLinux } from './platform';
|
||||
|
||||
const APP_NAME = 'auto-claude';
|
||||
const APP_NAME = 'aperant';
|
||||
|
||||
/**
|
||||
* Get the XDG config home directory
|
||||
@@ -70,11 +70,11 @@ export function getAppCacheDir(): string {
|
||||
|
||||
/**
|
||||
* Get the memories storage directory
|
||||
* This is where graph databases are stored (previously ~/.auto-claude/memories)
|
||||
* This is where graph databases are stored (previously ~/.aperant/memories)
|
||||
*/
|
||||
export function getMemoriesDir(): string {
|
||||
// For compatibility, we still support the legacy path
|
||||
const legacyPath = path.join(os.homedir(), '.auto-claude', 'memories');
|
||||
const legacyPath = path.join(os.homedir(), '.aperant', 'memories');
|
||||
|
||||
// On Linux with XDG variables set (AppImage, Flatpak, Snap), use XDG path
|
||||
if (isLinux() && (process.env.XDG_DATA_HOME || process.env.APPIMAGE || process.env.SNAP || process.env.FLATPAK_ID)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'path';
|
||||
|
||||
const INSIGHTS_DIR = '.auto-claude/insights';
|
||||
const INSIGHTS_DIR = '.aperant/insights';
|
||||
const SESSIONS_DIR = 'sessions';
|
||||
const CURRENT_SESSION_FILE = 'current_session.json';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Handles project lifecycle and Python environment management:
|
||||
- `PROJECT_REMOVE` - Remove project
|
||||
- `PROJECT_LIST` - List all projects
|
||||
- `PROJECT_UPDATE_SETTINGS` - Update project settings
|
||||
- `PROJECT_INITIALIZE` - Initialize .auto-claude directory
|
||||
- `PROJECT_INITIALIZE` - Initialize .aperant directory
|
||||
- `PROJECT_CHECK_VERSION` - Check initialization status
|
||||
- `project:has-local-source` - Check if project has local auto-claude source
|
||||
- Python environment initialization and status events
|
||||
|
||||
@@ -218,7 +218,7 @@ export interface MemoryDatabaseDetails {
|
||||
export function getMemoryDatabaseDetails(projectEnvVars: EnvironmentVars): MemoryDatabaseDetails {
|
||||
const dbPath = projectEnvVars['GRAPHITI_DB_PATH'] ||
|
||||
process.env.GRAPHITI_DB_PATH ||
|
||||
require('path').join(require('os').homedir(), '.auto-claude', 'memories');
|
||||
require('path').join(require('os').homedir(), '.aperant', 'memories');
|
||||
|
||||
const database = projectEnvVars['GRAPHITI_DATABASE'] ||
|
||||
process.env.GRAPHITI_DATABASE ||
|
||||
|
||||
@@ -288,7 +288,7 @@ ${existingVars['OLLAMA_EMBEDDING_DIM'] ? `OLLAMA_EMBEDDING_DIM=${existingVars['O
|
||||
|
||||
# LadybugDB Database (embedded - no Docker required)
|
||||
${existingVars['GRAPHITI_DATABASE'] ? `GRAPHITI_DATABASE=${existingVars['GRAPHITI_DATABASE']}` : '# GRAPHITI_DATABASE=auto_claude_memory'}
|
||||
${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_DB_PATH']}` : '# GRAPHITI_DB_PATH=~/.auto-claude/memories'}
|
||||
${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_DB_PATH']}` : '# GRAPHITI_DB_PATH=~/.aperant/memories'}
|
||||
`;
|
||||
|
||||
return content;
|
||||
|
||||
@@ -242,7 +242,7 @@ function createProject(): Project {
|
||||
id: 'project-1',
|
||||
name: 'Test Project',
|
||||
path: projectPath,
|
||||
autoBuildPath: '.auto-claude',
|
||||
autoBuildPath: '.aperant',
|
||||
settings: {
|
||||
model: 'default',
|
||||
memoryBackend: 'file',
|
||||
|
||||
@@ -28,7 +28,7 @@ import type { ModelShorthand, ThinkingLevel } from '../../ai/config/types';
|
||||
const { debug: debugLog } = createContextLogger('GitHub AutoFix');
|
||||
|
||||
/**
|
||||
* Auto-fix configuration stored in .auto-claude/github/config.json
|
||||
* Auto-fix configuration stored in .aperant/github/config.json
|
||||
*/
|
||||
export interface AutoFixConfig {
|
||||
enabled: boolean;
|
||||
@@ -99,7 +99,7 @@ export interface BatchProgress {
|
||||
* Get the GitHub directory for a project
|
||||
*/
|
||||
function getGitHubDir(project: Project): string {
|
||||
return path.join(project.path, '.auto-claude', 'github');
|
||||
return path.join(project.path, '.aperant', 'github');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -822,7 +822,7 @@ async function performCIWaitCheck(
|
||||
* Get the GitHub directory for a project
|
||||
*/
|
||||
function getGitHubDir(project: Project): string {
|
||||
return path.join(project.path, ".auto-claude", "github");
|
||||
return path.join(project.path, ".aperant", "github");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1081,7 +1081,7 @@ function createEmptyPRLogs(prNumber: number, repo: string, isFollowup: boolean):
|
||||
/**
|
||||
* Get PR logs file path
|
||||
*
|
||||
* Logs are stored at `.auto-claude/github/pr/logs_${prNumber}.json` within the project directory.
|
||||
* Logs are stored at `.aperant/github/pr/logs_${prNumber}.json` within the project directory.
|
||||
* This provides persistent storage for streaming log data during PR reviews.
|
||||
*/
|
||||
function getPRLogsPath(project: Project, prNumber: number): string {
|
||||
@@ -1163,7 +1163,7 @@ function addLogEntry(logs: PRLogs, entry: PRLogEntry): boolean {
|
||||
* This class implements a hybrid push/pull approach for real-time log streaming:
|
||||
*
|
||||
* 1. **File-Based Storage**: Logs are saved to disk every 3 entries (saveInterval)
|
||||
* - Location: .auto-claude/github/pr/logs_${prNumber}.json
|
||||
* - Location: .aperant/github/pr/logs_${prNumber}.json
|
||||
* - Format: JSON with phase status and log entries
|
||||
*
|
||||
* 2. **Push-Based Updates**: Emits IPC events (GITHUB_PR_LOGS_UPDATED) after each save
|
||||
@@ -1281,7 +1281,7 @@ class PRLogCollector {
|
||||
* Two-step update mechanism:
|
||||
* --------------------------
|
||||
* 1. **File Write**: Persists logs to disk via savePRLogs()
|
||||
* - Creates/updates .auto-claude/github/pr/logs_${prNumber}.json
|
||||
* - Creates/updates .aperant/github/pr/logs_${prNumber}.json
|
||||
* - Updates the `updated_at` timestamp
|
||||
*
|
||||
* 2. **IPC Push Event**: Sends GITHUB_PR_LOGS_UPDATED to renderer
|
||||
@@ -2508,7 +2508,7 @@ export function registerPRHandlers(getMainWindow: () => BrowserWindow | null): v
|
||||
}
|
||||
|
||||
// Use temp file to avoid shell escaping issues
|
||||
const tmpFile = join(project.path, ".auto-claude", "tmp_comment_body.txt");
|
||||
const tmpFile = join(project.path, ".aperant", "tmp_comment_body.txt");
|
||||
try {
|
||||
writeFileSync(tmpFile, body, "utf-8");
|
||||
// Use execFileSync with arguments array to prevent command injection
|
||||
@@ -2733,7 +2733,7 @@ export function registerPRHandlers(getMainWindow: () => BrowserWindow | null): v
|
||||
|
||||
const result = await withProjectOrNull(projectId, async (project) => {
|
||||
// Check if review exists and has reviewed_commit_sha
|
||||
const githubDir = path.join(project.path, ".auto-claude", "github");
|
||||
const githubDir = path.join(project.path, ".aperant", "github");
|
||||
const reviewPath = path.join(githubDir, "pr", `review_${prNumber}.json`);
|
||||
|
||||
let review: PRReviewResult;
|
||||
|
||||
@@ -92,7 +92,7 @@ export interface TriageProgress {
|
||||
* Get the GitHub directory for a project
|
||||
*/
|
||||
function getGitHubDir(project: Project): string {
|
||||
return path.join(project.path, '.auto-claude', 'github');
|
||||
return path.join(project.path, '.aperant', 'github');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,7 +68,7 @@ function validatePathWithinProject(projectPath: string, resolvedPath: string): v
|
||||
* Get the GitLab directory for a project
|
||||
*/
|
||||
function getGitLabDir(project: Project): string {
|
||||
const gitlabDir = path.join(project.path, '.auto-claude', 'gitlab');
|
||||
const gitlabDir = path.join(project.path, '.aperant', 'gitlab');
|
||||
validatePathWithinProject(project.path, gitlabDir);
|
||||
return gitlabDir;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ function getReviewKey(projectId: string, mrIid: number): string {
|
||||
* Get the GitLab directory for a project
|
||||
*/
|
||||
function getGitLabDir(project: Project): string {
|
||||
return path.join(project.path, '.auto-claude', 'gitlab');
|
||||
return path.join(project.path, '.aperant', 'gitlab');
|
||||
}
|
||||
|
||||
async function waitForRebaseCompletion(
|
||||
@@ -774,7 +774,7 @@ export function registerMRReviewHandlers(
|
||||
debugLog('checkNewCommits handler called', { projectId, mrIid });
|
||||
|
||||
const result = await withProjectOrNull(projectId, async (project) => {
|
||||
const gitlabDir = path.join(project.path, '.auto-claude', 'gitlab');
|
||||
const gitlabDir = path.join(project.path, '.aperant', 'gitlab');
|
||||
const reviewPath = path.join(gitlabDir, 'mr', `review_${mrIid}.json`);
|
||||
|
||||
if (!fs.existsSync(reviewPath)) {
|
||||
|
||||
@@ -95,7 +95,7 @@ function sanitizeTriageResult(result: GitLabTriageResult): {
|
||||
* Get the GitLab directory for a project
|
||||
*/
|
||||
function getGitLabDir(project: Project): string {
|
||||
return path.join(project.path, '.auto-claude', 'gitlab');
|
||||
return path.join(project.path, '.aperant', 'gitlab');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
isInitialized,
|
||||
hasLocalSource,
|
||||
checkGitStatus,
|
||||
initializeGit
|
||||
initializeGit,
|
||||
needsMigration,
|
||||
migrateProject
|
||||
} from '../project-initializer';
|
||||
import { getToolPath } from '../cli-tool-manager';
|
||||
import type { BrowserWindow } from 'electron';
|
||||
@@ -274,11 +276,11 @@ export function registerProjectHandlers(
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.PROJECT_LIST,
|
||||
async (): Promise<IPCResult<Project[]>> => {
|
||||
// Validate that .auto-claude folders still exist for all projects
|
||||
// Validate that .aperant folders still exist for all projects
|
||||
// If a folder was deleted, reset autoBuildPath so UI prompts for reinitialization
|
||||
const resetIds = projectStore.validateProjects();
|
||||
if (resetIds.length > 0) {
|
||||
console.warn('[IPC] PROJECT_LIST: Detected missing .auto-claude folders for', resetIds.length, 'project(s)');
|
||||
console.warn('[IPC] PROJECT_LIST: Detected missing .aperant folders for', resetIds.length, 'project(s)');
|
||||
}
|
||||
|
||||
const projects = projectStore.getProjects();
|
||||
@@ -382,7 +384,7 @@ export function registerProjectHandlers(
|
||||
|
||||
if (result.success) {
|
||||
// Update project's autoBuildPath
|
||||
projectStore.updateAutoBuildPath(projectId, '.auto-claude');
|
||||
projectStore.updateAutoBuildPath(projectId, '.aperant');
|
||||
}
|
||||
|
||||
return { success: result.success, data: result, error: result.error };
|
||||
@@ -396,7 +398,7 @@ export function registerProjectHandlers(
|
||||
);
|
||||
|
||||
// PROJECT_CHECK_VERSION now just checks if project is initialized
|
||||
// Version tracking for .auto-claude is removed since it only contains data
|
||||
// Version tracking for .aperant is removed since it only contains data
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.PROJECT_CHECK_VERSION,
|
||||
async (_, projectId: string): Promise<IPCResult<AutoBuildVersionInfo>> => {
|
||||
@@ -410,7 +412,7 @@ export function registerProjectHandlers(
|
||||
success: true,
|
||||
data: {
|
||||
isInitialized: isInitialized(project.path),
|
||||
updateAvailable: false // No updates for .auto-claude - it's just data
|
||||
updateAvailable: false // No updates for .aperant - it's just data
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -441,6 +443,20 @@ export function registerProjectHandlers(
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Migration Operations
|
||||
// ============================================
|
||||
|
||||
// Check if project needs migration from .auto-claude to .aperant
|
||||
ipcMain.handle('project:needs-migration', async (_event, projectPath: string) => {
|
||||
return needsMigration(projectPath);
|
||||
});
|
||||
|
||||
// Migrate project from .auto-claude to .aperant
|
||||
ipcMain.handle('project:migrate', async (_event, projectPath: string) => {
|
||||
return migrateProject(projectPath);
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Git Operations
|
||||
// ============================================
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
return { success: false, error: 'Task not found' };
|
||||
}
|
||||
|
||||
const autoBuildDir = project.autoBuildPath || '.auto-claude';
|
||||
const autoBuildDir = project.autoBuildPath || '.aperant';
|
||||
const specDir = path.join(project.path, autoBuildDir, 'specs', task.specId);
|
||||
|
||||
if (!existsSync(specDir)) {
|
||||
@@ -775,7 +775,7 @@
|
||||
}
|
||||
|
||||
// Get the spec directory
|
||||
const autoBuildDir = project.autoBuildPath || '.auto-claude';
|
||||
const autoBuildDir = project.autoBuildPath || '.aperant';
|
||||
const specDir = path.join(
|
||||
project.path,
|
||||
autoBuildDir,
|
||||
@@ -1207,7 +1207,7 @@
|
||||
}
|
||||
|
||||
const runScript = path.join(sourcePath, 'run.py');
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.auto-claude', 'specs', task.specId);
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.aperant', 'specs', task.specId);
|
||||
|
||||
if (!existsSync(specDir)) {
|
||||
return { success: false, error: 'Spec directory not found' };
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
return { success: false, error: 'Task not found' };
|
||||
}
|
||||
|
||||
const autoBuildDir = project.autoBuildPath || '.auto-claude';
|
||||
const autoBuildDir = project.autoBuildPath || '.aperant';
|
||||
const specDir = path.join(project.path, autoBuildDir, 'specs', task.specId);
|
||||
|
||||
if (!existsSync(specDir)) {
|
||||
@@ -775,7 +775,7 @@
|
||||
}
|
||||
|
||||
// Get the spec directory
|
||||
const autoBuildDir = project.autoBuildPath || '.auto-claude';
|
||||
const autoBuildDir = project.autoBuildPath || '.aperant';
|
||||
const specDir = path.join(
|
||||
project.path,
|
||||
autoBuildDir,
|
||||
@@ -1207,7 +1207,7 @@
|
||||
}
|
||||
|
||||
const runScript = path.join(sourcePath, 'run.py');
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.auto-claude', 'specs', task.specId);
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.aperant', 'specs', task.specId);
|
||||
|
||||
if (!existsSync(specDir)) {
|
||||
return { success: false, error: 'Spec directory not found' };
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: '/absolute/path/to/project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
const mockLogs: TaskLogs = {
|
||||
@@ -158,9 +158,9 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
expect(result.data).toEqual(mockLogs);
|
||||
expect(projectStore.getProject).toHaveBeenCalledWith('project-123');
|
||||
expect(taskLogService.loadLogs).toHaveBeenCalledWith(
|
||||
path.join('/absolute/path/to/project', '.auto-claude/specs', '001-test-task'),
|
||||
path.join('/absolute/path/to/project', '.aperant/specs', '001-test-task'),
|
||||
'/absolute/path/to/project',
|
||||
'.auto-claude/specs',
|
||||
'.aperant/specs',
|
||||
'001-test-task'
|
||||
);
|
||||
});
|
||||
@@ -173,7 +173,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: './relative/path',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
const mockLogs: TaskLogs = {
|
||||
@@ -228,7 +228,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: '/absolute/path/to/project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
(projectStore.getProject as Mock).mockReturnValue(mockProject);
|
||||
@@ -250,7 +250,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: '/absolute/path/to/project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
(projectStore.getProject as Mock).mockReturnValue(mockProject);
|
||||
@@ -274,7 +274,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: '/absolute/path/to/project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
(projectStore.getProject as Mock).mockReturnValue(mockProject);
|
||||
@@ -298,7 +298,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: '/absolute/path/to/project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
(projectStore.getProject as Mock).mockReturnValue(mockProject);
|
||||
@@ -310,9 +310,9 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
expect(result.success).toBe(true);
|
||||
expect(taskLogService.startWatching).toHaveBeenCalledWith(
|
||||
'001-test-task',
|
||||
path.join('/absolute/path/to/project', '.auto-claude/specs', '001-test-task'),
|
||||
path.join('/absolute/path/to/project', '.aperant/specs', '001-test-task'),
|
||||
'/absolute/path/to/project',
|
||||
'.auto-claude/specs'
|
||||
'.aperant/specs'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -343,7 +343,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: '/absolute/path/to/project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
(projectStore.getProject as Mock).mockReturnValue(mockProject);
|
||||
@@ -355,9 +355,9 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
expect(result.success).toBe(true);
|
||||
expect(taskLogService.startWatching).toHaveBeenCalledWith(
|
||||
'nonexistent-spec',
|
||||
path.join('/absolute/path/to/project', '.auto-claude/specs', 'nonexistent-spec'),
|
||||
path.join('/absolute/path/to/project', '.aperant/specs', 'nonexistent-spec'),
|
||||
'/absolute/path/to/project',
|
||||
'.auto-claude/specs'
|
||||
'.aperant/specs'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -369,7 +369,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: '/absolute/path/to/project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
(projectStore.getProject as Mock).mockReturnValue(mockProject);
|
||||
@@ -422,7 +422,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProjectRelative = {
|
||||
id: 'project-123',
|
||||
path: './my-project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
(projectStore.getProject as Mock).mockReturnValue(mockProjectRelative);
|
||||
@@ -463,7 +463,7 @@ describe('Task Logs Integration (IPC → Service → State)', () => {
|
||||
const mockProject = {
|
||||
id: 'project-123',
|
||||
path: '/absolute/path/to/project',
|
||||
autoBuildPath: '.auto-claude'
|
||||
autoBuildPath: '.aperant'
|
||||
};
|
||||
|
||||
(projectStore.getProject as Mock).mockReturnValue(mockProject);
|
||||
|
||||
+19
-19
@@ -12,10 +12,10 @@ import { describe, expect, it } from 'vitest';
|
||||
import { GIT_BRANCH_REGEX, validateWorktreeBranch } from '../worktree-handlers';
|
||||
|
||||
describe('GIT_BRANCH_REGEX', () => {
|
||||
it('should accept valid auto-claude branch names', () => {
|
||||
expect(GIT_BRANCH_REGEX.test('auto-claude/my-feature')).toBe(true);
|
||||
expect(GIT_BRANCH_REGEX.test('auto-claude/123-fix-bug')).toBe(true);
|
||||
expect(GIT_BRANCH_REGEX.test('auto-claude/feature_with_underscore')).toBe(true);
|
||||
it('should accept valid aperant branch names', () => {
|
||||
expect(GIT_BRANCH_REGEX.test('aperant/my-feature')).toBe(true);
|
||||
expect(GIT_BRANCH_REGEX.test('aperant/123-fix-bug')).toBe(true);
|
||||
expect(GIT_BRANCH_REGEX.test('aperant/feature_with_underscore')).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept valid feature branch names', () => {
|
||||
@@ -46,28 +46,28 @@ describe('GIT_BRANCH_REGEX', () => {
|
||||
});
|
||||
|
||||
describe('validateWorktreeBranch', () => {
|
||||
const expectedBranch = 'auto-claude/my-feature-123';
|
||||
const expectedBranch = 'aperant/my-feature-123';
|
||||
|
||||
describe('exact match scenarios', () => {
|
||||
it('should use detected branch when it matches expected exactly', () => {
|
||||
const result = validateWorktreeBranch('auto-claude/my-feature-123', expectedBranch);
|
||||
expect(result.branchToDelete).toBe('auto-claude/my-feature-123');
|
||||
const result = validateWorktreeBranch('aperant/my-feature-123', expectedBranch);
|
||||
expect(result.branchToDelete).toBe('aperant/my-feature-123');
|
||||
expect(result.usedFallback).toBe(false);
|
||||
expect(result.reason).toBe('exact_match');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pattern match scenarios', () => {
|
||||
it('should allow other auto-claude branches (specId renamed)', () => {
|
||||
const result = validateWorktreeBranch('auto-claude/renamed-feature', expectedBranch);
|
||||
expect(result.branchToDelete).toBe('auto-claude/renamed-feature');
|
||||
it('should allow other aperant branches (specId renamed)', () => {
|
||||
const result = validateWorktreeBranch('aperant/renamed-feature', expectedBranch);
|
||||
expect(result.branchToDelete).toBe('aperant/renamed-feature');
|
||||
expect(result.usedFallback).toBe(false);
|
||||
expect(result.reason).toBe('pattern_match');
|
||||
});
|
||||
|
||||
it('should allow auto-claude branches with different formats', () => {
|
||||
const result = validateWorktreeBranch('auto-claude/001-task', expectedBranch);
|
||||
expect(result.branchToDelete).toBe('auto-claude/001-task');
|
||||
it('should allow aperant branches with different formats', () => {
|
||||
const result = validateWorktreeBranch('aperant/001-task', expectedBranch);
|
||||
expect(result.branchToDelete).toBe('aperant/001-task');
|
||||
expect(result.usedFallback).toBe(false);
|
||||
expect(result.reason).toBe('pattern_match');
|
||||
});
|
||||
@@ -142,17 +142,17 @@ describe('validateWorktreeBranch', () => {
|
||||
expect(result.reason).toBe('invalid_pattern');
|
||||
});
|
||||
|
||||
it('should handle branch that starts with auto-claude but is malformed', () => {
|
||||
// "auto-claude" without a slash should still be rejected
|
||||
const result = validateWorktreeBranch('auto-claude', expectedBranch);
|
||||
it('should handle branch that starts with aperant but is malformed', () => {
|
||||
// "aperant" without a slash should still be rejected
|
||||
const result = validateWorktreeBranch('aperant', expectedBranch);
|
||||
expect(result.branchToDelete).toBe(expectedBranch);
|
||||
expect(result.usedFallback).toBe(true);
|
||||
expect(result.reason).toBe('invalid_pattern');
|
||||
});
|
||||
|
||||
it('should reject auto-claude/ with no suffix (invalid branch name)', () => {
|
||||
// "auto-claude/" alone is not a valid branch name - needs actual specId
|
||||
const result = validateWorktreeBranch('auto-claude/', expectedBranch);
|
||||
it('should reject aperant/ with no suffix (invalid branch name)', () => {
|
||||
// "aperant/" alone is not a valid branch name - needs actual specId
|
||||
const result = validateWorktreeBranch('aperant/', expectedBranch);
|
||||
expect(result.branchToDelete).toBe(expectedBranch);
|
||||
expect(result.usedFallback).toBe(true);
|
||||
expect(result.reason).toBe('invalid_pattern');
|
||||
|
||||
@@ -462,7 +462,7 @@ export function registerTaskCRUDHandlers(agentManager: AgentManager): void {
|
||||
return { success: false, error: 'Task not found' };
|
||||
}
|
||||
|
||||
const autoBuildDir = project.autoBuildPath || '.auto-claude';
|
||||
const autoBuildDir = project.autoBuildPath || '.aperant';
|
||||
const specDir = path.join(project.path, autoBuildDir, 'specs', task.specId);
|
||||
|
||||
if (!existsSync(specDir)) {
|
||||
@@ -674,7 +674,7 @@ export function registerTaskCRUDHandlers(agentManager: AgentManager): void {
|
||||
console.error(`[IPC] TASK_LOAD_IMAGE_THUMBNAIL: Unknown project: "${projectPath}"`);
|
||||
return { success: false, error: 'Unknown project' };
|
||||
}
|
||||
const autoBuildPath = project.autoBuildPath || '.auto-claude';
|
||||
const autoBuildPath = project.autoBuildPath || '.aperant';
|
||||
|
||||
// Build full path to the image
|
||||
const specsDir = getSpecsDir(autoBuildPath);
|
||||
|
||||
@@ -468,15 +468,15 @@ export function registerTaskExecutionHandlers(
|
||||
}
|
||||
|
||||
// Step 3: Clean untracked files that came from the merge
|
||||
// IMPORTANT: Exclude .auto-claude directory to preserve specs and worktree data
|
||||
const cleanResult = spawnSync(getToolPath('git'), ['clean', '-fd', '-e', '.auto-claude'], {
|
||||
// IMPORTANT: Exclude .aperant directory to preserve specs and worktree data
|
||||
const cleanResult = spawnSync(getToolPath('git'), ['clean', '-fd', '-e', '.aperant'], {
|
||||
cwd: project.path,
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
env: getIsolatedGitEnv()
|
||||
});
|
||||
if (cleanResult.status === 0) {
|
||||
console.log('[TASK_REVIEW] Cleaned untracked files in main (excluding .auto-claude)');
|
||||
console.log('[TASK_REVIEW] Cleaned untracked files in main (excluding .aperant)');
|
||||
}
|
||||
|
||||
console.log('[TASK_REVIEW] Main branch restored to pre-merge state');
|
||||
|
||||
@@ -33,9 +33,9 @@ export const GIT_BRANCH_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._/-]*[a-zA-Z0-9]$|^[a-zA
|
||||
/**
|
||||
* Validates a detected branch name and returns the safe branch to delete.
|
||||
*
|
||||
* Why `auto-claude/` prefix is considered safe:
|
||||
* - All task worktrees use branches named `auto-claude/{specId}`
|
||||
* - This pattern is controlled by Auto-Claude, not user input
|
||||
* Why `aperant/` prefix is considered safe:
|
||||
* - All task worktrees use branches named `aperant/{specId}`
|
||||
* - This pattern is controlled by Aperant, not user input
|
||||
* - If detected branch matches this pattern, it's a valid task branch
|
||||
* - If it doesn't match (e.g., `main`, `develop`, `feature/xxx`), it's likely
|
||||
* the main project's branch being incorrectly detected from a corrupted worktree
|
||||
@@ -66,9 +66,9 @@ export function validateWorktreeBranch(
|
||||
};
|
||||
}
|
||||
|
||||
// Matches auto-claude pattern with valid specId (not just "auto-claude/")
|
||||
// Matches aperant pattern with valid specId (not just "aperant/")
|
||||
// The specId must be non-empty for this to be a valid task branch
|
||||
if (detectedBranch.startsWith('auto-claude/') && detectedBranch.length > 'auto-claude/'.length) {
|
||||
if (detectedBranch.startsWith('aperant/') && detectedBranch.length > 'aperant/'.length) {
|
||||
return {
|
||||
branchToDelete: detectedBranch,
|
||||
usedFallback: false,
|
||||
@@ -1480,7 +1480,7 @@ function getEffectiveBaseBranch(projectPath: string, specId: string, projectMain
|
||||
}
|
||||
|
||||
// 1. Try task metadata baseBranch
|
||||
const specDir = path.join(projectPath, '.auto-claude', 'specs', specId);
|
||||
const specDir = path.join(projectPath, '.aperant', 'specs', specId);
|
||||
const taskBaseBranch = getTaskBaseBranch(specDir);
|
||||
if (taskBaseBranch) {
|
||||
return taskBaseBranch;
|
||||
@@ -1755,7 +1755,7 @@ export function registerWorktreeHandlers(
|
||||
): void {
|
||||
/**
|
||||
* Get the worktree status for a task
|
||||
* Per-spec architecture: Each spec has its own worktree at .auto-claude/worktrees/tasks/{spec-name}/
|
||||
* Per-spec architecture: Each spec has its own worktree at .aperant/worktrees/tasks/{spec-name}/
|
||||
*/
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.TASK_WORKTREE_STATUS,
|
||||
@@ -1766,7 +1766,7 @@ export function registerWorktreeHandlers(
|
||||
return { success: false, error: 'Task not found' };
|
||||
}
|
||||
|
||||
// Find worktree at .auto-claude/worktrees/tasks/{spec-name}/
|
||||
// Find worktree at .aperant/worktrees/tasks/{spec-name}/
|
||||
const worktreePath = findTaskWorktree(project.path, task.specId);
|
||||
|
||||
if (!worktreePath) {
|
||||
@@ -1871,7 +1871,7 @@ export function registerWorktreeHandlers(
|
||||
|
||||
/**
|
||||
* Get the diff for a task's worktree
|
||||
* Per-spec architecture: Each spec has its own worktree at .auto-claude/worktrees/tasks/{spec-name}/
|
||||
* Per-spec architecture: Each spec has its own worktree at .aperant/worktrees/tasks/{spec-name}/
|
||||
*/
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.TASK_WORKTREE_DIFF,
|
||||
@@ -1882,7 +1882,7 @@ export function registerWorktreeHandlers(
|
||||
return { success: false, error: 'Task not found' };
|
||||
}
|
||||
|
||||
// Find worktree at .auto-claude/worktrees/tasks/{spec-name}/
|
||||
// Find worktree at .aperant/worktrees/tasks/{spec-name}/
|
||||
const worktreePath = findTaskWorktree(project.path, task.specId);
|
||||
|
||||
if (!worktreePath) {
|
||||
@@ -1987,7 +1987,7 @@ export function registerWorktreeHandlers(
|
||||
|
||||
debug('Found task:', task.specId, 'project:', project.path);
|
||||
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.auto-claude', 'specs', task.specId);
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.aperant', 'specs', task.specId);
|
||||
const worktreePath = findTaskWorktree(project.path, task.specId);
|
||||
|
||||
// Auto-fix any misconfigured bare repo before merge operation
|
||||
@@ -2031,7 +2031,7 @@ export function registerWorktreeHandlers(
|
||||
const aiResolverFn = createMergeResolverFn(modelShorthand, 'low');
|
||||
|
||||
// Create the merge orchestrator
|
||||
const storageDir = path.join(project.path, project.autoBuildPath || '.auto-claude');
|
||||
const storageDir = path.join(project.path, project.autoBuildPath || '.aperant');
|
||||
const orchestrator = new MergeOrchestrator({
|
||||
projectDir: project.path,
|
||||
storageDir,
|
||||
@@ -2126,7 +2126,7 @@ export function registerWorktreeHandlers(
|
||||
|
||||
if (!hasActualStagedChanges) {
|
||||
// Check if worktree branch was already merged (merge commit exists)
|
||||
const specBranch = `auto-claude/${task.specId}`;
|
||||
const specBranch = `aperant/${task.specId}`;
|
||||
try {
|
||||
// Check if current branch contains all commits from spec branch
|
||||
// git merge-base --is-ancestor returns exit code 0 if true, 1 if false
|
||||
@@ -2269,7 +2269,7 @@ export function registerWorktreeHandlers(
|
||||
];
|
||||
// Add worktree plan path if worktree exists
|
||||
if (worktreePath) {
|
||||
const worktreeSpecDir = path.join(worktreePath, project.autoBuildPath || '.auto-claude', 'specs', task.specId);
|
||||
const worktreeSpecDir = path.join(worktreePath, project.autoBuildPath || '.aperant', 'specs', task.specId);
|
||||
planPaths.push({ path: path.join(worktreeSpecDir, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN), isMain: false });
|
||||
}
|
||||
|
||||
@@ -2433,7 +2433,7 @@ export function registerWorktreeHandlers(
|
||||
// 1. Task metadata baseBranch (explicit task-level override)
|
||||
// 2. Project settings mainBranch (project-level default)
|
||||
// 3. Default to 'main'
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.auto-claude', 'specs', task.specId);
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.aperant', 'specs', task.specId);
|
||||
const taskBaseBranch = getTaskBaseBranch(specDir);
|
||||
const projectMainBranch = project.settings?.mainBranch;
|
||||
const effectiveBaseBranch = taskBaseBranch || projectMainBranch || 'main';
|
||||
@@ -2442,7 +2442,7 @@ export function registerWorktreeHandlers(
|
||||
|
||||
// Run preview using the TypeScript MergeOrchestrator in dry-run mode
|
||||
// (no AI resolver needed for preview — only conflict detection and analysis)
|
||||
const storageDir = path.join(project.path, project.autoBuildPath || '.auto-claude');
|
||||
const storageDir = path.join(project.path, project.autoBuildPath || '.aperant');
|
||||
const orchestrator = new MergeOrchestrator({
|
||||
projectDir: project.path,
|
||||
storageDir,
|
||||
@@ -2515,7 +2515,7 @@ export function registerWorktreeHandlers(
|
||||
|
||||
/**
|
||||
* Discard the worktree changes
|
||||
* Per-spec architecture: Each spec has its own worktree at .auto-claude/worktrees/tasks/{spec-name}/
|
||||
* Per-spec architecture: Each spec has its own worktree at .aperant/worktrees/tasks/{spec-name}/
|
||||
*
|
||||
* Note: Uses the shared cleanupWorktree utility which handles Windows-specific issues
|
||||
* where `git worktree remove --force` fails when the directory contains untracked files.
|
||||
@@ -2530,7 +2530,7 @@ export function registerWorktreeHandlers(
|
||||
return { success: false, error: 'Task not found' };
|
||||
}
|
||||
|
||||
// Find worktree at .auto-claude/worktrees/tasks/{spec-name}/
|
||||
// Find worktree at .aperant/worktrees/tasks/{spec-name}/
|
||||
const worktreePath = findTaskWorktree(project.path, task.specId);
|
||||
|
||||
if (!worktreePath) {
|
||||
@@ -2622,7 +2622,7 @@ export function registerWorktreeHandlers(
|
||||
return { success: false, error: 'Project path is invalid' };
|
||||
}
|
||||
|
||||
// Find worktree at .auto-claude/worktrees/tasks/{spec-name}/
|
||||
// Find worktree at .aperant/worktrees/tasks/{spec-name}/
|
||||
const worktreePath = findTaskWorktree(project.path, specName);
|
||||
|
||||
if (!worktreePath) {
|
||||
@@ -2670,7 +2670,7 @@ export function registerWorktreeHandlers(
|
||||
|
||||
/**
|
||||
* List all spec worktrees for a project
|
||||
* Per-spec architecture: Each spec has its own worktree at .auto-claude/worktrees/tasks/{spec-name}/
|
||||
* Per-spec architecture: Each spec has its own worktree at .aperant/worktrees/tasks/{spec-name}/
|
||||
*/
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.TASK_LIST_WORKTREES,
|
||||
@@ -2699,7 +2699,7 @@ export function registerWorktreeHandlers(
|
||||
// Used for orphan detection - worktrees without a matching task are orphaned
|
||||
const tasks = projectStore.getTasks(projectId);
|
||||
// Track if task lookup was successful (empty array with existing specs dir = lookup failed)
|
||||
const mainSpecsDir = path.join(project.path, '.auto-claude', 'specs');
|
||||
const mainSpecsDir = path.join(project.path, '.aperant', 'specs');
|
||||
const taskLookupSuccessful = tasks.length > 0 || !existsSync(mainSpecsDir);
|
||||
|
||||
// Helper to process a single worktree entry (async)
|
||||
@@ -2997,7 +2997,7 @@ export function registerWorktreeHandlers(
|
||||
|
||||
debug('Found task:', task.specId, 'project:', project.path);
|
||||
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.auto-claude', 'specs', task.specId);
|
||||
const specDir = path.join(project.path, project.autoBuildPath || '.aperant', 'specs', task.specId);
|
||||
|
||||
// Use EAFP pattern - try to read specDir and catch ENOENT
|
||||
try {
|
||||
@@ -3034,7 +3034,7 @@ export function registerWorktreeHandlers(
|
||||
// Determine base branch and branch name
|
||||
const taskBaseBranch = getTaskBaseBranch(specDir);
|
||||
const baseBranch = options?.targetBranch || taskBaseBranch || 'main';
|
||||
const branchName = `auto-claude/${task.specId}`;
|
||||
const branchName = `aperant/${task.specId}`;
|
||||
const prTitle = options?.title || `auto-claude: ${task.specId}`;
|
||||
|
||||
if (taskBaseBranch) {
|
||||
|
||||
@@ -195,7 +195,7 @@ function getDefaultBranch(projectPath: string): string {
|
||||
return project.settings.mainBranch;
|
||||
}
|
||||
|
||||
const envPath = path.join(projectPath, '.auto-claude', '.env');
|
||||
const envPath = path.join(projectPath, '.aperant', '.env');
|
||||
if (existsSync(envPath)) {
|
||||
try {
|
||||
const content = readFileSync(envPath, 'utf-8');
|
||||
@@ -291,7 +291,7 @@ const DEFAULT_STRATEGY_MAP: Record<string, 'symlink' | 'recreate' | 'copy' | 'sk
|
||||
* node_modules-only behavior for backward compatibility.
|
||||
*/
|
||||
function loadDependencyConfigs(projectPath: string): DependencyConfig[] {
|
||||
const indexPath = path.join(projectPath, '.auto-claude', 'project_index.json');
|
||||
const indexPath = path.join(projectPath, '.aperant', 'project_index.json');
|
||||
|
||||
if (existsSync(indexPath)) {
|
||||
try {
|
||||
@@ -1045,9 +1045,9 @@ async function listTerminalWorktrees(projectPath: string): Promise<TerminalWorkt
|
||||
* List "other" worktrees - worktrees not managed by Auto Claude
|
||||
* These are discovered via `git worktree list` excluding:
|
||||
* - Main worktree (project root)
|
||||
* - .auto-claude/worktrees/terminal/*
|
||||
* - .auto-claude/worktrees/tasks/*
|
||||
* - .auto-claude/worktrees/pr/*
|
||||
* - .aperant/worktrees/terminal/*
|
||||
* - .aperant/worktrees/tasks/*
|
||||
* - .aperant/worktrees/pr/*
|
||||
*/
|
||||
async function listOtherWorktrees(projectPath: string): Promise<OtherWorktreeInfo[]> {
|
||||
// Validate projectPath against registered projects
|
||||
@@ -1061,9 +1061,9 @@ async function listOtherWorktrees(projectPath: string): Promise<OtherWorktreeInf
|
||||
// Paths to exclude (normalize for comparison)
|
||||
const normalizedProjectPath = path.resolve(projectPath);
|
||||
const excludePrefixes = [
|
||||
path.join(normalizedProjectPath, '.auto-claude', 'worktrees', 'terminal'),
|
||||
path.join(normalizedProjectPath, '.auto-claude', 'worktrees', 'tasks'),
|
||||
path.join(normalizedProjectPath, '.auto-claude', 'worktrees', 'pr'),
|
||||
path.join(normalizedProjectPath, '.aperant', 'worktrees', 'terminal'),
|
||||
path.join(normalizedProjectPath, '.aperant', 'worktrees', 'tasks'),
|
||||
path.join(normalizedProjectPath, '.aperant', 'worktrees', 'pr'),
|
||||
];
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'fs';
|
||||
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, renameSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { execFileSync } from 'child_process';
|
||||
import { getToolPath } from './cli-tool-manager';
|
||||
@@ -155,7 +155,7 @@ export function initializeGit(projectPath: string): InitializationResult {
|
||||
/**
|
||||
* Entries to add to .gitignore when initializing a project
|
||||
*/
|
||||
const GITIGNORE_ENTRIES = ['.auto-claude/'];
|
||||
const GITIGNORE_ENTRIES = ['.aperant/'];
|
||||
|
||||
/**
|
||||
* Ensure entries exist in the project's .gitignore file.
|
||||
@@ -219,7 +219,7 @@ function ensureGitignoreEntries(projectPath: string, entries: string[]): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Data directories created in .auto-claude for each project
|
||||
* Data directories created in .aperant for each project
|
||||
*/
|
||||
const DATA_DIRECTORIES = [
|
||||
'specs',
|
||||
@@ -259,17 +259,17 @@ export function getLocalSourcePath(projectPath: string): string | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if project is initialized (has .auto-claude directory)
|
||||
* Check if project is initialized (has .aperant directory)
|
||||
*/
|
||||
export function isInitialized(projectPath: string): boolean {
|
||||
const dotAutoBuildPath = path.join(projectPath, '.auto-claude');
|
||||
const dotAutoBuildPath = path.join(projectPath, '.aperant');
|
||||
return existsSync(dotAutoBuildPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize auto-claude data directory in a project.
|
||||
* Initialize aperant data directory in a project.
|
||||
*
|
||||
* Creates .auto-claude/ with data directories (specs, ideation, insights, roadmap).
|
||||
* Creates .aperant/ with data directories (specs, ideation, insights, roadmap).
|
||||
* The framework code runs from the source repo - only data is stored here.
|
||||
*
|
||||
* Requires:
|
||||
@@ -299,20 +299,20 @@ export function initializeProject(projectPath: string): InitializationResult {
|
||||
}
|
||||
|
||||
// Check if already initialized
|
||||
const dotAutoBuildPath = path.join(projectPath, '.auto-claude');
|
||||
const dotAutoBuildPath = path.join(projectPath, '.aperant');
|
||||
|
||||
if (existsSync(dotAutoBuildPath)) {
|
||||
debug('Already initialized - .auto-claude exists');
|
||||
debug('Already initialized - .aperant exists');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Project already has auto-claude initialized (.auto-claude exists)'
|
||||
error: 'Project already has aperant initialized (.aperant exists)'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
debug('Creating .auto-claude data directory', { dotAutoBuildPath });
|
||||
debug('Creating .aperant data directory', { dotAutoBuildPath });
|
||||
|
||||
// Create the .auto-claude directory
|
||||
// Create the .aperant directory
|
||||
mkdirSync(dotAutoBuildPath, { recursive: true });
|
||||
|
||||
// Create data directories
|
||||
@@ -323,7 +323,7 @@ export function initializeProject(projectPath: string): InitializationResult {
|
||||
writeFileSync(path.join(dirPath, '.gitkeep'), '', 'utf-8');
|
||||
}
|
||||
|
||||
// Update .gitignore to exclude .auto-claude/
|
||||
// Update .gitignore to exclude .aperant/
|
||||
ensureGitignoreEntries(projectPath, GITIGNORE_ENTRIES);
|
||||
|
||||
debug('Initialization complete');
|
||||
@@ -339,11 +339,11 @@ export function initializeProject(projectPath: string): InitializationResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure all data directories exist in .auto-claude.
|
||||
* Ensure all data directories exist in .aperant.
|
||||
* Useful if new directories are added in future versions.
|
||||
*/
|
||||
export function ensureDataDirectories(projectPath: string): InitializationResult {
|
||||
const dotAutoBuildPath = path.join(projectPath, '.auto-claude');
|
||||
const dotAutoBuildPath = path.join(projectPath, '.aperant');
|
||||
|
||||
if (!existsSync(dotAutoBuildPath)) {
|
||||
return {
|
||||
@@ -371,22 +371,73 @@ export function ensureDataDirectories(projectPath: string): InitializationResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auto-claude folder path for a project.
|
||||
* Get the aperant folder path for a project.
|
||||
*
|
||||
* IMPORTANT: Only .auto-claude/ is considered a valid "installed" auto-claude.
|
||||
* The auto-claude/ folder (if it exists) is the SOURCE CODE being developed,
|
||||
* IMPORTANT: Only .aperant/ is considered a valid "installed" aperant.
|
||||
* The aperant/ folder (if it exists) is the SOURCE CODE being developed,
|
||||
* not an installation. This allows Aperant to be used to develop itself.
|
||||
*/
|
||||
export function getAutoBuildPath(projectPath: string): string | null {
|
||||
const dotAutoBuildPath = path.join(projectPath, '.auto-claude');
|
||||
const dotAutoBuildPath = path.join(projectPath, '.aperant');
|
||||
|
||||
debug('getAutoBuildPath called', { projectPath, dotAutoBuildPath });
|
||||
|
||||
if (existsSync(dotAutoBuildPath)) {
|
||||
debug('Returning .auto-claude (installed version)');
|
||||
return '.auto-claude';
|
||||
debug('Returning .aperant (installed version)');
|
||||
return '.aperant';
|
||||
}
|
||||
|
||||
debug('No .auto-claude folder found - project not initialized');
|
||||
debug('No .aperant folder found - project not initialized');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if project needs migration from .auto-claude to .aperant
|
||||
*/
|
||||
export function needsMigration(projectPath: string): boolean {
|
||||
const oldPath = path.join(projectPath, '.auto-claude');
|
||||
const newPath = path.join(projectPath, '.aperant');
|
||||
return existsSync(oldPath) && !existsSync(newPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate project from .auto-claude to .aperant
|
||||
* Renames the directory and updates .gitignore entries
|
||||
*/
|
||||
export function migrateProject(projectPath: string): InitializationResult {
|
||||
const oldPath = path.join(projectPath, '.auto-claude');
|
||||
const newPath = path.join(projectPath, '.aperant');
|
||||
|
||||
if (!existsSync(oldPath)) {
|
||||
return { success: false, error: 'No .auto-claude directory found to migrate' };
|
||||
}
|
||||
|
||||
if (existsSync(newPath)) {
|
||||
return { success: false, error: '.aperant directory already exists' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Rename the directory
|
||||
renameSync(oldPath, newPath);
|
||||
|
||||
// Update .gitignore: replace .auto-claude/ with .aperant/
|
||||
const gitignorePath = path.join(projectPath, '.gitignore');
|
||||
try {
|
||||
let content = readFileSync(gitignorePath, 'utf-8');
|
||||
content = content.replace(/\.auto-claude\//g, '.aperant/');
|
||||
content = content.replace(/\.auto-claude-security\.json/g, '.aperant-security.json');
|
||||
content = content.replace(/\.auto-claude-status/g, '.aperant-status');
|
||||
writeFileSync(gitignorePath, content, 'utf-8');
|
||||
} catch {
|
||||
// .gitignore update is non-critical
|
||||
}
|
||||
|
||||
debug('Migration complete: .auto-claude → .aperant');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Migration failed'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,10 +97,10 @@ export class ProjectStore {
|
||||
// Check if project already exists (using absolute path for comparison)
|
||||
const existing = this.data.projects.find((p) => p.path === absolutePath);
|
||||
if (existing) {
|
||||
// Validate that .auto-claude folder still exists for existing project
|
||||
// Validate that .aperant folder still exists for existing project
|
||||
// If manually deleted, reset autoBuildPath so UI prompts for reinitialization
|
||||
if (existing.autoBuildPath && !isInitialized(existing.path)) {
|
||||
console.warn(`[ProjectStore] .auto-claude folder was deleted for project "${existing.name}" - resetting autoBuildPath`);
|
||||
console.warn(`[ProjectStore] .aperant folder was deleted for project "${existing.name}" - resetting autoBuildPath`);
|
||||
existing.autoBuildPath = '';
|
||||
existing.updatedAt = new Date();
|
||||
this.save();
|
||||
@@ -111,7 +111,7 @@ export class ProjectStore {
|
||||
// Derive name from path if not provided
|
||||
const projectName = name || path.basename(absolutePath);
|
||||
|
||||
// Determine auto-claude path (supports both 'auto-claude' and '.auto-claude')
|
||||
// Determine aperant path
|
||||
const autoBuildPath = getAutoBuildPath(absolutePath) || '';
|
||||
|
||||
const project: Project = {
|
||||
@@ -213,11 +213,11 @@ export class ProjectStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all projects to ensure their .auto-claude folders still exist.
|
||||
* Validate all projects to ensure their .aperant folders still exist.
|
||||
* If a project has autoBuildPath set but the folder was deleted,
|
||||
* reset autoBuildPath to empty string so the UI prompts for reinitialization.
|
||||
*
|
||||
* @returns Array of project IDs that were reset due to missing .auto-claude folder
|
||||
* @returns Array of project IDs that were reset due to missing .aperant folder
|
||||
*/
|
||||
validateProjects(): string[] {
|
||||
const resetProjectIds: string[] = [];
|
||||
@@ -235,9 +235,9 @@ export class ProjectStore {
|
||||
continue; // Don't reset - let user handle this case
|
||||
}
|
||||
|
||||
// Check if .auto-claude folder still exists
|
||||
// Check if .aperant folder still exists
|
||||
if (!isInitialized(project.path)) {
|
||||
console.warn(`[ProjectStore] .auto-claude folder missing for project "${project.name}" at ${project.path}`);
|
||||
console.warn(`[ProjectStore] .aperant folder missing for project "${project.name}" at ${project.path}`);
|
||||
project.autoBuildPath = '';
|
||||
project.updatedAt = new Date();
|
||||
resetProjectIds.push(project.id);
|
||||
@@ -247,7 +247,7 @@ export class ProjectStore {
|
||||
|
||||
if (hasChanges) {
|
||||
this.save();
|
||||
console.warn(`[ProjectStore] Reset ${resetProjectIds.length} project(s) due to missing .auto-claude folder`);
|
||||
console.warn(`[ProjectStore] Reset ${resetProjectIds.length} project(s) due to missing .aperant folder`);
|
||||
}
|
||||
|
||||
return resetProjectIds;
|
||||
|
||||
@@ -344,7 +344,7 @@ export class ReleaseService extends EventEmitter {
|
||||
tasks: Task[]
|
||||
): Promise<UnmergedWorktreeInfo[]> {
|
||||
const unmerged: UnmergedWorktreeInfo[] = [];
|
||||
const worktreesDir = path.join(projectPath, '.auto-claude', 'worktrees', 'tasks');
|
||||
const worktreesDir = path.join(projectPath, '.aperant', 'worktrees', 'tasks');
|
||||
|
||||
if (!existsSync(worktreesDir)) {
|
||||
return [];
|
||||
|
||||
@@ -119,15 +119,15 @@ describe('profile-manager', () => {
|
||||
expect(result).toEqual(mockData);
|
||||
});
|
||||
|
||||
it('should use auto-claude directory for profiles.json path', async () => {
|
||||
it('should use aperant directory for profiles.json path', async () => {
|
||||
fsMocks.readFile.mockRejectedValue(new Error('ENOENT'));
|
||||
|
||||
await loadProfilesFile();
|
||||
|
||||
// Verify the file path includes auto-claude
|
||||
// Verify the file path includes aperant
|
||||
const readFileCalls = fsMocks.readFile.mock.calls;
|
||||
const filePath = readFileCalls[0]?.[0];
|
||||
expect(filePath).toContain('auto-claude');
|
||||
expect(filePath).toContain('aperant');
|
||||
expect(filePath).toContain('profiles.json');
|
||||
});
|
||||
});
|
||||
@@ -147,7 +147,7 @@ describe('profile-manager', () => {
|
||||
const filePath = writeFileCall?.[0];
|
||||
const content = writeFileCall?.[1];
|
||||
|
||||
expect(filePath).toContain('auto-claude');
|
||||
expect(filePath).toContain('aperant');
|
||||
expect(filePath).toContain('profiles.json');
|
||||
expect(content).toBe(JSON.stringify(mockData, null, 2));
|
||||
});
|
||||
|
||||
@@ -14,11 +14,11 @@ import * as lockfile from 'proper-lockfile';
|
||||
import type { APIProfile, ProfilesFile } from '@shared/types/profile';
|
||||
|
||||
/**
|
||||
* Get the path to profiles.json in the auto-claude directory
|
||||
* Get the path to profiles.json in the aperant directory
|
||||
*/
|
||||
export function getProfilesFilePath(): string {
|
||||
const userDataPath = app.getPath('userData');
|
||||
return path.join(userDataPath, 'auto-claude', 'profiles.json');
|
||||
return path.join(userDataPath, 'aperant', 'profiles.json');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -164,7 +164,7 @@ export class TaskLogService extends EventEmitter {
|
||||
*
|
||||
* @param specDir - Main project spec directory
|
||||
* @param projectPath - Optional: Project root path (needed to find worktree if not registered)
|
||||
* @param specsRelPath - Optional: Relative path to specs (e.g., "auto-claude/specs")
|
||||
* @param specsRelPath - Optional: Relative path to specs (e.g., "aperant/specs")
|
||||
* @param specId - Optional: Spec ID (needed to find worktree if not registered)
|
||||
*/
|
||||
loadLogs(specDir: string, projectPath?: string, specsRelPath?: string, specId?: string): TaskLogs | null {
|
||||
@@ -247,7 +247,7 @@ export class TaskLogService extends EventEmitter {
|
||||
* @param specId - The spec ID (e.g., "013-screenshots-on-tasks")
|
||||
* @param specDir - Main project spec directory
|
||||
* @param projectPath - Optional: Project root path (needed to find worktree)
|
||||
* @param specsRelPath - Optional: Relative path to specs (e.g., "auto-claude/specs")
|
||||
* @param specsRelPath - Optional: Relative path to specs (e.g., "aperant/specs")
|
||||
*/
|
||||
startWatching(specId: string, specDir: string, projectPath?: string, specsRelPath?: string): void {
|
||||
debugLog('[TaskLogService.startWatching] Starting watch:', {
|
||||
|
||||
@@ -151,7 +151,7 @@ export function detectWorktreeBranch(
|
||||
options: { timeout?: number; logPrefix?: string } = {}
|
||||
): WorktreeBranchDetectionResult {
|
||||
const { timeout = 30000, logPrefix = '[WORKTREE_BRANCH_DETECTION]' } = options;
|
||||
const expectedBranch = `auto-claude/${specId}`;
|
||||
const expectedBranch = `aperant/${specId}`;
|
||||
let branch = expectedBranch;
|
||||
let usingFallback = false;
|
||||
|
||||
|
||||
@@ -106,15 +106,15 @@ describe('profile-manager', () => {
|
||||
expect(result).toEqual(mockData);
|
||||
});
|
||||
|
||||
it('should use auto-claude directory for profiles.json path', async () => {
|
||||
it('should use aperant directory for profiles.json path', async () => {
|
||||
vi.mocked(fsPromises.readFile).mockRejectedValue(new Error('ENOENT'));
|
||||
|
||||
await loadProfilesFile();
|
||||
|
||||
// Verify the file path includes auto-claude
|
||||
// Verify the file path includes aperant
|
||||
const readFileCalls = vi.mocked(fsPromises.readFile).mock.calls;
|
||||
const filePath = readFileCalls[0]?.[0];
|
||||
expect(filePath).toContain('auto-claude');
|
||||
expect(filePath).toContain('aperant');
|
||||
expect(filePath).toContain('profiles.json');
|
||||
});
|
||||
});
|
||||
@@ -136,7 +136,7 @@ describe('profile-manager', () => {
|
||||
const filePath = writeFileCall?.[0];
|
||||
const content = writeFileCall?.[1];
|
||||
|
||||
expect(filePath).toContain('auto-claude');
|
||||
expect(filePath).toContain('aperant');
|
||||
expect(filePath).toContain('profiles.json');
|
||||
expect(content).toBe(JSON.stringify(mockData, null, 2));
|
||||
});
|
||||
|
||||
@@ -11,11 +11,11 @@ import { app } from 'electron';
|
||||
import type { ProfilesFile } from '../../shared/types/profile';
|
||||
|
||||
/**
|
||||
* Get the path to profiles.json in the auto-claude directory
|
||||
* Get the path to profiles.json in the aperant directory
|
||||
*/
|
||||
export function getProfilesFilePath(): string {
|
||||
const userDataPath = app.getPath('userData');
|
||||
return path.join(userDataPath, 'auto-claude', 'profiles.json');
|
||||
return path.join(userDataPath, 'aperant', 'profiles.json');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@ export class SpecNumberLock {
|
||||
|
||||
constructor(projectDir: string) {
|
||||
this.projectDir = projectDir;
|
||||
this.lockDir = path.join(projectDir, '.auto-claude', '.locks');
|
||||
this.lockDir = path.join(projectDir, '.aperant', '.locks');
|
||||
this.lockFile = path.join(this.lockDir, 'spec-numbering.lock');
|
||||
}
|
||||
|
||||
@@ -147,14 +147,14 @@ export class SpecNumberLock {
|
||||
let maxNumber = 0;
|
||||
|
||||
// Determine specs directory base path
|
||||
const specsBase = autoBuildPath || '.auto-claude';
|
||||
const specsBase = autoBuildPath || '.aperant';
|
||||
|
||||
// 1. Scan main project specs
|
||||
const mainSpecsDir = path.join(this.projectDir, specsBase, 'specs');
|
||||
maxNumber = Math.max(maxNumber, this.scanSpecsDir(mainSpecsDir));
|
||||
|
||||
// 2. Scan all worktree specs
|
||||
const worktreesDir = path.join(this.projectDir, '.auto-claude', 'worktrees', 'tasks');
|
||||
const worktreesDir = path.join(this.projectDir, '.aperant', 'worktrees', 'tasks');
|
||||
if (existsSync(worktreesDir)) {
|
||||
try {
|
||||
const worktrees = readdirSync(worktreesDir, { withFileTypes: true });
|
||||
|
||||
@@ -29,7 +29,7 @@ export function isValidTaskId(taskId: string): boolean {
|
||||
* A task can exist in multiple locations (main + worktree), so return all paths
|
||||
*
|
||||
* @param projectPath - The root path of the project
|
||||
* @param specsBaseDir - The relative path to specs directory (e.g., '.auto-claude/specs')
|
||||
* @param specsBaseDir - The relative path to specs directory (e.g., '.aperant/specs')
|
||||
* @param taskId - The task/spec ID to find
|
||||
* @param logPrefix - Optional prefix for log messages (defaults to '[SpecPathHelpers]')
|
||||
* @returns Array of absolute paths where the spec exists
|
||||
|
||||
@@ -85,8 +85,8 @@ function getWorktreeBranch(worktreePath: string, specId: string, timeout: number
|
||||
return explicitBranchName;
|
||||
}
|
||||
|
||||
// Fall back to the naming convention: auto-claude/{spec-id}
|
||||
return `auto-claude/${specId}`;
|
||||
// Fall back to the naming convention: aperant/{spec-id}
|
||||
return `aperant/${specId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +163,7 @@ async function deleteDirectoryWithRetry(
|
||||
* @example
|
||||
* ```typescript
|
||||
* const result = await cleanupWorktree({
|
||||
* worktreePath: 'C:/projects/my-app/.auto-claude/worktrees/tasks/001-feature',
|
||||
* worktreePath: 'C:/projects/my-app/.aperant/worktrees/tasks/001-feature',
|
||||
* projectPath: 'C:/projects/my-app',
|
||||
* specId: '001-feature',
|
||||
* logPrefix: '[TASK_DELETE]'
|
||||
@@ -191,7 +191,7 @@ export async function cleanupWorktree(options: WorktreeCleanupOptions): Promise<
|
||||
|
||||
// Security: Validate that worktreePath is within the expected worktree directories
|
||||
// This prevents path traversal attacks and accidental deletion of wrong directories
|
||||
// Supports both task worktrees (.auto-claude/worktrees/tasks) and terminal worktrees (.auto-claude/worktrees/terminal)
|
||||
// Supports both task worktrees (.aperant/worktrees/tasks) and terminal worktrees (.aperant/worktrees/terminal)
|
||||
const taskBase = getTaskWorktreeDir(projectPath);
|
||||
const terminalBase = getTerminalWorktreeDir(projectPath);
|
||||
const isValidPath = isPathWithinBase(worktreePath, taskBase) || isPathWithinBase(worktreePath, terminalBase);
|
||||
|
||||
@@ -9,11 +9,11 @@ import path from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
// Path constants for worktree directories
|
||||
export const TASK_WORKTREE_DIR = '.auto-claude/worktrees/tasks';
|
||||
export const TERMINAL_WORKTREE_DIR = '.auto-claude/worktrees/terminal';
|
||||
export const TASK_WORKTREE_DIR = '.aperant/worktrees/tasks';
|
||||
export const TERMINAL_WORKTREE_DIR = '.aperant/worktrees/terminal';
|
||||
|
||||
// Metadata directories (separate from git worktrees to avoid uncommitted files)
|
||||
export const TERMINAL_WORKTREE_METADATA_DIR = '.auto-claude/terminal/metadata';
|
||||
export const TERMINAL_WORKTREE_METADATA_DIR = '.aperant/terminal/metadata';
|
||||
|
||||
// Legacy path for backwards compatibility
|
||||
export const LEGACY_WORKTREE_DIR = '.worktrees';
|
||||
|
||||
@@ -30,6 +30,8 @@ export interface ProjectAPI {
|
||||
) => Promise<IPCResult>;
|
||||
initializeProject: (projectId: string) => Promise<IPCResult<InitializationResult>>;
|
||||
checkProjectVersion: (projectId: string) => Promise<IPCResult<AutoBuildVersionInfo>>;
|
||||
needsMigration: (projectPath: string) => Promise<boolean>;
|
||||
migrateProject: (projectPath: string) => Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
// Tab State (persisted in main process for reliability)
|
||||
getTabState: () => Promise<IPCResult<TabState>>;
|
||||
@@ -158,6 +160,12 @@ export const createProjectAPI = (): ProjectAPI => ({
|
||||
checkProjectVersion: (projectId: string): Promise<IPCResult<AutoBuildVersionInfo>> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.PROJECT_CHECK_VERSION, projectId),
|
||||
|
||||
needsMigration: (projectPath: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke('project:needs-migration', projectPath),
|
||||
|
||||
migrateProject: (projectPath: string): Promise<{ success: boolean; error?: string }> =>
|
||||
ipcRenderer.invoke('project:migrate', projectPath),
|
||||
|
||||
// Tab State (persisted in main process for reliability)
|
||||
getTabState: (): Promise<IPCResult<TabState>> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.TAB_STATE_GET),
|
||||
|
||||
@@ -155,6 +155,12 @@ export function App() {
|
||||
const [skippedInitProjectId, setSkippedInitProjectId] = useState<string | null>(null);
|
||||
const [showAddProjectModal, setShowAddProjectModal] = useState(false);
|
||||
|
||||
// Migration dialog state
|
||||
const [showMigrateDialog, setShowMigrateDialog] = useState(false);
|
||||
const [migrationProject, setMigrationProject] = useState<Project | null>(null);
|
||||
const [isMigrating, setIsMigrating] = useState(false);
|
||||
const [migrateError, setMigrateError] = useState<string | null>(null);
|
||||
|
||||
// GitHub setup state (shown after Auto Claude init)
|
||||
const [showGitHubSetup, setShowGitHubSetup] = useState(false);
|
||||
const [gitHubSetupProject, setGitHubSetupProject] = useState<Project | null>(null);
|
||||
@@ -372,7 +378,7 @@ export function App() {
|
||||
setInitError(null);
|
||||
}, []);
|
||||
|
||||
// Check if selected project needs initialization (e.g., .auto-claude folder was deleted)
|
||||
// Check if selected project needs initialization (e.g., .aperant folder was deleted)
|
||||
useEffect(() => {
|
||||
// Don't show dialog while initialization is in progress
|
||||
if (isInitializing) return;
|
||||
@@ -411,6 +417,18 @@ export function App() {
|
||||
const project = await addProject(path);
|
||||
if (project) {
|
||||
openProjectTab(project.id);
|
||||
// Check migration before init
|
||||
try {
|
||||
const requiresMigration = await window.electronAPI.needsMigration(project.path);
|
||||
if (requiresMigration) {
|
||||
setMigrationProject(project);
|
||||
setMigrateError(null);
|
||||
setShowMigrateDialog(true);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Non-critical - fall through to init check
|
||||
}
|
||||
if (!project.autoBuildPath) {
|
||||
setPendingProject(project);
|
||||
setInitError(null);
|
||||
@@ -639,8 +657,22 @@ export function App() {
|
||||
setShowAddProjectModal(true);
|
||||
};
|
||||
|
||||
const handleProjectAdded = (project: Project, needsInit: boolean) => {
|
||||
const handleProjectAdded = async (project: Project, needsInit: boolean) => {
|
||||
openProjectTab(project.id);
|
||||
|
||||
// Check for migration before showing init dialog
|
||||
try {
|
||||
const requiresMigration = await window.electronAPI.needsMigration(project.path);
|
||||
if (requiresMigration) {
|
||||
setMigrationProject(project);
|
||||
setMigrateError(null);
|
||||
setShowMigrateDialog(true);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[App] Failed to check migration status:', error);
|
||||
}
|
||||
|
||||
if (needsInit) {
|
||||
setPendingProject(project);
|
||||
setInitError(null);
|
||||
@@ -713,6 +745,34 @@ export function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMigrate = async () => {
|
||||
if (!migrationProject) return;
|
||||
|
||||
setIsMigrating(true);
|
||||
setMigrateError(null);
|
||||
try {
|
||||
const result = await window.electronAPI.migrateProject(migrationProject.path);
|
||||
if (result.success) {
|
||||
setShowMigrateDialog(false);
|
||||
// Refresh projects so the updated autoBuildPath is reflected
|
||||
await loadProjects();
|
||||
setMigrationProject(null);
|
||||
} else {
|
||||
setMigrateError(result.error || t('initialize.migrateError'));
|
||||
}
|
||||
} catch (error) {
|
||||
setMigrateError(error instanceof Error ? error.message : t('initialize.migrateError'));
|
||||
} finally {
|
||||
setIsMigrating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSkipMigrate = () => {
|
||||
setShowMigrateDialog(false);
|
||||
setMigrationProject(null);
|
||||
setMigrateError(null);
|
||||
};
|
||||
|
||||
const handleInitialize = async () => {
|
||||
if (!pendingProject) return;
|
||||
|
||||
@@ -1014,6 +1074,51 @@ export function App() {
|
||||
onProjectAdded={handleProjectAdded}
|
||||
/>
|
||||
|
||||
{/* Migrate Project Data Dialog - shown when .auto-claude exists but .aperant does not */}
|
||||
<Dialog open={showMigrateDialog} onOpenChange={(open) => {
|
||||
if (!open && !isMigrating) {
|
||||
handleSkipMigrate();
|
||||
}
|
||||
}}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RefreshCw className="h-5 w-5" />
|
||||
{t('initialize.migrateTitle')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('initialize.migrateDescription')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{migrateError && (
|
||||
<div className="mt-2 rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-sm">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-4 w-4 text-destructive mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-destructive">{t('initialize.migrateError')}</p>
|
||||
<p className="text-muted-foreground mt-1">{migrateError}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleSkipMigrate} disabled={isMigrating}>
|
||||
{t('common:buttons.skip', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button onClick={handleMigrate} disabled={isMigrating}>
|
||||
{isMigrating ? (
|
||||
<>
|
||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||
{t('initialize.migrateAction')}
|
||||
</>
|
||||
) : (
|
||||
t('initialize.migrateAction')
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Initialize Auto Claude Dialog */}
|
||||
<Dialog open={showInitDialog} onOpenChange={(open) => {
|
||||
console.warn('[InitDialog] onOpenChange called', { open, pendingProject: !!pendingProject, isInitializing, initSuccess });
|
||||
|
||||
@@ -29,7 +29,7 @@ function createTestProject(overrides: Partial<Project> = {}): Project {
|
||||
id: `project-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
||||
name: 'Test Project',
|
||||
path: '/path/to/test-project',
|
||||
autoBuildPath: '.auto-claude',
|
||||
autoBuildPath: '.aperant',
|
||||
settings: defaultSettings,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
|
||||
@@ -152,7 +152,7 @@ const AGENT_CONFIGS: Record<string, AgentConfig> = {
|
||||
description: 'Creates implementation plan with subtasks',
|
||||
category: 'build',
|
||||
tools: ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'Bash', 'WebFetch', 'WebSearch'],
|
||||
mcp_servers: ['context7', 'memory', 'auto-claude'],
|
||||
mcp_servers: ['context7', 'memory', 'aperant'],
|
||||
mcp_optional: ['linear'],
|
||||
settingsSource: { type: 'phase', phase: 'planning' },
|
||||
},
|
||||
@@ -161,7 +161,7 @@ const AGENT_CONFIGS: Record<string, AgentConfig> = {
|
||||
description: 'Implements individual subtasks',
|
||||
category: 'build',
|
||||
tools: ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'Bash', 'WebFetch', 'WebSearch'],
|
||||
mcp_servers: ['context7', 'memory', 'auto-claude'],
|
||||
mcp_servers: ['context7', 'memory', 'aperant'],
|
||||
mcp_optional: ['linear'],
|
||||
settingsSource: { type: 'phase', phase: 'coding' },
|
||||
},
|
||||
@@ -172,7 +172,7 @@ const AGENT_CONFIGS: Record<string, AgentConfig> = {
|
||||
description: 'Validates acceptance criteria. Uses Electron or Puppeteer based on project type.',
|
||||
category: 'qa',
|
||||
tools: ['Read', 'Glob', 'Grep', 'Bash', 'WebFetch', 'WebSearch'],
|
||||
mcp_servers: ['context7', 'memory', 'auto-claude'],
|
||||
mcp_servers: ['context7', 'memory', 'aperant'],
|
||||
mcp_optional: ['linear', 'electron', 'puppeteer'],
|
||||
settingsSource: { type: 'phase', phase: 'qa' },
|
||||
},
|
||||
@@ -181,7 +181,7 @@ const AGENT_CONFIGS: Record<string, AgentConfig> = {
|
||||
description: 'Fixes QA-reported issues. Uses Electron or Puppeteer based on project type.',
|
||||
category: 'qa',
|
||||
tools: ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'Bash', 'WebFetch', 'WebSearch'],
|
||||
mcp_servers: ['context7', 'memory', 'auto-claude'],
|
||||
mcp_servers: ['context7', 'memory', 'aperant'],
|
||||
mcp_optional: ['linear', 'electron', 'puppeteer'],
|
||||
settingsSource: { type: 'phase', phase: 'qa' },
|
||||
},
|
||||
@@ -286,17 +286,17 @@ const MCP_SERVERS: Record<string, { name: string; description: string; icon: Rea
|
||||
'mcp__graphiti-memory__get_entity_edge',
|
||||
],
|
||||
},
|
||||
'auto-claude': {
|
||||
'aperant': {
|
||||
name: 'Aperant Tools',
|
||||
description: 'Build progress tracking, session context, discoveries & gotchas recording',
|
||||
icon: ListChecks,
|
||||
tools: [
|
||||
'mcp__auto-claude__update_subtask_status',
|
||||
'mcp__auto-claude__get_build_progress',
|
||||
'mcp__auto-claude__record_discovery',
|
||||
'mcp__auto-claude__record_gotcha',
|
||||
'mcp__auto-claude__get_session_context',
|
||||
'mcp__auto-claude__update_qa_status',
|
||||
'mcp__aperant__update_subtask_status',
|
||||
'mcp__aperant__get_build_progress',
|
||||
'mcp__aperant__record_discovery',
|
||||
'mcp__aperant__record_gotcha',
|
||||
'mcp__aperant__get_session_context',
|
||||
'mcp__aperant__update_qa_status',
|
||||
],
|
||||
},
|
||||
linear: {
|
||||
@@ -347,7 +347,7 @@ const ALL_MCP_SERVERS = [
|
||||
'linear',
|
||||
'electron',
|
||||
'puppeteer',
|
||||
'auto-claude'
|
||||
'aperant'
|
||||
] as const;
|
||||
|
||||
// Category metadata - neutral styling per design.json
|
||||
@@ -430,7 +430,7 @@ function AgentCard({ id, config, modelLabel, thinkingLabel, overrides, mcpServer
|
||||
const customServerIds = customServers.map(s => s.id);
|
||||
const allAvailableMcpIds = [...ALL_MCP_SERVERS, ...customServerIds];
|
||||
const availableMcps = allAvailableMcpIds.filter(
|
||||
mcp => !effectiveMcps.includes(mcp) && !removedMcps.includes(mcp) && mcp !== 'auto-claude'
|
||||
mcp => !effectiveMcps.includes(mcp) && !removedMcps.includes(mcp) && mcp !== 'aperant'
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -495,7 +495,7 @@ function AgentCard({ id, config, modelLabel, thinkingLabel, overrides, mcpServer
|
||||
const serverInfo = allMcpServers[server];
|
||||
const ServerIcon = serverInfo?.icon || Server;
|
||||
const isAdded = isCustomAdd(server);
|
||||
const canRemove = server !== 'auto-claude';
|
||||
const canRemove = server !== 'aperant';
|
||||
|
||||
return (
|
||||
<div key={server} className="flex items-center justify-between group">
|
||||
|
||||
@@ -14,7 +14,7 @@ function createTestProject(overrides: Partial<Project> = {}): Project {
|
||||
id: `project-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
||||
name: 'Test Project',
|
||||
path: '/path/to/test-project',
|
||||
autoBuildPath: '/path/to/test-project/.auto-claude',
|
||||
autoBuildPath: '/path/to/test-project/.aperant',
|
||||
settings: {
|
||||
model: 'claude-3-haiku-20240307',
|
||||
memoryBackend: 'file',
|
||||
|
||||
@@ -14,7 +14,7 @@ function createTestProject(overrides: Partial<Project> = {}): Project {
|
||||
id: `project-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
||||
name: 'Test Project',
|
||||
path: '/path/to/test-project',
|
||||
autoBuildPath: '/path/to/test-project/.auto-claude',
|
||||
autoBuildPath: '/path/to/test-project/.aperant',
|
||||
settings: {
|
||||
model: 'claude-3-haiku-20240307',
|
||||
memoryBackend: 'file',
|
||||
|
||||
@@ -166,7 +166,7 @@ export function MemoriesTab({
|
||||
<>
|
||||
<div className="grid gap-3 sm:grid-cols-2 text-sm">
|
||||
<InfoItem label={t('memory.info.database')} value={memoryStatus.database || 'auto_claude_memory'} />
|
||||
<InfoItem label={t('memory.info.path')} value={memoryStatus.dbPath || '~/.auto-claude/memories'} />
|
||||
<InfoItem label={t('memory.info.path')} value={memoryStatus.dbPath || '~/.aperant/memories'} />
|
||||
{memoryStatus.embeddingProvider && (
|
||||
<InfoItem label={t('memory.info.embedding')} value={memoryStatus.embeddingProvider} />
|
||||
)}
|
||||
|
||||
@@ -126,7 +126,7 @@ export function PRDetail({
|
||||
// a hybrid push/pull architecture:
|
||||
//
|
||||
// Backend (PRLogCollector in pr-handlers.ts):
|
||||
// - Writes logs to disk every 3 entries: .auto-claude/github/pr/logs_${prNumber}.json
|
||||
// - Writes logs to disk every 3 entries: .aperant/github/pr/logs_${prNumber}.json
|
||||
// - Emits GITHUB_PR_LOGS_UPDATED IPC events after each save
|
||||
// - Tracks phase status: pending → active → completed/failed
|
||||
//
|
||||
|
||||
@@ -907,7 +907,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) {
|
||||
disabled={isSaving || isValidating}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Stored in ~/.auto-claude/graphs/
|
||||
Stored in ~/.aperant/graphs/
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ export function MemoryBackendSection({
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-foreground">Database Name</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Name for the memory database (stored in ~/.auto-claude/memories/)
|
||||
Name for the memory database (stored in ~/.aperant/memories/)
|
||||
</p>
|
||||
<Input
|
||||
placeholder="auto_claude_memory"
|
||||
@@ -139,10 +139,10 @@ export function MemoryBackendSection({
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-foreground">Database Path (Optional)</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Custom storage location. Default: ~/.auto-claude/memories/
|
||||
Custom storage location. Default: ~/.aperant/memories/
|
||||
</p>
|
||||
<Input
|
||||
placeholder="~/.auto-claude/memories"
|
||||
placeholder="~/.aperant/memories"
|
||||
value={envConfig.memoryDbPath || ''}
|
||||
onChange={(e) => onUpdateConfig({ memoryDbPath: e.target.value || undefined })}
|
||||
/>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user