init

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

View Source
const CanaryEnd = "END"
View Source
const CanaryKey = "CANARY"
View Source
const CanaryStart = "START"
View Source
const CommentPrefix = "<!--"
View Source
const CommentSuffix = "-->"

Variables ΒΆ

View Source
var (

	// Public unnamed section markers (if needed externally for docs)
	StartMarker = gate.StartMarker("", markdownOptions...)
	EndMarker   = gate.EndMarker("", markdownOptions...)
)

Precomputed Start/End markers for unnamed CANARY section

View Source
var InitCmd = &cobra.Command{
	Use:   "init [project-name]",
	Short: "Initialize a new project with CANARY",
	Long: `Bootstrap a new project with CANARY spec-kit-inspired workflow.

Installation Modes:
  Global (default): Installs commands in ~/.claude/commands/, ~/.cursor/commands/, etc.
                    for use across all projects
  Local (--local):  Installs commands in .claude/commands/, .cursor/commands/, etc.
                    for project-specific use

Creates:
- .canary/ directory with templates, scripts, agents, and slash commands
- .canary/agents/ directory with pre-configured CANARY agent definitions
- README.md with CANARY token format specification
- GAP_ANALYSIS.md template for tracking requirements
- CLAUDE.md for AI agent integration (slash commands)

The agent files support template variables that can be customized:
  --agent-prefix: Agent name prefix (default: project key)
  --agent-model:  AI model to use (default: sonnet)
  --agent-color:  Agent color theme (default: blue)

Examples:
  canary init                  # Global install (default)
  canary init --local           # Local install in current project
  canary init myproject --local # Local install in new project`,
	RunE: func(cmd *cobra.Command, args []string) error {
		projectName := "."
		if len(args) > 0 {
			projectName = args[0]
		}

		canaryDir := filepath.Join(projectName, ".canary")
		isUpdate := false
		if _, err := os.Stat(canaryDir); err == nil {
			isUpdate = true
			fmt.Println("πŸ“¦ Existing CANARY project detected - updating...")
		}

		if projectName != "." {
			if err := os.MkdirAll(projectName, 0750); err != nil {
				return fmt.Errorf("create project dir: %w", err)
			}
		}

		projectKey, _ := cmd.Flags().GetString("key")
		projectYamlPath := filepath.Join(projectName, ".canary", "project.yaml")

		if isUpdate && projectKey == "" {
			if existingContent, err := os.ReadFile(projectYamlPath); err == nil {

				for _, line := range strings.Split(string(existingContent), "\n") {
					trimmed := strings.TrimSpace(line)
					if strings.HasPrefix(trimmed, "key:") {
						parts := strings.SplitN(trimmed, ":", 2)
						if len(parts) == 2 {
							existingKey := strings.TrimSpace(parts[1])
							existingKey = strings.Trim(existingKey, "\"' ")
							if existingKey != "" && existingKey != "{{PROJECT_KEY}}" {
								projectKey = existingKey
								fmt.Printf("πŸ“¦ Using existing project key: %s\n", projectKey)
								break
							}
						}
					}
				}
			} else {
				fmt.Printf("⚠️  Warning: Could not read project.yaml: %v\n", err)
			}
		}

		if projectKey == "" {

			fmt.Print("Enter project requirement ID prefix (e.g., CBIN, PROJ, ACME): ")
			var input string
			if _, err := fmt.Scanln(&input); err != nil {

				input = ""
			}
			projectKey = strings.TrimSpace(strings.ToUpper(input))
		}
		if projectKey == "" {
			projectKey = "PROJ"
		}

		if err := copyCanaryStructure(projectName); err != nil {
			return fmt.Errorf("copy .canary structure: %w", err)
		}

		canaryignoreContent, err := utils.ReadEmbeddedFile("base/.canaryignore")
		if err == nil {
			canaryignorePath := filepath.Join(projectName, ".canaryignore")
			if err := os.WriteFile(canaryignorePath, canaryignoreContent, 0640); err != nil {
				return fmt.Errorf("write .canaryignore: %w", err)
			}
		}

		if err := customizeProjectYaml(projectYamlPath, projectName, projectKey); err != nil {
			return fmt.Errorf("customize project.yaml: %w", err)
		}

		localInstall, _ := cmd.Flags().GetBool("local")

		agentsList, _ := cmd.Flags().GetStringSlice("agents")
		allAgents, _ := cmd.Flags().GetBool("all-agents")

		agentPrefix, _ := cmd.Flags().GetString("agent-prefix")
		agentModel, _ := cmd.Flags().GetString("agent-model")
		agentColor, _ := cmd.Flags().GetString("agent-color")

		if agentPrefix == "" {
			agentPrefix = projectKey
		}
		if agentModel == "" {
			agentModel = "sonnet"
		}
		if agentColor == "" {
			agentColor = "blue"
		}

		if err := copyAndProcessAgentFiles(projectName, agentPrefix, agentModel, agentColor); err != nil {
			return fmt.Errorf("copy agent files: %w", err)
		}

		slashCommandNotes, err := installSlashCommands(projectName, agentsList, allAgents, localInstall)
		if err != nil {
			return fmt.Errorf("install slash commands: %w", err)
		}

		if err := installAgentFilesToSystems(projectName, agentsList, allAgents, agentPrefix, agentModel, agentColor, localInstall); err != nil {
			return fmt.Errorf("install agent files to systems: %w", err)
		}

		if err := createCopilotInstructions(projectName, projectKey); err != nil {
			return fmt.Errorf("create Copilot instructions: %w", err)
		}

		if isUpdate {
			fmt.Println("πŸ”§ Rebuilding canary binary...")
			buildCmd := exec.Command("go", "build", "-ldflags=-s -w", "-o", "./bin/canary", "./cmd/canary")
			buildCmd.Stdout = os.Stdout
			buildCmd.Stderr = os.Stderr
			if err := buildCmd.Run(); err != nil {
				fmt.Printf("⚠️  Warning: Failed to rebuild canary binary: %v\n", err)
				fmt.Println("   Run 'make canary-build' or 'go build -o ./bin/canary ./cmd/canary/main.go' to rebuild manually")
			} else {
				fmt.Println("βœ… Canary binary updated")
			}
		}

		readme := "# CANARY Token Specification\n\n" +
			"## Format\n\n" +
			"CANARY tokens track requirements directly in source code:\n\n" +
			"```\n" +
			"// CANARY: REQ=CBIN-###; FEATURE=\"Name\"; ASPECT=API; STATUS=IMPL; [TEST=TestName]; [BENCH=BenchName]; [OWNER=team]; UPDATED=<YYYY-MM-DD>\n" +
			"```\n\n" +
			"## Required Fields\n\n" +
			"- **REQ**: Requirement ID (format: CBIN-###)\n" +
			"- **FEATURE**: Short feature name\n" +
			"- **ASPECT**: Category (API, CLI, Engine, Storage, etc.)\n" +
			"- **STATUS**: Implementation state\n" +
			"- **UPDATED**: Last update date (YYYY-MM-DD)\n\n" +
			"## Status Values\n\n" +
			"- **MISSING**: Planned but not implemented\n" +
			"- **STUB**: Placeholder implementation\n" +
			"- **IMPL**: Implemented\n" +
			"- **TESTED**: Implemented with tests (auto-promoted from IMPL+TEST)\n" +
			"- **BENCHED**: Tested with benchmarks (auto-promoted from TESTED+BENCH)\n" +
			"- **REMOVED**: Deprecated/removed\n\n" +
			"## Optional Fields\n\n" +
			"- **TEST**: Test function name (promotes IMPL β†’ TESTED)\n" +
			"- **BENCH**: Benchmark function name (promotes TESTED β†’ BENCHED)\n" +
			"- **OWNER**: Team/person responsible\n\n" +
			"## Example\n\n" +
			"```go\n" +
			"// CANARY: REQ=CBIN-001; FEATURE=\"UserAuth\"; ASPECT=API; STATUS=TESTED; TEST=TestUserAuth; OWNER=backend; UPDATED=2025-10-16\n" +
			"func AuthenticateUser(credentials *Credentials) (*Session, error) {\n" +
			"    // implementation\n" +
			"}\n" +
			"```\n\n" +
			"## Usage\n\n" +
			"```bash\n" +
			"# Scan for tokens and generate reports\n" +
			"canary scan --root . --out status.json --csv status.csv\n\n" +
			"# Verify GAP_ANALYSIS.md claims\n" +
			"canary scan --root . --verify GAP_ANALYSIS.md\n\n" +
			"# Check for stale tokens (30-day threshold)\n" +
			"canary scan --root . --strict\n\n" +
			"# Auto-update stale TESTED/BENCHED tokens\n" +
			"canary scan --root . --update-stale\n" +
			"```\n"
		readmePath := filepath.Join(projectName, "README_CANARY.md")
		if err := os.WriteFile(readmePath, []byte(readme), 0640); err != nil {
			return fmt.Errorf("write README: %w", err)
		}

		gap := "# Requirements Gap Analysis\n\n" +
			"## Claimed Requirements\n\n" +
			"List requirements that are fully implemented and verified:\n\n" +
			"βœ… CBIN-001 - UserAuth API fully tested\n" +
			"βœ… CBIN-002 - DataValidation with benchmarks\n\n" +
			"## Gaps\n\n" +
			"List requirements that are planned or in progress:\n\n" +
			"- [ ] CBIN-003 - ReportGeneration (STATUS=IMPL, needs tests)\n" +
			"- [ ] CBIN-004 - CacheOptimization (STATUS=STUB)\n\n" +
			"## Verification\n\n" +
			"Run verification with:\n\n" +
			"```bash\n" +
			"canary scan --root . --verify GAP_ANALYSIS.md\n" +
			"```\n\n" +
			"This will:\n" +
			"- βœ… Verify claimed requirements are TESTED or BENCHED\n" +
			"- ❌ Fail with exit code 2 if claims are overclaimed\n"
		gapPath := filepath.Join(projectName, "GAP_ANALYSIS.md")
		if err := os.WriteFile(gapPath, []byte(gap), 0640); err != nil {
			return fmt.Errorf("write GAP_ANALYSIS.md: %w", err)
		}

		if err := updateAgentContextFiles(projectName); err != nil {
			return fmt.Errorf("update agent context files: %w", err)
		}

		if isUpdate {
			fmt.Printf("\nβœ… Updated CANARY project in: %s\n\n", projectName)
			fmt.Println("Updated:")
		} else {
			fmt.Printf("\nβœ… Initialized CANARY project in: %s\n\n", projectName)
			fmt.Println("Created:")
		}
		fmt.Println("  βœ… .canary/ - Full workflow structure")
		fmt.Println("     β”œβ”€β”€ agents/ - Pre-configured CANARY agent definitions")
		fmt.Println("     β”œβ”€β”€ memory/constitution.md - Project principles")
		fmt.Println("     β”œβ”€β”€ scripts/ - Automation scripts")
		fmt.Println("     β”œβ”€β”€ templates/ - Spec/plan templates")
		fmt.Println("     └── templates/commands/ - Slash commands for AI agents")

		if localInstall {
			fmt.Println("  βœ… Agent Files - Installed LOCALLY in project directory")
		} else {
			homeDir, _ := os.UserHomeDir()
			fmt.Printf("  βœ… Agent Files - Installed GLOBALLY in %s\n", homeDir)
		}

		agentDirs := map[string]string{
			".claude":    "Claude Code",
			".cursor":    "Cursor",
			".github":    "GitHub Copilot",
			".windsurf":  "Windsurf",
			".kilocode":  "Kilocode",
			".roo":       "Roo",
			".opencode":  "opencode",
			".codex":     "Codex",
			".augment":   "Auggie",
			".codebuddy": "CodeBuddy",
			".amazonq":   "Amazon Q Developer",
		}

		checkDir := projectName
		if !localInstall {

			if homeDir, err := os.UserHomeDir(); err == nil {
				checkDir = homeDir
			}
		}

		installedAgents := []string{}
		for dir, name := range agentDirs {
			if _, err := os.Stat(filepath.Join(checkDir, dir)); err == nil {
				installedAgents = append(installedAgents, name)
			}
		}

		if len(installedAgents) > 0 {
			fmt.Printf("  βœ… AI Agent Integration (%d systems configured):\n", len(installedAgents))
			for _, agent := range installedAgents {
				fmt.Printf("     β€’ %s\n", agent)
			}
			for _, note := range slashCommandNotes {
				fmt.Printf("     β€’ %s\n", note)
			}
		}

		if !isUpdate {
			fmt.Println("  βœ… README_CANARY.md - Token format specification")
			fmt.Println("  βœ… GAP_ANALYSIS.md - Requirements tracking template")
			fmt.Println("  βœ… AGENTS.md - Codex / repository instructions")
			fmt.Println("  βœ… CLAUDE.md - Claude Code / Claude plugins")
			fmt.Println("  βœ… CURSOR.md - Cursor IDE / Cursor plugins")
			fmt.Println("  βœ… .cursor/rules/canary-requirements.mdc - Cursor rule (apply when editing requirements)")
			fmt.Println("  βœ… .cursor/mcp.json - Optional MCP (run `canary mcp` then use Cursor MCP tools)")
		}

		fmt.Print(`
Available Slash Commands for AI Agents:
	/canary.constitution - Create/update project principles
	/canary.specify - Create requirement specification
	/canary.plan - Generate implementation plan
	/canary.scan - Scan for CANARY tokens
	/canary.verify - Verify GAP_ANALYSIS.md claims
	/canary.update-stale - Update stale tokens
Next Steps:
	1. Open in AI agent (Claude Code, Cursor, etc.)
	2. Run: /canary.constitution to establish principles
	3. Run: /canary.specify "your feature description"
	4. Follow the spec-driven workflow!
`)

		return nil
	},
}

InitCmd bootstraps a new project with CANARY token conventions

Functions ΒΆ

func CreateCopilotInstructions ΒΆ

func CreateCopilotInstructions(projectName, projectKey string) error

CreateCopilotInstructions is an exported wrapper used by tests and higher-level callers.

func ReadDir ΒΆ

func ReadDir(path string) ([]fs.DirEntry, error)

ReadDir exposes ReadDir on the embedded filesystem

func ReadFile ΒΆ

func ReadFile(path string) ([]byte, error)

ReadFile exposes reading from the embedded filesystem

func WalkDir ΒΆ

func WalkDir(root string, fn fs.WalkDirFunc) error

WalkDir exposes fs.WalkDir over the embedded filesystem

Types ΒΆ

type AgentConfig ΒΆ

type AgentConfig struct {
	Dir    string // Directory for agent files
	Prefix string // Prefix for command files (e.g., "canary.")
}

CANARY: REQ=ENG-4300; FEATURE="InitWorkflow"; ASPECT=CLI; STATUS=IMPL; OWNER=canary; UPDATED=2025-10-16 AgentConfig defines configuration for each supported AI agent

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL