package gsp import ( "fmt" "path/filepath" "strings" ) type AIInitOptions struct { Agents bool Skill string All bool Force bool } func InitAIUsage(root string, options AIInitOptions) (InitResult, error) { project, err := LoadProject(root) if err != nil { return InitResult{}, err } if project.Manifest == nil { return InitResult{}, fmt.Errorf("gsp.manifest not found; run gsp init first") } result := InitResult{Root: filepath.ToSlash(project.Root)} projectName := project.Manifest.Project if projectName == "" { projectName = filepath.Base(project.Root) } entry := "project.entry" if len(project.Manifest.Entry) > 0 && project.Manifest.Entry[0] != "" { entry = project.Manifest.Entry[0] } scan := "design" if len(project.Manifest.Scan) > 0 && project.Manifest.Scan[0] != "" { scan = project.Manifest.Scan[0] } files := []initFile{ { Path: "README.md", Data: projectReadme(projectName, scan, entry), }, { Path: "AI_USAGE.md", Data: aiUsage(project.Manifest.GSPVersion, scan), }, } if options.All || options.Agents { files = append(files, initFile{ Path: "AGENTS.md", Data: agentsUsage(), }) } skill := strings.TrimSpace(options.Skill) if options.All && skill == "" { skill = "generic" } if skill != "" { path, data, err := skillUsage(skill) if err != nil { return InitResult{}, err } files = append(files, initFile{Path: path, Data: data}) } for _, file := range files { path := filepath.Join(project.Root, filepath.FromSlash(file.Path)) if err := writeInitFile(project.Root, path, []byte(file.Data), options.Force, &result); err != nil { return InitResult{}, err } } return result, nil } type initFile struct { Path string Data string } func projectReadme(projectName, scan, entry string) string { return fmt.Sprintf(`# %s This is a GSP project. - Manifest: `+"`gsp.manifest`"+` - GSP source: `+"`%s/`"+` - Entry GSP: `+"`%s`"+` - AI rules: `+"`AI_USAGE.md`"+` `, projectName, scan, entry) } func aiUsage(gspVersion, scan string) string { if gspVersion == "" { gspVersion = DefaultGSPVersion } return fmt.Sprintf(`# AI Usage - Read `+"`gsp.manifest`"+` first. - Use GSP version `+"`%s`"+`. - Treat `+"`%s/`"+` as the default GSP source directory. - `+"`.gsp`"+` files use YAML. - Preserve `+"`id`"+`; do not rename it unless explicitly requested. - `+"`id`"+` is the unique identity of a GSP unit. - `+"`title`"+` is display text; use `+"`id`"+` when `+"`title`"+` is missing. - `+"`links`"+` associates a GSP with paths, folders, URLs, or external addresses. - Use only fields valid for the declared GSP version. - `+"`with`"+` means related design context. - `+"`refines`"+` means single-source refinement. - Empty `+"`context`"+` means placeholder. - Do not invent missing referenced GSPs silently. - Use `+"`gsp validate`"+` after editing GSP files. - Use `+"`gsp index`"+` to locate GSP units. - Use `+"`gsp trace `"+` to inspect relations. - Use `+"`gsp flatten `"+` before implementation or task splitting. - Use `+"`gsp pack `"+` when a compact AI context is needed. - Use `+"`gsp links `"+` to inspect associated files, folders, URLs, or addresses. - Use `+"`gsp impact `"+` before changing shared GSP units. - Use `+"`gsp message validate `"+` for agent communication messages. - Use `+"`gsp stage-check --stage `"+` before stage handoff. `, gspVersion, scan) } func agentsUsage() string { return `# Agent Instructions Read ` + "`AI_USAGE.md`" + ` before working on this GSP project. ` } func skillUsage(kind string) (string, string, error) { switch strings.ToLower(strings.TrimSpace(kind)) { case "generic": return "skills/gsp/SKILL.md", `# GSP Use this skill when working in a GSP project. Read ` + "`gsp.manifest`" + `, then ` + "`AI_USAGE.md`" + `. Use ` + "`gsp validate`" + `, ` + "`gsp index`" + `, and ` + "`gsp flatten `" + ` when handling GSP files. `, nil case "codex": return ".codex/skills/gsp/SKILL.md", `# GSP Use this skill when working in a GSP project. Read ` + "`gsp.manifest`" + `, then ` + "`AI_USAGE.md`" + `. Use GSP Toolkit commands before editing, handoff, implementation, or review. `, nil default: return "", "", fmt.Errorf("unsupported skill %q; use generic or codex", kind) } }