Skip to content
Open
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
20 changes: 20 additions & 0 deletions pkg/lib/kitfile/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ var datasetSuffixes = []string{
".tar", ".zip", ".parquet", ".csv",
}

// Files that are considered prompt or agent files based on their names
var promptFilePatterns = []string{
"AGENTS.md",
"SKILL.md",
"CLAUDE.md",
}
Comment on lines +70 to +74
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The variable name promptFilePatterns is misleading since it contains agent-related files (AGENTS.md, SKILL.md, CLAUDE.md) that aren't necessarily prompt files. Consider renaming to something more inclusive like agentAndPromptFilePatterns or codeFilePatterns to better reflect its purpose of identifying files that should be treated as code.

Copilot uses AI. Check for mistakes.

// Generate a basic Kitfile by looking at the contents of a directory. Parameter
// packageOpt can be used to define metadata for the Kitfile (i.e. the package
// section), which is left empty if the parameter is nil.
Expand Down Expand Up @@ -270,6 +277,19 @@ func addDirToKitfile(kitfile *artifact.KitFile, dir DirectoryListing) (modelFile
}

func determineFileType(filename string) fileType {
// Check for exact agent and prompt file matches
baseName := strings.ToLower(filepath.Base(filename))
for _, pattern := range promptFilePatterns {
if baseName == strings.ToLower(pattern) {
Comment on lines +282 to +283
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Consider converting promptFilePatterns to lowercase once at initialization rather than calling strings.ToLower(pattern) on every iteration. This would avoid repeated string conversions. For example, you could create a var promptFilePatternsLower []string at package level that contains the lowercase versions, or directly store them as lowercase in the original slice.

Suggested change
for _, pattern := range promptFilePatterns {
if baseName == strings.ToLower(pattern) {
for _, pattern := range promptFilePatternsLower {
if baseName == pattern {

Copilot uses AI. Check for mistakes.
return fileTypeCode
}
}

// Check for .prompt pattern (substring match)
if strings.Contains(baseName, ".prompt") {
Comment on lines +288 to +289
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The substring check strings.Contains(baseName, ".prompt") may match unintended files. For example, files like "system.prompts.md" or "my.prompter.config" would also be matched because they contain ".prompt" as a substring. Consider using a more specific pattern, such as checking if the basename ends with ".prompt" or contains ".prompt." (with a trailing dot), or use strings.HasPrefix after splitting on dots to ensure ".prompt" is a complete segment.

Suggested change
// Check for .prompt pattern (substring match)
if strings.Contains(baseName, ".prompt") {
// Check for .prompt as a complete segment or suffix
if strings.HasSuffix(baseName, ".prompt") || strings.Contains(baseName, ".prompt.") {

Copilot uses AI. Check for mistakes.
return fileTypeCode
}

if anySuffix(filename, modelWeightsSuffixes) {
return fileTypeModel
}
Expand Down
133 changes: 133 additions & 0 deletions pkg/lib/kitfile/generate/generate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2024 The KitOps Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

package generate

import (
"testing"
)

func TestDetermineFileType(t *testing.T) {
tests := []struct {
name string
filename string
expectedType fileType
}{
// Prompt files - should be recognized as code
{
name: "prompt file without extension",
filename: "system.prompt",
expectedType: fileTypeCode,
},
{
name: "prompt file with .md extension",
filename: "chain.prompt.md",
expectedType: fileTypeCode,
},
{
name: "prompt file with .yaml extension",
filename: "my.prompt.yaml",
expectedType: fileTypeCode,
},
{
name: "prompt file with .txt extension",
filename: "instruction.prompt.txt",
expectedType: fileTypeCode,
},
{
name: "prompt file in subdirectory",
filename: "prompts/user.prompt",
expectedType: fileTypeCode,
},
// Agent files - should be recognized as code
{
name: "AGENTS.md file",
filename: "AGENTS.md",
expectedType: fileTypeCode,
},
{
name: "agents.md lowercase",
filename: "agents.md",
expectedType: fileTypeCode,
},
{
name: "SKILL.md file",
filename: "SKILL.md",
expectedType: fileTypeCode,
},
{
name: "skill.md lowercase",
filename: "skill.md",
expectedType: fileTypeCode,
},
{
name: "AGENTS.md in subdirectory",
filename: "docs/AGENTS.md",
expectedType: fileTypeCode,
},
// Edge cases - should NOT be recognized as prompt/code
{
name: "prompt without dot prefix (no leading dot)",
filename: "prompt.txt",
expectedType: fileTypeMetadata, // .txt is in metadataSuffixes, doesn't match .prompt pattern
},
{
name: "prompt with underscore",
filename: "my_prompt.md",
expectedType: fileTypeDocs, // .md suffix takes precedence
},
{
name: "file containing prompt in name",
filename: "prompter.py",
expectedType: fileTypeUnknown,
},
// Regular files - should use existing logic
{
name: "model file .gguf",
filename: "model.gguf",
expectedType: fileTypeModel,
},
{
name: "dataset file .csv",
filename: "data.csv",
expectedType: fileTypeDataset,
},
{
name: "docs file .md",
filename: "README.md",
expectedType: fileTypeDocs,
},
{
name: "metadata file .json",
filename: "config.json",
expectedType: fileTypeMetadata,
},
{
name: "unknown file .sh",
filename: "script.sh",
expectedType: fileTypeUnknown,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := determineFileType(tt.filename)
if result != tt.expectedType {
t.Errorf("determineFileType(%q) = %v, want %v", tt.filename, result, tt.expectedType)
}
})
}
}