refactor(cli): restructure cmd packages into subdirectories
- Move CLI commands into subdirectories matching command hierarchy:
dev/, go/, php/, build/, ci/, sdk/, pkg/, vm/, docs/, setup/, doctor/, test/, ai/
- Create shared/ package for common styles and utilities
- Add new `core ai` root command with claude subcommand
- Update package declarations and imports across all files
- Create commands.go entry points for each package
- Remove GUI-related files (moved to core-gui repo)
This makes the filesystem structure match the CLI command structure,
improving context capture and code organization.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:02:43 +00:00
|
|
|
package dev
|
2026-01-27 21:08:51 +00:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2026-02-17 19:19:40 +00:00
|
|
|
"forge.lthn.ai/core/go-ai/agentic"
|
2026-02-16 14:24:37 +00:00
|
|
|
"forge.lthn.ai/core/go/pkg/cli"
|
2026-02-17 19:19:40 +00:00
|
|
|
"forge.lthn.ai/core/go-scm/git"
|
2026-02-16 14:24:37 +00:00
|
|
|
"forge.lthn.ai/core/go/pkg/i18n"
|
2026-01-30 00:47:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Work command flags
|
|
|
|
|
var (
|
|
|
|
|
workStatusOnly bool
|
|
|
|
|
workAutoCommit bool
|
|
|
|
|
workRegistryPath string
|
2026-01-27 21:08:51 +00:00
|
|
|
)
|
|
|
|
|
|
feat: git command, build improvements, and go fmt git-aware (#74)
* 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>
2026-02-01 10:48:44 +00:00
|
|
|
// AddWorkCommand adds the 'work' command to the given parent command.
|
|
|
|
|
func AddWorkCommand(parent *cli.Command) {
|
2026-01-31 11:39:19 +00:00
|
|
|
workCmd := &cli.Command{
|
2026-01-30 00:47:54 +00:00
|
|
|
Use: "work",
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
Short: i18n.T("cmd.dev.work.short"),
|
|
|
|
|
Long: i18n.T("cmd.dev.work.long"),
|
2026-01-31 11:39:19 +00:00
|
|
|
RunE: func(cmd *cli.Command, args []string) error {
|
2026-01-30 00:47:54 +00:00
|
|
|
return runWork(workRegistryPath, workStatusOnly, workAutoCommit)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
workCmd.Flags().BoolVar(&workStatusOnly, "status", false, i18n.T("cmd.dev.work.flag.status"))
|
|
|
|
|
workCmd.Flags().BoolVar(&workAutoCommit, "commit", false, i18n.T("cmd.dev.work.flag.commit"))
|
refactor(i18n): consolidate duplicate translation keys into common section
Add common.* keys for reusable translations:
- common.label.* - UI labels (error, done, status, version, etc.)
- common.status.* - status words (running, stopped, dirty, synced)
- common.error.* - error messages (failed, not_found, working_dir)
- common.flag.* - CLI flag descriptions (registry, verbose, etc.)
- common.count.* - count templates (failed, passed, skipped)
- common.result.* - result messages (all_passed, no_issues)
- common.progress.* - progress messages (running, checking)
- common.hint.* - help hints (install_with, fix_deps)
Update all cmd/* files to use common keys instead of duplicated
command-specific keys. Reduces translation maintenance burden
and ensures consistency across the CLI.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 11:32:25 +00:00
|
|
|
workCmd.Flags().StringVar(&workRegistryPath, "registry", "", i18n.T("common.flag.registry"))
|
2026-01-30 00:47:54 +00:00
|
|
|
|
|
|
|
|
parent.AddCommand(workCmd)
|
2026-01-27 21:08:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
// Build worker bundle with required services
|
|
|
|
|
bundle, err := NewWorkBundle(WorkBundleOptions{
|
|
|
|
|
RegistryPath: registryPath,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2026-01-27 21:08:51 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
// Start services (registers handlers)
|
|
|
|
|
if err := bundle.Start(ctx); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
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
|
|
|
defer func() { _ = bundle.Stop(ctx) }()
|
2026-01-27 21:08:51 +00:00
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
// Load registry and get paths
|
2026-02-01 02:07:26 +00:00
|
|
|
paths, names, err := func() ([]string, map[string]string, error) {
|
|
|
|
|
reg, _, err := loadRegistryWithConfig(registryPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
var paths []string
|
|
|
|
|
names := make(map[string]string)
|
|
|
|
|
for _, repo := range reg.List() {
|
|
|
|
|
if repo.IsGitRepo() {
|
|
|
|
|
paths = append(paths, repo.Path)
|
|
|
|
|
names[repo.Path] = repo.Name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return paths, names, nil
|
|
|
|
|
}()
|
2026-01-30 10:18:54 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2026-01-27 21:08:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(paths) == 0 {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Text(i18n.T("cmd.dev.no_git_repos"))
|
2026-01-27 21:08:51 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
// QUERY git status
|
|
|
|
|
result, handled, err := bundle.Core.QUERY(git.QueryStatus{
|
2026-01-27 21:08:51 +00:00
|
|
|
Paths: paths,
|
|
|
|
|
Names: names,
|
|
|
|
|
})
|
2026-01-30 10:18:54 +00:00
|
|
|
if !handled {
|
2026-01-31 11:39:19 +00:00
|
|
|
return cli.Err("git service not available")
|
2026-01-30 10:18:54 +00:00
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
statuses := result.([]git.RepoStatus)
|
2026-01-27 21:08:51 +00:00
|
|
|
|
|
|
|
|
// Sort by repo name for consistent output
|
|
|
|
|
sort.Slice(statuses, func(i, j int) bool {
|
|
|
|
|
return statuses[i].Name < statuses[j].Name
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Display status table
|
|
|
|
|
printStatusTable(statuses)
|
|
|
|
|
|
|
|
|
|
// Collect dirty and ahead repos
|
|
|
|
|
var dirtyRepos []git.RepoStatus
|
|
|
|
|
var aheadRepos []git.RepoStatus
|
|
|
|
|
|
|
|
|
|
for _, s := range statuses {
|
|
|
|
|
if s.Error != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if s.IsDirty() {
|
|
|
|
|
dirtyRepos = append(dirtyRepos, s)
|
|
|
|
|
}
|
|
|
|
|
if s.HasUnpushed() {
|
|
|
|
|
aheadRepos = append(aheadRepos, s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto-commit dirty repos if requested
|
|
|
|
|
if autoCommit && len(dirtyRepos) > 0 {
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print("%s\n", cli.TitleStyle.Render(i18n.T("cmd.dev.commit.committing")))
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-27 21:08:51 +00:00
|
|
|
|
|
|
|
|
for _, s := range dirtyRepos {
|
2026-01-30 10:18:54 +00:00
|
|
|
// PERFORM commit via agentic service
|
|
|
|
|
_, handled, err := bundle.Core.PERFORM(agentic.TaskCommit{
|
|
|
|
|
Path: s.Path,
|
|
|
|
|
Name: s.Name,
|
|
|
|
|
})
|
|
|
|
|
if !handled {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s: %s\n", warningStyle.Render("!"), s.Name, "agentic service not available")
|
2026-01-30 10:18:54 +00:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
2026-01-27 21:08:51 +00:00
|
|
|
} else {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s\n", successStyle.Render("v"), s.Name)
|
2026-01-27 21:08:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
// Re-QUERY status after commits
|
|
|
|
|
result, _, _ = bundle.Core.QUERY(git.QueryStatus{
|
2026-01-27 21:08:51 +00:00
|
|
|
Paths: paths,
|
|
|
|
|
Names: names,
|
|
|
|
|
})
|
2026-01-30 10:18:54 +00:00
|
|
|
statuses = result.([]git.RepoStatus)
|
2026-01-27 21:08:51 +00:00
|
|
|
|
|
|
|
|
// Rebuild ahead repos list
|
|
|
|
|
aheadRepos = nil
|
|
|
|
|
for _, s := range statuses {
|
|
|
|
|
if s.Error == nil && s.HasUnpushed() {
|
|
|
|
|
aheadRepos = append(aheadRepos, s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If status only, we're done
|
|
|
|
|
if statusOnly {
|
|
|
|
|
if len(dirtyRepos) > 0 && !autoCommit {
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print("%s\n", dimStyle.Render(i18n.T("cmd.dev.work.use_commit_flag")))
|
2026-01-27 21:08:51 +00:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Push repos with unpushed commits
|
|
|
|
|
if len(aheadRepos) == 0 {
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Text(i18n.T("cmd.dev.work.all_up_to_date"))
|
2026-01-27 21:08:51 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print("%s\n", i18n.T("common.count.repos_unpushed", map[string]interface{}{"Count": len(aheadRepos)}))
|
2026-01-27 21:08:51 +00:00
|
|
|
for _, s := range aheadRepos {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s: %s\n", s.Name, i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead}))
|
2026-01-27 21:08:51 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-30 10:32:05 +00:00
|
|
|
if !cli.Confirm(i18n.T("cmd.dev.push.confirm")) {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Text(i18n.T("cli.aborted"))
|
2026-01-27 21:08:51 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-27 21:08:51 +00:00
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
// PERFORM push for each repo
|
|
|
|
|
var divergedRepos []git.RepoStatus
|
2026-01-30 08:01:52 +00:00
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
for _, s := range aheadRepos {
|
|
|
|
|
_, handled, err := bundle.Core.PERFORM(git.TaskPush{
|
|
|
|
|
Path: s.Path,
|
|
|
|
|
Name: s.Name,
|
|
|
|
|
})
|
|
|
|
|
if !handled {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, "git service not available")
|
2026-01-30 10:18:54 +00:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
if git.IsNonFastForward(err) {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s: %s\n", warningStyle.Render("!"), s.Name, i18n.T("cmd.dev.push.diverged"))
|
2026-01-30 10:18:54 +00:00
|
|
|
divergedRepos = append(divergedRepos, s)
|
2026-01-30 08:01:52 +00:00
|
|
|
} else {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
2026-01-30 08:01:52 +00:00
|
|
|
}
|
2026-01-30 10:18:54 +00:00
|
|
|
} else {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s\n", successStyle.Render("v"), s.Name)
|
2026-01-30 08:01:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle diverged repos - offer to pull and retry
|
|
|
|
|
if len(divergedRepos) > 0 {
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print("%s\n", i18n.T("cmd.dev.push.diverged_help"))
|
2026-01-30 10:32:05 +00:00
|
|
|
if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) {
|
2026-01-31 23:36:43 +00:00
|
|
|
cli.Blank()
|
2026-01-30 10:18:54 +00:00
|
|
|
for _, s := range divergedRepos {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s...\n", dimStyle.Render("↓"), s.Name)
|
2026-01-30 10:18:54 +00:00
|
|
|
|
|
|
|
|
// PERFORM pull
|
|
|
|
|
_, _, err := bundle.Core.PERFORM(git.TaskPull{Path: s.Path, Name: s.Name})
|
|
|
|
|
if err != nil {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
2026-01-30 08:01:52 +00:00
|
|
|
continue
|
|
|
|
|
}
|
2026-01-30 10:18:54 +00:00
|
|
|
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s...\n", dimStyle.Render("↑"), s.Name)
|
2026-01-30 10:18:54 +00:00
|
|
|
|
|
|
|
|
// PERFORM push
|
|
|
|
|
_, _, err = bundle.Core.PERFORM(git.TaskPush{Path: s.Path, Name: s.Name})
|
|
|
|
|
if err != nil {
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
2026-01-30 08:01:52 +00:00
|
|
|
continue
|
|
|
|
|
}
|
2026-01-30 10:18:54 +00:00
|
|
|
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print(" %s %s\n", successStyle.Render("v"), s.Name)
|
2026-01-30 08:01:52 +00:00
|
|
|
}
|
2026-01-27 21:08:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func printStatusTable(statuses []git.RepoStatus) {
|
|
|
|
|
// Calculate column widths
|
|
|
|
|
nameWidth := 4 // "Repo"
|
|
|
|
|
for _, s := range statuses {
|
|
|
|
|
if len(s.Name) > nameWidth {
|
|
|
|
|
nameWidth = len(s.Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Print header with fixed-width formatting
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Print("%-*s %8s %9s %6s %5s\n",
|
2026-01-27 21:08:51 +00:00
|
|
|
nameWidth,
|
refactor(i18n): migrate all pkg/* to grammar engine
Replace verbose map-based translation calls with concise grammar
engine helpers across all command packages:
- i18n.T("common.label.xxx") → i18n.Label("xxx")
- i18n.T("common.error.failed", map) → i18n.T("i18n.fail.verb", subj)
- i18n.T("common.progress.running", map) → i18n.ProgressSubject()
- i18n.T("common.count.xxx", map) → i18n.T("i18n.count.xxx", n)
Packages updated: ai, ci, dev, docs, php, pkgcmd, sdk, setup, test, vm
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:45 +00:00
|
|
|
cli.TitleStyle.Render(i18n.Label("repo")),
|
2026-01-30 10:32:05 +00:00
|
|
|
cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_modified")),
|
|
|
|
|
cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_untracked")),
|
|
|
|
|
cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_staged")),
|
|
|
|
|
cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_ahead")),
|
2026-01-27 21:08:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Print separator
|
2026-01-31 11:39:19 +00:00
|
|
|
cli.Text(strings.Repeat("-", nameWidth+2+10+11+8+7))
|
2026-01-27 21:08:51 +00:00
|
|
|
|
|
|
|
|
// Print rows
|
|
|
|
|
for _, s := range statuses {
|
|
|
|
|
if s.Error != nil {
|
2026-01-31 11:39:19 +00:00
|
|
|
paddedName := cli.Sprintf("%-*s", nameWidth, s.Name)
|
|
|
|
|
cli.Print("%s %s\n",
|
2026-01-27 21:08:51 +00:00
|
|
|
repoNameStyle.Render(paddedName),
|
feat(i18n): add translation keys to all CLI commands
Replace hardcoded strings with i18n.T() calls across all cmd/* packages:
- ai, build, ci, dev, docs, doctor, go, php, pkg, sdk, setup, test, vm
Adds 500+ translation keys to en.json for command descriptions,
flag descriptions, labels, messages, and error strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 02:37:57 +00:00
|
|
|
errorStyle.Render(i18n.T("cmd.dev.work.error_prefix")+" "+s.Error.Error()),
|
2026-01-27 21:08:51 +00:00
|
|
|
)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Style numbers based on values
|
2026-01-31 11:39:19 +00:00
|
|
|
modStr := cli.Sprintf("%d", s.Modified)
|
2026-01-27 21:08:51 +00:00
|
|
|
if s.Modified > 0 {
|
|
|
|
|
modStr = dirtyStyle.Render(modStr)
|
|
|
|
|
} else {
|
|
|
|
|
modStr = cleanStyle.Render(modStr)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 11:39:19 +00:00
|
|
|
untrackedStr := cli.Sprintf("%d", s.Untracked)
|
2026-01-27 21:08:51 +00:00
|
|
|
if s.Untracked > 0 {
|
|
|
|
|
untrackedStr = dirtyStyle.Render(untrackedStr)
|
|
|
|
|
} else {
|
|
|
|
|
untrackedStr = cleanStyle.Render(untrackedStr)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 11:39:19 +00:00
|
|
|
stagedStr := cli.Sprintf("%d", s.Staged)
|
2026-01-27 21:08:51 +00:00
|
|
|
if s.Staged > 0 {
|
|
|
|
|
stagedStr = aheadStyle.Render(stagedStr)
|
|
|
|
|
} else {
|
|
|
|
|
stagedStr = cleanStyle.Render(stagedStr)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 11:39:19 +00:00
|
|
|
aheadStr := cli.Sprintf("%d", s.Ahead)
|
2026-01-27 21:08:51 +00:00
|
|
|
if s.Ahead > 0 {
|
|
|
|
|
aheadStr = aheadStyle.Render(aheadStr)
|
|
|
|
|
} else {
|
|
|
|
|
aheadStr = cleanStyle.Render(aheadStr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pad name before styling to avoid ANSI code length issues
|
2026-01-31 11:39:19 +00:00
|
|
|
paddedName := cli.Sprintf("%-*s", nameWidth, s.Name)
|
|
|
|
|
cli.Print("%s %8s %9s %6s %5s\n",
|
2026-01-27 21:08:51 +00:00
|
|
|
repoNameStyle.Render(paddedName),
|
|
|
|
|
modStr,
|
|
|
|
|
untrackedStr,
|
|
|
|
|
stagedStr,
|
|
|
|
|
aheadStr,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
// claudeCommit shells out to claude for committing (legacy helper for other commands)
|
2026-01-27 21:08:51 +00:00
|
|
|
func claudeCommit(ctx context.Context, repoPath, repoName, registryPath string) error {
|
2026-01-30 09:02:16 +00:00
|
|
|
prompt := agentic.Prompt("commit")
|
2026-01-27 21:08:51 +00:00
|
|
|
|
|
|
|
|
cmd := exec.CommandContext(ctx, "claude", "-p", prompt, "--allowedTools", "Bash,Read,Glob,Grep")
|
|
|
|
|
cmd.Dir = repoPath
|
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
|
|
|
|
|
|
return cmd.Run()
|
|
|
|
|
}
|
2026-01-30 09:02:16 +00:00
|
|
|
|
2026-01-30 10:18:54 +00:00
|
|
|
// claudeEditCommit shells out to claude with edit permissions (legacy helper)
|
2026-01-30 09:02:16 +00:00
|
|
|
func claudeEditCommit(ctx context.Context, repoPath, repoName, registryPath string) error {
|
|
|
|
|
prompt := agentic.Prompt("commit")
|
|
|
|
|
|
|
|
|
|
cmd := exec.CommandContext(ctx, "claude", "-p", prompt, "--allowedTools", "Bash,Read,Write,Edit,Glob,Grep")
|
|
|
|
|
cmd.Dir = repoPath
|
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
|
|
|
|
|
|
return cmd.Run()
|
|
|
|
|
}
|