Skip to content

dev#204

Merged
recepgunes1 merged 3 commits into
mainfrom
dev
May 17, 2026
Merged

dev#204
recepgunes1 merged 3 commits into
mainfrom
dev

Conversation

@recepgunes1
Copy link
Copy Markdown
Member

@recepgunes1 recepgunes1 commented May 17, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added playbook run, playbook stop, and playbook list CLI commands to manage playbooks
    • Extended inspect command to support viewing both templates and playbooks
  • Documentation

    • Expanded README with playbook documentation, usage examples, and command reference tables
    • Updated Go installation prerequisite to version 1.25.6+

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

Walkthrough

This PR introduces playbooks as a composable feature for orchestrating sequences of templates. It adds Playbook types with validation, implements repository-level loading for both templates and playbooks, wires them into the application dependency graph, and provides CLI commands to run, stop, and list playbooks alongside supporting documentation updates.

Changes

Playbook Feature Implementation

Layer / File(s) Summary
Playbook Data Model and Validation
pkg/template/playbook.go, pkg/template/playbook_test.go
Introduces Playbook and PlaybookInfo structs with YAML tags, ID validation (format vt-pb-<number>), and public methods to query, format, and validate playbooks. Tests verify validation rules, ID/filename matching, and duplicate detection.
Playbook File Parsing
pkg/template/parser.go, pkg/template/parser_test.go
Adds loadPlaybookFromFile to read and parse playbook YAML with error wrapping. Refactors LoadTemplate to unexported loadTemplate and streamlines its error handling.
Repository Loading and Scanning
pkg/template/loader.go, pkg/template/loader_test.go
Implements LoadRepo to scan the repository filesystem, load playbooks from a playbooks/ directory, and load templates from category directories with recursion limits and duplicate detection. Includes SyncTemplates for repository updates and GetDockerComposePath to resolve template compose paths. Tests validate nested template loading, hidden-directory skipping, depth enforcement, and playbook integration.
Application Dependency Injection
internal/app/app.go, cmd/vt/main.go
Extends App struct with Playbooks field and updates NewApp constructor to accept playbooks. Updates main to load both templates and playbooks via LoadRepo and pass them to the app.
Playbook CLI Commands
internal/cli/playbook.go, internal/cli/cli.go
Implements newPlaybookCommand with three subcommands: run starts templates in sequence and aggregates failures; stop stops templates and reports results; list displays all playbooks. Registers playbook command in root CLI.
CLI Command Integration
internal/cli/inspect.go, internal/cli/template.go
Updates inspect to look up IDs as either templates or playbooks. Modifies template --update to reload both templates and playbooks via LoadRepo and update both in the app.
Template Code Refactoring and Documentation
pkg/template/template.go, pkg/template/template_test.go, README.md
Reorganizes template.go helpers and refactors ListTemplatesWithFilter to compute table cells inline. Simplifies test fixtures in template_test.go. Expands README with playbooks section, command reference tables, installation Go version bump (1.25.6+), and updated usage examples.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • HappyHackingSpace/vt#161: Modifies the same app.NewApp constructor signature and cmd/vt/main.go wiring, though PR #161 removes stateManager while this PR adds playbooks.
  • HappyHackingSpace/vt#97: Introduces the foundational app and CLI structure that this PR extends by adding playbook dependency injection and CLI command registration.

Suggested reviewers

  • ahsentekd
  • dogancanbakir

🐰 Playbooks now orchestrate templates in perfect harmony,
Run and stop them by their ID with CLI symphony,
Failures skip ahead, no need to despair,
A summary reports what happened with care! 📋✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'dev' is vague and generic, providing no meaningful information about the changeset which implements playbook support with templates, loaders, and CLI commands. Rename the title to something descriptive like 'Add playbook support with CLI commands and template integration' or 'Implement playbook feature with run/stop/list commands'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
pkg/template/loader_test.go (1)

113-171: ⚡ Quick win

Add regressions for cross-category duplicates and symlink-file traversal.

