Skip to content
Draft
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
Empty file added .jules/bolt.md
Empty file.
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 18 March 2026**
12 changes: 6 additions & 6 deletions internal/cli/crypto/salt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ func TestRunGenerateSalt(t *testing.T) {

// Read captured output
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
output := buf.String()
_, _ = buf.ReadFrom(r)
output := buf.String()

if tt.expectSkip {
assert.Empty(t, strings.TrimSpace(output))
return
}
if tt.expectSkip {
assert.Empty(t, strings.TrimSpace(output))
return
}

// Verify
lines := strings.Split(strings.TrimSpace(output), "\n")
Expand Down
8 changes: 6 additions & 2 deletions internal/cli/docker/compose/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,15 @@ func runCompose(ctx context.Context, autoAccept bool) {

func findDockerfiles() []string {
var dockerfiles []string
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
// ⚑ Bolt: Replaced filepath.Walk with filepath.WalkDir.
// filepath.WalkDir is more efficient as it avoids calling os.Lstat on every file
// or directory it visits, which significantly speeds up directory traversal,
// especially for large project structures.
filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
if err != nil {
return nil
}
if !info.IsDir() && strings.HasSuffix(info.Name(), "Dockerfile") {
if !d.IsDir() && strings.HasSuffix(d.Name(), "Dockerfile") {
// Use relative path
relPath, _ := filepath.Rel(".", path)
dockerfiles = append(dockerfiles, relPath)
Expand Down
86 changes: 42 additions & 44 deletions internal/cli/project/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ Return the result in strictly valid JSON format matching this schema:
return &result, nil
}


// ValidatorAgent validates and corrects the AnalysisResult.
type ValidatorAgent struct {
runtime *shared.RuntimeContext
Expand All @@ -116,30 +115,30 @@ func NewValidatorAgent(runtime *shared.RuntimeContext) *ValidatorAgent {

// Validate checks the analysis result for common issues and attempts to fix them via LLM.
func (v *ValidatorAgent) Validate(result *AnalysisResult) (*AnalysisResult, error) {
// 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" {
if _, ok := step.Parameters["command"]; !ok {
needsFix = true
issues = append(issues, fmt.Sprintf("Action '%s' Step %d ('%s'): Missing 'command' parameter in run_command.", action.Name, i+1, step.Instruction))
}
}
}
}

if !needsFix {
return result, nil
}
// 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" {
if _, ok := step.Parameters["command"]; !ok {
needsFix = true
issues = append(issues, fmt.Sprintf("Action '%s' Step %d ('%s'): Missing 'command' parameter in run_command.", action.Name, i+1, step.Instruction))
}
}
}
}

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

systemPrompt := `You are a Strict Configuration Validator.
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.
If the "instruction" contains the command, move it to "parameters.command" and keep "instruction" as a description.
Expand All @@ -149,7 +148,7 @@ Reported Issues:

Return the CORRECTED JSON strictly matching the input schema.`

userPrompt := string(resultJSON)
userPrompt := string(resultJSON)

service, err := llm.NewServiceBuilder(v.runtime).UseHeavyModel().Build()
if err != nil {
Expand Down Expand Up @@ -238,7 +237,7 @@ Schema:

var plan FileGenerationPlan
if err := json.Unmarshal([]byte(resp), &plan); err != nil {
// Fallback: try to find JSON block if strict JSON failed
// Fallback: try to find JSON block if strict JSON failed
return nil, fmt.Errorf("failed to parse planning response: %w (%s)", err, resp)
}

Expand Down Expand Up @@ -275,11 +274,11 @@ If it is a go file, include the package declaration.`, architecture, projectType
return nil, err
}

// Strip markdown code blocks if present
content := strings.TrimSpace(resp)
content = strings.TrimPrefix(content, "```go")
content = strings.TrimPrefix(content, "```")
content = strings.TrimSuffix(content, "```")
// Strip markdown code blocks if present
content := strings.TrimSpace(resp)
content = strings.TrimPrefix(content, "```go")
content = strings.TrimPrefix(content, "```")
content = strings.TrimSuffix(content, "```")

return &FileContent{
Path: file.Path,
Expand Down Expand Up @@ -342,17 +341,17 @@ 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.
// 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
}
// 1. Get File Tree
// 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
}

// 2. Build Prompt
systemPrompt := `You are an expert Software Architect.
Expand Down Expand Up @@ -389,7 +388,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 +416,3 @@ func getFileTree(root string) (string, error) {
})
return sb.String(), err
}

6 changes: 3 additions & 3 deletions internal/cli/project/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ func NewCheckCmd() *cobra.Command {
return &cobra.Command{
Use: "check",
Short: "Check compliance with project rules",
Long: `Verifies if the current project structure complies with the rules defined in AGENTS.md.`,
RunE: func(cmd *cobra.Command, args []string) error {
Long: `Verifies if the current project structure complies with the rules defined in AGENTS.md.`,
RunE: func(cmd *cobra.Command, args []string) error {
cwd, err := os.Getwd()
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
2 changes: 1 addition & 1 deletion internal/cli/project/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Examples:
cmd.AddCommand(NewCheckCmd())
cmd.AddCommand(NewUpdateCmd())
cmd.AddCommand(NewRedoCmd())
cmd.AddCommand(NewListCmd())
cmd.AddCommand(NewListCmd())

return cmd
}
6 changes: 3 additions & 3 deletions internal/cli/project/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ func TestNewProjectCmd(t *testing.T) {

// Check for expected subcommands
expectedParams := []string{"init", "exec", "check", "update", "redo", "list"}
foundCount := 0
foundCount := 0
for _, sub := range cmd.Commands() {
for _, expected := range expectedParams {
if sub.Name() == expected {
foundCount++
break
break
}
}
}
assert.Equal(t, len(expectedParams), foundCount, "Not all expected subcommands found")
assert.Equal(t, len(expectedParams), foundCount, "Not all expected subcommands found")
}
Loading
Loading