feat(db): add project personas and skills tables
CI / build-and-test (push) Failing after 1m52s

* Introduce project_personas table with foreign keys to projects and agent_personas
* Add project_skills table with foreign key to projects and agent_skills
* Include override boolean field in agent_persona_skills and project_skills
* Update schema and migration files to reflect new tables and fields
* Enhance CORS handling to reflect request origin
This commit is contained in:
2026-07-04 23:45:51 +02:00
parent 1adf50e3db
commit c179e014ad
69 changed files with 1329 additions and 184 deletions
+10 -6
View File
@@ -234,6 +234,7 @@ func (db *DB) GetPersona(ctx context.Context, name string, detail bool, override
Name: s.Name,
Description: s.Description,
Tags: s.Tags,
Override: s.Override,
}
if detail {
entry.Content = s.Content
@@ -341,6 +342,7 @@ func (db *DB) GetPersonaManifest(ctx context.Context, name string) (ext.PersonaM
ID: s.ID,
Name: s.Name,
Description: s.Description,
Override: s.Override,
})
}
for _, g := range guardrails {
@@ -567,11 +569,12 @@ func (db *DB) RemovePersonaPart(ctx context.Context, personaName, partName strin
// Persona-Skill links
// ──────────────────────────────────────────────
func (db *DB) AddPersonaSkill(ctx context.Context, personaID, skillID int64) error {
func (db *DB) AddPersonaSkill(ctx context.Context, personaID, skillID int64, override bool) error {
_, err := db.pool.Exec(ctx, `
insert into agent_persona_skills (persona_id, skill_id)
values ($1, $2) on conflict do nothing
`, personaID, skillID)
insert into agent_persona_skills (persona_id, skill_id, override)
values ($1, $2, $3)
on conflict (persona_id, skill_id) do update set override = excluded.override
`, personaID, skillID, override)
if err != nil {
return fmt.Errorf("add persona skill: %w", err)
}
@@ -1058,7 +1061,7 @@ func (db *DB) fetchPartsByNames(ctx context.Context, names []string) ([]rawPart,
func (db *DB) listPersonaSkills(ctx context.Context, personaID int64) ([]AgentSkillRow, error) {
rows, err := db.pool.Query(ctx, `
select s.id, s.name, s.description, s.content, s.tags::text[]
select s.id, s.name, s.description, s.content, s.tags::text[], aps.override
from agent_skills s
join agent_persona_skills aps on aps.skill_id = s.id
where aps.persona_id = $1
@@ -1073,7 +1076,7 @@ func (db *DB) listPersonaSkills(ctx context.Context, personaID int64) ([]AgentSk
for rows.Next() {
var s AgentSkillRow
var tags []string
if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Content, &tags); err != nil {
if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Content, &tags, &s.Override); err != nil {
return nil, fmt.Errorf("scan persona skill: %w", err)
}
s.Tags = nilToEmptyStrings(tags)
@@ -1139,6 +1142,7 @@ type AgentSkillRow struct {
Description string
Content string
Tags []string
Override bool
}
type AgentGuardrailRow struct {
+99
View File
@@ -0,0 +1,99 @@
package store
import (
"context"
"fmt"
"github.com/jackc/pgx/v5"
ext "git.warky.dev/wdevs/amcs/internal/types"
)
func (db *DB) AddProjectPersona(ctx context.Context, projectID, personaID int64, isDefault bool) error {
tx, err := db.pool.Begin(ctx)
if err != nil {
return fmt.Errorf("begin add project persona: %w", err)
}
defer func() { _ = tx.Rollback(ctx) }()
if _, err := tx.Exec(ctx, `select pg_advisory_xact_lock($1)`, projectID); err != nil {
return fmt.Errorf("lock project persona defaults: %w", err)
}
if isDefault {
if _, err := tx.Exec(ctx, `update project_personas set is_default = false where project_id = $1`, projectID); err != nil {
return fmt.Errorf("clear default project persona: %w", err)
}
}
if _, err := tx.Exec(ctx, `
insert into project_personas (project_id, persona_id, is_default)
values ($1, $2, $3)
on conflict (project_id, persona_id) do update set is_default = excluded.is_default
`, projectID, personaID, isDefault); err != nil {
return fmt.Errorf("add project persona: %w", err)
}
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("commit add project persona: %w", err)
}
return nil
}
func (db *DB) RemoveProjectPersona(ctx context.Context, projectID, personaID int64) error {
tag, err := db.pool.Exec(ctx, `delete from project_personas where project_id = $1 and persona_id = $2`, projectID, personaID)
if err != nil {
return fmt.Errorf("remove project persona: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("project persona link not found")
}
return nil
}
func (db *DB) ListProjectPersonas(ctx context.Context, projectID int64) ([]ext.ProjectPersona, error) {
rows, err := db.pool.Query(ctx, `
select p.id, p.guid, p.name, p.description, p.summary, p.detail,
p.compiled_summary, p.compiled_detail, p.compiled_at, p.tags::text[],
p.created_at, p.updated_at, pp.is_default
from agent_personas p
join project_personas pp on pp.persona_id = p.id
where pp.project_id = $1
order by pp.is_default desc, p.name
`, projectID)
if err != nil {
return nil, fmt.Errorf("list project personas: %w", err)
}
defer rows.Close()
var result []ext.ProjectPersona
for rows.Next() {
var item ext.ProjectPersona
var tags []string
if err := rows.Scan(&item.Persona.ID, &item.Persona.GUID, &item.Persona.Name,
&item.Persona.Description, &item.Persona.Summary, &item.Persona.Detail,
&item.Persona.CompiledSummary, &item.Persona.CompiledDetail, &item.Persona.CompiledAt,
&tags, &item.Persona.CreatedAt, &item.Persona.UpdatedAt, &item.IsDefault); err != nil {
return nil, fmt.Errorf("scan project persona: %w", err)
}
item.Persona.Tags = nilToEmptyStrings(tags)
result = append(result, item)
}
return result, rows.Err()
}
func (db *DB) GetDefaultProjectPersona(ctx context.Context, projectID int64) (*ext.Persona, error) {
row := db.pool.QueryRow(ctx, `
select p.id, p.guid, p.name, p.description, p.summary, p.detail,
p.compiled_summary, p.compiled_detail, p.compiled_at, p.tags::text[],
p.created_at, p.updated_at
from agent_personas p
join project_personas pp on pp.persona_id = p.id
where pp.project_id = $1 and pp.is_default
`, projectID)
persona, err := scanPersona(row)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("get default project persona: %w", err)
}
return &persona, nil
}
+31 -8
View File
@@ -116,6 +116,24 @@ func scanSkill(row skillScanner) (ext.AgentSkill, error) {
}, nil
}
func normalizeSkillSlices(skill *ext.AgentSkill) {
if skill.Tags == nil {
skill.Tags = []string{}
}
if skill.LanguageTags == nil {
skill.LanguageTags = []string{}
}
if skill.LibraryTags == nil {
skill.LibraryTags = []string{}
}
if skill.FrameworkTags == nil {
skill.FrameworkTags = []string{}
}
if skill.DomainTags == nil {
skill.DomainTags = []string{}
}
}
func (db *DB) GetSkill(ctx context.Context, id int64) (ext.AgentSkill, error) {
row := db.pool.QueryRow(ctx, `select `+skillSelectCols+` from agent_skills where id = $1`, id)
s, err := scanSkill(row)
@@ -278,12 +296,12 @@ func (db *DB) GetGuardrail(ctx context.Context, id int64) (ext.AgentGuardrail, e
// Project Skills
func (db *DB) AddProjectSkill(ctx context.Context, projectID, skillID int64) error {
func (db *DB) AddProjectSkill(ctx context.Context, projectID, skillID int64, override bool) error {
_, err := db.pool.Exec(ctx, `
insert into project_skills (project_id, skill_id)
values ($1, $2)
on conflict do nothing
`, projectID, skillID)
insert into project_skills (project_id, skill_id, override)
values ($1, $2, $3)
on conflict (project_id, skill_id) do update set override = excluded.override
`, projectID, skillID, override)
if err != nil {
return fmt.Errorf("add project skill: %w", err)
}
@@ -305,7 +323,9 @@ func (db *DB) RemoveProjectSkill(ctx context.Context, projectID, skillID int64)
func (db *DB) ListProjectSkills(ctx context.Context, projectID int64) ([]ext.AgentSkill, error) {
rows, err := db.pool.Query(ctx, `
select s.`+skillSelectCols+`
select s.id, s.name, s.description, s.content, s.tags::text[],
s.language_tags::text[], s.library_tags::text[], s.framework_tags::text[],
s.domain_tags::text[], s.created_at, s.updated_at, ps.override
from agent_skills s
join project_skills ps on ps.skill_id = s.id
where ps.project_id = $1
@@ -318,10 +338,13 @@ func (db *DB) ListProjectSkills(ctx context.Context, projectID int64) ([]ext.Age
var skills []ext.AgentSkill
for rows.Next() {
s, err := scanSkill(rows)
if err != nil {
var s ext.AgentSkill
if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Content, &s.Tags,
&s.LanguageTags, &s.LibraryTags, &s.FrameworkTags, &s.DomainTags,
&s.CreatedAt, &s.UpdatedAt, &s.Override); err != nil {
return nil, fmt.Errorf("scan project skill: %w", err)
}
normalizeSkillSlices(&s)
skills = append(skills, s)
}
return skills, rows.Err()