* 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>
148 lines
3.4 KiB
Go
148 lines
3.4 KiB
Go
package cli
|
|
|
|
import "fmt"
|
|
|
|
// Region represents one of the 5 HLCRF regions.
|
|
type Region rune
|
|
|
|
const (
|
|
// RegionHeader is the top region of the layout.
|
|
RegionHeader Region = 'H'
|
|
// RegionLeft is the left sidebar region.
|
|
RegionLeft Region = 'L'
|
|
// RegionContent is the main content region.
|
|
RegionContent Region = 'C'
|
|
// RegionRight is the right sidebar region.
|
|
RegionRight Region = 'R'
|
|
// RegionFooter is the bottom region of the layout.
|
|
RegionFooter Region = 'F'
|
|
)
|
|
|
|
// Composite represents an HLCRF layout node.
|
|
type Composite struct {
|
|
variant string
|
|
path string
|
|
regions map[Region]*Slot
|
|
parent *Composite
|
|
}
|
|
|
|
// Slot holds content for a region.
|
|
type Slot struct {
|
|
region Region
|
|
path string
|
|
blocks []Renderable
|
|
child *Composite
|
|
}
|
|
|
|
// Renderable is anything that can be rendered to terminal.
|
|
type Renderable interface {
|
|
Render() string
|
|
}
|
|
|
|
// StringBlock is a simple string that implements Renderable.
|
|
type StringBlock string
|
|
|
|
// Render returns the string content.
|
|
func (s StringBlock) Render() string { return string(s) }
|
|
|
|
// Layout creates a new layout from a variant string.
|
|
func Layout(variant string) *Composite {
|
|
c, err := ParseVariant(variant)
|
|
if err != nil {
|
|
return &Composite{variant: variant, regions: make(map[Region]*Slot)}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// ParseVariant parses a variant string like "H[LC]C[HCF]F".
|
|
func ParseVariant(variant string) (*Composite, error) {
|
|
c := &Composite{
|
|
variant: variant,
|
|
path: "",
|
|
regions: make(map[Region]*Slot),
|
|
}
|
|
|
|
i := 0
|
|
for i < len(variant) {
|
|
r := Region(variant[i])
|
|
if !isValidRegion(r) {
|
|
return nil, fmt.Errorf("invalid region: %c", r)
|
|
}
|
|
|
|
slot := &Slot{region: r, path: string(r)}
|
|
c.regions[r] = slot
|
|
i++
|
|
|
|
if i < len(variant) && variant[i] == '[' {
|
|
end := findMatchingBracket(variant, i)
|
|
if end == -1 {
|
|
return nil, fmt.Errorf("unmatched bracket at %d", i)
|
|
}
|
|
nested, err := ParseVariant(variant[i+1 : end])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nested.path = string(r) + "-"
|
|
nested.parent = c
|
|
slot.child = nested
|
|
i = end + 1
|
|
}
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func isValidRegion(r Region) bool {
|
|
return r == 'H' || r == 'L' || r == 'C' || r == 'R' || r == 'F'
|
|
}
|
|
|
|
func findMatchingBracket(s string, start int) int {
|
|
depth := 0
|
|
for i := start; i < len(s); i++ {
|
|
switch s[i] {
|
|
case '[':
|
|
depth++
|
|
case ']':
|
|
depth--
|
|
if depth == 0 {
|
|
return i
|
|
}
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// H adds content to Header region.
|
|
func (c *Composite) H(items ...any) *Composite { c.addToRegion(RegionHeader, items...); return c }
|
|
|
|
// L adds content to Left region.
|
|
func (c *Composite) L(items ...any) *Composite { c.addToRegion(RegionLeft, items...); return c }
|
|
|
|
// C adds content to Content region.
|
|
func (c *Composite) C(items ...any) *Composite { c.addToRegion(RegionContent, items...); return c }
|
|
|
|
// R adds content to Right region.
|
|
func (c *Composite) R(items ...any) *Composite { c.addToRegion(RegionRight, items...); return c }
|
|
|
|
// F adds content to Footer region.
|
|
func (c *Composite) F(items ...any) *Composite { c.addToRegion(RegionFooter, items...); return c }
|
|
|
|
func (c *Composite) addToRegion(r Region, items ...any) {
|
|
slot, ok := c.regions[r]
|
|
if !ok {
|
|
return
|
|
}
|
|
for _, item := range items {
|
|
slot.blocks = append(slot.blocks, toRenderable(item))
|
|
}
|
|
}
|
|
|
|
func toRenderable(item any) Renderable {
|
|
switch v := item.(type) {
|
|
case Renderable:
|
|
return v
|
|
case string:
|
|
return StringBlock(v)
|
|
default:
|
|
return StringBlock(fmt.Sprint(v))
|
|
}
|
|
}
|