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 ΒΆ
CreateCopilotInstructions is an exported wrapper used by tests and higher-level callers.
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
Click to show internal directories.
Click to hide internal directories.