@bamptee/aia-code 2.0.11 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +408 -34
- package/package.json +11 -2
- package/src/constants.js +24 -0
- package/src/providers/anthropic.js +2 -21
- package/src/providers/cli-runner.js +5 -3
- package/src/services/agent-sessions.js +110 -0
- package/src/services/apps.js +132 -0
- package/src/services/config.js +41 -0
- package/src/services/feature.js +28 -7
- package/src/services/model-call.js +2 -2
- package/src/services/runner.js +23 -1
- package/src/services/status.js +69 -1
- package/src/services/test-quick.js +229 -0
- package/src/services/worktrunk.js +135 -21
- package/src/types/test-quick.js +88 -0
- package/src/ui/api/config.js +28 -1
- package/src/ui/api/features.js +160 -50
- package/src/ui/api/index.js +2 -0
- package/src/ui/api/test-quick.js +207 -0
- package/src/ui/api/worktrunk.js +63 -25
- package/src/ui/public/components/config-view.js +95 -0
- package/src/ui/public/components/dashboard.js +823 -163
- package/src/ui/public/components/feature-detail.js +517 -124
- package/src/ui/public/components/test-quick.js +276 -0
- package/src/ui/public/components/worktrunk-panel.js +187 -25
- package/src/ui/public/index.html +13 -0
- package/src/ui/public/main.js +5 -1
- package/src/ui/server.js +97 -67
package/README.md
CHANGED
|
@@ -4,6 +4,19 @@ CLI tool that orchestrates AI-assisted development workflows using a `.aia` fold
|
|
|
4
4
|
|
|
5
5
|
AIA structures your feature development into steps (brief, spec, tech-spec, dev-plan, implement, etc.), builds rich prompts from project context and knowledge files, and delegates execution to AI CLI tools (Claude Code, Codex CLI, Gemini CLI) with weighted random model selection.
|
|
6
6
|
|
|
7
|
+
## Table of contents
|
|
8
|
+
|
|
9
|
+
- [Quick start](#quick-start)
|
|
10
|
+
- [Prerequisites](#prerequisites)
|
|
11
|
+
- [Commands](#commands)
|
|
12
|
+
- [Integrate into an existing project](#integrate-into-an-existing-project)
|
|
13
|
+
- [Web UI](#web-ui)
|
|
14
|
+
- [Feature workflow](#feature-workflow)
|
|
15
|
+
- [Prompt assembly](#prompt-assembly)
|
|
16
|
+
- [Project structure](#project-structure)
|
|
17
|
+
- [Dependencies](#dependencies)
|
|
18
|
+
- [Worktrunk Integration](#worktrunk-integration)
|
|
19
|
+
|
|
7
20
|
## Quick start
|
|
8
21
|
|
|
9
22
|
```bash
|
|
@@ -400,6 +413,37 @@ aia repo scan
|
|
|
400
413
|
|
|
401
414
|
Generates `.aia/repo-map.json` -- a categorized index of your source files (services, models, routes, controllers, middleware, utils, config). Useful as additional context for prompts.
|
|
402
415
|
|
|
416
|
+
## Web UI
|
|
417
|
+
|
|
418
|
+
Launch the local web interface to manage features visually:
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
aia ui
|
|
422
|
+
# Opens http://localhost:3000
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Dashboard
|
|
426
|
+
|
|
427
|
+
- View all features with their current step and progress
|
|
428
|
+
- Create new features
|
|
429
|
+
- Delete features
|
|
430
|
+
- Quick access to run next step
|
|
431
|
+
|
|
432
|
+
### Feature detail
|
|
433
|
+
|
|
434
|
+
- Execute steps with real-time log streaming (SSE)
|
|
435
|
+
- View step outputs (specs, plans, code)
|
|
436
|
+
- Reset steps to re-run them
|
|
437
|
+
- Edit `init.md` directly in the UI
|
|
438
|
+
|
|
439
|
+
### Integrated terminal
|
|
440
|
+
|
|
441
|
+
The UI includes a full terminal emulator (xterm.js + node-pty). Open a shell directly in your project directory without leaving the browser.
|
|
442
|
+
|
|
443
|
+
### Config editor
|
|
444
|
+
|
|
445
|
+
Edit your `.aia/config.yaml` directly in the UI with syntax highlighting and validation.
|
|
446
|
+
|
|
403
447
|
## Feature workflow
|
|
404
448
|
|
|
405
449
|
Each feature follows a fixed pipeline of 8 steps:
|
|
@@ -470,48 +514,378 @@ The full prompt is piped to the CLI tool via stdin, so there are no argument len
|
|
|
470
514
|
|
|
471
515
|
```
|
|
472
516
|
bin/
|
|
473
|
-
aia.js
|
|
517
|
+
aia.js # CLI entrypoint
|
|
474
518
|
src/
|
|
475
|
-
cli.js
|
|
476
|
-
constants.js
|
|
477
|
-
models.js
|
|
478
|
-
logger.js
|
|
479
|
-
knowledge-loader.js
|
|
480
|
-
prompt-builder.js
|
|
481
|
-
utils.js
|
|
519
|
+
cli.js # Commander program, registers commands
|
|
520
|
+
constants.js # Shared constants (dirs, steps, icons)
|
|
521
|
+
models.js # Config loader + validation, weighted model selection
|
|
522
|
+
logger.js # Execution log writer
|
|
523
|
+
knowledge-loader.js # Recursive markdown loader by category
|
|
524
|
+
prompt-builder.js # Assembles full prompt from all sources
|
|
525
|
+
utils.js # Shared filesystem helpers
|
|
482
526
|
commands/
|
|
483
|
-
init.js
|
|
484
|
-
feature.js
|
|
485
|
-
run.js
|
|
486
|
-
next.js
|
|
487
|
-
iterate.js
|
|
488
|
-
quick.js
|
|
489
|
-
status.js
|
|
490
|
-
reset.js
|
|
491
|
-
repo.js
|
|
527
|
+
init.js # aia init
|
|
528
|
+
feature.js # aia feature <name>
|
|
529
|
+
run.js # aia run <step> <feature>
|
|
530
|
+
next.js # aia next <feature>
|
|
531
|
+
iterate.js # aia iterate <step> <feature> <instructions>
|
|
532
|
+
quick.js # aia quick <name> [description]
|
|
533
|
+
status.js # aia status <feature>
|
|
534
|
+
reset.js # aia reset <step> <feature>
|
|
535
|
+
repo.js # aia repo scan
|
|
536
|
+
ui.js # aia ui
|
|
492
537
|
providers/
|
|
493
|
-
registry.js
|
|
494
|
-
cli-runner.js
|
|
495
|
-
openai.js
|
|
496
|
-
anthropic.js
|
|
497
|
-
gemini.js
|
|
538
|
+
registry.js # Model name + aliases -> provider routing
|
|
539
|
+
cli-runner.js # Shared CLI spawn (streaming, idle timeout, verbose)
|
|
540
|
+
openai.js # codex exec
|
|
541
|
+
anthropic.js # claude -p
|
|
542
|
+
gemini.js # gemini
|
|
498
543
|
services/
|
|
499
|
-
scaffold.js
|
|
500
|
-
config.js
|
|
501
|
-
feature.js
|
|
502
|
-
status.js
|
|
503
|
-
runner.js
|
|
504
|
-
model-call.js
|
|
505
|
-
repo-scan.js
|
|
544
|
+
scaffold.js # .aia/ folder creation
|
|
545
|
+
config.js # Default config generation
|
|
546
|
+
feature.js # Feature workspace creation + validation
|
|
547
|
+
status.js # status.yaml read/write/reset
|
|
548
|
+
runner.js # Step execution orchestrator
|
|
549
|
+
model-call.js # Provider dispatch
|
|
550
|
+
repo-scan.js # Codebase scanner + categorizer
|
|
551
|
+
agent-sessions.js # Real-time agent session tracking (SSE)
|
|
552
|
+
apps.js # Monorepo app/submodule detection
|
|
553
|
+
worktrunk.js # Worktrunk git worktree integration
|
|
554
|
+
types/
|
|
555
|
+
test-quick.js # Type definitions and validators
|
|
556
|
+
ui/
|
|
557
|
+
server.js # Express server for web UI
|
|
558
|
+
router.js # API route registration
|
|
559
|
+
api/
|
|
560
|
+
features.js # Feature CRUD + step execution
|
|
561
|
+
config.js # Config read/write endpoints
|
|
562
|
+
worktrunk.js # Worktree management endpoints
|
|
563
|
+
logs.js # Log streaming
|
|
564
|
+
public/
|
|
565
|
+
index.html # SPA entry point
|
|
566
|
+
main.js # App initialization
|
|
567
|
+
components/
|
|
568
|
+
dashboard.js # Feature list + status overview
|
|
569
|
+
feature-detail.js # Step execution + outputs
|
|
570
|
+
config-view.js # Config editor
|
|
571
|
+
terminal.js # Integrated xterm terminal
|
|
572
|
+
worktrunk-panel.js # Worktree management UI
|
|
506
573
|
```
|
|
507
574
|
|
|
508
575
|
## Dependencies
|
|
509
576
|
|
|
510
|
-
|
|
577
|
+
Runtime dependencies:
|
|
511
578
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
579
|
+
| Package | Purpose |
|
|
580
|
+
|---------|---------|
|
|
581
|
+
| `commander` | CLI framework |
|
|
582
|
+
| `yaml` | YAML parse/stringify |
|
|
583
|
+
| `fs-extra` | Filesystem utilities |
|
|
584
|
+
| `chalk` | Terminal colors |
|
|
585
|
+
| `@iarna/toml` | TOML parsing (for `wt.toml`) |
|
|
586
|
+
| `ws` | WebSocket server (UI real-time updates) |
|
|
587
|
+
| `node-pty` | Pseudo-terminal (UI integrated terminal) |
|
|
588
|
+
| `xterm` + `xterm-addon-fit` | Terminal emulator (UI) |
|
|
589
|
+
| `busboy` | Multipart form parsing |
|
|
516
590
|
|
|
517
591
|
AI calls use `child_process.spawn` to delegate to installed CLI tools. No API keys needed -- each CLI manages its own authentication.
|
|
592
|
+
|
|
593
|
+
## Worktrunk Integration
|
|
594
|
+
|
|
595
|
+
AIA integrates with [Worktrunk](https://github.com/bamptee/worktrunk) (`wt`) to create isolated development environments for each feature using git worktrees.
|
|
596
|
+
|
|
597
|
+
### Why Worktrunk?
|
|
598
|
+
|
|
599
|
+
- **Isolation**: Each feature gets its own directory and branch, no stashing needed
|
|
600
|
+
- **Services**: Run separate Docker containers per feature (database, cache, etc.)
|
|
601
|
+
- **Parallel work**: Work on multiple features simultaneously without conflicts
|
|
602
|
+
- **Clean state**: Delete the worktree when done, main branch stays untouched
|
|
603
|
+
|
|
604
|
+
### Installation
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
# Install Worktrunk CLI
|
|
608
|
+
cargo install worktrunk
|
|
609
|
+
|
|
610
|
+
# Verify installation
|
|
611
|
+
wt --version
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Quick Start
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
# In the AIA UI, click "Create Worktree" on any feature
|
|
618
|
+
# Or via CLI:
|
|
619
|
+
wt switch -c feature/my-feature
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Configuration
|
|
623
|
+
|
|
624
|
+
Create `wt.toml` at the root of your project:
|
|
625
|
+
|
|
626
|
+
```toml
|
|
627
|
+
# wt.toml - Worktrunk configuration
|
|
628
|
+
|
|
629
|
+
[worktree]
|
|
630
|
+
# Directory where worktrees are created (relative to repo root)
|
|
631
|
+
# Default: "../<repo-name>-wt"
|
|
632
|
+
base_path = "../my-project-wt"
|
|
633
|
+
|
|
634
|
+
# Branch prefix for feature worktrees
|
|
635
|
+
# AIA uses "feature/" by default
|
|
636
|
+
branch_prefix = "feature/"
|
|
637
|
+
|
|
638
|
+
[hooks]
|
|
639
|
+
# Hooks run automatically when creating/removing worktrees
|
|
640
|
+
# Available hooks: post_create, pre_remove, post_remove
|
|
641
|
+
|
|
642
|
+
# Run after worktree is created
|
|
643
|
+
post_create = [
|
|
644
|
+
"cp .env.example .env",
|
|
645
|
+
"docker-compose -f docker-compose.wt.yml up -d",
|
|
646
|
+
"npm install",
|
|
647
|
+
]
|
|
648
|
+
|
|
649
|
+
# Run before worktree is removed
|
|
650
|
+
pre_remove = [
|
|
651
|
+
"docker-compose -f docker-compose.wt.yml down -v",
|
|
652
|
+
]
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Docker Services per Feature
|
|
656
|
+
|
|
657
|
+
Create `docker-compose.wt.yml` for services that should run in each worktree:
|
|
658
|
+
|
|
659
|
+
```yaml
|
|
660
|
+
# docker-compose.wt.yml - Services for isolated development
|
|
661
|
+
|
|
662
|
+
version: '3.8'
|
|
663
|
+
|
|
664
|
+
# Use environment variable for unique container names
|
|
665
|
+
# WT_BRANCH is set by worktrunk (e.g., "feature-my-feature")
|
|
666
|
+
x-branch: &branch ${WT_BRANCH:-dev}
|
|
667
|
+
|
|
668
|
+
services:
|
|
669
|
+
postgres:
|
|
670
|
+
image: postgres:16-alpine
|
|
671
|
+
container_name: ${WT_BRANCH:-dev}-postgres
|
|
672
|
+
environment:
|
|
673
|
+
POSTGRES_DB: myapp_dev
|
|
674
|
+
POSTGRES_USER: dev
|
|
675
|
+
POSTGRES_PASSWORD: dev
|
|
676
|
+
ports:
|
|
677
|
+
- "${DB_PORT:-5432}:5432"
|
|
678
|
+
volumes:
|
|
679
|
+
- postgres_data:/var/lib/postgresql/data
|
|
680
|
+
|
|
681
|
+
redis:
|
|
682
|
+
image: redis:7-alpine
|
|
683
|
+
container_name: ${WT_BRANCH:-dev}-redis
|
|
684
|
+
ports:
|
|
685
|
+
- "${REDIS_PORT:-6379}:6379"
|
|
686
|
+
|
|
687
|
+
mailhog:
|
|
688
|
+
image: mailhog/mailhog
|
|
689
|
+
container_name: ${WT_BRANCH:-dev}-mailhog
|
|
690
|
+
ports:
|
|
691
|
+
- "${MAIL_UI_PORT:-8025}:8025"
|
|
692
|
+
- "${MAIL_SMTP_PORT:-1025}:1025"
|
|
693
|
+
|
|
694
|
+
volumes:
|
|
695
|
+
postgres_data:
|
|
696
|
+
name: ${WT_BRANCH:-dev}-postgres-data
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Port Management
|
|
700
|
+
|
|
701
|
+
To avoid port conflicts between worktrees, use a `.env` file with dynamic ports:
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
# .env.example - Copy to .env in each worktree
|
|
705
|
+
|
|
706
|
+
# Each worktree should use different ports
|
|
707
|
+
# Tip: Use feature hash or manual assignment
|
|
708
|
+
DB_PORT=5432
|
|
709
|
+
REDIS_PORT=6379
|
|
710
|
+
MAIL_UI_PORT=8025
|
|
711
|
+
MAIL_SMTP_PORT=1025
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
Or use a hook to auto-assign ports:
|
|
715
|
+
|
|
716
|
+
```toml
|
|
717
|
+
# wt.toml
|
|
718
|
+
[hooks]
|
|
719
|
+
post_create = [
|
|
720
|
+
# Generate random ports based on branch name hash
|
|
721
|
+
'''
|
|
722
|
+
HASH=$(echo "$WT_BRANCH" | md5sum | cut -c1-4)
|
|
723
|
+
PORT_OFFSET=$((16#$HASH % 1000))
|
|
724
|
+
cat > .env << EOF
|
|
725
|
+
DB_PORT=$((5432 + PORT_OFFSET))
|
|
726
|
+
REDIS_PORT=$((6379 + PORT_OFFSET))
|
|
727
|
+
MAIL_UI_PORT=$((8025 + PORT_OFFSET))
|
|
728
|
+
EOF
|
|
729
|
+
''',
|
|
730
|
+
"docker-compose -f docker-compose.wt.yml up -d",
|
|
731
|
+
]
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### Full Example Setup
|
|
735
|
+
|
|
736
|
+
Here's a complete setup for a Node.js project with PostgreSQL, Redis, and S3 (MinIO):
|
|
737
|
+
|
|
738
|
+
```
|
|
739
|
+
my-project/
|
|
740
|
+
├── wt.toml # Worktrunk config
|
|
741
|
+
├── docker-compose.wt.yml # Services template
|
|
742
|
+
├── .env.example # Environment template
|
|
743
|
+
├── scripts/
|
|
744
|
+
│ └── setup-worktree.sh # Custom setup script
|
|
745
|
+
└── .aia/
|
|
746
|
+
└── features/
|
|
747
|
+
└── my-feature/
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**wt.toml**:
|
|
751
|
+
```toml
|
|
752
|
+
[worktree]
|
|
753
|
+
base_path = "../my-project-wt"
|
|
754
|
+
|
|
755
|
+
[hooks]
|
|
756
|
+
post_create = [
|
|
757
|
+
"bash scripts/setup-worktree.sh",
|
|
758
|
+
]
|
|
759
|
+
|
|
760
|
+
pre_remove = [
|
|
761
|
+
"docker-compose -f docker-compose.wt.yml down -v --remove-orphans",
|
|
762
|
+
]
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
**scripts/setup-worktree.sh**:
|
|
766
|
+
```bash
|
|
767
|
+
#!/bin/bash
|
|
768
|
+
set -e
|
|
769
|
+
|
|
770
|
+
echo "🔧 Setting up worktree: $WT_BRANCH"
|
|
771
|
+
|
|
772
|
+
# Copy environment template
|
|
773
|
+
cp .env.example .env
|
|
774
|
+
|
|
775
|
+
# Generate unique ports based on branch
|
|
776
|
+
HASH=$(echo "$WT_BRANCH" | md5sum | cut -c1-4)
|
|
777
|
+
OFFSET=$((16#$HASH % 900 + 100))
|
|
778
|
+
|
|
779
|
+
sed -i "s/DB_PORT=.*/DB_PORT=$((5000 + OFFSET))/" .env
|
|
780
|
+
sed -i "s/REDIS_PORT=.*/REDIS_PORT=$((6000 + OFFSET))/" .env
|
|
781
|
+
sed -i "s/MINIO_PORT=.*/MINIO_PORT=$((9000 + OFFSET))/" .env
|
|
782
|
+
sed -i "s/APP_PORT=.*/APP_PORT=$((3000 + OFFSET))/" .env
|
|
783
|
+
|
|
784
|
+
echo "📦 Starting Docker services..."
|
|
785
|
+
docker-compose -f docker-compose.wt.yml up -d
|
|
786
|
+
|
|
787
|
+
echo "📚 Installing dependencies..."
|
|
788
|
+
npm install
|
|
789
|
+
|
|
790
|
+
echo "🗃️ Running migrations..."
|
|
791
|
+
npm run db:migrate
|
|
792
|
+
|
|
793
|
+
echo "✅ Worktree ready!"
|
|
794
|
+
echo " App: http://localhost:$((3000 + OFFSET))"
|
|
795
|
+
echo " Database: localhost:$((5000 + OFFSET))"
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
**docker-compose.wt.yml**:
|
|
799
|
+
```yaml
|
|
800
|
+
version: '3.8'
|
|
801
|
+
|
|
802
|
+
services:
|
|
803
|
+
postgres:
|
|
804
|
+
image: postgres:16-alpine
|
|
805
|
+
container_name: ${WT_BRANCH:-dev}-postgres
|
|
806
|
+
environment:
|
|
807
|
+
POSTGRES_DB: app_dev
|
|
808
|
+
POSTGRES_USER: dev
|
|
809
|
+
POSTGRES_PASSWORD: dev
|
|
810
|
+
ports:
|
|
811
|
+
- "${DB_PORT:-5432}:5432"
|
|
812
|
+
volumes:
|
|
813
|
+
- pg_data:/var/lib/postgresql/data
|
|
814
|
+
healthcheck:
|
|
815
|
+
test: ["CMD-SHELL", "pg_isready -U dev"]
|
|
816
|
+
interval: 5s
|
|
817
|
+
timeout: 5s
|
|
818
|
+
retries: 5
|
|
819
|
+
|
|
820
|
+
redis:
|
|
821
|
+
image: redis:7-alpine
|
|
822
|
+
container_name: ${WT_BRANCH:-dev}-redis
|
|
823
|
+
ports:
|
|
824
|
+
- "${REDIS_PORT:-6379}:6379"
|
|
825
|
+
healthcheck:
|
|
826
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
827
|
+
interval: 5s
|
|
828
|
+
timeout: 5s
|
|
829
|
+
retries: 5
|
|
830
|
+
|
|
831
|
+
minio:
|
|
832
|
+
image: minio/minio
|
|
833
|
+
container_name: ${WT_BRANCH:-dev}-minio
|
|
834
|
+
command: server /data --console-address ":9001"
|
|
835
|
+
environment:
|
|
836
|
+
MINIO_ROOT_USER: minioadmin
|
|
837
|
+
MINIO_ROOT_PASSWORD: minioadmin
|
|
838
|
+
ports:
|
|
839
|
+
- "${MINIO_PORT:-9000}:9000"
|
|
840
|
+
- "${MINIO_CONSOLE_PORT:-9001}:9001"
|
|
841
|
+
volumes:
|
|
842
|
+
- minio_data:/data
|
|
843
|
+
|
|
844
|
+
volumes:
|
|
845
|
+
pg_data:
|
|
846
|
+
name: ${WT_BRANCH:-dev}-pg-data
|
|
847
|
+
minio_data:
|
|
848
|
+
name: ${WT_BRANCH:-dev}-minio-data
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### Using Worktrunk in AIA UI
|
|
852
|
+
|
|
853
|
+
1. **Create a feature**: `aia feature my-feature` or via UI
|
|
854
|
+
2. **Open the feature** in the UI
|
|
855
|
+
3. **Click "Create Worktree"** in the Worktrunk panel
|
|
856
|
+
- Runs `wt switch -c feature/my-feature`
|
|
857
|
+
- Executes `post_create` hooks (Docker services, npm install, etc.)
|
|
858
|
+
4. **Open Terminal** to work in the worktree directory
|
|
859
|
+
5. **View Docker Containers** directly in the UI
|
|
860
|
+
- Start/Stop individual containers
|
|
861
|
+
- Open a shell inside any running container
|
|
862
|
+
6. **When done**: Click "Remove" to clean up
|
|
863
|
+
- Runs `pre_remove` hooks (docker-compose down)
|
|
864
|
+
- Removes the worktree directory
|
|
865
|
+
|
|
866
|
+
### Troubleshooting
|
|
867
|
+
|
|
868
|
+
**"Worktrunk not installed"**
|
|
869
|
+
```bash
|
|
870
|
+
cargo install worktrunk
|
|
871
|
+
# Make sure ~/.cargo/bin is in your PATH
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
**Containers not showing in UI**
|
|
875
|
+
- Containers must have names matching pattern: `feature-<name>-*`
|
|
876
|
+
- Check Docker is running: `docker ps`
|
|
877
|
+
- Click "Refresh Containers" in the UI
|
|
878
|
+
|
|
879
|
+
**Port conflicts**
|
|
880
|
+
- Each worktree needs unique ports
|
|
881
|
+
- Use the port auto-assignment hook above
|
|
882
|
+
- Or manually set ports in `.env` per worktree
|
|
883
|
+
|
|
884
|
+
**Worktree creation fails**
|
|
885
|
+
```bash
|
|
886
|
+
# Check git status - uncommitted changes can block
|
|
887
|
+
git status
|
|
888
|
+
|
|
889
|
+
# Manual worktree creation
|
|
890
|
+
wt switch -c feature/my-feature --force
|
|
891
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamptee/aia-code",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.13",
|
|
4
4
|
"description": "AI Architecture Assistant - orchestrate AI-assisted development workflows via CLI tools (Claude, Codex, Gemini)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,16 @@
|
|
|
30
30
|
"url": "git+https://github.com/bamptee/aia-code.git"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"start": "node bin/aia.js"
|
|
33
|
+
"start": "node bin/aia.js",
|
|
34
|
+
"test": "node --test tests/*.test.js",
|
|
35
|
+
"test:service": "node --test tests/test-quick.service.test.js",
|
|
36
|
+
"test:api": "node --test tests/test-quick.api.test.js",
|
|
37
|
+
"test:delete": "node --test tests/feature-delete.*.test.js",
|
|
38
|
+
"test:delete:service": "node --test tests/feature-delete.service.test.js",
|
|
39
|
+
"test:delete:api": "node --test tests/feature-delete.api.test.js",
|
|
40
|
+
"test:ux": "node --test tests/feature-list-ux.*.test.js",
|
|
41
|
+
"test:ux:unit": "node --test tests/feature-list-ux.unit.test.js",
|
|
42
|
+
"test:ux:api": "node --test tests/feature-list-ux.api.test.js"
|
|
34
43
|
},
|
|
35
44
|
"dependencies": {
|
|
36
45
|
"@iarna/toml": "^2.2.5",
|
package/src/constants.js
CHANGED
|
@@ -48,3 +48,27 @@ export const STEP_STATUS = {
|
|
|
48
48
|
DONE: 'done',
|
|
49
49
|
ERROR: 'error',
|
|
50
50
|
};
|
|
51
|
+
|
|
52
|
+
export const FEATURE_TYPES = Object.freeze(['feature', 'bug']);
|
|
53
|
+
export const DEFAULT_FEATURE_TYPE = 'feature';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Feature deletion status filter options
|
|
57
|
+
*/
|
|
58
|
+
export const DELETION_FILTER = Object.freeze({
|
|
59
|
+
ACTIVE: 'active',
|
|
60
|
+
DELETED: 'deleted',
|
|
61
|
+
ALL: 'all',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const APP_ICONS = {
|
|
65
|
+
react: '\u269B',
|
|
66
|
+
vue: '\uD83D\uDC9A',
|
|
67
|
+
angular: '\uD83C\uDD70',
|
|
68
|
+
node: '\uD83D\uDCE6',
|
|
69
|
+
go: '\uD83D\uDD27',
|
|
70
|
+
java: '\u2615',
|
|
71
|
+
python: '\uD83D\uDC0D',
|
|
72
|
+
rust: '\uD83E\uDD80',
|
|
73
|
+
generic: '\uD83D\uDCC1',
|
|
74
|
+
};
|
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { runCli } from './cli-runner.js';
|
|
7
7
|
|
|
8
|
-
export async function generate(prompt, model, { verbose = false, apply = false, onData } = {}) {
|
|
8
|
+
export async function generate(prompt, model, { verbose = false, apply = false, onData, cwd } = {}) {
|
|
9
9
|
const args = ['-p'];
|
|
10
10
|
if (model) {
|
|
11
11
|
args.push('--model', model);
|
|
@@ -17,24 +17,5 @@ export async function generate(prompt, model, { verbose = false, apply = false,
|
|
|
17
17
|
args.push('--verbose');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
// Write context to a temp file and give a short prompt to read it.
|
|
22
|
-
if (apply) {
|
|
23
|
-
const tmpFile = path.join(tmpdir(), `aia-prompt-${randomBytes(6).toString('hex')}.md`);
|
|
24
|
-
writeFileSync(tmpFile, prompt, 'utf-8');
|
|
25
|
-
console.error(chalk.gray(`[AI] Prompt written to temp file (${(prompt.length / 1024).toFixed(1)}KB): ${tmpFile}`));
|
|
26
|
-
const shortPrompt = `Read the file at ${tmpFile} — it contains your full instructions and context. Follow them exactly.`;
|
|
27
|
-
args.push('-');
|
|
28
|
-
console.error(chalk.gray(`[AI] Spawning: claude ${args.join(' ')}`));
|
|
29
|
-
console.error(chalk.gray(`[AI] Waiting for Claude agent response...`));
|
|
30
|
-
try {
|
|
31
|
-
return await runCli('claude', args, { stdin: shortPrompt, verbose: verbose || apply, apply, onData });
|
|
32
|
-
} finally {
|
|
33
|
-
try { unlinkSync(tmpFile); } catch {}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
args.push('-');
|
|
38
|
-
console.error(chalk.gray(`[AI] Spawning: claude ${args.join(' ')} (${(prompt.length / 1024).toFixed(1)}KB prompt)`));
|
|
39
|
-
return runCli('claude', args, { stdin: prompt, verbose: verbose || apply, apply, onData });
|
|
20
|
+
return runCli('claude', args, { stdin: prompt, verbose: verbose || apply, apply, onData, cwd });
|
|
40
21
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
2
3
|
import chalk from 'chalk';
|
|
3
4
|
|
|
4
5
|
const DEFAULT_IDLE_TIMEOUT_MS = 180_000;
|
|
5
6
|
const AGENT_IDLE_TIMEOUT_MS = 600_000;
|
|
6
7
|
|
|
7
|
-
export function runCli(command, args, { stdin: stdinData, verbose = false, apply = false, idleTimeoutMs, onData } = {}) {
|
|
8
|
+
export function runCli(command, args, { stdin: stdinData, verbose = false, apply = false, idleTimeoutMs, onData, cwd } = {}) {
|
|
8
9
|
if (!idleTimeoutMs) {
|
|
9
10
|
idleTimeoutMs = apply ? AGENT_IDLE_TIMEOUT_MS : DEFAULT_IDLE_TIMEOUT_MS;
|
|
10
11
|
}
|
|
@@ -12,7 +13,8 @@ export function runCli(command, args, { stdin: stdinData, verbose = false, apply
|
|
|
12
13
|
const { CLAUDECODE, ...cleanEnv } = process.env;
|
|
13
14
|
const child = spawn(command, args, {
|
|
14
15
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
15
|
-
env: { ...
|
|
16
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
17
|
+
cwd,
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
const chunks = [];
|
|
@@ -45,7 +47,7 @@ export function runCli(command, args, { stdin: stdinData, verbose = false, apply
|
|
|
45
47
|
console.error(chalk.gray('[AI] First stdout received — agent is running'));
|
|
46
48
|
}
|
|
47
49
|
const text = data.toString();
|
|
48
|
-
process.stdout.write(text);
|
|
50
|
+
if (verbose) process.stdout.write(text);
|
|
49
51
|
chunks.push(text);
|
|
50
52
|
if (onData) onData({ type: 'stdout', text });
|
|
51
53
|
resetTimer();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Agent session tracking - in-memory Map with log buffer
|
|
2
|
+
// Map<featureName, { step, startedAt, logs: Array<{text, type, ts}>, sseClients: Set<Response> }>
|
|
3
|
+
const sessions = new Map();
|
|
4
|
+
const MAX_LOGS = 500;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Start a new agent session for a feature
|
|
8
|
+
* @param {string} feature - Feature name
|
|
9
|
+
* @param {string} step - Step name
|
|
10
|
+
*/
|
|
11
|
+
export function startSession(feature, step) {
|
|
12
|
+
sessions.set(feature, {
|
|
13
|
+
step,
|
|
14
|
+
startedAt: Date.now(),
|
|
15
|
+
logs: [],
|
|
16
|
+
sseClients: new Set(),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* End an agent session and notify all SSE clients
|
|
22
|
+
* @param {string} feature - Feature name
|
|
23
|
+
*/
|
|
24
|
+
export function endSession(feature) {
|
|
25
|
+
const session = sessions.get(feature);
|
|
26
|
+
if (session) {
|
|
27
|
+
// Notify all SSE clients that session ended
|
|
28
|
+
for (const client of session.sseClients) {
|
|
29
|
+
try {
|
|
30
|
+
client.write(`event: done\ndata: {}\n\n`);
|
|
31
|
+
} catch {
|
|
32
|
+
// Client may have disconnected
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
sessions.delete(feature);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get an active session for a feature
|
|
41
|
+
* @param {string} feature - Feature name
|
|
42
|
+
* @returns {Object|null} Session object or null
|
|
43
|
+
*/
|
|
44
|
+
export function getSession(feature) {
|
|
45
|
+
return sessions.get(feature) || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Append a log entry to a session's buffer
|
|
50
|
+
* @param {string} feature - Feature name
|
|
51
|
+
* @param {string} text - Log text
|
|
52
|
+
* @param {string} type - Log type ('stdout' or 'stderr')
|
|
53
|
+
*/
|
|
54
|
+
export function appendLog(feature, text, type = 'stdout') {
|
|
55
|
+
const session = sessions.get(feature);
|
|
56
|
+
if (!session) return;
|
|
57
|
+
|
|
58
|
+
session.logs.push({ text, type, ts: Date.now() });
|
|
59
|
+
if (session.logs.length > MAX_LOGS) session.logs.shift();
|
|
60
|
+
|
|
61
|
+
// Broadcast to all SSE clients
|
|
62
|
+
for (const client of session.sseClients) {
|
|
63
|
+
try {
|
|
64
|
+
client.write(`event: log\ndata: ${JSON.stringify({ text, type })}\n\n`);
|
|
65
|
+
} catch {
|
|
66
|
+
// Client may have disconnected
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Register an SSE client for a session
|
|
73
|
+
* @param {string} feature - Feature name
|
|
74
|
+
* @param {Response} res - HTTP response object
|
|
75
|
+
*/
|
|
76
|
+
export function addSseClient(feature, res) {
|
|
77
|
+
const session = sessions.get(feature);
|
|
78
|
+
if (session) session.sseClients.add(res);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Unregister an SSE client from a session
|
|
83
|
+
* @param {string} feature - Feature name
|
|
84
|
+
* @param {Response} res - HTTP response object
|
|
85
|
+
*/
|
|
86
|
+
export function removeSseClient(feature, res) {
|
|
87
|
+
const session = sessions.get(feature);
|
|
88
|
+
if (session) session.sseClients.delete(res);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if a feature has an active session
|
|
93
|
+
* @param {string} feature - Feature name
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
export function isRunning(feature) {
|
|
97
|
+
return sessions.has(feature);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get all running sessions (for dashboard)
|
|
102
|
+
* @returns {Object} Map of feature name to session summary
|
|
103
|
+
*/
|
|
104
|
+
export function getAllRunningSessions() {
|
|
105
|
+
const result = {};
|
|
106
|
+
for (const [feature, session] of sessions) {
|
|
107
|
+
result[feature] = { step: session.step, startedAt: session.startedAt };
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|