Author SHA1 Message Date
warkanum 8db2141d45 ci: build ui before go tests
CI / build-and-test (push) Successful in 7m10s
CI / build-and-test (pull_request) Successful in 7m13s
2026-07-14 16:32:00 +02:00
warkanum 3198600031 feat(tools): add duplicate audit report
CI / build-and-test (push) Failing after 1m24s
CI / build-and-test (pull_request) Failing after 59s
2026-07-14 16:28:06 +02:00
warkanum 5c899d1635 Merge pull request 'Link thoughts and learnings' (#36) from issue-32-link-thoughts-learnings into main
CI / build-and-test (push) Failing after 1m6s
Reviewed-on: #36
Reviewed-by: Warky <2+warkanum@noreply@warky.dev>
2026-07-14 14:42:05 +00:00
warkanum 81a3470407 feat(tools): link thoughts and learnings
CI / build-and-test (pull_request) Failing after 1m2s
CI / build-and-test (push) Failing after 3m10s
2026-07-14 15:18:57 +02:00
warkanum c179e014ad 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
2026-07-04 23:45:51 +02:00
Hein 1adf50e3db fix(go.sum): update ResolveSpec dependency to v1.0.87
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s
2026-06-23 13:17:16 +02:00
warkanum 0227912325 feat(auth): add recent activity logging to access tracker
CI / build-and-test (push) Failing after 0s
* Introduced AccessLogEntry type for logging access details
* Updated AccessTracker to maintain a recent activity log
* Modified Metrics to include recent activity log in response
* Added RecentActivityLog component to display logged activities
2026-05-24 16:36:59 +02:00
Hein e38a0377d5 chore(db): comment out GRANT statements for various tables
CI / build-and-test (push) Failing after -29m16s
* Commented out GRANT ALL ON TABLE statements for household, maintenance, family calendar, meal planning, professional CRM, stored files, project skills, and project guardrails.
2026-05-07 14:52:45 +02:00
Hein a993859c62 feat(db): add oauth_clients table for dynamic client registration
CI / build-and-test (push) Has been cancelled
* Introduced oauth_clients table with fields for client_id, client_name, redirect_uris, and created_at.
* Updated agent_persona_parts, agent_persona_skills, agent_persona_guardrails, agent_persona_traits, and arc_stage_parts tables to use unique constraints instead of primary keys for composite indexes.
2026-05-07 13:30:30 +02:00
Hein fb9606ef2b feat(db): add generated models for public schema entities
CI / build-and-test (push) Has been cancelled
* Create ModelPublicPersonaArc for persona_arc table
* Create ModelPublicPlanDependencies for plan_dependencies table
* Create ModelPublicPlanGuardrails for plan_guardrails table
* Create ModelPublicPlanRelatedPlans for plan_related_plans table
* Create ModelPublicPlanSkills for plan_skills table
* Create ModelPublicPlans for plans table
* Create ModelPublicProjectGuardrails for project_guardrails table
* Create ModelPublicProjectSkills for project_skills table
* Create ModelPublicProjects for projects table
* Create ModelPublicStoredFiles for stored_files table
* Create ModelPublicThoughtLinks for thought_links table
* Create ModelPublicThoughts for thoughts table
* Create ModelPublicToolAnnotations for tool_annotations table
2026-05-07 11:19:15 +02:00
Hein a4a0d3ee56 chore(ui): remove placeholder file and add vite-env types
CI / build-and-test (push) Has been cancelled
2026-05-07 10:09:59 +02:00
warkanum 9230f39cb6 feat: add TraitsTab component for managing agent traits
CI / build-and-test (push) Failing after -32m5s
- Implemented TraitsTab.svelte to handle CRUD operations for agent traits.
- Integrated grid for displaying traits with context menu actions for add, edit, and delete.
- Added trait instruction editing functionality with a dedicated editor.
- Updated AdminShell to include PersonasPage for navigation.
- Enhanced AppSidebar with a new entry for Personas.
- Extended ShellPage type to include 'personas'.
- Defined new types for AgentPersona, AgentPart, and AgentTrait in types.ts.
2026-05-05 14:51:58 +02:00
warkanum e285a03639 Add schema for agent personas, parts, traits, and character arcs
CI / build-and-test (push) Failing after -31m18s
- Created tables for agent_personas, agent_parts, agent_traits, and character_arcs.
- Established relationships between personas, parts, skills, guardrails, and traits.
- Added arc stages and their corresponding parts, along with a persona_arc table to track current stages.
- Implemented cascading delete rules for referential integrity.
2026-05-05 09:43:14 +02:00
warkanum 1ceb317f4b feat(skills): enhance agent skills with additional tags and commands
CI / build-and-test (push) Failing after -29m56s
- Added language_tags, library_tags, framework_tags, and domain_tags to agent skills.
- Introduced new commands: get_skill and get_guardrail for fetching specific skills and guardrails.
- Updated database schema and migration scripts to accommodate new fields.
- Enhanced skill listing functionality to support filtering by new tags.
- Improved error handling and response structures for skill-related operations.
2026-05-05 09:24:58 +02:00
warkanum 1f671dad54 feat(personas): add implementation plan for agent personas
CI / build-and-test (push) Failing after -31m39s
2026-05-03 22:18:40 +02:00
warkanum f81de2d94b refactor(ui): replace NativeSelectCtrl with Boxer for project selection
CI / build-and-test (push) Failing after -31m20s
2026-05-03 21:34:29 +02:00
warkanum d6bf0b20d0 refactor(mcpserver): update tool IDs from string UUIDs to integers
CI / build-and-test (push) Failing after -31m3s
2026-05-03 17:26:41 +02:00
warkanum a8214aaa8b fix(db): remove array specifier from tags column
CI / build-and-test (push) Failing after -31m43s
2026-05-03 17:17:01 +02:00
warkanum bae63cb5cf fix(db): correct default values for jsonb and array columns
CI / build-and-test (push) Failing after -31m23s
* Update default values for metadata and messages columns in thoughts and chat_histories tables
* Change tags column type to text[] with default in learnings, plans, agent_skills, and agent_guardrails tables
* Adjust index creation for tags columns in learnings and plans tables
2026-05-03 16:24:16 +02:00
warkanum 91239bcf4b refactor(store,tools): migrate IDs from UUID to bigserial int64
CI / build-and-test (push) Failing after -31m12s
All internal entity lookups now use bigserial primary keys (int64) while
GUIDs are retained for external/public identification. Updated store
functions (TouchProject, UpdateThoughtMetadata, AddThoughtAttachment) to
query by id instead of guid, added GetThoughtByID, changed semanticSearch
and all tool helpers to use *int64 project IDs, and updated retry/backfill
workers to use int64 thought IDs throughout.
2026-05-03 11:43:34 +02:00
warkanum 9e6d05e055 feat(ui): add content editor components for skills and thoughts
CI / build-and-test (push) Failing after -31m24s
* Implement ContentEditorField for inline editing of content
* Create ContentEditorModal for editing content in a modal
* Introduce FormerShell for managing forms related to skills and thoughts
* Enhance SkillsPage and ThoughtsPage with new components for better content management
2026-05-02 19:35:27 +02:00
Hein 442cc3ef53 style(ui): update layout from grid to flex for consistency
CI / build-and-test (push) Failing after -31m52s
2026-04-30 21:02:37 +02:00
Hein 5e54167009 fix(db): update SQL default values and types for consistency
CI / build-and-test (push) Failing after -31m34s
* Corrected default values for various fields in SQL schema
* Changed tags field type from text[] to text[] with proper default
* Updated JSONB default values to remove unnecessary quotes
2026-04-30 20:31:34 +02:00
Hein 65715f7ad3 fix: remove redundant code in processing logic
CI / build-and-test (push) Failing after -31m35s
2026-04-30 16:04:04 +02:00
warkanum 537e65ea6d feat(ui): implement public status endpoint and update UI components
CI / build-and-test (push) Failing after -30m49s
* add public status handler and response types
* modify status API to restrict access and update client tracking
* adjust UI components to display public status information
* update routing to include public status endpoint
2026-04-27 00:23:06 +02:00
warkanum e208c62df3 feat(makefile): add release version handling and remote option
CI / build-and-test (push) Failing after -32m12s
2026-04-27 00:09:45 +02:00
warkanum f6a86e3933 .
CI / build-and-test (push) Failing after -32m5s
2026-04-27 00:04:11 +02:00
warkanum a4193b295a fix(ui): update AMCS references and add status handling
* Corrected "Advanced Module Control System" to "Avalon Memory Control Service" in documentation and UI components.
* Added status handling to the LoginInfoPanel and LoginPage components.
* Implemented new endpoints for robots.txt and llms.txt.
2026-04-27 00:04:08 +02:00
2623 changed files with 1103227 additions and 6353 deletions
+14
View File
@@ -31,6 +31,20 @@ jobs:
- name: Download dependencies - name: Download dependencies
run: go mod download run: go mod download
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Install pnpm
run: npm install -g pnpm
- name: Build UI
run: |
cd ui
pnpm install --frozen-lockfile
pnpm run build
- name: Tidy modules - name: Tidy modules
run: go mod tidy run: go mod tidy
+3 -1
View File
@@ -33,5 +33,7 @@ bin/
OB1/ OB1/
ui/node_modules/ ui/node_modules/
ui/.svelte-kit/ ui/.svelte-kit/
internal/app/ui/dist/ internal/app/ui/dist/*
!internal/app/ui/dist/placeholder.txt
.codex .codex
.worktrees/
+101
View File
@@ -0,0 +1,101 @@
## What This Project Is
AMCS (Avalon Memory Control Service) is an MCP server written in Go that provides memory, knowledge, and task management for AI agents. It exposes 56+ tools via HTTP/SSE MCP transport and includes a Svelte 5 admin UI embedded in the binary.
# Agent Rules
Keep your answers short. Question everything, never assume or guess. Ask the user if you are unsure about anything.
You must use the AMCS MCP system tools. Set Origin as the active project and always capture summaries of what was done as thoughts using the capture_thought tool. If amcs is not available write files to doc/llm/log/yyyymmdd_hh.md
When working on postgresql use the postgresql skill. On go/api/backend the go-skill and for the frontend the "svelte-architect" skill.
## Commands
### Build & Run
```bash
make build # Build server binary (outputs to bin/amcs-server), embeds UI
make build-cli # Build CLI binary (bin/amcs-cli)
make clean # Remove bin/
# Run locally (builds UI then starts server)
./scripts/run-local.sh configs/dev.yaml
# UI hot-reload dev server (proxy to backend on :8080)
make ui-dev # http://localhost:5173
```
### Testing
```bash
make test # All tests (svelte-check + go test ./...)
go test ./internal/tools -run TestFunctionName -v # Single test
go test ./internal/tools -v # Package tests
```
### Database
```bash
make migrate # Apply SQL migrations
make check-schema-drift # Verify no drift between DBML and SQL
```
## Architecture
### Key Pattern: Store → Tool → MCP Handler
Every tool domain follows the same three-layer pattern:
1. **Store** (`internal/store/`) — database access via BUN ORM + pgx. One file per domain (e.g., `thoughts.go`, `plans.go`).
2. **Tool** (`internal/tools/`) — MCP tool handler structs with a `Handle(ctx, req, input)` method. Input/output types are plain Go structs; JSON schemas are auto-generated from struct tags.
3. **Registration** (`internal/mcpserver/handlers.go`) — each domain has a `registerXxxTools(server, logger, ts)` function called from `NewHandlers`.
### Adding a New Tool Domain
Follow the checklist in `internal/tools/` (see `project_conventions.md` in memory, or look at any existing domain like `skills.go` as a reference):
- Define input/output structs with `jsonschema` tags
- Implement a struct with `Handle` method
- Add the struct to `ToolSet` in `mcpserver/handlers.go`
- Wire the store in `internal/store/db.go`
- Add a `registerXxxTools` call in `NewHandlers`
### AI Provider Architecture
Configured in YAML under `ai.providers`. Each role (embeddings, metadata extraction) supports a primary provider and a fallback chain. Providers are named references (litellm, ollama, openrouter) that resolve to OpenAI-compatible endpoints. Health tracking per provider with cooldown on transient failures.
### Error Handling
All tool errors return structured `mcperrors.RPCError` with:
- `data.type` — machine-readable category (`invalid_input`, `project_required`, `entity_not_found`, etc.)
- `data.field` / `data.detail` / `data.hint` — structured validation data
Use helpers in `internal/mcperrors/` rather than raw errors.
### Authentication
- API key via `x-brain-key` header (dev/simple deployments)
- OAuth 2.0 client credentials for production
- Session store tracks active project per client connection
### Database Schema
DBML files in `schema/` are the source of truth for the database schema. Never edit the generated SQL or Go models directly — always edit the DBML, then regenerate:
```bash
make generate-migrations # DBML → SQL migration files
make generate-models # DBML → Go model structs
make migrate # Apply pending migrations to the database
```
All primary keys are `bigserial int64` (migrated from UUID in migration 015).
### Frontend
Svelte 5 + Tailwind + Skeleton UI in `ui/`. Built via pnpm and embedded into the Go binary as a static filesystem. The Vite dev server proxies API calls to the backend on `:8080`.
The UI uses **resolvespec-js** for admin CRUD views. The backend API for the UI follows the **ResolveSpec** convention — resource endpoints, filtering, and pagination are structured according to that spec.
## Configuration
YAML config with schema `version: 2`. Key env var overrides:
- `DATABASE_URL` — PostgreSQL connection string
- `AMCS_LITELLM_API_KEY`, `AMCS_OPENROUTER_API_KEY` — AI provider keys
- `AMCS_SERVER_PORT` — defaults to 8080
See `configs/config.example.yaml` for the full schema. Config auto-migrates v1→v2 on startup.
+1
View File
@@ -0,0 +1 @@
Read the AGENTS.md file
+39 -15
View File
@@ -4,11 +4,15 @@ SERVER_BIN := $(BIN_DIR)/amcs-server
CMD_SERVER := ./cmd/amcs-server CMD_SERVER := ./cmd/amcs-server
BUILDINFO_PKG := git.warky.dev/wdevs/amcs/internal/buildinfo BUILDINFO_PKG := git.warky.dev/wdevs/amcs/internal/buildinfo
UI_DIR := $(CURDIR)/ui UI_DIR := $(CURDIR)/ui
AMCS_UI_BACKEND ?= http://127.0.0.1:8080
PATCH_INCREMENT ?= 1 PATCH_INCREMENT ?= 1
RELEASE_VERSION ?=
RELEASE_REMOTE ?= origin
VERSION_TAG ?= $(shell git describe --tags --exact-match 2>/dev/null || echo dev) VERSION_TAG ?= $(shell git describe --tags --exact-match 2>/dev/null || echo dev)
COMMIT_SHA ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) COMMIT_SHA ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
BUILD_DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) BUILD_DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
RELSPEC ?= $(shell command -v relspec 2>/dev/null || echo $(HOME)/go/bin/relspec) RELSPEC ?= $(shell command -v relspec 2>/dev/null || echo $(HOME)/go/bin/relspec)
#RELSPEC = /mnt/vault/relspecgo/build/relspec
SCHEMA_FILES := $(sort $(wildcard schema/*.dbml)) SCHEMA_FILES := $(sort $(wildcard schema/*.dbml))
MERGE_TARGET_TMP := $(CURDIR)/.cache/schema.merge-target.dbml MERGE_TARGET_TMP := $(CURDIR)/.cache/schema.merge-target.dbml
GENERATED_SCHEMA_MIGRATION := migrations/020_generated_schema.sql GENERATED_SCHEMA_MIGRATION := migrations/020_generated_schema.sql
@@ -20,7 +24,7 @@ LDFLAGS := -s -w \
-X $(BUILDINFO_PKG).Commit=$(COMMIT_SHA) \ -X $(BUILDINFO_PKG).Commit=$(COMMIT_SHA) \
-X $(BUILDINFO_PKG).BuildDate=$(BUILD_DATE) -X $(BUILDINFO_PKG).BuildDate=$(BUILD_DATE)
.PHONY: all build clean migrate release-version test generate-migrations generate-models check-schema-drift build-cli ui-install ui-build ui-dev ui-check help .PHONY: all build clean migrate release-version release-build test generate-migrations generate-models check-schema-drift build-cli ui-install ui-build ui-dev ui-check help
all: build all: build
@@ -31,13 +35,14 @@ help:
@echo " test Run all tests (includes UI check)" @echo " test Run all tests (includes UI check)"
@echo " clean Remove build artifacts" @echo " clean Remove build artifacts"
@echo " migrate Run database migrations" @echo " migrate Run database migrations"
@echo " release-version Tag and push a new patch release (PATCH_INCREMENT=N)" @echo " release-version Tag and push a release (auto patch bump or RELEASE_VERSION=vX.Y.Z)"
@echo " release-build Build with a specific release tag (RELEASE_VERSION=vX.Y.Z)"
@echo " generate-migrations Generate SQL migration from DBML schema files" @echo " generate-migrations Generate SQL migration from DBML schema files"
@echo " generate-models Generate Go models from DBML schema" @echo " generate-models Generate Go models from DBML schema"
@echo " check-schema-drift Verify generated migration matches current schema" @echo " check-schema-drift Verify generated migration matches current schema"
@echo " ui-install Install UI dependencies" @echo " ui-install Install UI dependencies"
@echo " ui-build Build UI assets" @echo " ui-build Build UI assets"
@echo " ui-dev Start UI dev server" @echo " ui-dev Start UI dev server with local API proxy"
@echo " ui-check Run UI type checks" @echo " ui-check Run UI type checks"
build: ui-build build: ui-build
@@ -51,7 +56,7 @@ ui-build: ui-install
cd $(UI_DIR) && $(PNPM) run build cd $(UI_DIR) && $(PNPM) run build
ui-dev: ui-install ui-dev: ui-install
cd $(UI_DIR) && $(PNPM) run dev cd $(UI_DIR) && VITE_API_URL=/api AMCS_UI_BACKEND=$(AMCS_UI_BACKEND) $(PNPM) run dev
ui-check: ui-install ui-check: ui-install
cd $(UI_DIR) && $(PNPM) run check cd $(UI_DIR) && $(PNPM) run check
@@ -64,22 +69,41 @@ release-version:
@case "$(PATCH_INCREMENT)" in \ @case "$(PATCH_INCREMENT)" in \
''|*[!0-9]*|0) echo "PATCH_INCREMENT must be a positive integer" >&2; exit 1 ;; \ ''|*[!0-9]*|0) echo "PATCH_INCREMENT must be a positive integer" >&2; exit 1 ;; \
esac esac
@latest=$$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1); \ @if ! git diff --quiet || ! git diff --cached --quiet; then \
if [ -z "$$latest" ]; then latest="v0.0.0"; fi; \ echo "Refusing to release from a dirty working tree. Commit or stash changes first." >&2; \
version=$${latest#v}; \ exit 1; \
major=$${version%%.*}; \ fi
rest=$${version#*.}; \ @next_tag="$(RELEASE_VERSION)"; \
minor=$${rest%%.*}; \ if [ -z "$$next_tag" ]; then \
patch=$${rest##*.}; \ latest=$$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1); \
next_patch=$$((patch + $(PATCH_INCREMENT))); \ if [ -z "$$latest" ]; then latest="v0.0.0"; fi; \
next_tag="v$$major.$$minor.$$next_patch"; \ version=$${latest#v}; \
major=$${version%%.*}; \
rest=$${version#*.}; \
minor=$${rest%%.*}; \
patch=$${rest##*.}; \
next_patch=$$((patch + $(PATCH_INCREMENT))); \
next_tag="v$$major.$$minor.$$next_patch"; \
fi; \
case "$$next_tag" in \
v[0-9]*.[0-9]*.[0-9]*) ;; \
*) echo "RELEASE_VERSION must look like vX.Y.Z (got '$$next_tag')" >&2; exit 1 ;; \
esac; \
if git rev-parse -q --verify "refs/tags/$$next_tag" >/dev/null; then \ if git rev-parse -q --verify "refs/tags/$$next_tag" >/dev/null; then \
echo "$$next_tag already exists" >&2; \ echo "$$next_tag already exists" >&2; \
exit 1; \ exit 1; \
fi; \ fi; \
git tag -a "$$next_tag" -m "Release $$next_tag"; \ git tag -a "$$next_tag" -m "Release $$next_tag"; \
git push origin "$$next_tag"; \ git push $(RELEASE_REMOTE) "$$next_tag"; \
echo "$$next_tag" $(MAKE) release-build RELEASE_VERSION="$$next_tag"; \
echo "Released $$next_tag"
release-build:
@case "$(RELEASE_VERSION)" in \
v[0-9]*.[0-9]*.[0-9]*) ;; \
*) echo "RELEASE_VERSION must look like vX.Y.Z" >&2; exit 1 ;; \
esac
@$(MAKE) build build-cli VERSION_TAG="$(RELEASE_VERSION)"
migrate: migrate:
./scripts/migrate.sh ./scripts/migrate.sh
+14 -7
View File
@@ -1,10 +1,10 @@
# AMCS Directory # AMCS Directory
This is the AMCS (Advanced Module Control System) directory. This is the AMCS (Avalon Memory Control Service) directory.
## Purpose ## Purpose
The AMCS directory is used to store configuration and code for the Advanced Module Control System, which handles... The AMCS directory is used to store configuration and code for the Avalon Memory Control Service, which handles...
## Structure ## Structure
@@ -65,6 +65,11 @@ The AMCS directory is used to store configuration and code for the Advanced Modu
| `add_project_guardrail` | Link a guardrail to a project; pass `project` if client is stateless | | `add_project_guardrail` | Link a guardrail to a project; pass `project` if client is stateless |
| `remove_project_guardrail` | Unlink a guardrail from a project; pass `project` if client is stateless | | `remove_project_guardrail` | Unlink a guardrail from a project; pass `project` if client is stateless |
| `list_project_guardrails` | Guardrails for a project; pass `project` if client is stateless | | `list_project_guardrails` | Guardrails for a project; pass `project` if client is stateless |
| `bootstrap_world_model` | Validate and activate a project, then load effective skills, guardrails, personas, and bounded context |
| `add_project_persona` | Link a persona to a project and optionally make it the default |
| `remove_project_persona` | Unlink a persona from a project |
| `set_default_project_persona` | Select the default persona for a project |
| `list_project_personas` | List project personas and identify the default |
| `get_version_info` | Build version, commit, and date | | `get_version_info` | Build version, commit, and date |
| `describe_tools` | List all available MCP tools with names, descriptions, categories, and model-authored usage notes; call this at the start of a session to orient yourself | | `describe_tools` | List all available MCP tools with names, descriptions, categories, and model-authored usage notes; call this at the start of a session to orient yourself |
| `annotate_tool` | Persist your own usage notes for a specific tool; notes are returned by `describe_tools` in future sessions | | `annotate_tool` | Persist your own usage notes for a specific tool; notes are returned by `describe_tools` in future sessions |
@@ -216,9 +221,9 @@ Use `get_version_info` to retrieve the runtime build metadata:
## Agent Skills and Guardrails ## Agent Skills and Guardrails
Skills and guardrails are reusable agent behaviour instructions and constraints that can be attached to projects. Skills and guardrails are reusable agent behaviour instructions and constraints that can be attached to projects. Projects can also expose personas and a default persona through the world model.
**At the start of every project session, always call `list_project_skills` and `list_project_guardrails` first.** Use the returned skills and guardrails to guide agent behaviour for that project. Only generate or create new skills/guardrails if none are returned. If your MCP client does not preserve sessions across calls, pass `project` explicitly instead of relying on `set_active_project`. **At the start of every project session, read `amcs://world-model/intro` and call `bootstrap_world_model` with an explicit project.** Apply its skills, guardrails, and default persona before continuing. If your MCP client does not preserve sessions across calls, keep passing `project` explicitly.
### Skills ### Skills
@@ -243,10 +248,12 @@ Severity levels: `low`, `medium`, `high`, `critical`.
Link existing skills and guardrails to a project so they are automatically available when that project is active: Link existing skills and guardrails to a project so they are automatically available when that project is active:
```json ```json
{ "project": "my-project", "skill_id": "<uuid>" } { "project": "my-project", "skill_id": 42, "override": false }
{ "project": "my-project", "guardrail_id": "<uuid>" } { "project": "my-project", "guardrail_id": 17 }
``` ```
Skills are additive by default. Set `override: true` on a project-skill or persona-skill link only when that skill must replace a lower-precedence skill with the same name. World-model precedence is project, selected persona, then runtime. Guardrails are additive and later layers cannot weaken them.
## Configuration ## Configuration
Config is YAML-driven. Copy `configs/config.example.yaml` and set: Config is YAML-driven. Copy `configs/config.example.yaml` and set:
@@ -417,7 +424,7 @@ Returns `{"file": {...}, "uri": "amcs://files/<id>"}`. Pass `thought_id`/`projec
### MCP resources ### MCP resources
Stored files are also exposed as MCP resources at `amcs://files/{id}`. MCP clients can read raw binary content directly via `resources/read` without going through `load_file`. Stored files are also exposed as MCP resources at `amcs://files/{id}`. MCP clients can read raw binary content directly via `resources/read` without going through `load_file`. The startup guide is available at `amcs://world-model/intro`.
### HTTP upload and download ### HTTP upload and download
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

+20
View File
@@ -0,0 +1,20 @@
Completed personas UI reachability and fixed the missing backend ResolveSpec registrations that blocked it.
- Added Personas navigation/page wiring in the Svelte admin shell.
- Added a personas overview page with tabs for personas, parts, and traits.
- Expanded the persona inspector to load linked parts, traits, skills, guardrails, and arc state.
- Found that `/api/rs/public/agent_parts` and related persona routes were missing because `internal/app/resolvespec_admin.go` manually whitelists ResolveSpec models.
- Registered persona-related ResolveSpec models: `agent_personas`, `agent_parts`, `agent_traits`, persona join tables, arc tables, and `persona_arc`.
- Allowed ResolveSpec mutations for `agent_personas`, `agent_parts`, and `agent_traits`.
- Verified the `internal/app` package still compiles with `env GOCACHE=/tmp/amcs-go-cache go test -run '^$' ./internal/app`.
Follow-up:
- Automated ResolveSpec model registration generation with `relspec templ`.
- Added `scripts/templates/resolvespec_models.tmpl`.
- Updated `scripts/generate-models.sh` to generate `internal/app/resolvespec_models_generated.go`.
- Removed the handwritten `resolveSpecModels()` from `internal/app/resolvespec_admin.go`.
- Extended `scripts/patch-generated-models.sh` to fix current relspec output quirks:
- incorrect `persona_arc` primary-key cast
- unused `resolvespec_common` imports in join-table models
- Added focused tests covering persona entity presence and persona mutation allowlisting.
+15
View File
@@ -0,0 +1,15 @@
Fixed the Gitea build break caused by `go:embed` requiring `internal/app/ui/dist` to exist in a clean checkout.
Changes made:
- Added `internal/app/ui/dist/placeholder.txt` so the embedded UI directory is always present in source control.
- Updated `internal/mcpserver/server_test.go` to derive expected tool names from `BuildToolCatalog()` instead of a stale hard-coded list.
- Removed stale maintenance tool entries from `internal/mcpserver/server.go` because those tools are not currently registered.
Verification:
- `env GOCACHE=/tmp/go-build go test ./internal/mcpserver -run TestNewListsAllRegisteredTools -v` passes.
- A broader `go test ./internal/app ./cmd/amcs-server` compile check was started, but it did not finish before this log entry was written.
Migration follow-up:
- Fixed `migrations/020_generated_schema.sql` after PostgreSQL failed with `operator does not exist: name[] = text[]`.
- Root cause: `pg_attribute.attname` is type `name`, so `ARRAY(SELECT a.attname ...)` produced `name[]`, which was compared against `text[]` literals.
- Updated each repeated primary-key introspection block to use `SELECT a.attname::text`, keeping the existing `ARRAY[]::text[]` and `ARRAY['id']` / `ARRAY['persona_id']` comparisons valid.
+30
View File
@@ -0,0 +1,30 @@
# World Model Planning
Project: AMCS
AMCS already exposes MCP initialization instructions from `llm/memory.md`, active-project tools, project-linked skills and guardrails, persona manifests, project context, and MCP file resources.
Recommended design:
- Keep a concise mandatory world-model bootstrap protocol in MCP initialization instructions.
- Add a readable static resource at `amcs://world-model/intro` containing the fuller introduction and project-selection protocol.
- Add one `bootstrap_world_model` tool. It accepts an explicit project name or ID, validates it, sets it active when the transport is stateful, and returns one bounded snapshot containing project identity, linked skills, linked guardrails, linked persona manifests/default persona, and recent project context.
- Never silently select or create a project. Return structured `project_not_found` or `project_ambiguous` errors with candidate projects; stateless clients must keep passing the project explicitly.
- Add project-to-persona associations because the current schema only associates skills and guardrails with projects. Support one optional default persona plus additional available personas. Return manifests at bootstrap and load full persona content on demand.
- Merge skills additively by default. Store an explicit `override` flag on project-skill and persona-skill association rows, not on the reusable skill itself. An overriding skill replaces lower-precedence skills with the same stable skill key; unrelated skills remain additive.
- Resolve precedence deterministically: project skills form the base, the selected persona is applied next, and explicit runtime/session skills are applied last. At each layer, `override: true` replaces the accumulated entry with the same key while `override: false` adds or de-duplicates it.
- Include a snapshot version and generation timestamp so clients can cache and refresh the world model safely.
Delivery phases:
1. Fix the existing `list_project_skills` SQL failure: `skillSelectCols` leaves `created_at` and other columns unqualified in a join.
2. Define the bootstrap response contract, size limits, precedence rules, and structured errors.
3. Add project-persona schema/store/tool support through DBML generation.
4. Add the intro resource and initialization instructions.
5. Implement and register `bootstrap_world_model` using existing project, skill, guardrail, persona, and context stores.
6. Add MCP lifecycle, stateful/stateless, missing/ambiguous project, payload-bound, and resource tests.
7. Add admin UI management for project personas/default persona and update client documentation.
Confirmed decision: skills are additive unless their association has `override: true`. Guardrails remain additive, use the stricter severity when duplicates occur, and cannot be weakened by persona or runtime context.
AMCS MCP writes and subsequent verification hung during this planning session, so this local log is the required fallback rather than a confirmed remote thought/plan.
+28 -28
View File
@@ -3,30 +3,29 @@ module git.warky.dev/wdevs/amcs
go 1.26.1 go 1.26.1
require ( require (
github.com/bitechdev/ResolveSpec v1.0.86 github.com/bitechdev/ResolveSpec v1.1.24
github.com/google/jsonschema-go v0.4.2 github.com/google/jsonschema-go v0.4.3
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.9.1 github.com/jackc/pgx/v5 v5.9.2
github.com/modelcontextprotocol/go-sdk v1.4.1 github.com/modelcontextprotocol/go-sdk v1.4.1
github.com/pgvector/pgvector-go v0.3.0 github.com/pgvector/pgvector-go v0.3.0
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
github.com/uptrace/bun v1.2.16 github.com/uptrace/bun v1.2.18
github.com/uptrace/bun/dialect/pgdialect v1.2.16 github.com/uptrace/bun/dialect/pgdialect v1.2.18
github.com/uptrace/bun/driver/pgdriver v1.1.12 github.com/uptrace/bun/driver/pgdriver v1.1.12
github.com/uptrace/bunrouter v1.0.23 github.com/uptrace/bunrouter v1.0.23
golang.org/x/sync v0.19.0 golang.org/x/sync v0.20.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/getsentry/sentry-go v0.46.2 // indirect
github.com/getsentry/sentry-go v0.40.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
@@ -36,17 +35,17 @@ require (
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-sqlite3 v1.14.33 // indirect github.com/mattn/go-sqlite3 v1.14.44 // indirect
github.com/microsoft/go-mssqldb v1.9.5 // indirect github.com/microsoft/go-mssqldb v1.10.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.19.2 // indirect github.com/prometheus/procfs v0.20.1 // indirect
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
github.com/redis/go-redis/v9 v9.17.2 // indirect github.com/redis/go-redis/v9 v9.19.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect github.com/segmentio/asm v1.1.3 // indirect
@@ -58,26 +57,27 @@ require (
github.com/spf13/viper v1.21.0 // indirect github.com/spf13/viper v1.21.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect github.com/stretchr/testify v1.11.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/gjson v1.19.0 // indirect
github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/uptrace/bun/dialect/mssqldialect v1.2.16 // indirect github.com/uptrace/bun/dialect/mssqldialect v1.2.18 // indirect
github.com/uptrace/bun/dialect/sqlitedialect v1.2.16 // indirect github.com/uptrace/bun/dialect/sqlitedialect v1.2.18 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect go.uber.org/zap v1.28.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.46.0 // indirect golang.org/x/crypto v0.51.0 // indirect
golang.org/x/mod v0.31.0 // indirect golang.org/x/mod v0.36.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.40.0 // indirect golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.32.0 // indirect golang.org/x/text v0.37.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
gorm.io/driver/postgres v1.6.0 // indirect gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/driver/sqlite v1.6.0 // indirect gorm.io/driver/sqlite v1.6.0 // indirect
+94 -91
View File
@@ -5,39 +5,39 @@ entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitechdev/ResolveSpec v1.0.86 h1:a4yFMMDizrmvDOV61cj/+kD+mEtKL/5EIHY2GcP3uJU= github.com/bitechdev/ResolveSpec v1.1.24 h1:+Ku3jE8ZSQ2c6IdVyqYp9CdCh4wSasCd1yLDF8GXy5U=
github.com/bitechdev/ResolveSpec v1.0.86/go.mod h1:YZOY2YCD0Kmb+pjAMhOqPh4q82Hij57F/CLlCMkzT78= github.com/bitechdev/ResolveSpec v1.1.24/go.mod h1:GF51sMRCWbAyri2WNae3IZAFM/2s6DG6i3eTTrobbVs=
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c h1:6Gpm9YYUEQx2T9zMsYolQhr6sjwwGtFitSA0pQsa7a8=
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bradfitz/gomemcache v0.0.0-20260422231931-4d751bb6e37c/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -59,10 +59,9 @@ github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
@@ -81,10 +80,10 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo= github.com/getsentry/sentry-go v0.46.2 h1:1jhYwrKGa3sIpo/y5iDNXS5wDoT7I1KNzMHrnK6ojns=
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= github.com/getsentry/sentry-go v0.46.2/go.mod h1:evVbw2qotNUdYG8KxXbAdjOQWWvWIwKxpjdZZIvcIPw=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
@@ -97,13 +96,13 @@ github.com/go-pg/pg/v10 v10.11.0 h1:CMKJqLgTrfpE/aOVeLdybezR2om071Vh38OLZjsyMI0=
github.com/go-pg/pg/v10 v10.11.0/go.mod h1:4BpHRoxE61y4Onpof3x1a2SQvi9c+q1dJnrNdMjsroA= github.com/go-pg/pg/v10 v10.11.0/go.mod h1:4BpHRoxE61y4Onpof3x1a2SQvi9c+q1dJnrNdMjsroA=
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -111,8 +110,8 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 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/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -128,8 +127,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
@@ -144,8 +143,10 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -161,13 +162,13 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo= github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
github.com/microsoft/go-mssqldb v1.9.5 h1:orwya0X/5bsL1o+KasupTkk2eNTNFkTQG0BEe/HxCn0= github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY=
github.com/microsoft/go-mssqldb v1.9.5/go.mod h1:VCP2a0KEZZtGLRHd1PsLavLFYy/3xX2yJUPycv3Sr2Q= github.com/microsoft/go-mssqldb v1.10.0/go.mod h1:mnG7lGa9iYJbzJqGCXyuQCegStKMr3kogDLD6+bmggg=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
@@ -196,8 +197,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pgvector/pgvector-go v0.3.0 h1:Ij+Yt78R//uYqs3Zk35evZFvr+G0blW0OUN+Q2D1RWc= github.com/pgvector/pgvector-go v0.3.0 h1:Ij+Yt78R//uYqs3Zk35evZFvr+G0blW0OUN+Q2D1RWc=
github.com/pgvector/pgvector-go v0.3.0/go.mod h1:duFy+PXWfW7QQd5ibqutBO4GxLsUZ9RVXhFZGIBsWSA= github.com/pgvector/pgvector-go v0.3.0/go.mod h1:duFy+PXWfW7QQd5ibqutBO4GxLsUZ9RVXhFZGIBsWSA=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@@ -216,14 +217,14 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI= github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
@@ -273,8 +274,8 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
@@ -289,14 +290,14 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/uptrace/bun v1.2.16 h1:QlObi6ZIK5Ao7kAALnh91HWYNZUBbVwye52fmlQM9kc= github.com/uptrace/bun v1.2.18 h1:3HnRcMfS6OBPMG1eSOzlbFJ/X/AyMEJb7rMxE6VQvDU=
github.com/uptrace/bun v1.2.16/go.mod h1:jMoNg2n56ckaawi/O/J92BHaECmrz6IRjuMWqlMaMTM= github.com/uptrace/bun v1.2.18/go.mod h1:wNltaKJk4JtOt4SG5I5zmA7v0/Mzjh1+/S906Rayd3Y=
github.com/uptrace/bun/dialect/mssqldialect v1.2.16 h1:rKv0cKPNBviXadB/+2Y/UedA/c1JnwGzUWZkdN5FdSQ= github.com/uptrace/bun/dialect/mssqldialect v1.2.18 h1:nYzHoyJKJlIyl5i95Exi8ZTK8ooKWG+o3z3f404d/yQ=
github.com/uptrace/bun/dialect/mssqldialect v1.2.16/go.mod h1:J5U7tGKWDsx2Q7MwDZF2417jCdpD6yD/ZMFJcCR80bk= github.com/uptrace/bun/dialect/mssqldialect v1.2.18/go.mod h1:Su45Je7z66sfeZ3d1ZsnOQEK8xfzGgaMzBvtoE8yFhk=
github.com/uptrace/bun/dialect/pgdialect v1.2.16 h1:KFNZ0LxAyczKNfK/IJWMyaleO6eI9/Z5tUv3DE1NVL4= github.com/uptrace/bun/dialect/pgdialect v1.2.18 h1:IZ6nM2+OYrL8lkEAy7UkSEZvoa3vluTAUlZfPtlRB2k=
github.com/uptrace/bun/dialect/pgdialect v1.2.16/go.mod h1:IJdMeV4sLfh0LDUZl7TIxLI0LipF1vwTK3hBC7p5qLo= github.com/uptrace/bun/dialect/pgdialect v1.2.18/go.mod h1:Tqdf4QP1okrGYpXfodXvCOK6Ob1OOTwSaoAzCgBB3IU=
github.com/uptrace/bun/dialect/sqlitedialect v1.2.16 h1:6wVAiYLj1pMibRthGwy4wDLa3D5AQo32Y8rvwPd8CQ0= github.com/uptrace/bun/dialect/sqlitedialect v1.2.18 h1:Z33SY/U++XK9uGWqS4h8OZVxfCXguIG+sU9cYq2PGFQ=
github.com/uptrace/bun/dialect/sqlitedialect v1.2.16/go.mod h1:Z7+5qK8CGZkDQiPMu+LSdVuDuR1I5jcwtkB1Pi3F82E= github.com/uptrace/bun/dialect/sqlitedialect v1.2.18/go.mod h1:1MVOS/Ncy4FZbkJcgUFH6OqYoQinYNjkEwsmNQEXz2A=
github.com/uptrace/bun/driver/pgdriver v1.1.12 h1:3rRWB1GK0psTJrHwxzNfEij2MLibggiLdTqjTtfHc1w= github.com/uptrace/bun/driver/pgdriver v1.1.12 h1:3rRWB1GK0psTJrHwxzNfEij2MLibggiLdTqjTtfHc1w=
github.com/uptrace/bun/driver/pgdriver v1.1.12/go.mod h1:ssYUP+qwSEgeDDS1xm2XBip9el1y9Mi5mTAvLoiADLM= github.com/uptrace/bun/driver/pgdriver v1.1.12/go.mod h1:ssYUP+qwSEgeDDS1xm2XBip9el1y9Mi5mTAvLoiADLM=
github.com/uptrace/bun/driver/sqliteshim v1.2.16 h1:M6Dh5kkDWFbUWBrOsIE1g1zdZ5JbSytTD4piFRBOUAI= github.com/uptrace/bun/driver/sqliteshim v1.2.16 h1:M6Dh5kkDWFbUWBrOsIE1g1zdZ5JbSytTD4piFRBOUAI=
@@ -318,24 +319,28 @@ github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT0
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -350,18 +355,16 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -379,10 +382,10 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -390,8 +393,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -411,8 +414,8 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -441,16 +444,16 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
@@ -475,11 +478,11 @@ gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
modernc.org/libc v1.67.4 h1:zZGmCMUVPORtKv95c2ReQN5VDjvkoRm9GWPTEPuvlWg= modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
modernc.org/libc v1.67.4/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA= modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.42.2 h1:7hkZUNJvJFN2PgfUdjni9Kbvd4ef4mNLOu0B9FGxM74= modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
modernc.org/sqlite v1.42.2/go.mod h1:+VkC6v3pLOAE0A0uVucQEcbVW0I5nHCeDaBf+DpsQT8= modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
+36 -26
View File
@@ -100,7 +100,7 @@ func Run(ctx context.Context, configPath string) error {
} }
} }
authCodes := auth.NewAuthCodeStore() authCodes := auth.NewAuthCodeStore()
dynClients := auth.NewDynamicClientStore() dynClients := auth.NewPostgresClientStore(db.Pool())
activeProjects := session.NewActiveProjects() activeProjects := session.NewActiveProjects()
logger.Info("ai providers initialised", logger.Info("ai providers initialised",
@@ -183,7 +183,7 @@ func Run(ctx context.Context, configPath string) error {
} }
} }
func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *store.DB, embeddings *ai.EmbeddingRunner, metadata *ai.MetadataRunner, bgEmbeddings *ai.EmbeddingRunner, bgMetadata *ai.MetadataRunner, keyring *auth.Keyring, oauthRegistry *auth.OAuthRegistry, tokenStore *auth.TokenStore, authCodes *auth.AuthCodeStore, dynClients *auth.DynamicClientStore, activeProjects *session.ActiveProjects) (http.Handler, error) { func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *store.DB, embeddings *ai.EmbeddingRunner, metadata *ai.MetadataRunner, bgEmbeddings *ai.EmbeddingRunner, bgMetadata *ai.MetadataRunner, keyring *auth.Keyring, oauthRegistry *auth.OAuthRegistry, tokenStore *auth.TokenStore, authCodes *auth.AuthCodeStore, dynClients auth.ClientStore, activeProjects *session.ActiveProjects) (http.Handler, error) {
mux := http.NewServeMux() mux := http.NewServeMux()
accessTracker := auth.NewAccessTracker() accessTracker := auth.NewAccessTracker()
oauthEnabled := oauthRegistry != nil oauthEnabled := oauthRegistry != nil
@@ -194,29 +194,35 @@ func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *st
adminActions := newAdminActions(backfillTool, enrichmentRetryer, logger) adminActions := newAdminActions(backfillTool, enrichmentRetryer, logger)
toolSet := mcpserver.ToolSet{ toolSet := mcpserver.ToolSet{
Capture: tools.NewCaptureTool(db, embeddings, cfg.Capture, activeProjects, enrichmentRetryer, backfillTool), Capture: tools.NewCaptureTool(db, embeddings, cfg.Capture, activeProjects, enrichmentRetryer, backfillTool),
Search: tools.NewSearchTool(db, embeddings, cfg.Search, activeProjects), Search: tools.NewSearchTool(db, embeddings, cfg.Search, activeProjects),
List: tools.NewListTool(db, cfg.Search, activeProjects), List: tools.NewListTool(db, cfg.Search, activeProjects),
Stats: tools.NewStatsTool(db), Stats: tools.NewStatsTool(db),
Get: tools.NewGetTool(db), Get: tools.NewGetTool(db),
Update: tools.NewUpdateTool(db, embeddings, metadata, cfg.Capture, logger), Update: tools.NewUpdateTool(db, embeddings, metadata, cfg.Capture, logger),
Delete: tools.NewDeleteTool(db), Delete: tools.NewDeleteTool(db),
Archive: tools.NewArchiveTool(db), Archive: tools.NewArchiveTool(db),
Projects: tools.NewProjectsTool(db, activeProjects), DuplicateAudit: tools.NewDuplicateAuditTool(db, cfg.Search, activeProjects),
Version: tools.NewVersionTool(cfg.MCP.ServerName, info), Projects: tools.NewProjectsTool(db, activeProjects),
Learnings: tools.NewLearningsTool(db, activeProjects, cfg.Search), Version: tools.NewVersionTool(cfg.MCP.ServerName, info),
Context: tools.NewContextTool(db, embeddings, cfg.Search, activeProjects), Learnings: tools.NewLearningsTool(db, activeProjects, cfg.Search),
Recall: tools.NewRecallTool(db, embeddings, cfg.Search, activeProjects), Plans: tools.NewPlansTool(db, activeProjects, cfg.Search),
Summarize: tools.NewSummarizeTool(db, embeddings, metadata, cfg.Search, activeProjects), ProjectPersonas: tools.NewProjectPersonasTool(db, activeProjects),
Links: tools.NewLinksTool(db, embeddings, cfg.Search), WorldModel: tools.NewWorldModelTool(db, activeProjects),
Files: filesTool, ThoughtLearningLinks: tools.NewThoughtLearningLinksTool(db),
Backfill: backfillTool, Context: tools.NewContextTool(db, embeddings, cfg.Search, activeProjects),
Reparse: tools.NewReparseMetadataTool(db, bgMetadata, cfg.Capture, activeProjects, logger), Recall: tools.NewRecallTool(db, embeddings, cfg.Search, activeProjects),
RetryMetadata: tools.NewRetryEnrichmentTool(enrichmentRetryer), Summarize: tools.NewSummarizeTool(db, embeddings, metadata, cfg.Search, activeProjects),
Maintenance: tools.NewMaintenanceTool(db), Links: tools.NewLinksTool(db, embeddings, cfg.Search),
Skills: tools.NewSkillsTool(db, activeProjects), Files: filesTool,
ChatHistory: tools.NewChatHistoryTool(db, activeProjects), Backfill: backfillTool,
Describe: tools.NewDescribeTool(db, mcpserver.BuildToolCatalog()), Reparse: tools.NewReparseMetadataTool(db, bgMetadata, cfg.Capture, activeProjects, logger),
RetryMetadata: tools.NewRetryEnrichmentTool(enrichmentRetryer),
//Maintenance: tools.NewMaintenanceTool(db),
Skills: tools.NewSkillsTool(db, activeProjects),
Personas: tools.NewAgentPersonasTool(db),
ChatHistory: tools.NewChatHistoryTool(db, activeProjects),
Describe: tools.NewDescribeTool(db, mcpserver.BuildToolCatalog()),
} }
mcpHandlers, err := mcpserver.NewHandlers(cfg.MCP, logger, toolSet, activeProjects.Clear) mcpHandlers, err := mcpserver.NewHandlers(cfg.MCP, logger, toolSet, activeProjects.Clear)
@@ -243,7 +249,11 @@ func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *st
mux.HandleFunc("/images/project.jpg", serveHomeImage) mux.HandleFunc("/images/project.jpg", serveHomeImage)
mux.HandleFunc("/images/icon.png", serveIcon) mux.HandleFunc("/images/icon.png", serveIcon)
mux.HandleFunc("/llm", serveLLMInstructions) mux.HandleFunc("/llm", serveLLMInstructions)
mux.HandleFunc("/api/status", statusAPIHandler(info, accessTracker, oauthEnabled)) mux.HandleFunc("/llms.txt", serveLLMSTXT)
mux.HandleFunc("/.well-known/llms.txt", serveLLMSTXT)
mux.HandleFunc("/robots.txt", serveRobotsTXT)
mux.Handle("/api/status", authMiddleware(statusAPIHandler(info, accessTracker, oauthEnabled)))
mux.HandleFunc("/status", publicStatusHandler(accessTracker))
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
+73
View File
@@ -1,7 +1,9 @@
package app package app
import ( import (
"fmt"
"net/http" "net/http"
"strings"
amcsllm "git.warky.dev/wdevs/amcs/llm" amcsllm "git.warky.dev/wdevs/amcs/llm"
) )
@@ -20,3 +22,74 @@ func serveLLMInstructions(w http.ResponseWriter, r *http.Request) {
} }
_, _ = w.Write(amcsllm.MemoryInstructions) _, _ = w.Write(amcsllm.MemoryInstructions)
} }
func serveRobotsTXT(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/robots.txt" {
http.NotFound(w, r)
return
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.Header().Set("Allow", "GET, HEAD")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=300")
w.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
body := fmt.Sprintf("User-agent: *\nAllow: /\n\n# LLM-friendly docs\nLLM: %s/llm\nLLMS: %s/llms.txt\n", requestBaseURL(r), requestBaseURL(r))
_, _ = w.Write([]byte(body))
}
func serveLLMSTXT(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/llms.txt" && r.URL.Path != "/.well-known/llms.txt" {
http.NotFound(w, r)
return
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.Header().Set("Allow", "GET, HEAD")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=300")
w.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
base := requestBaseURL(r)
body := fmt.Sprintf(
"# AMCS\n\n> A memory server for AI assistants (MCP tools, semantic retrieval, and structured project memory).\n\n## Endpoints\n- %s/llm\n- %s/status\n- %s/mcp\n- %s/.well-known/oauth-authorization-server\n",
base,
base,
base,
base,
)
_, _ = w.Write([]byte(body))
}
func requestBaseURL(r *http.Request) string {
scheme := "http"
if r != nil && r.TLS != nil {
scheme = "https"
}
if r != nil {
if proto := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); proto != "" {
scheme = proto
}
}
host := "localhost"
if r != nil {
if v := strings.TrimSpace(r.Host); v != "" {
host = v
}
}
return scheme + "://" + host
}
+68
View File
@@ -3,6 +3,7 @@ package app
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
amcsllm "git.warky.dev/wdevs/amcs/llm" amcsllm "git.warky.dev/wdevs/amcs/llm"
@@ -29,3 +30,70 @@ func TestServeLLMInstructions(t *testing.T) {
t.Fatalf("body = %q, want embedded instructions", body) t.Fatalf("body = %q, want embedded instructions", body)
} }
} }
func TestServeRobotsTXT(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/robots.txt", nil)
req.Host = "amcs.example.com"
req.Header.Set("X-Forwarded-Proto", "https")
rec := httptest.NewRecorder()
serveRobotsTXT(rec, req)
res := rec.Result()
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want %d", res.StatusCode, http.StatusOK)
}
if got := res.Header.Get("Content-Type"); got != "text/plain; charset=utf-8" {
t.Fatalf("content-type = %q, want %q", got, "text/plain; charset=utf-8")
}
body := rec.Body.String()
if !strings.Contains(body, "LLM: https://amcs.example.com/llm") {
t.Fatalf("body = %q, want LLM link", body)
}
if !strings.Contains(body, "LLMS: https://amcs.example.com/llms.txt") {
t.Fatalf("body = %q, want LLMS link", body)
}
}
func TestServeLLMSTXT(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/llms.txt", nil)
req.Host = "amcs.example.com"
req.Header.Set("X-Forwarded-Proto", "https")
rec := httptest.NewRecorder()
serveLLMSTXT(rec, req)
res := rec.Result()
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want %d", res.StatusCode, http.StatusOK)
}
if got := res.Header.Get("Content-Type"); got != "text/plain; charset=utf-8" {
t.Fatalf("content-type = %q, want %q", got, "text/plain; charset=utf-8")
}
body := rec.Body.String()
if !strings.Contains(body, "https://amcs.example.com/llm") {
t.Fatalf("body = %q, want /llm link", body)
}
if !strings.Contains(body, "https://amcs.example.com/.well-known/oauth-authorization-server") {
t.Fatalf("body = %q, want oauth discovery link", body)
}
}
func TestServeLLMSTXTWellKnownPath(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/.well-known/llms.txt", nil)
rec := httptest.NewRecorder()
serveLLMSTXT(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
}
+4 -4
View File
@@ -83,7 +83,7 @@ func oauthMetadataHandler() http.HandlerFunc {
// oauthRegisterHandler serves POST /oauth/register per RFC 7591 // oauthRegisterHandler serves POST /oauth/register per RFC 7591
// (OAuth 2.0 Dynamic Client Registration). // (OAuth 2.0 Dynamic Client Registration).
func oauthRegisterHandler(dynClients *auth.DynamicClientStore, log *slog.Logger) http.HandlerFunc { func oauthRegisterHandler(dynClients auth.ClientStore, log *slog.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
w.Header().Set("Allow", http.MethodPost) w.Header().Set("Allow", http.MethodPost)
@@ -130,7 +130,7 @@ func oauthRegisterHandler(dynClients *auth.DynamicClientStore, log *slog.Logger)
// oauthAuthorizeHandler serves GET and POST /oauth/authorize. // oauthAuthorizeHandler serves GET and POST /oauth/authorize.
// GET shows an approval page; POST processes the user's approve/deny action. // GET shows an approval page; POST processes the user's approve/deny action.
func oauthAuthorizeHandler(dynClients *auth.DynamicClientStore, authCodes *auth.AuthCodeStore, log *slog.Logger) http.HandlerFunc { func oauthAuthorizeHandler(dynClients auth.ClientStore, authCodes *auth.AuthCodeStore, log *slog.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
@@ -144,7 +144,7 @@ func oauthAuthorizeHandler(dynClients *auth.DynamicClientStore, authCodes *auth.
} }
} }
func handleAuthorizeGET(w http.ResponseWriter, r *http.Request, dynClients *auth.DynamicClientStore) { func handleAuthorizeGET(w http.ResponseWriter, r *http.Request, dynClients auth.ClientStore) {
q := r.URL.Query() q := r.URL.Query()
clientID := q.Get("client_id") clientID := q.Get("client_id")
redirectURI := q.Get("redirect_uri") redirectURI := q.Get("redirect_uri")
@@ -178,7 +178,7 @@ func handleAuthorizeGET(w http.ResponseWriter, r *http.Request, dynClients *auth
serveAuthorizePage(w, client.ClientName, clientID, redirectURI, state, codeChallenge, codeChallengeMethod, scope) serveAuthorizePage(w, client.ClientName, clientID, redirectURI, state, codeChallenge, codeChallengeMethod, scope)
} }
func handleAuthorizePOST(w http.ResponseWriter, r *http.Request, dynClients *auth.DynamicClientStore, authCodes *auth.AuthCodeStore, log *slog.Logger) { func handleAuthorizePOST(w http.ResponseWriter, r *http.Request, dynClients auth.ClientStore, authCodes *auth.AuthCodeStore, log *slog.Logger) {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
http.Error(w, "invalid form", http.StatusBadRequest) http.Error(w, "invalid form", http.StatusBadRequest)
return return
+52 -31
View File
@@ -4,11 +4,11 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
"strings"
"github.com/bitechdev/ResolveSpec/pkg/resolvespec" "github.com/bitechdev/ResolveSpec/pkg/resolvespec"
"github.com/uptrace/bunrouter" "github.com/uptrace/bunrouter"
"git.warky.dev/wdevs/amcs/internal/generatedmodels"
"git.warky.dev/wdevs/amcs/internal/store" "git.warky.dev/wdevs/amcs/internal/store"
) )
@@ -26,6 +26,21 @@ func registerResolveSpecAdminRoutes(mux *http.ServeMux, db *store.DB, middleware
rsMount := http.StripPrefix("/api/rs", rsRouter) rsMount := http.StripPrefix("/api/rs", rsRouter)
protectedRSMount := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { protectedRSMount := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" && strings.HasSuffix(r.URL.Path, "/") {
trimmed := strings.TrimRight(r.URL.Path, "/")
if trimmed == "" {
trimmed = "/"
}
clone := r.Clone(r.Context())
clone.URL.Path = trimmed
if clone.URL.RawPath != "" {
clone.URL.RawPath = strings.TrimRight(clone.URL.RawPath, "/")
if clone.URL.RawPath == "" {
clone.URL.RawPath = "/"
}
}
r = clone
}
if r.Method == http.MethodOptions { if r.Method == http.MethodOptions {
rsMount.ServeHTTP(w, r) rsMount.ServeHTTP(w, r)
return return
@@ -50,15 +65,51 @@ func registerResolveSpecGuards(rs *resolvespec.Handler) {
mutableByEntity := map[string]map[string]struct{}{ mutableByEntity := map[string]map[string]struct{}{
"projects": { "projects": {
"create": {}, "create": {},
"update": {},
"delete": {},
}, },
"thoughts": { "thoughts": {
"create": {},
"update": {},
"delete": {},
},
"plans": {
"create": {},
"update": {},
"delete": {},
},
"learnings": {
"create": {},
"update": {},
"delete": {},
},
"agent_personas": {
"create": {},
"update": {},
"delete": {},
},
"agent_parts": {
"create": {},
"update": {},
"delete": {},
},
"agent_traits": {
"create": {},
"update": {}, "update": {},
"delete": {}, "delete": {},
}, },
"agent_skills": { "agent_skills": {
"create": {},
"update": {},
"delete": {}, "delete": {},
}, },
"agent_guardrails": { "agent_guardrails": {
"create": {},
"update": {},
"delete": {},
},
"stored_files": {
"update": {},
"delete": {}, "delete": {},
}, },
} }
@@ -96,33 +147,3 @@ type resolveSpecModel struct {
entity string entity string
model any model any
} }
func resolveSpecModels() []resolveSpecModel {
return []resolveSpecModel{
{schema: "public", entity: "activities", model: generatedmodels.ModelPublicActivities{}},
{schema: "public", entity: "agent_guardrails", model: generatedmodels.ModelPublicAgentGuardrails{}},
{schema: "public", entity: "agent_skills", model: generatedmodels.ModelPublicAgentSkills{}},
{schema: "public", entity: "chat_histories", model: generatedmodels.ModelPublicChatHistories{}},
{schema: "public", entity: "contact_interactions", model: generatedmodels.ModelPublicContactInteractions{}},
{schema: "public", entity: "embeddings", model: generatedmodels.ModelPublicEmbeddings{}},
{schema: "public", entity: "family_members", model: generatedmodels.ModelPublicFamilyMembers{}},
{schema: "public", entity: "household_items", model: generatedmodels.ModelPublicHouseholdItems{}},
{schema: "public", entity: "household_vendors", model: generatedmodels.ModelPublicHouseholdVendors{}},
{schema: "public", entity: "important_dates", model: generatedmodels.ModelPublicImportantDates{}},
{schema: "public", entity: "learnings", model: generatedmodels.ModelPublicLearnings{}},
{schema: "public", entity: "maintenance_logs", model: generatedmodels.ModelPublicMaintenanceLogs{}},
{schema: "public", entity: "maintenance_tasks", model: generatedmodels.ModelPublicMaintenanceTasks{}},
{schema: "public", entity: "meal_plans", model: generatedmodels.ModelPublicMealPlans{}},
{schema: "public", entity: "opportunities", model: generatedmodels.ModelPublicOpportunities{}},
{schema: "public", entity: "professional_contacts", model: generatedmodels.ModelPublicProfessionalContacts{}},
{schema: "public", entity: "project_guardrails", model: generatedmodels.ModelPublicProjectGuardrails{}},
{schema: "public", entity: "project_skills", model: generatedmodels.ModelPublicProjectSkills{}},
{schema: "public", entity: "projects", model: generatedmodels.ModelPublicProjects{}},
{schema: "public", entity: "recipes", model: generatedmodels.ModelPublicRecipes{}},
{schema: "public", entity: "shopping_lists", model: generatedmodels.ModelPublicShoppingLists{}},
{schema: "public", entity: "stored_files", model: generatedmodels.ModelPublicStoredFiles{}},
{schema: "public", entity: "thought_links", model: generatedmodels.ModelPublicThoughtLinks{}},
{schema: "public", entity: "thoughts", model: generatedmodels.ModelPublicThoughts{}},
{schema: "public", entity: "tool_annotations", model: generatedmodels.ModelPublicToolAnnotations{}},
}
}
+53 -19
View File
@@ -63,10 +63,34 @@ func TestResolveSpecGuardAllowsSupportedMutations(t *testing.T) {
}{ }{
{name: "learnings read", entity: "learnings", operation: "read"}, {name: "learnings read", entity: "learnings", operation: "read"},
{name: "projects create", entity: "projects", operation: "create"}, {name: "projects create", entity: "projects", operation: "create"},
{name: "projects update", entity: "projects", operation: "update"},
{name: "projects delete", entity: "projects", operation: "delete"},
{name: "plans create", entity: "plans", operation: "create"},
{name: "plans update", entity: "plans", operation: "update"},
{name: "plans delete", entity: "plans", operation: "delete"},
{name: "learnings create", entity: "learnings", operation: "create"},
{name: "learnings update", entity: "learnings", operation: "update"},
{name: "learnings delete", entity: "learnings", operation: "delete"},
{name: "thoughts create", entity: "thoughts", operation: "create"},
{name: "thoughts update", entity: "thoughts", operation: "update"}, {name: "thoughts update", entity: "thoughts", operation: "update"},
{name: "thoughts delete", entity: "thoughts", operation: "delete"}, {name: "thoughts delete", entity: "thoughts", operation: "delete"},
{name: "agent_skills create", entity: "agent_skills", operation: "create"},
{name: "agent_skills update", entity: "agent_skills", operation: "update"},
{name: "agent_skills delete", entity: "agent_skills", operation: "delete"}, {name: "agent_skills delete", entity: "agent_skills", operation: "delete"},
{name: "agent_guardrails create", entity: "agent_guardrails", operation: "create"},
{name: "agent_guardrails update", entity: "agent_guardrails", operation: "update"},
{name: "agent_guardrails delete", entity: "agent_guardrails", operation: "delete"}, {name: "agent_guardrails delete", entity: "agent_guardrails", operation: "delete"},
{name: "agent_personas create", entity: "agent_personas", operation: "create"},
{name: "agent_personas update", entity: "agent_personas", operation: "update"},
{name: "agent_personas delete", entity: "agent_personas", operation: "delete"},
{name: "agent_parts create", entity: "agent_parts", operation: "create"},
{name: "agent_parts update", entity: "agent_parts", operation: "update"},
{name: "agent_parts delete", entity: "agent_parts", operation: "delete"},
{name: "agent_traits create", entity: "agent_traits", operation: "create"},
{name: "agent_traits update", entity: "agent_traits", operation: "update"},
{name: "agent_traits delete", entity: "agent_traits", operation: "delete"},
{name: "stored_files update", entity: "stored_files", operation: "update"},
{name: "stored_files delete", entity: "stored_files", operation: "delete"},
} }
for _, tc := range cases { for _, tc := range cases {
@@ -100,32 +124,18 @@ func TestResolveSpecGuardBlocksUnsupportedMutations(t *testing.T) {
wantMessageIn string wantMessageIn string
}{ }{
{ {
name: "create not allowed on thoughts", name: "mutations blocked for non-allowlisted operation",
entity: "thoughts", entity: "stored_files",
operation: "create", operation: "create",
wantCode: http.StatusForbidden, wantCode: http.StatusForbidden,
wantMessageIn: `operation "create" is not allowed for public.thoughts`, wantMessageIn: `operation "create" is not allowed for public.stored_files`,
},
{
name: "delete not allowed on projects",
entity: "projects",
operation: "delete",
wantCode: http.StatusForbidden,
wantMessageIn: `operation "delete" is not allowed for public.projects`,
}, },
{ {
name: "mutations blocked for non-allowlisted entity", name: "mutations blocked for non-allowlisted entity",
entity: "stored_files", entity: "maintenance_logs",
operation: "delete", operation: "delete",
wantCode: http.StatusForbidden, wantCode: http.StatusForbidden,
wantMessageIn: `operation "delete" is not allowed for public.stored_files`, wantMessageIn: `operation "delete" is not allowed for public.maintenance_logs`,
},
{
name: "mutations blocked for learnings",
entity: "learnings",
operation: "delete",
wantCode: http.StatusForbidden,
wantMessageIn: `operation "delete" is not allowed for public.learnings`,
}, },
{ {
name: "unknown operation is rejected", name: "unknown operation is rejected",
@@ -170,3 +180,27 @@ func TestResolveSpecModelsIncludeLearnings(t *testing.T) {
} }
t.Fatal("resolveSpecModels() missing public.learnings") t.Fatal("resolveSpecModels() missing public.learnings")
} }
func TestResolveSpecModelsIncludePersonaEntities(t *testing.T) {
models := resolveSpecModels()
required := map[string]bool{
"agent_personas": false,
"agent_parts": false,
"agent_traits": false,
}
for _, model := range models {
if model.schema != "public" {
continue
}
if _, ok := required[model.entity]; ok {
required[model.entity] = true
}
}
for entity, found := range required {
if !found {
t.Fatalf("resolveSpecModels() missing public.%s", entity)
}
}
}
@@ -0,0 +1,39 @@
// Code generated by relspec templ. DO NOT EDIT.
package app
import "git.warky.dev/wdevs/amcs/internal/generatedmodels"
func resolveSpecModels() []resolveSpecModel {
return []resolveSpecModel{
{schema: "public", entity: "agent_guardrails", model: generatedmodels.ModelPublicAgentGuardrails{}},
{schema: "public", entity: "agent_parts", model: generatedmodels.ModelPublicAgentParts{}},
{schema: "public", entity: "agent_persona_guardrails", model: generatedmodels.ModelPublicAgentPersonaGuardrails{}},
{schema: "public", entity: "agent_persona_parts", model: generatedmodels.ModelPublicAgentPersonaParts{}},
{schema: "public", entity: "agent_persona_skills", model: generatedmodels.ModelPublicAgentPersonaSkills{}},
{schema: "public", entity: "agent_persona_traits", model: generatedmodels.ModelPublicAgentPersonaTraits{}},
{schema: "public", entity: "agent_personas", model: generatedmodels.ModelPublicAgentPersonas{}},
{schema: "public", entity: "agent_skills", model: generatedmodels.ModelPublicAgentSkills{}},
{schema: "public", entity: "agent_traits", model: generatedmodels.ModelPublicAgentTraits{}},
{schema: "public", entity: "arc_stage_parts", model: generatedmodels.ModelPublicArcStageParts{}},
{schema: "public", entity: "arc_stages", model: generatedmodels.ModelPublicArcStages{}},
{schema: "public", entity: "character_arcs", model: generatedmodels.ModelPublicCharacterArcs{}},
{schema: "public", entity: "chat_histories", model: generatedmodels.ModelPublicChatHistories{}},
{schema: "public", entity: "embeddings", model: generatedmodels.ModelPublicEmbeddings{}},
{schema: "public", entity: "learnings", model: generatedmodels.ModelPublicLearnings{}},
{schema: "public", entity: "oauth_clients", model: generatedmodels.ModelPublicOauthClients{}},
{schema: "public", entity: "persona_arc", model: generatedmodels.ModelPublicPersonaArc{}},
{schema: "public", entity: "plan_dependencies", model: generatedmodels.ModelPublicPlanDependencies{}},
{schema: "public", entity: "plan_guardrails", model: generatedmodels.ModelPublicPlanGuardrails{}},
{schema: "public", entity: "plan_related_plans", model: generatedmodels.ModelPublicPlanRelatedPlans{}},
{schema: "public", entity: "plan_skills", model: generatedmodels.ModelPublicPlanSkills{}},
{schema: "public", entity: "plans", model: generatedmodels.ModelPublicPlans{}},
{schema: "public", entity: "project_guardrails", model: generatedmodels.ModelPublicProjectGuardrails{}},
{schema: "public", entity: "project_personas", model: generatedmodels.ModelPublicProjectPersonas{}},
{schema: "public", entity: "project_skills", model: generatedmodels.ModelPublicProjectSkills{}},
{schema: "public", entity: "projects", model: generatedmodels.ModelPublicProjects{}},
{schema: "public", entity: "stored_files", model: generatedmodels.ModelPublicStoredFiles{}},
{schema: "public", entity: "thought_links", model: generatedmodels.ModelPublicThoughtLinks{}},
{schema: "public", entity: "thoughts", model: generatedmodels.ModelPublicThoughts{}},
{schema: "public", entity: "tool_annotations", model: generatedmodels.ModelPublicToolAnnotations{}},
}
}
+56 -2
View File
@@ -29,8 +29,21 @@ type statusAPIResponse struct {
OAuthEnabled bool `json:"oauth_enabled"` OAuthEnabled bool `json:"oauth_enabled"`
} }
type publicClientStatus struct {
KeyID string `json:"key_id"`
RequestCount int `json:"request_count"`
LastAccessedAt time.Time `json:"last_accessed_at"`
}
type publicStatusResponse struct {
ConnectedCount int `json:"connected_count"`
ConnectedWindow string `json:"connected_window"`
Entries []publicClientStatus `json:"entries"`
}
func statusSnapshot(info buildinfo.Info, tracker *auth.AccessTracker, oauthEnabled bool, now time.Time) statusAPIResponse { func statusSnapshot(info buildinfo.Info, tracker *auth.AccessTracker, oauthEnabled bool, now time.Time) statusAPIResponse {
entries := tracker.Snapshot() entries := tracker.Snapshot()
metrics := tracker.Metrics(20)
return statusAPIResponse{ return statusAPIResponse{
Title: "Avelon Memory Crystal Server (AMCS)", Title: "Avelon Memory Crystal Server (AMCS)",
Description: "AMCS is a memory server that captures, links, and retrieves structured project thoughts for AI assistants using semantic search, summaries, and MCP tools.", Description: "AMCS is a memory server that captures, links, and retrieves structured project thoughts for AI assistants using semantic search, summaries, and MCP tools.",
@@ -40,8 +53,8 @@ func statusSnapshot(info buildinfo.Info, tracker *auth.AccessTracker, oauthEnabl
ConnectedCount: tracker.ConnectedCount(now, connectedWindow), ConnectedCount: tracker.ConnectedCount(now, connectedWindow),
TotalKnown: len(entries), TotalKnown: len(entries),
ConnectedWindow: "last 10 minutes", ConnectedWindow: "last 10 minutes",
Entries: entries, Entries: nil,
Metrics: tracker.Metrics(20), Metrics: metrics,
OAuthEnabled: oauthEnabled, OAuthEnabled: oauthEnabled,
} }
} }
@@ -75,6 +88,47 @@ func statusAPIHandler(info buildinfo.Info, tracker *auth.AccessTracker, oauthEna
} }
} }
func publicStatusHandler(tracker *auth.AccessTracker) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/status" {
http.NotFound(w, r)
return
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.Header().Set("Allow", "GET, HEAD")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
now := time.Now()
cutoff := now.UTC().Add(-connectedWindow)
snapshot := tracker.Snapshot()
entries := make([]publicClientStatus, 0, len(snapshot))
for _, item := range snapshot {
if item.LastAccessedAt.Before(cutoff) {
continue
}
entries = append(entries, publicClientStatus{
KeyID: item.KeyID,
RequestCount: item.RequestCount,
LastAccessedAt: item.LastAccessedAt,
})
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
_ = json.NewEncoder(w).Encode(publicStatusResponse{
ConnectedCount: len(entries),
ConnectedWindow: "last 10 minutes",
Entries: entries,
})
}
}
func homeHandler(_ buildinfo.Info, _ *auth.AccessTracker, _ bool) http.HandlerFunc { func homeHandler(_ buildinfo.Info, _ *auth.AccessTracker, _ bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead { if r.Method != http.MethodGet && r.Method != http.MethodHead {
+57 -5
View File
@@ -43,11 +43,8 @@ func TestStatusSnapshotShowsTrackedAccess(t *testing.T) {
if snapshot.ConnectedCount != 1 { if snapshot.ConnectedCount != 1 {
t.Fatalf("ConnectedCount = %d, want 1", snapshot.ConnectedCount) t.Fatalf("ConnectedCount = %d, want 1", snapshot.ConnectedCount)
} }
if len(snapshot.Entries) != 1 { if len(snapshot.Entries) != 0 {
t.Fatalf("len(Entries) = %d, want 1", len(snapshot.Entries)) t.Fatalf("len(Entries) = %d, want 0 for counts-only status", len(snapshot.Entries))
}
if snapshot.Entries[0].KeyID != "client-a" || snapshot.Entries[0].LastPath != "/files" {
t.Fatalf("entry = %+v, want keyID client-a and path /files", snapshot.Entries[0])
} }
if snapshot.Metrics.TotalRequests != 1 { if snapshot.Metrics.TotalRequests != 1 {
t.Fatalf("Metrics.TotalRequests = %d, want 1", snapshot.Metrics.TotalRequests) t.Fatalf("Metrics.TotalRequests = %d, want 1", snapshot.Metrics.TotalRequests)
@@ -61,6 +58,15 @@ func TestStatusSnapshotShowsTrackedAccess(t *testing.T) {
if snapshot.Metrics.UniqueTools != 1 { if snapshot.Metrics.UniqueTools != 1 {
t.Fatalf("Metrics.UniqueTools = %d, want 1", snapshot.Metrics.UniqueTools) t.Fatalf("Metrics.UniqueTools = %d, want 1", snapshot.Metrics.UniqueTools)
} }
if len(snapshot.Metrics.TopIPs) != 1 || len(snapshot.Metrics.TopAgents) != 1 || len(snapshot.Metrics.TopTools) != 1 {
t.Fatalf("Top breakdowns not populated: %+v", snapshot.Metrics)
}
if len(snapshot.Metrics.RecentLog) != 1 {
t.Fatalf("RecentLog len = %d, want 1", len(snapshot.Metrics.RecentLog))
}
if snapshot.Metrics.RecentLog[0].Tool != "list_projects" {
t.Fatalf("RecentLog[0].Tool = %q, want %q", snapshot.Metrics.RecentLog[0].Tool, "list_projects")
}
} }
func TestStatusAPIHandlerReturnsJSON(t *testing.T) { func TestStatusAPIHandlerReturnsJSON(t *testing.T) {
@@ -86,6 +92,52 @@ func TestStatusAPIHandlerReturnsJSON(t *testing.T) {
} }
} }
func TestStatusAPIHandlerRejectsStatusPath(t *testing.T) {
handler := statusAPIHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), true)
req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusNotFound)
}
}
func TestPublicStatusHandlerReturnsConnectedClientsOnly(t *testing.T) {
tracker := auth.NewAccessTracker()
now := time.Now().UTC()
tracker.Record("recent-client", "/mcp", "127.0.0.1:1234", "tester", "list_projects", now.Add(-2*time.Minute))
tracker.Record("stale-client", "/mcp", "127.0.0.1:9999", "tester", "list_projects", now.Add(-30*time.Minute))
handler := publicStatusHandler(tracker)
req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload publicStatusResponse
if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
t.Fatalf("json.Unmarshal() error = %v", err)
}
if payload.ConnectedCount != 1 {
t.Fatalf("ConnectedCount = %d, want 1", payload.ConnectedCount)
}
if len(payload.Entries) != 1 {
t.Fatalf("len(Entries) = %d, want 1", len(payload.Entries))
}
if payload.Entries[0].KeyID != "recent-client" {
t.Fatalf("Entries[0].KeyID = %q, want %q", payload.Entries[0].KeyID, "recent-client")
}
if payload.Entries[0].LastAccessedAt.Before(now.Add(-11 * time.Minute)) {
t.Fatalf("Entries[0].LastAccessedAt = %v, expected recent timestamp", payload.Entries[0].LastAccessedAt)
}
}
func TestHomeHandlerAllowsHead(t *testing.T) { func TestHomeHandlerAllowsHead(t *testing.T) {
handler := homeHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), false) handler := homeHandler(buildinfo.Info{Version: "v1"}, auth.NewAccessTracker(), false)
req := httptest.NewRequest(http.MethodHead, "/", nil) req := httptest.NewRequest(http.MethodHead, "/", nil)
+31
View File
@@ -17,12 +17,24 @@ type AccessSnapshot struct {
LastAccessedAt time.Time `json:"last_accessed_at"` LastAccessedAt time.Time `json:"last_accessed_at"`
} }
const maxRecentLog = 100
type AccessLogEntry struct {
Timestamp time.Time `json:"timestamp"`
KeyID string `json:"key_id"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
Tool string `json:"tool"`
Path string `json:"path"`
}
type AccessTracker struct { type AccessTracker struct {
mu sync.RWMutex mu sync.RWMutex
entries map[string]AccessSnapshot entries map[string]AccessSnapshot
ipCounts map[string]int ipCounts map[string]int
agentCounts map[string]int agentCounts map[string]int
toolCounts map[string]int toolCounts map[string]int
recentLog []AccessLogEntry
totalRequests int totalRequests int
} }
@@ -32,6 +44,7 @@ func NewAccessTracker() *AccessTracker {
ipCounts: make(map[string]int), ipCounts: make(map[string]int),
agentCounts: make(map[string]int), agentCounts: make(map[string]int),
toolCounts: make(map[string]int), toolCounts: make(map[string]int),
recentLog: make([]AccessLogEntry, 0, maxRecentLog),
} }
} }
@@ -64,6 +77,19 @@ func (t *AccessTracker) Record(keyID, path, remoteAddr, userAgent, toolName stri
if tool := strings.TrimSpace(toolName); tool != "" { if tool := strings.TrimSpace(toolName); tool != "" {
t.toolCounts[tool]++ t.toolCounts[tool]++
} }
logEntry := AccessLogEntry{
Timestamp: now.UTC(),
KeyID: keyID,
IP: normalizedRemoteAddr,
UserAgent: userAgent,
Tool: strings.TrimSpace(toolName),
Path: path,
}
t.recentLog = append([]AccessLogEntry{logEntry}, t.recentLog...)
if len(t.recentLog) > maxRecentLog {
t.recentLog = t.recentLog[:maxRecentLog]
}
} }
func normalizeRemoteAddr(value string) string { func normalizeRemoteAddr(value string) string {
@@ -130,6 +156,7 @@ type AccessMetrics struct {
TopIPs []RequestAggregate `json:"top_ips"` TopIPs []RequestAggregate `json:"top_ips"`
TopAgents []RequestAggregate `json:"top_agents"` TopAgents []RequestAggregate `json:"top_agents"`
TopTools []RequestAggregate `json:"top_tools"` TopTools []RequestAggregate `json:"top_tools"`
RecentLog []AccessLogEntry `json:"recent_log"`
} }
func (t *AccessTracker) Metrics(topN int) AccessMetrics { func (t *AccessTracker) Metrics(topN int) AccessMetrics {
@@ -143,6 +170,9 @@ func (t *AccessTracker) Metrics(topN int) AccessMetrics {
t.mu.RLock() t.mu.RLock()
defer t.mu.RUnlock() defer t.mu.RUnlock()
log := make([]AccessLogEntry, len(t.recentLog))
copy(log, t.recentLog)
return AccessMetrics{ return AccessMetrics{
TotalRequests: t.totalRequests, TotalRequests: t.totalRequests,
UniquePrincipals: len(t.entries), UniquePrincipals: len(t.entries),
@@ -152,6 +182,7 @@ func (t *AccessTracker) Metrics(topN int) AccessMetrics {
TopIPs: topAggregates(t.ipCounts, topN), TopIPs: topAggregates(t.ipCounts, topN),
TopAgents: topAggregates(t.agentCounts, topN), TopAgents: topAggregates(t.agentCounts, topN),
TopTools: topAggregates(t.toolCounts, topN), TopTools: topAggregates(t.toolCounts, topN),
RecentLog: log,
} }
} }
+6
View File
@@ -26,6 +26,12 @@ func (c *DynamicClient) HasRedirectURI(uri string) bool {
return false return false
} }
// ClientStore is the interface implemented by both DynamicClientStore and PostgresClientStore.
type ClientStore interface {
Register(name string, redirectURIs []string) (DynamicClient, error)
Lookup(clientID string) (DynamicClient, bool)
}
// DynamicClientStore holds dynamically registered OAuth clients in memory. // DynamicClientStore holds dynamically registered OAuth clients in memory.
type DynamicClientStore struct { type DynamicClientStore struct {
mu sync.RWMutex mu sync.RWMutex
+18
View File
@@ -17,6 +17,22 @@ type contextKey string
const keyIDContextKey contextKey = "auth.key_id" const keyIDContextKey contextKey = "auth.key_id"
// wwwAuthenticate returns the value for a WWW-Authenticate header.
// It advertises Bearer and, when a public URL is known, the OAuth metadata URL per RFC 9728.
func wwwAuthenticate(r *http.Request, publicURL string) string {
base := publicURL
if base == "" {
scheme := "https"
if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
scheme = strings.ToLower(proto)
} else if r.TLS == nil {
scheme = "http"
}
base = scheme + "://" + r.Host
}
return `Bearer resource_metadata="` + base + `/.well-known/oauth-authorization-server"`
}
func Middleware(cfg config.AuthConfig, keyring *Keyring, oauthRegistry *OAuthRegistry, tokenStore *TokenStore, tracker *AccessTracker, log *slog.Logger) func(http.Handler) http.Handler { func Middleware(cfg config.AuthConfig, keyring *Keyring, oauthRegistry *OAuthRegistry, tokenStore *TokenStore, tracker *AccessTracker, log *slog.Logger) func(http.Handler) http.Handler {
headerName := cfg.HeaderName headerName := cfg.HeaderName
if headerName == "" { if headerName == "" {
@@ -69,6 +85,7 @@ func Middleware(cfg config.AuthConfig, keyring *Keyring, oauthRegistry *OAuthReg
} }
} }
log.Warn("bearer token rejected", slog.String("remote_addr", remoteAddr)) log.Warn("bearer token rejected", slog.String("remote_addr", remoteAddr))
w.Header().Set("WWW-Authenticate", wwwAuthenticate(r, "")+`, error="invalid_token"`)
http.Error(w, "invalid token or API key", http.StatusUnauthorized) http.Error(w, "invalid token or API key", http.StatusUnauthorized)
return return
} }
@@ -105,6 +122,7 @@ func Middleware(cfg config.AuthConfig, keyring *Keyring, oauthRegistry *OAuthReg
} }
} }
w.Header().Set("WWW-Authenticate", wwwAuthenticate(r, ""))
http.Error(w, "authentication required", http.StatusUnauthorized) http.Error(w, "authentication required", http.StatusUnauthorized)
}) })
} }
+50
View File
@@ -0,0 +1,50 @@
package auth
import (
"context"
"crypto/rand"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
)
// PostgresClientStore persists dynamically registered OAuth clients (RFC 7591) in PostgreSQL.
type PostgresClientStore struct {
pool *pgxpool.Pool
}
func NewPostgresClientStore(pool *pgxpool.Pool) *PostgresClientStore {
return &PostgresClientStore{pool: pool}
}
func (s *PostgresClientStore) Register(name string, redirectURIs []string) (DynamicClient, error) {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return DynamicClient{}, err
}
clientID := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
var client DynamicClient
row := s.pool.QueryRow(context.Background(), `
insert into oauth_clients (client_id, client_name, redirect_uris)
values ($1, $2, $3)
returning client_id, client_name, redirect_uris, created_at
`, clientID, name, redirectURIs)
if err := row.Scan(&client.ClientID, &client.ClientName, &client.RedirectURIs, &client.CreatedAt); err != nil {
return DynamicClient{}, fmt.Errorf("register oauth client: %w", err)
}
return client, nil
}
func (s *PostgresClientStore) Lookup(clientID string) (DynamicClient, bool) {
var client DynamicClient
row := s.pool.QueryRow(context.Background(), `
select client_id, client_name, redirect_uris, created_at
from oauth_clients
where client_id = $1
`, clientID)
if err := row.Scan(&client.ClientID, &client.ClientName, &client.RedirectURIs, &client.CreatedAt); err != nil {
return DynamicClient{}, false
}
return client, true
}
@@ -1,70 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicActivities struct {
bun.BaseModel `bun:"table:public.activities,alias:activities"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
ActivityType resolvespec_common.SqlString `bun:"activity_type,type:text,nullzero," json:"activity_type"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
DayOfWeek resolvespec_common.SqlString `bun:"day_of_week,type:text,nullzero," json:"day_of_week"`
EndDate resolvespec_common.SqlDate `bun:"end_date,type:date,nullzero," json:"end_date"`
EndTime resolvespec_common.SqlTime `bun:"end_time,type:time,nullzero," json:"end_time"`
FamilyMemberID resolvespec_common.SqlUUID `bun:"family_member_id,type:uuid,nullzero," json:"family_member_id"`
Location resolvespec_common.SqlString `bun:"location,type:text,nullzero," json:"location"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
StartDate resolvespec_common.SqlDate `bun:"start_date,type:date,nullzero," json:"start_date"`
StartTime resolvespec_common.SqlTime `bun:"start_time,type:time,nullzero," json:"start_time"`
Title resolvespec_common.SqlString `bun:"title,type:text,notnull," json:"title"`
RelFamilyMemberID *ModelPublicFamilyMembers `bun:"rel:has-one,join:family_member_id=id" json:"relfamilymemberid,omitempty"` // Has one ModelPublicFamilyMembers
}
// TableName returns the table name for ModelPublicActivities
func (m ModelPublicActivities) TableName() string {
return "public.activities"
}
// TableNameOnly returns the table name without schema for ModelPublicActivities
func (m ModelPublicActivities) TableNameOnly() string {
return "activities"
}
// SchemaName returns the schema name for ModelPublicActivities
func (m ModelPublicActivities) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicActivities) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicActivities) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicActivities) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicActivities) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicActivities) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicActivities) GetPrefix() string {
return "ACT"
}
@@ -8,16 +8,19 @@ import (
) )
type ModelPublicAgentGuardrails struct { type ModelPublicAgentGuardrails struct {
bun.BaseModel `bun:"table:public.agent_guardrails,alias:agent_guardrails"` bun.BaseModel `bun:"table:public.agent_guardrails,alias:agent_guardrails"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"` ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"` Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
Severity resolvespec_common.SqlString `bun:"severity,type:text,default:'medium',notnull," json:"severity"` Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Tags resolvespec_common.SqlString `bun:"tags,type:text,nullzero," json:"tags"` Severity resolvespec_common.SqlString `bun:"severity,type:text,default:'medium',notnull," json:"severity"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
RelGuardrailIDPublicProjectGuardrails []*ModelPublicProjectGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicprojectguardrails,omitempty"` // Has many ModelPublicProjectGuardrails UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelGuardrailIDPublicAgentPersonaGuardrails []*ModelPublicAgentPersonaGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicagentpersonaguardrails,omitempty"` // Has many ModelPublicAgentPersonaGuardrails
RelGuardrailIDPublicPlanGuardrails []*ModelPublicPlanGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicplanguardrails,omitempty"` // Has many ModelPublicPlanGuardrails
RelGuardrailIDPublicProjectGuardrails []*ModelPublicProjectGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicprojectguardrails,omitempty"` // Has many ModelPublicProjectGuardrails
} }
// TableName returns the table name for ModelPublicAgentGuardrails // TableName returns the table name for ModelPublicAgentGuardrails
@@ -42,7 +45,7 @@ func (m ModelPublicAgentGuardrails) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicAgentGuardrails) GetIDStr() string { func (m ModelPublicAgentGuardrails) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -0,0 +1,69 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicAgentParts struct {
bun.BaseModel `bun:"table:public.agent_parts,alias:agent_parts"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
Content resolvespec_common.SqlString `bun:"content,type:text,default:'',notnull," json:"content"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
PartType resolvespec_common.SqlString `bun:"part_type,type:text,notnull," json:"part_type"`
Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"`
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelPartIDPublicAgentPersonaParts []*ModelPublicAgentPersonaParts `bun:"rel:has-many,join:id=part_id" json:"relpartidpublicagentpersonaparts,omitempty"` // Has many ModelPublicAgentPersonaParts
RelPartIDPublicArcStageParts []*ModelPublicArcStageParts `bun:"rel:has-many,join:id=part_id" json:"relpartidpublicarcstageparts,omitempty"` // Has many ModelPublicArcStageParts
}
// TableName returns the table name for ModelPublicAgentParts
func (m ModelPublicAgentParts) TableName() string {
return "public.agent_parts"
}
// TableNameOnly returns the table name without schema for ModelPublicAgentParts
func (m ModelPublicAgentParts) TableNameOnly() string {
return "agent_parts"
}
// SchemaName returns the schema name for ModelPublicAgentParts
func (m ModelPublicAgentParts) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicAgentParts) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicAgentParts) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicAgentParts) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicAgentParts) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicAgentParts) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicAgentParts) GetPrefix() string {
return "APG"
}
@@ -0,0 +1,62 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicAgentPersonaGuardrails struct {
bun.BaseModel `bun:"table:public.agent_persona_guardrails,alias:agent_persona_guardrails"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
GuardrailID int64 `bun:"guardrail_id,type:bigint,notnull," json:"guardrail_id"`
PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"`
RelGuardrailID *ModelPublicAgentGuardrails `bun:"rel:has-one,join:guardrail_id=id" json:"relguardrailid,omitempty"` // Has one ModelPublicAgentGuardrails
RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas
}
// TableName returns the table name for ModelPublicAgentPersonaGuardrails
func (m ModelPublicAgentPersonaGuardrails) TableName() string {
return "public.agent_persona_guardrails"
}
// TableNameOnly returns the table name without schema for ModelPublicAgentPersonaGuardrails
func (m ModelPublicAgentPersonaGuardrails) TableNameOnly() string {
return "agent_persona_guardrails"
}
// SchemaName returns the schema name for ModelPublicAgentPersonaGuardrails
func (m ModelPublicAgentPersonaGuardrails) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicAgentPersonaGuardrails) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicAgentPersonaGuardrails) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicAgentPersonaGuardrails) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicAgentPersonaGuardrails) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicAgentPersonaGuardrails) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicAgentPersonaGuardrails) GetPrefix() string {
return "APG"
}
@@ -0,0 +1,64 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicAgentPersonaParts struct {
bun.BaseModel `bun:"table:public.agent_persona_parts,alias:agent_persona_parts"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
PartID int64 `bun:"part_id,type:bigint,notnull," json:"part_id"`
PartOrder int32 `bun:"part_order,type:int,default:0,notnull," json:"part_order"`
PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"`
Priority int32 `bun:"priority,type:int,default:0,notnull," json:"priority"`
RelPartID *ModelPublicAgentParts `bun:"rel:has-one,join:part_id=id" json:"relpartid,omitempty"` // Has one ModelPublicAgentParts
RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas
}
// TableName returns the table name for ModelPublicAgentPersonaParts
func (m ModelPublicAgentPersonaParts) TableName() string {
return "public.agent_persona_parts"
}
// TableNameOnly returns the table name without schema for ModelPublicAgentPersonaParts
func (m ModelPublicAgentPersonaParts) TableNameOnly() string {
return "agent_persona_parts"
}
// SchemaName returns the schema name for ModelPublicAgentPersonaParts
func (m ModelPublicAgentPersonaParts) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicAgentPersonaParts) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicAgentPersonaParts) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicAgentPersonaParts) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicAgentPersonaParts) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicAgentPersonaParts) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicAgentPersonaParts) GetPrefix() string {
return "APP"
}
@@ -0,0 +1,63 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicAgentPersonaSkills struct {
bun.BaseModel `bun:"table:public.agent_persona_skills,alias:agent_persona_skills"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
Override bool `bun:"override,type:boolean,default:false,notnull," json:"override"`
PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"`
SkillID int64 `bun:"skill_id,type:bigint,notnull," json:"skill_id"`
RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas
RelSkillID *ModelPublicAgentSkills `bun:"rel:has-one,join:skill_id=id" json:"relskillid,omitempty"` // Has one ModelPublicAgentSkills
}
// TableName returns the table name for ModelPublicAgentPersonaSkills
func (m ModelPublicAgentPersonaSkills) TableName() string {
return "public.agent_persona_skills"
}
// TableNameOnly returns the table name without schema for ModelPublicAgentPersonaSkills
func (m ModelPublicAgentPersonaSkills) TableNameOnly() string {
return "agent_persona_skills"
}
// SchemaName returns the schema name for ModelPublicAgentPersonaSkills
func (m ModelPublicAgentPersonaSkills) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicAgentPersonaSkills) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicAgentPersonaSkills) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicAgentPersonaSkills) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicAgentPersonaSkills) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicAgentPersonaSkills) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicAgentPersonaSkills) GetPrefix() string {
return "APS"
}
@@ -0,0 +1,62 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicAgentPersonaTraits struct {
bun.BaseModel `bun:"table:public.agent_persona_traits,alias:agent_persona_traits"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"`
TraitID int64 `bun:"trait_id,type:bigint,notnull," json:"trait_id"`
RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas
RelTraitID *ModelPublicAgentTraits `bun:"rel:has-one,join:trait_id=id" json:"reltraitid,omitempty"` // Has one ModelPublicAgentTraits
}
// TableName returns the table name for ModelPublicAgentPersonaTraits
func (m ModelPublicAgentPersonaTraits) TableName() string {
return "public.agent_persona_traits"
}
// TableNameOnly returns the table name without schema for ModelPublicAgentPersonaTraits
func (m ModelPublicAgentPersonaTraits) TableNameOnly() string {
return "agent_persona_traits"
}
// SchemaName returns the schema name for ModelPublicAgentPersonaTraits
func (m ModelPublicAgentPersonaTraits) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicAgentPersonaTraits) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicAgentPersonaTraits) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicAgentPersonaTraits) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicAgentPersonaTraits) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicAgentPersonaTraits) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicAgentPersonaTraits) GetPrefix() string {
return "APT"
}
@@ -0,0 +1,75 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicAgentPersonas struct {
bun.BaseModel `bun:"table:public.agent_personas,alias:agent_personas"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CompiledAt resolvespec_common.SqlTimeStamp `bun:"compiled_at,type:timestamptz,nullzero," json:"compiled_at"`
CompiledDetail resolvespec_common.SqlString `bun:"compiled_detail,type:text,default:'',notnull," json:"compiled_detail"`
CompiledSummary resolvespec_common.SqlString `bun:"compiled_summary,type:text,default:'',notnull," json:"compiled_summary"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
Detail resolvespec_common.SqlString `bun:"detail,type:text,default:'',notnull," json:"detail"`
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"`
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelPersonaIDPublicAgentPersonaParts []*ModelPublicAgentPersonaParts `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaparts,omitempty"` // Has many ModelPublicAgentPersonaParts
RelPersonaIDPublicAgentPersonaSkills []*ModelPublicAgentPersonaSkills `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaskills,omitempty"` // Has many ModelPublicAgentPersonaSkills
RelPersonaIDPublicProjectPersonas []*ModelPublicProjectPersonas `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicprojectpersonas,omitempty"` // Has many ModelPublicProjectPersonas
RelPersonaIDPublicAgentPersonaGuardrails []*ModelPublicAgentPersonaGuardrails `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaguardrails,omitempty"` // Has many ModelPublicAgentPersonaGuardrails
RelPersonaIDPublicAgentPersonaTraits []*ModelPublicAgentPersonaTraits `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonatraits,omitempty"` // Has many ModelPublicAgentPersonaTraits
RelPersonaIDPublicPersonaArcs []*ModelPublicPersonaArc `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicpersonaarcs,omitempty"` // Has many ModelPublicPersonaArc
}
// TableName returns the table name for ModelPublicAgentPersonas
func (m ModelPublicAgentPersonas) TableName() string {
return "public.agent_personas"
}
// TableNameOnly returns the table name without schema for ModelPublicAgentPersonas
func (m ModelPublicAgentPersonas) TableNameOnly() string {
return "agent_personas"
}
// SchemaName returns the schema name for ModelPublicAgentPersonas
func (m ModelPublicAgentPersonas) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicAgentPersonas) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicAgentPersonas) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicAgentPersonas) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicAgentPersonas) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicAgentPersonas) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicAgentPersonas) GetPrefix() string {
return "APG"
}
@@ -8,16 +8,23 @@ import (
) )
type ModelPublicAgentSkills struct { type ModelPublicAgentSkills struct {
bun.BaseModel `bun:"table:public.agent_skills,alias:agent_skills"` bun.BaseModel `bun:"table:public.agent_skills,alias:agent_skills"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"` ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"` Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` DomainTags resolvespec_common.SqlStringArray `bun:"domain_tags,type:text[],default:'{}',notnull," json:"domain_tags"`
Tags resolvespec_common.SqlString `bun:"tags,type:text,nullzero," json:"tags"` FrameworkTags resolvespec_common.SqlStringArray `bun:"framework_tags,type:text[],default:'{}',notnull," json:"framework_tags"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
RelRelatedSkillIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:id=related_skill_id" json:"relrelatedskillidpubliclearnings,omitempty"` // Has many ModelPublicLearnings LanguageTags resolvespec_common.SqlStringArray `bun:"language_tags,type:text[],default:'{}',notnull," json:"language_tags"`
RelSkillIDPublicProjectSkills []*ModelPublicProjectSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicprojectskills,omitempty"` // Has many ModelPublicProjectSkills LibraryTags resolvespec_common.SqlStringArray `bun:"library_tags,type:text[],default:'{}',notnull," json:"library_tags"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelSkillIDPublicAgentPersonaSkills []*ModelPublicAgentPersonaSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicagentpersonaskills,omitempty"` // Has many ModelPublicAgentPersonaSkills
RelRelatedSkillIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:id=related_skill_id" json:"relrelatedskillidpubliclearnings,omitempty"` // Has many ModelPublicLearnings
RelSkillIDPublicPlanSkills []*ModelPublicPlanSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicplanskills,omitempty"` // Has many ModelPublicPlanSkills
RelSkillIDPublicProjectSkills []*ModelPublicProjectSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicprojectskills,omitempty"` // Has many ModelPublicProjectSkills
} }
// TableName returns the table name for ModelPublicAgentSkills // TableName returns the table name for ModelPublicAgentSkills
@@ -42,7 +49,7 @@ func (m ModelPublicAgentSkills) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicAgentSkills) GetIDStr() string { func (m ModelPublicAgentSkills) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -0,0 +1,67 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicAgentTraits struct {
bun.BaseModel `bun:"table:public.agent_traits,alias:agent_traits"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
Instruction resolvespec_common.SqlString `bun:"instruction,type:text,default:'',notnull," json:"instruction"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
TraitType resolvespec_common.SqlString `bun:"trait_type,type:text,notnull," json:"trait_type"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelTraitIDPublicAgentPersonaTraits []*ModelPublicAgentPersonaTraits `bun:"rel:has-many,join:id=trait_id" json:"reltraitidpublicagentpersonatraits,omitempty"` // Has many ModelPublicAgentPersonaTraits
}
// TableName returns the table name for ModelPublicAgentTraits
func (m ModelPublicAgentTraits) TableName() string {
return "public.agent_traits"
}
// TableNameOnly returns the table name without schema for ModelPublicAgentTraits
func (m ModelPublicAgentTraits) TableNameOnly() string {
return "agent_traits"
}
// SchemaName returns the schema name for ModelPublicAgentTraits
func (m ModelPublicAgentTraits) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicAgentTraits) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicAgentTraits) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicAgentTraits) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicAgentTraits) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicAgentTraits) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicAgentTraits) GetPrefix() string {
return "ATG"
}
@@ -0,0 +1,62 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicArcStageParts struct {
bun.BaseModel `bun:"table:public.arc_stage_parts,alias:arc_stage_parts"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
PartID int64 `bun:"part_id,type:bigint,notnull," json:"part_id"`
StageID int64 `bun:"stage_id,type:bigint,notnull," json:"stage_id"`
RelPartID *ModelPublicAgentParts `bun:"rel:has-one,join:part_id=id" json:"relpartid,omitempty"` // Has one ModelPublicAgentParts
RelStageID *ModelPublicArcStages `bun:"rel:has-one,join:stage_id=id" json:"relstageid,omitempty"` // Has one ModelPublicArcStages
}
// TableName returns the table name for ModelPublicArcStageParts
func (m ModelPublicArcStageParts) TableName() string {
return "public.arc_stage_parts"
}
// TableNameOnly returns the table name without schema for ModelPublicArcStageParts
func (m ModelPublicArcStageParts) TableNameOnly() string {
return "arc_stage_parts"
}
// SchemaName returns the schema name for ModelPublicArcStageParts
func (m ModelPublicArcStageParts) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicArcStageParts) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicArcStageParts) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicArcStageParts) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicArcStageParts) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicArcStageParts) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicArcStageParts) GetPrefix() string {
return "ASP"
}
@@ -0,0 +1,67 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicArcStages struct {
bun.BaseModel `bun:"table:public.arc_stages,alias:arc_stages"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
ArcID int64 `bun:"arc_id,type:bigint,notnull," json:"arc_id"`
Condition resolvespec_common.SqlString `bun:"condition,type:text,default:'',notnull," json:"condition"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
StageOrder int32 `bun:"stage_order,type:int,default:0,notnull," json:"stage_order"`
RelArcID *ModelPublicCharacterArcs `bun:"rel:has-one,join:arc_id=id" json:"relarcid,omitempty"` // Has one ModelPublicCharacterArcs
RelStageIDPublicArcStageParts []*ModelPublicArcStageParts `bun:"rel:has-many,join:id=stage_id" json:"relstageidpublicarcstageparts,omitempty"` // Has many ModelPublicArcStageParts
RelCurrentStageIDPublicPersonaArcs []*ModelPublicPersonaArc `bun:"rel:has-many,join:id=current_stage_id" json:"relcurrentstageidpublicpersonaarcs,omitempty"` // Has many ModelPublicPersonaArc
}
// TableName returns the table name for ModelPublicArcStages
func (m ModelPublicArcStages) TableName() string {
return "public.arc_stages"
}
// TableNameOnly returns the table name without schema for ModelPublicArcStages
func (m ModelPublicArcStages) TableNameOnly() string {
return "arc_stages"
}
// SchemaName returns the schema name for ModelPublicArcStages
func (m ModelPublicArcStages) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicArcStages) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicArcStages) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicArcStages) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicArcStages) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicArcStages) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicArcStages) GetPrefix() string {
return "ASR"
}
@@ -0,0 +1,65 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicCharacterArcs struct {
bun.BaseModel `bun:"table:public.character_arcs,alias:character_arcs"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Summary resolvespec_common.SqlString `bun:"summary,type:text,default:'',notnull," json:"summary"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelArcIDPublicArcStages []*ModelPublicArcStages `bun:"rel:has-many,join:id=arc_id" json:"relarcidpublicarcstages,omitempty"` // Has many ModelPublicArcStages
RelArcIDPublicPersonaArcs []*ModelPublicPersonaArc `bun:"rel:has-many,join:id=arc_id" json:"relarcidpublicpersonaarcs,omitempty"` // Has many ModelPublicPersonaArc
}
// TableName returns the table name for ModelPublicCharacterArcs
func (m ModelPublicCharacterArcs) TableName() string {
return "public.character_arcs"
}
// TableNameOnly returns the table name without schema for ModelPublicCharacterArcs
func (m ModelPublicCharacterArcs) TableNameOnly() string {
return "character_arcs"
}
// SchemaName returns the schema name for ModelPublicCharacterArcs
func (m ModelPublicCharacterArcs) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicCharacterArcs) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicCharacterArcs) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicCharacterArcs) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicCharacterArcs) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicCharacterArcs) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicCharacterArcs) GetPrefix() string {
return "CAH"
}
@@ -9,18 +9,19 @@ import (
type ModelPublicChatHistories struct { type ModelPublicChatHistories struct {
bun.BaseModel `bun:"table:public.chat_histories,alias:chat_histories"` bun.BaseModel `bun:"table:public.chat_histories,alias:chat_histories"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"` ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
AgentID resolvespec_common.SqlString `bun:"agent_id,type:text,nullzero," json:"agent_id"` AgentID resolvespec_common.SqlString `bun:"agent_id,type:text,nullzero," json:"agent_id"`
Channel resolvespec_common.SqlString `bun:"channel,type:text,nullzero," json:"channel"` Channel resolvespec_common.SqlString `bun:"channel,type:text,nullzero," json:"channel"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Messages resolvespec_common.SqlJSONB `bun:"messages,type:jsonb,default:'[',notnull," json:"messages"` GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
Messages resolvespec_common.SqlJSONB `bun:"messages,type:jsonb,default:'[]',notnull," json:"messages"`
Metadata resolvespec_common.SqlJSONB `bun:"metadata,type:jsonb,default:'{}',notnull," json:"metadata"` Metadata resolvespec_common.SqlJSONB `bun:"metadata,type:jsonb,default:'{}',notnull," json:"metadata"`
ProjectID resolvespec_common.SqlUUID `bun:"project_id,type:uuid,nullzero," json:"project_id"` ProjectID resolvespec_common.SqlInt64 `bun:"project_id,type:bigint,nullzero," json:"project_id"`
SessionID resolvespec_common.SqlString `bun:"session_id,type:text,notnull," json:"session_id"` SessionID resolvespec_common.SqlString `bun:"session_id,type:text,notnull," json:"session_id"`
Summary resolvespec_common.SqlString `bun:"summary,type:text,nullzero," json:"summary"` Summary resolvespec_common.SqlString `bun:"summary,type:text,nullzero," json:"summary"`
Title resolvespec_common.SqlString `bun:"title,type:text,nullzero," json:"title"` Title resolvespec_common.SqlString `bun:"title,type:text,nullzero," json:"title"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=guid" json:"relprojectid,omitempty"` // Has one ModelPublicProjects RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
} }
// TableName returns the table name for ModelPublicChatHistories // TableName returns the table name for ModelPublicChatHistories
@@ -45,7 +46,7 @@ func (m ModelPublicChatHistories) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicChatHistories) GetIDStr() string { func (m ModelPublicChatHistories) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -1,66 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicContactInteractions struct {
bun.BaseModel `bun:"table:public.contact_interactions,alias:contact_interactions"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
ContactID resolvespec_common.SqlUUID `bun:"contact_id,type:uuid,notnull," json:"contact_id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
FollowUpNeeded bool `bun:"follow_up_needed,type:boolean,default:false,notnull," json:"follow_up_needed"`
FollowUpNotes resolvespec_common.SqlString `bun:"follow_up_notes,type:text,nullzero," json:"follow_up_notes"`
InteractionType resolvespec_common.SqlString `bun:"interaction_type,type:text,notnull," json:"interaction_type"`
OccurredAt resolvespec_common.SqlTimeStamp `bun:"occurred_at,type:timestamptz,default:now(),notnull," json:"occurred_at"`
Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"`
RelContactID *ModelPublicProfessionalContacts `bun:"rel:has-one,join:contact_id=id" json:"relcontactid,omitempty"` // Has one ModelPublicProfessionalContacts
}
// TableName returns the table name for ModelPublicContactInteractions
func (m ModelPublicContactInteractions) TableName() string {
return "public.contact_interactions"
}
// TableNameOnly returns the table name without schema for ModelPublicContactInteractions
func (m ModelPublicContactInteractions) TableNameOnly() string {
return "contact_interactions"
}
// SchemaName returns the schema name for ModelPublicContactInteractions
func (m ModelPublicContactInteractions) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicContactInteractions) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicContactInteractions) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicContactInteractions) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicContactInteractions) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicContactInteractions) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicContactInteractions) GetPrefix() string {
return "CIO"
}
@@ -11,13 +11,13 @@ type ModelPublicEmbeddings struct {
bun.BaseModel `bun:"table:public.embeddings,alias:embeddings"` bun.BaseModel `bun:"table:public.embeddings,alias:embeddings"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),nullzero," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),nullzero," json:"created_at"`
Dim resolvespec_common.SqlInt32 `bun:"dim,type:int,notnull," json:"dim"` Dim int32 `bun:"dim,type:int,notnull," json:"dim"`
Embedding resolvespec_common.SqlString `bun:"embedding,type:vector,notnull," json:"embedding"` Embedding resolvespec_common.SqlVector `bun:"embedding,type:vector,notnull," json:"embedding"`
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
Model resolvespec_common.SqlString `bun:"model,type:text,notnull,unique:uidx_embeddings_thought_id_model," json:"model"` Model resolvespec_common.SqlString `bun:"model,type:text,notnull,unique:uidx_embeddings_thought_id_model," json:"model"`
ThoughtID resolvespec_common.SqlUUID `bun:"thought_id,type:uuid,notnull,unique:uidx_embeddings_thought_id_model," json:"thought_id"` ThoughtID int64 `bun:"thought_id,type:bigint,notnull,unique:uidx_embeddings_thought_id_model," json:"thought_id"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),nullzero," json:"updated_at"` UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),nullzero," json:"updated_at"`
RelThoughtID *ModelPublicThoughts `bun:"rel:has-one,join:thought_id=guid" json:"relthoughtid,omitempty"` // Has one ModelPublicThoughts RelThoughtID *ModelPublicThoughts `bun:"rel:has-one,join:thought_id=id" json:"relthoughtid,omitempty"` // Has one ModelPublicThoughts
} }
// TableName returns the table name for ModelPublicEmbeddings // TableName returns the table name for ModelPublicEmbeddings
@@ -42,7 +42,7 @@ func (m ModelPublicEmbeddings) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicEmbeddings) GetIDStr() string { func (m ModelPublicEmbeddings) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -1,65 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicFamilyMembers struct {
bun.BaseModel `bun:"table:public.family_members,alias:family_members"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
BirthDate resolvespec_common.SqlDate `bun:"birth_date,type:date,nullzero," json:"birth_date"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
Relationship resolvespec_common.SqlString `bun:"relationship,type:text,nullzero," json:"relationship"`
RelFamilyMemberIDPublicActivities []*ModelPublicActivities `bun:"rel:has-many,join:id=family_member_id" json:"relfamilymemberidpublicactivities,omitempty"` // Has many ModelPublicActivities
RelFamilyMemberIDPublicImportantDates []*ModelPublicImportantDates `bun:"rel:has-many,join:id=family_member_id" json:"relfamilymemberidpublicimportantdates,omitempty"` // Has many ModelPublicImportantDates
}
// TableName returns the table name for ModelPublicFamilyMembers
func (m ModelPublicFamilyMembers) TableName() string {
return "public.family_members"
}
// TableNameOnly returns the table name without schema for ModelPublicFamilyMembers
func (m ModelPublicFamilyMembers) TableNameOnly() string {
return "family_members"
}
// SchemaName returns the schema name for ModelPublicFamilyMembers
func (m ModelPublicFamilyMembers) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicFamilyMembers) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicFamilyMembers) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicFamilyMembers) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicFamilyMembers) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicFamilyMembers) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicFamilyMembers) GetPrefix() string {
return "FMA"
}
@@ -1,65 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicHouseholdItems struct {
bun.BaseModel `bun:"table:public.household_items,alias:household_items"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
Category resolvespec_common.SqlString `bun:"category,type:text,nullzero," json:"category"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Details resolvespec_common.SqlJSONB `bun:"details,type:jsonb,default:'{}',notnull," json:"details"`
Location resolvespec_common.SqlString `bun:"location,type:text,nullzero," json:"location"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
}
// TableName returns the table name for ModelPublicHouseholdItems
func (m ModelPublicHouseholdItems) TableName() string {
return "public.household_items"
}
// TableNameOnly returns the table name without schema for ModelPublicHouseholdItems
func (m ModelPublicHouseholdItems) TableNameOnly() string {
return "household_items"
}
// SchemaName returns the schema name for ModelPublicHouseholdItems
func (m ModelPublicHouseholdItems) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicHouseholdItems) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicHouseholdItems) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicHouseholdItems) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicHouseholdItems) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicHouseholdItems) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicHouseholdItems) GetPrefix() string {
return "HIO"
}
@@ -1,67 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicHouseholdVendors struct {
bun.BaseModel `bun:"table:public.household_vendors,alias:household_vendors"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Email resolvespec_common.SqlString `bun:"email,type:text,nullzero," json:"email"`
LastUsed resolvespec_common.SqlDate `bun:"last_used,type:date,nullzero," json:"last_used"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
Phone resolvespec_common.SqlString `bun:"phone,type:text,nullzero," json:"phone"`
Rating resolvespec_common.SqlInt32 `bun:"rating,type:int,nullzero," json:"rating"`
ServiceType resolvespec_common.SqlString `bun:"service_type,type:text,nullzero," json:"service_type"`
Website resolvespec_common.SqlString `bun:"website,type:text,nullzero," json:"website"`
}
// TableName returns the table name for ModelPublicHouseholdVendors
func (m ModelPublicHouseholdVendors) TableName() string {
return "public.household_vendors"
}
// TableNameOnly returns the table name without schema for ModelPublicHouseholdVendors
func (m ModelPublicHouseholdVendors) TableNameOnly() string {
return "household_vendors"
}
// SchemaName returns the schema name for ModelPublicHouseholdVendors
func (m ModelPublicHouseholdVendors) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicHouseholdVendors) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicHouseholdVendors) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicHouseholdVendors) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicHouseholdVendors) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicHouseholdVendors) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicHouseholdVendors) GetPrefix() string {
return "HVO"
}
@@ -1,66 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicImportantDates struct {
bun.BaseModel `bun:"table:public.important_dates,alias:important_dates"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
DateValue resolvespec_common.SqlDate `bun:"date_value,type:date,notnull," json:"date_value"`
FamilyMemberID resolvespec_common.SqlUUID `bun:"family_member_id,type:uuid,nullzero," json:"family_member_id"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
RecurringYearly bool `bun:"recurring_yearly,type:boolean,default:false,notnull," json:"recurring_yearly"`
ReminderDaysBefore resolvespec_common.SqlInt32 `bun:"reminder_days_before,type:int,default:7,notnull," json:"reminder_days_before"`
Title resolvespec_common.SqlString `bun:"title,type:text,notnull," json:"title"`
RelFamilyMemberID *ModelPublicFamilyMembers `bun:"rel:has-one,join:family_member_id=id" json:"relfamilymemberid,omitempty"` // Has one ModelPublicFamilyMembers
}
// TableName returns the table name for ModelPublicImportantDates
func (m ModelPublicImportantDates) TableName() string {
return "public.important_dates"
}
// TableNameOnly returns the table name without schema for ModelPublicImportantDates
func (m ModelPublicImportantDates) TableNameOnly() string {
return "important_dates"
}
// SchemaName returns the schema name for ModelPublicImportantDates
func (m ModelPublicImportantDates) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicImportantDates) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicImportantDates) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicImportantDates) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicImportantDates) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicImportantDates) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicImportantDates) GetPrefix() string {
return "IDM"
}
@@ -9,32 +9,33 @@ import (
type ModelPublicLearnings struct { type ModelPublicLearnings struct {
bun.BaseModel `bun:"table:public.learnings,alias:learnings"` bun.BaseModel `bun:"table:public.learnings,alias:learnings"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"` ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
ActionRequired bool `bun:"action_required,type:boolean,default:false,notnull," json:"action_required"` ActionRequired bool `bun:"action_required,type:boolean,default:false,notnull," json:"action_required"`
Area resolvespec_common.SqlString `bun:"area,type:text,default:'other',notnull," json:"area"` Area resolvespec_common.SqlString `bun:"area,type:text,default:'other',notnull," json:"area"`
Category resolvespec_common.SqlString `bun:"category,type:text,default:'insight',notnull," json:"category"` Category resolvespec_common.SqlString `bun:"category,type:text,default:'insight',notnull," json:"category"`
Confidence resolvespec_common.SqlString `bun:"confidence,type:text,default:'hypothesis',notnull," json:"confidence"` Confidence resolvespec_common.SqlString `bun:"confidence,type:text,default:'hypothesis',notnull," json:"confidence"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Details resolvespec_common.SqlString `bun:"details,type:text,default:'',notnull," json:"details"` Details resolvespec_common.SqlString `bun:"details,type:text,default:'',notnull," json:"details"`
DuplicateOfLearningID resolvespec_common.SqlUUID `bun:"duplicate_of_learning_id,type:uuid,nullzero," json:"duplicate_of_learning_id"` DuplicateOfLearningID resolvespec_common.SqlInt64 `bun:"duplicate_of_learning_id,type:bigint,nullzero," json:"duplicate_of_learning_id"`
Priority resolvespec_common.SqlString `bun:"priority,type:text,default:'medium',notnull," json:"priority"` GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
ProjectID resolvespec_common.SqlUUID `bun:"project_id,type:uuid,nullzero," json:"project_id"` Priority resolvespec_common.SqlString `bun:"priority,type:text,default:'medium',notnull," json:"priority"`
RelatedSkillID resolvespec_common.SqlUUID `bun:"related_skill_id,type:uuid,nullzero," json:"related_skill_id"` ProjectID resolvespec_common.SqlInt64 `bun:"project_id,type:bigint,nullzero," json:"project_id"`
RelatedThoughtID resolvespec_common.SqlUUID `bun:"related_thought_id,type:uuid,nullzero," json:"related_thought_id"` RelatedSkillID resolvespec_common.SqlInt64 `bun:"related_skill_id,type:bigint,nullzero," json:"related_skill_id"`
ReviewedAt resolvespec_common.SqlTimeStamp `bun:"reviewed_at,type:timestamptz,nullzero," json:"reviewed_at"` RelatedThoughtID resolvespec_common.SqlInt64 `bun:"related_thought_id,type:bigint,nullzero," json:"related_thought_id"`
ReviewedBy resolvespec_common.SqlString `bun:"reviewed_by,type:text,nullzero," json:"reviewed_by"` ReviewedAt resolvespec_common.SqlTimeStamp `bun:"reviewed_at,type:timestamptz,nullzero," json:"reviewed_at"`
SourceRef resolvespec_common.SqlString `bun:"source_ref,type:text,nullzero," json:"source_ref"` ReviewedBy resolvespec_common.SqlString `bun:"reviewed_by,type:text,nullzero," json:"reviewed_by"`
SourceType resolvespec_common.SqlString `bun:"source_type,type:text,nullzero," json:"source_type"` SourceRef resolvespec_common.SqlString `bun:"source_ref,type:text,nullzero," json:"source_ref"`
Status resolvespec_common.SqlString `bun:"status,type:text,default:'pending',notnull," json:"status"` SourceType resolvespec_common.SqlString `bun:"source_type,type:text,nullzero," json:"source_type"`
Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"` Status resolvespec_common.SqlString `bun:"status,type:text,default:'pending',notnull," json:"status"`
SupersedesLearningID resolvespec_common.SqlUUID `bun:"supersedes_learning_id,type:uuid,nullzero," json:"supersedes_learning_id"` Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"`
Tags resolvespec_common.SqlString `bun:"tags,type:text,nullzero," json:"tags"` SupersedesLearningID resolvespec_common.SqlInt64 `bun:"supersedes_learning_id,type:bigint,nullzero," json:"supersedes_learning_id"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
RelDuplicateOfLearningID *ModelPublicLearnings `bun:"rel:has-one,join:duplicate_of_learning_id=id" json:"relduplicateoflearningid,omitempty"` // Has one ModelPublicLearnings UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=guid" json:"relprojectid,omitempty"` // Has one ModelPublicProjects RelDuplicateOfLearningID *ModelPublicLearnings `bun:"rel:has-one,join:duplicate_of_learning_id=id" json:"relduplicateoflearningid,omitempty"` // Has one ModelPublicLearnings
RelRelatedSkillID *ModelPublicAgentSkills `bun:"rel:has-one,join:related_skill_id=id" json:"relrelatedskillid,omitempty"` // Has one ModelPublicAgentSkills RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
RelRelatedThoughtID *ModelPublicThoughts `bun:"rel:has-one,join:related_thought_id=guid" json:"relrelatedthoughtid,omitempty"` // Has one ModelPublicThoughts RelRelatedSkillID *ModelPublicAgentSkills `bun:"rel:has-one,join:related_skill_id=id" json:"relrelatedskillid,omitempty"` // Has one ModelPublicAgentSkills
RelSupersedesLearningID *ModelPublicLearnings `bun:"rel:has-one,join:supersedes_learning_id=id" json:"relsupersedeslearningid,omitempty"` // Has one ModelPublicLearnings RelRelatedThoughtID *ModelPublicThoughts `bun:"rel:has-one,join:related_thought_id=id" json:"relrelatedthoughtid,omitempty"` // Has one ModelPublicThoughts
RelSupersedesLearningID *ModelPublicLearnings `bun:"rel:has-one,join:supersedes_learning_id=id" json:"relsupersedeslearningid,omitempty"` // Has one ModelPublicLearnings
} }
// TableName returns the table name for ModelPublicLearnings // TableName returns the table name for ModelPublicLearnings
@@ -59,7 +60,7 @@ func (m ModelPublicLearnings) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicLearnings) GetIDStr() string { func (m ModelPublicLearnings) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -1,65 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicMaintenanceLogs struct {
bun.BaseModel `bun:"table:public.maintenance_logs,alias:maintenance_logs"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
CompletedAt resolvespec_common.SqlTimeStamp `bun:"completed_at,type:timestamptz,default:now(),notnull," json:"completed_at"`
Cost resolvespec_common.SqlFloat64 `bun:"cost,type:decimal(10,2),nullzero," json:"cost"`
NextAction resolvespec_common.SqlString `bun:"next_action,type:text,nullzero," json:"next_action"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
PerformedBy resolvespec_common.SqlString `bun:"performed_by,type:text,nullzero," json:"performed_by"`
TaskID resolvespec_common.SqlUUID `bun:"task_id,type:uuid,notnull," json:"task_id"`
RelTaskID *ModelPublicMaintenanceTasks `bun:"rel:has-one,join:task_id=id" json:"reltaskid,omitempty"` // Has one ModelPublicMaintenanceTasks
}
// TableName returns the table name for ModelPublicMaintenanceLogs
func (m ModelPublicMaintenanceLogs) TableName() string {
return "public.maintenance_logs"
}
// TableNameOnly returns the table name without schema for ModelPublicMaintenanceLogs
func (m ModelPublicMaintenanceLogs) TableNameOnly() string {
return "maintenance_logs"
}
// SchemaName returns the schema name for ModelPublicMaintenanceLogs
func (m ModelPublicMaintenanceLogs) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicMaintenanceLogs) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicMaintenanceLogs) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicMaintenanceLogs) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicMaintenanceLogs) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicMaintenanceLogs) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicMaintenanceLogs) GetPrefix() string {
return "MLA"
}
@@ -1,68 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicMaintenanceTasks struct {
bun.BaseModel `bun:"table:public.maintenance_tasks,alias:maintenance_tasks"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
Category resolvespec_common.SqlString `bun:"category,type:text,nullzero," json:"category"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
FrequencyDays resolvespec_common.SqlInt32 `bun:"frequency_days,type:int,nullzero," json:"frequency_days"`
LastCompleted resolvespec_common.SqlTimeStamp `bun:"last_completed,type:timestamptz,nullzero," json:"last_completed"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
NextDue resolvespec_common.SqlTimeStamp `bun:"next_due,type:timestamptz,nullzero," json:"next_due"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
Priority resolvespec_common.SqlString `bun:"priority,type:text,default:'medium',notnull," json:"priority"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelTaskIDPublicMaintenanceLogs []*ModelPublicMaintenanceLogs `bun:"rel:has-many,join:id=task_id" json:"reltaskidpublicmaintenancelogs,omitempty"` // Has many ModelPublicMaintenanceLogs
}
// TableName returns the table name for ModelPublicMaintenanceTasks
func (m ModelPublicMaintenanceTasks) TableName() string {
return "public.maintenance_tasks"
}
// TableNameOnly returns the table name without schema for ModelPublicMaintenanceTasks
func (m ModelPublicMaintenanceTasks) TableNameOnly() string {
return "maintenance_tasks"
}
// SchemaName returns the schema name for ModelPublicMaintenanceTasks
func (m ModelPublicMaintenanceTasks) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicMaintenanceTasks) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicMaintenanceTasks) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicMaintenanceTasks) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicMaintenanceTasks) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicMaintenanceTasks) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicMaintenanceTasks) GetPrefix() string {
return "MTA"
}
@@ -1,67 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicMealPlans struct {
bun.BaseModel `bun:"table:public.meal_plans,alias:meal_plans"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
CustomMeal resolvespec_common.SqlString `bun:"custom_meal,type:text,nullzero," json:"custom_meal"`
DayOfWeek resolvespec_common.SqlString `bun:"day_of_week,type:text,notnull," json:"day_of_week"`
MealType resolvespec_common.SqlString `bun:"meal_type,type:text,notnull," json:"meal_type"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
RecipeID resolvespec_common.SqlUUID `bun:"recipe_id,type:uuid,nullzero," json:"recipe_id"`
Servings resolvespec_common.SqlInt32 `bun:"servings,type:int,nullzero," json:"servings"`
WeekStart resolvespec_common.SqlDate `bun:"week_start,type:date,notnull," json:"week_start"`
RelRecipeID *ModelPublicRecipes `bun:"rel:has-one,join:recipe_id=id" json:"relrecipeid,omitempty"` // Has one ModelPublicRecipes
}
// TableName returns the table name for ModelPublicMealPlans
func (m ModelPublicMealPlans) TableName() string {
return "public.meal_plans"
}
// TableNameOnly returns the table name without schema for ModelPublicMealPlans
func (m ModelPublicMealPlans) TableNameOnly() string {
return "meal_plans"
}
// SchemaName returns the schema name for ModelPublicMealPlans
func (m ModelPublicMealPlans) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicMealPlans) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicMealPlans) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicMealPlans) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicMealPlans) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicMealPlans) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicMealPlans) GetPrefix() string {
return "MPE"
}
@@ -0,0 +1,62 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicOauthClients struct {
bun.BaseModel `bun:"table:public.oauth_clients,alias:oauth_clients"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
ClientID resolvespec_common.SqlString `bun:"client_id,type:text,notnull," json:"client_id"`
ClientName resolvespec_common.SqlString `bun:"client_name,type:text,default:'',notnull," json:"client_name"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
RedirectUris resolvespec_common.SqlStringArray `bun:"redirect_uris,type:text[],default:'{}',notnull," json:"redirect_uris"`
}
// TableName returns the table name for ModelPublicOauthClients
func (m ModelPublicOauthClients) TableName() string {
return "public.oauth_clients"
}
// TableNameOnly returns the table name without schema for ModelPublicOauthClients
func (m ModelPublicOauthClients) TableNameOnly() string {
return "oauth_clients"
}
// SchemaName returns the schema name for ModelPublicOauthClients
func (m ModelPublicOauthClients) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicOauthClients) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicOauthClients) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicOauthClients) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicOauthClients) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicOauthClients) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicOauthClients) GetPrefix() string {
return "OCA"
}
@@ -1,68 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicOpportunities struct {
bun.BaseModel `bun:"table:public.opportunities,alias:opportunities"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
ContactID resolvespec_common.SqlUUID `bun:"contact_id,type:uuid,nullzero," json:"contact_id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,nullzero," json:"description"`
ExpectedCloseDate resolvespec_common.SqlDate `bun:"expected_close_date,type:date,nullzero," json:"expected_close_date"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
Stage resolvespec_common.SqlString `bun:"stage,type:text,default:'identified',notnull," json:"stage"`
Title resolvespec_common.SqlString `bun:"title,type:text,notnull," json:"title"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
Value resolvespec_common.SqlFloat64 `bun:"value,type:decimal(12,2),nullzero," json:"value"`
RelContactID *ModelPublicProfessionalContacts `bun:"rel:has-one,join:contact_id=id" json:"relcontactid,omitempty"` // Has one ModelPublicProfessionalContacts
}
// TableName returns the table name for ModelPublicOpportunities
func (m ModelPublicOpportunities) TableName() string {
return "public.opportunities"
}
// TableNameOnly returns the table name without schema for ModelPublicOpportunities
func (m ModelPublicOpportunities) TableNameOnly() string {
return "opportunities"
}
// SchemaName returns the schema name for ModelPublicOpportunities
func (m ModelPublicOpportunities) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicOpportunities) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicOpportunities) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicOpportunities) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicOpportunities) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicOpportunities) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicOpportunities) GetPrefix() string {
return "OPP"
}
@@ -0,0 +1,65 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicPersonaArc struct {
bun.BaseModel `bun:"table:public.persona_arc,alias:persona_arc"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
PersonaID int64 `bun:"persona_id,type:bigint,pk," json:"persona_id"`
ArcID int64 `bun:"arc_id,type:bigint,notnull," json:"arc_id"`
CurrentStageID int64 `bun:"current_stage_id,type:bigint,notnull," json:"current_stage_id"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelArcID *ModelPublicCharacterArcs `bun:"rel:has-one,join:arc_id=id" json:"relarcid,omitempty"` // Has one ModelPublicCharacterArcs
RelCurrentStageID *ModelPublicArcStages `bun:"rel:has-one,join:current_stage_id=id" json:"relcurrentstageid,omitempty"` // Has one ModelPublicArcStages
RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas
}
// TableName returns the table name for ModelPublicPersonaArc
func (m ModelPublicPersonaArc) TableName() string {
return "public.persona_arc"
}
// TableNameOnly returns the table name without schema for ModelPublicPersonaArc
func (m ModelPublicPersonaArc) TableNameOnly() string {
return "persona_arc"
}
// SchemaName returns the schema name for ModelPublicPersonaArc
func (m ModelPublicPersonaArc) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicPersonaArc) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicPersonaArc) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicPersonaArc) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicPersonaArc) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicPersonaArc) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicPersonaArc) GetPrefix() string {
return "PAE"
}
@@ -0,0 +1,63 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicPlanDependencies struct {
bun.BaseModel `bun:"table:public.plan_dependencies,alias:plan_dependencies"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
DependsOnPlanID int64 `bun:"depends_on_plan_id,type:bigint,notnull,unique:uidx_plan_dependencies_plan_id_depends_on_plan_id," json:"depends_on_plan_id"`
PlanID int64 `bun:"plan_id,type:bigint,notnull,unique:uidx_plan_dependencies_plan_id_depends_on_plan_id," json:"plan_id"`
RelDependsOnPlanID *ModelPublicPlans `bun:"rel:has-one,join:depends_on_plan_id=id" json:"reldependsonplanid,omitempty"` // Has one ModelPublicPlans
RelPlanID *ModelPublicPlans `bun:"rel:has-one,join:plan_id=id" json:"relplanid,omitempty"` // Has one ModelPublicPlans
}
// TableName returns the table name for ModelPublicPlanDependencies
func (m ModelPublicPlanDependencies) TableName() string {
return "public.plan_dependencies"
}
// TableNameOnly returns the table name without schema for ModelPublicPlanDependencies
func (m ModelPublicPlanDependencies) TableNameOnly() string {
return "plan_dependencies"
}
// SchemaName returns the schema name for ModelPublicPlanDependencies
func (m ModelPublicPlanDependencies) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicPlanDependencies) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicPlanDependencies) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicPlanDependencies) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicPlanDependencies) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicPlanDependencies) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicPlanDependencies) GetPrefix() string {
return "PDL"
}
@@ -0,0 +1,63 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicPlanGuardrails struct {
bun.BaseModel `bun:"table:public.plan_guardrails,alias:plan_guardrails"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
GuardrailID int64 `bun:"guardrail_id,type:bigint,notnull,unique:uidx_plan_guardrails_plan_id_guardrail_id," json:"guardrail_id"`
PlanID int64 `bun:"plan_id,type:bigint,notnull,unique:uidx_plan_guardrails_plan_id_guardrail_id," json:"plan_id"`
RelGuardrailID *ModelPublicAgentGuardrails `bun:"rel:has-one,join:guardrail_id=id" json:"relguardrailid,omitempty"` // Has one ModelPublicAgentGuardrails
RelPlanID *ModelPublicPlans `bun:"rel:has-one,join:plan_id=id" json:"relplanid,omitempty"` // Has one ModelPublicPlans
}
// TableName returns the table name for ModelPublicPlanGuardrails
func (m ModelPublicPlanGuardrails) TableName() string {
return "public.plan_guardrails"
}
// TableNameOnly returns the table name without schema for ModelPublicPlanGuardrails
func (m ModelPublicPlanGuardrails) TableNameOnly() string {
return "plan_guardrails"
}
// SchemaName returns the schema name for ModelPublicPlanGuardrails
func (m ModelPublicPlanGuardrails) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicPlanGuardrails) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicPlanGuardrails) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicPlanGuardrails) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicPlanGuardrails) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicPlanGuardrails) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicPlanGuardrails) GetPrefix() string {
return "PGL"
}
@@ -0,0 +1,63 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicPlanRelatedPlans struct {
bun.BaseModel `bun:"table:public.plan_related_plans,alias:plan_related_plans"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
PlanAID int64 `bun:"plan_a_id,type:bigint,notnull,unique:uidx_plan_related_plans_plan_a_id_plan_b_id," json:"plan_a_id"`
PlanBID int64 `bun:"plan_b_id,type:bigint,notnull,unique:uidx_plan_related_plans_plan_a_id_plan_b_id," json:"plan_b_id"`
RelPlanAID *ModelPublicPlans `bun:"rel:has-one,join:plan_a_id=id" json:"relplanaid,omitempty"` // Has one ModelPublicPlans
RelPlanBID *ModelPublicPlans `bun:"rel:has-one,join:plan_b_id=id" json:"relplanbid,omitempty"` // Has one ModelPublicPlans
}
// TableName returns the table name for ModelPublicPlanRelatedPlans
func (m ModelPublicPlanRelatedPlans) TableName() string {
return "public.plan_related_plans"
}
// TableNameOnly returns the table name without schema for ModelPublicPlanRelatedPlans
func (m ModelPublicPlanRelatedPlans) TableNameOnly() string {
return "plan_related_plans"
}
// SchemaName returns the schema name for ModelPublicPlanRelatedPlans
func (m ModelPublicPlanRelatedPlans) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicPlanRelatedPlans) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicPlanRelatedPlans) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicPlanRelatedPlans) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicPlanRelatedPlans) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicPlanRelatedPlans) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicPlanRelatedPlans) GetPrefix() string {
return "PRP"
}
@@ -0,0 +1,63 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicPlanSkills struct {
bun.BaseModel `bun:"table:public.plan_skills,alias:plan_skills"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
PlanID int64 `bun:"plan_id,type:bigint,notnull,unique:uidx_plan_skills_plan_id_skill_id," json:"plan_id"`
SkillID int64 `bun:"skill_id,type:bigint,notnull,unique:uidx_plan_skills_plan_id_skill_id," json:"skill_id"`
RelPlanID *ModelPublicPlans `bun:"rel:has-one,join:plan_id=id" json:"relplanid,omitempty"` // Has one ModelPublicPlans
RelSkillID *ModelPublicAgentSkills `bun:"rel:has-one,join:skill_id=id" json:"relskillid,omitempty"` // Has one ModelPublicAgentSkills
}
// TableName returns the table name for ModelPublicPlanSkills
func (m ModelPublicPlanSkills) TableName() string {
return "public.plan_skills"
}
// TableNameOnly returns the table name without schema for ModelPublicPlanSkills
func (m ModelPublicPlanSkills) TableNameOnly() string {
return "plan_skills"
}
// SchemaName returns the schema name for ModelPublicPlanSkills
func (m ModelPublicPlanSkills) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicPlanSkills) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicPlanSkills) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicPlanSkills) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicPlanSkills) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicPlanSkills) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicPlanSkills) GetPrefix() string {
return "PSL"
}
@@ -0,0 +1,81 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicPlans struct {
bun.BaseModel `bun:"table:public.plans,alias:plans"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CompletedAt resolvespec_common.SqlTimeStamp `bun:"completed_at,type:timestamptz,nullzero," json:"completed_at"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
DueDate resolvespec_common.SqlTimeStamp `bun:"due_date,type:timestamptz,nullzero," json:"due_date"`
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
LastReviewedAt resolvespec_common.SqlTimeStamp `bun:"last_reviewed_at,type:timestamptz,nullzero," json:"last_reviewed_at"`
Owner resolvespec_common.SqlString `bun:"owner,type:text,nullzero," json:"owner"`
Priority resolvespec_common.SqlString `bun:"priority,type:text,default:'medium',notnull," json:"priority"` // low, medium, high, critical
ProjectID resolvespec_common.SqlInt64 `bun:"project_id,type:bigint,nullzero," json:"project_id"`
ReviewedBy resolvespec_common.SqlString `bun:"reviewed_by,type:text,nullzero," json:"reviewed_by"`
Status resolvespec_common.SqlString `bun:"status,type:text,default:'draft',notnull," json:"status"` // draft, active, blocked, completed, cancelled, superseded
SupersedesPlanID resolvespec_common.SqlInt64 `bun:"supersedes_plan_id,type:bigint,nullzero," json:"supersedes_plan_id"`
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
Title resolvespec_common.SqlString `bun:"title,type:text,notnull," json:"title"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
RelSupersedesPlanID *ModelPublicPlans `bun:"rel:has-one,join:supersedes_plan_id=id" json:"relsupersedesplanid,omitempty"` // Has one ModelPublicPlans
RelDependsOnPlanIDPublicPlanDependencies []*ModelPublicPlanDependencies `bun:"rel:has-many,join:id=depends_on_plan_id" json:"reldependsonplanidpublicplandependencies,omitempty"` // Has many ModelPublicPlanDependencies
RelPlanIDPublicPlanDependencies []*ModelPublicPlanDependencies `bun:"rel:has-many,join:id=plan_id" json:"relplanidpublicplandependencies,omitempty"` // Has many ModelPublicPlanDependencies
RelPlanAIDPublicPlanRelatedPlans []*ModelPublicPlanRelatedPlans `bun:"rel:has-many,join:id=plan_a_id" json:"relplanaidpublicplanrelatedplans,omitempty"` // Has many ModelPublicPlanRelatedPlans
RelPlanBIDPublicPlanRelatedPlans []*ModelPublicPlanRelatedPlans `bun:"rel:has-many,join:id=plan_b_id" json:"relplanbidpublicplanrelatedplans,omitempty"` // Has many ModelPublicPlanRelatedPlans
RelPlanIDPublicPlanSkills []*ModelPublicPlanSkills `bun:"rel:has-many,join:id=plan_id" json:"relplanidpublicplanskills,omitempty"` // Has many ModelPublicPlanSkills
RelPlanIDPublicPlanGuardrails []*ModelPublicPlanGuardrails `bun:"rel:has-many,join:id=plan_id" json:"relplanidpublicplanguardrails,omitempty"` // Has many ModelPublicPlanGuardrails
}
// TableName returns the table name for ModelPublicPlans
func (m ModelPublicPlans) TableName() string {
return "public.plans"
}
// TableNameOnly returns the table name without schema for ModelPublicPlans
func (m ModelPublicPlans) TableNameOnly() string {
return "plans"
}
// SchemaName returns the schema name for ModelPublicPlans
func (m ModelPublicPlans) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicPlans) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicPlans) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicPlans) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicPlans) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicPlans) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicPlans) GetPrefix() string {
return "PLA"
}
@@ -1,73 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicProfessionalContacts struct {
bun.BaseModel `bun:"table:public.professional_contacts,alias:professional_contacts"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
Company resolvespec_common.SqlString `bun:"company,type:text,nullzero," json:"company"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Email resolvespec_common.SqlString `bun:"email,type:text,nullzero," json:"email"`
FollowUpDate resolvespec_common.SqlDate `bun:"follow_up_date,type:date,nullzero," json:"follow_up_date"`
HowWeMet resolvespec_common.SqlString `bun:"how_we_met,type:text,nullzero," json:"how_we_met"`
LastContacted resolvespec_common.SqlTimeStamp `bun:"last_contacted,type:timestamptz,nullzero," json:"last_contacted"`
LinkedinURL resolvespec_common.SqlString `bun:"linkedin_url,type:text,nullzero," json:"linkedin_url"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
Phone resolvespec_common.SqlString `bun:"phone,type:text,nullzero," json:"phone"`
Tags resolvespec_common.SqlString `bun:"tags,type:text,nullzero," json:"tags"`
Title resolvespec_common.SqlString `bun:"title,type:text,nullzero," json:"title"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelContactIDPublicContactInteractions []*ModelPublicContactInteractions `bun:"rel:has-many,join:id=contact_id" json:"relcontactidpubliccontactinteractions,omitempty"` // Has many ModelPublicContactInteractions
RelContactIDPublicOpportunities []*ModelPublicOpportunities `bun:"rel:has-many,join:id=contact_id" json:"relcontactidpublicopportunities,omitempty"` // Has many ModelPublicOpportunities
}
// TableName returns the table name for ModelPublicProfessionalContacts
func (m ModelPublicProfessionalContacts) TableName() string {
return "public.professional_contacts"
}
// TableNameOnly returns the table name without schema for ModelPublicProfessionalContacts
func (m ModelPublicProfessionalContacts) TableNameOnly() string {
return "professional_contacts"
}
// SchemaName returns the schema name for ModelPublicProfessionalContacts
func (m ModelPublicProfessionalContacts) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicProfessionalContacts) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicProfessionalContacts) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicProfessionalContacts) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicProfessionalContacts) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicProfessionalContacts) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicProfessionalContacts) GetPrefix() string {
return "PCR"
}
@@ -9,12 +9,12 @@ import (
type ModelPublicProjectGuardrails struct { type ModelPublicProjectGuardrails struct {
bun.BaseModel `bun:"table:public.project_guardrails,alias:project_guardrails"` bun.BaseModel `bun:"table:public.project_guardrails,alias:project_guardrails"`
ID resolvespec_common.SqlInt32 `bun:"id,type:serial,pk,autoincrement," json:"id"` ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
GuardrailID resolvespec_common.SqlUUID `bun:"guardrail_id,type:uuid,notnull," json:"guardrail_id"` GuardrailID int64 `bun:"guardrail_id,type:bigint,notnull," json:"guardrail_id"`
ProjectID resolvespec_common.SqlUUID `bun:"project_id,type:uuid,notnull," json:"project_id"` ProjectID int64 `bun:"project_id,type:bigint,notnull," json:"project_id"`
RelGuardrailID *ModelPublicAgentGuardrails `bun:"rel:has-one,join:guardrail_id=id" json:"relguardrailid,omitempty"` // Has one ModelPublicAgentGuardrails RelGuardrailID *ModelPublicAgentGuardrails `bun:"rel:has-one,join:guardrail_id=id" json:"relguardrailid,omitempty"` // Has one ModelPublicAgentGuardrails
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=guid" json:"relprojectid,omitempty"` // Has one ModelPublicProjects RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
} }
// TableName returns the table name for ModelPublicProjectGuardrails // TableName returns the table name for ModelPublicProjectGuardrails
@@ -39,7 +39,7 @@ func (m ModelPublicProjectGuardrails) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicProjectGuardrails) GetIDStr() string { func (m ModelPublicProjectGuardrails) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -0,0 +1,64 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicProjectPersonas struct {
bun.BaseModel `bun:"table:public.project_personas,alias:project_personas"`
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
IsDefault bool `bun:"is_default,type:boolean,default:false,notnull," json:"is_default"`
PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"`
ProjectID int64 `bun:"project_id,type:bigint,notnull," json:"project_id"`
RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
}
// TableName returns the table name for ModelPublicProjectPersonas
func (m ModelPublicProjectPersonas) TableName() string {
return "public.project_personas"
}
// TableNameOnly returns the table name without schema for ModelPublicProjectPersonas
func (m ModelPublicProjectPersonas) TableNameOnly() string {
return "project_personas"
}
// SchemaName returns the schema name for ModelPublicProjectPersonas
func (m ModelPublicProjectPersonas) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicProjectPersonas) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicProjectPersonas) GetIDStr() string {
return m.ID.String()
}
// SetID sets the primary key value
func (m ModelPublicProjectPersonas) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicProjectPersonas) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicProjectPersonas) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicProjectPersonas) GetPrefix() string {
return "PPR"
}
@@ -9,12 +9,13 @@ import (
type ModelPublicProjectSkills struct { type ModelPublicProjectSkills struct {
bun.BaseModel `bun:"table:public.project_skills,alias:project_skills"` bun.BaseModel `bun:"table:public.project_skills,alias:project_skills"`
ID resolvespec_common.SqlInt32 `bun:"id,type:serial,pk,autoincrement," json:"id"` ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
ProjectID resolvespec_common.SqlUUID `bun:"project_id,type:uuid,notnull," json:"project_id"` Override bool `bun:"override,type:boolean,default:false,notnull," json:"override"`
SkillID resolvespec_common.SqlUUID `bun:"skill_id,type:uuid,notnull," json:"skill_id"` ProjectID int64 `bun:"project_id,type:bigint,notnull," json:"project_id"`
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=guid" json:"relprojectid,omitempty"` // Has one ModelPublicProjects SkillID int64 `bun:"skill_id,type:bigint,notnull," json:"skill_id"`
RelSkillID *ModelPublicAgentSkills `bun:"rel:has-one,join:skill_id=id" json:"relskillid,omitempty"` // Has one ModelPublicAgentSkills RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
RelSkillID *ModelPublicAgentSkills `bun:"rel:has-one,join:skill_id=id" json:"relskillid,omitempty"` // Has one ModelPublicAgentSkills
} }
// TableName returns the table name for ModelPublicProjectSkills // TableName returns the table name for ModelPublicProjectSkills
@@ -39,7 +40,7 @@ func (m ModelPublicProjectSkills) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicProjectSkills) GetIDStr() string { func (m ModelPublicProjectSkills) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -16,12 +16,14 @@ type ModelPublicProjects struct {
LastActiveAt resolvespec_common.SqlTimeStamp `bun:"last_active_at,type:timestamptz,default:now(),nullzero," json:"last_active_at"` LastActiveAt resolvespec_common.SqlTimeStamp `bun:"last_active_at,type:timestamptz,default:now(),nullzero," json:"last_active_at"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
ThoughtCount resolvespec_common.SqlInt64 `bun:"thought_count,scanonly" json:"thought_count"` ThoughtCount resolvespec_common.SqlInt64 `bun:"thought_count,scanonly" json:"thought_count"`
RelProjectIDPublicThoughts []*ModelPublicThoughts `bun:"rel:has-many,join:guid=project_id" json:"relprojectidpublicthoughts,omitempty"` // Has many ModelPublicThoughts RelProjectIDPublicProjectPersonas []*ModelPublicProjectPersonas `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicprojectpersonas,omitempty"` // Has many ModelPublicProjectPersonas
RelProjectIDPublicStoredFiles []*ModelPublicStoredFiles `bun:"rel:has-many,join:guid=project_id" json:"relprojectidpublicstoredfiles,omitempty"` // Has many ModelPublicStoredFiles RelProjectIDPublicThoughts []*ModelPublicThoughts `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicthoughts,omitempty"` // Has many ModelPublicThoughts
RelProjectIDPublicChatHistories []*ModelPublicChatHistories `bun:"rel:has-many,join:guid=project_id" json:"relprojectidpublicchathistories,omitempty"` // Has many ModelPublicChatHistories RelProjectIDPublicStoredFiles []*ModelPublicStoredFiles `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicstoredfiles,omitempty"` // Has many ModelPublicStoredFiles
RelProjectIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:guid=project_id" json:"relprojectidpubliclearnings,omitempty"` // Has many ModelPublicLearnings RelProjectIDPublicChatHistories []*ModelPublicChatHistories `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicchathistories,omitempty"` // Has many ModelPublicChatHistories
RelProjectIDPublicProjectSkills []*ModelPublicProjectSkills `bun:"rel:has-many,join:guid=project_id" json:"relprojectidpublicprojectskills,omitempty"` // Has many ModelPublicProjectSkills RelProjectIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:id=project_id" json:"relprojectidpubliclearnings,omitempty"` // Has many ModelPublicLearnings
RelProjectIDPublicProjectGuardrails []*ModelPublicProjectGuardrails `bun:"rel:has-many,join:guid=project_id" json:"relprojectidpublicprojectguardrails,omitempty"` // Has many ModelPublicProjectGuardrails RelProjectIDPublicPlans []*ModelPublicPlans `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicplans,omitempty"` // Has many ModelPublicPlans
RelProjectIDPublicProjectSkills []*ModelPublicProjectSkills `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicprojectskills,omitempty"` // Has many ModelPublicProjectSkills
RelProjectIDPublicProjectGuardrails []*ModelPublicProjectGuardrails `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicprojectguardrails,omitempty"` // Has many ModelPublicProjectGuardrails
} }
// TableName returns the table name for ModelPublicProjects // TableName returns the table name for ModelPublicProjects
@@ -46,7 +48,7 @@ func (m ModelPublicProjects) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicProjects) GetIDStr() string { func (m ModelPublicProjects) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -1,71 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicRecipes struct {
bun.BaseModel `bun:"table:public.recipes,alias:recipes"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
CookTimeMinutes resolvespec_common.SqlInt32 `bun:"cook_time_minutes,type:int,nullzero," json:"cook_time_minutes"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Cuisine resolvespec_common.SqlString `bun:"cuisine,type:text,nullzero," json:"cuisine"`
Ingredients resolvespec_common.SqlJSONB `bun:"ingredients,type:jsonb,default:'[',notnull," json:"ingredients"`
Instructions resolvespec_common.SqlJSONB `bun:"instructions,type:jsonb,default:'[',notnull," json:"instructions"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
PrepTimeMinutes resolvespec_common.SqlInt32 `bun:"prep_time_minutes,type:int,nullzero," json:"prep_time_minutes"`
Rating resolvespec_common.SqlInt32 `bun:"rating,type:int,nullzero," json:"rating"`
Servings resolvespec_common.SqlInt32 `bun:"servings,type:int,nullzero," json:"servings"`
Tags resolvespec_common.SqlString `bun:"tags,type:text,nullzero," json:"tags"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelRecipeIDPublicMealPlans []*ModelPublicMealPlans `bun:"rel:has-many,join:id=recipe_id" json:"relrecipeidpublicmealplans,omitempty"` // Has many ModelPublicMealPlans
}
// TableName returns the table name for ModelPublicRecipes
func (m ModelPublicRecipes) TableName() string {
return "public.recipes"
}
// TableNameOnly returns the table name without schema for ModelPublicRecipes
func (m ModelPublicRecipes) TableNameOnly() string {
return "recipes"
}
// SchemaName returns the schema name for ModelPublicRecipes
func (m ModelPublicRecipes) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicRecipes) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicRecipes) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicRecipes) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicRecipes) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicRecipes) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicRecipes) GetPrefix() string {
return "REC"
}
@@ -1,63 +0,0 @@
// Code generated by relspecgo. DO NOT EDIT.
package generatedmodels
import (
"fmt"
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
"github.com/uptrace/bun"
)
type ModelPublicShoppingLists struct {
bun.BaseModel `bun:"table:public.shopping_lists,alias:shopping_lists"`
ID resolvespec_common.SqlUUID `bun:"id,type:uuid,pk,default:gen_random_uuid()," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
Items resolvespec_common.SqlJSONB `bun:"items,type:jsonb,default:'[',notnull," json:"items"`
Notes resolvespec_common.SqlString `bun:"notes,type:text,nullzero," json:"notes"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
WeekStart resolvespec_common.SqlDate `bun:"week_start,type:date,notnull," json:"week_start"`
}
// TableName returns the table name for ModelPublicShoppingLists
func (m ModelPublicShoppingLists) TableName() string {
return "public.shopping_lists"
}
// TableNameOnly returns the table name without schema for ModelPublicShoppingLists
func (m ModelPublicShoppingLists) TableNameOnly() string {
return "shopping_lists"
}
// SchemaName returns the schema name for ModelPublicShoppingLists
func (m ModelPublicShoppingLists) SchemaName() string {
return "public"
}
// GetID returns the primary key value
func (m ModelPublicShoppingLists) GetID() int64 {
return m.ID.Int64()
}
// GetIDStr returns the primary key as a string
func (m ModelPublicShoppingLists) GetIDStr() string {
return fmt.Sprintf("%v", m.ID)
}
// SetID sets the primary key value
func (m ModelPublicShoppingLists) SetID(newid int64) {
m.UpdateID(newid)
}
// UpdateID updates the primary key value
func (m *ModelPublicShoppingLists) UpdateID(newid int64) {
m.ID.FromString(fmt.Sprintf("%d", newid))
}
// GetIDName returns the name of the primary key column
func (m ModelPublicShoppingLists) GetIDName() string {
return "id"
}
// GetPrefix returns the table prefix
func (m ModelPublicShoppingLists) GetPrefix() string {
return "SLH"
}
@@ -17,13 +17,13 @@ type ModelPublicStoredFiles struct {
Kind resolvespec_common.SqlString `bun:"kind,type:text,default:'file',notnull," json:"kind"` Kind resolvespec_common.SqlString `bun:"kind,type:text,default:'file',notnull," json:"kind"`
MediaType resolvespec_common.SqlString `bun:"media_type,type:text,notnull," json:"media_type"` MediaType resolvespec_common.SqlString `bun:"media_type,type:text,notnull," json:"media_type"`
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
ProjectID resolvespec_common.SqlUUID `bun:"project_id,type:uuid,nullzero," json:"project_id"` ProjectID resolvespec_common.SqlInt64 `bun:"project_id,type:bigint,nullzero," json:"project_id"`
Sha256 resolvespec_common.SqlString `bun:"sha256,type:text,notnull," json:"sha256"` Sha256 resolvespec_common.SqlString `bun:"sha256,type:text,notnull," json:"sha256"`
SizeBytes int64 `bun:"size_bytes,type:bigint,notnull," json:"size_bytes"` SizeBytes int64 `bun:"size_bytes,type:bigint,notnull," json:"size_bytes"`
ThoughtID resolvespec_common.SqlUUID `bun:"thought_id,type:uuid,nullzero," json:"thought_id"` ThoughtID resolvespec_common.SqlInt64 `bun:"thought_id,type:bigint,nullzero," json:"thought_id"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=guid" json:"relprojectid,omitempty"` // Has one ModelPublicProjects RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
RelThoughtID *ModelPublicThoughts `bun:"rel:has-one,join:thought_id=guid" json:"relthoughtid,omitempty"` // Has one ModelPublicThoughts RelThoughtID *ModelPublicThoughts `bun:"rel:has-one,join:thought_id=id" json:"relthoughtid,omitempty"` // Has one ModelPublicThoughts
} }
// TableName returns the table name for ModelPublicStoredFiles // TableName returns the table name for ModelPublicStoredFiles
@@ -48,7 +48,7 @@ func (m ModelPublicStoredFiles) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicStoredFiles) GetIDStr() string { func (m ModelPublicStoredFiles) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -9,7 +9,7 @@ import (
type ModelPublicThoughtLinks struct { type ModelPublicThoughtLinks struct {
bun.BaseModel `bun:"table:public.thought_links,alias:thought_links"` bun.BaseModel `bun:"table:public.thought_links,alias:thought_links"`
ID resolvespec_common.SqlInt32 `bun:"id,type:serial,pk,autoincrement," json:"id"` ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),nullzero," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),nullzero," json:"created_at"`
FromID int64 `bun:"from_id,type:bigint,notnull," json:"from_id"` FromID int64 `bun:"from_id,type:bigint,notnull," json:"from_id"`
Relation resolvespec_common.SqlString `bun:"relation,type:text,notnull," json:"relation"` Relation resolvespec_common.SqlString `bun:"relation,type:text,notnull," json:"relation"`
@@ -40,7 +40,7 @@ func (m ModelPublicThoughtLinks) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicThoughtLinks) GetIDStr() string { func (m ModelPublicThoughtLinks) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -14,15 +14,15 @@ type ModelPublicThoughts struct {
Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"` Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"`
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),nullzero," json:"created_at"` CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),nullzero," json:"created_at"`
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
Metadata resolvespec_common.SqlJSONB `bun:"metadata,type:jsonb,default:'{}::jsonb',nullzero," json:"metadata"` Metadata resolvespec_common.SqlJSONB `bun:"metadata,type:jsonb,default:{}::jsonb,nullzero," json:"metadata"`
ProjectID resolvespec_common.SqlUUID `bun:"project_id,type:uuid,nullzero," json:"project_id"` ProjectID resolvespec_common.SqlInt64 `bun:"project_id,type:bigint,nullzero," json:"project_id"`
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),nullzero," json:"updated_at"` UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),nullzero," json:"updated_at"`
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=guid" json:"relprojectid,omitempty"` // Has one ModelPublicProjects RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
RelFromIDPublicThoughtLinks []*ModelPublicThoughtLinks `bun:"rel:has-many,join:id=from_id" json:"relfromidpublicthoughtlinks,omitempty"` // Has many ModelPublicThoughtLinks RelFromIDPublicThoughtLinks []*ModelPublicThoughtLinks `bun:"rel:has-many,join:id=from_id" json:"relfromidpublicthoughtlinks,omitempty"` // Has many ModelPublicThoughtLinks
RelToIDPublicThoughtLinks []*ModelPublicThoughtLinks `bun:"rel:has-many,join:id=to_id" json:"reltoidpublicthoughtlinks,omitempty"` // Has many ModelPublicThoughtLinks RelToIDPublicThoughtLinks []*ModelPublicThoughtLinks `bun:"rel:has-many,join:id=to_id" json:"reltoidpublicthoughtlinks,omitempty"` // Has many ModelPublicThoughtLinks
RelThoughtIDPublicEmbeddings []*ModelPublicEmbeddings `bun:"rel:has-many,join:guid=thought_id" json:"relthoughtidpublicembeddings,omitempty"` // Has many ModelPublicEmbeddings RelThoughtIDPublicEmbeddings []*ModelPublicEmbeddings `bun:"rel:has-many,join:id=thought_id" json:"relthoughtidpublicembeddings,omitempty"` // Has many ModelPublicEmbeddings
RelThoughtIDPublicStoredFiles []*ModelPublicStoredFiles `bun:"rel:has-many,join:guid=thought_id" json:"relthoughtidpublicstoredfiles,omitempty"` // Has many ModelPublicStoredFiles RelThoughtIDPublicStoredFiles []*ModelPublicStoredFiles `bun:"rel:has-many,join:id=thought_id" json:"relthoughtidpublicstoredfiles,omitempty"` // Has many ModelPublicStoredFiles
RelRelatedThoughtIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:guid=related_thought_id" json:"relrelatedthoughtidpubliclearnings,omitempty"` // Has many ModelPublicLearnings RelRelatedThoughtIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:id=related_thought_id" json:"relrelatedthoughtidpubliclearnings,omitempty"` // Has many ModelPublicLearnings
} }
// TableName returns the table name for ModelPublicThoughts // TableName returns the table name for ModelPublicThoughts
@@ -47,7 +47,7 @@ func (m ModelPublicThoughts) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicThoughts) GetIDStr() string { func (m ModelPublicThoughts) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
@@ -38,7 +38,7 @@ func (m ModelPublicToolAnnotations) GetID() int64 {
// GetIDStr returns the primary key as a string // GetIDStr returns the primary key as a string
func (m ModelPublicToolAnnotations) GetIDStr() string { func (m ModelPublicToolAnnotations) GetIDStr() string {
return fmt.Sprintf("%v", m.ID) return m.ID.String()
} }
// SetID sets the primary key value // SetID sets the primary key value
+3 -6
View File
@@ -31,7 +31,7 @@ func TestSetToolSchemasAddsEmptyPropertiesForNoArgInput(t *testing.T) {
} }
} }
func TestSetToolSchemasUsesStringUUIDsInListOutput(t *testing.T) { func TestSetToolSchemasUsesIntegerIDsInListOutput(t *testing.T) {
tool := &mcp.Tool{Name: "list_thoughts"} tool := &mcp.Tool{Name: "list_thoughts"}
if err := setToolSchemas[tools.ListInput, tools.ListOutput](tool); err != nil { if err := setToolSchemas[tools.ListInput, tools.ListOutput](tool); err != nil {
@@ -55,11 +55,8 @@ func TestSetToolSchemasUsesStringUUIDsInListOutput(t *testing.T) {
if idSchema == nil { if idSchema == nil {
t.Fatal("missing id schema") t.Fatal("missing id schema")
} }
if idSchema.Type != "string" { if idSchema.Type != "integer" {
t.Fatalf("id schema type = %q, want %q", idSchema.Type, "string") t.Fatalf("id schema type = %q, want %q", idSchema.Type, "integer")
}
if idSchema.Format != "uuid" {
t.Fatalf("id schema format = %q, want %q", idSchema.Format, "uuid")
} }
} }
+398 -58
View File
@@ -1,6 +1,7 @@
package mcpserver package mcpserver
import ( import (
"context"
"log/slog" "log/slog"
"net/http" "net/http"
"strings" "strings"
@@ -18,29 +19,35 @@ const (
) )
type ToolSet struct { type ToolSet struct {
Version *tools.VersionTool Version *tools.VersionTool
Capture *tools.CaptureTool Capture *tools.CaptureTool
Search *tools.SearchTool Search *tools.SearchTool
List *tools.ListTool List *tools.ListTool
Stats *tools.StatsTool Stats *tools.StatsTool
Get *tools.GetTool Get *tools.GetTool
Update *tools.UpdateTool Update *tools.UpdateTool
Delete *tools.DeleteTool Delete *tools.DeleteTool
Archive *tools.ArchiveTool Archive *tools.ArchiveTool
Projects *tools.ProjectsTool DuplicateAudit *tools.DuplicateAuditTool
Context *tools.ContextTool Projects *tools.ProjectsTool
Recall *tools.RecallTool Context *tools.ContextTool
Summarize *tools.SummarizeTool Recall *tools.RecallTool
Links *tools.LinksTool Summarize *tools.SummarizeTool
Files *tools.FilesTool Links *tools.LinksTool
Backfill *tools.BackfillTool Files *tools.FilesTool
Reparse *tools.ReparseMetadataTool Backfill *tools.BackfillTool
RetryMetadata *tools.RetryEnrichmentTool Reparse *tools.ReparseMetadataTool
Maintenance *tools.MaintenanceTool RetryMetadata *tools.RetryEnrichmentTool
Skills *tools.SkillsTool //Maintenance *tools.MaintenanceTool
ChatHistory *tools.ChatHistoryTool Skills *tools.SkillsTool
Describe *tools.DescribeTool Personas *tools.AgentPersonasTool
Learnings *tools.LearningsTool ChatHistory *tools.ChatHistoryTool
Describe *tools.DescribeTool
Learnings *tools.LearningsTool
Plans *tools.PlansTool
ProjectPersonas *tools.ProjectPersonasTool
WorldModel *tools.WorldModelTool
ThoughtLearningLinks *tools.ThoughtLearningLinksTool
} }
// Handlers groups the HTTP handlers produced for an MCP server instance. // Handlers groups the HTTP handlers produced for an MCP server instance.
@@ -84,10 +91,14 @@ func NewHandlers(cfg config.MCPConfig, logger *slog.Logger, toolSet ToolSet, onS
registerSystemTools, registerSystemTools,
registerThoughtTools, registerThoughtTools,
registerProjectTools, registerProjectTools,
registerWorldModelTools,
registerLearningTools, registerLearningTools,
registerThoughtLearningLinkTools,
registerPlanTools,
registerFileTools, registerFileTools,
registerMaintenanceTools, registerMaintenanceTools,
registerSkillTools, registerSkillTools,
registerPersonaTools,
registerChatHistoryTools, registerChatHistoryTools,
registerDescribeTools, registerDescribeTools,
} { } {
@@ -119,6 +130,33 @@ func NewHandlers(cfg config.MCPConfig, logger *slog.Logger, toolSet ToolSet, onS
return h, nil return h, nil
} }
func registerWorldModelTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error {
server.AddResource(&mcp.Resource{
Name: "world_model_intro",
URI: "amcs://world-model/intro",
MIMEType: "text/markdown",
Description: "Mandatory startup instructions for discovering and loading the AMCS world model.",
}, func(_ context.Context, req *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
return &mcp.ReadResourceResult{Contents: []*mcp.ResourceContents{{URI: req.Params.URI, MIMEType: "text/markdown", Text: string(amcsllm.WorldModelIntro)}}}, nil
})
if err := addTool(server, logger, &mcp.Tool{Name: "bootstrap_world_model", Description: "Validate and activate a project, then return its effective skills, guardrails, personas, and bounded recent context."}, toolSet.WorldModel.Bootstrap); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "add_project_persona", Description: "Link a persona to a project and optionally make it the default."}, toolSet.ProjectPersonas.Add); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "remove_project_persona", Description: "Unlink a persona from a project."}, toolSet.ProjectPersonas.Remove); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "set_default_project_persona", Description: "Set a linked persona as the project's default persona."}, toolSet.ProjectPersonas.SetDefault); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "list_project_personas", Description: "List personas linked to a project, with the default marked."}, toolSet.ProjectPersonas.List); err != nil {
return err
}
return nil
}
// buildServerIcons returns icon definitions referencing the server's own /images/icon.png endpoint. // buildServerIcons returns icon definitions referencing the server's own /images/icon.png endpoint.
// Returns nil when publicURL is empty so the icons field is omitted from the MCP identity. // Returns nil when publicURL is empty so the icons field is omitted from the MCP identity.
func buildServerIcons(publicURL string) []mcp.Icon { func buildServerIcons(publicURL string) []mcp.Icon {
@@ -190,6 +228,12 @@ func registerThoughtTools(server *mcp.Server, logger *slog.Logger, toolSet ToolS
}, toolSet.Archive.Handle); err != nil { }, toolSet.Archive.Handle); err != nil {
return err return err
} }
if err := addTool(server, logger, &mcp.Tool{
Name: "audit_duplicates",
Description: "Dry-run duplicate audit for projects, thoughts, and metadata normalization candidates; performs no writes.",
}, toolSet.DuplicateAudit.Handle); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{ if err := addTool(server, logger, &mcp.Tool{
Name: "summarize_thoughts", Name: "summarize_thoughts",
Description: "LLM summary of a filtered set of thoughts.", Description: "LLM summary of a filtered set of thoughts.",
@@ -273,6 +317,128 @@ func registerLearningTools(server *mcp.Server, logger *slog.Logger, toolSet Tool
return nil return nil
} }
func registerThoughtLearningLinkTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error {
if err := addTool(server, logger, &mcp.Tool{
Name: "link_thought_learning",
Description: "Create or update an explicit association between a raw thought and a curated learning.",
}, toolSet.ThoughtLearningLinks.Link); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "unlink_thought_learning",
Description: "Remove an explicit association between a thought and a learning.",
}, toolSet.ThoughtLearningLinks.Unlink); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "get_thought_learnings",
Description: "List curated learnings explicitly linked to a thought.",
}, toolSet.ThoughtLearningLinks.GetThoughtLearnings); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "get_learning_thoughts",
Description: "List raw thoughts explicitly linked to a learning.",
}, toolSet.ThoughtLearningLinks.GetLearningThoughts); err != nil {
return err
}
return nil
}
func registerPlanTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error {
if err := addTool(server, logger, &mcp.Tool{
Name: "create_plan",
Description: "Create a structured plan linked to a project.",
}, toolSet.Plans.Create); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "get_plan",
Description: "Retrieve a plan with its dependencies, related plans, skills, and guardrails.",
}, toolSet.Plans.Get); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "update_plan",
Description: "Update plan fields; only provided fields are changed.",
}, toolSet.Plans.Update); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "delete_plan",
Description: "Hard-delete a plan by id.",
}, toolSet.Plans.Delete); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "list_plans",
Description: "List plans with optional project, status, priority, owner, tag, and text filters.",
}, toolSet.Plans.List); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "add_plan_dependency",
Description: "Mark plan_id as depending on depends_on_plan_id (must complete first).",
}, toolSet.Plans.AddDependency); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "remove_plan_dependency",
Description: "Remove a dependency between two plans.",
}, toolSet.Plans.RemoveDependency); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "add_related_plan",
Description: "Link two plans as thematically related (bidirectional).",
}, toolSet.Plans.AddRelated); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "remove_related_plan",
Description: "Unlink two related plans.",
}, toolSet.Plans.RemoveRelated); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "add_plan_skill",
Description: "Link an agent skill to a plan.",
}, toolSet.Plans.AddSkill); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "remove_plan_skill",
Description: "Unlink an agent skill from a plan.",
}, toolSet.Plans.RemoveSkill); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "list_plan_skills",
Description: "List skills linked to a plan.",
}, toolSet.Plans.ListSkills); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "add_plan_guardrail",
Description: "Link an agent guardrail to a plan.",
}, toolSet.Plans.AddGuardrail); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "remove_plan_guardrail",
Description: "Unlink an agent guardrail from a plan.",
}, toolSet.Plans.RemoveGuardrail); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{
Name: "list_plan_guardrails",
Description: "List guardrails linked to a plan.",
}, toolSet.Plans.ListGuardrails); err != nil {
return err
}
return nil
}
func registerFileTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { func registerFileTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error {
server.AddResourceTemplate(&mcp.ResourceTemplate{ server.AddResourceTemplate(&mcp.ResourceTemplate{
Name: "stored_file", Name: "stored_file",
@@ -326,30 +492,30 @@ func registerMaintenanceTools(server *mcp.Server, logger *slog.Logger, toolSet T
}, toolSet.RetryMetadata.Handle); err != nil { }, toolSet.RetryMetadata.Handle); err != nil {
return err return err
} }
if err := addTool(server, logger, &mcp.Tool{ // if err := addTool(server, logger, &mcp.Tool{
Name: "add_maintenance_task", // Name: "add_maintenance_task",
Description: "Create a recurring or one-time home maintenance task.", // Description: "Create a recurring or one-time home maintenance task.",
}, toolSet.Maintenance.AddTask); err != nil { // }, toolSet.Maintenance.AddTask); err != nil {
return err // return err
} // }
if err := addTool(server, logger, &mcp.Tool{ // if err := addTool(server, logger, &mcp.Tool{
Name: "log_maintenance", // Name: "log_maintenance",
Description: "Log completed maintenance; updates next due date.", // Description: "Log completed maintenance; updates next due date.",
}, toolSet.Maintenance.LogWork); err != nil { // }, toolSet.Maintenance.LogWork); err != nil {
return err // return err
} // }
if err := addTool(server, logger, &mcp.Tool{ // if err := addTool(server, logger, &mcp.Tool{
Name: "get_upcoming_maintenance", // Name: "get_upcoming_maintenance",
Description: "List maintenance tasks due within the next N days.", // Description: "List maintenance tasks due within the next N days.",
}, toolSet.Maintenance.GetUpcoming); err != nil { // }, toolSet.Maintenance.GetUpcoming); err != nil {
return err // return err
} // }
if err := addTool(server, logger, &mcp.Tool{ // if err := addTool(server, logger, &mcp.Tool{
Name: "search_maintenance_history", // Name: "search_maintenance_history",
Description: "Search the maintenance log by task name, category, or date range.", // Description: "Search the maintenance log by task name, category, or date range.",
}, toolSet.Maintenance.SearchHistory); err != nil { // }, toolSet.Maintenance.SearchHistory); err != nil {
return err // return err
} // }
return nil return nil
} }
@@ -368,10 +534,16 @@ func registerSkillTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet
} }
if err := addTool(server, logger, &mcp.Tool{ if err := addTool(server, logger, &mcp.Tool{
Name: "list_skills", Name: "list_skills",
Description: "List all agent skills, optionally filtered by tag.", Description: "List agent skills (metadata only by default). Set include_content=true to get full content, or use get_skill for a single skill.",
}, toolSet.Skills.ListSkills); err != nil { }, toolSet.Skills.ListSkills); err != nil {
return err return err
} }
if err := addTool(server, logger, &mcp.Tool{
Name: "get_skill",
Description: "Fetch a single agent skill with full content by id or name.",
}, toolSet.Skills.GetSkill); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{ if err := addTool(server, logger, &mcp.Tool{
Name: "add_guardrail", Name: "add_guardrail",
Description: "Store an agent guardrail (constraint or safety rule).", Description: "Store an agent guardrail (constraint or safety rule).",
@@ -390,6 +562,12 @@ func registerSkillTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet
}, toolSet.Skills.ListGuardrails); err != nil { }, toolSet.Skills.ListGuardrails); err != nil {
return err return err
} }
if err := addTool(server, logger, &mcp.Tool{
Name: "get_guardrail",
Description: "Fetch a single agent guardrail with full content by id or name.",
}, toolSet.Skills.GetGuardrail); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{ if err := addTool(server, logger, &mcp.Tool{
Name: "add_project_skill", Name: "add_project_skill",
Description: "Link a skill to a project. Pass project if client is stateless.", Description: "Link a skill to a project. Pass project if client is stateless.",
@@ -457,10 +635,114 @@ func registerChatHistoryTools(server *mcp.Server, logger *slog.Logger, toolSet T
return nil return nil
} }
func registerPersonaTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error {
p := toolSet.Personas
if err := addTool(server, logger, &mcp.Tool{Name: "create_agent_persona", Description: "Create a named, loadable agent persona."}, p.CreatePersona); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "update_agent_persona", Description: "Update persona fields (name, description, summary, detail, tags)."}, p.UpdatePersona); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "delete_agent_persona", Description: "Delete a persona by name."}, p.DeletePersona); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "list_agent_personas", Description: "List all personas, optionally filtered by tag."}, p.ListPersonas); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "get_agent_persona", Description: "Load a persona with assembled parts. detail=true returns full content. overrides replaces parts per type at runtime."}, p.GetPersona); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "get_persona_manifest", Description: "Lightweight structure-only view: parts, traits, skills, guardrails — no content. Includes on_demand_tools hints."}, p.GetPersonaManifest); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "compile_persona", Description: "Regenerate compiled_summary and compiled_detail from current parts and arc stage. Use compiled_summary for agents with tight context budgets."}, p.CompilePersona); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "create_agent_part", Description: "Create a reusable behaviour building block. Parts compose personas and can be overridden at load time by name."}, p.CreatePart); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "update_agent_part", Description: "Update part fields (name, part_type, description, summary, content, tags)."}, p.UpdatePart); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "delete_agent_part", Description: "Delete a part by name."}, p.DeletePart); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "list_agent_parts", Description: "List parts, optionally filtered by part_type or tag."}, p.ListParts); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "get_agent_part", Description: "Fetch a single part with full content by name."}, p.GetPart); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "add_persona_part", Description: "Link a part to a persona with optional order and priority."}, p.AddPersonaPart); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "remove_persona_part", Description: "Unlink a part from a persona."}, p.RemovePersonaPart); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "add_persona_skill", Description: "Link an agent_skill to a persona."}, p.AddPersonaSkill); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "remove_persona_skill", Description: "Unlink an agent_skill from a persona."}, p.RemovePersonaSkill); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "add_persona_guardrail", Description: "Link an agent_guardrail to a persona."}, p.AddPersonaGuardrail); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "remove_persona_guardrail", Description: "Unlink an agent_guardrail from a persona."}, p.RemovePersonaGuardrail); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "create_agent_trait", Description: "Create an atomic personality trait (personality, cognitive, emotional, social, behavioral)."}, p.CreateTrait); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "update_agent_trait", Description: "Update trait fields."}, p.UpdateTrait); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "delete_agent_trait", Description: "Delete a trait by name."}, p.DeleteTrait); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "list_agent_traits", Description: "List traits, optionally filtered by trait_type or tag."}, p.ListTraits); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "get_agent_trait", Description: "Fetch a single trait with instruction by name."}, p.GetTrait); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "add_persona_trait", Description: "Link a trait to a persona."}, p.AddPersonaTrait); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "remove_persona_trait", Description: "Unlink a trait from a persona."}, p.RemovePersonaTrait); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "create_character_arc", Description: "Define a named character progression arc."}, p.CreateCharacterArc); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "list_character_arcs", Description: "List all character arcs."}, p.ListCharacterArcs); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "add_arc_stage", Description: "Add an ordered stage to a character arc."}, p.AddArcStage); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "add_stage_part", Description: "Link a part to an arc stage — active when the persona is at that stage."}, p.AddStagePart); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "remove_stage_part", Description: "Unlink a part from an arc stage."}, p.RemoveStagePart); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "assign_persona_arc", Description: "Attach an arc to a persona and set the starting stage."}, p.AssignPersonaArc); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "advance_persona_stage", Description: "Move persona to the next stage in its arc."}, p.AdvancePersonaStage); err != nil {
return err
}
if err := addTool(server, logger, &mcp.Tool{Name: "reset_persona_stage", Description: "Reset persona to the first stage of its arc."}, p.ResetPersonaStage); err != nil {
return err
}
return nil
}
func registerDescribeTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { func registerDescribeTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error {
if err := addTool(server, logger, &mcp.Tool{ if err := addTool(server, logger, &mcp.Tool{
Name: "describe_tools", Name: "describe_tools",
Description: "Call first each session. All tools with categories and usage notes. Categories: system, thoughts, projects, files, admin, maintenance, skills, chat, meta.", Description: "Call first each session. All tools with categories and usage notes. Categories: system, thoughts, projects, files, admin, maintenance, skills, personas, plans, chat, meta.",
}, toolSet.Describe.Describe); err != nil { }, toolSet.Describe.Describe); err != nil {
return err return err
} }
@@ -489,6 +771,7 @@ func BuildToolCatalog() []tools.ToolEntry {
{Name: "update_thought", Description: "Update thought content or merge metadata.", Category: "thoughts"}, {Name: "update_thought", Description: "Update thought content or merge metadata.", Category: "thoughts"},
{Name: "delete_thought", Description: "Hard-delete a thought by id.", Category: "thoughts"}, {Name: "delete_thought", Description: "Hard-delete a thought by id.", Category: "thoughts"},
{Name: "archive_thought", Description: "Archive a thought so it is hidden from default search and listing.", Category: "thoughts"}, {Name: "archive_thought", Description: "Archive a thought so it is hidden from default search and listing.", Category: "thoughts"},
{Name: "audit_duplicates", Description: "Dry-run duplicate audit for exact/normalized project names, thought content, and metadata value variants. Reports candidates and recommendations; performs no writes.", Category: "admin"},
{Name: "summarize_thoughts", Description: "Produce an LLM prose summary of a filtered or searched set of thoughts.", Category: "thoughts"}, {Name: "summarize_thoughts", Description: "Produce an LLM prose summary of a filtered or searched set of thoughts.", Category: "thoughts"},
{Name: "recall_context", Description: "Recall semantically relevant and recent context for prompt injection. Combines vector similarity with recency. Falls back to full-text search when no embeddings exist.", Category: "thoughts"}, {Name: "recall_context", Description: "Recall semantically relevant and recent context for prompt injection. Combines vector similarity with recency. Falls back to full-text search when no embeddings exist.", Category: "thoughts"},
{Name: "link_thoughts", Description: "Create a typed relationship between two thoughts.", Category: "thoughts"}, {Name: "link_thoughts", Description: "Create a typed relationship between two thoughts.", Category: "thoughts"},
@@ -499,12 +782,38 @@ func BuildToolCatalog() []tools.ToolEntry {
{Name: "list_projects", Description: "List projects and their current thought counts.", Category: "projects"}, {Name: "list_projects", Description: "List projects and their current thought counts.", Category: "projects"},
{Name: "set_active_project", Description: "Set the active project for the current MCP session. Requires a stateful MCP client that reuses the same session across calls. If your client does not preserve sessions, pass project explicitly to each tool instead.", Category: "projects"}, {Name: "set_active_project", Description: "Set the active project for the current MCP session. Requires a stateful MCP client that reuses the same session across calls. If your client does not preserve sessions, pass project explicitly to each tool instead.", Category: "projects"},
{Name: "get_active_project", Description: "Return the active project for the current MCP session. If your client does not preserve MCP sessions, pass project explicitly to project-scoped tools instead of relying on this.", Category: "projects"}, {Name: "get_active_project", Description: "Return the active project for the current MCP session. If your client does not preserve MCP sessions, pass project explicitly to project-scoped tools instead of relying on this.", Category: "projects"},
{Name: "bootstrap_world_model", Description: "Validate and activate a project, then return effective skills, guardrails, personas, and bounded recent context.", Category: "projects"},
{Name: "add_project_persona", Description: "Link a persona to a project and optionally make it the default.", Category: "personas"},
{Name: "remove_project_persona", Description: "Unlink a persona from a project.", Category: "personas"},
{Name: "set_default_project_persona", Description: "Set a linked persona as the project's default persona.", Category: "personas"},
{Name: "list_project_personas", Description: "List personas linked to a project, with the default marked.", Category: "personas"},
{Name: "get_project_context", Description: "Get recent and semantic context for a project. Uses the explicit project when provided, otherwise the active MCP session project. Falls back to full-text search when no embeddings exist.", Category: "projects"}, {Name: "get_project_context", Description: "Get recent and semantic context for a project. Uses the explicit project when provided, otherwise the active MCP session project. Falls back to full-text search when no embeddings exist.", Category: "projects"},
// learnings // learnings
{Name: "add_learning", Description: "Create a curated learning record distinct from raw thoughts.", Category: "projects"}, {Name: "add_learning", Description: "Create a curated learning record distinct from raw thoughts.", Category: "projects"},
{Name: "get_learning", Description: "Retrieve a structured learning by id.", Category: "projects"}, {Name: "get_learning", Description: "Retrieve a structured learning by id.", Category: "projects"},
{Name: "list_learnings", Description: "List structured learnings with optional project, category, area, status, priority, tag, and text filters.", Category: "projects"}, {Name: "list_learnings", Description: "List structured learnings with optional project, category, area, status, priority, tag, and text filters.", Category: "projects"},
{Name: "link_thought_learning", Description: "Create or update a lightweight explicit association between a raw thought and a curated learning.", Category: "projects"},
{Name: "unlink_thought_learning", Description: "Remove a lightweight explicit association between a thought and a learning.", Category: "projects"},
{Name: "get_thought_learnings", Description: "List curated learnings explicitly associated with a thought.", Category: "projects"},
{Name: "get_learning_thoughts", Description: "List raw source thoughts explicitly associated with a learning.", Category: "projects"},
// plans
{Name: "create_plan", Description: "Create a structured plan with status, priority, owner, due date, and optional project link.", Category: "plans"},
{Name: "get_plan", Description: "Retrieve a full plan including dependencies (depends_on/blocks), related plans, linked skills, and guardrails.", Category: "plans"},
{Name: "update_plan", Description: "Partially update a plan; only provided fields are changed. Use mark_reviewed to stamp last_reviewed_at.", Category: "plans"},
{Name: "delete_plan", Description: "Hard-delete a plan by id.", Category: "plans"},
{Name: "list_plans", Description: "List plans with optional filters: project, status, priority, owner, tag, and full-text query.", Category: "plans"},
{Name: "add_plan_dependency", Description: "Declare that plan_id cannot proceed until depends_on_plan_id is complete.", Category: "plans"},
{Name: "remove_plan_dependency", Description: "Remove a directional dependency between two plans.", Category: "plans"},
{Name: "add_related_plan", Description: "Link two plans as thematically related (bidirectional, order-independent).", Category: "plans"},
{Name: "remove_related_plan", Description: "Unlink two related plans.", Category: "plans"},
{Name: "add_plan_skill", Description: "Link an agent skill to a plan so it is loaded with the plan's context.", Category: "plans"},
{Name: "remove_plan_skill", Description: "Unlink an agent skill from a plan.", Category: "plans"},
{Name: "list_plan_skills", Description: "List all skills linked to a plan.", Category: "plans"},
{Name: "add_plan_guardrail", Description: "Link an agent guardrail to a plan so it applies during plan execution.", Category: "plans"},
{Name: "remove_plan_guardrail", Description: "Unlink an agent guardrail from a plan.", Category: "plans"},
{Name: "list_plan_guardrails", Description: "List all guardrails linked to a plan.", Category: "plans"},
// files // files
{Name: "upload_file", Description: "Stage a file and get an amcs://files/{id} resource URI. Use content_path (absolute server-side path, no size limit) for large or binary files, or content_base64 (≤10 MB) for small files. Pass thought_id/project to link immediately, or omit and pass the URI to save_file later.", Category: "files"}, {Name: "upload_file", Description: "Stage a file and get an amcs://files/{id} resource URI. Use content_path (absolute server-side path, no size limit) for large or binary files, or content_base64 (≤10 MB) for small files. Pass thought_id/project to link immediately, or omit and pass the URI to save_file later.", Category: "files"},
@@ -517,19 +826,15 @@ func BuildToolCatalog() []tools.ToolEntry {
{Name: "reparse_thought_metadata", Description: "Re-extract and normalize metadata for stored thoughts from their content.", Category: "admin"}, {Name: "reparse_thought_metadata", Description: "Re-extract and normalize metadata for stored thoughts from their content.", Category: "admin"},
{Name: "retry_failed_metadata", Description: "Retry metadata extraction for thoughts still marked pending or failed.", Category: "admin"}, {Name: "retry_failed_metadata", Description: "Retry metadata extraction for thoughts still marked pending or failed.", Category: "admin"},
// maintenance
{Name: "add_maintenance_task", Description: "Create a recurring or one-time home maintenance task.", Category: "maintenance"},
{Name: "log_maintenance", Description: "Log completed maintenance work; automatically updates the task's next due date.", Category: "maintenance"},
{Name: "get_upcoming_maintenance", Description: "List maintenance tasks due within the next N days.", Category: "maintenance"},
{Name: "search_maintenance_history", Description: "Search the maintenance log by task name, category, or date range.", Category: "maintenance"},
// skills // skills
{Name: "add_skill", Description: "Store a reusable agent skill (behavioural instruction or capability prompt).", Category: "skills"}, {Name: "add_skill", Description: "Store a reusable agent skill. Supports language_tags, library_tags, framework_tags, and domain_tags for precise retrieval.", Category: "skills"},
{Name: "remove_skill", Description: "Delete an agent skill by id.", Category: "skills"}, {Name: "remove_skill", Description: "Delete an agent skill by id.", Category: "skills"},
{Name: "list_skills", Description: "List all agent skills, optionally filtered by tag.", Category: "skills"}, {Name: "list_skills", Description: "List agent skills (metadata only by default). Filter by tag (searches all tag fields). Set include_content=true for full bodies, or use get_skill to load one.", Category: "skills"},
{Name: "get_skill", Description: "Fetch a single agent skill with full content by id or name. Prefer this over list_skills when you know which skill you need.", Category: "skills"},
{Name: "add_guardrail", Description: "Store a reusable agent guardrail (constraint or safety rule).", Category: "skills"}, {Name: "add_guardrail", Description: "Store a reusable agent guardrail (constraint or safety rule).", Category: "skills"},
{Name: "remove_guardrail", Description: "Delete an agent guardrail by id.", Category: "skills"}, {Name: "remove_guardrail", Description: "Delete an agent guardrail by id.", Category: "skills"},
{Name: "list_guardrails", Description: "List all agent guardrails, optionally filtered by tag or severity.", Category: "skills"}, {Name: "list_guardrails", Description: "List all agent guardrails, optionally filtered by tag or severity.", Category: "skills"},
{Name: "get_guardrail", Description: "Fetch a single agent guardrail with full content by id or name.", Category: "skills"},
{Name: "add_project_skill", Description: "Link an agent skill to a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "add_project_skill", Description: "Link an agent skill to a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"},
{Name: "remove_project_skill", Description: "Unlink an agent skill from a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "remove_project_skill", Description: "Unlink an agent skill from a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"},
{Name: "list_project_skills", Description: "List all skills linked to a project. Call this at the start of every project session to load agent behaviour instructions before generating new ones. Only create new skills if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "list_project_skills", Description: "List all skills linked to a project. Call this at the start of every project session to load agent behaviour instructions before generating new ones. Only create new skills if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"},
@@ -537,6 +842,41 @@ func BuildToolCatalog() []tools.ToolEntry {
{Name: "remove_project_guardrail", Description: "Unlink an agent guardrail from a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "remove_project_guardrail", Description: "Unlink an agent guardrail from a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"},
{Name: "list_project_guardrails", Description: "List all guardrails linked to a project. Call this at the start of every project session to load agent constraints before generating new ones. Only create new guardrails if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "list_project_guardrails", Description: "List all guardrails linked to a project. Call this at the start of every project session to load agent constraints before generating new ones. Only create new guardrails if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"},
// personas
{Name: "create_agent_persona", Description: "Create a named, loadable agent persona that composes parts, traits, skills, and guardrails.", Category: "personas"},
{Name: "update_agent_persona", Description: "Update persona fields.", Category: "personas"},
{Name: "delete_agent_persona", Description: "Delete a persona by name.", Category: "personas"},
{Name: "list_agent_personas", Description: "List all personas, optionally filtered by tag.", Category: "personas"},
{Name: "get_agent_persona", Description: "Load a persona with all assembled parts. detail=true returns full content. overrides replaces parts per type at runtime without modifying the persona.", Category: "personas"},
{Name: "get_persona_manifest", Description: "Lightweight discovery: returns persona structure (parts, traits, skills, guardrails) with no content, plus on_demand_tools hints. Call this first for unknown personas.", Category: "personas"},
{Name: "compile_persona", Description: "Regenerate compiled_summary and compiled_detail from current parts and arc stage. Agents with tight context budgets can use compiled_summary directly.", Category: "personas"},
{Name: "create_agent_part", Description: "Create a reusable behaviour building block. Part types: system, agent, soul, identity, skill, specialization, tone, goal, context, protocol, backstory, motivation, voice, archetype, flaw, relationship.", Category: "personas"},
{Name: "update_agent_part", Description: "Update part fields.", Category: "personas"},
{Name: "delete_agent_part", Description: "Delete a part by name.", Category: "personas"},
{Name: "list_agent_parts", Description: "List parts, optionally filtered by part_type or tag.", Category: "personas"},
{Name: "get_agent_part", Description: "Fetch a single part with full content by name.", Category: "personas"},
{Name: "add_persona_part", Description: "Link a part to a persona with optional order and priority.", Category: "personas"},
{Name: "remove_persona_part", Description: "Unlink a part from a persona.", Category: "personas"},
{Name: "add_persona_skill", Description: "Link an agent_skill to a persona.", Category: "personas"},
{Name: "remove_persona_skill", Description: "Unlink an agent_skill from a persona.", Category: "personas"},
{Name: "add_persona_guardrail", Description: "Link an agent_guardrail to a persona.", Category: "personas"},
{Name: "remove_persona_guardrail", Description: "Unlink an agent_guardrail from a persona.", Category: "personas"},
{Name: "create_agent_trait", Description: "Create an atomic personality trait. Trait types: personality, cognitive, emotional, social, behavioral.", Category: "personas"},
{Name: "update_agent_trait", Description: "Update trait fields.", Category: "personas"},
{Name: "delete_agent_trait", Description: "Delete a trait by name.", Category: "personas"},
{Name: "list_agent_traits", Description: "List traits, optionally filtered by trait_type or tag.", Category: "personas"},
{Name: "get_agent_trait", Description: "Fetch a single trait with instruction by name. Use this for on-demand trait loading.", Category: "personas"},
{Name: "add_persona_trait", Description: "Link a trait to a persona.", Category: "personas"},
{Name: "remove_persona_trait", Description: "Unlink a trait from a persona.", Category: "personas"},
{Name: "create_character_arc", Description: "Define a named character progression arc with ordered stages.", Category: "personas"},
{Name: "list_character_arcs", Description: "List all character arcs.", Category: "personas"},
{Name: "add_arc_stage", Description: "Add an ordered stage to a character arc.", Category: "personas"},
{Name: "add_stage_part", Description: "Link a part to an arc stage — overrides matching persona parts when that stage is active.", Category: "personas"},
{Name: "remove_stage_part", Description: "Unlink a part from an arc stage.", Category: "personas"},
{Name: "assign_persona_arc", Description: "Attach a character arc to a persona and set the starting stage.", Category: "personas"},
{Name: "advance_persona_stage", Description: "Move persona to the next stage in its arc.", Category: "personas"},
{Name: "reset_persona_stage", Description: "Reset persona to the first stage of its arc.", Category: "personas"},
// chat // chat
{Name: "save_chat_history", Description: "Save a chat session's message history for later retrieval. Stores messages with optional title, summary, channel, agent, and project metadata.", Category: "chat"}, {Name: "save_chat_history", Description: "Save a chat session's message history for later retrieval. Stores messages with optional title, summary, channel, agent, and project metadata.", Category: "chat"},
{Name: "get_chat_history", Description: "Retrieve a saved chat history by its UUID or session_id. Returns the full message list.", Category: "chat"}, {Name: "get_chat_history", Description: "Retrieve a saved chat history by its UUID or session_id. Returns the full message list.", Category: "chat"},
@@ -544,7 +884,7 @@ func BuildToolCatalog() []tools.ToolEntry {
{Name: "delete_chat_history", Description: "Permanently delete a saved chat history by id.", Category: "chat"}, {Name: "delete_chat_history", Description: "Permanently delete a saved chat history by id.", Category: "chat"},
// meta // meta
{Name: "describe_tools", Description: "Call this first in every session. Returns all available MCP tools with names, descriptions, categories, and your accumulated usage notes. Filter by category to narrow results. Available categories: system, thoughts, projects, files, admin, household, maintenance, calendar, meals, crm, skills, chat, meta.", Category: "meta"}, {Name: "describe_tools", Description: "Call this first in every session. Returns all available MCP tools with names, descriptions, categories, and your accumulated usage notes. Filter by category to narrow results. Available categories: system, thoughts, projects, files, admin, household, maintenance, calendar, meals, crm, skills, personas, plans, chat, meta.", Category: "meta"},
{Name: "annotate_tool", Description: "Persist usage notes, gotchas, or workflow patterns for a specific tool. Notes survive across sessions and are returned by describe_tools. Call this whenever you discover something non-obvious about a tool's behaviour. Pass an empty string to clear notes.", Category: "meta"}, {Name: "annotate_tool", Description: "Persist usage notes, gotchas, or workflow patterns for a specific tool. Notes survive across sessions and are returned by describe_tools. Call this whenever you discover something non-obvious about a tool's behaviour. Pass an empty string to clear notes.", Category: "meta"},
} }
} }
+25 -51
View File
@@ -5,6 +5,7 @@ import (
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
"sort" "sort"
"strings"
"testing" "testing"
"time" "time"
@@ -27,57 +28,10 @@ func TestNewListsAllRegisteredTools(t *testing.T) {
} }
sort.Strings(got) sort.Strings(got)
want := []string{ catalog := BuildToolCatalog()
"add_guardrail", want := make([]string, 0, len(catalog))
"add_learning", for _, tool := range catalog {
"add_maintenance_task", want = append(want, tool.Name)
"add_project_guardrail",
"add_project_skill",
"add_skill",
"annotate_tool",
"archive_thought",
"backfill_embeddings",
"capture_thought",
"create_project",
"delete_chat_history",
"delete_thought",
"describe_tools",
"get_active_project",
"get_chat_history",
"get_learning",
"get_project_context",
"get_thought",
"get_upcoming_maintenance",
"get_version_info",
"link_thoughts",
"list_chat_histories",
"list_files",
"list_guardrails",
"list_learnings",
"list_project_guardrails",
"list_project_skills",
"list_projects",
"list_skills",
"list_thoughts",
"load_file",
"log_maintenance",
"recall_context",
"related_thoughts",
"remove_guardrail",
"remove_project_guardrail",
"remove_project_skill",
"remove_skill",
"reparse_thought_metadata",
"retry_failed_metadata",
"save_chat_history",
"save_file",
"search_maintenance_history",
"search_thoughts",
"set_active_project",
"summarize_thoughts",
"thought_stats",
"update_thought",
"upload_file",
} }
sort.Strings(want) sort.Strings(want)
@@ -107,6 +61,26 @@ func TestNewListsStoredFileResourceTemplate(t *testing.T) {
} }
} }
func TestNewExposesWorldModelIntroResource(t *testing.T) {
cs := newStreamableTestClient(t)
listed, err := cs.ListResources(context.Background(), nil)
if err != nil {
t.Fatalf("ListResources() error = %v", err)
}
if len(listed.Resources) != 1 || listed.Resources[0].URI != "amcs://world-model/intro" {
t.Fatalf("ListResources() = %#v, want world model intro", listed.Resources)
}
read, err := cs.ReadResource(context.Background(), &mcp.ReadResourceParams{URI: "amcs://world-model/intro"})
if err != nil {
t.Fatalf("ReadResource() error = %v", err)
}
if len(read.Contents) != 1 || !strings.Contains(read.Contents[0].Text, "bootstrap_world_model") {
t.Fatalf("ReadResource() did not return bootstrap instructions: %#v", read.Contents)
}
}
func newStreamableTestClient(t *testing.T) *mcp.ClientSession { func newStreamableTestClient(t *testing.T) *mcp.ClientSession {
t.Helper() t.Helper()
@@ -189,25 +189,31 @@ func TestStreamableHTTPReturnsStructuredToolErrors(t *testing.T) {
func streamableTestToolSet() ToolSet { func streamableTestToolSet() ToolSet {
return ToolSet{ return ToolSet{
Version: tools.NewVersionTool("test", buildinfo.Info{Version: "0.0.1", TagName: "v0.0.1", Commit: "test", BuildDate: "2026-03-31T00:00:00Z"}), Version: tools.NewVersionTool("test", buildinfo.Info{Version: "0.0.1", TagName: "v0.0.1", Commit: "test", BuildDate: "2026-03-31T00:00:00Z"}),
Capture: new(tools.CaptureTool), Capture: new(tools.CaptureTool),
Search: new(tools.SearchTool), Search: new(tools.SearchTool),
List: new(tools.ListTool), List: new(tools.ListTool),
Stats: new(tools.StatsTool), Stats: new(tools.StatsTool),
Get: new(tools.GetTool), Get: new(tools.GetTool),
Update: new(tools.UpdateTool), Update: new(tools.UpdateTool),
Delete: new(tools.DeleteTool), Delete: new(tools.DeleteTool),
Archive: new(tools.ArchiveTool), Archive: new(tools.ArchiveTool),
Projects: new(tools.ProjectsTool), Projects: new(tools.ProjectsTool),
Context: new(tools.ContextTool), Context: new(tools.ContextTool),
Recall: new(tools.RecallTool), Recall: new(tools.RecallTool),
Summarize: new(tools.SummarizeTool), Summarize: new(tools.SummarizeTool),
Links: new(tools.LinksTool), Links: new(tools.LinksTool),
Files: new(tools.FilesTool), Files: new(tools.FilesTool),
Backfill: new(tools.BackfillTool), Backfill: new(tools.BackfillTool),
Reparse: new(tools.ReparseMetadataTool), Reparse: new(tools.ReparseMetadataTool),
RetryMetadata: new(tools.RetryEnrichmentTool), RetryMetadata: new(tools.RetryEnrichmentTool),
Maintenance: new(tools.MaintenanceTool), Skills: new(tools.SkillsTool),
Skills: new(tools.SkillsTool), ChatHistory: new(tools.ChatHistoryTool),
Describe: new(tools.DescribeTool),
Learnings: new(tools.LearningsTool),
Plans: new(tools.PlansTool),
ProjectPersonas: new(tools.ProjectPersonasTool),
WorldModel: new(tools.WorldModelTool),
ThoughtLearningLinks: new(tools.ThoughtLearningLinksTool),
} }
} }
File diff suppressed because it is too large Load Diff
+182 -182
View File
@@ -1,206 +1,206 @@
package store package store
import ( // import (
"context" // "context"
"fmt" // "fmt"
"strings" // "strings"
"time" // "time"
"github.com/google/uuid" // "github.com/google/uuid"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" // "git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types" // ext "git.warky.dev/wdevs/amcs/internal/types"
) // )
func (db *DB) AddFamilyMember(ctx context.Context, m ext.FamilyMember) (ext.FamilyMember, error) { // func (db *DB) AddFamilyMember(ctx context.Context, m ext.FamilyMember) (ext.FamilyMember, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into family_members (name, relationship, birth_date, notes) // insert into family_members (name, relationship, birth_date, notes)
values ($1, $2, $3, $4) // values ($1, $2, $3, $4)
returning id, created_at // returning id, created_at
`, m.Name, nullStr(m.Relationship), m.BirthDate, nullStr(m.Notes)) // `, m.Name, nullStr(m.Relationship), m.BirthDate, nullStr(m.Notes))
created := m // created := m
var model generatedmodels.ModelPublicFamilyMembers // var model generatedmodels.ModelPublicFamilyMembers
if err := row.Scan(&model.ID, &model.CreatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt); err != nil {
return ext.FamilyMember{}, fmt.Errorf("insert family member: %w", err) // return ext.FamilyMember{}, fmt.Errorf("insert family member: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) ListFamilyMembers(ctx context.Context) ([]ext.FamilyMember, error) { // func (db *DB) ListFamilyMembers(ctx context.Context) ([]ext.FamilyMember, error) {
rows, err := db.pool.Query(ctx, `select id, name, relationship, birth_date, notes, created_at from family_members order by name`) // rows, err := db.pool.Query(ctx, `select id, name, relationship, birth_date, notes, created_at from family_members order by name`)
if err != nil { // if err != nil {
return nil, fmt.Errorf("list family members: %w", err) // return nil, fmt.Errorf("list family members: %w", err)
} // }
defer rows.Close() // defer rows.Close()
var members []ext.FamilyMember // var members []ext.FamilyMember
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicFamilyMembers // var model generatedmodels.ModelPublicFamilyMembers
if err := rows.Scan(&model.ID, &model.Name, &model.Relationship, &model.BirthDate, &model.Notes, &model.CreatedAt); err != nil { // if err := rows.Scan(&model.ID, &model.Name, &model.Relationship, &model.BirthDate, &model.Notes, &model.CreatedAt); err != nil {
return nil, fmt.Errorf("scan family member: %w", err) // return nil, fmt.Errorf("scan family member: %w", err)
} // }
members = append(members, familyMemberFromModel(model)) // members = append(members, familyMemberFromModel(model))
} // }
return members, rows.Err() // return members, rows.Err()
} // }
func (db *DB) AddActivity(ctx context.Context, a ext.Activity) (ext.Activity, error) { // func (db *DB) AddActivity(ctx context.Context, a ext.Activity) (ext.Activity, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into activities (family_member_id, title, activity_type, day_of_week, start_time, end_time, start_date, end_date, location, notes) // insert into activities (family_member_id, title, activity_type, day_of_week, start_time, end_time, start_date, end_date, location, notes)
values ($1, $2, $3, $4, $5::time, $6::time, $7, $8, $9, $10) // values ($1, $2, $3, $4, $5::time, $6::time, $7, $8, $9, $10)
returning id, created_at // returning id, created_at
`, a.FamilyMemberID, a.Title, nullStr(a.ActivityType), nullStr(a.DayOfWeek), // `, a.FamilyMemberID, a.Title, nullStr(a.ActivityType), nullStr(a.DayOfWeek),
nullStr(a.StartTime), nullStr(a.EndTime), a.StartDate, a.EndDate, // nullStr(a.StartTime), nullStr(a.EndTime), a.StartDate, a.EndDate,
nullStr(a.Location), nullStr(a.Notes)) // nullStr(a.Location), nullStr(a.Notes))
created := a // created := a
var model generatedmodels.ModelPublicActivities // var model generatedmodels.ModelPublicActivities
if err := row.Scan(&model.ID, &model.CreatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt); err != nil {
return ext.Activity{}, fmt.Errorf("insert activity: %w", err) // return ext.Activity{}, fmt.Errorf("insert activity: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) GetWeekSchedule(ctx context.Context, weekStart time.Time) ([]ext.Activity, error) { // func (db *DB) GetWeekSchedule(ctx context.Context, weekStart time.Time) ([]ext.Activity, error) {
weekEnd := weekStart.AddDate(0, 0, 7) // weekEnd := weekStart.AddDate(0, 0, 7)
rows, err := db.pool.Query(ctx, ` // rows, err := db.pool.Query(ctx, `
select a.id, a.family_member_id, fm.name, a.title, a.activity_type, // select a.id, a.family_member_id, fm.name, a.title, a.activity_type,
a.day_of_week, a.start_time::text, a.end_time::text, // a.day_of_week, a.start_time::text, a.end_time::text,
a.start_date, a.end_date, a.location, a.notes, a.created_at // a.start_date, a.end_date, a.location, a.notes, a.created_at
from activities a // from activities a
left join family_members fm on fm.id = a.family_member_id // left join family_members fm on fm.id = a.family_member_id
where (a.start_date >= $1 and a.start_date < $2) // where (a.start_date >= $1 and a.start_date < $2)
or (a.day_of_week is not null and (a.end_date is null or a.end_date >= $1)) // or (a.day_of_week is not null and (a.end_date is null or a.end_date >= $1))
order by a.start_date, a.start_time // order by a.start_date, a.start_time
`, weekStart, weekEnd) // `, weekStart, weekEnd)
if err != nil { // if err != nil {
return nil, fmt.Errorf("get week schedule: %w", err) // return nil, fmt.Errorf("get week schedule: %w", err)
} // }
defer rows.Close() // defer rows.Close()
return scanActivities(rows) // return scanActivities(rows)
} // }
func (db *DB) SearchActivities(ctx context.Context, query, activityType string, memberID *uuid.UUID) ([]ext.Activity, error) { // func (db *DB) SearchActivities(ctx context.Context, query, activityType string, memberID *uuid.UUID) ([]ext.Activity, error) {
args := []any{} // args := []any{}
conditions := []string{} // conditions := []string{}
if q := strings.TrimSpace(query); q != "" { // if q := strings.TrimSpace(query); q != "" {
args = append(args, "%"+q+"%") // args = append(args, "%"+q+"%")
conditions = append(conditions, fmt.Sprintf("(a.title ILIKE $%d OR a.notes ILIKE $%d)", len(args), len(args))) // conditions = append(conditions, fmt.Sprintf("(a.title ILIKE $%d OR a.notes ILIKE $%d)", len(args), len(args)))
} // }
if t := strings.TrimSpace(activityType); t != "" { // if t := strings.TrimSpace(activityType); t != "" {
args = append(args, t) // args = append(args, t)
conditions = append(conditions, fmt.Sprintf("a.activity_type = $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("a.activity_type = $%d", len(args)))
} // }
if memberID != nil { // if memberID != nil {
args = append(args, *memberID) // args = append(args, *memberID)
conditions = append(conditions, fmt.Sprintf("a.family_member_id = $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("a.family_member_id = $%d", len(args)))
} // }
q := ` // q := `
select a.id, a.family_member_id, fm.name, a.title, a.activity_type, // select a.id, a.family_member_id, fm.name, a.title, a.activity_type,
a.day_of_week, a.start_time::text, a.end_time::text, // a.day_of_week, a.start_time::text, a.end_time::text,
a.start_date, a.end_date, a.location, a.notes, a.created_at // a.start_date, a.end_date, a.location, a.notes, a.created_at
from activities a // from activities a
left join family_members fm on fm.id = a.family_member_id // left join family_members fm on fm.id = a.family_member_id
` // `
if len(conditions) > 0 { // if len(conditions) > 0 {
q += " where " + strings.Join(conditions, " and ") // q += " where " + strings.Join(conditions, " and ")
} // }
q += " order by a.start_date, a.start_time" // q += " order by a.start_date, a.start_time"
rows, err := db.pool.Query(ctx, q, args...) // rows, err := db.pool.Query(ctx, q, args...)
if err != nil { // if err != nil {
return nil, fmt.Errorf("search activities: %w", err) // return nil, fmt.Errorf("search activities: %w", err)
} // }
defer rows.Close() // defer rows.Close()
return scanActivities(rows) // return scanActivities(rows)
} // }
func (db *DB) AddImportantDate(ctx context.Context, d ext.ImportantDate) (ext.ImportantDate, error) { // func (db *DB) AddImportantDate(ctx context.Context, d ext.ImportantDate) (ext.ImportantDate, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into important_dates (family_member_id, title, date_value, recurring_yearly, reminder_days_before, notes) // insert into important_dates (family_member_id, title, date_value, recurring_yearly, reminder_days_before, notes)
values ($1, $2, $3, $4, $5, $6) // values ($1, $2, $3, $4, $5, $6)
returning id, created_at // returning id, created_at
`, d.FamilyMemberID, d.Title, d.DateValue, d.RecurringYearly, d.ReminderDaysBefore, nullStr(d.Notes)) // `, d.FamilyMemberID, d.Title, d.DateValue, d.RecurringYearly, d.ReminderDaysBefore, nullStr(d.Notes))
created := d // created := d
var model generatedmodels.ModelPublicImportantDates // var model generatedmodels.ModelPublicImportantDates
if err := row.Scan(&model.ID, &model.CreatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt); err != nil {
return ext.ImportantDate{}, fmt.Errorf("insert important date: %w", err) // return ext.ImportantDate{}, fmt.Errorf("insert important date: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) GetUpcomingDates(ctx context.Context, daysAhead int) ([]ext.ImportantDate, error) { // func (db *DB) GetUpcomingDates(ctx context.Context, daysAhead int) ([]ext.ImportantDate, error) {
if daysAhead <= 0 { // if daysAhead <= 0 {
daysAhead = 30 // daysAhead = 30
} // }
now := time.Now() // now := time.Now()
cutoff := now.AddDate(0, 0, daysAhead) // cutoff := now.AddDate(0, 0, daysAhead)
// For yearly recurring events, check if this year's occurrence falls in range // // For yearly recurring events, check if this year's occurrence falls in range
rows, err := db.pool.Query(ctx, ` // rows, err := db.pool.Query(ctx, `
select d.id, d.family_member_id, fm.name, d.title, d.date_value, // select d.id, d.family_member_id, fm.name, d.title, d.date_value,
d.recurring_yearly, d.reminder_days_before, d.notes, d.created_at // d.recurring_yearly, d.reminder_days_before, d.notes, d.created_at
from important_dates d // from important_dates d
left join family_members fm on fm.id = d.family_member_id // left join family_members fm on fm.id = d.family_member_id
where ( // where (
(d.recurring_yearly = false and d.date_value between $1 and $2) // (d.recurring_yearly = false and d.date_value between $1 and $2)
or // or
(d.recurring_yearly = true and // (d.recurring_yearly = true and
make_date(extract(year from now())::int, extract(month from d.date_value)::int, extract(day from d.date_value)::int) // make_date(extract(year from now())::int, extract(month from d.date_value)::int, extract(day from d.date_value)::int)
between $1 and $2) // between $1 and $2)
) // )
order by d.date_value // order by d.date_value
`, now, cutoff) // `, now, cutoff)
if err != nil { // if err != nil {
return nil, fmt.Errorf("get upcoming dates: %w", err) // return nil, fmt.Errorf("get upcoming dates: %w", err)
} // }
defer rows.Close() // defer rows.Close()
var dates []ext.ImportantDate // var dates []ext.ImportantDate
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicImportantDates // var model generatedmodels.ModelPublicImportantDates
var memberName *string // var memberName *string
if err := rows.Scan(&model.ID, &model.FamilyMemberID, &memberName, &model.Title, &model.DateValue, // if err := rows.Scan(&model.ID, &model.FamilyMemberID, &memberName, &model.Title, &model.DateValue,
&model.RecurringYearly, &model.ReminderDaysBefore, &model.Notes, &model.CreatedAt); err != nil { // &model.RecurringYearly, &model.ReminderDaysBefore, &model.Notes, &model.CreatedAt); err != nil {
return nil, fmt.Errorf("scan important date: %w", err) // return nil, fmt.Errorf("scan important date: %w", err)
} // }
dates = append(dates, importantDateFromModel(model, strVal(memberName))) // dates = append(dates, importantDateFromModel(model, strVal(memberName)))
} // }
return dates, rows.Err() // return dates, rows.Err()
} // }
func scanActivities(rows interface { // func scanActivities(rows interface {
Next() bool // Next() bool
Scan(...any) error // Scan(...any) error
Err() error // Err() error
Close() // Close()
}) ([]ext.Activity, error) { // }) ([]ext.Activity, error) {
defer rows.Close() // defer rows.Close()
var activities []ext.Activity // var activities []ext.Activity
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicActivities // var model generatedmodels.ModelPublicActivities
var memberName *string // var memberName *string
if err := rows.Scan( // if err := rows.Scan(
&model.ID, &model.FamilyMemberID, &memberName, &model.Title, &model.ActivityType, // &model.ID, &model.FamilyMemberID, &memberName, &model.Title, &model.ActivityType,
&model.DayOfWeek, &model.StartTime, &model.EndTime, // &model.DayOfWeek, &model.StartTime, &model.EndTime,
&model.StartDate, &model.EndDate, &model.Location, &model.Notes, &model.CreatedAt, // &model.StartDate, &model.EndDate, &model.Location, &model.Notes, &model.CreatedAt,
); err != nil { // ); err != nil {
return nil, fmt.Errorf("scan activity: %w", err) // return nil, fmt.Errorf("scan activity: %w", err)
} // }
activities = append(activities, activityFromModel(model, strVal(memberName))) // activities = append(activities, activityFromModel(model, strVal(memberName)))
} // }
return activities, rows.Err() // return activities, rows.Err()
} // }
+10 -9
View File
@@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" "git.warky.dev/wdevs/amcs/internal/generatedmodels"
@@ -28,7 +27,7 @@ func (db *DB) SaveChatHistory(ctx context.Context, h ext.ChatHistory) (ext.ChatH
insert into chat_histories insert into chat_histories
(session_id, title, channel, agent_id, project_id, messages, summary, metadata) (session_id, title, channel, agent_id, project_id, messages, summary, metadata)
values ($1, $2, $3, $4, $5, $6, $7, $8) values ($1, $2, $3, $4, $5, $6, $7, $8)
returning id, created_at, updated_at returning id, guid, created_at, updated_at
`, `,
h.SessionID, nullStr(h.Title), nullStr(h.Channel), nullStr(h.AgentID), h.SessionID, nullStr(h.Title), nullStr(h.Channel), nullStr(h.AgentID),
h.ProjectID, messages, nullStr(h.Summary), meta, h.ProjectID, messages, nullStr(h.Summary), meta,
@@ -36,16 +35,17 @@ func (db *DB) SaveChatHistory(ctx context.Context, h ext.ChatHistory) (ext.ChatH
created := h created := h
var model generatedmodels.ModelPublicChatHistories var model generatedmodels.ModelPublicChatHistories
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { if err := row.Scan(&model.ID, &model.GUID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.ChatHistory{}, fmt.Errorf("insert chat history: %w", err) return ext.ChatHistory{}, fmt.Errorf("insert chat history: %w", err)
} }
created.ID = model.ID.UUID() created.ID = model.ID.Int64()
created.GUID = model.GUID.UUID()
created.CreatedAt = model.CreatedAt.Time() created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() created.UpdatedAt = model.UpdatedAt.Time()
return created, nil return created, nil
} }
func (db *DB) GetChatHistory(ctx context.Context, id uuid.UUID) (ext.ChatHistory, bool, error) { func (db *DB) GetChatHistory(ctx context.Context, id int64) (ext.ChatHistory, bool, error) {
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
select id, session_id, title, channel, agent_id, project_id, select id, session_id, title, channel, agent_id, project_id,
messages, summary, metadata, created_at, updated_at messages, summary, metadata, created_at, updated_at
@@ -81,7 +81,7 @@ func (db *DB) GetChatHistoryBySessionID(ctx context.Context, sessionID string) (
} }
type ListChatHistoriesFilter struct { type ListChatHistoriesFilter struct {
ProjectID *uuid.UUID ProjectID *int64
Channel string Channel string
AgentID string AgentID string
SessionID string SessionID string
@@ -149,7 +149,7 @@ func (db *DB) ListChatHistories(ctx context.Context, f ListChatHistoriesFilter)
return result, rows.Err() return result, rows.Err()
} }
func (db *DB) DeleteChatHistory(ctx context.Context, id uuid.UUID) (bool, error) { func (db *DB) DeleteChatHistory(ctx context.Context, id int64) (bool, error) {
tag, err := db.pool.Exec(ctx, `delete from chat_histories where id = $1`, id) tag, err := db.pool.Exec(ctx, `delete from chat_histories where id = $1`, id)
if err != nil { if err != nil {
return false, fmt.Errorf("delete chat history: %w", err) return false, fmt.Errorf("delete chat history: %w", err)
@@ -171,7 +171,8 @@ func scanChatHistory(row rowScanner) (ext.ChatHistory, error) {
} }
h := ext.ChatHistory{ h := ext.ChatHistory{
ID: model.ID.UUID(), ID: model.ID.Int64(),
GUID: model.GUID.UUID(),
SessionID: model.SessionID.String(), SessionID: model.SessionID.String(),
Title: model.Title.String(), Title: model.Title.String(),
Channel: model.Channel.String(), Channel: model.Channel.String(),
@@ -181,7 +182,7 @@ func scanChatHistory(row rowScanner) (ext.ChatHistory, error) {
UpdatedAt: model.UpdatedAt.Time(), UpdatedAt: model.UpdatedAt.Time(),
} }
if model.ProjectID.Valid { if model.ProjectID.Valid {
id := model.ProjectID.UUID() id := model.ProjectID.Int64()
h.ProjectID = &id h.ProjectID = &id
} }
+205 -205
View File
@@ -1,235 +1,235 @@
package store package store
import ( // import (
"context" // "context"
"fmt" // "fmt"
"strings" // "strings"
"time" // "time"
"github.com/google/uuid" // "github.com/google/uuid"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" // "git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types" // ext "git.warky.dev/wdevs/amcs/internal/types"
) // )
func (db *DB) AddProfessionalContact(ctx context.Context, c ext.ProfessionalContact) (ext.ProfessionalContact, error) { // func (db *DB) AddProfessionalContact(ctx context.Context, c ext.ProfessionalContact) (ext.ProfessionalContact, error) {
if c.Tags == nil { // if c.Tags == nil {
c.Tags = []string{} // c.Tags = []string{}
} // }
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into professional_contacts (name, company, title, email, phone, linkedin_url, how_we_met, tags, notes, follow_up_date) // insert into professional_contacts (name, company, title, email, phone, linkedin_url, how_we_met, tags, notes, follow_up_date)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) // values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
returning id, created_at, updated_at // returning id, created_at, updated_at
`, c.Name, nullStr(c.Company), nullStr(c.Title), nullStr(c.Email), nullStr(c.Phone), // `, c.Name, nullStr(c.Company), nullStr(c.Title), nullStr(c.Email), nullStr(c.Phone),
nullStr(c.LinkedInURL), nullStr(c.HowWeMet), c.Tags, nullStr(c.Notes), c.FollowUpDate) // nullStr(c.LinkedInURL), nullStr(c.HowWeMet), c.Tags, nullStr(c.Notes), c.FollowUpDate)
created := c // created := c
var model generatedmodels.ModelPublicProfessionalContacts // var model generatedmodels.ModelPublicProfessionalContacts
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.ProfessionalContact{}, fmt.Errorf("insert contact: %w", err) // return ext.ProfessionalContact{}, fmt.Errorf("insert contact: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() // created.UpdatedAt = model.UpdatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) SearchContacts(ctx context.Context, query string, tags []string) ([]ext.ProfessionalContact, error) { // func (db *DB) SearchContacts(ctx context.Context, query string, tags []string) ([]ext.ProfessionalContact, error) {
args := []any{} // args := []any{}
conditions := []string{} // conditions := []string{}
if q := strings.TrimSpace(query); q != "" { // if q := strings.TrimSpace(query); q != "" {
args = append(args, "%"+q+"%") // args = append(args, "%"+q+"%")
idx := len(args) // idx := len(args)
conditions = append(conditions, fmt.Sprintf( // conditions = append(conditions, fmt.Sprintf(
"(name ILIKE $%[1]d OR company ILIKE $%[1]d OR title ILIKE $%[1]d OR notes ILIKE $%[1]d)", idx)) // "(name ILIKE $%[1]d OR company ILIKE $%[1]d OR title ILIKE $%[1]d OR notes ILIKE $%[1]d)", idx))
} // }
if len(tags) > 0 { // if len(tags) > 0 {
args = append(args, tags) // args = append(args, tags)
conditions = append(conditions, fmt.Sprintf("tags @> $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("tags @> $%d", len(args)))
} // }
q := `select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at from professional_contacts` // q := `select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at from professional_contacts`
if len(conditions) > 0 { // if len(conditions) > 0 {
q += " where " + strings.Join(conditions, " and ") // q += " where " + strings.Join(conditions, " and ")
} // }
q += " order by name" // q += " order by name"
rows, err := db.pool.Query(ctx, q, args...) // rows, err := db.pool.Query(ctx, q, args...)
if err != nil { // if err != nil {
return nil, fmt.Errorf("search contacts: %w", err) // return nil, fmt.Errorf("search contacts: %w", err)
} // }
defer rows.Close() // defer rows.Close()
return scanContacts(rows) // return scanContacts(rows)
} // }
func (db *DB) GetContact(ctx context.Context, id uuid.UUID) (ext.ProfessionalContact, error) { // func (db *DB) GetContact(ctx context.Context, id uuid.UUID) (ext.ProfessionalContact, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at // select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at
from professional_contacts where id = $1 // from professional_contacts where id = $1
`, id) // `, id)
var model generatedmodels.ModelPublicProfessionalContacts // var model generatedmodels.ModelPublicProfessionalContacts
var tags []string // var tags []string
if err := row.Scan(&model.ID, &model.Name, &model.Company, &model.Title, &model.Email, &model.Phone, // if err := row.Scan(&model.ID, &model.Name, &model.Company, &model.Title, &model.Email, &model.Phone,
&model.LinkedinURL, &model.HowWeMet, &tags, &model.Notes, &model.LastContacted, &model.FollowUpDate, // &model.LinkedinURL, &model.HowWeMet, &tags, &model.Notes, &model.LastContacted, &model.FollowUpDate,
&model.CreatedAt, &model.UpdatedAt); err != nil { // &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.ProfessionalContact{}, fmt.Errorf("get contact: %w", err) // return ext.ProfessionalContact{}, fmt.Errorf("get contact: %w", err)
} // }
c := professionalContactFromModel(model, tags) // c := professionalContactFromModel(model, tags)
return c, nil // return c, nil
} // }
func (db *DB) LogInteraction(ctx context.Context, interaction ext.ContactInteraction) (ext.ContactInteraction, error) { // func (db *DB) LogInteraction(ctx context.Context, interaction ext.ContactInteraction) (ext.ContactInteraction, error) {
occurredAt := interaction.OccurredAt // occurredAt := interaction.OccurredAt
if occurredAt.IsZero() { // if occurredAt.IsZero() {
occurredAt = time.Now() // occurredAt = time.Now()
} // }
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into contact_interactions (contact_id, interaction_type, occurred_at, summary, follow_up_needed, follow_up_notes) // insert into contact_interactions (contact_id, interaction_type, occurred_at, summary, follow_up_needed, follow_up_notes)
values ($1, $2, $3, $4, $5, $6) // values ($1, $2, $3, $4, $5, $6)
returning id, created_at // returning id, created_at
`, interaction.ContactID, interaction.InteractionType, occurredAt, interaction.Summary, // `, interaction.ContactID, interaction.InteractionType, occurredAt, interaction.Summary,
interaction.FollowUpNeeded, nullStr(interaction.FollowUpNotes)) // interaction.FollowUpNeeded, nullStr(interaction.FollowUpNotes))
created := interaction // created := interaction
created.OccurredAt = occurredAt // created.OccurredAt = occurredAt
var model generatedmodels.ModelPublicContactInteractions // var model generatedmodels.ModelPublicContactInteractions
if err := row.Scan(&model.ID, &model.CreatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt); err != nil {
return ext.ContactInteraction{}, fmt.Errorf("insert interaction: %w", err) // return ext.ContactInteraction{}, fmt.Errorf("insert interaction: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) GetContactHistory(ctx context.Context, contactID uuid.UUID) (ext.ContactHistory, error) { // func (db *DB) GetContactHistory(ctx context.Context, contactID uuid.UUID) (ext.ContactHistory, error) {
contact, err := db.GetContact(ctx, contactID) // contact, err := db.GetContact(ctx, contactID)
if err != nil { // if err != nil {
return ext.ContactHistory{}, err // return ext.ContactHistory{}, err
} // }
rows, err := db.pool.Query(ctx, ` // rows, err := db.pool.Query(ctx, `
select id, contact_id, interaction_type, occurred_at, summary, follow_up_needed, follow_up_notes, created_at // select id, contact_id, interaction_type, occurred_at, summary, follow_up_needed, follow_up_notes, created_at
from contact_interactions where contact_id = $1 order by occurred_at desc // from contact_interactions where contact_id = $1 order by occurred_at desc
`, contactID) // `, contactID)
if err != nil { // if err != nil {
return ext.ContactHistory{}, fmt.Errorf("get interactions: %w", err) // return ext.ContactHistory{}, fmt.Errorf("get interactions: %w", err)
} // }
defer rows.Close() // defer rows.Close()
var interactions []ext.ContactInteraction // var interactions []ext.ContactInteraction
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicContactInteractions // var model generatedmodels.ModelPublicContactInteractions
if err := rows.Scan(&model.ID, &model.ContactID, &model.InteractionType, &model.OccurredAt, &model.Summary, // if err := rows.Scan(&model.ID, &model.ContactID, &model.InteractionType, &model.OccurredAt, &model.Summary,
&model.FollowUpNeeded, &model.FollowUpNotes, &model.CreatedAt); err != nil { // &model.FollowUpNeeded, &model.FollowUpNotes, &model.CreatedAt); err != nil {
return ext.ContactHistory{}, fmt.Errorf("scan interaction: %w", err) // return ext.ContactHistory{}, fmt.Errorf("scan interaction: %w", err)
} // }
interactions = append(interactions, contactInteractionFromModel(model)) // interactions = append(interactions, contactInteractionFromModel(model))
} // }
if err := rows.Err(); err != nil { // if err := rows.Err(); err != nil {
return ext.ContactHistory{}, err // return ext.ContactHistory{}, err
} // }
oppRows, err := db.pool.Query(ctx, ` // oppRows, err := db.pool.Query(ctx, `
select id, contact_id, title, description, stage, value, expected_close_date, notes, created_at, updated_at // select id, contact_id, title, description, stage, value, expected_close_date, notes, created_at, updated_at
from opportunities where contact_id = $1 order by created_at desc // from opportunities where contact_id = $1 order by created_at desc
`, contactID) // `, contactID)
if err != nil { // if err != nil {
return ext.ContactHistory{}, fmt.Errorf("get opportunities: %w", err) // return ext.ContactHistory{}, fmt.Errorf("get opportunities: %w", err)
} // }
defer oppRows.Close() // defer oppRows.Close()
var opportunities []ext.Opportunity // var opportunities []ext.Opportunity
for oppRows.Next() { // for oppRows.Next() {
var model generatedmodels.ModelPublicOpportunities // var model generatedmodels.ModelPublicOpportunities
if err := oppRows.Scan(&model.ID, &model.ContactID, &model.Title, &model.Description, &model.Stage, &model.Value, // if err := oppRows.Scan(&model.ID, &model.ContactID, &model.Title, &model.Description, &model.Stage, &model.Value,
&model.ExpectedCloseDate, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // &model.ExpectedCloseDate, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.ContactHistory{}, fmt.Errorf("scan opportunity: %w", err) // return ext.ContactHistory{}, fmt.Errorf("scan opportunity: %w", err)
} // }
opportunities = append(opportunities, opportunityFromModel(model)) // opportunities = append(opportunities, opportunityFromModel(model))
} // }
if err := oppRows.Err(); err != nil { // if err := oppRows.Err(); err != nil {
return ext.ContactHistory{}, err // return ext.ContactHistory{}, err
} // }
return ext.ContactHistory{ // return ext.ContactHistory{
Contact: contact, // Contact: contact,
Interactions: interactions, // Interactions: interactions,
Opportunities: opportunities, // Opportunities: opportunities,
}, nil // }, nil
} // }
func (db *DB) CreateOpportunity(ctx context.Context, o ext.Opportunity) (ext.Opportunity, error) { // func (db *DB) CreateOpportunity(ctx context.Context, o ext.Opportunity) (ext.Opportunity, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into opportunities (contact_id, title, description, stage, value, expected_close_date, notes) // insert into opportunities (contact_id, title, description, stage, value, expected_close_date, notes)
values ($1, $2, $3, $4, $5, $6, $7) // values ($1, $2, $3, $4, $5, $6, $7)
returning id, created_at, updated_at // returning id, created_at, updated_at
`, o.ContactID, o.Title, nullStr(o.Description), o.Stage, o.Value, o.ExpectedCloseDate, nullStr(o.Notes)) // `, o.ContactID, o.Title, nullStr(o.Description), o.Stage, o.Value, o.ExpectedCloseDate, nullStr(o.Notes))
created := o // created := o
var model generatedmodels.ModelPublicOpportunities // var model generatedmodels.ModelPublicOpportunities
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.Opportunity{}, fmt.Errorf("insert opportunity: %w", err) // return ext.Opportunity{}, fmt.Errorf("insert opportunity: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() // created.UpdatedAt = model.UpdatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) GetFollowUpsDue(ctx context.Context, daysAhead int) ([]ext.ProfessionalContact, error) { // func (db *DB) GetFollowUpsDue(ctx context.Context, daysAhead int) ([]ext.ProfessionalContact, error) {
if daysAhead <= 0 { // if daysAhead <= 0 {
daysAhead = 7 // daysAhead = 7
} // }
cutoff := time.Now().AddDate(0, 0, daysAhead) // cutoff := time.Now().AddDate(0, 0, daysAhead)
rows, err := db.pool.Query(ctx, ` // rows, err := db.pool.Query(ctx, `
select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at // select id, name, company, title, email, phone, linkedin_url, how_we_met, tags::text[], notes, last_contacted, follow_up_date, created_at, updated_at
from professional_contacts // from professional_contacts
where follow_up_date <= $1 // where follow_up_date <= $1
order by follow_up_date asc // order by follow_up_date asc
`, cutoff) // `, cutoff)
if err != nil { // if err != nil {
return nil, fmt.Errorf("get follow-ups: %w", err) // return nil, fmt.Errorf("get follow-ups: %w", err)
} // }
defer rows.Close() // defer rows.Close()
return scanContacts(rows) // return scanContacts(rows)
} // }
func (db *DB) AppendThoughtToContactNotes(ctx context.Context, contactID uuid.UUID, thoughtContent string) error { // func (db *DB) AppendThoughtToContactNotes(ctx context.Context, contactID uuid.UUID, thoughtContent string) error {
_, err := db.pool.Exec(ctx, ` // _, err := db.pool.Exec(ctx, `
update professional_contacts // update professional_contacts
set notes = coalesce(notes, '') || $2 // set notes = coalesce(notes, '') || $2
where id = $1 // where id = $1
`, contactID, thoughtContent) // `, contactID, thoughtContent)
if err != nil { // if err != nil {
return fmt.Errorf("append thought to contact: %w", err) // return fmt.Errorf("append thought to contact: %w", err)
} // }
return nil // return nil
} // }
func scanContacts(rows interface { // func scanContacts(rows interface {
Next() bool // Next() bool
Scan(...any) error // Scan(...any) error
Err() error // Err() error
Close() // Close()
}) ([]ext.ProfessionalContact, error) { // }) ([]ext.ProfessionalContact, error) {
defer rows.Close() // defer rows.Close()
var contacts []ext.ProfessionalContact // var contacts []ext.ProfessionalContact
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicProfessionalContacts // var model generatedmodels.ModelPublicProfessionalContacts
var tags []string // var tags []string
if err := rows.Scan(&model.ID, &model.Name, &model.Company, &model.Title, &model.Email, &model.Phone, // if err := rows.Scan(&model.ID, &model.Name, &model.Company, &model.Title, &model.Email, &model.Phone,
&model.LinkedinURL, &model.HowWeMet, &tags, &model.Notes, &model.LastContacted, &model.FollowUpDate, // &model.LinkedinURL, &model.HowWeMet, &tags, &model.Notes, &model.LastContacted, &model.FollowUpDate,
&model.CreatedAt, &model.UpdatedAt); err != nil { // &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan contact: %w", err) // return nil, fmt.Errorf("scan contact: %w", err)
} // }
contacts = append(contacts, professionalContactFromModel(model, tags)) // contacts = append(contacts, professionalContactFromModel(model, tags))
} // }
return contacts, rows.Err() // return contacts, rows.Err()
} // }
+7
View File
@@ -131,3 +131,10 @@ func (db *DB) Bun() *bun.DB {
} }
return db.bun return db.bun
} }
func (db *DB) Pool() *pgxpool.Pool {
if db == nil {
return nil
}
return db.pool
}
+9 -6
View File
@@ -17,11 +17,12 @@ func (db *DB) InsertStoredFile(ctx context.Context, file thoughttypes.StoredFile
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
insert into stored_files (thought_id, project_id, name, media_type, kind, encoding, size_bytes, sha256, content) insert into stored_files (thought_id, project_id, name, media_type, kind, encoding, size_bytes, sha256, content)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)
returning guid, thought_id, project_id, name, media_type, kind, encoding, size_bytes, sha256, created_at, updated_at returning id, guid, thought_id, project_id, name, media_type, kind, encoding, size_bytes, sha256, created_at, updated_at
`, file.ThoughtID, file.ProjectID, file.Name, file.MediaType, file.Kind, file.Encoding, file.SizeBytes, file.SHA256, file.Content) `, file.ThoughtID, file.ProjectID, file.Name, file.MediaType, file.Kind, file.Encoding, file.SizeBytes, file.SHA256, file.Content)
var model generatedmodels.ModelPublicStoredFiles var model generatedmodels.ModelPublicStoredFiles
if err := row.Scan( if err := row.Scan(
&model.ID,
&model.GUID, &model.GUID,
&model.ThoughtID, &model.ThoughtID,
&model.ProjectID, &model.ProjectID,
@@ -42,13 +43,14 @@ func (db *DB) InsertStoredFile(ctx context.Context, file thoughttypes.StoredFile
func (db *DB) GetStoredFile(ctx context.Context, id uuid.UUID) (thoughttypes.StoredFile, error) { func (db *DB) GetStoredFile(ctx context.Context, id uuid.UUID) (thoughttypes.StoredFile, error) {
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
select guid, thought_id, project_id, name, media_type, kind, encoding, size_bytes, sha256, content, created_at, updated_at select id, guid, thought_id, project_id, name, media_type, kind, encoding, size_bytes, sha256, content, created_at, updated_at
from stored_files from stored_files
where guid = $1 where guid = $1
`, id) `, id)
var model generatedmodels.ModelPublicStoredFiles var model generatedmodels.ModelPublicStoredFiles
if err := row.Scan( if err := row.Scan(
&model.ID,
&model.GUID, &model.GUID,
&model.ThoughtID, &model.ThoughtID,
&model.ProjectID, &model.ProjectID,
@@ -89,7 +91,7 @@ func (db *DB) ListStoredFiles(ctx context.Context, filter thoughttypes.StoredFil
} }
query := ` query := `
select guid, thought_id, project_id, name, media_type, kind, encoding, size_bytes, sha256, created_at, updated_at select id, guid, thought_id, project_id, name, media_type, kind, encoding, size_bytes, sha256, created_at, updated_at
from stored_files from stored_files
` `
if len(conditions) > 0 { if len(conditions) > 0 {
@@ -109,6 +111,7 @@ func (db *DB) ListStoredFiles(ctx context.Context, filter thoughttypes.StoredFil
for rows.Next() { for rows.Next() {
var model generatedmodels.ModelPublicStoredFiles var model generatedmodels.ModelPublicStoredFiles
if err := rows.Scan( if err := rows.Scan(
&model.ID,
&model.GUID, &model.GUID,
&model.ThoughtID, &model.ThoughtID,
&model.ProjectID, &model.ProjectID,
@@ -132,7 +135,7 @@ func (db *DB) ListStoredFiles(ctx context.Context, filter thoughttypes.StoredFil
return files, nil return files, nil
} }
func (db *DB) AddThoughtAttachment(ctx context.Context, thoughtID uuid.UUID, attachment thoughttypes.ThoughtAttachment) error { func (db *DB) AddThoughtAttachment(ctx context.Context, thoughtID int64, attachment thoughttypes.ThoughtAttachment) error {
tx, err := db.pool.Begin(ctx) tx, err := db.pool.Begin(ctx)
if err != nil { if err != nil {
return fmt.Errorf("begin transaction: %w", err) return fmt.Errorf("begin transaction: %w", err)
@@ -142,7 +145,7 @@ func (db *DB) AddThoughtAttachment(ctx context.Context, thoughtID uuid.UUID, att
}() }()
var metadataBytes []byte var metadataBytes []byte
if err := tx.QueryRow(ctx, `select metadata from thoughts where guid = $1 for update`, thoughtID).Scan(&metadataBytes); err != nil { if err := tx.QueryRow(ctx, `select metadata from thoughts where id = $1 for update`, thoughtID).Scan(&metadataBytes); err != nil {
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
return err return err
} }
@@ -177,7 +180,7 @@ func (db *DB) AddThoughtAttachment(ctx context.Context, thoughtID uuid.UUID, att
update thoughts update thoughts
set metadata = $2::jsonb, set metadata = $2::jsonb,
updated_at = now() updated_at = now()
where guid = $1 where id = $1
`, thoughtID, updatedMetadata) `, thoughtID, updatedMetadata)
if err != nil { if err != nil {
return fmt.Errorf("update thought attachments: %w", err) return fmt.Errorf("update thought attachments: %w", err)
+114 -114
View File
@@ -1,133 +1,133 @@
package store package store
import ( // import (
"context" // "context"
"encoding/json" // "encoding/json"
"fmt" // "fmt"
"strings" // "strings"
"github.com/google/uuid" // "github.com/google/uuid"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" // "git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types" // ext "git.warky.dev/wdevs/amcs/internal/types"
) // )
func (db *DB) AddHouseholdItem(ctx context.Context, item ext.HouseholdItem) (ext.HouseholdItem, error) { // func (db *DB) AddHouseholdItem(ctx context.Context, item ext.HouseholdItem) (ext.HouseholdItem, error) {
details, err := json.Marshal(item.Details) // details, err := json.Marshal(item.Details)
if err != nil { // if err != nil {
return ext.HouseholdItem{}, fmt.Errorf("marshal details: %w", err) // return ext.HouseholdItem{}, fmt.Errorf("marshal details: %w", err)
} // }
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into household_items (name, category, location, details, notes) // insert into household_items (name, category, location, details, notes)
values ($1, $2, $3, $4::jsonb, $5) // values ($1, $2, $3, $4::jsonb, $5)
returning id, created_at, updated_at // returning id, created_at, updated_at
`, item.Name, nullStr(item.Category), nullStr(item.Location), details, nullStr(item.Notes)) // `, item.Name, nullStr(item.Category), nullStr(item.Location), details, nullStr(item.Notes))
created := item // created := item
var model generatedmodels.ModelPublicHouseholdItems // var model generatedmodels.ModelPublicHouseholdItems
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.HouseholdItem{}, fmt.Errorf("insert household item: %w", err) // return ext.HouseholdItem{}, fmt.Errorf("insert household item: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() // created.UpdatedAt = model.UpdatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) SearchHouseholdItems(ctx context.Context, query, category, location string) ([]ext.HouseholdItem, error) { // func (db *DB) SearchHouseholdItems(ctx context.Context, query, category, location string) ([]ext.HouseholdItem, error) {
args := []any{} // args := []any{}
conditions := []string{} // conditions := []string{}
if q := strings.TrimSpace(query); q != "" { // if q := strings.TrimSpace(query); q != "" {
args = append(args, "%"+q+"%") // args = append(args, "%"+q+"%")
conditions = append(conditions, fmt.Sprintf("(name ILIKE $%d OR notes ILIKE $%d)", len(args), len(args))) // conditions = append(conditions, fmt.Sprintf("(name ILIKE $%d OR notes ILIKE $%d)", len(args), len(args)))
} // }
if c := strings.TrimSpace(category); c != "" { // if c := strings.TrimSpace(category); c != "" {
args = append(args, c) // args = append(args, c)
conditions = append(conditions, fmt.Sprintf("category = $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("category = $%d", len(args)))
} // }
if l := strings.TrimSpace(location); l != "" { // if l := strings.TrimSpace(location); l != "" {
args = append(args, "%"+l+"%") // args = append(args, "%"+l+"%")
conditions = append(conditions, fmt.Sprintf("location ILIKE $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("location ILIKE $%d", len(args)))
} // }
q := `select id, name, category, location, details, notes, created_at, updated_at from household_items` // q := `select id, name, category, location, details, notes, created_at, updated_at from household_items`
if len(conditions) > 0 { // if len(conditions) > 0 {
q += " where " + strings.Join(conditions, " and ") // q += " where " + strings.Join(conditions, " and ")
} // }
q += " order by name" // q += " order by name"
rows, err := db.pool.Query(ctx, q, args...) // rows, err := db.pool.Query(ctx, q, args...)
if err != nil { // if err != nil {
return nil, fmt.Errorf("search household items: %w", err) // return nil, fmt.Errorf("search household items: %w", err)
} // }
defer rows.Close() // defer rows.Close()
var items []ext.HouseholdItem // var items []ext.HouseholdItem
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicHouseholdItems // var model generatedmodels.ModelPublicHouseholdItems
if err := rows.Scan(&model.ID, &model.Name, &model.Category, &model.Location, &model.Details, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := rows.Scan(&model.ID, &model.Name, &model.Category, &model.Location, &model.Details, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan household item: %w", err) // return nil, fmt.Errorf("scan household item: %w", err)
} // }
items = append(items, householdItemFromModel(model)) // items = append(items, householdItemFromModel(model))
} // }
return items, rows.Err() // return items, rows.Err()
} // }
func (db *DB) GetHouseholdItem(ctx context.Context, id uuid.UUID) (ext.HouseholdItem, error) { // func (db *DB) GetHouseholdItem(ctx context.Context, id uuid.UUID) (ext.HouseholdItem, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
select id, name, category, location, details, notes, created_at, updated_at // select id, name, category, location, details, notes, created_at, updated_at
from household_items where id = $1 // from household_items where id = $1
`, id) // `, id)
var model generatedmodels.ModelPublicHouseholdItems // var model generatedmodels.ModelPublicHouseholdItems
if err := row.Scan(&model.ID, &model.Name, &model.Category, &model.Location, &model.Details, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := row.Scan(&model.ID, &model.Name, &model.Category, &model.Location, &model.Details, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.HouseholdItem{}, fmt.Errorf("get household item: %w", err) // return ext.HouseholdItem{}, fmt.Errorf("get household item: %w", err)
} // }
return householdItemFromModel(model), nil // return householdItemFromModel(model), nil
} // }
func (db *DB) AddVendor(ctx context.Context, v ext.HouseholdVendor) (ext.HouseholdVendor, error) { // func (db *DB) AddVendor(ctx context.Context, v ext.HouseholdVendor) (ext.HouseholdVendor, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into household_vendors (name, service_type, phone, email, website, notes, rating, last_used) // insert into household_vendors (name, service_type, phone, email, website, notes, rating, last_used)
values ($1, $2, $3, $4, $5, $6, $7, $8) // values ($1, $2, $3, $4, $5, $6, $7, $8)
returning id, created_at // returning id, created_at
`, v.Name, nullStr(v.ServiceType), nullStr(v.Phone), nullStr(v.Email), // `, v.Name, nullStr(v.ServiceType), nullStr(v.Phone), nullStr(v.Email),
nullStr(v.Website), nullStr(v.Notes), v.Rating, v.LastUsed) // nullStr(v.Website), nullStr(v.Notes), v.Rating, v.LastUsed)
created := v // created := v
var model generatedmodels.ModelPublicHouseholdVendors // var model generatedmodels.ModelPublicHouseholdVendors
if err := row.Scan(&model.ID, &model.CreatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt); err != nil {
return ext.HouseholdVendor{}, fmt.Errorf("insert vendor: %w", err) // return ext.HouseholdVendor{}, fmt.Errorf("insert vendor: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) ListVendors(ctx context.Context, serviceType string) ([]ext.HouseholdVendor, error) { // func (db *DB) ListVendors(ctx context.Context, serviceType string) ([]ext.HouseholdVendor, error) {
args := []any{} // args := []any{}
q := `select id, name, service_type, phone, email, website, notes, rating, last_used, created_at from household_vendors` // q := `select id, name, service_type, phone, email, website, notes, rating, last_used, created_at from household_vendors`
if st := strings.TrimSpace(serviceType); st != "" { // if st := strings.TrimSpace(serviceType); st != "" {
args = append(args, st) // args = append(args, st)
q += " where service_type = $1" // q += " where service_type = $1"
} // }
q += " order by name" // q += " order by name"
rows, err := db.pool.Query(ctx, q, args...) // rows, err := db.pool.Query(ctx, q, args...)
if err != nil { // if err != nil {
return nil, fmt.Errorf("list vendors: %w", err) // return nil, fmt.Errorf("list vendors: %w", err)
} // }
defer rows.Close() // defer rows.Close()
var vendors []ext.HouseholdVendor // var vendors []ext.HouseholdVendor
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicHouseholdVendors // var model generatedmodels.ModelPublicHouseholdVendors
if err := rows.Scan(&model.ID, &model.Name, &model.ServiceType, &model.Phone, &model.Email, &model.Website, &model.Notes, &model.Rating, &model.LastUsed, &model.CreatedAt); err != nil { // if err := rows.Scan(&model.ID, &model.Name, &model.ServiceType, &model.Phone, &model.Email, &model.Website, &model.Notes, &model.Rating, &model.LastUsed, &model.CreatedAt); err != nil {
return nil, fmt.Errorf("scan vendor: %w", err) // return nil, fmt.Errorf("scan vendor: %w", err)
} // }
vendors = append(vendors, householdVendorFromModel(model)) // vendors = append(vendors, householdVendorFromModel(model))
} // }
return vendors, rows.Err() // return vendors, rows.Err()
} // }
+6 -6
View File
@@ -6,7 +6,6 @@ import (
"strings" "strings"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" "git.warky.dev/wdevs/amcs/internal/generatedmodels"
"github.com/google/uuid"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
thoughttypes "git.warky.dev/wdevs/amcs/internal/types" thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
@@ -25,7 +24,7 @@ func (db *DB) CreateLearning(ctx context.Context, learning thoughttypes.Learning
$13, $14, $15, $16, $13, $14, $15, $16,
$17, $18 $17, $18
) )
returning id, created_at, updated_at returning id, guid, created_at, updated_at
`, `,
strings.TrimSpace(learning.Summary), strings.TrimSpace(learning.Summary),
strings.TrimSpace(learning.Details), strings.TrimSpace(learning.Details),
@@ -49,16 +48,17 @@ func (db *DB) CreateLearning(ctx context.Context, learning thoughttypes.Learning
created := learning created := learning
var model generatedmodels.ModelPublicLearnings var model generatedmodels.ModelPublicLearnings
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { if err := row.Scan(&model.ID, &model.GUID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return thoughttypes.Learning{}, fmt.Errorf("create learning: %w", err) return thoughttypes.Learning{}, fmt.Errorf("create learning: %w", err)
} }
created.ID = model.ID.UUID() created.ID = model.ID.Int64()
created.GUID = model.GUID.UUID()
created.CreatedAt = model.CreatedAt.Time() created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() created.UpdatedAt = model.UpdatedAt.Time()
return created, nil return created, nil
} }
func (db *DB) GetLearning(ctx context.Context, id uuid.UUID) (thoughttypes.Learning, error) { func (db *DB) GetLearning(ctx context.Context, id int64) (thoughttypes.Learning, error) {
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
select id, summary, details, category, area, status, priority, confidence, select id, summary, details, category, area, status, priority, confidence,
action_required, source_type, source_ref, project_id, related_thought_id, action_required, source_type, source_ref, project_id, related_thought_id,
@@ -71,7 +71,7 @@ func (db *DB) GetLearning(ctx context.Context, id uuid.UUID) (thoughttypes.Learn
learning, err := scanLearning(row) learning, err := scanLearning(row)
if err != nil { if err != nil {
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
return thoughttypes.Learning{}, fmt.Errorf("learning not found: %s", id) return thoughttypes.Learning{}, fmt.Errorf("learning not found: %d", id)
} }
return thoughttypes.Learning{}, fmt.Errorf("get learning: %w", err) return thoughttypes.Learning{}, fmt.Errorf("get learning: %w", err)
} }
+3 -2
View File
@@ -25,12 +25,12 @@ func (db *DB) InsertLink(ctx context.Context, link thoughttypes.ThoughtLink) err
func (db *DB) LinkedThoughts(ctx context.Context, thoughtID uuid.UUID) ([]thoughttypes.LinkedThought, error) { func (db *DB) LinkedThoughts(ctx context.Context, thoughtID uuid.UUID) ([]thoughttypes.LinkedThought, error) {
rows, err := db.pool.Query(ctx, ` rows, err := db.pool.Query(ctx, `
select t.guid, t.content, t.metadata, t.project_id, t.archived_at, t.created_at, t.updated_at, l.relation, 'outgoing' as direction, l.created_at select t.id, t.guid, t.content, t.metadata, t.project_id, t.archived_at, t.created_at, t.updated_at, l.relation, 'outgoing' as direction, l.created_at
from thought_links l from thought_links l
join thoughts t on t.id = l.to_id join thoughts t on t.id = l.to_id
where l.from_id = (select id from thoughts where guid = $1) where l.from_id = (select id from thoughts where guid = $1)
union all union all
select t.guid, t.content, t.metadata, t.project_id, t.archived_at, t.created_at, t.updated_at, l.relation, 'incoming' as direction, l.created_at select t.id, t.guid, t.content, t.metadata, t.project_id, t.archived_at, t.created_at, t.updated_at, l.relation, 'incoming' as direction, l.created_at
from thought_links l from thought_links l
join thoughts t on t.id = l.from_id join thoughts t on t.id = l.from_id
where l.to_id = (select id from thoughts where guid = $1) where l.to_id = (select id from thoughts where guid = $1)
@@ -46,6 +46,7 @@ func (db *DB) LinkedThoughts(ctx context.Context, thoughtID uuid.UUID) ([]though
var linked thoughttypes.LinkedThought var linked thoughttypes.LinkedThought
var model generatedmodels.ModelPublicThoughts var model generatedmodels.ModelPublicThoughts
if err := rows.Scan( if err := rows.Scan(
&model.ID,
&model.GUID, &model.GUID,
&model.Content, &model.Content,
&model.Metadata, &model.Metadata,
+121 -121
View File
@@ -1,137 +1,137 @@
package store package store
import ( // import (
"context" // "context"
"fmt" // "fmt"
"strings" // "strings"
"time" // "time"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" // "git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types" // ext "git.warky.dev/wdevs/amcs/internal/types"
) // )
func (db *DB) AddMaintenanceTask(ctx context.Context, t ext.MaintenanceTask) (ext.MaintenanceTask, error) { // func (db *DB) AddMaintenanceTask(ctx context.Context, t ext.MaintenanceTask) (ext.MaintenanceTask, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into maintenance_tasks (name, category, frequency_days, next_due, priority, notes) // insert into maintenance_tasks (name, category, frequency_days, next_due, priority, notes)
values ($1, $2, $3, $4, $5, $6) // values ($1, $2, $3, $4, $5, $6)
returning id, created_at, updated_at // returning id, created_at, updated_at
`, t.Name, nullStr(t.Category), t.FrequencyDays, t.NextDue, t.Priority, nullStr(t.Notes)) // `, t.Name, nullStr(t.Category), t.FrequencyDays, t.NextDue, t.Priority, nullStr(t.Notes))
created := t // created := t
var model generatedmodels.ModelPublicMaintenanceTasks // var model generatedmodels.ModelPublicMaintenanceTasks
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.MaintenanceTask{}, fmt.Errorf("insert maintenance task: %w", err) // return ext.MaintenanceTask{}, fmt.Errorf("insert maintenance task: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() // created.UpdatedAt = model.UpdatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) LogMaintenance(ctx context.Context, log ext.MaintenanceLog) (ext.MaintenanceLog, error) { // func (db *DB) LogMaintenance(ctx context.Context, log ext.MaintenanceLog) (ext.MaintenanceLog, error) {
completedAt := log.CompletedAt // completedAt := log.CompletedAt
if completedAt.IsZero() { // if completedAt.IsZero() {
completedAt = time.Now() // completedAt = time.Now()
} // }
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into maintenance_logs (task_id, completed_at, performed_by, cost, notes, next_action) // insert into maintenance_logs (task_id, completed_at, performed_by, cost, notes, next_action)
values ($1, $2, $3, $4, $5, $6) // values ($1, $2, $3, $4, $5, $6)
returning id // returning id
`, log.TaskID, completedAt, nullStr(log.PerformedBy), log.Cost, nullStr(log.Notes), nullStr(log.NextAction)) // `, log.TaskID, completedAt, nullStr(log.PerformedBy), log.Cost, nullStr(log.Notes), nullStr(log.NextAction))
created := log // created := log
created.CompletedAt = completedAt // created.CompletedAt = completedAt
var model generatedmodels.ModelPublicMaintenanceLogs // var model generatedmodels.ModelPublicMaintenanceLogs
if err := row.Scan(&model.ID); err != nil { // if err := row.Scan(&model.ID); err != nil {
return ext.MaintenanceLog{}, fmt.Errorf("insert maintenance log: %w", err) // return ext.MaintenanceLog{}, fmt.Errorf("insert maintenance log: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
return created, nil // return created, nil
} // }
func (db *DB) GetUpcomingMaintenance(ctx context.Context, daysAhead int) ([]ext.MaintenanceTask, error) { // func (db *DB) GetUpcomingMaintenance(ctx context.Context, daysAhead int) ([]ext.MaintenanceTask, error) {
if daysAhead <= 0 { // if daysAhead <= 0 {
daysAhead = 30 // daysAhead = 30
} // }
cutoff := time.Now().Add(time.Duration(daysAhead) * 24 * time.Hour) // cutoff := time.Now().Add(time.Duration(daysAhead) * 24 * time.Hour)
rows, err := db.pool.Query(ctx, ` // rows, err := db.pool.Query(ctx, `
select id, name, category, frequency_days, last_completed, next_due, priority, notes, created_at, updated_at // select id, name, category, frequency_days, last_completed, next_due, priority, notes, created_at, updated_at
from maintenance_tasks // from maintenance_tasks
where next_due <= $1 or next_due is null // where next_due <= $1 or next_due is null
order by next_due asc nulls last, priority desc // order by next_due asc nulls last, priority desc
`, cutoff) // `, cutoff)
if err != nil { // if err != nil {
return nil, fmt.Errorf("get upcoming maintenance: %w", err) // return nil, fmt.Errorf("get upcoming maintenance: %w", err)
} // }
defer rows.Close() // defer rows.Close()
tasks := make([]ext.MaintenanceTask, 0) // tasks := make([]ext.MaintenanceTask, 0)
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicMaintenanceTasks // var model generatedmodels.ModelPublicMaintenanceTasks
if err := rows.Scan(&model.ID, &model.Name, &model.Category, &model.FrequencyDays, &model.LastCompleted, &model.NextDue, &model.Priority, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := rows.Scan(&model.ID, &model.Name, &model.Category, &model.FrequencyDays, &model.LastCompleted, &model.NextDue, &model.Priority, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan maintenance task: %w", err) // return nil, fmt.Errorf("scan maintenance task: %w", err)
} // }
tasks = append(tasks, maintenanceTaskFromModel(model)) // tasks = append(tasks, maintenanceTaskFromModel(model))
} // }
return tasks, rows.Err() // return tasks, rows.Err()
} // }
func (db *DB) SearchMaintenanceHistory(ctx context.Context, query, category string, start, end *time.Time) ([]ext.MaintenanceLogWithTask, error) { // func (db *DB) SearchMaintenanceHistory(ctx context.Context, query, category string, start, end *time.Time) ([]ext.MaintenanceLogWithTask, error) {
args := []any{} // args := []any{}
conditions := []string{} // conditions := []string{}
if q := strings.TrimSpace(query); q != "" { // if q := strings.TrimSpace(query); q != "" {
args = append(args, "%"+q+"%") // args = append(args, "%"+q+"%")
conditions = append(conditions, fmt.Sprintf("(mt.name ILIKE $%d OR ml.notes ILIKE $%d)", len(args), len(args))) // conditions = append(conditions, fmt.Sprintf("(mt.name ILIKE $%d OR ml.notes ILIKE $%d)", len(args), len(args)))
} // }
if c := strings.TrimSpace(category); c != "" { // if c := strings.TrimSpace(category); c != "" {
args = append(args, c) // args = append(args, c)
conditions = append(conditions, fmt.Sprintf("mt.category = $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("mt.category = $%d", len(args)))
} // }
if start != nil { // if start != nil {
args = append(args, *start) // args = append(args, *start)
conditions = append(conditions, fmt.Sprintf("ml.completed_at >= $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("ml.completed_at >= $%d", len(args)))
} // }
if end != nil { // if end != nil {
args = append(args, *end) // args = append(args, *end)
conditions = append(conditions, fmt.Sprintf("ml.completed_at <= $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("ml.completed_at <= $%d", len(args)))
} // }
q := ` // q := `
select ml.id, ml.task_id, ml.completed_at, ml.performed_by, ml.cost, ml.notes, ml.next_action, // select ml.id, ml.task_id, ml.completed_at, ml.performed_by, ml.cost, ml.notes, ml.next_action,
mt.name, mt.category // mt.name, mt.category
from maintenance_logs ml // from maintenance_logs ml
join maintenance_tasks mt on mt.id = ml.task_id // join maintenance_tasks mt on mt.id = ml.task_id
` // `
if len(conditions) > 0 { // if len(conditions) > 0 {
q += " where " + strings.Join(conditions, " and ") // q += " where " + strings.Join(conditions, " and ")
} // }
q += " order by ml.completed_at desc" // q += " order by ml.completed_at desc"
rows, err := db.pool.Query(ctx, q, args...) // rows, err := db.pool.Query(ctx, q, args...)
if err != nil { // if err != nil {
return nil, fmt.Errorf("search maintenance history: %w", err) // return nil, fmt.Errorf("search maintenance history: %w", err)
} // }
defer rows.Close() // defer rows.Close()
var logs []ext.MaintenanceLogWithTask // var logs []ext.MaintenanceLogWithTask
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicMaintenanceLogs // var model generatedmodels.ModelPublicMaintenanceLogs
var taskName, taskCategory string // var taskName, taskCategory string
if err := rows.Scan( // if err := rows.Scan(
&model.ID, &model.TaskID, &model.CompletedAt, &model.PerformedBy, &model.Cost, &model.Notes, &model.NextAction, // &model.ID, &model.TaskID, &model.CompletedAt, &model.PerformedBy, &model.Cost, &model.Notes, &model.NextAction,
&taskName, &taskCategory, // &taskName, &taskCategory,
); err != nil { // ); err != nil {
return nil, fmt.Errorf("scan maintenance log: %w", err) // return nil, fmt.Errorf("scan maintenance log: %w", err)
} // }
l := ext.MaintenanceLogWithTask{ // l := ext.MaintenanceLogWithTask{
MaintenanceLog: maintenanceLogFromModel(model), // MaintenanceLog: maintenanceLogFromModel(model),
TaskName: taskName, // TaskName: taskName,
TaskCategory: taskCategory, // TaskCategory: taskCategory,
} // }
logs = append(logs, l) // logs = append(logs, l)
} // }
return logs, rows.Err() // return logs, rows.Err()
} // }
+251 -251
View File
@@ -1,280 +1,280 @@
package store package store
import ( // import (
"context" // "context"
"encoding/json" // "encoding/json"
"fmt" // "fmt"
"strings" // "strings"
"time" // "time"
"github.com/google/uuid" // "github.com/google/uuid"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" // "git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types" // ext "git.warky.dev/wdevs/amcs/internal/types"
) // )
func (db *DB) AddRecipe(ctx context.Context, r ext.Recipe) (ext.Recipe, error) { // func (db *DB) AddRecipe(ctx context.Context, r ext.Recipe) (ext.Recipe, error) {
ingredients, err := json.Marshal(r.Ingredients) // ingredients, err := json.Marshal(r.Ingredients)
if err != nil { // if err != nil {
return ext.Recipe{}, fmt.Errorf("marshal ingredients: %w", err) // return ext.Recipe{}, fmt.Errorf("marshal ingredients: %w", err)
} // }
instructions, err := json.Marshal(r.Instructions) // instructions, err := json.Marshal(r.Instructions)
if err != nil { // if err != nil {
return ext.Recipe{}, fmt.Errorf("marshal instructions: %w", err) // return ext.Recipe{}, fmt.Errorf("marshal instructions: %w", err)
} // }
if r.Tags == nil { // if r.Tags == nil {
r.Tags = []string{} // r.Tags = []string{}
} // }
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into recipes (name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags, rating, notes) // insert into recipes (name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags, rating, notes)
values ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, $8, $9, $10) // values ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, $8, $9, $10)
returning id, created_at, updated_at // returning id, created_at, updated_at
`, r.Name, nullStr(r.Cuisine), r.PrepTimeMinutes, r.CookTimeMinutes, r.Servings, // `, r.Name, nullStr(r.Cuisine), r.PrepTimeMinutes, r.CookTimeMinutes, r.Servings,
ingredients, instructions, r.Tags, r.Rating, nullStr(r.Notes)) // ingredients, instructions, r.Tags, r.Rating, nullStr(r.Notes))
created := r // created := r
var model generatedmodels.ModelPublicRecipes // var model generatedmodels.ModelPublicRecipes
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.Recipe{}, fmt.Errorf("insert recipe: %w", err) // return ext.Recipe{}, fmt.Errorf("insert recipe: %w", err)
} // }
created.ID = model.ID.UUID() // created.ID = model.ID.UUID()
created.CreatedAt = model.CreatedAt.Time() // created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() // created.UpdatedAt = model.UpdatedAt.Time()
return created, nil // return created, nil
} // }
func (db *DB) SearchRecipes(ctx context.Context, query, cuisine string, tags []string, ingredient string) ([]ext.Recipe, error) { // func (db *DB) SearchRecipes(ctx context.Context, query, cuisine string, tags []string, ingredient string) ([]ext.Recipe, error) {
args := []any{} // args := []any{}
conditions := []string{} // conditions := []string{}
if q := strings.TrimSpace(query); q != "" { // if q := strings.TrimSpace(query); q != "" {
args = append(args, "%"+q+"%") // args = append(args, "%"+q+"%")
conditions = append(conditions, fmt.Sprintf("name ILIKE $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("name ILIKE $%d", len(args)))
} // }
if c := strings.TrimSpace(cuisine); c != "" { // if c := strings.TrimSpace(cuisine); c != "" {
args = append(args, c) // args = append(args, c)
conditions = append(conditions, fmt.Sprintf("cuisine = $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("cuisine = $%d", len(args)))
} // }
if len(tags) > 0 { // if len(tags) > 0 {
args = append(args, tags) // args = append(args, tags)
conditions = append(conditions, fmt.Sprintf("tags @> $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("tags @> $%d", len(args)))
} // }
if ing := strings.TrimSpace(ingredient); ing != "" { // if ing := strings.TrimSpace(ingredient); ing != "" {
args = append(args, "%"+ing+"%") // args = append(args, "%"+ing+"%")
conditions = append(conditions, fmt.Sprintf("ingredients::text ILIKE $%d", len(args))) // conditions = append(conditions, fmt.Sprintf("ingredients::text ILIKE $%d", len(args)))
} // }
q := `select id, name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags::text[], rating, notes, created_at, updated_at from recipes` // q := `select id, name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags::text[], rating, notes, created_at, updated_at from recipes`
if len(conditions) > 0 { // if len(conditions) > 0 {
q += " where " + strings.Join(conditions, " and ") // q += " where " + strings.Join(conditions, " and ")
} // }
q += " order by name" // q += " order by name"
rows, err := db.pool.Query(ctx, q, args...) // rows, err := db.pool.Query(ctx, q, args...)
if err != nil { // if err != nil {
return nil, fmt.Errorf("search recipes: %w", err) // return nil, fmt.Errorf("search recipes: %w", err)
} // }
defer rows.Close() // defer rows.Close()
var recipes []ext.Recipe // var recipes []ext.Recipe
for rows.Next() { // for rows.Next() {
r, err := scanRecipeRow(rows) // r, err := scanRecipeRow(rows)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
recipes = append(recipes, r) // recipes = append(recipes, r)
} // }
return recipes, rows.Err() // return recipes, rows.Err()
} // }
func (db *DB) GetRecipe(ctx context.Context, id uuid.UUID) (ext.Recipe, error) { // func (db *DB) GetRecipe(ctx context.Context, id uuid.UUID) (ext.Recipe, error) {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
select id, name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags::text[], rating, notes, created_at, updated_at // select id, name, cuisine, prep_time_minutes, cook_time_minutes, servings, ingredients, instructions, tags::text[], rating, notes, created_at, updated_at
from recipes where id = $1 // from recipes where id = $1
`, id) // `, id)
var model generatedmodels.ModelPublicRecipes // var model generatedmodels.ModelPublicRecipes
var tags []string // var tags []string
if err := row.Scan(&model.ID, &model.Name, &model.Cuisine, &model.PrepTimeMinutes, &model.CookTimeMinutes, &model.Servings, // if err := row.Scan(&model.ID, &model.Name, &model.Cuisine, &model.PrepTimeMinutes, &model.CookTimeMinutes, &model.Servings,
&model.Ingredients, &model.Instructions, &tags, &model.Rating, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // &model.Ingredients, &model.Instructions, &tags, &model.Rating, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.Recipe{}, fmt.Errorf("get recipe: %w", err) // return ext.Recipe{}, fmt.Errorf("get recipe: %w", err)
} // }
if tags == nil { // if tags == nil {
tags = []string{} // tags = []string{}
} // }
return recipeFromModel(model, tags), nil // return recipeFromModel(model, tags), nil
} // }
func (db *DB) UpdateRecipe(ctx context.Context, id uuid.UUID, r ext.Recipe) (ext.Recipe, error) { // func (db *DB) UpdateRecipe(ctx context.Context, id uuid.UUID, r ext.Recipe) (ext.Recipe, error) {
ingredients, err := json.Marshal(r.Ingredients) // ingredients, err := json.Marshal(r.Ingredients)
if err != nil { // if err != nil {
return ext.Recipe{}, fmt.Errorf("marshal ingredients: %w", err) // return ext.Recipe{}, fmt.Errorf("marshal ingredients: %w", err)
} // }
instructions, err := json.Marshal(r.Instructions) // instructions, err := json.Marshal(r.Instructions)
if err != nil { // if err != nil {
return ext.Recipe{}, fmt.Errorf("marshal instructions: %w", err) // return ext.Recipe{}, fmt.Errorf("marshal instructions: %w", err)
} // }
if r.Tags == nil { // if r.Tags == nil {
r.Tags = []string{} // r.Tags = []string{}
} // }
_, err = db.pool.Exec(ctx, ` // _, err = db.pool.Exec(ctx, `
update recipes set // update recipes set
name = $2, cuisine = $3, prep_time_minutes = $4, cook_time_minutes = $5, // name = $2, cuisine = $3, prep_time_minutes = $4, cook_time_minutes = $5,
servings = $6, ingredients = $7::jsonb, instructions = $8::jsonb, // servings = $6, ingredients = $7::jsonb, instructions = $8::jsonb,
tags = $9, rating = $10, notes = $11 // tags = $9, rating = $10, notes = $11
where id = $1 // where id = $1
`, id, r.Name, nullStr(r.Cuisine), r.PrepTimeMinutes, r.CookTimeMinutes, r.Servings, // `, id, r.Name, nullStr(r.Cuisine), r.PrepTimeMinutes, r.CookTimeMinutes, r.Servings,
ingredients, instructions, r.Tags, r.Rating, nullStr(r.Notes)) // ingredients, instructions, r.Tags, r.Rating, nullStr(r.Notes))
if err != nil { // if err != nil {
return ext.Recipe{}, fmt.Errorf("update recipe: %w", err) // return ext.Recipe{}, fmt.Errorf("update recipe: %w", err)
} // }
return db.GetRecipe(ctx, id) // return db.GetRecipe(ctx, id)
} // }
func (db *DB) CreateMealPlan(ctx context.Context, weekStart time.Time, entries []ext.MealPlanInput) ([]ext.MealPlanEntry, error) { // func (db *DB) CreateMealPlan(ctx context.Context, weekStart time.Time, entries []ext.MealPlanInput) ([]ext.MealPlanEntry, error) {
if _, err := db.pool.Exec(ctx, `delete from meal_plans where week_start = $1`, weekStart); err != nil { // if _, err := db.pool.Exec(ctx, `delete from meal_plans where week_start = $1`, weekStart); err != nil {
return nil, fmt.Errorf("clear meal plan: %w", err) // return nil, fmt.Errorf("clear meal plan: %w", err)
} // }
var results []ext.MealPlanEntry // var results []ext.MealPlanEntry
for _, e := range entries { // for _, e := range entries {
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into meal_plans (week_start, day_of_week, meal_type, recipe_id, custom_meal, servings, notes) // insert into meal_plans (week_start, day_of_week, meal_type, recipe_id, custom_meal, servings, notes)
values ($1, $2, $3, $4, $5, $6, $7) // values ($1, $2, $3, $4, $5, $6, $7)
returning id, created_at // returning id, created_at
`, weekStart, e.DayOfWeek, e.MealType, e.RecipeID, nullStr(e.CustomMeal), e.Servings, nullStr(e.Notes)) // `, weekStart, e.DayOfWeek, e.MealType, e.RecipeID, nullStr(e.CustomMeal), e.Servings, nullStr(e.Notes))
entry := ext.MealPlanEntry{ // entry := ext.MealPlanEntry{
WeekStart: weekStart, // WeekStart: weekStart,
DayOfWeek: e.DayOfWeek, // DayOfWeek: e.DayOfWeek,
MealType: e.MealType, // MealType: e.MealType,
RecipeID: e.RecipeID, // RecipeID: e.RecipeID,
CustomMeal: e.CustomMeal, // CustomMeal: e.CustomMeal,
Servings: e.Servings, // Servings: e.Servings,
Notes: e.Notes, // Notes: e.Notes,
} // }
var model generatedmodels.ModelPublicMealPlans // var model generatedmodels.ModelPublicMealPlans
if err := row.Scan(&model.ID, &model.CreatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt); err != nil {
return nil, fmt.Errorf("insert meal plan entry: %w", err) // return nil, fmt.Errorf("insert meal plan entry: %w", err)
} // }
entry.ID = model.ID.UUID() // entry.ID = model.ID.UUID()
entry.CreatedAt = model.CreatedAt.Time() // entry.CreatedAt = model.CreatedAt.Time()
results = append(results, entry) // results = append(results, entry)
} // }
return results, nil // return results, nil
} // }
func (db *DB) GetMealPlan(ctx context.Context, weekStart time.Time) ([]ext.MealPlanEntry, error) { // func (db *DB) GetMealPlan(ctx context.Context, weekStart time.Time) ([]ext.MealPlanEntry, error) {
rows, err := db.pool.Query(ctx, ` // rows, err := db.pool.Query(ctx, `
select mp.id, mp.week_start, mp.day_of_week, mp.meal_type, mp.recipe_id, r.name, mp.custom_meal, mp.servings, mp.notes, mp.created_at // select mp.id, mp.week_start, mp.day_of_week, mp.meal_type, mp.recipe_id, r.name, mp.custom_meal, mp.servings, mp.notes, mp.created_at
from meal_plans mp // from meal_plans mp
left join recipes r on r.id = mp.recipe_id // left join recipes r on r.id = mp.recipe_id
where mp.week_start = $1 // where mp.week_start = $1
order by // order by
case mp.day_of_week // case mp.day_of_week
when 'monday' then 1 when 'tuesday' then 2 when 'wednesday' then 3 // when 'monday' then 1 when 'tuesday' then 2 when 'wednesday' then 3
when 'thursday' then 4 when 'friday' then 5 when 'saturday' then 6 // when 'thursday' then 4 when 'friday' then 5 when 'saturday' then 6
when 'sunday' then 7 else 8 // when 'sunday' then 7 else 8
end, // end,
case mp.meal_type // case mp.meal_type
when 'breakfast' then 1 when 'lunch' then 2 when 'dinner' then 3 // when 'breakfast' then 1 when 'lunch' then 2 when 'dinner' then 3
when 'snack' then 4 else 5 // when 'snack' then 4 else 5
end // end
`, weekStart) // `, weekStart)
if err != nil { // if err != nil {
return nil, fmt.Errorf("get meal plan: %w", err) // return nil, fmt.Errorf("get meal plan: %w", err)
} // }
defer rows.Close() // defer rows.Close()
var entries []ext.MealPlanEntry // var entries []ext.MealPlanEntry
for rows.Next() { // for rows.Next() {
var model generatedmodels.ModelPublicMealPlans // var model generatedmodels.ModelPublicMealPlans
var recipeName *string // var recipeName *string
if err := rows.Scan(&model.ID, &model.WeekStart, &model.DayOfWeek, &model.MealType, &model.RecipeID, &recipeName, &model.CustomMeal, &model.Servings, &model.Notes, &model.CreatedAt); err != nil { // if err := rows.Scan(&model.ID, &model.WeekStart, &model.DayOfWeek, &model.MealType, &model.RecipeID, &recipeName, &model.CustomMeal, &model.Servings, &model.Notes, &model.CreatedAt); err != nil {
return nil, fmt.Errorf("scan meal plan entry: %w", err) // return nil, fmt.Errorf("scan meal plan entry: %w", err)
} // }
entries = append(entries, mealPlanEntryFromModel(model, strVal(recipeName))) // entries = append(entries, mealPlanEntryFromModel(model, strVal(recipeName)))
} // }
return entries, rows.Err() // return entries, rows.Err()
} // }
func (db *DB) GenerateShoppingList(ctx context.Context, weekStart time.Time) (ext.ShoppingList, error) { // func (db *DB) GenerateShoppingList(ctx context.Context, weekStart time.Time) (ext.ShoppingList, error) {
entries, err := db.GetMealPlan(ctx, weekStart) // entries, err := db.GetMealPlan(ctx, weekStart)
if err != nil { // if err != nil {
return ext.ShoppingList{}, err // return ext.ShoppingList{}, err
} // }
recipeIDs := map[uuid.UUID]bool{} // recipeIDs := map[uuid.UUID]bool{}
for _, e := range entries { // for _, e := range entries {
if e.RecipeID != nil { // if e.RecipeID != nil {
recipeIDs[*e.RecipeID] = true // recipeIDs[*e.RecipeID] = true
} // }
} // }
aggregated := map[string]*ext.ShoppingItem{} // aggregated := map[string]*ext.ShoppingItem{}
for id := range recipeIDs { // for id := range recipeIDs {
recipe, err := db.GetRecipe(ctx, id) // recipe, err := db.GetRecipe(ctx, id)
if err != nil { // if err != nil {
continue // continue
} // }
for _, ing := range recipe.Ingredients { // for _, ing := range recipe.Ingredients {
key := strings.ToLower(ing.Name) // key := strings.ToLower(ing.Name)
if existing, ok := aggregated[key]; ok { // if existing, ok := aggregated[key]; ok {
if ing.Quantity != "" { // if ing.Quantity != "" {
existing.Quantity += "+" + ing.Quantity // existing.Quantity += "+" + ing.Quantity
} // }
} else { // } else {
recipeIDCopy := id // recipeIDCopy := id
aggregated[key] = &ext.ShoppingItem{ // aggregated[key] = &ext.ShoppingItem{
Name: ing.Name, // Name: ing.Name,
Quantity: ing.Quantity, // Quantity: ing.Quantity,
Unit: ing.Unit, // Unit: ing.Unit,
Purchased: false, // Purchased: false,
RecipeID: &recipeIDCopy, // RecipeID: &recipeIDCopy,
} // }
} // }
} // }
} // }
items := make([]ext.ShoppingItem, 0, len(aggregated)) // items := make([]ext.ShoppingItem, 0, len(aggregated))
for _, item := range aggregated { // for _, item := range aggregated {
items = append(items, *item) // items = append(items, *item)
} // }
itemsJSON, err := json.Marshal(items) // itemsJSON, err := json.Marshal(items)
if err != nil { // if err != nil {
return ext.ShoppingList{}, fmt.Errorf("marshal shopping items: %w", err) // return ext.ShoppingList{}, fmt.Errorf("marshal shopping items: %w", err)
} // }
row := db.pool.QueryRow(ctx, ` // row := db.pool.QueryRow(ctx, `
insert into shopping_lists (week_start, items) // insert into shopping_lists (week_start, items)
values ($1, $2::jsonb) // values ($1, $2::jsonb)
on conflict (week_start) do update set items = excluded.items, updated_at = now() // on conflict (week_start) do update set items = excluded.items, updated_at = now()
returning id, created_at, updated_at // returning id, created_at, updated_at
`, weekStart, itemsJSON) // `, weekStart, itemsJSON)
var model generatedmodels.ModelPublicShoppingLists // var model generatedmodels.ModelPublicShoppingLists
list := ext.ShoppingList{WeekStart: weekStart, Items: items} // list := ext.ShoppingList{WeekStart: weekStart, Items: items}
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { // if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.ShoppingList{}, fmt.Errorf("upsert shopping list: %w", err) // return ext.ShoppingList{}, fmt.Errorf("upsert shopping list: %w", err)
} // }
list.ID = model.ID.UUID() // list.ID = model.ID.UUID()
list.CreatedAt = model.CreatedAt.Time() // list.CreatedAt = model.CreatedAt.Time()
list.UpdatedAt = model.UpdatedAt.Time() // list.UpdatedAt = model.UpdatedAt.Time()
return list, nil // return list, nil
} // }
func scanRecipeRow(rows interface{ Scan(...any) error }) (ext.Recipe, error) { // func scanRecipeRow(rows interface{ Scan(...any) error }) (ext.Recipe, error) {
var model generatedmodels.ModelPublicRecipes // var model generatedmodels.ModelPublicRecipes
var tags []string // var tags []string
if err := rows.Scan(&model.ID, &model.Name, &model.Cuisine, &model.PrepTimeMinutes, &model.CookTimeMinutes, &model.Servings, // if err := rows.Scan(&model.ID, &model.Name, &model.Cuisine, &model.PrepTimeMinutes, &model.CookTimeMinutes, &model.Servings,
&model.Ingredients, &model.Instructions, &tags, &model.Rating, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil { // &model.Ingredients, &model.Instructions, &tags, &model.Rating, &model.Notes, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.Recipe{}, fmt.Errorf("scan recipe: %w", err) // return ext.Recipe{}, fmt.Errorf("scan recipe: %w", err)
} // }
if tags == nil { // if tags == nil {
tags = []string{} // tags = []string{}
} // }
return recipeFromModel(model, tags), nil // return recipeFromModel(model, tags), nil
} // }
+375 -322
View File
@@ -5,8 +5,6 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/google/uuid"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" "git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types" ext "git.warky.dev/wdevs/amcs/internal/types"
) )
@@ -14,6 +12,7 @@ import (
func projectFromModel(m generatedmodels.ModelPublicProjects) ext.Project { func projectFromModel(m generatedmodels.ModelPublicProjects) ext.Project {
return ext.Project{ return ext.Project{
ID: m.GUID.UUID(), ID: m.GUID.UUID(),
NumericID: m.ID.Int64(),
Name: m.Name.String(), Name: m.Name.String(),
Description: m.Description.String(), Description: m.Description.String(),
CreatedAt: m.CreatedAt.Time(), CreatedAt: m.CreatedAt.Time(),
@@ -29,9 +28,9 @@ func thoughtFromModel(m generatedmodels.ModelPublicThoughts) (ext.Thought, error
} }
} }
var projectID *uuid.UUID var projectID *int64
if m.ProjectID.Valid { if m.ProjectID.Valid {
id := m.ProjectID.UUID() id := m.ProjectID.Int64()
projectID = &id projectID = &id
} }
@@ -42,7 +41,8 @@ func thoughtFromModel(m generatedmodels.ModelPublicThoughts) (ext.Thought, error
} }
return ext.Thought{ return ext.Thought{
ID: m.GUID.UUID(), ID: m.ID.Int64(),
GUID: m.GUID.UUID(),
Content: m.Content.String(), Content: m.Content.String(),
Metadata: metadata, Metadata: metadata,
ProjectID: projectID, ProjectID: projectID,
@@ -53,20 +53,21 @@ func thoughtFromModel(m generatedmodels.ModelPublicThoughts) (ext.Thought, error
} }
func storedFileFromModel(m generatedmodels.ModelPublicStoredFiles) ext.StoredFile { func storedFileFromModel(m generatedmodels.ModelPublicStoredFiles) ext.StoredFile {
var thoughtID *uuid.UUID var thoughtID *int64
if m.ThoughtID.Valid { if m.ThoughtID.Valid {
id := m.ThoughtID.UUID() id := m.ThoughtID.Int64()
thoughtID = &id thoughtID = &id
} }
var projectID *uuid.UUID var projectID *int64
if m.ProjectID.Valid { if m.ProjectID.Valid {
id := m.ProjectID.UUID() id := m.ProjectID.Int64()
projectID = &id projectID = &id
} }
return ext.StoredFile{ return ext.StoredFile{
ID: m.GUID.UUID(), ID: m.ID.Int64(),
GUID: m.GUID.UUID(),
ThoughtID: thoughtID, ThoughtID: thoughtID,
ProjectID: projectID, ProjectID: projectID,
Name: m.Name.String(), Name: m.Name.String(),
@@ -81,371 +82,422 @@ func storedFileFromModel(m generatedmodels.ModelPublicStoredFiles) ext.StoredFil
} }
} }
func maintenanceTaskFromModel(m generatedmodels.ModelPublicMaintenanceTasks) ext.MaintenanceTask { // func maintenanceTaskFromModel(m generatedmodels.ModelPublicMaintenanceTasks) ext.MaintenanceTask {
var frequencyDays *int // var frequencyDays *int
if m.FrequencyDays.Valid { // if m.FrequencyDays.Valid {
n := int(m.FrequencyDays.Int64()) // n := int(m.FrequencyDays.Int64())
frequencyDays = &n // frequencyDays = &n
} // }
var lastCompleted *time.Time // var lastCompleted *time.Time
if m.LastCompleted.Valid { // if m.LastCompleted.Valid {
t := m.LastCompleted.Time() // t := m.LastCompleted.Time()
lastCompleted = &t // lastCompleted = &t
} // }
var nextDue *time.Time // var nextDue *time.Time
if m.NextDue.Valid { // if m.NextDue.Valid {
t := m.NextDue.Time() // t := m.NextDue.Time()
nextDue = &t // nextDue = &t
} // }
return ext.MaintenanceTask{ // return ext.MaintenanceTask{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
Name: m.Name.String(), // Name: m.Name.String(),
Category: m.Category.String(), // Category: m.Category.String(),
FrequencyDays: frequencyDays, // FrequencyDays: frequencyDays,
LastCompleted: lastCompleted, // LastCompleted: lastCompleted,
NextDue: nextDue, // NextDue: nextDue,
Priority: m.Priority.String(), // Priority: m.Priority.String(),
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
UpdatedAt: m.UpdatedAt.Time(), // UpdatedAt: m.UpdatedAt.Time(),
} // }
} // }
func maintenanceLogFromModel(m generatedmodels.ModelPublicMaintenanceLogs) ext.MaintenanceLog { // func maintenanceLogFromModel(m generatedmodels.ModelPublicMaintenanceLogs) ext.MaintenanceLog {
var cost *float64 // var cost *float64
if m.Cost.Valid { // if m.Cost.Valid {
v := m.Cost.Float64() // v := m.Cost.Float64()
cost = &v // cost = &v
} // }
return ext.MaintenanceLog{ // return ext.MaintenanceLog{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
TaskID: m.TaskID.UUID(), // TaskID: m.TaskID.UUID(),
CompletedAt: m.CompletedAt.Time(), // CompletedAt: m.CompletedAt.Time(),
PerformedBy: m.PerformedBy.String(), // PerformedBy: m.PerformedBy.String(),
Cost: cost, // Cost: cost,
Notes: m.Notes.String(), // Notes: m.Notes.String(),
NextAction: m.NextAction.String(), // NextAction: m.NextAction.String(),
} // }
} // }
func householdItemFromModel(m generatedmodels.ModelPublicHouseholdItems) ext.HouseholdItem { // func householdItemFromModel(m generatedmodels.ModelPublicHouseholdItems) ext.HouseholdItem {
details := map[string]any{} // details := map[string]any{}
if len(m.Details) > 0 { // if len(m.Details) > 0 {
if err := json.Unmarshal(m.Details, &details); err != nil { // if err := json.Unmarshal(m.Details, &details); err != nil {
details = map[string]any{} // details = map[string]any{}
} // }
} // }
return ext.HouseholdItem{ // return ext.HouseholdItem{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
Name: m.Name.String(), // Name: m.Name.String(),
Category: m.Category.String(), // Category: m.Category.String(),
Location: m.Location.String(), // Location: m.Location.String(),
Details: details, // Details: details,
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
UpdatedAt: m.UpdatedAt.Time(), // UpdatedAt: m.UpdatedAt.Time(),
} // }
} // }
func householdVendorFromModel(m generatedmodels.ModelPublicHouseholdVendors) ext.HouseholdVendor { // func householdVendorFromModel(m generatedmodels.ModelPublicHouseholdVendors) ext.HouseholdVendor {
var rating *int // var rating *int
if m.Rating.Valid { // if m.Rating.Valid {
v := int(m.Rating.Int64()) // v := int(m.Rating.Int64())
rating = &v // rating = &v
} // }
var lastUsed *time.Time // var lastUsed *time.Time
if m.LastUsed.Valid { // if m.LastUsed.Valid {
t := m.LastUsed.Time() // t := m.LastUsed.Time()
lastUsed = &t // lastUsed = &t
} // }
return ext.HouseholdVendor{ // return ext.HouseholdVendor{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
Name: m.Name.String(), // Name: m.Name.String(),
ServiceType: m.ServiceType.String(), // ServiceType: m.ServiceType.String(),
Phone: m.Phone.String(), // Phone: m.Phone.String(),
Email: m.Email.String(), // Email: m.Email.String(),
Website: m.Website.String(), // Website: m.Website.String(),
Notes: m.Notes.String(), // Notes: m.Notes.String(),
Rating: rating, // Rating: rating,
LastUsed: lastUsed, // LastUsed: lastUsed,
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
} // }
} // }
func familyMemberFromModel(m generatedmodels.ModelPublicFamilyMembers) ext.FamilyMember { // func familyMemberFromModel(m generatedmodels.ModelPublicFamilyMembers) ext.FamilyMember {
var birthDate *time.Time // var birthDate *time.Time
if m.BirthDate.Valid { // if m.BirthDate.Valid {
t := m.BirthDate.Time() // t := m.BirthDate.Time()
birthDate = &t // birthDate = &t
} // }
return ext.FamilyMember{ // return ext.FamilyMember{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
Name: m.Name.String(), // Name: m.Name.String(),
Relationship: m.Relationship.String(), // Relationship: m.Relationship.String(),
BirthDate: birthDate, // BirthDate: birthDate,
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
} // }
} // }
func activityFromModel(m generatedmodels.ModelPublicActivities, memberName string) ext.Activity { // func activityFromModel(m generatedmodels.ModelPublicActivities, memberName string) ext.Activity {
var familyMemberID *uuid.UUID // var familyMemberID *uuid.UUID
if m.FamilyMemberID.Valid { // if m.FamilyMemberID.Valid {
id := m.FamilyMemberID.UUID() // id := m.FamilyMemberID.UUID()
familyMemberID = &id // familyMemberID = &id
} // }
var startDate *time.Time // var startDate *time.Time
if m.StartDate.Valid { // if m.StartDate.Valid {
t := m.StartDate.Time() // t := m.StartDate.Time()
startDate = &t // startDate = &t
} // }
var endDate *time.Time // var endDate *time.Time
if m.EndDate.Valid { // if m.EndDate.Valid {
t := m.EndDate.Time() // t := m.EndDate.Time()
endDate = &t // endDate = &t
} // }
return ext.Activity{ // return ext.Activity{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
FamilyMemberID: familyMemberID, // FamilyMemberID: familyMemberID,
MemberName: memberName, // MemberName: memberName,
Title: m.Title.String(), // Title: m.Title.String(),
ActivityType: m.ActivityType.String(), // ActivityType: m.ActivityType.String(),
DayOfWeek: m.DayOfWeek.String(), // DayOfWeek: m.DayOfWeek.String(),
StartTime: m.StartTime.String(), // StartTime: m.StartTime.String(),
EndTime: m.EndTime.String(), // EndTime: m.EndTime.String(),
StartDate: startDate, // StartDate: startDate,
EndDate: endDate, // EndDate: endDate,
Location: m.Location.String(), // Location: m.Location.String(),
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
} // }
} // }
func importantDateFromModel(m generatedmodels.ModelPublicImportantDates, memberName string) ext.ImportantDate { // func importantDateFromModel(m generatedmodels.ModelPublicImportantDates, memberName string) ext.ImportantDate {
var familyMemberID *uuid.UUID // var familyMemberID *uuid.UUID
if m.FamilyMemberID.Valid { // if m.FamilyMemberID.Valid {
id := m.FamilyMemberID.UUID() // id := m.FamilyMemberID.UUID()
familyMemberID = &id // familyMemberID = &id
} // }
return ext.ImportantDate{ // return ext.ImportantDate{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
FamilyMemberID: familyMemberID, // FamilyMemberID: familyMemberID,
MemberName: memberName, // MemberName: memberName,
Title: m.Title.String(), // Title: m.Title.String(),
DateValue: m.DateValue.Time(), // DateValue: m.DateValue.Time(),
RecurringYearly: m.RecurringYearly, // RecurringYearly: m.RecurringYearly,
ReminderDaysBefore: int(m.ReminderDaysBefore.Int64()), // ReminderDaysBefore: int(m.ReminderDaysBefore),
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
} // }
} // }
func professionalContactFromModel(m generatedmodels.ModelPublicProfessionalContacts, tags []string) ext.ProfessionalContact { // func professionalContactFromModel(m generatedmodels.ModelPublicProfessionalContacts, tags []string) ext.ProfessionalContact {
var lastContacted *time.Time // var lastContacted *time.Time
if m.LastContacted.Valid { // if m.LastContacted.Valid {
t := m.LastContacted.Time() // t := m.LastContacted.Time()
lastContacted = &t // lastContacted = &t
} // }
var followUpDate *time.Time // var followUpDate *time.Time
if m.FollowUpDate.Valid { // if m.FollowUpDate.Valid {
t := m.FollowUpDate.Time() // t := m.FollowUpDate.Time()
followUpDate = &t // followUpDate = &t
} // }
return ext.ProfessionalContact{ // return ext.ProfessionalContact{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
Name: m.Name.String(), // Name: m.Name.String(),
Company: m.Company.String(), // Company: m.Company.String(),
Title: m.Title.String(), // Title: m.Title.String(),
Email: m.Email.String(), // Email: m.Email.String(),
Phone: m.Phone.String(), // Phone: m.Phone.String(),
LinkedInURL: m.LinkedinURL.String(), // LinkedInURL: m.LinkedinURL.String(),
HowWeMet: m.HowWeMet.String(), // HowWeMet: m.HowWeMet.String(),
Tags: tags, // Tags: tags,
Notes: m.Notes.String(), // Notes: m.Notes.String(),
LastContacted: lastContacted, // LastContacted: lastContacted,
FollowUpDate: followUpDate, // FollowUpDate: followUpDate,
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
UpdatedAt: m.UpdatedAt.Time(), // UpdatedAt: m.UpdatedAt.Time(),
} // }
} // }
func contactInteractionFromModel(m generatedmodels.ModelPublicContactInteractions) ext.ContactInteraction { // func contactInteractionFromModel(m generatedmodels.ModelPublicContactInteractions) ext.ContactInteraction {
return ext.ContactInteraction{ // return ext.ContactInteraction{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
ContactID: m.ContactID.UUID(), // ContactID: m.ContactID.UUID(),
InteractionType: m.InteractionType.String(), // InteractionType: m.InteractionType.String(),
OccurredAt: m.OccurredAt.Time(), // OccurredAt: m.OccurredAt.Time(),
Summary: m.Summary.String(), // Summary: m.Summary.String(),
FollowUpNeeded: m.FollowUpNeeded, // FollowUpNeeded: m.FollowUpNeeded,
FollowUpNotes: m.FollowUpNotes.String(), // FollowUpNotes: m.FollowUpNotes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
} // }
} // }
func opportunityFromModel(m generatedmodels.ModelPublicOpportunities) ext.Opportunity { // func opportunityFromModel(m generatedmodels.ModelPublicOpportunities) ext.Opportunity {
var contactID *uuid.UUID // var contactID *uuid.UUID
if m.ContactID.Valid { // if m.ContactID.Valid {
id := m.ContactID.UUID() // id := m.ContactID.UUID()
contactID = &id // contactID = &id
} // }
var value *float64 // var value *float64
if m.Value.Valid { // if m.Value.Valid {
v := m.Value.Float64() // v := m.Value.Float64()
value = &v // value = &v
} // }
var expectedCloseDate *time.Time // var expectedCloseDate *time.Time
if m.ExpectedCloseDate.Valid { // if m.ExpectedCloseDate.Valid {
t := m.ExpectedCloseDate.Time() // t := m.ExpectedCloseDate.Time()
expectedCloseDate = &t // expectedCloseDate = &t
} // }
return ext.Opportunity{ // return ext.Opportunity{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
ContactID: contactID, // ContactID: contactID,
Title: m.Title.String(), // Title: m.Title.String(),
Description: m.Description.String(), // Description: m.Description.String(),
Stage: m.Stage.String(), // Stage: m.Stage.String(),
Value: value, // Value: value,
ExpectedCloseDate: expectedCloseDate, // ExpectedCloseDate: expectedCloseDate,
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
UpdatedAt: m.UpdatedAt.Time(), // UpdatedAt: m.UpdatedAt.Time(),
} // }
} // }
func recipeFromModel(m generatedmodels.ModelPublicRecipes, tags []string) ext.Recipe { // func recipeFromModel(m generatedmodels.ModelPublicRecipes, tags []string) ext.Recipe {
var prepTimeMinutes *int // var prepTimeMinutes *int
if m.PrepTimeMinutes.Valid { // if m.PrepTimeMinutes.Valid {
v := int(m.PrepTimeMinutes.Int64()) // v := int(m.PrepTimeMinutes.Int64())
prepTimeMinutes = &v // prepTimeMinutes = &v
} // }
var cookTimeMinutes *int // var cookTimeMinutes *int
if m.CookTimeMinutes.Valid { // if m.CookTimeMinutes.Valid {
v := int(m.CookTimeMinutes.Int64()) // v := int(m.CookTimeMinutes.Int64())
cookTimeMinutes = &v // cookTimeMinutes = &v
} // }
var servings *int // var servings *int
if m.Servings.Valid { // if m.Servings.Valid {
v := int(m.Servings.Int64()) // v := int(m.Servings.Int64())
servings = &v // servings = &v
} // }
var rating *int // var rating *int
if m.Rating.Valid { // if m.Rating.Valid {
v := int(m.Rating.Int64()) // v := int(m.Rating.Int64())
rating = &v // rating = &v
} // }
recipe := ext.Recipe{ // recipe := ext.Recipe{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
Name: m.Name.String(), // Name: m.Name.String(),
Cuisine: m.Cuisine.String(), // Cuisine: m.Cuisine.String(),
PrepTimeMinutes: prepTimeMinutes, // PrepTimeMinutes: prepTimeMinutes,
CookTimeMinutes: cookTimeMinutes, // CookTimeMinutes: cookTimeMinutes,
Servings: servings, // Servings: servings,
Tags: tags, // Tags: tags,
Rating: rating, // Rating: rating,
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
UpdatedAt: m.UpdatedAt.Time(), // UpdatedAt: m.UpdatedAt.Time(),
} // }
if err := json.Unmarshal(m.Ingredients, &recipe.Ingredients); err != nil { // if err := json.Unmarshal(m.Ingredients, &recipe.Ingredients); err != nil {
recipe.Ingredients = []ext.Ingredient{} // recipe.Ingredients = []ext.Ingredient{}
} // }
if err := json.Unmarshal(m.Instructions, &recipe.Instructions); err != nil { // if err := json.Unmarshal(m.Instructions, &recipe.Instructions); err != nil {
recipe.Instructions = []string{} // recipe.Instructions = []string{}
} // }
return recipe // return recipe
} // }
func mealPlanEntryFromModel(m generatedmodels.ModelPublicMealPlans, recipeName string) ext.MealPlanEntry { // func mealPlanEntryFromModel(m generatedmodels.ModelPublicMealPlans, recipeName string) ext.MealPlanEntry {
var recipeID *uuid.UUID // var recipeID *uuid.UUID
if m.RecipeID.Valid { // if m.RecipeID.Valid {
id := m.RecipeID.UUID() // id := m.RecipeID.UUID()
recipeID = &id // recipeID = &id
} // }
var servings *int // var servings *int
if m.Servings.Valid { // if m.Servings.Valid {
v := int(m.Servings.Int64()) // v := int(m.Servings.Int64())
servings = &v // servings = &v
} // }
return ext.MealPlanEntry{ // return ext.MealPlanEntry{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
WeekStart: m.WeekStart.Time(), // WeekStart: m.WeekStart.Time(),
DayOfWeek: m.DayOfWeek.String(), // DayOfWeek: m.DayOfWeek.String(),
MealType: m.MealType.String(), // MealType: m.MealType.String(),
RecipeID: recipeID, // RecipeID: recipeID,
RecipeName: recipeName, // RecipeName: recipeName,
CustomMeal: m.CustomMeal.String(), // CustomMeal: m.CustomMeal.String(),
Servings: servings, // Servings: servings,
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
} // }
} // }
func shoppingListFromModel(m generatedmodels.ModelPublicShoppingLists) ext.ShoppingList { // func shoppingListFromModel(m generatedmodels.ModelPublicShoppingLists) ext.ShoppingList {
list := ext.ShoppingList{ // list := ext.ShoppingList{
ID: m.ID.UUID(), // ID: m.ID.UUID(),
WeekStart: m.WeekStart.Time(), // WeekStart: m.WeekStart.Time(),
Notes: m.Notes.String(), // Notes: m.Notes.String(),
CreatedAt: m.CreatedAt.Time(), // CreatedAt: m.CreatedAt.Time(),
UpdatedAt: m.UpdatedAt.Time(), // UpdatedAt: m.UpdatedAt.Time(),
} // }
if err := json.Unmarshal(m.Items, &list.Items); err != nil { // if err := json.Unmarshal(m.Items, &list.Items); err != nil {
list.Items = []ext.ShoppingItem{} // list.Items = []ext.ShoppingItem{}
} // }
return list // return list
} // }
func learningFromModel(m generatedmodels.ModelPublicLearnings, tags []string) ext.Learning { func planFromModel(m generatedmodels.ModelPublicPlans, tags []string) ext.Plan {
var projectID *uuid.UUID var projectID *int64
if m.ProjectID.Valid { if m.ProjectID.Valid {
id := m.ProjectID.UUID() id := m.ProjectID.Int64()
projectID = &id projectID = &id
} }
var relatedThoughtID *uuid.UUID var dueDate *time.Time
if m.DueDate.Valid {
t := m.DueDate.Time()
dueDate = &t
}
var completedAt *time.Time
if m.CompletedAt.Valid {
t := m.CompletedAt.Time()
completedAt = &t
}
var lastReviewedAt *time.Time
if m.LastReviewedAt.Valid {
t := m.LastReviewedAt.Time()
lastReviewedAt = &t
}
var supersedesPlanID *int64
if m.SupersedesPlanID.Valid {
id := m.SupersedesPlanID.Int64()
supersedesPlanID = &id
}
return ext.Plan{
ID: m.ID.Int64(),
GUID: m.GUID.UUID(),
Title: m.Title.String(),
Description: m.Description.String(),
Status: ext.PlanStatus(m.Status.String()),
Priority: ext.PlanPriority(m.Priority.String()),
ProjectID: projectID,
Owner: m.Owner.String(),
DueDate: dueDate,
CompletedAt: completedAt,
ReviewedBy: m.ReviewedBy.String(),
LastReviewedAt: lastReviewedAt,
SupersedesPlanID: supersedesPlanID,
Tags: tags,
CreatedAt: m.CreatedAt.Time(),
UpdatedAt: m.UpdatedAt.Time(),
}
}
func learningFromModel(m generatedmodels.ModelPublicLearnings, tags []string) ext.Learning {
var projectID *int64
if m.ProjectID.Valid {
id := m.ProjectID.Int64()
projectID = &id
}
var relatedThoughtID *int64
if m.RelatedThoughtID.Valid { if m.RelatedThoughtID.Valid {
id := m.RelatedThoughtID.UUID() id := m.RelatedThoughtID.Int64()
relatedThoughtID = &id relatedThoughtID = &id
} }
var relatedSkillID *uuid.UUID var relatedSkillID *int64
if m.RelatedSkillID.Valid { if m.RelatedSkillID.Valid {
id := m.RelatedSkillID.UUID() id := m.RelatedSkillID.Int64()
relatedSkillID = &id relatedSkillID = &id
} }
var duplicateOfLearningID *uuid.UUID var duplicateOfLearningID *int64
if m.DuplicateOfLearningID.Valid { if m.DuplicateOfLearningID.Valid {
id := m.DuplicateOfLearningID.UUID() id := m.DuplicateOfLearningID.Int64()
duplicateOfLearningID = &id duplicateOfLearningID = &id
} }
var supersedesLearningID *uuid.UUID var supersedesLearningID *int64
if m.SupersedesLearningID.Valid { if m.SupersedesLearningID.Valid {
id := m.SupersedesLearningID.UUID() id := m.SupersedesLearningID.Int64()
supersedesLearningID = &id supersedesLearningID = &id
} }
@@ -462,7 +514,8 @@ func learningFromModel(m generatedmodels.ModelPublicLearnings, tags []string) ex
} }
return ext.Learning{ return ext.Learning{
ID: m.ID.UUID(), ID: m.ID.Int64(),
GUID: m.GUID.UUID(),
Summary: m.Summary.String(), Summary: m.Summary.String(),
Details: m.Details.String(), Details: m.Details.String(),
Category: m.Category.String(), Category: m.Category.String(),
+478
View File
@@ -0,0 +1,478 @@
package store
import (
"context"
"fmt"
"strings"
"github.com/jackc/pgx/v5"
"git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types"
)
const planColumns = `
id, guid, title, description, status, priority, project_id, owner, due_date,
completed_at, reviewed_by, last_reviewed_at, supersedes_plan_id, tags::text[], created_at, updated_at`
func (db *DB) CreatePlan(ctx context.Context, plan ext.Plan) (ext.Plan, error) {
row := db.pool.QueryRow(ctx, `
insert into plans (title, description, status, priority, project_id, owner, due_date,
completed_at, reviewed_by, last_reviewed_at, supersedes_plan_id, tags)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
returning`+planColumns,
strings.TrimSpace(plan.Title),
strings.TrimSpace(plan.Description),
string(plan.Status),
string(plan.Priority),
plan.ProjectID,
nullableText(plan.Owner),
plan.DueDate,
plan.CompletedAt,
nullableText(plan.ReviewedBy),
plan.LastReviewedAt,
plan.SupersedesPlanID,
plan.Tags,
)
return scanPlan(row)
}
func (db *DB) GetPlan(ctx context.Context, id int64) (ext.Plan, error) {
row := db.pool.QueryRow(ctx, `select`+planColumns+` from plans where id = $1`, id)
plan, err := scanPlan(row)
if err != nil {
if err == pgx.ErrNoRows {
return ext.Plan{}, fmt.Errorf("plan not found: %d", id)
}
return ext.Plan{}, fmt.Errorf("get plan: %w", err)
}
return plan, nil
}
func (db *DB) GetPlanDetail(ctx context.Context, id int64) (ext.PlanDetail, error) {
plan, err := db.GetPlan(ctx, id)
if err != nil {
return ext.PlanDetail{}, err
}
dependsOn, err := db.listPlansByQuery(ctx, `
select`+planColumns+`
from plans p
join plan_dependencies pd on pd.depends_on_plan_id = p.id
where pd.plan_id = $1 order by p.title`, id)
if err != nil {
return ext.PlanDetail{}, fmt.Errorf("get plan depends_on: %w", err)
}
blocks, err := db.listPlansByQuery(ctx, `
select`+planColumns+`
from plans p
join plan_dependencies pd on pd.plan_id = p.id
where pd.depends_on_plan_id = $1 order by p.title`, id)
if err != nil {
return ext.PlanDetail{}, fmt.Errorf("get plan blocks: %w", err)
}
related, err := db.listPlansByQuery(ctx, `
select`+planColumns+`
from plans p
where p.id in (
select plan_b_id from plan_related_plans where plan_a_id = $1
union
select plan_a_id from plan_related_plans where plan_b_id = $1
) order by p.title`, id)
if err != nil {
return ext.PlanDetail{}, fmt.Errorf("get plan related: %w", err)
}
skills, err := db.ListPlanSkills(ctx, id)
if err != nil {
return ext.PlanDetail{}, fmt.Errorf("get plan skills: %w", err)
}
guardrails, err := db.ListPlanGuardrails(ctx, id)
if err != nil {
return ext.PlanDetail{}, fmt.Errorf("get plan guardrails: %w", err)
}
return ext.PlanDetail{
Plan: plan,
DependsOn: dependsOn,
Blocks: blocks,
RelatedPlans: related,
Skills: skills,
Guardrails: guardrails,
}, nil
}
func (db *DB) UpdatePlan(ctx context.Context, id int64, u ext.PlanUpdate) (ext.Plan, error) {
sets := []string{"updated_at = now()"}
args := []any{}
if u.Title != nil {
args = append(args, strings.TrimSpace(*u.Title))
sets = append(sets, fmt.Sprintf("title = $%d", len(args)))
}
if u.Description != nil {
args = append(args, strings.TrimSpace(*u.Description))
sets = append(sets, fmt.Sprintf("description = $%d", len(args)))
}
if u.Status != nil {
args = append(args, strings.TrimSpace(*u.Status))
sets = append(sets, fmt.Sprintf("status = $%d", len(args)))
}
if u.Priority != nil {
args = append(args, strings.TrimSpace(*u.Priority))
sets = append(sets, fmt.Sprintf("priority = $%d", len(args)))
}
if u.Owner != nil {
args = append(args, nullableText(*u.Owner))
sets = append(sets, fmt.Sprintf("owner = $%d", len(args)))
}
if u.ClearDueDate {
sets = append(sets, "due_date = null")
} else if u.DueDate != nil {
args = append(args, *u.DueDate)
sets = append(sets, fmt.Sprintf("due_date = $%d", len(args)))
}
if u.ClearCompletedAt {
sets = append(sets, "completed_at = null")
} else if u.CompletedAt != nil {
args = append(args, *u.CompletedAt)
sets = append(sets, fmt.Sprintf("completed_at = $%d", len(args)))
}
if u.ReviewedBy != nil {
args = append(args, nullableText(*u.ReviewedBy))
sets = append(sets, fmt.Sprintf("reviewed_by = $%d", len(args)))
}
if u.MarkReviewed {
sets = append(sets, "last_reviewed_at = now()")
}
if u.ClearSupersedesPlanID {
sets = append(sets, "supersedes_plan_id = null")
} else if u.SupersedesPlanID != nil {
args = append(args, *u.SupersedesPlanID)
sets = append(sets, fmt.Sprintf("supersedes_plan_id = $%d", len(args)))
}
if u.Tags != nil {
args = append(args, *u.Tags)
sets = append(sets, fmt.Sprintf("tags = $%d", len(args)))
}
args = append(args, id)
query := fmt.Sprintf(
"update plans set %s where id = $%d returning%s",
strings.Join(sets, ", "), len(args), planColumns,
)
row := db.pool.QueryRow(ctx, query, args...)
plan, err := scanPlan(row)
if err != nil {
if err == pgx.ErrNoRows {
return ext.Plan{}, fmt.Errorf("plan not found: %d", id)
}
return ext.Plan{}, fmt.Errorf("update plan: %w", err)
}
return plan, nil
}
func (db *DB) DeletePlan(ctx context.Context, id int64) error {
tag, err := db.pool.Exec(ctx, `delete from plans where id = $1`, id)
if err != nil {
return fmt.Errorf("delete plan: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("plan not found")
}
return nil
}
func (db *DB) ListPlans(ctx context.Context, filter ext.PlanFilter) ([]ext.Plan, error) {
args := make([]any, 0, 8)
conditions := make([]string, 0, 8)
if filter.ProjectID != nil {
args = append(args, *filter.ProjectID)
conditions = append(conditions, fmt.Sprintf("project_id = $%d", len(args)))
}
if v := strings.TrimSpace(filter.Status); v != "" {
args = append(args, v)
conditions = append(conditions, fmt.Sprintf("status = $%d", len(args)))
}
if v := strings.TrimSpace(filter.Priority); v != "" {
args = append(args, v)
conditions = append(conditions, fmt.Sprintf("priority = $%d", len(args)))
}
if v := strings.TrimSpace(filter.Owner); v != "" {
args = append(args, v)
conditions = append(conditions, fmt.Sprintf("owner = $%d", len(args)))
}
if v := strings.TrimSpace(filter.Tag); v != "" {
args = append(args, v)
conditions = append(conditions, fmt.Sprintf("$%d = any(tags)", len(args)))
}
if v := strings.TrimSpace(filter.Query); v != "" {
args = append(args, v)
conditions = append(conditions, fmt.Sprintf(
"to_tsvector('simple', title || ' ' || coalesce(description, '')) @@ websearch_to_tsquery('simple', $%d)", len(args)))
}
query := "select" + planColumns + " from plans"
if len(conditions) > 0 {
query += " where " + strings.Join(conditions, " and ")
}
query += " order by updated_at desc"
if filter.Limit > 0 {
args = append(args, filter.Limit)
query += fmt.Sprintf(" limit $%d", len(args))
}
return db.listPlansByQuery(ctx, query, args...)
}
// Dependencies
func (db *DB) AddPlanDependency(ctx context.Context, planID, dependsOnPlanID int64) error {
_, err := db.pool.Exec(ctx, `
insert into plan_dependencies (plan_id, depends_on_plan_id)
values ($1, $2)
on conflict do nothing
`, planID, dependsOnPlanID)
if err != nil {
return fmt.Errorf("add plan dependency: %w", err)
}
return nil
}
func (db *DB) RemovePlanDependency(ctx context.Context, planID, dependsOnPlanID int64) error {
tag, err := db.pool.Exec(ctx, `
delete from plan_dependencies where plan_id = $1 and depends_on_plan_id = $2
`, planID, dependsOnPlanID)
if err != nil {
return fmt.Errorf("remove plan dependency: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("plan dependency not found")
}
return nil
}
// Related Plans
func (db *DB) AddRelatedPlan(ctx context.Context, planAID, planBID int64) error {
a, b := canonicalPlanPair(planAID, planBID)
_, err := db.pool.Exec(ctx, `
insert into plan_related_plans (plan_a_id, plan_b_id)
values ($1, $2)
on conflict do nothing
`, a, b)
if err != nil {
return fmt.Errorf("add related plan: %w", err)
}
return nil
}
func (db *DB) RemoveRelatedPlan(ctx context.Context, planAID, planBID int64) error {
a, b := canonicalPlanPair(planAID, planBID)
tag, err := db.pool.Exec(ctx, `
delete from plan_related_plans where plan_a_id = $1 and plan_b_id = $2
`, a, b)
if err != nil {
return fmt.Errorf("remove related plan: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("related plan link not found")
}
return nil
}
// Plan Skills
func (db *DB) AddPlanSkill(ctx context.Context, planID, skillID int64) error {
_, err := db.pool.Exec(ctx, `
insert into plan_skills (plan_id, skill_id) values ($1, $2) on conflict do nothing
`, planID, skillID)
if err != nil {
return fmt.Errorf("add plan skill: %w", err)
}
return nil
}
func (db *DB) RemovePlanSkill(ctx context.Context, planID, skillID int64) error {
tag, err := db.pool.Exec(ctx, `
delete from plan_skills where plan_id = $1 and skill_id = $2
`, planID, skillID)
if err != nil {
return fmt.Errorf("remove plan skill: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("plan skill link not found")
}
return nil
}
func (db *DB) ListPlanSkills(ctx context.Context, planID int64) ([]ext.AgentSkill, error) {
rows, err := db.pool.Query(ctx, `
select s.id, s.name, s.description, s.content, s.tags::text[], s.created_at, s.updated_at
from agent_skills s
join plan_skills ps on ps.skill_id = s.id
where ps.plan_id = $1
order by s.name
`, planID)
if err != nil {
return nil, fmt.Errorf("list plan skills: %w", err)
}
defer rows.Close()
var skills []ext.AgentSkill
for rows.Next() {
var model generatedmodels.ModelPublicAgentSkills
var tags []string
if err := rows.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan plan skill: %w", err)
}
s := ext.AgentSkill{
ID: model.ID.Int64(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Tags: tags,
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}
if s.Tags == nil {
s.Tags = []string{}
}
skills = append(skills, s)
}
return skills, rows.Err()
}
// Plan Guardrails
func (db *DB) AddPlanGuardrail(ctx context.Context, planID, guardrailID int64) error {
_, err := db.pool.Exec(ctx, `
insert into plan_guardrails (plan_id, guardrail_id) values ($1, $2) on conflict do nothing
`, planID, guardrailID)
if err != nil {
return fmt.Errorf("add plan guardrail: %w", err)
}
return nil
}
func (db *DB) RemovePlanGuardrail(ctx context.Context, planID, guardrailID int64) error {
tag, err := db.pool.Exec(ctx, `
delete from plan_guardrails where plan_id = $1 and guardrail_id = $2
`, planID, guardrailID)
if err != nil {
return fmt.Errorf("remove plan guardrail: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("plan guardrail link not found")
}
return nil
}
func (db *DB) ListPlanGuardrails(ctx context.Context, planID int64) ([]ext.AgentGuardrail, error) {
rows, err := db.pool.Query(ctx, `
select g.id, g.name, g.description, g.content, g.severity, g.tags::text[], g.created_at, g.updated_at
from agent_guardrails g
join plan_guardrails pg on pg.guardrail_id = g.id
where pg.plan_id = $1
order by g.name
`, planID)
if err != nil {
return nil, fmt.Errorf("list plan guardrails: %w", err)
}
defer rows.Close()
var guardrails []ext.AgentGuardrail
for rows.Next() {
var model generatedmodels.ModelPublicAgentGuardrails
var tags []string
if err := rows.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &model.Severity, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan plan guardrail: %w", err)
}
g := ext.AgentGuardrail{
ID: model.ID.Int64(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Severity: model.Severity.String(),
Tags: tags,
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}
if g.Tags == nil {
g.Tags = []string{}
}
guardrails = append(guardrails, g)
}
return guardrails, rows.Err()
}
// helpers
type planScanner interface {
Scan(dest ...any) error
}
func scanPlan(row planScanner) (ext.Plan, error) {
var model generatedmodels.ModelPublicPlans
var tags []string
err := row.Scan(
&model.ID,
&model.GUID,
&model.Title,
&model.Description,
&model.Status,
&model.Priority,
&model.ProjectID,
&model.Owner,
&model.DueDate,
&model.CompletedAt,
&model.ReviewedBy,
&model.LastReviewedAt,
&model.SupersedesPlanID,
&tags,
&model.CreatedAt,
&model.UpdatedAt,
)
if err != nil {
return ext.Plan{}, err
}
if tags == nil {
tags = []string{}
}
return planFromModel(model, tags), nil
}
func (db *DB) listPlansByQuery(ctx context.Context, query string, args ...any) ([]ext.Plan, error) {
rows, err := db.pool.Query(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
plans := make([]ext.Plan, 0)
for rows.Next() {
plan, err := scanPlan(rows)
if err != nil {
return nil, fmt.Errorf("scan plan: %w", err)
}
plans = append(plans, plan)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate plans: %w", err)
}
return plans, nil
}
// canonicalPlanPair ensures the smaller ID is always plan_a_id to prevent duplicates.
func canonicalPlanPair(a, b int64) (int64, int64) {
if a <= b {
return a, b
}
return b, a
}
+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
}
+11 -11
View File
@@ -16,11 +16,11 @@ func (db *DB) CreateProject(ctx context.Context, name, description string) (thou
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
insert into projects (name, description) insert into projects (name, description)
values ($1, $2) values ($1, $2)
returning guid, name, description, created_at, last_active_at returning id, guid, name, description, created_at, last_active_at
`, name, description) `, name, description)
var model generatedmodels.ModelPublicProjects var model generatedmodels.ModelPublicProjects
if err := row.Scan(&model.GUID, &model.Name, &model.Description, &model.CreatedAt, &model.LastActiveAt); err != nil { if err := row.Scan(&model.ID, &model.GUID, &model.Name, &model.Description, &model.CreatedAt, &model.LastActiveAt); err != nil {
return thoughttypes.Project{}, fmt.Errorf("create project: %w", err) return thoughttypes.Project{}, fmt.Errorf("create project: %w", err)
} }
return projectFromModel(model), nil return projectFromModel(model), nil
@@ -46,7 +46,7 @@ func (db *DB) GetProject(ctx context.Context, nameOrID string) (thoughttypes.Pro
func (db *DB) getProjectByGUID(ctx context.Context, id uuid.UUID) (thoughttypes.Project, error) { func (db *DB) getProjectByGUID(ctx context.Context, id uuid.UUID) (thoughttypes.Project, error) {
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
select guid, name, description, created_at, last_active_at select id, guid, name, description, created_at, last_active_at
from projects from projects
where guid = $1 where guid = $1
`, id) `, id)
@@ -55,7 +55,7 @@ func (db *DB) getProjectByGUID(ctx context.Context, id uuid.UUID) (thoughttypes.
func (db *DB) getProjectByName(ctx context.Context, name string) (thoughttypes.Project, error) { func (db *DB) getProjectByName(ctx context.Context, name string) (thoughttypes.Project, error) {
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
select guid, name, description, created_at, last_active_at select id, guid, name, description, created_at, last_active_at
from projects from projects
where name = $1 where name = $1
`, name) `, name)
@@ -64,7 +64,7 @@ func (db *DB) getProjectByName(ctx context.Context, name string) (thoughttypes.P
func scanProject(row pgx.Row) (thoughttypes.Project, error) { func scanProject(row pgx.Row) (thoughttypes.Project, error) {
var model generatedmodels.ModelPublicProjects var model generatedmodels.ModelPublicProjects
if err := row.Scan(&model.GUID, &model.Name, &model.Description, &model.CreatedAt, &model.LastActiveAt); err != nil { if err := row.Scan(&model.ID, &model.GUID, &model.Name, &model.Description, &model.CreatedAt, &model.LastActiveAt); err != nil {
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
return thoughttypes.Project{}, err return thoughttypes.Project{}, err
} }
@@ -75,10 +75,10 @@ func scanProject(row pgx.Row) (thoughttypes.Project, error) {
func (db *DB) ListProjects(ctx context.Context) ([]thoughttypes.ProjectSummary, error) { func (db *DB) ListProjects(ctx context.Context) ([]thoughttypes.ProjectSummary, error) {
rows, err := db.pool.Query(ctx, ` rows, err := db.pool.Query(ctx, `
select p.guid, p.name, p.description, p.created_at, p.last_active_at, count(t.id) as thought_count select p.id, p.guid, p.name, p.description, p.created_at, p.last_active_at, count(t.id) as thought_count
from projects p from projects p
left join thoughts t on t.project_id = p.guid and t.archived_at is null left join thoughts t on t.project_id = p.id and t.archived_at is null
group by p.guid, p.name, p.description, p.created_at, p.last_active_at group by p.id, p.guid, p.name, p.description, p.created_at, p.last_active_at
order by p.last_active_at desc, p.created_at desc order by p.last_active_at desc, p.created_at desc
`) `)
if err != nil { if err != nil {
@@ -90,7 +90,7 @@ func (db *DB) ListProjects(ctx context.Context) ([]thoughttypes.ProjectSummary,
for rows.Next() { for rows.Next() {
var model generatedmodels.ModelPublicProjects var model generatedmodels.ModelPublicProjects
var thoughtCount int var thoughtCount int
if err := rows.Scan(&model.GUID, &model.Name, &model.Description, &model.CreatedAt, &model.LastActiveAt, &thoughtCount); err != nil { if err := rows.Scan(&model.ID, &model.GUID, &model.Name, &model.Description, &model.CreatedAt, &model.LastActiveAt, &thoughtCount); err != nil {
return nil, fmt.Errorf("scan project summary: %w", err) return nil, fmt.Errorf("scan project summary: %w", err)
} }
projects = append(projects, thoughttypes.ProjectSummary{ projects = append(projects, thoughttypes.ProjectSummary{
@@ -104,8 +104,8 @@ func (db *DB) ListProjects(ctx context.Context) ([]thoughttypes.ProjectSummary,
return projects, nil return projects, nil
} }
func (db *DB) TouchProject(ctx context.Context, id uuid.UUID) error { func (db *DB) TouchProject(ctx context.Context, id int64) error {
tag, err := db.pool.Exec(ctx, `update projects set last_active_at = now() where guid = $1`, id) tag, err := db.pool.Exec(ctx, `update projects set last_active_at = now() where id = $1`, id)
if err != nil { if err != nil {
return fmt.Errorf("touch project: %w", err) return fmt.Errorf("touch project: %w", err)
} }
+134 -72
View File
@@ -5,8 +5,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/google/uuid"
"git.warky.dev/wdevs/amcs/internal/generatedmodels" "git.warky.dev/wdevs/amcs/internal/generatedmodels"
ext "git.warky.dev/wdevs/amcs/internal/types" ext "git.warky.dev/wdevs/amcs/internal/types"
) )
@@ -17,24 +15,38 @@ func (db *DB) AddSkill(ctx context.Context, skill ext.AgentSkill) (ext.AgentSkil
if skill.Tags == nil { if skill.Tags == nil {
skill.Tags = []string{} 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{}
}
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
insert into agent_skills (name, description, content, tags) insert into agent_skills (name, description, content, tags, language_tags, library_tags, framework_tags, domain_tags)
values ($1, $2, $3, $4) values ($1, $2, $3, $4, $5, $6, $7, $8)
returning id, created_at, updated_at returning id, guid, created_at, updated_at
`, skill.Name, skill.Description, skill.Content, skill.Tags) `, skill.Name, skill.Description, skill.Content, skill.Tags,
skill.LanguageTags, skill.LibraryTags, skill.FrameworkTags, skill.DomainTags)
created := skill created := skill
var model generatedmodels.ModelPublicAgentSkills var model generatedmodels.ModelPublicAgentSkills
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { if err := row.Scan(&model.ID, &model.GUID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentSkill{}, fmt.Errorf("insert agent skill: %w", err) return ext.AgentSkill{}, fmt.Errorf("insert agent skill: %w", err)
} }
created.ID = model.ID.UUID() created.ID = model.ID.Int64()
created.GUID = model.GUID.UUID()
created.CreatedAt = model.CreatedAt.Time() created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() created.UpdatedAt = model.UpdatedAt.Time()
return created, nil return created, nil
} }
func (db *DB) RemoveSkill(ctx context.Context, id uuid.UUID) error { func (db *DB) RemoveSkill(ctx context.Context, id int64) error {
tag, err := db.pool.Exec(ctx, `delete from agent_skills where id = $1`, id) tag, err := db.pool.Exec(ctx, `delete from agent_skills where id = $1`, id)
if err != nil { if err != nil {
return fmt.Errorf("delete agent skill: %w", err) return fmt.Errorf("delete agent skill: %w", err)
@@ -46,11 +58,11 @@ func (db *DB) RemoveSkill(ctx context.Context, id uuid.UUID) error {
} }
func (db *DB) ListSkills(ctx context.Context, tag string) ([]ext.AgentSkill, error) { func (db *DB) ListSkills(ctx context.Context, tag string) ([]ext.AgentSkill, error) {
q := `select id, name, description, content, tags::text[], created_at, updated_at from agent_skills` q := `select id, name, description, content, tags::text[], language_tags::text[], library_tags::text[], framework_tags::text[], domain_tags::text[], created_at, updated_at from agent_skills`
args := []any{} args := []any{}
if t := strings.TrimSpace(tag); t != "" { if t := strings.TrimSpace(tag); t != "" {
args = append(args, t) args = append(args, t)
q += fmt.Sprintf(" where $%d = any(tags)", len(args)) q += fmt.Sprintf(" where $%d = any(tags) or $%d = any(language_tags) or $%d = any(library_tags) or $%d = any(framework_tags) or $%d = any(domain_tags)", len(args), len(args), len(args), len(args), len(args))
} }
q += " order by name" q += " order by name"
@@ -62,52 +74,109 @@ func (db *DB) ListSkills(ctx context.Context, tag string) ([]ext.AgentSkill, err
var skills []ext.AgentSkill var skills []ext.AgentSkill
for rows.Next() { for rows.Next() {
var model generatedmodels.ModelPublicAgentSkills s, err := scanSkill(rows)
var tags []string if err != nil {
if err := rows.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan agent skill: %w", err) return nil, fmt.Errorf("scan agent skill: %w", err)
} }
s := ext.AgentSkill{
ID: model.ID.UUID(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Tags: tags,
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}
if s.Tags == nil {
s.Tags = []string{}
}
skills = append(skills, s) skills = append(skills, s)
} }
return skills, rows.Err() return skills, rows.Err()
} }
func (db *DB) GetSkill(ctx context.Context, id uuid.UUID) (ext.AgentSkill, error) { const skillSelectCols = `id, name, description, content, tags::text[], language_tags::text[], library_tags::text[], framework_tags::text[], domain_tags::text[], created_at, updated_at`
row := db.pool.QueryRow(ctx, `
select id, name, description, content, tags::text[], created_at, updated_at
from agent_skills where id = $1
`, id)
type skillScanner interface {
Scan(dest ...any) error
}
func scanSkill(row skillScanner) (ext.AgentSkill, error) {
var model generatedmodels.ModelPublicAgentSkills var model generatedmodels.ModelPublicAgentSkills
var tags []string var tags, langTags, libTags, fwTags, domTags []string
if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil { if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &langTags, &libTags, &fwTags, &domTags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentSkill{}, err
}
nilToEmpty := func(s []string) []string {
if s == nil {
return []string{}
}
return s
}
return ext.AgentSkill{
ID: model.ID.Int64(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Tags: nilToEmpty(tags),
LanguageTags: nilToEmpty(langTags),
LibraryTags: nilToEmpty(libTags),
FrameworkTags: nilToEmpty(fwTags),
DomainTags: nilToEmpty(domTags),
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}, 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)
if err != nil {
return ext.AgentSkill{}, fmt.Errorf("get agent skill: %w", err) return ext.AgentSkill{}, fmt.Errorf("get agent skill: %w", err)
} }
s := ext.AgentSkill{ return s, nil
ID: model.ID.UUID(), }
func (db *DB) GetSkillByName(ctx context.Context, name string) (ext.AgentSkill, error) {
row := db.pool.QueryRow(ctx, `select `+skillSelectCols+` from agent_skills where name = $1`, name)
s, err := scanSkill(row)
if err != nil {
return ext.AgentSkill{}, fmt.Errorf("get agent skill by name: %w", err)
}
return s, nil
}
func (db *DB) GetGuardrailByName(ctx context.Context, name string) (ext.AgentGuardrail, error) {
row := db.pool.QueryRow(ctx, `
select id, name, description, content, severity, tags::text[], created_at, updated_at
from agent_guardrails where name = $1
`, name)
var model generatedmodels.ModelPublicAgentGuardrails
var tags []string
if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &model.Severity, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentGuardrail{}, fmt.Errorf("get agent guardrail by name: %w", err)
}
g := ext.AgentGuardrail{
ID: model.ID.Int64(),
Name: model.Name.String(), Name: model.Name.String(),
Description: model.Description.String(), Description: model.Description.String(),
Content: model.Content.String(), Content: model.Content.String(),
Severity: model.Severity.String(),
Tags: tags, Tags: tags,
CreatedAt: model.CreatedAt.Time(), CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(), UpdatedAt: model.UpdatedAt.Time(),
} }
if s.Tags == nil { if g.Tags == nil {
s.Tags = []string{} g.Tags = []string{}
} }
return s, nil return g, nil
} }
// Agent Guardrails // Agent Guardrails
@@ -122,21 +191,22 @@ func (db *DB) AddGuardrail(ctx context.Context, g ext.AgentGuardrail) (ext.Agent
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
insert into agent_guardrails (name, description, content, severity, tags) insert into agent_guardrails (name, description, content, severity, tags)
values ($1, $2, $3, $4, $5) values ($1, $2, $3, $4, $5)
returning id, created_at, updated_at returning id, guid, created_at, updated_at
`, g.Name, g.Description, g.Content, g.Severity, g.Tags) `, g.Name, g.Description, g.Content, g.Severity, g.Tags)
created := g created := g
var model generatedmodels.ModelPublicAgentGuardrails var model generatedmodels.ModelPublicAgentGuardrails
if err := row.Scan(&model.ID, &model.CreatedAt, &model.UpdatedAt); err != nil { if err := row.Scan(&model.ID, &model.GUID, &model.CreatedAt, &model.UpdatedAt); err != nil {
return ext.AgentGuardrail{}, fmt.Errorf("insert agent guardrail: %w", err) return ext.AgentGuardrail{}, fmt.Errorf("insert agent guardrail: %w", err)
} }
created.ID = model.ID.UUID() created.ID = model.ID.Int64()
created.GUID = model.GUID.UUID()
created.CreatedAt = model.CreatedAt.Time() created.CreatedAt = model.CreatedAt.Time()
created.UpdatedAt = model.UpdatedAt.Time() created.UpdatedAt = model.UpdatedAt.Time()
return created, nil return created, nil
} }
func (db *DB) RemoveGuardrail(ctx context.Context, id uuid.UUID) error { func (db *DB) RemoveGuardrail(ctx context.Context, id int64) error {
tag, err := db.pool.Exec(ctx, `delete from agent_guardrails where id = $1`, id) tag, err := db.pool.Exec(ctx, `delete from agent_guardrails where id = $1`, id)
if err != nil { if err != nil {
return fmt.Errorf("delete agent guardrail: %w", err) return fmt.Errorf("delete agent guardrail: %w", err)
@@ -180,7 +250,7 @@ func (db *DB) ListGuardrails(ctx context.Context, tag, severity string) ([]ext.A
return nil, fmt.Errorf("scan agent guardrail: %w", err) return nil, fmt.Errorf("scan agent guardrail: %w", err)
} }
g := ext.AgentGuardrail{ g := ext.AgentGuardrail{
ID: model.ID.UUID(), ID: model.ID.Int64(),
Name: model.Name.String(), Name: model.Name.String(),
Description: model.Description.String(), Description: model.Description.String(),
Content: model.Content.String(), Content: model.Content.String(),
@@ -197,7 +267,7 @@ func (db *DB) ListGuardrails(ctx context.Context, tag, severity string) ([]ext.A
return guardrails, rows.Err() return guardrails, rows.Err()
} }
func (db *DB) GetGuardrail(ctx context.Context, id uuid.UUID) (ext.AgentGuardrail, error) { func (db *DB) GetGuardrail(ctx context.Context, id int64) (ext.AgentGuardrail, error) {
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
select id, name, description, content, severity, tags::text[], created_at, updated_at select id, name, description, content, severity, tags::text[], created_at, updated_at
from agent_guardrails where id = $1 from agent_guardrails where id = $1
@@ -209,7 +279,7 @@ func (db *DB) GetGuardrail(ctx context.Context, id uuid.UUID) (ext.AgentGuardrai
return ext.AgentGuardrail{}, fmt.Errorf("get agent guardrail: %w", err) return ext.AgentGuardrail{}, fmt.Errorf("get agent guardrail: %w", err)
} }
g := ext.AgentGuardrail{ g := ext.AgentGuardrail{
ID: model.ID.UUID(), ID: model.ID.Int64(),
Name: model.Name.String(), Name: model.Name.String(),
Description: model.Description.String(), Description: model.Description.String(),
Content: model.Content.String(), Content: model.Content.String(),
@@ -226,19 +296,19 @@ func (db *DB) GetGuardrail(ctx context.Context, id uuid.UUID) (ext.AgentGuardrai
// Project Skills // Project Skills
func (db *DB) AddProjectSkill(ctx context.Context, projectID, skillID uuid.UUID) error { func (db *DB) AddProjectSkill(ctx context.Context, projectID, skillID int64, override bool) error {
_, err := db.pool.Exec(ctx, ` _, err := db.pool.Exec(ctx, `
insert into project_skills (project_id, skill_id) insert into project_skills (project_id, skill_id, override)
values ($1, $2) values ($1, $2, $3)
on conflict do nothing on conflict (project_id, skill_id) do update set override = excluded.override
`, projectID, skillID) `, projectID, skillID, override)
if err != nil { if err != nil {
return fmt.Errorf("add project skill: %w", err) return fmt.Errorf("add project skill: %w", err)
} }
return nil return nil
} }
func (db *DB) RemoveProjectSkill(ctx context.Context, projectID, skillID uuid.UUID) error { func (db *DB) RemoveProjectSkill(ctx context.Context, projectID, skillID int64) error {
tag, err := db.pool.Exec(ctx, ` tag, err := db.pool.Exec(ctx, `
delete from project_skills where project_id = $1 and skill_id = $2 delete from project_skills where project_id = $1 and skill_id = $2
`, projectID, skillID) `, projectID, skillID)
@@ -251,9 +321,11 @@ func (db *DB) RemoveProjectSkill(ctx context.Context, projectID, skillID uuid.UU
return nil return nil
} }
func (db *DB) ListProjectSkills(ctx context.Context, projectID uuid.UUID) ([]ext.AgentSkill, error) { func (db *DB) ListProjectSkills(ctx context.Context, projectID int64) ([]ext.AgentSkill, error) {
rows, err := db.pool.Query(ctx, ` rows, err := db.pool.Query(ctx, `
select s.id, s.name, s.description, s.content, s.tags::text[], s.created_at, s.updated_at 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 from agent_skills s
join project_skills ps on ps.skill_id = s.id join project_skills ps on ps.skill_id = s.id
where ps.project_id = $1 where ps.project_id = $1
@@ -266,23 +338,13 @@ func (db *DB) ListProjectSkills(ctx context.Context, projectID uuid.UUID) ([]ext
var skills []ext.AgentSkill var skills []ext.AgentSkill
for rows.Next() { for rows.Next() {
var model generatedmodels.ModelPublicAgentSkills var s ext.AgentSkill
var tags []string if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Content, &s.Tags,
if err := rows.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil { &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) return nil, fmt.Errorf("scan project skill: %w", err)
} }
s := ext.AgentSkill{ normalizeSkillSlices(&s)
ID: model.ID.UUID(),
Name: model.Name.String(),
Description: model.Description.String(),
Content: model.Content.String(),
Tags: tags,
CreatedAt: model.CreatedAt.Time(),
UpdatedAt: model.UpdatedAt.Time(),
}
if s.Tags == nil {
s.Tags = []string{}
}
skills = append(skills, s) skills = append(skills, s)
} }
return skills, rows.Err() return skills, rows.Err()
@@ -290,7 +352,7 @@ func (db *DB) ListProjectSkills(ctx context.Context, projectID uuid.UUID) ([]ext
// Project Guardrails // Project Guardrails
func (db *DB) AddProjectGuardrail(ctx context.Context, projectID, guardrailID uuid.UUID) error { func (db *DB) AddProjectGuardrail(ctx context.Context, projectID, guardrailID int64) error {
_, err := db.pool.Exec(ctx, ` _, err := db.pool.Exec(ctx, `
insert into project_guardrails (project_id, guardrail_id) insert into project_guardrails (project_id, guardrail_id)
values ($1, $2) values ($1, $2)
@@ -302,7 +364,7 @@ func (db *DB) AddProjectGuardrail(ctx context.Context, projectID, guardrailID uu
return nil return nil
} }
func (db *DB) RemoveProjectGuardrail(ctx context.Context, projectID, guardrailID uuid.UUID) error { func (db *DB) RemoveProjectGuardrail(ctx context.Context, projectID, guardrailID int64) error {
tag, err := db.pool.Exec(ctx, ` tag, err := db.pool.Exec(ctx, `
delete from project_guardrails where project_id = $1 and guardrail_id = $2 delete from project_guardrails where project_id = $1 and guardrail_id = $2
`, projectID, guardrailID) `, projectID, guardrailID)
@@ -315,7 +377,7 @@ func (db *DB) RemoveProjectGuardrail(ctx context.Context, projectID, guardrailID
return nil return nil
} }
func (db *DB) ListProjectGuardrails(ctx context.Context, projectID uuid.UUID) ([]ext.AgentGuardrail, error) { func (db *DB) ListProjectGuardrails(ctx context.Context, projectID int64) ([]ext.AgentGuardrail, error) {
rows, err := db.pool.Query(ctx, ` rows, err := db.pool.Query(ctx, `
select g.id, g.name, g.description, g.content, g.severity, g.tags::text[], g.created_at, g.updated_at select g.id, g.name, g.description, g.content, g.severity, g.tags::text[], g.created_at, g.updated_at
from agent_guardrails g from agent_guardrails g
@@ -336,7 +398,7 @@ func (db *DB) ListProjectGuardrails(ctx context.Context, projectID uuid.UUID) ([
return nil, fmt.Errorf("scan project guardrail: %w", err) return nil, fmt.Errorf("scan project guardrail: %w", err)
} }
g := ext.AgentGuardrail{ g := ext.AgentGuardrail{
ID: model.ID.UUID(), ID: model.ID.Int64(),
Name: model.Name.String(), Name: model.Name.String(),
Description: model.Description.String(), Description: model.Description.String(),
Content: model.Content.String(), Content: model.Content.String(),
+148
View File
@@ -0,0 +1,148 @@
package store
import (
"context"
"fmt"
"github.com/google/uuid"
"git.warky.dev/wdevs/amcs/internal/generatedmodels"
thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
)
// LinkThoughtLearning creates or updates an association between a thought (by GUID) and a learning (by numeric ID).
// If the pair already exists, the relation label is updated.
func (db *DB) LinkThoughtLearning(ctx context.Context, thoughtGUID uuid.UUID, learningID int64, relation string) error {
_, err := db.pool.Exec(ctx, `
insert into thought_learning_links (thought_id, learning_id, relation)
select t.id, $2, $3
from thoughts t
where t.guid = $1
on conflict (thought_id, learning_id) do update set relation = excluded.relation
`, thoughtGUID, learningID, relation)
if err != nil {
return fmt.Errorf("link thought learning: %w", err)
}
return nil
}
// UnlinkThoughtLearning removes the association between a thought (by GUID) and a learning (by numeric ID).
func (db *DB) UnlinkThoughtLearning(ctx context.Context, thoughtGUID uuid.UUID, learningID int64) error {
_, err := db.pool.Exec(ctx, `
delete from thought_learning_links
where thought_id = (select id from thoughts where guid = $1)
and learning_id = $2
`, thoughtGUID, learningID)
if err != nil {
return fmt.Errorf("unlink thought learning: %w", err)
}
return nil
}
// GetLinkedLearnings returns all learnings explicitly associated with a thought (by GUID).
func (db *DB) GetLinkedLearnings(ctx context.Context, thoughtGUID uuid.UUID) ([]thoughttypes.LinkedLearning, error) {
rows, err := db.pool.Query(ctx, `
select l.id, l.guid, l.summary, l.details, l.category, l.area, l.status, l.priority, l.confidence,
l.action_required, l.source_type, l.source_ref, l.project_id, l.related_thought_id,
l.related_skill_id, l.reviewed_by, l.reviewed_at, l.duplicate_of_learning_id,
l.supersedes_learning_id, l.tags::text[], l.created_at, l.updated_at,
tll.relation, tll.created_at as linked_at
from thought_learning_links tll
join learnings l on l.id = tll.learning_id
where tll.thought_id = (select id from thoughts where guid = $1)
order by tll.created_at desc
`, thoughtGUID)
if err != nil {
return nil, fmt.Errorf("query linked learnings: %w", err)
}
defer rows.Close()
items := make([]thoughttypes.LinkedLearning, 0)
for rows.Next() {
var tags []string
var linked thoughttypes.LinkedLearning
var m generatedmodels.ModelPublicLearnings
if err := rows.Scan(
&m.ID,
&m.GUID,
&m.Summary,
&m.Details,
&m.Category,
&m.Area,
&m.Status,
&m.Priority,
&m.Confidence,
&m.ActionRequired,
&m.SourceType,
&m.SourceRef,
&m.ProjectID,
&m.RelatedThoughtID,
&m.RelatedSkillID,
&m.ReviewedBy,
&m.ReviewedAt,
&m.DuplicateOfLearningID,
&m.SupersedesLearningID,
&tags,
&m.CreatedAt,
&m.UpdatedAt,
&linked.Relation,
&linked.CreatedAt,
); err != nil {
return nil, fmt.Errorf("scan linked learning: %w", err)
}
linked.Learning = learningFromModel(m, tags)
items = append(items, linked)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate linked learnings: %w", err)
}
return items, nil
}
// GetLinkedThoughtsForLearning returns all thoughts explicitly associated with a learning (by numeric ID).
func (db *DB) GetLinkedThoughtsForLearning(ctx context.Context, learningID int64) ([]thoughttypes.LinkedLearningThought, error) {
rows, err := db.pool.Query(ctx, `
select t.id, t.guid, t.content, t.metadata, t.project_id, t.archived_at, t.created_at, t.updated_at,
tll.relation, tll.created_at as linked_at
from thought_learning_links tll
join thoughts t on t.id = tll.thought_id
where tll.learning_id = $1
order by tll.created_at desc
`, learningID)
if err != nil {
return nil, fmt.Errorf("query linked thoughts for learning: %w", err)
}
defer rows.Close()
items := make([]thoughttypes.LinkedLearningThought, 0)
for rows.Next() {
var linked thoughttypes.LinkedLearningThought
var m generatedmodels.ModelPublicThoughts
if err := rows.Scan(
&m.ID,
&m.GUID,
&m.Content,
&m.Metadata,
&m.ProjectID,
&m.ArchivedAt,
&m.CreatedAt,
&m.UpdatedAt,
&linked.Relation,
&linked.CreatedAt,
); err != nil {
return nil, fmt.Errorf("scan linked thought for learning: %w", err)
}
thought, err := thoughtFromModel(m)
if err != nil {
return nil, fmt.Errorf("map linked thought for learning: %w", err)
}
linked.Thought = thought
items = append(items, linked)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate linked thoughts for learning: %w", err)
}
return items, nil
}
+63 -40
View File
@@ -33,12 +33,12 @@ func (db *DB) InsertThought(ctx context.Context, thought thoughttypes.Thought, e
row := tx.QueryRow(ctx, ` row := tx.QueryRow(ctx, `
insert into thoughts (content, metadata, project_id) insert into thoughts (content, metadata, project_id)
values ($1, $2::jsonb, $3) values ($1, $2::jsonb, $3)
returning guid, created_at, updated_at returning id, guid, created_at, updated_at
`, thought.Content, metadata, thought.ProjectID) `, thought.Content, metadata, thought.ProjectID)
created := thought created := thought
created.Embedding = nil created.Embedding = nil
if err := row.Scan(&created.ID, &created.CreatedAt, &created.UpdatedAt); err != nil { if err := row.Scan(&created.ID, &created.GUID, &created.CreatedAt, &created.UpdatedAt); err != nil {
return thoughttypes.Thought{}, fmt.Errorf("insert thought: %w", err) return thoughttypes.Thought{}, fmt.Errorf("insert thought: %w", err)
} }
@@ -132,7 +132,7 @@ func (db *DB) ListThoughts(ctx context.Context, filter thoughttypes.ListFilter)
} }
query := ` query := `
select guid, content, metadata, project_id, archived_at, created_at, updated_at select id, guid, content, metadata, project_id, archived_at, created_at, updated_at
from thoughts from thoughts
` `
if len(conditions) > 0 { if len(conditions) > 0 {
@@ -151,7 +151,7 @@ func (db *DB) ListThoughts(ctx context.Context, filter thoughttypes.ListFilter)
thoughts := make([]thoughttypes.Thought, 0, filter.Limit) thoughts := make([]thoughttypes.Thought, 0, filter.Limit)
for rows.Next() { for rows.Next() {
var model generatedmodels.ModelPublicThoughts var model generatedmodels.ModelPublicThoughts
if err := rows.Scan(&model.GUID, &model.Content, &model.Metadata, &model.ProjectID, &model.ArchivedAt, &model.CreatedAt, &model.UpdatedAt); err != nil { if err := rows.Scan(&model.ID, &model.GUID, &model.Content, &model.Metadata, &model.ProjectID, &model.ArchivedAt, &model.CreatedAt, &model.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan listed thought: %w", err) return nil, fmt.Errorf("scan listed thought: %w", err)
} }
thought, err := thoughtFromModel(model) thought, err := thoughtFromModel(model)
@@ -218,13 +218,13 @@ func (db *DB) Stats(ctx context.Context) (thoughttypes.ThoughtStats, error) {
func (db *DB) GetThought(ctx context.Context, id uuid.UUID) (thoughttypes.Thought, error) { func (db *DB) GetThought(ctx context.Context, id uuid.UUID) (thoughttypes.Thought, error) {
row := db.pool.QueryRow(ctx, ` row := db.pool.QueryRow(ctx, `
select guid, content, metadata, project_id, archived_at, created_at, updated_at select id, guid, content, metadata, project_id, archived_at, created_at, updated_at
from thoughts from thoughts
where guid = $1 where guid = $1
`, id) `, id)
var model generatedmodels.ModelPublicThoughts var model generatedmodels.ModelPublicThoughts
if err := row.Scan(&model.GUID, &model.Content, &model.Metadata, &model.ProjectID, &model.ArchivedAt, &model.CreatedAt, &model.UpdatedAt); err != nil { if err := row.Scan(&model.ID, &model.GUID, &model.Content, &model.Metadata, &model.ProjectID, &model.ArchivedAt, &model.CreatedAt, &model.UpdatedAt); err != nil {
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
return thoughttypes.Thought{}, err return thoughttypes.Thought{}, err
} }
@@ -239,7 +239,30 @@ func (db *DB) GetThought(ctx context.Context, id uuid.UUID) (thoughttypes.Though
return thought, nil return thought, nil
} }
func (db *DB) UpdateThought(ctx context.Context, id uuid.UUID, content string, embedding []float32, embeddingModel string, metadata thoughttypes.ThoughtMetadata, projectID *uuid.UUID) (thoughttypes.Thought, error) { func (db *DB) GetThoughtByID(ctx context.Context, id int64) (thoughttypes.Thought, error) {
row := db.pool.QueryRow(ctx, `
select id, guid, content, metadata, project_id, archived_at, created_at, updated_at
from thoughts
where id = $1
`, id)
var model generatedmodels.ModelPublicThoughts
if err := row.Scan(&model.ID, &model.GUID, &model.Content, &model.Metadata, &model.ProjectID, &model.ArchivedAt, &model.CreatedAt, &model.UpdatedAt); err != nil {
if err == pgx.ErrNoRows {
return thoughttypes.Thought{}, err
}
return thoughttypes.Thought{}, fmt.Errorf("get thought by id: %w", err)
}
thought, err := thoughtFromModel(model)
if err != nil {
return thoughttypes.Thought{}, fmt.Errorf("map thought: %w", err)
}
return thought, nil
}
func (db *DB) UpdateThought(ctx context.Context, id uuid.UUID, content string, embedding []float32, embeddingModel string, metadata thoughttypes.ThoughtMetadata, projectID *int64) (thoughttypes.Thought, error) {
metadataBytes, err := json.Marshal(metadata) metadataBytes, err := json.Marshal(metadata)
if err != nil { if err != nil {
return thoughttypes.Thought{}, fmt.Errorf("marshal updated metadata: %w", err) return thoughttypes.Thought{}, fmt.Errorf("marshal updated metadata: %w", err)
@@ -271,7 +294,7 @@ func (db *DB) UpdateThought(ctx context.Context, id uuid.UUID, content string, e
if len(embedding) > 0 && embeddingModel != "" { if len(embedding) > 0 && embeddingModel != "" {
if _, err := tx.Exec(ctx, ` if _, err := tx.Exec(ctx, `
insert into embeddings (thought_id, model, dim, embedding) insert into embeddings (thought_id, model, dim, embedding)
values ($1, $2, $3, $4) select id, $2, $3, $4 from thoughts where guid = $1
on conflict (thought_id, model) do update on conflict (thought_id, model) do update
set embedding = excluded.embedding, set embedding = excluded.embedding,
dim = excluded.dim, dim = excluded.dim,
@@ -288,7 +311,7 @@ func (db *DB) UpdateThought(ctx context.Context, id uuid.UUID, content string, e
return db.GetThought(ctx, id) return db.GetThought(ctx, id)
} }
func (db *DB) UpdateThoughtMetadata(ctx context.Context, id uuid.UUID, metadata thoughttypes.ThoughtMetadata) (thoughttypes.Thought, error) { func (db *DB) UpdateThoughtMetadata(ctx context.Context, id int64, metadata thoughttypes.ThoughtMetadata) (thoughttypes.Thought, error) {
metadataBytes, err := json.Marshal(metadata) metadataBytes, err := json.Marshal(metadata)
if err != nil { if err != nil {
return thoughttypes.Thought{}, fmt.Errorf("marshal updated metadata: %w", err) return thoughttypes.Thought{}, fmt.Errorf("marshal updated metadata: %w", err)
@@ -298,7 +321,7 @@ func (db *DB) UpdateThoughtMetadata(ctx context.Context, id uuid.UUID, metadata
update thoughts update thoughts
set metadata = $2::jsonb, set metadata = $2::jsonb,
updated_at = now() updated_at = now()
where guid = $1 where id = $1
`, id, metadataBytes) `, id, metadataBytes)
if err != nil { if err != nil {
return thoughttypes.Thought{}, fmt.Errorf("update thought metadata: %w", err) return thoughttypes.Thought{}, fmt.Errorf("update thought metadata: %w", err)
@@ -307,7 +330,7 @@ func (db *DB) UpdateThoughtMetadata(ctx context.Context, id uuid.UUID, metadata
return thoughttypes.Thought{}, pgx.ErrNoRows return thoughttypes.Thought{}, pgx.ErrNoRows
} }
return db.GetThought(ctx, id) return db.GetThoughtByID(ctx, id)
} }
func (db *DB) DeleteThought(ctx context.Context, id uuid.UUID) error { func (db *DB) DeleteThought(ctx context.Context, id uuid.UUID) error {
@@ -332,7 +355,7 @@ func (db *DB) ArchiveThought(ctx context.Context, id uuid.UUID) error {
return nil return nil
} }
func (db *DB) RecentThoughts(ctx context.Context, projectID *uuid.UUID, limit int, days int) ([]thoughttypes.Thought, error) { func (db *DB) RecentThoughts(ctx context.Context, projectID *int64, limit int, days int) ([]thoughttypes.Thought, error) {
filter := thoughttypes.ListFilter{ filter := thoughttypes.ListFilter{
Limit: limit, Limit: limit,
ProjectID: projectID, ProjectID: projectID,
@@ -342,7 +365,7 @@ func (db *DB) RecentThoughts(ctx context.Context, projectID *uuid.UUID, limit in
return db.ListThoughts(ctx, filter) return db.ListThoughts(ctx, filter)
} }
func (db *DB) ListThoughtsPendingMetadataRetry(ctx context.Context, limit int, projectID *uuid.UUID, includeArchived bool, olderThanDays int) ([]thoughttypes.Thought, error) { func (db *DB) ListThoughtsPendingMetadataRetry(ctx context.Context, limit int, projectID *int64, includeArchived bool, olderThanDays int) ([]thoughttypes.Thought, error) {
args := make([]any, 0, 4) args := make([]any, 0, 4)
conditions := []string{ conditions := []string{
"(metadata->>'metadata_status' = 'pending' or metadata->>'metadata_status' = 'failed')", "(metadata->>'metadata_status' = 'pending' or metadata->>'metadata_status' = 'failed')",
@@ -361,7 +384,7 @@ func (db *DB) ListThoughtsPendingMetadataRetry(ctx context.Context, limit int, p
} }
query := ` query := `
select guid, content, metadata, project_id, archived_at, created_at, updated_at select id, guid, content, metadata, project_id, archived_at, created_at, updated_at
from thoughts from thoughts
where ` + strings.Join(conditions, " and ") where ` + strings.Join(conditions, " and ")
@@ -376,12 +399,12 @@ func (db *DB) ListThoughtsPendingMetadataRetry(ctx context.Context, limit int, p
thoughts := make([]thoughttypes.Thought, 0, limit) thoughts := make([]thoughttypes.Thought, 0, limit)
for rows.Next() { for rows.Next() {
var thought thoughttypes.Thought var model generatedmodels.ModelPublicThoughts
var metadataBytes []byte if err := rows.Scan(&model.ID, &model.GUID, &model.Content, &model.Metadata, &model.ProjectID, &model.ArchivedAt, &model.CreatedAt, &model.UpdatedAt); err != nil {
if err := rows.Scan(&thought.ID, &thought.Content, &metadataBytes, &thought.ProjectID, &thought.ArchivedAt, &thought.CreatedAt, &thought.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan pending metadata retry thought: %w", err) return nil, fmt.Errorf("scan pending metadata retry thought: %w", err)
} }
if err := json.Unmarshal(metadataBytes, &thought.Metadata); err != nil { thought, err := thoughtFromModel(model)
if err != nil {
return nil, fmt.Errorf("decode pending metadata retry thought: %w", err) return nil, fmt.Errorf("decode pending metadata retry thought: %w", err)
} }
thoughts = append(thoughts, thought) thoughts = append(thoughts, thought)
@@ -394,7 +417,7 @@ func (db *DB) ListThoughtsPendingMetadataRetry(ctx context.Context, limit int, p
return thoughts, nil return thoughts, nil
} }
func (db *DB) SearchSimilarThoughts(ctx context.Context, embedding []float32, embeddingModel string, threshold float64, limit int, projectID *uuid.UUID, excludeID *uuid.UUID) ([]thoughttypes.SearchResult, error) { func (db *DB) SearchSimilarThoughts(ctx context.Context, embedding []float32, embeddingModel string, threshold float64, limit int, projectID *int64, excludeID *uuid.UUID) ([]thoughttypes.SearchResult, error) {
args := []any{pgvector.NewVector(embedding), threshold, embeddingModel} args := []any{pgvector.NewVector(embedding), threshold, embeddingModel}
conditions := []string{ conditions := []string{
"t.archived_at is null", "t.archived_at is null",
@@ -412,9 +435,9 @@ func (db *DB) SearchSimilarThoughts(ctx context.Context, embedding []float32, em
args = append(args, limit) args = append(args, limit)
query := ` query := `
select t.guid, t.content, t.metadata, 1 - (e.embedding <=> $1) as similarity, t.created_at select t.id, t.content, t.metadata, 1 - (e.embedding <=> $1) as similarity, t.created_at
from thoughts t from thoughts t
join embeddings e on e.thought_id = t.guid join embeddings e on e.thought_id = t.id
where ` + strings.Join(conditions, " and ") + fmt.Sprintf(` where ` + strings.Join(conditions, " and ") + fmt.Sprintf(`
order by e.embedding <=> $1 order by e.embedding <=> $1
limit $%d`, len(args)) limit $%d`, len(args))
@@ -443,7 +466,7 @@ func (db *DB) SearchSimilarThoughts(ctx context.Context, embedding []float32, em
return results, nil return results, nil
} }
func (db *DB) HasEmbeddingsForModel(ctx context.Context, model string, projectID *uuid.UUID) (bool, error) { func (db *DB) HasEmbeddingsForModel(ctx context.Context, model string, projectID *int64) (bool, error) {
args := []any{model} args := []any{model}
conditions := []string{ conditions := []string{
"e.model = $1", "e.model = $1",
@@ -454,7 +477,7 @@ func (db *DB) HasEmbeddingsForModel(ctx context.Context, model string, projectID
conditions = append(conditions, fmt.Sprintf("t.project_id = $%d", len(args))) conditions = append(conditions, fmt.Sprintf("t.project_id = $%d", len(args)))
} }
query := `select exists(select 1 from embeddings e join thoughts t on t.guid = e.thought_id where ` + query := `select exists(select 1 from embeddings e join thoughts t on t.id = e.thought_id where ` +
strings.Join(conditions, " and ") + `)` strings.Join(conditions, " and ") + `)`
var exists bool var exists bool
@@ -464,7 +487,7 @@ func (db *DB) HasEmbeddingsForModel(ctx context.Context, model string, projectID
return exists, nil return exists, nil
} }
func (db *DB) ListThoughtsMissingEmbedding(ctx context.Context, model string, limit int, projectID *uuid.UUID, includeArchived bool, olderThanDays int) ([]thoughttypes.Thought, error) { func (db *DB) ListThoughtsMissingEmbedding(ctx context.Context, model string, limit int, projectID *int64, includeArchived bool, olderThanDays int) ([]thoughttypes.Thought, error) {
args := []any{model} args := []any{model}
conditions := []string{"e.id is null"} conditions := []string{"e.id is null"}
@@ -482,9 +505,9 @@ func (db *DB) ListThoughtsMissingEmbedding(ctx context.Context, model string, li
args = append(args, limit) args = append(args, limit)
query := ` query := `
select t.guid, t.content, t.metadata, t.project_id, t.archived_at, t.created_at, t.updated_at select t.id, t.guid, t.content, t.metadata, t.project_id, t.archived_at, t.created_at, t.updated_at
from thoughts t from thoughts t
left join embeddings e on e.thought_id = t.guid and e.model = $1 left join embeddings e on e.thought_id = t.id and e.model = $1
where ` + strings.Join(conditions, " and ") + ` where ` + strings.Join(conditions, " and ") + `
order by t.created_at asc order by t.created_at asc
limit $` + fmt.Sprintf("%d", len(args)) limit $` + fmt.Sprintf("%d", len(args))
@@ -497,12 +520,12 @@ func (db *DB) ListThoughtsMissingEmbedding(ctx context.Context, model string, li
thoughts := make([]thoughttypes.Thought, 0, limit) thoughts := make([]thoughttypes.Thought, 0, limit)
for rows.Next() { for rows.Next() {
var thought thoughttypes.Thought var model generatedmodels.ModelPublicThoughts
var metadataBytes []byte if err := rows.Scan(&model.ID, &model.GUID, &model.Content, &model.Metadata, &model.ProjectID, &model.ArchivedAt, &model.CreatedAt, &model.UpdatedAt); err != nil {
if err := rows.Scan(&thought.ID, &thought.Content, &metadataBytes, &thought.ProjectID, &thought.ArchivedAt, &thought.CreatedAt, &thought.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan missing-embedding thought: %w", err) return nil, fmt.Errorf("scan missing-embedding thought: %w", err)
} }
if err := json.Unmarshal(metadataBytes, &thought.Metadata); err != nil { thought, err := thoughtFromModel(model)
if err != nil {
return nil, fmt.Errorf("decode missing-embedding metadata: %w", err) return nil, fmt.Errorf("decode missing-embedding metadata: %w", err)
} }
thoughts = append(thoughts, thought) thoughts = append(thoughts, thought)
@@ -513,7 +536,7 @@ func (db *DB) ListThoughtsMissingEmbedding(ctx context.Context, model string, li
return thoughts, nil return thoughts, nil
} }
func (db *DB) ListThoughtsForMetadataReparse(ctx context.Context, limit int, projectID *uuid.UUID, includeArchived bool, olderThanDays int) ([]thoughttypes.Thought, error) { func (db *DB) ListThoughtsForMetadataReparse(ctx context.Context, limit int, projectID *int64, includeArchived bool, olderThanDays int) ([]thoughttypes.Thought, error) {
args := make([]any, 0, 3) args := make([]any, 0, 3)
conditions := make([]string, 0, 4) conditions := make([]string, 0, 4)
@@ -531,7 +554,7 @@ func (db *DB) ListThoughtsForMetadataReparse(ctx context.Context, limit int, pro
args = append(args, limit) args = append(args, limit)
query := ` query := `
select guid, content, metadata, project_id, archived_at, created_at, updated_at select id, guid, content, metadata, project_id, archived_at, created_at, updated_at
from thoughts from thoughts
` `
if len(conditions) > 0 { if len(conditions) > 0 {
@@ -547,12 +570,12 @@ func (db *DB) ListThoughtsForMetadataReparse(ctx context.Context, limit int, pro
thoughts := make([]thoughttypes.Thought, 0, limit) thoughts := make([]thoughttypes.Thought, 0, limit)
for rows.Next() { for rows.Next() {
var thought thoughttypes.Thought var model generatedmodels.ModelPublicThoughts
var metadataBytes []byte if err := rows.Scan(&model.ID, &model.GUID, &model.Content, &model.Metadata, &model.ProjectID, &model.ArchivedAt, &model.CreatedAt, &model.UpdatedAt); err != nil {
if err := rows.Scan(&thought.ID, &thought.Content, &metadataBytes, &thought.ProjectID, &thought.ArchivedAt, &thought.CreatedAt, &thought.UpdatedAt); err != nil {
return nil, fmt.Errorf("scan metadata-reparse thought: %w", err) return nil, fmt.Errorf("scan metadata-reparse thought: %w", err)
} }
if err := json.Unmarshal(metadataBytes, &thought.Metadata); err != nil { thought, err := thoughtFromModel(model)
if err != nil {
return nil, fmt.Errorf("decode metadata-reparse thought metadata: %w", err) return nil, fmt.Errorf("decode metadata-reparse thought metadata: %w", err)
} }
thoughts = append(thoughts, thought) thoughts = append(thoughts, thought)
@@ -564,7 +587,7 @@ func (db *DB) ListThoughtsForMetadataReparse(ctx context.Context, limit int, pro
return thoughts, nil return thoughts, nil
} }
func (db *DB) UpsertEmbedding(ctx context.Context, thoughtID uuid.UUID, model string, embedding []float32) error { func (db *DB) UpsertEmbedding(ctx context.Context, thoughtID int64, model string, embedding []float32) error {
_, err := db.pool.Exec(ctx, ` _, err := db.pool.Exec(ctx, `
insert into embeddings (thought_id, model, dim, embedding) insert into embeddings (thought_id, model, dim, embedding)
values ($1, $2, $3, $4) values ($1, $2, $3, $4)
@@ -579,7 +602,7 @@ func (db *DB) UpsertEmbedding(ctx context.Context, thoughtID uuid.UUID, model st
return nil return nil
} }
func (db *DB) SearchThoughtsText(ctx context.Context, query string, limit int, projectID *uuid.UUID, excludeID *uuid.UUID) ([]thoughttypes.SearchResult, error) { func (db *DB) SearchThoughtsText(ctx context.Context, query string, limit int, projectID *int64, excludeID *uuid.UUID) ([]thoughttypes.SearchResult, error) {
args := []any{query} args := []any{query}
conditions := []string{ conditions := []string{
"t.archived_at is null", "t.archived_at is null",
@@ -596,11 +619,11 @@ func (db *DB) SearchThoughtsText(ctx context.Context, query string, limit int, p
args = append(args, limit) args = append(args, limit)
q := ` q := `
select t.guid, t.content, t.metadata, select t.id, t.content, t.metadata,
ts_rank_cd(to_tsvector('simple', t.content) || to_tsvector('simple', coalesce(p.name, '')), websearch_to_tsquery('simple', $1)) as similarity, ts_rank_cd(to_tsvector('simple', t.content) || to_tsvector('simple', coalesce(p.name, '')), websearch_to_tsquery('simple', $1)) as similarity,
t.created_at t.created_at
from thoughts t from thoughts t
left join projects p on t.project_id = p.guid left join projects p on t.project_id = p.id
where ` + strings.Join(conditions, " and ") + ` where ` + strings.Join(conditions, " and ") + `
order by similarity desc order by similarity desc
limit $` + fmt.Sprintf("%d", len(args)) limit $` + fmt.Sprintf("%d", len(args))
+874
View File
@@ -0,0 +1,874 @@
package tools
import (
"context"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/store"
ext "git.warky.dev/wdevs/amcs/internal/types"
)
type AgentPersonasTool struct {
store *store.DB
}
func NewAgentPersonasTool(db *store.DB) *AgentPersonasTool {
return &AgentPersonasTool{store: db}
}
// ──────────────────────────────────────────────
// Personas
// ──────────────────────────────────────────────
// create_agent_persona
type CreatePersonaInput struct {
Name string `json:"name" jsonschema:"unique persona name — used as the load key"`
Description string `json:"description,omitempty" jsonschema:"short description of the persona"`
Summary string `json:"summary" jsonschema:"concise behaviour summary, returned by default on load"`
Detail string `json:"detail,omitempty" jsonschema:"full behaviour detail, returned only when detail=true"`
Tags []string `json:"tags,omitempty" jsonschema:"optional tags for grouping or filtering"`
}
type CreatePersonaOutput struct {
Persona ext.Persona `json:"persona"`
}
func (t *AgentPersonasTool) CreatePersona(ctx context.Context, _ *mcp.CallToolRequest, in CreatePersonaInput) (*mcp.CallToolResult, CreatePersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CreatePersonaOutput{}, errRequiredField("name")
}
if strings.TrimSpace(in.Summary) == "" {
return nil, CreatePersonaOutput{}, errRequiredField("summary")
}
if in.Tags == nil {
in.Tags = []string{}
}
persona, err := t.store.CreatePersona(ctx, ext.Persona{
Name: strings.TrimSpace(in.Name),
Description: strings.TrimSpace(in.Description),
Summary: strings.TrimSpace(in.Summary),
Detail: strings.TrimSpace(in.Detail),
Tags: in.Tags,
})
if err != nil {
return nil, CreatePersonaOutput{}, err
}
return nil, CreatePersonaOutput{Persona: persona}, nil
}
// update_agent_persona
type UpdatePersonaInput struct {
Name string `json:"name" jsonschema:"name of the persona to update"`
NewName string `json:"new_name,omitempty" jsonschema:"rename the persona"`
Description *string `json:"description,omitempty" jsonschema:"update description"`
Summary *string `json:"summary,omitempty" jsonschema:"update summary"`
Detail *string `json:"detail,omitempty" jsonschema:"update detail"`
Tags []string `json:"tags,omitempty" jsonschema:"replace tags"`
}
type UpdatePersonaOutput struct {
Persona ext.Persona `json:"persona"`
}
func (t *AgentPersonasTool) UpdatePersona(ctx context.Context, _ *mcp.CallToolRequest, in UpdatePersonaInput) (*mcp.CallToolResult, UpdatePersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, UpdatePersonaOutput{}, errRequiredField("name")
}
updates := map[string]any{}
if n := strings.TrimSpace(in.NewName); n != "" {
updates["name"] = n
}
if in.Description != nil {
updates["description"] = strings.TrimSpace(*in.Description)
}
if in.Summary != nil {
updates["summary"] = strings.TrimSpace(*in.Summary)
}
if in.Detail != nil {
updates["detail"] = strings.TrimSpace(*in.Detail)
}
if in.Tags != nil {
updates["tags"] = in.Tags
}
persona, err := t.store.UpdatePersona(ctx, strings.TrimSpace(in.Name), updates)
if err != nil {
return nil, UpdatePersonaOutput{}, err
}
return nil, UpdatePersonaOutput{Persona: persona}, nil
}
// delete_agent_persona
type DeletePersonaInput struct {
Name string `json:"name" jsonschema:"name of the persona to delete"`
}
type DeletePersonaOutput struct {
Deleted bool `json:"deleted"`
}
func (t *AgentPersonasTool) DeletePersona(ctx context.Context, _ *mcp.CallToolRequest, in DeletePersonaInput) (*mcp.CallToolResult, DeletePersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, DeletePersonaOutput{}, errRequiredField("name")
}
if err := t.store.DeletePersona(ctx, strings.TrimSpace(in.Name)); err != nil {
return nil, DeletePersonaOutput{}, err
}
return nil, DeletePersonaOutput{Deleted: true}, nil
}
// list_agent_personas
type ListPersonasInput struct {
Tag string `json:"tag,omitempty" jsonschema:"filter by tag"`
}
type ListPersonasOutput struct {
Personas []ext.Persona `json:"personas"`
}
func (t *AgentPersonasTool) ListPersonas(ctx context.Context, _ *mcp.CallToolRequest, in ListPersonasInput) (*mcp.CallToolResult, ListPersonasOutput, error) {
personas, err := t.store.ListPersonas(ctx, in.Tag)
if err != nil {
return nil, ListPersonasOutput{}, err
}
if personas == nil {
personas = []ext.Persona{}
}
return nil, ListPersonasOutput{Personas: personas}, nil
}
// get_agent_persona
type GetPersonaInput struct {
Name string `json:"name" jsonschema:"persona name to load"`
Detail bool `json:"detail,omitempty" jsonschema:"when true, return full part content and persona detail instead of summaries"`
Overrides map[string]string `json:"overrides,omitempty" jsonschema:"runtime part substitutions: {part_type: part_name}. Replaces all parts of that type without modifying the persona."`
}
type GetPersonaOutput struct {
Persona ext.PersonaFull `json:"persona"`
}
func (t *AgentPersonasTool) GetPersona(ctx context.Context, _ *mcp.CallToolRequest, in GetPersonaInput) (*mcp.CallToolResult, GetPersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, GetPersonaOutput{}, errRequiredField("name")
}
persona, err := t.store.GetPersona(ctx, strings.TrimSpace(in.Name), in.Detail, in.Overrides)
if err != nil {
return nil, GetPersonaOutput{}, err
}
return nil, GetPersonaOutput{Persona: persona}, nil
}
// get_persona_manifest
type GetPersonaManifestInput struct {
Name string `json:"name" jsonschema:"persona name"`
}
type GetPersonaManifestOutput struct {
Manifest ext.PersonaManifest `json:"manifest"`
}
func (t *AgentPersonasTool) GetPersonaManifest(ctx context.Context, _ *mcp.CallToolRequest, in GetPersonaManifestInput) (*mcp.CallToolResult, GetPersonaManifestOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, GetPersonaManifestOutput{}, errRequiredField("name")
}
manifest, err := t.store.GetPersonaManifest(ctx, strings.TrimSpace(in.Name))
if err != nil {
return nil, GetPersonaManifestOutput{}, err
}
return nil, GetPersonaManifestOutput{Manifest: manifest}, nil
}
// compile_persona
type CompilePersonaInput struct {
Name string `json:"name" jsonschema:"persona name to compile"`
}
type CompilePersonaOutput struct {
Persona ext.Persona `json:"persona"`
}
func (t *AgentPersonasTool) CompilePersona(ctx context.Context, _ *mcp.CallToolRequest, in CompilePersonaInput) (*mcp.CallToolResult, CompilePersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CompilePersonaOutput{}, errRequiredField("name")
}
persona, err := t.store.CompilePersona(ctx, strings.TrimSpace(in.Name))
if err != nil {
return nil, CompilePersonaOutput{}, err
}
return nil, CompilePersonaOutput{Persona: persona}, nil
}
// ──────────────────────────────────────────────
// Parts
// ──────────────────────────────────────────────
// create_agent_part
type CreatePartInput struct {
Name string `json:"name" jsonschema:"globally unique part name — used as the override key"`
PartType string `json:"part_type" jsonschema:"one of: system, agent, soul, identity, skill, specialization, tone, goal, context, protocol, backstory, motivation, voice, archetype, flaw, relationship"`
Description string `json:"description,omitempty" jsonschema:"short description of what this part does"`
Summary string `json:"summary" jsonschema:"concise version, used in summary mode"`
Content string `json:"content,omitempty" jsonschema:"full content, used in detail mode"`
Tags []string `json:"tags,omitempty" jsonschema:"optional tags"`
}
type CreatePartOutput struct {
Part ext.Part `json:"part"`
}
func (t *AgentPersonasTool) CreatePart(ctx context.Context, _ *mcp.CallToolRequest, in CreatePartInput) (*mcp.CallToolResult, CreatePartOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CreatePartOutput{}, errRequiredField("name")
}
if strings.TrimSpace(in.PartType) == "" {
return nil, CreatePartOutput{}, errRequiredField("part_type")
}
if strings.TrimSpace(in.Summary) == "" {
return nil, CreatePartOutput{}, errRequiredField("summary")
}
if in.Tags == nil {
in.Tags = []string{}
}
part, err := t.store.CreatePart(ctx, ext.Part{
Name: strings.TrimSpace(in.Name),
PartType: strings.TrimSpace(in.PartType),
Description: strings.TrimSpace(in.Description),
Summary: strings.TrimSpace(in.Summary),
Content: strings.TrimSpace(in.Content),
Tags: in.Tags,
})
if err != nil {
return nil, CreatePartOutput{}, err
}
return nil, CreatePartOutput{Part: part}, nil
}
// update_agent_part
type UpdatePartInput struct {
Name string `json:"name" jsonschema:"name of the part to update"`
NewName string `json:"new_name,omitempty" jsonschema:"rename the part"`
PartType *string `json:"part_type,omitempty" jsonschema:"update part type"`
Description *string `json:"description,omitempty" jsonschema:"update description"`
Summary *string `json:"summary,omitempty" jsonschema:"update summary"`
Content *string `json:"content,omitempty" jsonschema:"update content"`
Tags []string `json:"tags,omitempty" jsonschema:"replace tags"`
}
type UpdatePartOutput struct {
Part ext.Part `json:"part"`
}
func (t *AgentPersonasTool) UpdatePart(ctx context.Context, _ *mcp.CallToolRequest, in UpdatePartInput) (*mcp.CallToolResult, UpdatePartOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, UpdatePartOutput{}, errRequiredField("name")
}
updates := map[string]any{}
if n := strings.TrimSpace(in.NewName); n != "" {
updates["name"] = n
}
if in.PartType != nil {
updates["part_type"] = strings.TrimSpace(*in.PartType)
}
if in.Description != nil {
updates["description"] = strings.TrimSpace(*in.Description)
}
if in.Summary != nil {
updates["summary"] = strings.TrimSpace(*in.Summary)
}
if in.Content != nil {
updates["content"] = strings.TrimSpace(*in.Content)
}
if in.Tags != nil {
updates["tags"] = in.Tags
}
part, err := t.store.UpdatePart(ctx, strings.TrimSpace(in.Name), updates)
if err != nil {
return nil, UpdatePartOutput{}, err
}
return nil, UpdatePartOutput{Part: part}, nil
}
// delete_agent_part
type DeletePartInput struct {
Name string `json:"name" jsonschema:"name of the part to delete"`
}
type DeletePartOutput struct {
Deleted bool `json:"deleted"`
}
func (t *AgentPersonasTool) DeletePart(ctx context.Context, _ *mcp.CallToolRequest, in DeletePartInput) (*mcp.CallToolResult, DeletePartOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, DeletePartOutput{}, errRequiredField("name")
}
if err := t.store.DeletePart(ctx, strings.TrimSpace(in.Name)); err != nil {
return nil, DeletePartOutput{}, err
}
return nil, DeletePartOutput{Deleted: true}, nil
}
// list_agent_parts
type ListPartsInput struct {
PartType string `json:"part_type,omitempty" jsonschema:"filter by part type"`
Tag string `json:"tag,omitempty" jsonschema:"filter by tag"`
}
type ListPartsOutput struct {
Parts []ext.Part `json:"parts"`
}
func (t *AgentPersonasTool) ListParts(ctx context.Context, _ *mcp.CallToolRequest, in ListPartsInput) (*mcp.CallToolResult, ListPartsOutput, error) {
parts, err := t.store.ListParts(ctx, in.PartType, in.Tag)
if err != nil {
return nil, ListPartsOutput{}, err
}
if parts == nil {
parts = []ext.Part{}
}
return nil, ListPartsOutput{Parts: parts}, nil
}
// get_agent_part
type GetPartInput struct {
Name string `json:"name" jsonschema:"part name"`
}
type GetPartOutput struct {
Part ext.Part `json:"part"`
}
func (t *AgentPersonasTool) GetPart(ctx context.Context, _ *mcp.CallToolRequest, in GetPartInput) (*mcp.CallToolResult, GetPartOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, GetPartOutput{}, errRequiredField("name")
}
part, err := t.store.GetPartByName(ctx, strings.TrimSpace(in.Name))
if err != nil {
return nil, GetPartOutput{}, err
}
return nil, GetPartOutput{Part: part}, nil
}
// ──────────────────────────────────────────────
// Persona-Part / Skill / Guardrail links
// ──────────────────────────────────────────────
// add_persona_part
type AddPersonaPartInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
Part string `json:"part" jsonschema:"part name to link"`
Order int `json:"order,omitempty" jsonschema:"assembly order (lower first, default 0)"`
Priority int `json:"priority,omitempty" jsonschema:"context-budget priority (higher loads first when trimming)"`
}
type AddPersonaPartOutput struct {
Persona string `json:"persona"`
Part string `json:"part"`
}
func (t *AgentPersonasTool) AddPersonaPart(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaPartInput) (*mcp.CallToolResult, AddPersonaPartOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, AddPersonaPartOutput{}, errRequiredField("persona")
}
if strings.TrimSpace(in.Part) == "" {
return nil, AddPersonaPartOutput{}, errRequiredField("part")
}
if err := t.store.AddPersonaPart(ctx, strings.TrimSpace(in.Persona), strings.TrimSpace(in.Part), in.Order, in.Priority); err != nil {
return nil, AddPersonaPartOutput{}, err
}
return nil, AddPersonaPartOutput{Persona: in.Persona, Part: in.Part}, nil
}
// remove_persona_part
type RemovePersonaPartInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
Part string `json:"part" jsonschema:"part name to unlink"`
}
type RemovePersonaPartOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemovePersonaPart(ctx context.Context, _ *mcp.CallToolRequest, in RemovePersonaPartInput) (*mcp.CallToolResult, RemovePersonaPartOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, RemovePersonaPartOutput{}, errRequiredField("persona")
}
if strings.TrimSpace(in.Part) == "" {
return nil, RemovePersonaPartOutput{}, errRequiredField("part")
}
if err := t.store.RemovePersonaPart(ctx, strings.TrimSpace(in.Persona), strings.TrimSpace(in.Part)); err != nil {
return nil, RemovePersonaPartOutput{}, err
}
return nil, RemovePersonaPartOutput{Removed: true}, nil
}
// add_persona_skill
type AddPersonaSkillInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
SkillID int64 `json:"skill_id" jsonschema:"agent skill id to link"`
Override bool `json:"override,omitempty" jsonschema:"replace a project skill with the same name during world-model assembly"`
}
type AddPersonaSkillOutput struct {
PersonaID int64 `json:"persona_id"`
SkillID int64 `json:"skill_id"`
}
func (t *AgentPersonasTool) AddPersonaSkill(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaSkillInput) (*mcp.CallToolResult, AddPersonaSkillOutput, error) {
if err := t.store.AddPersonaSkill(ctx, in.PersonaID, in.SkillID, in.Override); err != nil {
return nil, AddPersonaSkillOutput{}, err
}
return nil, AddPersonaSkillOutput{PersonaID: in.PersonaID, SkillID: in.SkillID}, nil
}
// remove_persona_skill
type RemovePersonaSkillInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
SkillID int64 `json:"skill_id" jsonschema:"agent skill id to unlink"`
}
type RemovePersonaSkillOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemovePersonaSkill(ctx context.Context, _ *mcp.CallToolRequest, in RemovePersonaSkillInput) (*mcp.CallToolResult, RemovePersonaSkillOutput, error) {
if err := t.store.RemovePersonaSkill(ctx, in.PersonaID, in.SkillID); err != nil {
return nil, RemovePersonaSkillOutput{}, err
}
return nil, RemovePersonaSkillOutput{Removed: true}, nil
}
// add_persona_guardrail
type AddPersonaGuardrailInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
GuardrailID int64 `json:"guardrail_id" jsonschema:"agent guardrail id to link"`
}
type AddPersonaGuardrailOutput struct {
PersonaID int64 `json:"persona_id"`
GuardrailID int64 `json:"guardrail_id"`
}
func (t *AgentPersonasTool) AddPersonaGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaGuardrailInput) (*mcp.CallToolResult, AddPersonaGuardrailOutput, error) {
if err := t.store.AddPersonaGuardrail(ctx, in.PersonaID, in.GuardrailID); err != nil {
return nil, AddPersonaGuardrailOutput{}, err
}
return nil, AddPersonaGuardrailOutput{PersonaID: in.PersonaID, GuardrailID: in.GuardrailID}, nil
}
// remove_persona_guardrail
type RemovePersonaGuardrailInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
GuardrailID int64 `json:"guardrail_id" jsonschema:"agent guardrail id to unlink"`
}
type RemovePersonaGuardrailOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemovePersonaGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in RemovePersonaGuardrailInput) (*mcp.CallToolResult, RemovePersonaGuardrailOutput, error) {
if err := t.store.RemovePersonaGuardrail(ctx, in.PersonaID, in.GuardrailID); err != nil {
return nil, RemovePersonaGuardrailOutput{}, err
}
return nil, RemovePersonaGuardrailOutput{Removed: true}, nil
}
// ──────────────────────────────────────────────
// Traits
// ──────────────────────────────────────────────
// create_agent_trait
type CreateTraitInput struct {
Name string `json:"name" jsonschema:"globally unique trait name"`
TraitType string `json:"trait_type" jsonschema:"one of: personality, cognitive, emotional, social, behavioral"`
Description string `json:"description,omitempty" jsonschema:"short description of this trait"`
Instruction string `json:"instruction,omitempty" jsonschema:"how to apply this trait in practice"`
Tags []string `json:"tags,omitempty" jsonschema:"optional tags"`
}
type CreateTraitOutput struct {
Trait ext.Trait `json:"trait"`
}
func (t *AgentPersonasTool) CreateTrait(ctx context.Context, _ *mcp.CallToolRequest, in CreateTraitInput) (*mcp.CallToolResult, CreateTraitOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CreateTraitOutput{}, errRequiredField("name")
}
if strings.TrimSpace(in.TraitType) == "" {
return nil, CreateTraitOutput{}, errRequiredField("trait_type")
}
if in.Tags == nil {
in.Tags = []string{}
}
trait, err := t.store.CreateTrait(ctx, ext.Trait{
Name: strings.TrimSpace(in.Name),
TraitType: strings.TrimSpace(in.TraitType),
Description: strings.TrimSpace(in.Description),
Instruction: strings.TrimSpace(in.Instruction),
Tags: in.Tags,
})
if err != nil {
return nil, CreateTraitOutput{}, err
}
return nil, CreateTraitOutput{Trait: trait}, nil
}
// update_agent_trait
type UpdateTraitInput struct {
Name string `json:"name" jsonschema:"name of the trait to update"`
NewName string `json:"new_name,omitempty" jsonschema:"rename the trait"`
TraitType *string `json:"trait_type,omitempty" jsonschema:"update trait type"`
Description *string `json:"description,omitempty" jsonschema:"update description"`
Instruction *string `json:"instruction,omitempty" jsonschema:"update instruction"`
Tags []string `json:"tags,omitempty" jsonschema:"replace tags"`
}
type UpdateTraitOutput struct {
Trait ext.Trait `json:"trait"`
}
func (t *AgentPersonasTool) UpdateTrait(ctx context.Context, _ *mcp.CallToolRequest, in UpdateTraitInput) (*mcp.CallToolResult, UpdateTraitOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, UpdateTraitOutput{}, errRequiredField("name")
}
updates := map[string]any{}
if n := strings.TrimSpace(in.NewName); n != "" {
updates["name"] = n
}
if in.TraitType != nil {
updates["trait_type"] = strings.TrimSpace(*in.TraitType)
}
if in.Description != nil {
updates["description"] = strings.TrimSpace(*in.Description)
}
if in.Instruction != nil {
updates["instruction"] = strings.TrimSpace(*in.Instruction)
}
if in.Tags != nil {
updates["tags"] = in.Tags
}
trait, err := t.store.UpdateTrait(ctx, strings.TrimSpace(in.Name), updates)
if err != nil {
return nil, UpdateTraitOutput{}, err
}
return nil, UpdateTraitOutput{Trait: trait}, nil
}
// delete_agent_trait
type DeleteTraitInput struct {
Name string `json:"name" jsonschema:"name of the trait to delete"`
}
type DeleteTraitOutput struct {
Deleted bool `json:"deleted"`
}
func (t *AgentPersonasTool) DeleteTrait(ctx context.Context, _ *mcp.CallToolRequest, in DeleteTraitInput) (*mcp.CallToolResult, DeleteTraitOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, DeleteTraitOutput{}, errRequiredField("name")
}
if err := t.store.DeleteTrait(ctx, strings.TrimSpace(in.Name)); err != nil {
return nil, DeleteTraitOutput{}, err
}
return nil, DeleteTraitOutput{Deleted: true}, nil
}
// list_agent_traits
type ListTraitsInput struct {
TraitType string `json:"trait_type,omitempty" jsonschema:"filter by trait type"`
Tag string `json:"tag,omitempty" jsonschema:"filter by tag"`
}
type ListTraitsOutput struct {
Traits []ext.Trait `json:"traits"`
}
func (t *AgentPersonasTool) ListTraits(ctx context.Context, _ *mcp.CallToolRequest, in ListTraitsInput) (*mcp.CallToolResult, ListTraitsOutput, error) {
traits, err := t.store.ListTraits(ctx, in.TraitType, in.Tag)
if err != nil {
return nil, ListTraitsOutput{}, err
}
if traits == nil {
traits = []ext.Trait{}
}
return nil, ListTraitsOutput{Traits: traits}, nil
}
// get_agent_trait
type GetTraitInput struct {
Name string `json:"name" jsonschema:"trait name"`
}
type GetTraitOutput struct {
Trait ext.Trait `json:"trait"`
}
func (t *AgentPersonasTool) GetTrait(ctx context.Context, _ *mcp.CallToolRequest, in GetTraitInput) (*mcp.CallToolResult, GetTraitOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, GetTraitOutput{}, errRequiredField("name")
}
trait, err := t.store.GetTraitByName(ctx, strings.TrimSpace(in.Name))
if err != nil {
return nil, GetTraitOutput{}, err
}
return nil, GetTraitOutput{Trait: trait}, nil
}
// add_persona_trait
type AddPersonaTraitInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
TraitID int64 `json:"trait_id" jsonschema:"agent trait id to link"`
}
type AddPersonaTraitOutput struct {
PersonaID int64 `json:"persona_id"`
TraitID int64 `json:"trait_id"`
}
func (t *AgentPersonasTool) AddPersonaTrait(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaTraitInput) (*mcp.CallToolResult, AddPersonaTraitOutput, error) {
if err := t.store.AddPersonaTrait(ctx, in.PersonaID, in.TraitID); err != nil {
return nil, AddPersonaTraitOutput{}, err
}
return nil, AddPersonaTraitOutput{PersonaID: in.PersonaID, TraitID: in.TraitID}, nil
}
// remove_persona_trait
type RemovePersonaTraitInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
TraitID int64 `json:"trait_id" jsonschema:"agent trait id to unlink"`
}
type RemovePersonaTraitOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemovePersonaTrait(ctx context.Context, _ *mcp.CallToolRequest, in RemovePersonaTraitInput) (*mcp.CallToolResult, RemovePersonaTraitOutput, error) {
if err := t.store.RemovePersonaTrait(ctx, in.PersonaID, in.TraitID); err != nil {
return nil, RemovePersonaTraitOutput{}, err
}
return nil, RemovePersonaTraitOutput{Removed: true}, nil
}
// ──────────────────────────────────────────────
// Character Arcs
// ──────────────────────────────────────────────
// create_character_arc
type CreateCharacterArcInput struct {
Name string `json:"name" jsonschema:"unique arc name"`
Description string `json:"description,omitempty" jsonschema:"description of the arc"`
Summary string `json:"summary,omitempty" jsonschema:"brief arc summary"`
}
type CreateCharacterArcOutput struct {
Arc ext.CharacterArc `json:"arc"`
}
func (t *AgentPersonasTool) CreateCharacterArc(ctx context.Context, _ *mcp.CallToolRequest, in CreateCharacterArcInput) (*mcp.CallToolResult, CreateCharacterArcOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CreateCharacterArcOutput{}, errRequiredField("name")
}
arc, err := t.store.CreateCharacterArc(ctx, ext.CharacterArc{
Name: strings.TrimSpace(in.Name),
Description: strings.TrimSpace(in.Description),
Summary: strings.TrimSpace(in.Summary),
})
if err != nil {
return nil, CreateCharacterArcOutput{}, err
}
return nil, CreateCharacterArcOutput{Arc: arc}, nil
}
// list_character_arcs
type ListCharacterArcsInput struct{}
type ListCharacterArcsOutput struct {
Arcs []ext.CharacterArc `json:"arcs"`
}
func (t *AgentPersonasTool) ListCharacterArcs(ctx context.Context, _ *mcp.CallToolRequest, _ ListCharacterArcsInput) (*mcp.CallToolResult, ListCharacterArcsOutput, error) {
arcs, err := t.store.ListCharacterArcs(ctx)
if err != nil {
return nil, ListCharacterArcsOutput{}, err
}
if arcs == nil {
arcs = []ext.CharacterArc{}
}
return nil, ListCharacterArcsOutput{Arcs: arcs}, nil
}
// add_arc_stage
type AddArcStageInput struct {
Arc string `json:"arc" jsonschema:"arc name"`
Name string `json:"name" jsonschema:"stage name"`
StageOrder int `json:"stage_order,omitempty" jsonschema:"position in arc sequence (lower first)"`
Description string `json:"description,omitempty" jsonschema:"what happens at this stage"`
Condition string `json:"condition,omitempty" jsonschema:"trigger condition description (evaluated externally)"`
}
type AddArcStageOutput struct {
Stage ext.ArcStage `json:"stage"`
}
func (t *AgentPersonasTool) AddArcStage(ctx context.Context, _ *mcp.CallToolRequest, in AddArcStageInput) (*mcp.CallToolResult, AddArcStageOutput, error) {
if strings.TrimSpace(in.Arc) == "" {
return nil, AddArcStageOutput{}, errRequiredField("arc")
}
if strings.TrimSpace(in.Name) == "" {
return nil, AddArcStageOutput{}, errRequiredField("name")
}
stage, err := t.store.AddArcStage(ctx, strings.TrimSpace(in.Arc), ext.ArcStage{
Name: strings.TrimSpace(in.Name),
StageOrder: in.StageOrder,
Description: strings.TrimSpace(in.Description),
Condition: strings.TrimSpace(in.Condition),
})
if err != nil {
return nil, AddArcStageOutput{}, err
}
return nil, AddArcStageOutput{Stage: stage}, nil
}
// add_stage_part
type AddStagePartInput struct {
StageID int64 `json:"stage_id" jsonschema:"arc stage id"`
PartName string `json:"part_name" jsonschema:"part name to link"`
}
type AddStagePartOutput struct {
StageID int64 `json:"stage_id"`
PartName string `json:"part_name"`
}
func (t *AgentPersonasTool) AddStagePart(ctx context.Context, _ *mcp.CallToolRequest, in AddStagePartInput) (*mcp.CallToolResult, AddStagePartOutput, error) {
if strings.TrimSpace(in.PartName) == "" {
return nil, AddStagePartOutput{}, errRequiredField("part_name")
}
if err := t.store.AddStagePart(ctx, in.StageID, strings.TrimSpace(in.PartName)); err != nil {
return nil, AddStagePartOutput{}, err
}
return nil, AddStagePartOutput{StageID: in.StageID, PartName: in.PartName}, nil
}
// remove_stage_part
type RemoveStagePartInput struct {
StageID int64 `json:"stage_id" jsonschema:"arc stage id"`
PartName string `json:"part_name" jsonschema:"part name to unlink"`
}
type RemoveStagePartOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemoveStagePart(ctx context.Context, _ *mcp.CallToolRequest, in RemoveStagePartInput) (*mcp.CallToolResult, RemoveStagePartOutput, error) {
if strings.TrimSpace(in.PartName) == "" {
return nil, RemoveStagePartOutput{}, errRequiredField("part_name")
}
if err := t.store.RemoveStagePart(ctx, in.StageID, strings.TrimSpace(in.PartName)); err != nil {
return nil, RemoveStagePartOutput{}, err
}
return nil, RemoveStagePartOutput{Removed: true}, nil
}
// assign_persona_arc
type AssignPersonaArcInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
Arc string `json:"arc" jsonschema:"arc name to assign"`
StartStage string `json:"start_stage" jsonschema:"name of the starting stage"`
}
type AssignPersonaArcOutput struct {
Persona string `json:"persona"`
Arc string `json:"arc"`
Stage string `json:"stage"`
}
func (t *AgentPersonasTool) AssignPersonaArc(ctx context.Context, _ *mcp.CallToolRequest, in AssignPersonaArcInput) (*mcp.CallToolResult, AssignPersonaArcOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, AssignPersonaArcOutput{}, errRequiredField("persona")
}
if strings.TrimSpace(in.Arc) == "" {
return nil, AssignPersonaArcOutput{}, errRequiredField("arc")
}
if strings.TrimSpace(in.StartStage) == "" {
return nil, AssignPersonaArcOutput{}, errRequiredField("start_stage")
}
if err := t.store.AssignPersonaArc(ctx, strings.TrimSpace(in.Persona), strings.TrimSpace(in.Arc), strings.TrimSpace(in.StartStage)); err != nil {
return nil, AssignPersonaArcOutput{}, err
}
return nil, AssignPersonaArcOutput{Persona: in.Persona, Arc: in.Arc, Stage: in.StartStage}, nil
}
// advance_persona_stage
type AdvancePersonaStageInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
}
type AdvancePersonaStageOutput struct {
Stage ext.ArcStage `json:"stage"`
}
func (t *AgentPersonasTool) AdvancePersonaStage(ctx context.Context, _ *mcp.CallToolRequest, in AdvancePersonaStageInput) (*mcp.CallToolResult, AdvancePersonaStageOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, AdvancePersonaStageOutput{}, errRequiredField("persona")
}
stage, err := t.store.AdvancePersonaStage(ctx, strings.TrimSpace(in.Persona))
if err != nil {
return nil, AdvancePersonaStageOutput{}, err
}
return nil, AdvancePersonaStageOutput{Stage: stage}, nil
}
// reset_persona_stage
type ResetPersonaStageInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
}
type ResetPersonaStageOutput struct {
Stage ext.ArcStage `json:"stage"`
}
func (t *AgentPersonasTool) ResetPersonaStage(ctx context.Context, _ *mcp.CallToolRequest, in ResetPersonaStageInput) (*mcp.CallToolResult, ResetPersonaStageOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, ResetPersonaStageOutput{}, errRequiredField("persona")
}
stage, err := t.store.ResetPersonaStage(ctx, strings.TrimSpace(in.Persona))
if err != nil {
return nil, ResetPersonaStageOutput{}, err
}
return nil, ResetPersonaStageOutput{Stage: stage}, nil
}
+15 -13
View File
@@ -2,11 +2,11 @@ package tools
import ( import (
"context" "context"
"fmt"
"log/slog" "log/slog"
"sync" "sync"
"time" "time"
"github.com/google/uuid"
"github.com/modelcontextprotocol/go-sdk/mcp" "github.com/modelcontextprotocol/go-sdk/mcp"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
@@ -53,11 +53,12 @@ func NewBackfillTool(db *store.DB, embeddings *ai.EmbeddingRunner, sessions *ses
// QueueThought queues a single thought for background embedding generation. // QueueThought queues a single thought for background embedding generation.
// It is used by capture when the embedding provider is temporarily unavailable. // It is used by capture when the embedding provider is temporarily unavailable.
func (t *BackfillTool) QueueThought(ctx context.Context, id uuid.UUID, content string) { func (t *BackfillTool) QueueThought(ctx context.Context, id int64, content string) {
go func() { go func() {
started := time.Now() started := time.Now()
idStr := fmt.Sprint(id)
t.logger.Info("background embedding started", t.logger.Info("background embedding started",
slog.String("thought_id", id.String()), slog.String("thought_id", idStr),
slog.String("provider", t.embeddings.PrimaryProvider()), slog.String("provider", t.embeddings.PrimaryProvider()),
slog.String("model", t.embeddings.PrimaryModel()), slog.String("model", t.embeddings.PrimaryModel()),
) )
@@ -65,7 +66,7 @@ func (t *BackfillTool) QueueThought(ctx context.Context, id uuid.UUID, content s
result, err := t.embeddings.Embed(ctx, content) result, err := t.embeddings.Embed(ctx, content)
if err != nil { if err != nil {
t.logger.Warn("background embedding error", t.logger.Warn("background embedding error",
slog.String("thought_id", id.String()), slog.String("thought_id", idStr),
slog.String("provider", t.embeddings.PrimaryProvider()), slog.String("provider", t.embeddings.PrimaryProvider()),
slog.String("model", t.embeddings.PrimaryModel()), slog.String("model", t.embeddings.PrimaryModel()),
slog.String("stage", "embed"), slog.String("stage", "embed"),
@@ -76,7 +77,7 @@ func (t *BackfillTool) QueueThought(ctx context.Context, id uuid.UUID, content s
} }
if err := t.store.UpsertEmbedding(ctx, id, result.Model, result.Vector); err != nil { if err := t.store.UpsertEmbedding(ctx, id, result.Model, result.Vector); err != nil {
t.logger.Warn("background embedding error", t.logger.Warn("background embedding error",
slog.String("thought_id", id.String()), slog.String("thought_id", idStr),
slog.String("provider", t.embeddings.PrimaryProvider()), slog.String("provider", t.embeddings.PrimaryProvider()),
slog.String("model", result.Model), slog.String("model", result.Model),
slog.String("stage", "upsert"), slog.String("stage", "upsert"),
@@ -86,7 +87,7 @@ func (t *BackfillTool) QueueThought(ctx context.Context, id uuid.UUID, content s
return return
} }
t.logger.Info("background embedding complete", t.logger.Info("background embedding complete",
slog.String("thought_id", id.String()), slog.String("thought_id", idStr),
slog.String("provider", t.embeddings.PrimaryProvider()), slog.String("provider", t.embeddings.PrimaryProvider()),
slog.String("model", result.Model), slog.String("model", result.Model),
slog.Duration("duration", time.Since(started)), slog.Duration("duration", time.Since(started)),
@@ -105,9 +106,9 @@ func (t *BackfillTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in
return nil, BackfillOutput{}, err return nil, BackfillOutput{}, err
} }
var projectID *uuid.UUID var projectID *int64
if project != nil { if project != nil {
projectID = &project.ID projectID = &project.NumericID
} }
primaryModel := t.embeddings.PrimaryModel() primaryModel := t.embeddings.PrimaryModel()
@@ -140,24 +141,25 @@ func (t *BackfillTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in
break break
} }
wg.Add(1) wg.Add(1)
go func(id uuid.UUID, content string) { go func(id int64, content string) {
defer wg.Done() defer wg.Done()
defer sem.Release(1) defer sem.Release(1)
idStr := fmt.Sprint(id)
result, embedErr := t.embeddings.Embed(ctx, content) result, embedErr := t.embeddings.Embed(ctx, content)
if embedErr != nil { if embedErr != nil {
mu.Lock() mu.Lock()
out.Failures = append(out.Failures, BackfillFailure{ID: id.String(), Error: embedErr.Error()}) out.Failures = append(out.Failures, BackfillFailure{ID: idStr, Error: embedErr.Error()})
mu.Unlock() mu.Unlock()
t.logger.Warn("backfill embed failed", slog.String("thought_id", id.String()), slog.String("error", embedErr.Error())) t.logger.Warn("backfill embed failed", slog.String("thought_id", idStr), slog.String("error", embedErr.Error()))
return return
} }
if upsertErr := t.store.UpsertEmbedding(ctx, id, result.Model, result.Vector); upsertErr != nil { if upsertErr := t.store.UpsertEmbedding(ctx, id, result.Model, result.Vector); upsertErr != nil {
mu.Lock() mu.Lock()
out.Failures = append(out.Failures, BackfillFailure{ID: id.String(), Error: upsertErr.Error()}) out.Failures = append(out.Failures, BackfillFailure{ID: idStr, Error: upsertErr.Error()})
mu.Unlock() mu.Unlock()
t.logger.Warn("backfill upsert failed", slog.String("thought_id", id.String()), slog.String("error", upsertErr.Error())) t.logger.Warn("backfill upsert failed", slog.String("thought_id", idStr), slog.String("error", upsertErr.Error()))
return return
} }
+178 -178
View File
@@ -1,212 +1,212 @@
package tools package tools
import ( // import (
"context" // "context"
"strings" // "strings"
"time" // "time"
"github.com/google/uuid" // "github.com/google/uuid"
"github.com/modelcontextprotocol/go-sdk/mcp" // "github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/store" // "git.warky.dev/wdevs/amcs/internal/store"
ext "git.warky.dev/wdevs/amcs/internal/types" // ext "git.warky.dev/wdevs/amcs/internal/types"
) // )
type CalendarTool struct { // type CalendarTool struct {
store *store.DB // store *store.DB
} // }
func NewCalendarTool(db *store.DB) *CalendarTool { // func NewCalendarTool(db *store.DB) *CalendarTool {
return &CalendarTool{store: db} // return &CalendarTool{store: db}
} // }
// add_family_member // // add_family_member
type AddFamilyMemberInput struct { // type AddFamilyMemberInput struct {
Name string `json:"name" jsonschema:"person's name"` // Name string `json:"name" jsonschema:"person's name"`
Relationship string `json:"relationship,omitempty" jsonschema:"e.g. self, spouse, child, parent"` // Relationship string `json:"relationship,omitempty" jsonschema:"e.g. self, spouse, child, parent"`
BirthDate *time.Time `json:"birth_date,omitempty"` // BirthDate *time.Time `json:"birth_date,omitempty"`
Notes string `json:"notes,omitempty"` // Notes string `json:"notes,omitempty"`
} // }
type AddFamilyMemberOutput struct { // type AddFamilyMemberOutput struct {
Member ext.FamilyMember `json:"member"` // Member ext.FamilyMember `json:"member"`
} // }
func (t *CalendarTool) AddMember(ctx context.Context, _ *mcp.CallToolRequest, in AddFamilyMemberInput) (*mcp.CallToolResult, AddFamilyMemberOutput, error) { // func (t *CalendarTool) AddMember(ctx context.Context, _ *mcp.CallToolRequest, in AddFamilyMemberInput) (*mcp.CallToolResult, AddFamilyMemberOutput, error) {
if strings.TrimSpace(in.Name) == "" { // if strings.TrimSpace(in.Name) == "" {
return nil, AddFamilyMemberOutput{}, errRequiredField("name") // return nil, AddFamilyMemberOutput{}, errRequiredField("name")
} // }
member, err := t.store.AddFamilyMember(ctx, ext.FamilyMember{ // member, err := t.store.AddFamilyMember(ctx, ext.FamilyMember{
Name: strings.TrimSpace(in.Name), // Name: strings.TrimSpace(in.Name),
Relationship: strings.TrimSpace(in.Relationship), // Relationship: strings.TrimSpace(in.Relationship),
BirthDate: in.BirthDate, // BirthDate: in.BirthDate,
Notes: strings.TrimSpace(in.Notes), // Notes: strings.TrimSpace(in.Notes),
}) // })
if err != nil { // if err != nil {
return nil, AddFamilyMemberOutput{}, err // return nil, AddFamilyMemberOutput{}, err
} // }
return nil, AddFamilyMemberOutput{Member: member}, nil // return nil, AddFamilyMemberOutput{Member: member}, nil
} // }
// list_family_members // // list_family_members
type ListFamilyMembersInput struct{} // type ListFamilyMembersInput struct{}
type ListFamilyMembersOutput struct { // type ListFamilyMembersOutput struct {
Members []ext.FamilyMember `json:"members"` // Members []ext.FamilyMember `json:"members"`
} // }
func (t *CalendarTool) ListMembers(ctx context.Context, _ *mcp.CallToolRequest, _ ListFamilyMembersInput) (*mcp.CallToolResult, ListFamilyMembersOutput, error) { // func (t *CalendarTool) ListMembers(ctx context.Context, _ *mcp.CallToolRequest, _ ListFamilyMembersInput) (*mcp.CallToolResult, ListFamilyMembersOutput, error) {
members, err := t.store.ListFamilyMembers(ctx) // members, err := t.store.ListFamilyMembers(ctx)
if err != nil { // if err != nil {
return nil, ListFamilyMembersOutput{}, err // return nil, ListFamilyMembersOutput{}, err
} // }
if members == nil { // if members == nil {
members = []ext.FamilyMember{} // members = []ext.FamilyMember{}
} // }
return nil, ListFamilyMembersOutput{Members: members}, nil // return nil, ListFamilyMembersOutput{Members: members}, nil
} // }
// add_activity // // add_activity
type AddActivityInput struct { // type AddActivityInput struct {
Title string `json:"title" jsonschema:"activity title"` // Title string `json:"title" jsonschema:"activity title"`
ActivityType string `json:"activity_type,omitempty" jsonschema:"e.g. sports, medical, school, social"` // ActivityType string `json:"activity_type,omitempty" jsonschema:"e.g. sports, medical, school, social"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"leave empty for whole-family activities"` // FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"leave empty for whole-family activities"`
DayOfWeek string `json:"day_of_week,omitempty" jsonschema:"for recurring: monday, tuesday, etc."` // DayOfWeek string `json:"day_of_week,omitempty" jsonschema:"for recurring: monday, tuesday, etc."`
StartTime string `json:"start_time,omitempty" jsonschema:"HH:MM format"` // StartTime string `json:"start_time,omitempty" jsonschema:"HH:MM format"`
EndTime string `json:"end_time,omitempty" jsonschema:"HH:MM format"` // EndTime string `json:"end_time,omitempty" jsonschema:"HH:MM format"`
StartDate *time.Time `json:"start_date,omitempty"` // StartDate *time.Time `json:"start_date,omitempty"`
EndDate *time.Time `json:"end_date,omitempty" jsonschema:"for recurring activities, when they end"` // EndDate *time.Time `json:"end_date,omitempty" jsonschema:"for recurring activities, when they end"`
Location string `json:"location,omitempty"` // Location string `json:"location,omitempty"`
Notes string `json:"notes,omitempty"` // Notes string `json:"notes,omitempty"`
} // }
type AddActivityOutput struct { // type AddActivityOutput struct {
Activity ext.Activity `json:"activity"` // Activity ext.Activity `json:"activity"`
} // }
func (t *CalendarTool) AddActivity(ctx context.Context, _ *mcp.CallToolRequest, in AddActivityInput) (*mcp.CallToolResult, AddActivityOutput, error) { // func (t *CalendarTool) AddActivity(ctx context.Context, _ *mcp.CallToolRequest, in AddActivityInput) (*mcp.CallToolResult, AddActivityOutput, error) {
if strings.TrimSpace(in.Title) == "" { // if strings.TrimSpace(in.Title) == "" {
return nil, AddActivityOutput{}, errRequiredField("title") // return nil, AddActivityOutput{}, errRequiredField("title")
} // }
activity, err := t.store.AddActivity(ctx, ext.Activity{ // activity, err := t.store.AddActivity(ctx, ext.Activity{
FamilyMemberID: in.FamilyMemberID, // FamilyMemberID: in.FamilyMemberID,
Title: strings.TrimSpace(in.Title), // Title: strings.TrimSpace(in.Title),
ActivityType: strings.TrimSpace(in.ActivityType), // ActivityType: strings.TrimSpace(in.ActivityType),
DayOfWeek: strings.ToLower(strings.TrimSpace(in.DayOfWeek)), // DayOfWeek: strings.ToLower(strings.TrimSpace(in.DayOfWeek)),
StartTime: strings.TrimSpace(in.StartTime), // StartTime: strings.TrimSpace(in.StartTime),
EndTime: strings.TrimSpace(in.EndTime), // EndTime: strings.TrimSpace(in.EndTime),
StartDate: in.StartDate, // StartDate: in.StartDate,
EndDate: in.EndDate, // EndDate: in.EndDate,
Location: strings.TrimSpace(in.Location), // Location: strings.TrimSpace(in.Location),
Notes: strings.TrimSpace(in.Notes), // Notes: strings.TrimSpace(in.Notes),
}) // })
if err != nil { // if err != nil {
return nil, AddActivityOutput{}, err // return nil, AddActivityOutput{}, err
} // }
return nil, AddActivityOutput{Activity: activity}, nil // return nil, AddActivityOutput{Activity: activity}, nil
} // }
// get_week_schedule // // get_week_schedule
type GetWeekScheduleInput struct { // type GetWeekScheduleInput struct {
WeekStart time.Time `json:"week_start" jsonschema:"start of the week (Monday) to retrieve"` // WeekStart time.Time `json:"week_start" jsonschema:"start of the week (Monday) to retrieve"`
} // }
type GetWeekScheduleOutput struct { // type GetWeekScheduleOutput struct {
Activities []ext.Activity `json:"activities"` // Activities []ext.Activity `json:"activities"`
} // }
func (t *CalendarTool) GetWeekSchedule(ctx context.Context, _ *mcp.CallToolRequest, in GetWeekScheduleInput) (*mcp.CallToolResult, GetWeekScheduleOutput, error) { // func (t *CalendarTool) GetWeekSchedule(ctx context.Context, _ *mcp.CallToolRequest, in GetWeekScheduleInput) (*mcp.CallToolResult, GetWeekScheduleOutput, error) {
activities, err := t.store.GetWeekSchedule(ctx, in.WeekStart) // activities, err := t.store.GetWeekSchedule(ctx, in.WeekStart)
if err != nil { // if err != nil {
return nil, GetWeekScheduleOutput{}, err // return nil, GetWeekScheduleOutput{}, err
} // }
if activities == nil { // if activities == nil {
activities = []ext.Activity{} // activities = []ext.Activity{}
} // }
return nil, GetWeekScheduleOutput{Activities: activities}, nil // return nil, GetWeekScheduleOutput{Activities: activities}, nil
} // }
// search_activities // // search_activities
type SearchActivitiesInput struct { // type SearchActivitiesInput struct {
Query string `json:"query,omitempty" jsonschema:"search text matching title or notes"` // Query string `json:"query,omitempty" jsonschema:"search text matching title or notes"`
ActivityType string `json:"activity_type,omitempty" jsonschema:"filter by type"` // ActivityType string `json:"activity_type,omitempty" jsonschema:"filter by type"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"filter by family member"` // FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"filter by family member"`
} // }
type SearchActivitiesOutput struct { // type SearchActivitiesOutput struct {
Activities []ext.Activity `json:"activities"` // Activities []ext.Activity `json:"activities"`
} // }
func (t *CalendarTool) SearchActivities(ctx context.Context, _ *mcp.CallToolRequest, in SearchActivitiesInput) (*mcp.CallToolResult, SearchActivitiesOutput, error) { // func (t *CalendarTool) SearchActivities(ctx context.Context, _ *mcp.CallToolRequest, in SearchActivitiesInput) (*mcp.CallToolResult, SearchActivitiesOutput, error) {
activities, err := t.store.SearchActivities(ctx, in.Query, in.ActivityType, in.FamilyMemberID) // activities, err := t.store.SearchActivities(ctx, in.Query, in.ActivityType, in.FamilyMemberID)
if err != nil { // if err != nil {
return nil, SearchActivitiesOutput{}, err // return nil, SearchActivitiesOutput{}, err
} // }
if activities == nil { // if activities == nil {
activities = []ext.Activity{} // activities = []ext.Activity{}
} // }
return nil, SearchActivitiesOutput{Activities: activities}, nil // return nil, SearchActivitiesOutput{Activities: activities}, nil
} // }
// add_important_date // // add_important_date
type AddImportantDateInput struct { // type AddImportantDateInput struct {
Title string `json:"title" jsonschema:"description of the date"` // Title string `json:"title" jsonschema:"description of the date"`
DateValue time.Time `json:"date_value" jsonschema:"the date"` // DateValue time.Time `json:"date_value" jsonschema:"the date"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty"` // FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty"`
RecurringYearly bool `json:"recurring_yearly,omitempty" jsonschema:"if true, reminds every year"` // RecurringYearly bool `json:"recurring_yearly,omitempty" jsonschema:"if true, reminds every year"`
ReminderDaysBefore int `json:"reminder_days_before,omitempty" jsonschema:"how many days before to remind (default: 7)"` // ReminderDaysBefore int `json:"reminder_days_before,omitempty" jsonschema:"how many days before to remind (default: 7)"`
Notes string `json:"notes,omitempty"` // Notes string `json:"notes,omitempty"`
} // }
type AddImportantDateOutput struct { // type AddImportantDateOutput struct {
Date ext.ImportantDate `json:"date"` // Date ext.ImportantDate `json:"date"`
} // }
func (t *CalendarTool) AddImportantDate(ctx context.Context, _ *mcp.CallToolRequest, in AddImportantDateInput) (*mcp.CallToolResult, AddImportantDateOutput, error) { // func (t *CalendarTool) AddImportantDate(ctx context.Context, _ *mcp.CallToolRequest, in AddImportantDateInput) (*mcp.CallToolResult, AddImportantDateOutput, error) {
if strings.TrimSpace(in.Title) == "" { // if strings.TrimSpace(in.Title) == "" {
return nil, AddImportantDateOutput{}, errRequiredField("title") // return nil, AddImportantDateOutput{}, errRequiredField("title")
} // }
reminder := in.ReminderDaysBefore // reminder := in.ReminderDaysBefore
if reminder <= 0 { // if reminder <= 0 {
reminder = 7 // reminder = 7
} // }
d, err := t.store.AddImportantDate(ctx, ext.ImportantDate{ // d, err := t.store.AddImportantDate(ctx, ext.ImportantDate{
FamilyMemberID: in.FamilyMemberID, // FamilyMemberID: in.FamilyMemberID,
Title: strings.TrimSpace(in.Title), // Title: strings.TrimSpace(in.Title),
DateValue: in.DateValue, // DateValue: in.DateValue,
RecurringYearly: in.RecurringYearly, // RecurringYearly: in.RecurringYearly,
ReminderDaysBefore: reminder, // ReminderDaysBefore: reminder,
Notes: strings.TrimSpace(in.Notes), // Notes: strings.TrimSpace(in.Notes),
}) // })
if err != nil { // if err != nil {
return nil, AddImportantDateOutput{}, err // return nil, AddImportantDateOutput{}, err
} // }
return nil, AddImportantDateOutput{Date: d}, nil // return nil, AddImportantDateOutput{Date: d}, nil
} // }
// get_upcoming_dates // // get_upcoming_dates
type GetUpcomingDatesInput struct { // type GetUpcomingDatesInput struct {
DaysAhead int `json:"days_ahead,omitempty" jsonschema:"how many days to look ahead (default: 30)"` // DaysAhead int `json:"days_ahead,omitempty" jsonschema:"how many days to look ahead (default: 30)"`
} // }
type GetUpcomingDatesOutput struct { // type GetUpcomingDatesOutput struct {
Dates []ext.ImportantDate `json:"dates"` // Dates []ext.ImportantDate `json:"dates"`
} // }
func (t *CalendarTool) GetUpcomingDates(ctx context.Context, _ *mcp.CallToolRequest, in GetUpcomingDatesInput) (*mcp.CallToolResult, GetUpcomingDatesOutput, error) { // func (t *CalendarTool) GetUpcomingDates(ctx context.Context, _ *mcp.CallToolRequest, in GetUpcomingDatesInput) (*mcp.CallToolResult, GetUpcomingDatesOutput, error) {
dates, err := t.store.GetUpcomingDates(ctx, in.DaysAhead) // dates, err := t.store.GetUpcomingDates(ctx, in.DaysAhead)
if err != nil { // if err != nil {
return nil, GetUpcomingDatesOutput{}, err // return nil, GetUpcomingDatesOutput{}, err
} // }
if dates == nil { // if dates == nil {
dates = []ext.ImportantDate{} // dates = []ext.ImportantDate{}
} // }
return nil, GetUpcomingDatesOutput{Dates: dates}, nil // return nil, GetUpcomingDatesOutput{Dates: dates}, nil
} // }
+4 -5
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"strings" "strings"
"github.com/google/uuid"
"github.com/modelcontextprotocol/go-sdk/mcp" "github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/ai" "git.warky.dev/wdevs/amcs/internal/ai"
@@ -17,13 +16,13 @@ import (
// EmbeddingQueuer queues a thought for background embedding generation. // EmbeddingQueuer queues a thought for background embedding generation.
type EmbeddingQueuer interface { type EmbeddingQueuer interface {
QueueThought(ctx context.Context, id uuid.UUID, content string) QueueThought(ctx context.Context, id int64, content string)
} }
// MetadataQueuer queues a thought for background metadata retry. Both // MetadataQueuer queues a thought for background metadata retry. Both
// MetadataRetryer and EnrichmentRetryer satisfy this. // MetadataRetryer and EnrichmentRetryer satisfy this.
type MetadataQueuer interface { type MetadataQueuer interface {
QueueThought(id uuid.UUID) QueueThought(id int64)
} }
type CaptureTool struct { type CaptureTool struct {
@@ -66,7 +65,7 @@ func (t *CaptureTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in C
Metadata: rawMetadata, Metadata: rawMetadata,
} }
if project != nil { if project != nil {
thought.ProjectID = &project.ID thought.ProjectID = &project.NumericID
} }
created, err := t.store.InsertThought(ctx, thought, t.embeddings.PrimaryModel()) created, err := t.store.InsertThought(ctx, thought, t.embeddings.PrimaryModel())
@@ -74,7 +73,7 @@ func (t *CaptureTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in C
return nil, CaptureOutput{}, err return nil, CaptureOutput{}, err
} }
if project != nil { if project != nil {
_ = t.store.TouchProject(ctx, project.ID) _ = t.store.TouchProject(ctx, project.NumericID)
} }
if t.retryer != nil { if t.retryer != nil {
+11 -8
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"strings" "strings"
"github.com/google/uuid"
"github.com/modelcontextprotocol/go-sdk/mcp" "github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/session" "git.warky.dev/wdevs/amcs/internal/session"
@@ -64,7 +63,7 @@ func (t *ChatHistoryTool) SaveChatHistory(ctx context.Context, req *mcp.CallTool
h.Metadata = map[string]any{} h.Metadata = map[string]any{}
} }
if project != nil { if project != nil {
h.ProjectID = &project.ID h.ProjectID = &project.NumericID
} }
saved, err := t.store.SaveChatHistory(ctx, h) saved, err := t.store.SaveChatHistory(ctx, h)
@@ -77,7 +76,7 @@ func (t *ChatHistoryTool) SaveChatHistory(ctx context.Context, req *mcp.CallTool
// get_chat_history // get_chat_history
type GetChatHistoryInput struct { type GetChatHistoryInput struct {
ID string `json:"id,omitempty" jsonschema:"UUID of the saved chat history"` ID string `json:"id,omitempty" jsonschema:"numeric id of the saved chat history"`
SessionID string `json:"session_id,omitempty" jsonschema:"original session_id — returns the most recent history for that session"` SessionID string `json:"session_id,omitempty" jsonschema:"original session_id — returns the most recent history for that session"`
} }
@@ -91,9 +90,9 @@ func (t *ChatHistoryTool) GetChatHistory(ctx context.Context, _ *mcp.CallToolReq
} }
if in.ID != "" { if in.ID != "" {
id, err := uuid.Parse(in.ID) id, err := parseID(in.ID)
if err != nil { if err != nil {
return nil, GetChatHistoryOutput{}, errInvalidField("id", "invalid id", "must be a valid UUID") return nil, GetChatHistoryOutput{}, errInvalidField("id", "invalid id", "must be a valid numeric id")
} }
h, found, err := t.store.GetChatHistory(ctx, id) h, found, err := t.store.GetChatHistory(ctx, id)
if err != nil { if err != nil {
@@ -145,7 +144,7 @@ func (t *ChatHistoryTool) ListChatHistories(ctx context.Context, req *mcp.CallTo
return nil, ListChatHistoriesOutput{}, err return nil, ListChatHistoriesOutput{}, err
} }
if project != nil { if project != nil {
filter.ProjectID = &project.ID filter.ProjectID = &project.NumericID
} }
} }
@@ -162,7 +161,7 @@ func (t *ChatHistoryTool) ListChatHistories(ctx context.Context, req *mcp.CallTo
// delete_chat_history // delete_chat_history
type DeleteChatHistoryInput struct { type DeleteChatHistoryInput struct {
ID uuid.UUID `json:"id" jsonschema:"UUID of the chat history to delete"` ID string `json:"id" jsonschema:"numeric id of the chat history to delete"`
} }
type DeleteChatHistoryOutput struct { type DeleteChatHistoryOutput struct {
@@ -170,7 +169,11 @@ type DeleteChatHistoryOutput struct {
} }
func (t *ChatHistoryTool) DeleteChatHistory(ctx context.Context, _ *mcp.CallToolRequest, in DeleteChatHistoryInput) (*mcp.CallToolResult, DeleteChatHistoryOutput, error) { func (t *ChatHistoryTool) DeleteChatHistory(ctx context.Context, _ *mcp.CallToolRequest, in DeleteChatHistoryInput) (*mcp.CallToolResult, DeleteChatHistoryOutput, error) {
deleted, err := t.store.DeleteChatHistory(ctx, in.ID) id, err := parseID(in.ID)
if err != nil {
return nil, DeleteChatHistoryOutput{}, errInvalidField("id", "invalid id", "must be a valid numeric id")
}
deleted, err := t.store.DeleteChatHistory(ctx, id)
if err != nil { if err != nil {
return nil, DeleteChatHistoryOutput{}, err return nil, DeleteChatHistoryOutput{}, err
} }
+5 -5
View File
@@ -52,7 +52,7 @@ func (t *ContextTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in P
} }
limit := normalizeLimit(in.Limit, t.search) limit := normalizeLimit(in.Limit, t.search)
recent, err := t.store.RecentThoughts(ctx, &project.ID, limit, 0) recent, err := t.store.RecentThoughts(ctx, &project.NumericID, limit, 0)
if err != nil { if err != nil {
return nil, ProjectContextOutput{}, err return nil, ProjectContextOutput{}, err
} }
@@ -60,7 +60,7 @@ func (t *ContextTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in P
items := make([]ContextItem, 0, limit*2) items := make([]ContextItem, 0, limit*2)
seen := map[string]struct{}{} seen := map[string]struct{}{}
for _, thought := range recent { for _, thought := range recent {
key := thought.ID.String() key := fmt.Sprint(thought.ID)
seen[key] = struct{}{} seen[key] = struct{}{}
items = append(items, ContextItem{ items = append(items, ContextItem{
ID: key, ID: key,
@@ -72,12 +72,12 @@ func (t *ContextTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in P
query := strings.TrimSpace(in.Query) query := strings.TrimSpace(in.Query)
if query != "" { if query != "" {
semantic, err := semanticSearch(ctx, t.store, t.embeddings, t.search, query, limit, t.search.DefaultThreshold, &project.ID, nil) semantic, err := semanticSearch(ctx, t.store, t.embeddings, t.search, query, limit, t.search.DefaultThreshold, &project.NumericID, nil)
if err != nil { if err != nil {
return nil, ProjectContextOutput{}, err return nil, ProjectContextOutput{}, err
} }
for _, result := range semantic { for _, result := range semantic {
key := result.ID.String() key := fmt.Sprint(result.ID)
if _, ok := seen[key]; ok { if _, ok := seen[key]; ok {
continue continue
} }
@@ -97,7 +97,7 @@ func (t *ContextTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in P
lines = append(lines, thoughtContextLine(i, item.Content, item.Metadata, item.Similarity)) lines = append(lines, thoughtContextLine(i, item.Content, item.Metadata, item.Similarity))
} }
contextBlock := formatContextBlock(fmt.Sprintf("Project context for %s", project.Name), lines) contextBlock := formatContextBlock(fmt.Sprintf("Project context for %s", project.Name), lines)
_ = t.store.TouchProject(ctx, project.ID) _ = t.store.TouchProject(ctx, project.NumericID)
return nil, ProjectContextOutput{ return nil, ProjectContextOutput{
Project: *project, Project: *project,
+204 -204
View File
@@ -1,240 +1,240 @@
package tools package tools
import ( // import (
"context" // "context"
"errors" // "errors"
"fmt" // "fmt"
"strings" // "strings"
"time" // "time"
"github.com/google/uuid" // "github.com/google/uuid"
"github.com/jackc/pgx/v5" // "github.com/jackc/pgx/v5"
"github.com/modelcontextprotocol/go-sdk/mcp" // "github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/store" // "git.warky.dev/wdevs/amcs/internal/store"
ext "git.warky.dev/wdevs/amcs/internal/types" // ext "git.warky.dev/wdevs/amcs/internal/types"
) // )
type CRMTool struct { // type CRMTool struct {
store *store.DB // store *store.DB
} // }
func NewCRMTool(db *store.DB) *CRMTool { // func NewCRMTool(db *store.DB) *CRMTool {
return &CRMTool{store: db} // return &CRMTool{store: db}
} // }
// add_professional_contact // // add_professional_contact
type AddContactInput struct { // type AddContactInput struct {
Name string `json:"name" jsonschema:"contact's full name"` // Name string `json:"name" jsonschema:"contact's full name"`
Company string `json:"company,omitempty"` // Company string `json:"company,omitempty"`
Title string `json:"title,omitempty" jsonschema:"job title"` // Title string `json:"title,omitempty" jsonschema:"job title"`
Email string `json:"email,omitempty"` // Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"` // Phone string `json:"phone,omitempty"`
LinkedInURL string `json:"linkedin_url,omitempty"` // LinkedInURL string `json:"linkedin_url,omitempty"`
HowWeMet string `json:"how_we_met,omitempty"` // HowWeMet string `json:"how_we_met,omitempty"`
Tags []string `json:"tags,omitempty"` // Tags []string `json:"tags,omitempty"`
Notes string `json:"notes,omitempty"` // Notes string `json:"notes,omitempty"`
FollowUpDate *time.Time `json:"follow_up_date,omitempty"` // FollowUpDate *time.Time `json:"follow_up_date,omitempty"`
} // }
type AddContactOutput struct { // type AddContactOutput struct {
Contact ext.ProfessionalContact `json:"contact"` // Contact ext.ProfessionalContact `json:"contact"`
} // }
func (t *CRMTool) AddContact(ctx context.Context, _ *mcp.CallToolRequest, in AddContactInput) (*mcp.CallToolResult, AddContactOutput, error) { // func (t *CRMTool) AddContact(ctx context.Context, _ *mcp.CallToolRequest, in AddContactInput) (*mcp.CallToolResult, AddContactOutput, error) {
if strings.TrimSpace(in.Name) == "" { // if strings.TrimSpace(in.Name) == "" {
return nil, AddContactOutput{}, errRequiredField("name") // return nil, AddContactOutput{}, errRequiredField("name")
} // }
if in.Tags == nil { // if in.Tags == nil {
in.Tags = []string{} // in.Tags = []string{}
} // }
contact, err := t.store.AddProfessionalContact(ctx, ext.ProfessionalContact{ // contact, err := t.store.AddProfessionalContact(ctx, ext.ProfessionalContact{
Name: strings.TrimSpace(in.Name), // Name: strings.TrimSpace(in.Name),
Company: strings.TrimSpace(in.Company), // Company: strings.TrimSpace(in.Company),
Title: strings.TrimSpace(in.Title), // Title: strings.TrimSpace(in.Title),
Email: strings.TrimSpace(in.Email), // Email: strings.TrimSpace(in.Email),
Phone: strings.TrimSpace(in.Phone), // Phone: strings.TrimSpace(in.Phone),
LinkedInURL: strings.TrimSpace(in.LinkedInURL), // LinkedInURL: strings.TrimSpace(in.LinkedInURL),
HowWeMet: strings.TrimSpace(in.HowWeMet), // HowWeMet: strings.TrimSpace(in.HowWeMet),
Tags: in.Tags, // Tags: in.Tags,
Notes: strings.TrimSpace(in.Notes), // Notes: strings.TrimSpace(in.Notes),
FollowUpDate: in.FollowUpDate, // FollowUpDate: in.FollowUpDate,
}) // })
if err != nil { // if err != nil {
return nil, AddContactOutput{}, err // return nil, AddContactOutput{}, err
} // }
return nil, AddContactOutput{Contact: contact}, nil // return nil, AddContactOutput{Contact: contact}, nil
} // }
// search_contacts // // search_contacts
type SearchContactsInput struct { // type SearchContactsInput struct {
Query string `json:"query,omitempty" jsonschema:"search text matching name, company, title, or notes"` // Query string `json:"query,omitempty" jsonschema:"search text matching name, company, title, or notes"`
Tags []string `json:"tags,omitempty" jsonschema:"filter by tags (all must match)"` // Tags []string `json:"tags,omitempty" jsonschema:"filter by tags (all must match)"`
} // }
type SearchContactsOutput struct { // type SearchContactsOutput struct {
Contacts []ext.ProfessionalContact `json:"contacts"` // Contacts []ext.ProfessionalContact `json:"contacts"`
} // }
func (t *CRMTool) SearchContacts(ctx context.Context, _ *mcp.CallToolRequest, in SearchContactsInput) (*mcp.CallToolResult, SearchContactsOutput, error) { // func (t *CRMTool) SearchContacts(ctx context.Context, _ *mcp.CallToolRequest, in SearchContactsInput) (*mcp.CallToolResult, SearchContactsOutput, error) {
contacts, err := t.store.SearchContacts(ctx, in.Query, in.Tags) // contacts, err := t.store.SearchContacts(ctx, in.Query, in.Tags)
if err != nil { // if err != nil {
return nil, SearchContactsOutput{}, err // return nil, SearchContactsOutput{}, err
} // }
if contacts == nil { // if contacts == nil {
contacts = []ext.ProfessionalContact{} // contacts = []ext.ProfessionalContact{}
} // }
return nil, SearchContactsOutput{Contacts: contacts}, nil // return nil, SearchContactsOutput{Contacts: contacts}, nil
} // }
// log_interaction // // log_interaction
type LogInteractionInput struct { // type LogInteractionInput struct {
ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"` // ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"`
InteractionType string `json:"interaction_type" jsonschema:"one of: meeting, email, call, coffee, event, linkedin, other"` // InteractionType string `json:"interaction_type" jsonschema:"one of: meeting, email, call, coffee, event, linkedin, other"`
OccurredAt *time.Time `json:"occurred_at,omitempty" jsonschema:"when it happened (defaults to now)"` // OccurredAt *time.Time `json:"occurred_at,omitempty" jsonschema:"when it happened (defaults to now)"`
Summary string `json:"summary" jsonschema:"summary of the interaction"` // Summary string `json:"summary" jsonschema:"summary of the interaction"`
FollowUpNeeded bool `json:"follow_up_needed,omitempty"` // FollowUpNeeded bool `json:"follow_up_needed,omitempty"`
FollowUpNotes string `json:"follow_up_notes,omitempty"` // FollowUpNotes string `json:"follow_up_notes,omitempty"`
} // }
type LogInteractionOutput struct { // type LogInteractionOutput struct {
Interaction ext.ContactInteraction `json:"interaction"` // Interaction ext.ContactInteraction `json:"interaction"`
} // }
func (t *CRMTool) LogInteraction(ctx context.Context, _ *mcp.CallToolRequest, in LogInteractionInput) (*mcp.CallToolResult, LogInteractionOutput, error) { // func (t *CRMTool) LogInteraction(ctx context.Context, _ *mcp.CallToolRequest, in LogInteractionInput) (*mcp.CallToolResult, LogInteractionOutput, error) {
if strings.TrimSpace(in.Summary) == "" { // if strings.TrimSpace(in.Summary) == "" {
return nil, LogInteractionOutput{}, errRequiredField("summary") // return nil, LogInteractionOutput{}, errRequiredField("summary")
} // }
occurredAt := time.Now() // occurredAt := time.Now()
if in.OccurredAt != nil { // if in.OccurredAt != nil {
occurredAt = *in.OccurredAt // occurredAt = *in.OccurredAt
} // }
interaction, err := t.store.LogInteraction(ctx, ext.ContactInteraction{ // interaction, err := t.store.LogInteraction(ctx, ext.ContactInteraction{
ContactID: in.ContactID, // ContactID: in.ContactID,
InteractionType: in.InteractionType, // InteractionType: in.InteractionType,
OccurredAt: occurredAt, // OccurredAt: occurredAt,
Summary: strings.TrimSpace(in.Summary), // Summary: strings.TrimSpace(in.Summary),
FollowUpNeeded: in.FollowUpNeeded, // FollowUpNeeded: in.FollowUpNeeded,
FollowUpNotes: strings.TrimSpace(in.FollowUpNotes), // FollowUpNotes: strings.TrimSpace(in.FollowUpNotes),
}) // })
if err != nil { // if err != nil {
return nil, LogInteractionOutput{}, err // return nil, LogInteractionOutput{}, err
} // }
return nil, LogInteractionOutput{Interaction: interaction}, nil // return nil, LogInteractionOutput{Interaction: interaction}, nil
} // }
// get_contact_history // // get_contact_history
type GetContactHistoryInput struct { // type GetContactHistoryInput struct {
ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"` // ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"`
} // }
type GetContactHistoryOutput struct { // type GetContactHistoryOutput struct {
History ext.ContactHistory `json:"history"` // History ext.ContactHistory `json:"history"`
} // }
func (t *CRMTool) GetHistory(ctx context.Context, _ *mcp.CallToolRequest, in GetContactHistoryInput) (*mcp.CallToolResult, GetContactHistoryOutput, error) { // func (t *CRMTool) GetHistory(ctx context.Context, _ *mcp.CallToolRequest, in GetContactHistoryInput) (*mcp.CallToolResult, GetContactHistoryOutput, error) {
history, err := t.store.GetContactHistory(ctx, in.ContactID) // history, err := t.store.GetContactHistory(ctx, in.ContactID)
if err != nil { // if err != nil {
return nil, GetContactHistoryOutput{}, err // return nil, GetContactHistoryOutput{}, err
} // }
return nil, GetContactHistoryOutput{History: history}, nil // return nil, GetContactHistoryOutput{History: history}, nil
} // }
// create_opportunity // // create_opportunity
type CreateOpportunityInput struct { // type CreateOpportunityInput struct {
ContactID *uuid.UUID `json:"contact_id,omitempty"` // ContactID *uuid.UUID `json:"contact_id,omitempty"`
Title string `json:"title" jsonschema:"opportunity title"` // Title string `json:"title" jsonschema:"opportunity title"`
Description string `json:"description,omitempty"` // Description string `json:"description,omitempty"`
Stage string `json:"stage,omitempty" jsonschema:"one of: identified, in_conversation, proposal, negotiation, won, lost (default: identified)"` // Stage string `json:"stage,omitempty" jsonschema:"one of: identified, in_conversation, proposal, negotiation, won, lost (default: identified)"`
Value *float64 `json:"value,omitempty" jsonschema:"monetary value"` // Value *float64 `json:"value,omitempty" jsonschema:"monetary value"`
ExpectedCloseDate *time.Time `json:"expected_close_date,omitempty"` // ExpectedCloseDate *time.Time `json:"expected_close_date,omitempty"`
Notes string `json:"notes,omitempty"` // Notes string `json:"notes,omitempty"`
} // }
type CreateOpportunityOutput struct { // type CreateOpportunityOutput struct {
Opportunity ext.Opportunity `json:"opportunity"` // Opportunity ext.Opportunity `json:"opportunity"`
} // }
func (t *CRMTool) CreateOpportunity(ctx context.Context, _ *mcp.CallToolRequest, in CreateOpportunityInput) (*mcp.CallToolResult, CreateOpportunityOutput, error) { // func (t *CRMTool) CreateOpportunity(ctx context.Context, _ *mcp.CallToolRequest, in CreateOpportunityInput) (*mcp.CallToolResult, CreateOpportunityOutput, error) {
if strings.TrimSpace(in.Title) == "" { // if strings.TrimSpace(in.Title) == "" {
return nil, CreateOpportunityOutput{}, errRequiredField("title") // return nil, CreateOpportunityOutput{}, errRequiredField("title")
} // }
stage := strings.TrimSpace(in.Stage) // stage := strings.TrimSpace(in.Stage)
if stage == "" { // if stage == "" {
stage = "identified" // stage = "identified"
} // }
opp, err := t.store.CreateOpportunity(ctx, ext.Opportunity{ // opp, err := t.store.CreateOpportunity(ctx, ext.Opportunity{
ContactID: in.ContactID, // ContactID: in.ContactID,
Title: strings.TrimSpace(in.Title), // Title: strings.TrimSpace(in.Title),
Description: strings.TrimSpace(in.Description), // Description: strings.TrimSpace(in.Description),
Stage: stage, // Stage: stage,
Value: in.Value, // Value: in.Value,
ExpectedCloseDate: in.ExpectedCloseDate, // ExpectedCloseDate: in.ExpectedCloseDate,
Notes: strings.TrimSpace(in.Notes), // Notes: strings.TrimSpace(in.Notes),
}) // })
if err != nil { // if err != nil {
return nil, CreateOpportunityOutput{}, err // return nil, CreateOpportunityOutput{}, err
} // }
return nil, CreateOpportunityOutput{Opportunity: opp}, nil // return nil, CreateOpportunityOutput{Opportunity: opp}, nil
} // }
// get_follow_ups_due // // get_follow_ups_due
type GetFollowUpsDueInput struct { // type GetFollowUpsDueInput struct {
DaysAhead int `json:"days_ahead,omitempty" jsonschema:"look ahead window in days (default: 7)"` // DaysAhead int `json:"days_ahead,omitempty" jsonschema:"look ahead window in days (default: 7)"`
} // }
type GetFollowUpsDueOutput struct { // type GetFollowUpsDueOutput struct {
Contacts []ext.ProfessionalContact `json:"contacts"` // Contacts []ext.ProfessionalContact `json:"contacts"`
} // }
func (t *CRMTool) GetFollowUpsDue(ctx context.Context, _ *mcp.CallToolRequest, in GetFollowUpsDueInput) (*mcp.CallToolResult, GetFollowUpsDueOutput, error) { // func (t *CRMTool) GetFollowUpsDue(ctx context.Context, _ *mcp.CallToolRequest, in GetFollowUpsDueInput) (*mcp.CallToolResult, GetFollowUpsDueOutput, error) {
contacts, err := t.store.GetFollowUpsDue(ctx, in.DaysAhead) // contacts, err := t.store.GetFollowUpsDue(ctx, in.DaysAhead)
if err != nil { // if err != nil {
return nil, GetFollowUpsDueOutput{}, err // return nil, GetFollowUpsDueOutput{}, err
} // }
if contacts == nil { // if contacts == nil {
contacts = []ext.ProfessionalContact{} // contacts = []ext.ProfessionalContact{}
} // }
return nil, GetFollowUpsDueOutput{Contacts: contacts}, nil // return nil, GetFollowUpsDueOutput{Contacts: contacts}, nil
} // }
// link_thought_to_contact // // link_thought_to_contact
type LinkThoughtToContactInput struct { // type LinkThoughtToContactInput struct {
ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"` // ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"`
ThoughtID uuid.UUID `json:"thought_id" jsonschema:"id of the thought to link"` // ThoughtID uuid.UUID `json:"thought_id" jsonschema:"id of the thought to link"`
} // }
type LinkThoughtToContactOutput struct { // type LinkThoughtToContactOutput struct {
Contact ext.ProfessionalContact `json:"contact"` // Contact ext.ProfessionalContact `json:"contact"`
} // }
func (t *CRMTool) LinkThought(ctx context.Context, _ *mcp.CallToolRequest, in LinkThoughtToContactInput) (*mcp.CallToolResult, LinkThoughtToContactOutput, error) { // func (t *CRMTool) LinkThought(ctx context.Context, _ *mcp.CallToolRequest, in LinkThoughtToContactInput) (*mcp.CallToolResult, LinkThoughtToContactOutput, error) {
thought, err := t.store.GetThought(ctx, in.ThoughtID) // thought, err := t.store.GetThought(ctx, in.ThoughtID)
if err != nil { // if err != nil {
if errors.Is(err, pgx.ErrNoRows) { // if errors.Is(err, pgx.ErrNoRows) {
return nil, LinkThoughtToContactOutput{}, errEntityNotFound("thought", "thought_id", in.ThoughtID.String()) // return nil, LinkThoughtToContactOutput{}, errEntityNotFound("thought", "thought_id", in.ThoughtID.String())
} // }
return nil, LinkThoughtToContactOutput{}, err // return nil, LinkThoughtToContactOutput{}, err
} // }
appendText := fmt.Sprintf("\n\n[Linked thought %s]: %s", thought.ID, thought.Content) // appendText := fmt.Sprintf("\n\n[Linked thought %s]: %s", thought.ID, thought.Content)
if err := t.store.AppendThoughtToContactNotes(ctx, in.ContactID, appendText); err != nil { // if err := t.store.AppendThoughtToContactNotes(ctx, in.ContactID, appendText); err != nil {
return nil, LinkThoughtToContactOutput{}, err // return nil, LinkThoughtToContactOutput{}, err
} // }
contact, err := t.store.GetContact(ctx, in.ContactID) // contact, err := t.store.GetContact(ctx, in.ContactID)
if err != nil { // if err != nil {
if errors.Is(err, pgx.ErrNoRows) { // if errors.Is(err, pgx.ErrNoRows) {
return nil, LinkThoughtToContactOutput{}, errEntityNotFound("contact", "contact_id", in.ContactID.String()) // return nil, LinkThoughtToContactOutput{}, errEntityNotFound("contact", "contact_id", in.ContactID.String())
} // }
return nil, LinkThoughtToContactOutput{}, err // return nil, LinkThoughtToContactOutput{}, err
} // }
return nil, LinkThoughtToContactOutput{Contact: contact}, nil // return nil, LinkThoughtToContactOutput{Contact: contact}, nil
} // }

Some files were not shown because too many files have changed in this diff Show More