Skip to content

Commit f49556a

Browse files
feat: Add Copilot instruction creator
Co-authored-by: benji <benji@codepros.org>
1 parent 4948673 commit f49556a

File tree

4 files changed

+98
-3
lines changed

4 files changed

+98
-3
lines changed

cli/deps/deps_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func TestDepsValidateCommand_DetectsCycle(t *testing.T) {
205205

206206
// CANARY: REQ=CBIN-147; FEATURE="DepsParentCommand"; ASPECT=CLI; STATUS=TESTED; TEST=TestDepsParentCommand; UPDATED=2025-10-18
207207
func TestDepsParentCommand(t *testing.T) {
208-
cmd := createDepsCommand()
208+
cmd := CreateDepsCommand()
209209

210210
assert.NotNil(t, cmd)
211211
assert.Equal(t, "deps", cmd.Use)

cli/migrate/migrate_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ package migrate
1010
import (
1111
"os"
1212
"path/filepath"
13+
"strings"
1314
"testing"
1415

1516
"go.devnw.com/canary/internal/migrate"
1617
"go.devnw.com/canary/internal/storage"
1718
)
1819

20+
// Helper function for string containment checks
21+
func contains(s, substr string) bool {
22+
return strings.Contains(s, substr)
23+
}
24+
1925
// TestCANARY_CBIN_145_CLI_OrphanDetection verifies detection of orphaned requirements
2026
func TestCANARY_CBIN_145_CLI_OrphanDetection(t *testing.T) {
2127
tmpDir := t.TempDir()

cli/project/copilot.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2025 by Developer Network.
2+
//
3+
// For more details, see the LICENSE file in the root directory of this
4+
// source code repository or contact Developer Network at info@devnw.com.
5+
6+
package project
7+
8+
import (
9+
"fmt"
10+
"os"
11+
"path/filepath"
12+
"text/template"
13+
14+
"go.devnw.com/canary/cli/internal/utils"
15+
)
16+
17+
// CANARY: REQ=CBIN-148; FEATURE="CopilotInstructionCreator"; ASPECT=CLI; STATUS=BENCHED; TEST=TestCreateCopilotInstructions; BENCH=BenchmarkCreateCopilotInstructions; UPDATED=2025-10-19
18+
// createCopilotInstructions generates GitHub Copilot instruction files for the project
19+
func createCopilotInstructions(projectPath, projectKey string) error {
20+
instructionsDir := filepath.Join(projectPath, ".github", "instructions")
21+
22+
// Create .github/instructions/ directory structure
23+
if err := os.MkdirAll(instructionsDir, 0755); err != nil {
24+
return fmt.Errorf("create .github/instructions: %w", err)
25+
}
26+
27+
// Define instruction files to create
28+
instructionFiles := map[string]string{
29+
// Repository-wide instruction
30+
"repository.md": "base/copilot/repository.md",
31+
32+
// Path-specific instructions (nested directories)
33+
".canary/specs/instruction.md": "base/copilot/specs.md",
34+
".canary/instruction.md": "base/copilot/canary.md",
35+
"tests/instruction.md": "base/copilot/tests.md",
36+
}
37+
38+
// Template data for variable substitution
39+
type TemplateData struct {
40+
ProjectKey string
41+
}
42+
data := TemplateData{ProjectKey: projectKey}
43+
44+
for targetPath, templatePath := range instructionFiles {
45+
fullTargetPath := filepath.Join(instructionsDir, targetPath)
46+
47+
// Check if file already exists (preserve user customizations)
48+
if _, err := os.Stat(fullTargetPath); err == nil {
49+
// Silently skip existing files to avoid noise in tests
50+
continue
51+
}
52+
53+
// Create parent directories for path-specific instructions
54+
if err := os.MkdirAll(filepath.Dir(fullTargetPath), 0755); err != nil {
55+
return fmt.Errorf("create directory for %s: %w", targetPath, err)
56+
}
57+
58+
// Read template from embedded filesystem
59+
templateContent, err := utils.ReadEmbeddedFile(templatePath)
60+
if err != nil {
61+
return fmt.Errorf("read template %s: %w", templatePath, err)
62+
}
63+
64+
// Parse and execute template
65+
tmpl, err := template.New(targetPath).Parse(string(templateContent))
66+
if err != nil {
67+
return fmt.Errorf("parse template %s: %w", templatePath, err)
68+
}
69+
70+
// Write to file
71+
outFile, err := os.Create(fullTargetPath)
72+
if err != nil {
73+
return fmt.Errorf("create file %s: %w", fullTargetPath, err)
74+
}
75+
76+
if err := tmpl.Execute(outFile, data); err != nil {
77+
outFile.Close()
78+
return fmt.Errorf("execute template %s: %w", templatePath, err)
79+
}
80+
outFile.Close()
81+
}
82+
83+
return nil
84+
}
85+
86+
// readEmbeddedFile is a wrapper around utils.ReadEmbeddedFile for testing
87+
func readEmbeddedFile(path string) ([]byte, error) {
88+
return utils.ReadEmbeddedFile(path)
89+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package test
2-
// CANARY: REQ=CBIN-001; FEATURE="StaleToken"; ASPECT=API; STATUS=TESTED; TEST=Test1; UPDATED=2025-10-30
2+
// CANARY: REQ=CBIN-001; FEATURE="StaleToken"; ASPECT=API; STATUS=TESTED; TEST=Test1; UPDATED=2025-11-01
33
// CANARY: REQ=CBIN-002; FEATURE="FreshToken"; ASPECT=CLI; STATUS=TESTED; TEST=Test2; UPDATED=2025-10-15
44
// CANARY: REQ=CBIN-003; FEATURE="StaleImplNotUpdated"; ASPECT=Engine; STATUS=IMPL; UPDATED=2024-01-01
5-
// CANARY: REQ=CBIN-004; FEATURE="StaleBenchedToken"; ASPECT=Storage; STATUS=BENCHED; BENCH=Bench4; UPDATED=2025-10-30
5+
// CANARY: REQ=CBIN-004; FEATURE="StaleBenchedToken"; ASPECT=Storage; STATUS=BENCHED; BENCH=Bench4; UPDATED=2025-11-01

0 commit comments

Comments
 (0)