Skip to content

Commit 2a46bf0

Browse files
authored
Merge pull request #21 from supermodeltools/worktree-feat-arch-docs
feat: add arch-docs command for static architecture site generation
2 parents 2281ea3 + cad7f9b commit 2a46bf0

38 files changed

+9879
-50
lines changed

.github/workflows/architecture.yml

Lines changed: 0 additions & 48 deletions
This file was deleted.

.golangci.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ linters:
4040
- G122 # filepath.Walk TOCTOU — acceptable for repo archiving
4141
- G117 # secret field in marshaled struct — intentional (config file)
4242
- G104 # unhandled errors — covered by errcheck with targeted exclusions
43+
- G301 # directory permissions 0755 — intentional for static site output
44+
- G306 # file write permissions 0644 — intentional for static site output
45+
- G703 # path traversal — output paths come from user-supplied CLI flags
4346
exclusions:
4447
rules:
4548
# Deferred and best-effort closes are idiomatic Go — errors not actionable.
@@ -48,9 +51,13 @@ linters:
4851
# fmt.Fprintf/Fprintln to stdout/stderr — write errors are not actionable.
4952
- text: 'Error return value of `fmt\.Fp?rint'
5053
linters: [errcheck]
51-
# Best-effort temp file cleanup.
52-
- text: 'Error return value of `os\.Remove` is not checked'
54+
# Best-effort temp file/dir cleanup.
55+
- text: 'Error return value of `os\.Remove(All)?` is not checked'
5356
linters: [errcheck]
57+
# Ported packages from supermodeltools/arch-docs — style/complexity issues are
58+
# acceptable in vendored/ported code; fixing them would cause needless upstream divergence.
59+
- path: 'internal/archdocs/(graph2md|pssg)/'
60+
linters: [gocritic, gocyclo, gosec, revive, ineffassign, staticcheck]
5461
# Test files get more latitude.
5562
- path: _test\.go
5663
linters: [errcheck, gosec, gocritic]

cmd/archdocs.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/supermodeltools/cli/internal/archdocs"
7+
"github.com/supermodeltools/cli/internal/config"
8+
)
9+
10+
func init() {
11+
var opts archdocs.Options
12+
13+
c := &cobra.Command{
14+
Use: "arch-docs [path]",
15+
Short: "Generate static architecture documentation for a repository",
16+
Long: `Generate a static HTML site documenting the architecture of a codebase.
17+
18+
The command uploads the repository to the Supermodel API, converts the
19+
returned code graph to markdown, and builds a browsable static site with
20+
search, dependency graphs, taxonomy navigation, and SEO metadata.
21+
22+
The output directory can be served locally or deployed to any static host
23+
(GitHub Pages, Vercel, Netlify, Cloudflare Pages, etc.).
24+
25+
Examples:
26+
supermodel arch-docs
27+
supermodel arch-docs ./my-project --output ./docs-site
28+
supermodel arch-docs --repo owner/repo --base-url https://owner.github.io/repo
29+
supermodel arch-docs --site-name "My App Docs" --output /var/www/html`,
30+
Args: cobra.MaximumNArgs(1),
31+
RunE: func(cmd *cobra.Command, args []string) error {
32+
cfg, err := config.Load()
33+
if err != nil {
34+
return err
35+
}
36+
if err := cfg.RequireAPIKey(); err != nil {
37+
return err
38+
}
39+
dir := "."
40+
if len(args) > 0 {
41+
dir = args[0]
42+
}
43+
return archdocs.Run(cmd.Context(), cfg, dir, opts)
44+
},
45+
}
46+
47+
c.Flags().StringVar(&opts.SiteName, "site-name", "", "display title for the generated site (default: \"<repo> Architecture Docs\")")
48+
c.Flags().StringVar(&opts.BaseURL, "base-url", "", "canonical base URL where the site will be hosted (default: https://example.com)")
49+
c.Flags().StringVar(&opts.Repo, "repo", "", "GitHub repo slug owner/repo used to build source links")
50+
c.Flags().StringVarP(&opts.Output, "output", "o", "", "output directory for the generated site (default: ./arch-docs-output)")
51+
c.Flags().StringVar(&opts.TemplatesDir, "templates-dir", "", "override bundled HTML/CSS/JS templates with a custom directory")
52+
c.Flags().IntVar(&opts.MaxSourceFiles, "max-source-files", 3000, "maximum source files to include in analysis (0 = unlimited)")
53+
c.Flags().IntVar(&opts.MaxEntities, "max-entities", 12000, "maximum entity pages to generate (0 = unlimited)")
54+
c.Flags().BoolVar(&opts.Force, "force", false, "bypass cache and re-upload even if a cached result exists")
55+
56+
rootCmd.AddCommand(c)
57+
}

internal/api/client.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ func (c *Client) Analyze(ctx context.Context, zipPath, idempotencyKey string) (*
5050
return &result.Graph, nil
5151
}
5252

53+
// AnalyzeRaw uploads a repository ZIP and runs the full analysis pipeline,
54+
// returning the raw result JSON from the completed job. Use this when you need
55+
// the full response payload (e.g. for graph2md / arch-docs generation).
56+
func (c *Client) AnalyzeRaw(ctx context.Context, zipPath, idempotencyKey string) (json.RawMessage, error) {
57+
job, err := c.pollUntilComplete(ctx, zipPath, idempotencyKey)
58+
if err != nil {
59+
return nil, err
60+
}
61+
return job.Result, nil
62+
}
63+
5364
// AnalyzeDomains uploads a repository ZIP and runs the full analysis pipeline,
5465
// returning the complete SupermodelIR response (domains, summary, metadata, graph).
5566
// Use this instead of Analyze when you need high-level domain information.

0 commit comments

Comments
 (0)