feat: externalize apps/analysis to Gitea repos, add analysis table
- Migration 007: repo_url on apps table + analysis table with FTS5 - Analysis struct, parser, CRUD, validation, hash computation - Selective purge: remote-only apps/analysis preserved across fn index - CLI: fn app list/clone/pull, fn analysis list/clone/pull - search/show/list now include analysis results - Apps removed from git tracking (content lives in Gitea repos) - .gitkeep for apps/ and analysis/ dirs - Bash functions: jupyter analysis pipeline, shell utilities - Browser domain: CDP functions moved from infra to browser Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+47
-6
@@ -13,6 +13,7 @@ type IndexResult struct {
|
||||
Functions int
|
||||
Types int
|
||||
Apps int
|
||||
Analysis int
|
||||
ValidationErrors []string
|
||||
Errors []string
|
||||
}
|
||||
@@ -26,15 +27,11 @@ type IndexResult struct {
|
||||
// directories (e.g. python/functions/, python/types/).
|
||||
func Index(db *DB, root string) (*IndexResult, error) {
|
||||
// Load existing timestamps before purging so we can preserve created_at
|
||||
oldFuncs, oldTypes, oldApps, err := db.LoadTimestamps()
|
||||
oldFuncs, oldTypes, oldApps, oldAnalysis, err := db.LoadTimestamps()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading timestamps: %w", err)
|
||||
}
|
||||
|
||||
if err := db.Purge(); err != nil {
|
||||
return nil, fmt.Errorf("purging database: %w", err)
|
||||
}
|
||||
|
||||
result := &IndexResult{}
|
||||
|
||||
// Pass 1: parse everything from all source directories
|
||||
@@ -42,7 +39,6 @@ func Index(db *DB, root string) (*IndexResult, error) {
|
||||
var types []*Type
|
||||
|
||||
// Directories to scan for functions and types.
|
||||
// Base dirs + language-specific dirs discovered automatically.
|
||||
funcDirs := []string{filepath.Join(root, "functions")}
|
||||
typeDirs := []string{filepath.Join(root, "types")}
|
||||
|
||||
@@ -86,6 +82,7 @@ func Index(db *DB, root string) (*IndexResult, error) {
|
||||
|
||||
// Parse apps from apps/*/app.md
|
||||
var apps []*App
|
||||
localAppIDs := make(map[string]bool)
|
||||
appsDir := filepath.Join(root, "apps")
|
||||
if fi, err := os.Stat(appsDir); err == nil && fi.IsDir() {
|
||||
entries, _ := os.ReadDir(appsDir)
|
||||
@@ -103,9 +100,39 @@ func Index(db *DB, root string) (*IndexResult, error) {
|
||||
continue
|
||||
}
|
||||
apps = append(apps, a)
|
||||
localAppIDs[a.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Parse analysis from analysis/*/analysis.md
|
||||
var analyses []*Analysis
|
||||
localAnalysisIDs := make(map[string]bool)
|
||||
analysisDir := filepath.Join(root, "analysis")
|
||||
if fi, err := os.Stat(analysisDir); err == nil && fi.IsDir() {
|
||||
entries, _ := os.ReadDir(analysisDir)
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
analysisMD := filepath.Join(analysisDir, e.Name(), "analysis.md")
|
||||
if _, err := os.Stat(analysisMD); err != nil {
|
||||
continue
|
||||
}
|
||||
an, err := ParseAnalysisMD(analysisMD, root)
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("parse %s: %v", analysisMD, err))
|
||||
continue
|
||||
}
|
||||
analyses = append(analyses, an)
|
||||
localAnalysisIDs[an.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Selective purge: preserve remote-only apps/analysis (have repo_url, not cloned locally)
|
||||
if err := db.PurgeLocalOnly(localAppIDs, localAnalysisIDs); err != nil {
|
||||
return nil, fmt.Errorf("purging database: %w", err)
|
||||
}
|
||||
|
||||
// Build known ID sets
|
||||
knownFunctions := make(map[string]bool, len(functions))
|
||||
for _, f := range functions {
|
||||
@@ -161,6 +188,20 @@ func Index(db *DB, root string) (*IndexResult, error) {
|
||||
result.Apps++
|
||||
}
|
||||
|
||||
for _, an := range analyses {
|
||||
if verr := ValidateAnalysis(an, knownFunctions, knownTypes); verr != nil {
|
||||
result.ValidationErrors = append(result.ValidationErrors, verr.Error())
|
||||
continue
|
||||
}
|
||||
an.ContentHash = ComputeAnalysisHash(an)
|
||||
applyTimestamps(&an.CreatedAt, &an.UpdatedAt, an.ContentHash, oldAnalysis[an.ID], now)
|
||||
if err := db.InsertAnalysis(an); err != nil {
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("insert %s: %v", an.ID, err))
|
||||
continue
|
||||
}
|
||||
result.Analysis++
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user