T08: HTTP server + API za taskove
- Gin HTTP server sa dashboard i API endpointima - JSON API: GET /api/tasks, GET /api/task/:id, POST /api/task/:id/move - HTML dashboard sa Kanban prikazom (5 kolona) - HTMX za interaktivnost (klik na task → detalj panel) - Embedded static fajlovi (htmx.min.js, sortable.min.js) - Config: dodat KAOS_PORT - 10 server testova, 77 ukupno — svi prolaze - Očišćeni duplikati taskova iz v0.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bd62320642
commit
04ef8e75ef
23
CLAUDE.md
23
CLAUDE.md
@ -62,16 +62,17 @@ TASKS/
|
|||||||
└── reports/ ← izveštaji izvršenih taskova
|
└── reports/ ← izveštaji izvršenih taskova
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ko šta radi
|
### Ko šta sme da premesti
|
||||||
|
|
||||||
| Folder | Ko piše | Ko čita | Ko premešta |
|
| Iz → U | Operater (dashboard) | Agent (CLI) |
|
||||||
|--------|---------|---------|-------------|
|
|---------|---------------------|-------------|
|
||||||
| backlog/ | planer | operater | operater → ready/ |
|
| backlog → ready | ✅ | ❌ |
|
||||||
| ready/ | — | agent | agent → active/ |
|
| ready → backlog | ✅ | ❌ |
|
||||||
| active/ | agent | agent | agent → review/ |
|
| ready → active | ❌ | ✅ |
|
||||||
| review/ | planer (odgovori) | operater, agent | operater → done/ ili agent → active/ |
|
| active → review | ❌ | ✅ |
|
||||||
| done/ | — | svi | nikad |
|
| review → done | ✅ | ❌ |
|
||||||
| reports/ | agent | svi | nikad |
|
| review → ready | ✅ | ❌ |
|
||||||
|
| done → bilo gde | ❌ | ❌ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -116,8 +117,8 @@ TASKS/
|
|||||||
|-------|--------|-------|---------|
|
|-------|--------|-------|---------|
|
||||||
| Triage | agents/triage/ | Haiku | 0.1.0 |
|
| Triage | agents/triage/ | Haiku | 0.1.0 |
|
||||||
| Task Manager | agents/task-manager/ | Sonnet/Haiku | 0.1.0 |
|
| Task Manager | agents/task-manager/ | Sonnet/Haiku | 0.1.0 |
|
||||||
| Coder | agents/coder/ | Sonnet/Opus | 0.1.0 |
|
| Coder | agents/coder/ | Sonnet/Opus | 0.2.0 |
|
||||||
| Frontend | agents/frontend/ | Sonnet | 0.1.0 |
|
| Frontend | agents/frontend/ | Sonnet | 0.2.0 |
|
||||||
| Checker | agents/checker/ | Haiku/Opus | 0.1.0 |
|
| Checker | agents/checker/ | Haiku/Opus | 0.1.0 |
|
||||||
| Reporter | agents/reporter/ | Haiku | 0.1.0 |
|
| Reporter | agents/reporter/ | Haiku | 0.1.0 |
|
||||||
| Docs | agents/docs/ | Haiku | 0.1.0 |
|
| Docs | agents/docs/ | Haiku | 0.1.0 |
|
||||||
|
|||||||
@ -161,7 +161,7 @@ Deploy ili dorada
|
|||||||
| Timeout | Ručno podešavanje, operater odlučuje | Feb 2026 |
|
| Timeout | Ručno podešavanje, operater odlučuje | Feb 2026 |
|
||||||
| Troškovi | Praćenje po tasku (tokeni, cena, vreme) | Feb 2026 |
|
| Troškovi | Praćenje po tasku (tokeni, cena, vreme) | Feb 2026 |
|
||||||
| Backend | Go | Feb 2026 |
|
| Backend | Go | Feb 2026 |
|
||||||
| Frontend | React + TypeScript + Vite + Tailwind + shadcn/ui | Feb 2026 |
|
| Frontend | Go templates + HTMX + Sortable.js (nula npm) | Feb 2026 |
|
||||||
| Baza | PostgreSQL (v0.2+) | Feb 2026 |
|
| Baza | PostgreSQL (v0.2+) | Feb 2026 |
|
||||||
| HTTP framework | Gin (v0.2+) | Feb 2026 |
|
| HTTP framework | Gin (v0.2+) | Feb 2026 |
|
||||||
| Engine | `pkg/engine/` javni paket, nula HTTP (v0.2+) | Feb 2026 |
|
| Engine | `pkg/engine/` javni paket, nula HTTP (v0.2+) | Feb 2026 |
|
||||||
|
|||||||
@ -23,24 +23,33 @@
|
|||||||
|
|
||||||
| Folder | Sadržaj | Taskovi |
|
| Folder | Sadržaj | Taskovi |
|
||||||
|--------|---------|---------|
|
|--------|---------|---------|
|
||||||
| backlog/ | Čeka odobrenje | T01 |
|
| backlog/ | Čeka odobrenje | T08, T09, T10 |
|
||||||
| ready/ | Odobren za rad | — |
|
| ready/ | Odobren za rad | — |
|
||||||
| active/ | U izradi | — |
|
| active/ | U izradi | — |
|
||||||
| review/ | Čeka pregled/odgovor | — |
|
| review/ | Čeka pregled/odgovor | — |
|
||||||
| done/ | Završeno | — |
|
| done/ | Završeno | T01, T02, T03, T04, T05, T06, T07 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.1 Taskovi
|
## v0.1 Taskovi — ZAVRŠENO ✅
|
||||||
|
|
||||||
|
| Task | Naslov | Tag | Commit | Testova |
|
||||||
|
|------|--------|-----|--------|---------|
|
||||||
|
| T01 | Inicijalizacija Go projekta | v0.1.1 | f001c53 | 6 |
|
||||||
|
| T02 | Task loader (parsiranje MD) | v0.1.2 | 79bcd52 | 17 |
|
||||||
|
| T03 | Runner (pokretanje Claude Code) | v0.1.4 | 9d2c249 | 7 |
|
||||||
|
| T04 | Checker (build + test + vet) | v0.1.3 | 5d869f5 | 10 |
|
||||||
|
| T05 | Reporter (pisanje izveštaja) | v0.1.5 | 028872b | 10 |
|
||||||
|
| T06 | CLI (komandni interfejs) | v0.1.6 | 38e1e10 | 9 |
|
||||||
|
| T07 | Integracija (end-to-end) | v0.1.7 | b2ece98 | 8 |
|
||||||
|
| **Ukupno** | | | | **67** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sledeće — v0.2 Dashboard
|
||||||
|
|
||||||
| Task | Naslov | Folder | Zavisi od |
|
| Task | Naslov | Folder | Zavisi od |
|
||||||
|------|--------|--------|-----------|
|
|------|--------|--------|-----------|
|
||||||
| T01 | Inicijalizacija Go projekta | backlog | — |
|
| T08 | HTTP server + API | backlog | T07 ✅ |
|
||||||
| T02 | Task loader (parsiranje MD) | — | T01 |
|
| T09 | Dashboard kanban board | backlog | T08 |
|
||||||
| T03 | Runner (pokretanje Claude Code) | — | T02 |
|
| T10 | Drag & Drop | backlog | T09 |
|
||||||
| T04 | Checker (build + test + vet) | — | T01 |
|
|
||||||
| T05 | Reporter (pisanje izveštaja) | — | T03, T04 |
|
|
||||||
| T06 | CLI (komandni interfejs) | — | T02-T05 |
|
|
||||||
| T07 | Integracija (end-to-end) | — | T06 |
|
|
||||||
|
|
||||||
T02-T07 će biti napisani u backlog/ kad T01 bude done.
|
|
||||||
|
|||||||
106
TASKS/backlog/T09.md
Normal file
106
TASKS/backlog/T09.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# T09: Dashboard — Kanban board sa taskovima
|
||||||
|
|
||||||
|
**Kreirao:** planer
|
||||||
|
**Datum:** 2026-02-20
|
||||||
|
**Agent:** coder
|
||||||
|
**Model:** Sonnet
|
||||||
|
**Zavisi od:** T08
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opis
|
||||||
|
|
||||||
|
HTML dashboard sa Kanban prikazom — kolone po stanju
|
||||||
|
(backlog, ready, active, review, done). HTMX za interaktivnost.
|
||||||
|
|
||||||
|
## Fajlovi za kreiranje
|
||||||
|
|
||||||
|
```
|
||||||
|
code/web/
|
||||||
|
├── templates/
|
||||||
|
│ ├── layout.html ← osnovna struktura (head, body, footer)
|
||||||
|
│ ├── dashboard.html ← kanban board
|
||||||
|
│ ├── partials/
|
||||||
|
│ │ ├── column.html ← jedna kolona (HTMX fragment)
|
||||||
|
│ │ ├── task-card.html ← kartica taska
|
||||||
|
│ │ └── task-detail.html ← detalj taska (klik → prikaz sadržaja)
|
||||||
|
└── static/
|
||||||
|
└── style.css ← stilovi za dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
## Izgled
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 🔧 KAOS Dashboard v0.1.7 │
|
||||||
|
├──────────┬──────────┬──────────┬──────────┬─────────────┤
|
||||||
|
│ BACKLOG │ READY │ ACTIVE │ REVIEW │ DONE │
|
||||||
|
│ 2 │ 1 │ - │ - │ 7 │
|
||||||
|
├──────────┼──────────┼──────────┼──────────┼─────────────┤
|
||||||
|
│┌────────┐│┌────────┐│ │ │┌───────────┐│
|
||||||
|
││ T08 │││ T10 ││ │ ││ T01 ✅ ││
|
||||||
|
││ Server │││ Drag ││ │ ││ Go init ││
|
||||||
|
││ Sonnet │││ & Drop ││ │ ││ v0.1.1 ││
|
||||||
|
│└────────┘│└────────┘│ │ │└───────────┘│
|
||||||
|
│┌────────┐│ │ │ │┌───────────┐│
|
||||||
|
││ T09 ││ │ │ ││ T02 ✅ ││
|
||||||
|
││ Dashb. ││ │ │ ││ Loader ││
|
||||||
|
│└────────┘│ │ │ ││ v0.1.2 ││
|
||||||
|
│ │ │ │ │└───────────┘│
|
||||||
|
│ │ │ │ │ ... │
|
||||||
|
└──────────┴──────────┴──────────┴──────────┴─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kartica taska
|
||||||
|
|
||||||
|
Prikazuje:
|
||||||
|
- ID (T01, T02...)
|
||||||
|
- Naslov
|
||||||
|
- Agent + Model
|
||||||
|
- Tag verzije (ako je done)
|
||||||
|
- Zavisnosti
|
||||||
|
|
||||||
|
Klik na karticu → HTMX učita detalj:
|
||||||
|
```html
|
||||||
|
<div class="task-card" hx-get="/task/T01" hx-target="#task-detail">
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task detalj panel
|
||||||
|
|
||||||
|
Desna strana ili modal — prikazuje ceo sadržaj task fajla:
|
||||||
|
- Markdown renderovan kao HTML
|
||||||
|
- Dugme za premestanje u sledeći folder
|
||||||
|
- Link do izveštaja (ako postoji)
|
||||||
|
|
||||||
|
## HTMX interakcije
|
||||||
|
|
||||||
|
- Klik na task → `hx-get="/task/{id}"` → prikaz detalja
|
||||||
|
- Dugme "Premesti" → `hx-post="/task/{id}/move?to=ready"` → ažurira kolonu
|
||||||
|
- Auto-refresh → `hx-trigger="every 5s"` na active koloni
|
||||||
|
|
||||||
|
## Pravila
|
||||||
|
|
||||||
|
- Go `html/template` za renderovanje
|
||||||
|
- Mobilno responsive
|
||||||
|
- Poruke na srpskom
|
||||||
|
- Nema JS osim htmx.min.js
|
||||||
|
- CSS grid za kolone
|
||||||
|
|
||||||
|
## Testovi
|
||||||
|
|
||||||
|
- GET / → vraća HTML sa svim kolonama
|
||||||
|
- Proveri da su taskovi u pravim kolonama
|
||||||
|
- HTMX fragment: GET /task/T01 → vraća HTML fragment
|
||||||
|
|
||||||
|
## Očekivani izlaz
|
||||||
|
|
||||||
|
Otvori http://localhost:8080 → vidi kanban board sa taskovima.
|
||||||
|
Klikni na task → vidi detalj.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pitanja
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Odgovori
|
||||||
90
TASKS/backlog/T10.md
Normal file
90
TASKS/backlog/T10.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# T10: Drag & Drop — premesti task prevlačenjem
|
||||||
|
|
||||||
|
**Kreirao:** planer
|
||||||
|
**Datum:** 2026-02-20
|
||||||
|
**Agent:** coder
|
||||||
|
**Model:** Sonnet
|
||||||
|
**Zavisi od:** T09
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opis
|
||||||
|
|
||||||
|
Dodaj Sortable.js na kanban board — prevuci task iz jedne kolone
|
||||||
|
u drugu. Na drop, HTMX pošalje POST i Go premesti fajl.
|
||||||
|
|
||||||
|
## Fajlovi za izmenu
|
||||||
|
|
||||||
|
```
|
||||||
|
code/web/
|
||||||
|
├── templates/
|
||||||
|
│ ├── dashboard.html ← dodaj Sortable inicijalizaciju
|
||||||
|
│ └── partials/
|
||||||
|
│ └── column.html ← dodaj sortable atribute
|
||||||
|
└── static/
|
||||||
|
└── style.css ← drag stilovi (ghost, placeholder)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kako radi
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="column" id="col-ready" data-folder="ready">
|
||||||
|
<div class="task-card" data-id="T08">...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('.column').forEach(col => {
|
||||||
|
new Sortable(col, {
|
||||||
|
group: 'tasks',
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function(evt) {
|
||||||
|
const taskId = evt.item.dataset.id;
|
||||||
|
const toFolder = evt.to.dataset.folder;
|
||||||
|
htmx.ajax('POST', `/task/${taskId}/move?to=${toFolder}`, {
|
||||||
|
target: '#board',
|
||||||
|
swap: 'outerHTML'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pravila premestanja
|
||||||
|
|
||||||
|
Dozvoljena kretanja:
|
||||||
|
- backlog → ready (operater odobri)
|
||||||
|
- ready → backlog (operater povuče nazad)
|
||||||
|
- review → done (operater odobri)
|
||||||
|
- review → ready (operater vrati na doradu)
|
||||||
|
|
||||||
|
Zabranjena kretanja (agent radi ovo, ne operater):
|
||||||
|
- ready → active (samo agent)
|
||||||
|
- active → review (samo agent)
|
||||||
|
|
||||||
|
Server validira i odbije nedozvoljene poteze sa porukom.
|
||||||
|
|
||||||
|
## Vizuelni feedback
|
||||||
|
|
||||||
|
- Drag: kartica postaje poluprozirna
|
||||||
|
- Drop zona: highlight kad se kartica prevlači iznad
|
||||||
|
- Uspešan drop: zeleni flash
|
||||||
|
- Neuspešan drop: crveni flash + kartica se vrati
|
||||||
|
|
||||||
|
## Testovi
|
||||||
|
|
||||||
|
- Premesti T08 iz backlog u ready → fajl premešten, board ažuriran
|
||||||
|
- Pokušaj premesti u active → server odbije, kartica se vrati
|
||||||
|
- Drag & drop ne kvari postojeći klik za detalj
|
||||||
|
|
||||||
|
## Očekivani izlaz
|
||||||
|
|
||||||
|
Prevuci task iz kolone u kolonu. Server premesti fajl. Board se ažurira.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pitanja
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Odgovori
|
||||||
104
TASKS/ready/T07.md
Normal file
104
TASKS/ready/T07.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# T07: Integracija — sve zajedno
|
||||||
|
|
||||||
|
**Kreirao:** planer
|
||||||
|
**Datum:** 2026-02-20
|
||||||
|
**Agent:** coder
|
||||||
|
**Model:** Sonnet
|
||||||
|
**Zavisi od:** T06
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opis
|
||||||
|
|
||||||
|
End-to-end tok: CLI pozove run → učita task → pokrene agenta →
|
||||||
|
verifikuje → napiše izveštaj → premesti task. Sve komponente
|
||||||
|
povezane u jedan flow.
|
||||||
|
|
||||||
|
## Fajlovi za izmenu
|
||||||
|
|
||||||
|
```
|
||||||
|
code/internal/supervisor/
|
||||||
|
├── supervisor.go ← Supervisor struct, Run() metoda
|
||||||
|
└── supervisor_test.go ← end-to-end testovi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supervisor struct
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Supervisor struct {
|
||||||
|
Config *config.Config
|
||||||
|
TasksDir string
|
||||||
|
CodeDir string
|
||||||
|
ReportsDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSupervisor(cfg *config.Config) *Supervisor
|
||||||
|
func (s *Supervisor) Run(taskID string) error
|
||||||
|
func (s *Supervisor) RunNext() error
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run() tok
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *Supervisor) Run(taskID string) error {
|
||||||
|
// 1. ScanTasks(s.TasksDir)
|
||||||
|
// 2. FindTask(taskID) — proveri da je u ready/
|
||||||
|
// 3. MoveTask → active/
|
||||||
|
// 4. RunTask(task, s.CodeDir, s.Config.Timeout)
|
||||||
|
// 5. Verify(s.CodeDir)
|
||||||
|
// 6. WriteReport(task, runResult, verifyResult, s.ReportsDir)
|
||||||
|
// 7. Ako AllPassed → MoveTask → review/
|
||||||
|
// 8. Ako !AllPassed → MoveTask → review/ (sa statusom failed u izveštaju)
|
||||||
|
// 9. Prikaži rezime
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## RunNext() tok
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *Supervisor) RunNext() error {
|
||||||
|
// 1. ScanTasks
|
||||||
|
// 2. NextTask — prvi iz ready/ sa ispunjenim zavisnostima
|
||||||
|
// 3. Ako nema → vrati poruku "nema taskova"
|
||||||
|
// 4. Run(task.ID)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integracija sa CLI
|
||||||
|
|
||||||
|
main.go poziva:
|
||||||
|
- `run T01` → supervisor.Run("T01")
|
||||||
|
- `run` (bez ID) → supervisor.RunNext()
|
||||||
|
- `status` → ScanTasks + ispis
|
||||||
|
- `next` → NextTask + ispis
|
||||||
|
- `verify` → Verify + ispis
|
||||||
|
- `history` → čitaj reports/ + ispis
|
||||||
|
|
||||||
|
## Testovi
|
||||||
|
|
||||||
|
- End-to-end: napravi temp TASKS/ strukturu, stavi task u ready/,
|
||||||
|
pokreni Run() sa mock komandom → proveri da je task u review/,
|
||||||
|
izveštaj napisan, output tačan
|
||||||
|
- RunNext: dva taska u ready/, jedan sa neispunjenom zavisnošću →
|
||||||
|
pokrene pravi
|
||||||
|
- Nema taskova u ready/ → graceful poruka
|
||||||
|
- Task koji je već active/ → greška
|
||||||
|
- Failed verifikacija → task u review/ sa failed statusom u izveštaju
|
||||||
|
- Config greška → graceful poruka
|
||||||
|
|
||||||
|
## Očekivani izlaz
|
||||||
|
|
||||||
|
`kaos-supervisor run T01` prolazi ceo tok od učitavanja do izveštaja
|
||||||
|
(sa mock agentom). `go test ./... -v` — svi testovi zeleni.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pitanja
|
||||||
|
|
||||||
|
*(agent piše pitanja ovde, planer odgovara)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Odgovori
|
||||||
|
|
||||||
|
*(planer piše odgovore ovde)*
|
||||||
96
TASKS/ready/T08.md
Normal file
96
TASKS/ready/T08.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# T08: HTTP server + API za taskove
|
||||||
|
|
||||||
|
**Kreirao:** planer
|
||||||
|
**Datum:** 2026-02-20
|
||||||
|
**Agent:** coder
|
||||||
|
**Model:** Sonnet
|
||||||
|
**Zavisi od:** T07 ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opis
|
||||||
|
|
||||||
|
Go HTTP server koji servira dashboard i API za upravljanje taskovima.
|
||||||
|
Koristi postojeću supervisor logiku (ScanTasks, FindTask, MoveTask).
|
||||||
|
|
||||||
|
## Fajlovi za kreiranje
|
||||||
|
|
||||||
|
```
|
||||||
|
code/
|
||||||
|
├── cmd/kaos-server/
|
||||||
|
│ └── main.go ← HTTP server entry point
|
||||||
|
├── internal/server/
|
||||||
|
│ ├── server.go ← Server struct, rute, handler-i
|
||||||
|
│ └── server_test.go ← testovi API-ja
|
||||||
|
└── web/
|
||||||
|
└── static/
|
||||||
|
├── htmx.min.js ← HTMX (ugradi u binary)
|
||||||
|
└── sortable.min.js ← Sortable.js (ugradi u binary)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API endpointi
|
||||||
|
|
||||||
|
```
|
||||||
|
GET / → dashboard stranica (HTML)
|
||||||
|
GET /api/tasks → svi taskovi (JSON)
|
||||||
|
GET /api/task/{id} → jedan task (JSON + sadržaj fajla)
|
||||||
|
POST /api/task/{id}/move → premesti task (query: to=ready)
|
||||||
|
GET /task/{id} → task detalj (HTML fragment za HTMX)
|
||||||
|
POST /task/{id}/move → premesti + vrati ažuriran HTML
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pravila
|
||||||
|
|
||||||
|
- Gin framework (već odlučeno)
|
||||||
|
- Port iz .env: KAOS_PORT (default 8080)
|
||||||
|
- Static fajlovi ugrađeni u binary (embed.FS)
|
||||||
|
- CORS nije potreban (sve sa istog servera)
|
||||||
|
- Graceful shutdown
|
||||||
|
|
||||||
|
## Pravila premestanja (server MORA da validira)
|
||||||
|
|
||||||
|
Dozvoljeno iz dashboarda (operater):
|
||||||
|
- backlog → ready (odobri task)
|
||||||
|
- ready → backlog (povuče nazad)
|
||||||
|
- review → done (odobri završen)
|
||||||
|
- review → ready (vrati na doradu)
|
||||||
|
|
||||||
|
Dozvoljeno samo iz CLI/agenta:
|
||||||
|
- ready → active (agent preuzme)
|
||||||
|
- active → review (agent završi ili ima pitanje)
|
||||||
|
|
||||||
|
Zabranjeno (server odbije sa 403):
|
||||||
|
- done → bilo gde
|
||||||
|
- active → bilo gde osim review
|
||||||
|
- backlog → active (preskoči odobrenje)
|
||||||
|
- bilo šta → active osim ready → active
|
||||||
|
|
||||||
|
Move endpoint prima `source` parametar: `dashboard` ili `agent`.
|
||||||
|
Ako source=dashboard, dozvoljeni su samo operaterski potezi.
|
||||||
|
Ako source=agent, dozvoljeni su samo agentski potezi.
|
||||||
|
Nepoznat source → 403.
|
||||||
|
|
||||||
|
## Testovi
|
||||||
|
|
||||||
|
- GET /api/tasks → vraća JSON listu taskova
|
||||||
|
- GET /api/task/T01 → vraća task sa sadržajem
|
||||||
|
- POST /api/task/T08/move?to=ready&source=dashboard → premesti, 200
|
||||||
|
- POST /api/task/T08/move?to=active&source=dashboard → odbije, 403
|
||||||
|
- POST /api/task/T08/move?to=active&source=agent → premesti, 200
|
||||||
|
- POST /api/task/T01/move?to=backlog&source=dashboard → done task, 403
|
||||||
|
- POST /api/task/T99/move?to=ready → nepostojeći, 404
|
||||||
|
- POST /api/task/T01/move?to=invalid → nepoznat folder, 400
|
||||||
|
|
||||||
|
## Očekivani izlaz
|
||||||
|
|
||||||
|
`go build ./cmd/kaos-server/` kreira binary.
|
||||||
|
`go test ./internal/server/ -v` — svi testovi zeleni.
|
||||||
|
Server sluša na portu, vraća JSON na API pozive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pitanja
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Odgovori
|
||||||
62
TASKS/reports/T08-report.md
Normal file
62
TASKS/reports/T08-report.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# T08 Izveštaj: HTTP server + API za taskove
|
||||||
|
|
||||||
|
**Agent:** coder
|
||||||
|
**Model:** Opus
|
||||||
|
**Datum:** 2026-02-20
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Šta je urađeno
|
||||||
|
|
||||||
|
Implementiran HTTP server sa Gin framework-om i dashboard:
|
||||||
|
|
||||||
|
### Kreirani fajlovi
|
||||||
|
|
||||||
|
| Fajl | Opis |
|
||||||
|
|------|------|
|
||||||
|
| `cmd/kaos-server/main.go` | Entry point za HTTP server |
|
||||||
|
| `internal/server/server.go` | Server struct, API i HTML rute |
|
||||||
|
| `internal/server/render.go` | HTML renderovanje (dashboard, kartice, detalj) |
|
||||||
|
| `internal/server/server_test.go` | 10 testova za API i HTML |
|
||||||
|
| `web/embed.go` | embed.FS za static fajlove |
|
||||||
|
| `web/static/htmx.min.js` | HTMX 2.0.4 |
|
||||||
|
| `web/static/sortable.min.js` | Sortable.js 1.15.6 |
|
||||||
|
|
||||||
|
### Izmenjeni fajlovi
|
||||||
|
|
||||||
|
| Fajl | Izmena |
|
||||||
|
|------|--------|
|
||||||
|
| `internal/config/config.go` | Dodat KAOS_PORT (default 8080) |
|
||||||
|
| `.env.example` | Dodat KAOS_PORT |
|
||||||
|
|
||||||
|
### API endpointi
|
||||||
|
|
||||||
|
| Metod | Ruta | Opis |
|
||||||
|
|-------|------|------|
|
||||||
|
| GET | /api/tasks | JSON lista svih taskova |
|
||||||
|
| GET | /api/task/:id | JSON task sa sadržajem fajla |
|
||||||
|
| POST | /api/task/:id/move?to=X | Premesti task |
|
||||||
|
| GET | / | Dashboard HTML (Kanban board) |
|
||||||
|
| GET | /task/:id | Task detalj (HTMX fragment) |
|
||||||
|
| POST | /task/:id/move?to=X | Premesti + vrati ažuriran HTML |
|
||||||
|
|
||||||
|
### Testovi — 10/10 PASS
|
||||||
|
|
||||||
|
```
|
||||||
|
TestAPIGetTasks PASS
|
||||||
|
TestAPIGetTask PASS
|
||||||
|
TestAPIGetTask_NotFound PASS
|
||||||
|
TestAPIMoveTask PASS
|
||||||
|
TestAPIMoveTask_NotFound PASS
|
||||||
|
TestAPIMoveTask_InvalidFolder PASS
|
||||||
|
TestDashboardHTML PASS
|
||||||
|
TestTaskDetailHTML PASS
|
||||||
|
TestTaskDetailHTML_NotFound PASS
|
||||||
|
TestHTMLMoveTask PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ukupno projekat: 77 testova, svi prolaze
|
||||||
|
|
||||||
|
- `go vet ./...` — čist
|
||||||
|
- `go build ./cmd/kaos-server/` — prolazi
|
||||||
|
- `go build ./cmd/kaos-supervisor/` — prolazi
|
||||||
70
TASKS/review/T08.md
Normal file
70
TASKS/review/T08.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# T08: HTTP server + API za taskove
|
||||||
|
|
||||||
|
**Kreirao:** planer
|
||||||
|
**Datum:** 2026-02-20
|
||||||
|
**Agent:** coder
|
||||||
|
**Model:** Sonnet
|
||||||
|
**Zavisi od:** T07 ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opis
|
||||||
|
|
||||||
|
Go HTTP server koji servira dashboard i API za upravljanje taskovima.
|
||||||
|
Koristi postojeću supervisor logiku (ScanTasks, FindTask, MoveTask).
|
||||||
|
|
||||||
|
## Fajlovi za kreiranje
|
||||||
|
|
||||||
|
```
|
||||||
|
code/
|
||||||
|
├── cmd/kaos-server/
|
||||||
|
│ └── main.go ← HTTP server entry point
|
||||||
|
├── internal/server/
|
||||||
|
│ ├── server.go ← Server struct, rute, handler-i
|
||||||
|
│ └── server_test.go ← testovi API-ja
|
||||||
|
└── web/
|
||||||
|
└── static/
|
||||||
|
├── htmx.min.js ← HTMX (ugradi u binary)
|
||||||
|
└── sortable.min.js ← Sortable.js (ugradi u binary)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API endpointi
|
||||||
|
|
||||||
|
```
|
||||||
|
GET / → dashboard stranica (HTML)
|
||||||
|
GET /api/tasks → svi taskovi (JSON)
|
||||||
|
GET /api/task/{id} → jedan task (JSON + sadržaj fajla)
|
||||||
|
POST /api/task/{id}/move → premesti task (query: to=ready)
|
||||||
|
GET /task/{id} → task detalj (HTML fragment za HTMX)
|
||||||
|
POST /task/{id}/move → premesti + vrati ažuriran HTML
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pravila
|
||||||
|
|
||||||
|
- Gin framework (već odlučeno)
|
||||||
|
- Port iz .env: KAOS_PORT (default 8080)
|
||||||
|
- Static fajlovi ugrađeni u binary (embed.FS)
|
||||||
|
- CORS nije potreban (sve sa istog servera)
|
||||||
|
- Graceful shutdown
|
||||||
|
|
||||||
|
## Testovi
|
||||||
|
|
||||||
|
- GET /api/tasks → vraća JSON listu taskova
|
||||||
|
- GET /api/task/T01 → vraća task sa sadržajem
|
||||||
|
- POST /api/task/T08/move?to=ready → premesti fajl, vrati 200
|
||||||
|
- POST /api/task/T99/move?to=ready → 404
|
||||||
|
- POST /api/task/T01/move?to=invalid → 400
|
||||||
|
|
||||||
|
## Očekivani izlaz
|
||||||
|
|
||||||
|
`go build ./cmd/kaos-server/` kreira binary.
|
||||||
|
`go test ./internal/server/ -v` — svi testovi zeleni.
|
||||||
|
Server sluša na portu, vraća JSON na API pozive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pitanja
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Odgovori
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Coder Agent
|
# Coder Agent
|
||||||
|
|
||||||
**Verzija:** 0.1.0
|
**Verzija:** 0.2.0
|
||||||
**Poslednje ažuriranje:** 2026-02-20
|
**Poslednje ažuriranje:** 2026-02-20
|
||||||
|
|
||||||
## Uloga
|
## Uloga
|
||||||
@ -32,6 +32,17 @@ poštuje konvencije projekta.
|
|||||||
- Nazivi u kodu: engleski
|
- Nazivi u kodu: engleski
|
||||||
- Nema hardkodiranih vrednosti
|
- Nema hardkodiranih vrednosti
|
||||||
|
|
||||||
|
## Premestanje taskova — agent SME samo:
|
||||||
|
|
||||||
|
| Iz → U | Dozvoljeno |
|
||||||
|
|---------|-----------|
|
||||||
|
| ready → active | ✅ (preuzmi task) |
|
||||||
|
| active → review | ✅ (završi ili postavi pitanje) |
|
||||||
|
| Sve ostalo | ❌ ZABRANJENO |
|
||||||
|
|
||||||
|
Agent NIKAD ne premešta u: backlog, ready, done.
|
||||||
|
To radi SAMO operater.
|
||||||
|
|
||||||
## NE zna
|
## NE zna
|
||||||
- Druge module osim onih u zadatku
|
- Druge module osim onih u zadatku
|
||||||
- Poslovne odluke
|
- Poslovne odluke
|
||||||
|
|||||||
@ -1,36 +1,49 @@
|
|||||||
# Frontend Agent
|
# Frontend Agent — KAOS
|
||||||
|
|
||||||
**Verzija:** 0.1.0
|
**Verzija:** 0.2.0
|
||||||
**Poslednje ažuriranje:** 2026-02-20
|
**Poslednje ažuriranje:** 2026-02-20
|
||||||
|
|
||||||
## Uloga
|
## Uloga
|
||||||
Piše React frontend kod — komponente, stranice, API pozive, testove.
|
Piše frontend kod — Go HTML templates, HTMX interakcije, CSS.
|
||||||
|
|
||||||
## Model: Sonnet
|
## Model: Sonnet
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
- Go `html/template` — server-side renderovanje
|
||||||
|
- HTMX — interaktivnost bez JS frameworka
|
||||||
|
- Sortable.js — drag and drop
|
||||||
|
- Čist CSS — nema Tailwind, nema npm, nema build step-a
|
||||||
|
- Sve se servira iz Go binary-ja
|
||||||
|
|
||||||
## Dobija od masterminda
|
## Dobija od masterminda
|
||||||
- Zadatak (šta da napravi/izmeni)
|
- Zadatak
|
||||||
- API endpoint specifikaciju (šta backend vraća)
|
- API endpoint specifikaciju
|
||||||
- Wireframe ili opis UI-a
|
- Wireframe ili opis UI-a
|
||||||
- code/kaos-frontend/CLAUDE.md (pravila za frontend)
|
|
||||||
|
|
||||||
## Vraća mastermindu
|
## Vraća mastermindu
|
||||||
- Status: gotovo / neuspešno / treba pojašnjenje
|
- Status: gotovo / neuspešno / treba pojašnjenje
|
||||||
- Lista kreiranih/izmenjenih fajlova
|
- Lista kreiranih/izmenjenih fajlova
|
||||||
- Commit hash
|
- Commit hash
|
||||||
- Screenshot ili opis promena
|
|
||||||
|
|
||||||
## Pravila
|
## Pravila
|
||||||
- React + TypeScript + Vite + Tailwind + shadcn/ui
|
- `go build ./...` mora proći
|
||||||
- TanStack Query za API pozive
|
|
||||||
- Playwright testovi za svaki novi flow
|
|
||||||
- `npm run build` mora proći
|
|
||||||
- `npx playwright test` mora proći
|
|
||||||
- Responsivan dizajn
|
- Responsivan dizajn
|
||||||
- Poruke korisniku: srpski
|
- Poruke korisniku: srpski
|
||||||
|
- Nema npm-a, nema node_modules, nema build step-a
|
||||||
|
- Jedan binary servira sve (HTML, CSS, JS, API)
|
||||||
|
|
||||||
|
## Premestanje taskova — agent SME samo:
|
||||||
|
|
||||||
|
| Iz → U | Dozvoljeno |
|
||||||
|
|---------|-----------|
|
||||||
|
| ready → active | ✅ (preuzmi task) |
|
||||||
|
| active → review | ✅ (završi ili postavi pitanje) |
|
||||||
|
| Sve ostalo | ❌ ZABRANJENO |
|
||||||
|
|
||||||
|
Agent NIKAD ne premešta u: backlog, ready, done.
|
||||||
|
To radi SAMO operater.
|
||||||
|
|
||||||
## NE zna
|
## NE zna
|
||||||
- Backend implementaciju (samo API spec)
|
- Kako supervisor interno radi (samo API spec)
|
||||||
- Engine internals
|
|
||||||
- Bazu podataka
|
- Bazu podataka
|
||||||
- Poslovne odluke osim onih u zadatku
|
- Poslovne odluke
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
KAOS_TIMEOUT=30m
|
KAOS_TIMEOUT=30m
|
||||||
KAOS_PROJECT_PATH=.
|
KAOS_PROJECT_PATH=.
|
||||||
KAOS_TASKS_DIR=../TASKS
|
KAOS_TASKS_DIR=../TASKS
|
||||||
|
KAOS_PORT=8080
|
||||||
|
|||||||
26
code/cmd/kaos-server/main.go
Normal file
26
code/cmd/kaos-server/main.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Package main is the entry point for the KAOS dashboard HTTP server.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/dal/kaos/internal/config"
|
||||||
|
"github.com/dal/kaos/internal/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Greška pri učitavanju konfiguracije: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := server.New(cfg)
|
||||||
|
|
||||||
|
log.Printf("KAOS Dashboard pokrenut na http://localhost:%s", cfg.Port)
|
||||||
|
if err := srv.Run(); err != nil {
|
||||||
|
log.Fatalf("Server greška: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
36
code/go.mod
36
code/go.mod
@ -1,3 +1,39 @@
|
|||||||
module github.com/dal/kaos
|
module github.com/dal/kaos
|
||||||
|
|
||||||
go 1.23.6
|
go 1.23.6
|
||||||
|
|
||||||
|
require github.com/gin-gonic/gin v1.11.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
|
golang.org/x/crypto v0.40.0 // indirect
|
||||||
|
golang.org/x/mod v0.25.0 // indirect
|
||||||
|
golang.org/x/net v0.42.0 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
|
)
|
||||||
|
|||||||
88
code/go.sum
Normal file
88
code/go.sum
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@ -18,6 +18,8 @@ type Config struct {
|
|||||||
ProjectPath string
|
ProjectPath string
|
||||||
// TasksDir is the path to the TASKS directory.
|
// TasksDir is the path to the TASKS directory.
|
||||||
TasksDir string
|
TasksDir string
|
||||||
|
// Port is the HTTP server port.
|
||||||
|
Port string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load reads configuration from environment variables.
|
// Load reads configuration from environment variables.
|
||||||
@ -46,10 +48,16 @@ func Load() (*Config, error) {
|
|||||||
return nil, fmt.Errorf("KAOS_TASKS_DIR environment variable is required")
|
return nil, fmt.Errorf("KAOS_TASKS_DIR environment variable is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
port := os.Getenv("KAOS_PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "8080"
|
||||||
|
}
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
ProjectPath: projectPath,
|
ProjectPath: projectPath,
|
||||||
TasksDir: tasksDir,
|
TasksDir: tasksDir,
|
||||||
|
Port: port,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
133
code/internal/server/render.go
Normal file
133
code/internal/server/render.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dal/kaos/internal/supervisor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// statusIcons maps folder names to emoji icons.
|
||||||
|
var statusIcons = map[string]string{
|
||||||
|
"backlog": "📦",
|
||||||
|
"ready": "📋",
|
||||||
|
"active": "🔄",
|
||||||
|
"review": "👀",
|
||||||
|
"done": "✅",
|
||||||
|
}
|
||||||
|
|
||||||
|
// columnOrder defines the display order of columns.
|
||||||
|
var columnOrder = []string{"backlog", "ready", "active", "review", "done"}
|
||||||
|
|
||||||
|
// renderDashboard generates the full dashboard HTML page.
|
||||||
|
func renderDashboard(columns map[string][]supervisor.Task) string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
b.WriteString(`<!DOCTYPE html>
|
||||||
|
<html lang="sr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>KAOS Dashboard</title>
|
||||||
|
<script src="/static/htmx.min.js"></script>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #1a1a2e; color: #eee; }
|
||||||
|
.header { padding: 16px 24px; background: #16213e; display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #0f3460; }
|
||||||
|
.header h1 { font-size: 1.4em; }
|
||||||
|
.header .version { color: #888; font-size: 0.9em; }
|
||||||
|
.board { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; padding: 16px; min-height: calc(100vh - 60px); }
|
||||||
|
.column { background: #16213e; border-radius: 8px; padding: 12px; }
|
||||||
|
.column-header { font-weight: bold; padding: 8px; margin-bottom: 8px; border-bottom: 2px solid #0f3460; display: flex; justify-content: space-between; }
|
||||||
|
.column-count { background: #0f3460; border-radius: 12px; padding: 2px 8px; font-size: 0.85em; }
|
||||||
|
.task-card { background: #1a1a2e; border: 1px solid #333; border-radius: 6px; padding: 10px; margin-bottom: 8px; cursor: pointer; transition: border-color 0.2s; }
|
||||||
|
.task-card:hover { border-color: #e94560; }
|
||||||
|
.task-id { font-weight: bold; color: #e94560; }
|
||||||
|
.task-title { margin-top: 4px; font-size: 0.9em; }
|
||||||
|
.task-meta { margin-top: 6px; font-size: 0.75em; color: #888; }
|
||||||
|
.task-deps { font-size: 0.75em; color: #666; margin-top: 4px; }
|
||||||
|
#task-detail { position: fixed; top: 0; right: 0; width: 400px; height: 100vh; background: #16213e; border-left: 2px solid #0f3460; padding: 20px; overflow-y: auto; display: none; z-index: 10; }
|
||||||
|
#task-detail.active { display: block; }
|
||||||
|
.detail-close { cursor: pointer; float: right; font-size: 1.2em; color: #888; }
|
||||||
|
.detail-close:hover { color: #e94560; }
|
||||||
|
.detail-content { white-space: pre-wrap; font-family: monospace; font-size: 0.85em; margin-top: 16px; line-height: 1.5; }
|
||||||
|
@media (max-width: 900px) { .board { grid-template-columns: repeat(3, 1fr); } }
|
||||||
|
@media (max-width: 600px) { .board { grid-template-columns: 1fr; } }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>🔧 KAOS Dashboard</h1>
|
||||||
|
<span class="version">v0.2</span>
|
||||||
|
</div>
|
||||||
|
<div class="board" id="board">
|
||||||
|
`)
|
||||||
|
|
||||||
|
for _, col := range columnOrder {
|
||||||
|
tasks := columns[col]
|
||||||
|
icon := statusIcons[col]
|
||||||
|
b.WriteString(fmt.Sprintf(`<div class="column" id="col-%s" data-folder="%s">`, col, col))
|
||||||
|
b.WriteString(fmt.Sprintf(`<div class="column-header"><span>%s %s</span><span class="column-count">%d</span></div>`,
|
||||||
|
icon, strings.ToUpper(col), len(tasks)))
|
||||||
|
|
||||||
|
for _, t := range tasks {
|
||||||
|
b.WriteString(renderTaskCard(t))
|
||||||
|
}
|
||||||
|
b.WriteString("</div>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(`</div>
|
||||||
|
<div id="task-detail"></div>
|
||||||
|
<script>
|
||||||
|
document.body.addEventListener('htmx:afterSwap', function(e) {
|
||||||
|
if (e.detail.target.id === 'task-detail') {
|
||||||
|
e.detail.target.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function closeDetail() {
|
||||||
|
document.getElementById('task-detail').classList.remove('active');
|
||||||
|
document.getElementById('task-detail').innerHTML = '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`)
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderTaskCard generates HTML for a single task card.
|
||||||
|
func renderTaskCard(t supervisor.Task) string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
b.WriteString(fmt.Sprintf(`<div class="task-card" data-id="%s" hx-get="/task/%s" hx-target="#task-detail" hx-swap="innerHTML">`,
|
||||||
|
t.ID, t.ID))
|
||||||
|
b.WriteString(fmt.Sprintf(`<div class="task-id">%s</div>`, html.EscapeString(t.ID)))
|
||||||
|
b.WriteString(fmt.Sprintf(`<div class="task-title">%s</div>`, html.EscapeString(t.Title)))
|
||||||
|
b.WriteString(fmt.Sprintf(`<div class="task-meta">%s · %s</div>`, html.EscapeString(t.Agent), html.EscapeString(t.Model)))
|
||||||
|
|
||||||
|
if len(t.DependsOn) > 0 {
|
||||||
|
b.WriteString(fmt.Sprintf(`<div class="task-deps">Zavisi od: %s</div>`, html.EscapeString(strings.Join(t.DependsOn, ", "))))
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString("</div>\n")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderTaskDetail generates HTML fragment for task detail panel.
|
||||||
|
func renderTaskDetail(t supervisor.Task, content string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
b.WriteString(`<span class="detail-close" onclick="closeDetail()">✕</span>`)
|
||||||
|
b.WriteString(fmt.Sprintf(`<h2>%s: %s</h2>`, html.EscapeString(t.ID), html.EscapeString(t.Title)))
|
||||||
|
b.WriteString(fmt.Sprintf(`<p>Agent: %s · Model: %s · Status: %s</p>`,
|
||||||
|
html.EscapeString(t.Agent), html.EscapeString(t.Model), html.EscapeString(t.Status)))
|
||||||
|
|
||||||
|
if len(t.DependsOn) > 0 {
|
||||||
|
b.WriteString(fmt.Sprintf(`<p>Zavisi od: %s</p>`, html.EscapeString(strings.Join(t.DependsOn, ", "))))
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(fmt.Sprintf(`<div class="detail-content">%s</div>`, html.EscapeString(content)))
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
262
code/internal/server/server.go
Normal file
262
code/internal/server/server.go
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
// Package server implements the HTTP server and API for the KAOS dashboard.
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/dal/kaos/internal/config"
|
||||||
|
"github.com/dal/kaos/internal/supervisor"
|
||||||
|
"github.com/dal/kaos/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server holds the HTTP server state.
|
||||||
|
type Server struct {
|
||||||
|
Config *config.Config
|
||||||
|
Router *gin.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// taskResponse is the JSON representation of a task.
|
||||||
|
type taskResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Agent string `json:"agent"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
DependsOn []string `json:"depends_on"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
FilePath string `json:"file_path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// taskDetailResponse includes the raw file content.
|
||||||
|
type taskDetailResponse struct {
|
||||||
|
taskResponse
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validMoveTargets defines allowed destination folders for manual moves.
|
||||||
|
var validFolders = map[string]bool{
|
||||||
|
"backlog": true,
|
||||||
|
"ready": true,
|
||||||
|
"active": true,
|
||||||
|
"review": true,
|
||||||
|
"done": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Server with all routes configured.
|
||||||
|
func New(cfg *config.Config) *Server {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(gin.Recovery())
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Config: cfg,
|
||||||
|
Router: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.setupRoutes()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupRoutes configures all HTTP routes.
|
||||||
|
func (s *Server) setupRoutes() {
|
||||||
|
// Embedded static files
|
||||||
|
staticFS, _ := fs.Sub(web.StaticFS, "static")
|
||||||
|
s.Router.StaticFS("/static", http.FS(staticFS))
|
||||||
|
|
||||||
|
// API routes
|
||||||
|
s.Router.GET("/api/tasks", s.apiGetTasks)
|
||||||
|
s.Router.GET("/api/task/:id", s.apiGetTask)
|
||||||
|
s.Router.POST("/api/task/:id/move", s.apiMoveTask)
|
||||||
|
|
||||||
|
// HTML routes
|
||||||
|
s.Router.GET("/", s.handleDashboard)
|
||||||
|
s.Router.GET("/task/:id", s.handleTaskDetail)
|
||||||
|
s.Router.POST("/task/:id/move", s.handleMoveTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiGetTasks returns all tasks as JSON.
|
||||||
|
func (s *Server) apiGetTasks(c *gin.Context) {
|
||||||
|
tasks, err := supervisor.ScanTasks(s.Config.TasksDir)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := make([]taskResponse, len(tasks))
|
||||||
|
for i, t := range tasks {
|
||||||
|
resp[i] = toTaskResponse(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiGetTask returns a single task with its file content as JSON.
|
||||||
|
func (s *Server) apiGetTask(c *gin.Context) {
|
||||||
|
id := strings.ToUpper(c.Param("id"))
|
||||||
|
|
||||||
|
tasks, err := supervisor.ScanTasks(s.Config.TasksDir)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task := supervisor.FindTask(tasks, id)
|
||||||
|
if task == nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "task not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, _ := os.ReadFile(task.FilePath)
|
||||||
|
|
||||||
|
resp := taskDetailResponse{
|
||||||
|
taskResponse: toTaskResponse(*task),
|
||||||
|
Content: string(content),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiMoveTask moves a task to a different folder.
|
||||||
|
func (s *Server) apiMoveTask(c *gin.Context) {
|
||||||
|
id := strings.ToUpper(c.Param("id"))
|
||||||
|
toFolder := c.Query("to")
|
||||||
|
|
||||||
|
if !validFolders[toFolder] {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "nevažeći folder: " + toFolder})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks, err := supervisor.ScanTasks(s.Config.TasksDir)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task := supervisor.FindTask(tasks, id)
|
||||||
|
if task == nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "task not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := supervisor.MoveTask(s.Config.TasksDir, id, task.Status, toFolder); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"status": "ok", "moved": id, "to": toFolder})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDashboard serves the main dashboard page.
|
||||||
|
func (s *Server) handleDashboard(c *gin.Context) {
|
||||||
|
tasks, err := supervisor.ScanTasks(s.Config.TasksDir)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, "Greška: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := groupByStatus(tasks)
|
||||||
|
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||||
|
c.String(http.StatusOK, renderDashboard(columns))
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleTaskDetail serves task detail as HTML fragment for HTMX.
|
||||||
|
func (s *Server) handleTaskDetail(c *gin.Context) {
|
||||||
|
id := strings.ToUpper(c.Param("id"))
|
||||||
|
|
||||||
|
tasks, err := supervisor.ScanTasks(s.Config.TasksDir)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, "Greška: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task := supervisor.FindTask(tasks, id)
|
||||||
|
if task == nil {
|
||||||
|
c.String(http.StatusNotFound, "Task %s nije pronađen", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, _ := os.ReadFile(task.FilePath)
|
||||||
|
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||||
|
c.String(http.StatusOK, renderTaskDetail(*task, string(content)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMoveTask moves a task and returns updated board HTML.
|
||||||
|
func (s *Server) handleMoveTask(c *gin.Context) {
|
||||||
|
id := strings.ToUpper(c.Param("id"))
|
||||||
|
toFolder := c.Query("to")
|
||||||
|
|
||||||
|
if !validFolders[toFolder] {
|
||||||
|
c.String(http.StatusBadRequest, "Nevažeći folder: %s", toFolder)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks, err := supervisor.ScanTasks(s.Config.TasksDir)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, "Greška: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task := supervisor.FindTask(tasks, id)
|
||||||
|
if task == nil {
|
||||||
|
c.String(http.StatusNotFound, "Task %s nije pronađen", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := supervisor.MoveTask(s.Config.TasksDir, id, task.Status, toFolder); err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, "Greška: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-scan and return updated dashboard
|
||||||
|
s.handleDashboard(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts the HTTP server.
|
||||||
|
func (s *Server) Run() error {
|
||||||
|
return s.Router.Run(":" + s.Config.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// groupByStatus organizes tasks into columns by status folder.
|
||||||
|
func groupByStatus(tasks []supervisor.Task) map[string][]supervisor.Task {
|
||||||
|
columns := map[string][]supervisor.Task{
|
||||||
|
"backlog": {},
|
||||||
|
"ready": {},
|
||||||
|
"active": {},
|
||||||
|
"review": {},
|
||||||
|
"done": {},
|
||||||
|
}
|
||||||
|
for _, t := range tasks {
|
||||||
|
columns[t.Status] = append(columns[t.Status], t)
|
||||||
|
}
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
// reportExists checks if a report file exists for a task.
|
||||||
|
func reportExists(tasksDir, taskID string) bool {
|
||||||
|
reportPath := filepath.Join(tasksDir, "reports", taskID+"-report.md")
|
||||||
|
_, err := os.Stat(reportPath)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toTaskResponse(t supervisor.Task) taskResponse {
|
||||||
|
deps := t.DependsOn
|
||||||
|
if deps == nil {
|
||||||
|
deps = []string{}
|
||||||
|
}
|
||||||
|
return taskResponse{
|
||||||
|
ID: t.ID,
|
||||||
|
Title: t.Title,
|
||||||
|
Status: t.Status,
|
||||||
|
Agent: t.Agent,
|
||||||
|
Model: t.Model,
|
||||||
|
DependsOn: deps,
|
||||||
|
Description: t.Description,
|
||||||
|
FilePath: t.FilePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
268
code/internal/server/server_test.go
Normal file
268
code/internal/server/server_test.go
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dal/kaos/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testTask1 = `# T01: Prvi task
|
||||||
|
|
||||||
|
**Agent:** coder
|
||||||
|
**Model:** Sonnet
|
||||||
|
**Zavisi od:** —
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opis
|
||||||
|
|
||||||
|
Opis prvog taska.
|
||||||
|
|
||||||
|
---
|
||||||
|
`
|
||||||
|
|
||||||
|
const testTask2 = `# T08: HTTP server
|
||||||
|
|
||||||
|
**Agent:** coder
|
||||||
|
**Model:** Sonnet
|
||||||
|
**Zavisi od:** T07
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opis
|
||||||
|
|
||||||
|
Implementacija HTTP servera.
|
||||||
|
|
||||||
|
---
|
||||||
|
`
|
||||||
|
|
||||||
|
func setupTestServer(t *testing.T) *Server {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
tasksDir := filepath.Join(dir, "TASKS")
|
||||||
|
for _, folder := range []string{"backlog", "ready", "active", "review", "done", "reports"} {
|
||||||
|
os.MkdirAll(filepath.Join(tasksDir, folder), 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.WriteFile(filepath.Join(tasksDir, "done", "T01.md"), []byte(testTask1), 0644)
|
||||||
|
os.WriteFile(filepath.Join(tasksDir, "backlog", "T08.md"), []byte(testTask2), 0644)
|
||||||
|
|
||||||
|
cfg := &config.Config{
|
||||||
|
TasksDir: tasksDir,
|
||||||
|
ProjectPath: dir,
|
||||||
|
Port: "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetTasks(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/tasks", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var tasks []taskResponse
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &tasks); err != nil {
|
||||||
|
t.Fatalf("invalid JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tasks) != 2 {
|
||||||
|
t.Fatalf("expected 2 tasks, got %d", len(tasks))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that tasks have correct statuses
|
||||||
|
statuses := map[string]string{}
|
||||||
|
for _, task := range tasks {
|
||||||
|
statuses[task.ID] = task.Status
|
||||||
|
}
|
||||||
|
if statuses["T01"] != "done" {
|
||||||
|
t.Errorf("expected T01 status done, got %s", statuses["T01"])
|
||||||
|
}
|
||||||
|
if statuses["T08"] != "backlog" {
|
||||||
|
t.Errorf("expected T08 status backlog, got %s", statuses["T08"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetTask(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/task/T01", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var detail taskDetailResponse
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &detail); err != nil {
|
||||||
|
t.Fatalf("invalid JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if detail.ID != "T01" {
|
||||||
|
t.Errorf("expected T01, got %s", detail.ID)
|
||||||
|
}
|
||||||
|
if detail.Content == "" {
|
||||||
|
t.Error("expected non-empty content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetTask_NotFound(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/task/T99", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusNotFound {
|
||||||
|
t.Fatalf("expected 404, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIMoveTask(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=ready", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file was moved
|
||||||
|
if _, err := os.Stat(filepath.Join(srv.Config.TasksDir, "ready", "T08.md")); err != nil {
|
||||||
|
t.Error("expected T08.md in ready/")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(srv.Config.TasksDir, "backlog", "T08.md")); !os.IsNotExist(err) {
|
||||||
|
t.Error("expected T08.md removed from backlog/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIMoveTask_NotFound(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/task/T99/move?to=ready", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusNotFound {
|
||||||
|
t.Fatalf("expected 404, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIMoveTask_InvalidFolder(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/task/T08/move?to=invalid", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("expected 400, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDashboardHTML(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := w.Body.String()
|
||||||
|
if !containsStr(body, "KAOS Dashboard") {
|
||||||
|
t.Error("expected 'KAOS Dashboard' in HTML")
|
||||||
|
}
|
||||||
|
if !containsStr(body, "T01") {
|
||||||
|
t.Error("expected T01 in HTML")
|
||||||
|
}
|
||||||
|
if !containsStr(body, "T08") {
|
||||||
|
t.Error("expected T08 in HTML")
|
||||||
|
}
|
||||||
|
if !containsStr(body, "BACKLOG") {
|
||||||
|
t.Error("expected BACKLOG column in HTML")
|
||||||
|
}
|
||||||
|
if !containsStr(body, "DONE") {
|
||||||
|
t.Error("expected DONE column in HTML")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskDetailHTML(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/task/T01", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := w.Body.String()
|
||||||
|
if !containsStr(body, "T01") {
|
||||||
|
t.Error("expected T01 in detail HTML")
|
||||||
|
}
|
||||||
|
if !containsStr(body, "Prvi task") {
|
||||||
|
t.Error("expected task title in detail HTML")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTaskDetailHTML_NotFound(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/task/T99", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusNotFound {
|
||||||
|
t.Fatalf("expected 404, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTMLMoveTask(t *testing.T) {
|
||||||
|
srv := setupTestServer(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/task/T08/move?to=ready", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
srv.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should return updated dashboard HTML
|
||||||
|
body := w.Body.String()
|
||||||
|
if !containsStr(body, "KAOS Dashboard") {
|
||||||
|
t.Error("expected dashboard HTML after move")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsStr(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && findStr(s, substr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findStr(s, substr string) bool {
|
||||||
|
for i := 0; i <= len(s)-len(substr); i++ {
|
||||||
|
if s[i:i+len(substr)] == substr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
9
code/web/embed.go
Normal file
9
code/web/embed.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Package web embeds static assets for the KAOS dashboard.
|
||||||
|
package web
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
// StaticFS contains embedded static files (htmx, sortable, css).
|
||||||
|
//
|
||||||
|
//go:embed static/*
|
||||||
|
var StaticFS embed.FS
|
||||||
1
code/web/static/htmx.min.js
vendored
Normal file
1
code/web/static/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
code/web/static/sortable.min.js
vendored
Normal file
2
code/web/static/sortable.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -30,7 +30,15 @@ pokreće prave agente sa pravim kontekstom. NIKADA ne kodira.
|
|||||||
├── agents/ ← specijalizovani agenti
|
├── agents/ ← specijalizovani agenti
|
||||||
├── code/ ← kod projekta
|
├── code/ ← kod projekta
|
||||||
├── documentation/ ← eksterna dokumentacija
|
├── documentation/ ← eksterna dokumentacija
|
||||||
└── TASKS/ ← taskovi, status, izveštaji
|
└── TASKS/
|
||||||
|
├── backlog/ ← novi taskovi (čeka odobrenje)
|
||||||
|
├── ready/ ← odobreni za rad
|
||||||
|
├── active/ ← u izradi
|
||||||
|
├── review/ ← čeka pregled/odgovor
|
||||||
|
├── done/ ← završeno
|
||||||
|
├── reports/ ← izveštaji
|
||||||
|
├── MASTER-STATUS.md
|
||||||
|
└── Implementation-Tasks.md
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -86,6 +94,20 @@ pokreće prave agente sa pravim kontekstom. NIKADA ne kodira.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Ko šta sme da premesti
|
||||||
|
|
||||||
|
| Iz → U | Operater (dashboard) | Agent (CLI) |
|
||||||
|
|---------|---------------------|-------------|
|
||||||
|
| backlog → ready | ✅ | ❌ |
|
||||||
|
| ready → backlog | ✅ | ❌ |
|
||||||
|
| ready → active | ❌ | ✅ |
|
||||||
|
| active → review | ❌ | ✅ |
|
||||||
|
| review → done | ✅ | ❌ |
|
||||||
|
| review → ready | ✅ | ❌ |
|
||||||
|
| done → bilo gde | ❌ | ❌ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Pristup
|
## Pristup
|
||||||
|
|
||||||
| Folder | Čita | Piše |
|
| Folder | Čita | Piše |
|
||||||
|
|||||||
@ -33,6 +33,30 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Task folderi
|
||||||
|
|
||||||
|
| Folder | Sadržaj | Ko premešta |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| backlog/ | Novi taskovi | operater → ready |
|
||||||
|
| ready/ | Odobreni za rad | agent → active |
|
||||||
|
| active/ | U izradi | agent → review |
|
||||||
|
| review/ | Čeka pregled/odgovor | operater → done ili ready |
|
||||||
|
| done/ | Završeno | niko |
|
||||||
|
|
||||||
|
### Ko šta sme
|
||||||
|
|
||||||
|
| Iz → U | Operater | Agent |
|
||||||
|
|---------|----------|-------|
|
||||||
|
| backlog → ready | ✅ | ❌ |
|
||||||
|
| ready → backlog | ✅ | ❌ |
|
||||||
|
| ready → active | ❌ | ✅ |
|
||||||
|
| active → review | ❌ | ✅ |
|
||||||
|
| review → done | ✅ | ❌ |
|
||||||
|
| review → ready | ✅ | ❌ |
|
||||||
|
| done → bilo gde | ❌ | ❌ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Taskovi
|
## Taskovi
|
||||||
|
|
||||||
| Task | Naslov | Status | Zavisi od |
|
| Task | Naslov | Status | Zavisi od |
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Coder Agent — {{PROJECT_NAME}}
|
# Coder Agent — {{PROJECT_NAME}}
|
||||||
|
|
||||||
**Verzija:** 0.1.0
|
**Verzija:** 0.2.0
|
||||||
**Kreiran:** {{DATE}}
|
**Kreiran:** {{DATE}}
|
||||||
|
|
||||||
## Uloga
|
## Uloga
|
||||||
@ -27,6 +27,17 @@ Piše kod prema zadatku. Implementira funkcionalnost, piše testove.
|
|||||||
- Nazivi u kodu: engleski
|
- Nazivi u kodu: engleski
|
||||||
- Nema hardkodiranih vrednosti
|
- Nema hardkodiranih vrednosti
|
||||||
|
|
||||||
|
## Premestanje taskova — agent SME samo:
|
||||||
|
|
||||||
|
| Iz → U | Dozvoljeno |
|
||||||
|
|---------|-----------|
|
||||||
|
| ready → active | ✅ (preuzmi task) |
|
||||||
|
| active → review | ✅ (završi ili postavi pitanje) |
|
||||||
|
| Sve ostalo | ❌ ZABRANJENO |
|
||||||
|
|
||||||
|
Agent NIKAD ne premešta u: backlog, ready, done.
|
||||||
|
To radi SAMO operater.
|
||||||
|
|
||||||
## NE zna
|
## NE zna
|
||||||
- Druge module osim onih u zadatku
|
- Druge module osim onih u zadatku
|
||||||
- Poslovne odluke
|
- Poslovne odluke
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Frontend Agent — {{PROJECT_NAME}}
|
# Frontend Agent — {{PROJECT_NAME}}
|
||||||
|
|
||||||
**Verzija:** 0.1.0
|
**Verzija:** 0.2.0
|
||||||
**Kreiran:** {{DATE}}
|
**Kreiran:** {{DATE}}
|
||||||
|
|
||||||
## Uloga
|
## Uloga
|
||||||
@ -25,6 +25,18 @@ Piše frontend kod — komponente, stranice, API pozive, testove.
|
|||||||
- Responsivan dizajn
|
- Responsivan dizajn
|
||||||
- Poruke korisniku: srpski
|
- Poruke korisniku: srpski
|
||||||
|
|
||||||
|
## Premestanje taskova — agent SME samo:
|
||||||
|
|
||||||
|
| Iz → U | Dozvoljeno |
|
||||||
|
|---------|-----------|
|
||||||
|
| ready → active | ✅ (preuzmi task) |
|
||||||
|
| active → review | ✅ (završi ili postavi pitanje) |
|
||||||
|
| Sve ostalo | ❌ ZABRANJENO |
|
||||||
|
|
||||||
|
Agent NIKAD ne premešta u: backlog, ready, done.
|
||||||
|
To radi SAMO operater.
|
||||||
|
|
||||||
## NE zna
|
## NE zna
|
||||||
- Backend implementaciju (samo API spec)
|
- Backend implementaciju (samo API spec)
|
||||||
- Bazu podataka
|
- Bazu podataka
|
||||||
|
- Poslovne odluke
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user