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:
@@ -37,6 +37,7 @@ func ComputeFunctionHash(f *Function) string {
|
|||||||
fmt.Fprintf(h, "|%s", marshalStrings(f.Variant))
|
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.Notes, f.Documentation, f.Code)
|
||||||
fmt.Fprintf(h, "|%s|%s|%s", f.SourceRepo, f.SourceLicense, f.SourceFile)
|
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))
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// Post-insert: warn about file_path entries that don't exist on disk
|
||||||
|
// and functions missing params_schema
|
||||||
|
missingParams := 0
|
||||||
for _, f := range functions {
|
for _, f := range functions {
|
||||||
if f.FilePath != "" {
|
if f.FilePath != "" {
|
||||||
abs := filepath.Join(root, 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))
|
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 {
|
for _, t := range types {
|
||||||
if t.FilePath != "" {
|
if t.FilePath != "" {
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -50,6 +50,7 @@ type Function struct {
|
|||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
Documentation string `json:"documentation"`
|
Documentation string `json:"documentation"`
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
|
ParamsSchema string `json:"params_schema"`
|
||||||
Tested bool `json:"tested"`
|
Tested bool `json:"tested"`
|
||||||
Tests []string `json:"tests"`
|
Tests []string `json:"tests"`
|
||||||
TestFilePath string `json:"test_file_path"`
|
TestFilePath string `json:"test_file_path"`
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package registry
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -32,6 +33,10 @@ type rawFunction struct {
|
|||||||
TestFilePath string `yaml:"test_file_path"`
|
TestFilePath string `yaml:"test_file_path"`
|
||||||
FilePath string `yaml:"file_path"`
|
FilePath string `yaml:"file_path"`
|
||||||
|
|
||||||
|
// Params schema
|
||||||
|
Params []rawParam `yaml:"params"`
|
||||||
|
Output string `yaml:"output"`
|
||||||
|
|
||||||
// Source attribution
|
// Source attribution
|
||||||
SourceRepo string `yaml:"source_repo"`
|
SourceRepo string `yaml:"source_repo"`
|
||||||
SourceLicense string `yaml:"source_license"`
|
SourceLicense string `yaml:"source_license"`
|
||||||
@@ -45,6 +50,12 @@ type rawFunction struct {
|
|||||||
Variant []string `yaml:"variant"`
|
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.
|
// rawType mirrors the YAML frontmatter of a type .md file.
|
||||||
type rawType struct {
|
type rawType struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
@@ -175,6 +186,17 @@ func ParseFunctionMD(path string, root string) (*Function, error) {
|
|||||||
SourceFile: raw.SourceFile,
|
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 != "" {
|
if root != "" && raw.FilePath != "" {
|
||||||
codePath := filepath.Join(root, raw.FilePath)
|
codePath := filepath.Join(root, raw.FilePath)
|
||||||
if codeData, err := os.ReadFile(codePath); err == nil {
|
if codeData, err := os.ReadFile(codePath); err == nil {
|
||||||
|
|||||||
+6
-2
@@ -86,7 +86,8 @@ func (db *DB) InsertFunction(f *Function) error {
|
|||||||
tests, test_file_path, file_path, content_hash, created_at, updated_at,
|
tests, test_file_path, file_path, content_hash, created_at, updated_at,
|
||||||
props, emits, has_state, framework, variant,
|
props, emits, has_state, framework, variant,
|
||||||
notes, documentation, code,
|
notes, documentation, code,
|
||||||
source_repo, source_license, source_file
|
source_repo, source_license, source_file,
|
||||||
|
params_schema
|
||||||
) VALUES (
|
) 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.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),
|
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),
|
marshalProps(f.Props), marshalStrings(f.Emits), hasState, f.Framework, marshalStrings(f.Variant),
|
||||||
f.Notes, f.Documentation, f.Code,
|
f.Notes, f.Documentation, f.Code,
|
||||||
f.SourceRepo, f.SourceLicense, f.SourceFile,
|
f.SourceRepo, f.SourceLicense, f.SourceFile,
|
||||||
|
f.ParamsSchema,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -559,6 +562,7 @@ func scanFunctions(rows interface{ Next() bool; Scan(...any) error }) ([]Functio
|
|||||||
&propsJSON, &emitsJSON, &hasState, &f.Framework, &variantJSON,
|
&propsJSON, &emitsJSON, &hasState, &f.Framework, &variantJSON,
|
||||||
&f.Notes, &f.Documentation, &f.Code, &f.ContentHash,
|
&f.Notes, &f.Documentation, &f.Code, &f.ContentHash,
|
||||||
&f.SourceRepo, &f.SourceLicense, &f.SourceFile,
|
&f.SourceRepo, &f.SourceLicense, &f.SourceFile,
|
||||||
|
&f.ParamsSchema,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("scanning function: %w", err)
|
return nil, fmt.Errorf("scanning function: %w", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user