Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/internal-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ jobs:
if [[ -n $(git status -s) ]]; then
git add .
git commit -m "docs: update docs with PTerm-CI"
git push origin HEAD:${GITHUB_REF}
if [ "${{ github.event_name }}" == "pull_request" ]; then
git push origin HEAD:${{ github.head_ref }}
else
git push origin HEAD:${GITHUB_REF}
fi
else
echo "No changes to commit"
fi
2 changes: 1 addition & 1 deletion docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1043,4 +1043,4 @@ Run 'magi version --help' for more information on a specific command.


---
> **Documentation automatically generated with [PTerm](https://github.com/pterm/cli-template) on 06 February 2026**
> **Documentation automatically generated with [PTerm](https://github.com/pterm/cli-template) on 11 March 2026**
33 changes: 17 additions & 16 deletions internal/cli/i18n/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ type I18nKey struct {

// Agent Implementations

// extractPatterns are pre-compiled regular expressions for extracting i18n keys.
// Performance note: Hoisted to a package-level variable to avoid compiling these expressions
// redundantly on each KeyExtractor.Execute invocation.
var extractPatterns = []*regexp.Regexp{
// t('key') or t("key")
regexp.MustCompile(`(?:^|[^a-zA-Z0-9_])t\((?:'([^']+)'|"([^"]+)")\)`),
// i18n.t('key') or i18n.t("key")
regexp.MustCompile(`i18n\.t\((?:'([^']+)'|"([^"]+)")\)`),
// $t('key') or $t("key")
regexp.MustCompile(`\$t\((?:'([^']+)'|"([^"]+)")\)`),
// <T key="key" />
regexp.MustCompile(`<T[^>]+key=(?:'([^']+)'|"([^"]+)")`),
// <T keyName="key" />
regexp.MustCompile(`<T[^>]+keyName=(?:'([^']+)'|"([^"]+)")`),
}

// KeyExtractor Agent
type KeyExtractor struct {
diff string
Expand All @@ -47,21 +63,6 @@ func (a *KeyExtractor) Execute(input map[string]string) (string, error) {
var keys []I18nKey
lines := strings.Split(a.diff, "\n")

// Regex patterns for different i18n usage
// We use two capturing groups: one for single quotes, one for double quotes
patterns := []*regexp.Regexp{
// t('key') or t("key")
regexp.MustCompile(`(?:^|[^a-zA-Z0-9_])t\((?:'([^']+)'|"([^"]+)")\)`),
// i18n.t('key') or i18n.t("key")
regexp.MustCompile(`i18n\.t\((?:'([^']+)'|"([^"]+)")\)`),
// $t('key') or $t("key")
regexp.MustCompile(`\$t\((?:'([^']+)'|"([^"]+)")\)`),
// <T key="key" />
regexp.MustCompile(`<T[^>]+key=(?:'([^']+)'|"([^"]+)")`),
// <T keyName="key" />
regexp.MustCompile(`<T[^>]+keyName=(?:'([^']+)'|"([^"]+)")`),
}

for _, line := range lines {
// We only care about added lines
if !strings.HasPrefix(line, "+") {
Expand All @@ -71,7 +72,7 @@ func (a *KeyExtractor) Execute(input map[string]string) (string, error) {
// Remove the "+" prefix
content := line[1:]

for _, pattern := range patterns {
for _, pattern := range extractPatterns {
matches := pattern.FindAllStringSubmatch(content, -1)
for _, match := range matches {
// match[0] is full match
Expand Down
13 changes: 6 additions & 7 deletions internal/cli/project/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (v *ValidatorAgent) Validate(result *AnalysisResult) (*AnalysisResult, erro
// 1. Check for invalid steps programmaticall first to save tokens
needsFix := false
var issues []string

for _, action := range result.Actions {
for i, step := range action.Steps {
if step.Tool == "run_command" {
Expand All @@ -130,15 +130,15 @@ func (v *ValidatorAgent) Validate(result *AnalysisResult) (*AnalysisResult, erro
}
}
}

if !needsFix {
return result, nil
}

// 2. Fix via LLM
resultJSON, _ := json.Marshal(result)
issuesStr := strings.Join(issues, "\n")

systemPrompt := `You are a Strict Configuration Validator.
Your task is to FIX the provided Project Analysis JSON based on the reported validity issues.
Verify that all "run_command" steps have a "command" parameter with the actual executable shell command.
Expand Down Expand Up @@ -343,12 +343,12 @@ func NewReviewerAgent(runtime *shared.RuntimeContext) *ReviewerAgent {
// ReviewCompliance checks if the project structure matches the rules.
func (r *ReviewerAgent) ReviewCompliance(rootPath string, rulesContent string) (string, error) {
// 1. Get File Tree
// We reuse a similar file tree function or extract it to a helper.
// We reuse a similar file tree function or extract it to a helper.
// Since getFileTree is a method of ArchitectureAgent, let's copy or refactor.
// For simplicity I'll duplicate the walker logic here or make it a private function in agents.go
// assuming I can access it if I make it a function not method, or just duplicate.
// Let's refactor getFileTree to be a standalone function "getFileTree" in this package.

fileTree, err := getFileTree(rootPath)
if err != nil {
return "", err
Expand Down Expand Up @@ -389,7 +389,7 @@ Format the output as a bulleted list of issues.`
return resp, nil
}

// Helper function to be shared.
// Helper function to be shared.
// I need to change ArchitectureAgent.getFileTree to use this or be this.
func getFileTree(root string) (string, error) {
var sb strings.Builder
Expand Down Expand Up @@ -417,4 +417,3 @@ func getFileTree(root string) (string, error) {
})
return sb.String(), err
}

2 changes: 1 addition & 1 deletion internal/cli/project/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func NewCheckCmd() *cobra.Command {
if err != nil {
return fmt.Errorf("failed to get cwd: %w", err)
}

// 1. Find Rules file
configPath := filepath.Join(cwd, ".magi.yaml")
var rulesFile string = "AGENTS.md" // Default
Expand Down
16 changes: 8 additions & 8 deletions internal/cli/project/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func NewExecCmd() *cobra.Command {
pterm.Warning.Println(".magi.yaml not found. Run 'magi project init' first.")
return nil
}

data, err := os.ReadFile(configPath)
if err != nil {
return fmt.Errorf("failed to read config: %w", err)
Expand Down Expand Up @@ -81,7 +81,7 @@ func NewExecCmd() *cobra.Command {
return err
}
agent := NewGeneratorAgent(runtime)

architecture := config.Architecture
if architecture == "" { architecture = "Go Project" }
projectType := config.ProjectType
Expand All @@ -96,7 +96,7 @@ func NewExecCmd() *cobra.Command {
pterm.Success.Println("Action completed successfully!")
return nil
}

// Fallback to legacy Plan/Geneate
pterm.Info.Println("No steps defined. specific steps. Fallback to auto-planning...")

Expand All @@ -112,7 +112,7 @@ func NewExecCmd() *cobra.Command {
for _, f := range plan.Files {
pterm.Println(pterm.Green(" + ") + f.Path + pterm.Gray(" ("+f.Description+")"))
}

confirm, _ := pterm.DefaultInteractiveConfirm.Show("Proceed with generation?")
if !confirm {
pterm.Info.Println("Aborted.")
Expand All @@ -126,15 +126,15 @@ func NewExecCmd() *cobra.Command {
content, err := agent.GenerateContent(cwd, architecture, projectType, *selectedAction, params, f)
if err != nil {
pterm.Error.Printf("Failed to generate %s: %v\n", f.Path, err)
continue
continue
}

fullPath := filepath.Join(cwd, f.Path)
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
pterm.Error.Printf("Failed to create dir for %s: %v\n", f.Path, err)
continue
}

// Check overwrite
if _, err := os.Stat(fullPath); err == nil {
// In non-interactive mode or simply proceed for now as user confirmed plan.
Expand All @@ -152,5 +152,5 @@ func NewExecCmd() *cobra.Command {
return nil
},
}
return cmd
return cmd
}
6 changes: 3 additions & 3 deletions internal/cli/project/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (e *Executor) handleCreateFile(step ActionStep) error {
// handleEditFile handles editing existing files.
func (e *Executor) handleEditFile(step ActionStep) error {
targetFile := e.resolveVariable(step.Parameters["target"])

// If target is missing, try to resolve it via LLM or interactive prompt
if targetFile == "" {
// Use instruction to hint at the file. For now, interactive fallback.
Expand Down Expand Up @@ -142,7 +142,7 @@ func (e *Executor) handleRunCommand(step ActionStep) error {
}

pterm.Info.Printf("Command: %s\n", cmdStr)

if confirm, _ := pterm.DefaultInteractiveConfirm.WithDefaultValue(false).Show("Run this command?"); !confirm {
pterm.Info.Println("Skipped command execution.")
return nil
Expand All @@ -153,7 +153,7 @@ func (e *Executor) handleRunCommand(step ActionStep) error {
if len(parts) == 0 {
return nil
}

cmd := exec.Command(parts[0], parts[1:]...)
cmd.Dir = e.Cwd
cmd.Stdout = os.Stdout
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/project/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func RunAnalysisAndConfig(createRules bool, forceRules bool) error {
analysis, err = validAgent.Validate(analysis)
if err != nil {
spinnerVal.Warning("Validation incomplete: " + err.Error())
// Continue with original analysis instead of failing hard?
// Continue with original analysis instead of failing hard?
// Or fail? Let's log warning and proceed with potentially flawed analysis or original.
} else {
spinnerVal.Success("Validation complete!")
Expand Down Expand Up @@ -122,7 +122,7 @@ func RunAnalysisAndConfig(createRules bool, forceRules bool) error {
// 5. Create AGENTS.md if missing
if createRules || forceRules {
rulesPath := filepath.Join(cwd, config.RulesPath)

// Check existence
rulesExist := false
if _, err := os.Stat(rulesPath); err == nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/project/redo.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Updates .magi.yaml with findings.`,
return nil
}

// Reuse init logic
// Reuse init logic
return RunAnalysisAndConfig(false, forceRules)
},
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/project/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Requires the file path as an argument.`,
// TODO: In the future, show a helper diff here? For now, we rely on user trust/git.
pterm.Info.Printf("File: %s\n", updatedFile.Path)
pterm.Info.Println("Content Length:", len(updatedFile.Content))

confirm, _ := pterm.DefaultInteractiveConfirm.Show("Apply changes?")
if confirm {
if err := os.WriteFile(fullPath, []byte(updatedFile.Content), 0644); err != nil {
Expand Down
5 changes: 3 additions & 2 deletions internal/cli/ssh/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (
"github.com/spf13/viper"
)

// aliasRegex is pre-compiled to avoid redundantly recompiling the regex on each loop iteration
var aliasRegex = regexp.MustCompile("^[a-zA-Z0-9_-]+$")

func addCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Expand Down Expand Up @@ -79,8 +82,6 @@ func promptForAlias() (string, error) {

existing := viper.GetStringMap(ConfigSSHConnections)

aliasRegex := regexp.MustCompile("^[a-zA-Z0-9_-]+$")

for {
alias, err = pterm.DefaultInteractiveTextInput.WithDefaultText("Connection Alias").Show()
if err != nil {
Expand Down
7 changes: 4 additions & 3 deletions pkg/utils/parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package utils

import "regexp"

// codeBlockRegex is pre-compiled to optimize RemoveCodeBlock which is frequently called.
var codeBlockRegex = regexp.MustCompile(`(\` + "`" + "`" + "`" + `[\w-]*)\n([\s\S]*)(\` + "`" + "`" + "`" + `)`)

// RemoveCodeBlock removes code block tags from a string.
// If no code block tags are found, it returns the original string.
func RemoveCodeBlock(input string) string {
re := regexp.MustCompile(`(\` + "`" + "`" + "`" + `[\w-]*)\n([\s\S]*)(\` + "`" + "`" + "`" + `)`)

matches := re.FindStringSubmatch(input)
matches := codeBlockRegex.FindStringSubmatch(input)
if len(matches) == 0 {
return input
}
Expand Down
Loading