cli/internal/cmd/docs/cmd_scan.go
Snider 03c9188d79
feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs

Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.

Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.

Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add collect, config, crypt, plugin packages and fix all lint issues

Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)

Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.

Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00

141 lines
3.4 KiB
Go

package docs
import (
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/host-uk/core/internal/cmd/workspace"
"github.com/host-uk/core/pkg/cli"
"github.com/host-uk/core/pkg/i18n"
"github.com/host-uk/core/pkg/io"
"github.com/host-uk/core/pkg/repos"
)
// RepoDocInfo holds documentation info for a repo
type RepoDocInfo struct {
Name string
Path string
HasDocs bool
Readme string
ClaudeMd string
Changelog string
DocsFiles []string // All files in docs/ directory (recursive)
}
func loadRegistry(registryPath string) (*repos.Registry, string, error) {
var reg *repos.Registry
var err error
var registryDir string
if registryPath != "" {
reg, err = repos.LoadRegistry(registryPath)
if err != nil {
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
}
registryDir = filepath.Dir(registryPath)
} else {
registryPath, err = repos.FindRegistry()
if err == nil {
reg, err = repos.LoadRegistry(registryPath)
if err != nil {
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
}
registryDir = filepath.Dir(registryPath)
} else {
cwd, _ := os.Getwd()
reg, err = repos.ScanDirectory(cwd)
if err != nil {
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.scan", "directory"))
}
registryDir = cwd
}
}
// Load workspace config to respect packages_dir
wsConfig, err := workspace.LoadConfig(registryDir)
if err != nil {
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "workspace config"))
}
basePath := registryDir
if wsConfig != nil && wsConfig.PackagesDir != "" && wsConfig.PackagesDir != "./packages" {
pkgDir := wsConfig.PackagesDir
// Expand ~
if strings.HasPrefix(pkgDir, "~/") {
home, _ := os.UserHomeDir()
pkgDir = filepath.Join(home, pkgDir[2:])
}
if !filepath.IsAbs(pkgDir) {
pkgDir = filepath.Join(registryDir, pkgDir)
}
basePath = pkgDir
// Update repo paths if they were relative to registry
// This ensures consistency when packages_dir overrides the default
reg.BasePath = basePath
for _, repo := range reg.Repos {
repo.Path = filepath.Join(basePath, repo.Name)
}
}
return reg, basePath, nil
}
func scanRepoDocs(repo *repos.Repo) RepoDocInfo {
info := RepoDocInfo{
Name: repo.Name,
Path: repo.Path,
}
// Check for README.md
readme := filepath.Join(repo.Path, "README.md")
if io.Local.IsFile(readme) {
info.Readme = readme
info.HasDocs = true
}
// Check for CLAUDE.md
claudeMd := filepath.Join(repo.Path, "CLAUDE.md")
if io.Local.IsFile(claudeMd) {
info.ClaudeMd = claudeMd
info.HasDocs = true
}
// Check for CHANGELOG.md
changelog := filepath.Join(repo.Path, "CHANGELOG.md")
if io.Local.IsFile(changelog) {
info.Changelog = changelog
info.HasDocs = true
}
// Recursively scan docs/ directory for .md files
docsDir := filepath.Join(repo.Path, "docs")
// Check if directory exists by listing it
if _, err := io.Local.List(docsDir); err == nil {
_ = filepath.WalkDir(docsDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
// Skip plans/ directory
if d.IsDir() && d.Name() == "plans" {
return filepath.SkipDir
}
// Skip non-markdown files
if d.IsDir() || !strings.HasSuffix(d.Name(), ".md") {
return nil
}
// Get relative path from docs/
relPath, _ := filepath.Rel(docsDir, path)
info.DocsFiles = append(info.DocsFiles, relPath)
info.HasDocs = true
return nil
})
}
return info
}