Compare commits
5 Commits
develop
...
fix/rename
| Author | SHA1 | Date | |
|---|---|---|---|
| cba050243e | |||
| baf6e4bb35 | |||
| 4c832e6a35 | |||
| f6bf6d4ada | |||
| 18453adb1d |
@@ -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)
|
||||
```
|
||||
|
||||
@@ -231,9 +231,9 @@ test.describe('E2E Flow Verification (Mock-based)', () => {
|
||||
const projectPath = TEST_PROJECT_DIR;
|
||||
expect(existsSync(projectPath)).toBe(true);
|
||||
|
||||
// Check for auto-claude directory detection
|
||||
const autoBuildPath = path.join(projectPath, 'auto-claude');
|
||||
expect(existsSync(autoBuildPath)).toBe(true);
|
||||
// Check for aperant directory detection
|
||||
const aperantPath = path.join(projectPath, '.aperant');
|
||||
expect(existsSync(aperantPath)).toBe(true);
|
||||
|
||||
cleanupTestEnvironment();
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ if (typeof global.requestAnimationFrame === 'undefined') {
|
||||
}
|
||||
|
||||
// Test data directory for isolated file operations
|
||||
export const TEST_DATA_DIR = '/tmp/auto-claude-ui-tests';
|
||||
export const TEST_DATA_DIR = '/tmp/aperant-ui-tests';
|
||||
|
||||
// Create fresh test directory before each test
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('InsightsConfig', () => {
|
||||
|
||||
it('should build process env with profile settings', async () => {
|
||||
const config = new InsightsConfig();
|
||||
vi.spyOn(config, 'loadAutoBuildEnv').mockReturnValue({ CUSTOM_ENV: '1' });
|
||||
vi.spyOn(config, 'loadAperantEnv').mockReturnValue({ CUSTOM_ENV: '1' });
|
||||
|
||||
const env = await config.getProcessEnv();
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Tests for needsMigration() and migrateProject() in project-initializer.ts
|
||||
*/
|
||||
|
||||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// ---- fs mock ----
|
||||
const mockExistingPaths = new Set<string>();
|
||||
const mockFiles = new Map<string, string>();
|
||||
|
||||
vi.mock('fs', () => {
|
||||
const existsSync = vi.fn((p: string) => mockExistingPaths.has(p));
|
||||
|
||||
const renameSync = vi.fn((oldPath: string, newPath: string) => {
|
||||
// Simulate rename: remove old, add new
|
||||
mockExistingPaths.delete(oldPath);
|
||||
mockExistingPaths.add(newPath);
|
||||
});
|
||||
|
||||
const readFileSync = vi.fn((filePath: string, _encoding?: string): string => {
|
||||
const content = mockFiles.get(filePath);
|
||||
if (content === undefined) {
|
||||
const err = new Error(`ENOENT: no such file or directory, open '${filePath}'`) as NodeJS.ErrnoException;
|
||||
err.code = 'ENOENT';
|
||||
throw err;
|
||||
}
|
||||
return content;
|
||||
});
|
||||
|
||||
const writeFileSync = vi.fn((filePath: string, content: string) => {
|
||||
mockFiles.set(filePath, content);
|
||||
});
|
||||
|
||||
const appendFileSync = vi.fn((filePath: string, content: string) => {
|
||||
const existing = mockFiles.get(filePath) ?? '';
|
||||
mockFiles.set(filePath, existing + content);
|
||||
});
|
||||
|
||||
const mkdirSync = vi.fn();
|
||||
|
||||
return {
|
||||
default: { existsSync, renameSync, readFileSync, writeFileSync, appendFileSync, mkdirSync },
|
||||
existsSync,
|
||||
renameSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
appendFileSync,
|
||||
mkdirSync,
|
||||
};
|
||||
});
|
||||
|
||||
// ---- stub heavy transitive deps ----
|
||||
vi.mock('child_process', () => ({
|
||||
execFileSync: vi.fn(() => ''),
|
||||
}));
|
||||
|
||||
vi.mock('../cli-tool-manager', () => ({
|
||||
getToolPath: vi.fn(() => 'git'),
|
||||
}));
|
||||
|
||||
// ---- import after mocks ----
|
||||
import { needsMigration, migrateProject } from '../project-initializer';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const PROJECT = '/test/project';
|
||||
const OLD_PATH = path.join(PROJECT, '.auto-claude');
|
||||
const NEW_PATH = path.join(PROJECT, '.aperant');
|
||||
const GITIGNORE = path.join(PROJECT, '.gitignore');
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockExistingPaths.clear();
|
||||
mockFiles.clear();
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// needsMigration()
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
describe('needsMigration', () => {
|
||||
test('returns true when .auto-claude exists and .aperant does not', () => {
|
||||
mockExistingPaths.add(OLD_PATH);
|
||||
expect(needsMigration(PROJECT)).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false when .aperant already exists', () => {
|
||||
mockExistingPaths.add(OLD_PATH);
|
||||
mockExistingPaths.add(NEW_PATH);
|
||||
expect(needsMigration(PROJECT)).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false when neither exists', () => {
|
||||
expect(needsMigration(PROJECT)).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false when both exist', () => {
|
||||
mockExistingPaths.add(OLD_PATH);
|
||||
mockExistingPaths.add(NEW_PATH);
|
||||
expect(needsMigration(PROJECT)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// migrateProject()
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
describe('migrateProject', () => {
|
||||
test('successfully renames .auto-claude to .aperant', () => {
|
||||
mockExistingPaths.add(OLD_PATH);
|
||||
|
||||
const result = migrateProject(PROJECT);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(fs.renameSync).toHaveBeenCalledWith(OLD_PATH, NEW_PATH);
|
||||
});
|
||||
|
||||
test('returns error when .auto-claude does not exist', () => {
|
||||
// OLD_PATH not in mockExistingPaths
|
||||
|
||||
const result = migrateProject(PROJECT);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toMatch(/No \.auto-claude directory/i);
|
||||
expect(fs.renameSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('returns error when .aperant already exists', () => {
|
||||
mockExistingPaths.add(OLD_PATH);
|
||||
mockExistingPaths.add(NEW_PATH);
|
||||
|
||||
const result = migrateProject(PROJECT);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toMatch(/\.aperant directory already exists/i);
|
||||
expect(fs.renameSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('updates .gitignore entries during migration', () => {
|
||||
mockExistingPaths.add(OLD_PATH);
|
||||
mockFiles.set(GITIGNORE, '.auto-claude/\n.auto-claude-security.json\n.auto-claude-status\n');
|
||||
|
||||
const result = migrateProject(PROJECT);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(fs.writeFileSync).toHaveBeenCalled();
|
||||
// Find the call that wrote to the gitignore
|
||||
const gitignoreWrite = (fs.writeFileSync as ReturnType<typeof vi.fn>).mock.calls.find(
|
||||
(call: unknown[]) => call[0] === GITIGNORE
|
||||
);
|
||||
expect(gitignoreWrite).toBeDefined();
|
||||
const writtenContent = gitignoreWrite![1] as string;
|
||||
expect(writtenContent).toContain('.aperant/');
|
||||
expect(writtenContent).not.toContain('.auto-claude/');
|
||||
});
|
||||
|
||||
test('handles .gitignore not existing gracefully', () => {
|
||||
mockExistingPaths.add(OLD_PATH);
|
||||
// mockFiles has no GITIGNORE entry → readFileSync throws ENOENT
|
||||
|
||||
// Should not throw; the catch block swallows the .gitignore read error,
|
||||
// then ensureGitignoreEntries creates the file via writeFileSync
|
||||
const result = migrateProject(PROJECT);
|
||||
expect(result.success).toBe(true);
|
||||
// ensureGitignoreEntries creates a new .gitignore with .aperant/
|
||||
const written = (fs.writeFileSync as ReturnType<typeof vi.fn>).mock.calls.find(
|
||||
(call: unknown[]) => call[0] === GITIGNORE
|
||||
);
|
||||
expect(written).toBeDefined();
|
||||
expect(written![1] as string).toContain('.aperant/');
|
||||
});
|
||||
});
|
||||
@@ -88,25 +88,25 @@ 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.aperantPath).toBe('.aperant');
|
||||
});
|
||||
|
||||
it('should set empty autoBuildPath if not present', async () => {
|
||||
it('should set empty aperantPath if not present', async () => {
|
||||
const { ProjectStore } = await import('../project-store');
|
||||
const store = new ProjectStore();
|
||||
|
||||
const project = store.addProject(TEST_PROJECT_PATH);
|
||||
|
||||
expect(project.autoBuildPath).toBe('');
|
||||
expect(project.aperantPath).toBe('');
|
||||
});
|
||||
|
||||
it('should persist project to disk', 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';
|
||||
@@ -587,7 +587,7 @@ describe('ProjectStore', () => {
|
||||
id: 'test-id-123',
|
||||
name: 'Preexisting Project',
|
||||
path: '/test/path',
|
||||
autoBuildPath: '',
|
||||
aperantPath: '',
|
||||
settings: {
|
||||
model: 'sonnet',
|
||||
memoryBackend: 'memory',
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from './types';
|
||||
import type { IdeationConfig } from '../../shared/types';
|
||||
import { resetStuckSubtasks } from '../ipc-handlers/task/plan-file-utils';
|
||||
import { AUTO_BUILD_PATHS, getSpecsDir } from '../../shared/constants';
|
||||
import { APERANT_PATHS, getSpecsDir } from '../../shared/constants';
|
||||
import { projectStore } from '../project-store';
|
||||
import { resolveAuth, resolveAuthFromQueue } from '../ai/auth/resolver';
|
||||
import { resolveModelId } from '../ai/config/phase-config';
|
||||
@@ -112,10 +112,10 @@ export class AgentManager extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure paths for Python and auto-claude source
|
||||
* Configure paths for Python and aperant source
|
||||
*/
|
||||
configure(pythonPath?: string, autoBuildSourcePath?: string): void {
|
||||
this.processManager.configure(pythonPath, autoBuildSourcePath);
|
||||
configure(pythonPath?: string, aperantSourcePath?: string): void {
|
||||
this.processManager.configure(pythonPath, aperantSourcePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,11 +216,11 @@ export class AgentManager extends EventEmitter {
|
||||
|
||||
// Scan each project for stuck subtasks
|
||||
for (const project of projects) {
|
||||
if (!project.autoBuildPath) {
|
||||
if (!project.aperantPath) {
|
||||
continue; // Skip projects that haven't been initialized yet
|
||||
}
|
||||
|
||||
const specsDir = path.join(project.path, getSpecsDir(project.autoBuildPath));
|
||||
const specsDir = path.join(project.path, getSpecsDir(project.aperantPath));
|
||||
|
||||
// Check if specs directory exists
|
||||
if (!existsSync(specsDir)) {
|
||||
@@ -235,7 +235,7 @@ export class AgentManager extends EventEmitter {
|
||||
|
||||
// Process each spec directory
|
||||
for (const specDirName of specDirs) {
|
||||
const planPath = path.join(specsDir, specDirName, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN);
|
||||
const planPath = path.join(specsDir, specDirName, APERANT_PATHS.IMPLEMENTATION_PLAN);
|
||||
|
||||
// Check if implementation_plan.json exists
|
||||
if (!existsSync(planPath)) {
|
||||
@@ -336,7 +336,7 @@ export class AgentManager extends EventEmitter {
|
||||
|
||||
// Reset stuck subtasks if restarting an existing spec creation task
|
||||
if (specDir) {
|
||||
const planPath = path.join(specDir, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN);
|
||||
const planPath = path.join(specDir, APERANT_PATHS.IMPLEMENTATION_PLAN);
|
||||
console.log('[AgentManager] Resetting stuck subtasks before spec creation restart:', planPath);
|
||||
try {
|
||||
const { success, resetCount } = await resetStuckSubtasks(planPath);
|
||||
@@ -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,
|
||||
@@ -456,7 +456,7 @@ export class AgentManager extends EventEmitter {
|
||||
|
||||
// Resolve the spec directory from specId
|
||||
const project = projectStore.getProjects().find((p) => p.id === projectId || p.path === projectPath);
|
||||
const specsBaseDir = getSpecsDir(project?.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project?.aperantPath);
|
||||
const specDir = path.join(projectPath, specsBaseDir, specId);
|
||||
|
||||
// Load model configuration from task_metadata.json if available
|
||||
@@ -483,7 +483,7 @@ export class AgentManager extends EventEmitter {
|
||||
baseBranch,
|
||||
options.useLocalBranch ?? false,
|
||||
project?.settings?.pushNewBranches !== false,
|
||||
project?.autoBuildPath,
|
||||
project?.aperantPath,
|
||||
);
|
||||
worktreePath = result.worktreePath;
|
||||
// Spec dir in the worktree (spec files were copied by createOrGetWorktree)
|
||||
@@ -578,7 +578,7 @@ export class AgentManager extends EventEmitter {
|
||||
|
||||
// Resolve the spec directory from specId
|
||||
const project = projectStore.getProjects().find((p) => p.id === projectId || p.path === projectPath);
|
||||
const specsBaseDir = getSpecsDir(project?.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project?.aperantPath);
|
||||
const specDir = path.join(projectPath, specsBaseDir, specId);
|
||||
|
||||
// Load model configuration from task_metadata.json if available
|
||||
@@ -820,8 +820,8 @@ export class AgentManager extends EventEmitter {
|
||||
// Reset stuck subtasks before restart to avoid picking up stale in-progress states
|
||||
if (context.specId || context.specDir) {
|
||||
const planPath = context.specDir
|
||||
? path.join(context.specDir, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN)
|
||||
: path.join(context.projectPath, AUTO_BUILD_PATHS.SPECS_DIR, context.specId, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN);
|
||||
? path.join(context.specDir, APERANT_PATHS.IMPLEMENTATION_PLAN)
|
||||
: path.join(context.projectPath, APERANT_PATHS.SPECS_DIR, context.specId, APERANT_PATHS.IMPLEMENTATION_PLAN);
|
||||
|
||||
console.log('[AgentManager] Resetting stuck subtasks before restart:', planPath);
|
||||
try {
|
||||
|
||||
@@ -48,7 +48,7 @@ vi.mock('child_process', async (importOriginal) => {
|
||||
|
||||
// Mock project-initializer to avoid child_process.execSync issues
|
||||
vi.mock('../project-initializer', () => ({
|
||||
getAutoBuildPath: vi.fn(() => '/fake/auto-build'),
|
||||
getAperantPath: vi.fn(() => '/fake/aperant'),
|
||||
isInitialized: vi.fn(() => true),
|
||||
initializeProject: vi.fn(),
|
||||
getProjectStorePath: vi.fn(() => '/fake/store/path')
|
||||
@@ -126,7 +126,7 @@ vi.mock('../env-utils', () => ({
|
||||
getAugmentedEnv: vi.fn(() => ({ ...process.env }))
|
||||
}));
|
||||
|
||||
// Mock fs.existsSync for getAutoBuildSourcePath path validation
|
||||
// Mock fs.existsSync for getAperantSourcePath path validation
|
||||
vi.mock('fs', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('fs')>();
|
||||
return {
|
||||
@@ -135,10 +135,10 @@ vi.mock('fs', async (importOriginal) => {
|
||||
// Normalize path separators for cross-platform compatibility
|
||||
// path.join() uses backslashes on Windows, so we normalize to forward slashes
|
||||
const normalizedPath = inputPath.replace(/\\/g, '/');
|
||||
// Return true for the fake auto-build path and its expected files
|
||||
if (normalizedPath === '/fake/auto-build' ||
|
||||
normalizedPath === '/fake/auto-build/runners' ||
|
||||
normalizedPath === '/fake/auto-build/runners/spec_runner.py') {
|
||||
// Return true for the fake aperant path and its expected files
|
||||
if (normalizedPath === '/fake/aperant' ||
|
||||
normalizedPath === '/fake/aperant/runners' ||
|
||||
normalizedPath === '/fake/aperant/runners/spec_runner.py') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -102,7 +102,7 @@ export class AgentProcessManager {
|
||||
private state: AgentState;
|
||||
private events: AgentEvents;
|
||||
private emitter: EventEmitter;
|
||||
private autoBuildSourcePath: string = '';
|
||||
private aperantSourcePath: string = '';
|
||||
|
||||
constructor(state: AgentState, events: AgentEvents, emitter: EventEmitter) {
|
||||
this.state = state;
|
||||
@@ -110,14 +110,14 @@ export class AgentProcessManager {
|
||||
this.emitter = emitter;
|
||||
}
|
||||
|
||||
configure(_pythonPath?: string, autoBuildSourcePath?: string): void {
|
||||
if (autoBuildSourcePath) {
|
||||
this.autoBuildSourcePath = autoBuildSourcePath;
|
||||
configure(_pythonPath?: string, aperantSourcePath?: string): void {
|
||||
if (aperantSourcePath) {
|
||||
this.aperantSourcePath = aperantSourcePath;
|
||||
}
|
||||
}
|
||||
|
||||
getAutoBuildSourcePath(): string {
|
||||
return this.autoBuildSourcePath;
|
||||
getAperantSourcePath(): string {
|
||||
return this.aperantSourcePath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -493,31 +493,31 @@ 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> {
|
||||
// Find project by path to get autoBuildPath
|
||||
// Find project by path to get aperantPath
|
||||
const projects = projectStore.getProjects();
|
||||
const project = projects.find((p) => p.path === projectPath);
|
||||
|
||||
if (!project?.autoBuildPath) {
|
||||
if (!project?.aperantPath) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const envPath = path.join(projectPath, project.autoBuildPath, '.env');
|
||||
const envPath = path.join(projectPath, project.aperantPath, '.env');
|
||||
return this.parseEnvFile(envPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment variables from auto-claude .env file
|
||||
* Load environment variables from aperant .env file
|
||||
*/
|
||||
loadAutoBuildEnv(): Record<string, string> {
|
||||
if (!this.autoBuildSourcePath) {
|
||||
loadAperantEnv(): Record<string, string> {
|
||||
if (!this.aperantSourcePath) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const envPath = path.join(this.autoBuildSourcePath, '.env');
|
||||
const envPath = path.join(this.aperantSourcePath, '.env');
|
||||
return this.parseEnvFile(envPath);
|
||||
}
|
||||
|
||||
@@ -1036,13 +1036,13 @@ 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> {
|
||||
const autoBuildEnv = this.loadAutoBuildEnv();
|
||||
const aperantEnv = this.loadAperantEnv();
|
||||
const projectFileEnv = this.loadProjectEnv(projectPath);
|
||||
const projectSettingsEnv = this.getProjectEnvVars(projectPath);
|
||||
return { ...autoBuildEnv, ...projectFileEnv, ...projectSettingsEnv };
|
||||
return { ...aperantEnv, ...projectFileEnv, ...projectSettingsEnv };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { AgentEvents } from './agent-events';
|
||||
import { AgentProcessManager } from './agent-process';
|
||||
import { RoadmapConfig } from './types';
|
||||
import type { IdeationConfig, Idea } from '../../shared/types';
|
||||
import { AUTO_BUILD_PATHS } from '../../shared/constants';
|
||||
import { APERANT_PATHS } from '../../shared/constants';
|
||||
import { detectRateLimit, createSDKRateLimitInfo } from '../rate-limit-detector';
|
||||
import { debugLog, debugError } from '../../shared/utils/debug-logger';
|
||||
import { transformIdeaFromSnakeCase, transformSessionFromSnakeCase } from '../ipc-handlers/ideation/transformers';
|
||||
@@ -83,8 +83,8 @@ export class AgentQueueManager {
|
||||
isRunning: boolean
|
||||
): Promise<void> {
|
||||
try {
|
||||
const roadmapDir = path.join(projectPath, AUTO_BUILD_PATHS.ROADMAP_DIR);
|
||||
const progressPath = path.join(roadmapDir, AUTO_BUILD_PATHS.GENERATION_PROGRESS);
|
||||
const roadmapDir = path.join(projectPath, APERANT_PATHS.ROADMAP_DIR);
|
||||
const progressPath = path.join(roadmapDir, APERANT_PATHS.GENERATION_PROGRESS);
|
||||
|
||||
// Ensure roadmap directory exists
|
||||
if (!existsSync(roadmapDir)) {
|
||||
@@ -120,8 +120,8 @@ export class AgentQueueManager {
|
||||
try {
|
||||
const progressPath = path.join(
|
||||
projectPath,
|
||||
AUTO_BUILD_PATHS.ROADMAP_DIR,
|
||||
AUTO_BUILD_PATHS.GENERATION_PROGRESS
|
||||
APERANT_PATHS.ROADMAP_DIR,
|
||||
APERANT_PATHS.GENERATION_PROGRESS
|
||||
);
|
||||
|
||||
if (existsSync(progressPath)) {
|
||||
@@ -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');
|
||||
|
||||
@@ -196,7 +196,7 @@ export async function startCodexOAuthFlow(): Promise<CodexAuthResult> {
|
||||
authUrl.searchParams.set('state', state);
|
||||
authUrl.searchParams.set('code_challenge', codeChallenge);
|
||||
authUrl.searchParams.set('code_challenge_method', 'S256');
|
||||
authUrl.searchParams.set('originator', 'auto-claude');
|
||||
authUrl.searchParams.set('originator', 'aperant');
|
||||
authUrl.searchParams.set('codex_cli_simplified_flow', 'true');
|
||||
|
||||
debugLog('Built authorization URL', { url: authUrl.toString() });
|
||||
|
||||
@@ -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,19 +100,19 @@ 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 {
|
||||
function createAperantServer(specDir: string): McpServerConfig {
|
||||
return {
|
||||
id: 'auto-claude',
|
||||
id: 'aperant',
|
||||
name: 'Aperant',
|
||||
description: 'Build management tools (progress tracking, session context)',
|
||||
enabledByDefault: true,
|
||||
transport: {
|
||||
type: 'stdio',
|
||||
command: 'node',
|
||||
args: ['auto-claude-mcp-server.js'],
|
||||
args: ['aperant-mcp-server.js'],
|
||||
env: { SPEC_DIR: specDir },
|
||||
},
|
||||
};
|
||||
@@ -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,9 +175,9 @@ export function getMcpServerConfig(
|
||||
case 'puppeteer':
|
||||
return PUPPETEER_SERVER;
|
||||
|
||||
case 'auto-claude': {
|
||||
case 'aperant': {
|
||||
const specDir = options.specDir ?? '';
|
||||
return createAutoClaudeServer(specDir);
|
||||
return createAperantServer(specDir);
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -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]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -331,7 +331,7 @@ export async function generateSubtaskPrompt(config: SubtaskPromptConfig): Promis
|
||||
`5. **Commit your changes:**\n` +
|
||||
` \`\`\`bash\n` +
|
||||
` git add .\n` +
|
||||
` git commit -m "auto-claude: ${subtask.id} - ${subtask.description.slice(0, 50)}"\n` +
|
||||
` git commit -m "aperant: ${subtask.id} - ${subtask.description.slice(0, 50)}"\n` +
|
||||
` \`\`\`\n` +
|
||||
`6. **Update the plan** - set this subtask's status to "completed" in implementation_plan.json\n\n` +
|
||||
`## Quality Checklist\n\n` +
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -160,7 +160,7 @@ export async function validateAndNormalizeJsonFile<T>(
|
||||
if (result.valid && result.data) {
|
||||
// Write back the coerced data so downstream consumers get canonical field names.
|
||||
// Use a secure temp file + atomic rename to avoid TOCTOU races on the target path.
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'auto-claude-normalize-'));
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'aperant-normalize-'));
|
||||
const tempFile = join(tempDir, 'output.json');
|
||||
try {
|
||||
await writeFile(tempFile, JSON.stringify(result.data, null, 2));
|
||||
@@ -338,7 +338,7 @@ export async function repairJsonWithLLM<T>(
|
||||
const coerced = schema.safeParse(result.output);
|
||||
if (coerced.success) {
|
||||
// Use a secure temp file + atomic rename to avoid TOCTOU races
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'auto-claude-repair-'));
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'aperant-repair-'));
|
||||
const tempFile = join(tempDir, 'output.json');
|
||||
try {
|
||||
await writeFile(tempFile, JSON.stringify(coerced.data, null, 2));
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
+3
-3
@@ -3,9 +3,9 @@
|
||||
* =======================
|
||||
*
|
||||
* Reports current build progress from implementation_plan.json.
|
||||
* See apps/desktop/src/main/ai/tools/auto-claude/get-build-progress.ts for the TypeScript implementation.
|
||||
* See apps/desktop/src/main/ai/tools/aperant/get-build-progress.ts for the TypeScript implementation.
|
||||
*
|
||||
* Tool name: mcp__auto-claude__get_build_progress
|
||||
* Tool name: mcp__aperant__get_build_progress
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
@@ -50,7 +50,7 @@ interface ImplementationPlan {
|
||||
|
||||
export const getBuildProgressTool = Tool.define({
|
||||
metadata: {
|
||||
name: 'mcp__auto-claude__get_build_progress',
|
||||
name: 'mcp__aperant__get_build_progress',
|
||||
description:
|
||||
'Get the current build progress including completed subtasks, pending subtasks, and next subtask to work on.',
|
||||
permission: ToolPermission.ReadOnly,
|
||||
+3
-3
@@ -7,9 +7,9 @@
|
||||
* - memory/gotchas.md → gotchas & pitfalls
|
||||
* - memory/patterns.md → code patterns
|
||||
*
|
||||
* See apps/desktop/src/main/ai/tools/auto-claude/get-session-context.ts for the TypeScript implementation.
|
||||
* See apps/desktop/src/main/ai/tools/aperant/get-session-context.ts for the TypeScript implementation.
|
||||
*
|
||||
* Tool name: mcp__auto-claude__get_session_context
|
||||
* Tool name: mcp__aperant__get_session_context
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
@@ -40,7 +40,7 @@ interface CodebaseMap {
|
||||
|
||||
export const getSessionContextTool = Tool.define({
|
||||
metadata: {
|
||||
name: 'mcp__auto-claude__get_session_context',
|
||||
name: 'mcp__aperant__get_session_context',
|
||||
description:
|
||||
'Get context from previous sessions including codebase discoveries, gotchas, and patterns. Call this at the start of a session to pick up where the last session left off.',
|
||||
permission: ToolPermission.ReadOnly,
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Auto-Claude Custom Tools
|
||||
* ========================
|
||||
* Aperant Custom Tools
|
||||
* ====================
|
||||
*
|
||||
* Barrel export for all auto-claude builtin tools.
|
||||
* Barrel export for all aperant builtin tools.
|
||||
* These replace the Python tools_pkg/tools/* implementations.
|
||||
*
|
||||
* Tool names follow the mcp__auto-claude__* convention to match the
|
||||
* Tool names follow the mcp__aperant__* convention to match the
|
||||
* TOOL_* constants in registry.ts and AGENT_CONFIGS autoClaudeTools arrays.
|
||||
*/
|
||||
|
||||
+3
-3
@@ -3,9 +3,9 @@
|
||||
* =====================
|
||||
*
|
||||
* Records a codebase discovery to session memory (codebase_map.json).
|
||||
* See apps/desktop/src/main/ai/tools/auto-claude/record-discovery.ts for the TypeScript implementation.
|
||||
* See apps/desktop/src/main/ai/tools/aperant/record-discovery.ts for the TypeScript implementation.
|
||||
*
|
||||
* Tool name: mcp__auto-claude__record_discovery
|
||||
* Tool name: mcp__aperant__record_discovery
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
@@ -44,7 +44,7 @@ interface CodebaseMap {
|
||||
|
||||
export const recordDiscoveryTool = Tool.define({
|
||||
metadata: {
|
||||
name: 'mcp__auto-claude__record_discovery',
|
||||
name: 'mcp__aperant__record_discovery',
|
||||
description:
|
||||
'Record a codebase discovery to session memory. Use this when you learn something important about the codebase structure or behavior.',
|
||||
permission: ToolPermission.Auto,
|
||||
+3
-3
@@ -3,9 +3,9 @@
|
||||
* ==================
|
||||
*
|
||||
* Records a gotcha or pitfall to specDir/memory/gotchas.md.
|
||||
* See apps/desktop/src/main/ai/tools/auto-claude/record-gotcha.ts for the TypeScript implementation.
|
||||
* See apps/desktop/src/main/ai/tools/aperant/record-gotcha.ts for the TypeScript implementation.
|
||||
*
|
||||
* Tool name: mcp__auto-claude__record_gotcha
|
||||
* Tool name: mcp__aperant__record_gotcha
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
@@ -33,7 +33,7 @@ const inputSchema = z.object({
|
||||
|
||||
export const recordGotchaTool = Tool.define({
|
||||
metadata: {
|
||||
name: 'mcp__auto-claude__record_gotcha',
|
||||
name: 'mcp__aperant__record_gotcha',
|
||||
description:
|
||||
'Record a gotcha or pitfall to avoid. Use this when you encounter something that future sessions should know about to avoid repeating mistakes.',
|
||||
permission: ToolPermission.Auto,
|
||||
+3
-3
@@ -3,9 +3,9 @@
|
||||
* =====================
|
||||
*
|
||||
* Updates the QA sign-off status in implementation_plan.json.
|
||||
* See apps/desktop/src/main/ai/tools/auto-claude/update-qa-status.ts for the TypeScript implementation.
|
||||
* See apps/desktop/src/main/ai/tools/aperant/update-qa-status.ts for the TypeScript implementation.
|
||||
*
|
||||
* Tool name: mcp__auto-claude__update_qa_status
|
||||
* Tool name: mcp__aperant__update_qa_status
|
||||
*
|
||||
* IMPORTANT: Do NOT write plan["status"] or plan["planStatus"] here.
|
||||
* The frontend XState task state machine owns status transitions.
|
||||
@@ -68,7 +68,7 @@ interface ImplementationPlan {
|
||||
|
||||
export const updateQaStatusTool = Tool.define({
|
||||
metadata: {
|
||||
name: 'mcp__auto-claude__update_qa_status',
|
||||
name: 'mcp__aperant__update_qa_status',
|
||||
description:
|
||||
'Update the QA sign-off status in implementation_plan.json. Use this after completing a QA review to record the outcome.',
|
||||
permission: ToolPermission.Auto,
|
||||
+3
-3
@@ -3,9 +3,9 @@
|
||||
* ==========================
|
||||
*
|
||||
* Updates the status of a subtask in implementation_plan.json.
|
||||
* See apps/desktop/src/main/ai/tools/auto-claude/update-subtask-status.ts for the TypeScript implementation.
|
||||
* See apps/desktop/src/main/ai/tools/aperant/update-subtask-status.ts for the TypeScript implementation.
|
||||
*
|
||||
* Tool name: mcp__auto-claude__update_subtask_status
|
||||
* Tool name: mcp__aperant__update_subtask_status
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
@@ -82,7 +82,7 @@ function updateSubtaskInPlan(
|
||||
|
||||
export const updateSubtaskStatusTool = Tool.define({
|
||||
metadata: {
|
||||
name: 'mcp__auto-claude__update_subtask_status',
|
||||
name: 'mcp__aperant__update_subtask_status',
|
||||
description:
|
||||
'Update the status of a subtask in implementation_plan.json. Use this when completing or starting a subtask.',
|
||||
permission: ToolPermission.Auto,
|
||||
@@ -24,7 +24,7 @@ export class FetchBrowseProvider implements BrowseProvider {
|
||||
const response = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'User-Agent': 'AutoClaude/1.0',
|
||||
'User-Agent': 'Aperant/1.0',
|
||||
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -45,12 +45,12 @@ export {
|
||||
export const BASE_READ_TOOLS = ['Read', 'Glob', 'Grep'] as const;
|
||||
export const BASE_WRITE_TOOLS = ['Write', 'Edit', 'Bash'] as const;
|
||||
export const WEB_TOOLS = ['WebFetch', 'WebSearch'] as const;
|
||||
export const TOOL_UPDATE_SUBTASK_STATUS = 'mcp__auto-claude__update_subtask_status';
|
||||
export const TOOL_GET_BUILD_PROGRESS = 'mcp__auto-claude__get_build_progress';
|
||||
export const TOOL_RECORD_DISCOVERY = 'mcp__auto-claude__record_discovery';
|
||||
export const TOOL_RECORD_GOTCHA = 'mcp__auto-claude__record_gotcha';
|
||||
export const TOOL_GET_SESSION_CONTEXT = 'mcp__auto-claude__get_session_context';
|
||||
export const TOOL_UPDATE_QA_STATUS = 'mcp__auto-claude__update_qa_status';
|
||||
export const TOOL_UPDATE_SUBTASK_STATUS = 'mcp__aperant__update_subtask_status';
|
||||
export const TOOL_GET_BUILD_PROGRESS = 'mcp__aperant__get_build_progress';
|
||||
export const TOOL_RECORD_DISCOVERY = 'mcp__aperant__record_discovery';
|
||||
export const TOOL_RECORD_GOTCHA = 'mcp__aperant__record_gotcha';
|
||||
export const TOOL_GET_SESSION_CONTEXT = 'mcp__aperant__get_session_context';
|
||||
export const TOOL_UPDATE_QA_STATUS = 'mcp__aperant__update_qa_status';
|
||||
|
||||
// =============================================================================
|
||||
// MCP Config for dynamic server resolution
|
||||
@@ -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 aperantPath Optional custom data directory (e.g. ".aperant").
|
||||
* Passed to getSpecsDir() for spec-copy logic.
|
||||
*/
|
||||
export async function createOrGetWorktree(
|
||||
@@ -87,10 +87,10 @@ export async function createOrGetWorktree(
|
||||
baseBranch = 'main',
|
||||
useLocalBranch = false,
|
||||
pushNewBranches = true,
|
||||
autoBuildPath?: string,
|
||||
aperantPath?: 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(aperantPath); // 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 });
|
||||
|
||||
@@ -7,7 +7,7 @@ import { app } from 'electron';
|
||||
// ESM-compatible __dirname
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
import { AUTO_BUILD_PATHS, DEFAULT_CHANGELOG_PATH } from '../../shared/constants';
|
||||
import { APERANT_PATHS, DEFAULT_CHANGELOG_PATH } from '../../shared/constants';
|
||||
import { getToolPath, getToolInfo } from '../cli-tool-manager';
|
||||
import type {
|
||||
ChangelogTask,
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
*/
|
||||
export class ChangelogService extends EventEmitter {
|
||||
private claudePath: string;
|
||||
private autoBuildSourcePath: string = '';
|
||||
private aperantSourcePath: string = '';
|
||||
private debugEnabled: boolean | null = null;
|
||||
private generator: ChangelogGenerator | null = null;
|
||||
private versionSuggester: VersionSuggester | null = null;
|
||||
@@ -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
|
||||
@@ -71,14 +71,14 @@ export class ChangelogService extends EventEmitter {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check auto-claude .env file
|
||||
const env = this.loadAutoBuildEnv();
|
||||
// Check aperant .env file
|
||||
const env = this.loadAperantEnv();
|
||||
this.debugEnabled = env.DEBUG === 'true' || env.DEBUG === '1';
|
||||
return this.debugEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()) {
|
||||
@@ -86,18 +86,18 @@ export class ChangelogService extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
configure(_pythonPath?: string, autoBuildSourcePath?: string): void {
|
||||
if (autoBuildSourcePath) {
|
||||
this.autoBuildSourcePath = autoBuildSourcePath;
|
||||
configure(_pythonPath?: string, aperantSourcePath?: string): void {
|
||||
if (aperantSourcePath) {
|
||||
this.aperantSourcePath = aperantSourcePath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auto-claude source path (detects automatically if not configured)
|
||||
* Get the aperant source path (detects automatically if not configured)
|
||||
*/
|
||||
private getAutoBuildSourcePath(): string | null {
|
||||
if (this.autoBuildSourcePath && existsSync(this.autoBuildSourcePath)) {
|
||||
return this.autoBuildSourcePath;
|
||||
private getAperantSourcePath(): string | null {
|
||||
if (this.aperantSourcePath && existsSync(this.aperantSourcePath)) {
|
||||
return this.aperantSourcePath;
|
||||
}
|
||||
|
||||
const possiblePaths = [
|
||||
@@ -116,13 +116,13 @@ export class ChangelogService extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment variables from auto-claude .env file
|
||||
* Load environment variables from aperant .env file
|
||||
*/
|
||||
private loadAutoBuildEnv(): Record<string, string> {
|
||||
const autoBuildSource = this.getAutoBuildSourcePath();
|
||||
if (!autoBuildSource) return {};
|
||||
private loadAperantEnv(): Record<string, string> {
|
||||
const aperantSource = this.getAperantSourcePath();
|
||||
if (!aperantSource) return {};
|
||||
|
||||
const envPath = path.join(autoBuildSource, '.env');
|
||||
const envPath = path.join(aperantSource, '.env');
|
||||
if (!existsSync(envPath)) return {};
|
||||
|
||||
try {
|
||||
@@ -156,13 +156,13 @@ export class ChangelogService extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Ensure prerequisites are met for changelog generation
|
||||
* Validates auto-build source path and Claude CLI availability
|
||||
* Validates aperant source path and Claude CLI availability
|
||||
* Returns the resolved Claude CLI path to ensure we use the freshly validated path
|
||||
*/
|
||||
private ensurePrerequisites(): { autoBuildSource: string; claudePath: string } {
|
||||
const autoBuildSource = this.getAutoBuildSourcePath();
|
||||
if (!autoBuildSource) {
|
||||
throw new Error('Auto-build source path not found');
|
||||
private ensurePrerequisites(): { aperantSource: string; claudePath: string } {
|
||||
const aperantSource = this.getAperantSourcePath();
|
||||
if (!aperantSource) {
|
||||
throw new Error('Aperant source path not found');
|
||||
}
|
||||
|
||||
const claudeInfo = getToolInfo('claude');
|
||||
@@ -173,7 +173,7 @@ export class ChangelogService extends EventEmitter {
|
||||
|
||||
// Update cached path with freshly resolved value
|
||||
this.claudePath = claudeInfo.path;
|
||||
return { autoBuildSource, claudePath: claudeInfo.path };
|
||||
return { aperantSource, claudePath: claudeInfo.path };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,15 +181,15 @@ export class ChangelogService extends EventEmitter {
|
||||
*/
|
||||
private getGenerator(): ChangelogGenerator {
|
||||
if (!this.generator) {
|
||||
const { autoBuildSource, claudePath } = this.ensurePrerequisites();
|
||||
const { aperantSource, claudePath } = this.ensurePrerequisites();
|
||||
|
||||
const autoBuildEnv = this.loadAutoBuildEnv();
|
||||
const aperantEnv = this.loadAperantEnv();
|
||||
|
||||
this.generator = new ChangelogGenerator(
|
||||
'',
|
||||
claudePath,
|
||||
autoBuildSource,
|
||||
autoBuildEnv,
|
||||
aperantSource,
|
||||
aperantEnv,
|
||||
this.isDebugEnabled()
|
||||
);
|
||||
|
||||
@@ -219,12 +219,12 @@ export class ChangelogService extends EventEmitter {
|
||||
*/
|
||||
private getVersionSuggester(): VersionSuggester {
|
||||
if (!this.versionSuggester) {
|
||||
const { autoBuildSource, claudePath } = this.ensurePrerequisites();
|
||||
const { aperantSource, claudePath } = this.ensurePrerequisites();
|
||||
|
||||
this.versionSuggester = new VersionSuggester(
|
||||
'',
|
||||
claudePath,
|
||||
autoBuildSource,
|
||||
aperantSource,
|
||||
this.isDebugEnabled()
|
||||
);
|
||||
}
|
||||
@@ -240,13 +240,13 @@ export class ChangelogService extends EventEmitter {
|
||||
* Get completed tasks from a project
|
||||
*/
|
||||
getCompletedTasks(projectPath: string, tasks: Task[], specsBaseDir?: string): ChangelogTask[] {
|
||||
const specsDir = path.join(projectPath, specsBaseDir || AUTO_BUILD_PATHS.SPECS_DIR);
|
||||
const specsDir = path.join(projectPath, specsBaseDir || APERANT_PATHS.SPECS_DIR);
|
||||
|
||||
return tasks
|
||||
.filter(task => isCompletedTask(task.status, task.reviewReason) && !task.metadata?.archivedAt)
|
||||
.map(task => {
|
||||
const specDir = path.join(specsDir, task.specId);
|
||||
const hasSpecs = existsSync(specDir) && existsSync(path.join(specDir, AUTO_BUILD_PATHS.SPEC_FILE));
|
||||
const hasSpecs = existsSync(specDir) && existsSync(path.join(specDir, APERANT_PATHS.SPEC_FILE));
|
||||
|
||||
return {
|
||||
id: task.id,
|
||||
@@ -264,7 +264,7 @@ export class ChangelogService extends EventEmitter {
|
||||
* Load spec files for given tasks
|
||||
*/
|
||||
async loadTaskSpecs(projectPath: string, taskIds: string[], tasks: Task[], specsBaseDir?: string): Promise<TaskSpecContent[]> {
|
||||
const specsDir = path.join(projectPath, specsBaseDir || AUTO_BUILD_PATHS.SPECS_DIR);
|
||||
const specsDir = path.join(projectPath, specsBaseDir || APERANT_PATHS.SPECS_DIR);
|
||||
this.debug('loadTaskSpecs called', { projectPath, specsDir, taskCount: taskIds.length });
|
||||
|
||||
const results: TaskSpecContent[] = [];
|
||||
@@ -286,26 +286,26 @@ export class ChangelogService extends EventEmitter {
|
||||
|
||||
try {
|
||||
// Load spec.md
|
||||
const specPath = path.join(specDir, AUTO_BUILD_PATHS.SPEC_FILE);
|
||||
const specPath = path.join(specDir, APERANT_PATHS.SPEC_FILE);
|
||||
if (existsSync(specPath)) {
|
||||
content.spec = readFileSync(specPath, 'utf-8');
|
||||
this.debug('Loaded spec.md', { specId: task.specId, length: content.spec.length });
|
||||
}
|
||||
|
||||
// Load requirements.json
|
||||
const requirementsPath = path.join(specDir, AUTO_BUILD_PATHS.REQUIREMENTS);
|
||||
const requirementsPath = path.join(specDir, APERANT_PATHS.REQUIREMENTS);
|
||||
if (existsSync(requirementsPath)) {
|
||||
content.requirements = JSON.parse(readFileSync(requirementsPath, 'utf-8'));
|
||||
}
|
||||
|
||||
// Load qa_report.md
|
||||
const qaReportPath = path.join(specDir, AUTO_BUILD_PATHS.QA_REPORT);
|
||||
const qaReportPath = path.join(specDir, APERANT_PATHS.QA_REPORT);
|
||||
if (existsSync(qaReportPath)) {
|
||||
content.qaReport = readFileSync(qaReportPath, 'utf-8');
|
||||
}
|
||||
|
||||
// Load implementation_plan.json
|
||||
const planPath = path.join(specDir, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN);
|
||||
const planPath = path.join(specDir, APERANT_PATHS.IMPLEMENTATION_PLAN);
|
||||
if (existsSync(planPath)) {
|
||||
content.implementationPlan = JSON.parse(readFileSync(planPath, 'utf-8')) as ImplementationPlan;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ export class ChangelogGenerator extends EventEmitter {
|
||||
constructor(
|
||||
private pythonPath: string,
|
||||
private claudePath: string,
|
||||
private autoBuildSourcePath: string,
|
||||
private autoBuildEnv: Record<string, string>,
|
||||
private aperantSourcePath: string,
|
||||
private aperantEnv: Record<string, string>,
|
||||
debugEnabled: boolean
|
||||
) {
|
||||
super();
|
||||
@@ -146,7 +146,7 @@ export class ChangelogGenerator extends EventEmitter {
|
||||
// Use python3/python as fallback command (Python subprocess path removed in Vercel AI SDK migration)
|
||||
const pythonCommand = this.pythonPath || 'python3';
|
||||
const childProcess = spawn(pythonCommand, ['-c', script], {
|
||||
cwd: this.autoBuildSourcePath,
|
||||
cwd: this.aperantSourcePath,
|
||||
env: spawnEnv
|
||||
});
|
||||
|
||||
@@ -306,7 +306,7 @@ export class ChangelogGenerator extends EventEmitter {
|
||||
|
||||
const spawnEnv: Record<string, string> = {
|
||||
...augmentedEnv,
|
||||
...this.autoBuildEnv,
|
||||
...this.aperantEnv,
|
||||
...profileEnv, // Include active Claude profile config
|
||||
// Ensure critical env vars are set for claude CLI
|
||||
// Use USERPROFILE on Windows, HOME on Unix
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
export interface ChangelogConfig {
|
||||
pythonPath: string;
|
||||
claudePath: string;
|
||||
autoBuildSourcePath: string;
|
||||
aperantSourcePath: string;
|
||||
}
|
||||
|
||||
export interface PromptBuildOptions {
|
||||
|
||||
@@ -22,7 +22,7 @@ export class VersionSuggester {
|
||||
constructor(
|
||||
private pythonPath: string,
|
||||
private claudePath: string,
|
||||
private autoBuildSourcePath: string,
|
||||
private aperantSourcePath: string,
|
||||
debugEnabled: boolean
|
||||
) {
|
||||
this.debugEnabled = debugEnabled;
|
||||
@@ -57,7 +57,7 @@ export class VersionSuggester {
|
||||
// Use python3/python as fallback command (Python subprocess path removed in Vercel AI SDK migration)
|
||||
const pythonCommand = this.pythonPath || 'python3';
|
||||
const childProcess = spawn(pythonCommand, ['-c', script], {
|
||||
cwd: this.autoBuildSourcePath,
|
||||
cwd: this.aperantSourcePath,
|
||||
env: spawnEnv
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Claude Code CLI Settings Module
|
||||
*
|
||||
* Reads and merges Claude Code CLI settings files from the user's system.
|
||||
* These settings are separate from Auto Claude's own settings (settings-utils.ts).
|
||||
* These settings are separate from Aperant's own settings (settings-utils.ts).
|
||||
*
|
||||
* Usage:
|
||||
* import { getClaudeCodeEnv, readAllSettings } from './claude-code-settings';
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { existsSync } from 'fs';
|
||||
import { isLinux } from './platform';
|
||||
|
||||
const APP_NAME = 'auto-claude';
|
||||
const APP_NAME = 'aperant';
|
||||
|
||||
/**
|
||||
* Get the XDG config home directory
|
||||
@@ -70,10 +71,13 @@ 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
|
||||
// New path under ~/.aperant/memories
|
||||
const newPath = path.join(os.homedir(), '.aperant', 'memories');
|
||||
|
||||
// Legacy path from before the rebrand
|
||||
const legacyPath = path.join(os.homedir(), '.auto-claude', 'memories');
|
||||
|
||||
// On Linux with XDG variables set (AppImage, Flatpak, Snap), use XDG path
|
||||
@@ -81,8 +85,17 @@ export function getMemoriesDir(): string {
|
||||
return path.join(getXdgDataHome(), APP_NAME, 'memories');
|
||||
}
|
||||
|
||||
// Default to legacy path for backwards compatibility
|
||||
return legacyPath;
|
||||
// Use new path if it exists, otherwise fall back to legacy
|
||||
if (existsSync(newPath)) {
|
||||
return newPath;
|
||||
}
|
||||
|
||||
if (existsSync(legacyPath)) {
|
||||
return legacyPath;
|
||||
}
|
||||
|
||||
// Default to new path for fresh installs
|
||||
return newPath;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -444,17 +444,17 @@ app.whenReady().then(() => {
|
||||
// Initialize agent manager
|
||||
agentManager = new AgentManager();
|
||||
|
||||
// Load settings and configure agent manager with Python and auto-claude paths
|
||||
// Load settings and configure agent manager with Python and aperant paths
|
||||
// Uses EAFP pattern (try/catch) instead of LBYL (existsSync) to avoid TOCTOU race conditions
|
||||
const settingsPath = join(app.getPath('userData'), 'settings.json');
|
||||
try {
|
||||
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
|
||||
// Validate and migrate autoBuildPath - must contain planner.md (prompts directory)
|
||||
// Validate and migrate aperantPath - must contain planner.md (prompts directory)
|
||||
// Uses EAFP pattern (try/catch with accessSync) instead of existsSync to avoid TOCTOU race conditions
|
||||
let validAutoBuildPath = settings.autoBuildPath;
|
||||
if (validAutoBuildPath) {
|
||||
const plannerMdPath = join(validAutoBuildPath, 'planner.md');
|
||||
let validAperantPath = settings.aperantPath;
|
||||
if (validAperantPath) {
|
||||
const plannerMdPath = join(validAperantPath, 'planner.md');
|
||||
let plannerExists = false;
|
||||
try {
|
||||
accessSync(plannerMdPath);
|
||||
@@ -465,12 +465,12 @@ app.whenReady().then(() => {
|
||||
|
||||
if (!plannerExists) {
|
||||
// Migration: Try to fix stale paths from old project structure
|
||||
// Old structure: /path/to/project/auto-claude or apps/backend
|
||||
// Old structure: /path/to/project/aperant or apps/backend
|
||||
// New structure: /path/to/project/apps/desktop/prompts
|
||||
let migrated = false;
|
||||
const possibleCorrections = [
|
||||
join(validAutoBuildPath.replace(/[/\\]auto-claude[/\\]*$/, ''), 'apps', 'desktop', 'prompts'),
|
||||
join(validAutoBuildPath.replace(/[/\\]backend[/\\]*$/, ''), 'desktop', 'prompts'),
|
||||
join(validAperantPath.replace(/[/\\]auto-claude[/\\]*$/, ''), 'apps', 'desktop', 'prompts'),
|
||||
join(validAperantPath.replace(/[/\\]backend[/\\]*$/, ''), 'desktop', 'prompts'),
|
||||
];
|
||||
for (const correctedPath of possibleCorrections) {
|
||||
const correctedPlannerPath = join(correctedPath, 'planner.md');
|
||||
@@ -483,31 +483,31 @@ app.whenReady().then(() => {
|
||||
}
|
||||
|
||||
if (correctedPathExists) {
|
||||
console.log('[main] Migrating autoBuildPath from old structure:', validAutoBuildPath, '->', correctedPath);
|
||||
settings.autoBuildPath = correctedPath;
|
||||
validAutoBuildPath = correctedPath;
|
||||
console.log('[main] Migrating aperantPath from old structure:', validAperantPath, '->', correctedPath);
|
||||
settings.aperantPath = correctedPath;
|
||||
validAperantPath = correctedPath;
|
||||
migrated = true;
|
||||
|
||||
// Save the corrected setting - we're the only process modifying settings at startup
|
||||
try {
|
||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
||||
console.log('[main] Successfully saved migrated autoBuildPath to settings');
|
||||
console.log('[main] Successfully saved migrated aperantPath to settings');
|
||||
} catch (writeError) {
|
||||
console.warn('[main] Failed to save migrated autoBuildPath:', writeError);
|
||||
console.warn('[main] Failed to save migrated aperantPath:', writeError);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!migrated) {
|
||||
console.warn('[main] Configured autoBuildPath is invalid (missing planner.md), will use auto-detection:', validAutoBuildPath);
|
||||
validAutoBuildPath = undefined; // Let auto-detection find the correct path
|
||||
console.warn('[main] Configured aperantPath is invalid (missing planner.md), will use auto-detection:', validAperantPath);
|
||||
validAperantPath = undefined; // Let auto-detection find the correct path
|
||||
|
||||
// Clear the stale setting so this warning doesn't repeat every startup
|
||||
try {
|
||||
delete settings.autoBuildPath;
|
||||
delete settings.aperantPath;
|
||||
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
||||
console.log('[main] Cleared stale autoBuildPath from settings');
|
||||
console.log('[main] Cleared stale aperantPath from settings');
|
||||
} catch {
|
||||
// Non-critical - warning will just repeat next startup
|
||||
}
|
||||
@@ -515,12 +515,12 @@ app.whenReady().then(() => {
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.pythonPath || validAutoBuildPath) {
|
||||
if (settings.pythonPath || validAperantPath) {
|
||||
console.warn('[main] Configuring AgentManager with settings:', {
|
||||
pythonPath: settings.pythonPath,
|
||||
autoBuildPath: validAutoBuildPath
|
||||
aperantPath: validAperantPath
|
||||
});
|
||||
agentManager.configure(settings.pythonPath, validAutoBuildPath);
|
||||
agentManager.configure(settings.pythonPath, validAperantPath);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
// ENOENT means no settings file yet - that's fine, use defaults
|
||||
|
||||
@@ -56,10 +56,10 @@ export class InsightsService extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure paths for Python and auto-claude source
|
||||
* Configure paths for Python and aperant source
|
||||
*/
|
||||
configure(pythonPath?: string, autoBuildSourcePath?: string): void {
|
||||
this.config.configure(pythonPath, autoBuildSourcePath);
|
||||
configure(pythonPath?: string, aperantSourcePath?: string): void {
|
||||
this.config.configure(pythonPath, aperantSourcePath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,9 +18,9 @@ insights-service.ts (186 lines)
|
||||
## Module Responsibilities
|
||||
|
||||
### InsightsConfig (`config.ts`)
|
||||
- Manages Python and auto-claude source path configuration
|
||||
- Detects auto-claude installation automatically
|
||||
- Loads environment variables from auto-claude .env file
|
||||
- Manages Python and aperant source path configuration
|
||||
- Detects aperant installation automatically
|
||||
- Loads environment variables from aperant .env file
|
||||
- Provides complete process environment with profile support
|
||||
|
||||
### InsightsPaths (`paths.ts`)
|
||||
@@ -58,7 +58,7 @@ import { InsightsService } from './insights-service';
|
||||
const service = new InsightsService();
|
||||
|
||||
// Configure paths
|
||||
service.configure(pythonPath, autoBuildSourcePath);
|
||||
service.configure(pythonPath, aperantSourcePath);
|
||||
|
||||
// Load session
|
||||
const session = service.loadSession(projectId, projectPath);
|
||||
|
||||
@@ -83,7 +83,7 @@ No migration needed! The refactoring is transparent to consumers:
|
||||
// This code continues to work exactly as before
|
||||
import { insightsService } from '../insights-service';
|
||||
|
||||
insightsService.configure(pythonPath, autoBuildSourcePath);
|
||||
insightsService.configure(pythonPath, aperantSourcePath);
|
||||
const session = insightsService.loadSession(projectId, projectPath);
|
||||
await insightsService.sendMessage(projectId, projectPath, message);
|
||||
```
|
||||
|
||||
@@ -12,25 +12,25 @@ import { getEffectiveSourcePath } from '../updater/path-resolver';
|
||||
* Handles path detection and environment variable loading
|
||||
*/
|
||||
export class InsightsConfig {
|
||||
private autoBuildSourcePath: string = '';
|
||||
private aperantSourcePath: string = '';
|
||||
|
||||
configure(_pythonPath?: string, autoBuildSourcePath?: string): void {
|
||||
if (autoBuildSourcePath) {
|
||||
this.autoBuildSourcePath = autoBuildSourcePath;
|
||||
configure(_pythonPath?: string, aperantSourcePath?: string): void {
|
||||
if (aperantSourcePath) {
|
||||
this.aperantSourcePath = aperantSourcePath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auto-claude source path (detects automatically if not configured)
|
||||
* Get the aperant source path (detects automatically if not configured)
|
||||
* Uses getEffectiveSourcePath() which handles userData override for user-updated backend
|
||||
*/
|
||||
getAutoBuildSourcePath(): string | null {
|
||||
if (this.autoBuildSourcePath && existsSync(this.autoBuildSourcePath)) {
|
||||
return this.autoBuildSourcePath;
|
||||
getAperantSourcePath(): string | null {
|
||||
if (this.aperantSourcePath && existsSync(this.aperantSourcePath)) {
|
||||
return this.aperantSourcePath;
|
||||
}
|
||||
|
||||
// Use shared path resolver which handles:
|
||||
// 1. User settings (autoBuildPath)
|
||||
// 1. User settings (aperantPath)
|
||||
// 2. userData override (backend-source) for user-updated backend
|
||||
// 3. Bundled backend (process.resourcesPath/backend)
|
||||
// 4. Development paths
|
||||
@@ -43,13 +43,13 @@ export class InsightsConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load environment variables from auto-claude .env file
|
||||
* Load environment variables from aperant .env file
|
||||
*/
|
||||
loadAutoBuildEnv(): Record<string, string> {
|
||||
const autoBuildSource = this.getAutoBuildSourcePath();
|
||||
if (!autoBuildSource) return {};
|
||||
loadAperantEnv(): Record<string, string> {
|
||||
const aperantSource = this.getAperantSourcePath();
|
||||
if (!aperantSource) return {};
|
||||
|
||||
const envPath = path.join(autoBuildSource, '.env');
|
||||
const envPath = path.join(aperantSource, '.env');
|
||||
if (!existsSync(envPath)) return {};
|
||||
|
||||
try {
|
||||
@@ -83,10 +83,10 @@ export class InsightsConfig {
|
||||
|
||||
/**
|
||||
* Get complete environment for process execution
|
||||
* Includes system env, auto-claude env, and active Claude profile
|
||||
* Includes system env, aperant env, and active Claude profile
|
||||
*/
|
||||
async getProcessEnv(): Promise<Record<string, string>> {
|
||||
const autoBuildEnv = this.loadAutoBuildEnv();
|
||||
const aperantEnv = this.loadAperantEnv();
|
||||
// Get best available Claude profile environment (automatically handles rate limits)
|
||||
const profileResult = getBestAvailableProfileEnv();
|
||||
const profileEnv = profileResult.env;
|
||||
@@ -99,7 +99,7 @@ export class InsightsConfig {
|
||||
|
||||
return {
|
||||
...augmentedEnv,
|
||||
...autoBuildEnv,
|
||||
...aperantEnv,
|
||||
...oauthModeClearVars,
|
||||
...profileEnv,
|
||||
...apiProfileEnv,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# IPC Handlers - Modular Architecture
|
||||
|
||||
This directory contains the refactored IPC (Inter-Process Communication) handlers for Auto Claude UI, organized into domain-specific modules for better maintainability and code organization.
|
||||
This directory contains the refactored IPC (Inter-Process Communication) handlers for Aperant UI, organized into domain-specific modules for better maintainability and code organization.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -16,9 +16,9 @@ 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
|
||||
- `project:has-local-source` - Check if project has local aperant source
|
||||
- Python environment initialization and status events
|
||||
|
||||
#### `task-handlers.ts` (52KB) - Largest module
|
||||
@@ -243,7 +243,7 @@ Each module may depend on:
|
||||
- **Services**: AgentManager, TerminalManager, ChangelogService, etc.
|
||||
- **Stores**: projectStore
|
||||
- **Utilities**: fileWatcher, titleGenerator
|
||||
- **Constants**: IPC_CHANNELS, AUTO_BUILD_PATHS, getSpecsDir
|
||||
- **Constants**: IPC_CHANNELS, APERANT_PATHS, getSpecsDir
|
||||
- **Types**: Extensive TypeScript types from shared/types
|
||||
|
||||
All dependencies are explicitly imported at the module level.
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { BrowserWindow } from "electron";
|
||||
import path from "path";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { safeParseJson } from "../utils/json-repair";
|
||||
import { IPC_CHANNELS, AUTO_BUILD_PATHS, getSpecsDir } from "../../shared/constants";
|
||||
import { IPC_CHANNELS, APERANT_PATHS, getSpecsDir } from "../../shared/constants";
|
||||
import type {
|
||||
SDKRateLimitInfo,
|
||||
AuthFailureInfo,
|
||||
@@ -164,8 +164,8 @@ export function registerAgenteventsHandlers(
|
||||
if (exitTask && exitProject) {
|
||||
const worktreePath = findTaskWorktree(exitProject.path, exitTask.specId);
|
||||
if (worktreePath) {
|
||||
const specsBaseDir = getSpecsDir(exitProject.autoBuildPath);
|
||||
const worktreePlanPath = path.join(worktreePath, specsBaseDir, exitTask.specId, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN);
|
||||
const specsBaseDir = getSpecsDir(exitProject.aperantPath);
|
||||
const worktreePlanPath = path.join(worktreePath, specsBaseDir, exitTask.specId, APERANT_PATHS.IMPLEMENTATION_PLAN);
|
||||
try {
|
||||
const content = readFileSync(worktreePlanPath, 'utf-8');
|
||||
const parsed = safeParseJson<ImplementationPlan>(content);
|
||||
@@ -205,9 +205,9 @@ export function registerAgenteventsHandlers(
|
||||
if (code === 0) {
|
||||
const { task: specTask, project: specProject } = findTaskAndProject(taskId, projectId);
|
||||
if (specTask && specProject) {
|
||||
const specsBaseDir = getSpecsDir(specProject.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(specProject.aperantPath);
|
||||
const specDir = path.join(specProject.path, specsBaseDir, specTask.specId);
|
||||
const specFilePath = path.join(specDir, AUTO_BUILD_PATHS.SPEC_FILE);
|
||||
const specFilePath = path.join(specDir, APERANT_PATHS.SPEC_FILE);
|
||||
if (existsSync(specFilePath)) {
|
||||
console.warn(`[Task ${taskId}] Spec created successfully — starting task execution`);
|
||||
// Re-watch the spec directory for the build phase
|
||||
@@ -290,12 +290,12 @@ export function registerAgenteventsHandlers(
|
||||
|
||||
const worktreePath = findTaskWorktree(project.path, task.specId);
|
||||
if (worktreePath) {
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const worktreePlanPath = path.join(
|
||||
worktreePath,
|
||||
specsBaseDir,
|
||||
task.specId,
|
||||
AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN
|
||||
APERANT_PATHS.IMPLEMENTATION_PLAN
|
||||
);
|
||||
if (existsSync(worktreePlanPath)) {
|
||||
persistPlanLastEventSync(worktreePlanPath, event);
|
||||
@@ -329,11 +329,11 @@ export function registerAgenteventsHandlers(
|
||||
// Also persist to worktree if task has one
|
||||
const worktreePath = findTaskWorktree(project.path, task.specId);
|
||||
if (worktreePath) {
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const worktreeSpecDir = path.join(worktreePath, specsBaseDir, task.specId);
|
||||
const worktreePlanPath = path.join(
|
||||
worktreeSpecDir,
|
||||
AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN
|
||||
APERANT_PATHS.IMPLEMENTATION_PLAN
|
||||
);
|
||||
if (existsSync(worktreePlanPath)) {
|
||||
persistPlanPhaseSync(worktreePlanPath, progress.phase, project.id);
|
||||
@@ -426,12 +426,12 @@ export function registerAgenteventsHandlers(
|
||||
// Also re-stamp worktree copy if it exists
|
||||
const worktreePath = findTaskWorktree(project.path, task.specId);
|
||||
if (worktreePath) {
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const worktreePlanPath = path.join(
|
||||
worktreePath,
|
||||
specsBaseDir,
|
||||
task.specId,
|
||||
AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN
|
||||
APERANT_PATHS.IMPLEMENTATION_PLAN
|
||||
);
|
||||
if (existsSync(worktreePlanPath)) {
|
||||
persistPlanStatusAndReasonSync(worktreePlanPath, status, reviewReason, project.id, currentXState, phase);
|
||||
|
||||
@@ -113,7 +113,7 @@ export function registerChangelogHandlers(
|
||||
const tasks = rendererTasks || projectStore.getTasks(projectId);
|
||||
|
||||
// Get specs directory path
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const doneTasks = changelogService.getCompletedTasks(project.path, tasks, specsBaseDir);
|
||||
|
||||
return { success: true, data: doneTasks };
|
||||
@@ -131,7 +131,7 @@ export function registerChangelogHandlers(
|
||||
const tasks = projectStore.getTasks(projectId);
|
||||
|
||||
// Get specs directory path
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const specs = await changelogService.loadTaskSpecs(project.path, taskIds, tasks, specsBaseDir);
|
||||
|
||||
return { success: true, data: specs };
|
||||
@@ -164,7 +164,7 @@ export function registerChangelogHandlers(
|
||||
let specs: TaskSpecContent[] = [];
|
||||
if (request.sourceMode === 'tasks' && request.taskIds && request.taskIds.length > 0) {
|
||||
const tasks = projectStore.getTasks(request.projectId);
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
specs = await changelogService.loadTaskSpecs(project.path, request.taskIds, tasks, specsBaseDir);
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ export function registerChangelogHandlers(
|
||||
|
||||
// Load specs for selected tasks to analyze change types
|
||||
const tasks = projectStore.getTasks(projectId);
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const specs = await changelogService.loadTaskSpecs(project.path, taskIds, tasks, specsBaseDir);
|
||||
|
||||
// Analyze specs and suggest version
|
||||
|
||||
@@ -44,7 +44,7 @@ export function registerChangelogHandlers(
|
||||
const tasks = rendererTasks || projectStore.getTasks(projectId);
|
||||
|
||||
// Get specs directory path
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const doneTasks = changelogService.getCompletedTasks(project.path, tasks, specsBaseDir);
|
||||
|
||||
return { success: true, data: doneTasks };
|
||||
@@ -62,7 +62,7 @@ export function registerChangelogHandlers(
|
||||
const tasks = projectStore.getTasks(projectId);
|
||||
|
||||
// Get specs directory path
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const specs = await changelogService.loadTaskSpecs(project.path, taskIds, tasks, specsBaseDir);
|
||||
|
||||
return { success: true, data: specs };
|
||||
@@ -89,7 +89,7 @@ export function registerChangelogHandlers(
|
||||
let specs: import('../shared/types').TaskSpecContent[] = [];
|
||||
if (request.sourceMode === 'tasks' && request.taskIds && request.taskIds.length > 0) {
|
||||
const tasks = projectStore.getTasks(request.projectId);
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
specs = await changelogService.loadTaskSpecs(project.path, request.taskIds, tasks, specsBaseDir);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ export function registerChangelogHandlers(
|
||||
|
||||
// Load specs for selected tasks to analyze change types
|
||||
const tasks = projectStore.getTasks(projectId);
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const specs = await changelogService.loadTaskSpecs(project.path, taskIds, tasks, specsBaseDir);
|
||||
|
||||
// Analyze specs and suggest version
|
||||
|
||||
@@ -1372,7 +1372,7 @@ export function registerClaudeCodeHandlers(): void {
|
||||
|
||||
// If authenticated, update the profile with metadata from credentials
|
||||
// NOTE: We intentionally do NOT store the OAuth token in the profile.
|
||||
// Storing the token causes AutoClaude to use a stale cached token instead of
|
||||
// Storing the token causes Aperant to use a stale cached token instead of
|
||||
// letting Claude CLI read fresh tokens from Keychain (which auto-refreshes).
|
||||
// By only storing metadata, we ensure getProfileEnv() uses CLAUDE_CONFIG_DIR,
|
||||
// which allows Claude CLI's working token refresh mechanism to be used.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Context Handlers Module
|
||||
|
||||
This directory contains the refactored context-related IPC handlers for the Auto Claude UI application. The handlers manage project context, memory systems (both file-based and Graphiti/LadybugDB), and project index operations.
|
||||
This directory contains the refactored context-related IPC handlers for the Aperant UI application. The handlers manage project context, memory systems (both file-based and Graphiti/LadybugDB), and project index operations.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -12,9 +12,9 @@ The module is organized into focused, single-responsibility files:
|
||||
Shared utility functions for environment configuration and parsing.
|
||||
|
||||
**Exports:**
|
||||
- `getAutoBuildSourcePath()` - Get auto-build source path from settings
|
||||
- `getAperantSourcePath()` - Get aperant source path from settings
|
||||
- `parseEnvFile(content)` - Parse .env file content into key-value pairs
|
||||
- `loadProjectEnvVars(projectPath, autoBuildPath)` - Load project-specific environment variables
|
||||
- `loadProjectEnvVars(projectPath, aperantPath)` - Load project-specific environment variables
|
||||
- `loadGlobalSettings()` - Load global application settings
|
||||
- `isGraphitiEnabled(projectEnvVars)` - Check if Graphiti memory system is enabled
|
||||
- `hasOpenAIKey(projectEnvVars, globalSettings)` - Check if OpenAI API key is available
|
||||
@@ -29,8 +29,8 @@ Shared utility functions for environment configuration and parsing.
|
||||
Handlers for checking Graphiti/memory system configuration status.
|
||||
|
||||
**Exports:**
|
||||
- `loadGraphitiStateFromSpecs(projectPath, autoBuildPath)` - Load Graphiti state from most recent spec
|
||||
- `buildMemoryStatus(projectPath, autoBuildPath, memoryState)` - Build memory status from environment
|
||||
- `loadGraphitiStateFromSpecs(projectPath, aperantPath)` - Load Graphiti state from most recent spec
|
||||
- `buildMemoryStatus(projectPath, aperantPath, memoryState)` - Build memory status from environment
|
||||
- `registerMemoryStatusHandlers(getMainWindow)` - Register IPC handlers
|
||||
|
||||
**IPC Channels:**
|
||||
@@ -136,7 +136,7 @@ test('parseEnvFile handles quotes correctly', () => {
|
||||
import { buildMemoryStatus } from './memory-status-handlers';
|
||||
|
||||
test('buildMemoryStatus returns correct status', () => {
|
||||
const status = buildMemoryStatus('/path/to/project', 'auto-claude');
|
||||
const status = buildMemoryStatus('/path/to/project', 'aperant');
|
||||
expect(status).toHaveProperty('enabled');
|
||||
expect(status).toHaveProperty('available');
|
||||
});
|
||||
@@ -152,7 +152,7 @@ test('buildMemoryStatus returns correct status', () => {
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Project Memory System](../../../../auto-claude/memory.py)
|
||||
- [Graphiti Memory Integration](../../../../auto-claude/graphiti_memory.py)
|
||||
- [Project Memory System](../../../../aperant/memory.py)
|
||||
- [Graphiti Memory Integration](../../../../aperant/graphiti_memory.py)
|
||||
- [LadybugDB Integration](../../ladybug-service.ts)
|
||||
- [IPC Channels](../../../shared/constants.ts)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ipcMain } from 'electron';
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import path from 'path';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { IPC_CHANNELS, AUTO_BUILD_PATHS } from '../../../shared/constants';
|
||||
import { IPC_CHANNELS, APERANT_PATHS } from '../../../shared/constants';
|
||||
import type {
|
||||
IPCResult,
|
||||
ProjectContextData,
|
||||
@@ -47,7 +47,7 @@ function toRendererMemory(m: Memory): RendererMemory {
|
||||
* Load project index from file
|
||||
*/
|
||||
function loadProjectIndex(projectPath: string): ProjectIndex | null {
|
||||
const indexPath = path.join(projectPath, AUTO_BUILD_PATHS.PROJECT_INDEX);
|
||||
const indexPath = path.join(projectPath, APERANT_PATHS.PROJECT_INDEX);
|
||||
if (!existsSync(indexPath)) {
|
||||
return null;
|
||||
}
|
||||
@@ -137,7 +137,7 @@ export function registerProjectContextHandlers(
|
||||
}
|
||||
|
||||
try {
|
||||
const indexOutputPath = path.join(project.path, AUTO_BUILD_PATHS.PROJECT_INDEX);
|
||||
const indexOutputPath = path.join(project.path, APERANT_PATHS.PROJECT_INDEX);
|
||||
|
||||
// Run the TypeScript project indexer (replaces Python subprocess)
|
||||
const projectIndex = runProjectIndexer(project.path, indexOutputPath);
|
||||
|
||||
@@ -7,22 +7,22 @@ export interface EnvironmentVars {
|
||||
}
|
||||
|
||||
export interface GlobalSettings {
|
||||
autoBuildPath?: string;
|
||||
aperantPath?: string;
|
||||
globalOpenAIApiKey?: string;
|
||||
}
|
||||
|
||||
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
||||
|
||||
/**
|
||||
* Get the auto-build source path from settings
|
||||
* Get the aperant source path from settings
|
||||
*/
|
||||
export function getAutoBuildSourcePath(): string | null {
|
||||
export function getAperantSourcePath(): string | null {
|
||||
if (existsSync(settingsPath)) {
|
||||
try {
|
||||
const content = readFileSync(settingsPath, 'utf-8');
|
||||
const settings = JSON.parse(content);
|
||||
if (settings.autoBuildPath && existsSync(settings.autoBuildPath)) {
|
||||
return settings.autoBuildPath;
|
||||
if (settings.aperantPath && existsSync(settings.aperantPath)) {
|
||||
return settings.aperantPath;
|
||||
}
|
||||
} catch {
|
||||
// Fall through to null
|
||||
@@ -63,12 +63,12 @@ export function parseEnvFile(envContent: string): EnvironmentVars {
|
||||
/**
|
||||
* Load environment variables from project .env file
|
||||
*/
|
||||
export function loadProjectEnvVars(projectPath: string, autoBuildPath?: string): EnvironmentVars {
|
||||
if (!autoBuildPath) {
|
||||
export function loadProjectEnvVars(projectPath: string, aperantPath?: string): EnvironmentVars {
|
||||
if (!aperantPath) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const projectEnvPath = path.join(projectPath, autoBuildPath, '.env');
|
||||
const projectEnvPath = path.join(projectPath, aperantPath, '.env');
|
||||
if (!existsSync(projectEnvPath)) {
|
||||
return {};
|
||||
}
|
||||
@@ -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 ||
|
||||
|
||||
@@ -48,8 +48,8 @@ export function registerEnvHandlers(
|
||||
const existingVars = existingContent ? parseEnvFile(existingContent) : {};
|
||||
|
||||
// Update with new values
|
||||
if (config.autoBuildModel !== undefined) {
|
||||
existingVars['AUTO_BUILD_MODEL'] = config.autoBuildModel;
|
||||
if (config.aperantModel !== undefined) {
|
||||
existingVars['APERANT_MODEL'] = config.aperantModel;
|
||||
}
|
||||
if (config.linearApiKey !== undefined) {
|
||||
existingVars['LINEAR_API_KEY'] = config.linearApiKey;
|
||||
@@ -183,11 +183,11 @@ export function registerEnvHandlers(
|
||||
}
|
||||
|
||||
// Generate content with sections
|
||||
const content = `# Auto Claude Framework Environment Variables
|
||||
# Managed by Auto Claude UI
|
||||
const content = `# Aperant Framework Environment Variables
|
||||
# Managed by Aperant UI
|
||||
|
||||
# Model override (OPTIONAL)
|
||||
${existingVars['AUTO_BUILD_MODEL'] ? `AUTO_BUILD_MODEL=${existingVars['AUTO_BUILD_MODEL']}` : '# AUTO_BUILD_MODEL=claude-opus-4-6'}
|
||||
${existingVars['APERANT_MODEL'] ? `APERANT_MODEL=${existingVars['APERANT_MODEL']}` : '# APERANT_MODEL=claude-opus-4-6'}
|
||||
|
||||
# =============================================================================
|
||||
# LINEAR INTEGRATION (OPTIONAL)
|
||||
@@ -217,7 +217,7 @@ ${envLine(existingVars, GITLAB_ENV_KEYS.AUTO_SYNC, 'false')}
|
||||
# GIT/WORKTREE SETTINGS (OPTIONAL)
|
||||
# =============================================================================
|
||||
# Default base branch for worktree creation
|
||||
# If not set, Auto Claude will auto-detect main/master, or fall back to current branch
|
||||
# If not set, Aperant will auto-detect main/master, or fall back to current branch
|
||||
${existingVars['DEFAULT_BRANCH'] ? `DEFAULT_BRANCH=${existingVars['DEFAULT_BRANCH']}` : '# DEFAULT_BRANCH=main'}
|
||||
|
||||
# =============================================================================
|
||||
@@ -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;
|
||||
@@ -302,11 +302,11 @@ ${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_
|
||||
return { success: false, error: 'Project not found' };
|
||||
}
|
||||
|
||||
if (!project.autoBuildPath) {
|
||||
if (!project.aperantPath) {
|
||||
return { success: false, error: 'Project not initialized' };
|
||||
}
|
||||
|
||||
const envPath = path.join(project.path, project.autoBuildPath, '.env');
|
||||
const envPath = path.join(project.path, project.aperantPath, '.env');
|
||||
|
||||
// Load global settings for fallbacks
|
||||
let globalSettings: AppSettings = { ...DEFAULT_APP_SETTINGS };
|
||||
@@ -340,8 +340,8 @@ ${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_
|
||||
}
|
||||
}
|
||||
|
||||
if (vars['AUTO_BUILD_MODEL']) {
|
||||
config.autoBuildModel = vars['AUTO_BUILD_MODEL'];
|
||||
if (vars['APERANT_MODEL']) {
|
||||
config.aperantModel = vars['APERANT_MODEL'];
|
||||
}
|
||||
|
||||
if (vars['LINEAR_API_KEY']) {
|
||||
@@ -494,11 +494,11 @@ ${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_
|
||||
return { success: false, error: 'Project not found' };
|
||||
}
|
||||
|
||||
if (!project.autoBuildPath) {
|
||||
if (!project.aperantPath) {
|
||||
return { success: false, error: 'Project not initialized' };
|
||||
}
|
||||
|
||||
const envPath = path.join(project.path, project.autoBuildPath, '.env');
|
||||
const envPath = path.join(project.path, project.aperantPath, '.env');
|
||||
|
||||
try {
|
||||
// Read existing content if file exists (atomic read, no TOCTOU)
|
||||
|
||||
@@ -242,7 +242,7 @@ function createProject(): Project {
|
||||
id: 'project-1',
|
||||
name: 'Test Project',
|
||||
path: projectPath,
|
||||
autoBuildPath: '.auto-claude',
|
||||
aperantPath: '.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;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import path from 'path';
|
||||
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
||||
import { AUTO_BUILD_PATHS, getSpecsDir } from '../../../shared/constants';
|
||||
import { APERANT_PATHS, getSpecsDir } from '../../../shared/constants';
|
||||
import type { Project, TaskMetadata } from '../../../shared/types';
|
||||
import { withSpecNumberLock } from '../../utils/spec-number-lock';
|
||||
import { debugLog } from './utils/logger';
|
||||
@@ -101,7 +101,7 @@ export async function createSpecForIssue(
|
||||
labels: string[] = [],
|
||||
baseBranch?: string
|
||||
): Promise<SpecCreationData> {
|
||||
const specsBaseDir = getSpecsDir(project.autoBuildPath);
|
||||
const specsBaseDir = getSpecsDir(project.aperantPath);
|
||||
const specsDir = path.join(project.path, specsBaseDir);
|
||||
|
||||
if (!existsSync(specsDir)) {
|
||||
@@ -117,7 +117,7 @@ export async function createSpecForIssue(
|
||||
// Use coordinated spec numbering with lock to prevent collisions
|
||||
return await withSpecNumberLock(project.path, async (lock) => {
|
||||
// Get next spec number from global scan (main + all worktrees)
|
||||
const specNumber = lock.getNextSpecNumber(project.autoBuildPath);
|
||||
const specNumber = lock.getNextSpecNumber(project.aperantPath);
|
||||
const slugifiedTitle = slugifyTitle(safeTitle);
|
||||
const specId = `${String(specNumber).padStart(3, '0')}-${slugifiedTitle}`;
|
||||
|
||||
@@ -139,7 +139,7 @@ export async function createSpecForIssue(
|
||||
};
|
||||
// lgtm[js/http-to-file-access] - specDir is controlled, slugifiedTitle sanitizes input
|
||||
writeFileSync(
|
||||
path.join(specDir, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN),
|
||||
path.join(specDir, APERANT_PATHS.IMPLEMENTATION_PLAN),
|
||||
JSON.stringify(implementationPlan, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
@@ -151,7 +151,7 @@ export async function createSpecForIssue(
|
||||
};
|
||||
// lgtm[js/http-to-file-access] - specDir is controlled, slugifiedTitle sanitizes input
|
||||
writeFileSync(
|
||||
path.join(specDir, AUTO_BUILD_PATHS.REQUIREMENTS),
|
||||
path.join(specDir, APERANT_PATHS.REQUIREMENTS),
|
||||
JSON.stringify(requirements, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
@@ -234,7 +234,7 @@ Please analyze this issue and provide:
|
||||
* Used to immediately update the plan file so the frontend shows the correct status
|
||||
*/
|
||||
export function updateImplementationPlanStatus(specDir: string, status: string): void {
|
||||
const planPath = path.join(specDir, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN);
|
||||
const planPath = path.join(specDir, APERANT_PATHS.IMPLEMENTATION_PLAN);
|
||||
|
||||
try {
|
||||
const content = readFileSync(planPath, 'utf-8');
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -197,8 +197,8 @@ export async function getGitHubTokenForSubprocess(): Promise<string | null> {
|
||||
* Falls back to gh CLI token if GITHUB_TOKEN not in .env
|
||||
*/
|
||||
export function getGitHubConfig(project: Project): GitHubConfig | null {
|
||||
if (!project.autoBuildPath) return null;
|
||||
const envPath = path.join(project.path, project.autoBuildPath, '.env');
|
||||
if (!project.aperantPath) return null;
|
||||
const envPath = path.join(project.path, project.aperantPath, '.env');
|
||||
if (!existsSync(envPath)) return null;
|
||||
|
||||
try {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -388,7 +388,7 @@ export async function createSpecForIssue(
|
||||
const safeProject = sanitizeText(config.project, 200);
|
||||
const safeInstanceUrl = sanitizeInstanceUrl(config.instanceUrl);
|
||||
|
||||
const specsDir = path.join(project.path, project.autoBuildPath, 'specs');
|
||||
const specsDir = path.join(project.path, project.aperantPath, 'specs');
|
||||
|
||||
// Ensure specs directory exists
|
||||
await mkdir(specsDir, { recursive: true });
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user