feat: add params_schema column for function composability

Nueva columna params_schema en functions con migración 009. Almacena JSON
con descripción semántica de inputs/outputs para que agentes razonen sobre
composabilidad de funciones. Incluye: campo en modelo Go, parsing de params/output
del frontmatter YAML, serialización a JSON, FTS5 rebuild con nueva columna,
hash de contenido actualizado, y warning en indexer cuando faltan params.
This commit is contained in:
2026-04-05 18:45:01 +02:00
parent b55f120a00
commit 34ecadf5a4
6 changed files with 89 additions and 2 deletions
+1
View File
@@ -37,6 +37,7 @@ func ComputeFunctionHash(f *Function) string {
fmt.Fprintf(h, "|%s", marshalStrings(f.Variant))
fmt.Fprintf(h, "|%s|%s|%s", f.Notes, f.Documentation, f.Code)
fmt.Fprintf(h, "|%s|%s|%s", f.SourceRepo, f.SourceLicense, f.SourceFile)
fmt.Fprintf(h, "|%s", f.ParamsSchema)
return fmt.Sprintf("%x", h.Sum(nil))
}
+8
View File
@@ -238,6 +238,8 @@ func Index(db *DB, root string) (*IndexResult, error) {
}
// Post-insert: warn about file_path entries that don't exist on disk
// and functions missing params_schema
missingParams := 0
for _, f := range functions {
if f.FilePath != "" {
abs := filepath.Join(root, f.FilePath)
@@ -251,6 +253,12 @@ func Index(db *DB, root string) (*IndexResult, error) {
result.Warnings = append(result.Warnings, fmt.Sprintf("%s: test_file_path %q not found", f.ID, f.TestFilePath))
}
}
if f.ParamsSchema == "" {
missingParams++
}
}
if missingParams > 0 {
result.Warnings = append(result.Warnings, fmt.Sprintf("%d functions missing params_schema (run 'fn check params' to list)", missingParams))
}
for _, t := range types {
if t.FilePath != "" {
+51
View File
@@ -0,0 +1,51 @@
-- Add params_schema to functions: JSON with semantic descriptions of inputs/outputs.
-- Format: {"params":[{"name":"x","desc":"..."}],"output":"..."}
-- Enables agent reasoning about function composability.
ALTER TABLE functions ADD COLUMN params_schema TEXT NOT NULL DEFAULT '';
-- Rebuild FTS for functions: add params_schema
DROP TRIGGER IF EXISTS functions_ai;
DROP TRIGGER IF EXISTS functions_ad;
DROP TRIGGER IF EXISTS functions_au;
INSERT INTO functions_fts(functions_fts) VALUES('rebuild');
DROP TABLE IF EXISTS functions_fts;
CREATE VIRTUAL TABLE functions_fts USING fts5(
id,
name,
description,
tags,
signature,
domain,
example,
notes,
documentation,
code,
params_schema,
content='functions',
content_rowid='rowid'
);
-- Populate FTS from existing data
INSERT INTO functions_fts(rowid, id, name, description, tags, signature, domain, example, notes, documentation, code, params_schema)
SELECT rowid, id, name, description, tags, signature, domain, example, notes, documentation, code, params_schema
FROM functions;
CREATE TRIGGER functions_ai AFTER INSERT ON functions BEGIN
INSERT INTO functions_fts(rowid, id, name, description, tags, signature, domain, example, notes, documentation, code, params_schema)
VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.signature, new.domain, new.example, new.notes, new.documentation, new.code, new.params_schema);
END;
CREATE TRIGGER functions_ad AFTER DELETE ON functions BEGIN
INSERT INTO functions_fts(functions_fts, rowid, id, name, description, tags, signature, domain, example, notes, documentation, code, params_schema)
VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.signature, old.domain, old.example, old.notes, old.documentation, old.code, old.params_schema);
END;
CREATE TRIGGER functions_au AFTER UPDATE ON functions BEGIN
INSERT INTO functions_fts(functions_fts, rowid, id, name, description, tags, signature, domain, example, notes, documentation, code, params_schema)
VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.signature, old.domain, old.example, old.notes, old.documentation, old.code, old.params_schema);
INSERT INTO functions_fts(rowid, id, name, description, tags, signature, domain, example, notes, documentation, code, params_schema)
VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.signature, new.domain, new.example, new.notes, new.documentation, new.code, new.params_schema);
END;
+1
View File
@@ -50,6 +50,7 @@ type Function struct {
Notes string `json:"notes"`
Documentation string `json:"documentation"`
Code string `json:"code"`
ParamsSchema string `json:"params_schema"`
Tested bool `json:"tested"`
Tests []string `json:"tests"`
TestFilePath string `json:"test_file_path"`
+22
View File
@@ -2,6 +2,7 @@ package registry
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -32,6 +33,10 @@ type rawFunction struct {
TestFilePath string `yaml:"test_file_path"`
FilePath string `yaml:"file_path"`
// Params schema
Params []rawParam `yaml:"params"`
Output string `yaml:"output"`
// Source attribution
SourceRepo string `yaml:"source_repo"`
SourceLicense string `yaml:"source_license"`
@@ -45,6 +50,12 @@ type rawFunction struct {
Variant []string `yaml:"variant"`
}
// rawParam describes a function parameter with semantic meaning.
type rawParam struct {
Name string `yaml:"name" json:"name"`
Desc string `yaml:"desc" json:"desc"`
}
// rawType mirrors the YAML frontmatter of a type .md file.
type rawType struct {
Name string `yaml:"name"`
@@ -175,6 +186,17 @@ func ParseFunctionMD(path string, root string) (*Function, error) {
SourceFile: raw.SourceFile,
}
// Serialize params + output to JSON for params_schema column
if len(raw.Params) > 0 || raw.Output != "" {
schema := struct {
Params []rawParam `json:"params,omitempty"`
Output string `json:"output,omitempty"`
}{Params: raw.Params, Output: raw.Output}
if b, err := json.Marshal(schema); err == nil {
f.ParamsSchema = string(b)
}
}
if root != "" && raw.FilePath != "" {
codePath := filepath.Join(root, raw.FilePath)
if codeData, err := os.ReadFile(codePath); err == nil {
+6 -2
View File
@@ -86,7 +86,8 @@ func (db *DB) InsertFunction(f *Function) error {
tests, test_file_path, file_path, content_hash, created_at, updated_at,
props, emits, has_state, framework, variant,
notes, documentation, code,
source_repo, source_license, source_file
source_repo, source_license, source_file,
params_schema
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?,
@@ -94,7 +95,8 @@ func (db *DB) InsertFunction(f *Function) error {
?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?,
?, ?, ?,
?, ?, ?
?, ?, ?,
?
)`,
f.ID, f.Name, string(f.Kind), f.Lang, f.Domain, f.Version, string(f.Purity), f.Signature,
f.Description, marshalStrings(f.Tags), marshalStrings(f.UsesFunctions), marshalStrings(f.UsesTypes), marshalStrings(f.Returns),
@@ -103,6 +105,7 @@ func (db *DB) InsertFunction(f *Function) error {
marshalProps(f.Props), marshalStrings(f.Emits), hasState, f.Framework, marshalStrings(f.Variant),
f.Notes, f.Documentation, f.Code,
f.SourceRepo, f.SourceLicense, f.SourceFile,
f.ParamsSchema,
)
return err
}
@@ -559,6 +562,7 @@ func scanFunctions(rows interface{ Next() bool; Scan(...any) error }) ([]Functio
&propsJSON, &emitsJSON, &hasState, &f.Framework, &variantJSON,
&f.Notes, &f.Documentation, &f.Code, &f.ContentHash,
&f.SourceRepo, &f.SourceLicense, &f.SourceFile,
&f.ParamsSchema,
)
if err != nil {
return nil, fmt.Errorf("scanning function: %w", err)