* feat(go): make go fmt git-aware by default - By default, only check changed Go files (modified, staged, untracked) - Add --all flag to check all files (previous behaviour) - Reduces noise when running fmt on large codebases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(build): minimal output by default, add missing i18n - Default output now shows single line: "Success Built N artifacts (dir)" - Add --verbose/-v flag to show full detailed output - Add all missing i18n translations for build commands - Errors still show failure reason in minimal mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add root-level `core git` command - Create pkg/gitcmd with git workflow commands as root menu - Export command builders from pkg/dev (AddCommitCommand, etc.) - Commands available under both `core git` and `core dev` for compatibility - Git commands: health, commit, push, pull, work, sync, apply - GitHub orchestration stays in dev: issues, reviews, ci, impact Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(qa): add docblock coverage checking Implement docblock/docstring coverage analysis for Go code: - New `core qa docblock` command to check coverage - Shows compact file:line list when under threshold - Integrate with `core go qa` as a default check - Add --docblock-threshold flag (default 80%) The checker uses Go AST parsing to find exported symbols (functions, types, consts, vars) without documentation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - Fix doc comment: "status" → "health" in gitcmd package - Implement --check flag for `core go fmt` (exits non-zero if files need formatting) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add docstrings for 100% coverage Add documentation comments to all exported symbols: - pkg/build: ProjectType constants - pkg/cli: LogLevel, RenderStyle, TableStyle - pkg/framework: ServiceFor, MustServiceFor, Core.Core - pkg/git: GitError.Error, GitError.Unwrap - pkg/i18n: Handler Match/Handle methods - pkg/log: Level constants - pkg/mcp: Tool input/output types - pkg/php: Service constants, QA types, service methods - pkg/process: ServiceError.Error - pkg/repos: RepoType constants - pkg/setup: ChangeType, ChangeCategory constants - pkg/workspace: AddWorkspaceCommands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: standardize line endings to LF Add .gitattributes to enforce LF line endings for all text files. Normalize all existing files to use Unix-style line endings. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - cmd_format.go: validate --check/--fix mutual exclusivity, capture stderr - cmd_docblock.go: return error instead of os.Exit(1) for proper error handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback (round 2) - linuxkit.go: propagate state update errors, handle cmd.Wait() errors in waitForExit - mcp.go: guard against empty old_string in editDiff to prevent runaway edits - cmd_docblock.go: log parse errors instead of silently skipping Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
155 lines
4.2 KiB
Go
155 lines
4.2 KiB
Go
package docs
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/host-uk/core/pkg/cli"
|
|
"github.com/host-uk/core/pkg/i18n"
|
|
)
|
|
|
|
// Flag variables for sync command
|
|
var (
|
|
docsSyncRegistryPath string
|
|
docsSyncDryRun bool
|
|
docsSyncOutputDir string
|
|
)
|
|
|
|
var docsSyncCmd = &cli.Command{
|
|
Use: "sync",
|
|
Short: i18n.T("cmd.docs.sync.short"),
|
|
Long: i18n.T("cmd.docs.sync.long"),
|
|
RunE: func(cmd *cli.Command, args []string) error {
|
|
return runDocsSync(docsSyncRegistryPath, docsSyncOutputDir, docsSyncDryRun)
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
docsSyncCmd.Flags().StringVar(&docsSyncRegistryPath, "registry", "", i18n.T("common.flag.registry"))
|
|
docsSyncCmd.Flags().BoolVar(&docsSyncDryRun, "dry-run", false, i18n.T("cmd.docs.sync.flag.dry_run"))
|
|
docsSyncCmd.Flags().StringVar(&docsSyncOutputDir, "output", "", i18n.T("cmd.docs.sync.flag.output"))
|
|
}
|
|
|
|
// packageOutputName maps repo name to output folder name
|
|
func packageOutputName(repoName string) string {
|
|
// core -> go (the Go framework)
|
|
if repoName == "core" {
|
|
return "go"
|
|
}
|
|
// core-admin -> admin, core-api -> api, etc.
|
|
if strings.HasPrefix(repoName, "core-") {
|
|
return strings.TrimPrefix(repoName, "core-")
|
|
}
|
|
return repoName
|
|
}
|
|
|
|
// shouldSyncRepo returns true if this repo should be synced
|
|
func shouldSyncRepo(repoName string) bool {
|
|
// Skip core-php (it's the destination)
|
|
if repoName == "core-php" {
|
|
return false
|
|
}
|
|
// Skip template
|
|
if repoName == "core-template" {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func runDocsSync(registryPath string, outputDir string, dryRun bool) error {
|
|
// Find or use provided registry
|
|
reg, basePath, err := loadRegistry(registryPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Default output to core-php/docs/packages relative to registry
|
|
if outputDir == "" {
|
|
outputDir = filepath.Join(basePath, "core-php", "docs", "packages")
|
|
}
|
|
|
|
// Scan all repos for docs
|
|
var docsInfo []RepoDocInfo
|
|
for _, repo := range reg.List() {
|
|
if !shouldSyncRepo(repo.Name) {
|
|
continue
|
|
}
|
|
info := scanRepoDocs(repo)
|
|
if info.HasDocs && len(info.DocsFiles) > 0 {
|
|
docsInfo = append(docsInfo, info)
|
|
}
|
|
}
|
|
|
|
if len(docsInfo) == 0 {
|
|
cli.Text(i18n.T("cmd.docs.sync.no_docs_found"))
|
|
return nil
|
|
}
|
|
|
|
cli.Print("\n%s %s\n\n", dimStyle.Render(i18n.T("cmd.docs.sync.found_label")), i18n.T("cmd.docs.sync.repos_with_docs", map[string]interface{}{"Count": len(docsInfo)}))
|
|
|
|
// Show what will be synced
|
|
var totalFiles int
|
|
for _, info := range docsInfo {
|
|
totalFiles += len(info.DocsFiles)
|
|
outName := packageOutputName(info.Name)
|
|
cli.Print(" %s → %s %s\n",
|
|
repoNameStyle.Render(info.Name),
|
|
docsFileStyle.Render("packages/"+outName+"/"),
|
|
dimStyle.Render(i18n.T("cmd.docs.sync.files_count", map[string]interface{}{"Count": len(info.DocsFiles)})))
|
|
|
|
for _, f := range info.DocsFiles {
|
|
cli.Print(" %s\n", dimStyle.Render(f))
|
|
}
|
|
}
|
|
|
|
cli.Print("\n%s %s\n",
|
|
dimStyle.Render(i18n.Label("total")),
|
|
i18n.T("cmd.docs.sync.total_summary", map[string]interface{}{"Files": totalFiles, "Repos": len(docsInfo), "Output": outputDir}))
|
|
|
|
if dryRun {
|
|
cli.Print("\n%s\n", dimStyle.Render(i18n.T("cmd.docs.sync.dry_run_notice")))
|
|
return nil
|
|
}
|
|
|
|
// Confirm
|
|
cli.Blank()
|
|
if !confirm(i18n.T("cmd.docs.sync.confirm")) {
|
|
cli.Text(i18n.T("common.prompt.abort"))
|
|
return nil
|
|
}
|
|
|
|
// Sync docs
|
|
cli.Blank()
|
|
var synced int
|
|
for _, info := range docsInfo {
|
|
outName := packageOutputName(info.Name)
|
|
repoOutDir := filepath.Join(outputDir, outName)
|
|
|
|
// Clear existing directory
|
|
os.RemoveAll(repoOutDir)
|
|
|
|
if err := os.MkdirAll(repoOutDir, 0755); err != nil {
|
|
cli.Print(" %s %s: %s\n", errorStyle.Render("✗"), info.Name, err)
|
|
continue
|
|
}
|
|
|
|
// Copy all docs files
|
|
docsDir := filepath.Join(info.Path, "docs")
|
|
for _, f := range info.DocsFiles {
|
|
src := filepath.Join(docsDir, f)
|
|
dst := filepath.Join(repoOutDir, f)
|
|
os.MkdirAll(filepath.Dir(dst), 0755)
|
|
if err := copyFile(src, dst); err != nil {
|
|
cli.Print(" %s %s: %s\n", errorStyle.Render("✗"), f, err)
|
|
}
|
|
}
|
|
|
|
cli.Print(" %s %s → packages/%s/\n", successStyle.Render("✓"), info.Name, outName)
|
|
synced++
|
|
}
|
|
|
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.T("i18n.done.sync")), i18n.T("cmd.docs.sync.synced_packages", map[string]interface{}{"Count": synced}))
|
|
|
|
return nil
|
|
}
|