Current tests don’t cover duplicate template IDs across different top-level categories, or the symlink-file case that can affect directory traversal. Adding both will protect loader behavior from regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/template/loader_test.go` around lines 113 - 171, Add two new unit tests
to pkg/template/loader_test.go to prevent regressions: one that creates
templates with the same ID in two different top-level categories and asserts
LoadRepo returns an error or deduplicates as expected (reference LoadRepo and
createTestTemplate to set up categories and templates), and another that creates
a symlink-to-file inside a category (or a symlinked directory) and verifies
LoadRepo does not follow the symlink into unintended files/directories
(reference createTestTemplate, playbooksDirName if needed, and LoadRepo); ensure
the tests assert the correct template count and presence/absence of duplicate or
symlinked entries to lock in desired behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/inspect.go`:
- Line 34: The flag description for the "id" flag (registered via
cmd.Flags().String("id", ... ) in internal/cli/inspect.go) is outdated — update
the help text to reflect that the command accepts both template and playbook IDs
(e.g., change "Specify a template ID for targeted vulnerable environment" to
something like "Specify a template or playbook ID for targeted vulnerable
environment") so users see the correct supported inputs.

In `@internal/cli/playbook.go`:
- Around line 77-81: The code currently logs partial failures for playbook
execution but still returns success; update the failure handling in the playbook
command so any non-empty failed slice causes a non-zero exit or returned error:
replace the log.Warn branch in the run/stop handlers (the blocks referencing
failed and pb.Info.Name) to log the error and then return an error (e.g.,
fmt.Errorf("playbook finished with errors: %v", failed) ) or call os.Exit(1)
depending on the function signature; apply the same change to both occurrences
(the blocks around failed, pb.Info.Name and the second block reported in the
comment) so CI/scripts see a non-zero exit on partial failures.

In `@pkg/template/loader.go`:
- Around line 58-60: The merge loop silently overwrites template IDs when
multiple categories contain the same id; update the loop that iterates over
categoryTemplates and writes into templates to first check if the id already
exists in templates and return (or propagate) an error instead of overwriting
it. Concretely, in the function containing the maps categoryTemplates and
templates, replace the blind assignment templates[id] = tmpl with a test like if
_, exists := templates[id]; exists { return fmt.Errorf("duplicate template id %q
found in category %q", id, categoryName) } and only assign when not present,
ensuring the duplicate detection references the categoryTemplates/ templates
variables and returns a clear error.
- Around line 120-127: The code currently allows absolute paths and relative
paths that can escape templateDir; update the loader logic that handles
providerConfig.Path (the composePath, templateDir check) to reject absolute
providerConfig.Path and to resolve the joined path using filepath.Clean and
filepath.Abs (or filepath.Rel) and then verify the resolved composePath is
inside templateDir (e.g. by computing rel, ensuring it does not start with ".."
and is not equal to ".." segments); if the path escapes or is absolute return a
descriptive error instead of returning the external path. Ensure you still stat
the validated composePath before returning.
- Around line 155-158: The symlink handling returns filepath.SkipDir
unconditionally when d.Type()&os.ModeSymlink != 0, which incorrectly skips
sibling entries; change the check so you only return filepath.SkipDir for
directory symlinks by combining the symlink test with d.IsDir() (i.e., if
d.Type()&os.ModeSymlink != 0 && d.IsDir() { return filepath.SkipDir }),
otherwise continue normally; make the identical change to the other
symlink-handling block that also uses d.Type()&os.ModeSymlink and
filepath.SkipDir.

In `@pkg/template/playbook.go`:
- Around line 129-143: The validation currently accepts whitespace-only
names/IDs; update the checks in the playbook validation (where pb.Info.Name and
loop over pb.Templates are validated) to trim whitespace (e.g.,
strings.TrimSpace) and treat empty-after-trim as invalid: reject pb.Info.Name if
strings.TrimSpace(pb.Info.Name) == "" and reject any template id if
strings.TrimSpace(id) == ""; continue using the same error messages but
reference the trimmed values when checking duplicates and storing into seen to
avoid treating " a " and "a" as different.

---

Nitpick comments:
In `@pkg/template/loader_test.go`:
- Around line 113-171: Add two new unit tests to pkg/template/loader_test.go to
prevent regressions: one that creates templates with the same ID in two
different top-level categories and asserts LoadRepo returns an error or
deduplicates as expected (reference LoadRepo and createTestTemplate to set up
categories and templates), and another that creates a symlink-to-file inside a
category (or a symlinked directory) and verifies LoadRepo does not follow the
symlink into unintended files/directories (reference createTestTemplate,
playbooksDirName if needed, and LoadRepo); ensure the tests assert the correct
template count and presence/absence of duplicate or symlinked entries to lock in
desired behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d45d9495-3ca9-473a-b828-25237b3ec7d0

📥 Commits

Reviewing files that changed from the base of the PR and between 6a7f789 and 65dd082.

📒 Files selected for processing (15)
  • README.md
  • cmd/vt/main.go
  • internal/app/app.go
  • internal/cli/cli.go
  • internal/cli/inspect.go
  • internal/cli/playbook.go
  • internal/cli/template.go
  • pkg/template/loader.go
  • pkg/template/loader_test.go
  • pkg/template/parser.go
  • pkg/template/parser_test.go
  • pkg/template/playbook.go
  • pkg/template/playbook_test.go
  • pkg/template/template.go
  • pkg/template/template_test.go

Comment thread internal/cli/inspect.go
log.Fatal().Msgf("'%s' not found as a template or playbook", templateID)
},
}
cmd.Flags().String("id", "", "Specify a template ID for targeted vulnerable environment")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update --id help text to match new playbook support.

The flag description still says template-only, but the command now accepts both template and playbook IDs.

Proposed fix
-	cmd.Flags().String("id", "", "Specify a template ID for targeted vulnerable environment")
+	cmd.Flags().String("id", "", "Specify a template or playbook ID for targeted vulnerable environment")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cmd.Flags().String("id", "", "Specify a template ID for targeted vulnerable environment")
cmd.Flags().String("id", "", "Specify a template or playbook ID for targeted vulnerable environment")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/inspect.go` at line 34, The flag description for the "id" flag
(registered via cmd.Flags().String("id", ... ) in internal/cli/inspect.go) is
outdated — update the help text to reflect that the command accepts both
template and playbook IDs (e.g., change "Specify a template ID for targeted
vulnerable environment" to something like "Specify a template or playbook ID for
targeted vulnerable environment") so users see the correct supported inputs.

Comment thread internal/cli/playbook.go
Comment on lines +77 to +81
if len(failed) > 0 {
log.Warn().Msgf("playbook finished with errors — failed templates: %s", strings.Join(failed, ", "))
} else {
log.Info().Msgf("playbook '%s' completed successfully", pb.Info.Name)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return non-zero exit when any playbook item fails.

Both run and stop currently report partial failures with warnings but still exit successfully. That masks operational failure in scripts and CI.

Proposed fix
 			if len(failed) > 0 {
-				log.Warn().Msgf("playbook finished with errors — failed templates: %s", strings.Join(failed, ", "))
+				log.Fatal().Msgf("playbook finished with errors — failed templates: %s", strings.Join(failed, ", "))
 			} else {
 				log.Info().Msgf("playbook '%s' completed successfully", pb.Info.Name)
 			}
 			if len(failed) > 0 {
-				log.Warn().Msgf("playbook stop finished with errors — failed templates: %s", strings.Join(failed, ", "))
+				log.Fatal().Msgf("playbook stop finished with errors — failed templates: %s", strings.Join(failed, ", "))
 			} else {
 				log.Info().Msgf("playbook '%s' stopped successfully", pb.Info.Name)
 			}

Also applies to: 140-144

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/playbook.go` around lines 77 - 81, The code currently logs
partial failures for playbook execution but still returns success; update the
failure handling in the playbook command so any non-empty failed slice causes a
non-zero exit or returned error: replace the log.Warn branch in the run/stop
handlers (the blocks referencing failed and pb.Info.Name) to log the error and
then return an error (e.g., fmt.Errorf("playbook finished with errors: %v",
failed) ) or call os.Exit(1) depending on the function signature; apply the same
change to both occurrences (the blocks around failed, pb.Info.Name and the
second block reported in the comment) so CI/scripts see a non-zero exit on
partial failures.

Comment thread pkg/template/loader.go
Comment on lines +58 to +60
for id, tmpl := range categoryTemplates {
templates[id] = tmpl
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent silent template ID overwrite across categories.

The current merge lets later categories overwrite an already-loaded template ID without error, which can make template resolution nondeterministic.

Proposed fix
 		for id, tmpl := range categoryTemplates {
+			if _, exists := templates[id]; exists {
+				return nil, nil, fmt.Errorf("duplicate template id '%s' found across categories", id)
+			}
 			templates[id] = tmpl
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for id, tmpl := range categoryTemplates {
templates[id] = tmpl
}
for id, tmpl := range categoryTemplates {
if _, exists := templates[id]; exists {
return nil, nil, fmt.Errorf("duplicate template id '%s' found across categories", id)
}
templates[id] = tmpl
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/template/loader.go` around lines 58 - 60, The merge loop silently
overwrites template IDs when multiple categories contain the same id; update the
loop that iterates over categoryTemplates and writes into templates to first
check if the id already exists in templates and return (or propagate) an error
instead of overwriting it. Concretely, in the function containing the maps
categoryTemplates and templates, replace the blind assignment templates[id] =
tmpl with a test like if _, exists := templates[id]; exists { return
fmt.Errorf("duplicate template id %q found in category %q", id, categoryName) }
and only assign when not present, ensuring the duplicate detection references
the categoryTemplates/ templates variables and returns a clear error.

Comment thread pkg/template/loader.go
Comment on lines +120 to +127
if filepath.IsAbs(providerConfig.Path) {
return providerConfig.Path, filepath.Dir(providerConfig.Path), nil
}

composePath = filepath.Join(templateDir, providerConfig.Path)
if _, statErr := os.Stat(composePath); statErr != nil {
return "", "", fmt.Errorf("template %q has invalid docker-compose path %q: %w", templateID, composePath, statErr)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block docker-compose.path from escaping template directory.

Relative paths like ../../... can resolve outside templateDir, and absolute paths bypass repository boundaries entirely. This allows template metadata to point at arbitrary filesystem locations.

Proposed fix
-		if filepath.IsAbs(providerConfig.Path) {
-			return providerConfig.Path, filepath.Dir(providerConfig.Path), nil
-		}
-
-		composePath = filepath.Join(templateDir, providerConfig.Path)
+		if filepath.IsAbs(providerConfig.Path) {
+			return "", "", fmt.Errorf("template %q docker-compose.path must be relative", templateID)
+		}
+
+		composePath = filepath.Clean(filepath.Join(templateDir, providerConfig.Path))
+		rel, relErr := filepath.Rel(templateDir, composePath)
+		if relErr != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
+			return "", "", fmt.Errorf("template %q docker-compose.path escapes template directory", templateID)
+		}
 		if _, statErr := os.Stat(composePath); statErr != nil {
 			return "", "", fmt.Errorf("template %q has invalid docker-compose path %q: %w", templateID, composePath, statErr)
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if filepath.IsAbs(providerConfig.Path) {
return providerConfig.Path, filepath.Dir(providerConfig.Path), nil
}
composePath = filepath.Join(templateDir, providerConfig.Path)
if _, statErr := os.Stat(composePath); statErr != nil {
return "", "", fmt.Errorf("template %q has invalid docker-compose path %q: %w", templateID, composePath, statErr)
}
if filepath.IsAbs(providerConfig.Path) {
return "", "", fmt.Errorf("template %q docker-compose.path must be relative", templateID)
}
composePath = filepath.Clean(filepath.Join(templateDir, providerConfig.Path))
rel, relErr := filepath.Rel(templateDir, composePath)
if relErr != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return "", "", fmt.Errorf("template %q docker-compose.path escapes template directory", templateID)
}
if _, statErr := os.Stat(composePath); statErr != nil {
return "", "", fmt.Errorf("template %q has invalid docker-compose path %q: %w", templateID, composePath, statErr)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/template/loader.go` around lines 120 - 127, The code currently allows
absolute paths and relative paths that can escape templateDir; update the loader
logic that handles providerConfig.Path (the composePath, templateDir check) to
reject absolute providerConfig.Path and to resolve the joined path using
filepath.Clean and filepath.Abs (or filepath.Rel) and then verify the resolved
composePath is inside templateDir (e.g. by computing rel, ensuring it does not
start with ".." and is not equal to ".." segments); if the path escapes or is
absolute return a descriptive error instead of returning the external path.
Ensure you still stat the validated composePath before returning.

Comment thread pkg/template/loader.go
Comment on lines +155 to +158
if d.Type()&os.ModeSymlink != 0 {
log.Debug().Msgf("skipping symlink: %s", d.Name())
return filepath.SkipDir
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify both symlink branches currently return SkipDir unconditionally.
rg -n -C2 'd\.Type\(\)&os\.ModeSymlink != 0' pkg/template/loader.go

Repository: HappyHackingSpace/vt

Length of output: 301


🏁 Script executed:

# Get the full context of the functions containing lines 155-157 and 222-224
head -n 160 pkg/template/loader.go | tail -n 20

Repository: HappyHackingSpace/vt

Length of output: 380


🏁 Script executed:

# Also check the function signature and surrounding context
sed -n '140,170p' pkg/template/loader.go

Repository: HappyHackingSpace/vt

Length of output: 718


🏁 Script executed:

# Check the second occurrence around line 222
sed -n '210,235p' pkg/template/loader.go

Repository: HappyHackingSpace/vt

Length of output: 599


🏁 Script executed:

# Find the function names to understand context
rg -B 15 'd\.Type\(\)&os\.ModeSymlink' pkg/template/loader.go | head -n 50

Repository: HappyHackingSpace/vt

Length of output: 704


🌐 Web query:

Go filepath.SkipDir symlink files behavior WalkDir documentation

💡 Result:

In Go, the filepath.WalkDir function is documented as not following symbolic links [1][2][3]. This means that when the walker encounters a symbolic link, it reports the link itself as a file or directory entry rather than traversing into the target of the link [1][2]. There is one notable exception: if the root path provided to WalkDir is itself a symbolic link, the function will follow that link to its target and begin the walk from there [1]. Regarding the use of filepath.SkipDir: 1. When returned by the WalkDirFunc for a directory, SkipDir instructs the walker to skip the contents of that directory entirely [1]. 2. When returned for a non-directory file, SkipDir skips the remaining files in the current directory [4]. It is important to note that while WalkDir does not follow symlinks, it is still susceptible to time-of-check/time-of-use (TOCTOU) race conditions [5]. If a portion of the path being walked is replaced with a symbolic link while the walk is in progress, the behavior may be unpredictable [5]. For security-sensitive applications requiring protection against symlink traversal, developers are advised to use traversal-resistant APIs (such as those that operate on file descriptors) rather than relying on path-based walking functions [5]. Additionally, historical issues have been reported regarding platform-specific behavior, such as on Windows, where directory symlinks or junctions might sometimes be traversed [6][7]. However, the standard library documentation maintains that the function does not follow symbolic links [2][3].

Citations:


Only return SkipDir for directory symlinks.

Returning filepath.SkipDir for non-directory symlink entries skips remaining siblings in the current directory and causes templates to be missed during scans. The dotfile handling in this function already demonstrates the correct pattern: check d.IsDir() before returning SkipDir.

Proposed fix
 		if d.Type()&os.ModeSymlink != 0 {
 			log.Debug().Msgf("skipping symlink: %s", d.Name())
-			return filepath.SkipDir
+			if d.IsDir() {
+				return filepath.SkipDir
+			}
+			return nil
 		}
 		if d.Type()&os.ModeSymlink != 0 {
-			return filepath.SkipDir
+			if d.IsDir() {
+				return filepath.SkipDir
+			}
+			return nil
 		}

Also applies to: 222-224

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/template/loader.go` around lines 155 - 158, The symlink handling returns
filepath.SkipDir unconditionally when d.Type()&os.ModeSymlink != 0, which
incorrectly skips sibling entries; change the check so you only return
filepath.SkipDir for directory symlinks by combining the symlink test with
d.IsDir() (i.e., if d.Type()&os.ModeSymlink != 0 && d.IsDir() { return
filepath.SkipDir }), otherwise continue normally; make the identical change to
the other symlink-handling block that also uses d.Type()&os.ModeSymlink and
filepath.SkipDir.

Comment thread pkg/template/playbook.go
Comment on lines +129 to +143
if pb.Info.Name == "" {
return fmt.Errorf("playbook '%s': info.name cannot be empty", pb.ID)
}
if len(pb.Templates) == 0 {
return fmt.Errorf("playbook '%s': templates list cannot be empty", pb.ID)
}
seen := make(map[string]bool, len(pb.Templates))
for _, id := range pb.Templates {
if id == "" {
return fmt.Errorf("playbook '%s': template id cannot be empty", pb.ID)
}
if seen[id] {
return fmt.Errorf("playbook '%s': duplicate template id '%s'", pb.ID, id)
}
seen[id] = true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject whitespace-only info.name and template IDs in validation.

Current checks allow values like " ", which pass validation but fail later during lookup/use.

🔧 Proposed fix
 func (pb Playbook) Validate() error {
@@
-	if pb.Info.Name == "" {
+	if strings.TrimSpace(pb.Info.Name) == "" {
 		return fmt.Errorf("playbook '%s': info.name cannot be empty", pb.ID)
 	}
@@
-	seen := make(map[string]bool, len(pb.Templates))
-	for _, id := range pb.Templates {
-		if id == "" {
+	seen := make(map[string]bool, len(pb.Templates))
+	for _, rawID := range pb.Templates {
+		id := strings.TrimSpace(rawID)
+		if id == "" {
 			return fmt.Errorf("playbook '%s': template id cannot be empty", pb.ID)
 		}
 		if seen[id] {
 			return fmt.Errorf("playbook '%s': duplicate template id '%s'", pb.ID, id)
 		}
 		seen[id] = true
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if pb.Info.Name == "" {
return fmt.Errorf("playbook '%s': info.name cannot be empty", pb.ID)
}
if len(pb.Templates) == 0 {
return fmt.Errorf("playbook '%s': templates list cannot be empty", pb.ID)
}
seen := make(map[string]bool, len(pb.Templates))
for _, id := range pb.Templates {
if id == "" {
return fmt.Errorf("playbook '%s': template id cannot be empty", pb.ID)
}
if seen[id] {
return fmt.Errorf("playbook '%s': duplicate template id '%s'", pb.ID, id)
}
seen[id] = true
if strings.TrimSpace(pb.Info.Name) == "" {
return fmt.Errorf("playbook '%s': info.name cannot be empty", pb.ID)
}
if len(pb.Templates) == 0 {
return fmt.Errorf("playbook '%s': templates list cannot be empty", pb.ID)
}
seen := make(map[string]bool, len(pb.Templates))
for _, rawID := range pb.Templates {
id := strings.TrimSpace(rawID)
if id == "" {
return fmt.Errorf("playbook '%s': template id cannot be empty", pb.ID)
}
if seen[id] {
return fmt.Errorf("playbook '%s': duplicate template id '%s'", pb.ID, id)
}
seen[id] = true
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/template/playbook.go` around lines 129 - 143, The validation currently
accepts whitespace-only names/IDs; update the checks in the playbook validation
(where pb.Info.Name and loop over pb.Templates are validated) to trim whitespace
(e.g., strings.TrimSpace) and treat empty-after-trim as invalid: reject
pb.Info.Name if strings.TrimSpace(pb.Info.Name) == "" and reject any template id
if strings.TrimSpace(id) == ""; continue using the same error messages but
reference the trimmed values when checking duplicates and storing into seen to
avoid treating " a " and "a" as different.

@recepgunes1 recepgunes1 merged commit 66dbc40 into main May 17, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant