From 15062ad3d57075b65670befab21e9a2db0324e64 Mon Sep 17 00:00:00 2001 From: jonathanpopham Date: Thu, 9 Apr 2026 22:34:45 -0400 Subject: [PATCH 1/2] feat: send previousDomains on full refresh to stabilize domain names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Generate runs with --force and a prior shards.json cache exists, extract domain names + subdomain counts and pass them as the previousDomains query param. The API (v0.12.0) seeds the LLM prompt to reuse those names where the same groupings still apply. Tested on Directus (17,877 nodes) against production: - Run 1 (fresh): ArtificialIntelligence, DataModel, CoreServices, ServerInfrastucture, CliCommands, DirectusApp - Run 2 (seeded): 83% name reuse (5/6 identical, 1 typo corrected by LLM: ServerInfrastucture → ServerInfrastructure) Closes #110 --- internal/analyze/handler.go | 2 +- internal/api/client.go | 20 ++++++++++++++++++-- internal/shards/daemon.go | 4 ++-- internal/shards/handler.go | 16 +++++++++++++++- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/internal/analyze/handler.go b/internal/analyze/handler.go index 12b8663..83d5753 100644 --- a/internal/analyze/handler.go +++ b/internal/analyze/handler.go @@ -73,7 +73,7 @@ func GetGraph(ctx context.Context, cfg *config.Config, dir string, force bool) ( client := api.New(cfg) spin = ui.Start("Uploading and analyzing repository…") - ir, err := client.AnalyzeShards(ctx, zipPath, "analyze-"+hash[:16]) + ir, err := client.AnalyzeShards(ctx, zipPath, "analyze-"+hash[:16], nil) spin.Stop() if err != nil { return nil, hash, err diff --git a/internal/api/client.go b/internal/api/client.go index cf2206c..b614b87 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -8,6 +8,7 @@ import ( "io" "mime/multipart" "net/http" + "net/url" "os" "path/filepath" "time" @@ -115,11 +116,26 @@ func (c *Client) pollLoop(ctx context.Context, post func() (*JobResponse, error) return job, nil } +// PreviousDomain holds domain name + subdomain count for seeding the LLM prompt. +type PreviousDomain struct { + Name string `json:"name"` + SubdomainCount int `json:"subdomainCount"` +} + // AnalyzeShards uploads a repository ZIP and runs the full analysis pipeline, // returning the complete ShardIR response with full Node/Relationship data // required for sidecar rendering (IDs, labels, properties preserved). -func (c *Client) AnalyzeShards(ctx context.Context, zipPath, idempotencyKey string) (*ShardIR, error) { - job, err := c.pollUntilComplete(ctx, zipPath, idempotencyKey) +// If previousDomains is non-nil, passes them as a query param to seed domain names. +func (c *Client) AnalyzeShards(ctx context.Context, zipPath, idempotencyKey string, previousDomains []PreviousDomain) (*ShardIR, error) { + endpoint := analyzeEndpoint + if len(previousDomains) > 0 { + pd, err := json.Marshal(previousDomains) + if err == nil { + endpoint += "?previousDomains=" + url.QueryEscape(string(pd)) + } + } + post := func() (*JobResponse, error) { return c.postZipTo(ctx, zipPath, idempotencyKey, endpoint) } + job, err := c.pollLoop(ctx, post) if err != nil { return nil, err } diff --git a/internal/shards/daemon.go b/internal/shards/daemon.go index 1f5e425..619a33a 100644 --- a/internal/shards/daemon.go +++ b/internal/shards/daemon.go @@ -213,7 +213,7 @@ func (d *Daemon) fullGenerate(ctx context.Context) error { } defer os.Remove(zipPath) - ir, err := d.client.AnalyzeShards(ctx, zipPath, idemKey) + ir, err := d.client.AnalyzeShards(ctx, zipPath, idemKey, nil) if err != nil { return err } @@ -251,7 +251,7 @@ func (d *Daemon) incrementalUpdate(ctx context.Context, changedFiles []string) { } defer os.Remove(zipPath) - ir, err := d.client.AnalyzeShards(ctx, zipPath, "incremental-"+idemKey[:8]) + ir, err := d.client.AnalyzeShards(ctx, zipPath, "incremental-"+idemKey[:8], nil) if err != nil { d.logf("Incremental API error: %v", err) return diff --git a/internal/shards/handler.go b/internal/shards/handler.go index 9fd73fd..3168899 100644 --- a/internal/shards/handler.go +++ b/internal/shards/handler.go @@ -93,11 +93,25 @@ func Generate(ctx context.Context, cfg *config.Config, dir string, opts Generate } defer os.Remove(zipPath) + // Read previous domains from cache for LLM seeding (stabilizes names across refreshes) + var prevDomains []api.PreviousDomain + if data, readErr := os.ReadFile(cacheFile); readErr == nil { + var prev api.ShardIR + if json.Unmarshal(data, &prev) == nil && len(prev.Domains) > 0 { + for _, d := range prev.Domains { + prevDomains = append(prevDomains, api.PreviousDomain{ + Name: d.Name, + SubdomainCount: len(d.Subdomains), + }) + } + } + } + client := api.New(cfg) idemKey := newUUID() spin = ui.Start("Uploading and analyzing repository…") - ir, err := client.AnalyzeShards(ctx, zipPath, "shards-"+idemKey[:8]) + ir, err := client.AnalyzeShards(ctx, zipPath, "shards-"+idemKey[:8], prevDomains) spin.Stop() if err != nil { return err From bd92a9b8e25366e17017215fdfc1fc108cab1d9a Mon Sep 17 00:00:00 2001 From: jonathanpopham Date: Fri, 10 Apr 2026 15:52:25 -0400 Subject: [PATCH 2/2] fix: goimports alignment --- internal/api/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/api/client.go b/internal/api/client.go index b614b87..b88a3ae 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -118,8 +118,8 @@ func (c *Client) pollLoop(ctx context.Context, post func() (*JobResponse, error) // PreviousDomain holds domain name + subdomain count for seeding the LLM prompt. type PreviousDomain struct { - Name string `json:"name"` - SubdomainCount int `json:"subdomainCount"` + Name string `json:"name"` + SubdomainCount int `json:"subdomainCount"` } // AnalyzeShards uploads a repository ZIP and runs the full analysis pipeline,